Skip to content

Result types

AnalysisRoute dataclass

AnalysisRoute(route, reason, nodes=(), engine=None, engine_reason='', twin='')

How one analysis of an RBD is computed.

Attributes:

Name Type Description
route str

"exact", "numerical", "simulated" or "refused" (see repyability.rbd.routes).

reason str

Why. For "refused", the message the method raises.

nodes tuple

The nodes that decide the route, if any: those whose values are numerical or simulated, or that the method refuses.

engine str or None

For a RepairableRBD simulation, the engine engine="auto" runs a long simulation on: "numba" or "python" (see engine_reason). None otherwise.

engine_reason str

Why that engine.

twin str

For a RepairableRBD's simulated availability and cost: the exact twin control_variate=True controls the estimate by (see ControlVariate), as what it leaves out of the system, or why there is none. "" otherwise.

to_dict

to_dict()

The route as plain data, ready for json.dumps: its fields by name, the nodes as a list.

Returns:

Type Description
dict

route, reason, nodes, engine, engine_reason and twin.

AnalysisRoutes

Bases: dict

The routes of a diagram's analyses, as analysis_routes() gives them: a dict of AnalysisRoute by method name, which prints one method a line, name: route -- reason (the reason's first sentence).

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {"c": surv.Exponential.from_params([0.01])},
... )
>>> routes = rbd.analysis_routes()
>>> routes["mean"].route
'numerical'
>>> print(routes)
Hf: exact -- The structure function over the node reliabilities, exactly.
allocate_redundancy: exact -- ...
...
unreliability_interval: simulated -- ...

to_dict

to_dict()

The routes as plain data, ready for json.dumps: each route's to_dict() by method name.

Returns:

Type Description
dict

{method: route.to_dict()}.

AvailabilityResult dataclass

AvailabilityResult(timeline, availability, system_uptime, time_simulated_to, criticalities, node_uptime, node_downtime, system_downtime, system_failures, system_restorations, n_simulations, cost=None, system_planned_outages=0, uptimes=None, antithetic=False, capacity_timeline=None, capacity=None, capacity_time=None, demand=None, delivered=None, opportunistic_renewals=None, control_variate=None, conditional=None, delivered_control=None)

Bases: _ResultMapping

The result of RepairableRBD.availability().

Holds the simulated availability over time, the system's and every node's up and down totals, the system failure and restoration counts, and the criticality measures, all summed over the n_simulations simulations of the window [0, time_simulated_to].

One expected value: mean_availability is the run's estimate of the expected availability over the window, the one mean_availability_interval gives an interval for. By default it is exact where the exact methods work it out (method="exact", see ControlVariate), and otherwise taken given the histories of the system's modules where they apply ("conditional", see ConditionalRun); else it is the simulations' own ("simulated"). The simulations' own average, system_uptime / (n_simulations * time_simulated_to), is sample_mean_availability whatever the method, as the curve, the totals and the counts are theirs. Against a demand, the delivered_fraction is taken the same way (exact where mission_capacity works it out, given the modules' histories, or the simulations' own, sample_delivered_fraction), and delivered_fraction_interval says which. Its repr summarises the run.

The properties mean_up_time, mean_down_time and failure_frequency are simulation estimates derived from the fields below; their exact steady-state counterparts are the RepairableRBD.mean_up_time(), mean_down_time() and system_failure_frequency() methods. availability_se and availability_interval give the sampling uncertainty of the availability curve, and mean_availability_interval that of mean_availability.

Like the other result types it is also a read-only mapping of its fields, so dict-style access (result["availability"], result.keys(), dict(result)) keeps working.

Attributes:

Name Type Description
timeline ndarray

Event times at which the (mean) system availability changes: 0, every time at which some simulated system changed state, and time_simulated_to, in increasing order. With curve_points (see RepairableRBD.availability), the grid's times instead: time_simulated_to * k / curve_points for k from 0.

availability ndarray

Mean system availability at each time in timeline: the fraction of the simulated systems that are up from that time until the next (the estimated point availability). The last value, at time_simulated_to, repeats the one before it. On a grid (curve_points), the fraction up at each grid time (from just after any change there), which the full curve takes there too; it may change between grid times.

system_uptime float

Total system uptime summed over all simulations.

time_simulated_to float

The t_simulation the simulations were run to.

criticalities Criticalities

The importance/criticality measures (see Criticalities).

node_uptime dict

Total uptime per node summed over all simulations.

node_downtime dict

Total downtime per node summed over all simulations; a node's uptime plus downtime equals n_simulations * time_simulated_to.

system_downtime float

Total system downtime summed over all simulations.

system_failures int or float

Number of system failures observed across all simulations (changes from up to down caused by a failure, including the zero-length outages an instantly repaired component causes); with conditional=True, their expected number.

system_restorations int or float

Number of system restorations observed across all simulations (changes from down to up, after a failure or a planned outage); with conditional=True, their expected number.

n_simulations int

The number of simulations run (mc_samples).

cost (CostResult, optional)

The simulated cost distribution, when the RBD declares any costs; None when nothing is priced (no cost model to run).

system_planned_outages int or float

Number of planned outages of the system observed across all simulations: changes from up to down caused by preventive maintenance or an inspection that takes time. 0 without either; with conditional=True, their expected number.

uptimes (ndarray, optional)

The system's up time in each simulation, in order (they sum to system_uptime); None in a result built without them.

antithetic bool

Whether the simulations ran in antithetic pairs (simulations 2i and 2i + 1; see RepairableRBD.availability), by default False. mean_availability_interval then works from the pairs' means; the pointwise availability_se and availability_interval treat the simulations as independent.

capacity_timeline (ndarray, optional)

With capacities (the capacity of the RepairableRBD): 0, each time at which the capacity of some simulated system changed, and time_simulated_to, in increasing order. None without capacities, as are the other capacity fields.

capacity (ndarray, optional)

The simulated systems' mean capacity at each time in capacity_timeline, from that time until the next: the capacity curve. inf while one of them can carry an unlimited amount.

capacity_time (dict, optional)

Capacity -> the time spent at it, summed over the simulations: they add up to n_simulations * time_simulated_to. A node working at several levels counts at each in proportion to its probability.

demand (float, optional)

The demand the delivered fraction is measured against (see RepairableRBD.availability); None without one.

delivered (ndarray, optional)

Each simulation's delivered fraction, in order: the integral of min(capacity, demand) over the window, over demand times its length. None without a demand.

delivered_control (ControlVariate, optional)

With a demand, the control of the delivered fractions by the system itself where mission_capacity works out the expected fraction delivered (as control_variate controls the availability's): delivered_fraction is then that exact value. None otherwise.

opportunistic_renewals (dict, optional)

With maintenance groups (see RepairableRBD): each component's early renewals at another's stop, summed over the simulations. None without groups.

control_variate (ControlVariate, optional)

The exact twin the run was controlled by (see ControlVariate): by default the system itself, where it is its own exact twin, or with control_variate=True the system without what ties its components together. mean_availability_interval is then the controlled estimate's (for the system itself, its exact value), while everything else (uptimes, the curve, the totals) stays the simulations' own. None otherwise.

conditional (ConditionalRun, optional)

For a system with dependent modules (see ConditionalRun): by default (whole), the whole system was simulated, and mean_availability_interval is that of each simulation's expected fraction up given its modules' histories, while everything else stays the simulations' own. With conditional=True, the run simulated only the modules, and its values are each simulation's expected values given their histories: uptimes, the totals and counts (floats), the curve (on a grid, its chance of being up at each time) and the cost's samples; criticalities is then None. None otherwise.

Examples:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {
...         "c": {
...             "reliability": surv.Exponential.from_params([0.1]),
...             "repairability": surv.Exponential.from_params([1.0]),
...         }
...     },
... )
>>> result = rbd.availability(t_simulation=50, mc_samples=200, seed=0)
>>> float(result.availability[0])  # every simulation starts up
1.0
>>> round(result.mean_availability, 4)  # exact; long run: 10 / 11
0.9107
>>> round(result.sample_mean_availability, 4)  # the simulations' own
0.9117
>>> result.mean_availability_interval().method
'exact'
>>> lower, upper = result.availability_interval(0.95)
>>> a = result.availability
>>> bool(((lower <= a) & (a <= upper)).all())
True
>>> result["availability"] is result.availability
True

mean_capacity property

mean_capacity

Simulation estimate of the average capacity over the window: each capacity times the time spent at it, over n_simulations * time_simulated_to. Over a long window it approaches the exact long-run capacity_distribution().mean.

Returns:

Type Description
float or None

The average capacity (inf if some time was spent at an unlimited one), or None without capacities.

delivered_fraction property

delivered_fraction

The run's estimate of the expected fraction of the demand delivered over the window: the production availability, the estimate delivered_fraction_interval gives, exact where mission_capacity works it out, given the modules' histories where those apply, and otherwise the simulations' own, sample_delivered_fraction (as mean_availability is taken, see the class's notes). Over a long window it approaches the exact long-run capacity_distribution().delivered_fraction(demand).

Returns:

Type Description
float or None

The delivered fraction, in [0, 1], or None without a demand.

sample_delivered_fraction property

sample_delivered_fraction

The simulations' own delivered fraction over the window, the mean of delivered, whatever delivered_fraction is.

Returns:

Type Description
float or None

The fraction of the demand the simulated systems delivered, or None without a demand.

mean_availability property

mean_availability

The run's estimate of the expected availability over the window: the estimate mean_availability_interval gives, exact where the exact methods work it out, given the modules' histories where those apply, and otherwise the simulations' own, sample_mean_availability (see the class's notes). The long-run value is RepairableRBD.mean_availability().

Returns:

Type Description
float

The estimated mean availability over the window.

sample_mean_availability property

sample_mean_availability

The simulations' own mean availability over the window, system_uptime / (n_simulations * time_simulated_to), whatever mean_availability is.

Returns:

Type Description
float

The fraction of the simulated time the system was up.

availability_se property

availability_se

Pointwise standard error of the availability estimate.

At each time in timeline the availability is the proportion of the n_simulations systems that were up, so its sampling standard error is the binomial sqrt(A (1 - A) / n). It is 0 where the estimate is 0 or 1; availability_interval stays informative there. It treats the simulations as independent, which antithetic ones are not. For a run of the modules alone (conditional=True, see conditional), each simulation's value is its chance of being up then, and the error is that of their mean, from their mean square.

Returns:

Type Description
ndarray

The standard error at each time in timeline.

mean_up_time property

mean_up_time

Simulation estimate of the Mean Up Time, system uptime / (system failures + planned outages): an up period ends at either. Infinite if the system was never observed to go down (0.0 if it was never up).

Note: estimated from a finite window, so each simulation's final (unfinished) up period is censored; for windows that are short relative to the up-down cycle this biases the estimate. Prefer the exact RepairableRBD.mean_up_time() for steady-state values.

Returns:

Type Description
float

The estimated mean up time, possibly inf.

mean_down_time property

mean_down_time

Simulation estimate of the Mean Down Time, system downtime / system restorations. Infinite if downtime was observed but never restored (0.0 if the system was never down).

Note: estimated from a finite window (final unfinished down periods are censored), so short windows bias the estimate. Prefer the exact RepairableRBD.mean_down_time() for steady-state values.

Returns:

Type Description
float

The estimated mean down time, possibly inf.

failure_frequency property

failure_frequency

Simulation estimate of the system failure frequency (failures per unit time), system failures / total simulated time. Planned outages are not failures.

The total simulated time is n_simulations * time_simulated_to. The exact steady-state counterpart is RepairableRBD.system_failure_frequency(); over a short window the estimate is biased, as every simulation starts with its components working.

Returns:

Type Description
float

The estimated number of system failures per unit time.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

delivered_fraction_interval

delivered_fraction_interval(confidence=0.95)

Confidence interval for the expected delivered fraction over the window.

Around delivered_fraction, as mean_availability_interval is around the availability: an exact one has no error, and the interval is that value; otherwise it is the mean of the simulations' delivered fractions (the conditional ones, given the modules' histories, where those apply), with standard error std / sqrt(n) (of antithetic pairs' means, for an antithetic run), and the interval, clipped to [0, 1], describes the simulation error. Its method says how the estimate was found: "exact" (mission_capacity works it out, see delivered_control), "conditional" (see ConditionalRun) or "simulated", the simulations' own.

Parameters:

Name Type Description Default
confidence float

The confidence level, strictly between 0 and 1, by default 0.95.

0.95

Returns:

Type Description
ConfidenceInterval

The estimate, bounds, standard error, number of simulations and method.

Raises:

Type Description
ValueError

If confidence is not in (0, 1), or the result has no delivered fractions (no capacities, or no demand).

mean_availability_interval

mean_availability_interval(confidence=0.95)

Confidence interval for the expected availability over the window.

Around mean_availability: an exact one has no error, and the interval is that value; otherwise it is the mean of the simulations' values (their fractions of the window up, uptimes / time_simulated_to, controlled or conditional ones), which by the central limit theorem is normal with standard error std / sqrt(n) of those values (of antithetic pairs' means, for an antithetic run), from which the interval estimate +/- z * standard_error is built, clipped to [0, 1]. It describes the simulation error, and narrows like 1 / sqrt(n); availability(tolerance=...) runs until it is narrow enough. Its method says how the estimate was found: "exact", by the exact methods (a control_variate that is the system itself, see ControlVariate); "control_variate", the simulations' fractions controlled by an exact twin; "conditional", each simulation's expected fraction up given its modules' histories (see ConditionalRun); "simulated", the simulations' own.

Parameters:

Name Type Description Default
confidence float

The confidence level, strictly between 0 and 1, by default 0.95.

0.95

Returns:

Type Description
ConfidenceInterval

The estimate, bounds, standard error, number of simulations and method.

Raises:

Type Description
ValueError

If confidence is not in (0, 1), or the result has no per-simulation up times (one built by hand without them).

availability_interval

availability_interval(confidence=0.95)

Pointwise confidence band for the availability curve.

Uses the Wilson score interval for a binomial proportion, which remains well-behaved when the estimated availability is at or near 0 or 1 (where the plain normal interval collapses to zero width). For a run of the modules alone (conditional=True, see conditional), whose simulations' values are chances rather than 0 or 1, the normal interval of their mean (availability_se).

Parameters:

Name Type Description Default
confidence float

The confidence level, strictly between 0 and 1, by default 0.95.

0.95

Returns:

Type Description
tuple[ndarray, ndarray]

The lower and upper bounds at each time in timeline, within [0, 1].

Raises:

Type Description
ValueError

If confidence is not strictly between 0 and 1.

CapacityDistribution dataclass

CapacityDistribution(levels, probabilities)

Bases: _ResultMapping

The exact distribution of a system's capacity: how much it can deliver.

Returned by NonRepairableRBD.capacity_distribution (at a time, or at each of several), RepairableRBD.capacity_distribution (in the long run) and RBD.system_capacity. Each component carries its capacity while it works and nothing once it has failed, and the system's capacity is the most that can flow through the diagram from the input to the output. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
levels ndarray

The capacities the system can have, in increasing order: 0 when it is down, and each total its working components can carry. inf when components with no capacity given can join the input to the output on their own.

probabilities ndarray

The probability of each level: one per level, or for several times one row per level and one column per time. They sum to 1. In the long run, the fraction of time the system spends at each level.

Examples:

Three pumps of 50 units each, in parallel, each available 90% of the time:

>>> from repyability import RBD
>>> pumps = RBD(
...     [("s", "a"), ("s", "b"), ("s", "c"),
...      ("a", "t"), ("b", "t"), ("c", "t")],
...     capacity={"a": 50, "b": 50, "c": 50},
... )
>>> capacity = pumps.system_capacity({"a": 0.9, "b": 0.9, "c": 0.9})
>>> capacity.levels.tolist()
[0.0, 50.0, 100.0, 150.0]
>>> capacity.probabilities.round(4).tolist()
[0.001, 0.027, 0.243, 0.729]

Two of the three meet a demand of 100:

>>> round(capacity.meets(100), 4)
0.972
>>> round(capacity.mean, 4)
135.0
>>> round(capacity.delivered_fraction(100), 4)
0.9855

mean property

mean

The expected capacity.

In the long run, the average capacity over time. Infinite if the capacity can be infinite (see levels). A property, as the other results' values are (a method until 0.13, #235).

Returns:

Type Description
float or ndarray

The expected capacity, per time for several times.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

meets

meets(demand)

The probability that the capacity meets a demand: that it is at least demand.

For a non-repairable system at a time, it is the system's reliability for that demand; in the long run, the fraction of time the system can meet it. With every component's capacity positive, meets of any demand above 0 but no more than the smallest level above 0 is the system's reliability (or availability): the capacity is positive exactly when the system works.

Parameters:

Name Type Description Default
demand float

The demand, in the capacities' units. A capacity within rounding of it (a relative 1e-9) meets it.

required

Returns:

Type Description
float or ndarray

The probability, per time for several times.

Raises:

Type Description
ValueError

If demand is NaN.

delivered_fraction

delivered_fraction(demand)

The expected fraction of a demand the system delivers: E[min(capacity, demand)] / demand.

A system with more capacity than the demand delivers the demand, and one with less delivers what it can. In the long run, this is the fraction of the demand met over time: the production availability.

Parameters:

Name Type Description Default
demand float

The demand, a positive, finite number in the capacities' units.

required

Returns:

Type Description
float or ndarray

The fraction, in [0, 1], per time for several times.

Raises:

Type Description
ValueError

If demand is not a positive, finite number.

Criticalities dataclass

Criticalities(operational_criticality_index, iou, failure_criticality_index, restoration_criticality_index)

Bases: _ResultMapping

The importance/criticality measures from an availability simulation.

RepairableRBD.availability attributes the system's behaviour to its nodes in four ways, each keyed by node name. Every measure is a ratio of totals summed over all the simulations, and is 0 where its denominator is 0 (e.g. the down-time measures when the system was never down).

  • Operational criticality index: up[i] is the time node i and the system were both up over the system's total up time (the share of the system's up time during which node i was up); down[i] is the time both were down over the system's total down time.
  • Intersection over union: up[i] is the time both were up over the time either was up; down[i] is the time both were down over the time either was down. It is 1 when node i's up (or down) periods coincide with the system's.
  • Failure criticality index: how often node i's failures took the system down, per system failure and per failure of node i (see FailureCriticalityIndex).
  • Restoration criticality index: how often node i's restorations brought the system back, per system restoration and per restoration of node i (see RestorationCriticalityIndex).

The first two have an entry for every node; the last two only for the nodes that failed, or were restored, at least once.

Attributes:

Name Type Description
operational_criticality_index UpDownImportance

Time the system and a node were jointly up/down, over the system's up/down time.

iou UpDownImportance

Intersection-over-union of a node's and the system's up/down intervals.

failure_criticality_index FailureCriticalityIndex

How much each node drives system failures.

restoration_criticality_index RestorationCriticalityIndex

How much each node drives system restorations.

Examples:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> unit = {
...     "reliability": surv.Exponential.from_params([0.1]),
...     "repairability": surv.Exponential.from_params([1.0]),
... }
>>> rbd = RepairableRBD(
...     [("s", "a"), ("a", "b"), ("b", "t")], {"a": unit, "b": unit}
... )
>>> result = rbd.availability(t_simulation=50, mc_samples=200, seed=0)
>>> crit = result.criticalities

In series the system is up only while every node is up, but each node is down for only about half of the system's down time:

>>> oci = crit.operational_criticality_index
>>> {node: round(float(v), 4) for node, v in oci.up.items()}
{'a': 1.0, 'b': 1.0}
>>> {node: round(float(v), 2) for node, v in oci.down.items()}
{'a': 0.5, 'b': 0.55}
>>> crit["iou"] is crit.iou  # dict-style access also works
True

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

UpDownImportance dataclass

UpDownImportance(up, down)

Bases: _ResultMapping

An importance measure split by system state.

Holds a per-node measure computed twice: once from the system's and the nodes' up (working) time, and once from their down (failed) time. Used for the operational criticality index and the intersection-over-union measures of Criticalities, which defines them.

Attributes:

Name Type Description
up dict

Node -> the measure computed from up time.

down dict

Node -> the measure computed from down time.

Examples:

A parallel pair is down only while both nodes are, so the share of the system's down time during which each node is down (the down-time operational criticality index) is 1:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> unit = {
...     "reliability": surv.Exponential.from_params([0.1]),
...     "repairability": surv.Exponential.from_params([1.0]),
... }
>>> rbd = RepairableRBD(
...     [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")],
...     {"a": unit, "b": unit},
... )
>>> result = rbd.availability(t_simulation=50, mc_samples=200, seed=0)
>>> oci = result.criticalities.operational_criticality_index
>>> {node: round(float(v), 4) for node, v in oci.down.items()}
{'a': 1.0, 'b': 1.0}
>>> oci["down"] is oci.down
True

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

FailureCriticalityIndex dataclass

FailureCriticalityIndex(per_system_failure, per_component_failure)

Bases: _ResultMapping

Fractions relating a node's failures to system failures.

A node causes a system failure when its own failure takes the system from up to down. The counts are summed over all the simulations of RepairableRBD.availability, and only nodes that failed at least once appear (so not a node held working or broken).

Attributes:

Name Type Description
per_system_failure dict

For each node, the fraction of system failures that the node caused. These sum to 1 over the nodes, or are all 0 if the system never failed.

per_component_failure dict

For each node, the fraction of the node's own failures that caused a system failure.

Examples:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> unit = {
...     "reliability": surv.Exponential.from_params([0.1]),
...     "repairability": surv.Exponential.from_params([1.0]),
... }
>>> rbd = RepairableRBD(
...     [("s", "a"), ("a", "b"), ("b", "t")], {"a": unit, "b": unit}
... )
>>> result = rbd.availability(t_simulation=50, mc_samples=200, seed=0)
>>> fci = result.criticalities.failure_criticality_index
>>> round(sum(fci.per_system_failure.values()), 4)
1.0

In series a node's failure brings the system down unless the other node is already down for repair:

>>> share = fci.per_component_failure
>>> {node: round(share[node], 2) for node in sorted(share)}
{'a': 0.92, 'b': 0.89}

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

RestorationCriticalityIndex dataclass

RestorationCriticalityIndex(by_system, by_component)

Bases: _ResultMapping

Fractions relating a node's restorations to system restorations.

A node causes a system restoration when its own restoration takes the system from down to up. The counts are summed over all the simulations of RepairableRBD.availability, and only nodes restored at least once appear (so not a node held working or broken).

Attributes:

Name Type Description
by_system dict

For each node, the fraction of system restorations that the node caused. These sum to 1 over the nodes, or are all 0 if the system was never restored.

by_component dict

For each node, the fraction of the node's own restorations that restored the system.

Examples:

In a parallel pair the system comes back as soon as either node is repaired, so each node restores it about half the time:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> unit = {
...     "reliability": surv.Exponential.from_params([0.1]),
...     "repairability": surv.Exponential.from_params([1.0]),
... }
>>> rbd = RepairableRBD(
...     [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")],
...     {"a": unit, "b": unit},
... )
>>> result = rbd.availability(t_simulation=50, mc_samples=200, seed=0)
>>> share = result.criticalities.restoration_criticality_index.by_system
>>> {node: round(share[node], 1) for node in sorted(share)}
{'a': 0.5, 'b': 0.5}

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

CostResult dataclass

CostResult(samples, t_simulation, n_simulations, by_category, by_component, acquisition_cost=0.0, antithetic=False, control_variate=None, conditional=None)

Bases: _ResultMapping

The simulated cost of running the system over a window.

Returned by RepairableRBD.cost and as the cost of the AvailabilityResult of a priced RBD. Each of the n_simulations replications of the availability simulation yields one total cost over the window: repair/replace costs charged at each component failure, plus the cost of each preventive replacement, plus any per-component downtime cost, plus the system-downtime cost. samples is that distribution — the point of simulating rather than stopping at the exact RepairableRBD.expected_cost_rate() is the spread: percentile(90) answers "what could a bad window cost", which a mean cannot.

One expected value: mean is the run's estimate of the expected cost of a window, the one mean_interval gives an interval for, and cost_rate and the breakdowns follow it. By default it is exact where the exact methods work it out (method="exact", see ControlVariate), and otherwise taken given the histories of the system's modules where they apply ("conditional", see ConditionalRun); else it is the simulations' own ("simulated"). The simulations' own mean, the average of samples, is sample_mean whatever the method: with control_variate=False and conditional=False the two are the same.

Two different uncertainties live here. std and percentile describe how much the cost of a window varies; that is a property of the system and does not shrink with more replications. mean_se and mean_interval describe how precisely the expected cost has been estimated: 0 for an exact one, and otherwise a standard error that shrinks like 1 / sqrt(n_simulations).

Its repr summarises the run: the estimate, its standard error and method, the simulations' own mean and spread.

Attributes:

Name Type Description
samples ndarray

Total cost of each replication over the window (length n_simulations).

t_simulation float

The window length each replication was run for.

n_simulations int

The number of replications.

by_category dict

The expected cost of a window split into "repair" and "replace" (both charged per failure; for a hidden failure, when an inspection finds it), "preventive" (charged per preventive replacement), "inspection" (charged per inspection), "component_downtime", "system_downtime" and "setup" (a maintenance group's set-up cost, charged once per stop), found as mean is: exact for an exact mean, given the modules' histories for a conditional one, and otherwise the simulations' own means. The seven sum to mean, but for a run controlled by a twin that is not the system itself (control_variate=True), whose breakdowns are the simulations' own and sum to sample_mean.

by_component dict

The expected cost attributable to each costed component (its repair, replace, preventive, inspection and own downtime cost; the system-downtime cost is not attributed to components), found as by_category is.

acquisition_cost float

The one-off cost of buying the components (the sum of their "acquisition_cost"), 0.0 if none is given. It is not a running cost, so it is not in samples or mean: the cost of owning the system for one window from new is acquisition_cost + mean.

antithetic bool

Whether the replications ran in antithetic pairs (replications 2i and 2i + 1; see RepairableRBD.availability), by default False. Each sample is still a correct draw of a window's cost, but only the pairs are independent, so mean_se, sample_se and mean_interval are worked out from the pairs' means.

control_variate (ControlVariate, optional)

The exact twin the run was controlled by (see ControlVariate): by default the system itself, where the exact methods work out its expected cost, whose mean is then that cost; with control_variate=True, the system without what ties its components together, whose mean is the controlled estimate. samples and sample_mean stay the simulations' own. None otherwise.

conditional (ConditionalRun, optional)

For a system with dependent modules (see ConditionalRun): by default (whole), mean is the mean of each simulation's expected cost given its modules' histories, while samples and sample_mean stay the simulations' own. With conditional=True, samples are those expected costs, so mean is sample_mean, and std and percentile refuse. None otherwise.

Examples:

One component with MTTF 10 and MTTR 1, at 100 per repair, and 50 per hour of system downtime, over windows of 100 hours:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {
...         "c": {
...             "reliability": surv.Exponential.from_params([0.1]),
...             "repairability": surv.Exponential.from_params([1.0]),
...             "repair_cost": 100.0,
...         }
...     },
...     downtime_cost_rate=50.0,
... )
>>> result = rbd.cost(t_simulation=100.0, mc_samples=200, seed=0)

One component's expected cost over a window is exact, so by default mean is that cost, with no error, and the breakdowns split it; the simulations' own mean and spread are beside it:

>>> round(result.mean, 2), result.mean_se
(1360.33, 0.0)
>>> round(result.by_category["repair"], 2)  # 100 per failure
909.92
>>> round(result.by_category["system_downtime"], 2)  # 50 per hour down
450.41
>>> round(result.sample_mean, 2), round(result.std, 2)
(1387.42, 436.54)
>>> result.mean_interval(0.95).method
'exact'

The simulations' own estimate, and its interval, are a control_variate=False away:

>>> own = rbd.cost(
...     t_simulation=100.0, mc_samples=200, seed=0, control_variate=False
... )
>>> round(own.mean, 2), round(own.by_category["repair"], 2)
(1387.42, 927.0)
>>> interval = own.mean_interval(0.95)
>>> interval.method, round(interval.lower, 2), round(interval.upper, 2)
('simulated', 1326.92, 1447.92)

mean property

mean

The run's estimate of the expected cost of a window: the estimate mean_interval gives, exact where the exact methods work it out, given the modules' histories where those apply, and otherwise the simulations' own, sample_mean (see the class's notes and mean_interval().method).

Returns:

Type Description
float

The estimated expected cost over the window.

sample_mean property

sample_mean

The simulations' own mean cost over the window: the average of samples, whatever mean is.

Returns:

Type Description
float

The mean of samples.

std property

std

Sample standard deviation of the window's total cost.

How much the cost of one window varies from window to window (with ddof=1, so it is nan, with a numpy warning, for a single replication).

Returns:

Type Description
float

The standard deviation of samples.

Raises:

Type Description
ValueError

For a conditional run (see conditional).

mean_se property

mean_se

The standard error of mean: how far it is likely to be from the true expected cost. 0.0 for an exact mean; otherwise that of the values it is the mean of, std / sqrt(n_simulations) of the simulations' own (sample_se), controlled or conditional ones; for an antithetic run, of the pairs' means. nan where it cannot be known (fewer than two simulations, or a conditional run whose modules never changed state).

Returns:

Type Description
float

The standard error of the mean.

sample_se property

sample_se

The standard error of sample_mean, std / sqrt( n_simulations) (for an antithetic run, of the pairs' means; nan for fewer than two).

Returns:

Type Description
float

The standard error of the simulations' own mean.

cost_rate property

cost_rate

The expected cost per unit time over the window, mean / t_simulation; it converges to the exact RepairableRBD.expected_cost_rate() as the window grows.

Over a short window it differs from the long-run rate because every simulation starts with all components working.

Returns:

Type Description
float

The mean cost per unit time over the window.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

mean_interval

mean_interval(confidence=0.95)

Confidence interval for the expected total cost over the window.

Around mean, with standard error mean_se: by the central limit theorem a mean of simulations' values is normal, so the interval is mean +/- z * mean_se for the normal quantile z of confidence, its lower bound clipped at 0; an exact mean has no error, and the interval is that value. Use it to judge whether mc_samples was large enough; for the range a single window's cost could fall in, use percentile instead. Its method says how mean was found: "exact", by the exact methods (a control_variate that is the system itself, see ControlVariate); "control_variate", the simulations' values controlled by an exact twin; "conditional", each simulation's expected cost given its modules' histories (see ConditionalRun); "simulated", the simulations' own.

Parameters:

Name Type Description Default
confidence float

The confidence level, strictly between 0 and 1, by default 0.95.

0.95

Returns:

Type Description
ConfidenceInterval

The estimate (mean), bounds, standard error, sample count and method.

Raises:

Type Description
ValueError

If confidence is not strictly between 0 and 1.

percentile

percentile(q)

The q-th percentile of the window's total cost (e.g. percentile(90) for a planning-case budget).

Computed from samples with numpy.percentile (linear interpolation between samples).

Parameters:

Name Type Description Default
q float

The percentile, between 0 and 100: q strictly between 0 and 1 warns, as it is more likely a fraction meant.

required

Returns:

Type Description
float

The cost below which about q percent of the simulated windows fall.

Raises:

Type Description
ValueError

If q is outside [0, 100], or for a conditional run (see conditional).

ExpectedEvents dataclass

ExpectedEvents(window, system_failures, system_planned_outages, system_downtime, node_failures, node_corrective, node_preventive, node_inspections, node_downtime)

Bases: _ResultMapping

What a system is expected to do over a window from new: returned by RepairableRBD.expected_events.

Every value is exact (numerical, with no simulation): the mean of what RepairableRBD.availability counts in each simulation of the window, and divides by n_simulations. Events at the window's end itself fall outside it, as in the simulation. Each value is a float for one window, or an array in the shape of the windows given.

Attributes:

Name Type Description
window float or ndarray

The window's length, from new.

system_failures float or ndarray

The expected number of system failures in the window (changes from up to down caused by a failure, including the zero-length outages an instantly repaired component causes).

system_planned_outages float or ndarray

The expected number of planned outages of the system: changes from up to down caused by preventive maintenance that takes time.

system_downtime float or ndarray

The expected time the system is down in the window, window * (1 - mission_availability(window)).

node_failures dict

Each component's expected number of failures (a nested RBD's: its system failures).

node_corrective dict

Each component's expected number of corrective actions, at each of which repair_cost and replace_cost are charged: its failures, or for hidden failures, those found by a test in the window (a nested RBD's: its system failures).

node_preventive dict

Each component's expected number of preventive replacements (age or block), at each of which its preventive cost is charged (a nested RBD's: its planned outages, as the simulation counts them for the node; its own components' maintenance is counted by its own expected_events).

node_inspections dict

Each component's expected number of tests (of hidden failures).

node_downtime dict

Each component's expected time down in the window.

Examples:

One component with failure rate 0.1 and repair rate 1, over 10 time units, fails 0.1 * (10 / 1.1 + 0.1 / 1.1 ** 2 * (1 - exp(-11))) times on average:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {
...         "c": {
...             "reliability": surv.Exponential.from_params([0.1]),
...             "repairability": surv.Exponential.from_params([1.0]),
...         }
...     },
... )
>>> window = rbd.expected_events(10.0)
>>> round(window.system_failures, 4)
0.9174
>>> round(window.node_downtime["c"], 3)
0.826

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

RateBreakdown dataclass

RateBreakdown(x, rate, node_rate, jump_times, jumps, node_jumps)

Bases: _ResultMapping

How fast a system's availability (or reliability) is changing at each time, and each node's part in it: returned by RepairableRBD.availability_rate and NonRepairableRBD.reliability_rate.

With independent nodes the system's value is multilinear in theirs, so its rate is the sum, over the nodes, of each one's Birnbaum importance times its own rate: rate is sum(node_rate.values()), and a node's part says how much it is pulling the system down (negative) or up then. Where a scheduled event makes a node's availability jump (a block replacement or test that takes it off line), the system's may jump too: jumps holds the system's jumps, split among the nodes in node_jumps, whose parts add up to them.

Attributes:

Name Type Description
x float or ndarray

The times.

rate float or ndarray

The system's rate of change at each time, per unit time: after anything that happens at it.

node_rate dict

Each node's part in the rate.

jump_times ndarray

The times, after 0 and up to the last of x, at which a node's value jumps (and so the system's may).

jumps ndarray

The system's jump at each.

node_jumps dict

Each node's part in the jumps.

Examples:

One unit, failing at rate 0.1 and repaired at rate 1, from new: its availability A(t) = (1 + 0.1 exp(-1.1 t)) / 1.1 falls at 0.1 exp(-1.1 t):

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {
...         "c": {
...             "reliability": surv.Exponential.from_params([0.1]),
...             "repairability": surv.Exponential.from_params([1.0]),
...         }
...     },
... )
>>> round(rbd.availability_rate(1.0).rate, 5)  # -0.1 * exp(-1.1)
-0.03329

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

ExpectedCost dataclass

ExpectedCost(window, mean, by_category, by_component, acquisition_cost=0.0, discount_rate=0.0)

Bases: _ResultMapping

The expected cost of running a system over a window from new: returned by RepairableRBD.expected_cost.

Exact (numerical, with no simulation): the mean that RepairableRBD.cost estimates, by the same categories, from each category's expected events (see ExpectedEvents) times its mean cost, and the expected downtimes times their rates. Each value is a float for one window, or an array in the shape of the windows given.

Attributes:

Name Type Description
window float or ndarray

The window's length, from new.

mean float or ndarray

The expected running cost of the window: the sum of by_category.

by_category dict

The expected cost of "repair" and "replace" (per corrective action), "preventive" (per preventive replacement), "inspection" (per test), "component_downtime", "system_downtime" and "setup" (once per stop of a maintenance group), as in CostResult.

by_component dict

The expected cost attributable to each costed component (its repair, replace, preventive, inspection and own downtime cost).

acquisition_cost float

The one-off cost of buying the components, not in mean.

discount_rate float

The continuous rate the costs were discounted at: with one above 0, every value but acquisition_cost (paid at the start) is a present value. By default 0.

Examples:

One component with MTTF 10 and MTTR 1, at 100 per repair, and 50 per unit time of system downtime, over 100 time units:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {
...         "c": {
...             "reliability": surv.Exponential.from_params([0.1]),
...             "repairability": surv.Exponential.from_params([1.0]),
...             "repair_cost": 100.0,
...         }
...     },
...     downtime_cost_rate=50.0,
... )
>>> cost = rbd.expected_cost(100.0)
>>> round(cost.mean, 2)  # cost() simulates about this, with its spread
1360.33
>>> round(cost.by_category["repair"], 2)  # 100 per failure
909.92
>>> round(cost.by_category["system_downtime"], 2)  # 50 per unit down
450.41
>>> round(cost.cost_rate, 2), round(rbd.expected_cost_rate(), 2)
(13.6, 13.64)

total property

total

The cost of owning the system for the window from new: acquisition_cost + mean.

Returns:

Type Description
float or ndarray

The total cost for each window.

cost_rate property

cost_rate

The mean cost per unit time over the window, mean / window (nan for a window of 0); it approaches RepairableRBD.expected_cost_rate() as the window grows (but for a discounted cost, whose rate falls away).

Returns:

Type Description
float or ndarray

The cost rate for each window.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

ConfidenceInterval dataclass

ConfidenceInterval(estimate, lower, upper, confidence, standard_error, n_samples, method=None)

Bases: _ResultMapping

A Monte-Carlo estimate with its sampling uncertainty.

Returned by NonRepairableRBD.mean_time_to_failure_interval and CostResult.mean_interval. The estimate is a sample mean, which by the central limit theorem is approximately normal, so the interval is estimate +/- z * standard_error for the normal quantile z of the confidence level; both methods clip the lower bound at 0, as the MTTF and costs they estimate are non-negative. It describes how precisely the mean has been estimated, and narrows like 1 / sqrt(n_samples). Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
estimate float

The point estimate (the sample mean).

lower float

Lower bound of the confidence interval.

upper float

Upper bound of the confidence interval.

confidence float

The confidence level the bounds correspond to (e.g. 0.95).

standard_error float

The standard error of the estimate.

n_samples int

The number of Monte-Carlo samples the estimate was computed from.

method (str, optional)

How the estimate was found. For a RepairableRBD run's mean (mean_availability_interval, the cost's mean_interval): "simulated", the mean of the simulations' own values; "control_variate", of their values controlled by an exact twin (see ControlVariate); "exact", a run controlled by an exact twin that is the system itself, whose estimate is then its exact value; "conditional", the mean of each simulation's expected values given its modules' histories (see ConditionalRun). Where the method chooses how to simulate (NonRepairableRBD.unreliability_interval), the way it chose; "simulated" for the other simulated intervals (mean_time_to_failure_interval, PhasedMission.reliability_interval), and "exact" where compare works the difference out exactly.

Examples:

The MTTF of a single exponential component with mean 10, from 10,000 simulated lifetimes:

>>> import surpyval as surv
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
...     [("s", "c"), ("c", "t")],
...     {"c": surv.Exponential.from_params([0.1])},
... )
>>> interval = rbd.mean_time_to_failure_interval(mc_samples=10_000, seed=0)
>>> round(interval.estimate, 2), round(interval.standard_error, 2)
(9.91, 0.1)
>>> round(interval.lower, 2), round(interval.upper, 2)
(9.71, 10.1)
>>> interval["confidence"], interval.n_samples
(0.95, 10000)

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

ControlVariate dataclass

ControlVariate(twin, exact, coefficient, correlation, itself=False)

Bases: _ResultMapping

The exact twin a simulation run was controlled by, with RepairableRBD.availability's or cost's control_variate; by default, the system itself where the exact methods take it (see itself).

The twin is the system with its components failing and repaired independently: the same diagram, components and models, without what ties them together (a limit on repair crews, maintenance groups) or what the exact methods over time do not take (a standby group's switching, its units then operating together; imperfect repair; replacement on condition; inspections they do not take), so that its expected values over the window are exact. It is simulated alongside the system with common random numbers (each of its streams draws what the system's stream of the same name draws), so its values move with the system's, and its error against its exact value shows how far the system's own mean is off. Each simulation's controlled value is x - coefficient * (twin - exact): their mean estimates the system's mean without bias (but for the coefficient's coming from the same run, an error of order 1 / n), with 1 - correlation**2 times the variance of the plain mean.

Attributes:

Name Type Description
twin ndarray

The twin's value in each simulation, in order: its fraction of the window up, or its cost.

exact float

The twin's exact expected value over the window (mission_availability, or expected_cost's mean).

coefficient float

The multiple of the twin's error taken off: cov(x, twin) / var(twin) over the run (over antithetic pairs' means, for an antithetic run), the one that leaves the least variance.

correlation float

The correlation of the system's values with the twin's, over the same.

itself bool

Whether the twin is the system itself, whose expected values the exact methods work out (independent components, or tied together only as the exact methods take them: crews, standby groups or common-cause groups their chains follow): its exact value is then the system's, which the intervals give, with no error. A run takes it by default.

Examples:

Four simulations whose twin was up 96.5% of the time on average, where its exact mean is 95%: the system's own mean, 95.25%, is taken down by about as much.

>>> import numpy as np
>>> from repyability import ControlVariate
>>> x = np.array([0.90, 0.95, 0.99, 0.97])
>>> twin = np.array([0.92, 0.96, 1.00, 0.98])
>>> control = ControlVariate.of(x, twin, exact=0.95)
>>> round(control.coefficient, 4), round(control.correlation, 4)
(1.1286, 0.9981)
>>> round(float(control.controlled(x).mean()), 4)
0.9356
>>> round(control.variance_reduction)
261

variance_reduction property

variance_reduction

How many times as many plain simulations the controlled estimate is worth: 1 / (1 - correlation**2), the plain mean's variance over the controlled mean's (infinite for a twin that is the system itself).

Returns:

Type Description
float

The factor.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

of classmethod

of(values, twin, exact, antithetic=False, itself=False)

The control of a run whose simulations gave values, by a twin that gave twin (in the same order) and whose exact mean is exact.

Parameters:

Name Type Description Default
values array_like

The system's value in each simulation.

required
twin array_like

The twin's value in each simulation.

required
exact float

The twin's exact expected value.

required
antithetic bool

Whether the simulations come in antithetic pairs, by default False: the coefficient is then worked out from the pairs' means.

False
itself bool

Whether the twin is the system itself, by default False.

False

Returns:

Type Description
ControlVariate

The twin's values, its exact value, the coefficient and the correlation.

controlled

controlled(values)

The controlled values of the simulations whose own values (in the same order as the twin's) these were.

Parameters:

Name Type Description Default
values array_like

The system's value in each simulation.

required

Returns:

Type Description
ndarray

values - coefficient * (twin - exact).

ConditionalRun dataclass

ConditionalRun(modules, states, availability_square=None, whole=False, uptimes=None, costs=None, delivered=None)

Bases: _ResultMapping

How a run's expected values were taken given its modules' histories: RepairableRBD.availability or cost on a system with dependent modules, by default or with conditional=True.

The modules are the nodes whose values over time the exact methods do not work out (a standby group of other lives, a nested RBD that needs simulating, imperfect repair, a maintenance group's members, the nodes limited repair crews serve). Every other node is independent of them, so each simulation's expected values given its modules' histories are worked out exactly (the system given each joint state of the modules, once). Their mean, and its interval (mean_availability_interval, the cost's mean_interval), estimate the window's expected values without bias, and more precisely than the simulations' own values, whose randomness from the other nodes is gone.

By default (whole) the whole system is simulated, as a plain run simulates it, and only the means (mean_availability, the cost's mean and breakdowns) and their intervals take the simulations' expected values given their modules (uptimes, costs): everything else in the result, the spread included, is the simulations' own (sample_mean_availability, the cost's sample_mean). With conditional=True only the modules are simulated, which costs only their events: the result's own values are then the expected ones, whose spread is less than a window's own, so it gives no percentiles.

Attributes:

Name Type Description
modules tuple

The nodes simulated as modules, in the order of the RBD's components.

states int

The joint states of the modules (each up or down) the simulations met: the system was worked out exactly given each. Just one, and the modules never changed state in the run: their outages were not sampled, and every simulation's expected values given them are the same, which says nothing of the error. The means and their intervals are then the simulations' own (method="simulated") for a whole run, and have no error to give (nan) for a run of the modules alone, which a tolerance does not stop: run more simulations.

availability_square (ndarray, optional)

With conditional=True, at each time of the run's curve, the mean over the simulations of the square of each one's chance of being up then, for the curve's standard error (AvailabilityResult.availability_se); None otherwise.

whole bool

Whether the whole system was simulated, by default False: True for the default run of a system with modules, whose means alone take the values below; False with conditional=True.

uptimes (ndarray, optional)

With whole, each simulation's expected up time given its modules' histories, in order; None otherwise (with conditional=True they are the result's own uptimes).

costs (ndarray, optional)

With whole, each simulation's expected cost given them, in order, if anything is priced; None otherwise.

delivered (ndarray, optional)

With whole, each simulation's expected fraction of the demand delivered given them, in order, if a demand was given; None otherwise (with conditional=True they are the result's own delivered).

informative property

informative

Whether the modules changed state in the run, so that the spread of the simulations' expected values given them estimates the error, or there are none, and the values are exact.

unknown property

unknown

Whether the error of the run's means is unknown: a run of the modules alone in which they never changed state.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

UncertaintyResult dataclass

UncertaintyResult(samples, nominal, n_draws)

Bases: _ResultMapping

The spread of a system quantity over plausible node models.

Returned by NonRepairableRBD.sf_uncertainty. Each of the n_draws draws gives every uncertain node a plausible model (its parameters drawn from what is known about them), and the system quantity is computed exactly for that draw. The samples therefore describe epistemic uncertainty, about what the models are, and not the aleatory variability that the models themselves describe. Their percentiles give uncertainty (credible) intervals; they do not narrow as n_draws grows, which only makes them more precise.

Its repr summarises the draws: the nominal value, the median and the 90% interval.

Attributes:

Name Type Description
samples ndarray

One value per draw (n_draws values) for a single time, or one row per draw and one column per time for an array of times.

nominal float or ndarray

The value with every node's own model: the point estimate.

n_draws int

The number of draws.

Examples:

A pump whose Weibull model was fitted (by surpyval) to 50 failure times, in series with a valve that is 99% reliable. The fit's parameter covariance gives the draws:

>>> import numpy as np
>>> import surpyval as surv
>>> from repyability import NonRepairableRBD
>>> pump = surv.Weibull.fit(np.linspace(200, 1800, 50))
>>> valve = surv.FixedEventProbability.from_params(0.01)
>>> rbd = NonRepairableRBD(
...     [("s", "pump"), ("pump", "valve"), ("valve", "t")],
...     {"pump": pump, "valve": valve},
... )
>>> result = rbd.sf_uncertainty(500, {"pump": "fit"}, n_draws=5000, seed=0)
>>> round(result.nominal, 3), round(result.median, 3)
(0.847, 0.846)
>>> lower, upper = result.interval(0.9)
>>> round(lower, 3), round(upper, 3)
(0.777, 0.906)

mean property

mean

The mean over the draws (per time, for an array of times).

Returns:

Type Description
float or ndarray

The mean of samples over the draws.

median property

median

The median over the draws (per time, for an array of times).

Returns:

Type Description
float or ndarray

The median of samples over the draws.

std property

std

The standard deviation over the draws (per time).

Returns:

Type Description
float or ndarray

The sample standard deviation of samples over the draws.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

percentile

percentile(q)

The q-th percentile over the draws (per time).

Parameters:

Name Type Description Default
q float

The percentile, in [0, 100], as numpy takes it: q strictly between 0 and 1 warns, as it is more likely a fraction meant.

required

Returns:

Type Description
float or ndarray

The percentile of samples over the draws.

interval

interval(level=0.9)

The equal-tailed uncertainty interval over the draws (per time).

Parameters:

Name Type Description Default
level float

The probability the interval holds, in (0, 1), by default 0.9: from the 5th to the 95th percentile.

0.9

Returns:

Type Description
tuple

(lower, upper): floats, or arrays for an array of times.

Raises:

Type Description
ValueError

If level is not in (0, 1).

UncertaintyImportance dataclass

UncertaintyImportance(method, variance, first_order, total)

Bases: _ResultMapping

Each uncertain input's part in the uncertainty of a system quantity: returned by NonRepairableRBD.uncertainty_importance.

The inputs are the uncertainties given (a node, a tuple of nodes of one population, a common-cause group's model), by the keys they were given under. Which input's uncertainty widens the interval most is where more data would narrow it most.

Attributes:

Name Type Description
method str

"delta" (the delta method) or "sobol" (variance-based, from draws).

variance float or ndarray

The quantity's variance over the inputs' uncertainty: the delta method's approximation, the sum of its parts, or the draws'.

first_order dict

Each input's first-order share of the variance. The delta method's part, g^T Sigma g over the variance, g the quantity's gradient in the input's parameters and Sigma their covariance: the shares add up to 1. Sobol's first-order index, Var(E[Q | input]) / Var(Q): the share of the variance that knowing the input exactly would remove.

total dict

Each input's total share: Sobol's total index, E[Var(Q | the others)] / Var(Q), which counts its interactions with the others too (a total well above the first-order share says the input matters through them). The delta method's, its part again (a linear approximation has no interactions).

Examples:

>>> import surpyval as surv
>>> import scipy.stats as st
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
...     [("s", "a"), ("a", "b"), ("b", "t")],
...     {
...         "a": surv.Exponential.from_params([0.01]),
...         "b": surv.Exponential.from_params([0.01]),
...     },
... )
>>> rates = {
...     "a": {"failure_rate": st.uniform(0.005, 0.01)},
...     "b": {"failure_rate": st.uniform(0.008, 0.004)},
... }
>>> parts = rbd.uncertainty_importance(10.0, rates)
>>> {node: round(share, 3) for node, share in parts.first_order.items()}
{'a': 0.862, 'b': 0.138}

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

RedundancyAllocation dataclass

RedundancyAllocation(units, reliability, cost, method, resources=dict(), mix=dict(), strategy=dict())

Bases: _ResultMapping

The result of NonRepairableRBD.allocate_redundancy().

The chosen number of copies of each costed node, with the system reliability and total cost that allocation gives. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
units dict

How many identical copies of each costed node to fit in active parallel. Always at least 1: the original unit.

reliability float

The system reliability with that allocation (at the mission time t, for a time-varying RBD).

cost float

Total cost of the allocation, sum(costs[node] * units[node]). Every copy is costed, including the original. With several resources, the total of the one a target minimised, of "cost", or of the first resource.

method str

"exact" (a proven optimum) or "greedy" (a fast heuristic solution, usually but not always optimal).

resources dict

The total of every resource the copies use, keyed by resource ({"cost": ...} when costs gave numbers).

mix dict

For each node given a choice of component types, how many copies of each type it uses, {node: {type name: copies}} (types it does not use are left out).

strategy dict

The redundancy strategy of each costed node, "active" or "cold" (standby).

Examples:

Two components in series, 90% and 80% reliable, one cost unit each, with a budget of 3:

>>> from surpyval import FixedEventProbability
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
...     [("s", "a"), ("a", "b"), ("b", "t")],
...     {
...         "a": FixedEventProbability.from_params(0.1),
...         "b": FixedEventProbability.from_params(0.2),
...     },
... )
>>> best = rbd.allocate_redundancy({"a": 1.0, "b": 1.0}, budget=3)
>>> best.units, round(best.reliability, 4), best.cost, best.method
({'a': 1, 'b': 2}, 0.864, 3.0, 'exact')
>>> best.resources
{'cost': 3.0}
>>> best["units"] is best.units
True

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

ReliabilityRedundancyAllocation dataclass

ReliabilityRedundancyAllocation(units, component_reliability, reliability, cost, resources)

Bases: _ResultMapping

The component reliability and number of copies chosen for each node by NonRepairableRBD.allocate_reliability_redundancy.

Attributes:

Name Type Description
units dict

The number of active copies of each node.

component_reliability dict

The reliability chosen for each node's components (every copy of a node is the same).

reliability float

The system reliability.

cost float

The total of the "cost" resource (or of the first resource).

resources dict

The total of each resource the copies use.

Examples:

>>> import math
>>> from surpyval import FixedEventProbability
>>> from repyability import NonRepairableRBD
>>> rbd = NonRepairableRBD(
...     [("s", "a"), ("a", "t")],
...     {"a": FixedEventProbability.from_params(0.1)},
... )
>>> def cost(r, n):
...     return n * (10 + (-1 / math.log(r)) ** 1.5)
>>> best = rbd.allocate_reliability_redundancy(
...     {"a": cost}, budget=50, bounds=(0.5, 0.999)
... )
>>> best.units, round(best.component_reliability["a"], 3)
({'a': 3}, 0.754)
>>> round(best.reliability, 4), round(best.cost, 4)
(0.9851, 50.0)

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

TotalCostAllocation dataclass

TotalCostAllocation(units, total_cost, acquisition_cost, cost_rate, availability, horizon, method, trains=None, discount_rate=0.0)

Bases: _ResultMapping

The result of RepairableRBD.allocate_redundancy().

How many copies of each node (or train of nodes) give a repairable system the lowest total cost of ownership over a horizon: buying the copies, running them (repairs, replacements, maintenance, inspections, their own downtime), and the cost of the system being down. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
units dict

How many identical copies of each node considered to fit in active parallel, each repaired independently, and of each train to have alongside it (under the train's name). Always at least 1: the original unit or train.

total_cost float

The total cost of owning the system for horizon with those copies, acquisition_cost + cost_rate * horizon (with the horizon's present value for it when discounted): what RepairableRBD.total_cost(horizon, discount_rate=...) gives for the system with the copies drawn out.

acquisition_cost float

The one-off cost of buying every component, each copy included.

cost_rate float

The long-run running cost per unit time: the expected_cost_rate of the system with the copies drawn out.

availability float

The system's long-run availability with those copies: its mean_availability.

horizon float

The horizon the total cost is taken over.

method str

"exact" (a proven optimum) or "greedy" (a fast heuristic solution, usually but not always optimal).

trains dict or None

The trains considered (allocate_redundancy's trains), each name with its nodes in order; None without.

discount_rate float

The continuous discount rate the running costs were discounted at (0: undiscounted).

Examples:

A pump that fails on average every 1000 hours and takes 10 to repair, bought for 20,000 and repaired for 500, when an hour without pumping costs 100, over ten years (87,600 hours):

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "pump"), ("pump", "t")],
...     {
...         "pump": {
...             "reliability": surv.Exponential.from_params([1e-3]),
...             "repairability": surv.Exponential.from_params([0.1]),
...             "repair_cost": 500.0,
...             "acquisition_cost": 20000.0,
...         }
...     },
...     downtime_cost_rate=100.0,
... )
>>> best = rbd.allocate_redundancy(87600.0)
>>> best.units, round(best.total_cost), round(best.acquisition_cost)
({'pump': 2}, 127591, 40000)
>>> round(best.availability, 6), best.method
(0.999902, 'exact')

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

MaintenancePlan dataclass

MaintenancePlan(intervals, cost_rate, availability, offsets=None)

Bases: _ResultMapping

The result of RepairableRBD.optimal_replacement_intervals() and RepairableRBD.optimal_inspection_intervals().

The interval chosen for each component, and the system's exact long-run values with them. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
intervals dict

Node name -> its chosen interval: of age replacement (inf to replace it only when it fails), or of inspection.

cost_rate float

The system's long-run cost per unit time with those intervals: its expected_cost_rate.

availability float

The system's long-run availability with them: its mean_availability. For a safety system, 1 - availability is its average probability of failure on demand, PFDavg.

offsets dict or None

For optimal_inspection_intervals, node name -> the time of its first test (from 0 to less than its interval): those chosen with offset_shares, or else each keeping its share of the interval, so that plans compare as they are. with_intervals takes them as they are. None for optimal_replacement_intervals.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

Lever dataclass

Lever(key, name, value, discrete, bounds, calendar=False)

Bases: _ResultMapping

One lever of a diagram: a value that its parameter_sensitivity moves.

RepairableRBD.levers and NonRepairableRBD.levers list them, in the order parameter_sensitivity reports them, and with_levers builds the diagram with them at other values. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
key Hashable

Whose lever it is, as parameter_sensitivity keys its result: a node; the tuple of a common-cause group's members, for the parameters of the model they share and the group's own; or None, for the repair crews.

name str

The lever, as parameter_sensitivity names it: in a RepairableRBD "reliability.alpha", "inspection.interval", "standby.units", "ccf_beta", "repair_crews", ...; in a NonRepairableRBD the parameter's own name ("alpha") or the group's ("ccf_beta"), or a regression node's covariate ("covariate.load", its feature name, or "covariate.0", its place).

value float

Its value in the diagram.

discrete bool

Whether it counts something (standby units, repair crews), its sensitivity the change one more makes; else its sensitivity is a derivative.

bounds tuple of float

(lower, upper), the range of its values: a value outside it is refused (-inf and inf where there is no bound). An end may itself be a valid value or not: a coverage of 1 is, a rate of 0 is not.

calendar bool

Whether it moves the period of a calendar that its component shares with others (a block replacement's or a test's interval, with other components' block replacements or tests): the long-run values jump as it leaves the common calendar, so its long-run sensitivity takes its schedule apart from the others' (see RepairableRBD.parameter_sensitivity). Always False in a NonRepairableRBD.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

DemonstrationPlan dataclass

DemonstrationPlan(n, test_multiple, test_time, failures, consumer_risk, producer_risk)

Bases: _ResultMapping

The result of demonstration_plan and mtbf_demonstration_plan.

A demonstration test that keeps both of its risks: a design no better than the target passes it with a chance of at most the consumer's risk, and a good design fails it with a chance of at most the producer's. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
n int or None

The number of units to test (an attribute test); None for a test of a constant failure rate.

test_multiple float or None

How many missions long each unit's test is (an attribute test); None for a test of a constant failure rate.

test_time float or None

The total unit time to test (a test of a constant failure rate); None for an attribute test.

failures int

The failures the test allows: it passes with at most so many.

consumer_risk float

The chance that a design at the target passes.

producer_risk float

The chance that a design at the good level fails.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

AvailabilityAllocation dataclass

AvailabilityAllocation(availability, mttf, mttr, system_availability)

Bases: _ResultMapping

The result of RepairableRBD.availability_allocation() and RepairableRBD.mttf_mttr_allocation().

What each component must achieve for the system to meet an availability target, and the system's exact long-run availability if they do. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
availability dict

Node name -> the long-run availability allocated to each component. The components that keep theirs (see availability_allocation) are included, at their own.

mttf dict

Node name -> an MTTF, for each component allocated an availability: from availability_allocation, the MTTF that gives it that availability at its current MTTR; from mttf_mttr_allocation, its MTTF in the cheapest design, together with the MTTR in mttr.

mttr dict

Node name -> an MTTR, for the same components: from availability_allocation, the MTTR that gives the allocated availability at the current MTTF (so either this or the MTTF above will do); from mttf_mttr_allocation, its MTTR in the cheapest design.

system_availability float

The system's long-run availability with the allocated availabilities, as mean_availability gives it: the target, or more if the system already met it.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

SparesDemand dataclass

SparesDemand(probabilities, horizon, fleet, method, members=None)

Bases: _ResultMapping

How many spares a component uses over a horizon: the distribution of its replacements, from new, for one system or a fleet of them.

Returned (one per component) by RepairableRBD.spares_demand. A component uses a spare at each failure and each preventive replacement (a standby group, at each of its units' failures). Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
probabilities ndarray

The probability of each number of replacements, 0, 1, 2, ...; they sum to 1 (up to a tail below about 1e-12).

horizon float

The time they are counted over, from new.

fleet int

How many systems use them, each from new.

method str

"exact", worked out to about 1e-6, or "simulate", the fractions of simulations.

members tuple or None

The components whose spares a part pools (see spares_demand's parts), or None for one component's.

Examples:

A component with a constant failure rate of 0.01, replaced in no time, uses a Poisson number of spares, 10 on average in 1,000 hours:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "pump"), ("pump", "t")],
...     {
...         "pump": {
...             "reliability": surv.Exponential.from_params([0.01]),
...             "repairability": "instant",
...         }
...     },
... )
>>> demand = rbd.spares_demand(1000.0)["pump"]
>>> round(demand.mean, 4)
10.0
>>> demand.stock(0.95)  # covers the 1,000 hours 95% of the time
15

mean property

mean

The expected number of spares used (a property, as the other results' values are).

Returns:

Type Description
float

The mean number of replacements.

std property

std

The standard deviation of the number of spares used (a property, as mean is).

Returns:

Type Description
float

The standard deviation of the number of replacements.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

covered

covered(stock)

The probability that stock spares cover the horizon's demand: that it is stock or fewer.

Parameters:

Name Type Description Default
stock int

The spares held, none replenished.

required

Returns:

Type Description
float

The probability.

stock

stock(probability)

The fewest spares that cover the horizon's demand with at least probability, none replenished.

Parameters:

Name Type Description Default
probability float

The chance of not running out, in [0, 1).

required

Returns:

Type Description
int

The stock.

Raises:

Type Description
ValueError

If probability is not in [0, 1).

SparesStock dataclass

SparesStock(stock, fill_rate, stockout_probability, lead_time, fleet, on_order, on_order_at_demand, members=None)

Bases: _ResultMapping

The stock of a component's spares that meets a target when each spare used is reordered at once and arrives a lead time later (one-for-one, or (S - 1, S), replenishment), in the long run.

Returned (one per component) by RepairableRBD.spares_stock. With a stock of S, a spare is on the shelf when fewer than S are on order: those used in the last lead time. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
stock int

The fewest spares that meet the targets.

fill_rate float

With that stock, the fraction of demands met from the shelf.

stockout_probability float

With that stock, the fraction of time none is on the shelf.

lead_time float

The time a spare ordered takes to arrive.

fleet int

How many systems draw on the stock.

on_order ndarray

The distribution of how many spares are on order at a random time: the demand in a lead time.

on_order_at_demand ndarray

The same, as a demand finds it (not counting itself).

members tuple or None

The components whose spares a part pools (see spares_stock's parts), or None for one component's.

Examples:

A component with a constant failure rate of 0.01, replaced in no time, and a lead time of 300 hours: the spares on order are Poisson, 3 on average, and 7 on the shelf meet 96.6% of demands:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> rbd = RepairableRBD(
...     [("s", "pump"), ("pump", "t")],
...     {
...         "pump": {
...             "reliability": surv.Exponential.from_params([0.01]),
...             "repairability": "instant",
...         }
...     },
... )
>>> stock = rbd.spares_stock(300.0, fill_rate=0.95)["pump"]
>>> stock.stock
7
>>> round(stock.fill_rate, 4)
0.9665

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

fill_rate_for

fill_rate_for(stock)

The fraction of demands a stock of stock meets from the shelf: that a demand finds fewer than stock on order.

Parameters:

Name Type Description Default
stock int

The spares held.

required

Returns:

Type Description
float

The fill rate.

stockout_probability_for

stockout_probability_for(stock)

The fraction of time a stock of stock leaves the shelf empty: that stock or more are on order.

Parameters:

Name Type Description Default
stock int

The spares held.

required

Returns:

Type Description
float

The stock-out probability.

SimulationChunk

SimulationChunk(ranges, settings, tally)

Simulations of a repairable system's run, by their positions in it.

Made by a RepairableRBD's simulate_chunk for one range of simulations; merged with the run's other chunks by merge; saved with to_dict or to_json and loaded with from_dict or from_json; and turned into the run's AvailabilityResult by its availability_from_chunks.

Parameters:

Name Type Description Default
ranges iterable of (int, int)

The simulations held, as (start, stop) ranges, in order.

required
settings dict

The run they belong to (see settings below).

required
tally object

Their totals, as the simulation keeps them.

required

Attributes:

Name Type Description
ranges list[tuple[int, int]]

The simulations held, as (start, stop) ranges (stop excluded), in order.

settings dict

The run they belong to: "t_simulation", "entropy" (the number the seed gives the streams), "method", "working_nodes" and "broken_nodes", "antithetic", "demand", "fingerprint", a hash of the system saved as JSON (with the RePyability version), or None if it cannot be saved, "state", the components' states at the start as JSON, or None for new, and "curve_points", the grid the curve is counted on, or None for every change (see RepairableRBD.availability).

n_simulations property

n_simulations

How many simulations the chunk holds.

merge staticmethod

merge(chunks)

One chunk of the simulations of chunks, which must be of the same run (the same system, window, seed and settings) and hold different simulations. They are put in order of their first simulations, and each must end before the next begins.

Parameters:

Name Type Description Default
chunks iterable of SimulationChunk or dict

The chunks, or their to_dict data.

required

Returns:

Type Description
SimulationChunk

Their simulations, in order. The chunks given are unchanged.

Raises:

Type Description
ValueError

If there are none, they belong to different runs, or their simulations overlap or interleave.

to_dict

to_dict()

The chunk as JSON data (from_dict loads it).

Returns:

Type Description
dict

Its ranges, run settings and totals, with the RePyability version that made it.

to_npz

to_npz()

The chunk as the bytes of a NumPy .npz file (from_npz loads it): its per-simulation values and the curve's changes (or counts) as arrays, and the rest, the ranges, settings and exact totals, as JSON in an array of its own. Compact, and read without pickle, so a worker can send it back to any coordinator (see repyability.rbd.shards).

Returns:

Type Description
bytes

The .npz file's bytes.

from_npz classmethod

from_npz(data)

The chunk to_npz saved.

Parameters:

Name Type Description Default
data bytes

to_npz's output.

required

Returns:

Type Description
SimulationChunk

The chunk.

Raises:

Type Description
ValueError

If data is not a saved chunk.

from_dict classmethod

from_dict(data)

The chunk to_dict saved.

Parameters:

Name Type Description Default
data dict

to_dict's output (or its JSON, parsed).

required

Returns:

Type Description
SimulationChunk

The chunk.

Raises:

Type Description
ValueError

If data is not a saved chunk.

to_json

to_json(fp=None)

The chunk as a JSON document (from_json loads it): returned, or written to fp.

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

Returns:

Type Description
str or None

to_dict's output, as JSON, or None once written to fp.

from_json classmethod

from_json(text)

The chunk to_json saved.

Parameters:

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

to_json's output: its text, a path to a file holding it, or a file opened for reading.

required

Returns:

Type Description
SimulationChunk

The chunk.

TimelineSimulation dataclass

TimelineSimulation(system, components, time_simulated_to, n_simulations, antithetic=False, engine='python', method='event loop', start=0)

Bases: _ResultMapping

Simulated up/down histories of a repairable system and its components, one per simulation, as timelines.

Returned by RepairableRBD.simulate_timelines. Each Timelines works out its measures for every simulation at once (uptime, failures, first_failure, availability_curve(), ...), indexes to one simulation's Timeline, and merges with others. Like the other result types it is also a read-only mapping of its fields.

Attributes:

Name Type Description
system Timelines

The system's histories, as the event loop has them: each change's cause is the component whose change made it.

components dict

Node -> its Timelines: each component's histories (a nested RBD's, its own system's; a standby group's, the group's).

time_simulated_to float

The window's end.

n_simulations int

How many histories each holds.

antithetic bool

Whether the simulations came in antithetic pairs.

engine str

The engine that made them: "python" or "numba".

method str

How they were made: "event loop", recorded by the event loop as it ran them, the simulations availability runs with the same seed. (Until 0.13 independent components' histories could be "streams", drawn from their streams, the same histories.)

start int

The run's simulation the first history is (see join).

Examples:

>>> import surpyval as surv
>>> from repyability import RepairableRBD
>>> unit = {
...     "reliability": surv.Weibull.from_params([100, 1.5]),
...     "repairability": surv.Exponential.from_params([0.5]),
... }
>>> rbd = RepairableRBD(
...     [("s", "a"), ("s", "b"), ("a", "t"), ("b", "t")],
...     {"a": unit, "b": unit},
... )
>>> runs = rbd.simulate_timelines(
...     1000.0, mc_samples=500, seed=1, engine="python"
... )
>>> runs.method, len(runs.system)
('event loop', 500)
>>> pair, pump = runs.system.failures, runs.components["a"].failures
>>> bool(pair.mean() < pump.mean())
True

Two halves of the run, made apart and joined, are the run:

>>> halves = [
...     rbd.simulate_timelines(1000.0, mc_samples=250, seed=1, start=s)
...     for s in (0, 250)
... ]
>>> TimelineSimulation.join(halves).system == runs.system
True

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

join classmethod

join(parts)

Consecutive ranges of one run's simulations, made apart (with simulate_timelines' start, in other processes or on other machines, say), as the one result of them all.

Parameters:

Name Type Description Default
parts iterable of TimelineSimulation

The ranges, in any order: together, simulations start to start + n - 1 of one run, with no gap.

required

Returns:

Type Description
TimelineSimulation

Their histories one after another, from the first range's start. Its engine and method are the parts', joined by commas where they differ.

Raises:

Type Description
ValueError

If there are no parts, or they are not consecutive ranges of a run over one window, with the same components and pairing.

run_shard

run_shard(shard)

Run a shard (see the module docstring): its simulations of its run, by the engine it names, here.

Parameters:

Name Type Description Default
shard bytes or str

The shard, as RepairableRBD.shards made it: its JSON.

required

Returns:

Type Description
bytes

Its partial: the simulations' totals, as a SimulationChunk's to_npz bytes, for RepairableRBD.availability_from_chunks.

Raises:

Type Description
ValueError

If shard is not a shard, or was made by another version of RePyability (which can simulate differently).

Examples:

>>> import surpyval as surv
>>> from repyability import RepairableRBD, run_shard
>>> E = surv.Exponential.from_params
>>> rbd = RepairableRBD(
...     [("s", "a"), ("a", "t")],
...     {"a": {"reliability": E([0.1]), "repairability": E([1.0])}},
... )
>>> shards = rbd.shards(100.0, 2048, seed=3, size=1024)
>>> partials = [run_shard(shard) for shard in reversed(shards)]
>>> merged = rbd.availability_from_chunks(partials, mc_samples=2048)
>>> whole = rbd.availability(100.0, mc_samples=2048, seed=3)
>>> merged.system_uptime == whole.system_uptime
True

MaintenancePolicy dataclass

MaintenancePolicy(interval, cost_rate)

Bases: _ResultMapping

An optimal preventive-maintenance policy for a single component.

Returned by the optimal_replacement_policy() method of NonRepairable (age replacement: replace at age interval or on failure, whichever comes first) and by the optimal_overhaul_policy() method of Repairable (periodic overhaul: repair each failure and renew the unit every interval). It is an immutable (frozen) dataclass, read as a mapping too, with to_dict() for JSON.

Attributes:

Name Type Description
interval float

The optimal preventive interval, in the time units of the component's models: the replacement age for age replacement, or the overhaul interval for a repairable component. inf when preventive action never pays (run to failure / never overhaul).

cost_rate float

The long-run expected cost per unit time under the policy. When interval is inf this is the limiting cost rate of running without preventive action.

Examples:

>>> import surpyval as surv
>>> from repyability import NonRepairable
>>> unit = NonRepairable(surv.Weibull.from_params([1000, 2.5]))
>>> unit.set_costs_planned_and_unplanned(cp=1, cu=5)
>>> policy = unit.optimal_replacement_policy()
>>> round(policy.interval, 1), round(policy.cost_rate, 6)
(493.0, 0.003462)

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

FailureLimitPolicy dataclass

FailureLimitPolicy(failure_count, cost_rate)

Bases: _ResultMapping

An optimal failure-limit (replace-at-N-th-failure) policy.

Returned by the optimal_failure_limit_policy() method of Repairable: rather than renewing at a fixed age, the unit is repaired on each failure and replaced at the failure_count-th failure. It is an immutable (frozen) dataclass, read as a mapping too, with to_dict() for JSON.

Attributes:

Name Type Description
failure_count int

The optimal number of failures per replacement cycle: repair the first failure_count - 1 failures, then replace on the last. 1 means replace at every failure.

cost_rate float

The long-run expected cost per unit time under the policy, (cr * (n - 1) + co) / E[T_n] at n = failure_count, where E[T_n] is the expected time to the n-th failure.

Examples:

>>> import surpyval as surv
>>> from surpyval.recurrent import GeneralizedRenewal
>>> from repyability import Repairable
>>> grp = GeneralizedRenewal.fit_from_parameters(
...     [100.0, 2.0], 0.4, kijima="i", dist=surv.Weibull
... )
>>> unit = Repairable(grp)
>>> unit.set_repair_and_overhaul_costs(cr=1.0, co=5.0)
>>> policy = unit.optimal_failure_limit_policy(
...     seed=1, mc_samples=200, max_failures=15
... )
>>> policy.failure_count, round(policy.cost_rate, 3)
(7, 0.031)

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

FailureForecast dataclass

FailureForecast(window, age, probability, expected_failures, expected_cost)

Bases: _ResultMapping

The failures of a part replaced when it fails, over a window from its current unit's age: returned by NonRepairable.failures.

Attributes:

Name Type Description
window float or ndarray

The window's length, in the models' time unit.

age float

The current unit's age at the window's start.

probability float or ndarray

The chance the current unit fails in the window.

expected_failures float or ndarray

The expected number of failures in the window, each a replacement: the current unit's, and its replacements'.

expected_cost (float, ndarray or None)

Their expected cost: the unplanned cost times expected_failures; None if the part has no unplanned cost.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

SkipValue dataclass

SkipValue(resets, every, time_to_next, discount_rate, replace, skip, npv, best)

Bases: _ResultMapping

Whether to skip the next planned replacement of a part, right after a failure: returned by NonRepairable.skip_next_replacement.

Attributes:

Name Type Description
resets bool

Whether the plan resets at each replacement.

every float

The plan's interval, in the life's time unit.

time_to_next float

The time from the failure to the next planned replacement: every for a plan that resets.

discount_rate float

The continuous discount rate a unit of the life's time.

replace float

The present-value cost of doing the next planned replacement. On a plan that does not reset, the costs to the following planned replacement: the replacement, the new unit's failures to then and the failures before it. On a plan that resets, the cost of the whole future (the first cycle, then the plan from new), which with no discounting is endless: inf.

skip float

The present-value cost of skipping it, over the same comparison: the failures to the following planned replacement; or, on a plan that resets, the whole future from the longer first cycle (inf with no discounting).

npv float

What skipping saves, replace - skip: the planned cost saved, net of the failures it adds. Positive, skipping pays. Finite where both are endless: the first cycles' difference, each cycle's cost less its length at the plan's cost rate.

best str

"skip" where skipping saves, "replace" otherwise.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

NextReplacement dataclass

NextReplacement(age, best, now, choices, replace_from_age, cost_rate)

Bases: _ResultMapping

When to replace a part in service next: returned by NonRepairable.optimal_next_replacement.

Attributes:

Name Type Description
age float

The part's age now.

best ReplacementChoice

The cheapest choice (on a tie, the earlier in choices).

now bool

Whether the best is to replace it now.

choices list of ReplacementChoice

Every choice: now, each outage by its time, a planned replacement at its best time, and run to failure.

replace_from_age float

The age from which replacing now beats every later choice: the largest break-even age.

cost_rate float

The value of a unit of the part's service, the baseline: the long-run cost a unit of time of the policy given, or of the part's optimal_replacement_policy.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

ReplacementChoice dataclass

ReplacementChoice(kind, time, price, cost, break_even_age)

Bases: _ResultMapping

One choice of when to replace a part next: an entry of NextReplacement.

Attributes:

Name Type Description
kind str

"now", "outage" (at one of the scheduled outages), "planned" (a planned replacement at its best time) or "failure" (run to failure).

time float

When, from now: 0 for now, the outage's time, the planned replacement's best time (0 where no later time beats replacing at once, which is then replacing now, at now_cost; inf if none beats running to failure), inf at failure.

price float

What the replacement costs if the part has not failed by then (at failure, the failure price).

cost float

The expected cost until the part is replaced (at the failure price if it fails first), less the time it serves at cost_rate: the lowest is the best.

break_even_age float or None

The youngest age at which replacing now costs no more than this choice (inf: no age up to the one a new part reaches with a chance of 0.1%). None for replacing now.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

CalendarPolicy dataclass

CalendarPolicy(cost_rate, cost_rate_se, replacement_rates, cost_rates, method, basis, step, period, determined_until, threshold_only, settings=dict(), life=None)

Bases: _ResultMapping

The long-run optimal replacement policy of a part over a calendar of opportunities. Returned by NonRepairable.optimal_calendar_policy.

At each opportunity the policy replaces the part if it is at least a threshold age, which depends on the time: a shut shortly before a cheaper one waits for an older part than one long before it. schedule() lists the thresholds with the long-run chance of a replacement at each, next_replacement(age) says when a unit in service will be replaced, and replacement_rates and cost_rates say what the policy does and costs in the long run, by kind.

Attributes:

Name Type Description
cost_rate float

The long-run average cost a unit of time, g, whatever the start.

cost_rate_se float or None

Its standard error, where it is estimated by sampling (None where it is worked out).

replacement_rates dict

Replacements a unit of time in the long run, by kind: each opportunity's name, "anytime" (a day with no shut, where the calendar allows one) and "failure". A kind the policy never uses has 0. Exact for the grid where the cost rate is (basis "exact"); otherwise, with the schedule's chances, estimates from a new unit run in, with no standard error (only cost_rate_se has one): a sampled policy's move with the seed.

cost_rates dict

What each kind costs a unit of time in the long run (its rate times its price); they sum to cost_rate.

method str

"dp" (dynamic programming on the grid), "integration" (between opportunities in continuous time) or "sampling" (sampled windows of a long cycle).

basis str

"exact" (exact for the grid, or for the quadrature), "differenced" (from a long finite horizon) or "sampled"; the rates by kind are estimated from a new unit run in, where the cost rate is not exact.

step float or None

The grid's step, sized from the life and put on the opportunities' times (None for integration, which has none).

period float or None

The time after which the thresholds repeat (None if they do not, or too long to keep).

determined_until float or None

For a calendar with given dates: the first time at which the policy depends on how the calendar goes on after its last date (repeating it, or leaving only replacement on any day). None for a periodic calendar.

threshold_only bool

Whether, at every time worked out, the ages that replace are all those from the first (else the threshold is the youngest that replaces, and some older ones do not: a life whose hazard falls; the rates by kind then take every age from the youngest).

settings dict

How it was worked out: the calendar's tail, sweeps, bounds, samples, windows.

life dict or None

The saved form of the life it was worked out for (as the diagrams save a model), which a RepairableRBD component given the policy must have; None where the life does not save.

Examples:

A bearing with a Weibull life (scale 1000 days, shape 2.5), shuts every 91 days at 3 and every 364 days at 1, no replacement between them, and 20 at a failure:

>>> import surpyval as surv
>>> from repyability import Calendar, NonRepairable, Opportunity
>>> bearing = NonRepairable(surv.Weibull.from_params([1000.0, 2.5]))
>>> calendar = Calendar(
...     [
...         Opportunity("quarterly", 3.0, period=91.0),
...         Opportunity("annual", 1.0, period=364.0),
...     ],
...     failure=20.0,
...     anytime=None,
... )
>>> policy = bearing.optimal_calendar_policy(calendar)
>>> policy.method, policy.basis, policy.period
('dp', 'exact', 364.0)
>>> round(policy.cost_rate * 364, 3)
2.527
>>> {k: round(v * 364, 3) for k, v in policy.replacement_rates.items()}
{'quarterly': 0.0, 'annual': 0.962, 'failure': 0.078}
>>> policy.next_replacement(age=300.0, now=200.0)
PlannedReplacement(time=364.0, kind='annual', price=1.0)

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.

next_replacement

next_replacement(age, now=0.0)

When the unit in service, age old at time now, will be replaced on the policy, if it does not fail first: the first time, at or after now, at which a planned replacement is open and the unit is at least its threshold. A unit older than the oldest age the policy was worked out to (settings["max_age"]) is past every threshold, and is replaced at the next opportunity, or on any day now where the calendar allows one.

Parameters:

Name Type Description Default
age float

The unit's age now, at least 0 (0: just put in, so not replaced again now).

required
now float

The time now, in the calendar's time, by default 0.

0.0

Returns:

Type Description
PlannedReplacement

When, at which opportunity, and at what price.

schedule

schedule(kinds=(), until=None)

The planned replacements open from time 0 up to until, with each one's threshold and the chance the policy replaces the unit there.

Parameters:

Name Type Description Default
kinds tuple of str

The kinds to list, by name; by default every opportunity. "anytime" among them lists the days with only a replacement on any day open too.

()
until float

The time to list them up to (not including). By default the period over which the thresholds repeat, or the end of a calendar's given dates; give it where there is neither.

None

Returns:

Type Description
list of (float, str, float, float)

Each as (time, kind, threshold, chance): replace the unit there if it is at least threshold old, which happens with chance in the long run (a periodic calendar's), or from a new unit at 0 (given dates, or a cycle too long to keep). An opportunity the policy never uses has a chance of 0.

PlannedReplacement dataclass

PlannedReplacement(time, kind, price)

Bases: _ResultMapping

A unit's next planned replacement on a calendar policy: returned by CalendarPolicy.next_replacement.

Attributes:

Name Type Description
time float

When, in the calendar's time (inf: no planned replacement comes, as after the last date of a plan with none on any day).

kind str or None

The opportunity it falls at ("anytime" for a day with no shut); None if none.

price float or None

What the replacement costs there; None if none.

to_dict

to_dict()

The result as plain data, ready for json.dumps (#235): each field by name, arrays as lists, numpy numbers as Python ones, the results it holds as their own to_dict(), and keys JSON cannot hold (tuple node names, say) as their text.

Returns:

Type Description
dict

The fields, as plain data. Infinite and undefined values stay floats (inf, nan), which Python's json writes as Infinity and NaN.