Examples
Start with a small generic example, then adapt the same pattern to your domain.
Examples
This page keeps the examples simple. Start with one small generic problem, then adapt the same structure to your own domain.
Minimal example
This example uses a list of numbers as the state. One hard constraint keeps the sum at or below a limit, and one soft constraint prefers smaller totals.
import { SimulatedAnnealing } from 'timetable-sa';
import type { Constraint, MoveGenerator, SAConfig } from 'timetable-sa';
type State = {
values: number[];
};
const constraints: Constraint<State>[] = [
{
name: 'Sum must stay under 20',
key: 'sum_under_limit',
type: 'hard',
evaluate: (state) =>
state.values.reduce((sum, value) => sum + value, 0) <= 20 ? 1 : 0,
},
{
name: 'Prefer smaller totals',
type: 'soft',
weight: 5,
evaluate: (state) => {
const total = state.values.reduce((sum, value) => sum + value, 0);
return Math.max(0, Math.min(1, 1 - total / 20));
},
},
];
const moves: MoveGenerator<State>[] = [
{
name: 'Adjust one value',
targetConstraintTypes: ['hard'],
targetConstraintKeys: ['sum_under_limit'],
canApply: (state) => state.values.length > 0,
generate: (state) => {
const index = Math.floor(Math.random() * state.values.length);
const delta = Math.random() < 0.5 ? -1 : 1;
state.values[index] = Math.max(0, state.values[index] + delta);
return state;
},
},
];
const config: SAConfig<State> = {
initialTemperature: 100,
minTemperature: 0.01,
coolingRate: 0.995,
maxIterations: 10000,
hardConstraintWeight: 1000,
cloneState: (state) => ({ values: [...state.values] }),
};
const solver = new SimulatedAnnealing(
{ values: [8, 7, 9] },
constraints,
moves,
config
);
const result = await solver.solve();
console.log(result.fitness);
console.log(result.hardViolations);
console.log(result.state);What this example shows
Use this example to understand the minimum pieces you need:
- a typed state object,
- at least one constraint,
- at least one move generator,
- a
cloneStatefunction, - an awaited call to
solve().
Targeted repair metadata
The minimal example includes key, targetConstraintTypes, and
targetConstraintKeys because those fields are the preferred way to connect hard
constraints with repair-oriented move generators.
The same pattern appears in the timetabling example. A hard constraint such as
NoRoomConflict defines a stable key, and a repair operator such as
FixRoomConflict declares that it targets that key.
class NoRoomConflict implements Constraint<TimetableState> {
name = 'No Room Conflict';
key = 'no_room_conflict';
type = 'hard' as const;
evaluate(state: TimetableState): number {
return hasRoomConflicts(state) ? 0 : 1;
}
}
class FixRoomConflict implements MoveGenerator<TimetableState> {
name = 'Fix Room Conflict';
targetConstraintTypes = ['hard'] as const;
targetConstraintKeys = ['no_room_conflict'];
canApply(state: TimetableState): boolean {
return state.schedule.length > 0;
}
generate(state: TimetableState): TimetableState {
return repairRoomConflict(state);
}
}Use one stable lowercase key per constraint, then repeat that key in each move
operator that can repair the constraint. For broad hard-repair operators, use
targetConstraintTypes: ['hard'] even when there is no single precise key.
Advanced Phase 1.5 example
Once the minimal flow works, you can tune intensification more explicitly. This example keeps the same generic shape, but it adds targeted Phase 1.5 controls and reads the diagnostics payload after the solve.
const advancedConfig: SAConfig<State> = {
initialTemperature: 100,
minTemperature: 0.01,
coolingRate: 0.995,
maxIterations: 10000,
hardConstraintWeight: 1000,
cloneState: (state) => ({ values: [...state.values] }),
enableIntensification: true,
intensificationStartTemperatureMode: 'phase1-end',
intensificationTargetedOperatorNames: ['Adjust one value'],
intensificationTargetedSelectionRate: 0.8,
intensificationBudgetFractionOfMaxIterations: 0.2,
intensificationEarlyStopNoBestImproveIterations: 200,
};
const advancedSolver = new SimulatedAnnealing(
{ values: [8, 7, 9] },
constraints,
moves,
advancedConfig
);
const advancedResult = await advancedSolver.solve();
console.log(advancedResult.diagnostics?.phaseTimings.totalRuntimeMs);
console.log(advancedResult.diagnostics?.intensification.phase15EndedByBudget);This pattern is useful when you need to benchmark or debug why Phase 1.5 did or did not improve hard feasibility.
Next steps
After this example works, continue with:
- Quick Start for a guided setup,
- Configuration for tuning options,
- Testing Guide for validating your constraints and moves.