Observation Schemes: Resolving Information Asymmetry via Periodic Inspection
Source:vignettes/periodic-inspection.Rmd
periodic-inspection.RmdIntroduction
When we observe only the lifetime of a parallel system — the time until the last component fails — the system lifetime is a single number, but we want to recover individual component lifetime distributions. Components that fail quickly (“fast” components) have at the system failure time, contributing almost no curvature to the likelihood. Slow components dominate the system density and are estimated well. This information asymmetry is the central practical obstacle.
This vignette shows how periodic inspection — checking which components have failed at regular intervals — resolves this asymmetry at surprisingly low cost.
1. The observation scheme hierarchy
The kofn package defines three observation schemes,
ordered from least to most informative (Scheme 2 > Scheme 1
> Scheme 0):
| Scheme | What is observed | Constructor |
|---|---|---|
| Scheme 0 | System lifetime only (black box) | observe_right_censor() |
| Scheme 1 | System lifetime + periodic component inspections | observe_periodic(delta) |
| Scheme 2 | All component lifetimes exactly (complete data) | observe_exact() |
Scheme 1 occupies the practical middle ground. At each inspection time , we record which components have already failed. This requires only periodic access to the system.
# Scheme 0: system lifetime only (optionally right-censored at tau)
obs0 <- observe_right_censor(tau = 100)
obs0(42.7) # exact observation
#> $t
#> [1] 42.7
#>
#> $omega
#> [1] "exact"
#>
#> $t_upper
#> [1] NA
obs0(150.0) # right-censored at tau = 100
#> $t
#> [1] 100
#>
#> $omega
#> [1] "right"
#>
#> $t_upper
#> [1] NA
# Scheme 1: periodic inspection with interval width delta = 5
obs1 <- observe_periodic(delta = 5, tau = 100)
obs1(42.7) # failure in interval [40, 45)
#> $t
#> [1] 40
#>
#> $omega
#> [1] "interval"
#>
#> $t_upper
#> [1] 45
obs1(150.0) # right-censored
#> $t
#> [1] 100
#>
#> $omega
#> [1] "right"
#>
#> $t_upper
#> [1] NA
# Scheme 2: complete data (trivial)
obs2 <- observe_exact()
obs2(42.7) # exact observation, always
#> $t
#> [1] 42.7
#>
#> $omega
#> [1] "exact"
#>
#> $t_upper
#> [1] NA2. The information asymmetry problem
To see why Scheme 0 struggles, consider a two-component Weibull parallel system with parameters:
- Component 1 (fast): shape , scale
- Component 2 (slow): shape , scale
Component 1 has a median lifetime of about 1.4 time units; Component 2’s median is about 2.5. At the system failure time , the fast component has almost surely already failed — its CDF , so the likelihood surface is nearly flat with respect to Component 1’s parameters.
set.seed(42)
# True parameters: (shape_1, scale_1, shape_2, scale_2)
theta_true <- c(1.5, 2.0, 2.0, 3.0)
# Create the Weibull parallel system model (EM for Scheme 0)
model_em <- kofn(k = 2, m = 2, component = dfr_weibull(), method = "em")
# Generate Scheme 0 data (system lifetimes only)
gen <- rdata(model_em)
df0 <- gen(theta_true, n = 100)
cat("System lifetime summary:\n")
#> System lifetime summary:
summary(df0$t)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> 0.5859 1.9135 2.8543 3.0707 3.9466 8.2247
# At the median system lifetime, how much has each component's CDF accumulated?
t_med <- median(df0$t)
cat(sprintf("\nAt median system time %.2f:\n", t_med))
#>
#> At median system time 2.85:
cat(sprintf(" F_1(t) = %.4f (fast component: nearly saturated)\n",
pweibull(t_med, shape = 1.5, scale = 2.0)))
#> F_1(t) = 0.8182 (fast component: nearly saturated)
cat(sprintf(" F_2(t) = %.4f (slow component: still informative)\n",
pweibull(t_med, shape = 2.0, scale = 3.0)))
#> F_2(t) = 0.5955 (slow component: still informative)With , Component 1’s contribution to the system density is a tiny fraction of the total, and the likelihood carries almost no information about and . Fitting under Scheme 0 confirms this:
# Fit under Scheme 0 using the EM algorithm
solver <- fit(model_em)
mle0 <- solver(df0, n_starts = 1L)
est0 <- coef(mle0)
cat("Scheme 0 estimates vs truth:\n")
#> Scheme 0 estimates vs truth:
cat(sprintf(" Component 1 (fast): shape = %.3f (true: 1.500), scale = %.3f (true: 2.000)\n",
est0[1], est0[2]))
#> Component 1 (fast): shape = 3.067 (true: 1.500), scale = 1.496 (true: 2.000)
cat(sprintf(" Component 2 (slow): shape = %.3f (true: 2.000), scale = %.3f (true: 3.000)\n",
est0[3], est0[4]))
#> Component 2 (slow): shape = 1.879 (true: 2.000), scale = 3.345 (true: 3.000)
# Standard errors from the variance-covariance matrix
se_vec <- tryCatch({
V <- vcov(mle0)
if (is.null(V)) rep(NA_real_, length(coef(mle0))) else sqrt(diag(V))
}, error = function(e) rep(NA_real_, length(coef(mle0))))
if (!any(is.na(se_vec))) {
cat(sprintf("\n SE(shape_1) = %.3f, SE(shape_2) = %.3f\n",
se_vec[1], se_vec[3]))
cat(sprintf(" SE ratio (fast/slow): %.1fx\n", se_vec[1] / se_vec[3]))
}
#>
#> SE(shape_1) = 0.879, SE(shape_2) = 0.190
#> SE ratio (fast/slow): 4.6xThe SE ratio reflects a genuine information gap, not an algorithm deficiency.
3. Scheme 1: Periodic inspection
Under Scheme 1, we supplement system-level observation with periodic inspections. At each inspection time (for ), we check each component and record whether it has failed. This localizes each component’s failure time to an inspection interval where .
The likelihood
The likelihood for a single observation under Scheme 1 combines two pieces of information:
where is the parallel system density and is the inspection interval containing component ’s failure time.
Even a wide interval provides information: knowing that the fast
component failed in
versus
constrains the hazard function in a way that the system-level
observation alone cannot. The kofn package provides
dedicated functions for Scheme 1:
# Generate data under Scheme 1 with delta = 0.5
model_wei <- kofn(k = 2, m = 2, component = dfr_weibull())
gen_s1 <- rdata_scheme1(model_wei)
df1 <- gen_s1(theta_true, n = 100, delta = 0.5)
cat("Scheme 1 data (first 6 observations):\n")
#> Scheme 1 data (first 6 observations):
print(head(df1))
#> t comp_lower_1 comp_upper_1 comp_lower_2 comp_upper_2
#> 1 2.556432 0.0 0.5 2.5 3.0
#> 2 2.701081 1.5 2.0 2.5 3.0
#> 3 5.026235 0.5 1.0 5.0 5.5
#> 4 3.169580 1.5 2.0 3.0 3.5
#> 5 3.009575 3.0 3.5 1.0 1.5
#> 6 1.746040 1.5 2.0 0.5 1.0
cat("\nColumn meanings:\n")
#>
#> Column meanings:
cat(" t: exact system failure time\n")
#> t: exact system failure time
cat(" comp_lower_j: lower bound of component j's inspection interval\n")
#> comp_lower_j: lower bound of component j's inspection interval
cat(" comp_upper_j: upper bound of component j's inspection interval\n")
#> comp_upper_j: upper bound of component j's inspection interval
# Fit under Scheme 1
solver_s1 <- fit_scheme1(model_wei)
mle1 <- solver_s1(df1, n_starts = 1L)
est1 <- coef(mle1)
cat("Scheme 1 (delta = 0.5) estimates vs truth:\n")
#> Scheme 1 (delta = 0.5) estimates vs truth:
cat(sprintf(" Component 1 (fast): shape = %.3f (true: 1.500), scale = %.3f (true: 2.000)\n",
est1[1], est1[2]))
#> Component 1 (fast): shape = 1.641 (true: 1.500), scale = 2.243 (true: 2.000)
cat(sprintf(" Component 2 (slow): shape = %.3f (true: 2.000), scale = %.3f (true: 3.000)\n",
est1[3], est1[4]))
#> Component 2 (slow): shape = 1.944 (true: 2.000), scale = 2.942 (true: 3.000)4. Scheme 0 vs Scheme 1 comparison
The real test is a controlled comparison using the same true parameters. We generate data from a common set of component lifetimes and fit under both schemes.
set.seed(123)
n <- 100
m <- 2
alpha_true <- c(1.5, 2.0)
beta_true <- c(2.0, 3.0)
theta_true <- c(alpha_true[1], beta_true[1], alpha_true[2], beta_true[2])
delta <- 0.5
# Generate component lifetimes directly
comp_times <- matrix(0, nrow = n, ncol = m)
for (j in seq_len(m)) {
comp_times[, j] <- rweibull(n, shape = alpha_true[j], scale = beta_true[j])
}
sys_times <- apply(comp_times, 1, max)
# --- Scheme 0: system lifetime only ---
df_s0 <- data.frame(t = sys_times)
model_em <- kofn(k = 2, m = 2, component = dfr_weibull(), method = "em")
solver_em <- fit(model_em)
mle_s0 <- solver_em(df_s0, n_starts = 1L)
# --- Scheme 1: add inspection intervals ---
df_s1 <- data.frame(t = sys_times)
for (j in seq_len(m)) {
df_s1[[paste0("comp_lower_", j)]] <- floor(comp_times[, j] / delta) * delta
df_s1[[paste0("comp_upper_", j)]] <- floor(comp_times[, j] / delta) * delta + delta
}
model_mle <- kofn(k = 2, m = 2, component = dfr_weibull(), method = "mle")
solver_s1 <- fit_scheme1(model_mle)
mle_s1 <- solver_s1(df_s1, n_starts = 1L)
# --- Display results ---
cat("=== Single-sample comparison (n = 100) ===\n\n")
#> === Single-sample comparison (n = 100) ===
results <- data.frame(
Parameter = c("shape_1", "scale_1", "shape_2", "scale_2"),
Truth = theta_true,
Scheme_0 = round(coef(mle_s0), 3),
Scheme_1 = round(coef(mle_s1), 3)
)
results$Error_S0 <- round(abs(results$Scheme_0 - results$Truth), 3)
results$Error_S1 <- round(abs(results$Scheme_1 - results$Truth), 3)
print(results, row.names = FALSE)
#> Parameter Truth Scheme_0 Scheme_1 Error_S0 Error_S1
#> shape_1 1.5 0.721 1.442 0.779 0.058
#> scale_1 2.0 0.780 2.004 1.220 0.004
#> shape_2 2.0 3.188 2.425 1.188 0.425
#> scale_2 3.0 3.095 2.850 0.095 0.150Monte Carlo comparison
# Monte Carlo comparison: run_long = TRUE to execute
set.seed(2024)
n_rep <- 200
n <- 300
delta <- 0.5
est_s0 <- matrix(NA, nrow = n_rep, ncol = 4)
est_s1 <- matrix(NA, nrow = n_rep, ncol = 4)
conv_s0 <- logical(n_rep)
conv_s1 <- logical(n_rep)
for (r in seq_len(n_rep)) {
# Generate common component lifetimes
comp_times <- matrix(0, nrow = n, ncol = m)
for (j in seq_len(m)) {
comp_times[, j] <- rweibull(n, shape = alpha_true[j], scale = beta_true[j])
}
sys_times <- apply(comp_times, 1, max)
# Scheme 0
df_s0 <- data.frame(t = sys_times)
res0 <- tryCatch(solver_em(df_s0, n_starts = 3L),
error = function(e) NULL)
if (!is.null(res0) && res0$converged) {
est_s0[r, ] <- coef(res0)
conv_s0[r] <- TRUE
}
# Scheme 1
df_s1 <- data.frame(t = sys_times)
for (j in seq_len(m)) {
df_s1[[paste0("comp_lower_", j)]] <- floor(comp_times[, j] / delta) * delta
df_s1[[paste0("comp_upper_", j)]] <- floor(comp_times[, j] / delta) * delta + delta
}
res1 <- tryCatch(solver_s1(df_s1, n_starts = 3L),
error = function(e) NULL)
if (!is.null(res1) && res1$converged) {
est_s1[r, ] <- coef(res1)
conv_s1[r] <- TRUE
}
if (r %% 50 == 0) message(sprintf(" Replicate %d / %d", r, n_rep))
}
# Compute RMSE for converged replicates
rmse <- function(est, truth) {
valid <- !is.na(est)
sqrt(mean((est[valid] - truth)^2))
}
cat("\n=== Monte Carlo RMSE (n = 300, n_rep = 200) ===\n\n")
param_names <- c("shape_1", "scale_1", "shape_2", "scale_2")
for (p in seq_along(theta_true)) {
r0 <- rmse(est_s0[, p], theta_true[p])
r1 <- rmse(est_s1[, p], theta_true[p])
cat(sprintf(" %-8s Scheme 0: %.4f Scheme 1: %.4f ratio: %.1fx\n",
param_names[p], r0, r1, r0 / r1))
}
cat(sprintf("\n Convergence: Scheme 0 = %d/%d, Scheme 1 = %d/%d\n",
sum(conv_s0), n_rep, sum(conv_s1), n_rep))
# Pre-computed results from a 200-replicate Monte Carlo run:
cat("=== Monte Carlo RMSE (n = 300, n_rep = 200, pre-computed) ===\n\n")
#> === Monte Carlo RMSE (n = 300, n_rep = 200, pre-computed) ===
cat(" Parameter Scheme 0 Scheme 1 Ratio (S0/S1)\n")
#> Parameter Scheme 0 Scheme 1 Ratio (S0/S1)
cat(" -------- -------- -------- -------------\n")
#> -------- -------- -------- -------------
cat(" shape_1 0.6121 0.0291 21.0x\n")
#> shape_1 0.6121 0.0291 21.0x
cat(" scale_1 0.8434 0.0847 10.0x\n")
#> scale_1 0.8434 0.0847 10.0x
cat(" shape_2 0.0893 0.0409 2.2x\n")
#> shape_2 0.0893 0.0409 2.2x
cat(" scale_2 0.1567 0.0721 2.2x\n")
#> scale_2 0.1567 0.0721 2.2x
cat("\n The fast component (shape_1) sees a 21x RMSE improvement.\n")
#>
#> The fast component (shape_1) sees a 21x RMSE improvement.
cat(" The slow component improves ~2x --- already well-estimated under Scheme 0.\n")
#> The slow component improves ~2x --- already well-estimated under Scheme 0.The pattern is clear: periodic inspection disproportionately benefits the worst-estimated components. The fast component’s shape RMSE drops by a factor of roughly 21, while the slow component (already well-estimated) sees a modest 2x improvement.
5. Sensitivity to inspection interval width
A natural concern is whether the benefit of Scheme 1 depends on having a fine inspection grid. It does not. The improvement is remarkably insensitive to the interval width .
# Sensitivity analysis: delta in {0.1, 0.5, 1.0, 2.0}
set.seed(2024)
deltas <- c(0.1, 0.5, 1.0, 2.0)
n_rep_sens <- 100
n <- 300
rmse_by_delta <- matrix(NA, nrow = length(deltas), ncol = 4)
rownames(rmse_by_delta) <- paste0("delta=", deltas)
colnames(rmse_by_delta) <- c("shape_1", "scale_1", "shape_2", "scale_2")
for (d_idx in seq_along(deltas)) {
delta_d <- deltas[d_idx]
est_d <- matrix(NA, nrow = n_rep_sens, ncol = 4)
for (r in seq_len(n_rep_sens)) {
comp_times <- matrix(0, nrow = n, ncol = m)
for (j in seq_len(m)) {
comp_times[, j] <- rweibull(n, shape = alpha_true[j], scale = beta_true[j])
}
sys_times <- apply(comp_times, 1, max)
df_d <- data.frame(t = sys_times)
for (j in seq_len(m)) {
df_d[[paste0("comp_lower_", j)]] <- floor(comp_times[, j] / delta_d) * delta_d
df_d[[paste0("comp_upper_", j)]] <- floor(comp_times[, j] / delta_d) * delta_d + delta_d
}
res_d <- tryCatch(solver_s1(df_d, n_starts = 3L),
error = function(e) NULL)
if (!is.null(res_d) && res_d$converged) {
est_d[r, ] <- coef(res_d)
}
}
for (p in 1:4) {
rmse_by_delta[d_idx, p] <- rmse(est_d[, p], theta_true[p])
}
message(sprintf(" delta = %.1f complete", delta_d))
}
cat("\n=== RMSE by inspection interval width ===\n\n")
print(round(rmse_by_delta, 4))
cat("=== RMSE by inspection interval width (pre-computed) ===\n\n")
#> === RMSE by inspection interval width (pre-computed) ===
cat(" delta shape_1 scale_1 shape_2 scale_2\n")
#> delta shape_1 scale_1 shape_2 scale_2
cat(" ----- ------- ------- ------- -------\n")
#> ----- ------- ------- ------- -------
cat(" 0.1 0.0195 0.0583 0.0380 0.0685\n")
#> 0.1 0.0195 0.0583 0.0380 0.0685
cat(" 0.5 0.0291 0.0847 0.0409 0.0721\n")
#> 0.5 0.0291 0.0847 0.0409 0.0721
cat(" 1.0 0.0437 0.1102 0.0425 0.0748\n")
#> 1.0 0.0437 0.1102 0.0425 0.0748
cat(" 2.0 0.0812 0.1654 0.0461 0.0789\n")
#> 2.0 0.0812 0.1654 0.0461 0.0789
cat("\n")
cat(" Even delta = 2.0 (coarser than the median component lifetime)\n")
#> Even delta = 2.0 (coarser than the median component lifetime)
cat(" gives shape_1 RMSE of 0.08 vs 0.61 under Scheme 0: a 7.5x improvement.\n")
#> gives shape_1 RMSE of 0.08 vs 0.61 under Scheme 0: a 7.5x improvement.The key finding: even very coarse inspection intervals provide substantial improvement. Going from to degrades the fast component’s RMSE by only a factor of 4, while the gap between Scheme 0 and any Scheme 1 variant remains an order of magnitude.
6. Information-theoretic interpretation
Under Scheme 0, the fast component’s information comes through , but makes this nearly constant with respect to Component 1’s parameters — a flat likelihood surface. Under Scheme 1, the interval-censoring term varies meaningfully with the parameters even for the fast component: two inspection intervals suffice to distinguish increasing from decreasing hazard.
# Demonstrate: how much does F_j(a+) - F_j(a-) vary with shape?
t_fail <- 1.0 # a typical failure time for the fast component
delta <- 0.5
a_lower <- floor(t_fail / delta) * delta
a_upper <- a_lower + delta
shapes_grid <- seq(0.5, 3.0, by = 0.1)
probs <- sapply(shapes_grid, function(alpha) {
pweibull(a_upper, shape = alpha, scale = 2.0) -
pweibull(a_lower, shape = alpha, scale = 2.0)
})
plot(shapes_grid, probs, type = "l", lwd = 2,
xlab = expression(alpha[1] ~ "(shape parameter)"),
ylab = expression(F[1](a^"+") - F[1](a^"-")),
main = "Interval probability varies with shape",
sub = sprintf("Interval [%.1f, %.1f), scale = 2.0", a_lower, a_upper))
abline(v = 1.5, lty = 2, col = "red")
text(1.5, max(probs) * 0.9, expression(alpha[1] == 1.5 ~ "(truth)"),
pos = 4, col = "red")
The interval probability has substantial curvature as a function of the shape parameter. This is precisely the information that Scheme 0 lacks — and that Scheme 1 provides for free.
7. Fisher information comparison
The compare_fisher_info() function quantifies the total
information content of each scheme using the determinant of the observed
Fisher information matrix. The determinant ratio
is a scalar summary of relative efficiency, with values less than 1
indicating that Scheme
is more informative.
fi <- compare_fisher_info(
shapes = c(1.5, 2.0),
scales = c(2.0, 3.0),
n = 200, delta = 0.5, n_rep = 30,
component = dfr_weibull()
)
cat("=== Fisher Information Determinant Comparison ===\n\n")
cat(sprintf(" Median det(I), Scheme 0: %.4e\n", fi$median_det["scheme0"]))
cat(sprintf(" Median det(I), Scheme 1: %.4e\n", fi$median_det["scheme1"]))
cat(sprintf(" Median det(I), Scheme 2: %.4e\n", fi$median_det["scheme2"]))
cat(sprintf("\n Efficiency ratios:\n"))
cat(sprintf(" Scheme 0 / Scheme 1 = %.4f (Scheme 1 is %.1fx more informative)\n",
fi$efficiency_01, 1 / fi$efficiency_01))
cat(sprintf(" Scheme 1 / Scheme 2 = %.4f (Scheme 1 recovers %.0f%% of complete-data info)\n",
fi$efficiency_12, fi$efficiency_12 * 100))
cat(sprintf(" Scheme 0 / Scheme 2 = %.6f\n", fi$efficiency_02))
cat(sprintf("\n Valid replicates: Scheme 0 = %d, Scheme 1 = %d, Scheme 2 = %d\n",
fi$n_valid["scheme0"], fi$n_valid["scheme1"], fi$n_valid["scheme2"]))
cat("=== Fisher Information Determinant Comparison (pre-computed) ===\n\n")
#> === Fisher Information Determinant Comparison (pre-computed) ===
cat(" Median det(I), Scheme 0: 3.21e+04\n")
#> Median det(I), Scheme 0: 3.21e+04
cat(" Median det(I), Scheme 1: 1.89e+07\n")
#> Median det(I), Scheme 1: 1.89e+07
cat(" Median det(I), Scheme 2: 3.15e+07\n")
#> Median det(I), Scheme 2: 3.15e+07
cat("\n Efficiency ratios:\n")
#>
#> Efficiency ratios:
cat(" Scheme 0 / Scheme 1 = 0.0017 (Scheme 1 is ~590x more informative)\n")
#> Scheme 0 / Scheme 1 = 0.0017 (Scheme 1 is ~590x more informative)
cat(" Scheme 1 / Scheme 2 = 0.60 (Scheme 1 recovers ~60% of complete-data info)\n")
#> Scheme 1 / Scheme 2 = 0.60 (Scheme 1 recovers ~60% of complete-data info)
cat(" Scheme 0 / Scheme 2 = 0.0010\n")
#> Scheme 0 / Scheme 2 = 0.0010
cat("\n Scheme 1 with delta = 0.5 closes most of the gap between black-box\n")
#>
#> Scheme 1 with delta = 0.5 closes most of the gap between black-box
cat(" observation and complete monitoring, using only periodic access.\n")
#> observation and complete monitoring, using only periodic access.Scheme 0 retains only ~0.1% of complete-data information (dominated by near-total loss for the fast component), while Scheme 1 with recovers ~60% — a three-order-of-magnitude improvement. The remaining 40% gap reflects the intrinsic cost of interval censoring.
8. Scheme 1 across the k-spectrum
The analysis above focused on parallel systems (). Does periodic inspection help at other values of ? We compare Scheme 0 vs Scheme 1 for a 4-component exponential system across the full k-spectrum.
set.seed(42)
rates <- c(1.0, 0.8, 0.6, 0.4)
rates_sorted <- sort(rates)
n <- 50
k_results <- data.frame(
k = 2:4,
type = c("2-of-4", "3-of-4", "parallel"),
s0_mean_err = NA_real_,
s1_mean_err = NA_real_,
improvement = NA_character_
)
for (idx in seq_len(nrow(k_results))) {
k <- k_results$k[idx]
model_k <- kofn(k = k, m = 4, component = dfr_exponential())
# Scheme 0: system lifetime only
gen0 <- rdata(model_k)
dat0 <- gen0(theta = rates, n = n)
fitter0 <- fit(model_k)
res0 <- fitter0(dat0, n_starts = 1)
# Scheme 1: periodic inspection (delta = 0.5)
s1gen <- rdata_scheme1(model_k)
dat1 <- s1gen(theta = rates, n = n, delta = 0.5)
fitter1 <- fit_scheme1(model_k)
res1 <- fitter1(dat1, n_starts = 1)
if (res0$converged) {
k_results$s0_mean_err[idx] <- round(
mean(abs(sort(coef(res0)) - rates_sorted)), 3)
}
if (res1$converged) {
k_results$s1_mean_err[idx] <- round(
mean(abs(sort(coef(res1)) - rates_sorted)), 3)
}
if (!is.na(k_results$s0_mean_err[idx]) && !is.na(k_results$s1_mean_err[idx])) {
ratio <- k_results$s0_mean_err[idx] / k_results$s1_mean_err[idx]
k_results$improvement[idx] <- sprintf("%.0fx", ratio)
}
}
k_results
#> k type s0_mean_err s1_mean_err improvement
#> 1 2 2-of-4 54.922 0.057 964x
#> 2 3 3-of-4 0.200 0.080 2x
#> 3 4 parallel 0.220 0.076 3xPeriodic inspection consistently provides a large improvement for . The composite likelihood (system density component interval contributions) is effective here because all failed components have failure times within the observation window.
Note: Scheme 1 is not shown for
(series) because the composite likelihood breaks down — at
system failure,
components are still alive, and their unconditional interval
probabilities create a misleading likelihood surface. For series
systems, the maskedcauses package (with candidate cause
information) is the appropriate tool.
9. Practical guidelines
The results in this vignette point to clear practical recommendations:
When to use periodic inspection. Whenever component-level parameter estimates are needed and continuous monitoring is infeasible. The information gain from even coarse inspection is dramatic — particularly for components whose lifetimes are short relative to the system.
Choosing the inspection interval. The choice of is forgiving. A reasonable rule of thumb: set to be roughly the median component lifetime. Even at this coarse resolution, the RMSE improvement over Scheme 0 is typically an order of magnitude for the worst-estimated parameters. Finer inspection helps, but with diminishing returns.
When Scheme 0 suffices. If all components have similar lifetime distributions (homogeneous or near-homogeneous system), the information asymmetry is mild, and Scheme 0 may provide adequate estimates. The problem is acute when component lifetimes span a wide range.
The cost-benefit tradeoff. Periodic inspection has real operational cost, but a single inspection during the system’s lifetime is better than none, and a few inspections capture most of the available information. The case for periodic inspection is strong: it transforms an ill-conditioned estimation problem into a well-conditioned one at modest cost.