Skip to content
cpbenchy 0.1.0.dev0 is in alpha: until version 1.0, commands, options, the Python API and the result format may still change. Pin the version you use.

param_sweep.py

Finds the best settings for a solver by trying several on the same instances.

Updated View as Markdown
Examplecopy it from the repository's examples/
python examples/scripts/param_sweep.py instances/
Distributed as
a file in examples/, to copy
Last updated
6 Oct 2026 · 1 commit
Authors
ThomSerg
Shows
an Experiment with several settings, and results as they come in
Tags
tuningexperiment

What it does

Tunes a solver: runs OR-Tools with several parameter settings on the same instances, then shows per setting how many instances were solved and how fast. All settings are one experiment, so adding a setting and running again only runs the new setting.

Use it

python examples/scripts/param_sweep.py instances/ --out results/sweep --time-limit 60 --jobs 4

Edit SETTINGS in the script for your own settings and solver.

Options

Argument
sources instance files, directories or glob patterns
--out output directory
--time-limit seconds per run
--jobs runs in parallel

Implementation

The file examples/scripts/param_sweep.py, 59 lines, as of this version of the docs.

examples/scripts/param_sweep.pypython
"""Tune a solver: run OR-Tools with several parameter settings on the same instances, then compare.

    python examples/scripts/param_sweep.py instances/ --out results/sweep --time-limit 60 --jobs 4

Each setting is one `add()` with its own parameters and cores, all in one experiment, so they share
the output directory: run it again after adding a setting, and only the new runs happen. Results stream
in as runs finish; at the end, a table shows per setting how many instances were solved and how fast.
"""

import argparse

import cpbenchy

SETTINGS = {
    # name: (cores, OR-Tools parameters)
    "1 worker": (1, {"num_search_workers": 1}),
    "1 worker, no LP": (1, {"num_search_workers": 1, "linearization_level": 0}),
    "1 worker, full LP": (1, {"num_search_workers": 1, "linearization_level": 2}),
    "4 workers": (4, {"num_search_workers": 4}),
}


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("sources", nargs="+", help="instance files, directories or glob patterns")
    parser.add_argument("--out", default="results/param-sweep")
    parser.add_argument("--time-limit", type=float, default=60)
    parser.add_argument("--jobs", type=int, default=1)
    args = parser.parse_args(argv)

    exp = cpbenchy.Experiment(args.out, time_limit=args.time_limit, jobs=args.jobs, quiet=True)
    for cores, params in SETTINGS.values():
        exp.add(*args.sources, solver="ortools", params=params, cores=cores)

    print(f"{len(exp.runs)} runs; results in {args.out}")
    for result in exp.iter_results():
        print(f"  {setting_of(result):<20} {result.instance:<30} {result.status:<9} {result.walltime_s:7.2f}s")

    print(summary(exp.results()))


def setting_of(result) -> str | None:
    return next(
        (n for n, (cores, params) in SETTINGS.items() if (cores, params) == (result.cores, result.params)), None
    )


def summary(results) -> str:
    rows = []
    for name in SETTINGS:
        runs = [r for r in results if setting_of(r) == name]
        solved = [r for r in runs if r.solved]
        time_solved = sum(r.walltime_s for r in solved)
        rows.append(f"{name:<20} solved {len(solved):>3}/{len(runs):<3}  time on solved {time_solved:8.1f}s")
    return "\n".join(rows)


if __name__ == "__main__":
    main()
Navigation

Type to search…

↑↓ navigate↵ selectEsc close