Coverage Report

Created: 2025-08-28 06:30

/src/quantlib/ql/processes/jointstochasticprocess.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) 2007 Klaus Spanderen
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 jointstochasticprocess.cpp
21
    \brief multi model process for hybrid products
22
*/
23
24
#include <ql/math/matrixutilities/pseudosqrt.hpp>
25
#include <ql/math/matrixutilities/svd.hpp>
26
#include <ql/processes/jointstochasticprocess.hpp>
27
#include <utility>
28
29
namespace QuantLib {
30
31
    JointStochasticProcess::JointStochasticProcess(
32
        std::vector<ext::shared_ptr<StochasticProcess> > l, Size factors)
33
0
    : l_(std::move(l)), factors_(factors) {
34
35
0
        for (const auto& iter : l_) {
36
0
            registerWith(iter);
37
0
        }
38
39
0
        vsize_.reserve   (l_.size()+1);
40
0
        vfactors_.reserve(l_.size()+1);
41
42
0
        for (const auto& iter : l_) {
43
0
            vsize_.push_back(size_);
44
0
            size_ += iter->size();
45
46
0
            vfactors_.push_back(modelFactors_);
47
0
            modelFactors_ += iter->factors();
48
0
        }
49
50
0
        vsize_.push_back(size_);
51
0
        vfactors_.push_back(modelFactors_);
52
53
0
        if (factors_ == Null<Size>()) {
54
0
            factors_ = modelFactors_;
55
0
        } else {
56
0
            QL_REQUIRE(factors_ <= size_, "too many factors given");
57
0
        }
58
0
    }
59
60
0
    Size JointStochasticProcess::size() const {
61
0
        return size_;
62
0
    }
63
64
0
    Size JointStochasticProcess::factors() const {
65
0
        return factors_;
66
0
    }
67
68
    Array JointStochasticProcess::slice(const Array& x,
69
0
                                        Size i) const {
70
        // cut out the ith process' variables
71
0
        Size n = vsize_[i+1]-vsize_[i];
72
0
        Array y(n);
73
0
        std::copy(x.begin()+vsize_[i], x.begin()+vsize_[i+1], y.begin());
74
0
        return y;
75
0
    }
76
77
0
    Array JointStochasticProcess::initialValues() const {
78
0
        Array retVal(size());
79
80
0
        for (auto iter = l_.begin(); iter != l_.end(); ++iter) {
81
0
            const Array& pInitValues = (*iter)->initialValues();
82
83
0
            std::copy(pInitValues.begin(), pInitValues.end(),
84
0
                      retVal.begin()+vsize_[iter - l_.begin()]);
85
0
        }
86
87
0
        return retVal;
88
0
    }
89
90
91
    Array JointStochasticProcess::drift(Time t,
92
0
                                        const Array& x) const {
93
0
        Array retVal(size());
94
95
0
        for (Size i=0; i < l_.size(); ++i) {
96
97
0
            const Array& pDrift = l_[i]->drift(t, slice(x,i));
98
99
0
            std::copy(pDrift.begin(), pDrift.end(),
100
0
                      retVal.begin()+vsize_[i]);
101
0
        }
102
103
0
        return retVal;
104
0
    }
105
106
    Array JointStochasticProcess::expectation(Time t0,
107
                                              const Array& x0,
108
0
                                              Time dt) const {
109
0
        Array retVal(size());
110
111
0
        for (Size i=0; i < l_.size(); ++i) {
112
113
0
            const Array& pExpectation = l_[i]->expectation(t0, slice(x0,i), dt);
114
115
0
            std::copy(pExpectation.begin(), pExpectation.end(),
116
0
                      retVal.begin()+ vsize_[i]);
117
0
        }
118
119
0
        return retVal;
120
0
    }
121
122
123
0
    Matrix JointStochasticProcess::diffusion(Time t, const Array& x) const {
124
        // might need some improvement in the future
125
0
        const Time dt = 0.001;
126
0
        return pseudoSqrt(covariance(t, x, dt)/dt);
127
0
    }
128
129
130
    Matrix JointStochasticProcess::covariance(Time t0,
131
                                              const Array& x0,
132
0
                                              Time dt) const {
133
134
        // get the model intrinsic covariance matrix
135
0
        Matrix retVal(size(), size(), 0.0);
136
137
0
        for (Size j=0; j < l_.size(); ++j) {
138
139
0
            const Size vs = vsize_[j];
140
0
            const Matrix& pCov = l_[j]->covariance(t0, slice(x0,j), dt);
141
142
0
            for (Size i=0; i < pCov.rows(); ++i) {
143
0
                std::copy(pCov.row_begin(i), pCov.row_end(i),
144
0
                          retVal.row_begin(vs+i) + vs);
145
0
            }
146
0
        }
147
148
        // add the cross model covariance matrix
149
0
        const Array& volatility = Sqrt(retVal.diagonal());
150
0
        Matrix crossModelCovar = this->crossModelCorrelation(t0, x0);
151
152
0
        for (Size i=0; i < size(); ++i) {
153
0
            for (Size j=0; j < size(); ++j) {
154
0
                crossModelCovar[i][j] *= volatility[i]*volatility[j];
155
0
            }
156
0
        }
157
158
0
        retVal += crossModelCovar;
159
160
0
        return retVal;
161
0
    }
162
163
164
    Matrix JointStochasticProcess::stdDeviation(Time t0,
165
                                                const Array& x0,
166
0
                                                Time dt) const {
167
0
        return pseudoSqrt(covariance(t0, x0, dt));
168
0
    }
169
170
171
    Array JointStochasticProcess::apply(const Array& x0,
172
0
                                        const Array& dx) const {
173
0
        Array retVal(size());
174
175
0
        for (Size i=0; i < l_.size(); ++i) {
176
0
            const Array& pApply = l_[i]->apply(slice(x0,i), slice(dx,i));
177
178
0
            std::copy(pApply.begin(), pApply.end(),
179
0
                      retVal.begin()+vsize_[i]);
180
0
        }
181
182
0
        return retVal;
183
0
    }
184
185
    Array JointStochasticProcess::evolve(
186
0
        Time t0, const Array& x0, Time dt, const Array& dw) const {
187
0
        Array dv(modelFactors_);
188
189
0
        if (   correlationIsStateDependent()
190
0
            || correlationCache_.count(CachingKey(t0, dt)) == 0) {
191
0
            Matrix cov  = covariance(t0, x0, dt);
192
193
0
            const Array& sqrtDiag = Sqrt(cov.diagonal());
194
0
            for (Size i=0; i < cov.rows(); ++i) {
195
0
                for (Size j=i; j < cov.columns(); ++j) {
196
0
                    const Real div = sqrtDiag[i]*sqrtDiag[j];
197
198
0
                    cov[i][j] = cov[j][i] = ( div > 0) ? Real(cov[i][j]/div) : 0.0;
199
0
                }
200
0
            }
201
202
0
            Matrix diff(size(), modelFactors_, 0.0);
203
204
0
            for (Size j = 0; j < l_.size(); ++j) {
205
0
                const Size vs = vsize_   [j];
206
0
                const Size vf = vfactors_[j];
207
208
0
                Matrix stdDev = l_[j]->stdDeviation(t0, slice(x0,j), dt);
209
210
0
                for (Size i=0; i < stdDev.rows(); ++i) {
211
0
                    const Volatility vol = std::sqrt(
212
0
                        std::inner_product(stdDev.row_begin(i),
213
0
                                           stdDev.row_end(i),
214
0
                                           stdDev.row_begin(i), Real(0.0)));
215
0
                    if (vol > 0.0) {
216
0
                        std::transform(stdDev.row_begin(i), stdDev.row_end(i),
217
0
                                       stdDev.row_begin(i),
218
0
                                       [=](Real x) -> Real { return x / vol; });
219
0
                    }
220
0
                    else {
221
                        // keep the svd happy
222
0
                        std::fill(stdDev.row_begin(i), stdDev.row_end(i),
223
0
                                  100*i*QL_EPSILON);
224
0
                    }
225
0
                }
226
227
0
                SVD svd(stdDev);
228
0
                const Array& s = svd.singularValues();
229
0
                Matrix w(s.size(), s.size(), 0.0);
230
0
                for (Size i=0; i < s.size(); ++i) {
231
0
                    if (std::fabs(s[i]) > std::sqrt(QL_EPSILON)) {
232
0
                        w[i][i] = 1.0/s[i];
233
0
                    }
234
0
                }
235
236
0
                const Matrix inv = svd.U() * w * transpose(svd.V());
237
238
0
                for (Size i=0; i < stdDev.rows(); ++i) {
239
0
                    std::copy(inv.row_begin(i), inv.row_end(i),
240
0
                              diff.row_begin(i+vs)+vf);
241
0
                }
242
0
            }
243
244
0
            Matrix rs = rankReducedSqrt(cov, factors_, 1.0,
245
0
                                        SalvagingAlgorithm::Spectral);
246
247
0
            if (rs.columns() < factors_) {
248
                // less eigenvalues than expected factors.
249
                // fill the rest with zero's.
250
0
                Matrix tmp = Matrix(cov.rows(), factors_, 0.0);
251
0
                for (Size i=0; i < cov.rows(); ++i) {
252
0
                    std::copy(rs.row_begin(i), rs.row_end(i),
253
0
                              tmp.row_begin(i));
254
0
                }
255
0
                rs = tmp;
256
0
            }
257
258
0
            const Matrix m = transpose(diff) * rs;
259
260
0
            if (!correlationIsStateDependent()) {
261
0
                correlationCache_[CachingKey(t0,dt)] = m;
262
0
            }
263
0
            dv = m*dw;
264
0
        }
265
0
        else {
266
0
            if (!correlationIsStateDependent()) {
267
0
                dv = correlationCache_[CachingKey(t0,dt)] * dw;
268
0
            }
269
0
        }
270
271
0
        this->preEvolve(t0, x0, dt, dv);
272
273
274
0
        Array retVal(size());
275
0
        for (auto iter = l_.begin(); iter != l_.end(); ++iter) {
276
0
            const Size i = iter - l_.begin();
277
278
0
            Array dz((*iter)->factors());
279
0
            std::copy(dv.begin()+vfactors_[i],
280
0
                      dv.begin()+vfactors_[i] + (*iter)->factors(),
281
0
                      dz.begin());
282
0
            Array x((*iter)->size());
283
0
            std::copy(x0.begin()+vsize_[i],
284
0
                      x0.begin()+vsize_[i] + (*iter)->size(),
285
0
                      x.begin());
286
0
            const Array r = (*iter)->evolve(t0, x, dt, dz);
287
0
            std::copy(r.begin(), r.end(), retVal.begin()+vsize_[i]);
288
0
        }
289
290
0
        return this->postEvolve(t0, x0, dt, dv, retVal);
291
0
    }
292
293
    const std::vector<ext::shared_ptr<StochasticProcess> > &
294
0
                          JointStochasticProcess::constituents() const {
295
0
        return l_;
296
0
    }
297
298
0
    Time JointStochasticProcess::time(const Date& date) const {
299
0
        QL_REQUIRE(!l_.empty(), "process list is empty");
300
301
0
        return l_[0]->time(date);
302
0
    }
303
304
0
    void JointStochasticProcess::update() {
305
        // clear all caches
306
0
        correlationCache_.clear();
307
308
0
        this->StochasticProcess::update();
309
0
    }
310
}