Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
bucketeddistribution.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19
20/*! \file qle/math/bucketeddistribution.cpp
21 \brief Deals with a bucketed distribution
22*/
23
24#include <numeric>
25
26#include <functional>
27#include <ql/errors.hpp>
28#include <ql/math/comparison.hpp>
29
31
32using namespace std;
33using namespace QuantLib;
34
35namespace QuantExt {
36
37BucketedDistribution::BucketedDistribution(Real min, Real max, Size numberBuckets)
38 : buckets_(numberBuckets + 1, 0.0), probabilities_(numberBuckets, 0.0), points_(numberBuckets, 0.0) {
39
40 init(min, max, numberBuckets);
41
42 // Initially put all probability in 1st bucket
43 probabilities_[0] = 1.0;
44 previousProbabilities_ = probabilities_;
45}
46
47BucketedDistribution::BucketedDistribution(Real min, Real max, Size numberBuckets, Real initialValue)
48 : buckets_(numberBuckets + 1, 0.0), probabilities_(numberBuckets, initialValue), points_(numberBuckets, 0.0),
49 previousProbabilities_(probabilities_) {
50
51 init(min, max, numberBuckets);
52}
53
54BucketedDistribution::BucketedDistribution(const vector<Real>& buckets, const vector<Real>& initialProbabilities,
55 const vector<Real>& initialPoints)
56 : buckets_(buckets), probabilities_(initialProbabilities), points_(initialPoints),
57 previousProbabilities_(initialProbabilities), previousPoints_(initialPoints) {
58
59 // Initial checks
60 QL_REQUIRE(buckets_.size() >= 3, "There should be at least two buckets for the distribution");
61 QL_REQUIRE(buckets_.size() == probabilities_.size() + 1, "The number of elements in the buckets "
62 "vector must exceed the number of probabilities by 1");
63 QL_REQUIRE(buckets_.size() == points_.size() + 1, "The number of elements in the buckets "
64 "vector must exceed the number of point masses by 1");
65
66 // Check that the buckets are sorted
67 vector<Real>::iterator pos = adjacent_find(buckets_.begin(), buckets_.end(), greater<Real>());
68 QL_REQUIRE(pos == buckets_.end(), "The vector of buckets must be sorted in ascending order");
69}
70
71BucketedDistribution::BucketedDistribution(const BucketedDistribution& other)
72 : buckets_(other.buckets_), probabilities_(other.probabilities_), points_(other.points_),
73 previousProbabilities_(other.previousProbabilities_), previousPoints_(other.previousPoints_) {}
74
75const Real BucketedDistribution::minProbability_ = 0.00000001;
76
78
79 // Update the distribution with the values provided
82 vector<Real> tempPoints(points_.size(), 0.0);
83 vector<Real> tempProbabilities = previousProbabilities_;
84 vector<bool> bucketsChanged(points_.size(), false);
85
86 for (Size i = 0; i < buckets_.size() - 1; i++) {
87 // Skip buckets that have no probability of being occupied
89 // Update buckets reachable from ith bucket
90 for (Size j = 0; j < distribution.size(); j++) {
91 Distributionpair pair = distribution.get(j);
92 Real transitionPoint = previousPoints_[i] + pair.x_;
93 Real transitionProbability = previousProbabilities_[i] * pair.y_;
94
95 // Find in which bucket transitionPoint lies
96 QL_REQUIRE(buckets_[0] <= transitionPoint && transitionPoint <= buckets_.back(),
97 "Value, " << transitionPoint << ", is out of range of buckets: (" << buckets_[0] << ", "
98 << buckets_.back() << ")");
99
100 // Either transition to a higher bucket or stay in the same bucket
101 if (transitionPoint >= buckets_[i + 1]) {
102 Size bucketIndex = 0;
103 vector<Real>::const_iterator it =
104 upper_bound(buckets_.begin() + i + 1, buckets_.end(), transitionPoint);
105 if (it == buckets_.end()) {
106 // If here, must be equal to upper end of last bucket
107 bucketIndex = buckets_.size() - 2;
108 } else {
109 bucketIndex = it - buckets_.begin() - 1;
110 }
111
112 // Update probability if moved to different bucket
113 probabilities_[i] -= transitionProbability;
114 probabilities_[bucketIndex] += transitionProbability;
115 // Update temp points (divide again in separate loop below)
116 tempPoints[bucketIndex] += transitionPoint * transitionProbability;
117 tempProbabilities[bucketIndex] += transitionProbability;
118 bucketsChanged[bucketIndex] = true;
119 } else {
120 // If bucket does not change, do not update probability but shift
121 // the conditional expectation i.e. point in bucket
122 points_[i] += pair.x_ * pair.y_;
123 }
124 }
125 }
126 }
127
128 // If the probability of being in bucket is non-zero and the bucket has been hit by
129 // the addition of the distribution above, update the conditional expectation (i.e. points_)
130 for (Size i = 0; i < buckets_.size() - 1; i++) {
131 if (tempProbabilities[i] > minProbability_ && bucketsChanged[i]) {
132 points_[i] = (previousProbabilities_[i] * points_[i] + tempPoints[i]) / tempProbabilities[i];
133 }
134 }
135}
136
137Size BucketedDistribution::bucket(Real value) const {
138
139 QL_REQUIRE(buckets_[0] <= value && value <= buckets_.back(), "Value, " << value << ", is out of range of buckets: ("
140 << buckets_[0] << ", " << buckets_.back()
141 << ")");
142
143 vector<Real>::const_iterator it = upper_bound(buckets_.begin(), buckets_.end(), value);
144 if (it == buckets_.end()) {
145 // If here, must be equal to upper end of last bucket
146 return buckets_.size() - 2;
147 }
148 return it - buckets_.begin() - 1;
149}
150
151void BucketedDistribution::init(Real min, Real max, Size numberBuckets) {
152 // Initial checks
153 QL_REQUIRE(buckets_.size() >= 3, "There should be at least two buckets for the distribution");
154 QL_REQUIRE(max > min, "Max should be strictly greater than min");
155
156 // Divide [min, max] into equally sized buckets
157 Real bucketSize = (max - min) / numberBuckets;
158 for (Size i = 0; i < buckets_.size(); ++i) {
159 buckets_[i] = min + i * bucketSize;
160 }
161
162 // Initially set points to lower end of buckets
163 copy(buckets_.begin(), buckets_.end() - 1, points_.begin());
165}
166
168 vector<Real> cumulativeProbabilities(buckets_.size(), 0.0);
170 // Calculate running sum i.e. probability that <= endpoint of each bucket
171 transform(probabilities_.begin(), probabilities_.end(), cumulativeProbabilities.begin(),
172 cumulativeProbabilities.begin() + 1, plus<Real>());
174}
175
177 vector<Real> probabilities = cumulativeProbabilities();
178 // transform(probabilities.begin(), probabilities.end(), probabilities.begin(), std::bind1st(minus<Real>(), 1.0));
179 transform(probabilities.begin(), probabilities.end(), probabilities.begin(), [](const Real x) { return x - 1.0; });
180 return probabilities;
181}
182
184 // transform(buckets_.begin(), buckets_.end(), buckets_.begin(), std::bind1st(plus<Real>(), shift));
185 transform(buckets_.begin(), buckets_.end(), buckets_.begin(), [shift](const Real x) { return x + shift; });
186 // transform(points_.begin(), points_.end(), points_.begin(), std::bind1st(plus<Real>(), shift));
187 transform(points_.begin(), points_.end(), points_.begin(), [shift](const Real x) { return x + shift; });
188 // transform(previousPoints_.begin(), previousPoints_.end(), previousPoints_.begin(), std::bind1st(plus<Real>(),
189 // shift));
190 transform(previousPoints_.begin(), previousPoints_.end(), previousPoints_.begin(),
191 [shift](const Real x) { return x + shift; });
192}
193
195 // If factor is negative, reverse everything before scaling so buckets are still ascending
196 if (factor < 0.0) {
197 reverse(buckets_.begin(), buckets_.end());
198 reverse(points_.begin(), points_.end());
199 reverse(previousPoints_.begin(), previousPoints_.end());
200 reverse(probabilities_.begin(), probabilities_.end());
201 reverse(previousProbabilities_.begin(), previousProbabilities_.end());
202 }
203 // transform(buckets_.begin(), buckets_.end(), buckets_.begin(), std::bind1st(multiplies<Real>(), factor));
204 transform(buckets_.begin(), buckets_.end(), buckets_.begin(), [factor](const Real x) { return x * factor; });
205 // transform(points_.begin(), points_.end(), points_.begin(), std::bind1st(multiplies<Real>(), factor));
206 transform(points_.begin(), points_.end(), points_.begin(), [factor](const Real x) { return x * factor; });
207 // transform(previousPoints_.begin(), previousPoints_.end(), previousPoints_.begin(),
208 // std::bind1st(multiplies<Real>(), factor));
209 transform(previousPoints_.begin(), previousPoints_.end(), previousPoints_.begin(),
210 [factor](const Real x) { return x * factor; });
211}
212
214 vector<Real> probabilities = cumulativeProbabilities();
215 vector<Real>::const_iterator it = lower_bound(buckets_.begin(), buckets_.end(), x);
216 // If x > upper end of last bucket
217 if (it == buckets_.end())
218 return 1.0;
219 // If x <= lower end of first bucket
220 if (it == buckets_.begin())
221 return 0.0;
222 // If x is in range of buckets
223 Size index = it - buckets_.begin();
224 return probabilities[index - 1] + (x - buckets_[index - 1]) * (probabilities[index] - probabilities[index - 1]) /
225 (buckets_[index] - buckets_[index - 1]);
226}
227
229 QL_REQUIRE(0 <= p && p <= 1.0, "Probability must be between 0 and 1");
230 vector<Real> probabilities = cumulativeProbabilities();
231 vector<Real>::const_iterator it = lower_bound(probabilities.begin(), probabilities.end(), p);
232 // If p > last cumulative probability (shouldn't happen since p <= 1.0)
233 if (it == probabilities.end())
234 return buckets_.back();
235 // If p <= first cumulative probability (shouldn't happen since p >= 0.0)
236 if (it == probabilities.begin())
237 return buckets_.front();
238 // If p is in the range of probabilities
239 Size index = it - probabilities.begin();
240 return buckets_[index - 1] + (p - probabilities[index - 1]) * (buckets_[index] - buckets_[index - 1]) /
241 (probabilities[index] - probabilities[index - 1]);
242}
243
245
246 QL_REQUIRE(buckets_.size() - 1 == other.numberBuckets(), "Distributions must have same number of buckets to sum");
247
248 bool bucketsEqual = equal(buckets_.begin(), buckets_.end(), other.buckets().begin(),
249 static_cast<bool (*)(Real, Real)>(&close_enough));
250 QL_REQUIRE(bucketsEqual, "Distributions must have the same buckets to sum");
251
252 transform(probabilities_.begin(), probabilities_.end(), other.probabilities().begin(), probabilities_.begin(),
253 plus<Real>());
254
255 return *this;
256}
257
259 // Create a vector containing midpoint of each bucket
260 vector<Real> midpoints(probabilities_.size(), 0.0);
261 transform(buckets_.begin(), buckets_.end() - 1, buckets_.begin() + 1, midpoints.begin(), plus<Real>());
262 // transform(midpoints.begin(), midpoints.end(), midpoints.begin(), std::bind2nd(divides<Real>(), 2.0));
263 transform(midpoints.begin(), midpoints.end(), midpoints.begin(),
264 [](const Real x) { return x / 2.0; }); // IS THIS CORRECT?!?
265
266 return DiscreteDistribution(midpoints, probabilities_);
267}
268
270 QL_REQUIRE(n < numberBuckets(), "There are not enough buckets to erase");
271 buckets_.erase(buckets_.begin(), buckets_.begin() + n);
272 probabilities_.erase(probabilities_.begin(), probabilities_.begin() + n);
273 points_.erase(points_.begin(), points_.begin() + n);
275 previousPoints_.erase(previousPoints_.begin(), previousPoints_.begin() + n);
276}
277
279
280 BucketedDistribution result = lhs;
281 result += rhs;
282 return result;
283}
284
286 vector<Real> probabilities(rhs.numberBuckets(), 0.0);
287 // transform(rhs.probabilities().begin(), rhs.probabilities().end(), probabilities.begin(),
288 // std::bind1st(multiplies<Real>(), factor));
289 transform(rhs.probabilities().begin(), rhs.probabilities().end(), probabilities.begin(),
290 [factor](const Real x) { return x * factor; });
291 return BucketedDistribution(rhs.buckets(), probabilities, rhs.points());
292}
293
294BucketedDistribution operator*(const BucketedDistribution& lhs, QuantLib::Real factor) { return factor * lhs; }
295
296} // namespace QuantExt
Deals with a bucketed distribution.
Represents a bucketed probability distibution.
void erase(QuantLib::Size n)
Erase the first n buckets from the distribution.
BucketedDistribution & operator+=(const BucketedDistribution &other)
Utility functions.
void applyShift(QuantLib::Real shift)
Shift all buckets and points by an additive shift.
BucketedDistribution()
Default constructor.
void applyFactor(QuantLib::Real factor)
Shift all buckets and points by a multiplicative factor.
void init(QuantLib::Real min, QuantLib::Real max, QuantLib::Size numberBuckets)
Common code used in the constructor.
const std::vector< QuantLib::Real > & probabilities() const
Get the probabilities.
std::vector< QuantLib::Real > points_
Size numberBuckets() const
Return the number of buckets in the distribution.
Real inverseCumulativeProbability(QuantLib::Real p) const
Return inverse cumulative probability given a probability p using linear interpolation.
std::vector< QuantLib::Real > previousPoints_
const std::vector< QuantLib::Real > & buckets() const
Return the buckets of the distribution.
std::vector< QuantLib::Real > previousProbabilities_
void add(const DiscreteDistribution &distribution)
Update the bucketed distribution by adding a discrete distribution.
std::vector< QuantLib::Real > cumulativeProbabilities() const
Return the cumulative probabilities of the distribution.
Real cumulativeProbability(QuantLib::Real x) const
Return cumulative probability at point x using linear interpolation.
std::vector< QuantLib::Real > buckets_
Vector of numbers denoting buckets of distribution.
QuantLib::Size bucket(QuantLib::Real value) const
Returns the index of the bucket containing value.
const std::vector< QuantLib::Real > & points() const
Return the points of the distribution.
DiscreteDistribution createDiscrete() const
Create a DiscreteDistribution from a BucketedDistribution with discrete points at midpoints of the bu...
static const QuantLib::Real minProbability_
std::vector< QuantLib::Real > probabilities_
Vector of numbers denoting probabilities in each bucket.
std::vector< QuantLib::Real > complementaryProbabilities() const
Return 1.0 minus the cumulative probabilities of the distribution.
virtual vector< Distributionpair > get() const
Distributionpair is a helper class for DiscretDistribution.
BucketedDistribution operator+(const BucketedDistribution &lhs, const BucketedDistribution &rhs)
Sum probabilities in two bucketed distributions with equal buckets.
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
CompiledFormula min(CompiledFormula x, const CompiledFormula &y)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
Filter equal(Filter x, const Filter &y)
BucketedDistribution operator*(Real factor, const BucketedDistribution &rhs)