Repository navigation
Merge develop into master - #790
Merged
Merged
Conversation
Every fit builds a SurpyvalData first. Two costs dominated it at 100,000 rows: - np.unique(..., axis=0) for the distinct truncation windows, and again in _check_identifiable for the distinct non-right-censored values. It sorts rows as structured records. unique_pairs ranks each column and then the pairs of ranks as integers, with the same sorted pairs and inverse. - A list input was converted to an array once per check (durations, missing values, floats). xcnt_handler now converts a flat list of numbers once; pairs, ragged lists and object lists reach the checks as given. The data are identical, dtypes included, as are the error messages. On 100,000 units with mixed censoring and truncation: from arrays 58 -> 41 ms, from lists 89 -> 62 ms, the identifiability check 12 -> 2.5 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FMWxwbs7MLH1PcRoyS8Zxa
Pull requests, into develop or master, now run lint only. The conformance suite, the test matrix and the documentation build run once, on the push to master that a release merge makes. The nightly workflow no longer runs on a schedule; it can still be started by hand. Each change is tested locally by the tests for what it changed. The Contributing page lists what that covers: the tests and conformance cases for the touched code, its doctests, lint and mypy, the docs pages that call it, and other interpreters for numerical changes. A failure the merge run finds is fixed in a point release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe CI: full suite and docs build only on the merge into master
…low imputed points (#631, #632, #633) - #633: the offset's upper bound was the smallest value of any row. It is now the smallest exact failure, left-censoring time (not with zero inflation, whose mass at 0 meets it) or interval upper end. A right-censored time, an interval start or a truncation time below gamma meets S = 1 / F = 0 there. The right- and left-censored terms hold the shifted time at the support's edge, so a custom cumulative hazard is not evaluated below it. 500 Weibull units inspected every 3: gamma 3.0 (the first interval start, 'verified', -904.69) -> 5.16 (-896.85). - #632: a Rayleigh offset+lfp+zi fit on interval data ran onto that bound and warned 'No finite maximum'. It now finds the interior maximum, gamma 4.2765 and -746.4526, as 0.22 did. - #631: with zi, a left-censored row is imputed half way down to the exact zero, below the start taken from the nonzero data, and the shifted seed data went negative (Weibull OutsideSupportError, LogNormal NaN). The start is now below the imputed points too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…he corrected bound The start below imputed points (#631) took its step from the data and imputed points together, which changed every offset start. It now keeps the data's own step and only lowers the start when an imputed point is below it. Two tests expected the old bound. A right-censored -1 no longer caps gamma. Dataset B's offset Weibull, whose profile likelihood rises to the first failure, was stopped by the cap at a censored time and reported verified (#633); it now warns 'No finite maximum'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…maximum (#633) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
A model with no offset, limited-failure or zero-inflation part (gamma 0, lfp_p 1, f0 0) now gives its distribution's own sf, ff and df. The distribution's support guard already holds them at their edge values below the support, and the transforms it skips were identities, but five passes over the query. The support and missing-value checks wrapped around every distribution function now use plain numpy instead of autograd's wrappers, since x is not traced there. The results are bit-identical: 1,176 outputs over 14 families, with and without lfp/zi/offset, for scalar, list, 2-D, NaN, inf and below-support queries. A -0.0 below the support is still 0.0. Weibull sf on 16 points: 58 -> 28 us. Exponential sf on 20,000 points: 193 -> 79 us. LogNormal sf on 16 points: 60 -> 34 us. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
Build SurpyvalData about a third faster, with identical results
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
The names 0.23 deprecated (removal in REMOVED_IN = 0.24) are gone: - the limited-failure proportion's old name p (lfp_p) in fixed, param_cb, from_params and the attribute of the univariate and regression models; model.p is still the fitted parameter on Bernoulli, Binomial, Geometric, NegativeBinomial and FixedEventProbability, and elsewhere raises an AttributeError naming lfp_p; - beta_j for a regression coefficient in fixed and param_cb (the column's name, else coef_j); dicts saved with them still load; - se on Cox, Lin-Ying, proportional odds and Fine-Gray (standard_errors()); Parametric.cov_matrix, cov on Fine-Gray, proportional odds and additive hazards, and the covariance attribute of Royston-Parmar and the frailty models (covariance(), now a plain method); - the recurrent models' aic and bic properties (aic(), bic()); - MixtureModel.log_likelihood(params) (log_likelihood is a float), MixtureModel.loglike (neg_ll()) and the public names of its EM steps; - a Cox model's neg_ll(beta) (neg_ll_of(beta)); - CoxFrailty's loglik and loglik_no_frailty (log_likelihood, log_likelihood_no_frailty); - success_run's confidence= and alpha= (alpha_ci=, keyword only, so an old positional confidence raises rather than being read as alpha_ci); - surpyval.NUM, TINIEST and EPS, and their copies in surpyval.utils.numeric. The deprecation helpers stay. REMOVED_IN is now "0.25" and REMOVED_IN_NEXT "0.26" (nothing used REMOVED_IN_NEXT); the module and helper docstrings speak of the v0.23 renames in the past. The removal test's scan check now asserts it sees every helper in surpyval.utils.deprecation.__all__ rather than shims in the package, which has none left; it passes with __version__ at 0.24. The warns-tests become old-name-is-gone tests, and a few representative v0.23 names join test_removed_arguments. Tests that used an old spelling without failing (hpp.bic, getattr(model, "aic") expecting a raise, from_params(p=) as a structural argument) use the new one. Two mixture tests that counted EM steps by patching MixtureModel.EM and Q counted nothing once those became aliases; they now patch _em_iteration and _Q. The docs, the bundled skill's example and the changelog's Unreleased section follow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Faster sf, ff and df on fitted models (#642)
…uatqe Remove the names deprecated in 0.23 (next release is 0.24)
- The reference results the tests read (tests/reference/data/*.json) ship as package data. - The doctest comparison's helpers and the opt-in table move from the root conftest into surpyval/tests/_suite.py. test_doctest_checker imported them from the root conftest, which is not installed. The root conftest keeps the flags and the doctest patch, which must be in an initial conftest. - The marks and the opt-in skipping move to surpyval/tests/conftest.py, which ships. The installed suite, with no flags registered, skips the ml / invariants / calibration groups as the repository does by default. - Modules that need hypothesis, scikit-survival or bson are left out when those are not installed. The two checker tests that need the root conftest's doctest patch skip without it. - The merge into master builds the wheel, installs it in a clean venv with pytest, and collects the suite. From a clean install of the built wheel: collection went from 7 errors to 20,827 tests collected. Reference, tails and doctest-checker modules: 1,586 passed, 2 skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe The installed test suite collects (#661)
…' on cb, quantile_cb and param_cb (#645) An offset fit's covariance leaves the offset out, since a threshold's likelihood is not regular. So its Wald bounds hold gamma at its estimate and are far too narrow near it: a 90% bound on a 3-parameter Weibull's B1 life from 30 failures covered 40% of the time. They now warn. The plot's band does not. cb, quantile_cb and param_cb take method='bootstrap', a parametric bootstrap that follows the regressions' (#617). Each of n_boot data sets (200 by default) is simulated from the model, each unit censored as it was (Davison-Hinkley conditional censoring, the regressions' _Design) and within its truncation window. The model is refitted to each, with its offset and fixed parameters as fitted. The bound is the bias-corrected percentile interval of the refits. param_cb('gamma', method='bootstrap') is the one bound on the offset. Refits are kept per (n_boot, integer random_state) and not pickled. Left- or interval- censored data, lfp/zi models, discrete families and models without their data are refused with one message each. Also: bca_bounds no longer leaks an 'invalid value' RuntimeWarning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
The new __getstate__ (#645) replaced the likelihood-ratio mixin's, so the _lr_ caches were pickled again. It now builds on the mixin's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
Two tests used method='bootstrap' as an unknown method; it is known now (#645). An offset model's hf/df Wald bound test expects no warning other than the new one. test_extras still built lfp models with p=, removed in #669. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…gelog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Offset models: Wald bounds warn that gamma is held; bootstrap bounds (#645)
The alternative starts (a custom distribution's grid, the limited-failure start from the failures alone) were skipped for any fit with a fixed parameter. A Weibull with lfp=True and lfp_p fixed at 0.5 stopped on the infant-mortality mode (alpha 262.7, beta 0.96, -440.38) and reported it verified. The wear-out mode (92.25, 2.345, -429.07) is 11.3 units higher. The starts now run with the fixed values in place. From lfp_p 0.6 up the other mode is the global one, and the fit still finds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Fits with fixed parameters try the alternative starts too (#649)
#650) A component whose failures all lie beyond the data explains none. It only holds back a share of the units, as a limited-failure proportion does, and the likelihood keeps rising as it moves further out. A two-Weibull fit came back with a component of scale 33,561 (largest observation 1,150) as a verified maximum, 2e-8 below the limited-failure limit. After the fit, each component is compared with one that never fails (survival 1 at every time). If the likelihood is at least as high to the verification's tolerance, the fit warns 'No finite maximum', names the component and points to lfp=True. Truncated data are not checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…mes (#646) With some times at or below 0, every family whose support starts at 0 was outside its support, and fit_best returned the best of the rest in silence. With three zero ages in 50, that was a Normal putting 3% of the units before day 0. One warning now names the families passed over, counts the times at or below 0, says what was chosen, and gives zi=True for units that failed at time 0. A Beta passed over for data outside (0, 1) stays quiet (#485). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Mixture: a component that runs off past the data has no finite maximum (#650)
…uatqe fit_best warns when the lifetime families are passed over for zero times (#646)
…s at tl (#658) - A scalar i / c / n applies to every row (as tl / tr do); before, it failed with a bare IndexError in _check_xicn_lengths. - Recurrent fit_from_df refuses a column passed by fit's name (c="ev" for c_col="ev"; x, i, n, a column label for tl / tr, Z for the regressions) with the univariate fit_from_df's TypeError. - An exact event (c=0) given in 2-D x as [l, r] with l != r is refused: the intensity fits read it as l and the MCF as the midpoint. - c=2 with 1-D x is a ValueError in split_for_nhpp_likelihood (it was a bare assert, which also vanishes under python -O). - n > 1 on an exact event says to repeat the row (the likelihoods ignore n there); n > 1 on a c=1 row has its own message. - Messages give the user's item label (no np.str_ / np.int64 reprs, the default item as 1), and without i say every row is read as one item. - One rule for an event at tl: the window is (tl, T], so an event at tl is outside it. handle_xicn refuses it, naming the item and times, and the trend tests use the same message; the MCF counts an item at risk only after its tl (it counted it at tl), and gapped windows are (start, end] likewise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Recurrent input handling: scalars, column names, [l, r] events, events at tl (#658)
The univariate bootstrap (#645) imported _Design and _REFIT_ERRORS from the regressions' module. test_private_imports allows no new private imports across packages, so they are public now: ResampleDesign and REFIT_ERRORS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
cif was written with plain numpy's where, exp and asarray, so autograd could not trace it: the NHPP likelihood on a Cox-Lewis intensity had no autograd gradient, nor an ARI likelihood on a Cox-Lewis baseline (its search fell back to nan where the hand-written terms did not apply). (e**(beta x) - 1) / beta is now an autograd primitive: its value the same plain-numpy expression as before (so untraced calls cost what they did), its derivative in beta (x e**u - expm1(u) / beta) / beta, which cancels as u = beta x -> 0 and is 0 / 0 at beta = 0, taken from its series below |u| of 1/2 (x**2 / 2 at 0), and in x e**u; both written in autograd.numpy with each branch fed only points it is finite at, so the Hessian is exact too. Against mpmath: value, Jacobian and Hessian within 2e-15 from beta = 0 and 1e-12 to |beta x| of 50. iif uses autograd's exp as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe CoxLewis.cif is differentiable by autograd (#760, CoxLewis.cif autograd)
…ox-Lewis units) #746 made the ARI fits search and check a Cox-Lewis beta, a rate per unit time, in units of one over the longest time (search_floor); the plain Cox-Lewis NHPP fit did not, and its verification is affected. Its Nelder-Mead search and verify_or_polish ran on beta itself with a floor of 1: the check's central differences stepped a beta of 1e-5 by 1e-7 (1%) for the gradient and 1e-5 (100%) for the Hessian, and the simplex, with absolute tolerances of 1e-4, hardly moved beta at all. On 30 simulated fleets each in units of 1e4, 1e5 and 1e6 hours, 10, 28 and 30 fits were called unverified, with a warning; in the fleets' own units (times of order 1) every fit was verified. The likelihood's search and its check now run with each coordinate in its parameter's unit (NHPPFitter._search_units; CoxLewis.search_units: 1 for alpha, min(1, 1 / longest time) for beta, as CoxLewisDerivatives.search_floor, which now calls it). The least-squares search, and so every MSE fit, is unchanged, as is every other NHPP model's (units of 1). The fitted likelihood carries the units as its search_floor, as an ARI fit's does, and the conformance check of the maximum reads them. All 180 fits are verified now; on the larger units in about half the time (median 0.52x at 1e6 hours, 0.58x at 1e5, 0.84x at 1e4; 1.0x on data of order 1 to 10, MSE 1.0x everywhere). Log-likelihoods within -1.3e-7 to +6.8e-7 of before: the check is now as strict for beta as for alpha, where in units of 1 it had been 1 / max(|beta|, 1 / T) (T the longest time) times stricter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Cox-Lewis NHPP fit searched and checked in the units of beta (#760, Cox-Lewis units)
With BC modes, CrowAMSAA.projection took the demonstrated intensity lambda_CA from the MLE of the Crow-AMSAA shape. The handbook's test-fix-find-test example (MIL-HDBK-00189A section 7.5, also ReliaWiki's Crow Extended examples and Weibull++) uses (N - 1) / N of it: 56 failures to T = 400 give beta 0.91026 (the MLE is 0.9268), lambda_CA 0.12744, and a projected MTBF of 11.29418; the code gave 7.707 and 11.006. lambda_BD = N_BD / T and the (K - 1) / K shape in h(T) already matched. The example's data are now a test, reproducing the handbook's numbers, and Weibull++'s 11.3182 (the same data with the factors to one decimal). With BC modes and a single failure the projection raises, as the bias-corrected shape is 0 there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Crow-AMSAA BC-mode projection uses the bias-corrected shape (#730)
…omething (#769) Parametric.qf now checks the probabilities once (two reductions, all_in_unit_interval) and, when they are all in [0, 1], calls the distribution's formula directly, taking the zero-inflation, cure and offset steps only where they change something: a plain model gives its distribution's qf (a -0.0 as 0.0, as the offset always made it). NaN or out-of-range probabilities, autograd boxes and a distribution qf that is not the shared wrapper go through the old path, unchanged. The distribution qf wrapper skips its NaN and [0, 1] checks after the same two reductions; Parametric.random's plain path calls the formula on its uniforms directly; uniform_draws draws as scipy's uniform.rvs does without its argument handling. Values, shapes, types, warnings and the random streams are identical (checked bit for bit against develop over 13,000 cases). Weibull qf on 1e6 values 30 -> 15 ns each, on 2,000 101 -> 31 us. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe A fitted model's qf takes the identity steps only where they change something (#769)
…xFrailty group_hazard) CoxFrailty's E-step formed each group's A_g as w exp(beta'Z) times the Breslow (or Efron) cumulative hazard directly: at a run-off beta, exp overflowed and the increments underflowed to 0, so a row's term was nan, with numpy's warnings. Where beta'Z or beta'Z + offset is beyond 300 (the baseline's own threshold, now the public sums_directly), the risk sums come from log_baseline_sums and each row's term is exp(log w + beta'Z + log H), Efron's own-time fractions included. Elsewhere the direct code runs unchanged, so ordinary fits are the same to the bit. A CoxFrailty fit now reaches the log-scale CoxInformation path end to end: a covariate that orders the first deaths runs off to beta'Z of about 440, with an ordinary second covariate. Its standard error matches the full information's. The run-off coefficient's variance there was the inverse of an information at rounding (3e10, or negative a little further out); it is nan, as CoxPH reports it (#648, #728). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe CoxFrailty's group hazards summed in logs at a run-off beta (#777, CoxFrailty group_hazard)
Far along a run-off, CoxPH's r (each time's risk-set weight, the sum of n exp(beta'(Z - center)) over the units at risk) is beyond floating point: inf, or 0 at the other end, where the increments h0, computed from log r since #766, are right. Nothing in the package reads r but serialisation, which keeps inf and 0 through to_dict, JSON and from_dict. So r stays as it is, IEEE's representation of a value out of range, symmetric with h0 underflowing to 0 there. Any finite stand-in would be wrong, and a log-scale attribute would be new API with no reader. The model's docstring now documents r and d and what r is at a run-off; CoxPH.fit's Returns says so too. A test checks r against the log sums and the saved model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Cox risk-set weight r documented as inf at a run-off (#777, CoxPH.r)
Centring.finish's refusal quoted the baseline's parameters at Z = 0,
which it could not compute ("parameters [inf, 1.5]"), and said every
failure was an over- or underflow. #714's case is a likelihood that
precision does not reproduce, at finite values. It also named a moved
location or scale "running on" by the run-off check made at the
covariate means, as if at Z = 0. It now quotes the parameters by name
where the fit ran, at the means. It then gives one reason: the
parameters that would be out of range at Z = 0, named without values;
or the finite values there that do not reproduce the likelihood; or a
derivative of the move that is not finite. A moved parameter the search
found running on is said to run on "at the covariate means, where the
fit runs". Numbers are formatted plainly.
The semi-parametric refusal (Cox, proportional odds, Fine-Gray) said the
move by exp(beta'center) over- or underflowed even where beta'center was
0 and only the rows' exp(beta'Z) did: it now says so where the move is
not at fault. What is refused is unchanged. CoxFrailty's refusal, CoxPH's
from its first Cox fit, said to fit with center=True, which CoxFrailty
does not take. Its Cox fit now keeps its baseline at the means: only its
coefficients are used, and they are the same either way. Its own refusal
ends with advice it can follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Refusal at Z = 0 names what it can compute (#777, refusal at Z = 0)
, canonical order) canonical_order (the row order of every parametric regression, #728) sorted by time, censoring, count, truncation and covariates. canonical_rows (the semi-parametric fits', #772) puts the count last, so that counted data sort as their rows expanded one per unit do. The orders now agree: the count is canonical_order's last key, and grouped_data (the frailty fits', which follows canonical_order) puts it after the group. Only rows tied in time, censoring and truncation, with different counts, change places, so only the order of the likelihood's sums moves. On 84 fits of data with such ties (WeibullPH/AFT/PO, ExponentialPH, LogNormalAFT, WeibullFrailty, CoxFrailty, 12 seeds) every verdict is the same and the answers move at rounding: coefficients by up to 1e-13, a CoxFrailty theta by 2e-7 (its search tolerance), a theta at 0 from 5e-21 to 1e-16. The row-order tests (#744, #757, #772), the bootstrap tests, the doctests of the regression package and the executed cells of the two regression guides are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Parametric and frailty fits sort the count last, as canonical_rows (#777, canonical order)
…start) ARA failed with "Could not find a good solution" on a single item's ExpoWeibull data: the lifetime start, fitted to the gaps as from age 0, ran off to a power law ending at the longest gap, so a gap from any later virtual age had zero likelihood at every default start. Where no default start is finite, each restoration start now takes the lifetime fitted to the gaps from the ages it leaves (end ages left truncated at the start ages), the best lifetime for that restoration. The ARA and Kijima likelihoods expose those ages (`virtual_ages`). A single item's perfect-repair start is no longer fitted twice (it is the same fit as the default start's). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Renewal fits start where the likelihood is finite (#777, ARA run-off start)
…ar.hf(inf)) hf was df / sf: 0/0 at infinity, and wherever both underflow far in the tail (a hazard-scale fit's hazard was nan from x = 1e6), with a RuntimeWarning. It is now one exp of the logs of its terms (on the hazard scale log(-dS/deta / S) is eta itself), and at infinity the limit along the spline's last piece, linear in log x with slope s: on the hazard scale a Weibull's of shape s (inf, the constant, or 0), and 0 on the odds and normal scales, as a LogLogistic's and a LogNormal's hazard are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe RoystonParmar.hf at infinity is its limit, quietly (#777, RoystonParmar.hf(inf))
…derivatives) Below y = beta x = 1000 the Gamma hazard was f / S, exp(log f - log S), whose derivatives in x and alpha are differences of terms of size 1 that cancel to a size 1 / y: alpha's was 2e-7 off mpmath's at y = 100 and 4e-5 at 1000, x's 8e-8 at 1000, and the second derivative in alpha off by more than itself. The continued fraction of the upper incomplete gamma now takes over from y = 30 (and a + 2 sqrt(a) + 1), as an autograd primitive rather than a traced loop (traced from y = 10 it made a Gamma PH fit 1.7 times slower). Its derivative in y is r (a - 1) g / y, with no cancelling terms; in alpha the fraction's own derivative, a complex step through the same Lentz loop (about the cost of the value); the second derivatives are carried along the loop (forward mode), so a Hessian is exact. Values, first and second derivatives are within 1e-14 of mpmath from y = 30 up. Six GammaPH/AFT fits take 1.01 times as long in all (1.08 at most, on data far into the tail; best of runs alternated with develop's). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Gamma.hf's derivatives keep their digits from y = 30 (#777, Gamma.hf derivatives)
…ewal run-off verdict) An ExpoWeibull life running to its power-law limit (beta to 1e11, mu to 1e-11) left the ARA, Kijima and G1 fits stopped somewhere on the ridge: called "unverified", with the start's own lifetime fit warning of its run-off besides (twice), in terms of the start. A fit that is not a verified maximum now has its life fitted with the restoration held at its value, to the times whose likelihood is then the fit's (each gap's end age left truncated at its virtual age; G1's gaps rescaled). Where that fit has no finite maximum, by the family's own check, the renewal fit's maximum is "no finite maximum" and it warns in the package's form, with the life's reason and advice (the values its own), and it takes the life's parameters where they are further up the ridge. The start fits are quiet. Other fits are unchanged: on the 210 renewal benchmarks of #774 only the run-off data set's verdicts change. The fit is not walked further along the ridge: its supremum is not attained and the parameters are meaningless there, as for the univariate ExpoWeibull fit, which also stops where its check finds the run-off (one Kijima-II case stays at -43.93, ARA-inf on the same model reaches -43.81). Builds on the virtual_ages of the ARA run-off start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe A renewal fit whose life runs off says "No finite maximum" (#777, renewal run-off verdict)
A letter straight after the closing backticks (``qf``s) left the inline literal open, which the docs build (sphinx -W) reports as an error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
…uatqe Changelog: close the qf literal in #769's entry
This was referenced Oct 9, 2026
Merged
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.
Brings master up to develop: 115 merged pull requests since #639.
The work covers issues #597, #608, #611, #613, #624, #626, #627, #630–#634, #642, #645–#667, #671, #710, #711, #714, #715, #728, #730, #731, #733, #746, #760, #769 and #777. The details of each are in
docs/changelog.rstunder Unreleased.The full test suite runs on this merge, as master's CI does on every push: pytest with
--run-mland the doctests on Python 3.11, 3.12 and 3.13, plus the conformance tests, the docs build (sphinx -W) and the wheel check.🤖 Generated with Claude Code
https://claude.ai/code/session_01TRhKL2fJBiAAfNo9rihwts
Generated by Claude Code