process-improve 1.40.2__tar.gz → 1.44.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {process_improve-1.40.2 → process_improve-1.44.0}/PKG-INFO +1 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/pyproject.toml +1 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/__init__.py +5 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs.py +36 -7
- process_improve-1.44.0/src/process_improve/experiments/designs_omars.py +274 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs_response_surface.py +164 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/evaluate.py +429 -50
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/data/design_types.yaml +54 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/monitoring/control_charts.py +8 -6
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_pca.py +3 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_pls.py +3 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_preprocessing.py +11 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_tpls.py +1 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/univariate/metrics.py +5 -1
- {process_improve-1.40.2 → process_improve-1.44.0}/README.md +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/_extras.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/_linalg.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/_random.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/alignment_helpers.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/data_input.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/features.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/plotting.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/preprocessing.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/batch/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/bivariate/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/bivariate/_elbow_peak.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/bivariate/methods.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/bivariate/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/config.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/batch/batch-fake-data.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/batch/details.txt +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/batch/dryer.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/batch/nylon.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/experiments/boilingpot.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/experiments/test_doe1.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/monitoring/batch-yield-and-purity.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/monitoring/rubber-colour.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/C.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A3.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A6.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/LDPE.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/P.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/R.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/T.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/U.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/W.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/LDPE/Yhat_A6.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/NOTICE +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/extraction1.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/extraction2.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/extraction3.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/ftir1.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/ftir2.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/dtu-pectin/ftir3.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/kamyr.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tablet-spectra.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/SOURCE +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group1.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group2.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group3.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group4.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group5.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/process_conditions.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group1.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group2.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group3.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group4.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group5.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/datasets/multivariate/tpls-pyphi/quality_indicators.csv +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/_shared.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/box_cox.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/curvature.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/diagnostics.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/lack_of_fit.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/lenth.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/model_selection.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/ols_extractors.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_analyses/prediction.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_lm.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/analyze_experiment.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/augment_design.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/create_factorial_design.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/doe_knowledge.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/evaluate_design.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/fit_linear_model.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/generate_design.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/optimize_responses.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/recommend_strategy.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/_tools/visualize_doe.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/analysis.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/augment.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/datasets.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs_factorial.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs_mixture.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs_optimal.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs_screening.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs_utils.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/factor.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/api.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/data/concepts.yaml +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/data/decision_rules.yaml +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/data/diagnostics.yaml +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/engine.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/knowledge/models.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/models.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/optimal.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/optimization.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/simulations.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/strategy/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/strategy/budget.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/strategy/domain_templates.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/strategy/engine.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/strategy/models.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/structures.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/api.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/cube_plot.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/design_quality.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/diagnostics.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/effects.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/optimization_plots.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/registry.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/significance.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/square_plot.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/visualization/plots/surfaces.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/mcp_server.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/monitoring/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/monitoring/metrics.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/monitoring/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_base.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_common.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_diagnostics.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_limits.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_mbpca.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_mbpls.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_nipals.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_pca_pls.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/_resampling.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/methods.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/plots.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/multivariate/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/py.typed +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/regression/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/regression/_robust_regression.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/regression/methods.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/regression/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/simulation/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/simulation/context.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/simulation/model.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/simulation/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/tool_safety.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/tool_spec.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/univariate/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/univariate/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/adapters/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/adapters/base.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/adapters/echarts_adapter.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/adapters/plotly_adapter.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/charts/__init__.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/charts/boxplot.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/colors.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/raincloud.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/spec.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/themes.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/tools.py +0 -0
- {process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/visualization/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: process-improve
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.44.0
|
|
4
4
|
Summary: Designed Experiments; Latent Variables (PCA, PLS, multivariate methods with missing data); Process Monitoring; Batch data analysis.
|
|
5
5
|
Keywords: Designed Experiments,Latent Variables,PCA,PLS,Multivariate Data Analysis,Batch data analysis
|
|
6
6
|
Author: Kevin Dunn
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "process-improve"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.44.0"
|
|
4
4
|
description = 'Designed Experiments; Latent Variables (PCA, PLS, multivariate methods with missing data); Process Monitoring; Batch data analysis.'
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
{process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/__init__.py
RENAMED
|
@@ -4,7 +4,8 @@ from process_improve.experiments.analysis import analyze_experiment
|
|
|
4
4
|
from process_improve.experiments.augment import augment_design
|
|
5
5
|
from process_improve.experiments.designs import generate_design
|
|
6
6
|
from process_improve.experiments.designs_factorial import full_factorial
|
|
7
|
-
from process_improve.experiments.
|
|
7
|
+
from process_improve.experiments.designs_omars import is_omars, omars_properties
|
|
8
|
+
from process_improve.experiments.evaluate import evaluate_all, evaluate_design
|
|
8
9
|
from process_improve.experiments.factor import Constraint, DesignResult, Factor, Response, ResponseGoal
|
|
9
10
|
from process_improve.experiments.knowledge import doe_knowledge
|
|
10
11
|
from process_improve.experiments.models import Model, lm, predict, summary
|
|
@@ -33,13 +34,16 @@ __all__ = [
|
|
|
33
34
|
"augment_design",
|
|
34
35
|
"c",
|
|
35
36
|
"doe_knowledge",
|
|
37
|
+
"evaluate_all",
|
|
36
38
|
"evaluate_design",
|
|
37
39
|
"expand_grid",
|
|
38
40
|
"full_factorial",
|
|
39
41
|
"gather",
|
|
40
42
|
"generate_design",
|
|
43
|
+
"is_omars",
|
|
41
44
|
"lm",
|
|
42
45
|
"main_effects_plot",
|
|
46
|
+
"omars_properties",
|
|
43
47
|
"optimize_responses",
|
|
44
48
|
"predict",
|
|
45
49
|
"recommend_strategy",
|
{process_improve-1.40.2 → process_improve-1.44.0}/src/process_improve/experiments/designs.py
RENAMED
|
@@ -31,6 +31,7 @@ try:
|
|
|
31
31
|
from pyDOE3 import ff2n
|
|
32
32
|
except ImportError: # pragma: no cover - exercised via env-without-pyDOE3
|
|
33
33
|
from process_improve._extras import _MissingExtra
|
|
34
|
+
|
|
34
35
|
ff2n = _MissingExtra("pyDOE3", "expt") # type: ignore[assignment]
|
|
35
36
|
|
|
36
37
|
from process_improve.experiments.designs_utils import build_design_result
|
|
@@ -92,6 +93,9 @@ def _dispatch_ccd(
|
|
|
92
93
|
factors,
|
|
93
94
|
center_points=kwargs.get("center_points", 3),
|
|
94
95
|
alpha=kwargs.get("alpha"),
|
|
96
|
+
cube=kwargs.get("cube", "full"),
|
|
97
|
+
generators=kwargs.get("generators"),
|
|
98
|
+
resolution=kwargs.get("resolution"),
|
|
95
99
|
)
|
|
96
100
|
|
|
97
101
|
|
|
@@ -104,6 +108,15 @@ def _dispatch_dsd(
|
|
|
104
108
|
return dispatch_dsd(factors)
|
|
105
109
|
|
|
106
110
|
|
|
111
|
+
def _dispatch_omars(
|
|
112
|
+
factors: list[Factor],
|
|
113
|
+
**kwargs: Any, # noqa: ANN401
|
|
114
|
+
) -> tuple[np.ndarray, dict]:
|
|
115
|
+
from process_improve.experiments.designs_omars import dispatch_omars # noqa: PLC0415
|
|
116
|
+
|
|
117
|
+
return dispatch_omars(factors)
|
|
118
|
+
|
|
119
|
+
|
|
107
120
|
def _dispatch_d_optimal(
|
|
108
121
|
factors: list[Factor],
|
|
109
122
|
**kwargs: Any, # noqa: ANN401
|
|
@@ -178,6 +191,7 @@ _DESIGN_REGISTRY: dict[str, Callable[..., tuple[np.ndarray, dict]]] = {
|
|
|
178
191
|
"box_behnken": _dispatch_box_behnken,
|
|
179
192
|
"ccd": _dispatch_ccd,
|
|
180
193
|
"dsd": _dispatch_dsd,
|
|
194
|
+
"omars": _dispatch_omars,
|
|
181
195
|
"d_optimal": _dispatch_d_optimal,
|
|
182
196
|
"i_optimal": _dispatch_i_optimal,
|
|
183
197
|
"a_optimal": _dispatch_a_optimal,
|
|
@@ -255,6 +269,7 @@ def generate_design( # noqa: PLR0913
|
|
|
255
269
|
resolution: int | None = None,
|
|
256
270
|
generators: list[str] | None = None,
|
|
257
271
|
alpha: str | float | None = None,
|
|
272
|
+
cube: str = "full",
|
|
258
273
|
constraints: list[Constraint] | None = None,
|
|
259
274
|
hard_to_change: list[str] | None = None,
|
|
260
275
|
random_seed: int = 42,
|
|
@@ -271,8 +286,8 @@ def generate_design( # noqa: PLR0913
|
|
|
271
286
|
design_type : str or None
|
|
272
287
|
One of ``"full_factorial"``, ``"fractional_factorial"``,
|
|
273
288
|
``"plackett_burman"``, ``"box_behnken"``, ``"ccd"``, ``"dsd"``,
|
|
274
|
-
``"
|
|
275
|
-
``"taguchi"``.
|
|
289
|
+
``"omars"``, ``"d_optimal"``, ``"i_optimal"``, ``"a_optimal"``,
|
|
290
|
+
``"mixture"``, ``"taguchi"``.
|
|
276
291
|
If ``None``, the design type is chosen automatically based on the
|
|
277
292
|
factor count, budget, and constraints.
|
|
278
293
|
budget : int or None
|
|
@@ -293,6 +308,13 @@ def generate_design( # noqa: PLR0913
|
|
|
293
308
|
alpha : str, float, or None
|
|
294
309
|
Axial distance for CCD designs: ``"rotatable"``,
|
|
295
310
|
``"face_centered"``, ``"orthogonal"``, or a numeric value.
|
|
311
|
+
cube : str
|
|
312
|
+
For CCD designs, how to build the cube (factorial) portion:
|
|
313
|
+
``"full"`` (default) uses the complete 2^k factorial; ``"fractional"``
|
|
314
|
+
uses a resolution-V (or higher) fractional factorial, keeping the run
|
|
315
|
+
count practical for k >= 5. When ``"fractional"`` and *generators* is
|
|
316
|
+
given, those generators define the cube; otherwise a minimum-aberration
|
|
317
|
+
half-fraction is chosen automatically.
|
|
296
318
|
constraints : list[Constraint] or None
|
|
297
319
|
Constraints on the factor space.
|
|
298
320
|
hard_to_change : list[str] or None
|
|
@@ -331,10 +353,7 @@ def generate_design( # noqa: PLR0913
|
|
|
331
353
|
design_type = _auto_select(factors, budget, constraints, hard_to_change)
|
|
332
354
|
|
|
333
355
|
if design_type not in _DESIGN_REGISTRY:
|
|
334
|
-
raise ValueError(
|
|
335
|
-
f"Unknown design_type={design_type!r}. "
|
|
336
|
-
f"Choose from: {', '.join(sorted(_DESIGN_REGISTRY))}."
|
|
337
|
-
)
|
|
356
|
+
raise ValueError(f"Unknown design_type={design_type!r}. Choose from: {', '.join(sorted(_DESIGN_REGISTRY))}.")
|
|
338
357
|
|
|
339
358
|
# --- Dispatch ----------------------------------------------------------
|
|
340
359
|
dispatch_fn = _DESIGN_REGISTRY[design_type]
|
|
@@ -346,6 +365,7 @@ def generate_design( # noqa: PLR0913
|
|
|
346
365
|
"resolution": resolution,
|
|
347
366
|
"generators": generators,
|
|
348
367
|
"alpha": alpha,
|
|
368
|
+
"cube": cube,
|
|
349
369
|
"hard_to_change": hard_to_change,
|
|
350
370
|
"constraints": constraints,
|
|
351
371
|
}
|
|
@@ -355,7 +375,16 @@ def generate_design( # noqa: PLR0913
|
|
|
355
375
|
# --- Determine center-point handling -----------------------------------
|
|
356
376
|
# Designs that embed their own center points (CCD, Box-Behnken)
|
|
357
377
|
# already include them; don't add more.
|
|
358
|
-
designs_with_embedded_centers = {
|
|
378
|
+
designs_with_embedded_centers = {
|
|
379
|
+
"ccd",
|
|
380
|
+
"box_behnken",
|
|
381
|
+
"dsd",
|
|
382
|
+
"omars",
|
|
383
|
+
"mixture",
|
|
384
|
+
"d_optimal",
|
|
385
|
+
"i_optimal",
|
|
386
|
+
"a_optimal",
|
|
387
|
+
}
|
|
359
388
|
|
|
360
389
|
# Optimal designs from pyoptex produce a pre-optimized run order
|
|
361
390
|
# (especially important for split-plot). Skip randomization for these.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# (c) Kevin Dunn, 2010-2026. MIT License.
|
|
2
|
+
|
|
3
|
+
"""OMARS (Orthogonal Minimally Aliased Response Surface) designs.
|
|
4
|
+
|
|
5
|
+
OMARS designs are three-level designs (coded ``-1 / 0 / +1``) in which every
|
|
6
|
+
main effect is orthogonal to every other main effect **and** to all
|
|
7
|
+
second-order terms (the pure quadratics and the two-factor interactions),
|
|
8
|
+
confining all aliasing to the second-order block. They occupy the middle
|
|
9
|
+
ground between screening designs and full response-surface designs.
|
|
10
|
+
|
|
11
|
+
This module provides two things:
|
|
12
|
+
|
|
13
|
+
* :func:`dispatch_omars` - a constructive generator wired into
|
|
14
|
+
:func:`process_improve.experiments.generate_design` as
|
|
15
|
+
``design_type="omars"``. It builds the conference-matrix foldover family of
|
|
16
|
+
OMARS designs; the definitive screening design (DSD) is the minimal member
|
|
17
|
+
of that family, so the construction is shared with
|
|
18
|
+
:func:`process_improve.experiments.designs_response_surface.dispatch_dsd`.
|
|
19
|
+
* :func:`omars_properties` and :func:`is_omars` - dependency-free verifiers
|
|
20
|
+
that check the defining OMARS properties on *any* coded design matrix. Use
|
|
21
|
+
them to validate designs we generate, designs produced by a future
|
|
22
|
+
enumerator, or designs supplied from an external source.
|
|
23
|
+
|
|
24
|
+
A public catalogue of enumerated OMARS designs exists, but it is unlicensed
|
|
25
|
+
and is therefore **not** redistributed with this package. The verifiers here
|
|
26
|
+
let a user cross-check a generated design against such a catalogue offline.
|
|
27
|
+
|
|
28
|
+
References
|
|
29
|
+
----------
|
|
30
|
+
.. [1] Núñez Ares, J. and Goos, P. (2020). "Enumeration and multicriteria
|
|
31
|
+
selection of orthogonal minimally aliased response surface designs."
|
|
32
|
+
*Technometrics*, 62(1):21-36.
|
|
33
|
+
.. [2] Jones, B. and Nachtsheim, C. J. (2011). "A class of three-level
|
|
34
|
+
designs for definitive screening in the presence of second-order
|
|
35
|
+
effects." *Journal of Quality Technology*, 43(1):1-15.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
import itertools
|
|
41
|
+
from typing import TYPE_CHECKING
|
|
42
|
+
|
|
43
|
+
import numpy as np
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from process_improve.experiments.factor import Factor
|
|
47
|
+
|
|
48
|
+
# Default numerical tolerance for treating an inner product as zero. The
|
|
49
|
+
# constructive designs are integer-valued so exact-zero comparisons would also
|
|
50
|
+
# work, but a small tolerance keeps the verifiers usable on floating-point or
|
|
51
|
+
# slightly perturbed matrices.
|
|
52
|
+
_DEFAULT_TOL = 1e-9
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _second_order_terms(matrix: np.ndarray) -> tuple[np.ndarray, list[str]]:
|
|
56
|
+
"""Build the second-order model terms (quadratics + two-factor interactions).
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
matrix : np.ndarray
|
|
61
|
+
Coded design matrix of shape ``(n_runs, n_factors)``.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
tuple[np.ndarray, list[str]]
|
|
66
|
+
An ``(n_runs, n_terms)`` array whose columns are the ``k`` pure
|
|
67
|
+
quadratics ``x_i^2`` followed by the ``k (k - 1) / 2`` two-factor
|
|
68
|
+
interactions ``x_i x_j``, and a matching list of human-readable term
|
|
69
|
+
names.
|
|
70
|
+
"""
|
|
71
|
+
n_factors = matrix.shape[1]
|
|
72
|
+
columns: list[np.ndarray] = []
|
|
73
|
+
names: list[str] = []
|
|
74
|
+
for i in range(n_factors):
|
|
75
|
+
columns.append(matrix[:, i] * matrix[:, i])
|
|
76
|
+
names.append(f"x{i + 1}^2")
|
|
77
|
+
for i, j in itertools.combinations(range(n_factors), 2):
|
|
78
|
+
columns.append(matrix[:, i] * matrix[:, j])
|
|
79
|
+
names.append(f"x{i + 1}*x{j + 1}")
|
|
80
|
+
if not columns: # defensive: a zero-factor matrix has no second-order terms
|
|
81
|
+
return np.empty((matrix.shape[0], 0)), names
|
|
82
|
+
return np.column_stack(columns), names
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _max_abs_correlation(terms: np.ndarray) -> float:
|
|
86
|
+
"""Return the largest absolute pairwise correlation among the columns of *terms*.
|
|
87
|
+
|
|
88
|
+
Constant columns (zero variance) are skipped. Returns ``0.0`` when fewer
|
|
89
|
+
than two non-constant columns are present.
|
|
90
|
+
"""
|
|
91
|
+
if terms.shape[1] < 2:
|
|
92
|
+
return 0.0
|
|
93
|
+
centered = terms - terms.mean(axis=0, keepdims=True)
|
|
94
|
+
norms = np.linalg.norm(centered, axis=0)
|
|
95
|
+
keep = norms > 0
|
|
96
|
+
if keep.sum() < 2:
|
|
97
|
+
return 0.0
|
|
98
|
+
unit = centered[:, keep] / norms[keep]
|
|
99
|
+
corr = unit.T @ unit
|
|
100
|
+
off_diagonal = corr - np.diag(np.diag(corr))
|
|
101
|
+
return float(np.abs(off_diagonal).max())
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def omars_properties(matrix: np.ndarray, *, tol: float = _DEFAULT_TOL) -> dict:
|
|
105
|
+
"""Compute the OMARS-defining properties of a coded design matrix.
|
|
106
|
+
|
|
107
|
+
The three properties that define an OMARS design are:
|
|
108
|
+
|
|
109
|
+
1. **Three levels** - every entry is one of ``-1``, ``0`` or ``+1``.
|
|
110
|
+
2. **Orthogonal main effects** - the main-effect columns are mutually
|
|
111
|
+
orthogonal and balanced (each column sums to zero).
|
|
112
|
+
3. **Minimally aliased** - every main effect is orthogonal to every
|
|
113
|
+
second-order term (all quadratics and all two-factor interactions);
|
|
114
|
+
aliasing is confined to the second-order block.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
matrix : np.ndarray
|
|
119
|
+
Coded design matrix of shape ``(n_runs, n_factors)``.
|
|
120
|
+
tol : float
|
|
121
|
+
Absolute tolerance below which an inner product is treated as zero.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
dict
|
|
126
|
+
A report with the keys ``n_runs``, ``n_factors``, ``levels``,
|
|
127
|
+
``is_three_level``, ``quadratics_estimable``, ``is_balanced``,
|
|
128
|
+
``main_effects_orthogonal``, ``main_effects_clear_of_second_order``,
|
|
129
|
+
``max_main_effect_inner_product``,
|
|
130
|
+
``max_main_vs_second_order_inner_product``,
|
|
131
|
+
``max_second_order_correlation`` and ``is_omars``.
|
|
132
|
+
|
|
133
|
+
Notes
|
|
134
|
+
-----
|
|
135
|
+
``quadratics_estimable`` is ``True`` only when every factor takes the
|
|
136
|
+
middle (``0``) level at least once. A two-level design (e.g. a full
|
|
137
|
+
factorial) would otherwise pass the orthogonality checks but has constant,
|
|
138
|
+
inestimable quadratic terms, so it is not an OMARS design.
|
|
139
|
+
|
|
140
|
+
Examples
|
|
141
|
+
--------
|
|
142
|
+
>>> import numpy as np
|
|
143
|
+
>>> # The minimal 3-factor OMARS / DSD (conference-matrix foldover).
|
|
144
|
+
>>> from process_improve.experiments.factor import Factor
|
|
145
|
+
>>> from process_improve.experiments.designs_omars import dispatch_omars
|
|
146
|
+
>>> coded, _ = dispatch_omars([Factor(name=n, low=-1, high=1) for n in "ABC"])
|
|
147
|
+
>>> omars_properties(coded)["is_omars"]
|
|
148
|
+
True
|
|
149
|
+
"""
|
|
150
|
+
matrix = np.asarray(matrix, dtype=float)
|
|
151
|
+
n_runs, n_factors = matrix.shape
|
|
152
|
+
|
|
153
|
+
levels = sorted({round(float(v), 9) for v in np.unique(matrix)})
|
|
154
|
+
|
|
155
|
+
# Three-level validity: every entry is within tol of one of {-1, 0, +1}.
|
|
156
|
+
nearest = np.round(matrix)
|
|
157
|
+
is_three_level = bool(
|
|
158
|
+
np.all(np.abs(matrix - nearest) <= tol) and np.all(np.isin(nearest, (-1.0, 0.0, 1.0)))
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# OMARS factors are genuinely three-level: each must take the middle (0)
|
|
162
|
+
# level at least once, otherwise its pure quadratic is a constant column
|
|
163
|
+
# and cannot be estimated (as in a two-level factorial).
|
|
164
|
+
uses_middle_level = np.any(np.abs(matrix) <= tol, axis=0)
|
|
165
|
+
quadratics_estimable = bool(np.all(uses_middle_level))
|
|
166
|
+
|
|
167
|
+
column_sums = np.abs(matrix.sum(axis=0))
|
|
168
|
+
is_balanced = bool(np.all(column_sums <= tol))
|
|
169
|
+
|
|
170
|
+
# Main-effect orthogonality: off-diagonal of X'X.
|
|
171
|
+
gram = matrix.T @ matrix
|
|
172
|
+
me_off_diagonal = gram - np.diag(np.diag(gram))
|
|
173
|
+
max_me_inner = float(np.abs(me_off_diagonal).max()) if n_factors > 1 else 0.0
|
|
174
|
+
main_effects_orthogonal = max_me_inner <= tol
|
|
175
|
+
|
|
176
|
+
# Main effects clear of all second-order terms.
|
|
177
|
+
second_order, _ = _second_order_terms(matrix)
|
|
178
|
+
if second_order.shape[1] > 0:
|
|
179
|
+
cross = matrix.T @ second_order
|
|
180
|
+
max_me_vs_so = float(np.abs(cross).max())
|
|
181
|
+
else: # defensive: a zero-factor matrix has no second-order terms
|
|
182
|
+
max_me_vs_so = 0.0
|
|
183
|
+
main_effects_clear = max_me_vs_so <= tol
|
|
184
|
+
|
|
185
|
+
max_so_corr = _max_abs_correlation(second_order)
|
|
186
|
+
|
|
187
|
+
is_omars = bool(
|
|
188
|
+
is_three_level
|
|
189
|
+
and quadratics_estimable
|
|
190
|
+
and is_balanced
|
|
191
|
+
and main_effects_orthogonal
|
|
192
|
+
and main_effects_clear
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
"n_runs": int(n_runs),
|
|
197
|
+
"n_factors": int(n_factors),
|
|
198
|
+
"levels": levels,
|
|
199
|
+
"is_three_level": is_three_level,
|
|
200
|
+
"quadratics_estimable": quadratics_estimable,
|
|
201
|
+
"is_balanced": is_balanced,
|
|
202
|
+
"main_effects_orthogonal": main_effects_orthogonal,
|
|
203
|
+
"main_effects_clear_of_second_order": main_effects_clear,
|
|
204
|
+
"max_main_effect_inner_product": max_me_inner,
|
|
205
|
+
"max_main_vs_second_order_inner_product": max_me_vs_so,
|
|
206
|
+
"max_second_order_correlation": max_so_corr,
|
|
207
|
+
"is_omars": is_omars,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def is_omars(matrix: np.ndarray, *, tol: float = _DEFAULT_TOL) -> bool:
|
|
212
|
+
"""Return ``True`` iff *matrix* satisfies the OMARS-defining properties.
|
|
213
|
+
|
|
214
|
+
Convenience wrapper around :func:`omars_properties`.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
matrix : np.ndarray
|
|
219
|
+
Coded design matrix of shape ``(n_runs, n_factors)``.
|
|
220
|
+
tol : float
|
|
221
|
+
Absolute tolerance below which an inner product is treated as zero.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
bool
|
|
226
|
+
``True`` if the design is three-level with orthogonal main effects
|
|
227
|
+
that are clear of all second-order terms.
|
|
228
|
+
"""
|
|
229
|
+
return omars_properties(matrix, tol=tol)["is_omars"]
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def dispatch_omars(factors: list[Factor], *, verify: bool = True) -> tuple[np.ndarray, dict]:
|
|
233
|
+
"""Generate an OMARS design via the conference-matrix foldover construction.
|
|
234
|
+
|
|
235
|
+
The construction is shared with :func:`dispatch_dsd`: for ``k`` factors it
|
|
236
|
+
folds a conference matrix ``C`` of order ``m`` over its negative and adds a
|
|
237
|
+
center run, giving ``[C; -C; 0]``. ``m = k`` for even ``k`` (``2k + 1``
|
|
238
|
+
runs) and ``m = k + 1`` with the last column dropped for odd ``k``
|
|
239
|
+
(``2k + 3`` runs). This yields the minimal OMARS design for ``k`` factors;
|
|
240
|
+
a future enumerator will expand coverage to the larger, non-foldover
|
|
241
|
+
members of the OMARS family.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
factors : list[Factor]
|
|
246
|
+
Continuous factors (at least three).
|
|
247
|
+
verify : bool
|
|
248
|
+
When ``True`` (default) the generated matrix is checked with
|
|
249
|
+
:func:`is_omars` and the result is recorded in the metadata under
|
|
250
|
+
``"omars_verified"``. The check is cheap and guards against the
|
|
251
|
+
degraded orthogonality of the cyclic conference-matrix fallback.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
tuple[np.ndarray, dict]
|
|
256
|
+
The coded design matrix and metadata. Metadata includes
|
|
257
|
+
``"construction"`` (the conference-matrix construction used),
|
|
258
|
+
``"family"`` and, when *verify* is ``True``, ``"omars_verified"``.
|
|
259
|
+
|
|
260
|
+
Raises
|
|
261
|
+
------
|
|
262
|
+
ValueError
|
|
263
|
+
If fewer than three factors are supplied.
|
|
264
|
+
"""
|
|
265
|
+
from process_improve.experiments.designs_response_surface import dispatch_dsd # noqa: PLC0415
|
|
266
|
+
|
|
267
|
+
if len(factors) < 3:
|
|
268
|
+
raise ValueError("OMARS designs require at least 3 factors.")
|
|
269
|
+
|
|
270
|
+
coded_matrix, dsd_meta = dispatch_dsd(factors)
|
|
271
|
+
meta = {**dsd_meta, "family": "conference_foldover"}
|
|
272
|
+
if verify:
|
|
273
|
+
meta["omars_verified"] = is_omars(coded_matrix)
|
|
274
|
+
return coded_matrix, meta
|
|
@@ -17,6 +17,7 @@ try:
|
|
|
17
17
|
from pyDOE3 import bbdesign, ccdesign
|
|
18
18
|
except ImportError: # pragma: no cover - exercised via env-without-pyDOE3
|
|
19
19
|
from process_improve._extras import _MissingExtra
|
|
20
|
+
|
|
20
21
|
bbdesign = _MissingExtra("pyDOE3", "expt") # type: ignore[assignment]
|
|
21
22
|
ccdesign = _MissingExtra("pyDOE3", "expt") # type: ignore[assignment]
|
|
22
23
|
|
|
@@ -26,10 +27,13 @@ if TYPE_CHECKING:
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
27
28
|
|
|
28
29
|
|
|
29
|
-
def dispatch_ccd(
|
|
30
|
+
def dispatch_ccd( # noqa: PLR0913
|
|
30
31
|
factors: list[Factor],
|
|
31
32
|
center_points: int = 3,
|
|
32
33
|
alpha: str | float | None = None,
|
|
34
|
+
cube: str = "full",
|
|
35
|
+
generators: list[str] | None = None,
|
|
36
|
+
resolution: int | None = None,
|
|
33
37
|
) -> tuple[np.ndarray, dict]:
|
|
34
38
|
"""Generate a Central Composite Design (CCD).
|
|
35
39
|
|
|
@@ -43,6 +47,18 @@ def dispatch_ccd(
|
|
|
43
47
|
Axial distance. Accepted string values: ``"rotatable"``,
|
|
44
48
|
``"face_centered"``, ``"orthogonal"``. A numeric value sets
|
|
45
49
|
alpha directly. Defaults to ``"orthogonal"``.
|
|
50
|
+
cube : str
|
|
51
|
+
How to build the cube (factorial) portion: ``"full"`` (default) uses
|
|
52
|
+
the complete 2^k factorial; ``"fractional"`` uses a resolution-V (or
|
|
53
|
+
higher) fractional factorial, keeping the run count practical for
|
|
54
|
+
k >= 5.
|
|
55
|
+
generators : list[str] or None
|
|
56
|
+
Explicit cube generators (e.g. ``["E=ABCD"]``), used only when
|
|
57
|
+
``cube="fractional"``. When omitted, a minimum-aberration
|
|
58
|
+
half-fraction is chosen automatically.
|
|
59
|
+
resolution : int or None
|
|
60
|
+
Desired minimum cube resolution, used only when ``cube="fractional"``
|
|
61
|
+
and *generators* is not given.
|
|
46
62
|
|
|
47
63
|
Returns
|
|
48
64
|
-------
|
|
@@ -55,6 +71,11 @@ def dispatch_ccd(
|
|
|
55
71
|
``center`` parameter). The caller should set ``center_points=0`` in
|
|
56
72
|
``build_design_result`` to avoid adding duplicate center points.
|
|
57
73
|
"""
|
|
74
|
+
if cube == "fractional":
|
|
75
|
+
return _dispatch_ccd_fractional(factors, center_points, alpha, generators, resolution)
|
|
76
|
+
if cube != "full":
|
|
77
|
+
raise ValueError(f"cube must be 'full' or 'fractional', got {cube!r}.")
|
|
78
|
+
|
|
58
79
|
k = len(factors)
|
|
59
80
|
|
|
60
81
|
# Map alpha string to pyDOE3 face and alpha parameters.
|
|
@@ -97,6 +118,148 @@ def dispatch_ccd(
|
|
|
97
118
|
return coded_matrix, {"alpha_value": alpha_value, "face": face}
|
|
98
119
|
|
|
99
120
|
|
|
121
|
+
def _resolve_fractional_axial_distance(
|
|
122
|
+
alpha: str | float | None,
|
|
123
|
+
n_cube_runs: int,
|
|
124
|
+
k: int,
|
|
125
|
+
center_points: int,
|
|
126
|
+
) -> tuple[float, str]:
|
|
127
|
+
"""Axial (star-point) distance for a fractional-cube CCD.
|
|
128
|
+
|
|
129
|
+
Mirrors pyDOE3's :func:`star` formulas, but uses the actual number of
|
|
130
|
+
fractional cube runs *n_cube_runs* in place of the full ``2**k``.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
alpha : str, float, or None
|
|
135
|
+
``"face_centered"`` (alpha = 1), ``"rotatable"``
|
|
136
|
+
(alpha = n_cube_runs ** 0.25), ``"orthogonal"`` / None (the orthogonal
|
|
137
|
+
formula), or a numeric value used directly.
|
|
138
|
+
n_cube_runs : int
|
|
139
|
+
Number of runs in the (fractional) cube portion.
|
|
140
|
+
k : int
|
|
141
|
+
Number of factors.
|
|
142
|
+
center_points : int
|
|
143
|
+
Total number of center points; split between the cube and axial blocks
|
|
144
|
+
for the orthogonal-alpha formula.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
tuple[float, str]
|
|
149
|
+
The axial distance and a short label for the design metadata.
|
|
150
|
+
"""
|
|
151
|
+
if isinstance(alpha, (int, float)) and not isinstance(alpha, bool):
|
|
152
|
+
return float(alpha), "user"
|
|
153
|
+
|
|
154
|
+
alpha_lower = alpha.lower() if isinstance(alpha, str) else None
|
|
155
|
+
if alpha_lower in ("face_centered", "face centered", "ccf", "faced"):
|
|
156
|
+
return 1.0, "faced"
|
|
157
|
+
if alpha_lower in ("rotatable", "r"):
|
|
158
|
+
return float(n_cube_runs**0.25), "rotatable"
|
|
159
|
+
if alpha_lower in ("inscribed", "cci"):
|
|
160
|
+
raise ValueError(
|
|
161
|
+
"alpha='inscribed' is not supported with cube='fractional'; "
|
|
162
|
+
"use 'face_centered', 'rotatable', 'orthogonal', or a numeric alpha."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# "orthogonal", None, or any other string: orthogonal axial distance.
|
|
166
|
+
n_center_cube = center_points // 2
|
|
167
|
+
n_center_axial = center_points - n_center_cube
|
|
168
|
+
n_axial = 2 * k
|
|
169
|
+
a = (k * (1 + n_center_axial / n_axial) / (1 + n_center_cube / n_cube_runs)) ** 0.5
|
|
170
|
+
return float(a), "orthogonal"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _dispatch_ccd_fractional(
|
|
174
|
+
factors: list[Factor],
|
|
175
|
+
center_points: int,
|
|
176
|
+
alpha: str | float | None,
|
|
177
|
+
generators: list[str] | None,
|
|
178
|
+
resolution: int | None,
|
|
179
|
+
) -> tuple[np.ndarray, dict]:
|
|
180
|
+
"""Build a CCD whose cube portion is a resolution-V fractional factorial.
|
|
181
|
+
|
|
182
|
+
The cube is generated by reusing
|
|
183
|
+
:func:`process_improve.experiments.designs_screening.dispatch_fractional_factorial`,
|
|
184
|
+
then the axial (star) runs and the center runs are stacked on top.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
factors : list[Factor]
|
|
189
|
+
Continuous factors (at least 3).
|
|
190
|
+
center_points : int
|
|
191
|
+
Total number of center runs (added once, not split).
|
|
192
|
+
alpha : str, float, or None
|
|
193
|
+
Axial distance specification; see
|
|
194
|
+
:func:`_resolve_fractional_axial_distance`.
|
|
195
|
+
generators : list[str] or None
|
|
196
|
+
Explicit cube generators. When omitted, a minimum-aberration
|
|
197
|
+
half-fraction (last factor = product of all the others) is used.
|
|
198
|
+
resolution : int or None
|
|
199
|
+
Desired minimum cube resolution; used only when *generators* is None.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
tuple[np.ndarray, dict]
|
|
204
|
+
Coded design matrix and metadata, including ``alpha_value``,
|
|
205
|
+
``generators_used``, ``defining_relation``, and ``resolution``.
|
|
206
|
+
"""
|
|
207
|
+
from process_improve.experiments.designs_screening import dispatch_fractional_factorial # noqa: PLC0415
|
|
208
|
+
from process_improve.experiments.evaluate import ( # noqa: PLC0415
|
|
209
|
+
_defining_relation_from_generators,
|
|
210
|
+
_word_to_str,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
k = len(factors)
|
|
214
|
+
if k < 3:
|
|
215
|
+
raise ValueError("A fractional-cube CCD requires at least 3 factors; use cube='full' for fewer.")
|
|
216
|
+
|
|
217
|
+
factor_names = [f.name for f in factors]
|
|
218
|
+
|
|
219
|
+
if generators is None and resolution is None:
|
|
220
|
+
# Minimum-aberration half-fraction: last factor = product of all the others.
|
|
221
|
+
generators = [f"{factor_names[-1]}={''.join(factor_names[:-1])}"]
|
|
222
|
+
|
|
223
|
+
cube, frac_meta = dispatch_fractional_factorial(factors, resolution=resolution, generators=generators)
|
|
224
|
+
n_cube_runs = cube.shape[0]
|
|
225
|
+
|
|
226
|
+
# Record the cube's generators, defining relation, and (true) resolution.
|
|
227
|
+
used_generators = frac_meta.get("generators_used") or generators
|
|
228
|
+
res = frac_meta.get("resolution")
|
|
229
|
+
defining_relation: list[str] | None = None
|
|
230
|
+
if used_generators:
|
|
231
|
+
words = _defining_relation_from_generators(used_generators, factor_names)
|
|
232
|
+
defining_relation = [f"I={_word_to_str(w, factor_names)}" for w in words]
|
|
233
|
+
if words:
|
|
234
|
+
res = min(len(w) for w in words)
|
|
235
|
+
|
|
236
|
+
if res is not None and res < 5:
|
|
237
|
+
raise ValueError(
|
|
238
|
+
f"The fractional cube has resolution {res}, but a CCD needs resolution V or higher so the "
|
|
239
|
+
"full quadratic model is estimable. Supply resolution-V generators or use cube='full'."
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
axial_distance, face = _resolve_fractional_axial_distance(alpha, n_cube_runs, k, center_points)
|
|
243
|
+
|
|
244
|
+
# Axial (star) runs: 2k rows at +/- axial_distance, zeros elsewhere.
|
|
245
|
+
star = np.zeros((2 * k, k))
|
|
246
|
+
for i in range(k):
|
|
247
|
+
star[2 * i : 2 * i + 2, i] = (-axial_distance, axial_distance)
|
|
248
|
+
|
|
249
|
+
center = np.zeros((max(0, center_points), k))
|
|
250
|
+
|
|
251
|
+
coded_matrix = np.vstack([cube, star, center])
|
|
252
|
+
meta = {
|
|
253
|
+
"alpha_value": axial_distance,
|
|
254
|
+
"face": face,
|
|
255
|
+
"cube": "fractional",
|
|
256
|
+
"generators_used": used_generators,
|
|
257
|
+
"defining_relation": defining_relation,
|
|
258
|
+
"resolution": res,
|
|
259
|
+
}
|
|
260
|
+
return coded_matrix, meta
|
|
261
|
+
|
|
262
|
+
|
|
100
263
|
def dispatch_box_behnken(
|
|
101
264
|
factors: list[Factor],
|
|
102
265
|
center_points: int = 3,
|