Lesson 6: Repair and availability¶
In this lesson
You will learn:
- why repair changes the question, from "has it failed yet?" to "what fraction of the time does it work, how often does it stop, and for how long?";
- the long-run availability and failure frequency of a repaired component, \(A = \text{MTTF}/(\text{MTTF} + \text{MTTR})\) and \(\omega = 1/(\text{MTTF} + \text{MTTR})\);
- how availability changes after start-up, and how to simulate it and judge the simulation's error;
- how to simulate until an answer is precise enough, and how to compare two designs far more precisely (common random numbers);
- the availability of a system, how often it fails (from Birnbaum importance) and how long its outages last;
- the pitfalls: availability is not reliability, and the long run is not the first few hours.
Before you start: Lessons 1 to 4: lifetimes and the MTTF, series and parallel systems, the exact system probability and Birnbaum importance. About 40 minutes.
The question¶
Your pumping station, the two pumps in parallel feeding a valve of the earlier lessons, runs around the clock. A failed part is not thrown away: a technician repairs it and puts it back into service. So far you have asked about reliability: "has the system failed by time \(t\)?". For this plant the answer is certain. Over any long stretch it fails many times, and each time it comes back.
The useful questions are different. What fraction of the time does the plant deliver? How often does it stop, and when it stops, for how long? Production targets, contracts and staffing rest on these numbers. This lesson answers all three, first for one component and then for the plant.
To keep the arithmetic small, the numbers are illustrative: each pump fails on average every 10 hours and takes 1 hour to repair; the valve fails every 50 hours and takes 2 hours.
One repairable component¶
Up, down, up again¶
Follow one pump. It runs for a while, fails, is repaired, runs again, fails again. Its life is a sequence of cycles, each an up time \(U\) followed by a down time \(D\):
|<------ U1 ------>|<-- D1 -->|<---------- U2 ---------->|<-- D2 -->|<--- U3 ...
up +------------------+ +--------------------------+ +----------
| | | |
down +----------+ +----------+
0 fails repaired fails repaired time
RePyability assumes, and so will you, that:
- each up time is drawn from the pump's lifetime distribution (its reliability model, Lesson 1), whose mean is the MTTF;
- each down time is drawn from its repairability model, the distribution of the time to repair, whose mean is the MTTR (mean time to repair);
- every repair leaves the pump as good as new, and all these times are independent.
After each repair the pump starts afresh, so its future looks like its past. A process like this is called an alternating renewal process.
What fraction of the time is it up?¶
The availability is the fraction of time the component is up. Over a long time \(T\) the pump completes about \(T/(\text{MTTF} + \text{MTTR})\) cycles, since that is the average length of one. Each cycle contributes on average MTTF hours of up time, so the total up time is about \(T \cdot \text{MTTF}/(\text{MTTF} + \text{MTTR})\). Dividing by \(T\):
This is the renewal-reward theorem, stated intuitively: when a process starts afresh after every cycle, the long-run fraction of time it spends in a state is the expected time per cycle in that state divided by the expected length of a cycle. Notice two things. It is a ratio of means, not the average of each cycle's own up fraction: long cycles fill more of the timeline, so they count for more. And only the means matter: two pumps with the same MTTF and MTTR have the same long-run availability, whatever the shapes of their distributions.
How often does it fail?¶
Each cycle contains exactly one failure, and a cycle lasts \(\text{MTTF} + \text{MTTR}\) on average, so the component fails
times per unit time. This is its failure frequency.
For the pump, with MTTF = 10 hours and MTTR = 1 hour:
The pump is up 90.9% of the time and down 9.1%, about 2.2 hours a day (\(0.0909 \times 24 = 2.18\)). It fails about 2.2 times a day, once every 11 hours on average.
Failure frequency is not the failure rate
An exponential lifetime has the constant hazard (failure rate) \(\lambda = 1/\text{MTTF} = 0.1\) per hour (Lesson 1). That counts failures per hour of running. The failure frequency counts them per hour of calendar time, and the pump cannot fail while it is being repaired: \(\omega = A/\text{MTTF}\), here \(0.9091 \times 0.1 = 0.0909\).
In RePyability¶
A RepairableRBD is a block diagram of repairable components. Each
component is a dict holding its "reliability" (time-to-failure) and
"repairability" (time-to-repair) models. In practice both are fitted with
surpyval from failure and repair
records; here a helper builds exponential ones from their rates:
import numpy as np
import surpyval as surv
from repyability import RepairableRBD
def unit(failure_rate, repair_rate):
"""A repairable component with exponential failure and repair times."""
return {
"reliability": surv.Exponential.from_params([failure_rate]),
"repairability": surv.Exponential.from_params([repair_rate]),
}
one_pump = RepairableRBD([("in", "pump"), ("pump", "out")], {"pump": unit(0.1, 1.0)})
one_pump.mean_availability() # -> 0.9091 10 / (10 + 1)
one_pump.system_failure_frequency() # -> 0.09091 failures per hour: 1 / 11
The rates are per hour: a failure rate of 0.1 means an MTTF of 10 hours, and a repair rate of 1.0 an MTTR of 1 hour. Both results are exact long-run values, computed from the formulas above with no simulation.
Availability over time¶
A new component starts up¶
The long-run value describes a pump that has been in service for a long time. A brand-new pump is up at time 0, and for a while it is more likely than usual to be up, because it has not yet had time to fail. So define the point availability \(A(t)\): the probability that the component is up at time \(t\). It starts at \(A(0) = 1\) and settles towards the long-run availability, which is both its limit and the fraction of a long stretch of time spent up.
For an exponential component, with failure rate \(\lambda = 1/\text{MTTF}\) and repair rate \(\mu = 1/\text{MTTR}\), there is an exact formula:
The first term is the long-run availability, since \(\mu/(\lambda + \mu) = \text{MTTF}/(\text{MTTF} + \text{MTTR})\). The second is the start-up transient, which dies away at the rate \(\lambda + \mu\). For the pump, \(A(t) = 0.9091 + 0.0909\,e^{-1.1t}\): after three hours the transient term is down to 0.003.
Simulating the curve¶
For most distributions there is no such formula. RePyability can still
compute the curve without simulation, by solving the renewal equation behind
it numerically (point_availability, below), but a simulation shows where
the curve comes from, and gives more besides. It estimates
\(A(t)\) by Monte Carlo simulation. availability() plays out
mc_samples independent histories of the system from time 0, every component new. For
each component it draws a time to failure, then a time to repair, then
another time to failure, and so on; it merges the components' events in time
order and checks the system after each one. The fraction of histories in
which the system is up at time \(t\) estimates \(A(t)\).
result = one_pump.availability(t_simulation=5.0, mc_samples=10_000, seed=0)
result.availability[0] # -> 1.0 every history starts up
np.interp(1.0, result.timeline, result.availability) # -> 0.942 up at t = 1 h
np.interp(5.0, result.timeline, result.availability) # -> 0.9086 up at t = 5 h
t_simulation is the length of each history. result.timeline holds the
times at which some history changed state, and result.availability the
fraction of histories up from each of those times until the next;
np.interp reads the curve at any time. Here it is every half hour, beside
the exact formula:
hours = np.arange(0.0, 5.5, 0.5)
simulated = np.interp(hours, result.timeline, result.availability)
lam, mu = 0.1, 1.0
exact = mu / (lam + mu) + lam / (lam + mu) * np.exp(-(lam + mu) * hours)
simulated.round(3)
# array([1. , 0.963, 0.942, 0.932, 0.924, 0.918, 0.913, 0.913, 0.914,
# 0.913, 0.909])
exact.round(3)
# array([1. , 0.962, 0.939, 0.927, 0.919, 0.915, 0.912, 0.911, 0.91 ,
# 0.91 , 0.909])
xychart-beta
title "Availability of a new pump (simulated)"
x-axis "hours" ["0", "0.5", "1", "1.5", "2", "2.5", "3", "3.5", "4", "4.5", "5"]
y-axis "A(t)" 0.9 --> 1
line [1, 0.963, 0.942, 0.932, 0.924, 0.918, 0.913, 0.913, 0.914, 0.913, 0.909]
The simulation follows the exact curve down from 1 and levels off near 0.91. It is not perfectly smooth, though: between 3.5 and 4 hours it even rises a little, which the exact curve never does.
point_availability computes the exact curve for any distributions, and
mission_availability its mean over a window, with no simulation:
one_pump.point_availability(hours).round(3)
# array([1. , 0.962, 0.939, 0.927, 0.919, 0.915, 0.912, 0.911, 0.91 ,
# 0.91 , 0.909])
one_pump.mission_availability(5.0) # -> 0.92555 the mean over the first 5 hours
How much to trust a simulation¶
That wobble is sampling error. Each point is a proportion of \(N\)
histories, so its standard error is \(\sqrt{A(1 - A)/N}\): at \(A \approx 0.91\)
and \(N = 10\,000\), about 0.003. A 95% confidence band reaches roughly two
standard errors either side, and availability_interval() computes it at
every point (by the Wilson method, which stays sensible near 0 and 1):
result.availability_se[-1] # -> 0.0029 standard error at t = 5 h
lower, upper = result.availability_interval(confidence=0.95)
lower[-1] # -> 0.9028
upper[-1] # -> 0.9141 the exact A(5) = 0.9095 lies inside
Two arguments govern the error. mc_samples, the number of histories \(N\),
sets its size: the error shrinks like \(1/\sqrt{N}\), so halving it takes four times as many histories. The seed
makes a run reproducible: the same seed gives the same numbers, and another
seed gives different numbers that are equally valid, within the same error.
Precise enough, sooner¶
The band says how precise a run turned out to be. You can instead say how precise it must be, and let the simulation decide how long to run. Suppose you need the pump's mean availability over its first 5 hours, the fraction of those hours it is up, to within ±0.001 at 95% confidence. Each history has its own fraction up. Over 10 000 histories those fractions have a standard deviation of about \(s = 0.14\), so their mean has a standard error of \(s/\sqrt{N}\), and its 95% interval reaches \(1.96\,s/\sqrt{N}\) either side. For ±0.001 you need
tolerance does this for you: it runs mc_samples histories, checks the
interval, and runs another mc_samples until the interval is narrow enough.
(A pump on its own has an exact mean availability, worked out below, and by
default a run takes it, so one to a tolerance stops at once; to watch the
simulation converge, control_variate=False makes it simulate, and keeps
the simulations' own mean.)
precise = one_pump.availability(t_simulation=5.0, mc_samples=10_000, seed=0,
tolerance=0.001, control_variate=False)
precise.n_simulations # -> 80000
window = precise.mean_availability_interval()
window.estimate # -> 0.9259
window.upper - window.estimate # -> 0.00096
The exact answer is the average of the formula for \(A(t)\) over the window,
inside the interval, which runs from 0.9249 to 0.9269. A 95% interval misses the true value about one run in twenty, so another seed may leave it outside: the tolerance bounds the interval's width, not the error of every run.
Two ideas get more precision out of each history, without changing what is estimated.
Comparing designs with the same random numbers. Would a crew that
repairs the pump in half an hour on average, instead of an hour, be worth
it? Simulate each design separately and each estimate carries its own error,
so their difference carries both. Instead, simulate both with the same
random numbers: in each history the pump runs for the same up times in both
designs, and only the repairs differ. The chance in the histories is then
common to both designs and cancels in the difference. This is called
common random numbers, and compare does it (here asked to simulate,
with control_variate=False: for designs this simple it gives the exact
difference, 0.0314, by default):
quick = RepairableRBD([("in", "pump"), ("pump", "out")], {"pump": unit(0.1, 2.0)})
gain = quick.compare(one_pump, t_simulation=5.0, mc_samples=10_000, seed=0,
control_variate=False)
gain.estimate # -> 0.0303 exactly: 0.9569 - 0.9256 = 0.0314
gain.standard_error # -> 0.00061
Two separate runs of 10 000 histories each give the difference with a
standard error of about 0.0017: to match compare they would need about
eight times as many histories.
Antithetic pairs. A history is built from random numbers \(u\) between 0 and 1: a small \(u\) gives a short time, a large one a long time. Run the histories in pairs, the second using \(1 - u\) wherever the first used \(u\): where the first had an early failure, the second has a late one. Both are genuine histories, but they tend to err in opposite directions, so a pair's average is closer to the truth than two unrelated histories' would be:
paired = one_pump.availability(t_simulation=5.0, mc_samples=10_000, seed=0,
antithetic=True, control_variate=False)
paired.mean_availability_interval().standard_error # -> 0.0012 0.0014 without pairs
Here the pairs are worth about 1.4 times as many histories. A system's lifetime, which rises with every component's lifetime, gains more. The user guide's Simulation precision and speed has every option, including running the histories on several processor cores at once.
The availability of a system¶
Now the plant:
flowchart LR
s((in)) --> p1["pump1 (MTTF 10 h, MTTR 1 h)"] & p2["pump2 (MTTF 10 h, MTTR 1 h)"]
p1 & p2 --> v["valve (MTTF 50 h, MTTR 2 h)"] --> t((out))
Each component follows its own up-down cycle, independently of the others. In the long run, at a moment chosen at random, component \(i\) is up with probability \(A_i\), independently of the rest. The plant is up if its working components connect input to output: the question Lessons 2 and 3 answered for reliabilities. So the system's long-run availability is the same system probability, evaluated at the availabilities:
Here \(h(p_1, \dots, p_n)\) is the probability that the system works when each component \(i\) works with probability \(p_i\), independently: \(\prod_i p_i\) in series, \(1 - \prod_i (1 - p_i)\) in parallel, and the exact engine of Lesson 3 in general.
- Each pump: \(A_P = 10/11 = 0.90909\).
- The valve: \(A_V = 50/52 = 0.96154\).
- The pumps in parallel: \(1 - (1/11)^2 = 120/121 = 0.99174\).
- In series with the valve: \(A_{\text{sys}} = 0.99174 \times 0.96154 = 0.9536\).
edges = [("in", "pump1"), ("in", "pump2"),
("pump1", "valve"), ("pump2", "valve"), ("valve", "out")]
plant = RepairableRBD(
edges,
{"pump1": unit(0.1, 1.0), "pump2": unit(0.1, 1.0), "valve": unit(0.02, 0.5)},
)
plant.node_availability()
# {'pump1': 0.9091, 'pump2': 0.9091, 'valve': 0.9615, 'in': 1.0, 'out': 1.0}
plant.mean_availability() # -> 0.9536
plant.mean_unavailability() # -> 0.04641
The plant is up 95.4% of the time and down 4.6%, about 1.1 hours a day. The valve limits it despite its longer MTTF, because it has no backup; the pumps' redundancy turns two 91% components into a 99.2% pair.
How often does the system fail?¶
A failure of a critical component¶
Availability does not say whether the lost 4.6% comes as one long outage a month or as many short ones. For that you need the system's failure frequency, \(\omega_{\text{sys}}\).
The plant goes down at the moment a component fails while it is
critical, that is, while the rest of the plant is in a state where this
component alone decides the outcome (Lesson 4). If pump1
fails while pump2 runs, nothing happens; if it fails while pump2 is under
repair and the valve works, the plant stops. Component \(i\) fails \(\omega_i\)
times per hour. Whether it is critical depends only on the other components,
which are independent of it, so a fraction \(I_B(i)\) of its failures find it
critical, where \(I_B(i)\) is its Birnbaum importance (the probability that it
is critical), evaluated at the availabilities. Adding over the components:
| Component | \(\omega_i\) per hour | Critical when | \(I_B(i)\) | \(I_B(i)\,\omega_i\) |
|---|---|---|---|---|
| pump1 | \(1/11 = 0.0909\) | pump2 down, valve up | \(0.0909 \times 0.9615 = 0.0874\) | 0.00795 |
| pump2 | \(1/11 = 0.0909\) | pump1 down, valve up | \(0.0874\) | 0.00795 |
| valve | \(1/52 = 0.0192\) | at least one pump up | \(1 - 0.0909^2 = 0.9917\) | 0.01907 |
| plant | 0.03497 |
plant.birnbaum_importance() # {'pump1': 0.0874, 'pump2': 0.0874, 'valve': 0.9917}
plant.system_failure_frequency() # -> 0.03497 plant failures per hour
The plant fails 0.035 times an hour, about 0.84 times a day. The last column also says where those failures come from. Each pump fails nearly five times as often as the valve, but a pump failure stops the plant only 8.7% of the time: redundancy absorbs the rest. The valve, critical 99% of the time, causes 55% of the plant's failures (\(0.01907/0.03497\)).
Up time, down time and MTBF¶
The plant's own history alternates between up periods and outages. Over a long time \(T\) it is up for about \(A_{\text{sys}}T\) in total, spread over about \(\omega_{\text{sys}}T\) up periods, and down for \((1 - A_{\text{sys}})T\), spread over as many outages. Dividing:
These are the mean up time, the mean down time (the average outage) and the mean time between failures, from one failure to the next. For the plant, \(\text{MUT} = 0.9536/0.03497 = 27.27\) hours, \(\text{MDT} = 0.04641/0.03497 = 1.327\) hours and \(\text{MTBF} = 28.60\) hours:
plant.mean_up_time() # -> 27.27 hours up after each restoration
plant.mean_down_time() # -> 1.327 hours per outage
plant.mean_time_between_failures() # -> 28.6 1 / 0.03497
You can check the MDT against intuition. An outage caused by the valve lasts about its 2-hour repair. An outage caused by the second pump failing ends as soon as either pump is back, which with exponential repairs takes half an hour on average (the faster of two 1-hour repairs). Weighting by the shares above, \(0.55 \times 2 + 0.45 \times 0.5 \approx 1.3\) hours.
The MTBF is not an MTTF
The MTBF belongs to the repairable system as a whole: 28.6 hours here, neither a pump's MTTF (10 hours) nor the valve's (50 hours). Even for a single component it is \(\text{MTTF} + \text{MTTR}\) (11 hours for the pump), not the MTTF. Nor is it how long the plant would last without repair: from new, with no repairs, it would last \(\int_0^\infty (2e^{-0.12t} - e^{-0.22t})\,dt = 2/0.12 - 1/0.22 = 12.1\) hours on average (Lessons 1 and 2). With repair it runs 27.3 hours between outages, because a failed pump is usually back before its partner fails.
The same numbers by simulation¶
The simulation estimates the same quantities by counting: failure_frequency
is the number of system failures divided by the total simulated time,
mean_up_time the total up time divided by the number of failures, and
mean_down_time the total down time divided by the number of restorations.
sim = plant.availability(t_simulation=100.0, mc_samples=2_000, seed=0)
sim.failure_frequency # -> 0.035605 exact: 0.03497
sim.mean_up_time # -> 26.76 exact: 27.27
sim.mean_down_time # -> 1.344 exact: 1.327
They agree to within sampling error. They also carry a small bias, because every history starts new and its last period is cut short by the end of the window. The exact methods have neither problem, so prefer them for long-run values.
What the simulation adds¶
If the exact methods give the long-run answers, why simulate? For two things they cannot give.
The start-up transient. A new plant is up more often, at first, than in the long run. Over its first 8-hour shift:
first_shift = plant.availability(t_simulation=8.0, mc_samples=10_000, seed=0)
np.interp(1.0, first_shift.timeline, first_shift.availability) # -> 0.9807 at 1 h
np.interp(8.0, first_shift.timeline, first_shift.availability) # -> 0.9539 at 8 h
first_shift.system_uptime / (first_shift.n_simulations * 8.0) # -> 0.964 over the shift
A new plant is up 96.4% of its first shift, against 95.4% in the long run,
and by the end of the shift it has settled; exactly,
plant.mission_availability(8.0) is 0.9640. With slow repairs or wear-out
lifetimes the transient lasts longer, and it can dip below the long-run
value (Exercise 5).
A check by hand
Independence holds at every instant, not only in the long run, so \(A_{\text{sys}}(t) = h(A_1(t), \dots, A_n(t))\). With the exponential formula, at \(t = 1\) hour each pump is at 0.9394 and the valve at \(0.9615 + 0.0385\,e^{-0.52} = 0.9844\), so the plant is at \((1 - 0.0606^2) \times 0.9844 = 0.9808\). The simulation's 0.9807 is within its error.
Criticality from the histories. The simulation records which component's failure took the plant down each time. Two of its measures are the terms of the frequency formula, counted rather than computed:
fci = sim.criticalities.failure_criticality_index
fci.per_system_failure # {'pump1': 0.2188, 'pump2': 0.2213, 'valve': 0.5599}
fci.per_component_failure # {'pump1': 0.0852, 'pump2': 0.0871, 'valve': 0.9918}
per_system_failure is each component's share of the plant's failures, an
estimate of \(I_B(i)\,\omega_i/\omega_{\text{sys}}\) (exactly 0.227 for each
pump and 0.545 for the valve). per_component_failure is the fraction of the
component's own failures that stopped the plant, an estimate of the
probability that a failure finds it critical, \(I_B(i)\) (0.0874 and 0.9917).
The guide covers these and the time-based measures in Criticality
measures.
Instant repair, nested systems and planned outages¶
Instant repair. When a repair takes seconds and your question is about
hours, or when there is no repair-time data, give
"repairability": "instant". The component still fails, and each failure
counts (as a zero-length outage, and in the costs of Lesson 7),
but it is never down: its availability is exactly 1.
quick_valve = RepairableRBD(
edges,
{
"pump1": unit(0.1, 1.0),
"pump2": unit(0.1, 1.0),
"valve": {"reliability": surv.Exponential.from_params([0.02]),
"repairability": "instant"},
},
)
quick_valve.mean_availability() # -> 0.9917 only the pumps take it down
quick_valve.system_failure_frequency() # -> 0.03636 more failures than before
The availability rises from 0.9536 to 0.9917, yet the plant fails more often. The valve, never under repair, now fails every 50 hours instead of every 52, and with the valve always up, each pump is critical whenever its partner is down.
Nested systems. A RepairableRBD can itself be a component of another,
for example the pump pair as one subsystem node; the results are the same.
See Nested repairable RBDs.
Planned outages. Preventive maintenance (Lesson 8)
takes components down on purpose. That downtime counts against availability
and ends up periods, but it is not a failure: system_failure_frequency()
leaves it out, and the simulation counts it separately, in
system_planned_outages. The guide shows how to schedule it in Preventive
maintenance.
Pitfalls¶
Availability is not reliability
A component with MTTF 10 hours and MTTR 6 minutes is 99% available; so is one with MTTF 1000 hours and MTTR 10 hours. The first fails a hundred times as often. For a task that must not be interrupted (a production batch, a flight), what matters is the probability of no failure during it, which is reliability. Availability says only how much of the time you have the system, so report the failure frequency and the MDT beside it.
The long run is not the first few hours
The point availability starts at 1, and the long-run value is a limit.
Over a short window after start-up, or after an overhaul, the average
availability can differ from it: the plant's first shift averaged 0.964,
not 0.9536. Compute the window you care about, with
mission_availability, or simulate it.
Every component has its own repair crew
The model starts each repair the moment the component fails, and runs
it independently of every other repair, as if each component had its
own technician on site. With one shared crew, waiting for spares or
travel time, outages last longer and overlap more, so the model is
optimistic. repair_crews shares a number of crews (see Repair
crews): the simulation follows
the queue, and with exponential lives and repairs the long-run values
stay exact, and the values over time numerical, from a Markov chain of
the queue. The model also lets components keep running, and failing,
while the system is down; in a real plant a pump that stops with the
plant may not.
Simulated numbers carry sampling error
Everything availability() returns is an estimate. Quote it with its
standard error or confidence band, increase mc_samples when the error matters,
and compare with the exact long-run methods where they exist.
Summary¶
Key ideas
- With repair, the questions become how much of the time the system is up (availability), how often it fails (failure frequency), and for how long (mean down time).
- A repaired component is up a fraction \(A = \text{MTTF}/(\text{MTTF} + \text{MTTR})\) of the time in the long run and fails \(\omega = 1/(\text{MTTF} + \text{MTTR})\) times per unit time; only the means matter.
- A system's long-run availability is its system probability evaluated at the components' availabilities.
- A system fails when a component fails while critical: \(\omega_{\text{sys}} = \sum_i I_B(i)\,\omega_i\), and then \(\text{MUT} = A/\omega\), \(\text{MDT} = (1 - A)/\omega\) and \(\text{MTBF} = 1/\omega\).
- The point availability \(A(t)\) starts at 1 and settles to the long-run
value;
point_availabilitycomputes it exactly, and a simulation estimates it, with an error that shrinks like \(1/\sqrt{N}\). tolerancesimulates until an answer is precise enough;comparesimulates two designs with the same random numbers, so the chance in the histories cancels in their difference; antithetic pairs make each history count for more.- High availability can hide frequent failures: report the failure frequency and MDT too.
Exercises¶
1. A compressor has an MTTF of 200 hours and an MTTR of 5 hours. What are its long-run availability and failure frequency? About how many hours a year (8760 hours) is it down, and in how many outages?
Answer
\(A = 200/205 = 0.9756\) and \(\omega = 1/205 = 0.00488\) per hour. In a year it is down \(8760 \times 5/205 = 214\) hours, in about \(8760/205 = 43\) outages of 5 hours on average.
compressor = RepairableRBD([("in", "c"), ("c", "out")], {"c": unit(1 / 200, 1 / 5)})
compressor.mean_availability() # -> 0.9756
compressor.system_failure_frequency() # -> 0.004878 per hour
8760 * compressor.mean_unavailability() # -> 213.7 hours down a year
2. Two such compressors run in parallel: either can carry the load, and each has its own repair crew. Find the availability, failure frequency, MDT and MTBF of the pair.
Answer
The pair is down only when both are: \(A = 1 - (5/205)^2 = 0.999405\). Each compressor is critical when the other is down, which has probability \(I_B = 5/205 = 0.0244\), so \(\omega_{\text{sys}} = 2 \times 0.0244 \times 0.00488 = 0.000238\) per hour: one failure every 4202.5 hours, about every six months, twenty times less often than one compressor. The MDT is \((1 - 0.999405)/0.000238 = 2.5\) hours, half a repair: when the second unit fails, the first is already partway through its repair, and the outage ends when either is back.
pair = RepairableRBD(
[("in", "a"), ("in", "b"), ("a", "out"), ("b", "out")],
{"a": unit(1 / 200, 1 / 5), "b": unit(1 / 200, 1 / 5)},
)
pair.mean_availability() # -> 0.999405
pair.system_failure_frequency() # -> 0.000238
pair.mean_down_time() # -> 2.5
pair.mean_time_between_failures() # -> 4202.5
3. You can halve one repair time in the plant: the valve's (2 hours to 1) or one pump's (1 hour to 30 minutes). Which raises the plant's availability more? Explain with Birnbaum importance.
Answer
The valve's. Its availability rises from \(50/52 = 0.9615\) to \(50/51 = 0.9804\) (up 0.0189), a pump's from \(10/11 = 0.9091\) to \(10/10.5 = 0.9524\) (up 0.0433). The system availability is a straight line in each component's availability, with slope \(I_B(i)\) (Lesson 4), so the plant gains \(0.9917 \times 0.0189 = 0.0187\) from the valve but only \(0.0874 \times 0.0433 = 0.0038\) from the pump: five times less, although the pump's own gain is larger. The valve has no backup; the pump does.
faster_valve = RepairableRBD(
edges, {"pump1": unit(0.1, 1.0), "pump2": unit(0.1, 1.0), "valve": unit(0.02, 1.0)}
)
faster_pump = RepairableRBD(
edges, {"pump1": unit(0.1, 2.0), "pump2": unit(0.1, 1.0), "valve": unit(0.02, 0.5)}
)
faster_valve.mean_availability() # -> 0.9723 from 0.9536
faster_pump.mean_availability() # -> 0.9574
4. In the simulation sim above (100 hours, \(N = 2000\)), the curve ends
at 0.9465, below the exact long-run availability of 0.9536. Is the plant still
settling, is something wrong, or is it noise?
Answer
Noise. The transient decays at the rates \(1.1\) per hour (pumps) and \(0.52\) per hour (valve), so it has long gone by 100 hours, and the true \(A(100)\) is 0.9536. With \(N = 2000\) the standard error is \(\sqrt{0.947 \times 0.053/2000} = 0.0050\): the difference, 0.0071, is about one and a half standard errors, and the 95% band contains 0.9536. The fraction of the whole window the plant was up, which averages over time as well as over histories, is closer: 0.9529.
sim.availability[-1] # -> 0.9465
sim.availability_se[-1] # -> 0.0050
lower, upper = sim.availability_interval(confidence=0.95)
lower[-1] # -> 0.9358
upper[-1] # -> 0.9555
sim.system_uptime / (sim.n_simulations * sim.time_simulated_to) # -> 0.9529
5. A pump that wears out has a Weibull lifetime with shape 3 and an MTTF of 10 hours, and is repaired as before. What is its long-run availability? Simulate it over 40 hours: why does its availability dip below the long-run value around 12 hours?
Answer
The long-run availability is still \(10/11 = 0.9091\): only the means matter. The start-up is different. New pumps that wear out fail at similar ages, most of them between 5 and 15 hours, so around 12 hours an unusually large share is under repair: \(A(12) = 0.891\). The repaired pumps are as good as new, and young pumps that wear out rarely fail, so a few hours later the curve swings above the long-run value (0.919 at 18 hours). The swings fade as the pumps' cycles drift out of step. An exponential pump, whose failures do not depend on age, settles without swinging.
worn = RepairableRBD(
[("in", "pump"), ("pump", "out")],
{"pump": {"reliability": surv.Weibull.from_params([11.2, 3.0]), # MTTF 10.0 h
"repairability": surv.Exponential.from_params([1.0])}},
)
worn.mean_availability() # -> 0.9091
curve = worn.availability(t_simulation=40.0, mc_samples=10_000, seed=0)
np.interp(12.0, curve.timeline, curve.availability) # -> 0.891
np.interp(18.0, curve.timeline, curve.availability) # -> 0.919
6. (a) Compare the pump with itself, one_pump.compare(one_pump, 5.0).
What difference and standard error do you get, and why? What would two
separate runs give? (b) How many histories would it take to know the pump's
mean availability over its first 5 hours to within ±0.0005?
Answer
(a) A difference of exactly 0, with a standard error of 0: with the same random numbers the two (identical) designs have identical histories, so every history's difference is zero. Two separate runs of 10 000 histories would differ by chance, typically by about their combined standard error, \(\sqrt{2} \times 0.0014 = 0.002\).
same = one_pump.compare(one_pump, 5.0, mc_samples=1_000, seed=0)
same.estimate # -> 0.0
same.standard_error # -> 0.0
(b) Halving the tolerance takes four times the histories:
\((1.96 \times 0.14/0.0005)^2 \approx 4 \times 75\,700 \approx 303\,000\).
With tolerance=0.0005 (and mc_samples=10_000) the simulation stops
after 300 000 histories, close to that estimate: it judges the interval
by the spread it has measured, not by the rounded 0.14.
Where next¶
Lesson 7 puts a price on all this: repairs, replacements and
lost production, over the long run and over a finite window. The user guide's
Repairable systems page has every option of
RepairableRBD, including conditioning on components held working or
broken, and Concepts summarises the theory.
To go the other way, from an availability target for this plant to the
MTTF or MTTR each pump and the valve needs, see
Availability allocation.