Console, Csv, and DataFrame trackers and their Nested* variantsSolverPortfolio racing approachProblemDefinition, a ModelBuilderIfc, and an EvaluatorIfc.Solver companion object provides create... factory functions that build a local Evaluator and wire in a MemorySolutionCache() for you.printer function to react to progress, construct the solver and attach a tracker.val problemDefinition = makeRQInventoryModelProblemDefinition()
val modelBuilder = BuildRQModel
val solver = Solver.createCrossEntropySolver(
problemDefinition = problemDefinition,
modelBuilder = modelBuilder,
maxIterations = 100,
replicationsPerEvaluation = 50
)
val tracker = ConsoleSolverStateTracker(solver)
tracker.startTracking()
solver.runAllIterations()ksl.simopt.solvers.trackers package. It attaches itself as an observer to a solver and reacts automatically to the two kinds of events every Solver emits:
SolverStateSnapshot — the iteration number, cumulative oracle calls and replications requested so far, and the current and best solutions found so farSolverStatus — INITIALIZED, STARTED, COMPLETED, or ERRORAbstractSolverStateTracker, which handles attaching to and detaching from the two emitters.
The ksl.simopt.solvers.trackers package currently provides three trackers, differing only in where each snapshot goes:
ConsoleSolverStateTracker — prints each snapshot and life-cycle transition to the console. This is the tracker used in the running example.CsvSolverStateTracker — appends each snapshot to a CSV file, managing the file across multiple solver runs.DataFrameSolverStateTracker — accumulates snapshots in memory and compiles them into a Kotlin DataFrame for further analysis.All three inherit the same attach/detach machinery from AbstractSolverStateTracker; they differ only in their reaction to a snapshot.
startTracking() before runAllIterations().startTracking() attaches the tracker to the solver’s iterationEmitter and lifeCycleEmitter; calling it again on an already-tracking tracker has no effect.stopTracking() detaches the tracker if you want to silence it before the solver finishes.runAllIterations() returns.snapShotFrequency property (default 1, i.e. every iteration) controls how often iteration snapshots are emitted.>>> SOLVER INITIALIZED: Baseline captured.
[Iter: 0 | Oracle: 0] EstObj: 1.79769E+308 | PenObj: 1.79769E+308 | Cur (q,r)=(-2.15e9, -2.15e9)
>>> SOLVER STARTED: Beginning iterations...
[Iter: 1 | Oracle: 34] EstObj: 13.7967 | PenObj: 13.7967 | Cur (q,r)=(1,11) | Best (q,r)=(1,11)
[Iter: 2 | Oracle: 67] EstObj: 9.06001 | PenObj: 9.06001 | Cur (q,r)=(10,5) | Best (q,r)=(10,5)
[Iter: 7 | Oracle: 238] EstObj: 8.45271 | PenObj: 8.45271 | Cur (q,r)=(15,2) | Best (q,r)=(15,2)
. . .
[Iter: 40 | Oracle: 1129] EstObj: 4.61628 | PenObj: 4.61628 | Cur (q,r)=(4,3) | Best (q,r)=(4,3)
INITIALIZED baseline starts from the problem’s “bad solution” sentinel; the solution then improves over the first few iterations.q = 4, reorder point r = 3.Input-variable names abbreviated (q = reorder quantity, r = reorder point); sentinel values shortened; lines elided.
RandomRestartSolver, are really two solvers working together: an outer solver that manages the restarts and an inner solver that searches between restarts.NestedConsoleSolverStateTrackerNestedCsvSolverStateTrackerNestedDataFrameSolverStateTrackerMACRO line per completed restart (its cumulative oracle calls and best point so far), with each of that restart’s own iterations printed as an indented MICRO line beneath it.
createRandomRestart<Family>Solver factory. For simulated annealing:fun createRandomRestartSimulatedAnnealingSolver(
problemDefinition: ProblemDefinition,
modelBuilder: ModelBuilderIfc,
maxNumRestarts: Int = defaultMaxRestarts,
temperatureConfiguration: TemperatureConfiguration = TemperatureConfiguration.AutoCalibrate(),
coolingSchedule: CoolingScheduleIfc = ExponentialCoolingSchedule(defaultInitialTemperature),
stoppingTemperature: Double = SimulatedAnnealing.defaultStoppingTemperature,
maxIterations: Int = defaultMaxNumberIterations,
replicationsPerEvaluation: Int = defaultReplicationsPerEvaluation,
solutionCache: SolutionCacheIfc = MemorySolutionCache(),
concurrentRestarts: Int = 1
): RandomRestartSolvertemperatureConfiguration is TemperatureConfiguration.AutoCalibrate(), not a fixed value: each restart calibrates its own starting temperature to the landscape around its own randomly chosen starting point.concurrentRestarts (default 1, sequential) can be raised to run restarts at the same time.val solver = Solver.createRandomRestartSimulatedAnnealingSolver(
problemDefinition = problemDefinition,
modelBuilder = modelBuilder,
maxIterations = 100,
replicationsPerEvaluation = 50
)
val tracker = NestedConsoleSolverStateTracker(solver, solver.restartingSolver)
tracker.startTracking()
solver.runAllIterations()
solver.printResults()solver is the outer, restart-managing solver; solver.restartingSolver is the inner SimulatedAnnealing instance that actually searches between restarts.NestedConsoleSolverStateTracker attaches to both at once — the exact composite case the nested trackers were built for.makeRQInventoryModelProblemDefinition() and BuildRQModel from the earlier example.>>> EXPERIMENT INITIALIZED: DefaultExperiment (Run 1)
[Run 1 | MACRO: 0] Total Oracle Calls: 1 | Best EstObj: 38.95 | Best (q,r)=(14,33)
>>> MACRO EXPERIMENT STARTED
-> [Micro: 0] EstObj: 97.82 | PenObj: 97.82 | Cur (q,r)=(32,83)
-> [Micro: 1] EstObj: 97.82 | PenObj: 97.82 | Cur (r,q)=(83,88)
. . .
[Run 1 | MACRO: 1] Total Oracle Calls: 132 | Best EstObj: 16.90 | Best (r,q)=(9,18)
. . .
[Run 1 | MACRO: 4] Total Oracle Calls: 535 | Best EstObj: 11.92 | Best (r,q)=(10,5)
MACRO best estimated objective improves across restarts: 38.95 → 16.90 → … → 11.92.4, reorder quantity 9 with the fill-rate constraint met — slightly worse than the cross-entropy result.Input-variable names abbreviated; the tuple label order (q,r) vs (r,q) follows the book’s trace; lines elided.
Solver.createGeneticAlgorithmSolver(...).Solver.createParticleSwarmSolver(...).Solver.createBayesianOptimizationSolver(...).Solver.createISCSolver(...).SolverPortfolioSolverPortfolio class offers a different kind of choice: rather than committing to one algorithm, race several at once on the same problem.Console / Csv / DataFrame, or the Nested* variants for composite solvers like RandomRestartSolver. A snapshot is only emitted while something is listening.SolverPortfolio and confirm the winner under CRN