Skip to content

Commit 3d1695c

Browse files
committed
feat: add BackwardflatLinear and FlatExtrapolator2D interpolation bindings
1 parent cc6732c commit 3d1695c

9 files changed

Lines changed: 195 additions & 2 deletions

File tree

docs/api/math.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,29 @@ y = [0.01, 0.015, 0.02, 0.025, 0.03, 0.035]
318318
interp = ql.ConvexMonotoneInterpolation(x, y, quadraticity=0.3, monotonicity=0.7)
319319
```
320320

321+
### BackwardflatLinearInterpolation
322+
323+
```{eval-rst}
324+
.. autoclass:: pyquantlib.BackwardflatLinearInterpolation
325+
```
326+
327+
2-D interpolation: backward-flat in the first component, linear in the second.
328+
329+
### FlatExtrapolator2D
330+
331+
```{eval-rst}
332+
.. autoclass:: pyquantlib.FlatExtrapolator2D
333+
```
334+
335+
Decorator that adds flat extrapolation outside the range of any 2-D interpolation.
336+
337+
```python
338+
inner = ql.BilinearInterpolation(x, y, z)
339+
extrap = ql.FlatExtrapolator2D(inner)
340+
extrap.enableExtrapolation()
341+
extrap(0.0, 0.0) # clamps to boundary value
342+
```
343+
321344
### ForwardFlatInterpolation
322345

323346
```{eval-rst}

docs/changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- `LogMixedLinearCubicNaturalSpline` convenience class
2424
- `SABRInterpolation` SABR smile interpolation with calibration (alpha, beta, nu, rho accessors, RMS/max error, end criteria)
2525
- `ConvexMonotoneInterpolation` convex monotone yield-curve interpolation (Hagan & West, 2006)
26+
- `BackwardflatLinearInterpolation` 2-D backward-flat/linear interpolation
27+
- `FlatExtrapolator2D` 2-D flat extrapolation decorator
2628

2729
## [0.7.0] - 2026-03-14
2830

include/pyquantlib/pyquantlib.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ namespace ql_math {
101101
void loginterpolation(py::module_&);
102102
void sabrinterpolation(py::module_&);
103103
void convexmonotoneinterpolation(py::module_&);
104+
void backwardflatlinearinterpolation(py::module_&);
105+
void flatextrapolation2d(py::module_&);
104106
void normaldistribution(py::module_&);
105107
void bivariatenormaldistribution(py::module_&);
106108
void solvers1d(py::module_&);

pyquantlib/__init__.pyi

Lines changed: 3 additions & 1 deletion
Large diffs are not rendered by default.

pyquantlib/_pyquantlib/__init__.pyi

Lines changed: 17 additions & 1 deletion
Large diffs are not rendered by default.

src/math/all.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ DECLARE_MODULE_BINDINGS(math_bindings) {
9898
"Forward-flat interpolation");
9999
ADD_MAIN_BINDING(ql_math::lagrangeinterpolation, "Lagrange interpolation");
100100
ADD_MAIN_BINDING(ql_math::bilinearinterpolation, "Bilinear interpolation");
101+
ADD_MAIN_BINDING(ql_math::backwardflatlinearinterpolation, "Backward-flat linear 2D interpolation");
102+
ADD_MAIN_BINDING(ql_math::flatextrapolation2d, "Flat extrapolation 2D");
101103
ADD_MAIN_BINDING(ql_math::bicubicsplineinterpolation,
102104
"Bicubic spline interpolation");
103105
ADD_MAIN_BINDING(ql_math::chebyshevinterpolation,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* PyQuantLib: Python bindings for QuantLib
3+
* https://github.com/quantales/pyquantlib
4+
*
5+
* Copyright (c) 2025 Yassine Idyiahia
6+
* SPDX-License-Identifier: BSD-3-Clause
7+
* See LICENSE for details.
8+
*
9+
* ---
10+
* QuantLib is Copyright (c) 2000-2025 The QuantLib Authors
11+
* https://www.quantlib.org/
12+
*/
13+
14+
#include "pyquantlib/pyquantlib.h"
15+
#include "pyquantlib/interpolation_helper.h"
16+
#include <ql/math/interpolations/backwardflatlinearinterpolation.hpp>
17+
18+
namespace py = pybind11;
19+
using namespace QuantLib;
20+
21+
void ql_math::backwardflatlinearinterpolation(py::module_& m) {
22+
pyquantlib::bind_simple_interpolation2d<BackwardflatLinearInterpolation>(
23+
m, "BackwardflatLinearInterpolation",
24+
"Backward-flat in first component, linear in second component.");
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* PyQuantLib: Python bindings for QuantLib
3+
* https://github.com/quantales/pyquantlib
4+
*
5+
* Copyright (c) 2025 Yassine Idyiahia
6+
* SPDX-License-Identifier: BSD-3-Clause
7+
* See LICENSE for details.
8+
*
9+
* ---
10+
* QuantLib is Copyright (c) 2000-2025 The QuantLib Authors
11+
* https://www.quantlib.org/
12+
*/
13+
14+
#include "pyquantlib/pyquantlib.h"
15+
#include <ql/math/interpolations/flatextrapolation2d.hpp>
16+
#include <pybind11/pybind11.h>
17+
18+
namespace py = pybind11;
19+
using namespace QuantLib;
20+
21+
void ql_math::flatextrapolation2d(py::module_& m) {
22+
py::class_<FlatExtrapolator2D, Interpolation2D,
23+
ext::shared_ptr<FlatExtrapolator2D>>(
24+
m, "FlatExtrapolator2D",
25+
"2-D flat extrapolation decorator.")
26+
.def(py::init<const ext::shared_ptr<Interpolation2D>&>(),
27+
py::arg("interpolation"),
28+
"Constructs flat extrapolator wrapping a 2-D interpolation.");
29+
}

tests/test_math_interpolations.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,3 +639,95 @@ def make():
639639
val = interp(2.0)
640640
assert math.isfinite(val)
641641
assert val > 0
642+
643+
644+
# ---------------------------------------------------------------------------
645+
# BackwardflatLinearInterpolation (2D)
646+
# ---------------------------------------------------------------------------
647+
648+
def test_backwardflat_linear_2d_construction():
649+
"""BackwardflatLinearInterpolation constructs and evaluates."""
650+
x = [1.0, 2.0, 3.0]
651+
y = [10.0, 20.0, 30.0]
652+
z = ql.Matrix(3, 3)
653+
for i in range(3):
654+
for j in range(3):
655+
z[i][j] = (i + 1) * (j + 1) * 1.0
656+
interp = ql.BackwardflatLinearInterpolation(x, y, z)
657+
assert isinstance(interp, ql.base.Interpolation2D)
658+
659+
660+
def test_backwardflat_linear_2d_at_nodes():
661+
"""Backward-flat linear 2D hits node values."""
662+
x = [1.0, 2.0, 3.0]
663+
y = [10.0, 20.0]
664+
z = ql.Matrix(2, 3)
665+
z[0][0] = 1.0; z[0][1] = 2.0; z[0][2] = 3.0
666+
z[1][0] = 4.0; z[1][1] = 5.0; z[1][2] = 6.0
667+
interp = ql.BackwardflatLinearInterpolation(x, y, z)
668+
669+
assert interp(1.0, 10.0) == pytest.approx(1.0)
670+
assert interp(2.0, 20.0) == pytest.approx(5.0)
671+
assert interp(3.0, 20.0) == pytest.approx(6.0)
672+
673+
674+
def test_backwardflat_linear_2d_interpolation():
675+
"""Backward-flat in x, linear in y."""
676+
x = [1.0, 2.0]
677+
y = [0.0, 1.0]
678+
z = ql.Matrix(2, 2)
679+
z[0][0] = 10.0; z[0][1] = 20.0
680+
z[1][0] = 30.0; z[1][1] = 40.0
681+
interp = ql.BackwardflatLinearInterpolation(x, y, z)
682+
683+
# Linear in y at x=1.0
684+
assert interp(1.0, 0.5) == pytest.approx(20.0) # (10+30)/2
685+
686+
# Backward-flat in x: between 1 and 2, uses x=2 value
687+
assert interp(1.5, 0.0) == pytest.approx(20.0) # backflat -> z[0][1]
688+
689+
690+
# ---------------------------------------------------------------------------
691+
# FlatExtrapolator2D
692+
# ---------------------------------------------------------------------------
693+
694+
def test_flat_extrapolator_2d_construction():
695+
"""FlatExtrapolator2D wraps a 2D interpolation."""
696+
x = [1.0, 2.0, 3.0]
697+
y = [10.0, 20.0, 30.0]
698+
z = ql.Matrix(3, 3)
699+
for i in range(3):
700+
for j in range(3):
701+
z[i][j] = float(i + j)
702+
inner = ql.BilinearInterpolation(x, y, z)
703+
extrap = ql.FlatExtrapolator2D(inner)
704+
assert isinstance(extrap, ql.base.Interpolation2D)
705+
706+
707+
def test_flat_extrapolator_2d_within_range():
708+
"""FlatExtrapolator2D passes through to inner interpolation within range."""
709+
x = [1.0, 2.0, 3.0]
710+
y = [10.0, 20.0]
711+
z = ql.Matrix(2, 3)
712+
z[0][0] = 1.0; z[0][1] = 2.0; z[0][2] = 3.0
713+
z[1][0] = 4.0; z[1][1] = 5.0; z[1][2] = 6.0
714+
inner = ql.BilinearInterpolation(x, y, z)
715+
extrap = ql.FlatExtrapolator2D(inner)
716+
717+
assert extrap(2.0, 15.0) == pytest.approx(inner(2.0, 15.0))
718+
719+
720+
def test_flat_extrapolator_2d_outside_range():
721+
"""FlatExtrapolator2D clamps to boundary values outside range."""
722+
x = [1.0, 2.0]
723+
y = [10.0, 20.0]
724+
z = ql.Matrix(2, 2)
725+
z[0][0] = 1.0; z[0][1] = 2.0
726+
z[1][0] = 3.0; z[1][1] = 4.0
727+
inner = ql.BilinearInterpolation(x, y, z)
728+
extrap = ql.FlatExtrapolator2D(inner)
729+
extrap.enableExtrapolation()
730+
731+
# Outside x range: clamps to boundary
732+
assert extrap(0.0, 10.0) == pytest.approx(extrap(1.0, 10.0))
733+
assert extrap(5.0, 20.0) == pytest.approx(extrap(2.0, 20.0))

0 commit comments

Comments
 (0)