// Glauber model
// Copyright 2015 Jonah E. Bernhard, J. Scott Moreland
// MIT License

#include "nucleus.h"

#include <cmath>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>

#include <boost/math/constants/constants.hpp>

#include "random.h"

namespace glauber {

namespace {

// Correct Woods-Saxon surface thickness parameter (a) for finite Gaussian
// nucleon width (w):
//
//    a_corrected^2 = a^2 - c^2*w*2
//
// where c is a universal constant independent of a and w.
//
// See https://gist.github.com/jbernhard/60b3ab9662a4737658d8.
double correct_a(double a, double w) {
  constexpr auto c = 0.61;  // correction coefficient
  constexpr auto a_min = 0.01;  // min. value (prevent div. by zero, etc.)
  return std::sqrt(std::fmax(a*a - c*c*w*w, a_min*a_min));
}

}  // unnamed namespace

NucleusPtr Nucleus::create(const std::string& species, double nucleon_width) {
  // W-S params ref. in header
  // XXX: remember to add new species to the help output in main() and the readme
  if (species == "p")
    return NucleusPtr{new Proton{}};
  else if (species == "d")
    return NucleusPtr{new Deuteron{}};
  else if (species == "Cu")
    return NucleusPtr{new WoodsSaxonNucleus{
       62, 4.20, correct_a(0.596, nucleon_width)
    }};
  else if (species == "Cu2")
    return NucleusPtr{new DeformedWoodsSaxonNucleus{
       62, 4.20, correct_a(0.596, nucleon_width), 0.162, -0.006
    }};
  else if (species == "Au")
    return NucleusPtr{new WoodsSaxonNucleus{
      197, 6.38, correct_a(0.535, nucleon_width)
    }};
  else if (species == "Au2")
    return NucleusPtr{new DeformedWoodsSaxonNucleus{
      197, 6.38, correct_a(0.535, nucleon_width), -0.131, -0.031
    }};
  else if (species == "Pb")
    return NucleusPtr{new WoodsSaxonNucleus{
      208, 6.62, correct_a(0.546, nucleon_width)
    }};
  else if (species == "U")
    return NucleusPtr{new DeformedWoodsSaxonNucleus{
      238, 6.81, correct_a(0.600, nucleon_width), 0.280, 0.093
    }};
  else if (species == "U2")
    return NucleusPtr{new DeformedWoodsSaxonNucleus{
      238, 6.86, correct_a(0.420, nucleon_width), 0.265, 0.000
    }};
  else if (species == "U3")
    return NucleusPtr{new DeformedWoodsSaxonNucleus{
      238, 6.67, correct_a(0.440, nucleon_width), 0.280, 0.093
    }};
  else
    throw std::invalid_argument{"unknown projectile species: " + species};
}

Nucleus::Nucleus(std::size_t A) : nucleons_(A), offset_(0) {}

void Nucleus::sample_nucleons(double offset) {
  offset_ = offset;
  sample_nucleons_impl();
}

void Nucleus::set_nucleon_position(Nucleon& nucleon, double x, double y) {
  nucleon.set_position(x + offset_, y);
}

Proton::Proton() : Nucleus(1) {}

/// Always zero.
double Proton::radius() const {
  return 0.;
}

/// Always place the nucleon at the origin.
void Proton::sample_nucleons_impl() {
  set_nucleon_position(*begin(), 0., 0.);
}

// Without loss of generality, let the internal a_ parameter be the minimum of
// the given (a, b) and the internal b_ be the maximum.
Deuteron::Deuteron(double a, double b)
    : Nucleus(2),
      a_(std::fmin(a, b)),
      b_(std::fmax(a, b))
{}

double Deuteron::radius() const {
  // The quantile function for the exponential distribution exp(-2*a*r) is
  // -log(1-q)/(2a).  Return the 99% quantile.
  return -std::log(.01)/(2*a_);
}

void Deuteron::sample_nucleons_impl() {
  // Sample the inter-nucleon radius using rejection sampling with an envelope
  // function.  The Hulthén wavefunction including the r^2 Jacobian expands to
  // three exponential terms:  exp(-2*a*r) + exp(-2*b*r) - 2*exp(-(a+b)*r).
  // This does not have a closed-form inverse CDF, however we can easily sample
  // exponential numbers from the term that falls off the slowest, i.e.
  // exp(-2*min(a,b)*r).  In the ctor initializer list the "a" parameter is
  // always set to the minimum, so we should sample from exp(-2*a*r).
  double r, prob;
  do {
    // Sample a uniform random number, u = exp(-2*a*r).
    auto u = random::canonical<double>();
    // Invert to find the actual radius.
    r = -std::log(u) / (2*a_);
    // The acceptance probability is now the radial wavefunction over the
    // envelope function, both evaluated at the proposal radius r.
    // Conveniently, the envelope evaluated at r is just the uniform random
    // number u.
    prob = std::pow(std::exp(-a_*r) - std::exp(-b_*r), 2) / u;
  } while (prob < random::canonical<double>());

  // Now sample spherical rotation angles.
  auto cos_theta = random::cos_theta<double>();
  auto phi = random::phi<double>();

  // And compute the transverse coordinates of one nucleon.
  auto r_sin_theta = r * std::sqrt(1. - cos_theta*cos_theta);
  auto x = r_sin_theta * std::cos(phi);
  auto y = r_sin_theta * std::sin(phi);

  // Place the first nucleon at the sampled coordinates (x, y).
  set_nucleon_position(*begin(), x, y);
  // Place the second nucleon opposite to the first, at (-x, -y).
  set_nucleon_position(*std::next(begin()), -x, -y);
}

// Extend the W-S dist out to R + 10a; for typical values of (R, a), the
// probability of sampling a nucleon beyond this radius is O(10^-5).
WoodsSaxonNucleus::WoodsSaxonNucleus(std::size_t A, double R, double a)
    : Nucleus(A),
      R_(R),
      a_(a),
      woods_saxon_dist_(1000, 0., R + 10.*a,
        [R, a](double r) { return r*r/(1.+std::exp((r-R)/a)); })
{}

/// Return something a bit smaller than the true maximum radius.  The
/// Woods-Saxon distribution falls off very rapidly (exponentially), and since
/// this radius determines the impact parameter range, the true maximum radius
/// would cause far too many events with zero participants.
double WoodsSaxonNucleus::radius() const {
  return R_ + 3.*a_;
}

/// Sample uncorrelated Woods-Saxon nucleon positions.
void WoodsSaxonNucleus::sample_nucleons_impl() {
  for (auto&& nucleon : *this) {
    // Sample spherical radius from Woods-Saxon distribution.
    auto r = woods_saxon_dist_(random::engine);

    // Sample isotropic spherical angles.
    auto cos_theta = random::cos_theta<double>();
    auto phi = random::phi<double>();

    // Convert to transverse Cartesian coordinates
    auto r_sin_theta = r * std::sqrt(1. - cos_theta*cos_theta);
    auto x = r_sin_theta * std::cos(phi);
    auto y = r_sin_theta * std::sin(phi);

    set_nucleon_position(nucleon, x, y);
  }
  // XXX: re-center nucleon positions?
}

// Set rmax like the non-deformed case (R + 10a), but for the maximum
// "effective" radius.  The numerical coefficients for beta2 and beta4 are the
// approximate values of Y20 and Y40 at theta = 0.
DeformedWoodsSaxonNucleus::DeformedWoodsSaxonNucleus(
    std::size_t A, double R, double a, double beta2, double beta4)
    : Nucleus(A),
      R_(R),
      a_(a),
      beta2_(beta2),
      beta4_(beta4),
      rmax_(R*(1. + .63*std::fabs(beta2) + .85*std::fabs(beta4)) + 10.*a)
{}

/// Return something a bit smaller than the true maximum radius.  The
/// Woods-Saxon distribution falls off very rapidly (exponentially), and since
/// this radius determines the impact parameter range, the true maximum radius
/// would cause far too many events with zero participants.
double DeformedWoodsSaxonNucleus::radius() const {
  return rmax_ - 7.*a_;
}

double DeformedWoodsSaxonNucleus::deformed_woods_saxon_dist(
    double r, double cos_theta) const {
  auto cos_theta_sq = cos_theta*cos_theta;

  // spherical harmonics
  using math::double_constants::one_div_root_pi;
  auto Y20 = std::sqrt(5)/4. * one_div_root_pi * (3.*cos_theta_sq - 1.);
  auto Y40 = 3./16. * one_div_root_pi *
             (35.*cos_theta_sq*cos_theta_sq - 30.*cos_theta_sq + 3.);

  // "effective" radius
  auto Reff = R_ * (1. + beta2_*Y20 + beta4_*Y40);

  return 1. / (1. + std::exp((r - Reff) / a_));
}

/// Sample uncorrelated deformed Woods-Saxon nucleon positions.
void DeformedWoodsSaxonNucleus::sample_nucleons_impl() {
  // The deformed W-S distribution is defined so the symmetry axis is aligned
  // with the Z axis, so e.g. the long axis of uranium coincides with Z.
  //
  // After sampling positions, they must be randomly rotated.  In general this
  // requires three Euler rotations, but in this case we only need two
  // because there is no use in rotating about the nuclear symmetry axis.
  //
  // The two rotations are:
  //  - a polar "tilt", i.e. rotation about the X axis
  //  - an azimuthal "spin", i.e. rotation about the original Z axis

  // "tilt" angle
  const auto cos_a = random::cos_theta<double>();
  const auto sin_a = std::sqrt(1. - cos_a*cos_a);

  // "spin" angle
  const auto angle_b = random::phi<double>();
  const auto cos_b = std::cos(angle_b);
  const auto sin_b = std::sin(angle_b);

  for (auto&& nucleon : *this) {
    // Sample (r, theta) using a standard rejection method.
    // Remember to include the phase-space factors.
    double r, cos_theta;
    do {
      r = rmax_ * std::cbrt(random::canonical<double>());
      cos_theta = random::cos_theta<double>();
    } while (random::canonical<double>() > deformed_woods_saxon_dist(r, cos_theta));

    // Sample azimuthal angle.
    auto phi = random::phi<double>();

    // Convert to Cartesian coordinates.
    auto r_sin_theta = r * std::sqrt(1. - cos_theta*cos_theta);
    auto x = r_sin_theta * std::cos(phi);
    auto y = r_sin_theta * std::sin(phi);
    auto z = r * cos_theta;

    // Rotate.
    // The rotation formula was derived by composing the "tilt" and "spin"
    // rotations described above.
    auto x_rot = x*cos_b - y*cos_a*sin_b + z*sin_a*sin_b;
    auto y_rot = x*sin_b + y*cos_a*cos_b - z*sin_a*cos_b;

    set_nucleon_position(nucleon, x_rot, y_rot);
  }
}

}  // namespace glauber


#ifndef PYTHONIC_INCLUDE_NUMPY_LOG2_HPP
#define PYTHONIC_INCLUDE_NUMPY_LOG2_HPP

#include "pythonic/include/utils/functor.hpp"
#include "pythonic/include/types/ndarray.hpp"
#include "pythonic/include/utils/numpy_traits.hpp"
#include <boost/simd/function/log2.hpp>

PYTHONIC_NS_BEGIN

namespace numpy
{
  namespace wrapper
  {
    template <class T>
    std::complex<T> log2(std::complex<T> const &val)
    {
      return std::log(val) / std::log(2);
    }
    template <class T>
    auto log2(T const &val) -> decltype(boost::simd::log2(val))
    {
      return boost::simd::log2(val);
    }
  }
#define NUMPY_NARY_FUNC_NAME log2
#define NUMPY_NARY_FUNC_SYM wrapper::log2
#include "pythonic/include/types/numpy_nary_expr.hpp"
}
PYTHONIC_NS_END

#endif


//  Copyright (c) 2006 Xiaogang Zhang
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//  History:
//  XZ wrote the original of this file as part of the Google
//  Summer of Code 2006.  JM modified it to fit into the
//  Boost.Math conceptual framework better, and to correctly
//  handle the p < 0 case.
//

#ifndef BOOST_MATH_ELLINT_RJ_HPP
#define BOOST_MATH_ELLINT_RJ_HPP

#include <boost/math/special_functions/math_fwd.hpp>
#include <boost/math/tools/config.hpp>
#include <boost/math/policies/error_handling.hpp>
#include <boost/math/special_functions/ellint_rc.hpp>

// Carlson's elliptic integral of the third kind
// R_J(x, y, z, p) = 1.5 * \int_{0}^{\infty} (t+p)^{-1} [(t+x)(t+y)(t+z)]^{-1/2} dt
// Carlson, Numerische Mathematik, vol 33, 1 (1979)

namespace boost { namespace math { namespace detail{

template <typename T, typename Policy>
T ellint_rj_imp(T x, T y, T z, T p, const Policy& pol)
{
    T value, u, lambda, alpha, beta, sigma, factor, tolerance;
    T X, Y, Z, P, EA, EB, EC, E2, E3, S1, S2, S3;
    unsigned long k;

    BOOST_MATH_STD_USING
    using namespace boost::math::tools;

    static const char* function = "boost::math::ellint_rj<%1%>(%1%,%1%,%1%)";

    if (x < 0)
    {
       return policies::raise_domain_error<T>(function,
            "Argument x must be non-negative, but got x = %1%", x, pol);
    }
    if(y < 0)
    {
       return policies::raise_domain_error<T>(function,
            "Argument y must be non-negative, but got y = %1%", y, pol);
    }
    if(z < 0)
    {
       return policies::raise_domain_error<T>(function,
            "Argument z must be non-negative, but got z = %1%", z, pol);
    }
    if(p == 0)
    {
       return policies::raise_domain_error<T>(function,
            "Argument p must not be zero, but got p = %1%", p, pol);
    }
    if (x + y == 0 || y + z == 0 || z + x == 0)
    {
       return policies::raise_domain_error<T>(function,
            "At most one argument can be zero, "
            "only possible result is %1%.", std::numeric_limits<T>::quiet_NaN(), pol);
    }

    // error scales as the 6th power of tolerance
    tolerance = pow(T(1) * tools::epsilon<T>() / 3, T(1) / 6);

    // for p < 0, the integral is singular, return Cauchy principal value
    if (p < 0)
    {
       //
       // We must ensure that (z - y) * (y - x) is positive.
       // Since the integral is symmetrical in x, y and z
       // we can just permute the values:
       //
       if(x > y)
          std::swap(x, y);
       if(y > z)
          std::swap(y, z);
       if(x > y)
          std::swap(x, y);

       T q = -p;
       T pmy = (z - y) * (y - x) / (y + q);  // p - y

       BOOST_ASSERT(pmy >= 0);

       T p = pmy + y;
       value = boost::math::ellint_rj(x, y, z, p, pol);
       value *= pmy;
       value -= 3 * boost::math::ellint_rf(x, y, z, pol);
       value += 3 * sqrt((x * y * z) / (x * z + p * q)) * boost::math::ellint_rc(x * z + p * q, p * q, pol);
       value /= (y + q);
       return value;
    }

    // duplication
    sigma = 0;
    factor = 1;
    k = 1;
    do
    {
        u = (x + y + z + p + p) / 5;
        X = (u - x) / u;
        Y = (u - y) / u;
        Z = (u - z) / u;
        P = (u - p) / u;

        if ((tools::max)(abs(X), abs(Y), abs(Z), abs(P)) < tolerance)
           break;

        T sx = sqrt(x);
        T sy = sqrt(y);
        T sz = sqrt(z);

        lambda = sy * (sx + sz) + sz * sx;
        alpha = p * (sx + sy + sz) + sx * sy * sz;
        alpha *= alpha;
        beta = p * (p + lambda) * (p + lambda);
        sigma += factor * boost::math::ellint_rc(alpha, beta, pol);
        factor /= 4;
        x = (x + lambda) / 4;
        y = (y + lambda) / 4;
        z = (z + lambda) / 4;
        p = (p + lambda) / 4;
        ++k;
    }
    while(k < policies::get_max_series_iterations<Policy>());

    // Check to see if we gave up too soon:
    policies::check_series_iterations(function, k, pol);

    // Taylor series expansion to the 5th order
    EA = X * Y + Y * Z + Z * X;
    EB = X * Y * Z;
    EC = P * P;
    E2 = EA - 3 * EC;
    E3 = EB + 2 * P * (EA - EC);
    S1 = 1 + E2 * (E2 * T(9) / 88 - E3 * T(9) / 52 - T(3) / 14);
    S2 = EB * (T(1) / 6 + P * (T(-6) / 22 + P * T(3) / 26));
    S3 = P * ((EA - EC) / 3 - P * EA * T(3) / 22);
    value = 3 * sigma + factor * (S1 + S2 + S3) / (u * sqrt(u));

    return value;
}

} // namespace detail

template <class T1, class T2, class T3, class T4, class Policy>
inline typename tools::promote_args<T1, T2, T3, T4>::type
   ellint_rj(T1 x, T2 y, T3 z, T4 p, const Policy& pol)
{
   typedef typename tools::promote_args<T1, T2, T3, T4>::type result_type;
   typedef typename policies::evaluation<result_type, Policy>::type value_type;
   return policies::checked_narrowing_cast<result_type, Policy>(
      detail::ellint_rj_imp(
         static_cast<value_type>(x),
         static_cast<value_type>(y),
         static_cast<value_type>(z),
         static_cast<value_type>(p),
         pol), "boost::math::ellint_rj<%1%>(%1%,%1%,%1%,%1%)");
}

template <class T1, class T2, class T3, class T4>
inline typename tools::promote_args<T1, T2, T3, T4>::type
   ellint_rj(T1 x, T2 y, T3 z, T4 p)
{
   return ellint_rj(x, y, z, p, policies::policy<>());
}

}} // namespaces

#endif // BOOST_MATH_ELLINT_RJ_HPP


#include <Eigen/Core>

#include <geos/geom.h>
#include <geos/opBuffer.h>
#include <geos/opDistance.h>

#include <Pita/Node.hpp>
#include <Pita/Context.hpp>
#include <Pita/Vectorizer.hpp>
#include <Pita/Printer.hpp>

namespace cgl
{
	void GetQuadraticBezier(Vector<Eigen::Vector2d>& output, const Eigen::Vector2d& p0, const Eigen::Vector2d& p1, const Eigen::Vector2d& p2, int n, bool includesEndPoint)
	{
		for (int i = 0; i < n; ++i)
		{
			const double t = 1.0*i / n;
			output.push_back(p0*(1.0 - t)*(1.0 - t) + p1 * 2.0*(1.0 - t)*t + p2 * t*t);
		}

		if (includesEndPoint)
		{
			output.push_back(p2);
		}
	}

	void GetCubicBezier(Vector<Eigen::Vector2d>& output, const Eigen::Vector2d& p0, const Eigen::Vector2d& p1, const Eigen::Vector2d& p2, const Eigen::Vector2d& p3, int n, bool includesEndPoint)
	{
		for (int i = 0; i < n; ++i)
		{
			const double t = 1.0*i / n;
			output.push_back(p0*(1.0 - t)*(1.0 - t)*(1.0 - t) + p1 * 3.0*(1.0 - t)*(1.0 - t)*t + p2 * 3.0*(1.0 - t)*t*t + p3 * t*t*t);
		}

		if (includesEndPoint)
		{
			output.push_back(p3);
		}
	}

	bool IsClockWise(const Vector<Eigen::Vector2d>& closedPath)
	{
		double sum = 0;

		for (int i = 0; i + 1 < closedPath.size(); ++i)
		{
			const auto& p1 = closedPath[i];
			//const auto& p2 = closedPath[(i + 1) % closedPath.size()];
			const auto& p2 = closedPath[i + 1];

			sum += (p2.x() - p1.x())*(p2.y() + p1.y());
		}

		{
			const auto& p1 = closedPath[closedPath.size() - 1];
			const auto& p2 = closedPath[0];

			sum += (p2.x() - p1.x())*(p2.y() + p1.y());
		}

		return sum < 0.0;
	}

	bool IsClockWise(const gg::LineString* closedPath)
	{
		double sum = 0;

		for (size_t p = 0; p + 1 < closedPath->getNumPoints(); ++p)
		{
			const gg::Coordinate& p1 = closedPath->getCoordinateN(p);
			const gg::Coordinate& p2 = closedPath->getCoordinateN(p + 1);
			sum += (p2.x - p1.x)*(p2.y + p1.y);
		}
		{
			const gg::Coordinate& p1 = closedPath->getCoordinateN(closedPath->getNumPoints() - 1);
			const gg::Coordinate& p2 = closedPath->getCoordinateN(0);
			sum += (p2.x - p1.x)*(p2.y + p1.y);
		}

		return sum < 0.0;
	}

	std::tuple<bool, std::unique_ptr<gg::Geometry>> IsClockWise(std::unique_ptr<gg::Geometry> pLineString)
	{
		double sum = 0;

		const gg::LineString* closedPath = dynamic_cast<const gg::LineString*>(pLineString.get());

		for (size_t p = 0; p + 1 < closedPath->getNumPoints(); ++p)
		{
			const gg::Coordinate& p1 = closedPath->getCoordinateN(p);
			const gg::Coordinate& p2 = closedPath->getCoordinateN(p + 1);
			sum += (p2.x - p1.x)*(p2.y + p1.y);
		}
		{
			const gg::Coordinate& p1 = closedPath->getCoordinateN(closedPath->getNumPoints() - 1);
			const gg::Coordinate& p2 = closedPath->getCoordinateN(0);
			sum += (p2.x - p1.x)*(p2.y + p1.y);
		}

		return std::make_tuple(sum < 0.0, std::move(pLineString));
	}

	std::string GetGeometryType(gg::Geometry* geometry)
	{
		switch (geometry->getGeometryTypeId())
		{
		case geos::geom::GEOS_POINT:              return "Point";
		case geos::geom::GEOS_LINESTRING:         return "LineString";
		case geos::geom::GEOS_LINEARRING:         return "LinearRing";
		case geos::geom::GEOS_POLYGON:            return "Polygon";
		case geos::geom::GEOS_MULTIPOINT:         return "MultiPoint";
		case geos::geom::GEOS_MULTILINESTRING:    return "MultiLineString";
		case geos::geom::GEOS_MULTIPOLYGON:       return "MultiPolygon";
		case geos::geom::GEOS_GEOMETRYCOLLECTION: return "GeometryCollection";
		}

		return "Unknown";
	}

	gg::Polygon* ToPolygon(const Vector<Eigen::Vector2d>& exterior)
	{
		gg::CoordinateArraySequence pts;

		for (int i = 0; i < exterior.size(); ++i)
		{
			pts.add(gg::Coordinate(exterior[i].x(), exterior[i].y()));
		}

		if (!pts.empty())
		{
			pts.add(pts.front());
		}

		auto factory = gg::GeometryFactory::create();
		return factory->createPolygon(factory->createLinearRing(pts), {});
	}

	gg::LineString* ToLineString(const Vector<Eigen::Vector2d>& exterior)
	{
		gg::CoordinateArraySequence pts;

		for (int i = 0; i < exterior.size(); ++i)
		{
			pts.add(gg::Coordinate(exterior[i].x(), exterior[i].y()));
		}

		auto factory = gg::GeometryFactory::create();
		return factory->createLineString(pts);
	}

	void DebugPrint(const gg::Geometry* geometry)
	{
		CGL_DBG;
		switch (geometry->getGeometryTypeId())
		{
		case geos::geom::GEOS_POINT:
		{
			std::cout << "Point" << std::endl;
			break;
		}
		case geos::geom::GEOS_LINESTRING:
		{
			std::cout << "LineString" << std::endl;
			break;
		}
		case geos::geom::GEOS_LINEARRING:
		{
			std::cout << "LinearRing" << std::endl;
			break;
		}
		case geos::geom::GEOS_POLYGON:
		{
			std::cout << "Polygon" << std::endl;
			break;
		}
		case geos::geom::GEOS_MULTIPOINT:
		{
			std::cout << "MultiPoint" << std::endl;
			break;
		}
		case geos::geom::GEOS_MULTILINESTRING:
		{
			std::cout << "MultiLineString" << std::endl;
			break;
		}
		case geos::geom::GEOS_MULTIPOLYGON:
		{
			std::cout << "MultiPolygon" << std::endl;
			break;
		}
		case geos::geom::GEOS_GEOMETRYCOLLECTION:
		{
			std::cout << "GeometryCollection" << std::endl;
			break;
		}
		default:
		{
			std::cout << "Unknown" << std::endl;
		}
		}
		CGL_DBG;
	}

	Path Path::clone()const
	{
		Path resultPath;

		resultPath.cs = std::make_unique<gg::CoordinateArraySequence>();
		auto& csResult = resultPath.cs;
		auto& distancesResult = resultPath.distances;

		for (size_t i = 0; i < cs->size(); ++i)
		{
			csResult->add(cs->getAt(i));
		}
		distancesResult = distances;

		return std::move(resultPath);
	}

	BaseLineOffset Path::getOffset(double offset)const
	{
		BaseLineOffset result;

		auto it = std::upper_bound(distances.begin(), distances.end(), offset);
		if (it == distances.end())
		{
			const double innerDistance = offset - distances[distances.size() - 2];

			Eigen::Vector2d p0(cs->getAt(cs->size() - 2).x, cs->getAt(cs->size() - 2).y);
			Eigen::Vector2d p1(cs->getAt(cs->size() - 1).x, cs->getAt(cs->size() - 1).y);

			const Eigen::Vector2d v = (p1 - p0);
			const double currentLineLength = sqrt(v.dot(v));
			const double progress = innerDistance / currentLineLength;

			const Eigen::Vector2d targetPos = p0 + v * progress;
			result.x = targetPos.x();
			result.y = targetPos.y();

			const auto n = v.normalized();
			result.angle = rad2deg * atan2(n.y(), n.x());
			result.nx = n.y();
			result.ny = -n.x();
		}
		else
		{
			const int lineIndex = std::distance(distances.begin(), it) - 1;
			const double innerDistance = offset - distances[lineIndex];

			Eigen::Vector2d p0(cs->getAt(lineIndex).x, cs->getAt(lineIndex).y);
			Eigen::Vector2d p1(cs->getAt(lineIndex + 1).x, cs->getAt(lineIndex + 1).y);

			const Eigen::Vector2d v = (p1 - p0);
			const double currentLineLength = sqrt(v.dot(v));
			const double progress = innerDistance / currentLineLength;

			const Eigen::Vector2d targetPos = p0 + v * progress;
			result.x = targetPos.x();
			result.y = targetPos.y();

			const auto n = v.normalized();
			result.angle = rad2deg * atan2(n.y(), n.x());
			result.nx = n.y();
			result.ny = -n.x();
		}

		return result;
	}

	void BoundingRect::add(const Eigen::Vector2d& v)
	{
		if (v.x() < m_min.x())
		{
			m_min.x() = v.x();
		}
		if (v.y() < m_min.y())
		{
			m_min.y() = v.y();
		}
		if (m_max.x() < v.x())
		{
			m_max.x() = v.x();
		}
		if (m_max.y() < v.y())
		{
			m_max.y() = v.y();
		}
	}

	void BoundingRect::add(const Vector<Eigen::Vector2d>& vs)
	{
		for (const auto& v : vs)
		{
			add(v);
		}
	}

	TransformPacked::TransformPacked(const PackedRecord& record)
	{
		double px = 0, py = 0;
		double sx = 1, sy = 1;
		double angle = 0;

		for (const auto& member : record.values)
		{
			const PackedVal& value = member.second.value;
			const auto valOpt = AsOpt<PackedRecord>(value);

			if (valOpt)
			{
				const PackedRecord& childRecord = valOpt.get();
				if (member.first == "pos")
				{
					ReadDoublePacked(px, "x", childRecord);
					ReadDoublePacked(py, "y", childRecord);
				}
				else if (member.first == "scale")
				{
					ReadDoublePacked(sx, "x", childRecord);
					ReadDoublePacked(sy, "y", childRecord);
				}
			}
			else if (member.first == "angle")
			{
				ReadDoublePacked(angle, "angle", record);
			}
		}

		init(px, py, sx, sy, angle);
	}

	void TransformPacked::init(double px, double py, double sx, double sy, double angle)
	{
		const double pi = 3.1415926535;
		const double cosTheta = std::cos(pi*angle / 180.0);
		const double sinTheta = std::sin(pi*angle / 180.0);

		mat <<
			sx * cosTheta, -sy * sinTheta, px,
			sx*sinTheta, sy*cosTheta, py,
			0, 0, 1;
	}

	Eigen::Vector2d TransformPacked::product(const Eigen::Vector2d& v)const
	{
		Eigen::Vector3d xs;
		xs << v.x(), v.y(), 1;
		Eigen::Vector3d result = mat * xs;
		Eigen::Vector2d result2d;
		result2d << result.x(), result.y();
		return result2d;
	}

	void TransformPacked::printMat()const
	{
		std::cout << "Matrix(\n";
		for (int y = 0; y < 3; ++y)
		{
			std::cout << "    ";
			for (int x = 0; x < 3; ++x)
			{
				std::cout << mat(y, x) << " ";
			}
			std::cout << "\n";
		}
		std::cout << ")\n";
	}

	GeometryPtr MakeLine(const Eigen::Vector2d& p0, const Eigen::Vector2d& p1)
	{
		gg::CoordinateArraySequence pts;

		pts.add(gg::Coordinate(p0.x(), p0.y()));
		pts.add(gg::Coordinate(p1.x(), p1.y()));

		auto factory = gg::GeometryFactory::create();

		return ToUnique<GeometryDeleter>(factory->createLineString(pts));
	}
}


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

#include "required_defines.hpp"
#include "performance_measure.hpp"

#include <boost/math/special_functions/cbrt.hpp>

double cbrt_test()
{
   double result = 0;
   double val = 1e-100;
   for(int i = 0; i < 1000; ++i)
   {
      val *= 1.5;
      result += boost::math::cbrt(val);
   }
   return result;
}

BOOST_MATH_PERFORMANCE_TEST(cbrt_test, "cbrt")
{
   double result = cbrt_test();

   consume_result(result);
   set_call_count(1000);
}

double cbrt_pow_test()
{
   double result = 0;
   double val = 1e-100;
   for(int i = 0; i < 1000; ++i)
   {
      val *= 1.5;
      result += std::pow(val, 0.33333333333333333333333333333333);
   }
   return result;
}

BOOST_MATH_PERFORMANCE_TEST(cbrt_pow_test, "cbrt-pow")
{
   double result = cbrt_pow_test();

   consume_result(result);
   set_call_count(1000);
}

#ifdef TEST_CEPHES

extern "C" double cbrt(double);

double cbrt_cephes_test()
{
   double result = 0;
   double val = 1e-100;
   for(int i = 0; i < 1000; ++i)
   {
      val *= 1.5;
      result += ::cbrt(val);
   }
   return result;
}

BOOST_MATH_PERFORMANCE_TEST(cbrt_test, "cbrt-cephes")
{
   double result = cbrt_cephes_test();

   consume_result(result);
   set_call_count(1000);
}

#endif

#if defined(__GNUC__) && (__GNUC__ >= 4)

#include <math.h>

double cbrt_c99_test()
{
   double result = 0;
   double val = 1e-100;
   for(int i = 0; i < 1000; ++i)
   {
      val *= 1.5;
      result += ::cbrt(val);
   }
   return result;
}

BOOST_MATH_PERFORMANCE_TEST(cbrt_c99_test, "cbrt-c99")
{
   double result = cbrt_c99_test();

   consume_result(result);
   set_call_count(1000);
}

#endif


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

#include "catch.hpp"

#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>

#include <Eigen/Core>
#include <boost/math/special_functions/erf.hpp>

#include "green/dielectric_profile/OneLayerTanh.hpp"
#include "green/dielectric_profile/OneLayerErf.hpp"

double tanh_value(double point, double e1, double e2, double w, double c);
double tanh_deriv(double point, double e1, double e2, double w, double c);

double erf_value(double point, double e1, double e2, double w, double c);
double erf_deriv(double point, double e1, double e2, double w, double c);

double distribution(double fMin, double fMax);

SCENARIO("Diffuse permittivity single layers", "[dielectric_profile][one_layer]")
{
    GIVEN("The parameters for a one-layer diffuse permittivity profile")
    {
        std::srand(std::time(0));
        double eps1 = 78.39;
        double eps2 = 1.0;
        double center = 50.0;
        double width = 10.0;

        /*! \class OneLayerTanh
         *  \test \b TanhOneLayerTest tests the evaluation of the one layer hyperbolic tangent profile against the analytic value
         */
        WHEN("the sigmoidal profile is modelled by the hyperbolic tangent function")
        {
            OneLayerTanh diffuse(eps1, eps2, width, center);
            double value = 0.0, deriv = 0.0;
            double point = distribution(0.0, 100.0);
            pcm::tie(value, deriv) = diffuse(point);
            THEN("the value of the profile at a random point is")
            {
                double analytic = tanh_value(point, eps1, eps2, width, center);
                INFO(" The evaluation point is: " << point);
                REQUIRE(value == Approx(analytic));
            }
            AND_THEN("the value of the first derivative at a random point is")
            {
                double analyticDeriv = tanh_deriv(point, eps1, eps2, width, center);
                INFO(" The evaluation point is: " << point);
                REQUIRE(deriv == Approx(analyticDeriv));
            }
        }

        /*! \class OneLayerErf
         *  \test \b TanhOneLayerTest tests the evaluation of the one layer hyperbolic tangent profile against the analytic value
         */
        WHEN("the sigmoidal profile is modelled by the error function")
        {
            OneLayerErf diffuse(eps1, eps2, width, center);
            double value = 0.0, deriv = 0.0;
            double point = distribution(0.0, 100.0);
            pcm::tie(value, deriv) = diffuse(point);
            THEN("the value of the profile at a random point is")
            {
                double analytic = erf_value(point, eps1, eps2, width, center);
                INFO(" The evaluation point is: " << point);
                REQUIRE(value == Approx(analytic));
            }
            AND_THEN("the value of the first derivative at a random point is")
            {
                double analyticDeriv = erf_deriv(point, eps1, eps2, width, center);
                INFO(" The evaluation point is: " << point);
                REQUIRE(deriv == Approx(analyticDeriv));
            }
        }
    }
}

double tanh_value(double point, double e1, double e2, double w, double c)
{
    w /= 6.0;
    double epsPlus = (e1 + e2) / 2.0;
    double epsMinus = (e2 - e1) / 2.0;
    double tanh_r = std::tanh((point - c) / w);
    return (epsPlus + epsMinus * tanh_r);
}
double tanh_deriv(double point, double e1, double e2, double w, double c)
{
    w /= 6.0;
    double factor = (e2 - e1) / (2.0 * w);
    double tanh_r = std::tanh((point - c) / w);
    return (factor * (1 - std::pow(tanh_r, 2)));
}

double erf_value(double point, double e1, double e2, double w, double c)
{
    w /= 6.0;
    double epsPlus = (e1 + e2) / 2.0;
    double epsMinus = (e2 - e1) / 2.0;
    double val = boost::math::erf((point - c) / w);
    return (epsPlus + epsMinus * val);
}
double erf_deriv(double point, double e1, double e2, double w, double c)
{
    w /= 6.0;
    double factor = (e2 - e1) / (w * std::sqrt(M_PI));
    double t = (point - c) / w;
    double val = std::exp(-std::pow(t, 2));
    return (factor * val);
}

double distribution(double fMin, double fMax)
{
    double f = (double)std::rand() / RAND_MAX;
    return fMin + f * (fMax - fMin);
}


#pragma once

#if USE_STAN
#include <stan/math.hpp>
#include <stan/math/fwd.hpp>
#endif

// clang-format off
#ifdef USE_CPPAD
#include <cppad/cg.hpp>
#include "math/cppad/eigen_mat_inv.hpp"
#endif //USE_CPPAD

// clang-format on

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

#include "math/conditionals.hpp"
#include "math/tiny/neural_scalar.hpp"

#include "spatial_vector.hpp"
#undef max
#undef min

namespace tds {

template <typename ScalarT = double>
struct EigenAlgebraT {
  using Index = Eigen::Index;
  using Scalar = ScalarT;
  using EigenAlgebra = EigenAlgebraT<Scalar>;
  using Vector3 = Eigen::Matrix<Scalar, 3, 1>;
  using VectorX = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
  using Matrix3 = Eigen::Matrix<Scalar, 3, 3>;
  using Matrix6 = Eigen::Matrix<Scalar, 6, 6>;
  using Matrix3X = Eigen::Matrix<Scalar, 3, Eigen::Dynamic>;
  using MatrixX = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;
  using Quaternion = Eigen::Quaternion<Scalar>;
  using SpatialVector = tds::SpatialVector<EigenAlgebra>;
  using MotionVector = tds::MotionVector<EigenAlgebra>;
  using ForceVector = tds::ForceVector<EigenAlgebra>;

  template <typename T>
  EIGEN_ALWAYS_INLINE static auto transpose(const T &matrix) {
    return matrix.transpose();
  }

  template <typename T>
  EIGEN_ALWAYS_INLINE static auto inverse(const T &matrix) {
    return matrix.inverse();
  }

  template <typename T>
  EIGEN_ALWAYS_INLINE static auto inverse_transpose(const T &matrix) {
    return matrix.inverse().transpose();
  }

  template <typename T1, typename T2>
  EIGEN_ALWAYS_INLINE static auto cross(const T1 &vector_a,
                                        const T2 &vector_b) {
    return vector_a.cross(vector_b);
  }

  /**
   * V1 = mv(w1, v1)
   * V2 = mv(w2, v2)
   * V1 x V2 = mv(w1 x w2, w1 x v2 + v1 x w2)
   */
  static inline MotionVector cross(const MotionVector &a,
                                   const MotionVector &b) {
    return MotionVector(a.top.cross(b.top),
                        a.top.cross(b.bottom) + a.bottom.cross(b.top));
  }

  /**
   * V = mv(w, v)
   * F = fv(n, f)
   * V x* F = fv(w x n + v x f, w x f)
   */
  static inline ForceVector cross(const MotionVector &a, const ForceVector &b) {
    return ForceVector(a.top.cross(b.top) + a.bottom.cross(b.bottom),
                       a.top.cross(b.bottom));
  }

  EIGEN_ALWAYS_INLINE static Index size(const VectorX &v) { return v.size(); }

  EIGEN_ALWAYS_INLINE static Matrix3X create_matrix_3x(int num_cols) {
    return Matrix3X(3, num_cols);
  }
  EIGEN_ALWAYS_INLINE static MatrixX create_matrix_x(int num_rows,
                                                     int num_cols) {
    return MatrixX(num_rows, num_cols);
  }

  template <typename T>
  EIGEN_ALWAYS_INLINE static int num_rows(const T &matrix) {
    return matrix.rows();
  }

  template <typename T>
  EIGEN_ALWAYS_INLINE static int num_cols(const T &matrix) {
    return matrix.cols();
  }

  EIGEN_ALWAYS_INLINE static Scalar determinant(const Matrix3 &m) {
    return m.determinant();
  }

  EIGEN_ALWAYS_INLINE static Scalar determinant(const MatrixX &m) {
    return m.determinant();
  }

  /**
   * CppAD-friendly matrix inverse operation that assumes the input matrix is
   * positive-definite.
   */
  static void plain_symmetric_inverse(const MatrixX &mat, MatrixX &mat_inv) {
    assert(mat.rows() == mat.cols());
    VectorX diagonal = mat.diagonal();
    mat_inv = mat;
    const int n = mat.rows();
    int i, j, k;
    Scalar sum;
    for (i = 0; i < n; i++) {
      mat_inv(i, i) = one() / diagonal[i];
      for (j = i + 1; j < n; j++) {
        sum = zero();
        for (k = i; k < j; k++) {
          sum -= mat_inv(j, k) * mat_inv(k, i);
        }
        mat_inv(j, i) = sum / diagonal[j];
      }
    }
    for (i = 0; i < n; i++) {
      for (j = i + 1; j < n; j++) {
        mat_inv(i, j) = zero();
      }
    }
    for (i = 0; i < n; i++) {
      mat_inv(i, i) = mat_inv(i, i) * mat_inv(i, i);
      for (k = i + 1; k < n; k++) {
        mat_inv(i, i) += mat_inv(k, i) * mat_inv(k, i);
      }
      for (j = i + 1; j < n; j++) {
        for (k = j; k < n; k++) {
          mat_inv(i, j) += mat_inv(k, i) * mat_inv(k, j);
        }
      }
    }
    for (i = 0; i < n; i++) {
      for (j = 0; j < i; j++) {
        mat_inv(i, j) = mat_inv(j, i);
      }
    }
  }

  /**
   * Returns true if the matrix `mat` is positive-definite, and assigns
   * `mat_inv` to the inverse of mat.
   * `mat` must be a symmetric matrix.
   */
  static bool symmetric_inverse(const MatrixX &mat, MatrixX &mat_inv) {
    if constexpr (!is_cppad_scalar<Scalar>::value) {
      Eigen::LLT<MatrixX> llt(mat);
      if (llt.info() == Eigen::NumericalIssue) {
        return false;
      }
      mat_inv = mat.inverse();
    } else {
      plain_symmetric_inverse(mat, mat_inv);
      // // FIXME the atomic op needs to remain in memory but it will fail when
      // the
      // // dimensions of the input matrix are not always the same
      // using InnerScalar = typename Scalar::value_type;
      // static atomic_eigen_mat_inv<InnerScalar> mat_inv_op;
      // mat_inv = mat_inv_op.op(mat);
    }
    return true;
  }

  /**
   * V = mv(w, v)
   * F = mv(n, f)
   * V.F = w.n + v.f
   */
  EIGEN_ALWAYS_INLINE static Scalar dot(const MotionVector &a,
                                        const ForceVector &b) {
    return a.top.dot(b.top) + a.bottom.dot(b.bottom);
  }
  EIGEN_ALWAYS_INLINE static Scalar dot(const ForceVector &a,
                                        const MotionVector &b) {
    return dot(b, a);
  }

  template <typename T1, typename T2>
  EIGEN_ALWAYS_INLINE static auto dot(const T1 &vector_a, const T2 &vector_b) {
    return vector_a.dot(vector_b);
  }

  TINY_INLINE static Scalar norm(const MotionVector &v) {
    using std::sqrt;
    return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3] +
                v[4] * v[4] + v[5] * v[5]);
  }
  TINY_INLINE static Scalar norm(const ForceVector &v) {
    using std::sqrt;
    return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2] + v[3] * v[3] +
                v[4] * v[4] + v[5] * v[5]);
  }

  template <typename T>
  EIGEN_ALWAYS_INLINE static Scalar norm(const T &v) {
    return v.norm();
  }
  template <typename T>
  EIGEN_ALWAYS_INLINE static Scalar sqnorm(const T &v) {
    return v.squaredNorm();
  }

  template <typename T>
  EIGEN_ALWAYS_INLINE static auto normalize(T &v) {
    v.normalize();
    return v;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 cross_matrix(const Vector3 &v) {
    Matrix3 tmp;
#ifdef TDS_USE_LEFT_ASSOCIATIVE_TRANSFORMS
    tmp << zero(), v[2], -v[1], -v[2], zero(), v[0], v[1], -v[0], zero();
#else
    tmp << zero(), -v[2], v[1], v[2], zero(), -v[0], -v[1], v[0], zero();

#endif
    return tmp;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 zero33() { return Matrix3::Zero(); }

  EIGEN_ALWAYS_INLINE static VectorX zerox(Index size) {
    return VectorX::Zero(size);
  }

  EIGEN_ALWAYS_INLINE static Matrix3 diagonal3(const Vector3 &v) {
    Matrix3 tmp;
    tmp.setZero();
    tmp(0, 0) = v[0];
    tmp(1, 1) = v[1];
    tmp(2, 2) = v[2];
    return tmp;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 diagonal3(const Scalar &v) {
    Matrix3 tmp;
    tmp.setZero();
    tmp(0, 0) = v;
    tmp(1, 1) = v;
    tmp(2, 2) = v;
    return tmp;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 eye3() { return Matrix3::Identity(); }
  EIGEN_ALWAYS_INLINE static void set_identity(Quaternion &quat) {
    quat = Quaternion(Scalar(1.), Scalar(0.), Scalar(0.), Scalar(0.));
  }

  EIGEN_ALWAYS_INLINE static Scalar zero() { return Scalar(0); }
  EIGEN_ALWAYS_INLINE static Scalar one() { return Scalar(1); }
  EIGEN_ALWAYS_INLINE static Scalar two() { return Scalar(2); }
  EIGEN_ALWAYS_INLINE static Scalar half() { return Scalar(0.5); }
  EIGEN_ALWAYS_INLINE static Scalar pi() { return Scalar(M_PI); }
  EIGEN_ALWAYS_INLINE static Scalar fraction(int a, int b) {
    return (Scalar(a)) / b;
  }

  static Scalar scalar_from_string(const std::string &s) {
    return from_double(std::stod(s));
  }

  EIGEN_ALWAYS_INLINE static Vector3 zero3() { return Vector3::Zero(); }
  EIGEN_ALWAYS_INLINE static Vector3 unit3_x() {
    return Vector3(one(), zero(), zero());
  }
  EIGEN_ALWAYS_INLINE static Vector3 unit3_y() {
    return Vector3(zero(), one(), zero());
  }
  EIGEN_ALWAYS_INLINE static Vector3 unit3_z() {
    return Vector3(zero(), zero(), one());
  }

  EIGEN_ALWAYS_INLINE static VectorX segment(const VectorX &vec,
                                             int start_index, int length) {
    return vec.segment(start_index, length);
  }

  EIGEN_ALWAYS_INLINE static MatrixX block(const MatrixX &mat,
                                           int start_row_index,
                                           int start_col_index, int rows,
                                           int cols) {
    return mat.block(start_row_index, start_col_index, rows, cols);
  }

  EIGEN_ALWAYS_INLINE static void assign_block(Matrix3X &output,
                                               const Matrix3 &input, int i,
                                               int j, int m = -1, int n = -1,
                                               int input_i = 0,
                                               int input_j = 0) {
    if (m < 0) m = input.rows();
    if (n < 0) n = input.cols();
    assert(i + m <= output.rows() && j + n <= output.cols());
    assert(input_i + m <= input.rows() && input_j + n <= input.cols());
    for (int ii = 0; ii < m; ++ii) {
      for (int jj = 0; jj < n; ++jj) {
        output(ii + i, jj + j) = input(ii + input_i, jj + input_j);
      }
    }
  }

  EIGEN_ALWAYS_INLINE static void assign_block(Matrix6 &output,
                                               const Matrix3 &input, int i,
                                               int j, int m = -1, int n = -1,
                                               int input_i = 0,
                                               int input_j = 0) {
    if (m < 0) m = input.rows();
    if (n < 0) n = input.cols();
    assert(i + m <= output.rows() && j + n <= output.cols());
    assert(input_i + m <= input.rows() && input_j + n <= input.cols());
    for (int ii = 0; ii < m; ++ii) {
      for (int jj = 0; jj < n; ++jj) {
        output(ii + i, jj + j) = input(ii + input_i, jj + input_j);
      }
    }
  }

  EIGEN_ALWAYS_INLINE static void assign_block(Matrix3 &output,
                                               const Matrix6 &input, int i,
                                               int j, int m = -1, int n = -1,
                                               int input_i = 0,
                                               int input_j = 0) {
    if (m < 0) m = input.rows();
    if (n < 0) n = input.cols();
    assert(i + m <= output.rows() && j + n <= output.cols());
    assert(input_i + m <= input.rows() && input_j + n <= input.cols());
    for (int ii = 0; ii < m; ++ii) {
      for (int jj = 0; jj < n; ++jj) {
        output(ii + i, jj + j) = input(ii + input_i, jj + input_j);
      }
    }
  }

  EIGEN_ALWAYS_INLINE static void assign_block(MatrixX &output,
                                               const MatrixX &input, int i,
                                               int j, int m = -1, int n = -1,
                                               int input_i = 0,
                                               int input_j = 0) {
    if (m < 0) m = input.rows();
    if (n < 0) n = input.cols();
    assert(i + m <= output.rows() && j + n <= output.cols());
    assert(input_i + m <= input.rows() && input_j + n <= input.cols());
    for (int ii = 0; ii < m; ++ii) {
      for (int jj = 0; jj < n; ++jj) {
        output(ii + i, jj + j) = input(ii + input_i, jj + input_j);
      }
    }
  }

  template <int Rows1, int Cols1, int Rows2, int Cols2>
  EIGEN_ALWAYS_INLINE static void assign_block(
      Eigen::Matrix<Scalar, Rows1, Cols1> &output,
      const Eigen::Matrix<Scalar, Rows2, Cols2> &input, int i, int j,
      int m = -1, int n = -1, int input_i = 0, int input_j = 0) {
    if (m < 0) m = input.rows();
    if (n < 0) n = input.cols();
    assert(i + m <= output.rows() && j + n <= output.cols());
    assert(input_i + m <= input.rows() && input_j + n <= input.cols());
    for (int ii = 0; ii < m; ++ii) {
      for (int jj = 0; jj < n; ++jj) {
        output(ii + i, jj + j) = input(ii + input_i, jj + input_j);
      }
    }
  }

  EIGEN_ALWAYS_INLINE static void assign_column(Matrix3 &m, Index i,
                                                const Vector3 &v) {
    m.col(i) = v;
  }

  EIGEN_ALWAYS_INLINE static void assign_column(Matrix3 &m, Index i,
                                                const Matrix6 &v) {
    m.col(i) = v;
  }
  EIGEN_ALWAYS_INLINE static void assign_column(Matrix3X &m, Index i,
                                                const Vector3 &v) {
    m.col(i) = v;
  }

  EIGEN_ALWAYS_INLINE static void assign_column(MatrixX &m, Index i,
                                                const MatrixX &v) {
    m.col(i) = v;
  }

  EIGEN_ALWAYS_INLINE static void assign_column(MatrixX &m, Index i,
                                                const SpatialVector &v) {
    m.block(0, i, 3, 1) = v.top;
    m.block(3, i, 3, 1) = v.bottom;
  }
  template <int Rows, int Cols, typename Derived>
  EIGEN_ALWAYS_INLINE static void assign_column(
      Eigen::Matrix<Scalar, Rows, Cols> &m, Index i,
      const Eigen::DenseBase<Derived> &v) {
    assign_column(m, i, v.eval());
  }

  EIGEN_ALWAYS_INLINE static void assign_row(MatrixX &m, Index i,
                                             const MatrixX &v) {
    m.row(i) = v;
  }

  EIGEN_ALWAYS_INLINE static void assign_row(MatrixX &m, Index i,
                                             const SpatialVector &v) {
    m.block(i, 0, 1, 3) = v.top;
    m.block(i, 3, 1, 3) = v.bottom;
  }

  EIGEN_ALWAYS_INLINE static void assign_horizontal(MatrixX &mat,
                                                    const VectorX &vec,
                                                    int start_row_index,
                                                    int start_col_index) {
    mat.block(start_row_index, start_col_index, 1, vec.rows()) =
        vec.transpose();
  }

  template <int Rows>
  EIGEN_ALWAYS_INLINE static void assign_vertical(
      MatrixX &mat, const Eigen::Matrix<Scalar, Rows, 1> &vec,
      int start_row_index, int start_col_index) {
    mat.block(start_row_index, start_col_index, vec.rows(), 1) = vec;
  }

  template <int Rows, int Cols>
  TINY_INLINE static VectorX mul_transpose(
      const Eigen::Matrix<Scalar, Rows, Cols> &mat,
      const Eigen::Matrix<Scalar, Cols, 1> &vec) {
    return mat.transpose() * vec;
  }
  TINY_INLINE static VectorX mul_transpose(const MatrixX &mat,
                                           const VectorX &vec) {
    return mat.transpose() * vec;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 quat_to_matrix(const Quaternion &quat) {
    // NOTE: Eigen requires quat to be normalized
    return quat.toRotationMatrix();
  }
  EIGEN_ALWAYS_INLINE static Matrix3 quat_to_matrix(const Scalar &x,
                                                    const Scalar &y,
                                                    const Scalar &z,
                                                    const Scalar &w) {
    return Quaternion(w, x, y, z).toRotationMatrix();
  }
  EIGEN_ALWAYS_INLINE static Quaternion matrix_to_quat(const Matrix3 &m) {
    if constexpr (is_cppad_scalar<Scalar>::value) {
      // add epsilon to denominator to prevent division by zero
      const Scalar eps = from_double(1e-6);
      Scalar tr = m(0, 0) + m(1, 1) + m(2, 2);
      Scalar q1[4], q2[4], q3[4], q4[4];
      // if (tr > 0)
      {
        Scalar S = sqrt(abs(tr + 1.0)) * two() + eps;
        q1[0] = fraction(1, 4) * S;
        q1[1] = (m(2, 1) - m(1, 2)) / S;
        q1[2] = (m(0, 2) - m(2, 0)) / S;
        q1[3] = (m(1, 0) - m(0, 1)) / S;
      }
      // else if ((m(0,0) > m(1,1))&(m(0,0) > m(2,2)))
      {
        Scalar S = sqrt(abs(1.0 + m(0, 0) - m(1, 1) - m(2, 2))) * two() + eps;
        q2[0] = (m(2, 1) - m(1, 2)) / S;
        q2[1] = fraction(1, 4) * S;
        q2[2] = (m(0, 1) + m(1, 0)) / S;
        q2[3] = (m(0, 2) + m(2, 0)) / S;
      }
      // else if (m(1,1) > m(2,2))
      {
        Scalar S = sqrt(abs(1.0 + m(1, 1) - m(0, 0) - m(2, 2))) * two() + eps;
        q3[0] = (m(0, 2) - m(2, 0)) / S;
        q3[1] = (m(0, 1) + m(1, 0)) / S;
        q3[2] = fraction(1, 4) * S;
        q3[3] = (m(1, 2) + m(2, 1)) / S;
      }
      // else
      {
        Scalar S = sqrt(abs(1.0 + m(2, 2) - m(0, 0) - m(1, 1))) * two() + eps;
        q4[0] = (m(1, 0) - m(0, 1)) / S;
        q4[1] = (m(0, 2) + m(2, 0)) / S;
        q4[2] = (m(1, 2) + m(2, 1)) / S;
        q4[3] = fraction(1, 4) * S;
      }
      Quaternion q;
      // (m(0,0) > m(1,1))&(m(0,0) > m(2,2))
      Scalar m00_is_max = where_gt(
          m(0, 0), m(1, 1), where_gt(m(0, 0), m(2, 2), one(), zero()), zero());
      Scalar m11_is_max =
          (one() - m00_is_max) * where_gt(m(1, 1), m(2, 2), one(), zero());
      Scalar m22_is_max = (one() - m00_is_max) * (one() - m11_is_max);
      q.w() = where_gt(
          tr, zero(), q1[0],
          m00_is_max * q2[0] + m11_is_max * q3[0] + m22_is_max * q4[0]);
      q.x() = where_gt(
          tr, zero(), q1[1],
          m00_is_max * q2[1] + m11_is_max * q3[1] + m22_is_max * q4[1]);
      q.y() = where_gt(
          tr, zero(), q1[2],
          m00_is_max * q2[2] + m11_is_max * q3[2] + m22_is_max * q4[2]);
      q.z() = where_gt(
          tr, zero(), q1[3],
          m00_is_max * q2[3] + m11_is_max * q3[3] + m22_is_max * q4[3]);
      return q;
    } else {
      return Quaternion(m);
    }
  }
  EIGEN_ALWAYS_INLINE static Quaternion axis_angle_quaternion(
      const Vector3 &axis, const Scalar &angle) {
    return Quaternion(Eigen::AngleAxis(angle, axis));
  }

  EIGEN_ALWAYS_INLINE static Matrix3 rotation_x_matrix(const Scalar &angle) {
    using std::cos, std::sin;
    Scalar c = cos(angle);
    Scalar s = sin(angle);
    Matrix3 temp;
#ifdef TDS_USE_LEFT_ASSOCIATIVE_TRANSFORMS
    temp << one(), zero(), zero(), zero(), c, s, zero(), -s, c;
#else
    temp << one(), zero(), zero(), zero(), c, -s, zero(), s, c;
#endif
    //std::cout << "rot_x" << temp << std::endl;
    return temp;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 rotation_y_matrix(const Scalar &angle) {
    using std::cos, std::sin;
    Scalar c = cos(angle);
    Scalar s = sin(angle);
    Matrix3 temp;
#ifdef TDS_USE_LEFT_ASSOCIATIVE_TRANSFORMS
    temp << c, zero(), -s, zero(), one(), zero(), s, zero(), c;
#else
    temp << c, zero(), s, zero(), one(), zero(), -s, zero(), c;
#endif
    return temp;
  }

  EIGEN_ALWAYS_INLINE static Matrix3 rotation_z_matrix(const Scalar &angle) {
    using std::cos, std::sin;
    Scalar c = cos(angle);
    Scalar s = sin(angle);
    Matrix3 temp;
#ifdef TDS_USE_LEFT_ASSOCIATIVE_TRANSFORMS
    temp << c, s, zero(), -s, c, zero(), zero(), zero(), one();
#else
    temp << c, -s, zero(), s, c, zero(), zero(), zero(), one();
#endif
    return temp;
  }

  static Matrix3 rotation_zyx_matrix(const Scalar &r, const Scalar &p,
                                     const Scalar &y) {
    using std::cos, std::sin;
    Scalar ci(cos(r));
    Scalar cj(cos(p));
    Scalar ch(cos(y));
    Scalar si(sin(r));
    Scalar sj(sin(p));
    Scalar sh(sin(y));
    Scalar cc = ci * ch;
    Scalar cs = ci * sh;
    Scalar sc = si * ch;
    Scalar ss = si * sh;
    Matrix3 temp;
    temp << cj * ch, sj * sc - cs, sj * cc + ss, cj * sh, sj * ss + cc,
        sj * cs - sc, -sj, cj * si, cj * ci;
    return temp;
  }

  EIGEN_ALWAYS_INLINE static Vector3 rotate(const Quaternion &q,
                                            const Vector3 &v) {
    return q * v;
  }

  /**
   * Computes the quaternion delta given current rotation q, angular velocity w,
   * time step dt.
   */
  EIGEN_ALWAYS_INLINE static Quaternion quat_velocity(const Quaternion &q,
                                                      const Vector3 &w,
                                                      const Scalar &dt) {
    Quaternion delta((-q.x() * w[0] - q.y() * w[1] - q.z() * w[2]) * (0.5 * dt),
                     (q.w() * w[0] + q.y() * w[2] - q.z() * w[1]) * (0.5 * dt),
                     (q.w() * w[1] + q.z() * w[0] - q.x() * w[2]) * (0.5 * dt),
                     (q.w() * w[2] + q.x() * w[1] - q.y() * w[0]) * (0.5 * dt));
    return delta;
  }

  EIGEN_ALWAYS_INLINE static void quat_increment(Quaternion &a,
                                                 const Quaternion &b) {
    a.x() += b.x();
    a.y() += b.y();
    a.z() += b.z();
    a.w() += b.w();
  }

  EIGEN_ALWAYS_INLINE static const Scalar &quat_x(const Quaternion &q) {
    return q.x();
  }
  EIGEN_ALWAYS_INLINE static const Scalar &quat_y(const Quaternion &q) {
    return q.y();
  }
  EIGEN_ALWAYS_INLINE static const Scalar &quat_z(const Quaternion &q) {
    return q.z();
  }
  EIGEN_ALWAYS_INLINE static const Scalar &quat_w(const Quaternion &q) {
    return q.w();
  }
  EIGEN_ALWAYS_INLINE static const Quaternion quat_from_xyzw(const Scalar &x,
                                                             const Scalar &y,
                                                             const Scalar &z,
                                                             const Scalar &w) {
    // Eigen specific constructor coefficient order
    return Quaternion(w, x, y, z);
  }

  EIGEN_ALWAYS_INLINE static void set_zero(Matrix3X &m) { m.setZero(); }
  EIGEN_ALWAYS_INLINE static void set_zero(Vector3 &m) { m.setZero(); }
  EIGEN_ALWAYS_INLINE static void set_zero(VectorX &m) { m.setZero(); }

  EIGEN_ALWAYS_INLINE static void set_zero(MatrixX &m) { m.setZero(); }
  template <int Size1, int Size2 = 1>
  EIGEN_ALWAYS_INLINE static void set_zero(
      Eigen::Array<Scalar, Size1, Size2> &v) {
    v.setZero();
  }
  EIGEN_ALWAYS_INLINE static void set_zero(MotionVector &v) {
    v.top.setZero();
    v.bottom.setZero();
  }
  EIGEN_ALWAYS_INLINE static void set_zero(ForceVector &v) {
    v.top.setZero();
    v.bottom.setZero();
  }

  /**
   * Non-differentiable comparison operator.
   */
  TINY_INLINE static bool is_zero(const Scalar &a) { return a == zero(); }

  /**
   * Non-differentiable comparison operator.
   */
  EIGEN_ALWAYS_INLINE static bool less_than(const Scalar &a, const Scalar &b) {
    return a < b;
  }

  /**
   * Non-differentiable comparison operator.
   */
  EIGEN_ALWAYS_INLINE static bool less_than_zero(const Scalar &a) {
    return a < 0.;
  }

  /**
   * Non-differentiable comparison operator.
   */
  EIGEN_ALWAYS_INLINE static bool greater_than_zero(const Scalar &a) {
    return a > 0.;
  }

  /**
   * Non-differentiable comparison operator.
   */
  EIGEN_ALWAYS_INLINE static bool greater_than(const Scalar &a,
                                               const Scalar &b) {
    return a > b;
  }

  /**
   * Non-differentiable comparison operator.
   */
  EIGEN_ALWAYS_INLINE static bool equals(const Scalar &a, const Scalar &b) {
    return a == b;
  }

#ifdef USE_STAN
  template <typename InnerScalar>
  TINY_INLINE static std::enable_if_t<
      !std::is_same_v<Scalar, stan::math::fvar<InnerScalar>>, double>
  to_double(const stan::math::fvar<InnerScalar> &s) {
    return stan::math::value_of(s);
  }
#endif

  TINY_INLINE static double to_double(const Scalar &s) {
#ifdef USE_STAN
    if constexpr (std::is_same_v<Scalar, stan::math::var> ||
                  std::is_same_v<Scalar, stan::math::fvar<double>>) {
      return stan::math::value_of(s);
    } else
#endif
#ifdef USE_CPPAD
        if constexpr (std::is_same_v<std::remove_cv_t<Scalar>,
                                     CppAD::AD<CppAD::cg::CG<double>>>) {
      return CppAD::Value(CppAD::Var2Par(s)).getValue();
    } else if constexpr (std::is_same_v<std::remove_cv_t<Scalar>,
                                        CppAD::AD<double>>) {
      return CppAD::Value(CppAD::Var2Par(s));
    } else
#endif //USE_CPPAD
    {
      return static_cast<double>(s);
    }
  }

  TINY_INLINE static Scalar from_double(double s) {
    return static_cast<Scalar>(s);
  }

  template <int Size1, int Size2>
  static void print(const std::string &title,
                    Eigen::Matrix<Scalar, Size1, Size2> &m) {
    std::cout << title << "\n" << m << std::endl;
  }
  template <int Size1, int Size2 = 1>
  static void print(const std::string &title,
                    Eigen::Array<Scalar, Size1, Size2> &v) {
    std::cout << title << "\n" << v << std::endl;
  }
  static void print(const std::string &title, const Scalar &v) {
    std::cout << title << "\n" << to_double(v) << std::endl;
  }
  template <typename T>
  static void print(const std::string &title, const T &abi) {
    abi.print(title.c_str());
  }

  template <typename T>
  TINY_INLINE static auto sin(const T &s) {
    using std::sin;
    return sin(s);
  }

  template <typename T>
  TINY_INLINE static auto cos(const T &s) {
    using std::cos;
    return cos(s);
  }

  template <typename T>
  TINY_INLINE static auto tan(const T &s) {
    using std::tan;
    return tan(s);
  }

  template <typename T>
  TINY_INLINE static auto atan2(const T &dy, const T &dx) {
    using std::atan2;
    return atan2(dy, dx);
  }

  template <typename T>
  TINY_INLINE static auto abs(const T &s) {
    using std::abs;
    return abs(s);
  }

  template <typename T>
  TINY_INLINE static auto sqrt(const T &s) {
    using std::sqrt;
    return sqrt(s);
  }

  template <typename T>
  TINY_INLINE static auto tanh(const T &s) {
    using std::tanh;
    return tanh(s);
  }

  template <typename T>
  TINY_INLINE static auto pow(const T &s, const T &e) {
    using std::pow;
    return pow(s, e);
  }

  template <typename T>
  TINY_INLINE static auto exp(const T &s) {
    using std::exp;
    return exp(s);
  }

  template <typename T>
  TINY_INLINE static auto log(const T &s) {
    using std::log;
    return log(s);
  }

  template <typename T>
  TINY_INLINE static auto max(const T &x, const T &y) {
    return tds::where_gt(x, y, x, y);
  }

  template <typename T>
  TINY_INLINE static auto min(const T &x, const T &y) {
    return tds::where_lt(x, y, x, y);
  }

  EigenAlgebraT<Scalar>() = delete;
};

typedef EigenAlgebraT<double> EigenAlgebra;

// Helpers for NeuralAlgebra
#ifdef USE_CPPAD
template <typename Scalar>
struct is_cppad_scalar<NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>> {
  static constexpr bool value = true;
};

template <typename Scalar>
static TINY_INLINE NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>> where_gt(
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& x,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& y,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_true,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_false) {
    return CppAD::CondExpGt(x.evaluate(), y.evaluate(), if_true.evaluate(),
        if_false.evaluate());
}

template <typename Scalar>
static TINY_INLINE NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>> where_ge(
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& x,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& y,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_true,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_false) {
    return CppAD::CondExpGe(x.evaluate(), y.evaluate(), if_true.evaluate(),
        if_false.evaluate());
}

template <typename Scalar>
static TINY_INLINE NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>> where_lt(
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& x,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& y,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_true,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_false) {
    return CppAD::CondExpLt(x.evaluate(), y.evaluate(), if_true.evaluate(),
        if_false.evaluate());
}

template <typename Scalar>
static TINY_INLINE NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>> where_le(
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& x,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& y,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_true,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_false) {
    return CppAD::CondExpLe(x.evaluate(), y.evaluate(), if_true.evaluate(),
        if_false.evaluate());
}

template <typename Scalar>
static TINY_INLINE NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>> where_eq(
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& x,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& y,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_true,
    const NeuralScalar<EigenAlgebraT<CppAD::AD<Scalar>>>& if_false) {
    return CppAD::CondExpEq(x.evaluate(), y.evaluate(), if_true.evaluate(),
        if_false.evaluate());
}
#endif //USE_CPPAD

}  // end namespace tds


#ifndef SAXQUANTIZER_HPP
#define SAXQUANTIZER_HPP

#include "../CAPCA.h"
#include <deque>
#include <vector>
#include <cassert>
#include <boost/math/distributions/normal.hpp>

using std::deque;
using std::vector;
using namespace std;
namespace SaxQuantizer {

	inline void fill_cutpoints(const size_t& alphabet_size, vector<double> *cutpoints) {
		assert(alphabet_size > 0);
		static boost::math::normal dist(0.0, 1.0);
		//std::cout << "alphabet: " << alphabet_size << std::endl;
		cutpoints->reserve(alphabet_size);
		cutpoints->push_back(-DBL_MAX);
		//cout << "cdf: ";
		for (size_t i = 1; i < alphabet_size; ++i) {
			double cdf = ((double)i) / alphabet_size;
			//cout << quantile(dist, cdf) << " ";
			cutpoints->push_back(quantile(dist, cdf));
			//cout << cutpoints->begin();
		}
		//cout << endl;
	}

	/**
	 * Symbolic Aggregate Approximation with fractional sliding window, numerosity reduction, and scaling
	 */
	class SAX : virtual public APCA_QUAL {
	private:
		size_t m_window_size;
		size_t m_string_size;// the number of segments, N .
		size_t m_alphabet_size;

		double m_baseline_mean;
		double m_baseline_stdev;
		vector<double> m_cutpoints;

		bool m_trained;

		size_t segment_number;//200923

		/**
		 * SAX with fractional sliding window and automatic scaling.
		 * @param <it>: start iterator
		 * @param <end>: end iterator
		 * @param <syms> (out): quantized range
		 */
		template<class Iter>
		void saxify(Iter it, const Iter end, vector<int> *syms) {
			// perform PAA using a fractional sliding window
		   // double paa[m_string_size];
			double* paa = new double[m_string_size];
			double paa_window = ((double)m_window_size) / m_string_size;

			double p = 0; // p for progress
			double w = 1; // w for weight
			size_t available = 0;

			for (size_t i = 0; i < m_string_size && it != end; ++i, ++available) {
				// normalize around baseline

				double normalized = (*it - m_baseline_mean) / m_baseline_stdev;

				paa[i] = 0;
				double j = 0;

				while (j < paa_window && it != end) {

					paa[i] += w * normalized; // sum of (partial) elements inside the window
					j += w;
					p += w;

					// window full
					if (paa_window == p) {

						if (fabs(w - 1.0) <= 0.01) {   // if last element fully consumed,
							++it;                        // then just move next.
						}
						else {                       // o.w.,
							w = 1.0 - w;                 // set remaining portion.
						}
						p = 0;                         // reset progress

					  // window not full, but next must be split
					}
					else if (paa_window - p < 1.0) {

						w = paa_window - p;            // set needed portion
						++it;                          // move to next

					  // window not full, next can be fully consumed
					}
					else {
						++it;                          // move to next
					}
				}

				paa[i] /= j; // averaging
			}

			// map to symbols. 0-based.
			//cout << "available" << available << endl;
			for (size_t i = 0; i < available; ++i) {
				int cnt = -1;
				for (const auto & cp : m_cutpoints) {
					if (paa[i] >= cp) ++cnt;
				}
				syms->push_back(cnt);
			}
			delete[] paa;
		}

	public:
		/**
		* Constructs a SAX quantizer of a given window size, string size and alphabet size.
		* @param <window_size>: sliding window size
		* @param <string_size>: output string size for each sliding window (can be greater than window_size)
		* @param <alphabet_size>: number of codewords
		*/
		SAX(size_t window_size, size_t string_size, size_t alphabet_size)
			: m_window_size(window_size), m_string_size(string_size), m_alphabet_size(alphabet_size),
			m_baseline_mean(0), m_baseline_stdev(1), m_trained(false) {

			assert(window_size > 0);
			assert(string_size > 0);
			assert(alphabet_size > 0);

			fill_cutpoints(alphabet_size, &m_cutpoints);
		}

		/**
		* Constructs a SAX quantizer of timeseries and alphabet size.
		* @param <window_size>: sliding window size
		* @param <string_size>: output string size for each sliding window (can be greater than window_size)
		* @param <alphabet_size>: number of codewords

		*/
		SAX(const size_t& alphabet_size): m_window_size(1), m_string_size(1), m_alphabet_size(alphabet_size),m_baseline_mean(0), m_baseline_stdev(1), m_trained(false) {

			assert(alphabet_size > 0);

			fill_cutpoints(alphabet_size, &m_cutpoints);
		}

		virtual ~SAX() {
			m_cutpoints.clear();
		}

		/**
		 * Trains the quantizer from a given sample. This sets the baseline mean and stdevs, which are used in
		 * normalizing the input.
		 *
		 * @param <samples>: list of training values
		 */
		template<typename Container>
		void train(const Container & samples) {
			double mean = 0;
			double stdev = DBL_MIN;

			assert(!samples.empty());

			if (samples.size() < 2) {
				mean = samples[0];
				stdev = DBL_MIN;

			}
			else {
				size_t n = 0;
				double M2 = 0;
				for (const auto & val : samples) {
					++n;
					double delta = val - mean;
					mean += delta / n;
					M2 += delta * (val - mean);
				}
				stdev = sqrt(M2 / (n - 1));
			}

			if (stdev == 0) stdev = DBL_MIN;

			m_baseline_mean = mean;
			m_baseline_stdev = stdev;

			m_trained = true;
		}

		template<typename Container>
		size_t quantize(const Container & seq, vector<int> *qseq, bool reduce = true) {
			if (!m_trained) train(seq);

			vector<int> buf1, buf2;
			auto *syms_buf = &buf1;
			auto *old_syms_buf = &buf2;

			size_t consumed = 0;
			for (consumed = 0; consumed < seq.size(); ++consumed) {

				if (reduce) { // run-length numerosity reduction
					syms_buf->clear();
					saxify(seq.begin() + consumed, seq.end(), syms_buf);

					// skip window if same as previous
					if (*syms_buf != *old_syms_buf) {
						qseq->insert(qseq->end(), syms_buf->begin(), syms_buf->end());
						std::swap(syms_buf, old_syms_buf);
					}

				}
				else { // no reduction
					saxify(seq.begin() + consumed, seq.end(), qseq);
				}

				// ignore excess elements, if sequence size isn't a multiple of window size
				//if (seq.size() - consumed <= m_window_size) break;
			}

			return consumed;
		}

		/**
		* Constructs a SAX quantizer of timeseries and alphabet size.
		* @param <window_size>: sliding window size
		* @param <string_size>: output string size for each sliding window (can be greater than window_size)
		* @param <alphabet_size>: number of codewords
		* @date : 2018/3/31 15:48
		* @author :
		*/
		template<typename Container>
		size_t getSAX(const Container& seq, vector<char>& qseq) {
			if (!m_trained) train(seq);
			double normalized = NULL;
			for (auto& it : seq) {
				//cout << it << endl;
				normalized = (it - m_baseline_mean) / m_baseline_stdev;
				int cnt = -1;
				//cout << "*******************************************" << endl;
				for (const auto & cp : m_cutpoints) {
					//cout << normalized << ", " << cp << ", " << (normalized > cp ? true:false) << endl;
					if (normalized >= cp) ++cnt;
				}
				qseq.push_back(static_cast<char>(cnt + 65));
			}

			return 0;
		}

		/**
		 * Returns the order of the quantizer (here, the window size)
		 */
		inline size_t order() const {
			return m_window_size;
		}

		inline double ratio() const {
			return ((double)m_window_size) / m_string_size;
		}

		template<typename T, typename Y, typename U>
		void get_SAX(const vector<T>& const original_time_series_vector, const Y& const segment_number, const U& const SAX_container) {

			vector<double> paa_vector;
			vector<int> SAX_quantizer_vector;

			APCA_QUAL::get_PAA(original_time_series_vector, segment_number, SAX_container);

			for (int i = 0; i < segment_number; i++) {
				paa_vector.emplace_back(SAX_container.v[i]);
			}

			quantize(paa_vector, &SAX_quantizer_vector, false);

			copy_n(SAX_quantizer_vector.begin(), SAX_quantizer_vector.size(), SAX_container.v);

		}

		/**
		* @Name: distance_cell
		* @Qualifier: cell(r,c)
		* @Date: 200923
		* @author:
		*/
		template<typename T>
		inline double get_distance_cell(const T& const vaule_1, const T& const vaule_2) {
			if (fabs(vaule_1 - vaule_2) <= 1) {
				return 0;
			}
			else {
				return m_cutpoints[max(vaule_1 + 1, vaule_2 + 1) - 1] - m_cutpoints[min(vaule_1 + 1, vaule_2 + 1)];
			}
		}

		template<typename T>
		double distance_LB_SAX(const T& const SAX_container1, const T& const SAX_container2) {

			const auto& const QProjection = SAX_container1;
			const auto& const italicC = SAX_container2;

			int i = 0, j = 0;
			double distance = 0;

			double sum = (italicC.r[0] + 1) * pow(get_distance_cell(QProjection.v[0], italicC.v[0]), 2);

			for (i = 1; i < italicC.segmentNum; i++) {
				sum += (italicC.r[i] - italicC.r[i - 1]) * pow( get_distance_cell( QProjection.v[i], italicC.v[i] ), 2 );
			}
			distance = sqrt(sum);
			return distance;
		}
	};
}

#endif


#include "../bleichenbacher.h"
#include <NTL/ZZ_p.h>
#include <NTL/ZZ.h>
#include <vector>
#include <tuple>
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;
using namespace NTL;

void loadSigs(vector<tuple<ZZ_p, ZZ_p, ZZ_p>> *rsmTuples);
ZZ MSBguessFromM(int m, int l, ZZ mod);

int main(int argc, char *argv[])
{
	vector<tuple<ZZ_p, ZZ_p, ZZ_p>> rsmTuples;
	vector<tuple<ZZ_p, ZZ_p>> hcPairs;
	vector<tuple<int, double>> mValues;
	ZZ guess;

	/* Initialize NTL modulus for secp160r1.
	   n = FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 7FFFFFFF
	   Citation: http://www.secg.org/SEC2-Ver-1.0.pdf */
	ZZ mod;
	mod = to_ZZ("1461501637330902918203684832716283019653785059327");
	ZZ_p::init(mod);

	/* Load the (r,s,H(m)) tupples */
	cout << "[+] Loading (r,s,H(m)) tupples...\n";
	loadSigs(&rsmTuples);

	/*
	 *	Round 1
	 */

	/* Make (h,c) pairs */
	cout << "[+] Making (h,c) pairs for round 1...\n";
	hcFromRs(&rsmTuples, &hcPairs);

	/* Sort & Diff - we must get |c_i| <= 30 bits */
	cout << "[+] Starting sort & diff for round 1...\n";
	sortAndDiff(&hcPairs, 27, 20);
	cout << "\t" << hcPairs.size() << " pairs left.\n";

	/* Get max m value */
	cout << "[+] Finding top ten bias:MSB guesses for round 1...\n";
	mValues = maxM(&hcPairs, 27);

	for(int i = 0; i < 10; i++)
	{
		guess = MSBguessFromM(get<0>(mValues[i]), 27, mod);
		cout << "\t" << get<1>(mValues[i]) << " : " << guess << "\n";
	}

	cout << "[+] Average bias for round 1: " << avgBias() << "\n";
	cout << "[+] Standard deviation of round 1 bias: " << stdDevBias()
		<< "\n";

	hcPairs.clear();

	/*
	 *	Round 2
	 *
	 *	All rounds after round 1 should look like this.
	 */

	/* In your implementation get the real 20 MSBs from round 1
	results... */

	cout << "[+] Reinjecting correct 20 MSBs for demonstration... :P"
		<< "\n";

	// >>> bin(991662256230238939367140194553270109876310963800)[:22]
	// '0b10101101101100111010'
	// >>> int('10101101101100111010', 2)
	// 711482

	ZZ knownBits;
	knownBits = 711482;

	/* Make (h,c) pairs */
	cout << "[+] Making (h,c) pairs for round 2...\n";
	hcFromRs(&rsmTuples, &hcPairs, 20, knownBits);

	/* Sort & Diff - we must get |c_i| <= 30 bits */
	cout << "[+] Starting sort & diff for round 2...\n";
	sortAndDiff(&hcPairs, 27, 20);
	cout << "\t" << hcPairs.size() << " pairs left.\n";

	/* Get max m value */
	cout << "[+] Finding top ten bias:MSB guesses for round 2...\n";
	mValues = maxM(&hcPairs, 20, 27);

	for(int i = 0; i < 10; i++)
	{
		guess = MSBguessFromM(get<0>(mValues[i]), 27, mod);
		cout << "\t" << get<1>(mValues[i]) << " : " << guess << "\n";
	}

	cout << "[+] Average bias for round 2: " << avgBias() << "\n";
	cout << "[+] Standard deviation of round 2 bias: " << stdDevBias()
		<< "\n";

	hcPairs.clear();
}

ZZ MSBguessFromM(int m, int l, ZZ mod)
{
	ZZ zz_m, guess;

	zz_m = m;
	mul(guess, m, mod);

	guess >>= l;

	return guess;
}

void loadSigs(vector<tuple<ZZ_p, ZZ_p, ZZ_p>> *rsmTuples)
{
	ifstream in("rsmTuples");
	string line;

	while(getline(in, line))
	{
		string r, s, m;
		ZZ zz_r, zz_s, zz_m;
		stringstream lineStream(line);

		lineStream >> r;
		lineStream >> s;
		lineStream >> m;

		conv(zz_r, r.c_str());
		conv(zz_s, s.c_str());
		conv(zz_m, m.c_str());

		rsmTuples->push_back(
			make_tuple(
				to_ZZ_p(zz_r),
				to_ZZ_p(zz_s),
				to_ZZ_p(zz_m)
			)
		);
	}
}


/* Copyright (c) 2021 Grumpy Cat Software S.L.
 *
 * This Source Code is licensed under the MIT 2.0 license.
 * the terms can be found in  LICENSE.md at the root of
 * this project, or at http://mozilla.org/MPL/2.0/.
 */

#include <gauss/internal/scopedHostPtr.h>
#include <gauss/regression.h>
#include <gauss/statistics.h>

#include <boost/math/distributions/students_t.hpp>

constexpr auto EPSILON = 1e-20;

void gauss::regression::linear(const af::array &xss, const af::array &yss, af::array &slope, af::array &intercept,
                               af::array &rvalue, af::array &pvalue, af::array &stderrest) {
    auto n = xss.dims(0);

    af::array meanX = af::mean(xss, 0);
    af::array meanY = af::mean(yss, 0);

    af::array sumSquares = af::array(2, 2, xss.dims(1), xss.type());

    // Assuming xss and yss contain the same number of time series
    for (int i = 0; i < xss.dims(1); i++) {
        sumSquares(af::span, af::span, i) =
            gauss::statistics::covariance(af::join(1, xss(af::span, i), yss(af::span, i)));
    }

    af::array ssxm = sumSquares(0, 0, af::span);
    ssxm = af::reorder(ssxm, 0, 2, 1, 3);
    af::array ssxym = sumSquares(0, 1, af::span);
    ssxym = af::reorder(ssxym, 0, 2, 1, 3);
    af::array ssyxm = sumSquares(1, 0, af::span);
    ssyxm = af::reorder(ssyxm, 0, 2, 1, 3);
    af::array ssym = sumSquares(1, 1, af::span);
    ssym = af::reorder(ssym, 0, 2, 1, 3);

    af::array rNum = ssxym;

    af::array rDen = af::sqrt(ssxm * ssym);

    af::array r = af::transpose(af::constant(0, xss.dims(1), xss.type()));
    r = (rDen > 0.0) * rNum / rDen;
    r = af::min(r, 1.0);
    r = af::max(r, -1.0);
    rvalue = r;

    auto df = n - 2;
    slope = rNum / ssxm;
    intercept = meanY - slope * meanX;

    boost::math::students_t dist(df);

    af::array t = r * af::sqrt(df / ((1.0 - r + EPSILON) * (1.0 + r + EPSILON)));
    // Using boost to compute the CDF of the T-Student distribution
    // It would be better to move this computation to the GPU
    // Converting to af::dtype::f32 and back to the original type later on
    // to avoid templating this function and all the ones using it
    auto aux = gauss::utils::makeScopedHostPtr(af::abs(t).as(af::dtype::f32).host<float>());
    for (long i = 0; i < t.dims(1); i++) {
        aux[i] = 2.0f * (1.0f - static_cast<float>(boost::math::cdf(dist, aux[i])));
    }
    pvalue = af::array(1, t.dims(1), aux.get()).as(xss.type());
    stderrest = af::sqrt((1 - af::pow(r, 2)) * ssym / ssxm / df);
}


/**
 * @file semimprk.cc
 * @brief NPDE homework SemImpRK code
 * @author Unknown, Oliver Rietmann
 * @date 04.04.2021
 * @copyright Developed at ETH Zurich
 */

#include "semimprk.h"

#include <Eigen/Core>
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <vector>

#include "../../../lecturecodes/helperfiles/polyfit.h"

namespace SemImpRK {

/* SAM_LISTING_BEGIN_0 */
double CvgRosenbrock() {
  double cvgRate = 0.0;
  // Use polyfit to estimate the rate of convergence
  // for SolveRosenbrock.
  //====================
  // Your code goes here
  // explore the order of method 7.4.2 empirically by applying it to the IVP for the limit cycle
  // use uniform timesteps for size h=2^(-k), k= 4,...,10, and compute a reference solution yref with
  // timestep size h =2^(-12). Monitor the maximal error on the temporal mesh
  // max||yj-yref(tj)||2 and use it to estimate a rate of algebraic convergence by means of linear regression.

  Eigen::Matrix2d R;
  R << 0.0, -1.0, 1.0, 0.0;
  int lambda =1;
  auto f [R,lambda](Eigen::VectorXd y) -> Eigen::VectorXd{

    return R*y+lambda*(1-y.squaredNorm())*y;
  }

  auto df [R, lambda](Eigen::VectorXd y) {
    Eigen::Matrix2d J;
    J << lambda*(1-y.squaredNorm())-2*lambda*y(0)*y(0), -1-2*lambda*y(0)*y(1), 1-2*lambda*y(0)*y(1), lambda*(1-y.squaredNorm())-2*lambda*y(0)**2;
    return J;
  }

  int M_ref = std::pow(2,12);
  std::vector<Eigen::VectorXd> res_ref = SolveRosenbrock(f, df, y0, M_ref, T);

  Eigen::ArrayXd K= Eigen::ArrayXd::linspace(7,4,10);

  Eigen::ArrayXd Error(K.size());
  Eigen::ArrayXd M(K.size());

  for (int i=0; i<K.size(); i++){
    M[i] = std::pow(2,K[i]);
    std::vector<Eigen::VectorXd> res = SolveRosenbrock(f,df, y0, M[i], T);

    double maxerr =0;

    for(int j=0; j<res.size(); j++){
      maxerr = std::max(maxerr, (res[j]-res_ref[j*M_ref/M[i]].norm());
    }
    Error[i] = maxerr;
  }

  SolveRosenbrock(Func &&f, Jac &&df, const Eigen::VectorXd &y0, unsigned int M, double T)

  //====================
  return cvgRate;
}
/* SAM_LISTING_END_0 */

}  // namespace SemImpRK


// Copyright (C) 2010  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.

#include <dlib/optimization.h>
#include "optimization_test_functions.h"
#include <sstream>
#include <string>
#include <cstdlib>
#include <ctime>
#include <vector>
#include "../rand.h"

#include "tester.h"

namespace
{

    using namespace test;
    using namespace dlib;
    using namespace std;
    using namespace dlib::test_functions;

    logger dlog("test.trust_region");

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

    template <typename T>
    struct neg_rosen_model
    {
        typedef matrix<T,0,1> column_vector;
        typedef matrix<T,0,0> general_matrix;

        T operator() ( column_vector x) const
        {
            return -static_cast<T>(rosen<T>(x));
        }

        void get_derivative_and_hessian (
            const column_vector& x,
            column_vector& d,
            general_matrix& h
        ) const
        {
            d = -matrix_cast<T>(rosen_derivative<T>(x));
            h = -matrix_cast<T>(rosen_hessian<T>(x));
        }

    };

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

    dlib::rand rnd;

    template <typename T>
    void test_with_rosen()
    {
        print_spinner();

        matrix<T,2,1> ans;
        ans = 1,1;

        matrix<T,2,1> p = 100*matrix_cast<T>(randm(2,1,rnd)) - 50;

        T obj = find_min_trust_region(objective_delta_stop_strategy(1e-12, 100), rosen_function_model<T>(), p);

        DLIB_TEST_MSG(std::abs(obj) < 1e-10, "obj: " << obj);
        DLIB_TEST_MSG(length(p-ans) < 1e-5, "length(p): " << length(p-ans));

        matrix<T,0,1> p2 = 100*matrix_cast<T>(randm(2,1,rnd)) - 50;
        obj = find_max_trust_region(objective_delta_stop_strategy(1e-12, 100), neg_rosen_model<T>(), p2);

        DLIB_TEST_MSG(std::abs(obj) < 1e-10, "obj: " << obj);
        DLIB_TEST_MSG(length(p-ans) < 1e-5, "length(p): " << length(p-ans));
    }

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

    void test_trust_region_sub_problem()
    {
        dlog << LINFO << "subproblem test 1";
        {
            matrix<double,2,2> B;
            B = 1, 0,
                0, 1;

            matrix<double,2,1> g, p, ans;
            g = 0;

            ans = 0;

            solve_trust_region_subproblem(B,g,1,p, 0.001, 10);

            DLIB_TEST(length(p-ans) < 1e-10);
            solve_trust_region_subproblem(B,g,1,p, 0.001, 1);
            DLIB_TEST(length(p-ans) < 1e-10);
        }

        dlog << LINFO << "subproblem test 2";
        {
            matrix<double,2,2> B;
            B = 1, 0,
                0, 1;

            B *= 0.1;

            matrix<double,2,1> g, p, ans;
            g = 1;

            ans = -g / length(g);

            solve_trust_region_subproblem(B,g,1,p, 1e-6, 20);

            DLIB_TEST(length(p-ans) < 1e-4);
        }

        dlog << LINFO << "subproblem test 3";
        {
            matrix<double,2,2> B;
            B = 0, 0,
                0, 0;

            matrix<double,2,1> g, p, ans;
            g = 1;

            ans = -g / length(g);

            solve_trust_region_subproblem(B,g,1,p, 1e-6, 20);

            dlog << LINFO << "ans: " << trans(ans);
            dlog << LINFO << "p: " << trans(p);
            DLIB_TEST(length(p-ans) < 1e-4);
        }
        return;

        dlog << LINFO << "subproblem test 4";
        {
            matrix<double,2,2> B;
            B = 2, 0,
                0, -1;

            matrix<double,2,1> g, p, ans;
            g = 0;

            ans = 0, -1;

            solve_trust_region_subproblem(B,g,1,p, 1e-6, 20);

            DLIB_TEST(length(p-ans) < 1e-4);
        }

        dlog << LINFO << "subproblem test 5";
        {
            matrix<double,2,2> B;
            B = 2, 0,
                0, -1;

            matrix<double,2,1> g, p, ans;
            g = 0, 1;

            ans = 0, -1;

            solve_trust_region_subproblem(B,g,1,p, 1e-6, 20);

            DLIB_TEST(length(p-ans) < 1e-4);
        }

        dlog << LINFO << "subproblem test 6";
        for (int i = 0; i < 10; ++i)
        {
            matrix<double,10,10> B;

            B = randm(10,10, rnd);

            B = 0.01*B*trans(B);

            matrix<double,10,1> g, p, ans;
            g = 1;

            solve_trust_region_subproblem(B,g,1,p, 1e-6, 20);

            DLIB_TEST(std::abs(length(p) - 1) < 1e-4);
        }
    }

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

    void test_problems()
    {
        print_spinner();
        {
            matrix<double,4,1> ch;

            ch = brown_start();

            find_min_trust_region(objective_delta_stop_strategy(1e-7, 80),
                                  brown_function_model(),
                                  ch);

            dlog << LINFO << "brown obj: " << brown(ch);
            dlog << LINFO << "brown der: " << length(brown_derivative(ch));
            dlog << LINFO << "brown error: " << length(ch - brown_solution());

            DLIB_TEST(length(ch - brown_solution()) < 1e-5);

        }
        print_spinner();
        {
            matrix<double,2,1> ch;

            ch = rosen_start<double>();

            find_min_trust_region(objective_delta_stop_strategy(1e-7, 80),
                                  rosen_function_model<double>(),
                                  ch);

            dlog << LINFO << "rosen obj: " << rosen(ch);
            dlog << LINFO << "rosen der: " << length(rosen_derivative(ch));
            dlog << LINFO << "rosen error: " << length(ch - rosen_solution<double>());

            DLIB_TEST(length(ch - rosen_solution<double>()) < 1e-5);
        }

        print_spinner();
        {
            matrix<double,0,1> ch;

            ch = chebyquad_start(2);

            find_min_trust_region(objective_delta_stop_strategy(1e-7, 80),
                                  chebyquad_function_model(),
                                  ch);

            dlog << LINFO << "chebyquad 2 obj: " << chebyquad(ch);
            dlog << LINFO << "chebyquad 2 der: " << length(chebyquad_derivative(ch));
            dlog << LINFO << "chebyquad 2 error: " << length(ch - chebyquad_solution(2));

            DLIB_TEST(length(ch - chebyquad_solution(2)) < 1e-5);

        }
        print_spinner();
        {
            matrix<double,0,1> ch;

            ch = chebyquad_start(4);

            find_min_trust_region(objective_delta_stop_strategy(1e-7, 80),
                                  chebyquad_function_model(),
                                  ch);

            dlog << LINFO << "chebyquad 4 obj: " << chebyquad(ch);
            dlog << LINFO << "chebyquad 4 der: " << length(chebyquad_derivative(ch));
            dlog << LINFO << "chebyquad 4 error: " << length(ch - chebyquad_solution(4));

            DLIB_TEST(length(ch - chebyquad_solution(4)) < 1e-5);
        }
        print_spinner();
        {
            matrix<double,0,1> ch;

            ch = chebyquad_start(6);

            find_min_trust_region(objective_delta_stop_strategy(1e-12, 80),
                                  chebyquad_function_model(),
                                  ch);

            dlog << LINFO << "chebyquad 6 obj: " << chebyquad(ch);
            dlog << LINFO << "chebyquad 6 der: " << length(chebyquad_derivative(ch));
            dlog << LINFO << "chebyquad 6 error: " << length(ch - chebyquad_solution(6));

            DLIB_TEST(length(ch - chebyquad_solution(6)) < 1e-5);

        }
        print_spinner();
        {
            matrix<double,0,1> ch;

            ch = chebyquad_start(8);

            find_min_trust_region(objective_delta_stop_strategy(1e-10, 80),
                                  chebyquad_function_model(),
                                  ch);

            dlog << LINFO << "chebyquad 8 obj: " << chebyquad(ch);
            dlog << LINFO << "chebyquad 8 der: " << length(chebyquad_derivative(ch));
            dlog << LINFO << "chebyquad 8 error: " << length(ch - chebyquad_solution(8));

            DLIB_TEST(length(ch - chebyquad_solution(8)) < 1e-5);
        }

    }

    class optimization_tester : public tester
    {
    public:
        optimization_tester (
        ) :
            tester ("test_trust_region",
                    "Runs tests on the trust region optimization component.")
        {}

        void perform_test (
        )
        {
            dlog << LINFO << "test with rosen<float>";
            for (int i = 0; i < 50; ++i)
                test_with_rosen<float>();

            dlog << LINFO << "test with rosen<double>";
            for (int i = 0; i < 50; ++i)
                test_with_rosen<double>();

            test_trust_region_sub_problem();

            test_problems();
        }
    } a;

}