/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 | | } |