Problem
There is no built-in way to declare coupled factor constraints at design-generation time — i.e. constraints that say "factor A's allowed values depend on factor B's value". For example:
if active_criteria == "elbo_only" then patience <= 2
Today, users must either (a) generate the full Cartesian / Sobol design and post-filter (which wastes budget and distorts QMC space-filling properties), or (b) split the study into multiple phases with manually pruned grids (which doesn't compose well for >2 coupled factors).
This is distinct from #74 (closed) which addressed observable feasibility filters applied after simulation. This issue is about constraining the input design space before trials are run.
Proposed
- Add a
FactorConstraint (or DesignConstraint) protocol/dataclass that takes a predicate over a candidate config dict and returns bool.
- Plumb a
constraints: Sequence[FactorConstraint] parameter through design.sobol_design, design.full_factorial, and any other generators.
- For Sobol / QMC: use rejection sampling with oversampling, then trim to the requested
n feasible points (document the oversample factor and warn if feasibility ratio is too low).
- For full factorial: filter the Cartesian product before yielding.
- Optional sugar: a small DSL or helper for the common "if X == v then Y in S" pattern.
Example
from trade_study.design import sobol_design, FactorConstraint
constraints = [
FactorConstraint(
lambda c: c["active_criteria"] != "elbo_only" or c["patience"] <= 2,
name="elbo_only_short_patience",
),
]
design = sobol_design(factors, n=128, constraints=constraints, seed=0)
Notes
- QMC space-filling properties degrade under rejection; document this and consider exposing the realized discrepancy / coverage diagnostic.
- Should compose with the existing
Phase API so per-phase grids can carry their own constraints.
Problem
There is no built-in way to declare coupled factor constraints at design-generation time — i.e. constraints that say "factor A's allowed values depend on factor B's value". For example:
Today, users must either (a) generate the full Cartesian / Sobol design and post-filter (which wastes budget and distorts QMC space-filling properties), or (b) split the study into multiple phases with manually pruned grids (which doesn't compose well for >2 coupled factors).
This is distinct from #74 (closed) which addressed observable feasibility filters applied after simulation. This issue is about constraining the input design space before trials are run.
Proposed
FactorConstraint(orDesignConstraint) protocol/dataclass that takes a predicate over a candidate config dict and returnsbool.constraints: Sequence[FactorConstraint]parameter throughdesign.sobol_design,design.full_factorial, and any other generators.nfeasible points (document the oversample factor and warn if feasibility ratio is too low).Example
Notes
PhaseAPI so per-phase grids can carry their own constraints.