Fault trees¶
Learning this for the first time?
This page is the reference. The idea behind fault trees, and how they relate to block diagrams, is taught step by step in Lesson 3.
A reliability block diagram says how a system works. A fault tree says
how it fails: it starts from an undesired top event (the system
failing) and works down, through gates, to the basic events that
cause it (component failures). Safety and risk analyses are usually written
this way. FaultTree evaluates static fault trees
exactly, with the same engine as the diagrams, and converts them to and from
NonRepairableRBDs.
Building a tree¶
A tree is its gates, keyed by name, and its basic events, keyed by name with a probability or a lifetime model:
import numpy as np
import surpyval as surv
from repyability import FaultTree
cooling = FaultTree(
{
"no cooling": ("or", ["no flow", "valve"]), # either will do
"no flow": ("and", ["pump 1", "pump 2"]), # needs both
},
{"pump 1": 0.1, "pump 2": 0.1, "valve": 0.05}, # probability each has occurred
)
cooling.top # 'no cooling'
cooling.top_event_probability() # -> 0.0595 1 − (1 − 0.1²)(1 − 0.05)
cooling.ff() # -> 0.0595 the same, by an RBD's name
cooling.sf() # -> 0.9405 worked out in its own right
A tree answers to an RBD's names too, so the same code runs on either:
ff and sf, and the cut and path sets as a list
(minimal_cut_sets(), which an RBD has as well) or a set
(get_min_cut_sets(), as an RBD gives them).
| Gate | Written | Occurs when |
|---|---|---|
| OR | ("or", inputs) |
any input occurs |
| AND | ("and", inputs) |
every input occurs |
| VOTE | ("vote", k, inputs) |
at least k of the inputs occur (k out of n) |
An input is a gate or a basic event. The top event is the one gate that is
no other gate's input, or pass top=. Gates must not form a loop, every gate
and event must be below the top event, and a name cannot be both a gate and
an event; mistakes raise ValueError.
A basic event is a probability in [0, 1] that it has occurred, or a lifetime
model whose ff(t) is the probability that it has occurred by time t: a
surpyval distribution (Weibull, FixedEventProbability, ...) or any
RePyability node model. With lifetime models every method takes a time, a
number or an array:
pump = surv.Weibull.from_params([8000, 1.8])
valve = surv.Weibull.from_params([20000, 1.5])
timed = FaultTree(
{"no cooling": ("or", ["no flow", "valve"]),
"no flow": ("and", ["pump 1", "pump 2"])},
{"pump 1": pump, "pump 2": pump, "valve": valve},
)
timed.top_event_probability(5000) # -> 0.2249 failed within 5000 h
timed.top_event_probability(np.array([1000, 5000, 10000]))
# array([0.01166, 0.22494, 0.72021])
The top event probability by time t is the unreliability of the system
the tree describes.
Cut sets¶
A minimal cut set is a smallest set of basic events whose joint occurrence makes the top event occur. They are the failure combinations the tree allows, and a cut set of one event is a single point of failure:
cooling.minimal_cut_sets()
# [frozenset({'valve'}), frozenset({'pump 1', 'pump 2'})]
cooling.ranked_cut_sets() # most likely first
# [(frozenset({'valve'}), 0.05), (frozenset({'pump 1', 'pump 2'}), 0.01)]
timed.ranked_cut_sets(5000) # at 5000 h the pumps dominate
# [(frozenset({'pump 1', 'pump 2'}), 0.1217), (frozenset({'valve'}), 0.1175)]
A cut set's probability is the product of its events' (with
common causes, that of its events occurring together).
minimal_path_sets() gives the dual: the smallest sets of events whose not occurring keeps the top
event from occurring. occurs(events) evaluates the tree's logic for one
combination: whether the top event occurs when exactly events have.
Repeated events¶
An event may feed several gates. Here two channels, either of which will do, share a power supply:
shared = 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},
)
shared.repeated_events # frozenset({'supply'})
shared.top_event_probability() # -> 0.0199 0.01 + 0.99 × 0.1²
Multiplying the channels' probabilities as if they were independent,
(1 − 0.99 × 0.9)² = 0.0119, would be 40% too low: one supply failure takes
out both channels at once. The tree is evaluated exactly, repeated events
included. Every gate below which nothing is shared with the rest of the tree
is a module with a closed form (an OR gate is 1 − ∏(1 − q_i), an AND gate
∏ q_i), and what the repeated events tie together is worked out by a
binary decision diagram built from the gates themselves, which branches on
one event at a time (the pivotal, or Shannon, decomposition, as for the core
of a block diagram: see
Concepts). The cut and
path sets are not listed to build it, as they multiply with the shared
events: an OR of fifty AND gates over twenty-five shared events is built and
evaluated in a fraction of a second. A tree whose diagram would pass two
million nodes (repyability.fault_tree.DIAGRAM_LIMIT) is refused, with the
advice to convert it with to_rbd() and simulate the diagram. There is no
rare-event or min-cut upper-bound approximation.
Common causes¶
Redundant pumps of one design, in one room, also fail together: from one
flood, one bad batch of seals, one maintenance error. ccf_groups (#184)
declares such groups over basic events, with the
common-cause models a block diagram takes: a
BetaFactor or MGL model splits each member's probability into the part
that is its own and the shared causes that fail several members at once.
from repyability import BetaFactor, CCFGroup
grouped = 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},
ccf_groups=[CCFGroup(["pump 1", "pump 2"], BetaFactor(0.1))],
)
grouped.top_event_probability() # -> 0.0671 against 0.0595 independent
grouped.ranked_cut_sets()
# [(frozenset({'valve'}), 0.05), (frozenset({'pump 1', 'pump 2'}), 0.0180)]
grouped.criticality_importance()
# {'pump 1': 0.255, 'pump 2': 0.255, 'valve': 0.7315}
The pumps' pair now fails with probability 0.1 × 0.1 + 0.99 × 0.09² =
0.0180, nearly twice the independent 0.01: the shared cause fails both
with β Q, and without it each fails on its own with (1 − β) Q. A
BetaFactor group is that cause drawn as a repeated event (OR(pump 1's
own, cause) and OR(pump 2's own, cause)), which the group spares you
writing; an MGL group of three or more has a cause for each set of
members, as the models assign.
- Exact, as for a diagram: the top event probability and every
importance measure sum over the groups' shock outcomes, given each of
which the events are independent; a member's importance is conditioned
on its own state through them. A tree and its
to_rbd()diagram, which carries the groups, give the same values. - The cut sets stay sets of basic events.
ranked_cut_setsgives each the probability that its events occur together, the shared causes included, so a pair of members often leads the ranking. (PRA tools that draw each shared cause as an event of its own list it as a cut set of one.) - Rules, as a diagram's: a member is a basic event, in one group at
most, and the members have one model. A group splitting a probability
warns once its members' probability passes 0.1, and
basis="rate"splits the failure rate over a lifetime (Over a lifetime). - Conversions keep them:
to_rbd()passes the groups to the diagram andfrom_rbdtakes the diagram's, refusing a group with a member that cannot affect the system (it is no event of the tree, but the group's shocks would still strike it) and dropping one whose members none can.
Importance measures¶
Each measure returns {event: value} (floats for a number t, arrays for an
array), with Q the top event probability and q_e the event's:
| Method | Definition |
|---|---|
birnbaum_importance(t) |
Q(e occurred) − Q(e did not): how much Q depends on the event. |
criticality_importance(t) |
I_B(e) · q_e / Q: the share of the top event the event accounts for. |
fussell_vesely(t) |
The probability that some minimal cut set containing e has occurred, over Q (method="rare_event": their probabilities summed, which can pass 1). |
risk_achievement_worth(t) |
Q(e occurred) / Q. |
risk_reduction_worth(t) |
Q / Q(e did not occur). |
cooling.criticality_importance()
# {'pump 1': 0.1597, 'pump 2': 0.1597, 'valve': 0.8319}
cooling.risk_achievement_worth()["valve"] # -> 16.81 top event certain once the valve fails
They are the same measures, with the same definitions, as the
importance measures of a NonRepairableRBD: a tree and the
diagram of the same system give the same values.
From diagrams to trees and back¶
FaultTree.from_rbd(rbd) turns a NonRepairableRBD into the tree of its
failure. Its series blocks become OR gates, parallel blocks AND gates, and
k-out-of-n blocks VOTE gates on n − k + 1 failures; a part that is not
series-parallel (a bridge) becomes an OR gate over its minimal cut sets. The
nodes' models become the events', and nodes that can never fail
(PerfectReliability junctions) or never matter are left out:
from repyability import NonRepairableRBD
fail = surv.FixedEventProbability.from_params
pumps_valve = NonRepairableRBD(
[("in", "p1"), ("in", "p2"), ("p1", "v"), ("p2", "v"), ("v", "out")],
{"p1": fail(0.1), "p2": fail(0.1), "v": fail(0.05)},
)
tree = FaultTree.from_rbd(pumps_valve)
tree.gates # {'G1': ('and', ['p1', 'p2']), 'TOP': ('or', ['G1', 'v'])}
tree.top_event_probability() # -> 0.0595 = 1 − pumps_valve.sf()
to_rbd() goes the other way: an OR gate becomes its inputs in series, an
AND gate in parallel, and a VOTE gate a junction node (PerfectReliability,
named after the gate) that needs n − k + 1 of its inputs working. A
repeated event becomes a repeated node:
diagram = cooling.to_rbd()
diagram.sf() # -> 0.9405
shared.to_rbd().repeated # {'supply (2)': 'supply'}
An event or gate that feeds several gates is drawn once for each place, its later appearances as repeated nodes that the diagram treats as the one component, so every tree converts exactly: the diagram's reliability is one minus the tree's top event probability. The tree of a bridge, for example, whose events each appear in several cut sets, converts back to a diagram with the bridge's logic.
Saving¶
to_json() / FaultTree.from_json() (and to_dict / from_dict) save
the gates, the top event and the events, probabilities as numbers and
models as RePyability saves a diagram's node models, and the common-cause
groups as a diagram saves them.
The model and its limits¶
- Static gates only. OR, AND and VOTE. Dynamic gates (priority-AND, spares, sequence dependence) and NOT gates are not supported; for standby and load sharing, see Redundancy models.
- Independent basic events, but for the events they share and their common causes.
- Non-repairable. A tree gives the probability that the top event has
occurred by
t. For availability with repair, build aRepairableRBD.