/*    Copyright (c) 2010-2019, 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.
*/

#include <iostream>
#include <fstream>
#include <functional>

#include <boost/filesystem.hpp>
#include "Problems/applicationOutput.h"
#include "Problems/getAlgorithm.h"
#include "Problems/saveOptimizationResults.h"

#include "tudat/simulation/simulation.h"
#include "tudat/astro/LowThrustTrajectories/lowThrustOptimisationSetup.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/hodographicShaping.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/sphericalShaping.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/createBaseFunctionHodographicShaping.h"
#include "tudat/astro/LowThrustTrajectories/lowThrustLegSettings.h"
#include "tudat/astro/LowThrustTrajectories/lowThrustLeg.h"
#include "tudat/astro/ephemerides/approximatePlanetPositions.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/hodographicShapingOptimisationSetup.h"
#include "tudat/astro/LowThrustTrajectories/ShapeBasedMethods/getRecommendedBaseFunctionsHodographicShaping.h"
#include "tudat/simulation/optimisationSettings.h"

using namespace tudat;
using namespace tudat::shape_based_methods;
using namespace tudat::numerical_integrators;
using namespace tudat::simulation_setup;

std::vector< std::vector< std::shared_ptr< BaseFunctionHodographicShaping > > > getShapingBasisFunctions(
        const double timeOfFlight, const int numberOfRevolutions )
{
    Eigen::VectorXd dummyVector;

    // Get recommended base functions for the radial velocity composite function.
    std::vector< std::shared_ptr< BaseFunctionHodographicShaping > > radialVelocityFunctionComponents;
    getRecommendedRadialVelocityBaseFunctions(
                radialVelocityFunctionComponents, dummyVector, timeOfFlight );

    // Get recommended base functions for the normal velocity composite function.
    std::vector< std::shared_ptr< BaseFunctionHodographicShaping > > normalVelocityFunctionComponents;
    getRecommendedNormalAxialBaseFunctions(
                normalVelocityFunctionComponents, dummyVector, timeOfFlight );

    // Get recommended base functions for the axial velocity composite function.
    std::vector< std::shared_ptr< BaseFunctionHodographicShaping > > axialVelocityFunctionComponents;
    getRecommendedAxialVelocityBaseFunctions(
                axialVelocityFunctionComponents, dummyVector, timeOfFlight, numberOfRevolutions );

    {
        double frequency = 2.0 * mathematical_constants::PI / timeOfFlight;
        double scaleFactor = 1.0 / timeOfFlight;

        std::shared_ptr< BaseFunctionHodographicShapingSettings > fourthRadialVelocityBaseFunctionSettings =
                std::make_shared< PowerTimesTrigonometricFunctionHodographicShapingSettings >(
                    1.0, 0.5 * frequency, scaleFactor );
        std::shared_ptr< BaseFunctionHodographicShapingSettings > fifthRadialVelocityBaseFunctionSettings =
                std::make_shared< PowerTimesTrigonometricFunctionHodographicShapingSettings >(
                    1.0, 0.5 * frequency, scaleFactor );

        // Add two additional base functions
        radialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( scaledPowerSine, fourthRadialVelocityBaseFunctionSettings ) );
        radialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( scaledPowerCosine, fifthRadialVelocityBaseFunctionSettings ) );

    }

    {
        double scaleFactor = 1.0 / timeOfFlight;

        // Create base function settings for the components of the axial velocity composite function.
        std::shared_ptr< shape_based_methods::BaseFunctionHodographicShapingSettings > firstAxialVelocityBaseFunctionSettings =
                std::make_shared< shape_based_methods::PowerFunctionHodographicShapingSettings >( 3.0, scaleFactor );
        std::shared_ptr< shape_based_methods::BaseFunctionHodographicShapingSettings > secondAxialVelocityBaseFunctionSettings =
                std::make_shared< shape_based_methods::PowerFunctionHodographicShapingSettings >( 4.0, scaleFactor );
        std::shared_ptr< shape_based_methods::BaseFunctionHodographicShapingSettings > thirdAxialVelocityBaseFunctionSettings =
                std::make_shared< shape_based_methods::PowerFunctionHodographicShapingSettings >( 5.0, scaleFactor );

        // Set components for the axial velocity function.
        axialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( shape_based_methods::scaledPower, firstAxialVelocityBaseFunctionSettings ) );
        axialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( shape_based_methods::scaledPower, secondAxialVelocityBaseFunctionSettings ) );
        axialVelocityFunctionComponents.push_back(
                    createBaseFunctionHodographicShaping( shape_based_methods::scaledPower, thirdAxialVelocityBaseFunctionSettings ) );

    }

    return { radialVelocityFunctionComponents, normalVelocityFunctionComponents, axialVelocityFunctionComponents };
}

//! Execute  main
int main( )
{
    //Set seed for reproducible results
    pagmo::random_device::set_seed( 123 );

    tudat::spice_interface::loadStandardSpiceKernels( );

    // Ephemeris functions of bodies.
    ephemerides::EphemerisPointer pointerToDepartureBodyEphemeris = std::make_shared< ephemerides::ApproximatePlanetPositions>(
                ephemerides::ApproximatePlanetPositionsBase::BodiesWithEphemerisData::earthMoonBarycenter );
    ephemerides::EphemerisPointer pointerToArrivalBodyEphemeris = std::make_shared< ephemerides::ApproximatePlanetPositions >(
                ephemerides::ApproximatePlanetPositionsBase::BodiesWithEphemerisData::mars );

    std::function< Eigen::Vector6d( const double ) > departureStateFunction = [ = ]( const double currentTime )
    { return pointerToDepartureBodyEphemeris->getCartesianState( currentTime ); };
    std::function< Eigen::Vector6d( const double ) > arrivalStateFunction = [ = ]( const double currentTime )
    { return pointerToArrivalBodyEphemeris->getCartesianState( currentTime ); };

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////        GRID SEARCH FOR HODOGRAPHIC SHAPING LOWEST-ORDER SOLUTION            /////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    // Define bounds for departure date and time-of-flight.
    std::pair< double, double > departureTimeBounds =
            std::make_pair( 7304.5 * physical_constants::JULIAN_DAY, 13225.5 * physical_constants::JULIAN_DAY  );
    std::pair< double, double > timeOfFlightBounds =
            std::make_pair( 300.0 * physical_constants::JULIAN_DAY, 2000.0 * physical_constants::JULIAN_DAY );

    // Define lower and upper bounds for the radial velocity free coefficients.
    std::vector< std::vector< double > > bounds( 2, std::vector< double >( 7, 0.0 ) );
    bounds[ 0 ][ 0 ] =  departureTimeBounds.first;
    bounds[ 1 ][ 0 ] = departureTimeBounds.second;
    bounds[ 0 ][ 1 ] = timeOfFlightBounds.first;
    bounds[ 1 ][ 1 ] = timeOfFlightBounds.second;
    bounds[ 0 ][ 2 ] = - 5000.0;
    bounds[ 1 ][ 2 ] = 5000.0;
    bounds[ 0 ][ 3 ] = -5000;
    bounds[ 1 ][ 3 ] = 5000.0;
    bounds[ 0 ][ 4 ] = - 5000.0;
    bounds[ 1 ][ 4 ] = 5000.0;
    bounds[ 0 ][ 5 ] = -5000;
    bounds[ 1 ][ 5 ] = 5000.0;
    bounds[ 0 ][ 6 ] = -5000;
    bounds[ 1 ][ 6 ] = 5000.0;

    for( int useMultiObjective = 0; useMultiObjective < 2; useMultiObjective++ )
    {
        for( int revolutions = 2; revolutions < 3; revolutions++ )
        {
            // Create object to compute the problem fitness
            problem prob{ HodographicShapingOptimisationProblem(
                            departureStateFunction, arrivalStateFunction, spice_interface::getBodyGravitationalParameter( "Sun" ),
                            revolutions, std::bind( &getShapingBasisFunctions, std::placeholders::_1, revolutions ), bounds,
                            useMultiObjective, 2000.0 ) };

            //sade, gaco, sga, de
            algorithm algo;
            if( !useMultiObjective )
            {
                algo = algorithm{ simulated_annealing( ) };
            }
            else
            {
                algo = algorithm{ nsga2( ) };
            }

            // Create an island with 1000 individuals
            island isl{algo, prob, 2000 };

            // Evolve for 512 generations
            for( int i = 0 ; i < 101; i++ )
            {
                isl.evolve( );
                while( isl.status( ) != pagmo::evolve_status::idle &&
                       isl.status( ) != pagmo::evolve_status::idle_error )
                {
                    isl.wait( );
                }

                if( i % 25 == 0 )
                {
                    if( !useMultiObjective )
                    {
                        std::cout<<"Iteration: "<<" "<<i<<"; Best Delta V: "<<isl.get_population( ).champion_f( ).at( 0 )<<std::endl;

                        printPopulationToFile( isl.get_population( ).get_x( ), "hodograph_single_objective_" + std::to_string( i / 25 ), false );
                        printPopulationToFile( isl.get_population( ).get_f( ), "hodograph_single_objective_" + std::to_string( i / 25 ), true );
                    }
                    else
                    {
                        std::cout<<"Iteration: "<<" "<<i<<std::endl;

                        printPopulationToFile( isl.get_population( ).get_x( ), "hodograph_multi_objective_" + std::to_string( i / 25 ), false );
                        printPopulationToFile( isl.get_population( ).get_f( ), "hodograph_multi_objective_" + std::to_string( i / 25 ), true );
                    }
                }

            }

            if( !useMultiObjective )
            {
                std::cout<<"Final best Delta V: "<<isl.get_population( ).champion_f( ).at( 0 )<<std::endl;

                std::vector< double > bestPopulation = isl.get_population( ).champion_x( );

                Eigen::VectorXd radialFreeParameters = Eigen::VectorXd::Zero( 2 );
                Eigen::VectorXd normalFreeParameters = Eigen::VectorXd::Zero( 0 );
                Eigen::VectorXd axialFreeParameters = Eigen::VectorXd::Zero( 3 );

                radialFreeParameters << bestPopulation.at( 2 ), bestPopulation.at( 3 );
                axialFreeParameters << bestPopulation.at( 4 ), bestPopulation.at( 5 ), bestPopulation.at( 6 );

                double timeOfFlight = bestPopulation.at( 1 );
                double derpartureTime = bestPopulation.at( 0 );
                double arrivalTime = derpartureTime + timeOfFlight;

                double initialMass = 2000.0;
                double specificImpulse = 3000.0;

                std::vector< std::vector< std::shared_ptr< BaseFunctionHodographicShaping > > > shapingFunctions = getShapingBasisFunctions(
                            timeOfFlight, 2 );

                std::shared_ptr< HodographicShaping > hodographicShaping =
                        std::make_shared< HodographicShaping >(
                            departureStateFunction( derpartureTime ), arrivalStateFunction( arrivalTime ), timeOfFlight,
                            spice_interface::getBodyGravitationalParameter( "Sun" ), 2,
                            shapingFunctions.at( 0 ), shapingFunctions.at( 1 ), shapingFunctions.at( 2 ),
                            radialFreeParameters, normalFreeParameters, axialFreeParameters, initialMass );

                // Save results
                int numberOfSteps = 1000;
                double stepSize = timeOfFlight / static_cast< double >( numberOfSteps );
                std::shared_ptr< numerical_integrators::IntegratorSettings< double > > integratorSettings =
                        std::make_shared< numerical_integrators::IntegratorSettings< double > > ( numerical_integrators::rungeKutta4, 0.0, stepSize );

                std::vector< double > epochsToSaveResults;
                for ( int i = 0 ; i <= numberOfSteps ; i++ )
                {
                    epochsToSaveResults.push_back( i * stepSize );
                }

                std::map< double, Eigen::Vector6d > hodographicShapingTrajectory;
                std::map< double, Eigen::VectorXd > hodographicShapingMassProfile;
                std::map< double, Eigen::VectorXd > hodographicShapingThrustProfile;
                std::map< double, Eigen::VectorXd > hodographicShapingThrustAcceleration;

                hodographicShaping->getTrajectory(
                            epochsToSaveResults, hodographicShapingTrajectory );
                hodographicShaping->getMassProfile(
                            epochsToSaveResults, hodographicShapingMassProfile, [ = ]( const double ){ return specificImpulse; }, integratorSettings );
                hodographicShaping->getThrustForceProfile(
                            epochsToSaveResults, hodographicShapingThrustProfile, [ = ]( const double ){ return specificImpulse; }, integratorSettings );
                hodographicShaping->getCylindricalThrustAccelerationProfile(
                            epochsToSaveResults, hodographicShapingThrustAcceleration );

                input_output::writeDataMapToTextFile(
                            hodographicShapingTrajectory, "hodographicShapingOptimalTrajectory.dat", tudat_pagmo_applications::getOutputPath( ) );

                input_output::writeDataMapToTextFile(
                            hodographicShapingMassProfile, "hodographicShapingOptimalMassProfile.dat", tudat_pagmo_applications::getOutputPath( ) );

                input_output::writeDataMapToTextFile(
                            hodographicShapingThrustProfile, "hodographicShapingOptimalThrustProfile.dat", tudat_pagmo_applications::getOutputPath( ) );

                input_output::writeDataMapToTextFile(
                            hodographicShapingThrustAcceleration, "hodographicShapingOptimalThrustAcceleration.dat", tudat_pagmo_applications::getOutputPath( ) );
            }
        }
    }
}


#include <iostream>
#include <string>
#include <assert.h>
#include <vector>
#include <numeric>
#include <algorithm>
#include <sstream>
#include <conio.h>

#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/split.hpp>
#include "boost/algorithm/string/classification.hpp"

using namespace std;

namespace {

    float charactersPerLine(int total, int maxCharsPerLine) {
        assert(maxCharsPerLine > 0);
        float result = (total) / (1.f + (float)total / maxCharsPerLine);
        return result;
    }

    struct line
    {
        vector<string> words;

        int charCount() const {
            return std::accumulate(words.begin(), words.end(), 0, [](int val, const string& str) {
                return val + (int)str.length();
            })
                + (words.size() > 0 ? words.size() - 1 : 0);
        }

        void addWord(const string& word) {
            words.push_back(word);
        }

        static line lineFromString(const string& source) {
            line ln;
            string into;
            stringstream ssin(source);
            while (ssin.good()) {
                ssin >> into;
                ln.words.push_back(into);
            }

            return ln;
        }

        void print() const {
            cout << charCount() << ' ';
            for (auto&str : words) {
                cout << str << ' ';
            }
        }

        string wholeString() const {
            return boost::algorithm::join(words, " ");
        }
    };

    struct line_set
    {
        vector<line> lines;
        float idealCpl;

        line& operator[] (int idx) {
            return lines[idx];
        }

        line& top()  {
            return lines.back();
        }

        line& newLine() {
            addLine(line());
            return top();
        }

        void addLine(const line& ln) {
            lines.push_back(ln);
        }

        bool isLast(int lineIdx) const {
            return lineIdx == (lines.size() - 1);
        }

        bool isFirst(int lineIdx) const {
            return lineIdx == 0;
        }

        void incrLength(int lineIdx) {
            if (isFirst(lineIdx)) {

                if (lines.size() >= 2) {
                    decrLength(1);
                }

                return ;
            }

            auto& from =  lines[lineIdx - 1].words;

            if (from.empty()) {
                return ;
            }

            auto word = from.back();
            from.pop_back();

            auto& to = lines[lineIdx].words;
            to.insert(to.begin(), word);
        }

        void decrLength(int lineIdx) {
            if (isFirst(lineIdx)) {

                if (lines.size() >= 2) {
                    incrLength(1);
                }

                return ;
            }

            auto& from = lines[lineIdx].words;

            if (from.empty()) {
                return ;
            }

            auto word = from.front();
            from.erase(from.begin());

            auto& to = lines[lineIdx - 1].words;
            to.push_back(word);
        }

        float meanSquare() const {
            float avg = average();

            return std::accumulate(lines.begin(), lines.end(), 0.f, [=](float val, const line& ln) {
                return val + powf(ln.charCount() - avg, 2.f);
            })
                / (float)lines.size();
        }

        float charDiffIn(int lineIdx) const {
            return charDiffIn(lines[lineIdx]);
        }

        float charDiffIn(const line& ln) const {
            return ln.charCount() - average();
        }

        float average() const {
            return charCount() / (float)lines.size();
        }

        int charCount() const {
            return std::accumulate(lines.begin(), lines.end(), 0, [](int val, const line& ln) {
                return val + ln.charCount();
            });
        }

    /*
        int minIdx() const {
            return std::min_element(lines.cbegin(), lines.cend(), [=](const line& ln, const line& smallest){
                return charDiffIn(ln) < charDiffIn(smallest);
            })
                - lines.begin();
        }
    */
    /*

        vector<int> sortedIndices() const {
            vector<int> indices;

            int idx = 0;
            std::transform(lines.begin(), lines.end(), std::back_inserter(indices), [&](const line& ln) {
                return idx++;
            });

            std::sort(indices.begin(), indices.end(), [=](int left, int right) {
                return fabs(charDiffIn(left)) < fabs(charDiffIn(right));
            });

            return indices;
        }*/

    /*
        int maxIdx() const {
            return std::max_element(lines.cbegin(), lines.cend(), [=](const line& ln, const line& smallest){
                return charDiffIn(ln) < charDiffIn(smallest);
            })
                - lines.begin();
        }*/

        void print() const {
            for (auto& line : lines) {
                line.print();
                cout << endl;
            }
        }

        vector<string> strings() const {
            vector<string> result;

            std::transform(lines.begin(), lines.end(), std::back_inserter(result), [&](const line& ln) {
                return ln.wholeString();
            });

            return result;
        }
    };

    bool isBreak(char c) {
        return
            c == ' ' ||
            c == '\n'
            //||
            // c == '.' ||
           // c == '(' || c == ')' ||
           // c == ','
            ;
    }

    vector<string> substringRange(string::iterator beg, string::iterator end) {

        vector<string> strings;
        boost::split( strings, boost::make_iterator_range(beg, end), &isBreak );

        return strings;
    }

    line_set initialSet(std::string& data, int charsPerLine = 40) {

        float cpl = charactersPerLine(data.length(), charsPerLine);

        line_set result;

        result.idealCpl = cpl;

        //auto last = data.begin();
        auto first = data.begin();

        while (first != data.end())
        {
            auto& line = result.newLine();

            if ((data.end() - first) >= cpl ) {

                auto middle = first + (int)cpl;
                auto left = middle, right = middle;

                while (left > first && isBreak(*left) == false) {
                    //printf("last is '%c'\n", *left);
                    --left;
                }

                while (right < data.end() && isBreak(*right) == false) {
                    //printf("last is '%c'\n", *right);
                    ++right;
                }

                auto selected = (middle - left < (right - middle) * 1.2) ? left : right;

                line.words = substringRange(first, selected);

                first = selected;
            }
            else {
                line.words = substringRange(first, data.end());

                first = data.end();
            }
        }

        return result;

    }

    struct operation {
        line_set *lset;
        int lineIdx;
        bool increase;

        void doIncr(bool incr, int lnIdx, line_set& set) {
            lineIdx = lnIdx;
            increase = incr;
            lset = &set;

            if (incr) {
                set.incrLength(lnIdx);
            } else {
                set.decrLength(lnIdx);
            }
        }

        void revert() {
            doIncr(!increase, lineIdx, *lset);
        }
    };

}

vector<string> StringUtil_wrapString(const char *csource, int charsPerLine)
{

    string source = csource;

/*
    string source = STR(is a 2005 American documentary film by Steve Anderson, which argues the titular word is key
        to discussions on freedom of speech and censorship. It provides perspectives from art,
        linguistics and society. Oxford English Dictionary editor Jesse Sheidlower, journalism analyst David Shaw,
        and linguists Reinhold Aman and Geoffrey Nunberg explain the terms evolution. Comedian Billy Connolly states it can
        be understood regardless of ones background, and musician Alanis Morissette says its taboo nature gives it power.
        The film contains the last interview of author Hunter S. Thompson before his suicide. It features animated sequences by Bill Plympton.
        The documentary was first screened at the AFI Film Festival at ArcLight Hollywood. The New York Times critic A. O. Scott called
        the film a battle between advocates of morality and supporters of freedom of expression; a review by the American Film Institute said
        this freedom must extend to words that offend. Other reviewers criticized the films length and repetitiveness. Its DVD
        was released in the US and the UK and used in university cour);*/

    line_set set = initialSet(source, charsPerLine);

    //cout << "ideal " << set.idealCpl << endl;

    operation setOpr;

    float delta = set.meanSquare();

    //auto sorted = set.sortedIndices();

    //set.print();

    int loop = 0;
    while (loop < 40)
    {
        ++loop;

        float oldDelta = set.meanSquare();

        for (size_t i = 0; i < set.lines.size(); ++i) {

            bool needIncr = set.charDiffIn(i) < 0;

            float oldDelta = set.meanSquare();
            setOpr.doIncr(needIncr, i, set);
            float newDelta = set.meanSquare();

            if (newDelta > oldDelta) {
                setOpr.revert();
            } else {
                i = i;
            }

        }

        float newDelta = set.meanSquare();

        if (fabs(oldDelta - newDelta) < 0.00001f) {
            break;
        }
    }

    //cout << endl;

    //set.print();

   // getch();

    return set.strings();
}


//#include <cassert>
//#include <cstdio>
//#include <cmath>
//#include <iostream>
//#include <iomanip>
//#include <sstream>
//#include <vector>
//#include <set>
//#include <map>
//#include <queue>
//#include <numeric>
//#include <algorithm>
//
//#include <boost/heap/fibonacci_heap.hpp>
//
//using namespace std;
//using lint = long long;
//constexpr int MOD = 1000000007, INF = 1010101010;
//constexpr lint LINF = 1LL << 60;
//
//template <class T>
//ostream &operator<<(ostream &os, const vector<T> &vec) {
//	for (const auto &e : vec) os << e << (&e == &vec.back() ? "\n" : " ");
//	return os;
//}
//
//template <class T>
//ostream &operator<<(ostream &os, const set<T> &st) {
//	for (const auto &e : st) os << e << " ";
//	return os;
//}
//
//template <class T1, class T2>
//ostream &operator<<(ostream &os, const pair<T1, T2> &p) {
//	os << "(" << p.first << "," << p.second << ")";
//	return os;
//}
//
//template <class T1, class T2>
//ostream &operator<<(ostream &os, const map<T1, T2> &mp) {
//	for (const auto &e : mp) os << e << " ";
//	return os;
//}
//
//#ifdef _DEBUG
//template <class T>
//void dump(const char* str, T &&h) { cerr << str << " = " << h << "\n"; };
//template <class Head, class... Tail>
//void dump(const char* str, Head &&h, Tail &&... t) {
//	while (*str != ',') cerr << *str++; cerr << " = " << h << "\n";
//	dump(str + (*(str + 1) == ' ' ? 2 : 1), t...);
//}
//#define DMP(...) dump(#__VA_ARGS__, __VA_ARGS__)
//#else
//#define DMP(...) ((void)0)
//#endif
//
//template<class T = lint>
//struct Edge {
//	int to;
//	T cost;
//	int tank;
//	Edge() {}
//	Edge(int to, T cost, int tank = 0) : to(to), cost(cost), tank(tank) {}
//	bool operator>(const Edge &r) const { return this->cost > r.cost; }
//};
//
//int main() {
//
//	cin.tie(nullptr);
//	ios::sync_with_stdio(false);
//
//	int N, M, L;
//	cin >> N >> M >> L;
//
//	vector<vector<Edge<int>>> edges(N);
//	for (int i = 0; i < M; i++) {
//		int u, v, len;
//		cin >> u >> v >> len;
//		u--, v--;
//		if (len > L) continue;
//		edges[u].emplace_back(v, len);
//		edges[v].emplace_back(u, len);
//	}
//
//	auto dijkstra = [&](int st) {
//		vector<pair<int, int>> dp(N, { INF, 0 });
//		dp[st] = { 0, -L };
//		using ppi = pair<pair<int, int>,int>;
//		priority_queue<ppi, vector<ppi>, greater<ppi>> que;
//		que.emplace(dp[st], st);
//		while (!que.empty()) {
//			auto now = que.top();
//			que.pop();
//
//			if (now.first > dp[now.second]) continue;
//
//			for (const auto &e : edges[now.second]) {
//
//				auto tmp = now.first;
//				if (e.cost > -tmp.second) {
//					tmp.first++;
//					tmp.second = -L + e.cost;
//				}
//				else tmp.second += e.cost;
//
//				if (tmp < dp[e.to]) {
//					dp[e.to] = tmp;
//					que.emplace(tmp, e.to);
//				}
//
//			}
//		}
//		return dp;
//	};
//
//	vector<vector<pair<int, int>>> memo(N);
//	for (int i = 0; i < N; i++) memo[i] = dijkstra(i);
//
//	int Q;
//	cin >> Q;
//	for (int i = 0; i < Q; i++) {
//		int s, t;
//		cin >> s >> t;
//		s--, t--;
//		auto tmp = memo[s][t].first;
//		if (tmp != INF) cout << tmp << "\n";
//		else cout << -1 << "\n";
//	}
//
//	return 0;
//}


#include "stdafx.h"

#include <Adapters/tapkee.hpp>
#include <sampling.hpp>

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

using namespace Texturize;

///////////////////////////////////////////////////////////////////////////////////////////////////
///// PCA descriptor extractor implementation                                                 /////
///////////////////////////////////////////////////////////////////////////////////////////////////

cv::Mat Tapkee::PCADescriptorExtractor::calculateNeighborhoodDescriptors(const Sample& exemplar) const
{
	// Create a UV-Map for the sample.
	cv::Mat uv = this->createContinuousUvMap(exemplar);

	// Calculate the descriptors.
	return this->calculateNeighborhoodDescriptors(exemplar, uv);
}

cv::Mat Tapkee::PCADescriptorExtractor::calculateNeighborhoodDescriptors(const Sample& exemplar, const cv::Mat& uv) const
{
	// Get the pixel neighborhoods and a buffer to store the projected pixels to.
	cv::Mat projected, neighborhoods = this->getPixelNeighborhoods(exemplar, uv);

	// Convert into an Eigen matrix.
	tapkee::DenseMatrix eigenNeighbors, embeddedResult;
	cv::cv2eigen(neighborhoods, eigenNeighbors);

	// Apply PCA.
	tapkee::ParametersSet parameters = tapkee::kwargs[
		tapkee::method = tapkee::PCA,
		tapkee::target_dimension = exemplar.channels()
	];

	tapkee::TapkeeOutput result = tapkee::initialize()
		.withParameters(parameters)
		.embedUsing(eigenNeighbors);

	// Convert it back.
	cv::eigen2cv(result.embedding, projected);

	// Return the projected neighborhoods.
	TEXTURIZE_ASSERT(projected.cols == exemplar.channels());

	return projected;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
///// t-SNE descriptor extractor implementation                                               /////
///////////////////////////////////////////////////////////////////////////////////////////////////

cv::Mat Tapkee::SNEDescriptorExtractor::calculateNeighborhoodDescriptors(const Sample& exemplar) const
{
	// Create a UV-Map for the sample.
	cv::Mat uv = this->createContinuousUvMap(exemplar);

	// Calculate the descriptors.
	return this->calculateNeighborhoodDescriptors(exemplar, uv);
}

cv::Mat Tapkee::SNEDescriptorExtractor::calculateNeighborhoodDescriptors(const Sample& exemplar, const cv::Mat& uv) const
{
	// Get the pixel neighborhoods and a buffer to store the projected pixels to.
	cv::Mat projected, neighborhoods = this->getPixelNeighborhoods(exemplar, uv);

	// Convert into an Eigen matrix.
	tapkee::DenseMatrix eigenNeighbors;
	cv::cv2eigen(neighborhoods, eigenNeighbors);

	// Apply PCA.
	tapkee::ParametersSet parameters = tapkee::kwargs[
		tapkee::method = tapkee::tDistributedStochasticNeighborEmbedding,
		tapkee::target_dimension = exemplar.channels()
	];

	tapkee::TapkeeOutput result = tapkee::initialize()
		.withParameters(parameters)
		.embedUsing(eigenNeighbors);

	// Convert it back.
	cv::eigen2cv(result.embedding, projected);

	// Return the projected neighborhoods.
	TEXTURIZE_ASSERT(projected.cols == exemplar.channels());

	return projected;
}


/***************************************************************************
*   Copyright 2012 - 2013 Advanced Micro Devices, Inc.
*
*   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.

***************************************************************************/

/*
TODO:
1. Optimize the Kernel. In this version transform and reduce are called directly which performs better.
2. Found a caveat in Multi-GPU scenario (Evergreen+Tahiti). Which basically applies to most of the routines.
*/

#if !defined( INNERPRODUCT_INL )
#define INNERPRODUCT_INL
#define USE_KERNEL 0
#pragma once

#include <boost/thread/once.hpp>
#include <boost/bind.hpp>
#include <type_traits>
#include <bolt/cl/detail/reduce.inl>
#include <bolt/cl/detail/transform.inl>

#include "bolt/cl/bolt.h"

namespace bolt {
    namespace cl {
        // default control, two-input transform, std:: iterator
        template<typename InputIterator, typename OutputType, typename BinaryFunction1, typename BinaryFunction2>
        OutputType inner_product(bolt::cl::control& ctl, InputIterator first1, InputIterator last1, InputIterator first2,
             OutputType init, BinaryFunction1 f1, BinaryFunction2 f2, const std::string& user_code )
        {
            return detail::inner_product_detect_random_access( ctl, first1, last1, first2, init, f1, f2, user_code,
                std::iterator_traits< InputIterator >::iterator_category( ) );
        }

        // default control, two-input transform, std:: iterator
        template<typename InputIterator, typename OutputType, typename BinaryFunction1, typename BinaryFunction2>
        OutputType inner_product( InputIterator first1, InputIterator last1, InputIterator first2, OutputType init,
            BinaryFunction1 f1, BinaryFunction2 f2, const std::string& user_code )
        {
            return detail::inner_product_detect_random_access( control::getDefault(), first1, last1, first2, init, f1,
                f2, user_code, std::iterator_traits< InputIterator >::iterator_category( ) );
        }
        template<typename InputIterator, typename OutputType>
        OutputType inner_product(bolt::cl::control& ctl, InputIterator first1, InputIterator last1, InputIterator first2,
            OutputType init, const std::string& user_code )
        {
            typedef typename std::iterator_traits<InputIterator>::value_type iType;
            return detail::inner_product_detect_random_access( ctl, first1, last1, first2, init, bolt::cl::plus< iType >( ),
                bolt::cl::multiplies< iType >( ), user_code, std::iterator_traits< InputIterator >::iterator_category( ) );
        }

        // default control, two-input transform, std:: iterator
        template<typename InputIterator, typename OutputType>
        OutputType inner_product( InputIterator first1, InputIterator last1, InputIterator first2, OutputType init,
            const std::string& user_code )
        {
            typedef typename std::iterator_traits<InputIterator>::value_type iType;
            return detail::inner_product_detect_random_access( control::getDefault(), first1, last1, first2, init,
                bolt::cl::plus< iType >( ), bolt::cl::multiplies< iType >( ), user_code,
                std::iterator_traits< InputIterator >::iterator_category( ) );
        }

    }//end of cl namespace
};//end of bolt namespace

namespace bolt {
    namespace cl {
        namespace detail {

#if USE_KERNEL
             struct CallCompiler_InnerProduct {
                static void constructAndCompile(::cl::Kernel *masterKernel,  std::string cl_code, std::string valueTypeName,
                    std::string functorTypeName1, std::string functorTypeName2, const control *ctl) {

                    const std::string instantiationString =
                        "// Host generates this instantiation string with user-specified value type and functor\n"
                        "template __attribute__((mangled_name(innerProductInstantiated)))\n"
                        "__attribute__((reqd_work_group_size(64,1,1)))\n"
                        "kernel void innerProductTemplate(\n"
                        "global " + valueTypeName + "* A,\n"
                        "global " + valueTypeName + "* B,\n"
                        "const int length,\n"
                        "global " + functorTypeName1 + "* userFunctor,\n"
                        "global " + functorTypeName2 + "* userFunctor2,\n"
                        "global " + valueTypeName + "* result,\n"
                        "local " + valueTypeName + "* scratch\n"
                        ");\n\n";

                    std::string functorTypeNames = functorTypeName1 + " , " + functorTypeName2;

                    bolt::cl::constructAndCompileString( masterKernel, "innerProduct", inner_product_kernels,
                        instantiationString, cl_code, valueTypeName, functorTypeNames, *ctl);

                };
            };

#endif
            // Wrapper that uses default control class, iterator interface
            template<typename InputIterator, typename OutputType, typename BinaryFunction1, typename BinaryFunction2>
            OutputType inner_product_detect_random_access( bolt::cl::control& ctl, const InputIterator& first1,
                const InputIterator& last1, const InputIterator& first2, const OutputType& init, const BinaryFunction1& f1,
                const BinaryFunction2& f2, const std::string& user_code, std::input_iterator_tag )
            {
                //  TODO:  It should be possible to support non-random_access_iterator_tag iterators, if we copied the data
                //  to a temporary buffer.  Should we?
                static_assert( false, "Bolt only supports random access iterator types" );
            };

            template<typename InputIterator, typename OutputType, typename BinaryFunction1, typename BinaryFunction2>
            OutputType inner_product_detect_random_access( bolt::cl::control& ctl, const InputIterator& first1,
                const InputIterator& last1, const InputIterator& first2, const OutputType& init, const BinaryFunction1& f1,
                const BinaryFunction2& f2, const std::string& user_code, std::random_access_iterator_tag )
            {
                return inner_product_pick_iterator( ctl, first1, last1, first2, init, f1, f2, user_code,
                std::iterator_traits< InputIterator >::iterator_category( ) );
            };

            /*! \brief This template function overload is used to seperate device_vector iterators from all other iterators
                \detail This template is called by the non-detail versions of inclusive_scan, it already assumes random access
             *  iterators.  This overload is called strictly for non-device_vector iterators
            */
            template<typename InputIterator, typename OutputType, typename BinaryFunction1,typename BinaryFunction2>
            OutputType inner_product_pick_iterator(bolt::cl::control &ctl,  const InputIterator& first1,
                const InputIterator& last1, const InputIterator& first2, const OutputType& init, const BinaryFunction1& f1,
                const BinaryFunction2& f2, const std::string& user_code, std::random_access_iterator_tag )
            {
                typedef std::iterator_traits<InputIterator>::value_type iType;
                size_t sz = (last1 - first1);
                if (sz == 0)
                    return -1;

                // Use host pointers memory since these arrays are only read once - no benefit to copying.

                // Map the input iterator to a device_vector
                device_vector< iType > dvInput( first1, last1, CL_MEM_USE_HOST_PTR | CL_MEM_READ_ONLY, ctl );
                device_vector< iType > dvInput2( first2, sz, CL_MEM_USE_HOST_PTR|CL_MEM_READ_ONLY, true, ctl );

                // Map the output iterator to a device_vector
                //Make the fourth argument as true; We will use it as a temporary buffer for copying the transform result to reduce
                //device_vector< iType > dvOutput( result, sz, CL_MEM_USE_HOST_PTR|CL_MEM_READ_WRITE, false, ctl );

                return inner_product_enqueue( ctl, dvInput.begin( ), dvInput.end( ), dvInput2.begin( ), init, f1, f2, user_code );

                // This should immediately map/unmap the buffer
                //dvOutput.data( );
            }

            // This template is called by the non-detail versions of inclusive_scan, it already assumes random access iterators
            // This is called strictly for iterators that are derived from device_vector< T >::iterator
            template<typename DVInputIterator, typename OutputType, typename BinaryFunction1, typename BinaryFunction2>
            OutputType inner_product_pick_iterator(bolt::cl::control &ctl,  const DVInputIterator& first1,
                const DVInputIterator& last1, const DVInputIterator& first2, const OutputType& init, const BinaryFunction1& f1,
                const BinaryFunction2& f2, const std::string& user_code, bolt::cl::device_vector_tag )
            {
                return inner_product_enqueue( ctl, first1, last1, first2, init, f1, f2, user_code );
            }

            // This template is called by the non-detail versions of inclusive_scan, it already assumes random access iterators
            // This is called strictly for iterators that are derived from device_vector< T >::iterator
            template<typename DVInputIterator, typename OutputType, typename BinaryFunction1, typename BinaryFunction2>
            OutputType inner_product_pick_iterator(bolt::cl::control &ctl,  const DVInputIterator& first1,
                const DVInputIterator& last1, const DVInputIterator& first2, const OutputType& init, const BinaryFunction1& f1,
                const BinaryFunction2& f2, const std::string& user_code, bolt::cl::fancy_iterator_tag )
            {
                return inner_product_enqueue( ctl, first1, last1, first2, init, f1, f2, user_code );
            }

            template< typename DVInputIterator, typename OutputType, typename BinaryFunction1,typename BinaryFunction2>
            OutputType inner_product_enqueue(bolt::cl::control &ctl, const DVInputIterator& first1,
                const DVInputIterator& last1, const DVInputIterator& first2, const OutputType& init, const BinaryFunction1& f1,
                const BinaryFunction2& f2, const std::string& cl_code)
            {

#if USE_KERNEL
                //NOTE: Kernel version was found to be inefficient. Optimize!!
                static boost::once_flag initOnlyOnce;
                static  ::cl::Kernel masterKernel;

                // For user-defined types, the user must create a TypeName trait which returns the name of the class - note use of TypeName<>::get to retreive the name here.
                boost::call_once( initOnlyOnce, boost::bind( CallCompiler_InnerProduct::constructAndCompile, &masterKernel,
                    "\n//--user Code\n" + cl_code + "\n//---Functions\n" + ClCode<BinaryFunction1>::get() + ClCode<BinaryFunction2>::get(),
                    TypeName<OutputType>::get(), TypeName<BinaryFunction1>::get(), TypeName<BinaryFunction2>::get(), &ctl ) );

                // Set up shape of launch grid and buffers:
                // FIXME, read from device attributes.
                int computeUnits     = ctl.device().getInfo<CL_DEVICE_MAX_COMPUTE_UNITS>();  // round up if we don't know.
                int wgPerComputeUnit =  ctl.wgPerComputeUnit();
                int numWG = computeUnits * wgPerComputeUnit;

                cl_int l_Error = CL_SUCCESS;
                const size_t wgSize  = masterKernel.getWorkGroupInfo< CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE >( ctl.device( ), &l_Error );
                V_OPENCL( l_Error, "Error querying kernel for CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE" );

                // Create Buffer wrappers so we can access the host functors, for read or writing in the kernel
                ALIGNED( 256 ) BinaryFunction1 aligned_binary1( f1 );
                ALIGNED( 256 ) BinaryFunction2 aligned_binary2( f2 );

                ::cl::Buffer userFunctor1(ctl.context(), CL_MEM_USE_HOST_PTR, sizeof(aligned_binary1), &aligned_binary1 );
                ::cl::Buffer userFunctor2(ctl.context(), CL_MEM_USE_HOST_PTR, sizeof(aligned_binary2), &aligned_binary2 );
                ::cl::Buffer result(ctl.context(), CL_MEM_ALLOC_HOST_PTR|CL_MEM_WRITE_ONLY, sizeof(OutputType) * numWG);

                ::cl::Kernel k = masterKernel;  // hopefully create a copy of the kernel. FIXME, doesn't work.

                cl_uint szElements = static_cast< cl_uint >( std::distance( first1, last1 ) );

                /***** This is a temporaray fix - Same as Transform Reduce *****/
                /*What if  requiredWorkGroups > numWG? Do you want to loop or increase the work group size or increase the per item processing?*/
                int requiredWorkGroups = (int)ceil((float)szElements/wgSize);
                if (requiredWorkGroups < numWG)
                    numWG = requiredWorkGroups;
                /**********************/

                V_OPENCL( k.setArg(0, first1.getBuffer( ) ), "Error setting kernel argument" );
                V_OPENCL( k.setArg(1, first2.getBuffer( ) ), "Error setting kernel argument" );
                V_OPENCL( k.setArg(2, szElements), "Error setting kernel argument" );
                V_OPENCL( k.setArg(3, userFunctor1), "Error setting kernel argument" );
                V_OPENCL( k.setArg(4, userFunctor2), "Error setting kernel argument" );
                V_OPENCL( k.setArg(5, result), "Error setting kernel argument" );

                ::cl::LocalSpaceArg loc;
                loc.size_ = wgSize*sizeof(OutputType);
                V_OPENCL( k.setArg(6, loc), "Error setting kernel argument" );

                l_Error = ctl.commandQueue().enqueueNDRangeKernel(
                    k,
                    ::cl::NullRange,
                    ::cl::NDRange(numWG * wgSize),
                    ::cl::NDRange(wgSize) );
                V_OPENCL( l_Error, "enqueueNDRangeKernel() failed for inner_product() kernel" );

                ::cl::Event l_mapEvent;
                OutputType *h_result = (OutputType*)ctl.commandQueue().enqueueMapBuffer(result, false, CL_MAP_READ, 0, sizeof(OutputType)*numWG, NULL, &l_mapEvent, &l_Error );
                V_OPENCL( l_Error, "Error calling map on the result buffer" );

                //  Finish the tail end of the reduction on host side; the compute device reduces within the workgroups, with one result per workgroup
                size_t ceilNumWG = static_cast< size_t >( std::ceil( static_cast< float >( szElements ) / wgSize) );
                bolt::cl::minimum< size_t >  min_size_t;
                size_t numTailReduce = min_size_t( ceilNumWG, numWG );

                bolt::cl::wait(ctl, l_mapEvent);

                OutputType acc = static_cast< OutputType >( init );
                for(int i = 0; i < numTailReduce; ++i)
                {
                    acc = f1( acc, h_result[ i ] );
                }

                return acc;

#endif

                //Should we directly call transform and reduce routines or launch a separate kernel?
                typedef std::iterator_traits<DVInputIterator>::value_type iType;
                ::cl::Event innerproductEvent;

                cl_uint distVec = static_cast< cl_uint >( std::distance( first1, last1 ) );
                if( distVec == 0 )
                    return -1;

                device_vector< iType > tempDV( distVec, 0, CL_MEM_READ_WRITE, false, ctl );
                detail::transform_enqueue( ctl, first1, last1, first2, tempDV.begin() ,f2,cl_code);
                return detail::reduce_enqueue( ctl, tempDV.begin(), tempDV.end(), init, f1, cl_code);
                bolt::cl::wait(ctl, innerproductEvent);

            };

        }//End OF detail namespace
    }//End OF cl namespace
}//End OF bolt namespace

#endif


//
// Created by Alex Beccaro on 18/01/18.
//

#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include "../../src/problems/51-100/63/problem63.hpp"

BOOST_AUTO_TEST_SUITE( Problem63 )

    BOOST_AUTO_TEST_CASE( Solution ) {
        auto res = problems::problem63::solve();
        BOOST_CHECK_EQUAL(res, 49);
    }

BOOST_AUTO_TEST_SUITE_END()


//==============================================================================
//         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 NT2_LINALG_DETAILS_BLAS_MV_HPP_INCLUDED
#define NT2_LINALG_DETAILS_BLAS_MV_HPP_INCLUDED

#include <complex>
#include <boost/preprocessor/cat.hpp>
#include <nt2/linalg/details/blas/blas2.hpp>
#include <nt2/linalg/details/utility/f77_wrapper.hpp>

namespace nt2 { namespace details
{
#define NT2_MV(T, PX)                                                   \
inline void gemv( const char *ta                                        \
                , const nt2_la_int *m, const nt2_la_int *n              \
                , const T *al                                           \
                , const T *a, const nt2_la_int *lda                     \
                , const T *x, const nt2_la_int *incx                    \
                , const T *be                                           \
                , T *y      , const nt2_la_int *incy                    \
                )                                                       \
{                                                                       \
  NT2_F77NAME(BOOST_PP_CAT(PX,gemv))(ta,m,n,al,a,lda,x,incx,be,y,incy); \
}                                                                       \
/**/

  // INTERNAL ONLY
  // gemv binding for all types
  // _GEMV (TRANS, M, N, ALPHA, A, LDA, X, INCX, BETA,  Y, INCY )
  NT2_MV(double, d)
  NT2_MV(float,  s)
  NT2_MV(std::complex<double>, z)
  NT2_MV(std::complex<float>, c)

#undef NT2_MV

#define NT2_MV(T, PX)                                                         \
inline void gbmv( const char *ta                                              \
                , const nt2_la_int *m, const nt2_la_int *n                    \
                , const nt2_la_int *kl, const nt2_la_int *ku                  \
                , const T *al                                                 \
                , const T *a, const nt2_la_int *lda                           \
                , const T *x, const nt2_la_int *incx                          \
                , const T *be                                                 \
                , T *y      , const nt2_la_int *incy                          \
                )                                                             \
{                                                                             \
  NT2_F77NAME(BOOST_PP_CAT(PX,gbmv))(ta,m,n,kl,ku,al,a,lda,x,incx,be,y,incy); \
}                                                                             \
/**/

  // INTERNAL ONLY
  // gbmv binding for all types
  // _GBMV(TRANS, M, N, KL, KU, ALPHA, A, LDA, X, INCX, BETA, Y, INCY )
  NT2_MV(double, d)
  NT2_MV(float,  s)
  NT2_MV(std::complex<double>, z)
  NT2_MV(std::complex<float>, c)

#undef NT2_MV

#define NT2_MV(T, P, Q)                                                             \
inline void BOOST_PP_CAT(Q,mv)( const char *uplo, const nt2_la_int *n               \
                              , const T *al                                         \
                              , const T *a, const nt2_la_int *lda                   \
                              , const T *x, const nt2_la_int *incx                  \
                              , const T *be                                         \
                              , T *y      , const nt2_la_int *incy                  \
                              )                                                     \
{                                                                                \
  NT2_F77NAME(BOOST_PP_CAT(P,BOOST_PP_CAT(Q,mv)))(uplo,n,al,a,lda,x,incx,be,y,incy);  \
}                                                                                \
/**/

  //  INTERNAL ONLY
  // *mv binging for hermittian/symmetric cases
  // _HEMV ( UPLO, N, ALPHA, A, LDA, X, INCX, BETA,  Y, INCY )
  // _SYMV ( UPLO, N, ALPHA, A, LDA, X, INCX, BETA,  Y, INCY )
  NT2_MV(std::complex<double>, z, he)
  NT2_MV(std::complex<float>, c, he)
  NT2_MV(double, d, sy)
  NT2_MV(float,  s, sy)

#undef NT2_MV

#define NT2_MV(T, P, Q)                                                               \
inline void BOOST_PP_CAT(Q,mv)( const char *uplo                                      \
                              , const nt2_la_int *n, const nt2_la_int *k              \
                              , const T *al                                           \
                              , const T *a, const nt2_la_int *lda                     \
                              , const T *x, const nt2_la_int *incx                    \
                              , const T *be                                           \
                              , T *y      , const nt2_la_int *incy                    \
                              )                                                       \
{                                                                                \
  NT2_F77NAME(BOOST_PP_CAT(P,BOOST_PP_CAT(Q,mv)))(uplo,n,k,al,a,lda,x,incx,be,y,incy); \
}                                                                                \
/**/

  // _HBMV ( UPLO, N, K, ALPHA, A, LDA, X, INCX, BETA,  Y, INCY )
  // _SBMV ( UPLO, N, K, ALPHA, A, LDA, X, INCX, BETA,  Y, INCY )
  NT2_MV(std::complex<double>, z, hb)
  NT2_MV(std::complex<float>, c, hb)
  NT2_MV(double, d, sb)
  NT2_MV(float,  s, sb)

#undef NT2_MV

#define NT2_MV(T, P, Q)                                                         \
inline void BOOST_PP_CAT(Q,mv)( const char *uplo, const nt2_la_int *n           \
                              , const T *al , const T *ap                       \
                              , const T *x  , const nt2_la_int *incx            \
                              , const T *be                                     \
                              , T *y        , const nt2_la_int *incy            \
                              )                                                 \
{                                                                               \
  NT2_F77NAME(BOOST_PP_CAT(P,BOOST_PP_CAT(Q,mv)))(uplo,n,al,ap,x,incx,be,y,incy); \
}                                                                               \
/**/

  // _HPMV ( UPLO N, ALPHA, AP, X, INCX, BETA,  Y, INCY )
  // _SPMV ( UPLO N, ALPHA, AP, X, INCX, BETA,  Y, INCY )
  NT2_MV(std::complex<double> , z, hp )
  NT2_MV(std::complex<float>  , c, hp )
  NT2_MV(double, d, sp)
  NT2_MV(float,  s, sp)

#undef NT2_MV

#define NT2_MV(T, P)                                                    \
inline void tpmv( const char *uplo, const char *trans, const char *diag \
                , const nt2_la_int *n                                   \
                , const T *ap                                           \
                , T *x, const nt2_la_int *incx                          \
                )                                                       \
{                                                                       \
  NT2_F77NAME(BOOST_PP_CAT(P,tpmv))(uplo,trans,diag,n,ap,x,incx);       \
}                                                                       \
/**/

  // _TPMV ( UPLO, TRANS, DIAG, N, A, LDA, X, INCX )
  NT2_MV(double, d)
  NT2_MV(float,  s)
  NT2_MV(std::complex<double> , z )
  NT2_MV(std::complex<float>  , c )

#undef NT2_MV

#define NT2_MV(T, P)                                                    \
inline void trmv( const char *uplo, const char *trans, const char *diag \
                , const nt2_la_int *n                                   \
                , const T *a, const nt2_la_int *lda                     \
                , T *x      , const nt2_la_int *incx                    \
                )                                                       \
{                                                                       \
  NT2_F77NAME(BOOST_PP_CAT(P,trmv))(uplo,trans,diag,n,a,lda,x,incx);    \
}                                                                       \
/**/

  // _TRMV ( UPLO, TRANS, DIAG, N, AP, X, INCX )
  NT2_MV(double, d)
  NT2_MV(float,  s)
  NT2_MV(std::complex<double> , z )
  NT2_MV(std::complex<float>  , c )
#undef NT2_MV

#define NT2_MV(T, P)                                                    \
inline void tbmv( const char *uplo, const char *trans, const char *diag \
                , const nt2_la_int *n,  const nt2_la_int *k             \
                , const T *a, const nt2_la_int *lda                     \
                , T *x      , const nt2_la_int *incx                    \
                )                                                       \
{                                                                       \
  NT2_F77NAME(BOOST_PP_CAT(P,tbmv))(uplo,trans,diag,n,k, a,lda,x,incx); \
}                                                                       \
/**/

  // _TBMV ( UPLO, TRANS, DIAG, N, K, A, LDA, X, INCX )
  NT2_MV(double, d)
  NT2_MV(float,  s)
  NT2_MV(std::complex<double>, z)
  NT2_MV(std::complex<float>, c)

#undef NT2_MV
} }

#endif


/*=========================================================================

  Library   : Image Registration Toolkit (IRTK)
  Module    : $Id$
  Copyright : Imperial College, Department of Computing
              Visual Information Processing (VIP), 2008 onwards
  Date      : $Date$
  Version   : $Revision$
  Changes   : $Author$

Copyright (c) 1999-2014 and onwards, Imperial College London
All rights reserved.
See LICENSE for details

=========================================================================*/

#include <irtkImage.h>

#include <irtkNoise.h>

#include <boost/random.hpp>
#include <boost/random/uniform_int.hpp>

template <class VoxelType> irtkUniformNoise<VoxelType>::irtkUniformNoise(double Amplitude) : irtkNoise<VoxelType>(Amplitude)
{
  boost::mt19937 rng;
  rng.seed(this->_Init);

  boost::uniform_int<> ud(0, 1);
  boost::variate_generator<boost::mt19937&,
			   boost::uniform_int<> > uni_engine(rng, ud);
  (void) uni_engine();
}

template <class VoxelType> const char *irtkUniformNoise<VoxelType>::NameOfClass()
{
  return "irtkUniformNoise";
}

template <class VoxelType> double irtkUniformNoise<VoxelType>::Run(int x, int y, int z, int t)
{
  boost::mt19937 rng;
  rng.seed(this->_Init);

  boost::uniform_int<> ud(0, this->_Amplitude);
  boost::variate_generator<boost::mt19937&,
			   boost::uniform_int<> > uni_engine(rng, ud);
  return double(this->_input->Get(x, y, z, t)) + uni_engine();
}

template class irtkUniformNoise<irtkBytePixel>;
template class irtkUniformNoise<irtkGreyPixel>;
template class irtkUniformNoise<irtkRealPixel>;


#include "client.hpp"
#include "routing.hpp"
#include "stats.hpp"
#include "utils.hpp"

#include <boost/range.hpp>

#include <cmath>
#include <iostream>
#include <sstream>

using namespace std;

// The singleton of the class.  The compiler initializes it to null.
stats *stats::singleton;

stats::stats(const cli_args &args, const traffic &tra):
  m_args(args), m_tra(tra),
  m_dt((args.sim_time - args.kickoff) / nom)
{
  assert(!singleton);
  singleton = this;

  // We start collecting the utilization stats at the kickoff time.
  schedule(args.kickoff);
}

template <typename T>
void
report(const string &txt, const T &v)
{
  cout << txt << " " << v << endl;
}

stats::~stats()
{
  // The population name.
  report("population", m_args.population);

  // The network utilization.
  report("utilization", ba::mean(m_utilization));

  // The probability of establishing a connection.
  report("pec", ba::mean(m_pec));
  // The mean length of an established connection.
  report("lenec", ba::mean(m_lenec));
  // The mean number of links of an established connection.
  report("nolec", ba::mean(m_nolec));
  // The mean number of contiguous units of an established connection.
  report("ncuec", ba::mean(m_ncuec));

  // The algorithm statistics.
  for (const auto &e: m_costs)
    {
      // The routing type.
      auto rt = e.first;
      const string prefix = routing::to_string(rt) + '_';
      report(prefix + "mean_costs", ba::mean(m_costs[rt]));
      report(prefix + "max_costs", ba::max(m_costs[rt]));
      report(prefix + "mean_edges", ba::mean(m_edges[rt]));
      report(prefix + "max_edges", ba::max(m_edges[rt]));
      report(prefix + "mean_units", ba::mean(m_units[rt]));
      report(prefix + "max_units", ba::max(m_units[rt]));
    }

  // The number of currently active connections.
  report("conns", ba::mean(m_conns));
  // The capacity served.
  report("capser", ba::mean(m_capser));
  // The mean number of fragments on links.
  report("frags", ba::mean(m_frags));
}

stats &
stats::get()
{
  return *singleton;
}

void
stats::operator()(const double st)
{
  // The current network utilization.
  m_utilization(calculate_utilization(m_mdl));
  // The number of connections served.
  m_conns(m_tra.nr_clients());
  // The capacity served.
  m_capser(m_tra.capacity_served());
  // The number of fragments.
  m_frags(calculate_frags());

  schedule(st);
}

// Schedule the next event based on the current time 0.
void
stats::schedule(const double t)
{
  // We call the stats every second.
  module::schedule(t + m_dt);
}

void
stats::established(const bool status)
{
  if (m_args.kickoff <= now())
    m_pec(status);
}

void
stats::established_conn(const connection &conn)
{
  if (m_args.kickoff <= now())
    {
      int len = conn.get_len();
      int nol = conn.get_nol();
      int ncu = conn.get_ncu();

      m_lenec(len);
      m_nolec(nol);
      m_ncuec(ncu);
    }
}

void
stats::algo_perf(const routing::rt_t rt, const double dt,
                 const int costs, const int edges, const int units)
{
  if (m_args.kickoff <= now())
    {
      cerr << routing::to_string(rt) << " " << dt << endl;
      m_costs[rt](costs);
      m_edges[rt](edges);
      m_units[rt](units);
    }
}

double
stats::calculate_frags()
{
  dbl_acc frags;

  // Iterate over all edges.
  graph::edge_iterator ei, ee;
  for (tie(ei, ee) = boost::edges(m_mdl); ei != ee; ++ei)
    {
      const edge e = *ei;
      const SU &su = boost::get(boost::edge_su, m_mdl, e);
      int f = su.size();
      frags(f);
    }

  return ba::mean(frags);
}


//------------------------------------------------------------------------------
// \file Category_tests.cpp
//------------------------------------------------------------------------------
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(Categories)
BOOST_AUTO_TEST_SUITE(Category_tests)

// cf. Mac Lane Saunders (1978), pp. 11 Categories

template <typename T>
using TypeAsObject = T;

// Obj(A) for category A
template <typename T>
using TypeAsObjects = T;

// TODO: provide class implementation for classes and structs.
//template <typename TypeAsObjects>
//class Object
//{
//  Object

//};

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(NonegativeIntegersAsCategoryObject)
{
  using UnsignedIntObject = TypeAsObject<unsigned int>;

  const UnsignedIntObject a {42};

  BOOST_TEST(a == 42);
}

//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
BOOST_AUTO_TEST_CASE(NonegativeIntegersAsCategoryObjects)
{
  using UnsignedIntObjects = TypeAsObjects<unsigned int>;

  const UnsignedIntObjects a {42};

  BOOST_TEST(a == 42);
}

BOOST_AUTO_TEST_SUITE_END() // Category_tests
BOOST_AUTO_TEST_SUITE_END() // Categories


// Copyright (C) 2011 by the BEM++ Authors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#include "assembly/helmholtz_3d_operators_common.hpp"
#include "fiber/geometrical_data.hpp"
#include "fiber/modified_helmholtz_3d_adjoint_double_layer_potential_kernel_functor.hpp"
#include "fiber/modified_helmholtz_3d_adjoint_double_layer_potential_kernel_interpolated_functor.hpp"
#include "fiber/default_collection_of_kernels.hpp"

#include "../type_template.hpp"
#include "../check_arrays_are_close.hpp"
#include "../random_arrays.hpp"

#include <algorithm>
#include "common/armadillo_fwd.hpp"
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/version.hpp>
#include <complex>

// Tests

BOOST_AUTO_TEST_SUITE(ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelInterpolatedFunctor)

BOOST_AUTO_TEST_CASE_TEMPLATE(agrees_with_noninterpolated_for_real_wave_number,
                              ValueType, kernel_types)
{
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelFunctor<ValueType>
            NoninterpolatedFunctor;
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelInterpolatedFunctor<ValueType>
            InterpolatedFunctor;
    typedef Fiber::DefaultCollectionOfKernels<NoninterpolatedFunctor>
            NoninterpolatedKernels;
    typedef Fiber::DefaultCollectionOfKernels<InterpolatedFunctor>
            InterpolatedKernels;
    typedef typename Fiber::ScalarTraits<ValueType>::RealType CoordinateType;
    const ValueType waveNumber = 1;
    const CoordinateType wavelength = 2. * M_PI / std::abs(waveNumber);
    const double maxDist = 20.;
    const int interpPtsPerWavelength = Bempp::DEFAULT_HELMHOLTZ_INTERPOLATION_DENSITY;
    NoninterpolatedKernels noninterpKernels((NoninterpolatedFunctor(waveNumber)));
    InterpolatedKernels interpKernels((InterpolatedFunctor(waveNumber, maxDist,
                                                          interpPtsPerWavelength)));

    Fiber::GeometricalData<CoordinateType> testGeomData, trialGeomData;
    const int worldDim = 3;
    const int testPointCount = 3, trialPointCount = 50;
    testGeomData.globals.set_size(worldDim, testPointCount);
    testGeomData.globals.fill(0.);
    testGeomData.globals(0, 0) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(1, 1) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(2, 2) = wavelength / (2 * interpPtsPerWavelength);

    trialGeomData.globals = 0.5 * maxDist *
            generateRandomMatrix<CoordinateType>(worldDim, trialPointCount);
    trialGeomData.globals.col(0).fill(0.);
    trialGeomData.globals.cols(1, 10) *= 0.01; // to test well the area near origin
    trialGeomData.globals.cols(11, 20) *= 0.1;

    testGeomData.normals.set_size(worldDim, testPointCount);
    testGeomData.normals.fill(0.);
    testGeomData.normals.row(0).fill(0.5);
    testGeomData.normals.row(1).fill(0.5);
    testGeomData.normals.row(2).fill(1. / sqrt(2.)); // just to make them nonzero and nonequal

    Fiber::CollectionOf4dArrays<ValueType> noninterpResult, interpResult;
    noninterpKernels.evaluateOnGrid(testGeomData, trialGeomData, noninterpResult);
    interpKernels.evaluateOnGrid(testGeomData, trialGeomData, interpResult);

    CoordinateType tol = 50 * std::numeric_limits<CoordinateType>::epsilon();
    BOOST_CHECK(check_arrays_are_close<ValueType>(interpResult[0],
                                                  noninterpResult[0], tol));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(agrees_with_noninterpolated_for_negative_real_wave_number,
                              ValueType, kernel_types)
{
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelFunctor<ValueType>
            NoninterpolatedFunctor;
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelInterpolatedFunctor<ValueType>
            InterpolatedFunctor;
    typedef Fiber::DefaultCollectionOfKernels<NoninterpolatedFunctor>
            NoninterpolatedKernels;
    typedef Fiber::DefaultCollectionOfKernels<InterpolatedFunctor>
            InterpolatedKernels;
    typedef typename Fiber::ScalarTraits<ValueType>::RealType CoordinateType;
    const ValueType waveNumber = -1;
    const CoordinateType wavelength = 2. * M_PI / std::abs(waveNumber);
    const double maxDist = 20.;
    const int interpPtsPerWavelength = Bempp::DEFAULT_HELMHOLTZ_INTERPOLATION_DENSITY;
    NoninterpolatedKernels noninterpKernels((NoninterpolatedFunctor(waveNumber)));
    InterpolatedKernels interpKernels((InterpolatedFunctor(waveNumber, maxDist,
                                                          interpPtsPerWavelength)));

    Fiber::GeometricalData<CoordinateType> testGeomData, trialGeomData;
    const int worldDim = 3;
    const int testPointCount = 3, trialPointCount = 50;
    testGeomData.globals.set_size(worldDim, testPointCount);
    testGeomData.globals.fill(0.);
    testGeomData.globals(0, 0) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(1, 1) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(2, 2) = wavelength / (2 * interpPtsPerWavelength);

    trialGeomData.globals = 0.5 * maxDist *
            generateRandomMatrix<CoordinateType>(worldDim, trialPointCount);
    trialGeomData.globals.col(0).fill(0.);
    trialGeomData.globals.cols(1, 10) *= 0.01; // to test well the area near origin
    trialGeomData.globals.cols(11, 20) *= 0.1;

    testGeomData.normals.set_size(worldDim, testPointCount);
    testGeomData.normals.fill(0.);
    testGeomData.normals.row(0).fill(0.5);
    testGeomData.normals.row(1).fill(0.5);
    testGeomData.normals.row(2).fill(1. / sqrt(2.)); // just to make them nonzero and nonequal

    Fiber::CollectionOf4dArrays<ValueType> noninterpResult, interpResult;
    noninterpKernels.evaluateOnGrid(testGeomData, trialGeomData, noninterpResult);
    interpKernels.evaluateOnGrid(testGeomData, trialGeomData, interpResult);

    CoordinateType tol = 50 * std::numeric_limits<CoordinateType>::epsilon();
    BOOST_CHECK(check_arrays_are_close<ValueType>(interpResult[0],
                                                  noninterpResult[0], tol));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(agrees_with_noninterpolated_for_negative_imag_wave_number,
                              ValueType, complex_kernel_types)
{
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelFunctor<ValueType>
            NoninterpolatedFunctor;
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelInterpolatedFunctor<ValueType>
            InterpolatedFunctor;
    typedef Fiber::DefaultCollectionOfKernels<NoninterpolatedFunctor>
            NoninterpolatedKernels;
    typedef Fiber::DefaultCollectionOfKernels<InterpolatedFunctor>
            InterpolatedKernels;
    typedef typename Fiber::ScalarTraits<ValueType>::RealType CoordinateType;
    const ValueType waveNumber(0., -1.);
    const CoordinateType wavelength = 2. * M_PI / std::abs(waveNumber);
    const double maxDist = 20.;
    const int interpPtsPerWavelength = Bempp::DEFAULT_HELMHOLTZ_INTERPOLATION_DENSITY;
    NoninterpolatedKernels noninterpKernels((NoninterpolatedFunctor(waveNumber)));
    InterpolatedKernels interpKernels((InterpolatedFunctor(waveNumber, maxDist,
                                                          interpPtsPerWavelength)));

    Fiber::GeometricalData<CoordinateType> testGeomData, trialGeomData;
    const int worldDim = 3;
    const int testPointCount = 3, trialPointCount = 30;
    testGeomData.globals.set_size(worldDim, testPointCount);
    testGeomData.globals.fill(0.);
    testGeomData.globals(0, 0) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(1, 1) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(2, 2) = wavelength / (2 * interpPtsPerWavelength);

    trialGeomData.globals = 0.5 * maxDist *
            generateRandomMatrix<CoordinateType>(worldDim, trialPointCount);
    trialGeomData.globals.col(0).fill(0.);
    trialGeomData.globals.cols(1, 10) *= 0.01; // to test well the area near origin
    trialGeomData.globals.cols(11, 20) *= 0.1;

    testGeomData.normals.set_size(worldDim, testPointCount);
    testGeomData.normals.fill(0.);
    testGeomData.normals.row(0).fill(0.5);
    testGeomData.normals.row(1).fill(0.5);
    testGeomData.normals.row(2).fill(1. / sqrt(2.)); // just to make them nonzero and nonequal

    Fiber::CollectionOf4dArrays<ValueType> noninterpResult, interpResult;
    noninterpKernels.evaluateOnGrid(testGeomData, trialGeomData, noninterpResult);
    interpKernels.evaluateOnGrid(testGeomData, trialGeomData, interpResult);

    CoordinateType tol = 50 * std::numeric_limits<CoordinateType>::epsilon();
    BOOST_CHECK(check_arrays_are_close<ValueType>(interpResult[0],
                                                  noninterpResult[0], tol));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(agrees_with_noninterpolated_for_imag_wave_number,
                              ValueType, complex_kernel_types)
{
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelFunctor<ValueType>
            NoninterpolatedFunctor;
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelInterpolatedFunctor<ValueType>
            InterpolatedFunctor;
    typedef Fiber::DefaultCollectionOfKernels<NoninterpolatedFunctor>
            NoninterpolatedKernels;
    typedef Fiber::DefaultCollectionOfKernels<InterpolatedFunctor>
            InterpolatedKernels;
    typedef typename Fiber::ScalarTraits<ValueType>::RealType CoordinateType;
    const ValueType waveNumber(0., 1.);
    const CoordinateType wavelength = 2. * M_PI / std::abs(waveNumber);
    const double maxDist = 20.;
    const int interpPtsPerWavelength = Bempp::DEFAULT_HELMHOLTZ_INTERPOLATION_DENSITY;
    NoninterpolatedKernels noninterpKernels((NoninterpolatedFunctor(waveNumber)));
    InterpolatedKernels interpKernels((InterpolatedFunctor(waveNumber, maxDist,
                                                          interpPtsPerWavelength)));

    Fiber::GeometricalData<CoordinateType> testGeomData, trialGeomData;
    const int worldDim = 3;
    const int testPointCount = 3, trialPointCount = 30;
    testGeomData.globals.set_size(worldDim, testPointCount);
    testGeomData.globals.fill(0.);
    testGeomData.globals(0, 0) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(1, 1) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(2, 2) = wavelength / (2 * interpPtsPerWavelength);

    trialGeomData.globals = 0.5 * maxDist *
            generateRandomMatrix<CoordinateType>(worldDim, trialPointCount);
    trialGeomData.globals.col(0).fill(0.);
    trialGeomData.globals.cols(1, 10) *= 0.01; // to test well the area near origin
    trialGeomData.globals.cols(11, 20) *= 0.1;

    testGeomData.normals.set_size(worldDim, testPointCount);
    testGeomData.normals.fill(0.);
    testGeomData.normals.row(0).fill(0.5);
    testGeomData.normals.row(1).fill(0.5);
    testGeomData.normals.row(2).fill(1. / sqrt(2.)); // just to make them nonzero and nonequal

    Fiber::CollectionOf4dArrays<ValueType> noninterpResult, interpResult;
    noninterpKernels.evaluateOnGrid(testGeomData, trialGeomData, noninterpResult);
    interpKernels.evaluateOnGrid(testGeomData, trialGeomData, interpResult);

    CoordinateType tol = 50 * std::numeric_limits<CoordinateType>::epsilon();
    BOOST_CHECK(check_arrays_are_close<ValueType>(interpResult[0],
                                                  noninterpResult[0], tol));
}

BOOST_AUTO_TEST_CASE_TEMPLATE(agrees_with_noninterpolated_for_complex_wave_number,
                              ValueType, complex_kernel_types)
{
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelFunctor<ValueType>
            NoninterpolatedFunctor;
    typedef Fiber::ModifiedHelmholtz3dAdjointDoubleLayerPotentialKernelInterpolatedFunctor<ValueType>
            InterpolatedFunctor;
    typedef Fiber::DefaultCollectionOfKernels<NoninterpolatedFunctor>
            NoninterpolatedKernels;
    typedef Fiber::DefaultCollectionOfKernels<InterpolatedFunctor>
            InterpolatedKernels;
    typedef typename Fiber::ScalarTraits<ValueType>::RealType CoordinateType;
    const ValueType waveNumber(1., 1.);
    const CoordinateType wavelength = 2. * M_PI / std::abs(waveNumber);
    const double maxDist = 20.;
    const int interpPtsPerWavelength = Bempp::DEFAULT_HELMHOLTZ_INTERPOLATION_DENSITY;
    NoninterpolatedKernels noninterpKernels((NoninterpolatedFunctor(waveNumber)));
    InterpolatedKernels interpKernels((InterpolatedFunctor(waveNumber, maxDist,
                                                          interpPtsPerWavelength)));

    Fiber::GeometricalData<CoordinateType> testGeomData, trialGeomData;
    const int worldDim = 3;
    const int testPointCount = 3, trialPointCount = 30;
    testGeomData.globals.set_size(worldDim, testPointCount);
    testGeomData.globals.fill(0.);
    testGeomData.globals(0, 0) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(1, 1) = wavelength / (2 * interpPtsPerWavelength);
    testGeomData.globals(2, 2) = wavelength / (2 * interpPtsPerWavelength);

    trialGeomData.globals = 0.5 * maxDist *
            generateRandomMatrix<CoordinateType>(worldDim, trialPointCount);
    trialGeomData.globals.col(0).fill(0.);
    trialGeomData.globals.cols(1, 10) *= 0.01; // to test well the area near origin
    trialGeomData.globals.cols(11, 20) *= 0.1;

    testGeomData.normals.set_size(worldDim, testPointCount);
    testGeomData.normals.fill(0.);
    testGeomData.normals.row(0).fill(0.5);
    testGeomData.normals.row(1).fill(0.5);
    testGeomData.normals.row(2).fill(1. / sqrt(2.)); // just to make them nonzero and nonequal

    Fiber::CollectionOf4dArrays<ValueType> noninterpResult, interpResult;
    noninterpKernels.evaluateOnGrid(testGeomData, trialGeomData, noninterpResult);
    interpKernels.evaluateOnGrid(testGeomData, trialGeomData, interpResult);

    CoordinateType tol = 50 * std::numeric_limits<CoordinateType>::epsilon();
    BOOST_CHECK(check_arrays_are_close<ValueType>(interpResult[0],
                                                  noninterpResult[0], tol));
}

BOOST_AUTO_TEST_SUITE_END()


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2019 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020 Nikita Kaskov <nbering@nil.foundation>
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_DETAIL_ENDIAN_SHIFT_HPP
#define CRYPTO3_DETAIL_ENDIAN_SHIFT_HPP

#include <boost/assert.hpp>

#include <boost/crypto3/detail/stream_endian.hpp>
#include <boost/crypto3/detail/basic_functions.hpp>
#include <boost/crypto3/detail/unbounded_shift.hpp>

namespace boost {
    namespace crypto3 {
        namespace detail {

            template<typename Endianness, std::size_t WordBits>
            struct endian_shift;

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::big_unit_big_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {
                    // shift to most significant bits according to endianness
                    w = unbounded_shl(w, shift);
                    return w;
                }
            };

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::little_unit_big_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {
                    // shift to most significant bits according to endianness
                    std::size_t shift_rem = shift % UnitBits;
                    std::size_t shift_unit_bits = shift - shift_rem;

                    std::size_t sz[2] = {UnitBits - shift_rem, shift_rem};
                    word_type masks[2];
                    masks[0] = unbounded_shl(low_bits<word_bits>(~word_type(), sz[0]), shift_unit_bits);
                    masks[1] =
                        unbounded_shl(low_bits<word_bits>(~word_type(), sz[1]), shift_unit_bits + UnitBits + sz[0]);
                    std::size_t bits_left = word_bits - shift;

                    word_type w_combined = 0;
                    int ind = 0;

                    while (bits_left) {
                        w_combined |= (!ind ? unbounded_shl(w & masks[0], shift_rem) :
                                              unbounded_shr(w & masks[1], UnitBits + sz[0]));
                        bits_left -= sz[ind];
                        masks[ind] = unbounded_shl(masks[ind], UnitBits);
                        ind = 1 - ind;
                    }

                    w = unbounded_shr(w_combined, shift_unit_bits);
                    return w;
                }
            };

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::big_unit_little_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {
                    // shift to most significant bits according to endianness
                    std::size_t shift_rem = shift % UnitBits;
                    std::size_t shift_unit_bits = shift - shift_rem;

                    std::size_t sz[2] = {UnitBits - shift_rem, shift_rem};
                    word_type masks[2] = {
                        unbounded_shr(high_bits<word_bits>(~word_type(), sz[0]), shift_unit_bits),
                        unbounded_shr(high_bits<word_bits>(~word_type(), sz[1]), shift_unit_bits + UnitBits + sz[0])};

                    std::size_t bits_left = word_bits - shift;
                    word_type w_combined = 0;
                    int ind = 0;

                    while (bits_left) {
                        w_combined |= (!ind ? unbounded_shr(w & masks[0], shift_rem) :
                                              unbounded_shl(w & masks[1], UnitBits + sz[0]));
                        bits_left -= sz[ind];
                        masks[ind] = unbounded_shr(masks[ind], UnitBits);
                        ind = 1 - ind;
                    }

                    w = unbounded_shl(w_combined, shift_unit_bits);
                    return w;
                }
            };

            template<int UnitBits, std::size_t WordBits>
            struct endian_shift<stream_endian::little_unit_little_bit<UnitBits>, WordBits>
                : public basic_functions<WordBits> {

                constexpr static const std::size_t word_bits = basic_functions<WordBits>::word_bits;
                typedef typename basic_functions<WordBits>::word_type word_type;

                static word_type &to_msb(word_type &w, std::size_t shift) {

                    // shift to most significant bits according to endianness
                    w = unbounded_shr(w, shift);
                    return w;
                }
            };
        }    // namespace detail
    }        // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_DETAIL_ENDIAN_SHIFT_HPP