#include <stan/math/mix.hpp>
#include <test/unit/math/mix/meta/eigen_utils.hpp>
#include <Eigen/Sparse>
#include <gtest/gtest.h>

TEST(MathMetaPrim, is_eigen_sparse_base_hierarchy_tests) {
  using Eigen::Array;
  using Eigen::Matrix;
  using stan::is_eigen_sparse_base;
  using stan::math::test::all_eigen_dense;
  all_eigen_dense<false, false, false, false, false, is_eigen_sparse_base,
                  Matrix, -1, -1>();
  all_eigen_dense<false, false, false, false, false, is_eigen_sparse_base,
                  Matrix, 1, -1>();
  all_eigen_dense<false, false, false, false, false, is_eigen_sparse_base,
                  Matrix, -1, 1>();
  all_eigen_dense<false, false, false, false, false, is_eigen_sparse_base,
                  Array, -1, -1>();
  all_eigen_dense<false, false, false, false, false, is_eigen_sparse_base,
                  Array, 1, -1>();
  all_eigen_dense<false, false, false, false, false, is_eigen_sparse_base,
                  Array, -1, 1>();
}

TEST(MathMetaPrim, is_eigen_sparse_base_sparse_tests) {
  using Eigen::Array;
  using Eigen::Matrix;
  using stan::is_eigen_sparse_base;
  using stan::math::test::all_eigen_sparse;
  all_eigen_sparse<false, true, true, true, is_eigen_sparse_base>();
}

TEST(MathMetaPrim, is_eigen_sparse_base_decomp_tests) {
  using Eigen::Array;
  using Eigen::Matrix;
  using stan::is_eigen_sparse_base;
  using stan::math::test::all_eigen_dense_decomp;
  all_eigen_dense_decomp<false, is_eigen_sparse_base>();
}

TEST(MathMetaPrim, is_eigen_sparse_base_expr_tests) {
  using Eigen::Array;
  using Eigen::Matrix;
  using stan::is_eigen_sparse_base;
  using stan::math::test::all_eigen_dense_exprs;
  all_eigen_dense_exprs<false, false, false, false, is_eigen_sparse_base,
                        Matrix, -1, -1>();
  all_eigen_dense_exprs<false, false, false, false, is_eigen_sparse_base,
                        Matrix, 1, -1>();
  all_eigen_dense_exprs<false, false, false, false, is_eigen_sparse_base,
                        Matrix, -1, 1>();
  all_eigen_dense_exprs<false, false, false, false, is_eigen_sparse_base, Array,
                        -1, -1>();
  all_eigen_dense_exprs<false, false, false, false, is_eigen_sparse_base, Array,
                        1, -1>();
  all_eigen_dense_exprs<false, false, false, false, is_eigen_sparse_base, Array,
                        -1, 1>();
}


/* pcmsolver_copyright_start */
/*
 *     PCMSolver, an API for the Polarizable Continuum Model
 *     Copyright (C) 2013-2016 Roberto Di Remigio, Luca Frediani and contributors
 *
 *     This file is part of PCMSolver.
 *
 *     PCMSolver is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     PCMSolver is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
 *
 *     For information on the complete list of contributors to the
 *     PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
 */
/* pcmsolver_copyright_end */

#include <catch.hpp>

#include <cmath>

#include <Eigen/Core>

#include "cavity/GePolCavity.hpp"
#include "utils/Molecule.hpp"
#include "utils/Symmetry.hpp"
#include "TestingMolecules.hpp"

SCENARIO("GePol cavity for the linear CO2 molecule in different Abelian point groups", "[gepol][gepol_CO2_symmetry]")
{
    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is C1")
        {
	        Molecule molec = CO2<0>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.c1");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_size tests GePol cavity size for CO2 in C1 symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_irreducible_size tests GePol cavity irreducible size for CO2 in C1 symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_area tests GePol cavity surface area for CO2 in C1 symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_volume tests GePol cavity volume for CO2 in C1 symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is C2")
        {
            Molecule molec = CO2<1>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.c2");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_size tests GePol cavity size for CO2 in C2 symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_irreducible_size tests GePol cavity irreducible size for CO2 in C2 symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 224;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_area tests GePol cavity surface area for CO2 in C2 symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C1Test_volume tests GePol cavity volume for CO2 in C2 symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is Cs")
        {
            Molecule molec = CO2<2>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.cs");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CsTest_size tests GePol cavity size for CO2 in Cs symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CsTest_irreducible_size tests GePol cavity irreducible size for CO2 in Cs symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 224;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CsTest_area tests GePol cavity surface area for CO2 in Cs symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CsTest_volume tests GePol cavity volume for CO2 in Cs symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is Ci")
        {
            Molecule molec = CO2<3>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.ci");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CiTest_size tests GePol cavity size for CO2 in Ci symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CiTest_irreducible_size tests GePol cavity irreducible size for CO2 in Ci symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 224;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CiTest_area tests GePol cavity surface area for CO2 in Ci symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2CiTest_volume tests GePol cavity volume for CO2 in Ci symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is D2")
        {
            Molecule molec = CO2<4>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.d2");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2Test_size tests GePol cavity size for CO2 in D2 symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2Test_irreducible_size tests GePol cavity irreducible size for CO2 in D2 symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 112;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2Test_area tests GePol cavity surface area for CO2 in D2 symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2Test_volume tests GePol cavity volume for CO2 in D2 symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is C2v")
        {
            Molecule molec = CO2<5>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.c2v");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2vTest_size tests GePol cavity size for CO2 in C2v symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2vTest_irreducible_size tests GePol cavity irreducible size for CO2 in C2v symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 112;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2vTest_area tests GePol cavity surface area for CO2 in C2v symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2vTest_volume tests GePol cavity volume for CO2 in C2v symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is C2h")
        {
            Molecule molec = CO2<6>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.c2h");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2hTest_size tests GePol cavity size for CO2 in C2h symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2hTest_irreducible_size tests GePol cavity irreducible size for CO2 in C2h symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 112;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2hTest_area tests GePol cavity surface area for CO2 in C2h symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2C2hTest_volume tests GePol cavity volume for CO2 in C2h symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }

    GIVEN("The linear CO2 molecule")
    {
        double area = 0.2 / bohr2ToAngstrom2();
        double probeRadius = 1.385 / bohrToAngstrom();
        double minRadius = 0.2 / bohrToAngstrom();
        WHEN("the point group is D2h")
        {
            Molecule molec = CO2<7>();
            GePolCavity cavity = GePolCavity(molec, area, probeRadius, minRadius, "co2.d2h");

            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2hTest_size tests GePol cavity size for CO2 in D2h symmetry with added spheres
             */
            THEN("the size of the cavity is")
            {
                int size = 448;
                int actualSize = cavity.size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2hTest_irreducible_size tests GePol cavity irreducible size for CO2 in D2h symmetry with added spheres
             */
            AND_THEN("the irreducible size of the cavity is")
            {
                int size = 56;
                int actualSize = cavity.irreducible_size();
                REQUIRE(size == actualSize);
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2hTest_area tests GePol cavity surface area for CO2 in D2h symmetry with added spheres
             */
            AND_THEN("the surface area of the cavity is")
            {
                double area = 250.68176442433020;
                double actualArea = cavity.elementArea().sum();
                REQUIRE(area == Approx(actualArea));
            }
            /*! \class GePolCavity
             *  \test \b GePolCavityCO2D2hTest_volume tests GePol cavity volume for CO2 in D2h symmetry with added spheres
             */
            AND_THEN("the volume of the cavity is")
            {
                double volume = 352.55869984340751;
                Eigen::Matrix3Xd elementCenter = cavity.elementCenter();
                Eigen::Matrix3Xd elementNormal = cavity.elementNormal();
                double actualVolume = 0;
                for ( int i = 0; i < cavity.size(); ++i ) {
                    actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(
                                i));
                }
                actualVolume /= 3;
                REQUIRE(volume == Approx(actualVolume));
            }
        }
    }
}


//
//  test_rntn.cpp
//  NeuralNetwork
//
//  Created by Nikhil Joshi on 12/22/14.
//  Copyright (c) 2014 Nikhil Joshi. All rights reserved.
//

#include "rntn.hpp"

#define BOOST_TEST_MODULE test_network
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(rntn_tests)

BOOST_AUTO_TEST_CASE(networkImplementationTests) {

    // A small test network
    size_t dimRepresentation(3), numClasses(5), lowerClassIndex(0);
    NNetwork::RNTN testNet(dimRepresentation, numClasses, lowerClassIndex);

    // A test file
    std::string testFile("data/trees/dev.txt");
    size_t numExamples(10);

    std::cout << "RNTN test started!" << std::endl;
    BOOST_ASSERT(NNetwork::isValidRntnImplementation(testNet, testFile, numExamples));
    std::cout << "RNTN test ended!" << std::endl;
}

BOOST_AUTO_TEST_SUIT_END()


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

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

// 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)

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

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_TOPOLOGY_CHECK_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_TOPOLOGY_CHECK_HPP

#include <boost/geometry/util/range.hpp>

#include <boost/geometry/algorithms/detail/equals/point_point.hpp>
#include <boost/geometry/policies/compare.hpp>

#include <boost/geometry/util/has_nan_coordinate.hpp>

namespace boost { namespace geometry {

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace relate {

// TODO: change the name for e.g. something with the word "exterior"

template <typename Geometry,
          typename Tag = typename geometry::tag<Geometry>::type>
struct topology_check
    : not_implemented<Tag>
{};

//template <typename Point>
//struct topology_check<Point, point_tag>
//{
//    static const char interior = '0';
//    static const char boundary = 'F';
//
//    static const bool has_interior = true;
//    static const bool has_boundary = false;
//
//    topology_check(Point const&) {}
//    template <typename IgnoreBoundaryPoint>
//    topology_check(Point const&, IgnoreBoundaryPoint const&) {}
//};

template <typename Linestring>
struct topology_check<Linestring, linestring_tag>
{
    static const char interior = '1';
    static const char boundary = '0';

    bool has_interior;
    bool has_boundary;

    topology_check(Linestring const& ls)
    {
        init(ls, 0); /*dummy param*/
    }

    template <typename IgnoreBoundaryPoint>
    topology_check(Linestring const& ls, IgnoreBoundaryPoint const& ibp)
    {
        init(ls, ibp); /*dummy param, won't be used*/
    }

    // Even if some point is on the boundary, if the Linestring has the boundary,
    // there will be second boundary point different than IgnoreBoundaryPoint
    template <typename IgnoreBoundaryPoint>
    void init(Linestring const& ls, IgnoreBoundaryPoint const&)
    {
        std::size_t count = boost::size(ls);
        has_interior = count > 0;
        // NOTE: Linestring with all points equal is treated as 1d linear ring
        has_boundary = count > 1
                    && ! detail::equals::equals_point_point(range::front(ls), range::back(ls));
    }
};

template <typename MultiLinestring>
struct topology_check<MultiLinestring, multi_linestring_tag>
{
    static const char interior = '1';
    static const char boundary = '0';

    bool has_interior;
    bool has_boundary;

    topology_check(MultiLinestring const& mls)
    {
        init(mls, not_ignoring_counter());
    }

    template <typename IgnoreBoundaryPoint>
    topology_check(MultiLinestring const& mls, IgnoreBoundaryPoint const& ibp)
    {
        init(mls, ignoring_counter<IgnoreBoundaryPoint>(ibp));
    }

    template <typename OddCounter>
    void init(MultiLinestring const& mls, OddCounter const& odd_counter)
    {
        typedef typename geometry::point_type<MultiLinestring>::type point_type;
        std::vector<point_type> endpoints;
        endpoints.reserve(boost::size(mls) * 2);

        typedef typename boost::range_iterator<MultiLinestring const>::type ls_iterator;
        for ( ls_iterator it = boost::begin(mls) ; it != boost::end(mls) ; ++it )
        {
            typename boost::range_reference<MultiLinestring const>::type
                ls = *it;

            std::size_t count = boost::size(ls);

            if (count > 0)
            {
                has_interior = true;
            }

            if (count > 1)
            {
                typedef typename boost::range_reference
                    <
                        typename boost::range_value<MultiLinestring const>::type const
                    >::type point_reference;

                point_reference front_pt = range::front(ls);
                point_reference back_pt = range::back(ls);

                // don't store boundaries of linear rings, this doesn't change anything
                if (! equals::equals_point_point(front_pt, back_pt))
                {
                    // do not add points containing NaN coordinates
                    // because they cannot be reasonably compared, e.g. with MSVC
                    // an assertion failure is reported in std::equal_range()
                    // NOTE: currently ignoring_counter calling std::equal_range()
                    //   is not used anywhere in the code, still it's safer this way
                    if (! geometry::has_nan_coordinate(front_pt))
                    {
                        endpoints.push_back(front_pt);
                    }
                    if (! geometry::has_nan_coordinate(back_pt))
                    {
                        endpoints.push_back(back_pt);
                    }
                }
            }
        }

        has_boundary = false;

        if ( !endpoints.empty() )
        {
            std::sort(endpoints.begin(), endpoints.end(), geometry::less<point_type>());
            has_boundary = odd_counter(endpoints.begin(), endpoints.end());
        }
    }

    struct not_ignoring_counter
    {
        template <typename It>
        bool operator()(It first, It last) const
        {
            return find_odd_count(first, last);
        }
    };

    template <typename Point>
    struct ignoring_counter
    {
        ignoring_counter(Point const& pt) : m_pt(pt) {}

        template <typename It>
        bool operator()(It first, It last) const
        {
            typedef typename std::iterator_traits<It>::value_type point_type;

            std::pair<It, It> ignore_range
                              = std::equal_range(first, last, m_pt,
                                                 geometry::less<point_type>());

            if ( find_odd_count(first, ignore_range.first) )
                return true;

            return find_odd_count(ignore_range.second, last);
        }

        Point const& m_pt;
    };

    template <typename It>
    static inline bool find_odd_count(It first, It last)
    {
        if ( first == last )
            return false;

        std::size_t count = 1;
        It prev = first;
        ++first;
        for ( ; first != last ; ++first, ++prev )
        {
            // the end of the equal points subrange
            if ( ! equals::equals_point_point(*first, *prev) )
            {
                if ( count % 2 != 0 )
                    return true;

                count = 1;
            }
            else
            {
                ++count;
            }
        }

        return count % 2 != 0;
    }
};

template <typename Ring>
struct topology_check<Ring, ring_tag>
{
    static const char interior = '2';
    static const char boundary = '1';
    static const bool has_interior = true;
    static const bool has_boundary = true;

    topology_check(Ring const&) {}
    template <typename P>
    topology_check(Ring const&, P const&) {}
};

template <typename Polygon>
struct topology_check<Polygon, polygon_tag>
{
    static const char interior = '2';
    static const char boundary = '1';
    static const bool has_interior = true;
    static const bool has_boundary = true;

    topology_check(Polygon const&) {}
    template <typename P>
    topology_check(Polygon const&, P const&) {}
};

template <typename MultiPolygon>
struct topology_check<MultiPolygon, multi_polygon_tag>
{
    static const char interior = '2';
    static const char boundary = '1';
    static const bool has_interior = true;
    static const bool has_boundary = true;

    topology_check(MultiPolygon const&) {}
    template <typename P>
    topology_check(MultiPolygon const&, P const&) {}
};

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

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_TOPOLOGY_CHECK_HPP


#include <boost/accumulators/statistics/rolling_count.hpp>


#define BOOST_TEST_MODULE "test_omp_bond_angle_interaction"

#ifdef BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#else
#include <boost/test/included/unit_test.hpp>
#endif

#include <mjolnir/math/math.hpp>
#include <mjolnir/core/BoundaryCondition.hpp>
#include <mjolnir/core/SimulatorTraits.hpp>
#include <mjolnir/forcefield/MultipleBasin/MultipleBasinForceField.hpp>
#include <mjolnir/forcefield/MultipleBasin/MultipleBasin2BasinUnit.hpp>
#include <mjolnir/forcefield/MultipleBasin/MultipleBasin3BasinUnit.hpp>
#include <mjolnir/forcefield/local/HarmonicPotential.hpp>
#include <mjolnir/omp/BondLengthInteraction.hpp>
#include <mjolnir/omp/System.hpp>
#include <mjolnir/omp/RandomNumberGenerator.hpp>
#include <mjolnir/util/make_unique.hpp>

BOOST_AUTO_TEST_CASE(omp_MultipleBasin_2Basin_numerical_difference)
{
    mjolnir::LoggerManager::set_default_logger("test_omp_multiple_basin_forcefield.log");
    using traits_type      = mjolnir::OpenMPSimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coord_type       = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::HarmonicPotential<real_type>;
    using interaction_type = mjolnir::BondLengthInteraction<traits_type, potential_type>;

    const real_type k(10.0);
    const real_type native1(1.5);
    const real_type native2(2.0);

    mjolnir::LocalForceField<traits_type>    loc1, loc2, locc;
    mjolnir::GlobalForceField<traits_type>   glo1, glo2, gloc;
    mjolnir::ExternalForceField<traits_type> ext1, ext2, extc;

    std::vector<std::pair<std::array<std::size_t, 2>, potential_type>> param1, param2;
    param1.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native1));
    param2.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native2));

    loc1.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param1)));
    loc2.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param2)));

    std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>> unit1 =
        mjolnir::make_unique<mjolnir::MultipleBasin2BasinUnit<traits_type>>(
            -10.0, "short", "long", 0.0, 0.0,
            std::make_tuple(std::move(loc1), std::move(glo1), std::move(ext1)),
            std::make_tuple(std::move(loc2), std::move(glo2), std::move(ext2)));

    std::vector<std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>>> units;
    units.push_back(std::move(unit1));

    std::mt19937 mt(123456789);
    std::uniform_real_distribution<real_type> uni(-1.0, 1.0);

    mjolnir::MultipleBasinForceField<traits_type> forcefield(
            std::make_tuple(std::move(locc), std::move(gloc), std::move(extc)),
            std::move(units));

    for(std::size_t i = 0; i < 100; ++i)
    {
        system_type sys(2, boundary_type{});

        sys.at(0).mass  = 1.0;
        sys.at(1).mass  = 1.0;
        sys.at(0).rmass = 1.0;
        sys.at(1).rmass = 1.0;

        sys.at(0).position = coord_type( 0.0 + 0.01 * uni(mt), 0.0 + 0.01 * uni(mt), 0.0 + 0.01 * uni(mt));
        sys.at(1).position = coord_type( 1.0 + 0.01 * uni(mt), 1.0 + 0.01 * uni(mt), 1.0 + 0.01 * uni(mt));
        sys.at(0).velocity = coord_type( 0.0, 0.0, 0.0);
        sys.at(1).velocity = coord_type( 0.0, 0.0, 0.0);
        sys.at(0).force    = coord_type( 0.0, 0.0, 0.0);
        sys.at(1).force    = coord_type( 0.0, 0.0, 0.0);

        sys.at(0).name  = "X";
        sys.at(1).name  = "X";
        sys.at(0).group = "TEST";
        sys.at(1).group = "TEST";

        const auto init = sys;

        forcefield.initialize(init); // don't forget this

        constexpr real_type tol = 1e-4;
        constexpr real_type dr  = 1e-5;
        for(std::size_t idx=0; idx<2; ++idx)
        {
            {
                // ----------------------------------------------------------------
                // reset positions
                sys = init;

                // calc U(x-dx)
                const auto E0 = forcefield.calc_energy(sys);

                mjolnir::math::X(sys.position(idx)) += dr;

                // calc F(x)
                forcefield.calc_force(sys);

                mjolnir::math::X(sys.position(idx)) += dr;

                // calc U(x+dx)
                const auto E1 = forcefield.calc_energy(sys);

                // central difference
                const auto dE = (E1 - E0) * 0.5;

                BOOST_TEST(-dE == dr * mjolnir::math::X(sys.force(idx)),
                           boost::test_tools::tolerance(tol));
            }
            {
                // ----------------------------------------------------------------
                // reset positions
                sys = init;

                // calc U(x-dx)
                const auto E0 = forcefield.calc_energy(sys);

                mjolnir::math::Y(sys.position(idx)) += dr;

                // calc F(x)
                forcefield.calc_force(sys);

                mjolnir::math::Y(sys.position(idx)) += dr;

                // calc U(x+dx)
                const auto E1 = forcefield.calc_energy(sys);

                // central difference
                const auto dE = (E1 - E0) * 0.5;

                BOOST_TEST(-dE == dr * mjolnir::math::Y(sys.force(idx)),
                           boost::test_tools::tolerance(tol));
            }
            {
                // ----------------------------------------------------------------
                // reset positions
                sys = init;

                // calc U(x-dx)
                const auto E0 = forcefield.calc_energy(sys);

                mjolnir::math::Z(sys.position(idx)) += dr;

                // calc F(x)
                forcefield.calc_force(sys);

                mjolnir::math::Z(sys.position(idx)) += dr;

                // calc U(x+dx)
                const auto E1 = forcefield.calc_energy(sys);

                // central difference
                const auto dE = (E1 - E0) * 0.5;

                BOOST_TEST(-dE == dr * mjolnir::math::Z(sys.force(idx)),
                           boost::test_tools::tolerance(tol));
            }
        }
    }
}

// check difference of forces
BOOST_AUTO_TEST_CASE(omp_MultipleBasin_2Basin_consistency)
{
    mjolnir::LoggerManager::set_default_logger("test_omp_multiple_basin_forcefield.log");
    using traits_type      = mjolnir::OpenMPSimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coord_type       = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::HarmonicPotential<real_type>;
    using interaction_type = mjolnir::BondLengthInteraction<traits_type, potential_type>;

    using default_traits_type      = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using default_system_type      = mjolnir::System<default_traits_type>;
    using default_potential_type   = mjolnir::HarmonicPotential<real_type>;
    using default_interaction_type = mjolnir::BondLengthInteraction<default_traits_type, default_potential_type>;

    const real_type k(10.0);
    const real_type native1(1.5);
    const real_type native2(2.0);
    const std::size_t Nparticle = 1000;

    mjolnir::MultipleBasinForceField<traits_type> ff_openmp = [&]()
    {
        mjolnir::LocalForceField<traits_type>    loc1, loc2, locc;
        mjolnir::GlobalForceField<traits_type>   glo1, glo2, gloc;
        mjolnir::ExternalForceField<traits_type> ext1, ext2, extc;

        std::vector<std::pair<std::array<std::size_t, 2>, potential_type>> param1, param2;
        for(std::size_t i=0; i<Nparticle-1; ++i)
        {
            param1.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, potential_type(k, native1));
            param2.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, potential_type(k, native2));
        }
        loc1.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param1)));
        loc2.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param2)));

        std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>> unit1 =
            mjolnir::make_unique<mjolnir::MultipleBasin2BasinUnit<traits_type>>(
                -10.0, "short", "long", 0.0, 0.0,
                std::make_tuple(std::move(loc1), std::move(glo1), std::move(ext1)),
                std::make_tuple(std::move(loc2), std::move(glo2), std::move(ext2)));

        std::vector<std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>>> units;
        units.push_back(std::move(unit1));

        return mjolnir::MultipleBasinForceField<traits_type>(
                std::make_tuple(std::move(locc), std::move(gloc), std::move(extc)),
                std::move(units));
    }();

    mjolnir::MultipleBasinForceField<default_traits_type> ff_default = [&]()
    {
        mjolnir::LocalForceField<default_traits_type>    loc1, loc2, locc;
        mjolnir::GlobalForceField<default_traits_type>   glo1, glo2, gloc;
        mjolnir::ExternalForceField<default_traits_type> ext1, ext2, extc;

        std::vector<std::pair<std::array<std::size_t, 2>, default_potential_type>> param1, param2;
        for(std::size_t i=0; i<Nparticle-1; ++i)
        {
            param1.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, default_potential_type(k, native1));
            param2.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, default_potential_type(k, native2));
        }
        loc1.emplace(mjolnir::make_unique<default_interaction_type>("none", std::move(param1)));
        loc2.emplace(mjolnir::make_unique<default_interaction_type>("none", std::move(param2)));

        std::unique_ptr<mjolnir::MultipleBasinUnitBase<default_traits_type>> unit1 =
            mjolnir::make_unique<mjolnir::MultipleBasin2BasinUnit<default_traits_type>>(
                -10.0, "short", "long", 0.0, 0.0,
                std::make_tuple(std::move(loc1), std::move(glo1), std::move(ext1)),
                std::make_tuple(std::move(loc2), std::move(glo2), std::move(ext2)));

        std::vector<std::unique_ptr<mjolnir::MultipleBasinUnitBase<default_traits_type>>> units;
        units.push_back(std::move(unit1));

        return mjolnir::MultipleBasinForceField<default_traits_type>(
                std::make_tuple(std::move(locc), std::move(gloc), std::move(extc)),
                std::move(units));
    }();

    std::mt19937 mt(123456789);
    std::uniform_real_distribution<real_type> uni(-1.0, 1.0);

    for(std::size_t i = 0; i < 100; ++i)
    {
        system_type         sys_openmp (Nparticle, boundary_type{});
        default_system_type sys_default(Nparticle, boundary_type{});

        for(std::size_t j=0; j<Nparticle; ++j)
        {
            const coord_type pos(j * 1.0 + 0.01 * uni(mt),
                                 j * 1.0 + 0.01 * uni(mt),
                                 j * 1.0 + 0.01 * uni(mt));

            sys_openmp.mass    (j) = 1.0;
            sys_openmp.rmass   (j) = 1.0;
            sys_openmp.position(j) = pos;
            sys_openmp.velocity(j) = coord_type( 0.0, 0.0, 0.0);
            sys_openmp.force   (j) = coord_type( 0.0, 0.0, 0.0);
            sys_openmp.name    (j) = "X";
            sys_openmp.group   (j) = "TEST";

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

            sys_default.mass    (j) = 1.0;
            sys_default.rmass   (j) = 1.0;
            sys_default.position(j) = pos;
            sys_default.velocity(j) = coord_type( 0.0, 0.0, 0.0);
            sys_default.force   (j) = coord_type( 0.0, 0.0, 0.0);
            sys_default.name    (j) = "X";
            sys_default.group   (j) = "TEST";
        }

        ff_openmp .initialize(sys_openmp ); // don't forget this
        ff_default.initialize(sys_default); // don't forget this

        constexpr real_type tol = 1e-4;

        ff_openmp .calc_force(sys_openmp );
        ff_default.calc_force(sys_default);

        for(std::size_t j=0; j<Nparticle; ++j)
        {
            using namespace mjolnir::math;
            BOOST_TEST(X(sys_default.force(j)) == X(sys_openmp.force(j)),
                       boost::test_tools::tolerance(tol));

            BOOST_TEST(Y(sys_default.force(j)) == Y(sys_openmp.force(j)),
                       boost::test_tools::tolerance(tol));

            BOOST_TEST(Z(sys_default.force(j)) == Z(sys_openmp.force(j)),
                       boost::test_tools::tolerance(tol));
        }
    }
}

BOOST_AUTO_TEST_CASE(MultipleBasin_3Basin_numerical_difference)
{
    mjolnir::LoggerManager::set_default_logger("test_omp_multiple_basin_forcefield.log");
    using traits_type      = mjolnir::OpenMPSimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coord_type       = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::HarmonicPotential<real_type>;
    using interaction_type = mjolnir::BondLengthInteraction<traits_type, potential_type>;

    const real_type k(10.0);
    const real_type native1(1.6);
    const real_type native2(1.8);
    const real_type native3(1.9);

    mjolnir::LocalForceField<traits_type>    loc1, loc2, loc3, locc;
    mjolnir::GlobalForceField<traits_type>   glo1, glo2, glo3, gloc;
    mjolnir::ExternalForceField<traits_type> ext1, ext2, ext3, extc;

    std::vector<std::pair<std::array<std::size_t, 2>, potential_type>> param1, param2, param3;
    param1.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native1));
    param2.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native2));
    param3.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native3));

    loc1.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param1)));
    loc2.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param2)));
    loc3.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param3)));

    std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>> unit1 =
        mjolnir::make_unique<mjolnir::MultipleBasin3BasinUnit<traits_type>>(
            "short", "middle", "long", -10.0, -10.0, -10.0, 0.0, 0.0, 0.0,
            std::make_tuple(std::move(loc1), std::move(glo1), std::move(ext1)),
            std::make_tuple(std::move(loc2), std::move(glo2), std::move(ext2)),
            std::make_tuple(std::move(loc3), std::move(glo3), std::move(ext3)));

    std::vector<std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>>> units;
    units.push_back(std::move(unit1));

    std::mt19937 mt(123456789);
    std::uniform_real_distribution<real_type> uni(-1.0, 1.0);

    mjolnir::MultipleBasinForceField<traits_type> forcefield(
            std::make_tuple(std::move(locc), std::move(gloc), std::move(extc)),
            std::move(units));

    for(std::size_t i = 0; i < 100; ++i)
    {
        system_type sys(2, boundary_type{});

        sys.at(0).mass  = 1.0;
        sys.at(1).mass  = 1.0;
        sys.at(0).rmass = 1.0;
        sys.at(1).rmass = 1.0;

        sys.at(0).position = coord_type( 0.0 + 0.01 * uni(mt), 0.0 + 0.01 * uni(mt), 0.0 + 0.01 * uni(mt));
        sys.at(1).position = coord_type( 1.0 + 0.01 * uni(mt), 1.0 + 0.01 * uni(mt), 1.0 + 0.01 * uni(mt));
        sys.at(0).velocity = coord_type( 0.0, 0.0, 0.0);
        sys.at(1).velocity = coord_type( 0.0, 0.0, 0.0);
        sys.at(0).force    = coord_type( 0.0, 0.0, 0.0);
        sys.at(1).force    = coord_type( 0.0, 0.0, 0.0);

        sys.at(0).name  = "X";
        sys.at(1).name  = "X";
        sys.at(0).group = "TEST";
        sys.at(1).group = "TEST";

        const auto init = sys;

        forcefield.initialize(init); // don't forget this

        constexpr real_type tol = 1e-4;
        constexpr real_type dr  = 1e-5;
        for(std::size_t idx=0; idx<2; ++idx)
        {
            {
                // ----------------------------------------------------------------
                // reset positions
                sys = init;

                // calc U(x-dx)
                const auto E0 = forcefield.calc_energy(sys);

                mjolnir::math::X(sys.position(idx)) += dr;

                // calc F(x)
                forcefield.calc_force(sys);

                mjolnir::math::X(sys.position(idx)) += dr;

                // calc U(x+dx)
                const auto E1 = forcefield.calc_energy(sys);

                // central difference
                const auto dE = (E1 - E0) * 0.5;

                BOOST_TEST(-dE == dr * mjolnir::math::X(sys.force(idx)),
                           boost::test_tools::tolerance(tol));
            }
            {
                // ----------------------------------------------------------------
                // reset positions
                sys = init;

                // calc U(x-dx)
                const auto E0 = forcefield.calc_energy(sys);

                mjolnir::math::Y(sys.position(idx)) += dr;

                // calc F(x)
                forcefield.calc_force(sys);

                mjolnir::math::Y(sys.position(idx)) += dr;

                // calc U(x+dx)
                const auto E1 = forcefield.calc_energy(sys);

                // central difference
                const auto dE = (E1 - E0) * 0.5;

                BOOST_TEST(-dE == dr * mjolnir::math::Y(sys.force(idx)),
                           boost::test_tools::tolerance(tol));
            }
            {
                // ----------------------------------------------------------------
                // reset positions
                sys = init;

                // calc U(x-dx)
                const auto E0 = forcefield.calc_energy(sys);

                mjolnir::math::Z(sys.position(idx)) += dr;

                // calc F(x)
                forcefield.calc_force(sys);

                mjolnir::math::Z(sys.position(idx)) += dr;

                // calc U(x+dx)
                const auto E1 = forcefield.calc_energy(sys);

                // central difference
                const auto dE = (E1 - E0) * 0.5;

                BOOST_TEST(-dE == dr * mjolnir::math::Z(sys.force(idx)),
                           boost::test_tools::tolerance(tol));
            }
        }
    }
}

// check difference of forces
BOOST_AUTO_TEST_CASE(omp_MultipleBasin_3Basin_consistency)
{
    mjolnir::LoggerManager::set_default_logger("test_omp_multiple_basin_forcefield.log");
    using traits_type      = mjolnir::OpenMPSimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coord_type       = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::HarmonicPotential<real_type>;
    using interaction_type = mjolnir::BondLengthInteraction<traits_type, potential_type>;

    using default_traits_type      = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using default_system_type      = mjolnir::System<default_traits_type>;
    using default_potential_type   = mjolnir::HarmonicPotential<real_type>;
    using default_interaction_type = mjolnir::BondLengthInteraction<default_traits_type, default_potential_type>;

    const real_type k(10.0);
    const real_type native1(1.6);
    const real_type native2(1.8);
    const real_type native3(1.9);

    const std::size_t Nparticle = 1000;

    mjolnir::MultipleBasinForceField<traits_type> ff_openmp = [&]()
    {
        mjolnir::LocalForceField<traits_type>    loc1, loc2, loc3, locc;
        mjolnir::GlobalForceField<traits_type>   glo1, glo2, glo3, gloc;
        mjolnir::ExternalForceField<traits_type> ext1, ext2, ext3, extc;

        std::vector<std::pair<std::array<std::size_t, 2>, potential_type>> param1, param2, param3;
        for(std::size_t i=0; i<Nparticle-1; ++i)
        {
            param1.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, potential_type(k, native1));
            param2.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, potential_type(k, native2));
            param3.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, potential_type(k, native3));
        }
        loc1.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param1)));
        loc2.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param2)));
        loc3.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param3)));

        std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>> unit1 =
            mjolnir::make_unique<mjolnir::MultipleBasin3BasinUnit<traits_type>>(
                "short", "middle", "long", -10.0, -10.0, -10.0, 0.0, 0.0, 0.0,
                std::make_tuple(std::move(loc1), std::move(glo1), std::move(ext1)),
                std::make_tuple(std::move(loc2), std::move(glo2), std::move(ext2)),
                std::make_tuple(std::move(loc3), std::move(glo3), std::move(ext3)));

        std::vector<std::unique_ptr<mjolnir::MultipleBasinUnitBase<traits_type>>> units;
        units.push_back(std::move(unit1));

        return mjolnir::MultipleBasinForceField<traits_type>(
                std::make_tuple(std::move(locc), std::move(gloc), std::move(extc)),
                std::move(units));
    }();

    mjolnir::MultipleBasinForceField<default_traits_type> ff_default = [&]()
    {
        mjolnir::LocalForceField<default_traits_type>    loc1, loc2, loc3, locc;
        mjolnir::GlobalForceField<default_traits_type>   glo1, glo2, glo3, gloc;
        mjolnir::ExternalForceField<default_traits_type> ext1, ext2, ext3, extc;

        std::vector<std::pair<std::array<std::size_t, 2>, default_potential_type>> param1, param2, param3;
        for(std::size_t i=0; i<Nparticle-1; ++i)
        {
            param1.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, default_potential_type(k, native1));
            param2.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, default_potential_type(k, native2));
            param3.emplace_back(std::array<std::size_t, 2>{{i,i+1}}, default_potential_type(k, native3));
        }
        loc1.emplace(mjolnir::make_unique<default_interaction_type>("none", std::move(param1)));
        loc2.emplace(mjolnir::make_unique<default_interaction_type>("none", std::move(param2)));
        loc3.emplace(mjolnir::make_unique<default_interaction_type>("none", std::move(param3)));

        std::unique_ptr<mjolnir::MultipleBasinUnitBase<default_traits_type>> unit1 =
            mjolnir::make_unique<mjolnir::MultipleBasin3BasinUnit<default_traits_type>>(
                "short", "middle", "long", -10.0, -10.0, -10.0, 0.0, 0.0, 0.0,
                std::make_tuple(std::move(loc1), std::move(glo1), std::move(ext1)),
                std::make_tuple(std::move(loc2), std::move(glo2), std::move(ext2)),
                std::make_tuple(std::move(loc3), std::move(glo3), std::move(ext3)));

        std::vector<std::unique_ptr<mjolnir::MultipleBasinUnitBase<default_traits_type>>> units;
        units.push_back(std::move(unit1));

        return mjolnir::MultipleBasinForceField<default_traits_type>(
                std::make_tuple(std::move(locc), std::move(gloc), std::move(extc)),
                std::move(units));
    }();

    std::mt19937 mt(123456789);
    std::uniform_real_distribution<real_type> uni(-1.0, 1.0);

    for(std::size_t i = 0; i < 100; ++i)
    {
        system_type         sys_openmp (Nparticle, boundary_type{});
        default_system_type sys_default(Nparticle, boundary_type{});

        for(std::size_t j=0; j<Nparticle; ++j)
        {
            const coord_type pos(j * 1.0 + 0.01 * uni(mt),
                                 j * 1.0 + 0.01 * uni(mt),
                                 j * 1.0 + 0.01 * uni(mt));

            sys_openmp.mass    (j) = 1.0;
            sys_openmp.rmass   (j) = 1.0;
            sys_openmp.position(j) = pos;
            sys_openmp.velocity(j) = coord_type( 0.0, 0.0, 0.0);
            sys_openmp.force   (j) = coord_type( 0.0, 0.0, 0.0);
            sys_openmp.name    (j) = "X";
            sys_openmp.group   (j) = "TEST";

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

            sys_default.mass    (j) = 1.0;
            sys_default.rmass   (j) = 1.0;
            sys_default.position(j) = pos;
            sys_default.velocity(j) = coord_type( 0.0, 0.0, 0.0);
            sys_default.force   (j) = coord_type( 0.0, 0.0, 0.0);
            sys_default.name    (j) = "X";
            sys_default.group   (j) = "TEST";
        }

        ff_openmp .initialize(sys_openmp ); // don't forget this
        ff_default.initialize(sys_default); // don't forget this

        constexpr real_type tol = 1e-4;

        ff_openmp .calc_force(sys_openmp );
        ff_default.calc_force(sys_default);

        for(std::size_t j=0; j<Nparticle; ++j)
        {
            using namespace mjolnir::math;
            BOOST_TEST(X(sys_default.force(j)) == X(sys_openmp.force(j)),
                       boost::test_tools::tolerance(tol));

            BOOST_TEST(Y(sys_default.force(j)) == Y(sys_openmp.force(j)),
                       boost::test_tools::tolerance(tol));

            BOOST_TEST(Z(sys_default.force(j)) == Z(sys_openmp.force(j)),
                       boost::test_tools::tolerance(tol));
        }
    }
}