Non-coherent systems: cold standby

library(dist.structure)
library(algebraic.dist)

Why “non-coherent”

The structure-function framework assumes a monotone binary function phi: {0, 1}^m -> {0, 1} that maps per-component states to a system state. This captures a huge class of reliability topologies but not everything practitioners build. Cold standby is a canonical example.

A cold standby system has m components but only one is active at a time. When the active component fails, a dormant spare instantaneously takes over. System lifetime is the sum of component lifetimes, not an order statistic.

The topology is temporal succession, not a structure function on states. So cold standby:

dist.structure provides cold_standby_dist for this case, with its own class chain that deliberately does not inherit dist_structure.

Constructing a cold standby

sys <- cold_standby_dist(list(
  exponential(1),
  exponential(2),
  exponential(0.5)
))
class(sys)
#> [1] "cold_standby_dist" "univariate_dist"   "continuous_dist"  
#> [4] "dist"
dist.structure::is_dist_structure(sys)   # FALSE -- not a coherent system
#> [1] FALSE

Mean and sampling: exact

mean is exact when every component has an exact mean method: it sums the component means.

mean(sys)                              # 1/1 + 1/2 + 1/0.5 = 3.5
#> [1] 3.5

The sampler is also exact: independent samples from each component, summed.

set.seed(1)
x <- algebraic.dist::sampler(sys)(5000)
mean(x)                                # empirical ~ 3.5
#> [1] 3.505092

Survival and CDF: Monte Carlo with caching

For general components, the density of a sum-of-independent-RVs has no closed form (it’s a convolution). dist.structure computes surv and cdf via Monte Carlo sampling. The closure caches the samples on the first call, so repeated evaluations at different t values are deterministic given the same mc:

set.seed(42)
S <- algebraic.dist::surv(sys)
S(c(1, 2, 3, 5), mc = 5000)           # survival at multiple t values
#> [1] 0.9332 0.7136 0.4986 0.2104

A different mc triggers a fresh draw:

S(2, mc = 5000)                        # uses the same cache
#> [1] 0.7136
S(2, mc = 10000)                       # new cache with 10000 samples
#> [1] 0.7104

Because the cache lives inside the closure, using the same S closure keeps results consistent. cdf has its own closure and its own cache: cdf(sys)(t) + surv(sys)(t) computed independently is not exactly 1. If you need that identity, use one surv closure and compute the CDF from it:

S <- algebraic.dist::surv(sys)
F_t <- function(t) 1 - S(t)
F_t(2) + S(2)                          # exactly 1
#> [1] 1

For reproducibility across separate closure constructions, seed before each one:

set.seed(1); S1 <- algebraic.dist::surv(sys); s1 <- S1(2)
set.seed(1); S2 <- algebraic.dist::surv(sys); s2 <- S2(2)
all.equal(s1, s2)
#> [1] TRUE

Specialized closed-form case: iid exponentials

A cold standby of m iid Exp(rate) components has system lifetime Gamma(shape = m, rate = rate):

m <- 4; rate <- 2
sys <- cold_standby_dist(replicate(m, exponential(rate), simplify = FALSE))
# Monte Carlo survival at t = 3
set.seed(1)
mc_est <- algebraic.dist::surv(sys)(3, mc = 10000)
# Exact via Gamma
exact <- pgamma(3, shape = m, rate = rate, lower.tail = FALSE)
cat(sprintf("MC: %.4f, Exact: %.4f\n", mc_est, exact))
#> MC: 0.1522, Exact: 0.1512

If you hit this case often, you can avoid the Monte Carlo by constructing an algebraic.dist::gamma_dist directly.

What cold standby does NOT support

Generics requiring topology or component-level density/sup fail cleanly:

dist.structure::phi(sys, c(1, 1, 1, 1))
#> Error in UseMethod("phi"): no applicable method for 'phi' applied to an object of class "c('cold_standby_dist', 'univariate_dist', 'continuous_dist', 'dist')"
dist.structure::min_paths(sys)
#> Error in UseMethod("min_paths"): no applicable method for 'min_paths' applied to an object of class "c('cold_standby_dist', 'univariate_dist', 'continuous_dist', 'dist')"

Methods inherited from algebraic.dist::univariate_dist that require density or sup (notably vcov, expectation) are also not supported by default. A specialized subclass with a closed-form aggregate (e.g., iid exponential -> Gamma) can provide them by overriding.

When to reach for cold standby

When you need the full dist.structure protocol (topology, importance, composition), cold standby is the wrong tool; use a coherent system instead.