lme-python 0.1.7__tar.gz → 0.1.9__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.1.9/.JuliaFormatter.toml +8 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/audit.yml +10 -28
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/benchmarks.yml +24 -4
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/ci.yml +49 -49
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/python-release.yml +5 -10
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/repo-metadata.yml +13 -6
- {lme_python-0.1.7 → lme_python-0.1.9}/.gitignore +1 -0
- lme_python-0.1.9/AGENTS.md +192 -0
- lme_python-0.1.9/BENCHMARKS.md +386 -0
- lme_python-0.1.9/BENCHMARK_COVERAGE.md +106 -0
- lme_python-0.1.9/CHANGELOG.md +174 -0
- lme_python-0.1.9/CONTRIBUTING.md +191 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/Cargo.lock +33 -32
- {lme_python-0.1.7 → lme_python-0.1.9}/Cargo.toml +30 -3
- {lme_python-0.1.7 → lme_python-0.1.9}/GUIDE.md +102 -8
- lme_python-0.1.9/OPTIMIZATION.md +488 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/PKG-INFO +3 -1
- {lme_python-0.1.7 → lme_python-0.1.9}/README.md +18 -12
- lme_python-0.1.9/RELEASING.md +247 -0
- lme_python-0.1.9/REPO_COMPLETION_BY_AREA.md +102 -0
- lme_python-0.1.9/Taskfile.yml +154 -0
- lme_python-0.1.9/USABILITY.md +178 -0
- lme_python-0.1.9/benchmarks/fair-rust-julia-reference-2026-07-04.json +97 -0
- lme_python-0.1.9/benchmarks/fair-rust-julia-reference-2026-07-06.json +99 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/COMPARISONS.md +5 -2
- lme_python-0.1.9/comparisons/bench_fair_julia_perf.jl +95 -0
- lme_python-0.1.9/comparisons/bench_fair_julia_timing.jl +135 -0
- lme_python-0.1.9/comparisons/bench_fair_rust_julia.rs +464 -0
- lme_python-0.1.9/comparisons/bench_perf_breakdown.rs +141 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/categorical_anova.R +3 -2
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/categorical_anova.jl +1 -1
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_cbpp.R +8 -5
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_cbpp.jl +9 -9
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_gaussian.R +3 -6
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_grouseticks.R +1 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_grouseticks.jl +11 -11
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmer_weighted.R +1 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmer_weighted.jl +2 -1
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_dyestuff.jl +2 -2
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_pastes.R +2 -1
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_pastes.jl +8 -8
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_penicillin.jl +10 -10
- lme_python-0.1.9/comparisons/parity/glmm_offset.R +21 -0
- lme_python-0.1.9/comparisons/parity/glmm_offset.jl +27 -0
- lme_python-0.1.9/comparisons/parity/glmm_probit.R +21 -0
- lme_python-0.1.9/comparisons/parity/glmm_probit.jl +27 -0
- lme_python-0.1.9/comparisons/parity/glmm_weighted.R +22 -0
- lme_python-0.1.9/comparisons/parity/glmm_weighted.jl +27 -0
- lme_python-0.1.9/comparisons/parity/parity_r_setup.R +6 -0
- lme_python-0.1.9/comparisons/parity/sleepstudy_offset.R +21 -0
- lme_python-0.1.9/comparisons/parity/sleepstudy_offset.jl +27 -0
- lme_python-0.1.9/comparisons/parity_export.rs +125 -0
- lme_python-0.1.9/comparisons/r_setup.R +3 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy.R +1 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy.jl +12 -12
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy_ml.jl +1 -1
- lme_python-0.1.9/examples/nlmm_fixture_dump.rs +40 -0
- lme_python-0.1.9/lefthook.yml +54 -0
- lme_python-0.1.9/mise.toml +16 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/pyproject.toml +18 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/Cargo.lock +41 -73
- {lme_python-0.1.7 → lme_python-0.1.9}/python/Cargo.toml +2 -2
- lme_python-0.1.9/python/PYTHON_GUIDE.md +368 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/glmer_cbpp.py +5 -4
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/glmer_grouseticks.py +15 -14
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/lm_basics.py +2 -2
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/lmer_sleepstudy.py +9 -7
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/model_comparison.py +22 -20
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/compare_plots.py +22 -14
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/find_rscript.py +1 -2
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/numeric_overlay.py +1 -3
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/plot_demo.py +9 -3
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/run_all.py +10 -3
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/verification_project/parity.py +17 -21
- lme_python-0.1.9/python/lme_python.pyi +174 -0
- lme_python-0.1.9/python/src/lib.rs +1492 -0
- lme_python-0.1.9/python/tests/test_api_parity.py +189 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/tests/test_basic.py +97 -66
- lme_python-0.1.9/python/tests/test_dataframe_inputs.py +45 -0
- lme_python-0.1.9/python/tests/test_golden_parity.py +112 -0
- lme_python-0.1.9/python/tests/test_nlmer_custom_mean.py +54 -0
- lme_python-0.1.9/python/uv.lock +2289 -0
- lme_python-0.1.9/scripts/ci/README.md +27 -0
- lme_python-0.1.9/scripts/ci/julia_format.jl +56 -0
- lme_python-0.1.9/scripts/ci/lme_ci.py +710 -0
- lme_python-0.1.9/scripts/ci/r_format.R +19 -0
- lme_python-0.1.9/scripts/ci/run.sh +30 -0
- lme_python-0.1.9/scripts/local_ci.ps1 +4 -0
- lme_python-0.1.9/scripts/local_ci.sh +4 -0
- lme_python-0.1.9/scripts/run_fair_rust_julia_benchmark.py +655 -0
- lme_python-0.1.9/scripts/run_perf_breakdown.py +342 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/sync_github_repo_metadata.py +35 -4
- lme_python-0.1.9/scripts/verify_cross_language_parity.py +331 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/anova.rs +57 -169
- {lme_python-0.1.7 → lme_python-0.1.9}/src/anova_contrasts.rs +36 -2
- lme_python-0.1.9/src/contrast.rs +266 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/ddf.rs +11 -5
- {lme_python-0.1.7 → lme_python-0.1.9}/src/family.rs +194 -33
- {lme_python-0.1.7 → lme_python-0.1.9}/src/glmm_math.rs +42 -186
- lme_python-0.1.9/src/intercept_blocked.rs +1237 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/kenward_roger.rs +11 -1
- lme_python-0.1.9/src/kr_modcomp.rs +327 -0
- lme_python-0.1.9/src/kr_vcov_adj.rs +296 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/lib.rs +274 -138
- lme_python-0.1.9/src/math.rs +1699 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/model_matrix.rs +281 -64
- lme_python-0.1.9/src/nlmm/fit.rs +1075 -0
- lme_python-0.1.9/src/nlmm/formula.rs +284 -0
- lme_python-0.1.9/src/nlmm/mean.rs +25 -0
- lme_python-0.1.9/src/nlmm/mean_fn.rs +151 -0
- lme_python-0.1.9/src/nlmm/mod.rs +87 -0
- lme_python-0.1.9/src/nlmm/predict.rs +104 -0
- lme_python-0.1.9/src/nlmm/re_cov.rs +125 -0
- lme_python-0.1.9/src/nlmm/self_start.rs +214 -0
- lme_python-0.1.9/src/nlmm/ssasymp.rs +33 -0
- lme_python-0.1.9/src/nlmm/ssgompertz.rs +47 -0
- lme_python-0.1.9/src/nlmm/sslogis.rs +37 -0
- lme_python-0.1.9/src/nlmm/ssmicmen.rs +27 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/optimizer.rs +351 -26
- lme_python-0.1.9/src/perf_diag.rs +302 -0
- lme_python-0.1.9/src/quadrature.rs +187 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/satterthwaite.rs +2 -0
- lme_python-0.1.9/tests/data/cbpp_binary.csv +843 -0
- lme_python-0.1.9/tests/data/cbpp_binary_weighted.csv +843 -0
- lme_python-0.1.9/tests/data/glmm_binomial_probit.json +872 -0
- lme_python-0.1.9/tests/data/glmm_binomial_weighted.json +872 -0
- lme_python-0.1.9/tests/data/glmm_poisson_offset.json +536 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/golden_parity_manifest.json +372 -8
- lme_python-0.1.9/tests/data/grouseticks.csv +404 -0
- lme_python-0.1.9/tests/data/orange.csv +36 -0
- lme_python-0.1.9/tests/data/orange_nlmer.json +12 -0
- lme_python-0.1.9/tests/data/pastes_cask_reml.json +100 -0
- lme_python-0.1.9/tests/data/sleepstudy.csv +181 -0
- lme_python-0.1.9/tests/data/sleepstudy_offset_reml.json +246 -0
- lme_python-0.1.9/tests/data/ssasymp_synthetic.csv +61 -0
- lme_python-0.1.9/tests/data/ssfol_nlmer.json +10 -0
- lme_python-0.1.9/tests/data/ssgompertz_nlmer.json +10 -0
- lme_python-0.1.9/tests/data/ssgompertz_synthetic.csv +61 -0
- lme_python-0.1.9/tests/data/ssmicmen_nlmer.json +10 -0
- lme_python-0.1.9/tests/data/ssmicmen_synthetic.csv +61 -0
- lme_python-0.1.9/tests/gen_nlmm_extras.R +54 -0
- lme_python-0.1.9/tests/generate_nlmm_fixtures.R +65 -0
- lme_python-0.1.9/tests/generate_test_data.R +895 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_anova.rs +2 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_anova_types.rs +18 -0
- lme_python-0.1.9/tests/test_contrast.rs +87 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_coverage_gaps.rs +2 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_crossed_mock.rs +14 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_errors.rs +20 -21
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_failure_modes.rs +14 -1
- lme_python-0.1.9/tests/test_glmm_links.rs +78 -0
- lme_python-0.1.9/tests/test_glmm_offset_grouseticks.rs +43 -0
- lme_python-0.1.9/tests/test_glmm_weighted.rs +64 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_golden_parity.rs +81 -12
- lme_python-0.1.9/tests/test_kr_modcomp_pastes.rs +51 -0
- lme_python-0.1.9/tests/test_nlmm_agq.rs +37 -0
- lme_python-0.1.9/tests/test_nlmm_custom_mean.rs +51 -0
- lme_python-0.1.9/tests/test_nlmm_orange.rs +154 -0
- lme_python-0.1.9/tests/test_nlmm_orange_multi_re.rs +92 -0
- lme_python-0.1.9/tests/test_nlmm_self_start.rs +43 -0
- lme_python-0.1.9/tests/test_nlmm_ssasymp.rs +79 -0
- lme_python-0.1.9/tests/test_nlmm_ssfol.rs +98 -0
- lme_python-0.1.9/tests/test_nlmm_ssmicmen.rs +37 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_production_load.rs +60 -3
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_sandwich_math.R +3 -3
- lme_python-0.1.7/BENCHMARKS.md +0 -183
- lme_python-0.1.7/CHANGELOG.md +0 -103
- lme_python-0.1.7/CONTRIBUTING.md +0 -122
- lme_python-0.1.7/RELEASING.md +0 -208
- lme_python-0.1.7/REPO_COMPLETION_BY_AREA.md +0 -74
- lme_python-0.1.7/python/PYTHON_GUIDE.md +0 -266
- lme_python-0.1.7/python/requirements-ci.txt +0 -87
- lme_python-0.1.7/python/src/lib.rs +0 -675
- lme_python-0.1.7/scripts/local_ci.ps1 +0 -85
- lme_python-0.1.7/scripts/local_ci.sh +0 -86
- lme_python-0.1.7/src/math.rs +0 -420
- lme_python-0.1.7/tests/data/cbpp_binary.csv +0 -843
- lme_python-0.1.7/tests/data/grouseticks.csv +0 -404
- lme_python-0.1.7/tests/data/pastes_cask_reml.json +0 -13
- lme_python-0.1.7/tests/data/sleepstudy.csv +0 -181
- lme_python-0.1.7/tests/generate_test_data.R +0 -506
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/crate-publish-dry-run.yml +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/.github/workflows/fuzz-smoke.yml +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/LICENSE +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/benches/bench_load_production.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/benches/bench_math.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/categorical_anova.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/categorical_anova.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/comparison_chart.png +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_cbpp.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_cbpp.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_gaussian.jl +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_gaussian.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_gaussian.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_grouseticks.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/glmm_grouseticks.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmer_weighted.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmer_weighted.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_dyestuff.R +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_dyestuff.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_dyestuff.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_pastes.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_pastes.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_penicillin.R +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_penicillin.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/lmm_penicillin.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy_ml.R +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy_ml.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/comparisons/sleepstudy_ml.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/docs/benchmarks/app.js +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/docs/benchmarks/index.html +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/docs/benchmarks/styles.css +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/docs/index.html +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/.gitignore +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/README.md +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/figure_specs.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/paths.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/plotting_demo/plot_r.R +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/verification_project/README.md +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/verification_project/conftest.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/verification_project/paths.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/verification_project/run.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/python/examples/verification_project/test_parity.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/ast_explorations/test_fiasto.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/ast_explorations/test_fiasto_offset.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/build_benchmark_site.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/dump_dyestuff.R +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/dump_pastes.R +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/make_penicillin_csv.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/plot_comparisons.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/scripts/run_cross_language_benchmarks.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/formula.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/src/robust.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/categorical_anova_test.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/dyestuff.csv +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/glmm_binomial.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/glmm_poisson.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/intercept_only.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/mock_crossed.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/mock_ml.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/pastes.csv +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/penicillin.csv +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/penicillin.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/data/random_slopes.json +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/generate_mock_data.py +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_conditional_real.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_confint_simulate.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_coverage_edge_cases.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_e2e_lmer.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_edge_cases.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_formula.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_formula_stress.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_gaps.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_glmm.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_intercept_only.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_kenward_roger.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_ml_optimization.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_no_intercept.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_numerical_parity.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_predict.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_random_slopes.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_robust.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_satterthwaite.rs +0 -0
- {lme_python-0.1.7 → lme_python-0.1.9}/tests/test_statistical_identities.rs +0 -0
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
name: Security audit
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
schedule:
|
|
5
|
-
# Weekly: Mondays 12:00 UTC
|
|
6
|
-
- cron: "0 12 * * 1"
|
|
7
4
|
workflow_dispatch:
|
|
8
5
|
push:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
- "**/Cargo.toml"
|
|
12
|
-
- "**/Cargo.lock"
|
|
13
|
-
- "python/pyproject.toml"
|
|
14
|
-
- "python/requirements-ci.txt"
|
|
15
|
-
pull_request:
|
|
16
|
-
branches: [master]
|
|
17
|
-
paths:
|
|
18
|
-
- "**/Cargo.toml"
|
|
19
|
-
- "**/Cargo.lock"
|
|
20
|
-
- "python/pyproject.toml"
|
|
21
|
-
- "python/requirements-ci.txt"
|
|
6
|
+
tags:
|
|
7
|
+
- "v*"
|
|
22
8
|
|
|
23
9
|
permissions:
|
|
24
10
|
contents: read
|
|
@@ -49,25 +35,21 @@ jobs:
|
|
|
49
35
|
run: cargo audit
|
|
50
36
|
|
|
51
37
|
pip-audit:
|
|
52
|
-
name: pip-audit (
|
|
38
|
+
name: pip-audit (uv.lock)
|
|
53
39
|
runs-on: ubuntu-latest
|
|
54
40
|
steps:
|
|
55
41
|
- uses: actions/checkout@v6
|
|
56
42
|
|
|
57
|
-
- name: Set up
|
|
58
|
-
uses:
|
|
43
|
+
- name: Set up uv
|
|
44
|
+
uses: astral-sh/setup-uv@v6
|
|
59
45
|
with:
|
|
60
46
|
python-version: "3.11"
|
|
61
|
-
cache:
|
|
62
|
-
cache-dependency-
|
|
47
|
+
enable-cache: true
|
|
48
|
+
cache-dependency-glob: "python/uv.lock"
|
|
63
49
|
|
|
64
|
-
- name:
|
|
50
|
+
- name: pip-audit
|
|
65
51
|
working-directory: python
|
|
66
52
|
run: |
|
|
67
53
|
set -euo pipefail
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
- name: pip-audit
|
|
72
|
-
working-directory: python
|
|
73
|
-
run: python -m pip_audit
|
|
54
|
+
uv sync --extra dev --no-install-project
|
|
55
|
+
uv run pip-audit
|
|
@@ -122,13 +122,24 @@ jobs:
|
|
|
122
122
|
- name: Install R benchmark dependencies
|
|
123
123
|
run: |
|
|
124
124
|
mkdir -p "$R_LIBS_USER"
|
|
125
|
-
Rscript -e 'install.packages("lme4", repos="https://cloud.r-project.org", lib=Sys.getenv("R_LIBS_USER"))'
|
|
125
|
+
Rscript -e 'install.packages(c("lme4", "lmerTest", "car", "rlang", "styler"), repos="https://cloud.r-project.org", lib=Sys.getenv("R_LIBS_USER"))'
|
|
126
126
|
|
|
127
127
|
- name: Install Julia benchmark dependencies
|
|
128
|
-
run: julia -e 'using Pkg; Pkg.add(["CSV", "DataFrames", "MixedModels"])'
|
|
128
|
+
run: julia -e 'using Pkg; Pkg.add(["CSV", "DataFrames", "JSON", "MixedModels", "GLM", "JuliaFormatter"])'
|
|
129
|
+
|
|
130
|
+
- name: Check comparison script formatting
|
|
131
|
+
run: python3 scripts/ci/lme_ci.py comparison-format-check --required
|
|
129
132
|
|
|
130
133
|
- name: Build Rust examples
|
|
131
|
-
run: cargo build --release --examples
|
|
134
|
+
run: cargo build --release --locked --examples
|
|
135
|
+
|
|
136
|
+
- name: Run fair Rust vs Julia fit benchmark
|
|
137
|
+
run: |
|
|
138
|
+
python scripts/run_fair_rust_julia_benchmark.py \
|
|
139
|
+
--warmups "$BENCH_WARMUPS" \
|
|
140
|
+
--repeats "$BENCH_REPEATS" \
|
|
141
|
+
--output "benchmark-results/fair-rust-julia-${{ github.sha }}.json" \
|
|
142
|
+
--skip-rust-build
|
|
132
143
|
|
|
133
144
|
- name: Run cross-language benchmark script
|
|
134
145
|
id: cross_language_run
|
|
@@ -149,7 +160,9 @@ jobs:
|
|
|
149
160
|
uses: actions/upload-artifact@v7
|
|
150
161
|
with:
|
|
151
162
|
name: cross-language-${{ github.sha }}
|
|
152
|
-
path:
|
|
163
|
+
path: |
|
|
164
|
+
benchmark-results/cross-language-${{ github.sha }}.json
|
|
165
|
+
benchmark-results/fair-rust-julia-${{ github.sha }}.json
|
|
153
166
|
|
|
154
167
|
- name: Attach cross-language artifact to GitHub Release
|
|
155
168
|
if: always() && startsWith(github.ref, 'refs/tags/')
|
|
@@ -164,10 +177,17 @@ jobs:
|
|
|
164
177
|
fi
|
|
165
178
|
}
|
|
166
179
|
gh release upload "${GITHUB_REF_NAME}" "benchmark-results/cross-language-${{ github.sha }}.json" --clobber
|
|
180
|
+
if [ -f "benchmark-results/fair-rust-julia-${{ github.sha }}.json" ]; then
|
|
181
|
+
gh release upload "${GITHUB_REF_NAME}" "benchmark-results/fair-rust-julia-${{ github.sha }}.json" --clobber
|
|
182
|
+
fi
|
|
167
183
|
|
|
168
184
|
- name: Fail if cross-language benchmarks failed
|
|
169
185
|
if: steps.cross_language_run.outputs.status != '0'
|
|
170
186
|
run: |
|
|
187
|
+
json="benchmark-results/cross-language-${{ github.sha }}.json"
|
|
188
|
+
if [ -f "$json" ]; then
|
|
189
|
+
python3 scripts/ci/lme_ci.py benchmark-failures "$json" >&2 || true
|
|
190
|
+
fi
|
|
171
191
|
echo "Cross-language benchmark script failed with exit code ${{ steps.cross_language_run.outputs.status }}." >&2
|
|
172
192
|
exit 1
|
|
173
193
|
|
|
@@ -2,15 +2,9 @@ name: CI
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- "LICENSE"
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [master]
|
|
11
|
-
paths-ignore:
|
|
12
|
-
- "**/*.md"
|
|
13
|
-
- "LICENSE"
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
14
8
|
|
|
15
9
|
permissions:
|
|
16
10
|
contents: read
|
|
@@ -23,6 +17,28 @@ env:
|
|
|
23
17
|
CARGO_TERM_COLOR: always
|
|
24
18
|
|
|
25
19
|
jobs:
|
|
20
|
+
rust-lint:
|
|
21
|
+
name: Rust fmt & clippy
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v6
|
|
25
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
26
|
+
- name: Install OpenBLAS
|
|
27
|
+
run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
|
|
28
|
+
- name: Cache cargo registry & build
|
|
29
|
+
uses: actions/cache@v5
|
|
30
|
+
with:
|
|
31
|
+
path: |
|
|
32
|
+
~/.cargo/registry
|
|
33
|
+
~/.cargo/git
|
|
34
|
+
target
|
|
35
|
+
key: ubuntu-lint-${{ hashFiles('**/Cargo.lock') }}
|
|
36
|
+
restore-keys: ubuntu-lint-
|
|
37
|
+
- name: rustfmt --check
|
|
38
|
+
run: python3 scripts/ci/lme_ci.py fmt-check
|
|
39
|
+
- name: clippy
|
|
40
|
+
run: python3 scripts/ci/lme_ci.py clippy
|
|
41
|
+
|
|
26
42
|
test:
|
|
27
43
|
name: Test (${{ matrix.os }})
|
|
28
44
|
runs-on: ${{ matrix.os }}
|
|
@@ -37,10 +53,6 @@ jobs:
|
|
|
37
53
|
- name: Install Rust toolchain
|
|
38
54
|
uses: dtolnay/rust-toolchain@stable
|
|
39
55
|
|
|
40
|
-
- name: Install OpenBLAS (macOS)
|
|
41
|
-
if: matrix.os == 'macos-latest'
|
|
42
|
-
run: brew install openblas
|
|
43
|
-
|
|
44
56
|
- name: Install OpenBLAS (Ubuntu)
|
|
45
57
|
if: matrix.os == 'ubuntu-latest'
|
|
46
58
|
run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
|
|
@@ -55,35 +67,29 @@ jobs:
|
|
|
55
67
|
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
56
68
|
restore-keys: ${{ runner.os }}-cargo-
|
|
57
69
|
|
|
58
|
-
- name:
|
|
59
|
-
|
|
70
|
+
- name: Set OpenBLAS target (macOS)
|
|
71
|
+
if: matrix.os == 'macos-latest'
|
|
72
|
+
run: echo "OPENBLAS_TARGET=ARMV8" >> "$GITHUB_ENV"
|
|
60
73
|
|
|
61
74
|
- name: Run tests
|
|
62
75
|
run: cargo test --verbose --locked
|
|
63
76
|
|
|
64
|
-
- name: Set up
|
|
65
|
-
uses:
|
|
77
|
+
- name: Set up uv
|
|
78
|
+
uses: astral-sh/setup-uv@v6
|
|
66
79
|
with:
|
|
67
80
|
python-version: "3.11"
|
|
68
|
-
cache:
|
|
69
|
-
cache-dependency-
|
|
81
|
+
enable-cache: true
|
|
82
|
+
cache-dependency-glob: "python/uv.lock"
|
|
70
83
|
|
|
71
84
|
- name: Python bindings (maturin develop, pytest, wheel install, pytest)
|
|
72
85
|
working-directory: python
|
|
73
86
|
shell: bash
|
|
74
87
|
run: |
|
|
75
88
|
set -euo pipefail
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
VPY=.venv/bin/python
|
|
81
|
-
fi
|
|
82
|
-
"$VPY" -m pip install -U pip
|
|
83
|
-
"$VPY" -m pip install -r requirements-ci.txt
|
|
84
|
-
"$VPY" -m maturin develop --release
|
|
85
|
-
"$VPY" -m pytest tests/ -v
|
|
86
|
-
"$VPY" -m maturin build --release -o dist
|
|
89
|
+
uv sync --extra dev --no-install-project
|
|
90
|
+
uv run maturin develop --release
|
|
91
|
+
uv run pytest tests/ -v
|
|
92
|
+
uv run maturin build --release -o dist
|
|
87
93
|
shopt -s nullglob
|
|
88
94
|
wheels=(dist/lme_python-*.whl)
|
|
89
95
|
if [ ${#wheels[@]} -eq 0 ]; then
|
|
@@ -91,8 +97,8 @@ jobs:
|
|
|
91
97
|
ls -la dist 2>/dev/null || true
|
|
92
98
|
exit 1
|
|
93
99
|
fi
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
uv run pip install --force-reinstall "${wheels[0]}"
|
|
101
|
+
uv run pytest tests/ -v
|
|
96
102
|
|
|
97
103
|
rust-all-targets:
|
|
98
104
|
name: Rust check (all targets)
|
|
@@ -183,28 +189,22 @@ jobs:
|
|
|
183
189
|
target
|
|
184
190
|
key: ubuntu-cargo-${{ hashFiles('**/Cargo.lock') }}
|
|
185
191
|
restore-keys: ubuntu-cargo-
|
|
186
|
-
- name: Set up
|
|
187
|
-
uses:
|
|
192
|
+
- name: Set up uv
|
|
193
|
+
uses: astral-sh/setup-uv@v6
|
|
188
194
|
with:
|
|
189
195
|
python-version: ${{ matrix.python-version }}
|
|
190
|
-
cache:
|
|
191
|
-
cache-dependency-
|
|
196
|
+
enable-cache: true
|
|
197
|
+
cache-dependency-glob: "python/uv.lock"
|
|
198
|
+
|
|
192
199
|
- name: Python bindings (maturin develop, pytest, wheel install, pytest)
|
|
193
200
|
working-directory: python
|
|
194
201
|
shell: bash
|
|
195
202
|
run: |
|
|
196
203
|
set -euo pipefail
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
VPY=.venv/bin/python
|
|
202
|
-
fi
|
|
203
|
-
"$VPY" -m pip install -U pip
|
|
204
|
-
"$VPY" -m pip install -r requirements-ci.txt
|
|
205
|
-
"$VPY" -m maturin develop --release
|
|
206
|
-
"$VPY" -m pytest tests/ -v
|
|
207
|
-
"$VPY" -m maturin build --release -o dist
|
|
204
|
+
uv sync --extra dev --no-install-project
|
|
205
|
+
uv run maturin develop --release
|
|
206
|
+
uv run pytest tests/ -v
|
|
207
|
+
uv run maturin build --release -o dist
|
|
208
208
|
shopt -s nullglob
|
|
209
209
|
wheels=(dist/lme_python-*.whl)
|
|
210
210
|
if [ ${#wheels[@]} -eq 0 ]; then
|
|
@@ -212,8 +212,8 @@ jobs:
|
|
|
212
212
|
ls -la dist 2>/dev/null || true
|
|
213
213
|
exit 1
|
|
214
214
|
fi
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
uv run pip install --force-reinstall "${wheels[0]}"
|
|
216
|
+
uv run pytest tests/ -v
|
|
217
217
|
|
|
218
218
|
docs:
|
|
219
219
|
name: Build docs
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
name: Build and Publish Python Wheels
|
|
2
2
|
|
|
3
|
-
# Trigger
|
|
3
|
+
# Trigger automatically only for release tags; use workflow_dispatch for manual builds.
|
|
4
4
|
on:
|
|
5
5
|
push:
|
|
6
|
-
branches:
|
|
7
|
-
- master
|
|
8
6
|
tags:
|
|
9
|
-
-
|
|
10
|
-
pull_request:
|
|
11
|
-
paths:
|
|
12
|
-
- 'python/**'
|
|
13
|
-
- 'src/**'
|
|
7
|
+
- "v*"
|
|
14
8
|
workflow_dispatch:
|
|
15
9
|
|
|
16
10
|
permissions:
|
|
@@ -82,8 +76,9 @@ jobs:
|
|
|
82
76
|
- uses: actions/setup-python@v6
|
|
83
77
|
with:
|
|
84
78
|
python-version: '3.10'
|
|
85
|
-
- name:
|
|
86
|
-
|
|
79
|
+
- name: Set OpenBLAS target (Apple Silicon wheel)
|
|
80
|
+
if: matrix.target == 'aarch64'
|
|
81
|
+
run: echo "OPENBLAS_TARGET=ARMV8" >> "$GITHUB_ENV"
|
|
87
82
|
- name: Build wheels
|
|
88
83
|
uses: PyO3/maturin-action@v1
|
|
89
84
|
with:
|
|
@@ -2,11 +2,8 @@ name: Sync Repository Metadata
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Cargo.toml
|
|
8
|
-
- scripts/sync_github_repo_metadata.py
|
|
9
|
-
- .github/workflows/repo-metadata.yml
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
10
7
|
workflow_dispatch:
|
|
11
8
|
|
|
12
9
|
permissions:
|
|
@@ -24,10 +21,20 @@ jobs:
|
|
|
24
21
|
run: |
|
|
25
22
|
if [ -z "$REPO_ADMIN_TOKEN" ]; then
|
|
26
23
|
echo "REPO_ADMIN_TOKEN is not set."
|
|
27
|
-
echo "Create a fine-grained PAT
|
|
24
|
+
echo "Create a fine-grained PAT with repository Administration: Read and write."
|
|
25
|
+
echo "Add it under Settings → Secrets and variables → Actions → REPO_ADMIN_TOKEN."
|
|
28
26
|
exit 1
|
|
29
27
|
fi
|
|
30
28
|
|
|
29
|
+
- name: Dry-run metadata payload
|
|
30
|
+
run: python scripts/sync_github_repo_metadata.py --dry-run
|
|
31
|
+
|
|
32
|
+
- name: Verify admin token is valid
|
|
33
|
+
env:
|
|
34
|
+
REPO_ADMIN_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN }}
|
|
35
|
+
GITHUB_REPOSITORY: ${{ github.repository }}
|
|
36
|
+
run: python scripts/sync_github_repo_metadata.py --verify-token
|
|
37
|
+
|
|
31
38
|
- name: Sync repository metadata
|
|
32
39
|
env:
|
|
33
40
|
REPO_ADMIN_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN }}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Agent and contributor pre-flights
|
|
2
|
+
|
|
3
|
+
This repo uses **four tiers** of checks (one more than a typical Task + bash-hook setup). Pick the tier that matches how much you changed.
|
|
4
|
+
|
|
5
|
+
## Architecture (vs duplicated shell scripts)
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
flowchart TD
|
|
9
|
+
subgraph entry [Entry points]
|
|
10
|
+
task[Taskfile.yml]
|
|
11
|
+
lh[Lefthook]
|
|
12
|
+
gha[GitHub Actions]
|
|
13
|
+
legacy[scripts/local_ci.*]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
subgraph core [Single implementation]
|
|
17
|
+
lme["scripts/ci/lme_ci.py"]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
subgraph tools [Pinned tooling via mise.toml]
|
|
21
|
+
mise[mise install]
|
|
22
|
+
uv[uv — Python venv + Ruff]
|
|
23
|
+
lk[lefthook — git hooks]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
task --> lme
|
|
27
|
+
lh --> lme
|
|
28
|
+
gha --> lme
|
|
29
|
+
legacy --> lme
|
|
30
|
+
mise --> uv
|
|
31
|
+
mise --> lk
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Why this is better than parallel `.ps1` / `.sh` trees:**
|
|
35
|
+
|
|
36
|
+
| Piece | Role |
|
|
37
|
+
|---|---|
|
|
38
|
+
| [`scripts/ci/lme_ci.py`](lme_ci.py) | One cross-platform implementation (stdlib Python 3.10+) |
|
|
39
|
+
| [`lefthook.yml`](../lefthook.yml) | Declarative hooks, **parallel** pre-commit, `stage_fixed` auto-staging |
|
|
40
|
+
| [`mise.toml`](../mise.toml) | Pins Rust, Python 3.11, uv, lefthook, task — reproducible dev env |
|
|
41
|
+
| [`uv`](https://docs.astral.sh/uv/) | Locked Python env (`uv sync` + `uv.lock`); Ruff via `uv tool run` |
|
|
42
|
+
| **Ruff** | `python/tests` + `python/examples` on `task lint`; staged auto-fix on commit |
|
|
43
|
+
| **pre-push hook** | Automatic `preflight` (lint + `cargo check --all-targets` + `cargo audit` + repo metadata dry-run) |
|
|
44
|
+
|
|
45
|
+
[`Taskfile.yml`](../Taskfile.yml) is a thin alias layer; it does not duplicate logic.
|
|
46
|
+
|
|
47
|
+
## 0. One-time setup
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
mise install # rust, python 3.11, uv, lefthook, task
|
|
51
|
+
task setup # mise install + lefthook install
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or install tools manually, then:
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
task hooks:install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 1. On `git commit` (Lefthook — parallel, staged-only)
|
|
61
|
+
|
|
62
|
+
[`lefthook.yml`](../lefthook.yml) runs only when matching files are staged:
|
|
63
|
+
|
|
64
|
+
| Staged | Command | Notes |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `**/*.rs` | `fmt` then `clippy` | `stage_fixed: true` re-stages rustfmt fixes |
|
|
67
|
+
| `python/**/*.py` | `ruff-staged --fix` | Ruff check + format; auto-stages fixes |
|
|
68
|
+
| `comparisons/**/*.R`, `tests/*.R` | `r-format-staged --fix` | **styler** when Rscript + styler are installed; skips otherwise |
|
|
69
|
+
| `comparisons/**/*.jl` | `julia-format-staged --fix` | **JuliaFormatter** when julia + JuliaFormatter are installed; skips otherwise |
|
|
70
|
+
| `Cargo.toml`, `Cargo.lock`, `python/Cargo.toml`, `python/Cargo.lock` | `cargo check --all-targets` | Catches link/BLAS issues on your platform when manifests change |
|
|
71
|
+
| `Cargo.toml`, `scripts/sync_github_repo_metadata.py` | `repo-metadata` dry-run (+ token verify if `REPO_ADMIN_TOKEN` set) | Validates About-box payload before GHA metadata sync |
|
|
72
|
+
| `comparisons/**`, benchmark script/workflow | `benchmarks-smoke` | Rust sleepstudy example timing (release build) |
|
|
73
|
+
|
|
74
|
+
Runs **in parallel** where safe (Ruff vs Rust fmt). Clippy waits on fmt (`priority`); manifest `check` runs after clippy (`priority` 3).
|
|
75
|
+
|
|
76
|
+
**Not** on commit: full `cargo test`, maturin/pytest, `pip-audit`.
|
|
77
|
+
|
|
78
|
+
Bypass once: `git commit --no-verify`.
|
|
79
|
+
|
|
80
|
+
## 2. On `git push` (Lefthook pre-push)
|
|
81
|
+
|
|
82
|
+
Runs **`preflight`** in parallel with itself as one command:
|
|
83
|
+
|
|
84
|
+
1. `lint` — Rust fmt/clippy + Ruff
|
|
85
|
+
2. `cargo check --workspace --all-targets --locked` — compile graph including tests/examples
|
|
86
|
+
3. `cargo audit` — root crate + `python/` (same as GHA audit job, without `pip-audit`)
|
|
87
|
+
4. `repo-metadata` — dry-run from `Cargo.toml`; **`--verify-token`** when `REPO_ADMIN_TOKEN` is in the environment
|
|
88
|
+
|
|
89
|
+
Install `cargo-audit` once: `cargo install cargo-audit` (pinned in GHA as 0.22.1).
|
|
90
|
+
|
|
91
|
+
To catch **expired metadata tokens** before push (optional):
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
$env:REPO_ADMIN_TOKEN = "<fine-grained PAT with Administration: Read and write>"
|
|
95
|
+
task repo-metadata
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If GHA reports `401 Bad credentials` on **Sync Repository Metadata**, rotate the secret: **Settings → Secrets and variables → Actions → `REPO_ADMIN_TOKEN`**.
|
|
99
|
+
|
|
100
|
+
Bypass: `git push --no-verify`.
|
|
101
|
+
|
|
102
|
+
**macOS Apple Silicon BLAS** (`openblas-static` vs MKL) is only exercised on `macos-latest` in GHA — Windows/Linux preflights cannot catch that matrix cell. GHA now runs automatically only for `v*` tags (plus manual dispatch), so after changing `Cargo.toml` BLAS target tables or release workflows, run `task ci` locally or manually dispatch the CI workflow before tagging.
|
|
103
|
+
|
|
104
|
+
**Cross-language benchmarks** (R + Julia, tag-only [`.github/workflows/benchmarks.yml`](.github/workflows/benchmarks.yml)): pre-commit runs `benchmarks-smoke` when `comparisons/**` changes (Rust `sleepstudy` example only). Full matrix needs R (`lme4`, `lmerTest`, `car`, `rlang`, `styler`) and Julia (`JuliaFormatter`) — run it through the tag/manual GHA workflow or `task benchmarks:preflight` with R installed. Comparison script formatting is checked in that GHA job; locally use `task lint:comparisons` (skips when tools are missing) or `task lint:comparisons:required` when R/Julia formatters are installed.
|
|
105
|
+
|
|
106
|
+
## 3. Before finishing work (manual)
|
|
107
|
+
|
|
108
|
+
If you changed **LMM fit throughput** ([`src/math.rs`](src/math.rs) intercept path, [`src/optimizer.rs`](src/optimizer.rs) θ search), read **[OPTIMIZATION.md](OPTIMIZATION.md)** and re-run the fair harness cases listed there.
|
|
109
|
+
|
|
110
|
+
### Quick path
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
task lint
|
|
114
|
+
task test:fast # unit tests only (~seconds after compile)
|
|
115
|
+
task preflight # same as pre-push hook (without pip-audit)
|
|
116
|
+
task audit # full security audit incl. pip-audit
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Full integration tests: `task test` or `cargo test --locked` (uses `[profile.test] opt-level = 2` in [`Cargo.toml`](Cargo.toml)).
|
|
120
|
+
|
|
121
|
+
Runs Rust (`fmt --check` + clippy) and Python Ruff on `python/tests` and `python/examples`.
|
|
122
|
+
|
|
123
|
+
If you changed **Python bindings**:
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
task python
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Rust-only
|
|
130
|
+
|
|
131
|
+
```powershell
|
|
132
|
+
task rust
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Python-only
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
task lint:python
|
|
139
|
+
task python
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Direct runner (no Task):
|
|
143
|
+
|
|
144
|
+
```powershell
|
|
145
|
+
python scripts/ci/lme_ci.py lint
|
|
146
|
+
python scripts/ci/lme_ci.py python --reuse-venv
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 4. Before PR / large refactors
|
|
150
|
+
|
|
151
|
+
```powershell
|
|
152
|
+
task # full core CI mirror
|
|
153
|
+
task ci:fast # reuse python/.venv, skip wheel-reinstall pytest
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Equivalent:
|
|
157
|
+
|
|
158
|
+
```powershell
|
|
159
|
+
python scripts/ci/lme_ci.py ci
|
|
160
|
+
python scripts/ci/lme_ci.py ci --reuse-venv --skip-wheel-reinstall
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**GHA-only:** multi-OS matrix, Python 3.10/3.12/3.13, production-load gates, and `pip-audit`. These workflows are release-oriented: automatic runs are limited to `v*` tags, with manual dispatch available for pre-release checks.
|
|
164
|
+
|
|
165
|
+
## `lme_ci.py` commands
|
|
166
|
+
|
|
167
|
+
| Command | Purpose |
|
|
168
|
+
|---|---|
|
|
169
|
+
| `ci` | Full core CI |
|
|
170
|
+
| `lint` | Rust + Python static checks |
|
|
171
|
+
| `preflight` | Pre-push: lint + check + cargo audit + repo metadata |
|
|
172
|
+
| `audit` | cargo audit + pip-audit (GHA mirror) |
|
|
173
|
+
| `repo-metadata` | Dry-run GitHub About sync; verify token if `REPO_ADMIN_TOKEN` set |
|
|
174
|
+
| `benchmarks-smoke` | Release examples + one Rust cross-language benchmark |
|
|
175
|
+
| `benchmarks-preflight` | `benchmarks-smoke` + optional `sleepstudy.R` when R/lme4 present |
|
|
176
|
+
| `benchmarks-fair-rust-julia` | Fair fit-only Rust vs MixedModels.jl timing (tier-A cases; see [BENCHMARK_COVERAGE.md](../BENCHMARK_COVERAGE.md); skip if Julia/packages missing) |
|
|
177
|
+
| `perf-breakdown` | Rust `LME_PERF_DIAG` vs Julia `optsum.feval`; reports `prepare` / `fit_prepared` / cold `lmer` |
|
|
178
|
+
| `rust-lint` | `fmt --check` + clippy |
|
|
179
|
+
| `ruff-lint` | Ruff on `python/tests` + `python/examples` |
|
|
180
|
+
| `comparison-format-check` | Optional styler/JuliaFormatter check on `comparisons/` (+ `tests/*.R`); use `--required` in GHA |
|
|
181
|
+
| `r-format-staged --fix` | Staged R comparison/golden-parity scripts |
|
|
182
|
+
| `julia-format-staged --fix` | Staged Julia comparison scripts |
|
|
183
|
+
| `build-test` | `cargo test` (full suite) |
|
|
184
|
+
| `test-fast` | `cargo test --lib` only (quick unit tests) |
|
|
185
|
+
| `rust-all` | Rust slice without Python bindings tests |
|
|
186
|
+
| `python` | Maturin + pytest (+ wheel pass) |
|
|
187
|
+
| `ruff-staged --fix` | Staged Python lint/format |
|
|
188
|
+
| `hooks-install` | `lefthook install` |
|
|
189
|
+
|
|
190
|
+
Run `python scripts/ci/lme_ci.py --help` for the full list.
|
|
191
|
+
|
|
192
|
+
See also [`CONTRIBUTING.md`](../CONTRIBUTING.md).
|