Skip to contents

Overview

Empirical Quadrature Calibration (EQC) is the primary algorithm in IRTsimrel for solving the inverse reliability problem. Given a target marginal reliability \rho^*, EQC finds a global discrimination scaling factor c^* such that a fixed-form empirical approximation to population average-information reliability equals the target.

Reading time: approximately 25 minutes.

Prerequisites: This vignette assumes familiarity with the mathematical foundations developed in vignette("theory-reliability"). For applied usage tutorials and workflows, see vignette("introduction").

Key references: Lee (2026, arXiv:2512.16012v2), Sections 2.3–2.4; Brent (1973).

Property Benefit
Deterministic Same inputs always produce same output (given seed)
Explicit topology Detects multiple roots and local extrema before selection
No tuning parameters No step sizes, learning rates, or burn-in to configure
Verified bracketing Ordinary sign-changing crossings are locally polished when the bracket solver succeeds
Reproducible Fixed theta nodes and item form for a given seed

Algorithm Statement

EQC solves the scalar root-finding problem \hat{\rho}_M(c) = \rho^* using a three-step procedure.

Formal Algorithm

Algorithm 1 (Empirical Quadrature Calibration).

Input: Target reliability \rho^*, number of items I, latent distribution G, item parameter distribution H, quadrature size M, search bounds [c_L, c_U], tolerance \varepsilon.

Step 1 (Quadrature Sampling). Draw \{\theta_m\}_{m=1}^M \sim G and \{(\beta_i, \lambda_i^{(0)})\}_{i=1}^I \sim H. Fix these samples for all subsequent evaluations.

Step 2 (Empirical Reliability Function). Define \hat{\rho}_M : (0, \infty) \to (0, 1) by:

\hat{\rho}_M(c) = \rho\bigl(c;\, \{\theta_m\}_{m=1}^M,\, \boldsymbol{\beta},\, c \cdot \boldsymbol{\lambda}^{(0)}\bigr)

using the bracket-checked average-information reliability \tilde{\rho} (reliability_metric = "info" / "tilde").

Step 3 (Topology, Polishing, and Selection). Scan x=\log(c) adaptively on the exact interval [c_L,c_U], refine detected extrema and target features, polish every sign-changing bracket with Brent’s method, and select a root with root_policy. The default selects the lowest increasing crossing.

Output: Calibrated scaling factor \hat{c}^*_M, achieved reliability \hat{\rho}_M(\hat{c}^*_M), calibrated item parameters.

Why Fixed Quadrature?

A critical design choice is that the same Monte Carlo samples \{\theta_m\} and \{(\beta_i, \lambda_i^{(0)})\} are used for every evaluation of \hat{\rho}_M(c) during the root-finding iteration. This makes \hat{\rho}_M(c) a deterministic function of c for a given draw, which:

  1. Makes every scanner and root-polishing evaluation reproducible.
  2. Eliminates the need for step size tuning.
  3. Produces reproducible results for a given seed.

The trade-off is that the solution \hat{c}^*_M depends on the particular quadrature draw, introducing Monte Carlo error of order O(1/\sqrt{M}).

Adaptive Topology Scan and Brent Polishing

EQC does not call one global root solver and assume monotonicity. It first constructs an adaptive log-scale map of the configured interval. Boundaries, detected extrema, crossings, tangencies, and plateaus are stored in result$misc$topology. Brent’s method (Brent, 1973) is then attempted locally for each sign-changing bracket. Successfully polished ordinary crossings retain their solver diagnostic; a near-target grid fallback is recorded separately when polishing fails.

The interval is never expanded automatically. Topology is described as “detected at the configured resolution” because no finite grid can guarantee detection of an arbitrarily narrow feature between all sampled points.

Components of Brent’s Method

Brent’s method adaptively selects among three strategies at each iteration:

  1. Bisection: Takes the midpoint of the current bracketing interval [a, b]. Always converges but is slow (linear rate).

  2. Secant method: Uses linear interpolation between the two most recent points. Superlinear convergence rate of approximately \varphi \approx 1.618 (the golden ratio), but not guaranteed to stay within bounds.

  3. Inverse quadratic interpolation (IQI): Fits a quadratic through the three most recent points (in the inverse direction). Under the usual smoothness and simple-root conditions, pure IQI has asymptotic order p \approx 1.8393, the positive root of p^3 = p^2 + p + 1.

At each step, Brent’s method:

  • Attempts IQI or secant first (for speed).
  • Falls back to bisection if the superlinear step would leave the bracket or make insufficient progress.

Convergence Properties

Proposition (Bracket contraction). Given a continuous function g on [a,b] with g(a)g(b)<0, the bisection safeguard can reduce the root-containing bracket to a requested scale-width tolerance in:

O\!\left(\log_2\!\left(\frac{b-a}{\varepsilon_c}\right)\right) \text{ iterations (worst-case bracket contraction)}

Here \varepsilon_c is a tolerance on root location or bracket width, not an automatic bound on |g(c)|. Continuity implies the residual tends to zero as the bracket contracts around a root, but a finite residual guarantee requires additional information such as a modulus of continuity or a local Lipschitz bound.

In practice, accepted interpolation steps often converge much faster than pure bisection. For pure IQI, the local error follows a three-step product recurrence under regularity conditions, yielding the superlinear order p \approx 1.8393 above. Brent’s hybrid retains the bisection safeguard; it does not have quadratic or doubly exponential convergence as a general guarantee.

Why Brent’s Method is Used Locally

Several properties of the reliability function make Brent’s method particularly well-suited:

  1. Bracketed root: Every ordinary crossing passed to uniroot() already has a verified local sign change. Several brackets may coexist.

  2. Smooth function: \hat{\rho}_M(c) is infinitely differentiable in c, enabling fast superlinear convergence.

  3. No derivative needed: Unlike Newton’s method, Brent’s method does not require computing \partial \hat{\rho}_M / \partial c, avoiding the complexity of differentiating through the Monte Carlo sum.

  4. Robustness: The bisection fallback prevents divergence even when the function has near-zero slope (high or low reliability regions).

Root policies and statuses

root_policy = "lowest_increasing" is the scientific default: among detected positive-slope crossings it chooses the smallest scale, avoiding an implicit switch to a high-scale saturation branch. "nearest_increasing" uses c=1 as its reference. "lowest_any" and "highest_any" are expert policies that can select decreasing crossings; tangencies and plateaus require separate explicit opt-in.

Feasibility, topology quality, root kind/direction, and calibration outcome are stored as separate fields. A boundary hit is not mislabeled as an interior root. If the target is outside the detected reliability range, EQC returns the best-achievable scale with an infeasibility warning. If a feasible target has no root admissible under the policy, or the topology is non-finite/unresolved, EQC raises a classed error.

Convergence Theory

The theoretical properties of EQC are established in Lee (2026), Appendix A. Here we state the main results with proof sketches. For the full notation and definitions, see vignette("theory-reliability").

Consistency (Theorem A.1)

Theorem (Branch-conditional consistency of EQC). Let c^* be an isolated population solution \tilde{\rho}(c^*) = \rho^* on a specified increasing branch, and let \hat{c}^*_M be the matching EQC root with quadrature size M. Then, under the regularity and root-separation conditions below:

\hat{c}^*_M \xrightarrow{\text{a.s.}} c^* \quad \text{as } M \to \infty

Proof sketch. The argument proceeds in three steps:

  1. Uniform convergence: By the Uniform Law of Large Numbers (ULLN), the empirical reliability function converges uniformly over the compact set [c_L, c_U]: \sup_{c \in [c_L, c_U]} |\hat{\rho}_M(c) - \rho(c)| \xrightarrow{\text{a.s.}} 0 This requires verifying that the family \{h_c(\theta, \boldsymbol{\beta}, \boldsymbol{\lambda})\}_{c \in [c_L, c_U]} satisfies a Lipschitz or bounded variation condition in c, which follows from the smoothness of the logistic function.

  2. Local uniqueness of the root: On the population target branch described in Proposition 1 in vignette("theory-reliability"), the equation \rho(c) = \rho^* has one isolated solution c^*. Other branches may contain other roots. The finite empirical implementation enumerates local brackets and applies the stated selection rule.

  3. Root convergence: Combining uniform convergence of the objective with uniqueness of the root, the Argmax Continuous Mapping Theorem (or its zero-finding analogue) yields \hat{c}^*_M \to c^* a.s.

Asymptotic Normality (Theorem A.2)

Theorem (Asymptotic Normality of EQC). Under regularity conditions:

\sqrt{M}\,(\hat{c}^*_M - c^*) \xrightarrow{d} N\!\left(0,\, \frac{\sigma^2_g}{g'(c^*)^2}\right)

where g(c) = \rho(c) - \rho^*, g'(c^*) = \rho'(c^*) is the slope of the reliability function at the solution, and \sigma^2_g is the asymptotic variance of \hat{g}_M(c^*).

Proof sketch. Apply the delta method (implicit function theorem version). By the CLT:

\sqrt{M}\,\hat{g}_M(c^*) \xrightarrow{d} N(0, \sigma^2_g)

Since \hat{c}^*_M solves \hat{g}_M(\hat{c}^*_M) = 0, a Taylor expansion around c^* gives:

0 = \hat{g}_M(\hat{c}^*_M) \approx \hat{g}_M(c^*) + g'(c^*)\,(\hat{c}^*_M - c^*)

Solving for \hat{c}^*_M - c^* and scaling by \sqrt{M} yields the result. The key requirement is g'(c^*) \neq 0, supplied by strict local monotonicity on the selected branch.

Monte Carlo Error Analysis

Practical implications. The asymptotic normality result (above) implies that the Monte Carlo standard error of \hat{c}^*_M is:

\text{SE}(\hat{c}^*_M) \approx \frac{\sigma_g}{\sqrt{M}\, |g'(c^*)|}

The reliability function’s slope |g'(c^*)| acts as an amplification factor: steeper slopes (smaller c^*, moderate reliability targets) produce smaller estimation errors, while flat slopes (extreme reliability targets near 0 or 1) produce larger errors.

For the reliability estimate itself, by the delta method:

|\hat{\rho}_M(\hat{c}^*_M) - \rho^*| = O_p\!\left(\frac{1}{\sqrt{M}}\right)

Numerical Stability

Stable log-information reductions

The probability and information kernels are evaluated with stable logistic identities, including extreme linear predictors. Test information is reduced on the log scale and MSEM uses log-sum-exp arithmetic. The implementation does not impose a 1e-10 information floor: clipping would turn a divergent or under-resolved reciprocal-information estimand into a plausible-looking finite number. Non-finite diagnostics are surfaced instead.

Search Bound Sensitivity

The choice of [c_L, c_U] affects both feasibility and numerical behavior:

  • Too narrow: The target \rho^* may fall outside the detected achievable range on $[c_L, c_U]`; EQC returns the best-achievable point with an explicit status and warning.
  • Too wide: Finite-grid saturation can create additional extrema and crossings even for the information metric. The scanner enumerates them, but a wider interval costs more evaluations and makes the root policy important.

Default recommendation: c_bounds = c(0.3, 3) works well for most applications. Extend to c(0.1, 5) or c(0.1, 10) for high-reliability targets (\rho^* > 0.90) or unusual item/latent configurations.

Edge Cases

Scenario Behavior Recommendation
Target outside detected range Returns best-achievable c with warning/status Inspect misc$topology; revise design or bounds
Several admissible roots Selects by root_policy and records all roots Report policy and selected direction
Tangency or plateau only No default admissible root Opt in only with a scientific justification
Scan budget/non-finite gap Classed topology error Inspect controls and estimand integrability
n_items = 1 Very flat reliability curve Use larger M for precision

Reliability Metric Comparison

EQC targets the average-information reliability metric. The MSEM-based metric is still important for interpretation and cross-evaluation, but direct "msem" / "bar" targeting is handled by SAC because the MSEM objective can be non-monotone under scalar root finding.

Average-Information (rho-tilde): reliability_metric = "info"

Advantages:

  • Stable on practical EQC search intervals; on wider finite empirical bounds, EQC scans and refines all detected extrema before declaring infeasibility.
  • Faster convergence of Brent’s method (steeper slope in typical range).
  • Recommended by Lee (2026) as the default.

Interpretation: The coefficient obtained by applying the reliability transform to average test information. On the same finite information grid, Jensen’s inequality makes it an upper bound on the MSEM-based coefficient. The two are distinct estimands; this ordering is not a bias claim that one is the universal "true" reliability.

MSEM-Based (w-bar): cross-evaluation, not EQC targeting

Advantages:

  • MSEM-based reliability using the reciprocal-information approximation to conditional error variance.
  • Accounts for heterogeneous measurement precision across \theta.

Caution: Can be non-monotone at extreme c values. Use sac_calibrate(reliability_metric = "msem") for direct MSEM targeting.

Jensen’s Gap in Practice

# Calibrate with EQC under the supported info metric
eqc_info <- eqc_calibrate(
  target_rho = 0.80, n_items = 25, model = "rasch",
  item_source = "parametric", reliability_metric = "info",
  M = 5000L, seed = 42, verbose = FALSE
)

cat("Calibration with target rho* = 0.80:\n")
#> Calibration with target rho* = 0.80:
cat(sprintf("  Info metric: c* = %.4f, achieved = %.4f\n",
            eqc_info$c_star, eqc_info$achieved_rho))
#>   Info metric: c* = 0.8995, achieved = 0.8000
# Cross-evaluate: what does the EQC c* imply for both metrics?
both_at_info <- compute_rho_both(
  eqc_info$c_star,
  eqc_info$theta_quad,
  eqc_info$beta_vec,
  eqc_info$lambda_base,
  theta_var = eqc_info$theta_var
)

cat("\nCross-evaluation of the EQC c*:\n")
#> 
#> Cross-evaluation of the EQC c*:
cat(sprintf("  At c*_info = %.4f: rho_tilde = %.4f, rho_bar = %.4f\n",
            eqc_info$c_star, both_at_info$rho_tilde, both_at_info$rho_bar))
#>   At c*_info = 0.8995: rho_tilde = 0.8000, rho_bar = 0.7937
cat(sprintf("  Jensen gap: %.4f\n", both_at_info$rho_tilde - both_at_info$rho_bar))
#>   Jensen gap: 0.0063

3PL calibration contract

For model = "3pl", specify lower asymptotes through item_params$guessing_params (or custom_params$guessing for a custom bank). At every candidate scale EQC holds difficulty and guessing fixed and replaces \lambda_i^{(0)} by c\lambda_i^{(0)}. Setting every guessing value to zero reproduces the 2PL kernel and reliability calculation exactly. Positive guessing generally requires a larger selected discrimination scale for the same fixed-form target, but this is an empirical design comparison rather than a universal root-ordering theorem.

The symbol c in results and plots always denotes this global scale. It never denotes the conventional 3PL lower asymptote; documentation and output use guessing for the latter.

Diagnostic Examples

Monte Carlo Convergence Study

How does the EQC solution improve with quadrature size M?

M_values <- c(500, 1000, 2000, 5000)
n_reps <- 10
target <- 0.80

# Run multiple replications at each M
mc_results <- data.frame(
  M = integer(), rep = integer(), c_star = numeric()
)

for (M_val in M_values) {
  for (r in 1:n_reps) {
    res <- eqc_calibrate(
      target_rho = target, n_items = 25, model = "rasch",
      item_source = "parametric", reliability_metric = "info",
      M = as.integer(M_val), seed = 100 * r + M_val, verbose = FALSE
    )
    mc_results <- rbind(mc_results, data.frame(
      M = M_val, rep = r, c_star = res$c_star
    ))
  }
}

# Summary statistics
mc_summary <- aggregate(c_star ~ M, data = mc_results, FUN = function(x) {
  c(mean = mean(x), sd = sd(x), min = min(x), max = max(x))
})

cat("Monte Carlo convergence of c* (target = 0.80, 25 Rasch items):\n")
#> Monte Carlo convergence of c* (target = 0.80, 25 Rasch items):
cat(sprintf("  %-8s %-10s %-10s %-10s\n", "M", "Mean c*", "SD(c*)", "Range"))
#>   M        Mean c*    SD(c*)     Range
for (i in seq_len(nrow(mc_summary))) {
  vals <- mc_summary$c_star[i, ]
  cat(sprintf("  %-8d %-10.4f %-10.4f [%.4f, %.4f]\n",
              mc_summary$M[i], vals["mean"], vals["sd"],
              vals["min"], vals["max"]))
}
#>   500      0.9565     0.0337     [0.9111, 1.0141]
#>   1000     0.9417     0.0392     [0.8858, 1.0102]
#>   2000     0.9362     0.0227     [0.9031, 0.9718]
#>   5000     0.9169     0.0256     [0.8693, 0.9521]
oldpar <- par(mar = c(4.5, 4.5, 3, 1))
on.exit(par(oldpar))

# Box plot of c* across M values
M_factor <- factor(mc_results$M,
                   labels = paste0("M=", format(M_values, big.mark = ",")))
boxplot(c_star ~ M_factor, data = mc_results,
        col = "lightblue", border = "steelblue",
        xlab = "Quadrature Size M", ylab = "EQC c* estimate",
        main = "Monte Carlo Variability of EQC")

# Reference line at the grand mean
abline(h = mean(mc_results$c_star[mc_results$M == max(M_values)]),
       lty = 2, col = "red", lwd = 1.5)
legend("topright", legend = "Reference (largest M)",
       lty = 2, col = "red", lwd = 1.5, cex = 0.9)
Boxplots showing smaller spread in calibrated scaling factors for larger quadrature sample sizes.

Variability of EQC estimates decreases with quadrature size.

The standard deviation of \hat{c}^*_M decreases approximately as 1/\sqrt{M}, consistent with the theoretical prediction from Theorem A.2.

Sensitivity to Target Reliability

targets <- seq(0.50, 0.90, by = 0.05)
sensitivity <- data.frame(
  target  = targets,
  c_star  = numeric(length(targets)),
  achieved = numeric(length(targets))
)

for (j in seq_along(targets)) {
  res <- eqc_calibrate(
    target_rho = targets[j], n_items = 25, model = "rasch",
    item_source = "parametric", reliability_metric = "info",
    M = 5000L, seed = 42, verbose = FALSE
  )
  sensitivity$c_star[j]  <- res$c_star
  sensitivity$achieved[j] <- res$achieved_rho
}

cat("EQC sensitivity to target reliability (25 Rasch items, info metric):\n")
#> EQC sensitivity to target reliability (25 Rasch items, info metric):
cat(sprintf("  %-8s %-10s %-10s %-10s\n",
            "Target", "c*", "Achieved", "|Error|"))
#>   Target   c*         Achieved   |Error|
for (j in seq_len(nrow(sensitivity))) {
  cat(sprintf("  %-8.2f %-10.4f %-10.4f %-10.6f\n",
              sensitivity$target[j],
              sensitivity$c_star[j],
              sensitivity$achieved[j],
              abs(sensitivity$achieved[j] - sensitivity$target[j])))
}
#>   0.50     0.4114     0.5000     0.000000  
#>   0.55     0.4580     0.5500     0.000000  
#>   0.60     0.5118     0.6000     0.000000  
#>   0.65     0.5758     0.6500     0.000000  
#>   0.70     0.6548     0.7000     0.000000  
#>   0.75     0.7572     0.7500     0.000000  
#>   0.80     0.8995     0.8000     0.000000  
#>   0.85     1.1199     0.8500     0.000000  
#>   0.90     1.5328     0.9000     0.000000
oldpar <- par(mar = c(4.5, 4.5, 3, 1))
on.exit(par(oldpar))

plot(sensitivity$target, sensitivity$c_star, type = "b",
     pch = 19, col = "steelblue", lwd = 2,
     xlab = expression("Target reliability " * rho * "*"),
     ylab = expression("Calibrated scaling factor c*"),
     main = "EQC: Target vs Calibrated Scale")
grid(col = "gray90")
Line plot showing the calibrated scaling factor increasing with the target reliability.

Calibrated scaling factor as a function of target reliability.

Sensitivity to Latent Distribution Shape

shapes <- c("normal", "bimodal", "heavy_tail", "skew_pos")
shape_pars <- list(
  normal     = list(),
  bimodal    = list(delta = 0.9),
  heavy_tail = list(df = 5),
  skew_pos   = list(k = 4)
)

cat("EQC c* for different latent shapes (target = 0.80, 25 Rasch items):\n")
#> EQC c* for different latent shapes (target = 0.80, 25 Rasch items):
for (sh in shapes) {
  res <- eqc_calibrate(
    target_rho = 0.80, n_items = 25, model = "rasch",
    item_source = "parametric", reliability_metric = "info",
    latent_shape = sh, latent_params = shape_pars[[sh]],
    M = 5000L, seed = 42, verbose = FALSE
  )
  cat(sprintf("  %-12s: c* = %.4f, achieved = %.4f\n",
              sh, res$c_star, res$achieved_rho))
}
#>   normal      : c* = 0.8995, achieved = 0.8000
#> Auto-wrapping shape parameter(s) {delta} into latent_params$shape_params.
#>   bimodal     : c* = 0.9605, achieved = 0.8000
#> Auto-wrapping shape parameter(s) {df} into latent_params$shape_params.
#>   heavy_tail  : c* = 0.9365, achieved = 0.8000
#> Auto-wrapping shape parameter(s) {k} into latent_params$shape_params.
#>   skew_pos    : c* = 0.9132, achieved = 0.8000

Model Comparison: Rasch vs 2PL

eqc_rasch <- eqc_calibrate(
  target_rho = 0.80, n_items = 25, model = "rasch",
  item_source = "parametric", reliability_metric = "info",
  M = 5000L, seed = 42, verbose = FALSE
)

eqc_2pl <- eqc_calibrate(
  target_rho = 0.80, n_items = 25, model = "2pl",
  item_source = "parametric", reliability_metric = "info",
  M = 5000L, seed = 42, verbose = FALSE
)

cat("Model comparison (target = 0.80, 25 items, info metric):\n")
#> Model comparison (target = 0.80, 25 items, info metric):
cat(sprintf("  Rasch: c* = %.4f, achieved = %.4f\n",
            eqc_rasch$c_star, eqc_rasch$achieved_rho))
#>   Rasch: c* = 0.8995, achieved = 0.8000
cat(sprintf("  2PL:   c* = %.4f, achieved = %.4f\n",
            eqc_2pl$c_star, eqc_2pl$achieved_rho))
#>   2PL:   c* = 0.8637, achieved = 0.8000
cat(sprintf("  Rasch baseline lambda: %s\n",
            paste(unique(round(eqc_rasch$lambda_base, 2)), collapse = ", ")))
#>   Rasch baseline lambda: 1
cat(sprintf("  2PL baseline lambda range: [%.2f, %.2f]\n",
            min(eqc_2pl$lambda_base), max(eqc_2pl$lambda_base)))
#>   2PL baseline lambda range: [0.53, 2.08]

Verbose Output Walkthrough

The verbose mode reveals the internal steps of EQC.

eqc_v <- eqc_calibrate(
  target_rho = 0.80, n_items = 25, model = "rasch",
  item_source = "parametric", reliability_metric = "info",
  M = 5000L, seed = 42, verbose = TRUE
)
#> Step 1: Generating quadrature samples...
#>   M (quad persons) = 5000
#>   I (items)        = 25
#>   theta: mean = -0.014, sd = 1.005, var = 1.010
#>   beta:  mean = 0.000, sd = 0.861
#>   lambda_base: mean = 1.000, sd = 0.000
#>   metric = info
#> Step 2: Running root-finding algorithm...
#>   At c = 0.300: rho = 0.3539, g = -0.4461
#>   At c = 3.000: rho = 0.9550, g = 0.1550
#>   c* = 0.899499
#>   Target rho    = 0.8000
#>   Achieved rho  = 0.8000
#>   Root status   = uniroot_success

Key information from verbose output:

  • Observed range: Reports the scanned reliability range inside c_bounds.
  • Root topology: Reports detected root count, selected kind/direction, and scanner resolution status.
  • Achieved reliability: Final reliability at the calibrated c^*.

Output Structure

The eqc_calibrate() function returns an object of class "eqc_result" containing the calibration results.

# Key components
cat("EQC result components:\n")
#> EQC result components:
cat(sprintf("  c_star:       %.4f  (calibrated scaling factor)\n",
            eqc_v$c_star))
#>   c_star:       0.8995  (calibrated scaling factor)
cat(sprintf("  target_rho:   %.2f  (target reliability)\n",
            eqc_v$target_rho))
#>   target_rho:   0.80  (target reliability)
cat(sprintf("  achieved_rho: %.4f  (achieved reliability)\n",
            eqc_v$achieved_rho))
#>   achieved_rho: 0.8000  (achieved reliability)
cat(sprintf("  metric:       %s   (reliability metric used)\n",
            eqc_v$metric))
#>   metric:       info   (reliability metric used)
cat(sprintf("  n_items:      %d    (number of items)\n",
            length(eqc_v$beta_vec)))
#>   n_items:      25    (number of items)
cat(sprintf("  M (quadrature): %d\n", length(eqc_v$theta_quad)))
#>   M (quadrature): 5000

Prediction and Downstream Use

The predict() method evaluates reliability at arbitrary scaling factors using the stored quadrature samples.

# Evaluate reliability at different c values
c_query <- c(0.5, 1.0, 1.5, 2.0)
pred <- predict(eqc_v, newdata = c_query)

cat("Predict method: reliability at different c values\n")
#> Predict method: reliability at different c values
for (j in seq_along(c_query)) {
  cat(sprintf("  c = %.1f: rho = %.4f\n", c_query[j], pred[j]))
}
#>   c = 0.5: rho = 0.5897
#>   c = 1.0: rho = 0.8260
#>   c = 1.5: rho = 0.8972
#>   c = 2.0: rho = 0.9280

Connection to SAC Validation

For rigorous validation, EQC results should be cross-checked against the SAC algorithm using the same reliability estimand. The recommended workflow is:

  1. Run EQC as the primary calibration.
  2. Initialize SAC with the EQC result (warm start) and set reliability_metric = "info".
  3. Compare using compare_eqc_sac().

See vignette("algorithm-sac") for the SAC algorithm details and vignette("validation") for the complete validation framework.

# Quick cross-validation example
sac_check <- sac_calibrate(
  target_rho = 0.80, n_items = 25, model = "rasch",
  reliability_metric = "info", resample_items = FALSE,
  c_init = eqc_v, n_iter = 200L, M_per_iter = 1000L,
  seed = 42, verbose = FALSE,
  preflight_controls = list(M = 500L),
  evaluation_controls = list(n_forms = 3L, M = 1000L)
)

comparison <- compare_eqc_sac(eqc_v, sac_check, verbose = FALSE)
cat(sprintf("EQC-SAC agreement: %.2f%% difference\n", comparison$diff_pct))
#> EQC-SAC agreement: 0.15% difference

Summary

EQC is the recommended primary algorithm for reliability-targeted IRT simulation:

Aspect Summary
Method Adaptive log-scale topology scan plus local Brent polishing
Convergence Root polishing is superlinear; statistical consistency is branch-conditional
Asymptotic rate \sqrt{M}-normal (Theorem A.2)
Supported target metric Average-information ("info" / "tilde")
Accuracy Depends on quadrature size and local slope; use independent holdout evaluation
Recommended M 10,000 default; justify changes with sensitivity evidence
Validation Cross-check with SAC via compare_eqc_sac()

References

Lee, J.-H. (2026). Reliability-Targeted Simulation of Item Response Data: Solving the Inverse Design Problem. arXiv:2512.16012v2. https://doi.org/10.48550/arXiv.2512.16012

Brent, R. P. (1973). Algorithms for Minimization Without Derivatives. Prentice-Hall.

van der Vaart, A. W. (1998). Asymptotic Statistics. Cambridge University Press.