Quick Start
Build the smallest complete `timetable-sa` setup that solves a real problem.
Quick Start
This page walks through the smallest complete setup that can solve a
constraint-driven problem with timetable-sa. The example is intentionally
small, but it uses the same API shape that larger production integrations use.
1. Define the state
Your state type is the full candidate solution seen by constraints and move generators.
type Assignment = { task: string; worker: string; slot: number };
interface State {
assignments: Assignment[];
}2. Define constraints
Each constraint returns a satisfaction score in [0, 1], where 1 means the
state satisfies the constraint.
import type { Constraint } from 'timetable-sa';
class NoWorkerCollision implements Constraint<State> {
name = 'No Worker Collision';
type = 'hard' as const;
evaluate(state: State): number {
for (let i = 0; i < state.assignments.length; i++) {
for (let j = i + 1; j < state.assignments.length; j++) {
const a = state.assignments[i]!;
const b = state.assignments[j]!;
if (a.worker === b.worker && a.slot === b.slot) {
return 0;
}
}
}
return 1;
}
}
class PreferEarlierSlots implements Constraint<State> {
name = 'Prefer Earlier Slots';
type = 'soft' as const;
weight = 10;
evaluate(state: State): number {
const avg =
state.assignments.reduce((sum, a) => sum + a.slot, 0) /
state.assignments.length;
return Math.max(0, Math.min(1, 1 - avg / 10));
}
}3. Define a move generator
Move generators receive a clone prepared by the solver, so they can mutate the provided state directly and return it.
import type { MoveGenerator } from 'timetable-sa';
class ChangeSlot implements MoveGenerator<State> {
name = 'Change Slot';
canApply(state: State): boolean {
return state.assignments.length > 0;
}
generate(state: State): State {
const idx = Math.floor(Math.random() * state.assignments.length);
state.assignments[idx]!.slot = Math.floor(Math.random() * 10);
return state;
}
}4. Configure the solver
The configuration defines temperature, iteration budget, cloning behavior, and optional advanced features.
import type { SAConfig } from 'timetable-sa';
const config: SAConfig<State> = {
initialTemperature: 1000,
minTemperature: 0.01,
coolingRate: 0.995,
maxIterations: 20000,
hardConstraintWeight: 10000,
cloneState: (s) => ({
assignments: s.assignments.map((a) => ({ ...a })),
}),
tabuSearchEnabled: true,
logging: { enabled: true, level: 'info', logInterval: 1000 },
};5. Solve the problem
Create an initial state, instantiate the solver, and await solve().
import { SimulatedAnnealing } from 'timetable-sa';
const initialState: State = {
assignments: [
{ task: 'A', worker: 'Alice', slot: 0 },
{ task: 'B', worker: 'Bob', slot: 0 },
{ task: 'C', worker: 'Alice', slot: 0 },
],
};
const solver = new SimulatedAnnealing(
initialState,
[new NoWorkerCollision(), new PreferEarlierSlots()],
[new ChangeSlot()],
config
);
const solution = await solver.solve();
console.log({
fitness: solution.fitness,
hardViolations: solution.hardViolations,
softViolations: solution.softViolations,
iterations: solution.iterations,
});6. Inspect the result
The returned Solution<TState> contains more than the final state.
solution.stateis the best state found,solution.fitnessis the final scalar objective,solution.violationscontains structured diagnostic records,solution.operatorStatsshows how each move generator performed.
What to build next
Once the minimal flow works, the most valuable improvements are usually:
- add more targeted move generators,
- implement
getViolations()on important hard constraints, - tune
hardConstraintWeight, tabu, and intensification settings, - add
onProgresstelemetry for observability.
Next steps
To deepen the integration:
- read Core Concepts for the exact contracts,
- read Configuration for tuning strategy,
- read API Reference for the full public surface.