1 //                                               -*- C++ -*-
2 /**
3  *  @brief The Student 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 <cstdlib>
22 #include <cmath>
23 
24 #include "openturns/Student.hxx"
25 #include "openturns/Distribution.hxx"
26 #include "openturns/RandomGenerator.hxx"
27 #include "openturns/SpecFunc.hxx"
28 #include "openturns/DistFunc.hxx"
29 #include "openturns/Log.hxx"
30 #include "openturns/OSS.hxx"
31 #include "openturns/PersistentObjectFactory.hxx"
32 #include "openturns/Point.hxx"
33 #include "openturns/Exception.hxx"
34 
35 BEGIN_NAMESPACE_OPENTURNS
36 
37 
38 
39 CLASSNAMEINIT(Student)
40 
41 static const Factory<Student> Factory_Student;
42 
43 /* Default constructor */
Student(const Scalar nu,const UnsignedInteger dimension)44 Student::Student(const Scalar nu,
45                  const UnsignedInteger dimension)
46   : EllipticalDistribution(Point(dimension, 0.0),
47                            Point(dimension, 1.0),
48                            CorrelationMatrix(dimension), -1.0)
49   , nu_(0.0)
50   , studentNormalizationFactor_(0.0)
51 {
52   setName("Student");
53   setDimension( dimension );
54   // This call set also the range
55   setNu(nu);
56 }
57 
58 /* Constructor for 1D Student distribution */
Student(const Scalar nu,const Scalar mu,const Scalar sigma)59 Student::Student(const Scalar nu,
60                  const Scalar mu,
61                  const Scalar sigma)
62   : EllipticalDistribution(Point(1, mu), Point(1, sigma), CorrelationMatrix(1), -1.0)
63   , nu_(0.0)
64   , studentNormalizationFactor_(0.0)
65 {
66   setName("Student");
67   setDimension(1);
68   // Set nu with checks. This call set also the range.
69   setNu(nu);
70 }
71 
72 /* Constructor for multiD Student distribution */
Student(const Scalar nu,const Point & mu,const Point & sigma,const CorrelationMatrix & R)73 Student::Student(const Scalar nu,
74                  const Point & mu,
75                  const Point & sigma,
76                  const CorrelationMatrix & R)
77   : EllipticalDistribution(mu, sigma, R, -1.0)
78   , nu_(0.0)
79   , studentNormalizationFactor_(0.0)
80 {
81   setName("Student");
82   setDimension(mu.getDimension());
83   // Set nu with checks. This call set also the range.
84   setNu(nu);
85 }
86 
Student(const Scalar nu,const Point & mu,const Point & sigma)87 Student::Student(const Scalar nu,
88                  const Point & mu,
89                  const Point & sigma)
90   : Student(nu, mu, sigma, CorrelationMatrix(mu.getDimension()))
91 {
92   // Nothing to do
93 }
94 
95 /* Comparison operator */
operator ==(const Student & other) const96 Bool Student::operator ==(const Student & other) const
97 {
98   if (this == &other) return true;
99   return (nu_ == other.nu_) && EllipticalDistribution::equals(other);
100 }
101 
equals(const DistributionImplementation & other) const102 Bool Student::equals(const DistributionImplementation & other) const
103 {
104   const Student* p_other = dynamic_cast<const Student*>(&other);
105   return p_other && (*this == *p_other);
106 }
107 
108 /* String converter */
__repr__() const109 String Student::__repr__() const
110 {
111   OSS oss(true);
112   oss << "class=" << Student::GetClassName()
113       << " name=" << getName()
114       << " dimension=" << getDimension()
115       << " nu=" << nu_
116       << " mean=" << mean_
117       << " sigma=" << sigma_
118       << " correlationMatrix=" << R_;
119   return oss;
120 }
121 
__str__(const String & offset) const122 String Student::__str__(const String & offset) const
123 {
124   OSS oss(false);
125   oss << getClassName();
126   if (getDimension() == 1) oss << "(nu = " << nu_ << ", mu = " << getMean()[0] << ", sigma = " << getSigma()[0] << ")";
127   else oss << "(nu = " << nu_ << ", mu = " << getMean().__str__() << ", sigma = " << getSigma().__str__() << ", R = " << getCorrelation().__str__(offset) << ")";
128   return oss;
129 }
130 
131 /* Compute the density generator of the elliptical generator, i.e.
132  *  the function phi such that the density of the distribution can
133  *  be written as p(x) = phi(t(x-mu)S^(-1)(x-mu))                      */
computeDensityGenerator(const Scalar betaSquare) const134 Scalar Student::computeDensityGenerator(const Scalar betaSquare) const
135 {
136   return std::exp(studentNormalizationFactor_ - 0.5 * (nu_ + getDimension()) * log1p(betaSquare / nu_));
137 }
138 
computeLogDensityGenerator(const Scalar betaSquare) const139 Scalar Student::computeLogDensityGenerator(const Scalar betaSquare) const
140 {
141   return studentNormalizationFactor_ - 0.5 * (nu_ + getDimension()) * log1p(betaSquare / nu_);
142 }
143 
144 /* Compute the derivative of the density generator */
computeDensityGeneratorDerivative(const Scalar betaSquare) const145 Scalar Student::computeDensityGeneratorDerivative(const Scalar betaSquare) const
146 {
147   const Scalar iNu = 1.0 / nu_;
148   const UnsignedInteger dimension = getDimension();
149   return -0.5 * std::exp(studentNormalizationFactor_ - (0.5 * (nu_ + dimension) + 1.0) * log1p(betaSquare * iNu)) * (1.0 + dimension * iNu);
150 }
151 
152 /* Compute the second derivative of the density generator */
computeDensityGeneratorSecondDerivative(const Scalar betaSquare) const153 Scalar Student::computeDensityGeneratorSecondDerivative(const Scalar betaSquare) const
154 {
155   const Scalar iNu = 1.0 / nu_;
156   const UnsignedInteger dimension = getDimension();
157   return 0.25 * std::exp(studentNormalizationFactor_ - (0.5 * (nu_ + dimension) + 2.0) * log1p(betaSquare * iNu)) * (1.0 + dimension * iNu) * (1.0 + (dimension + 2.0) * iNu);
158 }
159 
160 
161 /* Virtual constructor */
clone() const162 Student * Student::clone() const
163 {
164   return new Student(*this);
165 }
166 
167 /* Get one realization of the distribution */
getRealization() const168 Point Student::getRealization() const
169 {
170   const UnsignedInteger dimension = getDimension();
171   if (dimension == 1) return Point(1, mean_[0] + sigma_[0] * DistFunc::rStudent(nu_));
172   Point value(dimension);
173   // First, a realization of independant standard normal coordinates
174   for (UnsignedInteger i = 0; i < dimension; ++i) value[i] = DistFunc::rNormal();
175   return std::sqrt(0.5 * nu_ / DistFunc::rGamma(0.5 * nu_)) * (cholesky_ * value) + mean_;
176 }
177 
178 
getSample(const UnsignedInteger size) const179 Sample Student::getSample(const UnsignedInteger size) const
180 {
181   const UnsignedInteger dimension = getDimension();
182   Sample normalSample(size, dimension);
183   Point gammaDeviates(size);
184   for (UnsignedInteger i = 0; i < size; ++i)
185   {
186     for (UnsignedInteger j = 0; j < dimension; ++j)
187       normalSample(i, j) = DistFunc::rNormal();
188     gammaDeviates[i] = DistFunc::rGamma(0.5 * nu_);
189   }
190   Sample result;
191   if (dimension == 1)
192     result = normalSample * sigma_[0];
193   else
194     result = (cholesky_.getImplementation()->genSampleProd(normalSample, true, false, 'R'));
195   for (UnsignedInteger i = 0; i < size; ++i)
196   {
197     const Scalar alpha = std::sqrt(0.5 * nu_ / gammaDeviates[i]);
198     for (UnsignedInteger j = 0; j < dimension; ++j)
199       result(i, j) = result(i, j) * alpha + mean_[j];
200   }
201   result.setName(getName());
202   result.setDescription(getDescription());
203   return result;
204 }
205 
206 
207 /* Get the CDF of the distribution */
computeCDF(const Point & point) const208 Scalar Student::computeCDF(const Point & point) const
209 {
210   const UnsignedInteger dimension = getDimension();
211   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point has a dimension incompatible with the distribution.";
212   // Special case for dimension 1
213   if (dimension == 1) return DistFunc::pStudent(nu_, (point[0] - mean_[0]) / sigma_[0]);
214   // For moderate dimension, use a Gauss-Legendre integration
215   if (dimension <= ResourceMap::GetAsUnsignedInteger("Student-SmallDimension"))
216   {
217     // Reduce the default integration point number for CDF computation in the range 3 < dimension <= Student-SmallDimension
218     const UnsignedInteger maximumNumber = static_cast< UnsignedInteger > (round(std::pow(ResourceMap::GetAsUnsignedInteger( "Student-MaximumNumberOfPoints" ), 1.0 / getDimension())));
219     const UnsignedInteger candidateNumber = ResourceMap::GetAsUnsignedInteger( "Student-MarginalIntegrationNodesNumber" );
220     if (candidateNumber > maximumNumber) LOGWARN(OSS() << "Warning! The requested number of marginal integration nodes=" << candidateNumber << " would lead to an excessive number of PDF evaluations. It has been reduced to " << maximumNumber << ". You should increase the ResourceMap key \"Student-MaximumNumberOfPoints\"");
221     setIntegrationNodesNumber(std::min(maximumNumber, candidateNumber));
222     return ContinuousDistribution::computeCDF(point);
223   }
224   // For very large dimension, use a MonteCarlo algorithm
225   LOGWARN(OSS() << "Warning, in Student::computeCDF(), the dimension is very high. We will use a Monte Carlo method for the computation with a relative precision of 0.1% at 99% confidence level and a maximum of " << 10 * ResourceMap::GetAsUnsignedInteger( "Student-MaximumNumberOfPoints" ) << " realizations. Expect a long running time and a poor accuracy for small values of the CDF...");
226   RandomGeneratorState initialState(RandomGenerator::GetState());
227   RandomGenerator::SetSeed(ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" ));
228   Scalar value = 0.0;
229   Scalar variance = 0.0;
230   Scalar a99 = DistFunc::qNormal(0.995);
231   const UnsignedInteger blockSize = ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" );
232   UnsignedInteger outerMax = 10 * ResourceMap::GetAsUnsignedInteger( "Student-MaximumNumberOfPoints" ) / blockSize;
233   Scalar precision = 0.0;
234   for (UnsignedInteger indexOuter = 0; indexOuter < outerMax; ++indexOuter)
235   {
236     const Sample sample(getSample(blockSize));
237     LOGDEBUG(OSS(false) << "indexOuter=" << indexOuter << ", point=" << point << ", sample=" << sample);
238     const Scalar valueBlock = sample.computeEmpiricalCDF(point);
239     const Scalar varianceBlock = valueBlock * (1.0 - valueBlock) / blockSize;
240     LOGDEBUG(OSS(false) << "valueBlock=" << valueBlock << ", varianceBlock=" << varianceBlock);
241     const Scalar norm = 1.0 / (indexOuter + 1.0);
242     variance = (varianceBlock + indexOuter * variance + (1.0 - norm) * (value - valueBlock) * (value - valueBlock)) * norm;
243     value = (value * indexOuter + valueBlock) * norm;
244     LOGDEBUG(OSS(false) << "value=" << value << ", variance=" << variance);
245     // Quick return for value = 1
246     if ((value >= 1.0 - ResourceMap::GetAsScalar("Distribution-DefaultQuantileEpsilon")) && (variance == 0.0)) return 1.0;
247     precision = a99 * std::sqrt(variance / (indexOuter + 1.0) / ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" ));
248     if (precision < ResourceMap::GetAsScalar( "Student-MinimumCDFEpsilon" ) * value) return value;
249     // 0.1 * ((1000 * indexOuter) / outerMax) is to print percents with one figure after the decimal point
250     LOGINFO(OSS() << 0.1 * ((1000 * indexOuter) / outerMax) << "% value=" << value << " absolute precision(99%)=" << precision << " relative precision(99%)=" << ((value > 0.0) ? precision / value : -1.0));
251   }
252   RandomGenerator::SetState(initialState);
253   return value;
254 } // computeCDF
255 
computeCDF(const Sample & sample) const256 Sample Student::computeCDF(const Sample & sample) const
257 {
258   if (dimension_ <= ResourceMap::GetAsUnsignedInteger("Student-SmallDimension"))
259     return DistributionImplementation::computeCDFParallel(sample);
260   return DistributionImplementation::computeCDFSequential(sample);
261 }
262 
263 /* Compute the probability content of an interval */
computeProbability(const Interval & interval) const264 Scalar Student::computeProbability(const Interval & interval) const
265 {
266   const UnsignedInteger dimension = getDimension();
267   if (interval.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given interval must have dimension=" << dimension << ", here dimension=" << interval.getDimension();
268 
269   if (interval.isEmpty()) return 0.0;
270   // The generic implementation provided by the DistributionImplementation upper class is more accurate than the generic implementation provided by the ContinuousDistribution upper class for dimension = 1
271   if (dimension == 1) return DistributionImplementation::computeProbability(interval);
272   // Decompose and normalize the interval
273   Point lower(normalize(interval.getLowerBound()));
274   Point upper(normalize(interval.getUpperBound()));
275   const Interval::BoolCollection finiteLower(interval.getFiniteLowerBound());
276   const Interval::BoolCollection finiteUpper(interval.getFiniteUpperBound());
277   /* General case */
278   // For moderate dimension, use a Gauss-Legendre integration
279   if (dimension <= ResourceMap::GetAsUnsignedInteger("Student-SmallDimension"))
280   {
281     // Reduce the default integration point number for CDF computation in the range 3 < dimension <= Student-SmallDimension
282     const UnsignedInteger maximumNumber = static_cast< UnsignedInteger > (round(std::pow(ResourceMap::GetAsUnsignedInteger( "Student-MaximumNumberOfPoints" ), 1.0 / getDimension())));
283     const UnsignedInteger candidateNumber = ResourceMap::GetAsUnsignedInteger( "Student-MarginalIntegrationNodesNumber" );
284     if (candidateNumber > maximumNumber) LOGWARN(OSS() << "Warning! The requested number of marginal integration nodes=" << candidateNumber << " would lead to an excessive number of PDF evaluations. It has been reduced to " << maximumNumber << ". You should increase the ResourceMap key \"Student-MaximumNumberOfPoints\"");
285     setIntegrationNodesNumber(std::min(maximumNumber, candidateNumber));
286     return ContinuousDistribution::computeProbability(interval);
287   }
288   // For very large dimension, use a MonteCarlo algorithm
289   LOGWARN(OSS() << "Warning, in Student::computeProbability(), the dimension is very high. We will use a Monte Carlo method for the computation with a relative precision of 0.1% at 99% confidence level and a maximum of " << 10.0 * ResourceMap::GetAsUnsignedInteger( "Student-MaximumNumberOfPoints" ) << " realizations. Expect a long running time and a poor accuracy for low values of the CDF...");
290   RandomGeneratorState initialState(RandomGenerator::GetState());
291   RandomGenerator::SetSeed(ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" ));
292   Scalar value = 0.0;
293   Scalar variance = 0.0;
294   const Scalar a99 = DistFunc::qNormal(0.995);
295   UnsignedInteger outerMax = 10 * ResourceMap::GetAsUnsignedInteger( "Student-MaximumNumberOfPoints" ) / ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" );
296   Scalar precision = 0.0;
297   for (UnsignedInteger indexOuter = 0; indexOuter < outerMax; ++indexOuter)
298   {
299     Scalar valueBlock = 0.0;
300     Scalar varianceBlock = 0.0;
301     for (UnsignedInteger indexSample = 0; indexSample < ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" ); ++indexSample)
302     {
303       // ind value is 1.0 if the realization is inside of the integration domain, 0.0 else.
304       Scalar ind = interval.numericallyContains(getRealization());
305       Scalar norm = 1.0 / (indexSample + 1.0);
306       varianceBlock = (varianceBlock * indexSample + (1.0 - norm) * (valueBlock - ind) * (valueBlock - ind)) * norm;
307       valueBlock = (valueBlock * indexSample + ind) * norm;
308     }
309     Scalar norm = 1.0 / (indexOuter + 1.0);
310     variance = (varianceBlock + indexOuter * variance + (1.0 - norm) * (value - valueBlock) * (value - valueBlock)) * norm;
311     value = (value * indexOuter + valueBlock) * norm;
312     // Quick return for value = 1
313     const Scalar quantileEpsilon = ResourceMap::GetAsScalar("Distribution-DefaultQuantileEpsilon");
314     if ((value >= 1.0 - quantileEpsilon) && (variance == 0.0)) return 1.0;
315     precision = a99 * std::sqrt(variance / (indexOuter + 1.0) / ResourceMap::GetAsUnsignedInteger( "Student-MinimumNumberOfPoints" ));
316     if (precision < ResourceMap::GetAsScalar( "Student-MinimumCDFEpsilon" ) * value) return value;
317     // 0.1 * ((1000 * indexOuter) / outerMax) is to print percents with one figure after the decimal point
318     LOGINFO(OSS() << 0.1 * ((1000 * indexOuter) / outerMax) << "% value=" << value << " absolute precision(99%)=" << precision << " relative precision(99%)=" << ((value > 0.0) ? precision / value : -1.0));
319   }
320   RandomGenerator::SetState(initialState);
321   return value;
322 }
323 
324 /* Compute the entropy of the distribution */
computeEntropy() const325 Scalar Student::computeEntropy() const
326 {
327   const UnsignedInteger dimension = getDimension();
328   // normalizationFactor_ == 1/\sqrt{|Det(\Sigma)|}
329   // studentNormalizationFactor_ = SpecFunc::LnGamma(0.5 * (nu + dimension)) - SpecFunc::LnGamma(0.5 * nu) - 0.5 * dimension * std::log(nu * M_PI);
330   return 0.5 * (nu_ + dimension) * (SpecFunc::Psi(0.5 * (nu_ + dimension)) - SpecFunc::Psi(0.5 * nu_)) - std::log(normalizationFactor_) - studentNormalizationFactor_;
331 }
332 
333 /* Get the PDFGradient of the distribution */
computePDFGradient(const Point & point) const334 Point Student::computePDFGradient(const Point & point) const
335 {
336   const UnsignedInteger dimension = getDimension();
337 
338   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
339 
340   Point pdfGradient(2 * dimension + 1, 0.0);
341   if (dimension == 1)
342   {
343     const Point ellipticalPDFGradient(EllipticalDistribution::computePDFGradient(point));
344     const Scalar epsNu = 1e-3;
345     pdfGradient[0] = (Student(nu_ + epsNu, mean_, sigma_, R_).computePDF(point) - Student(nu_ - epsNu, mean_, sigma_, R_).computePDF(point)) / (2.0 * epsNu);
346     for (UnsignedInteger i = 0; i < 2 * dimension; ++i) pdfGradient[i + 1] = ellipticalPDFGradient[i];
347     return pdfGradient;
348   }
349   else throw NotYetImplementedException(HERE) << "In Student::computePDFGradient(const Point & point) const";
350 }
351 
352 /* Get the CDFGradient of the distribution */
computeCDFGradient(const Point & point) const353 Point Student::computeCDFGradient(const Point & point) const
354 {
355   const UnsignedInteger dimension = getDimension();
356 
357   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
358 
359   if (dimension == 1)
360   {
361     Point cdfGradient(3, 0.0);
362     const Scalar x = point[0] - mean_[0];
363     const Scalar eps = std::pow(ResourceMap::GetAsScalar("DistFunc-Precision"), 1.0 / 3.0);
364     const Scalar i2Eps = 0.5 / eps;
365     cdfGradient[0] = (DistFunc::pStudent(nu_ + eps, x / sigma_[0]) - DistFunc::pStudent(nu_ - eps, x / sigma_[0])) * i2Eps;
366     // Opposite sign for eps because x - eps = point[0] - (mu + eps)
367     cdfGradient[1] = (DistFunc::pStudent(nu_, (x - eps) / sigma_[0]) - DistFunc::pStudent(nu_, (x + eps) / sigma_[0])) * i2Eps;
368     cdfGradient[2] = (DistFunc::pStudent(nu_, x / (sigma_[0] + eps)) - DistFunc::pStudent(nu_, x / (sigma_[0] - eps))) * i2Eps;
369     return cdfGradient;
370   }
371   else throw NotYetImplementedException(HERE) << "In Student::computeCDFGradient(const Point & point) const";
372 }
373 
374 /* Compute the PDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1)
375    For Student distribution, the conditional distribution is also Student, with mean and covariance
376    such as:
377    mean_cond = mean(x) + cov(x, y).cov(y, y)^(-1)(y - mean(y))
378    cov_cond = cov(x, x) - cov(x, y).cov(y, y)^(-1)cov(x, y)
379    This expression simplifies if we use the inverse of the Cholesky factor of the covariance matrix.
380    See [Lebrun, Dutfoy, "Rosenblatt and Nataf transformation"]
381    The number of degrees of freedom is modified according to nu_cond = nu + dim(cond)
382 */
computeConditionalPDF(const Scalar x,const Point & y) const383 Scalar Student::computeConditionalPDF(const Scalar x,
384                                       const Point & y) const
385 {
386   const UnsignedInteger conditioningDimension = y.getDimension();
387   if (conditioningDimension >= getDimension()) throw InvalidArgumentException(HERE) << "Error: cannot compute a conditional PDF with a conditioning point of dimension greater or equal to the distribution dimension.";
388   // Special case for no conditioning or independent copula
389   if (conditioningDimension == 0)
390   {
391     const Scalar z = (x - mean_[conditioningDimension]) / sigma_[conditioningDimension];
392     return std::exp(-0.5 * (nu_ + 1.0) * log1p(z * z / nu_) - SpecFunc::LogBeta(0.5, 0.5 * nu_)) / sigma_[conditioningDimension] / std::sqrt(nu_);
393   }
394   // General case
395   // Extract the Cholesky factor of the covariance of Y
396   MatrixImplementation cholY(conditioningDimension, conditioningDimension);
397   UnsignedInteger start = 0;
398   UnsignedInteger stop = conditioningDimension;
399   UnsignedInteger shift = 0;
400   Point yCentered(conditioningDimension);
401   for (UnsignedInteger i = 0; i < conditioningDimension; ++i)
402   {
403     yCentered[i] = y[i] - mean_[i];
404     std::copy(cholesky_.getImplementation()->begin() + start, cholesky_.getImplementation()->begin() + stop, cholY.begin() + shift);
405     start += dimension_ + 1;
406     stop += dimension_;
407     shift += conditioningDimension + 1;
408   }
409   const Scalar sigmaRos = 1.0 / inverseCholesky_(conditioningDimension, conditioningDimension);
410   const Scalar nuCond = nu_ + conditioningDimension;
411   Scalar sigmaCond = std::sqrt((nu_ + Point(cholY.solveLinearSystemTri(yCentered)).normSquare()) / nuCond) * sigmaRos;
412   Scalar meanRos = 0.0;
413   for (UnsignedInteger i = 0; i < conditioningDimension; ++i)
414     meanRos += inverseCholesky_(conditioningDimension, i) * yCentered[i] / std::sqrt(sigma_[i]);
415   meanRos = mean_[conditioningDimension] - sigmaRos * std::sqrt(sigma_[conditioningDimension]) * meanRos;
416   const Scalar z = (x - meanRos) / sigmaCond;
417   Scalar value = std::exp(-0.5 * (nuCond + 1.0) * log1p(z * z / nuCond) - SpecFunc::LogBeta(0.5, 0.5 * nuCond)) / sigmaCond / std::sqrt(nuCond);
418   return value;
419 }
420 
computeSequentialConditionalPDF(const Point & x) const421 Point Student::computeSequentialConditionalPDF(const Point & x) const
422 {
423   if (x.getDimension() != dimension_) throw InvalidArgumentException(HERE) << "Error: cannot compute sequential conditional PDF with an argument of dimension=" << x.getDimension() << " different from distribution dimension=" << dimension_;
424   Point result(dimension_);
425   // Waiting for a better implementation
426   Point y(0);
427   for (UnsignedInteger i = 0; i < dimension_; ++i)
428   {
429     const Scalar xI = x[i];
430     result[i] = computeConditionalPDF(xI, y);
431     y.add(xI);
432   }
433   return result;
434 }
435 
436 /* Compute the CDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1) */
computeConditionalCDF(const Scalar x,const Point & y) const437 Scalar Student::computeConditionalCDF(const Scalar x,
438                                       const Point & y) const
439 {
440   const UnsignedInteger conditioningDimension = y.getDimension();
441   if (conditioningDimension >= getDimension()) throw InvalidArgumentException(HERE) << "Error: cannot compute a conditional CDF with a conditioning point of dimension greater or equal to the distribution dimension.";
442   // Special case for no conditioning or independent copula
443   if (conditioningDimension == 0)
444     return DistFunc::pStudent(nu_, (x - mean_[conditioningDimension]) / sigma_[conditioningDimension]);
445   // General case
446   // Extract the Cholesky factor of the covariance of Y
447   MatrixImplementation cholY(conditioningDimension, conditioningDimension);
448   UnsignedInteger start = 0;
449   UnsignedInteger stop = conditioningDimension;
450   UnsignedInteger shift = 0;
451   Point yCentered(conditioningDimension);
452   for (UnsignedInteger i = 0; i < conditioningDimension; ++i)
453   {
454     yCentered[i] = y[i] - mean_[i];
455     std::copy(cholesky_.getImplementation()->begin() + start, cholesky_.getImplementation()->begin() + stop, cholY.begin() + shift);
456     start += dimension_ + 1;
457     stop += dimension_;
458     shift += conditioningDimension + 1;
459   }
460   const Scalar sigmaRos = 1.0 / inverseCholesky_(conditioningDimension, conditioningDimension);
461   const Scalar nuCond = nu_ + conditioningDimension;
462   Scalar sigmaCond = std::sqrt((nu_ + Point(cholY.solveLinearSystemTri(yCentered)).normSquare()) / nuCond) * sigmaRos;
463   Scalar meanRos = 0.0;
464   for (UnsignedInteger i = 0; i < conditioningDimension; ++i)
465     meanRos += inverseCholesky_(conditioningDimension, i) * yCentered[i] / std::sqrt(sigma_[i]);
466   meanRos = mean_[conditioningDimension] - sigmaRos * std::sqrt(sigma_[conditioningDimension]) * meanRos;
467   return DistFunc::pStudent(nuCond, (x - meanRos) / sigmaCond);
468 }
469 
computeSequentialConditionalCDF(const Point & x) const470 Point Student::computeSequentialConditionalCDF(const Point & x) const
471 {
472   if (x.getDimension() != dimension_) throw InvalidArgumentException(HERE) << "Error: cannot compute sequential conditional CDF with an argument of dimension=" << x.getDimension() << " different from distribution dimension=" << dimension_;
473   Point result(dimension_);
474   // Waiting for a better implementation
475   Point y(0);
476   for (UnsignedInteger i = 0; i < dimension_; ++i)
477   {
478     const Scalar xI = x[i];
479     result[i] = computeConditionalCDF(xI, y);
480     y.add(xI);
481   }
482   return result;
483 }
484 
485 /* Compute the quantile of Xi | X1, ..., Xi-1, i.e. x such that CDF(x|y) = q with x = Xi, y = (X1,...,Xi-1) */
computeConditionalQuantile(const Scalar q,const Point & y) const486 Scalar Student::computeConditionalQuantile(const Scalar q,
487     const Point & y) const
488 {
489   const UnsignedInteger conditioningDimension = y.getDimension();
490   if (conditioningDimension >= getDimension()) throw InvalidArgumentException(HERE) << "Error: cannot compute a conditional quantile with a conditioning point of dimension greater or equal to the distribution dimension.";
491   if ((q < 0.0) || (q > 1.0)) throw InvalidArgumentException(HERE) << "Error: cannot compute a conditional quantile for a probability level outside of [0, 1]";
492   // Special case when no contitioning or independent copula
493   if (conditioningDimension == 0) return mean_[0] + sigma_[0] * DistFunc::qStudent(nu_, q);
494   // General case
495   // Extract the Cholesky factor of the covariance of Y
496   MatrixImplementation cholY(conditioningDimension, conditioningDimension);
497   UnsignedInteger start = 0;
498   UnsignedInteger stop = conditioningDimension;
499   UnsignedInteger shift = 0;
500   Point yCentered(conditioningDimension);
501   for (UnsignedInteger i = 0; i < conditioningDimension; ++i)
502   {
503     yCentered[i] = y[i] - mean_[i];
504     std::copy(cholesky_.getImplementation()->begin() + start, cholesky_.getImplementation()->begin() + stop, cholY.begin() + shift);
505     start += dimension_ + 1;
506     stop += dimension_;
507     shift += conditioningDimension + 1;
508   }
509   const Scalar sigmaRos = 1.0 / inverseCholesky_(conditioningDimension, conditioningDimension);
510   const Scalar nuCond = nu_ + conditioningDimension;
511   Scalar sigmaCond = std::sqrt((nu_ + Point(cholY.solveLinearSystemTri(yCentered)).normSquare()) / nuCond) * sigmaRos;
512   Scalar meanRos = 0.0;
513   for (UnsignedInteger i = 0; i < conditioningDimension; ++i)
514     meanRos += inverseCholesky_(conditioningDimension, i) * yCentered[i] / std::sqrt(sigma_[i]);
515   meanRos = mean_[conditioningDimension] - sigmaRos * std::sqrt(sigma_[conditioningDimension]) * meanRos;
516   return meanRos + sigmaCond * DistFunc::qStudent(nuCond, q);
517 }
518 
computeSequentialConditionalQuantile(const Point & q) const519 Point Student::computeSequentialConditionalQuantile(const Point & q) const
520 {
521   if (q.getDimension() != dimension_) throw InvalidArgumentException(HERE) << "Error: cannot compute sequential conditional quantile with an argument of dimension=" << q.getDimension() << " different from distribution dimension=" << dimension_;
522   Point result(dimension_);
523   // Waiting for a better implementation
524   Point y(0);
525   for (UnsignedInteger i = 0; i < dimension_; ++i)
526   {
527     const Scalar qI = q[i];
528     result[i] = computeConditionalQuantile(qI, y);
529     y.add(result[i]);
530   }
531   return result;
532 }
533 
534 /* Get the i-th marginal distribution */
getMarginal(const UnsignedInteger i) const535 Distribution Student::getMarginal(const UnsignedInteger i) const
536 {
537   if (i >= getDimension()) throw InvalidArgumentException(HERE) << "The index of a marginal distribution must be in the range [0, dim-1]";
538   // Special case for dimension 1
539   if (getDimension() == 1) return clone();
540   // General case
541   const CorrelationMatrix R(1);
542   const Point sigma(1, sigma_[i]);
543   const Point mean(1, mean_[i]);
544   Student::Implementation marginal(new Student(nu_, mean, sigma, R));
545   marginal->setDescription(Description(1, getDescription()[i]));
546   return marginal;
547 }
548 
549 /* Get the distribution of the marginal distribution corresponding to indices dimensions */
getMarginal(const Indices & indices) const550 Distribution Student::getMarginal(const Indices & indices) const
551 {
552   const UnsignedInteger dimension = getDimension();
553   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";
554   // Special case for dimension 1
555   if (dimension == 1) return clone();
556   // General case
557   const UnsignedInteger outputDimension = indices.getSize();
558   CorrelationMatrix R(outputDimension);
559   Point sigma(outputDimension);
560   Point mean(outputDimension);
561   Description description(getDescription());
562   Description marginalDescription(outputDimension);
563   // Extract the correlation matrix, the marginal standard deviations and means
564   for (UnsignedInteger i = 0; i < outputDimension; ++i)
565   {
566     const UnsignedInteger index_i = indices[i];
567     sigma[i] = sigma_[index_i];
568     mean[i] = mean_[index_i];
569     for (UnsignedInteger j = 0; j <= i; ++j) R(i, j) = R_(index_i, indices[j]);
570     marginalDescription[i] = description[index_i];
571   }
572   Student::Implementation marginal(new Student(nu_, mean, sigma, R));
573   marginal->setDescription(marginalDescription);
574   return marginal;
575 } // getMarginal(Indices)
576 
577 /* Compute the radial distribution CDF */
computeRadialDistributionCDF(const Scalar radius,const Bool tail) const578 Scalar Student::computeRadialDistributionCDF(const Scalar radius,
579     const Bool tail) const
580 {
581   const Scalar r2 = radius * radius;
582   return DistFunc::pBeta(0.5 * getDimension(), 0.5 * nu_, r2 / (nu_ + r2), tail);
583 }
584 
585 /* Mu accessor */
setMu(const Scalar mu)586 void Student::setMu(const Scalar mu)
587 {
588   if (getDimension() == 1) mean_ = Point(1, mu);
589   computeRange();
590 }
591 
getMu() const592 Scalar Student::getMu() const
593 {
594   if (getDimension() == 1) return mean_[0];
595   throw InvalidArgumentException(HERE) << "Error: cannot call this method if dimension > 1.";
596 }
597 
598 /* Get the mean of the distribution */
getMean() const599 Point Student::getMean() const
600 {
601   if (!(nu_ > 1.0)) throw NotDefinedException(HERE) << "Student mean is defined only for nu > 1, here nu=" << nu_;
602   return EllipticalDistribution::getMean();
603 }
604 
605 /* Get the standard deviation of the distribution */
getStandardDeviation() const606 Point Student::getStandardDeviation() const
607 {
608   if (!(nu_ > 2.0)) throw NotDefinedException(HERE) << "Student standard deviation is defined only for nu > 2, here nu=" << nu_;
609   return EllipticalDistribution::getStandardDeviation();
610 }
611 
612 /* Get the skewness of the distribution */
getSkewness() const613 Point Student::getSkewness() const
614 {
615   if (!(nu_ > 3.0)) throw NotDefinedException(HERE) << "Student skewness is defined only for nu > 3, here nu=" << nu_;
616   return Point(getDimension(), 0.0);
617 }
618 
619 /* Get the kurtosis of the distribution */
getKurtosis() const620 Point Student::getKurtosis() const
621 {
622   if (!(nu_ > 4.0)) throw NotDefinedException(HERE) << "Student kurtosis is defined only for nu > 4, here nu=" << nu_;
623   return Point(getDimension(), 3.0 + 6.0 / (nu_ - 4.0));
624 }
625 
626 /* Get the covariance of the distribution */
getCovariance() const627 CovarianceMatrix Student::getCovariance() const
628 {
629   if (!(nu_ > 2.0)) throw NotDefinedException(HERE) << "Student covariance is defined only for nu > 2, here nu=" << nu_;
630   return EllipticalDistribution::getCovariance();
631 }
632 
633 /* Get the moments of the standardized distribution */
getStandardMoment(const UnsignedInteger n) const634 Point Student::getStandardMoment(const UnsignedInteger n) const
635 {
636   if (n >= nu_) throw NotDefinedException(HERE) << "Error: cannot compute a standard moment of order greater or equal to the number of degrees of freedom";
637   if (n % 2 == 1) return Point(1, 0.0);
638   Scalar moment = 1.0;
639   for (UnsignedInteger i = 0; i < n / 2; ++i) moment *= (nu_ * (2 * i + 1)) / (nu_ - 2 * (i + 1));
640   // Alternate expression, not very useful as the raw moments overflow the double precision for n approximately equal to 300 (if nu is large enough), and for these values the loop is equivalent to the analytic expression both in terms of speed and
641   // const Scalar moment(exp(0.5 * n * std::log(nu_) + SpecFunc::LogGamma(0.5 * (n + 1.0)) + SpecFunc::LogGamma(0.5 * (nu_ - n)) - SpecFunc::LogGamma(0.5 * nu_)) / sqrt(M_PI));
642   return Point(1, moment);
643 }
644 
645 /* Get the standard representative in the parametric family, associated with the standard moments */
getStandardRepresentative() const646 Distribution Student::getStandardRepresentative() const
647 {
648   return new Student(nu_);
649 }
650 
651 /* Parameters value and description accessor */
getParametersCollection() const652 Student::PointWithDescriptionCollection Student::getParametersCollection() const
653 {
654   // First, get the parameters of the underlying elliptical distribution, it means mu, sigma and R
655   PointWithDescriptionCollection parameters(EllipticalDistribution::getParametersCollection());
656   // We get a collection of PointWithDescription, we append the value of nu at the beginning of each PointWithDescription
657   const UnsignedInteger size = parameters.getSize();
658   for (UnsignedInteger i = 0; i < size; ++i)
659   {
660     const PointWithDescription ellipticalParameterI(parameters[i]);
661     const Description ellipticalDescriptionI(ellipticalParameterI.getDescription());
662     const UnsignedInteger ellipticalParameterIDimension = ellipticalParameterI.getDimension();
663     PointWithDescription parameterI(ellipticalParameterIDimension + 1);
664     Description descriptionI(ellipticalParameterIDimension + 1);
665     parameterI[0] = nu_;
666     descriptionI[0] = "nu";
667     for (UnsignedInteger j = 0; j < ellipticalParameterIDimension; ++j)
668     {
669       parameterI[j + 1] = ellipticalParameterI[j];
670       descriptionI[j + 1] = ellipticalDescriptionI[j];
671     }
672     parameterI.setDescription(descriptionI);
673     parameters[i] = parameterI;
674   }
675   return parameters;
676 }
677 
setParametersCollection(const PointCollection & parametersCollection)678 void Student::setParametersCollection(const PointCollection & parametersCollection)
679 {
680   const Scalar w = getWeight();
681   const UnsignedInteger size = parametersCollection.getSize();
682   const UnsignedInteger dimension = size > 1 ? size - 1 : size;
683   if (dimension == 1) *this = Student(parametersCollection[0][0], parametersCollection[0][1], parametersCollection[0][2]);
684   else
685   {
686     const Scalar nu = parametersCollection[0][0];
687     Point mean(dimension);
688     Point sigma(dimension);
689     CorrelationMatrix R(dimension);
690     for (UnsignedInteger i = 0; i < dimension; ++i)
691     {
692       mean[i] = parametersCollection[i][1];
693       sigma[i] = parametersCollection[i][2];
694     }
695     UnsignedInteger parameterIndex = 1;
696     for (UnsignedInteger i = 0; i < dimension; ++i)
697     {
698       for (UnsignedInteger j = 0; j < i; ++j)
699       {
700         R(i, j) = parametersCollection[size - 1][parameterIndex];
701         ++parameterIndex;
702       }
703     }
704     *this = Student(nu, mean, sigma, R);
705   }
706   setWeight(w);
707 }
708 
709 
710 /* Parameters value accessor */
getParameter() const711 Point Student::getParameter() const
712 {
713   Point point(1, nu_);
714   point.add(EllipticalDistribution::getParameter());
715   return point;
716 }
717 
setParameter(const Point & parameter)718 void Student::setParameter(const Point & parameter)
719 {
720   // N = 2*d+((d-1)*d)/2+1
721   const UnsignedInteger size = parameter.getSize();
722   Scalar dimReal = 0.5 * std::sqrt(1.0 + 8.0 * size) - 1.5;
723   if (dimReal != round(dimReal)) throw InvalidArgumentException(HERE) << "Error: invalid parameter number for Student";
724   const UnsignedInteger dimension = dimReal;
725   const Scalar nu = parameter[0];
726 
727   if (dimension == 1)
728   {
729     *this = Student(nu, parameter[1], parameter[2]);
730   }
731   else
732   {
733     Point mean(dimension);
734     Point sigma(dimension);
735     CorrelationMatrix R(dimension);
736     for (UnsignedInteger i = 0; i < dimension; ++ i)
737     {
738       mean[i] = parameter[2 * i + 1];
739       sigma[i] = parameter[2 * i + 2];
740     }
741     UnsignedInteger parameterIndex = 2 * dimension;
742     for (UnsignedInteger i = 0; i < dimension; ++ i)
743     {
744       for (UnsignedInteger j = 0; j < i; ++ j)
745       {
746         R(i, j) = parameter[parameterIndex];
747         ++ parameterIndex;
748       }
749     }
750     *this = Student(nu, mean, sigma, R);
751   }
752 }
753 
754 /* Parameters description accessor */
getParameterDescription() const755 Description Student::getParameterDescription() const
756 {
757   Description description(1, "nu");
758   description.add(EllipticalDistribution::getParameterDescription());
759   return description;
760 }
761 
762 /* Nu accessor */
setNu(const Scalar nu)763 void Student::setNu(const Scalar nu)
764 {
765   if (nu <= 2.0) LOGWARN(OSS() << "Warning! As nu <= 2, the covariance of the distribution will not be defined");
766   const UnsignedInteger dimension = getDimension();
767   nu_ = nu;
768   // Only set the covarianceScalingFactor if nu > 0, else its value is -1.0
769   if (nu > 2.0) covarianceScalingFactor_ = nu_ / (nu_ - 2.0);
770   studentNormalizationFactor_ = SpecFunc::LnGamma(0.5 * (nu + dimension)) - SpecFunc::LnGamma(0.5 * nu) - 0.5 * dimension * std::log(nu * M_PI);
771   computeRange();
772 }
773 
774 /* Tell if the distribution has independent copula */
hasIndependentCopula() const775 Bool Student::hasIndependentCopula() const
776 {
777   // A multivariate Student distribution never has an independent copula
778   return getDimension() == 1;
779 }
780 
781 /* Nu accessor */
getNu() const782 Scalar Student::getNu() const
783 {
784   return nu_;
785 }
786 
787 /* Quantile computation for dimension=1 */
computeScalarQuantile(const Scalar prob,const Bool tail) const788 Scalar Student::computeScalarQuantile(const Scalar prob,
789                                       const Bool tail) const
790 {
791   if (dimension_ != 1) throw InvalidDimensionException(HERE) << "Error: the method computeScalarQuantile is only defined for 1D distributions";
792   return mean_[0] + sigma_[0] * DistFunc::qStudent(nu_, prob, tail);
793 }
794 
795 /* Method save() stores the object through the StorageManager */
save(Advocate & adv) const796 void Student::save(Advocate & adv) const
797 {
798   EllipticalDistribution::save(adv);
799   adv.saveAttribute( "nu_", nu_ );
800   adv.saveAttribute( "studentNormalizationFactor_", studentNormalizationFactor_ );
801 }
802 
803 /* Method load() reloads the object from the StorageManager */
load(Advocate & adv)804 void Student::load(Advocate & adv)
805 {
806   EllipticalDistribution::load(adv);
807   adv.loadAttribute( "nu_", nu_ );
808   adv.loadAttribute( "studentNormalizationFactor_", studentNormalizationFactor_ );
809   computeRange();
810 }
811 
812 
813 
814 
815 END_NAMESPACE_OPENTURNS
816