process-improve 1.2.8__tar.gz → 1.3.2__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.
Files changed (134) hide show
  1. {process_improve-1.2.8 → process_improve-1.3.2}/PKG-INFO +3 -2
  2. {process_improve-1.2.8 → process_improve-1.3.2}/README.md +1 -1
  3. process_improve-1.3.2/process_improve/experiments/designs_response_surface.py +293 -0
  4. {process_improve-1.2.8 → process_improve-1.3.2}/pyproject.toml +3 -1
  5. process_improve-1.2.8/process_improve/experiments/designs_response_surface.py +0 -194
  6. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/__init__.py +0 -0
  7. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/__init__.py +0 -0
  8. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/alignment_helpers.py +0 -0
  9. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/data_input.py +0 -0
  10. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/features.py +0 -0
  11. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/plotting.py +0 -0
  12. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/preprocessing.py +0 -0
  13. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/batch/tools.py +0 -0
  14. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/bivariate/__init__.py +0 -0
  15. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/bivariate/methods.py +0 -0
  16. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/bivariate/tools.py +0 -0
  17. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/batch/batch-fake-data.csv +0 -0
  18. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/batch/details.txt +0 -0
  19. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/batch/dryer.csv +0 -0
  20. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/batch/nylon.csv +0 -0
  21. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/experiments/test_doe1.csv +0 -0
  22. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/monitoring/batch-yield-and-purity.csv +0 -0
  23. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/monitoring/rubber-colour.csv +0 -0
  24. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/C.csv +0 -0
  25. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A3.csv +0 -0
  26. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A6.csv +0 -0
  27. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/LDPE.csv +0 -0
  28. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/P.csv +0 -0
  29. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/R.csv +0 -0
  30. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/T.csv +0 -0
  31. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/U.csv +0 -0
  32. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/W.csv +0 -0
  33. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/LDPE/Yhat_A6.csv +0 -0
  34. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/kamyr.csv +0 -0
  35. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tablet-spectra.csv +0 -0
  36. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/SOURCE +0 -0
  37. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group1.csv +0 -0
  38. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group2.csv +0 -0
  39. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group3.csv +0 -0
  40. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group4.csv +0 -0
  41. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group5.csv +0 -0
  42. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/process_conditions.csv +0 -0
  43. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group1.csv +0 -0
  44. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group2.csv +0 -0
  45. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group3.csv +0 -0
  46. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group4.csv +0 -0
  47. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/properties_Group5.csv +0 -0
  48. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/datasets/multivariate/tpls-pyphi/quality_indicators.csv +0 -0
  49. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/docs/outline.txt +0 -0
  50. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/__init__.py +0 -0
  51. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/analysis.py +0 -0
  52. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/augment.py +0 -0
  53. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/datasets.py +0 -0
  54. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/designs.py +0 -0
  55. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/designs_factorial.py +0 -0
  56. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/designs_mixture.py +0 -0
  57. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/designs_optimal.py +0 -0
  58. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/designs_screening.py +0 -0
  59. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/designs_utils.py +0 -0
  60. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/evaluate.py +0 -0
  61. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/factor.py +0 -0
  62. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/__init__.py +0 -0
  63. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/api.py +0 -0
  64. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/data/concepts.yaml +0 -0
  65. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/data/decision_rules.yaml +0 -0
  66. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/data/design_types.yaml +0 -0
  67. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/data/diagnostics.yaml +0 -0
  68. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/engine.py +0 -0
  69. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/knowledge/models.py +0 -0
  70. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/models.py +0 -0
  71. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/optimal.py +0 -0
  72. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/optimization.py +0 -0
  73. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/simulations.py +0 -0
  74. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/strategy/__init__.py +0 -0
  75. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/strategy/budget.py +0 -0
  76. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/strategy/domain_templates.py +0 -0
  77. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/strategy/engine.py +0 -0
  78. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/strategy/models.py +0 -0
  79. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/structures.py +0 -0
  80. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/tools.py +0 -0
  81. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/__init__.py +0 -0
  82. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/adapters/__init__.py +0 -0
  83. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/adapters/base.py +0 -0
  84. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/adapters/echarts_adapter.py +0 -0
  85. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/adapters/plotly_adapter.py +0 -0
  86. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/api.py +0 -0
  87. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/colors.py +0 -0
  88. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/__init__.py +0 -0
  89. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/cube_plot.py +0 -0
  90. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/design_quality.py +0 -0
  91. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/diagnostics.py +0 -0
  92. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/effects.py +0 -0
  93. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/optimization_plots.py +0 -0
  94. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/registry.py +0 -0
  95. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/significance.py +0 -0
  96. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/plots/surfaces.py +0 -0
  97. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/spec.py +0 -0
  98. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/experiments/visualization/types.py +0 -0
  99. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/mcp_server.py +0 -0
  100. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/media/boilingpot.csv +0 -0
  101. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/media/distillate-flow.csv +0 -0
  102. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/media/oil-company-doe.csv +0 -0
  103. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/media/trade-off-table.html +0 -0
  104. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/media/trade-off-table.pdf +0 -0
  105. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/media/trade-off-table.png +0 -0
  106. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/monitoring/__init__.py +0 -0
  107. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/monitoring/control_charts.py +0 -0
  108. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/monitoring/metrics.py +0 -0
  109. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/monitoring/tools.py +0 -0
  110. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/multivariate/__init__.py +0 -0
  111. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/multivariate/methods.py +0 -0
  112. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/multivariate/plots.py +0 -0
  113. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/multivariate/tools.py +0 -0
  114. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/Example notebook.ipynb +0 -0
  115. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/Tablets.xlsx +0 -0
  116. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/__init__.py +0 -0
  117. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/alignment-and-pca-example.ipynb +0 -0
  118. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/batch-optimization.ipynb +0 -0
  119. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/batch_llm.py +0 -0
  120. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/batch_llm_multivariate.py +0 -0
  121. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/batch_music.py +0 -0
  122. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/batch/demo-multiplots.ipynb +0 -0
  123. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/experiments/case-studies.py +0 -0
  124. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/least-squares-modelling/Misguided-reliance-on-R2-alone.ipynb +0 -0
  125. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/notebooks_examples/multivariate/pca_example.py +0 -0
  126. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/regression/__init__.py +0 -0
  127. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/regression/methods.py +0 -0
  128. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/regression/tools.py +0 -0
  129. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/tool_spec.py +0 -0
  130. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/univariate/__init__.py +0 -0
  131. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/univariate/metrics.py +0 -0
  132. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/univariate/tools.py +0 -0
  133. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/visualization/__init__.py +0 -0
  134. {process_improve-1.2.8 → process_improve-1.3.2}/process_improve/visualization/plots.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: process-improve
3
- Version: 1.2.8
3
+ Version: 1.3.2
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
@@ -23,6 +23,7 @@ Requires-Dist: seaborn>=0.13.2
23
23
  Requires-Dist: statsmodels>=0.14.6
24
24
  Requires-Dist: tqdm>=4.67.1
25
25
  Requires-Dist: coverage>=7.13.1 ; extra == 'dev'
26
+ Requires-Dist: hypothesis>=6.100 ; extra == 'dev'
26
27
  Requires-Dist: matplotlib-stubs>=0.3.11 ; extra == 'dev'
27
28
  Requires-Dist: mypy>=1.19.1 ; extra == 'dev'
28
29
  Requires-Dist: pandas-stubs>=2.3.3.260113 ; extra == 'dev'
@@ -47,7 +48,7 @@ Description-Content-Type: text/markdown
47
48
 
48
49
  # Process Improvement using Data
49
50
 
50
- A Python package for multivariate data analysis, designed experiments, and process monitoring. Companion to the online textbook [Process Improvement using Data](https://learnche.org/pid).
51
+ A Python package for multivariate data analysis, designed experiments, and process monitoring. Companion to the online textbook [Process Improvement using Data](https://learnche.org/pid). This package also powers the statistical engine behind [factori.al](https://factori.al).
51
52
 
52
53
  ## Installation
53
54
 
@@ -1,6 +1,6 @@
1
1
  # Process Improvement using Data
2
2
 
3
- A Python package for multivariate data analysis, designed experiments, and process monitoring. Companion to the online textbook [Process Improvement using Data](https://learnche.org/pid).
3
+ A Python package for multivariate data analysis, designed experiments, and process monitoring. Companion to the online textbook [Process Improvement using Data](https://learnche.org/pid). This package also powers the statistical engine behind [factori.al](https://factori.al).
4
4
 
5
5
  ## Installation
6
6
 
@@ -0,0 +1,293 @@
1
+ # (c) Kevin Dunn, 2010-2026. MIT License.
2
+
3
+ """Response surface designs: CCD, Box-Behnken, Definitive Screening Design.
4
+
5
+ All functions accept a list of ``Factor`` objects and return a raw coded
6
+ numpy array. Post-processing is handled by ``designs_utils.build_design_result``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import TYPE_CHECKING
13
+
14
+ import numpy as np
15
+ from pyDOE3 import bbdesign, ccdesign
16
+
17
+ if TYPE_CHECKING:
18
+ from process_improve.experiments.factor import Factor
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def dispatch_ccd(
24
+ factors: list[Factor],
25
+ center_points: int = 3,
26
+ alpha: str | float | None = None,
27
+ ) -> tuple[np.ndarray, dict]:
28
+ """Generate a Central Composite Design (CCD).
29
+
30
+ Parameters
31
+ ----------
32
+ factors : list[Factor]
33
+ Continuous factors.
34
+ center_points : int
35
+ Number of center points (split between cube and axial portions).
36
+ alpha : str, float, or None
37
+ Axial distance. Accepted string values: ``"rotatable"``,
38
+ ``"face_centered"``, ``"orthogonal"``. A numeric value sets
39
+ alpha directly. Defaults to ``"orthogonal"``.
40
+
41
+ Returns
42
+ -------
43
+ tuple[np.ndarray, dict]
44
+ Coded design matrix and metadata (includes ``alpha_value``).
45
+
46
+ Notes
47
+ -----
48
+ Center points are embedded in the CCD structure itself (via pyDOE3's
49
+ ``center`` parameter). The caller should set ``center_points=0`` in
50
+ ``build_design_result`` to avoid adding duplicate center points.
51
+ """
52
+ k = len(factors)
53
+
54
+ # Map alpha string to pyDOE3 face and alpha parameters.
55
+ # pyDOE3 alpha accepts: "orthogonal"/"o", "rotatable"/"r"
56
+ # pyDOE3 face accepts: "circumscribed"/"ccc", "inscribed"/"cci", "faced"/"ccf"
57
+ face = "circumscribed"
58
+ alpha_str = "orthogonal"
59
+ if isinstance(alpha, str):
60
+ alpha_lower = alpha.lower()
61
+ if alpha_lower in ("face_centered", "face centered", "ccf", "faced"):
62
+ face = "faced"
63
+ alpha_str = "orthogonal"
64
+ elif alpha_lower in ("inscribed", "cci"):
65
+ face = "inscribed"
66
+ alpha_str = "orthogonal"
67
+ elif alpha_lower in ("rotatable", "r"):
68
+ face = "circumscribed"
69
+ alpha_str = "rotatable"
70
+ else:
71
+ # "orthogonal" or default
72
+ face = "circumscribed"
73
+ alpha_str = "orthogonal"
74
+ elif isinstance(alpha, (int, float)):
75
+ alpha_str = "orthogonal"
76
+ face = "circumscribed"
77
+
78
+ # Split center points between cube and axial portions
79
+ n_center_cube = max(1, center_points // 2)
80
+ n_center_axial = max(1, center_points - n_center_cube)
81
+
82
+ coded_matrix = ccdesign(k, center=(n_center_cube, n_center_axial), alpha=alpha_str, face=face)
83
+
84
+ # Determine actual alpha value used
85
+ alpha_value: float | None = None
86
+ if face == "ccf":
87
+ alpha_value = 1.0
88
+ elif coded_matrix.shape[0] > 0:
89
+ alpha_value = float(np.max(np.abs(coded_matrix)))
90
+
91
+ return coded_matrix, {"alpha_value": alpha_value, "face": face}
92
+
93
+
94
+ def dispatch_box_behnken(
95
+ factors: list[Factor],
96
+ center_points: int = 3,
97
+ ) -> tuple[np.ndarray, dict]:
98
+ """Generate a Box-Behnken design.
99
+
100
+ Parameters
101
+ ----------
102
+ factors : list[Factor]
103
+ Continuous factors (requires at least 3).
104
+ center_points : int
105
+ Number of center point replicates.
106
+
107
+ Returns
108
+ -------
109
+ tuple[np.ndarray, dict]
110
+ Coded design matrix (-1 / 0 / +1) and metadata.
111
+
112
+ Notes
113
+ -----
114
+ Center points are embedded in the BB structure. The caller should set
115
+ ``center_points=0`` in ``build_design_result``.
116
+ """
117
+ k = len(factors)
118
+ if k < 3:
119
+ raise ValueError("Box-Behnken designs require at least 3 factors.")
120
+ coded_matrix = bbdesign(k, center=center_points)
121
+ return coded_matrix, {}
122
+
123
+
124
+ def dispatch_dsd(factors: list[Factor]) -> tuple[np.ndarray, dict]:
125
+ """Generate a Definitive Screening Design (DSD).
126
+
127
+ Follows the conference-matrix-based construction of Jones & Nachtsheim
128
+ (2011). For *k* factors the DSD has ``2k + 1`` runs when *k* is even
129
+ (using a conference matrix of order *k*) and ``2k + 3`` runs when *k*
130
+ is odd (using a conference matrix of order ``k + 1`` and dropping the
131
+ last column; Xiao, Lin & Bai 2012). The design can estimate all main
132
+ effects and quadratic effects and detect two-factor interactions with
133
+ minimal confounding, provided the underlying conference matrix is
134
+ genuine (``C.T @ C == (m-1) * I``).
135
+
136
+ The Paley construction used by :func:`_conference_matrix` produces a
137
+ genuine conference matrix whenever ``m - 1`` is an odd prime. For other
138
+ *m* the function falls back to a cyclic approximation and logs a
139
+ warning; the resulting DSD will still run but its main-effects
140
+ orthogonality may be degraded.
141
+
142
+ Parameters
143
+ ----------
144
+ factors : list[Factor]
145
+ Continuous factors.
146
+
147
+ Returns
148
+ -------
149
+ tuple[np.ndarray, dict]
150
+ Coded design matrix and metadata. Metadata includes the name of
151
+ the conference-matrix construction that was used.
152
+
153
+ References
154
+ ----------
155
+ .. [1] Jones, B. and Nachtsheim, C. J. (2011). "A class of three-level
156
+ designs for definitive screening in the presence of second-order
157
+ effects." *Journal of Quality Technology*, 43(1):1-15.
158
+ .. [2] Xiao, L., Lin, D. K. J. and Bai, F. (2012). "Constructing
159
+ definitive screening designs using conference matrices." *Journal
160
+ of Quality Technology*, 44(1):2-8.
161
+ """
162
+ k = len(factors)
163
+ if k < 3:
164
+ raise ValueError("Definitive Screening Designs require at least 3 factors.")
165
+
166
+ # For even k, use a conference matrix of order k (-> 2k + 1 runs).
167
+ # For odd k, use a conference matrix of order k + 1 and drop the last
168
+ # column so the design has k factors and 2(k+1) + 1 = 2k + 3 runs.
169
+ m = k if k % 2 == 0 else k + 1
170
+ C, construction = _conference_matrix(m)
171
+
172
+ zero_row = np.zeros((1, m))
173
+ coded_matrix = np.vstack([C, -C, zero_row])
174
+
175
+ if k % 2 == 1:
176
+ coded_matrix = coded_matrix[:, :k]
177
+
178
+ return coded_matrix, {"construction": construction}
179
+
180
+
181
+ def _is_prime(n: int) -> bool:
182
+ """Return True iff *n* is a (positive) prime."""
183
+ if n < 2:
184
+ return False
185
+ if n < 4:
186
+ return True
187
+ if n % 2 == 0:
188
+ return False
189
+ i = 3
190
+ while i * i <= n:
191
+ if n % i == 0:
192
+ return False
193
+ i += 2
194
+ return True
195
+
196
+
197
+ def _paley_conference_matrix(q: int) -> np.ndarray:
198
+ """Build a conference matrix of order ``q + 1`` via Paley's construction.
199
+
200
+ Requires *q* to be an odd prime. Works for both ``q ≡ 1 (mod 4)``
201
+ (symmetric conference matrix, "Paley type II") and ``q ≡ 3 (mod 4)``
202
+ (skew-symmetric conference matrix, "Paley type I"). In both cases
203
+ ``C.T @ C == q * I``.
204
+
205
+ Parameters
206
+ ----------
207
+ q : int
208
+ An odd prime.
209
+
210
+ Returns
211
+ -------
212
+ np.ndarray
213
+ ``(q + 1) x (q + 1)`` matrix with 0s on the diagonal and ±1
214
+ off-diagonal.
215
+ """
216
+ # Legendre symbol χ : GF(q) -> {-1, 0, 1}
217
+ quadratic_residues = {(x * x) % q for x in range(1, q)}
218
+ chi = np.zeros(q, dtype=int)
219
+ for x in range(1, q):
220
+ chi[x] = 1 if x in quadratic_residues else -1
221
+
222
+ # Jacobsthal matrix Q[a, b] = χ(b - a)
223
+ q_matrix = np.zeros((q, q), dtype=int)
224
+ for a in range(q):
225
+ for b in range(q):
226
+ q_matrix[a, b] = chi[(b - a) % q]
227
+
228
+ n = q + 1
229
+ c_matrix = np.zeros((n, n), dtype=int)
230
+ c_matrix[0, 1:] = 1
231
+ if q % 4 == 1:
232
+ # Symmetric Paley conference matrix.
233
+ c_matrix[1:, 0] = 1
234
+ else:
235
+ # Skew-symmetric Paley conference matrix (q ≡ 3 mod 4).
236
+ c_matrix[1:, 0] = -1
237
+ c_matrix[1:, 1:] = q_matrix
238
+ return c_matrix
239
+
240
+
241
+ def _cyclic_conference_matrix(k: int) -> np.ndarray:
242
+ """Legacy cyclic approximation of a conference matrix.
243
+
244
+ Does **not** satisfy ``C.T @ C == (k - 1) * I`` in general; used only as
245
+ a fallback when no Paley construction is available for the requested
246
+ order.
247
+ """
248
+ c_matrix = np.zeros((k, k))
249
+ half = (k - 1) // 2
250
+ sequence = [1] * half + [-1] * (k - 1 - half)
251
+ for i in range(k):
252
+ for j in range(k):
253
+ if i != j:
254
+ idx = (j - i - 1) % (k - 1) if j > i else (j - i) % (k - 1)
255
+ c_matrix[i, j] = sequence[idx]
256
+ return c_matrix
257
+
258
+
259
+ def _conference_matrix(m: int) -> tuple[np.ndarray, str]:
260
+ """Construct an ``m x m`` conference matrix.
261
+
262
+ Uses Paley's construction when ``m - 1`` is an odd prime (covering
263
+ ``m ∈ {4, 6, 8, 12, 14, 18, 20, 24, 30, 32, 38, 42, 44, 48, 54, 60, 62,
264
+ 68, 72, 74, 80, 84, 90, 98, ...}``), which returns a genuine conference
265
+ matrix with ``C.T @ C == (m - 1) * I``. For other orders (e.g.
266
+ ``m ∈ {10, 16, 22, 26, 28, 34, 36, 40, ...}``) no Paley construction
267
+ with a prime *q* is available; the function falls back to a cyclic
268
+ approximation and logs a warning.
269
+
270
+ Parameters
271
+ ----------
272
+ m : int
273
+ Desired order of the conference matrix.
274
+
275
+ Returns
276
+ -------
277
+ tuple[np.ndarray, str]
278
+ The matrix and a short string identifying the construction used
279
+ (e.g. ``"paley_q=13"`` or ``"cyclic_fallback"``).
280
+ """
281
+ q = m - 1
282
+ if q >= 3 and q % 2 == 1 and _is_prime(q):
283
+ return _paley_conference_matrix(q).astype(float), f"paley_q={q}"
284
+
285
+ logger.warning(
286
+ "No Paley conference-matrix construction known for order m=%d "
287
+ "(q = m - 1 = %d is not an odd prime); falling back to a cyclic "
288
+ "approximation. The resulting DSD's main-effects orthogonality "
289
+ "may be degraded.",
290
+ m,
291
+ q,
292
+ )
293
+ return _cyclic_conference_matrix(m), "cyclic_fallback"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "process-improve"
3
- version = "1.2.8"
3
+ version = "1.3.2"
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"
@@ -33,6 +33,7 @@ dependencies = [
33
33
  mcp = ["mcp>=1.0"]
34
34
  dev = [
35
35
  "coverage>=7.13.1",
36
+ "hypothesis>=6.100",
36
37
  "matplotlib-stubs>=0.3.11",
37
38
  "mypy>=1.19.1",
38
39
  "pandas-stubs>=2.3.3.260113",
@@ -64,6 +65,7 @@ build-backend = "uv_build"
64
65
  [dependency-groups]
65
66
  dev = [
66
67
  "coverage>=7.13.1",
68
+ "hypothesis>=6.100",
67
69
  "matplotlib-stubs>=0.3.11",
68
70
  "mypy>=1.19.1",
69
71
  "pandas-stubs>=2.3.3.260113",
@@ -1,194 +0,0 @@
1
- # (c) Kevin Dunn, 2010-2026. MIT License.
2
-
3
- """Response surface designs: CCD, Box-Behnken, Definitive Screening Design.
4
-
5
- All functions accept a list of ``Factor`` objects and return a raw coded
6
- numpy array. Post-processing is handled by ``designs_utils.build_design_result``.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import TYPE_CHECKING
12
-
13
- import numpy as np
14
- from pyDOE3 import bbdesign, ccdesign
15
-
16
- if TYPE_CHECKING:
17
- from process_improve.experiments.factor import Factor
18
-
19
-
20
- def dispatch_ccd(
21
- factors: list[Factor],
22
- center_points: int = 3,
23
- alpha: str | float | None = None,
24
- ) -> tuple[np.ndarray, dict]:
25
- """Generate a Central Composite Design (CCD).
26
-
27
- Parameters
28
- ----------
29
- factors : list[Factor]
30
- Continuous factors.
31
- center_points : int
32
- Number of center points (split between cube and axial portions).
33
- alpha : str, float, or None
34
- Axial distance. Accepted string values: ``"rotatable"``,
35
- ``"face_centered"``, ``"orthogonal"``. A numeric value sets
36
- alpha directly. Defaults to ``"orthogonal"``.
37
-
38
- Returns
39
- -------
40
- tuple[np.ndarray, dict]
41
- Coded design matrix and metadata (includes ``alpha_value``).
42
-
43
- Notes
44
- -----
45
- Center points are embedded in the CCD structure itself (via pyDOE3's
46
- ``center`` parameter). The caller should set ``center_points=0`` in
47
- ``build_design_result`` to avoid adding duplicate center points.
48
- """
49
- k = len(factors)
50
-
51
- # Map alpha string to pyDOE3 face and alpha parameters.
52
- # pyDOE3 alpha accepts: "orthogonal"/"o", "rotatable"/"r"
53
- # pyDOE3 face accepts: "circumscribed"/"ccc", "inscribed"/"cci", "faced"/"ccf"
54
- face = "circumscribed"
55
- alpha_str = "orthogonal"
56
- if isinstance(alpha, str):
57
- alpha_lower = alpha.lower()
58
- if alpha_lower in ("face_centered", "face centered", "ccf", "faced"):
59
- face = "faced"
60
- alpha_str = "orthogonal"
61
- elif alpha_lower in ("inscribed", "cci"):
62
- face = "inscribed"
63
- alpha_str = "orthogonal"
64
- elif alpha_lower in ("rotatable", "r"):
65
- face = "circumscribed"
66
- alpha_str = "rotatable"
67
- else:
68
- # "orthogonal" or default
69
- face = "circumscribed"
70
- alpha_str = "orthogonal"
71
- elif isinstance(alpha, (int, float)):
72
- alpha_str = "orthogonal"
73
- face = "circumscribed"
74
-
75
- # Split center points between cube and axial portions
76
- n_center_cube = max(1, center_points // 2)
77
- n_center_axial = max(1, center_points - n_center_cube)
78
-
79
- coded_matrix = ccdesign(k, center=(n_center_cube, n_center_axial), alpha=alpha_str, face=face)
80
-
81
- # Determine actual alpha value used
82
- alpha_value: float | None = None
83
- if face == "ccf":
84
- alpha_value = 1.0
85
- elif coded_matrix.shape[0] > 0:
86
- alpha_value = float(np.max(np.abs(coded_matrix)))
87
-
88
- return coded_matrix, {"alpha_value": alpha_value, "face": face}
89
-
90
-
91
- def dispatch_box_behnken(
92
- factors: list[Factor],
93
- center_points: int = 3,
94
- ) -> tuple[np.ndarray, dict]:
95
- """Generate a Box-Behnken design.
96
-
97
- Parameters
98
- ----------
99
- factors : list[Factor]
100
- Continuous factors (requires at least 3).
101
- center_points : int
102
- Number of center point replicates.
103
-
104
- Returns
105
- -------
106
- tuple[np.ndarray, dict]
107
- Coded design matrix (-1 / 0 / +1) and metadata.
108
-
109
- Notes
110
- -----
111
- Center points are embedded in the BB structure. The caller should set
112
- ``center_points=0`` in ``build_design_result``.
113
- """
114
- k = len(factors)
115
- if k < 3:
116
- raise ValueError("Box-Behnken designs require at least 3 factors.")
117
- coded_matrix = bbdesign(k, center=center_points)
118
- return coded_matrix, {}
119
-
120
-
121
- def dispatch_dsd(factors: list[Factor]) -> tuple[np.ndarray, dict]:
122
- """Generate a Definitive Screening Design (DSD).
123
-
124
- Uses the conference-matrix-based construction of Jones & Nachtsheim (2011).
125
- For *k* factors the DSD has ``2k + 1`` runs (odd number of factors) or
126
- ``2k + 3`` runs (even) and can estimate all main effects and quadratic
127
- effects, plus detect two-factor interactions, with minimal confounding.
128
-
129
- Parameters
130
- ----------
131
- factors : list[Factor]
132
- Continuous factors.
133
-
134
- Returns
135
- -------
136
- tuple[np.ndarray, dict]
137
- Coded design matrix and metadata.
138
- """
139
- k = len(factors)
140
- if k < 3:
141
- raise ValueError("Definitive Screening Designs require at least 3 factors.")
142
-
143
- # Build a conference matrix C of size k x k
144
- # A conference matrix has 0 on the diagonal and +/-1 off-diagonal,
145
- # with C'C = (k-1)*I.
146
- # For the DSD we use a simple fold-over construction.
147
- # Start with a diagonal matrix of +1s and fill off-diagonal with a
148
- # balanced +/-1 pattern.
149
- C = _conference_matrix(k)
150
-
151
- # DSD construction: stack [C; -C; 0-row]
152
- zero_row = np.zeros((1, k))
153
- coded_matrix = np.vstack([C, -C, zero_row])
154
-
155
- # For even k, add two extra center-ish rows for estimability
156
- if k % 2 == 0:
157
- extra1 = np.ones((1, k))
158
- extra2 = -np.ones((1, k))
159
- coded_matrix = np.vstack([coded_matrix, extra1, extra2])
160
-
161
- return coded_matrix, {"construction": "conference_matrix_fold_over"}
162
-
163
-
164
- def _conference_matrix(k: int) -> np.ndarray:
165
- """Construct a k x k conference matrix.
166
-
167
- Uses a Paley-type construction when k-1 is a prime power, otherwise
168
- falls back to a cyclic construction.
169
-
170
- Parameters
171
- ----------
172
- k : int
173
- Size of the conference matrix.
174
-
175
- Returns
176
- -------
177
- np.ndarray
178
- k x k matrix with 0s on diagonal and +/-1 off-diagonal.
179
- """
180
- C = np.zeros((k, k))
181
-
182
- # Simple construction: use a cyclic shift of a balanced sequence
183
- # For a k x k conference matrix, we need a (k-1)-length sequence
184
- # with equal numbers of +1 and -1
185
- half = (k - 1) // 2
186
- sequence = [1] * half + [-1] * (k - 1 - half)
187
-
188
- for i in range(k):
189
- for j in range(k):
190
- if i != j:
191
- idx = (j - i - 1) % (k - 1) if j > i else (j - i) % (k - 1)
192
- C[i, j] = sequence[idx]
193
-
194
- return C