libitofin 0.52.1

A ground-up Rust port of QuantLib: quantitative-finance primitives for pricing, risk, and numerical methods.
Documentation
use super::*;
use crate::models::calibrationhelper::CalibrationHelper;
use crate::models::model::{CalibratedModel, CalibratedModelHolder, calibrate};
use crate::models::parameter::ConstantParameter;
use crate::shared::{SharedMut, shared_mut};
use std::rc::Rc;

struct LinearModel(CalibratedModel);
impl CalibratedModelHolder for LinearModel {
    fn calibrated_model(&self) -> &CalibratedModel {
        &self.0
    }
    fn calibrated_model_mut(&mut self) -> &mut CalibratedModel {
        &mut self.0
    }
}
struct LinearHelper {
    model: SharedMut<LinearModel>,
    index: usize,
    target: f64,
    calls: Rc<Cell<usize>>,
    fail: bool,
}
impl CalibrationHelper for LinearHelper {
    fn calibration_error(&mut self) -> QlResult<f64> {
        self.calls.set(self.calls.get() + 1);
        let model = self.model.borrow();
        let params = model.0.params();
        assert_eq!(params[1], 3.0);
        if self.fail {
            return Err(ql_error("test pricing failure"));
        }
        Ok(params[self.index] - self.target)
    }
}
fn linear_model() -> SharedMut<LinearModel> {
    let mut model = CalibratedModel::new(3);
    for (index, value) in [0.5, 3.0, 12.0].into_iter().enumerate() {
        model.arguments_mut()[index] =
            ConstantParameter::new(value, Rc::new(NoConstraint)).unwrap();
    }
    shared_mut(LinearModel(model))
}
fn linear_helpers(
    model: &SharedMut<LinearModel>,
    fail: bool,
) -> (Vec<SharedMut<dyn CalibrationHelper>>, Rc<Cell<usize>>) {
    let calls = Rc::new(Cell::new(0));
    let helpers = [(0, 0.25), (2, 14.0)]
        .into_iter()
        .map(|(index, target)| {
            shared_mut(LinearHelper {
                model: model.clone(),
                index,
                target,
                calls: calls.clone(),
                fail,
            }) as SharedMut<dyn CalibrationHelper>
        })
        .collect();
    (helpers, calls)
}
fn linear_method(common: Common) -> Firefly {
    Firefly::new(
        Bounds {
            lower: vec![-1.0, 10.0],
            upper: vec![1.0, 20.0],
        },
        FireflyOptions::default(),
        common,
    )
    .unwrap()
}

#[test]
fn projected_nonadjacent_parameters_preserve_weighted_rss_and_true_counts() {
    let model = linear_model();
    let (helpers, calls) = linear_helpers(&model, false);
    let mut method = linear_method(Common::default());
    calibrate(
        &model,
        &helpers,
        &mut method,
        &criteria(),
        None,
        vec![4.0, 9.0],
        vec![false, true, false],
    )
    .unwrap();
    let result = method.last_result().unwrap();
    let model = model.borrow();
    let params = model.0.params();
    assert_eq!(params[1], 3.0);
    assert!((params[0] - 0.25).abs() < 1e-4);
    assert!((params[2] - 14.0).abs() < 1e-4);
    assert_eq!(result.x, vec![params[0], params[2]]);
    assert_eq!(model.0.function_evaluation() as usize, result.nfev + 1);
    assert_eq!(calls.get(), 2 * (result.nfev + 1));
    let expected = (4.0 * (params[0] - 0.25).powi(2) + 9.0 * (params[2] - 14.0).powi(2)).sqrt();
    assert!((result.fun - expected).abs() < 1e-12);
}

#[test]
fn evaluation_budget_returns_root_sum_of_squares_not_residual_mean() {
    let model = linear_model();
    let (helpers, calls) = linear_helpers(&model, false);
    let mut method = linear_method(Common {
        maxfev: Some(1),
        ..Common::default()
    });
    calibrate(
        &model,
        &helpers,
        &mut method,
        &criteria(),
        None,
        vec![4.0, 9.0],
        vec![false, true, false],
    )
    .unwrap();
    let result = method.last_result().unwrap();
    assert_eq!(
        (result.status, result.nit, result.nfev),
        (Termination::MaxEvaluations, 0, 1)
    );
    assert!((result.fun - 36.25_f64.sqrt()).abs() < 1e-12);
    assert_eq!(model.borrow().0.end_criteria(), EndCriteriaType::Unknown);
    assert_eq!(model.borrow().0.function_evaluation(), 2);
    assert_eq!(calls.get(), 4);
}

#[test]
fn coupled_candidate_constraint_aborts_before_pricing_and_restores_model() {
    struct OnlyInitial;
    impl Constraint for OnlyInitial {
        fn test(&self, x: &Array) -> bool {
            x[0] == 0.5 && x[2] == 12.0
        }
    }
    let model = linear_model();
    let original = model.borrow().0.params();
    let (helpers, calls) = linear_helpers(&model, false);
    let mut method = linear_method(Common::default());
    let error = calibrate(
        &model,
        &helpers,
        &mut method,
        &criteria(),
        Some(Box::new(OnlyInitial)),
        vec![],
        vec![false, true, false],
    )
    .unwrap_err();
    assert!(error.message().contains("wholly feasible box"));
    assert_eq!(calls.get(), 2);
    assert_eq!(model.borrow().0.params(), original);
    assert!(method.last_result().is_none());
    assert_eq!(model.borrow().0.end_criteria(), EndCriteriaType::None);
    assert_eq!(model.borrow().0.function_evaluation(), 0);
}

#[test]
fn pricing_failure_restores_model_and_preserves_nonfinite_diagnostic() {
    let model = linear_model();
    let original = model.borrow().0.params();
    let (helpers, calls) = linear_helpers(&model, true);
    let mut method = linear_method(Common::default());
    assert!(
        calibrate(
            &model,
            &helpers,
            &mut method,
            &criteria(),
            None,
            vec![],
            vec![false, true, false]
        )
        .is_err()
    );
    assert_eq!(calls.get(), 1);
    assert_eq!(model.borrow().0.params(), original);
    let result = method.last_result().unwrap();
    assert_eq!((result.status, result.nfev), (Termination::Nonfinite, 1));
}

#[test]
fn model_preflight_rejection_retains_previous_diagnostics_without_pricing() {
    let model = linear_model();
    let (helpers, calls) = linear_helpers(&model, false);
    let mut method = linear_method(Common {
        maxfev: Some(1),
        ..Common::default()
    });
    calibrate(
        &model,
        &helpers,
        &mut method,
        &criteria(),
        None,
        vec![],
        vec![false, true, false],
    )
    .unwrap();
    let result = method.last_result().unwrap().clone();
    let params = model.borrow().0.params();
    let count = calls.get();
    for (weights, fixed) in [
        (vec![-1.0, 1.0], vec![false, true, false]),
        (vec![], vec![true, true, true]),
        (vec![], vec![false]),
    ] {
        assert!(
            calibrate(
                &model,
                &helpers,
                &mut method,
                &criteria(),
                None,
                weights,
                fixed
            )
            .is_err()
        );
        assert_eq!(method.last_result(), Some(&result));
        assert_eq!(model.borrow().0.params(), params);
        assert_eq!(calls.get(), count);
    }
}