//                                               -*- C++ -*-
/**
 *  @file  Multinomial.cxx
 *  @brief The Multinomial distribution
 *
 *  Copyright (C) 2005-2013 EDF-EADS-Phimeca
 *
 *  This library is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  along with this library.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  @author schueller
 *  @date   2012-04-18 17:56:46 +0200 (Wed, 18 Apr 2012)
 */
#include <cmath>
#include "Collection.hxx"
#include "Multinomial.hxx"
#include "Binomial.hxx"
#include "Poisson.hxx"
#include "TruncatedDistribution.hxx"
#include "RandomGenerator.hxx"
#include "SpecFunc.hxx"
#include "DistFunc.hxx"
#include "Exception.hxx"
#include "ResourceMap.hxx"
#include "PersistentObjectFactory.hxx"
#include "LinearEnumerateFunction.hxx"

BEGIN_NAMESPACE_OPENTURNS

typedef Collection<UnsignedLong>     UnsignedLongCollection;

CLASSNAMEINIT(Multinomial);

static Factory<Multinomial> RegisteredFactory("Multinomial");

/* Default constructor */
Multinomial::Multinomial()
  : DiscreteDistribution("Multinomial")
  , n_(0)
  , p_(0)
  , sumP_(0.0)
  , smallA_(ResourceMap::GetAsNumericalScalar("Multinomial-smallA"))
  , eta_(ResourceMap::GetAsNumericalScalar("Multinomial-eta"))
{
  // This method compute the range also
  setN(1);
  setP( NumericalPoint(1, 0.5) );
}

/* Parameters constructor */
Multinomial::Multinomial(const UnsignedLong n,
                         const NumericalPoint & p)
  : DiscreteDistribution("Multinomial")
  , n_(0)
  , p_(0)
  , sumP_(0.0)
  , smallA_(ResourceMap::GetAsNumericalScalar("Multinomial-smallA"))
  , eta_(ResourceMap::GetAsNumericalScalar("Multinomial-eta"))
{
  // This method compute the range also
  setN( n );
  setP( p );
  computeRange();
}

/* Comparison operator */
Bool Multinomial::operator ==(const Multinomial & other) const
{
  if (this == &other) return true;
  return (n_ == other.n_) && (p_ == other.p_);
}

/* String converter */
String Multinomial::__repr__() const
{
  OSS oss;
  oss << "class=" << Multinomial::GetClassName()
      << " name=" << getName()
      << " dimension=" << getDimension()
      << " p=" << p_
      << " n=" << n_;
  return oss;
}

String Multinomial::__str__(const String & offset) const
{
  OSS oss;
  oss << offset << getClassName() << "(n = " << n_ << ", p = " << p_.__str__() << ")";
  return oss;
}

/* Virtual constructor */
Multinomial * Multinomial::clone() const
{
  return new Multinomial(*this);
}

/* Compute the numerical range of the distribution given the parameters values */
void Multinomial::computeRange()
{
  const UnsignedLong dimension(getDimension());
  const NumericalPoint lowerBound(dimension, 0.0);
  const NumericalPoint upperBound(dimension, n_);
  const Interval::BoolCollection finiteLowerBound(dimension, true);
  const Interval::BoolCollection finiteUpperBound(dimension, true);
  setRange(Interval(lowerBound, upperBound, finiteLowerBound, finiteUpperBound));
}

/* Get one realization of the distribution */
NumericalPoint Multinomial::getRealization() const
{
  const UnsignedLong dimension(getDimension());
  NumericalPoint realization(dimension);
  /* We use an elementary algorithm based on the definition of the Multinomial distribution:
   * the i-th component is generated using a Binomial distribution */
  UnsignedLong n(n_);
  NumericalScalar sum(1.0);
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    /* The current component follow a binomial distribution with parameters p_[i] and N */
    const UnsignedLong xI(DistFunc::rBinomial(n, p_[i] / sum));
    realization[i] = xI;
    n -= xI;
    sum -= p_[i];
  }
  return realization;
}

/* Get the PDF of the distribution */
NumericalScalar Multinomial::computePDF(const NumericalPoint & point) const
{
  const UnsignedLong dimension(getDimension());
  if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();

  // First, check the validity of the input
  NumericalScalar sumX(0.0);
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    const NumericalScalar k(point[i]);
    // Early exit if the given point is not in the support of the distribution
    if ((fabs(k - round(k)) > supportEpsilon_) || (k < -supportEpsilon_) || (k > n_ + supportEpsilon_)) return 0.0;
    sumX += k;
  }
  if (sumX > n_ + supportEpsilon_) return 0.0;
  NumericalScalar logPDF(lgamma(n_ + 1.0) - lgamma(n_ - sumX + 1.0) + (n_ - sumX) * log1p(-sumP_));
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    const NumericalScalar k(point[i]);
    // For p_[i] > 0, it is possible to obtain any value of k between 0 and n
    if (p_[i] > 0.0) logPDF += k * log(p_[i]) - lgamma(k + 1.0);
    // Else only k == 0 is allowed, with a zero contribution to the log PDF
    else if (k > 0) return 0.0;
  }
  return exp(logPDF);
}

/* Compute the generating function of a sum of truncated Poisson distributions as needed in the computeCDF() method */
NumericalComplex Multinomial::computeGlobalPhi(const NumericalComplex & z,
    const NumericalPoint & x) const
{
  // Initialize with the non truncated term
  NumericalComplex value(exp(-(1.0 - sumP_) * n_ * (1.0 - z)));
  const UnsignedLong dimension(getDimension());
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    value *= computeLocalPhi(z, n_ * p_[i], x[i]);
    if (abs(value) == 0.0)
    {
      LOGWARN("Underflow in Multinomial::computePhi");
      return 0.0;
    }
  }
  return value;
}

/* Compute the generating function of a truncated Poisson distributions as needed in the computeCDF() method */
NumericalComplex Multinomial::computeLocalPhi(const NumericalComplex & z,
    const NumericalScalar lambda,
    const NumericalScalar a) const
{
  if (z == 0.0) return 1.0;
  const NumericalComplex u(lambda * z);
  const UnsignedLong iMax(static_cast< UnsignedLong > (floor(a)));
  // Small value of a, evaluate the generating function as a polynomial
  if (a <= smallA_)
  {
    NumericalComplex value(exp(-lambda));
    NumericalComplex term(value);
    for (UnsignedLong i = 1; i <= iMax; ++i)
    {
      term *= u / NumericalComplex(i);
      value += term;
    }
    return value;
  } // smallA_
  // Large a
  NumericalComplex value(exp(-lambda + u));
  UnsignedLong i(iMax + 1);
  NumericalComplex term(exp(-lambda + NumericalComplex(i) * log(u) - lgamma(i + 1.0)));
  while (abs(term) > SpecFunc::NumericalScalarEpsilon * abs(value))
  {
    value -= term;
    ++i;
    term *= u / NumericalComplex(i);
  }
  return value - term;
}

/* Get the CDF of the distribution
   Algorithm described in:
   R. Lebrun, "Efficient time/space algorithm to compute rectangular probabilities of multinomial, multivariate hypergeometric and multivariate Polya distributions", Statistics and Computing, submitted (2011).
*/
NumericalScalar Multinomial::computeCDF(const NumericalPoint & point) const
{
  const UnsignedLong dimension(getDimension());
  if (point.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given point must have dimension=" << dimension << ", here dimension=" << point.getDimension();

  // Early exit for 1D case
  if (dimension == 1)
  {
    const NumericalScalar k(point[0]);
    if (k < -supportEpsilon_) return 0.0;
    if (k > n_ + supportEpsilon_) return 1.0;
    return SpecFunc::BetaRatioInc(n_ - floor(k), floor(k) + 1, 1.0 - p_[0]);
  }
  // First, check the bording cases
  Indices indices(0);
  Bool allZero(true);
  NumericalScalar sumX(0.0);
  // Trivial cases
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    // If the given point does not cover any point of the support, return 0.0
    if (point[i] < -supportEpsilon_) return 0.0;
    if (point[i] < n_ - supportEpsilon_) indices.add(i);
    allZero = allZero && (fabs(point[i]) < supportEpsilon_);
    sumX += point[i];
  }
  // If we are at the origin, CDF=PDF(0,...,0)
  if (allZero) return pow(1.0 - sumP_, n_);
  // If the atoms with non zero probability sum to N
  if ((fabs(sumP_ - 1.0) < supportEpsilon_) && (sumX == n_))
  {
    NumericalScalar value(lgamma(n_ + 1));
    for (UnsignedLong j = 0; j < dimension; ++j)
      value += point[j] * log(p_[j]) - lgamma(point[j] + 1.0);
    return exp(value);
  }
  // If the point covers the whole support of the distribution, return 1.0
  const UnsignedLong size(indices.getSize());
  if (size == 0) return 1.0;
  // Now, check if there is a possible reduction of the dimension
  if (size < dimension)
  {
    // reduce the dimension to the active indices
    NumericalPoint pReduced(size);
    NumericalPoint xReduced(size);
    for (UnsignedLong i = 0; i < size; ++i)
    {
      pReduced[i] = p_[indices[i]];
      xReduced[i] = point[indices[i]];
    }
    return Multinomial(n_, pReduced).computeCDF(xReduced);
  }
  // Evaluation of P(W=n) using Poisson's formula
  NumericalComplex phiK(computeGlobalPhi(r_, point));
  const NumericalComplex zetaN(exp(NumericalComplex(0.0, M_PI / n_)));
  NumericalComplex phiKp1(computeGlobalPhi(r_ * zetaN, point));
  NumericalComplex delta(phiK - phiKp1);
  NumericalScalar value(delta.real());
  const NumericalScalar dv0(abs(delta));
  if (dv0 == 0.0)
  {
    LOGWARN("Underflow in Multinomial::computeCDF");
    return 0.0;
  }
  NumericalScalar sign(-1.0);
  NumericalComplex t(zetaN);
  for (UnsignedLong k = 1; k < n_; ++k)
  {
    phiK = phiKp1;
    t *= zetaN;
    phiKp1 = computeGlobalPhi(r_ * t, point);
    delta = phiK - phiKp1;
    value += sign * delta.real();
    const NumericalScalar dv(abs(delta));
    if (dv < SpecFunc::NumericalScalarEpsilon * dv0) break;
    sign = -sign;
  }
  // Due to round-off errors, the computed CDF can be slightly below 0 or over 1.
  return std::max(0.0, std::min(1.0, value * normalizationCDF_));
}

/* Compute the scalar quantile of the 1D multinomial distribution */
NumericalScalar Multinomial::computeScalarQuantile(const NumericalScalar prob,
    const Bool tail) const
{
  return Binomial(n_, p_[0]).computeQuantile(prob, tail)[0];
} // computeScalarQuantile

/* Compute the PDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1)
   For Multinomial distribution, the conditional distribution is Binomial
*/
NumericalScalar Multinomial::computeConditionalPDF(const NumericalScalar x,
    const NumericalPoint & y) const
{
  const UnsignedLong conditioningDimension(y.getDimension());
  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.";
  // Special case for no conditioning or independent copula
  if (conditioningDimension == 0) return Binomial(n_, p_[0]).computePDF(x);
  // General case
  // Check that y is a valid conditioning vector
  UnsignedLongCollection intY(conditioningDimension);
  NumericalScalar sumY(0.0);
  NumericalScalar sumP(0.0);
  for (UnsignedLong i = 0; i < conditioningDimension; ++i)
  {
    const NumericalScalar yI(y[i]);
    const UnsignedLong intYI(static_cast<UnsignedLong>(round(yI)));
    if (fabs(yI - intYI) > supportEpsilon_) throw InvalidArgumentException(HERE) << "Error: the conditioning vector has non-integer values";
    sumY += yI;
    intY[i] = intYI;
    sumP += p_[i];
  }
  if (sumY > n_) throw InvalidArgumentException(HERE) << "Error: the conditioning vector has a sum of components greater than the allowed range.";
  return Binomial(static_cast<UnsignedLong>(n_ - sumY), p_[conditioningDimension] / sumP).computePDF(x);
}

/* Compute the CDF of Xi | X1, ..., Xi-1. x = Xi, y = (X1,...,Xi-1) */
NumericalScalar Multinomial::computeConditionalCDF(const NumericalScalar x,
    const NumericalPoint & y) const
{
  const UnsignedLong conditioningDimension(y.getDimension());
  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.";
  // Special case for no conditioning or independent copula
  if (conditioningDimension == 0) return Binomial(n_, p_[0]).computeCDF(x);
  // General case
  // Check that y is a valid conditioning vector
  UnsignedLongCollection intY(conditioningDimension);
  NumericalScalar sumY(0.0);
  NumericalScalar sumP(0.0);
  for (UnsignedLong i = 0; i < conditioningDimension; ++i)
  {
    const NumericalScalar yI(y[i]);
    const UnsignedLong intYI(static_cast<UnsignedLong>(round(yI)));
    if (fabs(yI - intYI) > supportEpsilon_) throw InvalidArgumentException(HERE) << "Error: the conditioning vector has non-integer values";
    sumY += yI;
    intY[i] = intYI;
    sumP += p_[i];
  }
  if (sumY > n_) throw InvalidArgumentException(HERE) << "Error: the conditioning vector has a sum of components greater than the allowed range.";
  return Binomial(static_cast<UnsignedLong>(n_ - sumY), p_[conditioningDimension] / sumP).computeCDF(x);
}

/* Compute the quantile of Xi | X1, ..., Xi-1, i.e. x such that CDF(x|y) = q with x = Xi, y = (X1,...,Xi-1) */
NumericalScalar Multinomial::computeConditionalQuantile(const NumericalScalar q,
    const NumericalPoint & y) const
{
  const UnsignedLong conditioningDimension(y.getDimension());
  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.";
  if ((q < 0.0) || (q > 1.0)) throw InvalidArgumentException(HERE) << "Error: cannot compute a conditional quantile for a probability level outside of [0, 1]";
  // Special case when no contitioning or independent copula
  if (conditioningDimension == 0) return Binomial(n_, p_[0]).computeQuantile(q)[0];
  // General case
  // Check that y is a valid conditioning vector
  UnsignedLongCollection intY(conditioningDimension);
  NumericalScalar sumY(0.0);
  NumericalScalar sumP(0.0);
  for (UnsignedLong i = 0; i < conditioningDimension; ++i)
  {
    const NumericalScalar yI(y[i]);
    const UnsignedLong intYI(static_cast<UnsignedLong>(round(yI)));
    if (fabs(yI - intYI) > supportEpsilon_) throw InvalidArgumentException(HERE) << "Error: the conditioning vector has non-integer values";
    sumY += yI;
    intY[i] = intYI;
    sumP += p_[i];
  }
  if (sumY > n_) throw InvalidArgumentException(HERE) << "Error: the conditioning vector has a sum of components greater than the allowed range.";
  return Binomial(static_cast<UnsignedLong>(n_ - sumY), p_[conditioningDimension] / sumP).computeQuantile(q)[0];
}

/* Get the i-th marginal distribution */
Multinomial::Implementation Multinomial::getMarginal(const UnsignedLong i) const
{
  const UnsignedLong dimension(getDimension());
  if (i >= dimension) throw InvalidArgumentException(HERE) << "The index of a marginal distribution must be in the range [0, dim-1]";
  // Special case for dimension 1
  if (dimension == 1) return clone();
  // General case
  return new Binomial(n_, p_[i]);
}

/* Get the distribution of the marginal distribution corresponding to indices dimensions */
Multinomial::Implementation Multinomial::getMarginal(const Indices & indices) const
{
  const UnsignedLong dimension(getDimension());
  if (!indices.check(dimension - 1)) throw InvalidArgumentException(HERE) << "The indices of a marginal distribution must be in the range [0, dim-1] and  must be different";
  // Special case for dimension 1
  if (dimension == 1) return clone();
  // General case
  const UnsignedLong outputDimension(indices.getSize());
  NumericalPoint marginalP(outputDimension);
  // Extract the correlation matrix, the marginal standard deviations and means
  for (UnsignedLong i = 0; i < outputDimension; ++i)
  {
    const UnsignedLong index_i(indices[i]);
    marginalP[i] = p_[index_i];
  }
  return new Multinomial(n_, marginalP);
} // getMarginal(Indices)

/* Get the support of a discrete distribution that intersect a given interval */
NumericalSample Multinomial::getSupport(const Interval & interval) const
{
  const UnsignedLong dimension(getDimension());
  if (interval.getDimension() != dimension) throw InvalidArgumentException(HERE) << "Error: the given interval has a dimension that does not match the distribution dimension.";
  NumericalSample support(0, dimension);
  // Quick return if the lower bound of the interval is already outside of the support
  const NumericalPoint lowerBound(interval.getLowerBound());
  const NumericalPoint upperBound(interval.getUpperBound());
  NumericalScalar sumLower(0.0);
  NumericalScalar sumUpper(0.0);
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    // One of the components of the upper bound is negative, so the intersection with the positive quadrant is empty
    if (upperBound[i] < 0.0) return support;
    sumLower += lowerBound[i];
    sumUpper += upperBound[i];
  }
  if ((sumLower > n_) || (sumUpper < 0.0)) return support;
  // Here we know that all the components of the upper bound are positive or null
  const NumericalSample fullSupport(getSupport());
  // Quick return if the interval contains all the support
  // It cannot be possible if the sum of the components of the upper bound is less than n_
  if ((sumUpper >= n_) && (sumLower <= 0.0))
  {
    Bool allInside(true);
    for (UnsignedLong i = 0; i < dimension; ++i)
    {
      allInside = allInside && (lowerBound[i] <= 0.0);
      if (!allInside) break;
    }
    if (allInside) return fullSupport;
  }
  // We have to remove some points
  const UnsignedLong size(fullSupport.getSize());
  for (UnsignedLong i = 0; i < size; ++i)
  {
    const NumericalPoint point(fullSupport[i]);
    const Bool isInside(interval.contains(point));
    if (isInside) support.add(point);
  }
  return support;
}

/* Get the support of a discrete distribution that intersect a given interval */
NumericalSample Multinomial::getSupport() const
{
  const UnsignedLong dimension(getDimension());
  LinearEnumerateFunction enumerate(dimension);
  const UnsignedLong size(enumerate.getStrataCumulatedCardinal(n_));
  NumericalSample support(0, dimension);
  for (UnsignedLong i = 0; i < size; ++i)
    support.add(enumerate(i));
  return support;
}

/* Compute the mean of the distribution */
void Multinomial::computeMean() const
{
  mean_ = n_ * p_;
  isAlreadyComputedMean_ = true;
}

/* Compute the covariance of the distribution */
void Multinomial::computeCovariance() const
{
  const UnsignedLong dimension(getDimension());
  // To insure a zero initialization
  covariance_ = CovarianceMatrix(dimension);
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    const NumericalScalar pI(p_[i]);
    covariance_(i, i) = pI * (1.0 - pI) * n_;
    // Be careful! in these computations, n_ cannot be at the begining of the formula else -n_ will underflow the UnsignedLong range!
    for (UnsignedLong j = 0; j < i; ++j) covariance_(i, j) = -pI * p_[j] * n_;
  }
  isAlreadyComputedCovariance_ = true;
}

/* Parameters value and description accessor */
Multinomial::NumericalPointWithDescriptionCollection Multinomial::getParametersCollection() const
{
  const UnsignedLong dimension(getDimension());
  NumericalPointWithDescriptionCollection parameters((dimension == 1 ? 1 : dimension + 1));
  for (UnsignedLong i = 0; i < dimension; ++i)
  {
    NumericalPointWithDescription point(2);
    point[0] = n_;
    point[1] = p_[i];
    Description description(2);
    description[0] = "n";
    OSS oss;
    oss << "p_" << i;
    description[1] = oss;
    point.setDescription(description);
    point.setName(getDescription()[i]);
    parameters[i] = point;
  }
  if (dimension > 1)
  {
    NumericalPointWithDescription point(dimension + 1);
    Description description(dimension + 1);
    point[0] = n_;
    description[0] = "n";
    for (UnsignedLong i = 0; i < dimension; ++i)
    {
      point[i + 1] = p_[i];
      OSS oss;
      oss << "p_" << i;
      description[i + 1] = oss;
    }
    point.setDescription(description);
    point.setName("dependence");
    parameters[dimension] = point;
  }
  return parameters;
}

/* P accessor */
void Multinomial::setP(const NumericalPoint & p)
{
  // We check that the elements are all positive
  const UnsignedLong dimension(p.getDimension());
  if (dimension == 0) throw InvalidArgumentException(HERE) << "P must have a positive dimension.";
  NumericalScalar sum(0.0);
  for(UnsignedLong i = 0; i < dimension; ++i)
  {
    NumericalScalar pI(p[i]);
    if (pI < 0.0) throw InvalidArgumentException(HERE) << "P elements MUST be nonnegative";
    sum += pI;
  }
  p_ = p;
  sumP_ = sum;
  setDimension(dimension);
  isAlreadyComputedMean_ = false;
  isAlreadyComputedCovariance_ = false;
  isAlreadyCreatedGeneratingFunction_ = false;
}

/* P accessor */
NumericalPoint Multinomial::getP() const
{
  return p_;
}

/* N accessor */
void Multinomial::setN(const UnsignedLong n)
{
  if (n == 0) throw InvalidArgumentException(HERE) << "Error: n must be > 0.";
  if (n != n_)
  {
    n_ = n;
    // Best overall performance for Poisson's formula, see reference
    r_ = pow(eta_, 1.0 / (2.0 * n));
    normalizationCDF_ = exp(lgamma(n + 1.0) - n * log(n) + n - log(2.0 * n) - 0.5 * log(eta_));
    isAlreadyComputedMean_ = false;
    isAlreadyComputedCovariance_ = false;
    isAlreadyCreatedGeneratingFunction_ = false;
    computeRange();
  }
}

UnsignedLong Multinomial::getN() const
{
  return n_;
}

/* SmallA accessor */
void Multinomial::setSmallA(const NumericalScalar smallA)
{
  smallA_ = smallA;
}

NumericalScalar Multinomial::getSmallA() const
{
  return smallA_;
}

/* Eta accessor */
void Multinomial::setEta(const NumericalScalar eta)
{
  eta_ = eta;
}

NumericalScalar Multinomial::getEta() const
{
  return eta_;
}

/* Method save() stores the object through the StorageManager */
void Multinomial::save(Advocate & adv) const
{
  DiscreteDistribution::save(adv);
  adv.saveAttribute( "n_", n_ );
  adv.saveAttribute( "p_", p_ );
  adv.saveAttribute( "sumP_", sumP_ );
  adv.saveAttribute( "normalizationCDF_", normalizationCDF_ );
  adv.saveAttribute( "r_", r_ );
  adv.saveAttribute( "smallA_", smallA_ );
  adv.saveAttribute( "eta_", eta_ );
}

/* Method load() reloads the object from the StorageManager */
void Multinomial::load(Advocate & adv)
{
  DiscreteDistribution::load(adv);
  adv.loadAttribute( "n_", n_ );
  adv.loadAttribute( "p_", p_ );
  adv.loadAttribute( "sumP_", sumP_ );
  adv.loadAttribute( "normalizationCDF_", normalizationCDF_ );
  adv.loadAttribute( "r_", r_ );
  adv.loadAttribute( "smallA_", smallA_ );
  adv.loadAttribute( "eta_", eta_ );
  computeRange();
}

END_NAMESPACE_OPENTURNS
