#include <ceres/jet.h>
#include <ceres/rotation.h>
#include <geometry/camera.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <Eigen/Dense>
#include <random>
#include <unsupported/Eigen/AutoDiff>

class FunctionFixture : public ::testing::Test {
 public:
  /* Evaluates the following function :
   *
   * f = a * exp( b - c * log( d * (e/x - f)^2 ) )
   *
   * Decomposed as follows :
   *
   * f1 = a * exp(x)
   * f2 = b - c * log(x)
   * f3 = d * x^2
   * f4 = e/x - f
   * f = f1(f2(f3(f4))) */

  struct F1 : public geometry::Functor<1, 1, 1> {
    template <class T>
    static void Apply(const T* in, const T* p, T* out) {
      out[0] = p[0] * exp(in[0]);
    }
    template <class T, bool COMP_PARAM>
    static void ForwardDerivatives(const T* in, const T* p, T* out,
                                   T* jacobian) {
      jacobian[0] = p[0] * exp(in[0]);
      if (COMP_PARAM) {
        jacobian[1] = exp(in[0]);
      }
      Apply(in, p, out);
    }
  };
  struct F2 : public geometry::Functor<1, 2, 1> {
    template <class T>
    static void Apply(const T* in, const T* p, T* out) {
      out[0] = p[0] - p[1] * log(in[0]);
    }
    template <class T, bool COMP_PARAM>
    static void ForwardDerivatives(const T* in, const T* p, T* out,
                                   T* jacobian) {
      jacobian[0] = -p[1] / in[0];
      if (COMP_PARAM) {
        jacobian[1] = T(1.0);
        jacobian[2] = -log(in[0]);
      }
      Apply(in, p, out);
    }
  };
  struct F3 : public geometry::Functor<1, 1, 1> {
    template <class T>
    static void Apply(const T* in, const T* p, T* out) {
      out[0] = p[0] * in[0] * in[0];
    }
    template <class T, bool COMP_PARAM>
    static void ForwardDerivatives(const T* in, const T* p, T* out,
                                   T* jacobian) {
      jacobian[0] = T(2.0) * p[0] * in[0];
      if (COMP_PARAM) {
        jacobian[1] = in[0] * in[0];
      }
      Apply(in, p, out);
    }
  };
  struct F4 : public geometry::Functor<1, 2, 1> {
    template <class T>
    static void Apply(const T* in, const T* p, T* out) {
      out[0] = p[0] / in[0] - p[1];
    }
    template <class T, bool COMP_PARAM>
    static void ForwardDerivatives(const T* in, const T* p, T* out,
                                   T* jacobian) {
      jacobian[0] = -p[0] / (in[0] * in[0]);
      if (COMP_PARAM) {
        jacobian[1] = T(1.0) / in[0];
        jacobian[2] = -T(1.0);
      }
      Apply(in, p, out);
    }
  };
};

TEST_F(FunctionFixture, EvaluatesCorrectly) {
  double in[] = {1.0};

  /* Parameters needs to be stored in the order of evaluation :
   * params f4 |params f3 | params f2 | params f1 */
  double parameters[] = {4.0, 2.0, 3.0, 1.0, 2.0, 3.0};
  double evaluated = 0;
  geometry::ComposeFunctions<double, F1, F2, F3, F4>(in, parameters,
                                                     &evaluated);

  const double expected =
      3.0 * exp(1.0 - 2.0 * log(3.0 * std::pow(4.0 / in[0] - 2.0, 2)));
  ASSERT_NEAR(expected, evaluated, 1e-20);
}

TEST_F(FunctionFixture, EvaluatesDerivativesCorrectly) {
  double in[] = {1.0};

  /* Parameters needs to be stored in the order of evaluation :
   * params f4 |params f3 | params f2 | params f1 */
  double parameters[] = {4.0, 2.0, 3.0, 1.0, 2.0, 3.0};
  double evaluated = 0;
  double jacobian[7];
  geometry::ComposeForwardDerivatives<double, true, F1, F2, F3, F4>(
      in, parameters, &evaluated, &jacobian[0]);

  const double expected =
      3.0 * exp(1.0 - 2.0 * log(3.0 * std::pow(4.0 / in[0] - 2.0, 2)));
  ASSERT_NEAR(expected, evaluated, 1e-20);

  /* Jacobian ordering : d_input | d_parameters */
  typedef Eigen::AutoDiffScalar<VecXd> AScalar;
  VecX<AScalar> eval_adiff(1);
  eval_adiff(0).value() = in[0];
  eval_adiff(0).derivatives() = VecXd::Unit(7, 0);
  VecX<AScalar> parameters_adiff(6);
  for (int i = 0; i < 6; ++i) {
    parameters_adiff[i].value() = parameters[i];
    parameters_adiff[i].derivatives() = VecXd::Unit(7, i + 1);
  }

  AScalar evaluated_addif;
  geometry::ComposeFunctions<AScalar, F1, F2, F3, F4>(
      eval_adiff.data(), parameters_adiff.data(), &evaluated_addif);
  for (int i = 0; i < 7; ++i) {
    ASSERT_NEAR(evaluated_addif.derivatives()(i), jacobian[i], 1e-20);
  }
}

class PoseFixture : public ::testing::Test {
 public:
  const double point[3] = {1.0, 2.0, 3.0};
  const double rt[6] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6};
};

TEST_F(PoseFixture, EvaluatesCorrectly) {
  double transformed[3] = {0., 0., 0.};

  /* Parameters order : angle_axis | pose_center */
  geometry::PoseFunctor::Forward(point, rt, transformed);

  /* Use Ceres as groundtruth */
  const double pt[3] = {
      point[0] - rt[geometry::PoseFunctor::Tx],
      point[1] - rt[geometry::PoseFunctor::Ty],
      point[2] - rt[geometry::PoseFunctor::Tz],
  };
  const double Rt[3] = {-rt[geometry::PoseFunctor::Rx],
                        -rt[geometry::PoseFunctor::Ry],
                        -rt[geometry::PoseFunctor::Rz]};
  double expected[] = {1., 1., 1.};
  ceres::AngleAxisRotatePoint(Rt, pt, expected);

  for (int i = 0; i < 3; ++i) {
    ASSERT_NEAR(expected[i], transformed[i], 1e-15);
  }
}

TEST_F(PoseFixture, EvaluatesAllDerivativesCorrectly) {
  typedef Eigen::AutoDiffScalar<VecXd> AScalar;

  VecX<AScalar> point_adiff(3);
  constexpr int size = 9;

  /* Autodifferentaied as a reference */
  for (int i = 0; i < 3; ++i) {
    point_adiff(i).value() = point[i];
    point_adiff(i).derivatives() = VecXd::Unit(size, i);
  }
  VecX<AScalar> rt_adiff(6);
  for (int i = 0; i < 6; ++i) {
    rt_adiff[i].value() = rt[i];
    rt_adiff[i].derivatives() = VecXd::Unit(size, 3 + i);
  }
  VecX<AScalar> expected_adiff(3);
  geometry::PoseFunctor::Forward(point_adiff.data(), rt_adiff.data(),
                                 expected_adiff.data());

  /* Analytic version */
  double transformed[] = {0., 0., 0.};
  double jacobian[size * 3];
  geometry::PoseFunctor::ForwardDerivatives<double, true>(
      &point[0], &rt[0], &transformed[0], &jacobian[0]);

  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < size; ++j) {
      ASSERT_NEAR(expected_adiff(i).derivatives()(j), jacobian[i * size + j],
                  1e-15);
    }
  }
}

TEST_F(PoseFixture, EvaluatesPointDerivativesCorrectly) {
  typedef Eigen::AutoDiffScalar<VecXd> AScalar;

  VecX<AScalar> point_adiff(3);

  /* Autodifferentaied as a reference */
  for (int i = 0; i < 3; ++i) {
    point_adiff(i).value() = point[i];
    point_adiff(i).derivatives() = VecXd::Unit(3, i);
  }
  VecX<AScalar> rt_adiff(6);
  for (int i = 0; i < 6; ++i) {
    rt_adiff[i].value() = rt[i];
    rt_adiff[i].derivatives() = Vec3d::Zero();
  }
  VecX<AScalar> expected_adiff(3);
  geometry::PoseFunctor::Forward(point_adiff.data(), rt_adiff.data(),
                                 expected_adiff.data());

  /* Analytic version */
  double transformed[] = {0., 0., 0.};
  double jacobian[9];
  geometry::PoseFunctor::ForwardDerivatives<double, false>(
      &point[0], &rt[0], &transformed[0], &jacobian[0]);

  for (int i = 0; i < 3; ++i) {
    for (int j = 0; j < 3; ++j) {
      ASSERT_NEAR(expected_adiff(i).derivatives()(j), jacobian[i * 3 + j],
                  1e-15);
    }
  }
}

class CameraDerivativesFixture : public ::testing::Test {
 public:
  CameraDerivativesFixture() {
    distortion.resize(2);
    distortion << -0.1, 0.03;
    distortion_brown.resize(5);
    distortion_brown << -0.1, 0.03, 0.001, 0.001, 0.002;
    distortion_fisheye.resize(4);
    distortion_fisheye << -0.1, 0.03, 0.001, 0.005;
    distortion_fisheye62.resize(8);
    distortion_fisheye62 << -0.1, 0.03, 0.001, 0.005, 0.02, 0.001, 0.0007,
        -0.01;
    distortion_radial << 0.1, 0.03;
    principal_point << 0.1, -0.05;

    for (int i = 0; i < 3; ++i) {
      point_adiff[i].value() = point[i] = (i + 1) / 10.0;
    }
  }

  template <class MAT>
  void RunJacobianEval(const geometry::Camera& camera,
                       geometry::ProjectionType projection, MAT* jacobian) {
    const VecXd camera_params = camera.GetParametersValues();
    const int size_params = 3 + camera_params.size();

    // Prepare Eigen's Autodiff structures
    for (int i = 0; i < 3; ++i) {
      point_adiff[i].derivatives() = VecXd::Unit(size_params, i);
    }
    camera_adiff.resize(camera_params.size());
    for (int i = 0; i < camera_params.size(); ++i) {
      camera_adiff(i).value() = camera_params(i);
      camera_adiff(i).derivatives() = VecXd::Unit(size_params, 3 + i);
    }

    // Run project with Autodiff types to get expected jacobian
    geometry::Dispatch<geometry::ProjectFunction>(
        projection, point_adiff, camera_adiff.data(), projection_expected);

    // Analytical derivatives
    geometry::Dispatch<geometry::ProjectDerivativesFunction>(
        projection, point, camera_params.data(), projected, jacobian->data());
  }

  template <class MAT>
  void CheckJacobian(const MAT& jacobian, int size_params) {
    const double eps = 1e-12;
    for (int i = 0; i < 2; ++i) {
      for (int j = 0; j < size_params; ++j) {
        ASSERT_NEAR(projection_expected[i].derivatives()(j), jacobian(i, j),
                    eps);
      }
      ASSERT_NEAR(projection_expected[i].value(), projected[i], eps);
    }
  }

  const double focal{0.4};
  const double new_ar{0.9};

  VecXd distortion;
  VecXd distortion_brown;
  VecXd distortion_fisheye;
  VecXd distortion_fisheye62;
  Vec2d distortion_radial;
  Vec2d principal_point;

  double point[3];
  typedef Eigen::AutoDiffScalar<VecXd> AScalar;
  AScalar point_adiff[3];
  VecX<AScalar> camera_adiff;

  AScalar projection_expected[2];
  double projected[2];
};

TEST_F(CameraDerivativesFixture, ComputePerspectiveAnalyticalDerivatives) {
  const geometry::Camera camera =
      geometry::Camera::CreatePerspectiveCamera(focal, -0.1, 0.01);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 6, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::PERSPECTIVE, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeFisheyeAnalyticalDerivatives) {
  const geometry::Camera camera =
      geometry::Camera::CreateFisheyeCamera(focal, -0.1, 0.01);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 6, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::FISHEYE, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeFisheyeOpencvAnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::CreateFisheyeOpencvCamera(
      focal, new_ar, principal_point, distortion_fisheye);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 11, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::FISHEYE_OPENCV, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeFisheye62AnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::CreateFisheye62Camera(
      focal, 1.0, principal_point, distortion_fisheye62);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 15, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::FISHEYE62, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeRadialAnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::CreateRadialCamera(
      focal, 1.0, principal_point, distortion_radial);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 9, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::RADIAL, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeSimpleRadialAnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::CreateSimpleRadialCamera(
      focal, 1.0, principal_point, distortion_radial[0]);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 8, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::SIMPLE_RADIAL, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeBrownAnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::CreateBrownCamera(
      focal, new_ar, principal_point, distortion_brown);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 12, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::BROWN, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeSphericalAnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::CreateSphericalCamera();

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3;

  Eigen::Matrix<double, 2, 3, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::SPHERICAL, &jacobian);
  CheckJacobian(jacobian, size_params);
}

TEST_F(CameraDerivativesFixture, ComputeDualAnalyticalDerivatives) {
  const geometry::Camera camera = geometry::Camera::Camera::CreateDualCamera(
      0.5, focal, distortion[0], distortion[1]);

  const VecXd camera_params = camera.GetParametersValues();
  const int size_params = 3 + camera_params.size();

  Eigen::Matrix<double, 2, 7, Eigen::RowMajor> jacobian;
  RunJacobianEval(camera, geometry::ProjectionType::DUAL, &jacobian);
  CheckJacobian(jacobian, size_params);
}


//=======================================================================
// Copyright (c)
//
// 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 generalised_assignment_long_test.cpp
 * @brief
 * @author Piotr Wygocki
 * @version 1.0
 * @date 2013-02-15
 */

#include "test_utils/logger.hpp"
#include "test_utils/read_gen_ass.hpp"
#include "test_utils/get_test_dir.hpp"
#include "test_utils/system.hpp"

#include "paal/data_structures/components/components_replace.hpp"
#include "paal/data_structures/metric/basic_metrics.hpp"
#include "paal/iterative_rounding/generalised_assignment/generalised_assignment.hpp"
#include "paal/utils/assign_updates.hpp"
#include "paal/utils/parse_file.hpp"

#include <boost/test/unit_test.hpp>

#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>

using namespace paal::ir;
using namespace paal;

template <typename Machines, typename JobsToMachines, typename Times,
        typename MachineBounds, typename Jobs>
void check_result(IRResult result, const Machines & machines,
        const JobsToMachines & jobs_to_machines, const Times & times,
        const MachineBounds & machines_bounds, const Jobs & jobs, int opt) {

    BOOST_CHECK(result.first == lp::OPTIMAL);

    std::vector<int> machines_load(machines.size(), 0);
    for (auto jm : jobs_to_machines) {
        LOGLN("job " << jm.first << " assigned to machine " << jm.second);
        machines_load[jm.second] += times(jm.first, jm.second);
    }
    double approximation_ratio = 1.;
    for (int m : machines) {
        BOOST_CHECK(machines_load[m] <= 2 * machines_bounds[m]);
        assign_max(approximation_ratio, double(machines_load[m]) / double(machines_bounds[m]));
    }

    for (int j : jobs) {
        BOOST_CHECK(jobs_to_machines.find(j) != jobs_to_machines.end());
    }
    BOOST_CHECK(std::abs(std::round(*(result.second))-*(result.second)) < 1e-6);
    int c = std::round(*(result.second));
    LOGLN("cost " << c);
    BOOST_CHECK(c <= opt);
    LOGLN(std::setprecision(10) << "APPROXIMATION RATIO: " << approximation_ratio << " cost / opt = " << double(c) / double(opt));
}

BOOST_AUTO_TEST_CASE(generalised_assignment_long) {
    std::string test_dir = paal::system::get_test_data_dir("GENERALISED_ASSIGNMENT");
    using paal::system::build_path;

    parse(build_path(test_dir, "gapopt.txt"), [&](const std::string & fname, std::istream & is_test_cases) {
        int opt;
        int number_of_cases;

        is_test_cases >> number_of_cases;

        LOGLN(fname << " " << number_of_cases);
        std::ifstream ifs(build_path(test_dir, "/cases/" + fname + ".txt"));
        assert(ifs.good());

        int num;
        ifs >> num;
        assert(num == number_of_cases);
        for(int i = 0; i < number_of_cases; ++i) {
            is_test_cases >> opt;
            LOGLN("case " << i << " opt " << opt);

            paal::M costs;
            paal::M times;
            std::vector<int> machines_bounds;
            boost::integer_range<int> machines(0,0);
            boost::integer_range<int> jobs(0,0);
            paal::read_gen_ass(ifs, costs, times, machines_bounds, machines, jobs);
            auto Tf = [&](int i){return machines_bounds[i];};
            {
                LOGLN("Unlimited relaxations");
                std::unordered_map<int, int> jobs_to_machines;
                auto result = generalised_assignment_iterative_rounding(
                    machines.begin(), machines.end(),
                    jobs.begin(), jobs.end(),
                    costs, times, Tf, std::inserter(jobs_to_machines, jobs_to_machines.begin()));
                check_result(result, machines, jobs_to_machines, times, machines_bounds, jobs, opt);
            }
            {
                LOGLN("Relaxations limit = 1/iter");
                std::unordered_map<int, int> jobs_to_machines;
                ir::ga_ir_components<> comps;
                auto components =
                    paal::data_structures::replace<ir::RelaxationsLimit>(
                        ir::relaxations_limit_condition(), comps);
                auto result = generalised_assignment_iterative_rounding(
                    machines.begin(), machines.end(),
                    jobs.begin(), jobs.end(),
                    costs, times, Tf, std::inserter(jobs_to_machines, jobs_to_machines.begin()),
                    components);
                check_result(result, machines, jobs_to_machines, times, machines_bounds, jobs, opt);
            }
        }
        int MAX_LINE = 256;
        char buf[MAX_LINE];
        is_test_cases.getline(buf, MAX_LINE);
    });
}


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

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

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

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <geometry_test_common.hpp>

#include <boost/concept_check.hpp>

#include <boost/geometry/extensions/gis/geographic/strategies/andoyer.hpp>

#include <boost/geometry/strategies/strategies.hpp>
#include <boost/geometry/algorithms/assign.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <test_common/test_point.hpp>

#ifdef HAVE_TTMATH
#  include <boost/geometry/extensions/contrib/ttmath_stub.hpp>
#endif

template <typename P1, typename P2>
void test_andoyer(double lon1, double lat1, double lon2, double lat2, double expected_km)
{
    // Set radius type, but for integer coordinates we want to have floating point radius type
    typedef typename bg::promote_floating_point
        <
            typename bg::coordinate_type<P1>::type
        >::type rtype;

    typedef bg::strategy::distance::andoyer<rtype> andoyer_type;

    BOOST_CONCEPT_ASSERT
        (
            (bg::concept::PointDistanceStrategy<andoyer_type, P1, P2>)
        );

    andoyer_type andoyer;
    typedef typename bg::strategy::distance
        ::services::return_type<andoyer_type, P1, P2>::type return_type;

    P1 p1, p2;

    bg::assign_values(p1, lon1, lat1);
    bg::assign_values(p2, lon2, lat2);

    BOOST_CHECK_CLOSE(andoyer.apply(p1, p2), return_type(1000.0 * expected_km), 0.001);
}

template <typename P1, typename P2>
void test_all()
{
    test_andoyer<P1, P2>(0, 90, 1, 80, 1116.814237); // polar
    test_andoyer<P1, P2>(4, 52, 4, 52, 0.0); // no point difference
    test_andoyer<P1, P2>(4, 52, 3, 40, 1336.039890); // normal case

    /* SQL Server gives:
        1116.82586908528, 0, 1336.02721932545

       with:
SELECT 0.001 * geography::STGeomFromText('POINT(0 90)', 4326).STDistance(geography::STGeomFromText('POINT(1 80)', 4326))
union SELECT 0.001 * geography::STGeomFromText('POINT(4 52)', 4326).STDistance(geography::STGeomFromText('POINT(4 52)', 4326))
union SELECT 0.001 * geography::STGeomFromText('POINT(4 52)', 4326).STDistance(geography::STGeomFromText('POINT(3 40)', 4326))
     */
}

template <typename P>
void test_all()
{
    test_all<P, P>();
}

int test_main(int, char* [])
{
    //test_all<float[2]>();
    //test_all<double[2]>();
    test_all<bg::model::point<int, 2, bg::cs::geographic<bg::degree> > >();
    test_all<bg::model::point<float, 2, bg::cs::geographic<bg::degree> > >();
    test_all<bg::model::point<double, 2, bg::cs::geographic<bg::degree> > >();

#if defined(HAVE_TTMATH)
    test_all<bg::model::point<ttmath::Big<1,4>, 2, bg::cs::geographic<bg::degree> > >();
    test_all<bg::model::point<ttmath_big, 2, bg::cs::geographic<bg::degree> > >();
#endif

    return 0;
}


#include <random>
#include <boost/range/irange.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/range/numeric.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm/for_each.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include "kde.hpp"

using std::vector;
using boost::irange;
using boost::lambda::_1;
using boost::lambda::_2;
using std::mt19937;
using std::normal_distribution;

double sample_from_normal(
    double mu = 0.0, /**< The mean of the distribution.*/
    double sd = 1.0  /**< The standard deviation of the distribution.*/
) {
  mt19937 gen = RNG::rng()->get_RNG();
  normal_distribution<> d{mu, sd};
  return d(gen);
}

KDE::KDE(std::vector<double> v) : dataset(v) {
    using boost::adaptors::transformed;
    using boost::lambda::_1;
    using utils::mean;

    // Compute the bandwidth using Silverman's rule
    mu = mean(v);
    auto X = v | transformed(_1 - mu);

    // Compute standard deviation of the sample.
    size_t N = v.size();
    double stdev = sqrt(inner_product(X, X, 0.0) / (N - 1));
    bw = pow(4 * pow(stdev, 5) / (3 * N), 1 / 5);
  }

vector<double> KDE::resample(int n_samples) {
  vector<double> samples;
  for (int i : irange(0, n_samples)) {
    double element = select_random_element(dataset);
    samples.push_back(sample_from_normal(element, bw));
  }
  return samples;
}

double KDE::pdf(double x) {
  using utils::sqr;
  double p = 0.0;
  size_t N = dataset.size();
  for (double elem : dataset) {
    double x1 = exp(-sqr(x - elem) / (2 * sqr(bw)));
    x1 /= N * bw * sqrt(2 * M_PI);
    p += x1;
  }
  return p;
}

vector<double> KDE::pdf(vector<double> v) {
  vector<double> values;
  for (double elem : v) {
    values.push_back(pdf(elem));
  }
  return values;
}

double KDE::logpdf(double x) { return log(pdf(x)); }


/* Cam Mannett 2020
 *
 * See LICENSE file
 */

#include "malbolge/math/ipow.hpp"
#include "malbolge/traits.hpp"

#include "test_helpers.hpp"

#include <boost/mp11.hpp>

#include <utility>

using namespace malbolge;

BOOST_AUTO_TEST_SUITE(ipow_suite)

BOOST_AUTO_TEST_CASE(squares)
{
    using data_set = std::tuple<
        std::pair<traits::integral_constant<0u>,  traits::integral_constant<1u>>,
        std::pair<traits::integral_constant<1u>,  traits::integral_constant<2u>>,
        std::pair<traits::integral_constant<2u>,  traits::integral_constant<4u>>,
        std::pair<traits::integral_constant<3u>,  traits::integral_constant<8u>>,
        std::pair<traits::integral_constant<4u>,  traits::integral_constant<16u>>,
        std::pair<traits::integral_constant<10u>, traits::integral_constant<1024u>>
    >;

    auto f = [](auto expo, auto result) {
        constexpr auto r = math::ipow<std::uint32_t, 2u, decltype(expo)::value>();
        BOOST_CHECK_EQUAL(r, decltype(result)::value);
    };

    test::data_set(f, data_set{});
}

BOOST_AUTO_TEST_CASE(cubes)
{
    using data_set = std::tuple<
        std::pair<traits::integral_constant<0u>,  traits::integral_constant<1u>>,
        std::pair<traits::integral_constant<1u>,  traits::integral_constant<3u>>,
        std::pair<traits::integral_constant<2u>,  traits::integral_constant<9u>>,
        std::pair<traits::integral_constant<3u>,  traits::integral_constant<27u>>,
        std::pair<traits::integral_constant<4u>,  traits::integral_constant<81u>>,
        std::pair<traits::integral_constant<10u>, traits::integral_constant<59049u>>
    >;

    auto f = [](auto expo, auto result) {
        constexpr auto r = math::ipow<std::uint32_t, 3u, decltype(expo)::value>();
        BOOST_CHECK_EQUAL(r, decltype(result)::value);
    };

    test::data_set(f, data_set{});
}

BOOST_AUTO_TEST_CASE(cubes_runtime)
{
    auto f = [](auto expo, auto result) {
        const auto r = math::ipow<std::uint32_t>(3u, expo);
        BOOST_CHECK_EQUAL(r, result);
    };

    test::data_set(
        f,
        {
            std::tuple{0u, 1u},
            std::tuple{1u, 3u},
            std::tuple{2u, 9u},
            std::tuple{3u, 27u},
            std::tuple{4u, 81u},
            std::tuple{10u, 59049u},
        }
    );
}

BOOST_AUTO_TEST_SUITE_END()


/*
 *  Explicit to implicit reconstruction
 *  Date : 20 Feb 2020
 *  Author : Sachin Krishnan T V (sachu92@gmail.com)
 */

#include <iostream>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <cstring>
#include "reconstruct.hpp"
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

int main(int argc, char *argv[])
{
  // Input: point cloud filename
  // Input: mesh size, box boundaries

  // Output: the implicit surface representation in a VTP file. (mesh points with SDFs)

  if(argc < 9)
  {
    cout<<"Usage: <pointcloud_file> <mesh_size> <low_x> <high_x> <low_y> <high_y> <low_z> <high_z>";
    return 0;
  }

  cout<<endl;
  cout<<"Reading the input file...";
  // Read input file
  readPointCloud(argv[1]);

  cout<<endl<<"Reading parameters...";
  // Set parameters for the output mesh
  mesh_size = atoi(argv[2]);
  xlo = atof(argv[3]);
  xhi = atof(argv[4]);
  ylo = atof(argv[5]);
  yhi = atof(argv[6]);
  zlo = atof(argv[7]);
  zhi = atof(argv[8]);

  xbinsize = (xhi - xlo) / mesh_size;
  ybinsize = (yhi - ylo) / mesh_size;
  zbinsize = (zhi - zlo) / mesh_size;

  cout<<endl<<"Evaluating the SDF...";
  // Evaluate the signed distance functions
  evaluateSDF();

  cout<<endl<<"Reconstructing mesh...";
  reconstructMesh();
  cout<<endl<<"Writing mesh structure to file...";
  outputMesh();
  cout<<endl<<"Done."<<endl;

  return 0;
}

void readPointCloud(char *filename)
{
  int i;
  int nump = 0;
  bool done_flag = false;
  string line;
  string temp;
  ifstream infile;
  stringstream iss;

  infile.open(filename, ios::in);

  /* Reading the header */
  while(!infile.eof() && !done_flag)
  {
    infile>>line;
    if(strcmp(line.c_str(),"<Piece")==0)
    {
      infile>>line;
      iss.str(line);
      getline(iss,temp,'\"');
      getline(iss,temp,'\"');
      nump = atoi(temp.c_str());
      pc_coord.resize(nump, std::vector<double>(3, 0.0));
      pc_norm.resize(nump, std::vector<double>(3, 0.0));
    }
    else if(strcmp(line.c_str(),"<Points>")==0)
    {
      double rx, ry, rz;
      for(i = 0;i < 5;i++)
        infile>>line;
      for(i = 0;i < nump;i++)
      {
        infile>>rx>>ry>>rz;
        pc_coord[i][0] = rx;
        pc_coord[i][1] = ry;
        pc_coord[i][2] = rz;
      }
    }
    else if(strcmp(line.c_str(), "Name=\"Normals\"")==0)
    {
      double nx, ny, nz;
      // Ignore next string
      infile>>line>>line;
      for(i = 0;i < nump;i++)
      {
        infile>>nx>>ny>>nz;
        pc_norm[i][0] = nx;
        pc_norm[i][1] = ny;
        pc_norm[i][2] = nz;
      }
      done_flag = true;
    }
  }
  infile.close();
  return;
}

double distance(double x1, double y1, double z1, double x2, double y2, double z2)
{
  double d;
  d = sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2) + (z1 - z2)*(z1 - z2));
  return d;
}

double triharmonic_kernel(double x)
{
  return x*x*x;
}

// Calculate the weights
void evaluateSDF()
{
  int i, j;
  int nump = pc_coord.size();
  double x1, y1, z1, x2, y2, z2;

  Matrix<double, Dynamic, Dynamic> K;
  Matrix<double, Dynamic, 1> d;

  K.resize(2*nump, 2*nump);
  d.resize(2*nump, NoChange);

  for(i = 0;i < 2*nump;i++)
  {
    if(i < nump)
    {
      x1 = pc_coord[i][0];
      y1 = pc_coord[i][1];
      z1 = pc_coord[i][2];
      d(i) = 0.0;
    }
    else
    {
      x1 = pc_coord[i-nump][0] + EPS*pc_norm[i-nump][0];
      y1 = pc_coord[i-nump][1] + EPS*pc_norm[i-nump][1];
      z1 = pc_coord[i-nump][2] + EPS*pc_norm[i-nump][2];
      d(i) = EPS;
    }
    for(j = 0;j < 2*nump;j++)
    {
      if(j < nump)
      {
        x2 = pc_coord[j][0];
        y2 = pc_coord[j][1];
        z2 = pc_coord[j][2];
      }
      else
      {
        x2 = pc_coord[j-nump][0] + EPS*pc_norm[j-nump][0];
        y2 = pc_coord[j-nump][1] + EPS*pc_norm[j-nump][1];
        z2 = pc_coord[j-nump][2] + EPS*pc_norm[j-nump][2];
      }
      K(i, j) = triharmonic_kernel(distance(x1, y1, z1, x2, y2, z2));
    }
  }

  Matrix<double, Dynamic, 1> w = K.inverse()*d;
  w.resize(2*nump, NoChange);

  rbf_weight.resize(2*nump);
  for(i = 0;i < 2*nump;i++)
  {
    rbf_weight[i] = w(i);
  }
  return;
}

// Reconstruct the mesh based on the calculated weights
void reconstructMesh()
{
  int i, j, k, l;
  int nump = pc_coord.size();
  long index;
  double mx, my, mz, mphi;

  mesh_ls.resize(mesh_size*mesh_size*mesh_size, 0);

  for(i = 0;i < mesh_size;i++)
  {
    mx = xlo + xbinsize*i;
    for(j = 0;j < mesh_size;j++)
    {
      my = ylo + ybinsize*j;
      for(k = 0;k < mesh_size;k++)
      {
        mz = zlo + zbinsize*k;

        mphi = 0.0;
        for(l = 0;l < nump;l++)
        {
          mphi += rbf_weight[l]*triharmonic_kernel(distance(mx, my, mz, pc_coord[l][0],
                                                            pc_coord[l][1], pc_coord[l][2]));
        }
        for(l = 0;l < nump;l++)
        {
          mphi += rbf_weight[l+nump]*triharmonic_kernel(distance(mx, my, mz,
                                                                 pc_coord[l][0] + EPS*pc_norm[l][0],
                                                                 pc_coord[l][1] + EPS*pc_norm[l][1],
                                                                 pc_coord[l][2] + EPS*pc_norm[l][2]));
        }
        index = k*mesh_size*mesh_size + j*mesh_size + i;
        mesh_ls[index] = mphi;
      }
    }
  }
  return;
}

// Write output file in VTK format
void outputMesh()
{
  int i;
  ofstream outfile;

  outfile.open("output.vtk", ios::out);
  outfile<<"# vtk DataFile Version 2.0"<<endl;
  outfile<<"Level set data"<<endl;
  outfile<<"ASCII"<<endl;
  outfile<<"DATASET RECTILINEAR_GRID"<<endl;
  outfile<<"DIMENSIONS "<<mesh_size<<" "<<mesh_size<<" "<<mesh_size<<endl;
  outfile<<"X_COORDINATES "<<mesh_size<<" float"<<endl;
  for(i = 0;i < mesh_size;i++)
  {
    outfile<<i*xbinsize + xlo<<endl;
  }
  outfile<<"Y_COORDINATES "<<mesh_size<<" float"<<endl;
  for(i = 0;i < mesh_size;i++)
  {
    outfile<<i*ybinsize + ylo<<endl;
  }
  outfile<<"Z_COORDINATES "<<mesh_size<<" float"<<endl;
  for(i = 0;i < mesh_size;i++)
  {
    outfile<<i*zbinsize + zlo<<endl;
  }
  outfile<<"POINT_DATA "<<mesh_ls.size()<<endl;
  outfile<<"SCALARS ls_phi float 1"<<endl;
  outfile<<"LOOKUP_TABLE default"<<endl;
  for(i = 0;i < mesh_ls.size();i++)
  {
    outfile<<mesh_ls[i]<<endl;
  }
  outfile.close();
}


#ifndef CUAUV_SIM_GEOMETRY_H
#define CUAUV_SIM_GEOMETRY_H

#include <Eigen/Core>
#include <Eigen/Geometry>

namespace cuauv {
namespace fishbowl {

/**
 * sphere_sphere_sweep calculates the normalized first and second times of
 * intersection of two spheres. The times are expressed in terms of the given
 * positions, and are as if the distances traveled by the spheres were covered
 * in unit time.
 * @param ar Radius of the first sphere.
 * @param ax0 Previous position of the first sphere.
 * @param ax1 Current position of the first sphere.
 * @param br Radius of the second sphere.
 * @param bx0 Previous position of the second sphere.
 * @param bx1 Current position of the second sphere.
 * @param t0 Will hold the (normalized) time of the first intersection, if any.
 *           If no intersection occurs, the value is undefined.
 * @param t1 Will hold the time of the second intersection.
 * @returns True iff an intersection occurs.
 * @see http://www.gamasutra.com/view/feature/131790/simple_intersection_tests_for_games.php?page=2
 */
bool sphere_sphere_sweep(double ar, const Eigen::Vector3d& ax0, const Eigen::Vector3d& ax1,
                         double br, const Eigen::Vector3d& bx0, const Eigen::Vector3d& bx1,
                         double& t0, double& t1);

double line_distance(const Eigen::Vector3d& x0, const Eigen::Vector3d& x1, const Eigen::Vector3d& x);

/**
 * swing_twist decomposes a quaternion into swing and twist components.  The
 * swing twist component is a rotation around the twist axis, while the swing
 * component is a rotation around a direction vector perpendicular to the twist
 * axis.
 * The original quaternion q = swing * twist.
 * @param q The quaternion to decompose.
 * @param vt The twist axis.
 * @param swing The swing component of the rotation.
 * @param twist The twist component of the rotation.
 * @see http://www.alinenormoyle.com/weblog/?p=726.
 * @see "Swing-twist decomposition in Clifford algebra" (Dobrowolski, 2015).
 */
void swing_twist(const Eigen::Quaterniond& q, const Eigen::Vector3d& vt,
                 Eigen::Quaterniond& swing, Eigen::Quaterniond& twist);

Eigen::Vector3d quat_to_euler(Eigen::Quaterniond q);
Eigen::Quaterniond euler_to_quat(double h, double p, double r);

} // namespace fishbowl
} // namespace cuauv

#endif // CUAUV_SIM_GEOMETRY_H


//=======================================================================
// Copyright 2007 Aaron Windsor
//
// 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 __IS_STRAIGHT_LINE_DRAWING_HPP__
#define __IS_STRAIGHT_LINE_DRAWING_HPP__

#include <boost/config.hpp>
#include <boost/utility.hpp> //for next and prior
#include <boost/tuple/tuple.hpp>
#include <boost/tuple/tuple_comparison.hpp>
#include <boost/property_map.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/planar_detail/bucket_sort.hpp>

#include <algorithm>
#include <vector>
#include <set>

namespace boost
{

  // Return true exactly when the line segments s1 = ((x1,y1), (x2,y2)) and
  // s2 = ((a1,b1), (a2,b2)) intersect in a point other than the endpoints of
  // the line segments. The one exception to this rule is when s1 = s2, in
  // which case false is returned - this is to accomodate multiple edges
  // between the same pair of vertices, which shouldn't invalidate the straight
  // line embedding. A tolerance variable epsilon can also be used, which
  // defines how far away from the endpoints of s1 and s2 we want to consider
  // an intersection.

  bool intersects(double x1, double y1,
                  double x2, double y2,
                  double a1, double b1,
                  double a2, double b2,
                  double epsilon = 0.000001
                  )
  {

    if (x1 - x2 == 0)
      {
        std::swap(x1,a1);
        std::swap(y1,b1);
        std::swap(x2,a2);
        std::swap(y2,b2);
      }

    if (x1 - x2 == 0)
      {
        BOOST_USING_STD_MAX();
        BOOST_USING_STD_MIN();

        //two vertical line segments
        double min_y = min BOOST_PREVENT_MACRO_SUBSTITUTION(y1,y2);
        double max_y = max BOOST_PREVENT_MACRO_SUBSTITUTION(y1,y2);
        double min_b = min BOOST_PREVENT_MACRO_SUBSTITUTION(b1,b2);
        double max_b = max BOOST_PREVENT_MACRO_SUBSTITUTION(b1,b2);
        if ((max_y > max_b && max_b > min_y) ||
            (max_b > max_y && max_y > min_b)
            )
          return true;
        else
          return false;
      }

    double x_diff = x1 - x2;
    double y_diff = y1 - y2;
    double a_diff = a2 - a1;
    double b_diff = b2 - b1;

    double beta_denominator = b_diff - (y_diff/((double)x_diff)) * a_diff;

    if (beta_denominator == 0)
      {
        //parallel lines
        return false;
      }

    double beta = (b2 - y2 - (y_diff/((double)x_diff)) * (a2 - x2)) /
      beta_denominator;
    double alpha = (a2 - x2 - beta*(a_diff))/x_diff;

    double upper_bound = 1 - epsilon;
    double lower_bound = 0 + epsilon;

    return (beta < upper_bound && beta > lower_bound &&
            alpha < upper_bound && alpha > lower_bound);

  }

  template <typename Graph,
            typename GridPositionMap,
            typename VertexIndexMap
            >
  bool is_straight_line_drawing(const Graph& g,
                                GridPositionMap drawing,
                                VertexIndexMap vm
                                )
  {

    typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
    typedef typename graph_traits<Graph>::vertex_iterator vertex_iterator_t;
    typedef typename graph_traits<Graph>::edge_descriptor edge_t;
    typedef typename graph_traits<Graph>::edge_iterator edge_iterator_t;
    typedef typename graph_traits<Graph>::edges_size_type e_size_t;
    typedef typename graph_traits<Graph>::vertices_size_type v_size_t;

    typedef std::size_t x_coord_t;
    typedef std::size_t y_coord_t;
    typedef boost::tuple<edge_t, x_coord_t, y_coord_t> edge_event_t;
    typedef typename std::vector< edge_event_t > edge_event_queue_t;

    typedef tuple<y_coord_t, y_coord_t, x_coord_t, x_coord_t> active_map_key_t;
    typedef edge_t active_map_value_t;
    typedef std::map< active_map_key_t, active_map_value_t > active_map_t;
    typedef typename active_map_t::iterator active_map_iterator_t;

    edge_event_queue_t edge_event_queue;
    active_map_t active_edges;

    edge_iterator_t ei, ei_end;
    for(tie(ei,ei_end) = edges(g); ei != ei_end; ++ei)
      {
        edge_t e(*ei);
        vertex_t s(source(e,g));
        vertex_t t(target(e,g));
        edge_event_queue.push_back
          (make_tuple(e,
                      static_cast<std::size_t>(drawing[s].x),
                      static_cast<std::size_t>(drawing[s].y)
                      )
           );
        edge_event_queue.push_back
          (make_tuple(e,
                      static_cast<std::size_t>(drawing[t].x),
                      static_cast<std::size_t>(drawing[t].y)
                      )
           );
      }

    // Order by edge_event_queue by first, then second coordinate
    // (bucket_sort is a stable sort.)
    bucket_sort(edge_event_queue.begin(), edge_event_queue.end(),
                property_map_tuple_adaptor<edge_event_t, 2>()
                );

    bucket_sort(edge_event_queue.begin(), edge_event_queue.end(),
                property_map_tuple_adaptor<edge_event_t, 1>()
                );

    typedef typename edge_event_queue_t::iterator event_queue_iterator_t;
    event_queue_iterator_t itr_end = edge_event_queue.end();
    for(event_queue_iterator_t itr = edge_event_queue.begin();
        itr != itr_end; ++itr
        )
      {
        edge_t e(get<0>(*itr));
        vertex_t source_v(source(e,g));
        vertex_t target_v(target(e,g));
        if (drawing[source_v].x > drawing[target_v].x)
          std::swap(source_v, target_v);

        active_map_key_t key(get(drawing, source_v).y,
                             get(drawing, target_v).y,
                             get(drawing, source_v).x,
                             get(drawing, target_v).x
                             );

        active_map_iterator_t a_itr = active_edges.find(key);
        if (a_itr == active_edges.end())
          {
            active_edges[key] = e;
          }
        else
          {
            active_map_iterator_t before, after;
            if (a_itr == active_edges.begin())
              before = active_edges.end();
            else
              before = prior(a_itr);
            after = next(a_itr);

            if (after != active_edges.end() || before != active_edges.end())
              {

                edge_t f = after != active_edges.end() ?
                  after->second : before->second;

                vertex_t e_source(source(e,g));
                vertex_t e_target(target(e,g));
                vertex_t f_source(source(f,g));
                vertex_t f_target(target(f,g));

                if (intersects(drawing[e_source].x,
                               drawing[e_source].y,
                               drawing[e_target].x,
                               drawing[e_target].y,
                               drawing[f_source].x,
                               drawing[f_source].y,
                               drawing[f_target].x,
                               drawing[f_target].y
                               )
                    )
                  return false;
              }

            active_edges.erase(a_itr);

          }
      }

    return true;

  }

  template <typename Graph, typename GridPositionMap>
  bool is_straight_line_drawing(const Graph& g, GridPositionMap drawing)
  {
    return is_straight_line_drawing(g, drawing, get(vertex_index,g));
  }

}

#endif // __IS_STRAIGHT_LINE_DRAWING_HPP__


/*
   Copyright (C) 2015-2021 by Synge Todo <wistaria@phys.s.u-tokyo.ac.jp>

   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.
*/

#include <cmath>
#include <gtest/gtest.h>
#include <boost/math/differentiation/autodiff.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include "ising/mp_wrapper.hpp"
#include "ising/free_energy/common.hpp"
#include "ising/free_energy/square.hpp"

using namespace boost::multiprecision;
using namespace ising::free_energy;

TEST(IsingFreeEnergy, Square0) {
  typedef double real_t;
  auto Jx = convert<real_t>("1.5");
  auto Jy = convert<real_t>("2.5");
  auto t = convert<real_t>("2");
  auto beta = boost::math::differentiation::make_fvar<real_t, 2>(1 / t);
  auto f = square::infinite(Jx, Jy, beta);
  double eps = 1e-12;
  EXPECT_DOUBLE_EQ(-4.0007112633153277, free_energy(f, beta));
  EXPECT_DOUBLE_EQ(-3.9941706779898838, energy(f, beta));
  EXPECT_DOUBLE_EQ(0.024138928587813167, specific_heat(f, beta));
}


/**
 */
#ifndef __POLYNOMIAL_FILTERING_HPP
#define __POLYNOMIAL_FILTERING_HPP

#include <cmath>
#include <iostream>
#include <string>
#include <exception>
#include <tuple>

namespace polynomialfiltering {

	class PolynomialFilteringException : public std::exception {
	protected:
		std::string message;
	public:
		PolynomialFilteringException() :
			message("Polynomial Filtering Exception: ") {}

		virtual const char* what() const throw() {
			return message.c_str();
		}
	};

	class ValueError : public PolynomialFilteringException {
	public:
		ValueError(std::string message) {
			this->message += message;
		}

	};

	class EigenException : public PolynomialFilteringException {
	public:
		EigenException(std::string where) {
			this->message += "Fatai Eigen Exception; " + where;
		}
	};

}

#undef  eigen_assert
#define eigen_assert(X) do { if(!(X)) throw ::polynomialfiltering::EigenException(#X); } while(false);

#include <Eigen/Dense>
#include <gsl/gsl_cdf.h>

using namespace Eigen;
namespace polynomialfiltering {

	typedef VectorXd RealVector;
	typedef MatrixXd RealMatrix;
	typedef Matrix<double, 1, 1> RealVector1;
	typedef Matrix<double, 2, 1> RealVector2;
	typedef Matrix<double, 3, 1> RealVector3;
	typedef Matrix<double, 4, 1> RealVector4;
	typedef Matrix<double, 5, 1> RealVector5;
	typedef Matrix<double, 6, 1> RealVector6;
	typedef Matrix<double, 7, 1> RealVector7;
	typedef Matrix<double, 8, 1> RealVector8;
	typedef Matrix<double, 9, 1> RealVector9;
	typedef Matrix<double, 10, 1> RealVector10;
    typedef Matrix<double, 11, 1> RealVector11;
    typedef Matrix<double, 12, 1> RealVector12;
    typedef Matrix<double, 13, 1> RealVector13;
    typedef Matrix<double, 14, 1> RealVector14;
    typedef Matrix<double, 15, 1> RealVector15;
    typedef Matrix<double, 16, 1> RealVector16;
    typedef Matrix<double, 17, 1> RealVector17;
    typedef Matrix<double, 18, 1> RealVector18;
    typedef Matrix<double, 19, 1> RealVector19;
	typedef Matrix<double, 1, 1> RealMatrix1;
	typedef Matrix<double, 2, 2> RealMatrix2;
	typedef Matrix<double, 3, 3> RealMatrix3;
	typedef Matrix<double, 4, 4> RealMatrix4;
	typedef Matrix<double, 5, 5> RealMatrix5;
	typedef Matrix<double, 6, 6> RealMatrix6;
	typedef Matrix<double, 7, 7> RealMatrix7;
	typedef Matrix<double, 8, 8> RealMatrix8;
	typedef Matrix<double, 9, 9> RealMatrix9;
	typedef Matrix<double, 10, 10> RealMatrix10;

	using std::shared_ptr;

	inline void NOOP() {} // Used to delete lines such as std::vector (Python list) initializations

	inline int integerCast(Index a) {
		return static_cast<int>(a);
	}

	inline int min(int a, int b) {
		return std::min(a, b);
	}

	inline int min(int a, Index b) {
		return std::min(a, static_cast<int>(b));
	}

	inline int min(Index a, int b) {
		return std::min(b, static_cast<int>(a));
	}

	inline double min(double a, double b) {
		return std::min(a, b);
	}

	inline double max(double a, double b) {
		return std::max(a, b);
	}

	inline RealMatrix copy(const RealMatrix& m) {
		return m;
	}

	// wrapper to match Python eye syntax for square matrices
	inline RealMatrix identity(Index N) {
		return MatrixXd::Identity(N, N);
	}

	inline RealMatrix ones(Index N, Index M = 1) {
		return MatrixXd::Constant(N, M, 1.0);
	}

	inline RealVector diag(const RealMatrix& m) {
		return m.diagonal();
	}

	inline RealMatrix diag(const RealVector& v) {
		return v.asDiagonal();
	}

	inline RealMatrix sqrt(const RealMatrix& m) {
		return m.array().sqrt();
	}

	// inline RealVector sqrt(const RealVector& v) {
	// 	return v.array().sqrt();
	// }

	inline RealMatrix zeros(Index N, Index M = 1) {
		return MatrixXd::Constant(N, M, 0.0);
	}

	inline RealMatrix solve(const RealMatrix& A, const RealMatrix& B) {
		return A.ldlt().solve(B); //  A.colPivHouseholderQr().solve(B); // A.completeOrthogonalDecomposition().solve(B); //
	}

	inline RealMatrix inv(const RealMatrix& M) {
		return M.inverse();
	}

	inline RealMatrix transpose(const RealMatrix& M) {
		return M.transpose();
	}

	inline RealMatrix operator+(const RealMatrix& m, double x) {
		return m.array() + x;
	}

	inline RealMatrix operator-(const RealMatrix& m, double x) {
		return m.array() - x;
	}

	inline RealMatrix arrayTimes(const RealMatrix& a, const RealMatrix& b) {
		return a.array() * b.array();
	}

	inline double arrayTimes(double a, double b) {
		return a * b;
	}

	inline RealMatrix arrayDivide(const RealMatrix& a, const RealMatrix& b) {
		return a.array() / b.array();
	}

	inline double sqrt(double x) {
		return ::sqrt(x);
	}

	inline double cos(double x) {
		return ::cos(x);
	}

	inline double sin(double x) {
		return ::sin(x);
	}

	inline double atan(double x) {
		return ::atan(x);
	}

	inline double pow(double x, double p) {
		return ::pow(x, p);
	}

	inline double atan2(double x, double y) {
		return ::atan2(x, y);
	}

	inline RealMatrix atan(RealMatrix A) {
		return A.array().atan();
	}

	inline RealMatrix atan2(RealMatrix A, RealMatrix B) {
		RealMatrix O(A.rows(), A.cols());
		for (int c = 0; c < A.cols(); c++) {
			for (int r = 0; r < A.rows(); r++) {
				O(r,c) = ::atan2(A(r,c), B(r,c));
			}
		}
		return O;
	}

	inline RealMatrix cos(RealMatrix A) {
		return A.array().cos();
	}

	inline RealMatrix sin(RealMatrix A) {
		return A.array().sin();
	}

	inline RealMatrix pow(RealMatrix A, double p) {
		RealMatrix O(A.rows(), A.cols());
		for (int c = 0; c < A.cols(); c++) {
			for (int r = 0; r < A.rows(); r++) {
				O(r,c) = ::pow(A(r,c), p );
			}
		}
		return O;
	}

	inline long len(const RealMatrix& a) {
		return (long) (a.rows() * a.cols());
	}

	inline long len(const RealVector& a) {
		return (long) (a.size());
	}

	inline RealVector test() {
		return Map<RowVectorXd>(new double[3] { 1, 2, 3 }, 3);
	}

	inline double chi2Cdf(double chi, int df) {
		return gsl_cdf_chisq_P(chi, df);
	}

	inline double chi2Ppf(double p, int df) {
		return gsl_cdf_chisq_Pinv(1.0 - p, df);
	}

	inline double fdistCdf(double chi, int df1, int df2) {
		return gsl_cdf_fdist_P(chi, df1, df2);
	}

	inline double fdistPpf(double p, int df1, int df2) {
		return gsl_cdf_fdist_Pinv(1.0 - p, df1, df2);
	}
}

#endif // __POLYNOMIAL_


/*
*   Greedy Search
*   by R. Falque
*   29/11/2018
*/

#ifndef DOWNSAMPLING
#define DOWNSAMPLING

#include <Eigen/Core>
#include <vector>
#include <limits>

#include <cfloat>
#include <iostream>
#include <cmath>
#include <algorithm>

#include "getMinMax.hpp"
#include "farther_sampling.hpp"
#include "nanoflannWrapper.hpp"

using namespace std;

class three_d_point{
public:
	double x;
	double y;
	double z;
};

class point_and_occurences{
public:
	double x;
	double y;
	double z;
	int occurence;
};

inline void voxel_grid_downsampling(Eigen::MatrixXd & in_cloud, double leaf_size, Eigen::MatrixXd & out_cloud)
{
	Eigen::Vector3d min_point, max_point;
	getMinMax(in_cloud, min_point, max_point);

	double inv_leaf_size;
	inv_leaf_size = 1.0/leaf_size;

	Eigen::Vector3i min_box, max_box;
	min_box << floor(min_point(0) * inv_leaf_size ), floor(min_point(1) * inv_leaf_size ), floor(min_point(2) * inv_leaf_size);
	max_box << floor(max_point(0) * inv_leaf_size ), floor(max_point(1) * inv_leaf_size ), floor(max_point(2) * inv_leaf_size);

    Eigen::Vector3i divb, divb_mul;
    divb << max_box(0) - min_box(0) + 1, max_box(1) - min_box(1) + 1, max_box(2) - min_box(2) + 1;
    divb_mul << 1, divb(0), divb(0) * divb(1);

	std::vector < std::vector < std::vector < point_and_occurences> > > voxels;

	voxels.resize(divb(0));
	for (int x_index = 0; x_index < voxels.size(); ++x_index)
	{
		voxels[x_index].resize(divb(1));
		for (int y_index = 0; y_index < voxels[0].size(); ++y_index)
		{
			voxels[x_index][y_index].resize(divb(2));
		}
	}

	// plus assign zeros to voxel_count
	for (int i = 0; i < in_cloud.rows(); ++i)
	{
        int x_index = static_cast<int> ( floor(in_cloud(i, 0) * inv_leaf_size) - min_box(0) );
        int y_index = static_cast<int> ( floor(in_cloud(i, 1) * inv_leaf_size) - min_box(1) );
        int z_index = static_cast<int> ( floor(in_cloud(i, 2) * inv_leaf_size) - min_box(2) );

        voxels[x_index][y_index][z_index].x += in_cloud(i,0);
        voxels[x_index][y_index][z_index].y += in_cloud(i,1);
        voxels[x_index][y_index][z_index].z += in_cloud(i,2);
        voxels[x_index][y_index][z_index].occurence ++;
	}

	std::vector< three_d_point> final_cloud;
	three_d_point temp;
	for (int x_index = 0; x_index < voxels.size(); ++x_index)
	{
		for (int y_index = 0; y_index < voxels[0].size(); ++y_index)
		{
			for (int z_index = 0; z_index < voxels[0][0].size(); ++z_index)
			{
				if (voxels[x_index][y_index][z_index].occurence!= 0)
				{
					temp.x = voxels[x_index][y_index][z_index].x / voxels[x_index][y_index][z_index].occurence;
					temp.y = voxels[x_index][y_index][z_index].y / voxels[x_index][y_index][z_index].occurence;
					temp.z = voxels[x_index][y_index][z_index].z / voxels[x_index][y_index][z_index].occurence;
					final_cloud.push_back(temp);
				}
			}
		}
	}

	out_cloud.resize(final_cloud.size(), 3);
	for (int i = 0; i < final_cloud.size(); ++i)
	{
		out_cloud.row(i) << final_cloud[i].x, final_cloud[i].y, final_cloud[i].z;
	}

};

inline void downsampling(Eigen::MatrixXd & in_cloud,
                         Eigen::MatrixXd & out_cloud,
						 std::vector<int> & in_cloud_samples,
						 double grid_resolution,
						 double leaf_size,
						 bool use_farthest_sampling,
						 bool use_relative_grid)
{
	// overwrite the leaf_size
	if (use_relative_grid) {
		double scale;
		getScale(in_cloud, scale);
		leaf_size = scale / grid_resolution;
	}

	// downsampling
	Eigen::MatrixXd downsampled_cloud;
	if (use_farthest_sampling)
	{
		farthest_sampling_by_sphere(in_cloud, leaf_size/100, downsampled_cloud);
	}
	else
	{
		voxel_grid_downsampling(in_cloud, leaf_size, downsampled_cloud);
	}

	out_cloud.resize(downsampled_cloud.rows(), 3);

	nanoflann_wrapper tree(in_cloud);
	for (int i = 0; i < downsampled_cloud.rows(); ++i)
	{
		std::vector< int > closest_point;
		closest_point = tree.return_k_closest_points(downsampled_cloud.row(i), 1);

		out_cloud.row(i) = in_cloud.row( closest_point[0] );
		in_cloud_samples.push_back(closest_point[0]);
	}

};

#endif


#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <utility>
#include <numeric>
#include <string>
#include <fstream>
#include <map>
#include <sstream>
#include <limits>
#include <bitset>
#include <cmath>
#include <unordered_map>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>

class MemoryGame
{
    public:
        MemoryGame(std::vector<long long> start_num) : starting_numbers(start_num) {}
        std::vector<long long> starting_numbers;
        long long play(const long long end_turn)
        {
            // key - number, value - vector of indices
            std::map<long long, std::vector<long long>> map;
            long long last_spoken;
            long long turn = 1LL;
            std::transform(starting_numbers.begin(), starting_numbers.end(), std::inserter(map, map.end()),
            [&](const long long num)
            {
                std::vector<long long> indices {turn};
                last_spoken = num;
                turn++;
                return std::pair<long long, std::vector<long long>>(num, indices);
            });
            while(turn <= end_turn)
            {
                // if size == 1 speak 0 because number was spoken only once, otherwise speak difference between turns
                last_spoken = (map[last_spoken].size() == 1) ? 0 : map[last_spoken][1] - map[last_spoken][0];
                // save current turn in map for spoken number
                if(map[last_spoken].size() == 2)
                {
                    map[last_spoken][0] = map[last_spoken][1]; // shift to first
                    map[last_spoken].pop_back(); // remove second
                }
                map[last_spoken].push_back(turn);
                turn++;
            }
            return last_spoken;
        }
};

void part1(MemoryGame game)
{
    std::cout << "======\nPart 1\n======\n";
    constexpr long long end = 2020LL;
    std::cout << end << "th number spoken = " << game.play(end) << '\n';
}

void part2(MemoryGame game)
{
    std::cout << "======\nPart 2\n======\n";
    constexpr long long end = 30000000LL;
    std::cout << end <<  "th number spoken = " << game.play(end) << '\n';
}

std::vector<long long> get_input(const std::string file_name)
{
    std::ifstream file(file_name);
    std::string line;
    std::vector<std::string> line_elements;
    std::vector<long long> all_elements;
    if(file.is_open())
    {
        while (std::getline(file, line))
        {
            boost::split(line_elements, line, boost::is_any_of(","), boost::token_compress_on);
            std::transform(line_elements.begin(), line_elements.end(), std::back_inserter(all_elements),
               [](const std::string& str) { return std::stoll(str); });
        }
    }
    file.close();
    return all_elements;
}

int main()
{
    const std::string file_name = "/home/daria/Documents/AoC2020/input/day15.txt";
    std::vector<long long> puzzle_input = get_input(file_name);
    MemoryGame game(puzzle_input);
    part1(game);
    std::cout << '\n';
    part2(game);
    std::cout << '\n';
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2011 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <iostream>
#include <utility> // std::move

#include <boost/timer/timer.hpp>

#include "dune/grid/config.h"

#include "dune/grid/onedgrid.hh"
#include "dune/grid/onedgrid/onedgridfactory.hh"

#include "fem/assemble.hh"
#include "fem/norms.hh"
#include "fem/lagrangespace.hh"
//#include "fem/hierarchicspace.hh"   // ContinuousHierarchicMapper
#include "fem/istlinterface.hh"
#include "linalg/umfpack_solve.hh"
#include "linalg/triplet.hh"
#include "io/gnuplot.hh"
#include "utilities/kaskopt.hh"

using namespace Kaskade;
#include "atp.hh"

int main(int argc, char *argv[])
  {
  using namespace boost::fusion;

  std::cout << "Start atp transfer tutorial program" << std::endl;

  boost::timer::cpu_timer totalTimer;

  int verbosity = 1;
  bool dump = true;
  std::unique_ptr<boost::property_tree::ptree> pt = getKaskadeOptions(argc, argv, verbosity, dump);

  int  direct = 1,
       refinements = getParameter(pt, "refinement", 5),
       order =  getParameter(pt, "order", 2);
  double fsign = static_cast<double>(getParameter(pt, "sign", -1.0));
  int graphicalOutput= getParameter(pt, "graphicalOutput", 1);

  std::cout << "refinements of original mesh : " << refinements << std::endl;
  std::cout << "discretization order         : " << order << std::endl;

  std::cout << "fsign = " << fsign << "\n";

  //   one-dimensional space: dim=1
  int const dim=1;
  using Grid = Dune::OneDGrid;
  using LeafView = Grid::LeafGridView;
  // construction of finite element space for the scalar solution T
  using H1Space = FEFunctionSpace<ContinuousLagrangeMapper<double,LeafView> >;
  // using H1Space = FEFunctionSpace<ContinuousHierarchicMapper<double,LeafView> >;
  using Spaces = boost::fusion::vector<H1Space const*>;
  using VariableDescriptions = boost::fusion::vector<VariableDescription<0,1,0> >;
  using VarSetDesc = VariableSetDescription<Spaces,VariableDescriptions>;
  using Functional = ATPFunctional<double,VarSetDesc>;
  using Assembler = VariationalFunctionalAssembler<LinearizationAt<Functional> >;
  using CoefficientVectors = VarSetDesc::CoefficientVectorRepresentation<0,1>::type;
  constexpr int nvars = ATPFunctional<double,VarSetDesc>::AnsatzVars::noOfVariables;
  constexpr int neq = ATPFunctional<double,VarSetDesc>::TestVars::noOfVariables;

  Dune::GridFactory<Grid> factory;

  // point (in case of dimension>1: vertex) coordinates v[0]
  Dune::FieldVector<double,dim> v;
  v[0]=-3; factory.insertVertex(v);
  v[0]=0; factory.insertVertex(v);
  v[0]=3; factory.insertVertex(v);
  std::vector<unsigned int> vid(2);
  Dune::GeometryType gt(Dune::GeometryType::simplex,dim);
  vid[0]=0; vid[1]=1; factory.insertElement(gt,vid);
  vid[0]=1; vid[1]=2; factory.insertElement(gt,vid);
  // interval defined by 2 point indices
  std::unique_ptr<Grid> grid( factory.createGrid() ) ;
  // the coarse grid will be refined refinements times
  grid->globalRefine(refinements);
  // some information on the refined mesh
  std::cout << "Grid: " << grid->size(1) << " points " << std::endl;
  // a gridmanager is constructed
  // as connector between geometric and algebraic information
  GridManager<Grid> gridManager(std::move(grid));

  H1Space temperatureSpace(gridManager,gridManager.grid().leafGridView(),
               order);
  Spaces spaces(&temperatureSpace);
  // VariableDescription<int spaceId, int components, int Id>
  // spaceId: number of associated FEFunctionSpace
  // components: number of components in this variable
  // Id: number of this variable
  VarSetDesc varSetDesc(spaces,{ "T" });
  Functional F(fsign);
  Assembler assembler(gridManager,spaces);
  VarSetDesc::VariableSet x(varSetDesc);
  VarSetDesc::VariableSet dx(varSetDesc);

  // set nnz to the number of structural nonzero elements of the matrix to be assembled below
  size_t  nnz = assembler.nnz(0,neq,0,nvars,false);
  size_t  size = varSetDesc.degreesOfFreedom(0,nvars);
  AssembledGalerkinOperator<Assembler,0,neq,0,nvars> A(assembler);
  MatrixAsTriplet<double> triplet(nnz);

  std::vector<double> rhs(size), sol(size);

  int k=0;
  L2Norm l2Norm;
  double norm_dx, norm_rhs;
  x=0;

  std::cout << std::endl << "Newton iteration starts:" << std::endl <<
            "iter   ||correction||            ||F||  assemble time  linsolve time"
            << std::endl;

// begin of ordinary Newton iteration loop
  do
  {
    boost::timer::cpu_timer assembTimer;
    assembler.assemble(linearization(F,x));
	double assembleTime = (double)(assembTimer.elapsed().user)/1e9;
    triplet = A.get<MatrixAsTriplet<double> >();
    //     for (k=0; k< nnz; k++)
    //       {
    //         printf("%3d %3d %e\n", triplet.ridx[k], triplet.cidx[k], triplet.data[k]);
    //       }
    boost::timer::cpu_timer directTimer;
    Factorization<double> *matrix = 0;
    matrix = new UMFFactorization<double>(size,0,triplet.ridx,triplet.cidx,triplet.data);
    assembler.toSequence(0,neq,rhs.begin());
    for (int l=0; l<rhs.size(); ++l) assert(std::isfinite(rhs[l]));
    matrix->solve(rhs,sol);
    double solveTime = (double)(directTimer.elapsed().user)/1e9;
    delete matrix;
    for (int l=0; l<sol.size(); ++l) assert(std::isfinite(sol[l]));
    dx.read(sol.begin());
    dx *= -1;
    x += dx;
    norm_dx=l2Norm(component<0>(dx));
    VarSetDesc::VariableSet tmp(dx);
    tmp.read(rhs.begin());
    norm_rhs=l2Norm(component<0>(tmp));
    std::cout << std::setw(4) << k+1 << "  "
              << std::setw(15) << std::setprecision(5) << std::scientific << norm_dx << "  "
              << std::setw(15) << norm_rhs << "  " << std::setprecision(3);
    std::cout.unsetf(std::ios::fixed | std::ios::scientific);
    std::cout << std::fixed << std::setw(6) << "       " << std::setprecision(3)
              << assembleTime << "s        " << std::setw(6) << solveTime << "s " << std::endl;
    // output of solution in VTK format for visualization,
    // the data are written as ascii stream into file temperature.vtu,
    // possible is also binary
    // char fname[6];
    // IoOptions options;
    // options.outputType = IoOptions::ascii;
    // LeafView leafView = gridManager.grid().leafGridView();
    // sprintf(fname,"atp_%#02d",k);
    // writeVTKFile(x,fname,options,order);
    if ( graphicalOutput!=0 )
    {
      IoOptions gnuplotOptions{};
      std::string empty;
      std::string s = "names.gnuplotinfo." + getParameter(pt, "gnuplotinfo", empty);
      gnuplotOptions.info = static_cast<IoOptions::Info>(getParameter(pt,s,0));
      writeGnuplotFile(x,"function",gnuplotOptions);
    };
    // output of solution for Amira visualization,
    // the data are written in binary format into file temperature.am,
    // possible is also ascii
    //  IoOptions options;
    //  options.outputType = IoOptions::ascii;
    //  LeafView leafView = gridManager.grid().leafGridView();
    //  writeAMIRAFile(leafView,varSetDesc,x,"atp",options);
    //StopSnippet6
    k ++;
  }
  while ( norm_dx > 1.0e-5 );

  std::cout << "total computing time: " << boost::timer::format(totalTimer.elapsed()) << "\n";
  std::cout << "End atp transfer tutorial program" << std::endl;
}


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

// Copyright (c) 2011 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//[boost_range_filtered
//` Shows how to use a Boost.Geometry linestring, filtered by Boost.Range adaptor

#include <iostream>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/adapted/boost_range/filtered.hpp>

struct not_two
{
    template <typename P>
    bool operator()(P const& p) const
    {
        return boost::geometry::get<1>(p) != 2;
    }
};

int main()
{
    typedef boost::geometry::model::d2::point_xy<int> xy;
    boost::geometry::model::linestring<xy> line;
    line.push_back(xy(0, 0));
    line.push_back(xy(1, 1));
    line.push_back(xy(2, 2));
    line.push_back(xy(3, 1));
    line.push_back(xy(4, 0));
    line.push_back(xy(5, 1));
    line.push_back(xy(6, 2));
    line.push_back(xy(7, 1));
    line.push_back(xy(8, 0));

    using boost::adaptors::filtered;
    std::cout
        << boost::geometry::length(line) << std::endl
        << boost::geometry::length(line | filtered(not_two())) << std::endl
        << boost::geometry::dsv(line | filtered(not_two())) << std::endl;

    return 0;
}

//]

//[boost_range_filtered_output
/*`
Output:
[pre
11.3137
9.65685
((0, 0), (1, 1), (3, 1), (4, 0), (5, 1), (7, 1), (8, 0))
]
*/
//]