mixle.doe.factorial module

Classical factorial, screening, and response-surface experiment designs.

These complement the space-filling generators in mixle.doe.designs with the structured “named” designs of classical DOE: two-level fractional factorials and Plackett-Burman screening designs, and the central-composite and Box-Behnken response-surface designs.

Every generator takes per-dimension bounds (a sequence of (low, high) pairs, one per factor) and returns a (n_runs, d) array. The design is built in coded units – two-level factors at -1 / +1, response-surface axial/centre points relative to that – then mapped into bounds so -1 -> low, +1 -> high, 0 -> the midpoint. Pass coded=True to get the raw coded matrix instead (the natural input to the analysis routines in mixle.doe.analysis).

bounds is a hard requirement only for the factorial cube itself: central_composite()’s rotatable/orthogonal axial points are placed by a design-theory property, not clamped to bounds, and routinely extend past them on purpose – see its docstring and central_composite_point_kinds(). fractional_factorial()’s generator-string aliasing is likewise published, not just used internally, via generator_alias_structure().

fractional_factorial(bounds, generators, *, coded=False)[source]

Two-level fractional factorial 2**(k-p) from pyDOE-style generator strings.

generators names one column per factor as a product of base factors, e.g. "a b c ab ac" – a 2**(5-2) design whose factors d, e are deliberately aliased as d = ab, e = ac. Base factors are the distinct single letters; each token is the elementwise product of its letters, optionally negated with a leading -. The number of tokens must equal len(bounds).

Rejects a generators spec that collapses a factor to a constant column (repeated letters cancelling entirely, e.g. "aa") or that aliases two of the named factors with each other (two tokens whose reduced letter-words are identical or exact opposites, e.g. tokens "a" and "-a") – both silently produced a degenerate design with no diagnostic before (MXR-080-0179). This does not affect the intentional aliasing a fractional design is built on: a compound token like "ab" is fine and expected, it just cannot repeat another factor’s own word. Use generator_alias_structure() to see which factors are aliased with which interaction.

Returns a (2**k, d) design (k = number of base factors) mapped into bounds – or the raw coded +/-1 matrix if coded=True.

Parameters:
Return type:

ndarray

generator_alias_structure(generators, *, max_order=2)[source]

Return signed defining words and complete alias chains through max_order.

The result is a JSON-friendly record with:

  • factor_labels mapping every public positional factor name to its source token;

  • base_factors mapping each latent generator letter to its named factor and sign;

  • generators expressing every compound generator using those public factor names;

  • defining_relations and the complete generated defining_group as signed words; and

  • alias_sets for the identity and every effect through the requested interaction order.

A word record has an integer sign (+1 or -1) and a factors list. For example, "a b ab" defines I = +x0:x1:x2; through order two, x0 is aliased with +x1:x2 and x2 with +x0:x1. Negated base or compound tokens propagate their signs through every relation and alias. This supplies the actual confounding structure rather than only repeating the compound generator fragments (MXR-080-1477).

Every latent base letter must have one named singleton token so aliases can be mapped unambiguously to the positional xN labels used by mixle.doe.analysis. The generator syntax is otherwise validated exactly like fractional_factorial().

Parameters:
Return type:

dict[str, object]

plackett_burman(bounds, *, coded=False)[source]

Plackett-Burman two-level screening design for len(bounds) factors.

Returns N runs where N is the smallest multiple of four that is at least d + 1 (so the design is saturated or near-saturated, ideal for screening many factors in few runs). For N a power of two the design is a Hadamard matrix (a resolution-III fractional factorial); for N in {12, 20, 24} a known cyclic generator is used; otherwise N is rounded up to the next power of two so a design always exists. Main effects are mutually orthogonal but aliased with two-factor interactions – use it to find the few large effects, then follow up with a fuller design.

Parameters:
Return type:

ndarray

central_composite(bounds, *, center=4, alpha='rotatable', coded=False)[source]

Central-composite design (CCD) for fitting a full second-order response surface.

A CCD stacks three parts, in this row order (see central_composite_point_kinds() to recover which output row is which): the 2**d two-level factorial corners (estimate linear and interaction terms), 2*d axial / star points at distance alpha on each axis (estimate the pure-quadratic curvature), and center replicates at the centre (estimate pure error and curvature). alpha sets the axial distance:

  • "rotatable" (default) – alpha = (2**d)**0.25, so prediction variance depends only on distance from the centre;

  • "orthogonal" – the value making the second-order terms orthogonal (depends on center);

  • "face" (a face-centred CCD / CCF) – alpha = 1, keeping every run inside the cube;

  • a positive, finite float – used directly.

bounds defines the factorial cube (-1/+1 map to each factor’s low/high): the factorial-corner and centre rows are therefore always exactly within bounds. Axial points sit at coded distance alpha on a single axis, and for "rotatable"/"orthogonal" that distance is normally greater than 1 – a mathematical requirement of the rotatability/orthogonality property, not a clamp to bounds – so those rows routinely fall outside bounds on purpose. Only alpha="face" (or a numeric alpha <= 1) guarantees every row stays within bounds (MXR-080-0179); use central_composite_point_kinds() to tell the row kinds apart programmatically when that distinction matters to the caller.

Returns (2**d + 2*d + center, d) rows mapped into bounds (or coded if coded=True).

Parameters:
Return type:

ndarray

central_composite_point_kinds(bounds, *, center=4)[source]

Row-kind labels for central_composite()’s output, in the same row order.

Returns a length-(2**d + 2*d + center) string array of "factorial" / "axial" / "center" entries. "factorial" and "center" rows are always exactly within bounds; "axial" rows are guaranteed within bounds only when central_composite() was called with alpha="face" (or a numeric alpha <= 1) – see its docstring for why rotatable/ orthogonal axial points routinely extend past bounds on purpose (MXR-080-0179). Pass the same center used in the matching central_composite() call so the lengths line up, e.g.:

design = central_composite(bounds, center=4, alpha="rotatable")
kinds = central_composite_point_kinds(bounds, center=4)
in_bounds_rows = design[kinds != "axial"]
Parameters:
Return type:

ndarray

box_behnken(bounds, *, center=None, coded=False)[source]

Box-Behnken response-surface design (3 levels per factor, no corner runs).

For every pair of factors it runs the four (+/-1, +/-1) combinations with all other factors at the centre, plus center centre replicates. Unlike a CCD it never sets all factors to an extreme at once (no cube corners), which is useful when those combinations are expensive or infeasible, and it needs only three levels per factor. Every row is always within bounds (no analogue of a CCD’s rotatable axial points here). Requires d >= 3.

Returns (4 * C(d, 2) + center, d) rows mapped into bounds (or coded if coded=True).

Parameters:
Return type:

ndarray