//---------------------------------------------------------------------------//
//!
//! \file   tstExponentialDistribution.cpp
//! \author Alex Robinson
//! \brief  Histogram distribution unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>
#include <cmath>

// Boost Includes
#include <boost/units/systems/si.hpp>
#include <boost/units/systems/cgs.hpp>
#include <boost/units/io.hpp>

// FRENSIE Includes
#include "Utility_ExponentialDistribution.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_UnitTraits.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "ArchiveTestHelpers.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//

using boost::units::quantity;
using namespace Utility::Units;
namespace si = boost::units::si;
namespace cgs = boost::units::cgs;

typedef TestArchiveHelper::TestArchives TestArchives;

typedef std::tuple<
  std::tuple<si::energy,si::amount,cgs::energy,si::amount>,
  std::tuple<cgs::energy,si::amount,si::energy,si::amount>,
  std::tuple<si::energy,si::length,cgs::energy,cgs::length>,
  std::tuple<cgs::energy,cgs::length,si::energy,si::length>,
  std::tuple<si::energy,si::mass,cgs::energy,cgs::mass>,
  std::tuple<cgs::energy,cgs::mass,si::energy,si::mass>,
  std::tuple<si::energy,si::dimensionless,cgs::energy,cgs::dimensionless>,
  std::tuple<cgs::energy,cgs::dimensionless,si::energy,si::dimensionless>,
  std::tuple<si::energy,void*,cgs::energy,void*>,
  std::tuple<cgs::energy,void*,si::energy,void*>,
  std::tuple<ElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<ElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<ElectronVolt,si::amount,KiloElectronVolt,si::amount>,
  std::tuple<ElectronVolt,si::amount,MegaElectronVolt,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,ElectronVolt,si::amount>,
  std::tuple<KiloElectronVolt,si::amount,MegaElectronVolt,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,si::energy,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,cgs::energy,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,ElectronVolt,si::amount>,
  std::tuple<MegaElectronVolt,si::amount,KiloElectronVolt,si::amount>,
  std::tuple<void*,MegaElectronVolt,void*,KiloElectronVolt>
 > TestUnitTypeQuads;

//---------------------------------------------------------------------------//
// Testing Variables
//---------------------------------------------------------------------------//

std::shared_ptr<Utility::UnivariateDistribution> distribution(
			     new Utility::ExponentialDistribution( 2.0, 3.0 ) );
std::shared_ptr<Utility::UnitAwareUnivariateDistribution<cgs::length,si::amount> > unit_aware_distribution( new Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 300.0/si::meter, 0.0*si::meter ) );

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( ExponentialDistribution, evaluate )
{
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), 2.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY(distribution->evaluate( 1.0 ), 2.0*exp(-3.0), 1e-12);
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, evaluate )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate(-1.0*cgs::centimeter),
		       0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*cgs::centimeter),
		       2.0*si::mole );
  FRENSIE_CHECK_FLOATING_EQUALITY(
		       unit_aware_distribution->evaluate( 1.0*cgs::centimeter),
		       2.0*exp(-3.0)*si::mole,
		       1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST( ExponentialDistribution, evaluatePDF )
{
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), 3.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY(distribution->evaluatePDF(1.0), 3.0*exp(-3.0), 1e-12);
}

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, evaluatePDF )
{
  FRENSIE_CHECK_EQUAL(
		  unit_aware_distribution->evaluatePDF( -1.0*cgs::centimeter ),
		  0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL(
		   unit_aware_distribution->evaluatePDF( 0.0*cgs::centimeter ),
		   3.0/cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY(
		   unit_aware_distribution->evaluatePDF( 1.0*cgs::centimeter ),
		   3.0*exp(-3.0)/cgs::centimeter,
		   1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( ExponentialDistribution, sample_basic_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = Utility::ExponentialDistribution::sample( 3.0 );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = Utility::ExponentialDistribution::sample( 3.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 11.5131919974469596, 1e-15 );

  sample = Utility::ExponentialDistribution::sample( 3.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -std::log(0.5)/3.0, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, sample_basic_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::length> sample =
    Utility::UnitAwareExponentialDistribution<cgs::length>::sample( 3.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = Utility::UnitAwareExponentialDistribution<cgs::length>::sample( 3.0/cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  11.5131919974469596*cgs::centimeter,
				  1e-15 );

  sample = Utility::UnitAwareExponentialDistribution<cgs::length>::sample( 3.0/cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -std::log(0.5)/3.0*cgs::centimeter,
				  1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( ExponentialDistribution, sample_static )
{
  std::vector<double> fake_stream( 5 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double inf = std::numeric_limits<double>::infinity();

  double sample = Utility::ExponentialDistribution::sample( 3.0, 0.0, inf );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = Utility::ExponentialDistribution::sample( 3.0, 0.0, inf );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 11.5131919974469596, 1e-15 );

  sample = Utility::ExponentialDistribution::sample( 3.0, 0.0, inf );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -std::log(0.5)/3.0, 1e-12 );

  sample = Utility::ExponentialDistribution::sample( 3.0, 1.0, inf );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.2310490601866484, 1e-12 );

  sample = Utility::ExponentialDistribution::sample( 3.0, 1.0, 2.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.2148532763287345, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, sample_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::length> inf =
    Utility::QuantityTraits<quantity<cgs::length> >::inf();

  quantity<cgs::length> sample =
    Utility::UnitAwareExponentialDistribution<cgs::length>::sample(
			       3.0/cgs::centimeter, 0.0*cgs::centimeter, inf );
  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = Utility::UnitAwareExponentialDistribution<cgs::length>::sample(
			       3.0/cgs::centimeter, 0.0*cgs::centimeter, inf );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  11.5131919974469596*cgs::centimeter,
				  1e-15 );

  sample = Utility::UnitAwareExponentialDistribution<cgs::length>::sample(
			       3.0/cgs::centimeter, 0.0*cgs::centimeter, inf );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -std::log(0.5)/3.0*cgs::centimeter,
				  1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( ExponentialDistribution, sample )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 11.5131919974469596, 1e-15 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -std::log(0.5)/3.0, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, sample )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::length> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  11.5131919974469596*cgs::centimeter,
				  1e-15 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -std::log(0.5)/3.0*cgs::centimeter,
				  1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( ExponentialDistribution, sampleAndRecordTrials )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  double sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 1 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 11.5131919974469596, 1e-15 );
  FRENSIE_CHECK_EQUAL( trials, 2 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -std::log(0.5)/3.0, 1e-12 );
  FRENSIE_CHECK_EQUAL( trials, 3 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, sampleAndRecordTrials )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;
  fake_stream[2] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<cgs::length> sample =
    unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 1 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  11.5131919974469596*cgs::centimeter,
				  1e-15 );
  FRENSIE_CHECK_EQUAL( trials, 2 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -std::log(0.5)/3.0*cgs::centimeter,
				  1e-12 );
  FRENSIE_CHECK_EQUAL( trials, 3 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( ExponentialDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(),
		       std::numeric_limits<double>::infinity() );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       Utility::QuantityTraits<quantity<cgs::length> >::inf());
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( ExponentialDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       Utility::QuantityTraits<quantity<cgs::length> >::zero());
}

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
FRENSIE_UNIT_TEST( ExponentialDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
		       Utility::EXPONENTIAL_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
		       Utility::EXPONENTIAL_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
FRENSIE_UNIT_TEST( ExponentialDistribution, isTabular )
{
  FRENSIE_CHECK( !distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, isTabular )
{
  FRENSIE_CHECK( !unit_aware_distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is continuous
FRENSIE_UNIT_TEST( ExponentialDistribution, isContinuous )
{
  FRENSIE_CHECK( distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is continuous
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, isContinuous )
{
  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST( ExponentialDistribution, isCompatibleWithInterpType )
{
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLog>() );

  // Create another distribution that is compatible with all interpolation
  // types
  Utility::ExponentialDistribution test_dist( 1.0, 1.0, 0.1, 1.0 );

  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is compatible with the interp type
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution,
                   isCompatibleWithInterpType )
{
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );

  // Create another distribution that is compatible with all interpolation
  // types
  Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>
    test_dist( 1.0*si::mole, 1.0/si::meter, 0.1*si::meter, 1.0*si::meter );

  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be placed in a stream
FRENSIE_UNIT_TEST( ExponentialDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::ExponentialDistribution();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::ExponentialDistribution( 2.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::ExponentialDistribution( 2.0, 3.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::ExponentialDistribution( 2.0, 3.0, 1.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::ExponentialDistribution( 2.0, 3.0, 1.0, 2.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 1.0 );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(), 2.0 );

  oss.str( "" );
  oss.clear();

  oss << *distribution;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void" );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toDouble(), 3.0 );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 2.0 );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf() );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be placed in a stream
FRENSIE_UNIT_TEST( UnitAwareExponentialDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toType<quantity<Utility::UnitTraits<cgs::length>::InverseUnit> >(),
                       1.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       1.0*si::mole );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toType<quantity<Utility::UnitTraits<cgs::length>::InverseUnit> >(),
                       1.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       2.0*si::mole );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 3.0/cgs::centimeter );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toType<quantity<Utility::UnitTraits<cgs::length>::InverseUnit> >(),
                       3.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       2.0*si::mole );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 3.0/cgs::centimeter, 1.0*cgs::centimeter );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toType<quantity<Utility::UnitTraits<cgs::length>::InverseUnit> >(),
                       3.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       2.0*si::mole );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       1.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf() );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 3.0/cgs::centimeter, 1.0*cgs::centimeter, 2.0*cgs::centimeter );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toType<quantity<Utility::UnitTraits<cgs::length>::InverseUnit> >(),
                       3.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       2.0*si::mole );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       1.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       2.0*cgs::centimeter );

  oss.str( "" );
  oss.clear();

  oss << *unit_aware_distribution;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Exponential Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name() );
  FRENSIE_CHECK_EQUAL( dist_data["exponent multiplier"].toType<quantity<Utility::UnitTraits<cgs::length>::InverseUnit> >(),
                       3.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       2.0*si::mole );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf() );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( ExponentialDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_exponential_dist" );
  std::ostringstream archive_ostream;

  // Create and archive some exponential distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::ExponentialDistribution dist_a;
    Utility::ExponentialDistribution dist_b( 2.0 );
    Utility::ExponentialDistribution dist_c( 2.0, 3.0 );
    Utility::ExponentialDistribution dist_d( 2.0, 3.0, 1.0 );
    Utility::ExponentialDistribution dist_e( 2.0, 3.0, 1.0, 2.0 );

    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e ) );
    FRENSIE_REQUIRE_NO_THROW(
                      (*oarchive) << BOOST_SERIALIZATION_NVP( distribution ) );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  Utility::ExponentialDistribution dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, Utility::ExponentialDistribution() );

  Utility::ExponentialDistribution dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, Utility::ExponentialDistribution( 2.0 ) );

  Utility::ExponentialDistribution dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c ) );
  FRENSIE_CHECK_EQUAL( dist_c, Utility::ExponentialDistribution( 2.0, 3.0 ) );
  FRENSIE_CHECK_EQUAL( dist_c.evaluatePDF( -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( dist_c.evaluatePDF( 0.0 ), 3.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY(dist_c.evaluatePDF(1.0), 3.0*exp(-3.0), 1e-12);

  Utility::ExponentialDistribution dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d ) );
  FRENSIE_CHECK_EQUAL( dist_d, Utility::ExponentialDistribution( 2.0, 3.0, 1.0 ) );

  Utility::ExponentialDistribution dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e ) );
  FRENSIE_CHECK_EQUAL( dist_e, Utility::ExponentialDistribution( 2.0, 3.0, 1.0, 2.0 ) );

  std::shared_ptr<Utility::UnivariateDistribution> shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( *dynamic_cast<Utility::ExponentialDistribution*>( shared_dist.get() ),
                       *dynamic_cast<Utility::ExponentialDistribution*>( distribution.get() ) );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareExponentialDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_unit_aware_exponential_dist" );
  std::ostringstream archive_ostream;

  // Create and archive some exponential distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_a;
    Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_b( 2.0*si::mole );
    Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_c( 2.0*si::mole, 3.0/cgs::centimeter );
    Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_d( 2.0*si::mole, 3.0/cgs::centimeter, 1.0*cgs::centimeter );
    Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_e( 2.0*si::mole, 3.0/cgs::centimeter, 1.0*cgs::centimeter, 2.0*cgs::centimeter );

    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e ) );
    FRENSIE_REQUIRE_NO_THROW(
           (*oarchive) << BOOST_SERIALIZATION_NVP( unit_aware_distribution ) );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, (Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>()) );

  Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, (Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole )) );

  Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c ) );
  FRENSIE_CHECK_EQUAL( dist_c, (Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 3.0/cgs::centimeter )) );

  Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d ) );
  FRENSIE_CHECK_EQUAL( dist_d, (Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 3.0/cgs::centimeter, 1.0*cgs::centimeter )) );

  Utility::UnitAwareExponentialDistribution<cgs::length,si::amount> dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e ) );
  FRENSIE_CHECK_EQUAL( dist_e, (Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>( 2.0*si::mole, 3.0/cgs::centimeter, 1.0*cgs::centimeter, 2.0*cgs::centimeter )) );

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<cgs::length,si::amount>> shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "unit_aware_distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( (*dynamic_cast<Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>*>( shared_dist.get() )),
                       (*dynamic_cast<Utility::UnitAwareExponentialDistribution<cgs::length,si::amount>*>( unit_aware_distribution.get() )) );
}

//---------------------------------------------------------------------------//
// Check that distributions can be scaled
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareExponentialDistribution,
				   explicit_conversion,
                                   TestUnitTypeQuads )
{
  FETCH_TEMPLATE_PARAM( 0, RawIndepUnitA );
  FETCH_TEMPLATE_PARAM( 1, RawDepUnitA );
  FETCH_TEMPLATE_PARAM( 2, RawIndepUnitB );
  FETCH_TEMPLATE_PARAM( 3, RawDepUnitB );

  typedef typename std::remove_pointer<RawIndepUnitA>::type IndepUnitA;
  typedef typename std::remove_pointer<RawDepUnitA>::type DepUnitA;
  typedef typename std::remove_pointer<RawIndepUnitB>::type IndepUnitB;
  typedef typename std::remove_pointer<RawDepUnitB>::type DepUnitB;

  typedef typename Utility::UnitTraits<IndepUnitA>::template GetQuantityType<double>::type IndepQuantityA;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitA>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityA;

  typedef typename Utility::UnitTraits<IndepUnitB>::template GetQuantityType<double>::type IndepQuantityB;
  typedef typename Utility::UnitTraits<typename Utility::UnitTraits<IndepUnitB>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityB;

  typedef typename Utility::UnitTraits<DepUnitA>::template GetQuantityType<double>::type DepQuantityA;
  typedef typename Utility::UnitTraits<DepUnitB>::template GetQuantityType<double>::type DepQuantityB;

  // Copy from unitless distribution to distribution type A (static method)
  Utility::UnitAwareExponentialDistribution<IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareExponentialDistribution<IndepUnitA,DepUnitA>::fromUnitlessDistribution( *dynamic_cast<Utility::ExponentialDistribution*>( distribution.get() ) );

  // Copy from distribution type A to distribution type B (explicit cast)
  Utility::UnitAwareExponentialDistribution<IndepUnitB,DepUnitB>
    unit_aware_dist_b_copy( unit_aware_dist_a_copy );

  IndepQuantityA indep_quantity_a =
    Utility::QuantityTraits<IndepQuantityA>::initializeQuantity( 0.0 );
  InverseIndepQuantityA inv_indep_quantity_a =
    Utility::QuantityTraits<InverseIndepQuantityA>::initializeQuantity( 3.0 );
  DepQuantityA dep_quantity_a =
    Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 2.0 );

  IndepQuantityB indep_quantity_b( indep_quantity_a );
  InverseIndepQuantityB inv_indep_quantity_b( inv_indep_quantity_a );
  DepQuantityB dep_quantity_b( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );

  Utility::setQuantity( indep_quantity_a, 1.0 );
  Utility::setQuantity( inv_indep_quantity_a, 3.0*exp(-3.0) );
  Utility::setQuantity( dep_quantity_a, 2.0*exp(-3.0) );

  indep_quantity_b = IndepQuantityB( indep_quantity_a );
  inv_indep_quantity_b = InverseIndepQuantityB( inv_indep_quantity_a );
  dep_quantity_b = DepQuantityB( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );
}

//---------------------------------------------------------------------------//
// Custom setup
//---------------------------------------------------------------------------//
FRENSIE_CUSTOM_UNIT_TEST_SETUP_BEGIN();

FRENSIE_CUSTOM_UNIT_TEST_INIT()
{
  // Initialize the random number generator
  Utility::RandomNumberGenerator::createStreams();
}

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstExponentialDistribution.cpp
//---------------------------------------------------------------------------//


// Standard libraries
#ifndef INCLUDE_STL
	#include <cstdlib>
	#include <iostream>
	#include <fstream>
	#include <sstream>
	#include <cmath>
	#include <string>
	#include <vector>
	#include <algorithm>
	#include <thread>
	#include <stdexcept>
	#define INCLUDE_STL
#endif

// External libraries
#ifndef INCLUDE_EIGEN
	#include <Eigen/Dense>
	#define INCLUDE_EIGEN
#endif

// My libraries
#ifndef INCLUDE_VOLUME
	#include "volume.hpp"
	#define INCLUDE_VOLUME
#endif

const unsigned int N = 3;

int main(int argc, char** argv) {
	Eigen::MatrixXd u = Eigen::MatrixXd::Random(N, N);
	std::cout << u << std::endl;

	Eigen::MatrixXd v = Eigen::MatrixXd::Random(N, N);
	// std::cout << v << std::endl;

	Eigen::MatrixXd p = Eigen::MatrixXd::Random(N, N);
	// std::cout << p << std::endl;

	Eigen::Matrix<Node*, N+2, N+2> volumes;

	// Inner volumes
	for (unsigned int i = 1; i < N+1; i++) {
		for (unsigned int j = 1; j < N+1; j++) {
			volumes(j, i) = new Volume(u(j-1, i-1), v(j-1, i-1), p(j-1, i-1));
		}
	}

	// Boundary volumes
	for (unsigned int i = 1; i < N+1; i++) {
		volumes(0, i) = new Boundary(volumes(N, i));
		volumes(i, 0) = new Boundary(volumes(i, N));
		volumes(N+1, i) = new Boundary(volumes(1, i));
		volumes(i, N+1) = new Boundary(volumes(i, 1));
	}

	// Corner volumes
	volumes(0, 0) = new Node();
	volumes(N+1, 0) = new Node();
	volumes(0, N+1) = new Node();
	volumes(N+1, N+1) = new Node();

	/* Test */

	for (unsigned int i = 0; i < N+2; i++) {
		for (unsigned int j = 0; j < N+2; j++) {
			std::cout << volumes(j, i)->get_u() << std::endl;
		}
		std::cout << std::endl;
	}

	std::cout << "SET U" << std::endl;
	volumes(1, 1)->set_u(100.0);

	for (unsigned int i = 0; i < N+2; i++) {
		for (unsigned int j = 0; j < N+2; j++) {
			std::cout << volumes(j, i)->get_u() << std::endl;
		}
		std::cout << std::endl;
	}

	return 0;
}


#pragma once

#include <Eigen/Dense>
#include <Eigen/Core>

#if USE_DOUBLE
    typedef double real;
    typedef Eigen::MatrixXd MatrixReal;
#else
    typedef float real;
    typedef Eigen::MatrixXf MatrixReal;
#endif
typedef Eigen::Matrix<real, Eigen::Dynamic, 3, Eigen::RowMajor> ObservationMatrix3;


#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/Surface_mesh/IO.h>
#include <CGAL/boost/graph/selection.h>

#include <boost/property_map/property_map.hpp>

#include <iostream>
#include <fstream>
#include <set>
#include <unordered_map>

typedef CGAL::Exact_predicates_inexact_constructions_kernel Kernel;
typedef CGAL::Surface_mesh<Kernel::Point_3> SM;
typedef boost::graph_traits<SM>::face_descriptor face_descriptor;

int main()
{
  SM sm;
  std::ifstream input(CGAL::data_file_path("meshes/head.off"));
  input >> sm;

// define my selection of faces to remove
  std::unordered_map<face_descriptor, bool> is_selected_map;

  const int selection_indices[30] = {652,18,328,698,322,212,808,353,706,869,646,352,788,696,714,796,937,2892,374,697,227,501,786,794,345,16,21,581,347,723};
  std::set<int> index_set(&selection_indices[0], &selection_indices[0]+30);

  std::vector<face_descriptor> faces_to_remove;
  int index = 0;
  for(face_descriptor fh : faces(sm))
  {
    if(index_set.count(index)==0)
      is_selected_map[fh]=false;
    else
    {
      faces_to_remove.push_back(fh);
      is_selected_map[fh]=true;
    }
    ++index;
  }

  CGAL::regularize_face_selection_borders (sm, boost::make_assoc_property_map(is_selected_map), 0.5);

  return 0;
}


// -----------------------------------------------------------------------------
// Fern © Geoneric
//
// This file is part of Geoneric Fern which is available under the terms of
// the GNU General Public License (GPL), version 2. If you do not want to
// be bound by the terms of the GPL, you may purchase a proprietary license
// from Geoneric (http://www.geoneric.eu/contact).
// -----------------------------------------------------------------------------
#define BOOST_TEST_MODULE fern algorithm algebra vector laplacian
#include <boost/test/unit_test.hpp>
#include "fern/core/data_customization_point/scalar.h"
#include "fern/core/type_traits.h"
#include "fern/core/types.h"
#include "fern/feature/core/data_customization_point/masked_raster.h"
#include "fern/algorithm/core/argument_customization_point/masked_raster.h"
#include "fern/algorithm/core/mask_customization_point/array.h"
#include "fern/algorithm/core/argument_traits/masked_raster.h"
#include "fern/algorithm/algebra/vector/laplacian.h"

namespace fa = fern::algorithm;

template<
    class Value,
    class Result>
using OutOfRangePolicy = fa::laplacian::OutOfRangePolicy<Value, Result>;

BOOST_AUTO_TEST_CASE(out_of_range_policy)
{
    {
        OutOfRangePolicy<fern::float32_t, fern::float32_t> policy;
        BOOST_CHECK(policy.within_range(123.456f, 4.5f));
        BOOST_CHECK(!policy.within_range(123.456f,
            fern::nan<fern::float32_t>()));
        BOOST_CHECK(!policy.within_range(123.456f,
            fern::infinity<fern::float32_t>()));
    }
}

template<
    class T>
using MaskedRaster = fern::MaskedRaster<T, 2>;

BOOST_AUTO_TEST_CASE(algorithm)
{
    // Create input raster:
    // +----+----+----+----+
    // |  0 |  1 |  2 |  3 |
    // +----+----+----+----+
    // |  4 |  5 |  6 |  7 |
    // +----+----+----+----+
    // |  8 |  9 | 10 | 11 |
    // +----+----+----+----+
    // | 12 | 13 | 14 | 15 |
    // +----+----+----+----+
    // | 16 | 17 | 18 | 19 |
    // +----+----+----+----+
    size_t const nr_rows = 5;
    size_t const nr_cols = 4;
    auto extents = fern::extents[nr_rows][nr_cols];

    double const cell_width = 2.0;
    double const cell_height = 3.0;
    double const west = 0.0;
    double const north = 0.0;

    MaskedRaster<double>::Transformation transformation{{west, cell_width,
        north, cell_height}};
    MaskedRaster<double> raster(extents, transformation);

    std::iota(raster.data(), raster.data() + raster.num_elements(), 0);

    fa::SequentialExecutionPolicy sequential;

    // Calculate laplacian.
    MaskedRaster<double> result(extents, transformation);

    // Without masking input and output values.
    {
        fa::algebra::laplacian(sequential, raster, result);

        // Verify the result.
        BOOST_CHECK_EQUAL(get(result, index(result, 0, 0)),
            (25.0 - (8.0 * 0.0)) / 6.0);
        BOOST_CHECK_EQUAL(get(result, index(result, 1, 1)),
            (100.0 - (20.0 * 5.0)) / 6.0);
    }

    using InputNoDataPolicy = fa::InputNoDataPolicies<
        fa::DetectNoDataByValue<fern::Mask<2>>>;
    using OutputNoDataPolicy = fa::MarkNoDataByValue<fern::Mask<2>>;

    // With masking input and output values.
    {
        result.fill(999.0);
        result.mask().fill(false);
        raster.mask()[1][1] = true;

        OutputNoDataPolicy output_no_data_policy(result.mask(), true);

        fa::algebra::laplacian<fa::laplacian::OutOfRangePolicy>(
            InputNoDataPolicy{{raster.mask(), true}},
            output_no_data_policy,
            sequential,
            raster, result);

        // Verify the result.
        BOOST_CHECK_EQUAL(get(result.mask(), index(result.mask(), 0, 0)),
            false);
        BOOST_CHECK_EQUAL(get(result.mask(), index(result.mask(), 1, 1)),
            true);
        BOOST_CHECK_EQUAL(get(result, index(result, 0, 0)),
            (15.0 - (6.0 * 0.0)) / 6.0);
        BOOST_CHECK_EQUAL(get(result, index(result, 1, 1)), 999.0);
    }
}


#ifndef NURSINGROBOT_STATESPACE_HPP
#define NURSINGROBOT_STATESPACE_HPP

#include <vector>
#include <array>
#include <Eigen/Dense>
#include <Eigen/Core>
#include <Eigen/StdVector>
#include "macros/class_forward.h"
#include <glog/logging.h>

namespace state_space {
    //exponential coordinate of se(3) s = [w,v]
    typedef Eigen::Matrix<double, 6, 1> R6;

    //lie algebra se(3) of the lie group SE(3)
    typedef Eigen::Matrix4d se_3;

    //lie group SE(3)
    typedef Eigen::Matrix4d SE_3;

    //exponential coordinate of so(3)
    typedef Eigen::Vector3d R3;

    //lie algebra so(3) of the lie group SO(3)
    typedef Eigen::Matrix3d so_3;

    //lie group SO(3)
    typedef Eigen::Matrix3d SO_3;

    //adjoint matrix 6X6 for Lie bracket
    typedef Eigen::Matrix<double, 6, 6> adjoint_mat;

    typedef Eigen::Matrix<double, 6, 6> jacobian_mat;

    /**\note: 2e-8 denotes 1e-6 degree precision*/
    static bool NearZero(const double val)
    {
        return (std::abs(val) < 1e-8);
    }

    /**
    * A state space represents the set of possible states for a planning problem.
    * for example Rn SO2 SO3 SE3 SE3
    * This class is abstract and must be subclassed in order to provide actual
    * functionality.
    */
    template<typename T>
    class StateSpace {
    public:
        StateSpace() = default;

        virtual ~StateSpace() = default;

        //
        virtual T operator+(const T &input) const = 0;

        virtual T operator-(const T &input) const = 0;

        virtual T operator*(double s) const = 0;

        virtual T operator()(double theta) const = 0;

        virtual bool operator==(const T &other) const = 0;

        virtual T inverse() const = 0;

        virtual T random(std::default_random_engine &randomEngine, const Eigen::MatrixX2d *bounds_ptr) const = 0;

        virtual double distance(const T &to) const = 0;

        virtual double norm() const = 0;

        virtual const double *data() const = 0;

        virtual unsigned int Dimensions() const = 0;
    };

}

#endif //NURSINGROBOT_STATESPACE_HPP


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.
// Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland.

// This file was modified by Oracle on 2016, 2017, 2018.
// Modifications copyright (c) 2016-2018, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_AREA_HPP
#define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_AREA_HPP

#include <boost/mpl/if.hpp>

//#include <boost/geometry/arithmetic/determinant.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/coordinate_type.hpp>
#include <boost/geometry/core/coordinate_dimension.hpp>
#include <boost/geometry/strategies/area.hpp>
#include <boost/geometry/util/select_most_precise.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace area
{

/*!
\brief Cartesian area calculation
\ingroup strategies
\details Calculates cartesian area using the trapezoidal rule
\tparam CalculationType \tparam_calculation

\qbk{
[heading See also]
[link geometry.reference.algorithms.area.area_2_with_strategy area (with strategy)]
}

*/
template
<
    typename CalculationType = void
>
class cartesian
{
public :
    template <typename Geometry>
    struct result_type
        : strategy::area::detail::result_type
            <
                Geometry,
                CalculationType
            >
    {};

    template <typename Geometry>
    class state
    {
        friend class cartesian;

        typedef typename result_type<Geometry>::type return_type;

    public:
        inline state()
            : sum(0)
        {
            // Strategy supports only 2D areas
            assert_dimension<Geometry, 2>();
        }

    private:
        inline return_type area() const
        {
            return_type const two = 2;
            return sum / two;
        }

        return_type sum;
    };

    template <typename PointOfSegment, typename Geometry>
    static inline void apply(PointOfSegment const& p1,
                             PointOfSegment const& p2,
                             state<Geometry>& st)
    {
        typedef typename state<Geometry>::return_type return_type;

        // Below formulas are equivalent, however the two lower ones
        // suffer less from accuracy loss for great values of coordinates.
        // See: https://svn.boost.org/trac/boost/ticket/11928

        // SUM += x2 * y1 - x1 * y2;
        // state.sum += detail::determinant<return_type>(p2, p1);

        // SUM += (x2 - x1) * (y2 + y1)
        //state.sum += (return_type(get<0>(p2)) - return_type(get<0>(p1)))
        //           * (return_type(get<1>(p2)) + return_type(get<1>(p1)));

        // SUM += (x1 + x2) * (y1 - y2)
        st.sum += (return_type(get<0>(p1)) + return_type(get<0>(p2)))
                * (return_type(get<1>(p1)) - return_type(get<1>(p2)));
    }

    template <typename Geometry>
    static inline typename result_type<Geometry>::type
        result(state<Geometry>& st)
    {
        return st.area();
    }

};

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

namespace services
{
    template <>
    struct default_strategy<cartesian_tag>
    {
        typedef strategy::area::cartesian<> type;
    };

} // namespace services

#endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

}} // namespace strategy::area

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_AREA_HPP


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_EULER_FUNCTIONS_SCALAR_ERF_HPP_INCLUDED
#define NT2_EULER_FUNCTIONS_SCALAR_ERF_HPP_INCLUDED

#include <nt2/euler/functions/erf.hpp>
#include <nt2/euler/functions/details/erf_kernel.hpp>
#include <nt2/include/constants/one.hpp>
#include <nt2/include/constants/pi.hpp>
#include <nt2/include/constants/two.hpp>
#include <nt2/include/constants/twothird.hpp>
#include <nt2/include/constants/zero.hpp>
#include <nt2/include/functions/scalar/abs.hpp>
#include <nt2/include/functions/scalar/exp.hpp>
#include <nt2/include/functions/scalar/is_ltz.hpp>
#include <nt2/include/functions/scalar/negif.hpp>
#include <nt2/include/functions/scalar/oneminus.hpp>
#include <nt2/include/functions/scalar/oneplus.hpp>
#include <nt2/include/functions/scalar/sqr.hpp>
#include <nt2/include/functions/scalar/sqrt.hpp>
#include <nt2/sdk/meta/scalar_of.hpp>

#include <boost/simd/sdk/config.hpp>

#ifndef BOOST_SIMD_NO_INVALIDS
#include <nt2/include/functions/scalar/is_nan.hpp>
#endif

#ifndef BOOST_SIMD_NO_INFINITIES
#include <nt2/include/functions/scalar/is_inf.hpp>
#include <nt2/include/functions/scalar/signnz.hpp>
#endif

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( erf_, tag::cpu_
                            , (A0)
                            , (scalar_< double_<A0> >)
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL(1)
    {
      #ifndef BOOST_SIMD_NO_INVALIDS
      if(is_nan(a0)) return a0;
      #endif
      A0 x =  nt2::abs(a0);
      A0 xx = nt2::sqr(x);
      if(x<= A0(0.0000000001))
        return a0*nt2::Two<A0>()/nt2::sqrt(Pi<A0>());
      else if (x<= A0(0.65))
      {
        return a0*details::erf_kernel<A0>::erf1(xx);
      }
      else if(x<= A0(2.2))
      {
        A0 z = oneminus(exp(-xx)*details::erf_kernel<A0>::erfc2(x));
        return negif(is_ltz(a0), z);
      }
      else if(x<= A0(6))
      {
        A0 z = nt2::oneminus(exp(-xx)*details::erf_kernel<A0>::erfc3(x));
        return nt2::negif(nt2::is_ltz(a0), z);
      }
      else
        return nt2::One<A0>();
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( erf_, tag::cpu_
                            , (A0)
                            , (scalar_< single_<A0> >)
                            )
  {
    typedef A0 result_type;
    NT2_FUNCTOR_CALL(1)
    {
      #ifndef BOOST_SIMD_NO_INVALIDS
      if(is_nan(a0)) return a0;
      #endif
      #ifndef BOOST_SIMD_NO_INFINITIES
      if (nt2::is_inf(a0)) return signnz(a0);
      #endif
      A0 x =  nt2::abs(a0);
      if (x < Twothird<A0>())
      {
        return a0*details::erf_kernel<A0>::erf1(sqr(x));
      }
      else
      {
       A0 z = x/oneplus(x)-A0(0.4);
        A0 r2 =   oneminus(exp(-sqr(x))*details::erf_kernel<A0>::erfc2(z));
        if (a0 < Zero<A0>()) r2 = -r2;
        return r2;
      }

    }
  };
} }

#endif


#pragma once

#include <boost/units/quantity.hpp>
#include <boost/units/unit.hpp>

#include <boost/units/base_units/si/kilogram.hpp>
#include <boost/units/base_units/si/meter.hpp>
#include <boost/units/base_units/si/second.hpp>
#include <boost/units/physical_dimensions/length.hpp>
#include <boost/units/physical_dimensions/mass.hpp>

#include <boost/units/systems/si/acceleration.hpp>
#include <boost/units/systems/si/force.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/systems/si/mass.hpp>
#include <boost/units/systems/si/momentum.hpp>
#include <boost/units/systems/si/time.hpp>
#include <boost/units/systems/si/velocity.hpp>

namespace bu = boost::units;
namespace si = bu::si;

class IEngine
{
protected:
    explicit IEngine() = default;

public:
    virtual ~IEngine() = default;

    using Length_t = bu::quantity<si::length>;
    using Mass_t = bu::quantity<si::mass>;
    using Time_t = bu::quantity<si::time>;
    using Acceleration_t = bu::quantity<si::acceleration>;
    using Speed_t = bu::quantity<si::velocity>;
    using Momentum_t = bu::quantity<si::momentum>;
    using Force_t = bu::quantity<si::force>;

    virtual void reset(Length_t new_state) = 0;

    virtual std::pair<Length_t, Speed_t> update(const Time_t dt) = 0;

    void setThrottle(const double t);

protected:
    Mass_t currentMass() const;

    Force_t currentThrust() const;

    Force_t gravity(const Length_t& height) const;

    Force_t totalForce(const Length_t& height) const;

    static const inline Mass_t dry_mass = 2792 * si::kilograms;
    static const inline Mass_t total_mass = 16437 * si::kilograms;
    double m_throttle;
};


#include "../sigmaextended_prover.h"
#include "../sigmaextended_verifier.h"

#include "lelantus_test_fixture.h"

#include <boost/test/unit_test.hpp>

namespace lelantus {

class SigmaExtendedTests : public LelantusTestingSetup {
public:
    struct Secret {
    public:
        Secret(std::size_t l) : l(l) {
            s.randomize();
            v.randomize();
            r.randomize();
        }

    public:
        std::size_t l;
        Scalar s, v, r;
    };

public:
    typedef SigmaExtendedProver Prover;
    typedef SigmaExtendedProof Proof;
    typedef SigmaExtendedVerifier Verifier;

public:
    SigmaExtendedTests() {}

public:
    void GenerateParams(std::size_t _N, std::size_t _n, std::size_t _m = 0) {
        N = _N;
        n = _n;
        m = _m;
        if (!m) {
            if (n <= 1) {
                throw std::logic_error("Try to get value of m from invalid n");
            }

            m = (std::size_t)std::round(log(N) / log(n));
        }

        h_gens = RandomizeGroupElements(n * m);
        g.randomize();
    }

    void GenerateBatchProof(
        Prover &prover,
        std::vector<GroupElement> const &coins,
        std::size_t l,
        Scalar const &s,
        Scalar const &v,
        Scalar const &r,
        Scalar const &x,
        Proof &proof
    ) {
        auto gs = g * s.negate();
        std::vector<GroupElement> commits(coins.begin(), coins.end());
        for (auto &c : commits) {
            c += gs;
        }

        Scalar rA, rB, rC, rD;
        rA.randomize();
        rB.randomize();
        rC.randomize();
        rD.randomize();

        std::vector<Scalar> sigma;
        std::vector<Scalar> Tk, Pk, Yk;
        Tk.resize(m);
        Pk.resize(m);
        Yk.resize(m);

        std::vector<Scalar> a;
        a.resize(n * m);

        prover.sigma_commit(
            commits, l, rA, rB, rC, rD, a, Tk, Pk, Yk, sigma, proof);

        prover.sigma_response(
            sigma, a, rA, rB, rC, rD, v, r, Tk, Pk, x, proof);
    }

public:
    std::size_t N;
    std::size_t n;
    std::size_t m;

    std::vector<GroupElement> h_gens;
    GroupElement g;
};

BOOST_FIXTURE_TEST_SUITE(lelantus_sigma_tests, SigmaExtendedTests)

BOOST_AUTO_TEST_CASE(one_out_of_N_variable_batch)
{
    GenerateParams(64, 4);

    std::size_t commit_size = 60; // require padding
    auto commits = RandomizeGroupElements(commit_size);

    // Generate
    std::vector<Secret> secrets;
    std::vector<std::size_t> indexes = { 0, 1, 3, 59 };
    std::vector<std::size_t> set_sizes = { 60, 60, 59, 16 };

    for (auto index : indexes) {
        secrets.emplace_back(index);

        auto &s = secrets.back();

        commits[index] = Primitives::double_commit(
            g, s.s, h_gens[1], s.v, h_gens[0], s.r
        );
    }

    Prover prover(g, h_gens, n, m);
    Verifier verifier(g, h_gens, n, m);
    std::vector<Proof> proofs;
    std::vector<Scalar> serials;
    std::vector<Scalar> challenges;

    for (std::size_t i = 0; i < indexes.size(); i++) {
        Scalar x;
        x.randomize();
        proofs.emplace_back();
        serials.push_back(secrets[i].s);
        std::vector<GroupElement> commits_(commits.begin() + commit_size - set_sizes[i], commits.end());
        GenerateBatchProof(
            prover,
            commits_,
            secrets[i].l - (commit_size - set_sizes[i]),
            secrets[i].s,
            secrets[i].v,
            secrets[i].r,
            x,
            proofs.back()
        );
        challenges.emplace_back(x);

        // Verify individual proofs as a sanity check
        BOOST_CHECK(verifier.singleverify(commits, x, secrets[i].s, set_sizes[i], proofs.back()));
        BOOST_CHECK(verifier.singleverify(commits_, x, secrets[i].s, proofs.back()));
    }

    BOOST_CHECK(verifier.batchverify(commits, challenges, serials, set_sizes, proofs));
}

BOOST_AUTO_TEST_CASE(one_out_of_N_batch)
{
    GenerateParams(16, 4);

    auto commits = RandomizeGroupElements(N);

    // Generate
    std::vector<Secret> secrets;

    for (auto index : {1, 3, 5, 9, 15}) {
        secrets.emplace_back(index);

        auto &s = secrets.back();

        commits[index] = Primitives::double_commit(
            g, s.s, h_gens[1], s.v, h_gens[0], s.r);
    }

    Prover prover(g, h_gens, n, m);
    std::vector<Proof> proofs;
    std::vector<Scalar> serials;

    Scalar x;
    x.randomize();

    for (auto const &s : secrets) {
        proofs.emplace_back();
        serials.push_back(s.s);
        GenerateBatchProof(
            prover, commits, s.l, s.s, s.v, s.r, x, proofs.back());
    }

    Verifier verifier(g, h_gens, n, m);
    BOOST_CHECK(verifier.batchverify(commits, x, serials, proofs));

    // verify subset of valid proofs should success also
    serials.pop_back();
    proofs.pop_back();
    BOOST_CHECK(verifier.batchverify(commits, x, serials, proofs));
}

BOOST_AUTO_TEST_CASE(one_out_of_N_batch_with_some_invalid_proof)
{
    GenerateParams(16, 4);

    auto commits = RandomizeGroupElements(N);

    // Generate
    std::vector<Secret> secrets;

    for (auto index : {1, 3}) {
        secrets.emplace_back(index);

        auto &s = secrets.back();

        commits[index] = Primitives::double_commit(
            g, s.s, h_gens[1], s.v, h_gens[0], s.r);
    }

    Prover prover(g, h_gens, n, m);
    std::vector<Proof> proofs;
    std::vector<Scalar> serials;

    Scalar x;
    x.randomize();

    for (auto const &s : secrets) {
        proofs.emplace_back();
        serials.push_back(s.s);
        GenerateBatchProof(
            prover, commits, s.l, s.s, s.v, s.r, x, proofs.back());
    }

    // Add an invalid
    proofs.push_back(proofs.back());

    serials.emplace_back(serials.back());
    serials.back().randomize();

    Verifier verifier(g, h_gens, n, m);
    BOOST_CHECK(!verifier.batchverify(commits, x, serials, proofs));
}

BOOST_AUTO_TEST_SUITE_END()

} // namespace lelantus


#ifndef PHD_PARTICLE_FILTER
#define PHD_PARTICLE_FILTER

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <Eigen/Dense>
#include <opencv2/core/eigen.hpp>

#include "../likelihood/gaussian.hpp"
#include "../likelihood/multivariate_gaussian.hpp"
#include "../utils/image_generator.hpp"
#include "../utils/utils.hpp"
#include "../detectors/hog_detector.hpp"
#include "hungarian.h"
#include "nms.hpp"
#include "dpp.hpp"

#include <time.h>
#include <float.h>
#include <vector>
#include <set>
#include <iostream>
#include <random>
#include <chrono>
#include <limits>
#include <algorithm>

using namespace cv;
using namespace std;
using namespace Eigen;

class PHDGaussianMixture {
public:
    int n_particles;
   ~PHDGaussianMixture();
    PHDGaussianMixture(bool verbose);
    PHDGaussianMixture(bool verbose, double epsilon);
    PHDGaussianMixture(bool verbose, double threshold, int neighbors, double min_scores_sum);
    PHDGaussianMixture();
    void initialize(Mat& current_frame, vector<Rect> detections,VectorXd detectionsWeights);
    void update(Mat& image, vector<Rect> detections, VectorXd detectionsWeights);
    void predict();
    bool is_initialized();
    vector<MyTarget> estimate(Mat& image, bool draw = false);

protected:
    mt19937 generator;
    vector<VectorXd> theta_x;
    bool initialized;
    normal_distribution<double> position_random_walk, velocity_random_walk, scale_random_walk;
    Size img_size;
    vector<MyTarget> tracks;
    vector<MyTarget> birth_model;
    RNG rng;
    set<int> labels;
    bool verbose;

    string pruning_method;
    /* DPP parameters*/
    double epsilon;

    /* NMS parameters */
    double threshold, min_scores_sum;
    int neighbors;
};

#endif


#include <iostream>

#include <boost/array.hpp>
#include <boost/numeric/odeint.hpp>

#include "Dynamics.h"

using std::cout;
using std::endl;

using namespace boost::numeric::odeint;

Dynamics::Dynamics() : PI(3.14159265359), rng_(new boost::mt19937()),
                       nd_(0, 1.0), var_nor(rng_, nd_),
                       process_noise_(0), measurement_noise_(0)
{
     for (int i = 0; i < 5; i++) {
          u_[i] = 0;
          x0_[i] = 0;
          state_[i] = 0;
     }

     rng_->seed((++seed_) + time(NULL));
}

std::vector<double> Dynamics::time_vector(double t0, double dt, double tend)
{
     std::vector<double> tt;
     for( double t = t0; t < tend ; t += dt ) {
          tt.push_back(t);
     }
     return tt;
}

void Dynamics::set_time(double t0, double dt, double tend)
{
     t0_ = t0;
     dt_ = dt;
     tend_ = tend;

     tt_.clear();
     for( double t = t0_ ; t < tend_ ; t += dt_ ) {
          tt_.push_back(t);
     }
}

void Dynamics::set_x0(state_5d_type x0)
{
     for(int i = 0; i < 5; i++) {
          x0_[i] = x0[i];
          state_[i] = x0[i];
          measurement_[i] = 0;
     }

     measurement_[0] = x0[0] + var_nor() * measurement_noise_;
     measurement_[1] = x0[1] + var_nor() * measurement_noise_;
}

void Dynamics::compute_trajectory()
{
     headings_.clear();
     truth_points_.clear();

     if (model_ == cart || model_ == roomba) {
          state_5d_type x = {x0_[0], x0_[1], x0_[2], x0_[3], x0_[4]};
          runge_kutta4< state_5d_type > stepper;

          std::vector<double>::iterator it = tt_.begin();
          for (; it != tt_.end(); it++) {
               cv::Point2d truth, measured;

               truth = cv::Point2d(x[0], x[1]);

               measured.x = truth.x + var_nor() * measurement_noise_;
               measured.y = truth.y + var_nor() * measurement_noise_;

               headings_.push_back(x[2]*180.0/PI);
               truth_points_.push_back(truth);
               measured_points_.push_back(measured);

               state_5d_type state;
               state[0] = x[0];
               state[1] = x[1];
               state[2] = x[2];
               state[3] = 0;
               state[4] = 0;
               states_.push_back(state);

               if (model_ == cart) {
                    stepper.do_step(make_ode_wrapper( *this , &Dynamics::cart_model ), x , *it , dt_ );
               } else {
                    stepper.do_step(make_ode_wrapper( *this , &Dynamics::roomba_model ), x , *it , dt_ );
               }
          }
     } else if (model_ == constant_velocity) {
     }
}

void Dynamics::step_motion_model(double dt, double time)
{
     if (input_sequence_.size() > 0 && input_sequence_.front().time <= time) {
          cout << "Using input: " << endl;
          for (int i = 0; i < 5; i++) {
               u_[i] = input_sequence_.front().input[i];

               printf("%d : %f\n", i, u_[i]);
          }
          input_sequence_.erase(input_sequence_.begin());
     }

     if (model_ == cart || model_ == roomba) {
          if (model_ == cart) {
               stepper_.do_step(make_ode_wrapper( *this , &Dynamics::cart_model ), state_ , 0 , dt );
          } else {
               stepper_.do_step(make_ode_wrapper( *this , &Dynamics::roomba_model ), state_ , 0 , dt );
          }

          cv::Point2d truth, measured;
          truth = cv::Point2d(state_[0], state_[1]);

          measured.x = truth.x + var_nor() * measurement_noise_;
          measured.y = truth.y + var_nor() * measurement_noise_;

          measurement_[0] = measured.x;
          measurement_[1] = measured.y;
          measurement_[2] = 0;
          measurement_[3] = 0;
          measurement_[4] = 0;

          headings_.push_back(state_[2]*180.0/PI);
          truth_points_.push_back(truth);
          measured_points_.push_back(measured);

     } else if (model_ == constant_velocity) {
     }
}

void Dynamics::set_input(state_5d_type input)
{
     input_sequence_.clear();

     ControlInput control_input;
     control_input.time = 0;
     for(int i = 0; i < 5; i++) {
          control_input.input[i] = input[i];
     }

     input_sequence_.push_back(control_input);
}

void Dynamics::set_input(state_5d_type input, double time)
{
     ControlInput control_input;
     control_input.time = time;
     for(int i = 0; i < 5; i++) {
          control_input.input[i] = input[i];
     }

     input_sequence_.push_back(control_input);
}

void Dynamics::set_input_sequence(std::vector<ControlInput> &input_sequence)
{
     input_sequence_.clear();

     for (std::vector<ControlInput>::iterator it = input_sequence.begin();
          it != input_sequence.end(); it++) {

          ControlInput control_input;
          control_input.time = it->time;
          for(int i = 0; i < 5; i++) {
               control_input.input[i] = it->input[i];
          }
          input_sequence_.push_back(control_input);
     }
}

void Dynamics::cart_model(const state_5d_type &x , state_5d_type &dxdt , double t)
{
     /// 0 : x-position
     /// 1 : y-position
     /// 2 : theta

     //u_[0] = 2;
     //u_[1] = 3.14159265359/20;

     double u = u_[0];
     double u_theta = u_[1];
     double L = 3;

     dxdt[0] = u*cos(x[2]) + var_nor() * process_noise_;
     dxdt[1] = u*sin(x[2]) + var_nor() * process_noise_;
     dxdt[2] = u/L*tan(u_theta) + var_nor() * process_noise_;
     dxdt[3] = 0;
     dxdt[4] = 0;
}

void Dynamics::roomba_model(const state_5d_type &x , state_5d_type &dxdt , double t)
{
     /// 0 : x-position
     /// 1 : y-position
     /// 2 : theta

     double R = 1;
     double b = 0.5;

     double w_l = u_[0];
     double w_r = u_[1];

     dxdt[0] = R*cos(x[2])*(w_r + w_l)/2;
     dxdt[1] = R*sin(x[2])*(w_r + w_l)/2;
     dxdt[2] = R*(w_r-w_l)/(2*b);
     dxdt[3] = 0;
     dxdt[4] = 0;
}

//void Dynamics::aircraft_6DOF_model(const state_type &x , state_type &dxdt , double t)
//{
//     /// 0:  u     : surge velocity
//     /// 1:  v     : sway velocity
//     /// 2:  w     : heave velocity
//     /// 3:  p     : roll rate
//     /// 4:  q     : pitch rate
//     /// 5:  r     : yaw rate
//     /// 6:  xpos  : earth x-pos
//     /// 7:  ypos  : earth y-pos
//     /// 8:  zpos  : earth z-pos
//     /// 9:  phi   : roll angle
//     /// 10: theta : pitch angle
//     /// 11: psi   : yaw angle
//
//     // Get current state values
//     double u     = x[0];
//     double v     = x[1];
//     double w     = x[2];
//     double p     = x[3];
//     double q     = x[4];
//     double r     = x[5];
//     //double xpos  = x[6];
//     //double ypos  = x[7];
//     //double zpos  = x[8];
//     double phi   = (x[9]);
//     double theta = (x[10]);
//     double psi   = (x[11]);
//
//     // Precalculate trig functions
//     double c1 = cos(phi);
//     double c2 = cos(theta);
//     double c3 = cos(psi);
//     double s1 = sin(phi);
//     double s2 = sin(theta);
//     double s3 = sin(psi);
//     double t2 = tan(theta);
//
//     //double m = 4536; // mass
//     double m = 1;
//
//     // Thrust is in surge direction
//
//     // Force Inputs
//     double Fx = u_[0];
//     double Fy = 0;
//     double Fz = 0;
//     double Fk = u_[1]; // roll
//     double Fm = u_[2]; // pitch
//     double Fn = 0; // yaw
//
//     // For x-z symmetry:
//     // 1.) Ixy = Iyx = 0
//     // 2.) Iyz = Izy = 0
//     //double Ixx = 23;
//     //double Ixy = 0;
//     //double Ixz = 2.97;
//     //double Iyy = 15.13;
//     //double Iyz = 0;
//     //double Izz = 16.99;
//
//     double Ixx = 1;
//     double Ixy = 0;
//     double Ixz = 0;
//     double Iyy = 1;
//     double Iyz = 0;
//     double Izz = 1;
//
//     //double Ixx = 35926.5;
//     //double Ixy = 0;
//     //double Ixz = 3418.17;
//     //double Iyy = 33940.7;
//     //double Iyz = 0;
//     //double Izz = 67085.5;
//
//     // Calculate Translational Forces
//     double X = Fx - m*q*w + m*r*v;
//     double Y = Fy - m*r*u + m*p*w;
//     double Z = Fz - m*p*v + m*q*u;
//
//     // Calculate Rotational Forces
//     double K = Fk + Iyz*(pow(q,2)-pow(r,2)) + Ixz*p*q - Ixy*r*p + (Iyy-Izz)*q*r;
//     double M = Fm + Ixz*(pow(r,2)-pow(p,2)) + Ixy*q*r - Iyz*p*q + (Izz-Ixx)*r*p;
//     double N = Fn + Ixy*(pow(p,2)-pow(q,2)) + Iyz*r*p - Ixz*q*r + (Ixx-Iyy)*p*q;
//
//     // Calculate angles
//     double P = p + (q*s1 + r*c1)*t2;
//     double Q = q*c1 - r*s1;
//     double R = (q*s1 + r*c1)*1.0/c2;
//
//     // Calculate Velocities and Rates
//     // xdot(1:6) = inv(A)*[X Y Z K M N]'
//     Eigen::MatrixXd A, Forces, derivs;
//     A.resize(9,9); Forces.resize(9,1);
//
//     A << m, 0, 0,   0,    0,    0,  0, 0, 0,
//          0, m, 0,   0,    0,    0,  0, 0, 0,
//          0, 0, m,   0,    0,    0,  0, 0, 0,
//          0, 0, 0,  Ixx, -Ixy, -Ixz, 0, 0, 0,
//          0, 0, 0, -Ixy,  Iyy, -Iyz, 0, 0, 0,
//          0, 0, 0, -Ixz, -Iyz,  Izz, 0, 0, 0,
//          0, 0, 0,   0,    0,    0,  1, 0, 0,
//          0, 0, 0,   0,    0,    0,  0, 1, 0,
//          0, 0, 0,   0,    0,    0,  0, 0, 1;
//
//
//     Forces << X, Y, Z, K, M, N, P, Q, R;
//
//     derivs = A.inverse() * Forces;
//
//     dxdt[0] = derivs(0,0);
//     dxdt[1] = derivs(1,0);
//     dxdt[2] = derivs(2,0);
//     dxdt[3] = derivs(3,0);
//     dxdt[4] = derivs(4,0);
//     dxdt[5] = derivs(5,0);
//
//     // Calculate Positions
//     dxdt[6] = c3*c2*u + (c3*s2*s1-s3*c1)*v + (s3*s1+c3*c1*s2)*w;
//     dxdt[7] = s3*c2*u + (c1*c3+s1*s2*s3)*v + (c1*s2*s3-c3*s1)*w;
//     dxdt[8] = -s2*u + c2*s1*v + c1*c2*w;
//
//     // Calculate Angles
//     dxdt[9] = derivs(6,0);
//     dxdt[10] = derivs(7,0);
//     dxdt[11] = derivs(8,0);
//}


#include <boost/assert.hpp>
#include <cmath>
#include <limits>
#include <utility>

#include "mh_moves/mh_moves.hpp"
#include "mh_moves/mh_move_weights.hpp"
#include "sampling/simple.hpp"
#include "mh_moves/utility.hpp"

namespace biggles {

namespace mh_moves
{

/// \brief get the probability that pieces are merged given tracks (which contains pieces)
float get_log_merge_prob(const partition_ptr_t &end_partition, const shared_const_track_ptr &front_piece,
    const shared_const_track_ptr &back_piece)
{
    const track_collection &tracks = end_partition->tracks();
    BOOST_ASSERT(tracks.contains(front_piece));
    BOOST_ASSERT(tracks.contains(back_piece));
    BOOST_ASSERT(tracks_could_be_merged2(*front_piece, *back_piece)>0.f);
    front_weight_merging fw(end_partition->first_time_stamp(), end_partition->last_time_stamp());
    float front_piece_weight = fw(front_piece);
    float total_weight = fun_sum(tracks.begin(), tracks.end(), fw, 0.f);
    float log_front_piece_prob = std::log(front_piece_weight) - std::log(total_weight);
    back_weight_merging bw(front_piece);
    float back_piece_weight = bw(back_piece);
    BOOST_ASSERT(back_piece_weight > 0);
    total_weight = fun_sum(tracks.begin(), tracks.end(), bw, 0.f);
    float log_back_piece_prob = std::log(back_piece_weight) - std::log(total_weight);
    return log_front_piece_prob + log_back_piece_prob;
}

bool split(const partition_ptr_t& start_partition_ptr, partition_ptr_t& end_partition, float& proposal_density_ratio)
{
    const track_collection& tracks(start_partition_ptr->tracks());

    capability_recorder_ptr cap_rec_ptr = start_partition_ptr->get_capability_recorder();
    BOOST_ASSERT(cap_rec_ptr.get() not_eq 0); // there actually is something assigned

    // early-out: there must be at least one track
    if(tracks.empty())
        return false;

    // choose a track to split
    float log_track_prob(1.f);
    /*
    track_collection::const_iterator track_it(sample_track_with_minimum_size(tracks, 4, log_track_prob));
    if(track_it == tracks.end())
        return false;

    // check we found one
    BOOST_ASSERT(track_it != tracks.end());
    BOOST_ASSERT((*track_it)->size() >= 4);
    BOOST_ASSERT(log_track_prob <= 0.f);
    const shared_const_track_ptr& track_to_split(*track_it);
        */
    shared_const_track_ptr track_to_split;
    if (not select_track(split_weight_t(), tracks, track_to_split, log_track_prob)) {
        return false;
    }

    // choose a splitting point
    time_stamp ts_to_split_at(0);
    float log_ts_prob(1.f);
    // IMPORTANT: BOOST_VERIFY will still evaluate its argument even when not in debug mode
    BOOST_VERIFY(sample_time_stamp_to_split_track(track_to_split, ts_to_split_at, log_ts_prob));
    BOOST_ASSERT(log_ts_prob <= 0.f);

    // create new tracks
    float log_splitting_prob = 0.f; // due to noobs at the splitting end of the new tracks
    shared_const_track_ptr_pair new_track_pair(
        split_track_at_time_stamp(track_to_split, ts_to_split_at, log_splitting_prob));

    // sanity check tracks
    BOOST_ASSERT(new_track_pair.first->size() + new_track_pair.second->size() == track_to_split->size());
    BOOST_ASSERT(new_track_pair.first->size() >= 2);
    BOOST_ASSERT(new_track_pair.second->size() >= 2);
    BOOST_ASSERT(new_track_pair.first->last_time_stamp() <= new_track_pair.second->first_time_stamp());
    BOOST_ASSERT(tracks_could_be_merged(*new_track_pair.first, *new_track_pair.second));

    // create copies of the tracks. The clutter is unaffected
    shared_track_collection_ptr new_tracks(new track_collection(tracks));

    cap_rec_ptr->add_erase_track(track_to_split);
    cap_rec_ptr->add_insert_track(new_track_pair.first);
    cap_rec_ptr->add_insert_track(new_track_pair.second);
    cap_rec_ptr->set_editing_finished();

    // remove the track from the collection
    new_tracks->remove(track_to_split);
    new_tracks->insert(new_track_pair.first);
    new_tracks->insert(new_track_pair.second);

    // update partition
    end_partition = partition_ptr_t(
        new partition(start_partition_ptr->pool(), new_tracks, start_partition_ptr->clutter_ptr(), start_partition_ptr->expansion())
        );
    end_partition->set_first_time_stamp(start_partition_ptr->first_time_stamp());
    end_partition->set_last_time_stamp(start_partition_ptr->last_time_stamp());

    // calculate forward (split) log prob.
    float forward_log_prob = log_ts_prob + log_track_prob + log_splitting_prob;

    float backward_log_prob = get_log_merge_prob(end_partition, new_track_pair.first, new_track_pair.second);

    proposal_density_ratio = backward_log_prob - forward_log_prob;

    return true;
}

}

}