Coverage Report

Created: 2026-09-28 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/experimental/math/convolvedstudentt.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2014 Jose Aparicio
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
#include <ql/experimental/math/convolvedstudentt.hpp>
21
#include <ql/errors.hpp>
22
#include <ql/math/factorial.hpp>
23
#include <ql/math/distributions/normaldistribution.hpp>
24
#include <ql/math/solvers1d/brent.hpp>
25
#include <ql/math/functional.hpp>
26
#include <boost/math/distributions/students_t.hpp>
27
28
namespace QuantLib {
29
30
    CumulativeBehrensFisher::CumulativeBehrensFisher(const std::vector<Integer>& degreesFreedom,
31
                                                     const std::vector<Real>& factors)
32
0
    : degreesFreedom_(degreesFreedom), factors_(factors), polyConvolved_(std::vector<Real>(1, 1.))
33
34
0
    {
35
0
        QL_REQUIRE(degreesFreedom.size() == factors.size(),
36
0
            "Incompatible sizes in convolution.");
37
0
        for (int i : degreesFreedom) {
38
0
            QL_REQUIRE(i % 2 != 0, "Even degree of freedom not allowed");
39
0
            QL_REQUIRE(i >= 0, "Negative degree of freedom not allowed");
40
0
        }
41
0
        for(Size i=0; i<degreesFreedom_.size(); i++)
42
0
            polynCharFnc_.push_back(polynCharactT((degreesFreedom[i]-1)/2));
43
        // adjust the polynomial coefficients by the factors in the linear
44
        //   combination:
45
0
        for(Size i=0; i<degreesFreedom_.size(); i++) {
46
0
            Real multiplier = 1.;
47
0
            for(Size k=1; k<polynCharFnc_[i].size(); k++) {
48
0
                multiplier *= std::abs(factors_[i]);
49
0
                polynCharFnc_[i][k] *= multiplier;
50
0
            }
51
0
        }
52
        //convolution, here it is a product of polynomials and exponentials
53
0
        for (auto& i : polynCharFnc_)
54
0
            polyConvolved_ = convolveVectorPolynomials(polyConvolved_, i);
55
        // trim possible zeros that might have arised:
56
0
        auto it = polyConvolved_.rbegin();
57
0
        while (it != polyConvolved_.rend()) {
58
0
            if (*it == 0.) {
59
0
                polyConvolved_.pop_back();
60
0
                it = polyConvolved_.rbegin();
61
0
              }else{
62
0
                  break;
63
0
              }
64
0
          }
65
          // cache 'a' value (the exponent)
66
0
          for(Size i=0; i<degreesFreedom_.size(); i++)
67
0
              a_ += std::sqrt(static_cast<Real>(degreesFreedom_[i]))
68
0
                * std::abs(factors_[i]);
69
0
          a2_ = a_ * a_;
70
0
    }
71
72
0
    std::vector<Real> CumulativeBehrensFisher::polynCharactT(Natural n) const {
73
0
        Natural nu = 2 * n +1;
74
0
        std::vector<Real> low(1,1.), high(1,1.);
75
0
        high.push_back(std::sqrt(static_cast<Real>(nu)));
76
0
        if(n==0) return low;
77
0
        if(n==1) return high;
78
79
0
        for(Size k=1; k<n; k++) {
80
0
            std::vector<Real> recursionFactor(1,0.); // 0 coef
81
0
            recursionFactor.push_back(0.); // 1 coef
82
0
            recursionFactor.push_back(nu/((2.*k+1.)*(2.*k-1.))); // 2 coef
83
0
            std::vector<Real> lowUp =
84
0
                convolveVectorPolynomials(recursionFactor, low);
85
            //add them up:
86
0
            for(Size i=0; i<high.size(); i++)
87
0
                lowUp[i] += high[i];
88
0
            low = high;
89
0
            high = lowUp;
90
0
        }
91
0
        return high;
92
0
    }
93
94
    std::vector<Real> CumulativeBehrensFisher::convolveVectorPolynomials(
95
        const std::vector<Real>& v1,
96
0
        const std::vector<Real>& v2) const {
97
    #if defined(QL_EXTRA_SAFETY_CHECKS)
98
        QL_REQUIRE(!v1.empty() && !v2.empty(),
99
            "Incorrect vectors in polynomial.");
100
    #endif
101
102
0
        const std::vector<Real>& shorter = v1.size() < v2.size() ? v1 : v2;
103
0
        const std::vector<Real>& longer = (v1 == shorter) ? v2 : v1;
104
105
0
        Size newDegree = v1.size()+v2.size()-2;
106
0
        std::vector<Real> resultB(newDegree+1, 0.);
107
0
        for(Size polyOrdr=0; polyOrdr<resultB.size(); polyOrdr++) {
108
0
            for(Size i=std::max<Integer>(0, polyOrdr-longer.size()+1);
109
0
                i<=std::min(polyOrdr, shorter.size()-1); i++)
110
0
                resultB[polyOrdr] += shorter[i]*longer[polyOrdr-i];
111
0
        }
112
0
        return resultB;
113
0
    }
114
115
0
    Probability CumulativeBehrensFisher::operator()(const Real x) const {
116
        // 1st & 0th terms with the table integration
117
0
        Real integral = polyConvolved_[0] * std::atan(x/a_);
118
0
        Real squared = a2_ + x*x;
119
0
        Real rootsqr = std::sqrt(squared);
120
0
        Real atan2xa = std::atan2(-x,a_);
121
0
        if(polyConvolved_.size()>1)
122
0
            integral += polyConvolved_[1] * x/squared;
123
124
0
        for(Size exponent = 2; exponent <polyConvolved_.size(); exponent++) {
125
0
            integral -= polyConvolved_[exponent] *
126
0
                Factorial::get(exponent-1) * std::sin((exponent)*atan2xa)
127
0
                    /std::pow(rootsqr, static_cast<Real>(exponent));
128
0
         }
129
0
        return .5 + integral / M_PI;
130
0
    }
131
132
    Probability
133
0
    CumulativeBehrensFisher::density(const Real x) const {
134
0
        Real squared = a2_ + x*x;
135
0
        Real integral = polyConvolved_[0] * a_ / squared;
136
0
        Real rootsqr = std::sqrt(squared);
137
0
        Real atan2xa = std::atan2(-x,a_);
138
0
        for(Size exponent=1; exponent <polyConvolved_.size(); exponent++) {
139
0
            integral += polyConvolved_[exponent] *
140
0
                Factorial::get(exponent) * std::cos((exponent+1)*atan2xa)
141
0
                    /std::pow(rootsqr, static_cast<Real>(exponent+1) );
142
0
        }
143
0
        return integral / M_PI;
144
0
    }
145
146
147
148
    InverseCumulativeBehrensFisher::InverseCumulativeBehrensFisher(
149
        const std::vector<Integer>& degreesFreedom,
150
        const std::vector<Real>& factors,
151
        Real accuracy)
152
0
    : normSqr_(std::inner_product(factors.begin(), factors.end(),
153
0
        factors.begin(), Real(0.))),
154
0
      accuracy_(accuracy), distrib_(degreesFreedom, factors) { }
155
156
0
    Real InverseCumulativeBehrensFisher::operator()(const Probability q) const {
157
0
        Probability effectiveq;
158
0
        Real sign;
159
        // since the distrib is symmetric solve only on the right side:
160
0
        if(q==0.5) {
161
0
            return 0.;
162
0
        }else if(q < 0.5) {
163
0
            sign = -1.;
164
0
            effectiveq = 1.-q;
165
0
        }else{
166
0
            sign = 1.;
167
0
            effectiveq = q;
168
0
        }
169
0
        Real xMin =
170
0
            InverseCumulativeNormal::standard_value(effectiveq) * normSqr_;
171
        // inversion will fail at the Brent's bounds-check if this is not enough
172
        // (q is very close to 1.), in a bad combination fails around 1.-1.e-7
173
0
        Real xMax = 1.e6;
174
0
        return sign *
175
0
            Brent().solve([&](Real x) -> Real { return distrib_(x) - effectiveq; },
176
0
                          accuracy_, (xMin+xMax)/2., xMin, xMax);
177
0
    }
178
179
}