#include "server_http.hpp"
#include <iostream>
#include <typeinfo>
#include <string>
#include <utility>
//Models for rtree
// #include "models/Polygon.cpp"
// #include "models/Region.cpp"
#include "models/RTree.h"
// Added for the json-example
#define BOOST_SPIRIT_THREADSAFE
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>

// Added for the default_resource example
#include <algorithm>
#include <boost/filesystem.hpp>
#include <fstream>
#include <vector>
#ifdef HAVE_OPENSSL
#include "crypto.hpp"
#endif

using namespace std;
using namespace boost::property_tree;

typedef int dtype;
typedef Point<dtype> P;

using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;

int main() {
    HttpServer server;
    server.config.port = 8090;
    RTree<dtype> * tree = new RTree<dtype>(5, 2);

    int count = 1;
    //Get | get regions and polygons
    server.resource["^/rtree$"]["GET"] = [&tree](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        stringstream stream;
        SimpleWeb::CaseInsensitiveMultimap header;
        stream << tree->get_json_string();
        response->write_get(stream,header);

    };

    //Get | get graph rtree
    server.resource["^/rtree/graph$"]["GET"] = [&tree](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
        stringstream stream;
        SimpleWeb::CaseInsensitiveMultimap header;
        string json_string = "{\"graph\": [";
        json_string += tree->graphviz();
        json_string += "]}";
        stream << json_string;
        response->write_get(stream,header);
    };

    //Post | add polygons
    server.resource["^/rtree$"]["POST"] = [&tree, &count](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        vector<P> pv;
        vector<dtype> v;
        stringstream stream;
        string json_string = "";
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            ptree pt;
            read_json(request->content, pt);
            for (boost::property_tree::ptree::value_type& rowPair:pt.get_child("polygon")) {
                for (boost::property_tree::ptree::value_type& itemPair : rowPair.second) {
                    int value = itemPair.second.get_value<int>();
                    v.push_back(value);
                }
            }
            for (size_t i = 0; i < v.size(); i += 2) {
                P point(v[i], v[i+1]);
                pv.push_back(point);
            }
            int identifier_polygon = count++;
            tree->insert(new Polygon<dtype>(pv, identifier_polygon));
            json_string = "{\"status\": true}";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Post | nearest search
    server.resource["^/rtree/nearest$"]["POST"] = [&tree](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string = "{\"polygons\": [ ";
        vector<dtype> v;
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            ptree pt;
            read_json(request->content, pt);
            for (boost::property_tree::ptree::value_type& rowPair:pt.get_child("polygon")) {
                for (boost::property_tree::ptree::value_type& itemPair : rowPair.second) {
                    int value = itemPair.second.get_value<int>();
                    v.push_back(value);
                }
            }
            size_t k = pt.get_child("k").get_value<size_t>();
            P point(v[0], v[1]);
            vector <Polygon<dtype> * > array = tree->nearestSearch(point, k);
            if(array.size() != 0)
            {
                for (auto a : array) {
                    json_string += to_string(a->get_id()) + ",";
                }
                json_string.pop_back();
                json_string += "]}";
                // tree->print();
                stream << json_string;
                response->write_get(stream,header);
            }
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Post | range search
    server.resource["^/rtree/range$"]["POST"] = [&tree](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string = "{\"polygons\": [ ";
        vector<dtype> v;
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            ptree pt;
            read_json(request->content, pt);
            for (boost::property_tree::ptree::value_type& rowPair:pt.get_child("polygon")) {
                for (boost::property_tree::ptree::value_type& itemPair : rowPair.second) {
                    int value = itemPair.second.get_value<int>();
                    v.push_back(value);
                }
            }
            P min(v[0],v[1]);
            P max(v[2],v[3]);
            Rectangle<dtype>* r = new Rectangle<dtype>(min,max);
            auto polygons = tree->rangeSearch(r);
            for (auto p : polygons) {
                json_string += to_string(p.get_id()) + ",";
            }
            json_string.pop_back();
            json_string += "]}";
            stream << json_string;
            //use polygonVector for search
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Post | reset rtree
    server.resource["^/rtree/reset$"]["POST"] = [&tree](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string;
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            ptree pt;
            read_json(request->content, pt);
            int m = pt.get_child("m").get_value<int>();
            int M = pt.get_child("M").get_value<int>();
            if (m > M) {
                int tmp = m;
                m = M;
                M = tmp;
            }
            cout << "M: " << M << "m: " << m << endl;
            tree = new RTree<dtype>(M,m);
            json_string = "{'status': true}";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Option range search
    server.resource["^/rtree/range$"]["OPTIONS"] = [](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string = "{}";
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            json_string = "['status': true]";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Option rtree
    server.resource["^/rtree$"]["OPTIONS"] = [](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string = "";
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            json_string = "['status': true]";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Option nearest search
    server.resource["^/rtree/nearest$"]["OPTIONS"] = [](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string = "";
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            json_string = "['status': true]";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Options reset rtree
    server.resource["^/rtree/reset"]["OPTIONS"] = [](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        stringstream stream;
        string json_string;
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            json_string = "{'status': true}";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };

    //Options graph rtree
    server.resource["^/rtree/graph$"]["OPTIONS"] = [](
            shared_ptr<HttpServer::Response> response,
            shared_ptr<HttpServer::Request> request
        ) {
        cout << "method: " << request->path << endl;
        stringstream stream;
        string json_string;
        SimpleWeb::CaseInsensitiveMultimap header;
        try {
            json_string = "['status': true]";
            stream << json_string;
            response->write_get(stream,header);
        } catch (const exception &e) {
            response->write(
                SimpleWeb::StatusCode::client_error_bad_request,
                e.what()
            );
        }
    };
    server.on_error = [](shared_ptr<HttpServer::Request> /*request*/, const SimpleWeb::error_code & /*ec*/) {
        // Handle errors here
        // Note that connection timeouts will also call this handle with ec set to SimpleWeb::errc::operation_canceled
    };

    thread server_thread([&server]() {
        // Start server
        server.start();
    });

    // Wait for server to start so that the client can connect
    this_thread::sleep_for(chrono::seconds(1));
    server_thread.join();
}


#include <boost/sort/spreadsort/spreadsort.hpp>


#include "DeepController.h"
#include <iostream>
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <fstream>
#include <cmath>
#include <random>
//always return 1-dim array
np::ndarray toNumPyArray(const std::vector<float>& val)
{
	int n = val.size();
	p::tuple shape = p::make_tuple(n);
	np::dtype dtype = np::dtype::get_builtin<float>();
	np::ndarray array = np::empty(shape,dtype);

	float* dest = reinterpret_cast<float*>(array.get_data());
	for(int i=0;i<n;i++)
	{
		dest[i] = val[i];
	}

	return array;
}

//always return 1-dim array
np::ndarray toNumPyArray(const std::vector<int>& val)
{
	int n = val.size();
	p::tuple shape = p::make_tuple(n);
	np::dtype dtype = np::dtype::get_builtin<int>();
	np::ndarray array = np::empty(shape,dtype);

	int* dest = reinterpret_cast<int*>(array.get_data());
	for(int i=0;i<n;i++)
	{
		dest[i] = val[i];
	}

	return array;
}

//always return 1-dim array
np::ndarray toNumPyArray(const std::vector<bool>& val)
{
	int n = val.size();
	p::tuple shape = p::make_tuple(n);
	np::dtype dtype = np::dtype::get_builtin<bool>();
	np::ndarray array = np::empty(shape,dtype);

	bool* dest = reinterpret_cast<bool*>(array.get_data());
	for(int i=0;i<n;i++)
	{
		dest[i] = val[i];
	}

	return array;
}

np::ndarray toNumPyArray(const std::vector<Eigen::VectorXd>& val)
{
	int n =val.size();
	int m = val[0].rows();
	p::tuple shape = p::make_tuple(n,m);
	np::dtype dtype = np::dtype::get_builtin<float>();
	np::ndarray array = np::empty(shape,dtype);

	float* dest = reinterpret_cast<float*>(array.get_data());
	int index = 0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			dest[index++] = val[i][j];
		}
	}

	return array;
}

//always return 1-dim array
np::ndarray toNumPyArray(const Eigen::VectorXd& vec)
{
	int n = vec.rows();
	p::tuple shape = p::make_tuple(n);
	np::dtype dtype = np::dtype::get_builtin<float>();
	np::ndarray array = np::empty(shape,dtype);

	float* dest = reinterpret_cast<float*>(array.get_data());
	for(int i =0;i<n;i++)
	{
		dest[i] = vec[i];
	}

	return array;
}
//always return 2-dim array
np::ndarray toNumPyArray(const Eigen::MatrixXd& matrix)
{
	int n = matrix.rows();
	int m = matrix.cols();

	p::tuple shape = p::make_tuple(n,m);
	np::dtype dtype = np::dtype::get_builtin<float>();
	np::ndarray array = np::empty(shape,dtype);

	float* dest = reinterpret_cast<float*>(array.get_data());
	int index = 0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			dest[index++] = matrix(i,j);
		}
	}

	return array;
}
p::dict toNumPyDict(const std::map<std::string,double>& map)
{
	np::dtype dtype = np::dtype::get_builtin<float>();
	p::dict dict;

	for(auto const& iter: map) {
    	dict[iter.first] = iter.second;
    	// std::cout << iter.first << "\t" << iter.second << std::endl;
	}

	return dict;
}
Eigen::VectorXd toEigenVector(const np::ndarray& array,int n)
{
	Eigen::VectorXd vec(n);

	float* srcs = reinterpret_cast<float*>(array.get_data());

	for(int i=0;i<n;i++)
	{
		vec[i] = srcs[i];
	}
	return vec;
}
Eigen::MatrixXd toEigenMatrix(const np::ndarray& array,int n,int m)
{
	Eigen::MatrixXd mat(n,m);

	float* srcs = reinterpret_cast<float*>(array.get_data());

	int index = 0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<m;j++)
		{
			mat(i,j) = srcs[index++];
		}
	}
	return mat;
}

DeepController::
DeepController(std::string name)
{
	std::cout<<"==================================================="<<std::endl;
	std::cout<<"Class to Python is "<<name<<std::endl;
	std::cout<<"Project dir : "<<SOFTCON_DIR<<std::endl;
	mEnvironment = new Environment();
}
np::ndarray
DeepController::
GetStates()
{
	Eigen::VectorXd states;
	states = mEnvironment->GetStates();

	return toNumPyArray(states);
}
void
DeepController::
SetActions(np::ndarray a,int n)
{
	Eigen::VectorXd a_eigen = toEigenVector(a,n);
	mEnvironment->SetActions(a_eigen);
}
void
DeepController::
Step()
{
	mEnvironment->Step();
}
p::dict
DeepController::
GetRewards()
{
	auto reward =mEnvironment->GetRewards();
	return toNumPyDict(reward);
}
void
DeepController::
Reset()
{
	mEnvironment->Reset();
}
bool
DeepController::
isEndOfEpisode()
{
	bool eoe;
	eoe = mEnvironment->isEndOfEpisode();
	return eoe;
}
void
DeepController::
UpdateRandomTargetVelocity()
{
	mEnvironment->UpdateRandomTargetVelocity();
}
np::ndarray
DeepController::
GetTargetVelocity()
{
	Eigen::Vector3d target_velocity = mEnvironment->GetTargetVelocity();
	Eigen::VectorXd target_velocity_eigen(3);
	target_velocity_eigen = target_velocity;

	return toNumPyArray(target_velocity_eigen);
}
np::ndarray
DeepController::
GetNormUpperBound()
{
	Eigen::VectorXd norm_upper_bound = mEnvironment->GetNormUpperBound();
	return toNumPyArray(norm_upper_bound);
}
np::ndarray
DeepController::
GetNormLowerBound()
{
	Eigen::VectorXd norm_lower_bound = mEnvironment->GetNormLowerBound();
	return toNumPyArray(norm_lower_bound);
}
using namespace boost::python;
BOOST_PYTHON_MODULE(deeprl)
{
	Py_Initialize();
	np::initialize();

	class_<DeepController>("Env",init<std::string>())
		.def("GetStates",&DeepController::GetStates)
		.def("SetActions",&DeepController::SetActions)
		.def("Step",&DeepController::Step)
		.def("GetRewards",&DeepController::GetRewards)
		.def("isEndOfEpisode",&DeepController::isEndOfEpisode)
		.def("Reset",&DeepController::Reset)
		.def("GetTimeStep",&DeepController::GetTimeStep)
		.def("GetTargetVelocity",&DeepController::GetTargetVelocity)
		.def("UpdateRandomTargetVelocity",&DeepController::UpdateRandomTargetVelocity)
		.def("GetSimulationHz",&DeepController::GetSimulationHz)
		.def("GetControlHz",&DeepController::GetControlHz)
		.def("GetNormUpperBound",&DeepController::GetNormUpperBound)
		.def("GetNormLowerBound",&DeepController::GetNormLowerBound)
		;
}


#include "formats.hpp"
#include "io.hpp"
#include "types.hpp"

#include <vector>
#include <string>

#include <boost/program_options.hpp>

#include <ctime>
time_t t = clock();

namespace po = boost::program_options;

// Returns true, if command line arguments parse ok
bool parse(int ac, char* av[], po::variables_map& vm);

// Returns true if cube file was strided successfully.
bool calculate(types::String cubeFile,
               types::Uint dir,
               types::Uint index);

int main(int ac, char* av[]) {

    po::variables_map args;
    if (parse(ac, av, args)) {
        types::Uint dir;
        types::String dirLetter = args["dir"].as< types::String >();
        if( dirLetter == "x") dir = 0;
        else if ( dirLetter == "y") dir = 1;
        else if ( dirLetter == "z") dir = 2;

        calculate(args["cubefile"].as< types::String >(),
                  dir,
                  args["index"].as< types::Uint >());
    }

    return 0;
}

bool calculate(types::String cubeFile, types::Uint dir, types::Uint index) {
    using namespace types;

    formats::Cube c = formats::Cube();
    std::cout << "Reading cube file " << cubeFile << "\n";
    t=clock();
    c.readCubeFile(cubeFile);
    std::cout << "Read cube file in " << (clock() -t)/1000.0 << " ms\n";
    String outFile = io::getFileName(cubeFile);

    std::vector<types::Real> plane;
    c.grid.plane(dir, index, plane);
    outFile += ".plane";
    std::cout << "Writing plane with index " << index << " to " << outFile << "\n";
    std::cout << "Plane dimensions " << plane.size() << std::endl;
    c.grid.writeDirPlane(outFile, plane, dir);

    return true;
}

bool parse(int ac, char* av[], po::variables_map& vm) {

    // Declare regular options
    po::options_description desc("Allowed options");
    desc.add_options()
    ("version,v", "print version information")
    ("cubefile", po::value<types::String>(), "Gaussian cube file")
    ("dir", po::value<types::String>(), "=x, y or z")
    ("index", po::value<types::Uint>(), "Index of plane in cube file.")
    ;

    // Register positional options
    po::positional_options_description p;
    p.add("cubefile", 1).add("dir", 1).add("index", 1);

    // Parse
    po::store(po::command_line_parser(ac,av).
              options(desc).positional(p).run(), vm);
    po::notify(vm);

    // Display help message
    if (    !vm.count("cubefile")) {
        std::cout << "Usage: cubeplane [options]\n";
        std::cout << desc << "\n";
    } else if (vm.count("version")) {
        std::cout << "Aug 3rd 2012\n";
    } else if (vm["dir"].as< types::String >() != "x" &&
            vm["dir"].as< types::String >() != "y" &&
            vm["dir"].as< types::String >() != "z") {
        std::cout << "dir must be 'x', 'y' or 'z'";
    } else {
        return true;
    }

    return false;
}


#define BOOST_TEST_MODULE "C++ Unit Tests for metaLBM"
#include <boost/test/included/unit_test.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
namespace tt = boost::test_tools;

#include <iostream>
#include <vector>
template<class T, std::size_t Alignment = 1>
#ifdef ALIGN_MEMORY
using vector = std::vector<T,
                           boost::alignment::aligned_allocator<T, Alignment> >;
#else
using vector = std::vector<T>;
#endif

#define NPROCS 1
#include "input.h"
#include "commons.h"
#include "structure.h"
#include "forcingScheme.h"

using namespace lbm;

BOOST_AUTO_TEST_SUITE(TestForcingScheme)

BOOST_AUTO_TEST_CASE(TestGuo) {
  auto forcingScheme = std::shared_ptr<ForcingScheme<valueType>>(new Guo<valueType>());

  vector<MathVector<valueType, L::dimD>> eqVelocityForcing1_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimD>> eqVelocityForcing2_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimQ>> collisionForcing1_r(sizeX_l()*sizeY_l()*sizeZ_l());
  vector<MathVector<valueType, L::dimQ>> collisionForcing2_r(sizeX_l()*sizeY_l()*sizeZ_l());
  vector<MathVector<valueType, L::dimD>> hydroVelocityForcing1_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimD>> hydroVelocityForcing2_r(L::lX_g *L::lY_g*L::lZ_g);

  for(int idx = 0; idx < L::lX_g *L::lY_g*L::lZ_g; ++idx) {
    valueType density = 0;
    MathVector<valueType, L::dimD> velocity{(valueType) 0};
    for(int iQ = 0; iQ < L::dimQ; ++iQ) {
      forcingScheme->force = MathVector<valueType, L::dimD>{(valueType) 0};
      density = (valueType)(idx+1)/(valueType) (L::lX_g*L::lY_g*L::lZ_g);
      velocity = L::celerity()[iQ];
      collisionForcing1_r[idx][iQ] = forcingScheme->getCollisionForcing(iQ, density,
                                                                        velocity,
                                                                        velocity.norm2());
      forcingScheme->force = MathVector<valueType, L::dimD>{(valueType)(L::weight()[iQ]*(idx+1))/(valueType) (L::lX_g*L::lY_g*L::lZ_g)};
      density = (valueType)(idx+1)/(valueType) (L::lX_g*L::lY_g*L::lZ_g);
      velocity = MathVector<valueType, L::dimD>{(valueType) 0};
      collisionForcing2_r[idx][iQ] = forcingScheme->getCollisionForcing(iQ, density,
                                                                        velocity,
                                                                        velocity.norm2());
    }
    eqVelocityForcing1_r[idx] = forcingScheme->getEqVelocityForcing(density);
    hydroVelocityForcing1_r[idx] = forcingScheme->getHydroVelocityForcing(density);

    forcingScheme->force = MathVector<valueType, L::dimD>{(valueType) 0};
    eqVelocityForcing2_r[idx] = forcingScheme->getEqVelocityForcing(density);
    hydroVelocityForcing2_r[idx] = forcingScheme->getHydroVelocityForcing(density);
  }

  for(int idx = 0; idx < L::lX_g *L::lY_g*L::lZ_g; ++idx) {
    for(int d = 0; d < L::dimD; ++d) {
      BOOST_TEST(hydroVelocityForcing1_r[idx][d] == (valueType) (0.5*L::weight()[L::dimQ-1]),
                 tt::tolerance(1e-15));
      BOOST_TEST(eqVelocityForcing1_r[idx][d] == (valueType) (0.5*L::weight()[L::dimQ-1]),
                 tt::tolerance(1e-15));
      BOOST_TEST(hydroVelocityForcing2_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));
      BOOST_TEST(eqVelocityForcing2_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));
    }
    for(int iQ = 0; iQ < L::dimQ; ++iQ) {
      BOOST_TEST(collisionForcing1_r[idx][iQ] == (valueType) 0,
                 tt::tolerance(1e-15));
      BOOST_TEST(collisionForcing2_r[idx][iQ] == (1.0-beta)*L::weight()[iQ]*L::inv_cs2
                 *(valueType)(L::weight()[iQ]*(idx+1))/(valueType) (L::lX_g*L::lY_g*L::lZ_g)
                 *L::celerity()[iQ].dot(MathVector<valueType, L::dimD>{(valueType) 1}),
                 tt::tolerance(1e-15));
    }
  }
}

BOOST_AUTO_TEST_CASE(TestShanChen) {
  auto forcingScheme = std::shared_ptr<ForcingScheme<valueType>>(new ShanChen<valueType>());

  vector<MathVector<valueType, L::dimD>> eqVelocityForcing1_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimD>> eqVelocityForcing2_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimQ>> collisionForcing1_r(sizeX_l()*sizeY_l()*sizeZ_l());
  vector<MathVector<valueType, L::dimQ>> collisionForcing2_r(sizeX_l()*sizeY_l()*sizeZ_l());
  vector<MathVector<valueType, L::dimD>> hydroVelocityForcing1_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimD>> hydroVelocityForcing2_r(L::lX_g *L::lY_g*L::lZ_g);

  for(int idx = 0; idx < L::lX_g *L::lY_g*L::lZ_g; ++idx) {
    valueType density = 0;
    MathVector<valueType, L::dimD> velocity{(valueType) 0};
    for(int iQ = 0; iQ < L::dimQ; ++iQ) {
      forcingScheme->force = MathVector<valueType, L::dimD>{(valueType) 0};
      density = (valueType)(idx+1)/(valueType) (L::lX_g*L::lY_g*L::lZ_g);
      velocity = L::celerity()[iQ];
      collisionForcing1_r[idx][iQ] = forcingScheme->getCollisionForcing(iQ, density,
                                                                        velocity,
                                                                        velocity.norm2());
      forcingScheme->force = MathVector<valueType, L::dimD>{(valueType)(L::weight()[iQ]*(idx+1))/(valueType) (L::lX_g*L::lY_g*L::lZ_g)};
      density = (valueType)(idx+1)/(valueType) (L::lX_g*L::lY_g*L::lZ_g);
      velocity = MathVector<valueType, L::dimD>{(valueType) 0};
      collisionForcing2_r[idx][iQ] = forcingScheme->getCollisionForcing(iQ, density,
                                                                        velocity,
                                                                        velocity.norm2());
    }
    eqVelocityForcing1_r[idx] = forcingScheme->getEqVelocityForcing(density);
    hydroVelocityForcing1_r[idx] = forcingScheme->getHydroVelocityForcing(density);

    forcingScheme->force = MathVector<valueType, L::dimD>{(valueType) 0};
    eqVelocityForcing2_r[idx] = forcingScheme->getEqVelocityForcing(density);
    hydroVelocityForcing2_r[idx] = forcingScheme->getHydroVelocityForcing(density);
  }

  for(int idx = 0; idx < L::lX_g *L::lY_g*L::lZ_g; ++idx) {
    for(int d = 0; d < L::dimD; ++d) {
      BOOST_TEST(hydroVelocityForcing1_r[idx][d] == (valueType) (0.5*L::weight()[L::dimQ-1]),
                 tt::tolerance(1e-15));
      BOOST_TEST(eqVelocityForcing1_r[idx][d] == (valueType) (tau*L::weight()[L::dimQ-1]),
                 tt::tolerance(1e-15));
       BOOST_TEST(hydroVelocityForcing2_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));
      BOOST_TEST(eqVelocityForcing2_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));

    }
    for(int iQ = 0; iQ < L::dimQ; ++iQ) {
      BOOST_TEST(collisionForcing1_r[idx][iQ] == (valueType) 0,
                 tt::tolerance(1e-15));
      BOOST_TEST(collisionForcing2_r[idx][iQ] == (valueType) 0,
                 tt::tolerance(1e-15));
    }
  }
}

BOOST_AUTO_TEST_CASE(TestExactDifferenceMethod) {
  auto forcingScheme = std::shared_ptr<ForcingScheme<valueType>>(new ExactDifferenceMethod<valueType>());

  vector<MathVector<valueType, L::dimD>> eqVelocityForcing1_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimD>> eqVelocityForcing2_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimQ>> collisionForcing_r(sizeX_l()*sizeY_l()*sizeZ_l());
  vector<MathVector<valueType, L::dimD>> hydroVelocityForcing1_r(L::lX_g *L::lY_g*L::lZ_g);
  vector<MathVector<valueType, L::dimD>> hydroVelocityForcing2_r(L::lX_g *L::lY_g*L::lZ_g);

  for(int idx = 0; idx < L::lX_g *L::lY_g*L::lZ_g; ++idx) {
    valueType density = 0;
    MathVector<valueType, L::dimD> velocity{(valueType) 0};
    for(int iQ = 0; iQ < L::dimQ; ++iQ) {
      forcingScheme->force = MathVector<valueType, L::dimD>{(valueType) 0};
      density = (valueType)(idx+1)/(valueType) (L::lX_g*L::lY_g*L::lZ_g);
      velocity = L::celerity()[iQ]/(valueType) 10.0;
      collisionForcing_r[idx][iQ] = forcingScheme->getCollisionForcing(iQ, density,
                                                                       velocity,
                                                                       velocity.norm2());
    }
    forcingScheme->force = MathVector<valueType, L::dimD>{(valueType)(L::weight()[L::dimQ-1]*(idx+1))/(valueType) (L::lX_g*L::lY_g*L::lZ_g)};
    eqVelocityForcing1_r[idx] = forcingScheme->getEqVelocityForcing(density);
    hydroVelocityForcing1_r[idx] = forcingScheme->getHydroVelocityForcing(density);

    forcingScheme->force = MathVector<valueType, L::dimD>{(valueType) 0};
    eqVelocityForcing2_r[idx] = forcingScheme->getEqVelocityForcing(density);
    hydroVelocityForcing2_r[idx] = forcingScheme->getHydroVelocityForcing(density);

  }

  for(int idx = 0; idx < L::lX_g *L::lY_g*L::lZ_g; ++idx) {
    for(int d = 0; d < L::dimD; ++d) {
      BOOST_TEST(eqVelocityForcing1_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));
      BOOST_TEST(eqVelocityForcing2_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));

      BOOST_TEST(hydroVelocityForcing1_r[idx][d] == (valueType) (0.5*L::weight()[L::dimQ-1]),
                 tt::tolerance(1e-15));
      BOOST_TEST(hydroVelocityForcing2_r[idx][d] == (valueType) 0,
                 tt::tolerance(1e-15));

    }
    for(int iQ = 0; iQ < L::dimQ; ++iQ) {
      BOOST_TEST(collisionForcing_r[idx][iQ] == (valueType) 0,
                 tt::tolerance(1e-15));
    }
  }
}

BOOST_AUTO_TEST_SUITE_END()


//          Copyright Keld Helsgaun 2000, Oliver Kowalke 2014.
// 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)

#include <boost/coroutine/all.hpp>

#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <vector>

#include <boost/bind.hpp>
#include <boost/foreach.hpp>

typedef boost::coroutines::symmetric_coroutine< void >  coro_t;

class merger
{
private:
    std::size_t                 max_;
    std::vector< int >      &   to_;

    void run_( coro_t::yield_type & yield)
    {
        while ( idx < from.size() )
        {
            if ( other->from[other->idx] < from[idx])
                yield( other->coro);
            to_.push_back(from[idx++]);
        }
        while ( to_.size() < max_)
            to_.push_back( other->from[other->idx++]);
    }

    merger( merger const&);
    merger & operator=( merger const&);

public:
    std::vector< int >  const&  from;
    std::size_t                 idx;
    merger                  *   other;
    coro_t::call_type           coro;

    merger( std::vector< int > const& from_, std::vector< int > & to, std::size_t max) :
        max_( max),
        to_( to),
        from( from_),
        idx( 0),
        other( 0),
        coro( boost::bind( & merger::run_, this, _1) )
    {}

    void run()
    { coro(); }
};

std::vector< int > merge( std::vector< int > const& a, std::vector< int > const& b)
{
    std::vector< int > c;
    merger ma( a, c, a.size() + b. size() );
    merger mb( b, c, a.size() + b. size() );

    ma.other = & mb;
    mb.other = & ma;

    ma.run();

    return c;
}

void print( std::string const& name, std::vector< int > const& v)
{
    std::cout << name << " : ";
    BOOST_FOREACH( int itm, v)
    { std::cout << itm << " "; }
    std::cout << "\n";
}

int main( int argc, char * argv[])
{
    std::vector< int > a;
    a.push_back( 1);
    a.push_back( 5);
    a.push_back( 6);
    a.push_back( 10);
    print( "a", a);

    std::vector< int > b;
    b.push_back( 2);
    b.push_back( 4);
    b.push_back( 7);
    b.push_back( 8);
    b.push_back( 9);
    b.push_back( 13);
    print( "b", b);

    std::vector< int > c = merge( a, b);
    print( "c", c);

    std::cout << "Done" << std::endl;

    return EXIT_SUCCESS;
}


#include <algorithm>

#include <seqan/basic.h>
#include <seqan/sequence.h>

#include <boost/functional/hash.hpp>

#include <graphtyper/utilities/hash_seqan.hpp>

namespace seqan
{

inline
std::size_t
hash_value(String<char> const & s)
{
  return boost::hash_range(begin(s), end(s));
}

inline
std::size_t
hash_value(String<Dna> const & s)
{
  std::size_t hash_val = 0;
  std::size_t const MAX_VAL = std::max(seqan::length(s), static_cast<std::size_t>(32));

  for (unsigned i = 0; i < MAX_VAL; ++i)
  {
    hash_val *= 4;
    hash_val += ordValue(s[i]);
  }

  return hash_val;
}

} // namespace seqan

namespace std
{

std::size_t
hash<seqan::String<seqan::Dna> >::operator()(seqan::String<seqan::Dna> const & s) const
{
  std::size_t hash_val = 0;

  for (seqan::Iterator<seqan::String<seqan::Dna> const>::Type it = seqan::begin(s); it != seqan::end(s); ++it)
  {
    hash_val = hash_val * 4 + seqan::ordValue(*it);
  }

  return hash_val;
}

std::size_t
hash<seqan::String<char> >::operator()(seqan::String<char> const & s) const
{
  return boost::hash_range(seqan::begin(s), seqan::end(s));
}

} // namespace std


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

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

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/function/bitwise_and.hpp>
#include <boost/simd/function/genmask.hpp>
#include <boost/simd/function/extract.hpp>
#include <boost/simd/constant/signmask.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>
#include <cstddef>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD(hmsb_
                        , (typename A0, typename X)
                        , bd::cpu_
                        , bs::pack_<bd::arithmetic_<A0>, X>
                        )
  {
    using result_t = std::bitset<cardinal_of<A0>::value>;

    template<typename I> BOOST_FORCEINLINE void piece(const A0& a0, result_t& r) const BOOST_NOEXCEPT
    {
      r.set ( I::value
            , bs::bitwise_and ( bs::Signmask<bd::as_integer_t<typename A0::value_type>>()
                              , bs::extract<I::value>(a0)
                              ) != 0
            );
    }

    template<typename... N>
    BOOST_FORCEINLINE void do_(const A0& a0, result_t& r, brigand::list<N...> const&) const BOOST_NOEXCEPT
    {
      (void)(std::initializer_list<bool>{(piece<N>(a0,r),true)...});
    }

    BOOST_FORCEINLINE result_t operator()(const A0& a0) const BOOST_NOEXCEPT
    {
      result_t r;
      do_(a0,r,typename A0::traits::element_range());
      return r;
    }
  };

  BOOST_DISPATCH_OVERLOAD ( hmsb_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::pack_<bs::logical_<A0>, X>
                          )
  {
    BOOST_FORCEINLINE std::bitset<cardinal_of<A0>::value> operator()(const A0& a0) const BOOST_NOEXCEPT
    {
      return bs::hmsb(bs::genmask(a0));
    }
  };
} } }

#endif


// Boost.Geometry

// Copyright (c) 2020, Oracle and/or its affiliates.

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

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_HPP
#define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_HPP

#include <boost/geometry/strategies/area/spherical.hpp>
#include <boost/geometry/strategies/convex_hull/spherical.hpp>
#include <boost/geometry/strategies/envelope/spherical.hpp>
#include <boost/geometry/strategies/expand/spherical.hpp>

namespace boost { namespace geometry
{

namespace strategies
{

template
<
    typename RadiusTypeOrSphere = double,
    typename CalculationType = void
>
class spherical : strategies::detail::spherical_base<RadiusTypeOrSphere>
{
    using base_t = strategies::detail::spherical_base<RadiusTypeOrSphere>;

public:
    spherical()
        : base_t()
    {}

    template <typename RadiusOrSphere>
    explicit spherical(RadiusOrSphere const& radius_or_sphere)
        : base_t(radius_or_sphere)
    {}

    // area

    template <typename Geometry>
    auto area(Geometry const&) const
    {
        return strategy::area::spherical
            <
                typename base_t::radius_type, CalculationType
            >(base_t::m_radius);
    }

    // envelope

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         typename util::enable_if_point_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_point();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         typename util::enable_if_multi_point_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_multipoint();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         typename util::enable_if_box_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_box();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         typename util::enable_if_segment_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical_segment<CalculationType>();
    }

    template <typename Geometry, typename Box>
    static auto envelope(Geometry const&, Box const&,
                         typename util::enable_if_polysegmental_t<Geometry> * = nullptr)
    {
        return strategy::envelope::spherical<CalculationType>();
    }

    // expand

    template <typename Box, typename Geometry>
    static auto expand(Box const&, Geometry const&,
                       typename util::enable_if_point_t<Geometry> * = nullptr)
    {
        return strategy::expand::spherical_point();
    }

    template <typename Box, typename Geometry>
    static auto expand(Box const&, Geometry const&,
                       typename util::enable_if_box_t<Geometry> * = nullptr)
    {
        return strategy::expand::spherical_box();
    }

    template <typename Box, typename Geometry>
    static auto expand(Box const&, Geometry const&,
                       typename util::enable_if_segment_t<Geometry> * = nullptr)
    {
        return strategy::expand::spherical_segment<CalculationType>();
    }
};

} // namespace strategies

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_HPP


/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2018, Locus Robotics
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   * Neither the name of the copyright holder nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */
#include <fuse_constraints/relative_pose_2d_stamped_constraint.h>

#include <fuse_constraints/normal_delta_pose_2d_cost_functor.h>
#include <pluginlib/class_list_macros.h>

#include <boost/serialization/export.hpp>
#include <ceres/autodiff_cost_function.h>

#include <string>
#include <vector>

namespace fuse_constraints
{

RelativePose2DStampedConstraint::RelativePose2DStampedConstraint(
  const std::string& source,
  const fuse_variables::Position2DStamped& position1,
  const fuse_variables::Orientation2DStamped& orientation1,
  const fuse_variables::Position2DStamped& position2,
  const fuse_variables::Orientation2DStamped& orientation2,
  const fuse_core::VectorXd& partial_delta,
  const fuse_core::MatrixXd& partial_covariance,
  const std::vector<size_t>& linear_indices,
  const std::vector<size_t>& angular_indices) :
    fuse_core::Constraint(
      source,
      {position1.uuid(), orientation1.uuid(), position2.uuid(), orientation2.uuid()})  // NOLINT(whitespace/braces)
{
  size_t total_variable_size = position1.size() + orientation1.size();
  size_t total_indices = linear_indices.size() + angular_indices.size();

  assert(partial_delta.rows() == static_cast<int>(total_indices));
  assert(partial_covariance.rows() == static_cast<int>(total_indices));
  assert(partial_covariance.cols() == static_cast<int>(total_indices));

  // Compute the sqrt information of the provided cov matrix
  fuse_core::MatrixXd partial_sqrt_information = partial_covariance.inverse().llt().matrixU();

  // Assemble a mean vector and sqrt information matrix from the provided values, but in proper variable order
  // What are we doing here?
  // The constraint equation is defined as: cost(x) = ||A * (x - b)||^2
  // If we are measuring a subset of dimensions, we only want to produce costs for the measured dimensions.
  // But the variable vectors will be full sized. We can make this all work out by creating a non-square A
  // matrix, where each row computes a cost for one measured dimensions, and the columns are in the order
  // defined by the variable.
  delta_ = fuse_core::Vector3d::Zero();
  sqrt_information_ = fuse_core::MatrixXd::Zero(total_indices, total_variable_size);

  for (size_t i = 0; i < linear_indices.size(); ++i)
  {
    delta_(linear_indices[i]) = partial_delta(i);
    sqrt_information_.col(linear_indices[i]) = partial_sqrt_information.col(i);
  }

  for (size_t i = linear_indices.size(); i < total_indices; ++i)
  {
    size_t final_index = position1.size() + angular_indices[i - linear_indices.size()];
    delta_(final_index) = partial_delta(i);
    sqrt_information_.col(final_index) = partial_sqrt_information.col(i);
  }
}

fuse_core::Matrix3d RelativePose2DStampedConstraint::covariance() const
{
  // We want to compute:
  // cov = (sqrt_info' * sqrt_info)^-1
  // With some linear algebra, we can swap the transpose and the inverse.
  // cov = (sqrt_info^-1) * (sqrt_info^-1)'
  // But sqrt_info _may_ not be square. So we need to compute the pseudoinverse instead.
  // Eigen doesn't have a pseudoinverse function (for probably very legitimate reasons).
  // So we set the right hand side to identity, then solve using one of Eigen's many decompositions.
  auto I = fuse_core::MatrixXd::Identity(sqrt_information_.rows(), sqrt_information_.cols());
  fuse_core::MatrixXd pinv = sqrt_information_.colPivHouseholderQr().solve(I);
  return pinv * pinv.transpose();
}

void RelativePose2DStampedConstraint::print(std::ostream& stream) const
{
  stream << type() << "\n"
         << "  source: " << source() << "\n"
         << "  uuid: " << uuid() << "\n"
         << "  position1 variable: " << variables().at(0) << "\n"
         << "  orientation1 variable: " << variables().at(1) << "\n"
         << "  position2 variable: " << variables().at(2) << "\n"
         << "  orientation2 variable: " << variables().at(3) << "\n"
         << "  delta: " << delta().transpose() << "\n"
         << "  sqrt_info: " << sqrtInformation() << "\n";
}

ceres::CostFunction* RelativePose2DStampedConstraint::costFunction() const
{
  return new ceres::AutoDiffCostFunction<NormalDeltaPose2DCostFunctor, ceres::DYNAMIC, 2, 1, 2, 1>(
    new NormalDeltaPose2DCostFunctor(sqrt_information_, delta_), sqrt_information_.rows());
}

}  // namespace fuse_constraints

BOOST_CLASS_EXPORT_IMPLEMENT(fuse_constraints::RelativePose2DStampedConstraint);
PLUGINLIB_EXPORT_CLASS(fuse_constraints::RelativePose2DStampedConstraint, fuse_core::Constraint);


#pragma once
#include <sophiatx/protocol/config.hpp>
#include <sophiatx/protocol/types.hpp>
#include <sophiatx/chain/sophiatx_object_types.hpp>
#include <sophiatx/chain/database/database_interface.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>

namespace sophiatx { namespace chain {
namespace bip = boost::interprocess;

using namespace sophiatx::protocol;

//technically, anything in economic_model_object pools do not exists yet; any transfers (by withdraw and add methods) must affect current_supply
class economic_model_object: public object< economic_model_object_type, economic_model_object> {
public:
   template< typename Constructor, typename Allocator >
   economic_model_object( Constructor&& c, allocator< Allocator > a ) : historic_supply( historic_supply_allocator_type( a.get_segment_manager() ) )
   {
      c( *this );
   }

   economic_model_object() =delete;

   id_type           id;

   share_type mining_pool_from_coinbase;
   share_type mining_pool_from_fees;
   share_type interest_pool_from_coinbase;
   share_type interest_pool_from_fees;
   share_type promotion_pool;
   share_type burn_pool=0;
   share_type initial_promotion_pool;
   share_type init_supply;
   share_type total_supply;
   share_type coinbase_block_reward;

   typedef bip::allocator< economic_model_object, bip::managed_mapped_file::segment_manager >  allocator_type;
   typedef bip::allocator< std::pair< uint32_t, share_type >, bip::managed_mapped_file::segment_manager > historic_supply_allocator_type;
   typedef bip::flat_map< uint32_t, share_type, std::less< uint32_t >, historic_supply_allocator_type > historic_supply_map;

   historic_supply_map historic_supply;
   share_type accumulated_supply;   //< sum of coin supplies over last $SOPHIATX_INTEREST_BLOCKS blocks

   void init_economics(share_type init_supply, share_type total_supply);
   void record_block(uint32_t block, share_type current_supply);
   share_type get_mining_reward(uint32_t block_number) const;
   share_type withdraw_mining_reward(uint32_t block_number, uint32_t nominator, uint32_t denominator);
   share_type withdraw_interests(share_type holding, uint32_t period);
   share_type get_available_promotion_pool(uint32_t block_number) const;
   share_type withdraw_from_promotion_pool(share_type amount, uint32_t block_number);
   void add_fee(share_type fee);

};

typedef multi_index_container<
      economic_model_object,
      indexed_by<
            ordered_unique< tag< by_id >,
                  member< economic_model_object, economic_model_object::id_type, &economic_model_object::id > >
      >,
      allocator< economic_model_object >
> economic_model_index;

}}//namespace

FC_REFLECT(sophiatx::chain::economic_model_object, (id)
           (mining_pool_from_coinbase)
           (mining_pool_from_fees)
           (interest_pool_from_coinbase)
           (interest_pool_from_fees)
           (promotion_pool)
           (initial_promotion_pool)
           (init_supply)
           (total_supply)
           (historic_supply)
           (accumulated_supply)
           (coinbase_block_reward)
)
CHAINBASE_SET_INDEX_TYPE( sophiatx::chain::economic_model_object, sophiatx::chain::economic_model_index )


/**
 * Copyright (c) 2017 Melown Technologies SE
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * @file mesh-voxelizer.hpp
 * @author Tomas Drinovsky <tomas.drinovsky@citationtech.net>
 *
 * Mesh voxelization class.
 *
 * This module provides a class voxelization of the generic meshes.
 * The implementation is based on the paper "Simplification and Repair of
 * Polygonal Models Using Volumetric Techniques" by F.S. Nooruddin and Greg Turk
 */

#ifndef GEOMETRY_MESH_VOXELIZER_HPP
#define GEOMETRY_MESH_VOXELIZER_HPP

#include "geometry/mesh.hpp"
#include "geometry/volume.hpp"

#include <memory>

#include <boost/filesystem.hpp>

namespace geometry{

namespace fs = boost::filesystem;

/**
 * @brief MeshVoxelizer
 * @details Class Mesh Voxelizer is able to voxelize arbitrary mesh
 * (even non-water-tight), refilter the voxel grid to different resolution
 * and extract iso surface of this voxel grid.
 * Right now the voxel sizes must be all same - voxel must be a cube.
 */
class MeshVoxelizer{

public:
    typedef ScalarField_t<unsigned short, VolumeArray<unsigned short>> Volume;
    typedef VolumeUnit<unsigned short> VoxelizerUnit;

    enum class Method{ PARITY_COUNT, RAY_STABING };

    struct Parameters {
        float voxelSize;
        bool addSeal;
        float isoThreshold;
        float sealFactor;
        Method method;
        bool shaveVolume; // turn hack on or off
        // if provided, expected to be compatible with voxel size
        math::Extents3 overrideExtents;

        Parameters():
            voxelSize(0.25)
          , addSeal(true)
          , isoThreshold(0.5)
          , sealFactor(5)
          , method(Method::PARITY_COUNT)
          , shaveVolume(true)
          , overrideExtents(math::InvalidExtents{})
         {};
    };

    /**
     * @brief Constructs MeshVoxelizer class
     * @param mesh The geometry to voxelize
     * @param params parameters of MeshVoxelizer
     */

    MeshVoxelizer(const Parameters & params);

    void add( geometry::Mesh & mesh );
    void voxelize();
    std::shared_ptr<Volume> volume();
    void reset();
private:
    /**
     * LayeredZbuffer is container for storing 2D image
     * with multiple values per pixel.
     * It models the following concepts:
     *
     * class LayeredBuffer {
     * public:
     *      LayeredBuffer( math::Size2i & size );
     *      Iterator begin(uint x, uint y);
     *      Iterator end(uint x, uint y);
     * };
     */

    struct LayeredZBuffer{
        typedef std::vector<std::vector<std::vector<float>>> LZBData;
        typedef std::vector<float>::iterator Iterator;
        math::Size2i size;
        LZBData data;

        LayeredZBuffer(math::Size2i & size){
            this->size = size;
            data = LZBData(size.width);
            for(int i=0;i<size.width; ++i){
                data[i]= std::vector<std::vector<float>>(size.height);
                for(int j=0;j<size.height; ++j){
                    data[i][j].reserve(0);
                };
            };
        }

        void sortCells(){
            for( auto &col : data){
                for( auto &cell : col){
                    std::sort(cell.begin(),cell.end());
                }
            }
        }

        long long mem(){
            long long dataMem=0;
            for( auto &col : data){
                for( auto &cell : col){
                    dataMem+= cell.size()*sizeof(float);
                }
            }

            return sizeof(std::vector<float>)*(size.width*size.height+size.width)
                     + dataMem;
        }

        Iterator begin(uint x, uint y){
            return data[x][y].begin();
        }

        Iterator end(uint x, uint y){
            return data[x][y].end();
        }
    };

    struct CompressedLayeredZBuffer{
        typedef std::vector<float>::iterator Iterator;
        typedef std::vector<float>::const_iterator CIterator;
        math::Size2i size;
        std::vector<float> data;
        std::vector<uint> rowpos;
        std::vector<size_t> colStart;
        std::vector<unsigned short> count;

        CompressedLayeredZBuffer():size(math::Size2(0,0)){}

        CompressedLayeredZBuffer(LayeredZBuffer &lzBuffer){
            this->size = lzBuffer.size;
            colStart.reserve(size.width);
            rowpos.reserve(size.width*size.height);
            count.reserve(size.width*size.height);

            std::size_t size = 0;
            for( auto &col : lzBuffer.data){
                colStart.push_back(size);
                for( auto &cell : col){
                    count.push_back(cell.size());
                    rowpos.push_back(size-colStart.back());
                    size+=cell.size();
                }
            }
            data.reserve(size);
            for( auto &col : lzBuffer.data){
                for( auto &cell : col){
                    for( auto &zval : cell){
                        data.push_back(zval);
                    }
                }
            }
        }

        long long mem(){
            return data.size()*sizeof(float) + rowpos.size()*sizeof(uint)
                + colStart.size()*sizeof(size_t)
                + count.size()*sizeof(unsigned short);
        }

        Iterator begin(uint x, uint y){
            return data.begin()+colStart[x]
                    +rowpos[(std::size_t)x*size.height+y];
        }

        CIterator begin(uint x, uint y) const {
            return data.begin()+colStart[x]
                    +rowpos[(std::size_t)x*size.height+y];
        }

        Iterator end(uint x, uint y) {
            return begin(x,y)+count[(std::size_t)x*size.height+y];
        }

        CIterator end(uint x, uint y) const {
            return begin(x,y)+count[(std::size_t)x*size.height+y];
        }

    };

    struct Projection{
        math::Matrix4 transformation;
        math::Size2 viewportSize;

        Projection( math::Matrix4 transformation, math::Size2 viewportSize):
                  transformation(transformation),viewportSize(viewportSize){}
    };

    struct ProjectionResult{
        math::Matrix4 transformation;
        CompressedLayeredZBuffer buffer;

        ProjectionResult( math::Matrix4 transformation
                        , CompressedLayeredZBuffer buffer):
            transformation(transformation), buffer(buffer){};
    };

    typedef std::vector<ProjectionResult> ProjectionResults;
    typedef std::vector<Projection> Projections;
    typedef std::vector<CompressedLayeredZBuffer> CompLZBuffers;

    Parameters params_;
    std::shared_ptr<Volume> volume_;
    std::vector<geometry::Mesh*> meshes;

    Projection orthoProj( const math::Point3 &direction
                               , const math::Extents3 &extents
                               , const float &voxelSize);

    geometry::Mesh sealOfMesh(geometry::Mesh & mesh);
    void fillVolumeFromSeal();

    /**
     * @brief rasterizeMesh renders the mesh into layered z-buffer using
     * given projection matrix.
     * @param mesh
     * @param projection
     */
    void rasterizeMesh( const geometry::Mesh & mesh
                       , const math::Matrix4 & projMat
                       , LayeredZBuffer & lZBuffer);

    /**
     * @brief Determines if point on given position is inside.
     * From each projection it determines if the voxel is inside or outside
     * by parity-counting or by ray-stabing method.
     * @return True if the voxel is inside
     */
    bool isInside(  const math::Point3 & position
                  , ProjectionResults & projectionResults);

    void visualizeDepthMap(const ProjectionResult &proj
                           , const math::Extents3 &extents, const fs::path &path);

};

} //namespace geometry

#endif// GEOMETRY_MESH_VOXELIZER_HPP


#ifdef COMPILATION// -*-indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4;-*-
$CXX $0 -o $0x -lcudart -lcufft `pkg-config --libs fftw3` -lboost_unit_test_framework&&$0x&&rm $0x;exit
#endif
// © Alfredo A. Correa 2020

#ifndef MULTI_ADAPTORS_CUFFTW_HPP
#define MULTI_ADAPTORS_CUFFTW_HPP

#include "../adaptors/../utility.hpp"
#include "../adaptors/../array.hpp"
#include "../adaptors/../config/NODISCARD.hpp"

#include "../adaptors/cuda.hpp"

#include<numeric>

#include<experimental/tuple>
#include<experimental/array>

#include<vector>

#include "../complex.hpp"

//#include<execution>
#include<future>
#include<cufft.h>

namespace boost{
namespace multi{
namespace memory{
namespace cuda{

#if 0
template<class T1, class T1const, class T2, class T2const>
auto copy(
	array_iterator<T1, 1, managed::ptr<T1const>> first,
	array_iterator<T1, 1, managed::ptr<T1const>> last,
	array_iterator<T2, 1, managed::ptr<T2const>> d_first
){
	assert(first.stride() == last.stride());
	auto s = cudaMemcpy2D(raw_pointer_cast(d_first.data()), d_first.stride()*sizeof(T2), raw_pointer_cast(first.data()), first.stride()*sizeof(T2), sizeof(T2), last - first, cudaMemcpyDefault);
	switch(s){
		case cudaSuccess: break;
		case cudaErrorInvalidValue: assert(0);
		case cudaErrorInvalidPitchValue: assert(0);
		case cudaErrorInvalidDevicePointer: assert(0);
		case cudaErrorInvalidMemcpyDirection: assert(0);
		default: assert(0); // unknown error
	}
	return d_first + (last - first);
}
#endif

}}}}

namespace boost{
namespace multi{
namespace cufft{

class sign{
	int impl_;
public:
	sign() = default;
	constexpr sign(int i) : impl_{i}{}
	constexpr operator int() const{return impl_;}
};

constexpr sign forward{CUFFT_FORWARD};
constexpr sign none{0};
constexpr sign backward{CUFFT_INVERSE};

static_assert(forward != none and none != backward and backward != forward, "!");

class plan{
	using complex_type = cufftDoubleComplex;
	complex_type const* idata_     = nullptr;
	complex_type*       odata_     = nullptr;
	int                 direction_ = 0;
	cufftHandle h_;
	plan() = default;
	plan(plan const&) = delete;
	plan(plan&& other) :
		idata_{std::exchange(other.idata_, nullptr)},
		odata_{std::exchange(other.odata_, nullptr)},
		direction_{std::exchange(other.direction_, 0)},
		h_{std::exchange(other.h_, {})}
	{} // needed in <=C++14 for return
	void ExecZ2Z(complex_type const* idata, complex_type* odata, int direction) const{
		++tl_execute_count;
	//	assert(idata_ and odata_);
	//	assert(direction_!=0);
		cufftResult r = ::cufftExecZ2Z(h_, const_cast<complex_type*>(idata), odata, direction);
		switch(r){
			case CUFFT_SUCCESS        : break;// "cuFFT successfully executed the FFT plan."
			case CUFFT_INVALID_PLAN   : throw std::runtime_error{"The plan parameter is not a valid handle."};
		//	case CUFFT_ALLOC_FAILED   : throw std::runtime_error{"CUFFT failed to allocate GPU memory."};
		//	case CUFFT_INVALID_TYPE   : throw std::runtime_error{"The user requests an unsupported type."};
			case CUFFT_INVALID_VALUE  : throw std::runtime_error{"At least one of the parameters idata, odata, and direction is not valid."};
			case CUFFT_INTERNAL_ERROR : throw std::runtime_error{"Used for all internal driver errors."};
			case CUFFT_EXEC_FAILED    : throw std::runtime_error{"CUFFT failed to execute an FFT on the GPU."};
			case CUFFT_SETUP_FAILED   : throw std::runtime_error{"The cuFFT library failed to initialize."};
		//	case CUFFT_INVALID_SIZE   : throw std::runtime_error{"The user specifies an unsupported FFT size."};
		//	case CUFFT_UNALIGNED_DATA : throw std::runtime_error{"Unaligned data."};
		//	case CUFFT_INCOMPLETE_PARAMETER_LIST: throw std::runtime_error{"Incomplete parameter list."};
		//	case CUFFT_INVALID_DEVICE : throw std::runtime_error{"Invalid device."};
		//	case CUFFT_PARSE_ERROR    : throw std::runtime_error{"Parse error."};
		//	case CUFFT_NO_WORKSPACE   : throw std::runtime_error{"No workspace."};
		//	case CUFFT_NOT_IMPLEMENTED: throw std::runtime_error{"Not implemented."};
		//	case CUFFT_LICENSE_ERROR  : throw std::runtime_error{"License error."};
		//	case CUFFT_NOT_SUPPORTED  : throw std::runtime_error{"CUFFT_NOT_SUPPORTED"};
			default                   : throw std::runtime_error{"cufftExecZ2Z unknown error"};
		}
	//	if(cudaDeviceSynchronize() != cudaSuccess) throw std::runtime_error{"Cuda error: Failed to synchronize"};
	}
	void swap(plan& other){
		using std::swap;
		swap(idata_, other.idata_);
		swap(odata_, other.odata_);
		swap(direction_, other.direction_);
		swap(h_, other.h_);
	}
public:
	thread_local static int tl_execute_count;
	plan& operator=(plan other){swap(other); return *this;}
	void operator()() const{ExecZ2Z(idata_, odata_, direction_);}
	template<class I, class O>
	O&& execute_dft(I&& i, O&& o, int direction) const{
		ExecZ2Z(
			const_cast<complex_type*>(reinterpret_cast<complex_type const*>(base(i))),
			const_cast<complex_type*>(reinterpret_cast<complex_type const*>(base(o))),
			direction
		);
		return std::forward<O>(o);
	}
	template<class I, class O>
	void execute_dft(I&& i, O&& o) const{execute_dft(std::forward<I>(i), std::forward<O>(o), direction_);}
	~plan(){if(h_) cufftDestroy(h_);}
	using size_type = int;
	using ssize_type = int;

	template<class I, class O, //std::enable_if_t<(I::dimensionality < 4), int> =0,
		dimensionality_type D = I::dimensionality,
		typename = decltype(raw_pointer_cast(base(std::declval<I const&>())), reinterpret_cast<complex_type*      >(raw_pointer_cast(base(std::declval<O&>()))))
	>
	plan(I const& i, O&& o, sign s) :
		idata_{                          reinterpret_cast<complex_type const*>(raw_pointer_cast(base(i))) },
		odata_{const_cast<complex_type*>(reinterpret_cast<complex_type*      >(raw_pointer_cast(base(o))))},
		direction_{s}
	{
		assert( I::dimensionality < 4 );
		assert( CUFFT_FORWARD == s or CUFFT_INVERSE == s or s == 0 );
		assert( sizes(i) == sizes(o) );

		using std::experimental::apply;// using std::experimental::make_array;
		auto ion      = apply([](auto... t){return std::array< size_type, D>{static_cast< size_type>(t)...};}, sizes  (i));
		auto istrides = apply([](auto... t){return std::array<ssize_type, D>{static_cast<ssize_type>(t)...};}, strides(i));
		auto ostrides = apply([](auto... t){return std::array<ssize_type, D>{static_cast<ssize_type>(t)...};}, strides(o));

		std::array<std::tuple<int, int, int>, I::dimensionality> ssn;
		for(std::size_t i = 0; i != ssn.size(); ++i) ssn[i] = std::make_tuple(istrides[i], ostrides[i], ion[i]);
		std::sort(ssn.begin(), ssn.end(), std::greater<>{});

		for(std::size_t i = 0; i != ssn.size(); ++i){
			istrides[i] = std::get<0>(ssn[i]);
			ostrides[i] = std::get<1>(ssn[i]);
			ion[i]      = std::get<2>(ssn[i]);
		}// = std::tuple<int, int, int>(istrides[i], ostrides[i], ion[i]);

		int istride = istrides.back();
		auto inembed = istrides; inembed.fill(0);
		int ostride = ostrides.back();
		auto onembed = ostrides; onembed.fill(0);
		for(std::size_t i = 1; i != onembed.size(); ++i){
			assert(ostrides[i-1] >= ostrides[i]); // otherwise ordering is incompatible
			assert(ostrides[i-1]%ostrides[i]==0);
			onembed[i]=ostrides[i-1]/ostrides[i]; //	assert( onembed[i] <= ion[i] );
			assert(istrides[i-1]%istrides[i]==0);
			inembed[i]=istrides[i-1]/istrides[i]; //	assert( inembed[i] <= ion[i] );
		}

		direction_ = s;
		idata_ =                           reinterpret_cast<complex_type const*>(raw_pointer_cast(base(i))) ;
		odata_ = const_cast<complex_type*>(reinterpret_cast<complex_type*      >(raw_pointer_cast(base(o))));

		switch(::cufftPlanMany(
			/*cufftHandle *plan*/ &h_,
			/*int rank*/          ion.size(),
			/*int *n*/            ion.data(), //	/*NX*/      last - first,
			/*int *inembed*/      inembed.data(),
			/*int istride*/       istride,
			/*int idist*/         1, //stride(first),
			/*int *onembed*/      onembed.data(),
			/*int ostride*/       ostride,
			/*int odist*/         1, //stride(d_first),
			/*cufftType type*/    CUFFT_Z2Z,
			/*int batch*/         1 //BATCH
		)){
			case CUFFT_SUCCESS        : break;// "cuFFT successfully executed the FFT plan."
			case CUFFT_ALLOC_FAILED   : throw std::runtime_error{"CUFFT failed to allocate GPU memory."};
			case CUFFT_INVALID_VALUE  : throw std::runtime_error{"At least one of the parameters idata, odata, and direction is not valid."};
			case CUFFT_INTERNAL_ERROR : throw std::runtime_error{"Used for all internal driver errors."};
			case CUFFT_SETUP_FAILED   : throw std::runtime_error{"The cuFFT library failed to initialize."};
			case CUFFT_INVALID_SIZE   : throw std::runtime_error{"The user specifies an unsupported FFT size."};
			default                   : throw std::runtime_error{"cufftPlanMany unknown error"};
		}
	}
#ifndef __INTEL_COMPILER
	template<class It1, class It2, dimensionality_type D = decltype(*It1{})::dimensionality>
	static auto many(It1 first, It1 last, It2 d_first, int sign = 0, unsigned = 0)
	->std::decay_t<decltype(const_cast<complex_type*>(reinterpret_cast<complex_type*>(raw_pointer_cast(base(d_first)))), std::declval<plan>())>
#else
	template<class It1, class It2,
		dimensionality_type D = decltype(*It1{})::dimensionality,
		typename TT = decltype(const_cast<complex_type*>(reinterpret_cast<complex_type*>(It2{}.base().raw_pointer_cast())))
	>
	static auto many(It1 first, It1 last, It2 d_first, int sign = 0, unsigned = 0)
#endif
	{
		assert( CUFFT_FORWARD == sign or CUFFT_INVERSE == sign or sign == 0 );
		using namespace std::experimental; //using std::apply;
		assert(sizes(*first)==sizes(*d_first));
		auto ion      = apply([](auto... t){return std::array< size_type, D>{static_cast< size_type>(t)...};}, sizes  (*  first));

		assert(strides(*first) == strides(*last));
		auto istrides = apply([](auto... t){return std::array<ssize_type, D>{static_cast<ssize_type>(t)...};}, strides(*  first));
		auto ostrides = apply([](auto... t){return std::array<ssize_type, D>{static_cast<ssize_type>(t)...};}, strides(*d_first));

		std::array<std::tuple<int, int, int>, std::decay_t<decltype(*It1{})>::dimensionality> ssn;
		for(std::size_t i = 0; i != ssn.size(); ++i) ssn[i] = std::make_tuple(istrides[i], ostrides[i], ion[i]);
		std::sort(ssn.begin(), ssn.end(), std::greater<>{});

		for(std::size_t i = 0; i != ssn.size(); ++i){
			istrides[i] = std::get<0>(ssn[i]);
			ostrides[i] = std::get<1>(ssn[i]);
			ion[i]      = std::get<2>(ssn[i]);
		}

		int istride = istrides.back();
		auto inembed = istrides; inembed.fill(0);
		int ostride = ostrides.back();
		auto onembed = ostrides; onembed.fill(0);
		for(std::size_t i = 1; i != onembed.size(); ++i){
			assert(ostrides[i-1] >= ostrides[i]); // otherwise ordering is incompatible
			assert(ostrides[i-1]%ostrides[i]==0);
			onembed[i]=ostrides[i-1]/ostrides[i]; //	assert( onembed[i] <= ion[i] );
			assert(istrides[i-1]%istrides[i]==0);
			inembed[i]=istrides[i-1]/istrides[i]; //	assert( inembed[i] <= ion[i] );
		}

		plan ret;
		ret.direction_ = sign;
		ret.idata_ =                           reinterpret_cast<complex_type const*>(  first.base().raw_pointer_cast()) ;
		ret.odata_ = const_cast<complex_type*>(reinterpret_cast<complex_type*      >(d_first.base().raw_pointer_cast()));

		switch(::cufftPlanMany(
			/*cufftHandle *plan*/ &ret.h_,
			/*int rank*/          ion.size(),
			/*int *n*/            ion.data(), //	/*NX*/      last - first,
			/*int *inembed*/      inembed.data(),
			/*int istride*/       istride,
			/*int idist*/         stride(first),
			/*int *onembed*/      onembed.data(),
			/*int ostride*/       ostride,
			/*int odist*/         stride(d_first),
			/*cufftType type*/    CUFFT_Z2Z,
			/*int batch*/         last - first //BATCH
		)){
			case CUFFT_SUCCESS        : break;// "cuFFT successfully executed the FFT plan."
		//	case CUFFT_INVALID_PLAN   : throw std::runtime_error{"The plan parameter is not a valid handle."};
			case CUFFT_ALLOC_FAILED   : throw std::runtime_error{"CUFFT failed to allocate GPU memory."};
		//	case CUFFT_INVALID_TYPE   : throw std::runtime_error{"The user requests an unsupported type."};
			case CUFFT_INVALID_VALUE  : throw std::runtime_error{"At least one of the parameters idata, odata, and direction is not valid."};
			case CUFFT_INTERNAL_ERROR : throw std::runtime_error{"Used for all internal driver errors."};
		//	case CUFFT_EXEC_FAILED    : throw std::runtime_error{"CUFFT failed to execute an FFT on the GPU."};
			case CUFFT_SETUP_FAILED   : throw std::runtime_error{"The cuFFT library failed to initialize."};
			case CUFFT_INVALID_SIZE   : throw std::runtime_error{"The user specifies an unsupported FFT size."};
		//	case CUFFT_UNALIGNED_DATA : throw std::runtime_error{"Unaligned data."};
		//	case CUFFT_INCOMPLETE_PARAMETER_LIST: throw std::runtime_error{"Incomplete parameter list."};
		//	case CUFFT_INVALID_DEVICE : throw std::runtime_error{"Invalid device."};
		//	case CUFFT_PARSE_ERROR    : throw std::runtime_error{"Parse error."};
		//	case CUFFT_NO_WORKSPACE   : throw std::runtime_error{"No workspace."};
		//	case CUFFT_NOT_IMPLEMENTED: throw std::runtime_error{"Not implemented."};
		//	case CUFFT_LICENSE_ERROR  : throw std::runtime_error{"License error."};
		//	case CUFFT_NOT_SUPPORTED  : throw std::runtime_error{"CUFFT_NOT_SUPPORTED"};
			default                   : throw std::runtime_error{"cufftPlanMany unknown error"};
		}
		return ret;
	}
};

thread_local int plan::tl_execute_count = 0;

template<typename In, class Out>
auto dft(In const& i, Out&& o, int s)
->decltype(cufft::plan{i, o, s}(), std::forward<Out>(o)){
	return cufft::plan{i, o, s}(), std::forward<Out>(o);}

template<typename In, typename R = multi::array<typename In::element_type, In::dimensionality, decltype(get_allocator(std::declval<In>()))>>
NODISCARD("when first argument is const")
R dft(In const& i, int s){
	static_assert(std::is_trivially_default_constructible<typename In::element_type>{}, "!");
	R ret(extensions(i), get_allocator(i));
	cufft::dft(i, ret, s);
	return ret;
}

#ifndef __INTEL_COMPILER
template<typename It1, typename It2>
auto many_dft(It1 first, It1 last, It2 d_first, sign s)
->decltype(plan::many(first, last, d_first, s)(), d_first + (last - first)){
	return plan::many(first, last, d_first, s)(), d_first + (last - first);}
#else
template<typename It1, typename It2>
auto many_dft(It1 first, It1 last, It2 d_first, sign s)
->decltype(plan::many(first, last, d_first, s)(), d_first + (last - first)){
	return plan::many(first, last, d_first, s)(), d_first + (last - first);}
#endif

template<typename In, class Out,  std::size_t D = In::dimensionality, std::enable_if_t<(D==1), int> = 0>
Out&& dft(std::array<bool, D> which, In const& i, Out&& o, int s){
	if(which[0]) return cufft::dft(i, std::forward<Out>(o), s);
	else return std::forward<Out>(std::forward<Out>(o) = i);
}

template<typename In, class Out, std::size_t D = In::dimensionality, std::enable_if_t<(D>1), int> = 0>
auto dft(std::array<bool, D> which, In const& i, Out&& o, int s)
->decltype(many_dft(i.begin(), i.end(), o.begin(), s),std::forward<Out>(o))
{
	assert(extension(i) == extension(o));
	std::array<bool, D-1> tail = reinterpret_cast<std::array<bool, D-1> const&>(which[1]);
	auto ff = std::find(begin(which)+1, end(which), false);
	if(which[0] == true){
		if(ff==end(which)) cufft::dft(i, std::forward<Out>(o), s);
		else{
			auto n = ff - which.begin();
			std::rotate(begin(which), ff, end(which));
			dft(which, (i<<n), (o<<n), s);
		}
	}else if(which[0]==false){
		if(D==1 or std::none_of(begin(which)+1, end(which), [](auto e){return e;})){
			if(base(o) != base(i)) std::forward<Out>(o) = i;
			else if(o.layout() != i.layout()) std::forward<Out>(o) = +i;
		}
//		if(D==1 or std::all_of(begin(which)+1, end(which), [](auto e){return e==false;})){
//			if(base(o) != base(i)) std::forward<Out>(o) = i;//.assign(i);//std::forward<Out>(o) = i;
//			else if(o.layout() != i.layout()) std::forward<Out>(o) = +i;
//		}
		else if(ff==end(which)) many_dft(i.begin(), i.end(), o.begin(), s);
		else{
			if(which.size() > 1 and which[1] == false and i.is_flattable() and o.is_flattable()) cufft::dft(tail, i.flatted(), o.flatted(), s);
			else{
				auto d_min = 0; auto n_min = size(i);
				for(auto d = 0; d != D; ++d){if((size(i<<d) < n_min) and (tail[d]==false)){n_min = size(i<<d); d_min = d;}}
				if(d_min!=0){
					std::rotate(which.begin(), which.begin()+d_min, which.end());
					dft(which, i<<d_min, o<<d_min, s);
				}else{
					if(base(i) == base(o) and i.layout() != o.layout()){
						auto tmp = +i;
						for(auto idx : extension(i)) cufft::dft(tail, tmp[idx], o[idx], s);
					}else for(auto idx : extension(i)) cufft::dft(tail, i[idx], o[idx], s);
				}
			}
		}
	}
	return std::forward<Out>(o);
}

template<typename In,  std::size_t D = In::dimensionality>
NODISCARD("when passing a const argument")
auto dft(std::array<bool, D> which, In const& i, int sign)->std::decay_t<decltype(
dft(which, i, typename In::decay_type(extensions(i), get_allocator(i)), sign))>{return
dft(which, i, typename In::decay_type(extensions(i), get_allocator(i)), sign);}

template<typename In,  std::size_t D = In::dimensionality>
auto dft(std::array<bool, D> which, In&& i, int sign)
->decltype(dft(which, i, i, sign), std::forward<In>(i)){
	return dft(which, i, i, sign), std::forward<In>(i);}

//template<typename... A> auto            dft_forward(A&&... a)
//->decltype(cufft::dft(std::forward<A>(a)..., cufft::forward)){
//	return cufft::dft(std::forward<A>(a)..., cufft::forward);}

template<typename Array, typename A> NODISCARD("when passing a const argument")
auto dft_forward(Array arr, A const& a)
->decltype(cufft::dft(arr, a, cufft::forward)){
	return cufft::dft(arr, a, cufft::forward);}

template<typename Array, dimensionality_type D> NODISCARD("when passing a const argument")
auto dft_forward(Array arr, multi::cuda::array<std::complex<double>, D>&& a)
->decltype(cufft::dft(arr, a, cufft::forward), multi::cuda::array<std::complex<double>, D>{}){//assert(0);
	return cufft::dft(arr, a, cufft::forward), std::move(a);}

template<typename A> NODISCARD("when passing a const argument")
auto dft_forward(A const& a)
->decltype(cufft::dft(a, cufft::forward)){
	return cufft::dft(a, cufft::forward);}

template<typename... A> auto            dft_backward(A&&... a)
->decltype(cufft::dft(std::forward<A>(a)..., cufft::backward)){
	return cufft::dft(std::forward<A>(a)..., cufft::backward);}

template<typename Array, typename A> NODISCARD("when passing a const argument")
auto dft_backward(Array arr, A const& a)
->decltype(cufft::dft(arr, a, cufft::backward)){
	return cufft::dft(arr, a, cufft::backward);}

template<typename A> NODISCARD("when passing a const argument")
auto dft_backward(A const& a)
->decltype(cufft::dft(a, cufft::backward)){
	return cufft::dft(a, cufft::backward);}

}

}}

#if not __INCLUDE_LEVEL__ // TEST BELOW

#define BOOST_TEST_MODULE "C++ Unit Tests for Multi cuFFT adaptor"
#define BOOST_TEST_DYN_LINK
#include<boost/test/unit_test.hpp>

#include <boost/timer/timer.hpp>

#include "../adaptors/cuda.hpp"
#include "../adaptors/fftw.hpp"
#include "../adaptors/cufft.hpp"

//#include "../adaptors/fft.hpp"

#include<complex>
#include<thrust/complex.h>
#include "../complex.hpp"

#include<cuda_runtime.h> // cudaDeviceSynchronize

#include<iostream>

namespace multi = boost::multi;
using complex = std::complex<double>;
namespace utf = boost::unit_test;

template <class T>
__attribute__((always_inline)) inline void DoNotOptimize(const T &value) {
	asm volatile("" : "+m"(const_cast<T &>(value)));
}

struct watch : private std::chrono::high_resolution_clock{
	std::string label_; time_point  start_;
	watch(std::string label ="") : label_{label}, start_{}{
		cudaDeviceSynchronize();
		start_ = now();
	}
	~watch(){
		cudaDeviceSynchronize();
		auto const count = std::chrono::duration<double>(now() - start_).count();
		std::cerr<< label_<<": "<< count <<" sec"<<std::endl;
	}
};

constexpr complex I{0, 1};

#if 1

BOOST_AUTO_TEST_CASE(cufft_2D, *boost::unit_test::tolerance(0.0001)){

	multi::array<complex, 2> const in_cpu = {
		{ 1. + 2.*I, 9. - 1.*I, 2. + 4.*I},
		{ 3. + 3.*I, 7. - 4.*I, 1. + 9.*I},
		{ 4. + 1.*I, 5. + 3.*I, 2. + 4.*I},
		{ 3. - 1.*I, 8. + 7.*I, 2. + 1.*I},
		{ 31. - 1.*I, 18. + 7.*I, 2. + 10.*I}
	};
	multi::array<complex, 2> fw_cpu(extensions(in_cpu));
	multi::fftw::dft(in_cpu, fw_cpu, multi::fftw::forward);

	multi::cuda::array<complex, 2> const in_gpu = in_cpu;
	multi::cuda::array<complex, 2> fw_gpu(extensions(in_gpu));
	multi::cufft::dft(in_gpu, fw_gpu, multi::cufft::forward);

	BOOST_TEST( imag(static_cast<complex>(fw_gpu[3][2]) - fw_cpu[3][2]) == 0. );

	auto fw2_gpu = multi::cufft::dft(in_gpu, multi::cufft::forward);
	BOOST_TEST( imag(static_cast<complex>(fw2_gpu[3][1]) - fw_cpu[3][1]) == 0. );

	multi::cuda::managed::array<complex, 2> const in_mng = in_cpu;
	multi::cuda::managed::array<complex, 2> fw_mng(extensions(in_gpu));
	multi::cufft::dft(in_mng, fw_mng, multi::cufft::forward);

	BOOST_TEST( imag(fw_mng[3][2] - fw_cpu[3][2]) == 0. );

	auto fw2_mng = multi::fftw::dft(in_mng, multi::fftw::forward);
	BOOST_TEST( imag(fw2_mng[3][1] - fw_cpu[3][1]) == 0. );

}

BOOST_AUTO_TEST_CASE(cufft_3D_timing, *boost::unit_test::tolerance(0.0001)){

	auto x = std::make_tuple(300, 300, 300);
	{
		multi::array<complex, 3> const in_cpu(x, 10.);
		BOOST_ASSERT( in_cpu.num_elements()*sizeof(complex) < 2e9 );
		multi::array<complex, 3> fw_cpu(extensions(in_cpu), 99.);
		{
		//	boost::timer::auto_cpu_timer t;  // 1.041691s wall, 1.030000s user + 0.000000s system = 1.030000s CPU (98.9%)
			multi::fftw::dft(in_cpu, fw_cpu, multi::fftw::forward);
			BOOST_TEST( fw_cpu[8][9][10] != 99. );
		}
	}
	{
		multi::cuda::array<complex, 3> const in_gpu(x, 10.);
		multi::cuda::array<complex, 3> fw_gpu(extensions(in_gpu), 99.);
		{
		//	boost::timer::auto_cpu_timer t; //  0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%)
			multi::cufft::dft(in_gpu, fw_gpu, multi::fftw::forward);

			BOOST_TEST( static_cast<complex>(fw_gpu[8][9][10]) != 99. );
		}
	}
	{
		multi::cuda::managed::array<complex, 3> const in_gpu(x, 10.);
		multi::cuda::managed::array<complex, 3> fw_gpu(extensions(in_gpu), 99.);
		{
		//	boost::timer::auto_cpu_timer t; //  0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%)
			multi::cufft::dft(in_gpu, fw_gpu, multi::cufft::forward);
		//	BOOST_TEST( fw_gpu[8][9][10].operator complex() != 99. );
		}
		{
		//	boost::timer::auto_cpu_timer t; //  0.208237s wall, 0.200000s user + 0.010000s system = 0.210000s CPU (100.8%)
			multi::cufft::dft(in_gpu, fw_gpu, multi::cufft::forward);
		//	BOOST_TEST( fw_gpu[8][9][10].operator complex() != 99. );
		}
	}
}

BOOST_AUTO_TEST_CASE(cufft_combinations, *utf::tolerance(0.00001)){

	auto const in = []{
		multi::array<complex, 4> ret({32, 90, 98, 96});
		std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(),
			[](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};}
		);
		return ret;
	}();
	std::clog<<"memory size "<< in.num_elements()*sizeof(complex)/1e6 <<" MB\n";

	multi::cuda::array<complex, 4> const in_gpu = in;
	multi::cuda::managed::array<complex, 4> const in_mng = in;

	using std::clog;
	for(auto c : std::vector<std::array<bool, 4>>{
		{false, true , true , true },
		{false, true , true , false},
		{true , false, false, false},
		{true , true , false, false},
		{false, false, true , false},
		{false, false, false, false},
	}){
		std::clog<<"case "; copy(begin(c), end(c), std::ostream_iterator<bool>{std::clog,", "}); std::clog<<std::endl;
		multi::array<complex, 4> out = in;
		multi::array<complex, 4> in_rw = in;
		[&, _ = watch{"cpu_opl "}]{
			multi::fftw::dft_forward(c, in, out);
		}();
		[&, _ = watch{"cpu_ipl "}]{
			multi::fftw::dft(c, in_rw, multi::fftw::forward);
			BOOST_TEST( abs( static_cast<multi::complex<double>>(in_rw[5][4][3][1]) - multi::complex<double>(out[5][4][3][1]) ) == 0. );
		}();
		{
			multi::array<complex, 4> in_rw2 = in;
			[&, _ = watch{"cpu_mov "}]{
				multi::array<complex, 4> const out_mov = multi::fftw::dft_forward(c, std::move(in_rw2));
			//	what(out_mov);
				BOOST_TEST( abs( static_cast<multi::complex<double>>(out_mov[5][4][3][1]) - multi::complex<double>(out[5][4][3][1]) ) == 0. );
				BOOST_REQUIRE( is_empty(in_rw2) );
				BOOST_REQUIRE( extensions(out_mov) == extensions(in) );
			}();
		}

		[&, _ = watch{"cpu_new "}]{
			auto const out_cpy = multi::fftw::dft_forward(c, in);
			BOOST_TEST( abs( static_cast<multi::complex<double>>(out_cpy[5][4][3][1]) - multi::complex<double>(out[5][4][3][1]) ) == 0. );
		}();
		multi::cuda::array<complex, 4> out_gpu(extensions(in_gpu));
		[&, _ = watch{"gpu_opl "}]{
			multi::cufft::dft(c, in_gpu   , out_gpu, multi::cufft::forward);
			BOOST_TEST( abs( static_cast<complex>(out_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. );
		}();
		{
			multi::cuda::array<complex, 4> in_rw_gpu = in_gpu;
			[&, _ = watch{"gpu_ipl "}]{
				multi::cufft::dft(c, in_rw_gpu, multi::cufft::forward);
				BOOST_TEST( abs( static_cast<complex>(in_rw_gpu[5][4][3][1]) - out[5][4][3][1] ) == 0. );
			}();
		}
		{
			multi::cuda::array<complex, 4> in_rw_gpu = in_gpu;
			[&, _ = watch{"gpu_mov "}]{
				multi::cuda::array<complex, 4> const out_mov = multi::cufft::dft_forward(c, std::move(in_rw_gpu));
			//	BOOST_REQUIRE( in_rw_gpu.empty() );
			//	BOOST_TEST( abs( static_cast<complex>(out_mov[5][4][3][1]) - out[5][4][3][1] ) == 0. );
			}();
		}
		{
			multi::cuda::array<complex, 4> in_rw_gpu = in_gpu;
			[&, _ = watch{"gpu_mov "}]{
				multi::cuda::array<complex, 4> out_mov = std::move(in_rw_gpu);
				multi::cufft::dft(c, out_mov, multi::cufft::forward);
			//	BOOST_REQUIRE( in_rw_gpu.empty() );
			//	BOOST_TEST( abs( static_cast<complex>(out_mov[5][4][3][1]) - out[5][4][3][1] ) == 0. );
			}();
		}
		cudaDeviceSynchronize();
		[&, _ = watch{"gpu_new "}]{
			multi::cuda::array<complex, 4> const out_cpy = multi::cufft::dft(c, in_gpu, multi::cufft::forward);
		}();
		multi::cuda::managed::array<complex, 4> out_mng(extensions(in_mng));
		[&, _ = watch{"mng_cld "}]{
			multi::cufft::dft(c, in_mng, out_mng, multi::cufft::forward);
			BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}();
		[&, _ = watch{"mng_hot "}]{
			multi::cufft::dft(c, in_mng   , out_mng, multi::cufft::forward);
			BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}();
		[&, _ = watch{"mng_new "}]{
			auto const out_mng = multi::cufft::dft(c, in_mng, multi::cufft::forward);
			BOOST_TEST( abs( out_mng[5][4][3][1] - out[5][4][3][1] ) == 0. );
		}();
	}
	std::clog<<std::endl;

}

BOOST_AUTO_TEST_CASE(cufft_many_3D, *utf::tolerance(0.00001) ){

	auto const in_cpu = []{
		multi::array<complex, 4> ret({45, 18, 32, 16});
		std::generate(
			ret.data_elements(), ret.data_elements() + ret.num_elements(),
			[](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};}
		);
		return ret;
	}();

	multi::cuda::array<complex, 4> const in = in_cpu;
	multi::cuda::array<complex, 4>       out(extensions(in));

#if 0
	multi::cufft::many_dft(begin(unrotated(in)), end(unrotated(in)), begin(unrotated(out)), +1);

	multi::array<complex, 4> out_cpu(extensions(in));
	multi::fft::many_dft(begin(unrotated(in_cpu)), end(unrotated(in_cpu)), begin(unrotated(out_cpu)), +1);

	BOOST_TEST( imag( static_cast<complex>(out[5][4][3][2]) - out_cpu[5][4][3][2]) == 0. );
#endif
}

#if 0

BOOST_AUTO_TEST_CASE(cufft_4D, *utf::tolerance(0.00001)){
	auto const in = []{
		multi::array<complex, 3> ret({10, 10, 10});
		std::generate(ret.data_elements(), ret.data_elements() + ret.num_elements(),
			[](){return complex{std::rand()*1./RAND_MAX, std::rand()*1./RAND_MAX};}
		);
		return ret;
	}();

	multi::array<complex, 3> out(extensions(in));
//	multi::fftw::dft({true, false, true}, in, out, multi::fftw::forward);
	multi::fft::many_dft(begin(in<<1), end(in<<1), begin(out<<1), multi::fftw::forward);

	multi::cuda::array<complex, 3> in_gpu = in;
	multi::cuda::array<complex, 3> out_gpu(extensions(in));

//	multi::cufft::dft({true, false, true}, in_gpu, out_gpu, multi::fft::forward);//multi::cufft::forward);
	multi::cufft::many_dft(begin(in_gpu<<1), end(in_gpu<<1), begin(out_gpu<<1), multi::fftw::forward);
	BOOST_TEST( imag( static_cast<complex>(out_gpu[5][4][3]) - out[5][4][3]) == 0. );
}

//BOOST_AUTO_TEST_CASE(cu

#if 0
BOOST_AUTO_TEST_CASE(cufft_4D){
	auto const in = []{
		multi::array<complex, 3> ret({10, 10, 10});
		ret[2][3][4] = 99.;
		return ret;
	}();
	multi::array<complex, 3> out(extensions(in));

	multi::fftw::dft({true, true, false}, in, out, multi::fftw::forward);

//	auto fwd = multi::fftw::dft({true, true, true, true}, in, out, multi::fftw::forward);
//	BOOST_REQUIRE(in[2][3][4][5] == 99.);
	std::cout << out[9][1][2] << std::endl;
	for(auto i = 0; i != out.num_elements(); ++i) std:cout << (out.data_elements()[i]) <<' ';

#if 0
	multi::cuda::array<complex, 3> in_gpu = in;//[]{
//		multi::cuda::array<complex, 4> ret({10, 10, 10, 10});
//		ret[2][3][4][5] = 99.;
//		return ret;
//	}();
	multi::cuda::array<complex, 3> out_gpu(extensions(in));
	multi::cufft::dft({true, true, false}, in_gpu, out_gpu, multi::cufft::forward);

	std::cout << out_gpu[5][4][3].operator complex() << std::endl;

//	multi::cufft::dft({true, true, true, true}, in_gpu, out_gpu, multi::cufft::forward);
//	multi::cufft::dft({true, true, true, true}, in_gpu, out_gpu, multi::cufft::forward);
#endif

}
#endif
#endif
#endif

#endif
#endif


// Copyright (C) 2016 Vicente J. Botet Escriba
//
//  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)

// <experimental/monad_error.hpp>
// <experimental/optional.hpp>

#include <experimental/memory.hpp>

#include <boost/detail/lightweight_test.hpp>

std::unique_ptr<int> twice(int i) {
  if (i==0)
  {
    return nullptr;
  }
  return std::experimental::make<std::unique_ptr>(2*i);
}

std::unique_ptr<int> recover( ) {
  return std::experimental::make<std::unique_ptr>(-1);
}
std::unique_ptr<int> fwd() {
  return nullptr;
}

int main()
{
  namespace stde = std::experimental;

  static_assert(stde::is_monad_error<std::unique_ptr<stde::_t>>::value, "ERROR");

  {
    std::unique_ptr<int> x = stde::monad_error::make_error<std::unique_ptr>();
    std::unique_ptr<int> y = stde::monad_error::bind(x,twice);
    BOOST_TEST(! y);
    std::unique_ptr<int> z = stde::monad_error::catch_error(y,recover);
    BOOST_TEST(z);
    BOOST_TEST_EQ(*z,-1);
  }
  {
    int v=1;
    std::unique_ptr<int> x = stde::make<std::unique_ptr>(v);
    std::unique_ptr<int> y = stde::monad_error::bind(x,twice);
    std::unique_ptr<int> z = stde::monad_error::catch_error(y,fwd);
    BOOST_TEST(z);
    BOOST_TEST(*z == 2);
  }
  {
    int v=0;
    std::unique_ptr<int> x = stde::make<std::unique_ptr>(v);
    std::unique_ptr<int> y = stde::monad_error::bind(x,twice);
    BOOST_TEST(! y);
  }
  return ::boost::report_errors();
}


//
//  mesh_Abaqus_multi_faults.hpp
//  hybrid_fem_bie
//
//  Created by Max on 2/13/18.
//
//

#ifndef mesh_Abaqus_multi_faults_hpp
#define mesh_Abaqus_multi_faults_hpp

#include <stdio.h>
#include <iostream>
#include <Eigen/Eigen>
#include "Meshrectangular.hpp"
#include "update_mesh_topology.hpp"
#include "share_header.hpp"
#include "Abaqus_read.hpp"
using namespace Eigen;

void mesh_Abaqus_multi_faults(std::string &fname,MatrixXd &Node ,MatrixXi_rm &Element,
                              std::vector<int> &BIE_top, std::vector<int> &BIE_bot,
                              std::vector<std::vector<int>> &fault_nodes, double num_faults);
#endif /* mesh_Abaqus_multi_faults_hpp */


/// @file audio_block_format_hoa.hpp
#pragma once

#include <boost/optional.hpp>
#include "adm/elements/time.hpp"
#include "adm/elements/audio_block_format_id.hpp"
#include "adm/elements/common_parameters.hpp"
#include "adm/elements_fwd.hpp"
#include "adm/detail/named_option_helper.hpp"
#include "adm/detail/named_type.hpp"
#include "adm/export.h"
#include "adm/elements/screen_ref.hpp"
#include "adm/elements/nfc_ref_dist.hpp"
#include "adm/elements/normalization.hpp"
#include "adm/detail/auto_base.hpp"

namespace adm {

  class Document;

  /// @brief Tag for AudioBlockFormatHoa
  struct AudioBlockFormatHoaTag {};
  /// @brief Tag for NamedType ::Order
  struct OrderTag {};
  /// @brief NamedType for order parameter
  using Order = detail::NamedType<int, OrderTag>;
  /// @brief Tag for NamedType ::Degree
  struct DegreeTag {};
  /// @brief NamedType for degree parameter
  using Degree = detail::NamedType<int, DegreeTag>;
  /// @brief Tag for NamedType ::Equation
  struct EquationTag {};
  /// @brief NamedType for a equation parameter
  using Equation = detail::NamedType<std::string, EquationTag>;

  namespace detail {
    using AudioBlockFormatHoaBase =
        HasParameters<DefaultParameter<Gain>,
                      DefaultParameter<HeadphoneVirtualise>,
                      DefaultParameter<HeadLocked>,
                      DefaultParameter<Importance>>;
  }  // namespace detail

  /**
   * @brief Class representation for ADM element audioBlockFormat if
   * audioChannelFormat.typeDefinition == "HOA"
   *
   * Supported parameters are as follows:
   *
   * \rst
   * +---------------------+------------------------------+----------------------------+
   * | ADM Parameter       | Parameter Type               | Pattern Type               |
   * +=====================+==============================+============================+
   * | audioBlockFormatId  | :class:`AudioBlockFormatId`  | :class:`RequiredParameter` |
   * +---------------------+------------------------------+----------------------------+
   * | rtime               | :type:`Rtime`                | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | duration            | :type:`Duration`             | :class:`OptionalParameter` |
   * +---------------------+------------------------------+----------------------------+
   * | order               | :type:`Order`                | :class:`OptionalParameter` |
   * +---------------------+------------------------------+----------------------------+
   * | degree              | :type:`Degree`               | :class:`OptionalParameter` |
   * +---------------------+------------------------------+----------------------------+
   * | normalization       | :type:`Normalization`        | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | nfcRefDist          | :type:`NfcRefDist`           | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | gain                | :class:`Gain`                | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | importance          | :type:`Importance`           | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | headLocked          | :type:`HeadLocked`           | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | headphoneVirtualise | :class:`HeadphoneVirtualise` | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | screenRef           | :type:`ScreenRef`            | :class:`DefaultParameter`  |
   * +---------------------+------------------------------+----------------------------+
   * | equation            | :type:`Equation`             | :class:`OptionalParameter` |
   * +---------------------+------------------------------+----------------------------+
   * \endrst
   *
   * Note that while order and degree are technically optional, in practice
   * they must always be specified.
   */
  class AudioBlockFormatHoa : private detail::AudioBlockFormatHoaBase {
   public:
    typedef AudioBlockFormatHoaTag tag;
    /// Type that holds the id for this element;
    typedef AudioBlockFormatId id_type;

    template <typename... Parameters>
    AudioBlockFormatHoa(Order order, Degree degree,
                        Parameters... optionalNamedArgs);

    ADM_EXPORT AudioBlockFormatHoa(const AudioBlockFormatHoa&) = default;
    ADM_EXPORT AudioBlockFormatHoa(AudioBlockFormatHoa&&) = default;
    ADM_EXPORT AudioBlockFormatHoa& operator=(const AudioBlockFormatHoa&) =
        default;
    ADM_EXPORT AudioBlockFormatHoa& operator=(AudioBlockFormatHoa&&) = default;

    /**
     * @brief ADM parameter getter template
     *
     * Templated getter with the wanted ADM parameter type as template
     * argument. If currently no value is available trying to get the adm
     * parameter will result in an exception. Check with the has method before
     */
    template <typename Parameter>
    Parameter get() const;

    /**
     * @brief ADM parameter has template
     *
     * Templated has method with the ADM parameter type as template argument.
     * Returns true if the ADM parameter is set or has a default value.
     */
    template <typename Parameter>
    bool has() const;

    /**
     * @brief ADM parameter isDefault template
     *
     * Templated isDefault method with the ADM parameter type as template
     * argument. Returns true if the ADM parameter is the default value.
     */
    template <typename Parameter>
    bool isDefault() const;

    /// @brief AudioBlockFormatId setter
    ADM_EXPORT void set(AudioBlockFormatId id);
    /// @brief Rtime setter
    ADM_EXPORT void set(Rtime rtime);
    /// @brief Duration setter
    ADM_EXPORT void set(Duration duration);
    /// @brief Order setter
    ADM_EXPORT void set(Order order);
    /// @brief Degree setter
    ADM_EXPORT void set(Degree degree);
    /// @brief NfcRefDist setter
    ADM_EXPORT void set(NfcRefDist dist);
    /// @brief ScreenRef setter
    ADM_EXPORT void set(ScreenRef screenRef);
    /// @brief Normalization setter
    ADM_EXPORT void set(Normalization normalization);
    /// @brief Equation setter
    ADM_EXPORT void set(Equation equation);

    using detail::AudioBlockFormatHoaBase::set;

    /**
     * @brief ADM parameter unset template
     *
     * Templated unset method with the ADM parameter type as template
     * argument. Removes an ADM parameter if it is optional or resets it to
     * the default value if there is one.
     */
    template <typename Parameter>
    void unset();

   private:
    using detail::AudioBlockFormatHoaBase::get;
    using detail::AudioBlockFormatHoaBase::has;
    using detail::AudioBlockFormatHoaBase::isDefault;
    using detail::AudioBlockFormatHoaBase::unset;

    ADM_EXPORT AudioBlockFormatId
        get(detail::ParameterTraits<AudioBlockFormatId>::tag) const;
    ADM_EXPORT Rtime get(detail::ParameterTraits<Rtime>::tag) const;
    ADM_EXPORT Duration get(detail::ParameterTraits<Duration>::tag) const;
    ADM_EXPORT Order get(detail::ParameterTraits<Order>::tag) const;
    ADM_EXPORT Degree get(detail::ParameterTraits<Degree>::tag) const;
    ADM_EXPORT NfcRefDist get(detail::ParameterTraits<NfcRefDist>::tag) const;
    ADM_EXPORT ScreenRef get(detail::ParameterTraits<ScreenRef>::tag) const;
    ADM_EXPORT Normalization
        get(detail::ParameterTraits<Normalization>::tag) const;
    ADM_EXPORT Equation get(detail::ParameterTraits<Equation>::tag) const;

    ADM_EXPORT bool has(detail::ParameterTraits<AudioBlockFormatId>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<Rtime>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<Duration>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<Order>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<Degree>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<NfcRefDist>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<ScreenRef>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<Normalization>::tag) const;
    ADM_EXPORT bool has(detail::ParameterTraits<Equation>::tag) const;

    template <typename Tag>
    bool isDefault(Tag) const {
      return false;
    }
    ADM_EXPORT bool isDefault(detail::ParameterTraits<Rtime>::tag) const;
    ADM_EXPORT bool isDefault(detail::ParameterTraits<NfcRefDist>::tag) const;
    ADM_EXPORT bool isDefault(detail::ParameterTraits<ScreenRef>::tag) const;
    ADM_EXPORT bool isDefault(
        detail::ParameterTraits<Normalization>::tag) const;

    ADM_EXPORT void unset(detail::ParameterTraits<Rtime>::tag);
    ADM_EXPORT void unset(detail::ParameterTraits<Duration>::tag);
    ADM_EXPORT void unset(detail::ParameterTraits<NfcRefDist>::tag);
    ADM_EXPORT void unset(detail::ParameterTraits<ScreenRef>::tag);
    ADM_EXPORT void unset(detail::ParameterTraits<Normalization>::tag);
    ADM_EXPORT void unset(detail::ParameterTraits<Equation>::tag);
    AudioBlockFormatId id_;
    boost::optional<Rtime> rtime_;
    boost::optional<Duration> duration_;
    boost::optional<Order> order_;
    boost::optional<Degree> degree_;
    boost::optional<NfcRefDist> nfcRefDist_;
    boost::optional<ScreenRef> screenRef_;
    boost::optional<Normalization> normalization_;
    boost::optional<Equation> equation_;
  };

  // ---- Implementation ---- //

  template <typename... Parameters>
  AudioBlockFormatHoa::AudioBlockFormatHoa(Order order, Degree degree,
                                           Parameters... optionalNamedArgs) {
    set(order);
    set(degree);
    detail::setNamedOptionHelper(
        this, std::forward<Parameters>(optionalNamedArgs)...);
  }

  template <typename Parameter>
  Parameter AudioBlockFormatHoa::get() const {
    typedef typename detail::ParameterTraits<Parameter>::tag Tag;
    return get(Tag());
  }

  template <typename Parameter>
  bool AudioBlockFormatHoa::has() const {
    typedef typename detail::ParameterTraits<Parameter>::tag Tag;
    return has(Tag());
  }

  template <typename Parameter>
  bool AudioBlockFormatHoa::isDefault() const {
    typedef typename detail::ParameterTraits<Parameter>::tag Tag;
    return isDefault(Tag());
  }

  template <typename Parameter>
  void AudioBlockFormatHoa::unset() {
    typedef typename detail::ParameterTraits<Parameter>::tag Tag;
    return unset(Tag());
  }

}  // namespace adm