lme-python 0.2.1__tar.gz → 0.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 (350) hide show
  1. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/audit.yml +1 -1
  2. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/cache-prime.yml +9 -3
  3. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/ci.yml +26 -13
  4. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/fuzz-smoke.yml +1 -1
  5. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/python-release.yml +88 -8
  6. {lme_python-0.2.1 → lme_python-0.2.2}/AGENTS.md +14 -2
  7. {lme_python-0.2.1 → lme_python-0.2.2}/BENCHMARKS.md +1 -1
  8. {lme_python-0.2.1 → lme_python-0.2.2}/BENCHMARK_COVERAGE.md +21 -5
  9. {lme_python-0.2.1 → lme_python-0.2.2}/CHANGELOG.md +25 -0
  10. {lme_python-0.2.1 → lme_python-0.2.2}/CONTRIBUTING.md +5 -2
  11. {lme_python-0.2.1 → lme_python-0.2.2}/Cargo.lock +1 -1
  12. {lme_python-0.2.1 → lme_python-0.2.2}/Cargo.toml +1 -1
  13. {lme_python-0.2.1 → lme_python-0.2.2}/GUIDE.md +38 -10
  14. {lme_python-0.2.1 → lme_python-0.2.2}/OPTIMIZATION.md +2 -2
  15. lme_python-0.2.2/PKG-INFO +67 -0
  16. {lme_python-0.2.1 → lme_python-0.2.2}/README.md +7 -7
  17. {lme_python-0.2.1 → lme_python-0.2.2}/RELEASING.md +20 -7
  18. {lme_python-0.2.1 → lme_python-0.2.2}/REPO_COMPLETION_BY_AREA.md +16 -16
  19. {lme_python-0.2.1 → lme_python-0.2.2}/Taskfile.yml +20 -0
  20. {lme_python-0.2.1 → lme_python-0.2.2}/USABILITY.md +6 -6
  21. {lme_python-0.2.1 → lme_python-0.2.2}/benches/bench_math.rs +21 -0
  22. lme_python-0.2.2/benchmarks/external-nlmm-inference-python-timings-2026-08-14.json +198 -0
  23. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/COMPARISONS.md +9 -4
  24. lme_python-0.2.2/comparisons/bench_external_timings.R +138 -0
  25. lme_python-0.2.2/comparisons/pastes_emmeans.R +48 -0
  26. lme_python-0.2.2/comparisons/pastes_glht.R +36 -0
  27. lme_python-0.2.2/comparisons/sleepstudy_confint_profile.R +16 -0
  28. lme_python-0.2.2/completion_manifest.json +20 -0
  29. lme_python-0.2.2/examples/bench_external_timings.rs +262 -0
  30. lme_python-0.2.2/examples/explore_formula_ast.rs +137 -0
  31. lme_python-0.2.2/examples/explore_mcp_adjust.rs +78 -0
  32. lme_python-0.2.2/examples/explore_theta_grid.rs +76 -0
  33. {lme_python-0.2.1 → lme_python-0.2.2}/pyproject.toml +8 -0
  34. {lme_python-0.2.1 → lme_python-0.2.2}/python/Cargo.lock +2 -2
  35. {lme_python-0.2.1 → lme_python-0.2.2}/python/Cargo.toml +2 -1
  36. {lme_python-0.2.1 → lme_python-0.2.2}/python/PYTHON_GUIDE.md +40 -10
  37. lme_python-0.2.2/python/README.md +44 -0
  38. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/parity.py +5 -3
  39. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/run.py +1 -1
  40. {lme_python-0.2.1 → lme_python-0.2.2}/python/lme_python.pyi +66 -2
  41. {lme_python-0.2.1 → lme_python-0.2.2}/python/src/lib.rs +592 -175
  42. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_api_parity.py +59 -9
  43. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/README.md +5 -1
  44. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/lme_ci.py +208 -3
  45. lme_python-0.2.2/scripts/explorations/README.md +20 -0
  46. lme_python-0.2.2/scripts/run_external_timings.py +236 -0
  47. lme_python-0.2.2/src/basis.rs +529 -0
  48. {lme_python-0.2.1 → lme_python-0.2.2}/src/bootstrap.rs +143 -18
  49. {lme_python-0.2.1 → lme_python-0.2.2}/src/contrast.rs +16 -12
  50. lme_python-0.2.2/src/emmeans.rs +459 -0
  51. {lme_python-0.2.1 → lme_python-0.2.2}/src/family.rs +65 -0
  52. lme_python-0.2.2/src/formula.rs +1360 -0
  53. {lme_python-0.2.1 → lme_python-0.2.2}/src/glmm_math.rs +80 -11
  54. {lme_python-0.2.1 → lme_python-0.2.2}/src/lib.rs +48 -28
  55. lme_python-0.2.2/src/mcp.rs +350 -0
  56. {lme_python-0.2.1 → lme_python-0.2.2}/src/model_matrix.rs +532 -176
  57. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/fit.rs +124 -7
  58. {lme_python-0.2.1 → lme_python-0.2.2}/src/optimizer.rs +139 -15
  59. lme_python-0.2.2/src/profile_ci.rs +1118 -0
  60. {lme_python-0.2.1 → lme_python-0.2.2}/src/robust.rs +2 -1
  61. {lme_python-0.2.1 → lme_python-0.2.2}/src/satterthwaite.rs +1 -0
  62. lme_python-0.2.2/src/studentized_range.rs +224 -0
  63. {lme_python-0.2.1 → lme_python-0.2.2}/tests/ci_consolidated.rs +10 -0
  64. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_gamma_dyestuff.json +4 -1
  65. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/golden_parity_manifest.json +8 -1
  66. lme_python-0.2.2/tests/data/pastes_emmeans.json +15 -0
  67. lme_python-0.2.2/tests/data/pastes_glht_tukey.json +18 -0
  68. lme_python-0.2.2/tests/data/r_edge_case_matrix.json +432 -0
  69. lme_python-0.2.2/tests/data/sleepstudy_confint_profile_vc.json +12 -0
  70. {lme_python-0.2.1 → lme_python-0.2.2}/tests/generate_nlmm_fixtures.R +17 -6
  71. lme_python-0.2.2/tests/generate_r_edge_case_matrix.R +211 -0
  72. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_anova.rs +1 -0
  73. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_bootstrap.rs +15 -0
  74. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_confint_profile.rs +80 -1
  75. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_coverage_gaps.rs +1 -0
  76. lme_python-0.2.2/tests/test_emmeans.rs +166 -0
  77. lme_python-0.2.2/tests/test_explorations.rs +159 -0
  78. lme_python-0.2.2/tests/test_external_timings_artifact.rs +72 -0
  79. lme_python-0.2.2/tests/test_formula.rs +94 -0
  80. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_formula_stress.rs +170 -11
  81. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm.rs +86 -1
  82. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm_offset_grouseticks.rs +11 -1
  83. lme_python-0.2.2/tests/test_mcp.rs +182 -0
  84. lme_python-0.2.2/tests/test_nlmm_agq.rs +75 -0
  85. lme_python-0.2.2/tests/test_r_edge_case_matrix.rs +247 -0
  86. lme_python-0.2.1/PKG-INFO +0 -15
  87. lme_python-0.2.1/completion_manifest.json +0 -20
  88. lme_python-0.2.1/src/formula.rs +0 -653
  89. lme_python-0.2.1/src/profile_ci.rs +0 -473
  90. lme_python-0.2.1/tests/test_formula.rs +0 -19
  91. lme_python-0.2.1/tests/test_nlmm_agq.rs +0 -37
  92. {lme_python-0.2.1 → lme_python-0.2.2}/.JuliaFormatter.toml +0 -0
  93. {lme_python-0.2.1 → lme_python-0.2.2}/.github/dependabot.yml +0 -0
  94. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/benchmarks.yml +0 -0
  95. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/crate-publish-dry-run.yml +0 -0
  96. {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/repo-metadata.yml +0 -0
  97. {lme_python-0.2.1 → lme_python-0.2.2}/.gitignore +0 -0
  98. {lme_python-0.2.1 → lme_python-0.2.2}/CI_PERFORMANCE.md +0 -0
  99. {lme_python-0.2.1 → lme_python-0.2.2}/LICENSE +0 -0
  100. {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/Apache-2.0.txt +0 -0
  101. {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/BSD-3-Clause-OpenBLAS.txt +0 -0
  102. {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/GPL-2.0-or-later.txt +0 -0
  103. {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/Intel-Simplified-Software-License.txt +0 -0
  104. {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/LGPL-2.1-only.txt +0 -0
  105. {lme_python-0.2.1 → lme_python-0.2.2}/RELINKING.md +0 -0
  106. {lme_python-0.2.1 → lme_python-0.2.2}/THIRD_PARTY_NOTICES.md +0 -0
  107. {lme_python-0.2.1 → lme_python-0.2.2}/benches/bench_load_production.rs +0 -0
  108. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-04.json +0 -0
  109. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-06.json +0 -0
  110. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-08.json +0 -0
  111. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-direct-intercept-gram.json +0 -0
  112. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-full-lmm.json +0 -0
  113. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-glmm.json +0 -0
  114. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-large-intercept-setup.json +0 -0
  115. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-large-slopes-linear-cache.json +0 -0
  116. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-large-slopes.json +0 -0
  117. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-sleepstudy-slopes.json +0 -0
  118. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09.json +0 -0
  119. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-10-cold-setup-followup.json +0 -0
  120. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-16-cold-fit-lt1.json +0 -0
  121. {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-22-full-tier-a.json +0 -0
  122. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_fair_julia_perf.jl +0 -0
  123. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_fair_julia_timing.jl +0 -0
  124. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_fair_rust_julia.rs +0 -0
  125. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_perf_breakdown.rs +0 -0
  126. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.R +0 -0
  127. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.jl +0 -0
  128. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.py +0 -0
  129. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.rs +0 -0
  130. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/comparison_chart.png +0 -0
  131. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.R +0 -0
  132. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.jl +0 -0
  133. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.py +0 -0
  134. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.rs +0 -0
  135. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.R +0 -0
  136. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.jl +0 -0
  137. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.py +0 -0
  138. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.rs +0 -0
  139. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.R +0 -0
  140. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.jl +0 -0
  141. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.py +0 -0
  142. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.rs +0 -0
  143. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.R +0 -0
  144. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.jl +0 -0
  145. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.py +0 -0
  146. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.rs +0 -0
  147. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.R +0 -0
  148. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.jl +0 -0
  149. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.py +0 -0
  150. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.rs +0 -0
  151. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.R +0 -0
  152. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.jl +0 -0
  153. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.py +0 -0
  154. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.rs +0 -0
  155. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.R +0 -0
  156. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.jl +0 -0
  157. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.py +0 -0
  158. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.rs +0 -0
  159. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/nlmm_sspower.R +0 -0
  160. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_offset.R +0 -0
  161. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_offset.jl +0 -0
  162. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_probit.R +0 -0
  163. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_probit.jl +0 -0
  164. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_weighted.R +0 -0
  165. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_weighted.jl +0 -0
  166. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/parity_r_setup.R +0 -0
  167. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/sleepstudy_offset.R +0 -0
  168. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/sleepstudy_offset.jl +0 -0
  169. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity_export.rs +0 -0
  170. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/r_setup.R +0 -0
  171. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.R +0 -0
  172. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.jl +0 -0
  173. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.py +0 -0
  174. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.rs +0 -0
  175. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.R +0 -0
  176. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.jl +0 -0
  177. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.py +0 -0
  178. {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.rs +0 -0
  179. {lme_python-0.2.1 → lme_python-0.2.2}/docs/CALO_CALIBRATION.md +0 -0
  180. {lme_python-0.2.1 → lme_python-0.2.2}/docs/benchmarks/app.js +0 -0
  181. {lme_python-0.2.1 → lme_python-0.2.2}/docs/benchmarks/index.html +0 -0
  182. {lme_python-0.2.1 → lme_python-0.2.2}/docs/benchmarks/styles.css +0 -0
  183. {lme_python-0.2.1 → lme_python-0.2.2}/docs/index.html +0 -0
  184. {lme_python-0.2.1 → lme_python-0.2.2}/examples/batch_sspower_cpu.rs +0 -0
  185. {lme_python-0.2.1 → lme_python-0.2.2}/examples/dump_cascade_fixtures.rs +0 -0
  186. {lme_python-0.2.1 → lme_python-0.2.2}/examples/nlmm_fixture_dump.rs +0 -0
  187. {lme_python-0.2.1 → lme_python-0.2.2}/lefthook.yml +0 -0
  188. {lme_python-0.2.1 → lme_python-0.2.2}/legal/dependency-license-exceptions.json +0 -0
  189. {lme_python-0.2.1 → lme_python-0.2.2}/legal/fixture-provenance.json +0 -0
  190. {lme_python-0.2.1 → lme_python-0.2.2}/mise.toml +0 -0
  191. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/glmer_cbpp.py +0 -0
  192. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/glmer_grouseticks.py +0 -0
  193. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/lm_basics.py +0 -0
  194. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/lmer_sleepstudy.py +0 -0
  195. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/model_comparison.py +0 -0
  196. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/.gitignore +0 -0
  197. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/README.md +0 -0
  198. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/compare_plots.py +0 -0
  199. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/figure_specs.py +0 -0
  200. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/find_rscript.py +0 -0
  201. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/numeric_overlay.py +0 -0
  202. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/paths.py +0 -0
  203. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/plot_demo.py +0 -0
  204. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/plot_r.R +0 -0
  205. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/run_all.py +0 -0
  206. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/README.md +0 -0
  207. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/conftest.py +0 -0
  208. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/paths.py +0 -0
  209. {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/test_parity.py +0 -0
  210. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_basic.py +0 -0
  211. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_cv.py +0 -0
  212. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_dataframe_inputs.py +0 -0
  213. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_golden_parity.py +0 -0
  214. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_nlmer_custom_mean.py +0 -0
  215. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_simulate_scale.py +0 -0
  216. {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_wheel_notices.py +0 -0
  217. {lme_python-0.2.1 → lme_python-0.2.2}/python/uv.lock +0 -0
  218. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/build_benchmark_site.py +0 -0
  219. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/check_completion_score.py +0 -0
  220. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/check_legal_compliance.py +0 -0
  221. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/julia_format.jl +0 -0
  222. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/package_wheel_notices.py +0 -0
  223. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/r_format.R +0 -0
  224. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/run.sh +0 -0
  225. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/dump_dyestuff.R +0 -0
  226. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/dump_pastes.R +0 -0
  227. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/local_ci.ps1 +0 -0
  228. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/local_ci.sh +0 -0
  229. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/make_penicillin_csv.py +0 -0
  230. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/plot_comparisons.py +0 -0
  231. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/run_cross_language_benchmarks.py +0 -0
  232. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/run_fair_rust_julia_benchmark.py +0 -0
  233. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/run_perf_breakdown.py +0 -0
  234. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/sync_github_repo_metadata.py +0 -0
  235. {lme_python-0.2.1 → lme_python-0.2.2}/scripts/verify_cross_language_parity.py +0 -0
  236. {lme_python-0.2.1 → lme_python-0.2.2}/src/anova.rs +0 -0
  237. {lme_python-0.2.1 → lme_python-0.2.2}/src/anova_contrasts.rs +0 -0
  238. {lme_python-0.2.1 → lme_python-0.2.2}/src/cv.rs +0 -0
  239. {lme_python-0.2.1 → lme_python-0.2.2}/src/ddf.rs +0 -0
  240. {lme_python-0.2.1 → lme_python-0.2.2}/src/intercept_blocked.rs +0 -0
  241. {lme_python-0.2.1 → lme_python-0.2.2}/src/kenward_roger.rs +0 -0
  242. {lme_python-0.2.1 → lme_python-0.2.2}/src/kr_modcomp.rs +0 -0
  243. {lme_python-0.2.1 → lme_python-0.2.2}/src/kr_vcov_adj.rs +0 -0
  244. {lme_python-0.2.1 → lme_python-0.2.2}/src/math.rs +0 -0
  245. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/formula.rs +0 -0
  246. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/mean.rs +0 -0
  247. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/mean_fn.rs +0 -0
  248. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/mod.rs +0 -0
  249. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/predict.rs +0 -0
  250. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/re_cov.rs +0 -0
  251. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/self_start.rs +0 -0
  252. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssasymp.rs +0 -0
  253. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssasympoff.rs +0 -0
  254. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssasymporig.rs +0 -0
  255. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssbiexp.rs +0 -0
  256. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssfpl.rs +0 -0
  257. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssgompertz.rs +0 -0
  258. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/sslogis.rs +0 -0
  259. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssmicmen.rs +0 -0
  260. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/sspower.rs +0 -0
  261. {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssweibull.rs +0 -0
  262. {lme_python-0.2.1 → lme_python-0.2.2}/src/perf_diag.rs +0 -0
  263. {lme_python-0.2.1 → lme_python-0.2.2}/src/quadrature.rs +0 -0
  264. {lme_python-0.2.1 → lme_python-0.2.2}/src/simulate.rs +0 -0
  265. {lme_python-0.2.1 → lme_python-0.2.2}/tests/categorical_anova_test.rs +0 -0
  266. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/cbpp_binary.csv +0 -0
  267. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/cbpp_binary_weighted.csv +0 -0
  268. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/dyestuff.csv +0 -0
  269. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/dyestuff_intercept_reml.json +0 -0
  270. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial.json +0 -0
  271. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_agq.json +0 -0
  272. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_cloglog.json +0 -0
  273. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_probit.json +0 -0
  274. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_weighted.json +0 -0
  275. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_poisson.json +0 -0
  276. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_poisson_offset.json +0 -0
  277. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/grouseticks.csv +0 -0
  278. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/intercept_only.json +0 -0
  279. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/mock_crossed.json +0 -0
  280. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/mock_ml.json +0 -0
  281. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/orange.csv +0 -0
  282. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/orange_nlmer.json +0 -0
  283. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/pastes.csv +0 -0
  284. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/pastes_cask_reml.json +0 -0
  285. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/penicillin.csv +0 -0
  286. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/penicillin.json +0 -0
  287. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/random_slopes.json +0 -0
  288. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sleepstudy.csv +0 -0
  289. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sleepstudy_confint_profile.json +0 -0
  290. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sleepstudy_offset_reml.json +0 -0
  291. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasymp_synthetic.csv +0 -0
  292. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasympoff_nlmer.json +0 -0
  293. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasympoff_synthetic.csv +0 -0
  294. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasymporig_nlmer.json +0 -0
  295. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasymporig_synthetic.csv +0 -0
  296. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssbiexp_nlmer.json +0 -0
  297. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssbiexp_synthetic.csv +0 -0
  298. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssfol_nlmer.json +0 -0
  299. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssfpl_nlmer.json +0 -0
  300. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssfpl_synthetic.csv +0 -0
  301. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssgompertz_nlmer.json +0 -0
  302. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssgompertz_synthetic.csv +0 -0
  303. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssmicmen_nlmer.json +0 -0
  304. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssmicmen_synthetic.csv +0 -0
  305. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sspower_nlmer.json +0 -0
  306. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sspower_synthetic.csv +0 -0
  307. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssweibull_nlmer.json +0 -0
  308. {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssweibull_synthetic.csv +0 -0
  309. {lme_python-0.2.1 → lme_python-0.2.2}/tests/gen_nlmm_extras.R +0 -0
  310. {lme_python-0.2.1 → lme_python-0.2.2}/tests/generate_mock_data.py +0 -0
  311. {lme_python-0.2.1 → lme_python-0.2.2}/tests/generate_test_data.R +0 -0
  312. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_anova_types.rs +0 -0
  313. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_conditional_real.rs +0 -0
  314. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_confint_simulate.rs +0 -0
  315. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_contrast.rs +0 -0
  316. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_coverage_edge_cases.rs +0 -0
  317. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_crossed_mock.rs +0 -0
  318. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_cv.rs +0 -0
  319. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_e2e_lmer.rs +0 -0
  320. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_edge_cases.rs +0 -0
  321. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_errors.rs +0 -0
  322. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_failure_modes.rs +0 -0
  323. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_gaps.rs +0 -0
  324. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm_links.rs +0 -0
  325. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm_weighted.rs +0 -0
  326. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_golden_parity.rs +0 -0
  327. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_intercept_only.rs +0 -0
  328. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_kenward_roger.rs +0 -0
  329. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_kr_modcomp_pastes.rs +0 -0
  330. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_ml_optimization.rs +0 -0
  331. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_bounds.rs +0 -0
  332. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_custom_mean.rs +0 -0
  333. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_orange.rs +0 -0
  334. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_orange_multi_re.rs +0 -0
  335. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_self_start.rs +0 -0
  336. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ss_new_means.rs +0 -0
  337. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssasymp.rs +0 -0
  338. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssasymp_off_orig.rs +0 -0
  339. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssfol.rs +0 -0
  340. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssmicmen.rs +0 -0
  341. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_sspower.rs +0 -0
  342. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_no_intercept.rs +0 -0
  343. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_numerical_parity.rs +0 -0
  344. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_predict.rs +0 -0
  345. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_production_load.rs +0 -0
  346. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_random_slopes.rs +0 -0
  347. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_robust.rs +0 -0
  348. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_sandwich_math.R +0 -0
  349. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_satterthwaite.rs +0 -0
  350. {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_statistical_identities.rs +0 -0
@@ -28,7 +28,7 @@ jobs:
28
28
  toolchain: stable
29
29
 
30
30
  - name: Install cargo-audit
31
- uses: taiki-e/install-action@c44f6b046f1c29ae5918b1e0bfdbb2f1813836fd # v2
31
+ uses: taiki-e/install-action@7f4eb899022d8fe70b20c4f3de697aa85c309026 # v2
32
32
  with:
33
33
  tool: cargo-audit@0.22.1
34
34
 
@@ -54,7 +54,7 @@ jobs:
54
54
  if: matrix.os == 'macos-latest'
55
55
  run: echo "OPENBLAS_TARGET=ARMV8" >> "$GITHUB_ENV"
56
56
  - name: Cache Rust dependencies
57
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
57
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
58
58
  with:
59
59
  shared-key: ci-v2-${{ runner.os }}-rust-test
60
60
  cache-workspace-crates: ${{ runner.os != 'Linux' }}
@@ -76,7 +76,7 @@ jobs:
76
76
  - name: Install OpenBLAS
77
77
  run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
78
78
  - name: Cache production-load Rust dependencies
79
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
79
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
80
80
  with:
81
81
  shared-key: ci-v2-${{ runner.os }}-rust-release
82
82
  cache-workspace-crates: true
@@ -105,11 +105,17 @@ jobs:
105
105
  if: matrix.os == 'macos-latest'
106
106
  run: echo "OPENBLAS_TARGET=ARMV8" >> "$GITHUB_ENV"
107
107
  - name: Cache Python Rust dependencies
108
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
108
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
109
109
  with:
110
110
  shared-key: ci-${{ runner.os }}-python
111
111
  workspaces: python -> target
112
112
  - name: Compile Python binding dependencies
113
+ if: matrix.os != 'macos-latest'
113
114
  env:
114
115
  PYO3_PYTHON: python
115
116
  run: cargo build --release --manifest-path python/Cargo.toml
117
+ - name: Compile Python binding dependencies (macOS)
118
+ if: matrix.os == 'macos-latest'
119
+ uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1
120
+ with:
121
+ args: --release --out python/dist --manifest-path python/Cargo.toml
@@ -34,6 +34,8 @@ jobs:
34
34
  python-version: "3.11"
35
35
  - name: Validate legal notices and provenance
36
36
  run: python3 scripts/ci/lme_ci.py legal
37
+ - name: Validate Markdown links
38
+ run: python3 scripts/ci/lme_ci.py docs-links
37
39
  - name: Validate completion score claims
38
40
  run: python3 scripts/ci/lme_ci.py completion-check
39
41
 
@@ -54,7 +56,7 @@ jobs:
54
56
  - name: Install OpenBLAS
55
57
  run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
56
58
  - name: Cache Rust dependencies
57
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
59
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
58
60
  with:
59
61
  shared-key: ci-v2-${{ runner.os }}-rust-test
60
62
  save-if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/master' }}
@@ -111,7 +113,7 @@ jobs:
111
113
  toolchain: stable
112
114
 
113
115
  - name: Cache Rust dependencies
114
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
116
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
115
117
  with:
116
118
  shared-key: ci-v2-${{ runner.os }}-rust-test
117
119
  cache-workspace-crates: true
@@ -139,7 +141,7 @@ jobs:
139
141
  run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
140
142
 
141
143
  - name: Restore Rust dependencies
142
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
144
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
143
145
  with:
144
146
  shared-key: ci-v2-${{ runner.os }}-rust-release
145
147
  cache-workspace-crates: true
@@ -167,7 +169,7 @@ jobs:
167
169
  - name: Install OpenBLAS
168
170
  run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
169
171
  - name: Cache Python Rust dependencies
170
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
172
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
171
173
  with:
172
174
  shared-key: ci-${{ runner.os }}-python
173
175
  workspaces: python -> target
@@ -181,7 +183,7 @@ jobs:
181
183
 
182
184
  - name: Python bindings (canonical editable identity + isolated wheel)
183
185
  if: matrix.python-version == '3.11'
184
- run: python scripts/ci/lme_ci.py python --python-version ${{ matrix.python-version }}
186
+ run: python scripts/ci/lme_ci.py python --python-version ${{ matrix.python-version }} --examples
185
187
 
186
188
  - name: Python bindings (isolated wheel compatibility)
187
189
  if: matrix.python-version != '3.11'
@@ -203,7 +205,7 @@ jobs:
203
205
  if: matrix.os == 'macos-latest'
204
206
  run: echo "OPENBLAS_TARGET=ARMV8" >> "$GITHUB_ENV"
205
207
  - name: Cache Python Rust dependencies
206
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
208
+ uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
207
209
  with:
208
210
  shared-key: ci-${{ runner.os }}-python
209
211
  workspaces: python -> target
@@ -215,7 +217,7 @@ jobs:
215
217
  enable-cache: true
216
218
  cache-dependency-glob: "python/uv.lock"
217
219
  - name: Python bindings (isolated wheel OS compatibility)
218
- run: python scripts/ci/lme_ci.py python --python-version 3.11 --wheel-only
220
+ run: python scripts/ci/lme_ci.py python --python-version 3.11 --wheel-only --examples
219
221
 
220
222
  publish-crate:
221
223
  name: Publish Rust crate after CI
@@ -236,7 +238,7 @@ jobs:
236
238
  secrets: inherit
237
239
 
238
240
  publish-python:
239
- name: Publish Python artifacts after CI
241
+ name: Dispatch Python release after CI
240
242
  if: startsWith(github.ref, 'refs/tags/v')
241
243
  needs:
242
244
  - release-policy
@@ -247,8 +249,19 @@ jobs:
247
249
  - python-bindings-versions
248
250
  - python-bindings-os
249
251
  permissions:
250
- contents: write
251
- id-token: write
252
- uses: ./.github/workflows/python-release.yml
253
- with:
254
- publish: true
252
+ actions: write
253
+ contents: read
254
+ runs-on: ubuntu-latest
255
+ steps:
256
+ - name: Dispatch verified Python release
257
+ env:
258
+ GH_TOKEN: ${{ github.token }}
259
+ RELEASE_SHA: ${{ github.sha }}
260
+ RELEASE_TAG: ${{ github.ref_name }}
261
+ run: |
262
+ gh workflow run python-release.yml \
263
+ --repo "${GITHUB_REPOSITORY}" \
264
+ --ref master \
265
+ -f release_tag="${RELEASE_TAG}" \
266
+ -f release_sha="${RELEASE_SHA}" \
267
+ -f publish=true
@@ -31,7 +31,7 @@ jobs:
31
31
  - name: Install cargo-fuzz
32
32
  run: cargo install cargo-fuzz@0.13.2 --locked
33
33
 
34
- - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
34
+ - uses: Swatinem/rust-cache@a45951ff880207c249adf57334cf2e9bd81d6e1e # v2
35
35
  with:
36
36
  workspaces: fuzz
37
37
 
@@ -1,28 +1,96 @@
1
1
  name: Build and Publish Python Wheels
2
2
 
3
- # CI calls this workflow after every release-tag validation job succeeds.
4
- # workflow_dispatch remains available for non-publishing artifact builds.
3
+ # CI dispatches this top-level workflow after every release-tag validation job
4
+ # succeeds. PyPI Trusted Publishing does not support publishing from a reusable
5
+ # workflow with attestations, so this workflow must retain its own identity.
5
6
  on:
6
- workflow_call:
7
+ workflow_dispatch:
7
8
  inputs:
9
+ release_tag:
10
+ description: Validated v* tag to build; blank builds the selected ref
11
+ required: false
12
+ type: string
13
+ default: ""
14
+ release_sha:
15
+ description: Exact validated commit for a publishing run
16
+ required: false
17
+ type: string
18
+ default: ""
8
19
  publish:
9
- description: Publish the built artifacts for a validated release tag
20
+ description: Publish the verified release tag to PyPI
10
21
  required: false
11
22
  type: boolean
12
23
  default: false
13
- workflow_dispatch:
14
24
 
15
25
  permissions:
16
26
  contents: read
17
27
 
28
+ env:
29
+ RELEASE_REF: ${{ inputs.release_tag || github.ref }}
30
+
18
31
  jobs:
32
+ verify-release:
33
+ name: Verify release target
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
37
+ with:
38
+ fetch-depth: 0
39
+ ref: ${{ env.RELEASE_REF }}
40
+
41
+ - name: Verify validated tag and package versions
42
+ if: inputs.publish
43
+ env:
44
+ RELEASE_SHA: ${{ inputs.release_sha }}
45
+ RELEASE_TAG: ${{ inputs.release_tag }}
46
+ run: |
47
+ set -euo pipefail
48
+
49
+ if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$ ]]; then
50
+ echo "Publishing requires an explicit semantic v* release tag." >&2
51
+ exit 1
52
+ fi
53
+ if [[ ! "${RELEASE_SHA}" =~ ^[0-9a-f]{40}$ ]]; then
54
+ echo "Publishing requires the exact 40-character validated commit SHA." >&2
55
+ exit 1
56
+ fi
57
+
58
+ actual_sha="$(git rev-list -n 1 "refs/tags/${RELEASE_TAG}")"
59
+ if [[ "${actual_sha}" != "${RELEASE_SHA}" ]]; then
60
+ echo "Tag ${RELEASE_TAG} resolves to ${actual_sha}, not validated SHA ${RELEASE_SHA}." >&2
61
+ exit 1
62
+ fi
63
+ if [[ "$(git rev-parse HEAD)" != "${RELEASE_SHA}" ]]; then
64
+ echo "Checked-out release does not match validated SHA ${RELEASE_SHA}." >&2
65
+ exit 1
66
+ fi
67
+
68
+ python - "${RELEASE_TAG#v}" <<'PY'
69
+ import pathlib
70
+ import sys
71
+ import tomllib
72
+
73
+ expected = sys.argv[1]
74
+ manifests = (pathlib.Path("Cargo.toml"), pathlib.Path("python/Cargo.toml"))
75
+ for manifest in manifests:
76
+ with manifest.open("rb") as stream:
77
+ actual = tomllib.load(stream)["package"]["version"]
78
+ if actual != expected:
79
+ raise SystemExit(
80
+ f"{manifest}: package version {actual!r} does not match tag {expected!r}"
81
+ )
82
+ PY
83
+
19
84
  linux:
85
+ needs: verify-release
20
86
  runs-on: ubuntu-latest
21
87
  strategy:
22
88
  matrix:
23
89
  target: [x86_64, aarch64]
24
90
  steps:
25
91
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
92
+ with:
93
+ ref: ${{ env.RELEASE_REF }}
26
94
  - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
27
95
  with:
28
96
  python-version: '3.10'
@@ -59,12 +127,15 @@ jobs:
59
127
  path: dist
60
128
 
61
129
  windows:
130
+ needs: verify-release
62
131
  runs-on: windows-latest
63
132
  strategy:
64
133
  matrix:
65
134
  target: [x64]
66
135
  steps:
67
136
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
137
+ with:
138
+ ref: ${{ env.RELEASE_REF }}
68
139
  - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
69
140
  with:
70
141
  python-version: '3.10'
@@ -89,12 +160,15 @@ jobs:
89
160
  path: dist
90
161
 
91
162
  macos:
163
+ needs: verify-release
92
164
  runs-on: macos-latest
93
165
  strategy:
94
166
  matrix:
95
167
  target: [x86_64, aarch64]
96
168
  steps:
97
169
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
170
+ with:
171
+ ref: ${{ env.RELEASE_REF }}
98
172
  - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
99
173
  with:
100
174
  python-version: '3.10'
@@ -124,9 +198,12 @@ jobs:
124
198
  path: dist
125
199
 
126
200
  sdist:
201
+ needs: verify-release
127
202
  runs-on: ubuntu-latest
128
203
  steps:
129
204
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
205
+ with:
206
+ ref: ${{ env.RELEASE_REF }}
130
207
  - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
131
208
  with:
132
209
  toolchain: stable
@@ -147,14 +224,16 @@ jobs:
147
224
  name: Publish to PyPI
148
225
  needs: [linux, windows, macos, sdist]
149
226
  runs-on: ubuntu-latest
150
- # Only CI's validated release-tag call can enable publication.
151
- if: "inputs.publish && startsWith(github.ref, 'refs/tags/v')"
227
+ # Publication requires the explicit opt-in and exact tag/SHA verification above.
228
+ if: inputs.publish
152
229
  environment: pypi
153
230
  permissions:
154
231
  id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
155
232
  contents: write # needed to create GitHub Release
156
233
  steps:
157
234
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
235
+ with:
236
+ ref: ${{ env.RELEASE_REF }}
158
237
 
159
238
  - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
160
239
  with:
@@ -174,9 +253,10 @@ jobs:
174
253
  done
175
254
 
176
255
  - name: Publish to PyPI
177
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
256
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
178
257
 
179
258
  - name: Upload to GitHub Release
180
259
  uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
181
260
  with:
182
261
  files: dist/*
262
+ tag_name: ${{ inputs.release_tag }}
@@ -10,6 +10,7 @@ Use the smallest validation tier that proves the change is sound, then run the e
10
10
  | Python bindings | `task lint:python`, `task python` |
11
11
  | CI, manifests, release tooling | `task preflight`; use `task ci` before a release or broad refactor |
12
12
  | R / Julia comparison scripts | `task lint:comparisons`; use `task lint:comparisons:required` when the formatters are installed |
13
+ | Documentation or portable examples | `task docs:check`; use `task consumer:smoke` when install or example behavior changes |
13
14
  | LMM throughput paths (`src/math.rs`, `src/optimizer.rs`) | Read [OPTIMIZATION.md](OPTIMIZATION.md) and run the applicable fair-harness cases |
14
15
  | Completion score files: `README.md`, `REPO_COMPLETION_BY_AREA.md`, `completion_manifest.json`, or `scripts/ci/check_completion_score.py` | `task completion:check` |
15
16
 
@@ -70,11 +71,11 @@ Use `--no-verify` only when explicitly necessary; report the bypass and the chec
70
71
 
71
72
  ## CI and release boundaries
72
73
 
73
- GitHub Actions validation runs automatically on pull requests and `v*` tags, and can be manually dispatched. Ordinary non-PR branch pushes do not receive the hosted validation matrix automatically. A lightweight cache-prime workflow runs when Rust dependency inputs change on `master` and weekly so new PRs can reuse trusted dependency artifacts. Pull requests run the full matrix except the four ignored heavy production-load cases. The tag CI calls the crates.io and PyPI workflows only after every validation job succeeds; the publishing workflows do not listen to tags independently.
74
+ GitHub Actions validation runs automatically on pull requests and `v*` tags, and can be manually dispatched. Ordinary non-PR branch pushes do not receive the hosted validation matrix automatically. A lightweight cache-prime workflow runs when Rust dependency inputs change on `master` and weekly so new PRs can reuse trusted dependency artifacts. Pull requests run the full matrix except the four ignored heavy production-load cases. The tag CI calls the crates.io workflow and dispatches the top-level PyPI workflow only after every validation job succeeds; the publishing workflows do not listen to tags independently. The PyPI workflow must remain top-level because Trusted Publishing attestations do not support a reusable publishing workflow.
74
75
 
75
76
  External GitHub Actions are pinned to full commit SHAs, with the readable release line retained as a comment. Dependabot proposes grouped weekly pin updates; do not replace SHA pins with mutable tags. Dependency audits and libFuzzer smoke tests run weekly in addition to their release/manual entry points.
76
77
 
77
- - `task ci` mirrors the core hosted flow: Rust tests, Python bindings, lint, all-targets check, legal checks, doctests, docs, and the completion-score check.
78
+ - `task ci` mirrors the core hosted flow: Rust tests, Python bindings, portable consumer examples, lint, all-targets check, legal checks, documentation/link verification, and the completion-score check.
78
79
  - Hosted-only coverage includes the multi-OS matrix, Python 3.10–3.13, production-load gates, and `pip-audit`.
79
80
  - After changing BLAS target tables or release workflows, run `task ci` locally or manually dispatch CI before tagging; macOS Apple Silicon BLAS is not exercised on Windows/Linux.
80
81
  - Benchmark workflow coverage requiring R or Julia belongs in the tag/manual workflow. `task benchmarks:preflight` runs the Rust smoke plus the R smoke when R/lme4 is available.
@@ -93,8 +94,19 @@ For repository-metadata token issues, set `REPO_ADMIN_TOKEN` locally and run `ta
93
94
  | `task ci` / `task ci:fast` | Core CI mirror / reuse the editable Python environment and skip the isolated-wheel pass |
94
95
  | `task audit` / `task legal` | Security audit / provenance and license checks |
95
96
  | `task completion:check` | Verify the manifest-derived completion score and published markers |
97
+ | `task docs:check` / `task consumer:smoke` | Validate docs/links/API examples / run clean-install Rust and Python workflows |
96
98
  | `task benchmarks:fair-rust-julia` | Fair fit-only Rust vs MixedModels.jl timing when Julia packages are installed |
97
99
  | `task benchmarks:perf-breakdown` | Rust phase timings against Julia optimizer evaluation counts |
100
+ | `task benchmarks:external-timings` | Fair-ish `nlmer`, post-fit inference, and Python FFI timings vs R when available |
101
+ | `task explorations` | Standalone native-parser AST, θ-grid, and MCP exploration examples |
98
102
  | `task lint:comparisons` | Optional R/Julia comparison formatting check |
99
103
 
100
104
  Run `python scripts/ci/lme_ci.py --help` for the complete command list. See [CONTRIBUTING.md](CONTRIBUTING.md) for contributor workflow and [RELEASING.md](RELEASING.md) for release steps.
105
+
106
+ ## Cursor Cloud specific instructions
107
+
108
+ This repo is a pure Rust library (`lme-rs`) plus a PyO3/maturin Python binding (`lme_python`); there are no servers or long-running services. "Running the app" means building the crate and running examples/tests. See the command reference above for the canonical `task` aliases.
109
+
110
+ - Toolchain comes from `mise` (per [mise.toml](mise.toml): Rust stable, Python 3.11, `uv`, `task`, `lefthook`). The startup update script runs `mise install`. Interactive shells auto-activate mise via `~/.bashrc`. In a non-interactive shell where `mise`/`task`/`uv` aren't on `PATH`, run `eval "$($HOME/.local/bin/mise activate bash --shims)"` first (or invoke tools with `$HOME/.local/bin/mise exec -- <cmd>`).
111
+ - On x86_64 the core crate links Intel MKL statically via `ndarray-linalg`, so no `gfortran`/system BLAS is needed; the first `cargo build`/`task test:fast` compiles MKL and takes a few minutes (subsequent builds are cached). Examples must be run with `--release` (e.g. `cargo run --release --example sleepstudy`) — a debug build is very slow.
112
+ - Python bindings gotcha: `uv sync` / `task ci` / `task python` reinstall the venv and **uninstall** the maturin-built `lme_python` before rebuilding it. If you run a bare `uv sync` (or interrupt `task python` before its maturin step), `import lme_python` breaks; restore it with `cd python && uv run --no-sync maturin develop --release` (or re-run `task python`). Run Python examples from `python/` via `uv run --no-sync python examples/<name>.py`.
@@ -436,7 +436,7 @@ The repository also includes a dedicated workflow in [.github/workflows/benchmar
436
436
 
437
437
  ## Latest published results
438
438
 
439
- Release-attached Criterion and cross-language JSON artifacts are uploaded by [`.github/workflows/benchmarks.yml`](../.github/workflows/benchmarks.yml) on `v*` tags (and manual dispatch). Prefer the [latest GitHub Release](https://github.com/x4g4p3x/lme-rs/releases/latest) for downloadable CI artifacts, and [`CHANGELOG.md`](CHANGELOG.md) for which crate version each tag ships.
439
+ Release-attached Criterion and cross-language JSON artifacts are uploaded by [`.github/workflows/benchmarks.yml`](.github/workflows/benchmarks.yml) on `v*` tags (and manual dispatch). Prefer the [latest GitHub Release](https://github.com/x4g4p3x/lme-rs/releases/latest) for downloadable CI artifacts, and [`CHANGELOG.md`](CHANGELOG.md) for which crate version each tag ships.
440
440
 
441
441
  **Engineering source of truth for fair Rust vs Julia fit timing** is the checked-in reference JSON under [`benchmarks/`](benchmarks/), especially the full strict-target [2026-07-22 tier-A artifact](benchmarks/fair-rust-julia-reference-2026-07-22-full-tier-a.json). Older tags (for example [v0.1.3](https://github.com/x4g4p3x/lme-rs/releases/tag/v0.1.3)) still carry historical CI tarballs useful for release-to-release comparison, but they are **not** the current performance baseline.
442
442
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  This file maps **which parts of `lme-rs` have external performance references** (not just Rust-only Criterion benches). Use it to ground [REPO_COMPLETION_BY_AREA.md](REPO_COMPLETION_BY_AREA.md) axis (3) and [USABILITY.md](USABILITY.md) performance posture.
4
4
 
5
- **Last assessed:** 2026-07-22
5
+ **Last assessed:** 2026-08-14
6
6
 
7
7
  ---
8
8
 
@@ -50,16 +50,19 @@ Hot-path target (batch / CV): **`fit_prepared` ≤ ~1× Julia `fit`** when `--wi
50
50
  | `cbpp_binomial_ml` | GLMM | Real binomial | MixedModels.jl GLMM | **0.749×** | N/A | Laplace; not R `nAGQ`-in-θ |
51
51
  | `grouseticks_poisson_ml` | GLMM | Real Poisson | MixedModels.jl GLMM | **0.033×** | N/A | Laplace |
52
52
 
53
- Cases not in tier A (no fair external fit timing yet):
53
+ Cases not in tier A (no fair MixedModels.jl fit timing yet):
54
54
 
55
55
  | Area | Rust bench | Comparable | Status |
56
56
  |:-----|:-----------|:-----------|:-------|
57
- | `nlmer` / Orange | Golden parity only | `lme4::nlmer` | **No** fair harness |
57
+ | `nlmer` / Orange | Golden parity + [`scripts/run_external_timings.py`](scripts/run_external_timings.py) | `lme4::nlmer` | **External R timing** (not Julia) |
58
58
  | GLMM AGQ (`n_agq > 1`) | Criterion | R semantics differ | **No** apples-to-apples |
59
- | Post-fit inference (KR, ANOVA, predict) | Criterion | `lmerTest` / MixedModels | **No** external timing |
60
- | Python `lme_python` FFI | None | N/A | **No** |
59
+ | Post-fit inference (KR, ANOVA, predict) | Criterion + external harness | `lmerTest` when installed | **External R timing** for KR / Satterthwaite ANOVA |
60
+ | LMM estimated marginal means / pairs | Criterion (`inference/emmeans_*`) | R `emmeans` correctness fixture | **Rust microbenchmark**; no cross-language speed claim |
61
+ | Python `lme_python` FFI | External harness | Rust `lmer` | **External Python timing** |
61
62
  | `lm` / `lm_df` | Minimal | R `lm` | **No** (usually negligible) |
62
63
 
64
+ The 2026-08-14 Windows Criterion run measured `inference/emmeans_reference_grid` at **42.5–43.1 µs** and `inference/emmeans_pairs_tukey` at **52.9–56.8 µs** on the pastes fixture. These are post-fit operations and do not warrant a specialized cache at the current scale.
65
+
63
66
  ---
64
67
 
65
68
  ## What each completion row may claim
@@ -101,6 +104,19 @@ using Pkg; Pkg.add("GLM")
101
104
 
102
105
  ---
103
106
 
107
+ ### External nlmer / inference / Python timings
108
+
109
+ Julia is not required. Times Rust and R `nlmer`, Rust Kenward–Roger / Satterthwaite ANOVA (and `lmerTest` when installed), and `lme_python.lmer` FFI overhead:
110
+
111
+ ```powershell
112
+ python scripts/run_external_timings.py --repeats 5 --output benchmarks/external-nlmm-inference-python-timings.json
113
+ task benchmarks:external-timings
114
+ ```
115
+
116
+ A dated reference JSON lives under [`benchmarks/`](benchmarks/). Use `--rust-only` for a smoke that skips R and Python.
117
+
118
+ ---
119
+
104
120
  ## Maintenance
105
121
 
106
122
  1. After optimization work on a workflow, add or refresh its tier-A case.
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-08-14
11
+
12
+ ### Added
13
+
14
+ - **Estimated marginal means (`emmeans`)** — [`LmeFit::emmeans`](src/emmeans.rs) builds equal-weight categorical reference grids for linear models/LMMs, holding numeric columns at their means; [`LmeFit::emmeans_pairs`](src/emmeans.rs) adds none / Bonferroni / Holm / Tukey–Kramer pairwise comparisons. Rust and Python APIs support asymptotic `z` or stored Satterthwaite / Kenward–Roger dfs. Pastes `cask` estimates, SEs, confidence limits, and Tukey pairs are locked directly to R `emmeans` in [`tests/data/pastes_emmeans.json`](tests/data/pastes_emmeans.json).
15
+ - **Tukey / Dunnett multiple comparisons (`glht`)** — [`LmeFit::glht`](src/mcp.rs) implements `multcomp::mcp` pairwise (Tukey) and vs-control (Dunnett) Wald tests with Bonferroni, Holm, or Tukey–Kramer (`stats::ptukey`) adjustment. Default is asymptotic `z`; Satterthwaite / Kenward–Roger `t` is optional. Pastes `cask` fixture: [`tests/data/pastes_glht_tukey.json`](tests/data/pastes_glht_tukey.json).
16
+ - **R formula edge-case fit matrix** — eight lme4 goldens covering independent `||` slopes, `I(Days^2)`, `sqrt(Days)`, raw/orthogonal `poly()`, `ns()`, cell-means `0 + cask`, and nested `batch/cask` ([`tests/data/r_edge_case_matrix.json`](tests/data/r_edge_case_matrix.json), [`tests/test_r_edge_case_matrix.rs`](tests/test_r_edge_case_matrix.rs)).
17
+ - **External `nlmer` / inference / Python FFI timings** — [`scripts/run_external_timings.py`](scripts/run_external_timings.py) times Rust and R `nlmer`, Kenward–Roger / Satterthwaite ANOVA, and `lme_python.lmer` FFI overhead (Julia not required).
18
+ - **Native-parser explorations** — standalone examples [`explore_formula_ast`](examples/explore_formula_ast.rs), [`explore_theta_grid`](examples/explore_theta_grid.rs), and [`explore_mcp_adjust`](examples/explore_mcp_adjust.rs), tested in [`tests/test_explorations.rs`](tests/test_explorations.rs) and run by `task explorations`.
19
+ - **Profile-likelihood CIs for variance components** — [`confint_profile_vc`](src/profile_ci.rs) / [`confint_profile_all`](src/profile_ci.rs) match lme4 `confint.merMod(..., method = "profile")` on the `oldNames=TRUE` scale: `.sig01` is the RE SD (`θ·σ`) for a scalar random intercept and `.sigma` is the residual SD. [`BootLmerResult::confint_percentile_vc`](src/bootstrap.rs) summarizes the same parameters from bootstrap draws. Sleepstudy ML fixture: [`tests/data/sleepstudy_confint_profile_vc.json`](tests/data/sleepstudy_confint_profile_vc.json).
20
+ - Formula materialization for two-way colon interactions (`a:b`), unary column transforms (`log`, `sqrt`, `exp`), `I()` arithmetic, and `offset(log(x))`-style transformed offsets. These terms are now built into the design matrix instead of being rejected at parse time.
21
+ - Formula `poly()`, `ns()`, and `y ~ .` materialization. Orthogonal or raw `poly(x, degree)` and natural cubic `ns(x, df)` become design-matrix columns; training encodings are reused by `predict`. `.` expands to remaining DataFrame columns except the response and names already in the formula.
22
+ - Multivariate **AGQ-in-θ** for GLMMs and `nlmer`: when `n_agq ≥ 2`, the outer θ search uses adaptive Gauss–Hermite quadrature for vector random effects (product rule per group) and for multiple RE terms when total `q` is small enough. Scalar `k = 1` still matches `lme4`; vector/joint AGQ-in-θ goes beyond lme4 (which stays Laplace for multivariate RE). Large crossed models remain Laplace-in-θ.
23
+ - Python `lm(y, x)` for numeric OLS (same calling shape as Rust `lm(y, x)`). `lm(formula, data)` is unchanged; `lm_matrix` remains an alias.
24
+
25
+ ### Changed
26
+
27
+ - [`FormulaModel::offset`](src/formula.rs) is now `Option<NumericExpr>` so transformed offsets can be represented. Plain column offsets still work; use [`FormulaModel::offset_column`](src/formula.rs) when you need the old string form.
28
+ - Tag CI dispatches the top-level PyPI Trusted Publishing workflow only after every validation job succeeds; `task ci` now includes consumer-smoke and documentation/link gates.
29
+
30
+ ### Fixed
31
+
32
+ - Non-numeric `offset()` columns (and other numeric formula terms) are rejected again when values cannot be parsed as floats. Formula materialization had dropped invalid entries instead of erroring, which let string offsets fit.
33
+ - Gamma GLMM random-effect θ no longer collapses to the bound: PIRLS profiles residual φ (IRLS weights `1/φ`, unscaled `u'u` penalty) and scalar θ uses a log-grid plus golden-section search. `gamma_dyestuff_log_laplace` now locks Batch θ against lme4 VarCorr Std.Dev.
34
+
10
35
  ## [0.2.1] - 2026-08-10
11
36
 
12
37
  ### Changed
@@ -60,6 +60,9 @@ task test:fast # cargo test --lib only (quick unit tests)
60
60
  task test # full Rust test suite
61
61
  task preflight # pre-push hook: lint + check + cargo audit + repo-metadata dry-run
62
62
  task audit # cargo audit + pip-audit (GHA security audit mirror)
63
+ task docs:check # local Markdown links + Rust examples/doctests + generated docs
64
+ task consumer:smoke # Rust quick-start + clean-wheel Python examples
65
+ task explorations # native-parser AST / θ-grid / MCP probes
63
66
  task rust # full Rust slice (no Python)
64
67
  task # full core CI mirror
65
68
  task ci:fast # reuse python/.venv, skip isolated-wheel pytest
@@ -105,7 +108,7 @@ uv lock
105
108
 
106
109
  Use `uv run --no-sync pytest tests/` after the explicit `uv sync` so only [`python/tests/`](python/tests/) runs against the extension Maturin just installed; `pytest` alone also collects optional demos under `python/examples/`.
107
110
 
108
- [`task python`](Taskfile.yml) / [`scripts/ci/lme_ci.py python`](scripts/ci/lme_ci.py) assert the editable extension's version and environment path, then build, install, assert, and test the wheel in a separate locked environment. Pull-request/tag CI runs that full identity flow on Python **3.11**, tests isolated wheels on Python **3.10–3.13** on Ubuntu, and tests isolated Python 3.11 wheels on Windows and macOS (jobs `python-bindings-versions` and `python-bindings-os` in [`.github/workflows/ci.yml`](.github/workflows/ci.yml)). If you use **CPython 3.14** locally, set `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` before `maturin develop` (see [python/PYTHON_GUIDE.md](python/PYTHON_GUIDE.md)).
111
+ [`task python`](Taskfile.yml) / [`scripts/ci/lme_ci.py python`](scripts/ci/lme_ci.py) assert the editable extension's version and environment path, then build, install, assert, and test the wheel in a separate locked environment. `task consumer:smoke` adds a dependency-only clean environment and runs the portable examples against the installed wheel. Pull-request/tag CI runs the full identity and consumer-example flow on Python **3.11**, tests isolated wheels on Python **3.10–3.13** on Ubuntu, and runs the consumer examples against isolated Python 3.11 wheels on Windows and macOS (jobs `python-bindings-versions` and `python-bindings-os` in [`.github/workflows/ci.yml`](.github/workflows/ci.yml)). If you use **CPython 3.14** locally, set `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1` before `maturin develop` (see [python/PYTHON_GUIDE.md](python/PYTHON_GUIDE.md)).
109
112
 
110
113
  ## Working on numerical changes
111
114
 
@@ -163,7 +166,7 @@ Do not describe a feature as supported unless it is exposed by the public API an
163
166
  - [`.github/workflows/audit.yml`](.github/workflows/audit.yml) — a required release-CI gate running `cargo audit` on the root and `python/` Rust crates plus `pip-audit` on the [`python/uv.lock`](python/uv.lock) dev environment; it also runs weekly and supports manual dispatch.
164
167
  - [`.github/workflows/fuzz-smoke.yml`](.github/workflows/fuzz-smoke.yml) — weekly and manually dispatched libFuzzer coverage for formula parsing and the formula-to-matrix pipeline.
165
168
  - [`.github/workflows/crate-publish-dry-run.yml`](.github/workflows/crate-publish-dry-run.yml) — called by release CI to publish only after the full tag matrix succeeds; manual dispatch runs `cargo publish --dry-run --locked` only.
166
- - [`.github/workflows/python-release.yml`](.github/workflows/python-release.yml) — called by release CI to build and publish only after the full tag matrix succeeds; manual dispatch builds artifacts without publishing.
169
+ - [`.github/workflows/python-release.yml`](.github/workflows/python-release.yml) — dispatched as a top-level workflow by release CI to verify the tag/SHA, build, and publish only after the full tag matrix succeeds; ordinary manual dispatch builds artifacts without publishing.
167
170
 
168
171
  Pull requests automatically run CI. Ordinary non-PR branch pushes do not start workflows; use Lefthook and Task locally before pushing, and use manual dispatch when a remote check is useful before tagging.
169
172
 
@@ -1508,7 +1508,7 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
1508
1508
 
1509
1509
  [[package]]
1510
1510
  name = "lme-rs"
1511
- version = "0.2.1"
1511
+ version = "0.2.2"
1512
1512
  dependencies = [
1513
1513
  "ahash",
1514
1514
  "anyhow",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "lme-rs"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  edition = "2021"
5
5
  authors = ["Martin Huck <x4g4p3x@users.noreply.github.com>"]
6
6
  description = "Rust port of R's lme4: linear, generalized, and nonlinear mixed-effects models"