Current benchmark surface in this branch:
- CVRP (list-variable routing)
- INRC2 nurse rostering (scalar-variable assignment/scheduling)
- JSPLIB job-shop scheduling (scalar-variable machine scheduling)
The JSSP benchmark adds a third core optimization class while preserving the same shared harness shape already used in this repository: deterministic dataset loader, solver adapters, shared run matrix, shared timing/watchdog policy, shared CSV/PostgreSQL logging, and shared result rows.
Use the public benchmark ecosystem around JSPLIB / OR-Library classic JSSP instances.
Primary sources:
- JSPLIB benchmark catalog: https://scheduleopt.github.io/benchmarks/jsplib/
- OR-Library job-shop instances: https://people.brunel.ac.uk/~mastjjb/jeb/orlib/jobshopinfo.html
Bundled instances in this branch:
ft06from the FT family, known best makespan55la01from the LA family, known best makespan666la02from the LA family, known best makespan655
The manifest currently defines:
quick:ft06,la01canonical:ft06,la01,la02
The problem package follows the existing benchmark pattern:
scalar-variable/job-shop-scheduling/src/job_shop_bench/__init__.pysrc/job_shop_bench/spec.pysrc/job_shop_bench/loader.pysrc/job_shop_bench/domain/models.pysrc/job_shop_bench/validation.pysrc/job_shop_bench/solver/instance_json.pysrc/job_shop_bench/solver/solver.py(adapter registry)src/job_shop_bench/solver/solverforge.pysrc/job_shop_bench/solver/solverforge_jssp/src/job_shop_bench/solver/ortools/src/job_shop_bench/solver/timefold.pysrc/job_shop_bench/solver/timefold/data/jsplib/...(instance files + manifest)scripts/validate_all.py
The benchmark framework remains centralized in src/solverforge_bench/.
Use a canonical JSSP definition:
- Jobs: ordered operation chains
- Each operation:
(machine_id, duration) - Hard constraints:
- Precedence within each job
- No overlap for operations sharing a machine
- Objective: minimize makespan
Current instance model:
@dataclass(frozen=True)
class Operation:
job_id: int
op_index: int
machine_id: int
duration: int
@dataclass(frozen=True)
class JobShopInstance:
name: str
family: str
num_jobs: int
num_machines: int
operations_by_job: tuple[tuple[Operation, ...], ...]Standardize solver return payload so validation is strict and comparable:
- Per-operation start times for every operation.
reported_makespan, which is compared against a fresh Python validator value.- No missing operation and no unassigned start may be fabricated into a valid schedule.
A validated schedule must produce:
hard_feasible = True/Falsecost= validator-computed makespan when feasiblereported_cost= solver-reported makespan when presentvalidation_errorwhen infeasible/invalid
Registered job-shop scheduling solvers are:
solverforge: Rust/PyO3 SolverForge model insolver/solverforge_jssp/. It uses scalar operation start-time variables, hard constraints for assigned starts, job precedence, and machine non-overlap, and a soft objective that minimizes makespan.timefold: Java Timefold model insolver/timefold/, packaged astimefold-jssp.jar.ortools: native C++ OR-Tools CP-SAT model insolver/ortools/.
The deterministic dispatch schedule is only used inside SolverForge and Timefold as an initial seed. It is not a registered benchmark solver.
The benchmark exposes native columns similar to existing adapters:
num_jobsnum_machinesnum_operationssource_family(for exampleftorlain the current manifest)known_best_makespan(if available in manifest)makespan_gap_to_best(if known best present)
These are produced by the job-shop benchmark spec and passed to the shared framework row writer.
data/jsplib/manifest.json owns deterministic groups and known-best metadata.
The current manifest intentionally stays small for this PR: two quick smoke
instances and three canonical instances.
The root harness integration does not change shared policy:
- Benchmark id:
job-shop-scheduling - Benchmark-specific selector options in the adapter layer only:
--dataset-set--datasets
Update benchmark.example.toml:
benchmark = "job-shop-scheduling"
[benchmarks.job-shop-scheduling]
dataset_set = "quick"
datasets = ["ft06"]Root targets mirror existing conventions:
make validate-job-shop-schedulingmake build-job-shop-schedulingmake build-job-shop-scheduling-solverforgemake build-job-shop-scheduling-timefoldmake build-job-shop-scheduling-ortoolsmake bench-job-shop-scheduling-quickmake bench-job-shop-scheduling-quick-dbmake bench-job-shop-schedulingmake bench-job-shop-scheduling-db
validation.py verifies:
- Every operation appears exactly once.
- Start times are non-negative.
- Job precedence respected.
- Machine disjunctive constraints respected.
- Makespan computed from operation end times.
Return deterministic error messages so solver failures are distinguishable from loader/adapter bugs.
make validate-job-shop-schedulingmake bench-job-shop-scheduling-quickPYTHONPATH=src:list-variable/cvrp/src:scalar-variable/employee-scheduling/src:scalar-variable/job-shop-scheduling/src .venv/bin/python3 scripts/run_benchmark.py job-shop-scheduling --run-kind quick --dataset-set quick --solver solverforge --time-limits 1PYTHONPATH=src:list-variable/cvrp/src:scalar-variable/employee-scheduling/src:scalar-variable/job-shop-scheduling/src .venv/bin/python3 scripts/run_benchmark.py job-shop-scheduling --run-kind quick --dataset-set quick --time-limits 1 10
- Instance format variance: keep loader format-specific and normalize to one in-memory model.
- Objective comparability: make makespan the only scored objective for now.
- Solver-output shape differences: adapt all solver outputs into one canonical schedule payload before validation.
JSSP is implemented as the third benchmark family in this branch. It is problem-class specific, publicly benchmarked, scalable from quick to nightly, and fits the same shared harness architecture already used by this repository.