timetable-sa

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].

  • 1 means satisfied,
  • 0 means 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.

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.5 as preferring operators whose names merely looked like repair operators. The branch API now exposes an explicit targeting surface.

intensificationTargetedOperatorNames: ['Repair hard conflict']

Matching is case-insensitive, but it is still an exact name match. If you want deterministic targeting, rely on this field instead of assuming the solver will infer the right operators from substrings alone.

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.diagnostics from 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.

  • SAConfigError
  • ConstraintValidationError
  • SolveConcurrencyError

You should also be aware that user-thrown exceptions from constraint evaluation can still propagate as plain errors.

Use this order to reduce migration risk.

  1. Define a typed TState that contains the full mutable candidate solution.
  2. Move all domain rules into Constraint implementations.
  3. Implement a fast, deterministic cloneState.
  4. Port mutation logic into MoveGenerator implementations.
  5. Add progress callbacks and logging only after the base solve works.
  6. Run repeated solves and tune parameters empirically.

Next steps

Once the migration is complete:

On this page