Skip to contents

sac_calibrate() uses branch-guarded Robbins–Monro stochastic approximation to choose a positive global discrimination multiplier that targets a marginal reliability estimand for a unidimensional Rasch, 2PL, or 3PL item generator.

Usage

sac_calibrate(
  target_rho,
  n_items,
  model = c("rasch", "2pl", "3pl"),
  latent_shape = "normal",
  item_source = "parametric",
  latent_params = list(),
  item_params = list(),
  reliability_metric = c("msem", "info", "bar", "tilde"),
  c_init = NULL,
  M_per_iter = 500L,
  M_pre = 10000L,
  n_iter = 300L,
  burn_in = NULL,
  step_params = list(),
  c_bounds = c(0.01, 20),
  resample_items = TRUE,
  seed = NULL,
  verbose = FALSE,
  root_policy = "lowest_increasing",
  preflight_controls = list(),
  evaluation_controls = list()
)

# S3 method for class 'sac_result'
print(x, digits = 4, ...)

# S3 method for class 'sac_result'
summary(object, ...)

# S3 method for class 'summary.sac_result'
print(x, digits = 4, ...)

# S3 method for class 'sac_result'
coef(object, ...)

# S3 method for class 'sac_result'
predict(object, newdata = NULL, theta_vec = NULL, ...)

Arguments

target_rho

Finite numeric scalar in (0, 1). Target marginal reliability.

n_items

Positive integer. Number of items per generated form.

model

Character. One of "rasch", "2pl", or "3pl". Rasch fixes baseline discriminations at one. In the 3PL model, item_params$guessing_params (or custom_params$guessing for a custom source) supplies the lower asymptotes. SAC scales discrimination only and uses the fixed logistic convention D = 1.

latent_shape

Character shape passed to sim_latentG().

item_source

Character source passed to sim_item_params(). The default, "parametric", has no external data dependency; "irw" requires the optional Item Response Warehouse package.

latent_params

Named list of additional arguments passed to sim_latentG().

item_params

Named list of additional arguments passed to sim_item_params().

reliability_metric

Character reliability estimand. "msem" (the default) and its synonym "bar" target reciprocal-information MSEM reliability, \(\bar w\). "info" and its synonym "tilde" target average-information reliability, \(\tilde\rho\). The stored metric is canonicalized to "msem" or "info". Population MSEM is rejected for the built-in untruncated "heavy_tail" distribution because its reciprocal-information expectation is not finite.

c_init

NULL, a positive numeric scalar, or an eqc_result. With NULL, 3PL uses the selected preflight root; Rasch/2PL uses APC when APC lies inside the selected branch and otherwise uses the preflight root. A numeric initializer is projected into the effective branch. An EQC warm start is accepted only when target, model, item count, canonical metric, fixed-form scope, realized item form, and increasing branch agree.

M_per_iter

Positive integer. Ability draws per Robbins–Monro iteration. Default 500L.

M_pre

Integer at least two. Draws used once to estimate the population latent variance held fixed during calibration. Default 10000L.

n_iter

Positive integer. Robbins–Monro iterations. Default 300L.

burn_in

Non-negative integer less than n_iter. Iterations through burn_in are excluded from Polyak–Ruppert averaging. The default is floor(n_iter / 2).

step_params

Named list overriding a = 1, A = 50, or gamma = 0.67 in \(a_n=a/(n+A)^\gamma\). a must be positive, A non-negative, and gamma in (0.5, 1].

c_bounds

Positive increasing length-two numeric vector. The preflight scanner never expands these requested bounds. Iterates are projected to the selected increasing branch after applying its safety margin.

resample_items

Logical. FALSE calibrates one realized fixed form and sets item_scope = "fixed_form". TRUE (the default) draws a form at each iteration, targets the item-superpopulation mean, and sets item_scope = "item_superpopulation".

seed

Optional integer seed. When supplied, the caller's RNG state is restored on exit; isolated deterministic substreams are used for preflight and independent post-calibration evaluation.

verbose

Logical or non-negative numeric verbosity level. Level one reports checkpoints; level two reports every iteration.

root_policy

Character root-selection policy: "lowest_increasing", "nearest_increasing", "lowest_any", or "highest_any". The default selects the lowest admissible increasing root. Tangent and plateau roots are not made admissible by SAC's public controls.

preflight_controls

Named list of branch-preflight overrides: n_forms (default 8L for item-superpopulation calibration), M (default min(M_pre, max(500L, M_per_iter))), split_check (default resample_items), split_log_tolerance (default log(1.5)), root_controls (a named list passed to the topology scanner), branch_margin (default 0.01), max_consecutive_hits (default 5L), and max_post_burn_hit_rate (default 0.10). Unknown names are rejected.

evaluation_controls

Named list controlling the independent final evaluation: n_forms (default 20L), M per form (default max(1000L, M_per_iter)), conf_level (default 0.95), and probs (default c(0.025, 0.5, 0.975)). Unknown names are rejected. For a fixed form, theta blocks are concatenated before computing achieved_rho; for item-superpopulation calibration, form reliabilities are averaged.

x

A sac_result or summary.sac_result, as appropriate.

digits

Number of decimal places used by print methods.

...

Additional arguments passed to or from methods. Current SAC methods do not otherwise use them.

object

A sac_result.

newdata

Optional numeric vector of positive discrimination scales for predict.sac_result(). NULL returns the stored achieved_rho.

theta_vec

Optional finite ability vector for prediction. When supplied, its sample variance defines the prediction variance basis.

Value

sac_calibrate() returns a sac_result list. Its historical fields remain present and v0.3 metadata are appended:

Calibration

c_star is the Polyak–Ruppert scale; c_final is the last iterate; target_rho, achieved_rho, achieved_se, achieved_distribution, metric, and calibration_status describe the independently evaluated result. M_final is the total number of final evaluation theta draws.

Trajectories

trajectory stores updated scales and rho_trajectory stores reliabilities recomputed at those same scales. evaluation_trajectory, rho_update_trajectory, raw_trajectory, step_size_trajectory, gradient_trajectory, projected, projection_side, projection_source, and iteration_trace preserve the full update audit trail.

Algorithm settings

theta_var, model, n_items, n_iter, burn_in, M_per_iter, M_pre, step_params, c_bounds, requested_c_bounds, c_init, init_method, preflight_controls, and evaluation_controls record the effective run settings.

Item design

beta_vec, lambda_base, lambda_scaled, guessing_vec, items_base, items_calib, item_design, and item_scope describe the representative stored form. Rasch/2PL results store zero guessing; 3PL results preserve the generated lower asymptotes. For item-superpopulation runs the representative form is not itself the estimand.

Contracts and diagnostics

schema_version, estimand_signature, design_signature, calibration_design, evaluation_design, convergence, preflight, branch, rng_provenance, warm_start, representative_achieved_rho, theta_quad, and call preserve estimand, topology, evaluation, RNG, and provenance diagnostics.

print() returns its input invisibly; summary() returns a summary.sac_result; coef() returns calibrated item parameters (including guessing for 3PL); and predict() returns stored or recomputed reliability.

Details

The first 18 arguments preserve the v0.2 positional API. The v0.3 branch and evaluation controls are appended after verbose.

Estimand and branch contract

SAC first scans the requested log-scale interval and must resolve an admissible increasing root branch. A classed preflight error is raised when the target is infeasible, a stable branch cannot be resolved, or only an inadmissible boundary/decreasing branch is available. The effective branch bounds are then used as projection guards. Repeated branch-bound contact or a non-positive final branch slope produces calibration_status = "branch_lost" rather than silently treating the run as converged.

reliability_metric and item_scope are both part of the estimand. Thus an EQC result (metric = "info", fixed form) is directly comparable to SAC only when SAC also uses reliability_metric = "info" and resample_items = FALSE on that same form. Passing the compatible EQC object as c_init without overriding item-generation arguments reuses its form.

3PL and external validation

The 3PL probability and Fisher-information kernels use D = 1; the global scale multiplies discrimination and never guessing. The package's public compute_reliability_tam() helper intentionally supports Rasch and 2PL only because the validated TAM workflow requires both WLE and EAP outputs. Phase 7's 3PL external oracle is an EAP-only validation path; TAM 3PL WLE is not a supported public-package workflow. Do not interpret WLE and EAP as ordered versions of the same estimand.

spc_calibrate() remains a deprecated forwarding alias.

References

Robbins, H., & Monro, S. (1951). A stochastic approximation method. The Annals of Mathematical Statistics, 22(3), 400–407.

Polyak, B. T., & Juditsky, A. B. (1992). Acceleration of stochastic approximation by averaging. SIAM Journal on Control and Optimization, 30(4), 838–855.

Examples

# \donttest{
# Small fixed-form 3PL calibration with explicit lower asymptotes.
form <- list(
  custom_params = list(
    beta = c(-1.1, -0.55, -0.1, 0.35, 0.8, 1.25),
    lambda = c(0.75, 0.9, 1.05, 1.2, 1.35, 1.5),
    guessing = c(0.08, 0.12, 0.16, 0.20, 0.24, 0.28)
  ),
  center_difficulties = FALSE
)
fit <- sac_calibrate(
  target_rho = 0.40,
  n_items = 6L,
  model = "3pl",
  item_source = "custom",
  item_params = form,
  reliability_metric = "info",
  M_per_iter = 128L,
  M_pre = 512L,
  n_iter = 8L,
  burn_in = 4L,
  c_bounds = c(0.1, 3),
  resample_items = FALSE,
  seed = 7601L,
  preflight_controls = list(
    M = 256L,
    split_check = FALSE,
    root_controls = list(min_grid = 65L, max_evals = 257L)
  ),
  evaluation_controls = list(n_forms = 4L, M = 512L)
)
print(fit)
#> 
#> =======================================================
#>   Stochastic Approximation Calibration (SAC) Results
#> =======================================================
#> 
#> Calibration Summary:
#>   Model                        : 3PL
#>   Item scope                   : fixed_form
#>   Target reliability (rho*)    : 0.4000
#>   Achieved reliability         : 0.3955
#>   Achieved reliability SE      : 0.0022
#>   Evaluation forms             : 4
#>   Absolute error               : 4.50e-03
#>   Scaling factor (c*)          : 0.9408
#>   Calibration status           : ok
#>   Selected preflight root      : 0.9384
#>   Selected branch              : id=1, increasing, [0.1, 3]
#>   Root policy                  : lowest_increasing
#>   Guessing (min/mean/max)      : 0.0800 / 0.1800 / 0.2800
#> 
#> Algorithm Settings:
#>   Number of items (I)          : 6
#>   M per iteration              : 128
#>   M for variance pre-calc      : 512
#>   Total iterations             : 8
#>   Burn-in                      : 4
#>   Reliability metric           : Average-information (tilde)
#>   Step params: a=1.00, A=50, gamma=0.67
#> 
#> Convergence Diagnostics:
#>   Initialization method        : preflight_root
#>   Initial c_0                  : 0.9384
#>   Final iterate c_n            : 0.9412
#>   Polyak-Ruppert c*            : 0.9408
#>   Pre-calculated theta_var     : 0.8879
#>   Converged                    : Yes
#>   Post-burn-in SD              : 0.0007
#>   Final iter gradient          : +0.0017
#>   Gradient at c*               : -0.0045
#>   Projection count             : 0 (0.0%)
#>   Status flags                 : ok
#> 
coef(fit)
#>   item_id  beta lambda_base lambda_scaled    c_star guessing
#> 1       1 -1.10        0.75     0.7055645 0.9407526     0.08
#> 2       2 -0.55        0.90     0.8466774 0.9407526     0.12
#> 3       3 -0.10        1.05     0.9877903 0.9407526     0.16
#> 4       4  0.35        1.20     1.1289032 0.9407526     0.20
#> 5       5  0.80        1.35     1.2700161 0.9407526     0.24
#> 6       6  1.25        1.50     1.4111290 0.9407526     0.28
predict(fit, newdata = c(0.5, 1))
#>     c=0.5     c=1.0 
#> 0.1905445 0.4176017 
# }