//                                               -*- C++ -*-
/**
 *  @file  StrongMaximumTest.cxx
 *  @brief StrongMaxTest implements an algorithm to check if a given design point
 *
 *  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-07-16 15:59:45 +0200 (Mon, 16 Jul 2012)
 */
#include <cmath>
#include <cstdlib>

#include "StrongMaximumTest.hxx"
#include "Normal.hxx"
#include "IdentityMatrix.hxx"
#include "NearestPointChecker.hxx"
#include "DistributionImplementation.hxx"
#include "ComparisonOperatorImplementation.hxx"
#include "ResourceMap.hxx"
#include "PersistentObjectFactory.hxx"

BEGIN_NAMESPACE_OPENTURNS



CLASSNAMEINIT(StrongMaximumTest);

static Factory< StrongMaximumTest > RegisteredFactory("StrongMaximumTest");
typedef DistributionImplementation::Implementation                        Implementation;
typedef DistributionImplementation::InverseIsoProbabilisticTransformation InverseIsoProbabilisticTransformation;


/* For save/load mechanism*/
StrongMaximumTest::StrongMaximumTest()
  : PersistentObject(),
    event_(),
    standardSpaceDesignPoint_(0),
    nearDesignPointVerifyingEventPoints_(0, 0),
    nearDesignPointVerifyingEventValues_(0, 0),
    farDesignPointVerifyingEventPoints_(0, 0),
    farDesignPointVerifyingEventValues_(0, 0),
    nearDesignPointViolatingEventPoints_(0, 0),
    nearDesignPointViolatingEventValues_(0, 0),
    farDesignPointViolatingEventPoints_(0, 0),
    farDesignPointViolatingEventValues_(0, 0)
{
  // Nothing to do
}

/*
 * @class StrongMaximumTest
 * StrongMaximumTest allows to validate a design point
 */

/* Standard constructor */
StrongMaximumTest::StrongMaximumTest(const StandardEvent & event,
                                     const NumericalPoint & standardSpaceDesignPoint,
                                     const NumericalScalar importanceLevel,
                                     const NumericalScalar accuracyLevel,
                                     const NumericalScalar confidenceLevel)
  : PersistentObject(),
    event_(event),
    standardSpaceDesignPoint_(standardSpaceDesignPoint),
    nearDesignPointVerifyingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    nearDesignPointVerifyingEventValues_(0, 1),
    farDesignPointVerifyingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    farDesignPointVerifyingEventValues_(0, 1),
    nearDesignPointViolatingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    nearDesignPointViolatingEventValues_(0, 1),
    farDesignPointViolatingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    farDesignPointViolatingEventValues_(0, 1)
{
  setImportanceLevel(importanceLevel);
  setAccuracyLevel(accuracyLevel);
  setConfidenceLevel(confidenceLevel);
  initializeParametersGivenConfidenceLevel();
}

/* Standard constructor */
StrongMaximumTest::StrongMaximumTest(const StandardEvent & event,
                                     const NumericalPoint & standardSpaceDesignPoint,
                                     const NumericalScalar importanceLevel,
                                     const NumericalScalar accuracyLevel,
                                     const UnsignedLong pointNumber)
  : PersistentObject(),
    event_(event),
    standardSpaceDesignPoint_(standardSpaceDesignPoint),
    nearDesignPointVerifyingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    nearDesignPointVerifyingEventValues_(0, 1),
    farDesignPointVerifyingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    farDesignPointVerifyingEventValues_(0, 1),
    nearDesignPointViolatingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    nearDesignPointViolatingEventValues_(0, 1),
    farDesignPointViolatingEventPoints_(0, standardSpaceDesignPoint.getDimension()),
    farDesignPointViolatingEventValues_(0, 1)
{
  if (standardSpaceDesignPoint.norm() < ResourceMap::GetAsNumericalScalar( "StrongMaximumTest-Epsilon" )) throw InvalidArgumentException(HERE) << "Error: the given standard space design point is too close to the origin for the strong maximum test to work, norm=" << standardSpaceDesignPoint.norm();
  setImportanceLevel(importanceLevel);
  setAccuracyLevel(accuracyLevel);
  setPointNumber(pointNumber);
  initializeParametersGivenPointNumber();
}

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

/* standardSpaceDesignPoint accessor */
void StrongMaximumTest::setStandardSpaceDesignPoint(const NumericalPoint & standardSpaceDesignPoint)
{
  if(standardSpaceDesignPoint.norm() <= 0.0) throw InvalidRangeException(HERE) << "DesignPoint with norm <= 0.0";
  standardSpaceDesignPoint_ = standardSpaceDesignPoint;
}

/* standardSpaceDesignPoint accessor */
NumericalPoint StrongMaximumTest::getStandardSpaceDesignPoint() const
{
  return standardSpaceDesignPoint_;
}

/* deltaEpsilon accessor */
NumericalScalar StrongMaximumTest::getDeltaEpsilon() const
{
  return deltaEpsilon_;
}

/* Event accessor */
StandardEvent StrongMaximumTest::getEvent() const
{
  return event_;
}

/* ImportanceLevel  accessor */
NumericalScalar StrongMaximumTest::getImportanceLevel() const
{
  return importanceLevel_;
}

/* ImportanceLevel accessor */
void StrongMaximumTest::setImportanceLevel(const NumericalScalar importanceLevel)
{
  if((importanceLevel >= 1.0) or (importanceLevel <= 0.0)) throw InvalidRangeException(HERE) << "importanceLevel is not within 0.0 and 1.0";
  importanceLevel_ = importanceLevel ;
}

/* AccuracyLevel  accessor */
NumericalScalar StrongMaximumTest::getAccuracyLevel() const
{
  return accuracyLevel_;
}

/* AccuracyLevel accessor */
void StrongMaximumTest::setAccuracyLevel(const NumericalScalar accuracyLevel)
{
  if (accuracyLevel <= 1.0) throw InvalidRangeException(HERE) << "accuracyLevel is not > 1.0";
  accuracyLevel_ = accuracyLevel ;
}

/* ConfidenceLevel accessor */
NumericalScalar StrongMaximumTest::getConfidenceLevel() const
{
  return confidenceLevel_;
}

/* ConfidenceLevel accessor */
void StrongMaximumTest::setConfidenceLevel(const NumericalScalar confidenceLevel)
{
  if((confidenceLevel > 1.0) or (confidenceLevel < 0.0)) throw InvalidRangeException(HERE) << "confidenceLevel is not within 0.0 and 1.0";
  confidenceLevel_ = confidenceLevel ;
}

/* DesignPointVicinity accessor */
NumericalScalar StrongMaximumTest::getDesignPointVicinity() const
{
  return designPointVicinity_;
}

/* StrongMaxTestDesignPointVicinity accessor */
void  StrongMaximumTest::setDesignPointVicinity(const NumericalScalar designPointVicinity)
{
  if((designPointVicinity >= 1.0) or (designPointVicinity <= 0.0)) throw InvalidRangeException(HERE) << "designPointVicinity is not within 0.0 and 1.0";
  designPointVicinity_ =  designPointVicinity;
}

/* StrongMaxTestPointNumber  accessor */
UnsignedLong  StrongMaximumTest::getPointNumber() const
{
  return pointNumber_;
}

/* StrongMaxTestPointNumber accessor */
void StrongMaximumTest::setPointNumber(const UnsignedLong pointNumber)
{
  if(pointNumber == 0) throw InvalidRangeException(HERE) << "pointNumber is equal to 0";
  pointNumber_ = pointNumber ;
}

/* NearDesignPointVerifyingEventPoints accessor */
NumericalSample StrongMaximumTest::getNearDesignPointVerifyingEventPoints() const
{
  return nearDesignPointVerifyingEventPoints_;
}

/* NearDesignPointVerifyingEventValues accessor */
NumericalSample StrongMaximumTest::getNearDesignPointVerifyingEventValues() const
{
  return nearDesignPointVerifyingEventValues_;
}

/* FarDesignPointVerifyingEventPoints accessor */
NumericalSample StrongMaximumTest::getFarDesignPointVerifyingEventPoints() const
{
  return farDesignPointVerifyingEventPoints_;
}

/* FarDesignPointVerifyingEventValues accessor */
NumericalSample StrongMaximumTest::getFarDesignPointVerifyingEventValues() const
{
  return farDesignPointVerifyingEventValues_;
}

/* NearDesignPointViolatingEventPoints accessor */
NumericalSample StrongMaximumTest::getNearDesignPointViolatingEventPoints() const
{
  return nearDesignPointViolatingEventPoints_;
}

/* NearDesignPointViolatingEventValues accessor */
NumericalSample StrongMaximumTest::getNearDesignPointViolatingEventValues() const
{
  return nearDesignPointViolatingEventValues_;
}

/* FarDesignPointViolatingEventPoints accessor */
NumericalSample StrongMaximumTest::getFarDesignPointViolatingEventPoints() const
{
  return farDesignPointViolatingEventPoints_;
}

/* FarDesignPointViolatingEventValues accessor */
NumericalSample StrongMaximumTest::getFarDesignPointViolatingEventValues() const
{
  return farDesignPointViolatingEventValues_;
}


/* String converter */
String StrongMaximumTest::__repr__() const
{
  OSS oss;
  oss << "class=" << StrongMaximumTest::GetClassName()
      << " event=" << event_
      << " standardSpaceDesignPoint=" << standardSpaceDesignPoint_
      << " importanceLevel=" << importanceLevel_
      << " accuracyLevel=" << accuracyLevel_
      << " confidenceLevel=" << confidenceLevel_
      << " designPointVicinity=" << designPointVicinity_
      << " pointNumber=" << pointNumber_
      << " deltaEpsilon=" << deltaEpsilon_
      << " nearDesignPointVerifyingEventPoints=" << nearDesignPointVerifyingEventPoints_
      << " nearDesignPointVerifyingEventValues=" << nearDesignPointVerifyingEventValues_
      << " farDesignPointVerifyingEventPoints=" << farDesignPointVerifyingEventPoints_
      << " farDesignPointVerifyingEventValues=" << farDesignPointVerifyingEventValues_
      << " nearDesignPointViolatingEventPoints=" << nearDesignPointViolatingEventPoints_
      << " nearDesignPointViolatingEventValues=" << nearDesignPointViolatingEventValues_
      << " farDesignPointViolatingEventPoints=" << farDesignPointViolatingEventPoints_
      << " farDesignPointViolatingEventValues=" << farDesignPointViolatingEventValues_;
  return oss;
}

/* Method save() stores the object through the StorageManager */
void StrongMaximumTest::save(Advocate & adv) const
{
  PersistentObject::save(adv);
  adv.saveAttribute( "event_", event_ );
  adv.saveAttribute( "standardSpaceDesignPoint_", standardSpaceDesignPoint_ );
  adv.saveAttribute( "importanceLevel_", importanceLevel_ );
  adv.saveAttribute( "accuracyLevel_", accuracyLevel_ );
  adv.saveAttribute( "confidenceLevel_", confidenceLevel_ );
  adv.saveAttribute( "designPointVicinity_", designPointVicinity_ );
  adv.saveAttribute( "pointNumber_", pointNumber_ );
  adv.saveAttribute( "deltaEpsilon_", deltaEpsilon_ );
  adv.saveAttribute( "nearDesignPointVerifyingEventPoints_", nearDesignPointVerifyingEventPoints_ );
  adv.saveAttribute( "nearDesignPointVerifyingEventValues_", nearDesignPointVerifyingEventValues_ );
  adv.saveAttribute( "farDesignPointVerifyingEventPoints_", farDesignPointVerifyingEventPoints_ );
  adv.saveAttribute( "farDesignPointVerifyingEventValues_", farDesignPointVerifyingEventValues_ );
  adv.saveAttribute( "nearDesignPointViolatingEventPoints_", nearDesignPointViolatingEventPoints_ );
  adv.saveAttribute( "nearDesignPointViolatingEventValues_", nearDesignPointViolatingEventValues_ );
  adv.saveAttribute( "farDesignPointViolatingEventPoints_", farDesignPointViolatingEventPoints_ );
  adv.saveAttribute( "farDesignPointViolatingEventValues_", farDesignPointViolatingEventValues_ );
}

/* Method load() reloads the object from the StorageManager */
void StrongMaximumTest::load(Advocate & adv)
{
  PersistentObject::load(adv);
  adv.loadAttribute( "event_", event_ );
  adv.loadAttribute( "standardSpaceDesignPoint_", standardSpaceDesignPoint_ );
  adv.loadAttribute( "importanceLevel_", importanceLevel_ );
  adv.loadAttribute( "accuracyLevel_", accuracyLevel_ );
  adv.loadAttribute( "confidenceLevel_", confidenceLevel_ );
  adv.loadAttribute( "designPointVicinity_", designPointVicinity_ );
  adv.loadAttribute( "pointNumber_", pointNumber_ );
  adv.loadAttribute( "deltaEpsilon_", deltaEpsilon_ );
  adv.loadAttribute( "nearDesignPointVerifyingEventPoints_", nearDesignPointVerifyingEventPoints_ );
  adv.loadAttribute( "nearDesignPointVerifyingEventValues_", nearDesignPointVerifyingEventValues_ );
  adv.loadAttribute( "farDesignPointVerifyingEventPoints_", farDesignPointVerifyingEventPoints_ );
  adv.loadAttribute( "farDesignPointVerifyingEventValues_", farDesignPointVerifyingEventValues_ );
  adv.loadAttribute( "nearDesignPointViolatingEventPoints_", nearDesignPointViolatingEventPoints_ );
  adv.loadAttribute( "nearDesignPointViolatingEventValues_", nearDesignPointViolatingEventValues_ );
  adv.loadAttribute( "farDesignPointViolatingEventPoints_", farDesignPointViolatingEventPoints_ );
  adv.loadAttribute( "farDesignPointViolatingEventValues_", farDesignPointViolatingEventValues_ );
}

/* Initialize Strong Max Test Parameters : method 1 */
void StrongMaximumTest::initializeParametersGivenConfidenceLevel()
{
  /* evaluate the intermediate parameter delta_epsilon (see documentation) */
  deltaEpsilon_ = computeDeltaEpsilon();

  /* evaluate the HyperSphereSurfaceRatio (see documentation) */
  NumericalScalar p(computeHyperSphereSurfaceRatio());
  // put eps1 and eps2 instead of 1.0 and 0.0
  if((p >= 1.0) or (p <= 0.0)) throw InvalidRangeException(HERE) << "hyperSphereSurfaceRatio is not strictly within 0.0 and 1.0";

  /* evaluate and affect the pointNumber_ */
  setPointNumber(UnsignedLong(round(log1p(-confidenceLevel_) / log1p(-p))));


  /* evaluate and affect the designPointVicinity_ */
  setDesignPointVicinity(1.0 / (1.0 + accuracyLevel_ * deltaEpsilon_));
}

/* Initialize Strong Max Test Parameters : method 2 */
void StrongMaximumTest::initializeParametersGivenPointNumber()
{
  /* evaluate the intermediate parameter delta_epsilon (see documentation) */
  deltaEpsilon_ = computeDeltaEpsilon();

  /* evaluate the HyperSphereSurfaceRatio (see documentation) */
  NumericalScalar p(computeHyperSphereSurfaceRatio());

  // put eps1 and eps2 instead of 1.0 and 0.0
  if((p >= 1.0) or (p <= 0.0)) throw InvalidRangeException(HERE) << "hyperSphereSurfaceRatio is not strictly within 0.0 and 1.0";

  /* evaluate and affect the confidenceLevel */
  setConfidenceLevel(1.0 - pow(1.0 - p, pointNumber_));

  /* evaluate and affect the designPointVicinity_ */
  setDesignPointVicinity(1.0 / (1.0 + accuracyLevel_ * deltaEpsilon_));
}

/*  the function that evaluates the HyperSphereSurfaceRatio (see documentation) */
NumericalScalar StrongMaximumTest::computeHyperSphereSurfaceRatio()
{
  const UnsignedLong dimension(standardSpaceDesignPoint_.getDimension());
  const NumericalScalar a( acos((1.0 + deltaEpsilon_) / ( 1.0 + accuracyLevel_ * deltaEpsilon_) ) );
  const NumericalScalar sinA(sin(a));
  const NumericalScalar squareSinA(sinA * sinA);
  NumericalScalar sum(0.0);
  /* even dimension  */
  if (dimension % 2 == 0)
  {
    const UnsignedLong indexMax(dimension / 2 - 1);
    NumericalScalar u(sinA);
    for (UnsignedLong index = 0; index < indexMax; ++index)
    {
      sum += u;
      u *= (1.0 - 1.0 / (2.0 * index + 3.0)) * squareSinA;
    }
    /* M_1_PI = 1/PI cf cmath */
    return M_1_PI * (a - cos(a) * sum);
  }
  else
    /* odd dimension  */
  {
    const UnsignedLong indexMax((dimension - 1) / 2);
    NumericalScalar u(1.0);
    for (UnsignedLong index = 0; index < indexMax; ++index)
    {
      sum += u;
      u *= (1.0 - 1.0 / (2.0 * index + 2.0)) * squareSinA;
    }
    return 0.5 * (1.0 - cos(a) * sum);
  }
}

/*  the function that evaluates the  intermediate parameter delta_epsilon (see documentation) */
NumericalScalar StrongMaximumTest::computeDeltaEpsilon()
{
  /* evaluate the reliability index */
  const NumericalScalar betaSquare(standardSpaceDesignPoint_.norm2());

  /* get the input distribution in the standard space */
  const Implementation p_inputStandardDistribution(event_.getImplementation()->getAntecedent()->getDistribution().getImplementation());

  /* evaluate the generator at beta square */
  const NumericalScalar pdfMin(importanceLevel_ * p_inputStandardDistribution->computeDensityGenerator(betaSquare));

  /* research the interval [deltaMin deltaMax] including the solution */
  NumericalScalar deltaMax(1.0);

  while ( p_inputStandardDistribution->computeDensityGenerator(betaSquare * pow(1.0 + deltaMax, 2)) > pdfMin ) ++deltaMax;
  NumericalScalar deltaMin(deltaMax - 1.0);

  /* we proceed to the dichotomie on [deltaMin deltaMax] */
  NumericalScalar deltaMiddle(0.0);
  const NumericalScalar deltaEpsilon(ResourceMap::GetAsNumericalScalar( "StrongMaximumTest-DefaultDeltaPrecision" ));
  while ( (deltaMax - deltaMin) > deltaEpsilon )
  {
    /* we evaluate the middle of  [deltaMin deltaMax] */
    deltaMiddle = 0.5 * (deltaMax + deltaMin);
    if(  p_inputStandardDistribution->computeDensityGenerator(betaSquare * pow(1.0 + deltaMiddle, 2)) > pdfMin )
    {
      deltaMin = deltaMiddle;
    }
    else
    {
      deltaMax = deltaMiddle;
    }
  }
  return 0.5 * (deltaMax + deltaMin);
}

/* the function that evaluate if a point is in the vicinity of the design point */
Bool StrongMaximumTest::isInTheVicinityOfTheDesignPoint(const NumericalPoint & numericalPoint)
{
  return (dot(numericalPoint, standardSpaceDesignPoint_) > numericalPoint.norm() * standardSpaceDesignPoint_.norm() * designPointVicinity_);
}

/* The function that runs the Strong Max Test */
void StrongMaximumTest::run()
{
  /* prepare test parameters */

  /* radius of the inner sphere */
  const NumericalScalar beta(standardSpaceDesignPoint_.norm());
  /* radius of the sphere to be sampled */
  const NumericalScalar radius(beta * (1.0 + accuracyLevel_ * deltaEpsilon_));
  /* sample of the sphere */
  const NumericalSample sample(sampleSphere(radius, standardSpaceDesignPoint_.getDimension(), pointNumber_));
  /* create a nearestPointChecker, in charge of the evaluation of the level function over the sample and to classify the points according to the operator and the threshold */
  NearestPointChecker nearestPointChecker(event_.getImplementation()->getFunction(), event_.getOperator(), event_.getThreshold(), sample);
  /* access to the inverse isoprobabilistic transformation */
  InverseIsoProbabilisticTransformation inverseIsoProbabilisticTransformation(event_.getImplementation()->getAntecedent()->getDistribution().getInverseIsoProbabilisticTransformation());
  /* run test */
  try
  {
    nearestPointChecker.run();
  }
  catch(InvalidArgumentException & ex)
  {
    throw InvalidArgumentException(HERE) << ex;
  }
  catch(InternalException & ex)
  {
    throw  InternalException(HERE) << ex;
  }

  /* get nearestPointChecker result */
  NearestPointCheckerResult nearestPointCheckerResult(nearestPointChecker.getResult());
  /* split the two samples according to the vicinity of the design point
   * everything is done in place, using the attributs of the class in order
   * to limit the memory usage */

  nearDesignPointVerifyingEventPoints_ = nearestPointCheckerResult.getVerifyingConstraintPoints();
  nearDesignPointVerifyingEventValues_ = nearestPointCheckerResult.getVerifyingConstraintValues();

  UnsignedLong sampleSize(nearDesignPointVerifyingEventPoints_.getSize());
  /* If there is something to classify */
  if (sampleSize > 0)
  {
    UnsignedLong leftCounter(0);
    UnsignedLong rightCounter(sampleSize - 1);

    /* we sort among the nearDesignPointVerifyingEventPoints_ (ie which realise the event) the ones which are in the vicinity of the design point */
    while (leftCounter < rightCounter)
    {
      if (isInTheVicinityOfTheDesignPoint(nearDesignPointVerifyingEventPoints_[leftCounter]))
      {
        /* we leave at the beginning of the sample all the points (and the corresponding values) in the vicinity of the design point */
        ++leftCounter;
      }
      else
      {
        /* we put at the end of the sample  all the points (and the corresponding values) not in the vicinity of the design point */
        const NumericalPoint point(nearDesignPointVerifyingEventPoints_[leftCounter]);
        const NumericalPoint value(nearDesignPointVerifyingEventValues_[leftCounter]);
        nearDesignPointVerifyingEventPoints_[leftCounter] = nearDesignPointVerifyingEventPoints_[rightCounter];
        nearDesignPointVerifyingEventValues_[leftCounter] = nearDesignPointVerifyingEventValues_[rightCounter];
        nearDesignPointVerifyingEventPoints_[rightCounter] = point;
        nearDesignPointVerifyingEventValues_[rightCounter] = value;
        --rightCounter;
      }
    }
    /* At the end, we still have to check the point at the position leftCounter but without updating rightCounter, which might be already equals to 0 and then might try to become < 0 */
    if (isInTheVicinityOfTheDesignPoint(nearDesignPointVerifyingEventPoints_[leftCounter])) ++leftCounter;
    /* substitute physical points to standard points */
    nearDesignPointVerifyingEventPoints_ = inverseIsoProbabilisticTransformation(nearDesignPointVerifyingEventPoints_);

    /* we build the final two NumericalSamples (points, values) for each group */
    /* we split the sortedVerifyingConstraintPoints and the sortedVerifyingConstraintValues in 2 NumericalSamples each : one with the left side (points in the vicinity of the design point) and the other with the right side (points not in the vicinity of the design point) */
    if (leftCounter < sampleSize)
    {
      farDesignPointVerifyingEventPoints_ = nearDesignPointVerifyingEventPoints_.split(leftCounter);
      farDesignPointVerifyingEventValues_ = nearDesignPointVerifyingEventValues_.split(leftCounter);
    }
  }
  /* we do the same thing for points which violate the constraints (ie which don't realise the event) */
  farDesignPointViolatingEventPoints_ = nearestPointCheckerResult.getViolatingConstraintPoints();
  farDesignPointViolatingEventValues_ = nearestPointCheckerResult.getViolatingConstraintValues();

  sampleSize = farDesignPointViolatingEventPoints_.getSize();
  /* If there is something to classify */
  if (sampleSize > 0)
  {
    UnsignedLong leftCounter(0);
    UnsignedLong rightCounter(sampleSize - 1);

    /* we sort among the nearDesignPointViolatingEventPoints_ (ie which realise the event) the ones which are in the vicinity of the design point */
    while (leftCounter < rightCounter)
    {
      if (isInTheVicinityOfTheDesignPoint(farDesignPointViolatingEventPoints_[leftCounter]))
      {
        /* we put at the end of the sample  all the points (and the corresponding values) not in the vicinity of the design point */
        NumericalPoint point(farDesignPointViolatingEventPoints_[leftCounter]);
        NumericalPoint value(farDesignPointViolatingEventValues_[leftCounter]);
        farDesignPointViolatingEventPoints_[leftCounter] = farDesignPointViolatingEventPoints_[rightCounter];
        farDesignPointViolatingEventValues_[leftCounter] = farDesignPointViolatingEventValues_[rightCounter];
        farDesignPointViolatingEventPoints_[rightCounter] = point;
        farDesignPointViolatingEventValues_[rightCounter] = value;
        --rightCounter;
      }
      else
      {
        /* we leave at the beginning of the sample all the points (and the corresponding values) in the vicinity of the design point */
        ++leftCounter;
      }
    }
    /* At the end, we still have to check the point at the position leftCounter but without updating rightCounter, which should be already equals to 0 and then could try to become < 0 */
    if (!isInTheVicinityOfTheDesignPoint(farDesignPointViolatingEventPoints_[leftCounter])) ++leftCounter;
    /* substitute physical points to standard points */
    farDesignPointViolatingEventPoints_ = inverseIsoProbabilisticTransformation(farDesignPointViolatingEventPoints_);

    /* we build the final two NumericalSamples (points, values) for each group */
    /* we split the sortedViolatingConstraintPoints and the sortedViolatingConstraintValues in 2 NumericalSamples each : one with the left side (points in the vicinity of the design point) and the other with the right side (points not in the vicinity of the design point) */
    if (leftCounter < sampleSize)
    {
      nearDesignPointViolatingEventPoints_ = farDesignPointViolatingEventPoints_.split(leftCounter);
      nearDesignPointViolatingEventValues_ = farDesignPointViolatingEventValues_.split(leftCounter);
    }
  }
}

/* the function that samples the sphere (radius) with N points */
NumericalSample StrongMaximumTest::sampleSphere(const NumericalScalar radius,
    const UnsignedLong dimension,
    const UnsignedLong pointNumber) const
{
  // First, generate a sample of a standard normal distribution of the proper size and dimension
  const Normal standardNormal(dimension);
  NumericalSample sample(standardNormal.getSample(pointNumber));
  // Then, normalize the points to have length radius
  for (UnsignedLong i = 0; i < pointNumber; ++i)
  {
    NumericalScalar norm(static_cast<NumericalPoint>(sample[i]).norm());
    // If the point is the origin, we reject it
    while (norm == 0.0)
    {
      sample[i] = standardNormal.getRealization();
      norm = NumericalPoint(sample[i]).norm();
    }
    sample[i] *= (radius / norm);
  }
  // The normalize sample follow the uniform distribution over the hypersphere
  return sample;
}

END_NAMESPACE_OPENTURNS
