From 66382290108576776adb8f9e3c18664c9831d60e Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (win11)" Date: Tue, 24 Sep 2024 19:23:53 +0200 Subject: [PATCH 1/4] REF: splines restructure (cherry picked from commit 97cf3de00a4dbbb8d35cb30f74a05b57f68cdbd8) --- rust/curves/interpolation/intp_log_cubic.rs | 91 ++++++++++----------- rust/curves/interpolation/mod.rs | 1 + rust/splines/spline.rs | 58 +++++++++---- rust/splines/spline_py.rs | 2 +- 4 files changed, 84 insertions(+), 68 deletions(-) diff --git a/rust/curves/interpolation/intp_log_cubic.rs b/rust/curves/interpolation/intp_log_cubic.rs index 363388aa..9356d43d 100644 --- a/rust/curves/interpolation/intp_log_cubic.rs +++ b/rust/curves/interpolation/intp_log_cubic.rs @@ -1,7 +1,8 @@ use crate::curves::interpolation::utils::log_linear_interp; use crate::curves::nodes::NodesTimestamp; use crate::curves::CurveInterpolation; -use crate::dual::DualsOrF64; +use crate::dual::{Number, ADOrder, NumberPPSpline, set_order_clone, Dual, Dual2}; +use crate::splines::{PPSplineF64, PPSplineDual, PPSplineDual2}; use bincode::{deserialize, serialize}; use chrono::NaiveDateTime; use pyo3::prelude::*; @@ -12,56 +13,48 @@ use std::cmp::PartialEq; /// Define log-linear interpolation of nodes. #[pyclass(module = "rateslib.rs")] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct LogCubicInterpolator { - spline: T +#[derive(Clone, PartialEq, Serialize, Deserialize)] +pub struct LogCubicInterpolator { + spline: NumberPPSpline } #[pymethods] -impl LogCubicInterpolator -where T: PartialOrd + Signed + Clone + Sum + Zero, - for<'a> &'a T: Sub<&'a T, Output = T>, - for<'a> &'a f64: Mul<&'a T, Output = T>, -{ +impl LogCubicInterpolator { #[new] - pub fn new(t: Vec, c: Option>) -> Self { - let spline: PPSpline = PPSpline.new(3_usize, t, c); - LogCubicInterpolator { - spline - } - } - - // Pickling - pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> { - *self = deserialize(state.as_bytes()).unwrap(); - Ok(()) - } - pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult> { - Ok(PyBytes::new_bound(py, &serialize(&self).unwrap())) - } - pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<(Vec, Option>)> { - Ok((self.t.clone(), )) - } -} - -impl CurveInterpolation for LogLinearInterpolator { - fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> DualsOrF64 { - let x = date.and_utc().timestamp(); - let index = self.node_index(nodes, x); - - macro_rules! interp { - ($Variant: ident, $indexmap: expr) => {{ - let (x1, y1) = $indexmap.get_index(index).unwrap(); - let (x2, y2) = $indexmap.get_index(index + 1_usize).unwrap(); - DualsOrF64::$Variant(log_linear_interp(*x1 as f64, y1, *x2 as f64, y2, x as f64)) - }}; - } - match nodes { - NodesTimestamp::F64(m) => interp!(F64, m), - NodesTimestamp::Dual(m) => interp!(Dual, m), - NodesTimestamp::Dual2(m) => interp!(Dual2, m), + pub fn new(t: Vec, ad: ADOrder, c: Option>) -> Self { + match (c, ad) { + (Some(v), ADOrder::Zero) => { + let c_: Vec = v.iter().map(|x| set_order_clone(&x, ADOrder::Zero, vec![])).map(f64::from).collect(); + let spline = NumberPPSpline::F64(PPSplineF64::new(3, t, Some(c_))); + LogCubicInterpolator {spline} + } + (Some(v), ADOrder::One) => { + let c_: Vec = v.iter().map(|x| set_order_clone(&x, ADOrder::One, vec![])).map(Dual::from).collect(); + let spline = NumberPPSpline::Dual(PPSplineDual::new(3, t, Some(c_))); + LogCubicInterpolator {spline} + } + (Some(v), ADOrder::Two) => { + let c_: Vec = v.iter().map(|x| set_order_clone(&x, ADOrder::Zero, vec![])).map(Dual2::from).collect(); + let spline = NumberPPSpline::Dual2(PPSplineDual2::new(3, t, Some(c_))); + LogCubicInterpolator {spline} + } + (None, ADOrder::Zero) => {LogCubicInterpolator {spline: NumberPPSpline::F64(PPSplineF64::new(3, t, None))}}, + (None, ADOrder::One) => {LogCubicInterpolator {spline: NumberPPSpline::Dual(PPSplineDual::new(3, t, None))}}, + (None, ADOrder::Two) => {LogCubicInterpolator {spline: NumberPPSpline::Dual2(PPSplineDual2::new(3, t, None))}}, } } + // + // // Pickling + // pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> { + // *self = deserialize(state.as_bytes()).unwrap(); + // Ok(()) + // } + // pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult> { + // Ok(PyBytes::new_bound(py, &serialize(&self).unwrap())) + // } + // pub fn __getnewargs__<'py>(&self, py: Python<'py>) -> PyResult<(Vec, Option>)> { + // Ok((self.t.clone(), )) + // } } #[cfg(test)] @@ -81,11 +74,9 @@ mod tests { } #[test] - fn test_log_linear() { + fn test_log_cubic() { let nts = nodes_timestamp_fixture(); - let ll = LogLinearInterpolator::new(); - let result = ll.interpolated_value(&nts, &ndt(2000, 7, 1)); - // expected = exp(0 + (182 / 366) * (ln(0.99) - ln(1.0)) = 0.995015 - assert_eq!(result, DualsOrF64::F64(0.9950147597711371)); + let t = vec![1.0,1.0,1.0,1.0, 2.0, 3.0,3.0,3.0,3.0]; + let ll = LogCubicInterpolator::new(t, ADOrder::Zero, None); } } diff --git a/rust/curves/interpolation/mod.rs b/rust/curves/interpolation/mod.rs index e986f62c..e57f7574 100644 --- a/rust/curves/interpolation/mod.rs +++ b/rust/curves/interpolation/mod.rs @@ -5,6 +5,7 @@ pub(crate) mod intp_flat_forward; pub(crate) mod intp_linear; pub(crate) mod intp_linear_zero_rate; pub(crate) mod intp_log_linear; +pub(crate) mod intp_log_cubic; pub(crate) mod intp_null; pub(crate) mod utils; diff --git a/rust/splines/spline.rs b/rust/splines/spline.rs index f8aca13a..7550c7f3 100644 --- a/rust/splines/spline.rs +++ b/rust/splines/spline.rs @@ -399,26 +399,50 @@ where } } -/// Definitive [f64] type variant of a [PPSpline]. -#[pyclass(module = "rateslib.rs")] -#[derive(Clone, Deserialize, Serialize)] -pub struct PPSplineF64 { - pub(crate) inner: PPSpline, -} +// /// Definitive [f64] type variant of a [PPSpline]. +// #[pyclass(module = "rateslib.rs")] +// #[derive(Clone, Deserialize, Serialize)] +// pub struct PPSplineF64 { +// pub(crate) inner: PPSpline, +// } +// +// /// Definitive [Dual] type variant of a [PPSpline]. +// #[pyclass(module = "rateslib.rs")] +// #[derive(Clone, Deserialize, Serialize)] +// pub struct PPSplineDual { +// pub(crate) inner: PPSpline, +// } +// +// /// Definitive [Dual2] type variant of a [PPSpline]. +// #[pyclass(module = "rateslib.rs")] +// #[derive(Clone, Deserialize, Serialize)] +// pub struct PPSplineDual2 { +// pub(crate) inner: PPSpline, +// } + +macro_rules! define_specific_type_splines { + ($name: ident, $type: ident) => { + + #[doc = concat!("Definitive [", stringify!($type), "] type variant of a [PPSpline]")] + #[pyclass(module = "rateslib.rs")] + #[derive(Clone, Deserialize, Serialize)] + pub struct $name { + pub(crate) inner: PPSpline<$type>, + } -/// Definitive [Dual] type variant of a [PPSpline]. -#[pyclass(module = "rateslib.rs")] -#[derive(Clone, Deserialize, Serialize)] -pub struct PPSplineDual { - pub(crate) inner: PPSpline, + impl $name { + pub fn new(k: usize, t: Vec, c: Option>) -> Self { + Self { + inner: PPSpline::new(k, t, c), + } + } + } + } } +define_specific_type_splines!(PPSplineF64, f64); +define_specific_type_splines!(PPSplineDual, Dual); +define_specific_type_splines!(PPSplineDual2, Dual2); -/// Definitive [Dual2] type variant of a [PPSpline]. -#[pyclass(module = "rateslib.rs")] -#[derive(Clone, Deserialize, Serialize)] -pub struct PPSplineDual2 { - pub(crate) inner: PPSpline, -} impl PartialEq for PPSplineF64 { /// Equality of `PPSplineF64` if diff --git a/rust/splines/spline_py.rs b/rust/splines/spline_py.rs index 1e83e864..cb7bee7f 100644 --- a/rust/splines/spline_py.rs +++ b/rust/splines/spline_py.rs @@ -17,7 +17,7 @@ macro_rules! create_interface { #[pymethods] impl $name { #[new] - fn new(k: usize, t: Vec, c: Option>) -> Self { + fn new_py(k: usize, t: Vec, c: Option>) -> Self { Self { inner: PPSpline::new(k, t, c), } From cb742cf6549510bf0e1451e419b58b35ae626a46 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (win11)" Date: Tue, 24 Sep 2024 22:07:07 +0200 Subject: [PATCH 2/4] REF: splines restructure --- rust/curves/interpolation/intp_log_cubic.rs | 11 ++--- rust/curves/nodes.rs | 54 ++++++++++----------- rust/splines/spline.rs | 21 -------- 3 files changed, 31 insertions(+), 55 deletions(-) diff --git a/rust/curves/interpolation/intp_log_cubic.rs b/rust/curves/interpolation/intp_log_cubic.rs index 9356d43d..f42fc3f2 100644 --- a/rust/curves/interpolation/intp_log_cubic.rs +++ b/rust/curves/interpolation/intp_log_cubic.rs @@ -1,13 +1,9 @@ -use crate::curves::interpolation::utils::log_linear_interp; use crate::curves::nodes::NodesTimestamp; use crate::curves::CurveInterpolation; use crate::dual::{Number, ADOrder, NumberPPSpline, set_order_clone, Dual, Dual2}; use crate::splines::{PPSplineF64, PPSplineDual, PPSplineDual2}; -use bincode::{deserialize, serialize}; -use chrono::NaiveDateTime; use pyo3::prelude::*; -use pyo3::types::{PyBytes, PyTuple}; -use pyo3::{pyclass, pymethods, Bound, PyResult, Python}; +use pyo3::{pyclass, pymethods}; use serde::{Deserialize, Serialize}; use std::cmp::PartialEq; @@ -76,7 +72,10 @@ mod tests { #[test] fn test_log_cubic() { let nts = nodes_timestamp_fixture(); - let t = vec![1.0,1.0,1.0,1.0, 2.0, 3.0,3.0,3.0,3.0]; + let s = nts.get_index_as_f64(0).0; + let m = nts.get_index_as_f64(1).0; + let e = nts.get_index_as_f64(2).0; + let t = vec![s, s, s, s, m, e, e, e, e]; let ll = LogCubicInterpolator::new(t, ADOrder::Zero, None); } } diff --git a/rust/curves/nodes.rs b/rust/curves/nodes.rs index edbfc832..84db1ce6 100644 --- a/rust/curves/nodes.rs +++ b/rust/curves/nodes.rs @@ -19,16 +19,6 @@ pub enum NodesTimestamp { Dual2(IndexMap), } -impl NodesTimestamp { - pub fn first_key(&self) -> i64 { - match self { - NodesTimestamp::F64(m) => *m.first().unwrap().0, - NodesTimestamp::Dual(m) => *m.first().unwrap().0, - NodesTimestamp::Dual2(m) => *m.first().unwrap().0, - } - } -} - impl From for NodesTimestamp { fn from(value: Nodes) -> Self { match value { @@ -76,6 +66,14 @@ impl NodesTimestamp { // } // } + pub fn first_key(&self) -> i64 { + match self { + NodesTimestamp::F64(m) => *m.first().unwrap().0, + NodesTimestamp::Dual(m) => *m.first().unwrap().0, + NodesTimestamp::Dual2(m) => *m.first().unwrap().0, + } + } + pub(crate) fn sort_keys(&mut self) { match self { NodesTimestamp::F64(m) => m.sort_keys(), @@ -92,6 +90,24 @@ impl NodesTimestamp { } } + /// Refactors the `get_index` method of an IndexMap and type casts the return values. + pub(crate) fn get_index_as_f64(&self, index: usize) -> (f64, Number) { + match self { + NodesTimestamp::F64(m) => { + let (k, v) = m.get_index(index).unwrap(); + (*k as f64, Number::F64(*v)) + }, + NodesTimestamp::Dual(m) => { + let (k, v) = m.get_index(index).unwrap(); + (*k as f64, Number::Dual(v.clone())) + }, + NodesTimestamp::Dual2(m) => { + let (k, v) = m.get_index(index).unwrap(); + (*k as f64, Number::Dual2(v.clone())) + }, + } + } + pub(crate) fn index_map(&self) -> IndexMap { macro_rules! create_map { ($map:ident, $Variant:ident) => { @@ -111,21 +127,3 @@ impl NodesTimestamp { } } } - -// /// Refactors the `get_index` method of an IndexMap and type casts the return values. -// pub(crate) fn get_index_as_f64(&self, index: usize) -> (f64, Number) { -// match self { -// NodesTimestamp::F64(m) => { -// let (k, v) = m.get_index(index).unwrap(); -// (*k as f64, Number::F64(*v)) -// }, -// NodesTimestamp::Dual(m) => { -// let (k, v) = m.get_index(index).unwrap(); -// (*k as f64, Number::Dual(v.clone())) -// }, -// NodesTimestamp::Dual2(m) => { -// let (k, v) = m.get_index(index).unwrap(); -// (*k as f64, Number::Dual2(v.clone())) -// }, -// } -// } diff --git a/rust/splines/spline.rs b/rust/splines/spline.rs index 7550c7f3..34abb842 100644 --- a/rust/splines/spline.rs +++ b/rust/splines/spline.rs @@ -399,27 +399,6 @@ where } } -// /// Definitive [f64] type variant of a [PPSpline]. -// #[pyclass(module = "rateslib.rs")] -// #[derive(Clone, Deserialize, Serialize)] -// pub struct PPSplineF64 { -// pub(crate) inner: PPSpline, -// } -// -// /// Definitive [Dual] type variant of a [PPSpline]. -// #[pyclass(module = "rateslib.rs")] -// #[derive(Clone, Deserialize, Serialize)] -// pub struct PPSplineDual { -// pub(crate) inner: PPSpline, -// } -// -// /// Definitive [Dual2] type variant of a [PPSpline]. -// #[pyclass(module = "rateslib.rs")] -// #[derive(Clone, Deserialize, Serialize)] -// pub struct PPSplineDual2 { -// pub(crate) inner: PPSpline, -// } - macro_rules! define_specific_type_splines { ($name: ident, $type: ident) => { From 7dccbe98eb1430ce3bead8a2228a2f22ffc3c78d Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (win)" <24256554+attack68@users.noreply.github.com> Date: Wed, 25 Sep 2024 10:29:41 +0200 Subject: [PATCH 3/4] calibrate method --- rust/curves/curve.rs | 5 +++++ rust/curves/interpolation/intp_log_cubic.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rust/curves/curve.rs b/rust/curves/curve.rs index 11f73bc2..3605478d 100644 --- a/rust/curves/curve.rs +++ b/rust/curves/curve.rs @@ -32,6 +32,11 @@ pub trait CurveInterpolation { // let timestamp = date.and_utc().timestamp(); index_left(&nodes.keys(), &date_timestamp, None) } + + /// Calibrate the interpolator to the Curve nodes if necessary + fn calibrate(&self, nodes: &NodesTimestamp) -> Result<(), PyErr> { + Ok(()) + } } impl CurveDF { diff --git a/rust/curves/interpolation/intp_log_cubic.rs b/rust/curves/interpolation/intp_log_cubic.rs index f42fc3f2..c4504f80 100644 --- a/rust/curves/interpolation/intp_log_cubic.rs +++ b/rust/curves/interpolation/intp_log_cubic.rs @@ -7,7 +7,7 @@ use pyo3::{pyclass, pymethods}; use serde::{Deserialize, Serialize}; use std::cmp::PartialEq; -/// Define log-linear interpolation of nodes. +/// Define log-cubic interpolation of nodes. #[pyclass(module = "rateslib.rs")] #[derive(Clone, PartialEq, Serialize, Deserialize)] pub struct LogCubicInterpolator { From ba8855a8c69dbf9c99a46bf51af799f4d01f7303 Mon Sep 17 00:00:00 2001 From: "JHM Darbyshire (win)" <24256554+attack68@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:38:44 +0200 Subject: [PATCH 4/4] try to calibrate --- rust/curves/interpolation/intp_log_cubic.rs | 17 +++++++++++++++++ rust/dual/enums.rs | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/rust/curves/interpolation/intp_log_cubic.rs b/rust/curves/interpolation/intp_log_cubic.rs index c4504f80..4cbeacfe 100644 --- a/rust/curves/interpolation/intp_log_cubic.rs +++ b/rust/curves/interpolation/intp_log_cubic.rs @@ -2,6 +2,7 @@ use crate::curves::nodes::NodesTimestamp; use crate::curves::CurveInterpolation; use crate::dual::{Number, ADOrder, NumberPPSpline, set_order_clone, Dual, Dual2}; use crate::splines::{PPSplineF64, PPSplineDual, PPSplineDual2}; +use chrono::NaiveDateTime; use pyo3::prelude::*; use pyo3::{pyclass, pymethods}; use serde::{Deserialize, Serialize}; @@ -53,6 +54,22 @@ impl LogCubicInterpolator { // } } +impl CurveInterpolation for LogCubicInterpolator { + fn interpolated_value(&self, nodes: &NodesTimestamp, date: &NaiveDateTime) -> Number { + Number::F64(2.3) + } + + /// Calibrate the interpolator to the Curve nodes if necessary + fn calibrate(&self, nodes: &NodesTimestamp) -> Result<(), PyErr> { + // will call csolve on the spline with the appropriate data. + let t = self.spline.t().clone(); + let t_min = t[0]; let t_max = t[t.len()-1]; + + let f = nodes.clone().iter().filter(|(k,v)| (k as f64) >= t_min && (k as f64) <= t_max); + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/dual/enums.rs b/rust/dual/enums.rs index 37e54baa..29a0d0bc 100644 --- a/rust/dual/enums.rs +++ b/rust/dual/enums.rs @@ -56,6 +56,17 @@ pub enum NumberPPSpline { Dual2(PPSplineDual2), } +impl NumberPPSpline { + /// Return a reference to the knot sequence of the inner pp-spline. + pub fn t(&self) -> &Vec { + match self { + NumberPPSpline::F64(s) => s.inner.t(), + NumberPPSpline::Dual(s) => s.inner.t(), + NumberPPSpline::Dual2(s) => s.inner.t(), + } + } +} + /// Generic trait indicating a function exists to map one [Number] to another. /// /// An example of this trait is used by certain [PPSpline] indicating that an x-value as