Coverage Report

Created: 2025-10-14 06:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/methods/finitedifferences/meshers/concentrating1dmesher.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2009 Ralph Schreyer
5
 Copyright (C) 2014 Johannes Göttker-Schnetmann
6
 Copyright (C) 2014 Klaus Spanderen
7
 Copyright (C) 2015 Peter Caspers
8
9
 This file is part of QuantLib, a free-software/open-source library
10
 for financial quantitative analysts and developers - http://quantlib.org/
11
12
 QuantLib is free software: you can redistribute it and/or modify it
13
 under the terms of the QuantLib license.  You should have received a
14
 copy of the license along with this program; if not, please email
15
 <quantlib-dev@lists.sf.net>. The license is also available online at
16
 <https://www.quantlib.org/license.shtml>.
17
18
 This program is distributed in the hope that it will be useful, but WITHOUT
19
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
20
 FOR A PARTICULAR PURPOSE.  See the license for more details.
21
*/
22
23
/*! \file concentrating1dmesher.cpp
24
    \brief One-dimensional grid mesher concentrating around critical points
25
*/ 
26
27
#include <ql/errors.hpp>
28
#include <ql/timegrid.hpp>
29
#include <ql/utilities/null.hpp>
30
#include <ql/math/array.hpp>
31
#include <ql/math/functional.hpp>
32
#include <ql/math/comparison.hpp>
33
#include <ql/math/solvers1d/brent.hpp>
34
#include <ql/math/interpolations/linearinterpolation.hpp>
35
#include <ql/math/ode/adaptiverungekutta.hpp>
36
#include <ql/methods/finitedifferences/meshers/concentrating1dmesher.hpp>
37
#include <cmath>
38
39
namespace QuantLib {
40
41
    Concentrating1dMesher::Concentrating1dMesher(
42
        Real start, Real end, Size size, const std::pair<Real, Real>& cPoints,
43
        const bool requireCPoint)
44
0
        : Fdm1dMesher(size) {
45
46
0
        QL_REQUIRE(end > start, "end must be larger than start");
47
48
0
        const Real cPoint = cPoints.first;
49
0
        const Real density = cPoints.second == Null<Real>() ?
50
0
            Null<Real>() : cPoints.second*(end - start);
51
52
0
        QL_REQUIRE(cPoint == Null<Real>() || (cPoint >= start && cPoint <= end),
53
0
            "cPoint must be between start and end");
54
0
        QL_REQUIRE(density == Null<Real>() || density > 0.0,
55
0
            "density > 0 required");
56
0
        QL_REQUIRE(cPoint == Null<Real>() || density != Null<Real>(),
57
0
            "density must be given if cPoint is given");
58
0
        QL_REQUIRE(!requireCPoint || cPoint != Null<Real>(),
59
0
            "cPoint is required in grid but not given");
60
61
0
        const Real dx = 1.0 / (size - 1);
62
63
0
        if (cPoint != Null<Real>()) {
64
0
            std::vector<Real> u, z;
65
0
            ext::shared_ptr<Interpolation> transform;
66
0
            const Real c1 = std::asinh((start - cPoint) / density);
67
0
            const Real c2 = std::asinh((end - cPoint) / density);
68
0
            if (requireCPoint) {
69
0
                u.push_back(0.0);
70
0
                z.push_back(0.0);
71
0
                if (!close(cPoint, start) && !close(cPoint, end)) {
72
0
                    const Real z0 = -c1 / (c2 - c1);
73
0
                    const Real u0 =
74
0
                        std::max(std::min(std::lround(z0 * (size - 1)),
75
0
                                          static_cast<long>(size) - 2),
76
0
                                 1L) /
77
0
                        ((Real)(size - 1));
78
0
                    u.push_back(u0);
79
0
                    z.push_back(z0);
80
0
                }
81
0
                u.push_back(1.0);
82
0
                z.push_back(1.0);
83
0
                transform = ext::shared_ptr<Interpolation>(
84
0
                    new LinearInterpolation(u.begin(), u.end(), z.begin()));
85
0
            }
86
87
0
            for (Size i = 1; i < size - 1; ++i) {
88
0
                const Real li = requireCPoint ? (*transform)(i*dx) : i*dx;
89
0
                locations_[i] = cPoint
90
0
                    + density*std::sinh(c1*(1.0 - li) + c2*li);
91
0
            }
92
0
        }
93
0
        else {
94
0
            for (Size i = 1; i < size - 1; ++i) {
95
0
                locations_[i] = start + i*dx*(end - start);
96
0
            }
97
0
        }
98
99
0
        locations_.front() = start;
100
0
        locations_.back() = end;
101
102
0
        for (Size i = 0; i < size - 1; ++i) {
103
0
            dplus_[i] = dminus_[i + 1] = locations_[i + 1] - locations_[i];
104
0
        }
105
0
        dplus_.back() = dminus_.front() = Null<Real>();
106
0
    }
107
108
    namespace {
109
        class OdeIntegrationFct {
110
          public:
111
            OdeIntegrationFct(const std::vector<Real>& points,
112
                              const std::vector<Real>& betas,
113
                              Real tol)
114
0
          : rk_(tol), points_(points), betas_(betas) {}
115
116
0
            Real solve(Real a, Real y0, Real x0, Real x1) {
117
0
                AdaptiveRungeKutta<>::OdeFct1d odeFct([&](Real x, Real y){ return jac(a, x, y); });
118
0
                return rk_(odeFct, y0, x0, x1);
119
0
            }
120
121
          private:
122
0
            Real jac(Real a, Real, Real y) const {
123
0
                Real s=0.0;
124
0
                for (Size i=0; i < points_.size(); ++i) {
125
0
                    s+=1.0/(betas_[i] + squared(y - points_[i]));
126
0
                }
127
0
                return a/std::sqrt(s);
128
0
            }
129
130
            AdaptiveRungeKutta<> rk_;
131
            const std::vector<Real> &points_, &betas_;
132
        };
133
134
        bool equal_on_first(const std::pair<Real, Real>& p1,
135
0
                            const std::pair<Real, Real>& p2) {
136
0
            return close_enough(p1.first, p2.first, 1000);
137
0
        }
138
    }
139
140
141
    Concentrating1dMesher::Concentrating1dMesher(
142
        Real start, Real end, Size size,
143
        const std::vector<std::tuple<Real, Real, bool> >& cPoints,
144
        Real tol)
145
0
    : Fdm1dMesher(size) {
146
147
0
        QL_REQUIRE(end > start, "end must be larger than start");
148
149
0
        std::vector<Real> points, betas;
150
0
        for (const auto& cPoint : cPoints) {
151
0
            points.push_back(std::get<0>(cPoint));
152
0
            betas.push_back(squared(std::get<1>(cPoint) * (end - start)));
153
0
        }
154
155
        // get scaling factor a so that y(1) = end
156
0
        Real aInit = 0.0;
157
0
        for (Size i=0; i < points.size(); ++i) {
158
0
            const Real c1 = std::asinh((start-points[i])/betas[i]);
159
0
            const Real c2 = std::asinh((end-points[i])/betas[i]);
160
0
            aInit+=(c2-c1)/points.size();
161
0
        }
162
163
0
        OdeIntegrationFct fct(points, betas, tol);
164
0
        const Real a = Brent().solve(
165
0
            [&](Real x) -> Real { return fct.solve(x, start, 0.0, 1.0) - end; },
166
0
            tol, aInit, 0.1*aInit);
167
168
        // solve ODE for all grid points
169
0
        Array x(size), y(size);
170
0
        x[0] = 0.0; y[0] = start;
171
0
        const Real dx = 1.0/(size-1);
172
0
        for (Size i=1; i < size; ++i) {
173
0
            x[i] = i*dx;
174
0
            y[i] = fct.solve(a, y[i-1], x[i-1], x[i]);
175
0
        }
176
177
        // eliminate numerical noise and ensure y(1) = end
178
0
        const Real dy = y.back() - end;
179
0
        for (Size i=1; i < size; ++i) {
180
0
            y[i] -= i*dx*dy;
181
0
        }
182
183
0
        LinearInterpolation odeSolution(x.begin(), x.end(), y.begin());
184
185
        // ensure required points are part of the grid
186
0
        std::vector<std::pair<Real, Real> > w(1, std::make_pair(0.0, 0.0));
187
188
0
        for (Size i=0; i < points.size(); ++i) {
189
0
            if (std::get<2>(cPoints[i]) && points[i] > start && points[i] < end) {
190
191
0
                const Size j = std::distance(y.begin(),
192
0
                        std::lower_bound(y.begin(), y.end(), points[i]));
193
194
0
                const Real e = Brent().solve(
195
0
                    [&](Real x) -> Real { return odeSolution(x, true) - points[i]; },
196
0
                    QL_EPSILON, x[j], 0.5/size);
197
198
0
                w.emplace_back(std::min(x[size - 2], x[j]), e);
199
0
            }
200
0
        }
201
0
        w.emplace_back(1.0, 1.0);
202
0
        std::sort(w.begin(), w.end());
203
0
        w.erase(std::unique(w.begin(), w.end(), equal_on_first), w.end());
204
205
0
        std::vector<Real> u(w.size()), z(w.size());
206
0
        for (Size i=0; i < w.size(); ++i) {
207
0
            u[i] = w[i].first;
208
0
            z[i] = w[i].second;
209
0
        }
210
0
        LinearInterpolation transform(u.begin(), u.end(), z.begin());
211
212
0
        for (Size i=0; i < size; ++i) {
213
0
            locations_[i] = odeSolution(transform(i*dx));
214
0
        }
215
216
0
        for (Size i=0; i < size-1; ++i) {
217
0
            dplus_[i] = dminus_[i+1] = locations_[i+1] - locations_[i];
218
0
        }
219
0
        dplus_.back() = dminus_.front() = Null<Real>();
220
0
    }
221
}