#define BOOST_TEST_MODULE GEOJSONVT

#include "Clipper.h"
#include "Simplifier.h"
#include "MBVTLayerEncoder.h"
#include "MBVTTileBuilder.h"

#include "mapnikvt/mbvtpackage/MBVTPackage.pb.h"

#include <picojson/picojson.h>

#include <boost/math/constants/constants.hpp>
#include <boost/test/included/unit_test.hpp>

using namespace carto::mbvtbuilder;

static std::vector<std::vector<cglib::vec2<float>>> decodeGeometry(const vector_tile::Tile::Feature& feature, float scale = 1.0f / 4096.0f) {
    std::vector<std::vector<cglib::vec2<float>>> verticesList;
    int cx = 0, cy = 0;
    int cmd = 0, length = 0;
    std::vector<cglib::vec2<float>> vertices;
    vertices.reserve(feature.geometry_size());
    for (int i = 0; i < feature.geometry_size(); ) {
        if (length == 0) {
            int cmdLength = feature.geometry(i++);
            length = cmdLength >> 3;
            cmd = cmdLength & 7;
            if (length == 0) {
                continue;
            }
        }

        length--;
        if ((cmd == 1 || cmd == 2) && i + 2 <= feature.geometry_size()) {
            if (cmd == 1) {
                if (!vertices.empty()) {
                    verticesList.emplace_back();
                    std::swap(verticesList.back(), vertices);
                }
            }
            int dx = feature.geometry(i++);
            int dy = feature.geometry(i++);
            dx = ((dx >> 1) ^ (-(dx & 1)));
            dy = ((dy >> 1) ^ (-(dy & 1)));
            cx += dx;
            cy += dy;
            vertices.emplace_back(static_cast<float>(cx) * scale, static_cast<float>(cy) * scale);
        }
        else if (cmd == 7) {
            if (!vertices.empty()) {
                if (vertices.front() != vertices.back()) {
                    cglib::vec2<float> p = vertices.front();
                    vertices.emplace_back(p);
                }
            }
        }
    }
    if (!vertices.empty()) {
        verticesList.emplace_back();
        std::swap(verticesList.back(), vertices);
    }

    return verticesList;
}

static cglib::vec2<double> wgs84ToWM(const cglib::vec2<double>& posWgs84) {
    static constexpr double EARTH_RADIUS = 6378137.0;
    static constexpr double PI = boost::math::constants::pi<double>();

    double x = EARTH_RADIUS * posWgs84(0) * PI / 180.0;
    double a = posWgs84(1) * PI / 180.0;
    double y = 0.5 * EARTH_RADIUS * std::log((1.0 + std::sin(a)) / (1.0 - std::sin(a)));
    return cglib::vec2<double>(x, y);
}

// Test clipper for different primitives
BOOST_AUTO_TEST_CASE(clipper) {
    typedef cglib::vec2<double> Point;

    Clipper<double> clipper(cglib::bbox2<double>({ -1.0,-1.0 }, { 1.0, 1.0 }));

    {
        BOOST_CHECK(clipper.testPoint({ -0.9, 0.9 }));
        BOOST_CHECK(clipper.testPoint({ -1, 1 }));
        BOOST_CHECK(!clipper.testPoint({ -1.1, 1 }));
    }

    {
        BOOST_CHECK((clipper.clipLineString(std::vector<Point> { { -1, 0 }, { 1, 0 } }) == std::vector<std::vector<Point>> { { { -1, 0 }, { 1, 0 } } }));
        BOOST_CHECK((clipper.clipLineString(std::vector<Point> { { -2, 0 }, { 2, 0 } }) == std::vector<std::vector<Point>> { { { -1, 0 }, { 1, 0 } } }));
        BOOST_CHECK((clipper.clipLineString(std::vector<Point> { { -2, -2 }, { 2, 2 } }) == std::vector<std::vector<Point>> { { { -1, -1 }, { 1, 1 } } }));;
        BOOST_CHECK((clipper.clipLineString(std::vector<Point> { { -4, 0 }, { 0, 2 } }) == std::vector<std::vector<Point>> { }));
        BOOST_CHECK((clipper.clipLineString(std::vector<Point> { { -4, 0 }, { 0, 0 }, { 4, 0.5 }, { 0, 0.5 } }) == std::vector<std::vector<Point>> { { { -1, 0 }, { 0, 0 }, { 1, 0.125 } }, { { 1, 0.5 }, { 0, 0.5 } } }));
    }

    {
        BOOST_CHECK((clipper.clipPolygonRing(std::vector<Point> { { -1, 0 }, { 1, 0 }, { 0, 1 } }) == std::vector<Point> { { -1, 0 }, { 1, 0 }, { 0, 1 } }));
        BOOST_CHECK((clipper.clipPolygonRing(std::vector<Point> { { -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 } }) == std::vector<Point> { { -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 } }));
        BOOST_CHECK((clipper.clipPolygonRing(std::vector<Point> { { -2, -2 }, { -2, 2 }, { 2, 2 }, { 2, -2 } }) == std::vector<Point> { { -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 } }));
        BOOST_CHECK((clipper.clipPolygonRing(std::vector<Point> { { -2, 0 }, { 0, 2 }, { 2, 0 }, { 0, -2 } }) == std::vector<Point> { { -1, -1 }, { -1, 1 }, { 1, 1 }, { 1, -1 }, { 0, -1 } }));
    }
}

// Test simplifier for line strings
BOOST_AUTO_TEST_CASE(simplifier) {
    typedef cglib::vec2<double> Point;

    Simplifier<double> simplifier(1.0);

    {
        BOOST_CHECK((simplifier.simplifyLineString(std::vector<Point> { { -1, 0 }, { 1, 0 } }) == std::vector<Point> { { -1, 0 }, { 1, 0 } }));
        BOOST_CHECK((simplifier.simplifyLineString(std::vector<Point> { { -1, 0 }, { 1, 0 }, { 2, 0 } }) == std::vector<Point> { { -1, 0 }, { 2, 0 } }));
        BOOST_CHECK((simplifier.simplifyLineString(std::vector<Point> { { -1, 0 }, { 1, 1 }, { 2, 0 } }) == std::vector<Point> { { -1, 0 }, { 1, 1 }, { 2, 0 } }));
        BOOST_CHECK((simplifier.simplifyLineString(std::vector<Point> { { -1, 0 }, { 1, 2 }, { 2, 0 } }) == std::vector<Point> { { -1, 0 }, { 1, 2 }, { 2, 0 } }));
        BOOST_CHECK((simplifier.simplifyLineString(std::vector<Point> { { -1, 0 }, { 1, 0.5 }, { 2, 0 } }) == std::vector<Point> { { -1, 0 }, { 2, 0 } }));
    }
}

// Test that properties are encoded correctly in vector tiles
BOOST_AUTO_TEST_CASE(propertiesEncoding) {
    std::vector<vector_tile::Tile_Layer> layers;

    auto encodeAndDecodeValue = [&layers](const picojson::value& value) -> vector_tile::Tile_Value {
        MBVTLayerEncoder encoder("");
        encoder.addMultiPoint(0, { cglib::vec2<float>(0, 0) }, picojson::value(picojson::object({ { "_key_", value } })));
        auto encodedMsg = encoder.buildLayer();
        protobuf::message msg(encodedMsg.data().data(), encodedMsg.data().size());
        layers.emplace_back(msg);
        BOOST_ASSERT(layers.back().keys().at(0) == "_key_");
        return layers.back().values().at(0);
    };

    auto encodeAndDecodeKeyValue = [&layers](const std::string& key, const picojson::value& value) -> std::pair<std::string, std::string> {
        MBVTLayerEncoder encoder("");
        encoder.addMultiPoint(0, { cglib::vec2<float>(0, 0) }, picojson::value(picojson::object({ { key, value } })));
        auto encodedMsg = encoder.buildLayer();
        protobuf::message msg(encodedMsg.data().data(), encodedMsg.data().size());
        layers.emplace_back(msg);
        BOOST_ASSERT(layers.back().values().at(0).has_string_value());
        return std::make_pair(layers.back().keys().at(0), layers.back().values().at(0).string_value());
    };

    BOOST_CHECK(encodeAndDecodeValue(picojson::value(false)).bool_value() == false);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(true)).bool_value() == true);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(1LL)).uint_value() == 1);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(-1LL)).sint_value() == -1);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(-1564564561LL)).sint_value() == -1564564561);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(91564564561LL)).uint_value() == 91564564561);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(16.25)).float_value() == 16.25f);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value(99916.251111111)).double_value() == 99916.251111111);
    BOOST_CHECK(encodeAndDecodeValue(picojson::value("Test str")).string_value() == "Test str");

    BOOST_CHECK((encodeAndDecodeKeyValue("key1", picojson::value(picojson::object { { "a", picojson::value("XX") } })) == std::make_pair(std::string("key1.a"), std::string("XX"))));
    BOOST_CHECK((encodeAndDecodeKeyValue("key2", picojson::value(picojson::array { { picojson::value("XX") } })) == std::make_pair(std::string("key2[0]"), std::string("XX"))));
}

// Test that point geometry is encoded correctly in vector tiles
BOOST_AUTO_TEST_CASE(pointEncoding) {
    typedef cglib::vec2<float> Point;

    auto encodeAndDecodeGeometry = [](const std::vector<Point>& geom) -> std::vector<Point> {
        MBVTLayerEncoder encoder("");
        encoder.addMultiPoint(0, geom, picojson::value());
        auto encodedMsg = encoder.buildLayer();
        protobuf::message msg(encodedMsg.data().data(), encodedMsg.data().size());
        vector_tile::Tile_Layer layer(msg);
        BOOST_ASSERT(layer.features_size() == 1);
        auto decodedGeom = decodeGeometry(layer.features(0));
        std::vector<Point> points;
        for (auto& decodedPoint : decodedGeom) {
            BOOST_ASSERT(decodedPoint.size() == 1);
            points.push_back(decodedPoint.at(0));
        }
        return points;
    };

    BOOST_CHECK((encodeAndDecodeGeometry({ Point(0, 0) }) == std::vector<Point> { Point(0, 0) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(0, 1) }) == std::vector<Point> { Point(0, 1) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(-0.125f, 1.25f) }) == std::vector<Point> { Point(-0.125f, 1.25f) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(-0.125f, 1.25f), Point(-0.125f, 1.25f) }) == std::vector<Point> { Point(-0.125f, 1.25f), Point(-0.125f, 1.25f) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(-0.125f, 1.25f), Point(0.125f, -1.25f) }) == std::vector<Point> { Point(-0.125f, 1.25f), Point(0.125f, -1.25f) }));
}

// Test that linestring geometry is encoded correctly in vector tiles
BOOST_AUTO_TEST_CASE(lineStringEncoding) {
    typedef cglib::vec2<float> Point;

    auto encodeAndDecodeGeometry = [](const std::vector<Point>& geom) -> std::vector<Point> {
        MBVTLayerEncoder encoder("");
        encoder.addMultiLineString(0, { geom }, picojson::value());
        auto encodedMsg = encoder.buildLayer();
        protobuf::message msg(encodedMsg.data().data(), encodedMsg.data().size());
        vector_tile::Tile_Layer layer(msg);
        BOOST_ASSERT(layer.features_size() == 1);
        auto decodedGeom = decodeGeometry(layer.features(0));
        BOOST_ASSERT(decodedGeom.size() == 1);
        return decodedGeom.at(0);
    };

    BOOST_CHECK((encodeAndDecodeGeometry({ Point(0, 0), Point(1, 0) }) == std::vector<Point> { Point(0, 0), Point(1, 0) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(0, 0), Point(1, 0), Point(0, 1) }) == std::vector<Point> { Point(0, 0), Point(1, 0), Point(0, 1) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(0, 0), Point(1, 0), Point(1, 0) }) == std::vector<Point> { Point(0, 0), Point(1, 0) }));
    BOOST_CHECK((encodeAndDecodeGeometry({ Point(-0.125f, 1.25f), Point(0.125f, -1.25f) }) == std::vector<Point> { Point(-0.125f, 1.25f), Point(0.125f, -1.25f) }));
}

// Test that polygon geometry is encoded correctly in vector tiles
BOOST_AUTO_TEST_CASE(polygonEncoding) {
    typedef cglib::vec2<float> Point;

    auto encodeAndDecodeGeometry = [](const std::vector<std::vector<Point>>& geom) -> std::vector<std::vector<Point>> {
        MBVTLayerEncoder encoder("");
        encoder.addMultiPolygon(0, geom, picojson::value());
        auto encodedMsg = encoder.buildLayer();
        protobuf::message msg(encodedMsg.data().data(), encodedMsg.data().size());
        vector_tile::Tile_Layer layer(msg);
        BOOST_ASSERT(layer.features_size() == 1);
        auto decodedGeom = decodeGeometry(layer.features(0));
        BOOST_ASSERT(decodedGeom.size() == 1);
        return decodedGeom;
    };

    BOOST_CHECK((encodeAndDecodeGeometry({ { Point(0, 0), Point(1, 0), Point(0, 1) } }) == std::vector<std::vector<Point>> { { Point(0, 0), Point(1, 0), Point(0, 1), Point(0, 0) } }));
    BOOST_CHECK((encodeAndDecodeGeometry({ { Point(0, 0), Point(1, 0), Point(1, 0), Point(0, 1) } }) == std::vector<std::vector<Point>> { { Point(0, 0), Point(1, 0), Point(0, 1), Point(0, 0) } }));
    BOOST_CHECK((encodeAndDecodeGeometry({ { Point(1, 1), Point(-0.125f, 1.25f), Point(0.125f, -1.25f) } }) == std::vector<std::vector<Point>> { { Point(1, 1), Point(-0.125f, 1.25f), Point(0.125f, -1.25f), Point(1, 1) } }));
}

// Test high-level tile builder functionality
BOOST_AUTO_TEST_CASE(tileBuilder) {
    typedef cglib::vec2<double> Point;

    {
        MBVTTileBuilder tileBuilder(18, 18);
        tileBuilder.addMultiPoint(0, { Point(0, 0) }, picojson::value());
        std::vector<protobuf::encoded_message> encodedTiles;
        tileBuilder.buildTiles([&encodedTiles](int zoom, int tileX, int tileY, const protobuf::encoded_message& encodedTile) { encodedTiles.push_back(encodedTile); });
        BOOST_CHECK(encodedTiles.size() == 1);
    }

    {
        MBVTTileBuilder tileBuilder(18, 18);
        tileBuilder.addMultiPoint(0, { Point(0, 0), Point(1, 1) }, picojson::value());
        std::vector<protobuf::encoded_message> encodedTiles;
        tileBuilder.buildTiles([&encodedTiles](int zoom, int tileX, int tileY, const protobuf::encoded_message& encodedTile) { encodedTiles.push_back(encodedTile); });
        BOOST_CHECK(encodedTiles.size() == 1);
    }

    {
        MBVTTileBuilder tileBuilder(18, 18);
        auto layerIndex = tileBuilder.createLayer("", 0.0000001f);
        tileBuilder.addMultiPoint(layerIndex, { Point(0.5, 0.5), Point(100000, 1) }, picojson::value());
        std::vector<protobuf::encoded_message> encodedTiles;
        tileBuilder.buildTiles([&encodedTiles](int zoom, int tileX, int tileY, const protobuf::encoded_message& encodedTile) { encodedTiles.push_back(encodedTile); });
        BOOST_CHECK(encodedTiles.size() == 2);
    }

    {
        MBVTTileBuilder tileBuilder(18, 18);
        auto layerIndex = tileBuilder.createLayer("", 0.0000001f);
        tileBuilder.addMultiLineString(layerIndex, { { Point(0.5, 0.5), Point(100000, 1) } }, picojson::value());
        std::vector<protobuf::encoded_message> encodedTiles;
        tileBuilder.buildTiles([&encodedTiles](int zoom, int tileX, int tileY, const protobuf::encoded_message& encodedTile) { encodedTiles.push_back(encodedTile); });
        BOOST_CHECK(encodedTiles.size() == 655);
    }

    {
        MBVTTileBuilder tileBuilder(18, 18);
        auto layerIndex = tileBuilder.createLayer("", 0.0000001f);
        tileBuilder.addMultiLineString(layerIndex, { { Point(0.5, 0.5), Point(10000, 10000) } }, picojson::value());
        std::vector<protobuf::encoded_message> encodedTiles;
        tileBuilder.buildTiles([&encodedTiles](int zoom, int tileX, int tileY, const protobuf::encoded_message& encodedTile) { encodedTiles.push_back(encodedTile); });
        BOOST_CHECK(encodedTiles.size() == 196);
    }

    {
        MBVTTileBuilder tileBuilder(18, 18);
        auto layerIndex = tileBuilder.createLayer("", 0.0000001f);
        tileBuilder.addMultiPolygon(layerIndex, { { { Point(0.5, 0.5), Point(0.5, 10000), Point(10000, 5000) } } }, picojson::value());
        std::vector<protobuf::encoded_message> encodedTiles;
        tileBuilder.buildTiles([&encodedTiles](int zoom, int tileX, int tileY, const protobuf::encoded_message& encodedTile) { encodedTiles.push_back(encodedTile); });
        BOOST_CHECK(encodedTiles.size() == 2211);
    }
}

// Test high-level tile builder functionality, simplifier part
BOOST_AUTO_TEST_CASE(tileBuilderSimplifier) {
    typedef cglib::vec2<double> Point;

    {
        MBVTTileBuilder tileBuilder(0, 18);
        std::vector<Point> coords;
        for (int i = 0; i < 10000; i++) {
            double a = static_cast<double>(i) / 10000 * std::atan(1.0) * 8;
            double r = 80;
            coords.emplace_back(wgs84ToWM(cglib::vec2<double>(std::cos(a), std::sin(a)) * r));
        }
        tileBuilder.addMultiPolygon(0, { { coords } }, picojson::value());
        protobuf::encoded_message encodedTile;
        tileBuilder.buildTile(0, 0, 0, encodedTile);
    }
}


// Copyright (C) 2015, Pawel Tomulik <ptomulik@meil.pw.edu.pl>
//
// 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)
#define BOOST_TEST_MODULE test_txpl_vm_eval_binary_mul

#include <txpl/test_config.hpp>
#include <boost/test/unit_test.hpp>

#ifndef TXPL_TEST_SKIP_VM_EVAL_BINARY_MUL

#include <txpl/vm/eval_binary_op.hpp>
#include <txpl/vm/basic_types.hpp>
#include <txpl/vm/value.hpp>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/get.hpp>

using namespace txpl::vm;
typedef basic_types<>::char_type   char_type;
typedef basic_types<>::int_type    int_type;
typedef basic_types<>::bool_type   bool_type;
typedef basic_types<>::real_type   real_type;
typedef basic_types<>::string_type string_type;
typedef basic_types<>::regex_type  regex_type;
typedef basic_types<>::blank_type  blank_type;
typedef array<value<> >            array_type;
typedef object<value<> >           object_type;

BOOST_AUTO_TEST_CASE(char__mul__char)
{
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'\2'};
    const value<> v2 = char_type{'\4'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (char_type{'\2'} * char_type{'\4'}));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'\2'};
    const value<> v2 = int_type{4};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (char_type{'\2'} * int_type{4}));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'\3'};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (char_type{'\3'} * bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'\2'};
    const value<> v2 = real_type{2.1};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (char_type{'\2'} * real_type{2.1}));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(char__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = char_type{'a'};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(int__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{2};
    const value<> v2 = char_type{'\3'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (int_type{2} * char_type{'\3'}));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{2};
    const value<> v2 = int_type{3};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (int_type{2} * int_type{3}));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{3};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (int_type{3} * bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{12};
    const value<> v2 = real_type{3.21};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (int_type{12} * real_type{3.21}));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(int__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = int_type{0};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(bool__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = char_type{'\3'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (bool_type{true} * char_type{'\3'}));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = int_type{3};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (bool_type{true} * int_type{3}));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    int_type x = int_type{654};
    BOOST_CHECK_NO_THROW(x = boost::get<int_type>(r));
    BOOST_CHECK(x == (bool_type{true} * bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{true};
    const value<> v2 = real_type{2.12};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (bool_type{true} * real_type{2.12}));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(bool__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = bool_type{false};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(real__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = char_type{'\3'};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} * char_type{'\3'}));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = int_type{3};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} * int_type{3}));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = bool_type{true};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} * bool_type{true}));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{1.2};
    const value<> v2 = real_type{3.4};
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    real_type x = real_type{.654};
    BOOST_CHECK_NO_THROW(x = boost::get<real_type>(r));
    BOOST_CHECK(x == (real_type{1.2} * real_type{3.4}));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(real__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = real_type{0.0};
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(string__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = char_type{'a'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type("asd");
    const value<> v2 = string_type{"qwer"};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(string__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = string_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(regex__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = char_type{'\0'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(regex__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = regex_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(array__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = char_type{'\0'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<array_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(array__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = array_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

BOOST_AUTO_TEST_CASE(object__mul__char)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = char_type{'\0'};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__int)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = int_type{0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__bool)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = bool_type{false};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__real)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = real_type{0.0};
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__string)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = string_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__regex)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = regex_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__array)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = array_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}
BOOST_AUTO_TEST_CASE(object__mul__object)
{
  using namespace txpl::vm;
  value<> r;
  auto op = eval_binary_op<op_t::mul_>(r);
  {
    const value<> v1 = object_type();
    const value<> v2 = object_type();
    r = blank_type();
    BOOST_CHECK(!boost::apply_visitor(op, v1, v2));
    BOOST_CHECK_NO_THROW(boost::get<blank_type>(r));
  }
}

#else
BOOST_AUTO_TEST_CASE(dummy)
{
  BOOST_CHECK(true);
}
#endif


//
// Copyright (c) 2018-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// 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)
//
// Official repository: https://github.com/boostorg/beast
//

#ifndef BOOST_BEAST_JSON_IMPL_NUMBER_IPP
#define BOOST_BEAST_JSON_IMPL_NUMBER_IPP

#include <boost/beast/_experimental/json/number.hpp>
#include <boost/beast/core/static_string.hpp>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <ostream>
#include <string>
#include <vector>

namespace boost {
namespace beast {
namespace json {

number::
number(
    mantissa_type mant,
    exponent_type exp,
    bool negative) noexcept
    : mant_(mant)
    , exp_(exp)
    , neg_(negative)
{
    //normalize();
}

//------------------------------------------------------------------------------

number::
number(short v) noexcept
{
    assign(v);
}

number::
number(int v) noexcept
{
    assign(v);
}

number::
number(long v) noexcept
{
    assign(v);
}

number::
number(long long v) noexcept
{
    assign(v);
}

number::
number(unsigned short v) noexcept
{
    assign(v);
}

number::
number(unsigned int v) noexcept
{
    assign(v);
}

number::
number(unsigned long v) noexcept
{
    assign(v);
}

number::
number(unsigned long long v) noexcept
{
    assign(v);
}

number::
number(float v) noexcept
{
    assign(v);
}

number::
number(double v) noexcept
{
    assign(v);
}

number::
number(long double v) noexcept
{
    assign(v);
}

//------------------------------------------------------------------------------

bool
number::
is_int64() const noexcept
{
    if(exp_ != 0)
        return false;
    if(neg_)
        return mant_ <= static_cast<
            mantissa_type>(
                (std::numeric_limits<
                    std::int64_t>::max)()) + 1;
    return mant_ <= static_cast<
        mantissa_type>(
            (std::numeric_limits<
                std::int64_t>::max)());
}

bool
number::
is_uint64() const noexcept
{
    if(exp_ != 0)
        return false;
    return true;
}

std::int_least64_t
number::
get_int64() const noexcept
{
    if(neg_)
        return -static_cast<
            std::int_least64_t>(mant_);
    return static_cast<
        std::int_least64_t>(mant_);
}

std::uint_least64_t
number::
get_uint64() const noexcept
{
    return mant_;
}

long double
number::
get_double() const noexcept
{
    static exponent_type const bias =
        static_cast<exponent_type>(std::floor(
            std::log10((std::numeric_limits<
                mantissa_type>::max)()))) - 2;
    long double v =
        static_cast<long double>(mant_);
    if(exp_ < -bias)
    {
        v = (v * std::pow(10, -bias)) *
            std::pow(10, exp_ + bias);
    }
    else
    {
        v = v * std::pow(10, exp_);
    }
    if(neg_)
        return -v;
    return v;
}

bool
operator==(
    number const& lhs,
    number const& rhs) noexcept
{
    return
        lhs.mantissa() == rhs.mantissa() &&
        lhs.exponent() == rhs.exponent() &&
        lhs.is_negative() == rhs.is_negative();
}

bool
operator!=(
    number const& lhs,
    number const& rhs) noexcept
{
    return
        lhs.mantissa() != rhs.mantissa() ||
        lhs.exponent() != rhs.exponent() ||
        lhs.is_negative() != rhs.is_negative();
}

string_view
number::
print(char* dest) const noexcept
{
    if(mant_ == 0)
    {
        BOOST_ASSERT(exp_ == 0);
        BOOST_ASSERT(! neg_);
        *dest = '0';
        return { dest, 1 };
    }
    auto const start = dest;
    auto const tab = pow10::get();
    auto dig =
        std::upper_bound(
            tab.begin(),
            tab.end(),
            mant_) -
        tab.begin();
    if(neg_)
        *dest++ = '-';
    auto rp = dest + dig;
    dest = rp;
    auto mant = mant_;
    while(mant > 0)
    {
        *--rp = '0' + mant % 10;
        mant /= 10;
    }
    auto exp = static_cast<int>(exp_);
    if(exp != 0)
    {
        *dest++ = 'e';
        if(exp < 0)
        {
            *dest++ = '-';
            exp = std::abs(exp);
        }
    }
    dig =
        std::upper_bound(
            tab.begin(),
            tab.end(),
            exp) -
        tab.begin();
    rp = dest + dig;
    dest = rp;
    while(exp > 0)
    {
        *--rp = '0' + exp % 10;
        exp /= 10;
    }
    return { start, static_cast<
        std::size_t>(dest - start) };
}

//------------------------------------------------------------------------------

auto
number::
pow10::
get() noexcept ->
    pow10 const&
{
    struct pow10_impl : pow10
    {
        pow10_impl()
        {
            static mantissa_type constexpr list[] = {
                1ULL,
                10ULL,
                100ULL,
                1000ULL,
                10000ULL,
                100000ULL,
                1000000ULL,
                10000000ULL,
                100000000ULL,
                1000000000ULL,
                10000000000ULL,
                100000000000ULL,
                1000000000000ULL,
                10000000000000ULL,
                100000000000000ULL,
                1000000000000000ULL,
                10000000000000000ULL,
                100000000000000000ULL,
                1000000000000000000ULL,
                10000000000000000000ULL
            };
            size_ = std::extent<
                decltype(list)>::value;
            begin_ = &list[0];
            end_ = &list[size_];
        }
    };

    static pow10_impl const tab;
    return tab;
}

void
number::
normalize() noexcept
{
    if(mant_ == 0)
        return;
    while(mant_ % 10 == 0)
    {
        mant_ /= 10;
        ++exp_;
    }
}

template<class I>
void
number::
assign(I v
    ,typename std::enable_if<
        std::is_signed<I>::value &&
        std::is_integral<I>::value
            >::type*) noexcept
{
    BOOST_STATIC_ASSERT(
        sizeof(I) <= sizeof(mantissa_type));
    if(v >= 0)
    {
        neg_ = false;
        mant_ = v;
    }
    else if(v != (std::numeric_limits<I>::min)())
    {
        neg_ = true;
        mant_ = static_cast<
            mantissa_type>(std::abs(v));
    }
    else
    {
        neg_ = true;
        mant_ = (std::numeric_limits<I>::max)();
        mant_ += 1;
    }
    exp_ = 0;
}

template<class U>
void
number::
assign(U v
    ,typename std::enable_if<
        std::is_unsigned<U>::value
            >::type*) noexcept
{
    BOOST_STATIC_ASSERT(
        sizeof(U) <= sizeof(mantissa_type));
    mant_ = v;
    exp_ = 0;
    neg_ = false;
}

template<class F>
void
number::
assign(F v
    ,typename std::enable_if<
        std::is_floating_point<
        F>::value>::type*) noexcept
{
/*
    Ryu's algorithm is Boost licensed and performs
    this operation with more speed and more precision:

    https://github.com/ulfjack/ryu/blob/41910f966fdc2307a279ef645de976e0fc431b48/ryu/d2s.c#L211
*/
    if(v < 0)
    {
        v = std::abs(v);
        neg_ = true;
    }
    else
    {
        neg_ = false;
    }
    static exponent_type const bias =
        static_cast<exponent_type>(std::floor(
            std::log10((std::numeric_limits<
                mantissa_type>::max)()))) - 2;
    exp_ = static_cast<exponent_type>(
        std::floor(std::log10(v)));
    auto mant = static_cast<long double>(v) /
        std::pow(10, exp_);
    mant_ = static_cast<mantissa_type>(
        mant * std::pow(10, bias));
    exp_ -= bias;
    normalize();
}

//------------------------------------------------------------------------------

std::ostream&
operator<<(std::ostream& os, number const& n)
{
    char buf[number::max_string_chars];
    os << n.print(buf);
    return os;
}

} // json
} // beast
} // boost

#endif


#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/copy_const_reference.hpp>

#include <scitbx/vec3.h>

#include <mmtbx/geometry/primitive.hpp>

namespace mmtbx
{

namespace geometry
{

namespace primitive
{

namespace
{

template < typename Vector >
struct primitive_wrappers
{
  static void wrap()
  {
    using namespace boost::python;
    using namespace mmtbx::geometry::primitive;

    typedef typename Traits< Vector >::vector_type vector_type;
    typedef typename Traits< Vector >::value_type value_type;

    class_< Sphere< Vector > >( "sphere", no_init )
      .def(
        init< const vector_type&, const value_type& >(
          ( arg( "centre" ), arg( "radius" ) )
          )
        )
      .add_property(
        "centre",
        make_function(
          &Sphere< Vector >::centre,
          return_value_policy< copy_const_reference >()
          )
        )
      .add_property(
        "radius",
        make_function(
          &Sphere< Vector >::radius,
          return_value_policy< copy_const_reference >()
          )
        )
      .add_property(
        "radius_sq",
        make_function(
          &Sphere< Vector >::radius_sq,
          return_value_policy< copy_const_reference >()
          )
        )
      ;

    class_< Box< Vector > >( "box", no_init )
      .add_property(
        "low",
        make_function(
          &Box< Vector >::low,
          return_value_policy< copy_const_reference >()
          )
        )
      .add_property(
        "high",
        make_function(
          &Box< Vector >::high,
          return_value_policy< copy_const_reference >()
          )
        )
      .def(
        "from_corners",
        &Box< Vector >::from_corners,
        ( arg( "corner1" ), arg( "corner2" ) )
        )
      .staticmethod( "from_corners" )
      .def(
        "around_sphere",
        &Box< Vector >::around_sphere,
        ( arg( "centre" ), arg( "radius" ) )
        )
      .staticmethod( "around_sphere" )
      ;

    class_< BSphere< Vector >, bases< Sphere< Vector >, Box< Vector > > >(
      "bsphere",
      no_init
      )
      .def(
        init< const vector_type&, const value_type& >(
          ( arg( "centre" ), arg( "radius" ) )
          )
        )
      ;
  }
};

void init_module()
{
  primitive_wrappers< scitbx::vec3< double > >::wrap();
}

} // namespace <anonymous>
} // namespace primitive
} // namespace geometry
} // namespace mmtbx

BOOST_PYTHON_MODULE(mmtbx_geometry_primitive_ext)
{
  mmtbx::geometry::primitive::init_module();
}


//  (C) Copyright John Maddock 2006.
//  Use, modification and distribution are subject to 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)

#if 1
#include <boost/math/bindings/rr.hpp>
#include <boost/test/included/test_exec_monitor.hpp>
#include <boost/math/special_functions/beta.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/tools/test.hpp>
#include <boost/lexical_cast.hpp>
#include <fstream>

#include <boost/math/tools/test_data.hpp>

using namespace boost::math::tools;

//
// Force trunctation to float precision of input values:
// we must ensure that the input values are exactly representable
// in whatever type we are testing, or the output values will all
// be thrown off:
//
float external_f;
float force_truncate(const float* f)
{
   external_f = *f;
   return external_f;
}

float truncate_to_float(boost::math::ntl::RR r)
{
   float f = boost::math::tools::real_cast<float>(r);
   return force_truncate(&f);
}

std::tr1::mt19937 rnd;
std::tr1::uniform_real<float> ur_a(1.0F, 5.0F);
std::tr1::variate_generator<std::tr1::mt19937, std::tr1::uniform_real<float> > gen(rnd, ur_a);
std::tr1::uniform_real<float> ur_a2(0.0F, 100.0F);
std::tr1::variate_generator<std::tr1::mt19937, std::tr1::uniform_real<float> > gen2(rnd, ur_a2);

struct ibeta_inv_data_generator
{
   boost::math::tuple<boost::math::ntl::RR, boost::math::ntl::RR, boost::math::ntl::RR, boost::math::ntl::RR, boost::math::ntl::RR> operator()(boost::math::ntl::RR ap, boost::math::ntl::RR bp, boost::math::ntl::RR x_)
   {
      float a = truncate_to_float(real_cast<float>(gen() * pow(boost::math::ntl::RR(10), ap)));
      float b = truncate_to_float(real_cast<float>(gen() * pow(boost::math::ntl::RR(10), bp)));
      float x = truncate_to_float(real_cast<float>(x_));
      std::cout << a << " " << b << " " << x << std::flush;
      boost::math::ntl::RR inv = boost::math::ibeta_inv(boost::math::ntl::RR(a), boost::math::ntl::RR(b), boost::math::ntl::RR(x));
      std::cout << " " << inv << std::flush;
      boost::math::ntl::RR invc = boost::math::ibetac_inv(boost::math::ntl::RR(a), boost::math::ntl::RR(b), boost::math::ntl::RR(x));
      std::cout << " " << invc << std::endl;
      return boost::math::make_tuple(a, b, x, inv, invc);
   }
};

int test_main(int argc, char*argv [])
{
   boost::math::ntl::RR::SetPrecision(1000);
   boost::math::ntl::RR::SetOutputPrecision(100);

   bool cont;
   std::string line;

   parameter_info<boost::math::ntl::RR> arg1, arg2, arg3;
   test_data<boost::math::ntl::RR> data;

   std::cout << "Welcome.\n"
      "This program will generate spot tests for the inverse incomplete beta function:\n"
      "  ibeta_inv(a, p) and ibetac_inv(a, q)\n\n";

   arg1 = make_periodic_param(boost::math::ntl::RR(-5), boost::math::ntl::RR(6), 11);
   arg2 = make_periodic_param(boost::math::ntl::RR(-5), boost::math::ntl::RR(6), 11);
   arg3 = make_random_param(boost::math::ntl::RR(0.0001), boost::math::ntl::RR(1), 10);

   arg1.type |= dummy_param;
   arg2.type |= dummy_param;
   arg3.type |= dummy_param;

   data.insert(ibeta_inv_data_generator(), arg1, arg2, arg3);

   line = "ibeta_inv_data.ipp";
   std::ofstream ofs(line.c_str());
   write_code(ofs, data, "ibeta_inv_data");

   return 0;
}

#endif


/******************************************************************************
**
** Copyright (C) 2009-2011 Kyle Lutz <kyle.r.lutz@gmail.com>
** All rights reserved.
**
** This file is a part of the chemkit project. For more information
** see <http://www.chemkit.org>.
**
** 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 chemkit project 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
** OWNER 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 "uffcalculation.h"

#include <boost/algorithm/string.hpp>

#include "uffatomtyper.h"
#include "uffforcefield.h"
#include "uffparameters.h"

#include <chemkit/topology.h>
#include <chemkit/constants.h>
#include <chemkit/cartesiancoordinates.h>

// === UffCalculation ====================================================== //
UffCalculation::UffCalculation(int type, int atomCount, int parameterCount)
    : ForceFieldCalculation(type, atomCount, parameterCount)
{
}

// Returns the parameters for the given atom.
const UffAtomParameters* UffCalculation::parameters(const std::string &type) const
{
    const UffForceField *forceField = static_cast<const UffForceField *>(this->forceField());

    return forceField->parameters()->parameters(type);
}

// Returns the bond order of the bond between atom's a and b. If both
// atoms have a resonant type the bond order returned is 1.5.
// Otherwise the integer value of the bond order is returned.
chemkit::Real UffCalculation::bondOrder(size_t a, size_t b) const
{
    const boost::shared_ptr<chemkit::Topology> &topology = this->topology();

    int type = topology->bondedInteractionType(a, b);

    if(type == UffAtomTyper::Resonant){
        return 1.5; // resonant
    }
    else{
        return type;
    }
}

// Returns the length of the bond between two atoms.
chemkit::Real UffCalculation::bondLength(const UffAtomParameters *a, const UffAtomParameters *b, chemkit::Real bondOrder) const
{
    // r_ij = r_i + r_j + r_bo - r_en
    chemkit::Real r_bo = -0.1332 * (a->r + b->r) * log(bondOrder);
    chemkit::Real r_en = ((a->r * b->r) * pow((sqrt(a->X) - sqrt(b->X)), 2)) / (a->X*a->r + b->X*b->r);

    chemkit::Real r_ij = a->r + b->r + r_bo - r_en;

    return r_ij;
}

// === UffBondStrechCalculation ============================================ //
UffBondStrechCalculation::UffBondStrechCalculation(size_t a, size_t b)
    : UffCalculation(BondStrech, 2, 2)
{
    setAtom(0, a);
    setAtom(1, b);
}

bool UffBondStrechCalculation::setup()
{
    const UffAtomParameters *pa = parameters(atomType(0));
    const UffAtomParameters *pb = parameters(atomType(1));

    if(!pa || !pb){
        return false;
    }

    // n = bondorder (1.5 for aromatic, 1.366 for amide)
    chemkit::Real bondorder = bondOrder(atom(0), atom(1));

    chemkit::Real r0 = bondLength(pa, pb, bondorder);

    // parameter(1) = k_ij = 664.12 * (Z*_i * Z*_j) / r_ij^3
    chemkit::Real za = pa->Z;
    chemkit::Real zb = pb->Z;
    chemkit::Real kb = 664.12 * (za * zb) / pow(r0, 3);

    setParameter(0, kb);
    setParameter(1, r0);

    return true;
}

chemkit::Real UffBondStrechCalculation::energy(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);

    chemkit::Real kb = parameter(0);
    chemkit::Real r0 = parameter(1);
    chemkit::Real r = coordinates->distance(a, b);

    return 0.5 * kb * pow(r - r0, 2);
}

std::vector<chemkit::Vector3> UffBondStrechCalculation::gradient(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);

    chemkit::Real kb = parameter(0);
    chemkit::Real r0 = parameter(1);
    chemkit::Real r = coordinates->distance(a, b);

    // dE/dr
    chemkit::Real de_dr = kb * (r - r0);

    boost::array<chemkit::Vector3, 2> gradient = coordinates->distanceGradient(a, b);

    gradient[0] *= de_dr;
    gradient[1] *= de_dr;

    return std::vector<chemkit::Vector3>(gradient.begin(), gradient.end());
}

// === UffAngleBendCalculation ============================================= //
UffAngleBendCalculation::UffAngleBendCalculation(size_t a, size_t b, size_t c)
    : UffCalculation(AngleBend, 3, 4)
{
    setAtom(0, a);
    setAtom(1, b);
    setAtom(2, c);
}

bool UffAngleBendCalculation::setup()
{
    const UffAtomParameters *pa = parameters(atomType(0));
    const UffAtomParameters *pb = parameters(atomType(1));
    const UffAtomParameters *pc = parameters(atomType(2));

    if(!pa || !pb || !pc){
        return false;
    }

    chemkit::Real theta0 = pb->theta * chemkit::constants::DegreesToRadians;

    chemkit::Real bo_ij = bondOrder(atom(0), atom(1));
    chemkit::Real bo_jk = bondOrder(atom(1), atom(2));

    chemkit::Real r_ab = bondLength(pa, pb, bo_ij);
    chemkit::Real r_bc = bondLength(pb, pc, bo_jk);
    chemkit::Real r_ac = sqrt(pow(r_ab, 2)  + pow(r_bc, 2) - (2.0 * r_ab * r_bc * cos(theta0)));

    chemkit::Real beta = 664.12 / (r_ab * r_bc);

    chemkit::Real z_a = pa->Z;
    chemkit::Real z_c = pc->Z;

    // equation 13
    chemkit::Real ka = beta * ((z_a * z_c) / pow(r_ac, 5)) * r_ab * r_bc * (3.0 * r_ab * r_bc * (1.0 - pow(cos(theta0), 2.0)) - (pow(r_ac, 2.0) * cos(theta0)));

    setParameter(0, ka);

    chemkit::Real sinTheta0 = sin(theta0);

    // clamp sin(theta0) to 1e-3 because for some atoms theta0 == pi which
    // would lead to a division by zero error when calculating c2 below
    if(std::abs(sinTheta0) < 1e-3){
        sinTheta0 = 1e-3;
    }

    chemkit::Real c2 = 1 / (4 * pow(sinTheta0, 2));
    chemkit::Real c1 = -4 * c2 * cos(theta0);
    chemkit::Real c0 = c2 * (2 * pow(cos(theta0), 2) + 1);

    setParameter(1, c0);
    setParameter(2, c1);
    setParameter(3, c2);

    return true;
}

chemkit::Real UffAngleBendCalculation::energy(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);

    chemkit::Real ka = parameter(0);
    chemkit::Real c0 = parameter(1);
    chemkit::Real c1 = parameter(2);
    chemkit::Real c2 = parameter(3);

    chemkit::Real theta = coordinates->angleRadians(a, b, c);

    return ka * (c0 + (c1 * cos(theta)) + (c2 * cos(2*theta)));
}

std::vector<chemkit::Vector3> UffAngleBendCalculation::gradient(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);

    chemkit::Real ka = parameter(0);
    chemkit::Real c1 = parameter(2);
    chemkit::Real c2 = parameter(3);

    chemkit::Real theta = coordinates->angleRadians(a, b, c);

    // dE/dtheta
    chemkit::Real de_dtheta = -ka * (c1 * sin(theta) + 2 * c2 * sin(2 * theta));

    boost::array<chemkit::Vector3, 3> gradient = coordinates->angleGradientRadians(a, b, c);

    gradient[0] *= de_dtheta;
    gradient[1] *= de_dtheta;
    gradient[2] *= de_dtheta;

    return std::vector<chemkit::Vector3>(gradient.begin(), gradient.end());
}

// === UffTorsionCalculation =============================================== //
UffTorsionCalculation::UffTorsionCalculation(size_t a, size_t b, size_t c, size_t d)
    : UffCalculation(Torsion, 4, 3)
{
    setAtom(0, a);
    setAtom(1, b);
    setAtom(2, c);
    setAtom(3, d);
}

bool UffTorsionCalculation::setup()
{
    UffForceField *forceField = static_cast<UffForceField *>(this->forceField());
    const boost::shared_ptr<chemkit::Topology> &topology = this->topology();

    size_t b = atom(1);
    size_t c = atom(2);

    std::string typeB = topology->type(b);
    std::string typeC = topology->type(c);

    if(typeB.length() < 3 || typeC.length() < 3){
        return false;
    }

    const UffAtomParameters *pb = parameters(typeB);
    const UffAtomParameters *pc = parameters(typeC);

    chemkit::Real V = 0;
    chemkit::Real n = 0;
    chemkit::Real phi0 = 0;

    // sp3-sp3
    if(typeB[2] == '3' && typeC[2] == '3'){

        // exception for two group six atoms
        if(forceField->isGroupSix(atom(1)) && forceField->isGroupSix(atom(2))){
            if(boost::starts_with(typeB, "O_") && boost::starts_with(typeC, "O_")){
                V = 2; // sqrt(2*2)
            }
            else if(boost::starts_with(typeB, "O_") || boost::starts_with(typeC, "O_")){
                V = sqrt(2 * 6.8);
            }
            else{
                V = sqrt(6.8 * 6.8);
            }

            n = 2;
            phi0 = 90;
        }

        // general case
        else{
            // equation 16
            V = sqrt(pb->V * pc->V);

            n = 3;
            phi0 = 180 * chemkit::constants::DegreesToRadians;
        }
    }
    // sp2-sp2
    else if((typeB[2] == '2' || typeB[2] == 'R') && (typeC[2] == '2' || typeC[2] == 'R')){
        chemkit::Real bondorder = bondOrder(b, c);

        // equation 17
        V = 5 * sqrt(pb->U * pc->U) * (1 + 4.18 * log(bondorder));

        n = 2;
        phi0 = 180 * chemkit::constants::DegreesToRadians;
    }
    // group 6 sp3 - any sp2 or R
    else if((forceField->isGroupSix(b) && (typeC[2] == '2' || typeC[2] == 'R')) ||
            (forceField->isGroupSix(c) && (typeB[2] == '2' || typeB[2] == 'R'))){
        chemkit::Real bondorder = bondOrder(b, c);

        // equation 17
        V = 5 * sqrt(pb->U * pc->U) * (1 + 4.18 * log(bondorder));

        n = 2;
        phi0 = 90 * chemkit::constants::DegreesToRadians;
    }
    // sp3-sp2
    else if((typeB[2] == '3' && (typeC[2] == '2' || typeC[2] == 'R')) ||
            (typeC[2] == '3' && (typeB[2] == '2' || typeB[2] == 'R'))){
        V = 1;
        n = 6;
        phi0 = 0;
    }
    else{
        return false;
    }

    setParameter(0, V);
    setParameter(1, n);
    setParameter(2, phi0);

    return true;
}

chemkit::Real UffTorsionCalculation::energy(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);
    size_t d = atom(3);

    chemkit::Real V = parameter(0);
    chemkit::Real n = parameter(1);
    chemkit::Real phi0 = parameter(2);

    chemkit::Real phi = coordinates->torsionAngleRadians(a, b, c, d);

    return 0.5 * V * (1 - cos(n * phi0) * cos(n * phi));
}

std::vector<chemkit::Vector3> UffTorsionCalculation::gradient(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);
    size_t d = atom(3);

    chemkit::Real V = parameter(0);
    chemkit::Real n = parameter(1);
    chemkit::Real phi0 = parameter(2);

    chemkit::Real phi = coordinates->torsionAngleRadians(a, b, c, d);

    // dE/dphi
    chemkit::Real de_dphi = 0.5 * V * n * cos(n * phi0) * sin(n * phi);

    boost::array<chemkit::Vector3, 4> gradient = coordinates->torsionAngleGradientRadians(a, b, c, d);

    gradient[0] *= de_dphi;
    gradient[1] *= de_dphi;
    gradient[2] *= de_dphi;
    gradient[3] *= de_dphi;

    return std::vector<chemkit::Vector3>(gradient.begin(), gradient.end());
}

// === UffInversionCalculation ============================================= //
UffInversionCalculation::UffInversionCalculation(size_t a, size_t b, size_t c, size_t d)
    : UffCalculation(Inversion, 4, 4)
{
    setAtom(0, a);
    setAtom(1, b);
    setAtom(2, c);
    setAtom(3, d);
}

bool UffInversionCalculation::setup()
{
    const boost::shared_ptr<chemkit::Topology> &topology = this->topology();

    // b is the center atom
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);
    size_t d = atom(3);

    std::string typeA = topology->type(a);
    std::string typeB = topology->type(b);
    std::string typeC = topology->type(c);
    std::string typeD = topology->type(d);

    chemkit::Real k = 0;
    chemkit::Real c0 = 0;
    chemkit::Real c1 = 0;
    chemkit::Real c2 = 0;

    // sp2 carbon
    if(typeB == "C_2" || typeB == "C_R"){
        if(typeA == "O_2" || typeC == "O_2" || typeD == "O_2"){
            k = 50;
        }
        else{
            k = 6;
        }

        c0 = 1;
        c1 = -1;
        c2 = 0;
    }

    // divide by 3
    k /= 3;

    setParameter(0, k);
    setParameter(1, c0);
    setParameter(2, c1);
    setParameter(3, c2);

    return true;
}

chemkit::Real UffInversionCalculation::energy(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);
    size_t d = atom(3);

    chemkit::Real k = parameter(0);
    chemkit::Real c0 = parameter(1);
    chemkit::Real c1 = parameter(2);
    chemkit::Real c2 = parameter(3);

    chemkit::Real w = coordinates->wilsonAngleRadians(a, b, c, d);
    chemkit::Real y = w + (chemkit::constants::Pi / 2.0);

    return k * (c0 + c1 * sin(y) + c2 * cos(2 * y));
}

std::vector<chemkit::Vector3> UffInversionCalculation::gradient(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);
    size_t c = atom(2);
    size_t d = atom(3);

    chemkit::Real k = parameter(0);
    chemkit::Real c1 = parameter(2);
    chemkit::Real c2 = parameter(3);

    chemkit::Real w = coordinates->wilsonAngleRadians(a, b, c, d);
    chemkit::Real y = w + (chemkit::constants::Pi / 2.0);

    // dE/dw
    chemkit::Real de_dw = k * (c1 * cos(y) - 2 * c2 * sin(2 * y));

    boost::array<chemkit::Vector3, 4> gradient = coordinates->wilsonAngleGradientRadians(a, b, c, d);

    gradient[0] *= de_dw;
    gradient[1] *= de_dw;
    gradient[2] *= de_dw;
    gradient[3] *= de_dw;

    return std::vector<chemkit::Vector3>(gradient.begin(), gradient.end());
}

// === UffVanDerWaalsCalculation =========================================== //
UffVanDerWaalsCalculation::UffVanDerWaalsCalculation(size_t a, size_t b)
    : UffCalculation(VanDerWaals, 2, 2)
{
    setAtom(0, a);
    setAtom(1, b);
}

bool UffVanDerWaalsCalculation::setup()
{
    const UffAtomParameters *pa = parameters(atomType(0));
    const UffAtomParameters *pb = parameters(atomType(1));
    if(!pa || !pb){
        return false;
    }

    // equation 22
    chemkit::Real d = sqrt(pa->D * pb->D);

    // equation 21b
    chemkit::Real x = sqrt(pa->x * pb->x);

    setParameter(0, d);
    setParameter(1, x);

    return true;
}

chemkit::Real UffVanDerWaalsCalculation::energy(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);

    chemkit::Real d = parameter(0);
    chemkit::Real x = parameter(1);
    chemkit::Real r = coordinates->distance(a, b);

    return d * (-2 * pow(x/r, 6) + pow(x/r, 12));
}

std::vector<chemkit::Vector3> UffVanDerWaalsCalculation::gradient(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);

    chemkit::Real d = parameter(0);
    chemkit::Real x = parameter(1);
    chemkit::Real r = coordinates->distance(a, b);

    // dE/dr
    chemkit::Real de_dr = -12 * d * x / pow(r, 2) * (pow(x/r, 11) - pow(x/r, 5));

    boost::array<chemkit::Vector3, 2> gradient = coordinates->distanceGradient(a, b);

    gradient[0] *= de_dr;
    gradient[1] *= de_dr;

    return std::vector<chemkit::Vector3>(gradient.begin(), gradient.end());
}

// === UffElectrostaticCalculation ========================================= //
UffElectrostaticCalculation::UffElectrostaticCalculation(size_t a, size_t b)
    : UffCalculation(Electrostatic, 2, 2)
{
    setAtom(0, a);
    setAtom(1, b);
}

bool UffElectrostaticCalculation::setup()
{
    return false;
}

chemkit::Real UffElectrostaticCalculation::energy(const chemkit::CartesianCoordinates *coordinates) const
{
    size_t a = atom(0);
    size_t b = atom(1);

    chemkit::Real qa = parameter(0);
    chemkit::Real qb = parameter(1);

    chemkit::Real e = 1;
    chemkit::Real r = coordinates->distance(a, b);

    return 332.037 * (qa * qb) / (e * r);
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_TOOLBOX_IEEE_FUNCTIONS_SCALAR_NEGATION_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_IEEE_FUNCTIONS_SCALAR_NEGATION_HPP_INCLUDED
#include <boost/simd/toolbox/ieee/functions/negation.hpp>
#include <boost/simd/include/functions/scalar/is_nez.hpp>
#include <boost/simd/include/functions/scalar/is_gez.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::negation_, tag::cpu_
                            , (A0)
                            , (scalar_< signed_<A0> >)(scalar_< signed_<A0> >)
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return bool(is_nez(a1))*(is_gez(a1)?a0:-a0);
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::negation_, tag::cpu_
                            , (A0)
                            , (scalar_< unsigned_<A0> >)(scalar_< unsigned_<A0> >)
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return bool(is_nez(a1))*a0;
    }
  };
} } }

#endif


#ifndef MOTION_GENERATOR_HPP_
#define MOTION_GENERATOR_HPP_

#include <array>
#include <Eigen/Core>

#include <franka/control_types.h>
#include <franka/duration.h>
#include <franka/robot.h>
#include <franka/robot_state.h>

namespace RobotControl
{
namespace FrankaLowLevelDriver
{

/**
 * Maximum torque rate
 */
constexpr std::array<double, 7> kMaxTorqueRate{{1000, 1000, 1000, 1000, 1000, 1000, 1000}};
/**
 * Maximum joint velocity
 */
constexpr std::array<double, 7> kMaxJointVel{
    {2.3925, 2.3925, 2.3925, 2.3925, 2.8710, 2.8710, 2.8710}};
/**
 * Maximum joint acceleration
 */
constexpr std::array<double, 7> kMaxJointAcc{
    {16.5000, 8.2500, 13.7500, 13.7500, 16.5000, 22.0000, 22.0000}};

/**
 * @brief the implementation of this class if based on the LibFranka MotionGenerator example with the
 * following discription:
 *
 * An example showing how to generate a joint pose motion to a goal position. Adapted from:
 * Wisama Khalil and Etienne Dombre. 2002. Modeling, Identification and Control of Robots
 * (Kogan Page Science Paper edition).
 *
 * For License and Notice details please see the franka_low_level_driver directory
 */
class MotionGenerator {
public:
    /**
     * Creates a new MotionGenerator instance for a target q.
     *
     * @param[in] speed_factor General speed factor in range [0, 1].
     * @param[in] q_goal Target joint positions.
     */
    MotionGenerator(double speed_factor, const std::array<double, 7> q_goal);

    /**
     * Sends joint position calculations
     *
     * @param[in] robot_state Current state of the robot.
     * @param[in] period Duration of execution.
     *
     * @return Joint positions for use inside a control loop.
     */
    franka::JointPositions operator()(const franka::RobotState& robot_state, franka::Duration period);

    /**
     * Limits the rate of an input vector of per-joint commands considering the maximum allowed time
     * derivatives.
     *
     * @param[in] max_derivatives Per-joint maximum allowed time derivative.
     * @param[in] desired_values Desired values of the current time step.
     * @param[in] last_desired_values Desired values of the previous time step.
     *
     * @return Rate-limited vector of desired values.
     */
    std::array<double, 7> limitRate(const std::array<double, 7>& max_derivatives,
                                    const std::array<double, 7>& desired_values,
                                    const std::array<double, 7>& last_desired_values);

private:
    using Vector7d = Eigen::Matrix<double, 7, 1, Eigen::ColMajor>;
    using Vector7i = Eigen::Matrix<int, 7, 1, Eigen::ColMajor>;

    bool calculateDesiredValues(double t, Vector7d* delta_q_d) const;
    void calculateSynchronizedValues();

    static constexpr double kDeltaQMotionFinished = 1e-6;
    const Vector7d q_goal_;

    Vector7d q_start_;
    Vector7d delta_q_;

    Vector7d dq_max_sync_;
    Vector7d t_1_sync_;
    Vector7d t_2_sync_;
    Vector7d t_f_sync_;
    Vector7d q_1_;

    double time_ = 0.0;

    Vector7d dq_max_ = (Vector7d() << 2.0, 2.0, 2.0, 2.0, 2.5, 2.5, 2.5).finished();
    Vector7d ddq_max_start_ = (Vector7d() << 5, 5, 5, 5, 5, 5, 5).finished();
    Vector7d ddq_max_goal_ = (Vector7d() << 5, 5, 5, 5, 5, 5, 5).finished();
};

} //namespace RobotControl
} //nameSpace FrankaLowLevelDriver

#endif //MOTION_GENERATOR_HPP_


#pragma once

#include <Eigen/Core>
#include <assimp/material.h>

namespace vgl {
struct Material {
    Eigen::Vector4f ambient = Eigen::Vector4f::Ones();
    Eigen::Vector4f diffuse = Eigen::Vector4f::Ones();
    Eigen::Vector4f specular = Eigen::Vector4f::Zero();
    Eigen::Vector4f emissive = Eigen::Vector4f::Zero();
    Eigen::Vector4f reflective = Eigen::Vector4f::Zero();
    Eigen::Vector4f transparent = Eigen::Vector4f::Zero();
};

inline Material process_material(aiMaterial* ai_mat) {
    Material mat{};
    ai_mat->Get(AI_MATKEY_COLOR_AMBIENT, reinterpret_cast<aiColor4D&>(mat.ambient));
    ai_mat->Get(AI_MATKEY_COLOR_DIFFUSE, reinterpret_cast<aiColor4D&>(mat.diffuse));
    ai_mat->Get(AI_MATKEY_COLOR_SPECULAR, reinterpret_cast<aiColor4D&>(mat.specular));
    ai_mat->Get(AI_MATKEY_COLOR_EMISSIVE, reinterpret_cast<aiColor4D&>(mat.emissive));
    ai_mat->Get(AI_MATKEY_COLOR_REFLECTIVE, reinterpret_cast<aiColor4D&>(mat.reflective));
    ai_mat->Get(AI_MATKEY_COLOR_TRANSPARENT, reinterpret_cast<aiColor4D&>(mat.transparent));
    ai_mat->Get(AI_MATKEY_SHININESS, mat.specular.w());
    return mat;
}
} // namespace vgl


// Compare SoA, AoS, and Eigen (with expression parser)
// Ideal situation: everything known at compile time
//
// g++ ampBench.cpp -O3 -march=native -std=c++17 -ffast-math -I/usr/local/opt/eigen/include/eigen3 -I/usr/local/Cellar/boost/1.72.0/include/boost/ -Rpass-missed=loop-vectorize -lboost_timer -o ampBench
//

// SoA:
//  1.167841s wall, 1.100000s user + 0.060000s system = 1.160000s CPU (99.3%)
//
// SoA (array):
//  0.637546s wall, 0.570000s user + 0.060000s system = 0.630000s CPU (98.8%)
//
// SoA (stack):
//  0.926747s wall, 0.870000s user + 0.060000s system = 0.930000s CPU (100.4%)
//
// AoS:
//  14.228890s wall, 12.890000s user + 1.260000s system = 14.150000s CPU (99.4%)
//
// AoS (stack):
//  1.105483s wall, 0.910000s user + 0.180000s system = 1.090000s CPU (98.6%)
//
// Eigen:
//  1.072285s wall, 0.930000s user + 0.140000s system = 1.070000s CPU (99.8%)

#include <iostream>
#include <vector>
#include <array>
#include <memory>

#include <Eigen/Dense>

#include <boost/timer/timer.hpp>

// #include <blaze/math/StaticVector.h>
// #include <blaze/math/DynamicVector.h>

#include <blaze/Math.h>

#include "xtensor/xtensor.hpp"
#include "xtensor/xfixed.hpp"
#include "xtensor/xarray.hpp"
#include "xtensor/xio.hpp"

using blaze::StaticVector;
using blaze::DynamicVector;

static const int n_global = 1E6;

// Spin 5 - no need for template here
float legFunc(float cosHel)
{
    return -32.0*(63.0*cosHel*cosHel*cosHel*cosHel*cosHel - 70.0*cosHel*cosHel*cosHel + 15.0*cosHel)/63.0;
}

// AoS
struct ResParams
{
public:

    float mass;
    float qTerm;
    float ffRatioP;
    float ffRatioR;
    float spinTerms;

    float ampRe;
    float ampIm;

    ResParams()
    : mass(0),
      qTerm(0),
      ffRatioP(0),
      ffRatioR(0),
      spinTerms(0),
      ampRe(0),
      ampIm(0) { }

};

struct ResParamsSoA
{
public:

    float resMass;
    float resWidth;

    std::vector<float> * mass;
    std::vector<float> * qTerm;
    std::vector<float> * ffRatioP;
    std::vector<float> * ffRatioR;
    std::vector<float> * spinTerms;

    std::vector<float> * ampRe;
    std::vector<float> * ampIm;

    // Deal with these guys later...
    static const int spin = 4;
    static const int spinType = 1;

    ResParamsSoA(int s)
    {
        mass = new std::vector<float>(s);
        qTerm = new std::vector<float>(s);
        ffRatioP = new std::vector<float>(s);
        ffRatioR = new std::vector<float>(s);
        spinTerms = new std::vector<float>(s);

        ampRe = new std::vector<float>(s);
        ampIm = new std::vector<float>(s);
    }

    ~ResParamsSoA()
    {
        delete mass;
        delete qTerm;
        delete ffRatioP;
        delete ffRatioR;
        delete spinTerms;

        delete ampRe;
        delete ampIm;
    }

};

struct ResParamsSoAArray
{
public:

    float resMass;
    float resWidth;

    std::array<float, n_global> * mass;
    std::array<float, n_global> * qTerm;
    std::array<float, n_global> * ffRatioP;
    std::array<float, n_global> * ffRatioR;
    std::array<float, n_global> * spinTerms;

    std::array<float, n_global> * ampRe;
    std::array<float, n_global> * ampIm;

    // Deal with these guys later...
    static const int spin = 4;
    static const int spinType = 1;

    ResParamsSoAArray(int s)
    {
        mass = new std::array<float, n_global>();
        qTerm = new std::array<float, n_global>();
        ffRatioP = new std::array<float, n_global>();
        ffRatioR = new std::array<float, n_global>();
        spinTerms = new std::array<float, n_global>();

        ampRe = new std::array<float, n_global>();
        ampIm = new std::array<float, n_global>();
    }

    ~ResParamsSoAArray()
    {
        delete mass;
        delete qTerm;
        delete ffRatioP;
        delete ffRatioR;
        delete spinTerms;

        delete ampRe;
        delete ampIm;
    }

};

struct ResParamsSoAStack
{
public:

    float resMass;
    float resWidth;

    std::vector<float> mass;
    std::vector<float> qTerm;
    std::vector<float> ffRatioP;
    std::vector<float> ffRatioR;
    std::vector<float> spinTerms;

    std::vector<float> ampRe;
    std::vector<float> ampIm;

    // Deal with these guys later...
    static const int spin = 4;
    static const int spinType = 1;

    ResParamsSoAStack(int n) {

        mass.resize(n);
        qTerm.resize(n);
        ffRatioP.resize(n);
        ffRatioR.resize(n);
        spinTerms.resize(n);

        ampRe.resize(n);
        ampIm.resize(n);
    }

    ~ResParamsSoAStack() { }

};

struct ResParamsEigen
{
public:

    float resMass;
    float resWidth;

    // Too large to allocate on stack (?)
    // Possible to do more with static allocation?

    Eigen::Array<float,Eigen::Dynamic,1> mass;
    Eigen::Array<float,Eigen::Dynamic,1> qTerm;
    Eigen::Array<float,Eigen::Dynamic,1> ffRatioP;
    Eigen::Array<float,Eigen::Dynamic,1> ffRatioR;
    Eigen::Array<float,Eigen::Dynamic,1> spinTerms;

    Eigen::Array<float,Eigen::Dynamic,1> ampRe;
    Eigen::Array<float,Eigen::Dynamic,1> ampIm;

    // Deal with these guys later...
    static const int spin = 4;
    static const int spinType = 1;

    ResParamsEigen(int n)
    {

        resMass = 0;
        resWidth = 0;

        mass = Eigen::Array<float,Eigen::Dynamic,1>(n);
        qTerm = Eigen::Array<float,Eigen::Dynamic,1>(n);
        ffRatioP = Eigen::Array<float,Eigen::Dynamic,1>(n);
        ffRatioR = Eigen::Array<float,Eigen::Dynamic,1>(n);
        spinTerms = Eigen::Array<float,Eigen::Dynamic,1>(n);

        ampRe = Eigen::Array<float,Eigen::Dynamic,1>(n);
        ampIm = Eigen::Array<float,Eigen::Dynamic,1>(n);
    }

    ~ResParamsEigen()
    {

    }

};

struct ResParamsBlaze
{
public:

    float resMass;
    float resWidth;

    static const int n = n_global;

    // DynamicVector<float> mass;
    // DynamicVector<float> qTerm;
    // DynamicVector<float> ffRatioP;
    // DynamicVector<float> ffRatioR;
    // DynamicVector<float> spinTerms;
    //
    // DynamicVector<float> ampRe;
    // DynamicVector<float> ampIm;

    StaticVector<float, n> mass;
    StaticVector<float, n> qTerm;
    StaticVector<float, n> ffRatioP;
    StaticVector<float, n> ffRatioR;
    StaticVector<float, n> spinTerms;

    StaticVector<float, n> ampRe;
    StaticVector<float, n> ampIm;

    // Deal with these guys later...
    static const int spin = 4;
    static const int spinType = 1;

    ResParamsBlaze()
    {

        resMass = 0;
        resWidth = 0;

        // mass = DynamicVector<float>(this->n);
        // qTerm = DynamicVector<float>(this->n);
        // ffRatioP = DynamicVector<float>(this->n);
        // ffRatioR = DynamicVector<float>(this->n);
        // spinTerms = DynamicVector<float>(this->n);
        //
        // ampRe = DynamicVector<float>(this->n);
        // ampIm = DynamicVector<float>(this->n);

    }

    ~ResParamsBlaze()
    {

    }

};

struct ResParamsXTensor
{
public:

    float resMass;
    float resWidth;

    static const int n = n_global;

    // DynamicVector<float> mass;
    // DynamicVector<float> qTerm;
    // DynamicVector<float> ffRatioP;
    // DynamicVector<float> ffRatioR;
    // DynamicVector<float> spinTerms;
    //
    // DynamicVector<float> ampRe;
    // DynamicVector<float> ampIm;

    xt::xtensor_fixed<double, xt::xshape<n, 1>> mass;
    xt::xtensor_fixed<double, xt::xshape<n, 1>> qTerm;
    xt::xtensor_fixed<double, xt::xshape<n, 1>> ffRatioP;
    xt::xtensor_fixed<double, xt::xshape<n, 1>> ffRatioR;
    xt::xtensor_fixed<double, xt::xshape<n, 1>> spinTerms;

    xt::xtensor_fixed<double, xt::xshape<n, 1>> ampRe;
    xt::xtensor_fixed<double, xt::xshape<n, 1>> ampIm;

    // Deal with these guys later...
    static const int spin = 4;
    static const int spinType = 1;

    ResParamsXTensor()
    {

        resMass = 0;
        resWidth = 0;

        // mass = DynamicVector<float>(this->n);
        // qTerm = DynamicVector<float>(this->n);
        // ffRatioP = DynamicVector<float>(this->n);
        // ffRatioR = DynamicVector<float>(this->n);
        // spinTerms = DynamicVector<float>(this->n);
        //
        // ampRe = DynamicVector<float>(this->n);
        // ampIm = DynamicVector<float>(this->n);

    }

    ~ResParamsXTensor()
    {

    }

};

void calcAmpSoA(const ResParamsSoA & inParams)
{
    float resMass = inParams.resMass;
    float resWidth = inParams.resWidth;

    for (int i = 0; i < inParams.mass->size(); i++) {
        float totWidth = resWidth * inParams.qTerm->at(i);
        totWidth *= (resMass / inParams.mass->at(i));
        totWidth *= inParams.ffRatioP->at(i) * inParams.ffRatioR->at(i);

        float m2 = inParams.mass->at(i) * inParams.mass->at(i);
        float m2Term = resMass * resMass - m2;

        float scale = inParams.spinTerms->at(i);
        scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
        scale *= inParams.ffRatioP->at(i) * inParams.ffRatioR->at(i); // Optional -> template specialise?
        scale *= legFunc(inParams.spinTerms->at(i));

        inParams.ampRe->at(i) = m2Term * scale;
        inParams.ampIm->at(i) = resMass * totWidth * scale;
    }
}

void calcAmpSoA(const ResParamsSoAArray & inParams)
{
    float resMass = inParams.resMass;
    float resWidth = inParams.resWidth;

    #pragma clang loop vectorize(assume_safety)
    for (int i = 0; i < n_global; i++) {
        float totWidth = resWidth * inParams.qTerm->at(i);
        totWidth *= (resMass / inParams.mass->at(i));
        totWidth *= inParams.ffRatioP->at(i) * inParams.ffRatioR->at(i);

        float m2 = inParams.mass->at(i) * inParams.mass->at(i);
        float m2Term = resMass * resMass - m2;

        float scale = inParams.spinTerms->at(i);
        scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
        scale *= inParams.ffRatioP->at(i) * inParams.ffRatioR->at(i); // Optional -> template specialise?
        scale *= legFunc(inParams.spinTerms->at(i));

        inParams.ampRe->at(i) = m2Term * scale;
        inParams.ampIm->at(i) = resMass * totWidth * scale;
    }
}

void calcAmpSoAStack(ResParamsSoAStack & inParams)
{
    float resMass = inParams.resMass;
    float resWidth = inParams.resWidth;

    for (int i = 0; i < inParams.mass.size(); i++) {
        float totWidth = resWidth * inParams.qTerm.at(i);
        totWidth *= (resMass / inParams.mass.at(i));
        totWidth *= inParams.ffRatioP.at(i) * inParams.ffRatioR.at(i);

        float m2 = inParams.mass.at(i) * inParams.mass.at(i);
        float m2Term = resMass * resMass - m2;

        float scale = inParams.spinTerms.at(i);
        scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
        scale *= inParams.ffRatioP.at(i) * inParams.ffRatioR.at(i); // Optional -> template specialise?
        scale *= legFunc(inParams.spinTerms.at(i));

        inParams.ampRe.at(i) = m2Term * scale;
        inParams.ampIm.at(i) = resMass * totWidth * scale;
    }
}

void calcAmpAoS(const std::vector<std::unique_ptr<ResParams> > & inParams, float resMass, float resWidth)
{

    for (int i = 0; i < inParams.size(); i++) {
        float totWidth = resWidth * inParams.at(i)->qTerm;
        totWidth *= (resMass / inParams.at(i)->mass);
        totWidth *= inParams.at(i)->ffRatioP * inParams.at(i)->ffRatioR;

        float m2 = inParams.at(i)->mass * inParams.at(i)->mass;
        float m2Term = resMass * resMass - m2;

        float scale = inParams.at(i)->spinTerms;
        scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
        scale *= inParams.at(i)->ffRatioP * inParams.at(i)->ffRatioR; // Optional -> template specialise?
        scale *= legFunc(inParams.at(i)->spinTerms);

        inParams.at(i)->ampRe = m2Term * scale;
        inParams.at(i)->ampIm = resMass * totWidth * scale;
    }
}

void calcAmpAoSStack(std::vector<ResParams> & inParams, float resMass, float resWidth)
{

    for (int i = 0; i < inParams.size(); i++) {
        float totWidth = resWidth * inParams.at(i).qTerm;
        totWidth *= (resMass / inParams.at(i).mass);
        totWidth *= inParams.at(i).ffRatioP * inParams.at(i).ffRatioR;

        float m2 = inParams.at(i).mass * inParams.at(i).mass;
        float m2Term = resMass * resMass - m2;

        float scale = inParams.at(i).spinTerms;
        scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
        scale *= inParams.at(i).ffRatioP * inParams.at(i).ffRatioR; // Optional -> template specialise?
        scale *= legFunc(inParams.at(i).spinTerms);

        inParams.at(i).ampRe = m2Term * scale;
        inParams.at(i).ampIm = resMass * totWidth * scale;
    }
}

void calcAmpEigen(ResParamsEigen & inParams)
{
    float resMass = inParams.resMass;
    float resWidth = inParams.resWidth;

    Eigen::Array<float,Eigen::Dynamic,1> totWidth = resWidth * (inParams.qTerm);
    totWidth *= (resMass / (inParams.mass));
    totWidth *= totWidth * (inParams.ffRatioP) * (inParams.ffRatioR);

    Eigen::Array<float,Eigen::Dynamic,1> m2 = (inParams.mass) * (inParams.mass);
    Eigen::Array<float,Eigen::Dynamic,1> m2Term = resMass * resMass - m2;

    Eigen::Array<float,Eigen::Dynamic,1> scale = (inParams.spinTerms);
    scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
    scale *= (inParams.ffRatioP) * (inParams.ffRatioR);

    inParams.ampRe = m2Term * scale;
    inParams.ampIm = resMass * totWidth * scale;
}

void calcAmpBlaze(ResParamsBlaze & inParams)
{
    float resMass = inParams.resMass;
    float resWidth = inParams.resWidth;

    StaticVector<float, n_global> totWidth = resWidth * (inParams.qTerm);
    totWidth *= (resMass / (inParams.mass));
    totWidth *= totWidth * (inParams.ffRatioP) * (inParams.ffRatioR);

    StaticVector<float, n_global> m2 = (inParams.mass) * (inParams.mass);
    StaticVector<float, n_global> m2Term = resMass * resMass - m2;

    StaticVector<float, n_global> scale = (inParams.spinTerms);
    scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
    scale *= (inParams.ffRatioP) * (inParams.ffRatioR);

    inParams.ampRe = m2Term * scale;
    inParams.ampIm = resMass * totWidth * scale;
}

void calcAmpXTensor(ResParamsXTensor & inParams)
{
    float resMass = inParams.resMass;
    float resWidth = inParams.resWidth;

    xt::xtensor_fixed<double, xt::xshape<n_global, 1>> totWidth = resWidth * (inParams.qTerm);
    totWidth *= (resMass / (inParams.mass));
    totWidth *= totWidth * (inParams.ffRatioP) * (inParams.ffRatioR);

    xt::xtensor_fixed<double, xt::xshape<n_global, 1>> m2 = (inParams.mass) * (inParams.mass);
    xt::xtensor_fixed<double, xt::xshape<n_global, 1>> m2Term = resMass * resMass - m2;

    xt::xtensor_fixed<double, xt::xshape<n_global, 1>> scale = (inParams.spinTerms);
    scale /= m2Term * m2Term + resMass * resMass * totWidth * totWidth;
    scale *= (inParams.ffRatioP) * (inParams.ffRatioR);

    inParams.ampRe = m2Term * scale;
    inParams.ampIm = resMass * totWidth * scale;
}

void benchAoS()
{
    static const int n = n_global;

    std::vector<std::unique_ptr<ResParams>> resParamsAoS;
    resParamsAoS.resize(n);

    for (auto & p : resParamsAoS) {

        p = std::make_unique<ResParams>();

        p->qTerm = 0.05;
        p->mass = 0.3;
        p->ffRatioP = 3.3;
        p->ffRatioR = 1.0;
        p->spinTerms = 1.0;

        p->ampRe = 1.0;
        p->ampIm = 1.0;
    }

    float resMass = 1.0;
    float resWidth = 0.1;

    calcAmpAoS(resParamsAoS, resMass, resWidth);

    // std::cout << resParamsAoS.at(5)->ampRe << std::endl;

}

void benchAoSStack()
{
    int n = n_global;

    std::vector<ResParams> resParamsAoSStack;
    resParamsAoSStack.reserve(n);

    for (int i = 0; i < n; i++) {

        ResParams params;

        params.qTerm = 0.05;
        params.mass = 0.3;
        params.ffRatioP = 3.3;
        params.ffRatioR = 1.0;
        params.spinTerms = 1.0;

        params.ampRe = 1.0;
        params.ampIm = 1.0;

        resParamsAoSStack.push_back(params);

    }

    float resMass = 1.0;
    float resWidth = 0.1;

    calcAmpAoSStack(resParamsAoSStack, resMass, resWidth);

    // std::cout << resParamsAoSStack.at(5).ampRe << std::endl;
    // std::cout << resParamsAoSStack.at(5).ampIm << std::endl;
}

void benchSoA()
{
    ResParamsSoA parsR(n_global);

    std::random_device r;

    std::default_random_engine e1(r());
    std::uniform_real_distribution<float> uniform_dist(0, 1);

    std::fill(parsR.qTerm->begin(), parsR.qTerm->end(), uniform_dist(e1));
    std::fill(parsR.mass->begin(), parsR.mass->end(), uniform_dist(e1));
    std::fill(parsR.ffRatioP->begin(), parsR.ffRatioP->end(), uniform_dist(e1));
    std::fill(parsR.ffRatioR->begin(), parsR.ffRatioR->end(), uniform_dist(e1));
    std::fill(parsR.spinTerms->begin(), parsR.spinTerms->end(), uniform_dist(e1));

    std::fill(parsR.ampRe->begin(), parsR.ampRe->end(), uniform_dist(e1));
    std::fill(parsR.ampIm->begin(), parsR.ampIm->end(), uniform_dist(e1));

    parsR.resMass =uniform_dist(e1);
    parsR.resWidth =uniform_dist(e1);

    calcAmpSoA(parsR);

    // std::cout << (parsR.ampRe)->at(5) << std::endl;
    // std::cout << (parsR.ampIm)->at(5) << std::endl;
}

void benchSoAArray()
{
    ResParamsSoAArray parsR(n_global);

    std::random_device r;

    std::default_random_engine e1(r());
    std::uniform_real_distribution<float> uniform_dist(0, 1);

    std::fill(parsR.qTerm->begin(), parsR.qTerm->end(), uniform_dist(e1));
    std::fill(parsR.mass->begin(), parsR.mass->end(), uniform_dist(e1));
    std::fill(parsR.ffRatioP->begin(), parsR.ffRatioP->end(), uniform_dist(e1));
    std::fill(parsR.ffRatioR->begin(), parsR.ffRatioR->end(), uniform_dist(e1));
    std::fill(parsR.spinTerms->begin(), parsR.spinTerms->end(), uniform_dist(e1));

    std::fill(parsR.ampRe->begin(), parsR.ampRe->end(), uniform_dist(e1));
    std::fill(parsR.ampIm->begin(), parsR.ampIm->end(), uniform_dist(e1));

    parsR.resMass = uniform_dist(e1);
    parsR.resWidth = uniform_dist(e1);

    calcAmpSoA(parsR);

    // std::cout << (parsR.ampRe)->at(5) << std::endl;
    // std::cout << (parsR.ampIm)->at(5) << std::endl;
}

void benchSoAStack()
{
    ResParamsSoAStack parsR(n_global);

    std::random_device r;

    std::default_random_engine e1(r());
    std::uniform_real_distribution<float> uniform_dist(0, 1);

    std::fill(parsR.qTerm.begin(), parsR.qTerm.end(), uniform_dist(e1));
    std::fill(parsR.mass.begin(), parsR.mass.end(), uniform_dist(e1));
    std::fill(parsR.ffRatioP.begin(), parsR.ffRatioP.end(), uniform_dist(e1));
    std::fill(parsR.ffRatioR.begin(), parsR.ffRatioR.end(), uniform_dist(e1));
    std::fill(parsR.spinTerms.begin(), parsR.spinTerms.end(), uniform_dist(e1));

    std::fill(parsR.ampRe.begin(), parsR.ampRe.end(), uniform_dist(e1));
    std::fill(parsR.ampIm.begin(), parsR.ampIm.end(), uniform_dist(e1));

    parsR.resMass = uniform_dist(e1);
    parsR.resWidth = uniform_dist(e1);

    calcAmpSoAStack(parsR);

    // std::cout << parsR.ampRe.at(5) << std::endl;
    // std::cout << parsR.ampIm.at(5) << std::endl;
}

void benchEigen()
{
    int n = n_global;
    ResParamsEigen parsR(n);

    parsR.qTerm = Eigen::ArrayXf::Ones(n, 1);
    parsR.mass = Eigen::ArrayXf::Ones(n, 1);
    parsR.ffRatioP = Eigen::ArrayXf::Ones(n, 1);
    parsR.ffRatioR = Eigen::ArrayXf::Ones(n, 1);
    parsR.spinTerms = Eigen::ArrayXf::Ones(n, 1);

    parsR.ampRe = Eigen::ArrayXf::Ones(n, 1);
    parsR.ampIm = Eigen::ArrayXf::Ones(n, 1);

    parsR.resMass = 1.0;
    parsR.resWidth = 0.1;

    calcAmpEigen(parsR);

    // std::cout << (*parsR.ampRe)[5] << std::endl;
    // std::cout << (*parsR.ampIm)[5] << std::endl;
}

void benchBlaze()
{
    ResParamsBlaze parsR;

    std::random_device r;

    std::default_random_engine e1(r());
    std::uniform_real_distribution<float> uniform_dist(0, 1);

    parsR.qTerm = uniform_dist(e1);
    parsR.mass = uniform_dist(e1);
    parsR.ffRatioP = uniform_dist(e1);
    parsR.ffRatioR = uniform_dist(e1);
    parsR.spinTerms = uniform_dist(e1);

    parsR.ampRe = uniform_dist(e1);
    parsR.ampIm = uniform_dist(e1);

    parsR.resMass = uniform_dist(e1);
    parsR.resWidth = uniform_dist(e1);

    calcAmpBlaze(parsR);

    // std::cout << parsR.ampRe[5] << std::endl;
    // std::cout << parsR.ampIm[5] << std::endl;
}

void benchXTensor()
{
    ResParamsXTensor parsR;

    std::random_device r;

    std::default_random_engine e1(r());
    std::uniform_real_distribution<float> uniform_dist(0.0, 1.0);

    parsR.qTerm.fill(uniform_dist(e1));
    parsR.mass.fill(uniform_dist(e1));
    parsR.ffRatioP.fill(uniform_dist(e1));
    parsR.ffRatioR.fill(uniform_dist(e1));
    parsR.spinTerms.fill(uniform_dist(e1));

    parsR.ampRe.fill(uniform_dist(e1));
    parsR.ampIm.fill(uniform_dist(e1));

    parsR.resMass = uniform_dist(e1);
    parsR.resWidth = uniform_dist(e1);

    calcAmpXTensor(parsR);

    // std::cout << parsR.ampRe[5] << std::endl;
    // std::cout << parsR.ampIm[5] << std::endl;
}

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

    int n_itr = 100;

    std::cout<< "SoA:" << std::endl;
    {
    boost::timer::auto_cpu_timer t;
    for (int i = 0; i < n_itr; i++) { benchSoA(); }
    }

    std::cout << std::endl;

    std::cout<< "SoA (array):" << std::endl;
    {
    boost::timer::auto_cpu_timer t;
    for (int i = 0; i < n_itr; i++) { benchSoAArray(); }
    }

    std::cout << std::endl;

    std::cout<< "SoA (stack):" << std::endl;
    {
    boost::timer::auto_cpu_timer t;
    for (int i = 0; i < n_itr; i++) { benchSoAStack(); }
    }

    std::cout << std::endl;

    std::cout<< "AoS:" << std::endl;
    {
    boost::timer::auto_cpu_timer t;
    for (int i = 0; i < n_itr; i++) { benchAoS(); }
    }

    std::cout << std::endl;

    std::cout<< "AoS (stack):" << std::endl;
    {
    boost::timer::auto_cpu_timer t;
    for (int i = 0; i < n_itr; i++) { benchAoSStack(); }
    }

    std::cout << std::endl;

    // Without Legendre term

    std::cout<< "Eigen:" << std::endl;
    {
    boost::timer::auto_cpu_timer t;
    for (int i = 0; i < n_itr; i++) { benchEigen(); }
    }

    // Segfaults for some reason?

    // std::cout << std::endl;
    //
    // std::cout<< "Blaze:" << std::endl;
    // {
    // boost::timer::auto_cpu_timer t;
    // for (int i = 0; i < n_itr; i++) { benchBlaze(); }
    // }
    //
    // std::cout << std::endl;
    //
    // std::cout<< "XTensor:" << std::endl;
    // {
    // boost::timer::auto_cpu_timer t;
    // for (int i = 0; i < n_itr; i++) { benchXTensor(); }
    // }

    return 0;
}