Skip to content

Fault trees, phased missions and networks

FaultTree

FaultTree(gates, events, top=None, *, ccf_groups=None)

A static fault tree: gates over basic events.

Parameters:

Name Type Description Default
gates dict

{gate name: gate}, each gate one of ("or", inputs) (occurs when any input occurs), ("and", inputs) (when all do) or ("vote", k, inputs) (when at least k of them do, 1 <= k <= len(inputs)). inputs is a list of gate and event names. An input may feed several gates, but the gates must not form a loop.

required
events dict

{event name: model} for the basic events: the probability that the event has occurred, a number in [0, 1], or a lifetime model whose ff(t) is the probability that it has occurred by time t (a surpyval distribution such as Weibull or FixedEventProbability, or a RePyability model with sf). Gate and event names must differ.

required
top Hashable

The top event, a gate. By default the one gate that is no other gate's input.

None
ccf_groups list of CCFGroup

Common-cause groups over basic events (keyword only), as a NonRepairableRBD takes them: each group's members, events with the same model, occur together through its shared causes as well as on their own, split by its BetaFactor, MGL or AlphaFactor model at every t. The top event probability and the importance measures sum over the groups' shock outcomes, exactly as the diagram's do; the cut sets stay sets of basic events (their probabilities, in ranked_cut_sets, take the groups in). By default none.

None

Attributes:

Name Type Description
top Hashable

The top event.

gates dict

{gate name: gate} as given, the kind in lower case.

events dict

{event name: model}, probabilities as floats.

repeated_events frozenset

The events that feed more than one gate.

is_fixed bool

Whether no event's probability depends on time, so that t may be left out.

ccf_groups list of CCFGroup

The common-cause groups, as given.

Raises:

Type Description
ValueError

If a gate is malformed, an input is neither a gate nor an event, a name is both, the gates form a loop, a gate or event is not below the top event, the top event is not a gate (or cannot be inferred), an event's model is invalid, or a common-cause group's member is not an event, is in two groups, or has a model the others do not.

Examples:

Cooling is lost if both pumps fail or the valve fails:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {
...         "no cooling": ("or", ["no flow", "valve"]),
...         "no flow": ("and", ["pump 1", "pump 2"]),
...     },
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> tree.top
'no cooling'
>>> round(tree.top_event_probability(), 4)  # 1 - (1 - 0.01) * 0.95
0.0595
>>> [sorted(c) for c in tree.minimal_cut_sets()]
[['valve'], ['pump 1', 'pump 2']]

gates property

gates

{gate name: gate}: ("or", inputs), ("and", inputs) or ("vote", k, inputs).

top_event_probability

top_event_probability(t=None)

The probability that the top event has occurred.

Exact, repeated events included. With lifetime models it is the probability that the top event has occurred by time t: the unreliability of the system the tree describes.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
float or ndarray

The probability, a float for a number t and an array for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

A power supply shared by two redundant channels (a repeated event). Treating its two appearances as independent would give 0.0119, too low: one supply failure takes out both channels.

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {
...         "no output": ("and", ["channel A", "channel B"]),
...         "channel A": ("or", ["supply", "pump A"]),
...         "channel B": ("or", ["supply", "pump B"]),
...     },
...     {"supply": 0.01, "pump A": 0.1, "pump B": 0.1},
... )
>>> round(tree.top_event_probability(), 4)  # 0.01 + 0.99 * 0.1 ** 2
0.0199

ff

ff(t=None)

The probability that the top event has occurred by t: the unreliability of the system the tree describes, by the name an RBD's ff has (the same as top_event_probability).

Parameters:

Name Type Description Default
t array_like

Time/s, as for top_event_probability.

None

Returns:

Type Description
float or ndarray

The probability.

sf

sf(t=None)

The probability that the top event has not occurred by t: the reliability of the system the tree describes, as an RBD's sf gives it. Worked out in its own right, not as one less ff, so a small one keeps its precision.

Parameters:

Name Type Description Default
t array_like

Time/s, as for top_event_probability.

None

Returns:

Type Description
float or ndarray

The probability.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("and", ["a", "b"])}, {"a": 0.1, "b": 0.2}
... )
>>> round(tree.sf(), 4), round(tree.ff(), 4)
(0.98, 0.02)

Hf

Hf(t=None)

The cumulative hazard H(t) = -ln R(t) of the system the tree describes, R its sf: exact (so it honours the common-cause groups), +inf where the top event is certain. While the top event is less likely than not it is -log1p(-F(t)) from its probability F, so that a small one keeps its precision, as NonRepairableRBD.Hf has it.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
float or ndarray

The cumulative hazard: a float for a number t, an array for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

One exponential event's cumulative hazard is its rate times t:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Exponential.from_params([0.01])},
... )
>>> round(tree.Hf(50), 6)
0.5

df

df(t=None, dx=1e-06)

The density of the time to the top event, f(t) = -dR/dt.

By a central finite difference of the exact sf (so it honours the common-cause groups), with step dx * |t|, relative to t (at 0, dx of the shortest of the events' typical failure times), as NonRepairableRBD.df takes the diagram's: the difference is of the top event probability while it is the smaller, which keeps its precision, the lower point is clipped at 0, and a negative result (rounding) is clipped to 0. It assumes the probability is smooth near t, which does not hold where an event's model has a kink or a step; a tree of probabilities alone has a density of 0.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None
dx float

The relative step of the difference, by default 1e-6.

1e-06

Returns:

Type Description
float or ndarray

The density: a float for a number t, an array for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

One exponential event: f(t) = rate * exp(-rate * t).

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Exponential.from_params([0.01])},
... )
>>> round(tree.df(50), 6)
0.006065

hf

hf(t=None, dx=1e-06)

The hazard rate of the top event, h(t) = f(t) / R(t): the numerical density of df over the exact sf (both honour the common-cause groups), +inf where the top event is certain.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None
dx float

The relative step of the difference, passed to df, by default 1e-6.

1e-06

Returns:

Type Description
float or ndarray

The hazard rate: a float for a number t, an array for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

One exponential event has a constant hazard rate:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Exponential.from_params([0.01])},
... )
>>> round(tree.hf(50), 6)
0.01

mean

mean(method='exact', *, mc_samples=None, seed=None)

The mean time to the top event: the MTTF of the system the tree describes.

Exact to about 1e-10, relative: the area under the probability that the top event has not occurred, integral from 0 to infinity of R(t) dt with R the exact sf, integrated by adaptive quadrature on pieces that start at the events' models' kinks, as NonRepairableRBD.mean integrates the diagram's (it is as exact as the events' models' own probabilities: see analysis_routes). inf if the top event may never occur (an event of a limited failure population). An event given as a probability has occurred, or not, from the start and for good, as sf takes it. A common-cause group that splits the failure rate (basis="rate") is included; one splitting a probability is refused, as the diagram refuses it.

method="simulate" estimates it from mc_samples lifetimes of the tree's diagram (random): the draws are the diagram's, to_rbd().mean(method="simulate").

Parameters:

Name Type Description Default
method str

"exact" (the default) or "simulate".

'exact'
mc_samples int

With method="simulate", the number of simulated lifetimes, by default 100_000.

None
seed int

With method="simulate", seeds the draws, by default None.

None

Returns:

Type Description
float

The mean time to the top event (inf if it may never occur).

Raises:

Type Description
ValueError

If every event is a probability, so that the top event has no time; if method is unknown, or mc_samples or seed is given with method="exact".

NotImplementedError

With a common-cause group that splits a failure probability (the default basis): its model assumes it is small (see CCFGroup), while over a whole lifetime it runs to 1.

Examples:

Either of two exponential events, with rates 0.01 and 0.03, is a failure rate of 0.04: a mean time of 25.

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> E = surv.Exponential.from_params
>>> tree = FaultTree(
...     {"top": ("or", ["a", "b"])}, {"a": E([0.01]), "b": E([0.03])}
... )
>>> round(tree.mean(), 8)
25.0
>>> round(tree.mean(method="simulate", mc_samples=10_000, seed=1), 1)
25.3

random

random(size, seed=None)

size random times to the top event (Monte-Carlo): the lifetimes of the tree's diagram, NonRepairableRBD.random of to_rbd() (converted once), so the draws are the diagram's (an event given as a probability occurs at the start, with its probability, or never; a common-cause group that splits a probability is left out, as there).

Parameters:

Name Type Description Default
size int

The number of lifetimes.

required
seed int

Seeds the draws, by default None.

None

Returns:

Type Description
ndarray

size times to the top event (inf where it never occurs).

Raises:

Type Description
ValueError

If size is not a positive integer.

Examples:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> E = surv.Exponential.from_params
>>> tree = FaultTree(
...     {"top": ("or", ["a", "b"])}, {"a": E([0.01]), "b": E([0.03])}
... )
>>> times = tree.random(5, seed=1)
>>> times.shape, bool((times == tree.to_rbd().random(5, seed=1)).all())
((5,), True)

time_to_reliability

time_to_reliability(target, upper_bound=None)

The time at which the probability that the top event has not occurred falls to target: the inverse of sf (so it honours the common-cause groups), found by Brent's method between 0 and an upper bound found by doubling from 1 unless given, as NonRepairableRBD.time_to_reliability finds the diagram's.

Parameters:

Name Type Description Default
target float or array_like

The reliability to solve for, in (0, 1), or several.

required
upper_bound float

A time at which the reliability is below target; by default found by doubling.

None

Returns:

Type Description
float or ndarray

The time at which sf(t) == target: an array of one for each target, if several are given.

Raises:

Type Description
ValueError

If target is not in (0, 1); if every event is a probability (the reliability is constant in time); if target exceeds the reliability at t = 0, so it is never reached; if no upper bound is found (after 1000 doublings); or if the reliability at upper_bound is still above target.

Examples:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Weibull.from_params([100, 2])},
... )
>>> round(tree.time_to_reliability(0.9), 2)
32.46

bx_life

bx_life(x, upper_bound=None)

The Bx life: the time by which the top event has occurred in x percent of systems, time_to_reliability(1 - x / 100). bx_life(10) is the B10 life.

Parameters:

Name Type Description Default
x float or array_like

The percentage in which the top event has occurred (10 for the B10 life, not a fraction), in (0, 100), or several.

required
upper_bound float

As for time_to_reliability.

None

Returns:

Type Description
float or ndarray

The time (one for each x, if several are given).

Raises:

Type Description
ValueError

If x is not in (0, 100), or as for time_to_reliability.

Examples:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Weibull.from_params([100, 2])},
... )
>>> round(tree.bx_life(10), 2)
32.46

occurs

occurs(events)

Whether the top event occurs when exactly events have.

Parameters:

Name Type Description Default
events Collection[Hashable]

The basic events that have occurred; every other has not.

required

Returns:

Type Description
bool

Whether the top event occurs.

Raises:

Type Description
ValueError

If an event is unknown.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("vote", 2, ["a", "b", "c"])},
...     {"a": 0.1, "b": 0.1, "c": 0.1},
... )
>>> tree.occurs({"a"}), tree.occurs({"a", "c"})
(False, True)

minimal_cut_sets

minimal_cut_sets()

The minimal cut sets: the smallest sets of basic events whose joint occurrence makes the top event occur.

Returns:

Type Description
list of frozenset

The minimal cut sets, smallest first (then by name).

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("vote", 2, ["a", "b", "c"])},
...     {"a": 0.1, "b": 0.1, "c": 0.1},
... )
>>> [sorted(c) for c in tree.minimal_cut_sets()]
[['a', 'b'], ['a', 'c'], ['b', 'c']]

get_min_cut_sets

get_min_cut_sets()

The minimal cut sets as a set, as an RBD's get_min_cut_sets gives them (minimal_cut_sets lists them).

Returns:

Type Description
set of frozenset

The minimal cut sets.

get_min_path_sets

get_min_path_sets()

The minimal path sets as a set, as an RBD's get_min_path_sets(include_in_out_nodes=False) gives them (minimal_path_sets lists them).

Returns:

Type Description
set of frozenset

The minimal path sets.

minimal_path_sets

minimal_path_sets()

The minimal path sets: the smallest sets of basic events whose not occurring keeps the top event from occurring.

Returns:

Type Description
list of frozenset

The minimal path sets, smallest first (then by name).

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["a", "both"]), "both": ("and", ["b", "c"])},
...     {"a": 0.1, "b": 0.1, "c": 0.1},
... )
>>> [sorted(p) for p in tree.minimal_path_sets()]
[['a', 'b'], ['a', 'c']]

ranked_cut_sets

ranked_cut_sets(t=None)

The minimal cut sets with their probabilities, most likely first.

A cut set's probability is that all its events occur: the product of theirs, or with common-cause groups, as the groups' shared causes add to it (a cut set of two members of a group is often the largest). The largest say which combinations of failures dominate the top event.

Parameters:

Name Type Description Default
t float

The time, a single number. May be left out when no event's probability depends on time.

None

Returns:

Type Description
list of tuple

(cut set, probability), in decreasing probability (ties smallest first).

Raises:

Type Description
ValueError

If t is not a single number, or is left out and an event's probability depends on time.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.005},
... )
>>> [(sorted(c), round(p, 4)) for c, p in tree.ranked_cut_sets()]
[(['pump 1', 'pump 2'], 0.01), (['valve'], 0.005)]

birnbaum_importance

birnbaum_importance(t=None)

Birnbaum importance of each basic event.

In the guide's Greeks it is delta: how far the system moves with each basic event (see Sensitivities: the Greeks).

P(top | e occurred) - P(top | e did not): how much the top event probability depends on the event, the rate at which it rises with the event's probability.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
dict

{event: importance}: floats for a number t, arrays for an array.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> {e: round(v, 4) for e, v in tree.birnbaum_importance().items()}
{'pump 1': 0.095, 'pump 2': 0.095, 'valve': 0.99}

criticality_importance

criticality_importance(t=None, kind='failure')

Criticality importance of each basic event.

kind="failure" (the default): I_B(e) * P(e) / P(top), the probability that the event has occurred and is critical, given that the top event has occurred, so the share of the top event it accounts for; nan where the top event cannot occur. kind="success": I_B(e) * (1 - P(e)) / (1 - P(top)), the probability that the event has not occurred and is critical, given that the top event has not, as NonRepairableRBD.criticality_importance defines both forms; nan where the top event is certain.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None
kind str

"failure" (the default) or "success".

'failure'

Returns:

Type Description
dict

{event: importance}: floats for a number t, arrays for an array.

Raises:

Type Description
ValueError

If kind is neither "failure" nor "success", or t is left out and an event's probability depends on time.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> {e: round(v, 4) for e, v in tree.criticality_importance().items()}
{'pump 1': 0.1597, 'pump 2': 0.1597, 'valve': 0.8319}

risk_achievement_worth

risk_achievement_worth(t=None)

Risk achievement worth of each basic event.

P(top | e occurred) / P(top): how many times more likely the top event is once the event has occurred.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
dict

{event: importance}: floats for a number t, arrays for an array.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> {e: round(v, 2) for e, v in tree.risk_achievement_worth().items()}
{'pump 1': 2.44, 'pump 2': 2.44, 'valve': 16.81}

risk_reduction_worth

risk_reduction_worth(t=None)

Risk reduction worth of each basic event.

P(top) / P(top | e did not occur): the factor by which the top event would be less likely if the event could not occur.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
dict

{event: importance}: floats for a number t, arrays for an array (inf where preventing the event would make the top event impossible).

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> {e: round(v, 3) for e, v in tree.risk_reduction_worth().items()}
{'pump 1': 1.19, 'pump 2': 1.19, 'valve': 5.95}

fussell_vesely

fussell_vesely(t=None, fv_type='c', method='exact')

Fussell-Vesely importance of each basic event.

The share of the top event probability carried by the minimal cut sets that contain the event: the probability that one of them has occurred (every event in it), over the top event probability, as NonRepairableRBD.fussell_vesely computes it. It is exact, and between 0 and 1, by default; method="rare_event" sums the cut sets' probabilities instead (the rare-event approximation of their union, as many PRA tools report it), which can exceed 1 when the events are not rare. nan where the top event cannot occur. With fv_type="p" the minimal path sets (see minimal_path_sets) take the place of the cut sets, as the diagram's fv_type="p" does: the probability that every event of some path set containing the event has occurred.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None
fv_type str

"c" (the cut sets, the default) or "p" (the path sets).

'c'
method str

"exact" (the default) or "rare_event".

'exact'

Returns:

Type Description
dict

{event: importance}: floats for a number t, arrays for an array.

Raises:

Type Description
ValueError

If fv_type is not "c" or "p", method is not "exact" or "rare_event", or t is left out and an event's probability depends on time.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> {e: round(v, 4) for e, v in tree.fussell_vesely().items()}
{'pump 1': 0.1681, 'pump 2': 0.1681, 'valve': 0.8403}

differential_importance

differential_importance(t=None, *, change='uniform', kind='failure', groups=None)

Each basic event's share of the change in the top event probability when they all change together: the differential importance measure (DIM, Borgonovo & Apostolakis, 2001).

In the guide's Greeks it is DIM, the shares of a change (see Sensitivities: the Greeks).

DIM_e = dP/dq_e dq_e / sum_f dP/dq_f dq_f, with q_e the events' probabilities, so the shares add up to 1, and a group's share is the sum of its members' (groups): what share of a possible gain lies in the pumps, say. change="uniform" moves every probability by as much, which shares out the Birnbaum importance; "proportional" each by the same fraction of itself, which shares out the criticality importance. With common-cause groups, a member's measures are conditioned through the groups' outcomes, as birnbaum_importance and criticality_importance work them out. NaN where the shares' total is 0 (the top event cannot occur, or cannot be helped).

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None
change str

"uniform" (the default) or "proportional".

'uniform'
kind str

With change="proportional", the criticality importance shared out: "failure" (the default) or "success" (see criticality_importance), as NonRepairableRBD.differential_importance takes it.

'failure'
groups dict

{name: events}: each group's share, the sum of its events', instead of each event's.

None

Returns:

Type Description
dict

{event: share}, or {group: share} with groups: floats for a number t, arrays for an array.

Raises:

Type Description
ValueError

If change is not "uniform" or "proportional", kind is not "failure" or "success", a group names an unknown event, or t is left out and an event's probability depends on time.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> shares = tree.differential_importance(
...     groups={"pumps": ["pump 1", "pump 2"], "valve": ["valve"]}
... )
>>> {g: round(v, 4) for g, v in shares.items()}
{'pumps': 0.161, 'valve': 0.839}

joint_importance

joint_importance(t=None)

The joint (second-order) importance of each pair of basic events: whether preventing the two together is worth more than preventing each.

In the guide's Greeks it is gamma: complements or substitutes (see Sensitivities: the Greeks).

JRI(e, f) = -d2P / dq_e dq_f, P the top event probability and q the events' probabilities: how much event f's Birnbaum importance falls when event e goes from occurring to not. It is the joint importance of the diagram the tree describes (see to_rbd), in its reliability. Positive, the two are complements, as under an OR gate: preventing either makes preventing the other worth more. Negative, they are substitutes, as under an AND gate: either one prevented keeps the gate from occurring. Exact, from the top event probability with both events held, as the tree is multilinear in the events. With common-cause groups, the top event probability given each of the pair occurring and not is conditioned on their states, as birnbaum_importance conditions on one (two members of one group on their joint state), and an event outside the groups is held.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
dict

{(e, f): JRI} for each pair once, its events' names in order as text, and found either way round (the measure is symmetric): floats for a number t, arrays for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> joint = tree.joint_importance()
>>> round(joint[("pump 1", "pump 2")], 4)  # substitutes
-0.95
>>> round(joint[("pump 1", "valve")], 4)  # complements
0.1

structural_importance

structural_importance()

The structural importance of each basic event: the fraction of the states of the other events in which it is pivotal, the top event occurring when it has and not when it has not.

It is the Birnbaum importance with every event's probability at 1/2, so it depends on the gates alone, not on the events' models or the common-cause groups: useful at design time, before any data. It is the diagram's structural_importance (see to_rbd).

Returns:

Type Description
dict

{event: importance}, each in [0, 1].

Examples:

The valve is pivotal unless both pumps have failed (three states of four), and a pump when the other has failed and the valve has not (one of four):

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> tree.structural_importance()
{'pump 1': 0.25, 'pump 2': 0.25, 'valve': 0.75}

improvement_potential

improvement_potential(t=None)

The improvement potential of each basic event: P(top) - P(top | e did not occur), how far the top event probability would fall if the event could not occur. It is I_B(e) * q_e, the Birnbaum importance times the event's probability, and is worked out as that product, so a small one keeps its precision; with common-cause groups, a member's is conditioned through the groups' outcomes, as birnbaum_importance is.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None

Returns:

Type Description
dict

{event: potential}: floats for a number t, arrays for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

A valve that could not fail would take the top event from 0.0595 to the pumps' 0.01:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> {e: round(v, 4) for e, v in tree.improvement_potential().items()}
{'pump 1': 0.0095, 'pump 2': 0.0095, 'valve': 0.0495}

barlow_proschan_importance

barlow_proschan_importance(t=None)

Each basic event's Barlow-Proschan importance: the probability that the top event is caused by the event's occurring (Barlow & Proschan, 1975), given that it has occurred by t, or ever.

In the guide's Greeks it is theta, integrated: who caused the failures (see Sensitivities: the Greeks).

An event's occurring causes the top event when the event is critical then, so the probability that the top event has occurred by t through event e is integral_0^t I_B^e(s) f_e(s) ds, its Birnbaum importance times its model's density, and its share of the sum over the events is its importance: the shares add up to 1. A common-cause group's part is its members' together, under the tuple of their names (the top event probability's change with the members' lives moved on, by differences). The integrals are by adaptive quadrature to about 1e-9, as NonRepairableRBD.barlow_proschan_importance works out the diagram's. An event given as a probability has no density, so no share; with every event one, the top event occurs at the start or never, and the measure is refused.

Parameters:

Name Type Description Default
t array_like

Time/s by which the top event has occurred, a number or an array; by default over the whole life.

None

Returns:

Type Description
dict

{event: share} (and each common-cause group's): floats for a number t or the whole life, arrays for an array. NaN where the top event cannot have occurred.

Raises:

Type Description
ValueError

For a negative time, or if every event is a probability (the system then has no lifetimes).

Examples:

Either of two exponential events, with rates 1 and 3: the second causes three quarters of the top events.

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> E = surv.Exponential.from_params
>>> tree = FaultTree(
...     {"top": ("or", ["a", "b"])}, {"a": E([1.0]), "b": E([3.0])}
... )
>>> shares = tree.barlow_proschan_importance()
>>> {e: round(v, 6) for e, v in shares.items()}
{'a': 0.25, 'b': 0.75}

parameter_sensitivity

parameter_sensitivity(t=None, rel_step=1e-05)

The sensitivity of the probability that the top event has not occurred (sf) to each parameter of the events' models.

In the guide's Greeks it is the levers' deltas (see Sensitivities: the Greeks).

For event e with parameter theta, d sf / d theta = I_B(e) * d sf_e(t; theta) / d theta: its Birnbaum importance times how far its model's reliability moves with the parameter, the latter by a central difference with step rel_step * |theta| (rel_step if theta is 0), the model rebuilt with from_params (one-sided where one side is not a valid value, nan where neither is), as NonRepairableRBD.parameter_sensitivity works out the diagram's. An event given as a probability has one parameter, p, the probability (as a FixedEventProbability names it). The members of a common-cause group carry one model, so its parameters are reported once, under the tuple of the members, each moved for every member at once, and so are the group's own ("ccf_beta", and an MGL model's "ccf_gamma", ...), each by a central difference of the exact probabilities with the groups. levers() lists the parameters, with their values and ranges, and with_levers builds the tree with them moved.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when no event's probability depends on time.

None
rel_step float

The relative step of the differences, by default 1e-5.

1e-05

Returns:

Type Description
dict

{event: {parameter: sensitivity}}, by surpyval's parameter names, a common-cause group's under the tuple of its members: floats for a number t, arrays for an array.

Raises:

Type Description
ValueError

If t is left out and an event's probability depends on time.

Examples:

One exponential event: R = exp(-rate t), so dR/d rate = -t exp(-rate t), about -9.048 at t = 10 with rate 0.01:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Exponential.from_params([0.01])},
... )
>>> sens = tree.parameter_sensitivity(10)
>>> {name: round(v, 3) for name, v in sens["c"].items()}
{'failure_rate': -9.048}

levers

levers()

The parameters parameter_sensitivity moves, in the order it reports them: each event's model's, by surpyval's names (p for an event given as a probability), and each common-cause group's under the tuple of its members (the one model its members carry, then its own, "ccf_beta", ...), each a Lever with its value and range, as a diagram's levers() lists them. Nothing is worked out; with_levers sets them. The levers are the events' models' own parameters: those of the units inside a composite model (a StandbyModel, a LoadSharingModel, a DegradingNode) or of a nested diagram are not reached (see the guide's Greeks).

Returns:

Type Description
list of Lever

The levers, in parameter_sensitivity's order.

Examples:

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c", "d"])},
...     {"c": surv.Weibull.from_params([100, 2]), "d": 0.05},
... )
>>> for lever in tree.levers():
...     print(lever.key, lever.name, lever.value, lever.bounds)
c alpha 100.0 (0.0, inf)
c beta 2.0 (0.0, inf)
d p 0.05 (0.0, 1.0)

with_levers

with_levers(values)

A copy of this tree with parameters at new values, each moved as parameter_sensitivity moves it: an event's model rebuilt with the parameter changed (an event given as a probability keeps being one), a common-cause group's members' one model for every member, a group's own ("ccf_beta", ...) in its model. levers() lists the parameters.

Parameters:

Name Type Description Default
values dict

Each parameter's new value, by its Lever (from levers()) or by its (key, name), as parameter_sensitivity reports it.

required

Returns:

Type Description
FaultTree

The new tree, built as this one was with the parameters moved; this one is unchanged.

Raises:

Type Description
ValueError

For a parameter the tree does not have (with the closest name), or a value its model refuses (one outside the lever's bounds).

TypeError

If values is not a dict, a lever is neither a Lever nor a (key, name), or a value is not a number.

Examples:

Twice the Weibull scale: at 100 the top event has not occurred with probability exp(-(100 / 200) ** 2), about 0.7788, where it had exp(-1):

>>> import surpyval as surv
>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["c"])},
...     {"c": surv.Weibull.from_params([100, 2])},
... )
>>> longer = tree.with_levers({("c", "alpha"): 200.0})
>>> round(longer.sf(100), 4)
0.7788

sf_uncertainty

sf_uncertainty(t=None, uncertainty=None, *, n_draws=1000, seed=None, sampling='random')

The probability that the top event has not occurred, over plausible event models (parameter uncertainty).

As NonRepairableRBD.sf_uncertainty does for a diagram's reliability (see there for the kinds of uncertainty): each of the n_draws draws gives every uncertain event a plausible model, from the normal approximation of its surpyval fit ("fit"), from distributions over named parameters, or from a list of models, and the probability at t is worked out exactly for the draw, with the common-cause groups (whose models may be drawn too, the CCFGroup given as a key). The draws are the diagram's of the tree (to_rbd, converted once): the events are its nodes, so they are keyed as its nodes are, events of one population together in a tuple, and a group's members together. An event given as a probability has no fit, so it is not drawn by default; its p may be given a distribution.

Parameters:

Name Type Description Default
t array_like

Time/s, a number or an array. May be left out when every event's probability, drawn or not, is fixed.

None
uncertainty dict

{event or tuple of events: uncertainty}, and {CCFGroup: uncertainty}, as for NonRepairableRBD.sf_uncertainty. By default every event whose model is a surpyval fit with a parameter covariance, as "fit".

None
n_draws int

The number of draws, by default 1000.

1000
seed int

Seeds the draws, for reproducible results.

None
sampling str

"random" (the default), or "sobol" for the points of a scrambled Sobol sequence.

'random'

Returns:

Type Description
UncertaintyResult

The probability of every draw (samples), the nominal value with the events' own models, and summaries such as interval (see UncertaintyResult).

Raises:

Type Description
ValueError

As for NonRepairableRBD.sf_uncertainty: an unknown event or parameter, an uncertainty that cannot be drawn, a group's members not given together, or t left out for lifetime models.

Examples:

Cooling is lost if both pumps fail or the valve does. The pumps are of one type, whose exponential failure rate is known only to lie between 1 and 3 per 1000 h, so they share the one draw:

>>> import scipy.stats as st
>>> import surpyval as surv
>>> from repyability import FaultTree
>>> pump = surv.Exponential.from_params([0.002])
>>> tree = FaultTree(
...     {"no cooling": ("or", ["no flow", "valve"]),
...      "no flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": pump, "pump 2": pump, "valve": 0.01},
... )
>>> rate = {"failure_rate": st.uniform(0.001, 0.002)}
>>> result = tree.sf_uncertainty(
...     500, {("pump 1", "pump 2"): rate}, n_draws=10_000, seed=1
... )
>>> round(result.nominal, 4)
0.5944
>>> lower, upper = result.interval(0.9)
>>> round(lower, 3), round(upper, 3)
(0.409, 0.813)

mean_uncertainty

mean_uncertainty(uncertainty=None, *, n_draws=1000, seed=None, sampling='random')

The mean time to the top event over plausible event models (parameter uncertainty).

As sf_uncertainty does for the probability: each draw gives every uncertain event a plausible model, and the mean time to the top event is worked out exactly for the draw, as mean works it out. The draws are the diagram's of the tree (to_rbd, converted once), as for sf_uncertainty.

Parameters:

Name Type Description Default
uncertainty dict

{event or tuple of events: uncertainty}, and {CCFGroup: uncertainty}, as for sf_uncertainty. By default every event whose model is a surpyval fit with a parameter covariance, as "fit".

None
n_draws int

The number of draws, by default 1000.

1000
seed int

Seeds the draws, for reproducible results.

None
sampling str

"random" (the default), or "sobol".

'random'

Returns:

Type Description
UncertaintyResult

The mean time of every draw (samples; inf for a draw in which the top event may never occur), the exact mean with the events' own models (nominal), and summaries such as interval.

Raises:

Type Description
ValueError

As for sf_uncertainty, or if every event is a probability (the top event has no time).

NotImplementedError

With a common-cause group that splits a failure probability, as for mean.

Examples:

Two pumps of one type, both of which must fail, whose exponential failure rate is known only to lie between 1 and 3 per 1000 h:

>>> import scipy.stats as st
>>> import surpyval as surv
>>> from repyability import FaultTree
>>> pump = surv.Exponential.from_params([0.002])
>>> tree = FaultTree(
...     {"no flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": pump, "pump 2": pump},
... )
>>> rate = {"failure_rate": st.uniform(0.001, 0.002)}
>>> result = tree.mean_uncertainty(
...     {("pump 1", "pump 2"): rate}, seed=1
... )
>>> round(result.nominal, 6)  # 1.5 / 0.002
750.0
>>> lower, upper = result.interval(0.9)
>>> round(lower), round(upper)
(515, 1352)

analysis_routes

analysis_routes()

How each analysis of this tree is computed, found without running it: exactly, numerically, by simulation, or not at all.

As a diagram's analysis_routes: the gates are always evaluated exactly, but a value is only as exact as the events' probabilities it is made of, so an event whose model's reliability is numerical makes the analyses built on it numerical, and one with no reliability makes them refuse. A refusal is found by the check the method itself runs, and its reason is the message it would raise.

Returns:

Type Description
dict[str, AnalysisRoute]

For each public analysis, by method name: its route ("exact", "numerical", "simulated" or "refused"), the reason, and the events that decide it, as nodes (see AnalysisRoute).

Examples:

A tree of probabilities has no time to the top event:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("and", ["a", "b"])}, {"a": 0.1, "b": 0.2}
... )
>>> routes = tree.analysis_routes()
>>> routes["top_event_probability"].route
'exact'
>>> routes["mean"].route
'refused'
>>> routes["sf_uncertainty"].route
'simulated'

to_rbd

to_rbd()

The reliability block diagram of the same system.

The diagram says when the system works: an OR gate becomes a series block of its inputs, an AND gate a parallel block, and a VOTE gate needing k of n failures a block needing n - k + 1 of its n inputs working, through a perfect junction node. Each event becomes a node with the event's model (a probability p becomes a FixedEventProbability of p); junctions are PerfectReliability nodes named after their gates.

An event or gate that feeds several gates is drawn once for each place: an event's later appearances are repeated nodes (named "event (2)", ...), which the diagram treats as the one component. So every tree converts exactly.

Returns:

Type Description
NonRepairableRBD

The diagram, from input node "input" to output node "output" (renamed if an event has that name).

Examples:

>>> from repyability import FaultTree
>>> tree = FaultTree(
...     {"top": ("or", ["valve", "flow"]),
...      "flow": ("and", ["pump 1", "pump 2"])},
...     {"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05},
... )
>>> rbd = tree.to_rbd()
>>> round(rbd.sf(), 4), round(1 - tree.top_event_probability(), 4)
(0.9405, 0.9405)

from_rbd classmethod

from_rbd(rbd)

The fault tree of a reliability block diagram.

The top event is the system failing. The diagram's modules become gates (a series block an OR gate, a parallel block an AND gate, a block needing k of n members working a VOTE gate on n - k + 1 failures), and a part that is not series-parallel (e.g. a bridge) an OR gate over its minimal cut sets, each an AND gate. The nodes' models become the events' (a node's failure by time t is the event). Gates are named "TOP" and "G1", "G2", ... (renamed if a node has that name). Nodes that cannot affect the system are left out, but for a member of a common-cause group with one that can: the tree keeps it, for the group's causes to strike it with the others, as an event its logic makes irrelevant (the top event OR(G, AND(G, member)), which is G).

Parameters:

Name Type Description Default
rbd NonRepairableRBD

The diagram.

required

Returns:

Type Description
FaultTree

The tree: its top event probability is the diagram's unreliability.

Raises:

Type Description
TypeError

If rbd is not a NonRepairableRBD.

ValueError

If the diagram cannot fail (a direct input-to-output edge).

Warns:

Type Description
UserWarning

If the diagram has node capacities, which the tree leaves out (a tree has no capacity analysis).

Examples:

>>> from surpyval import FixedEventProbability
>>> from repyability import FaultTree, NonRepairableRBD
>>> rbd = NonRepairableRBD(
...     [("s", "p1"), ("s", "p2"), ("p1", "v"), ("p2", "v"),
...      ("v", "t")],
...     {
...         "p1": FixedEventProbability.from_params(0.1),
...         "p2": FixedEventProbability.from_params(0.1),
...         "v": FixedEventProbability.from_params(0.05),
...     },
... )
>>> tree = FaultTree.from_rbd(rbd)
>>> tree.gates
{'G1': ('and', ['p1', 'p2']), 'TOP': ('or', ['G1', 'v'])}
>>> round(tree.top_event_probability(), 4), round(1 - rbd.sf(), 4)
(0.0595, 0.0595)

to_dict

to_dict()

The tree as a JSON-compatible dict (see from_dict).

Returns:

Type Description
dict

The gates, the events (probabilities as numbers, models as RePyability serialises them), the top event and any common-cause groups (as a diagram saves them).

from_dict classmethod

from_dict(d)

Rebuild a tree from to_dict's output.

Parameters:

Name Type Description Default
d dict

As to_dict returns.

required

Returns:

Type Description
FaultTree

The tree.

Raises:

Type Description
ValueError

If d is not a fault tree's dict.

to_json

to_json(fp=None, **json_kwargs)

The tree as a JSON document (see to_dict): returned, or written to fp (as surpyval's models' to_json(fp) writes them).

Parameters:

Name Type Description Default
fp (str, PathLike or file)

A path, or a file opened for writing, to write the document to; by default None: it is returned.

None
**json_kwargs

Passed to json.dumps, e.g. indent=2.

{}

Returns:

Type Description
str or None

The JSON document, or None once written to fp.

from_json classmethod

from_json(s)

Rebuild a tree from to_json's output.

Parameters:

Name Type Description Default
s (str, PathLike or file)

The JSON document: its text, a path to a file holding it, or a file opened for reading.

required

Returns:

Type Description
FaultTree

The tree.

PhasedMission

PhasedMission(phases)

A mission through phases, each with its own duration and diagram over the same non-repairable components.

A mission (take-off, cruise, landing) runs through its phases in order. Each phase is a NonRepairableRBD over some of the mission's components, which it must keep working for the phase's duration. A component is the same in every phase that uses it (the same node name, with the same model), and its life runs through the whole mission: once failed, it stays failed in the later phases, even those whose diagrams tolerate it. So the phases are not independent, and the mission's reliability is not the product of theirs. The mission succeeds if every phase's diagram works to the phase's end.

reliability and phase_failure_probabilities are exact by default: each component's life through the mission is a chain of independent segments, one per phase (Esary and Ziehms, 1975), and the phases' structures over those segments are combined in one binary decision diagram, built from their diagrams' modules and cores without listing path sets (see the module docstring), up to MAX_NODES nodes. method="simulate" draws each component's life once per mission instead, and reliability_interval gives the simulated reliability with a confidence interval, to a tolerance if asked. The importance measures (birnbaum_importance and the others) hold a component across every phase, working throughout or failed from the start, and minimal_cut_sets gives the events, a component failed by the end of a phase, that together fail the mission.

Parameters:

Name Type Description Default
phases Sequence[tuple[Hashable, float, NonRepairableRBD]]

The phases in order, each a (name, duration, rbd): a distinct name, a duration (finite, and not negative: a phase of no duration needs its diagram working at that instant), and the phase's diagram, whose node names are the mission's components.

required

Attributes:

Name Type Description
phases list

The phases, each with its name, duration, rbd, end (from the mission's start) and the components it uses.

components dict

Each component's model, by node name, in order of first use.

duration float

The mission's length: the sum of the phases' durations.

Raises:

Type Description
ValueError

If phases is empty, a phase is not a (name, duration, rbd) with a distinct name, a duration that is finite and not negative and a NonRepairableRBD, a phase has common-cause groups, or a node name has different models in different phases.

Examples:

Two engines, either of which is enough to cruise, but both needed to take off, for 0.1 hours then 5:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> round(mission.reliability(), 6)
0.995628

reliability

reliability(method='exact', *, mc_samples=None, seed=None)

The probability that the mission succeeds: that every phase's diagram works to the phase's end.

Parameters:

Name Type Description Default
method str

"exact" (the default): the Esary-Ziehms decomposition (see the class docstring). "simulate": the fraction of mc_samples simulated missions that succeed.

'exact'
mc_samples int

The number of simulated missions, by default 10_000 (method="simulate" only).

None
seed int

Seeds the simulation, by default None.

None

Returns:

Type Description
float

The mission's reliability.

Raises:

Type Description
ValueError

If method is unknown, or mc_samples or seed is given with method="exact", or mc_samples is not a positive integer.

NotImplementedError

If the mission's decision diagram needs more than MAX_NODES nodes (with METHOD = "paths", the decomposition more than MAX_STATES sub-problems): simulate instead.

Examples:

Two phases with the same diagram are one phase as long as both:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> unit = surv.Weibull.from_params([100, 2])
>>> pair = NonRepairableRBD(
...     [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")],
...     {"a": unit, "b": unit},
... )
>>> mission = PhasedMission(
...     [("first", 30.0, pair), ("second", 20.0, pair)]
... )
>>> round(mission.reliability(), 10) == round(float(pair.sf(50.0)), 10)
True

unreliability

unreliability(method='exact', *, mc_samples=None, seed=None)

The probability that the mission fails, 1 - reliability(), computed to its own relative precision when it is small (see reliability for the parameters).

Parameters:

Name Type Description Default
method str

"exact" (the default) or "simulate".

'exact'
mc_samples int

The number of simulated missions, by default 10_000.

None
seed int

Seeds the simulation, by default None.

None

Returns:

Type Description
float

The mission's unreliability.

phase_failure_probabilities

phase_failure_probabilities(method='exact', *, mc_samples=None, seed=None)

The probability that the mission fails in each phase: that the earlier phases get through and this one does not. They add up to the mission's unreliability.

Parameters:

Name Type Description Default
method str

"exact" (the default) or "simulate": the fraction of mc_samples simulated missions that fail in each phase.

'exact'
mc_samples int

The number of simulated missions, by default 10_000.

None
seed int

Seeds the simulation, by default None.

None

Returns:

Type Description
dict[Hashable, float]

Per phase, by name, the probability of failing in it.

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> unit = surv.Exponential.from_params([0.01])
>>> one = NonRepairableRBD([("s", "a"), ("a", "t")], {"a": unit})
>>> mission = PhasedMission([("out", 10.0, one), ("back", 10.0, one)])
>>> failing = mission.phase_failure_probabilities()
>>> {name: round(chance, 5) for name, chance in failing.items()}
{'out': 0.09516, 'back': 0.08611}

reliability_interval

reliability_interval(mc_samples=10000, seed=None, *, confidence=0.95, tolerance=None, max_samples=None, antithetic=False)

The mission's reliability by simulation, with a confidence interval: each component's life drawn once per mission.

Parameters:

Name Type Description Default
mc_samples int

The number of simulated missions, by default 10_000; with a tolerance, the size of each batch.

10000
seed int

Seeds the simulation, by default None.

None
confidence float

The interval's confidence level, by default 0.95.

0.95
tolerance float

Simulate until the interval is at most tolerance either side of the estimate, in batches of mc_samples, or until max_samples missions (then with a RuntimeWarning). By default None: mc_samples missions.

None
max_samples int

The most missions a run to tolerance simulates, by default 100 * mc_samples.

None
antithetic bool

Draw the missions in antithetic pairs, the second of each from 1 - u for the first's uniforms u, by default False (mc_samples must then be even).

False

Returns:

Type Description
ConfidenceInterval

The estimate, its standard error and a normal interval, clipped to [0, 1].

Raises:

Type Description
ValueError

If mc_samples is not a whole number of at least 2 (an interval needs two missions; even, with antithetic), or confidence, tolerance or max_samples is invalid.

NotImplementedError

If antithetic is asked for and a component's lives cannot be drawn from uniforms.

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> unit = surv.Exponential.from_params([0.01])
>>> one = NonRepairableRBD([("s", "a"), ("a", "t")], {"a": unit})
>>> mission = PhasedMission([("out", 10.0, one), ("back", 10.0, one)])
>>> interval = mission.reliability_interval(20_000, seed=1)
>>> bool(interval.lower < mission.reliability() < interval.upper)
True

birnbaum_importance

birnbaum_importance()

Birnbaum importance of each component over the mission.

B_i = R(i works) - R(i failed): the mission's reliability with component i working throughout (every phase's segment of its life survived) less its reliability with i failed from the start (failed in the first phase, so in every phase), the probability that i is critical to the mission. Each is the mission's decision diagram replayed with the component's segments so held, and the difference is taken from the probabilities of succeeding or of failing, whichever are the smaller, so a small importance in a reliable mission keeps its precision. Exact, at the mission's end; it assumes independent components.

Returns:

Type Description
dict[Hashable, float]

{component: importance} over the mission's components.

Raises:

Type Description
NotImplementedError

If the mission's decision diagram needs more than MAX_NODES nodes.

Examples:

Two engines, both needed to take off and either to cruise: an engine failed from the start fails the take-off, and one working throughout carries the mission unless the other fails in the take-off, so each is critical with the probability that the other lasts the take-off, exp(-0.01 * 0.1).

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> {k: round(v, 6) for k, v in mission.birnbaum_importance().items()}
{'e1': 0.999, 'e2': 0.999}

improvement_potential

improvement_potential()

Improvement potential of each component over the mission.

IP_i = R(i works) - R: how much the mission's reliability would rise if component i worked throughout (every phase's segment of its life survived). Each is the mission's decision diagram replayed with the component's segments so held (see birnbaum_importance), the difference taken from the probabilities of succeeding or of failing, whichever are the smaller. Exact, at the mission's end; it assumes independent components.

Returns:

Type Description
dict[Hashable, float]

{component: improvement potential} over the mission's components.

Raises:

Type Description
NotImplementedError

If the mission's decision diagram needs more than MAX_NODES nodes.

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> potential = mission.improvement_potential()
>>> {k: round(v, 6) for k, v in potential.items()}
{'e1': 0.003372, 'e2': 0.003372}

risk_achievement_worth

risk_achievement_worth()

Risk achievement worth (RAW) of each component over the mission.

RAW_i = F(i failed) / F, per Modarres & Kaminskiy: the factor by which the mission's unreliability would grow if component i were failed from the start (failed in the first phase, so in every phase). It is at least 1. Each unreliability is the mission's decision diagram replayed with the component's segments so held (see birnbaum_importance), in its own right (not as one less a reliability), so a small one keeps its precision; where the mission cannot fail the division gives inf or nan. Exact, at the mission's end; it assumes independent components.

Returns:

Type Description
dict[Hashable, float]

{component: RAW} over the mission's components.

Raises:

Type Description
NotImplementedError

If the mission's decision diagram needs more than MAX_NODES nodes.

Examples:

Either engine failed from the start fails the take-off, so the mission: its unreliability becomes 1.

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> raw = mission.risk_achievement_worth()
>>> {k: round(v, 2) for k, v in raw.items()}
{'e1': 228.74, 'e2': 228.74}
>>> round(1 / mission.unreliability(), 2)
228.74

risk_reduction_worth

risk_reduction_worth()

Risk reduction worth (RRW) of each component over the mission.

RRW_i = F / F(i works), per Modarres & Kaminskiy: the factor by which the mission's unreliability would shrink if component i worked throughout (every phase's segment of its life survived). It is at least 1, and inf where the component working throughout makes the mission certain. Each unreliability is the mission's decision diagram replayed with the component's segments so held (see birnbaum_importance), in its own right, so a small one keeps its precision. Exact, at the mission's end; it assumes independent components.

Returns:

Type Description
dict[Hashable, float]

{component: RRW} over the mission's components.

Raises:

Type Description
NotImplementedError

If the mission's decision diagram needs more than MAX_NODES nodes.

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> rrw = mission.risk_reduction_worth()
>>> {k: round(v, 4) for k, v in rrw.items()}
{'e1': 4.374, 'e2': 4.374}

criticality_importance

criticality_importance(kind='failure')

Criticality importance of each component over the mission.

With R and F the mission's reliability and unreliability, and R(i works) and R(i failed) its reliability with component i working throughout and failed from the start (see birnbaum_importance):

  • kind="failure" (the default) gives the failure-oriented form (Rausand & Høyland), CI_i = (R(i works) - R) / F: the probability, given that the mission has failed, that it would have succeeded with component i working throughout; the share of the mission's failures that i accounts for. For a diagram this is B_i * (1 - R_i) / (1 - R_sys), as criticality_importance gives it. It is nan where the mission cannot fail.
  • kind="success" gives the success-oriented form, CI_i = (R - R(i failed)) / R: the probability, given that the mission succeeds, that it would have failed with component i failed from the start (for a diagram, B_i * R_i / R_sys). It is nan where the mission cannot succeed.

Each difference is taken from the probabilities of succeeding or of failing, whichever are the smaller, so a small share keeps its precision. Exact, at the mission's end; it assumes independent components.

Parameters:

Name Type Description Default
kind str

"failure" (the default) or "success".

'failure'

Returns:

Type Description
dict[Hashable, float]

{component: criticality importance} over the mission's components.

Raises:

Type Description
ValueError

If kind is neither "failure" nor "success".

NotImplementedError

If the mission's decision diagram needs more than MAX_NODES nodes.

Examples:

Two engines, both needed to take off and either to cruise: the mission's failures without either engine's own failure are the other's in the take-off, so each engine accounts for a share 1 - 1 / RRW_i of the failures.

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> ci = mission.criticality_importance()
>>> {k: round(v, 4) for k, v in ci.items()}
{'e1': 0.7714, 'e2': 0.7714}
>>> round(1 - 1 / mission.risk_reduction_worth()["e1"], 4)
0.7714

fussell_vesely

fussell_vesely(method='exact')

Fussell-Vesely importance of each component over the mission: the share of the mission's failure probability that involves the component, through the mission's minimal cut sets (see minimal_cut_sets), sets of events "component c has failed by the end of phase j", each with probability F_c(T_j), that together fail the mission. A cut set has occurred when every one of its events has, and a component's are the cut sets naming it in any phase:

FV_i = P(some minimal cut set naming i has occurred) / F

with F the mission's unreliability. method="exact" (the default) works out the probability of that union exactly, from a decision diagram of the cut sets' events over the components' segments (an event being the component's first j + 1 segments not all survived), so it is between 0 and 1. method="rare_event" gives the rare-event approximation that many PRA tools report, summing the cut sets' probabilities, each the product of its events', instead of taking their union:

FV_i = sum over minimal cut sets C naming i of
       prod over (c, j) in C of F_c(T_j), divided by F

The sum over-estimates the union, so it can exceed 1 when the failure probabilities are not small. F and each event's probability are worked out in their own right (the mission's as unreliability is, a component's from its model's ff), and so is the union, so small ones keep their precision. Exact; it assumes independent components.

Parameters:

Name Type Description Default
method str

"exact" (the default): the probability of the union of the cut sets' occurrences; or "rare_event": the sum of their probabilities.

'exact'

Returns:

Type Description
dict[Hashable, float]

{component: Fussell-Vesely importance} over the mission's components.

Raises:

Type Description
ValueError

If method is not "exact" or "rare_event".

NotImplementedError

If the mission's decision diagram, or with method="exact" the cut sets', needs more than MAX_NODES nodes.

Examples:

Two engines, both needed to take off and either to cruise: an engine's share is the failures through its own loss in the take-off or both engines' by the end of the cruise, not through the other's loss in the take-off alone. The rare-event sum counts a failure that is both at once twice.

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> fv = mission.fussell_vesely()
>>> {k: round(v, 4) for k, v in fv.items()}
{'e1': 0.7827, 'e2': 0.7827}
>>> fv = mission.fussell_vesely(method="rare_event")
>>> {k: round(v, 4) for k, v in fv.items()}
{'e1': 0.7941, 'e2': 0.7941}

minimal_cut_sets

minimal_cut_sets()

The mission's minimal cut sets: sets of events (component, phase name), "the component has failed by the end of that phase", that together fail the mission, smallest first (then by name).

A failure by one phase is one by every later phase, so an event is implied by the same component's at an earlier phase. A cut set is minimal when no event can be dropped and none replaced by a weaker one, the component's failure by a later phase. They are the phases' own minimal cut sets (each a phase's diagram's, its components' failures dated to that phase), less those another implies: a set is dropped when every event of another follows from one of its own (the other names no component it lacks, each at that phase or a later one), the usual phased-mission minimisation. A phase whose diagram can never work has the empty cut set, which implies every other. They are worked out on first use and kept.

Returns:

Type Description
list of frozenset

The minimal cut sets, each a frozenset of (component, phase name) events.

Raises:

Type Description
NotImplementedError

If a phase's diagram is too meshed for its cut sets.

Examples:

Two engines, both needed to take off and either to cruise: an engine failed by the end of the take-off fails the mission, as do both failed by the end of the cruise.

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> engine = surv.Exponential.from_params([0.01])
>>> both = NonRepairableRBD(
...     [("s", "e1"), ("e1", "e2"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> either = NonRepairableRBD(
...     [("s", "e1"), ("s", "e2"), ("e1", "t"), ("e2", "t")],
...     {"e1": engine, "e2": engine},
... )
>>> mission = PhasedMission(
...     [("take-off", 0.1, both), ("cruise", 5.0, either)]
... )
>>> for cut in mission.minimal_cut_sets():
...     print(sorted(cut))
[('e1', 'take-off')]
[('e2', 'take-off')]
[('e1', 'cruise'), ('e2', 'cruise')]

analysis_routes

analysis_routes()

How each analysis of the mission is computed, found without running it: exactly, by simulation, or not at all.

The exact analyses replay the phases' decision diagram over the components' segments, and refuse when it would need more than MAX_NODES nodes (the diagram is built, and kept for the analyses; the Fussell-Vesely importance's cut sets' diagram too). They are as exact as the components' reliabilities: a component whose reliability is numerical makes them numerical, and one with none (a StandbyModel only simulations take) makes them refuse, as the diagrams' routes have it. The refusal is the message the method would raise.

Returns:

Type Description
dict[str, AnalysisRoute]

For each public analysis, by method name: its route ("exact", "numerical", "simulated" or "refused"), the reason and the components that decide it (see AnalysisRoute).

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD, PhasedMission
>>> unit = surv.Exponential.from_params([0.01])
>>> one = NonRepairableRBD([("s", "a"), ("a", "t")], {"a": unit})
>>> mission = PhasedMission([("out", 10.0, one), ("back", 10.0, one)])
>>> routes = mission.analysis_routes()
>>> routes["reliability"].route, routes["reliability_interval"].route
('exact', 'simulated')

Network

Network(links, source, target, nodes=None)

An undirected network whose links, and optionally nodes, fail, and the reliability of the connection between two of its nodes.

The network works while some path of working links, through working nodes, joins source to target. A link works both ways. Nodes work for ever unless given a model in nodes; the terminals can be given one too. Every element fails independently of the others.

The exact values come from a binary decision diagram built from the network, link by link, whose size grows with the network's width rather than with its number of paths (see the module docstring): a grid of 100 nodes takes a second and a half. One of more than MAX_STATES (5,000,000) states refuses, pointing to the simulation. The importance measures are those of a NonRepairableRBD, over the links and the failing nodes; analysis_routes says how each analysis is worked out, and to_dict saves the network.

Parameters:

Name Type Description Default
links dict

Each link's name, mapped to (node, node, model): the two nodes it joins, which must differ, and its lifetime model (anything with sf and ff, such as a fitted surpyval distribution), or the probability that it has failed (a number, taken as a FixedEventProbability). Two links may join the same two nodes.

required
source Hashable

One terminal: a node of the network.

required
target Hashable

The other terminal, another node.

required
nodes dict

The nodes that can fail, each mapped to its model (or the probability that it has failed), by default none. Their names must differ from the links'.

None

Attributes:

Name Type Description
links dict

Each link's two nodes, by name.

models dict

Each element's model, by name: the links', then the nodes'.

source, target Hashable

The terminals.

is_fixed bool

Whether every model is a fixed probability (time is then irrelevant, and x may be left out).

Raises:

Type Description
ValueError

If there is no link, a link is not a (node, node, model) of two different nodes, a model has no sf or ff, a terminal or a node in nodes is in no link, the terminals are the same node, or a node has the name of a link.

Examples:

The bridge network: links a and b out of the source, d and e into the target, and c across the middle, which works either way. With every link working with probability 0.9, the terminals stay connected with probability 2p^2 + 2p^3 - 5p^4 + 2p^5:

>>> from surpyval import FixedEventProbability
>>> from repyability import Network
>>> link = FixedEventProbability.from_params(0.1)
>>> bridge = Network(
...     {
...         "a": ("s", "x", link),
...         "b": ("s", "y", link),
...         "c": ("x", "y", link),
...         "d": ("x", "t", link),
...         "e": ("y", "t", link),
...     },
...     source="s",
...     target="t",
... )
>>> round(bridge.sf(), 5)
0.97848
>>> sorted(sorted(p) for p in bridge.path_sets())
[['a', 'c', 'e'], ['a', 'd'], ['b', 'c', 'd'], ['b', 'e']]

path_sets

path_sets()

The minimal path sets, as a set: for each simple path between the terminals, the set of its links and of its nodes that can fail (minimal_path_sets lists them, smallest first).

Returns:

Type Description
set[frozenset]

The minimal path sets, each a frozenset of link and node names.

Raises:

Type Description
NotImplementedError

If there are more than MAX_PATHS.

cut_sets

cut_sets()

The minimal cut sets, as a set: the smallest sets of links and nodes whose failure parts the terminals (minimal_cut_sets lists them, smallest first).

Returns:

Type Description
set[frozenset]

The minimal cut sets, each a frozenset of link and node names.

Raises:

Type Description
NotImplementedError

If the network's decision diagram would have more than MAX_STATES states (with METHOD = "paths", if there are more than MAX_PATHS paths).

minimal_path_sets

minimal_path_sets()

The minimal path sets, listed smallest first (then by name), as a diagram's minimal_path_sets and a tree's list them: the sets of path_sets().

Returns:

Type Description
list[frozenset]

The minimal path sets, each a frozenset of link and node names.

Raises:

Type Description
NotImplementedError

As for path_sets.

Examples:

>>> from repyability import Network
>>> fork = Network(
...     {"a": ("s", "x", 0.1), "b": ("x", "t", 0.1),
...      "c": ("s", "t", 0.2)},
...     "s", "t",
... )
>>> [sorted(path) for path in fork.minimal_path_sets()]
[['c'], ['a', 'b']]

minimal_cut_sets

minimal_cut_sets()

The minimal cut sets, listed smallest first (then by name), as a diagram's minimal_cut_sets and a tree's list them: the sets of cut_sets().

Returns:

Type Description
list[frozenset]

The minimal cut sets, each a frozenset of link and node names.

Raises:

Type Description
NotImplementedError

As for cut_sets.

Examples:

>>> from repyability import Network
>>> fork = Network(
...     {"a": ("s", "x", 0.1), "b": ("x", "t", 0.1),
...      "c": ("s", "t", 0.2)},
...     "s", "t",
... )
>>> [sorted(cut) for cut in fork.minimal_cut_sets()]
[['a', 'c'], ['b', 'c']]

sf

sf(x=None, method='exact', *, mc_samples=None, seed=None)

The probability that the terminals are connected at time/s x.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None
method str

"exact" (the default), from the minimal path sets, or "simulate": the fraction of mc_samples simulated networks still connected.

'exact'
mc_samples int

The number of simulated networks, by default 10_000.

None
seed int

Seeds the simulation, by default None.

None

Returns:

Type Description
float or ndarray

The reliability: a float for a scalar x.

Raises:

Type Description
ValueError

If x is left out of a network whose models depend on time, or method is unknown, or mc_samples or seed is given with method="exact".

NotImplementedError

With method="exact", if the network's decision diagram would have more than MAX_STATES states (with METHOD = "paths", if there are more than MAX_PATHS paths between the terminals).

ff

ff(x=None, method='exact', *, mc_samples=None, seed=None)

The probability that the terminals are parted at time/s x, 1 - sf(x), worked out to its own precision when it is small (see sf for the parameters).

Parameters:

Name Type Description Default
x float or array_like

The time/s.

None
method str

"exact" (the default) or "simulate".

'exact'
mc_samples int

The number of simulated networks, by default 10_000.

None
seed int

Seeds the simulation, by default None.

None

Returns:

Type Description
float or ndarray

The unreliability.

birnbaum_importance

birnbaum_importance(x=None)

Each element's Birnbaum importance at time/s x: the reliability with it working less that with it failed.

Every element's at once, as the derivative of the reliability, by one pass forward through the plan and one back, from whichever of the reliability and the unreliability is the smaller, so that a small importance keeps its precision.

Parameters:

Name Type Description Default
x float or array_like

The time/s.

None

Returns:

Type Description
dict

Per link and failing node, by name, its importance (0 for one that is in no path).

Raises:

Type Description
(ValueError, NotImplementedError)

As for sf.

improvement_potential

improvement_potential(x=None)

Each element's improvement potential at time/s x: how much the reliability would rise if it were made perfect.

IP_i = R(i working) - R, worked out as the Birnbaum importance times the element's probability of failing (its model's ff), so that a small one keeps its precision; as NonRepairableRBD.improvement_potential defines it.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None

Returns:

Type Description
dict

Per link and failing node, by name, its improvement potential: floats for a scalar x, arrays otherwise.

Raises:

Type Description
(ValueError, NotImplementedError)

As for sf.

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> round(bridge.improvement_potential()["a"], 5)
0.01062

risk_achievement_worth

risk_achievement_worth(x=None)

Each element's risk achievement worth (RAW) at time/s x: the factor by which the unreliability would grow if it were failed.

RAW_i = (1 - R(i failed)) / (1 - R), each unreliability worked out in its own right from the decision diagram with the element held failed, as NonRepairableRBD.risk_achievement_worth defines it. It is at least 1; inf or nan, with a numpy RuntimeWarning, where the unreliability is 0.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None

Returns:

Type Description
dict

Per link and failing node, by name, its RAW: floats for a scalar x, arrays otherwise.

Raises:

Type Description
(ValueError, NotImplementedError)

As for sf.

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> round(bridge.risk_achievement_worth()["a"], 4)
5.4414

risk_reduction_worth

risk_reduction_worth(x=None)

Each element's risk reduction worth (RRW) at time/s x: the factor by which the unreliability would shrink if it were made perfect.

RRW_i = (1 - R) / (1 - R(i working)), each unreliability worked out in its own right from the decision diagram with the element held working, as NonRepairableRBD.risk_reduction_worth defines it. It is at least 1, and inf where a perfect element makes the network perfect (a link that alone joins the terminals).

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None

Returns:

Type Description
dict

Per link and failing node, by name, its RRW: floats for a scalar x, arrays otherwise.

Raises:

Type Description
(ValueError, NotImplementedError)

As for sf.

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> round(bridge.risk_reduction_worth()["a"], 4)
1.9743

criticality_importance

criticality_importance(x=None, kind='failure')

Each element's criticality importance at time/s x.

With B_i the Birnbaum importance, R_i the element's reliability and R the network's, kind="failure" (the default) gives CI_i = B_i (1 - R_i) / (1 - R): the probability that the element has failed and is critical, given that the terminals are parted, the share of the network's failures it accounts for; kind="success" gives CI_i = B_i R_i / R, the probability that it is working and critical, given that the terminals are joined. Each is worked out from the Birnbaum importance and the network's unreliability (or reliability) in their own right, so a small one keeps its precision; nan where the network cannot fail (or cannot work). As NonRepairableRBD.criticality_importance defines it.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None
kind str

"failure" (the default) or "success".

'failure'

Returns:

Type Description
dict

Per link and failing node, by name, its criticality importance: floats for a scalar x, arrays otherwise.

Raises:

Type Description
ValueError

If kind is neither "failure" nor "success", or as for sf.

NotImplementedError

As for sf.

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> round(bridge.criticality_importance()["a"], 4)
0.4935

fussell_vesely

fussell_vesely(x=None, fv_type='c', method='exact')

Each element's Fussell-Vesely importance at time/s x: the share of the network's failure probability that involves it.

With fv_type="c" (the default), the probability that some minimal cut set containing the element has failed (every element in it), over the probability that the terminals are parted; with fv_type="p", the minimal path sets take the cut sets' place. method="exact" (the default) works out the union exactly, from a decision diagram of each element's sets (built once), as NonRepairableRBD.fussell_vesely does; "rare_event" sums the sets' probabilities instead, the rare-event approximation, which can exceed 1 when failures are not rare. Each probability is worked out in its own right, so a small one keeps its precision; nan where the network cannot fail.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None
fv_type str

"c" (the default) for the minimal cut sets and "p" for the minimal path sets.

'c'
method str

"exact" (the default) or "rare_event".

'exact'

Returns:

Type Description
dict

Per link and failing node, by name, its Fussell-Vesely importance: floats for a scalar x, arrays otherwise.

Raises:

Type Description
ValueError

If fv_type is not "c" or "p", method is not "exact" or "rare_event", or as for sf.

NotImplementedError

As for sf (with method="rare_event", as for cut_sets or path_sets).

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> round(bridge.fussell_vesely()["a"], 4)
0.5065
>>> round(bridge.fussell_vesely(method="rare_event")["a"], 4)
0.5112

structural_importance

structural_importance()

Each element's structural importance: the fraction of the other elements' states in which it is pivotal (the terminals joined when it works and parted when it fails), from the structure alone.

It is the Birnbaum importance with every element's probability at 1/2, as RBD.structural_importance works it out, and does not depend on the models.

Returns:

Type Description
dict

Per link and failing node, by name, its structural importance, in [0, 1].

Raises:

Type Description
NotImplementedError

If the network's decision diagram would have more than MAX_STATES states.

Examples:

The bridge's middle link is pivotal in an eighth of the other links' states, each other link in three eighths:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> structural = bridge.structural_importance()
>>> structural["c"], structural["a"]
(0.125, 0.375)

differential_importance

differential_importance(x=None, *, change='uniform', kind='failure', groups=None)

Each element's share of the change in the reliability when they all change together: the differential importance measure (DIM, Borgonovo & Apostolakis, 2001), as NonRepairableRBD.differential_importance defines it over the components.

change="uniform" moves every element's probability by as much, and shares out the Birnbaum importance; "proportional" each by the same fraction of itself, and shares out the criticality importance of that kind. The shares add up to 1, and a group's share is the sum of its members' (groups). NaN where there is nothing to share out.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None
change str

"uniform" (the default) or "proportional".

'uniform'
kind str

With a proportional change, which of each element's probabilities moves in proportion: "failure" (the default), of failing, or "success", of working.

'failure'
groups dict

{name: elements}: each group's share, the sum of its elements', instead of each element's.

None

Returns:

Type Description
dict

The shares, by element or group name: floats for a scalar x, arrays otherwise.

Raises:

Type Description
ValueError

For an unknown change or kind, a group naming an unknown element, or as for sf.

NotImplementedError

As for sf.

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> shares = bridge.differential_importance(
...     groups={"ends": ["a", "b", "d", "e"], "middle": ["c"]}
... )
>>> {name: round(share, 4) for name, share in shares.items()}
{'ends': 0.9633, 'middle': 0.0367}

joint_importance

joint_importance(x=None)

The joint (second-order) importance of each pair of elements: whether improving the two together is worth more than improving each (Hong & Lie, 1993), as NonRepairableRBD.joint_importance defines it.

JRI(i, j) = R(1_i, 1_j) - R(1_i, 0_j) - R(0_i, 1_j) + R(0_i, 0_j): how much element j's Birnbaum importance rises when element i goes from failed to working, worked out as that difference from the decision diagram with i held. Positive, the two are complements (links in a chain); negative, substitutes (links side by side); 0 where neither changes what the other is worth.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model is a fixed probability.

None

Returns:

Type Description
dict

{(i, j): JRI} for each pair once, its names in order as text, and found either way round: floats for a scalar x, arrays otherwise.

Raises:

Type Description
(ValueError, NotImplementedError)

As for sf.

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> joint = bridge.joint_importance()
>>> round(joint["a", "d"], 4), round(joint["a", "b"], 4)
(0.028, -0.972)

barlow_proschan_importance

barlow_proschan_importance(x=None)

Each element's Barlow-Proschan importance: the probability that the network's failure is caused by the element's, given that it fails by x, or over its whole life, as NonRepairableRBD.barlow_proschan_importance defines it.

An element's failure parts the terminals when the element is critical then, so the network has failed by x through element i with probability integral_0^x B_i(t) f_i(t) dt, its Birnbaum importance times its failure density (its model's df, or differences of its ff), integrated by adaptive quadrature (to about 1e-9, relative); its share of the sum over the elements is its importance, and the shares add up to 1.

Parameters:

Name Type Description Default
x float or array_like

The time/s by which the network has failed, by default its whole life.

None

Returns:

Type Description
dict

Per link and failing node, by name, its share: floats for a scalar x or the whole life, arrays otherwise; nan where the network cannot have failed.

Raises:

Type Description
ValueError

For a negative time, or if every element is a fixed probability (the network then has no lifetimes). A fixed element among others has no density, so no share: it has failed from the start, or never does.

NotImplementedError

As for sf.

Examples:

Two links in a chain, with failure rates 1 and 3: the second causes three quarters of the failures.

>>> import surpyval as surv
>>> from repyability import Network
>>> E = surv.Exponential.from_params
>>> chain = Network(
...     {"a": (0, 1, E([1.0])), "b": (1, 2, E([3.0]))}, 0, 2
... )
>>> shares = chain.barlow_proschan_importance()
>>> {name: round(share, 6) for name, share in shares.items()}
{'a': 0.25, 'b': 0.75}

time_to_reliability

time_to_reliability(target, upper_bound=None)

The time at which the reliability falls to target: the inverse of sf, by root-finding, as NonRepairableRBD.time_to_reliability finds it.

Parameters:

Name Type Description Default
target float or array_like

The reliability to solve for, in (0, 1), or several.

required
upper_bound float

A time at which the reliability is below target; found by doubling from 1 if None.

None

Returns:

Type Description
float or ndarray

The time at which sf(t) == target: one for each target, if several are given.

Raises:

Type Description
ValueError

If target is not in (0, 1), or exceeds the reliability at 0; if every model is a fixed probability (the reliability does not vary with time); or if no upper bound is found.

NotImplementedError

As for sf.

Examples:

>>> import surpyval as surv
>>> from repyability import Network
>>> chain = Network(
...     {"a": (0, 1, surv.Weibull.from_params([100, 2]))}, 0, 1
... )
>>> round(chain.time_to_reliability(0.9), 2)
32.46

bx_life

bx_life(x, upper_bound=None)

The Bx life: the time by which x percent of networks have failed, time_to_reliability(1 - x / 100).

Parameters:

Name Type Description Default
x float or array_like

The percentage failed (10 for the B10 life, not a fraction), in (0, 100), or several.

required
upper_bound float

As for time_to_reliability.

None

Returns:

Type Description
float or ndarray

The time by which x percent have failed: one for each x, if several are given.

Raises:

Type Description
ValueError

If x is not in (0, 100), or as for time_to_reliability.

NotImplementedError

As for sf.

Examples:

>>> import surpyval as surv
>>> from repyability import Network
>>> chain = Network(
...     {"a": (0, 1, surv.Weibull.from_params([100, 2]))}, 0, 1
... )
>>> round(chain.bx_life(10), 2)
32.46

sf_uncertainty

sf_uncertainty(x=None, uncertainty=None, *, n_draws=1000, seed=None, sampling='random')

The reliability over plausible models of the links and nodes (parameter uncertainty), as NonRepairableRBD.sf_uncertainty gives a diagram's.

Each of the n_draws draws gives every uncertain element a plausible model, and the reliability at x is worked out exactly for that draw. An element's uncertainty is "fit" (its parameters drawn from the normal approximation of its surpyval fit, through its covariance), {parameter name: distribution}, or a list of models drawn with replacement; elements given together in a tuple share their draws, as links of one type fitted to one set of data should.

Parameters:

Name Type Description Default
x float or array_like

The time/s; may be left out if every model, and every drawn model, is a fixed probability.

None
uncertainty dict

{element or tuple of elements: uncertainty} for the uncertain links and nodes; the others keep their models. By default, every element whose model is a surpyval fit with a parameter covariance is drawn as "fit", those sharing one model object together.

None
n_draws int

The number of draws, by default 1000.

1000
seed int

Seeds the draws, by default None.

None
sampling str

"random" (the default), or "sobol": the points of a scrambled Sobol sequence, which cover the parameters more evenly.

'random'

Returns:

Type Description
UncertaintyResult

The reliability of every draw (samples), the nominal value with the elements' own models, and summaries (mean, interval, ...; see UncertaintyResult).

Raises:

Type Description
ValueError

If uncertainty is empty, names an unknown element or one twice, or a tuple's elements have different models; if an uncertainty cannot be drawn; if n_draws is not a positive integer; or if x is left out for time-varying models.

NotImplementedError

As for sf.

Examples:

A chain of two lines of one type, whose failure rate is known only to lie between 0.05 and 0.15 a year, drawn once for both:

>>> import scipy.stats as st
>>> import surpyval as surv
>>> from repyability import Network
>>> line = surv.Exponential.from_params([0.1])
>>> chain = Network({"a": (0, 1, line), "b": (1, 2, line)}, 0, 2)
>>> rate = {"failure_rate": st.uniform(0.05, 0.1)}
>>> result = chain.sf_uncertainty(
...     1.0, {("a", "b"): rate}, n_draws=10_000, seed=1
... )
>>> round(result.nominal, 4)
0.8187
>>> lower, upper = result.interval(0.9)
>>> round(lower, 3), round(upper, 3)
(0.748, 0.896)

mean_uncertainty

mean_uncertainty(uncertainty=None, *, n_draws=1000, seed=None, sampling='random')

The mean time until the terminals are parted over plausible models of the links and nodes (parameter uncertainty), as NonRepairableRBD.mean_uncertainty gives a diagram's: each draw's mean worked out as mean works it out, the area under its exact reliability.

Parameters:

Name Type Description Default
uncertainty dict

As for sf_uncertainty.

None
n_draws int

The number of draws, by default 1000.

1000
seed int

Seeds the draws, by default None.

None
sampling str

"random" (the default) or "sobol", as for sf_uncertainty.

'random'

Returns:

Type Description
UncertaintyResult

The mean lifetime of every draw (samples; inf for one in which the terminals may never part), the exact mean with the elements' own models (nominal), and summaries.

Raises:

Type Description
ValueError

As for sf_uncertainty, or if every element is a fixed probability (the network then has no lifetimes).

NotImplementedError

As for sf.

Examples:

>>> import scipy.stats as st
>>> import surpyval as surv
>>> from repyability import Network
>>> line = surv.Exponential.from_params([0.1])
>>> chain = Network({"a": (0, 1, line), "b": (1, 2, line)}, 0, 2)
>>> rate = {"failure_rate": st.uniform(0.05, 0.1)}
>>> result = chain.mean_uncertainty({("a", "b"): rate}, seed=1)
>>> round(result.nominal, 6)  # 1 / 0.2
5.0
>>> lower, upper = result.interval(0.9)
>>> round(lower, 2), round(upper, 2)
(3.43, 9.01)

mean

mean(method='exact', *, mc_samples=None, seed=None)

The mean time until the terminals are parted.

Parameters:

Name Type Description Default
method str

"exact" (the default): the exact reliability integrated over time by quadrature (to about 1e-10). "simulate": the mean of mc_samples simulated lifetimes.

'exact'
mc_samples int

The number of simulated networks, by default 10_000.

None
seed int

Seeds the simulation, by default None.

None

Returns:

Type Description
float

The mean lifetime: inf if some networks never part (a fixed probability that keeps the terminals joined, say, as a diagram's mean gives it).

Raises:

Type Description
ValueError

If every model is a fixed probability (the network then has no lifetimes), method is unknown, or mc_samples or seed is given with method="exact".

random

random(size, seed=None)

size random lifetimes of the connection: in each, every element's lifetime is drawn, and the connection lasts as long as its longest-lasting path (whose life is that of its first element to fail).

The path is found by adding links in order of their lives, longest first, until the terminals are joined: a link lasts no longer than the nodes it joins.

Parameters:

Name Type Description Default
size int

The number of lifetimes.

required
seed int

Seeds the draws, by default None.

None

Returns:

Type Description
ndarray

The lifetimes (inf where the terminals never part).

Raises:

Type Description
ValueError

If size is not a positive integer.

to_dict

to_dict()

The network as a JSON-friendly dict (see from_dict), as a diagram saves its nodes (see RBD.to_dict).

Returns:

Type Description
dict

The repyability_version that wrote it, the type ("Network"), the terminals, and the links (each its name, the two nodes it joins and its model, serialised as a diagram's node models are: surpyval's in surpyval's own format, so a fit's covariance round-trips) and the failing nodes.

Raises:

Type Description
NotImplementedError

If a model cannot be serialised: one that is not surpyval's (nor a RePyability node model).

Examples:

>>> import surpyval as surv
>>> from repyability import Network
>>> chain = Network(
...     {"a": (0, 1, surv.Weibull.from_params([100, 2]))}, 0, 1
... )
>>> d = chain.to_dict()
>>> d["type"], d["links"][0]["nodes"]
('Network', [0, 1])

from_dict classmethod

from_dict(d)

Rebuild a network from to_dict's output, by calling the constructor again, so it is checked again. Tuple names (a grid's (i, j)), which JSON turns into lists, are turned back.

Parameters:

Name Type Description Default
d dict

As to_dict returns (possibly after a JSON round trip).

required

Returns:

Type Description
Network

The network.

Raises:

Type Description
ValueError

If d is not a network's dict, a model kind is unknown, or the constructor rejects the stored inputs.

Examples:

>>> import surpyval as surv
>>> from repyability import Network
>>> chain = Network(
...     {"a": (0, 1, surv.Weibull.from_params([100, 2]))}, 0, 1
... )
>>> Network.from_dict(chain.to_dict()).sf(50) == chain.sf(50)
True

to_json

to_json(fp=None, **json_kwargs)

The network as a JSON document (see to_dict): returned, or written to fp (as surpyval's models' to_json(fp) writes them).

Parameters:

Name Type Description Default
fp (str, PathLike or file)

A path, or a file opened for writing, to write the document to; by default None: it is returned.

None
**json_kwargs

Passed to json.dumps, e.g. indent=2.

{}

Returns:

Type Description
str or None

The JSON document, or None once written to fp.

Raises:

Type Description
NotImplementedError

If a model cannot be serialised (see to_dict).

TypeError

If a name cannot be encoded as JSON.

from_json classmethod

from_json(s)

Rebuild a network from to_json's output.

Parameters:

Name Type Description Default
s (str, PathLike or file)

The JSON document: its text, a path to a file holding it, or a file opened for reading.

required

Returns:

Type Description
Network

The network.

Raises:

Type Description
ValueError

If s is not valid JSON, or for the reasons given in from_dict.

FileNotFoundError

If s is neither a JSON document nor the path of a file.

Examples:

>>> import surpyval as surv
>>> from repyability import Network
>>> chain = Network(
...     {"a": (0, 1, surv.Weibull.from_params([100, 2]))}, 0, 1
... )
>>> round(Network.from_json(chain.to_json()).sf(50), 4)
0.7788

analysis_routes

analysis_routes()

How each analysis of this network is computed, found without running it: exactly, numerically, by simulation, or not at all (as NonRepairableRBD.analysis_routes reports a diagram's).

The exact values come from the network's decision diagram, so a network of more than MAX_STATES states refuses every analysis but the simulation (and the paths listed), with the message the methods raise; an element whose model has no reliability of its own makes the analyses built on it refuse, as it does.

Returns:

Type Description
dict[str, AnalysisRoute]

For each public analysis, by method name: its route ("exact", "numerical", "simulated" or "refused"), the reason and the elements that decide it (see AnalysisRoute).

Examples:

>>> from repyability import Network
>>> bridge = Network(
...     {"a": ("s", "x", 0.1), "b": ("s", "y", 0.1),
...      "c": ("x", "y", 0.1), "d": ("x", "t", 0.1),
...      "e": ("y", "t", 0.1)},
...     "s", "t",
... )
>>> routes = bridge.analysis_routes()
>>> routes["fussell_vesely"].route, routes["random"].route
('exact', 'simulated')
>>> routes["mean"].route  # a fixed probability has no lifetime
'refused'