use crate::errors::QlResult;
use crate::quotes::{Quote, SimpleQuote};
use crate::require;
use crate::shared::Shared;
use crate::termstructures::globalbootstrap::AdditionalBootstrapVariables;
use crate::types::{Real, Size};
pub struct SimpleQuoteVariables {
quotes: Vec<Shared<SimpleQuote>>,
initial_guesses: Vec<Real>,
lower_bounds: Vec<Real>,
}
impl SimpleQuoteVariables {
pub fn new(
quotes: Vec<Shared<SimpleQuote>>,
initial_guesses: Vec<Real>,
lower_bounds: Vec<Real>,
) -> QlResult<SimpleQuoteVariables> {
require!(
initial_guesses.len() <= quotes.len(),
"too many initialGuesses ({}) for {} quotes",
initial_guesses.len(),
quotes.len()
);
require!(
lower_bounds.len() <= quotes.len(),
"too many lowerBounds ({}) for {} quotes",
lower_bounds.len(),
quotes.len()
);
Ok(SimpleQuoteVariables {
quotes,
initial_guesses,
lower_bounds,
})
}
fn transform_direct(&self, x: Real, i: Size) -> Real {
match self.lower_bounds.get(i) {
Some(lower_bound) => x.exp() + lower_bound,
None => x,
}
}
fn transform_inverse(&self, x: Real, i: Size) -> Real {
match self.lower_bounds.get(i) {
Some(lower_bound) => (x - lower_bound).ln(),
None => x,
}
}
}
impl AdditionalBootstrapVariables for SimpleQuoteVariables {
fn initialize(&self, valid_data: bool) -> QlResult<Vec<Real>> {
let mut guesses = Vec::with_capacity(self.quotes.len());
for (i, quote) in self.quotes.iter().enumerate() {
let guess = if valid_data {
quote.value()?
} else {
let initial = self.initial_guesses.get(i).copied().unwrap_or(0.0);
quote.set_value(initial);
initial
};
guesses.push(self.transform_inverse(guess, i));
}
Ok(guesses)
}
fn update(&self, x: &[Real]) -> QlResult<()> {
require!(
x.len() <= self.quotes.len(),
"trial point of size {} for {} quotes",
x.len(),
self.quotes.len()
);
for (i, (quote, value)) in self.quotes.iter().zip(x).enumerate() {
quote.set_value(self.transform_direct(*value, i));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shared::shared;
fn variables(
initial_guesses: Vec<Real>,
lower_bounds: Vec<Real>,
) -> (Shared<SimpleQuote>, SimpleQuoteVariables) {
let quote = shared(SimpleQuote::new(None));
let variables =
SimpleQuoteVariables::new(vec![Shared::clone("e)], initial_guesses, lower_bounds)
.expect("one guess and one bound for one quote");
(quote, variables)
}
#[test]
fn a_lower_bound_puts_the_variable_in_log_space() {
let (quote, variables) = variables(vec![1.25], vec![0.25]);
let guesses = variables.initialize(false).unwrap();
assert_eq!(quote.value().unwrap(), 1.25);
assert!((guesses[0] - Real::ln(1.0)).abs() < 1.0e-15);
variables.update(&[Real::ln(0.75)]).unwrap();
assert!((quote.value().unwrap() - 1.0).abs() < 1.0e-15);
}
#[test]
fn a_variable_without_a_lower_bound_is_untransformed() {
let (quote, variables) = variables(vec![-0.5], Vec::new());
let guesses = variables.initialize(false).unwrap();
assert_eq!(quote.value().unwrap(), -0.5);
assert_eq!(guesses[0], -0.5);
variables.update(&[-2.0]).unwrap();
assert_eq!(quote.value().unwrap(), -2.0);
}
#[test]
fn a_missing_configuration_entry_falls_back_to_its_default() {
let first = shared(SimpleQuote::new(None));
let second = shared(SimpleQuote::new(None));
let variables = SimpleQuoteVariables::new(
vec![Shared::clone(&first), Shared::clone(&second)],
vec![3.0],
Vec::new(),
)
.unwrap();
let guesses = variables.initialize(false).unwrap();
assert_eq!(first.value().unwrap(), 3.0);
assert_eq!(second.value().unwrap(), 0.0);
assert_eq!(guesses, vec![3.0, 0.0]);
}
#[test]
fn valid_data_seeds_the_guess_from_the_current_quote_value() {
let (quote, variables) = variables(vec![1.0], vec![0.0]);
quote.set_value(4.0);
let guesses = variables.initialize(true).unwrap();
assert_eq!(quote.value().unwrap(), 4.0);
assert!((guesses[0] - Real::ln(4.0)).abs() < 1.0e-15);
}
#[test]
fn more_configuration_entries_than_quotes_are_rejected() {
let quote = shared(SimpleQuote::new(None));
assert!(
SimpleQuoteVariables::new(vec![Shared::clone("e)], vec![1.0, 2.0], Vec::new())
.is_err()
);
assert!(SimpleQuoteVariables::new(vec![quote], Vec::new(), vec![0.0, 0.0]).is_err());
}
}