Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ other release, fixes included, the minor.

## [Unreleased]

## [0.15] - 2026-10-17

surpyval 0.25, and a joint model in a diagram. A unit of a surpyval joint
model of readings and failures (`surpyval.WeibullJointPH`), given its
readings so far, is a block of a `NonRepairableRBD` (`JointNode`, or its
`DynamicPrediction` given as the node): its reliability is from now, and
it takes part in every analysis of the diagram. A `RepairableRBD` refuses
it, as the units that replace it are new and without its readings. What
0.14 deprecated is gone.

Behaviour changes: surpyval 0.25 is required; `NodeState`'s `phase` is
refused (it is `since_scheduled`).

### Added

- A unit of a surpyval joint model of readings and failures, given its
Expand All @@ -27,6 +40,11 @@ other release, fixes included, the minor.

- surpyval 0.25 is required (for its joint models).

### Removed

- `NodeState`'s `phase`, deprecated in 0.14 and renamed `since_scheduled`
(#350): giving it raises `TypeError`, and a state has no `phase`.

## [0.14] - 2026-10-16

Fitted models in, every kind of model brought up to the diagram, and
Expand Down
13 changes: 6 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,13 +435,12 @@ or later.
- **Deprecations** follow CONTRIBUTING (one minor release's notice, a
`FutureWarning` through `repyability/utils/deprecation.py`). Each
release's removals stay gone through `test_removed_in_0_12.py`,
`_0_13.py` and `_0_14.py`: a removed argument raises `TypeError`. What
0.14 deprecates goes in 0.15 (`NEXT_REMOVAL`): `NodeState`'s `phase`,
renamed `since_scheduled` (#350, through `deprecation.renamed` for the
argument and `renamed_attribute` for reading it). A new deprecation
warns through those, `called` or `ignored`;
`test_what_0_14_deprecates_goes_in_0_15` fails once the version reaches
it.
`_0_13.py`, `_0_14.py` and `_0_15.py` (`NodeState`'s `phase`, #350):
a removed argument raises `TypeError`. 0.15 deprecates nothing; what it
would deprecate goes in 0.16 (`NEXT_REMOVAL`). A new deprecation warns
through `deprecation.renamed`, `renamed_attribute`, `called` or
`ignored`, with a test that fails once the version reaches its removal
(as `test_what_0_14_deprecates_goes_in_0_15` did).
- **No result depends on the unit of time (or of a covariate).** A step,
grid or probe is sized from the models (a typical life,
`failure_time_scale`), never in absolute time, and a proportional
Expand Down
6 changes: 4 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,10 @@ go once the minimum surpyval version has the fix.

A deprecation warns (a `FutureWarning`, through
`repyability/utils/deprecation.py`) for one minor release, and the next
removes it. `test_what_0_14_deprecates_goes_in_0_15` and its kind fail once
the version reaches the removal, as a reminder.
removes it. A test of the kind `test_what_0_14_deprecates_goes_in_0_15`
(in `test_api_consistency.py`) fails once the version reaches the removal,
as a reminder; the removed names then stay gone through
`test_removed_in_X_Y.py`.

## Releases (maintainers)

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/saving.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ path = Path(tempfile.mkdtemp()) / "plant.json"
rbd.to_json(path) # written; returns None
NonRepairableRBD.from_json(path).sf(30) == rbd.sf(30) # True
type(RBD.from_dict(data)).__name__ # 'NonRepairableRBD': the base class dispatches on type
data["type"], data["repyability_version"] # ('NonRepairableRBD', '0.14')
data["type"], data["repyability_version"] # ('NonRepairableRBD', '0.15')
```

What is saved:
Expand Down
2 changes: 1 addition & 1 deletion repyability/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
dependencies) via ``[tool.setuptools.dynamic]`` in ``pyproject.toml``.
"""

__version__ = "0.14"
__version__ = "0.15"
65 changes: 1 addition & 64 deletions repyability/rbd/node_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,8 @@
from dataclasses import dataclass
from typing import Optional

from repyability.utils.deprecation import renamed, renamed_attribute

#: The release that refuses ``phase``, renamed ``since_scheduled`` in 0.14
#: (#350).
_PHASE_REMOVAL = "0.15"
_PHASE_WHY = (
"it is the time since the last scheduled replacement or test, and a "
"phased mission has phases of its own"
)


@dataclass(frozen=True, init=False)
@dataclass(frozen=True)
class NodeState:
"""The current condition of a single RBD node.

Expand Down Expand Up @@ -93,9 +83,6 @@ class NodeState:
``virtual_age`` now and ``since_failure``: give ``age=
since_failure`` and ``virtual_age=virtual_age - since_failure``.
The simulations take it.
phase : float, optional
Deprecated: ``since_scheduled``, which it is renamed (#350), as a
phased mission has phases of its own. Refused in 0.15.

Raises
------
Expand All @@ -104,8 +91,6 @@ class NodeState:
is negative or not finite, ``down_for`` or ``maintenance`` is given
for a component that is alive, or another field is given with
``stationary``.
TypeError
If ``since_scheduled`` is given under both names.

Notes
-----
Expand Down Expand Up @@ -162,54 +147,6 @@ class NodeState:
stationary: bool = False
virtual_age: Optional[float] = None

def __init__(
self,
age: float = 0.0,
alive: bool = True,
down_for: float = 0.0,
maintenance: bool = False,
since_scheduled: Optional[float] = None,
stationary: bool = False,
virtual_age: Optional[float] = None,
*,
phase: Optional[float] = None,
) -> None:
if phase is not None:
if since_scheduled is not None:
raise TypeError(
"NodeState takes since_scheduled alone: phase is its "
"old name."
)
renamed(
"NodeState",
"phase",
"since_scheduled",
_PHASE_WHY,
removal=_PHASE_REMOVAL,
)
since_scheduled = phase
object.__setattr__(self, "age", age)
object.__setattr__(self, "alive", alive)
object.__setattr__(self, "down_for", down_for)
object.__setattr__(self, "maintenance", maintenance)
object.__setattr__(self, "since_scheduled", since_scheduled)
object.__setattr__(self, "stationary", stationary)
object.__setattr__(self, "virtual_age", virtual_age)
self.__post_init__()

@property
def phase(self) -> Optional[float]:
"""``since_scheduled``, by its old name: deprecated, and refused in
0.15 (#350)."""
renamed_attribute(
"NodeState",
"phase",
"since_scheduled",
_PHASE_WHY,
removal=_PHASE_REMOVAL,
)
return self.since_scheduled

def __post_init__(self) -> None:
if self.age < 0:
raise ValueError(
Expand Down
31 changes: 0 additions & 31 deletions repyability/tests/test_api_consistency.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
mtbf_pass_probability,
mtbf_test_time,
)
from repyability._version import __version__
from repyability.rbd.ccf import with_parameters
from repyability.rbd.uncertainty import draw_ccf_models
from repyability.utils import deprecation
Expand Down Expand Up @@ -261,13 +260,6 @@ def test_the_spares_demand_values_are_properties():
assert demand.std == pytest.approx(np.sqrt(10.0), rel=1e-4)


def test_what_0_14_deprecates_goes_in_0_15():
# NodeState's phase, renamed since_scheduled (#350): giving phase= and
# reading .phase warn, and NEXT_REMOVAL (0.15) refuses them.
version = tuple(map(int, __version__.split(".")))
assert version < tuple(map(int, NEXT_REMOVAL.split(".")))


def test_a_deprecation_warns_until_the_next_removal():
# The helpers a deprecation warns through.
match = f"{NEXT_REMOVAL} will refuse it"
Expand All @@ -284,29 +276,6 @@ def test_a_deprecation_warns_until_the_next_removal():
assert np.array_equal(array(), [1.0, 2.0])


def test_a_node_state_takes_phase_as_since_scheduled_for_now():
with pytest.warns(
FutureWarning, match="phase= is renamed since_scheduled"
):
state = NodeState(age=10.0, phase=4.0)
assert state.since_scheduled == 4.0
assert state == NodeState(age=10.0, since_scheduled=4.0)
assert (
repr(state) == "NodeState(age=10.0, alive=True, since_scheduled=4.0)"
)
with pytest.warns(FutureWarning, match="phase is renamed since_scheduled"):
assert state.phase == 4.0
with pytest.raises(TypeError, match="since_scheduled alone"):
NodeState(phase=4.0, since_scheduled=4.0)
with warnings.catch_warnings():
warnings.simplefilter("error")
assert NodeState(since_scheduled=4.0).since_scheduled == 4.0
assert (
NodeState(stationary=True, since_scheduled=4.0).since_scheduled
== 4.0
)


def test_an_uncertainty_result_shows_a_summary():
pump = surv.Weibull.fit(np.linspace(200, 1800, 50))
rbd = NonRepairableRBD([("s", "pump"), ("pump", "t")], {"pump": pump})
Expand Down
15 changes: 15 additions & 0 deletions repyability/tests/test_removed_in_0_15.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""What 0.14 deprecated is gone in 0.15: ``NodeState``'s ``phase``,
renamed ``since_scheduled`` (#350), as a phased mission has phases of its
own."""

import pytest

from repyability import NodeState


def test_phase_is_refused_for_since_scheduled():
with pytest.raises(TypeError, match="phase"):
NodeState(age=10.0, phase=4.0)
state = NodeState(age=10.0, since_scheduled=4.0)
assert state.since_scheduled == 4.0
assert not hasattr(state, "phase")
41 changes: 19 additions & 22 deletions repyability/utils/deprecation.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
"""Deprecated names, arguments and calls.

A deprecation gives one minor release's notice: what 0.14 deprecates goes
in 0.15 (``NEXT_REMOVAL``). Each warns with a ``FutureWarning``, which
A deprecation gives one minor release's notice: what 0.15 deprecates goes
in 0.16 (``NEXT_REMOVAL``). Each warns with a ``FutureWarning``, which
Python always shows, as the notice is short: an argument renamed
(``renamed``; an attribute, ``renamed_attribute``), a result's value
called that is now a property (``called``), or arguments a method ignores
(``ignored``). 0.14 deprecates:

- ``NodeState``'s ``phase``, renamed ``since_scheduled`` (``renamed`` for
the argument, ``renamed_attribute`` for reading it, #350).

What 0.13 deprecated went in 0.14: ``optimal_inspection_intervals``'
``offsets``, renamed ``offset_shares`` (#222); calling
``CapacityDistribution.mean()``, a property since 0.13 (#235); the
simulation options given to the exact ``mean`` of a ``StandbyModel``,
``LoadSharingModel`` or ``DegradingNode``, which it would ignore (#233);
and ``rbd._sensitivity``'s ``levers`` and ``Lever``, the names callers
used before the levers were public (#244). What 0.12 deprecated went in
0.13: calling ``SparesDemand.mean()`` and ``std()``, properties since 0.12
(#184), and ``StandbyModel``'s and ``LoadSharingModel``'s ``mc_samples``,
``lower`` and ``seed``, which set a fit to simulated lifetimes that 0.12
removed (#149). What 0.11 deprecated went in 0.12 (#149): its old names
are refused with the names that took their place
(``refuses_removed_names``, #232).
(``ignored``). 0.15 deprecates nothing.

What 0.14 deprecated went in 0.15: ``NodeState``'s ``phase``, renamed
``since_scheduled`` (#350). What 0.13 deprecated went in 0.14:
``optimal_inspection_intervals``' ``offsets``, renamed ``offset_shares``
(#222); calling ``CapacityDistribution.mean()``, a property since 0.13 (#235);
the simulation options given to the exact ``mean`` of a ``StandbyModel``,
``LoadSharingModel`` or ``DegradingNode``, which it would ignore (#233); and
``rbd._sensitivity``'s ``levers`` and ``Lever``, the names callers used before
the levers were public (#244). What 0.12 deprecated went in 0.13: calling
``SparesDemand.mean()`` and ``std()``, properties since 0.12 (#184), and
``StandbyModel``'s and ``LoadSharingModel``'s ``mc_samples``, ``lower`` and
``seed``, which set a fit to simulated lifetimes that 0.12 removed (#149). What
0.11 deprecated went in 0.12 (#149): its old names are refused with the names
that took their place (``refuses_removed_names``, #232).
"""

import functools
Expand All @@ -34,8 +31,8 @@

from repyability.utils.wrappers import outside_level

#: The release that removes what 0.14 deprecates.
NEXT_REMOVAL = "0.15"
#: The release that removes what 0.15 deprecates.
NEXT_REMOVAL = "0.16"


def _called_warning(name: str, removal: str) -> None:
Expand Down
Loading