# 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:

- is a valid distribution object (you can sample, compute survival, ask for mean),
- is NOT a `dist_structure`: it has no `phi`, no `min_paths`, no signature, no Birnbaum importance.

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

- Sequential redundancy with dormant spares (perfect switching, zero standby hazard).
- Modelling sum-of-lifetimes directly as a distribution.
- Monte Carlo as a first-pass answer when no closed form is available.

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