-
Notifications
You must be signed in to change notification settings - Fork 0
Feature: Simulated annealing optimization #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,8 +2,8 @@ | |||||||||||
|
|
||||||||||||
| import com.bobrust.util.data.AppConstants; | ||||||||||||
|
|
||||||||||||
| import java.util.ArrayList; | ||||||||||||
| import java.util.List; | ||||||||||||
| import java.util.concurrent.ThreadLocalRandom; | ||||||||||||
|
|
||||||||||||
| class HillClimbGenerator { | ||||||||||||
| private static State getBestRandomState(List<State> random_states) { | ||||||||||||
|
|
@@ -14,59 +14,162 @@ private static State getBestRandomState(List<State> random_states) { | |||||||||||
| state.shape.randomize(); | ||||||||||||
| } | ||||||||||||
| random_states.parallelStream().forEach(State::getEnergy); | ||||||||||||
|
|
||||||||||||
| float bestEnergy = 0; | ||||||||||||
| State bestState = null; | ||||||||||||
| for (int i = 0; i < len; i++) { | ||||||||||||
| State state = random_states.get(i); | ||||||||||||
| float energy = state.getEnergy(); | ||||||||||||
|
|
||||||||||||
| if (bestState == null || energy < bestEnergy) { | ||||||||||||
| bestEnergy = energy; | ||||||||||||
| bestState = state; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return bestState; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| public static State getHillClimb(State state, int maxAge) { | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Original hill climbing implementation. Kept as fallback when | ||||||||||||
| * {@link AppConstants#USE_SIMULATED_ANNEALING} is false. | ||||||||||||
| */ | ||||||||||||
| public static State getHillClimbClassic(State state, int maxAge) { | ||||||||||||
| float minimumEnergy = state.getEnergy(); | ||||||||||||
|
|
||||||||||||
| // Prevent infinite recursion | ||||||||||||
| int maxLoops = 4096; | ||||||||||||
|
|
||||||||||||
| State undo = state.getCopy(); | ||||||||||||
|
|
||||||||||||
| // This function will minimize the energy of the input state | ||||||||||||
| for (int i = 0; i < maxAge && (maxLoops-- > 0); i++) { | ||||||||||||
| state.doMove(undo); | ||||||||||||
| float energy = state.getEnergy(); | ||||||||||||
|
|
||||||||||||
| if (energy >= minimumEnergy) { | ||||||||||||
| state.fromValues(undo); | ||||||||||||
| } else { | ||||||||||||
| minimumEnergy = energy; | ||||||||||||
| i = -1; | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| if (maxLoops <= 0 && AppConstants.DEBUG_GENERATOR) { | ||||||||||||
| AppConstants.LOGGER.warn("HillClimbGenerator failed to find a better shape after {} tries", 4096); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| return state; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Simulated annealing implementation that can escape local minima by | ||||||||||||
| * probabilistically accepting worse moves early in the search. | ||||||||||||
| */ | ||||||||||||
| public static State getHillClimbSA(State state, int maxAge) { | ||||||||||||
| float currentEnergy = state.getEnergy(); | ||||||||||||
| State bestState = state.getCopy(); | ||||||||||||
| float bestEnergy = currentEnergy; | ||||||||||||
|
|
||||||||||||
| // Estimate initial temperature from sample mutations | ||||||||||||
| float temperature = estimateTemperature(state); | ||||||||||||
| int totalIterations = maxAge * 10; | ||||||||||||
| float coolingRate = computeCoolingRate(temperature, maxAge); | ||||||||||||
|
|
||||||||||||
| State undo = state.getCopy(); | ||||||||||||
|
|
||||||||||||
| for (int i = 0; i < totalIterations; i++) { | ||||||||||||
| state.doMove(undo); | ||||||||||||
| float newEnergy = state.getEnergy(); | ||||||||||||
| float delta = newEnergy - currentEnergy; | ||||||||||||
|
|
||||||||||||
| if (delta < 0) { | ||||||||||||
| // Improvement — always accept | ||||||||||||
| currentEnergy = newEnergy; | ||||||||||||
| if (currentEnergy < bestEnergy) { | ||||||||||||
| bestEnergy = currentEnergy; | ||||||||||||
| bestState = state.getCopy(); | ||||||||||||
| } | ||||||||||||
| } else if (temperature > 0.001f) { | ||||||||||||
| // Worse move — accept with probability exp(-delta/T) | ||||||||||||
| double acceptProb = Math.exp(-delta / temperature); | ||||||||||||
| if (ThreadLocalRandom.current().nextDouble() < acceptProb) { | ||||||||||||
| currentEnergy = newEnergy; | ||||||||||||
| } else { | ||||||||||||
| state.fromValues(undo); | ||||||||||||
| } | ||||||||||||
| } else { | ||||||||||||
| state.fromValues(undo); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| temperature *= coolingRate; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // Return the best state found during the entire SA run | ||||||||||||
| return bestState; | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Dispatches to SA or classic hill climbing based on the feature flag. | ||||||||||||
| */ | ||||||||||||
| public static State getHillClimb(State state, int maxAge) { | ||||||||||||
| if (AppConstants.USE_SIMULATED_ANNEALING) { | ||||||||||||
| return getHillClimbSA(state, maxAge); | ||||||||||||
| } else { | ||||||||||||
| return getHillClimbClassic(state, maxAge); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Estimate a good starting temperature by sampling random mutations and | ||||||||||||
| * measuring average energy deltas. Sets T so that roughly 60% of uphill | ||||||||||||
| * moves are accepted at the start. | ||||||||||||
| */ | ||||||||||||
| static float estimateTemperature(State state) { | ||||||||||||
| State probe = state.getCopy(); | ||||||||||||
| State undo = probe.getCopy(); | ||||||||||||
| float totalDelta = 0; | ||||||||||||
| int samples = 30; | ||||||||||||
|
|
||||||||||||
| for (int i = 0; i < samples; i++) { | ||||||||||||
| float before = probe.getEnergy(); | ||||||||||||
| probe.doMove(undo); | ||||||||||||
| float after = probe.getEnergy(); | ||||||||||||
| totalDelta += Math.abs(after - before); | ||||||||||||
| probe.fromValues(undo); // restore | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| float avgDelta = totalDelta / samples; | ||||||||||||
| // Set T so ~60% of uphill moves are accepted initially | ||||||||||||
| // P = exp(-avgDelta / T) = 0.6 => T = -avgDelta / ln(0.6) | ||||||||||||
| // -1/ln(0.6) ≈ 1.957, but we use avgDelta / 0.5108 which is equivalent | ||||||||||||
| return (float) (avgDelta / 0.5108); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Compute the geometric cooling rate so that temperature decays from | ||||||||||||
| * {@code initialTemp} to near-zero (0.001) over {@code maxAge * 10} iterations. | ||||||||||||
| */ | ||||||||||||
| static float computeCoolingRate(float initialTemp, int maxAge) { | ||||||||||||
| int totalIterations = maxAge * 10; | ||||||||||||
| float finalTemp = 0.001f; | ||||||||||||
|
||||||||||||
| float finalTemp = 0.001f; | |
| float finalTemp = 0.001f; | |
| if (maxAge <= 0 || totalIterations <= 0) { | |
| return 1.0f; // no cooling when iteration budget is invalid | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -56,7 +56,7 @@ private void addShape(Circle shape) { | |||||||||
|
|
||||||||||
| private static final int max_random_states = 1000; | ||||||||||
| private static final int age = 100; | ||||||||||
| private static final int times = 1; | ||||||||||
| private static final int times = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); | ||||||||||
|
||||||||||
| private static final int times = Math.max(1, Runtime.getRuntime().availableProcessors() / 2); | |
| private static final int max_times = 8; | |
| private static final int times = Math.min(max_times, | |
| Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
estimateTemperatureuses the magic constant0.5108(approx-ln(0.6)) which makes the math harder to audit and invites drift from the comment. Consider computing it fromMath.log(0.6)(or declaring a named constant) to keep the intent precise and self-documenting.