refactor: narrow the mode-specific inputs in compute_lmm_chunk_numpy#125
Merged
Conversation
Clears the last 6 pyrefly baseline entries in lmm/compute_numpy.py (113 -> 107). Two root causes. `logl_H0` and `Hi_eval_null` are optional because only some LMM modes take them. They were guarded up front with `if lmm_mode in (2, 4) and ... is None`, but the branches that read them test `lmm_mode == 2`, and nothing connects the two conditions — so all four reads still saw `float | None` / `ndarray | None`. The guard now sits at the point of use, via `_require_mode_input`, which ties each check to the branch that actually needs the value. Behaviour is preserved exactly, including two details worth naming: - Mode 4 binds both inputs at the top of its branch, before the Score step, so an omitted argument still fails before any compute rather than partway through. - Mode 4 checks `logl_H0` first. With both absent that is the one reported, matching the order the up-front guards established. `testcompute_lmm_chunk_numpy_missing_args_raise` pins this; my first attempt checked `Hi_eval_null` first and the test caught it. The production order was restored rather than the test rewritten. `result.update(...)` with a `WaldResult` fails because a TypedDict is not a `Mapping[str, ndarray | None]` — its value types are per-key, so the update overloads reject it. `dict(wald)` does not help either; it degrades to `dict[str, object]`. The two sites now go through `_store_wald`, which copies the five keys by name. That also makes explicit which keys the Wald path fills, which the blanket update hid. The `_compute_lrt_numpy` and `_compute_score_numpy` updates are untouched: they return plain dicts and always type-checked. The new helper is `_require_mode_input`, not `_require` — the module already has a `_require(symbol, what, abi)` for binding C symbols. Verified numerically rather than assumed. All four LMM modes on both the batch and streaming backends over mouse_hs1940 produce byte-identical .assoc.txt output and identical pve/pve_se — 8 runs, all matching. The guard behaviour was swept across all 16 combinations of mode x (neither / logl_H0 only / Hi_eval_null only / both): identical outcome and identical message text in every cell, including which key set each mode populates. `pytest tests/` — 2241 passed, 10 skipped, 3 xfailed. Baseline regenerated on Python 3.11 / numpy 2.4.6 per #121; `pyrefly check --baseline` reports 0 errors on both 3.11.13/2.4.6 and 3.13.5/2.5.1.
This was referenced Jul 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Continues the pyrefly burn-down. 113 → 107 entries. Clears the last 6 in
lmm/compute_numpy.py.1. Optional inputs never narrowed — 4 errors
logl_H0andHi_eval_nullare optional because only some LMM modes take them. They wereguarded up front:
but the branches that read them test
lmm_mode == 2, and nothing connectsin (2, 4)to== 2. So all four reads still sawfloat | None/ndarray | None.The guard now sits at the point of use, through
_require_mode_input, tying each check tothe branch that actually needs the value. Two behavioural details preserved deliberately:
omitted argument still fails before any compute runs, not partway through.
logl_H0first. With both absent, that is the one reported.testcompute_lmm_chunk_numpy_missing_args_raisepins this ordering. My first attemptchecked
Hi_eval_nullfirst and the test caught it; I restored the production orderrather than rewriting the test.
The helper is named
_require_mode_input, not_require, because the module already has a_require(symbol, what, abi)for binding C symbols.2.
result.update(WaldResult)— 2 errorsA TypedDict is not a
Mapping[str, ndarray | None]— its value types are per-key, so theupdateoverloads reject it.dict(wald)does not help; it degrades todict[str, object](checked, not assumed). The two sites now go through
_store_wald, which copies the fivekeys by name. Side benefit: it makes explicit which keys the Wald path fills, which the
blanket update hid.
The
_compute_lrt_numpy/_compute_score_numpyupdates are untouched — they return plaindicts and always type-checked.
Verification
This is the LMM compute dispatch, so it got a full numerical sweep rather than a spot check.
.assoc.txtfor modes 1/2/3/4 × batch and streaming backends — 8 runspve_estimate,pve_se,n_snps_testedfor all 8pytest tests/ruff check/ruff format --checkThe guard matrix, for the record:
Baseline
Regenerated on Python 3.11.13 / numpy 2.4.6 per #121, verified on both:
pyrefly check --baselineRunning total
174 (#121) → 152 (#122) → 135 (#123) → 113 (#124) → 107.
src/is now down to 18 entries across 5 files. Everything else is tests and scripts:tests/test_incremental_writer.py(23, still undiagnosed and the single largest cluster),tests/test_jlinalg_dgemm.py(8),lmm/io.py(7 — an unguarded file handle plus anunbound-name),tests/test_kinship_io.py(7).