process-improve 2.2.0__tar.gz → 2.2.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 (325) hide show
  1. {process_improve-2.2.0 → process_improve-2.2.2}/PKG-INFO +1 -1
  2. {process_improve-2.2.0 → process_improve-2.2.2}/pyproject.toml +30 -1
  3. {process_improve-2.2.0 → process_improve-2.2.2}/pyproject.toml.orig +29 -1
  4. process_improve-2.2.2/src/process_improve/_tool_registry.py +205 -0
  5. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/glm.py +4 -3
  6. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_lm.py +2 -2
  7. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/fit_linear_model.py +1 -4
  8. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_uniform_sampling.py +3 -5
  9. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/analysis.py +80 -17
  10. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_mixture_constrained.py +3 -1
  11. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_response_surface.py +1 -6
  12. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_supersaturated.py +8 -10
  13. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/next_step.py +161 -50
  14. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/optimal.py +2 -7
  15. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/random_effects.py +10 -2
  16. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/monitoring/control_charts.py +61 -49
  17. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/monitoring/metrics.py +7 -5
  18. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_limits.py +13 -8
  19. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_pls.py +9 -5
  20. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_preprocessing.py +0 -97
  21. process_improve-2.2.2/src/process_improve/multivariate/_prm.py +399 -0
  22. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/_variance_components.py +54 -0
  23. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/ingest.py +19 -0
  24. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/tool_spec.py +6 -190
  25. process_improve-2.2.0/src/process_improve/multivariate/_prm.py +0 -390
  26. {process_improve-2.2.0 → process_improve-2.2.2}/README.md +0 -0
  27. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/__init__.py +0 -0
  28. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/_extras.py +0 -0
  29. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/_linalg.py +0 -0
  30. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/_random.py +0 -0
  31. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/_remote_data.py +0 -0
  32. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/__init__.py +0 -0
  33. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_batch_monitor.py +0 -0
  34. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_batch_pca.py +0 -0
  35. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_batch_plots.py +0 -0
  36. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_batch_pls.py +0 -0
  37. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_common.py +0 -0
  38. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_gaps.py +0 -0
  39. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/_online.py +0 -0
  40. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/alignment_helpers.py +0 -0
  41. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/control.py +0 -0
  42. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/data_input.py +0 -0
  43. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/datasets.py +0 -0
  44. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/features.py +0 -0
  45. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/plotting.py +0 -0
  46. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/preprocessing.py +0 -0
  47. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/batch/tools.py +0 -0
  48. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/bivariate/__init__.py +0 -0
  49. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/bivariate/_elbow_peak.py +0 -0
  50. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/bivariate/methods.py +0 -0
  51. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/bivariate/tools.py +0 -0
  52. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/chemistry/__init__.py +0 -0
  53. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/chemistry/preprocessing.py +0 -0
  54. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/config.py +0 -0
  55. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/batch/batch-fake-data.csv +0 -0
  56. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/batch/details.txt +0 -0
  57. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/batch/dryer.csv +0 -0
  58. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/batch/nylon.csv +0 -0
  59. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/boilingpot.csv +0 -0
  60. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/corrosion.csv +0 -0
  61. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/golf.csv +0 -0
  62. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/pollutant.csv +0 -0
  63. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/NOTICE.md +0 -0
  64. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/anderson-whitcomb-2007.json +0 -0
  65. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/box-draper-2007.json +0 -0
  66. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/box-hunter-hunter-1978.json +0 -0
  67. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/davies-1956.json +0 -0
  68. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/derringer-suich-1980.json +0 -0
  69. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/efi-mooc-quizzes.json +0 -0
  70. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/fries-hunter-1980.json +0 -0
  71. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/goos-2025.json +0 -0
  72. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/goos-jones-2011.json +0 -0
  73. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/jones-nachtsheim-2011.json +0 -0
  74. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/jones-nachtsheim-2013.json +0 -0
  75. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/lenth-1989.json +0 -0
  76. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/montgomery-2013.json +0 -0
  77. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/nair-ffd-tutorial-2004.json +0 -0
  78. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/nist-ehandbook-ch5.json +0 -0
  79. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/nunez-ares-et-al-2023.json +0 -0
  80. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/nunez-ares-goos-2019.json +0 -0
  81. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/nunez-ares-goos-2020.json +0 -0
  82. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/pid-book.json +0 -0
  83. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/schmidt-launsby-2000.json +0 -0
  84. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/topics.json +0 -0
  85. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/reference_catalogue/ullmann-doe-2012.json +0 -0
  86. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/solar.csv +0 -0
  87. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/experiments/test_doe1.csv +0 -0
  88. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/monitoring/batch-yield-and-purity.csv +0 -0
  89. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/monitoring/rubber-colour.csv +0 -0
  90. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/C.csv +0 -0
  91. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A3.csv +0 -0
  92. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/Hotellings_T2_A6.csv +0 -0
  93. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/LDPE.csv +0 -0
  94. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/P.csv +0 -0
  95. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/R.csv +0 -0
  96. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/T.csv +0 -0
  97. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/U.csv +0 -0
  98. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/W.csv +0 -0
  99. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/LDPE/Yhat_A6.csv +0 -0
  100. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/cheddar-cheese.csv +0 -0
  101. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/NOTICE +0 -0
  102. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/extraction1.csv +0 -0
  103. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/extraction2.csv +0 -0
  104. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/extraction3.csv +0 -0
  105. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/ftir1.csv +0 -0
  106. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/ftir2.csv +0 -0
  107. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/dtu-pectin/ftir3.csv +0 -0
  108. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/kamyr.csv +0 -0
  109. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/solvents.csv +0 -0
  110. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tablet-spectra.csv +0 -0
  111. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/SOURCE +0 -0
  112. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group1.csv +0 -0
  113. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group2.csv +0 -0
  114. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group3.csv +0 -0
  115. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group4.csv +0 -0
  116. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/formulas_Group5.csv +0 -0
  117. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/process_conditions.csv +0 -0
  118. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group1.csv +0 -0
  119. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group2.csv +0 -0
  120. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group3.csv +0 -0
  121. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group4.csv +0 -0
  122. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/properties_Group5.csv +0 -0
  123. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/datasets/multivariate/tpls-pyphi/quality_indicators.csv +0 -0
  124. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/__init__.py +0 -0
  125. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/__init__.py +0 -0
  126. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/_shared.py +0 -0
  127. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/aliasing.py +0 -0
  128. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/box_cox.py +0 -0
  129. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/curvature.py +0 -0
  130. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/diagnostics.py +0 -0
  131. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/lack_of_fit.py +0 -0
  132. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/lenth.py +0 -0
  133. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/mixture.py +0 -0
  134. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/model_selection.py +0 -0
  135. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/ols_extractors.py +0 -0
  136. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/prediction.py +0 -0
  137. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/split_plot.py +0 -0
  138. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_analyses/transforms.py +0 -0
  139. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_blocking.py +0 -0
  140. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_classical.py +0 -0
  141. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_desirability.py +0 -0
  142. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_expression_text.py +0 -0
  143. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_finite_fields.py +0 -0
  144. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_moment_aberration.py +0 -0
  145. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_orthogonal_arrays.py +0 -0
  146. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_region_geometry.py +0 -0
  147. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_region_quadrature.py +0 -0
  148. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_rules.py +0 -0
  149. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/__init__.py +0 -0
  150. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/analyze_experiment.py +0 -0
  151. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/analyze_omars.py +0 -0
  152. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/augment_design.py +0 -0
  153. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/compare_designs.py +0 -0
  154. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/create_factorial_design.py +0 -0
  155. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/doe_knowledge.py +0 -0
  156. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/evaluate_design.py +0 -0
  157. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/generate_design.py +0 -0
  158. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/optimize_responses.py +0 -0
  159. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/recommend_next_step.py +0 -0
  160. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/recommend_strategy.py +0 -0
  161. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/run_sheet.py +0 -0
  162. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/search_design_catalog.py +0 -0
  163. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/simulate_design_performance.py +0 -0
  164. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/trade_off_table.py +0 -0
  165. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/_tools/visualize_doe.py +0 -0
  166. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/allocation.py +0 -0
  167. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/augment.py +0 -0
  168. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/benchmark.py +0 -0
  169. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/compare.py +0 -0
  170. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/crossed_arrays.py +0 -0
  171. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/datasets.py +0 -0
  172. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/design_catalog.py +0 -0
  173. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/design_measures.py +0 -0
  174. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs.py +0 -0
  175. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_constrained.py +0 -0
  176. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_covariates.py +0 -0
  177. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_factorial.py +0 -0
  178. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_mixture.py +0 -0
  179. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_mixture_process.py +0 -0
  180. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_omars.py +0 -0
  181. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_omars_ilp.py +0 -0
  182. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_optimal.py +0 -0
  183. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_screening.py +0 -0
  184. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_space_filling.py +0 -0
  185. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_split_plot.py +0 -0
  186. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_three_level.py +0 -0
  187. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/designs_utils.py +0 -0
  188. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/evaluate.py +0 -0
  189. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/factor.py +0 -0
  190. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/gaussian_process.py +0 -0
  191. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/incomplete_blocks.py +0 -0
  192. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/__init__.py +0 -0
  193. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/api.py +0 -0
  194. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/data/concepts.yaml +0 -0
  195. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/data/decision_rules.yaml +0 -0
  196. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/data/design_types.yaml +0 -0
  197. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/data/diagnostics.yaml +0 -0
  198. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/engine.py +0 -0
  199. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/knowledge/models.py +0 -0
  200. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/layouts.py +0 -0
  201. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/least_squares_means.py +0 -0
  202. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/marginal_means.py +0 -0
  203. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/model_comparison.py +0 -0
  204. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/models.py +0 -0
  205. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/multistratum.py +0 -0
  206. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/omars.py +0 -0
  207. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/omars_trade_off.py +0 -0
  208. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/optimization.py +0 -0
  209. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/projections.py +0 -0
  210. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/pseudocomponents.py +0 -0
  211. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/reference_catalogue.py +0 -0
  212. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/region.py +0 -0
  213. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/run_order.py +0 -0
  214. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/run_sheet.py +0 -0
  215. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/separating.py +0 -0
  216. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/sequential.py +0 -0
  217. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/simulate.py +0 -0
  218. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/simulations.py +0 -0
  219. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/strategy/__init__.py +0 -0
  220. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/strategy/budget.py +0 -0
  221. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/strategy/domain_templates.py +0 -0
  222. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/strategy/engine.py +0 -0
  223. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/strategy/models.py +0 -0
  224. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/structures.py +0 -0
  225. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/surface_checks.py +0 -0
  226. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/three_level_components.py +0 -0
  227. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/tools.py +0 -0
  228. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/trade_off.py +0 -0
  229. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/true_process.py +0 -0
  230. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/variance_dispersion.py +0 -0
  231. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/__init__.py +0 -0
  232. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/api.py +0 -0
  233. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/__init__.py +0 -0
  234. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/cube_plot.py +0 -0
  235. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/design_quality.py +0 -0
  236. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/diagnostics.py +0 -0
  237. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/effects.py +0 -0
  238. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/optimization_plots.py +0 -0
  239. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/registry.py +0 -0
  240. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/significance.py +0 -0
  241. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/square_plot.py +0 -0
  242. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/visualization/plots/surfaces.py +0 -0
  243. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/experiments/yates.py +0 -0
  244. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/interactions.py +0 -0
  245. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/mcp_server.py +0 -0
  246. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/monitoring/__init__.py +0 -0
  247. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/monitoring/evop.py +0 -0
  248. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/monitoring/tools.py +0 -0
  249. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/__init__.py +0 -0
  250. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_adaptive.py +0 -0
  251. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_asca.py +0 -0
  252. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_base.py +0 -0
  253. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_categorical.py +0 -0
  254. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_common.py +0 -0
  255. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_cv_criteria.py +0 -0
  256. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_diagnostics.py +0 -0
  257. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_impute.py +0 -0
  258. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_mbpca.py +0 -0
  259. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_mbpls.py +0 -0
  260. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_nipals.py +0 -0
  261. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_null.py +0 -0
  262. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_opls.py +0 -0
  263. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_pca.py +0 -0
  264. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_pca_pls.py +0 -0
  265. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_plsda.py +0 -0
  266. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_procrustes.py +0 -0
  267. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_projection.py +0 -0
  268. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_resampling.py +0 -0
  269. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_robust.py +0 -0
  270. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/_tpls.py +0 -0
  271. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/methods.py +0 -0
  272. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/plots.py +0 -0
  273. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/multivariate/tools.py +0 -0
  274. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/py.typed +0 -0
  275. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/recipes.py +0 -0
  276. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/__init__.py +0 -0
  277. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/_robust_regression.py +0 -0
  278. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/box_tidwell.py +0 -0
  279. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/methods.py +0 -0
  280. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/nonlinear.py +0 -0
  281. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/regression/tools.py +0 -0
  282. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/__init__.py +0 -0
  283. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/analysis.py +0 -0
  284. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/designed.py +0 -0
  285. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/diagnostics.py +0 -0
  286. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/mam.py +0 -0
  287. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/panel.py +0 -0
  288. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/recipes.py +0 -0
  289. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/tools.py +0 -0
  290. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/sensory/validation.py +0 -0
  291. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/__init__.py +0 -0
  292. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/batch.py +0 -0
  293. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/context.py +0 -0
  294. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/latent.py +0 -0
  295. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/model.py +0 -0
  296. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/recipes.py +0 -0
  297. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/simulation/tools.py +0 -0
  298. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/tool_safety.py +0 -0
  299. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/__init__.py +0 -0
  300. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/classical.py +0 -0
  301. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/error_transmission.py +0 -0
  302. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/metrics.py +0 -0
  303. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/reference_distribution.py +0 -0
  304. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/serial_correlation.py +0 -0
  305. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/stated_values.py +0 -0
  306. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/univariate/tools.py +0 -0
  307. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/__init__.py +0 -0
  308. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/adapters/__init__.py +0 -0
  309. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/adapters/base.py +0 -0
  310. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/adapters/echarts_adapter.py +0 -0
  311. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/adapters/plotly_adapter.py +0 -0
  312. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/charts/__init__.py +0 -0
  313. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/charts/boxplot.py +0 -0
  314. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/colors.py +0 -0
  315. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/raincloud.py +0 -0
  316. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/spec.py +0 -0
  317. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/themes.py +0 -0
  318. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/tools.py +0 -0
  319. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/visualization/types.py +0 -0
  320. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/workbench/__init__.py +0 -0
  321. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/workbench/_expressions.py +0 -0
  322. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/workbench/project.py +0 -0
  323. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/workbench/recipe.py +0 -0
  324. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/workbench/tools.py +0 -0
  325. {process_improve-2.2.0 → process_improve-2.2.2}/src/process_improve/workbench/variant.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: process-improve
3
- Version: 2.2.0
3
+ Version: 2.2.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,Design of Experiments,DoE,Optimal Design,Response Surface,Latent Variables,PCA,PLS,Multivariate Data Analysis,Chemometrics,Process Monitoring,Batch data analysis
6
6
  Author: Kevin Dunn
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "process-improve"
3
- version = "2.2.0"
3
+ version = "2.2.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"
@@ -301,3 +301,32 @@ known-third-party = [
301
301
  "pytest",
302
302
  "pandas",
303
303
  ]
304
+
305
+ [tool.mutmut]
306
+ source_paths = ["src"]
307
+ only_mutate = [
308
+ "src/process_improve/multivariate/_limits.py",
309
+ "src/process_improve/monitoring/metrics.py",
310
+ "src/process_improve/monitoring/evop.py",
311
+ "src/process_improve/monitoring/control_charts.py",
312
+ ]
313
+ pytest_add_cli_args = [
314
+ "-o",
315
+ "addopts=",
316
+ "-p",
317
+ "no:cacheprovider",
318
+ ]
319
+ pytest_add_cli_args_test_selection = [
320
+ "tests/test_multivariate_limits.py",
321
+ "tests/test_multivariate.py",
322
+ "tests/test_audit_regressions_multivariate.py",
323
+ "tests/test_multiblock_validation.py",
324
+ "tests/test_multivariate_introspection.py",
325
+ "tests/test_monitoring.py",
326
+ "tests/test_monitoring_helpers.py",
327
+ "tests/test_audit_regressions_univariate_monitoring.py",
328
+ "tests/test_evop.py",
329
+ "tests/test_control_chart_cusum.py",
330
+ "tests/test_control_chart_ewma.py",
331
+ "tests/test_control_chart_hw_golden.py",
332
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "process-improve"
3
- version = "2.2.0"
3
+ version = "2.2.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"
@@ -449,3 +449,31 @@ docstring-quotes = "double"
449
449
 
450
450
  [tool.ruff.lint.isort]
451
451
  known-third-party=["pytest", "pandas"]
452
+
453
+ # Mutation testing: see "Mutation testing" in CONTRIBUTING.md. Modules join `only_mutate` one at a
454
+ # time, each with no survivor, starting with those whose numbers users act on; the tests listed
455
+ # are those that reach them.
456
+ [tool.mutmut]
457
+ source_paths = ["src"]
458
+ only_mutate = [
459
+ "src/process_improve/multivariate/_limits.py",
460
+ "src/process_improve/monitoring/metrics.py",
461
+ "src/process_improve/monitoring/evop.py",
462
+ "src/process_improve/monitoring/control_charts.py",
463
+ ]
464
+ # The runner drops pytest.ini's addopts: no xdist workers and no coverage inside each mutant run.
465
+ pytest_add_cli_args = ["-o", "addopts=", "-p", "no:cacheprovider"]
466
+ pytest_add_cli_args_test_selection = [
467
+ "tests/test_multivariate_limits.py",
468
+ "tests/test_multivariate.py",
469
+ "tests/test_audit_regressions_multivariate.py",
470
+ "tests/test_multiblock_validation.py",
471
+ "tests/test_multivariate_introspection.py",
472
+ "tests/test_monitoring.py",
473
+ "tests/test_monitoring_helpers.py",
474
+ "tests/test_audit_regressions_univariate_monitoring.py",
475
+ "tests/test_evop.py",
476
+ "tests/test_control_chart_cusum.py",
477
+ "tests/test_control_chart_ewma.py",
478
+ "tests/test_control_chart_hw_golden.py",
479
+ ]
@@ -0,0 +1,205 @@
1
+ """(c) Kevin Dunn, 2010-2026. MIT License.
2
+
3
+ The ``@tool_spec`` decorator, the registry it fills, and ``clean``.
4
+
5
+ These are the parts of :mod:`process_improve.tool_spec` that every ``tools.py``
6
+ module imports. They live apart from tool discovery, which names every
7
+ ``tools.py`` module, so that a tool module depends on this file alone and not,
8
+ through discovery, on every other tool module. ``tool_spec.py`` re-exports all
9
+ of it, and callers import it from there.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+ from collections.abc import Callable
16
+ from typing import Any
17
+
18
+ import numpy as np
19
+ from pydantic import BaseModel
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Registry
23
+ # ---------------------------------------------------------------------------
24
+
25
+ #: Maps tool name -> decorated callable. Populated by ``@tool_spec``.
26
+ _TOOL_REGISTRY: dict[str, Callable[..., Any]] = {}
27
+
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # RNG metadata validation
31
+ # ---------------------------------------------------------------------------
32
+
33
+
34
+ def _validate_rng_metadata(name: str, rng: dict[str, Any]) -> None:
35
+ """Reject ``rng`` payloads that don't match the published contract.
36
+
37
+ The contract is intentionally narrow so downstream consumers (the
38
+ factorial reproducible-export service) can introspect specs without
39
+ defensive type-checking. See ``CLAUDE.md`` for the full schema.
40
+ """
41
+ if not isinstance(rng, dict):
42
+ raise TypeError(f"@tool_spec(name={name!r}): 'rng' must be a dict, got {type(rng).__name__}.")
43
+ if "uses_rng" not in rng or not isinstance(rng["uses_rng"], bool):
44
+ raise ValueError(f"@tool_spec(name={name!r}): 'rng' must have a boolean 'uses_rng' key.")
45
+ allowed_keys = {"uses_rng", "seed_param", "default_seed", "note"}
46
+ extra = set(rng) - allowed_keys
47
+ if extra:
48
+ raise ValueError(
49
+ f"@tool_spec(name={name!r}): unknown keys in 'rng': {sorted(extra)}. Allowed: {sorted(allowed_keys)}."
50
+ )
51
+ if not rng["uses_rng"]:
52
+ # Deterministic tools shouldn't carry a seed_param / default_seed.
53
+ if rng.get("seed_param") is not None or rng.get("default_seed") is not None:
54
+ raise ValueError(
55
+ f"@tool_spec(name={name!r}): 'seed_param' / 'default_seed' must be omitted when uses_rng is False."
56
+ )
57
+ return
58
+ seed_param = rng.get("seed_param")
59
+ if seed_param is not None and not isinstance(seed_param, str):
60
+ raise TypeError(
61
+ f"@tool_spec(name={name!r}): 'seed_param' must be a string or None, got {type(seed_param).__name__}."
62
+ )
63
+ default_seed = rng.get("default_seed")
64
+ if default_seed is not None and not isinstance(default_seed, int):
65
+ raise TypeError(
66
+ f"@tool_spec(name={name!r}): 'default_seed' must be an int or None, got {type(default_seed).__name__}."
67
+ )
68
+ if seed_param is None and default_seed is not None:
69
+ raise ValueError(f"@tool_spec(name={name!r}): 'default_seed' requires a 'seed_param' name.")
70
+
71
+
72
+ # ---------------------------------------------------------------------------
73
+ # Decorator
74
+ # ---------------------------------------------------------------------------
75
+
76
+
77
+ def tool_spec( # noqa: PLR0913
78
+ name: str,
79
+ description: str,
80
+ *,
81
+ input_model: type[BaseModel],
82
+ examples: str = "",
83
+ category: str = "",
84
+ rng: dict[str, Any] | None = None,
85
+ ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
86
+ """Mark a function as an agent-callable tool.
87
+
88
+ Parameters
89
+ ----------
90
+ name:
91
+ Unique tool name exposed to the LLM (snake_case).
92
+ description:
93
+ Natural-language description of what the tool does and when to use it.
94
+ input_model:
95
+ A :class:`pydantic.BaseModel` subclass describing the tool's
96
+ input. The model is the single source of truth for both the
97
+ function's Python signature and the MCP JSON Schema (derived
98
+ via :meth:`BaseModel.model_json_schema`). Every input model
99
+ **must** set ``model_config = ConfigDict(extra="forbid")`` so
100
+ unknown keys are rejected (closes the SEC-15
101
+ kwarg-injection vector at the schema layer).
102
+
103
+ The decorated function then receives the parsed model as its
104
+ single positional argument::
105
+
106
+ class FooInput(BaseModel):
107
+ model_config = ConfigDict(extra="forbid")
108
+ values: list[float] = Field(..., min_length=2)
109
+
110
+ @tool_spec(name="foo", description="...", input_model=FooInput)
111
+ def foo(spec: FooInput) -> dict:
112
+ return {"n": len(spec.values)}
113
+ examples:
114
+ Optional natural-language -> tool-call mappings appended to
115
+ ``description`` so the LLM sees worked examples.
116
+ category:
117
+ Optional category string (e.g. ``"univariate"``). Used for
118
+ filtering with :func:`get_tool_specs`.
119
+ rng:
120
+ Optional reproducibility metadata; see :func:`_validate_rng_metadata`
121
+ for the contract.
122
+
123
+ Returns
124
+ -------
125
+ Callable
126
+ The original function, with an attached ``_tool_spec`` dict
127
+ and ``_input_model`` attribute.
128
+ """
129
+ if rng is not None:
130
+ _validate_rng_metadata(name, rng)
131
+
132
+ if not (isinstance(input_model, type) and issubclass(input_model, BaseModel)):
133
+ raise TypeError(
134
+ f"@tool_spec(name={name!r}): input_model must be a pydantic BaseModel subclass; got {input_model!r}."
135
+ )
136
+ extra_policy = input_model.model_config.get("extra")
137
+ if extra_policy != "forbid":
138
+ raise ValueError(
139
+ f"@tool_spec(name={name!r}): input_model {input_model.__name__} "
140
+ "must set model_config = ConfigDict(extra='forbid') so unknown "
141
+ "kwargs are rejected (ENG-04 / SEC-15 contract)."
142
+ )
143
+
144
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
145
+ """Attach the assembled tool spec to ``func`` and return it unchanged."""
146
+ full_description = description
147
+ if examples:
148
+ full_description = f"{description}\n\nExamples\n--------\n{examples}"
149
+
150
+ spec: dict[str, Any] = {
151
+ "name": name,
152
+ "description": full_description,
153
+ "input_schema": input_model.model_json_schema(),
154
+ }
155
+ if category:
156
+ spec["category"] = category
157
+ if rng is not None:
158
+ spec["rng"] = dict(rng)
159
+
160
+ func._tool_spec = spec # type: ignore[attr-defined]
161
+ func._input_model = input_model # type: ignore[attr-defined]
162
+ _TOOL_REGISTRY[name] = func
163
+ return func
164
+
165
+ return decorator
166
+
167
+
168
+ # ---------------------------------------------------------------------------
169
+ # Serialisation helper
170
+ # ---------------------------------------------------------------------------
171
+
172
+
173
+ def clean(value: Any) -> Any: # noqa: PLR0911, ANN401
174
+ """Recursively convert numpy scalars / arrays to plain Python types.
175
+
176
+ All ``tools.py`` modules should call ``clean(result)`` before returning
177
+ so that every tool output is JSON-serialisable.
178
+ """
179
+ if isinstance(value, dict):
180
+ # Keys need unwrapping too: json.dumps rejects numpy scalar keys (a
181
+ # dict keyed by np.int64 group labels from a pandas groupby is the
182
+ # common case), and previously they leaked through untouched. Only
183
+ # numpy scalars are unwrapped - other key types pass through so
184
+ # hashability is never at risk.
185
+ return {(k.item() if isinstance(k, np.generic) else k): clean(v) for k, v in value.items()}
186
+ if isinstance(value, (list, tuple, set, frozenset)):
187
+ return [clean(v) for v in value]
188
+ # np.bool_ subclasses neither np.integer nor Python bool, and json.dumps
189
+ # rejects it; it must be tested before the numeric branches.
190
+ if isinstance(value, np.bool_):
191
+ return bool(value)
192
+ if isinstance(value, np.integer):
193
+ return int(value)
194
+ if isinstance(value, np.floating):
195
+ v = float(value)
196
+ return None if math.isnan(v) or math.isinf(v) else v
197
+ if isinstance(value, float):
198
+ return None if math.isnan(value) or math.isinf(value) else value
199
+ if isinstance(value, np.ndarray):
200
+ return clean(value.tolist())
201
+ if isinstance(value, np.generic):
202
+ # Catch-all for the remaining numpy scalar types (datetime64,
203
+ # complex, str_, ...): unwrap to the closest Python equivalent.
204
+ return clean(value.item())
205
+ return value
@@ -88,9 +88,10 @@ def _trials(df: pd.DataFrame, trials: str | float | Sequence[float] | None) -> n
88
88
  if trials is None:
89
89
  return None
90
90
  if isinstance(trials, str):
91
- return df[trials].to_numpy(dtype=float)
92
- found = np.broadcast_to(np.asarray(trials, dtype=float), (len(df),)).copy()
93
- if np.any(found <= 0):
91
+ found = df[trials].to_numpy(dtype=float) # a missing count already dropped its run
92
+ else:
93
+ found = np.broadcast_to(np.asarray(trials, dtype=float), (len(df),)).copy()
94
+ if np.any(found <= 0): # a column too (#1001): a zero reached statsmodels, which divided by it
94
95
  raise ValueError("Every run's number of trials must be positive.")
95
96
  return found
96
97
 
@@ -526,8 +526,8 @@ class Model(OLS):
526
526
 
527
527
  return smry
528
528
 
529
- def get_parameters(self, drop_intercept: bool = True) -> pd.DataFrame:
530
- """Get the parameter values; return them in a Pandas dataframe."""
529
+ def get_parameters(self, drop_intercept: bool = True) -> pd.Series:
530
+ """Get the parameter values; return them in a pandas Series indexed by term."""
531
531
 
532
532
  params = self._OLS.params.copy()
533
533
  try:
@@ -98,10 +98,7 @@ def fit_linear_model(spec: FitLinearModelInput) -> dict[str, Any]:
98
98
  model = lm(spec.formula, expt_data)
99
99
 
100
100
  params = model.get_parameters(drop_intercept=True)
101
- if isinstance(params, pd.Series):
102
- coefficients = [{"name": str(name), "estimate": float(value)} for name, value in params.items()]
103
- else:
104
- coefficients = params.to_dict(orient="records")
101
+ coefficients = [{"name": str(name), "estimate": float(value)} for name, value in params.items()]
105
102
 
106
103
  r2 = float(model._OLS.rsquared)
107
104
 
@@ -317,14 +317,12 @@ class UniformSampler:
317
317
  t_lo = np.maximum(t_lo, np.where(rate < 0, limit, -np.inf).max(axis=1))
318
318
  return np.minimum(t_lo, 0.0), np.maximum(t_hi, 0.0)
319
319
 
320
- def _step(self, x: np.ndarray, rng: np.random.Generator, shape: np.ndarray | None = None) -> np.ndarray:
320
+ def _step(self, x: np.ndarray, rng: np.random.Generator, shape: np.ndarray) -> np.ndarray:
321
321
  """Move every chain once: a random direction, a uniform point on its chord, shrinking on a miss.
322
322
 
323
- Directions are Gaussian with covariance ``shape @ shape.T`` (isotropic by default).
323
+ Directions are Gaussian with covariance ``shape @ shape.T`` (see :meth:`_direction_shape`).
324
324
  """
325
- d = rng.standard_normal(x.shape)
326
- if shape is not None:
327
- d = d @ shape.T
325
+ d = rng.standard_normal(x.shape) @ shape.T
328
326
  if self.on_simplex:
329
327
  d -= d.mean(axis=1, keepdims=True) # stay on the plane where the proportions sum to 1
330
328
  d /= np.linalg.norm(d, axis=1, keepdims=True)
@@ -428,8 +428,13 @@ def _missing_cells_text(df: pd.DataFrame, lost: list[str]) -> str:
428
428
 
429
429
 
430
430
  def _drop_incomplete_runs(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame:
431
- """Drop runs with a missing response or factor setting, warning how many went."""
431
+ """Drop runs with a missing response or factor setting, warning how many went; refuse when none is left."""
432
432
  complete = df[columns].notna().all(axis=1)
433
+ if not complete.any():
434
+ raise ValueError(
435
+ f"No run has a value in every one of {', '.join(map(str, columns))}, so there is nothing to analyse: "
436
+ "each run is missing its response or a factor setting."
437
+ )
433
438
  n_dropped = int((~complete).sum())
434
439
  if n_dropped:
435
440
  warnings.warn(
@@ -441,6 +446,33 @@ def _drop_incomplete_runs(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame:
441
446
  return df
442
447
 
443
448
 
449
+ def _stand_in(name: str, taken: pd.Index) -> str:
450
+ """Return a name a formula can hold for the response: ``name_``, else ``name_1``, ``name_2``, ...
451
+
452
+ The stand-in never gains a second underscore, which the formula check refuses as dunder access (#1002).
453
+ """
454
+ alias, k = f"{name}_", 0
455
+ while alias in taken:
456
+ k += 1
457
+ alias = f"{name}_{k}"
458
+ return alias
459
+
460
+
461
+ def _check_response_scale(response: pd.Series, name: str) -> None:
462
+ """Refuse a response whose sums of squares cannot be held in double precision (#1005).
463
+
464
+ Every test, R-squared and p-value is unchanged when the response is divided by a constant,
465
+ so the remedy is a change of units; past this size ``n * max(y)**2`` overflows.
466
+ """
467
+ largest = float(np.max(np.abs(response.to_numpy(dtype=float))))
468
+ if largest > np.sqrt(np.finfo(float).max / len(response)):
469
+ raise ValueError(
470
+ f"The response {name!r} is too large in its units to analyse: its sums of squares overflow, as its "
471
+ f"largest value is {largest:.3g}. Divide it by a power of 10 and analyse it again; the tests, "
472
+ "R-squared and p-values do not change."
473
+ )
474
+
475
+
444
476
  def _top_level_terms(rhs: str) -> list[str]:
445
477
  """Split a formula's right-hand side at the ``+`` signs outside parentheses."""
446
478
  terms, depth, start = [], 0, 0
@@ -1284,11 +1316,8 @@ def analyze_experiment( # noqa: PLR0913
1284
1316
  # "yield" is the commonest response in chemistry, and a Python keyword, which a
1285
1317
  # formula cannot name; a column named C, I or Q hides patsy's function of that
1286
1318
  # name, so I(x1 ** 2) fails. Fit under a stand-in name and report the real one.
1287
- alias = f"{response_col}_"
1288
- while alias in df.columns:
1289
- alias += "_"
1290
- df = df.rename(columns={response_col: alias})
1291
- response_col = alias
1319
+ response_col = _stand_in(response_col, df.columns)
1320
+ df = df.rename(columns={reported_response: response_col})
1292
1321
 
1293
1322
  # Factor columns = everything except the response and the design-bookkeeping
1294
1323
  # columns. A DesignResult carries "RunOrder" (and optionally "Block"); if a
@@ -1324,6 +1353,7 @@ def analyze_experiment( # noqa: PLR0913
1324
1353
  *([trials_col] if trials_col else []),
1325
1354
  ]
1326
1355
  df = _drop_incomplete_runs(df, used)
1356
+ _check_response_scale(df[response_col], reported_response)
1327
1357
  coding = _resolve_coding(coding, df, factor_cols, model)
1328
1358
 
1329
1359
  categorical = [c for c in factor_cols if not _is_numeric(df[c])]
@@ -1466,21 +1496,54 @@ def _design_rank(df: pd.DataFrame, rhs: str, factor_cols: list[str], model: str)
1466
1496
  design's in floating point when the units are very unequal (pascals beside kelvin)
1467
1497
  or a span is tiny next to its distance from zero. Coding each numeric factor to
1468
1498
  -1/+1 removes both without changing which terms the runs can separate, as long as
1469
- the coding re-expresses the model. A model that is not hierarchical can span other
1470
- columns once coded: ``A:B`` without ``B``, or a factor nested in another through the
1471
- other's 0/1 indicators, ``I1:B + I2:B``, which coded are ``(2*I1 - 1)*B`` and its
1472
- negative. Its rank is then its own, of its columns as given scaled to unit length,
1473
- which no choice of units changes. A mixture model's components are proportions and
1474
- are not coded.
1499
+ the coding re-expresses the model (see :func:`_coding_re_expresses`). A model that is
1500
+ not hierarchical can span other columns once coded: ``A:B`` without ``A`` and ``B``,
1501
+ which coded varies where ``A * B`` is the same in every run, or a factor nested in
1502
+ another through the other's 0/1 indicators, ``I1:B + I2:B``, which coded are
1503
+ ``(2*I1 - 1)*B`` and its negative. Its rank is then its own, of its columns as given
1504
+ scaled to unit length, which no choice of units changes. A mixture model's components
1505
+ are proportions and are not coded.
1475
1506
  """
1476
1507
  coded = np.asarray(_coded_model_matrix(df, rhs, factor_cols, model), dtype=float)
1477
1508
  rank = int(np.linalg.matrix_rank(coded))
1478
- if model in SCHEFFE_MODELS:
1509
+ if model in SCHEFFE_MODELS or _coding_re_expresses(df, rhs, factor_cols, coded):
1479
1510
  return rank
1480
- given = _unit_columns(np.asarray(dmatrix(rhs, df, return_type="dataframe"), dtype=float))
1481
- if int(np.linalg.matrix_rank(np.hstack([_unit_columns(coded), given]))) == rank:
1482
- return rank # the columns as given lie in the coded ones' span: coding re-expressed the model
1483
- return int(np.linalg.matrix_rank(given))
1511
+ return int(np.linalg.matrix_rank(_unit_columns(np.asarray(dmatrix(rhs, df, return_type="dataframe"), dtype=float))))
1512
+
1513
+
1514
+ #: The golden ratio, whose multiples give each factor a different second coding (see :func:`_coding_re_expresses`).
1515
+ _GOLDEN_RATIO = (1 + 5**0.5) / 2
1516
+
1517
+
1518
+ def _coding_re_expresses(df: pd.DataFrame, rhs: str, factor_cols: list[str], coded: np.ndarray) -> bool:
1519
+ """Whether coding the numeric factors re-expresses the model, rather than changing it.
1520
+
1521
+ A hierarchical model spans the same columns whatever each factor's origin and scale:
1522
+ ``(A - a)*(B - b)`` lies in the span of 1, ``A``, ``B`` and ``A:B``. Another model's span
1523
+ moves with them, and the factors' own units are one more origin and scale. Comparing the
1524
+ coded columns with those as given cannot tell the two apart, since it is the columns as
1525
+ given that may have lost rank in floating point. So the model matrix ``coded`` is compared
1526
+ with a second coding instead, as well conditioned as the first: the i-th numeric factor's
1527
+ -1/+1 coding ``x`` becomes ``(1 + g) * (x + g)``, with ``g`` the fractional part of ``i``
1528
+ times the golden ratio. A span that moves keeps to this coding only if the runs line up
1529
+ with it by chance. When the coded columns already span every run, no coding can add to
1530
+ them, the comparison cannot tell, and the model is taken as changed.
1531
+ """
1532
+ rank = int(np.linalg.matrix_rank(coded))
1533
+ if rank == len(coded):
1534
+ # shortcut: a saturated design is then ranked as given, which a span tiny next to its distance from zero can
1535
+ # lower; compare the codings on more points than the runs if such a design is met.
1536
+ return False
1537
+ second = df.copy()
1538
+ numeric = [col for col in factor_cols if _is_numeric(df[col])]
1539
+ for i, col in enumerate(numeric, start=1):
1540
+ g = i * _GOLDEN_RATIO % 1
1541
+ second[col] = (1 + g) * (_code_factor(df[col])[0] + g)
1542
+ moved = _unit_columns(
1543
+ np.asarray(dmatrix(_coded_formula(rhs, second, factor_cols), second, return_type="dataframe"), dtype=float)
1544
+ )
1545
+ both = np.hstack([_unit_columns(coded), moved])
1546
+ return int(np.linalg.matrix_rank(moved)) == rank == int(np.linalg.matrix_rank(both))
1484
1547
 
1485
1548
 
1486
1549
  def _unit_columns(matrix: np.ndarray) -> np.ndarray:
@@ -245,7 +245,9 @@ def extreme_vertices(a_mat: np.ndarray, b_vec: np.ndarray) -> np.ndarray:
245
245
  found = []
246
246
  combos = itertools.combinations(range(m), q - 1)
247
247
  while chunk := list(itertools.islice(combos, _VERTEX_CHUNK)):
248
- subsets = np.array(chunk, dtype=int).reshape(-1, q - 1)
248
+ # The row count is given, not -1: with one component each subset is empty, and numpy
249
+ # cannot infer a row count from rows of length 0 (#999).
250
+ subsets = np.array(chunk, dtype=int).reshape(len(chunk), q - 1)
249
251
  systems = np.concatenate([a_red[subsets], np.full((len(subsets), 1, q), 1.0 / np.sqrt(q))], axis=1)
250
252
  rhs = np.concatenate([b_red[subsets], np.full((len(subsets), 1), 1.0 / np.sqrt(q))], axis=1)
251
253
  solvable = np.abs(np.linalg.det(systems)) > 1e-12 # every row has unit length, so this is a relative test
@@ -420,12 +420,7 @@ def _fractional_cube(
420
420
  if not asked:
421
421
  generators = [f"{factor_names[-1]}={''.join(factor_names[:-1])}"]
422
422
  cube, frac_meta = dispatch_fractional_factorial(factors, resolution=resolution, generators=generators)
423
- res = frac_meta.get("resolution")
424
- if res is not None and res < 3:
425
- raise ValueError(
426
- f"The fractional cube has resolution {res}: it aliases main effects with each other, so no model of "
427
- "them can be fitted. Supply other generators or use cube='full'."
428
- )
423
+ res = frac_meta.get("resolution") # 3 or more: dispatch_fractional_factorial refuses generators that alias mains
429
424
  aliased = _aliased_interactions(cube, factor_names)
430
425
  if aliased and not asked:
431
426
  raise ValueError(
@@ -109,18 +109,16 @@ def _drop_worst_columns(columns: np.ndarray, k: int) -> np.ndarray:
109
109
  def _lin_half_fraction(h: np.ndarray, k: int) -> np.ndarray:
110
110
  """Best Lin half-fraction of ``h`` with ``k`` columns, over every choice of branching column."""
111
111
  order = h.shape[0]
112
- best, best_key = None, (np.inf, np.inf, np.inf)
113
- for branch in range(1, order):
114
- rows = h[:, branch] > 0
112
+
113
+ def branched(branch: int) -> np.ndarray:
115
114
  others = [c for c in range(1, order) if c != branch]
116
- design = _drop_worst_columns(h[rows][:, others], k)
115
+ return _drop_worst_columns(h[h[:, branch] > 0][:, others], k)
116
+
117
+ def key(design: np.ndarray) -> tuple[int, float, float]:
117
118
  s = design.T @ design
118
- key = (n_fully_aliased(design), e_s2(design), float(np.abs(s[np.triu_indices_from(s, k=1)]).max()))
119
- if key < best_key:
120
- best, best_key = design, key
121
- if best is None: # unreachable for order >= 4, which always offers a branching column
122
- raise RuntimeError("No branching column was available.")
123
- return best
119
+ return n_fully_aliased(design), e_s2(design), float(np.abs(s[np.triu_indices_from(s, k=1)]).max())
120
+
121
+ return min((branched(b) for b in range(1, order)), key=key) # the first branch of the best key, as before
124
122
 
125
123
 
126
124
  def _smallest_unaliased(k: int) -> np.ndarray | None: