Node models¶
Composite and helper nodes¶
Anything exposing sf/ff can be a node; these are the composite and helper
models provided here (see Building an RBD and
Redundancy models).
StandbyModel ¶
StandbyModel(reliabilities, k=1, *, switching_probability=1.0, dormancy_factor=0.0)
A k-out-of-n standby arrangement, from cold through warm to hot.
n = len(reliabilities) units with k operating at a time: the
first k units in the list start operating, spares are promoted in
list order as operating units fail, and the arrangement fails when
fewer than k units survive. dormancy_factor sets how fast a
dormant spare ages relative to an operating unit (the
cumulative-exposure model, the same virtual-age machinery as
LoadSharingModel):
0— cold standby (the default): spares do not age while dormant. Withk = 1the lifetime is the sum of the units' lifetimes.0 < dormancy_factor < 1— warm standby: a dormant spare ages at that fraction of the operating rate, so it can fail latent (dead before it is ever switched in; it is skipped at promotion).1— hot standby: spares age as fast as operating units, which is exactly an ordinary k-out-of-n parallel arrangement.
The survival function is set up once, at construction, by the first of these methods that applies:
- Exact closed form, for identical Exponential units (one common
rate) with perfect switching. Cold, the lifetime is
Erlang(n - k + 1, k * rate)for anyk; warm or hot, it is hypoexponential with stage ratesrate * (k + (j - k) * dormancy_factor)forj = n, ..., kunits alive (unless the stage rates are too close for surpyval to separate, as with a tinydormancy_factor). - Exact k-out-of-n, for hot standby of any units: the
arrangement works while at least
kof its units do, each independently. -
Numerical, deterministic and accurate to about
1e-5of a probability or better: -
cold standby with
k = 1: the lifetime is the sum of the units' lifetimes (under imperfect switching, a mixture of partial sums), convolved on a time grid; - cold standby of identical units with
k >= 2: each operating position runs a renewal process of the units' lives, and the arrangement fails at then - k + 1-th failure in all; - cold standby of different units with
k = 2: a recursion over the switch-ins on the time and the other operating unit's start (about1e-4,1e-3for lives with a steep start, such as a Weibull of shape below 1); - warm standby with
k = 1: a recursion over the spares' switch-ins on a time grid (a spare switched in attauhas ageddormancy_factor * tau). - None otherwise (cold standby of different units with
k >= 3, and warm standby withk >= 2):sf,ff,csandmean()refuse, andis_simulatedis True. Its lifetimes are still drawn byrandom, so a diagram with it is simulated:random,mean(method="simulate")andunreliability_intervalof aNonRepairableRBD, andavailabilityandcostof aRepairableRBD.mean(method="simulate", mc_samples=..., seed=...)estimates its own mean from new draws. (Until 0.12 itssfwas a Kaplan-Meier fit to simulated lifetimes.)
As an RBD node, sf/ff give its reliability, random its
lifetimes for Monte-Carlo system simulation and mean its MTTF.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reliabilities
|
sequence of lifetime models
|
The units' lifetime distributions (e.g. surpyval distributions), in promotion order. The same model object may be listed several times; each entry is an independent unit. |
required |
k
|
int
|
The number of units that must operate for the arrangement to work,
from 1 to |
1
|
switching_probability
|
float or sequence of float
|
The probability, in |
1.0
|
dormancy_factor
|
float
|
The dormant-to-operating aging ratio, in |
0.0
|
Attributes:
| Name | Type | Description |
|---|---|---|
N |
int
|
The number of units, |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
NotImplementedError
|
If |
Examples:
One operating unit and one cold spare, both Exponential with mean life 100: the lifetimes add, and the exact Erlang form applies.
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> cold = StandbyModel([unit, unit])
>>> round(float(cold.mean()), 4)
200.0
>>> round(float(cold.sf(100.0)), 4) # exp(-1) * (1 + 1)
0.7358
A warm spare aging at half the operating rate, and a hot spare (an ordinary parallel pair):
>>> warm = StandbyModel([unit, unit], dormancy_factor=0.5)
>>> round(float(warm.mean()), 4) # 1 / (1.5 * 0.01) + 1 / 0.01
166.6667
>>> hot = StandbyModel([unit, unit], dormancy_factor=1.0)
>>> round(float(hot.mean()), 4) # 1 / (2 * 0.01) + 1 / 0.01
150.0
Two of three identical Weibull units must operate: each operating position renews its unit from the spares, and the reliability is numerical, with no simulation:
>>> w = surv.Weibull.from_params([100.0, 2.0])
>>> two = StandbyModel([w, w, w], k=2)
>>> two.is_simulated
False
>>> round(float(two.sf(50.0)), 4)
0.9364
A warm spare, aging at a third of the operating rate:
>>> warm = StandbyModel([w, w], dormancy_factor=0.3)
>>> round(float(warm.sf(150.0)), 4)
0.4815
As a node of an RBD:
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
... [("s", "pumps"), ("pumps", "t")], {"pumps": cold}
... )
>>> round(rbd.sf(100.0), 4)
0.7358
is_simulated
property
¶
is_simulated
Whether only simulations take the arrangement: no exact or
numerical method gives its reliability, so sf, ff, cs
and mean() refuse, and a diagram with it is simulated.
random ¶
random(size, seed=None)
Draw random lifetimes of the arrangement.
Each draw samples every unit's lifetime and plays the arrangement
out. Cold with k = 1, the lifetimes are summed; under imperfect
switching a spare adds its lifetime only if every switch up to and
including its own succeeds (one uniform draw per switch). Cold with
k >= 2, each spare in turn extends whichever of the k
operating positions ends first, and the arrangement fails at the
first end after the spares run out. Warm or hot, a virtual-age loop
runs: operating units age at rate 1 and dormant spares at
dormancy_factor, and a spare whose dormant aging reaches its
failure age dies latent and is skipped. This is the sampler behind
the simulated sf and an RBD's Monte-Carlo random/mean.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of lifetimes to draw. |
required |
seed
|
int or None
|
If given, numpy's global RNG is seeded for the draw and its previous state restored afterwards, so the result is reproducible. By default None (draw from the current state). |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
Examples:
>>> import numpy as np
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> standby = StandbyModel([unit, unit])
>>> standby.random(5, seed=1).shape
(5,)
>>> a = standby.random(5, seed=1)
>>> bool(np.array_equal(a, standby.random(5, seed=1)))
True
mean ¶
mean(mc_samples=None, seed=None, *, method=None)
Mean lifetime (MTTF) of the arrangement.
Exact for the Erlang and hypoexponential closed forms, and
deterministic for the numerical methods (the integral of the
survival function). An arrangement with neither (see
is_simulated) has no exact mean, and refuses, unless asked for
an estimate: the mean of mc_samples new draws of random.
method="simulate" estimates it so whatever the arrangement, to
check the exact mean against draws, say.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mc_samples
|
int
|
The number of draws to estimate the mean from, 10_000 by
default: with |
None
|
seed
|
int or None
|
Seed for those draws (see |
None
|
method
|
(None, exact, simulate)
|
None (the default): the exact or numerical mean, or for an
arrangement with neither, the draws' estimate when
|
None
|
Returns:
| Type | Description |
|---|---|
float
|
The mean lifetime, or its estimate. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the arrangement has no exact mean and neither |
ValueError
|
If |
Examples:
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> round(float(StandbyModel([unit] * 3).mean()), 4)
300.0
>>> w = surv.Weibull.from_params([100.0, 2.0])
>>> round(float(StandbyModel([w, w, w], k=2).mean()), 2)
104.44
Warm standby with two operating has no exact mean, which draws estimate:
>>> sim = StandbyModel([w, w, w], k=2, dormancy_factor=0.5)
>>> sim.is_simulated
True
>>> round(sim.mean(method="simulate", mc_samples=20_000, seed=1), 1)
95.3
The exact mean, checked against draws:
>>> exact = StandbyModel([w, w, w], k=2)
>>> drawn = exact.mean(method="simulate", mc_samples=20_000, seed=1)
>>> round(drawn, 1)
104.5
sf ¶
sf(x, *args, **kwargs)
Survival function (reliability) of the arrangement.
Evaluates the survival function set up at construction: the exact closed form or the numerical method (interpolated on its grid). An RBD calls this for the node's reliability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s). |
required |
*args
|
Passed on, with |
()
|
|
**kwargs
|
Passed on, with |
()
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of surviving beyond |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If no exact or numerical method gives it (see
|
Examples:
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> standby = StandbyModel([unit, unit])
>>> [round(float(r), 4) for r in standby.sf([50.0, 100.0])]
[0.9098, 0.7358]
ff ¶
ff(x, *args, **kwargs)
Cumulative failure probability, 1 - sf(x).
Evaluated from the same survival function as sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s). |
required |
*args
|
Passed on, with |
()
|
|
**kwargs
|
Passed on, with |
()
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of failing by |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
As for |
cs ¶
cs(x, X)
Conditional survival R(x | X) = sf(X + x) / sf(X).
The probability of surviving a further x given the arrangement
has already survived to age X, computed from sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The further time(s) to survive. |
required |
X
|
float or array_like
|
The age(s) already survived. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The conditional survival, clipped to |
Examples:
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> round(StandbyModel([unit, unit]).cs(50.0, 100.0), 4)
0.7582
DegradingNode ¶
DegradingNode(stages)
Bases: StandbyModel
A component that degrades through stages before it fails.
It runs in its first stage, at the first capacity, for a time drawn from the first stage's model; then in the second stage, at the second capacity, for a time drawn from the second's; and so on. It has failed once its last stage ends. A pump that runs at full output until it wears, then at half output until it fails, has two stages. The stages' times are independent, and each is a lifetime model, such as one fitted by surpyval to the times units spent in that stage.
As an RBD node its reliability is the probability that its last stage
has not ended: its lifetime is the sum of its stages' times, as a cold
standby arrangement's is, so it is a
StandbyModel of the stage models and can
be used wherever one can (a node of a NonRepairableRBD, or the
reliability of a RepairableRBD component). The capacity analysis
(capacity_distribution) uses its stages' capacities, weighted by
the probability of being in each stage, unless the RBD gives the node a
capacity of its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stages
|
sequence of (float, model)
|
The stages in order, each a |
required |
Attributes:
| Name | Type | Description |
|---|---|---|
stages |
tuple of (float, model)
|
The stages, with the capacities as floats. |
capacities |
tuple of float
|
The stages' capacities, in order. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If there are no stages, a stage is not a |
Examples:
A pump at full output (100) for a Weibull time, then at half output for an exponential one:
>>> import surpyval as surv
>>> from repyability import DegradingNode
>>> pump = DegradingNode([
... (100, surv.Weibull.from_params([1000, 2])),
... (50, surv.Exponential.from_params([1 / 500])),
... ])
>>> round(pump.mean(), 1) # 886.2 + 500
1386.2
>>> capacity = pump.capacity_distribution(1000)
>>> capacity.levels.tolist()
[0.0, 50.0, 100.0]
>>> capacity.probabilities.round(4).tolist()
[0.3152, 0.3169, 0.3679]
>>> round(float(pump.sf(1000)), 4) # in either stage
0.6848
is_simulated
property
¶
is_simulated
Whether only simulations take the arrangement: no exact or
numerical method gives its reliability, so sf, ff, cs
and mean() refuse, and a diagram with it is simulated.
random ¶
random(size, seed=None)
Draw random lifetimes of the arrangement.
Each draw samples every unit's lifetime and plays the arrangement
out. Cold with k = 1, the lifetimes are summed; under imperfect
switching a spare adds its lifetime only if every switch up to and
including its own succeeds (one uniform draw per switch). Cold with
k >= 2, each spare in turn extends whichever of the k
operating positions ends first, and the arrangement fails at the
first end after the spares run out. Warm or hot, a virtual-age loop
runs: operating units age at rate 1 and dormant spares at
dormancy_factor, and a spare whose dormant aging reaches its
failure age dies latent and is skipped. This is the sampler behind
the simulated sf and an RBD's Monte-Carlo random/mean.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of lifetimes to draw. |
required |
seed
|
int or None
|
If given, numpy's global RNG is seeded for the draw and its previous state restored afterwards, so the result is reproducible. By default None (draw from the current state). |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
Examples:
>>> import numpy as np
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> standby = StandbyModel([unit, unit])
>>> standby.random(5, seed=1).shape
(5,)
>>> a = standby.random(5, seed=1)
>>> bool(np.array_equal(a, standby.random(5, seed=1)))
True
cs ¶
cs(x, X)
Conditional survival R(x | X) = sf(X + x) / sf(X).
The probability of surviving a further x given the arrangement
has already survived to age X, computed from sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The further time(s) to survive. |
required |
X
|
float or array_like
|
The age(s) already survived. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The conditional survival, clipped to |
Examples:
>>> import surpyval as surv
>>> from repyability import StandbyModel
>>> unit = surv.Exponential.from_params([0.01])
>>> round(StandbyModel([unit, unit]).cs(50.0, 100.0), 4)
0.7582
stage_probabilities ¶
stage_probabilities(x)
The probability that the component is in each stage at time/s
x: one row per stage, in order (one value per stage for a
scalar x). The rest of the probability, ff(x), is that it
has failed.
Being in stage j at x means the first j - 1 stages have
ended by x and the j-th has not, so its probability is the
survival function of the sum of the first j stages' times less
that of the first j - 1. Those come from the same (exact or
numerical) convolution as sf, so the rows add up to sf(x).
A time before 0 is taken as 0: a new component is in its first
stage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The time(s). |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
sf ¶
sf(x, *args, **kwargs)
Survival function (reliability): the probability that the last
stage has not ended by time/s x.
With one stage, the stage model's own sf; with several, that of
the sum of the stages' times (see
StandbyModel.sf).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s). |
required |
*args
|
Passed on, with |
()
|
|
**kwargs
|
Passed on, with |
()
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of surviving beyond |
ff ¶
ff(x, *args, **kwargs)
Cumulative failure probability, 1 - sf(x).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s). |
required |
*args
|
Passed on, with |
()
|
|
**kwargs
|
Passed on, with |
()
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability that the last stage has ended by |
mean ¶
mean(mc_samples=None, seed=None, *, method=None)
Mean lifetime (MTTF): the sum of the stages' mean times, exactly;
infinite if a stage may never end. method="simulate" estimates
it from mc_samples draws of random instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mc_samples
|
int
|
With |
None
|
seed
|
int or None
|
Seed for those draws, refused as |
None
|
method
|
(None, exact, simulate)
|
As for |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The mean lifetime, or its estimate. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
capacity_distribution ¶
capacity_distribution(x)
The distribution of the component's capacity at time/s x: the
capacity of the stage it is in, or 0 once it has failed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The time(s). |
required |
Returns:
| Type | Description |
|---|---|
CapacityDistribution
|
The capacities it can have (0 and each stage's, equal ones
merged) and their probabilities: one per level for a scalar
|
stage_fractions ¶
stage_fractions()
The long-run fraction of its working time the component spends in each stage, when it is renewed after each failure.
Each renewal runs through the stages again, so by the
renewal-reward theorem the fraction in stage j is its mean time
over the mean lifetime. A stage that may never end (a model with
units that never fail) catches the component for good sooner or
later, and the fractions are then the probabilities of being caught
in each stage.
Returns:
| Type | Description |
|---|---|
ndarray
|
One fraction per stage; they sum to 1. |
RepeatedNode ¶
RepeatedNode(model, repeats, kind)
Independent identical copies of one component, in series or parallel.
One RBD node standing for repeats independent copies of model,
arranged in series (the node fails at the first copy failure) or in
parallel (at the last). Its survival function is exact:
R(x) ** repeats in series and 1 - F(x) ** repeats in parallel,
with R and F the model's sf and ff, and so is its
mean, the area under sf.
As an RBD node, sf/ff give its reliability, random its
lifetimes for Monte-Carlo system simulation and mean its MTTF.
The copies fail independently. This differs from a repeated node of an
RBD (a reliabilities entry that names another node), which puts
one component in several places of the diagram, failing in all of them
at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
lifetime model
|
The component's lifetime distribution, e.g. a surpyval
distribution. Sampling calls its |
required |
repeats
|
int
|
The number of copies, at least 1. |
required |
kind
|
(series, parallel)
|
How the copies are arranged. |
'series'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import surpyval as surv
>>> from repyability import RepeatedNode
>>> unit = surv.Weibull.from_params([100.0, 2.0])
>>> series = RepeatedNode(unit, 3, "series")
>>> round(float(series.sf(50.0)), 4) # unit.sf(50) ** 3
0.4724
>>> parallel = RepeatedNode(unit, 3, "parallel")
>>> round(float(parallel.sf(50.0)), 4) # 1 - unit.ff(50) ** 3
0.9892
random ¶
random(size, seed=None)
Draw random lifetimes of the node.
Draws repeats independent lifetimes of model per sample and
takes their minimum (series) or maximum (parallel).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of lifetimes to draw. |
required |
seed
|
int or None
|
If given, numpy's global RNG is seeded for the draw and its previous state restored afterwards, so the result is reproducible. By default None (draw from the current state). |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
mean ¶
mean(mc_samples=None, seed=None, *, method='exact')
Mean lifetime (MTTF) of the node.
Exact by default: the area under sf, integrated by adaptive
Gauss-Legendre quadrature to about 1e-10, relative, and inf
if the node may never fail (its model has units that never fail,
and the node needs only them). method="simulate" estimates it
instead, as the mean of mc_samples draws of random.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mc_samples
|
int
|
With |
None
|
seed
|
int or None
|
With |
None
|
method
|
(exact, simulate)
|
How to find the mean, by default |
"exact"
|
Returns:
| Type | Description |
|---|---|
float
|
The mean lifetime, or with |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
If a simulation option is given without |
Examples:
>>> import surpyval as surv
>>> from repyability import RepeatedNode
>>> unit = surv.Weibull.from_params([100.0, 2.0])
>>> node = RepeatedNode(unit, 3, "series")
>>> round(node.mean(), 4) # 100 * Gamma(1.5) / sqrt(3)
51.1663
>>> round(node.mean(method="simulate", mc_samples=10_000, seed=1), 1)
51.1
sf ¶
sf(x)
Survival function (reliability) of the node, exactly.
model.sf(x) ** repeats in series and
1 - model.ff(x) ** repeats in parallel. An RBD calls this for
the node's reliability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The time(s) at which to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of surviving beyond |
ff ¶
ff(x)
Cumulative failure probability of the node, 1 - sf(x).
1 - model.sf(x) ** repeats in series and
model.ff(x) ** repeats in parallel, each worked out from the
model's ff so that a small one keeps its precision (in series,
as -expm1(repeats * log1p(-model.ff(x)))).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The time(s) at which to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of failing by |
cs ¶
cs(x, X)
Conditional survival R(x | X) = sf(X + x) / sf(X).
The probability that the node survives a further x given it has
already survived to age X, computed from sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The further time(s) to survive. |
required |
X
|
float or array_like
|
The age(s) already survived. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The conditional survival, clipped to |
RepeatedStandbyNode ¶
RepeatedStandbyNode(model, repeats, *, switching_probability=1.0)
Cold standby of identical copies of one component, as one node.
repeats independent copies of model in cold standby with one
operating at a time: the first copy runs and, each time the running
copy fails, the next is switched in. Spares do not age while dormant,
so with perfect switching the lifetime is the sum of repeats
independent lifetimes. With imperfect switching it is a mixture of
partial sums, since a failed switch ends the node's life when the copy
it should replace fails. The survival function is computed at
construction by numerical convolution of the copies' densities on a
time grid: deterministic (no Monte-Carlo sampling), and linearly
interpolated when evaluated.
For different units, k >= 2 or warm and hot spares, use
StandbyModel. Unlike this node, which
always convolves numerically, StandbyModel([model] * repeats) uses
the exact Erlang form for identical Exponential units with perfect
switching.
As an RBD node, sf/ff give its reliability, random its
lifetimes for Monte-Carlo system simulation and mean its MTTF.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
lifetime model
|
The copies' lifetime distribution, e.g. a surpyval distribution. It
must expose |
required |
repeats
|
int
|
The number of copies, at least 1. |
required |
switching_probability
|
float or sequence of float
|
The probability, in |
1.0
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
Three copies of a Weibull unit with mean life 88.62; the lifetimes
add, so the mean is close to 3 * 88.62 = 265.87:
>>> import surpyval as surv
>>> from repyability import RepeatedStandbyNode
>>> unit = surv.Weibull.from_params([100.0, 2.0])
>>> node = RepeatedStandbyNode(unit, 3)
>>> round(float(node.mean()), 1)
265.9
>>> round(float(node.sf(200.0)), 4)
0.786
If each switch works with probability 0.9, the mean falls to about
88.62 * (1 + 0.9 + 0.81):
>>> flaky = RepeatedStandbyNode(unit, 3, switching_probability=0.9)
>>> round(float(flaky.mean()), 1)
240.2
random ¶
random(size, seed=None)
Draw random lifetimes of the node.
Each lifetime is the sum of repeats independent draws of
model. Under imperfect switching a spare adds its lifetime only
if every switch up to and including its own succeeds (one uniform
draw per switch).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of lifetimes to draw. |
required |
seed
|
int or None
|
If given, numpy's global RNG is seeded for the draw and its previous state restored afterwards, so the result is reproducible. By default None (draw from the current state). |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
mean ¶
mean(*args, **kwargs)
Mean lifetime (MTTF) of the node, from the convolution.
E[T], the integral of the survival function over its grid
(trapezoidal rule): deterministic, with no sampling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
Ignored; accepted so |
()
|
|
**kwargs
|
Ignored; accepted so |
()
|
Returns:
| Type | Description |
|---|---|
float
|
The mean lifetime. |
sf ¶
sf(*args, **kwargs)
Survival function (reliability) of the node.
The numerically convolved survival function, linearly interpolated
on its grid: 1 for x below 0 and 0 beyond the grid's end
(placed where the survival is negligible). An RBD calls this for
the node's reliability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
array_like
|
The time(s) |
()
|
**kwargs
|
Passed on with |
{}
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of surviving beyond |
ff ¶
ff(*args, **kwargs)
Cumulative failure probability of the node, 1 - sf(x).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
array_like
|
The time(s) |
()
|
**kwargs
|
Passed on with |
{}
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of failing by |
cs ¶
cs(x, X)
Conditional survival R(x | X) = sf(X + x) / sf(X).
The probability that the node survives a further x given it has
already survived to age X, computed from sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The further time(s) to survive. |
required |
X
|
float or array_like
|
The age(s) already survived. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The conditional survival, clipped to |
LoadSharingModel ¶
LoadSharingModel(models, load, k=1)
A load-sharing arrangement of coupled AFT units as one RBD node.
N = len(models) units share a total load L: while s of them
survive, each carries L / s and ages on its baseline clock at its
accelerated-failure-time rate phi(L / s), so when a unit fails the
survivors pick up its share and age faster (dependent failure). The
group works while at least k units survive and fails at the
(N - k + 1)-th unit failure. Because each unit's aging depends on
how many siblings are alive, the group is one node; it cannot be
modelled as N independent nodes.
The survival function is set up once, at construction:
- Exact, when every unit's baseline is Exponential with one common
rate
lambdaand the units'phiagree at every stage: the lifetime is hypoexponential with stage ratess * lambda * phi(L / s)fors = N, ..., ksurvivors. - Numerical, for other identical units (one baseline and one
phi): every survivor ages alike, so the units fail in the order of their exposures to failure, and a recursion over the failures on a grid of exposure and time gives the lifetime's distribution, to about1e-4of a probability. - None for different units:
sf,ff,csandmean()refuse, andis_simulatedis True. Its lifetimes are still drawn byrandom, so a diagram with it is simulated:random,mean(method="simulate")andunreliability_intervalof aNonRepairableRBD, andavailabilityandcostof aRepairableRBD.mean(method="simulate", mc_samples=..., seed=...)estimates its own mean from new draws. (Until 0.12 itssfwas a Kaplan-Meier fit to simulated lifetimes.)
As an RBD node, sf/ff give its reliability, random its
lifetimes for Monte-Carlo system simulation and mean its MTTF.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
sequence of fitted surpyval AFT models
|
The units: fitted accelerated-failure-time regression models (e.g.
from |
required |
load
|
float
|
The total load |
required |
k
|
int
|
The minimum number of surviving units for the group to work, from
1 to |
1
|
Attributes:
| Name | Type | Description |
|---|---|---|
N |
int
|
The number of units, |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
phi with no load effect (phi == 1) reproduces the ordinary
k-out-of-n parallel result exactly, since the units then neither share
stress nor age faster as siblings fail.
Examples:
Fit an Exponential AFT model with load as its covariate in surpyval. With these data the life at load 1 is 100 and at load 2 is 25:
>>> import numpy as np
>>> import surpyval as surv
>>> from repyability import LoadSharingModel
>>> x = np.array([50.0, 100.0, 150.0, 12.5, 25.0, 37.5])
>>> load = np.array([[1.0], [1.0], [1.0], [2.0], [2.0], [2.0]])
>>> pump = surv.ExponentialAFT.fit(x, Z=load)
Two such pumps share a total load of 2. Each carries 1 while both run (rate 0.01 each); the survivor carries 2 (rate 0.04). The closed form applies:
>>> pumps = LoadSharingModel([pump, pump], load=2.0, k=1)
>>> pumps.is_simulated
False
>>> round(float(pumps.mean()), 1) # 1 / (2 * 0.01) + 1 / 0.04
75.0
>>> round(float(pumps.sf(50.0)), 4)
0.6004
A Weibull baseline has no closed form, so the reliability of two such pumps is numerical:
>>> w = surv.WeibullAFT.fit(x, Z=load)
>>> numerical = LoadSharingModel([w, w], load=2.0)
>>> numerical.is_simulated
False
>>> round(float(numerical.sf(50.0)), 2)
0.92
is_simulated
property
¶
is_simulated
Whether only simulations take the group: its units are
different, so no exact or numerical method gives its reliability,
sf, ff, cs and mean() refuse, and a diagram with it
is simulated.
random ¶
random(size, seed=None)
Simulate group lifetimes with the cumulative-exposure event loop.
Each unit draws the baseline exposure at which it fails (its
virtual age at failure, from its baseline distribution). The loop
then steps from one unit failure to the next: while s units
survive each accrues exposure at phi(L / s), the unit that
reaches its threshold first fails, and the survivors carry on at
the new share. The group lifetime is the time at which fewer than
k units survive. This is the sampler behind the simulated
sf and an RBD's Monte-Carlo random/mean; it is used
even when sf is exact.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of lifetimes to draw. |
required |
seed
|
int or None
|
If given, numpy's global RNG is seeded for the draw and its previous state restored afterwards, so the result is reproducible. By default None (draw from the current state). |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
mean ¶
mean(mc_samples=None, seed=None, *, method=None)
Mean lifetime (MTTF) of the group.
Exact (the hypoexponential mean, the sum of the stage means) when
the closed form applies, and numerical for identical units (the
integral of the survival function). A group of different units has
neither (see is_simulated), and refuses, unless asked for an
estimate: the mean of mc_samples new draws of random.
method="simulate" estimates it so whatever the group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mc_samples
|
int
|
The number of draws to estimate the mean from, 10_000 by
default: with |
None
|
seed
|
int or None
|
Seed for those draws (see |
None
|
method
|
(None, exact, simulate)
|
As for |
None
|
Returns:
| Type | Description |
|---|---|
float
|
The mean lifetime, or its estimate. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the group has no exact mean and neither |
ValueError
|
If |
sf ¶
sf(x, *args, **kwargs)
Survival function (reliability) of the group.
Evaluates the exact hypoexponential closed form, or the numerical recursion for identical units. An RBD calls this for the node's reliability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s). |
required |
*args
|
Passed on, with |
()
|
|
**kwargs
|
Passed on, with |
()
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability that the group survives beyond |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
For different units (see |
ff ¶
ff(x, *args, **kwargs)
Cumulative failure probability, 1 - sf(x).
Evaluated from the same survival function as sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s). |
required |
*args
|
Passed on, with |
()
|
|
**kwargs
|
Passed on, with |
()
|
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability that the group has failed by |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
As for |
cs ¶
cs(x, X)
Conditional survival R(x | X) = sf(X + x) / sf(X).
The probability that the group survives a further x given it
has already survived to age X, computed from sf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
float or array_like
|
The further time(s) to survive. |
required |
X
|
float or array_like
|
The age(s) already survived. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The conditional survival, clipped to |
RegressionNode ¶
RegressionNode(model, covariates=None, schedule=None)
An RBD node backed by a fitted surpyval regression model.
Pairs a fitted regression model (accelerated failure time, proportional hazards, proportional odds, ...) with this component's covariates, giving an ordinary univariate node. Provide exactly one of:
covariates, a fixed operating pointZ: the reliability isR(x) = model.sf(x, Z), for any regression family; orschedule, a time-varying covariate pathZ(t): the reliability isR(x) = model.sf_tvc(x, schedule), the survival along that path (accelerated-failure-time, proportional- and additive-hazards, and proportional-odds models).
sf and ff evaluate that curve directly, so the node takes part
in system reliability, importance measures and the condition-based
(age) methods with no special handling. mean integrates the
curve to a relative 1e-10, and random inverts it exactly (the
model's quantile at fixed covariates; the cumulative hazard along a
schedule), which needs a proper parametric curve: a semiparametric
model such as surpyval.CoxPH supports sf but not mean or
random. Do the regression fit
in surpyval and pass the fitted model in.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
surpyval regression model
|
A fitted regression model, e.g. |
required |
covariates
|
array_like
|
The component's fixed covariate vector |
None
|
schedule
|
surpyval StepSchedule
|
A piecewise-constant covariate path |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If not exactly one of |
Examples:
Fit an Exponential AFT model with load as its covariate in surpyval. With these data the life at load 1 is 100 and at load 2 is 25:
>>> import numpy as np
>>> import surpyval as surv
>>> from repyability import RegressionNode
>>> x = np.array([50.0, 100.0, 150.0, 12.5, 25.0, 37.5])
>>> load = np.array([[1.0], [1.0], [1.0], [2.0], [2.0], [2.0]])
>>> model = surv.ExponentialAFT.fit(x, Z=load)
A component always run at load 1:
>>> node = RegressionNode(model, covariates=[1.0])
>>> round(float(node.sf(100.0)), 4) # exp(-100 / 100)
0.3679
>>> round(node.mean(), 1)
100.0
One run at load 1 for 50 time units and at load 2 after that:
>>> schedule = surv.StepSchedule.from_changepoints(
... [0, 50], [[1.0], [2.0]]
... )
>>> ramped = RegressionNode(model, schedule=schedule)
>>> round(float(ramped.sf(100.0)), 4) # exp(-50 / 100 - 50 / 25)
0.0821
sf ¶
sf(x)
Reliability at the stored covariates or along the schedule.
model.sf(x, Z) at the fixed covariates Z, or
model.sf_tvc(x, schedule) along the covariate schedule. An RBD
calls this for the node's reliability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) at which to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of surviving beyond each |
ff ¶
ff(x)
Unreliability: 1 - sf(x).
model.ff(x, Z) at the fixed covariates, or
-expm1(-model.Hf_tvc(x, schedule)) along the schedule: worked
out in its own right, so that a small one keeps its precision (for
a proportional-odds model along a schedule too, from surpyval 0.22:
surpyval #528).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) at which to evaluate. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
The probability of failing by each |
mean ¶
mean()
Mean time to failure at the stored covariates / along the schedule.
For a non-negative lifetime E[T] = integral of R(t), integrated
to a relative accuracy of 1e-10 on pieces split at the model's
quantiles (fixed covariates) or the schedule's change points, as
an RBD's MTTF is: inf if some lifetimes never end.
Returns:
| Type | Description |
|---|---|
float
|
The mean time to failure. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the model is semiparametric (a Cox model: its baseline is
defined on the observed range only), or if the survival curve
is improper ( |
random ¶
random(size)
Draw size failure times at the stored covariates / schedule.
Inverse-transform sampling, exact: each uniform u maps to the
time at which the distribution reaches u, the model's
quantile qf(u, Z) at fixed covariates, and along a schedule the
time at which the cumulative hazard reaches -log(1 - u) (found
by bisection, to the last bits). There is no seed argument:
this uses numpy's global RNG, so seed it (np.random.seed) or
wrap the call in repyability.utils.wrappers.numpy_seed to
reproduce the draws. An RBD's seeded random and mean do
this for you.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of failure times to draw. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the survival curve is improper, as for |
to_dict ¶
to_dict()
Serialise the node to a JSON-friendly dict.
The fitted model is stored with its own to_dict(), plus either
"covariates" (a list of floats) or "schedule" (the
schedule's finite segment edges as "times" and its covariate
rows as "values"). from_dict rebuilds the node, and an
RBD's to_dict/to_json use this for a regression node.
A schedule is rebuilt with StepSchedule.from_changepoints, so
only one whose last segment is open-ended round-trips (e.g. from
from_changepoints). One whose last edge is finite (e.g. from
from_intervals) is written, but from_dict then raises
ValueError.
Returns:
| Type | Description |
|---|---|
dict
|
|
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If the schedule is cyclic (has a |
from_dict
classmethod
¶
from_dict(d)
Reconstruct a node from the output of to_dict.
The fitted model round-trips through surpyval.from_dict, and a
schedule is rebuilt with StepSchedule.from_changepoints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d
|
dict
|
A dict produced by |
required |
Returns:
| Type | Description |
|---|---|
RegressionNode
|
The reconstructed node. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If |
ValueError
|
If the schedule cannot be rebuilt, or the node fails the constructor's checks. |
Examples:
>>> import json
>>> import numpy as np
>>> import surpyval as surv
>>> from repyability import RegressionNode
>>> x = np.array([50.0, 100.0, 150.0, 12.5, 25.0, 37.5])
>>> load = np.array([[1.0], [1.0], [1.0], [2.0], [2.0], [2.0]])
>>> model = surv.ExponentialAFT.fit(x, Z=load)
>>> node = RegressionNode(model, covariates=[1.0])
>>> text = json.dumps(node.to_dict())
>>> restored = RegressionNode.from_dict(json.loads(text))
>>> round(float(restored.sf(100.0)), 4)
0.3679
JointNode ¶
JointNode(model, Z=None, markers=None, age=None)
One unit of a fitted joint model, given its readings, as a node.
surpyval's joint models (surpyval.WeibullJointPH) model a marker
read repeatedly on each unit together with the unit's life; a fitted
model's predict(Z, markers, given) is one unit's failure-age
distribution given its covariates, its readings so far and that it
is still running at age given. This node is that unit in a
NonRepairableRBD, from now: at time
t from now its reliability is pred.sf(age + t) (already
conditioned on the unit reaching age), its lifetimes are
pred.random() - age and its mean is the unit's mean residual
life. A DynamicPrediction given as a diagram's node is taken as
the JointNode of its unit.
The node's age is the prediction's: a NodeState for it in
sf_given_state and the other condition-based methods may say
that it has failed, or give that same age, but not another, which
would condition it again (predict again, with given the new age
and the readings since). A
RepairableRBD refuses it: the units
that replace this one are new, without its readings. See the guide's
joint-model page.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
surpyval DynamicPrediction or JointModel
|
The unit's prediction ( |
required |
Z
|
dict, array_like or surpyval ItemLifetime
|
With a |
None
|
markers
|
dict
|
With a |
None
|
age
|
float
|
With a |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If the model's checks refuse the unit's inputs, or a covariate is
missing ( |
Examples:
Fit a joint model to a small fleet in surpyval, and put a pump whose marker has risen into a diagram, 40 hours old:
>>> import numpy as np
>>> from surpyval import ItemLifetime, SurpyvalData, WeibullJointPH
>>> from repyability import JointNode, NonRepairableRBD
>>> rng = np.random.default_rng(3)
>>> items = []
>>> for k in range(60):
... start, rate = rng.normal(2.0, 0.4), rng.normal(0.02, 0.005)
... life = 100 * rng.weibull(2.0) * np.exp(-0.9 * (start - 2))
... end = min(life, 120.0)
... ages = np.arange(0.0, end, 10.0)
... y = start + rate * ages + rng.normal(0, 0.2, ages.size)
... outcome = "observed" if life < 120 else "right_censored"
... items.append(
... ItemLifetime(k, **{outcome: end},
... markers=dict(zip(ages, y)))
... )
>>> model = WeibullJointPH.fit(SurpyvalData.from_items(items))
>>> pred = model.predict(markers={0: 2.1, 20: 2.5, 40: 3.0}, given=40)
>>> pump = JointNode(pred)
>>> bool(pump.sf(20.0) == pred.sf(60.0))
True
>>> rbd = NonRepairableRBD([("in", "pump"), ("pump", "out")],
... {"pump": pump})
>>> bool(rbd.sf(20.0) == pred.sf(60.0))
True
sf ¶
sf(x)
The probability that the unit survives a further x:
pred.sf(age + x).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) from now. |
required |
Returns:
| Type | Description |
|---|---|
float or ndarray
|
With the shape of |
ff ¶
ff(x)
The probability that the unit fails within a further x:
pred.ff(age + x), worked out in its own right.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) from now. |
required |
Hf ¶
Hf(x)
The cumulative hazard over the next x, -log sf(x).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) from now. |
required |
hf ¶
hf(x)
The unit's hazard x from now, given its readings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) from now. |
required |
df ¶
df(x)
The density of the unit's life left at x from now.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The time(s) from now. |
required |
qf ¶
qf(p)
The life left that the unit outlives with probability 1 -
p: pred.qf(p) - age (inf where the survival never falls
to 1 - p).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
p
|
array_like
|
The probabilities of failing. |
required |
mean ¶
mean()
The unit's mean residual life: the integral of sf from now,
to a relative 1e-10 on pieces split at its quantiles; inf where
its survival stays above 0 for ever (a marker path that falls
away can leave a chance of never failing).
Returns:
| Type | Description |
|---|---|
float
|
The mean life left. |
random ¶
random(size)
Draw size lives left, from now: pred.qf(u) - age of
uniforms u from numpy's global RNG, as pred.random draws
failure ages. Seed it (np.random.seed) to reproduce the draws;
an RBD's seeded methods do.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int
|
The number of draws. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The |
to_dict ¶
to_dict()
The node as a JSON-friendly dict: the joint model's own
to_dict() and the unit's inputs, its covariates (in the order
of the model's feature_names), its readings as [age,
value] pairs and its age. from_dict rebuilds it, and an
RBD's to_dict/to_json save a joint node so. A joint model
on a path model of one's own does not save (surpyval raises
NotImplementedError).
Returns:
| Type | Description |
|---|---|
dict
|
|
from_dict
classmethod
¶
from_dict(d)
Rebuild a node from :meth:to_dict's dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
d
|
dict
|
A dict |
required |
Returns:
| Type | Description |
|---|---|
JointNode
|
The node, its prediction made again from the saved model and the unit's inputs. |
Examples:
>>> import json
>>> import numpy as np
>>> from surpyval import ItemLifetime, SurpyvalData, WeibullJointPH
>>> from repyability import JointNode
>>> rng = np.random.default_rng(3)
>>> items = []
>>> for k in range(60):
... start, rate = rng.normal(2.0, 0.4), rng.normal(0.02, 0.005)
... life = 100 * rng.weibull(2.0) * np.exp(-0.9 * (start - 2))
... end = min(life, 120.0)
... ages = np.arange(0.0, end, 10.0)
... y = start + rate * ages + rng.normal(0, 0.2, ages.size)
... outcome = "observed" if life < 120 else "right_censored"
... items.append(
... ItemLifetime(k, **{outcome: end},
... markers=dict(zip(ages, y)))
... )
>>> model = WeibullJointPH.fit(SurpyvalData.from_items(items))
>>> node = JointNode(model, markers={0: 2.1, 40: 3.0}, age=40)
>>> back = JointNode.from_dict(json.loads(json.dumps(node.to_dict())))
>>> bool(back.sf(20.0) == node.sf(20.0))
True
PerfectReliability ¶
A node model that never fails.
Its survival function is 1 at every time, its failure probability 0,
and a simulated failure time is infinite. A
NonRepairableRBD uses it for the
input and output nodes and for nodes forced working (working_nodes),
and it can be given as a node's model, e.g. for a connection that
cannot fail. Its methods are class methods, and the RBD recognises it (as
time-invariant, and when serialising) by the class: an instance,
PerfectReliability(), is taken as the class itself.
Examples:
A perfectly reliable node in series changes nothing:
>>> from surpyval import FixedEventProbability
>>> from repyability import NonRepairableRBD, PerfectReliability
>>> rbd = NonRepairableRBD(
... [("s", "a"), ("a", "b"), ("b", "t")],
... {
... "a": FixedEventProbability.from_params(0.1),
... "b": PerfectReliability,
... },
... )
>>> round(rbd.sf(), 4)
0.9
>>> PerfectReliability.sf([10.0, 20.0]).tolist()
[1.0, 1.0]
sf
classmethod
¶
sf(x)
Return the survival function (reliability): 1 at every time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
Time(s); only the shape is used. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Ones (floats) with the shape of |
ff
classmethod
¶
ff(x)
Return the failure probability (unreliability): 0 at every time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
Time(s); only the shape is used. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Zeros (floats) with the shape of |
cs
classmethod
¶
cs(x, X)
Return the conditional survival: 1 for any further time.
The probability of surviving a further x given survival to age
X, which is 1 because the node never fails.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The further time(s); only the shape is used. |
required |
X
|
array_like
|
The age(s) survived to; not used. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Ones (floats) with the shape of |
mean
classmethod
¶
mean()
Return the mean life: infinite, as the node never fails.
Returns:
| Type | Description |
|---|---|
float
|
|
random
classmethod
¶
random(size)
Draw failure times: all infinite, as the node never fails.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int or tuple of int
|
The shape of the output. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
An array of |
PerfectUnreliability ¶
A node model that has always failed.
Its survival function is 0 at every time, its failure probability 1,
and a simulated failure time is 0. A
NonRepairableRBD uses it for nodes
forced failed (broken_nodes) and for failed components in
condition-based evaluation (a NodeState with
alive=False), and it can be given as a node's model, e.g. to study
the system with a component missing. Its methods are class methods,
and the RBD recognises it (as time-invariant, and when serialising) by
the class: an instance is taken as the class itself.
Examples:
With one of two parallel nodes failed, the other carries the system:
>>> from surpyval import FixedEventProbability
>>> from repyability import NonRepairableRBD, PerfectUnreliability
>>> rbd = NonRepairableRBD(
... [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")],
... {
... "a": FixedEventProbability.from_params(0.1),
... "b": PerfectUnreliability,
... },
... )
>>> round(rbd.sf(), 4)
0.9
>>> PerfectUnreliability.sf([10.0, 20.0]).tolist()
[0.0, 0.0]
sf
classmethod
¶
sf(x)
Return the survival function (reliability): 0 at every time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
Time(s); only the shape is used. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Zeros (floats) with the shape of |
ff
classmethod
¶
ff(x)
Return the failure probability (unreliability): 1 at every time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
Time(s); only the shape is used. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Ones (floats) with the shape of |
cs
classmethod
¶
cs(x, X)
Return the conditional survival: 0 for any further time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array_like
|
The further time(s); only the shape is used. |
required |
X
|
array_like
|
The age(s) survived to; not used. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Zeros (floats) with the shape of |
mean
classmethod
¶
mean()
Return the mean life: 0, as the node has always failed.
Returns:
| Type | Description |
|---|---|
float
|
|
random
classmethod
¶
random(size)
Draw failure times: all 0, as the node has always failed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size
|
int or tuple of int
|
The shape of the output. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
An array of zeros with shape |
Design inputs¶
ComponentOption
dataclass
¶
ComponentOption(name, reliability, cost)
A candidate component type for a node, in redundancy allocation.
Give a node a list of these in the costs of
allocate_redundancy
to choose its copies among several types, e.g. a standard pump and a
premium one, each with its own reliability and cost. The node's own
model in the RBD is then not used: list it as an option too if it is a
candidate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
Hashable
|
What the result's |
required |
reliability
|
model or float
|
The reliability of one copy: a model with an |
required |
cost
|
float or dict
|
What one copy uses: a number (its cost) or a dict of resources, as for a node without options. |
required |
Examples:
>>> import surpyval as surv
>>> from repyability import ComponentOption
>>> pumps = [
... ComponentOption(
... "standard", surv.Weibull.from_params([8000, 1.8]), cost=4000
... ),
... ComponentOption(
... "premium", surv.Weibull.from_params([15000, 2.2]), cost=9000
... ),
... ]
>>> pumps[1].name, pumps[1].cost
('premium', 9000)
Common-cause failures¶
CCFGroup ¶
CCFGroup(members, model)
A common-cause group: member nodes coupled by a shared failure cause.
Pass groups to NonRepairableRBD as
ccf_groups=[...]. At each time the RBD takes the members' failure
probability Q (1 - sf(t), or for the RBD's ff their model's
own ff(t), which keeps a small one's precision), splits it with the
model's decompose into independent failures and mutually exclusive
shocks,
and computes the exact system reliability by conditioning on every
group's shock outcome. The groups enter the RBD's sf and ff
and the methods built on them (sf raises NotImplementedError
if a member is forced through working_nodes/broken_nodes).
A model that splits the failure probability (the default) is meant
for a small Q: the RBD warns, once per group, when a member's
Q passes VALIDITY (0.1), and its exact mean refuses the
group, while its Monte-Carlo random and mean(method="simulate")
leave the group out. A model that splits the failure rate
(basis="rate") holds over the whole life: the exact mean
includes the group, and the simulations draw its shared shocks. The
RBD's importance measures condition a member on its state through the
shock outcomes, its parameter sensitivity and uncertainty take a
group's members together (with the model's own parameters), and its
redundancy allocation lets a BetaFactor member's copies join the
group; its condition-based methods raise NotImplementedError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
collection of node names
|
The RBD nodes that share the common cause (at least two, distinct). Standard CCF theory is for symmetric groups, so the members must carry identical component models. |
required |
model
|
(BetaFactor, MGL or AlphaFactor)
|
The common-cause model coupling the members. An
|
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If there are fewer than two members, a member is listed twice,
|
Notes
The RBD validates the groups when it is built. It rejects a member that is not a component node of the RBD (an unknown node, the input or output node, or a repeated node), a node in more than one group, and a group whose members carry different models (compared through their serialised form; members that cannot be serialised are not compared).
Examples:
Two filters in parallel, each failing with probability 0.1, where 10% of their failures come from a shared cause:
>>> from surpyval import FixedEventProbability
>>> from repyability import BetaFactor, CCFGroup, NonRepairableRBD
>>> edges = [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")]
>>> nodes = {n: FixedEventProbability.from_params(0.1) for n in "ab"}
>>> round(NonRepairableRBD(edges, nodes).sf(), 4) # independent
0.99
>>> group = CCFGroup(["a", "b"], BetaFactor(0.1))
>>> rbd = NonRepairableRBD(edges, nodes, ccf_groups=[group])
>>> round(rbd.sf(), 4) # (1 - 0.01) * (1 - 0.09 ** 2)
0.982
BetaFactor ¶
BetaFactor(beta, basis='probability')
Bases: _Model
The beta-factor common-cause model (all-or-nothing).
A fraction beta of each component's failures come from a cause
shared across the whole group (which fails every member
simultaneously); the rest are independent. It applies to a group of
any size. Use it through a CCFGroup; models
with equal beta and basis compare equal.
basis says what beta is a fraction of. By default,
"probability", of each member's probability of failing Q: the
shared cause fails the group with beta * Q, and each member fails
on its own with (1 - beta) * Q. That is the PRA basic-event model,
for a small Q, as over a mission or a proof-test interval; over a
whole life it stops describing a lifetime (each member would only ever
fail with 1 - beta * (1 - beta)), and the RBD warns once a member's
Q passes 0.1. With "rate", of each member's failure rate: the
shared cause is a shock that has not struck by t with probability
R(t) ** beta, and each member survives its own causes with
R(t) ** (1 - beta), R the members' reliability. Every member
keeps its own life distribution, the model holds over the whole life,
and the RBD's MTTF and simulations include the group. The two agree to
first order in Q.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
beta
|
float
|
The common-cause fraction, in |
required |
basis
|
(probability, rate)
|
What |
"probability"
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
With a failure probability Q = 0.2 per member and beta = 0.1,
each member fails independently with 0.9 * 0.2 and the shared
cause fails both with 0.1 * 0.2:
>>> from repyability import BetaFactor
>>> q_independent, shocks = BetaFactor(0.1).decompose(["a", "b"], 0.2)
>>> round(float(q_independent[0]), 4)
0.18
>>> [(sorted(members), round(float(q[0]), 4)) for members, q in shocks]
[(['a', 'b'], 0.02)]
Split by rate, the shock strikes with 1 - 0.8 ** 0.1 and each
member fails on its own with 1 - 0.8 ** 0.9: close to the above at
this Q, and a lifetime model at any:
>>> rate = BetaFactor(0.1, basis="rate")
>>> q_independent, shocks = rate.decompose(["a", "b"], 0.2)
>>> round(float(q_independent[0]), 4)
0.1819
>>> [(sorted(members), round(float(q[0]), 4)) for members, q in shocks]
[(['a', 'b'], 0.0221)]
shocks
property
¶
shocks
How the shared causes combine: "independent" events (by
rate, always), or "exclusive" ones (the probability split's
default; see MGL).
required_group_size ¶
required_group_size()
The group size this model requires: None, meaning any size.
CCFGroup checks a model's required size
against its members; a beta-factor model fits a group of any size
(two or more members).
Returns:
| Type | Description |
|---|---|
None
|
Always None. |
decompose ¶
decompose(members, Q)
Split the failure probability into independent and shared parts.
Each member's failure probability Q splits into an independent
part and one whole-group shock. The RBD calls this at each
evaluation time with Q, the probability that the group's first
member has failed by then.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
collection of hashable
|
The group's member node names. |
required |
Q
|
float or array_like
|
Each member's total failure probability (the same for every member of a symmetric group), at one or more times. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
q_independent |
ndarray
|
Each member's independent failure probability, at least 1-d:
|
shocks |
list of (frozenset, numpy.ndarray)
|
One shock, |
MGL ¶
MGL(*letters, basis='probability', shocks=None)
Bases: _SpecificSets
The Multiple Greek Letter common-cause model.
The parameters are the conditional probabilities of a common-cause failure
escalating to the next level: beta = P(shared by >= 2 | failed),
gamma = P(>= 3 | >= 2), delta = P(>= 4 | >= 3), and so on. The
number of parameters fixes the group size: n letters describe a group
of n + 1 members. MGL(beta) is exactly
BetaFactor on a two-member group.
The probability that a common cause fails a specific set of k of the
m members is the standard MGL basic-event probability
Q_k = [1 / C(m-1, k-1)] * (rho_1 * ... * rho_k) * (1 - rho_{k+1}) * Q
with rho_1 = 1, rho_2 = beta, rho_3 = gamma, ..., and
rho_{m+1} = 0. Those of the sets holding a component sum to its
total failure probability Q; its own failure and the shared causes
being separate events, it fails with probability Q to first order
(Q - beta (1 - beta) Q**2 for one shared cause), as in PRA's basic
events. The AlphaFactor model gives the
same Q_k from the parameters event counts estimate.
That is the default basis, "probability": the PRA basic-event
model, for a small Q (see BetaFactor).
With basis="rate" the same fractions split each member's failure
rate instead: each specific set of k members has a cause of its
own, a shock that has not struck by t with probability
R(t) ** (Q_k / Q), independently of the others, and the members it
strikes fail together. Every member keeps its own life distribution,
and the model holds over the whole life.
How the shocks combine, splitting the probability. By default
(shocks="exclusive") the outcomes are mutually exclusive: the
group is struck by one shared cause at most, set S with
probability Q_k, so each member fails with probability Q to
first order. PRA codes (SAPHIRE, CAFTA, RiskSpectrum) instead take each
Q_k as a basic event of its own, independent of the others, so
that several may strike: shocks="independent". The two differ at
second order in Q (a 2-out-of-3 group of MGL(0.2, 0.3) at
Q = 0.0311 fails with probability 0.010219 one way and 0.010192
the other), so match the tool you check against. A
BetaFactor, or an MGL model with one shared cause, has one
shock, and both agree. By rate, the causes strike independently.
Use it through a CCFGroup; models with equal
letters, basis and shocks compare equal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*letters
|
float
|
|
()
|
basis
|
(probability, rate)
|
What the letters split, by default |
"probability"
|
shocks
|
(exclusive, independent)
|
How the shared causes combine when the letters split the
probability (keyword only): by default |
"exclusive"
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If no letters are given, any is outside |
Examples:
A three-member group with beta = 0.1 and gamma = 0.3, each
member failing with probability Q = 0.1:
>>> from repyability import MGL
>>> mgl = MGL(0.1, 0.3)
>>> mgl.group_size
3
>>> q_independent, shocks = mgl.decompose(["a", "b", "c"], 0.1)
>>> round(float(q_independent[0]), 4) # (1 - beta) * Q
0.09
>>> for members, q in shocks:
... print(sorted(members), round(float(q[0]), 4))
['a', 'b'] 0.0035
['a', 'c'] 0.0035
['b', 'c'] 0.0035
['a', 'b', 'c'] 0.003
shocks
property
¶
shocks
How the shared causes combine: "exclusive" (one at most,
the probability split's default) or "independent" (as
independent basic events; by rate, always).
required_group_size ¶
required_group_size()
The group size this model requires, group_size.
CCFGroup rejects a group whose number of
members differs.
Returns:
| Type | Description |
|---|---|
int
|
|
decompose ¶
decompose(members, Q)
Split the failure probability into independent and shared parts.
With m = len(members), each member fails alone with Q_1
and each specific set of k >= 2 members fails together with
the basic-event probability Q_k given in the class
description. The RBD calls this at each evaluation time with
Q, the probability that the group's first member has failed
by then.
By rate, or with shocks="independent", the shocks are the sets
of members the causes that have struck fail between them: several
causes may strike, and a member fails if any of its causes has, so
these are the distinct unions, mutually exclusive as the RBD needs,
each with its probability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
collection of hashable
|
The group's member node names: |
required |
Q
|
float or array_like
|
Each member's total failure probability (the same for every member of a symmetric group), at one or more times. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
q_independent |
ndarray
|
|
shocks |
list of (frozenset, numpy.ndarray)
|
|
AlphaFactor ¶
AlphaFactor(alphas, *, testing='staggered', basis='probability', shocks=None)
Bases: _SpecificSets
The alpha-factor common-cause model.
The parameters are the shares of a group's failure events by how many
members each failed: alpha_k is the fraction of the events that
failed exactly k of the m members, so alpha_1, ..., alpha_m
sum to 1. Each is estimated directly from event counts, which is why
common-cause data sources (NUREG/CR-5485, the NRC's CCF parameter
estimates) publish this model's parameters. The multiplicities are the
MGL model's: the probability that a common cause
fails a specific set of k of the m members is
Q_k = [1 / C(m-1, k-1)] * alpha_k * Q
for a group whose members are tested one after another, in a
staggered scheme (testing="staggered"), and
Q_k = [k / C(m-1, k-1)] * (alpha_k / alpha_t) * Q
with alpha_t = sum of k * alpha_k, for one tested all at once,
in a non-staggered scheme (testing="non-staggered"): the schemes
count a multiple failure differently (NUREG/CR-5485), so use the one
the estimates were published for.
Either way the Q_k of the sets holding a component sum to its total
failure probability Q, and everything else is as for MGL (the
basis, the shocks, and what the diagrams do with the group):
each AlphaFactor model is an MGL model with the letters
rho_k = sum over j >= k of C(m-1, j-1) Q_j / sum over j >= k-1.
The levers of the model (its parameters, for levers(),
parameter_sensitivity and the parameter uncertainty) are
alpha_2, ..., alpha_m, named ccf_alpha_2, ...; alpha_1 is
the remainder, so a change that would take their sum past 1 is
refused.
Use it through a CCFGroup; models with equal
alpha factors, testing, basis and shocks compare equal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alphas
|
sequence of float
|
|
required |
testing
|
(staggered, non - staggered)
|
The testing scheme the alpha factors were estimated under, by
default |
"staggered"
|
basis
|
(probability, rate)
|
What the alpha factors split, by default |
"probability"
|
shocks
|
(exclusive, independent)
|
How the shared causes combine when the alpha factors split the
probability (keyword only): by default |
"exclusive"
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two alpha factors are given, any is not a number in
|
Examples:
A three-member group in which 95% of the failure events failed one
member, 4% two and 1% all three, each member failing with probability
Q = 0.1, its tests staggered:
>>> from repyability import AlphaFactor
>>> model = AlphaFactor([0.95, 0.04, 0.01])
>>> model.group_size
3
>>> q_independent, shocks = model.decompose(["a", "b", "c"], 0.1)
>>> round(float(q_independent[0]), 4) # alpha_1 * Q
0.095
>>> for members, q in shocks:
... print(sorted(members), round(float(q[0]), 4))
['a', 'b'] 0.002
['a', 'c'] 0.002
['b', 'c'] 0.002
['a', 'b', 'c'] 0.001
The same alpha factors estimated under non-staggered testing give
more weight to the multiple failures (alpha_t = 1.06):
>>> model = AlphaFactor([0.95, 0.04, 0.01], testing="non-staggered")
>>> q_independent, shocks = model.decompose(["a", "b", "c"], 0.1)
>>> round(float(q_independent[0]), 4) # 0.95 / 1.06 * Q
0.0896
>>> round(float(shocks[0][1][0]), 4) # 2 / 2 * 0.04 / 1.06 * Q
0.0038
>>> round(float(shocks[-1][1][0]), 4) # 3 * 0.01 / 1.06 * Q
0.0028
shocks
property
¶
shocks
How the shared causes combine: "exclusive" (one at most,
the probability split's default) or "independent" (as
independent basic events; by rate, always).
required_group_size ¶
required_group_size()
The group size this model requires, group_size.
CCFGroup rejects a group whose number of
members differs.
Returns:
| Type | Description |
|---|---|
int
|
|
decompose ¶
decompose(members, Q)
Split the failure probability into independent and shared parts.
With m = len(members), each member fails alone with Q_1
and each specific set of k >= 2 members fails together with
the basic-event probability Q_k given in the class
description. The RBD calls this at each evaluation time with
Q, the probability that the group's first member has failed
by then.
By rate, or with shocks="independent", the shocks are the sets
of members the causes that have struck fail between them: several
causes may strike, and a member fails if any of its causes has, so
these are the distinct unions, mutually exclusive as the RBD needs,
each with its probability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
members
|
collection of hashable
|
The group's member node names: |
required |
Q
|
float or array_like
|
Each member's total failure probability (the same for every member of a symmetric group), at one or more times. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
q_independent |
ndarray
|
|
shocks |
list of (frozenset, numpy.ndarray)
|
|
Condition-based evaluation¶
NodeState
dataclass
¶
NodeState(age=0.0, alive=True, down_for=0.0, maintenance=False, since_scheduled=None, stationary=False, virtual_age=None)
The current condition of a single RBD node.
Passed, as a dict {node: NodeState}, to the condition-based methods
of NonRepairableRBD
(sf_given_state, remaining_life and
importances_given_state). A working component's forward reliability
is conditioned on the life it has already survived:
R_i(x | age) = R_i(age + x) / R_i(age). A failed component
(alive=False) contributes zero reliability regardless of its age. A
node left out of the dict is treated as new and unconditioned. The state
is immutable (a frozen dataclass).
A RepairableRBD takes the same states,
with more fields, to start its analyses from now (state= of
point_availability, availability and the others): a component
up is age since it was last put into service as new; one down has
been down for down_for, in a repair, or (maintenance=True) in
its preventive maintenance; one on a calendar (block replacement, or
tests of hidden failures) is since_scheduled past its last
scheduled replacement or test; one repaired imperfectly has the
virtual_age it had at its last repair; and stationary=True
puts it in its long-run state instead, for a component long in
service whose state is not known.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
age
|
float
|
The component's current age / accumulated operating time ( |
0.0
|
alive
|
bool
|
Whether the component is currently working, by default |
True
|
down_for
|
float
|
For a repairable component that is down: how long it has been down
so far, by default |
0.0
|
maintenance
|
bool
|
For a repairable component that is down: whether it is down for
its preventive maintenance rather than for a repair, by default
|
False
|
since_scheduled
|
float
|
For a repairable component on a calendar: the time since its last
block replacement, or its last test (of hidden failures, at which
it was found working; with an |
None
|
stationary
|
bool
|
For a repairable component: start it in its long-run state, by
default |
False
|
virtual_age
|
float
|
For a repairable component repaired imperfectly (a spec's
|
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
Only lifetime (time-varying) distributions age. A fixed-probability
component given a state with alive=True contributes reliability 1
whatever its age: observing it working resolves its per-demand
uncertainty. Leave it out of the dict to use its unconditioned
probability instead. Covariate/load dependence is a property of the
component, not its transient state: a
RegressionNode stores the covariates, so
age keeps a single meaning (operating time) for every node type.
Examples:
>>> from repyability import NodeState
>>> NodeState(age=1200)
NodeState(age=1200, alive=True)
>>> NodeState(alive=False)
NodeState(age=0.0, alive=False)
Two Weibull components in parallel: one 50 hours old and one already
failed, so the system's reliability over the next 20 hours is that of
the survivor, R(70) / R(50):
>>> import surpyval as surv
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
... [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")],
... {
... "a": surv.Weibull.from_params([100, 2]),
... "b": surv.Weibull.from_params([100, 2]),
... },
... )
>>> state = {"a": NodeState(age=50), "b": NodeState(alive=False)}
>>> round(rbd.sf_given_state(20, state), 4)
0.7866
A repairable pump down for 2 hours of a repair, one 300 hours old, and one tested weekly, last tested 100 hours ago:
>>> NodeState(alive=False, down_for=2.0)
NodeState(age=0.0, alive=False, down_for=2.0)
>>> NodeState(age=300.0)
NodeState(age=300.0, alive=True)
>>> NodeState(age=300.0, since_scheduled=100.0)
NodeState(age=300.0, alive=True, since_scheduled=100.0)
new
property
¶
new
Whether the state is that of a component new now: up, at age 0, at the start of its calendar.