LifeModel.__init__ declares:
phi_param_map: dict[str, int]
phi_bounds: tuple[tuple[int | None, int | None], ...]
GeneralLogLinear passes callables of Z for both, because its parameterisation depends on the covariate dimension — there is one beta_i per stress column, so neither the map nor the bounds can be known until the data arrives:
lambda Z: {"beta_" + str(i): i for i in range(Z.shape[1])}
lambda Z: (((None, None),) * Z.shape[1])
This is already known and deliberate — accelerated_life/__init__.py excludes it from LIFE_MODELS for exactly this reason, so it cannot be rebuilt from a name and cannot be deserialised. It is also not exported from surpyval, and nothing in the package consumes it.
Consequences
Every other accelerated-life module is now in the type ratchet (#143). general_log_linear is the one exclusion, and it needs two # type: ignore[arg-type] comments to typecheck at all, because the values genuinely are not the declared types.
Fixed in passing
The two arguments were swapped: the bounds lambda sat in the phi_param_map slot and the param-map lambda in the phi_bounds slot. Verified before the fix:
phi_param_map(Z) -> ((None, None), (None, None), (None, None)) <- bounds-shaped
phi_bounds(Z) -> {'beta_0': 0, 'beta_1': 1, 'beta_2': 2} <- a param map
Nothing consumed either, so the swap had no observable effect, but it would have bitten whoever finished the model.
Options
- Widen the contract. Let
LifeModel accept either the static values or callables of Z, resolved once the covariate dimension is known. Everything else keeps its current shape, and GeneralLogLinear becomes a first-class life model that can join LIFE_MODELS and the ratchet.
- Give it a separate base. A
DimensionDependentLifeModel with phi_param_map(Z) / phi_bounds(Z) as real abstract methods, and have the fitter branch on which base it is. Honest about there being two kinds, at the cost of a second hierarchy.
- Remove it. It is unexported, unfinished and unused; multi-stress fitting is otherwise unavailable, so this closes off a capability rather than deleting dead weight.
I would lean towards 1 — the callables already work, they just are not declared — but it touches every life model's constructor signature, so it is worth deciding rather than drifting into.
Context
surpyval/univariate/regression/accelerated_life/general_log_linear.py
surpyval/univariate/regression/accelerated_life/lifemodel.py
surpyval/univariate/regression/accelerated_life/__init__.py (the LIFE_MODELS exclusion comment)
- Ratchet list in
pyproject.toml under [[tool.mypy.overrides]]
LifeModel.__init__declares:GeneralLogLinearpasses callables ofZfor both, because its parameterisation depends on the covariate dimension — there is onebeta_iper stress column, so neither the map nor the bounds can be known until the data arrives:This is already known and deliberate —
accelerated_life/__init__.pyexcludes it fromLIFE_MODELSfor exactly this reason, so it cannot be rebuilt from a name and cannot be deserialised. It is also not exported fromsurpyval, and nothing in the package consumes it.Consequences
Every other accelerated-life module is now in the type ratchet (#143).
general_log_linearis the one exclusion, and it needs two# type: ignore[arg-type]comments to typecheck at all, because the values genuinely are not the declared types.Fixed in passing
The two arguments were swapped: the bounds lambda sat in the
phi_param_mapslot and the param-map lambda in thephi_boundsslot. Verified before the fix:Nothing consumed either, so the swap had no observable effect, but it would have bitten whoever finished the model.
Options
LifeModelaccept either the static values or callables ofZ, resolved once the covariate dimension is known. Everything else keeps its current shape, andGeneralLogLinearbecomes a first-class life model that can joinLIFE_MODELSand the ratchet.DimensionDependentLifeModelwithphi_param_map(Z)/phi_bounds(Z)as real abstract methods, and have the fitter branch on which base it is. Honest about there being two kinds, at the cost of a second hierarchy.I would lean towards 1 — the callables already work, they just are not declared — but it touches every life model's constructor signature, so it is worth deciding rather than drifting into.
Context
surpyval/univariate/regression/accelerated_life/general_log_linear.pysurpyval/univariate/regression/accelerated_life/lifemodel.pysurpyval/univariate/regression/accelerated_life/__init__.py(theLIFE_MODELSexclusion comment)pyproject.tomlunder[[tool.mypy.overrides]]