---
title: "dist.structure integration"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{dist.structure integration}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
set.seed(1)
```

```{r setup}
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:

```{r eval = FALSE}
# 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:

```{r}
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)
dist.structure::reliability(sys_fitted, 0.8)
sapply(seq_len(model$m), function(j)
  dist.structure::structural_importance(sys_fitted, j))
```

**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.
