#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>

#include <Eigen/Dense>
using Eigen::Matrix;
using Eigen::Dynamic;

#include "ceres/autodiff_cost_function.h"
#include "ceres/cost_function.h"
#include "ceres/internal/autodiff.h"
#include "ceres/internal/eigen.h"

#include <glog/logging.h>

#include <adept.h>

#include <game/vsr/cga_op.h>

#include <hep/ga.hpp>

#include <iostream>
#include <sstream>
#include <string>

using namespace vsr::cga;
namespace py = pybind11;

template <typename T>
void InnerProductVectorBivector(const T *vec, const T *biv, T *res) {
  Vector<T> vector(vec);
  Bivector<T> bivector(biv);
  Vector<T> result = vector <= bivector;
  for (int i = 0; i < 3; ++i)
    res[i] = result[i];
}

template <typename T>
void InnerProductVectorVector(const T *vec, const T *vec1, T *res) {
  Vector<T> vector(vec);
  Vector<T> vector1(vec1);
  Scalar<T> result = vector <= vector1;
  res[0] = result[0];
}

template <typename T>
void InnerProductVectorBivector2(const T *a, const T *b, T *res) {
  res[0] = -a[2] * b[1] - a[1] * b[0];
  res[1] = -a[2] * b[2] + a[0] * b[0];
  res[2] = a[1] * b[2] + a[0] * b[1];
}

struct InnerProductVectorBivectorFunctor2 {
  template <typename T>
  bool operator()(const T *vec, const T *biv, T *res) const {
    InnerProductVectorBivector2(vec, biv, res);
    return true;
  }
};

py::list AdeptDiffInnerProductVectorBivector(const Vec &vec, const Biv &biv) {
  auto result = py::array(py::buffer_info(
      nullptr,        /* Pointer to data (nullptr -> ask NumPy to allocate!) */
      sizeof(double), /* Size of one item */
      py::format_descriptor<double>::value(), /* Buffer format */
      2,                                      /* How many dimensions? */
      {3, 3},                 /* Number of elements for each dimension */
      {sizeof(double) * 3, 3} /* Strides for each dimension */
      ));

  auto buf = result.request();

  adept::Stack stack;

  double jac[9];
  std::array<adept::adouble, 3> vector{vec[0], vec[1], vec[2]};
  std::array<adept::adouble, 3> bivector{biv[0], biv[1], biv[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> vec_ip_biv{0.0, 0.0, 0.0};

  InnerProductVectorBivector2(vector.begin(), bivector.begin(),
                              vec_ip_biv.begin());

  stack.independent(vector.begin(), 3);
  // stack.independent(bivector.begin(), 3);
  stack.dependent(vec_ip_biv.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf.ptr), true);
  stack.jacobian_reverse(static_cast<double *>(buf.ptr), true);
  // stack.jacobian_forward(static_cast<double *>(buf.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  py::list list;
  list.append(result);
  list.append(py::str(ss.str().c_str()));

  return list;
}

struct InnerProductVectorBivectorFunctor {
  template <typename T>
  bool operator()(const T *vec, const T *biv, T *res) const {
    InnerProductVectorBivector(vec, biv, res);
    return true;
  }
};

struct InnerProductVectorVectorFunctor {
  template <typename T>
  bool operator()(const T *vec, const T *vec2, T *res) const {
    InnerProductVectorVector(vec, vec2, res);
    return true;
  }
};

py::list CeresDiffInnerProductVectorVector(const Vec &vec, const Vec &vec2) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {1, 3}, {sizeof(double) * 3, sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {1, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  const double *parameters[2] = {vec.begin(), vec2.begin()};
  double *jacobians[2] = {static_cast<double *>(buf_jac.ptr), nullptr};

  ceres::AutoDiffCostFunction<InnerProductVectorVectorFunctor, 1, 3, 3>(
      new InnerProductVectorVectorFunctor())
      .Evaluate(parameters, static_cast<double *>(buf_res.ptr), jacobians);

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);

  return list;
}
py::list CeresDiffInnerProductVectorBivector(const Vec &vec, const Biv &biv) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 3}, {sizeof(double) * 3, sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double) * 3, sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  const double *parameters[2] = {vec.begin(), biv.begin()};
  double *jacobians[2] = {static_cast<double *>(buf_jac.ptr), nullptr};

  ceres::AutoDiffCostFunction<InnerProductVectorBivectorFunctor, 3, 3, 3>(
      new InnerProductVectorBivectorFunctor())
      .Evaluate(parameters, static_cast<double *>(buf_res.ptr), jacobians);

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);

  return list;
}

template <typename T> using Matrix4 = Eigen::Matrix<T, 4, 4>;

template <typename T> inline static Matrix4<T> s() {
  Matrix4<T> m;
  m << T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(1), T(0),
      T(0), T(0), T(0), T(1);
  return m;
}

template <typename T> inline static Matrix4<T> e1() {
  Matrix4<T> m;
  m << T(0), T(0), T(0), T(1), T(0), T(0), T(1), T(0), T(0), T(1), T(0), T(0),
      T(1), T(0), T(0), T(0);
  return m;
}

template <typename T> inline static Matrix4<T> e2() {
  Matrix4<T> m;
  m << T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(-1), T(1), T(0), T(0), T(0),
      T(0), T(-1), T(0), T(0);
  return m;
}

template <typename T> inline static Matrix4<T> e3() {
  Matrix4<T> m;
  m << T(1), T(0), T(0), T(0), T(0), T(1), T(0), T(0), T(0), T(0), T(-1), T(0),
      T(0), T(0), T(0), T(-1);
  return m;
}

struct DiffRotorMatrixFunctor {
  template <typename T> bool operator()(const T *th, const T *a, T *b) const {
    Matrix4<T> rotor =
        cos(T(0.5) * th[0]) * s<T>() - sin(T(0.5) * th[0]) * e1<T>() * e2<T>();
    Matrix4<T> rotor_inv =
        cos(T(0.5) * th[0]) * s<T>() + sin(T(0.5) * th[0]) * e1<T>() * e2<T>();
    Matrix4<T> vec_a = a[0] * e1<T>() + a[1] * e2<T>() + a[2] * e3<T>();
    Matrix4<T> vec_b = rotor * vec_a * rotor_inv;
    b[0] = vec_b(0, 3); // e1
    b[1] = vec_b(0, 2); // e2
    b[2] = vec_b(0, 0); // e3
    return true;
  }
};

py::list CeresDiffRotorMatrix(const double theta, const Vec &a) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  const double *parameters[2] = {&theta, a.begin()};
  double *jacobians[2] = {static_cast<double *>(buf_jac.ptr), nullptr};

  ceres::AutoDiffCostFunction<DiffRotorMatrixFunctor, 3, 1, 3>(
      new DiffRotorMatrixFunctor())
      .Evaluate(parameters, static_cast<double *>(buf_res.ptr), jacobians);

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);

  return list;
}

struct DiffRotorVersorFunctor {
  template <typename T> bool operator()(const T *th, const T *a, T *b) const {
    Rotor<T> rotor{cos(T(0.5) * th[0]), -sin(T(0.5) * th[0]), T(0.0), T(0.0)};
    Vector<T> vec_a{a[0], a[1], a[2]};
    Vector<T> vec_b = vec_a.spin(rotor);
    for (int i = 0; i < 3; ++i)
      b[i] = vec_b[i];
    return true;
  }
};
py::list CeresDiffRotorVersor(const double theta, const Vec &a) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  const double *parameters[2] = {&theta, a.begin()};
  double *jacobians[2] = {static_cast<double *>(buf_jac.ptr), nullptr};

  ceres::AutoDiffCostFunction<DiffRotorVersorFunctor, 3, 1, 3>(
      new DiffRotorVersorFunctor())
      .Evaluate(parameters, static_cast<double *>(buf_res.ptr), jacobians);

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);

  return list;
}

py::list AdeptDiffRotorMatrixForward(const double theta, const Vec &vec) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  adept::Stack stack;
  adept::adouble th{theta};
  std::array<adept::adouble, 3> a{vec[0], vec[1], vec[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> b{0.0, 0.0, 0.0};
  DiffRotorMatrixFunctor()(&th, a.begin(), b.begin());
  stack.independent(&th, 1);
  stack.dependent(b.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf_jac.ptr), true);
  // stack.jacobian_reverse(static_cast<double *>(buf_jac.ptr), true);
  stack.jacobian_forward(static_cast<double *>(buf_jac.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  auto res_ptr = static_cast<double *>(buf_res.ptr);
  for (int i = 0; i < 3; ++i)
    res_ptr[i] = b[i].value();

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);
  list.append(py::str(ss.str().c_str()));
  std::stringstream sstatus;
  stack.print_status(sstatus);
  list.append(py::str(sstatus.str().c_str()));

  return list;
}

py::list AdeptDiffRotorMatrixReverse(const double theta, const Vec &vec) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  adept::Stack stack;
  adept::adouble th{theta};
  std::array<adept::adouble, 3> a{vec[0], vec[1], vec[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> b{0.0, 0.0, 0.0};
  DiffRotorMatrixFunctor()(&th, a.begin(), b.begin());
  stack.independent(&th, 1);
  stack.dependent(b.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf_jac.ptr), true);
  stack.jacobian_reverse(static_cast<double *>(buf_jac.ptr), true);
  // stack.jacobian_forward(static_cast<double *>(buf_jac.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  auto res_ptr = static_cast<double *>(buf_res.ptr);
  for (int i = 0; i < 3; ++i)
    res_ptr[i] = b[i].value();

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);
  list.append(py::str(ss.str().c_str()));
  std::stringstream sstatus;
  stack.print_status(sstatus);
  list.append(py::str(sstatus.str().c_str()));

  return list;
}

py::list AdeptDiffRotorVersorForward(const double theta, const Vec &vec) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  adept::Stack stack;
  adept::adouble th{theta};
  std::array<adept::adouble, 3> a{vec[0], vec[1], vec[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> b{0.0, 0.0, 0.0};
  DiffRotorVersorFunctor()(&th, a.begin(), b.begin());
  stack.independent(&th, 1);
  stack.dependent(b.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf_jac.ptr), true);
  // stack.jacobian_reverse(static_cast<double *>(buf_jac.ptr), true);
  stack.jacobian_forward(static_cast<double *>(buf_jac.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  auto res_ptr = static_cast<double *>(buf_res.ptr);
  for (int i = 0; i < 3; ++i)
    res_ptr[i] = b[i].value();

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);
  list.append(py::str(ss.str().c_str()));
  std::stringstream sstatus;
  stack.print_status(sstatus);
  list.append(py::str(sstatus.str().c_str()));

  return list;
}

py::list AdeptDiffRotorVersorReverse(const double theta, const Vec &vec) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  adept::Stack stack;
  adept::adouble th{theta};
  std::array<adept::adouble, 3> a{vec[0], vec[1], vec[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> b{0.0, 0.0, 0.0};
  DiffRotorVersorFunctor()(&th, a.begin(), b.begin());
  stack.independent(&th, 1);
  stack.dependent(b.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf_jac.ptr), true);
  stack.jacobian_reverse(static_cast<double *>(buf_jac.ptr), true);
  // stack.jacobian_forward(static_cast<double *>(buf_jac.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  auto res_ptr = static_cast<double *>(buf_res.ptr);
  for (int i = 0; i < 3; ++i)
    res_ptr[i] = b[i].value();

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);
  list.append(py::str(ss.str().c_str()));
  std::stringstream sstatus;
  stack.print_status(sstatus);
  list.append(py::str(sstatus.str().c_str()));

  return list;
}

struct DiffRotorHepGAFunctor {
  template <typename T> bool operator()(const T *th, const T *a, T *b) const {
    using Algebra = hep::algebra<T, 3, 0>;
    using Rotor = hep::multi_vector<Algebra, hep::list<0, 3, 5, 6>>;
    using Vector = hep::multi_vector<Algebra, hep::list<1, 2, 4>>;
    Rotor rotor{cos(T(0.5) * th[0]), -sin(T(0.5) * th[0]), T(0.0), T(0.0)};
    Vector pnt_a{a[0], a[1], a[2]};
    Vector pnt_b = hep::grade<1>(rotor * pnt_a * ~rotor);
    for (int i = 0; i < 3; ++i)
      b[i] = pnt_b[i];
    return true;
  }
};

py::list AdeptDiffRotorHepGAForward(const double theta, const Vec &vec) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  adept::Stack stack;
  adept::adouble th{theta};
  std::array<adept::adouble, 3> a{vec[0], vec[1], vec[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> b{0.0, 0.0, 0.0};
  DiffRotorHepGAFunctor()(&th, a.begin(), b.begin());
  stack.independent(&th, 1);
  stack.dependent(b.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf_jac.ptr), true);
  // stack.jacobian_reverse(static_cast<double *>(buf_jac.ptr), true);
  stack.jacobian_forward(static_cast<double *>(buf_jac.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  auto res_ptr = static_cast<double *>(buf_res.ptr);
  for (int i = 0; i < 3; ++i)
    res_ptr[i] = b[i].value();

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);
  list.append(py::str(ss.str().c_str()));
  std::stringstream sstatus;
  stack.print_status(sstatus);
  list.append(py::str(sstatus.str().c_str()));

  return list;
}

py::list AdeptDiffRotorHepGAReverse(const double theta, const Vec &vec) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  adept::Stack stack;
  adept::adouble th{theta};
  std::array<adept::adouble, 3> a{vec[0], vec[1], vec[2]};
  stack.new_recording();
  std::array<adept::adouble, 3> b{0.0, 0.0, 0.0};
  DiffRotorHepGAFunctor()(&th, a.begin(), b.begin());
  stack.independent(&th, 1);
  stack.dependent(b.begin(), 3);

  // stack.jacobian(static_cast<double *>(buf_jac.ptr), true);
  stack.jacobian_reverse(static_cast<double *>(buf_jac.ptr), true);
  // stack.jacobian_forward(static_cast<double *>(buf_jac.ptr), true);

  std::stringstream ss;
  stack.print_statements(ss);

  auto res_ptr = static_cast<double *>(buf_res.ptr);
  for (int i = 0; i < 3; ++i)
    res_ptr[i] = b[i].value();

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);
  list.append(py::str(ss.str().c_str()));
  std::stringstream sstatus;
  stack.print_status(sstatus);
  list.append(py::str(sstatus.str().c_str()));

  return list;
}

py::list CeresDiffRotorHepGA(const double theta, const Vec &a) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  const double *parameters[2] = {&theta, a.begin()};
  double *jacobians[2] = {static_cast<double *>(buf_jac.ptr), nullptr};

  ceres::AutoDiffCostFunction<DiffRotorHepGAFunctor, 3, 1, 3>(
      new DiffRotorHepGAFunctor())
      .Evaluate(parameters, static_cast<double *>(buf_res.ptr), jacobians);

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);

  return list;
}
struct DiffRotorGaalopFunctor {
  template <typename T>
  bool operator()(const T *th, const T *a, T *b) const {
    b[0] = (-(a[0] * sin(th[0] / 2.0) * sin(th[0] / 2.0))) -
           2.0 * a[1] * cos(th[0] / 2.0) * sin(th[0] / 2.0) +
           a[0] * cos(th[0] / 2.0) * cos(th[0] / 2.0); // e1
    b[1] = (-(a[1] * sin(th[0] / 2.0) * sin(th[0] / 2.0))) +
           2.0 * a[0] * cos(th[0] / 2.0) * sin(th[0] / 2.0) +
           a[1] * cos(th[0] / 2.0) * cos(th[0] / 2.0); // e2
    b[2] = a[2] * sin(th[0] / 2.0) * sin(th[0] / 2.0) +
           a[2] * cos(th[0] / 2.0) * cos(th[0] / 2.0); // e3
    return true;
  }
};

py::list CeresDiffRotorGaalop(const double theta, const Vec &a) {
  auto py_array_jac = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto py_array_result = py::array(py::buffer_info(
      nullptr, sizeof(double), py::format_descriptor<double>::value(), 2,
      {3, 1}, {sizeof(double), sizeof(double)}));

  auto buf_jac = py_array_jac.request();
  auto buf_res = py_array_result.request();

  const double *parameters[2] = {&theta, a.begin()};
  double *jacobians[2] = {static_cast<double *>(buf_jac.ptr), nullptr};

  ceres::AutoDiffCostFunction<DiffRotorGaalopFunctor, 3, 1, 3>(
      new DiffRotorGaalopFunctor())
      .Evaluate(parameters, static_cast<double *>(buf_res.ptr), jacobians);

  py::list list;
  list.append(py_array_result);
  list.append(py_array_jac);

  return list;
}

PYBIND11_PLUGIN(autodiff_multivector) {
  py::module m("autodiff_multivector", "autodiff_multivector");
  m.def("diff_adept", &AdeptDiffInnerProductVectorBivector);
  m.def("diff_ceres", &CeresDiffInnerProductVectorBivector);
  m.def("diff_ceres2", &CeresDiffInnerProductVectorVector);
  m.def("diff_ceres_rotor_versor", &CeresDiffRotorVersor);
  m.def("diff_adept_rotor_versor_forward", &AdeptDiffRotorVersorForward);
  m.def("diff_adept_rotor_versor_reverse", &AdeptDiffRotorVersorReverse);
  m.def("diff_ceres_rotor_matrix", &CeresDiffRotorMatrix);
  m.def("diff_adept_rotor_matrix_forward", &AdeptDiffRotorMatrixForward);
  m.def("diff_adept_rotor_matrix_reverse", &AdeptDiffRotorMatrixReverse);
  m.def("diff_ceres_rotor_hepga", &CeresDiffRotorHepGA);
  m.def("diff_ceres_rotor_gaalop", &CeresDiffRotorGaalop);
  m.def("diff_adept_rotor_hepga_forward", &AdeptDiffRotorHepGAForward);
  m.def("diff_adept_rotor_hepga_reverse", &AdeptDiffRotorHepGAReverse);
  return m.ptr();
}


#include <qubus/performance_models/symbolic_regression.hpp>

#include <Eigen/Core>
#include <unsupported/Eigen/NonLinearOptimization>

#include <boost/circular_buffer.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/irange.hpp>

#include <qubus/util/assert.hpp>
#include <qubus/util/integers.hpp>
#include <qubus/util/unused.hpp>

#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <functional>
#include <iterator>
#include <limits>
#include <random>
#include <stack>
#include <string>
#include <unordered_map>
#include <utility>

namespace qubus
{

namespace
{

#ifndef __GNUC__
class compiled_expression
{
public:
    static constexpr std::size_t stack_size = 40;

    using bytecode_unit_t = std::uint8_t;

    enum opcode : bytecode_unit_t
    {
        add,
        sub,
        mul,
        div,
        exp,
        log,
        push,
        push_param,
        push_arg,
        kronecker,
        quot_rule,
        prod_rule,
        halt
    };

    compiled_expression() = default;

    explicit compiled_expression(std::vector<bytecode_unit_t> bytecode_)
    : bytecode_(std::move(bytecode_))
    {
    }

    double operator()(const Eigen::VectorXd& parameters, const std::vector<double>& arguments,
                      long int diff_index) const
    {
        std::array<double, stack_size> stack;

        std::int64_t sp = 0;
        std::int64_t pc = 0;

        for (;;)
        {
            opcode op;
            static_assert(sizeof(opcode) == sizeof(bytecode_unit_t),
                          "Unexpected size of bytecode unit.");
            std::memcpy(&op, &bytecode_[pc++], sizeof(opcode));

            switch (op)
            {
            case opcode::push:
            {
                static_assert(sizeof(double) == 8 * sizeof(bytecode_unit_t),
                              "Unexpected size of bytecode unit.");

                double value;
                std::memcpy(&value, &bytecode_[pc], sizeof(double));
                pc += 8;

                stack[sp++] = value;
                break;
            }
            case opcode::push_param:
            {
                static_assert(sizeof(long int) == 8 * sizeof(bytecode_unit_t),
                              "Unexpected size of bytecode unit.");

                long int index;
                std::memcpy(&index, &bytecode_[pc], sizeof(long int));
                pc += 8;

                stack[sp++] = parameters(index);
                break;
            }
            case opcode::push_arg:
            {
                static_assert(sizeof(long int) == 8 * sizeof(bytecode_unit_t),
                              "Unexpected size of bytecode unit.");

                long int index;
                std::memcpy(&index, &bytecode_[pc], sizeof(long int));
                pc += 8;

                stack[sp++] = arguments[index];
                break;
            }
            case opcode::add:
                stack[sp - 2] = stack[sp - 2] + stack[sp - 1];
                --sp;
                break;
            case opcode::sub:
                stack[sp - 2] = stack[sp - 2] - stack[sp - 1];
                --sp;
                break;
            case opcode::mul:
                stack[sp - 2] = stack[sp - 2] * stack[sp - 1];
                --sp;
                break;
            case opcode::div:
                stack[sp - 2] = stack[sp - 2] / stack[sp - 1];
                --sp;
                break;
            case opcode::exp:
                stack[sp - 1] = std::exp(stack[sp - 1]);
                break;
            case opcode::log:
                stack[sp - 1] = std::log(stack[sp - 1]);
                break;
            case opcode::kronecker:
            {
                static_assert(sizeof(long int) == 8 * sizeof(bytecode_unit_t),
                              "Unexpected size of bytecode unit.");

                long int index;
                std::memcpy(&index, &bytecode_[pc], sizeof(long int));
                pc += 8;

                stack[sp++] = index == diff_index ? 1.0 : 0.0;
                break;
            }
            case opcode::quot_rule:
            {
                auto lhs = stack[sp - 4];
                auto rhs = stack[sp - 3];

                auto lhs_diff = stack[sp - 2];
                auto rhs_diff = stack[sp - 1];

                stack[sp - 4] = (rhs * lhs_diff - lhs * rhs_diff) / (rhs * rhs);

                sp -= 3;
                break;
            }
            case opcode::prod_rule:
            {
                auto lhs = stack[sp - 4];
                auto rhs = stack[sp - 3];

                auto lhs_diff = stack[sp - 2];
                auto rhs_diff = stack[sp - 1];

                stack[sp - 4] = lhs * rhs_diff + lhs_diff * rhs;

                sp -= 3;
                break;
            }
            case opcode::halt:
                return stack[sp - 1];
            default:
                QUBUS_UNREACHABLE_BECAUSE("Invalid opcode.");
            }
        }
    }

private:
    std::vector<bytecode_unit_t> bytecode_;
};
#else
class compiled_expression
{
public:
    static constexpr std::size_t stack_size = 40;

    using bytecode_unit_t = std::uint8_t;

    enum opcode : bytecode_unit_t
    {
        add,
        sub,
        mul,
        div,
        exp,
        log,
        push,
        push_param,
        push_arg,
        kronecker,
        quot_rule,
        prod_rule,
        halt
    };

    compiled_expression() = default;

    explicit compiled_expression(std::vector<bytecode_unit_t> bytecode_)
    : bytecode_(std::move(bytecode_))
    {
    }

    double operator()(const Eigen::VectorXd& parameters, const std::vector<double>& arguments,
                      long int diff_index) const
    {
        static constexpr void* dispatch_table[] = {
            &&do_add,       &&do_sub,       &&do_mul,        &&do_div,      &&do_exp,
            &&do_log,       &&do_push,      &&do_push_param, &&do_push_arg, &&do_kronecker,
            &&do_quot_rule, &&do_prod_rule, &&do_halt};

        std::array<double, stack_size> stack;

        std::int64_t sp = 0;
        std::int64_t pc = 0;

        goto* dispatch_table[next_opcode(pc)];

        {
        do_push:
        {
            static_assert(sizeof(double) == 8 * sizeof(bytecode_unit_t),
                          "Unexpected size of bytecode unit.");

            double value;
            std::memcpy(&value, &bytecode_[pc], sizeof(double));
            pc += 8;

            stack[sp++] = value;
            goto* dispatch_table[next_opcode(pc)];
        }
        do_push_param:
        {
            static_assert(sizeof(long int) == 8 * sizeof(bytecode_unit_t),
                          "Unexpected size of bytecode unit.");

            long int index;
            std::memcpy(&index, &bytecode_[pc], sizeof(long int));
            pc += 8;

            stack[sp++] = parameters(index);
            goto* dispatch_table[next_opcode(pc)];
        }
        do_push_arg:
        {
            static_assert(sizeof(long int) == 8 * sizeof(bytecode_unit_t),
                          "Unexpected size of bytecode unit.");

            long int index;
            std::memcpy(&index, &bytecode_[pc], sizeof(long int));
            pc += 8;

            stack[sp++] = arguments[index];
            goto* dispatch_table[next_opcode(pc)];
        }
        do_add:
            stack[sp - 2] = stack[sp - 2] + stack[sp - 1];
            --sp;
            goto* dispatch_table[next_opcode(pc)];
        do_sub:
            stack[sp - 2] = stack[sp - 2] - stack[sp - 1];
            --sp;
            goto* dispatch_table[next_opcode(pc)];
        do_mul:
            stack[sp - 2] = stack[sp - 2] * stack[sp - 1];
            --sp;
            goto* dispatch_table[next_opcode(pc)];
        do_div:
            stack[sp - 2] = stack[sp - 2] / stack[sp - 1];
            --sp;
            goto* dispatch_table[next_opcode(pc)];
        do_exp:
            stack[sp - 1] = std::exp(stack[sp - 1]);
            goto* dispatch_table[next_opcode(pc)];
        do_log:
            stack[sp - 1] = std::log(stack[sp - 1]);
            goto* dispatch_table[next_opcode(pc)];
        do_kronecker:
        {
            static_assert(sizeof(long int) == 8 * sizeof(bytecode_unit_t),
                          "Unexpected size of bytecode unit.");

            long int index;
            std::memcpy(&index, &bytecode_[pc], sizeof(long int));
            pc += 8;

            stack[sp++] = index == diff_index ? 1.0 : 0.0;
            goto* dispatch_table[next_opcode(pc)];
        }
        do_quot_rule:
        {
            auto lhs = stack[sp - 4];
            auto rhs = stack[sp - 3];

            auto lhs_diff = stack[sp - 2];
            auto rhs_diff = stack[sp - 1];

            stack[sp - 4] = (rhs * lhs_diff - lhs * rhs_diff) / (rhs * rhs);

            sp -= 3;
            goto* dispatch_table[next_opcode(pc)];
        }
        do_prod_rule:
        {
            auto lhs = stack[sp - 4];
            auto rhs = stack[sp - 3];

            auto lhs_diff = stack[sp - 2];
            auto rhs_diff = stack[sp - 1];

            stack[sp - 4] = lhs * rhs_diff + lhs_diff * rhs;

            sp -= 3;
            goto* dispatch_table[next_opcode(pc)];
        }
        do_halt:
            return stack[sp - 1];
        }
    }

private:
    opcode next_opcode(std::int64_t& pc) const
    {
        opcode op;
        static_assert(sizeof(opcode) == sizeof(bytecode_unit_t),
                      "Unexpected size of bytecode unit.");
        std::memcpy(&op, &bytecode_[pc++], sizeof(opcode));

        return op;
    }

    std::vector<bytecode_unit_t> bytecode_;
};
#endif

class model_expression
{
public:
    model_expression() = default;
    virtual ~model_expression() = default;

    model_expression(const model_expression&) = delete;
    model_expression& operator=(const model_expression&) = delete;

    model_expression(model_expression&&) = delete;
    model_expression& operator=(model_expression&&) = delete;

    virtual long int arity() const = 0;
    virtual const model_expression& child(long int index) const = 0;
    virtual model_expression& child(long int index) = 0;

    auto children() const
    {
        return boost::irange<long int>(0, this->arity()) |
               boost::adaptors::transformed(
                   [this](long int index) -> decltype(auto) { return this->child(index); });
    }

    auto children()
    {
        return boost::irange<long int>(0, this->arity()) |
               boost::adaptors::transformed(
                   [this](long int index) -> decltype(auto) { return this->child(index); });
    }

    model_expression* parent() const
    {
        return parent_;
    }

    void set_parent(model_expression& parent)
    {
        parent_ = &parent;
    }

    virtual std::unique_ptr<model_expression> clone() const = 0;

    virtual void substitute_child(const model_expression& old_child,
                                  std::unique_ptr<model_expression> new_child) = 0;

    virtual double evaluate(const Eigen::VectorXd& parameters,
                            const std::vector<double>& arguments) const = 0;

    virtual double df(const Eigen::VectorXd& parameters, const std::vector<double>& arguments,
                      long int index) const = 0;

    virtual void
    emit_evaluate_bytecode(std::vector<compiled_expression::bytecode_unit_t>& bytecode) const = 0;

    virtual void
    emit_df_bytecode(std::vector<compiled_expression::bytecode_unit_t>& bytecode) const = 0;

    virtual std::string dump(const Eigen::VectorXd& parameters) const = 0;

private:
    model_expression* parent_ = nullptr;
};

std::unique_ptr<model_expression> clone(const model_expression& expr)
{
    return expr.clone();
}

long int determine_number_of_expressions(const model_expression& root)
{
    long int number_of_expressions = 1;

    for (const auto& child : root.children())
    {
        number_of_expressions += determine_number_of_expressions(child);
    }

    QUBUS_ASSERT(number_of_expressions >= 0, "The number of expressions should be non-negative.");

    return number_of_expressions;
}

long int determine_number_of_parameters(const model_expression& root);

long int determine_depth(const model_expression& root)
{
    if (root.arity() == 0)
        return 1;

    long int depth = 1;

    for (const auto& child : root.children())
    {
        auto depth_of_child = determine_depth(child);

        depth = std::max(depth, depth_of_child);
    }

    return depth + 1;
}

class binary_operator_expression final : public model_expression
{
public:
    enum class tag
    {
        plus,
        minus,
        multiplies,
        divides
    };

    explicit binary_operator_expression(tag tag_, std::unique_ptr<model_expression> lhs_,
                                        std::unique_ptr<model_expression> rhs_)
    : tag_(tag_), lhs_(std::move(lhs_)), rhs_(std::move(rhs_))
    {
        this->lhs_->set_parent(*this);
        this->rhs_->set_parent(*this);
    }

    long int arity() const override
    {
        return 2;
    }

    const model_expression& child(long int index) const override
    {
        QUBUS_ASSERT(0 <= index && index < 2, "Invalid child index.");

        switch (index)
        {
        case 0:
            return *lhs_;
        case 1:
            return *rhs_;
        default:
            QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
        }
    }

    model_expression& child(long int index) override
    {
        QUBUS_ASSERT(0 <= index && index < 2, "Invalid child index.");

        switch (index)
        {
        case 0:
            return *lhs_;
        case 1:
            return *rhs_;
        default:
            QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
        }
    }

    std::unique_ptr<model_expression> clone() const override
    {
        return std::make_unique<binary_operator_expression>(tag_, lhs_->clone(), rhs_->clone());
    }

    void substitute_child(const model_expression& old_child,
                          std::unique_ptr<model_expression> new_child) override
    {
        if (&old_child == lhs_.get())
        {
            lhs_ = std::move(new_child);
            lhs_->set_parent(*this);
        }
        else if (&old_child == rhs_.get())
        {
            rhs_ = std::move(new_child);
            rhs_->set_parent(*this);
        }
        else
        {
            QUBUS_UNREACHABLE_BECAUSE("old_child is not a child of this node.");
        }
    }

    double evaluate(const Eigen::VectorXd& parameters,
                    const std::vector<double>& arguments) const override
    {
        switch (tag_)
        {
        case tag::plus:
            return lhs_->evaluate(parameters, arguments) + rhs_->evaluate(parameters, arguments);
        case tag::minus:
            return lhs_->evaluate(parameters, arguments) - rhs_->evaluate(parameters, arguments);
        case tag::multiplies:
            return lhs_->evaluate(parameters, arguments) * rhs_->evaluate(parameters, arguments);
        case tag::divides:
            return lhs_->evaluate(parameters, arguments) / rhs_->evaluate(parameters, arguments);
        }
    }

    double df(const Eigen::VectorXd& parameters, const std::vector<double>& arguments,
              long int index) const override
    {
        switch (tag_)
        {
        case tag::plus:
            return lhs_->df(parameters, arguments, index) + rhs_->df(parameters, arguments, index);
        case tag::minus:
            return lhs_->df(parameters, arguments, index) - rhs_->df(parameters, arguments, index);
        case tag::multiplies:
            return lhs_->df(parameters, arguments, index) * rhs_->evaluate(parameters, arguments) +
                   lhs_->evaluate(parameters, arguments) * rhs_->df(parameters, arguments, index);
        case tag::divides:
        {
            auto lhs_result = rhs_->evaluate(parameters, arguments);
            auto rhs_result = rhs_->evaluate(parameters, arguments);

            return (lhs_->df(parameters, arguments, index) * rhs_result -
                    lhs_result * rhs_->df(parameters, arguments, index)) /
                   (rhs_result * rhs_result);
        }
        }
    }

    void emit_evaluate_bytecode(
        std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        lhs_->emit_evaluate_bytecode(bytecode);
        rhs_->emit_evaluate_bytecode(bytecode);

        switch (tag_)
        {
        case tag::plus:
            bytecode.push_back(compiled_expression::opcode::add);
            return;
        case tag::minus:
            bytecode.push_back(compiled_expression::opcode::sub);
            return;
        case tag::multiplies:
            bytecode.push_back(compiled_expression::opcode::mul);
            return;
        case tag::divides:
            bytecode.push_back(compiled_expression::opcode::div);
            return;
        }
    }

    void
    emit_df_bytecode(std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        switch (tag_)
        {
        case tag::plus:
            lhs_->emit_df_bytecode(bytecode);
            rhs_->emit_df_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::add);
            break;
        case tag::minus:
            lhs_->emit_df_bytecode(bytecode);
            rhs_->emit_df_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::sub);
            break;
        case tag::multiplies:
            lhs_->emit_evaluate_bytecode(bytecode);
            rhs_->emit_evaluate_bytecode(bytecode);

            lhs_->emit_df_bytecode(bytecode);
            rhs_->emit_df_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::prod_rule);
            break;
        case tag::divides:
            lhs_->emit_evaluate_bytecode(bytecode);
            rhs_->emit_evaluate_bytecode(bytecode);

            lhs_->emit_df_bytecode(bytecode);
            rhs_->emit_df_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::quot_rule);

            break;
        }
    }

    std::string dump(const Eigen::VectorXd& parameters) const override
    {
        switch (tag_)
        {
        case tag::plus:
            return "(" + lhs_->dump(parameters) + " + " + rhs_->dump(parameters) + ")";
        case tag::minus:
            return "(" + lhs_->dump(parameters) + " - " + rhs_->dump(parameters) + ")";
        case tag::multiplies:
            return "(" + lhs_->dump(parameters) + " * " + rhs_->dump(parameters) + ")";
        case tag::divides:
            return "(" + lhs_->dump(parameters) + " / " + rhs_->dump(parameters) + ")";
        }
    }

private:
    tag tag_;
    std::unique_ptr<model_expression> lhs_;
    std::unique_ptr<model_expression> rhs_;
};

class function_expression final : public model_expression
{
public:
    enum class tag
    {
        exp,
        log
    };

    explicit function_expression(tag tag_, std::unique_ptr<model_expression> arg_)
    : tag_(tag_), arg_(std::move(arg_))
    {
        this->arg_->set_parent(*this);
    }

    long int arity() const override
    {
        return 1;
    }

    const model_expression& child(long int index) const override
    {
        QUBUS_ASSERT(0 <= index && index < 1, "Invalid child index.");

        switch (index)
        {
        case 0:
            return *arg_;
        default:
            QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
        }
    }

    model_expression& child(long int index) override
    {
        QUBUS_ASSERT(0 <= index && index < 1, "Invalid child index.");

        switch (index)
        {
        case 0:
            return *arg_;
        default:
            QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
        }
    }

    std::unique_ptr<model_expression> clone() const override
    {
        return std::make_unique<function_expression>(tag_, arg_->clone());
    }

    void substitute_child(const model_expression& old_child,
                          std::unique_ptr<model_expression> new_child) override
    {
        if (&old_child == arg_.get())
        {
            arg_ = std::move(new_child);
            arg_->set_parent(*this);
        }
        else
        {
            QUBUS_UNREACHABLE_BECAUSE("old_child is not a child of this node.");
        }
    }

    double evaluate(const Eigen::VectorXd& parameters,
                    const std::vector<double>& arguments) const override
    {
        switch (tag_)
        {
        case tag::exp:
            return std::exp(arg_->evaluate(parameters, arguments));
        case tag::log:
            return std::log(arg_->evaluate(parameters, arguments));
        }
    }

    double df(const Eigen::VectorXd& parameters, const std::vector<double>& arguments,
              long int index) const override
    {
        switch (tag_)
        {
        case tag::exp:
            return std::exp(arg_->evaluate(parameters, arguments)) *
                   arg_->df(parameters, arguments, index);
        case tag::log:
            return arg_->df(parameters, arguments, index) / arg_->evaluate(parameters, arguments);
        }
    }

    void emit_evaluate_bytecode(
        std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        arg_->emit_evaluate_bytecode(bytecode);

        switch (tag_)
        {
        case tag::exp:
            bytecode.push_back(compiled_expression::opcode::exp);
            return;
        case tag::log:
            bytecode.push_back(compiled_expression::opcode::log);
            return;
        }
    }

    void
    emit_df_bytecode(std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        switch (tag_)
        {
        case tag::exp:
            arg_->emit_evaluate_bytecode(bytecode);
            bytecode.push_back(compiled_expression::opcode::exp);

            arg_->emit_df_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::mul);
            return;
        case tag::log:
            arg_->emit_df_bytecode(bytecode);
            arg_->emit_evaluate_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::div);
            return;
        }
    }

    std::string dump(const Eigen::VectorXd& parameters) const override
    {
        switch (tag_)
        {
        case tag::exp:
            return "exp(" + arg_->dump(parameters) + ")";
        case tag::log:
            return "log(" + arg_->dump(parameters) + ")";
        }
    }

private:
    tag tag_;
    std::unique_ptr<model_expression> arg_;
};

class parameter_expression final : public model_expression
{
public:
    explicit parameter_expression(long int index_) : index_(std::move(index_))
    {
        QUBUS_ASSERT(this->index_ >= 0, "Invalid index.");
    }

    long int arity() const override
    {
        return 0;
    }

    const model_expression& child(long int index) const override
    {
        QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
    }

    model_expression& child(long int index) override
    {
        QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
    }

    std::unique_ptr<model_expression> clone() const override
    {
        return std::make_unique<parameter_expression>(index_);
    }

    void substitute_child(const model_expression& QUBUS_UNUSED(old_child),
                          std::unique_ptr<model_expression> QUBUS_UNUSED(new_child)) override
    {
        QUBUS_UNREACHABLE_BECAUSE("old_child is not a child of this node.");
    }

    long int index() const
    {
        return index_;
    }

    void set_index(long int index)
    {
        index_ = index;
    }

    double evaluate(const Eigen::VectorXd& parameters,
                    const std::vector<double>& QUBUS_UNUSED(arguments)) const override
    {
        QUBUS_ASSERT(index_ < parameters.size(), "Invalid index.");

        return parameters(util::integer_cast<Eigen::Index>(index_));
    }

    double df(const Eigen::VectorXd& QUBUS_UNUSED(parameters),
              const std::vector<double>& QUBUS_UNUSED(arguments), long int index) const override
    {
        return index_ == index ? 1.0 : 0.0;
    }

    void emit_evaluate_bytecode(
        std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        bytecode.push_back(compiled_expression::opcode::push_param);

        compiled_expression::bytecode_unit_t data[8];

        static_assert(sizeof(long int) == 8 * sizeof(compiled_expression::bytecode_unit_t),
                      "Unexpected size of bytecode unit.");
        std::memcpy(&data, &index_, sizeof(long int));

        for (std::size_t i = 0; i < 8; ++i)
        {
            bytecode.push_back(data[i]);
        }
    }

    void
    emit_df_bytecode(std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        bytecode.push_back(compiled_expression::opcode::kronecker);

        compiled_expression::bytecode_unit_t data[8];

        static_assert(sizeof(long int) == 8 * sizeof(compiled_expression::bytecode_unit_t),
                      "Unexpected size of bytecode unit.");
        std::memcpy(&data, &index_, sizeof(long int));

        for (std::size_t i = 0; i < 8; ++i)
        {
            bytecode.push_back(data[i]);
        }
    }

    std::string dump(const Eigen::VectorXd& parameters) const override
    {
        QUBUS_ASSERT(index_ < parameters.size(), "Invalid index.");

        return std::to_string(parameters(util::integer_cast<Eigen::Index>(index_)));
    }

private:
    long int index_;
};

class variable_expression final : public model_expression
{
public:
    explicit variable_expression(long int index_) : index_(std::move(index_))
    {
        QUBUS_ASSERT(this->index_ >= 0, "Invalid index.");
    }

    long int arity() const override
    {
        return 0;
    }

    const model_expression& child(long int index) const override
    {
        QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
    }

    model_expression& child(long int index) override
    {
        QUBUS_UNREACHABLE_BECAUSE("Invalid child index.");
    }

    std::unique_ptr<model_expression> clone() const override
    {
        return std::make_unique<variable_expression>(index_);
    }

    void substitute_child(const model_expression& QUBUS_UNUSED(old_child),
                          std::unique_ptr<model_expression> QUBUS_UNUSED(new_child)) override
    {
        QUBUS_UNREACHABLE_BECAUSE("old_child is not a child of this node.");
    }

    double evaluate(const Eigen::VectorXd& QUBUS_UNUSED(parameters),
                    const std::vector<double>& arguments) const override
    {
        QUBUS_ASSERT(index_ < arguments.size(), "Invalid index.");

        return arguments[util::integer_cast<std::size_t>(index_)];
    }

    double df(const Eigen::VectorXd& QUBUS_UNUSED(parameters),
              const std::vector<double>& QUBUS_UNUSED(arguments),
              long int QUBUS_UNUSED(index)) const override
    {
        return 0.0;
    }

    void emit_evaluate_bytecode(
        std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        bytecode.push_back(compiled_expression::opcode::push_arg);

        compiled_expression::bytecode_unit_t data[8];

        static_assert(sizeof(long int) == 8 * sizeof(compiled_expression::bytecode_unit_t),
                      "Unexpected size of bytecode unit.");
        std::memcpy(&data, &index_, sizeof(long int));

        for (std::size_t i = 0; i < 8; ++i)
        {
            bytecode.push_back(data[i]);
        }
    }

    void
    emit_df_bytecode(std::vector<compiled_expression::bytecode_unit_t>& bytecode) const override
    {
        bytecode.push_back(compiled_expression::opcode::push);

        compiled_expression::bytecode_unit_t data[8];
        const double value = 0.0;

        static_assert(sizeof(double) == 8 * sizeof(compiled_expression::bytecode_unit_t),
                      "Unexpected size of bytecode unit.");
        std::memcpy(&data, &index_, sizeof(double));

        for (std::size_t i = 0; i < 8; ++i)
        {
            bytecode.push_back(data[i]);
        }
    }

    std::string dump(const Eigen::VectorXd& QUBUS_UNUSED(parameters)) const override
    {
        return "a" + std::to_string(index_);
    }

private:
    long int index_;
};

long int determine_number_of_parameters(const model_expression& root)
{
    long int number_of_parameters = 0;

    if (auto parameter_expr = dynamic_cast<const parameter_expression*>(&root))
    {
        number_of_parameters = parameter_expr->index() + 1;
    }

    for (const auto& child : root.children())
    {
        number_of_parameters =
            std::max(number_of_parameters, determine_number_of_parameters(child));
    }

    QUBUS_ASSERT(number_of_parameters >= 0, "The number of parameters has to be non-negative.");

    return number_of_parameters;
}

template <typename Engine>
std::unique_ptr<model_expression>
generate_random_expression(Engine& engine, long int number_of_arguments,
                           long int& number_of_parameters, long int depth = 0)
{
    QUBUS_ASSERT(number_of_arguments > 0, "The expression has to contain at least one argument.");

    enum expression_type
    {
        parameter,
        variable,
        plus,
        minus,
        multiplies,
        divides,
        exp,
        log,
        number_of_types
    };

    std::discrete_distribution<int> type_dist({
        10, // parameter
        10, // variable
        10, // plus
        10, // minus
        10, // multiplies
        10, // divides
        2,  // exp
        2   // log
    });

    std::uniform_int_distribution<long int> parameter_index_dist(0, 2 * number_of_parameters);
    std::uniform_int_distribution<long int> argument_index_dist(0, number_of_arguments - 1);

    auto r = type_dist(engine);

    if (depth > 2)
        r = r % 2;

    expression_type type = static_cast<expression_type>(r);

    switch (type)
    {
    case parameter:
    {
        long int index = parameter_index_dist(engine);

        if (index < number_of_parameters)
        {
            return std::make_unique<parameter_expression>(index);
        }
        else
        {
            long int new_parameter = number_of_parameters;

            ++number_of_parameters;

            return std::make_unique<parameter_expression>(new_parameter);
        }
    }
    case variable:
    {
        long int index = argument_index_dist(engine);

        return std::make_unique<variable_expression>(index);
    }
    case plus:
    {
        auto lhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);
        auto rhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);

        return std::make_unique<binary_operator_expression>(binary_operator_expression::tag::plus,
                                                            std::move(lhs), std::move(rhs));
    }
    case minus:
    {
        auto lhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);
        auto rhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);

        return std::make_unique<binary_operator_expression>(binary_operator_expression::tag::minus,
                                                            std::move(lhs), std::move(rhs));
    }
    case multiplies:
    {
        auto lhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);
        auto rhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);

        return std::make_unique<binary_operator_expression>(
            binary_operator_expression::tag::multiplies, std::move(lhs), std::move(rhs));
    }
    case divides:
    {
        auto lhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);
        auto rhs = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);

        return std::make_unique<binary_operator_expression>(
            binary_operator_expression::tag::divides, std::move(lhs), std::move(rhs));
    }
    case exp:
    {
        auto arg = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);

        return std::make_unique<function_expression>(function_expression::tag::exp, std::move(arg));
    }
    case log:
    {
        auto arg = generate_random_expression(engine, number_of_arguments, number_of_parameters,
                                              depth + 1);

        return std::make_unique<function_expression>(function_expression::tag::log, std::move(arg));
    }
    case number_of_types:
        QUBUS_UNREACHABLE_BECAUSE("number_of_types is not an actual expression type.");
    }

    QUBUS_UNREACHABLE();
}

template <typename Engine>
const model_expression& select_random_node(const model_expression& expr, Engine& engine)
{
    auto number_of_nodes = determine_number_of_expressions(expr);

    std::uniform_int_distribution<long int> dist(0, number_of_nodes - 1);

    auto number_of_steps = dist(engine);

    std::stack<const model_expression*> node_stack;

    node_stack.push(&expr);

    while (!node_stack.empty())
    {
        auto current_node = node_stack.top();
        node_stack.pop();

        if (number_of_steps == 0)
            return *current_node;

        --number_of_steps;

        for (const auto& child : current_node->children())
        {
            node_stack.push(&child);
        }
    }

    QUBUS_UNREACHABLE_BECAUSE("The algorithm should have reached the selected node long ago.");
}

void remove_dead_parameters(model_expression& expr)
{
    std::unordered_map<long int, long int> index_map;

    std::stack<model_expression*> node_stack;

    node_stack.push(&expr);

    while (!node_stack.empty())
    {
        auto& current_node = *node_stack.top();
        node_stack.pop();

        if (auto typed_node = dynamic_cast<parameter_expression*>(&current_node))
        {
            auto old_index = typed_node->index();

            auto search_result = index_map.find(old_index);

            if (search_result != index_map.end())
            {
                typed_node->set_index(search_result->second);
            }
            else
            {
                auto new_index = util::integer_cast<long int>(index_map.size());

                typed_node->set_index(new_index);

                index_map.emplace(old_index, new_index);
            }
        }

        for (auto& child : current_node.children())
        {
            node_stack.push(&child);
        }
    }
}

template <typename Engine>
std::unique_ptr<model_expression> mutate_expression(const model_expression& expr,
                                                    long int number_of_arguments,
                                                    long int number_of_parameters, Engine& engine)
{
    auto cloned_expression = clone(expr);

    const auto& random_node = select_random_node(*cloned_expression, engine);

    auto new_sub_tree =
        generate_random_expression(engine, number_of_arguments, number_of_parameters);

    auto parent = random_node.parent();

    if (!parent)
        return new_sub_tree;

    parent->substitute_child(random_node, std::move(new_sub_tree));

    remove_dead_parameters(*cloned_expression);

    return cloned_expression;
}

template <typename Engine>
std::unique_ptr<model_expression> mix_expressions(const model_expression& mother,
                                                  const model_expression& father, Engine& engine)
{
    auto cloned_expression = clone(mother);

    const auto& random_node = select_random_node(*cloned_expression, engine);

    auto parent = random_node.parent();

    if (!parent)
        return clone(father);

    const auto& random_node2 = select_random_node(father, engine);

    parent->substitute_child(random_node, clone(random_node2));

    remove_dead_parameters(*cloned_expression);

    return cloned_expression;
}

template <typename Engine>
std::unique_ptr<model_expression> wrap_expression(const model_expression& expr, Engine& engine)
{
    std::uniform_int_distribution<long int> dist(0, 1);

    auto tag = dist(engine);

    switch (tag)
    {
    case 0:
        return std::make_unique<function_expression>(function_expression::tag::exp, clone(expr));
    case 1:
        return std::make_unique<function_expression>(function_expression::tag::log, clone(expr));
    default:
        QUBUS_UNREACHABLE_BECAUSE("No case left.");
    }
}

template <typename Engine>
std::unique_ptr<model_expression>
combine_expressions(const model_expression& mother, const model_expression& father, Engine& engine)
{
    std::uniform_int_distribution<long int> dist(0, 3);

    auto tag = dist(engine);

    switch (tag)
    {
    case 0:
        return std::make_unique<binary_operator_expression>(binary_operator_expression::tag::plus,
                                                            clone(mother), clone(father));
    case 1:
        return std::make_unique<binary_operator_expression>(binary_operator_expression::tag::minus,
                                                            clone(mother), clone(father));
    case 2:
        return std::make_unique<binary_operator_expression>(
            binary_operator_expression::tag::multiplies, clone(mother), clone(father));
    case 3:
        return std::make_unique<binary_operator_expression>(
            binary_operator_expression::tag::divides, clone(mother), clone(father));
    default:
        QUBUS_UNREACHABLE_BECAUSE("No case left.");
    }
}

struct data_point
{
    std::vector<double> arguments;
    std::chrono::microseconds execution_time;
};

template <typename Dataset>
class model
{
public:
    static constexpr long int max_depth = 20;

    explicit model(std::unique_ptr<model_expression> root_, const Dataset& dataset_)
    : root_(std::move(root_)),
      number_of_parameters_(determine_number_of_parameters(*this->root_)),
      dataset_(&dataset_)
    {
        QUBUS_ASSERT(determine_depth(*this->root_) <= max_depth,
                   "The depth is larger than expected.");

        static_assert(max_depth < compiled_expression::stack_size,
                      "The size of the stack might not be sufficient.");

        {
            std::vector<compiled_expression::bytecode_unit_t> bytecode;

            this->root_->emit_evaluate_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::halt);

            evaluate_ = compiled_expression(std::move(bytecode));
        }

        {
            std::vector<compiled_expression::bytecode_unit_t> bytecode;

            this->root_->emit_df_bytecode(bytecode);

            bytecode.push_back(compiled_expression::opcode::halt);

            df_ = compiled_expression(std::move(bytecode));
        }
    }

    model(const model& other)
    : root_(clone(*other.root_)),
      number_of_parameters_(other.number_of_parameters_),
      dataset_(other.dataset_),
      evaluate_(other.evaluate_),
      df_(other.df_)
    {
    }

    model& operator=(const model& other)
    {
        root_ = clone(*other.root_);
        number_of_parameters_ = other.number_of_parameters_;
        dataset_ = other.dataset_;
        evaluate_ = other.evaluate_;
        df_ = other.df_;

        return *this;
    }

    model(model&& other) noexcept
    : root_(std::move(other.root_)),
      number_of_parameters_(std::move(other.number_of_parameters_)),
      dataset_(other.dataset_),
      evaluate_(std::move(other.evaluate_)),
      df_(std::move(other.df_))
    {
    }

    model& operator=(model&& other) noexcept
    {
        root_ = std::move(other.root_);
        number_of_parameters_ = std::move(other.number_of_parameters_);
        dataset_ = other.dataset_;
        evaluate_ = std::move(other.evaluate_);
        df_ = std::move(other.df_);

        return *this;
    }

    const model_expression& expr() const
    {
        QUBUS_ASSERT(root_, "Invalid object.");

        return *root_;
    }

    long int number_of_arguments() const
    {
        return dataset_->front().arguments.size();
    }

    long int number_of_parameters() const
    {
        return number_of_parameters_;
    }

    const Dataset& data_set() const
    {
        return *dataset_;
    }

    int values() const
    {
        return util::integer_cast<int>(dataset_->size());
    }

    std::chrono::microseconds query(const Eigen::VectorXd& parameters,
                                    const std::vector<double>& arguments) const
    {
        auto result = root_->evaluate(parameters, arguments);

        using rep = std::chrono::microseconds::rep;
        double max_value = std::numeric_limits<rep>::max() / 2;

        if (!std::isfinite(result) || result > max_value || result < 0)
        {
            result = max_value;
        }

        auto count = static_cast<rep>(result);

        QUBUS_ASSERT(count >= 0, "Invalid duration.");

        return std::chrono::microseconds(std::move(count));
    }

    int operator()(const Eigen::VectorXd& parameters, Eigen::VectorXd& result) const
    {
        QUBUS_ASSERT(number_of_parameters_ == parameters.size(), "Unexpected number of parameters.");

        for (long int i = 0; i < result.size(); ++i)
        {
            auto i_idx = util::integer_cast<Eigen::Index>(i);
            auto i_std = util::integer_cast<std::size_t>(i);

            result(i_idx) = evaluate_(parameters, (*dataset_)[i_std].arguments, 0) -
                            (*dataset_)[i_std].execution_time.count();
        }

        return 0;
    }

    int df(const Eigen::VectorXd& parameters, Eigen::MatrixXd& result) const
    {
        QUBUS_ASSERT(result.rows() == dataset_->size(), "Unexpected number of results.");
        QUBUS_ASSERT(result.cols() == parameters.size(), "Unexpected number of variables.");

        for (long int i = 0; i < result.rows(); ++i)
        {
            auto i_idx = util::integer_cast<Eigen::Index>(i);
            auto i_std = util::integer_cast<std::size_t>(i);

            for (long int col = 0; col < result.cols(); ++col)
            {
                auto col_idx = util::integer_cast<Eigen::Index>(col);

                result(i_idx, col_idx) = df_(parameters, (*dataset_)[i_std].arguments, col);
            }
        }

        return 0;
    }

    std::string dump(const Eigen::VectorXd& parameters) const
    {
        return root_->dump(parameters);
    }

private:
    std::unique_ptr<model_expression> root_;
    long int number_of_parameters_;
    const Dataset* dataset_;

    compiled_expression evaluate_;
    compiled_expression df_;
};

template <typename Dataset>
class regression_model
{
public:
    template <typename Engine>
    regression_model(model<Dataset> m, Engine& engine) : model_(m)
    {
        auto number_of_parameters = this->model_.number_of_parameters();

        parameters_.resize(number_of_parameters);

        std::uniform_real_distribution<double> parameter_dist(-100, 100);

        for (long int i = 0; i < number_of_parameters; ++i)
        {
            parameters_(util::integer_cast<Eigen::Index>(i)) = parameter_dist(engine);
        }

        update();
    }

    const model_expression& expr() const
    {
        return model_.expr();
    }

    long int number_of_arguments() const
    {
        return model_.number_of_arguments();
    }

    long int number_of_parameters() const
    {
        return model_.number_of_parameters();
    }

    std::chrono::microseconds accuracy() const
    {
        return accuracy_;
    }

    double fitness() const
    {
        return fitness_;
    }

    std::chrono::microseconds query(const std::vector<double>& arguments) const
    {
        return model_.query(parameters_, arguments);
    }

    void update()
    {
        update_parameters();
        update_accuarcy();
        update_fitness();
    }

private:
    void update_parameters()
    {
        auto number_of_parameters = this->model_.number_of_parameters();

        if (number_of_parameters > 0 && number_of_parameters < this->model_.data_set().size())
        {
            Eigen::LevenbergMarquardt<model<Dataset>> lm(this->model_);
            lm.minimize(parameters_);
        }
    }

    void update_accuarcy()
    {
        double norm = 0.0;

        for (const auto& point : model_.data_set())
        {
            norm +=
                std::abs(static_cast<double>(model_.query(parameters_, point.arguments).count()) -
                         static_cast<double>(point.execution_time.count()));
        }

        norm /= model_.data_set().size();

        QUBUS_ASSERT(norm >= 0, "A norm shall be non-negative.");

        double max_norm = std::numeric_limits<std::chrono::microseconds::rep>::max() / 2;

        if (!std::isfinite(norm))
        {
            norm = max_norm;
        }
        else
        {
            norm = std::min(norm, max_norm);
        }

        accuracy_ = std::chrono::microseconds(static_cast<std::chrono::microseconds::rep>(norm));

        QUBUS_ASSERT(accuracy_.count() >= 0, "An accuary shall be non-negative.");
    }

    void update_fitness()
    {
        fitness_ = static_cast<double>(accuracy_.count()) +
                   2 * determine_number_of_expressions(model_.expr());
    }

    model<Dataset> model_;
    Eigen::VectorXd parameters_;
    std::chrono::microseconds accuracy_;
    double fitness_;
};

template <typename Dataset, typename Engine>
std::vector<regression_model<Dataset>>
perform_genetic_programming_step(std::vector<regression_model<Dataset>> old_generation,
                                 long int number_of_arguments, const Dataset& dataset,
                                 Engine& engine)
{
    constexpr long int elite_share = 10;
    constexpr long int immigrant_share = 10;
    constexpr long int mutation_rate = 20;

    constexpr long int wrap_rate = 10;
    constexpr long int combination_rate = 10;

    constexpr long int max_depth = model<Dataset>::max_depth;

    constexpr long int crossover_rate = 100 - mutation_rate - wrap_rate - combination_rate;

    const auto fitness_comperator = [](const regression_model<Dataset>& lhs,
                                       const regression_model<Dataset>& rhs) {
        return lhs.fitness() < rhs.fitness();
    };

    QUBUS_ASSERT(std::is_sorted(old_generation.begin(), old_generation.end(), fitness_comperator),
               "The old generation has to be sorted.");

    const auto population_size = old_generation.size();

    std::discrete_distribution<int> dist(
        {mutation_rate, wrap_rate, combination_rate, crossover_rate});
    std::uniform_int_distribution<long int> population_index_dist(0, population_size - 1);

    std::vector<regression_model<Dataset>> new_generation;
    new_generation.reserve(population_size);

    auto number_of_elites = (population_size * elite_share) / 100;

    for (std::size_t i = 0; i < number_of_elites; ++i)
    {
        new_generation.push_back(old_generation[i]);
        new_generation.back().update();
    }

    auto number_of_immigrants = (population_size * immigrant_share) / 100;

    for (std::size_t i = number_of_elites; i < number_of_elites + number_of_immigrants; ++i)
    {
        long int number_of_parameters = 0;

        auto new_expr =
            generate_random_expression(engine, number_of_arguments, number_of_parameters);

        auto new_model = model<Dataset>(std::move(new_expr), dataset);

        new_generation.push_back(regression_model<Dataset>(std::move(new_model), engine));
    }

    for (std::size_t i = number_of_elites + number_of_immigrants; i < population_size; ++i)
    {
        auto action = dist(engine);

        switch (action)
        {
        case 0:
        {
            auto index = population_index_dist(engine);

            const auto& reg_model = old_generation[index];

            auto new_expr = mutate_expression(reg_model.expr(), reg_model.number_of_arguments(),
                                              reg_model.number_of_parameters(), engine);

            if (determine_depth(*new_expr) <= max_depth)
            {
                remove_dead_parameters(*new_expr);
            }
            else
            {
                new_expr = clone(reg_model.expr());
            }

            auto new_model = model<Dataset>(std::move(new_expr), dataset);

            new_generation.push_back(regression_model<Dataset>(std::move(new_model), engine));

            break;
        }
        case 1:
        {
            auto index = population_index_dist(engine);

            const auto& reg_model = old_generation[index];

            auto new_expr = wrap_expression(reg_model.expr(), engine);

            if (determine_depth(*new_expr) > max_depth)
            {
                new_expr = clone(reg_model.expr());
            }

            auto new_model = model<Dataset>(std::move(new_expr), dataset);

            new_generation.push_back(regression_model<Dataset>(std::move(new_model), engine));

            break;
        }
        case 2:
        {
            auto index = population_index_dist(engine);
            auto index2 = population_index_dist(engine);

            const auto& mother = old_generation[index];
            const auto& father = old_generation[index2];

            auto new_expr = combine_expressions(mother.expr(), father.expr(), engine);

            if (determine_depth(*new_expr) > max_depth)
            {
                new_expr = clone(mother.expr());
            }

            auto new_model = model<Dataset>(std::move(new_expr), dataset);

            new_generation.push_back(regression_model<Dataset>(std::move(new_model), engine));

            break;
        }
        case 3:
        {
            auto index = population_index_dist(engine);
            auto index2 = population_index_dist(engine);

            const auto& mother = old_generation[index];
            const auto& father = old_generation[index2];

            auto new_expr = mix_expressions(mother.expr(), father.expr(), engine);

            if (determine_depth(*new_expr) <= max_depth)
            {
                remove_dead_parameters(*new_expr);
            }
            else
            {
                new_expr = clone(mother.expr());
            }

            auto new_model = model<Dataset>(std::move(new_expr), dataset);

            new_generation.push_back(regression_model<Dataset>(std::move(new_model), engine));

            break;
        }
        default:
            QUBUS_UNREACHABLE_BECAUSE("No case left.");
        }
    }

    std::sort(new_generation.begin(), new_generation.end(), fitness_comperator);

    QUBUS_ASSERT(std::is_sorted(new_generation.begin(), new_generation.end(), fitness_comperator),
               "The new generation has to be sorted.");

    return new_generation;
}
}

class symbolic_regression_impl
{
public:
    explicit symbolic_regression_impl(long int window_size_)
    : dataset_(util::integer_cast<dataset_type::size_type>(window_size_))
    {
        std::random_device dev;
        engine_ = std::mt19937(dev());
    }

    void add_datapoint(std::vector<double> arguments, std::chrono::microseconds execution_time)
    {
        dataset_.push_back(data_point{std::move(arguments), std::move(execution_time)});
    }

    boost::optional<std::chrono::microseconds> update()
    {
        if (dataset_.empty())
            return accuracy();

        if (models_.empty())
        {
            constexpr long int size_of_population = 200;

            models_.reserve(size_of_population);

            for (long int i = 0; i < size_of_population; ++i)
            {
                long int number_of_parameters = 0;

                auto new_model = model<dataset_type>(
                    generate_random_expression(engine_, dataset_.front().arguments.size(),
                                               number_of_parameters),
                    dataset_);

                models_.push_back(regression_model<dataset_type>(std::move(new_model), engine_));
            }

            const auto fitness_comperator = [](const regression_model<dataset_type>& lhs,
                                               const regression_model<dataset_type>& rhs) {
                return lhs.fitness() < rhs.fitness();
            };

            std::sort(models_.begin(), models_.end(), fitness_comperator);
        }
        else
        {
            models_ = perform_genetic_programming_step(
                std::move(models_), dataset_.front().arguments.size(), dataset_, engine_);
        }

        return accuracy();
    }

    boost::optional<std::chrono::microseconds> update_cheaply()
    {
        if (models_.empty())
            return boost::none;

        models_.front().update();

        return accuracy();
    }

    long int size_of_dataset() const
    {
        return util::integer_cast<long int>(dataset_.size());
    }

    boost::optional<std::chrono::microseconds> query(const std::vector<double>& arguments) const
    {
        if (models_.empty())
            return boost::none;

        return models_.front().query(arguments);
    }

    boost::optional<std::chrono::microseconds> accuracy() const
    {
        if (models_.empty())
            return boost::none;

        return models_.front().accuracy();
    }

private:
    using dataset_type = boost:: circular_buffer_space_optimized<data_point>;

    std::mt19937 engine_;

    dataset_type dataset_;
    std::vector<regression_model<dataset_type>> models_;
};

symbolic_regression::symbolic_regression(long int window_size_)
: impl_(std::make_unique<symbolic_regression_impl>(window_size_))
{
}

symbolic_regression::~symbolic_regression() = default;

symbolic_regression::symbolic_regression(symbolic_regression&&) = default;
symbolic_regression& symbolic_regression::operator=(symbolic_regression&&) = default;

void symbolic_regression::add_datapoint(std::vector<double> arguments,
                                        std::chrono::microseconds execution_time)
{
    impl_->add_datapoint(std::move(arguments), std::move(execution_time));
}

boost::optional<std::chrono::microseconds> symbolic_regression::update()
{
    return impl_->update();
}

boost::optional<std::chrono::microseconds> symbolic_regression::update_cheaply()
{
    return impl_->update_cheaply();
}

long int symbolic_regression::size_of_dataset() const
{
    return impl_->size_of_dataset();
}

boost::optional<std::chrono::microseconds>
symbolic_regression::query(const std::vector<double>& arguments) const
{
    return impl_->query(arguments);
}

boost::optional<std::chrono::microseconds> symbolic_regression::accuracy() const
{
    return impl_->accuracy();
}
}


/*******************************************************************************
 * Copyright (c) 2018-, UT-Battelle, LLC.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the MIT License
 * which accompanies this distribution.
 *
 * Contributors:
 *   Alexander J. McCaskey - initial API and implementation
 *   Thien Nguyen - implementation
 *******************************************************************************/
#include "clifford_gate_utils.hpp"
#include <Eigen/Dense>
#include <cassert>
#include <cmath>
#include <set>

namespace {
double mod_2pi(double theta) {
  while (theta > M_PI or theta <= -M_PI) {
    if (theta > M_PI) {
      theta = theta - 2 * M_PI;
    } else if (theta <= -M_PI) {
      theta = theta + 2 * M_PI;
    }
  }
  assert(theta >= -M_PI && theta <= M_PI);
  return theta;
}
} // namespace
namespace qcor {
namespace utils {
GenRot_t computeRotationInPauliFrame(const GenRot_t &in_rot,
                                     PauliLabel in_newPauli,
                                     PauliLabel in_netPauli) {
  auto [theta1, theta2, theta3] = in_rot;

  theta1 = mod_2pi(theta1);
  theta2 = mod_2pi(theta2);
  theta3 = mod_2pi(theta3);
  if (in_netPauli == PauliLabel::X || in_netPauli == PauliLabel::Z) {
    theta2 *= -1.0;
  }
  if (in_netPauli == PauliLabel::X || in_netPauli == PauliLabel::Y) {
    theta3 *= -1.0;
    theta1 *= -1.0;
  }

  // if x or y
  if (in_newPauli == PauliLabel::X || in_newPauli == PauliLabel::Y) {
    theta1 = -theta1 + M_PI;
    theta2 = theta2 + M_PI;
  }
  // if y or z
  if (in_newPauli == PauliLabel::Y || in_newPauli == PauliLabel::Z) {
    theta1 = theta1 + M_PI;
  }

  // make everything between - pi and pi
  theta1 = mod_2pi(theta1);
  theta2 = mod_2pi(theta2);
  theta3 = mod_2pi(theta3);
  return std::make_tuple(theta1, theta2, theta3);
}

GenRot_t invU3Gate(const GenRot_t &in_rot) {
  auto [theta1, theta2, theta3] = in_rot;
  theta1 = mod_2pi(M_PI - theta1);
  theta2 = mod_2pi(-theta2);
  theta3 = mod_2pi(-theta3 + M_PI);
  return std::make_tuple(theta1, theta2, theta3);
}

SrepDict_t computeGateSymplecticRepresentations(
    const std::vector<std::string> &in_gateList) {
  static const SrepDict_t standardDict = []() {
    std::unordered_map<std::string, Smatrix_t> complete_s_dict;
    std::unordered_map<std::string, Pvec_t> complete_p_dict;

    // The Pauli gates
    complete_s_dict["I"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_s_dict["X"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_s_dict["Y"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_s_dict["Z"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};

    complete_p_dict["I"] = std::vector<int>{0, 0};
    complete_p_dict["X"] = std::vector<int>{0, 2};
    complete_p_dict["Y"] = std::vector<int>{2, 2};
    complete_p_dict["Z"] = std::vector<int>{2, 0};

    // Five single qubit gates that each represent one of five classes of
    // Cliffords that equivalent up to Pauli gates and are not equivalent to
    // idle (that class is covered by any one of the Pauli gates above).
    complete_s_dict["H"] = std::vector<std::vector<int>>{{0, 1}, {1, 0}};
    complete_s_dict["P"] = std::vector<std::vector<int>>{{1, 0}, {1, 1}};
    complete_s_dict["PH"] = std::vector<std::vector<int>>{{0, 1}, {1, 1}};
    complete_s_dict["HP"] = std::vector<std::vector<int>>{{1, 1}, {1, 0}};
    complete_s_dict["HPH"] = std::vector<std::vector<int>>{{1, 1}, {0, 1}};
    complete_p_dict["H"] = std::vector<int>{0, 0};
    complete_p_dict["P"] = std::vector<int>{1, 0};
    complete_p_dict["PH"] = std::vector<int>{0, 1};
    complete_p_dict["HP"] = std::vector<int>{3, 0};
    complete_p_dict["HPH"] = std::vector<int>{0, 3};
    // The full 1-qubit Cliffor group, using the same labelling as in
    // extras.rb.group
    complete_s_dict["C0"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_p_dict["C0"] = std::vector<int>{0, 0};
    complete_s_dict["C1"] = std::vector<std::vector<int>>{{1, 1}, {1, 0}};
    complete_p_dict["C1"] = std::vector<int>{1, 0};
    complete_s_dict["C2"] = std::vector<std::vector<int>>{{0, 1}, {1, 1}};
    complete_p_dict["C2"] = std::vector<int>{0, 1};
    complete_s_dict["C3"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_p_dict["C3"] = std::vector<int>{0, 2};
    complete_s_dict["C4"] = std::vector<std::vector<int>>{{1, 1}, {1, 0}};
    complete_p_dict["C4"] = std::vector<int>{1, 2};
    complete_s_dict["C5"] = std::vector<std::vector<int>>{{0, 1}, {1, 1}};
    complete_p_dict["C5"] = std::vector<int>{0, 3};
    complete_s_dict["C6"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_p_dict["C6"] = std::vector<int>{2, 2};
    complete_s_dict["C7"] = std::vector<std::vector<int>>{{1, 1}, {1, 0}};
    complete_p_dict["C7"] = std::vector<int>{3, 2};
    complete_s_dict["C8"] = std::vector<std::vector<int>>{{0, 1}, {1, 1}};
    complete_p_dict["C8"] = std::vector<int>{2, 3};
    complete_s_dict["C9"] = std::vector<std::vector<int>>{{1, 0}, {0, 1}};
    complete_p_dict["C9"] = std::vector<int>{2, 0};
    complete_s_dict["C10"] = std::vector<std::vector<int>>{{1, 1}, {1, 0}};
    complete_p_dict["C10"] = std::vector<int>{3, 0};
    complete_s_dict["C11"] = std::vector<std::vector<int>>{{0, 1}, {1, 1}};
    complete_p_dict["C11"] = std::vector<int>{2, 1};
    complete_s_dict["C12"] = std::vector<std::vector<int>>{{0, 1}, {1, 0}};
    complete_p_dict["C12"] = std::vector<int>{0, 0};
    complete_s_dict["C13"] = std::vector<std::vector<int>>{{1, 1}, {0, 1}};
    complete_p_dict["C13"] = std::vector<int>{0, 1};
    complete_s_dict["C14"] = std::vector<std::vector<int>>{{1, 0}, {1, 1}};
    complete_p_dict["C14"] = std::vector<int>{1, 0};
    complete_s_dict["C15"] = std::vector<std::vector<int>>{{0, 1}, {1, 0}};
    complete_p_dict["C15"] = std::vector<int>{0, 2};
    complete_s_dict["C16"] = std::vector<std::vector<int>>{{1, 1}, {0, 1}};
    complete_p_dict["C16"] = std::vector<int>{0, 3};
    complete_s_dict["C17"] = std::vector<std::vector<int>>{{1, 0}, {1, 1}};
    complete_p_dict["C17"] = std::vector<int>{1, 2};
    complete_s_dict["C18"] = std::vector<std::vector<int>>{{0, 1}, {1, 0}};
    complete_p_dict["C18"] = std::vector<int>{2, 2};
    complete_s_dict["C19"] = std::vector<std::vector<int>>{{1, 1}, {0, 1}};
    complete_p_dict["C19"] = std::vector<int>{2, 3};
    complete_s_dict["C20"] = std::vector<std::vector<int>>{{1, 0}, {1, 1}};
    complete_p_dict["C20"] = std::vector<int>{3, 2};
    complete_s_dict["C21"] = std::vector<std::vector<int>>{{0, 1}, {1, 0}};
    complete_p_dict["C21"] = std::vector<int>{2, 0};
    complete_s_dict["C22"] = std::vector<std::vector<int>>{{1, 1}, {0, 1}};
    complete_p_dict["C22"] = std::vector<int>{2, 1};
    complete_s_dict["C23"] = std::vector<std::vector<int>>{{1, 0}, {1, 1}};
    complete_p_dict["C23"] = std::vector<int>{3, 0};
    // The CNOT gate, CPHASE gate, and SWAP gate.
    complete_s_dict["CNOT"] = std::vector<std::vector<int>>{
        {1, 0, 0, 0}, {1, 1, 0, 0}, {0, 0, 1, 1}, {0, 0, 0, 1}};
    complete_s_dict["CPHASE"] = std::vector<std::vector<int>>{
        {1, 0, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0}, {1, 0, 0, 1}};
    complete_s_dict["SWAP"] = std::vector<std::vector<int>>{
        {0, 1, 0, 0}, {1, 0, 0, 0}, {0, 0, 0, 1}, {0, 0, 1, 0}};
    complete_p_dict["CNOT"] = std::vector<int>{0, 0, 0, 0};
    complete_p_dict["CPHASE"] = std::vector<int>{0, 0, 0, 0};
    complete_p_dict["SWAP"] = std::vector<int>{0, 0, 0, 0};

    SrepDict_t result;
    for (const auto &[k, v] : complete_s_dict) {
      assert(complete_p_dict.find(k) != complete_p_dict.end());
      result[k] = std::make_pair(v, complete_p_dict[k]);
    }
    return result;
  }();

  if (!in_gateList.empty()) {
    // Filter a subset of gates:
    SrepDict_t result;
    for (const auto &gateLabel : in_gateList) {
      const auto iter = standardDict.find(gateLabel);
      assert(iter != standardDict.end());
      result[gateLabel] = iter->second;
    }
    return result;
  }
  return standardDict;
}

Srep_t computeLayerSymplecticRepresentations(const CliffordGateLayer_t &layers,
                                             int nQubits,
                                             const SrepDict_t &srep_dict) {
  // Initilize
  Pvec_t p(2 * nQubits, 0);
  Smatrix_t s(2 * nQubits, p);
  std::set<int> seen_qubits;
  for (const auto &[name, operands] : layers) {
    const auto iter = srep_dict.find(name);
    assert(iter != srep_dict.end());
    const auto &[matrix, phase] = iter->second;
    const auto nforgate = operands.size();
    assert(nforgate > 0);
    for (int ind1 = 0; ind1 < operands.size(); ++ind1) {
      const auto qindex1 = operands[ind1];
      assert(seen_qubits.find(qindex1) == seen_qubits.end());
      seen_qubits.emplace(qindex1);
      for (int ind2 = 0; ind2 < operands.size(); ++ind2) {
        const auto qindex2 = operands[ind2];
        // Put in the symp matrix elements
        s[qindex1][qindex2] = matrix[ind1][ind2];
        s[qindex1][qindex2 + nQubits] = matrix[ind1][ind2 + nforgate];
        s[qindex1 + nQubits][qindex2] = matrix[ind1 + nforgate][ind2];
        s[qindex1 + nQubits][qindex2 + nQubits] =
            matrix[ind1 + nforgate][ind2 + nforgate];
      }
      // Put in the phase elements
      p[qindex1] = phase[ind1];
      p[qindex1 + nQubits] = phase[ind1 + nforgate];
    }
  }

  return std::make_pair(s, p);
}

Srep_t composeCliffords(const Srep_t &C1, const Srep_t &C2) {
  assert(C1.first.size() == C2.first.size());
  assert(C1.second.size() == C2.second.size());
  assert(C1.first.size() % 2 == 0);
  const int n = C1.first.size() / 2;
  using Mat_t = Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic>;
  using Vec_t = Eigen::Matrix<int, Eigen::Dynamic, 1>;
  Mat_t s1(2 * n, 2 * n);
  Mat_t s2(2 * n, 2 * n);
  Vec_t p1(2 * n);
  Vec_t p2(2 * n);
  // Load to eigen for processing
  for (int i = 0; i < 2 * n; ++i) {
    for (int j = 0; j < 2 * n; ++j) {
      s1(i, j) = C1.first[i][j];
      s2(i, j) = C2.first[i][j];
    }
    p1(i) = C1.second[i];
    p2(i) = C2.second[i];
  }

  Mat_t s = s2 * s1;
  // Mod 2
  for (int i = 0; i < 2 * n; ++i) {
    for (int j = 0; j < 2 * n; ++j) {
      s(i, j) = s(i, j) % 2;
    }
  }

  Mat_t u = Mat_t::Zero(2 * n, 2 * n);
  u(Eigen::seq(n, 2 * n - 1), Eigen::seq(0, n - 1)) = Mat_t::Identity(n, n);

  Vec_t vec1 = s1.transpose() * p2;
  Mat_t inner = (s2.transpose() * u) * s2;
  const auto strictly_upper_triangle = [](const Mat_t &m) -> Mat_t {
    auto l = m.rows();
    Mat_t out = m;

    for (int i = 0; i < l; ++i) {
      for (int j = 0; j < i + 1; ++j) {
        out(i, j) = 0;
      }
    }
    return out;
  };

  // Returns a diagonal matrix containing the diagonal of m.
  const auto diagonal_as_matrix = [](const Mat_t &m) -> Mat_t {
    auto l = m.rows();
    Mat_t out = Mat_t::Zero(l, l);
    for (int i = 0; i < l; ++i) {
      out(i, i) = m(i, i);
    }

    return out;
  };

  // Returns a 1D array containing the diagonal of the input square 2D array m.
  const auto diagonal_as_vec = [](const Mat_t &m) -> Mat_t {
    auto l = m.rows();
    Vec_t vec = Vec_t::Zero(l);
    for (int i = 0; i < l; ++i) {
      vec(i) = m(i, i);
    }

    return vec;
  };

  Mat_t matrix = 2 * strictly_upper_triangle(inner) + diagonal_as_matrix(inner);
  Vec_t vec2 = diagonal_as_vec((s1.transpose() * matrix) * s1);
  Vec_t vec3 = s1.transpose() * diagonal_as_vec(inner);
  Vec_t p = p1 + vec1 + vec2 - vec3;
  for (int i = 0; i < p.size(); ++i) {
    p(i) = p(i) % 4;
  }

  Pvec_t p_res(2 * n, 0);
  Smatrix_t s_res(2 * n, p_res);
  for (int i = 0; i < 2 * n; ++i) {
    for (int j = 0; j < 2 * n; ++j) {
      s_res[i][j] = s(i, j);
    }
    p_res[i] = p(i);
  }
  return std::make_pair(s_res, p_res);
}

Srep_t computeCircuitSymplecticRepresentations(
    const std::vector<CliffordGateLayer_t> &layers, int nQubits,
    const SrepDict_t &srep_dict) {
  // Initilize
  Pvec_t p(2 * nQubits, 0);
  Smatrix_t s(2 * nQubits, p);
  // S must be initialized as an identity matrix
  for (int i = 0; i < 2 * nQubits; ++i) {
    s[i][i] = 1;
  }
  for (const auto &layer : layers) {
    const auto layerRep =
        computeLayerSymplecticRepresentations(layer, nQubits, srep_dict);

    std::tie(s, p) = composeCliffords(std::make_pair(s, p), layerRep);
  }
  return std::make_pair(s, p);
}

std::vector<PauliLabel> find_pauli_labels(const Pvec_t &pvec) {
  assert(pvec.size() % 2 == 0);
  const auto n = pvec.size() / 2;
  std::vector<int> v(n, 0);
  for (int i = 0; i < n; ++i) {
    v[i] = (pvec[i] / 2) + 2 * (pvec[n + i] / 2);
  }
  // [0,0]=I, [2,0]=Z, [0,2]=X, and [2,2]=Y.
  std::vector<PauliLabel> result;
  for (const auto &el : v) {
    assert(el < 4);
    static const std::vector<PauliLabel> ARRAY{PauliLabel::I, PauliLabel::Z,
                                               PauliLabel::X, PauliLabel::Y};
    result.emplace_back(ARRAY[el]);
  }
  return result;
}
} // namespace utils
} // namespace qcor


// Copyright (c) 2020 CNES
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <tuple>

namespace pyinterp::detail::math {

namespace detail {

/// Calculates the area of a polygon.
template <template <class> class Point, typename Strategy, typename T>
inline auto calculate_area(boost::geometry::model::polygon<Point<T>>& polygon,
                           const Strategy& strategy) -> T {
  auto result = boost::geometry::area(polygon, strategy);
  if (result < 0) {
    boost::geometry::reverse(polygon);
    result = boost::geometry::area(polygon, strategy);
  }
  return result;
}

/// Calculate the area of a polygon. If the area is less than one epsilon, the
/// calculated area is set to zero.
template <template <class> class Point, typename Strategy, typename T>
inline auto calculate_and_normalize_area(
    boost::geometry::model::polygon<Point<T>>& polygon,
    const Strategy& strategy, const double total_area) -> T {
  auto result = calculate_area<Point, Strategy, T>(polygon, strategy);
  if (result > total_area || std::fabs(result) < 1e-12) {
    result = 0;
  }
  return result;
}

}  // namespace detail

/// Linear binning 2D
///
/// p01 (D/ABCD)         p11 (C/ABCD)
///   ┌────────────┰──────┐
///   │     A      ┃ B    │
///   ┝━━━━━━━━━━━━╋━━━━━━┥ j
///   │            ┃      │
///   │     C      ┃ D    │
///   │            ┃      │
///   └────────────┸──────┘
/// p00 (B/ABCD)   i     p10 (A/ABCD)
///
/// @param p Query point (i, j)
/// @param p00 Point of coordinate (x0, y0)
/// @param p11 Point of coordinate (x1, y1)
/// @return a tuple that contains
///   * w00 Weight for the coordinate (x0, y0)
///   * w01 Weight for the coordinate (x0, y1)
///   * w11 Weight for the coordinate (x1, y1)
///   * w10 Weight for the coordinate (x1, y0)
template <template <class> class Point, typename Strategy, typename T>
auto binning_2d(const Point<T>& pij, const Point<T>& p00, const Point<T>& p11,
                Strategy const& strategy) -> std::tuple<T, T, T, T> {
  // Coordinates of the grid points deducted.
  const auto p01 =
      Point<T>{boost::geometry::get<0>(p00), boost::geometry::get<1>(p11)};
  const auto p10 =
      Point<T>{boost::geometry::get<0>(p11), boost::geometry::get<1>(p00)};

  // Coordinates of intersections between the point of interest and the grid
  // points.
  const auto p0j =
      Point<T>{boost::geometry::get<0>(p00), boost::geometry::get<1>(pij)};
  const auto pi1 =
      Point<T>{boost::geometry::get<0>(pij), boost::geometry::get<1>(p11)};
  const auto p1j =
      Point<T>{boost::geometry::get<0>(p11), boost::geometry::get<1>(pij)};
  const auto pi0 =
      Point<T>{boost::geometry::get<0>(pij), boost::geometry::get<1>(p00)};

  // Polygon to process
  auto polygon_a =
      boost::geometry::model::polygon<Point<T>>{{pij, p0j, p01, pi1, pij}};
  auto polygon_b =
      boost::geometry::model::polygon<Point<T>>{{pij, pi1, p11, p1j, pij}};
  auto polygon_c =
      boost::geometry::model::polygon<Point<T>>{{pij, pi0, p00, p0j, pij}};
  auto polygon_d =
      boost::geometry::model::polygon<Point<T>>{{pij, p1j, p10, pi0, pij}};
  auto grid_cell =
      boost::geometry::model::polygon<Point<T>>{{p00, p01, p11, p10, p00}};

  // Area calculation.
  auto total_area =
      detail::calculate_area<Point, Strategy, T>(grid_cell, strategy);
  auto area_a = detail::calculate_and_normalize_area<Point, Strategy, T>(
      polygon_a, strategy, total_area);
  auto area_b = detail::calculate_and_normalize_area<Point, Strategy, T>(
      polygon_b, strategy, total_area);
  auto area_c = detail::calculate_and_normalize_area<Point, Strategy, T>(
      polygon_c, strategy, total_area);
  auto area_d = detail::calculate_and_normalize_area<Point, Strategy, T>(
      polygon_d, strategy, total_area);

  return std::make_tuple(area_b / total_area, area_d / total_area,
                         area_c / total_area, area_a / total_area);
}

}  // namespace pyinterp::detail::math


#pragma once

#include <BilinearPatch.hpp>
#include <boost/optional.hpp>
#include <ScalarType.hpp>

template <class VertexType> struct Bezier;

template <class CurveType>
struct UniformCoonsPatchTraits {
	typedef typename VertexType<CurveType>::type VertexType;
	typedef CurveType LeftCurveType;
	typedef CurveType RightCurveType;
	typedef CurveType BottomCurveType;
	typedef CurveType TopCurveType;
};

template <class Traits>
struct CoonsPatch {
	typedef typename Traits::VertexType VertexType;
	typedef typename ScalarType<VertexType>::type ScalarType;

	typedef typename Traits::LeftCurveType LeftCurveType;
	typedef typename Traits::RightCurveType RightCurveType;
	typedef typename Traits::BottomCurveType BottomCurveType;
	typedef typename Traits::TopCurveType TopCurveType;

	CoonsPatch() { }

	template <class L, class R, class B, class T>
	CoonsPatch(L const & left, R const & right, B const & bottom, T const & top)
		: left_(left), right_(right), bottom_(bottom), top_(top) { }

	VertexType operator () (ScalarType u, ScalarType v) const {
		VertexType a = interpolate(u, left_(v), right_(v)),
			b = interpolate(v, bottom_(u), top_(u)),
			c = get_patch_value(u, v);

		return interpolate(.5,
			interpolate(-1, a, c),
			interpolate(-1, b, c)
		);
	}

	LeftCurveType & get_left() { return left_; }
	LeftCurveType const & get_left() const { return left_; }

	RightCurveType & get_right() { return right_; }
	RightCurveType const & get_right() const { return right_; }

	BottomCurveType & get_bottom() { return bottom_; }
	BottomCurveType const & get_bottom() const { return bottom_; }

	TopCurveType & get_top() { return top_; }
	TopCurveType const & get_top() const { return top_; }

	private:
		void init_patch() const {
			if (bilinear_patch_) { return; }

			bilinear_patch_.reset(
				BilinearPatch<VertexType>(
					bottom_(static_cast<ScalarType>(0.0)), bottom_(static_cast<ScalarType>(1.0)),
					top_(static_cast<ScalarType>(0.0)), top_(static_cast<ScalarType>(1.0))
				)
			);
		}

		VertexType get_patch_value(ScalarType u, ScalarType v) const {
			init_patch();
			return (*bilinear_patch_)(u, v);
		}

		LeftCurveType left_;
		RightCurveType right_;
		BottomCurveType bottom_;
		TopCurveType top_;

		mutable boost::optional<BilinearPatch<VertexType> > bilinear_patch_;
};