# Distribution interface

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

## dist_structure IS-A dist

`dist_structure` is a virtual S3 class that inherits from `univariate_dist` and `dist`. Every concrete constructor returns an object that participates in the full `algebraic.dist` distribution algebra via inheritance:

    sys <- series_dist(list(
      exponential(0.5),
      exponential(0.3),
      exponential(0.2)
    ))
    class(sys)
    #> [1] "series_dist"     "coherent_dist"   "dist_structure"  "univariate_dist"
    #> [5] "continuous_dist" "dist"
    algebraic.dist::is_dist(sys)
    #> [1] TRUE

## Default dist methods

When you call `surv`, `cdf`, `sampler` on a `dist_structure` object, dispatch goes through three paths in order of specificity:

1. Specialized closed-form methods (e.g., `surv.exp_series`, `surv.wei_homogeneous_series`). Fastest; used when available.

2. Topology shortcut methods (e.g., `surv.series_dist`). Rare; most specializations land on the specific parametric class.

3. The dist_structure default, which composes component distributions through the structure function via the reliability polynomial identity:

       S_sys(t) = R(S_1(t), S_2(t), ..., S_m(t))

   where `R` is the multilinear extension of `phi`. This is how `coherent_dist(min_paths = ...)` gets a surv function for any topology, even one you specify by hand.

Samplers follow the analogous composition: sample each component independently, then apply `system_lifetime` to reduce to a scalar.

## Choosing specializations vs general constructors

dist.structure provides both general and specialized constructors for common cases. You should prefer the specialization when available:

    # General: any components, arbitrary topology
    sys_general <- series_dist(replicate(3, exponential(1), simplify = FALSE))

    # Specialization: exploits Exp(sum(rates)) closed form
    sys_special <- exp_series(c(1, 1, 1))

Both return identical distributions, but the specialization’s `surv` is a one-liner (`exp(-total_rate * t)`) while the general version computes `prod(exp(-rate_j * t))` per component per t. Same math, different cost.

Verify:

    for (ti in c(0.5, 1, 2)) {
      stopifnot(isTRUE(all.equal(
        algebraic.dist::surv(sys_general)(ti),
        algebraic.dist::surv(sys_special)(ti),
        tolerance = 1e-10
      )))
    }

## Closed-form specializations in dist.structure

Constructor | Closed form
--- | ---
`exp_series(rates)` | `Exp(sum(rates))` exactly
`wei_series(shapes, scales)` | `exp(-sum((t/scale)^shape))` for survival
`wei_homogeneous_series(shape, scales)` | single Weibull with aggregate scale
`gamma_series(shapes, rates)` | product of Gamma upper tails
`lognormal_series(meanlogs, sdlogs)` | product of Lognormal upper tails
`exp_parallel(rates)` | `1 - prod(1 - exp(-rate*t))`
`exp_kofn(k, rates)` | subset enumeration (`O(2^m)`)
`wei_kofn(k, shapes, scales)` | same for Weibull components

Each has closed-form `surv`, `cdf`, `sampler`, and (where feasible) `density`. The `surv` / `cdf` / `density` methods registered on these subclasses dispatch before the `dist_structure` default.

## min, max, and order statistics as dist_structure

The base `algebraic.dist` has `min` and `max` operators on dists that return plain dist objects. `dist.structure` provides structure-aware counterparts:

    d <- exponential(1)

    # Plain min: just a dist, no topology
    d_min <- min(d, d, d)
    class(d_min)
    #> [1] "exponential"     "univariate_dist" "continuous_dist" "dist"

    # Structure-aware: a series_dist, topology preserved
    sys_min <- min_iid(d, m = 3)
    class(sys_min)
    #> [1] "series_dist"     "coherent_dist"   "dist_structure"  "univariate_dist"
    #> [5] "continuous_dist" "dist"
    dist.structure::min_paths(sys_min)
    #> [[1]]
    #> [1] 1 2 3

Same survival function, but the structured version lets you ask `phi`, `min_paths`, `structural_importance`, and so on.

Similarly:

    # Parallel: max of iid
    sys_max <- max_iid(d, m = 3)
    class(sys_max)
    #> [1] "parallel_dist"   "coherent_dist"   "dist_structure"  "univariate_dist"
    #> [5] "continuous_dist" "dist"

    # k-th order statistic
    sys_k <- order_statistic(d, k = 2, m = 5)
    class(sys_k)
    #> [1] "kofn_dist"       "coherent_dist"   "dist_structure"  "univariate_dist"
    #> [5] "continuous_dist" "dist"

## Density and hazard

`density` and `hazard` have specialized implementations only where efficient formulas exist:

    # Density of exp_series: dexp at the aggregate rate.
    f <- density(exp_series(c(0.5, 0.3, 0.2)))
    f(1)
    #> [1] 0.3678794
    dexp(1, rate = 1.0)
    #> [1] 0.3678794

For general `coherent_dist` objects, `density` is typically computed via `-d/dt surv(t)` numerically. If you need `density` on a system where it matters for performance, register a specialized method or use a specialization that ships one.

## The `dist.structure` + `algebraic.dist` stack

    algebraic.dist                    dist, generics, arithmetic on RVs
        |
    dist.structure                    dist with internal component structure
        |
    specialized + general impls       exp_series, kofn_dist, coherent_dist, ...
        |
    downstream packages               serieshaz, kofn, maskedcauses

Downstream packages (e.g., `serieshaz`’s `dfr_dist_series`) inherit from `dist_structure` and get topology + importance + composition for free. Users never need to know whether a given generic is provided by the specialized class, the dist_structure default, or the algebraic.dist base; S3 dispatch finds the most specific method and that’s the one that runs.