//---------------------------------------------------------------------------//
//!
//! \file   tstTabularDistribution.cpp
//! \author Alex Robinson
//! \brief  Tabular distribution unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>

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

// FRENSIE Includes
#include "Utility_TabularDistribution.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 std::tuple<Utility::LinLin,
                   Utility::LogLin,
                   Utility::LinLog,
                   Utility::LogLog
                  > TestInterpPolicies;

template<typename ArchivePair>
struct InterpPolicyArchiveList
{
  typedef std::tuple<
    std::tuple<Utility::LinLin,typename Utility::TupleElement<0,ArchivePair>::type,typename Utility::TupleElement<1,ArchivePair>::type>,
    std::tuple<Utility::LogLin,typename Utility::TupleElement<0,ArchivePair>::type,typename Utility::TupleElement<1,ArchivePair>::type>,
    std::tuple<Utility::LinLog,typename Utility::TupleElement<0,ArchivePair>::type,typename Utility::TupleElement<1,ArchivePair>::type>,
    std::tuple<Utility::LogLog,typename Utility::TupleElement<0,ArchivePair>::type,typename Utility::TupleElement<1,ArchivePair>::type>
   > type;
};

template<typename... Types>
struct CreateInterpPolicyArchiveList;

template<typename T, typename... Types>
struct CreateInterpPolicyArchiveList<T,Types...>
{
  typedef decltype(std::tuple_cat(typename InterpPolicyArchiveList<T>::type(), typename CreateInterpPolicyArchiveList<Types...>::type())) type;
};

template<typename T>
struct CreateInterpPolicyArchiveList<T>
{
  typedef typename InterpPolicyArchiveList<T>::type type;
};

template<typename... Types>
struct CreateInterpPolicyArchiveList<std::true_type,std::tuple<Types...> > : public CreateInterpPolicyArchiveList<Types...>
{ /* ... */ };

template<typename... Types>
struct CreateInterpPolicyArchiveList<std::false_type,std::tuple<Types...> > : public CreateInterpPolicyArchiveList<std::tuple<Types...> >
{ /* ... */ };

typedef typename CreateInterpPolicyArchiveList<std::true_type,TestArchiveHelper::TestArchives>::type InterpPoliciesAndArchives;

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;
std::shared_ptr<Utility::TabularUnivariateDistribution> tab_distribution;

std::shared_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount> >
  unit_aware_distribution;
std::shared_ptr<Utility::UnitAwareTabularUnivariateDistribution<MegaElectronVolt,si::amount> >
unit_aware_tab_distribution;

//---------------------------------------------------------------------------//
// Testing Functions.
//---------------------------------------------------------------------------//
// Initialize the distribution
template<typename InterpolationPolicy, typename BaseDistribution>
void initialize( std::shared_ptr<BaseDistribution>& dist )
{
  // Use the basic constructor
  std::vector<typename BaseDistribution::IndepQuantity>
    independent_values( 4 );
  Utility::setQuantity( independent_values[0], 1e-3 );
  Utility::setQuantity( independent_values[1], 1e-2 );
  Utility::setQuantity( independent_values[2], 1e-1 );
  Utility::setQuantity( independent_values[3], 1.0 );

  std::vector<typename BaseDistribution::DepQuantity> dependent_values( 4 );
  Utility::setQuantity( dependent_values[0], 1e2 );
  Utility::setQuantity( dependent_values[1], 1e1 );
  Utility::setQuantity( dependent_values[2], 1.0 );
  Utility::setQuantity( dependent_values[3], 1e-1 );

  dist.reset(new Utility::UnitAwareTabularDistribution<InterpolationPolicy,typename BaseDistribution::IndepUnit, typename BaseDistribution::DepUnit>(
							  independent_values,
							  dependent_values ) );
}

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            evaluate,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1e-3 ), 1e2 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1e-2 ), 1e1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1e-1 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0 ), 1e-1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 2.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            evaluate,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV ),
		       0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1e-3*MeV ),
		       1e2*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1e-2*MeV ),
		       1e1*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1e-1*MeV ),
		       1.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV ),
		       1e-1*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 2.0*MeV ),
		       0.0*si::mole );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            evaluatePDF,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( distribution->evaluatePDF( 1e-3 ),
			  67.340006734,
			  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( distribution->evaluatePDF( 1e-2 ),
			  6.7340006734,
			  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( distribution->evaluatePDF( 1e-1 ),
			  0.67340006734,
			  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( distribution->evaluatePDF( 1.0 ),
			  0.067340006734,
			  1e-6 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 2.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            evaluatePDF,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.0*MeV ),
		       0.0/MeV );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			     unit_aware_distribution->evaluatePDF( 1e-3*MeV ),
			     67.340006734/MeV,
			     1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			     unit_aware_distribution->evaluatePDF( 1e-2*MeV ),
			     6.7340006734/MeV,
			     1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			     unit_aware_distribution->evaluatePDF( 1e-1*MeV ),
			     0.67340006734/MeV,
			     1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			      unit_aware_distribution->evaluatePDF( 1.0*MeV ),
			      0.067340006734/MeV,
			      1e-6 );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 2.0*MeV ),
		       0.0/MeV );
}

//---------------------------------------------------------------------------//
// Check that the CDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            evaluateCDF,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 0.0 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1e-3 ),
			  0.0000000000,
			  1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1e-2 ),
			  0.33333333333,
			  1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1e-1 ),
			  0.66666666667,
			  1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1.0 ),
			  1.0000000000,
			  1e-10 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 2.0 ), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware CDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            evaluateCDF,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_tab_distribution->evaluateCDF( 0.0*MeV ),
		       0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1e-3*MeV ),
			  0.0000000000,
			  1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1e-2*MeV ),
			  0.33333333333,
			  1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1e-1*MeV ),
			  0.66666666667,
			  1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 1.0*MeV ),
			  1.0000000000,
			  1e-10 );
  FRENSIE_CHECK_EQUAL( unit_aware_tab_distribution->evaluateCDF( 2.0*MeV ),
		       1.0 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            sample,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, 1e-3 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sample();
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            sample,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, 1e-3*MeV );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3*MeV );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            sampleAndRecordTrials,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  double sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, 1e-3 );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-12 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1.0 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            sampleAndRecordTrials,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<MegaElectronVolt> sample =
    unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, 1e-3*MeV );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3*MeV );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1.0*MeV );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            sampleAndRecordBinIndex,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  size_t bin_index;

  double sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, 1e-3 );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            sampleAndRecordBinIndex,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  size_t bin_index;

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, 1e-3*MeV );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3*MeV );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            sampleWithRandomNumber,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  double sample = tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, 1e-3 );

  sample = tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            sampleWithRandomNumber,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, 1e-3*MeV );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*MeV, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            sampleInSubrange,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = tab_distribution->sampleInSubrange( 1e-1  );
  FRENSIE_CHECK_EQUAL( sample, 1e-3 );

  sample = tab_distribution->sampleInSubrange( 1e-1 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1e-1, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleInSubrange( 1e-1 );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1e-1 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            sampleInSubrange,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleInSubrange( 1e-1*MeV  );
  FRENSIE_CHECK_EQUAL( sample, 1e-3*MeV );

  sample = unit_aware_tab_distribution->sampleInSubrange( 1e-1*MeV );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1e-1*MeV, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleInSubrange( 1e-1*MeV );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, 1e-3*MeV );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 1e-1*MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            sampleWithRandomNumberInSubrange,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  double sample =
    tab_distribution->sampleWithRandomNumberInSubrange( 0.0, 1e-1  );
  FRENSIE_CHECK_EQUAL( sample, 1e-3 );

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 1.0, 1e-1 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1e-1, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            sampleWithRandomNumberInSubrange,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  quantity<MegaElectronVolt> sample =
    unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							       0.0, 1e-1*MeV );
  FRENSIE_CHECK_EQUAL( sample, 1e-3*MeV );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							       1.0, 1e-1*MeV );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1e-1*MeV, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            getUpperBoundOfIndepVar,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            getUpperBoundOfIndepVar,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       1.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            getLowerBoundOfIndepVar,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(), 1e-3 );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            getLowerBoundOfIndepVar,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       1e-3*MeV );
}

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            getDistributionType,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
		       Utility::TABULAR_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            getDistributionType,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
		       Utility::TABULAR_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            isTabular,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            isTabular,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check that the distribution is continuous
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            isContinuous,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution is continuous
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            isContinuous,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            isCompatibleWithInterpType,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLog>() );

  // if( boost::is_same<InterpolationPolicy,Utility::LinLin>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LinLog>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LogLin>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LogLog>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // }
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is compatible with the interp type
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            isCompatibleWithInterpType,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  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>() );

  // if( boost::is_same<InterpolationPolicy,Utility::LinLin>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LinLog>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LogLin>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LogLog>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // }
}

//---------------------------------------------------------------------------//
// Check that the distribution can be placed in a stream
FRENSIE_UNIT_TEST_TEMPLATE( TabularDistribution,
                            ostream_operator,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  std::ostringstream oss;

  oss << Utility::TabularDistribution<InterpolationPolicy>();

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Tabular Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["interp"].toString(),
                       InterpolationPolicy::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent values"].toType<std::vector<double> >(),
                       std::vector<double>({(std::is_same<typename InterpolationPolicy::IndepVarProcessingTag,Utility::LinIndepVarProcessingTag>::value ? 0.0 : 0.1), 1.0}),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent values"].toType<std::vector<double> >(),
                       std::vector<double>({1.0, 1.0}) );

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

  oss << Utility::TabularDistribution<InterpolationPolicy>( {1, 2, 3}, {3, 2, 1} );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Tabular Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["interp"].toString(),
                       InterpolationPolicy::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent values"].toType<std::vector<double> >(),
                       std::vector<double>({1.0, 2.0, 3.0}),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent values"].toType<std::vector<double> >(),
                       std::vector<double>({3.0, 2.0, 1.0}),
                       SHOW_LHS );

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

  std::unique_ptr<Utility::UnivariateDistribution> local_dist( new Utility::TabularDistribution<InterpolationPolicy>( {1, 2, 3}, {3, 2, 1} ) );

  oss << *local_dist;

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Tabular Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["interp"].toString(),
                       InterpolationPolicy::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent values"].toType<std::vector<double> >(),
                       std::vector<double>({1.0, 2.0, 3.0}),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent values"].toType<std::vector<double> >(),
                       std::vector<double>({3.0, 2.0, 1.0}),
                       SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be placed in a stream
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularDistribution,
                            ostream_operator,
                            TestInterpPolicies )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  std::ostringstream oss;

  oss << Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount>();

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Tabular Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["interp"].toString(),
                       InterpolationPolicy::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent values"].toType<std::vector<quantity<MegaElectronVolt> > >(),
                       std::vector<quantity<MegaElectronVolt> >({(std::is_same<typename InterpolationPolicy::IndepVarProcessingTag,Utility::LinIndepVarProcessingTag>::value ? 0.0*MeV : 0.1*MeV), 1.0*MeV}),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent values"].toType<std::vector<quantity<si::amount> > >(),
                       std::vector<quantity<si::amount> >({1.0*si::mole, 1.0*si::mole}),
                       SHOW_LHS );

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

  oss << Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount>( {1, 2, 3}, {3, 2, 1} );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Tabular Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["interp"].toString(),
                       InterpolationPolicy::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent values"].toType<std::vector<quantity<MegaElectronVolt> > >(),
                       std::vector<quantity<MegaElectronVolt> >({1.0*MeV, 2.0*MeV, 3.0*MeV}),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent values"].toType<std::vector<quantity<si::amount> > >(),
                       std::vector<quantity<si::amount> >({3.0*si::mole, 2.0*si::mole, 1.0*si::mole}),
                       SHOW_LHS );

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

  std::unique_ptr<Utility::UnitAwareUnivariateDistribution<MegaElectronVolt,si::amount> > local_dist( new Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount>( {1, 2, 3}, {3, 2, 1} ) );

  oss << *local_dist;

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Tabular Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["interp"].toString(),
                       InterpolationPolicy::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent values"].toType<std::vector<quantity<MegaElectronVolt> > >(),
                       std::vector<quantity<MegaElectronVolt> >({1.0*MeV, 2.0*MeV, 3.0*MeV}),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent values"].toType<std::vector<quantity<si::amount> > >(),
                       std::vector<quantity<si::amount> >({3.0*si::mole, 2.0*si::mole, 1.0*si::mole}),
                       SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( TabularDistribution,
                                   archive,
                                   InterpPoliciesAndArchives )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );
  FETCH_TEMPLATE_PARAM( 1, RawOArchive );
  FETCH_TEMPLATE_PARAM( 2, RawIArchive );

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

  std::string archive_base_name( "test_tabular_" );
  archive_base_name += InterpolationPolicy::name();
  archive_base_name += "_dist";

  std::ostringstream archive_ostream;

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::TabularDistribution<InterpolationPolicy> dist_a( {1.0, 2.0, 3.0, 4.0}, {4.0, 3.0, 2.0, 1.0} );

    initialize<InterpolationPolicy>( distribution );

    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    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::TabularDistribution<InterpolationPolicy> dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, Utility::TabularDistribution<InterpolationPolicy>( {1.0, 2.0, 3.0, 4.0}, {4.0, 3.0, 2.0, 1.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::TabularDistribution<InterpolationPolicy>*>( shared_dist.get() ),
                       *dynamic_cast<Utility::TabularDistribution<InterpolationPolicy>*>( distribution.get() ) );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareTabularDistribution,
                                   archive,
                                   InterpPoliciesAndArchives )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );
  FETCH_TEMPLATE_PARAM( 1, RawOArchive );
  FETCH_TEMPLATE_PARAM( 2, RawIArchive );

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

  std::string archive_base_name( "test_tabular_" );
  archive_base_name += InterpolationPolicy::name();
  archive_base_name += "_dist";

  std::ostringstream archive_ostream;

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount> dist_a( {1.0, 2.0, 3.0, 4.0}, {4.0, 3.0, 2.0, 1.0} );

    initialize<InterpolationPolicy>( unit_aware_distribution );

    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    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::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount> dist_a;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, (Utility::UnitAwareTabularDistribution<InterpolationPolicy,MegaElectronVolt,si::amount>( {1.0, 2.0, 3.0, 4.0}, {4.0, 3.0, 2.0, 1.0} )) );

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

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

//---------------------------------------------------------------------------//
// Check that distributions can be scaled
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareTabularDistribution,
				   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;

  initialize<Utility::LinLin>( distribution );

  // Copy from unitless distribution to distribution type A
  Utility::UnitAwareTabularDistribution<Utility::LinLin,IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareTabularDistribution<Utility::LinLin,IndepUnitA,DepUnitA>::fromUnitlessDistribution( *dynamic_cast<Utility::TabularDistribution<Utility::LinLin>*>( distribution.get() ) );

  // Copy from distribution type A to distribution type B
  Utility::UnitAwareTabularDistribution<Utility::LinLin,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( 0.0 );
  DepQuantityA dep_quantity_a =
    Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 0.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, 0.1 );
  Utility::setQuantity( inv_indep_quantity_a, 0.67340006734 );
  Utility::setQuantity( dep_quantity_a, 1.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-6 );
  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-6 );
}

//---------------------------------------------------------------------------//
// 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 tstTabularDistribution.cpp
//---------------------------------------------------------------------------//


/**
 * \file
 * \copyright
 * Copyright (c) 2012-2022, OpenGeoSys Community (http://www.opengeosys.org)
 *            Distributed under a Modified BSD License.
 *              See accompanying file LICENSE.txt or
 *              http://www.opengeosys.org/project/license
 */

#include "Coulomb.h"

#include <boost/math/constants/constants.hpp>

#include "BaseLib/Error.h"
#include "LogPenalty.h"
#include "MathLib/MathTools.h"
#include "NumLib/Exceptions.h"

namespace MaterialLib
{
namespace Fracture
{
namespace Coulomb
{
struct MaterialPropertyValues
{
    double Kn = 0.0;
    double Ks = 0.0;
    double phi = 0.0;  // friction angle
    double psi = 0.0;  // dilation angle
    double c = 0.0;

    template <typename MaterialProperties>
    MaterialPropertyValues(MaterialProperties const& mp,
                           double const t,
                           ParameterLib::SpatialPosition const& x)
    {
        Kn = mp.normal_stiffness(t, x)[0];
        Ks = mp.shear_stiffness(t, x)[0];
        auto constexpr degree =
            boost::math::constants::degree<double>();  // pi/180
        phi = mp.friction_angle(t, x)[0] * degree;
        psi = mp.dilatancy_angle(t, x)[0] * degree;
        c = mp.cohesion(t, x)[0];
    }
};

template <int DisplacementDim>
void Coulomb<DisplacementDim>::computeConstitutiveRelation(
    double const t,
    ParameterLib::SpatialPosition const& x,
    double const aperture0,
    Eigen::Ref<Eigen::VectorXd const>
    /*sigma0*/,
    Eigen::Ref<Eigen::VectorXd const>
        w_prev,
    Eigen::Ref<Eigen::VectorXd const>
        w,
    Eigen::Ref<Eigen::VectorXd const>
        sigma_prev,
    Eigen::Ref<Eigen::VectorXd>
        sigma,
    Eigen::Ref<Eigen::MatrixXd>
        Kep,
    typename FractureModelBase<DisplacementDim>::MaterialStateVariables&
        material_state_variables)
{
    assert(dynamic_cast<StateVariables<DisplacementDim> const*>(
               &material_state_variables) != nullptr);

    StateVariables<DisplacementDim>& state =
        static_cast<StateVariables<DisplacementDim>&>(material_state_variables);

    MaterialPropertyValues const mat(_mp, t, x);

    const int index_ns = DisplacementDim - 1;
    double const aperture = w[index_ns] + aperture0;

    Eigen::MatrixXd Ke;
    {  // Elastic tangent stiffness
        Ke = Eigen::MatrixXd::Zero(DisplacementDim, DisplacementDim);
        for (int i = 0; i < index_ns; i++)
        {
            Ke(i, i) = mat.Ks;
        }

        Ke(index_ns, index_ns) = mat.Kn;
    }

    // Total plastic aperture compression
    // NOTE: Initial condition sigma0 seems to be associated with an initial
    // condition of the w0 = 0. Therefore the initial state is not associated
    // with a plastic aperture change.
    {  // Exact elastic predictor
        sigma.noalias() = Ke * (w - w_prev);

        sigma.coeffRef(index_ns) *=
            logPenaltyDerivative(aperture0, aperture, _penalty_aperture_cutoff);
        sigma.noalias() += sigma_prev;
    }

    // correction for an opening fracture
    if (_tension_cutoff && sigma[DisplacementDim - 1] >= 0)
    {
        Kep.setZero();
        sigma.setZero();
        state.w_p = w;
        material_state_variables.setTensileStress(true);
        return;
    }

    auto yield_function = [&mat](Eigen::VectorXd const& s)
    {
        double const sigma_n = s[DisplacementDim - 1];
        Eigen::VectorXd const sigma_s = s.head(DisplacementDim - 1);
        double const mag_tau = sigma_s.norm();  // magnitude
        return mag_tau + sigma_n * std::tan(mat.phi) - mat.c;
    };

    {  // Exit if still in elastic range by checking the shear yield function.
        double const Fs = yield_function(sigma);
        material_state_variables.setShearYieldFunctionValue(Fs);
        if (Fs < .0)
        {
            Kep = Ke;
            Kep(index_ns, index_ns) *= logPenaltyDerivative(
                aperture0, aperture, _penalty_aperture_cutoff);
            return;
        }
    }

    auto yield_function_derivative = [&mat](Eigen::VectorXd const& s)
    {
        Eigen::Matrix<double, DisplacementDim, 1> dFs_dS;
        dFs_dS.template head<DisplacementDim - 1>().noalias() =
            s.template head<DisplacementDim - 1>().normalized();
        dFs_dS.coeffRef(DisplacementDim - 1) = std::tan(mat.phi);
        return dFs_dS;
    };

    // plastic potential function: Qs = |tau| + Sn * tan da
    auto plastic_potential_derivative = [&mat](Eigen::VectorXd const& s)
    {
        Eigen::Matrix<double, DisplacementDim, 1> dQs_dS;
        dQs_dS.template head<DisplacementDim - 1>().noalias() =
            s.template head<DisplacementDim - 1>().normalized();
        dQs_dS.coeffRef(DisplacementDim - 1) = std::tan(mat.psi);
        return dQs_dS;
    };

    {  // Newton

        Eigen::FullPivLU<Eigen::Matrix<double, 1, 1, Eigen::RowMajor>>
            linear_solver;
        using ResidualVectorType = Eigen::Matrix<double, 1, 1, Eigen::RowMajor>;
        using JacobianMatrix = Eigen::Matrix<double, 1, 1, Eigen::RowMajor>;

        JacobianMatrix jacobian;
        ResidualVectorType solution;
        solution << 0;

        auto const update_residual = [&](ResidualVectorType& residual)
        { residual[0] = yield_function(sigma); };

        auto const update_jacobian = [&](JacobianMatrix& jacobian)
        {
            jacobian(0, 0) = -yield_function_derivative(sigma).transpose() *
                             Ke * plastic_potential_derivative(sigma);
        };

        auto const update_solution = [&](ResidualVectorType const& increment)
        {
            solution += increment;
            /*DBUG("analytical = {:g}",
                 Fs / (mat.Ks + mat.Kn * std::tan(mat.psi) * std::tan(mat.phi)))
                 */
            state.w_p = state.w_p_prev +
                        solution[0] * plastic_potential_derivative(sigma);

            sigma.noalias() = Ke * (w - w_prev - state.w_p + state.w_p_prev);

            sigma.coeffRef(index_ns) *= logPenaltyDerivative(
                aperture0, aperture, _penalty_aperture_cutoff);
            sigma.noalias() += sigma_prev;
        };

        auto newton_solver =
            NumLib::NewtonRaphson<decltype(linear_solver), JacobianMatrix,
                                  decltype(update_jacobian), ResidualVectorType,
                                  decltype(update_residual),
                                  decltype(update_solution)>(
                linear_solver, update_jacobian, update_residual,
                update_solution, _nonlinear_solver_parameters);

        auto const success_iterations = newton_solver.solve(jacobian);

        if (!success_iterations)
        {
            throw NumLib::AssemblyException(
                "FractureModel/Coulomb local nonlinear solver didn't "
                "converge.");
        }

        // Solution containing lambda is not needed; w_p and sigma already
        // up to date.
    }

    {  // Update material state shear yield function value.
        double const Fs = yield_function(sigma);
        material_state_variables.setShearYieldFunctionValue(Fs);
    }

    Ke(index_ns, index_ns) *=
        logPenaltyDerivative(aperture0, aperture, _penalty_aperture_cutoff);
    Eigen::RowVectorXd const A = yield_function_derivative(sigma).transpose() *
                                 Ke /
                                 (yield_function_derivative(sigma).transpose() *
                                  Ke * plastic_potential_derivative(sigma));
    Kep = Ke - Ke * plastic_potential_derivative(sigma) * A;
}

template class Coulomb<2>;
template class Coulomb<3>;

}  // namespace Coulomb
}  // namespace Fracture
}  // namespace MaterialLib


#include "fbstab/fbstab_mpc.h"

#include <Eigen/Dense>
#include <memory>
#include <stdexcept>
#include <vector>

#include "fbstab/components/full_feasibility.h"
#include "fbstab/components/full_residual.h"
#include "fbstab/components/full_variable.h"
#include "fbstab/components/mpc_data.h"
#include "fbstab/components/riccati_linear_solver.h"
#include "tools/matrix_sequence.h"
#include "tools/utilities.h"

namespace fbstab {

FBstabMpc::ProblemDataRef::ProblemDataRef(
    const MatrixSequence* Q_, const MatrixSequence* R_,
    const MatrixSequence* S_, const MatrixSequence* q_,
    const MatrixSequence* r_, const MatrixSequence* A_,
    const MatrixSequence* B_, const MatrixSequence* c_,
    const MatrixSequence* E_, const MatrixSequence* L_,
    const MatrixSequence* d_, const Eigen::VectorXd* x0_)
    : Q(*Q_),
      R(*R_),
      S(*S_),
      q(*q_),
      r(*r_),
      A(*A_),
      B(*B_),
      c(*c_),
      E(*E_),
      L(*L_),
      d(*d_),
      x0(x0_->data(), x0_->size()) {}

FBstabMpc::Variable::Variable(int N, int nx, int nu, int nc) {
  z = Eigen::VectorXd::Zero((N + 1) * (nx + nu));
  l = Eigen::VectorXd::Zero((N + 1) * nx);
  v = Eigen::VectorXd::Zero((N + 1) * nc);
  y = Eigen::VectorXd::Zero((N + 1) * nc);
}

FBstabMpc::Variable::Variable(const Eigen::Vector4d& s)
    : Variable(s(0), s(1), s(2), s(3)) {}

FBstabMpc::VariableRef::VariableRef(Eigen::Map<Eigen::VectorXd> z_,
                                    Eigen::Map<Eigen::VectorXd> l_,
                                    Eigen::Map<Eigen::VectorXd> v_,
                                    Eigen::Map<Eigen::VectorXd> y_)
    : z(z_), l(l_), v(v_), y(y_) {}

void FBstabMpc::VariableRef::fill(double a) {
  z.fill(a);
  l.fill(a);
  v.fill(a);
  y.fill(a);
}

FBstabMpc::FBstabMpc(int N, int nx, int nu, int nc) {
  if (N < 1 || nx < 1 || nu < 1 || nc < 1) {
    throw std::runtime_error(
        "In FBstabMpc::FBstabMpc: problem sizes must be positive.");
  }
  N_ = N;
  nx_ = nx;
  nu_ = nu;
  nc_ = nc;
  nz_ = (nx + nu) * (N + 1);
  nl_ = nx * (N + 1);
  nv_ = nc * (N + 1);

  // create the components
  x1_ = tools::make_unique<FullVariable>(nz_, nl_, nv_);
  x2_ = tools::make_unique<FullVariable>(nz_, nl_, nv_);
  x3_ = tools::make_unique<FullVariable>(nz_, nl_, nv_);
  x4_ = tools::make_unique<FullVariable>(nz_, nl_, nv_);
  r1_ = tools::make_unique<FullResidual>(nz_, nl_, nv_);
  r2_ = tools::make_unique<FullResidual>(nz_, nl_, nv_);
  feasibility_checker_ = tools::make_unique<FullFeasibility>(nz_, nl_, nv_);
  linear_solver_ = tools::make_unique<RiccatiLinearSolver>(N, nx, nu, nc);

  algorithm_ = tools::make_unique<Algorithm>(
      x1_.get(), x2_.get(), x3_.get(), x4_.get(), r1_.get(), r2_.get(),
      linear_solver_.get(), feasibility_checker_.get());

  opts_ = DefaultOptions();
}

FBstabMpc::FBstabMpc(const Eigen::Vector4d& s)
    : FBstabMpc(s(0), s(1), s(2), s(3)) {}

void FBstabMpc::UpdateOptions(const Options& options) {
  // No need to validate since there are no additional options and the
  // algorithm will check the algorithmic ones.
  algorithm_->UpdateParameters(&options);
}

FBstabMpc::Options FBstabMpc::DefaultOptions() {
  Options opts;
  opts.DefaultParameters();
  return opts;
}

FBstabMpc::Options FBstabMpc::ReliableOptions() {
  Options opts;
  opts.ReliableParameters();
  return opts;
}

// Explicit instantiation.
template class FBstabAlgorithm<FullVariable, FullResidual, RiccatiLinearSolver,
                               FullFeasibility>;

}  // namespace fbstab


#include <blitz/array.h>

using namespace blitz;

class Base {
       public:
         virtual void multiply(Array<int,1>& array) const=0;
       };

       class Derived1 : public Base {
       public:
         Derived1(){}
         void multiply(Array<int,1>& array) const {array*=1;}
       };

       class Derived2 : public Base {
       public:
         Derived2(){}
         void multiply(Array<int,1>& array) const {array*=2;}
       };

       Array<int,1>& operator*=(Array<int,1>& array, const Derived1& object) {
         object.multiply(array);
         return array;
       }

int main()
{
       Array<int,1> array(5);
       array=1;

       Derived1 derived1;

       array*=derived1;
}


/*****************************************************************************
*
* Rokko: Integrated Interface for libraries of eigenvalue decomposition
*
* Copyright (C) 2017 by Rokko Developers https://github.com/t-sakashita/rokko
*
* Distributed under 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 ROKKO_LAPACK_UNGQR_HPP
#define ROKKO_LAPACK_UNGQR_HPP

#include <complex>
#ifdef I
# undef I
#endif
#include <stdexcept>
#include <boost/type_traits/is_same.hpp>
#include <lapacke.h>
#include <rokko/traits/value_t.hpp>
#include "complex_cast.hpp"

namespace rokko {
namespace lapack {

namespace {

template<typename T> struct ungqr_dispatch;

template<>
struct ungqr_dispatch<float> {
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau) {
    return LAPACKE_sorgqr(matrix_layout, m, n, k, storage(a), ld(a), storage(tau));
  }
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau, VECTOR& work) {
    return LAPACKE_sorgqr(matrix_layout, m, n, k, storage(a), ld(a), storage(tau),
                          storage(work), size(work));
  }
};

template<>
struct ungqr_dispatch<double> {
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau) {
    return LAPACKE_dorgqr(matrix_layout, m, n, k, storage(a), ld(a), storage(tau));
  }
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau, VECTOR& work) {
    return LAPACKE_dorgqr(matrix_layout, m, n, k, storage(a), ld(a), storage(tau),
                          storage(work), size(work));
  }
};

template<>
struct ungqr_dispatch<std::complex<float>> {
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau) {
    return LAPACKE_cungqr(matrix_layout, m, n, k, complex_cast(storage(a)), ld(a),
                          complex_cast(storage(tau)));
  }
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau, VECTOR& work) {
    return LAPACKE_cungqr(matrix_layout, m, n, k, complex_cast(storage(a)), ld(a),
                          complex_cast(storage(tau)), complex_cast(storage(work)),
                          size(work));
  }
};

template<>
struct ungqr_dispatch<std::complex<double>> {
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau) {
    return LAPACKE_zungqr(matrix_layout, m, n, k, complex_cast(storage(a)), ld(a),
                          complex_cast(storage(tau)));
  }
  template<typename MATRIX, typename VECTOR>
  static lapack_int ungqr(int matrix_layout, lapack_int m, lapack_int n, lapack_int k,
                          MATRIX& a, VECTOR& tau, VECTOR& work) {
    return LAPACKE_zungqr(matrix_layout, m, n, k, complex_cast(storage(a)), ld(a),
                          complex_cast(storage(tau)), complex_cast(storage(work)),
                          size(work));
  }
};

}

template<typename MATRIX, typename VECTOR>
lapack_int ungqr(lapack_int k, MATRIX& a, VECTOR const& tau) {
  static_assert(std::is_same<value_t<MATRIX>, value_t<VECTOR>>::value, "");
  lapack_int m = rows(a);
  lapack_int n = cols(a);
  if (size(tau) != k)
    throw std::invalid_argument("vector tau size mismatch");
  return ungqr_dispatch<value_t<MATRIX>>
    ::ungqr((is_col_major(a) ? LAPACK_COL_MAJOR : LAPACK_ROW_MAJOR), m, n, k, a, tau);
}

template<typename MATRIX, typename VECTOR>
lapack_int ungqr(lapack_int k, MATRIX& a, VECTOR const& tau, VECTOR& work) {
  static_assert(std::is_same<value_t<MATRIX>, value_t<VECTOR>>::value, "");
  lapack_int m = rows(a);
  lapack_int n = cols(a);
  if (size(tau) != k)
    throw std::invalid_argument("vector tau size mismatch");
  if (size(work) < std::max(1, n))
    throw std::invalid_argument("vector work size mismatch");
  return ungqr_dispatch<value_t<MATRIX>>
    ::ungqr((is_col_major(a) ? LAPACK_COL_MAJOR : LAPACK_ROW_MAJOR), m, n, k, a, tau, work);
}

template<typename MATRIX, typename VECTOR>
lapack_int orgqr(lapack_int k, MATRIX& a, VECTOR const& tau) {
  return ungqr(k, a, tau);
}

template<typename MATRIX, typename VECTOR>
lapack_int orgqr(lapack_int k, MATRIX& a, VECTOR const& tau, VECTOR& work) {
  return ungqr(k, a, tau, work);
};

} // end namespace lapack
} // end namespace rokko

#endif // ROKKO_LAPACK_UNGQR_HPP


#include <algorithm>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>

#ifndef CPPDEBUG /* Ubuntu's Boost does not provide binaries compatible with libstdc++'s debug mode so we just reduce functionality here */
#include <boost/program_options.hpp>
#endif

#include "boost_profile.cpp"
#include <libff/algebra/curves/edwards/edwards_pp.hpp>
#include <libff/algebra/curves/alt_bn128/alt_bn128_pp.hpp>

#include "libiop/algebra/fields/gf64.hpp"
#include "libiop/algebra/fields/gf128.hpp"
#include "libiop/algebra/fields/gf192.hpp"
#include "libiop/algebra/fields/gf256.hpp"

#include "libiop/snark/ligero_snark.hpp"
#include "libiop/bcs/bcs_common.hpp"
#include "libiop/bcs/common_bcs_parameters.hpp"
#include "libiop/relations/examples/r1cs_examples.hpp"

#ifndef CPPDEBUG
bool process_prover_command_line(const int argc, const char** argv,
                                 options &options,
                                 float &height_width_ratio,
                                 std::size_t &RS_extra_dimensions)
{
    namespace po = boost::program_options;

    try
    {
        po::options_description desc = gen_options(options);
        desc.add_options()
             ("height_width_ratio", po::value<float>(&height_width_ratio)->default_value(0.1))
             ("RS_extra_dimensions", po::value<std::size_t>(&RS_extra_dimensions)->default_value(2));

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        po::notify(vm);
        options.hash_enum = static_cast<libiop::bcs_hash_type>(options.hash_enum_val);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }

    return true;
}
#endif

using namespace libiop;

template<typename FieldT, typename hash_type>
void instrument_ligero_snark(options &options,
                             LDT_reducer_soundness_type ldt_reducer_soundness_type,
                             const field_subset_type domain_type,
                             float height_width_ratio,
                             std::size_t RS_extra_dimensions)
{
    ligero_snark_parameters<FieldT, hash_type> parameters;
    parameters.security_level_ = options.security_level;
    parameters.LDT_reducer_soundness_type_ = ldt_reducer_soundness_type;
    parameters.height_width_ratio_ = height_width_ratio;
    parameters.RS_extra_dimensions_ = RS_extra_dimensions;
    parameters.make_zk_ = options.make_zk;
    parameters.domain_type_ = domain_type;
    parameters.bcs_params_ = default_bcs_params<FieldT, hash_type>(options.hash_enum, options.security_level, options.log_n_min);
    parameters.describe();

    for (std::size_t log_n = options.log_n_min; log_n <= options.log_n_max; ++log_n)
    {
        print_separator();
        const std::size_t n = 1ul << log_n;
        /* k+1 needs to be a power of 2 (proof system artifact) and k <= n+2 (example generation artifact) so we just fix it to 15 here */
        const std::size_t k = 15;
        const std::size_t m = n - 1;
        r1cs_example<FieldT> example = generate_r1cs_example<FieldT>(n, k, m);
        parameters.bcs_params_ = default_bcs_params<FieldT, hash_type>(options.hash_enum, options.security_level, log_n);

        enter_block("Check satisfiability of R1CS example");
        const bool is_satisfied = example.constraint_system_.is_satisfied(
            example.primary_input_, example.auxiliary_input_);
        assert(is_satisfied);
        leave_block("Check satisfiability of R1CS example");
        printf("\n");
        print_indent(); printf("* R1CS number of constraints: %zu\n", example.constraint_system_.num_constraints());
        print_indent(); printf("* R1CS number of variables: %zu\n", example.constraint_system_.num_variables());
        print_indent(); printf("* R1CS number of variables for primary input: %zu\n", example.primary_input_.size());
        print_indent(); printf("* R1CS number of variables for auxiliary input: %zu\n", example.auxiliary_input_.size());
        print_indent(); printf("* R1CS size of constraint system (bytes): %zu\n", example.constraint_system_.size_in_bytes());
        print_indent(); printf("* R1CS size of primary input (bytes): %zu\n", example.primary_input_.size() * sizeof(FieldT));
        print_indent(); printf("* R1CS size of auxiliary input (bytes): %zu\n", example.auxiliary_input_.size() * sizeof(FieldT));
        printf("\n");
        const ligero_snark_argument<FieldT, hash_type> proof = ligero_snark_prover<FieldT, hash_type>(
            example.constraint_system_,
            example.primary_input_,
            example.auxiliary_input_,
            parameters);

        printf("\n");

        print_indent(); printf("* Argument size in bytes (IOP): %zu\n", proof.IOP_size_in_bytes());
        print_indent(); printf("* Argument size in bytes (BCS): %zu\n", proof.BCS_size_in_bytes());
        print_indent(); printf("* Argument size in bytes (total): %zu\n", proof.size_in_bytes());

        printf("\nIf we were to remove pruning of authentication paths in BCS,\n"
               "the argument would have the following sizes:\n");
        print_indent(); printf("* Argument size in bytes (BCS, no pruning): %zu\n", proof.BCS_size_in_bytes_without_pruning());
        print_indent(); printf("* Argument size in bytes (total, no pruning): %zu\n", proof.size_in_bytes_without_pruning());

        printf("\n");

        const bool bit = ligero_snark_verifier<FieldT, hash_type>(
            example.constraint_system_,
            example.primary_input_,
            proof,
            parameters);

        printf("\n\n");

        print_indent(); printf("* Verifier satisfied: %s\n", bit ? "true" : "false");
    }
}

int main(int argc, const char * argv[])
{

    options default_vals;

    float height_width_ratio = 0.1;
    std::size_t RS_extra_dimensions = 2;

#ifdef CPPDEBUG
    /* set reasonable defaults */

#else
    if (!process_prover_command_line(argc, argv, default_vals, height_width_ratio, RS_extra_dimensions))
    {
        return 1;
    }
#endif

    /** TODO: eventually get a string from program options, and then have a from string method in LDT reducer */
    LDT_reducer_soundness_type ldt_reducer_soundness_type = LDT_reducer_soundness_type::proven;
    if (default_vals.heuristic_ldt_reducer_soundness)
    {
        ldt_reducer_soundness_type = LDT_reducer_soundness_type::optimistic_heuristic;
    }
    start_profiling();

    printf("Selected parameters:\n");
    printf("- log_n_min = %zu\n", default_vals.log_n_min);
    printf("- log_n_max = %zu\n", default_vals.log_n_max);
    printf("- height_width_ratio = %f\n", height_width_ratio);
    printf("- RS_extra_dimensions = %zu\n", RS_extra_dimensions);
    printf("- security_level = %zu\n", default_vals.security_level);
    printf("- LDT_reducer_soundness_type = %s\n", LDT_reducer_soundness_type_to_string(ldt_reducer_soundness_type));
    printf("- field_size = %zu\n", default_vals.field_size);
    printf("- make_zk = %d\n", default_vals.make_zk);
    printf("- hash_enum = %s\n", bcs_hash_type_names[default_vals.hash_enum]);

    if (default_vals.is_multiplicative)
    {
        switch (default_vals.field_size) {
            case 181:
                edwards_pp::init_public_params();
                instrument_ligero_snark<edwards_Fr, binary_hash_digest>(
                                        default_vals, ldt_reducer_soundness_type, multiplicative_coset_type,
                                        height_width_ratio, RS_extra_dimensions);
                break;
            case 256:
                libff::alt_bn128_pp::init_public_params();
                if (default_vals.hash_enum == blake2b_type)
                {
                    instrument_ligero_snark<libff::alt_bn128_Fr, binary_hash_digest>(
                                            default_vals, ldt_reducer_soundness_type, multiplicative_coset_type,
                                            height_width_ratio, RS_extra_dimensions);
                }
                else
                {
                    instrument_ligero_snark<libff::alt_bn128_Fr, libff::alt_bn128_Fr>(
                                            default_vals, ldt_reducer_soundness_type, multiplicative_coset_type,
                                            height_width_ratio, RS_extra_dimensions);
                }
                break;
            default:
                throw std::invalid_argument("Field size not supported.");
        }
    }
    else
    {
        switch (default_vals.field_size)
        {
            case 64:
                instrument_ligero_snark<gf64, binary_hash_digest>(
                                        default_vals, ldt_reducer_soundness_type, affine_subspace_type,
                                        height_width_ratio, RS_extra_dimensions);
                break;
            case 128:
                instrument_ligero_snark<gf128, binary_hash_digest>(
                                        default_vals, ldt_reducer_soundness_type, affine_subspace_type,
                                        height_width_ratio, RS_extra_dimensions);
                break;
            case 192:
                instrument_ligero_snark<gf192, binary_hash_digest>(
                                        default_vals, ldt_reducer_soundness_type, affine_subspace_type,
                                        height_width_ratio, RS_extra_dimensions);
                break;
            case 256:
                instrument_ligero_snark<gf256, binary_hash_digest>(
                                        default_vals, ldt_reducer_soundness_type, affine_subspace_type,
                                        height_width_ratio, RS_extra_dimensions);
                break;
            default:
                throw std::invalid_argument("Field size not supported.");
        }
    }
}


#include <cstdlib>
#include <memory>
#include <vector>

#include <Eigen/Core>
#include <eigen-checks/gtest.h>
#include <glog/logging.h>
#include <gtest/gtest.h>

#include <aslam/cameras/camera.h>
#include <aslam/cameras/distortion.h>
#include <aslam/cameras/camera-pinhole.h>
#include <aslam/cameras/distortion-fisheye.h>
#include <aslam/common/entrypoint.h>
#include <aslam/common/macros.h>
#include <aslam/common/pose-types.h>

#include "aslam/geometric-vision/pnp-pose-estimator.h"

class VariableCameraAngle : public ::testing::TestWithParam<double> {
 public:
  typedef aslam::FisheyeDistortion DistortionType;
  typedef aslam::PinholeCamera CameraType;

  std::shared_ptr<CameraType> createCamera();
};

std::shared_ptr<VariableCameraAngle::CameraType>
VariableCameraAngle::createCamera() {
  const double distortion_param = 0.95;
  const double fu = 200;
  const double fv = 200;
  const unsigned int ru = 640;
  const unsigned int rv = 480;
  const double cu = ru / 2;
  const double cv = rv / 2;
  std::shared_ptr<CameraType> camera;
  Eigen::VectorXd distortion_params(1);
  distortion_params << distortion_param;
  aslam::Distortion::UniquePtr distortion(
      new DistortionType(distortion_params));

  Eigen::VectorXd intrinsics(4);
  intrinsics << fu, fv, cu, cv;

  camera.reset(new CameraType(intrinsics, ru, rv, distortion));
  CHECK(camera);

  aslam::CameraId cam_id;
  cam_id.randomize();
  camera->setId(cam_id);

  return camera;
}

INSTANTIATE_TEST_CASE_P(OpengvPoseEstimation, VariableCameraAngle,
                        ::testing::Values(0, M_PI / 6.0, M_PI / 2.0,
                                          -M_PI / 2.0, -M_PI / 6.0));

TEST_P(VariableCameraAngle, PinholeCameraP3pInterface) {
  // To make RANSAC deterministic.
  srand(1);

  // Force RANSAC to not use a random seed.
  constexpr bool kNonlinearRefinement = true;
  constexpr bool kRandomSeed = false;
  aslam::geometric_vision::PnpPoseEstimator pose_estimator(kNonlinearRefinement,
                                                           kRandomSeed);

  std::shared_ptr<CameraType> camera = createCamera();

  Eigen::Quaterniond q_G_C(
      Eigen::AngleAxisd(GetParam(), Eigen::Vector3d::UnitY()));
  Eigen::Matrix3d R_G_C = q_G_C.toRotationMatrix();
  const Eigen::Vector3d p_G_C(1, 2, 3);

  const unsigned int num_of_points = 200;
  Eigen::Matrix2Xd measurements;
  Eigen::Matrix3Xd G_landmark_positions;
  measurements.resize(Eigen::NoChange, num_of_points);
  G_landmark_positions.resize(Eigen::NoChange, num_of_points);
  for (unsigned int i = 0; i < num_of_points; ++i) {
    // We need to vary the depth -- plane may not be enough to recover
    // the camera pose.
    Eigen::Vector3d p_C_fi = camera->createRandomVisiblePoint(i + 50);
    Eigen::Vector2d keypoint_measurement;
    camera->project3(p_C_fi, &keypoint_measurement);
    measurements.col(i) = keypoint_measurement;
    G_landmark_positions.col(i) = R_G_C * p_C_fi + p_G_C;
  }
  std::vector<int> inliers;
  int num_iters;

  aslam::Transformation T_G_C;
  pose_estimator.absolutePoseRansacPinholeCam(
      measurements, G_landmark_positions, 0.8, 500, camera, &T_G_C, &inliers,
      &num_iters);

  EXPECT_TRUE(EIGEN_MATRIX_NEAR(T_G_C.getPosition(), p_G_C, 1e-5));
  EXPECT_TRUE(EIGEN_MATRIX_NEAR(T_G_C.getRotation().toImplementation().coeffs(),
                                q_G_C.coeffs(), 1e-9));
}

TEST_P(VariableCameraAngle, MultiPinholeCameraP3pInterface) {
  // To make RANSAC deterministic.
  srand(1);

  // Force RANSAC to not use a random seed.
  constexpr bool kNonlinearRefinement = true;
  constexpr bool kRandomSeed = false;
  aslam::geometric_vision::PnpPoseEstimator pose_estimator(kNonlinearRefinement,
                                                           kRandomSeed);

  // Create a complete NCamera object.
  aslam::NCameraId cam_id;
  cam_id.randomize();

  // Cameras.
  std::vector<aslam::Camera::Ptr> cameras;
  aslam::TransformationVector T_C_Bs;

  // Create a multi-camera system.
  const int num_cams = 2;
  for (int i = 0; i < num_cams; ++i) {
    cameras.push_back(createCamera());

    // Extrinsics - have the cameras be translated and slightly rotated from
    // each other.
    aslam::Quaternion q_C_B(
        Eigen::AngleAxisd(M_PI / 6.0 * i, Eigen::Vector3d::UnitY()));
    aslam::Position3D p_C_B(2 * i - 1, i - 1, i * 5);
    T_C_Bs.emplace_back(q_C_B, p_C_B);
  }

  aslam::NCamera::Ptr ncameras(
      new aslam::NCamera(cam_id, T_C_Bs, cameras, "testrig"));

  Eigen::Quaterniond q_G_B(
      Eigen::AngleAxisd(GetParam(), Eigen::Vector3d::UnitY()));
  const Eigen::Vector3d p_G_B(1, 2, 3);
  aslam::Transformation T_G_B(q_G_B, p_G_B);

  const unsigned int num_of_points = 800;
  const unsigned int num_of_outliers = 20;
  Eigen::Matrix2Xd measurements(2, num_of_points);
  Eigen::Matrix3Xd G_landmark_positions(3, num_of_points);
  std::vector<int> measurement_camera_indices(num_of_points);
  for (unsigned int i = 0; i < num_of_points; ++i) {
    // Same as in the single-camera case, but this time also do half the points
    // in each camera.
    int cam_index = i % num_cams;

    // We need to vary the depth -- plane may not be enough to recover
    // the camera pose.
    Eigen::Vector3d p_C_fi =
        ncameras->getCamera(cam_index).createRandomVisiblePoint(i + 50);
    Eigen::Vector2d keypoint_measurement;
    ncameras->getCamera(cam_index).project3(p_C_fi, &keypoint_measurement);
    const aslam::Transformation& T_C_B = ncameras->get_T_C_B(cam_index);
    aslam::Transformation T_G_C = T_G_B * T_C_B.inverse();

    measurement_camera_indices[i] = cam_index;
    measurements.col(i) = keypoint_measurement;
    if (i >= num_of_outliers) {
      G_landmark_positions.col(i) = T_G_C * p_C_fi;
    } else {
      // If this should be an outlier, just give it a random position instead
      // of the projected 3D position.
      G_landmark_positions.col(i) =
          T_G_C * Eigen::Vector3d(i / 10, i / 4, (i - 1) / 3);
    }
  }
  std::vector<int> inliers;
  int num_iters;

  aslam::Transformation T_G_B_out;
  pose_estimator.absoluteMultiPoseRansacPinholeCam(
      measurements, measurement_camera_indices, G_landmark_positions, 0.8, 500,
      ncameras, &T_G_B_out, &inliers, &num_iters);

  EXPECT_TRUE(EIGEN_MATRIX_NEAR(T_G_B_out.getPosition(), p_G_B, 1e-5));
  EXPECT_TRUE(EIGEN_MATRIX_NEAR(T_G_B_out.getRotation().toImplementation().coeffs(),
                                q_G_B.coeffs(), 1e-5));
  EXPECT_EQ(inliers.size(), num_of_points - num_of_outliers);
}

ASLAM_UNITTEST_ENTRYPOINT


#include "cnn/rnn.h"
#include <boost/lexical_cast.hpp>
#include <string>
#include <cassert>
#include <vector>
#include <iostream>

#include "cnn/nodes.h"
#include "cnn/expr-xtra.h"

using namespace std;
using namespace cnn::expr;
using namespace cnn;

namespace cnn {

enum { X2H=0, H2H, HB, L2H };

RNNBuilder::~RNNBuilder() {}

void RNNBuilder::display(ComputationGraph& cg) {

    for (unsigned i = 0; i < layers; ++i) {
        const std::vector<Expression>& vars = param_vars[i];
        for (size_t i = 0; i < vars.size(); i++)
            display_value(vars[i], cg);
    }
}

SimpleRNNBuilder::SimpleRNNBuilder(unsigned ilayers,
                       const vector<unsigned>& dims,
                       Model* model,
                       cnn::real iscale,
                       string name,
                       bool support_lags) : lagging(support_lags)
{
  unsigned input_dim = dims[INPUT_LAYER];
  unsigned hidden_dim = dims[HIDDEN_LAYER];
  string i_name;
  layers = ilayers;
  unsigned layer_input_dim = input_dim;
  input_dims = vector<unsigned>(layers, layer_input_dim);

  for (unsigned i = 0; i < layers; ++i) {
    input_dims[i] = layer_input_dim;

    if (name.size() > 0)
        i_name = name + "p_x2h" + boost::lexical_cast<string>(i);
    Parameters* p_x2h = model->add_parameters({ hidden_dim, layer_input_dim }, iscale, i_name);
    if (name.size() > 0)
        i_name = name + "p_h2h" + boost::lexical_cast<string>(i);
    Parameters* p_h2h = model->add_parameters({ hidden_dim, hidden_dim }, iscale, i_name);
    if (name.size() > 0)
        i_name = name + "p_hb" + boost::lexical_cast<string>(i);
    Parameters* p_hb = model->add_parameters({ hidden_dim }, iscale, i_name);
    vector<Parameters*> ps = {p_x2h, p_h2h, p_hb};
    if (lagging)
    {
        if (name.size() > 0)
            i_name = name + "lagging" + boost::lexical_cast<string>(i);
        ps.push_back(model->add_parameters({ hidden_dim, hidden_dim }, iscale, i_name));
    }
    params.push_back(ps);
    layer_input_dim = hidden_dim;
  }
}

void SimpleRNNBuilder::new_graph_impl(ComputationGraph& cg) {
  param_vars.clear();
  for (unsigned i = 0; i < layers; ++i) {
    Parameters* p_x2h = params[i][X2H];
    Parameters* p_h2h = params[i][H2H];
    Parameters* p_hb = params[i][HB];
    Expression i_x2h =  parameter(cg,p_x2h);
    Expression i_h2h =  parameter(cg,p_h2h);
    Expression i_hb =  parameter(cg,p_hb);
    vector<Expression> vars = {i_x2h, i_h2h, i_hb};

    if (lagging) {
        Parameters* p_l2h = params[i][L2H];
        Expression i_l2h =  parameter(cg,p_l2h);
        vars.push_back(i_l2h);
    }

    param_vars.push_back(vars);
  }
  set_data_in_parallel(data_in_parallel());
}

void SimpleRNNBuilder::set_data_in_parallel(int n)
{
    RNNBuilder::set_data_in_parallel(n);

    biases.clear();
    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];
        Expression bimb = concatenate_cols(vector<Expression>(data_in_parallel(), vars[2]));
        Expression bcmb = concatenate_cols(vector<Expression>(data_in_parallel(), vars[HB]));

        vector<Expression> b = { bimb,
            bcmb
        };
        biases.push_back(b);
    }
}

void SimpleRNNBuilder::start_new_sequence_impl(const vector<Expression>& h_0) {
  h.clear();
  h0 = h_0;
  if (h0.size()) { assert(h0.size() == layers); }
}

Expression SimpleRNNBuilder::add_input_impl(int prev, const Expression &in) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    Expression x = in;

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];

        Expression y = affine_transform({ biases[i][0], vars[0], x });

        if (prev == -1 && h0.size() > 0)
            y = y + vars[1] * h0[i];
        else if (prev >= 0)
            y = y + vars[1] * h[prev][i];

        x = h[t][i] = tanh(y);
    }
    return h[t].back();
}

Expression SimpleRNNBuilder::add_input_impl(int prev, const std::vector<Expression> &in) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    assert(in.size() == layers);
    Expression x = in[0];

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];
        Expression x_additional;
        Expression y = affine_transform({ biases[i][0], vars[0], x });

        if (prev == -1 && h0.size() > 0)
            y = y + vars[1] * h0[i];
        else if (prev >= 0)
            y = y + vars[1] * h[prev][i];
        Expression z = tanh(y);
        if (i > 0)
        {
            z = z + in[i];
        }
        x = h[t][i] = z;
    }
    return h[t].back();
}

Expression SimpleRNNBuilder::add_input_impl(const vector<Expression>& prev_history, const Expression &in) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    Expression x = in;

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];

        //    Expression y = affine_transform({ vars[2], vars[0], x });
        Expression y = affine_transform({ biases[i][0], vars[0], x });

        if (prev_history.size() == layers)
            y = y + vars[1] * prev_history[i];

        x = h[t][i] = tanh(y);
    }
    return h[t].back();
}

Expression SimpleRNNBuilder::add_auxiliary_input(const Expression &in, const Expression &aux) {
  const unsigned t = h.size();
  h.push_back(vector<Expression>(layers));

  Expression x = in;

  for (unsigned i = 0; i < layers; ++i) {
    const vector<Expression>& vars = param_vars[i];
    assert(vars.size() >= L2H + 1);

//    Expression y = affine_transform({ vars[HB], vars[X2H], x, vars[L2H], aux });
    Expression y = affine_transform({ biases[i][1], vars[X2H], x, vars[L2H], aux });

    if (t == 0 && h0.size() > 0)
      y = y + vars[H2H] * h0[i];
    else if (t >= 1)
      y = y + vars[H2H] * h[t-1][i];

    x = h[t][i] = tanh(y);
  }
  return h[t].back();
}

void SimpleRNNBuilder::copy(const RNNBuilder & rnn) {
  const SimpleRNNBuilder & rnn_simple = (const SimpleRNNBuilder&)rnn;
  assert(params.size() == rnn_simple.params.size());
  for(size_t i = 0; i < rnn_simple.params.size(); ++i) {
      params[i][0]->copy(*rnn_simple.params[i][0]);
      params[i][1]->copy(*rnn_simple.params[i][1]);
      params[i][2]->copy(*rnn_simple.params[i][2]);
  }
}

Expression SimpleRNNBuilderWithELU::add_input_impl(int prev, const Expression &in) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    Expression x = in;

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];

        Expression y = affine_transform({ biases[i][0], vars[0], x });

        if (prev == -1 && h0.size() > 0)
            y = y + vars[1] * h0[i];
        else if (prev >= 0)
            y = y + vars[1] * h[prev][i];

        x = h[t][i] = exponential_linear_units(y);
    }
    return h[t].back();
}

Expression SimpleRNNBuilderWithELU::add_input_impl(int prev, const std::vector<Expression> &in) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    assert(in.size() == layers);
    Expression x = in[0];

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];
        Expression x_additional;
        Expression y = affine_transform({ biases[i][0], vars[0], x });

        if (prev == -1 && h0.size() > 0)
            y = y + vars[1] * h0[i];
        else if (prev >= 0)
            y = y + vars[1] * h[prev][i];
        Expression z = exponential_linear_units(y);
        if (i > 0)
        {
            z = z + in[i];
        }
        x = h[t][i] = z;
    }
    return h[t].back();
}

Expression SimpleRNNBuilderWithELU::add_input_impl(const vector<Expression>& prev_history, const Expression &in) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    Expression x = in;

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];

        //    Expression y = affine_transform({ vars[2], vars[0], x });
        Expression y = affine_transform({ biases[i][0], vars[0], x });

        if (prev_history.size() == layers)
            y = y + vars[1] * prev_history[i];

        x = h[t][i] = exponential_linear_units(y);
    }
    return h[t].back();
}

Expression SimpleRNNBuilderWithELU::add_auxiliary_input(const Expression &in, const Expression &aux) {
    const unsigned t = h.size();
    h.push_back(vector<Expression>(layers));

    Expression x = in;

    for (unsigned i = 0; i < layers; ++i) {
        const vector<Expression>& vars = param_vars[i];
        assert(vars.size() >= L2H + 1);

        //    Expression y = affine_transform({ vars[HB], vars[X2H], x, vars[L2H], aux });
        Expression y = affine_transform({ biases[i][1], vars[X2H], x, vars[L2H], aux });

        if (t == 0 && h0.size() > 0)
            y = y + vars[H2H] * h0[i];
        else if (t >= 1)
            y = y + vars[H2H] * h[t - 1][i];

        x = h[t][i] = exponential_linear_units(y);
    }
    return h[t].back();
}

} // namespace cnn


////////////////////////////////////////////////////////////////////////////////
//  Copyright (c) 2011 Bryce Lelbach
//
//  Distributed under 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)
////////////////////////////////////////////////////////////////////////////////

// Naive SMP version implemented with futures.

#include <hpx/hpx.hpp>
#include <hpx/hpx_init.hpp>
//#include <hpx/include/actions.hpp>
#include <hpx/include/util.hpp>
#include <iostream>
#include <boost/cstdint.hpp>
#include <boost/format.hpp>

boost::uint64_t threshold = 2;

boost::uint64_t fibonacci(boost::uint64_t n) {
    if (n < 2)
        return n;
    if (n < threshold)
        return fibonacci(n-1) + fibonacci(n-2);
    hpx::future<boost::uint64_t> n1 = hpx::async(fibonacci, n - 1);
    hpx::future<boost::uint64_t> n2 = hpx::async(fibonacci, n - 2);

    return n1.get() + n2.get();
}

int hpx_main(boost::program_options::variables_map& vm)
{
    boost::uint64_t n = vm["n-value"].as<boost::uint64_t>();
    threshold = vm["threshold"].as<unsigned int>();
    {
        hpx::util::high_resolution_timer t;
        boost::uint64_t r = fibonacci(n);
        char const* fmt = "fibonacci(%1%) == %2%\nelapsed time: %3% [s]\n";
        std::cout << (boost::format(fmt) % n % r % t.elapsed());
    }
    return hpx::finalize(); // Handles HPX shutdown
}

int main(int argc, char* argv[])
{
    boost::program_options::options_description
       desc_commandline("Usage: " HPX_APPLICATION_STRING " [options]");

    using boost::program_options::value;
    desc_commandline.add_options()
        ( "n-value",
          value<boost::uint64_t>()->default_value(10),
          "n value for the Fibonacci function")
        ( "threshold", value<unsigned int>()->default_value(2),
          "threshold for switching to serial code");

    return hpx::init(desc_commandline, argc, argv);
}


#pragma once

#include "halfedge.hpp"
#include "entity_containers.hpp"

#include <vector>
#include <array>
#include <cassert>
#include <string>

#include <Eigen/Dense>

namespace Discregrid
{

class TriangleMesh
{

public:

	TriangleMesh(std::vector<Eigen::Vector3d> const& vertices,
		std::vector<std::array<unsigned int, 3>> const& faces);

	TriangleMesh(double const* vertices,
		unsigned int const* faces,
		std::size_t nv, std::size_t nf);

	TriangleMesh(std::string const& filename);

	void exportOBJ(std::string const& filename) const;

	// Halfedge modifiers.
	unsigned int source(Halfedge const h) const
	{
		if (h.isBoundary()) return target(opposite(h));
		return m_faces[h.face()][h.edge()];
	}
	unsigned int target(Halfedge const h) const
	{
		if (h.isBoundary()) return source(opposite(h));
		return source(h.next());
	}
	Halfedge opposite(Halfedge const h) const
	{
		if (h.isBoundary()) return m_b2e[h.face()];
		return m_e2e[h.face()][h.edge()];
	}

	// Container getters.
	FaceContainer faces() { return FaceContainer(this); }
	FaceConstContainer faces() const { return FaceConstContainer(this); }
	IncidentFaceContainer incident_faces(unsigned int v) const {
		return IncidentFaceContainer(v, this); }
	VertexContainer vertices() { return VertexContainer(this); }
	VertexConstContainer vertices() const { return VertexConstContainer(this); }

	// Entity size getters.
	std::size_t nFaces() const { return m_faces.size(); }
	std::size_t nVertices() const { return m_v2e.size(); }
	std::size_t nBorderEdges() const { return m_b2e.size(); }

	// Entity getters.
	unsigned int const& faceVertex(unsigned int f, unsigned int i) const
	{
		assert(i < 3);
		assert(f < m_faces.size());
		return m_faces[f][i];
	}
	unsigned int& faceVertex(unsigned int f, unsigned int i)
	{
		assert(i < 3);
		assert(f < m_faces.size());
		return m_faces[f][i];
	}

	Eigen::Vector3d const& vertex(unsigned int i) const { return m_vertices[i]; }
	Eigen::Vector3d& vertex(unsigned int i) { return m_vertices[i]; }
	std::array<unsigned int, 3> const& face(unsigned int i) const {
		return m_faces[i]; }
	std::array<unsigned int, 3>& face(unsigned int i) {
		return m_faces[i];
	}
	Halfedge incident_halfedge(unsigned int v) const { return m_v2e[v]; }

	// Data getters.
	std::vector<Eigen::Vector3d> const& vertex_data() const {
		return m_vertices; }
	std::vector<Eigen::Vector3d>& vertex_data() { return m_vertices; }
	std::vector<std::array<unsigned int, 3>> const& face_data() const {
		return m_faces; }
	std::vector<std::array<unsigned int, 3>>& face_data() { return m_faces; }

	Eigen::Vector3d computeFaceNormal(unsigned int f) const;

private:

	void construct();

private:

	std::vector<Eigen::Vector3d> m_vertices;
	std::vector<std::array<unsigned int, 3>> m_faces;
	std::vector<std::array<Halfedge, 3>> m_e2e;
	std::vector<Halfedge> m_v2e;
	std::vector<Halfedge> m_b2e;
};
}


#pragma once

#include "protocols/protocol.hpp"
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/median.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <fstream>
#include <map>
#include <memory>
#include <string_view>

/**
 * Stores test results of a single run with a chosen error rate and protocol.
 *
 * Tests are always done with a specified error rate and
 * protocol to use. Protocols then write statistics about the runs in here.
 */
class TestResults {
public:
  explicit TestResults(std::string protocolName);
  virtual ~TestResults();

  /** Adds result to internal accumulator.
   *
   * The probability must be 1000 * p, so 1 % (or p = 0.01) becomes 10.
   */
  void addResult(int errorPropability, int trips, bool successfulTransmission,
                 bool correctDiagnosis);

  void writeResults(std::ofstream &out);

  static std::unique_ptr<TestResults> startAutomatedTest(protocol::Protocol &p);

  std::string_view name();

private:
  using IntAccumulatorSet = boost::accumulators::accumulator_set<
      int, boost::accumulators::features<
               boost::accumulators::tag::min, boost::accumulators::tag::max,
               boost::accumulators::tag::mean, boost::accumulators::tag::median,
               boost::accumulators::tag::variance>>;
  using FloatAccumulatorSet = boost::accumulators::accumulator_set<
      float, boost::accumulators::features<boost::accumulators::tag::mean,
                                           boost::accumulators::tag::median>>;

  struct Result {
    IntAccumulatorSet trips;
    FloatAccumulatorSet successfulTransmissions;
    FloatAccumulatorSet correctDiagnosis;
  };
  std::map<int, Result> m_results;
  std::string m_protocolName;
};