# dist.structure integration

    library(kofn)
    library(flexhaz)
    library(dist.structure)

## Two packages, two missions

`kofn` and `dist.structure` have complementary roles:

Concern | Package | Generics / constructors
--- | --- | ---
Topology and DGP for coherent systems | `dist.structure` | `coherent_dist`, `series_dist`, `parallel_dist`, `kofn_dist`, `bridge_dist`, `exp_kofn`, `wei_kofn`, `exp_series`, `wei_series`, …; `phi`, `min_paths`, `min_cuts`, `system_signature`, `critical_states`, `reliability`, `structural_importance`, `dual`, …
Inference for k-out-of-n | `kofn` | `kofn()` model, `loglik`, `score`, `hess_loglik`, `fit`, `rdata`; observation schemes; EM for Weibull; Scheme 1 / masked likelihoods; Fisher info comparison

Before v0.3.0, kofn bundled both, which meant 1500+ lines of topology/DGP code duplicated functionality that properly belongs to a distribution-algebra package. The v0.3.0 refactor delegated that work to dist.structure, shrinking kofn’s source by ~36% and focusing it on its distinctive contribution: inference under varied observation schemes.

## What kofn delegates to dist.structure

The general-k log-likelihood path computes per-observation contributions through dist.structure generics. Under the hood:

    # Pseudo-code of kofn's loglik.exp_kofn general-k path:
    function(df, par) {
      dgp <- dist.structure::exp_kofn(k_dist, par)  # k_dist = m - k_kofn + 1
      surv_fn <- algebraic.dist::surv(dgp)
      cdf_fn  <- algebraic.dist::cdf(dgp)
      dens_fn <- density(dgp)
      # ... then loop over observations, accumulating log contributions.
    }

Your code calling `loglik(kofn_model)(df, par)` doesn’t see any of this; the integration is invisible.

The parallel fast path (`k == m`) retains kofn’s inclusion-exclusion expansion because that closed form is specific to exponential parallel systems and faster than the general dist.structure density for that case. Cross-validation tests confirm both paths produce identical results.

## When you might invoke dist.structure directly

1. Post-fit analysis on the DGP. After you fit a kofn model, you have the estimated parameters. Construct a `dist.structure` DGP to exercise the full topology protocol:

    model <- kofn(k = 2, m = 3, component = dfr_exponential())
    df <- rdata(model)(theta = c(1, 2, 3), n = 100)
    result <- fit(model)(df, n_starts = 1)
    rates_hat <- coef(result)

    # Build the DGP from the fitted rates.
    sys_fitted <- dist.structure::exp_kofn(
      k = model$m - model$k + 1,   # convert to :G convention
      rates = rates_hat
    )

    # Query structural and reliability properties.
    dist.structure::system_signature(sys_fitted)
    #> [1] 0 1 0
    dist.structure::reliability(sys_fitted, 0.8)
    #> [1] 0.896
    sapply(seq_len(model$m), function(j)
      dist.structure::structural_importance(sys_fitted, j))
    #> [1] 0.5 0.5 0.5

2. Non-k-of-n topologies. kofn v0.3.0 removed the `system = ...` argument from the constructor. If you need to estimate a bridge or other arbitrary coherent system, use dist.structure directly: it provides `coherent_dist`, `bridge_dist`, `consecutive_k_dist`, and can evaluate loglik at user-supplied component distributions.

## Convention conversion

kofn uses the :F convention: `k_kofn` = number of failures that trigger system failure. `k=1` is series; `k=m` is parallel.

dist.structure uses :G: `k_dist` = number of functioning components required. `k=1` is parallel; `k=m` is series.

They convert via:

    k_dist = m - k_kofn + 1

Examples:

kofn shape | kofn k | dist.structure constructor
--- | --- | ---
Series (fails on 1st failure) | `k_kofn = 1` | `exp_kofn(k = m, ...)` or `exp_series(rates)`
Parallel (fails on m-th) | `k_kofn = m` | `exp_kofn(k = 1, ...)` or `exp_parallel(rates)`
2-of-3 (fails on 2nd of 3) | `k_kofn = 2, m = 3` | `exp_kofn(k = 2, rates = ...)`

The conversion is handled inside kofn whenever you pass a model through loglik/fit/rdata; you only need to remember it when calling dist.structure directly.

## Migration notes: v0.2.0 -> v0.3.0

If you used kofn 0.2.0, here are the removed APIs and their replacements:

Removed (v0.2.0) | Replacement (v0.3.0)
--- | ---
`parallel_system(m)` | `dist.structure::parallel_dist(components)`
`series_system(m)` | `dist.structure::series_dist(components)`
`kofn_system(k, m)` | `dist.structure::kofn_dist(k, components)` (:G conv)
`bridge_system()` | `dist.structure::bridge_dist(components)`
`consecutive_k_system(k, n)` | `dist.structure::consecutive_k_dist(k, components)`
`coherent_system(min_paths, m)` | `dist.structure::coherent_dist(min_paths, components, m)`
`kofn(system = ...)` | no longer supported; use dist.structure for non-k-of-n
`make_dists(par, family)` | `algebraic.dist::exponential(rate)` / `weibull_dist(shape, scale)`
`loglik_system(t, sys, par, family)` | `-sum(log(algebraic.dist::density(dist.structure::exp_kofn(...))(t)))` (or wei_kofn, or coherent_dist)
`fit_system(t, sys, family, ...)` | For k-of-n: `fit(kofn(k, m, component = dfr_exponential()))(df, ...)` (or `dfr_weibull()`). For general coherent: roll your own optimizer with dist.structure densities.
`rdata_system(sys, par, ...)` | `algebraic.dist::sampler(dgp)(n)` on the appropriate dist.structure object
`system_signature(sys)` | `dist.structure::system_signature(sys)`
`system_censoring(sys, times)` | `dist.structure::system_censoring(sys, times)` (note: returns `$system_time` and `$component_status`, not kofn’s `$T_sys`/`$critical`/`$status`)
`critical_states(sys, j)` | `dist.structure::critical_states(sys, j)`
`phi(sys, x)`, `min_paths`, `min_cuts` | `dist.structure::` equivalents
`f_sys_general`, `S_sys_general` | `algebraic.dist::density(dgp)` and `surv(dgp)`
`ie_expand`, `w_j_exact`, `w_j_integral`, `F_sys_exp`, `S_sys_exp` | Still exported from kofn (they’re kofn’s parallel-fast-path internals)

## Why this architecture

The reliability ecosystem now has a single source of truth for coherent-system DGP and topology (dist.structure). Downstream packages specialize:

- `serieshaz` implements the protocol on `dfr_dist_series` for arbitrary dynamic failure rate components.
- `maskedcauses` thin-wraps its exponential-series API over `dist.structure::exp_series`.
- `kofn` provides inference for the k-out-of-n family.

Any new reliability package that needs topology or DGP machinery should import dist.structure and add its specialized math layer on top, not reimplement what dist.structure already provides.