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

#define BOOST_TEST_MODULE algebra_curves_test

#include <iostream>
#include <type_traits>

#include <boost/test/unit_test.hpp>
#include <boost/test/data/test_case.hpp>
#include <boost/test/data/monomorphic.hpp>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

#include <nil/crypto3/algebra/curves/alt_bn128.hpp>
#include <nil/crypto3/algebra/curves/bls12.hpp>
// #include <nil/crypto3/algebra/curves/brainpool_r1.hpp>
#include <nil/crypto3/algebra/curves/edwards.hpp>
#include <nil/crypto3/algebra/curves/jubjub.hpp>
#include <nil/crypto3/algebra/curves/babyjubjub.hpp>
// #include <nil/crypto3/algebra/curves/frp_v1.hpp>
// #include <nil/crypto3/algebra/curves/gost_A.hpp>
#include <nil/crypto3/algebra/curves/mnt4.hpp>
#include <nil/crypto3/algebra/curves/mnt6.hpp>
// #include <nil/crypto3/algebra/curves/p192.hpp>
// #include <nil/crypto3/algebra/curves/p224.hpp>
// #include <nil/crypto3/algebra/curves/p256.hpp>
// #include <nil/crypto3/algebra/curves/p384.hpp>
// #include <nil/crypto3/algebra/curves/p521.hpp>
// #include <nil/crypto3/algebra/curves/secp.hpp>
// #include <nil/crypto3/algebra/curves/sm2p_v1.hpp>
// #include <nil/crypto3/algebra/curves/x962_p.hpp>

#include <nil/crypto3/algebra/random_element.hpp>

#include <nil/crypto3/multiprecision/cpp_int.hpp>

using namespace nil::crypto3::algebra;

template<typename FieldParams>
void print_field_element(typename fields::detail::element_fp<FieldParams> e) {
    std::cout << e.data << std::endl;
}

template<typename FieldParams>
void print_field_element(typename fields::detail::element_fp2<FieldParams> e) {
    std::cout << e.data[0].data << " " << e.data[1].data << std::endl;
}

template<typename FieldParams>
void print_field_element(typename fields::detail::element_fp3<FieldParams> e) {
    std::cout << e.data[0].data << " " << e.data[1].data << " " << e.data[2].data << std::endl;
}

template<typename FpCurveGroupElement>
void print_fp_curve_group_element(std::ostream &os, const FpCurveGroupElement &e) {
    os << "( " << e.X.data << " : " << e.Y.data << " : " << e.Z.data << " )";
}

template<typename Fp2CurveGroupElement>
void print_fp2_curve_group_element(std::ostream &os, const Fp2CurveGroupElement &e) {
    os << "(" << e.X.data[0].data << " , " << e.X.data[1].data << ") : (" << e.Y.data[0].data << " , "
       << e.Y.data[1].data << ") : (" << e.Z.data[0].data << " , " << e.Z.data[1].data << ")" << std::endl;
}

template<typename Fp3CurveGroupElement>
void print_fp3_curve_group_element(std::ostream &os, const Fp3CurveGroupElement &e) {
    os << "(" << e.X.data[0].data << " , " << e.X.data[1].data << " , " << e.X.data[2].data << ") : ("
       << e.Y.data[0].data << " , " << e.Y.data[1].data << " , " << e.Y.data[2].data << ") : (" << e.Z.data[0].data
       << " , " << e.Z.data[1].data << " , " << e.Z.data[2].data << ")" << std::endl;
}

namespace boost {
    namespace test_tools {
        namespace tt_detail {

            template<>
            struct print_log_value<typename curves::jubjub::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::jubjub::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::edwards<183>::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::edwards<183>::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::mnt4<298>::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::mnt4<298>::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::mnt6<298>::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::mnt6<298>::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::alt_bn128<254>::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::alt_bn128<254>::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::bls12<381>::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::bls12<381>::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::bls12<377>::g1_type::value_type> {
                void operator()(std::ostream &os, typename curves::bls12<377>::g1_type::value_type const &e) {
                    print_fp_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::mnt4<298>::g2_type::value_type> {
                void operator()(std::ostream &os, typename curves::mnt4<298>::g2_type::value_type const &e) {
                    print_fp2_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::bls12<381>::g2_type::value_type> {
                void operator()(std::ostream &os, typename curves::bls12<381>::g2_type::value_type const &e) {
                    print_fp2_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::bls12<377>::g2_type::value_type> {
                void operator()(std::ostream &os, typename curves::bls12<377>::g2_type::value_type const &e) {
                    print_fp2_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::alt_bn128<254>::g2_type::value_type> {
                void operator()(std::ostream &os, typename curves::alt_bn128<254>::g2_type::value_type const &e) {
                    print_fp2_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::edwards<183>::g2_type::value_type> {
                void operator()(std::ostream &os, typename curves::edwards<183>::g2_type::value_type const &e) {
                    print_fp3_curve_group_element(os, e);
                }
            };

            template<>
            struct print_log_value<typename curves::mnt6<298>::g2_type::value_type> {
                void operator()(std::ostream &os, typename curves::mnt6<298>::g2_type::value_type const &e) {
                    print_fp3_curve_group_element(os, e);
                }
            };

            template<template<typename, typename> class P, typename K, typename V>
            struct print_log_value<P<K, V>> {
                void operator()(std::ostream &, P<K, V> const &) {
                }
            };

        }    // namespace tt_detail
    }        // namespace test_tools
}    // namespace boost

// if target == check-algebra just data/curves.json
const char *test_data = "../../../../libs/algebra/test/data/curves.json";

boost::property_tree::ptree string_data(std::string test_name) {
    boost::property_tree::ptree string_data;
    boost::property_tree::read_json(test_data, string_data);

    return string_data.get_child(test_name);
}

enum curve_operation_test_constants : std::size_t { C1, C2 };

enum curve_operation_test_points : std::size_t {
    p1,
    p2,
    p1_plus_p2,
    p1_minus_p2,
    p1_mul_C1,
    p2_mul_C1_plus_p2_mul_C2,
    p1_dbl,
    p1_mixed_add_p2,
    p1_to_affine,
    p2_to_special
};

template<typename CurveGroup>
void check_curve_operations(const std::vector<typename CurveGroup::value_type> &points,
                            const std::vector<std::size_t> &constants) {
    using nil::crypto3::multiprecision::cpp_int;

    BOOST_CHECK_EQUAL(points[p1] + points[p2], points[p1_plus_p2]);
    BOOST_CHECK_EQUAL(points[p1] - points[p2], points[p1_minus_p2]);
    BOOST_CHECK_EQUAL(points[p1].doubled(), points[p1_dbl]);
    BOOST_CHECK_EQUAL(points[p1] * static_cast<cpp_int>(constants[C1]), points[p1_mul_C1]);
    BOOST_CHECK_EQUAL((points[p2] * static_cast<cpp_int>(constants[C1])) +
                          (points[p2] * static_cast<cpp_int>(constants[C2])),
                      points[p2_mul_C1_plus_p2_mul_C2]);
    BOOST_CHECK_EQUAL((points[p2] * static_cast<cpp_int>(constants[C1])) +
                          (points[p2] * static_cast<cpp_int>(constants[C2])),
                      points[p2] * static_cast<cpp_int>(constants[C1] + constants[C2]));
    BOOST_CHECK_EQUAL(points[p1].mixed_add(points[p2]), points[p1_mixed_add_p2]);
    typename CurveGroup::value_type p1_copy = typename CurveGroup::value_type(points[p1]).to_affine();
    BOOST_CHECK_EQUAL(p1_copy, points[p1_to_affine]);
    typename CurveGroup::value_type p2_copy = typename CurveGroup::value_type(points[p2]).to_projective();
    BOOST_CHECK_EQUAL(p2_copy, points[p2_to_special]);
}

// temporary separated test for JubJub and BabyJubJub
template<typename CurveGroup>
void check_curve_operations_twisted_edwards(const std::vector<typename CurveGroup::value_type> &points,
                            const std::vector<typename CurveGroup::curve_type::number_type> &constants) {
    using nil::crypto3::multiprecision::cpp_int;

    BOOST_CHECK_EQUAL(points[p1] + points[p2], points[p1_plus_p2]);
    BOOST_CHECK_EQUAL(points[p1] - points[p2], points[p1_minus_p2]);
    BOOST_CHECK_EQUAL(points[p1].doubled(), points[p1_dbl]);
    BOOST_CHECK_EQUAL(points[p1] * static_cast<cpp_int>(constants[C1]), points[p1_mul_C1]);
    BOOST_CHECK_EQUAL((points[p2] * static_cast<cpp_int>(constants[C1])) +
                          (points[p2] * static_cast<cpp_int>(constants[C2])),
                      points[p2_mul_C1_plus_p2_mul_C2]);
    BOOST_CHECK_EQUAL((points[p2] * static_cast<cpp_int>(constants[C1])) +
                          (points[p2] * static_cast<cpp_int>(constants[C2])),
                      points[p2] * static_cast<cpp_int>(constants[C1] + constants[C2]));
    // BOOST_CHECK_EQUAL(points[p1].mixed_add(points[p2]), points[p1_mixed_add_p2]);
    // typename CurveGroup::value_type p1_copy = typename CurveGroup::value_type(points[p1]).to_affine();
    // BOOST_CHECK_EQUAL(p1_copy, points[p1_to_affine]);
    // typename CurveGroup::value_type p2_copy = typename CurveGroup::value_type(points[p2]).to_projective();
    // BOOST_CHECK_EQUAL(p2_copy, points[p2_to_special]);
}

template<typename FpCurveGroup, typename TestSet>
void fp_curve_test_init(std::vector<typename FpCurveGroup::value_type> &points,
                        std::vector<std::size_t> &constants,
                        const TestSet &test_set) {
    typedef typename FpCurveGroup::underlying_field_type::value_type field_value_type;
    std::array<field_value_type, 3> coordinates;

    for (auto &point : test_set.second.get_child("point_coordinates")) {
        auto i = 0;
        for (auto &coordinate : point.second) {
            coordinates[i++] = field_value_type(typename field_value_type::modulus_type(coordinate.second.data()));
        }
        points.emplace_back(typename FpCurveGroup::value_type(coordinates[0], coordinates[1], coordinates[2]));
    }

    for (auto &constant : test_set.second.get_child("constants")) {
        constants.emplace_back(std::stoul(constant.second.data()));
    }
}

template<typename FpCurveGroup, typename TestSet>
void fp_curve_twisted_edwards_test_init(std::vector<typename FpCurveGroup::value_type> &points,
                        std::vector<typename FpCurveGroup::curve_type::number_type> &constants,
                        const TestSet &test_set) {
    typedef typename FpCurveGroup::underlying_field_type::value_type field_value_type;
    std::array<field_value_type, 3> coordinates;

    for (auto &point : test_set.second.get_child("point_coordinates")) {
        auto i = 0;
        for (auto &coordinate : point.second) {
            coordinates[i++] = field_value_type(typename field_value_type::modulus_type(coordinate.second.data()));
        }
        points.emplace_back(typename FpCurveGroup::value_type(coordinates[0], coordinates[1]));
    }

    for (auto &constant : test_set.second.get_child("constants")) {
        constants.emplace_back(typename FpCurveGroup::curve_type::number_type(constant.second.data()));
    }
}

template<typename Fp2CurveGroup, typename TestSet>
void fp2_curve_test_init(std::vector<typename Fp2CurveGroup::value_type> &points,
                         std::vector<std::size_t> &constants,
                         const TestSet &test_set) {
    using fp2_value_type = typename Fp2CurveGroup::underlying_field_type::value_type;
    using modulus_type = typename fp2_value_type::underlying_type::modulus_type;
    std::array<modulus_type, 6> coordinates;

    for (auto &point : test_set.second.get_child("point_coordinates")) {
        auto i = 0;
        for (auto &coordinate_pairs : point.second) {
            for (auto &coordinate : coordinate_pairs.second) {
                coordinates[i++] = modulus_type(coordinate.second.data());
            }
        }
        points.emplace_back(typename Fp2CurveGroup::value_type(fp2_value_type(coordinates[0], coordinates[1]),
                                                               fp2_value_type(coordinates[2], coordinates[3]),
                                                               fp2_value_type(coordinates[4], coordinates[5])));
    }

    for (auto &constant : test_set.second.get_child("constants")) {
        constants.emplace_back(std::stoul(constant.second.data()));
    }
}

template<typename Fp3CurveGroup, typename TestSet>
void fp3_curve_test_init(std::vector<typename Fp3CurveGroup::value_type> &points,
                         std::vector<std::size_t> &constants,
                         const TestSet &test_set) {
    using fp3_value_type = typename Fp3CurveGroup::underlying_field_type::value_type;
    using modulus_type = typename fp3_value_type::underlying_type::modulus_type;

    std::array<modulus_type, 9> coordinates;

    for (auto &point : test_set.second.get_child("point_coordinates")) {
        auto i = 0;
        for (auto &coordinate_pairs : point.second) {
            for (auto &coordinate : coordinate_pairs.second) {
                coordinates[i++] = modulus_type(coordinate.second.data());
            }
        }
        points.emplace_back(
            typename Fp3CurveGroup::value_type(fp3_value_type(coordinates[0], coordinates[1], coordinates[2]),
                                               fp3_value_type(coordinates[3], coordinates[4], coordinates[5]),
                                               fp3_value_type(coordinates[6], coordinates[7], coordinates[8])));
    }

    for (auto &constant : test_set.second.get_child("constants")) {
        constants.emplace_back(std::stoul(constant.second.data()));
    }
}

template<typename CurveGroup, typename TestSet>
void curve_operation_test(const TestSet &test_set,
                          void (&test_init)(std::vector<typename CurveGroup::value_type> &,
                                            std::vector<std::size_t> &,
                                            const TestSet &)) {

    std::vector<typename CurveGroup::value_type> points;
    std::vector<std::size_t> constants;

    test_init(points, constants, test_set);

    check_curve_operations<CurveGroup>(points, constants);
}

template<typename CurveGroup, typename TestSet>
void curve_operation_test_twisted_edwards(const TestSet &test_set,
                          void (&test_init)(std::vector<typename CurveGroup::value_type> &,
                                            std::vector<typename CurveGroup::curve_type::number_type> &,
                                            const TestSet &)) {

    std::vector<typename CurveGroup::value_type> points;
    std::vector<typename CurveGroup::curve_type::number_type> constants;

    test_init(points, constants, test_set);

    check_curve_operations_twisted_edwards<CurveGroup>(points, constants);
}

BOOST_AUTO_TEST_SUITE(curves_manual_tests)

BOOST_DATA_TEST_CASE(curve_operation_test_jubjub_g1, string_data("curve_operation_test_jubjub_g1"), data_set) {
    using policy_type = curves::jubjub::g1_type;

    curve_operation_test_twisted_edwards<policy_type>(data_set, fp_curve_twisted_edwards_test_init<policy_type>);
}

BOOST_AUTO_TEST_CASE(curve_operation_test_babyjubjub_g1) {
    using policy_type = curves::babyjubjub::g1_type;

    typename policy_type::value_type P1(typename policy_type::underlying_field_type::value_type(0x274DBCE8D15179969BC0D49FA725BDDF9DE555E0BA6A693C6ADB52FC9EE7A82C_cppui254),
                          typename policy_type::underlying_field_type::value_type(0x5CE98C61B05F47FE2EAE9A542BD99F6B2E78246231640B54595FEBFD51EB853_cppui251)),
                       P2(typename policy_type::underlying_field_type::value_type(0x2491ABA8D3A191A76E35BC47BD9AFE6CC88FEE14D607CBE779F2349047D5C157_cppui254),
                          typename policy_type::underlying_field_type::value_type(0x2E07297F8D3C3D7818DBDDFD24C35583F9A9D4ED0CB0C1D1348DD8F7F99152D7_cppui254)),
                       P3(typename policy_type::underlying_field_type::value_type(0x11805510440A3488B3B811EAACD0EC7C72DDED51978190E19067A2AFAEBAF361_cppui253),
                          typename policy_type::underlying_field_type::value_type(0x1F07AA1B3C598E2FF9FF77744A39298A0A89A9027777AF9FA100DD448E072C13_cppui253));

    typename policy_type::value_type P1pP2 = P1 + P2;

    BOOST_CHECK(P1 + P2 == P3);

    typename policy_type::value_type
                       P4(typename policy_type::underlying_field_type::value_type(0xF3C160E26FC96C347DD9E705EB5A3E8D661502728609FF95B3B889296901AB5_cppui252),
                          typename policy_type::underlying_field_type::value_type(0x9979273078B5C735585107619130E62E315C5CAFE683A064F79DFED17EB14E1_cppui252));

    BOOST_CHECK(P1.doubled() == P4);

    typename policy_type::value_type
        P5(typename policy_type::underlying_field_type::value_type(0x274dbce8d15179969bc0d49fa725bddf9de555e0ba6a693c6adb52fc9ee7a82c_cppui252),
           typename policy_type::underlying_field_type::value_type(0x5ce98c61b05f47fe2eae9a542bd99f6b2e78246231640b54595febfd51eb853_cppui252)),
        et_s1P5(typename policy_type::underlying_field_type::value_type(0x2ad46cbfb78773b6254adc1d80c6efa02f3bf948c37e5a2222136421d7bec942_cppui252),
           typename policy_type::underlying_field_type::value_type(0x14e9693f16d75f7065ce51e1f46ae6c60841ca1e0cf264eda26398e36ca2ed69_cppui252)),
        et_s2P5(typename policy_type::underlying_field_type::value_type(0x25bd7aefee96617d4f715ecf8e50ef9fa102eeb452642c6322d38aa9b32c2ca5_cppui252),
               typename policy_type::underlying_field_type::value_type(0x8e043ec729eedea414b63de474c8f0930ea966733ae283e01f348ca3c35e3ab_cppui252)),
        P6(typename policy_type::underlying_field_type::value_type(0xf3c160e26fc96c347dd9e705eb5a3e8d661502728609ff95b3b889296901ab5_cppui252),
           typename policy_type::underlying_field_type::value_type(0x9979273078b5c735585107619130e62e315c5cafe683a064f79dfed17eb14e1_cppui252)),
        et_s1P6(typename policy_type::underlying_field_type::value_type(0x1dfce39036af5e722b6c8a3214b93b93b2eac662ec2cf67195ef3994b944fb0f_cppui252),
                typename policy_type::underlying_field_type::value_type(0x12aa55c3cc7ff986c520ddcae3927877e682f01bed87628f643f34905692880e_cppui252))
        ;

    BOOST_CHECK(et_s1P5 == static_cast<nil::crypto3::multiprecision::cpp_int>(3) * P5);
    BOOST_CHECK(et_s2P5 == nil::crypto3::multiprecision::cpp_int("14035240266687799601661095864649209771790948434046947201833777492504781204499") * P5);
    BOOST_CHECK(et_s1P6 == nil::crypto3::multiprecision::cpp_int("20819045374670962167435360035096875258406992893633759881276124905556507972311") * P6);
    BOOST_CHECK(P5.is_well_formed());
    BOOST_CHECK(P6.is_well_formed());

    // curve_operation_test_twisted_edwards<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_edwards_g1, string_data("curve_operation_test_edwards_g1"), data_set) {
    using policy_type = curves::edwards<183>::g1_type;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_mnt4_g1, string_data("curve_operation_test_mnt4_g1"), data_set) {
    using policy_type = curves::mnt4<298>::g1_type;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_mnt6_g1, string_data("curve_operation_test_mnt6_g1"), data_set) {
    using policy_type = curves::mnt6<298>::g1_type;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_mnt4_g2, string_data("curve_operation_test_mnt4_g2"), data_set) {
    using policy_type = curves::mnt4<298>::g2_type;

    curve_operation_test<policy_type>(data_set, fp2_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_edwards_g2, string_data("curve_operation_test_edwards_g2"), data_set) {
    using policy_type = curves::edwards<183>::g2_type;

    curve_operation_test<policy_type>(data_set, fp3_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_mnt6_g2, string_data("curve_operation_test_mnt6_g2"), data_set) {
    using policy_type = curves::mnt6<298>::g2_type;

    curve_operation_test<policy_type>(data_set, fp3_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_bls12_381_g1, string_data("curve_operation_test_bls12_381_g1"), data_set) {
    using policy_type = curves::bls12<381>::g1_type;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_bls12_377_g1, string_data("curve_operation_test_bls12_377_g1"), data_set) {
    using policy_type = curves::bls12<377>::g1_type;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_bls12_381_g2, string_data("curve_operation_test_bls12_381_g2"), data_set) {
    using policy_type = curves::bls12<381>::g2_type;

    curve_operation_test<policy_type>(data_set, fp2_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_bls12_377_g2, string_data("curve_operation_test_bls12_377_g2"), data_set) {
    using policy_type = curves::bls12<377>::g2_type;

    curve_operation_test<policy_type>(data_set, fp2_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_alt_bn128_g1, string_data("curve_operation_test_alt_bn128_g1"), data_set) {
    using policy_type = curves::alt_bn128<254>::g1_type;

    curve_operation_test<policy_type>(data_set, fp_curve_test_init<policy_type>);
}

BOOST_DATA_TEST_CASE(curve_operation_test_alt_bn128_g2, string_data("curve_operation_test_alt_bn128_g2"), data_set) {
    using policy_type = curves::alt_bn128<254>::g2_type;

    curve_operation_test<policy_type>(data_set, fp2_curve_test_init<policy_type>);
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright Abel Sinkovics (abel@sinkovics.hu) 2011.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include <mpllibs/metamonad/monoid.hpp>
#include <mpllibs/metamonad/tmp_tag.hpp>
#include <mpllibs/metamonad/lambda.hpp>
#include <mpllibs/metamonad/name.hpp>

#include <mpllibs/metamonad/mconcat.hpp>

#include <boost/test/unit_test.hpp>

#include "common.hpp"

#include <boost/mpl/equal_to.hpp>
#include <boost/mpl/list_c.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/times.hpp>
#include <boost/mpl/assert.hpp>

using boost::mpl::_1;
using boost::mpl::_2;
using boost::mpl::plus;
using boost::mpl::times;

using mpllibs::metamonad::tmp_tag;
using mpllibs::metamonad::lambda;

using namespace mpllibs::metamonad::name;

namespace
{
  struct plus_tag : tmp_tag<plus_tag> {};
  struct mult_tag : tmp_tag<mult_tag> {};
}

namespace mpllibs
{
  namespace metamonad
  {
    namespace v1
    {
      template <>
      struct monoid<plus_tag> : monoid_defaults<plus_tag>
      {
        typedef int0 mempty;
        typedef lambda_c<a, b, plus<a, b> >::type mappend;
      };

      template <>
      struct monoid<mult_tag> : monoid_defaults<mult_tag>
      {
        typedef int1 mempty;
        typedef lambda_c<a, b, times<a, b> >::type mappend;
      };
    }
  }
}

BOOST_AUTO_TEST_CASE(test_monoid)
{
  using mpllibs::metamonad::mconcat;

  using boost::mpl::equal_to;
  using boost::mpl::list_c;
  using boost::mpl::int_;

  // test_concat_plus
  BOOST_MPL_ASSERT((
    equal_to<int13, mconcat<plus_tag, list_c<int, 1, 2, 3, 4, 3> >::type>
  ));

  // test_concat_mult
  BOOST_MPL_ASSERT((
    equal_to<int_<72>, mconcat<mult_tag, list_c<int, 1, 2, 3, 4, 3> >::type>
  ));
}


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

  @copyright 2012-2015 NumScale SAS
  @copyright 2015 J.T.Lapreste

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!

    @ingroup group-arithmetic
    Function object function implementing correct_fma capabilities

    Computes fused multiply/add of its parameter.

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

    @code
    T r = correct_fma(x, y, z);
    @endcode

    is similar to:

    @code
    T r = x*y+z;
    @endcode

    but with only one rounding

    @par Note:

    - For integer x*y+z is performed

    - For floating points numbers, always computes the correct fused multiply add,
    this means the computation of x*y+z with only one rounding operation.
    On machines not possessing this hard wired capability this can be very
    expansive.

    - @c correct_fma is in fact a transitory function that allows to ensure
    strict @ref fma capabilities,  i.e. only one rounding operation and no undue
    overflow in intermediate computations.

    - If you are using this function for a system with no hard wired @ref fma
    and are sure that overflow is not a problem
    you can define BOOST_SIMD_DONT_CARE_CORRECT_FMA_OVERFLOW to get better
    performances.

    correct_fma is never used internally in boost.simd.

    @par Decorators

    std_ for floating entries

    @see  fma
**/
     const boost::dispatch::functor<tag::correct_fma_> correct_fma = {};
} }
#endif

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

#endif


#include <iostream>
#include <cmath>
#include <boost/make_shared.hpp>
#include <problem/solver.h>
#include <communicators/mpicom.h>
#include <communicators/localcom.h>
#include <problem/subtasks.h>
#include <fstream>
#include <getopt.h>
#include <cstring>
#include <omp.h>

Solver::Solver(int& argc, char**& argv)
    : xSplit_(0)
    , ySplit_(0)
    , normOfDifference_(0)
    , maxDifference_(0)
    , iterations_(0)
    , elapsedTime_(0)
    , dump_(false)
{
    int opt;
    while (-1 != (opt = getopt(argc, argv, "m:n:d")))
    {
        switch (opt)
        {
        case 'm':
            xSplit_ = std::atoi(optarg);
            break;
        case 'n':
            ySplit_ = std::atoi(optarg);
            break;
        case 'd':
            dump_ = true;
            break;
        default:
            throw std::runtime_error("invalid arg");
            break;
        }
    }
    if (xSplit_ < 3 || ySplit_ < 3)
    {
        throw std::runtime_error("set nonzero splits");
    }

#ifdef CMC_OPENMPI
    mpi_.reset(new MpiHolder(&argc, &argv));
    com_ = boost::make_shared<MpiCommunicator>(xSplit_, ySplit_);
#else
    com_ = boost::make_shared<LocalCommunicator>();
#endif
}

void
Solver::SetProblem(Problem p, double eps)
{
    SetSubtask(
        p.domain,
        xSplit_, ySplit_,
        com_->CountSubtask(Vertical), com_->CountSubtask(Horizontal),
        com_->GetSubtask(Vertical),   com_->GetSubtask(Horizontal)
    );
    m_ = boost::make_shared<FiniteElementMethod>(com_, p, xSplit_, ySplit_);
    eps_ = eps;
}

void
Solver::Solve()
{
    double startTime = MPI_Wtime();

    Matrix w = m_->FirstApproximation();
    do
    {
        Matrix next = m_->EvaluateNext(w);
        normOfDifference_ = m_->NormOfDifference(w, next);
        w = next;
        iterations_++;
    } while (normOfDifference_ > eps_);

    double endTime = MPI_Wtime();

    elapsedTime_ = endTime - startTime;

    Matrix etalon = m_->Etalon();
    double localError = 0;
    for (int i = 0; i < w.GetRows(); i++)
    {
        for (int j = 0; j < w.GetCols(); j++)
        {
            double diff = std::fabs(w(i, j) - etalon(i, j));
            if (diff > localError)
            {
                localError = diff;
            }
        }
    }
    maxDifference_ = com_->UpdateError(localError);
    solution_ = w.Unwrapped();
}

void
Solver::PrintReport()
{
    if (dump_)
    {
        int row = com_->GetSubtask(Vertical);
        int col = com_->GetSubtask(Horizontal);
        DumpSolution(row, col);
        DumpEtalon(row, col);
    }

    if (!com_->IsMaster())
    {
        return;
    }

    std::cout << "Max difference: " << maxDifference_ << std::endl;
    std::cout << "Norm:           " << normOfDifference_ << std::endl;
    std::cout << "Iterations:     " << iterations_ << std::endl;
    std::cout << "Elapsed time:   " << elapsedTime_ << std::endl;
}

void
Solver::DumpSolution(int row, int col) const
{
    char filename[32];
    std::sprintf(filename, "solution_%d-%d", row, col);
    std::ofstream f(filename);
    for (int i = 0; i < solution_.GetRows(); i++)
    {
        for (int j = 0; j < solution_.GetCols(); j++)
        {
            f << i << ',' << j << ',' << solution_(i, j) << std::endl;
        }
    }
}

void
Solver::DumpEtalon(int row, int col) const
{
    char filename[32];
    std::sprintf(filename, "etalon_%d-%d", row, col);
    std::ofstream f(filename);

    const Matrix etalon = m_->Etalon().Unwrapped();
    for (int i = 0; i < etalon.GetRows(); i++)
    {
        for (int j = 0; j < etalon.GetCols(); j++)
        {
            f << i << ',' << j << ',' << etalon(i, j) << std::endl;
        }
    }
}


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.
// Copyright (c) 2013 Adam Wulkiewicz, Lodz, Poland.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is 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)

#ifndef BOOST_GEOMETRY_EXTENSIONS_NSPHERE_ALGORITHMS_DISJOINT_HPP
#define BOOST_GEOMETRY_EXTENSIONS_NSPHERE_ALGORITHMS_DISJOINT_HPP

#include <boost/geometry/algorithms/disjoint.hpp>

#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_same.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace disjoint
{

template
<
    typename PoS1, typename PoS2,
    std::size_t Dimension, std::size_t DimensionCount
>
struct points_or_spheres_comparable_distance_cartesian
{
    typedef typename boost::mpl::if_<
        ::boost::is_same<typename tag<PoS1>::type, point_tag>, PoS1, typename point_type<PoS1>::type
    >::type point1_type;

    typedef typename boost::mpl::if_<
        ::boost::is_same<typename tag<PoS2>::type, point_tag>, PoS2, typename point_type<PoS2>::type
    >::type point2_type;

    typedef typename geometry::select_most_precise<
        typename coordinate_type<point1_type>::type, typename coordinate_type<point2_type>::type
    >::type coordinate_type;

    typedef typename geometry::default_distance_result<
        point1_type, point2_type
    >::type result_type;

    static inline result_type apply(PoS1 const& g1, PoS2 const& g2)
    {
        coordinate_type tmp = get<Dimension>(g1) - get<Dimension>(g2);

        return tmp * tmp + points_or_spheres_comparable_distance_cartesian<
                               PoS1, PoS2, Dimension + 1, DimensionCount
                           >::apply(g1, g2);
    }
};

template <typename PoS1, typename PoS2, std::size_t DimensionCount>
struct points_or_spheres_comparable_distance_cartesian<PoS1, PoS2, DimensionCount, DimensionCount>
{
    static inline int apply(PoS1 const& , PoS2 const& )
    {
        return 0;
    }
};

// Arvo's algorithm implemented
// TODO - implement the method mentioned in the article below and compare performance
// "On Faster Sphere-Box Overlap Testing" - Larsson, T.; Akeine-Moller, T.; Lengyel, E.
template
<
    typename Box, typename NSphere,
    std::size_t Dimension, std::size_t DimensionCount
>
struct box_nsphere_comparable_distance_cartesian
{
    typedef typename geometry::select_most_precise<
        typename coordinate_type<typename point_type<Box>::type>::type,
        typename coordinate_type<typename point_type<NSphere>::type>::type
    >::type coordinate_type;

    typedef typename geometry::default_distance_result<
        Box,
        typename point_type<NSphere>::type
    >::type result_type;

    static inline result_type apply(Box const& box, NSphere const& nsphere)
    {
        result_type r = 0;

        if( get<Dimension>(nsphere) < get<min_corner, Dimension>(box) )
        {
            coordinate_type tmp = get<min_corner, Dimension>(box) - get<Dimension>(nsphere);
            r = tmp*tmp;
        }
        else if( get<max_corner, Dimension>(box) < get<Dimension>(nsphere) )
        {
            coordinate_type tmp = get<Dimension>(nsphere) - get<max_corner, Dimension>(box);
            r = tmp*tmp;
        }

        return r + box_nsphere_comparable_distance_cartesian<
                        Box, NSphere, Dimension + 1, DimensionCount
                    >::apply(box, nsphere);
    }
};

template <typename Box, typename NSphere, std::size_t DimensionCount>
struct box_nsphere_comparable_distance_cartesian<Box, NSphere, DimensionCount, DimensionCount>
{
    static inline int apply(Box const& , NSphere const& )
    {
        return 0;
    }
};

}} // namespace detail::disjoint
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename Point, typename NSphere, std::size_t DimensionCount, bool Reverse>
struct disjoint<Point, NSphere, DimensionCount, point_tag, nsphere_tag, Reverse>
{
    static inline bool apply(Point const& p, NSphere const& s)
    {
        typedef typename coordinate_system<Point>::type p_cs;
        typedef typename typename coordinate_system<NSphere>::type s_cs;
        static const bool check_cs = ::boost::is_same<p_cs, cs::cartesian>::value && ::boost::is_same<s_cs, cs::cartesian>::value;
        BOOST_MPL_ASSERT_MSG(check_cs, NOT_IMPLEMENTED_FOR_THOSE_COORDINATE_SYSTEMS, (p_cs, s_cs));

        return get_radius<0>(s) * get_radius<0>(s)
               <   geometry::detail::disjoint::points_or_spheres_comparable_distance_cartesian<
                       Point, NSphere, 0, DimensionCount
                   >::apply(p, s);
    }
};

template <typename NSphere, typename Box, std::size_t DimensionCount, bool Reverse>
struct disjoint<NSphere, Box, DimensionCount, nsphere_tag, box_tag, Reverse>
{
    static inline bool apply(NSphere const& s, Box const& b)
    {
        typedef typename coordinate_system<Box>::type b_cs;
        typedef typename typename coordinate_system<NSphere>::type s_cs;
        static const bool check_cs = ::boost::is_same<b_cs, cs::cartesian>::value && ::boost::is_same<s_cs, cs::cartesian>::value;
        BOOST_MPL_ASSERT_MSG(check_cs, NOT_IMPLEMENTED_FOR_THOSE_COORDINATE_SYSTEMS, (b_cs, s_cs));

        return get_radius<0>(s) * get_radius<0>(s)
               <   geometry::detail::disjoint::box_nsphere_comparable_distance_cartesian<
                       Box, NSphere, 0, DimensionCount
                   >::apply(b, s);
    }
};

template <typename NSphere1, typename NSphere2, std::size_t DimensionCount, bool Reverse>
struct disjoint<NSphere1, NSphere2, DimensionCount, nsphere_tag, nsphere_tag, Reverse>
{
    static inline bool apply(NSphere1 const& s1, NSphere2 const& s2)
    {
        typedef typename typename coordinate_system<NSphere1>::type s1_cs;
        typedef typename typename coordinate_system<NSphere2>::type s2_cs;
        static const bool check_cs = ::boost::is_same<s1_cs, cs::cartesian>::value && ::boost::is_same<s2_cs, cs::cartesian>::value;
        BOOST_MPL_ASSERT_MSG(check_cs, NOT_IMPLEMENTED_FOR_THOSE_COORDINATE_SYSTEMS, (s1_cs, s2_cs));

        return get_radius<0>(s1) + get_radius<0>(s2)
               <   ::sqrt(geometry::detail::disjoint::points_or_spheres_comparable_distance_cartesian<
                              Point, NSphere, 0, DimensionCount
                          >::apply(p, s));
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_EXTENSIONS_NSPHERE_ALGORITHMS_DISJOINT_HPP


#include "compute_ranks.h"
#include "powerscore.h"
#include <cmath>
#include <Eigen/Dense>

// This initializes an instance of the class
ComputeRanks::ComputeRanks(const Napi::CallbackInfo& info) : ObjectWrap(info) {
    Napi::Env env = info.Env();
    // Validate input
    if (info.Length() < 1) {
        Napi::TypeError::New(env, "ComputeRanks must be initialized with 1 argument")
          .ThrowAsJavaScriptException();
        return;
    }
    if (!info[0].IsNumber()) {
        Napi::TypeError::New(env, "Dimension must be a number")
          .ThrowAsJavaScriptException();
        return;
    }
    // Test if the argument is an integer
    if (std::trunc(info[0].As<Napi::Number>().DoubleValue()) != info[0].As<Napi::Number>().DoubleValue()) {
      Napi::TypeError::New(env, "Please specify an integer dimension")
        .ThrowAsJavaScriptException();
      return;
    }
    this->_dimension = info[0].As<Napi::Number>().DoubleValue();
    this->_A = Eigen::MatrixXd::Zero(this->_dimension, this->_dimension);
}

Napi::Value ComputeRanks::ComputePowerScores(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    if (info.Length() < 1) {
        Napi::TypeError::New(env, "Wrong number of arguments")
          .ThrowAsJavaScriptException();
        return env.Null();
    }

    if (!info[0].IsFunction()) {
        Napi::TypeError::New(env, "Callback must be a JavaScript function")
          .ThrowAsJavaScriptException();
        return env.Null();
    }

    Napi::Function callback = info[0].As<Napi::Function>();

    PowerScoreWorker* worker = new PowerScoreWorker(callback, this->_A);
    worker->Queue();
    // Return immediately, while the work is being done asynchonously
    return info.Env().Undefined();
}

Napi::Value ComputeRanks::UpdateAggressionMatrix(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    // Check that there are exactly 2 arguments
    if (info.Length() != 2) {
        Napi::TypeError::New(env, "UpdateAggressionMatrix needs 2 arguments")
          .ThrowAsJavaScriptException();
        return env.Null();
    }
    // Check that arguments are numbers
    if (!info[0].IsNumber() || !info[1].IsNumber()) {
        Napi::TypeError::New(env, "Arguments to UpdateAggressionMatrix must be numbers")
          .ThrowAsJavaScriptException();
        return env.Null();
    }
    // Check that arguments are integers
    if ( (std::trunc(info[0].As<Napi::Number>().DoubleValue()) != info[0].As<Napi::Number>().DoubleValue()) ||
         (std::trunc(info[1].As<Napi::Number>().DoubleValue()) != info[1].As<Napi::Number>().DoubleValue()) ) {
      Napi::TypeError::New(env, "Arguments to UpdateAggressionMatrix must be integers")
        .ThrowAsJavaScriptException();
      return env.Null();
    }
    int i = info[0].As<Napi::Number>().Int32Value();
    int j = info[1].As<Napi::Number>().Int32Value();
    // Check that arguments are within expected limits 0 <= i,j < this->_dimension and not equal
    if (i < 0 || j < 0) {
      Napi::TypeError::New(env, "Arguments to UpdateAggressionMatrix must be non-negative")
        .ThrowAsJavaScriptException();
      return env.Null();
    }
    if (i >= this->_dimension || j >= this->_dimension) {
      Napi::TypeError::New(env, "Arguments to UpdateAggressionMatrix must be less than the dimension of the aggression matrix")
        .ThrowAsJavaScriptException();
      return env.Null();
    }
    if (i == j) {
      Napi::TypeError::New(env, "Arguments to UpdateAggressionMatrix must not be identical (no self-aggression)")
        .ThrowAsJavaScriptException();
      return env.Null();
    }
    // Add the attack to aggression matrix
    this->_A(i, j) += 1;
    return env.Null();
}

// Define the methods that can be called from JavaScript
Napi::Function ComputeRanks::GetClass(Napi::Env env) {
    return DefineClass(env, "ComputeRanks", {
        ComputeRanks::InstanceMethod("computePowerScores", &ComputeRanks::ComputePowerScores),
        ComputeRanks::InstanceMethod("updateAggressionMatrix", &ComputeRanks::UpdateAggressionMatrix),
    });
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
    Napi::String name = Napi::String::New(env, "ComputeRanks");
    exports.Set(name, ComputeRanks::GetClass(env));
    return exports;
}

NODE_API_MODULE(addon, Init)


#include <iostream>
#include <fstream>
#include <map>
#include <algorithm>

#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/IO/Polyhedron_iostream.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>
#include <CGAL/boost/graph/properties_Surface_mesh.h>

#include <CGAL/Timer.h>
#include<boost/range/iterator_range.hpp>
#include <boost/unordered_map.hpp>
#include <unordered_map>

#include <boost/random/random_number_generator.hpp>
#include <boost/random/linear_congruential.hpp>

typedef CGAL::Simple_cartesian<double>  Kernel;
typedef Kernel::Point_3                 Point_3;
typedef Kernel::Vector_3                Vector_3;

typedef CGAL::Surface_mesh<Point_3>      Mesh;
typedef boost::graph_traits<Mesh>::vertex_descriptor vertex_descriptor;

typedef CGAL::Timer                     Timer;

void  fct(int ii, int jj)
{
  typedef std::map<vertex_descriptor,Point_3> SM;
  typedef std::unordered_map<vertex_descriptor,Point_3> SUM;
  typedef boost::unordered_map<vertex_descriptor,Point_3> BUM;

  Mesh mesh;
  typedef boost::property_map<Mesh,CGAL::vertex_point_t>::type  VPM;
  VPM vpm = get(CGAL::vertex_point,mesh);

  Vector_3 v(0,0,0);

  for(int i =0; i < ii; i++){
    vertex_descriptor vd = add_vertex(mesh);
    put(vpm,vd, Point_3(i,0,0));
  }

  std::vector<vertex_descriptor> V;
  for(vertex_descriptor vd : vertices(mesh)){
    V.push_back(vd);
  }
  random_shuffle(V.begin(), V.end());

  Timer tsmc, tsumc, tbumc;
  Timer tsmq, tsumq, tbumq;
  for(int j=0; j <jj; j++){

    {
      tsmc.start();
      SM sm;
      for(vertex_descriptor vh : V){
        sm[vh] = get(vpm,vh);
      }
      tsmc.stop();

      tsmq.start();
      for(vertex_descriptor vh : V){
        v = v + (sm[vh] - CGAL::ORIGIN);
      }
      tsmq.stop();
    }
    {
      tsumc.start();
      SUM sm;
      for(vertex_descriptor vh : V){
        sm[vh] = get(vpm,vh);
      }
      tsumc.stop();

      tsumq.start();
      for(vertex_descriptor vh : V){
        v = v + (sm[vh] - CGAL::ORIGIN);
      }
      tsumq.stop();
    }
    {
      tbumc.start();
      BUM sm;
      for(vertex_descriptor vh : V){
        sm[vh] = get(vpm,vh);
      }
      tbumc.stop();

      tbumq.start();
      for(vertex_descriptor vh : V){
        v = v + (sm[vh] - CGAL::ORIGIN);
      }
      tbumq.stop();
    }
  }
  std::cerr << ii << " items and queries (repeated " << jj << " times)" << std::endl;
  std::cerr << "std::map             construction : "<< tsmc.time() << " sec." << std::endl;
  std::cerr << "std::map             queries      : "<< tsmq.time() << " sec." << std::endl;

  std::cerr << "std::unordered_map   construction : "<< tsumc.time() << " sec." << std::endl;
  std::cerr << "std::unordered_map   queries      : "<< tsumq.time() << " sec." << std::endl;

  std::cerr << "boost::unordered_map construction : "<< tbumc.time() << " sec." << std::endl;
  std::cerr << "boost::unordered_map queries      : "<< tbumq.time() << " sec.\n" << std::endl;

}

int main(int , char* argv[])
{
  fct(1000000, 10);
  fct(100000, 100);
  fct(10000, 1000);
  fct(1000, 10000);
  fct(500,  20000);
  fct(250,  40000);
  fct(100, 100000);
  return 0;
}


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

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
//! [shuffle-perm]
#include <boost/simd/pack.hpp>
#include <boost/simd/function/shuffle.hpp>
#include <iostream>

struct last_
{
  template<typename I, typename C> struct apply : std::integral_constant<int, C::value-1> {};
};

constexpr int reverse(int i, int c)
{
  return c - 1 - i;
}

int main()
{
  boost::simd::pack<float,4> x{1.f,2.f,3.f,4.f};

  std::cout << "Original: " << x <<  std::endl
            << "Permuted: " << boost::simd::shuffle<boost::simd::pattern<reverse>>(x) << std::endl
            << "Permuted: " << boost::simd::shuffle<last_>(x) << std::endl;

  return 0;
}
//! [shuffle-perm]


#pragma once
#include "Misc.hpp"
#include <algorithm>
#include <boost/iterator/iterator_facade.hpp>
#include <cassert>
#include <vector>

namespace discreture
{

// An integer interval is a closed-open interval of integers. For example,
// IntegerInterval(3,8) = {3,4,5,6,7}.
template <class IntType = int>
class IntegerInterval
{
public:
    static_assert(std::is_integral<IntType>::value,
                  "Template parameter IntType must be integral");
    using value_type = IntType;
    using difference_type = std::ptrdiff_t;
    using size_type = difference_type;
    class iterator;
    using const_iterator = iterator;
    class reverse_iterator;
    using const_reverse_iterator = reverse_iterator;

public:
    explicit IntegerInterval() = default;

    explicit IntegerInterval(IntType n) : last_(n)
    {
        if (last_ < 0)
            last_ = 0;
    }

    explicit IntegerInterval(IntType from, IntType to) : first_(from), last_(to)
    {
        if (last_ < first_) // empty interval
            last_ = first_;
    }

    size_type size() const { return last_ - first_; }
    IntType operator[](size_type i) const { return first_ + i; }

    class iterator
        : public boost::iterator_facade<iterator, const IntType&, boost::random_access_traversal_tag>
    {
    public:
        explicit iterator(IntType t = 0) : value_(t) {}

    private:
        void increment() { ++value_; }

        void decrement() { --value_; }

        const IntType& dereference() const { return value_; }

        void advance(difference_type n) { value_ += n; }

        bool equal(const iterator& other) const
        {
            return value_ == other.value_;
        }

        difference_type distance_to(const iterator& other) const
        {
            return static_cast<difference_type>(other.value_) - value_;
        }

    private:
        IntType value_{0};

        friend class boost::iterator_core_access;
    }; // end class iterator

    iterator begin() const { return iterator(first_); }
    iterator end() const { return iterator(last_); }

    class reverse_iterator
        : public boost::iterator_facade<reverse_iterator,
                                        const IntType&,
                                        boost::random_access_traversal_tag>
    {
    public:
        explicit reverse_iterator(IntType t = 0) : value_(t) {}

    private:
        void increment() { --value_; }

        void decrement() { ++value_; }

        const IntType& dereference() const { return value_; }

        void advance(difference_type n) { value_ -= n; }

        bool equal(const reverse_iterator& other) const
        {
            return value_ == other.value_;
        }

        difference_type distance_to(const reverse_iterator& other) const
        {
            return value_ - other.value_;
        }

    private:
        IntType value_{0};

        friend class boost::iterator_core_access;
    }; // end class iterator

    reverse_iterator rbegin() const { return reverse_iterator(last_ - 1); }
    reverse_iterator rend() const { return reverse_iterator(first_ - 1); }

    // returns the first integer that does NOT satisfy Predicate
    template <class Predicate>
    IntType partition_point(Predicate p)
    {
        return *std::partition_point(begin(), end(), p);
    }

private:
    IntType first_{0};
    IntType last_{0};
}; // end class IntegerInterval

using integer_interval = IntegerInterval<int>;
using big_integer_interval = IntegerInterval<std::int64_t>;

// Think of NN as the set of natural numbers.
template <class IntType>
auto NN(IntType n)
{
    return IntegerInterval<IntType>{n};
}

template <class IntTypeFrom, class IntTypeTo>
auto II(IntTypeFrom from, IntTypeTo to)
{
    using intt = std::common_type_t<IntTypeFrom, IntTypeTo>;
    return IntegerInterval<intt>{from, to};
}

template <class Container, class Index = std::ptrdiff_t>
auto indices(const Container& C)
{
    return IntegerInterval<Index>(C.size());
}

} // namespace discreture


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

// Currently needs -DMTL_DEEP_COPY_CONSTRUCTOR !!!

#include <iostream>
#include <boost/numeric/mtl/mtl.hpp>

int main(int, char**)
{
    using namespace std;

    mtl::compressed2D<double> res(3, 3), m(3, 3);
    {
	mtl::mat::inserter<mtl::compressed2D<double> > ins(m);
	ins(0, 1) << 2.0; ins(1, 0) << 1.0;
	ins(1, 1) << 4.0; ins(2, 2) << 5.0;
    }

    res = trans(m)*m;
    cout << res;

    MTL_THROW_IF(res[0][1] != 4.0, mtl::runtime_error("res[0][1] should be 4.0!\n"));

    return 0;
}


/* ----------------------------------------------------------------------------

 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)

 * See LICENSE for the license information

 * -------------------------------------------------------------------------- */

/**
 * @file    testTangentPreintegration.cpp
 * @brief   Unit test for the InertialNavFactor
 * @author  Frank Dellaert
 */

#include <gtsam/navigation/TangentPreintegration.h>
#include <gtsam/base/numericalDerivative.h>
#include <gtsam/nonlinear/expressions.h>
#include <gtsam/nonlinear/ExpressionFactor.h>
#include <gtsam/nonlinear/expressionTesting.h>

#include <CppUnitLite/TestHarness.h>
#include <boost/bind.hpp>

#include "imuFactorTesting.h"

static const double kDt = 0.1;

Vector9 f(const Vector9& zeta, const Vector3& a, const Vector3& w) {
  return TangentPreintegration::UpdatePreintegrated(a, w, kDt, zeta);
}

namespace testing {
// Create default parameters with Z-down and above noise parameters
static std::shared_ptr<PreintegrationParams> Params() {
  auto p = PreintegrationParams::MakeSharedD(kGravity);
  p->gyroscopeCovariance = kGyroSigma * kGyroSigma * I_3x3;
  p->accelerometerCovariance = kAccelSigma * kAccelSigma * I_3x3;
  p->integrationCovariance = 0.0001 * I_3x3;
  return p;
}
}

/* ************************************************************************* */
TEST(TangentPreintegration, UpdateEstimate1) {
  TangentPreintegration pim(testing::Params());
  const Vector3 acc(0.1, 0.2, 10), omega(0.1, 0.2, 0.3);
  Vector9 zeta;
  zeta.setZero();
  Matrix9 aH1;
  Matrix93 aH2, aH3;
  pim.UpdatePreintegrated(acc, omega, kDt, zeta, aH1, aH2, aH3);
  EXPECT(assert_equal(numericalDerivative31(f, zeta, acc, omega), aH1, 1e-9));
  EXPECT(assert_equal(numericalDerivative32(f, zeta, acc, omega), aH2, 1e-9));
  EXPECT(assert_equal(numericalDerivative33(f, zeta, acc, omega), aH3, 1e-9));
}

/* ************************************************************************* */
TEST(TangentPreintegration, UpdateEstimate2) {
  TangentPreintegration pim(testing::Params());
  const Vector3 acc(0.1, 0.2, 10), omega(0.1, 0.2, 0.3);
  Vector9 zeta;
  zeta << 0.01, 0.02, 0.03, 100, 200, 300, 10, 5, 3;
  Matrix9 aH1;
  Matrix93 aH2, aH3;
  pim.UpdatePreintegrated(acc, omega, kDt, zeta, aH1, aH2, aH3);
  // NOTE(frank): tolerance of 1e-3 on H1 because approximate away from 0
  EXPECT(assert_equal(numericalDerivative31(f, zeta, acc, omega), aH1, 1e-3));
  EXPECT(assert_equal(numericalDerivative32(f, zeta, acc, omega), aH2, 1e-8));
  EXPECT(assert_equal(numericalDerivative33(f, zeta, acc, omega), aH3, 1e-9));
}

/* ************************************************************************* */
TEST(ImuFactor, BiasCorrectionJacobians) {
  testing::SomeMeasurements measurements;

  boost::function<Vector9(const Vector3&, const Vector3&)> preintegrated =
      [=](const Vector3& a, const Vector3& w) {
        TangentPreintegration pim(testing::Params(), Bias(a, w));
        testing::integrateMeasurements(measurements, &pim);
        return pim.preintegrated();
      };

  // Actual pre-integrated values
  TangentPreintegration pim(testing::Params());
  testing::integrateMeasurements(measurements, &pim);

  EXPECT(
      assert_equal(numericalDerivative21(preintegrated, kZero, kZero),
          pim.preintegrated_H_biasAcc()));
  EXPECT(
      assert_equal(numericalDerivative22(preintegrated, kZero, kZero),
          pim.preintegrated_H_biasOmega(), 1e-3));
}

/* ************************************************************************* */
TEST(TangentPreintegration, computeError) {
  TangentPreintegration pim(testing::Params());
  NavState x1, x2;
  imuBias::ConstantBias bias;
  Matrix9 aH1, aH2;
  Matrix96 aH3;
  pim.computeError(x1, x2, bias, aH1, aH2, aH3);
  boost::function<Vector9(const NavState&, const NavState&,
                          const imuBias::ConstantBias&)> f =
      boost::bind(&TangentPreintegration::computeError, pim, _1, _2, _3,
                  boost::none, boost::none, boost::none);
  // NOTE(frank): tolerance of 1e-3 on H1 because approximate away from 0
  EXPECT(assert_equal(numericalDerivative31(f, x1, x2, bias), aH1, 1e-9));
  EXPECT(assert_equal(numericalDerivative32(f, x1, x2, bias), aH2, 1e-9));
  EXPECT(assert_equal(numericalDerivative33(f, x1, x2, bias), aH3, 1e-9));
}

/* ************************************************************************* */
TEST(TangentPreintegration, Compose) {
  testing::SomeMeasurements measurements;
  TangentPreintegration pim(testing::Params());
  testing::integrateMeasurements(measurements, &pim);

  boost::function<Vector9(const Vector9&, const Vector9&)> f =
      [pim](const Vector9& zeta01, const Vector9& zeta12) {
        return TangentPreintegration::Compose(zeta01, zeta12, pim.deltaTij());
      };

  // Expected merge result
  TangentPreintegration expected_pim02(testing::Params());
  testing::integrateMeasurements(measurements, &expected_pim02);
  testing::integrateMeasurements(measurements, &expected_pim02);

  // Actual result
  Matrix9 H1, H2;
  TangentPreintegration actual_pim02 = pim;
  actual_pim02.mergeWith(pim, &H1, &H2);

  const Vector9 zeta = pim.preintegrated();
  const Vector9 actual_zeta =
      TangentPreintegration::Compose(zeta, zeta, pim.deltaTij());
  EXPECT(assert_equal(expected_pim02.preintegrated(), actual_zeta, 1e-7));
  EXPECT(assert_equal(numericalDerivative21(f, zeta, zeta), H1, 1e-7));
  EXPECT(assert_equal(numericalDerivative22(f, zeta, zeta), H2, 1e-7));
}

/* ************************************************************************* */
TEST(TangentPreintegration, MergedBiasDerivatives) {
  testing::SomeMeasurements measurements;

  auto f = [=](const Vector3& a, const Vector3& w) {
    TangentPreintegration pim02(testing::Params(), Bias(a, w));
    testing::integrateMeasurements(measurements, &pim02);
    testing::integrateMeasurements(measurements, &pim02);
    return pim02.preintegrated();
  };

  // Expected merge result
  TangentPreintegration expected_pim02(testing::Params());
  testing::integrateMeasurements(measurements, &expected_pim02);
  testing::integrateMeasurements(measurements, &expected_pim02);

  EXPECT(assert_equal(numericalDerivative21<Vector9, Vector3, Vector3>(f, Z_3x1, Z_3x1),
                      expected_pim02.preintegrated_H_biasAcc()));
  EXPECT(assert_equal(numericalDerivative22<Vector9, Vector3, Vector3>(f, Z_3x1, Z_3x1),
                      expected_pim02.preintegrated_H_biasOmega(), 1e-7));
}

/* ************************************************************************* */
int main() {
  TestResult tr;
  return TestRegistry::runAllTests(tr);
}
/* ************************************************************************* */


//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#include <boost/config.hpp>
#include <iostream>
#include <fstream>
#include <boost/graph/adjacency_list.hpp>

using namespace boost;

template < typename VertexDescriptor, typename VertexNameMap >
void print_vertex_name(VertexDescriptor v, VertexNameMap name_map)
{
    std::cout << get(name_map, v);
}

template < typename Graph, typename TransDelayMap, typename VertexNameMap >
void print_trans_delay(typename graph_traits< Graph >::edge_descriptor e,
    const Graph& g, TransDelayMap delay_map, VertexNameMap name_map)
{
    std::cout << "trans-delay(" << get(name_map, source(e, g)) << ","
              << get(name_map, target(e, g)) << ") = " << get(delay_map, e);
}

template < typename Graph, typename VertexNameMap >
void print_vertex_names(const Graph& g, VertexNameMap name_map)
{
    std::cout << "vertices(g) = { ";
    typedef typename graph_traits< Graph >::vertex_iterator iter_t;
    for (std::pair< iter_t, iter_t > p = vertices(g); p.first != p.second;
         ++p.first)
    {
        print_vertex_name(*p.first, name_map);
        std::cout << ' ';
    }
    std::cout << "}" << std::endl;
}

template < typename Graph, typename TransDelayMap, typename VertexNameMap >
void print_trans_delays(
    const Graph& g, TransDelayMap trans_delay_map, VertexNameMap name_map)
{
    typename graph_traits< Graph >::edge_iterator first, last;
    for (boost::tie(first, last) = edges(g); first != last; ++first)
    {
        print_trans_delay(*first, g, trans_delay_map, name_map);
        std::cout << std::endl;
    }
}

template < typename Graph, typename VertexNameMap, typename TransDelayMap >
void build_router_network(
    Graph& g, VertexNameMap name_map, TransDelayMap delay_map)
{
    typename graph_traits< Graph >::vertex_descriptor a, b, c, d, e;
    a = add_vertex(g);
    name_map[a] = 'a';
    b = add_vertex(g);
    name_map[b] = 'b';
    c = add_vertex(g);
    name_map[c] = 'c';
    d = add_vertex(g);
    name_map[d] = 'd';
    e = add_vertex(g);
    name_map[e] = 'e';

    typename graph_traits< Graph >::edge_descriptor ed;
    bool inserted;

    boost::tie(ed, inserted) = add_edge(a, b, g);
    delay_map[ed] = 1.2;
    boost::tie(ed, inserted) = add_edge(a, d, g);
    delay_map[ed] = 4.5;
    boost::tie(ed, inserted) = add_edge(b, d, g);
    delay_map[ed] = 1.8;
    boost::tie(ed, inserted) = add_edge(c, a, g);
    delay_map[ed] = 2.6;
    boost::tie(ed, inserted) = add_edge(c, e, g);
    delay_map[ed] = 5.2;
    boost::tie(ed, inserted) = add_edge(d, c, g);
    delay_map[ed] = 0.4;
    boost::tie(ed, inserted) = add_edge(d, e, g);
    delay_map[ed] = 3.3;
}

int main()
{
    typedef adjacency_list< listS, listS, directedS,
        property< vertex_name_t, char >, property< edge_weight_t, double > >
        graph_t;
    graph_t g;

    property_map< graph_t, vertex_name_t >::type name_map = get(vertex_name, g);
    property_map< graph_t, edge_weight_t >::type delay_map
        = get(edge_weight, g);

    build_router_network(g, name_map, delay_map);
    print_vertex_names(g, name_map);
    print_trans_delays(g, delay_map, name_map);
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2010-2012 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is 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)

#include <iostream>
#include <string>

#include <geometry_test_common.hpp>

#include <boost/geometry/algorithms/detail/overlay/dissolver.hpp>

#include <boost/geometry/strategies/strategies.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/multi/multi.hpp>
#include <boost/geometry/multi/geometries/multi_polygon.hpp>
#include <boost/geometry/multi/io/wkt/read.hpp>

#include <test_common/test_point.hpp>

#if defined(TEST_WITH_SVG)
#  include <boost/geometry/io/svg/svg_mapper.hpp>
#  include <boost/geometry/io/svg/write_svg_multi.hpp>
#endif

// Collection might be a multi-geometry, or std::vector<ring>
template <typename GeometryOut, typename Collection, typename T>
void test_dissolve_plusmin(std::string const& caseid, Collection const& input,
            T const& expected_positive_area,
            T const& expected_negative_area)
{
    typedef typename boost::range_value<GeometryOut>::type geometry_type;
    typedef typename bg::point_type<geometry_type>::type point_type;

    GeometryOut output;
    bg::dissolver(input, output);

    T zero = T();
    T positive_area = T();
    T negative_area = T();

    BOOST_FOREACH(geometry_type const& geometry, output)
    {
        T a = bg::area(geometry);
        if (a > zero)
        {
            positive_area += a;
        }
        else
        {
            negative_area += a;
        }
    }

    BOOST_CHECK_CLOSE(positive_area, expected_positive_area, 0.001);
    BOOST_CHECK_CLOSE(negative_area, expected_negative_area, 0.001);

#if defined(TEST_WITH_SVG)
    {
        std::ostringstream filename;
        filename << "dissolve_plusmin_"
            << caseid << ".svg";

        std::ofstream svg(filename.str().c_str());

        bg::svg_mapper<point_type> mapper(svg, 500, 500);

        typedef typename boost::range_value<Collection>::type value_type;
        BOOST_FOREACH(value_type const& geometry, input)
        {
            mapper.add(geometry);
        }

        BOOST_FOREACH(value_type const& geometry, input)
        {
            mapper.map(geometry,
                "opacity:0.6;fill:rgb(0,255,0);stroke:rgb(0,0,0);stroke-width:0.5");
        }
        BOOST_FOREACH(geometry_type const& geometry, output)
        {
            mapper.map(geometry,
                bg::area(geometry) > 0
                ? "opacity:0.5;fill:none;stroke:rgb(255,0,0);stroke-width:5"
                : "opacity:0.5;fill:none;stroke:rgb(0,0,255);stroke-width:5"
                );
        }
    }
#endif

}

template <typename MultiPolygon, typename T>
void test_geometry(std::string const& caseid, std::string const& wkt,
            T const& expected_positive_area,
            T const& expected_negative_area = T())
{

    MultiPolygon multi_polygon;
    bg::read_wkt(wkt, multi_polygon);

    // Test std::vector<Polygon> (= multi_polygon)
    test_dissolve_plusmin<MultiPolygon>(caseid, multi_polygon,
        expected_positive_area,
        expected_negative_area);

    // Test std::vector<ring>
    {
        typedef typename boost::range_value<MultiPolygon>::type polygon_type;
        typedef typename bg::ring_type<MultiPolygon>::type ring_type;
        std::vector<ring_type> rings;
        BOOST_FOREACH(polygon_type const& polygon, multi_polygon)
        {
            rings.push_back(bg::exterior_ring(polygon));
        }

        test_dissolve_plusmin<MultiPolygon>(caseid + "_rings", rings,
            expected_positive_area,
            expected_negative_area);
    }

    // Test different combinations
#define BOOST_GEOMETRY_TEST_PERMUTATIONS
#ifdef BOOST_GEOMETRY_TEST_PERMUTATIONS

    int n = multi_polygon.size();

    // test them in all orders
    std::vector<int> indices;
    for (int i = 0; i < n; i++)
    {
        indices.push_back(i);
    }
    int permutation = 0;
    do
    {
        std::ostringstream out;
        out << caseid;
        MultiPolygon multi_polygon2;
        for (int i = 0; i < n; i++)
        {
            int index = indices[i];
            out << "_" << index;
            multi_polygon2.push_back(multi_polygon[index]);
        }
        test_dissolve_plusmin<MultiPolygon>(out.str(), multi_polygon2, expected_positive_area,
                expected_negative_area);
    } while (std::next_permutation(indices.begin(), indices.end()));
#endif
}

template <typename Point>
void test_all()
{
    typedef bg::model::polygon<Point> polygon;
    typedef bg::model::multi_polygon<polygon> multi_polygon;

    test_geometry<multi_polygon>("simplex_one",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)))",
        7.5);

    test_geometry<multi_polygon>("simplex_two",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)))",
        14.7);
    test_geometry<multi_polygon>("simplex_three",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)))",
        16.7945);
    test_geometry<multi_polygon>("simplex_four",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((5 5,7 7,8 4,5 5)))",
        20.7581);

    // disjoint
    test_geometry<multi_polygon>("simplex_disjoint",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((1 6,2 10,5 7,1 6)),((3 4,5 6,6 2,3 4)),((6 5,8 7,9 4,6 5)))",
        24.0);

    // new hole of four
    test_geometry<multi_polygon>("new_hole",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,3 6,6 3,2 2)),((3 4,5 6,6 2,3 4)),((3 1,5 4,8 4,3 1)))",
        19.5206);

    // intersection of positive/negative ring
    test_geometry<multi_polygon>("plus_min_one",
        "MULTIPOLYGON(((0 0,1 4,4 1,0 0)),((2 2,6 3,3 6,2 2)))",
        7.5, -7.2);

    // negative ring within a positive ring
    test_geometry<multi_polygon>("plus_min_one_within",
        "MULTIPOLYGON(((0 0,1 7,7 3,0 0)),((1 2,4 4,2 5,1 2)))",
        23.0, -3.5);

    // from buffer
    test_geometry<multi_polygon>("from_buffer_1",
        "MULTIPOLYGON(((2.4 3.03431,1.71716 3.71716,2.4 4,2.4 3.03431))"
            ",((2.4 1.96569,2.4 1,1.71716 1.28284,2.4 1.96569))"
            ",((2.93431 2.5,2.4 3.03431,2.4 1.96569,2.93431 2.5))"
            ",((3.06569 2.5,3 2.43431,2.93431 2.5,3 2.56569,3.06569 2.5))"
            ",((-0.4 5.4,4.4 5.4,4.4 3.83431,3.06569 2.5,4.4 1.16569,4.4 -0.4,-0.4 -0.4,-0.4 5.4)))"
            ,
        26.0596168239, -0.2854871761);

}

int test_main(int, char* [])
{
    test_all<bg::model::d2::point_xy<double> >();
    return 0;
}


//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// 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)
//=======================================================================
//
// This example is similar to the one in edge_property.cpp.
// The only difference is that this example uses exterior
// property storage instead of interior (properties).
//
//  Sample output:
//
//    0        --(10, 8)--> 1
//
//    1        --(20, 12)--> 4        --(40, 12)--> 3
//            <--(10,8)-- 0        <--(20,16)-- 2
//    2        --(20, 16)--> 1
//            <--(20,16)-- 5
//    3        --(40, 12)--> 6
//            <--(40,12)-- 1
//    4        --(20, 12)--> 7
//            <--(20,12)-- 1
//    5        --(20, 16)--> 2
//            <--(20,16)-- 6
//    6        --(20, 16)--> 5         --(10, 8)--> 8
//            <--(20,12)-- 7 <--(40,12)-- 3
//    7        --(20, 12)--> 6
//            <--(20,12)-- 4
//    8
//            <--(10,8)-- 6

#include <boost/config.hpp>
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/property_map/property_map.hpp>

template <class Graph, class Capacity, class Flow>
void print_network(Graph& G, Capacity capacity, Flow flow)
{
  typedef typename boost::graph_traits<Graph>::vertex_iterator    Viter;
  typedef typename boost::graph_traits<Graph>::out_edge_iterator OutEdgeIter;
  typedef typename boost::graph_traits<Graph>::in_edge_iterator InEdgeIter;

  Viter ui, uiend;
  for (boost::tie(ui, uiend) = boost::vertices(G); ui != uiend; ++ui) {
    OutEdgeIter out, out_end;
    std::cout << *ui << "\t";

    for(boost::tie(out, out_end) = boost::out_edges(*ui, G); out != out_end; ++out)
      std::cout << "--(" << boost::get(capacity, *out) << ", "
           << boost::get(flow, *out) << ")--> " << boost::target(*out,G) << "\t";
    std::cout << std::endl << "\t";

    InEdgeIter in, in_end;
    for(boost::tie(in, in_end) = boost::in_edges(*ui, G); in != in_end; ++in)
      std::cout << "<--(" << boost::get(capacity, *in) << "," << boost::get(flow, *in) << ")-- "
           << boost::source(*in, G) << "\t";
    std::cout << std::endl;
  }
}

int main(int , char* []) {

  typedef boost::adjacency_list<boost::vecS, boost::vecS,
    boost::bidirectionalS, boost::no_property,
    boost::property<boost::edge_index_t, std::size_t> > Graph;

  const int num_vertices = 9;
  Graph G(num_vertices);

  /*          2<----5
             /       ^
            /         \
           V           \
    0 ---->1---->3----->6--->8
           \           ^
            \         /
             V       /
             4----->7
   */

  int capacity[] = { 10, 20, 20, 20, 40, 40, 20, 20, 20, 10 };
  int flow[] = { 8, 12, 12, 12, 12, 12, 16, 16, 16, 8 };

  // insert edges into the graph, and assign each edge an ID number
  // to index into the property arrays
  boost::add_edge(0, 1, 0, G);

  boost::add_edge(1, 4, 1, G);
  boost::add_edge(4, 7, 2, G);
  boost::add_edge(7, 6, 3, G);

  boost::add_edge(1, 3, 4, G);
  boost::add_edge(3, 6, 5, G);

  boost::add_edge(6, 5, 6, G);
  boost::add_edge(5, 2, 7, G);
  boost::add_edge(2, 1, 8, G);

  boost::add_edge(6, 8, 9, G);

  typedef boost::property_map<Graph, boost::edge_index_t>::type EdgeIndexMap;
  EdgeIndexMap edge_id = boost::get(boost::edge_index, G);

  typedef boost::iterator_property_map<int*, EdgeIndexMap, int, int&> IterMap;

  print_network(G, IterMap(capacity, edge_id), IterMap(flow, edge_id));

  return 0;
}


#ifndef itkLaplaceEquationSolverImageFilter_hxx_included
#define itkLaplaceEquationSolverImageFilter_hxx_included

#include "itkLaplaceEquationSolverImageFilter.h"

#include "itkImageRegionConstIteratorWithIndex.h"

#include <algorithm>
#include <ctime>
#include <limits>

#include <Eigen/SparseCholesky>

namespace itk
{

template< typename TInputImage, typename TOutputImage >
inline void
LaplaceEquationSolverImageFilter< TInputImage, TOutputImage >
::UpdateAB( size_t i, InputIndexType voxelIndex, OutputPixelType invDxDx,
            TripletListType & tripletList, VectorType & b,
            OffsetValueType solutionIndex )
{
  bool labelFound = false;
  InputPixelType label = this->GetInput()->GetPixel( voxelIndex );
  if ( label == m_SolutionLabel )
    {
    Eigen::Triplet< OutputPixelType > triplet( i, solutionIndex, invDxDx );
    tripletList.push_back( triplet );
    labelFound = true;
    }
  else if ( label == m_NeumannBoundaryConditionLabel )
    {
    Eigen::Triplet< OutputPixelType > triplet( i, i, invDxDx );
    tripletList.push_back( triplet );
    labelFound = true;
    }
  else
    {
    typename DirichletMapType::iterator iter =
      m_DirichletBoundaryConditionMap.find( label );
    if ( iter != m_DirichletBoundaryConditionMap.end() )
      {
      b[i] -= iter->second * invDxDx;
      labelFound = true;
      }
    }
  if ( !labelFound )
    {
    itkExceptionMacro( << "Unknown label value "
                       << label << " at index " << voxelIndex );
    }
}

template< typename TInputImage, typename TOutputImage >
LaplaceEquationSolverImageFilter< TInputImage, TOutputImage >
::LaplaceEquationSolverImageFilter()
{
  m_SolutionLabel = 11;
  m_NeumannBoundaryConditionLabel = 6;
}

template< typename TInputImage, typename TOutputImage >
void
LaplaceEquationSolverImageFilter< TInputImage, TOutputImage >
::ResetDirichletBoundaryConditions()
{
  m_DirichletBoundaryConditionMap.reset();

  this->Modified();
}

template< typename TInputImage, typename TOutputImage >
void
LaplaceEquationSolverImageFilter< TInputImage, TOutputImage >
::AddDirichletBoundaryCondition( InputPixelType boundaryConditionLabel,
                                 OutputPixelType boundaryConditionValue )
{
  m_DirichletBoundaryConditionMap[ boundaryConditionLabel ] =
    boundaryConditionValue;

  this->Modified();
}

template< typename TInputImage, typename TOutputImage >
size_t
LaplaceEquationSolverImageFilter< TInputImage, TOutputImage >
::GetNumberOfDirichletBoundaryConditions() const
{
  return m_DirichletBoundaryConditionMap.size();
}

template< typename TInputImage, typename TOutputImage >
void
LaplaceEquationSolverImageFilter< TInputImage, TOutputImage >
::GenerateData()
{
  typename TInputImage::ConstPointer input = this->GetInput();
  typename TOutputImage::Pointer output = this->GetOutput();

  // Allocate the output and initialize to NaN
  this->AllocateOutputs();
  output->FillBuffer( std::numeric_limits< OutputPixelType >::quiet_NaN() );

  // Do Laplace solution by iterative solver using 6 neighborhood
  InputIndexType minIndex = input->GetBufferedRegion().GetIndex();
  InputIndexType maxIndex = input->GetBufferedRegion().GetUpperIndex();

  // First get all the solution domain indices. We do this, otherwise
  // the matrices to solve will be far too large to fit into memory.
  std::vector< InputIndexType > solutionIndices;

  typedef typename TInputImage::OffsetValueType OffsetValueType;
  std::vector< OffsetValueType > linearIndex2UnknownIndex(
    input->GetPixelContainer()->Size(), 0 );

  long counter = 0;
  typedef ImageRegionConstIteratorWithIndex< TInputImage > IteratorWithIndexType;
  IteratorWithIndexType fullRegionIter( input.GetPointer(), input->GetBufferedRegion() );
  for ( fullRegionIter.GoToBegin(); !fullRegionIter.IsAtEnd(); ++fullRegionIter )
    {
    InputPixelType pixelValue = fullRegionIter.Get();
    InputIndexType index = fullRegionIter.GetIndex();
    if ( pixelValue == m_SolutionLabel )
      {
      solutionIndices.push_back( index );
      linearIndex2UnknownIndex[ input->ComputeOffset( fullRegionIter.GetIndex() ) ] = counter;
      ++counter;
      }
    }

  itkDebugMacro( << "Done with noting indices" );

  size_t numberOfUnknowns = solutionIndices.size();

  itkDebugMacro( << "Number of unknowns: " << numberOfUnknowns );

  // Set up the linear system by going through all the indices of the
  // solution domain and creating the sparse matrix A and the right
  // vector b
  typedef Eigen::SparseMatrix< OutputPixelType, Eigen::ColMajor > MatrixType;
  MatrixType A( numberOfUnknowns, numberOfUnknowns );

  // Set up the b vector
  typedef Eigen::Matrix< OutputPixelType, Eigen::Dynamic, 1 > VectorType;
  VectorType b( numberOfUnknowns );
  b.fill( 0.0 );

  // Set the diagonal elements
  OutputPixelType dx = input->GetSpacing()[0];
  OutputPixelType dy = input->GetSpacing()[1];
  OutputPixelType dz = input->GetSpacing()[2];

  OutputPixelType invDxDx = 1.0 / (dx * dx);
  OutputPixelType invDyDy = 1.0 / (dy * dy);
  OutputPixelType invDzDz = 1.0 / (dz * dz);

  itkDebugMacro( << "Done setting diagonal elements of A" );

  // Triplets to feed into A via
  // Eigen::SparseMatrix<>::setFromTriplets.  Note that triplets with
  // the same i, j indices will be summed in the sparse matrix.
  typedef Eigen::Triplet< OutputPixelType > Triplet;
  std::vector< Triplet > tripletList;
  tripletList.reserve( 7 * numberOfUnknowns );

  // This is somewhat of a goofy way to set up A. It is nearly a
  // direct translation from MATLAB code. Setting the elements based
  // on a traditional stencil approach might be faster and use less
  // memory.
  for ( size_t i = 0; i < numberOfUnknowns; ++i )
    {
    tripletList.push_back( Triplet( i, i, -2.0 * ( invDxDx + invDyDy + invDzDz ) ) );
    }

  for ( size_t i = 0; i < numberOfUnknowns; ++i )
    {
    InputIndexType index = solutionIndices[i];

    // +x, -x
    InputIndexType indexXP = index;
    indexXP[0] = std::min( maxIndex[0], index[0] + 1 );
    InputIndexType indexXM = index;
    indexXM[0] = std::max( minIndex[0], index[0] - 1 );

    // +y, -y
    InputIndexType indexYP = index;
    indexYP[1] = std::min( maxIndex[1], index[1] + 1 );
    InputIndexType indexYM = index;
    indexYM[1] = std::max( minIndex[1], index[1] - 1 );

    // +z, -z
    InputIndexType indexZP = index;
    indexZP[2] = std::min( maxIndex[2], index[2] + 1 );
    InputIndexType indexZM = index;
    indexZM[2] = std::max( minIndex[2], index[2] - 1 );

    OffsetValueType linearIndex;
    linearIndex = input->ComputeOffset( indexXP );
    this->UpdateAB( i, indexXP, invDxDx, tripletList, b,
                    linearIndex2UnknownIndex[ linearIndex ] );
    linearIndex = input->ComputeOffset( indexXM );
    this->UpdateAB( i, indexXM, invDxDx, tripletList, b,
                    linearIndex2UnknownIndex[ linearIndex ] );
    linearIndex = input->ComputeOffset( indexYP );
    this->UpdateAB( i, indexYP, invDyDy, tripletList, b,
                    linearIndex2UnknownIndex[ linearIndex ] );
    linearIndex = input->ComputeOffset( indexYM );
    this->UpdateAB( i, indexYM, invDyDy, tripletList, b,
                    linearIndex2UnknownIndex[ linearIndex ] );
    linearIndex = input->ComputeOffset( indexZP );
    this->UpdateAB( i, indexZP, invDzDz, tripletList, b,
                    linearIndex2UnknownIndex[ linearIndex ] );
    linearIndex = input->ComputeOffset( indexZM );
    this->UpdateAB( i, indexZM, invDzDz, tripletList, b,
                    linearIndex2UnknownIndex[ linearIndex ] );
    }

  itkDebugMacro( << "Done building linear systems" );

  // Fill A
  A.setFromTriplets( tripletList.begin(), tripletList.end() );
  tripletList.clear();

  // Compress the matrix before handing it off to the solver
  A.makeCompressed();

  itkDebugMacro( << "Done compressing" );

  std::time_t startTime;
  std::time( &startTime );

  // Tried several solvers on one large image to find the fastest:
  // ConjugateGradient  - 111 seconds
  // SimplicialCholesky - 204 seconds
  // SimplicialLDLT     - 203 seconds
  // SimplicalLLT       - error
  Eigen::ConjugateGradient< MatrixType > solver;
  solver.compute( A );
  VectorType x = solver.solve( b );

  itkDebugMacro( << "Done solving" );

  std::time_t endTime;
  std::time( &endTime );

  double elapsedSeconds = std::difftime( endTime, startTime );
  (void) elapsedSeconds; // To avoid warning when compiled in release mode
  itkDebugMacro( << "Execution took " << elapsedSeconds << " seconds." );

  if ( solver.info() != Eigen::Success )
    {
    itkExceptionMacro( << "Failed to solve linear system." );
    }

  // Now copy the output vector to the output image
  for ( size_t i = 0; i < solutionIndices.size(); ++i )
    {
    output->SetPixel( solutionIndices[i], x[i] );
    }

  // Now set the values for the Dirichlet boundary conditions
  // in the output image.
  ImageRegionConstIterator< TInputImage > iterIn( input.GetPointer(),
                                                     input->GetBufferedRegion() );
  ImageRegionIterator< TOutputImage > iterOut( output.GetPointer(),
                                                  output->GetBufferedRegion() );

  while ( !iterIn.IsAtEnd() && !iterOut.IsAtEnd() )
    {
    typename DirichletMapType::iterator iter = m_DirichletBoundaryConditionMap.find( iterIn.Get() );
    if ( iter != m_DirichletBoundaryConditionMap.end() )
      {
      iterOut.Set( iter->second );
      }
    ++iterIn;
    ++iterOut;
    }
}

} // end namespace itk

#endif