pylmrob 0.5.13__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 (234) hide show
  1. pylmrob-0.5.13/.github/workflows/ci.yml +106 -0
  2. pylmrob-0.5.13/.github/workflows/wheels.yml +109 -0
  3. pylmrob-0.5.13/.gitignore +58 -0
  4. pylmrob-0.5.13/.pre-commit-config.yaml +37 -0
  5. pylmrob-0.5.13/.readthedocs.yaml +26 -0
  6. pylmrob-0.5.13/CHANGELOG.md +635 -0
  7. pylmrob-0.5.13/CITATION.cff +82 -0
  8. pylmrob-0.5.13/LICENSE +674 -0
  9. pylmrob-0.5.13/NOTICE +46 -0
  10. pylmrob-0.5.13/PKG-INFO +877 -0
  11. pylmrob-0.5.13/README.md +154 -0
  12. pylmrob-0.5.13/docs/Makefile +15 -0
  13. pylmrob-0.5.13/docs/api.md +49 -0
  14. pylmrob-0.5.13/docs/bench-report.md +107 -0
  15. pylmrob-0.5.13/docs/conf.py +108 -0
  16. pylmrob-0.5.13/docs/dev/engine_c_parallel.md +158 -0
  17. pylmrob-0.5.13/docs/engine_c.md +103 -0
  18. pylmrob-0.5.13/docs/index.md +71 -0
  19. pylmrob-0.5.13/docs/numerical-notes.md +189 -0
  20. pylmrob-0.5.13/docs/porting-from-r.md +77 -0
  21. pylmrob-0.5.13/docs/quickstart.md +146 -0
  22. pylmrob-0.5.13/docs/r-source-map.md +47 -0
  23. pylmrob-0.5.13/docs/research-notes.md +73 -0
  24. pylmrob-0.5.13/meson.build +85 -0
  25. pylmrob-0.5.13/plan.md +658 -0
  26. pylmrob-0.5.13/pyproject.toml +156 -0
  27. pylmrob-0.5.13/scripts/bench_engine_c.py +84 -0
  28. pylmrob-0.5.13/scripts/benchmark.R +175 -0
  29. pylmrob-0.5.13/scripts/benchmark.py +263 -0
  30. pylmrob-0.5.13/scripts/build_bench_report.py +219 -0
  31. pylmrob-0.5.13/scripts/generate_r_reference.R +288 -0
  32. pylmrob-0.5.13/src/pylmrob/__init__.py +21 -0
  33. pylmrob-0.5.13/src/pylmrob/_core/__init__.py +19 -0
  34. pylmrob-0.5.13/src/pylmrob/_core/_fast_s.pyx +938 -0
  35. pylmrob-0.5.13/src/pylmrob/_core/_lmrob.pyx +2364 -0
  36. pylmrob-0.5.13/src/pylmrob/_core/_psi.pyx +874 -0
  37. pylmrob-0.5.13/src/pylmrob/_core/_stub.pyx +27 -0
  38. pylmrob-0.5.13/src/pylmrob/_core/meson.build +55 -0
  39. pylmrob-0.5.13/src/pylmrob/_fast_s.py +686 -0
  40. pylmrob-0.5.13/src/pylmrob/_mm.py +54 -0
  41. pylmrob-0.5.13/src/pylmrob/_psifuns.py +811 -0
  42. pylmrob-0.5.13/src/pylmrob/_utils.py +28 -0
  43. pylmrob-0.5.13/src/pylmrob/_version.py +9 -0
  44. pylmrob-0.5.13/src/pylmrob/anova.py +231 -0
  45. pylmrob-0.5.13/src/pylmrob/control.py +173 -0
  46. pylmrob-0.5.13/src/pylmrob/d_scale.py +217 -0
  47. pylmrob-0.5.13/src/pylmrob/diagnostics.py +123 -0
  48. pylmrob-0.5.13/src/pylmrob/formula.py +154 -0
  49. pylmrob-0.5.13/src/pylmrob/inference.py +429 -0
  50. pylmrob-0.5.13/src/pylmrob/lmrob.py +734 -0
  51. pylmrob-0.5.13/src/pylmrob/meson.build +24 -0
  52. pylmrob-0.5.13/src/pylmrob/ms_estimator.py +273 -0
  53. pylmrob-0.5.13/src/pylmrob/psi.py +148 -0
  54. pylmrob-0.5.13/src/pylmrob/results.py +128 -0
  55. pylmrob-0.5.13/src/pylmrob/scale.py +284 -0
  56. pylmrob-0.5.13/src/pylmrob/summary.py +224 -0
  57. pylmrob-0.5.13/tests/__init__.py +0 -0
  58. pylmrob-0.5.13/tests/bench/py/classical_aircraft.json +138 -0
  59. pylmrob-0.5.13/tests/bench/py/classical_coleman.json +143 -0
  60. pylmrob-0.5.13/tests/bench/py/classical_delivery.json +122 -0
  61. pylmrob-0.5.13/tests/bench/py/classical_hbk.json +282 -0
  62. pylmrob-0.5.13/tests/bench/py/classical_pension.json +93 -0
  63. pylmrob-0.5.13/tests/bench/py/classical_phosphor.json +101 -0
  64. pylmrob-0.5.13/tests/bench/py/classical_salinity.json +141 -0
  65. pylmrob-0.5.13/tests/bench/py/classical_stackloss.json +120 -0
  66. pylmrob-0.5.13/tests/bench/py/classical_starsCYG.json +180 -0
  67. pylmrob-0.5.13/tests/bench/py/classical_wood.json +143 -0
  68. pylmrob-0.5.13/tests/bench/py/psi_bisquare.json +120 -0
  69. pylmrob-0.5.13/tests/bench/py/psi_ggw.json +120 -0
  70. pylmrob-0.5.13/tests/bench/py/psi_hampel.json +120 -0
  71. pylmrob-0.5.13/tests/bench/py/psi_lqq.json +120 -0
  72. pylmrob-0.5.13/tests/bench/py/psi_optimal.json +120 -0
  73. pylmrob-0.5.13/tests/bench/py/setting_KS2011_stackloss.json +120 -0
  74. pylmrob-0.5.13/tests/bench/py/setting_KS2014_stackloss.json +120 -0
  75. pylmrob-0.5.13/tests/bench/py/synth_bisquare_n2000_p20.json +6533 -0
  76. pylmrob-0.5.13/tests/bench/py/synth_bisquare_n500_p10.json +1683 -0
  77. pylmrob-0.5.13/tests/bench/py/synth_ggw_n2000_p20.json +6533 -0
  78. pylmrob-0.5.13/tests/bench/py/synth_ggw_n500_p10.json +1683 -0
  79. pylmrob-0.5.13/tests/bench/py/synth_hampel_n2000_p20.json +6533 -0
  80. pylmrob-0.5.13/tests/bench/py/synth_hampel_n500_p10.json +1683 -0
  81. pylmrob-0.5.13/tests/bench/py/synth_lqq_n2000_p20.json +6533 -0
  82. pylmrob-0.5.13/tests/bench/py/synth_lqq_n500_p10.json +1683 -0
  83. pylmrob-0.5.13/tests/bench/py/synth_n10000_p20.json +30533 -0
  84. pylmrob-0.5.13/tests/bench/py/synth_n10000_p50.json +32783 -0
  85. pylmrob-0.5.13/tests/bench/py/synth_n1000_p10.json +3183 -0
  86. pylmrob-0.5.13/tests/bench/py/synth_n100_p5.json +383 -0
  87. pylmrob-0.5.13/tests/bench/py/synth_n2000_p20.json +6533 -0
  88. pylmrob-0.5.13/tests/bench/py/synth_n5000_p20.json +15533 -0
  89. pylmrob-0.5.13/tests/bench/py/synth_n500_p10.json +1683 -0
  90. pylmrob-0.5.13/tests/bench/py/synth_optimal_n2000_p20.json +6533 -0
  91. pylmrob-0.5.13/tests/bench/py/synth_optimal_n500_p10.json +1683 -0
  92. pylmrob-0.5.13/tests/bench/py_engine_c/classical_aircraft.json +138 -0
  93. pylmrob-0.5.13/tests/bench/py_engine_c/classical_coleman.json +143 -0
  94. pylmrob-0.5.13/tests/bench/py_engine_c/classical_delivery.json +122 -0
  95. pylmrob-0.5.13/tests/bench/py_engine_c/classical_hbk.json +282 -0
  96. pylmrob-0.5.13/tests/bench/py_engine_c/classical_pension.json +93 -0
  97. pylmrob-0.5.13/tests/bench/py_engine_c/classical_phosphor.json +101 -0
  98. pylmrob-0.5.13/tests/bench/py_engine_c/classical_salinity.json +141 -0
  99. pylmrob-0.5.13/tests/bench/py_engine_c/classical_stackloss.json +120 -0
  100. pylmrob-0.5.13/tests/bench/py_engine_c/classical_starsCYG.json +180 -0
  101. pylmrob-0.5.13/tests/bench/py_engine_c/classical_wood.json +143 -0
  102. pylmrob-0.5.13/tests/bench/py_engine_c/psi_bisquare.json +120 -0
  103. pylmrob-0.5.13/tests/bench/py_engine_c/psi_ggw.json +120 -0
  104. pylmrob-0.5.13/tests/bench/py_engine_c/psi_hampel.json +120 -0
  105. pylmrob-0.5.13/tests/bench/py_engine_c/psi_lqq.json +120 -0
  106. pylmrob-0.5.13/tests/bench/py_engine_c/psi_optimal.json +120 -0
  107. pylmrob-0.5.13/tests/bench/py_engine_c/setting_KS2011_stackloss.json +120 -0
  108. pylmrob-0.5.13/tests/bench/py_engine_c/setting_KS2014_stackloss.json +120 -0
  109. pylmrob-0.5.13/tests/bench/py_engine_c/synth_bisquare_n2000_p20.json +6533 -0
  110. pylmrob-0.5.13/tests/bench/py_engine_c/synth_bisquare_n500_p10.json +1683 -0
  111. pylmrob-0.5.13/tests/bench/py_engine_c/synth_ggw_n2000_p20.json +6533 -0
  112. pylmrob-0.5.13/tests/bench/py_engine_c/synth_ggw_n500_p10.json +1683 -0
  113. pylmrob-0.5.13/tests/bench/py_engine_c/synth_hampel_n2000_p20.json +6533 -0
  114. pylmrob-0.5.13/tests/bench/py_engine_c/synth_hampel_n500_p10.json +1683 -0
  115. pylmrob-0.5.13/tests/bench/py_engine_c/synth_lqq_n2000_p20.json +6533 -0
  116. pylmrob-0.5.13/tests/bench/py_engine_c/synth_lqq_n500_p10.json +1683 -0
  117. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n10000_p20.json +30533 -0
  118. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n10000_p50.json +32783 -0
  119. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n1000_p10.json +3183 -0
  120. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n100_p5.json +383 -0
  121. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n2000_p20.json +6533 -0
  122. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n5000_p20.json +15533 -0
  123. pylmrob-0.5.13/tests/bench/py_engine_c/synth_n500_p10.json +1683 -0
  124. pylmrob-0.5.13/tests/bench/py_engine_c/synth_optimal_n2000_p20.json +6533 -0
  125. pylmrob-0.5.13/tests/bench/py_engine_c/synth_optimal_n500_p10.json +1683 -0
  126. pylmrob-0.5.13/tests/bench/r/classical_aircraft.json +1 -0
  127. pylmrob-0.5.13/tests/bench/r/classical_coleman.json +1 -0
  128. pylmrob-0.5.13/tests/bench/r/classical_delivery.json +1 -0
  129. pylmrob-0.5.13/tests/bench/r/classical_hbk.json +1 -0
  130. pylmrob-0.5.13/tests/bench/r/classical_pension.json +1 -0
  131. pylmrob-0.5.13/tests/bench/r/classical_phosphor.json +1 -0
  132. pylmrob-0.5.13/tests/bench/r/classical_salinity.json +1 -0
  133. pylmrob-0.5.13/tests/bench/r/classical_stackloss.json +1 -0
  134. pylmrob-0.5.13/tests/bench/r/classical_starsCYG.json +1 -0
  135. pylmrob-0.5.13/tests/bench/r/classical_wood.json +1 -0
  136. pylmrob-0.5.13/tests/bench/r/psi_bisquare.json +1 -0
  137. pylmrob-0.5.13/tests/bench/r/psi_ggw.json +1 -0
  138. pylmrob-0.5.13/tests/bench/r/psi_hampel.json +1 -0
  139. pylmrob-0.5.13/tests/bench/r/psi_lqq.json +1 -0
  140. pylmrob-0.5.13/tests/bench/r/psi_optimal.json +1 -0
  141. pylmrob-0.5.13/tests/bench/r/setting_KS2011_stackloss.json +1 -0
  142. pylmrob-0.5.13/tests/bench/r/setting_KS2014_stackloss.json +1 -0
  143. pylmrob-0.5.13/tests/bench/r/synth_bisquare_n2000_p20.json +1 -0
  144. pylmrob-0.5.13/tests/bench/r/synth_bisquare_n500_p10.json +1 -0
  145. pylmrob-0.5.13/tests/bench/r/synth_ggw_n2000_p20.json +1 -0
  146. pylmrob-0.5.13/tests/bench/r/synth_ggw_n500_p10.json +1 -0
  147. pylmrob-0.5.13/tests/bench/r/synth_hampel_n2000_p20.json +1 -0
  148. pylmrob-0.5.13/tests/bench/r/synth_hampel_n500_p10.json +1 -0
  149. pylmrob-0.5.13/tests/bench/r/synth_lqq_n2000_p20.json +1 -0
  150. pylmrob-0.5.13/tests/bench/r/synth_lqq_n500_p10.json +1 -0
  151. pylmrob-0.5.13/tests/bench/r/synth_n10000_p20.json +1 -0
  152. pylmrob-0.5.13/tests/bench/r/synth_n10000_p50.json +1 -0
  153. pylmrob-0.5.13/tests/bench/r/synth_n1000_p10.json +1 -0
  154. pylmrob-0.5.13/tests/bench/r/synth_n100_p5.json +1 -0
  155. pylmrob-0.5.13/tests/bench/r/synth_n2000_p20.json +1 -0
  156. pylmrob-0.5.13/tests/bench/r/synth_n5000_p20.json +1 -0
  157. pylmrob-0.5.13/tests/bench/r/synth_n500_p10.json +1 -0
  158. pylmrob-0.5.13/tests/bench/r/synth_optimal_n2000_p20.json +1 -0
  159. pylmrob-0.5.13/tests/bench/r/synth_optimal_n500_p10.json +1 -0
  160. pylmrob-0.5.13/tests/conftest.py +234 -0
  161. pylmrob-0.5.13/tests/data/aircraft.csv +24 -0
  162. pylmrob-0.5.13/tests/data/coleman.csv +21 -0
  163. pylmrob-0.5.13/tests/data/delivery.csv +26 -0
  164. pylmrob-0.5.13/tests/data/education.csv +51 -0
  165. pylmrob-0.5.13/tests/data/hbk.csv +76 -0
  166. pylmrob-0.5.13/tests/data/pension.csv +19 -0
  167. pylmrob-0.5.13/tests/data/phosphor.csv +19 -0
  168. pylmrob-0.5.13/tests/data/salinity.csv +29 -0
  169. pylmrob-0.5.13/tests/data/stackloss.csv +22 -0
  170. pylmrob-0.5.13/tests/data/starsCYG.csv +48 -0
  171. pylmrob-0.5.13/tests/data/synth_n10000_p20.csv +10001 -0
  172. pylmrob-0.5.13/tests/data/synth_n10000_p50.csv +10001 -0
  173. pylmrob-0.5.13/tests/data/synth_n1000_p10.csv +1001 -0
  174. pylmrob-0.5.13/tests/data/synth_n100_p5.csv +101 -0
  175. pylmrob-0.5.13/tests/data/synth_n2000_p20.csv +2001 -0
  176. pylmrob-0.5.13/tests/data/synth_n5000_p20.csv +5001 -0
  177. pylmrob-0.5.13/tests/data/synth_n500_p10.csv +501 -0
  178. pylmrob-0.5.13/tests/data/wood.csv +21 -0
  179. pylmrob-0.5.13/tests/integration/__init__.py +0 -0
  180. pylmrob-0.5.13/tests/integration/test_engine_c_parity.py +70 -0
  181. pylmrob-0.5.13/tests/integration/test_factor_designs.py +163 -0
  182. pylmrob-0.5.13/tests/integration/test_lmrob_api.py +173 -0
  183. pylmrob-0.5.13/tests/property/__init__.py +0 -0
  184. pylmrob-0.5.13/tests/property/test_equivariance.py +107 -0
  185. pylmrob-0.5.13/tests/reference/aircraft_default.json +1 -0
  186. pylmrob-0.5.13/tests/reference/coleman_default.json +1 -0
  187. pylmrob-0.5.13/tests/reference/delivery_default.json +1 -0
  188. pylmrob-0.5.13/tests/reference/education_simple.json +1 -0
  189. pylmrob-0.5.13/tests/reference/hbk_default.json +1 -0
  190. pylmrob-0.5.13/tests/reference/pension_default.json +1 -0
  191. pylmrob-0.5.13/tests/reference/phosphor_default.json +1 -0
  192. pylmrob-0.5.13/tests/reference/salinity_default.json +1 -0
  193. pylmrob-0.5.13/tests/reference/stackloss_KS2011.json +1 -0
  194. pylmrob-0.5.13/tests/reference/stackloss_KS2014.json +1 -0
  195. pylmrob-0.5.13/tests/reference/stackloss_default.json +1 -0
  196. pylmrob-0.5.13/tests/reference/stackloss_psi_bisquare.json +1 -0
  197. pylmrob-0.5.13/tests/reference/stackloss_psi_ggw.json +1 -0
  198. pylmrob-0.5.13/tests/reference/stackloss_psi_hampel.json +1 -0
  199. pylmrob-0.5.13/tests/reference/stackloss_psi_lqq.json +1 -0
  200. pylmrob-0.5.13/tests/reference/stackloss_psi_optimal.json +1 -0
  201. pylmrob-0.5.13/tests/reference/starsCYG_default.json +1 -0
  202. pylmrob-0.5.13/tests/reference/synthetic_n1000_p10_c00.json +1 -0
  203. pylmrob-0.5.13/tests/reference/synthetic_n1000_p10_c10.json +1 -0
  204. pylmrob-0.5.13/tests/reference/synthetic_n1000_p10_c30.json +1 -0
  205. pylmrob-0.5.13/tests/reference/synthetic_n1000_p3_c00.json +1 -0
  206. pylmrob-0.5.13/tests/reference/synthetic_n1000_p3_c10.json +1 -0
  207. pylmrob-0.5.13/tests/reference/synthetic_n1000_p3_c30.json +1 -0
  208. pylmrob-0.5.13/tests/reference/synthetic_n200_p10_c00.json +1 -0
  209. pylmrob-0.5.13/tests/reference/synthetic_n200_p10_c10.json +1 -0
  210. pylmrob-0.5.13/tests/reference/synthetic_n200_p10_c30.json +1 -0
  211. pylmrob-0.5.13/tests/reference/synthetic_n200_p3_c00.json +1 -0
  212. pylmrob-0.5.13/tests/reference/synthetic_n200_p3_c10.json +1 -0
  213. pylmrob-0.5.13/tests/reference/synthetic_n200_p3_c30.json +1 -0
  214. pylmrob-0.5.13/tests/reference/synthetic_n50_p10_c00.json +1 -0
  215. pylmrob-0.5.13/tests/reference/synthetic_n50_p10_c10.json +1 -0
  216. pylmrob-0.5.13/tests/reference/synthetic_n50_p10_c30.json +1 -0
  217. pylmrob-0.5.13/tests/reference/synthetic_n50_p3_c00.json +1 -0
  218. pylmrob-0.5.13/tests/reference/synthetic_n50_p3_c10.json +1 -0
  219. pylmrob-0.5.13/tests/reference/synthetic_n50_p3_c30.json +1 -0
  220. pylmrob-0.5.13/tests/reference/wood_default.json +1 -0
  221. pylmrob-0.5.13/tests/unit/__init__.py +0 -0
  222. pylmrob-0.5.13/tests/unit/test_d_scale.py +182 -0
  223. pylmrob-0.5.13/tests/unit/test_fast_s_parallel.py +127 -0
  224. pylmrob-0.5.13/tests/unit/test_inference.py +104 -0
  225. pylmrob-0.5.13/tests/unit/test_lmrob_kernel.py +138 -0
  226. pylmrob-0.5.13/tests/unit/test_psi.py +131 -0
  227. pylmrob-0.5.13/tests/unit/test_scale.py +56 -0
  228. pylmrob-0.5.13/tests/unit/test_smoke.py +44 -0
  229. pylmrob-0.5.13/tests/validation/__init__.py +0 -0
  230. pylmrob-0.5.13/tests/validation/test_reference_loader.py +87 -0
  231. pylmrob-0.5.13/tests/validation/test_summary_anova.py +219 -0
  232. pylmrob-0.5.13/tests/validation/test_vs_r_classical.py +100 -0
  233. pylmrob-0.5.13/tests/validation/test_vs_r_psi_families.py +83 -0
  234. pylmrob-0.5.13/tests/validation/test_weights_vs_r.py +149 -0
@@ -0,0 +1,106 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ # Cancel in-progress runs of the same workflow on the same ref.
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ lint:
16
+ name: Lint + type check
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+ cache: "pip"
24
+ - name: Install project + lint deps
25
+ # Install the project (so ty sees numpy/scipy/pandas/formulaic in
26
+ # the same env) plus ruff and ty.
27
+ run: |
28
+ python -m pip install --upgrade pip
29
+ python -m pip install ".[dev]" ruff ty
30
+ - name: Ruff
31
+ run: ruff check src tests
32
+ - name: Ruff format check
33
+ run: ruff format --check src tests
34
+ - name: ty (type check)
35
+ # Astral's ty is the project's standard type checker (replaces mypy).
36
+ run: ty check src/pylmrob
37
+
38
+ test:
39
+ name: Tests (${{ matrix.os }} / py${{ matrix.python-version }})
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ os: [ubuntu-latest, macos-latest, windows-latest]
45
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
46
+ steps:
47
+ - uses: actions/checkout@v4
48
+
49
+ - uses: actions/setup-python@v5
50
+ with:
51
+ python-version: ${{ matrix.python-version }}
52
+ cache: "pip"
53
+
54
+ # ------------------------------------------------------------------
55
+ # R + robustbase. We only run rpy2-backed tests on Linux to keep CI
56
+ # cheap; correctness against the JSON references is platform-agnostic.
57
+ # ------------------------------------------------------------------
58
+ - name: Install R (Linux)
59
+ if: runner.os == 'Linux'
60
+ uses: r-lib/actions/setup-r@v2
61
+ with:
62
+ r-version: "release"
63
+ use-public-rspm: true
64
+
65
+ - name: Cache R packages (Linux)
66
+ if: runner.os == 'Linux'
67
+ uses: actions/cache@v4
68
+ with:
69
+ path: ${{ env.R_LIBS_USER }}
70
+ key: r-${{ runner.os }}-robustbase-${{ hashFiles('scripts/generate_r_reference.R') }}
71
+
72
+ - name: Install robustbase (Linux)
73
+ if: runner.os == 'Linux'
74
+ run: Rscript -e 'install.packages(c("robustbase","jsonlite"), repos="https://cloud.r-project.org")'
75
+
76
+ - name: Install OpenMP runtime (macOS)
77
+ if: runner.os == 'macOS'
78
+ run: |
79
+ brew install libomp
80
+ echo "LDFLAGS=-L$(brew --prefix libomp)/lib" >> $GITHUB_ENV
81
+ echo "CPPFLAGS=-I$(brew --prefix libomp)/include" >> $GITHUB_ENV
82
+
83
+ - name: Install build deps + project (no rpy2)
84
+ # Non-editable install: lets meson-python's build-isolation handle
85
+ # Cython/meson/ninja without us tracking them. Editable mode would
86
+ # require keeping the build env around for the rebuild-on-import
87
+ # loop, which is more brittle on CI.
88
+ run: |
89
+ python -m pip install --upgrade pip
90
+ python -m pip install -v ".[dev]"
91
+
92
+ # rpy2 is only needed for the live R-bridge tests, which we skip on
93
+ # macOS/Windows. Installing it elsewhere fails because the build
94
+ # requires R to be on PATH.
95
+ - name: Install rpy2 (Linux only)
96
+ if: runner.os == 'Linux'
97
+ run: python -m pip install ".[validation]"
98
+
99
+ - name: Generate R reference (Linux only)
100
+ if: runner.os == 'Linux'
101
+ run: Rscript scripts/generate_r_reference.R
102
+
103
+ - name: Run pytest
104
+ env:
105
+ PYROBUSTLM_SKIP_RPY2: ${{ runner.os != 'Linux' && '1' || '' }}
106
+ run: pytest -v --maxfail=10
@@ -0,0 +1,109 @@
1
+ name: Build wheels
2
+
3
+ # Manual trigger only for the first release; once we've confirmed that
4
+ # wheels build and ``pip install pylmrob`` works from TestPyPI, flip
5
+ # to ``push: tags: ["v*"]`` so tagging publishes to real PyPI
6
+ # automatically.
7
+ on:
8
+ workflow_dispatch:
9
+ inputs:
10
+ target:
11
+ description: "Publish target"
12
+ required: true
13
+ default: "testpypi"
14
+ type: choice
15
+ options:
16
+ - testpypi
17
+ - pypi
18
+ - none
19
+
20
+ jobs:
21
+ build_wheels:
22
+ name: ${{ matrix.os }} ${{ matrix.arch }}
23
+ runs-on: ${{ matrix.os }}
24
+ strategy:
25
+ fail-fast: false
26
+ matrix:
27
+ include:
28
+ - os: ubuntu-latest
29
+ arch: x86_64
30
+ - os: ubuntu-latest
31
+ arch: aarch64
32
+ # macos-13 (Intel) dropped: the free runner queue is unusable
33
+ # in 2026 (5+ hour waits) as GitHub winds down Intel mac
34
+ # capacity. Apple Silicon covers all Macs sold since late
35
+ # 2020; Intel users can install from the sdist.
36
+ - os: macos-14 # Apple Silicon runner
37
+ arch: arm64
38
+ - os: windows-latest
39
+ arch: AMD64
40
+ steps:
41
+ - uses: actions/checkout@v4
42
+
43
+ - name: Set up QEMU (linux aarch64)
44
+ if: matrix.os == 'ubuntu-latest' && matrix.arch == 'aarch64'
45
+ uses: docker/setup-qemu-action@v3
46
+ with:
47
+ platforms: arm64
48
+
49
+ - name: Build wheels
50
+ uses: pypa/cibuildwheel@v2.21
51
+ env:
52
+ CIBW_ARCHS: ${{ matrix.arch }}
53
+
54
+ - uses: actions/upload-artifact@v4
55
+ with:
56
+ name: wheels-${{ matrix.os }}-${{ matrix.arch }}
57
+ path: ./wheelhouse/*.whl
58
+
59
+ build_sdist:
60
+ name: Build sdist
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ - uses: actions/setup-python@v5
65
+ with:
66
+ python-version: "3.12"
67
+ - run: |
68
+ python -m pip install --upgrade pip build
69
+ python -m build --sdist
70
+ - uses: actions/upload-artifact@v4
71
+ with:
72
+ name: sdist
73
+ path: dist/*.tar.gz
74
+
75
+ publish_testpypi:
76
+ name: Publish to TestPyPI
77
+ needs: [build_wheels, build_sdist]
78
+ runs-on: ubuntu-latest
79
+ if: inputs.target == 'testpypi'
80
+ environment:
81
+ name: testpypi
82
+ url: https://test.pypi.org/p/pylmrob
83
+ permissions:
84
+ id-token: write # OIDC trusted publisher
85
+ steps:
86
+ - uses: actions/download-artifact@v4
87
+ with:
88
+ path: dist
89
+ merge-multiple: true
90
+ - uses: pypa/gh-action-pypi-publish@release/v1
91
+ with:
92
+ repository-url: https://test.pypi.org/legacy/
93
+
94
+ publish:
95
+ name: Publish to PyPI
96
+ needs: [build_wheels, build_sdist]
97
+ runs-on: ubuntu-latest
98
+ if: inputs.target == 'pypi' || startsWith(github.ref, 'refs/tags/v')
99
+ environment:
100
+ name: pypi
101
+ url: https://pypi.org/p/pylmrob
102
+ permissions:
103
+ id-token: write # OIDC trusted publisher
104
+ steps:
105
+ - uses: actions/download-artifact@v4
106
+ with:
107
+ path: dist
108
+ merge-multiple: true
109
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,58 @@
1
+ # Build artifacts
2
+ build/
3
+ builddir/
4
+ dist/
5
+ *.egg-info/
6
+ *.egg
7
+ __pycache__/
8
+ *.py[cod]
9
+ *.so
10
+ *.dylib
11
+ *.pyd
12
+ *.dll
13
+
14
+ # Cython generated
15
+ src/pylmrob/_core/*.c
16
+ src/pylmrob/_core/*.cpp
17
+ src/pylmrob/_core/*.html
18
+
19
+ # Environments
20
+ .venv/
21
+ venv/
22
+ .env
23
+ .python-version
24
+
25
+ # Tooling caches
26
+ .pytest_cache/
27
+ .mypy_cache/
28
+ .ruff_cache/
29
+ .hypothesis/
30
+ .coverage
31
+ .coverage.*
32
+ htmlcov/
33
+ coverage.xml
34
+ .tox/
35
+
36
+ # IDE / editor
37
+ .idea/
38
+ .vscode/
39
+ *.swp
40
+ *~
41
+ \#*\#
42
+ .DS_Store
43
+
44
+ # Generated docs
45
+ docs/_build/
46
+ docs/_static/
47
+ docs/api/_autosummary/
48
+
49
+ # R reference outputs (regenerated on demand; small ones may be committed under tests/reference/)
50
+ scripts/_rcache/
51
+
52
+ # Test scratch (regenerated by tests/unit/test_d_scale.py)
53
+ tests/data/_d_test_*.csv
54
+
55
+ # Local-only
56
+ local/
57
+ scratch/
58
+ uv.lock
@@ -0,0 +1,37 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: check-added-large-files
11
+ args: ["--maxkb=500"]
12
+
13
+ - repo: https://github.com/astral-sh/ruff-pre-commit
14
+ rev: v0.6.9
15
+ hooks:
16
+ - id: ruff
17
+ args: ["--fix"]
18
+ - id: ruff-format
19
+
20
+ # ty is run in CI via `uvx ty check`. Adding it here as a local hook so
21
+ # it runs against the project venv (which has the compiled extension
22
+ # available, unlike isolated pre-commit envs).
23
+ - repo: local
24
+ hooks:
25
+ - id: ty
26
+ name: ty (type check)
27
+ entry: uvx ty check
28
+ language: system
29
+ pass_filenames: false
30
+ files: ^src/pylmrob/.*\.py$
31
+
32
+ - repo: https://github.com/compilerla/conventional-pre-commit
33
+ rev: v3.4.0
34
+ hooks:
35
+ - id: conventional-pre-commit
36
+ stages: [commit-msg]
37
+ args: []
@@ -0,0 +1,26 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ # Read the Docs config for pylmrob. Build the Sphinx site from docs/.
3
+
4
+ version: 2
5
+
6
+ build:
7
+ os: ubuntu-22.04
8
+ tools:
9
+ python: "3.11"
10
+ apt_packages:
11
+ - libopenblas-dev
12
+ - liblapack-dev
13
+ jobs:
14
+ # meson-python needs the package built before Sphinx can autodoc it.
15
+ # Install the build chain explicitly so --no-build-isolation works.
16
+ post_install:
17
+ - pip install "meson-python>=0.16" "meson>=1.4" ninja "Cython>=3" "numpy>=2"
18
+ - pip install -e ".[docs]" --no-build-isolation
19
+
20
+ sphinx:
21
+ configuration: docs/conf.py
22
+ builder: html
23
+ fail_on_warning: true
24
+
25
+ # Don't ship epub/pdf builders; HTML only.
26
+ formats: []