Migration Guide
Migrate older integrations to the current generic `timetable-sa` solver API.
Migration Guide
This guide helps you migrate from older, more domain-specific usage patterns to the current generic solver API. The modern package surface is intentionally more abstract: the library owns the search engine, while you own the domain model, constraints, and move operators.
Migrate constructor usage
Older usage patterns commonly passed domain collections directly into the solver constructor.
Old pattern:
new SimulatedAnnealing(rooms, lecturers, classes, config)Current pattern:
new SimulatedAnnealing(initialState, constraints, moveGenerators, config)The conceptual shift is important. You now encode domain logic explicitly in
TState, Constraint<TState>, and MoveGenerator<TState>.
Migrate to async solving
solve() is asynchronous and returns Promise<Solution<TState>>.
Update call sites accordingly.
const result = await solver.solve();If you previously treated solving as synchronous, you may need to propagate
async through service, controller, or CLI layers.
Migrate the constraint score contract
The current solver expects Constraint.evaluate(state) to return a finite
satisfaction score in [0, 1].
1means satisfied,0means violated,- intermediate values mean partial satisfaction.
If older code used penalty-style semantics where larger numbers meant worse states, you must invert or normalize that logic.
Migrate move-generator assumptions
The current engine clones state before calling generate(...). That means move
generators may mutate the provided working state directly.
This differs from designs where move operators are expected to deep-clone their input on every call.
Migrate progress callbacks
If you use onProgress, make the callback mode explicit when behavior matters.
onProgressMode: 'await' | 'fire-and-forget'Also note that the callback receives state = null in the current
implementation for performance reasons.
Do not throw from onProgress to stop the solver. Callback errors are still
caught and logged for backward compatibility, so cancellation must use
cancelSignal.
Migrate cancellation handling
Use SAConfig.cancelSignal when application code must stop a running solve.
The field accepts an AbortController.signal or any object with an aborted
boolean property.
const controller = new AbortController();
const solver = new SimulatedAnnealing(initialState, constraints, moves, {
...config,
cancelSignal: controller.signal,
});
controller.abort();When the signal is aborted, solve() rejects with SolveCancelledError and
does not create a final Solution.
Migrate Phase 1.5 start-temperature assumptions
The current branch no longer defaults to restarting Phase 1.5 from
initialTemperature.
Instead, the default behavior is:
intensificationStartTemperatureMode: 'phase1-end',- optional scaling through
intensificationStartTempMultiplier, - optional capping through
intensificationStartTempCapRatio.
If your older tuning assumed the legacy restart behavior, make it explicit.
intensificationStartTemperatureMode: 'initial-reset'This preserves the old mental model of each intensification attempt beginning
from the configured initialTemperature.
Migrate operator targeting assumptions
Older mental models often described Phase 1 and Phase 1.5 as preferring operators whose names merely looked like repair operators. The current API exposes an explicit targeting surface.
const noRoomConflict: Constraint<TimetableState> = {
name: 'No room conflict',
key: 'no_room_conflict',
type: 'hard',
evaluate: evaluateRoomConflicts,
};
const fixRoomConflict: MoveGenerator<TimetableState> = {
name: 'Fix room conflict',
targetConstraintTypes: ['hard'],
targetConstraintKeys: ['no_room_conflict'],
canApply: canRepairRooms,
generate: repairRoomConflict,
};The metadata fields are optional, so existing constraints and move generators remain compatible. Add them when you want predictable hard-repair targeting.
You can still use config-level explicit names:
intensificationTargetedOperatorNames: ['Repair hard conflict']Matching is case-insensitive, but it is still an exact name match. For targeting
that survives display-name changes, prefer stable Constraint.key values and
matching MoveGenerator.targetConstraintKeys instead of substring assumptions.
Migrate Phase 1.5 budget assumptions
The current implementation caps total Phase 1.5 work and can stop an attempt early when the global best hard-violation objective stalls.
intensificationBudgetFractionOfMaxIterations: 0.25,
intensificationEarlyStopNoBestImproveIterations: 800,This means intensificationIterations * maxIntensificationAttempts is no
longer the whole story. The true upper bound is the smaller of the per-attempt
settings and the global Phase 1.5 budget.
Migrate telemetry and benchmarking code
If you previously relied only on logs or onProgress to understand solver
behavior, use the new diagnostics surface for post-run analysis.
- Read
solution.diagnosticsfrom the solve result. - Call
solver.getDiagnostics()when you want a snapshot copy after the run.
This is the preferred branch-compatible way to inspect timing, first-feasible milestones, budget usage, and Phase 1.5 stop reasons.
Migrate logging setup
To log to files, set logging.output and logging.filePath explicitly.
logging: {
enabled: true,
output: 'file',
filePath: './logs/sa.log',
}The logger creates parent directories automatically.
Migrate error handling
If older integration code relied on generic catches, prefer typed catches where appropriate.
SAConfigErrorConstraintValidationErrorSolveConcurrencyErrorSolveCancelledError
You should also be aware that user-thrown exceptions from constraint evaluation can still propagate as plain errors.
Migrate to timetable-sa@3.2.1
Version 3.2.1 keeps the existing constructor, constraint, and move-generator
contracts compatible. The new targeting metadata is additive.
Use this release to improve hard-repair targeting without rewriting your solver integration:
- Add stable
keyvalues to hard constraints that have dedicated repair moves. - Add matching
targetConstraintKeysto precise repair operators. - Add
targetConstraintTypes: ['hard']to broad feasibility-repair operators. - Keep
intensificationTargetedOperatorNamesonly when a deployment needs to prefer exact operator names from configuration.
If your old tuning depended on operator names containing words such as fix,
capacity, or lecturer, migrate that intent to metadata. Metadata targeting is
more predictable because it does not depend on display labels.
Recommended migration sequence
Use this order to reduce migration risk.
- Define a typed
TStatethat contains the full mutable candidate solution. - Move all domain rules into
Constraintimplementations. - Implement a fast, deterministic
cloneState. - Port mutation logic into
MoveGeneratorimplementations. - Add progress callbacks and logging only after the base solve works.
- Run repeated solves and tune parameters empirically.
Next steps
Once the migration is complete:
- read API Reference to verify current contracts,
- read Configuration to tune the new runtime surface,
- read Troubleshooting if the migrated system behaves differently than expected.