From 37a054c2c8e8cd5fe4d75964b1b024707dcd2162 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 22:12:14 +0000 Subject: [PATCH 1/3] Resolve recurrent dead-code/docs items (DEVELOPMENT.md 4B) - Extract duplicated _validate_memory from ARA/ARI into a shared validate_memory helper in utils/recurrent_utils.py - Fix copy-pasted "Parameters of the Duane model" docstrings in CrowAMSAA to read "Crow-AMSAA model" - Fix has_left_censoing typo -> has_left_censoring in the NHPP parametric and proportional-intensity regression fitters https://claude.ai/code/session_01PeqiCJotMqG5ujuJdvVvzf --- DEVELOPMENT.md | 10 +++++++--- surpyval/recurrent/parametric/crow_amsaa.py | 8 ++++---- surpyval/recurrent/parametric/nhpp_fitter.py | 6 +++--- .../regression/nhpp_proportional_intensity.py | 8 ++++---- surpyval/recurrent/renewal/ara.py | 14 +++----------- surpyval/recurrent/renewal/ari.py | 14 +++----------- surpyval/utils/recurrent_utils.py | 15 +++++++++++++++ 7 files changed, 39 insertions(+), 36 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index e6e36f4..297aeb5 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -59,11 +59,15 @@ theme; items already resolved are marked **[done]**. `ParametricRecurrenceModel`; recurrence-regression results are served by `ProportionalIntensityModel`. **[done]** (file removed) - `_validate_memory` duplicated verbatim in `renewal/ara.py` and - `renewal/ari.py`. + `renewal/ari.py`. **[done]** (shared `validate_memory` in + `utils/recurrent_utils.py`) - 16 copy-pasted docstrings read "Parameters of the Duane model" inside - `CrowAMSAA`/`CoxLewis`. + `CrowAMSAA`/`CoxLewis`. **[done]** (`CrowAMSAA` now reads "Crow-AMSAA + model", `CoxLewis` "Cox-Lewis model") - `has_left_censoing` typo (`nhpp_fitter.py`, NHPP regression file). -- Doubled `to_xrd()` call in `nonparametric/mcf.py:106-107`. + **[done]** (renamed to `has_left_censoring` in both files) +- Doubled `to_xrd()` call in `nonparametric/mcf.py:106-107`. **[done]** + (single `to_xrd()` call) **C. Simplification** - The multi-start MLE fit scaffolding is copy-pasted across the four repair diff --git a/surpyval/recurrent/parametric/crow_amsaa.py b/surpyval/recurrent/parametric/crow_amsaa.py index e374812..5b49a20 100644 --- a/surpyval/recurrent/parametric/crow_amsaa.py +++ b/surpyval/recurrent/parametric/crow_amsaa.py @@ -61,7 +61,7 @@ def cif(self, x, *params): x : float The value at which CIF is evaluated. params : tuple - Parameters of the Duane model. + Parameters of the Crow-AMSAA model. Returns @@ -85,7 +85,7 @@ def iif(self, x, *params): x : float The value at which IIF is evaluated. params : tuple - Parameters of the Duane model. + Parameters of the Crow-AMSAA model. Returns ------- @@ -108,7 +108,7 @@ def log_iif(self, x, *params): x : float The value at which log(IIF) is evaluated. params : tuple - Parameters of the Duane model. + Parameters of the Crow-AMSAA model. Returns ------- @@ -131,7 +131,7 @@ def inv_cif(self, N, *params): N : float The number of events expected to have occured. params : tuple - Parameters of the Duane model. + Parameters of the Crow-AMSAA model. Returns ------- diff --git a/surpyval/recurrent/parametric/nhpp_fitter.py b/surpyval/recurrent/parametric/nhpp_fitter.py index 1417d96..275fb01 100644 --- a/surpyval/recurrent/parametric/nhpp_fitter.py +++ b/surpyval/recurrent/parametric/nhpp_fitter.py @@ -16,7 +16,7 @@ def create_negll_func(self, data): has_interval_censoring = True if 2 in c else False has_observed = True if 0 in c else False - has_left_censoing = True if -1 in c else False + has_left_censoring = True if -1 in c else False has_right_censoring = True if 1 in c else False x_l = x if x.ndim == 1 else x[:, 0] @@ -41,8 +41,8 @@ def create_negll_func(self, data): x_prev_l[c == 1] if has_right_censoring else np.array([]) ) - x_left = x_l[c == -1] if has_left_censoing else np.array([]) - n_left = n[c == -1] if has_left_censoing else np.array([]) + x_left = x_l[c == -1] if has_left_censoring else np.array([]) + n_left = n[c == -1] if has_left_censoring else np.array([]) x_i_l = x_l[c == 2] if has_interval_censoring else np.array([]) x_i_r = x_r[c == 2] if has_interval_censoring else np.array([]) diff --git a/surpyval/recurrent/regression/nhpp_proportional_intensity.py b/surpyval/recurrent/regression/nhpp_proportional_intensity.py index 46d46ba..d7e0505 100644 --- a/surpyval/recurrent/regression/nhpp_proportional_intensity.py +++ b/surpyval/recurrent/regression/nhpp_proportional_intensity.py @@ -80,7 +80,7 @@ def create_negll_func(self, data, dist): has_interval_censoring = True if 2 in c else False has_observed = True if 0 in c else False - has_left_censoing = True if -1 in c else False + has_left_censoring = True if -1 in c else False has_right_censoring = True if 1 in c else False x_l = x if x.ndim == 1 else x[:, 0] @@ -112,9 +112,9 @@ def create_negll_func(self, data, dist): ) # Untangle the left censored data - x_left = x_l[c == -1] if has_left_censoing else np.array([]) - n_left = n[c == -1] if has_left_censoing else np.array([]) - Z_left = Z[c == -1] if has_left_censoing else np.zeros((1, Z.shape[1])) + x_left = x_l[c == -1] if has_left_censoring else np.array([]) + n_left = n[c == -1] if has_left_censoring else np.array([]) + Z_left = Z[c == -1] if has_left_censoring else np.zeros((1, Z.shape[1])) # Untangle the interval censored data x_i_l = x_l[c == 2] if has_interval_censoring else np.array([]) diff --git a/surpyval/recurrent/renewal/ara.py b/surpyval/recurrent/renewal/ara.py index 4184ff4..5b26344 100644 --- a/surpyval/recurrent/renewal/ara.py +++ b/surpyval/recurrent/renewal/ara.py @@ -8,6 +8,7 @@ from surpyval.utils.recurrent_utils import ( handle_xicn, reject_left_truncation, + validate_memory, validate_renewal_censoring, ) @@ -81,15 +82,6 @@ class ARA: >>> model = ARA.fit(x, i, c=c, m=2) """ - @staticmethod - def _validate_memory(m): - if m == np.inf: - return - if not (isinstance(m, (int, np.integer)) and m >= 1): - raise ValueError( - "m must be a positive integer or numpy.inf; got {!r}".format(m) - ) - @staticmethod def _build_sampler(model): rho = model.rho @@ -179,7 +171,7 @@ def fit_from_recurrent_data(self, data, dist=Weibull, m=1, init=None): RenewalModel A fitted renewal model. """ - self._validate_memory(m) + validate_memory(m) validate_renewal_censoring(data.c, type(self).__name__) reject_left_truncation(data, type(self).__name__) @@ -293,6 +285,6 @@ def fit_from_parameters(self, params, rho, m=1, dist=Weibull): ARA An ARA object built from the supplied parameters. """ - self._validate_memory(m) + validate_memory(m) model = dist.from_params(params) return self._make_model(model, rho, m) diff --git a/surpyval/recurrent/renewal/ari.py b/surpyval/recurrent/renewal/ari.py index 0fe59a1..abd80ff 100644 --- a/surpyval/recurrent/renewal/ari.py +++ b/surpyval/recurrent/renewal/ari.py @@ -7,6 +7,7 @@ from surpyval.utils.recurrent_utils import ( handle_xicn, reject_left_truncation, + validate_memory, validate_renewal_censoring, ) @@ -69,15 +70,6 @@ class ARI: >>> model = ARI.fit(x, i, m=1, dist=CrowAMSAA) """ - @staticmethod - def _validate_memory(m): - if m == np.inf: - return - if not (isinstance(m, (int, np.integer)) and m >= 1): - raise ValueError( - "m must be a positive integer or numpy.inf; got {!r}".format(m) - ) - @staticmethod def _build_sampler(model): dist = model.model.dist @@ -185,7 +177,7 @@ def fit_from_recurrent_data(self, data, dist=CrowAMSAA, m=1, init=None): ARI A fitted ARI object. """ - self._validate_memory(m) + validate_memory(m) validate_renewal_censoring(data.c, type(self).__name__) reject_left_truncation(data, type(self).__name__) @@ -294,5 +286,5 @@ def fit_from_parameters(self, dist_params, rho, m=1, dist=CrowAMSAA): ARI An ARI object built from the supplied parameters. """ - self._validate_memory(m) + validate_memory(m) return self._make_model(dist, dist_params, rho, m) diff --git a/surpyval/utils/recurrent_utils.py b/surpyval/utils/recurrent_utils.py index 82a6c00..7861f6e 100644 --- a/surpyval/utils/recurrent_utils.py +++ b/surpyval/utils/recurrent_utils.py @@ -54,6 +54,21 @@ def reject_unsupported_nonparametric( ) +def validate_memory(m: object) -> None: + """ + The Arithmetic Reduction of Age/Intensity models (``ARA``/``ARI``) are + parameterised by an integer memory ``m`` (how many prior failures the + repair acts on), with ``m = numpy.inf`` recovering the infinite-memory + limit. Reject anything that is neither a positive integer nor ``inf``. + """ + if m == np.inf: + return + if not (isinstance(m, (int, np.integer)) and m >= 1): + raise ValueError( + "m must be a positive integer or numpy.inf; got {!r}".format(m) + ) + + def validate_renewal_censoring(c: npt.ArrayLike, model_name: str) -> None: """ The renewal models only define likelihood contributions for exact events From 88a62d591d8f8efeb4c404b3fb36e689a846bf50 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 22:18:07 +0000 Subject: [PATCH 2/3] Remove resolved section 4B from DEVELOPMENT.md All dead-code/stubs/docs items in section 4B are now resolved, so drop the subsection. https://claude.ai/code/session_01PeqiCJotMqG5ujuJdvVvzf --- DEVELOPMENT.md | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 297aeb5..cbe2fdc 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -53,22 +53,6 @@ A current-state audit of the whole `recurrent/` package (parametric intensity, renewal, regression, nonparametric, competing risks). Findings are grouped by theme; items already resolved are marked **[done]**. -**B. Dead code / stubs / docs** -- `ParametricRecurrenceRegressionModel` (`parametric_regression.py`) — was a - `# TODO` stub, never imported/exported and a duplicate of - `ParametricRecurrenceModel`; recurrence-regression results are served by - `ProportionalIntensityModel`. **[done]** (file removed) -- `_validate_memory` duplicated verbatim in `renewal/ara.py` and - `renewal/ari.py`. **[done]** (shared `validate_memory` in - `utils/recurrent_utils.py`) -- 16 copy-pasted docstrings read "Parameters of the Duane model" inside - `CrowAMSAA`/`CoxLewis`. **[done]** (`CrowAMSAA` now reads "Crow-AMSAA - model", `CoxLewis` "Cox-Lewis model") -- `has_left_censoing` typo (`nhpp_fitter.py`, NHPP regression file). - **[done]** (renamed to `has_left_censoring` in both files) -- Doubled `to_xrd()` call in `nonparametric/mcf.py:106-107`. **[done]** - (single `to_xrd()` call) - **C. Simplification** - The multi-start MLE fit scaffolding is copy-pasted across the four repair fitters (`GeneralizedRenewal`, `GeneralizedOneRenewal`, `ARA`, `ARI`): the From bc8890711930e5dd68771b32930d44210d40570c Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 22:20:50 +0000 Subject: [PATCH 3/3] Wrap Z_left assignment to satisfy flake8 line-length The has_left_censoring rename pushed the Z_left line to 80 chars; wrap it under the 79-char limit. https://claude.ai/code/session_01PeqiCJotMqG5ujuJdvVvzf --- surpyval/recurrent/regression/nhpp_proportional_intensity.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/surpyval/recurrent/regression/nhpp_proportional_intensity.py b/surpyval/recurrent/regression/nhpp_proportional_intensity.py index d7e0505..b68d4c1 100644 --- a/surpyval/recurrent/regression/nhpp_proportional_intensity.py +++ b/surpyval/recurrent/regression/nhpp_proportional_intensity.py @@ -114,7 +114,9 @@ def create_negll_func(self, data, dist): # Untangle the left censored data x_left = x_l[c == -1] if has_left_censoring else np.array([]) n_left = n[c == -1] if has_left_censoring else np.array([]) - Z_left = Z[c == -1] if has_left_censoring else np.zeros((1, Z.shape[1])) + Z_left = ( + Z[c == -1] if has_left_censoring else np.zeros((1, Z.shape[1])) + ) # Untangle the interval censored data x_i_l = x_l[c == 2] if has_interval_censoring else np.array([])