RunOptimizationInLoop — Stopping a Solver Stage After N Loops
In advanced scenarios you may want to run the optimizer in a controlled loop, for example:
- run a fixed number of loops in a specific stage (e.g., Genetic phase),
- inspect intermediate results or metrics after each loop,
- stop early once you reached a quality threshold,
- chain the run into an application-specific pipeline.
RunOptimizationInLoopExample demonstrates a clean technique: use progress callbacks to count loops and programmatically jump to the next stage after a chosen number of loops.
References
- Example source: RunOptimizationInLoopExample.java
What the example does
Goal
The example stops the Genetic Algorithm stage after three loops, then continues with the default flow.
Implementation intent in the code:
MAX_LOOP_COUNT_GE = 3
How the genetic stage keeps looping
Before the run starts, setGeneticPhase2LoopMode(...) sets the start mode of the two algorithms:
OptimizationAlgorithmStart modeSA = OptimizationAlgorithmStart.STATE_MODE_BATCH;
OptimizationAlgorithmStart modeGE = OptimizationAlgorithmStart.STATE_MODE_CONTINIOUS;
opti.setOptimizationAlgorithmStart(modeSA, modeGE);
Simulated annealing runs in batch mode, that is, for its configured number of iterations. The genetic stage runs in continuous mode: it runs until it is stopped (the toString() of the example: "Run an optimization in a loop until it is stopped externally."). The loop counter described below is that stop.
How it counts loops
The example increments a counter based on progress events:
- It listens to
onProgress(IOptimizationProgress rapoptProgress). - It checks for:
optimizationStage == 2(genetic phase),progress == 99.0(near end of a loop).
When both conditions match:
currentLoopCount++
This effectively means:
- “Count one loop each time the genetic stage reports 99.0% progress.”
How it stops the stage
When the counter reaches the configured threshold, the example calls:
jumpToNextStage();
This instructs the solver pipeline to:
- stop the current stage and proceed to the next stage.
The output prints:
- “Done with loop: X of 3”
- “Max loop count reached. Stopping stage...”
Why this pattern is useful
1) Deterministic compute budgeting
Instead of relying on:
- wall clock time,
- or a global generation count, you can budget compute per stage, e.g.:
- “Run GE exactly 3 cycles.”
- “Run SA exactly 1 cycle.”
- “Stop after N construction attempts.”
2) Stage-specific control
Some instances respond well to:
- a short SA run,
- then more GE, or vice versa.
Stage loop control allows you to align compute with:
- problem structure,
- business deadlines,
- or runtime SLAs.
3) Hook point for custom monitoring and decision rules
Once you have a loop boundary, you can:
- capture intermediate KPIs,
- track best-so-far improvements,
- stop early if improvement stalls,
- implement escalation logic (e.g., enable AutoFilter, change weights, add more SA).
Understanding “stage” and “progress” in this example
rapoptProgress.getOptimizationStage()
Stages are indexed numerically in progress events.
In this example:
- stage
2corresponds to the genetic phase (as indicated by the comment in the code).
rapoptProgress.getProgress()
Progress is reported as a percentage-like number.
The example uses:
99.0to detect “end of loop”.
Important nuance:
- This is an implementation coupling to the solver’s progress reporting behavior.
- It is intentionally simple and works well for examples and many production cases.
- For mission-critical systems, you may want to additionally guard against:
- repeated 99.0 events,
- stage transitions,
- or use a more explicit “loop end” indicator if your architecture provides one.
Relationship to standard exit conditions
The example still configures normal exit conditions:
JOptExitCondition.JOptGenerationCount = 5000JOpt.Algorithm.PreOptimization.SA.NumIterations = 100000JOpt.Algorithm.PreOptimization.SA.NumRepetitions = 1
These remain relevant because:
- the generation count and the SA settings still configure the two algorithms,
- the simulated annealing stage runs in batch mode, for its configured number of iterations.
Because the genetic stage runs in continuous mode, the loop counter and jumpToNextStage() end it in this example.
Think of “loop run” as an additional control layer:
- ExitCondition limits the maximum,
- Loop control can stop earlier under your logic.
Recommended production patterns
Pattern A — Stop after N loops and export intermediate results
After each loop boundary:
- export
toOverviewResult()for telemetry, - optionally export KML for debugging,
- persist “best so far” for fail-safe.
Pattern B — Adaptive stopping based on improvement
Replace the hard loop limit with:
- “Stop if best cost improved less than X% over the last N loops.”
You can implement this by:
- capturing
rapoptProgressdata, - reading best result snapshots (if available),
- and calling
jumpToNextStage()once the improvement stalls.
Pattern C — Progressive tightening
You can run early loops with:
- relaxed weights,
- relaxed AutoFilter settings, then tighten them after N loops.
This is often effective when you want:
- quick feasibility first,
- then quality improvements.
Pitfalls and how to avoid them
Pitfall 1 — Using a fragile loop boundary signal
The example uses (stage == 2 && progress == 99.0).
If you find in your environment that:
- progress reports multiple times at 99.0,
- or progress granularity changes, then add a guard, for example:
- remember last seen progress per stage,
- increment only on transition into 99.0,
- or require a time delta.
Pitfall 2 — Stopping too early
Stopping GE too early may yield:
- a feasible but low-quality plan.
Mitigation:
- combine loop control with KPI-based stopping,
- or allow fallback: if result quality is poor, run additional loops.
Pitfall 3 — Misinterpreting stages
Stage numbering is framework-specific.
Do not hard-code stage indices without confirming which stage you are controlling in your integration.
In this example, stage 2 is explicitly commented as the genetic phase.
Summary
- The example shows how to run optimization in a controlled loop by using progress callbacks.
- It counts “loops” of the genetic stage by watching for
stage == 2andprogress == 99.0. - After
MAX_LOOP_COUNT_GEloops (3), it callsjumpToNextStage()to exit the stage early. - This pattern is valuable for deterministic compute budgeting, stage-specific control, and adaptive optimization pipelines.
Return To Start (Return2Start) — Insert Mandatory Depot Returns Between Visits
Return To Start (often referred to as Return2Start) is a routing feature that forces a resource to return to its start location (home/depot) between visits.
Wait On Early Arrival (First Node) — Prevent “Working Before Shift Start” Only at Route Start
In time-window routing, there are two common interpretations when a resource arrives early: