1 //                                               -*- C++ -*-
2 /**
3  *  @brief A class that implements an independent copula
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/IndependentCopula.hxx"
23 #include "openturns/Normal.hxx"
24 #include "openturns/IdentityMatrix.hxx"
25 #include "openturns/NatafIndependentCopulaEvaluation.hxx"
26 #include "openturns/NatafIndependentCopulaGradient.hxx"
27 #include "openturns/NatafIndependentCopulaHessian.hxx"
28 #include "openturns/InverseNatafIndependentCopulaEvaluation.hxx"
29 #include "openturns/InverseNatafIndependentCopulaGradient.hxx"
30 #include "openturns/InverseNatafIndependentCopulaHessian.hxx"
31 #include "openturns/PersistentObjectFactory.hxx"
32 #include "openturns/RandomGenerator.hxx"
33 #include "openturns/SymbolicFunction.hxx"
34 #include "openturns/Distribution.hxx"
35 
36 BEGIN_NAMESPACE_OPENTURNS
37 
38 CLASSNAMEINIT(IndependentCopula)
39 
40 static const Factory<IndependentCopula> Factory_IndependentCopula;
41 
42 /* Default constructor */
IndependentCopula(const UnsignedInteger dimension)43 IndependentCopula::IndependentCopula(const UnsignedInteger dimension)
44   : DistributionImplementation()
45 {
46   isCopula_ = true;
47   setName( "IndependentCopula" );
48   setDimension(dimension);
49   computeRange();
50 }
51 
52 /* Comparison operator */
operator ==(const IndependentCopula & other) const53 Bool IndependentCopula::operator ==(const IndependentCopula & other) const
54 {
55   if (this == &other) return true;
56   return getDimension() == other.getDimension();
57 }
58 
equals(const DistributionImplementation & other) const59 Bool IndependentCopula::equals(const DistributionImplementation & other) const
60 {
61   const IndependentCopula* p_other = dynamic_cast<const IndependentCopula*>(&other);
62   return p_other && (*this == *p_other);
63 }
64 
65 /* String converter */
__repr__() const66 String IndependentCopula::__repr__() const
67 {
68   OSS oss(true);
69   oss << "class=" << IndependentCopula::GetClassName()
70       << " name=" << getName()
71       << " dimension=" << getDimension();
72   return oss;
73 }
74 
75 /* String converter */
__str__(const String &) const76 String IndependentCopula::__str__(const String & ) const
77 {
78   OSS oss(false);
79   oss << getClassName() << "(dimension = " << getDimension() << ")";
80   return oss;
81 }
82 
83 /* Virtual constructor */
clone() const84 IndependentCopula * IndependentCopula::clone() const
85 {
86   return new IndependentCopula(*this);
87 }
88 
89 /* Get one realization of the distribution */
getRealization() const90 Point IndependentCopula::getRealization() const
91 {
92   return RandomGenerator::Generate(getDimension());
93 }
94 
95 /* Get the DDF of the distribution */
computeDDF(const Point & point) const96 Point IndependentCopula::computeDDF(const Point & point) const
97 {
98   const UnsignedInteger dimension = getDimension();
99   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
100 
101   return Point(dimension, 0.0);
102 }
103 
104 /* Compute the probability content of an interval */
computeProbability(const Interval & interval) const105 Scalar IndependentCopula::computeProbability(const Interval & interval) const
106 {
107   const UnsignedInteger dimension = getDimension();
108   if (interval.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given interval must have dimension=" << dimension << ", here dimension=" << interval.getDimension();
109 
110   // Reduce the given interval to the support of the distribution, which is the nD unit cube
111   const Interval intersect(interval.intersect(Interval(dimension)));
112   // If the intersection is empty
113   if (intersect.isEmpty()) return 0.0;
114   const Point lower(intersect.getLowerBound());
115   const Point upper(intersect.getUpperBound());
116   Scalar value = 1.0;
117   for (UnsignedInteger i = 0; i < dimension; ++i)
118   {
119     value *= upper[i] - lower[i];
120   }
121   return value;
122 }
123 
124 /* Get the PDF of the distribution */
computePDF(const Point & point) const125 Scalar IndependentCopula::computePDF(const Point & point) const
126 {
127   const UnsignedInteger dimension = getDimension();
128   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
129 
130   for (UnsignedInteger i = 0; i < dimension; i++)
131   {
132     Scalar x = point[i];
133     // If one component is outside of the support, the PDF is null
134     if ((x <= 0.0) || (x >= 1.0)) return 0.0;
135   }
136   // The point is in the support
137   return 1.0;
138 }
139 
140 /* Get the CDF of the distribution */
computeCDF(const Point & point) const141 Scalar IndependentCopula::computeCDF(const Point & point) const
142 {
143   const UnsignedInteger dimension = getDimension();
144   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
145 
146   Scalar value = 1.0;
147   for (UnsignedInteger i = 0; i < dimension; i++)
148   {
149     Scalar x = point[i];
150     // If one component is at the left of the support of its matginal distribution, the CDF is null
151     if (x <= 0.0) return 0.0;
152     // If the component is inside of the support, multiply the value of the CDF by x
153     // FIXME
154     if (x < 1.0) value *= x;
155   }
156   return value;
157 }
158 
159 /* Get the survival function of the distribution */
computeSurvivalFunction(const Point & point) const160 Scalar IndependentCopula::computeSurvivalFunction(const Point & point) const
161 {
162   return computeCDF(Point(getDimension(), 1.0) - point);
163 }
164 
165 /* Get the Kendall concordance of the distribution */
getKendallTau() const166 CorrelationMatrix IndependentCopula::getKendallTau() const
167 {
168   return IdentityMatrix(getDimension());
169 }
170 
171 /* Get the PDF gradient of the distribution */
computePDFGradient(const Point & point) const172 Point IndependentCopula::computePDFGradient(const Point & point) const
173 {
174   const UnsignedInteger dimension = getDimension();
175   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
176 
177   return Point(0, 0.0);
178 }
179 
180 /* Get the CDF gradient of the distribution */
computeCDFGradient(const Point & point) const181 Point IndependentCopula::computeCDFGradient(const Point & point) const
182 {
183   const UnsignedInteger dimension = getDimension();
184   if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();
185 
186   return Point(0, 0.0);
187 }
188 
189 
190 /* Get the quantile of the distribution */
computeQuantile(const Scalar prob,const Bool tail,Scalar & marginalProb) const191 Point IndependentCopula::computeQuantile(const Scalar prob,
192     const Bool tail,
193     Scalar & marginalProb) const
194 {
195   if (!((prob >= 0.0) && (prob <= 1.0))) throw InvalidArgumentException(HERE) << "Error: cannot compute a quantile for a probability level outside of [0, 1]";
196   const Scalar q = tail ? 1.0 - prob : prob;
197   marginalProb = std::pow(q, 1.0 / dimension_);
198   if (q == 0.0) return Point(dimension_, 0.0);
199   if (q == 1.0) return Point(dimension_, 1.0);
200   return Point(dimension_, marginalProb);
201 }
202 
203 /* Compute the entropy of the distribution */
computeEntropy() const204 Scalar IndependentCopula::computeEntropy() const
205 {
206   return 0.0;
207 }
208 
209 /* Get the product minimum volume interval containing a given probability of the distribution */
computeMinimumVolumeIntervalWithMarginalProbability(const Scalar prob,Scalar & marginalProb) const210 Interval IndependentCopula::computeMinimumVolumeIntervalWithMarginalProbability(const Scalar prob, Scalar & marginalProb) const
211 {
212   return computeBilateralConfidenceIntervalWithMarginalProbability(prob, marginalProb);
213 }
214 
215 /* Get the product bilateral confidence interval containing a given probability of the distribution */
computeBilateralConfidenceIntervalWithMarginalProbability(const Scalar prob,Scalar & marginalProb) const216 Interval IndependentCopula::computeBilateralConfidenceIntervalWithMarginalProbability(const Scalar prob, Scalar & marginalProb) const
217 {
218   marginalProb = std::pow(prob, 1.0 / dimension_);
219   return Interval(Point(dimension_, 0.5 * (1.0 - marginalProb)), Point(dimension_, 0.5 * (1.0 + marginalProb)));
220 }
221 
222 /* Get the minimum volume level set containing a given probability of the distribution */
computeMinimumVolumeLevelSetWithThreshold(const Scalar prob,Scalar & threshold) const223 LevelSet IndependentCopula::computeMinimumVolumeLevelSetWithThreshold(const Scalar prob, Scalar & threshold) const
224 {
225   const Description inVars(Description::BuildDefault(dimension_, "x"));
226   OSS formula;
227   formula << "2*max(abs(" << inVars[0] << "-0.5";
228   for (UnsignedInteger i = 1; i < dimension_; ++i)
229     formula << "),abs(" << inVars[i] << "-0.5";
230   formula << "))";
231   threshold = std::pow(prob, 1.0 / dimension_);
232   return LevelSet(SymbolicFunction(inVars, Description(1, formula)), LessOrEqual(), threshold);
233 }
234 
235 /* Get the distribution of the marginal distribution corresponding to indices dimensions */
getMarginal(const Indices & indices) const236 Distribution IndependentCopula::getMarginal(const Indices & indices) const
237 {
238   UnsignedInteger dimension = getDimension();
239   if (!indices.check(dimension)) throw InvalidArgumentException(HERE) << "Error: the indices of a marginal distribution must be in the range [0, dim-1] and must be different";
240   // General case
241   return new IndependentCopula(indices.getSize());
242 }
243 
244 /* Compute the covariance of the distribution */
computeCovariance() const245 void IndependentCopula::computeCovariance() const
246 {
247   const UnsignedInteger dimension = getDimension();
248   covariance_ = CovarianceMatrix(dimension);
249   for (UnsignedInteger i = 0; i < dimension; i++)
250     covariance_(i, i) = 1.0 / 12.0;
251   isAlreadyComputedCovariance_ = true;
252 }
253 
254 /* Compute the DDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1) */
computeConditionalDDF(const Scalar,const Point &) const255 Scalar IndependentCopula::computeConditionalDDF(const Scalar, const Point & ) const
256 {
257   return 0.0;
258 }
259 
260 /* Compute the PDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1) */
computeConditionalPDF(const Scalar x,const Point & y) const261 Scalar IndependentCopula::computeConditionalPDF(const Scalar x, const Point & y) const
262 {
263   const UnsignedInteger conditioningDimension = y.getDimension();
264   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.";
265   if (x < 0.0) return 0.0;
266   if (x < 1.0) return 1.0;
267   return 0.0;
268 }
269 
270 /* Compute the CDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1) */
computeConditionalCDF(const Scalar x,const Point & y) const271 Scalar IndependentCopula::computeConditionalCDF(const Scalar x, const Point & y) const
272 {
273   const UnsignedInteger conditioningDimension = y.getDimension();
274   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.";
275   if (x < 0.0) return 0.0;
276   if (x < 1.0) return x;
277   return 1.0;
278 }
279 
280 /* 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) const281 Scalar IndependentCopula::computeConditionalQuantile(const Scalar q, const Point & y) const
282 {
283   const UnsignedInteger conditioningDimension = y.getDimension();
284   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.";
285   if ((q < 0.0) || (q > 1.0)) throw InvalidArgumentException(HERE) << "Error: cannot compute a conditional quantile for a probability level outside of [0, 1]";
286   return q;
287 }
288 
289 /* Get the isoprobabilist transformation */
getIsoProbabilisticTransformation() const290 IndependentCopula::IsoProbabilisticTransformation IndependentCopula::getIsoProbabilisticTransformation() const
291 {
292   IsoProbabilisticTransformation transformation;
293   transformation.setEvaluation(new NatafIndependentCopulaEvaluation(getDimension()));
294   transformation.setGradient(new NatafIndependentCopulaGradient(getDimension()));
295   transformation.setHessian(new NatafIndependentCopulaHessian(getDimension()));
296 
297   return transformation;
298 }
299 
300 /* Get the inverse isoprobabilistic transformation */
getInverseIsoProbabilisticTransformation() const301 IndependentCopula::InverseIsoProbabilisticTransformation IndependentCopula::getInverseIsoProbabilisticTransformation() const
302 {
303   InverseIsoProbabilisticTransformation transformation;
304   transformation.setEvaluation(new InverseNatafIndependentCopulaEvaluation(getDimension()));
305   transformation.setGradient(new InverseNatafIndependentCopulaGradient(getDimension()));
306   transformation.setHessian(new InverseNatafIndependentCopulaHessian(getDimension()));
307 
308   return transformation;
309 }
310 
311 /* Tell if the distribution is elliptical */
isElliptical() const312 Bool IndependentCopula::isElliptical() const
313 {
314   return dimension_ == 1;
315 }
316 
317 /* Tell if the distribution has elliptical copula */
hasEllipticalCopula() const318 Bool IndependentCopula::hasEllipticalCopula() const
319 {
320   return true;
321 }
322 
323 /* Tell if the distribution has independent copula */
hasIndependentCopula() const324 Bool IndependentCopula::hasIndependentCopula() const
325 {
326   return true;
327 }
328 
329 /* Parameters value and description accessor */
getParameter() const330 Point IndependentCopula::getParameter() const
331 {
332   return Point();
333 }
334 
getParameterDescription() const335 Description IndependentCopula::getParameterDescription() const
336 {
337   return Description();
338 }
339 
340 /* Method save() stores the object through the StorageManager */
save(Advocate & adv) const341 void IndependentCopula::save(Advocate & adv) const
342 {
343   DistributionImplementation::save(adv);
344 }
345 
346 /* Method load() reloads the object from the StorageManager */
load(Advocate & adv)347 void IndependentCopula::load(Advocate & adv)
348 {
349   DistributionImplementation::load(adv);
350   computeRange();
351 }
352 
353 END_NAMESPACE_OPENTURNS
354 
355