use std::cell::RefCell;
use crate::errors::{QlError, QlResult};
use crate::math::array::Array;
use crate::math::interpolations::{Interpolator, LocalInterpolator};
use crate::math::optimization::constraint::{Constraint, NoConstraint, PositiveConstraint};
use crate::math::optimization::costfunction::CostFunction;
use crate::math::optimization::endcriteria::EndCriteria;
use crate::math::optimization::levenbergmarquardt::LevenbergMarquardt;
use crate::math::optimization::method::OptimizationMethod;
use crate::math::optimization::problem::Problem;
use crate::require;
use crate::shared::Shared;
use crate::termstructures::bootstraphelper::{BootstrapHelperShared, sort_by_pillar_date};
use crate::termstructures::bootstraptraits::BootstrapTraits;
use crate::termstructures::iterativebootstrap::{Bootstrap, PiecewiseCurve};
use crate::types::{Real, Size};
#[derive(Clone, Copy, Debug)]
pub struct LocalBootstrap {
localisation: Size,
force_positive: bool,
accuracy: Option<Real>,
}
impl LocalBootstrap {
pub fn new(localisation: Size, force_positive: bool, accuracy: Option<Real>) -> LocalBootstrap {
LocalBootstrap {
localisation,
force_positive,
accuracy,
}
}
}
impl Default for LocalBootstrap {
fn default() -> LocalBootstrap {
LocalBootstrap::new(2, true, None)
}
}
struct LocalCost<'a, C: PiecewiseCurve>
where
C::Interp: LocalInterpolator,
{
curve: &'a C,
window: &'a [Shared<C::Helper>],
initial_data_pt: Size,
i_inst: Size,
localisation: Size,
n_insts: Size,
step_prev: Option<&'a <C::Interp as Interpolator>::Output>,
error: RefCell<Option<QlError>>,
}
impl<C: PiecewiseCurve> LocalCost<'_, C>
where
C::Interp: LocalInterpolator,
{
fn try_values(&self, x: &Array) -> QlResult<Array> {
{
let mut cd = self.curve.curve_data().borrow_mut();
for k in 0..x.size() {
C::Traits::update_guess(cd.data_mut(), x[k], self.initial_data_pt + k);
}
let interpolation = self.curve.interpolator().local_interpolate(
&cd.times()[..self.i_inst + 2],
&cd.data()[..self.i_inst + 2],
self.localisation,
self.step_prev,
self.n_insts + 1,
)?;
cd.set_interpolation(interpolation);
}
let mut penalties = Array::with_size(self.localisation);
for (k, helper) in self.window.iter().enumerate() {
penalties[k] = helper.quote_error()?;
}
Ok(penalties)
}
}
impl<C: PiecewiseCurve> CostFunction for LocalCost<'_, C>
where
C::Interp: LocalInterpolator,
{
fn values(&self, x: &Array) -> Array {
match self.try_values(x) {
Ok(values) => values,
Err(err) => {
let mut slot = self.error.borrow_mut();
if slot.is_none() {
*slot = Some(err);
}
std::iter::repeat_n(Real::NAN, self.localisation).collect()
}
}
}
}
impl<C: PiecewiseCurve> Bootstrap<C> for LocalBootstrap
where
C::Interp: LocalInterpolator,
{
fn calculate(&self, curve: &C) -> QlResult<()> {
let mut helpers: Vec<Shared<C::Helper>> = curve.instruments().to_vec();
let n_insts = helpers.len();
let required = curve.interpolator().required_points();
require!(
n_insts >= required,
"not enough instruments: {n_insts} provided, {required} required"
);
require!(
n_insts > self.localisation,
"not enough instruments: {n_insts} provided, {} required.",
self.localisation
);
sort_by_pillar_date(&mut helpers);
for i in 1..n_insts {
let m1 = helpers[i - 1].pillar_date();
let m2 = helpers[i].pillar_date();
require!(m1 != m2, "two instruments have the same pillar date ({m1})");
}
for (i, helper) in helpers.iter().enumerate() {
if helper.quote_value().is_err() {
crate::fail!(
"instrument {} (maturity: {}, pillar: {}) has an invalid quote",
i + 1,
helper.maturity_date(),
helper.pillar_date()
);
}
}
let term_structure = curve.term_structure_shared()?;
for helper in &helpers {
helper.set_term_structure(&term_structure);
}
let first_date = curve.initial_date()?;
let initial_value = curve.initial_value()?;
let mut dates = Vec::with_capacity(n_insts + 1);
let mut times = Vec::with_capacity(n_insts + 1);
dates.push(first_date);
times.push(curve.time_from_reference(first_date)?);
let mut max_date = first_date;
for helper in &helpers {
let pillar = helper.pillar_date();
dates.push(pillar);
times.push(curve.time_from_reference(pillar)?);
max_date = max_date.max(pillar.max(helper.latest_relevant_date()));
}
{
let mut cd = curve.curve_data().borrow_mut();
cd.set_pillars(dates, times);
cd.reset_data(initial_value, n_insts + 1);
cd.set_max_date(max_date);
}
let accuracy = self.accuracy.unwrap_or_else(|| curve.accuracy());
let mut solver = LevenbergMarquardt::new(accuracy, accuracy, accuracy, false);
let end_criteria = EndCriteria::new(100, Some(10), 0.0, accuracy, Some(0.0))?;
let positive = PositiveConstraint;
let unconstrained = NoConstraint;
let constraint: &dyn Constraint = if self.force_positive {
&positive
} else {
&unconstrained
};
let data_adjust = <C::Interp as LocalInterpolator>::DATA_SIZE_ADJUSTMENT;
let mut step_prev: Option<<C::Interp as Interpolator>::Output> = None;
let mut i_inst = self.localisation - 1;
loop {
let initial_data_pt = i_inst + 1 - self.localisation + data_adjust;
let mut start_array = Array::with_size(self.localisation + 1 - data_adjust);
{
let cd = curve.curve_data().borrow();
for j in 0..start_array.size() - 1 {
start_array[j] = cd.data()[initial_data_pt + j];
}
let entry = curve.interpolator().local_interpolate(
&cd.times()[..i_inst + 2],
&cd.data()[..i_inst + 2],
self.localisation,
step_prev.as_ref(),
n_insts + 1,
)?;
start_array[self.localisation - data_adjust] = if i_inst >= self.localisation {
C::Traits::guess(i_inst, cd.times(), cd.data(), false)
} else {
cd.data()[0]
};
drop(cd);
curve.curve_data().borrow_mut().set_interpolation(entry);
}
let window = &helpers[i_inst + 1 - self.localisation..i_inst + 1];
let cost = LocalCost::<C> {
curve,
window,
initial_data_pt,
i_inst,
localisation: self.localisation,
n_insts,
step_prev: step_prev.as_ref(),
error: RefCell::new(None),
};
let (end_type, solution) = {
let mut problem = Problem::new(&cost, constraint, start_array);
let outcome = solver.minimize(&mut problem, &end_criteria);
(outcome, problem.current_value().clone())
};
if let Some(inner) = cost.error.into_inner() {
return Err(inner);
}
let end_type = end_type?;
require!(
end_type.succeeded(),
"Unable to strip yieldcurve to required accuracy: {end_type}"
);
{
let mut cd = curve.curve_data().borrow_mut();
for k in 0..solution.size() {
C::Traits::update_guess(cd.data_mut(), solution[k], initial_data_pt + k);
}
let for_curve = curve.interpolator().local_interpolate(
&cd.times()[..i_inst + 2],
&cd.data()[..i_inst + 2],
self.localisation,
step_prev.as_ref(),
n_insts + 1,
)?;
let next_prev = curve.interpolator().local_interpolate(
&cd.times()[..i_inst + 2],
&cd.data()[..i_inst + 2],
self.localisation,
step_prev.as_ref(),
n_insts + 1,
)?;
cd.set_interpolation(for_curve);
step_prev = Some(next_prev);
}
i_inst += 1;
if i_inst >= n_insts {
break;
}
}
Ok(())
}
}