/*    Copyright (c) 2010-2018, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 */

#define BOOST_TEST_MAIN

#include <limits>

#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>

#include "Tudat/Astrodynamics/BasicAstrodynamics/physicalConstants.h"

#include "Tudat/Astrodynamics/BasicAstrodynamics/timeConversions.h"

namespace tudat
{
namespace unit_tests
{

using namespace basic_astrodynamics;
using namespace physical_constants;

//! Test the functionality of the time conversion functions.
BOOST_AUTO_TEST_SUITE( test_Time_Conversions )

//! Unit test for Julian day to seconds conversion function.
BOOST_AUTO_TEST_CASE( testJulianDayToSecondsConversions )
{
    // Test conversion from Julian day to seconds since epoch at 0 MJD.
    {
        // Set reference epoch and Julian day for tests.
        const double referenceEpoch = JULIAN_DAY_AT_0_MJD;
        const double julianDay = JULIAN_DAY_AT_0_MJD + 1.0e6 / 86400.0;

        // Set expected seconds since epoch result.
        const double expectedSecondsSinceEpoch = 1.0e6;

        // Compute seconds since epoch given Julian day and reference epoch.
        const double computedSecondsSinceEpoch = convertJulianDayToSecondsSinceEpoch(
                    julianDay, referenceEpoch );

        // Test that computed result matches expected result.
        // Test is run at reduced tolerance, because the final digits of the seconds were lost
        // when converting to Julian day.
        BOOST_CHECK_CLOSE_FRACTION( computedSecondsSinceEpoch, expectedSecondsSinceEpoch,
                                    1.0e-11 );
    }

    // Test conversion from Julian day to seconds since J2000 epoch.
    {
        // Set reference epoch and Julian day for tests.
        const double referenceEpoch = JULIAN_DAY_ON_J2000;
        const double julianDay = JULIAN_DAY_ON_J2000 + 0.5;

        // Set expected seconds since epoch result.
        double expectedSecondsSinceEpoch
                = physical_constants::JULIAN_DAY / 2.0;

        // Compute seconds since epoch given Julian day and reference epoch.
        const double computedSecondsSinceEpoch = convertJulianDayToSecondsSinceEpoch(
                    julianDay, referenceEpoch );

        // Test that computed result matches expected result.
        // Test is run at reduced tolerance, because the final digits of the seconds were lost
        // when converting to Julian day.
        BOOST_CHECK_CLOSE_FRACTION( computedSecondsSinceEpoch, expectedSecondsSinceEpoch,
                                    std::numeric_limits< double >::epsilon( ) );
    }

    // Test conversion from Julian day to seconds since epoch at 0 MJD with long doubles.
    {
        // Set reference epoch and Julian day for tests.
        const long double referenceEpoch = JULIAN_DAY_AT_0_MJD_LONG;
        const long double julianDay = JULIAN_DAY_AT_0_MJD_LONG + 1.0e6L / 86400.0L;

        // Set expected seconds since epoch result.
        const long double expectedSecondsSinceEpoch = 1.0e6L;

        // Compute seconds since epoch given Julian day and reference epoch.
        const long double computedSecondsSinceEpoch = convertJulianDayToSecondsSinceEpoch< long double >(
                    julianDay, referenceEpoch );

        // Test that computed result matches expected result.
        // Test is run at reduced tolerance, because the final digits of the seconds were lost
        // when converting to Julian day.
        BOOST_CHECK_CLOSE_FRACTION( computedSecondsSinceEpoch, expectedSecondsSinceEpoch,
                                    1.0e-14 );
    }

    // Test conversion from Julian day to seconds since J2000 epoch  with long doubles
    {
        // Set reference epoch and Julian day for tests.
        const long double referenceEpoch = JULIAN_DAY_ON_J2000_LONG;
        const long double julianDay = JULIAN_DAY_ON_J2000_LONG + 0.5L;

        // Set expected seconds since epoch result.
        long double expectedSecondsSinceEpoch
                = physical_constants::JULIAN_DAY_LONG / 2.0L;

        // Compute seconds since epoch given Julian day and reference epoch.
        const long double computedSecondsSinceEpoch = convertJulianDayToSecondsSinceEpoch< long double >(
                    julianDay, referenceEpoch );

        // Test that computed result matches expected result.
        // Test is run at reduced tolerance, because the final digits of the seconds were lost
        // when converting to Julian day.
        BOOST_CHECK_CLOSE_FRACTION( computedSecondsSinceEpoch, expectedSecondsSinceEpoch,
                                    std::numeric_limits< long double >::epsilon( ) );
    }
}

//! Unit test for seconds to Julian day conversion function.
BOOST_AUTO_TEST_CASE( testSecondsSinceEpochToJulianDayConversions )
{
    {
        // Test conversion from seconds since epoch to Julian day.

        // Set reference epoch and seconds since epoch for tests.
        const double referenceEpoch = JULIAN_DAY_AT_0_MJD;
        const double secondsSinceEpoch = 1.0e6;

        // Set expected Julian day result.
        const double expectedJulianDay = JULIAN_DAY_AT_0_MJD + 1.0e6 / 86400.0;

        // Compute Julian day with seconds since reference epoch specified.
        const double computedJulianDay = convertSecondsSinceEpochToJulianDay(
                    secondsSinceEpoch, referenceEpoch );

        // Test that computed result matches expected result.
        BOOST_CHECK_CLOSE_FRACTION( computedJulianDay, expectedJulianDay,
                                    std::numeric_limits< double >::epsilon( ) );
    }

    {
        // Test conversion from seconds since epoch to Julian day with long doubles.

        // Set reference epoch and seconds since epoch for tests.
        const long double referenceEpoch = JULIAN_DAY_AT_0_MJD_LONG;
        const long double secondsSinceEpoch = 1.0e6L;

        // Set expected Julian day result.
        const long double expectedJulianDay = JULIAN_DAY_AT_0_MJD_LONG + 1.0e6L / 86400.0L;

        // Compute Julian day with seconds since reference epoch specified.
        const long double computedJulianDay = convertSecondsSinceEpochToJulianDay< long double >(
                    secondsSinceEpoch, referenceEpoch );

        // Test that computed result matches expected result.
        BOOST_CHECK_CLOSE_FRACTION( computedJulianDay, expectedJulianDay,
                                    std::numeric_limits< long double >::epsilon( ) );
    }
}

//! Unit test for calendar date to Julian day conversion function.
BOOST_AUTO_TEST_CASE( testConversionCalendarDateToJulianDay )
{
    // Compute the Julian day of the calendar date: January 1st, 2000, at 12h0m0s.
    {
        //Use the function to compute the Julian day.
        const double computedJulianDay = convertCalendarDateToJulianDay ( 2000, 1, 1, 12, 0, 0 );

        //Known Julian day at this calendar date.
        const double expectedJulianDay = basic_astrodynamics::JULIAN_DAY_ON_J2000;

        // Test that computed result matches expected result.
        BOOST_CHECK_CLOSE_FRACTION( computedJulianDay, expectedJulianDay,
                                    std::numeric_limits< double >::epsilon( ) );
    }

    //Compute the Julian day of the calendar date: November 17th, 1858, At 0h0m0s.
    {

        //Use the function to compute the Julian day.
        const double computedJulianDay = convertCalendarDateToJulianDay(
                    1858, 11, 17, 0, 0, 0.0 );

        //Known Julian day at this calendar date
        const double expectedJulianDay = basic_astrodynamics::JULIAN_DAY_AT_0_MJD;

        // Test that computed result matches expected result.
        BOOST_CHECK_CLOSE_FRACTION( computedJulianDay, expectedJulianDay,
                                    std::numeric_limits< double >::epsilon( ) );

    }

    //Test conversion wrapper against boost result.
    {
        const int year = 1749;
        const int month = 3;
        const int day = 30;

        BOOST_CHECK_CLOSE_FRACTION( boost::gregorian::date( year, month, day ).julian_day( ) - 0.5,
                                    convertCalendarDateToJulianDay( year, month, day, 0, 0, 0.0 ),
                                    std::numeric_limits< double >::epsilon( ) );
        const int hour = 4;
        BOOST_CHECK_CLOSE_FRACTION( boost::gregorian::date( year, month, day ).julian_day( ) - 0.5 +
                                    static_cast< double >( hour ) / 24.0,
                                    convertCalendarDateToJulianDay( year, month, day, hour, 0, 0.0 ),
                                    std::numeric_limits< double >::epsilon( ) );

        const int minute = 36;
        BOOST_CHECK_CLOSE_FRACTION( boost::gregorian::date( year, month, day ).julian_day( ) - 0.5 +
                                    static_cast< double >( hour ) / 24.0 + static_cast< double >( minute ) / ( 24.0 * 60.0 ),
                                    convertCalendarDateToJulianDay( year, month, day, hour, minute, 0.0 ),
                                    std::numeric_limits< double >::epsilon( ) );

        const double second = 21.36474854836359;
        BOOST_CHECK_CLOSE_FRACTION( boost::gregorian::date( year, month, day ).julian_day( ) - 0.5 +
                                    static_cast< double >( hour ) / 24.0 +
                                    static_cast< double >( minute ) / ( 24.0 * 60.0 ) +
                                    second / ( 24.0 * 60.0 * 60.0 ),
                                    convertCalendarDateToJulianDay( year, month, day, hour, minute, second ),
                                    std::numeric_limits< double >::epsilon( ) );

    }
}

BOOST_AUTO_TEST_CASE( testTimeConversions )
{
    const double testModifiedJulianDay = 54583.87;
    const double testJulianDay = testModifiedJulianDay + JULIAN_DAY_AT_0_MJD;
    {
        // Test conversions to/from Modified Julian day

        BOOST_CHECK_CLOSE_FRACTION( testJulianDay, convertModifiedJulianDayToJulianDay( testModifiedJulianDay ),
                                    2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( testModifiedJulianDay, convertJulianDayToModifiedJulianDay( testJulianDay ),
                                    10.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( convertJulianDayToModifiedJulianDay(
                                        convertModifiedJulianDayToJulianDay( testModifiedJulianDay ) ),
                                    testModifiedJulianDay, 10.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( convertModifiedJulianDayToJulianDay(
                                        convertJulianDayToModifiedJulianDay( testJulianDay ) ),
                                    testJulianDay, 2.0 * std::numeric_limits< double >::epsilon( ) );

        // Test conversions to/from seconds since epoch
        double secondsSinceModifedJulianDayZero = testModifiedJulianDay * JULIAN_DAY;
        BOOST_CHECK_CLOSE_FRACTION( secondsSinceModifedJulianDayZero, convertJulianDayToSecondsSinceEpoch(
                                        testJulianDay, JULIAN_DAY_AT_0_MJD ),
                                    10.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( testJulianDay, convertSecondsSinceEpochToJulianDay(
                                        secondsSinceModifedJulianDayZero, JULIAN_DAY_AT_0_MJD ),
                                    2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( testModifiedJulianDay /  JULIAN_YEAR_IN_DAYS,
                                    convertSecondsSinceEpochToJulianYearsSinceEpoch( secondsSinceModifedJulianDayZero ),
                                    2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( testModifiedJulianDay / (  JULIAN_YEAR_IN_DAYS * 100.0 ),
                                    convertSecondsSinceEpochToJulianCenturiesSinceEpoch( secondsSinceModifedJulianDayZero ),
                                    2.0 * std::numeric_limits< double >::epsilon( ) );
    }

    {
        // Test conversion from Julian day to calendar date
        int testYear = 2008;
        int testMonth = 4;
        int testDay = 27;
        boost::gregorian::date testCalendarDate( testYear, testMonth, testDay );
        double testFractionOfDay = 0.37;

        // Check direct conversion from julian day to calendar date
        boost::gregorian::date calendarDate = convertJulianDayToCalendarDate( testJulianDay );
        BOOST_CHECK_EQUAL( testYear, calendarDate.year( ) );
        BOOST_CHECK_EQUAL( testMonth, calendarDate.month( ) );
        BOOST_CHECK_EQUAL( testDay, calendarDate.day( ) );

        // Check indirect conversion from julian day to calendar date
        BOOST_CHECK_EQUAL( getDaysInMonth( 1, testYear ) +
                           getDaysInMonth( 2, testYear ) +
                           getDaysInMonth( 3, testYear ) +
                           testDay, testCalendarDate.day_of_year( ) );

        calendarDate = convertYearAndDaysInYearToDate(
                    testYear, ( getDaysInMonth( 1, testYear ) +
                                getDaysInMonth( 2, testYear ) +
                                getDaysInMonth( 3, testYear ) +
                                testDay ) - 1 ); // Subtract 1 to go to day 0 for first day of year.
        BOOST_CHECK_EQUAL( testYear, calendarDate.year( ) );
        BOOST_CHECK_EQUAL( testMonth, calendarDate.month( ) );
        BOOST_CHECK_EQUAL( testDay, calendarDate.day( ) );

        // Check if noon reference of Julian day is handled properly
        calendarDate = convertJulianDayToCalendarDate( testJulianDay + 0.5 );
        BOOST_CHECK_EQUAL( testYear, calendarDate.year( ) );
        BOOST_CHECK_EQUAL( testMonth, calendarDate.month( ) );
        BOOST_CHECK_EQUAL( testDay + 1, calendarDate.day( ) );

        BOOST_CHECK_CLOSE_FRACTION( calculateJulianDaySinceEpoch(
                                        testCalendarDate, 0.5 + testFractionOfDay, JULIAN_DAY_AT_0_MJD ),
                                    testModifiedJulianDay,
                                    50.0 * std::numeric_limits< double >::epsilon( ) );
    }

    // Test relativistic time scale conversions (TCG, TT, TDB, TCB) for double precision.
    {
        double secondsSinceJ2000Synchronization = getTimeOfTaiSynchronizationSinceJ2000< double >( );

        // Test whether TCG and TT are both 0 at synchronization time.
        double testTcg = convertTtToTcg( secondsSinceJ2000Synchronization );
        double testTt = convertTcgToTt( secondsSinceJ2000Synchronization );

        BOOST_CHECK_CLOSE_FRACTION( testTcg, secondsSinceJ2000Synchronization,
                                    2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( testTt, secondsSinceJ2000Synchronization,
                                    2.0 * std::numeric_limits< double >::epsilon( ) );

        // Test whether TDB and TCB are both at defined relative offset at given time.
        double testTcb = convertTdbToTcb( secondsSinceJ2000Synchronization );
        double testTdb = convertTcbToTdb( secondsSinceJ2000Synchronization );

        BOOST_CHECK_CLOSE_FRACTION( testTdb, secondsSinceJ2000Synchronization + TDB_SECONDS_OFFSET_AT_SYNCHRONIZATION,
                                    2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_CLOSE_FRACTION( testTcb, secondsSinceJ2000Synchronization - TDB_SECONDS_OFFSET_AT_SYNCHRONIZATION,
                                    2.0 * std::numeric_limits< double >::epsilon( ) );

        // Test back and forth TCG<->TT, and expected value of TCG at TT=0..
        double testTime = 0.0;

        testTcg = convertTtToTcg( testTime);
        testTt = convertTcgToTt( testTcg );

        double expectedTcg = -secondsSinceJ2000Synchronization * LG_TIME_RATE_TERM /
                ( 1.0 - physical_constants::LG_TIME_RATE_TERM_LONG );
        BOOST_CHECK_CLOSE_FRACTION( testTcg, expectedTcg, 2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_SMALL( testTt, std::numeric_limits< double >::epsilon( ) );

        // Test back and forth TDB<->TCB, and expected value of TDB at TCB=0.
        testTdb = convertTcbToTdb( testTime );
        testTcb = convertTdbToTcb( testTdb );

        double expectedTdb = secondsSinceJ2000Synchronization * LB_TIME_RATE_TERM + TDB_SECONDS_OFFSET_AT_SYNCHRONIZATION;

        BOOST_CHECK_CLOSE_FRACTION( testTdb, expectedTdb, 2.0 * std::numeric_limits< double >::epsilon( ) );
        BOOST_CHECK_SMALL( testTcb, 2.0 * std::numeric_limits< double >::epsilon( ) );

        // Test back and forth TCG<->TT.
        double secondsSinceModifedJulianDayZero = testModifiedJulianDay * JULIAN_DAY;
        testTime = secondsSinceModifedJulianDayZero;

        testTcg = convertTtToTcg< double >( testTime );
        testTt = convertTcgToTt< double >( testTcg );

        BOOST_CHECK_CLOSE_FRACTION( testTt, testTime, 2.0 * std::numeric_limits< double >::epsilon( ) );

        // Test back and forth TDB<->TCB.
        testTdb = convertTcbToTdb< long double >( testTime );
        testTcb = convertTdbToTcb< long double >( testTdb );

        BOOST_CHECK_CLOSE_FRACTION( testTcb, testTime, 2.0 * std::numeric_limits< double >::epsilon( ) );

        secondsSinceModifedJulianDayZero = 1.0E12;

        // Test rate difference between TCG and TT
        double testTt1 = 0.0;
        double testTt2 = secondsSinceModifedJulianDayZero;
        double testTcg1 = convertTtToTcg< double >( testTt1 );
        double testTcg2 = convertTtToTcg< double >( testTt2 );
        double computedLg = 1.0 - ( testTt2 - testTt1 ) / ( testTcg2 - testTcg1 );
        BOOST_CHECK_SMALL( computedLg - physical_constants::LG_TIME_RATE_TERM,
                                    std::numeric_limits< double >::epsilon( ) );

        testTcg1 = 0.0;
        testTcg2 = secondsSinceModifedJulianDayZero;
        testTt1 = convertTcgToTt< double >( testTcg1 );
        testTt2 = convertTcgToTt< double >( testTcg2 );
        computedLg = 1.0 - ( testTt2 - testTt1 ) / ( testTcg2 - testTcg1 );
        BOOST_CHECK_SMALL( computedLg - physical_constants::LG_TIME_RATE_TERM,
                                    std::numeric_limits< double >::epsilon( ) );

        // Test rate difference between TDB and TCB
        double testTcb1 = 0.0;
        double testTcb2 = secondsSinceModifedJulianDayZero;
        double testTdb1 = convertTcbToTdb< double >( testTcb1 );
        double testTdb2 = convertTcbToTdb< double >( testTcb2 );
        double computedLb = 1.0 - ( testTdb2 - testTdb1 ) / ( testTcb2 - testTcb1 );
        BOOST_CHECK_SMALL( computedLb - physical_constants::LB_TIME_RATE_TERM,
                                    std::numeric_limits< double >::epsilon( ) );

        testTdb1 = 0.0;
        testTdb2 = secondsSinceModifedJulianDayZero;
        testTcb1 = convertTdbToTcb< double >( 0.0 );
        testTcb2 = convertTdbToTcb< double >( secondsSinceModifedJulianDayZero );
        computedLb = 1.0 - ( testTdb2 - testTdb1 ) / ( testTcb2 - testTcb1 );
        BOOST_CHECK_SMALL( computedLb - physical_constants::LB_TIME_RATE_TERM,
                                    std::numeric_limits< double >::epsilon( ) );
    }

    // Compare TT/TCG conversions against Sofa cookbook (only limited precision).
    {
        double expectedTT = physical_constants::JULIAN_DAY *
                convertCalendarDateToJulianDaysSinceEpoch( 2006, 1, 15, 12, 25, 42.68400, JULIAN_DAY_ON_J2000 );
        double expectedTCG = physical_constants::JULIAN_DAY *
                convertCalendarDateToJulianDaysSinceEpoch( 2006, 1, 15, 12, 25, 43.32269, JULIAN_DAY_ON_J2000 );

        double calculatedTT = convertTcgToTt( expectedTCG );
        double calculatedTCG = convertTtToTcg( expectedTT );

        BOOST_CHECK_SMALL( calculatedTCG - expectedTCG, 5.0E-5 );
        BOOST_CHECK_SMALL( calculatedTT - expectedTT, 5.0E-5 );

    }
}

// Test relativistic time scale conversions (TCG, TT, TDB, TCB) for long double precision.
BOOST_AUTO_TEST_CASE( testTimeConversionsLong )
{
    // Define test dates (arbitrary).
    const long double testModifiedJulianDay = static_cast< long double >( 54583.87 );
    const long double testJulianDay = testModifiedJulianDay + JULIAN_DAY_AT_0_MJD_LONG;

    // Test whether back and forth conversion between JD and MJD provides correct resulats.
    BOOST_CHECK_CLOSE_FRACTION( testJulianDay, convertModifiedJulianDayToJulianDay< long double >( testModifiedJulianDay ),
                                2.0 * std::numeric_limits< long double >::epsilon( ));
    BOOST_CHECK_CLOSE_FRACTION( testModifiedJulianDay, convertJulianDayToModifiedJulianDay< long double >( testJulianDay ),
                                2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_CLOSE_FRACTION( convertJulianDayToModifiedJulianDay< long double >(
                                    convertModifiedJulianDayToJulianDay< long double >( testModifiedJulianDay ) ),
                                testModifiedJulianDay, 2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_CLOSE_FRACTION( convertModifiedJulianDayToJulianDay(
                                    convertJulianDayToModifiedJulianDay( testJulianDay ) ),
                                testJulianDay, 2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test conversion to seconds since Epoch for JD and MJD
    long double secondsSinceModifedJulianDayZero = testModifiedJulianDay * JULIAN_DAY_LONG;

    BOOST_CHECK_CLOSE_FRACTION( secondsSinceModifedJulianDayZero, convertJulianDayToSecondsSinceEpoch< long double >(
                                    testJulianDay, JULIAN_DAY_AT_0_MJD_LONG ),
                                2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_CLOSE_FRACTION( testJulianDay, convertSecondsSinceEpochToJulianDay< long double >(
                                    secondsSinceModifedJulianDayZero, JULIAN_DAY_AT_0_MJD_LONG ),
                                2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test whether TCG and TT are both 0 at synchronization time.
    long double secondsSinceJ2000Synchronization = getTimeOfTaiSynchronizationSinceJ2000< long double >( );

    long double testTcg = convertTtToTcg< long double >( secondsSinceJ2000Synchronization );
    long double testTt = convertTcgToTt< long double >( secondsSinceJ2000Synchronization );

    BOOST_CHECK_CLOSE_FRACTION( testTcg, secondsSinceJ2000Synchronization,
                                2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_CLOSE_FRACTION( testTt, secondsSinceJ2000Synchronization,
                                2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test whether TDB and TCB are both at defined relative offset at given time.
    long double testTcb = convertTdbToTcb< long double >( secondsSinceJ2000Synchronization );
    long double testTdb = convertTcbToTdb< long double >( secondsSinceJ2000Synchronization );

    BOOST_CHECK_CLOSE_FRACTION( testTdb, secondsSinceJ2000Synchronization + TDB_SECONDS_OFFSET_AT_SYNCHRONIZATION,
                                2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_CLOSE_FRACTION( testTcb, secondsSinceJ2000Synchronization - TDB_SECONDS_OFFSET_AT_SYNCHRONIZATION,
                                2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test back and forth TCG<->TT at t=0, and expected value of TCG at TT=0..
    long double testTime = 0.0L;

    testTcg = convertTtToTcg< long double >( testTime);
    testTt = convertTcgToTt< long double >( testTcg );

    long double expectedTcg = -secondsSinceJ2000Synchronization * LG_TIME_RATE_TERM_LONG /
            ( 1.0L - LG_TIME_RATE_TERM_LONG);
    BOOST_CHECK_CLOSE_FRACTION( testTcg, expectedTcg, 2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_CLOSE_FRACTION( testTt, testTime, 2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test back and forth TDB<->TCB at t=0, and expected value of TDB at TCB=0.
    testTdb = convertTcbToTdb< long double >( testTime );
    testTcb = convertTdbToTcb< long double >( testTdb );

    long double expectedTdb = secondsSinceJ2000Synchronization * LB_TIME_RATE_TERM_LONG +
            TDB_SECONDS_OFFSET_AT_SYNCHRONIZATION_LONG;

    BOOST_CHECK_CLOSE_FRACTION( testTdb, expectedTdb, 2.0 * std::numeric_limits< long double >::epsilon( ) );
    BOOST_CHECK_SMALL( testTcb, 2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test back and forth TCG<->TT.
    testTime = secondsSinceModifedJulianDayZero;

    testTcg = convertTtToTcg< long double >( testTime );
    testTt = convertTcgToTt< long double >( testTcg );

    BOOST_CHECK_CLOSE_FRACTION( testTt, testTime, 2.0 * std::numeric_limits< long double >::epsilon( ) );

    // Test back and forth TDB<->TCB.
    testTdb = convertTcbToTdb< long double >( testTime );
    testTcb = convertTdbToTcb< long double >( testTdb );

    BOOST_CHECK_CLOSE_FRACTION( testTcb, testTime, 2.0 * std::numeric_limits< long double >::epsilon( ) );

    secondsSinceModifedJulianDayZero = 1.0E12;

    // Test rate difference between TCG and TT
    long double testTt1 = 0.0;
    long double testTt2 = secondsSinceModifedJulianDayZero;
    long double testTcg1 = convertTtToTcg< long double >( testTt1 );
    long double testTcg2 = convertTtToTcg< long double >( testTt2 );
    long double computedLg = 1.0L - ( testTt2 - testTt1 ) / ( testTcg2 - testTcg1 );
    BOOST_CHECK_SMALL( computedLg - physical_constants::LG_TIME_RATE_TERM_LONG,
                                std::numeric_limits< long double >::epsilon( ) );

    testTcg1 = 0.0;
    testTcg2 = secondsSinceModifedJulianDayZero;
    testTt1 = convertTcgToTt< long double >( testTcg1 );
    testTt2 = convertTcgToTt< long double >( testTcg2 );
    computedLg = 1.0L - ( testTt2 - testTt1 ) / ( testTcg2 - testTcg1 );
    BOOST_CHECK_SMALL( computedLg - physical_constants::LG_TIME_RATE_TERM_LONG,
                                std::numeric_limits< long double >::epsilon( ) );

    // Test rate difference between TDB and TCB
    long double testTcb1 = 0.0L;
    long double testTcb2 = secondsSinceModifedJulianDayZero;
    long double testTdb1 = convertTcbToTdb< long double >( testTcb1 );
    long double testTdb2 = convertTcbToTdb< long double >( testTcb2 );
    long double computedLb = 1.0L - ( testTdb2 - testTdb1 ) / ( testTcb2 - testTcb1 );
    BOOST_CHECK_SMALL( computedLb - physical_constants::LB_TIME_RATE_TERM_LONG,
                                std::numeric_limits< long double >::epsilon( ) );

    testTdb1 = 0.0;
    testTdb2 = secondsSinceModifedJulianDayZero;
    testTcb1 = convertTdbToTcb< long double >( 0.0 );
    testTcb2 = convertTdbToTcb< long double >( secondsSinceModifedJulianDayZero );
    computedLb = 1.0L - ( testTdb2 - testTdb1 ) / ( testTcb2 - testTcb1 );
    BOOST_CHECK_SMALL( computedLb - physical_constants::LB_TIME_RATE_TERM_LONG,
                                std::numeric_limits< long double >::epsilon( ) );

}

BOOST_AUTO_TEST_SUITE_END( )

} // namespace unit_tests
} // namespace tudat


#include "needleman.h"

#include "ereal.h"
#include <cassert>
#include <algorithm>
#include <blitz/tinyvec-et.h>
#include <btl/logspace.h>

// We assume the BLASTN default scoring scheme with Juke's/Cantor background. All logs are in base sqrt(2).
// Defaults          Adjusted to remove/include the odds portion.
// Match:       1   4
// Mismatch:   -3   -12
// Gap open:   -5   -20
// Gap extend: -2   -8
void needleman::init_pr() {
   // match/mismatch probabilities
   // pno_open should be rolled in here, but it's value is -0.006 which is
   // barely representable by the precisian we use here. Ignore for now.
   s =   4.0, 0.015625, 0.015625, 0.015625,
         0.015625, 4.0, 0.015625, 0.015625,
         0.015625, 0.015625, 4.0, 0.015625,
         0.015625, 0.015625, 0.015625, 4.0;

   // gap open
   popen = 0.001043033;

   // gap extend
   pext = 0.0625;
   pno_ext = 1.0-0.0625;
}

void needleman::set_parameters( nw_model_parameters const &mp ){
	set_mean_gap_length( mp.mean_gap_length );
	set_s( mp.pr_open, mp.p, mp.q );
}

// set mean gap length to n
void needleman::set_mean_gap_length( double n ) {
   // Since a gap is length 1 by the virtue of opening it, we adjust n down by 1.
   // The remaining gap extension probability corresponds to the mean of a
   // geometric distribution allowing zero extensions.
   pext =  1.0-1.0/n;
   pno_ext =  1.0/n;
	}

// computes score matrix from match probabilities and background
void needleman::set_s( double new_popen, blitz::Array<double,2> const &p, blitz::TinyVector<double,4> const &q ) {
   using namespace std;
   assert( p.rows() == 4 );
   assert( p.cols() == 4 );

   popen = new_popen;
	double pno_open = 1.0-2*new_popen;
   for( int i = 0; i < 4; i++ ) for( int j = 0; j < 4; j++ ) s(i,j) = pno_open*p(i,j)/q(i)/q(j);
}

void needleman::maindp_init_borders( dparray &dp, dna_sequence_region_data &a, dna_sequence_region_data &b ) {
   int n = (int) a.size(), m = (int)b.size();
   assert( (n>=1) && (m>=1) );

   // top corner is the only valid starting point
   init_cell(dp(0,0).M); init_cell(dp(0,0).Ga); init_cell(dp(0,0).Gb);
   dp(0,0).M.s = 1.0;

   // Initialize borders. Scores represent opening a big gap.
   init_cell(dp(1,0).M); init_cell(dp(1,0).Ga); init_cell(dp(1,0).Gb);
   dp(1,0).Gb.from = M_state; dp(1,0).Gb.s = popen*pno_ext;
   for( int i = 2; i <= n; i++ ) {
      init_cell(dp(i,0).M); init_cell(dp(i,0).Ga); init_cell(dp(i,0).Gb);
      dp(i,0).Gb.from = Gb_state; dp(i,0).Gb.s = dp(i-1,0).Gb.s*pext;
   }
   init_cell(dp(0,1).M); init_cell(dp(0,1).Ga); init_cell(dp(0,1).Gb);
   dp(0,1).Ga.from = M_state; dp(0,1).Ga.s = popen*pno_ext;
   for( int j = 2; j <= m; j++ ) {
      init_cell(dp(0,j).M); init_cell(dp(0,j).Ga); init_cell(dp(0,j).Gb);
      dp(0,j).Ga.from = Ga_state; dp(0,j).Ga.s = dp(0,j-1).Ga.s*pext;
   }
}

void needleman::maindp_filldp( dparray &dp, dna_sequence_region_data &a, dna_sequence_region_data &b ) {
   int n = (int) a.size(), m = (int)b.size();
	ereal score, match_cost;
   for( int i = 1; i <= n; i++ ) {
   for( int j = 1; j <= m; j++ ) {
      // first consider a match, default is come from match
      match_cost = s((int)a[(uint)i-1],(int)b[(uint)j-1]);
      dp(i,j).M.s = dp(i-1,j-1).M.s*match_cost;
      dp(i,j).M.from = M_state;

      // consider coming from a gap in A
      score = dp(i-1,j-1).Ga.s*match_cost;
      if( score > dp(i,j).M.s ) {
         dp(i,j).M.s = score;
         dp(i,j).M.from = Ga_state;
      }

      // consider coming from a gap in B
      score = dp(i-1,j-1).Gb.s*match_cost;
      if( score > dp(i,j).M.s ) {
         dp(i,j).M.s = score;
         dp(i,j).M.from = Gb_state;
      }

      // now consider extending or opening a gap in A, default is open a gap
      dp(i,j).Ga.s = dp(i,j-1).M.s*popen*pno_ext;
      dp(i,j).Ga.from = M_state;

      // extend a gap
      score = dp(i,j-1).Ga.s*pext;
      if( score > dp(i,j).Ga.s ) {
         dp(i,j).Ga.s = score;
         dp(i,j).Ga.from = Ga_state;
      }

      // now consider extending or opening a gap in B, default is open a gap
      dp(i,j).Gb.s = dp(i-1,j).M.s*popen*pno_ext;
      dp(i,j).Gb.from = M_state;

      // extend a gap
      score = dp(i-1,j).Gb.s*pext;
      if( score > dp(i,j).Gb.s ) {
         dp(i,j).Gb.s = score;
         dp(i,j).Gb.from = Gb_state;
      }
   } // for j
   } // for i
}

pairwise_dna_alignment needleman::maindp_traceback( dparray &dp, dna_sequence_region_data &a, dna_sequence_region_data &b ) {
   using namespace std;
   int n = (int) a.size(), m = (int)b.size();

   // alignment sequences
   dna_alignment_sequence_ptr newa = new_dna_alignment_sequence(), newb = new_dna_alignment_sequence();

   // determine starting block
   int i = n, j = m, next_i = n, next_j = m;
   uint8_t type = M_state, next_type = no_state;
   if( state_score(dp,i,j,type) < dp(i,j).Ga.s ) type = Ga_state;
   if( state_score(dp,i,j,type) < dp(i,j).Gb.s ) type = Gb_state;
   ereal alignment_score = state_score(dp,i,j,type);

   while( (i > 0) || (j > 0) ) {
      assert( type != no_state );
      assert( type < num_states );
      switch( type ) {
         case M_state:
            next_i = i - 1;
            next_j = j - 1;
            next_type = dp(i,j).M.from;
            newa->data.push_front( a[(uint)i-1] );
            newb->data.push_front( b[(uint)j-1] );
            break;
         case Ga_state:
            next_i = i;
            next_j = j - 1;
            next_type = dp(i,j).Ga.from;
            newa->data.push_front( dna_alignment_alpha::GAP );
            newb->data.push_front( b[(uint)j-1] );
            break;
         case Gb_state:
            next_i = i - 1;
            next_j = j;
            next_type = dp(i,j).Gb.from;
            newa->data.push_front( a[(uint)i-1] );
            newb->data.push_front( dna_alignment_alpha::GAP );
      }
      i = next_i;
      j = next_j;
      type = next_type;
   }
   return pairwise_dna_alignment( newa, newb, alignment_score.as_base() );
}

pairwise_dna_alignment needleman::maindp( dna_sequence_region_data &a, dna_sequence_region_data &b ) {
   int n = (int) a.size(), m = (int)b.size();
   dparray dp(n+1,m+1);
   maindp_init_borders( dp, a, b );
   maindp_filldp( dp, a, b );
   return maindp_traceback( dp, a, b );
}

// only need to initialize score scheme
needleman::needleman() {
   s.resize(dna_alpha::SIZE,dna_alpha::SIZE);
   init_pr();
}

pairwise_dna_alignment needleman::align( dna_sequence_region &seqa, dna_sequence_region &seqb ) {
   return maindp( seqa.data, seqb.data );
}

std::pair<nw_model_parameters_ptr,ereal > needleman::estimate( dna_sequence_region &seqa, dna_sequence_region &seqb ) {
	using namespace std;
	pairwise_dna_alignment aln = align(seqa,seqb);
	counts cnt;
	do_statistics(aln,cnt);
	nw_model_parameters_ptr nwp = update_probabilities(cnt);
	return make_pair(nwp,aln.score);
}

void needleman::do_statistics( pairwise_dna_alignment &align, needleman::counts &cnt ) {
	assert( align.a->data.size() == align.b->data.size() );

	int gap_a = -1, gap_b = -1;
	for( int c = 0; c < (int)align.a->data.size(); ++c ) {
		dna_alignment_alpha::symbol c_a = align.a->data[c], c_b = align.b->data[c];

		// found a pair!
		if( c_a != dna_alignment_alpha::GAP && c_b != dna_alignment_alpha::GAP ) {
			int base_a = dna_alpha::symbol(c_a).index();
			int base_b = dna_alpha::symbol(c_b).index();
			cnt.pairs( base_a, base_b) += 1;
			cnt.bases_a(base_a) += 1;
			cnt.bases_b(base_b) += 1;

			// closing a gap in A
			if( gap_a >= 0 ) {
				cnt.gap_lengths_a += c - gap_a + 1;
				gap_a = -1;
			}

			// closing a gap in B
			if( gap_b >= 0 ) {
				cnt.gap_lengths_b += c - gap_b + 1;
				gap_b = -1;
			}

			// found a gap in A
		} else if( c_a == dna_alignment_alpha::GAP && c_b != dna_alignment_alpha::GAP ) {
			cnt.bases_b((int)dna_alpha::symbol(c_b)) += 1;

			// gap is not open
			if( gap_a == -1 ) {
				gap_a = c;
				cnt.gaps_a += 1;
			} // otherwise nothing to do

			// closing a gap in B
			if( gap_b >= 0 ) {
				cnt.gap_lengths_b += c - gap_b + 1;
				gap_b = -1;
			}
			// found a gap in B
		} else if( c_a != dna_alignment_alpha::GAP && c_b == dna_alignment_alpha::GAP ) {
			cnt.bases_a((int)dna_alpha::symbol(c_a)) += 1;

			// closing a gap in A
			if( gap_a >= 0 ) {
				cnt.gap_lengths_a += c - gap_a + 1;
				gap_a = -1;
			}

			// gap is not open
			if( gap_b == -1 ) {
				gap_b = c;
				cnt.gaps_b += 1;
			} // otherwise nothing to do
		} else {
			using namespace std;
			cerr << c << "\t" << c_a << "\t" << c_b << endl;
			assert( 0 && "corrupt alignment" );
		}

	} // for c

	// finished region, count any last gaps
	// closing a gap in A
	if( gap_a >= 0 ) {
		cnt.gap_lengths_a += align.a->data.size() - gap_a;
		gap_a = -1;
	}

	// closing a gap in B
	if( gap_b >= 0 ) {
		cnt.gap_lengths_b += align.a->data.size() - gap_b;
		gap_b = -1;
	}

}

nw_model_parameters_ptr needleman::update_probabilities( needleman::counts &cnt ) {
	using namespace std;
	using namespace blitz;

	// get combined base count for both sequences
	base_count all_bases;
	all_bases = cnt.bases_a + cnt.bases_b;
	int total_bases = sum( all_bases );

	nw_model_parameters_ptr newp( new nw_model_parameters() );

	// compute p
	int total_pairs = sum( cnt.pairs(Range::all(),Range::all()) );
	for( int i = 0; i < dna_alpha::SIZE; ++i ) {
		for( int j = 0; j < dna_alpha::SIZE; ++j ) {
			newp->p(i,j) = (double) cnt.pairs(i,j)/(double)total_pairs;
		} // j
	} // i

	for( int i = 0; i < dna_alpha::SIZE; ++i ) {
		newp->q(i) = (double) all_bases(i)/(double)total_bases;
	}

	// gap open
	int total_gaps = cnt.gaps_a + cnt.gaps_b;
	newp->pr_open = (double)total_gaps/(double)total_bases;

	// mean gap length
	newp->mean_gap_length = (double)(cnt.gap_lengths_a+cnt.gap_lengths_b)/(double)total_gaps;
	return newp;
}


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NETKET_RMSPROP_HPP
#define NETKET_RMSPROP_HPP

#include <Eigen/Core>
#include <Eigen/Dense>
#include <cassert>
#include <cmath>
#include <complex>
#include <iostream>
#include "abstract_optimizer.hpp"

namespace netket {

class RMSProp : public AbstractOptimizer {
  int npar_;

  double eta_;
  double beta_;

  Eigen::VectorXd st_;

  double epscut_;

  const Complex I_;

 public:
  explicit RMSProp(double eta = 0.001, double beta = 0.9,
                   double epscut = 1.0e-7)
      : eta_(eta), beta_(beta), epscut_(epscut), I_(0, 1) {
    npar_ = -1;
    PrintParameters();
  }

  // TODO remove
  // Json constructor
  explicit RMSProp(const json &pars) : I_(0, 1) {
    npar_ = -1;

    from_json(pars);
    PrintParameters();
  }

  void PrintParameters() {
    InfoMessage() << "RMSProp optimizer initialized with these parameters :"
                  << std::endl;
    InfoMessage() << "Learning Rate = " << eta_ << std::endl;
    InfoMessage() << "Beta = " << beta_ << std::endl;
    InfoMessage() << "Epscut = " << epscut_ << std::endl;
  }

  void Init(const Eigen::VectorXd &pars) override {
    npar_ = pars.size();
    st_.setZero(npar_);
  }

  void Init(const Eigen::VectorXcd &pars) override {
    npar_ = 2 * pars.size();
    st_.setZero(npar_);
  }

  void Update(const Eigen::VectorXd &grad, Eigen::VectorXd &pars) override {
    assert(npar_ > 0);

    st_ = beta_ * st_ + (1. - beta_) * grad.cwiseAbs2();

    for (int i = 0; i < npar_; i++) {
      pars(i) -= eta_ * grad(i) / (std::sqrt(st_(i)) + epscut_);
    }
  }

  void Update(const Eigen::VectorXcd &grad, Eigen::VectorXd &pars) override {
    Update(Eigen::VectorXd(grad.real()), pars);
  }

  void Update(const Eigen::VectorXcd &grad, Eigen::VectorXcd &pars) override {
    assert(npar_ == 2 * pars.size());

    for (int i = 0; i < pars.size(); i++) {
      st_(2 * i) =
          beta_ * st_(2 * i) + (1. - beta_) * std::pow(grad(i).real(), 2);
      st_(2 * i + 1) =
          beta_ * st_(2 * i + 1) + (1. - beta_) * std::pow(grad(i).imag(), 2);
      pars(i) -= eta_ * grad(i).real() / (std::sqrt(st_(2 * i)) + epscut_);
      pars(i) -=
          eta_ * I_ * grad(i).imag() / (std::sqrt(st_(2 * i + 1)) + epscut_);
    }
  }

  void Reset() override { st_ = Eigen::VectorXd::Zero(npar_); }

  // TODO remove
  void from_json(const json &pars) {
    // DEPRECATED (to remove for v2.0.0)
    std::string section = "Optimizer";
    if (!FieldExists(pars, section)) {
      section = "Learning";
    }
    eta_ = FieldOrDefaultVal(pars[section], "LearningRate", 0.001);
    beta_ = FieldOrDefaultVal(pars[section], "Beta", 0.9);
    epscut_ = FieldOrDefaultVal(pars[section], "Epscut", 1.0e-7);
  }
};

}  // namespace netket

#endif


#include <string>
#include <fstream>
#include <cmath>

#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>

#include <am/graphchi/graphchi_basic_includes.hpp>
#include <am/graphchi/engine/dynamic_graphs/graphchi_dynamicgraph_engine.hpp>
#include <am/graphchi/api/functional/functional_api.hpp>
#include <am/graphchi/util/toplist.hpp>

using namespace graphchi;
namespace bfs = boost::filesystem;

#define DIR_PREFIX "./tmp/am_graphchi_"

#define THRESHOLD 1e-1
#define RANDOMRESETPROB 0.15

typedef float VertexDataType;
typedef float EdgeDataType;

struct PagerankProgram : public GraphChiProgram<VertexDataType, EdgeDataType> {

    /**
      * Called before an iteration starts. Not implemented.
      */
    void before_iteration(int iteration, graphchi_context &info) {
    }

    /**
      * Called after an iteration has finished. Not implemented.
      */
    void after_iteration(int iteration, graphchi_context &ginfo) {
    }

    /**
      * Called before an execution interval is started. Not implemented.
      */
    void before_exec_interval(vid_t window_st, vid_t window_en, graphchi_context &ginfo) {
    }

    /**
      * Pagerank update function.
      */
    void update(graphchi_vertex<VertexDataType, EdgeDataType> &v, graphchi_context &ginfo) {
        float sum=0;
        if (ginfo.iteration == 0) {
            /* On first iteration, initialize vertex and out-edges.
               The initialization is important,
               because on every run, GraphChi will modify the data in the edges on disk.
             */
            for(int i=0; i < v.num_outedges(); i++) {
                graphchi_edge<float> * edge = v.outedge(i);
                edge->set_data(1.0 / v.num_outedges());
            }
            v.set_data(RANDOMRESETPROB);
        } else {
            /* Compute the sum of neighbors' weighted pageranks by
               reading from the in-edges. */
            for(int i=0; i < v.num_inedges(); i++) {
                float val = v.inedge(i)->get_data();
                sum += val;
            }

            /* Compute my pagerank */
            float pagerank = RANDOMRESETPROB + (1 - RANDOMRESETPROB) * sum;

            /* Write my pagerank divided by the number of out-edges to
               each of my out-edges. */
            if (v.num_outedges() > 0) {
                float pagerankcont = pagerank / v.num_outedges();
                for(int i=0; i < v.num_outedges(); i++) {
                    graphchi_edge<float> * edge = v.outedge(i);
                    edge->set_data(pagerankcont);
                }
            }

            /* Keep track of the progression of the computation.
               GraphChi engine writes a file filename.deltalog. */
            ginfo.log_change(std::abs(pagerank - v.get_data()));

            /* Set my new pagerank as the vertex value */
            v.set_data(pagerank);
        }
    }

};

struct pagerank_kernel : public functional_kernel<float, float> {

    /* Initial value - on first iteration */
    float initial_value(graphchi_context &info, vertex_info& myvertex) {
        return 1.0;
    }

    /* Called before first "gather" */
    float reset() {
        return 0.0;
    }

    // Note: Unweighted version, edge value should also be passed
    // "Gather"
    float op_neighborval(graphchi_context &info, vertex_info& myvertex, vid_t nbid, float nbval) {
        return nbval;
    }

    // "Sum"
    float plus(float curval, float toadd) {
        return curval + toadd;
    }

    // "Apply"
    float compute_vertexvalue(graphchi_context &ginfo, vertex_info& myvertex, float nbvalsum) {
        assert(ginfo.nvertices > 0);
        return RANDOMRESETPROB / ginfo.nvertices + (1 - RANDOMRESETPROB) * nbvalsum;
    }

    // "Scatter
    float value_to_neighbor(graphchi_context &info, vertex_info& myvertex, vid_t nbid, float myval) {
        assert(myvertex.outdegree > 0);
        return myval / myvertex.outdegree;
    }

};

BOOST_AUTO_TEST_SUITE( graphchi_app_pagerank_suite )

BOOST_AUTO_TEST_CASE(graphchi_pagerank)
{
    metrics m("pagerank");

    /* Basic arguments for application */
    bfs::path db_dir(DIR_PREFIX);
    boost::filesystem::remove_all(db_dir);
    bfs::create_directories(db_dir);
    std::string filename = db_dir.string() + "/graphchi_pagerank";
    ///generate fake inputs
    std::ofstream of(filename.c_str());
    const unsigned int size = 8;
    const unsigned int src[size] = {1,2,3,4,5,6,7,8};
    const unsigned int dest[size] = {4,2,3,1,3,2,8,1};
    const float weight[size] = {0.1,0.2,0.3,0.1,0.5,0.6,0.8,1};
    for(unsigned int i = 0; i < size; ++i)
        of<<src[i]<<" "<<dest[i]<<" "<<weight[i]<<std::endl;
    of.close();

    int niters = get_option_int("niters", 4); // Number of iterations
    bool scheduler = false;                       // Whether to use selective scheduling

    int ntop = get_option_int("top", 20);

    /* Detect the number of shards or preprocess an input to creae them */
    int nshards = convert_if_notexists<EdgeDataType>(filename,
                                                              get_option_string("nshards", "auto"));

    /* Run */
    graphchi_engine<float, float> engine(filename, nshards, scheduler, m);
    engine.set_modifies_inedges(false); // Improves I/O performance.
    PagerankProgram program;
    engine.run(program, niters);

    /* Output top ranked vertices */
    std::vector< vertex_value<float> > top = get_top_vertices<float>(filename, ntop);
    std::cout << "Print top " << ntop << " vertices:" << std::endl;
    for(int i=0; i < (int)top.size(); i++) {
        std::cout << (i+1) << ". " << top[i].vertex << "\t" << top[i].value << std::endl;
    }
    metrics_report(m);

    logstream(LOG_INFO) << "Pagerank executed successfully!" << std::endl;
}

BOOST_AUTO_TEST_CASE(graphchi_pagerank_functional)
{
    metrics m("pagerank-functional");

    /* Basic arguments for application */
    bfs::path db_dir(DIR_PREFIX);
    boost::filesystem::remove_all(db_dir);
    bfs::create_directories(db_dir);
    std::string filename = db_dir.string() + "/graphchi_pagerank-functional";
    ///generate fake inputs
    std::ofstream of(filename.c_str());
    const unsigned int size = 8;
    const unsigned int src[size] = {1,2,3,4,5,6,7,8};
    const unsigned int dest[size] = {4,2,3,1,3,2,8,1};
    const float weight[size] = {0.1,0.2,0.3,0.1,0.5,0.6,0.8,1};
    for(unsigned int i = 0; i < size; ++i)
        of<<src[i]<<" "<<dest[i]<<" "<<weight[i]<<std::endl;
    of.close();

    int niters = get_option_int("niters", 4);
    int ntop = get_option_int("top", 20);
    std::string mode = get_option_string("mode", "semisync");

    logstream(LOG_INFO) << "Running pagerank functional." << std::endl;
    run_functional_unweighted_synchronous<pagerank_kernel>(filename, niters, m);
    logstream(LOG_INFO) << "Pagerank functional passed successfully! Your system is working!" << std::endl;
    /* Write Top 20 */
    std::vector< vertex_value<float> > top = get_top_vertices<float>(filename, ntop);
    std::cout << "Print top 20 vertices: " << std::endl;
    for(int i=0; i < (int) top.size(); i++) {
        std::cout << (i+1) << ". " << top[i].vertex << "\t" << top[i].value << std::endl;
    }

}

BOOST_AUTO_TEST_SUITE_END()


#ifndef AIKIDO_STATESPACE_SO3STATESPACE_HPP_
#define AIKIDO_STATESPACE_SO3STATESPACE_HPP_
#include <Eigen/Geometry>

#include "aikido/statespace/ScopedState.hpp"
#include "aikido/statespace/StateSpace.hpp"

namespace aikido {
namespace statespace {

// Defined in detail/SO3-impl.hpp
template <class>
class SO3StateHandle;

/// The two-dimensional special orthogonal group SO(3), i.e. the space of
/// spatial rigid body rotations.
class SO3 : virtual public StateSpace
{
public:
  /// State in SO(3), a spatial rotation.
  class State : public StateSpace::State
  {
  public:
    using Quaternion = Eigen::Quaternion<double, Eigen::DontAlign>;

    /// Constructs the identity element.
    State();

    ~State() = default;

    /// Constructs a state in SO(3) from a unit quaternion.
    ///
    /// \param _quaternion unit quaternion representing orientation
    explicit State(const Quaternion& _quaternion);

    /// Gets a state as a unit quaternion.
    ///
    /// \return unit quaternion representing orientation
    const Quaternion& getQuaternion() const;

    /// Sets a state to a unit quaternion.
    ///
    /// \param _quaternion unit quaternion representing orientation
    void setQuaternion(const Quaternion& _quaternion);

  private:
    Quaternion mValue;

    friend class SO3;
  };

  using StateHandle = SO3StateHandle<State>;
  using StateHandleConst = SO3StateHandle<const State>;

  using ScopedState = statespace::ScopedState<StateHandle>;
  using ScopedStateConst = statespace::ScopedState<StateHandleConst>;

  using StateSpace::compose;

  using Quaternion = State::Quaternion;

  /// Constructs a state space representing SO(3).
  SO3() = default;

  /// Helper function to create a \c ScopedState.
  ///
  /// \return new \c ScopedState
  ScopedState createState() const;

  /// Creates an identical clone of \c stateIn.
  ScopedState cloneState(const StateSpace::State* stateIn) const;

  /// Gets a state as a unit quaternion.
  ///
  /// \param _state input state
  /// \return unit quaternion representing orientation
  const Quaternion& getQuaternion(const State* _state) const;

  /// Sets a state to a unit quaternion.
  ///
  /// \param _state input state
  /// \param _quaternion unit quaternion representing orientation
  void setQuaternion(State* _state, const Quaternion& _quaternion) const;

  // Documentation inherited.
  std::size_t getStateSizeInBytes() const override;

  // Documentation inherited.
  StateSpace::State* allocateStateInBuffer(void* _buffer) const override;

  // Documentation inherited.
  void freeStateInBuffer(StateSpace::State* _state) const override;

  // Documentation inherited.
  void compose(
      const StateSpace::State* _state1,
      const StateSpace::State* _state2,
      StateSpace::State* _out) const override;

  // Documentation inherited
  void getIdentity(StateSpace::State* _out) const override;

  // Documentation inherited
  void getInverse(
      const StateSpace::State* _in, StateSpace::State* _out) const override;

  // Documentation inherited
  std::size_t getDimension() const override;

  // Documentation inherited
  void copyState(
      const StateSpace::State* _source,
      StateSpace::State* _destination) const override;

  /// Exponential mapping of Lie algebra element to a Lie group element. The
  /// tangent space is parameterized as a spatial rotation velocity.
  ///
  /// \param _tangent element of the tangent space
  /// \param[out] _out corresponding element of the Lie group
  void expMap(
      const Eigen::VectorXd& _tangent, StateSpace::State* _out) const override;

  /// Log mapping of Lie group element to a Lie algebra element. The tangent
  /// space is parameterized as a spatial rotational velocity.
  ///
  /// \param _in element of this Lie group
  /// \param[out] _tangent corresponding element of the tangent space
  void logMap(
      const StateSpace::State* _in, Eigen::VectorXd& _tangent) const override;

  /// Print the quaternion represented by the state.
  /// Format: [w, x, y, z]
  void print(const StateSpace::State* _state, std::ostream& _os) const override;
};

} // namespace statespace
} // namespace aikido

#include "detail/SO3-impl.hpp"

#endif // ifndef AIKIDO_STATESPACE_SO3STATESPACE_HPP_


/******************************************************************************
This benchmark allows to compute the Tangential Complex from input files or
generated point sets.

It reads the benchmark_script.txt file (located in the same folder as this
file) and compute one or several complexes for each line. Unless TC_NO_EXPORT
is defined, each complex is exported as an OFF file and/or as a RIB file
(RenderMan). In addition an XML file is created at each run of the benchmark.
It contains statistics about the complexes that were created. This XML file
can be processed in Excel, for example.
 ******************************************************************************/

// Without TBB_USE_THREADING_TOOL Intel Inspector XE will report false positives in Intel TBB
// (http://software.intel.com/en-us/articles/compiler-settings-for-threading-error-analysis-in-intel-inspector-xe/)
#ifdef _DEBUG
#define TBB_USE_THREADING_TOOL
#endif

#include <cstddef>

//#define GUDHI_TC_USE_ANOTHER_POINT_SET_FOR_TANGENT_SPACE_ESTIM
//#define TC_INPUT_STRIDES 3 // only take one point every TC_INPUT_STRIDES points
#define TC_NO_EXPORT // do not output OFF files
//#define TC_EXPORT_TO_RIB //
//#define GUDHI_TC_EXPORT_SPARSIFIED_POINT_SET
//#define GUDHI_TC_EXPORT_ALL_COORDS_IN_OFF

const std::size_t ONLY_LOAD_THE_FIRST_N_POINTS = 20000000;

#include <gudhi/Debug_utils.h>
#include <gudhi/Clock.h>
#include <gudhi/Tangential_complex.h>
#include <gudhi/sparsify_point_set.h>
#include <gudhi/random_point_generators.h>
#include <gudhi/Tangential_complex/utilities.h>

#include <CGAL/assertions_behaviour.h>
#include <CGAL/Epick_d.h>
#include <CGAL/Random.h>

#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/trim_all.hpp>
#include <boost/range/adaptor/strided.hpp>

#include <cstdlib>
#include <ctime>
#include <fstream>
#include <cmath>  // for std::sqrt

#ifdef GUDHI_USE_TBB
#include <tbb/task_scheduler_init.h>
#endif
#include "XML_exporter.h"
#include "RIB_exporter.h"
#define GUDHI_TC_EXPORT_PERFORMANCE_DATA
#define GUDHI_TC_SET_PERFORMANCE_DATA(value_name, value) \
        XML_perf_data::set(value_name, value);

namespace subsampl = Gudhi::subsampling;
namespace tc = Gudhi::tangential_complex;

const char * const BENCHMARK_SCRIPT_FILENAME = "benchmark_script.txt";

typedef CGAL::Epick_d<CGAL::Dynamic_dimension_tag> Kernel;
typedef Kernel::FT FT;
typedef Kernel::Point_d Point;
typedef Kernel::Vector_d Vector;
typedef tc::Tangential_complex<
Kernel, CGAL::Dynamic_dimension_tag,
CGAL::Parallel_tag> TC;
typedef TC::Simplex Simplex;
typedef TC::Simplex_set Simplex_set;

class XML_perf_data {
 public:
  typedef Streaming_XML_exporter<std::string> XML_exporter;

  XML_perf_data(const std::string &filename)
      : m_xml(filename, "ContainerPerformance", "Perf",
              construct_subelements_names()) { }

  virtual ~XML_perf_data() { }

  static XML_perf_data &get() {
    static XML_perf_data singleton(build_filename());
    return singleton;
  }

  template <typename Value_type>
  static void set(const std::string &name, Value_type value) {
    get().set_data(name, value);
  }

  static void commit() {
    get().commit_current_element();
  }

 protected:

  static std::string build_filename() {
    std::stringstream sstr;
    sstr << "perf_logs/Performance_log_" << time(0) << ".xml";
    return sstr.str();
  }

  static std::vector<std::string> construct_subelements_names() {
    std::vector<std::string> subelements;
    subelements.push_back("Input");
    subelements.push_back("Param1");
    subelements.push_back("Param2");
    subelements.push_back("Param3");
    subelements.push_back("Intrinsic_dim");
    subelements.push_back("Ambient_dim");
    subelements.push_back("Num_threads");
    subelements.push_back("Sparsity");
    subelements.push_back("Max_perturb");
    subelements.push_back("Num_points_in_input");
    subelements.push_back("Num_points");
    subelements.push_back("Perturb_technique");
    subelements.push_back("Perturb_which_points");
    subelements.push_back("Initial_num_inconsistent_local_tr");
    subelements.push_back("Best_num_inconsistent_local_tr");
    subelements.push_back("Final_num_inconsistent_local_tr");
    subelements.push_back("Init_time");
    subelements.push_back("Comput_time");
    subelements.push_back("Perturb_successful");
    subelements.push_back("Perturb_time");
    subelements.push_back("Perturb_steps");
    subelements.push_back("Result_pure_pseudomanifold");
    subelements.push_back("Result_num_wrong_dim_simplices");
    subelements.push_back("Result_num_wrong_number_of_cofaces");
    subelements.push_back("Result_num_unconnected_stars");
    subelements.push_back("Info");

    return subelements;
  }

  void set_data(const std::string &name, const std::string &value) {
    m_current_element[name] = value;
  }

  template <typename Value_type>
  void set_data(const std::string &name, Value_type value) {
    std::stringstream sstr;
    sstr << value;
    set_data(name, sstr.str());
  }

  void commit_current_element() {
    m_xml.add_element(m_current_element);
    m_current_element.clear();
  }

  XML_exporter m_xml;
  XML_exporter::Element_with_map m_current_element;
};

template<
typename Kernel, typename OutputIteratorPoints>
bool load_points_from_file(
                           const std::string &filename,
                           OutputIteratorPoints points,
                           std::size_t only_first_n_points = (std::numeric_limits<std::size_t>::max)()) {
  typedef typename Kernel::Point_d Point;

  std::ifstream in(filename);
  if (!in.is_open()) {
    std::cerr << "Could not open '" << filename << "'" << std::endl;
    return false;
  }

  Kernel k;
  Point p;
  int num_ppints;
  in >> num_ppints;

  std::size_t i = 0;
  while (i < only_first_n_points && in >> p) {
    *points++ = p;
    ++i;
  }

#ifdef DEBUG_TRACES
  std::cerr << "'" << filename << "' loaded." << std::endl;
#endif

  return true;
}

template<
typename Kernel, typename Tangent_space_basis,
typename OutputIteratorPoints, typename OutputIteratorTS>
bool load_points_and_tangent_space_basis_from_file(
                                                   const std::string &filename,
                                                   OutputIteratorPoints points,
                                                   OutputIteratorTS tangent_spaces,
                                                   int intrinsic_dim,
                                                   std::size_t only_first_n_points = (std::numeric_limits<std::size_t>::max)()) {
  typedef typename Kernel::Point_d Point;
  typedef typename Kernel::Vector_d Vector;

  std::ifstream in(filename);
  if (!in.is_open()) {
    std::cerr << "Could not open '" << filename << "'" << std::endl;
    return false;
  }

  Kernel k;
  Point p;
  int num_ppints;
  in >> num_ppints;

  std::size_t i = 0;
  while (i < only_first_n_points && in >> p) {
    *points++ = p;

    Tangent_space_basis tsb(i);
    for (int d = 0; d < intrinsic_dim; ++d) {
      Vector v;
      in >> v;
      tsb.push_back(tc::internal::normalize_vector(v, k));
    }
    *tangent_spaces++ = tsb;
    ++i;
  }

#ifdef DEBUG_TRACES
  std::cerr << "'" << filename << "' loaded." << std::endl;
#endif

  return true;
}

// color_inconsistencies: only works if p_complex = NULL
template <typename TC>
bool export_to_off(
                   TC const& tc,
                   std::string const& input_name_stripped,
                   std::string const& suffix,
                   bool color_inconsistencies = false,
                   typename TC::Simplicial_complex const* p_complex = NULL,
                   Simplex_set const *p_simpl_to_color_in_red = NULL,
                   Simplex_set const *p_simpl_to_color_in_green = NULL,
                   Simplex_set const *p_simpl_to_color_in_blue = NULL) {
#ifdef TC_NO_EXPORT
  return true;
#endif

  CGAL::Identity<Point> proj_functor;

  if (tc.intrinsic_dimension() <= 3) {
    std::stringstream output_filename;
    output_filename << "output/" << input_name_stripped << "_"
        << tc.intrinsic_dimension() << "_in_R"
        << tc.ambient_dimension() << "_"
        << tc.number_of_vertices() << "v"
        << suffix << ".off";
    std::ofstream off_stream(output_filename.str().c_str());

    if (p_complex) {
#ifndef TC_NO_EXPORT
      tc.export_to_off(
                       *p_complex, off_stream,
                       p_simpl_to_color_in_red,
                       p_simpl_to_color_in_green,
                       p_simpl_to_color_in_blue,
                       proj_functor);
#endif
    } else {
      tc.export_to_off(
                       off_stream, color_inconsistencies,
                       p_simpl_to_color_in_red,
                       p_simpl_to_color_in_green,
                       p_simpl_to_color_in_blue,
                       NULL,
                       proj_functor);
    }
    return true;
  }
  return false;
}

void make_tc(std::vector<Point> &points,
             TC::TS_container const& tangent_spaces,  // can be empty
             int intrinsic_dim,
             double sparsity = 0.01,
             double max_perturb = 0.005,
             bool perturb = true,
             bool add_high_dim_simpl = false,
             bool collapse = false,
             double time_limit_for_perturb = 0.,
             const char *input_name = "tc") {
  Kernel k;

  if (sparsity > 0. && !tangent_spaces.empty()) {
    std::cerr << "Error: cannot sparsify point set with pre-computed normals.\n";
    return;
  }

  //===========================================================================
  // Init
  //===========================================================================
  Gudhi::Clock t;

  // Get input_name_stripped
  std::string input_name_stripped(input_name);
  size_t slash_index = input_name_stripped.find_last_of('/');
  if (slash_index == std::string::npos)
    slash_index = input_name_stripped.find_last_of('\\');
  if (slash_index == std::string::npos)
    slash_index = 0;
  else
    ++slash_index;
  input_name_stripped = input_name_stripped.substr(
                                                   slash_index, input_name_stripped.find_last_of('.') - slash_index);

  GUDHI_TC_SET_PERFORMANCE_DATA("Num_points_in_input", points.size());

#ifdef GUDHI_TC_USE_ANOTHER_POINT_SET_FOR_TANGENT_SPACE_ESTIM
  std::vector<Point> points_not_sparse = points;
#endif

  //===========================================================================
  // Sparsify point set if requested
  //===========================================================================
  if (sparsity > 0.) {
    std::size_t num_points_before = points.size();
    std::vector<Point> sparsified_points;
    subsampl::sparsify_point_set(k, points, sparsity*sparsity,
                                 std::back_inserter(sparsified_points));
    sparsified_points.swap(points);
    std::cerr << "Number of points before/after sparsification: "
        << num_points_before << " / " << points.size() << "\n";

#ifdef GUDHI_TC_EXPORT_SPARSIFIED_POINT_SET
    std::ofstream ps_stream("output/sparsified_point_set.txt");
    tc::internal::export_point_set(k, points, ps_stream);
#endif
  }

  GUDHI_TC_SET_PERFORMANCE_DATA("Sparsity", sparsity);
  GUDHI_TC_SET_PERFORMANCE_DATA("Max_perturb", max_perturb);
  GUDHI_TC_SET_PERFORMANCE_DATA("Num_points", points.size());

  //===========================================================================
  // Compute Tangential Complex
  //===========================================================================

  TC tc(
        points,
        intrinsic_dim,
#ifdef GUDHI_TC_USE_ANOTHER_POINT_SET_FOR_TANGENT_SPACE_ESTIM
      points_not_sparse.begin(), points_not_sparse.end(),
#endif
      k);

  if (!tangent_spaces.empty()) {
    tc.set_tangent_planes(tangent_spaces);
  }

  t.end();
  double init_time = t.num_seconds();

  t.begin();
  tc.compute_tangential_complex();
  t.end();
  double computation_time = t.num_seconds();

  //===========================================================================
  // Export to OFF
  //===========================================================================

  // Create complex
  int max_dim = -1;
  TC::Simplicial_complex complex;
  Simplex_set inconsistent_simplices;
  max_dim = tc.create_complex(complex, true, false, 2, &inconsistent_simplices);

  // TODO(CJ): TEST
  Gudhi::Simplex_tree<> stree;
  tc.create_complex(stree, true, false);
  // std::cerr << stree;

  t.begin();
  bool ret = export_to_off(
                           tc, input_name_stripped, "_INITIAL_TC", true,
                           &complex, &inconsistent_simplices);
  t.end();
  double export_before_time = (ret ? t.num_seconds() : -1);

  unsigned int num_perturb_steps = 0;
  double perturb_time = -1;
  double export_after_perturb_time = -1.;
  bool perturb_success = false;
  if (perturb) {
    //=========================================================================
    // Try to fix inconsistencies by perturbing points
    //=========================================================================
    t.begin();
    auto fix_result =
        tc.fix_inconsistencies_using_perturbation(max_perturb, time_limit_for_perturb);
    t.end();
    perturb_time = t.num_seconds();

    perturb_success = fix_result.success;
    GUDHI_TC_SET_PERFORMANCE_DATA("Initial_num_inconsistent_local_tr",
                                  fix_result.initial_num_inconsistent_stars);
    GUDHI_TC_SET_PERFORMANCE_DATA("Best_num_inconsistent_local_tr",
                                  fix_result.best_num_inconsistent_stars);
    GUDHI_TC_SET_PERFORMANCE_DATA("Final_num_inconsistent_local_tr",
                                  fix_result.final_num_inconsistent_stars);

    //=========================================================================
    // Export to OFF
    //=========================================================================

    // Re-build the complex
    Simplex_set inconsistent_simplices;
    max_dim = tc.create_complex(complex, true, false, 2, &inconsistent_simplices);

    t.begin();
    bool exported = export_to_off(
                                  tc, input_name_stripped, "_AFTER_FIX", true, &complex,
                                  &inconsistent_simplices);
    t.end();
    export_after_perturb_time = (exported ? t.num_seconds() : -1);

    //std::string fn = "output/inc_stars/";
    //fn += input_name_stripped;
    //tc.export_inconsistent_stars_to_OFF_files(fn);

#if !defined(TC_NO_EXPORT) && defined(TC_EXPORT_TO_RIB)
    std::ofstream rib(std::string("output/") + input_name_stripped + ".rib");
    RIB_exporter<TC::Points, TC::Simplicial_complex::Simplex_set> rib_exporter(
                                                                               tc.points(),
                                                                               complex.simplex_range(),
                                                                               rib,
                                                                               input_name_stripped + ".tif",
                                                                               false,  // is_preview
                                                                               std::make_tuple(2, 4, 6),
                                                                               1600, 503  // resolution
                                                                               );
    rib_exporter.write_file();

    std::ofstream rib_LQ(std::string("output/") + input_name_stripped + "_LQ.rib");
    RIB_exporter<TC::Points, TC::Simplicial_complex::Simplex_set> rib_exporter_LQ(
                                                                                tc.points(),
                                                                                complex.simplex_range(),
                                                                                rib_LQ,
                                                                                input_name_stripped + "_LQ.tif",
                                                                                true,  // is_preview
                                                                                std::make_tuple(0, 4, 5)
                                                                                );
    rib_exporter_LQ.write_file();
#endif
  } else {
    GUDHI_TC_SET_PERFORMANCE_DATA("Initial_num_inconsistent_local_tr", "N/A");
    GUDHI_TC_SET_PERFORMANCE_DATA("Best_num_inconsistent_local_tr", "N/A");
    GUDHI_TC_SET_PERFORMANCE_DATA("Final_num_inconsistent_local_tr", "N/A");
  }

  max_dim = tc.create_complex(complex, true, false, 2);

  complex.display_stats();

  if (intrinsic_dim == 2)
    complex.euler_characteristic(true);

  //===========================================================================
  // Collapse
  //===========================================================================
  if (collapse) {
    complex.collapse(max_dim);
    complex.display_stats();
  }

  //===========================================================================
  // Is the result a pure pseudomanifold?
  //===========================================================================
  std::size_t num_wrong_dim_simplices,
      num_wrong_number_of_cofaces,
      num_unconnected_stars;
  Simplex_set wrong_dim_simplices;
  Simplex_set wrong_number_of_cofaces_simplices;
  Simplex_set unconnected_stars_simplices;
  bool is_pure_pseudomanifold = complex.is_pure_pseudomanifold(
                                                               intrinsic_dim, tc.number_of_vertices(),
                                                               false,  // do NOT allow borders
                                                               false, 1,
                                                               &num_wrong_dim_simplices, &num_wrong_number_of_cofaces,
                                                               &num_unconnected_stars,
                                                               &wrong_dim_simplices, &wrong_number_of_cofaces_simplices,
                                                               &unconnected_stars_simplices);

  //===========================================================================
  // Export to OFF
  //===========================================================================

  double export_after_collapse_time = -1.;
  if (collapse) {
    t.begin();
    bool exported = export_to_off(
                                  tc, input_name_stripped, "_AFTER_COLLAPSE", false, &complex,
                                  &wrong_dim_simplices, &wrong_number_of_cofaces_simplices,
                                  &unconnected_stars_simplices);
    t.end();
    std::cerr
        << " OFF colors:\n"
        << "   * Red: wrong dim simplices\n"
        << "   * Green: wrong number of cofaces simplices\n"
        << "   * Blue: not-connected stars\n";
    export_after_collapse_time = (exported ? t.num_seconds() : -1.);
  }

  //===========================================================================
  // Display info
  //===========================================================================

  std::cerr
      << "\n================================================\n"
      << "Number of vertices: " << tc.number_of_vertices() << "\n"
      << "Computation times (seconds): \n"
      << "  * Tangential complex: " << init_time + computation_time << "\n"
      << "    - Init + kd-tree = " << init_time << "\n"
      << "    - TC computation = " << computation_time << "\n"
      << "  * Export to OFF (before perturb): " << export_before_time << "\n"
      << "  * Fix inconsistencies 1: " << perturb_time
      << " (" << num_perturb_steps << " steps) ==> "
      << (perturb_success ? "FIXED" : "NOT fixed") << "\n"
      << "  * Export to OFF (after perturb): " << export_after_perturb_time << "\n"
      << "  * Export to OFF (after collapse): "
      << export_after_collapse_time << "\n"
      << "================================================\n";

  //===========================================================================
  // Export info
  //===========================================================================
  GUDHI_TC_SET_PERFORMANCE_DATA("Init_time", init_time);
  GUDHI_TC_SET_PERFORMANCE_DATA("Comput_time", computation_time);
  GUDHI_TC_SET_PERFORMANCE_DATA("Perturb_successful",
                                (perturb_success ? 1 : 0));
  GUDHI_TC_SET_PERFORMANCE_DATA("Perturb_time", perturb_time);
  GUDHI_TC_SET_PERFORMANCE_DATA("Perturb_steps", num_perturb_steps);
  GUDHI_TC_SET_PERFORMANCE_DATA("Result_pure_pseudomanifold",
                                (is_pure_pseudomanifold ? 1 : 0));
  GUDHI_TC_SET_PERFORMANCE_DATA("Result_num_wrong_dim_simplices",
                                num_wrong_dim_simplices);
  GUDHI_TC_SET_PERFORMANCE_DATA("Result_num_wrong_number_of_cofaces",
                                num_wrong_number_of_cofaces);
  GUDHI_TC_SET_PERFORMANCE_DATA("Result_num_unconnected_stars",
                                num_unconnected_stars);
  GUDHI_TC_SET_PERFORMANCE_DATA("Info", "");
}

int main() {
  CGAL::set_error_behaviour(CGAL::ABORT);

#ifdef GUDHI_USE_TBB
#ifdef _DEBUG
  int num_threads = 1;
#else
  int num_threads = tbb::task_scheduler_init::default_num_threads() - 4;
#endif
#endif

  std::ifstream script_file;
  script_file.open(BENCHMARK_SCRIPT_FILENAME);
  // Script?
  // Script file format: each line gives
  //    - Filename (point set) or "generate_XXX" (point set generation)
  //    - Ambient dim
  //    - Intrinsic dim
  //    - Number of iterations with these parameters
  if (script_file.is_open()) {
    int i = 1;
#ifdef GUDHI_USE_TBB
#ifdef BENCHMARK_WITH_1_TO_MAX_THREADS
    for (num_threads = 1;
         num_threads <= tbb::task_scheduler_init::default_num_threads();
         ++num_threads)
#endif
#endif
      /*for (Concurrent_mesher_config::get().num_work_items_per_batch = 5 ;
        Concurrent_mesher_config::get().num_work_items_per_batch < 100 ;
        Concurrent_mesher_config::get().num_work_items_per_batch += 5)*/ {
#ifdef GUDHI_USE_TBB
      tbb::task_scheduler_init init(
                                    num_threads > 0 ? num_threads : tbb::task_scheduler_init::automatic);
#endif

      std::cerr << "Script file '" << BENCHMARK_SCRIPT_FILENAME << "' found.\n";
      script_file.seekg(0);
      while (script_file.good()) {
        std::string line;
        std::getline(script_file, line);
        if (line.size() > 1 && line[0] != '#') {
          boost::replace_all(line, "\t", " ");
          boost::trim_all(line);
          std::cerr << "\n\n";
          std::cerr << "*****************************************\n";
          std::cerr << "******* " << line << "\n";
          std::cerr << "*****************************************\n";
          std::stringstream sstr(line);

          std::string input;
          std::string param1;
          std::string param2;
          std::string param3;
          std::size_t num_points;
          int ambient_dim;
          int intrinsic_dim;
          double sparsity;
          double max_perturb;
          char perturb, add_high_dim_simpl, collapse;
          double time_limit_for_perturb;
          int num_iteration;
          sstr >> input;
          sstr >> param1;
          sstr >> param2;
          sstr >> param3;
          sstr >> num_points;
          sstr >> ambient_dim;
          sstr >> intrinsic_dim;
          sstr >> sparsity;
          sstr >> max_perturb;
          sstr >> perturb;
          sstr >> add_high_dim_simpl;
          sstr >> collapse;
          sstr >> time_limit_for_perturb;
          sstr >> num_iteration;

          for (int j = 0; j < num_iteration; ++j) {
            std::string input_stripped = input;
            size_t slash_index = input_stripped.find_last_of('/');
            if (slash_index == std::string::npos)
              slash_index = input_stripped.find_last_of('\\');
            if (slash_index == std::string::npos)
              slash_index = 0;
            else
              ++slash_index;
            input_stripped = input_stripped.substr(
                                                   slash_index, input_stripped.find_last_of('.') - slash_index);

            GUDHI_TC_SET_PERFORMANCE_DATA("Input", input_stripped);
            GUDHI_TC_SET_PERFORMANCE_DATA("Param1", param1);
            GUDHI_TC_SET_PERFORMANCE_DATA("Param2", param2);
            GUDHI_TC_SET_PERFORMANCE_DATA("Param3", param3);
            GUDHI_TC_SET_PERFORMANCE_DATA("Ambient_dim", ambient_dim);
            GUDHI_TC_SET_PERFORMANCE_DATA("Intrinsic_dim", intrinsic_dim);
            GUDHI_TC_SET_PERFORMANCE_DATA("Perturb_technique", "Tangential_translation");
            GUDHI_TC_SET_PERFORMANCE_DATA("Perturb_which_points", "Center_vertex");

#ifdef GUDHI_USE_TBB
            GUDHI_TC_SET_PERFORMANCE_DATA(
                                          "Num_threads",
                                          (num_threads == -1 ? tbb::task_scheduler_init::default_num_threads() : num_threads));
#else
            GUDHI_TC_SET_PERFORMANCE_DATA("Num_threads", "N/A");
#endif

            std::cerr << "\nTC #" << i << "...\n";

#ifdef GUDHI_TC_PROFILING
            Gudhi::Clock t_gen;
#endif

            std::vector<Point> points;
            TC::TS_container tangent_spaces;

            if (input == "generate_moment_curve") {
              points = Gudhi::generate_points_on_moment_curve<Kernel>(
                                                                      num_points, ambient_dim,
                                                                      std::atof(param1.c_str()), std::atof(param2.c_str()));
            } else if (input == "generate_plane") {
              points = Gudhi::generate_points_on_plane<Kernel>(
                                                               num_points, intrinsic_dim, ambient_dim);
            } else if (input == "generate_sphere_d") {
              points = Gudhi::generate_points_on_sphere_d<Kernel>(
                                                                  num_points, ambient_dim,
                                                                  std::atof(param1.c_str()),  // radius
                                                                  std::atof(param2.c_str()));  // radius_noise_percentage
            } else if (input == "generate_two_spheres_d") {
              points = Gudhi::generate_points_on_two_spheres_d<Kernel>(
                                                                       num_points, ambient_dim,
                                                                       std::atof(param1.c_str()),
                                                                       std::atof(param2.c_str()),
                                                                       std::atof(param3.c_str()));
            } else if (input == "generate_3sphere_and_circle_d") {
              GUDHI_CHECK(intrinsic_dim == 3,
                          std::logic_error("Intrinsic dim should be 3"));
              GUDHI_CHECK(ambient_dim == 5,
                          std::logic_error("Ambient dim should be 5"));
              points = Gudhi::generate_points_on_3sphere_and_circle<Kernel>(
                                                                            num_points,
                                                                            std::atof(param1.c_str()));
            } else if (input == "generate_torus_3D") {
              points = Gudhi::generate_points_on_torus_3D<Kernel>(
                                                                  num_points,
                                                                  std::atof(param1.c_str()),
                                                                  std::atof(param2.c_str()),
                                                                  param3 == "Y");
            } else if (input == "generate_torus_d") {
              points = Gudhi::generate_points_on_torus_d<Kernel>(
                                                                 num_points,
                                                                 intrinsic_dim,
                                                                 (param1 == "Y") ? "grid" : "random",  // grid or random sample type
                                                                 std::atof(param2.c_str()));  // radius_noise_percentage
            } else if (input == "generate_klein_bottle_3D") {
              points = Gudhi::generate_points_on_klein_bottle_3D<Kernel>(
                                                                         num_points,
                                                                         std::atof(param1.c_str()), std::atof(param2.c_str()));
            } else if (input == "generate_klein_bottle_4D") {
              points = Gudhi::generate_points_on_klein_bottle_4D<Kernel>(
                                                                         num_points,
                                                                         std::atof(param1.c_str()), std::atof(param2.c_str()),
                                                                         std::atof(param3.c_str()));  // noise
            } else if (input == "generate_klein_bottle_variant_5D") {
              points = Gudhi::generate_points_on_klein_bottle_variant_5D<Kernel>(
                                                                                num_points,
                                                                                std::atof(param1.c_str()), std::atof(param2.c_str()));
            } else {
              // Contains tangent space basis
              if (input.substr(input.size() - 3) == "pwt") {
                load_points_and_tangent_space_basis_from_file
                    <Kernel, typename TC::Tangent_space_basis > (
                                                                 input, std::back_inserter(points),
                                                                 std::back_inserter(tangent_spaces),
                                                                 intrinsic_dim,
                                                                 ONLY_LOAD_THE_FIRST_N_POINTS);
              } else {
                load_points_from_file<Kernel>(
                                              input, std::back_inserter(points),
                                              ONLY_LOAD_THE_FIRST_N_POINTS);
              }
            }

#ifdef GUDHI_TC_PROFILING
            t_gen.end();
            std::cerr << "Point set generated/loaded in " << t_gen.num_seconds()
                << " seconds.\n";
#endif

            if (!points.empty()) {
#if defined(TC_INPUT_STRIDES) && TC_INPUT_STRIDES > 1
              auto p = points | boost::adaptors::strided(TC_INPUT_STRIDES);
              std::vector<Point> points(p.begin(), p.end());
              std::cerr << "****************************************\n"
                  << "WARNING: taking 1 point every " << TC_INPUT_STRIDES
                  << " points.\n"
                  << "****************************************\n";
#endif

              make_tc(points, tangent_spaces, intrinsic_dim,
                      sparsity, max_perturb,
                      perturb == 'Y', add_high_dim_simpl == 'Y', collapse == 'Y',
                      time_limit_for_perturb, input.c_str());

              std::cerr << "TC #" << i++ << " done.\n";
              std::cerr << "\n---------------------------------\n";
            } else {
              std::cerr << "TC #" << i++ << ": no points loaded.\n";
            }

            XML_perf_data::commit();
          }
        }
      }
      script_file.seekg(0);
      script_file.clear();
    }

    script_file.close();
  }    // Or not script?
  else {
    std::cerr << "Script file '" << BENCHMARK_SCRIPT_FILENAME << "' NOT found.\n";
  }

  // system("pause");
  return 0;
}


#ifndef __SPARSEINDEXSET__CLASS__
#define __SPARSEINDEXSET__CLASS__

// Eigen Library
#include <Eigen/Dense>

#include "CONSTANTS.hpp"

class SparseIndexSet {
 public:
  /**     \brief initializes the sparse anisotropic index set
  *       \param[in] q is the maximum level for the sparse index set
  *       \param[in] dim is the dimension of the sparse index set
  *       \param[in] cpFun is the comparison Function. sparse index set is
  *                  constructed such that cpFun(alpha) <= q for all alpha in
  *                  the sparse index set
  *
  * 	    Additionally, the set of multiindices _alpha and the weights _cw
  *     	 are provided globally.
  *
  * 	    The function comp_indexSet computes a sparse multi index set
  * 	    _alpha\subset\N^n by the function call
  *       sparseIndexSet::Yw_alpha(0, &n, cpFun)
  * 	    and corresponding weights _cw \in \Z^n to each multi index.
  * 	    To determine the weights, we have to check for each multi index
  *       alpha contained in _alpha if alpha + beta is in _alpha for each beta
  *       in {0,1}^{n}. If this is the case, we update the weight by
  *       (-1)^|beta|. Of course, the condition is always fulfilled for the
  *       multiindex beta = 0. Therefore, we skip this multiindex and initialize
  *       the weight cw with 1 which is associated with the second variable in
  *       the function call to cw_alpha.
  * 	    For a multi index alpha, the corresponding weight has only to be
  *       computed if the multiindex alpha+1 is not contained in _alpha.
  *       Otherwise, the corresponding weight is 0. With the index set, the
  *       weights and the unidirectional quadrature formulas at hand, the
  *       integral I(f) is approximated by the combination technique formula
  * 	    I(f) \approx \sum_{i =0}^{_alpha.cols-1} _cw(i)*Q_{alpha.col(i)}.
  * 	    Herein, _alpha.cols determines the number of multiindices in _alpha
  *       and alpha.col(i) gives the i-th index in _alpha. The tensor product
  *       quadrature formula Q_{alpha.col(i)} is specified by the unidirectional
  *       quadrature formulas.
  */
  template <class Compare>
  void computeIndexSet(int q, int dim, const Compare &cpFun) {
    int k = 0;
    Eigen::VectorXi currInd(dim);
    // set max level for the sparse index set
    _q = q;
    // set dimension of the sparse grid
    _dim = dim;
    // allocate memory for _alpha and _cw to keep the overhead at a low level
    // blocks of size __MEMCHUNKSIZE__ are allocated at once
    _alpha.resize(_dim, __MEMCHUNKSIZE__);
    _cw.resize(__MEMCHUNKSIZE__);
    // set memory to 0
    _alpha.setZero();
    _cw.setZero();
    // get a vector with all ones to reduce the overhead for the constructor of
    // Eigen::VectorXi::Ones(_dim);
    _myOnes = Eigen::VectorXi::Ones(_dim);
    // start from multi index 0\in Xw_alpha
    currInd.setZero();
    k = 0;
    // test if 0 is in Xw_alpha, else index set empty due to downward closedness
    if (cpFun(currInd) <= _q) {
      // check if _cw(0)\neq 0
      if (cpFun(_myOnes) > _q) _cw(0) = combiWeights(0, 1, 1, currInd, cpFun);
      if (_cw(0)) ++k;
      // compute all other indices in Yw_alpha recursively
      combiIndexSet(0, &k, cpFun, currInd);
    }
    // crop memory for _alpha and _cw to actual size
    _alpha.conservativeResize(_dim, k);
    _cw.conservativeResize(k);
  }

  /**   \brief make an educated guess, what this function does...
  *
  */
  const Eigen::MatrixXi &get_alpha(void) const { return _alpha; };

  /**   \brief make an educated guess, what this function does...
  *
  */
  const Eigen::VectorXi &get_cw(void) const { return _cw; };

 protected:
  /**     \brief computes indices in weighted sparse grid space (recursive)
  *	    \param[in] maxBit position of the digit of the current multiindex
  *                  such that only digits with position greater or equal to
  *                  maxBit are considered in the recursion.
  *	 	 \param[in] k counter for the number of multiindices is passed
  *                  as pointer
  *	    \param[in] cpFun function/functor/whatever which decides whether a
  *                  multiindex is in the indexset or not.
  *
  *	    Additionally, the level of the sparse grid _q, the dimension of the
  *	    multiindices _dim and the memory _alpha for the indexset is
  *	    provided globally and referred to.
  *
  *	    The function Yw_alpha recursively computes the indexset of a
  *	    generalized sparse grid which is described by a level _q and a
  *	    function cpFun, i.e. it finds all multiindices alpha such that
  *	    cpFun(alpha)<= _q. To that end, we travel the set Xw_alpha and
  *       at a multi index only if the corresponding _cw is non zero.
  *	    The function needs to be called with input parameters
  *	    maxBit=0, k=0 defined as a reference, predefined
  *	    function cpFun which maps from \mathbb{N}^{_dim}\to \mathbb{R}.
  *	    Since cpFun(0)=0, the zero multiindex is always the first index
  *	    which is included in the sparse grid on default and serves as the
  *	    root node in the tree structure of the index set.
  *	    On level l of the tree, all indices alpha with
  *	    cpFun(alpha)<= _q and |alpha| = l-1 are computed.
  *	    Moreover, the tree is structured such that all indices alpha' which
  *	    are sons of a multiindex alpha fulfill alpha'>=alpha elementwise.
  *	    This structure immediately allows to exploit the downward closeness
  *	    of the indexset since we do not need a further recursion level
  *	    at an edge where the corresponding index does not belong to the
  *       indexset.
  *	    Moreover, the integer maxBit is necessary to avoid multiple
  *       repetition of multiindices. In every step maxBit is the position of
  *       the digit which is modified to obtain the current multiindex. In the
  *       subtree corresponding to this index only digits greater or equal to
  *       maxBit will be considered for modification. A simple example for the
  *       tree structure for _q=2, _dim=2 and cpFun(alpha) = alpha_1+alpha_2 is
  *       depicted below.
  *
  *								(0,0;mB=0;*k=0)
  *								/            \
  *						(1,0;mB=0;*k=1) (0,1;mB=1;*k=4)
  *						/		      \               \
  *			(2,0;mB=0;*k=2) (1,1;mB=1;*k=3)	(0,2;mB=1;*k=5)
  *
  */
  template <class Compare>
  void combiIndexSet(int maxBit, int *k, const Compare &cpFun,
                     Eigen::VectorXi &currInd) {
    int cw = 0;
    // successively increase all entries in the current index
    for (int i = maxBit; i < _dim; ++i) {
      ++currInd(i);
      if (cpFun(currInd) <= _q) {
        // if the index is in Xw_alpha, check if its weight is non zero
        if (cpFun(currInd + _myOnes) > _q)
          cw = combiWeights(0, 1, 1, currInd, cpFun);
        else
          cw = 0;
        // if its weight is non zero, add it to the index set
        if (cw) {
          if (_alpha.cols() <= *k) {
            _alpha.conservativeResize(_dim, _alpha.cols() + __MEMCHUNKSIZE__);
            _cw.conservativeResize(_cw.size() + __MEMCHUNKSIZE__);
          }
          _alpha.col(*k) = currInd;
          _cw(*k) = cw;
          ++(*k);
        }
        // check son indices only if father index is in Xw. this is possible
        // due to the downward closedness assumption
        combiIndexSet(i, k, cpFun, currInd);
      }
      --currInd(i);
    }
  }

  /**     \brief computes weights for the tensor product quadrature (recursive)
  *	    \param[in] maxBit position of the digit of the current multiindex
  *                  such that only digits with position greater to maxBit are
  *	 		         considered in the recursion.
  *       \param[in] cw is initialize by 1 due to alpha+0\in Xw if alpha in Xw
  *	    \param[in] lvl current level-1 in the recursion tree.
  *	    \param[in] ind current multi index alpha
  *	    \param[in] cpFun function/functor/whatever which decides whether a
  *                  multi index is in the indexset or not.
  *
  *	    Additionally, the level of the sparse grid _q, the dimension of the
  *	    multiindices _dim and the memory _alpha for the indexset is
  *	    provided globally.
  *
  *	    The function cw_alpha computes the coefficient of the indth
  *	    multi index in the combination technique formula corresponding to
  *	    the generalized sparse quadrature. This coefficient is given by
  *	    cw(alpha) = sum_{beta in {0,1}^_dim, alpha+beta in _alpha}
  *       (-1)^|beta|. Hence, in order to determine the value of the
  *       coefficient, we have to check for each beta in {0,1}^_dim if
  *       alpha+beta belongs to the index set _alpha. For beta=(0,...,0),
  *       alpha+beta belongs to _alpha and, hence, we
  *       initialize cw=1 and do not have to consider this multiindex.
  *	    The downward closeness of the index set can be exploited once more
  *	    which results in a similar tree structure as in the function
  *       Yw_alpha. The only difference of the tree is that once a digit of the
  *       multiindex alpha is modified, we do not have to consider further
  *       modification of this digit due to the condition beta in {0,1}^_dim.
  *       This is realized in the recursive algorithm consider only digits which
  *       are greater than maxBit in contrast to the "greater than and equal"
  *       condition in Yw_alpha.
  *	    In the recursive algorithm, the multiindices beta do not need to be
  *       computed explicitly, but the multiindex alpha is modified at each node
  *       of the tree. Of course, this modification has to be revoked when the
  *       recursion goes back to the parent node in the tree.
  *	    The value of lvl=|beta| is important since it determines whether the
  *	    coefficient is modified by +1 or -1.
  *	    A simple example of the algorithm for _q=2, _dim=2, ind = (1,0) and
  *	    cpFun(alpha) = alpha_1+alpha_2 is depicted below.
  *	    The further initial values are maxBit=mB=0, cw = 1, lvl=0.
  *
  *								(1,0;mB=0;lvl=0;cw=1)
  *								/                   \
  *					(2,0;mB=0;lvl=1;cw=0)   (1,1;mB=1;lvl=1;cw=-1)
  *							/
  *			[ (2,1;mB=1;lvl=2;cw=0) ] <-- is not contained in the
  *                                   indexset, no modification of cw
  *
  *	    The algorithm returns the final value of cw. In the example above
  *       this would be the correct value cw(1,0)=-1.
  */
  template <class Compare>
  int combiWeights(int maxBit, int cw, int lvl, Eigen::VectorXi &ind,
                   const Compare &cpFun) {
    // successively check all bits of the multiindex is contained
    for (int i = maxBit; i < _dim; ++i) {
      ++ind(i);
      if (cpFun(ind) <= _q) {
        if (lvl % 2)
          --cw;
        else
          ++cw;
        // again, exploit downward closedness and perform recursion only
        // if father is contained in Xw_alpha
        cw = combiWeights(i + 1, cw, lvl + 1, ind, cpFun);
      }
      --ind(i);
    }

    return cw;
  }

  // Member variables
  Eigen::MatrixXi _alpha;
  Eigen::VectorXi _cw;
  Eigen::VectorXi _myOnes;
  int _dim;
  int _q;
};
#endif


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_NEARBYINT_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_NEARBYINT_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-arithmetic
    Function object implementing nearbyint capabilities

    Computes the rounded to even value of its parameter.

    @par semantic:
    For any given value @c x of type @c T:

    @code
    T r = nearbyint(x);
    @endcode

    Returns the nearest integer to x.

    @par Note:
    - If arg is /f$\infty/f$, it is returned, unmodified
    - If arg is $\pm0/f$, it is returned, unmodified
    - If arg is NaN, NaN is returned

    - to even means that half integer values are rounded to the nearest
    even value.

    This function is in general quicker than @ref round which do the rouding on
    half integer values away from zero.

    @see round, ceil, floor, trunc
  **/
  Value nearbyint(Value const & x);
} }
#endif

#include <boost/simd/function/scalar/nearbyint.hpp>
#include <boost/simd/function/simd/nearbyint.hpp>

#endif


#include <stan/math/prim/scal.hpp>
#include <gtest/gtest.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/math/distributions.hpp>
#include <test/unit/math/prim/scal/prob/util.hpp>
#include <limits>
#include <vector>

TEST(ProbDistributionsPareto, error_check) {
  boost::random::mt19937 rng;
  EXPECT_NO_THROW(stan::math::pareto_rng(2.0, 1.0, rng));

  EXPECT_THROW(stan::math::pareto_rng(2.0, -1.0, rng), std::domain_error);
  EXPECT_THROW(stan::math::pareto_rng(-2.0, 1.0, rng), std::domain_error);
  EXPECT_THROW(
      stan::math::pareto_rng(stan::math::positive_infinity(), 1.0, rng),
      std::domain_error);
  EXPECT_THROW(stan::math::pareto_rng(2, stan::math::positive_infinity(), rng),
               std::domain_error);
}

TEST(ProbDistributionsPareto, chiSquareGoodnessFitTest) {
  boost::random::mt19937 rng;
  int N = 10000;
  int K = boost::math::round(2 * std::pow(N, 0.4));

  std::vector<double> samples;
  for (int i = 0; i < N; ++i) {
    samples.push_back(stan::math::pareto_rng(2.0, 1.0, rng));
  }

  // Generate quantiles from boost's Pareto distribution
  boost::math::pareto_distribution<> dist(2.0, 1.0);
  std::vector<double> quantiles;
  for (int i = 1; i < K; ++i) {
    double frac = static_cast<double>(i) / K;
    quantiles.push_back(quantile(dist, frac));
  }
  quantiles.push_back(std::numeric_limits<double>::max());

  // Assert that they match
  assert_matches_quantiles(samples, quantiles, 1e-6);
}