/* Copyright (C) 2020 IBM Corp.
 * This program is Licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. See accompanying LICENSE file.
 */

// This is a sample program for education purposes only.
// It implements a very simple homomorphic encryption based
// db search algorithm for demonstration purposes.

// This country lookup example is derived from the BGV database demo
// code originally written by Jack Crawford for a lunch and learn
// session at IBM Research (Hursley) in 2019.
// The original example code ships with HElib and can be found at
// https://github.com/IBM-HElib/HElib/tree/master/examples/BGV_database_lookup

#include <iostream>

#include <helib/helib.h>
#include <helib/EncryptedArray.h>
#include <helib/ArgMap.h>
#include <NTL/BasicThreadPool.h>

// Utility function to print polynomials
void printPoly(NTL::ZZX& poly)
{
  for (int i = NTL::deg(poly); i >= 0; i--) {
    std::cout << poly[i] << "x^" << i;
    if (i > 0)
      std::cout << " + ";
    else
      std::cout << "\n";
  }
}

// Utility function to read <K,V> CSV data from file
std::vector<std::pair<std::string, std::string>> read_csv(std::string filename)
{
  std::vector<std::pair<std::string, std::string>> dataset;
  std::ifstream data_file(filename);

  if (!data_file.is_open())
    throw std::runtime_error(
        "Error: This example failed trying to open the data file: " + filename +
        "\n           Please check this file exists and try again.");

  std::vector<std::string> row;
  std::string line, entry, temp;

  if (data_file.good()) {
    // Read each line of file
    while (std::getline(data_file, line)) {
      row.clear();
      std::stringstream ss(line);
      while (getline(ss, entry, ',')) {
        row.push_back(entry);
      }
      // Add key value pairs to dataset
      dataset.push_back(std::make_pair(row[0], row[1]));
    }
  }

  data_file.close();
  return dataset;
}

int main(int argc, char* argv[])
{
  /************ HElib boiler plate ************/

  // Note: The parameters have been chosen to provide a somewhat
  // faster running time with a non-realistic security level.
  // Do Not use these parameters in real applications.

  // Plaintext prime modulus
  unsigned long p = 131;
  // Cyclotomic polynomial - defines phi(m)
  unsigned long m = 130; // this will give 48 slots
  // Hensel lifting (default = 1)
  unsigned long r = 1;
  // Number of bits of the modulus chain
  unsigned long bits = 1000;
  // Number of columns of Key-Switching matrix (default = 2 or 3)
  unsigned long c = 2;
  // Size of NTL thread pool (default =1)
  unsigned long nthreads = 1;
  // input database file name
  std::string db_filename = "./countries_dataset.csv";
  // debug output (default no debug output)
  bool debug = false;

  helib::ArgMap amap;
  amap.arg("m", m, "Cyclotomic polynomial ring");
  amap.arg("p", p, "Plaintext prime modulus");
  amap.arg("r", r, "Hensel lifting");
  amap.arg("bits", bits, "# of bits in the modulus chain");
  amap.arg("c", c, "# fo columns of Key-Switching matrix");
  amap.arg("nthreads", nthreads, "Size of NTL thread pool");
  amap.arg("db_filename",
           db_filename,
           "Qualified name for the database filename");
  amap.toggle().arg("-debug", debug, "Toggle debug output", "");
  amap.parse(argc, argv);

  // set NTL Thread pool size
  if (nthreads > 1)
    NTL::SetNumThreads(nthreads);

  std::cout << "\n*********************************************************";
  std::cout << "\n*           Privacy Preserving Search Example           *";
  std::cout << "\n*           =================================           *";
  std::cout << "\n*                                                       *";
  std::cout << "\n* This is a sample program for education purposes only. *";
  std::cout << "\n* It implements a very simple homomorphic encryption    *";
  std::cout << "\n* based db search algorithm for demonstration purposes. *";
  std::cout << "\n*                                                       *";
  std::cout << "\n*********************************************************";
  std::cout << "\n" << std::endl;

  std::cout << "---Initialising HE Environment ... ";
  // Initialize context
  // This object will hold information about the algebra used for this scheme.
  std::cout << "\nInitializing the Context ... ";
  HELIB_NTIMER_START(timer_Context);
  helib::Context context = helib::ContextBuilder<helib::BGV>()
                               .m(m)
                               .p(p)
                               .r(r)
                               .bits(bits)
                               .c(c)
                               .build();
  HELIB_NTIMER_STOP(timer_Context);

  // Secret key management
  std::cout << "\nCreating Secret Key ...";
  HELIB_NTIMER_START(timer_SecKey);
  // Create a secret key associated with the context
  helib::SecKey secret_key = helib::SecKey(context);
  // Generate the secret key
  secret_key.GenSecKey();
  HELIB_NTIMER_STOP(timer_SecKey);

  // Compute key-switching matrices that we need
  HELIB_NTIMER_START(timer_SKM);
  helib::addSome1DMatrices(secret_key);
  HELIB_NTIMER_STOP(timer_SKM);

  // Public key management
  // Set the secret key (upcast: FHESecKey is a subclass of FHEPubKey)
  std::cout << "\nCreating Public Key ...";
  HELIB_NTIMER_START(timer_PubKey);
  const helib::PubKey& public_key = secret_key;
  HELIB_NTIMER_STOP(timer_PubKey);

  // Get the EncryptedArray of the context
  const helib::EncryptedArray& ea = context.getEA();

  // Print the context
  std::cout << std::endl;
  if (debug)
    context.printout();

  // Print the security level
  // Note: This will be negligible to improve performance time.
  std::cout << "\n***Security Level: " << context.securityLevel()
            << " *** Negligible for this example ***" << std::endl;

  // Get the number of slot (phi(m))
  long nslots = ea.size();
  std::cout << "\nNumber of slots: " << nslots << std::endl;

  /************ Read in the database ************/
  std::vector<std::pair<std::string, std::string>> country_db;
  try {
    country_db = read_csv(db_filename);
  } catch (std::runtime_error& e) {
    std::cerr << "\n" << e.what() << std::endl;
    exit(1);
  }

  // Convert strings into numerical vectors
  std::cout << "\n---Initializing the encrypted key,value pair database ("
            << country_db.size() << " entries)...";
  std::cout
      << "\nConverting strings to numeric representation into Ptxt objects ..."
      << std::endl;

  // Generating the Plain text representation of Country DB
  HELIB_NTIMER_START(timer_PtxtCountryDB);
  std::vector<std::pair<helib::Ptxt<helib::BGV>, helib::Ptxt<helib::BGV>>>
      country_db_ptxt;
  for (const auto& country_capital_pair : country_db) {
    if (debug) {
      std::cout << "\t\tname_addr_pair.first size = "
                << country_capital_pair.first.size() << " ("
                << country_capital_pair.first << ")"
                << "\tname_addr_pair.second size = "
                << country_capital_pair.second.size() << " ("
                << country_capital_pair.second << ")" << std::endl;
    }

    helib::Ptxt<helib::BGV> country(context);
    // std::cout << "\tname size = " << country.size() << std::endl;
    for (long i = 0; i < country_capital_pair.first.size(); ++i)
      country.at(i) = country_capital_pair.first[i];

    helib::Ptxt<helib::BGV> capital(context);
    for (long i = 0; i < country_capital_pair.second.size(); ++i)
      capital.at(i) = country_capital_pair.second[i];
    country_db_ptxt.emplace_back(std::move(country), std::move(capital));
  }
  HELIB_NTIMER_STOP(timer_PtxtCountryDB);

  // Encrypt the Country DB
  std::cout << "Encrypting the database..." << std::endl;
  HELIB_NTIMER_START(timer_CtxtCountryDB);
  std::vector<std::pair<helib::Ctxt, helib::Ctxt>> encrypted_country_db;
  for (const auto& country_capital_pair : country_db_ptxt) {
    helib::Ctxt encrypted_country(public_key);
    helib::Ctxt encrypted_capital(public_key);
    public_key.Encrypt(encrypted_country, country_capital_pair.first);
    public_key.Encrypt(encrypted_capital, country_capital_pair.second);
    encrypted_country_db.emplace_back(std::move(encrypted_country),
                                      std::move(encrypted_capital));
  }

  HELIB_NTIMER_STOP(timer_CtxtCountryDB);

  // Print DB Creation Timers
  if (debug) {
    helib::printNamedTimer(std::cout << std::endl, "timer_Context");
    helib::printNamedTimer(std::cout, "timer_Chain");
    helib::printNamedTimer(std::cout, "timer_SecKey");
    helib::printNamedTimer(std::cout, "timer_SKM");
    helib::printNamedTimer(std::cout, "timer_PubKey");
    helib::printNamedTimer(std::cout, "timer_PtxtCountryDB");
    helib::printNamedTimer(std::cout, "timer_CtxtCountryDB");
  }

  std::cout << "\nInitialization Completed - Ready for Queries" << std::endl;
  std::cout << "--------------------------------------------" << std::endl;

  /** Create the query **/

  // Read in query from the command line
  std::string query_string;
  std::cout << "\nPlease enter the name of an European Country: ";
  // std::cin >> query_string;
  std::getline(std::cin, query_string);
  std::cout << "Looking for the Capital of " << query_string << std::endl;
  std::cout << "This may take few minutes ... " << std::endl;

  HELIB_NTIMER_START(timer_TotalQuery);

  HELIB_NTIMER_START(timer_EncryptQuery);
  // Convert query to a numerical vector
  helib::Ptxt<helib::BGV> query_ptxt(context);
  for (long i = 0; i < query_string.size(); ++i)
    query_ptxt[i] = query_string[i];

  // Encrypt the query
  helib::Ctxt query(public_key);
  public_key.Encrypt(query, query_ptxt);
  HELIB_NTIMER_STOP(timer_EncryptQuery);

  /************ Perform the database search ************/

  HELIB_NTIMER_START(timer_QuerySearch);
  std::vector<helib::Ctxt> mask;
  mask.reserve(country_db.size());
  for (const auto& encrypted_pair : encrypted_country_db) {
    helib::Ctxt mask_entry = encrypted_pair.first; // Copy of database key
    mask_entry -= query;                           // Calculate the difference
    mask_entry.power(p - 1);                       // Fermat's little theorem
    mask_entry.negate();                           // Negate the ciphertext
    mask_entry.addConstant(NTL::ZZX(1));           // 1 - mask = 0 or 1
    // Create a vector of copies of the mask
    std::vector<helib::Ctxt> rotated_masks(ea.size(), mask_entry);
    for (int i = 1; i < rotated_masks.size(); i++)
      ea.rotate(rotated_masks[i], i);             // Rotate each of the masks
    totalProduct(mask_entry, rotated_masks);      // Multiply each of the masks
    mask_entry.multiplyBy(encrypted_pair.second); // multiply mask with values
    mask.push_back(mask_entry);
  }

  // Aggregate the results into a single ciphertext
  // Note: This code is for educational purposes and thus we try to refrain
  // from using the STL and do not use std::accumulate
  helib::Ctxt value = mask[0];
  for (int i = 1; i < mask.size(); i++)
    value += mask[i];

  HELIB_NTIMER_STOP(timer_QuerySearch);

  /************ Decrypt and print result ************/

  HELIB_NTIMER_START(timer_DecryptQueryResult);
  helib::Ptxt<helib::BGV> plaintext_result(context);
  secret_key.Decrypt(plaintext_result, value);
  HELIB_NTIMER_STOP(timer_DecryptQueryResult);

  // Convert from ASCII to a string
  std::string string_result;
  for (long i = 0; i < plaintext_result.size(); ++i)
    string_result.push_back(static_cast<long>(plaintext_result[i]));

  HELIB_NTIMER_STOP(timer_TotalQuery);

  // Print DB Query Timers
  if (debug) {
    helib::printNamedTimer(std::cout << std::endl, "timer_EncryptQuery");
    helib::printNamedTimer(std::cout, "timer_QuerySearch");
    helib::printNamedTimer(std::cout, "timer_DecryptQueryResult");
    std::cout << std::endl;
  }

  if (string_result.at(0) == 0x00) {
    string_result =
        "Country name not in the database."
        "\n*** Please make sure to enter the name of a European Country"
        "\n*** with the first letter in upper case.";
  }
  std::cout << "\nQuery result: " << string_result << std::endl;
  helib::printNamedTimer(std::cout, "timer_TotalQuery");

  return 0;
}


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

#include <cmath>
#include <string>

#include <boost/format.hpp>
#include <boost/ptr_container/ptr_vector.hpp>

namespace sdm {

double PolynomialKernel::transformDivergence(double div) const {
    return std::pow(div + coef0, (int) degree);
}

std::string PolynomialKernel::name() const {
    return (boost::format("Polynomial(%d, %g)") % degree % coef0).str();
}
size_t PolynomialKernel::getDegree() const { return degree; }
double PolynomialKernel::getCoef0() const { return coef0; }

PolynomialKernel* PolynomialKernel::do_clone() const {
    return new PolynomialKernel(degree, coef0);
}

////////////////////////////////////////////////////////////////////////////////

const boost::ptr_vector<Kernel>* PolynomialKernelGroup::getTuningVector(
        const double* divs, size_t n)
const {
    boost::ptr_vector<Kernel>* kerns = new boost::ptr_vector<Kernel>;
    for (size_t d = 0; d < degrees.size(); d++) {
        for (size_t c = 0; c < coef0s.size(); c++) {
            kerns->push_back(new PolynomialKernel(degrees[d], coef0s[c]));
        }
    }
    return kerns;
}

PolynomialKernelGroup* PolynomialKernelGroup::do_clone() const {
    return new PolynomialKernelGroup(degrees, coef0s);
}

} // end namespace


/*
 [auto_generated]
 boost/numeric/odeint/util/detail/less_with_sign.hpp

 [begin_description]
 Helper function to compare times taking into account the sign of dt
 [end_description]

 Copyright 2012-2015 Mario Mulansky
 Copyright 2012 Karsten Ahnert

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_NUMERIC_ODEINT_INTEGRATE_DETAIL_LESS_WITH_SIGN_HPP_INCLUDED
#define BOOST_NUMERIC_ODEINT_INTEGRATE_DETAIL_LESS_WITH_SIGN_HPP_INCLUDED

#include <limits>

#include <boost/numeric/odeint/util/unit_helper.hpp>

namespace boost {
namespace numeric {
namespace odeint {
namespace detail {

/**
 * return t1 < t2 if dt > 0 and t1 > t2 if dt < 0 with epsilon accuracy
 */
template< typename T >
bool less_with_sign( T t1 , T t2 , T dt )
{
    if( get_unit_value(dt) > 0 )
        //return t1 < t2;
        return t2-t1 > std::numeric_limits<T>::epsilon();
    else
        //return t1 > t2;
        return t1-t2 > std::numeric_limits<T>::epsilon();
}

/**
 * return t1 <= t2 if dt > 0 and t1 => t2 if dt < 0 with epsilon accuracy
 */
template< typename T >
bool less_eq_with_sign( T t1 , T t2 , T dt )
{
    if( get_unit_value(dt) > 0 )
        return t1-t2 <= std::numeric_limits<T>::epsilon();
    else
        return t2-t1 <= std::numeric_limits<T>::epsilon();
}

template< typename T >
T min_abs( T t1 , T t2 )
{
    BOOST_USING_STD_MIN();
    BOOST_USING_STD_MAX();
    if( get_unit_value(t1)>0 )
        return min BOOST_PREVENT_MACRO_SUBSTITUTION ( t1 , t2 );
    else
        return max BOOST_PREVENT_MACRO_SUBSTITUTION ( t1 , t2 );
}

template< typename T >
T max_abs( T t1 , T t2 )
{
    BOOST_USING_STD_MIN();
    BOOST_USING_STD_MAX();
    if( get_unit_value(t1)>0 )
        return max BOOST_PREVENT_MACRO_SUBSTITUTION ( t1 , t2 );
    else
        return min BOOST_PREVENT_MACRO_SUBSTITUTION ( t1 , t2 );
}
} } } }

#endif


#include <iostream>
#include <fstream>
#include <vector>

#include <boost/algorithm/string.hpp>
#include <TopoMap.hpp>

std::string inputFile;
std::string outputFile;
int leafSize = 1;
bool verbose = false;

void readData(std::string dataFile, std::vector<double> &data, int &dim) {
    std::ifstream fi(dataFile);
    if(!fi.good()) {
        std::cerr << "Error reading file: " << dataFile << std::endl;
        exit(0);
    }

    dim = -1;
    data.clear();
    std::string line;
    while(std::getline(fi,line)) {

        std::vector<std::string> list;
        boost::split(list,line,boost::is_any_of(","));
        if(dim == -1) {
            dim = list.size();
        } else {
            if(dim != list.size()) {
                std::cerr << "Error!!! incorrect dimensions in input" << std::endl;
                exit(0);
            }
        }
        for(int d = 0;d < dim;d ++) {
            double val = std::atof(list[d].c_str());
            data.push_back(val);
        }
    }
    fi.close();
}

void writeProjection(std::vector<Point> &pts, std::string opFile) {
    std::ofstream fo(opFile);
    fo.precision(10);
    for(int i = 0;i < pts.size();i ++) {
        fo << pts[i].x << "," << pts[i].y << std::endl;
    }
    fo.close();
}

void printUsage() {
    std::cerr << "arguments:" << std::endl;
    std::cerr << "--input <csv file> (required argument) \t input point cloud as csv without any header. "
                 "Each line should be the coordinates of a single point." << std::endl;
    std::cerr << "--output <csv file> (required argument) \t file to which the projected points are stored." << std::endl;
    std::cerr << "--leafsize <integer> (optional argument. default = 1) \t this is used to set the leaf size of the kd-tree used for EMST computation" << std::endl;
    std::cerr << "--verbose (optional argument. default = false)" << std::endl;
}

void parseArguments(int argc, char *argv[]) {
    int ct = 0;
    for(int i = 1;i < argc;i ++) {
        std::string a(argv[i]);
        boost::trim(a);
        if(a.compare("--input") == 0) {
            inputFile = std::string((argv[i+1]));
            boost::trim(inputFile);
            ct ++;
        }
        if(a.compare("--output") == 0) {
            outputFile = std::string((argv[i+1]));
            boost::trim(outputFile);
            ct ++;
        }
        if(a.compare("--leafsize") == 0) {
            leafSize = atoi(argv[i+1]);
            if(leafSize < 1) {
                std::cerr << "Invalid usage: leaf size should be greater than 0." << std::endl;
                printUsage();
                exit(0);
            }
        }
        if(a.compare("--verbose") == 0) {
            verbose = true;
        }
        if(a.compare("--help") == 0) {
            printUsage();
            exit(0);
        }
    }

    if(ct != 2) {
        std::cerr << "Invalid usage: no input or output file present." << std::endl;
        printUsage();
        exit(0);
    }
}

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

    std::vector<double> data;
    int dim;
    readData(inputFile,data,dim);
    std::cerr << "read data file " << inputFile << " with dimension " << dim << std::endl;

    TopoMap tm(leafSize,verbose);
    std::vector<Point> pts = tm.project(data,dim);

    std::cerr << "writing output" << std::endl;
    writeProjection(pts,outputFile);
    return 0;
}


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

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

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

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

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_RING_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_RING_HPP

#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/point_order.hpp>

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

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

#include <boost/geometry/views/reversible_view.hpp>
#include <boost/geometry/views/closeable_view.hpp>

#include <boost/geometry/algorithms/area.hpp>
#include <boost/geometry/algorithms/intersects.hpp>
#include <boost/geometry/algorithms/detail/is_valid/has_spikes.hpp>
#include <boost/geometry/algorithms/detail/is_valid/has_duplicates.hpp>

#include <boost/geometry/strategies/area.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace is_valid
{

// struct to check whether a ring is topologically closed
template <typename Ring, closure_selector Closure /* open */>
struct is_topologically_closed
{
    static inline bool apply(Ring const&)
    {
        return true;
    }
};

template <typename Ring>
struct is_topologically_closed<Ring, closed>
{
    static inline bool apply(Ring const& ring)
    {
        return geometry::equals(range::front(ring), range::back(ring));
    }
};

template <typename ResultType, bool IsInteriorRing /* false */>
struct ring_area_predicate
{
    typedef std::greater<ResultType> type;
};

template <typename ResultType>
struct ring_area_predicate<ResultType, true>
{
    typedef std::less<ResultType> type;
};

template <typename Ring, bool IsInteriorRing>
struct is_properly_oriented
{
    typedef typename point_type<Ring>::type point_type;

    typedef typename strategy::area::services::default_strategy
        <
            typename cs_tag<point_type>::type,
            point_type
        >::type strategy_type;

    typedef detail::area::ring_area
        <
            order_as_direction<geometry::point_order<Ring>::value>::value,
            geometry::closure<Ring>::value
        > ring_area_type;

    typedef typename default_area_result<Ring>::type area_result_type;

    static inline bool apply(Ring const& ring)
    {
        typename ring_area_predicate
            <
                area_result_type, IsInteriorRing
            >::type predicate;

        // Check area
        area_result_type const zero = area_result_type();
        return predicate(ring_area_type::apply(ring, strategy_type()), zero);
    }
};

template
<
    typename Ring,
    bool AllowDuplicates,
    bool CheckSelfIntersections = true,
    bool IsInteriorRing = false
>
struct is_valid_ring
{
    static inline bool apply(Ring const& ring)
    {
        // return invalid if any of the following condition holds:
        // (a) the ring's size is below the minimal one
        // (b) the ring is not topologically closed
        // (c) the ring has spikes
        // (d) the ring has duplicate points (if AllowDuplicates is false)
        // (e) the boundary of the ring has self-intersections
        // (f) the order of the points is inconsistent with the defined order
        //
        // Note: no need to check if the area is zero. If this is the
        // case, then the ring must have at least two spikes, which is
        // checked by condition (c).

        closure_selector const closure = geometry::closure<Ring>::value;

        return
            ( boost::size(ring)
              >= core_detail::closure::minimum_ring_size<closure>::value )
            && is_topologically_closed<Ring, closure>::apply(ring)
            && (AllowDuplicates || !has_duplicates<Ring, closure>::apply(ring))
            && !has_spikes<Ring, closure>::apply(ring)
            && !(CheckSelfIntersections && geometry::intersects(ring))
            && is_properly_oriented<Ring, IsInteriorRing>::apply(ring);
    }
};

}} // namespace dispatch
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

// A Ring is a Polygon with exterior boundary only.
// The Ring's boundary must be a LinearRing (see OGC 06-103-r4,
// 6.1.7.1, for the definition of LinearRing)
//
// Reference (for polygon validity): OGC 06-103r4 (6.1.11.1)
template <typename Ring, bool AllowSpikes, bool AllowDuplicates>
struct is_valid<Ring, ring_tag, AllowSpikes, AllowDuplicates>
    : detail::is_valid::is_valid_ring<Ring, AllowDuplicates>
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_RING_HPP


/**
 *  @file
 *  @copyright defined in evt/LICENSE.txt
 */
#include <evt/chain/percent_slim.hpp>

// This fixes the issue in safe_numerics in boost 1.69
#include <evt/chain/workaround/boost/safe_numerics/exception.hpp>
#include <boost/safe_numerics/safe_integer.hpp>

namespace evt { namespace chain {

string
percent_slim::to_string() const {
    if(v_.value == 0) {
        return "0";
    }

    auto str = fc::to_string(v_.value);
    if(kPrecision >= str.size()) {
        auto zeros = kPrecision - str.size();
        str.insert(0, "0.");
        str.insert(2, zeros, '0');
        str.erase(str.find_last_not_of('0') + 1);
    }
    else {
        assert(v_.value == kMaxAmount);
        return "1";
    }

    return str;
}

percent_slim
percent_slim::from_string(const string& from) {
    using namespace boost::safe_numerics;
    static uint32_t _pows[] = { 100'000, 10'000, 1'000, 100, 10, 1 };

    try {
        string s = fc::trim(from);

        // Ensure that if decimal point is used (.), decimal fraction is specified
        auto dot_pos = s.find('.');
        if(dot_pos != string::npos) {
            EVT_ASSERT((dot_pos != s.size() - 1), percent_type_exception,
                       "Missing decimal fraction after decimal point");
        }

        // Parse precision
        auto p = 0u;
        if(dot_pos != string::npos) {
            p = s.size() - dot_pos - 1;
            EVT_ASSERT2(p <= kPrecision, percent_type_exception,
                "Exceed percent max precision: {}", kPrecision);
        }

        // Parse amount
        auto amount = safe<uint32_t>(0);
        if(dot_pos != string::npos) {
            s.erase(dot_pos, 1);
        }
        amount  = fc::to_int64(s);
        amount *= _pows[p];

        return percent_slim((uint32_t)amount);
    }
    EVT_CAPTURE_AND_RETHROW(percent_type_exception, (from));
}

}}  // namespace evt::chain


#include <vector>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <fstream>
#include "OccupancyGrid/cvmat_serialization.h"
#include <boost/math/constants/constants.hpp>
using namespace std;
int main(int argc, const char *argv[])
{
  ifstream logfile;
  if (argc > 1)
    logfile.open(argv[1]);
  std::string line;

  cv::Mat laser_pose_all(0, 3, CV_64F);
  cv::Mat laser_range_all;
  cv::Mat scan_angles_all;
  cv::Mat laser_reflectance_all;
  bool first_iteration = true;
  while (std::getline(logfile, line)) {
    if (line.length() > 0 && line[0] == '#')
      continue;
    std::istringstream iss(line);
    std::string message_name; // ODOM / PARAM / FLASER
    if (!(iss >> message_name))
      std::cerr << "Error parsing line 1:" << line << std::endl;
    const double pi = boost::math::constants::pi<double>();
    double max_angle = +pi/2;
    double min_angle = -pi/2;
    if (message_name == "FLASER") {
      size_t count;
      iss >> count;
      if (first_iteration == true) {
        first_iteration = false;
        laser_range_all.create(0, count, CV_64F);
        scan_angles_all.create(0, count, CV_64F);
        laser_reflectance_all.create(0, count, CV_8U);
      }
      cv::Mat range(1, count, CV_64F);
      cv::Mat angles(1, count, CV_64F);
      cv::Mat reflectance(1, count, CV_8U);
      for(size_t i = 0; i < count; ++i) {
        double r;
        iss >> r;
        double angle = min_angle + i * (max_angle - min_angle) / (count - 1);
        range.at<double>(0, i) = r;
        angles.at<double>(0, i) = angle;
        reflectance.at<uint8_t>(0, i) = 1;
      }
      laser_range_all.push_back(range);
      scan_angles_all.push_back(angles);
      laser_reflectance_all.push_back(reflectance);

      double x, y, theta, odom_x, odom_y, odom_theta;
      iss >> x >> y >> theta >> odom_x >> odom_y >> odom_theta;
      cv::Mat current_pose(1, 3, CV_64F);
      current_pose.at<double>(0, 0) = x;
      current_pose.at<double>(0, 1) = y;
      current_pose.at<double>(0, 2) = theta;
      laser_pose_all.push_back(current_pose);
      // this is to check
      double ipc_timestamp, logger_timestamp;
      std::string ipc_hostname;
      iss >> ipc_timestamp >> ipc_timestamp >> logger_timestamp;
    }
  }
  saveMat(laser_pose_all, "laser_pose_all.bin");
  saveMat(laser_range_all, "laser_range_all.bin");
  saveMat(scan_angles_all, "scan_angles_all.bin");
  saveMat(laser_reflectance_all, "laser_reflectance_all.bin");
  return 0;
}


#include <ros/ros.h>
#include <image_transport/image_transport.h>
#include <opencv2/highgui/highgui.hpp>
#include <cv_bridge/cv_bridge.h>
#include <arpa/inet.h>
//#include <boost/endian/conversion.hpp>
#include <tf/transform_broadcaster.h>

#include <nav_msgs/Odometry.h>

#include <unsupported/Eigen/MatrixFunctions>
#include <eigen3/Eigen/Core>
#include <eigen3/Eigen/Dense>
#include <eigen3/Eigen/Geometry>

#include <cmath>

using namespace std;
using namespace cv;
using namespace Eigen;

double dt = 1;

Eigen::VectorXd		p_k(3);
Eigen::VectorXd 		q_k(4);
Eigen::VectorXd		q_k_1(4);
Eigen::VectorXd		q_k_dot(4);
Eigen::MatrixXd 		R_k(3,3);		//passo attuale
Eigen::MatrixXd 		R_k_1(3,3);		//passo precedente
Eigen::MatrixXd 		R_k_dot(3,3);
Eigen::MatrixXd 		R_k_dot2(3,3);
Eigen::MatrixXd		S_w(3,3);
Eigen::MatrixXd		S_w2(3,3);
Eigen::MatrixXd		E_q(4,3);
Eigen::VectorXd		w(3);

void skew(const geometry_msgs::TransformStamped::ConstPtr& Transform_estimate)
{

std::cout<<"---------------FRAME_-----------------------"<< endl;
	cout <<	Transform_estimate->header.frame_id << endl;
std::cout<<"--------------------------------------"<< endl;

std::cout<<"---------------FRAME_CHILD-----------------------"<< endl;
	cout <<	Transform_estimate->child_frame_id <<endl;
std::cout<<"--------------------------------------"<< endl;

//____prendo la misura corrispondente al centro dell'oggetto

		p_k[0] = Transform_estimate->transform.translation.x;
		p_k[1] = Transform_estimate->transform.translation.y;
		p_k[2] = Transform_estimate->transform.translation.z;
		q_k[0] = Transform_estimate->transform.rotation.w;
		q_k[1] = Transform_estimate->transform.rotation.x;
		q_k[2] = Transform_estimate->transform.rotation.y;
		q_k[3] = Transform_estimate->transform.rotation.z;

//		q[0] w q[1] x  q[2] y  q[3] z

		std::cout<<"---------------QUATERNION-----------------------"<< endl;
		std::cout<<  q_k[0] << endl;
		std::cout<<  q_k[1] << endl;
		std::cout<<  q_k[2] << endl;
		std::cout<<  q_k[3] << endl;
		std::cout<<"------------------------------------------------"<< endl;

		std::cout<<"---------------QUATERNION_PREVIOUS--------------"<< endl;
		std::cout<<  q_k_1[0] << endl;
		std::cout<<  q_k_1[1] << endl;
		std::cout<<  q_k_1[2] << endl;
		std::cout<<  q_k_1[3] << endl;
		std::cout<<"------------------------------------------------"<< endl;

		std::cout<<"---------------SAMPLING_TIME--------------------"<< endl;
		std::cout<<  dt << endl;
		std::cout<<"------------------------------------------------"<< endl;

		q_k_dot(0) = ( q_k(0) - q_k_1(0) ) / dt ;
		q_k_dot(1) = ( q_k(1) - q_k_1(1) ) / dt ;
		q_k_dot(2) = ( q_k(2) - q_k_1(2) ) / dt ;
		q_k_dot(3) = ( q_k(3) - q_k_1(3) ) / dt ;

		std::cout<<"---------------QUATERNION_DOT-------------------"<< endl;
		std::cout<<  q_k_dot[0] << endl;
		std::cout<<  q_k_dot[1] << endl;
		std::cout<<  q_k_dot[2] << endl;
		std::cout<<  q_k_dot[3] << endl;
		std::cout<<"------------------------------------------------"<< endl;

		E_q(0,0) = - q_k(1) ;
		E_q(0,1) = - q_k(2) ;
		E_q(0,2) = - q_k(3) ;
		E_q(1,0) =   q_k(0) ;
		E_q(1,1) =   q_k(3) ;
		E_q(1,2) = - q_k(2) ;
		E_q(2,0) = - q_k(3) ;
		E_q(2,1) =   q_k(0) ;
		E_q(2,2) =   q_k(1) ;
		E_q(3,0) =   q_k(2) ;
		E_q(3,1) = - q_k(1) ;
		E_q(3,2) =   q_k(0) ;

		std::cout<<"---------------E_q-----------------------------"<< endl;
		std::cout<<  E_q << endl;
		std::cout<<"------------------------------------------------"<< endl;

		w = 2*E_q.transpose()*q_k_dot ;

		std::cout<<"---------------ANGULAR_VELOCITY--------------------"<< endl;
		std::cout<<  w << endl;
		std::cout<<"------------------------------------------------"<< endl;

		R_k(0,0)	= q_k[0]*q_k[0] + q_k[1]*q_k[1] - q_k[2]*q_k[2] - q_k[3]*q_k[3];
		R_k(0,1)	= 2*q_k[1]*q_k[2] - 2*q_k[0]*q_k[3];
		R_k(0,2)	= 2*q_k[1]*q_k[3] + 2*q_k[0]*q_k[2];
		R_k(1,0)	= 2*q_k[1]*q_k[2] + 2*q_k[0]*q_k[3];
		R_k(1,1)	= q_k[0]*q_k[0] + q_k[2]*q_k[2] - q_k[1]*q_k[1] - q_k[3]*q_k[3];
		R_k(1,2)	= 2*q_k[2]*q_k[3] - 2*q_k[0]*q_k[1];
		R_k(2,0)	= 2*q_k[1]*q_k[3] - 2*q_k[0]*q_k[2];
		R_k(2,1)	= 2*q_k[2]*q_k[3] + 2*q_k[0]*q_k[1];
		R_k(2,2)	= q_k[0]*q_k[0] + q_k[3]*q_k[3] - q_k[2]*q_k[2] - q_k[1]*q_k[1];

		//			R_k_dot = ( R_k - R_k_1 ) / dt ;

		R_k_dot(0,0)	= ( R_k(0,0) - R_k_1(0,0) ) / dt ;
		R_k_dot(0,1)	= ( R_k(0,1) - R_k_1(0,1) ) / dt ;
		R_k_dot(0,2)	= ( R_k(0,2) - R_k_1(0,2) ) / dt ;
		R_k_dot(1,0)	= ( R_k(1,0) - R_k_1(1,0) ) / dt ;
		R_k_dot(1,1)	= ( R_k(1,1) - R_k_1(1,1) ) / dt ;
		R_k_dot(1,2)	= ( R_k(1,2) - R_k_1(1,2) ) / dt ;
		R_k_dot(2,0)	= ( R_k(2,0) - R_k_1(2,0) ) / dt ;
		R_k_dot(2,1)	= ( R_k(2,1) - R_k_1(2,1) ) / dt ;
		R_k_dot(2,2)	= ( R_k(2,2) - R_k_1(2,2) ) / dt ;

		R_k_dot2(0,0)	= 2*(q_k(0)*q_k_dot(0) + q_k(1)*q_k_dot(1) - q_k(2)*q_k_dot(2) - q_k(3)*q_k_dot(3));
		R_k_dot2(0,1)	= 2*(q_k_dot(1)*q_k(2) + q_k(1)*q_k_dot(2) - q_k_dot(0)*q_k(3) - q_k(0)*q_k_dot(3));
		R_k_dot2(0,2)	= 2*(q_k_dot(1)*q_k(3) + q_k(1)*q_k_dot(3) + q_k_dot(0)*q_k(2) + q_k(0)*q_k_dot(2));
		R_k_dot2(1,0)	= 2*(q_k_dot(1)*q_k(2) + q_k(1)*q_k_dot(2) + q_k_dot(0)*q_k(3) + q_k(0)*q_k_dot(3));
		R_k_dot2(1,1)	= 2*(q_k(0)*q_k_dot(0) - q_k(1)*q_k_dot(1) + q_k(2)*q_k_dot(2) - q_k(3)*q_k_dot(3));
		R_k_dot2(1,2)	= 2*(q_k_dot(2)*q_k(3) + q_k(2)*q_k_dot(3) - q_k_dot(0)*q_k(1) - q_k(0)*q_k_dot(1));
		R_k_dot2(2,0)	= 2*(q_k_dot(1)*q_k(3) + q_k(1)*q_k_dot(3) - q_k_dot(0)*q_k(2) - q_k(0)*q_k_dot(2));
		R_k_dot2(2,1)	= 2*(q_k_dot(2)*q_k(3) + q_k(2)*q_k_dot(3) + q_k_dot(0)*q_k(1) + q_k(0)*q_k_dot(1));
		R_k_dot2(2,2)	= 2*(q_k(0)*q_k_dot(0) + q_k(1)*q_k_dot(1) - q_k(2)*q_k_dot(2) - q_k(3)*q_k_dot(3));

		S_w2 = R_k_dot2 * R_k.transpose() ;

		S_w = R_k_dot * R_k.transpose() ;			//body_frame

/*

		std::cout<<"---------------SAMPLING_TIME--------------------"<< endl;
		std::cout<<  dt << endl;
		std::cout<<"------------------------------------------------"<< endl;

*/

		std::cout<<"----------------ROTATION_MATRIX-----------------"<< endl;
		std::cout<<  R_k << endl;
		std::cout<<"------------------------------------------------"<< endl;

   	std::cout<<"-------------DOT_ROTATION_MATRIX----------------"<< endl;
		std::cout<<  R_k_dot << endl;
		std::cout<<"------------------------------------------------"<< endl;

		std::cout<<"-----------------SKEW_MATRIX--------------------"<< endl;
		std::cout<<  S_w << endl;
		std::cout<<"------------------------------------------------"<< endl;

/*

	   std::cout<<"----------2_____________DOT_ROTATION_MATRIX----------------"<< endl;
		std::cout<<  R_k_dot2 << endl;
		std::cout<<"------------------------------------------------"<< endl;

		std::cout<<"----------2_______SKEW_MATRIX--------------------"<< endl;
		std::cout<<  S_w2 << endl;
		std::cout<<"------------------------------------------------"<< endl;

*/

		R_k_1 = R_k ;

		q_k_1 = q_k ;

}

int main(int argc, char** argv)
{

	//inizializzazione nodo
	ros::init(argc, argv, "skew");// ROS node
   ros::NodeHandle nn;

	cout<<"1"<<endl;

	ros::Subscriber pose_sub;
	pose_sub = nn.subscribe("/transform_obj", 1, &skew);

	ros::Rate loop_rate(100);//RATE, non metterlo al massimo sennò occupo tutta la CPU ( no good )

	cout<<"2"<<endl;

//inizializzazione

	p_k     <<  0,0,0;
	q_k	  <<  1,0,0,0;
	q_k_1	  <<  1,0,0,0;
	q_k_dot <<  1,0,0,0;

	w			<< 0,0,0 ;

	R_k     <<  MatrixXd::Zero(3,3);
	R_k_1   <<  MatrixXd::Zero(3,3);
	R_k_dot <<  MatrixXd::Zero(3,3);
	R_k_dot2<<  MatrixXd::Zero(3,3);
	E_q     <<  MatrixXd::Zero(4,3);

    ros::Time current_time, last_time;
    current_time = ros::Time::now();
    last_time = ros::Time::now();

	while(nn.ok())
	{

		current_time = ros::Time::now();
		dt = (current_time - last_time).toSec();

		ros::spinOnce();

		last_time = current_time;

		loop_rate.sleep();

	}

}


#include <NTL/GF2EX.h>
#include <NTL/GF2XFactoring.h>

namespace NTL {

void DivRemPlain(GF2EX& q, GF2EX& r, const GF2EX& a, const GF2EX& b, bool plain);

}

NTL_CLIENT

#define TIME_IT(t, action) \
do { \
   double _t0, _t1; \
   long _iter = 1; \
   long _cnt = 0; \
   do { \
      _t0 = GetTime(); \
      for (long _i = 0; _i < _iter; _i++) { action; _cnt++; } \
      _t1 = GetTime(); \
   } while ( _t1 - _t0 < 2 && (_iter *= 2)); \
   t = (_t1 - _t0)/_iter; \
} while(0)

long test(long k)
{
   GF2X P;

   BuildIrred(P, k);
   GF2EPush push(P);

   for (long n = 25; ; n+=25) {
      cerr << ",";
      GF2EX a, b, q, r;
      random(a, 2*n);
      random(b, n);
      double t1, t2;
      TIME_IT(t1, DivRemPlain(q, r, a, b, false));
      TIME_IT(t2, DivRemPlain(q, r, a, b, true));
      double t = t1/t2;
      if (t <= 0.95) return n;
   }
}

int main()
{
   cerr << "0.5 " << test(32) << "\n";
   for (long i = 1; i <= 50; i++) {
      cerr << i << " " << test(64*i) << "\n";
   }

   for (long i = 75; i <= 200 ; i+=25) {
      cerr << i << " " << test(64*i) << "\n";
   }
}


#include "geometry.h"
#include "atomdata.h"
#include "random.h"
#include "average.h"
#include "aux/eigensupport.h"
#include <spdlog/spdlog.h>
#include <cereal/archives/binary.hpp>
#include <Eigen/Eigenvalues>

namespace Faunus::Geometry {

GeometryBase::~GeometryBase() = default;

GeometryImplementation::~GeometryImplementation() = default;

Cuboid::Cuboid(const Point &side_length) {
    boundary_conditions = BoundaryCondition(ORTHOGONAL, {PERIODIC, PERIODIC, PERIODIC});
    setLength(side_length);
}

Cuboid::Cuboid() : Cuboid(Point::Zero()) {}

Point Cuboid::getLength() const { return box; }

void Cuboid::setLength(const Point &len) {
    box = len;
    box_half = 0.5 * box;
    box_inv = box.cwiseInverse();
}

double Cuboid::getVolume(int) const { return box.x() * box.y() * box.z(); }

Point Cuboid::setVolume(double volume, const VolumeMethod method) {
    const double old_volume = getVolume();
    double alpha;
    Point new_box, box_scaling;
    switch (method) {
    case ISOTROPIC:
        alpha = std::cbrt(volume / old_volume);
        box_scaling = {alpha, alpha, alpha};
        break;
    case XY:
        alpha = std::sqrt(volume / old_volume);
        box_scaling = {alpha, alpha, 1.0};
        break;
    case Z:
        alpha = volume / old_volume;
        box_scaling = {1.0, 1.0, alpha};
        break;
    case ISOCHORIC:
        // z is scaled by 1/alpha/alpha, x and y are scaled by alpha
        alpha = std::cbrt(volume / old_volume);
        box_scaling = {alpha, alpha, 1 / (alpha * alpha)};
        volume = old_volume;
        break;
    default:
        throw std::invalid_argument("unsupported volume scaling method for the cuboid geometry");
    }
    new_box = box.cwiseProduct(box_scaling);
    setLength(new_box);
    assert(fabs(getVolume() - volume) < 1e-6);
    return box_scaling; // this will scale any point to new volume
}

void Cuboid::boundary(Point &a) const {
    if (boundary_conditions.direction.x() == PERIODIC) {
        if (std::fabs(a.x()) > box_half.x())
            a.x() -= box.x() * anint(a.x() * box_inv.x());
    }
    if (boundary_conditions.direction.y() == PERIODIC) {
        if (std::fabs(a.y()) > box_half.y())
            a.y() -= box.y() * anint(a.y() * box_inv.y());
    }
    if (boundary_conditions.direction.z() == PERIODIC) {
        if (std::fabs(a.z()) > box_half.z())
            a.z() -= box.z() * anint(a.z() * box_inv.z());
    }
}

Point Cuboid::vdist(const Point &a, const Point &b) const {
    Point distance(a - b);
    if (boundary_conditions.direction.x() == PERIODIC) {
        if (distance.x() > box_half.x())
            distance.x() -= box.x();
        else if (distance.x() < -box_half.x())
            distance.x() += box.x();
    }
    if (boundary_conditions.direction.y() == PERIODIC) {
        if (distance.y() > box_half.y())
            distance.y() -= box.y();
        else if (distance.y() < -box_half.y())
            distance.y() += box.y();
    }
    if (boundary_conditions.direction.z() == PERIODIC) {
        if (distance.z() > box_half.z())
            distance.z() -= box.z();
        else if (distance.z() < -box_half.z())
            distance.z() += box.z();
    }
    return distance;
}

void Cuboid::randompos(Point &m, Random &rand) const {
    m.x() = (rand() - 0.5) * box.x();
    m.y() = (rand() - 0.5) * box.y();
    m.z() = (rand() - 0.5) * box.z();
}

bool Cuboid::collision(const Point &a) const {
    return (std::fabs(a.x()) > box_half.x() || std::fabs(a.y()) > box_half.y() || std::fabs(a.z()) > box_half.z());
}

void Cuboid::from_json(const json &j) {
    box.setZero();
    if (auto length_ = j.at("length"); length_.is_number()) {
        auto l = length_.get<double>();
        setLength({l, l, l});
        return;
    } else if (length_.is_array()) {
        if (length_.size() == 3) {
            setLength(length_.get<Point>());
            return;
        }
    }
    throw ConfigurationError("sidelength syntax error");
}

void Cuboid::to_json(json &j) const { j = {{"length", box}}; }

// =============== Slit ===============

Slit::Slit(const Point &p) : Tbase(p) {
    boundary_conditions = BoundaryCondition(ORTHOGONAL, {PERIODIC, PERIODIC, FIXED});
}

Slit::Slit(double x, double y, double z) : Slit(Point(x, y, z)) {}
Slit::Slit(double x) : Slit(x, x, x) {}

// =============== Sphere ===============

Sphere::Sphere(double radius) : radius(radius) {
    boundary_conditions = BoundaryCondition(ORTHOGONAL, {FIXED, FIXED, FIXED});
}

Point Sphere::getLength() const { return {2.0 * radius, 2.0 * radius, 2.0 * radius}; }

double Sphere::getVolume(int dim) const {
    double result;
    switch (dim) {
    case 3:
        result = 4.0 / 3.0 * pc::pi * radius * radius * radius; // volume
        break;
    case 2:
        result = 4.0 * pc::pi * radius * radius; // surface area
        break;
    case 1:
        result = 2.0 * radius; // diameter
        break;
    default:
        throw std::invalid_argument("unsupported volume dimension for the sphere: " + std::to_string(dim));
    }
    return result;
}

Point Sphere::setVolume(double volume, const VolumeMethod method) {
    const double old_radius = radius;
    Point box_scaling;
    if (method == ISOTROPIC) {
        radius = std::cbrt(volume / (4.0 / 3.0 * pc::pi));
        assert(std::fabs(getVolume() - volume) < 1e-6 && "error setting sphere volume");
    } else {
        throw std::invalid_argument("unsupported volume scaling method for the spherical geometry");
    }
    box_scaling.setConstant(radius / old_radius);
    return box_scaling;
}

void Sphere::boundary(Point &) const {} // no PBC

bool Sphere::collision(const Point &point) const { return point.squaredNorm() > radius * radius; }

Point Sphere::vdist(const Point &a, const Point &b) const {
    // no pbc; shall we check points coordinates?
    Point distance(a - b);
    return distance;
}

void Sphere::randompos(Point &m, Random &rand) const {
    double r2 = radius * radius, d = 2 * radius;
    do {
        m.x() = (rand() - 0.5) * d;
        m.y() = (rand() - 0.5) * d;
        m.z() = (rand() - 0.5) * d;
    } while (m.squaredNorm() > r2);
}

void Sphere::from_json(const json &j) { radius = j.at("radius").get<double>(); }

void Sphere::to_json(json &j) const { j = {{"radius", radius}}; }

// =============== Hypersphere 2D ===============

Hypersphere2d::Hypersphere2d(double radius) : Sphere(radius) { boundary_conditions = BoundaryCondition(NON3D); }

Point Hypersphere2d::vdist(const Point &a, const Point &b) const {
    // ugly but works, needs fixing though...
    Point distance3d(a - b);
    double angle = std::acos(a.dot(b) / (radius * radius));
    double distance = radius * angle;
    return distance3d / distance3d.norm() * distance;
}

void Hypersphere2d::randompos(Point &m, Random &rand) const {
    Sphere::randompos(m, rand);
    m = m / m.norm() * radius;
}

bool Hypersphere2d::collision(const Point &a) const {
    bool collision = std::fabs(a.norm() - radius) > 1e-6;
    return collision;
}

// =============== Hexagonal Prism ===============

const Eigen::Matrix3d HexagonalPrism::rhombic2cartesian =
    (Eigen::Matrix3d() << std::cos(-pc::pi / 6.0), 0.0, 0.0, std::sin(-pc::pi / 6.0), 1.0, 0.0, 0.0, 0.0, 1.0)
        .finished();

const Eigen::Matrix3d HexagonalPrism::cartesian2rhombic = rhombic2cartesian.inverse();

HexagonalPrism::HexagonalPrism(double side, double height) {
    // the current implementation is hardcoded as bellow and ignores other periodic_directions settings
    boundary_conditions = BoundaryCondition(ORTHOHEXAGONAL, {PERIODIC, PERIODIC, FIXED});
    set_box(side, height);
}

Point HexagonalPrism::getLength() const { return box; }

double HexagonalPrism::getVolume(int) const {
    return 3.0 / 4.0 * box.x() * box.y() * box.z(); // 3 * inner_radius * outer_radius * height
}

void HexagonalPrism::set_box(double side, double height) { box = {std::sqrt(3.0) * side, 2.0 * side, height}; }

Point HexagonalPrism::setVolume(double volume, const VolumeMethod method) {
    const double old_volume = getVolume();
    double alpha;
    Point box_scaling;

    switch (method) {
    case ISOTROPIC:
        alpha = std::cbrt(volume / old_volume);
        box_scaling = {alpha, alpha, alpha};
        break;
    case XY:
        alpha = std::sqrt(volume / old_volume);
        box_scaling = {alpha, alpha, 1.0};
        break;
    case Z:
        alpha = volume / old_volume;
        box_scaling = {1.0, 1.0, alpha};
        break;
    case ISOCHORIC:
        // radius is scaled by alpha, z is scaled by 1/alpha/alpha
        alpha = std::cbrt(volume / old_volume);
        box_scaling = {alpha, alpha, 1.0 / (alpha * alpha)};
        volume = old_volume;
        break;
    default:
        throw std::invalid_argument("unsupported volume scaling method for the hexagonal-prism geometry");
    }
    box = box.cwiseProduct(box_scaling);
    assert(fabs(getVolume() - volume) < 1e-6);
    return box_scaling;
}

Point HexagonalPrism::vdist(const Point &a, const Point &b) const {
    Point distance(a - b);
    boundary(distance);
    return distance;
}

bool HexagonalPrism::collision(const Point &a) const {
    const double height = box.z();
    const double outer_radius = 0.5 * box.y();

    // Hexagon can be divided into three rhombuses. Using natural rhombic coordinates, it can be
    // straightforwardly determined if the particular point is inside the rhombus. If the point is mirrored to
    // the first quadrant taking the absolute values of coordinates, only one rhombus needs to be evaluated.
    Point b = cartesian2rhombic * a.cwiseAbs();
    bool collision = b.z() > 0.5 * height || b.x() > outer_radius || b.y() > outer_radius;
    return collision;
}

void HexagonalPrism::boundary(Point &a) const {
    // TODO optimise and add documentation
    const double sqrtThreeByTwo = sqrt(3.0) / 2.0;
    const Point unitvX = {1.0, 0.0, 0.0};
    const Point unitvY = {0.5, sqrtThreeByTwo, 0.0};
    const Point unitvZ = {-0.5, sqrtThreeByTwo, 0.0};

    double tmp = a.dot(unitvX);
    if (std::fabs(tmp) > 0.5 * box.x())
        a -= box.x() * anint(tmp / box.x()) * unitvX;

    if (a.dot(unitvY) > 0.5 * box.x()) {
        a -= box.x() * unitvY;
        if (a.dot(unitvX) < -0.5 * box.x()) // Check that point did not get past x-limit
            a += box.x() * unitvX;
    }
    if (a.dot(unitvY) < -0.5 * box.x()) {
        a = a + box.x() * unitvY;
        if (a.dot(unitvX) > 0.5 * box.x()) // Check that point did not get past x-limit
            a = a - box.x() * unitvX;
    }

    tmp = a.dot(unitvZ);
    if (std::fabs(tmp) > 0.5 * box.x())
        a -= box.x() * anint(tmp / box.x()) * unitvZ;
    if (std::fabs(a.z()) > 0.5 * box.z())
        a.z() -= box.z() * anint(a.z() / box.z());
}

void HexagonalPrism::randompos(Point &m, Random &rand) const {
    // Generating random points in hexagonal-prism coordinates is not feasible as the space coverage
    // will not be homogeneous back in the cartesian coordinates.
    m.z() = (rand() - 0.5) * box.z();
    do {
        // x,y shall be always generated as a pair; 75% chance to hit
        m.x() = (rand() - 0.5) * box.x();
        m.y() = (rand() - 0.5) * box.y();
    } while (collision(m));
}

void HexagonalPrism::from_json(const json &j) {
    auto radius = j.at("radius").get<double>(); // inner radius
    auto height = j.at("length").get<double>();
    auto edge = 2.0 / std::sqrt(3.0) * radius;
    set_box(edge, height);
}

void HexagonalPrism::to_json(json &j) const { j = {{"radius", 0.5 * box.x()}, {"length", box.z()}}; }

double HexagonalPrism::innerRadius() const { return 0.5 * box.x(); }
double HexagonalPrism::outerRadius() const { return 0.5 * box.y(); }
double HexagonalPrism::height() const { return box.z(); }

// =============== Cylinder ===============

Cylinder::Cylinder(double radius, double height) : radius(radius), height(height) {
    boundary_conditions = BoundaryCondition(ORTHOGONAL, {FIXED, FIXED, PERIODIC});
}

Point Cylinder::getLength() const { return {2.0 * radius, 2.0 * radius, height}; }

double Cylinder::getVolume(int) const { return pc::pi * radius * radius * height; }

Point Cylinder::setVolume(double volume, const VolumeMethod method) {
    const double old_volume = getVolume();
    double alpha;
    Point box_scaling;

    switch (method) {
    case ISOTROPIC:
        alpha = std::cbrt(volume / old_volume);
        radius *= alpha;
        height *= alpha;
        box_scaling = {alpha, alpha, alpha};
        break;
    case XY: // earlier wrongly named ISOTROPIC!
        alpha = std::sqrt(volume / old_volume);
        radius *= alpha;
        box_scaling = {alpha, alpha, 1.0};
        break;
    case Z:
        alpha = volume / old_volume;
        height *= alpha;
        box_scaling = {1.0, 1.0, alpha};
        break;
    case ISOCHORIC:
        // height is scaled by 1/alpha/alpha, radius is scaled by alpha
        alpha = std::cbrt(volume / old_volume);
        radius *= alpha;
        height /= (alpha * alpha);
        box_scaling = {alpha, alpha, 1.0 / (alpha * alpha)};
        volume = old_volume;
        break;
    default:
        throw std::invalid_argument("unsupported volume scaling method for the cylindrical geometry");
    }
    assert(std::fabs(getVolume() - volume) < 1e-6 && "error setting sphere volume");
    return box_scaling;
}

void Cylinder::boundary(Point &a) const {
    // z-pbc
    if (std::fabs(a.z()) > 0.5 * height)
        a.z() -= height * anint(a.z() / height);
}

bool Cylinder::collision(const Point &a) const {
    return std::fabs(a.z()) > 0.5 * height || a.x() * a.x() + a.y() * a.y() > radius * radius;
}

Point Cylinder::vdist(const Point &a, const Point &b) const {
    Point distance(a - b);
    if (distance.z() > 0.5 * height)
        distance.z() -= height;
    else if (distance.z() < -0.5 * height)
        distance.z() += height;
    return distance;
}

void Cylinder::randompos(Point &m, Random &rand) const {
    double r2 = radius * radius, d = 2.0 * radius;
    m.z() = (rand() - 0.5) * height;
    do {
        // x,y shall always generated as a pair; 78.5% chance to hit
        m.x() = (rand() - 0.5) * d;
        m.y() = (rand() - 0.5) * d;
    } while (m.x() * m.x() + m.y() * m.y() > r2);
}

void Cylinder::from_json(const json &j) {
    radius = j.at("radius").get<double>();
    height = j.at("length").get<double>();
}

void Cylinder::to_json(json &j) const { j = {{"radius", radius}, {"length", height}}; }

// =============== Truncated Octahedron ===============

TruncatedOctahedron::TruncatedOctahedron(double side) : side(side) {
    // the current implementation is hardcoded as bellow and ignores other periodic_directions settings
    boundary_conditions = BoundaryCondition(TRUNC_OCTAHEDRAL, {PERIODIC, PERIODIC, PERIODIC});
}

Point TruncatedOctahedron::getLength() const {
    // todo check orientation in xyz
    return Point::Constant(2.0 * std::sqrt(2.0) * side); // distance between opposite square faces
}

double TruncatedOctahedron::getVolume(int) const { return std::sqrt(128.0) * side * side * side; }

Point TruncatedOctahedron::setVolume(double volume, const VolumeMethod method) {
    const double old_side = side;
    Point box_scaling;

    if (method == ISOTROPIC) {
        side = std::cbrt(volume / std::sqrt(128.0));
        assert(std::fabs(getVolume() - volume) < 1e-6 && "error setting sphere volume");
    } else {
        throw std::invalid_argument("unsupported volume scaling method for the truncated-octahedral geometry");
    }
    box_scaling.setConstant(side / old_side);
    assert(fabs(getVolume() - volume) < 1e-6);
    return box_scaling;
}

Point TruncatedOctahedron::vdist(const Point &a, const Point &b) const {
    Point distance(a - b);
    boundary(distance);
    return distance;
}

bool TruncatedOctahedron::collision(const Point &a) const {
    const double sqrtThreeI = 1.0 / std::sqrt(3.0);
    const double origin_to_square_face = std::sqrt(2.0) * side;
    const double origin_to_hexagonal_face = std::sqrt(1.5) * side;

    // ugly
    if (std::fabs(a.dot(Point(1.0, 0.0, 0.0))) > origin_to_square_face)
        return true;
    if (std::fabs(a.dot(Point(0.0, 1.0, 0.0))) > origin_to_square_face)
        return true;
    if (std::fabs(a.dot(Point(0.0, 0.0, 1.0))) > origin_to_square_face)
        return true;
    if (std::fabs(a.dot(Point(1.0, 1.0, 1.0) * sqrtThreeI)) > origin_to_hexagonal_face)
        return true;
    if (std::fabs(a.dot(Point(1.0, 1.0, -1.0) * sqrtThreeI)) > origin_to_hexagonal_face)
        return true;
    if (std::fabs(a.dot(Point(1.0, -1.0, -1.0) * sqrtThreeI)) > origin_to_hexagonal_face)
        return true;
    if (std::fabs(a.dot(Point(1.0, -1.0, 1.0) * sqrtThreeI)) > origin_to_hexagonal_face)
        return true;
    return false;
}

void TruncatedOctahedron::boundary(Point &a) const {
    const double sqrtThreeI = 1.0 / std::sqrt(3.0);
    const double square_face_distance = std::sqrt(8.0) * side;
    const double hexagonal_face_distance = std::sqrt(6.0) * side;
    const Point unitvXYZ = Point(1, 1, 1) * sqrtThreeI;
    const Point unitvXiYZ = Point(1, 1, -1) * sqrtThreeI;
    const Point unitvXYiZ = Point(1, -1, -1) * sqrtThreeI;
    const Point unitvXYZi = Point(1, -1, 1) * sqrtThreeI;

    // todo improve
    bool outside = false;
    do {
        outside = false;
        double tmp = a.dot(unitvXYZ);
        if (std::fabs(tmp) > hexagonal_face_distance * 0.5) {
            a -= hexagonal_face_distance * anint(tmp / hexagonal_face_distance) * unitvXYZ;
            outside = true;
        }
        tmp = a.dot(unitvXiYZ);
        if (std::fabs(tmp) > hexagonal_face_distance * 0.5) {
            a -= hexagonal_face_distance * anint(tmp / hexagonal_face_distance) * unitvXiYZ;
            outside = true;
        }
        tmp = a.dot(unitvXYiZ);
        if (std::fabs(tmp) > hexagonal_face_distance * 0.5) {
            a -= hexagonal_face_distance * anint(tmp / hexagonal_face_distance) * unitvXYiZ;
            outside = true;
        }
        tmp = a.dot(unitvXYZi);
        if (std::fabs(tmp) > hexagonal_face_distance * 0.5) {
            a -= hexagonal_face_distance * anint(tmp / hexagonal_face_distance) * unitvXYZi;
            outside = true;
        }
    } while (outside);

    if (std::fabs(a.x()) > square_face_distance * 0.5)
        a.x() -= square_face_distance * anint(a.x() / square_face_distance);

    if (std::fabs(a.y()) > square_face_distance * 0.5)
        a.y() -= square_face_distance * anint(a.y() / square_face_distance);

    if (std::fabs(a.z()) > square_face_distance * 0.5)
        a.z() -= square_face_distance * anint(a.z() / square_face_distance);
}

void TruncatedOctahedron::randompos(Point &pos, Random &rand) const {
    const double d = std::sqrt(10.0) * side; // use circumdiameter
    const double r2 = d * d / 4.0;
    do {
        do {
            pos.x() = (rand() - 0.5) * d;
            pos.y() = (rand() - 0.5) * d;
            pos.z() = (rand() - 0.5) * d;
        } while (pos.squaredNorm() > r2);
    } while (collision(pos));
}

void TruncatedOctahedron::from_json(const json &j) { side = j.at("radius").get<double>(); }

void TruncatedOctahedron::to_json(json &j) const { j = {{"radius", side}}; }

// =============== Chameleon==============

const std::map<std::string, Variant> Chameleon::names = {{
    {"cuboid", CUBOID},
    {"cylinder", CYLINDER},
    {"slit", SLIT},
    {"sphere", SPHERE},
    {"hexagonal", HEXAGONAL},
    {"octahedron", OCTAHEDRON},
    {"hypersphere2d", HYPERSPHERE2D}
}};

void from_json(const json &j, Chameleon &g) {
    try {
        g.from_json(j);
    }
    catch (std::exception &e) {
        usageTip.pick("geometry");
        throw ConfigurationError("geometry construction error: {}", e.what());
    }
}

void to_json(json &j, const Chameleon &g) { g.to_json(j); }

ParticleVector mapParticlesOnSphere(const ParticleVector &source) {
    assert(source.size() > 1);
    Average<double> radius; // average radial distance
    ParticleVector destination = source;
    Point COM = massCenter(source.begin() + 1, source.end());
    for (size_t i = 1; i < source.size(); i++) {
        destination[i].pos = source[i].pos - COM; // make COM origin
        double r = destination[i].pos.norm();     // radial distance
        destination[i].pos /= r;                  // normalize to unit vector
        radius += r;                         // radius is the average r
    }
    destination[0].pos.setZero();
    for (auto &i : destination) // scale positions to surface of sphere
        i.pos = i.pos * radius.avg() + COM;

    // rmsd, skipping the first particle
    double _rmsd =
        rootMeanSquareDeviation(destination.begin() + 1, destination.end(), source.begin() + 1,
                                [](const Particle &a, const Particle &b) { return (a.pos - b.pos).squaredNorm(); });

    faunus_logger->info("{} particles mapped on sphere of radius {} with RMSD {} {}; the first particle ({}) is a "
                        "dummy and COM placeholder",
                        destination.size(), radius.avg(), _rmsd, u8::angstrom,
                        Faunus::atoms.at(destination.at(0).id).name);
    return destination;
}

ShapeDescriptors::ShapeDescriptors(const Tensor &gyration_tensor) {
    const auto principal_moment = Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d>(gyration_tensor).eigenvalues();
    gyration_radius_squared = gyration_tensor.trace();
    asphericity = 3.0 / 2.0 * principal_moment.z() - gyration_radius_squared / 2.0;
    acylindricity = principal_moment.y() - principal_moment.x();
    relative_shape_anisotropy = (asphericity * asphericity + 3.0 / 4.0 * acylindricity * acylindricity) /
                                (gyration_radius_squared * gyration_radius_squared);
}

ShapeDescriptors &ShapeDescriptors::operator+=(const ShapeDescriptors &other) {
    gyration_radius_squared += other.gyration_radius_squared;
    asphericity += other.asphericity;
    acylindricity += other.acylindricity;
    relative_shape_anisotropy += other.relative_shape_anisotropy;
    return *this;
}

ShapeDescriptors ShapeDescriptors::operator*(const double scale) const {
    auto scaled = *this;
    scaled.gyration_radius_squared *= scale;
    scaled.asphericity *= scale;
    scaled.acylindricity *= scale;
    scaled.relative_shape_anisotropy *= scale;
    return scaled;
}

void to_json(json &j, const ShapeDescriptors &shape) {
    j = {{"Rg = √⟨s²⟩", std::sqrt(shape.gyration_radius_squared)},
         {"asphericity (b)", shape.asphericity},
         {"acylindricity (c)", shape.acylindricity},
         {"relative shape anisotropy (𝜅²)", shape.relative_shape_anisotropy}};
}

TEST_CASE("[Faunus] ShapeDescriptors") {
    using doctest::Approx;
    std::vector<Point> positions = {{0, 0, 0}, {1, 0, 0}};
    std::vector<double> masses = {1, 1};
    Point origin = {0, 0, 0};
    auto gyration_tensor = gyration(positions.begin(), positions.end(), masses.begin(), origin);
    CHECK(gyration_tensor(0, 0) == Approx(0.5));

    auto shape = ShapeDescriptors(gyration_tensor);
    CHECK(shape.relative_shape_anisotropy == Approx(1.0));

    positions = {{0, 1, 0}, {1, 0, 0}, {-1, 0, 0}, {0, -1, 0}, {0, 1, 1}, {1, 0, 1}, {-1, 0, 1}, {0, -1, 1}};
    masses = {1, 1, 1, 1, 1, 1, 1, 1};
    gyration_tensor = gyration(positions.begin(), positions.end(), masses.begin(), origin);
    shape = ShapeDescriptors(gyration_tensor);
    CHECK(shape.relative_shape_anisotropy == Approx(0.0));
}

TEST_CASE("[Faunus] HexagonalPrismToCuboid") {
    using doctest::Approx;
    double radius = 2.0, height = 20.0;
    double side = 2.0 / std::sqrt(3.0) * radius;
    HexagonalPrism hexagonal_prism(side, height);
    ParticleVector p(6); // corners of a hexagon
    p[0].pos = {0, 1, 0};
    p[1].pos = {0.866, 0.5, 0};
    p[2].pos = {0.866, -0.5, 0};
    p[3].pos = {0, -1, 0};
    p[4].pos = {-0.866, -0.5, 0};
    p[5].pos = {-0.866, 0.5, 0};
    auto [cuboid, p_new] = HexagonalPrismToCuboid(hexagonal_prism, p);
    CHECK(p_new.size() == 12);
    CHECK(cuboid.getLength().x() == Approx(radius * 2.0));
    CHECK(cuboid.getLength().y() == Approx(side * 3.0));
    CHECK(cuboid.getLength().z() == Approx(height));
    CHECK(cuboid.getVolume() == Approx(2.0 * hexagonal_prism.getVolume()));

    std::vector<Point> positions = {{0, 1, 0},           {0.866, 0.5, 0},  {0.866, -0.5, 0},   {0, -1, 0},
                                    {-0.866, -0.5, 0},   {-0.866, 0.5, 0}, {2, -2.4641, 0},    {-1.134, -2.9641, 0},
                                    {-1.134, 2.9641, 0}, {2, 2.4641, 0},   {1.134, 2.9641, 0}, {1.134, -2.9641, 0}};
    size_t i = 0;
    for (auto &particle : p_new) { // compared actual positions w. expected positions
        CHECK(particle.pos.x() == Approx(positions[i].x()));
        CHECK(particle.pos.y() == Approx(positions[i].y()));
        CHECK(particle.pos.z() == Approx(positions[i].z()));
        i++;
    }
}

Chameleon::Chameleon(const Variant type) {
    makeGeometry(type);
    _setLength(geometry->getLength());
}

Chameleon::Chameleon(const GeometryImplementation &geo, const Variant type) : geometry(geo.clone()), _type(type) {
    _setLength(geometry->getLength());
}

Point Chameleon::getLength() const {
    assert(geometry);
    return geometry->getLength();
}

void Chameleon::setLength(const Point &l) {
    assert(geometry);
    _setLength(l);
    // ugly
    if (type == CUBOID) {
        Cuboid &cuboid = dynamic_cast<Cuboid &>(*geometry);
        cuboid.setLength(l);
    } else {
        throw std::runtime_error("setLength allowed only for the Cuboid geometry");
    }
}

void Chameleon::makeGeometry(const Variant type) {
    switch (type) {
    case CUBOID:
        geometry = std::make_unique<Cuboid>();
        break;
    case SLIT:
        geometry = std::make_unique<Slit>();
        break;
    case SPHERE:
        geometry = std::make_unique<Sphere>();
        break;
    case CYLINDER:
        geometry = std::make_unique<Cylinder>();
        break;
    case HEXAGONAL:
        geometry = std::make_unique<HexagonalPrism>();
        break;
    case OCTAHEDRON:
        geometry = std::make_unique<TruncatedOctahedron>();
        break;
    case HYPERSPHERE2D:
        geometry = std::make_unique<Hypersphere2d>();
        break;
    default:
        throw std::invalid_argument("unknown geometry");
    }
    _type = type;
}

void Chameleon::from_json(const json &j) {
    std::tie(_name, _type) = variantName(j);
    makeGeometry(_type);
    geometry->from_json(j);
    _setLength(geometry->getLength());
}

void Chameleon::to_json(json &j) const {
    assert(geometry);
    geometry->to_json(j);
    j["type"] = name;
}

void Chameleon::_setLength(const Point &l) {
    len = l;
    len_half = l * 0.5;
    len_inv = l.cwiseInverse();
    len_or_zero.setZero();

    // this is to speed up the `sqdist()` by avoiding branching when testing
    // for PBC in each direction. The variable `len_or_zero` either equals
    // `len` for PBC or zero if not.
    if (geometry->boundary_conditions.coordinates == ORTHOGONAL)
        for (size_t i = 0; i < 3; i++)
            len_or_zero[i] = len[i] * (geometry->boundary_conditions.direction[i] == PERIODIC);
}

double Chameleon::getVolume(int dim) const {
    assert(geometry);
    return geometry->getVolume(dim);
}

Point Chameleon::setVolume(double V, VolumeMethod method) {
    auto scale = geometry->setVolume(V, method);
    _setLength(geometry->getLength());
    return scale;
}

Chameleon::VariantName Chameleon::variantName(const std::string &name) {
    if (auto it = names.find(name); it == names.end()) {
        throw std::runtime_error("unknown geometry: " + name);
    } else {
        return *it;
    }
}

Chameleon::VariantName Chameleon::variantName(const json &j) { return variantName(j.at("type").get<std::string>()); }

Chameleon &Chameleon::operator=(const Chameleon &geo) {
    if (&geo != this) {
        GeometryBase::operator=(geo);
        len = geo.len;
        len_half = geo.len_half;
        len_inv = geo.len_inv;
        len_or_zero = geo.len_or_zero;
        _type = geo._type;
        _name = geo._name;
        geometry = geo.geometry != nullptr ? geo.geometry->clone() : nullptr;
    }
    return *this;
}

const BoundaryCondition &Chameleon::boundaryConditions() const { return geometry->boundary_conditions; }

Chameleon::Chameleon(const Chameleon &geo)
    : GeometryBase(geo), len(geo.len), len_half(geo.len_half), len_inv(geo.len_inv),
      geometry(geo.geometry != nullptr ? geo.geometry->clone() : nullptr), _type(geo._type), _name(geo._name) {}

std::shared_ptr<GeometryImplementation> Chameleon::asSimpleGeometry() { return geometry->clone(); }

TEST_CASE("[Faunus] spherical coordinates") {
    using doctest::Approx;

    Point sph1 = {2, 0.5, -0.3};
    auto pnt1 = rtp2xyz(sph1); // sph --> cart
    auto sph2 = xyz2rtp(pnt1); // cart --> sph

    CHECK(pnt1.norm() == Approx(2));
    CHECK(sph1.x() == Approx(sph2.x()));
    // CHECK( sph1.y() == Approx(sph2.y()));
    // CHECK( sph1.z() == Approx(sph2.z()));
}

TEST_CASE("[Faunus] Geometry") {
    using doctest::Approx;
    Random slump;

    SUBCASE("cuboid") {
        double x = 2, y = 3, z = 4;
        Cuboid geo({x, y, z});
        CHECK(geo.getVolume() == doctest::Approx(x * y * z));

        // check boundaries and pbc
        Point a(1.1, 1.5, -2.001);
        CHECK(geo.collision(a) == true);
        geo.getBoundaryFunc()(a);
        CHECK(geo.collision(a) == false);
        CHECK(a.x() == Approx(-0.9));  // x has been wrapped
        CHECK(a.y() == Approx(1.5));   // y is unchanged
        CHECK(a.z() == Approx(1.999)); // z has been wrapped
        a.y() = 1.51;                  // move y out of box
        geo.getBoundaryFunc()(a);      // wrap around boundary
        CHECK(a.y() == Approx(-1.49)); // check y-boundary
        a.y() = 1.5;                   // restore

        // check distances
        Point distance = geo.vdist({0.1, 0.5, -1.001}, a);
        CHECK(distance.x() == Approx(1.0));
        CHECK(distance.y() == Approx(-1.0));
        CHECK(distance.z() == Approx(1.0));
        CHECK(geo.vdist({1, 2, 3}, a) == geo.getDistanceFunc()({1, 2, 3}, a));

        // check that geometry is properly inscribed in a cuboid
        Point box = geo.getLength();
        CHECK(box.x() == Approx(x));
        CHECK(box.y() == Approx(y));
        CHECK(box.z() == Approx(z));

        // check random position
        Point c(x + 1, y + 1, z + 1); // out of the box
        bool container_overlap = false;
        for (int i = 0; i < 1e4; i++) {
            geo.randompos(c, slump);
            if (geo.collision(c))
                container_overlap = true;
        }
        CHECK(container_overlap == false);

        // volume scaling
        double sf = 2.;
        auto scaling = geo.setVolume(sf * sf * sf * x * y * z);
        CHECK(geo.getVolume() == doctest::Approx(sf * sf * sf * x * y * z));
        CHECK(geo.getLength().x() == Approx(sf * x));
        CHECK(geo.getLength().y() == Approx(sf * y));
        CHECK(geo.getLength().z() == Approx(sf * z));
        CHECK(scaling.x() == Approx(sf));
        CHECK(scaling.y() == Approx(sf));
        CHECK(scaling.z() == Approx(sf));

        // check json
        geo.from_json(R"( {"type": "cuboid", "length": [2.5,3.5,4.5]} )"_json);
        CHECK(geo.getVolume() == doctest::Approx(2.5 * 3.5 * 4.5));
    }

    SUBCASE("slit") {
        double x = 2, y = 4, z = 3;
        Slit geo(x, y, z);
        CHECK(geo.getVolume() == doctest::Approx(x * y * z));

        // check boundaries and pbc
        Point a(1.1, -2.001, 1.499);
        CHECK(geo.collision(a) == true);
        geo.getBoundaryFunc()(a);
        CHECK(geo.collision(a) == false);
        CHECK(a.x() == Approx(-0.9));
        CHECK(a.y() == Approx(1.999));
        CHECK(a.z() == Approx(1.499));
        Point b = a;
        geo.boundary(b);
        CHECK(a == b);
        Point c(0, 0, -0.51 * z);

        // check distances
        Point distance = geo.vdist({0.1, -1.001, -0.501}, a);
        CHECK(distance.x() == Approx(1.0));
        CHECK(distance.y() == Approx(1.0));
        CHECK(distance.z() == Approx(-2.0));
        CHECK(geo.vdist({1, 2, 3}, a) == geo.getDistanceFunc()({1, 2, 3}, a));

        // check that geometry is properly enscribed in a cuboid
        Point box = geo.getLength();
        CHECK(box.x() == Approx(x));
        CHECK(box.y() == Approx(y));
        CHECK(box.z() == Approx(z));

        // check random position
        Point d(x + 1, y + 1, z + 1); // out of the box
        bool containerOverlap = false;
        for (int i = 0; i < 1e4; i++) {
            geo.randompos(d, slump);
            if (geo.collision(d))
                containerOverlap = true;
        }
        CHECK(containerOverlap == false);

        // volume scaling
        double sf = 2.;
        auto scaling = geo.setVolume(sf * sf * x * y * z, XY);
        CHECK(geo.getVolume() == doctest::Approx(sf * sf * x * y * z));
        CHECK(geo.getLength().x() == Approx(sf * x));
        CHECK(geo.getLength().y() == Approx(sf * y));
        CHECK(geo.getLength().z() == Approx(z));
        CHECK(scaling.x() == Approx(sf));
        CHECK(scaling.y() == Approx(sf));
        CHECK(scaling.z() == Approx(1.0));

        // check json
        geo.from_json(R"( {"type": "cuboid", "length": [2.5,3.5,4.5]} )"_json);
        CHECK(geo.getVolume() == doctest::Approx(2.5 * 3.5 * 4.5));
    }

    SUBCASE("sphere") {
        double radius = 5.;
        Sphere geo(radius);
        CHECK(geo.getVolume() == doctest::Approx(4. / 3. * pc::pi * radius * radius * radius));

        // check boundaries
        CHECK(geo.collision({5.01, 0, 0}) == true);
        CHECK(geo.collision({4.99, 0, 0}) == false);
        Point a(radius - 1, 0, -0.5 * radius);
        Point b = a;
        geo.boundary(a);
        CHECK(a == b);

        // check distances
        Point distance = geo.vdist({3.0, 1.0, -2.0}, {-3.0, -1.0, 2.0});
        CHECK(distance.x() == Approx(6.0));
        CHECK(distance.y() == Approx(2.0));
        CHECK(distance.z() == Approx(-4.0));

        // check that geometry is properly enscribed in a cuboid
        Point box = geo.getLength();
        CHECK(box.x() == Approx(10));
        CHECK(box.y() == Approx(10));
        CHECK(box.z() == Approx(10));

        // check random position
        Point c(radius + 1, radius + 1, radius + 1); // out of the box
        bool container_overlap = false;
        for (int i = 0; i < 1e4; i++) {
            geo.randompos(c, slump);
            if (geo.collision(c))
                container_overlap = true;
        }
        CHECK(container_overlap == false);

        // volume scaling
        geo.setVolume(123.4);
        CHECK(geo.getVolume() == Approx(123.4));
        CHECK_THROWS_AS(geo.setVolume(100., ISOCHORIC), std::invalid_argument);
        CHECK_THROWS_AS(geo.setVolume(100., XY), std::invalid_argument);

        // check json
        geo.from_json(R"( { "type": "sphere", "radius": 2.0 } )"_json);
        CHECK(geo.getVolume() == doctest::Approx(4. / 3. * pc::pi * 2.0 * 2.0 * 2.0));
    }

    SUBCASE("cylinder") {
        double radius = 1., volume = 1.;
        double height = volume / (pc::pi * radius * radius);
        Point box;
        Cylinder geo(radius, height);

        // check boundaries
        CHECK(geo.getVolume() == Approx(volume));
        CHECK(geo.collision({-1.01 * radius, 0, 0}) == true);
        CHECK(geo.collision({0.99 * radius, 0, 0}) == false);
        CHECK(geo.collision({-0.99 * radius, 0.15 * radius, 0}) == true);
        CHECK(geo.collision({0, 0, -0.51 * height}) == true);
        CHECK(geo.collision({0, 0, 0.49 * height}) == false);

        // check that geometry is properly enscribed in a cuboid
        box = geo.getLength();
        CHECK(box.x() == Approx(2 * radius));
        CHECK(box.y() == Approx(2 * radius));
        CHECK(box.z() == Approx(height));

        // check random position
        Point a(2. * radius, 0, 0); // out of the box
        bool container_overlap = false;
        for (int i = 0; i < 1e4; i++) {
            geo.randompos(a, slump);
            if (geo.collision(a))
                container_overlap = true;
        }
        CHECK(container_overlap == false);

        // volume scaling
        geo.setVolume(9.0, XY);
        CHECK(geo.getVolume() == Approx(9.0));
        box = geo.getLength();
        CHECK(box.x() == Approx(3 * 2 * radius));
        CHECK(box.y() == Approx(3 * 2 * radius));
        CHECK(box.z() == Approx(height));

        // check json
        json j = {{"type", "cylinder"}, {"radius", 2.0}, {"length", 2 / pc::pi}};
        geo.from_json(j);
        CHECK(geo.getVolume() == doctest::Approx(8.0));
    }

    SUBCASE("hexagonal prism") {
        double side = 1., volume = 1.;
        double outer_radius = side, inner_radius = side * std::sqrt(3.0) / 2.;
        double height = volume / (3. * outer_radius * inner_radius);
        Point box;
        HexagonalPrism geo(side, height);

        CHECK(geo.getVolume() == Approx(volume));
        CHECK(geo.collision({-1.01 * inner_radius, 0, 0}) == true);
        CHECK(geo.collision({0.99 * inner_radius, 0, 0}) == false);
        CHECK(geo.collision({0.0, -1.01 * outer_radius, 0}) == true);
        CHECK(geo.collision({0.0, 0.99 * outer_radius, 0}) == false);
        CHECK(geo.collision({0.99 * std::cos(pc::pi / 3.) * inner_radius, 0.99 * std::sin(pc::pi / 3.) * inner_radius,
                             0}) == false);
        CHECK(geo.collision({1.01 * std::cos(pc::pi / 3.) * inner_radius, 1.01 * std::sin(pc::pi / 3.) * inner_radius,
                             0}) == true);
        CHECK(geo.collision({0, 0, -0.51 * height}) == true);
        CHECK(geo.collision({0, 0, 0.49 * height}) == false);

        // check that geometry is properly inscribed in a cuboid
        box = geo.getLength();
        CHECK(box.x() == Approx(2 * inner_radius));
        CHECK(box.y() == Approx(2 * outer_radius));
        CHECK(box.z() == Approx(height));

        // check random position
        Point a;
        bool container_overlap = false;
        for (int i = 0; i < 1e4; i++) {
            geo.randompos(a, slump);
            if (geo.collision(a))
                container_overlap = true;
        }
        CHECK(container_overlap == false);

        json j = {{"type", "hexagonal"}, {"radius", 3 * inner_radius}, {"length", 5 * height}};
        geo.from_json(j);
        CHECK(geo.getVolume() == Approx(9 * 5 * volume));
    }
}

TEST_CASE("[Faunus] Chameleon") {

    using doctest::Approx;
    Random slump;

    //! function compares if Chamelon's and Geometry's boundary methods produce the same result
    //! using n random points
    auto compare_boundary = [&slump](Chameleon &chameleon, GeometryImplementation &geo, Cuboid &box, int n = 100) {
        Point a, b;
        for (int i = 0; i < n; i++) {
            box.randompos(a, slump);
            b = a;
            chameleon.boundary(a);
            geo.boundary(b);
            CHECK(a.x() == Approx(b.x()));
            CHECK(a.y() == Approx(b.y()));
            CHECK(a.z() == Approx(b.z()));
        }
    };

    //! function compares if Chamelon's and Geometry's vdist methods produce the same result
    //! using n random points
    auto compare_vdist = [&slump](Chameleon &chameleon, GeometryImplementation &geo, Cuboid &box, int n = 100) {
        Point a, b, d_cham, d_geo;
        for (int i = 0; i < n; i++) {
            box.randompos(a, slump);
            box.randompos(b, slump);
            d_cham = chameleon.vdist(a, b);
            d_geo = geo.vdist(a, b);
            CHECK(d_cham.x() == Approx(d_geo.x()));
            CHECK(d_cham.y() == Approx(d_geo.y()));
            CHECK(d_cham.z() == Approx(d_geo.z()));
            CHECK(chameleon.sqdist(a, b) == Approx(d_cham.squaredNorm()));
        }
    };

    SUBCASE("cuboid") {
        double x = 2.0, y = 3.0, z = 4.0;
        Point box_size = std::cbrt(2.0) * Point(x, y, z);
        Cuboid box(box_size);
        Cuboid geo({x, y, z});
        Chameleon chameleon(geo, CUBOID);
        compare_boundary(chameleon, geo, box);
        compare_vdist(chameleon, geo, box);
    }

    SUBCASE("slit") {
        double x = 2.0, y = 3.0, z = 4.0;
        Point box_size = std::cbrt(2.0) * Point(x, y, z);
        Cuboid box(box_size);
        Slit geo(x, y, z);
        Chameleon chameleon(geo, SLIT);
        compare_boundary(chameleon, geo, box);
        compare_vdist(chameleon, geo, box);
    }

    SUBCASE("sphere") {
        double radius = 10.0;
        Point box_size;
        box_size.setConstant(std::cbrt(2.0) * 2 * radius);
        Cuboid box(box_size);
        Sphere geo(radius);
        Chameleon chameleon(geo, SPHERE);
        compare_boundary(chameleon, geo, box);
        compare_vdist(chameleon, geo, box);
    }

    SUBCASE("cylinder") {
        double radius = 2.0, height = 10.0;
        Point box_size = std::cbrt(2.0) * Point(2 * radius, 2 * radius, height);
        Cuboid box(box_size);
        Cylinder geo(radius, height);
        Chameleon chameleon(geo, CYLINDER);
        compare_boundary(chameleon, geo, box);
        compare_vdist(chameleon, geo, box);
    }

    SUBCASE("hexagonal prism") {
        double edge = 5.0, height = 20.0;
        Point box_size = std::cbrt(2.0) * Point(2 * edge, 2 * edge, height); // a bit larger in x-direction
        Cuboid box(box_size);
        HexagonalPrism geo(edge, height);
        Chameleon chameleon(geo, HEXAGONAL);
        compare_boundary(chameleon, geo, box);
        compare_vdist(chameleon, geo, box);
    }

    SUBCASE("truncated octahedron") {
        double edge = 5.0;
        Point box_size;
        box_size.setConstant(std::cbrt(2.0) * edge * std::sqrt(5.0 / 2.0)); // enlarged circumradius
        Cuboid box(box_size);
        TruncatedOctahedron geo(edge);
        Chameleon chameleon(geo, OCTAHEDRON);
        compare_boundary(chameleon, geo, box);
        compare_vdist(chameleon, geo, box);
    }

    SUBCASE("Cereal serialisation") {
        double x = 2.0, y = 3.0, z = 4.0;
        std::ostringstream os(std::stringstream::binary);
        { // write
            Cuboid geo({x, y, z});
            cereal::BinaryOutputArchive archive(os);
            archive(geo);
        }

        { // read
            Cuboid geo({10, 20, 30});
            std::istringstream in(os.str());
            cereal::BinaryInputArchive archive(in);
            archive(geo);
            CHECK(geo.getLength().x() == Approx(x));
            CHECK(geo.getLength().y() == Approx(y));
            CHECK(geo.getLength().z() == Approx(z));
        }
    }
}

TEST_CASE("[Faunus] anyCenter") {
    Chameleon cyl = json({{"type", "cuboid"}, {"length", 100}, {"radius", 20}});
    std::vector<Particle> p;

    CHECK(!atoms.empty()); // set in a previous test
    p.push_back(atoms[0]);
    p.push_back(atoms[0]);

    p.front().pos = {10, 10, -10};
    p.back().pos = {15, -10, 10};

    Point cm = Geometry::massCenter(p.begin(), p.end(), cyl.getBoundaryFunc());
    CHECK(cm.x() == doctest::Approx(12.5));
    CHECK(cm.y() == doctest::Approx(0));
    CHECK(cm.z() == doctest::Approx(0));
}

TEST_CASE("[Faunus] rootMeanSquareDeviation") {
    std::vector<double> v1 = {1.3, 4.4, -1.1};
    std::vector<double> v2 = {1.1, 4.6, -1.0};
    auto f = [](double a, double b) { return std::pow(a - b, 2); };
    double rmsd = Geometry::rootMeanSquareDeviation(v1.begin(), v1.end(), v2.begin(), f);
    CHECK(rmsd == doctest::Approx(0.17320508075688745));
}

} // namespace Faunus::Geometry


///////////////////////////////////////////////////////////////////////////////
// statistics::survival::data::constant.hpp                                  //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_SURVIVAL_CONSTANT_HPP_ER_2009
#define BOOST_STATISTICS_SURVIVAL_CONSTANT_HPP_ER_2009
#include <limits>
#include <iostream>
#include <boost/numeric/conversion/converter.hpp>
#include <boost/math/tools/precision.hpp>
#include <boost/type_traits.hpp>

namespace boost{
namespace statistics{
namespace survival{

        template<typename T>
        struct constant{
            typedef numeric::converter<T,unsigned> conv_t;
            static T zero_;
            static T eps_;
            static T one_;
            static T two_;
            static T lmin_; //exp(lmin_)>eps
            static T lmax_; //exp(lmax_)<inf
            static T inf_;
            static T quiet_nan_;
            static T sig_nan_;
        };
        template<typename T>
        T constant<T>::zero_ = conv_t::convert(0);
        template<typename T>
        T constant<T>::eps_ = boost::math::tools::epsilon<T>();
        template<typename T>
        T constant<T>::one_ = conv_t::convert(1);
        template<typename T>
        T constant<T>::two_ = conv_t::convert(2);
        template<typename T>
        T constant<T>::lmin_ = math::tools::log_min_value<T>();
        template<typename T>
        T constant<T>::lmax_ = math::tools::log_max_value<T>();
        template<typename T>
        T constant<T>::inf_ = std::numeric_limits<T>::infinity();
        template<typename T>
        T constant<T>::quiet_nan_ = std::numeric_limits<T>::quiet_NaN();
        template<typename T>
        T constant<T>::sig_nan_ = std::numeric_limits<T>::signaling_NaN();

}// survival
}// statistics
}// boost

#endif


//=======================================================================
// Copyright (c) 2014 Andrzej Pacuk
//
// 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)
//=======================================================================
/**
 * @file lsh_nearest_neighbors_regression_test.cpp
 * @brief
 * @author Andrzej Pacuk
 * @version 1.0
 * @date 2014-10-07
 */
#include "test_utils/logger.hpp"
#include "test_utils/get_test_dir.hpp"
#include "test_utils/system.hpp"

#include "paal/regression/lsh_nearest_neighbors_regression.hpp"
#include "paal/utils/functors.hpp"
#include "paal/utils/irange.hpp"
#include "paal/utils/performance_measures.hpp"
#include "paal/utils/parse_file.hpp"
#include "paal/utils/read_svm.hpp"

#include <boost/mpl/list.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/vector_sparse.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/algorithm/fill.hpp>
#include <boost/range/empty.hpp>
#include <boost/range/size.hpp>
#include <boost/test/unit_test.hpp>

#include <algorithm>
#include <cassert>
#include <cmath>
#include <fstream>
#include <istream>
#include <string>
#include <utility>
#include <vector>

namespace {

template<typename FloatType = double,
         typename TrainingResults, typename TestResults>
FloatType training_avg_const_result_log_loss(TrainingResults &&training_results,
                                          TestResults &&test_results) {
    assert(!boost::empty(training_results));
    assert(!boost::empty(test_results));

    FloatType training_avg = boost::accumulate(training_results, FloatType{}) /
        boost::size(training_results);
    assert(0.0 < training_avg);
    assert(training_avg < 1.0);

    auto avg_const_iterator = paal::irange(boost::size(test_results)) |
        boost::adaptors::transformed(
                paal::utils::make_dynamic_return_constant_functor(training_avg));
    return paal::log_loss<FloatType>(avg_const_iterator, test_results);
}

template <typename ResultType = double,
          typename PointWithResultType,
          typename HashFunctionGenerator>
bool beats_average(const std::vector<PointWithResultType> &training_points,
                   const std::vector<PointWithResultType> &test_points,
                   unsigned passes,
                   unsigned hash_funs_per_row,
                   HashFunctionGenerator hash_function_generator) {
    assert(!boost::empty(training_points));
    constexpr paal::utils::tuple_get<0> get_coordinates{};
    constexpr paal::utils::tuple_get<1> get_result{};

    using boost::adaptors::transformed;
    auto const training_points_coordinates =
        training_points | transformed(get_coordinates);
    auto const training_points_results =
        training_points | transformed(get_result);
    auto const test_points_coordinates =
        test_points | transformed(get_coordinates);
    auto const test_points_results =
        test_points | transformed(get_result);

    const int thread_nr = 2;

    auto model = paal::make_lsh_nearest_neighbors_regression_tuple_hash(
            training_points_coordinates,
            training_points_results,
            passes,
            std::move(hash_function_generator),
            hash_funs_per_row,
            thread_nr);

    std::vector<ResultType> alg_results(test_points.size());
    model.test(test_points_coordinates, alg_results.begin());

    auto alg_results_log_loss = paal::log_loss<ResultType>(alg_results,
                                                           test_points_results);
    auto benchmark = training_avg_const_result_log_loss(training_points_results,
                                                     test_points_results);
    auto benchmark_ratio = 1.0 - 1e-5;
    LOGLN("log loss: " << alg_results_log_loss << " benchmark: " << benchmark);
    return alg_results_log_loss / benchmark < benchmark_ratio;
}

template <typename ResultType = double,
          typename PointWithResultType>
bool beats_average(const std::vector<PointWithResultType> &training_points,
                   const std::vector<PointWithResultType> &test_points,
                   std::size_t dimensions) {
    for (auto passes : {100, 500, 1000}) {
        std::size_t max_hash_funs = std::ceil(std::sqrt(dimensions));
        for (auto hash_funs_per_row : paal::irange(1ul, max_hash_funs)) {
            //TODO compute w_param based on training_points
            auto w_param = 10000.0;
            LOGLN("passes: " << passes <<
                  " hash_funs_per_row: " << hash_funs_per_row <<
                  " w_param: " << w_param);

            using l_1_hash_gen = paal::lsh::l_1_hash_function_generator<>;
            using l_2_hash_gen = paal::lsh::l_2_hash_function_generator<>;
            using hamming_gen = paal::lsh::hamming_hash_function_generator;

            LOGLN("Checking l_1:");
            if (beats_average(training_points, test_points,
                              passes, hash_funs_per_row,
                              l_1_hash_gen{dimensions, w_param})) {
                return true;
            }

            LOGLN("Checking l_2:");
            if (beats_average(training_points, test_points,
                              passes, hash_funs_per_row,
                              l_2_hash_gen{dimensions, w_param})) {
                return true;
            }

            LOGLN("Checking Hamming:");
            if (beats_average(training_points, test_points,
                              passes, hash_funs_per_row,
                              hamming_gen{dimensions})) {
                return true;
            }
        }
    }
    return false;
}

} //! unnamed

BOOST_AUTO_TEST_SUITE(lsh_nearest_neighbors_regression_long)

using coordinate_t = double;
typedef boost::mpl::list<
    boost::numeric::ublas::vector<coordinate_t>,
    boost::numeric::ublas::mapped_vector<coordinate_t>,
    boost::numeric::ublas::compressed_vector<coordinate_t>,
    boost::numeric::ublas::coordinate_vector<coordinate_t>
    > test_vector_types;

BOOST_AUTO_TEST_CASE_TEMPLATE(l1_l2_regression, VectorType, test_vector_types) {
    std::string test_dir = paal::system::get_test_data_dir("LSH_REGRESSION/");
    using paal::system::build_path;

    paal::parse(build_path(test_dir, "cases.txt"),
                [&](const std::string &file_name, paal::utils::ignore_param) {
        LOGLN("TEST " << file_name);
        std::string training_file_path = build_path(test_dir, "cases/"+ file_name);
        std::ifstream training_file_stream(training_file_path);
        assert(training_file_stream.good());
        std::ifstream test_file_stream(training_file_path + ".t");
        assert(test_file_stream.good());

        auto training_points = paal::read_svm<VectorType>(training_file_stream);
        auto test_points = paal::read_svm<VectorType>(test_file_stream);

        using std::get;
        using paal::utils::tuple_get;
        auto dimensions = std::max(get<1>(training_points), get<1>(test_points));

        using paal::detail::resize_rows;
        resize_rows(get<0>(training_points), tuple_get<0>(), dimensions);
        resize_rows(get<0>(test_points), tuple_get<0>(), dimensions);

        BOOST_CHECK_MESSAGE(beats_average(get<0>(training_points),
                                          get<0>(test_points),
                                          dimensions),
                            "Failed to beat average for case:" << file_name);
    });
}

BOOST_AUTO_TEST_SUITE_END()


#include <iostream>
#include <boost/iterator/iterator_adaptor.hpp>

#include <CGAL/Simple_cartesian.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits.h>

typedef CGAL::Simple_cartesian<double> K;

// The points are stored in a flat array of doubles
// The triangles are stored in a flat array of indices
// referring to an array of coordinates: three consecutive
// coordinates represent a point, and three consecutive
// indices represent a triangle.

typedef size_t* Point_index_iterator;

// Let us now define the iterator on triangles that the tree needs:
class Triangle_iterator
    : public boost::iterator_adaptor<
    Triangle_iterator               // Derived
    , Point_index_iterator            // Base
    , boost::use_default              // Value
    , boost::forward_traversal_tag    // CategoryOrTraversal
    >
{
public:
    Triangle_iterator()
        : Triangle_iterator::iterator_adaptor_() {}

    explicit Triangle_iterator(Point_index_iterator p)
        : Triangle_iterator::iterator_adaptor_(p) {}

private:
    friend class boost::iterator_core_access;
    void increment() { this->base_reference() += 3; }
};

// The following primitive provides the conversion facilities between
// my own triangle and point types and the CGAL ones
struct My_triangle_primitive {
public:
    typedef Triangle_iterator    Id;

    // the CGAL types returned
    typedef K::Point_3    Point;
    typedef K::Triangle_3 Datum;

    // a static pointer to the vector containing the points
    // is needed to build the triangles on the fly:
    static const double* point_container;

private:
    Id m_it; // this is what the AABB tree stores internally

public:
    My_triangle_primitive() {} // default constructor needed

    // the following constructor is the one that receives the iterators from the
    // iterator range given as input to the AABB_tree
    My_triangle_primitive(Triangle_iterator a)
        : m_it(a) {}

    Id id() const { return m_it; }

    // on the fly conversion from the internal data to the CGAL types
    Datum datum() const
    {
        Point_index_iterator p_it = m_it.base();
        Point p(*(point_container + 3 * (*p_it)),
                *(point_container + 3 * (*p_it) + 1),
                *(point_container + 3 * (*p_it) + 2) );
        ++p_it;
        Point q(*(point_container + 3 * (*p_it)),
                *(point_container + 3 * (*p_it) + 1),
                *(point_container + 3 * (*p_it) + 2));
        ++p_it;
        Point r(*(point_container + 3 * (*p_it)),
                *(point_container + 3 * (*p_it) + 1),
                *(point_container + 3 * (*p_it) + 2));

        return Datum(p, q, r); // assembles triangle from three points
    }

    // one point which must be on the primitive
    Point reference_point() const
    {
      return Point(*(point_container + 3 * (*m_it)),
                   *(point_container + 3 * (*m_it) + 1),
                   *(point_container + 3 * (*m_it) + 2));
    }
};

// types
typedef CGAL::AABB_traits<K, My_triangle_primitive> My_AABB_traits;
typedef CGAL::AABB_tree<My_AABB_traits> Tree;
const double* My_triangle_primitive::point_container = 0;

int main()
{
    // generates point set
    double points[12];
    My_triangle_primitive::point_container = points;
    points[0] = 1.0; points[1] = 0.0; points[2] = 0.0;
    points[3] = 0.0; points[4] = 1.0; points[5] = 0.0;
    points[6] = 0.0; points[7] = 0.0; points[8] = 1.0;
    points[9] = 0.0; points[10] = 0.0; points[11] = 0.0;

    // generates indexed triangle set
    size_t triangles[9];
    triangles[0] = 0; triangles[1] = 1; triangles[2] = 2;
    triangles[3] = 0; triangles[4] = 1; triangles[5] = 3;
    triangles[6] = 0; triangles[7] = 3; triangles[8] = 2;

    // constructs AABB tree
    Tree tree(Triangle_iterator(triangles),
        Triangle_iterator(triangles+9));

    // counts #intersections
    K::Ray_3 ray_query(K::Point_3(0.2, 0.2, 0.2), K::Point_3(0.0, 1.0, 0.0));
    std::cout << tree.number_of_intersected_primitives(ray_query)
        << " intersections(s) with ray query" << std::endl;

    // computes closest point
    K::Point_3 point_query(2.0, 2.0, 2.0);
    K::Point_3 closest_point = tree.closest_point(point_query);
    std::cout << "closest point to " << point_query << " is: " << closest_point.x() << " " << closest_point.y() << " " << closest_point.z() << std::endl;

    return EXIT_SUCCESS;
}