Parameter studies (bt.sweep, bt.optimize)¶
Sweeps and deterministic optimisation over a cell authored as a function of
its parameters. bt.sweep and bt.optimize are the module's sweep and
optimize; the results are bt.study.Sweep and bt.study.Optimum. See the
Parameter sweeps tutorial.
result = bt.sweep(build_cell, grid={"velocity": [0.1, 0.2, 0.3], "lane_y": [0.4, 0.5, 0.6]},
metrics=lambda tl: {"cycle": tl.duration, "clearance": float(tl.min_clearance())})
print(result.pivot("lane_y", "velocity", "cycle"))
best = bt.optimize(build_cell, space={"velocity": (0.1, 0.4, 0.05), "lane_y": (0.3, 0.7, 0.05)},
objective="cycle", constraints={"clearance": (">=", 0.4)}, method="descent")
study
¶
Parameter studies over a cell: sweeps and deterministic optimisation.
A cell authored as a function of its parameters can be baked at every
variant and read by the numbers that matter — that is what makes layout
studies a loop and cycle time a regression test. sweep runs the grid and
tables the numbers; optimize searches it (a full grid, or a coordinate
descent on the grid) for the best feasible point. Neither uses a random
number: every row is a deterministic bake, the same every run, so a study
is as assertable as a single cell.
result = bt.sweep(build_cell, grid={"velocity": [0.1, 0.2, 0.3], "lane_y": [0.4, 0.5, 0.6]},
metrics=lambda tl: {"cycle": tl.duration, "clearance": float(tl.min_clearance())})
print(result.to_markdown())
print(result.pivot("lane_y", "velocity", "cycle"))
best = bt.optimize(build_cell, space={"velocity": (0.1, 0.4, 0.05), "lane_y": (0.3, 0.7, 0.05)},
objective="cycle", constraints={"clearance": (">=", 0.3)}, metrics=...)
print(best.params, best.row["cycle"])
build(**params) returns a Scene (baked with sequence= / sequences=,
default: every sequence together) or a SequenceTimeline (used as is);
metrics(timeline) returns a dict of numbers (default {"cycle":
duration}). A variant that does not bake — a layout the planner cannot
solve — is a row with ok=False and its error, not an exception: the
table says where the cliff is.
Optimum
¶
What optimize found: the best feasible parameters and their row,
every evaluated row (a Sweep, in evaluation order), and how it got
there.
Sweep
¶
The rows of a study, in evaluation order: each is the variant's
parameters, the metrics measured (missing on a failed row), ok and
error.
best
¶
best(
metric: Union[str, Callable[[dict], float]],
*,
minimize: bool = True,
where: Optional[Callable[[dict], bool]] = None,
) -> Optional[dict]
The row with the smallest (or largest) metric among the rows
that baked and satisfy where; ties go to the earlier row (grid
order), so the answer is stable.
pareto
¶
The non-dominated rows for several objectives at once — the trade-off front (a shorter cycle against a wider clearance).
pivot
¶
A two-axis view as a Markdown table: one row per value of
parameter rows, one column per value of cols, metric in the
cells (missing where the variant failed or was not run).
to_markdown
¶
The rows as a Markdown table — parameters as written, metrics at
table precision; the ok / error columns only when a row failed
(CSV and JSON always carry them).
evaluate
¶
evaluate(
build,
params: dict,
*,
metrics=None,
sequence=None,
sequences=None,
max_duration: float = 120.0,
) -> dict
One variant: build, bake, measure. Returns the row (params ∪
metrics ∪ ok / error); a variant that fails to build or bake is a
row with ok=False.
optimize
¶
optimize(
build: Callable[..., Any],
space: dict[str, Any],
*,
objective: Union[
str, Callable[[dict], float]
] = "cycle",
minimize: bool = True,
constraints: Union[
None,
dict[str, tuple[str, float]],
Callable[[dict], bool],
] = None,
metrics: Optional[
Callable[[Any], dict[str, Any]]
] = None,
sequence: Optional[str] = None,
sequences: Optional[Sequence[str]] = None,
max_duration: float = 120.0,
method: str = "grid",
start: Optional[dict] = None,
max_evals: int = 500,
workers: int = 1,
) -> Optimum
Searches space — {name: [values]} or {name: (lo, hi, step)} —
for the feasible point with the best objective (a metric name, or a
callable over the row). Two deterministic methods:
"grid"— every point, in grid order, then the best feasible one (ties to the earlier point). Exhaustive and parallel (workers)."descent"— coordinate descent on the grid: fromstart(default: the grid's middle point), try each parameter one step down and one step up, move to the best feasible improvement, repeat until nothing improves ormax_evalsis spent. Far fewer bakes on a large space; finds a local optimum, and says so inmethod. Infeasible neighbours are stepped over, not into.
Both return every evaluated row, so the search itself is a table.
sweep
¶
sweep(
build: Callable[..., Any],
grid: Optional[dict[str, Iterable[Any]]] = None,
*,
points: Optional[Iterable[dict]] = None,
metrics: Optional[
Callable[[Any], dict[str, Any]]
] = None,
sequence: Optional[str] = None,
sequences: Optional[Sequence[str]] = None,
max_duration: float = 120.0,
workers: int = 1,
) -> Sweep
Bakes build(**params) at every point of grid (the Cartesian
product of the lists, in order — the last parameter varies fastest) or
at the explicit points, and returns the table. workers > 1 bakes in
parallel processes — build and metrics must then be importable
(module-level) functions; the rows still come back in grid order, so
the result does not depend on scheduling.