Runner¶
Execute simulations across experimental grids.
For expensive grids, pass checkpoint_path="run.sqlite" and a caller-managed
checkpoint_key="model-v1-data-v3". Successful evaluations are committed one
at a time, including by parallel workers. Resume with the same ordered grid,
replicate count, observable/annotation definitions, and revision. Completed
tasks are loaded; only unfinished tasks are evaluated. Configurations with
identical values at different grid positions remain separate design points.
Callbacks run for all rows, including recovered rows, in original task order.
max_retries=2 permits two additional attempts for each unfinished task during
that invocation. The default is zero and propagates the first exception.
Simulator and scorer exceptions are retried with the same configuration and
replicate id. Interrupts are not retried. Metadata records total attempts across
invocations and marks recovered rows with recovered=True.
Checkpoint identity checks inspectable class code and structural definitions. Change the revision when instance settings, external data, or randomness semantics change. Use a new path for an incompatible definition. Run one grid invocation at a time against a ledger; its workers may run in parallel. A crash between an external side effect and saving its result may require repeating that evaluation. Make side effects safe to repeat. Retry limits do not bound the number of explicit resume invocations.
Reusing identified evaluations¶
Reuse is opt-in through an explicit cache:
cache = EvaluationCache(
"evaluations.sqlite",
revision="simulator-v2-scorer-v1-data-v3-annotations-v1",
replicate_namespace="experiment-2026-seed-17",
fidelity="high",
)
results = run_grid(world, scorer, grid, observables, n_reps=3, cache=cache)
The key includes a typed canonical configuration, replicate id, caller revision, replicate namespace, fidelity, inspectable simulator/scorer class code, objective definitions and annotation semantics. Grid position and total replicate count are excluded: reordering designs or requesting more replicates can reuse identical existing evaluations. Raw result rows still retain the current grid's design-point ids. Different replicate ids and independent experiment namespaces remain distinct even when their configurations match.
The caller must update the revision when instance settings, external data, globals or opaque callable behavior changes. The replicate namespace must identify the actual randomness/seed convention. Use a fresh namespace for an independent experiment; matching configuration alone never establishes reuse. Supported scalar/container configurations preserve types; opaque values are refused. Cached evaluators must not mutate their configuration.
cache_bypass=True skips reads and writes, leaving stored evidence untouched.
cache.clear() invalidates all contexts in the cache file. Changing identity
creates a new context. Conflicting scores for one identity are refused.
Metadata exposes cache_hit, cache_key, and the revision/namespace/fidelity
context. Cached wall time is the original evaluation's time. Completed grid
checkpoints can populate the cache without re-evaluation; bypassing the cache
does not bypass checkpoints. Concurrent workers may evaluate an absent identity
more than once before either saves; reuse does not promise exactly-once effects.
trade_study.run_grid(world, scorer, grid, observables, *, annotations=None, n_jobs=1, n_reps=1, callback=None, checkpoint_path=None, checkpoint_key=None, max_retries=0, cache=None, cache_bypass=False)
¶
Run all configurations in a grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
world
|
Simulator
|
Simulator that generates (truth, observations). |
required |
scorer
|
Scorer
|
Scorer that evaluates observables. |
required |
grid
|
list[dict[str, Any]]
|
List of config dicts to evaluate. |
required |
observables
|
list[Observable]
|
Observable definitions (for column ordering). |
required |
annotations
|
list[Annotation] | None
|
Optional external annotations (costs, etc.). |
None
|
n_jobs
|
int
|
Number of parallel workers (-1 for all CPUs). |
1
|
n_reps
|
int
|
Number of times to evaluate each design point.
When greater than 1 and |
1
|
callback
|
ProgressCallback | None
|
Optional progress callback invoked after each trial
with |
None
|
checkpoint_path
|
str | Path | None
|
Optional SQLite ledger. Each completed evaluation is committed before callbacks and retained across interruptions. Reopening skips completed tasks with the same definition. |
None
|
checkpoint_key
|
str | None
|
Caller-managed model/data revision. Change this when instance state, external data, or other behavior changes; class source alone cannot detect those changes. |
None
|
max_retries
|
int
|
Additional attempts per unfinished task per invocation. Zero (default) propagates the first evaluation exception. Retries retain the same config and replicate id. |
0
|
cache
|
EvaluationCache | None
|
Opt-in revision/replicate/fidelity/schema-aware evaluation reuse. Independent experiments must use different replicate namespaces. |
None
|
cache_bypass
|
bool
|
Skip cache reads and writes. Completed checkpoint tasks still resume; use a new checkpoint path to re-evaluate them. |
False
|
Returns:
| Type | Description |
|---|---|
ResultsTable
|
ResultsTable with scored results. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/trade_study/runner.py
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | |
trade_study.run_adaptive(world, scorer, factors, observables, *, n_trials=100, n_reps=1, seed=42)
¶
Run adaptive multi-objective optimization via optuna.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
world
|
Simulator
|
Simulator. |
required |
scorer
|
Scorer
|
Scorer for observables. |
required |
factors
|
list[Factor]
|
Factor definitions (from design module). |
required |
observables
|
list[Observable]
|
Observable definitions. |
required |
n_trials
|
int
|
Number of optuna trials. |
100
|
n_reps
|
int
|
Replicate draws averaged into each trial's objective
values before Optuna sees them, the same convention
|
1
|
seed
|
int
|
Random seed. |
42
|
Returns:
| Type | Description |
|---|---|
ResultsTable
|
ResultsTable with scored results, one row per optuna trial (each |
ResultsTable
|
row the weighted mean of |
ResultsTable
|
retains trial ids, raw means, standard errors and replicate counts. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/trade_study/runner.py
Sequential replication¶
run_sequential(..., policy=ReplicationPolicy(...)) starts every design with
min_reps, then allocates additional replicates among unresolved designs,
preferring the least sampled design with grid-order tie breaking. It retires
confidently infeasible or dominated designs and stops when it resolves a single
choice, a Pareto trade-off, or practical equivalence. Otherwise it reports
max_reps or budget_exhausted; a budget stop does not establish a winner.
Specify known almost-sure score_bounds for every observable, min_reps,
max_reps, max_evaluations, and joint confidence. Optional equivalence
tolerances use raw observable units. Directions guide comparisons; observable
weights do not transform scores. Monotone constraints use the simultaneous
intervals and policy confidence instead of normal standard-error bounds.
For D designs, M observables, horizon H=max_reps, and alpha=1-confidence,
the interval radius after n replicates is
(upper_bound - lower_bound) * sqrt(log(2*D*M*H/alpha)/(2*n)), clipped to the
declared bounds. Hoeffding's inequality bounds each failure by alpha/(DMH);
a union bound gives simultaneous coverage over every design, observable, and
allowed sample size. This permits data-dependent allocation and stopping under
independent replicates with a fixed mean within each design. Common random
numbers may correlate designs; the within-design replicate sequences must
still satisfy those assumptions. Bounds must be known before inspecting data,
not estimated from the observed minimum and maximum. Out-of-bounds/non-finite
scores are errors. The bounds are conservative and may spend the entire budget
on small gaps, even for a deterministic simulator with broad score bounds.
For context on time-uniform inference and sharper approaches, see
Kuchibhotla and Zheng (2021).
SequentialResult.results preserves raw scores and design-point/replicate ids;
summary contains design means and exportable allocation/stopping metadata.
Use intervals and feasibility for the sequential decision. Ordinary
ResultsTable.feasible() normal bounds and post-selection paired bootstrap/t
intervals do not inherit sequential coverage. Allocations may have unmatched
tail replicates; explicitly select a common prefix for descriptive paired
comparisons. Existing run_grid(n_reps=...) behavior is unchanged.
Fixed plans and recorded policy¶
Set ReplicationPolicy(..., allocation="fixed") to collect exactly max_reps
for every design, even when an early winner or infeasible design is apparent.
The total budget must cover the entire plan before any simulator call. Feasibility
is assessed only after completion, stopping reasons are fixed_plan_complete,
and no winner is automatically selected. This mode uses the same bounded-mean
Hoeffding method with one final look: replace H by 1 in the radius above.
It does not repeatedly inspect fixed-sample Wilson/Clopper–Pearson intervals.
allocation="adaptive" preserves the existing default behavior.
The runner snapshots bounds/tolerances and the outer candidate/observable lists before evaluation; caller-owned mapping changes cannot narrow the used bounds after results are seen. Configuration contents, scoring definitions, means and scientific units must still remain fixed. Recorded assumptions are declarations, not verification of the simulator or authentication of a saved artifact.
Each summary row retains the existing interval/stopping fields and adds a
primitive-JSON replication_contract with schema_version=1:
| Recorded field | Interpretation |
|---|---|
| Allocation, bounds, objectives, constraints, confidence | The actual used policy; constraint requests use the joint policy intervals. |
| Planned minimum, exact fixed-plan count or null, maximum and actual replicates | Counts of simulator/scorer evaluations, not individual observations inside one evaluation. |
| Total budget and actual evaluations | All completed evaluations across the declared design family. |
| Look horizon, multiplicity and per-look error | The prespecified inference family; adaptive mode includes even unvisited sample counts through the horizon. |
| Interval half-widths | Observed interval precision in raw score units, not gate power. |
| Gate power / gate false-failure rate | Explicitly not_assessed, with null values. Interval coverage does not estimate either quantity. |
| Decision status | unresolved for an exhausted adaptive budget/horizon, resolved for the existing retirement rules, or not_assessed for a fixed plan. This does not certify a statistical gate. |
| Failure policy / evaluability | abort; available completed runs have zero failed replicates. A failed run returns no successful summary. |
replicate_unit="independent_cluster" can declare one independent cluster
score per simulator/scorer evaluation. Members may be dependent inside that
cluster, but they never inflate the replicate count. The estimand is the mean
declared replicate/cluster score; it is not automatically an individual-weighted
population mean. General serial or across-cluster dependence remains unsupported.
The default is independent_replicate. Cross-design common random numbers remain
allowed because the union bound needs no independence across designs. Neither
mode supplies a paired-contrast interval; ordinary paired post-selection
inference does not acquire sequential coverage from this metadata.
Missing, non-finite or out-of-bound scores abort instead of producing a pass with a reduced denominator. Simulator/scorer exceptions also propagate by default. Opt-in abort capture, described below, preserves diagnostic rows and failed attempt identities for the existing bundle writer. It supplies no automatic persistence/resume or gate-specific power/false-failure thresholds. An aborted run remains unavailable, and unresolved precision is not relabeled as assessed power. Statistical qualification and gate operating characteristics remain separate obligations.
Howard et al. (2021) and the authors' confseq implementations describe mixture, stitching and betting alternatives for tighter or unbounded-horizon inference. They need separate domain/tuning/numerical qualification; the current known finite horizon and generic bounded scores do not require replacing this NumPy-only baseline or introducing a compiled dependency.
Aborted-run artifacts¶
By default, evaluator and declared-score errors retain their existing exception
behavior. With capture_failure=True, an ordinary evaluation or score-validation
exception raises SequentialEvaluationError, with the original error in
__cause__ and a detached SequentialFailure in .failure. The runner snapshots
the declared definitions before evaluation; completed raw rows are copied only
when capturing a failure. Successful runs keep the same scores and allocation.
The failure record separates completed, attempted and failed replicate counts,
retains the failed design/replicate identity, stage and error, and distinguishes
fixed planned counts from adaptive capacity. Raw rows include their original
evaluation_index, even though table rows are grouped by design. All captured
rows are marked run_status="aborted" and decision_status="unavailable".
The record supplies no intervals, feasibility pass or completed sequential result.
Given the caller's simulator, scorer, grid, objectives and policy:
from trade_study import SequentialEvaluationError, run_sequential, save_result_bundle
try:
result = run_sequential(
world, scorer, grid, observables,
policy=policy, capture_failure=True,
)
except SequentialEvaluationError as error:
abort = error.failure
save_result_bundle(
abort.results, "new-aborted-run-directory",
observables=abort.observables,
provenance={
"model_revision": "caller-supplied-revision",
"random_stream": "caller-supplied-stream-identity",
"sequential_failure": abort.to_metadata(),
},
)
The existing bundle writer preserves diagnostic metadata even when the first attempt fails and there are zero completed rows. Configuration/provenance values must satisfy its primitive-JSON contract; it refuses existing destinations. These artifacts support inspection and preservation of work. They supply no automatic retries/resume, process-kill recovery or inference conditional on successful attempts. Do not treat the completed subset as a complete fixed plan or an available sequential decision.
trade_study.ReplicationPolicy(score_bounds, min_reps=2, max_reps=100, max_evaluations=1000, confidence=0.95, equivalence=dict(), allocation='adaptive', replicate_unit='independent_replicate')
dataclass
¶
Explicit budget and bounded-score assumptions for sequential replication.
Attributes:
| Name | Type | Description |
|---|---|---|
score_bounds |
dict[str, tuple[float, float]]
|
Known almost-sure bounds in raw observable units. |
min_reps |
int
|
Initial adaptive batch per design; must not exceed max_reps. |
max_reps |
int
|
Adaptive per-design maximum/horizon, or exact fixed-plan count. |
max_evaluations |
int
|
Total simulator/scorer evaluations allowed. |
confidence |
float
|
Joint coverage across all designs, observables and looks. |
equivalence |
dict[str, float]
|
Practical-equivalence tolerances in raw observable units; omitted observables use zero. |
allocation |
Literal['adaptive', 'fixed']
|
Adaptive retirement/allocation, or a fixed plan collecting exactly max_reps for every design before any inference look. |
replicate_unit |
Literal['independent_replicate', 'independent_cluster']
|
Independent simulator/scorer evaluations, or independent cluster scores. Cluster members are not additional replicates. |
trade_study.SequentialResult(results, summary, intervals, allocations, stopping_reasons, feasibility, policy)
dataclass
¶
Raw replicates, allocation decisions, and simultaneous mean intervals.
trade_study.SequentialFailure(results, grid, observables, constraints, policy, completed_reps, failed_design, failed_rep, stage, exception_type, exception_message)
dataclass
¶
Detached diagnostics from an aborted run, without inferential results.
Attributes:
| Name | Type | Description |
|---|---|---|
results |
ResultsTable
|
Completed raw rows, each marked as belonging to an aborted run. |
grid |
list[dict[str, Any]]
|
Declared designs captured before evaluation. |
observables |
list[Observable]
|
Declared objectives captured before evaluation. |
constraints |
list[Constraint]
|
Declared constraints captured before evaluation. |
policy |
ReplicationPolicy
|
Detached policy used by the failed run. |
completed_reps |
tuple[int, ...]
|
Completed counts in declared design order. |
failed_design |
int
|
Zero-based design index of the failed attempt. |
failed_rep |
int
|
Zero-based replicate index of the failed attempt. |
stage |
Literal['evaluation', 'score_validation']
|
Evaluation or declared-score validation stage. |
exception_type |
str
|
Qualified type of the original evaluation error. |
exception_message |
str
|
Original error message. |
to_metadata()
¶
Describe the abort for caller provenance in a result bundle.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Versioned diagnostic metadata. Designs must also satisfy the bundle |
dict[str, Any]
|
writer's primitive-JSON contract when persisted. Counts distinguish |
dict[str, Any]
|
failed attempts from completed rows and unattempted capacity. |
Source code in src/trade_study/sequential.py
trade_study.SequentialEvaluationError(failure)
¶
Bases: RuntimeError
An opt-in aborted-run error carrying diagnostics and its original cause.
Attributes:
| Name | Type | Description |
|---|---|---|
failure |
Detached raw rows and failure metadata, without intervals, summary feasibility or a completed sequential result. |
Store diagnostics for an explicitly requested failure capture.
Source code in src/trade_study/sequential.py
trade_study.run_sequential(world, scorer, grid, observables, *, policy, constraints=None, capture_failure=False)
¶
Allocate extra replicates to unresolved comparisons and constraints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
world
|
Simulator
|
Simulator, with optional replicate-id support as in run_grid. |
required |
scorer
|
Scorer
|
Scorer returning finite values within declared score bounds. |
required |
grid
|
list[dict[str, Any]]
|
Fixed candidate designs; adaptive proposal selection is excluded. |
required |
observables
|
list[Observable]
|
Objectives with directions; scores and bounds use raw units. |
required |
policy
|
ReplicationPolicy
|
Explicit budgets, bounds, confidence and adaptive/fixed allocation. The validated used policy is snapshotted before simulator calls. |
required |
constraints
|
list[Constraint] | None
|
Monotone feasibility constraints on scored observables. Simultaneous policy intervals replace normal standard-error bounds. |
None
|
capture_failure
|
bool
|
On ordinary evaluation/score-validation exceptions, raise SequentialEvaluationError with detached raw diagnostics and the original exception as its cause. No successful result is returned. |
False
|
Returns:
| Type | Description |
|---|---|
SequentialResult
|
Raw per-replicate results, per-design means, simultaneous mean intervals, |
SequentialResult
|
allocations and stopping reasons. Unresolved cases retain unknown |
SequentialResult
|
feasibility and stop at max_reps or the total evaluation budget. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If definitions/budgets are invalid or a score violates bounds. |
TypeError
|
If capture_failure is not boolean. |
SequentialEvaluationError
|
If explicitly requested failure capture aborts an evaluation. Its failure record supplies no inferential result. |
Notes
Coverage requires independent replicates with a fixed mean within each design and known almost-sure score bounds. Dependence across designs through common random numbers is allowed. Adaptive intervals use Hoeffding bounds and a union bound over all designs, observables and sample sizes through max_reps, permitting data-dependent allocation and stopping. Fixed mode collects every planned replicate and uses one final look; those intervals supply no interim-look guarantee. Independent clusters can supply one score per replicate, without counting members separately. Gate power and false-failure rates are not assessed by these intervals. Missing/non-finite/out-of-bound scores abort rather than reduce the denominator. Returned metadata records the used policy and actual counts; ordinary post-selection paired intervals gain no sequential coverage.
Source code in src/trade_study/sequential.py
700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 | |
trade_study.run_successive_halving(trials, sim, *, rungs, eta=3.0, metric, mode='min')
¶
Successive-halving multi-fidelity early-stopping.
Evaluates every trial at the lowest rung, keeps the top 1/eta by
metric (according to mode), promotes survivors to the next
budget, and repeats until the highest rung. Every (trial, rung)
evaluation is recorded as one row in the returned :class:ResultsTable,
with rung index and budget stored in per-row metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trials
|
list[dict[str, Any]]
|
Candidate configurations to evaluate. |
required |
sim
|
PartialEvaluator
|
A :class: |
required |
rungs
|
list[float]
|
Strictly ascending list of budgets (e.g. epochs, iterations). Length determines the number of halving rounds. |
required |
eta
|
float
|
Reduction factor between rungs (>1). Each rung keeps
|
3.0
|
metric
|
str
|
Observable name used to rank trials at each rung. |
required |
mode
|
str
|
|
'min'
|
Returns:
| Type | Description |
|---|---|
ResultsTable
|
class: |
ResultsTable
|
Per-row metadata contains |
ResultsTable
|
|
ResultsTable
|
|
ResultsTable
|
|
ResultsTable
|
validator when arguments are invalid. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
Source code in src/trade_study/runner.py
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 | |
trade_study.run_hyperband(trial_factory, sim, *, max_budget, eta=3.0, metric, mode='min')
¶
Hyperband: multi-bracket successive-halving.
Wraps :func:run_successive_halving with the bracket schedule from
Li et al. (2017). Each bracket trades off the number of initial
trials against the minimum budget per trial; together they hedge
against picking either ratio wrong.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trial_factory
|
Callable[[int, int], list[dict[str, Any]]]
|
Callable |
required |
sim
|
PartialEvaluator
|
A :class: |
required |
max_budget
|
float
|
Maximum resource |
required |
eta
|
float
|
Reduction factor (>1). Defaults to 3. |
3.0
|
metric
|
str
|
Observable used for ranking within each bracket. |
required |
mode
|
str
|
|
'min'
|
Returns:
| Name | Type | Description |
|---|---|---|
Concatenated |
ResultsTable
|
class: |
ResultsTable
|
additional |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/trade_study/runner.py
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 | |