Skip to content

refactor: narrow the mode-specific inputs in compute_lmm_chunk_numpy#125

Merged
michael-denyer merged 1 commit into
masterfrom
chore/pyrefly-compute-numpy
Jul 26, 2026
Merged

refactor: narrow the mode-specific inputs in compute_lmm_chunk_numpy#125
michael-denyer merged 1 commit into
masterfrom
chore/pyrefly-compute-numpy

Conversation

@michael-denyer

Copy link
Copy Markdown
Owner

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_H0 and Hi_eval_null are optional because only some LMM modes take them. They were
guarded up front:

if lmm_mode in (2, 4) and logl_H0 is None:
    raise ValueError(...)

but the branches that read them test lmm_mode == 2, and nothing connects in (2, 4) to
== 2. So all four reads still saw float | None / ndarray | None.

The guard now sits at the point of use, through _require_mode_input, tying each check to
the branch that actually needs the value. Two behavioural details preserved deliberately:

  • Mode 4 binds both inputs at the top of its branch, before the Score step — so an
    omitted argument still fails before any compute runs, not partway through.
  • Mode 4 checks logl_H0 first. With both absent, that is the one reported.
    testcompute_lmm_chunk_numpy_missing_args_raise pins this ordering. My first attempt
    checked Hi_eval_null first and the test caught it; I restored the production order
    rather 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 errors

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; it degrades to dict[str, object]
(checked, not assumed). The two sites now go through _store_wald, which copies the five
keys by name. Side benefit: it makes explicit which keys the Wald path fills, which the
blanket update hid.

The _compute_lrt_numpy / _compute_score_numpy updates are untouched — they return plain
dicts and always type-checked.

Verification

This is the LMM compute dispatch, so it got a full numerical sweep rather than a spot check.

Compared (before vs after) Result
.assoc.txt for modes 1/2/3/4 × batch and streaming backends — 8 runs byte-identical (sha256)
pve_estimate, pve_se, n_snps_tested for all 8 identical
Guard sweep: all 16 combinations of mode × (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
ruff check / ruff format --check clean

The guard matrix, for the record:

m2_neither    ValueError: logl_H0 is required for LRT (mode 2) and All (mode 4)
m3_logl_only  ValueError: Hi_eval_null is required for Score (mode 3) and All (mode 4)
m4_neither    ValueError: logl_H0 is required for LRT (mode 2) and All (mode 4)
m4_hi_only    ValueError: logl_H0 is required for LRT (mode 2) and All (mode 4)
m4_logl_only  ValueError: Hi_eval_null is required for Score (mode 3) and All (mode 4)
m4_both       ok:betas,lambdas,lambdas_mle,logls,p_lrts,p_scores,pwalds,ses

Baseline

Regenerated on Python 3.11.13 / numpy 2.4.6 per #121, verified on both:

Environment pyrefly check --baseline
Python 3.11.13, numpy 2.4.6 0 errors
Python 3.13.5, numpy 2.5.1 0 errors

Running 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 an
unbound-name), tests/test_kinship_io.py (7).

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.
@michael-denyer
michael-denyer merged commit f40a39a into master Jul 26, 2026
12 checks passed
@michael-denyer
michael-denyer deleted the chore/pyrefly-compute-numpy branch July 26, 2026 23:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant