Designed Experiments#

class process_improve.experiments.structures.Column(data=None, index=None, dtype=None, name=None, copy=None)[source]#

Bases: Series

Create a column. Can be used as a factor, or a response vector.

Parameters:
  • dtype (Dtype | None)

  • copy (bool | None)

pi_index: bool#
pi_numeric: bool#
pi_lo: float | None#
pi_hi: float | None#
pi_range: tuple | None#
pi_center: float | None#
pi_is_coded: bool#
pi_units: str | None#
pi_name: str | None#
pi_levels: dict#
to_coded(center=None, range=None)[source]#

Convert the column vector to coded units.

Parameters:
Return type:

Column

to_realworld(center=None, range=None)[source]#

Convert the column vector to real-world units.

Parameters:
Return type:

Column

copy(deep=True)[source]#

Create a copy of this Column, preserving the name.

Parameters:

deep (bool)

Return type:

Column

extend(values)[source]#

Extend the column with the list of new values.

Parameters:

values (list)

Return type:

Column

class process_improve.experiments.structures.Expt(data=None, index=None, columns=None, dtype=None, copy=None)[source]#

Bases: DataFrame

Dataframe carrying experimental data plus process-improve metadata.

Expt (short for “Experiment”) is a pandas.DataFrame subclass that adds library-managed metadata fields prefixed with pi_ – short for “process-improve”. The prefix is what keeps these reserved attribute names from colliding with column names from a caller-supplied DataFrame.

Pinned metadata (preserved across subsetting via _metadata):

  • pi_title – short human-readable name for the dataset

  • pi_source – provenance string (file path, URL, …)

  • pi_units – units string for the numeric columns

Other pi_* attributes (pi_range, pi_lo, pi_hi, pi_center, pi_name) are set by the experiments factory helpers in this module; see expt() / create_names().

The pi_ prefix is documented in CONTRIBUTING.md and is part of the package’s public API surface; new metadata fields should follow the same prefix.

Parameters:
  • index (Axes | None)

  • columns (Axes | None)

  • dtype (Dtype | None)

  • copy (bool | None)

pi_source: dict | None#
pi_title: str | None#
pi_units: dict | None#
get_title()[source]#

Return the experiment title, or empty string if not set.

Return type:

str

process_improve.experiments.structures.create_names(n, letters=True, prefix='X', start_at=1, padded=True)[source]#

Return default factor names, for a given number of n [integer] factors. The factor name “I” is never used.

If letters is True (default), then at most 25 factors can be returned.

If letters is False, then the prefix is used to construct names which are the combination of the prefix and numbers, starting at start_at.

Example:
>>> create_names(5)
    ["A", "B", "C", "D", "E"]
>>> create_names(3, letters=False)
    ["X1", "X2", "X3"]
>>> create_names(3, letters=False, prefix='Q', start_at=9,
                     padded=True)
    ["Q09", "Q10", "Q11"]
Parameters:
Return type:

list[str]

process_improve.experiments.structures.c(*args, **kwargs)[source]#

Perform the equivalent of the R function “c(…)”, to combine data elements into a Column. Numeric entries are converted to floating point; entries are left as-is for categorical columns (when levels=... is passed, or when the entries cannot be coerced to float).

Inputs#

index: a list of names for the entries in args

name: a name for the column

Usage#

# All equivalent ways of creating a factor, “A”

A = c(-1, 0, +1, -1, +1)

A = c(-1, 0, +1, -1, +1, index=[‘lo’, ‘cp’, ‘hi’, ‘lo’, ‘hi’]) A = c( 4, 5, 6, 4, 6, range=(4, 6)) A = c( 4, 5, 6, 4, 6, center=5, range=(4, 6)) # more explicit A = c( 4, 5, 6, 4, 6, lo=4, hi=6) A = c( 4, 5, 6, 4, 6, lo=4, hi=6, name = ‘A’) A = c([4, 5, 6, 4, 6], lo=4, hi=6, name = ‘A’) A = c([4, 5, 6, 4, 6], lo=4, hi=6, name = ‘A’)

# By default, the assumption is the variable levels supplied are coded # units. But if any one of the following: lo, hi, center, range OR # units are specified, then immediately it is assumed that the variable # values are not coded. # So, to force the specification, you may supply the optional input of # coded as True or False A = c([4, 5, 6, 4, 6], lo=1, hi=3, coded=True) A = c([4, 5, 6, 4, 6], lo=1, hi=3, coded=False, units=”g/mL”)

# Categorical variables B = c(0, 1, 0, 1, 0, 2, levels =(0, 1, 2)) M = c(“Dry”, “Wet”, “Dry”, “Wet”, levels = (“Dry”, “Wet”))

Return type:

Column

process_improve.experiments.structures.expand_grid(**kwargs)[source]#

Create the expanded grid here.

Parameters:

kwargs (Column)

Return type:

list[Column]

process_improve.experiments.structures.supplement(x, **kwargs)[source]#

Supplement an existing column with additional metadata (name, units, lo, hi, etc.).

Parameters:
Return type:

Column

process_improve.experiments.structures.gather(*args, title=None, **kwargs)[source]#

Gathers the named inputs together as columns for a data frame.

Removes any rows that have ANY missing values. If even 1 value in a row is missing, then that row is removed.

Usage#

expt = gather(A=A, B=B, y=y, title=’My experiment in factors A and B’)

A multi-column input (a pandas.DataFrame, e.g. a categorical factor expanded into several indicator columns) is gathered column by column.

Parameters:
Return type:

Expt

Unified design generation: generate_design() dispatcher.

This module provides a single entry point for creating any standard experimental design. It dispatches to specialised modules based on design_type and applies common post-processing (center points, replication, randomization, coded/actual mapping).

Examples

>>> from process_improve.experiments import generate_design, Factor
>>> factors = [
...     Factor(name="Temperature", low=150, high=200, units="degC"),
...     Factor(name="Pressure", low=1, high=5, units="bar"),
... ]
>>> result = generate_design(factors, design_type="full_factorial")
>>> result.n_runs
7
>>> result.design
process_improve.experiments.designs.generate_design(factors, design_type=None, budget=None, center_points=3, replicates=1, blocks=None, resolution=None, generators=None, alpha=None, cube='full', constraints=None, hard_to_change=None, random_seed=42)[source]#

Generate an experimental design matrix.

Parameters:
  • factors (list[Factor]) – Factor specifications. Each Factor has a name, type ("continuous", "categorical", "mixture"), low/high bounds (for continuous), levels (for categorical), and optional units.

  • design_type (str or None) – One of "full_factorial", "fractional_factorial", "plackett_burman", "box_behnken", "ccd", "dsd", "omars", "omars_ilp", "d_optimal", "i_optimal", "a_optimal", "mixture", "taguchi". If None, the design type is chosen automatically based on the factor count, budget, and constraints.

  • budget (int or None) – Maximum number of runs the experimenter can afford.

  • center_points (int) – Number of center-point replicates (default 3). For designs that embed their own center points (CCD, Box-Behnken), this parameter controls the count within the design structure.

  • replicates (int) – Number of full replicates of the design (default 1 = no replication).

  • blocks (int or None) – Number of blocks.

  • resolution (int or None) – Desired minimum resolution for fractional factorials (III=3, IV=4, V=5).

  • generators (list[str] or None) – Explicit generators for fractional factorials, e.g. ["D=ABC", "E=AC"].

  • alpha (str, float, or None) – Axial distance for CCD designs: "rotatable", "face_centered", "orthogonal", or a numeric value.

  • cube (str) – For CCD designs, how to build the cube (factorial) portion: "full" (default) uses the complete 2^k factorial; "fractional" uses a resolution-V (or higher) fractional factorial, keeping the run count practical for k >= 5. When "fractional" and generators is given, those generators define the cube; otherwise a minimum-aberration half-fraction is chosen automatically.

  • constraints (list[Constraint] or None) – Constraints on the factor space.

  • hard_to_change (list[str] or None) – Names of hard-to-change factors (triggers split-plot structure).

  • random_seed (int) – Seed for reproducible randomization (default 42).

Returns:

Contains design (coded Expt), design_actual (actual-units Expt), run_order, and design metadata (generators, defining relation, resolution, etc.).

Return type:

DesignResult

Raises:

ValueError – If design_type is unknown, or if factor/budget constraints cannot be satisfied.

Examples

>>> from process_improve.experiments import generate_design, Factor
>>> factors = [
...     Factor(name="T", low=150, high=200, units="degC"),
...     Factor(name="P", low=1, high=5, units="bar"),
... ]
>>> result = generate_design(factors, design_type="full_factorial")
>>> result.design_actual

Integer-programming generator for OMARS designs.

The constructive generator in process_improve.experiments.designs_omars (dispatch_omars) only builds the minimal conference-foldover member of the OMARS family (2k + 1 / 2k + 3 runs). That design is saturated for a full second-order model, so process_improve.experiments.analyze_omars() has no error degrees of freedom to work with. This module builds larger OMARS designs that leave error degrees of freedom, by selecting runs with an integer linear program (ILP).

Method#

Every design here is a foldover [H; -H; 0]: a half-design H, its mirror image -H, and a single centre run. The foldover structure makes three of the four OMARS-defining conditions hold automatically:

  • balance - h and -h cancel, so every main-effect column sums to zero;

  • main effects clear of the two-factor interactions - x_i x_a x_b is an odd function, so its contributions from h and -h cancel;

  • main effects clear of the pure quadratics - x_i x_j^2 is odd in x_i, so those contributions cancel too;

and the centre run makes every pure quadratic estimable (each x_i^2 column takes the value 0 there). The only condition that is not automatic is the mutual orthogonality of the main effects, which is linear in the binary “include this half-run” variables s_r: for each pair i < j, sum_r (x[r,i] x[r,j]) s_r = 0. The run count is 2 * sum_r s_r + 1.

So the ILP selects a half-design from the (3**k - 1) / 2 distinct non-mirror three-level runs subject to a handful of linear equalities - only k(k-1)/2 of them - which keeps it tractable up to seven factors. Because the coefficients are integers, the equalities are exact; the floating-point is_omars() re-check only guards against mistakes. A pure feasibility solve, however, returns an arbitrary OMARS design that is usually far from the most efficient member. To search for a high-quality design the solve is repeated with random linear objectives (a multistart): each random objective sends the solver to a different vertex of the feasibility polytope, so the retained designs span the high-D-efficiency / low-A members. The best is then chosen by a satisficing-and-dominance rule over D-efficiency and the maximum second-order correlation, following the selection philosophy of Nunez Ares and Goos (2020). This makes the generator competitive with their enumerated catalogue without consulting it.

This realises, for OMARS designs, the integer-programming construction of Nunez Ares and Goos (2020); the ILP-over-design-points framing is shared with their trend-robust run-order work (Nunez Ares and Goos, 2019). An exhaustively enumerated OMARS catalogue exists but is unlicensed and is not redistributed here. Only the (dominant) foldover OMARS family is generated; the rarer non-foldover members are a documented future extension.

References

  • Nunez Ares, J. and Goos, P. (2020). “Enumeration and multicriteria selection of orthogonal minimally aliased response surface designs.” Technometrics, 62(1):21-36.

  • Nunez Ares, J. and Goos, P. (2019). “An integer linear programming approach to find trend-robust run orders of experimental designs.” Journal of Quality Technology.

class process_improve.experiments.designs_omars_ilp.OmarsSearchReport(n_factors=0, half_pool_size=0, n_restarts=0, ilp_iterations=0, feasible_designs=0, run_size=0, total_solve_seconds=0.0)[source]#

Bases: object

Diagnostics from the ILP search, recorded on DesignResult.metadata.

Parameters:
  • n_factors (int)

  • half_pool_size (int)

  • n_restarts (int)

  • ilp_iterations (int)

  • feasible_designs (int)

  • run_size (int)

  • total_solve_seconds (float)

n_factors#

Number of factors.

Type:

int

half_pool_size#

Number of distinct non-mirror three-level runs the ILP chose from.

Type:

int

n_restarts#

Number of randomized-objective ILP solves the multistart was allowed (the actual count can be lower when early-stopping ends it).

Type:

int

ilp_iterations#

Number of ILP solves (the outer search iterations): the minimize-size probe, the baseline feasibility solve, and every randomized-objective restart.

Type:

int

feasible_designs#

Number of distinct verified OMARS designs found and ranked.

Type:

int

run_size#

Run count of the winning design.

Type:

int

total_solve_seconds#

Cumulative wall-clock time spent inside the ILP solver.

Type:

float

process_improve.experiments.designs_omars_ilp.solve_omars_ilp(half_pool, *, n_half=None, half_bounds=None, minimize_size=False, objective=None, exclude_solutions=None, solver_options=None)[source]#

Select a half-design from half_pool and return the foldover OMARS design.

Exactly one of n_half (exact half count) or half_bounds (inclusive (min, max) half count) sets the size constraint. The returned design has 2 * n_half + 1 runs.

Parameters:
  • half_pool (np.ndarray) – Candidate half-runs of shape (n_candidates, n_factors), coded to {-1, 0, +1} (see _half_pool()).

  • n_half (int, optional) – Exact number of half-runs to select.

  • half_bounds (tuple[int, int], optional) – Inclusive (min, max) half-run count.

  • minimize_size (bool, optional) – When True the objective minimises the half-run count (smallest feasible design); otherwise the solve is a pure feasibility search.

  • objective (np.ndarray, optional) – Per-candidate linear cost of shape (n_candidates,). When given, the solver minimises sum_r objective[r] * s_r instead of running a pure feasibility (or minimise-size) search. A random objective drives the solver to a different vertex of the OMARS-feasibility polytope, which is how generate_omars() samples diverse, high-quality designs. Takes precedence over minimize_size.

  • exclude_solutions (list[list[int]], optional) – Previously found half-index sets to forbid via no-good cuts.

  • solver_options (dict, optional) – {"msg": bool, "time_limit": int seconds}.

Returns:

(design or None, solver_status, chosen_half_indices). None means the solver returned no feasible selection.

Return type:

tuple

Raises:

ImportError – If PuLP (the ilp extra) is not installed.

process_improve.experiments.designs_omars_ilp.generate_omars(factors, *, n_runs=None, n_runs_range=None, selection_criterion='dominance', satisfice=None, center_runs=1, n_restarts=50, max_candidates=6, model='full_second_order', solver_options=None, tol=1e-09, random_seed=42, verify=True)[source]#

Generate a foldover OMARS design by integer-programming run selection.

Builds a three-level OMARS design large enough to leave error degrees of freedom for the chosen analysis model, so it can be analysed with process_improve.experiments.analyze_omars(). The design is a foldover [H; -H; 0] with 2*h + 1 runs (an odd run count). Regardless of model, the design is a genuine OMARS design: the main effects stay orthogonal to every second-order term (quadratics and interactions alike).

Parameters:
  • factors (list[Factor]) – At least three continuous factors.

  • n_runs (int, optional) – Exact (odd) run size. Must exceed the number of parameters in the chosen model (1 + 2k + k(k-1)/2 for "full_second_order", 1 + 2k for "main_quadratic"). If None a size is chosen automatically.

  • n_runs_range (tuple[int, int], optional) – Inclusive (min, max) run-size window to search when n_runs is None; the smallest feasible size is used.

  • selection_criterion ({"dominance", "d_efficiency", "min_second_order_correlation", "a_optimal"}) – How to choose among the feasible designs found. "dominance" (default) keeps the Pareto front on D-efficiency and the maximum second-order correlation, then prefers the smallest, most efficient design. "a_optimal" minimises the summed coefficient variance trace((X'X)^-1) of the sizing model (lower prediction variance on average), which is the natural choice when the design is judged on precision rather than on aliasing.

  • satisfice (dict, optional) – Acceptability thresholds applied before selection: a design is kept only if it clears every threshold. Supported keys are "d_efficiency" (a minimum, higher is better) and "max_second_order_correlation" (a maximum, lower is better), for example {"d_efficiency": 5.0, "max_second_order_correlation": 0.7}. A ValueError is raised if no enumerated design meets the thresholds.

  • center_runs (int, optional) – Number of centre runs in the design (at least one; the foldover already contributes one). Default 1.

  • n_restarts (int, optional) – Number of randomized-objective ILP solves used to search for a high-quality design. Each restart drives the solver to a different feasible OMARS design; the best one (by selection_criterion) is kept. Higher values explore more of the feasible set and approach the catalogue-optimal designs more closely, at a roughly linear cost in runtime. The search early-stops once the feasible set stops yielding new designs, so small factor counts finish quickly regardless. Default 50, which reaches catalogue-competitive D-efficiency for up to seven factors. Deterministic for a fixed random_seed.

  • max_candidates (int, optional) – Legacy alias retained for backward compatibility. It now sets a floor on n_restarts (the effective restart budget is max(n_restarts, max_candidates)), so calls that raised it to enumerate more designs still explore at least that many. Default 6.

  • model ({"full_second_order", "main_quadratic"}, optional) – The analysis model the design is sized for. "full_second_order" (default) leaves room for every two-factor interaction, so the smallest feasible design must exceed 1 + 2k + k(k-1)/2 runs. "main_quadratic" sizes for only the main effects and pure quadratics (1 + 2k parameters), admitting smaller designs such as a thirteen-run, four-factor OMARS; the interactions are still present in the design and confined to the second-order block, they are simply not part of the model the run count is chosen for. The D-efficiency reported in the metadata is read from this same model.

  • solver_options (dict, optional) – Passed to the solver: {"msg": bool, "time_limit": int seconds}.

  • tol (float, optional) – Tolerance for the floating-point is_omars() re-check.

  • random_seed (int, optional) – Seed for both the randomized-objective search (which design is found) and the run-order randomisation of the returned design. A fixed seed makes the whole call reproducible.

  • verify (bool, optional) – When True (default) every selected design is re-checked with is_omars() before it is accepted.

Returns:

The OMARS design, with ILP provenance and search diagnostics under metadata (family, sparsity, omars_search report, …).

Return type:

DesignResult

Raises:
  • ValueError – If fewer than three factors are given, the factor count exceeds the combinatorial cap, model is not recognised, n_runs is too small or even, or no feasible design is found.

  • ImportError – If PuLP (the ilp extra) is not installed.

Examples

>>> from process_improve.experiments import Factor, generate_omars, analyze_omars
>>> factors = [Factor(name=n, low=-1, high=1) for n in "ABCDE"]
>>> result = generate_omars(factors)
>>> result.metadata["omars_verified"]
True

Various factorial designs.

process_improve.experiments.designs_factorial.full_factorial(nfactors, names=None)[source]#

Create a full factorial (2^k) design for the case when there are nfactors [integer] number of factors.

The optional list of names can be provided. The entries in the list should be strings. If not provided, the names will be created.

Raises:

ValueError – If nfactors < 1 (the empty design is undefined) or nfactors > settings.max_factors_combinatorial (SEC-19 #268: a request for 2**40 rows is a memory-exhaustion attack, not a legitimate design).

Parameters:
  • nfactors (int)

  • names (list | None)

Return type:

list

Backwards-compatible re-exporter for process_improve.experiments.

The implementation now lives in process_improve.experiments._lm (ENG-23 / #305): the renamed file makes filename-ranked tooling (Jump-to-File, fuzzy search, codecov reports) less ambiguous about which models.py is being shown.

Every public name remains importable as before:

from process_improve.experiments.models import Model, lm, predict, summary, validate_formula_is_safe
class process_improve.experiments.models.Model(OLS_instance, model_spec, aliasing=None, name=None)[source]#

Bases: OLS

Just a thin wrapper around the OLS class from Statsmodels.

Parameters:
  • OLS_instance (Any)

  • model_spec (str)

  • aliasing (dict | None)

  • name (str | None)

name: str | None#
data: Expt | None#
aliasing: dict | None#
summary(alpha=0.05, print_to_screen=True)[source]#

Build the OLS summary table for this model and return it.

The returned object is the statsmodels summary instance, with the underlying self._OLS.summary() adjusted to label the residual standard error row. The method does NOT print anything by itself; the top-level summary() wrapper handles screen output via its own show flag. The alpha and print_to_screen arguments are unused and kept for backwards compatibility.

Parameters:
Return type:

Any

get_parameters(drop_intercept=True)[source]#

Get the parameter values; return them in a Pandas dataframe.

Parameters:

drop_intercept (bool)

Return type:

DataFrame

get_factor_names(level=1)[source]#

Get the factors in a model which correspond to a certain level.

1 : pure factors 2 : 2-factor interactions and quadratic terms 3 : 3-factor interactions and cubic terms 4 : etc

Parameters:

level (int)

Return type:

list[str]

get_response_name()[source]#

Get the name of the response variable from the model specification.

Return type:

str

get_title()[source]#

Get the model’s title, if it has one. Always returns a string.

Return type:

str

get_aliases(aliasing_up_to_level=2, drop_intercept=True, websafe=False)[source]#

Return a list, containing strings, representing the aliases of the fitted effects.

aliasing_up_to_level: up to which level of interactions shown

drop_intercept: default is True, but sometimes it is interesting to

know which effects are aliased with the intercept

websafe: default is False; if True, will print the first term

in the aliasing in bold, since that is the nominally estimated effect.

Parameters:
  • aliasing_up_to_level (int)

  • drop_intercept (bool | None)

  • websafe (bool | None)

Return type:

list

exception process_improve.experiments.models.UnsafeFormulaError[source]#

Bases: ValueError

Raised when a model formula contains tokens outside the safe Wilkinson subset.

Patsy and statsmodels evaluate each formula term as a Python expression, so a formula coming from an untrusted source (for example the fit_linear_model MCP tool) is a code-execution vector. validate_formula_is_safe() rejects anything that is not a plain Wilkinson formula over known data columns before it ever reaches patsy.

process_improve.experiments.models.forg(x, prec=3)[source]#

Yanked from the code for Statsmodels / iolib / summary.py and adjusted.

Formats x with prec significant/decimal digits, switching to the g format for very large or very small magnitudes. Any positive prec is supported; prec=3 and prec=4 reproduce the original widths.

Parameters:
Return type:

str

process_improve.experiments.models.lm(model_spec, data, name=None, alias_threshold=0.995)[source]#

Create a linear model.

Parameters:
  • model_spec (str)

  • data (Expt)

  • name (str | None)

  • alias_threshold (float | None)

Return type:

Model

process_improve.experiments.models.predict(model, **kwargs)[source]#

Make predictions from the model.

Parameters:
Return type:

Any

process_improve.experiments.models.summary(model, show=True, aliasing_up_to_level=3)[source]#

Print a summary to the screen of the model.

Appends, if there is any aliasing, a summary of those aliases, up to the (integer) level of interaction: aliasing_up_to_level.

Parameters:
  • model (Model)

  • show (bool | None)

  • aliasing_up_to_level (int)

Return type:

Any

process_improve.experiments.models.validate_formula_is_safe(formula, allowed_names, *, allow_transforms=False, allow_numpy=False)[source]#

Reject a model formula that is not a safe Wilkinson formula over allowed_names.

This is the guard for untrusted callers (e.g. the fit_linear_model tool). Patsy evaluates every formula term as a Python expression with builtins and numpy in scope, so a string such as y ~ I(__import__('os').system('id')) would execute arbitrary code.

By default only a plain Wilkinson formula is allowed:

  • identifiers that name an actual data column,

  • the operators ~ + - * : ^ and grouping parentheses,

  • integer literals (for powers like (A + B)**2) and whitespace.

Any quote, dot, comma, dunder, or unknown identifier (np, I, __import__, …) is rejected.

The optional flags relax this for trusted-but-still-validated callers. They switch on an AST-based check that admits a curated set of transforms while still rejecting attribute access, string literals, dunders, and any call other than the allowlisted ones:

  • allow_transforms - permit I(...) / Q(...) wrapping arithmetic over data columns (e.g. the quadratic shorthand’s I(A ** 2)).

  • allow_numpy - additionally permit a curated allowlist of element-wise numpy calls such as np.log(A) or np.power(A, 2).

Parameters:
  • formula (str) – The model formula in Wilkinson notation, e.g. "y ~ A*B".

  • allowed_names (Iterable[str]) – The legal identifier names, i.e. the columns present in the data.

  • allow_transforms (bool) – If true, permit I(...) / Q(...) transforms of column arithmetic.

  • allow_numpy (bool) – If true, permit a curated allowlist of element-wise np.<func> calls.

Raises:

UnsafeFormulaError – If formula is not a string, contains a __ dunder, or references a token / construct outside the permitted subset.

Return type:

None

process_improve.experiments.models.validate_identifier_is_safe(name)[source]#

Reject a column / response name that is not a plain Python identifier.

User-supplied names (design_matrix dict keys, response_column) are interpolated into a patsy formula, so a name such as "A); import os; (" is an injection vector. We require a bare identifier and forbid dunders.

Parameters:

name (object) – The candidate column or response name.

Raises:

UnsafeFormulaError – If name is not a string, contains __, or is not a plain identifier.

Return type:

None

Design evaluation: quality metrics for experimental designs.

Provides evaluate_design(), which computes properties and quality metrics of an existing design matrix. Supported metrics include efficiency values (D/I/G), prediction variance, VIF, condition number, power analysis, alias structure, confounding, resolution, defining relation, clear effects, minimum aberration, and degrees of freedom.

Example

>>> from process_improve.experiments import evaluate_design, generate_design, Factor
>>> factors = [Factor(name="A", low=0, high=10), Factor(name="B", low=0, high=10)]
>>> result = generate_design(factors, design_type="full_factorial", center_points=0)
>>> metrics = evaluate_design(result, model="interactions", metric=["d_efficiency", "vif"])
process_improve.experiments.evaluate.evaluate_design(design_matrix, model=None, metric='d_efficiency', effect_size=None, alpha=0.05, sigma=None, region='cuboidal', n_samples=100000, include_vertices=True, random_seed=42, fds_resolution=None)[source]#

Compute quality metrics for an experimental design.

Parameters:
  • design_matrix (DataFrame or DesignResult) – The design to evaluate. If a DesignResult is passed, the coded design matrix and any generator / defining-relation metadata are extracted automatically.

  • model (str or None) – Model type: "main_effects", "interactions", "quadratic", or an explicit patsy formula. None defaults to "interactions".

  • metric (str or list[str]) – One or more metric names to compute, or the special value "all" to compute every metric. Valid names: "d_efficiency", "i_efficiency", "g_efficiency", "a_optimality", "e_optimality", "correlation", "alias_matrix", "fds", "prediction_variance", "vif", "condition_number", "power", "degrees_of_freedom", "alias_structure", "confounding", "resolution", "defining_relation", "clear_effects", "minimum_aberration".

  • effect_size (float or None) – Expected effect size for power calculation. When None, a power curve over a range of effect sizes is returned instead.

  • alpha (float) – Significance level for power calculation (default 0.05).

  • sigma (float or None) – Estimated noise standard deviation. Defaults to 1.0 when needed but not provided.

  • region ({"cuboidal", "spherical"}) – Design region over which the region-based metrics (i_efficiency, g_efficiency, fds) integrate the prediction variance. "cuboidal" (default) is [-1, 1]^k; "spherical" is the ball of radius sqrt(k).

  • n_samples (int) – Number of random samples drawn over the region (default 100,000).

  • include_vertices (bool) – When True (default), all 2**k cube vertices are added to the region sample so the worst-case (G) value at a corner is represented.

  • random_seed (int) – Seed for the region sampler (full reproducibility).

  • fds_resolution (int or None) – Resolution of the dense FDS curve. When None (default) the fds metric returns only the coarse 11-point quantiles summary. When set (e.g. 200), a curve sub-dict with length-fds_resolution fraction / prediction_variance / scaled_prediction_variance arrays is added for smooth plotting; the endpoints are the minimum and maximum prediction variance.

Returns:

Results keyed by metric name. The structure of each value depends on the metric - see individual metric documentation.

Return type:

dict[str, Any]

Examples

>>> from process_improve.experiments import evaluate_design, generate_design, Factor
>>> factors = [Factor(name="A", low=0, high=10), Factor(name="B", low=0, high=10)]
>>> result = generate_design(factors, design_type="full_factorial", center_points=0)
>>> metrics = evaluate_design(result, model="main_effects", metric="d_efficiency")
>>> metrics["d_efficiency"]
100.0
process_improve.experiments.evaluate.evaluate_all(design_matrix, model=None, effect_size=None, alpha=0.05, sigma=None, region='cuboidal', n_samples=100000, include_vertices=True, random_seed=42, fds_resolution=None)[source]#

Compute every available metric for a design in one call.

Thin convenience wrapper around evaluate_design() with metric="all" so callers need not enumerate the metric list. All parameters have the same meaning as in evaluate_design().

Returns:

Results keyed by metric name (the union of every registered metric).

Return type:

dict[str, Any]

Parameters:
  • design_matrix (DataFrame | DesignResult)

  • model (str | None)

  • effect_size (float | None)

  • alpha (float)

  • sigma (float | None)

  • region (str)

  • n_samples (int)

  • include_vertices (bool)

  • random_seed (int)

  • fds_resolution (int | None)

See also

evaluate_design

Compute one or more named metrics.

Experiment analysis: fit models, ANOVA, diagnostics, residuals.

Provides analyze_experiment(), the main analytical workhorse for designed experiments (Tool 3 in the DOE tool architecture).

Uses statsmodels and scipy for the heavy lifting, with thin custom code for lack-of-fit, curvature test, Lenth’s method, pred-R², adequate precision, and confirmation run testing.

process_improve.experiments.analysis.build_formula(response, factors, model=None)[source]#

Build a patsy/statsmodels formula string.

Parameters:
  • response (str) – Name of the response column.

  • factors (list[str]) – Factor column names.

  • model (str or None) – "main_effects", "interactions", "quadratic", or an explicit formula string. None defaults to "interactions".

Returns:

A formula like "Y ~ A + B + A:B".

Return type:

str

class process_improve.experiments.analysis.AnalysisResult(ols_result=None, formula='', results=<factory>)[source]#

Bases: object

Container returned by analyze_experiment().

Holds the fitted OLS result and all requested analysis outputs.

Parameters:
  • ols_result (RegressionResultsWrapper)

  • formula (str)

  • results (dict[str, Any])

ols_result: RegressionResultsWrapper = None#
formula: str = ''#
results: dict[str, Any]#
process_improve.experiments.analysis.analyze_experiment(design_matrix, responses=None, model=None, analysis_type='anova', significance_level=0.05, transform=None, coding='coded', new_points=None, observed_at_new=None, response_column=None)[source]#

Fit models, run ANOVA, compute effects, diagnose residuals.

Parameters:
  • design_matrix (DataFrame) – Factor settings per run. May also contain the response column(s).

  • responses (DataFrame, Series, or None) – Response column(s). If None, response_column must name a column already present in design_matrix.

  • model (str or None) – "main_effects", "interactions", "quadratic", an explicit formula, or None (defaults to "interactions").

  • analysis_type (str or list[str]) – One or more of: "anova", "effects", "coefficients", "significance", "residual_diagnostics", "lack_of_fit", "curvature_test", "model_selection", "box_cox", "lenth_method", "confidence_intervals", "prediction", "confirmation_test".

  • significance_level (float) – Default 0.05.

  • transform (str or None) – "log", "sqrt", "inverse", "box_cox", or None.

  • coding (str) – "coded" or "actual".

  • new_points (DataFrame or None) – For prediction or confirmation testing.

  • observed_at_new (list[float] or None) – Observed values at new_points (for confirmation testing).

  • response_column (str or None) – Name of the response column when it lives inside design_matrix.

Returns:

Results keyed by analysis type. Always includes "model_summary" with R², adj-R², pred-R², and adequate precision.

Return type:

dict[str, Any]

Examples

>>> import pandas as pd
>>> from process_improve.experiments.analysis import analyze_experiment
>>> df = pd.DataFrame({
...     "A": [-1, 1, -1, 1], "B": [-1, -1, 1, 1],
...     "y": [28, 36, 18, 31],
... })
>>> result = analyze_experiment(df, response_column="y", analysis_type="coefficients")
>>> result["coefficients"][0]["term"]
'Intercept'

Design augmentation: extend or modify an existing experimental design.

Provides augment_design(), which takes an existing design matrix and augments it by adding runs (foldover, semifold, center points, axial points, D-optimal runs), upgrading to a response surface design, adding blocks, or replicating.

Example

>>> import pandas as pd
>>> from process_improve.experiments.augment import augment_design
>>> design = pd.DataFrame({"A": [-1, 1, -1, 1], "B": [-1, -1, 1, 1]})
>>> result = augment_design(design, augmentation_type="add_center_points", n_additional_runs=3)
>>> result["augmented_design"].shape
(7, 2)
process_improve.experiments.augment.augment_design(existing_design, augmentation_type, target_model=None, n_additional_runs=None, fold_on=None, alpha=None, generators=None)[source]#

Extend or modify an existing experimental design.

Parameters:
  • existing_design (DataFrame) – The current design matrix with factor columns in coded units (-1/+1).

  • augmentation_type (str) – One of "foldover", "semifold", "add_center_points", "add_axial_points", "add_runs_optimal", "upgrade_to_rsm", "add_blocks", "replicate".

  • target_model (str or None) – Desired model after augmentation: "main_effects", "interactions", "quadratic". Used by "add_runs_optimal" and "upgrade_to_rsm".

  • n_additional_runs (int or None) – Budget for additional runs. Interpretation depends on the augmentation type (number of center points, number of D-optimal runs, number of replicates, or number of blocks).

  • fold_on (str or None) – For "semifold" only: which factor to fold on. If None, the best factor is auto-selected.

  • alpha (str, float, or None) – Axial distance for "add_axial_points" and "upgrade_to_rsm". String values: "rotatable", "face_centered", "orthogonal". Or a numeric value.

  • generators (list[str] or None) – Generator strings from the original design (e.g. ["D=ABC"]). Needed for meaningful alias analysis in foldover/semifold.

Returns:

Keys include "augmented_design" (list of dicts), "new_runs" (list of dicts), "n_runs_before", "n_runs_after", "explanation" (narrative), "before_metrics", "after_metrics", and augmentation-specific keys.

Return type:

dict[str, Any]

Raises:

ValueError – If augmentation_type is unknown, or if required parameters are missing for the requested augmentation.

Examples

>>> import pandas as pd
>>> from process_improve.experiments.augment import augment_design
>>> design = pd.DataFrame({
...     "A": [-1, 1, -1, 1, -1, 1, -1, 1],
...     "B": [-1, -1, 1, 1, -1, -1, 1, 1],
...     "C": [-1, -1, -1, -1, 1, 1, 1, 1],
... })
>>> result = augment_design(design, "add_center_points", n_additional_runs=3)
>>> result["n_runs_after"]
11

Response optimization for designed experiments (Tool 4).

Find optimal factor settings for one or multiple responses after fitting a model with analyze_experiment() (Tool 3).

Implemented methods#

  • desirability - Derringer-Suich desirability functions (single and multi-response) with scipy.optimize.minimize (SLSQP).

  • steepest_ascent / steepest_descent - Move along the gradient of a first-order model from the design centre.

  • stationary_point - Locate the stationary point of a second-order model via numpy.linalg.solve.

  • canonical_analysis - Eigenvalue decomposition of the B matrix to classify the stationary point (max / min / saddle).

Stubs (not yet implemented)#

  • ridge_analysis - Trace the optimum along increasing radii.

  • pareto_front - Multi-objective Pareto frontier (NSGA-II).

process_improve.experiments.optimization.evaluate_model(coefficients, factor_names, point)[source]#

Evaluate predicted response at an arbitrary coded point.

Parameters:
  • coefficients (list[dict]) – Coefficient list from analyze_experiment.

  • factor_names (list[str]) – Ordered factor names.

  • point (dict[str, float]) – Factor settings in coded units, e.g. {"A": 0.5, "B": -1.0}.

Returns:

Predicted response value.

Return type:

float

process_improve.experiments.optimization.optimize_responses(fitted_models, goals=None, method='desirability', factor_ranges=None, step_size=0.5, n_steps=10, desirability_weights=None)[source]#

Find optimal factor settings for one or multiple responses.

Parameters:
  • fitted_models (list[dict]) –

    Each dict describes a fitted model with keys:

    • "response_name" (str) - name of the response.

    • "coefficients" (list[dict]) - coefficient list, each with "term" and "coefficient" keys as returned by analyze_experiment(..., analysis_type="coefficients").

    • "factor_names" (list[str]) - ordered factor names.

    • "mse_residual" (float, optional) - mean squared error.

    • "r_squared" (float, optional) - model R-squared.

  • goals (list[dict] or None) –

    Per-response optimisation goals. Each dict has keys:

    • "response" (str) - response name (must match a model).

    • "goal" (str) - "maximize", "minimize", or "target".

    • "target" (float, optional) - target value (required when goal="target").

    • "low" (float) - lower acceptable bound.

    • "high" (float) - upper acceptable bound.

    • "weight" (float, default 1) - desirability shape parameter.

    • "importance" (float, default 1) - relative importance for composite desirability.

  • method (str) – Optimisation method: "desirability", "steepest_ascent", "steepest_descent", "stationary_point", "canonical_analysis", "ridge_analysis" (stub), "pareto_front" (stub).

  • factor_ranges (dict or None) – Maps factor name to {"low": float, "high": float} in actual units. Used for coded ↔ actual conversion.

  • step_size (float) – Step magnitude for steepest ascent/descent (coded units).

  • n_steps (int) – Number of steps for steepest ascent/descent.

  • desirability_weights (list[float] or None) – Importance weights for composite desirability (overrides per-goal "importance" values).

Returns:

Results keyed by method. Always includes "method" and "factor_names".

Return type:

dict[str, Any]

Examples

>>> from process_improve.experiments.optimization import optimize_responses
>>> model = {
...     "response_name": "yield",
...     "coefficients": [
...         {"term": "Intercept", "coefficient": 40.0},
...         {"term": "A", "coefficient": 5.25},
...         {"term": "B", "coefficient": -2.0},
...         {"term": "I(A ** 2)", "coefficient": -3.0},
...         {"term": "I(B ** 2)", "coefficient": -1.5},
...         {"term": "A:B", "coefficient": 1.5},
...     ],
...     "factor_names": ["A", "B"],
... }
>>> result = optimize_responses(
...     fitted_models=[model],
...     method="stationary_point",
... )
>>> result["stationary_point"]["classification"]
'maximum'

Strategy Recommender#

Multi-stage experimental strategy recommender.

Given a DOE problem specification (factors, responses, budget, constraints, domain, prior knowledge), recommend a multi-stage experimental strategy using deterministic decision rules from Montgomery, NIST, and Stat-Ease SCOR.

Quick start:

from process_improve.experiments.strategy import recommend_strategy

result = recommend_strategy(
    factors=[Factor(name="A", low=0, high=100), ...],
    responses=[Response(name="Yield", goal="maximize")],
    budget=40,
    domain="fermentation",
)
process_improve.experiments.strategy.recommend_strategy(*, factors, responses=None, budget=None, constraints=None, hard_to_change_factors=None, prior_knowledge=None, existing_data=None, domain=None, detail_level='intermediate')[source]#

Recommend a multi-stage experimental strategy.

Given a DOE problem description, apply deterministic decision rules to recommend a staged experimental plan (screening → optimisation → confirmation).

Parameters:
  • factors (list[Factor]) – All candidate experimental factors.

  • responses (list[Response] or None) – Response variables with optimisation goals.

  • budget (int or None) – Total run budget across all stages. None = no constraint.

  • constraints (list[Constraint] or None) – Factor-space constraints (linear or nonlinear).

  • hard_to_change_factors (list[str] or None) – Factor names that are expensive to reset between runs.

  • prior_knowledge (str or None) – Free-text description of what the user already knows.

  • existing_data (DataFrame or None) – Prior experimental data (summary extracted internally).

  • domain (str or None) – Application domain (e.g. "fermentation"). Defaults to "general".

  • detail_level (str) – "novice" or "intermediate" (default).

Returns:

JSON-serialisable dictionary with the ExperimentalStrategy fields.

Return type:

dict

Examples

>>> from process_improve.experiments.factor import Factor, Response
>>> factors = [Factor(name=chr(65+i), low=0, high=100) for i in range(7)]
>>> result = recommend_strategy(factors=factors, budget=40, domain="fermentation")
>>> result["total_estimated_runs"] <= 40
True

Deterministic rule engine for DOE strategy recommendation.

Implements ~50 decision rules from Montgomery, NIST, and Stat-Ease SCOR to recommend multi-stage experimental strategies. No LLM or randomness - identical inputs always produce identical outputs.

process_improve.experiments.strategy.engine.recommend_strategy(*, factors, responses=None, budget=None, constraints=None, hard_to_change_factors=None, prior_knowledge=None, existing_data=None, domain=None, detail_level='intermediate')[source]#

Recommend a multi-stage experimental strategy.

Given a DOE problem description, apply deterministic decision rules to recommend a staged experimental plan (screening → optimisation → confirmation).

Parameters:
  • factors (list[Factor]) – All candidate experimental factors.

  • responses (list[Response] or None) – Response variables with optimisation goals.

  • budget (int or None) – Total run budget across all stages. None = no constraint.

  • constraints (list[Constraint] or None) – Factor-space constraints (linear or nonlinear).

  • hard_to_change_factors (list[str] or None) – Factor names that are expensive to reset between runs.

  • prior_knowledge (str or None) – Free-text description of what the user already knows.

  • existing_data (DataFrame or None) – Prior experimental data (summary extracted internally).

  • domain (str or None) – Application domain (e.g. "fermentation"). Defaults to "general".

  • detail_level (str) – "novice" or "intermediate" (default).

Returns:

JSON-serialisable dictionary with the ExperimentalStrategy fields.

Return type:

dict

Examples

>>> from process_improve.experiments.factor import Factor, Response
>>> factors = [Factor(name=chr(65+i), low=0, high=100) for i in range(7)]
>>> result = recommend_strategy(factors=factors, budget=40, domain="fermentation")
>>> result["total_estimated_runs"] <= 40
True

Pydantic models for the DOE strategy recommender.

Defines the input specification (DOEProblemSpec), the output (ExperimentalStrategy, ExperimentalStage, TransitionRule), and supporting types (DomainType, PriorKnowledge).

class process_improve.experiments.strategy.models.DomainType(value)[source]#

Bases: str, Enum

Application domain for domain-specific strategy adjustments.

pharma_formulation = 'pharma_formulation'#
fermentation = 'fermentation'#
food_science = 'food_science'#
extraction = 'extraction'#
analytical_method = 'analytical_method'#
cell_culture = 'cell_culture'#
bioprocess = 'bioprocess'#
general = 'general'#
class process_improve.experiments.strategy.models.PriorKnowledge(*, raw_text='', confidence=0.0, known_significant_factors=<factory>, known_ranges_reliable=False, has_supporting_data=False)[source]#

Bases: BaseModel

Parsed prior knowledge with a confidence score.

Parameters:
  • raw_text (str) – The original free-text description provided by the user.

  • confidence (float) – Confidence score between 0.0 (no knowledge) and 1.0 (confirmed).

  • known_significant_factors (list[str]) – Factor names identified as significant in the prior knowledge.

  • known_ranges_reliable (bool) – Whether the user’s factor ranges are informed by prior data.

  • has_supporting_data (bool) – Whether the prior knowledge is backed by experimental data.

raw_text: str#
confidence: float#
known_significant_factors: list[str]#
known_ranges_reliable: bool#
has_supporting_data: bool#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class process_improve.experiments.strategy.models.TransitionRule(*, condition, action, fallback)[source]#

Bases: BaseModel

Rule governing the transition between consecutive experimental stages.

Parameters:
  • condition (str) – Human-readable condition, e.g. "2-5 significant factors identified".

  • action (str) – Action to take when the condition is met, e.g. "proceed_to_rsm".

  • fallback (str) – Action if the condition is not met, e.g. "broaden_factor_ranges".

condition: str#
action: str#
fallback: str#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class process_improve.experiments.strategy.models.ExperimentalStage(*, stage_number, stage_name, design_type, design_params=<factory>, factors=<factory>, estimated_runs=0, purpose='', success_criteria=<factory>, transition_rules=<factory>)[source]#

Bases: BaseModel

One stage in a multi-stage experimental strategy.

Parameters:
  • stage_number (int) – 1-based stage index.

  • stage_name (str) – Human-readable name, e.g. "Screening", "Optimization".

  • design_type (str) – Design type key, e.g. "plackett_burman", "ccd", "bbd".

  • design_params (dict) – Design-specific parameters (resolution, center_points, alpha, etc.).

  • factors (list[str]) – Factor names involved in this stage.

  • estimated_runs (int) – Estimated number of experimental runs.

  • purpose (str) – Brief description of what this stage accomplishes.

  • success_criteria (dict) – Criteria for deeming this stage successful.

  • transition_rules (list[TransitionRule]) – Rules governing the transition to the next stage.

stage_number: int#
stage_name: str#
design_type: str#
design_params: dict[str, Any]#
factors: list[str]#
estimated_runs: int#
purpose: str#
success_criteria: dict[str, Any]#
transition_rules: list[TransitionRule]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class process_improve.experiments.strategy.models.ExperimentalStrategy(*, strategy_id='', stages=<factory>, total_estimated_runs=0, budget_allocation=<factory>, assumptions=<factory>, risks=<factory>, alternative_strategies=<factory>, domain='general', detail_level='intermediate', reasoning=<factory>)[source]#

Bases: BaseModel

Complete multi-stage experimental strategy recommendation.

Parameters:
  • strategy_id (str) – Deterministic hash of the input specification.

  • stages (list[ExperimentalStage]) – Ordered list of experimental stages.

  • total_estimated_runs (int) – Sum of estimated runs across all stages.

  • budget_allocation (dict[str, int]) – Stage name to allocated run count mapping.

  • assumptions (list[str]) – Key assumptions underlying the recommendation.

  • risks (list[str]) – Risks and potential issues with the strategy.

  • alternative_strategies (list[str]) – Brief descriptions of alternative approaches.

  • domain (str) – The domain used for domain-specific adjustments.

  • detail_level (str) – The detail level used for explanations.

  • reasoning (list[str]) – Step-by-step explanation of the decision logic.

strategy_id: str#
stages: list[ExperimentalStage]#
total_estimated_runs: int#
budget_allocation: dict[str, int]#
assumptions: list[str]#
risks: list[str]#
alternative_strategies: list[str]#
domain: str#
detail_level: str#
reasoning: list[str]#
model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class process_improve.experiments.strategy.models.DOEProblemSpec(*, factors, responses=<factory>, budget=None, constraints=None, hard_to_change_factors=None, prior_knowledge=None, existing_data_summary=None, domain=DomainType.general, detail_level='intermediate')[source]#

Bases: BaseModel

Validated input specification for the strategy recommender.

Wraps all inputs into a single object for pipeline processing.

Parameters:
  • factors (list[Factor]) – All candidate experimental factors.

  • responses (list[Response]) – Response variables with optimisation goals.

  • budget (int or None) – Total run budget across all stages.

  • constraints (list[Constraint] or None) – Factor-space constraints.

  • hard_to_change_factors (list[str] or None) – Factor names that are expensive to reset between runs.

  • prior_knowledge (PriorKnowledge or None) – Parsed prior knowledge with confidence score.

  • existing_data_summary (dict or None) – Summary of any existing experimental data.

  • domain (DomainType) – Application domain.

  • detail_level (str) – "novice" or "intermediate".

factors: list[Factor]#
responses: list[Response]#
budget: int | None#
constraints: list[Constraint] | None#
hard_to_change_factors: list[str] | None#
prior_knowledge: PriorKnowledge | None#
existing_data_summary: dict[str, Any] | None#
domain: DomainType#
detail_level: Literal['novice', 'intermediate']#
property n_factors: int#

Total number of factors.

property factor_names: list[str]#

Ordered list of factor names.

property n_continuous: int#

Number of continuous factors.

property n_categorical: int#

Number of categorical factors.

property n_mixture: int#

Number of mixture factors.

property has_mixture: bool#

Whether any mixture factors are present.

property has_hard_to_change: bool#

Whether any hard-to-change factors are specified.

property has_constraints: bool#

Whether any constraints are specified.

property goal_includes_optimization: bool#

Whether any response has an optimisation goal.

model_config = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

Domain-specific strategy templates for DOE recommendations.

Each domain template provides preferred design choices, budget weight adjustments, and domain-specific advice. Templates are Python dicts (not YAML) because they encode algorithmic adjustments, not reference data.

Sources:
  • ICH Q8/Q9/Q10 for pharma QbD

  • Stat-Ease SCOR framework

  • NIST Engineering Statistics Handbook section 5.3.3

  • Montgomery, Design and Analysis of Experiments, 10th ed.

process_improve.experiments.strategy.domain_templates.get_domain_template(domain)[source]#

Return the domain template for the given domain string.

Parameters:

domain (str) – Domain key (e.g. "fermentation"). Falls back to "general" if the key is not recognised.

Returns:

The domain template dictionary.

Return type:

dict

Budget allocation logic for multi-stage DOE strategies.

Implements the 25-40-55-15 framework:
  • Screening: 25-40 % of total budget

  • Optimisation: 40-55 %

  • Confirmation: 5-15 % (minimum 3 runs)

Sources:
  • Montgomery, Design and Analysis of Experiments, 10th ed. (25% rule)

  • Stat-Ease SCOR framework

  • NIST Engineering Statistics Handbook section 5.3.3

process_improve.experiments.strategy.budget.estimate_screening_runs(n_factors, design_type)[source]#

Estimate the number of runs for a screening design.

Parameters:
  • n_factors (int) – Number of factors to screen.

  • design_type (str) – One of "plackett_burman", "definitive_screening", "fractional_factorial", "full_factorial".

Returns:

Estimated run count including center points.

Return type:

int

process_improve.experiments.strategy.budget.estimate_rsm_runs(n_factors, design_type, center_points=3)[source]#

Estimate the number of runs for an RSM design.

Parameters:
  • n_factors (int) – Number of factors (typically 2-5 after screening).

  • design_type (str) – One of "ccd", "box_behnken", "ccd_face_centered", "d_optimal".

  • center_points (int) – Number of center point replicates (default 3).

Returns:

Estimated run count.

Return type:

int

process_improve.experiments.strategy.budget.estimate_confirmation_runs(min_runs=3)[source]#

Return the number of confirmation runs.

Parameters:

min_runs (int) – Minimum confirmation runs (default 3).

Returns:

Confirmation run count (always at least 3).

Return type:

int

process_improve.experiments.strategy.budget.allocate_budget(total_budget, n_factors, needs_screening, needs_rsm, screening_design='plackett_burman', rsm_design='box_behnken', domain_weights=None, min_confirmation=3, center_points=3)[source]#

Allocate a total run budget across experimental stages.

Parameters:
  • total_budget (int or None) – Total runs across all stages. If None, computes an ideal budget.

  • n_factors (int) – Total number of candidate factors.

  • needs_screening (bool) – Whether a screening stage is needed.

  • needs_rsm (bool) – Whether an RSM optimisation stage is needed.

  • screening_design (str) – Preferred screening design type.

  • rsm_design (str) – Preferred RSM design type.

  • domain_weights (dict or None) – Stage-to-fraction mapping from the domain template.

  • min_confirmation (int) – Minimum confirmation runs (domain-dependent).

  • center_points (int) – Center points for RSM design.

Returns:

Keys: "screening", "optimization", "confirmation", "total", "ideal_total", "is_tight", "warnings".

Return type:

dict