Final pre-freeze corrections for the discrimination-not-calibration research study.
- Tightened the executable H1 and H3 estimands so the primary PH-violation
analyses exclude the null-effect arm;
effect_size = 0is now treated as a negative-control arm. - Required complete common support for H3: each matched support point must contain all structural DGPs and all PH/baseline DGPs before contributing to the primary contrast.
- Corrected the headline bootstrap uncertainty: the reported quantile standard error is now the bootstrap standard deviation, with bootstrap Monte Carlo error stored separately.
- Added bootstrap SE and percentile intervals for H1-H4 and propagated them to generated manuscript macros and Table 6.
- Made the IPCW availability gate audit the production evaluation sample size instead of the scenario training size.
- Made experiment freezing consume the IPCW and grid-convergence gate artifacts, verify that both passed, and embed their hashes and result summaries in the lock.
- Included full environment metadata and gate evidence in the experiment lock hash, and made strict verification reject locks created from dirty source.
R parity fixtures, and the maturity classifier returned to
5 - Production/Stable.
- Multi-state generators now reject underflowed observed transitions. A
transition with
start == stopis a zero-exposure counting-process interval, so it is rejected instead of emitted.
- Added the truth-based survival-distribution evaluation study under
research/discrimination-not-calibration/, with deterministic paired simulations, known-truth metrics, provenance capture, pre-freeze safeguards and manuscript-generation scripts. - Corrected pre-freeze study issues found during review: native-time distribution metric evaluation for step-function predictions, exact log-logistic truth for the generator's clipped law, paired Monte Carlo contrasts, corrected second-stage MCSE propagation, append-only raw result shards, bounded parallel submission and removal of tracked pilot result tables.
- Raised dependency floors for
scipy,pyarrow,scikit-survival,pytest,pytest-benchmark,black,isort,invoke,pre-commitandmkdocstrings.
-
R parity fixtures.
scripts/generate_r_fixtures.Rfreezes real output from RgenSurv1.0.6 intotests/fixtures/r_parity/, andtests/test_r_parity.pycompares against it. CI never needs R installed.Parity is on distributions, not values: R draws from the Mersenne Twister and we draw from PCG64, so identical numbers are impossible however faithful the port. Each comparison is a statistic with a three-sigma band allowing for the Monte Carlo error on both sides, and both sides are frozen, so the tests are deterministic.
cphm,cmmandthmmagree with R. -
tdcm's divergence from R is now pinned as intended behaviour. Asked for Weibull marginals withdist.par = c(1, 2, 1, 2), R'sdgBIVreturns mean 2 and median2*log(2)— chi-square with two degrees of freedom — ignoring the parameterisation it was given. Against the Weibull it was asked for, a KS test givesp = 0for R andp = 0.87for ours. This is the same defect 2.0.0 corrected for the exponential case; a test now records it for the Weibull case so our sampler is not "fixed" into agreement with R.
Development Status :: 5 - Production/Stable, replacing4 - Beta. The three conditions the roadmap set are met: distribution tests and property-based tests across all twelve generators, and R parity fixtures for the four ported from R.
Property-based tests across all twelve generators, and the two tdcm defects
they found.
gen_tdcmraises whenexp(beta[0] * z)leaves the range of a float instead of returning a frame. A Weibulldist_parshape below 1 is an exponent above 1 in(-log(1 - u) / a) ** (1 / b), so the covariate reached the tens of thousands and the linear predictor overflowed.t = log_term / infis exactly 0.0, andstatus = (t <= c)then reported an observed event at time zero for every subject, in a zero-length risk interval; with the sign ofbeta[0]flipped it underflowed instead and every subject came back censored. Both frames had the right columns, the right dtypes and no NaN. No correct call changes behaviour — the affected parameter combinations never produced usable data.gen_tdcmrejectscorrat the endpoints.validate_gen_tdcm_inputsallowed(0, 1]for Weibull and[-1, 1]for exponential, and the documentation promised the same, but the Gaussian copula underneath needs strict inequalities: its covariance[[1, corr], [corr, 1]]is singular at|corr| = 1. Those values already failed — deeper in, reporting a different range from a helper the caller never named. They now fail at the model's own boundary, quoting the range it actually enforces.
tests/test_properties.pydrives every generator from Hypothesis: output invariants (the column contract, no NaN or infinity,statuswithin its declared set, no zero-length risk intervals, no event at time zero), the seed contract (the same seed gives the same frame, and anintagrees with the generator it seeds), and rejection of out-of-domain values. A test fails if a model is registered without a strategy.
- The
corrrange on the TDCM page and in thegen_tdcmdocstring now match what is enforced.
A general multistate engine, with the two illness-death models rebuilt on top of
it. The engine is the reason for the major version: gen_cmm and gen_thmm
produce different data for a given seed than they did in 2.1.0.
gen_cmmandgen_thmmno longer reproduce their 2.1.0 output for a given seed. Both are now configurations ofgen_multistate, which draws one candidate per outgoing edge per visit where the old implementations drew all three latent times up front. Their columns, dtypes and id bases are unchanged —thmmstill numbers subjects from 1 — and every distribution test passes untouched, so analysis code keeps working. Only the numbers move. Pin the version alongside the seed if you need to reproduce earlier results.- NaN and infinity are rejected wherever a number is expected. Calls that
previously returned a frame quietly full of NaN, raised an unrelated
OverflowErrorfrom NumPy, or — forgen_recurrent_events(followup_time=nan)— never returned at all, now raise aValidationErrornaming the argument. Thirty-nine arguments across the twelve generators were affected. A NaN amongbetaswas previously reported asNumericSequenceErrorand is now aParameterError, since NaN is numeric and that error named the wrong problem.
-
gen_multistate, an engine over an arbitrary transition graph. Each edge is aTransitioncarrying its ownBaselineHazardand coefficients, so the intensity of thei -> jtransition ish0_ij(t) * exp(X'beta_ij). Both clocks are supported:clock="forward"measures the hazard from entry to the study, making the process Markov, andclock="reset"restarts it at each state, making it semi-Markov. Either canonical layout can be returned. A state with no outgoing edge is absorbing, and cycles are allowed, so recovery is a transition like any other.It is not reachable through
generate(): a graph is a list of objects rather than a set of scalars, so there is no string form and no command-line equivalent. -
py.typed. Every public function was annotated and mypy checked them on every commit, but PEP 561 tells a type checker to ignore an installed package's inline types without the marker, so none of it reached anyone. A downstreammypyreportedCannot find implementation or library stub for module named "gen_surv"and acceptedgen_cphm(n="not an integer", ...)without comment. -
ensure_finite, for arguments with no sign constraint.cphm'sbetais a log hazard ratio, so no positivity check reached it and nothing had been validating it at all.
cmmandthmmchecked only the length ofrate, never its contents, so a negative entry surfaced asValueError: scale < 0from inside NumPy.
- The engine advances the whole cohort a wave at a time: subjects sharing a
state are drawn for together, so the sampling and the hazard inversions are
array operations. At ten thousand subjects,
thmmis 8.8x faster than in 2.1.0 — it had always looped per subject — whilecmm, which had been vectorised, is 1.7x slower, the price of generality.
- Distribution tests for the engine: each edge's intensity recovered by occurrence over exposure, the sojourn in a state exponential in the summed intensity, the destination share following the competing intensities, the two clocks identical for a constant hazard and sharply different for a rising one, cyclic graphs and absorbing states.
tests/test_input_hardening.pywalks every numeric argument of every model, scalar and sequence, and requires aValidationError.tests/test_packaging.pychecks thepy.typedmarker on the imported package, and that every public parameter and return value really is annotated: a marker promising types that were not there would be worse than no marker.
A twelfth model, the configuration and ground truth behind every dataset, a baseline hazard abstraction, and two sampler corrections. The documentation is rebuilt from scratch on MkDocs; the API reference on the published site had been empty since it was written.
Two generators produce different data for the same seed than they did in
2.0.1: piecewise_exponential with two or more breakpoints, and tdcm. Both
were producing wrong data, described below. Pin the version alongside the seed
if you need to reproduce earlier results.
-
gen_recurrent_events, a twelfth model, for events that repeat within a subject. Three processes matching the models the data is analysed with:ag(Andersen-Gill), andpwp_ttandpwp_gt(Prentice-Williams-Peterson in total and gap time, the latter resetting the clock at each event). Exponential, Weibull and Gompertz baselines. Returns counting-process intervals with anenumcolumn. -
simulate(), returning aSimulationResult: the frame, theSimulationConfigthat produced it — parameters, seed and thegen_survversion — and atruthmapping of what a real dataset could never contain. Coefficients actually used, covariates, linear predictors, latent event and censoring times, cure status, cause-specific and transition times, and thetdcmcrossover time the frame cannot express. All twelve generators report.Most useful where several generators draw their coefficients when the caller omits them: there was previously no way to learn what they were, which quietly made those datasets useless for validating an estimator.
-
gen_surv.baseline, aBaselineHazardprotocol withhazard,cumulative_hazardand its inverse, implemented for exponential, Weibull, Gompertz, log-logistic and piecewise-constant hazards. A generator written against it accepts any shape:gen_recurrent_eventstakes a name or an object, so it already samples from families it does not name. -
The CLI can reach every registered model.
cmm,thmmandtdcmhad no way to pass their parameters and failed with aTypeError, while being advertised as valid values ofMODEL. Adds--rate,--dist,--corr,--dist-parand--lam.
gen_piecewise_exponentialdrew middle-interval events at the wrong hazard. The inversion assigned the event time and broke out of the loop, but the trailing "no event yet" branch ran anyway and overwrote it using the last rate. With breakpoints[1, 3]and rates[0.5, 2.0, 0.2], the hazard measured on[1, 3)was 0.201 against a declared 2.0. Only specifications with two or more breakpoints were affected.gen_tdcmhad a sign error in its post-crossover inversion, placing events drawn after the covariate switch before it and, for a large enoughbeta[1], at negative times: 6886 of 50000 subjects atbeta[1] = 1.0. The hazard ratio across the switch measured 4.58 whereexp(beta[1])was 2.0.tdcovdescribed the wrong interval. It was set from the branch the event time was drawn on, so a subject censored before its crossover was recorded as having switched though its covariate never did while observed. It is now whether the crossover was reached by the observed exit.summarize_survival_datasetcrashed on Windows. Its verbose report, the default, printed check and cross marks that a console on a legacy code page cannot encode, raisingUnicodeEncodeErrorbefore printing anything.GenSurvDataGeneratorwas not scikit-learn compatible.get_paramsreported onlymodelandreturn_type, soset_paramsraised on any model argument andclone— used internally by pipelines,GridSearchCVandcross_val_score— silently dropped every parameter, producing an estimator that failed on first use.
- Rebuilt on MkDocs with Material and mkdocstrings, replacing Sphinx.
docs/source/api/index.mdhad been written in mkdocstrings syntax, which Sphinx rendered as literal YAML, so the published API reference contained no signatures and no docstrings at all. Read the Docs is retired; the GitHub Pages site, built from the release tag, is the single home. - Rewritten rather than ported: per-model pages with parameters, mathematics, a worked example and a check that the parameters can be recovered; guides for baselines, ground truth, censoring, covariates, summaries, plotting, export, interoperability and the CLI; a full API reference over every public module.
- Three docstrings in
gen_surv/aft.pyindented theirReturnsandExamplessections six spaces instead of four, so numpydoc never parsed them and neither rendered. - The example scripts and Binder notebooks are repaired. Two passed a
qmatargument removed long ago, one of them describing the Gaussian-emission hidden Markov model that was never implemented; two passed a deprecated third coefficient togen_tdcm; and all three notebooks callednp.random.seed, which no generator reads, so they were not reproducible.
- The frozen-output regression suite had been inert:
tests/baselineswas empty, so every case hit apytest.skipand the run reported success while comparing against nothing, and it covered four of twelve generators. Baselines are committed for all twelve, a missing one now fails, and the tolerance is tightened from1e-6to1e-12. - Distribution tests for every generator, by probability integral transform, closing the roadmap's highest-priority gap.
- The documentation's examples are executed and their pasted output compared against what they print, and the example scripts and notebooks are run.
- Metadata migrated to PEP 621
[project], clearing three Poetry deprecations. The wheel gainsLicense-Expression: MITand shipsLICENSE. poetry.lockis committed, which makes CI reproducible and gives five cache keys that had never varied something to hash. Three workflows depended on it being tracked and had therefore never done anything.- Removes the
[tool.semantic_release]configuration no workflow read, and thepython-semantic-releasedependency that served it.
No library changes: gen_surv behaves exactly as it does in v2.0.0. This release
publishes the documentation site and relaxes dependency ranges that were pinned
to a single major version.
- The Sphinx documentation is now published to GitHub Pages at
https://diogoribeiro7.github.io/genSurvPy/. The site is built when a release
is published, from the tag that was uploaded to PyPI, so it always documents
the released version rather than unreleased work on
develop.
- Fixed the GitHub Pages Sphinx configuration. It inherited
html_static_pathfromdocs/source/conf.py, but Sphinx resolves that against the configuration directory, so_staticdid not exist andcustom.csswas never copied into the built site. It also pointedhtml_extra_pathat a.nojekyllfile that does not exist, whichsphinx.ext.githubpagesalready writes. The site base URL now comes from the workflow rather than being hardcoded.
pyarrowaccepts>=21,<26instead of^21.0.0, so it no longer holds installations back to the 21.x series.- Widened the development and documentation dependency ranges for
pytest,invoke,black,isort,flake8,scikit-survival,myst-parserandsphinx-design.
Completes the illness-death models. gen_cmm and gen_thmm previously reported
only a subject's first transition, which left the 2 -> 3 transition entirely
absent from their output and several declared parameters with no effect. Both now
emit the full trajectory, so their returned shape changes and the major
version is incremented.
gen_cmmnow returns counting-process records with columnsid,start,stop,from_state,to_state,status,X0, replacing the previousid,start,stop,status,X0,transitionframe. Subjects contribute two or three rows rather than one: while in state 1 a subject is at risk of both1 -> 2and1 -> 3, so it gets a row for each over the same interval withstatusmarking whichever occurred, and a subject reaching state 2 gets a further2 -> 3row.- The
transitioncolumn is gone. It encoded the destination as an integer, and in the opposite sense to the R package'stranscodes, which made it easy to misread.from_stateandto_statestate the transition explicitly. gen_thmmnow returns the full state trajectory. Columns are unchanged (id,time,state,X0) but subjects contribute two or three rows instead of one: an entry observation in state 1 at time 0, then one observation per transition, or an observation in the occupied state at the censoring time.- Any code that assumed one row per subject from either generator needs to group
by
id.
gen_cmmignoredrate[4],rate[5]andbeta[2]. It drew the2 -> 3sojourn time and discarded it, so a third of its declared parameters could be changed with no effect on the output whatsoever. All six rates and all three coefficients now reach the result.gen_thmmignoredrate[2]andbeta[2]for the same reason.gen_cmmnow resolves its seed through the shared RNG helper, so it accepts anumpy.random.Generatoras well as anint, consistent with the other generators.- Tie handling in both generators now matches the R implementation, which treats a censoring time equal to the first transition time as an event.
- The CMM and THMM sections of the algorithms and theory pages now describe the
emitted layouts, including why the two differ:
gen_cmmreturns transition intervals andgen_thmmreturns states observed at times, mirroringgenCMMandgenTHMMin the R package. The 1.3.0 note recording the missing trajectory as a known limitation has been removed, since it is now fixed.
- Added
tests/test_multistate_schema.pywith 21 structural tests: row counts per subject, both competing transitions being at risk over a shared interval, the2 -> 3row appearing exactly when illness was observed, the reset clock on entry to state 2, monotone trajectories, death being terminal, and direct guards that every rate and coefficient influences the output. The parameter-influence tests were confirmed to fail against the 1.3.0 code. - Replaced the two
gen_cmmsnapshot tests, which asserted exact values of the old one-row-per-subject frame, with reproducibility and schema tests.
A scientific-correctness release. Three of the fixes below change the numbers the simulators produce, so results generated with 1.2.0 or earlier are not comparable with results from this release.
- The bivariate sampler produced the wrong distribution entirely. It mapped
correlated normals to uniforms with
u = 1 - exp(-z**2 / 2), which is the chi-squared(2) CDF applied to a chi-squared(1) variable. The composed transform reduced toz**2 / (2 * lambda), so a requested Exponential(1) marginal was reallychi2(1) / 2with mean 0.5 instead of 1.0. Replaced with the normal CDF, making this a correctly specified Gaussian copula with exact marginals. - Negative dependence was unreachable in the bivariate sampler. Squaring the
normals mapped
+rand-ronto the same positive dependence, so a requested correlation of-0.8produced roughly+0.64. The sign is now preserved. gen_tdcmwas affected by both of the above, since it draws its covariates from that sampler.- The competing-risks generators fabricated events. When fewer than two
distinct statuses appeared in a sample, both generators overwrote
status[0]andstatus[1]with event labels, attaching events to subjects whose event times had not occurred. A cause that is absent from a finite sample is a valid stochastic outcome, so this post-processing has been removed. gen_tdcmrejected its own documented signature. The docstring specified two coefficients and the model uses two, but validation required three and silently ignored the third, so the documented call raisedLengthError. Two are now accepted; three still work but emit aDeprecationWarning.
- Event times, covariates and statuses differ from 1.2.0 for
gen_tdcm,sample_bivariate_distribution,gen_competing_risksandgen_competing_risks_weibull. This is the point of the release, but it does mean any stored 1.2.0 output should be regenerated. scipyis now a declared runtime dependency. It was already installed as a transitive dependency oflifelines, so this should not change resolution.- The PyPI maturity classifier moves from
5 - Production/Stableto4 - Beta. A package that has just corrected the marginal distribution and the dependence sign of one of its core samplers is not accurately described as production-stable, and known correctness gaps remain: CMM and THMM report only the first transition rather than a full trajectory, and the CLI cannot drive every registered generator. The classifier is intended to return to5 - Production/Stableonce the multistate output schema lands.
- Unified the RNG contract.
sample_bivariate_distribution,gen_tdcmandgen_thmmdrew from the global NumPy random state and could not be seeded;gen_thmmhad noseedparameter at all. All three now acceptseed, which may be anint, anumpy.random.Generatorfor sharing one stream across simulators, orNone. No simulator touches the global state. - Censoring draws in
gen_tdcmandgen_thmmnow share the caller's generator rather than creating an unseeded one, so a single seed reproduces a whole dataset.
- THMM was documented as a Hidden Markov Model, which it is not. The name means Time-Homogeneous Markov Model. The docs additionally described latent states with Gaussian emissions, none of which exists in the implementation. Rewritten to describe the three-state model with constant transition intensities that the code actually simulates, and re-cited to Andersen et al. instead of an HMM textbook. The known limitation that only the first transition is emitted is now stated explicitly.
- Added
tests/test_statistical_correctness.py: Kolmogorov-Smirnov tests for the exponential and Weibull marginals, moment checks, a dependence-sign test, a monotonicity test, a Spearman check against the Gaussian copula identity, no-fabrication tests for competing risks, and seed-reproducibility plus global-state-independence tests for every affected generator. Each was confirmed to fail against the 1.2.0 code. - Replaced two tests that asserted the fabricated competing-risks statuses as required behaviour, and removed a property-based assertion that every sample must contain at least two distinct statuses, which is not a property the model guarantees.
- Python 3.10 is no longer supported; the minimum supported version is now 3.11.
This is required by the current
numpy(>=2.3) andlifelines(>=0.30) releases, neither of which ships for 3.10. - Removed the
devextra. It declaredProvides-Extra: devwith no dependencies behind it, sopip install gen-surv[dev]never actually installed anything. Usepoetry install --with devfor development, orpip install scikit-survivalfor the optional scikit-survival integration.
- Added official support for Python 3.13; CI now tests 3.11, 3.12 and 3.13.
- Raised the
lifelinesfloor to 0.30.3. Earlier releases crash inadd_at_risk_countsunder numpy 2.x withTypeError: only 0-dimensional arrays can be converted to Python scalars, which broke every survival-curve and covariate-effect plot. - Made optional dependencies lazy, normalized identifier handling and stabilized the test suite (#116).
- Fixed dependency resolution, which failed for every job because the declared
Python floor (3.10) was incompatible with the pinned
numpyandlifelinesconstraints. - Dropped the
scripts/check_version_match.pysteps and thebump-versionworkflow. The script had been deleted in #86/#90, so all three workflows referencing it failed. Release tags are now created manually.
- Added Zenodo metadata.
- Updated dependency constraints (
numpy,pandas,matplotlib,pyarrow,typer,click) and refreshed pinned GitHub Actions.
- export datasets to RDS files
- test workflow runs on a Python version matrix
- scikit-learn compatible data generator
- compatibility helpers for lifelines and scikit-survival
- updated usage examples and tutorials
- document optional scikit-survival dependency throughout the docs
- auto-tag releases using the version check script
- README quick example uses
covariate_range
- ensure absolute path resolution in
conf.py - drop unsupported theme option
- define bibliography anchors and headings
- fix tutorial links to non-existing docs
- add additional references to the bibliography
- add CLI integration test
- expand piecewise generator test coverage
- remove fix_recommendations.md
- Align pyproject version with GitHub tag
- Add project Code of Conduct
- Fix import
(
3cdb59a)
-
Add tasks.py with Invoke CLI for project automation (
87868b8) -
Badges (
79c1d0d) -
Docs (
4b2704e) -
Update pyproject (
127d1f6)
-
Add mathematical foundations page for all survival models (
8472ff1) -
Add roadmap for advanced survival models and extend TODO (
1990109) -
Add usage examples for all models in index.md (
57c5fb0) -
Fix version number in pyproject (
6cff868)
- Update documentation
(
d34e32f)
- docs: Document generic interface
generate()and update examples (55e22a4)
- Fix pyproject toml
(
a06f58a)
- Fix readme
(
f4897cd)
- Docs
(
75417f1)
- Add documentation to readthedocs
(
1b00d74)
- Fix codecoverage
(
16bc525)
- Bump version
(
5cc649d)
- Add documentation to readthedocs
(
c33f666)
- Fix git hub actions
(
c9559ba)
- Bump version
(
e111826)
-
Add name to pyproject (
6a3a8f3) -
Fix mixing tags creation (
93ac8a0) -
Fix pyproject toml (
8537f63) -
Fix recommended by copilot (
f188f76) -
Fix semantic release (
3d18b02) -
Fix version bump (
e5114de) -
Github address (
43f9d93)
-
Add GitHub Actions workflow for test automation (
0d57884) -
Add GitHub Actions workflow for test automation (
33e1e40)