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.
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/audit.yml +1 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/cache-prime.yml +9 -3
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/ci.yml +26 -13
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/fuzz-smoke.yml +1 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/python-release.yml +88 -8
- {lme_python-0.2.1 → lme_python-0.2.2}/AGENTS.md +14 -2
- {lme_python-0.2.1 → lme_python-0.2.2}/BENCHMARKS.md +1 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/BENCHMARK_COVERAGE.md +21 -5
- {lme_python-0.2.1 → lme_python-0.2.2}/CHANGELOG.md +25 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/CONTRIBUTING.md +5 -2
- {lme_python-0.2.1 → lme_python-0.2.2}/Cargo.lock +1 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/Cargo.toml +1 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/GUIDE.md +38 -10
- {lme_python-0.2.1 → lme_python-0.2.2}/OPTIMIZATION.md +2 -2
- lme_python-0.2.2/PKG-INFO +67 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/README.md +7 -7
- {lme_python-0.2.1 → lme_python-0.2.2}/RELEASING.md +20 -7
- {lme_python-0.2.1 → lme_python-0.2.2}/REPO_COMPLETION_BY_AREA.md +16 -16
- {lme_python-0.2.1 → lme_python-0.2.2}/Taskfile.yml +20 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/USABILITY.md +6 -6
- {lme_python-0.2.1 → lme_python-0.2.2}/benches/bench_math.rs +21 -0
- lme_python-0.2.2/benchmarks/external-nlmm-inference-python-timings-2026-08-14.json +198 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/COMPARISONS.md +9 -4
- lme_python-0.2.2/comparisons/bench_external_timings.R +138 -0
- lme_python-0.2.2/comparisons/pastes_emmeans.R +48 -0
- lme_python-0.2.2/comparisons/pastes_glht.R +36 -0
- lme_python-0.2.2/comparisons/sleepstudy_confint_profile.R +16 -0
- lme_python-0.2.2/completion_manifest.json +20 -0
- lme_python-0.2.2/examples/bench_external_timings.rs +262 -0
- lme_python-0.2.2/examples/explore_formula_ast.rs +137 -0
- lme_python-0.2.2/examples/explore_mcp_adjust.rs +78 -0
- lme_python-0.2.2/examples/explore_theta_grid.rs +76 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/pyproject.toml +8 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/Cargo.lock +2 -2
- {lme_python-0.2.1 → lme_python-0.2.2}/python/Cargo.toml +2 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/python/PYTHON_GUIDE.md +40 -10
- lme_python-0.2.2/python/README.md +44 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/parity.py +5 -3
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/run.py +1 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/python/lme_python.pyi +66 -2
- {lme_python-0.2.1 → lme_python-0.2.2}/python/src/lib.rs +592 -175
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_api_parity.py +59 -9
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/README.md +5 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/lme_ci.py +208 -3
- lme_python-0.2.2/scripts/explorations/README.md +20 -0
- lme_python-0.2.2/scripts/run_external_timings.py +236 -0
- lme_python-0.2.2/src/basis.rs +529 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/bootstrap.rs +143 -18
- {lme_python-0.2.1 → lme_python-0.2.2}/src/contrast.rs +16 -12
- lme_python-0.2.2/src/emmeans.rs +459 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/family.rs +65 -0
- lme_python-0.2.2/src/formula.rs +1360 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/glmm_math.rs +80 -11
- {lme_python-0.2.1 → lme_python-0.2.2}/src/lib.rs +48 -28
- lme_python-0.2.2/src/mcp.rs +350 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/model_matrix.rs +532 -176
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/fit.rs +124 -7
- {lme_python-0.2.1 → lme_python-0.2.2}/src/optimizer.rs +139 -15
- lme_python-0.2.2/src/profile_ci.rs +1118 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/robust.rs +2 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/src/satterthwaite.rs +1 -0
- lme_python-0.2.2/src/studentized_range.rs +224 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/ci_consolidated.rs +10 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_gamma_dyestuff.json +4 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/golden_parity_manifest.json +8 -1
- lme_python-0.2.2/tests/data/pastes_emmeans.json +15 -0
- lme_python-0.2.2/tests/data/pastes_glht_tukey.json +18 -0
- lme_python-0.2.2/tests/data/r_edge_case_matrix.json +432 -0
- lme_python-0.2.2/tests/data/sleepstudy_confint_profile_vc.json +12 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/generate_nlmm_fixtures.R +17 -6
- lme_python-0.2.2/tests/generate_r_edge_case_matrix.R +211 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_anova.rs +1 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_bootstrap.rs +15 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_confint_profile.rs +80 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_coverage_gaps.rs +1 -0
- lme_python-0.2.2/tests/test_emmeans.rs +166 -0
- lme_python-0.2.2/tests/test_explorations.rs +159 -0
- lme_python-0.2.2/tests/test_external_timings_artifact.rs +72 -0
- lme_python-0.2.2/tests/test_formula.rs +94 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_formula_stress.rs +170 -11
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm.rs +86 -1
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm_offset_grouseticks.rs +11 -1
- lme_python-0.2.2/tests/test_mcp.rs +182 -0
- lme_python-0.2.2/tests/test_nlmm_agq.rs +75 -0
- lme_python-0.2.2/tests/test_r_edge_case_matrix.rs +247 -0
- lme_python-0.2.1/PKG-INFO +0 -15
- lme_python-0.2.1/completion_manifest.json +0 -20
- lme_python-0.2.1/src/formula.rs +0 -653
- lme_python-0.2.1/src/profile_ci.rs +0 -473
- lme_python-0.2.1/tests/test_formula.rs +0 -19
- lme_python-0.2.1/tests/test_nlmm_agq.rs +0 -37
- {lme_python-0.2.1 → lme_python-0.2.2}/.JuliaFormatter.toml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/dependabot.yml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/benchmarks.yml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/crate-publish-dry-run.yml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/.github/workflows/repo-metadata.yml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/.gitignore +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/CI_PERFORMANCE.md +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/LICENSE +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/Apache-2.0.txt +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/BSD-3-Clause-OpenBLAS.txt +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/GPL-2.0-or-later.txt +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/Intel-Simplified-Software-License.txt +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/LICENSES/LGPL-2.1-only.txt +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/RELINKING.md +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/THIRD_PARTY_NOTICES.md +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benches/bench_load_production.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-04.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-06.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-08.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-direct-intercept-gram.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-full-lmm.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-glmm.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-large-intercept-setup.json +0 -0
- {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
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-large-slopes.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09-sleepstudy-slopes.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-09.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-10-cold-setup-followup.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-16-cold-fit-lt1.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/benchmarks/fair-rust-julia-reference-2026-07-22-full-tier-a.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_fair_julia_perf.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_fair_julia_timing.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_fair_rust_julia.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/bench_perf_breakdown.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/categorical_anova.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/comparison_chart.png +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_cbpp.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_gaussian.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/glmm_grouseticks.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmer_weighted.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_dyestuff.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_pastes.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/lmm_penicillin.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/nlmm_sspower.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_offset.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_offset.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_probit.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_probit.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_weighted.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/glmm_weighted.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/parity_r_setup.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/sleepstudy_offset.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity/sleepstudy_offset.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/parity_export.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/r_setup.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/comparisons/sleepstudy_ml.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/docs/CALO_CALIBRATION.md +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/docs/benchmarks/app.js +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/docs/benchmarks/index.html +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/docs/benchmarks/styles.css +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/docs/index.html +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/examples/batch_sspower_cpu.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/examples/dump_cascade_fixtures.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/examples/nlmm_fixture_dump.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/lefthook.yml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/legal/dependency-license-exceptions.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/legal/fixture-provenance.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/mise.toml +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/glmer_cbpp.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/glmer_grouseticks.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/lm_basics.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/lmer_sleepstudy.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/model_comparison.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/.gitignore +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/README.md +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/compare_plots.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/figure_specs.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/find_rscript.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/numeric_overlay.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/paths.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/plot_demo.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/plot_r.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/plotting_demo/run_all.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/README.md +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/conftest.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/paths.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/examples/verification_project/test_parity.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_basic.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_cv.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_dataframe_inputs.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_golden_parity.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_nlmer_custom_mean.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_simulate_scale.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/tests/test_wheel_notices.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/python/uv.lock +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/build_benchmark_site.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/check_completion_score.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/check_legal_compliance.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/julia_format.jl +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/package_wheel_notices.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/r_format.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/ci/run.sh +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/dump_dyestuff.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/dump_pastes.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/local_ci.ps1 +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/local_ci.sh +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/make_penicillin_csv.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/plot_comparisons.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/run_cross_language_benchmarks.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/run_fair_rust_julia_benchmark.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/run_perf_breakdown.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/sync_github_repo_metadata.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/scripts/verify_cross_language_parity.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/anova.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/anova_contrasts.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/cv.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/ddf.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/intercept_blocked.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/kenward_roger.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/kr_modcomp.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/kr_vcov_adj.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/math.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/formula.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/mean.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/mean_fn.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/mod.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/predict.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/re_cov.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/self_start.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssasymp.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssasympoff.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssasymporig.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssbiexp.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssfpl.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssgompertz.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/sslogis.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssmicmen.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/sspower.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/nlmm/ssweibull.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/perf_diag.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/quadrature.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/src/simulate.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/categorical_anova_test.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/cbpp_binary.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/cbpp_binary_weighted.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/dyestuff.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/dyestuff_intercept_reml.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_agq.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_cloglog.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_probit.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_binomial_weighted.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_poisson.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/glmm_poisson_offset.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/grouseticks.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/intercept_only.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/mock_crossed.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/mock_ml.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/orange.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/orange_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/pastes.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/pastes_cask_reml.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/penicillin.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/penicillin.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/random_slopes.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sleepstudy.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sleepstudy_confint_profile.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sleepstudy_offset_reml.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasymp_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasympoff_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasympoff_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasymporig_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssasymporig_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssbiexp_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssbiexp_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssfol_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssfpl_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssfpl_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssgompertz_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssgompertz_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssmicmen_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssmicmen_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sspower_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/sspower_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssweibull_nlmer.json +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/data/ssweibull_synthetic.csv +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/gen_nlmm_extras.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/generate_mock_data.py +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/generate_test_data.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_anova_types.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_conditional_real.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_confint_simulate.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_contrast.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_coverage_edge_cases.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_crossed_mock.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_cv.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_e2e_lmer.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_edge_cases.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_errors.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_failure_modes.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_gaps.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm_links.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_glmm_weighted.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_golden_parity.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_intercept_only.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_kenward_roger.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_kr_modcomp_pastes.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_ml_optimization.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_bounds.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_custom_mean.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_orange.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_orange_multi_re.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_self_start.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ss_new_means.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssasymp.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssasymp_off_orig.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssfol.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_ssmicmen.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_nlmm_sspower.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_no_intercept.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_numerical_parity.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_predict.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_production_load.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_random_slopes.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_robust.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_sandwich_math.R +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_satterthwaite.rs +0 -0
- {lme_python-0.2.1 → lme_python-0.2.2}/tests/test_statistical_identities.rs +0 -0
|
@@ -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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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:
|
|
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
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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@
|
|
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
|
|
4
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
151
|
-
if:
|
|
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@
|
|
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
|
|
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,
|
|
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`](
|
|
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-
|
|
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
|
|
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
|
|
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`
|
|
60
|
-
|
|
|
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
|
|
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) —
|
|
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
|
|