lme-python 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. lme_python-0.1.4/.github/workflows/benchmarks.yml +234 -0
  2. lme_python-0.1.4/.github/workflows/repo-metadata.yml +35 -0
  3. {lme_python-0.1.2 → lme_python-0.1.4}/.gitignore +4 -0
  4. lme_python-0.1.4/BENCHMARKS.md +183 -0
  5. lme_python-0.1.4/CHANGELOG.md +72 -0
  6. lme_python-0.1.4/CONTRIBUTING.md +91 -0
  7. {lme_python-0.1.2 → lme_python-0.1.4}/Cargo.lock +1 -1
  8. {lme_python-0.1.2 → lme_python-0.1.4}/Cargo.toml +31 -2
  9. lme_python-0.1.4/GUIDE.md +412 -0
  10. {lme_python-0.1.2 → lme_python-0.1.4}/PKG-INFO +1 -1
  11. lme_python-0.1.4/README.md +98 -0
  12. lme_python-0.1.4/RELEASING.md +185 -0
  13. lme_python-0.1.4/benches/bench_math.rs +898 -0
  14. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/COMPARISONS.md +12 -12
  15. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_cbpp.R +4 -4
  16. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_grouseticks.R +4 -4
  17. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_pastes.R +4 -4
  18. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy.R +4 -4
  19. lme_python-0.1.4/docs/benchmarks/app.js +207 -0
  20. lme_python-0.1.4/docs/benchmarks/index.html +92 -0
  21. lme_python-0.1.4/docs/benchmarks/styles.css +305 -0
  22. lme_python-0.1.4/docs/index.html +63 -0
  23. {lme_python-0.1.2 → lme_python-0.1.4}/python/Cargo.lock +2 -2
  24. {lme_python-0.1.2 → lme_python-0.1.4}/python/Cargo.toml +1 -1
  25. lme_python-0.1.4/python/PYTHON_GUIDE.md +265 -0
  26. lme_python-0.1.4/python/examples/glmer_cbpp.py +92 -0
  27. lme_python-0.1.4/python/examples/glmer_grouseticks.py +99 -0
  28. lme_python-0.1.4/python/examples/lm_basics.py +84 -0
  29. lme_python-0.1.4/python/examples/lmer_sleepstudy.py +96 -0
  30. lme_python-0.1.4/python/examples/model_comparison.py +117 -0
  31. lme_python-0.1.4/python/src/lib.rs +642 -0
  32. lme_python-0.1.4/python/tests/test_basic.py +406 -0
  33. lme_python-0.1.4/scripts/build_benchmark_site.py +210 -0
  34. {lme_python-0.1.2 → lme_python-0.1.4}/scripts/plot_comparisons.py +2 -2
  35. lme_python-0.1.4/scripts/run_cross_language_benchmarks.py +348 -0
  36. lme_python-0.1.4/scripts/sync_github_repo_metadata.py +161 -0
  37. {lme_python-0.1.2 → lme_python-0.1.4}/src/family.rs +2 -2
  38. {lme_python-0.1.2 → lme_python-0.1.4}/src/glmm_math.rs +57 -9
  39. {lme_python-0.1.2 → lme_python-0.1.4}/src/lib.rs +198 -18
  40. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_confint_simulate.rs +51 -1
  41. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_glmm.rs +47 -0
  42. lme_python-0.1.2/CHANGELOG.md +0 -21
  43. lme_python-0.1.2/GUIDE.md +0 -490
  44. lme_python-0.1.2/README.md +0 -101
  45. lme_python-0.1.2/benches/bench_math.rs +0 -240
  46. lme_python-0.1.2/python/PYTHON_GUIDE.md +0 -322
  47. lme_python-0.1.2/python/src/lib.rs +0 -201
  48. lme_python-0.1.2/python/tests/test_basic.py +0 -164
  49. {lme_python-0.1.2 → lme_python-0.1.4}/.github/workflows/ci.yml +0 -0
  50. {lme_python-0.1.2 → lme_python-0.1.4}/.github/workflows/python-release.yml +0 -0
  51. {lme_python-0.1.2 → lme_python-0.1.4}/LICENSE +0 -0
  52. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/comparison_chart.png +0 -0
  53. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_cbpp.jl +0 -0
  54. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_cbpp.py +0 -0
  55. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_cbpp.rs +0 -0
  56. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_grouseticks.jl +0 -0
  57. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_grouseticks.py +0 -0
  58. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/glmm_grouseticks.rs +0 -0
  59. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_dyestuff.R +0 -0
  60. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_dyestuff.jl +0 -0
  61. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_dyestuff.py +0 -0
  62. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_dyestuff.rs +0 -0
  63. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_pastes.jl +0 -0
  64. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_pastes.py +0 -0
  65. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_pastes.rs +0 -0
  66. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_penicillin.R +0 -0
  67. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_penicillin.jl +0 -0
  68. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_penicillin.py +0 -0
  69. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/lmm_penicillin.rs +0 -0
  70. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy.jl +0 -0
  71. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy.py +0 -0
  72. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy.rs +0 -0
  73. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy_ml.R +0 -0
  74. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy_ml.jl +0 -0
  75. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy_ml.py +0 -0
  76. {lme_python-0.1.2/examples → lme_python-0.1.4/comparisons}/sleepstudy_ml.rs +0 -0
  77. {lme_python-0.1.2 → lme_python-0.1.4}/pyproject.toml +0 -0
  78. {lme_python-0.1.2 → lme_python-0.1.4}/scripts/ast_explorations/test_fiasto.rs +0 -0
  79. {lme_python-0.1.2 → lme_python-0.1.4}/scripts/ast_explorations/test_fiasto_offset.rs +0 -0
  80. {lme_python-0.1.2 → lme_python-0.1.4}/scripts/dump_dyestuff.R +0 -0
  81. {lme_python-0.1.2 → lme_python-0.1.4}/scripts/dump_pastes.R +0 -0
  82. {lme_python-0.1.2 → lme_python-0.1.4}/scripts/make_penicillin_csv.py +0 -0
  83. {lme_python-0.1.2 → lme_python-0.1.4}/src/anova.rs +0 -0
  84. {lme_python-0.1.2 → lme_python-0.1.4}/src/formula.rs +0 -0
  85. {lme_python-0.1.2 → lme_python-0.1.4}/src/kenward_roger.rs +0 -0
  86. {lme_python-0.1.2 → lme_python-0.1.4}/src/math.rs +0 -0
  87. {lme_python-0.1.2 → lme_python-0.1.4}/src/model_matrix.rs +0 -0
  88. {lme_python-0.1.2 → lme_python-0.1.4}/src/optimizer.rs +0 -0
  89. {lme_python-0.1.2 → lme_python-0.1.4}/src/robust.rs +0 -0
  90. {lme_python-0.1.2 → lme_python-0.1.4}/src/satterthwaite.rs +0 -0
  91. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/cbpp_binary.csv +0 -0
  92. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/dyestuff.csv +0 -0
  93. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/glmm_binomial.json +0 -0
  94. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/glmm_poisson.json +0 -0
  95. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/grouseticks.csv +0 -0
  96. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/intercept_only.json +0 -0
  97. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/mock_crossed.json +0 -0
  98. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/mock_ml.json +0 -0
  99. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/pastes.csv +0 -0
  100. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/penicillin.csv +0 -0
  101. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/penicillin.json +0 -0
  102. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/random_slopes.json +0 -0
  103. {lme_python-0.1.2 → lme_python-0.1.4}/tests/data/sleepstudy.csv +0 -0
  104. {lme_python-0.1.2 → lme_python-0.1.4}/tests/generate_mock_data.py +0 -0
  105. {lme_python-0.1.2 → lme_python-0.1.4}/tests/generate_test_data.R +0 -0
  106. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_anova.rs +0 -0
  107. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_conditional_real.rs +0 -0
  108. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_coverage_edge_cases.rs +0 -0
  109. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_coverage_gaps.rs +0 -0
  110. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_crossed_mock.rs +0 -0
  111. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_e2e_lmer.rs +0 -0
  112. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_edge_cases.rs +0 -0
  113. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_errors.rs +0 -0
  114. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_formula.rs +0 -0
  115. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_gaps.rs +0 -0
  116. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_intercept_only.rs +0 -0
  117. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_kenward_roger.rs +0 -0
  118. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_ml_optimization.rs +0 -0
  119. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_no_intercept.rs +0 -0
  120. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_numerical_parity.rs +0 -0
  121. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_predict.rs +0 -0
  122. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_random_slopes.rs +0 -0
  123. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_robust.rs +0 -0
  124. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_sandwich_math.R +0 -0
  125. {lme_python-0.1.2 → lme_python-0.1.4}/tests/test_satterthwaite.rs +0 -0
@@ -0,0 +1,234 @@
1
+ name: Benchmarks
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ warmups:
7
+ description: Warmup runs per benchmark command
8
+ required: true
9
+ default: "1"
10
+ repeats:
11
+ description: Measured runs per benchmark command
12
+ required: true
13
+ default: "3"
14
+ push:
15
+ tags:
16
+ - "v*"
17
+
18
+ permissions:
19
+ contents: write
20
+ pages: write
21
+ id-token: write
22
+
23
+ env:
24
+ CARGO_TERM_COLOR: always
25
+ FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
26
+ JULIA_VERSION: "1.10.11"
27
+
28
+ jobs:
29
+ criterion:
30
+ name: Criterion Benchmarks
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - uses: actions/checkout@v6
34
+
35
+ - uses: dtolnay/rust-toolchain@stable
36
+
37
+ - name: Install OpenBLAS
38
+ run: sudo apt-get update && sudo apt-get install -y libopenblas-dev
39
+
40
+ - name: Cache cargo registry and build outputs
41
+ uses: actions/cache@v5
42
+ with:
43
+ path: |
44
+ ~/.cargo/registry
45
+ ~/.cargo/git
46
+ target
47
+ key: ${{ runner.os }}-bench-cargo-${{ hashFiles('**/Cargo.lock') }}
48
+ restore-keys: ${{ runner.os }}-bench-cargo-
49
+
50
+ - name: Run Criterion benchmarks
51
+ run: cargo bench
52
+
53
+ - name: Package Criterion baseline
54
+ run: |
55
+ mkdir -p benchmark-results
56
+ tar -czf "benchmark-results/criterion-${{ github.sha }}.tar.gz" target/criterion
57
+
58
+ - name: Upload Criterion artifact
59
+ uses: actions/upload-artifact@v7
60
+ with:
61
+ name: criterion-${{ github.sha }}
62
+ path: benchmark-results/criterion-${{ github.sha }}.tar.gz
63
+
64
+ - name: Attach Criterion artifact to GitHub Release
65
+ if: startsWith(github.ref, 'refs/tags/')
66
+ env:
67
+ GH_TOKEN: ${{ github.token }}
68
+ run: |
69
+ gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || {
70
+ if [[ "${GITHUB_REF_NAME}" == *-* ]]; then
71
+ gh release create "${GITHUB_REF_NAME}" --title "${GITHUB_REF_NAME}" --prerelease --latest=false
72
+ else
73
+ gh release create "${GITHUB_REF_NAME}" --title "${GITHUB_REF_NAME}"
74
+ fi
75
+ }
76
+ gh release upload "${GITHUB_REF_NAME}" "benchmark-results/criterion-${{ github.sha }}.tar.gz" --clobber
77
+
78
+ cross-language:
79
+ name: Cross-Language Benchmarks
80
+ runs-on: ubuntu-latest
81
+ env:
82
+ BENCH_WARMUPS: ${{ github.event_name == 'workflow_dispatch' && inputs.warmups || '1' }}
83
+ BENCH_REPEATS: ${{ github.event_name == 'workflow_dispatch' && inputs.repeats || '3' }}
84
+ R_LIBS_USER: ${{ github.workspace }}/.r-lib
85
+ steps:
86
+ - uses: actions/checkout@v6
87
+
88
+ - uses: dtolnay/rust-toolchain@stable
89
+
90
+ - uses: actions/setup-python@v6
91
+ with:
92
+ python-version: "3.11"
93
+
94
+ - name: Install system dependencies
95
+ run: |
96
+ sudo apt-get update
97
+ sudo apt-get install -y libopenblas-dev libcurl4-openssl-dev libssl-dev libxml2-dev r-base
98
+ curl -fsSL "https://julialang-s3.julialang.org/bin/linux/x64/1.10/julia-${JULIA_VERSION}-linux-x86_64.tar.gz" -o /tmp/julia.tar.gz
99
+ sudo tar -xzf /tmp/julia.tar.gz -C /opt
100
+ sudo ln -sf "/opt/julia-${JULIA_VERSION}/bin/julia" /usr/local/bin/julia
101
+ Rscript --version
102
+ julia --version
103
+
104
+ - name: Cache cargo registry and build outputs
105
+ uses: actions/cache@v5
106
+ with:
107
+ path: |
108
+ ~/.cargo/registry
109
+ ~/.cargo/git
110
+ target
111
+ key: ${{ runner.os }}-crosslang-cargo-${{ hashFiles('**/Cargo.lock') }}
112
+ restore-keys: ${{ runner.os }}-crosslang-cargo-
113
+
114
+ - name: Install Python benchmark dependencies
115
+ run: python -m pip install maturin polars pandas statsmodels
116
+
117
+ - name: Build Python wheel
118
+ run: maturin build --release --manifest-path python/Cargo.toml
119
+
120
+ - name: Install Python bindings
121
+ run: python -m pip install --force-reinstall python/target/wheels/*.whl
122
+
123
+ - name: Install R benchmark dependencies
124
+ run: |
125
+ mkdir -p "$R_LIBS_USER"
126
+ Rscript -e 'install.packages("lme4", repos="https://cloud.r-project.org", lib=Sys.getenv("R_LIBS_USER"))'
127
+
128
+ - name: Install Julia benchmark dependencies
129
+ run: julia -e 'using Pkg; Pkg.add(["CSV", "DataFrames", "MixedModels"])'
130
+
131
+ - name: Build Rust examples
132
+ run: cargo build --release --examples
133
+
134
+ - name: Run cross-language benchmark script
135
+ id: cross_language_run
136
+ run: |
137
+ mkdir -p benchmark-results
138
+ set +e
139
+ python scripts/run_cross_language_benchmarks.py \
140
+ --warmups "$BENCH_WARMUPS" \
141
+ --repeats "$BENCH_REPEATS" \
142
+ --output "benchmark-results/cross-language-${{ github.sha }}.json" \
143
+ --skip-rust-build
144
+ status=$?
145
+ echo "status=$status" >> "$GITHUB_OUTPUT"
146
+ exit 0
147
+
148
+ - name: Upload cross-language artifact
149
+ if: always()
150
+ uses: actions/upload-artifact@v7
151
+ with:
152
+ name: cross-language-${{ github.sha }}
153
+ path: benchmark-results/cross-language-${{ github.sha }}.json
154
+
155
+ - name: Attach cross-language artifact to GitHub Release
156
+ if: always() && startsWith(github.ref, 'refs/tags/')
157
+ env:
158
+ GH_TOKEN: ${{ github.token }}
159
+ run: |
160
+ gh release view "${GITHUB_REF_NAME}" >/dev/null 2>&1 || {
161
+ if [[ "${GITHUB_REF_NAME}" == *-* ]]; then
162
+ gh release create "${GITHUB_REF_NAME}" --title "${GITHUB_REF_NAME}" --prerelease --latest=false
163
+ else
164
+ gh release create "${GITHUB_REF_NAME}" --title "${GITHUB_REF_NAME}"
165
+ fi
166
+ }
167
+ gh release upload "${GITHUB_REF_NAME}" "benchmark-results/cross-language-${{ github.sha }}.json" --clobber
168
+
169
+ - name: Fail if cross-language benchmarks failed
170
+ if: steps.cross_language_run.outputs.status != '0'
171
+ run: |
172
+ echo "Cross-language benchmark script failed with exit code ${{ steps.cross_language_run.outputs.status }}." >&2
173
+ exit 1
174
+
175
+ pages:
176
+ name: Publish Benchmark Dashboard
177
+ runs-on: ubuntu-latest
178
+ needs:
179
+ - criterion
180
+ - cross-language
181
+ if: needs.criterion.result == 'success' && needs.cross-language.result == 'success' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/'))
182
+ environment:
183
+ name: github-pages
184
+ url: ${{ steps.deploy.outputs.page_url }}
185
+ steps:
186
+ - uses: actions/checkout@v6
187
+
188
+ - uses: actions/setup-python@v6
189
+ with:
190
+ python-version: "3.11"
191
+
192
+ - name: Download cross-language artifact
193
+ uses: actions/download-artifact@v5
194
+ with:
195
+ name: cross-language-${{ github.sha }}
196
+ path: site-input
197
+
198
+ - name: Resolve release URL
199
+ id: release_url
200
+ run: |
201
+ if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
202
+ echo "value=${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
203
+ else
204
+ echo "value=" >> "$GITHUB_OUTPUT"
205
+ fi
206
+
207
+ - name: Build benchmark dashboard
208
+ run: |
209
+ set -euo pipefail
210
+ mkdir -p site
211
+ cp -R docs/. site/
212
+ mkdir -p site/benchmarks/data
213
+ cross_json="$(find site-input -name 'cross-language-*.json' -print -quit)"
214
+ python scripts/build_benchmark_site.py \
215
+ --cross-language-json "$cross_json" \
216
+ --output-dir "site/benchmarks/data" \
217
+ --run-url "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" \
218
+ --release-url "${{ steps.release_url.outputs.value }}" \
219
+ --criterion-asset-name "criterion-${GITHUB_SHA}.tar.gz" \
220
+ --cross-language-asset-name "cross-language-${GITHUB_SHA}.json" \
221
+ --ref-name "${GITHUB_REF_NAME}"
222
+
223
+ - uses: actions/configure-pages@v5
224
+ with:
225
+ token: ${{ secrets.REPO_ADMIN_TOKEN != '' && secrets.REPO_ADMIN_TOKEN || github.token }}
226
+ enablement: ${{ secrets.REPO_ADMIN_TOKEN != '' }}
227
+
228
+ - uses: actions/upload-pages-artifact@v3
229
+ with:
230
+ path: site
231
+
232
+ - name: Deploy to GitHub Pages
233
+ id: deploy
234
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,35 @@
1
+ name: Sync Repository Metadata
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ paths:
7
+ - Cargo.toml
8
+ - scripts/sync_github_repo_metadata.py
9
+ - .github/workflows/repo-metadata.yml
10
+ workflow_dispatch:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ sync:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Verify admin token is configured
22
+ env:
23
+ REPO_ADMIN_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN }}
24
+ run: |
25
+ if [ -z "$REPO_ADMIN_TOKEN" ]; then
26
+ echo "REPO_ADMIN_TOKEN is not set."
27
+ echo "Create a fine-grained PAT or GitHub App token with repository Administration: write."
28
+ exit 1
29
+ fi
30
+
31
+ - name: Sync repository metadata
32
+ env:
33
+ REPO_ADMIN_TOKEN: ${{ secrets.REPO_ADMIN_TOKEN }}
34
+ GITHUB_REPOSITORY: ${{ github.repository }}
35
+ run: python scripts/sync_github_repo_metadata.py
@@ -4,6 +4,10 @@ rlib/
4
4
  julia-portable/
5
5
  julia-portable.zip
6
6
  flamegraph.svg
7
+ artifacts/
8
+ benchmark-results/
9
+ benchmark-site/
10
+ site/
7
11
 
8
12
  # Python bindings
9
13
  python/target/
@@ -0,0 +1,183 @@
1
+ # Benchmarks
2
+
3
+ This repository includes Criterion benchmarks for the Rust crate. Performance is an important selling point of `lme-rs`, but the benchmark suite should be described accurately: it is useful and non-trivial, yet it is not comprehensive enough to support blanket speed claims on its own.
4
+
5
+ ## Current benchmark coverage
6
+
7
+ The existing benchmark target is [benches/bench_math.rs](benches/bench_math.rs).
8
+
9
+ It currently covers these benchmark families:
10
+
11
+ - formula parsing for representative random-slope, nested, and crossed formulas
12
+ - model-matrix construction for representative random-slope, nested, and crossed datasets
13
+ - internal REML deviance evaluation on a fixture-backed random-slopes model
14
+ - isolated large-scale REML deviance evaluation on a synthetic sparse model (`50k` observations)
15
+ - end-to-end `lmer()` fitting on `sleepstudy`
16
+ - end-to-end weighted `lmer_weighted()` fitting on fixture-backed and large synthetic cases
17
+ - end-to-end `glmer()` fitting on `grouseticks` and `cbpp`
18
+ - population-level and conditional prediction on a larger repeated `sleepstudy` frame
19
+ - response-scale and conditional response-scale GLMM prediction on repeated Poisson and Binomial frames
20
+ - GLMM post-fit helpers such as Wald confidence intervals
21
+ - end-to-end `lmer()` fitting on a large synthetic random-intercept model (`100k` observations)
22
+ - end-to-end large crossed-effects and nested-effects fits
23
+ - parameterized size and complexity sweeps for random-intercept, crossed-effects, and nested-effects fits
24
+ - inference helpers: `with_robust_se()`, `with_satterthwaite()`, `with_kenward_roger()`, and ANOVA paths
25
+
26
+ That is enough to catch obvious regressions in the parser, design-matrix path, core math path, several end-to-end fits, and a meaningful slice of the inference and post-fit surface.
27
+
28
+ ## What the current suite does well
29
+
30
+ - Exercises both internal evaluation and public API entry points.
31
+ - Covers multiple stages of the modeling pipeline instead of only full-model fits.
32
+ - Covers both LMM and GLMM workflows.
33
+ - Includes both fixture-backed and synthetic large-scale cases.
34
+ - Includes nested and crossed random-effects structures.
35
+ - Includes weighted-model coverage.
36
+ - Includes parameterized size and complexity sweeps instead of only a few one-off scales.
37
+ - Includes several inference-heavy operations that are materially more expensive than fitting alone.
38
+ - Uses Criterion, which gives stable repeated measurements and report generation.
39
+
40
+ ## What it does not cover yet
41
+
42
+ The current benchmark suite is still narrow in a few important ways.
43
+
44
+ ### Missing comparisons
45
+
46
+ The repo now includes automated cross-language timing for representative example workloads through [scripts/run_cross_language_benchmarks.py](scripts/run_cross_language_benchmarks.py), including Rust, Python, R, and Julia runs where the required runtimes are installed.
47
+
48
+ What it still does not provide is a fully normalized cross-ecosystem benchmark harness with carefully matched optimizer settings, dataset loading rules, and machine-locked baselines for publication-grade claims.
49
+
50
+ ### Missing workload diversity
51
+
52
+ It does not yet isolate or compare:
53
+
54
+ - prediction throughput on large crossed and nested structures
55
+ - weighted GLMM-style workflows if those are added later
56
+ - GLMM inference and post-fit helper costs beyond the currently benchmarked paths
57
+ - confidence interval and simulation throughput across multiple model sizes
58
+
59
+ ### Missing benchmark dimensions
60
+
61
+ It does not currently sweep over controlled workload dimensions such as:
62
+
63
+ - number of observations
64
+ - number of groups
65
+ - random intercept vs random slope structure
66
+ - family and link combinations
67
+ - dense-like vs sparse-like random-effect structure
68
+
69
+ ## Cross-language benchmark methodology
70
+
71
+ If you want to make public performance claims against R, Python, or Julia, use a fixed methodology and record it alongside the results. The repo now automates example-level cross-language timing, but the methodology still matters because the ecosystems do not all expose exactly the same optimizers or post-fit APIs.
72
+
73
+ Recommended rules:
74
+
75
+ - run all languages on the same machine
76
+ - record CPU model, RAM, OS, compiler/interpreter versions, and BLAS backend
77
+ - use fixed datasets and fixed formulas
78
+ - compare like with like: REML vs REML, ML vs ML, same family, same link, same random-effects structure
79
+ - exclude file I/O from timed sections unless I/O is the thing being compared
80
+ - warm up the runtime before collecting measurements
81
+ - run multiple repetitions and report medians or confidence intervals, not a single best run
82
+ - note when optimizers are not directly comparable across ecosystems
83
+ - keep output artifacts or raw logs so later releases can be compared to the same baseline
84
+
85
+ For this repo, the most defensible cross-language benchmark set would start with the same reference datasets already used in `comparisons/COMPARISONS.md`:
86
+
87
+ - `sleepstudy`
88
+ - `dyestuff`
89
+ - `pastes`
90
+ - `penicillin`
91
+ - `cbpp`
92
+ - `grouseticks`
93
+
94
+ ## How to run the benchmarks
95
+
96
+ Run from the repository root:
97
+
98
+ ```bash
99
+ cargo bench
100
+ ```
101
+
102
+ To compile the benchmarks without executing them:
103
+
104
+ ```bash
105
+ cargo bench --no-run
106
+ ```
107
+
108
+ Criterion will emit reports under `target/criterion/`.
109
+
110
+ For automated cross-language timing, run:
111
+
112
+ ```bash
113
+ python scripts/run_cross_language_benchmarks.py
114
+ ```
115
+
116
+ This script writes JSON output with runtime versions, machine metadata, and per-command timing summaries.
117
+
118
+ The repository also includes a dedicated workflow in [.github/workflows/benchmarks.yml](.github/workflows/benchmarks.yml) that:
119
+
120
+ - runs Criterion benchmarks
121
+ - archives `target/criterion`
122
+ - runs the cross-language benchmark script
123
+ - uploads the resulting artifacts in CI
124
+ - attaches them to GitHub Releases on tag pushes
125
+ - publishes the latest benchmark dashboard to GitHub Pages at `https://x4g4p3x.github.io/lme-rs/benchmarks/`
126
+
127
+ ## Latest published results
128
+
129
+ The latest published benchmark artifacts are attached to the [v0.1.3 release](https://github.com/x4g4p3x/lme-rs/releases/tag/v0.1.3).
130
+
131
+ That release currently includes:
132
+
133
+ - `criterion-e8a82e0b97b88c5549bc61e89c22dc12c9060a02.tar.gz`
134
+ - `cross-language-e8a82e0b97b88c5549bc61e89c22dc12c9060a02.json`
135
+
136
+ The cross-language JSON includes representative timings for:
137
+
138
+ - `sleepstudy`
139
+ - `pastes`
140
+ - `cbpp`
141
+ - `grouseticks`
142
+
143
+ across:
144
+
145
+ - Rust
146
+ - Python
147
+ - R
148
+ - Julia
149
+
150
+ Treat those numbers as versioned release artifacts, not as universal constants. They were produced on GitHub-hosted runners with the workflow's pinned toolchain setup, so they are useful for release-to-release comparison and public transparency, but not as machine-independent proof of absolute speed.
151
+
152
+ If you want to cite benchmark results in release notes or external docs, prefer linking the release asset directly instead of copying raw numbers into the README. That keeps the landing page stable while still making the measured outputs inspectable.
153
+
154
+ ## How to interpret results
155
+
156
+ Use the existing suite primarily for:
157
+
158
+ - detecting regressions after changes to fitting logic
159
+ - tracking the cost of core LMM and GLMM paths over time
160
+ - sanity-checking large synthetic scaling cases
161
+
162
+ Do not use the current suite alone as evidence that `lme-rs` is universally faster than `lme4`, `statsmodels`, or `MixedModels.jl`.
163
+
164
+ ## Recommended next extensions
165
+
166
+ If performance is going to remain a central public claim, the benchmark suite should be extended. The highest-value additions are:
167
+
168
+ 1. Version-to-version regression tracking with saved benchmark outputs in CI or release notes.
169
+ 2. More controlled cross-language benchmark cases with tighter optimizer comparability notes.
170
+ 3. Prediction and post-fit sweeps for larger crossed and nested structures than the current medium-scale cases.
171
+ 4. More explicit GLMM post-fit benchmarks beyond fitting, prediction, and Wald intervals.
172
+ 5. Benchmarks that isolate optimizer iteration cost separately from formula parsing and matrix construction.
173
+
174
+ ## Recommendation for this repo today
175
+
176
+ The current benchmarking is meaningful, but not comprehensive.
177
+
178
+ That means:
179
+
180
+ - yes, keep performance as a selling point
181
+ - no, avoid absolute claims without extending the suite further
182
+ - yes, run benchmarks for performance-sensitive changes before release
183
+ - yes, extend the benchmark surface if performance is going to stay front-and-center in the README and release notes
@@ -0,0 +1,72 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.4] - 2026-03-22
9
+
10
+ ### Added
11
+
12
+ - Formula-string entry point `lm()` and corresponding Python binding for fitting ordinary least squares models using DataFrames.
13
+ - New Python examples directory `python/examples` with five runnable demonstration scripts mapping to common mixed-modeling workflows.
14
+
15
+ ### Changed
16
+
17
+ - Renamed `examples/` directory to `comparisons/` to clarify that it contains cross-language parity scripts, updating all build and documentation references accordingly.
18
+ - Promoted Kenward-Roger degrees of freedom from "provisional" status after validating numerical parity (within 0.01 df) against R's `pbkrtest`.
19
+
20
+ ## [0.1.3] - 2026-03-12
21
+
22
+ ### Added
23
+
24
+ - Expanded the benchmark suite to cover parsing, matrix construction, prediction paths, inference helpers, weighted fits, Kenward-Roger timing, and size sweeps.
25
+ - Added automated cross-language benchmark scripting and a GitHub workflow that saves benchmark artifacts in CI and on release tags.
26
+ - Added `CONTRIBUTING.md`, `RELEASING.md`, and `BENCHMARKS.md` to document contributor setup, release flow, and benchmark methodology.
27
+
28
+ ### Changed
29
+
30
+ - Reworked the README, Rust guide, Python guide, and comparison documentation to better reflect current capabilities, limitations, and release workflow.
31
+ - Switched the crate homepage to the docs.rs site and expanded repository metadata sync for GitHub description, topics, and website fields.
32
+ - Hardened the benchmark automation and example scripts so cross-language benchmark runs complete consistently on GitHub Actions.
33
+
34
+ ## [0.1.2] - 2026-03-09
35
+
36
+ ### Fixed
37
+
38
+ - Fixed a NaN deviance issue in the fitting path.
39
+ - Expanded Python test coverage for the 0.1.2 release.
40
+
41
+ ### Changed
42
+
43
+ - Aligned `Cargo.lock` with the 0.1.2 version bump.
44
+
45
+ ## [0.1.1] - 2026-03-09
46
+
47
+ ### Changed
48
+
49
+ - Bumped the crate and Python extension versions for the 0.1.1 release.
50
+ - Updated lockfiles to remove yanked dependencies.
51
+ - Fixed missing documentation warnings needed for publishing.
52
+
53
+ ### Fixed
54
+
55
+ - Hardened the GitHub Actions and wheel build pipeline across Linux, macOS, and Windows.
56
+ - Restored missing dependencies and build flags needed for OpenBLAS-based wheel builds.
57
+ - Fixed several maturin and PyO3 packaging workflow issues for the Python release path.
58
+
59
+ ## [0.1.0] - 2026-03-08
60
+
61
+ ### Added
62
+
63
+ - **Core LMM** - `lmer()` with REML and ML estimation, matching R's `lme4` to 4 decimal places
64
+ - **GLMM** - `glmer()` with Poisson, Binomial, Gaussian, and Gamma families via Laplace approximation
65
+ - **Predictions** - `predict()`, `predict_conditional()`, `predict_response()`, `predict_conditional_response()` with `allow_new_levels` support
66
+ - **Inference** - Satterthwaite and Kenward-Roger degrees of freedom, robust sandwich standard errors
67
+ - **Model comparison** - `anova()` for likelihood ratio tests between nested models
68
+ - **Diagnostics** - `confint()` for Wald confidence intervals, `simulate()` for parametric bootstrap
69
+ - **Formula parser** - Wilkinson notation with nested (`a/b`), crossed (`a + b`), and correlated slope+intercept (`x | group`) random effects
70
+ - **Sparse matrix architecture** - Compressed sparse column format with sparse Cholesky for large datasets
71
+ - **Python bindings** - `lme_python` package via PyO3/maturin with `lmer()`, `glmer()`, `predict()`
72
+ - **Documentation** - `GUIDE.md`, `PYTHON_GUIDE.md`, and cross-language `COMPARISONS.md`
@@ -0,0 +1,91 @@
1
+ # Contributing
2
+
3
+ ## Scope
4
+
5
+ This repository contains:
6
+
7
+ - the Rust crate in the repository root
8
+ - the Python bindings in `python/`
9
+ - cross-language comparison scripts in `comparisons/`
10
+ - regression fixtures and integration tests in `tests/`
11
+
12
+ ## Local setup
13
+
14
+ ### Rust
15
+
16
+ Install a stable Rust toolchain via [rustup](https://rustup.rs).
17
+
18
+ Useful commands:
19
+
20
+ ```bash
21
+ cargo build
22
+ cargo test
23
+ cargo test --doc
24
+ cargo fmt --check
25
+ cargo clippy -- -D warnings
26
+ ```
27
+
28
+ ### Python bindings
29
+
30
+ If you are changing `python/` or verifying the Python package locally:
31
+
32
+ ```bash
33
+ cd python
34
+ python -m venv .venv
35
+
36
+ # Windows
37
+ .venv\Scripts\activate
38
+
39
+ # macOS / Linux
40
+ source .venv/bin/activate
41
+
42
+ pip install maturin polars pytest
43
+ maturin develop --release
44
+ pytest
45
+ ```
46
+
47
+ ## Working on numerical changes
48
+
49
+ If you change fitting logic, optimizer behavior, variance calculations, or inference code:
50
+
51
+ - add or update Rust tests in `tests/`
52
+ - prefer fixture-backed tests for parity-sensitive behavior
53
+ - update `comparisons/COMPARISONS.md` when the reference output changes materially
54
+ - validate hard cases against R `lme4` where practical
55
+
56
+ Relevant files and directories:
57
+
58
+ - `src/` for the crate implementation
59
+ - `tests/data/` for fixture inputs
60
+ - `tests/generate_test_data.R` for R-backed fixture generation
61
+ - `comparisons/` for cross-language parity scripts (R, Python/statsmodels, Julia)
62
+
63
+ ## Working on documentation
64
+
65
+ When documentation changes affect user-visible behavior, keep these files aligned:
66
+
67
+ - `README.md` for the repository landing page
68
+ - `GUIDE.md` for Rust usage
69
+ - `python/PYTHON_GUIDE.md` for Python usage
70
+ - `CHANGELOG.md` for release-facing notes
71
+
72
+ Do not describe a feature as supported unless it is exposed by the public API and covered by tests or concrete examples.
73
+
74
+ ## Repository metadata sync
75
+
76
+ The GitHub About box is synced from `Cargo.toml` using:
77
+
78
+ - `.github/workflows/repo-metadata.yml`
79
+ - `scripts/sync_github_repo_metadata.py`
80
+
81
+ If you change `package.description`, `homepage`, `keywords`, or `categories`, the metadata workflow will update the GitHub repository metadata on the next run.
82
+
83
+ ## Pull request expectations
84
+
85
+ Keep changes focused. For non-trivial changes, include:
86
+
87
+ - the user-visible behavior change
88
+ - the tests you ran
89
+ - any compatibility or migration note if behavior changed
90
+
91
+ If a change is provisional or only partially mirrors R behavior, document that explicitly instead of implying full parity.
@@ -1528,7 +1528,7 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
1528
1528
 
1529
1529
  [[package]]
1530
1530
  name = "lme-rs"
1531
- version = "0.1.2"
1531
+ version = "0.1.4"
1532
1532
  dependencies = [
1533
1533
  "anyhow",
1534
1534
  "argmin",
@@ -1,12 +1,12 @@
1
1
  [package]
2
2
  name = "lme-rs"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  edition = "2021"
5
5
  authors = ["Martin Huck <x4g4p3x@users.noreply.github.com>"]
6
6
  description = "Rust port of R's lme4: linear mixed-effects models with 1:1 numerical compatibility"
7
7
  license = "MIT"
8
8
  repository = "https://github.com/x4g4p3x/lme-rs"
9
- homepage = "https://github.com/x4g4p3x/lme-rs"
9
+ homepage = "https://docs.rs/lme-rs/latest/lme_rs/"
10
10
  readme = "README.md"
11
11
  keywords = ["statistics", "lme4", "mixed-effects", "glmm", "regression"]
12
12
  categories = ["science", "mathematics"]
@@ -38,6 +38,35 @@ debug = true
38
38
 
39
39
  [[bench]]
40
40
  name = "bench_math"
41
+ harness = false
42
+
43
+ [[example]]
44
+ name = "glmm_cbpp"
45
+ path = "comparisons/glmm_cbpp.rs"
46
+
47
+ [[example]]
48
+ name = "glmm_grouseticks"
49
+ path = "comparisons/glmm_grouseticks.rs"
50
+
51
+ [[example]]
52
+ name = "lmm_dyestuff"
53
+ path = "comparisons/lmm_dyestuff.rs"
54
+
55
+ [[example]]
56
+ name = "lmm_pastes"
57
+ path = "comparisons/lmm_pastes.rs"
58
+
59
+ [[example]]
60
+ name = "lmm_penicillin"
61
+ path = "comparisons/lmm_penicillin.rs"
62
+
63
+ [[example]]
64
+ name = "sleepstudy"
65
+ path = "comparisons/sleepstudy.rs"
66
+
67
+ [[example]]
68
+ name = "sleepstudy_ml"
69
+ path = "comparisons/sleepstudy_ml.rs"
41
70
 
42
71
  [target.'cfg(target_arch = "x86_64")'.dependencies]
43
72
  ndarray-linalg = { version = "0.17", features = ["intel-mkl-static"] }