mixmogam 2.0.1__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.
- mixmogam-2.0.1/CHANGELOG.md +1259 -0
- mixmogam-2.0.1/CITATION.cff +31 -0
- mixmogam-2.0.1/LICENSE +15 -0
- mixmogam-2.0.1/MANIFEST.in +11 -0
- mixmogam-2.0.1/PKG-INFO +231 -0
- mixmogam-2.0.1/README.md +184 -0
- mixmogam-2.0.1/benchmarks/_harness.py +365 -0
- mixmogam-2.0.1/benchmarks/efficiency_followup.py +171 -0
- mixmogam-2.0.1/benchmarks/efficiency_followup_plan.md +31 -0
- mixmogam-2.0.1/benchmarks/hratt_efficiency.py +402 -0
- mixmogam-2.0.1/benchmarks/ldak_kvik_comparison.py +690 -0
- mixmogam-2.0.1/docs/ROADMAP.md +86 -0
- mixmogam-2.0.1/docs/design.md +524 -0
- mixmogam-2.0.1/docs/formats.md +89 -0
- mixmogam-2.0.1/docs/quickstart.md +331 -0
- mixmogam-2.0.1/docs/review-2026-10-03.md +115 -0
- mixmogam-2.0.1/docs/review-2026-10-25.md +159 -0
- mixmogam-2.0.1/docs/review-2026-10-29.md +167 -0
- mixmogam-2.0.1/docs/tutorial.md +208 -0
- mixmogam-2.0.1/examples/mixed_model_gwas.py +41 -0
- mixmogam-2.0.1/examples/permutation_threshold.py +16 -0
- mixmogam-2.0.1/examples/stepwise_mlmm.py +18 -0
- mixmogam-2.0.1/mixmogam/__init__.py +44 -0
- mixmogam-2.0.1/mixmogam/__main__.py +6 -0
- mixmogam-2.0.1/mixmogam/_binary.py +194 -0
- mixmogam-2.0.1/mixmogam/_cg.py +110 -0
- mixmogam-2.0.1/mixmogam/_chol.py +291 -0
- mixmogam-2.0.1/mixmogam/_fast.py +190 -0
- mixmogam-2.0.1/mixmogam/_he.py +112 -0
- mixmogam-2.0.1/mixmogam/_loco.py +466 -0
- mixmogam-2.0.1/mixmogam/_packed.py +261 -0
- mixmogam-2.0.1/mixmogam/_slq.py +231 -0
- mixmogam-2.0.1/mixmogam/_spa.py +358 -0
- mixmogam-2.0.1/mixmogam/_standardize.py +720 -0
- mixmogam-2.0.1/mixmogam/_vb.py +1233 -0
- mixmogam-2.0.1/mixmogam/association.py +236 -0
- mixmogam-2.0.1/mixmogam/cli.py +227 -0
- mixmogam-2.0.1/mixmogam/genotypes.py +394 -0
- mixmogam-2.0.1/mixmogam/io/__init__.py +68 -0
- mixmogam-2.0.1/mixmogam/io/_hardcalls.py +59 -0
- mixmogam-2.0.1/mixmogam/io/bgen.py +169 -0
- mixmogam-2.0.1/mixmogam/io/covariates.py +248 -0
- mixmogam-2.0.1/mixmogam/io/eigenstrat.py +68 -0
- mixmogam-2.0.1/mixmogam/io/hdf5.py +113 -0
- mixmogam-2.0.1/mixmogam/io/pgen.py +148 -0
- mixmogam-2.0.1/mixmogam/io/phenofile.py +149 -0
- mixmogam-2.0.1/mixmogam/io/plink.py +195 -0
- mixmogam-2.0.1/mixmogam/io/regmap.py +157 -0
- mixmogam-2.0.1/mixmogam/io/vcf.py +140 -0
- mixmogam-2.0.1/mixmogam/kinship.py +406 -0
- mixmogam-2.0.1/mixmogam/ldscore.py +110 -0
- mixmogam-2.0.1/mixmogam/lmm.py +1149 -0
- mixmogam-2.0.1/mixmogam/pca.py +157 -0
- mixmogam-2.0.1/mixmogam/phenotypes.py +224 -0
- mixmogam-2.0.1/mixmogam/plotting.py +110 -0
- mixmogam-2.0.1/mixmogam/results.py +440 -0
- mixmogam-2.0.1/mixmogam/scan.py +398 -0
- mixmogam-2.0.1/mixmogam/simulate.py +105 -0
- mixmogam-2.0.1/mixmogam/stage1.py +356 -0
- mixmogam-2.0.1/mixmogam/stepwise.py +259 -0
- mixmogam-2.0.1/mixmogam/twostep.py +1725 -0
- mixmogam-2.0.1/mixmogam.egg-info/SOURCES.txt +119 -0
- mixmogam-2.0.1/pyproject.toml +75 -0
- mixmogam-2.0.1/setup.cfg +4 -0
- mixmogam-2.0.1/tests/__init__.py +0 -0
- mixmogam-2.0.1/tests/_reference.py +132 -0
- mixmogam-2.0.1/tests/_retrospective.py +25 -0
- mixmogam-2.0.1/tests/conftest.py +23 -0
- mixmogam-2.0.1/tests/test_association.py +50 -0
- mixmogam-2.0.1/tests/test_calibration.py +57 -0
- mixmogam-2.0.1/tests/test_cholesky_reml.py +135 -0
- mixmogam-2.0.1/tests/test_cli.py +266 -0
- mixmogam-2.0.1/tests/test_covariates.py +158 -0
- mixmogam-2.0.1/tests/test_data.py +238 -0
- mixmogam-2.0.1/tests/test_decode_and_whitening.py +98 -0
- mixmogam-2.0.1/tests/test_efficiency_regressions.py +247 -0
- mixmogam-2.0.1/tests/test_format_dispatch.py +94 -0
- mixmogam-2.0.1/tests/test_genotype_input_memory.py +182 -0
- mixmogam-2.0.1/tests/test_hratt_binary.py +468 -0
- mixmogam-2.0.1/tests/test_hratt_crossfit.py +333 -0
- mixmogam-2.0.1/tests/test_hratt_cv_tol.py +114 -0
- mixmogam-2.0.1/tests/test_hratt_he.py +119 -0
- mixmogam-2.0.1/tests/test_hratt_parallel.py +58 -0
- mixmogam-2.0.1/tests/test_hratt_preconditioner_reuse.py +138 -0
- mixmogam-2.0.1/tests/test_hratt_weights.py +505 -0
- mixmogam-2.0.1/tests/test_integration_at.py +98 -0
- mixmogam-2.0.1/tests/test_io.py +193 -0
- mixmogam-2.0.1/tests/test_io_bgen.py +191 -0
- mixmogam-2.0.1/tests/test_io_pgen.py +147 -0
- mixmogam-2.0.1/tests/test_io_vcf.py +129 -0
- mixmogam-2.0.1/tests/test_ldak_kvik_comparison.py +121 -0
- mixmogam-2.0.1/tests/test_ldak_kvik_preparation.py +148 -0
- mixmogam-2.0.1/tests/test_lmm.py +173 -0
- mixmogam-2.0.1/tests/test_lmm_variance_only.py +155 -0
- mixmogam-2.0.1/tests/test_loco_parallel.py +355 -0
- mixmogam-2.0.1/tests/test_loco_prepared_moments.py +132 -0
- mixmogam-2.0.1/tests/test_loco_workspace.py +117 -0
- mixmogam-2.0.1/tests/test_operator.py +158 -0
- mixmogam-2.0.1/tests/test_packed.py +201 -0
- mixmogam-2.0.1/tests/test_pca.py +137 -0
- mixmogam-2.0.1/tests/test_phenofile.py +129 -0
- mixmogam-2.0.1/tests/test_predict_rows.py +144 -0
- mixmogam-2.0.1/tests/test_projected_he.py +151 -0
- mixmogam-2.0.1/tests/test_qc_memory.py +62 -0
- mixmogam-2.0.1/tests/test_results.py +198 -0
- mixmogam-2.0.1/tests/test_results_text.py +76 -0
- mixmogam-2.0.1/tests/test_review_regressions.py +383 -0
- mixmogam-2.0.1/tests/test_scale_study.py +178 -0
- mixmogam-2.0.1/tests/test_scan_extras.py +287 -0
- mixmogam-2.0.1/tests/test_slq_adaptive.py +194 -0
- mixmogam-2.0.1/tests/test_slq_defaults.py +58 -0
- mixmogam-2.0.1/tests/test_spa.py +382 -0
- mixmogam-2.0.1/tests/test_stage1.py +287 -0
- mixmogam-2.0.1/tests/test_stepwise.py +99 -0
- mixmogam-2.0.1/tests/test_twostep.py +432 -0
- mixmogam-2.0.1/tests/test_twostep_variance_only.py +88 -0
- mixmogam-2.0.1/tests/test_twostep_workspace.py +237 -0
- mixmogam-2.0.1/tests/test_vb_column_layout.py +268 -0
- mixmogam-2.0.1/tests/test_vb_covariate_corrections.py +258 -0
- mixmogam-2.0.1/tests/test_vb_gram_preparation.py +324 -0
- mixmogam-2.0.1/tests/test_vb_parallel.py +584 -0
- mixmogam-2.0.1/tests/test_vb_workspace.py +475 -0
|
@@ -0,0 +1,1259 @@
|
|
|
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
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [2.0.1] - 2026-10-29
|
|
11
|
+
|
|
12
|
+
The first release on PyPI. The package is unchanged from 2.0.0 apart from the
|
|
13
|
+
version string.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- README links are absolute GitHub URLs pinned to the `v2.0.1` tag, so they
|
|
18
|
+
resolve on the PyPI project page.
|
|
19
|
+
|
|
20
|
+
## [2.0.0] - 2026-10-29
|
|
21
|
+
|
|
22
|
+
The first stable release of the 2.0 series. Its statistical content is
|
|
23
|
+
identical to 2.0.0.dev9: this release changes the version string, the
|
|
24
|
+
packaging metadata and the provenance documentation of the bundled real-data
|
|
25
|
+
panel, removes unused API, merges duplicated code and tests, and rewrites the
|
|
26
|
+
documentation and report more plainly. No estimator, default or numerical
|
|
27
|
+
result changed, so the measurements recorded for dev8 and dev9 still describe
|
|
28
|
+
this release.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- `CITATION.cff`, and a citation and references section in the README, so the
|
|
33
|
+
software and the methods it implements can be cited without reading the
|
|
34
|
+
manuscript. Its identities come from the verified bibliography in
|
|
35
|
+
`report/references.json`.
|
|
36
|
+
- `at_data/README.md` records the provenance of the bundled *A. thaliana*
|
|
37
|
+
panel: the RegMap genotypes and Atwell phenotypes (Atwell et al. 2010;
|
|
38
|
+
Horton et al. 2012) and the TAIR10 gene annotations. Those data are third
|
|
39
|
+
party and are not covered by this repository's MIT licence, which applies to
|
|
40
|
+
the software only.
|
|
41
|
+
- Distribution metadata for a public release: PyPI classifiers, keywords, and
|
|
42
|
+
`Documentation` and `Changelog` project URLs.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `LICENSE` states the copyright years as 2013-2026 instead of 2013 alone; the
|
|
47
|
+
grant text is unchanged.
|
|
48
|
+
- Distribution metadata names `mixmogam contributors` as the author, matching
|
|
49
|
+
the manuscript's author block. The copyright holder remains named in
|
|
50
|
+
`LICENSE`.
|
|
51
|
+
- The source distribution no longer carries the stale editable-install
|
|
52
|
+
metadata in `mixmogam.egg-info/`, six files whose `PKG-INFO` still recorded
|
|
53
|
+
the previous version, and now includes `examples/` and `CITATION.cff`.
|
|
54
|
+
Setuptools still writes a freshly generated `SOURCES.txt` there during every
|
|
55
|
+
sdist build; that one is current, not stale.
|
|
56
|
+
- The README's install line lists every optional extra, and the README and
|
|
57
|
+
research report describe the released version rather than a development one.
|
|
58
|
+
- The saddlepoint tail uses one root solver and one pair of row loops for
|
|
59
|
+
both genotype models; the binomial copy is gone. P-values are bit-identical.
|
|
60
|
+
- `benchmarks/hratt_efficiency.py` and `benchmarks/ldak_kvik_comparison.py`
|
|
61
|
+
import the shared helpers from `benchmarks/_harness.py` instead of carrying
|
|
62
|
+
copies.
|
|
63
|
+
- Tests share one `numpy_fallback` fixture (`tests/conftest.py`) in place of
|
|
64
|
+
seven copies of the module-loading code. The duplicated power-guard tests
|
|
65
|
+
became one identity check, and the second seed of the strong-structure
|
|
66
|
+
refit test runs only in the slow leg.
|
|
67
|
+
- Documentation and the report are shorter: limitations are stated once, and
|
|
68
|
+
version history now lives here instead of in the README and abstract.
|
|
69
|
+
|
|
70
|
+
### Removed
|
|
71
|
+
|
|
72
|
+
- `at_data/call_method_75_info.tsv` is no longer tracked. It held 1,307 rows of
|
|
73
|
+
accession-collection metadata, including collectors' given and family names
|
|
74
|
+
with their site, region and country, and nothing in the package, its tests,
|
|
75
|
+
benchmarks or documentation read it. It remains in the repository's history;
|
|
76
|
+
rewriting history was out of scope for this release. The file stays on disk
|
|
77
|
+
for local use and is now ignored.
|
|
78
|
+
- `Genotypes.load_vcf`, `load_pgen`, `load_tped` and `load_hdf5`. Nothing
|
|
79
|
+
called or documented them, and `Genotypes.load` reads all four formats.
|
|
80
|
+
`load_plink`, `load_bgen` and `load_eigenstrat` stay.
|
|
81
|
+
- `Phenotypes.incidence_matrix` and `GwasResult.neg_log10_p`, which had no
|
|
82
|
+
caller or test.
|
|
83
|
+
- The ignored `dtype` argument of `scan_gxe` and `seed` argument of
|
|
84
|
+
`fit_two_kinships`.
|
|
85
|
+
|
|
86
|
+
### Fixed
|
|
87
|
+
|
|
88
|
+
- `benchmarks/hratt_efficiency.py` passed `--cache-bytes` as `cache_bytes`,
|
|
89
|
+
which the package renamed `gram_cache_bytes` in 02a8c3a7. The worker now
|
|
90
|
+
uses whichever name the benchmarked source accepts. Its paired mode also
|
|
91
|
+
forwards `--loco-folds` to the workers.
|
|
92
|
+
- `benchmarks/kvik_scale.py` parsed `--max-load` but never passed it to its
|
|
93
|
+
fit workers; it now gates both the reference and the mixmogam runs.
|
|
94
|
+
|
|
95
|
+
## [2.0.0.dev9] - 2026-10-29
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- Update the documentation and research report with the 29 October package
|
|
100
|
+
benchmarks, including the 100,000-sample comparison, paired cache studies,
|
|
101
|
+
convergence diagnostics and the limits of each measurement. A compact,
|
|
102
|
+
checksummed evidence bundle accompanies the report. The benchmarks used
|
|
103
|
+
2.0.0.dev8 at commit `d550c412`; this release changes documentation only.
|
|
104
|
+
|
|
105
|
+
## [2.0.0.dev8] - 2026-10-29
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
|
|
109
|
+
- `principal_components(..., solver="randomized")` provides a top-k
|
|
110
|
+
alternative to the exact full SVD (the unchanged default), with matching
|
|
111
|
+
CLI `pca --solver` and `gwas --pca-solver` options. Randomized PCs use
|
|
112
|
+
the same thinning, signs, metadata and working-memory guard.
|
|
113
|
+
- BOLT and HRATT accept `gram_cache_bytes`; a partial VB Gram cache now
|
|
114
|
+
warns instead of silently recomputing the remainder every sweep.
|
|
115
|
+
`ld_scores(..., max_memory_gib=...)` bounds dense LD-window workspaces.
|
|
116
|
+
- `Genotypes.variant_major()` makes the one-time layout conversion used by
|
|
117
|
+
analysis entry points for sample-major NumPy arrays.
|
|
118
|
+
- HRATT's `cv_tol` stops the hold-out fit of the elastic-net grid at its
|
|
119
|
+
own tolerance, and `extra["cv_converged"]` follows it. The default
|
|
120
|
+
(None: `vb_tol`) changes nothing (bit-identical p-values in all 16
|
|
121
|
+
checked configurations). `cv_tol=1e-4` is a speed option that changes the
|
|
122
|
+
estimator: about half the hold-out sweeps, and whole one-thread runs
|
|
123
|
+
1.18-1.23x faster on 5,000 x 40,000, 1.04-1.10x on 20,000 x 10,000 and
|
|
124
|
+
1.45x on the strong-structure refit panel. On 5,000 x 40,000 it changed
|
|
125
|
+
the chosen grid model in 4 of 32 runs, each time away from the converged
|
|
126
|
+
choice (log10 p moved by up to 1.45, at most one call at 0.05/m), and 37-48
|
|
127
|
+
calls in 2 of 3 strong-structure runs whose hold-out fit had not
|
|
128
|
+
converged; on 20,000 x 10,000 it changed no choice in 32 runs.
|
|
129
|
+
|
|
130
|
+
### Changed
|
|
131
|
+
|
|
132
|
+
- HRATT cross-fits can reuse the Gram budget released by the hold-out fit;
|
|
133
|
+
strong-structure refits predict only the rows held out by each fold.
|
|
134
|
+
Automatic spectral width selection evaluates rejected candidates on the
|
|
135
|
+
calibration variants, with whole-genome checks near the decision boundary
|
|
136
|
+
or under ill-conditioning. Conditional-binomial saddlepoint tails use a
|
|
137
|
+
specialized CGF, falling back to the original calculation near zero and
|
|
138
|
+
support boundaries. These alternative arithmetic orders can change the
|
|
139
|
+
last bits of scores and tail probabilities. Packed sample selection
|
|
140
|
+
gathers two-bit codes directly with Numba, and alignment avoids copying
|
|
141
|
+
its newly repacked calls a second time.
|
|
142
|
+
- Parallel VB fits (`n_threads` above one, several model columns) let
|
|
143
|
+
Numba's OpenMP workers spin for 1 ms between launches instead of
|
|
144
|
+
sleeping at once (LLVM's default on hybrid CPUs such as Apple silicon),
|
|
145
|
+
which cost 26-35 us per launch at four threads, twice per 128-SNP
|
|
146
|
+
sub-block; runtimes that already spin longer are left alone. With four
|
|
147
|
+
threads whole HRATT runs are 1.12-1.24x faster on 5,000 x 40,000 (sweeps
|
|
148
|
+
1.3x), 1.55x on 1,000 x 10,000 (strong-structure refit) and 1.01-1.03x
|
|
149
|
+
on 20,000 x 10,000, for 1.5-2x the process CPU time; two threads gain
|
|
150
|
+
1.06x, one thread is unchanged. The fit's own
|
|
151
|
+
thread restores its blocktime on exit; nothing changes under Numba's
|
|
152
|
+
other threading layers or when `KMP_BLOCKTIME` or `OMP_WAIT_POLICY` is
|
|
153
|
+
set. Bit-identical (p-values in all 16 checked configurations at one and
|
|
154
|
+
four threads).
|
|
155
|
+
- Genotype preparation (each variant's call counts, mean, SD, covariate
|
|
156
|
+
coefficients and projected norm) counts calls in one contiguous pass
|
|
157
|
+
(integer sums of int8 calls, per-byte code counts of two-bit calls) and
|
|
158
|
+
then centers eight variants at a time into 256-sample float64 rows whose
|
|
159
|
+
covariate sums stay in registers, instead of reading 64 variants a
|
|
160
|
+
sample at a time, twice. It takes 0.64 ns per genotype instead of 1.81 on
|
|
161
|
+
a 5,000 x 40,000 panel (0.82 instead of 2.50 from two-bit calls, 1.39
|
|
162
|
+
instead of 2.38 with five covariates; one thread), and whole HRATT runs
|
|
163
|
+
take 4-10% less time on 5,000 x 40,000 and 20,000 x 10,000. Each
|
|
164
|
+
variant still sums its samples in order, so results are bit-identical
|
|
165
|
+
(p-values in all 16 checked configurations, and with four threads).
|
|
166
|
+
Sample-major int8 keeps the sample-at-a-time kernel.
|
|
167
|
+
- VB fits correct each sub-block's products and updates for the covariates
|
|
168
|
+
in two Numba loops over all model columns, instead of NumPy products per
|
|
169
|
+
held-out fold, wherever those calls cost more than the arithmetic
|
|
170
|
+
(covariates x columns at most 128 per held-out fold: always in HRATT's
|
|
171
|
+
cross-fit, up to 12 covariates in its hold-out fit and 5 in the
|
|
172
|
+
strong-structure refit over 22 chromosomes). HRATT's cross-fit takes 0.75 s instead of
|
|
173
|
+
0.93 s on a 5,000 x 40,000 panel, and the refit 2.7 s instead of 3.5 s
|
|
174
|
+
on a 1,000-sample panel with strong structure; whole runs at one thread
|
|
175
|
+
are 4-7% faster on 5,000 x 40,000 (HE and REML, with or without five
|
|
176
|
+
covariates), 17% on the refit and unchanged on 20,000 x 10,000. Results
|
|
177
|
+
move at rounding level: bit-identical p-values with only an intercept
|
|
178
|
+
and no sampling weights (12 of 16 checked configurations, binary traits
|
|
179
|
+
included), up to 1.2e-13 in log10 p with weights or covariates there,
|
|
180
|
+
and up to 9e-7 on the strong-structure panel with principal components or
|
|
181
|
+
covariates, where reassociating one float64 product in the NumPy
|
|
182
|
+
corrections moves p as much. No call at 0.05/m changed. Without Numba
|
|
183
|
+
the NumPy products remain.
|
|
184
|
+
- VB fits of 2 to 32 model columns (HRATT's hold-out, cross-fitted and LOCO
|
|
185
|
+
fits; BOLT-LMM's LOCO fit) keep the residual and its updates
|
|
186
|
+
column-major, (columns, samples), so the genotype GEMMs and the residual
|
|
187
|
+
pass stream each column's samples. HRATT's two fits take 1.48 s instead
|
|
188
|
+
of 1.95 s on a 50,000 x 4,000 panel (1.55x on 50,000 x 4,096; one
|
|
189
|
+
thread, 1.13x with four), and whole HRATT runs are 2.4-7.2% faster on
|
|
190
|
+
5,000 x 40,000 and 20,000 x 10,000. Wider fits (BOLT-LMM's 90-column
|
|
191
|
+
cross-validation, HRATT's strong-structure refit) keep the sample-major
|
|
192
|
+
layout, which is faster there. Results are bit-identical with Accelerate
|
|
193
|
+
(p-values in all 16 checked configurations, and with four threads, now
|
|
194
|
+
equal to one thread at 50,000 samples too). Other BLAS builds may differ
|
|
195
|
+
at float32 GEMM rounding level, and so may Accelerate with a few hundred
|
|
196
|
+
samples, or over 16 columns in float64 storage or at 120,000 samples.
|
|
197
|
+
- HRATT's HE alpha selection computes the backward products of all
|
|
198
|
+
candidate alphas as one GEMM per sample tile instead of one per alpha.
|
|
199
|
+
The step takes 0.23 s instead of 0.29 s on a 5,000 x 40,000 panel and
|
|
200
|
+
0.33 s instead of 0.40 s on 20,000 x 10,000 (one thread); whole HE and
|
|
201
|
+
REML fits are 0.6-1.1% faster. Results are bit-identical with Accelerate
|
|
202
|
+
(p-values in all 16 checked configurations); other BLAS builds may differ
|
|
203
|
+
at rounding level. No sample tile is a single row, whose GEMV product
|
|
204
|
+
would round differently.
|
|
205
|
+
- Stochastic Lanczos REML stops once the heritability has converged instead
|
|
206
|
+
of always running 24 steps (new `slq_tol`, default 1e-6, on `LMM.fit`,
|
|
207
|
+
`hratt`, `bolt_inf` and `bolt`). After step j >= 8 it refits h2 from the
|
|
208
|
+
j-step rules (exact truncations) and stops when h2 changed by less than
|
|
209
|
+
`slq_tol` at each of the last two steps and the last three fits each found
|
|
210
|
+
a bracketed root; 24 steps stay the cap, and ML runs all of them. Without
|
|
211
|
+
the root guard, panels with more samples than variants stopped while short
|
|
212
|
+
rules held h2 on the grid end (0.99995 for a true 0.5). HRATT REML takes
|
|
213
|
+
5.9 s instead of 7.7 s on a 5,000 x 40,000 panel (9 steps) and 5.5 s
|
|
214
|
+
instead of 7.2 s on 20,000 x 10,000 (14 steps); `alpha_method="reml"`
|
|
215
|
+
11.8 s instead of 21.2 s and BOLT-LMM-inf 9.5 s instead of 11.2 s (one
|
|
216
|
+
thread). This changes a numerical default: p-values move by up to 8e-6 in
|
|
217
|
+
log10 (3.7e-5 on the strong-structure refit) and h2 by up to 3e-6, against
|
|
218
|
+
a Monte Carlo SD of 2-6e-3, and no call at 0.05/m changed in 16 checked
|
|
219
|
+
configurations. `slq_tol=0` restores the fixed 24 steps bit for bit.
|
|
220
|
+
- HRATT's cross-validation hold-out error predicts only the held-out tenth
|
|
221
|
+
of the samples, from their calls alone (`VBEngine.predict(beta,
|
|
222
|
+
rows=...)`: compiled gathers for int8 in either order and two-bit calls,
|
|
223
|
+
NumPy otherwise), instead of predicting every sample. The step takes
|
|
224
|
+
19 ms instead of 82 ms on a 5,000 x 40,000 panel and 18 ms instead of
|
|
225
|
+
87 ms on 20,000 x 10,000 (one thread; 6.5-9x with two-bit calls or
|
|
226
|
+
sampling weights); whole HE and REML fits are 1.3-2.1% faster. Results
|
|
227
|
+
are bit-identical with Accelerate (p-values in all 16 checked
|
|
228
|
+
configurations); other BLAS builds may differ at rounding level.
|
|
229
|
+
- HRATT prepares the Gram matrices of its hold-out and cross-fitted fits in
|
|
230
|
+
one pass: each sub-block is decoded once and gathered in cross-fit fold
|
|
231
|
+
order by a compiled kernel into a reused float64 buffer, and the fold
|
|
232
|
+
products, their sum and one product over the hold-out give both caches
|
|
233
|
+
(before: two decodes, two NumPy gathers and an extra all-sample product).
|
|
234
|
+
VB Grams are stored as packed upper triangles, the only part the sweeps
|
|
235
|
+
read, and HRATT's two caches omit the all-sample Gram, which neither fit
|
|
236
|
+
reads. `gram_cache_bytes` is now one budget shared by both caches,
|
|
237
|
+
filled with the hold-out Grams first. Both are held during the hold-out
|
|
238
|
+
fit: at 5,000 x 400,000 about 0.65 GB, 0.22 GB more than that fit held,
|
|
239
|
+
but below the 1 GB the cross-fit's budget-bound cache took. 1 GB holds
|
|
240
|
+
about 620,000 variants instead of 320,000, for BOLT too. Gram preparation
|
|
241
|
+
takes 0.38 s instead of 0.79 s on a 5,000 x 40,000 panel and 0.37 s
|
|
242
|
+
instead of 0.86 s on 20,000 x 10,000; whole HE and REML fits take 8-16%
|
|
243
|
+
less time (one thread), 18% with `gram_cache_bytes=1e8`, which leaves a
|
|
244
|
+
fifth of the cross-fit uncached as the default did at 400,000 variants,
|
|
245
|
+
and peak memory is up to 89 MB lower. Rounding-level: the cross-fitted
|
|
246
|
+
Grams are unchanged bit for bit, but the hold-out fit's training Gram
|
|
247
|
+
sums its products in another order, which moves the hold-out errors by
|
|
248
|
+
up to 2e-8 relative (with covariates; 1e-15 without) and could flip a
|
|
249
|
+
near-tie in the choice of prior. p-values were bit-identical in all 16
|
|
250
|
+
checked configurations, with four threads, with binding budgets and for
|
|
251
|
+
BOLT. Grams rebuilt outside the budget use the cached arithmetic, so the
|
|
252
|
+
budget still changes no result.
|
|
253
|
+
|
|
254
|
+
### Fixed
|
|
255
|
+
|
|
256
|
+
- Packed calls have compiled general decoding and packing, direct byte-table
|
|
257
|
+
QC counts, and blockwise PLINK read/write paths. QC no longer decodes a
|
|
258
|
+
packed block merely to count called genotypes and dosages; arbitrary
|
|
259
|
+
padding bits remain ignored.
|
|
260
|
+
- `realized_relationship` uses one float64 widening and symmetric rank-k
|
|
261
|
+
products. `loco_kinships` retains only the full and current-chromosome
|
|
262
|
+
intermediates rather than every chromosome at once, and
|
|
263
|
+
`GenotypeKinship` reuses four standardized call values per variant after
|
|
264
|
+
its float-panel cache is exhausted.
|
|
265
|
+
- Dense `LMM` construction validates and symmetrizes K in bounded blocks,
|
|
266
|
+
avoiding about three full-matrix copies. Two-kinship profiles reuse their
|
|
267
|
+
validated design, use eigenvalues alone for the PSD gate, and avoid
|
|
268
|
+
repeated mixture temporaries.
|
|
269
|
+
- LD scores chunk windows to a memory budget. Derived genotype containers
|
|
270
|
+
no longer revalidate inherited calls or synthesize new default IDs;
|
|
271
|
+
analysis makes one variant-major copy when repeated decoding would
|
|
272
|
+
otherwise stride through sample-major input.
|
|
273
|
+
- Two-step methods warn when Numba is unavailable rather than silently
|
|
274
|
+
taking their slow Python fallbacks. BOLT's fold mask is boolean, and
|
|
275
|
+
batched Lanczos avoids pre-zeroing fully assigned work arrays.
|
|
276
|
+
|
|
277
|
+
No statistical default or convergence threshold was changed in that review (the
|
|
278
|
+
later `slq_tol` entry above is the one exception). BOLT's
|
|
279
|
+
model-selection cross-validation, float64 full Lanczos reorthogonalization,
|
|
280
|
+
the SPA threshold, and the two-kinship mixture profile remain in place.
|
|
281
|
+
|
|
282
|
+
### Benchmarks
|
|
283
|
+
|
|
284
|
+
- The 400,000-variant, covariate and four-BLAS-thread LDAK-KVIK benchmarks were
|
|
285
|
+
rerun after the speed branch (26
|
|
286
|
+
October, master `6a63f215`) on the same panels, seeds, traits and
|
|
287
|
+
threads. Statistics reproduce everywhere (lambda_GC to four decimals,
|
|
288
|
+
the same detected causal variants, agreement rows moving in the
|
|
289
|
+
eighth decimal of Spearman at 400,000 variants and by up to 4e-5 in log10 p on
|
|
290
|
+
the 1,000 x 10,000 structured panel); on the unstructured 5,000 x 400,000 panel
|
|
291
|
+
HRATT-HE goes 34 -> 29 s (22 s under the null, now faster than the
|
|
292
|
+
reference program) and BOLT-LMM-inf 51 -> 35 s at one thread, with
|
|
293
|
+
HRATT-HE's peak RSS down about 0.5 GiB (the packed Gram cache) and
|
|
294
|
+
BOLT-LMM-inf's about 0.2 GiB; at four BLAS threads all three programs
|
|
295
|
+
are level. Archives `20261026T143800Z-kvik-scale`,
|
|
296
|
+
`20261026T144700Z-kvik-scale-blas4`,
|
|
297
|
+
`20261026T145400Z-kvik-covariates-small`,
|
|
298
|
+
`20261026T150200Z-kvik-covariates-large`,
|
|
299
|
+
`20261026T152200Z-kvik-covariates-small` and
|
|
300
|
+
`20261026T152400Z-kvik-covariates-large`, each with its before/after
|
|
301
|
+
table and the reference program's own day-to-day drift recorded as the
|
|
302
|
+
noise floor for cross-day ratios.
|
|
303
|
+
- A sample-size ladder (fixed m = 20,000, n = 1,000 to 50,000, one
|
|
304
|
+
thread, archive `20261026T161500Z-kvik-sample-scaling`) measures how
|
|
305
|
+
each method scales in samples: LDAK-KVIK and both HRATT paths are
|
|
306
|
+
linear (local exponents 0.80-0.97 over n = 20,000 to 50,000, their
|
|
307
|
+
sweep counts bounded, falling from 10-13 to 5), BOLT-LMM-inf is superlinear (1.21, its
|
|
308
|
+
conjugate-gradient count growing from 10 to 22), and `exact` is cubic
|
|
309
|
+
and stops at n = 2,500. HRATT-HE is the fastest method from
|
|
310
|
+
n = 10,000 up. Peak memory is linear in n for every streaming method.
|
|
311
|
+
- `benchmarks/DESIGN.md` maps the measured-evidence suite: the four
|
|
312
|
+
layers (invariants, resources, evidence, external comparison), what
|
|
313
|
+
each layer can support, the gaps the 19 and 25 October efficiency
|
|
314
|
+
reviews exposed, and the rules every measurement now follows (a
|
|
315
|
+
fresh process, operating-system resource counters, a stated
|
|
316
|
+
configuration, a protocol written before the first measurement).
|
|
317
|
+
- `benchmarks/scale_study.py` runs the study that closes those gaps,
|
|
318
|
+
under a protocol written before the first measurement and hashed into
|
|
319
|
+
the archive (`benchmarks/scale_study_plan.md`): fits at up to 600,000
|
|
320
|
+
variants with peak resident set size from the operating system, the
|
|
321
|
+
point where the default variational Gram-cache budget runs out,
|
|
322
|
+
invariance of the reported statistics under the memory budget, the
|
|
323
|
+
worker count, the storage format and the absence of the acceleration
|
|
324
|
+
package, type-I error, power and effect attenuation with binomial
|
|
325
|
+
intervals, and the cost of the pure NumPy fallbacks.
|
|
326
|
+
- `benchmarks/kvik_scale.py` runs the official LDAK-KVIK and mixmogam on
|
|
327
|
+
one identical 5,000 x 400,000 panel (the scale study's panel, exported
|
|
328
|
+
to PLINK and verified independently), under its own protocol
|
|
329
|
+
(`benchmarks/kvik_scale_plan.md`). The official program was as fast as
|
|
330
|
+
mixmogam's HRATT-HE (24 s against 34 s) and faster than BOLT-LMM-inf
|
|
331
|
+
(51 s) with a third of the memory; the two programs' p-values
|
|
332
|
+
correlated at 0.9991 under the null (0.998 on the mixed trait) and they detected the same 8 of 10
|
|
333
|
+
causal variants. The same driver also runs a structured, confounded
|
|
334
|
+
panel of four populations without covariates and with two and three
|
|
335
|
+
ancestry principal components, at 10,000 variants (the scale study's
|
|
336
|
+
arm B panel and trait) and at 400,000, and records what each program
|
|
337
|
+
reports about its own fit. With three components every method
|
|
338
|
+
converges and they agree, the official program included once every
|
|
339
|
+
predictor is adjusted for the covariates (its default adjusts only the
|
|
340
|
+
most significant predictors and is conservative). Without covariates,
|
|
341
|
+
or with two components that leave most of the environment in the trait
|
|
342
|
+
(at 10,000 variants they explain 42% of it), the official program
|
|
343
|
+
reports its own fit as failed and its statistics are inflated, and
|
|
344
|
+
mixmogam's variational methods do not converge; the exact path and
|
|
345
|
+
BOLT-LMM-inf do.
|
|
346
|
+
- `benchmarks/scaling_ladder.py` decomposes what a panel costs: the
|
|
347
|
+
archived HAPNEST panel at 50,000 samples, the same panel subsampled to
|
|
348
|
+
5,000 samples, and the 400,000-variant panel. Ten times fewer samples
|
|
349
|
+
make the reference program 7.1 times faster and HRATT-HE 7.0 times,
|
|
350
|
+
with the reported iteration counts falling as well, which is why a
|
|
351
|
+
panel with twenty times the variants can cost the same. The run also
|
|
352
|
+
shows that the October archives were measured on a slower host with
|
|
353
|
+
older code: the same executable on the same archived panel takes
|
|
354
|
+
20.1 s today against the archived 33.5 s.
|
|
355
|
+
- `benchmarks/_harness.py` holds the shared measurement infrastructure
|
|
356
|
+
(power guard, thread environment, source snapshot, environment
|
|
357
|
+
manifest, fresh-process measurement, p-value comparison) for the new
|
|
358
|
+
drivers, and `benchmarks/index.py` indexes the archives.
|
|
359
|
+
|
|
360
|
+
## [2.0.0.dev7] - 2026-10-25
|
|
361
|
+
|
|
362
|
+
### Added
|
|
363
|
+
|
|
364
|
+
- Standard genotype input through `Genotypes.load` and CLI `--geno-format`:
|
|
365
|
+
VCF/gzip VCF, BCF, PLINK 2 PGEN, and explicit probability-threshold BGEN
|
|
366
|
+
hard calling, alongside BED, TPED, EIGENSTRAT and HDF5. Calls retain their
|
|
367
|
+
counted allele and sample order; unsupported ploidy/multiallelic records
|
|
368
|
+
fail explicitly. Optional binary readers are in the `formats` and `bgen` extras.
|
|
369
|
+
- PLINK phenotype headers, whitespace tables and gzip, explicit headerless
|
|
370
|
+
ID columns, PLINK2 covariate headers, CSV/TSV/gzip result files, and CLI
|
|
371
|
+
`--binary-coding 1/2`. Fractional-dosage inference remains unsupported.
|
|
372
|
+
- CLI `--packed` and `--mmap` expose compact genotype storage; BED mapping
|
|
373
|
+
is recommended for repeated hard-call analyses. Format contracts and the
|
|
374
|
+
measured 25 October review are in `docs/formats.md` and
|
|
375
|
+
`docs/review-2026-10-25.md`.
|
|
376
|
+
|
|
377
|
+
### Fixed
|
|
378
|
+
|
|
379
|
+
- Genotype QC bounds temporary blocks by call count and avoids a masked
|
|
380
|
+
genotype copy. A packed slice no longer builds a genome-wide index.
|
|
381
|
+
- CLI complete-case filtering now subsets supplied covariates before
|
|
382
|
+
appending PCs. HDF5 validates hard calls before narrowing, streams packed
|
|
383
|
+
exports, and handles empty dimensions. EIGENSTRAT streams genotype rows
|
|
384
|
+
and rejects malformed calls and inconsistent variant counts.
|
|
385
|
+
- Explicit wide TSV input preserves empty cells even under an `IID`
|
|
386
|
+
header; PLINK covariates recognize -9 as missing, with an explicit
|
|
387
|
+
numeric override; PSAM preamble comments are not parsed as samples.
|
|
388
|
+
Uncompressed BGEN is rejected before a known upstream native crash.
|
|
389
|
+
- Permutation scans evaluate one F tail per permutation after finding the
|
|
390
|
+
maximum statistic. The optional LDpred3 stage-1 LD builder reuses its
|
|
391
|
+
owned float buffer; invalid block sizes fail instead of looping forever.
|
|
392
|
+
Paired numerical results were identical; timings are in the review.
|
|
393
|
+
- Normalized `GenotypeKinship` collects scaling and diagonal moments in
|
|
394
|
+
its first traversal, eliminating two standardized passes and retaining
|
|
395
|
+
only one extra sample-length vector. Ordinary HRATT cross-fitting skips
|
|
396
|
+
a full prediction/residual that its LOCO scoring does not use; CV,
|
|
397
|
+
chromosome-refitted and in-sample fits retain their predictions.
|
|
398
|
+
- `simulate_traits(..., n_causal=0)` returned an all-NaN phenotype: the QTL
|
|
399
|
+
scaling divided by the zero standard deviation of a trait with no QTLs.
|
|
400
|
+
A trait without QTLs is now purely infinitesimal and keeps the full `h2`
|
|
401
|
+
rather than `h2/2` (review 2026-10-24, L3).
|
|
402
|
+
- `simulate_genotypes` clipped allele frequencies only in its structured
|
|
403
|
+
branch, so `maf` within about 0.05 of 0 or 1 raised numpy's
|
|
404
|
+
`ValueError: p < 0, p > 1 or p contains NaNs` from `rng.binomial`. Both
|
|
405
|
+
branches now clip to [0.01, 0.99] (L4).
|
|
406
|
+
- `gwas(..., stage1_kwargs=...)` with a method other than `"hratt"` reported
|
|
407
|
+
`bolt() got an unexpected keyword argument 'stage1_kwargs'` instead of the
|
|
408
|
+
"options of method='hratt' only" message its sibling options give (L5).
|
|
409
|
+
|
|
410
|
+
### Testing
|
|
411
|
+
|
|
412
|
+
- `stage1="ldpred3-lassosum2"` is documented, offered by `mixmogam gwas
|
|
413
|
+
--stage1` and described in the changelog, but had no test at all: it was the
|
|
414
|
+
one branch of the LDpred3 stage-1 seam that no test had ever executed. Two
|
|
415
|
+
tests now cover it (determinism, finiteness, `stage1_kwargs` plumbing), and
|
|
416
|
+
CI gained an `external` job that installs ldpred3 and runs `-m external`,
|
|
417
|
+
where the whole seam's tests live (L1, L2).
|
|
418
|
+
|
|
419
|
+
### Computational efficiency
|
|
420
|
+
|
|
421
|
+
Efficiency review of 19 October (evidence: `benchmarks/results/20261019-efficiency-review`
|
|
422
|
+
and `20261020-efficiency-packed-fix`). Unless noted, statistics are identical to
|
|
423
|
+
2.0.0.dev6 on the paired benchmark panels; each noted exception is measured below.
|
|
424
|
+
|
|
425
|
+
- Decoding variant-major int8 calls selects each standardized value by
|
|
426
|
+
comparisons instead of a table gather: 0.16 instead of 0.26 ns per
|
|
427
|
+
genotype on one M2 Pro thread, in every streamed pass of the two-step
|
|
428
|
+
methods.
|
|
429
|
+
- `Genotypes.filter_samples` and `align_samples` kept the calls but made
|
|
430
|
+
them sample-major, which every later pass decoded about six times slower
|
|
431
|
+
(1 ns per genotype); they now keep variant-major calls variant-major,
|
|
432
|
+
copying in bounded variant blocks. The HDF5 and EIGENSTRAT readers
|
|
433
|
+
return variant-major calls, as the PLINK readers did.
|
|
434
|
+
- The variational coordinate kernel sweeps one model column at a time
|
|
435
|
+
with unsigned loop bounds, so the in-block Gram corrections vectorize:
|
|
436
|
+
1.2-1.7 times faster sweeps, serial and parallel kernels identical.
|
|
437
|
+
- The variational residual update tests fold labels instead of reading an
|
|
438
|
+
(n, columns) float64 mask (BOLT-LMM's cross-validation: 90 columns).
|
|
439
|
+
- Gram matrices of folds that partition the samples (HRATT's cross-fit,
|
|
440
|
+
BOLT-LMM's cross-validation) come from fold-sorted samples, the
|
|
441
|
+
all-sample Gram as their sum: no all-sample GEMM and no mask scans. The
|
|
442
|
+
summation order changes the float32 storage rounding of the Grams, which
|
|
443
|
+
moves p-values by up to 1.3e-5 in log10 for BOLT-LMM's cross-validated
|
|
444
|
+
fit and 2e-6 for HRATT-HE and HRATT-REML (verified by swapping only this
|
|
445
|
+
function back: the other changes are then identical to 2.0.0.dev6).
|
|
446
|
+
- Predictions, out-of-fold LOCO scores and the retrospective and binary
|
|
447
|
+
score statistics decode their float64 rows directly (with a sample
|
|
448
|
+
range) instead of widening float32 copies; same values and products.
|
|
449
|
+
- BOLT-LMM's LD scores compute each pair once and credit both variants:
|
|
450
|
+
about half the window products. No p-value changed on the benchmark
|
|
451
|
+
panels (checked with the Gram change reverted, as above).
|
|
452
|
+
- The exact path's REML search starts Brent's method from its three known
|
|
453
|
+
bracket values (the first step is their parabola): 13-15% fewer Cholesky
|
|
454
|
+
factorizations on the 3,000 x 44,000 panel at the same 1e-6 tolerance
|
|
455
|
+
in log delta. Against 2.0.0.dev6 the exact scan's log10 p moved by at
|
|
456
|
+
most 3.4e-6 (this and the tolerance change below, not measured apart).
|
|
457
|
+
- The exact path resolves log delta to 1e-5 instead of 1e-6 (EMMA's root
|
|
458
|
+
finder: 1e-6 in delta). Parabolic steps land well inside it: on the same
|
|
459
|
+
panel 13% fewer factorizations again (229 instead of 264), variance
|
|
460
|
+
ratios within 2e-6 in log of those at 1e-6 and log10 p within 2.6e-6.
|
|
461
|
+
A 1e-4 tolerance would have saved only 4% more.
|
|
462
|
+
- HRATT's REML builds its 128-wide deflation basis only under strong
|
|
463
|
+
structure, where the ridge solves reuse it. With ancestry covariates it
|
|
464
|
+
kept no eigenvalue on the benchmark panels, so results are identical
|
|
465
|
+
without it (the trace probes have their own stream); it cost 6 passes
|
|
466
|
+
over 140 columns, about a seventh of a REML fit. With an
|
|
467
|
+
inversion-like LD block that the structure test did not flag, h2
|
|
468
|
+
without deflation was as close to exact REML (0.002-0.006 at n = 2,000)
|
|
469
|
+
as with it.
|
|
470
|
+
|
|
471
|
+
### Fixed
|
|
472
|
+
|
|
473
|
+
- Binary and sampling-weighted result exports omit the headcount `n`,
|
|
474
|
+
which downstream sumstats readers may treat as effective sample size.
|
|
475
|
+
The API retains the headcount; explicit `n_eff` is now separate, accepts
|
|
476
|
+
nonintegral values, and survives CSV loading and variant selection.
|
|
477
|
+
- PCA checks a conservative working-memory estimate before frequency
|
|
478
|
+
scanning or allocating the float64 panel (default budget: 4 GiB,
|
|
479
|
+
configurable through the API and both CLI commands). Its SVD and
|
|
480
|
+
numerical results are unchanged within the budget.
|
|
481
|
+
- `read_regmap` streams its file in 4,096-row chunks. The bundled 250K
|
|
482
|
+
panel (1,307 x 214,051) peaked at a 21 GB footprint when every line,
|
|
483
|
+
cell and float64 value was held at once; it now reads in 37 s instead
|
|
484
|
+
of 60 s, with calls identical to the previous reader and to a per-line
|
|
485
|
+
decode. A padded missing cell (`, NA`) parses as missing instead of
|
|
486
|
+
failing, and non-calls are refused before the int8 narrowing.
|
|
487
|
+
- Benchmark power checks read the AC profile from `pmset -g custom`;
|
|
488
|
+
`pmset -g` can omit profile settings. Inactive battery settings do not
|
|
489
|
+
reject an AC-powered run, and missing AC settings fail explicitly.
|
|
490
|
+
`sim_study.py`, `run_benchmarks.py`, `structure_calibration.py`,
|
|
491
|
+
`ldak_kvik_reference.py` and `review_memory.py` use the same check;
|
|
492
|
+
the first four refused only battery power, not Low Power Mode.
|
|
493
|
+
- The report abstract retains the follow-up study's failed effect-bias
|
|
494
|
+
criterion (weighted S3: -5.4%, MCSE 2.1%) and weighted-runtime criterion
|
|
495
|
+
(1.55 times HRATT-HE versus a target of 1.3).
|
|
496
|
+
|
|
497
|
+
### Added
|
|
498
|
+
|
|
499
|
+
- `hratt(stage1=...)` / CLI `--stage1`: the step-(1e) whole-genome predictor
|
|
500
|
+
is a seam (`mixmogam.stage1`) with an optional LDpred3 implementation.
|
|
501
|
+
`ldpred3-inf` and `ldpred3-lassosum2` fit cross-fitted effects from
|
|
502
|
+
covariate-adjusted marginal statistics and in-cohort LD blocks
|
|
503
|
+
(deterministic; quantitative traits and `loco_folds >= 2` only; the
|
|
504
|
+
separate `ldpred3` package is imported only when requested and stays out
|
|
505
|
+
of the install requirements); `auto` keeps whichever candidate scores
|
|
506
|
+
better on the hold-out. The default `enet` path is unchanged, down to the
|
|
507
|
+
CSV bytes. Effects are the projected standardized row units of
|
|
508
|
+
`_out_of_fold_loco`, and the LD blocks are group-pure, so the LOCO score
|
|
509
|
+
is the genome-wide fitted value less the group's own share exactly under
|
|
510
|
+
the block-independent model. `mixmogam.stage1` documents the accepted
|
|
511
|
+
consequences (blockwise LD, second-order hyperparameter leakage,
|
|
512
|
+
cohort-shared LD across folds).
|
|
513
|
+
- `benchmarks/atwell_controls.py`: a prespecified positive-control check
|
|
514
|
+
on the bundled RegMap panel. Seven Atwell phenotypes have a known major
|
|
515
|
+
gene; a gene is found when its 50 kb window reaches 0.05/m. The 17
|
|
516
|
+
October run ([`20261017-atwell-controls`](benchmarks/results/20261017-atwell-controls/README.md))
|
|
517
|
+
found RPS5, RPM1 (twice), RPS2, FRI and HKT1 with `exact`, `bolt-inf`,
|
|
518
|
+
`hratt` and OLS with 10 PCs. No method found MOT1 on raw Mo values,
|
|
519
|
+
so criterion A1 failed. On log10 values an exploratory rerun ranked MOT1
|
|
520
|
+
first. HRATT's variational fits did not converge on the five traits
|
|
521
|
+
whose heritability estimate reached its upper bound. The report gains
|
|
522
|
+
Section 4.13, and `tests/test_integration_at.py` now requires `exact` to
|
|
523
|
+
find RPS5 and RPM1 on a partial panel (it fails when phenotypes are
|
|
524
|
+
shuffled across accessions).
|
|
525
|
+
- Optional `gwas(..., genome_build=...)` / CLI `--genome-build` declares
|
|
526
|
+
the coordinate assembly; CSV, DataFrame and variant selection preserve
|
|
527
|
+
the label without guessing or converting coordinates. The `af` contract
|
|
528
|
+
explicitly counts `effect_allele`, including frequencies above 0.5.
|
|
529
|
+
- `mixmogam.pca.principal_components`: built-in ancestry principal
|
|
530
|
+
components, the left singular vectors of a thinned marker panel
|
|
531
|
+
(default: 2,000 random variants with sample MAF >= 5%, standardized,
|
|
532
|
+
exact SVD with fixed column signs). `hratt_followups.py` uses it in its
|
|
533
|
+
"+PCs" cells instead of its own randomized approximation; the thinned
|
|
534
|
+
panels and seeds are unchanged, so only the eigensolver changed.
|
|
535
|
+
`ldak_kvik_comparison.py` keeps its bounded-memory full-panel Lanczos
|
|
536
|
+
until that comparison is next rerun.
|
|
537
|
+
- The S4 rerun with the built-in PCs
|
|
538
|
+
([`20261008-hratt-followups-builtin-pcs`](benchmarks/results/20261008-hratt-followups-builtin-pcs/README.md),
|
|
539
|
+
130 cases): F5 passed (lambda_GC 0.984-0.989; MAF 1-5% rates at 1e-3
|
|
540
|
+
0.82-0.98 times nominal, against 3.95-7.05 with pooled frequencies),
|
|
541
|
+
reproducing the 6 October S4 rows within Monte Carlo error. One case
|
|
542
|
+
segfaulted inside Apple Accelerate's `dgemm` under load and regenerates
|
|
543
|
+
cleanly (run README).
|
|
544
|
+
- `mixmogam.io.covariates.read_covariates` / `write_covariates`: a
|
|
545
|
+
covariate-file reader for the PLINK 1 `.cov` layout (FID IID plus
|
|
546
|
+
covariates, header optional) and the single-ID layout, aligned to
|
|
547
|
+
genotypes by sample ID. Missing values are rejected naming the samples.
|
|
548
|
+
- `GwasResult.n` and an `n` column in the summary-statistics CSV (the
|
|
549
|
+
headers already matched downstream sumstats column aliases; the sample
|
|
550
|
+
count was missing). `read_csv` reads `n`/`n_eff` back; older files load
|
|
551
|
+
unchanged with `n` unset.
|
|
552
|
+
- A thin `mixmogam` command line (`[project.scripts]`; also
|
|
553
|
+
`python -m mixmogam`): `mixmogam gwas` scans one trait from PLINK
|
|
554
|
+
genotypes and phenotype/covariate/weight files (`--pcs K` appends the
|
|
555
|
+
built-in PCs), `mixmogam pca` writes PC covariates. Outputs are
|
|
556
|
+
byte-identical to the same call through the API.
|
|
557
|
+
- The prespecified validation of the 2.0.0.dev6 follow-ups
|
|
558
|
+
([`20261006-hratt-followups`](benchmarks/results/20261006-hratt-followups/README.md),
|
|
559
|
+
660 cases) passed five of seven criteria.
|
|
560
|
+
- Cross-fitted effects were within 2% of the population slopes, where
|
|
561
|
+
in-sample scores were 28-36% low. Weighted effects under selection on
|
|
562
|
+
the outcome were 5.4% low, and weighted least squares 4.0%.
|
|
563
|
+
- Calibration held at 1-20% prevalence. Under ancestry-dependent weights
|
|
564
|
+
with principal components, rates for MAF 1-5% at 1e-3 were 0.85-0.96
|
|
565
|
+
times nominal, against 4.0-6.5 with pooled allele frequencies.
|
|
566
|
+
- Cross-fitting took 1.08-1.11 times as long. Weighted fits took 1.55
|
|
567
|
+
times as long as HRATT-HE, against a target of 1.3.
|
|
568
|
+
- `--loco-folds` in `hratt_thread_scaling.py` and `hratt_efficiency.py`.
|
|
569
|
+
- `--max-load` in `ldak_kvik_comparison.py`, `hratt_efficiency.py`,
|
|
570
|
+
`hratt_he_comparison.py` and `hratt_thread_scaling.py`, as in
|
|
571
|
+
`efficiency_paired.py`: each timed fit waits until the one-minute load
|
|
572
|
+
average is below the limit, and records the load and the wait.
|
|
573
|
+
|
|
574
|
+
### Removed
|
|
575
|
+
|
|
576
|
+
- The adaptive-rank arguments of `randomized_eigh_op` (`variance`, `total`,
|
|
577
|
+
`rank_cap`, `initial_k`); no caller used them and `k` is now always the
|
|
578
|
+
returned width.
|
|
579
|
+
- The ignored `max_points` argument of `plot_manhattan`.
|
|
580
|
+
- The unused inverse functions in the phenotype transformation table.
|
|
581
|
+
|
|
582
|
+
### Changed
|
|
583
|
+
|
|
584
|
+
- The tutorial's ancestry PCs come from `mixmogam.pca` (its weighted-HRATT
|
|
585
|
+
lambda_GC 1.02 to 1.03; figures unchanged), and the report's evidence
|
|
586
|
+
table records the built-in-PC rerun.
|
|
587
|
+
- The report (9 October) covers the changes of 7-8 October. Table 10 and the
|
|
588
|
+
abstract give the 20,000-variant cross-fitted fits with threaded fold
|
|
589
|
+
products (7 October: 33.2 and 29.2 s on four threads, against 34.6 and
|
|
590
|
+
31.6 s with 2.0.0.dev6); in-sample, storage and official rows are
|
|
591
|
+
unchanged. Section 2.13 describes the built-in PCs, Section 4.12 their S4
|
|
592
|
+
rerun, and the software section the command line, covariate files, the
|
|
593
|
+
CSV `n` column and the tutorial.
|
|
594
|
+
- `hratt_followups.py --scenarios` runs a subset of the selection
|
|
595
|
+
scenarios; the sampling streams are unchanged.
|
|
596
|
+
- The parallel-fit tests use a small NumPy panel instead of `phensim`, so
|
|
597
|
+
CI runs them rather than skipping 23 of them.
|
|
598
|
+
- Corrected the whitening docstring (the dropped spectrum is scaled at its
|
|
599
|
+
mean), the six-column threshold in `docs/design.md`, the cross-fitting
|
|
600
|
+
test introduction and the `hratt_efficiency.py` comparison note.
|
|
601
|
+
|
|
602
|
+
- Benchmark drivers are named by what they compare:
|
|
603
|
+
- `ldak_kvik_comparison.py` (was `kvik_simulation.py`) and
|
|
604
|
+
`ldak_kvik_reference.py` run official LDAK-KVIK;
|
|
605
|
+
- `hratt_efficiency.py`, `hratt_he_comparison.py`,
|
|
606
|
+
`hratt_he_validation.py` and `hratt_thread_scaling.py` time or check
|
|
607
|
+
HRATT, with LDAK-KVIK optional;
|
|
608
|
+
- likewise their plans, tests and `report/make_ldak_kvik_figures.py`.
|
|
609
|
+
|
|
610
|
+
Archives keep the names they ran with.
|
|
611
|
+
- The report's matched LDAK-KVIK comparisons, HAPNEST workloads and
|
|
612
|
+
HE-versus-REML fits are reruns with 2.0.0.dev6 (`20261006-*-dev6`), each
|
|
613
|
+
timed fit gated on the host load. Exact LOCO and BOLT-inf results are
|
|
614
|
+
unchanged; HRATT's cross-fitted scores changed 0.3-1.8% of its decisions
|
|
615
|
+
at 1% and its rejection rates by at most 0.2 percentage points. On one
|
|
616
|
+
host and day, 2.0.0.dev6 took 1.07 (exact LOCO) and 1.03 (HRATT) times as
|
|
617
|
+
long as commit e4089d8 at n = 800
|
|
618
|
+
([`20261006-same-day-e4089d8-dev6`](benchmarks/results/20261006-same-day-e4089d8-dev6/README.md)).
|
|
619
|
+
- The 20,000-variant workload was rerun with 2.0.0.dev6
|
|
620
|
+
([`20261006-kvik-20k-dev6`](benchmarks/results/20261006-kvik-20k-dev6/README.md)),
|
|
621
|
+
with cross-fitted and with in-sample scores. Cross-fitting took 1.28-1.38
|
|
622
|
+
times the in-sample time on one thread and 1.53-1.59 on four: its five
|
|
623
|
+
fold fits stayed below the six columns from which the variational sweep
|
|
624
|
+
threaded its genotype products (`_vb._GEMM_MIN_COLUMNS`), so four threads
|
|
625
|
+
gained 1.27-1.33-fold instead of 1.47-1.57, and one and four threads gave
|
|
626
|
+
identical association results. The next item removes that threshold.
|
|
627
|
+
- The variational sweep threads its genotype products from 50,000 samples
|
|
628
|
+
whatever the number of model columns; `_vb._GEMM_MIN_COLUMNS` (six) is
|
|
629
|
+
removed. Six was the smallest shape probed when it was set, not a
|
|
630
|
+
break-even: at 50,000 samples, threaded products sped fitted sweeps up
|
|
631
|
+
1.31-1.40-fold for two to eight columns alike (1.04-1.08 at 25,000).
|
|
632
|
+
HRATT's cross-fitted fold fits now use them. In a rerun of the
|
|
633
|
+
20,000-variant workload
|
|
634
|
+
([`20261007-kvik-20k-crossfit-gemm`](benchmarks/results/20261007-kvik-20k-crossfit-gemm/README.md)),
|
|
635
|
+
four threads sped cross-fitted fits up 1.40 and 1.48 times instead of 1.27
|
|
636
|
+
and 1.33, and in same-day pairs four-thread fits took 0.90-0.94 times as
|
|
637
|
+
long as with 2.0.0.dev6. One-thread results are unchanged bit for bit.
|
|
638
|
+
Four-thread cross-fitted results now differ from one-thread ones by
|
|
639
|
+
rounding, as in-sample results already did (up to 1.3e-7 in effects and
|
|
640
|
+
1.3e-5 in p), with identical decisions at 0.05, 0.01, 0.001, Bonferroni
|
|
641
|
+
and 5e-8.
|
|
642
|
+
- `HRATT_ALPHAS` is in `twostep.__all__`, as `HRATT_GRID` is.
|
|
643
|
+
- Docstrings corrected: the stochastic trace estimator estimates tr f(A),
|
|
644
|
+
not tr f(A) / n; complete orthogonal HE probes need squared norm df to
|
|
645
|
+
give tr(K²) exactly; exact LMM scans take one GEMM per SNP block; window
|
|
646
|
+
kinships reconstruct the full GRM only weighted by their variant counts;
|
|
647
|
+
`simulate_traits`'s `u` is the total genetic value; two-kinship fits need
|
|
648
|
+
positive semidefinite kinships, which a scaled IBS matrix generally is
|
|
649
|
+
not. Public methods without docstrings now have them.
|
|
650
|
+
|
|
651
|
+
### Fixed
|
|
652
|
+
|
|
653
|
+
- `Phenotypes.transform(..., revert=True)` after two transformations
|
|
654
|
+
restored the values before the last one and labelled them untransformed.
|
|
655
|
+
Transformations, including `box_cox`, now keep the untransformed values,
|
|
656
|
+
which a revert restores.
|
|
657
|
+
- Saddlepoint tails of weighted and binary HRATT ran on every Numba worker
|
|
658
|
+
whatever `n_threads`; they now use at most `n_threads`, with unchanged
|
|
659
|
+
values.
|
|
660
|
+
|
|
661
|
+
## [2.0.0.dev6] - 2026-10-06
|
|
662
|
+
|
|
663
|
+
### Fixed
|
|
664
|
+
|
|
665
|
+
- Refine the last Cholesky REML search interval before accepting its
|
|
666
|
+
boundary; the missed interior optimum made exact LOCO results change
|
|
667
|
+
when chromosome labels were renamed.
|
|
668
|
+
- Weighted/binary HRATT saddlepoint tails use sample-specific genotype
|
|
669
|
+
distributions with covariates, conditional on the missing-call mask.
|
|
670
|
+
Correcting only the pooled distribution's variance left extreme tails
|
|
671
|
+
inflated. The conditional model assumes Hardy-Weinberg equilibrium.
|
|
672
|
+
- BOLT mixture results report infinitesimal-model effects and standard
|
|
673
|
+
errors (`extra["effect_method"]`), removing attenuation from slopes
|
|
674
|
+
against in-sample mixture residuals while retaining the mixture test.
|
|
675
|
+
- Weighted/binary standard errors no longer depend on the saddlepoint
|
|
676
|
+
tail rule. Binary effects remain one-step approximations: CSV/DataFrame
|
|
677
|
+
labels are `beta_one_step` and `se_null_score`; CSV reload retains this
|
|
678
|
+
interpretation and `posterior_probabilities()` rejects them.
|
|
679
|
+
- Two-kinship profiles include weights zero and one, reject nonidentifiable
|
|
680
|
+
covariance components, and return `weight=None` with zero shares when
|
|
681
|
+
the ordinary linear model wins. Only the best candidate fit is retained.
|
|
682
|
+
- Genotypic tests reject covariate-confounded contrasts consisting only
|
|
683
|
+
of projection round-off, rather than treating them as extra degrees of
|
|
684
|
+
freedom.
|
|
685
|
+
- EIGENSTRAT imports preserve both supplied allele columns through PLINK
|
|
686
|
+
export instead of replacing them with synthetic labels.
|
|
687
|
+
- `Phenotypes.most_normal()` respects transformation domains and excludes
|
|
688
|
+
constant results; counts above one cannot collapse under arcsine-square-root.
|
|
689
|
+
|
|
690
|
+
### Added
|
|
691
|
+
|
|
692
|
+
- HRATT for case-control outcomes and sampling weights:
|
|
693
|
+
`gwas(..., method="hratt", trait="binary")` and `sample_weights=w`
|
|
694
|
+
(e.g. inverse probabilities of participation), alone or together. Step 1
|
|
695
|
+
fits a row-scaled weighted model (the logistic working response for binary
|
|
696
|
+
traits); with weights, heritability comes from HE with design-consistent
|
|
697
|
+
moments. Step 2 tests each score against its variance under genotype
|
|
698
|
+
exchangeability, with a saddlepoint approximation of the genotype
|
|
699
|
+
distribution above |z| = 2 (`spa_threshold`); binary scores are those of
|
|
700
|
+
the logistic model with the LOCO score as offset, and effects one-step log
|
|
701
|
+
odds ratios. The Huber-White sandwich and the model-based logistic
|
|
702
|
+
variance are not used: in pilots the sandwich score test was inflated
|
|
703
|
+
17-fold at 1e-3 for MAF 1-5% under outcome-dependent selection, against
|
|
704
|
+
0.75-fold for the retrospective test, and with HRATT's offsets the
|
|
705
|
+
model-based logistic variance gave lambda_GC 0.55 with 50 cases in 5,000,
|
|
706
|
+
against 1.01-1.04. New extras of weighted or binary fits: `trait`,
|
|
707
|
+
`heritability_method`, `spa_threshold`, `n_spa`; with weights
|
|
708
|
+
`kish_n` and `design_effect`; for binary traits `n_cases`, `n_controls`,
|
|
709
|
+
`prevalence`, `offset_sd`, `null_converged`, `mu0_clipped`. `gwas` rejects
|
|
710
|
+
`trait`, `sample_weights` and `spa_threshold` for other methods.
|
|
711
|
+
Unweighted quantitative HRATT is unchanged, bit for bit. Validation
|
|
712
|
+
([`20261005-hratt-weights-binary`](benchmarks/results/20261005-hratt-weights-binary/README.md),
|
|
713
|
+
prespecified, 30 null and 10 mixed replicates per cell): two of seven
|
|
714
|
+
criteria passed. Weighted quantitative tests were calibrated, including
|
|
715
|
+
under selection on the outcome (the sandwich: 12-fold at 1e-3); with
|
|
716
|
+
weights that depend on ancestry they were anti-conservative for MAF 1-5%
|
|
717
|
+
(5.75-fold at 1e-3); binary per-tail rates were lopsided at 1-5%
|
|
718
|
+
prevalence with near-nominal two-sided rates; 2 of 30 replicates at 1%
|
|
719
|
+
prevalence stopped with a separation error.
|
|
720
|
+
|
|
721
|
+
- `spa_two_sided="doubled"` doubles the saddlepoint tail beyond the
|
|
722
|
+
observed score, LDAK's default, putting half the level in each tail of a
|
|
723
|
+
skewed null. The default, `"distance"`, adds both tails at +-|u|, as
|
|
724
|
+
SPAtest, SAIGE and REGENIE do. `gwas` rejects `loco_folds` and
|
|
725
|
+
`spa_two_sided` for methods other than HRATT.
|
|
726
|
+
|
|
727
|
+
### Changed
|
|
728
|
+
|
|
729
|
+
- HRATT's LOCO scores are cross-fitted (`loco_folds=5`, new): each fold's
|
|
730
|
+
predictor excludes that fold's outcomes, while variance components and
|
|
731
|
+
model selection still use the whole trait.
|
|
732
|
+
- Five genome-wide variational fits run, each on four fifths of the
|
|
733
|
+
samples (folds stratified by case status for binary traits).
|
|
734
|
+
- A sample's score for chromosome group g is its own fold's fit without
|
|
735
|
+
group g's variants, as in REGENIE's level 0.
|
|
736
|
+
- Under strong structure each group is refitted instead. Splitting a
|
|
737
|
+
genome-wide fit leaves ancestry in the residual: at Fst 0.15 without
|
|
738
|
+
principal components, the null chromosome's lambda_GC was 1.5-4.4 split
|
|
739
|
+
and 0.9-1.1 refitted.
|
|
740
|
+
- Each group's score enters with its out-of-fold calibration slope, kept
|
|
741
|
+
in [0, 1] (`extra["offset_slope"]`).
|
|
742
|
+
- In-sample scores absorbed part of every effect: in simulations, HRATT's
|
|
743
|
+
effects were 0.59-0.69 of the truth and are now 0.95-1.0, at equal
|
|
744
|
+
power.
|
|
745
|
+
- At 1% prevalence, in-sample scores gave cases offsets of tens of logit
|
|
746
|
+
units (+43 against -0.4 for controls on a null trait). This is what
|
|
747
|
+
stopped 2 of 30 replicates of the 5 October validation; that case now
|
|
748
|
+
runs with lambda_GC 1.04.
|
|
749
|
+
- `loco_folds=1` restores in-sample scores, bit for bit for unweighted
|
|
750
|
+
traits.
|
|
751
|
+
- In a one-replicate pilot at 5,000 samples, 20,000 markers and ten
|
|
752
|
+
groups, the cross-fitted fits took a median 1.11 times as long. The
|
|
753
|
+
prespecified validation (`benchmarks/hratt_followups_plan.md`) is
|
|
754
|
+
running; its archive will follow.
|
|
755
|
+
- The retrospective tests of weighted and binary HRATT take each sample's
|
|
756
|
+
allele frequency from the covariates, as SPAmix does.
|
|
757
|
+
- Deviations from the mean are shrunk by the fit's F statistic, so
|
|
758
|
+
covariates unrelated to genotype leave the old variance.
|
|
759
|
+
- The score variance becomes zvar |a|^2 rho_j.
|
|
760
|
+
- With ancestry principal components among the covariates, weights that
|
|
761
|
+
depend on ancestry gave low-frequency rejection rates of 1.2-1.4-fold
|
|
762
|
+
at 1e-3 for MAF 1-5% in a prototype of the S4 scenario. The pooled
|
|
763
|
+
variance gave 6.7-7.2-fold even with the PCs.
|
|
764
|
+
- HRATT warns when strong structure remains after the covariates in
|
|
765
|
+
weighted or binary analyses.
|
|
766
|
+
- Binary null fits take the cross-fitted score as a covariate, its
|
|
767
|
+
coefficient kept in [0, 1]:
|
|
768
|
+
- a fixed offset above one;
|
|
769
|
+
- dropped if it anti-predicts or its fit diverges (`offsets_dropped`),
|
|
770
|
+
instead of stopping the analysis.
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
- `heritability_method` defaults to `"auto"` (was `"reml"`): REML without
|
|
774
|
+
sampling weights, HE with them.
|
|
775
|
+
|
|
776
|
+
- The KVIK-style method is now HRATT, the Heritability-weighted Residual
|
|
777
|
+
Association Two-step Test: `gwas(..., method="hratt")`,
|
|
778
|
+
`twostep.hratt`, `HRATT_GRID` and `HRATT_ALPHAS`, and results report
|
|
779
|
+
`extra["method"] == "hratt"`. HRATT is inspired by LDAK-KVIK (Hof & Speed
|
|
780
|
+
2025) and follows its two-step design, but its variance fitting,
|
|
781
|
+
variational sweep and calibration solves are this package's own and its
|
|
782
|
+
results differ from the reference program's, so it no longer carries that
|
|
783
|
+
name. `method="kvik"`, `twostep.kvik`, `KVIK_GRID` and `KVIK_ALPHAS` are
|
|
784
|
+
removed without aliases; archived benchmark runs keep the old name.
|
|
785
|
+
|
|
786
|
+
### Removed
|
|
787
|
+
|
|
788
|
+
- `cache_bytes` from `bolt_inf`, `bolt`, `hratt` and `LocoGenotypes`: the
|
|
789
|
+
float copy of the standardized genotypes is gone, and every pass decodes
|
|
790
|
+
them. The cache only helped data small enough to fit its 4e9-byte budget
|
|
791
|
+
(paired fits: [`20261004-packed-calls`](benchmarks/results/20261004-packed-calls/README.md);
|
|
792
|
+
final comparison under Benchmarks).
|
|
793
|
+
|
|
794
|
+
### Memory
|
|
795
|
+
|
|
796
|
+
- Calls can be stored two bits each, a quarter of the int8 bytes:
|
|
797
|
+
`Genotypes(G, packed=True)`, or `read_plink(prefix, packed=True)` to hold
|
|
798
|
+
the bed's codes in memory and `mmap=True` to map them from the file.
|
|
799
|
+
`PackedCalls` keeps PLINK's bed layout; indexing returns int8 calls,
|
|
800
|
+
filters keep the calls packed and `write_plink` copies the codes. The
|
|
801
|
+
two-step engines read the codes directly, with results identical to int8
|
|
802
|
+
storage. Preparation now takes each variant's mean and variance from exact
|
|
803
|
+
call counts, the same for every thread count, layout and storage format;
|
|
804
|
+
prepared values move by floating-point rounding.
|
|
805
|
+
- Prepared genotypes are never stored covariate-projected. Preparation keeps
|
|
806
|
+
each variant's three standardized call values, its covariate coefficients
|
|
807
|
+
c = z Q and the squared norm of its projected values. Operator products
|
|
808
|
+
project their n x c operands instead; variational sweeps keep their
|
|
809
|
+
residual unprojected and correct each 128-variant block's products by
|
|
810
|
+
k x q terms; Gram matrices, the HE diagonal and LD scores add rank-q
|
|
811
|
+
corrections to products of unprojected rows. No pass projects variants.
|
|
812
|
+
- With the `fast` extra, one compiled kernel prepares the variants for every
|
|
813
|
+
thread count and storage order, over 64-variant tiles, with identical
|
|
814
|
+
values, including the one-thread default. Decoding follows the storage
|
|
815
|
+
order: one M2 Pro thread decodes about 0.26 ns per genotype from
|
|
816
|
+
variant-major (PLINK) storage and 0.7 ns from sample-major arrays, against
|
|
817
|
+
0.6 and 3.7 ns in 2.0.0.dev5.
|
|
818
|
+
- One-pass consumers decode 16 MiB slices (at least 256 variants, at most
|
|
819
|
+
512 MiB) into reused buffers.
|
|
820
|
+
- BOLT-LMM's in-sample LD scores stream each chromosome through a sliding
|
|
821
|
+
position window. They held every projected genotype (4 bytes per genotype)
|
|
822
|
+
whatever `cache_bytes`.
|
|
823
|
+
- The spectral denominator projects one LOCO basis at a time.
|
|
824
|
+
- Variational Gram matrices are stored in the genotype precision (half the
|
|
825
|
+
bytes with float32 storage), and blocks are cached in order up to the
|
|
826
|
+
1e9-byte budget instead of all or none; the rest are recomputed each sweep
|
|
827
|
+
for the folds that fit uses, so a full-data fit after cross-validation
|
|
828
|
+
rebuilds one Gram per block rather than one per fold. Paired fits
|
|
829
|
+
([`20261004-gram-cache`](benchmarks/results/20261004-gram-cache/README.md)):
|
|
830
|
+
HRATT-HE at 2,000 samples and 300,000 markers peaked 0.27 GiB lower, and
|
|
831
|
+
BOLT-LMM's five-fold Grams, 1.87 GB in float64 and so recomputed every
|
|
832
|
+
sweep, now fit (0.95 GB); the cache makes those cross-validation fits
|
|
833
|
+
1.5 (BOLT-LMM) and 2.5 (HRATT) times faster.
|
|
834
|
+
- HRATT and BOLT-LMM release the cross-validation fit's effects, residuals and
|
|
835
|
+
masks once its scores are taken, and the LOCO fit's effects and the
|
|
836
|
+
variational engine's Gram cache once its residuals are extracted.
|
|
837
|
+
BOLT-LMM's cross-validation held 90 float64 effect columns (720 bytes per
|
|
838
|
+
variant) through the LOCO fit.
|
|
839
|
+
|
|
840
|
+
### Benchmarks
|
|
841
|
+
|
|
842
|
+
- Paired fits of 2.0.0.dev5 against 12aa504
|
|
843
|
+
([`20261004-genotype-streaming`](benchmarks/results/20261004-genotype-streaming/README.md),
|
|
844
|
+
idle host, two fresh-process repetitions; final numbers below). Report
|
|
845
|
+
Sections 2.11 and 4.10 describe the method and results;
|
|
846
|
+
`efficiency_paired.py` gained the BOLT-LMM, uncached and marker-scaling
|
|
847
|
+
workloads.
|
|
848
|
+
- Rerun the mixmogam methods of the matched phensim (n = 800, 2,000 and
|
|
849
|
+
4,000), HAPNEST (10K and 50K), HE-versus-REML and 20,000-marker benchmarks
|
|
850
|
+
with 2.0.0.dev5, on their archived inputs. Official LDAK-KVIK outputs are
|
|
851
|
+
reused after the inputs are verified by hash. `kvik_simulation.py
|
|
852
|
+
--rerun-from` and a `--methods` option in `kvik_thread_scaling.py` and
|
|
853
|
+
`kvik_he_comparison.py` support this.
|
|
854
|
+
- A same-day 2.0.0.dev4/dev5 check at 50,000 samples found no memory change
|
|
855
|
+
and REML fits 1.57 times faster. The HE path was unchanged. Identical
|
|
856
|
+
dev4 code took 27% less time than on the day of the original runs, so most of
|
|
857
|
+
the differences from those archives in time and peak RSS reflect host
|
|
858
|
+
conditions.
|
|
859
|
+
- Rerun the same benchmarks with commit e4089d8 (`20261005-*-e4089d8`); the
|
|
860
|
+
report now uses these reruns. At 50,000 samples and 20,000 markers,
|
|
861
|
+
four-thread HRATT-HE peaked at 1.4 GiB instead of 5.1, or 0.73 GiB with
|
|
862
|
+
two-bit calls (official LDAK-KVIK: 0.65). A same-day check against dev5
|
|
863
|
+
([`20261005-same-day-dev5-e4089d8`](benchmarks/results/20261005-same-day-dev5-e4089d8/README.md))
|
|
864
|
+
found four-thread HE 17% faster and one-thread REML 1.2 times slower
|
|
865
|
+
without the cache. Across the reruns, rejection rates, power and QTL
|
|
866
|
+
detection are unchanged, and log10 p moved by at most 1.8e-4 (same-day
|
|
867
|
+
check: 1.8e-5). One- versus four-thread differences
|
|
868
|
+
on the PC-adjusted 20K panel grew tenfold (up to 2.0e-5 in p), with
|
|
869
|
+
unchanged decisions. The 20K cache experiment is now an int8 against
|
|
870
|
+
two-bit storage experiment: `kvik_efficiency.py` and
|
|
871
|
+
`kvik_thread_scaling.py` gained `--storage`.
|
|
872
|
+
- `efficiency_paired.py` compares any number of labelled sources
|
|
873
|
+
(`--source LABEL=PATH`), skips workloads a source cannot run (the float
|
|
874
|
+
genotype cache, two-bit calls) instead of failing or running a fit twice,
|
|
875
|
+
calls older sources by the method's earlier name, rotates the source order
|
|
876
|
+
over repetitions, and with `--max-load` starts each measurement only below
|
|
877
|
+
a given load average; it records the load and the wait. dev5 against
|
|
878
|
+
commit d51b4c3, three gated repetitions
|
|
879
|
+
([`20261005-efficiency-paired-hratt`](benchmarks/results/20261005-efficiency-paired-hratt/README.md)):
|
|
880
|
+
the two-step paths need 2.4 to 2.8 times less memory with int8 calls and
|
|
881
|
+
3.1 to 4.2 times less with two-bit calls; HRATT-HE runs 4% (one thread)
|
|
882
|
+
and 14% (four threads) faster, BOLT-LMM-inf, BOLT-LMM and HRATT with REML
|
|
883
|
+
1.19 to 1.23 times slower; HRATT-HE's peak RSS grows by 0.36 bytes per
|
|
884
|
+
genotype with two-bit calls against 4.06 with dev5's cache. Report
|
|
885
|
+
Tables 15 and 16 now come from this archive. The Lanczos study, rerun
|
|
886
|
+
with commit e4089d8
|
|
887
|
+
([`20261005-slq-defaults-e4089d8`](benchmarks/results/20261005-slq-defaults-e4089d8/README.md)),
|
|
888
|
+
reproduces dev5's to 5e-7 and renders Table 14.
|
|
889
|
+
|
|
890
|
+
## [2.0.0.dev5] - 2026-10-04
|
|
891
|
+
|
|
892
|
+
### Computational efficiency
|
|
893
|
+
|
|
894
|
+
Archived paired fits against 2.0.0.dev4
|
|
895
|
+
([`20261004-efficiency-paired`](benchmarks/results/20261004-efficiency-paired/README.md):
|
|
896
|
+
fresh processes, alternating order, two repetitions, M2 Pro under unrelated
|
|
897
|
+
background load): exact LOCO at n = 3,000 and m = 44,000 took 13.3 s instead
|
|
898
|
+
of 79.6 s; bolt-inf at n = 10,000 and m = 30,000 7.3 s instead of 12.9 s;
|
|
899
|
+
KVIK with REML 9.8 s instead of 14.4 s; MLMM at n = 2,000 with ten steps
|
|
900
|
+
5.5 s instead of 27.4 s; uncached KVIK-HE at n = m = 20,000 with four threads
|
|
901
|
+
peaked at 0.93 GiB instead of 1.14 GiB with identical associations. The new
|
|
902
|
+
probe count and calibration draw change two-step results within their Monte
|
|
903
|
+
Carlo error (up to 0.07 in log10 p at p = 4e-31); the exact scan agrees to
|
|
904
|
+
3e-5 in log10 p. The bullets give each change's own development measurement.
|
|
905
|
+
|
|
906
|
+
- Exact LOCO scans fit each group's REML variance ratio through Cholesky
|
|
907
|
+
factorizations of K_{-g} + delta I and whiten SNPs by triangular solves,
|
|
908
|
+
instead of one eigendecomposition per group. LAPACK's eigensolvers used
|
|
909
|
+
about 1.2 cores here; at n = 3,000 with 22 chromosome groups the scan
|
|
910
|
+
took 14.5 s instead of 79 s. Delta agrees with the EMMA fit to 1e-6 and
|
|
911
|
+
log10 p to 1.2e-7 (float64). Results add `variance_solver` and
|
|
912
|
+
`reml_factorizations`; `LMM` itself keeps its eigendecomposition.
|
|
913
|
+
- Stochastic Lanczos REML defaults to 24 steps and 48 probes (was 96 and
|
|
914
|
+
12). Each step is a genotype pass whose cost hardly depends on the column
|
|
915
|
+
count up to about 64, and 24 steps reproduced 96 to five digits across
|
|
916
|
+
unstructured, structured, high-heritability and fewer-markers-than-samples
|
|
917
|
+
spectra. The probes carried the error: at n = 500 with strong structure
|
|
918
|
+
the h2 SD over probe seeds fell from 0.12 (one error of 0.38) to 0.02
|
|
919
|
+
([`20261004-slq-defaults`](benchmarks/results/20261004-slq-defaults/README.md)).
|
|
920
|
+
bolt-inf at n = 10,000 and m = 30,000 took 8.5 s instead of 12.7 s.
|
|
921
|
+
Estimates change within their Monte Carlo error. Probes that a covariate
|
|
922
|
+
projection reduces to rounding noise are dropped.
|
|
923
|
+
- BOLT-LMM, BOLT-LMM-inf and KVIK under strong structure compute one
|
|
924
|
+
randomized kinship basis for both the REML deflation and the CG
|
|
925
|
+
preconditioner, and solve the calibration SNPs in the same CG run as the
|
|
926
|
+
LOCO residuals. Calibration candidates are now drawn before the residuals
|
|
927
|
+
and filtered by chi2 < 5 afterwards (the same distribution), so a given
|
|
928
|
+
`random_state` selects different SNPs than before. bolt-inf at n = 10,000
|
|
929
|
+
took 7.4 s instead of 8.5 s.
|
|
930
|
+
- Relationship matrices standardize genotype blocks in one fused Numba
|
|
931
|
+
pass (1.6 times faster serially, 5 times with six threads, same values to
|
|
932
|
+
rounding of the moments). `gwas(method="exact")` accepts `n_threads` for
|
|
933
|
+
parallel standardization and block conversion with identical results;
|
|
934
|
+
n = 3,000 exact LOCO took 12.9 s serially and 11.8 s with six threads.
|
|
935
|
+
- `LMM` scans whiten in eigen coordinates, one GEMM per SNP block instead
|
|
936
|
+
of two (1.5 times faster in float32, 1.7 in float64, and closer to the
|
|
937
|
+
float64 statistics in float32); permutations keep sample coordinates and
|
|
938
|
+
so their per-seed results. Eigenvectors come from the divide-and-conquer
|
|
939
|
+
driver (23-42% faster than MRRR, MRRR as fallback) and are stored
|
|
940
|
+
contiguously in descending order.
|
|
941
|
+
- Uncached two-step passes (`cache_bytes` below the float32 cache size)
|
|
942
|
+
decode hard calls through per-variant value tables and project covariates
|
|
943
|
+
in the preparation route's own arithmetic, so streamed blocks still equal
|
|
944
|
+
cached ones bit for bit; operator products instead project their n x c
|
|
945
|
+
operands once. Decoded slices and VB subblocks reuse buffers. An uncached
|
|
946
|
+
kinship pass at n = 10,000 and m = 30,000 took 128 ms instead of 1,033 ms
|
|
947
|
+
(cached 64 ms). KVIK-HE at n = m = 20,000 with four threads peaked at
|
|
948
|
+
0.97 GiB uncached against 2.26 GiB cached, 16% slower with identical
|
|
949
|
+
associations; uncached bolt-inf agrees with cached to 1.4e-5 in log10 p.
|
|
950
|
+
- `mlmm` rotates the SNPs into the kinship's eigen coordinates once (new
|
|
951
|
+
`cache_bytes`, default 4e9; `block`) and rescales them for each forward
|
|
952
|
+
scan, O(n m q) per step instead of O(n^2 m). Forward scans default to
|
|
953
|
+
float32, like `gwas` (was float64); cofactor tests and likelihoods stay
|
|
954
|
+
float64. At n = 2,000, m = 50,000 and ten forward steps it took 5.5 s
|
|
955
|
+
instead of 27.2 s (8.9 s with `dtype=np.float64`), selecting the same
|
|
956
|
+
cofactors.
|
|
957
|
+
- Archive the paired comparison with 2.0.0.dev4 and the Lanczos
|
|
958
|
+
steps-versus-probes study, with their drivers `benchmarks/efficiency_paired.py`
|
|
959
|
+
and `benchmarks/slq_defaults.py`, and the release checks in
|
|
960
|
+
`20261004-release-dev5`.
|
|
961
|
+
- Describe the methods in a new manuscript Section 2.10, report the paired
|
|
962
|
+
fits and Lanczos accuracy in two tables rendered by
|
|
963
|
+
`report/make_dev5_tables.py`, and update the README, quickstart and design
|
|
964
|
+
notes.
|
|
965
|
+
|
|
966
|
+
## [2.0.0.dev4] - 2026-10-03
|
|
967
|
+
|
|
968
|
+
### KVIK computational efficiency
|
|
969
|
+
|
|
970
|
+
- Add explicit `n_threads` for parallel Numba updates across independent
|
|
971
|
+
KVIK candidate models and LOCO fits, preserving within-model update and
|
|
972
|
+
reduction order. Restore the caller's thread mask after fitting.
|
|
973
|
+
- Parallelize optional genotype preparation over independent variants with
|
|
974
|
+
float64 two-pass variance and projection, bounded private worker buffers,
|
|
975
|
+
and the unchanged one-thread NumPy default.
|
|
976
|
+
- Reuse projection coefficients for direct, parallel hard-call decoding;
|
|
977
|
+
uncached variational sweeps decode only their current small SNP block.
|
|
978
|
+
- Use contiguous BLAS inputs and parallel sample slices for large residual
|
|
979
|
+
matrix products, with reusable workspaces and restored BLAS thread limits.
|
|
980
|
+
- Bound hard-call validation, uint8 missing-code conversion and packed BED
|
|
981
|
+
input scratch instead of allocating masks or retaining packed inputs for
|
|
982
|
+
the complete dataset.
|
|
983
|
+
- Reuse full-data Gram matrices and final predictions between cross-validation
|
|
984
|
+
and LOCO; compute only the held-out Gram slices actually requested. Reuse
|
|
985
|
+
the spectral preconditioner in the strong-structure calibration path.
|
|
986
|
+
- Reuse variational fitting buffers and fuse residual bookkeeping with Numba,
|
|
987
|
+
retaining float64 residuals, the existing posterior updates and convergence
|
|
988
|
+
rule, and the NumPy fallback.
|
|
989
|
+
- Bound large temporary arrays in standardization, prediction, retrospective
|
|
990
|
+
statistics and HE diagonal calculation. Genotype precision and fitting
|
|
991
|
+
defaults remain unchanged.
|
|
992
|
+
- Add a paired time/RSS comparison against frozen pre-optimization source on
|
|
993
|
+
existing phensim HAPNEST inputs, with source/input hashes and full numerical
|
|
994
|
+
and convergence comparisons; see `benchmarks/kvik_efficiency.py`.
|
|
995
|
+
- Add `heritability_method="he"` to KVIK: reuse the alpha scan's products for
|
|
996
|
+
a covariance-projected, nonnegative two-component moment fit. Report raw
|
|
997
|
+
estimates, boundaries, trace-probe uncertainty and the variational noise
|
|
998
|
+
floor. This is a separate estimator; REML remains the default.
|
|
999
|
+
- Fit only the needed variance components in two-step methods, avoiding the
|
|
1000
|
+
unused GLS completion. Compute weighted kinship traces only when requested.
|
|
1001
|
+
- Prepare called-sample moments and kinship trace once; uncached requested-row
|
|
1002
|
+
access converts only those rows. Decode BED into contiguous variant columns
|
|
1003
|
+
while preserving the public sample-by-variant array and allele counts.
|
|
1004
|
+
- Extend the verified HAPNEST workload to exactly 20,000 retained variants
|
|
1005
|
+
at 50,000 samples. Genotype-only chromosome quotas precede PC and phenotype
|
|
1006
|
+
generation; preparation can run independently of association methods.
|
|
1007
|
+
- Archive 24 one/four-thread comparisons with official LDAK-KVIK and 12
|
|
1008
|
+
paired cache fits. Cache routes give identical saved results; six thread
|
|
1009
|
+
comparisons exceed strict array tolerances without changing the checked
|
|
1010
|
+
association decisions. Retain timing ranges and the observed system-wide
|
|
1011
|
+
swapping limitation, rather than claiming uniform superiority to LDAK.
|
|
1012
|
+
- Update the manuscript, API guides and research agenda with HE, parallel
|
|
1013
|
+
execution, numerical audits and measured time/memory tradeoffs. Timed source
|
|
1014
|
+
snapshots retain their original version; this release does not rewrite them.
|
|
1015
|
+
|
|
1016
|
+
## [2.0.0.dev3] - 2026-10-03
|
|
1017
|
+
|
|
1018
|
+
### Matched reference benchmark
|
|
1019
|
+
|
|
1020
|
+
- Extend measured workloads to 50,000 samples with phensim's new HAPNEST
|
|
1021
|
+
model, keeping all production fitting defaults. Preparation uses tiled
|
|
1022
|
+
genotype operations, audited iterative PCs, and isolated processes.
|
|
1023
|
+
- Add simulator speed/RSS evidence through 100,000 samples, resource tables,
|
|
1024
|
+
uncertainty-aware matched results and empirical-reference research priorities
|
|
1025
|
+
to the report. Simulation scale and association-validation claims remain
|
|
1026
|
+
distinct; large-n cells have one realization each.
|
|
1027
|
+
- Add a prespecified phensim simulation comparison with the official
|
|
1028
|
+
LDAK-KVIK executable, including population structure, within-population
|
|
1029
|
+
LD, environmental confounding and paired PC adjustment.
|
|
1030
|
+
- Validate actual BED calls independently and check external-reader sample
|
|
1031
|
+
IDs, allele orientation, frequencies and call rates. Retain raw results,
|
|
1032
|
+
failures, seeds, source snapshots, convergence diagnostics and process RSS.
|
|
1033
|
+
- Distinguish global genetic-null calibration, null-chromosome rejection
|
|
1034
|
+
under mixed traits, and direct causal-marker detection with uncertainty
|
|
1035
|
+
across independent genotype/phenotype replicates.
|
|
1036
|
+
|
|
1037
|
+
## [2.0.0.dev2] - 2026-10-03
|
|
1038
|
+
|
|
1039
|
+
### Research report
|
|
1040
|
+
|
|
1041
|
+
- Expand the manuscript with model and calibration derivations, explicit
|
|
1042
|
+
evidence boundaries, and six testable research priorities. Withdraw the
|
|
1043
|
+
invalid external-program results and qualify historical QTL-free loci.
|
|
1044
|
+
- Add a reproducible known-covariance denominator experiment with held-out
|
|
1045
|
+
audit variants, analytic tail probabilities, and phenotype-replicate
|
|
1046
|
+
uncertainty; regenerate the report figures with provenance hashes.
|
|
1047
|
+
|
|
1048
|
+
## [2.0.0.dev1] - 2026-10-03
|
|
1049
|
+
|
|
1050
|
+
### Critical review
|
|
1051
|
+
|
|
1052
|
+
- Correct PLINK BED bit encoding/decoding against a specification fixture;
|
|
1053
|
+
preserve counted A1/A2 alleles, accept standard TPED allele pairs, and keep
|
|
1054
|
+
distinct chromosome labels. Legacy dosage TPED files warn on import.
|
|
1055
|
+
- Count diploid MAC in allele copies, retain counted-allele frequency, reject
|
|
1056
|
+
invalid hard calls/duplicate IDs, and represent absent aligned samples as
|
|
1057
|
+
missing. Merge RegMap chromosomes on the variant axis in a common sample
|
|
1058
|
+
order. Reject silently overwritten phenotype replicates.
|
|
1059
|
+
- Correct the extra genetic-variance factor in BLUP and include the genetic
|
|
1060
|
+
component in operator predictions. Use ML's n denominator, respect changes
|
|
1061
|
+
to fitting options, provide an OLS fit when K is absent, and stabilize
|
|
1062
|
+
likelihood quadratics against large fixed effects.
|
|
1063
|
+
- Reject phenotypes explained entirely by covariates and operations on a
|
|
1064
|
+
superseded fit, rather than reporting projection roundoff or new parameters.
|
|
1065
|
+
- Apply weights in streaming kinships, bound their genotype cache, correct
|
|
1066
|
+
overlapping/gapped window complements, and remove the extra group axis
|
|
1067
|
+
from LOCO product storage. Reject nonconverged CG association solves.
|
|
1068
|
+
- Permute independent residual coordinates for the default whitened scheme;
|
|
1069
|
+
retain the prior approximation explicitly as `scheme="projected"`.
|
|
1070
|
+
Mean-impute missing genotypic contrasts and mark non-estimable SNPs NaN.
|
|
1071
|
+
- Replace the invalid RSS-based pseudo-posterior with a normal-approximation
|
|
1072
|
+
Bayes factor requiring explicit `prior_variance` and beta/se. This is an
|
|
1073
|
+
intentional API correction; the old values are not posterior probabilities.
|
|
1074
|
+
- Preserve IDs, effects, standard errors, alleles and precision through CSV;
|
|
1075
|
+
include test oracles in source distributions and declare the pandas extra.
|
|
1076
|
+
CI now covers Python 3.10 without optional analysis dependencies.
|
|
1077
|
+
- Fix small-sample spectral scans, forward block size, reject ignored options,
|
|
1078
|
+
and expose incomplete variational fits. SWLM no longer uses an inapplicable
|
|
1079
|
+
genetic-variance stopping rule; MLMM resets its consecutive-low-h2 counter.
|
|
1080
|
+
- Mark old reports as historical and withdraw the matched-input LDAK
|
|
1081
|
+
comparison pending a rerun with corrected encoding, MAC and missingness.
|
|
1082
|
+
See `docs/review-2026-10-03.md` for evidence and remaining priorities.
|
|
1083
|
+
|
|
1084
|
+
### Fixed
|
|
1085
|
+
|
|
1086
|
+
- `LMM` and its cached `LMFit` referenced each other, so a dropped model
|
|
1087
|
+
(its K and eigendecomposition) waited for the cyclic garbage collector
|
|
1088
|
+
instead of being freed. An exact LOCO `gwas()` stranded its 25 group
|
|
1089
|
+
models, about 8 GB at n = 4,000, and the sim study reached a 17 GB
|
|
1090
|
+
footprint. `LMM.fit_result` now stores the fit without the back
|
|
1091
|
+
reference and returns it bound to the model; a fit returned by
|
|
1092
|
+
`fit()` still keeps its model alive.
|
|
1093
|
+
- The simulation study's S2 confounder was phensim's
|
|
1094
|
+
`simulate_confounded_trait` axis, the leading eigenvector of the
|
|
1095
|
+
tested SNPs' GRM: in a panmictic sample a weighted sum of those SNPs,
|
|
1096
|
+
so S2 could not compare LOCO with non-LOCO. S2 now samples two demes
|
|
1097
|
+
with msprime and puts the confounder on an environment that differs
|
|
1098
|
+
between them (archive 20261003T122520Z-sim-study).
|
|
1099
|
+
- The simulation study cut phensim's contiguous coalescent segment into
|
|
1100
|
+
"chromosomes" every 200 SNPs, through strong LD: every S1 false locus
|
|
1101
|
+
was a QTL tag in the neighbouring block. S1, S3 and S4 blocks are now
|
|
1102
|
+
ldpred3's LD split (bigsnpr's `snp_ldsplit`) into the same number of
|
|
1103
|
+
blocks of 100-400 SNPs.
|
|
1104
|
+
- `GwasResult.genomic_control` returned median(p)/0.5, which is not
|
|
1105
|
+
lambda_GC and runs the other way (below 1 meant inflation). It now
|
|
1106
|
+
returns the median 1-df chi-square over 0.455. The archived sim-study
|
|
1107
|
+
notes were read on the wrong scale; dated errata are appended to both
|
|
1108
|
+
NOTES.md files. Re-read: the truncated top-1024 scan at n = 10k was
|
|
1109
|
+
anti-conservative (lambda_GC 1.30, 84 vs 50 significant non-causal
|
|
1110
|
+
SNPs), not conservative, and exact non-LOCO scans were deflated
|
|
1111
|
+
(0.85-0.93, proximal contamination).
|
|
1112
|
+
- `scan_gxe` tested the interaction column without the SNP main effect:
|
|
1113
|
+
with a 0/1 environment a pure main-effect SNP gave interaction
|
|
1114
|
+
p ~ 1e-6 in 20/20 replicates. The 1-df test is now the interaction
|
|
1115
|
+
given the main effect; E is added to the covariates (with a warning)
|
|
1116
|
+
when absent; `polygenic_gxe=True` adds a K * (EE') variance component
|
|
1117
|
+
(Sul et al. 2016). The previous "GLS oracle" test fitted the same
|
|
1118
|
+
mis-specified design and now uses the full one.
|
|
1119
|
+
- `permutation_min_p` permuted the raw phenotype, which breaks the
|
|
1120
|
+
kinship covariance: family-wise error 7.6% +/- 0.6% at a nominal 5%
|
|
1121
|
+
under 4 populations at F_ST 0.3. It now permutes GLS-whitened null
|
|
1122
|
+
residuals (Abney 2015, MVNpermute); `scheme="raw"` keeps v1 behaviour.
|
|
1123
|
+
In the sim study the genome-wide 5% threshold moved from 8.6e-5 to
|
|
1124
|
+
4.4e-6 (Bonferroni 2.5e-6): the archived "30x less conservative than
|
|
1125
|
+
Bonferroni" was mostly the invalid permutation.
|
|
1126
|
+
- `stepwise.mlmm` was not the published MLMM: it selected with plain BIC
|
|
1127
|
+
(Segura et al. 2012 found it too tolerant), scored REML likelihoods
|
|
1128
|
+
across different fixed-effect designs, compared backward models
|
|
1129
|
+
against the wrong likelihood, sliced a list it had mutated, and fed
|
|
1130
|
+
missing calls into cofactors as dosage -1. It is now a port of v1's
|
|
1131
|
+
`linear_models.mlmm`: ML likelihoods, EBIC and mBonf (and BIC)
|
|
1132
|
+
selection over all forward and backward models, v1's pseudo-
|
|
1133
|
+
heritability stop rule, mean-imputed cofactors. `mlmm_bic` is removed;
|
|
1134
|
+
`mlmm(...)["selected"]` holds every criterion's model.
|
|
1135
|
+
- The quickstart's per-chromosome LOCO recipe scanned the SNPs outside
|
|
1136
|
+
each chromosome with that chromosome's LOCO kinship (backwards).
|
|
1137
|
+
- docs/methods.tex: the LDAK-KVIK reference ("Tian et al. 2024") did not
|
|
1138
|
+
exist and is replaced by Hof & Speed (2025, Nat Genet); the BOLT-LMM
|
|
1139
|
+
title was wrong and the truncated spectrum was attributed to it; the
|
|
1140
|
+
whitening operator `\H` typeset as a Hungarian-umlaut accent.
|
|
1141
|
+
- `power_analysis` counted LD tags of causal variants as false
|
|
1142
|
+
positives (`n_false_positive`); the field is now
|
|
1143
|
+
`n_significant_noncausal`, and `locus_summary` gives locus-level
|
|
1144
|
+
power and FDR. The simulation study reports loci.
|
|
1145
|
+
|
|
1146
|
+
### Added
|
|
1147
|
+
|
|
1148
|
+
- `report/`: a paper-style LaTeX report of the evidence: per-SNP
|
|
1149
|
+
calibration under structure on *A. thaliana* and simulated data, the
|
|
1150
|
+
confounding and LD-block simulation designs, LOCO and computation.
|
|
1151
|
+
`report/make_figures.py` rebuilds its figures and tables from the
|
|
1152
|
+
archived results; the methods documentation covers PC covariates under
|
|
1153
|
+
strong environmental stratification and the model/fit memory design.
|
|
1154
|
+
- `mixmogam.gwas()`: leave-one-chromosome-out association by default.
|
|
1155
|
+
`method="exact"` is EMMAX with one eigendecomposition and REML refit
|
|
1156
|
+
per LOCO group; `"bolt-inf"`, `"bolt"` and `"kvik"` are the two-step
|
|
1157
|
+
K-free statistics; `"auto"` picks exact up to n = 5,000. More than 25
|
|
1158
|
+
chromosomes are merged into 25 contiguous groups.
|
|
1159
|
+
- `mixmogam.twostep`: BOLT-LMM-inf (batched CG LOCO solves, retrospective
|
|
1160
|
+
statistic, calibration against exact prospective statistics at 30
|
|
1161
|
+
SNPs), BOLT-LMM (two-Gaussian mixture prior by blocked variational
|
|
1162
|
+
Bayes, 5-fold CV over BOLT's 18-point grid, LD Score regression
|
|
1163
|
+
intercept calibration) and LDAK-KVIK (structure test, LDAK-Thin power
|
|
1164
|
+
model, elastic-net prior, OLS-on-offset statistics with KVIK's lambda
|
|
1165
|
+
rule), on a shared streaming LOCO operator. Each reports
|
|
1166
|
+
`calibration_cv`, the spread of the exact/retrospective denominator
|
|
1167
|
+
ratio its single calibration constant assumes away.
|
|
1168
|
+
- Structure-aware denominator (`denominator="spectral"`, a mixmogam
|
|
1169
|
+
extension): a low-rank-plus-flat approximation of the prospective
|
|
1170
|
+
denominator on the top eigenvectors of each SNP's own LOCO kinship.
|
|
1171
|
+
On null SNPs binned by structure loading, single-constant statistics
|
|
1172
|
+
ran from lambda_GC 1.34-1.37 to 0.64-0.73 (FPR at 1%: 2.5-2.9% to
|
|
1173
|
+
0.1%) under simulated F_ST 0.3. On A. thaliana RegMap genotypes the
|
|
1174
|
+
reference LDAK binary's KVIK ran from 1.21 to 0.92. The spectral
|
|
1175
|
+
denominator holds BOLT-LMM-inf within 0.92-1.05 of exact in every
|
|
1176
|
+
quintile (`benchmarks/structure_calibration.py`, `kvik_reference.py`).
|
|
1177
|
+
For the mixture and elastic-net statistics it is a heuristic transfer.
|
|
1178
|
+
The basis width adapts (64 to 512 eigenvectors) until the calibration
|
|
1179
|
+
spread over the 30 exact SNPs falls below 3%.
|
|
1180
|
+
- `ldscore.ld_scores` / `ldsc_intercept`, `results.locus_summary`,
|
|
1181
|
+
`LMM` fits on streaming operators without the truncated
|
|
1182
|
+
eigendecomposition (GLS by conjugate gradients).
|
|
1183
|
+
|
|
1184
|
+
### Changed
|
|
1185
|
+
|
|
1186
|
+
- Stochastic Lanczos REML runs the y rule and all trace probes as one
|
|
1187
|
+
batched Lanczos process (`_slq.lanczos_quadrature_batch`): one pass over
|
|
1188
|
+
the genotypes per step instead of one per probe, same probes, fits
|
|
1189
|
+
equal to 1e-13. LDAK-KVIK picks alpha by randomized Haseman-Elston
|
|
1190
|
+
regression with all alphas in one pass (`alpha_method="he"`, LDAK-KVIK's
|
|
1191
|
+
own approach) and runs one REML fit at that alpha; `alpha_method="reml"`
|
|
1192
|
+
keeps one REML fit per alpha. The variational-Bayes kernel reorders its
|
|
1193
|
+
loops for contiguous access, shares one erfc between log Phi and the
|
|
1194
|
+
Mills ratio, and runs its GEMMs in float32 with an exact float64
|
|
1195
|
+
residual at the end. On A. thaliana (n = 1,307, m = 53k) `kvik` went
|
|
1196
|
+
from ~150 s to ~15 s (REML alpha selection: ~41 s); the LDAK binary
|
|
1197
|
+
takes ~9 s.
|
|
1198
|
+
- Scans on a truncated spectrum warn: they were anti-conservative in
|
|
1199
|
+
simulation. Docstrings no longer attribute the truncated spectrum to
|
|
1200
|
+
BOLT-LMM or SNP-subsampled fits to LDAK-KVIK.
|
|
1201
|
+
|
|
1202
|
+
- K-free large-n path (BOLT-LMM's design): `GenotypeKinship`
|
|
1203
|
+
wraps the genotype store as a streaming linear operator (K x =
|
|
1204
|
+
Z(Z'x)/m over SNP blocks, machine-precision equal to the dense GRM),
|
|
1205
|
+
and `LMM` accepts it directly for the randomized eigensolver, SLQ
|
|
1206
|
+
fitting, scanning and gBLUP -- no n x n matrix is ever formed. SLQ
|
|
1207
|
+
deflation is now adaptive by spectral gap (well-separated extremes
|
|
1208
|
+
only), fixing a systematic bias when the deflation window crossed a
|
|
1209
|
+
near-degenerate bulk.
|
|
1210
|
+
- Scan transform casts the eigenbasis once per dtype, so float32 scans
|
|
1211
|
+
run genuine float32 GEMMs end-to-end: ~3.3x faster than float64 at
|
|
1212
|
+
n=2000-5000 (the earlier "float32" path computed in float64
|
|
1213
|
+
internally); headline scan speed-up vs the v1-style loop is now 123x.
|
|
1214
|
+
- Fixed `scan_gxe`: the interaction column is now formed in raw space
|
|
1215
|
+
(g * E) and then transformed and residualized. The previous
|
|
1216
|
+
product-of-transforms approximation was wrong in general and
|
|
1217
|
+
catastrophically degenerate when E is also a covariate; the corrected
|
|
1218
|
+
statistic is certified against a direct GLS oracle in the tests.
|
|
1219
|
+
- Complete revision of the package (the Python-2-era flat modules of v1.0 are
|
|
1220
|
+
preserved under the `v1.0-legacy` git tag). mixmogam 2.0 is a modern Python
|
|
1221
|
+
(>=3.10) package with a NumPy/SciPy BLAS-3 mixed-model engine:
|
|
1222
|
+
- `LMM` fits variance components with the EMMA algorithm (vectorized
|
|
1223
|
+
log-delta grid + Newton refinement, REML or ML) and scans SNPs in batched
|
|
1224
|
+
GEMM blocks instead of one least-squares solve per SNP, with float32 scan
|
|
1225
|
+
arithmetic and chunked SNP streaming.
|
|
1226
|
+
- Kinship suite (IBS, realized relationship/IBD, scaling, LOCO, windowed
|
|
1227
|
+
local/global) replaces the broken `SNPsDataSet` kinship helpers.
|
|
1228
|
+
- Genotype/phenotype containers and IO for PLINK (bed and legacy tped),
|
|
1229
|
+
EIGENSTRAT, the v1 CSV formats, and HDF5 (new v2 layout, reads v1).
|
|
1230
|
+
- GWAS results container with Manhattan/QQ plotting, genomic-control
|
|
1231
|
+
inflation, Bayes factors / posterior probabilities from SNP priors,
|
|
1232
|
+
power/FDR analysis.
|
|
1233
|
+
- Stepwise multi-locus mixed model (MLMM), GxE scans, batched permutation
|
|
1234
|
+
thresholds, 2-df genotypic tests, and 2-kinship mixture models.
|
|
1235
|
+
- Test suite with a reference oracle ported from the v1 math; the old
|
|
1236
|
+
package had none.
|
|
1237
|
+
- Large-n paths: randomized top-k spectrum with a flat tail correction
|
|
1238
|
+
for n > 8000 (anti-conservative; see Fixed above), and a deflated
|
|
1239
|
+
stochastic-Lanczos-quadrature REML/ML solver that needs no O(n^3)
|
|
1240
|
+
eigendecomposition; batched permutation thresholds push all
|
|
1241
|
+
replicates through one SNP-block GEMM pass.
|
|
1242
|
+
- Optional Numba kernel for the fused int8-to-float block conversion
|
|
1243
|
+
with mean imputation (`fast` extra).
|
|
1244
|
+
- Benchmarks (measured suite under `benchmarks/`), docs (`docs/`), and
|
|
1245
|
+
runnable examples (`examples/`).
|
|
1246
|
+
- Simulation study on phensim coalescent data: LMM vs plain-LM genomic
|
|
1247
|
+
control (lambda_GC 3.56 -> 0.98 on the corrected scale; the archive
|
|
1248
|
+
printed 0.41 -> 1.01), SLQ fits within 1-2% of exact, top-1024 scans
|
|
1249
|
+
with 94/100 top-hit overlap, subsampled-kinship h2 within 0.01
|
|
1250
|
+
(`benchmarks/sim_study.py`, archive 20261002T194331Z-sim-study).
|
|
1251
|
+
- Missing genotypes are now handled by per-SNP missingness filtering plus mean
|
|
1252
|
+
imputation (EMMAX-consistent) so SNP blocks stay batchable (v1's per-SNP
|
|
1253
|
+
sample subsetting is not available).
|
|
1254
|
+
- Fixed v1 defects by design: `get_ibd_kinship_matrix` TypeError, the
|
|
1255
|
+
nonexistent `_calc_*_kinship_*` methods behind chromosome-vs-rest and
|
|
1256
|
+
region-split kinships, `update_kinship` stray-`self` bug, integer-division
|
|
1257
|
+
and precedence bugs in the QQ helpers, and modeless `h5py.File` calls.
|
|
1258
|
+
- Dropped the optional rpy2 / R `nearPD` Cholesky fallback (never reached on
|
|
1259
|
+
the eigendecomposition path) and the dead PLINK .ped parser.
|