Source code for process_improve.experiments.designs_factorial

# (c) Kevin Dunn, 2010-2026. MIT License.

"""Various factorial designs."""

from process_improve.config import settings

from .structures import c, create_names, expand_grid


[docs] def full_factorial(nfactors: int, names: list | None = None) -> list: """Create a two-level full factorial (2^k) design in coded units. Parameters ---------- nfactors : int Number of factors, k. The design has ``2**k`` runs. names : list of str or None One name per factor. If ``None``, names are created (``A``, ``B``, ...). Returns ------- list[Column] One ``Column`` per factor, each of length ``2**k``, holding the coded settings -1 and +1 in standard (Yates) order. Raises ------ ValueError If ``nfactors < 1`` (the empty design is undefined), ``nfactors > settings.max_factors_combinatorial`` (SEC-19 #268: a request for 2**40 rows is a memory-exhaustion attack, not a legitimate design), or *names* does not hold ``nfactors`` distinct names. Examples -------- >>> from process_improve.experiments.designs_factorial import full_factorial >>> columns = full_factorial(2, names=["Temp", "Pressure"]) >>> len(columns), len(columns[0]) (2, 4) """ nfactors = int(nfactors) if nfactors < 1: raise ValueError(f"nfactors must be >= 1; got {nfactors}.") cap = settings.max_factors_combinatorial if nfactors > cap: raise ValueError( f"nfactors={nfactors} exceeds the SEC-19 combinatorial cap of {cap}; " f"a full factorial would require 2**{nfactors} rows. " "Increase settings.max_factors_combinatorial if intentional." ) if names is None: names = create_names(nfactors) names = list(names) if len(names) != nfactors or len(set(names)) != len(names): raise ValueError(f"names must hold nfactors={nfactors} distinct names; got {names}.") # Expand the full factorial out into variables return expand_grid(**{name: c(-1, +1) for name in names})