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