#define BOOST_TEST_MAIN

#include <string>

#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>

#include "Tudat/Mathematics/BasicMathematics/linearAlgebra.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/physicalConstants.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/orbitalElementConversions.h"

#include "Tudat/External/SpiceInterface/spiceInterface.h"
#include "Tudat/Mathematics/NumericalIntegrators/rungeKuttaCoefficients.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/accelerationModel.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/keplerPropagator.h"
#include "Tudat/SimulationSetup/tudatSimulationHeader.h"
#include "Tudat/SimulationSetup/PropagationSetup/dynamicsSimulator.h"

namespace tudat
{

namespace unit_tests
{

//! Using declarations.
using namespace tudat;
using namespace interpolators;
using namespace numerical_integrators;
using namespace spice_interface;
using namespace ephemerides;
using namespace simulation_setup;
using namespace basic_astrodynamics;
using namespace orbital_element_conversions;
using namespace propagators;
using namespace unit_conversions;

BOOST_AUTO_TEST_SUITE( test_hybrid_arc_dynamics )

//! Reset Mats ephemeris to tabulated ephemeris from default settings
void resetMarsEphemeris(
        const NamedBodyMap& bodyMap, const double ephemerisStartTime, const double ephemerisEndTime )
{
    std::shared_ptr< Ephemeris > marsEphemeris =
            createBodyEphemeris( getDefaultEphemerisSettings( "Mars", ephemerisStartTime, ephemerisEndTime ), "Mars" );

    std::shared_ptr< OneDimensionalInterpolator< double, Eigen::Vector6d > > defaultMarsStateInterpolator =
            std::dynamic_pointer_cast< TabulatedCartesianEphemeris< double, double > >( marsEphemeris )->getInterpolator( );

    std::dynamic_pointer_cast< TabulatedCartesianEphemeris< double, double > >(
                bodyMap.at( "Mars" )->getEphemeris( ) )->resetInterpolator( defaultMarsStateInterpolator );
}

//! Test if hybrid-arc orbit propagation is done correctly (single-arc Mars w.r.t. SSB and multi-arc orbiter w.r.t. Mars)
BOOST_AUTO_TEST_CASE( testHybridArcDynamics )
{
    //Load spice kernels.
    spice_interface::loadStandardSpiceKernels( );

    // Specify initial time
    double initialEphemerisTime = 1.0E7;
    double finalEphemerisTime = 1.5E7;
    double maximumTimeStep = 3600.0;
    double buffer = 5.0 * maximumTimeStep;

    // Create bodies needed in simulation
    std::vector< std::string > bodyNames;
    bodyNames.push_back( "Sun" );
    bodyNames.push_back( "Mars" );
    bodyNames.push_back( "Jupiter" );
    bodyNames.push_back( "Earth" );
    std::map< std::string, std::shared_ptr< BodySettings > > bodySettings =
            getDefaultBodySettings( bodyNames, initialEphemerisTime - buffer, finalEphemerisTime + buffer );
    NamedBodyMap bodyMap = createBodies( bodySettings );

    // Create orbiter
    bodyMap[ "Orbiter" ] = std::make_shared< Body >( );
    bodyMap[ "Orbiter" ]->setConstantBodyMass( 5.0E3 );
    bodyMap[ "Orbiter" ]->setEphemeris( std::make_shared< MultiArcEphemeris >(
                                            std::map< double, std::shared_ptr< Ephemeris > >( ),
                                            "Mars", "ECLIPJ2000" ) );

    // Create and set radiation pressure settings
    double referenceAreaRadiation = 4.0;
    double radiationPressureCoefficient = 1.2;
    std::vector< std::string > occultingBodies;
    occultingBodies.push_back( "Earth" );
    std::shared_ptr< RadiationPressureInterfaceSettings > orbiterRadiationPressureSettings =
            std::make_shared< CannonBallRadiationPressureInterfaceSettings >(
                "Sun", referenceAreaRadiation, radiationPressureCoefficient, occultingBodies );
    bodyMap[ "Orbiter" ]->setRadiationPressureInterface(
                "Sun", createRadiationPressureInterface(
                    orbiterRadiationPressureSettings, "Orbiter", bodyMap ) );

    // Finalize body creation.
    setGlobalFrameBodyEphemerides( bodyMap, "SSB", "ECLIPJ2000" );

    // Set accelerations for Mars
    SelectedAccelerationMap singleArcAccelerationMap;
    std::map< std::string, std::vector< std::shared_ptr< AccelerationSettings > > > accelerationsOfMars;
    accelerationsOfMars[ "Earth" ].push_back( std::make_shared< AccelerationSettings >( central_gravity ) );
    accelerationsOfMars[ "Sun" ].push_back( std::make_shared< AccelerationSettings >( central_gravity ) );
    accelerationsOfMars[ "Jupiter" ].push_back( std::make_shared< AccelerationSettings >( central_gravity ) );
    singleArcAccelerationMap[ "Mars" ] = accelerationsOfMars;

    std::vector< std::string > singleArcBodiesToIntegrate, singleArcCentralBodies;
    singleArcBodiesToIntegrate.push_back( "Mars" );
    singleArcCentralBodies.push_back( "SSB" );

    AccelerationMap singleArcAccelerationModelMap = createAccelerationModelsMap(
                bodyMap, singleArcAccelerationMap, singleArcBodiesToIntegrate, singleArcCentralBodies );

    // Create single-arc propagator settings for Mars
    Eigen::VectorXd singleArcInitialStates = getInitialStatesOfBodies(
                singleArcBodiesToIntegrate, singleArcCentralBodies, bodyMap, initialEphemerisTime );
    std::shared_ptr< TranslationalStatePropagatorSettings< > > singleArcPropagatorSettings =
            std::make_shared< TranslationalStatePropagatorSettings< > >(
                singleArcCentralBodies, singleArcAccelerationModelMap, singleArcBodiesToIntegrate,
                singleArcInitialStates, finalEphemerisTime );

    // Set accelerations for Orbiter
    SelectedAccelerationMap multiArcAccelerationMap;
    std::map< std::string, std::vector< std::shared_ptr< AccelerationSettings > > > accelerationsOfOrbiter;
    accelerationsOfOrbiter[ "Mars" ].push_back( std::make_shared< SphericalHarmonicAccelerationSettings >( 2, 2 ) );
    accelerationsOfOrbiter[ "Sun" ].push_back( std::make_shared< AccelerationSettings >( central_gravity ) );
    accelerationsOfOrbiter[ "Sun" ].push_back( std::make_shared< AccelerationSettings >( cannon_ball_radiation_pressure ) );
    accelerationsOfOrbiter[ "Jupiter" ].push_back( std::make_shared< AccelerationSettings >( central_gravity ) );
    multiArcAccelerationMap[ "Orbiter" ] = accelerationsOfOrbiter;

    std::vector< std::string > multiArcBodiesToIntegrate, multiArcCentralBodies;
    multiArcBodiesToIntegrate.push_back( "Orbiter" );
    multiArcCentralBodies.push_back( "Mars" );

    AccelerationMap multiArcAccelerationModelMap = createAccelerationModelsMap(
                bodyMap, multiArcAccelerationMap, multiArcBodiesToIntegrate, multiArcCentralBodies );

    // Creater arc times for orbiter
    std::vector< double > integrationArcStarts, integrationArcEnds;
    double integrationStartTime = initialEphemerisTime + 1.0E4;
    double integrationEndTime = finalEphemerisTime - 1.0E4;
    double arcDuration = 1.0E6;
    double arcOverlap = 1.0E4;
    double currentStartTime = integrationStartTime;
    double currentEndTime = integrationStartTime + arcDuration;
    do
    {
        integrationArcStarts.push_back( currentStartTime );
        integrationArcEnds.push_back( currentEndTime );

        currentStartTime = currentEndTime - arcOverlap;
        currentEndTime = currentStartTime + arcDuration;
    }
    while( currentEndTime < integrationEndTime );

    // Create list of multi-arc Orbiter initial states
    unsigned int numberOfIntegrationArcs = integrationArcStarts.size( );
    std::vector< Eigen::VectorXd > multiArcSystemInitialStates;
    multiArcSystemInitialStates.resize( numberOfIntegrationArcs );

    // Define (quasi-arbitrary) arc Orbiter initial states
    double marsGravitationalParameter =  bodyMap.at( "Mars" )->getGravityFieldModel( )->getGravitationalParameter( );
    for( unsigned int j = 0; j < numberOfIntegrationArcs; j++ )
    {
        Eigen::Vector6d orbiterInitialStateInKeplerianElements;
        orbiterInitialStateInKeplerianElements( semiMajorAxisIndex ) = 4000.0E3;
        orbiterInitialStateInKeplerianElements( eccentricityIndex ) = 0.05;
        orbiterInitialStateInKeplerianElements( inclinationIndex ) = convertDegreesToRadians( 85.3 );
        orbiterInitialStateInKeplerianElements( argumentOfPeriapsisIndex )
                = convertDegreesToRadians( 235.7 - j );
        orbiterInitialStateInKeplerianElements( longitudeOfAscendingNodeIndex )
                = convertDegreesToRadians( 23.4 + j );
        orbiterInitialStateInKeplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 139.87 + j * 10.0 );

        // Convert state from Keplerian elements to Cartesian elements.
        multiArcSystemInitialStates[ j ]  = convertKeplerianToCartesianElements(
                    orbiterInitialStateInKeplerianElements,
                    marsGravitationalParameter );;
    }

    // Create multi-arc propagator settings for Orbiter
    std::vector< std::shared_ptr< SingleArcPropagatorSettings< double > > > arcPropagationSettingsList;
    for( unsigned int i = 0; i < numberOfIntegrationArcs; i++ )
    {
        arcPropagationSettingsList.push_back(
                    std::make_shared< TranslationalStatePropagatorSettings< double > >
                    ( multiArcCentralBodies, multiArcAccelerationModelMap, multiArcBodiesToIntegrate,
                      multiArcSystemInitialStates.at( i ), integrationArcEnds.at( i ) ) );
    }
    std::shared_ptr< MultiArcPropagatorSettings< > > multiArcPropagatorSettings =
            std::make_shared< MultiArcPropagatorSettings< > >( arcPropagationSettingsList );

    // Perform separate single-arc propagation
    std::map< double, Eigen::VectorXd > singleArcSolution;
    {
        std::shared_ptr< IntegratorSettings< > > integratorSettings =
                std::make_shared< IntegratorSettings< > >
                ( rungeKutta4, initialEphemerisTime, 120.0 );
        SingleArcDynamicsSimulator< > singleArcDynamicsSimulator(
                    bodyMap, integratorSettings, singleArcPropagatorSettings, true, false, true );
        singleArcSolution = singleArcDynamicsSimulator.getEquationsOfMotionNumericalSolution( );
    }

    // Perform separate multi-arc propagation
    std::vector< std::map< double, Eigen::VectorXd > > multiArcSolution;
    {
        std::shared_ptr< IntegratorSettings< > > integratorSettings =
                std::make_shared< IntegratorSettings< > >
                ( rungeKutta4, initialEphemerisTime, 120.0 );
        MultiArcDynamicsSimulator< > multiArcDynamicsSimulator(
                    bodyMap, integratorSettings, multiArcPropagatorSettings, integrationArcStarts, true, false, false );
        multiArcSolution = multiArcDynamicsSimulator.getEquationsOfMotionNumericalSolution( );
    }

    // Perform hybrid arc propagation
    resetMarsEphemeris( bodyMap, initialEphemerisTime - buffer, finalEphemerisTime + buffer );
    std::map< double, Eigen::VectorXd > singleArcSolutionFromHybrid;
    std::vector< std::map< double, Eigen::VectorXd > > multiArcSolutionFromHybrid;
    {
        std::shared_ptr< IntegratorSettings< > > integratorSettings =
                std::make_shared< IntegratorSettings< > >
                ( rungeKutta4, initialEphemerisTime, 120.0 );
        HybridArcDynamicsSimulator< > hybridArcDynamicsSimulator(
                    bodyMap, integratorSettings, std::make_shared< HybridArcPropagatorSettings< > >(
                        singleArcPropagatorSettings, multiArcPropagatorSettings ), integrationArcStarts );

        singleArcSolutionFromHybrid = hybridArcDynamicsSimulator.getSingleArcDynamicsSimulator( )->
                getEquationsOfMotionNumericalSolution( );
        multiArcSolutionFromHybrid = hybridArcDynamicsSimulator.getMultiArcDynamicsSimulator( )->
                getEquationsOfMotionNumericalSolution( );
    }

    // Compare separate propagation results with hybrid arc
    BOOST_CHECK_EQUAL( singleArcSolutionFromHybrid.size( ), singleArcSolution.size( ) );

    std::map< double, Eigen::VectorXd >::const_iterator singleArcSolutionFromHybridIterator =
            singleArcSolutionFromHybrid.begin( );
    std::map< double, Eigen::VectorXd >::const_iterator singleArcSolutionIterator =
            singleArcSolution.begin( );

    for( unsigned int i = 0; i < singleArcSolution.size( ); i++ )
    {
        BOOST_CHECK_CLOSE_FRACTION(
                    singleArcSolutionFromHybridIterator->first, singleArcSolutionIterator->first,
                    4.0 * std::numeric_limits< double >::epsilon( ) );
        for( unsigned int j = 0; j < 3; j++ )
        {
            BOOST_CHECK_SMALL(
                        std::fabs(
                            singleArcSolutionFromHybridIterator->second( j ) -
                            singleArcSolutionIterator->second( j ) ), 1.0E-5 );
            BOOST_CHECK_SMALL(
                        std::fabs(
                            singleArcSolutionFromHybridIterator->second( j + 3 ) -
                            singleArcSolutionIterator->second( j + 3  ) ), 1.0E-11 );
        }
        singleArcSolutionFromHybridIterator++;
        singleArcSolutionIterator++;
    }

    BOOST_CHECK_EQUAL( multiArcSolutionFromHybrid.size( ), multiArcSolution.size( ) );
    for( unsigned int arc = 0; arc < multiArcSolutionFromHybrid.size( ); arc++ )
    {
        BOOST_CHECK_EQUAL( multiArcSolutionFromHybrid.at( arc ).size( ), multiArcSolution.at( arc ).size( ) );

        std::map< double, Eigen::VectorXd >::const_iterator currentArcSolutionFromHybridIterator =
                multiArcSolutionFromHybrid.at( arc ).begin( );
        std::map< double, Eigen::VectorXd >::const_iterator currentArcSolutionIterator =
                multiArcSolution.at( arc ).begin( );

        for( unsigned int i = 0; i < multiArcSolution.at( arc ).size( ); i++ )
        {
            BOOST_CHECK_CLOSE_FRACTION(
                        currentArcSolutionFromHybridIterator->first, currentArcSolutionIterator->first,
                        4.0 * std::numeric_limits< double >::epsilon( ) );
            for( unsigned int j = 0; j < 3; j++ )
            {
                BOOST_CHECK_SMALL(
                            std::fabs(
                                currentArcSolutionFromHybridIterator->second( j ) -
                                currentArcSolutionIterator->second( j ) ), 1.0E-5 );
                BOOST_CHECK_SMALL(
                            std::fabs(
                                currentArcSolutionFromHybridIterator->second( j + 3 ) -
                                currentArcSolutionIterator->second( j + 3  ) ), 1.0E-11 );
            }
            currentArcSolutionFromHybridIterator++;
            currentArcSolutionIterator++;
        }
    }
}

BOOST_AUTO_TEST_SUITE_END( )

}

}


// Copyright (c) 2015-2018 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "variational_bayes_mixture_mixture_model.hpp"

#include <utility>
#include <cmath>
#include <iterator>
#include <algorithm>
#include <numeric>
#include <iostream>

#include <boost/math/special_functions/digamma.hpp>

#include "utils/maths.hpp"

namespace octopus { namespace model {

VariationalBayesMixtureMixtureModel::VariationalBayesMixtureMixtureModel(Options options)
: options_ {std::move(options)}
{}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const GroupConcentrationVector& group_concentrations,
                                              const MixtureConcentrationArray& mixture_concentrations,
                                              std::vector<LogProbabilityVector> seeds) const
{
    Inferences result {};
    result.approx_log_evidence = std::numeric_limits<double>::lowest();
    for (auto& seed : seeds) {
        auto inferences = evaluate(genotype_log_priors, log_likelihoods, group_concentrations, mixture_concentrations, seed);
        if (inferences.approx_log_evidence > result.approx_log_evidence) {
            result = std::move(inferences);
        }
    }
    return result;
}

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const double group_concentration, const double mixture_concentration,
                                              std::vector<LogProbabilityVector> seeds) const
{
    const auto num_samples = log_likelihoods.size();
    const auto num_groups = log_likelihoods.front().front().size();
    std::vector<unsigned> group_mixture_sizes {};
    group_mixture_sizes.reserve(num_groups);
    for (const auto& genotype : log_likelihoods.front().front()) {
        group_mixture_sizes.push_back(genotype.size());
    }
    const GroupConcentrationVector group_concentrations(num_groups, group_concentration);
    MixtureConcentrationArray mixture_concentrations(num_samples, MixtureConcentrationVector(num_groups));
    for (auto& sample_concentrations : mixture_concentrations) {
        unsigned t {0};
        for (auto& sample_group_sample_concentrations : sample_concentrations) {
            sample_group_sample_concentrations.assign(group_mixture_sizes[t++], mixture_concentration);
        }
    }
    return evaluate(genotype_log_priors, log_likelihoods, group_concentrations, mixture_concentrations, std::move(seeds));
}

// Private methods

namespace {

VariationalBayesMixtureMixtureModel::ProbabilityVector&
exp(const LogProbabilityVector& log_probabilities, ProbabilityVector& result) noexcept
{
    std::transform(std::cbegin(log_probabilities), std::cend(log_probabilities), std::begin(result),
                   [] (const auto lp) noexcept { return std::exp(lp); });
    return result;
}

auto exp(const VariationalBayesMixtureMixtureModel::LogProbabilityVector& log_probabilities)
{
    VariationalBayesMixtureMixtureModel::ProbabilityVector result(log_probabilities.size());
    return exp(log_probabilities, result);
}

template <typename T>
auto sum(const std::vector<T>& values) noexcept
{
    return std::accumulate(std::cbegin(values), std::cend(values), T {0});
}

auto sum(const VBReadLikelihoodArray& likelihoods) noexcept
{
    using T = VBReadLikelihoodArray::BaseType::value_type;
    return std::accumulate(std::cbegin(likelihoods), std::cend(likelihoods), T {0});
}

template <typename T1, typename T2>
auto inner_product(const T1& lhs, const T2& rhs) noexcept
{
    assert(std::distance(std::cbegin(lhs), std::cend(lhs)) == std::distance(std::cbegin(rhs), std::cend(rhs)));
    using T = typename T1::value_type;
    return std::inner_product(std::cbegin(lhs), std::cend(lhs), std::cbegin(rhs), T {0});
}

template <typename T>
inline auto digamma_diff(const T a, const T b)
{
    using boost::math::digamma;
    return digamma(a) - digamma(b);
}

template <typename T>
auto dirichlet_expectation_log(const std::vector<T>& concentrations)
{
    std::vector<T> result(concentrations.size());
    const auto alpha_0= sum(concentrations);
    std::transform(std::cbegin(concentrations), std::cend(concentrations), std::begin(result),
                   [=] (auto alpha) { return digamma_diff(alpha, alpha_0); });
    return result;
}

template <typename T>
T log_sum_exp(const std::vector<T>& logs)
{
    return maths::log_sum_exp(logs);
}

} // namespace

VariationalBayesMixtureMixtureModel::Inferences
VariationalBayesMixtureMixtureModel::evaluate(const LogProbabilityVector& genotype_log_priors,
                                              const HaplotypeLikelihoodMatrix& log_likelihoods,
                                              const GroupConcentrationVector& prior_group_concentrations,
                                              const MixtureConcentrationArray& prior_mixture_concentrations,
                                              LogProbabilityVector& genotype_log_posteriors) const
{
    auto genotype_posteriors = exp(genotype_log_posteriors);
    auto posterior_group_concentrations = prior_group_concentrations;
    auto posterior_mixture_concentrations = prior_mixture_concentrations;
    auto group_responsabilities = init_responsabilities(prior_group_concentrations, prior_mixture_concentrations,
                                                        genotype_posteriors, log_likelihoods);
    auto component_responsabilities = init_responsabilities(prior_group_concentrations, prior_mixture_concentrations,
                                                            genotype_posteriors, group_responsabilities, log_likelihoods);
    auto prev_evidence = std::numeric_limits<double>::lowest();
    for (unsigned i {0}; i < options_.max_iterations; ++i) {
        update_genotype_log_posteriors(genotype_log_posteriors, genotype_log_priors,
                                       group_responsabilities, component_responsabilities,
                                       log_likelihoods);
        exp(genotype_log_posteriors, genotype_posteriors);
        update_group_concentrations(posterior_group_concentrations, prior_group_concentrations, group_responsabilities);
        update_mixture_concentrations(posterior_mixture_concentrations, prior_mixture_concentrations,
                                      group_responsabilities, component_responsabilities);
        auto curr_evidence = calculate_evidence(prior_group_concentrations, posterior_group_concentrations,
                                                prior_mixture_concentrations, posterior_mixture_concentrations,
                                                genotype_log_priors, genotype_log_posteriors, genotype_posteriors,
                                                group_responsabilities, component_responsabilities,
                                                log_likelihoods);
//        assert(curr_evidence + options_.epsilon >= prev_evidence);
        if (curr_evidence <= prev_evidence || (curr_evidence - prev_evidence) < options_.epsilon) {
            prev_evidence = curr_evidence;
            break;
        }
        prev_evidence = curr_evidence;
        update_responsabilities(group_responsabilities, posterior_group_concentrations, posterior_mixture_concentrations,
                                genotype_posteriors, component_responsabilities, log_likelihoods);
        update_responsabilities(component_responsabilities, posterior_group_concentrations, posterior_mixture_concentrations,
                                genotype_posteriors, group_responsabilities, log_likelihoods);
    }
    Inferences result {};
    result.genotype_log_posteriors = std::move(genotype_log_posteriors);
    result.genotype_posteriors = std::move(genotype_posteriors);
    result.group_concentrations = std::move(posterior_group_concentrations);
    result.mixture_concentrations = std::move(posterior_mixture_concentrations);
    result.group_responsabilities = std::move(group_responsabilities);
    result.approx_log_evidence = prev_evidence;
    return result;
}

VariationalBayesMixtureMixtureModel::GroupResponsabilityVector
VariationalBayesMixtureMixtureModel::init_responsabilities(const GroupConcentrationVector& group_concentrations,
                                                           const MixtureConcentrationArray& mixture_concentrations,
                                                           const ProbabilityVector& genotype_priors,
                                                           const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto S = log_likelihoods.size();
    const auto T = group_concentrations.size();
    GroupResponsabilityVector result(S, Sigma(T));
    const auto G = genotype_priors.size();
    const auto ln_ex_psi = dirichlet_expectation_log(group_concentrations);
    std::size_t max_K {0};
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            max_K = std::max(max_K, mixture_concentrations[s][t].size());
        }
    }
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = log_likelihoods[s][0][0][0].size();
        const auto tau = 1.0 / max_K;
        const auto tau_sum = N * tau;
        for (std::size_t t {0}; t < T; ++t) {
            result[s][t] = ln_ex_psi[t];
            const auto ln_ex_pi = dirichlet_expectation_log(mixture_concentrations[s][t]);
            const auto K = mixture_concentrations[s][t].size();
            for (std::size_t k {0}; k < K; ++k) {
                result[s][t] += ln_ex_pi[k] * tau_sum;
                for (std::size_t g {0}; g < G; ++g) {
                    result[s][t] += genotype_priors[g] * tau * sum(log_likelihoods[s][g][t][k]);
                }
            }
        }
        maths::normalise_exp(result[s]);
    }
    return result;
}

void
VariationalBayesMixtureMixtureModel::update_responsabilities(GroupResponsabilityVector& result,
                                                             const GroupConcentrationVector& group_concentrations,
                                                             const MixtureConcentrationArray& mixture_concentrations,
                                                             const ProbabilityVector& genotype_posteriors,
                                                             const ComponentResponsabilityMatrix& component_responsabilities,
                                                             const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto T = group_concentrations.size();
    const auto S = log_likelihoods.size();
    const auto G = genotype_posteriors.size();
    const auto ln_ex_psi = dirichlet_expectation_log(group_concentrations);
    for (std::size_t s {0}; s < S; ++s) {
        std::vector<double> component_responsability_sums(component_responsabilities[s].size());
        std::transform(std::cbegin(component_responsabilities[s]), std::cend(component_responsabilities[s]),
                       std::begin(component_responsability_sums), [] (const auto& taus) { return sum(taus); });
        for (std::size_t t {0}; t < T; ++t) {
            result[s][t] = ln_ex_psi[t];
            const auto ln_ex_pi = dirichlet_expectation_log(mixture_concentrations[s][t]);
            const auto K = ln_ex_pi.size();
            for (std::size_t k {0}; k < K; ++k) {
                result[s][t] += ln_ex_pi[k] * component_responsability_sums[k];
                for (std::size_t g {0}; g < G; ++g) {
                    result[s][t] += genotype_posteriors[g] * inner_product(component_responsabilities[s][k], log_likelihoods[s][g][t][k]);
                }
            }
        }
        maths::normalise_exp(result[s]);
    }
}

VariationalBayesMixtureMixtureModel::ComponentResponsabilityMatrix
VariationalBayesMixtureMixtureModel::init_responsabilities(const GroupConcentrationVector& group_concentrations,
                                                           const MixtureConcentrationArray& mixture_concentrations,
                                                           const ProbabilityVector& genotype_priors,
                                                           const GroupResponsabilityVector& group_responsabilities,
                                                           const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto S = log_likelihoods.size();
    const auto T = group_concentrations.size();
    std::size_t K {0};
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            K = std::max(K, mixture_concentrations[s][t].size());
        }
    }
    ComponentResponsabilityMatrix result(S, ComponentResponsabilityVector(K));
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = log_likelihoods[s][0][0][0].size();
        for (std::size_t k {0}; k < K; ++k) {
            result[s][k].resize(N);
        }
    }
    update_responsabilities(result, group_concentrations, mixture_concentrations,
                            genotype_priors, group_responsabilities, log_likelihoods);
    return result;
}

namespace {

auto inner_product(const VariationalBayesMixtureMixtureModel::ProbabilityVector& genotype_posteriors,
                   const VariationalBayesMixtureMixtureModel::GenotypeCombinationLikelihoodVector& log_likelihoods,
                   const std::size_t t, const std::size_t k, const std::size_t n) noexcept
{
    ProbabilityVector::value_type result {0};
    const auto G = genotype_posteriors.size();
    for (std::size_t g {0}; g < G; ++g) {
        result += genotype_posteriors[g] * log_likelihoods[g][t][k][n];
    }
    return result;
}

} // namespace

void
VariationalBayesMixtureMixtureModel::update_responsabilities(ComponentResponsabilityMatrix& result,
                                                             const GroupConcentrationVector& group_concentrations,
                                                             const MixtureConcentrationArray& mixture_concentrations,
                                                             const ProbabilityVector& genotype_posteriors,
                                                             const GroupResponsabilityVector& group_responsabilities,
                                                             const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto T = group_concentrations.size();
    const auto S = log_likelihoods.size();
    const auto max_K = result[0].size();
    for (std::size_t s {0}; s < S; ++s) {
        const auto N = log_likelihoods[s][0][0][0].size();
        for (std::size_t t {0}; t < T; ++t) {
            const auto ln_exp_pi = dirichlet_expectation_log(mixture_concentrations[s][t]);
            const auto K = ln_exp_pi.size();
            for (std::size_t k {0}; k < K; ++k) {
                for (std::size_t n {0}; n < N; ++n) {
                    if (t == 0) result[s][k][n] = 0;
                    result[s][k][n] += group_responsabilities[s][t] * (ln_exp_pi[k] + inner_product(genotype_posteriors, log_likelihoods[s], t, k, n));
                }
            }
        }
        std::vector<double> ln_rho(max_K);
        for (std::size_t n {0}; n < N; ++n) {
            for (std::size_t k {0}; k < max_K; ++k) {
                ln_rho[k] = result[s][k][n];
            }
            const auto ln_rho_norm = log_sum_exp(ln_rho);
            for (std::size_t k {0}; k < max_K; ++k) {
                result[s][k][n] = std::exp(ln_rho[k] - ln_rho_norm);
            }
        }
    }
}

void
VariationalBayesMixtureMixtureModel::update_genotype_log_posteriors(LogProbabilityVector& result,
                                                                    const LogProbabilityVector& genotype_log_priors,
                                                                    const GroupResponsabilityVector& group_responsabilities,
                                                                    const ComponentResponsabilityMatrix& component_responsabilities,
                                                                    const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    for (std::size_t g {0}; g < genotype_log_priors.size(); ++g) {
        result[g] = genotype_log_priors[g] + marginalise(group_responsabilities, component_responsabilities, log_likelihoods, g);
    }
    maths::normalise_logs(result);
}

VariationalBayesMixtureMixtureModel::LogProbability
VariationalBayesMixtureMixtureModel::marginalise(const GroupResponsabilityVector& group_responsabilities,
                                                 const ComponentResponsabilityMatrix& component_responsabilities,
                                                 const HaplotypeLikelihoodMatrix& log_likelihoods,
                                                 const std::size_t g) const noexcept
{
    const auto T = group_responsabilities.front().size();
    const auto S = component_responsabilities.size();
    LogProbability result {0};
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            result += group_responsabilities[s][t] * marginalise(component_responsabilities[s], log_likelihoods[s][g][t]);
        }
    }
    return result;
}

VariationalBayesMixtureMixtureModel::LogProbability
VariationalBayesMixtureMixtureModel::marginalise(const ComponentResponsabilityVector& responsabilities,
                                                 const HaplotypeLikelihoodVector& log_likelihoods) const noexcept
{
    const auto K = log_likelihoods.size();
    LogProbability result {0};
    for (std::size_t k {0}; k < K; ++k) {
        result += inner_product(responsabilities[k], log_likelihoods[k]);
    }
    return result;
}

void
VariationalBayesMixtureMixtureModel::update_group_concentrations(GroupConcentrationVector& result,
                                                                 const GroupConcentrationVector& prior_group_concentrations,
                                                                 const GroupResponsabilityVector& group_responsabilities) const
{
    assert(result.size() == prior_group_concentrations.size());
    const auto T = prior_group_concentrations.size();
    const auto S = group_responsabilities.size();
    for (std::size_t t {0}; t < T; ++t) {
        result[t] = prior_group_concentrations[t];
        for (std::size_t s {0}; s < S; ++s) {
            result[t] += group_responsabilities[s][t];
        }
    }
}

void
VariationalBayesMixtureMixtureModel::update_mixture_concentrations(MixtureConcentrationArray& result,
                                                                   const MixtureConcentrationArray& prior_mixture_concentrations,
                                                                   const GroupResponsabilityVector& group_responsabilities,
                                                                   const ComponentResponsabilityMatrix& component_responsabilities) const
{
    const auto S = prior_mixture_concentrations.size();
    const auto T = group_responsabilities.front().size();
    for (std::size_t s {0}; s < S; ++s) {
        for (std::size_t t {0}; t < T; ++t) {
            const auto K = prior_mixture_concentrations[s][t].size();
            for (std::size_t k {0}; k < K; ++k) {
                result[s][t][k] = prior_mixture_concentrations[s][t][k] + group_responsabilities[s][t] * sum(component_responsabilities[s][k]);
            }
        }
    }
}

namespace {

template <typename Range1, typename Range2>
auto inner_inner_product(const Range1& lhs, const Range2& rhs) noexcept
{
    using T = decltype(inner_product(lhs[0], rhs[0]));
    const auto k = std::min(lhs.size(), rhs.size());
    return std::inner_product(std::cbegin(lhs), std::next(std::cbegin(lhs), k), std::cbegin(rhs), T {}, std::plus<> {},
                              [] (const auto& a, const auto& b) { return inner_product(a, b); });
}

template <typename T>
auto shannon_entropy(const std::vector<T>& probabilities) noexcept
{
    return -std::accumulate(std::cbegin(probabilities), std::cend(probabilities), T {0},
                            [] (const auto curr, const auto p) noexcept { return curr + (p > 0 ? p * std::log(p) : 0.0); });
}

template <typename T>
auto shannon_entropy(const std::vector<std::vector<T>>& probabilities) noexcept
{
    return std::accumulate(std::cbegin(probabilities), std::cend(probabilities), T {0},
                           [] (const auto curr, const auto& ps) noexcept { return curr + shannon_entropy(ps); });
}

} // namespace

double
VariationalBayesMixtureMixtureModel::calculate_evidence(const GroupConcentrationVector& prior_group_concentrations,
                                                        const GroupConcentrationVector& posterior_group_concentrations,
                                                        const MixtureConcentrationArray& prior_mixture_concentrations,
                                                        const MixtureConcentrationArray& posterior_mixture_concentrations,
                                                        const LogProbabilityVector& genotype_log_priors,
                                                        const LogProbabilityVector& genotype_log_posteriors,
                                                        const ProbabilityVector& genotype_posteriors,
                                                        const GroupResponsabilityVector& group_responsabilities,
                                                        const ComponentResponsabilityMatrix& component_responsabilities,
                                                        const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    const auto G = genotype_log_priors.size();
    const auto S = prior_mixture_concentrations.size();
    const auto T = group_responsabilities.front().size();
    double result {0};
    for (std::size_t g {0}; g < G; ++g) {
        auto w = genotype_log_priors[g] - genotype_log_posteriors[g];
        for (std::size_t s {0}; s < S; ++s) {
            for (std::size_t t {0}; t < T; ++t) {
                w += group_responsabilities[s][t] * inner_inner_product(component_responsabilities[s], log_likelihoods[s][g][t]);
            }
        }
        result += genotype_posteriors[g] * w;
    }
    for (std::size_t s {0}; s < S; ++s) {
        result += shannon_entropy(group_responsabilities[s]);
        result += shannon_entropy(component_responsabilities[s]);
        for (std::size_t t {0}; t < T; ++t) {
            result += maths::log_beta(posterior_mixture_concentrations[s][t]) - maths::log_beta(prior_mixture_concentrations[s][t]);
        }
    }
    result += maths::log_beta(posterior_group_concentrations) - maths::log_beta(prior_group_concentrations);
    return result;
}

void VariationalBayesMixtureMixtureModel::print_concentrations(const GroupConcentrationVector& concentrations) const
{
    for (std::size_t t {0}; t < concentrations.size(); ++t) {
        std::cout << "t: " << t << " = " << concentrations[t] << std::endl;
    }
}

void VariationalBayesMixtureMixtureModel::print_concentrations(const MixtureConcentrationArray& concentrations) const
{
    for (std::size_t s {0}; s < concentrations.size(); ++s) {
        for (std::size_t t {0}; t < concentrations[s].size(); ++t) {
            for (std::size_t k {0}; k < concentrations[s][t].size(); ++k) {
                std::cout << "s: " << s << " t: " << t << " k: " << k << " = " << concentrations[s][t][k] << std::endl;
            }
        }
    }
}

void VariationalBayesMixtureMixtureModel::print(const ProbabilityVector& probabilities) const
{
    for (std::size_t g {0}; g < probabilities.size(); ++g) {
        std::cout << "g: " << g << " = " << probabilities[g] << std::endl;
    }
}

void VariationalBayesMixtureMixtureModel::print(const GroupResponsabilityVector& responsabilities) const
{
    for (std::size_t s {0}; s < responsabilities.size(); ++s) {
        for (std::size_t t {0}; t < responsabilities[s].size(); ++t) {
            std::cout << "s: " << s << " t: " << t << " = " << responsabilities[s][t] << std::endl;
        }
    }
}

void VariationalBayesMixtureMixtureModel::print(const ComponentResponsabilityMatrix& responsabilities) const
{
    for (std::size_t s {0}; s < responsabilities.size(); ++s) {
        for (std::size_t k {0}; k < responsabilities[s].size(); ++k) {
            for (std::size_t n {0}; n < responsabilities[s][k].size(); ++n) {
                std::cout << "s: " << s <<  " k: " << k << " n: " << n << " = " << responsabilities[s][k][n] << std::endl;
            }
        }
    }
}

void VariationalBayesMixtureMixtureModel::print(const HaplotypeLikelihoodMatrix& log_likelihoods) const
{
    for (std::size_t s {0}; s < log_likelihoods.size(); ++s) {
        for (std::size_t g {0}; g < log_likelihoods[s].size(); ++g) {
            for (std::size_t t {0}; t < log_likelihoods[s][g].size(); ++t) {
                for (std::size_t k {0}; k < log_likelihoods[s][g][t].size(); ++k) {
                    for (std::size_t n {0}; n < log_likelihoods[s][g][t][k].size(); ++n) {
                        std::cout << "s: " << s <<  " g: " << g << " t: " << t << " k: " << k
                                  << " n: " << n << " = " << log_likelihoods[s][g][t][k][n] << std::endl;
                    }
                }
            }
        }
    }
}

} // namespace model
} // namespace octopus


#include <boost/test/unit_test.hpp>

#include <openssl/evp.h>

#include <base/crypto.hpp>

BOOST_AUTO_TEST_CASE(Rsa_pub_encrypt_priv_decrypt_check)
{
    auto rsa = base::generateKeys(3688);

    base::Bytes msg{ "RS@_Pub_Priv !EST" };
    auto enc_msg = rsa.first.encrypt(msg);
    auto dec_msg = rsa.second.decrypt(enc_msg);
    BOOST_CHECK(msg == dec_msg);
}

BOOST_AUTO_TEST_CASE(Rsa_priv_encrypt_pub_decrypt_check)
{
    auto rsa = base::generateKeys(2644);

    base::Bytes msg{ "RSA_Pr1v Pub_TES!" };
    auto enc_msg = rsa.second.encrypt(msg);
    auto dec_msg = rsa.first.decrypt(enc_msg);
    BOOST_CHECK(msg == dec_msg);
}

BOOST_AUTO_TEST_CASE(Rsa_stress_pub_encrypt_priv_decrypt_check)
{
    auto rsa = base::generateKeys(2048);

    base::Bytes stress_msg(rsa.first.maxEncryptSize());
    auto enc_msg = rsa.first.encrypt(stress_msg);
    auto dec_msg = rsa.second.decrypt(enc_msg);
    BOOST_CHECK(stress_msg == dec_msg);
}

BOOST_AUTO_TEST_CASE(Rsa_stress_priv_encrypt_pub_decrypt_check)
{
    auto rsa = base::generateKeys(3888);

    base::Bytes stress_msg(rsa.second.maxEncryptSize());
    auto enc_msg = rsa.second.encrypt(stress_msg);
    auto dec_msg = rsa.first.decrypt(enc_msg);
    BOOST_CHECK(stress_msg == dec_msg);
}

BOOST_AUTO_TEST_CASE(Rsa_constructor_random_keys)
{
    auto rsa1 = base::generateKeys(3422);
    auto rsa2 = base::generateKeys(1898);

    base::Bytes msg{ "RSA&Cons1r5tor" };
    auto enc_msg1 = rsa1.first.encrypt(msg);
    auto enc_msg2 = rsa2.first.encrypt(msg);
    BOOST_CHECK(enc_msg1 != enc_msg2);

    enc_msg1 = rsa1.second.encrypt(msg);
    enc_msg2 = rsa2.second.encrypt(msg);
    BOOST_CHECK(enc_msg1 != enc_msg2);
}

BOOST_AUTO_TEST_CASE(Rsa_serialization_constructor)
{
    auto rsa = base::generateKeys(3456);
    base::RsaPublicKey pub_key(rsa.first.toBytes());
    base::RsaPrivateKey priv_key(rsa.second.toBytes());

    base::Bytes msg{ "Rs@_ser1al7ze construc" };
    auto enc_msg1 = rsa.first.encrypt(msg);
    auto enc_msg2 = pub_key.encrypt(msg);

    auto dec_msg1 = rsa.second.decrypt(enc_msg1);
    auto dec_msg2 = priv_key.decrypt(enc_msg2);
    BOOST_CHECK((msg == dec_msg1) && (msg == dec_msg2));
}

BOOST_AUTO_TEST_CASE(RsaPubKey_constructor_from_file_save_in_file)
{
    auto [pub_rsa, priv_rsa] = base::generateKeys(2012);
    base::RsaPublicKey pub_rsa2(pub_rsa);
    auto [pub_rsa3, priv_rsa3] = base::generateKeys(2344);
    pub_rsa3 = pub_rsa;

    BOOST_CHECK(pub_rsa.toBytes() == pub_rsa2.toBytes());
    BOOST_CHECK(pub_rsa.toBytes() == pub_rsa3.toBytes());
    BOOST_CHECK(pub_rsa3.toBytes() == pub_rsa2.toBytes());

    base::Bytes msg("RSa_FI1E_TES!");
    auto enc_msg = priv_rsa.encrypt(msg);

    BOOST_CHECK(pub_rsa.decrypt(enc_msg) == msg);
    BOOST_CHECK(pub_rsa2.decrypt(enc_msg) == msg);
    BOOST_CHECK(pub_rsa3.decrypt(enc_msg) == msg);
}

BOOST_AUTO_TEST_CASE(Rsa_constructor_from_file_save_in_file)
{
    auto [pub_rsa, priv_rsa] = base::generateKeys(3738);
    std::filesystem::path private_key_path{ "ssh/rsa.priv" };
    std::filesystem::path public_key_path{ "ssh/rsa.pub" };

    priv_rsa.save(private_key_path);
    pub_rsa.save(public_key_path);

    auto priv_rsa2 = base::RsaPrivateKey::load(private_key_path);
    auto pub_rsa2 = base::RsaPublicKey::load(public_key_path);

    base::Bytes msg("RSa_FI1E_TES!");
    auto enc_msg1 = priv_rsa.encrypt(msg);
    auto enc_msg2 = priv_rsa2.encrypt(msg);
    auto dec_msg1 = pub_rsa.decrypt(enc_msg1);
    auto dec_msg2 = pub_rsa2.decrypt(enc_msg2);
    BOOST_CHECK((dec_msg1 == dec_msg2) && (dec_msg1 == msg));
    BOOST_CHECK(enc_msg1 == enc_msg2);

    enc_msg1 = pub_rsa.encrypt(msg);
    enc_msg2 = pub_rsa2.encrypt(msg);
    dec_msg1 = priv_rsa.decrypt(enc_msg1);
    dec_msg2 = priv_rsa2.decrypt(enc_msg2);
    BOOST_CHECK(dec_msg1 == dec_msg2);

    std::filesystem::remove(private_key_path);
    std::filesystem::remove(public_key_path);
}

BOOST_AUTO_TEST_CASE(RsaAes_constructor_encrypt_decrypt)
{
    auto rsa = base::generateKeys(2894);
    base::Bytes msg("f1rst RsaAes_tes!");
    auto enc_msg = rsa.first.encryptWithAes(msg);
    auto dec_msg = rsa.second.decryptWithAes(enc_msg);
    BOOST_CHECK(msg == dec_msg);
}

BOOST_AUTO_TEST_CASE(RsaAes_small_key_encrypt_decrypt)
{
    auto rsa = base::generateKeys(1024);
    base::Bytes msg("f1rst RsaAes_tes!");
    auto enc_msg = rsa.first.encryptWithAes(msg);
    auto dec_msg = rsa.second.decryptWithAes(enc_msg);
    BOOST_CHECK(msg == dec_msg);
}

BOOST_AUTO_TEST_CASE(aes_encrypt_decrypt_256bit)
{
    base::Bytes msg("dfjbvalgecnhq=ygrbn3f5xgvidytnwucgfim2yx139sv7yx");
    base::AesKey key;
    auto encrypted_data = key.encrypt(msg);
    auto decrypt_target = key.decrypt(encrypted_data);
    BOOST_CHECK_EQUAL(msg.toString(), decrypt_target.toString());
}

BOOST_AUTO_TEST_CASE(aes_encrypt_decrypt_256bit_by_serialized_key)
{
    base::Bytes msg("dfjbvalgecnhq=ygrbn3f5xgvidytnwucgfim2yx139sv7yx");
    base::AesKey key;
    auto encrypted_data = key.encrypt(msg);
    auto serialised_key = key.toBytes();
    base::AesKey deserialized_key(serialised_key);
    auto decrypt_target = deserialized_key.decrypt(encrypted_data);
    BOOST_CHECK_EQUAL(msg.toString(), decrypt_target.toString());
}

BOOST_AUTO_TEST_CASE(aes_encrypt_decrypt_128bit)
{
    base::Bytes msg("dfjbvalgecnhq=ygrbnf5xgvidytnwucgfim2y139sv7yx");
    base::AesKey key(base::AesKey::KeyType::K128BIT);
    auto encrypted_data = key.encrypt(msg);
    auto decrypt_target = key.decrypt(encrypted_data);
    BOOST_CHECK_EQUAL(msg.toString(), decrypt_target.toString());
}

BOOST_AUTO_TEST_CASE(aes_encrypt_decrypt_128bit_by_serialized_key)
{
    base::Bytes msg("djbvalcnhq=ygrn3f5xvidytwugfim2yx19vyx");
    base::AesKey key(base::AesKey::KeyType::K128BIT);
    auto encrypted_data = key.encrypt(msg);
    auto serialised_key = key.toBytes();
    base::AesKey deserialized_key(serialised_key);
    auto decrypt_target = deserialized_key.decrypt(encrypted_data);
    BOOST_CHECK_EQUAL(msg.toString(), decrypt_target.toString());
}

BOOST_AUTO_TEST_CASE(aes_double_encrypt_128bit)
{
    base::Bytes msg("dfjbvalgecnhq=ygrbn3f5xgvidytnwucgfim2yx139sv7yx");
    base::AesKey key(base::AesKey::KeyType ::K128BIT);
    auto encrypted_data_1 = key.encrypt(msg);
    auto encrypted_data_2 = key.encrypt(msg);
    BOOST_CHECK_EQUAL(encrypted_data_1.toString(), encrypted_data_2.toString());
}

BOOST_AUTO_TEST_CASE(aes_serialization_256bit)
{
    base::Bytes target_msg("dfjbvalgecnhq=ygrbn3f5xgvidytnwucgfim2yx139sv7yx");
    base::AesKey target_key(base::AesKey::KeyType ::K128BIT);
    auto encrypted_data = target_key.encrypt(target_msg);

    std::filesystem::path key_path{ "test.aes" };
    target_key.save(key_path);

    auto deserialized_key = base::AesKey::read(key_path);
    auto decrypt_message = deserialized_key.decrypt(encrypted_data);
    BOOST_CHECK_EQUAL(target_msg, decrypt_message);

    std::filesystem::remove(key_path);
}

BOOST_AUTO_TEST_CASE(secp256_sign_verify)
{
    auto [pub_key, priv_key] = base::generateSecp256Keys();
    auto hash = base::Sha256::compute(base::Bytes("111"));
    auto signature = priv_key.sign(hash.getBytes());
    BOOST_CHECK(pub_key.verifySignature(signature, hash.getBytes()));
}

BOOST_AUTO_TEST_CASE(secp256_sign_verify_from_copy)
{
    auto [pub_key, priv_key] = base::generateSecp256Keys();
    auto pub_key2{ pub_key };
    auto priv_key2{ std::move(priv_key) };
    auto hash = base::Sha256::compute(base::Bytes("222"));

    auto signature = priv_key2.sign(hash.getBytes());
    BOOST_CHECK(pub_key2.verifySignature(signature, hash.getBytes()));
}

BOOST_AUTO_TEST_CASE(secp256_sign_verify_from_bytes_copy)
{
    auto [pub_key, priv_key] = base::generateSecp256Keys();
    base::Secp256PublicKey pub_key2{ pub_key.getBytes() };
    base::Secp256PrivateKey priv_key2{ priv_key.getBytes() };
    auto hash = base::Sha256::compute(base::Bytes("333"));

    auto signature1 = priv_key.sign(hash.getBytes());
    auto signature2 = priv_key2.sign(hash.getBytes());

    BOOST_CHECK(pub_key2.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key2.verifySignature(signature2, hash.getBytes()));

    BOOST_CHECK(pub_key.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key.verifySignature(signature2, hash.getBytes()));
}

BOOST_AUTO_TEST_CASE(secp256_sign_verify_with_another_key)
{
    auto [pub_key1, priv_key1] = base::generateSecp256Keys();
    auto [pub_key2, priv_key2] = base::generateSecp256Keys();
    auto hash = base::Sha256::compute(base::Bytes("444"));

    auto signature1 = priv_key1.sign(hash.getBytes());
    auto signature2 = priv_key2.sign(hash.getBytes());

    BOOST_CHECK(pub_key1.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key2.verifySignature(signature2, hash.getBytes()));

    BOOST_CHECK(!pub_key2.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(!pub_key1.verifySignature(signature2, hash.getBytes()));
}

BOOST_AUTO_TEST_CASE(secp256_save_load)
{
    auto [pub_key1, priv_key1] = base::generateSecp256Keys();
    std::filesystem::path private_key_path{ "ssh/rsa.priv" };
    std::filesystem::path public_key_path{ "ssh/rsa.pub" };

    priv_key1.save(private_key_path);
    pub_key1.save(public_key_path);

    auto priv_key2 = base::Secp256PrivateKey::load(private_key_path);
    auto pub_key2 = base::Secp256PublicKey::load(public_key_path);
    auto hash = base::Sha256::compute(base::Bytes("555"));

    auto signature1 = priv_key1.sign(hash.getBytes());
    auto signature2 = priv_key2.sign(hash.getBytes());

    BOOST_CHECK(pub_key2.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key2.verifySignature(signature2, hash.getBytes()));

    BOOST_CHECK(pub_key1.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key1.verifySignature(signature2, hash.getBytes()));

    std::filesystem::remove(private_key_path);
    std::filesystem::remove(public_key_path);
}

BOOST_AUTO_TEST_CASE(secp256_serialization)
{
    auto [pub_key1, priv_key1] = base::generateSecp256Keys();

    base::SerializationOArchive oa;
    oa.serialize(pub_key1);
    oa.serialize(priv_key1);

    base::SerializationIArchive ia(oa.getBytes());

    auto pub_key2 = ia.deserialize<base::Secp256PublicKey>();
    auto priv_key2 = ia.deserialize<base::Secp256PrivateKey>();
    auto hash = base::Sha256::compute(base::Bytes("667"));

    auto signature1 = priv_key1.sign(hash.getBytes());
    auto signature2 = priv_key2.sign(hash.getBytes());

    BOOST_CHECK(pub_key2.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key2.verifySignature(signature2, hash.getBytes()));

    BOOST_CHECK(pub_key1.verifySignature(signature1, hash.getBytes()));
    BOOST_CHECK(pub_key1.verifySignature(signature2, hash.getBytes()));
}


#ifndef INCLUDED_STDDEFX
#include "stddefx.h"
#define INCLUDED_STDDEFX
#endif

#ifndef INCLUDED_CALC
#include "calc.h"
#define INCLUDED_CALC
#endif

// Library headers.
#include <utility>
#ifndef INCLUDED_BOOST_NONCOPYABLE
#include <boost/noncopyable.hpp>
#define INCLUDED_BOOST_NONCOPYABLE
#endif
#ifndef INCLUDED_BOOST_MATH_SPECIAL_FUNCTIONS_ROUND
#include <boost/math/special_functions/round.hpp>
#define INCLUDED_BOOST_MATH_SPECIAL_FUNCTIONS_ROUND
#endif
#ifndef INCLUDED_BOOST_MATH_TR1
#include <boost/math/tr1.hpp>
#define INCLUDED_BOOST_MATH_TR1
#endif

// PCRaster library headers.

#ifndef INCLUDED_MISC
#include "misc.h"
#define INCLUDED_MISC
#endif

#ifndef INCLUDED_COM_MATH
#include "com_math.h"
#define INCLUDED_COM_MATH
#endif
#ifndef INCLUDED_COM_INTERVALTYPES
#include "com_intervaltypes.h"
#define INCLUDED_COM_INTERVALTYPES
#endif

#ifndef INCLUDED_GEO_CELLLOCVISITOR
#include "geo_celllocvisitor.h"
#define INCLUDED_GEO_CELLLOCVISITOR
#endif

#ifndef INCLUDED_GEO_SCANCONVERSION
#include "geo_scanconversion.h"
#define INCLUDED_GEO_SCANCONVERSION
#endif

#ifndef INCLUDED_FIELDAPI_INTERFACE
#include "fieldapi_interface.h"
#define INCLUDED_FIELDAPI_INTERFACE
#endif

#ifndef INCLUDED_FIELDAPI_SCALARDOMAINCHECK
#include "fieldapi_scalardomaincheck.h"
#define INCLUDED_FIELDAPI_SCALARDOMAINCHECK
#endif

// Module headers.

/*!
  \file
  This file contains the implementation of the ExtentOfView class.
*/

template<class T>
class RememberPoints: public boost::noncopyable
{

private:

  const fieldapi::ReadOnly<INT4>& d_classes;
  INT4 d_class;
  std::vector<std::pair<T, T> > d_points;

public:

  RememberPoints(const fieldapi::ReadOnly<INT4>& classes)
    : d_classes(classes)
  {
  }

  RememberPoints(const fieldapi::ReadOnly<INT4>& classes, INT4 currentClass)
    : d_classes(classes), d_class(currentClass)
  {
  }

  void setClass(INT4 currentClass) {
    d_class = currentClass;
  }

  bool operator()(T x, T y) {
    static INT4 value;
    if(d_classes.get(value, y, x) && value == d_class) {
      d_points.push_back(std::make_pair(y, x));
      return true;
    }

    return false;
  }

  std::pair<T, T>& operator[](size_t i) {
    return d_points[i];
  }

  void clear() {
    d_points.clear();
  }

  size_t size() {
    return d_points.size();
  }

  // Distance in cells.
  double distance() {
    double distance = 0.0;

    if(size()) {
      const std::pair<T, T>& begin = d_points.front();
      const std::pair<T, T>& end = d_points.back();
      double dx = static_cast<double>(end.first - begin.first);
      double dy = static_cast<double>(end.second - begin.second);
      // Add one for current cell.
      distance = boost::math::tr1::hypot(dx,dy) + 1.0;
    }

    return distance;
  }

};

extern "C" int ExtentOfView(
  MAP_REAL8 *m_result,                  // scalar, average extent of view
  const MAP_INT4* m_classes,            // nominal, classes
  const MAP_REAL8* m_nrDirections)      // scalar, number of directions
{
  ReadWriteReal8_ref(result, m_result);

  std::vector<const fieldapi::Common*> inputs;
  ReadOnlyInt4_ref(classes, m_classes);
  inputs.push_back(&classes);
  ReadOnlyReal8_ref(nrDirectionsInterface, m_nrDirections);
  inputs.push_back(&nrDirectionsInterface);

  PRECOND(classes.spatial());
  PRECOND(!nrDirectionsInterface.spatial());
  size_t nrDirections = static_cast<size_t>(nrDirectionsInterface.value(0,0));

  std::vector<fieldapi::ScalarDomainCheck> nsDomains;
  nsDomains.push_back(fieldapi::ScalarDomainCheck(nrDirectionsInterface,
        "Number of directions", com::GreaterThan<double>(0)));
  int nsCheck = fieldapi::checkScalarDomains(nsDomains,geo::CellLoc(0,0));
  if(nsCheck != -1) {
    return RetError(1, nsDomains[nsCheck].msg().c_str());
  }

  size_t nrRows = classes.nrRows();
  size_t nrCols = classes.nrCols();

  // Result is missing value if any of the inputs is.
  for(geo::CellLocVisitor visitor(classes); visitor.valid(); ++visitor) {

    if(fieldapi::nonMV(inputs, *visitor)) {
       result.put(0.0, *visitor);
    }
    else {
      result.putMV(*visitor);
    }
  }

  // Determine all direction angles.
  // Determine max distance in cells in the raster.
  double maxExtent = MAX(nrRows,nrCols);
  int offsetX = com::ceil<int, double>(boost::math::tr1::hypot(maxExtent,maxExtent));
  int offsetY;
  double subAngle = 360.0 / nrDirections;
  double angle;
  typedef std::pair<int, int> Offset;
  typedef std::vector<std::pair<double, Offset> > Offsets;
  Offsets offsets;

  for(size_t direction = 0; direction < nrDirections; ++direction) {

    angle = direction * subAngle;
    PRECOND(angle >= 0 && angle < 360);

    if(angle == 90.0) {
      offsetX = 0;
      offsetY = 100;
    }
    else if(angle == 270.0) {
      offsetX = 0;
      offsetY = -100;
    }
    else {
      if(angle < 90 || angle > 270) {
        offsetX = 100;
      }
      else {
        offsetX = -100;
      }

      angle *= M_PI / 180;
      offsetY = boost::math::iround(std::tan(angle) * offsetX);
    }
    offsets.push_back(std::make_pair(angle,
         std::make_pair(offsetX, offsetY)));
  }

  int x, y;
  double sum;
  RememberPoints<int> points(classes);

  // Loop over each cell.
  for(geo::CellLocVisitor visitor(classes); visitor.valid(); ++visitor) {
    x = (*visitor).col();
    y = (*visitor).row();
    sum = 0.0;

    // Determine class.
    if(result.isMV(*visitor)) {
      continue;
    }

    points.setClass(classes[*visitor]);

    // Loop over each direction.
    for(Offsets::const_iterator it = offsets.begin(); it != offsets.end();
         ++it) {

      points.clear();
      POSTCOND(!points.size());

      // Determine number of cells with same class.
      PRECOND(!classes.isMV(*visitor));
      geo::midpointLine(x, y, x + (*it).second.first, y + (*it).second.second,
         points);
      POSTCOND(points.size());

      sum += points.distance();
    }

    // Write sum to cell.
    // Check whether --unitcell or --unittrue is set.
    // unittrue: area is computed in true area represented by cells (default)
    // unitcell: area is computed in number of cells
    result.put(Side() * sum, *visitor);
  }

  return 0;
}


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

#include <nt2/include/functions/qr.hpp>
#include <boost/dispatch/attributes.hpp>
#include <nt2/core/container/table/table.hpp>
#include <nt2/include/functions/colon.hpp>
#include <nt2/include/functions/expand.hpp>
#include <nt2/include/functions/eye.hpp>
#include <nt2/include/functions/geqp3.hpp>
#include <nt2/include/functions/geqrf.hpp>
#include <nt2/include/functions/gqr.hpp>
#include <nt2/include/functions/min.hpp>
#include <nt2/include/functions/mqr.hpp>
#include <nt2/include/functions/tie.hpp>
#include <nt2/include/functions/triu.hpp>
#include <nt2/include/functions/width.hpp>
#include <nt2/include/functions/zeros.hpp>
#include <nt2/linalg/details/utility/lapack_verify.hpp>
#include <nt2/sdk/meta/as_integer.hpp>
#include <nt2/core/container/dsl/as_terminal.hpp>
#include <nt2/core/utility/assign_swap.hpp>
#include <nt2/core/container/colon/colon.hpp>
#include <nt2/linalg/options.hpp>

namespace nt2 { namespace ext
{
  //============================================================================
  //QR Scalar
  //============================================================================
  BOOST_DISPATCH_IMPLEMENT  ( qr_, tag::cpu_
                            , (A0)
                            , (scalar_<unspecified_<A0> >)
                            )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(const A0& a0) const
    {
      return a0;
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( qr_, tag::cpu_
                            , (A0)(A1)
                            , (scalar_<unspecified_<A0> >)
                              (unspecified_<A1>)
                            )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(const A0& a0, const A1&) const
    {
      return a0;
    }
  };

  //============================================================================
  //[Q,R] = QR(A)
  //============================================================================
  BOOST_DISPATCH_IMPLEMENT  ( qr_, tag::cpu_
                            , (A0)(N0)(A1)(N1)
                            , ((node_<A0, nt2::tag::qr_
                                    , N0, nt2::container::domain
                                      >
                              ))
                              ((node_<A1, nt2::tag::tie_
                                    , N1, nt2::container::domain
                                     >
                              ))
                            )
  {
    typedef void  result_type;
    typedef typename boost::proto::result_of::child_c<A0&,0>::value_type     child0;
    typedef typename child0::value_type                                      type_t;
    typedef typename meta::as_real<type_t>::type                            rtype_t;
    typedef typename meta::as_integer<rtype_t>::type                        itype_t;
    typedef nt2::memory::container<tag::table_, type_t, nt2::_2D>        o_semantic;
    typedef nt2::memory::container<tag::table_, nt2_la_int, nt2::_2D>    i_semantic;

    BOOST_FORCEINLINE result_type operator()( A0& a0, A1& a1 ) const
    {
      eval(a0, a1, N0(), N1());
    }

  private:
    /// INTERNAL ONLY - x = qr(a)
    /// return a matrix x such that triu(x) is the upper triangular factor r.
    /// this is the direct return of LAPACK ?geqp3, however tau is lost (and so q)
    BOOST_FORCEINLINE
    void eval ( A0& a0, A1& a1
              , boost::mpl::long_<1> const& // 1 input
              , boost::mpl::long_<1> const& // 1 output
              ) const
    {
      eval1_1(a0, a1, nt2::policy<ext::raw_>());
    }

    /// INTERNAL ONLY - x = qr(a, econ_/0)
    /// return a matrix x such that triu(x) is the upper triangular factor r.
    /// this is the direct return of LAPACK ?geqp3, however tau is lost (and so q)
    BOOST_FORCEINLINE
    void eval ( A0& a0, A1& a1
              , boost::mpl::long_<2> const& // 2 input
              , boost::mpl::long_<1> const& // 1 output
              ) const
    {
      eval2_1(a0, a1, boost::proto::value(boost::proto::child_c<1>(a0)));
    }

    /// INTERNAL ONLY - [q, r] = qr(a)
    /// where a is m-by-n, produces an m-by-n upper triangular matrix r
    ///  and an m-by-m unitary matrix q so that a = q*r.
    BOOST_FORCEINLINE
    void eval ( A0& a0, A1& a1
              , boost::mpl::long_<1> const&  // 1 input
              , boost::mpl::long_<2> const&  // 2 output
              ) const
    {
      eval2_2(a0, a1, nt2::policy<ext::matrix_>());
    }

    /// INTERNAL ONLY - [q, r, e] = qr(a)
    /// produces unitary q, upper triangular r and a
    /// permutation row vector e so that a(e, _)= q*r.
    /// the column permutation e is chosen so that abs(diag_of(r)) is decreasing.
    BOOST_FORCEINLINE
    void eval ( A0& a0, A1& a1
              , boost::mpl::long_<1> const&  // 1 input
              , boost::mpl::long_<3> const&  // 3 output
              ) const
    {
      eval2_3(a0, a1, nt2::policy<ext::matrix_>());
    }

    /// INTERNAL ONLY - [x, y] = qr(a, raw_/matrix_/vector_/econ_/0 )
    /// with raw_
    ///  [x, tau] = qr(a, raw_)
    /// returns a matrix x such that triu(x) is the upper triangular factor r.
    /// The elements below the diagonal, together with the array tau, represent the
    /// unitary matrix Q as a product of min(M,N) elementary reflectors.
    /// tau contains The scalar factors of the elementary reflectors.
    /// This is the direct return of LAPACK ?geqp3
    ///
    /// with matrix_/vector is the same as  [q, r] = qr(a)
    /// [q, r] = qr(a, econ_) or  [q, r] = qr(a, 0)
    BOOST_FORCEINLINE
    void eval ( A0& a0, A1& a1
              , boost::mpl::long_<2> const&
              , boost::mpl::long_<2> const&
              ) const
    {
      //1_2 means  1 input 2 outputs
      eval2_2(a0, a1
             , boost::proto::value(boost::proto::child_c<1>(a0)));
    }

    /// INTERNAL ONLY - [q, r, e] = qr(a, vector_/matrix_)
    /// with vector_
    /// produces unitary q, upper triangular r and a
    /// permutation row vector e so that a(e, _)= q*r.
    /// the column permutation e is chosen so that abs(diag_of(r)) is decreasing.
    /// with matrix_
    /// produces unitary q, upper triangular r and a
    /// permutation matrix e so that a*e = q*r.
    /// the column permutation e is chosen so that abs(diag_of(r)) is decreasing.
    /// Same as   [q, r, e] = qr(a)
    BOOST_FORCEINLINE
    void eval ( A0& a0, A1& a1
              , boost::mpl::long_<2> const& // 2 input
              , boost::mpl::long_<3> const& // 2 output
              ) const
    {
      eval2_3(a0, a1, boost::proto::value(boost::proto::child_c<1>(a0)));
    }

    ///////////////////////////////////////////////////////////////////////////////
    // evali_j bunch
    ///////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 1i 1o: x  = qr(a)
    BOOST_FORCEINLINE
    void eval1_1 ( A0& a0, A1& a1
                   , const nt2::policy<ext::raw_>&
                   ) const
    {
      NT2_AS_TERMINAL_INOUT(o_semantic, r
                           , boost::proto::child_c<0>(a0), boost::proto::child_c<0>(a1));
      o_semantic tau(of_size(height(r), 1));
      NT2_LAPACK_VERIFY(nt2::geqrf( boost::proto::value(r)
                                  , tau));
      assign_swap(boost::proto::child_c<0>(a1), r);
    }

    /// INTERNAL ONLY: 2i 1o raw_: x  = qr(a, raw_)
    BOOST_FORCEINLINE
    void eval2_1 ( A0& a0, A1& a1
                   , const nt2::policy<ext::raw_>&
                   ) const
    {
      eval1_1(a0, a1, nt2::policy<ext::raw_>());
    }

    /// INTERNAL ONLY: 2i 1o upper_: x  = qr(a, upper_)
    BOOST_FORCEINLINE
    void eval2_1 ( A0& a0, A1& a1
                   , const nt2::policy<ext::upper_>&
                   ) const
    {
       nt2::container::table<type_t> work;
       NT2_AS_TERMINAL_INOUT(o_semantic
                            , r, boost::proto::child_c<0>(a0), work);
       o_semantic tau(of_size(height(r), 1));
       NT2_LAPACK_VERIFY(nt2::geqrf( boost::proto::value(r)
                                   ,tau));
       boost::proto::child_c<0>(a1) = triu(r);
    }

    /// INTERNAL ONLY: 2i 1o econ_: x  = qr(a, econ_)
    BOOST_FORCEINLINE
    void eval2_1 ( A0& a0, A1& a1
                   , const nt2::policy<ext::econ_>&
                   ) const
    {
      eval1_1(a0, a1, nt2::policy<ext::raw_>());
    }

    /// INTERNAL ONLY: 2i 1o 0: x  = qr(a, 0)
    BOOST_FORCEINLINE
    void eval2_1 ( A0& a0, A1& a1
                   , const int&
                   ) const
    {
      eval1_1(a0, a1, nt2::policy<ext::raw_>());
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 2o raw_: tie(x, tau) = qr(a, raw_)
    BOOST_FORCEINLINE
    void eval2_2 ( A0& a0, A1& a1
                   , const nt2::policy<ext::raw_>&
                   ) const
    {
      NT2_AS_TERMINAL_INOUT(o_semantic, x
                           , boost::proto::child_c<0>(a0), boost::proto::child_c<0>(a1));
      NT2_AS_TERMINAL_OUT(o_semantic, tau
                         , boost::proto::child_c<1>(a1));
      tau.resize(of_size(dim(x), 1));
      NT2_LAPACK_VERIFY(nt2::geqrf( boost::proto::value(x)
                                  , boost::proto::value(tau)));
      assign_swap(boost::proto::child_c<0>(a1), x);
      assign_swap(boost::proto::child_c<1>(a1), tau);
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 3o raw_: tie(x, tau, ip) = qr(a, nt2::raw_)
    BOOST_FORCEINLINE
    void eval2_3 ( A0& a0, A1& a1
                   , const nt2::policy<ext::raw_>&
                   ) const
    {
      nt2::container::table<type_t> work;
      NT2_AS_TERMINAL_INOUT(o_semantic, x
                           , boost::proto::child_c<0>(a0), boost::proto::child_c<0>(a1));
      NT2_AS_TERMINAL_OUT(o_semantic, tau
                         , boost::proto::child_c<1>(a1));
      NT2_AS_TERMINAL_OUT(i_semantic,  ip
                         , boost::proto::child_c<2>(a1));
      tau.resize(of_size(dim(x), 1));
      ip = nt2::zeros(nt2::width(x),1,nt2::meta::as_<nt2_la_int>());
      NT2_LAPACK_VERIFY(nt2::geqp3( boost::proto::value(x)
                                  , boost::proto::value(ip)
                                  , boost::proto::value(tau)));
      assign_swap(boost::proto::child_c<0>(a1), x);
      assign_swap(boost::proto::child_c<1>(a1), tau);
      assign_swap(boost::proto::child_c<2>(a1), ip);
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 2o matrix_/econ_: tie(q, r) = qr(a, matrix_) or
    /// tie(q, r) = qr(a, econ_), only extracting phases differ
    template < class T >
    BOOST_FORCEINLINE
    void eval2_2 ( A0& a0, A1& a1
                 , const T&
                 ) const
    {
      nt2::container::table<type_t> work;
      NT2_AS_TERMINAL_INOUT(o_semantic
                           , r, boost::proto::child_c<0>(a0), work);
      NT2_AS_TERMINAL_OUT  (o_semantic
                           , x, boost::proto::child_c<0>(a1));
      nt2::container::table<type_t>     tau;
      tie(x, tau) = qr(r, nt2::raw_);
      extract_qr(x, tau, r, T()); // extract raw or econ
      assign_swap(boost::proto::child_c<0>(a1), x);
      assign_swap(boost::proto::child_c<1>(a1), r);
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 2o vector_: tie(q, r) = qr(a, vector_)
    /// this is identical to  tie(q, r) = qr(a, matrix_)  ot  tie(q, r) = qr(a)
    BOOST_FORCEINLINE
    void eval2_2 ( A0& a0, A1& a1
                   , const nt2::policy<ext::vector_>&
                   ) const
    {
      eval2_2(a0, a1,  nt2::policy<ext::matrix_>()); //with 2 outputs vector_ is useless
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 2o vector_: tie(q, r) = qr(a, 0)
    BOOST_FORCEINLINE
    void eval2_2 ( A0& a0, A1& a1
                   , const int &
                   ) const
    {
      eval2_2(a0, a1, nt2::policy<ext::econ_>()); // 0 and econ_ are equivalent
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 3o vector_: tie(q, r, ip) = qr(a, econ_)
    BOOST_FORCEINLINE
    void eval2_3 ( A0& a0, A1& a1
                   , const nt2::policy<ext::econ_>&
                   ) const
    {
      nt2::container::table<type_t> work;
      NT2_AS_TERMINAL_INOUT(o_semantic, r
                           , boost::proto::child_c<0>(a0), work);
      NT2_AS_TERMINAL_OUT  (o_semantic, x
                           , boost::proto::child_c<0>(a1));
      NT2_AS_TERMINAL_OUT  (i_semantic, ip
                           , boost::proto::child_c<2>(a1));
      nt2::container::table<type_t> tau;
      tie(x, tau, ip) = qr(r, nt2::raw_);

      extract_qr(x, tau, r, nt2::policy<ext::econ_>());
      assign_swap(boost::proto::child_c<0>(a1), x);
      assign_swap(boost::proto::child_c<1>(a1), r);
      assign_swap(boost::proto::child_c<2>(a1), ip);
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 3o vector_: tie(q, r, ip) = qr(a, 0)
    BOOST_FORCEINLINE
    void eval2_3 ( A0& a0, A1& a1
                   , const int &
                   ) const
    {
      eval2_3(a0, a1,  nt2::policy<ext::econ_>()); // 0 and econ_ are equivalent
    }

    ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 3o vector_: tie(q, r, ip) = qr(a, vector_)
    BOOST_FORCEINLINE
    void eval2_3 ( A0& a0, A1& a1
                   , const nt2::policy<ext::vector_>&
                   ) const
    {
      nt2::container::table<type_t> work;
      NT2_AS_TERMINAL_INOUT(o_semantic, r
                           , boost::proto::child_c<0>(a0), work);
      NT2_AS_TERMINAL_OUT  (o_semantic, x
                           , boost::proto::child_c<0>(a1));
      NT2_AS_TERMINAL_OUT  (i_semantic, ip
                           , boost::proto::child_c<2>(a1));
      nt2::container::table<type_t>     tau;
      tie(x, tau, ip) = qr(r, nt2::raw_);

      extract_qr(x, tau, r, nt2::policy<ext::raw_>());
      assign_swap(boost::proto::child_c<0>(a1), x);
      assign_swap(boost::proto::child_c<1>(a1), r);
      assign_swap(boost::proto::child_c<2>(a1), ip);
    }

     ///////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY: 2i 3o matrix_: tie(q, r, p) = qr(a, matrix_)
    BOOST_FORCEINLINE
    void eval2_3 ( A0& a0, A1& a1
                   , const nt2::policy<ext::matrix_>&
                   ) const
    {
      nt2::container::table<type_t> work;
      NT2_AS_TERMINAL_INOUT(o_semantic, r
                           , boost::proto::child_c<0>(a0), work);
      NT2_AS_TERMINAL_OUT  (o_semantic, x
                           , boost::proto::child_c<0>(a1));
      nt2::container::table<type_t>     tau;
      nt2::container::table<nt2_la_int>  ip;
      tie(x, tau, ip) = qr(r, nt2::raw_);

      extract_qr(x, tau, r, nt2::policy<ext::raw_>());
      assign_swap(boost::proto::child_c<0>(a1), x);
      assign_swap(boost::proto::child_c<1>(a1), r);
      boost::proto::child_c<2>(a1) = eye(numel(ip), nt2::meta::as_<type_t>())(nt2::_, ip);
    }

    //////////////////////////////////////////////////////////////////////////////////////////
    /// extraction helpers
    //////////////////////////////////////////////////////////////////////////////////////////

    /// INTERNAL ONLY - Helper for Q/R extraction
    template<typename XQ, typename TAU,typename R, typename CHOICE >
    BOOST_FORCEINLINE
    void extract_qr(XQ& xq, TAU& tau, R& r, CHOICE const &) const
    {
      nt2_la_int  m  = nt2::height(xq);
      nt2_la_int  n  = nt2::width(xq);
      typedef typename XQ::value_type xqtype_t;
      r = nt2::triu(xq);

      if (m>n)
      {
        nt2::container::table<xqtype_t> complete_q = nt2::eye(m,m, nt2::meta::as_<xqtype_t>());
        nt2::mqr( boost::proto::value(xq)
                , boost::proto::value(tau)
                , boost::proto::value(complete_q) );
        xq = complete_q;
      }
      else
      {
        if(m < n)
        {
          /// TODO: Remove when aliasing works
          nt2::container::table<xqtype_t> local(xq);
          xq = nt2::expand(local,nt2::of_size(m,m));
        }
        nt2::gqr( boost::proto::value(xq)
                , boost::proto::value(tau));
      }
    }

    /// INTERNAL ONLY - Helper for Q/R extraction economy mode
    template<typename XQ, typename TAU,typename R >
    BOOST_FORCEINLINE
    void extract_qr(XQ& xq, TAU& tau, R& r, nt2::policy<ext::econ_> const &) const
    {
      nt2_la_int  m  = nt2::height(xq);
      nt2_la_int  n  = nt2::width(xq);
      typedef typename XQ::value_type xqtype_t;

      // economy mode
      r = nt2::triu(xq(_(1,nt2::min(n, m)), _) );

      if(m < n)
      {
        /// TODO: Remove when aliasing works
        nt2::container::table<xqtype_t> local(xq);
        xq = nt2::expand(local,nt2::of_size(m,m));
      }

      nt2::gqr( boost::proto::value(xq)
              , boost::proto::value(tau));
    }

    ////////////////////////////////////////////////////////////////////////////
    // some utilitaries
    ////////////////////////////////////////////////////////////////////////////
    /// INTERNAL ONLY - Size of L/U
    template<typename W>
    BOOST_FORCEINLINE std::size_t dim(W const& work) const
    {
      return std::min(nt2::height(work),nt2::width(work));
    }
  };
} }

#endif


#define BOOST_GEOMETRY_NO_ROBUSTNESS
#include "geom.h"

#include <boost/geometry/geometries/segment.hpp>
#include <boost/geometry/index/rtree.hpp>

#include "geometry/correct.hpp"

#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/irange.hpp>

typedef boost::geometry::model::segment<Point> simplify_segment;
typedef boost::geometry::index::rtree<simplify_segment, boost::geometry::index::quadratic<16>> simplify_rtree;

template<typename GeometryType>
static inline void simplify_ring(GeometryType const &input, GeometryType &output, double distance, simplify_rtree const &outer_rtree = simplify_rtree())
{
    std::deque<std::size_t> nodes(input.size());
    for(std::size_t i = 0; i < input.size(); ++i)
        nodes[i] = i;

	simplify_rtree rtree(
		boost::irange<std::size_t>(0, input.size() - 1)
		| boost::adaptors::transformed(std::function<simplify_segment(std::size_t)>([&input](std::size_t i) {
			return simplify_segment(input[i], input[i+1]);
		})));

    Box envelope; boost::geometry::envelope(input, envelope);

	for(std::size_t pq = input.size() - 2; pq--; ) {
        auto entry = pq;

        auto start = nodes[entry];
        auto middle = nodes[entry + 1];
        auto end = nodes[entry + 2];

        if (input[middle].x()==envelope.min_corner().x() ||
            input[middle].y()==envelope.min_corner().y() ||
            input[middle].x()==envelope.max_corner().x() ||
            input[middle].y()==envelope.max_corner().y()) continue;

        simplify_segment line(input[start], input[end]);

        double max_comp_distance = 0.0;
		std::size_t max_comp_i = start + 1;

        for(auto i = start + 1; i < end; ++i) {
			auto comp_distance = boost::geometry::comparable_distance(line, input[i]);
            if(comp_distance > max_comp_distance) {
				max_comp_distance = comp_distance;
				max_comp_i = i;
			}
		}

        if(boost::geometry::distance(line, input[max_comp_i]) < distance) {
			std::size_t query_count = 0;
            for(auto const &result: rtree | boost::geometry::index::adaptors::queried(boost::geometry::index::intersects(line)))
				++query_count;
            for(auto const &result: outer_rtree | boost::geometry::index::adaptors::queried(boost::geometry::index::intersects(line)))
				++query_count;

			std::size_t expected_count = std::min<std::size_t>(4, nodes.size() - 1);
            if(query_count == expected_count) {
                nodes.erase(nodes.begin() + entry + 1);
                rtree.remove(simplify_segment(input[start], input[middle]));
                rtree.remove(simplify_segment(input[middle], input[end]));
                rtree.insert(line);
            }
        }
    }

	output.resize(nodes.size());
	for(std::size_t i = 0; i < nodes.size(); ++i)
		output[i] = input[nodes[i]];
}

Polygon simplify(Polygon const &p, double max_distance)
{
	Polygon result;

	simplify_rtree outer_rtree(
		boost::irange<std::size_t>(0, p.outer().size() - 1)
		| boost::adaptors::transformed(std::function<simplify_segment(std::size_t)>([&p](std::size_t i) {
			return simplify_segment(p.outer()[i], p.outer()[i+1]);
		})));

	for(auto const &inner: p.inners()) {
		Ring new_inner;
		simplify_ring(inner, new_inner, max_distance, outer_rtree);

		std::reverse(new_inner.begin(), new_inner.end());
		if(new_inner.size() > 3 && boost::geometry::perimeter(new_inner) > 3 * max_distance) {
			simplify_combine(result.inners(), std::move(new_inner));
		}
	}

	simplify_rtree inners_rtree;
	for(auto &inner: result.inners()) {
		std::reverse(inner.begin(), inner.end());

		inners_rtree.insert(
			boost::irange<std::size_t>(0, inner.size() - 1)
			| boost::adaptors::transformed(std::function<simplify_segment(std::size_t)>([&inner](std::size_t i) {
				return simplify_segment(inner[i], inner[i+1]);
			})));
	}

	simplify_ring(p.outer(), result.outer(), max_distance, inners_rtree);
	if(result.outer().size() > 3 && boost::geometry::perimeter(result.outer()) > 3 * max_distance) {
		return result;
	}

	result = Polygon();
	return result;
}

Linestring simplify(Linestring const &ls, double max_distance)
{
	Linestring result;
	boost::geometry::simplify(ls, result, max_distance);
	return result;
}

MultiPolygon simplify(MultiPolygon const &mp, double max_distance)
{
	MultiPolygon result_mp;
	for(auto const &p: mp) {
		Polygon new_p = simplify(p, max_distance);
    	if(!new_p.outer().empty()) {
			simplify_combine(result_mp, std::move(new_p));
		}
	}

	return result_mp;
}

void make_valid(MultiPolygon &mp)
{
	MultiPolygon result;
	for(auto const &p: mp) {
		geometry::correct(p, result, 1E-12);
	}
	mp = result;
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_BITWISE_FUNCTIONS_BITWISE_SELECT_HPP_INCLUDED
#define BOOST_SIMD_BITWISE_FUNCTIONS_BITWISE_SELECT_HPP_INCLUDED
#include <boost/simd/include/functor.hpp>
#include <boost/dispatch/include/functor.hpp>
namespace boost { namespace simd { namespace tag
  {
    /*!
      @brief  bitwise_select generic tag

      Represents the bitwise_select function in generic contexts.

      @par Models:
      Hierarchy
    **/
    struct bitwise_select_ : ext::elementwise_<bitwise_select_>
    {
      /// @brief Parent hierarchy
      typedef ext::elementwise_<bitwise_select_> parent;
      template<class... Args>
      static BOOST_FORCEINLINE BOOST_AUTO_DECLTYPE dispatch(Args&&... args)
      BOOST_AUTO_DECLTYPE_BODY( dispatching_bitwise_select_( ext::adl_helper(), static_cast<Args&&>(args)... ) )
    };
  }
  namespace ext
  {
    template<class Site>
    BOOST_FORCEINLINE generic_dispatcher<tag::bitwise_select_, Site> dispatching_bitwise_select_(adl_helper, boost::dispatch::meta::unknown_<Site>, ...)
    {
      return generic_dispatcher<tag::bitwise_select_, Site>();
    }
    template<class... Args>
    struct impl_bitwise_select_;
  }
  /*!
    Returns the bitwise selection of bits from the second
    or third operand according to the setting of the bits
    of the first operand

    @par semantic:
    For any given value @c x, of type @c T1, @c y of type @c T2 and @c z of type @c T2
    of same memory size:

    @code
    T2 r = bitwise_select(x, y, z);
    @endcode

    The code is equivalent to:

    @code
    T2 r = (x&y)|(z&~y);
    @endcode

    @par Alias

    b_select

    @param  a0 selector
    @param  a1 bits selected for a0 bits that are on
    @param  a2 bits selected for a0 bits that are off

    @return      a value of the same type as the second input.

  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::bitwise_select_, bitwise_select, 3)
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::bitwise_select_, b_select, 3)
} }

#endif


//
//  TerrainRebuildActor.cpp
//  PinkTopaz
//
//  Created by Andrew Fox on 6/12/17.
//
//

#include "Terrain/TerrainRebuildActor.hpp"
#include "ThreadName.hpp"
#include "AutoreleasePool.hpp"
#include <glm/gtx/string_cast.hpp>
#include <unordered_map>

#include <boost/functional/hash.hpp>
namespace std {
    template <> struct hash<glm::ivec2>
    {
        size_t operator()(const glm::ivec2 &vec) const
        {
            std::hash<glm::ivec2::value_type> hasher;
            size_t seed = 0;
            boost::hash_combine(seed, hasher(vec.x));
            boost::hash_combine(seed, hasher(vec.y));
            return seed;
        }
    };
}

// The threshold below which changes in the search point are ignored.
// This prevents re-sorting uneccessarily as all cells fall on a regular grid,
// and small changes will not affect the sorting.
constexpr float searchPointThreshold = 8.f;

TerrainRebuildActor::~TerrainRebuildActor()
{
    _threadShouldExit = true;
    _cvar.notify_all();
    for (auto& thread : _threads) {
        thread.join();
    }
}

TerrainRebuildActor::TerrainRebuildActor(std::shared_ptr<spdlog::logger> log,
                                         unsigned numThreads,
                                         glm::vec3 initialSearchPoint,
                                         std::shared_ptr<TaskDispatcher> mainThreadDispatcher,
                                         entityx::EventManager &events,
                                         std::chrono::steady_clock::time_point appStartTime,
                                         std::function<void(const Batch &)> &&processBatch)
: _threadShouldExit(false),
  _processBatch(std::move(processBatch)),
  _searchPoint(initialSearchPoint),
  _mainThreadDispatcher(mainThreadDispatcher),
  _events(events),
  _log(log),
  _appStartTime(appStartTime)
{
    for (size_t i = 0; i < numThreads; ++i) {
        _threads.emplace_back([this]{
            setNameForCurrentThread("TerrainRebuildActor");
            worker();
        });
    }
}

void TerrainRebuildActor::push(const std::vector<std::pair<Morton3, AABB>> &cells)
{
    std::scoped_lock lock(_lock);

    int numberAdded = 0;
    std::unordered_map<glm::ivec2, std::vector<Cell>> mapColumnToCells;

    for (const auto &cellPair : cells) {
        const Morton3 cellIndex = cellPair.first;
        const AABB &boundingBox = cellPair.second;
        const auto insertResult = _set.insert(boundingBox);
        if (insertResult.second) {
            numberAdded++;

            const glm::ivec3 cellCoords = cellIndex.decode();
            const glm::ivec2 columnCoords(cellCoords.x, cellCoords.z);

            auto iter = mapColumnToCells.find(columnCoords);
            if (iter == mapColumnToCells.end()) {
                mapColumnToCells.insert(std::make_pair(columnCoords, std::vector<Cell>{}));
                iter = mapColumnToCells.find(columnCoords);
            }

            iter->second.emplace_back(_log,
                                      cellIndex,
                                      boundingBox,
                                      insertResult.first,
                                      _mainThreadDispatcher,
                                      _events,
                                      _appStartTime);
        }
    }

    _log->trace("Added {} chunks in push()", numberAdded);
    if (numberAdded > 0) {
        for (auto &pair : mapColumnToCells) {
            _pendingBatches.emplace_back(std::move(pair.second));
        }
        sort();
        if (mapColumnToCells.size() == 1) {
            _cvar.notify_one();
        } else {
            _cvar.notify_all();
        }
    }
}

void TerrainRebuildActor::setSearchPoint(glm::vec3 searchPoint)
{
    std::scoped_lock lock(_lock);
    if (glm::distance(_searchPoint, searchPoint) > searchPointThreshold) {
        _log->trace("Updating search point from {} to {}",
                    glm::to_string(_searchPoint), glm::to_string(searchPoint));
        _searchPoint = searchPoint;
        sort();
    }
}

void TerrainRebuildActor::worker()
{
    while (!_threadShouldExit) {
        AutoreleasePool pool;

        // Wait for a cell to work on.
        boost::optional<Batch> maybeBatch;
        {
            std::unique_lock<std::mutex> lock(_lock);
            _cvar.wait(lock, [this]{
                return _threadShouldExit || !_pendingBatches.empty();
            });
            if (!(_threadShouldExit || _pendingBatches.empty())) {
                maybeBatch = boost::make_optional(std::move(_pendingBatches.front()));
                _pendingBatches.pop_front();
            }
        }

        if (maybeBatch) {
            Batch &batch = *maybeBatch;
            _processBatch(batch);

            // Remove from the set only at the very end.
            std::unique_lock<std::mutex> lock(_lock);
            for (Cell &cell : batch.requestedCells()) {
                cell.progress.setState(TerrainProgressEvent::Complete);
                cell.progress.dump();
                _set.erase(cell.setIterator);
            }
        }
    }
}

void TerrainRebuildActor::sort()
{
    std::sort(_pendingBatches.begin(), _pendingBatches.end(),
              [searchPoint=_searchPoint](const Batch& batch1, const Batch& batch2){
                  const AABB &a = batch1.boundingBox();
                  const AABB &b = batch2.boundingBox();
                  const auto distA = glm::distance(a.center, searchPoint);
                  const auto distB = glm::distance(b.center, searchPoint);
                  return distA < distB;
              });
}


// -*- C++ -*-
//
// Jiao Lin <jiao.lin@gmail.com>
//

#include <sstream>
#include <boost/python.hpp>
#include "mccomponents/kernels/sample/diffraction/SingleCrystalDiffractionKernel.h"
#include "mccomponents/boostpython_binding/wrap_kernel.h"
#include "wrap_vector.h"

namespace wrap_mccomponents {

  void wrap_SingleCrystalDiffractionKernel()
  {
    using namespace boost::python;
    using namespace mccomponents::boostpython_binding;
    using namespace mccomponents::kernels;
    typedef mccomponents::kernels::SingleCrystalDiffractionKernel w_t;

    class_<Lattice>
      ("Lattice",
       init<const R_t &, const R_t &, const R_t &>()
       )
      .def_readonly("ra", &Lattice::ra)
      .def_readonly("rb", &Lattice::rb)
      .def_readonly("rc", &Lattice::rc)
      ;

    class_<HKL>
      ("HKL",
       init<int, int, int, w_t::float_t>()
       )
      .def_readonly("h", &HKL::h)
      .def_readonly("k", &HKL::k)
      .def_readonly("l", &HKL::l)
      .def_readonly("F2", &HKL::F2)
      ;

    wrap_vector2<HKL>( "HKL" );

    kernel_wrapper<w_t>::wrap
      ("SingleCrystalDiffractionKernel",
       init<const Lattice &, const w_t::hkllist_t &, w_t::float_t, w_t::float_t, w_t::float_t>()
       [with_custodian_and_ward<1,2,
        with_custodian_and_ward<1,3> >()]
       )
      .def("check_reflections", &w_t::check_reflections)
      ;
  }
}

// End of file