Design¶
Experimental design: factors, grids, and screening.
Sobol evaluation budgets¶
For base sample size N and D continuous factors, both
screen(method="sobol", n_trajectories=N) and sobol_indices(n_samples=N)
currently request the second-order Saltelli design with N * (2*D + 2) nominal
configurations. screen returns first-order indices, but still uses that larger
design. A completed run invokes run_fn N * (2*D + 2) * n_reps times.
Repeated configurations in the design do not reduce that callback count.
For example, N=8, D=3 and n_reps=2 mean 64 nominal configurations and
128 callable invocations. Replication costs apply even when a callback does not
accept the optional rep keyword; meaningful stochastic replication requires
the caller's randomness contract. These counts describe the screening callback,
not hidden work or caching inside it. Surrogate fitting/CV has its own cost.
This correction preserves the sampler/analyzer settings and existing defaults;
there is no new first/total-only sampling option.
trade_study.FactorType
¶
Bases: Enum
Type of design factor.
trade_study.Factor(name, factor_type, levels=None, bounds=None, log_scale=False)
dataclass
¶
A single design factor.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Factor identifier (e.g. "alpha", "layer1_method"). |
factor_type |
FactorType
|
Continuous, discrete, or categorical. |
levels |
list[Any] | None
|
For categorical/discrete: list of allowed values. |
bounds |
tuple[float, float] | None
|
For continuous: (low, high) tuple. |
log_scale |
bool
|
For continuous factors with positive bounds: sample and
model the factor uniformly in |
__post_init__()
¶
Validate factor constraints.
Raises:
| Type | Description |
|---|---|
ValueError
|
If name is empty, continuous factor has missing or invalid bounds, a log-scale factor has non-positive bounds or is not continuous, or discrete/categorical factor has empty levels. |
Source code in src/trade_study/design.py
trade_study.FactorConstraint(predicate, name='factor_constraint')
dataclass
¶
Coupled-factor constraint applied at design-generation time.
Unlike :class:trade_study.protocols.Constraint, which filters trials
after simulation based on observables, a FactorConstraint filters
candidate configurations before they are evaluated. It is the way
to express coupled constraints such as "if method == 'a' then
patience <= 2" without distorting the design via post-hoc
filtering of returned results.
Attributes:
| Name | Type | Description |
|---|---|---|
predicate |
Callable[[dict[str, Any]], bool]
|
Callable taking a candidate config dict and returning
|
name |
str
|
Human-readable label used in error messages and warnings. |
is_feasible(config)
¶
Return whether config satisfies this constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Candidate design point. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Source code in src/trade_study/design.py
trade_study.build_grid(factors, *, method='full', n_samples=100, seed=42, scramble=True, constraints=None)
¶
Build an experimental design grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factors
|
list[Factor]
|
List of design factors. |
required |
method
|
str
|
Design method. One of:
- "full": Full factorial (categorical/discrete only).
- "lhs": Latin hypercube sampling (continuous factors, maps
categorical factors to uniform random selection).
- "sobol": Scrambled Sobol' sequence via |
'full'
|
n_samples
|
int
|
Number of samples for LHS / QMC methods. |
100
|
seed
|
int
|
Random seed. |
42
|
scramble
|
bool
|
Whether to apply scrambling to QMC sequences (Sobol / Halton). Ignored for other methods. |
True
|
constraints
|
Sequence[FactorConstraint] | None
|
Optional sequence of :class: |
None
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
List of config dictionaries, one per design point. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If an unknown design method is specified, or if constraints reject more than the maximum oversample budget allows. |
Examples:
>>> factors = [Factor("size", FactorType.DISCRETE, levels=[10, 20])]
>>> build_grid(factors)
[{'size': 10}, {'size': 20}]
>>> constraint = FactorConstraint(lambda c: c["size"] <= 10)
>>> build_grid(factors, constraints=[constraint])
[{'size': 10}]
Source code in src/trade_study/design.py
trade_study.reduce_factors(factors, importance, *, threshold=0.1)
¶
Keep only factors whose max importance exceeds threshold.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factors
|
list[Factor]
|
Original factor list. |
required |
importance
|
dict[str, NDArray[floating[Any]]]
|
Output of |
required |
threshold
|
float
|
Minimum importance to retain a factor. |
0.1
|
Returns:
| Type | Description |
|---|---|
list[Factor]
|
Reduced list of influential factors. |
Warns:
| Type | Description |
|---|---|
UserWarning
|
If every observable's importance is NaN for one or
more factors -- those factors are dropped (there's no
valid data to compare against |
Source code in src/trade_study/design.py
trade_study.screen(run_fn, factors, *, method='morris', n_trajectories=100, n_reps=1, seed=42)
¶
Screen factors for influence on observables via SALib.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_fn
|
Callable[[dict[str, Any]], dict[str, float]]
|
Callable that takes a config dict and returns a dict of observable name → scalar score. |
required |
factors
|
list[Factor]
|
List of continuous factors to screen. |
required |
method
|
str
|
Screening method ( |
'morris'
|
n_trajectories
|
int
|
Number of Morris trajectories. For Sobol, this
controls the base sample size N; the total number of model
configurations is N x (2 x num_vars + 2), using the current
second-order Saltelli design even though |
100
|
n_reps
|
int
|
Replicate draws averaged into each sampled point's scores
before SALib sees them, the same convention
|
1
|
seed
|
int
|
Random seed. |
42
|
Returns:
| Type | Description |
|---|---|
dict[str, NDArray[floating[Any]]]
|
Dictionary mapping observable names to arrays of factor importance |
dict[str, NDArray[floating[Any]]]
|
(mu_star for Morris, S1 for Sobol), one value per factor. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If method is unknown, no continuous factors are
provided, or |
Source code in src/trade_study/design.py
trade_study.sobol_indices(run_fn, factors, *, n_samples=100, n_reps=1, seed=42)
¶
Sobol first- and total-order sensitivity indices.
Like screen(method="sobol"), but returns both S1 (first-order) and
ST (total-order) per observable instead of discarding ST. ST - S1
is the standard way to detect interaction effects: a factor with small
S1 but large ST is interacting with other factors rather than acting
independently -- information screen() can't surface at all, since
it only keeps S1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
run_fn
|
Callable[[dict[str, Any]], dict[str, float]]
|
Callable that takes a config dict and returns a dict of observable name -> scalar score. |
required |
factors
|
list[Factor]
|
Factor list to analyze; only continuous factors are
varied (as in |
required |
n_samples
|
int
|
Base sample size N for the second-order Saltelli design;
total configurations are N x (2 x num_vars + 2). Physical
|
100
|
n_reps
|
int
|
Replicate draws averaged into each sampled point's scores
before SALib sees them -- see |
1
|
seed
|
int
|
Random seed. |
42
|
Returns:
| Type | Description |
|---|---|
dict[str, tuple[NDArray[floating[Any]], NDArray[floating[Any]]]]
|
Dictionary mapping observable name to an |
dict[str, tuple[NDArray[floating[Any]], NDArray[floating[Any]]]]
|
an array of one value per continuous factor (in the order they |
dict[str, tuple[NDArray[floating[Any]], NDArray[floating[Any]]]]
|
appear in |
Raises:
| Type | Description |
|---|---|
ValueError
|
If no continuous factors are provided, or |
Source code in src/trade_study/design.py
trade_study.unit_to_value(factor, unit)
¶
Map a unit-interval coordinate to a continuous factor's value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
Factor
|
Continuous factor with bounds. |
required |
unit
|
float
|
Coordinate in [0, 1]. |
required |
Returns:
| Type | Description |
|---|---|
float
|
|
float
|
log-scale factor. |
Source code in src/trade_study/design.py
trade_study.value_to_unit(factor, value)
¶
Map a continuous factor's value to its unit-interval coordinate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factor
|
Factor
|
Continuous factor with bounds. |
required |
value
|
float
|
Factor value within its bounds. |
required |
Returns:
| Type | Description |
|---|---|
float
|
The inverse of :func: |