//                                               -*- C++ -*-
/**
 *  @file  OrderStatisticsMarginalChecker.cxx
 *  @brief OrderStatisticsMarginalChecker class
 *
 *  Copyright 2005-2015 Airbus-EDF-IMACS-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-02-17 19:35:43 +0100 (Fri, 17 Feb 2012)
 */
#include "OrderStatisticsMarginalChecker.hxx"
#include "TNC.hxx"
#include "MethodBoundNumericalMathEvaluationImplementation.hxx"
#include "CenteredFiniteDifferenceGradient.hxx"
#include "FiniteDifferenceStep.hxx"
#include "BlendedStep.hxx"
#include "SpecFunc.hxx"

BEGIN_NAMESPACE_OPENTURNS

CLASSNAMEINIT(OrderStatisticsMarginalChecker);

/* Parameters constructor */
OrderStatisticsMarginalChecker::OrderStatisticsMarginalChecker(const DistributionCollection & collection)
  : Object()
{
  for (UnsignedInteger i = 0; i < collection.getSize(); ++ i)
  {
    if (collection[i].getDimension() != 1) throw InvalidDimensionException(HERE) << "Marginal " << i << " should be 1-d.";
    if (!collection[i].isContinuous()) throw InvalidArgumentException(HERE) << "Marginal " << i << " should be continuous.";
  }
  collection_ = collection;
}

struct OrderStatisticsMarginalCheckerWrapper
{
  OrderStatisticsMarginalCheckerWrapper(const Distribution & distributionI, const Distribution & distributionIp1)
    : distributionI_(distributionI)
    , distributionIp1_(distributionIp1)
  {
    // Nothing to do
  }

  NumericalPoint computeDeltaSquare(const NumericalPoint & point) const
  {
    const NumericalScalar delta(distributionI_.computeCDF(point) - distributionIp1_.computeCDF(point));
    const NumericalPoint phik(1, delta);
    return phik;
  }

  const Distribution & distributionI_;
  const Distribution & distributionIp1_;
  UnsignedInteger i_;
}; // struct OrderStatisticsMarginalCheckerWrapper

void OrderStatisticsMarginalChecker::check() const
{
  UnsignedInteger quantileIteration(ResourceMap::GetAsUnsignedInteger("OrderStatisticsMarginalChecker-QuantileIteration"));
  NumericalScalar epsilon(ResourceMap::GetAsNumericalScalar("OrderStatisticsMarginalChecker-OptimizationEpsilon"));

  // Algorithm used to solve difficult situations
  TNC algo;
  const FiniteDifferenceStep step(BlendedStep(NumericalPoint(1, std::pow(SpecFunc::NumericalScalarEpsilon, 1.0 / 3.0)), std::sqrt(SpecFunc::NumericalScalarEpsilon)));
  for (UnsignedInteger i = 0; (i < collection_.getSize() - 1); ++ i)
  {
    // check that a_i <= a_i+1
    const NumericalScalar aI(collection_[i].getRange().getLowerBound()[0]);
    const NumericalScalar aIp1(collection_[i + 1].getRange().getLowerBound()[0]);
    if (aI <= aIp1)
    {
      // check that b_i <= b_i+1
      const NumericalScalar bI(collection_[i].getRange().getUpperBound()[0]);
      const NumericalScalar bIp1(collection_[i + 1].getRange().getUpperBound()[0]);
      if (bI <= bIp1)
      {
        // check when supports are overlapping, ie !(b_i <= a_i+1)
        if (bI > aI)
        {
          // check that x_i,k <= x_i+1,k for all k
          const OrderStatisticsMarginalCheckerWrapper wrapper(collection_[i], collection_[i + 1]);
          NumericalMathFunction f(bindMethod<OrderStatisticsMarginalCheckerWrapper, NumericalPoint, NumericalPoint>(wrapper, &OrderStatisticsMarginalCheckerWrapper::computeDeltaSquare, 1, 1));
          f.setGradient(CenteredFiniteDifferenceGradient(step, f.getEvaluation()->clone()));
          NumericalScalar qIp1Prev(aIp1);
          for (UnsignedInteger k = 0; k < quantileIteration; ++ k)
          {
            const NumericalScalar prob((k + 1.0) / (quantileIteration + 1.0));
            const NumericalScalar qI(collection_[i].computeQuantile(prob)[0]);
            const NumericalScalar qIp1(collection_[i + 1].computeQuantile(prob)[0]);
            // First quick check on the quantiles
            if (qI >= qIp1)
            {
              throw InvalidArgumentException(HERE) << "margins are not compatible: the quantile=" << qI << " of margin " << i << " is greater than the quantile=" << qIp1 << " of margin " << i + 1 << " at level " << prob;
            } // qI > qIp1
            // Second costly check by minimization
            NumericalScalar xMin(qI);
            NumericalScalar xMax(qIp1);
            if (k > 0) xMin = qIp1Prev;
            algo.setBoundConstraints(Interval(xMin, xMax));
            algo.setObjectiveFunction(f);
            algo.setStartingPoint(NumericalPoint(1, 0.5 * (xMin + xMax)));
            algo.run();
            const NumericalPoint minimizer(algo.getResult().getOptimizer());
            const NumericalScalar minValue(f(minimizer)[0]);
            LOGDEBUG(OSS() << "Optimisation on [" << xMin << ", " << xMax << "] gives " << algo.getResult());
            if (minValue < epsilon) throw InvalidArgumentException(HERE) << "margins are not compatible: the CDF at x=" << minimizer[0] << " of margin " << i << " is not enough larger than the CDF of margin " << i + 1 << ". Gap is " << minValue << ".";
            qIp1Prev = qIp1;
          } // k
        } // b_i > a_i+1
      } // b_i <= b_i+1
      else
      {
        throw InvalidArgumentException(HERE) << "margins are not compatible: the upper bound of margin " << i << " is greater than the upper bound of margin " << i + 1;
      }
    } // a_i <= a_i+1
    else
    {
      throw InvalidArgumentException(HERE) << "margins are not compatible: the lower bound of margin " << i << " is greater than the lower bound of margin " << i + 1;
    }
  } // i
}


Bool OrderStatisticsMarginalChecker::isCompatible() const
{
  Bool isCompatible = true;
  try
  {
    check();
  }
  catch (InvalidArgumentException & ex)
  {
    isCompatible = false;
  }
  return isCompatible;
}


Indices OrderStatisticsMarginalChecker::buildPartition() const
{
  Indices partition;
  for (UnsignedInteger i = 0; i < collection_.getSize() - 1; ++ i)
  {
    // check if b_i <= a_i+1
    // so i is in partition <=> (X_0,...,X_i) is independent from (X_{i+1},...,X_{d-1})
    if (collection_[i].getRange().getUpperBound()[0] <= collection_[i + 1].getRange().getLowerBound()[0]) partition.add(i);
  }
  return partition;
}

END_NAMESPACE_OPENTURNS
