Release notes
CensoredDistributions.jl follows a hybrid changelog approach:
GitHub Releases: All releases (automatically generated by TagBot)
NEWS.md: Major releases requiring detailed context (shown below)
See GitHub Releases for minor/patch release information.
Unreleased
Bug fixes
double_interval_censoredandprimary_censorednow select the analytic-vs-numeric solver by dispatching on the argument types rather than branching on aBoolvalue, so the return type stays concrete when the delay parameters are runtime values (e.g. inside a Turing model). Previously the value-levelforce_numericbranch inferred to aUnionof theAnalyticalSolverandNumericSolverspecialisations at non-constant-folded call sites, which propagated an abstract element type into downstreampdfloops and poisoned reverse-mode AD. Closes #367.Skip the CDF-saturation early-return in the numeric
primarycensored_cdfpath when the lower bound is at the distribution boundary (lower == minimum(dist)). Evaluatingcdf(dist, lower)there is unnecessary (cdfis 0 by construction) and trips degenerate0·(-Inf)reverse-mode rules inDistributions.jl(e.g.LogNormalvialog(0)), contaminating the ReverseDiff tape with NaN. Restores ReverseDiff and Mooncake gradient correctness onPrimaryCensored LogNormal+Uniform numerical. Closes #249.
Added
primary_censoredanddouble_interval_censoredaccept amethodkeyword taking a solver object,AnalyticalSolver()(the default) orNumericSolver(), each optionally given a quadrature solver (e.g.NumericSolver(QuadGKJL())).AnalyticalSolverandNumericSolverare now exported. Passing a concrete method object is the type-stable way to choose the CDF backend, and is preferred over the deprecatedforce_numericflag.
Deprecated
- The
force_numerickeyword ofprimary_censoredanddouble_interval_censoredis deprecated. Passmethod = NumericSolver()(ormethod = AnalyticalSolver()) instead.force_numericstill works but emits a deprecation warning and, being a runtimeBool, does not guarantee a concrete return type.
Breaking
primary_censored(...; solver)defaults toGaussLegendre(; n = 64)(wasQuadGKJL()). The fixed-node solver traces cleanly through every AD backend, where adaptive quadrature does not. Passsolver = QuadGKJL()explicitly to keep adaptive accuracy.
AD gradient infrastructure
New
test/ad/sub-environment withDifferentiationInterfaceTest-driven gradient correctness tests for thelogpdfof every censored distribution type, plus unit-level tests for the gamma CDF rrule (_grad_p_a_seriesbaseline,Mooncake.TestUtils.test_rule, and defensive guards). Wired intotask test-ad,task test, and a dedicated CI job.New
test/ADFixturespath package is the single source of truth for the scenario set and the working/broken backend matrix. The test suite, benchmark suite (benchmark/src/ad_gradients.jl), and docs tutorial all add it as a path dep so the three AD surfaces stay in lock-step.Gradient references are computed with ForwardDiff. Adaptive finite-difference baselines (e.g.
central_fdm(5, 1)) disagree with every AD backend by ~10% on Weibull analytical scenarios becausePrimaryCensored.logpdfinternally finite-differences the CDF with a hardcodedh = 1e-8; ForwardDiff's Dual propagation through that same FD gives the exact derivative the package computes and matches the other backends to ~1e-6.New "Automatic differentiation backends" tutorial at
docs/src/getting-started/tutorials/ad-backends.jlrenders the full backend × scenario matrix viaDIT.benchmark_differentiationwith failures left visible.README gains a per-backend support badge row reflecting the measured state (ForwardDiff full; ReverseDiff tape, Mooncake reverse, and Mooncake forward partial; Enzyme forward/reverse broken).
Known follow-ups tracked in #217 (
gamma_incDualdispatch gap on theDistributions.cdf(Gamma)path used byIntervalCensored Gamma arbitrary), #225 (Enzyme + DIT-Mooncake interaction).
v0.1.0 - Initial Release
CensoredDistributions.jl extends Distributions.jl to support primary event censoring and interval censoring, enabling modelling of scenarios where observation times are subject to various forms of censoring and truncation.
Core functionality
Distribution Constructors
primary_censored(dist, primary_event): Creates primary event censored distributions where an initiating event occurs within a time window, then experiences a delayinterval_censored(dist, interval): Creates interval censored distributions for continuous values observed only within discrete intervals (regular or arbitrary intervals)double_interval_censored(dist; kwargs...): Combines primary event censoring, optional truncation, and optional secondary interval censoring in an appropriate mathematical sequence
Distribution Types
PrimaryCensored{D, P}: Wraps delay distributionDwith primary event distributionPIntervalCensored{D, T}: Wraps continuous distributionDwith interval boundaries of typeTDoubleIntervalCensored: Composition of multiple censoring mechanisms
Extensible CDF Method
primarycensored_cdf(): User-extensible method for computing CDFs of primary censored distributions with analytical solutions for common distribution pairs
Utility Functions
weight(dist, weights): Creates weighted distributions for efficient likelihood computation
Distributions.jl interface
Partial implementation of Distributions.jl interface including:
pdf,logpdf: Probability density functionscdf,logcdf: Cumulative distribution functionsquantile: Inverse CDF (where analytically tractable)rand: Random number generationminimum,maximum,insupport: Support queriesmean,var,std: Moments (where analytically tractable)
Migration from primarycensored R package
CensoredDistributions.jl provides a Julia-native implementation with enhanced functionality compared to the primarycensored R package:
Function Mapping
R's
dprimarycensored()→ Julia'spdf(primary_censored(...))R's
pprimarycensored()→ Julia'scdf(primary_censored(...))R's
qprimarycensored()→ Julia'squantile(primary_censored(...))R's
rprimarycensored()→ Julia'srand(primary_censored(...))
Parameter Differences
Primary event window: Use
primary_event = Uniform(0, pwindow)instead of R'spwindowparameterSolver selection: Configure via
solverkeyword instead of R's integration method parametersTruncation: Apply via
truncated()from Distributions.jl or usedouble_interval_censored()convenience function
Contributors
Sam Abbott (@seabbs)
Damon Bayer (@damonbayer)
Sam Brand (@sambrand)
Michael DeWitt (@dewittpe)
Joseph Lemaitre