use core::cell::Cell;
use crate::calibration::CalibrationResult;
use crate::calibration::config::{
ConstraintMode, InitializationPolicy, SliceCalibrationConfig, validate_effective_quotes,
};
use crate::calibration::report::{ParameterizationEvidence, TerminationReason};
use crate::calibration::slice::residual_diagnostics;
use crate::error::CalibrationError;
use crate::market::quote::Quote;
use crate::numerics::{nelder_mead, solve_spd_3};
use crate::smile::raw::RawSvi;
const MIN_QUOTES: usize = 5;
pub fn calibrate(quotes: &[Quote]) -> Result<CalibrationResult, CalibrationError> {
calibrate_with_config(quotes, SliceCalibrationConfig::default())
}
#[allow(clippy::too_many_lines)] pub fn calibrate_with_config(
quotes: &[Quote],
config: SliceCalibrationConfig,
) -> Result<CalibrationResult, CalibrationError> {
validate_effective_quotes(quotes, MIN_QUOTES, config.distinct_tolerance())?;
let k_min = quotes.iter().map(|q| q.k).fold(f64::INFINITY, f64::min);
let k_max = quotes.iter().map(|q| q.k).fold(f64::NEG_INFINITY, f64::max);
let k_span = (k_max - k_min).max(1e-3);
let w_max = quotes
.iter()
.map(|q| q.w)
.fold(0.0_f64, f64::max)
.max(1e-12);
let evaluations = Cell::new(0_usize);
let outer = |p: &[f64]| -> f64 {
evaluations.set(evaluations.get().saturating_add(1));
let m = p[0];
let sigma = p[1].exp();
if !m.is_finite() || !sigma.is_finite() || sigma <= 0.0 {
return f64::INFINITY;
}
inner_solve(quotes, m, sigma, w_max).0
};
let m_seeds = [
k_min,
0.5 * (k_min + k_max),
k_max,
k_min - 0.25 * k_span,
k_max + 0.25 * k_span,
];
let sigma_seeds = [0.1 * k_span, 0.3 * k_span, k_span, 2.0 * k_span];
let mut best_obj = f64::INFINITY;
let mut best_slice = None;
let mut best_start = None;
let mut best_condition = None;
let mut selected_termination = crate::numerics::OptimizerTermination::IterationLimit;
let mut total_iterations = 0_usize;
if config.constraint_mode() == ConstraintMode::BestEffort {
let total_weight: f64 = quotes.iter().map(|quote| quote.weight()).sum();
let flat_level = quotes
.iter()
.map(|quote| quote.weight() * quote.total_variance())
.sum::<f64>()
/ total_weight;
if let Ok(fallback) = RawSvi::new(flat_level, 0.0, 0.0, 0.5 * (k_min + k_max), 0.3 * k_span)
{
let fallback_objective =
residual_diagnostics(&fallback, quotes, config.distinct_tolerance()).objective();
if fallback_objective.is_finite() {
best_obj = fallback_objective;
best_slice = Some(fallback);
}
}
}
let m_count = if config.initialization() == InitializationPolicy::DeterministicSingleStart {
1
} else {
m_seeds.len()
};
let sigma_count = if config.initialization() == InitializationPolicy::DeterministicSingleStart {
1
} else {
sigma_seeds.len()
};
for (m_index, &m0) in m_seeds[..m_count].iter().enumerate() {
for (sigma_index, &s0) in sigma_seeds[..sigma_count].iter().enumerate() {
let start = [m0, s0.max(1e-6).ln()];
let res = nelder_mead(outer, &start, config.tolerance(), config.outer_iterations());
total_iterations = total_iterations.saturating_add(res.iterations);
let eligible = res.converged || config.constraint_mode() == ConstraintMode::BestEffort;
if eligible {
let [candidate_m, candidate_log_sigma] = res.x.as_slice() else {
continue;
};
let candidate_sigma = candidate_log_sigma.exp();
let (_, a, d, c, condition_estimate) =
inner_solve(quotes, *candidate_m, candidate_sigma, w_max);
let b = c / candidate_sigma;
let rho = if c.abs() > 1e-300 { d / c } else { 0.0 };
let Ok(candidate) = RawSvi::new(a, b, rho, *candidate_m, candidate_sigma) else {
continue;
};
let candidate_objective =
residual_diagnostics(&candidate, quotes, config.distinct_tolerance())
.objective();
if candidate_objective.is_finite() && candidate_objective < best_obj {
best_obj = candidate_objective;
best_slice = Some(candidate);
best_start = Some(m_index * sigma_count + sigma_index);
best_condition = Some(condition_estimate);
selected_termination = res.termination;
}
}
}
}
let slice = best_slice.ok_or(CalibrationError::DidNotConverge {
iterations: total_iterations,
residual: best_obj,
})?;
let residuals = residual_diagnostics(&slice, quotes, config.distinct_tolerance());
let termination = TerminationReason::from(selected_termination);
CalibrationResult::from_parts(
slice,
residuals,
total_iterations,
evaluations.get(),
termination,
config,
"quasi-explicit / Nelder–Mead",
m_count * sigma_count,
best_start,
best_condition,
ParameterizationEvidence::new(
"sigma=exp; affine (a,d,c) polytope recovery",
"no projection; non-representable open-domain candidates discarded",
),
)
}
#[allow(clippy::too_many_lines)]
fn inner_solve(quotes: &[Quote], m: f64, sigma: f64, w_max: f64) -> (f64, f64, f64, f64, f64) {
let max_weight = quotes
.iter()
.map(|quote| quote.weight())
.fold(0.0_f64, f64::max);
if max_weight <= 0.0 || !max_weight.is_finite() {
return (f64::INFINITY, 0.0, 0.0, 0.0, f64::INFINITY);
}
let mut rows: Vec<([f64; 3], f64, f64)> = Vec::with_capacity(quotes.len());
for q in quotes {
if q.weight <= 0.0 {
continue;
}
let y = (q.k - m) / sigma;
let z = y.hypot(1.0);
rows.push(([1.0, y, z], q.w, q.weight / max_weight));
}
if rows.is_empty() {
return (f64::INFINITY, 0.0, 0.0, 0.0, f64::INFINITY);
}
let mut a_mat = [0.0_f64; 6]; let mut rhs = [0.0_f64; 3];
for (phi, w, weight) in &rows {
let ww = *weight;
a_mat[0] += ww * phi[0] * phi[0];
a_mat[1] += ww * phi[0] * phi[1];
a_mat[2] += ww * phi[0] * phi[2];
a_mat[3] += ww * phi[1] * phi[1];
a_mat[4] += ww * phi[1] * phi[2];
a_mat[5] += ww * phi[2] * phi[2];
rhs[0] += ww * phi[0] * w;
rhs[1] += ww * phi[1] * w;
rhs[2] += ww * phi[2] * w;
}
let residual_of = |a: f64, d: f64, c: f64| -> f64 {
rows.iter()
.map(|(phi, w, weight)| {
let model = d.mul_add(phi[1], c.mul_add(phi[2], a));
let r = model - w;
weight * r * r
})
.sum()
};
let c_hi = 4.0 * sigma;
let feasible = |a: f64, d: f64, c: f64| -> bool {
let eps = 1e-9 * (1.0 + c_hi + w_max);
a >= -eps
&& a <= w_max + eps
&& c >= -eps
&& c <= c_hi + eps
&& d.abs() <= c + eps
&& d.abs() <= c_hi - c + eps
};
let mut best_resid = f64::INFINITY;
let mut best = (0.0_f64, 0.0_f64, 0.0_f64);
let mut consider = |a: f64, d: f64, c: f64| {
if a.is_finite() && d.is_finite() && c.is_finite() && feasible(a, d, c) {
let r = residual_of(a, d, c);
if r < best_resid {
best_resid = r;
best = (a, d, c);
}
}
};
let (unconstrained, condition_estimate) = solve_normalized_spd_3(&a_mat, &rhs);
if let Some(x) = unconstrained {
consider(x[0], x[1], x[2]);
}
for &a_fix in &[0.0, w_max] {
if let Some((d, c)) = solve_2x2(
a_mat[3],
a_mat[4],
a_mat[5],
rhs[1] - a_mat[1] * a_fix,
rhs[2] - a_mat[2] * a_fix,
) {
consider(a_fix, d, c);
}
}
for &c_fix in &[0.0, c_hi] {
if let Some((a, d)) = solve_2x2(
a_mat[0],
a_mat[1],
a_mat[3],
rhs[0] - a_mat[2] * c_fix,
rhs[1] - a_mat[4] * c_fix,
) {
consider(a, d, c_fix);
}
}
for &s in &[1.0_f64, -1.0] {
let a00 = a_mat[0];
let a01 = s * a_mat[1] + a_mat[2];
let a11 = a_mat[5] + 2.0 * s * a_mat[4] + a_mat[3];
let r0 = rhs[0];
let r1 = s * rhs[1] + rhs[2];
if let Some((a, c)) = solve_2x2(a00, a01, a11, r0, r1) {
consider(a, s * c, c);
}
}
for &s in &[1.0_f64, -1.0] {
let a00 = a_mat[0];
let a01 = a_mat[2] - s * a_mat[1];
let a11 = a_mat[5] - 2.0 * s * a_mat[4] + a_mat[3];
let r0 = rhs[0] - s * c_hi * a_mat[1];
let r1 = (rhs[2] - s * rhs[1]) - s * c_hi * (a_mat[4] - s * a_mat[3]);
if let Some((a, c)) = solve_2x2(a00, a01, a11, r0, r1) {
consider(a, s * c_hi - s * c, c);
}
}
for &a_fix in &[0.0, w_max] {
for &c_fix in &[0.0, c_hi] {
let denom = a_mat[3];
if denom > 0.0 {
let d = (rhs[1] - a_mat[1] * a_fix - a_mat[4] * c_fix) / denom;
consider(a_fix, d, c_fix);
}
}
for &s in &[1.0_f64, -1.0] {
let a11 = a_mat[5] + 2.0 * s * a_mat[4] + a_mat[3];
if a11 > 0.0 {
let c = (s * (rhs[1] - a_mat[1] * a_fix) + (rhs[2] - a_mat[2] * a_fix)) / a11;
consider(a_fix, s * c, c);
}
let a11b = a_mat[5] - 2.0 * s * a_mat[4] + a_mat[3];
if a11b > 0.0 {
let r = (rhs[2] - a_mat[2] * a_fix)
- s * (rhs[1] - a_mat[1] * a_fix)
- s * c_hi * (a_mat[4] - s * a_mat[3]);
let c = r / a11b;
consider(a_fix, s * c_hi - s * c, c);
}
}
}
let half = c_hi / 2.0;
let dc_vertices = [(0.0, 0.0), (0.0, c_hi), (half, half), (-half, half)];
for &a_fix in &[0.0, w_max] {
for &(d, c) in &dc_vertices {
consider(a_fix, d, c);
}
}
for &(d, c) in &dc_vertices {
if a_mat[0] > 0.0 {
let a = (rhs[0] - a_mat[1] * d - a_mat[2] * c) / a_mat[0];
consider(a, d, c);
}
}
(best_resid, best.0, best.1, best.2, condition_estimate)
}
fn solve_normalized_spd_3(matrix: &[f64; 6], rhs: &[f64; 3]) -> (Option<[f64; 3]>, f64) {
let scales = [matrix[0].sqrt(), matrix[3].sqrt(), matrix[5].sqrt()];
if scales
.iter()
.any(|scale| !scale.is_finite() || *scale <= 0.0)
{
return (None, f64::INFINITY);
}
let a = matrix[1] / scales[0] / scales[1];
let b = matrix[2] / scales[0] / scales[2];
let c = matrix[4] / scales[1] / scales[2];
let normalized = [1.0, a, b, 1.0, c, 1.0];
let determinant = 1.0 + 2.0 * a * b * c - a * a - b * b - c * c;
let matrix_norm = (1.0 + a.abs() + b.abs())
.max(1.0 + a.abs() + c.abs())
.max(1.0 + b.abs() + c.abs());
let rank_threshold = 256.0 * f64::EPSILON * matrix_norm.powi(3);
if !determinant.is_finite() || determinant <= rank_threshold {
return (None, f64::INFINITY);
}
let inverse_norm = ((1.0 - c * c).abs() + (b * c - a).abs() + (a * c - b).abs())
.max((b * c - a).abs() + (1.0 - b * b).abs() + (a * b - c).abs())
.max((a * c - b).abs() + (a * b - c).abs() + (1.0 - a * a).abs())
/ determinant;
let condition_estimate = matrix_norm * inverse_norm;
let normalized_rhs = [rhs[0] / scales[0], rhs[1] / scales[1], rhs[2] / scales[2]];
let solution = solve_spd_3(&normalized, &normalized_rhs).map(|scaled| {
[
scaled[0] / scales[0],
scaled[1] / scales[1],
scaled[2] / scales[2],
]
});
(solution, condition_estimate)
}
fn solve_2x2(a00: f64, a01: f64, a11: f64, r0: f64, r1: f64) -> Option<(f64, f64)> {
if !a00.is_finite() || !a11.is_finite() || a00 <= 0.0 || a11 <= 0.0 {
return None;
}
let scale0 = a00.sqrt();
let scale1 = a11.sqrt();
let correlation = a01 / scale0 / scale1;
let determinant = 1.0 - correlation * correlation;
if !determinant.is_finite() || determinant <= 128.0 * f64::EPSILON {
return None;
}
let rhs0 = r0 / scale0;
let rhs1 = r1 / scale1;
let x0 = (rhs0 - correlation * rhs1) / determinant / scale0;
let x1 = (rhs1 - correlation * rhs0) / determinant / scale1;
if x0.is_finite() && x1.is_finite() {
Some((x0, x1))
} else {
None
}
}
#[cfg(test)]
#[allow(clippy::expect_used)] mod tests {
use super::*;
fn synthetic(svi: &RawSvi, ks: &[f64]) -> Vec<Quote> {
ks.iter()
.map(|&k| {
Quote::new(k, svi.total_variance(k), 1.0)
.expect("valid test or documentation fixture")
})
.collect()
}
#[test]
fn rejects_empty() {
assert!(matches!(calibrate(&[]), Err(CalibrationError::EmptyQuotes)));
}
#[test]
fn rejects_too_few_quotes() {
let q = Quote::new(0.0, 0.04, 1.0).expect("valid test or documentation fixture");
assert!(matches!(
calibrate(&[q, q, q]),
Err(CalibrationError::InsufficientEffectiveQuotes { .. })
));
}
#[test]
fn rejects_all_zero_weights() {
let quotes: Vec<Quote> = (0..6)
.map(|i| {
Quote::new(f64::from(i) * 0.1 - 0.3, 0.04, 0.0)
.expect("valid test or documentation fixture")
})
.collect();
assert!(matches!(
calibrate("es),
Err(CalibrationError::AllWeightsZero)
));
}
#[test]
fn constrained_non_convergence_is_not_success() {
let truth =
RawSvi::new(0.04, 0.4, -0.3, 0.05, 0.15).expect("valid test or documentation fixture");
let quotes = synthetic(&truth, &[-0.4, -0.2, 0.0, 0.2, 0.4]);
let constrained = SliceCalibrationConfig::new(
1,
1,
1e-15,
1e-10,
1e-12,
ConstraintMode::Constrained,
InitializationPolicy::DeterministicSingleStart,
)
.expect("valid test or documentation fixture");
assert!(matches!(
calibrate_with_config("es, constrained),
Err(CalibrationError::DidNotConverge { .. })
));
let best_effort = constrained.with_constraint_mode(ConstraintMode::BestEffort);
let result = calibrate_with_config("es, best_effort)
.expect("valid test or documentation fixture");
assert_eq!(
result.report().termination(),
TerminationReason::MaximumIterations
);
let recomputed: f64 = quotes
.iter()
.map(|quote| {
let residual =
result.slice().total_variance(quote.log_moneyness()) - quote.total_variance();
quote.weight() * residual * residual
})
.sum();
assert!(result.report().residuals().objective().is_finite());
assert!(
(result.report().residuals().objective() - recomputed).abs()
<= f64::EPSILON * (1.0 + recomputed.abs())
);
}
#[test]
fn recovers_synthetic_parameters() {
let truth =
RawSvi::new(0.04, 0.4, -0.3, 0.05, 0.15).expect("valid test or documentation fixture");
let ks = [-0.4, -0.25, -0.1, 0.0, 0.1, 0.25, 0.4];
let quotes = synthetic(&truth, &ks);
let fit = calibrate("es).expect("valid test or documentation fixture");
assert!(fit.report().selected_start().is_some());
assert!(matches!(
fit.report().condition_estimate(),
Some(condition) if condition.is_finite() && condition >= 1.0
));
assert!(
fit.report()
.parameterization()
.transform()
.contains("sigma=exp")
);
assert!(
fit.report()
.parameterization()
.repair()
.contains("discarded")
);
assert!(fit.rmse() < 1e-5, "rmse = {}", fit.rmse());
for &k in &[-0.6, -0.2, 0.0, 0.2, 0.6] {
let err = (fit.slice().total_variance(k) - truth.total_variance(k)).abs();
assert!(err < 1e-4, "k = {k}, err = {err}");
}
}
#[test]
fn recovers_symmetric_smile() {
let truth =
RawSvi::new(0.03, 0.3, 0.0, 0.0, 0.2).expect("valid test or documentation fixture");
let ks = [-0.5, -0.3, -0.1, 0.0, 0.1, 0.3, 0.5];
let quotes = synthetic(&truth, &ks);
let fit = calibrate("es).expect("valid test or documentation fixture");
assert!(fit.rmse() < 1e-5, "rmse = {}", fit.rmse());
assert!(fit.slice().rho.abs() < 1e-2, "rho = {}", fit.slice().rho);
}
#[test]
fn recovers_positive_skew() {
let truth =
RawSvi::new(0.05, 0.35, 0.4, -0.1, 0.18).expect("valid test or documentation fixture");
let ks = [-0.4, -0.2, -0.05, 0.05, 0.2, 0.4, 0.6];
let quotes = synthetic(&truth, &ks);
let fit = calibrate("es).expect("valid test or documentation fixture");
assert!(fit.rmse() < 1e-4, "rmse = {}", fit.rmse());
for &k in &[-0.3, 0.0, 0.3] {
let err = (fit.slice().total_variance(k) - truth.total_variance(k)).abs();
assert!(err < 1e-3, "k = {k}, err = {err}");
}
}
#[test]
fn graceful_degradation_with_noise() {
let truth =
RawSvi::new(0.04, 0.4, -0.3, 0.05, 0.15).expect("valid test or documentation fixture");
let ks = [-0.4, -0.25, -0.1, 0.0, 0.1, 0.25, 0.4];
let mut state = 12_345_u64;
let quotes: Vec<Quote> = ks
.iter()
.map(|&k| {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
let noise =
(f64::from((state >> 40) as u32) / f64::from(u32::MAX) - 0.5) * 2.0 * 5e-4;
Quote::new(k, truth.total_variance(k) + noise, 1.0)
.expect("valid test or documentation fixture")
})
.collect();
let fit = calibrate("es).expect("valid test or documentation fixture");
assert!(fit.rmse() < 1e-2, "rmse = {}", fit.rmse());
let atm_err = (fit.slice().total_variance(0.0) - truth.total_variance(0.0)).abs();
assert!(atm_err < 5e-3, "atm err = {atm_err}");
}
#[test]
fn solve_2x2_identity() {
let (x, y) =
solve_2x2(1.0, 0.0, 1.0, 3.0, 7.0).expect("valid test or documentation fixture");
assert!((x - 3.0).abs() < 1e-15);
assert!((y - 7.0).abs() < 1e-15);
}
#[test]
fn solve_2x2_rejects_singular() {
assert!(solve_2x2(1.0, 1.0, 1.0, 1.0, 1.0).is_none());
}
#[test]
fn normalized_three_by_three_solve_is_invariant_to_column_scale() {
let scales = [1e-100_f64, 1.0, 1e100];
let matrix = [
scales[0] * scales[0],
0.1 * scales[0] * scales[1],
0.2 * scales[0] * scales[2],
scales[1] * scales[1],
0.3 * scales[1] * scales[2],
scales[2] * scales[2],
];
let rhs = [1.8 * scales[0], 3.0 * scales[1], 3.8 * scales[2]];
let (solution, condition) = solve_normalized_spd_3(&matrix, &rhs);
let solution = solution.expect("well-conditioned normalized design");
for ((coefficient, scale), expected) in
solution.into_iter().zip(scales).zip([1.0, 2.0, 3.0])
{
assert!((coefficient * scale - expected).abs() < 1e-12);
}
assert!(condition.is_finite() && condition >= 1.0);
}
#[test]
fn normalized_three_by_three_solve_rejects_relative_rank_loss() {
let (solution, condition) =
solve_normalized_spd_3(&[1.0, 1.0, 1.0, 1.0, 1.0, 1.0], &[1.0; 3]);
assert!(solution.is_none());
assert!(condition.is_infinite() && condition.is_sign_positive());
}
#[test]
fn inner_qp_is_invariant_to_common_weight_rescaling() {
let truth = RawSvi::new(0.04, 0.4, -0.3, 0.05, 0.15).expect("valid test fixture");
let make_quotes = |weight| {
[-0.4, -0.2, 0.0, 0.2, 0.4]
.into_iter()
.map(|k| {
Quote::new(k, truth.total_variance(k), weight)
.expect("finite positive weighted fixture")
})
.collect::<Vec<_>>()
};
let low = inner_solve(&make_quotes(1e-200), 0.05, 0.15, 1.0);
let high = inner_solve(&make_quotes(1e200), 0.05, 0.15, 1.0);
for (left, right) in [low.1, low.2, low.3, low.4]
.into_iter()
.zip([high.1, high.2, high.3, high.4])
{
assert!((left - right).abs() <= 1e-12 * (1.0 + left.abs()));
}
}
}