Coverage Report

Created: 2026-02-03 07:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/math/matrixutilities/tqreigendecomposition.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2005 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
 <https://www.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 tqreigendecomposition.hpp
21
    \brief tridiag. QR eigen decompositions with implicit shift
22
*/
23
24
#include <ql/math/matrixutilities/tqreigendecomposition.hpp>
25
#include <vector>
26
27
namespace QuantLib {
28
29
    TqrEigenDecomposition::TqrEigenDecomposition(const Array& diag,
30
                                                 const Array& sub,
31
                                                 EigenVectorCalculation calc,
32
                                                 ShiftStrategy strategy)
33
0
    : d_(diag), ev_((calc == WithEigenVector)    ? d_.size() :
34
0
                    (calc == WithoutEigenVector) ? 0 :
35
0
                                                   1,
36
0
                    d_.size(),
37
0
                    0) {
38
0
        Size n = diag.size();
39
40
0
        QL_REQUIRE(n == sub.size()+1, "Wrong dimensions");
41
42
0
        Array e(n, 0.0);
43
0
        std::copy(sub.begin(),sub.end(),e.begin()+1);
44
0
        for (Size i=0; i < ev_.rows(); ++i) {
45
0
            ev_[i][i] = 1.0;
46
0
        }
47
48
0
        for (Size k=n-1; k >=1; --k) {
49
0
            while (!offDiagIsZero(k, e)) {
50
0
                Size l = k;
51
0
                while (--l > 0 && !offDiagIsZero(l,e)); // NOLINT(bugprone-inc-dec-in-conditions)
52
0
                iter_++;
53
54
0
                Real q = d_[l];
55
0
                if (strategy != NoShift) {
56
                    // calculated eigenvalue of 2x2 sub matrix of
57
                    // [ d_[k-1] e_[k] ]
58
                    // [  e_[k]  d_[k] ]
59
                    // which is closer to d_[k+1].
60
0
                    const Real t1 = std::sqrt(
61
0
                                          0.25*(d_[k]*d_[k] + d_[k-1]*d_[k-1])
62
0
                                          - 0.5*d_[k-1]*d_[k] + e[k]*e[k]);
63
0
                    const Real t2 = 0.5*(d_[k]+d_[k-1]);
64
65
0
                    const Real lambda =
66
0
                        (std::fabs(t2+t1 - d_[k]) < std::fabs(t2-t1 - d_[k]))?
67
0
                        Real(t2+t1) : Real(t2-t1);
68
69
0
                    if (strategy == CloseEigenValue) {
70
0
                        q-=lambda;
71
0
                    } else {
72
0
                        q-=((k==n-1)? 1.25 : 1.0)*lambda;
73
0
                    }
74
0
                }
75
76
                // the QR transformation
77
0
                Real sine = 1.0;
78
0
                Real cosine = 1.0;
79
0
                Real u = 0.0;
80
81
0
                bool recoverUnderflow = false;
82
0
                for (Size i=l+1; i <= k && !recoverUnderflow; ++i) {
83
0
                    const Real h = cosine*e[i];
84
0
                    const Real p = sine*e[i];
85
86
0
                    e[i-1] = std::sqrt(p*p+q*q);
87
0
                    if (e[i-1] != 0.0) {
88
0
                        sine = p/e[i-1];
89
0
                        cosine = q/e[i-1];
90
91
0
                        const Real g = d_[i-1]-u;
92
0
                        const Real t = (d_[i]-g)*sine+2*cosine*h;
93
94
0
                        u = sine*t;
95
0
                        d_[i-1] = g + u;
96
0
                        q = cosine*t - h;
97
98
0
                        for (Size j=0; j < ev_.rows(); ++j) {
99
0
                            const Real tmp = ev_[j][i-1];
100
0
                            ev_[j][i-1] = sine*ev_[j][i] + cosine*tmp;
101
0
                            ev_[j][i] = cosine*ev_[j][i] - sine*tmp;
102
0
                        }
103
0
                    } else {
104
                        // recover from underflow
105
0
                        d_[i-1] -= u;
106
0
                        e[l] = 0.0;
107
0
                        recoverUnderflow = true;
108
0
                    }
109
0
                }
110
111
0
                if (!recoverUnderflow) {
112
0
                    d_[k] -= u;
113
0
                    e[k] = q;
114
0
                    e[l] = 0.0;
115
0
                }
116
0
            }
117
0
        }
118
119
        // sort (eigenvalues, eigenvectors),
120
        // code taken from symmetricSchureDecomposition.cpp
121
0
        std::vector<std::pair<Real, std::vector<Real> > > temp(n);
122
0
        std::vector<Real> eigenVector(ev_.rows());
123
0
        for (Size i=0; i<n; i++) {
124
0
            if (ev_.rows() > 0)
125
0
                std::copy(ev_.column_begin(i),
126
0
                          ev_.column_end(i), eigenVector.begin());
127
0
            temp[i] = std::make_pair(d_[i], eigenVector);
128
0
        }
129
0
        std::sort(temp.begin(), temp.end(), std::greater<>());
130
        // first element is positive
131
0
        for (Size i=0; i<n; i++) {
132
0
            d_[i] = temp[i].first;
133
0
            Real sign = 1.0;
134
0
            if (ev_.rows() > 0 && temp[i].second[0]<0.0)
135
0
                sign = -1.0;
136
0
            for (Size j=0; j<ev_.rows(); ++j) {
137
0
                ev_[j][i] = sign * temp[i].second[j];
138
0
            }
139
0
        }
140
0
    }
141
142
    // see NR for abort assumption as it is
143
    // not part of the original Wilkinson algorithm
144
0
    bool TqrEigenDecomposition::offDiagIsZero(Size k, Array& e) {
145
0
        return std::fabs(d_[k-1])+std::fabs(d_[k])
146
0
            == std::fabs(d_[k-1])+std::fabs(d_[k])+std::fabs(e[k]);
147
0
    }
148
149
}