1 // -*- C++ -*-
2 /**
3 * @brief The KPermutationsDistribution distribution
4 *
5 * Copyright 2005-2021 Airbus-EDF-IMACS-ONERA-Phimeca
6 *
7 * This library is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21 #include <cmath>
22 #include "openturns/Collection.hxx"
23 #include "openturns/KPermutationsDistribution.hxx"
24 #include "openturns/KPermutations.hxx"
25 #include "openturns/Binomial.hxx"
26 #include "openturns/Poisson.hxx"
27 #include "openturns/TruncatedDistribution.hxx"
28 #include "openturns/RandomGenerator.hxx"
29 #include "openturns/SpecFunc.hxx"
30 #include "openturns/DistFunc.hxx"
31 #include "openturns/Exception.hxx"
32 #include "openturns/PersistentObjectFactory.hxx"
33
34 BEGIN_NAMESPACE_OPENTURNS
35
36 typedef Collection<UnsignedInteger> UnsignedIntegerCollection;
37
38 CLASSNAMEINIT(KPermutationsDistribution)
39
40 static const Factory<KPermutationsDistribution> Factory_KPermutationsDistribution;
41
42 /* Default constructor */
KPermutationsDistribution()43 KPermutationsDistribution::KPermutationsDistribution()
44 : DiscreteDistribution()
45 , k_(0)
46 , n_(0)
47 {
48 setName("KPermutationsDistribution");
49 setKN(1, 1);
50 }
51
52 /* Parameters constructor */
KPermutationsDistribution(const UnsignedInteger k,const UnsignedInteger n)53 KPermutationsDistribution::KPermutationsDistribution(const UnsignedInteger k,
54 const UnsignedInteger n)
55 : DiscreteDistribution()
56 , k_(0)
57 , n_(0)
58 {
59 setName("KPermutationsDistribution");
60 setKN(k, n);
61 }
62
63 /* Comparison operator */
operator ==(const KPermutationsDistribution & other) const64 Bool KPermutationsDistribution::operator ==(const KPermutationsDistribution & other) const
65 {
66 if (this == &other) return true;
67 return (k_ == other.k_) && (n_ == other.n_);
68 }
69
equals(const DistributionImplementation & other) const70 Bool KPermutationsDistribution::equals(const DistributionImplementation & other) const
71 {
72 const KPermutationsDistribution* p_other = dynamic_cast<const KPermutationsDistribution*>(&other);
73 return p_other && (*this == *p_other);
74 }
75
76 /* String converter */
__repr__() const77 String KPermutationsDistribution::__repr__() const
78 {
79 OSS oss;
80 oss << "class=" << KPermutationsDistribution::GetClassName()
81 << " name=" << getName()
82 << " dimension=" << getDimension()
83 << " k=" << k_
84 << " n=" << n_;
85 return oss;
86 }
87
__str__(const String &) const88 String KPermutationsDistribution::__str__(const String & ) const
89 {
90 OSS oss;
91 oss << getClassName() << "(k = " << k_ << ", n = " << n_ << ")";
92 return oss;
93 }
94
95 /* Virtual constructor */
clone() const96 KPermutationsDistribution * KPermutationsDistribution::clone() const
97 {
98 return new KPermutationsDistribution(*this);
99 }
100
101 /* Compute the numerical range of the distribution given the parameters values */
computeRange()102 void KPermutationsDistribution::computeRange()
103 {
104 const Point lowerBound(k_, 0.0);
105 const Point upperBound(k_, n_ - 1.0);
106 const Interval::BoolCollection finiteLowerBound(k_, true);
107 const Interval::BoolCollection finiteUpperBound(k_, true);
108 setRange(Interval(lowerBound, upperBound, finiteLowerBound, finiteUpperBound));
109 }
110
111 /* Get one realization of the distribution */
getRealization() const112 Point KPermutationsDistribution::getRealization() const
113 {
114 Point realization(k_);
115 Indices buffer(n_);
116 buffer.fill();
117 for (UnsignedInteger i = 0; i < k_; ++i)
118 {
119 const UnsignedInteger index = i + RandomGenerator::IntegerGenerate(n_ - i);
120 realization[i] = buffer[index];
121 buffer[index] = buffer[i];
122 }
123 return realization;
124 }
125
126 /* Get the PDF of the distribution */
computeLogPDF(const Point & point) const127 Scalar KPermutationsDistribution::computeLogPDF(const Point & point) const
128 {
129 const UnsignedInteger dimension = getDimension();
130 if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
131 Indices x(k_);
132 for (UnsignedInteger i = 0; i < dimension; ++i)
133 {
134 const Scalar k = point[i];
135 if ((k < -supportEpsilon_) || (k > n_ + supportEpsilon_)) return SpecFunc::LowestScalar;
136 const UnsignedInteger ik = static_cast< UnsignedInteger > (round(k));
137 if (std::abs(k - ik) > supportEpsilon_) return SpecFunc::LowestScalar;
138 x[i] = ik;
139 }
140 if (!x.check(n_)) return SpecFunc::LowestScalar;
141 return logPDFValue_;
142 }
143
computePDF(const Point & point) const144 Scalar KPermutationsDistribution::computePDF(const Point & point) const
145 {
146 const Scalar logPDF = computeLogPDF(point);
147 if (logPDF == SpecFunc::LowestScalar) return 0.0;
148 return std::exp(logPDF);
149 }
150
151 /* Get the CDF of the distribution */
computeCDF(const Point & point) const152 Scalar KPermutationsDistribution::computeCDF(const Point & point) const
153 {
154 const UnsignedInteger dimension = getDimension();
155 if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
156
157 if (dimension == 1) return static_cast < Scalar >(k_) / n_;
158 Point sortedPoint(dimension);
159 for (UnsignedInteger i = 0; i < dimension; ++i)
160 {
161 const Scalar x = point[i];
162 if (x < -supportEpsilon_) return 0.0;
163 sortedPoint[i] = std::min(n_ - 1.0, floor(x + supportEpsilon_));
164 }
165 std::sort(sortedPoint.begin(), sortedPoint.end());
166 Scalar cdfValue = 1.0;
167 for (UnsignedInteger i = 0; i < dimension; ++i) cdfValue *= (sortedPoint[i] + 1.0 - i) / (n_ - i);
168 return cdfValue;
169 }
170
171 /* Compute the scalar quantile of the 1D KPermutationsDistribution distribution */
computeScalarQuantile(const Scalar prob,const Bool tail) const172 Scalar KPermutationsDistribution::computeScalarQuantile(const Scalar prob,
173 const Bool tail) const
174 {
175 const UnsignedInteger i = static_cast< UnsignedInteger >(ceil(prob * (n_ - 1.0)));
176 return (tail ? n_ - 1.0 - i : i);
177 } // computeScalarQuantile
178
179 /* Compute the quantile of the KPermutationsDistribution distribution */
computeQuantile(const Scalar prob,const Bool tail,Scalar & marginalProb) const180 Point KPermutationsDistribution::computeQuantile(const Scalar prob,
181 const Bool tail,
182 Scalar & marginalProb) const
183 {
184 const Scalar p = (tail ? 1.0 - prob : prob);
185 if (p <= 0.0) return Point(k_, 0.0);
186 if (p >= 1.0) return Point(k_, n_);
187 UnsignedInteger iMin = 0;
188 UnsignedInteger iMax = n_;
189 while (iMax > iMin + 1)
190 {
191 const UnsignedInteger iMiddle = (iMax + iMin) / 2;
192 const Scalar cdfMiddle = computeCDF(Point(k_, iMiddle));
193 if (cdfMiddle < p)
194 {
195 iMin = iMiddle;
196 }
197 else
198 {
199 iMax = iMiddle;
200 }
201 } // while
202 marginalProb = computeScalarQuantile(prob, tail);
203 return Point(k_, iMax);
204 } // computeQuantile
205
206 /* Get the i-th marginal distribution */
getMarginal(const UnsignedInteger i) const207 Distribution KPermutationsDistribution::getMarginal(const UnsignedInteger i) const
208 {
209 const UnsignedInteger dimension = getDimension();
210 if (i >= dimension) throw InvalidArgumentException(HERE) << "The index of a marginal distribution must be in the range [0, dim-1]";
211 KPermutationsDistribution::Implementation marginal(new KPermutationsDistribution(1, n_));
212 marginal->setDescription(Description(1, getDescription()[i]));
213 return marginal;
214 }
215
216 /* Get the distribution of the marginal distribution corresponding to indices dimensions */
getMarginal(const Indices & indices) const217 Distribution KPermutationsDistribution::getMarginal(const Indices & indices) const
218 {
219 const UnsignedInteger dimension = getDimension();
220 if (!indices.check(dimension)) throw InvalidArgumentException(HERE) << "The indices of a marginal distribution must be in the range [0, dim-1] and must be different";
221 // Special case for dimension 1
222 if (dimension == 1) return clone();
223 // General case
224 const UnsignedInteger outputDimension = indices.getSize();
225 Description description(getDescription());
226 Description marginalDescription(outputDimension);
227 for (UnsignedInteger i = 0; i < outputDimension; ++i)
228 {
229 const UnsignedInteger index_i = indices[i];
230 marginalDescription[i] = description[index_i];
231 }
232 KPermutationsDistribution::Implementation marginal(new KPermutationsDistribution(outputDimension, n_));
233 marginal->setDescription(marginalDescription);
234 return marginal;
235 } // getMarginal(Indices)
236
237 /* Get the support of a discrete distribution that intersect a given interval */
getSupport(const Interval & interval) const238 Sample KPermutationsDistribution::getSupport(const Interval & interval) const
239 {
240 if (interval.getDimension() != getDimension()) throw InvalidArgumentException(HERE) << "Error: the given interval has a dimension that does not match the distribution dimension.";
241 // Convert int values into float
242 const IndicesCollection intResult(KPermutations(k_, n_).generate());
243 const UnsignedInteger size = intResult.getSize();
244 if (size == 0) return Sample();
245 const UnsignedInteger dimension = intResult.cend_at(0) - intResult.cbegin_at(0);
246 Sample result(size, dimension);
247 for (UnsignedInteger i = 0; i < size; ++i)
248 for (UnsignedInteger j = 0; j < dimension; ++j)
249 result(i, j) = intResult(i, j);
250 return result;
251 }
252
253 /* Compute the mean of the distribution */
computeMean() const254 void KPermutationsDistribution::computeMean() const
255 {
256 mean_ = Point(k_, 0.5 * (n_ - 1.0));
257 isAlreadyComputedMean_ = true;
258 }
259
260 /* Compute the covariance of the distribution */
computeCovariance() const261 void KPermutationsDistribution::computeCovariance() const
262 {
263 const Scalar var = (n_ * n_ - 1.0) / 12.0;
264 const Scalar cov = -(n_ + 1.0) / 12.0;
265 covariance_ = CovarianceMatrix(k_, Point(k_ * k_, cov));
266 for (UnsignedInteger i = 0; i < k_; ++i) covariance_(i, i) = var;
267 isAlreadyComputedCovariance_ = true;
268 }
269
270 /* Parameters value and description accessor */
getParametersCollection() const271 KPermutationsDistribution::PointWithDescriptionCollection KPermutationsDistribution::getParametersCollection() const
272 {
273 const UnsignedInteger dimension = getDimension();
274 PointWithDescriptionCollection parameters((dimension == 1 ? 1 : dimension + 1));
275 for (UnsignedInteger i = 0; i < dimension; ++i)
276 {
277 PointWithDescription point(1);
278 point[0] = n_;
279 Description description(1);
280 description[0] = "n";
281 point.setDescription(description);
282 point.setName(getDescription()[i]);
283 parameters[i] = point;
284 }
285 if (dimension > 1)
286 {
287 PointWithDescription point(2);
288 Description description = {"k", "n"};
289 point[0] = k_;
290 point[1] = n_;
291 point.setDescription(description);
292 point.setName("dependence");
293 parameters[dimension] = point;
294 }
295 return parameters;
296 }
297
298 /* K accessor */
setK(const UnsignedInteger k)299 void KPermutationsDistribution::setK(const UnsignedInteger k)
300 {
301 if (k == 0) throw InvalidArgumentException(HERE) << "Error: k must be > 0.";
302 if (k > n_) throw InvalidArgumentException(HERE) << "Error: k must be less or equal to n, here k=" << k << " and n=" << n_;
303 if (k != k_)
304 {
305 k_ = k;
306 logPDFValue_ = SpecFunc::LnGamma(n_ - k_ + 1) - SpecFunc::LnGamma(n_ + 1);
307 setDimension(k);
308 isAlreadyComputedMean_ = false;
309 isAlreadyComputedCovariance_ = false;
310 isAlreadyCreatedGeneratingFunction_ = false;
311 }
312 }
313
314 /* K accessor */
getK() const315 UnsignedInteger KPermutationsDistribution::getK() const
316 {
317 return k_;
318 }
319
320 /* N accessor */
setN(const UnsignedInteger n)321 void KPermutationsDistribution::setN(const UnsignedInteger n)
322 {
323 if (n == 0) throw InvalidArgumentException(HERE) << "Error: n must be > 0.";
324 if (n < k_) throw InvalidArgumentException(HERE) << "Error: n must be greater or equal to k, here n=" << n << " and k=" << k_;
325 if (n != n_)
326 {
327 n_ = n;
328 logPDFValue_ = SpecFunc::LnGamma(n_ - k_ + 1) - SpecFunc::LnGamma(n_ + 1);
329 isAlreadyComputedMean_ = false;
330 isAlreadyComputedCovariance_ = false;
331 computeRange();
332 }
333 }
334
getN() const335 UnsignedInteger KPermutationsDistribution::getN() const
336 {
337 return n_;
338 }
339
340 /* K/N accessor */
setKN(const UnsignedInteger k,const UnsignedInteger n)341 void KPermutationsDistribution::setKN(const UnsignedInteger k,
342 const UnsignedInteger n)
343 {
344 if (k == 0) throw InvalidArgumentException(HERE) << "Error: k must be > 0.";
345 if (n == 0) throw InvalidArgumentException(HERE) << "Error: n must be > 0.";
346 if (k > n) throw InvalidArgumentException(HERE) << "Error: k must be less or equal to n, here k=" << k << " and n=" << n;
347 k_ = k;
348 setDimension(k);
349 n_ = n;
350 logPDFValue_ = SpecFunc::LnGamma(n_ - k_ + 1) - SpecFunc::LnGamma(n_ + 1);
351 isAlreadyComputedMean_ = false;
352 isAlreadyComputedCovariance_ = false;
353 computeRange();
354 }
355
356 /* Method save() stores the object through the StorageManager */
save(Advocate & adv) const357 void KPermutationsDistribution::save(Advocate & adv) const
358 {
359 DiscreteDistribution::save(adv);
360 adv.saveAttribute( "k_", k_ );
361 adv.saveAttribute( "n_", n_ );
362 adv.saveAttribute( "logPDFValue_", logPDFValue_ );
363 }
364
365 /* Method load() reloads the object from the StorageManager */
load(Advocate & adv)366 void KPermutationsDistribution::load(Advocate & adv)
367 {
368 DiscreteDistribution::load(adv);
369 adv.loadAttribute( "k_", k_ );
370 adv.loadAttribute( "n_", n_ );
371 adv.loadAttribute( "logPDFValue_", logPDFValue_ );
372 computeRange();
373 }
374
375 END_NAMESPACE_OPENTURNS
376