Skip to content

Derive censoring logprob from minimum, maximum, rounding and casting operations#8363

Open
ricardoV94 wants to merge 4 commits into
pymc-devs:mainfrom
ricardoV94:censoring_by_another_name
Open

Derive censoring logprob from minimum, maximum, rounding and casting operations#8363
ricardoV94 wants to merge 4 commits into
pymc-devs:mainfrom
ricardoV94:censoring_by_another_name

Conversation

@ricardoV94

Copy link
Copy Markdown
Member
  • Infer logprob of maximum/minimum censoring
  • Infer logprob of trunc and round_half_away_from_zero rounding

@read-the-docs-community

read-the-docs-community Bot commented Jul 15, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 45.12195% with 45 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.42%. Comparing base (df1a369) to head (1824e0d).

Files with missing lines Patch % Lines
pymc/logprob/censoring.py 49.31% 37 Missing ⚠️
pymc/logprob/tensor.py 11.11% 8 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #8363      +/-   ##
==========================================
- Coverage   91.79%   87.42%   -4.38%     
==========================================
  Files         128      128              
  Lines       21007    21068      +61     
==========================================
- Hits        19283    18418     -865     
- Misses       1724     2650     +926     
Files with missing lines Coverage Δ
pymc/logprob/tensor.py 48.11% <11.11%> (-48.69%) ⬇️
pymc/logprob/censoring.py 53.19% <49.31%> (-44.49%) ⬇️

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ricardoV94
ricardoV94 force-pushed the censoring_by_another_name branch from 64e51bf to c821d37 Compare July 16, 2026 14:52
@ricardoV94
ricardoV94 marked this pull request as ready for review July 16, 2026 15:04
@ricardoV94
ricardoV94 force-pushed the censoring_by_another_name branch from c821d37 to 7e07d43 Compare July 16, 2026 21:05
@ricardoV94 ricardoV94 changed the title Derive censoring logprob from min max operations Derive censoring logprob from minimum, maximum and round/cast operations Jul 16, 2026
@ricardoV94 ricardoV94 changed the title Derive censoring logprob from minimum, maximum and round/cast operations Derive censoring logprob from minimum, maximum, rounding and casting operations Jul 16, 2026
@ricardoV94
ricardoV94 force-pushed the censoring_by_another_name branch 2 times, most recently from 7fd1c82 to 1824e0d Compare July 16, 2026 21:54
The rewrite claimed any measurable base and derived the mass of the
interval a continuous variable would round into. That interval is only the
right one for a continuous base, and the dtype does not settle the question
in either direction.

A base that already sits on the integers is left untouched by every
rounding, save for the upcast the Ops apply to a discrete input. Deriving
the mass of the resulting degenerate cell shifted it by one: floor of a
poisson(3) at 3 returned the mass of 4. Reducing the rounding to that cast
instead leaves the base's own logprob to apply, and also covers a rounding
of a rounding, which used to be declined.

A float dtype, on the other hand, does not establish continuity, as an
intermediate MeasurableVariable can be supported on the integers while
carrying one. Rounding a clipped variable pooled the mass at its upper
bound into the neighbouring cell and left the bound with a probability of
zero: floor(clip(normal(0.5, 1), 0, 3)) at 3 returned -inf instead of
log(P(x >= 3)) = -5.08. Only a RandomVariable states its own support, so
other bases are now declined rather than assumed continuous.
maximum(x, c) and minimum(x, c) with a single measurable operand are
rewritten to one-sided clips (both-measurable graphs are order
statistics and are not claimed), reusing the existing clip machinery
for the discrete and continuous cases alike. The unbounded side uses
the measurable variable itself as its bound, which find_measurable_clips
already understood as one-sided clipping and which, unlike +-inf
constants, does not upcast discrete variables (previously one-sided
clips of discrete variables failed to be claimed for this reason).
Whether each side is bounded is decided once at claim time and stored
on the MeasurableClip op.

Clips of clips are fused eagerly into a single clip by combining the
bounds with maximum/minimum (constant bounds fold), so two-sided
censoring like maximum(minimum(x, ub), lb) becomes one node.
logcdf/icdf are also implemented for clipped variables (the point
masses at the bounds absorb the tail quantiles).
Truncation towards zero is the interval [x, x+1) for positive values,
(x-1, x] for negative ones, and (-1, 1) pooled at zero. Rounding half
away from zero shares the intervals of round half to even, since the
tie-breaking rule only differs on a measure-zero set of the continuous
base variable.
A float -> int cast rounds towards zero, so it is the `trunc` a user could
have written themselves composed with a cast that only relabels the dtype.
Rewriting it to that form leaves the judgement of whether the base may be
truncated to `find_measurable_roundings`, and the relabelling cast is then
claimed as measure-preserving.

The other narrowing casts are not truncations and stay unclaimed: casting
to an unsigned int wraps around for negative values (-2.7 -> 254 for
uint8), and casting to bool tests `x != 0`, which collapses the support
onto two points rather than partitioning it.

`round_logprob` snaps the value onto the cell whose mass it asks for, and
the Op it uses to do so rejects the integer value that a cast to int hands
it. The snapping is an identity on such a value, so only its dtype needs
adjusting.
@ricardoV94
ricardoV94 force-pushed the censoring_by_another_name branch from 1824e0d to d69246b Compare July 16, 2026 22:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants