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