---
title: "Advanced Series System Composition"
output:
  rmarkdown::html_vignette:
    toc: true
vignette: >
  %\VignetteIndexEntry{Advanced Series System Composition}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 4
)
```

## Mixed Component Types

Real systems often have failure modes with different aging characteristics. Consider a server with:

- **Hard disk** — Weibull wear-out (increasing hazard)
- **Memory** — exponential random failures (constant hazard)
- **Power supply** — Gompertz degradation (accelerating hazard)

```{r mixed-types}
library(serieshaz)

disk <- dfr_weibull(shape = 2, scale = 500)
mem  <- dfr_exponential(0.001)
psu  <- dfr_gompertz(a = 0.0001, b = 0.02)

server <- dfr_dist_series(list(disk, mem, psu))
```

### Hazard Decomposition

Each component contributes differently to system risk over time:

```{r hazard-decomp}
h_disk <- component_hazard(server, 1)
h_mem  <- component_hazard(server, 2)
h_psu  <- component_hazard(server, 3)
h_sys  <- hazard(server)

t_grid <- seq(10, 300, by = 10)
cat("Time | Disk     | Memory   | PSU      | System\n")
cat("-----|----------|----------|----------|--------\n")
for (t0 in c(50, 100, 150, 200, 250)) {
    cat(sprintf("%4d | %.6f | %.6f | %.6f | %.6f\n",
                t0, h_disk(t0), h_mem(t0), h_psu(t0), h_sys(t0)))
}
```

At $t = 50$, the constant memory hazard ($0.001$) is the largest contributor --- the Weibull ($0.0004$) and Gompertz ($0.0003$) are still small. Memory remains dominant through $t = 100$, but the Gompertz's exponential acceleration ($\propto e^{bt}$) overtakes it by around $t = 125\text{--}150$. By $t = 250$, the Gompertz contributes $0.015$ vs memory's constant $0.001$, dominating system risk. This crossover pattern is characteristic of mixed-type series systems: each failure mode has its "era" of dominance.

## Nested Series Systems

Since a `dfr_dist_series` is itself a `dfr_dist`, it can be used as a component in another series system. This enables hierarchical modeling.

### Example: Subsystem Composition

```{r nested}
# CPU subsystem: two exponential failure modes
cpu <- dfr_dist_series(list(
    dfr_exponential(0.001),  # thermal failure
    dfr_exponential(0.0005)  # electrical failure
))

# Storage subsystem: Weibull wear + exponential shock
storage <- dfr_dist_series(list(
    dfr_weibull(shape = 2, scale = 1000),
    dfr_exponential(0.0002)
))

# Full system: CPU + storage + power supply
full <- dfr_dist_series(list(
    cpu,
    storage,
    dfr_gompertz(a = 0.0001, b = 0.01)
))

cat("Full system components:", ncomponents(full), "\n")
cat("Total parameters:", length(params(full)), "\n")
```

### Verifying Nested Hazard

The nested system's hazard should equal the sum of all leaf-component hazards:

```{r nested-verify}
h_full <- hazard(full)

# Manually sum all leaf hazards
h_cpu1 <- component_hazard(cpu, 1)
h_cpu2 <- component_hazard(cpu, 2)
h_stor1 <- component_hazard(storage, 1)
h_stor2 <- component_hazard(storage, 2)
h_psu <- component_hazard(full, 3)

t0 <- 100
nested_sum <- h_cpu1(t0) + h_cpu2(t0) + h_stor1(t0) + h_stor2(t0) + h_psu(t0)
system_h <- h_full(t0)

cat(sprintf("Sum of leaf hazards: %.8f\n", nested_sum))
cat(sprintf("System hazard:       %.8f\n", system_h))
cat(sprintf("Difference:          %.2e\n", abs(nested_sum - system_h)))
```

## Custom Components

The `dfr_dist()` constructor lets you define components with arbitrary hazard functions. These can be used in series systems alongside the built-in distributions.

### Bathtub Curve Component

A bathtub-shaped hazard (high early, low middle, high late) can model products with infant mortality and wear-out:

```{r custom-component}
# Bathtub: h(t) = a/t^b + c*t^d  (infant mortality + wear-out)
bathtub <- dfr_dist(
    rate = function(t, par, ...) {
        a <- par[1]; b <- par[2]; c <- par[3]; d <- par[4]
        a * t^(-b) + c * t^d
    },
    par = c(0.5, 0.5, 0.0001, 2)
)

# Series system with bathtub + exponential random shock
sys <- dfr_dist_series(list(bathtub, dfr_exponential(0.001)))

h_sys <- hazard(sys)
for (t0 in c(1, 10, 50, 100, 200)) {
    cat(sprintf("t=%3d: h_sys = %.6f\n", t0, h_sys(t0)))
}
```

The bathtub shape is visible: high hazard at $t = 1$ ($\approx 0.50$, dominated by the infant mortality term $a/t^b$), dropping to a minimum at $t = 10$ ($\approx 0.17$), then rising steeply as the wear-out term $c \cdot t^d$ kicks in ($\approx 1.05$ at $t = 100$, $\approx 4.04$ at $t = 200$). The constant exponential shock ($0.001$) adds a negligible baseline floor.

### Effect on Cumulative Hazard

When a custom component doesn't provide `cum_haz_rate`, the series system falls back to numerical integration:

```{r custom-cumhaz}
cat("Analytical cum_haz available:", !is.null(sys$cum_haz_rate), "\n")

# The system still computes survival correctly via numerical integration
S <- surv(sys)
for (t0 in c(10, 50, 100)) {
    cat(sprintf("t=%3d: S(t) = %.6f\n", t0, S(t0)))
}
```

With this bathtub parameterization, the high infant mortality ($a = 0.5$, $b = 0.5$) drives the cumulative hazard so high that survival drops below 5% by $t = 10$. In practice, you would calibrate the bathtub parameters to match observed failure patterns. The numerical integration fallback handles these steep hazard functions correctly, just more slowly than the analytical path.

## Failure Attribution

`sample_components()` generates component-level lifetimes, enabling identification of which component caused each system failure.

```{r failure-attribution}
server <- dfr_dist_series(list(
    dfr_weibull(shape = 2, scale = 200),     # mechanical wear
    dfr_exponential(0.005),            # random shock
    dfr_gompertz(a = 0.0001, b = 0.02)       # degradation
))

set.seed(42)
n <- 10000
mat <- sample_components(server, n = n)

# System lifetime = minimum across components
t_sys <- apply(mat, 1, min)

# Identify the failing component for each observation
failing <- apply(mat, 1, which.min)
```

### Attribution Proportions

```{r attribution-props}
props <- table(failing) / n
names(props) <- c("Weibull (wear)", "Exp (shock)", "Gompertz (degrad.)")
cat("Failure attribution:\n")
print(round(props, 3))
```

### Attribution by Time Window

Failure causes change over the system's lifetime:

```{r attribution-time}
# Split into early, middle, and late failures
breaks <- quantile(t_sys, probs = c(0, 1/3, 2/3, 1))
period <- cut(t_sys, breaks = breaks, labels = c("Early", "Middle", "Late"),
              include.lowest = TRUE)

cat("\nAttribution by time period:\n")
for (p in levels(period)) {
    idx <- which(period == p)
    props_p <- table(factor(failing[idx], levels = 1:3)) / length(idx)
    cat(sprintf("  %s (n=%d): Wear=%.1f%% Shock=%.1f%% Degrad=%.1f%%\n",
                p, length(idx),
                100 * props_p[1], 100 * props_p[2], 100 * props_p[3]))
}
```

## Parameter Sensitivity

The parameter layout enables systematic sensitivity analysis: vary one component's parameters while holding others fixed.

```{r sensitivity}
server <- dfr_dist_series(list(
    dfr_weibull(shape = 2, scale = 100),
    dfr_exponential(0.01)
))

S_fn <- surv(server)
base_par <- params(server)  # c(2, 100, 0.01)
t0 <- 50

cat("Sensitivity of S(50) to Weibull scale parameter:\n")
for (scale_val in c(50, 75, 100, 125, 150)) {
    par_mod <- base_par
    par_mod[2] <- scale_val  # layout tells us par[2] is Weibull scale
    cat(sprintf("  scale = %3d: S(50) = %.4f\n", scale_val, S_fn(t0, par = par_mod)))
}

cat("\nSensitivity of S(50) to exponential rate:\n")
for (rate_val in c(0.005, 0.01, 0.02, 0.05, 0.1)) {
    par_mod <- base_par
    par_mod[3] <- rate_val  # layout tells us par[3] is exp rate
    cat(sprintf("  rate = %.3f: S(50) = %.4f\n", rate_val, S_fn(t0, par = par_mod)))
}
```
