use crate::math::array::Array;
use crate::methods::finitedifferences::StepCondition;
use crate::methods::finitedifferences::meshers::FdmMesher;
use crate::methods::finitedifferences::utilities::FdmInnerValueCalculator;
use crate::shared::Shared;
use crate::time::date::Date;
use crate::time::daycounter::DayCounter;
use crate::types::Time;
pub struct FdmBermudanStepCondition {
mesher: Shared<dyn FdmMesher>,
calculator: Shared<dyn FdmInnerValueCalculator>,
exercise_times: Vec<Time>,
}
impl FdmBermudanStepCondition {
pub fn new(
exercise_dates: &[Date],
reference_date: Date,
day_counter: &DayCounter,
mesher: Shared<dyn FdmMesher>,
calculator: Shared<dyn FdmInnerValueCalculator>,
) -> FdmBermudanStepCondition {
let exercise_times = exercise_dates
.iter()
.map(|date| day_counter.year_fraction(reference_date, *date))
.collect();
FdmBermudanStepCondition {
mesher,
calculator,
exercise_times,
}
}
pub fn exercise_times(&self) -> &[Time] {
&self.exercise_times
}
}
impl StepCondition for FdmBermudanStepCondition {
fn apply_to(&self, a: &mut Array, t: Time) {
if !self.exercise_times.contains(&t) {
return;
}
let layout = self.mesher.layout();
assert_eq!(layout.size(), a.size(), "inconsistent array dimensions");
for iter in layout.iter() {
let inner_value = self.calculator.inner_value(&iter, t);
if inner_value > a[iter.index()] {
a[iter.index()] = inner_value;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::methods::finitedifferences::meshers::UniformGridMesher;
use crate::methods::finitedifferences::operators::{FdmLinearOpIterator, FdmLinearOpLayout};
use crate::shared::shared;
use crate::time::date::Month;
use crate::time::daycounters::actual360::Actual360;
use crate::types::Real;
struct TabulatedInnerValue {
values: Vec<Real>,
}
impl FdmInnerValueCalculator for TabulatedInnerValue {
fn inner_value(&self, iter: &FdmLinearOpIterator, _t: Time) -> Real {
self.values[iter.index()]
}
fn avg_inner_value(&self, iter: &FdmLinearOpIterator, t: Time) -> Real {
self.inner_value(iter, t)
}
}
fn reference() -> Date {
Date::new(15, Month::June, 2026)
}
fn condition(values: Vec<Real>, exercise_dates: &[Date]) -> FdmBermudanStepCondition {
let layout = shared(FdmLinearOpLayout::new(vec![values.len()]));
let mesher = shared(UniformGridMesher::new(layout, &[(0.0, 1.0)]).unwrap());
FdmBermudanStepCondition::new(
exercise_dates,
reference(),
&Actual360::new(),
mesher as Shared<dyn FdmMesher>,
shared(TabulatedInnerValue { values }) as Shared<dyn FdmInnerValueCalculator>,
)
}
#[test]
fn the_dates_are_mapped_onto_the_day_counters_clock() {
let condition = condition(vec![1.0], &[reference() + 90, reference() + 180]);
assert_eq!(condition.exercise_times(), &[0.25, 0.5]);
}
#[test]
fn values_below_the_intrinsic_value_are_lifted_at_an_exercise_time() {
let condition = condition(vec![3.0, 1.0, 0.0], &[reference() + 180]);
let mut values = Array::from([2.0, 1.5, 0.0]);
condition.apply_to(&mut values, 0.5);
assert_eq!(values, Array::from([3.0, 1.5, 0.0]));
}
#[test]
fn nothing_is_lifted_between_the_exercise_times() {
let condition = condition(vec![3.0, 1.0, 0.0], &[reference() + 180]);
let mut values = Array::from([2.0, 1.5, 0.0]);
let before = values.clone();
condition.apply_to(&mut values, 0.4);
assert_eq!(values, before);
}
#[test]
fn every_exercise_time_fires() {
let condition = condition(vec![3.0], &[reference() + 90, reference() + 180]);
for time in [0.25, 0.5] {
let mut values = Array::from([2.0]);
condition.apply_to(&mut values, time);
assert_eq!(values, Array::from([3.0]), "at {time}");
}
}
}