# Composition and substitution

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

## Building new systems from old

dist.structure provides two compositional operations:

- `substitute_component(sys, j, new_component)` swaps one component’s distribution while preserving the topology.
- `compose_systems(outer, inner_list)` replaces each component of the outer system with an inner sub-system, flattening the components and enumerating the composed minimal paths.

These let you build hierarchical reliability networks without writing min_paths by hand.

## Substituting a component

Start with a 3-component parallel system, then upgrade one component to a longer-lived Weibull:

    sys <- parallel_dist(list(
      exponential(1),
      exponential(1),
      exponential(1)
    ))

    # Survival at t = 1 for the all-exponential parallel:
    algebraic.dist::surv(sys)(1)
    #> [1] 0.7474195

    sys2 <- substitute_component(sys, j = 1,
      new_component = weibull_dist(shape = 2, scale = 2))

    # Survival improves because component 1 is now longer-lived:
    algebraic.dist::surv(sys2)(1)
    #> [1] 0.911614

`substitute_component` returns a `coherent_dist` with the same min_paths and the modified component list. Topology queries work identically:

    length(min_paths(sys2))                # still 3 (parallel)
    #> [1] 3
    ncomponents(sys2)                      # still 3
    #> [1] 3

## Hierarchical composition

`compose_systems` nests: give an outer topology and a list of inner systems (one per outer component), and get back a flattened system whose min_paths combine both levels of topology.

### Series of parallels

A common redundant design: two parallel blocks in series.

    inner_a <- parallel_dist(list(exponential(1), exponential(1)))
    inner_b <- parallel_dist(list(exponential(1), exponential(1)))

    # Outer: series of 2 "slots" (the components are placeholder; they get
    # replaced by the inners during compose_systems).
    outer <- series_dist(list(exponential(1), exponential(1)))

    sys <- compose_systems(outer, list(inner_a, inner_b))
    ncomponents(sys)                       # 4 total leaf components
    #> [1] 4
    length(min_paths(sys))                 # 4 composed paths
    #> [1] 4

The composed paths are the Cartesian product of inner paths within each outer path. Outer path is `{1, 2}`; inner_a min_paths = `{1}, {2}`; inner_b shifted min_paths = `{3}, {4}`. Four combinations: `{1, 3}, {1, 4}, {2, 3}, {2, 4}`.

    min_paths(sys)
    #> [[1]]
    #> [1] 1 3
    #>
    #> [[2]]
    #> [1] 2 3
    #>
    #> [[3]]
    #> [1] 1 4
    #>
    #> [[4]]
    #> [1] 2 4

### Parallel of series

The dual: two series blocks in parallel.

    inner_a <- series_dist(list(exponential(1), exponential(1)))
    inner_b <- series_dist(list(exponential(1), exponential(1)))

    outer <- parallel_dist(list(exponential(1), exponential(1)))

    sys <- compose_systems(outer, list(inner_a, inner_b))
    ncomponents(sys)                       # 4 total
    #> [1] 4
    length(min_paths(sys))                 # 2 composed paths
    #> [1] 2
    min_paths(sys)                         # {1, 2} and {3, 4}
    #> [[1]]
    #> [1] 1 2
    #>
    #> [[2]]
    #> [1] 3 4

## Mixing a sub-system with a plain dist

`compose_systems` handles a mix of `dist_structure` inner systems and plain `dist` inner components. A plain `dist` is treated as a single- component atomic sub-system:

    # Series of: (a 2-parallel block) + (a single exponential)
    inner_par <- parallel_dist(list(exponential(1), exponential(1)))
    outer <- series_dist(list(exponential(1), exponential(1)))

    sys <- compose_systems(outer, list(inner_par, exponential(1)))
    ncomponents(sys)                       # 3: two from parallel + one single
    #> [1] 3
    min_paths(sys)                         # {1, 3} and {2, 3}
    #> [[1]]
    #> [1] 1 3
    #>
    #> [[2]]
    #> [1] 2 3

Component 3 is the atomic component from the second outer slot; it must function jointly with either component 1 or component 2 for the system to function.

## Arbitrary depth

Composition is arbitrarily nestable:

    # A parallel of two different sub-systems:
    sub_a <- series_dist(list(exponential(1), exponential(1)))     # simple series
    sub_b <- bridge_dist(replicate(5, exponential(1), simplify = FALSE))  # bridge

    outer <- parallel_dist(list(exponential(1), exponential(1)))
    combined <- compose_systems(outer, list(sub_a, sub_b))

    ncomponents(combined)                  # 2 + 5 = 7 total
    #> [1] 7
    length(min_paths(combined))            # = 1 + 4 (series has 1 path, bridge has 4)
    #> [1] 5

The structural richness compounds: you can analyze the whole system via the full dist.structure protocol (sampler, surv, system_signature, importance measures, dual, etc.).

## Substitution vs composition

- `substitute_component`: same topology, different component distribution. Use when exploring sensitivity to a single component’s parameters or swapping one family for another.
- `compose_systems`: richer topology by nesting. Use when the system has hierarchical structure (redundant subsystems, modular designs, networks of networks).

Both produce objects that participate in the full dist.structure protocol, so further substitutions and compositions compose cleanly.