Add Hypothesis property-based tests for core tHMM invariants - #1010
Merged
Merged
Conversation
Covers transition-matrix stochasticity, CellVar.divide, the stationary distribution, weighted Bernoulli/Gamma emission estimators, LineageTree generation, and Viterbi/log-score consistency. One test is a pinned, strict xfail documenting a real bug it found: StateDistribution.logpdf can raise FloatingPointError (NaN from +inf/-inf) when an unobserved (-1) Bernoulli sentinel coincides with an uncensored, exactly-zero Gamma observation under a shape parameter < 1.
Exclude sentinel/negative Bernoulli and Gamma observations from the Gamma and Bernoulli log-likelihood terms up front, instead of only zeroing them out afterwards. Previously an unobserved (-1) Bernoulli value on a row with an uncensored, exactly-zero Gamma observation and a shape parameter < 1 (where the Gamma density diverges to +inf at x=0) could produce a (+inf) + (-inf) = NaN, which crashes under np.seterr(all="raise"). Un-xfails test_gamma_logpdf_never_produces_nan now that the bug it documented is fixed.
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.
Summary
Adds
hypothesisas a dev dependency and a newlineage/tests/test_properties.pywith 13 property-based tests covering invariants of the tHMM machinery that the existing example-based tests don't exercise across a range of inputs:CellVar.divide: daughter states are always valid, parent/gen bookkeeping is correct, and results are reproducible for a given RNG seed, for any row-stochastic transition matrix.BaumWelch.calculate_stationary: the returned vector is always a valid probability distribution and a genuine fixed point (w @ T == w) of any stochasticT.stateCommon.bern_estimator: the weighted Bernoulli MAP estimate always stays in[0, 1].StateDistributionGamma.StateDistribution.logpdf/dist: negative "unobserved" sentinel rows always contribute exactly zero log-likelihood, and a distribution's distance to itself is zero.LineageTree.rand_init: for any(pi, T)and requested size, the produced tree is a valid full binary tree (every non-leaf has exactly 2 children) with valid state indices, and simulation is reproducible for a given seed.Viterbi/tHMM.log_score: decoded state sequences are always valid and always score at least as well as an arbitrary random assignment of the same lineage.One test (
test_gamma_logpdf_never_produces_nan) is a strict, pinnedxfailthat documents a real bug Hypothesis found:StateDistribution.logpdfcan raiseFloatingPointErrorinstead of returning a finite log-likelihood. This happens becauselineage/__init__.pysetsnp.seterr(all="raise"), and an "unobserved" Bernoulli sentinel (-1) on a row with an uncensored, exactly-zero Gamma observation under a shape parameter< 1(where the Gamma density diverges to+infatx=0) produces a(+inf) + (-inf) = NaNinsidelogpdf, which the current code only zeroes out after the NaN has already been created. This is left as a documentedxfailrather than fixed here, since correcting the ordering of the masking logic touches numerically sensitive code that's out of scope for a test-only PR.Test plan
uv run pytest lineage/tests/test_properties.py -v— 12 passed, 1 xfailed (repeated 3x to confirm no flakiness)uv run pytest lineage/tests -q— full existing suite still green: 66 passed, 1 xfaileduv run ruff check/ruff format --checkon the new fileuv run ty check lineage/tests/test_properties.py