Lesson 2: Systems of components¶
In this lesson
You will learn:
- what a reliability block diagram shows: the logic of success, not the wiring;
- how reliabilities combine in series, in parallel and k-out-of-n, by hand
and with
NonRepairableRBD; - how component lifetimes become a system reliability curve, and why redundancy can make the hazard rise;
- how to find a system's mean time to failure, exactly and by simulation;
- how uncertainty about the component models carries to the system.
Before you start: Lesson 1. About 40 minutes.
The question¶
A cooling circuit has two identical pumps, either of which can deliver the full flow, feeding one control valve. From Lesson 1 you can describe each part: a pump fails to start one time in ten, the valve fails to open one time in twenty, and over a long run each has a lifetime distribution. But the plant manager asks about the circuit: when the plant calls for cooling, what is the probability that water flows? And how likely is the circuit to run for 50 hours without failing?
The parts alone cannot answer that. Losing one pump does no harm, because the other carries the flow; losing the valve stops everything. You need a model of the logic of success: which combinations of working parts make the system work. This lesson builds that model and pushes the component numbers through it, by hand and with RePyability.
Reliability block diagrams¶
A reliability block diagram (RBD) draws each component as a block on the way from an input node to an output node. The system works when some path of working blocks connects the input to the output. Here is the cooling circuit:
flowchart LR
s((in)) --> p1[pump 1] & p2[pump 2]
p1 & p2 --> v[valve] --> t((out))
Two paths run through it, one through each pump. If a pump fails, the other path still works; if the valve fails, both are broken. The valve is a single point of failure, a block whose failure alone fails the system.
An RBD shows success logic, not wiring. Plants often fit two valves one after the other in a pipe, so that the flow can be stopped even if one valve sticks:
tank ───── valve 1 ───── valve 2 ───── process
For the function "let the flow through", both valves must open, so their blocks are in series, one after the other on a single path. For the function "stop the flow", either valve closing is enough, so their blocks are in parallel, each on a path of its own:
flowchart LR
s((in)) --> o1[valve 1 opens] --> o2[valve 2 opens] --> t((out))
flowchart LR
s((in)) --> c1[valve 1 closes] & c2[valve 2 closes]
c1 & c2 --> t((out))
The same two valves give two diagrams, because reliability is always the reliability of a function, such as "stop the flow" or "run for 50 hours". The second valve makes stopping the flow more reliable and letting it through less so. State the function before you draw anything.
Two assumptions come with every diagram here: each block is either working or failed, and blocks fail independently, so one failing does not change the chance that another fails. Lesson 5 relaxes the second.
Series: every block must work¶
In a series system every block lies on the one path, so every block must work, like a chain that holds only if every link holds.
flowchart LR
s((in)) --> c1[part 1] --> c2[part 2] --> c3["..."] --> c10[part 10] --> t((out))
Write \(R_i\) for the reliability of block \(i\) and \(q_i = 1 - R_i\) for its unreliability (the \(F\) of Lesson 1). The system works only if block 1 works and block 2 works and so on, and for independent blocks the probability of all of these is the product:
Take ten parts, each with reliability 0.99. The weakest link is 0.99, but the chain is \(0.99^{10} = 0.904\). A chain is weaker than its weakest link, because every block adds one more way to fail: ten parts that each fail one time in a hundred make a system that fails about one time in ten.
That "about one in ten" is a useful shortcut. Multiplying out the system unreliability \(Q_{\text{sys}}\) gives
When the \(q_i\) are small their products are far smaller, so unreliabilities nearly add: \(\sum_i q_i = 10 \times 0.01 = 0.1\), against the exact 0.0956. The sum always overstates \(Q_{\text{sys}}\), because it counts more than once the cases where several blocks fail together; its relative error is at most about half the sum, 5% here. Trust it while \(\sum_i q_i\) is below about 0.1. For ten parts with \(q_i = 0.1\) it gives 1, a certain failure, against the true 0.651.
import numpy as np
import surpyval as surv
from math import comb
from scipy.integrate import quad
from repyability import NonRepairableRBD
0.99**10 # -> 0.9044 ten parts in series
1 - 0.99**10 # -> 0.0956 exact; the shortcut gives 0.1
1 - 0.9**10 # -> 0.6513 exact; the shortcut gives 1.0
In RePyability¶
NonRepairableRBD models a system whose failed blocks stay failed (repair
comes in Lesson 6). It needs the diagram's edges, as
(from, to) pairs of node names, and a dict giving each block a model.
For a fixed probability of failure, such as failing on a demand, the model is
surpyval's FixedEventProbability. The input and output nodes need no model:
they are the one node that no edge enters and the one that no edge leaves.
parts = [f"part {i}" for i in range(1, 11)]
chain = ["in"] + parts + ["out"]
series_edges = list(zip(chain[:-1], chain[1:])) # ("in", "part 1"), ("part 1", "part 2"), ...
series = NonRepairableRBD(
series_edges,
{part: surv.FixedEventProbability.from_params(0.01) for part in parts},
)
series.sf() # -> 0.9044 system reliability
series.ff() # -> 0.0956 system unreliability
sf is the reliability (the survival function of Lesson 1) and ff the
unreliability. No time is needed, because fixed probabilities do not change
with time.
The parameter is the probability of failure
FixedEventProbability.from_params(0.01) is a part that fails with
probability 0.01. Passing its reliability, 0.99, by mistake gives a part
that fails 99 times in 100, and this chain a reliability of
\(0.01^{10} = 10^{-20}\). If a result looks absurdly low, check this first.
Parallel: one block is enough¶
In a parallel system each block has a path of its own, so one working block is enough. This is active redundancy: all the units run together, and the system fails only when all have failed.
flowchart LR
s((in)) --> u1[unit 1] & u2[unit 2] & u3[unit 3]
u1 & u2 & u3 --> t((out))
"Every unit has failed" is again an and, so for independent blocks the unreliabilities multiply:
With units of reliability 0.9:
| Units | \(Q_{\text{sys}}\) | \(R_{\text{sys}}\) | Gained by the last unit |
|---|---|---|---|
| 1 | 0.1 | 0.9 | |
| 2 | 0.01 | 0.99 | 0.09 |
| 3 | 0.001 | 0.999 | 0.009 |
Each unit divides the unreliability by ten, but buys a tenth as much absolute reliability as the one before, at the same price: the diminishing returns of redundancy. Lesson 5 adds a harder limit, a common cause that fails every unit at once.
In RePyability, a parallel diagram fans out from the input to every unit and back in to the output:
def parallel(n, q):
"""n identical units in parallel, each failing with probability q."""
units = [f"unit {i}" for i in range(1, n + 1)]
edges = [("in", u) for u in units] + [(u, "out") for u in units]
models = {u: surv.FixedEventProbability.from_params(q) for u in units}
return NonRepairableRBD(edges, models)
parallel(1, 0.1).sf() # -> 0.9
parallel(2, 0.1).sf() # -> 0.99
parallel(3, 0.1).sf() # -> 0.999
Spares that wait switched off (standby redundancy) behave differently: see Redundancy models.
Reducing a series-parallel system¶
The cooling circuit mixes both patterns: the pumps are in parallel, and the pair is in series with the valve. Such a series-parallel system can be solved by reduction: replace a group of blocks purely in series, or purely in parallel, by one equivalent block, and repeat until one block is left. Take pumps with \(q = 0.1\) and a valve with \(q = 0.05\).
Step 1. The pumps are in parallel, so the pair fails only if both fail, with probability \(0.1 \times 0.1 = 0.01\). Replace them by one block with reliability 0.99:
flowchart LR
s((in)) --> pp["pump pair, R = 0.99"] --> v["valve, R = 0.95"] --> t((out))
Step 2. Two blocks remain, in series: \(R_{\text{sys}} = 0.99 \times 0.95 = 0.9405\).
q_pair = 0.1 * 0.1 # step 1: the pair fails only if both pumps fail
(1 - q_pair) * (1 - 0.05) # -> 0.9405 step 2: the pair in series with the valve
The circuit delivers water on about 94 demands in 100. Its unreliability, 0.0595, is close to \(0.01 + 0.05\) (the series shortcut), which shows where the failures come from: the valve causes five times as many as the pump pair.
In RePyability you describe the whole diagram and let the library evaluate it:
edges = [
("in", "pump1"), ("in", "pump2"), # the flow splits into two branches
("pump1", "valve"), ("pump2", "valve"), # which rejoin at the valve
("valve", "out"), # then one path to the output
]
models = {
"pump1": surv.FixedEventProbability.from_params(0.1), # fails 1 time in 10
"pump2": surv.FixedEventProbability.from_params(0.1),
"valve": surv.FixedEventProbability.from_params(0.05), # fails 1 time in 20
}
rbd = NonRepairableRBD(edges, models)
rbd.sf() # -> 0.9405
rbd.ff() # -> 0.0595
Each edge is one arrow of the diagram: the two edges leaving "in" start
two branches, the two entering "valve" join them, and the edge out of the
valve puts it in series with everything before. RePyability reduces the
diagram just as you did, and evaluates any part that cannot be reduced by a
method that handles every diagram exactly (Lesson 3).
k-out-of-n: enough of them must work¶
Sometimes one unit is not enough, yet not all are needed: three pumps may each deliver half the flow, so that any two meet the demand. Such a system is k-out-of-n: it works while at least \(k\) of its \(n\) units work.
flowchart LR
s((in)) --> a[unit A] & b[unit B] & c[unit C]
a & b & c --> t(("out: 2 of 3"))
For \(n\) identical, independent units of reliability \(p\), the number of working units has a binomial distribution, and the system works when that number is \(k\) or more:
Here \(\binom{n}{j}\), "n choose j", counts the ways of picking which \(j\) units work. For 2-out-of-3 with \(p = 0.9\):
- all three work: \(0.9^3 = 0.729\);
- exactly two work: three ways (any one unit may be the failed one), each with probability \(0.9^2 \times 0.1 = 0.081\), so 0.243;
- the system works in either case: \(R_{\text{sys}} = 0.729 + 0.243 = 0.972\).
p = 0.9
sum(comb(3, j) * p**j * (1 - p)**(3 - j) for j in (2, 3)) # -> 0.972
The extreme cases are familiar: 1-out-of-n is parallel (one unit is enough: 0.999 here) and n-out-of-n is series (all must work: 0.729), with 2-out-of-3 between them.
In RePyability, draw the units as parallel branches and give k to the node
that the branches enter: that node is reached only when at least k of
its incoming branches are reached through working blocks. Here the branches
enter the output node:
units = ["A", "B", "C"]
vote_edges = [("in", u) for u in units] + [(u, "out") for u in units]
three = {u: surv.FixedEventProbability.from_params(0.1) for u in units}
NonRepairableRBD(vote_edges, three, k={"out": 2}).sf() # -> 0.972
NonRepairableRBD(vote_edges, three, k={"out": 1}).sf() # -> 0.999 the default: parallel
NonRepairableRBD(vote_edges, three, k={"out": 3}).sf() # -> 0.729 series
If three pumps fed the valve instead, k={"valve": 2} would mean "at least
two pumps working, and the valve too". The units need not be identical:
RePyability handles any models, where the binomial formula would not apply.
Reliability over time¶
So far each block had one fixed probability. Over a period of running, each block has a lifetime instead, and its reliability \(R_i(t)\) falls with time. The diagram does not change, so the same formula holds at every instant, with \(R_i(t)\) in place of \(R_i\). For the cooling circuit, with \(R_{\text{p}}\) for a pump and \(R_{\text{v}}\) for the valve:
Give the pumps a Weibull lifetime with scale \(\alpha = 100\) h and shape \(\beta = 2\), and the valve one with \(\alpha = 200\) h and \(\beta = 1.5\), so that each \(R(t) = e^{-(t/\alpha)^\beta}\). (In practice you would fit these to failure data with surpyval.) At \(t = 50\) h:
- each pump: \(R_{\text{p}}(50) = e^{-(50/100)^2} = e^{-0.25} = 0.7788\);
- the pump pair: \(1 - (1 - 0.7788)^2 = 0.9511\);
- the valve: \(R_{\text{v}}(50) = e^{-(50/200)^{1.5}} = e^{-0.125} = 0.8825\);
- the system: \(0.9511 \times 0.8825 = 0.8393\).
pump = surv.Weibull.from_params([100, 2]) # alpha = 100 h, beta = 2
valve = surv.Weibull.from_params([200, 1.5]) # alpha = 200 h, beta = 1.5
circuit = NonRepairableRBD(edges, {"pump1": pump, "pump2": pump, "valve": valve})
circuit.sf(50) # -> 0.8393
Both pumps share one model object, which describes a type of pump; each node is still its own pump, failing independently. An array of times gives the whole curve:
hours = np.arange(0, 201, 20)
np.round(circuit.sf(hours), 4)
# array([1. , 0.9674, 0.8944, 0.7709, 0.603 , 0.4216, 0.2624, 0.1458,
# 0.0727, 0.0327, 0.0134])
circuit.sf(100) # -> 0.4216
xychart-beta
title "Cooling circuit: system reliability"
x-axis "hours" ["0", "20", "40", "60", "80", "100", "120", "140", "160", "180", "200"]
y-axis "R_sys(t)" 0 --> 1
line [1, 0.9674, 0.8944, 0.7709, 0.603, 0.4216, 0.2624, 0.1458, 0.0727, 0.0327, 0.0134]
That answers the manager's second question: the circuit runs 50 hours
without failing with probability 0.84. The curve starts flat, while both
pumps are probably running, then falls steeply: by 100 h, the pumps'
characteristic life, fewer than half of circuits still work. sf computes
these values exactly, not by simulation.
Redundancy changes the shape of the hazard¶
Recall from Lesson 1 the hazard \(h(t) = f(t)/R(t)\), the failure rate among the survivors, where \(f(t) = -dR/dt\) is the density. An exponential unit has a constant hazard \(\lambda\): it does not age. Put two of them in parallel:
flowchart LR
s((in)) --> a[unit a] & b[unit b]
a & b --> t((out))
The pair's reliability is \(R(t) = 1 - (1 - e^{-\lambda t})^2 = 2e^{-\lambda t} - e^{-2\lambda t}\), its density \(f(t) = 2\lambda e^{-\lambda t}(1 - e^{-\lambda t})\), and so
This is 0 at \(t = 0\) and rises towards \(\lambda\). With \(\lambda = 0.01\) per hour (an MTTF of 100 h for each unit), at 100 h: \(e^{-1} = 0.3679\), so \(h = 0.02 \times 0.6321 / 1.6321 = 0.00775\) per hour.
| \(t\) (h) | 10 | 50 | 100 | 200 | 400 |
|---|---|---|---|---|---|
| \(h(t)\) (per hour) | 0.0017 | 0.0056 | 0.0077 | 0.0093 | 0.0099 |
The pair's hazard rises with time, although neither unit ages. A new pair needs two failures to go down; as time passes, it is ever more likely that one unit has already failed, leaving a single point of failure with hazard \(\lambda\). In fact \(h(t)\) is exactly \(\lambda\) times the probability that a working pair is down to one unit. The pair has lost the exponential's lack of memory: a new pair survives 100 h with probability 0.600, a pair that has already run 100 h only with probability \(R(200)/R(100) = 0.420\).
unit = surv.Exponential.from_params([0.01]) # lambda = 0.01 per hour
pair = NonRepairableRBD(
[("in", "a"), ("in", "b"), ("a", "out"), ("b", "out")],
{"a": unit, "b": unit},
)
lam, t = 0.01, 100
2 * lam * (1 - np.exp(-lam * t)) / (2 - np.exp(-lam * t)) # -> 0.007746 by hand
pair.hf(100) # -> 0.007746
pair.sf(100) # -> 0.6004 a new pair lasts 100 h
pair.sf(200) / pair.sf(100) # -> 0.4203 a pair aged 100 h lasts 100 h more
hf differentiates the exact reliability numerically, so it works for any
diagram, formula or not.
Mean time to failure of a system¶
As for a component in Lesson 1, a system's mean time to failure is the area under its reliability curve:
For the exponential pair, \(\int_0^\infty (2e^{-\lambda t} - e^{-2\lambda t})\,dt = 2/\lambda - 1/(2\lambda) = 1.5/\lambda\). The story of the failures gives the same answer in two stages:
- Until the first failure, the two units act as a series pair, since either failure ends the stage. Its reliability is \(e^{-\lambda t} \cdot e^{-\lambda t} = e^{-2\lambda t}\), an exponential with rate \(2\lambda\), so the stage lasts \(1/(2\lambda)\) on average.
- After it, the survivor is as good as new, because an exponential unit does not age, so it lasts a further \(1/\lambda\) on average.
RePyability computes the MTTF of an RBD the first way: mean_time_to_failure
integrates the system's reliability curve numerically. It can also
simulate it, the second way made random: method="simulate" draws a
lifetime for every block, finds when the last path breaks, and averages
100 000 such system lifetimes. Pass a seed to make the result repeatable:
pair.mean_time_to_failure() # -> 150.0 exact: 1.5 / 0.01
pair.mean_time_to_failure(method="simulate", seed=0) # -> 150.4
ci = pair.mean_time_to_failure_interval(seed=0)
ci.standard_error # -> 0.3544
ci.lower, ci.upper # (149.70, 151.09)
The simulation's 0.4 h gap from 150 is simulation error, of the size you should expect. The two stages are independent exponentials with means 50 and 100 h, so the pair's lifetime has a standard deviation of \(\sqrt{50^2 + 100^2} = 112\) h, and an average of 100 000 lifetimes is off by about \(112/\sqrt{100\,000} = 0.35\) h: the standard error reported. The 95% interval built from it contains the exact 150.
The MTTF undersells redundancy for short missions. The second unit adds only 50% to the mean life, but it multiplies the B10 life, the time by which 10% of systems have failed, by 3.6. A single unit reaches \(R = 0.9\) at \(t = -\ln(0.9)/0.01 = 10.5\) h; the pair reaches it when each unit's unreliability is \(\sqrt{0.1} = 0.316\), at \(-\ln(0.684)/0.01 = 38.0\) h.
single = NonRepairableRBD([("in", "a"), ("a", "out")], {"a": unit})
single.bx_life(10) # -> 10.54 h
pair.bx_life(10) # -> 38.01 h
The cooling circuit's MTTF has no neat formula, but the same tools apply:
circuit.mean_time_to_failure() # -> 93.82 h, the area under R_sys(t)
quad(circuit.sf, 0, np.inf)[0] # -> 93.82 h, the same integral, by scipy
circuit.time_to_reliability(0.9) # -> 38.84 h until R_sys falls to 0.9
circuit.bx_life(10) # -> 38.84 h, the same question
The circuit lasts 94 h on average, yet one in ten has failed by 39 h. When a mission must succeed, ask for the time to a reliability target, not the mean.
How sure are you of the inputs?¶
The circuit's reliability at 50 h is 0.8393, but that number rests on the models: a pump scale of exactly 100 h, a valve shape of exactly 1.5. Models are estimated from data, and a model fitted to twenty failures is not known exactly. So there are two kinds of uncertainty in the answer. The first is aleatory: the variability the models describe. A reliability of 0.84 means that about 16 circuits in 100 fail by 50 h, and nobody can say which. The second is epistemic: not knowing the models exactly, so that the 0.84 itself is uncertain. More circuits do not reduce the first; more data reduces the second.
Suppose the pump's scale \(\alpha\) is known only to lie between 80 and 120 h, equally likely anywhere in that range, and everything else is known. A pump survives 50 h with probability \(e^{-(50/\alpha)^2}\), and the valve with \(e^{-(50/200)^{1.5}} = 0.8825\). At the two ends of the range:
| \(\alpha\) | pump | pair \(1 - (1 - R_p)^2\) | circuit |
|---|---|---|---|
| 80 h | 0.6766 | 0.8954 | 0.7902 |
| 120 h | 0.8406 | 0.9746 | 0.8601 |
The circuit's reliability rises with \(\alpha\), so its percentiles are those of \(\alpha\): the 5th and 95th percentiles of \(\alpha\), 82 and 118 h, give a 90% interval of 0.797 to 0.859.
Both pumps are the same type, so they share the one unknown \(\alpha\): if it is low, both pumps are worse. Treating each pump's \(\alpha\) as a separate unknown would let a bad draw for one pump be offset by a good one for the other, and narrow the interval to 0.811 to 0.855, claiming more certainty than you have.
import scipy.stats as st
alpha = {"alpha": st.uniform(80, 40)} # uniform between 80 and 120 h
same_type = circuit.sf_uncertainty(
50, {("pump1", "pump2"): alpha}, n_draws=10_000, seed=0
)
same_type.nominal # -> 0.8393 with alpha = 100 h
same_type.interval(0.9) # (0.7974, 0.8587)
separately = circuit.sf_uncertainty(
50, {"pump1": alpha, "pump2": alpha}, n_draws=10_000, seed=0
)
separately.interval(0.9) # (0.8111, 0.8553) too narrow
sf_uncertainty draws a plausible model for each uncertain node,
n_draws times, and computes the circuit exactly for each draw. A tuple of
nodes shares one draw. The uncertainty can also come straight from a fit: for
a model fitted in surpyval, "fit" draws its parameters from the fit's own
estimate of how uncertain they are (see the
guide).
Pitfalls¶
An RBD is not a wiring diagram
Draw the blocks from the question "which combinations of working parts let the function succeed?", not by copying the piping or circuit layout.
Reliability belongs to a function
Each function has its own diagram and its own failure modes. If each of two valves in a line fails to open with probability 0.05 and fails to close with probability 0.1, the pair lets the flow through with probability \(0.95^2 = 0.9025\) and stops it with probability \(1 - 0.1^2 = 0.99\).
0.95**2 # -> 0.9025 both must open
1 - 0.1**2 # -> 0.99 one closing is enough
Independence is an assumption
The product rules hold only for independent failures. Redundant units that share a power supply, a load or a manufacturing batch fail together more often than the rules predict. Lesson 5 models this; the guide shows how to draw one component in several places.
Redundancy has running costs
Two pumps fail twice as often as one: redundancy trades system failures for more component failures, each needing a repair. And a failed unit in a parallel group is a hidden failure: the system keeps working, so nothing raises an alarm, yet the group is one failure from down. Lesson 6 brings in repair, and Lesson 8 maintenance on a schedule.
Summary¶
Key ideas
- An RBD models the success logic of one function, not the wiring: the system works while a path of working blocks joins input to output.
- In series, reliabilities multiply, so a chain is weaker than its weakest link. While unreliabilities are small they nearly add: \(Q_{\text{sys}} \approx \sum_i q_i\).
- In parallel, unreliabilities multiply, and each extra unit buys less absolute reliability than the last.
- k-out-of-n works while at least \(k\) of \(n\) units work (a binomial sum);
1-out-of-n is parallel and n-out-of-n is series. In RePyability,
kgoes on the node the branches enter. - With lifetimes, the same diagram turns the curves \(R_i(t)\) into
\(R_{\text{sys}}(t)\), which
sf(t)computes exactly. A redundant pair of units that do not age has a rising hazard. - \(\text{MTTF}_{\text{sys}} = \int_0^\infty R_{\text{sys}}(t)\,dt\), which is \(1.5/\lambda\) for an exponential pair. RePyability estimates it by seeded simulation, with a small error you can quantify.
- The models themselves are uncertain (epistemic uncertainty):
sf_uncertaintycarries that to the system reliability. Units of one type share their uncertainty, and must be drawn together.
Exercises¶
1. A series system has five components, each with reliability 0.98. What is the system reliability? What does the shortcut \(Q_{\text{sys}} \approx \sum_i q_i\) give?
Answer
\(0.98^5 = 0.9039\): about one system in ten fails, although each part fails only one time in fifty. The shortcut gives \(5 \times 0.02 = 0.1\), close to the exact unreliability 0.0961 because the sum is small.
0.98**5 # -> 0.9039
1 - 0.98**5 # -> 0.0961
2. Three identical units in parallel each have reliability 0.8. What is the system reliability, and how much does the third unit add?
Answer
\(1 - 0.2^3 = 0.992\). Two units give \(1 - 0.2^2 = 0.96\), so the third adds 0.032, against 0.16 for the second: diminishing returns.
parallel(3, 0.2).sf() # -> 0.992
parallel(2, 0.2).sf() # -> 0.96
3. A 2-out-of-3 system has units of reliability 0.95. What is its reliability? Compare it with one unit, and with two units in parallel.
Answer
\(3 \times 0.95^2 \times 0.05 + 0.95^3 = 0.135375 + 0.857375 = 0.99275\). That beats a single unit (0.95) but not a parallel pair (\(1 - 0.05^2 = 0.9975\)), because it needs two units working rather than one.
q05 = {u: surv.FixedEventProbability.from_params(0.05) for u in units}
NonRepairableRBD(vote_edges, q05, k={"out": 2}).sf() # -> 0.99275
4. You can make one improvement to the cooling circuit (pumps \(q = 0.1\), valve \(q = 0.05\)): add a third identical pump in parallel, or fit a valve that fails half as often (\(q = 0.025\)). Which helps more, and why?
Answer
The better valve: a third pump gives \((1 - 0.1^3) \times 0.95 = 0.94905\), the better valve \(0.99 \times 0.975 = 0.96525\). The valve, a single point of failure, carries 0.05 of the circuit's unreliability of about 0.06, and the pump pair only 0.01, so even perfect pumps would leave the circuit at 0.95. Lesson 4 turns this reasoning into importance measures.
three_pumps = NonRepairableRBD(
edges + [("in", "pump3"), ("pump3", "valve")],
{**models, "pump3": surv.FixedEventProbability.from_params(0.1)},
)
three_pumps.sf() # -> 0.94905
better_valve = NonRepairableRBD(
edges, {**models, "valve": surv.FixedEventProbability.from_params(0.025)}
)
better_valve.sf() # -> 0.96525
5. Two identical units, each exponential with an MTTF of 1000 h, run in parallel. What is the MTTF of the pair? What would a third unit add?
Answer
With \(\lambda = 1/1000\) per hour, the pair lasts \(1/(2\lambda) + 1/\lambda = 500 + 1000 = 1500\) h. Three units pass through three stages, ending at the first of three failures (rate \(3\lambda\), 333 h on average), the first of the remaining two (500 h) and the last (1000 h): 1833 h in all. The second unit adds 500 h, the third only 333 h; the \(n\)th adds \(\text{MTTF}/n\).
mttf = 1000
mttf / 2 + mttf # -> 1500.0
mttf / 3 + mttf / 2 + mttf # -> 1833.3
u = surv.Exponential.from_params([1 / 1000])
trio = NonRepairableRBD(
[("in", x) for x in "abc"] + [(x, "out") for x in "abc"],
{x: u for x in "abc"},
)
trio.mean_time_to_failure() # -> 1833.3
6. In the circuit, the valve's shape is also uncertain: anywhere between 1.2 and 1.8. With the pumps' scale known (100 h), what is the 90% interval for the circuit's reliability at 50 h? Compare it with the pumps' interval from the lesson, and then make both uncertain at once.
Answer
The valve survives 50 h with probability \(e^{-0.25^{\beta}}\), which rises with \(\beta\): from 0.834 at \(\beta = 1.23\) to 0.918 at 1.77 (the 5th and 95th percentiles). The pair of pumps is fixed at \(1 - (1 - e^{-0.25})^2 = 0.9511\), so the circuit's interval is \(0.9511 \times (0.834, 0.918) = (0.793, 0.873)\): wider than the pumps' (0.797, 0.859), although the valve's shape is known about as well. The valve is in series, so its uncertainty passes straight through, while the pair damps its pumps': across the range of \(\alpha\) one pump's reliability moves by 0.16, the pair's by only 0.08. With both uncertain the interval widens to (0.778, 0.883). Uncertainty, like unreliability, matters most in the single points of failure, which is where more data is worth most.
valve_beta = {"beta": st.uniform(1.2, 0.6)} # uniform between 1.2 and 1.8
circuit.sf_uncertainty(50, {"valve": valve_beta}, n_draws=10_000, seed=0).interval(0.9)
# (0.7930, 0.8727)
circuit.sf_uncertainty(
50, {("pump1", "pump2"): alpha, "valve": valve_beta}, n_draws=10_000, seed=0
).interval(0.9)
# (0.7777, 0.8828)
Where next¶
- Lesson 3 handles systems that are not series-parallel,
such as the bridge, with path sets and cut sets, and shows how
sfevaluates any diagram exactly. - In the user guide: Building an RBD (node models, k-out-of-n nodes, nested diagrams), Reliability of a system (hazard, MTTF intervals, time to a reliability) and Redundancy models (standby, repeated and load-sharing units).