-
-
Notifications
You must be signed in to change notification settings - Fork 173
Natural Cubic Spline Interpolation #301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Hi @yfnaji :) Thanks for this one, splines have been needed for a while! I'll take a closer look tomorrow afternoon, and hopefully merge it |
Hey @avhz - just saw the Clippy warnings, I am working on them now, but still feel free to review and comment on this PR (: |
@yfnaji |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple points
($b:ty, $c:ty) => { | ||
impl IntoValue<$c> for $b { | ||
fn into_value(self) -> $c { | ||
self.as_seconds_f64() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unsure about fixing a unit like this, why seconds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At some point we need to apply some arithmetic operations to the Delta
type and so a complication arises when apply arithmetic operations between an f64
(the ValueType
for time::Date
) and time::Duration
e.g. in lower_tri_transpose_inv_times_diag_inv()
(line 207):
ValueType::one() / diagonal[j].into_value()
where diagonal[j]
is a Delta
type.
As for converting time::Duration
(to f64
), the only possible method that provides this is as_seconds_f64().
We could perhaps create our own conversion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly, we can't do this because then we are setting the units in seconds which would make no sense on a yield curve, for example.
e.g. for a 1 day delta on a curve, the value would be 86,400.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created an ad-hoc commit that removes all references to dates in the interpolation methods. This also required changes to curve.rs
, since the Curve
struct previously relied on date
types. I have updated these to f64
, representing year fractions, similar to the approach in QuantLib.
I also commented out the from_nodes()
method for now, since it requires BTreeMap<OrderedFloat<f64>, f64>
to implement PyFunctionArgument, which seems non-trivial without replacing it with PyDict or another Python object. I may need some help with this part.
All date-removal changes have been grouped into a single commit. If you do not like it, we can revert back to commit b72b0e0
…alue traits + introduce the IntoValue trait
…d attribute to false
…th Cholesky Decomposition
…terpolator implementation
…oots needed for Cholesky decomposition
Hi @avhz This PR has been amended to use Cholesky decomposition and elimination methods instead of LDLT, which has yielded more accurate results (more consistent with SciPy). This, however, introduces an additional trait bound for square roots, needed for the Cholesky decomposition. I also removed date references from the Interpolators, since implementing cubic splines requires interaction between Delta types and Values; some meaningul conversion of time durations necessary. I have needed to amend curve.rs to reflect these changes (see comment). This change may also allow us to simplify Note that I also commented out the I’d appreciate feedback on whether these changes should remain grouped together in this PR, or if it would be clearer to split them into separate PRs. In particular, I’m unsure whether removing dates (and the possible obsolescence of |
Natural Cubic Spline Interpolation
This Pull Request implements the cubic spline interpolation as one of the polynomial interpolators mentioned in issue #5.
The implementation was benchmarked against SciPy’s
CubicSpline
.Mathematical Background
We want to construct a cubic spline$S(x)$ that interpolates the points
Since we are working with a natural cubic spline, we define
the second derivative of the spline at each interpolation point and set
The full derivation of the cubic spline formulation is fairly involved, so some details are omitted here. For reference, the derivation can be found in the lecture notes by T. Gambill (2011) Interpolation/Splines (slides 14-27), which served as the main source for this implementation.
The equation of a natural cubic spline on the interval$x\in\left[x_i, x_{i+1}\right)$ is given by:
(Equation (1))
The next step is to determine suitable values for$z_i$ in order to construct the natural cubic spline.
Differentiating$S_i(x)$ yields
At the interpolation point$x_i$ , the spline segments must join smoothly. This requires the gradients at the interpolation point to be equal on both sides. Hence, the following condition is enforced:
Substituting these expressions and rearranging gives the relation
where
This leads to an$(n-1)\times(n-1)$ system of equations:
where
Now, we can solve the above equation to obtain the values of$z_i$ .
All diagonal and off-diagonal entries of$A$ are positive, implying that $A$ is positive definite. This property guarantees the existence of a Cholesky decomposition, allowing us to factor $A$ as:
We can solve for$\mathbf{z}$ in two steps. First, define $\mathbf{x} := L^T \mathbf{z}$ , and then solve for $\mathbf{x}$ using forward substitution:
Once$\mathbf{x}$ is obtained, solve for $\mathbf{z}$ using backward substitution:
Amendments to the
InterpolationIndex
traitAdditional traits have been added to the type
Delta
:Add
:Delta
+Delta
=Delta
Sub
:Delta
-Delta
=Delta
IntoValue
: Converts aDelta
into anInterpolationValue
. This is required for cubic splines, as the implementation involves arithmetic operations betweenDelta
andInterpolationValue
typesCopy
: Required when unpackingDelta
values from vectorsAmendments to the
InterpolationValue
traitMulAssign
:value_1 *= value_2
num::FromPrimitive
: In order to take anf64
value and convert it to the appropriateInterpolationValue
valueRemoving Unsigned integers from
InterpolationIndex
implementationu8
,u16
,u32
,u64
,u128
, andusize
have been removed from the trait implementation ofInterpolationIndex
because cubic splines can produce negativeDelta
values. The trait implementation fori8
,i16
,i32
,i64
,i128
, andisize
should be sufficient for cases where integers are used as indices.