Coverage Report

Created: 2025-08-05 06:45

/src/quantlib/ql/experimental/math/laplaceinterpolation.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2015, 2024 Peter Caspers
5
6
 This file is part of QuantLib, a free-software/open-source library
7
 for financial quantitative analysts and developers - http://quantlib.org/
8
9
 QuantLib is free software: you can redistribute it and/or modify it
10
 under the terms of the QuantLib license.  You should have received a
11
 copy of the license along with this program; if not, please email
12
 <quantlib-dev@lists.sf.net>. The license is also available online at
13
 <http://quantlib.org/license.shtml>.
14
15
 This program is distributed in the hope that it will be useful, but WITHOUT
16
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17
 FOR A PARTICULAR PURPOSE.  See the license for more details.
18
*/
19
20
/*! \file laplaceinterpolation.hpp
21
    \brief Laplace interpolation of missing values
22
*/
23
24
#include <ql/experimental/math/laplaceinterpolation.hpp>
25
#include <ql/math/matrix.hpp>
26
#include <ql/math/matrixutilities/bicgstab.hpp>
27
#include <ql/math/matrixutilities/sparsematrix.hpp>
28
#include <ql/methods/finitedifferences/meshers/fdm1dmesher.hpp>
29
#include <ql/methods/finitedifferences/meshers/fdmmeshercomposite.hpp>
30
#include <ql/methods/finitedifferences/meshers/predefined1dmesher.hpp>
31
#include <ql/methods/finitedifferences/operators/fdmlinearopcomposite.hpp>
32
#include <ql/methods/finitedifferences/operators/fdmlinearoplayout.hpp>
33
#include <ql/methods/finitedifferences/operators/secondderivativeop.hpp>
34
#include <ql/methods/finitedifferences/operators/triplebandlinearop.hpp>
35
36
namespace QuantLib {
37
38
    LaplaceInterpolation::LaplaceInterpolation(std::function<Real(const std::vector<Size>&)> y,
39
                                               std::vector<std::vector<Real>> x,
40
                                               Real relTol,
41
                                               Size maxIterMultiplier)
42
0
    : y_(std::move(y)), x_(std::move(x)), relTol_(relTol), maxIterMultiplier_(maxIterMultiplier) {
43
44
        // set up the mesher
45
46
0
        std::vector<Size> dim;
47
0
        coordinateIncluded_.resize(x_.size());
48
0
        for (Size i = 0; i < x_.size(); ++i) {
49
0
            coordinateIncluded_[i] = x_[i].size() > 1;
50
0
            if (coordinateIncluded_[i])
51
0
                dim.push_back(x_[i].size());
52
0
        }
53
54
0
        numberOfCoordinatesIncluded_ = dim.size();
55
56
0
        if (numberOfCoordinatesIncluded_ == 0) {
57
0
            return;
58
0
        }
59
60
0
        QL_REQUIRE(!dim.empty(), "LaplaceInterpolation: singular point or no points given");
61
62
0
        layout_ = ext::make_shared<FdmLinearOpLayout>(dim);
63
64
0
        std::vector<ext::shared_ptr<Fdm1dMesher>> meshers;
65
0
        for (auto & i : x_) {
66
0
            if (i.size() > 1)
67
0
                meshers.push_back(ext::make_shared<Predefined1dMesher>(i));
68
0
        }
69
70
0
        auto mesher = ext::make_shared<FdmMesherComposite>(layout_, meshers);
71
72
        // set up the Laplace operator and convert it to matrix
73
74
0
        struct LaplaceOp : public FdmLinearOpComposite {
75
0
            explicit LaplaceOp(const ext::shared_ptr<FdmMesher>& mesher) {
76
0
                for (Size direction = 0; direction < mesher->layout()->dim().size(); ++direction) {
77
0
                    if (mesher->layout()->dim()[direction] > 1)
78
0
                        map_.push_back(SecondDerivativeOp(direction, mesher));
79
0
                }
80
0
            }
81
0
            std::vector<TripleBandLinearOp> map_;
82
83
0
            Size size() const override { QL_FAIL("no impl"); }
84
0
            void setTime(Time t1, Time t2) override { QL_FAIL("no impl"); }
85
0
            Array apply(const array_type& r) const override { QL_FAIL("no impl"); }
86
0
            Array apply_mixed(const Array& r) const override { QL_FAIL("no impl"); }
87
0
            Array apply_direction(Size direction, const Array& r) const override {
88
0
                QL_FAIL("no impl");
89
0
            }
90
0
            Array solve_splitting(Size direction, const Array& r, Real s) const override {
91
0
                QL_FAIL("no impl");
92
0
            }
93
0
            Array preconditioner(const Array& r, Real s) const override { QL_FAIL("no impl"); }
94
0
            std::vector<SparseMatrix> toMatrixDecomp() const override {
95
0
                std::vector<SparseMatrix> decomp;
96
0
                decomp.reserve(map_.size());
97
0
for (auto const& m : map_)
98
0
                    decomp.push_back(m.toMatrix());
99
0
                return decomp;
100
0
            }
101
0
        };
102
103
0
        SparseMatrix op = LaplaceOp(mesher).toMatrix();
104
105
        // set up the linear system to solve
106
107
0
        Size N = layout_->size();
108
109
0
        SparseMatrix g(N, N, 5 * N);
110
0
        Array rhs(N, 0.0), guess(N, 0.0);
111
0
        Real guessTmp = 0.0;
112
113
0
        struct f_A {
114
0
            const SparseMatrix& g;
115
0
            explicit f_A(const SparseMatrix& g) : g(g) {}
116
0
            Array operator()(const Array& x) const { return prod(g, x); }
117
0
        };
118
119
0
        auto rowit = op.begin1();
120
0
        Size count = 0;
121
0
        std::vector<Real> corner_h(dim.size());
122
0
        std::vector<Size> corner_neighbour_index(dim.size());
123
0
        for (auto const& pos : *layout_) {
124
0
            const auto& coord = pos.coordinates();
125
0
            Real val =
126
0
                y_(numberOfCoordinatesIncluded_ == x_.size() ? coord : fullCoordinates(coord));
127
0
            QL_REQUIRE(rowit != op.end1() && rowit.index1() == count,
128
0
                       "LaplaceInterpolation: op matrix row iterator ("
129
0
                           << (rowit != op.end1() ? std::to_string(rowit.index1()) : "na")
130
0
                           << ") does not match expected row count (" << count << ")");
131
0
            if (val == Null<Real>()) {
132
0
                bool isCorner = true;
133
0
                for (Size d = 0; d < dim.size() && isCorner; ++d) {
134
0
                    if (coord[d] == 0) {
135
0
                        corner_h[d] = meshers[d]->dplus(0);
136
0
                        corner_neighbour_index[d] = 1;
137
0
                    } else if (coord[d] == layout_->dim()[d] - 1) {
138
0
                        corner_h[d] = meshers[d]->dminus(dim[d] - 1);
139
0
                        corner_neighbour_index[d] = dim[d] - 2;
140
0
                    } else {
141
0
                        isCorner = false;
142
0
                    }
143
0
                }
144
0
                if (isCorner) {
145
                    // handling of the "corners", all second derivs are zero in the op
146
                    // this directly generalizes Numerical Recipes, 3rd ed, eq 3.8.6
147
0
                    Real sum_corner_h =
148
0
                        std::accumulate(corner_h.begin(), corner_h.end(), Real(0.0), std::plus<>());
149
0
                    for (Size j = 0; j < dim.size(); ++j) {
150
0
                        std::vector<Size> coord_j(coord);
151
0
                        coord_j[j] = corner_neighbour_index[j];
152
0
                        Real weight = 0.0;
153
0
                        for (Size i = 0; i < dim.size(); ++i) {
154
0
                            if (i != j)
155
0
                                weight += corner_h[i];
156
0
                        }
157
0
                        weight = dim.size() == 1 ? Real(1.0) : Real(weight / sum_corner_h);
158
0
                        g(count, layout_->index(coord_j)) = -weight;
159
0
                    }
160
0
                    g(count, count) = 1.0;
161
0
                } else {
162
                    // point with at least one dimension with non-trivial second derivative
163
0
                    for (auto colit = rowit.begin(); colit != rowit.end(); ++colit)
164
0
                        g(count, colit.index2()) = *colit;
165
0
                }
166
0
                rhs[count] = 0.0;
167
0
                guess[count] = guessTmp;
168
0
            } else {
169
0
                g(count, count) = 1;
170
0
                rhs[count] = val;
171
0
                guess[count] = guessTmp = val;
172
0
            }
173
0
            ++count;
174
0
            ++rowit;
175
0
        }
176
177
0
        interpolatedValues_ = BiCGstab(f_A(g), maxIterMultiplier_ * N, relTol_).solve(rhs, guess).x;
178
0
    }
179
180
    std::vector<Size>
181
0
    LaplaceInterpolation::projectedCoordinates(const std::vector<Size>& coordinates) const {
182
0
        std::vector<Size> tmp;
183
0
        for (Size i = 0; i < coordinates.size(); ++i) {
184
0
            if (coordinateIncluded_[i])
185
0
                tmp.push_back(coordinates[i]);
186
0
        }
187
0
        return tmp;
188
0
    }
189
190
    std::vector<Size>
191
0
    LaplaceInterpolation::fullCoordinates(const std::vector<Size>& projectedCoordinates) const {
192
0
        std::vector<Size> tmp(coordinateIncluded_.size(), 0);
193
0
        for (Size i = 0, count = 0; i < coordinateIncluded_.size(); ++i) {
194
0
            if (coordinateIncluded_[i])
195
0
                tmp[i] = projectedCoordinates[count++];
196
0
        }
197
0
        return tmp;
198
0
    }
199
200
0
    Real LaplaceInterpolation::operator()(const std::vector<Size>& coordinates) const {
201
0
        QL_REQUIRE(coordinates.size() == x_.size(), "LaplaceInterpolation::operator(): expected "
202
0
                                                        << x_.size() << " coordinates, got "
203
0
                                                        << coordinates.size());
204
0
        if (numberOfCoordinatesIncluded_ == 0) {
205
0
            Real val = y_(coordinates);
206
0
            return val == Null<Real>() ? 0.0 : val;
207
0
        } else {
208
0
            return interpolatedValues_[layout_->index(numberOfCoordinatesIncluded_ == x_.size() ?
209
0
                                                          coordinates :
210
0
                                                          projectedCoordinates(coordinates))];
211
0
        }
212
0
    }
213
214
    void laplaceInterpolation(Matrix& A,
215
                              const std::vector<Real>& x,
216
                              const std::vector<Real>& y,
217
                              Real relTol,
218
0
                              Size maxIterMultiplier) {
219
220
0
        std::vector<std::vector<Real>> tmp;
221
0
        tmp.push_back(y);
222
0
        tmp.push_back(x);
223
224
0
        if (y.empty()) {
225
0
            tmp[0].resize(A.rows());
226
0
            std::iota(tmp[0].begin(), tmp[0].end(), 0.0);
227
0
        }
228
229
0
        if (x.empty()) {
230
0
            tmp[1].resize(A.columns());
231
0
            std::iota(tmp[1].begin(), tmp[1].end(), 0.0);
232
0
        }
233
234
0
        LaplaceInterpolation interpolation(
235
0
            [&A](const std::vector<Size>& coordinates) {
236
0
                return A(coordinates[0], coordinates[1]);
237
0
            },
238
0
            tmp, relTol, maxIterMultiplier);
239
240
0
        for (Size i = 0; i < A.rows(); ++i) {
241
0
            for (Size j = 0; j < A.columns(); ++j) {
242
0
                if (A(i, j) == Null<Real>())
243
0
                    A(i, j) = interpolation({i, j});
244
0
            }
245
0
        }
246
0
    }
247
248
} // namespace QuantLib