#ifdef TORSTEN_MPI_DYN

#include <stan/math.hpp>
#include <stan/math/rev/core.hpp>
#include <stan/math/torsten/mpi/dynamic_load.hpp>
#include <test/unit/math/rev/mat/fun/util.hpp>
#include <test/unit/math/torsten/pmx_ode_test_fixture.hpp>
#include <test/unit/math/prim/arr/functor/harmonic_oscillator.hpp>
#include <nvector/nvector_serial.h>
#include <boost/mpi.hpp>
#include <test/unit/util.hpp>
#include <gtest/gtest.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <memory>
#include <iostream>
#include <fstream>
#include <limits>
#include <vector>
#include <chrono>
#include <ctime>
#include <random>

namespace torsten {
  namespace dsolve {

    template<typename... Args>
    inline auto torsten::dsolve::pmx_ode_group_mpi_functor::operator()(Args&&... args) const {
      if (id == 0) { const TwoCptNeutModelODE f; return f(std::forward<Args>(args)...); }

      // return default
      TwoCptNeutModelODE f;
      return f(std::forward<Args>(args)...);
    }

    template<>
    struct pmx_ode_group_mpi_functor_id<TwoCptNeutModelODE> { static constexpr int value = 0; };
  }
}

using torsten::dsolve::PMXCvodesFwdSystem;
using torsten::dsolve::pmx_ode_group_mpi_functor;
using torsten::dsolve::integrator_id;
using torsten::pmx_integrate_ode_group_adams;
using torsten::pmx_integrate_ode_adams;
using Eigen::MatrixXd;
using Eigen::Matrix;
using stan::math::var;
using std::vector;

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_set_use_uniform_work) {
  torsten::mpi::Envionment::init();

  // size of population
  const int np = 8;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > theta_m (np, theta);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);

  load.init_buf = std::vector<int>{0, 1, static_cast<int>(y0.size()), static_cast<int>(theta.size()), static_cast<int>(x_r.size()), static_cast<int>(x_i.size()), 0, 0, 0};
  load.set_work(1, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-10, 10000);
  std::vector<double> y0_slave;
  double t0_slave;
  std::vector<double> ts_slave;
  std::vector<double> theta_slave;
  std::vector<double> x_r_slave;
  std::vector<int> x_i_slave;
  double rtol_slave;
  double atol_slave;
  int max_num_step_slave;
  load.use_work(y0_slave, t0_slave, ts_slave, theta_slave, x_r_slave, x_i_slave, rtol_slave, atol_slave, max_num_step_slave);
  torsten::test::test_val(t0_slave, t0);
  torsten::test::test_val(y0_slave, y0);
  torsten::test::test_val(ts_slave, ts);
  torsten::test::test_val(theta_slave, theta);
  torsten::test::test_val(x_r_slave, x_r);
  torsten::test::test_val(rtol_slave, 1.e-8);
  torsten::test::test_val(atol_slave, 1.e-10);
  torsten::test::test_val(max_num_step_slave, 10000);
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_set_use_non_uniform_work) {
  torsten::mpi::Envionment::init();

  // size of population
  const int np = 3;
  std::vector<std::vector<double>> tss(3, ts);
  tss[0].resize(ts.size() - 1);
  tss[2].resize(ts.size() - 3);

  vector<var> theta_var = stan::math::to_var(theta);

  vector<size_t> len{tss[0].size(), tss[1].size(), tss[2].size()};
  vector<double> ts_m;
  ts_m.reserve(tss[0].size() + tss[1].size() + tss[2].size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), tss[i].begin(), tss[i].end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > theta_m (np, theta);
  x_r.push_back(1.0); vector<vector<double> > x_r_m (np, x_r);
  x_i.push_back(1.0); vector<vector<int> > x_i_m (np, x_i);

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);

  load.init_buf = std::vector<int>{0, 1, static_cast<int>(y0.size()), static_cast<int>(theta.size()), static_cast<int>(x_r.size()), static_cast<int>(x_i.size()), 0, 0, 0};
  load.set_work(1, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-10, 10000);
  std::vector<double> y0_slave;
  double t0_slave;
  std::vector<double> ts_slave;
  std::vector<double> theta_slave;
  std::vector<double> x_r_slave;
  std::vector<int> x_i_slave;
  double rtol_slave;
  double atol_slave;
  int max_num_step_slave;
  load.use_work(y0_slave, t0_slave, ts_slave, theta_slave, x_r_slave, x_i_slave, rtol_slave, atol_slave, max_num_step_slave);
  torsten::test::test_val(y0_slave, y0);
  torsten::test::test_val(theta_slave, theta);
  torsten::test::test_val(x_r_slave, x_r);
  torsten::test::test_val(rtol_slave, 1.e-8);
  torsten::test::test_val(atol_slave, 1.e-10);
  torsten::test::test_val(max_num_step_slave, 10000);

  for (size_t i = 0; i < tss.size(); ++i) {
    load.set_work(i, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-10, 10000);
    load.use_work(y0_slave, t0_slave, ts_slave, theta_slave, x_r_slave, x_i_slave, rtol_slave, atol_slave, max_num_step_slave);
    torsten::test::test_val(ts_slave, tss[i]);
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_single_work) {
  torsten::mpi::Envionment::init();

  // size of population
  const int np = 1;
  std::vector<double> ts0 {ts};

  vector<var> theta_var = stan::math::to_var(theta);

  vector<int> len(np, ts0.size());
  vector<double> ts_m;
  ts_m.reserve(np * ts0.size());
  for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

  vector<vector<double> > y0_m (np, y0);
  vector<vector<var> > theta_var_m (np, theta_var);
  vector<vector<double> > theta_m (np, theta);
  vector<vector<double> > x_r_m (np, x_r);
  vector<vector<int> > x_i_m (np, x_i);

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);

  torsten::dsolve::pmx_ode_group_mpi_functor fdyn(0);
  if (pmx_comm.rank == 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    MatrixXd res = load.master(f, 1, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
    MatrixXd sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0, t0, ts, theta, x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
    torsten::test::test_val(res, sol);
    load.kill_slaves();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_multiple_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    for (int np = 1; np < 8; ++np) {
      vector<int> len(np, ts0.size());
      vector<double> ts_m;
      ts_m.reserve(np * ts0.size());
      for (int i = 0; i < np; ++i) ts_m.insert(ts_m.end(), ts0.begin(), ts0.end());

      vector<vector<double> > y0_m (np, y0);
      vector<vector<double> > theta_m (np, theta);
      vector<vector<double> > x_r_m (np, x_r);
      vector<vector<int> > x_i_m (np, x_i);

      MatrixXd res = load.master(f, 1, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      MatrixXd sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0, t0, ts, theta, x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
      for (int i = 0; i < np; ++i) {
        MatrixXd res_i = res.block(0, ts.size() * i, y0.size(), ts.size());
        torsten::test::test_val(res_i, sol);
      }
    }

    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_bdf_master_slave_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    const int np = 7;
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    vector<double> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<double> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<double> > y0_m (np, y0);
    vector<vector<double> > theta_m (np, theta);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    size_t integ_id = integrator_id<PMXCvodesFwdSystem<TwoCptNeutModelODE, double, double, double, CV_BDF, AD>>::value;
    pmx_ode_group_mpi_functor fdyn(0);
    MatrixXd res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
    int ic = 0;
    for (int i = 0; i < np; ++i) {
      std::vector<double> ts_i(ts0.begin(), ts0.begin() + len[i]);
      MatrixXd sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0, t0, ts_i, theta, x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
      MatrixXd res_i = res.block(0, ic, y0.size(), len[i]);
      torsten::test::test_val(res_i, sol);
      ic += len[i];
    }
    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_ts_par_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    const int np = len.size();
    vector<var> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<var> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<double> > y0_m (np, y0);
    vector<vector<double> > theta_m (np, theta);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, var, double, double, CV_BDF, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts1(its, its + len[i]);
        std::vector<var> ts2(ts0.begin(), ts0.begin() + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0, t0, ts2, theta, x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(ts1, ts2, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, var, double, double, CV_ADAMS, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts1(its, its + len[i]);
        std::vector<var> ts2(ts0.begin(), ts0.begin() + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0, t0, ts2, theta, x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(ts1, ts2, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using scheme_t = boost::numeric::odeint::runge_kutta_dopri5<std::vector<double>, double, std::vector<double>, double>;
      using Ode = dsolve::PMXOdeintSystem<TwoCptNeutModelODE, var, double, double>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts1(its, its + len[i]);
        std::vector<var> ts2(ts0.begin(), ts0.begin() + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_rk45(f, y0, t0, ts2, theta, x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(ts1, ts2, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }
    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_theta_par_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    const int np = len.size();
    vector<double> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<double> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<double> > y0_m (np, y0);
    vector<vector<var> > theta_m (np);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    // perturb theta_m
    theta_m[0] = std::vector<var>{10, 15, 35, 105, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[1] = std::vector<var>{9, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[2] = std::vector<var>{10, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[3] = std::vector<var>{10, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[4] = std::vector<var>{11, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[5] = std::vector<var>{11, 14, 35, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[6] = std::vector<var>{10, 14, 35, 103, 2, 120, 4, 0.17, 2.0e-4};

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, double, double, var, CV_BDF, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0, t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(theta_m[i], theta_m[i], res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, double, double, var, CV_ADAMS, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0, t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(theta_m[i], theta_m[i], res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using scheme_t = boost::numeric::odeint::runge_kutta_dopri5<std::vector<double>, double, std::vector<double>, double>;
      using Ode = dsolve::PMXOdeintSystem<TwoCptNeutModelODE, double, double, var>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_rk45(f, y0, t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(theta_m[i], theta_m[i], res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }
    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_y0_par_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    const int np = len.size();
    vector<double> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<double> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<var> > y0_m (np);
    vector<vector<double> > theta_m (np);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    // perturb theta_m
    theta_m[0] = std::vector<double>{10, 15, 35, 105, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[1] = std::vector<double>{9, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[2] = std::vector<double>{10, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[3] = std::vector<double>{10, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[4] = std::vector<double>{11, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[5] = std::vector<double>{11, 14, 35, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[6] = std::vector<double>{10, 14, 35, 103, 2, 120, 4, 0.17, 2.0e-4};

    y0_m[0] = std::vector<var>{100.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[1] = std::vector<var>{88.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[2] = std::vector<var>{88.0, 12.0, 12.0, 13.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[3] = std::vector<var>{88.0, 12.0, 12.0, 13.0, 9.0, 11.0, 9.0, 10.0};
    y0_m[4] = std::vector<var>{95.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[5] = std::vector<var>{95.0, 12.0, 12.0, 13.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[6] = std::vector<var>{106.0, 12.0, 12.0, 13.0, 9.0, 11.0, 9.0, 10.0};

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, double, var, double, CV_BDF, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(y0_m[i], y0_m[i], res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, double, var, double, CV_ADAMS, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(y0_m[i], y0_m[i], res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using scheme_t = boost::numeric::odeint::runge_kutta_dopri5<std::vector<double>, double, std::vector<double>, double>;
      using Ode = dsolve::PMXOdeintSystem<TwoCptNeutModelODE, double, var, double>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_rk45(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        torsten::test::test_grad(y0_m[i], y0_m[i], res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }
    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_y0_theta_par_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    const int np = len.size();
    vector<double> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<double> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<var> > y0_m (np);
    vector<vector<var> > theta_m (np);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    // perturb theta_m
    theta_m[0] = std::vector<var>{10, 15, 35, 105, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[1] = std::vector<var>{9, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[2] = std::vector<var>{10, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[3] = std::vector<var>{10, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[4] = std::vector<var>{11, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[5] = std::vector<var>{11, 14, 35, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[6] = std::vector<var>{10, 14, 35, 103, 2, 120, 4, 0.17, 2.0e-4};

    y0_m[0] = std::vector<var>{100.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[1] = std::vector<var>{88.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[2] = std::vector<var>{88.0, 12.0, 12.0, 13.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[3] = std::vector<var>{88.0, 12.0, 12.0, 13.0, 9.0, 11.0, 9.0, 10.0};
    y0_m[4] = std::vector<var>{95.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[5] = std::vector<var>{95.0, 12.0, 12.0, 13.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[6] = std::vector<var>{106.0, 12.0, 12.0, 13.0, 9.0, 11.0, 9.0, 10.0};

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, double, var, var, CV_ADAMS, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), theta_m[i].begin(), theta_m[i].end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, double, var, var, CV_BDF, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), theta_m[i].begin(), theta_m[i].end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using scheme_t = boost::numeric::odeint::runge_kutta_dopri5<std::vector<double>, double, std::vector<double>, double>;
      using Ode = dsolve::PMXOdeintSystem<TwoCptNeutModelODE, double, var, var>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<double>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<double> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_rk45(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), theta_m[i].begin(), theta_m[i].end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }
    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_ts_y0_theta_par_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    const int np = len.size();
    vector<var> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<var> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<var> > y0_m (np);
    vector<vector<var> > theta_m (np);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    // perturb theta_m
    theta_m[0] = std::vector<var>{10, 15, 35, 105, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[1] = std::vector<var>{9, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[2] = std::vector<var>{10, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[3] = std::vector<var>{10, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[4] = std::vector<var>{11, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[5] = std::vector<var>{11, 14, 35, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[6] = std::vector<var>{10, 14, 35, 103, 2, 120, 4, 0.17, 2.0e-4};

    y0_m[0] = std::vector<var>{100.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[1] = std::vector<var>{88.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[2] = std::vector<var>{88.0, 12.0, 12.0, 13.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[3] = std::vector<var>{88.0, 12.0, 12.0, 13.0, 9.0, 11.0, 9.0, 10.0};
    y0_m[4] = std::vector<var>{95.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[5] = std::vector<var>{95.0, 12.0, 12.0, 13.0, 10.0, 10.0, 10.0, 10.0};
    y0_m[6] = std::vector<var>{106.0, 12.0, 12.0, 13.0, 9.0, 11.0, 9.0, 10.0};

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, var, var, var, CV_BDF, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), theta_m[i].begin(), theta_m[i].end());
        pars.insert(pars.end(), ts_i.begin(), ts_i.end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, var, var, var, CV_ADAMS, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), theta_m[i].begin(), theta_m[i].end());
        pars.insert(pars.end(), ts_i.begin(), ts_i.end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using scheme_t = boost::numeric::odeint::runge_kutta_dopri5<std::vector<double>, double, std::vector<double>, double>;
      using Ode = dsolve::PMXOdeintSystem<TwoCptNeutModelODE, var, var, var>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_rk45(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), theta_m[i].begin(), theta_m[i].end());
        pars.insert(pars.end(), ts_i.begin(), ts_i.end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }
    load.kill_slaves();
  }
}

TEST_F(TorstenOdeTest_neutropenia, mpi_dynamic_load_master_slave_ts_y0_par_multiple_non_uniform_work) {
  torsten::mpi::Envionment::init();

  torsten::mpi::Communicator pmx_comm(torsten::mpi::Session<NUM_TORSTEN_COMM>::env, MPI_COMM_WORLD);
  std::vector<double> ts0 {ts};

  if (pmx_comm.rank > 0) {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_SLAVE> load(pmx_comm);
    load.slave();
  } else {
    torsten::mpi::PMXDynamicLoad<TORSTEN_MPI_DYN_MASTER> load(pmx_comm);
    vector<int> len{19, 17, 10, 14, 18, 17, 16};
    const int np = len.size();
    vector<var> ts_m;
    for (int i = 0; i < np; ++i) {
      std::vector<var> ts_i(ts0.begin(), ts0.begin() + len[i]);
      ts_m.insert(ts_m.end(), ts_i.begin(), ts_i.end());
    }

    vector<vector<var> > y0_m (np, stan::math::to_var(y0));
    vector<vector<double> > theta_m (np);
    vector<vector<double> > x_r_m (np, x_r);
    vector<vector<int> > x_i_m (np, x_i);

    // perturb theta_m
    theta_m[0] = std::vector<double>{10, 15, 35, 105, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[1] = std::vector<double>{9, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[2] = std::vector<double>{10, 14, 34, 104, 2, 125, 5, 0.17, 2.0e-4};
    theta_m[3] = std::vector<double>{10, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[4] = std::vector<double>{11, 14, 34, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[5] = std::vector<double>{11, 14, 35, 101, 2, 124, 4, 0.17, 2.0e-4};
    theta_m[6] = std::vector<double>{10, 14, 35, 103, 2, 120, 4, 0.17, 2.0e-4};

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, var, var, double, CV_ADAMS, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_adams(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), ts_i.begin(), ts_i.end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using Ode = PMXCvodesFwdSystem<TwoCptNeutModelODE, var, var, double, CV_BDF, AD>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_bdf(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), ts_i.begin(), ts_i.end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }

    {
      using scheme_t = boost::numeric::odeint::runge_kutta_dopri5<std::vector<double>, double, std::vector<double>, double>;
      using Ode = dsolve::PMXOdeintSystem<TwoCptNeutModelODE, var, var, double>;
      size_t integ_id = integrator_id<Ode>::value;
      Matrix<var, -1, -1> res = load.master(f, integ_id, y0_m, t0, len, ts_m, theta_m, x_r_m, x_i_m, 1.e-8, 1.e-8, 10000);
      std::vector<var>::const_iterator its = ts_m.begin();
      int ic = 0;
      for (int i = 0; i < np; ++i) {
        std::vector<var> ts_i(its, its + len[i]);
        Matrix<var, -1, -1> sol = torsten::to_matrix(pmx_integrate_ode_rk45(f, y0_m[i], t0, ts_i, theta_m[i], x_r, x_i, 0, 1.e-8, 1.e-8, 10000));
        Matrix<var, -1, -1> res_i = res.block(0, ic, y0.size(), len[i]);
        std::vector<var> pars;
        pars.insert(pars.end(), y0_m[i].begin(), y0_m[i].end());
        pars.insert(pars.end(), ts_i.begin(), ts_i.end());
        torsten::test::test_grad(pars, pars, res_i, sol);
        ic += len[i];
        its += len[i];
      }
    }
    load.kill_slaves();
  }
}

#endif


/**
 * @file methods/ann/visitor/output_parameter_visitor.hpp
 * @author Marcus Edel
 *
 * This file provides an abstraction for the OutputParameter() function for
 * different layers and automatically directs any parameter to the right layer type.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#ifndef MLPACK_METHODS_ANN_VISITOR_OUTPUT_PARAMETER_VISITOR_HPP
#define MLPACK_METHODS_ANN_VISITOR_OUTPUT_PARAMETER_VISITOR_HPP

#include <mlpack/methods/ann/layer/layer_traits.hpp>
#include <mlpack/methods/ann/layer/layer_types.hpp>

#include <boost/variant.hpp>

namespace mlpack {
namespace ann {

/**
 * OutputParameterVisitor exposes the output parameter of the given module.
 */
class OutputParameterVisitor : public boost::static_visitor<arma::mat&>
{
 public:
  //! Return the output parameter set.
  template<typename LayerType>
  arma::mat& operator()(LayerType* layer) const;

  arma::mat& operator()(MoreTypes layer) const;
};

} // namespace ann
} // namespace mlpack

// Include implementation.
#include "output_parameter_visitor_impl.hpp"

#endif


#ifndef _PYVIENNACL_VECTOR_H
#define _PYVIENNACL_VECTOR_H

#include "common.hpp"

#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/vector_of_vector.hpp>

#include <viennacl/linalg/sparse_matrix_operations.hpp>
#include <viennacl/compressed_matrix.hpp>
#include <viennacl/compressed_compressed_matrix.hpp>
#include <viennacl/coordinate_matrix.hpp>
#include <viennacl/ell_matrix.hpp>
#include <viennacl/sliced_ell_matrix.hpp>
#include <viennacl/hyb_matrix.hpp>

namespace ublas = boost::numeric::ublas;

template <class ScalarType>
class cpu_sparse_matrix_wrapper
{
public:
  typedef ublas::mapped_matrix<ScalarType, ublas::row_major> ublas_sparse_t;

  void update_places()
  {

    if (!_dirty)
      return;

    typedef typename ublas_sparse_t::iterator1 it1;
    typedef typename ublas_sparse_t::iterator2 it2;

    if (_places)
      delete _places;

    _places = new bp::list;

    for (it1 i = cpu_sparse_matrix.begin1();
         i != cpu_sparse_matrix.end1(); ++i) {
      for (it2 j = i.begin(); j != i.end(); ++j) {

	if (cpu_sparse_matrix(j.index1(), j.index2()))
          _places->append(bp::make_tuple(j.index1(), j.index2()));

      }
    }

    _dirty = false;

  }

  bp::list places()
  {
    update_places();

    return *_places;
  }

  vcl::vcl_size_t nnz()
  {
    update_places();

    return bp::len(*_places);
  }

  cpu_sparse_matrix_wrapper() : _dirty(true)
  {
    _places = NULL;
    cpu_sparse_matrix = ublas_sparse_t(0,0,0);
  }

  cpu_sparse_matrix_wrapper(vcl::vcl_size_t _size1, vcl::vcl_size_t _size2)
    : _dirty(true)
  {
    _places = NULL;
    cpu_sparse_matrix = ublas_sparse_t(_size1, _size2);
  }

  cpu_sparse_matrix_wrapper(vcl::vcl_size_t _size1,
                                vcl::vcl_size_t _size2,
                                vcl::vcl_size_t _nnz) : _dirty(true)
  {
    _places = NULL;
    cpu_sparse_matrix = ublas_sparse_t(_size1, _size2, _nnz);
  }

  cpu_sparse_matrix_wrapper(const cpu_sparse_matrix_wrapper& w)
    : cpu_sparse_matrix(w.cpu_sparse_matrix), _dirty(true)
  {
    _places = NULL;
  }

  template<class SparseT>
  cpu_sparse_matrix_wrapper(const SparseT& vcl_sparse_matrix)
    : _dirty(true)
  {
    cpu_sparse_matrix = ublas_sparse_t(vcl_sparse_matrix.size1(),
                                       vcl_sparse_matrix.size2());
    vcl::copy(vcl_sparse_matrix, cpu_sparse_matrix);
    _places = NULL;
  }

  cpu_sparse_matrix_wrapper(const ublas_sparse_t& new_sparse_matrix)
    : _dirty(true)
  {
    cpu_sparse_matrix = new_sparse_matrix;
    _places = NULL;
  }

  cpu_sparse_matrix_wrapper(const np::ndarray& array) : _dirty(true)
  {
    _places = NULL;

    int d = array.get_nd();
    if (d != 2) {
      PyErr_SetString(PyExc_TypeError, "Can only create a matrix from a 2-D array!");
      bp::throw_error_already_set();
    }

    vcl::vcl_size_t n = array.shape(0);
    vcl::vcl_size_t m = array.shape(1);

    cpu_sparse_matrix = ublas_sparse_t(n, m);

    for (vcl::vcl_size_t i = 0; i < n; ++i) {
      for (vcl::vcl_size_t j = 0; j < m; ++j) {
	ScalarType val = bp::extract<ScalarType>(array[i][j]);
	if (val != 0)
          insert_entry(i, j, val);
      }
    }
  }

  np::ndarray as_ndarray()
  {
    np::dtype dt = np::dtype::get_builtin<ScalarType>();
    bp::tuple shape = bp::make_tuple(size1(), size2());
    np::ndarray array = np::zeros(shape, dt);

    typedef typename ublas_sparse_t::iterator1 it1;
    typedef typename ublas_sparse_t::iterator2 it2;

    for (it1 i = cpu_sparse_matrix.begin1();
         i != cpu_sparse_matrix.end1(); ++i) {
      for (it2 j = i.begin(); j != i.end(); ++j) {

	if (cpu_sparse_matrix(j.index1(), j.index2()) != 0) {
          array[j.index1()][j.index2()] = get_entry(j.index1(), j.index2());
        }

      }
    }

    return array;
  }

  template<class SparseT>
  vcl::tools::shared_ptr<SparseT>
  as_vcl_sparse_matrix()
  {
    SparseT* vcl_sparse_matrix = new SparseT(*_context);
    vcl::copy(cpu_sparse_matrix, *vcl_sparse_matrix);
    return vcl::tools::shared_ptr<SparseT>(vcl_sparse_matrix);
  }

  template<class SparseT>
  vcl::tools::shared_ptr<SparseT>
  as_vcl_sparse_matrix_with_size()
  {
    SparseT* vcl_sparse_matrix = new SparseT(size1(), size2(), nnz(),
                                             *_context);
    vcl::copy(cpu_sparse_matrix, *vcl_sparse_matrix);
    return vcl::tools::shared_ptr<SparseT>(vcl_sparse_matrix);
  }

  vcl::vcl_size_t size1() const
  {
    return cpu_sparse_matrix.size1();
  }

  vcl::vcl_size_t size2() const
  {
    return cpu_sparse_matrix.size2();
  }

  void resize(vcl::vcl_size_t _size1, vcl::vcl_size_t _size2)
  {

    if ((_size1 == size1()) && (_size2 == size2()))
      return;

    // TODO NB: ublas compressed matrix does not support preserve on resize
    //          so this below is annoyingly hacky...

    ublas_sparse_t temp(cpu_sparse_matrix); // Incurs a copy of all the data!!
    cpu_sparse_matrix.resize(_size1, _size2, false); // preserve == false!

    typedef typename ublas_sparse_t::iterator1 it1;
    typedef typename ublas_sparse_t::iterator2 it2;

    for (it1 i = temp.begin1(); i != temp.end1(); ++i) {
      for (it2 j = i.begin(); j != i.end(); ++j) {
	if ((temp(j.index1(), j.index2()) != 0)
            && (j.index1() < _size1) && (j.index2() < _size2)) {
          cpu_sparse_matrix(j.index1(), j.index2()) = temp(j.index1(), j.index2());
        }
      }
    }

  }

  void set_vcl_context(vcl::context *ctx) {
    _context = ctx;
  }

  vcl::context* get_vcl_context() const {
    return _context;
  }

  void set_entry(vcl::vcl_size_t n, vcl::vcl_size_t m, ScalarType val)
  {
    if (n >= size1()) {
      if (m >= size2())
        resize(n+1, m+1);
      else
        resize(n+1, size2());
    } else {
      if (m >= size2())
        resize(size1(), m+1);
    }

    cpu_sparse_matrix(n, m) = val;
    _dirty = true;
  }

  // Need this because bp cannot deal with operator()
  ScalarType get_entry(vcl::vcl_size_t n, vcl::vcl_size_t m) const
  {
    return cpu_sparse_matrix(n, m);
  }

  void erase_entry(vcl::vcl_size_t n, vcl::vcl_size_t m)
  {
    cpu_sparse_matrix.erase_element(n, m);
    _dirty = true;
  }

  void insert_entry(vcl::vcl_size_t n, vcl::vcl_size_t m, ScalarType t)
  {
    cpu_sparse_matrix.insert_element(n, m, t);
    _dirty = true;
  }

private:
  ublas_sparse_t cpu_sparse_matrix;
  bp::list* _places;
  vcl::context* _context;
  bool _dirty;

};

#endif


/*
 Copyright (C) 2018 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

/*! \file ored/configuration/commoditycurveconfig.hpp
    \brief Commodity curve configuration class
    \ingroup configuration
*/

#pragma once

#include <ored/configuration/bootstrapconfig.hpp>
#include <ored/configuration/curveconfig.hpp>
#include <boost/optional/optional.hpp>

namespace ore {
namespace data {

/*! Class for holding information on a set of instruments used in bootstrapping a piecewise price curve.
    \ingroup configuration
*/
class PriceSegment : public XMLSerializable {
public:
    //! Type of price segment being represented, i.e. type of instrument in the price segment.
    enum class Type { Future, AveragingFuture, AveragingSpot, AveragingOffPeakPower, OffPeakPowerDaily };

    //! Class to store quotes used in building daily off-peak power quotes.
    class OffPeakDaily : public XMLSerializable {
    public:
        //! Constructor.
        OffPeakDaily();

        //! Detailed constructor.
        OffPeakDaily(
            const std::vector<std::string>& offPeakQuotes,
            const std::vector<std::string>& peakQuotes);

        const std::vector<std::string>& offPeakQuotes() const { return offPeakQuotes_; }
        const std::vector<std::string>& peakQuotes() const { return peakQuotes_; }

        void fromXML(XMLNode* node) override;
        XMLNode* toXML(XMLDocument& doc) override;

    private:
        std::vector<std::string> offPeakQuotes_;
        std::vector<std::string> peakQuotes_;
    };

    //! \name Constructors
    //@{
    //! Default constructor
    PriceSegment();
    //! Detailed constructor
    PriceSegment(const std::string& type,
        const std::string& conventionsId,
        const std::vector<std::string>& quotes,
        const boost::optional<unsigned short>& priority = boost::none,
        const boost::optional<OffPeakDaily>& offPeakDaily = boost::none,
        const std::string& peakPriceCurveId = "",
        const std::string& peakPriceCalendar = "");
    //@}

    //! \name Inspectors
    //@{
    Type type() const;
    const std::string& conventionsId() const;
    const std::vector<std::string>& quotes() const;
    const boost::optional<unsigned short>& priority() const;
    const boost::optional<OffPeakDaily>& offPeakDaily() const;
    const std::string& peakPriceCurveId() const;
    const std::string& peakPriceCalendar() const;
    bool empty() const;
    //@}

    //! \name Serialisation
    //@{
    void fromXML(XMLNode* node) override;
    XMLNode* toXML(XMLDocument& doc) override;
    //@}

private:
    //! Populate quotes
    void populateQuotes();

    std::string strType_;
    std::string conventionsId_;
    std::vector<std::string> quotes_;
    boost::optional<unsigned short> priority_;
    boost::optional<OffPeakDaily> offPeakDaily_;
    std::string peakPriceCurveId_;
    std::string peakPriceCalendar_;

    bool empty_;
    Type type_;
};

//! Commodity curve configuration
/*!
    \ingroup configuration
*/
class CommodityCurveConfig : public CurveConfig {
public:
    /*! The type of commodity curve that has been configured:
         - Direct: if the commodity price curve is built from commodity forward quotes
         - CrossCurrency: if the commodity price curve is implied from a price curve in a different currency
         - Basis: if the commodity price curve is built from basis quotes
         - Piecewise: if the commodity price curve is bootstrapped from sets of instruments
    */
    enum class Type { Direct, CrossCurrency, Basis, Piecewise };

    //! \name Constructors
    //@{
    //! Default constructor
    CommodityCurveConfig() : type_(Type::Direct), extrapolation_(true), addBasis_(true),
        monthOffset_(0), averageBase_(true) {}

    //! Detailed constructor for Direct commodity curve configuration
    CommodityCurveConfig(const std::string& curveId, const std::string& curveDescription, const std::string& currency,
                         const std::vector<std::string>& quotes, const std::string& commoditySpotQuote = "",
                         const std::string& dayCountId = "A365", const std::string& interpolationMethod = "Linear",
                         bool extrapolation = true, const std::string& conventionsId = "");

    //! Detailed constructor for CrossCurrency commodity curve configuration
    CommodityCurveConfig(const std::string& curveId, const std::string& curveDescription, const std::string& currency,
                         const std::string& basePriceCurveId, const std::string& baseYieldCurveId,
                         const std::string& yieldCurveId, bool extrapolation = true);

    //! Detailed constructor for Basis commodity curve configuration
    CommodityCurveConfig(const std::string& curveId, const std::string& curveDescription, const std::string& currency,
                         const std::string& basePriceCurveId, const std::string& baseConventionsId,
                         const std::vector<std::string>& basisQuotes, const std::string& basisConventionsId,
                         const std::string& dayCountId = "A365", const std::string& interpolationMethod = "Linear",
                         bool extrapolation = true, bool addBasis = true, QuantLib::Natural monthOffset = 0,
                         bool averageBase = true);

    //! Detailed constructor for Piecewise commodity curve configuration
    CommodityCurveConfig(const std::string& curveId, const std::string& curveDescription, const std::string& currency,
                         const std::vector<PriceSegment>& priceSegments, const std::string& dayCountId = "A365",
                         const std::string& interpolationMethod = "Linear", bool extrapolation = true,
                         const boost::optional<BootstrapConfig>& bootstrapConfig = boost::none);
    //@}

    //! \name Serialisation
    //@{
    void fromXML(XMLNode* node) override;
    XMLNode* toXML(XMLDocument& doc) override;
    //@}

    //! \name Inspectors
    //@{
    const Type& type() const { return type_; }
    const std::string& currency() const { return currency_; }
    const std::string& commoditySpotQuoteId() const { return commoditySpotQuoteId_; }
    const std::string& dayCountId() const { return dayCountId_; }
    const std::string& interpolationMethod() const { return interpolationMethod_; }
    const std::string& basePriceCurveId() const { return basePriceCurveId_; }
    const std::string& baseYieldCurveId() const { return baseYieldCurveId_; }
    const std::string& yieldCurveId() const { return yieldCurveId_; }
    bool extrapolation() const { return extrapolation_; }
    const vector<string>& fwdQuotes() const { return fwdQuotes_; }
    const std::string& conventionsId() const { return conventionsId_; }
    const std::string& baseConventionsId() const { return baseConventionsId_; }
    bool addBasis() const { return addBasis_; }
    QuantLib::Natural monthOffset() const { return monthOffset_; }
    bool averageBase() const { return averageBase_; }
    const std::map<unsigned short, PriceSegment>& priceSegments() const { return priceSegments_; }
    const boost::optional<BootstrapConfig>& bootstrapConfig() const { return bootstrapConfig_; }
    //@}

    //! \name Setters
    //@{
    Type& type() { return type_; }
    std::string& currency() { return currency_; }
    std::string& commoditySpotQuoteId() { return commoditySpotQuoteId_; }
    std::string& dayCountId() { return dayCountId_; }
    std::string& interpolationMethod() { return interpolationMethod_; }
    std::string& basePriceCurveId() { return basePriceCurveId_; }
    std::string& baseYieldCurveId() { return baseYieldCurveId_; }
    std::string& yieldCurveId() { return yieldCurveId_; }
    bool& extrapolation() { return extrapolation_; }
    std::string& conventionsId() { return conventionsId_; }
    std::string& baseConventionsId() { return baseConventionsId_; }
    bool& addBasis() { return addBasis_; }
    QuantLib::Natural& monthOffset() { return monthOffset_; }
    bool& averageBase() { return averageBase_; }
    void setPriceSegments(const std::map<unsigned short, PriceSegment>& priceSegments) {
        priceSegments_ = priceSegments;
    }
    void setBootstrapConfig(const BootstrapConfig& bootstrapConfig) { bootstrapConfig_ = bootstrapConfig; }
    //@}

private:
    //! Populate any dependent curve IDs.
    void populateRequiredCurveIds();

    //! Process price segments when configuring a Piecewise curve.
    void processSegments(std::vector<PriceSegment> priceSegments);

    Type type_;
    vector<string> fwdQuotes_;
    std::string currency_;
    std::string commoditySpotQuoteId_;
    std::string dayCountId_;
    std::string interpolationMethod_;
    std::string basePriceCurveId_;
    std::string baseYieldCurveId_;
    std::string yieldCurveId_;
    bool extrapolation_;
    std::string conventionsId_;
    std::string baseConventionsId_;
    bool addBasis_;
    QuantLib::Natural monthOffset_;
    bool averageBase_;
    /*! The map key is the internal priority of the price segment and does not necessarily map the PriceSegment's
        priority member value. We are allowing here for the priority to be unspecified in the PriceSegment during
        configuration.
    */
    std::map<unsigned short, PriceSegment> priceSegments_;
    boost::optional<BootstrapConfig> bootstrapConfig_;
};

} // namespace data
} // namespace ore


///////////////////////////////////////////////////////////////
//  Copyright 2020 Madhur Chauhan.
//  Copyright 2020 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_MP_ADD_UNSIGNED_ADDC_32_HPP
#define BOOST_MP_ADD_UNSIGNED_ADDC_32_HPP

#include <boost/multiprecision/cpp_int/intel_intrinsics.hpp>
#include <boost/multiprecision/detail/assert.hpp>

namespace boost { namespace multiprecision { namespace backends {

template <class CppInt1, class CppInt2, class CppInt3>
inline BOOST_MP_CXX14_CONSTEXPR void add_unsigned_constexpr(CppInt1& result, const CppInt2& a, const CppInt3& b) noexcept(is_non_throwing_cpp_int<CppInt1>::value)
{
   using ::boost::multiprecision::std_constexpr::swap;
   //
   // This is the generic, C++ only version of addition.
   // It's also used for all constexpr branches, hence the name.
   // Nothing fancy, just let uintmax_t take the strain:
   //
   double_limb_type carry = 0;
   std::size_t         m(0), x(0);
   std::size_t         as = a.size();
   std::size_t         bs = b.size();
   minmax(as, bs, m, x);
   if (x == 1)
   {
      bool s = a.sign();
      result = static_cast<double_limb_type>(*a.limbs()) + static_cast<double_limb_type>(*b.limbs());
      result.sign(s);
      return;
   }
   result.resize(x, x);
   typename CppInt2::const_limb_pointer pa     = a.limbs();
   typename CppInt3::const_limb_pointer pb     = b.limbs();
   typename CppInt1::limb_pointer       pr     = result.limbs();
   typename CppInt1::limb_pointer       pr_end = pr + m;

   if (as < bs)
      swap(pa, pb);

   // First where a and b overlap:
   while (pr != pr_end)
   {
      carry += static_cast<double_limb_type>(*pa) + static_cast<double_limb_type>(*pb);
#ifdef __MSVC_RUNTIME_CHECKS
      *pr = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
#else
      *pr = static_cast<limb_type>(carry);
#endif
      carry >>= CppInt1::limb_bits;
      ++pr, ++pa, ++pb;
   }
   pr_end += x - m;
   // Now where only a has digits:
   while (pr != pr_end)
   {
      if (!carry)
      {
         if (pa != pr)
            std_constexpr::copy(pa, pa + (pr_end - pr), pr);
         break;
      }
      carry += static_cast<double_limb_type>(*pa);
#ifdef __MSVC_RUNTIME_CHECKS
      *pr = static_cast<limb_type>(carry & ~static_cast<limb_type>(0));
#else
      *pr = static_cast<limb_type>(carry);
#endif
      carry >>= CppInt1::limb_bits;
      ++pr, ++pa;
   }
   if (carry)
   {
      // We overflowed, need to add one more limb:
      result.resize(x + 1, x + 1);
      if (result.size() > x)
         result.limbs()[x] = static_cast<limb_type>(1u);
   }
   result.normalize();
   result.sign(a.sign());
}
//
// Core subtraction routine for all non-trivial cpp_int's:
//
template <class CppInt1, class CppInt2, class CppInt3>
inline BOOST_MP_CXX14_CONSTEXPR void subtract_unsigned_constexpr(CppInt1& result, const CppInt2& a, const CppInt3& b) noexcept(is_non_throwing_cpp_int<CppInt1>::value)
{
   using ::boost::multiprecision::std_constexpr::swap;
   //
   // This is the generic, C++ only version of subtraction.
   // It's also used for all constexpr branches, hence the name.
   // Nothing fancy, just let uintmax_t take the strain:
   //
   double_limb_type borrow = 0;
   std::size_t         m(0), x(0);
   minmax(a.size(), b.size(), m, x);
   //
   // special cases for small limb counts:
   //
   if (x == 1)
   {
      bool      s  = a.sign();
      limb_type al = *a.limbs();
      limb_type bl = *b.limbs();
      if (bl > al)
      {
         ::boost::multiprecision::std_constexpr::swap(al, bl);
         s = !s;
      }
      result = al - bl;
      result.sign(s);
      return;
   }
   // This isn't used till later, but comparison has to occur before we resize the result,
   // as that may also resize a or b if this is an inplace operation:
   int c = a.compare_unsigned(b);
   // Set up the result vector:
   result.resize(x, x);
   // Now that a, b, and result are stable, get pointers to their limbs:
   typename CppInt2::const_limb_pointer pa      = a.limbs();
   typename CppInt3::const_limb_pointer pb      = b.limbs();
   typename CppInt1::limb_pointer       pr      = result.limbs();
   bool                                 swapped = false;
   if (c < 0)
   {
      swap(pa, pb);
      swapped = true;
   }
   else if (c == 0)
   {
      result = static_cast<limb_type>(0);
      return;
   }

   std::size_t i = 0;
   // First where a and b overlap:
   while (i < m)
   {
      borrow = static_cast<double_limb_type>(pa[i]) - static_cast<double_limb_type>(pb[i]) - borrow;
      pr[i]  = static_cast<limb_type>(borrow);
      borrow = (borrow >> CppInt1::limb_bits) & 1u;
      ++i;
   }
   // Now where only a has digits, only as long as we've borrowed:
   while (borrow && (i < x))
   {
      borrow = static_cast<double_limb_type>(pa[i]) - borrow;
      pr[i]  = static_cast<limb_type>(borrow);
      borrow = (borrow >> CppInt1::limb_bits) & 1u;
      ++i;
   }
   // Any remaining digits are the same as those in pa:
   if ((x != i) && (pa != pr))
      std_constexpr::copy(pa + i, pa + x, pr + i);
   BOOST_MP_ASSERT(0 == borrow);

   //
   // We may have lost digits, if so update limb usage count:
   //
   result.normalize();
   result.sign(a.sign());
   if (swapped)
      result.negate();
}

#ifdef BOOST_MP_HAS_IMMINTRIN_H
//
// This is the key addition routine where all the argument types are non-trivial cpp_int's:
//
//
// This optimization is limited to: GCC, LLVM, ICC (Intel), MSVC for x86_64 and i386.
// If your architecture and compiler supports ADC intrinsic, please file a bug
//
// As of May, 2020 major compilers don't recognize carry chain though adc
// intrinsics are used to hint compilers to use ADC and still compilers don't
// unroll the loop efficiently (except LLVM) so manual unrolling is done.
//
// Also note that these intrinsics were only introduced by Intel as part of the
// ADX processor extensions, even though the addc instruction has been available
// for basically all x86 processors.  That means gcc-9, clang-9, msvc-14.2 and up
// are required to support these intrinsics.
//
template <class CppInt1, class CppInt2, class CppInt3>
inline BOOST_MP_CXX14_CONSTEXPR void add_unsigned(CppInt1& result, const CppInt2& a, const CppInt3& b) noexcept(is_non_throwing_cpp_int<CppInt1>::value)
{
#ifndef BOOST_MP_NO_CONSTEXPR_DETECTION
   if (BOOST_MP_IS_CONST_EVALUATED(a.size()))
   {
      add_unsigned_constexpr(result, a, b);
   }
   else
#endif
   {
      using std::swap;

      // Nothing fancy, just let uintmax_t take the strain:
      std::size_t m(0), x(0);
      std::size_t as = a.size();
      std::size_t bs = b.size();
      minmax(as, bs, m, x);
      if (x == 1)
      {
         bool s = a.sign();
         result = static_cast<double_limb_type>(*a.limbs()) + static_cast<double_limb_type>(*b.limbs());
         result.sign(s);
         return;
      }
      result.resize(x, x);
      typename CppInt2::const_limb_pointer pa = a.limbs();
      typename CppInt3::const_limb_pointer pb = b.limbs();
      typename CppInt1::limb_pointer       pr = result.limbs();

      if (as < bs)
         swap(pa, pb);
      // First where a and b overlap:
      std::size_t      i = 0;
      unsigned char carry = 0;
#if defined(BOOST_MSVC) && !defined(BOOST_HAS_INT128) && defined(_M_X64)
      //
      // Special case for 32-bit limbs on 64-bit architecture - we can process
      // 2 limbs with each instruction.
      //
      for (; i + 8 <= m; i += 8)
      {
         carry = _addcarry_u64(carry, *(unsigned long long*)(pa + i + 0), *(unsigned long long*)(pb + i + 0), (unsigned long long*)(pr + i));
         carry = _addcarry_u64(carry, *(unsigned long long*)(pa + i + 2), *(unsigned long long*)(pb + i + 2), (unsigned long long*)(pr + i + 2));
         carry = _addcarry_u64(carry, *(unsigned long long*)(pa + i + 4), *(unsigned long long*)(pb + i + 4), (unsigned long long*)(pr + i + 4));
         carry = _addcarry_u64(carry, *(unsigned long long*)(pa + i + 6), *(unsigned long long*)(pb + i + 6), (unsigned long long*)(pr + i + 6));
      }
#else
      for (; i + 4 <= m; i += 4)
      {
         carry = ::boost::multiprecision::detail::addcarry_limb(carry, pa[i + 0], pb[i + 0], pr + i);
         carry = ::boost::multiprecision::detail::addcarry_limb(carry, pa[i + 1], pb[i + 1], pr + i + 1);
         carry = ::boost::multiprecision::detail::addcarry_limb(carry, pa[i + 2], pb[i + 2], pr + i + 2);
         carry = ::boost::multiprecision::detail::addcarry_limb(carry, pa[i + 3], pb[i + 3], pr + i + 3);
      }
#endif
      for (; i < m; ++i)
         carry = ::boost::multiprecision::detail::addcarry_limb(carry, pa[i], pb[i], pr + i);
      for (; i < x && carry; ++i)
         // We know carry is 1, so we just need to increment pa[i] (ie add a literal 1) and capture the carry:
         carry = ::boost::multiprecision::detail::addcarry_limb(0, pa[i], 1, pr + i);
      if (i == x && carry)
      {
         // We overflowed, need to add one more limb:
         result.resize(x + 1, x + 1);
         if (result.size() > x)
            result.limbs()[x] = static_cast<limb_type>(1u);
      }
      else if ((x != i) && (pa != pr))
         // Copy remaining digits only if we need to:
         std_constexpr::copy(pa + i, pa + x, pr + i);
      result.normalize();
      result.sign(a.sign());
   }
}

template <class CppInt1, class CppInt2, class CppInt3>
inline BOOST_MP_CXX14_CONSTEXPR void subtract_unsigned(CppInt1& result, const CppInt2& a, const CppInt3& b) noexcept(is_non_throwing_cpp_int<CppInt1>::value)
{
#ifndef BOOST_MP_NO_CONSTEXPR_DETECTION
   if (BOOST_MP_IS_CONST_EVALUATED(a.size()))
   {
      subtract_unsigned_constexpr(result, a, b);
   }
   else
#endif
   {
      using std::swap;

      // Nothing fancy, just let uintmax_t take the strain:
      std::size_t         m(0), x(0);
      minmax(a.size(), b.size(), m, x);
      //
      // special cases for small limb counts:
      //
      if (x == 1)
      {
         bool      s = a.sign();
         limb_type al = *a.limbs();
         limb_type bl = *b.limbs();
         if (bl > al)
         {
            ::boost::multiprecision::std_constexpr::swap(al, bl);
            s = !s;
         }
         result = al - bl;
         result.sign(s);
         return;
      }
      // This isn't used till later, but comparison has to occur before we resize the result,
      // as that may also resize a or b if this is an inplace operation:
      int c = a.compare_unsigned(b);
      // Set up the result vector:
      result.resize(x, x);
      // Now that a, b, and result are stable, get pointers to their limbs:
      typename CppInt2::const_limb_pointer pa = a.limbs();
      typename CppInt3::const_limb_pointer pb = b.limbs();
      typename CppInt1::limb_pointer       pr = result.limbs();
      bool                                 swapped = false;
      if (c < 0)
      {
         swap(pa, pb);
         swapped = true;
      }
      else if (c == 0)
      {
         result = static_cast<limb_type>(0);
         return;
      }

      std::size_t i = 0;
      unsigned char borrow = 0;
      // First where a and b overlap:
#if defined(BOOST_MSVC) && !defined(BOOST_HAS_INT128) && defined(_M_X64)
      //
      // Special case for 32-bit limbs on 64-bit architecture - we can process
      // 2 limbs with each instruction.
      //
      for (; i + 8 <= m; i += 8)
      {
         borrow = _subborrow_u64(borrow, *reinterpret_cast<const unsigned long long*>(pa + i), *reinterpret_cast<const unsigned long long*>(pb + i), reinterpret_cast<unsigned long long*>(pr + i));
         borrow = _subborrow_u64(borrow, *reinterpret_cast<const unsigned long long*>(pa + i + 2), *reinterpret_cast<const unsigned long long*>(pb + i + 2), reinterpret_cast<unsigned long long*>(pr + i + 2));
         borrow = _subborrow_u64(borrow, *reinterpret_cast<const unsigned long long*>(pa + i + 4), *reinterpret_cast<const unsigned long long*>(pb + i + 4), reinterpret_cast<unsigned long long*>(pr + i + 4));
         borrow = _subborrow_u64(borrow, *reinterpret_cast<const unsigned long long*>(pa + i + 6), *reinterpret_cast<const unsigned long long*>(pb + i + 6), reinterpret_cast<unsigned long long*>(pr + i + 6));
      }
#else
      for(; i + 4 <= m; i += 4)
      {
         borrow = boost::multiprecision::detail::subborrow_limb(borrow, pa[i], pb[i], pr + i);
         borrow = boost::multiprecision::detail::subborrow_limb(borrow, pa[i + 1], pb[i + 1], pr + i + 1);
         borrow = boost::multiprecision::detail::subborrow_limb(borrow, pa[i + 2], pb[i + 2], pr + i + 2);
         borrow = boost::multiprecision::detail::subborrow_limb(borrow, pa[i + 3], pb[i + 3], pr + i + 3);
      }
#endif
      for (; i < m; ++i)
         borrow = boost::multiprecision::detail::subborrow_limb(borrow, pa[i], pb[i], pr + i);

      // Now where only a has digits, only as long as we've borrowed:
      while (borrow && (i < x))
      {
         borrow = boost::multiprecision::detail::subborrow_limb(borrow, pa[i], 0, pr + i);
         ++i;
      }
      // Any remaining digits are the same as those in pa:
      if ((x != i) && (pa != pr))
         std_constexpr::copy(pa + i, pa + x, pr + i);
      BOOST_MP_ASSERT(0 == borrow);

      //
      // We may have lost digits, if so update limb usage count:
      //
      result.normalize();
      result.sign(a.sign());
      if (swapped)
         result.negate();
   }  // constepxr.
}

#else

template <class CppInt1, class CppInt2, class CppInt3>
inline BOOST_MP_CXX14_CONSTEXPR void add_unsigned(CppInt1& result, const CppInt2& a, const CppInt3& b) noexcept(is_non_throwing_cpp_int<CppInt1>::value)
{
   add_unsigned_constexpr(result, a, b);
}

template <class CppInt1, class CppInt2, class CppInt3>
inline BOOST_MP_CXX14_CONSTEXPR void subtract_unsigned(CppInt1& result, const CppInt2& a, const CppInt3& b) noexcept(is_non_throwing_cpp_int<CppInt1>::value)
{
   subtract_unsigned_constexpr(result, a, b);
}

#endif

} } }  // namespaces

#endif


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

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

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

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

#ifndef BOOST_GEOMETRY_ALGORITHMS_PERIMETER_HPP
#define BOOST_GEOMETRY_ALGORITHMS_PERIMETER_HPP

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/closure.hpp>
#include <boost/geometry/geometries/concepts/check.hpp>
#include <boost/geometry/strategies/default_length_result.hpp>
#include <boost/geometry/algorithms/length.hpp>
#include <boost/geometry/algorithms/detail/calculate_null.hpp>
#include <boost/geometry/algorithms/detail/calculate_sum.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

// Default perimeter is 0.0, specializations implement calculated values
template <typename Tag, typename Geometry, typename Strategy>
struct perimeter : detail::calculate_null
    <
        typename default_length_result<Geometry>::type,
        Geometry,
        Strategy
    >
{};

template <typename Geometry, typename Strategy>
struct perimeter<ring_tag, Geometry, Strategy>
    : detail::length::range_length
        <
            Geometry,
            Strategy,
            closure<Geometry>::value
        >
{};

template <typename Polygon, typename Strategy>
struct perimeter<polygon_tag, Polygon, Strategy>
    : detail::calculate_polygon_sum
        <
            typename default_length_result<Polygon>::type,
            Polygon,
            Strategy,
            detail::length::range_length
                <
                    typename ring_type<Polygon>::type,
                    Strategy,
                    closure<Polygon>::value
                >
        >
{};

// box,n-sphere: to be implemented

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

/*!
\brief \brief_calc{perimeter}
\ingroup perimeter
\details The function perimeter returns the perimeter of a geometry,
    using the default distance-calculation-strategy
\tparam Geometry \tparam_geometry
\param geometry \param_geometry
\return \return_calc{perimeter}

\qbk{[include reference/algorithms/perimeter.qbk]}
 */
template<typename Geometry>
inline typename default_length_result<Geometry>::type perimeter(
        Geometry const& geometry)
{
    concept::check<Geometry const>();

    typedef typename point_type<Geometry>::type point_type;
    typedef typename strategy::distance::services::default_strategy
        <
            point_tag, point_type
        >::type strategy_type;

    return dispatch::perimeter
        <
            typename tag<Geometry>::type,
            Geometry,
            strategy_type
        >::apply(geometry, strategy_type());
}

/*!
\brief \brief_calc{perimeter} \brief_strategy
\ingroup perimeter
\details The function perimeter returns the perimeter of a geometry,
    using specified strategy
\tparam Geometry \tparam_geometry
\tparam Strategy \tparam_strategy{distance}
\param geometry \param_geometry
\param strategy strategy to be used for distance calculations.
\return \return_calc{perimeter}

\qbk{distinguish,with strategy}
\qbk{[include reference/algorithms/perimeter.qbk]}
 */
template<typename Geometry, typename Strategy>
inline typename default_length_result<Geometry>::type perimeter(
        Geometry const& geometry, Strategy const& strategy)
{
    concept::check<Geometry const>();

    return dispatch::perimeter
        <
            typename tag<Geometry>::type,
            Geometry,
            Strategy
        >::apply(geometry, strategy);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_PERIMETER_HPP


/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <Core/Algorithms/Field/InterfaceWithCleaver2Algorithm.h>
#include <Core/Algorithms/Base/AlgorithmVariableNames.h>

#include <cleaver2/vec3.h>
#include <cleaver2/BoundingBox.h>
#include <cleaver2/Cleaver.h>
#include <cleaver2/CleaverMesher.h>
#include <cleaver2/InverseField.h>
#include <cleaver2/SizingFieldCreator.h>
#include <cleaver2/Volume.h>
#include <cleaver2/TetMesh.h>
#include <Core/Algorithms/Base/AlgorithmPreconditions.h>

#include <Core/GeometryPrimitives/Vector.h>
#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/VField.h>
#include <Core/Datatypes/Legacy/Field/VMesh.h>
#include <Core/Datatypes/Legacy/Field/FieldInformation.h>
#include <iostream>
#include <Core/GeometryPrimitives/Point.h>
#include <Core/Utils/StringUtil.h>
#include <boost/scoped_ptr.hpp>
#include <Core/Logging/Log.h>
#include <Core/Math/MiscMath.h>

using namespace SCIRun;
using namespace SCIRun::Core;
using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Core::Algorithms::Fields;
using namespace SCIRun::Core::Geometry;

ALGORITHM_PARAMETER_DEF(Fields, Verbose);
ALGORITHM_PARAMETER_DEF(Fields, VolumeScaling);
ALGORITHM_PARAMETER_DEF(Fields, VolumeMultiplier);
ALGORITHM_PARAMETER_DEF(Fields, Lipschitz);
ALGORITHM_PARAMETER_DEF(Fields, Padding);
ALGORITHM_PARAMETER_DEF(Fields, AlphaShort);
ALGORITHM_PARAMETER_DEF(Fields, AlphaLong);
ALGORITHM_PARAMETER_DEF(Fields, SimpleMode);
ALGORITHM_PARAMETER_DEF(Fields, MeshMode);
ALGORITHM_PARAMETER_DEF(Fields, ReverseJacobians);

const AlgorithmInputName InterfaceWithCleaver2Algorithm::SizingField("SizingField");
const AlgorithmInputName InterfaceWithCleaver2Algorithm::BackgroundField("BackgroundField");
const AlgorithmOutputName InterfaceWithCleaver2Algorithm::SizingFieldUsed("SizingFieldUsed");
const AlgorithmOutputName InterfaceWithCleaver2Algorithm::BackgroundFieldUsed("BackgroundFieldUsed");

namespace detail
{
  using CleaverScalarField = boost::shared_ptr<cleaver2::ScalarField<float>>;
  using CleaverInputField = boost::shared_ptr<cleaver2::AbstractScalarField>;
  using CleaverInputFieldList = std::vector<CleaverInputField>;

  static const double kDefaultAlpha = 0.4;
  static const double kDefaultAlphaLong = 0.357;
  static const double kDefaultAlphaShort = 0.203;
  static const double kDefaultSamplingRate = 1.0;
  static const double kDefaultLipschitz = 0.2;
  static const double kDefaultFeatureScaling = 1.0;
  //static const int    kDefaultPadding = 0;
  //static const int    kDefaultMaxIterations = 1000;
  //static const double kDefaultSigma = 1.;

  struct Cleaver2Parameters
  {
    cleaver2::MeshType mesh_mode;
    double alphaLong;
    double alphaShort;
    double samplingRate;
    double lipschitz;
    double featureScaling;
    bool verbose;
    bool simpleMode;
    bool reverseJacobians;
  };

  int dataSize(CleaverScalarField field)
  {
    auto databounds = field->dataBounds();
    int w = (int)databounds.size.x;
    int h = (int)databounds.size.y;
    int d = (int)databounds.size.z;
    return w * h * d;
  }

  class Cleaver2Impl
  {
  public:
    Cleaver2Impl(const AlgorithmBase* algo, const Cleaver2Parameters& params, FieldHandle sizingField, FieldHandle backgroundMesh) :
      algo_(algo), params_(params), inputSizingField_(sizingField), inputBackgroundMesh_(backgroundMesh)
    {
      LOG_DEBUG("Cleaver 2 parameters: \n\tmesh_mode: {}\n\talphaLong: {}\n\talphaShort: {}\n\tsampling_rate: {}\n\tlipschitz: {}\n\tfeature_scaling: {}\n\tverbose: {}\n\tsimpleMode: {}",
        params_.mesh_mode, params_.alphaLong, params_.alphaShort,
        params_.samplingRate, params_.lipschitz, params_.featureScaling,
        params_.verbose, params_.simpleMode);
    }

    FieldHandle cleave(CleaverInputFieldList fields)
    {
      volume_.reset(new cleaver2::Volume(toVectorOfRawPointers(fields)));

      // TODO DAN: add optional padding to sizing field...
      /// Padding is now optional!
      //boost::shared_ptr<cleaver2::AbstractVolume> paddedVolume(volume);
      // const bool verbose = get(Verbose).toBool();
      // const bool pad = get(Padding).toBool();
      //
      // if (pad)
      // {
      //   paddedVolume.reset(new Cleaver::PaddedVolume(volume.get()));
      // }
      //
      // if (verbose)
      // {
      //  std::cout << "Input Dimensions: " << dims[0] << " x " << dims[1] << " x " << dims[2] << std::endl;
      //  if (pad)
      //   std::cout << "Padded Mesh with Volume Size " << paddedVolume->size().toString() << std::endl;
      //      else
      //         std::cout << "Creating Mesh with Volume Size " << volume->size().toString() << std::endl;
      // }

      addSizingFieldToVolume();

      bool verbose = params_.verbose;

      bool simple = params_.simpleMode;
      cleaver2::CleaverMesher mesher(simple);
      mesher.setVolume(volume_.get());
      const double alpha = kDefaultAlpha; // do not expose
      mesher.setAlphaInit(alpha);

      //-----------------------------------
      // Load background mesh if provided
      //-----------------------------------
      cleaver2::TetMesh* bgMesh = nullptr;
      if (inputBackgroundMesh_)
      {
        bgMesh = makeCleaver2TetVolFromSCIRunTetVol(inputBackgroundMesh_);
        mesher.setBackgroundMesh(bgMesh);
        outputBackgroundMesh_ = inputBackgroundMesh_;
      }
      else
      {
        switch (params_.mesh_mode)
        {
          case cleaver2::Regular:
          {
            double alpha_long = kDefaultAlphaLong;
            double alpha_short = kDefaultAlphaShort;
            mesher.setAlphas(alpha_long, alpha_short);
            mesher.setRegular(true);
            bgMesh = mesher.createBackgroundMesh(verbose);
            break;
          }
          case cleaver2::Structured:
          {
            mesher.setRegular(false);
            bgMesh = mesher.createBackgroundMesh(verbose);
            break;
          }
        }
        outputBackgroundMesh_ = convertCleaverOutputToField(bgMesh);
      }

      mesher.buildAdjacency(verbose);
      mesher.sampleVolume(verbose);
      mesher.computeAlphas(verbose);
      mesher.computeInterfaces(verbose);
      mesher.generalizeTets(verbose);
      mesher.snapsAndWarp(verbose);
      mesher.stencilTets(verbose);

      auto mesh(boost::shared_ptr<cleaver2::TetMesh>(mesher.getTetMesh()));

      if (params_.reverseJacobians)
        mesh->fixVertexWindup(verbose);

      return convertCleaverOutputToField(mesh.get());
    }

    FieldHandle run(FieldList inputs)
    {
      detail::CleaverInputFieldList fields;

      for (auto& input : inputs)
      {
        fields.push_back(convertToCleaverFormat(input));
      }
      return cleave(fields);
    }

    void resetLatVolConverter()
    {
      x_ = y_ = z_ = 0;
    }

    CleaverScalarField convertToCleaverFormat(FieldHandle input)
    {
      VMesh::dimension_type dims;
      auto imesh1 = input->vmesh();

      if (!imesh1->is_structuredmesh())
      {
        THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "needs to be structured mesh!");
      }
      else
      {
        auto vfield1 = input->vfield();
        if (!vfield1->is_scalar())
        {
          THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "values at the node needs to be scalar!");
        }

        imesh1->get_dimensions(dims);
        if (x_ == 0)
        {
          x_ = dims[0]; y_ = dims[1]; z_ = dims[2];
          if (x_ < 1 || y_ < 1 || z_ < 1)
          {
            THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, " Size of input fields should be non-zero !");
          }
        }
        else
        {
          if (dims[0] != x_ || dims[1] != y_ || dims[2] != z_)
          {
            THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, " Size of input fields is inconsistent !");
          }
        }

        if (dims.size() != 3)
        {
          THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "need a three dimensional indicator function");
        }

        //0 = constant, 1 = linear
        if (1 != vfield1->basis_order())
        {
          THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "Input data need to be defined on input mesh nodes.");
        }

        if (vfield1->is_float())
        {
          auto ptr = static_cast<float*>(vfield1->fdata_pointer());
          if (ptr)
          {
            return makeCleaver2FieldFromLatVol(input);
          }
          else
          {
            THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "float field is null pointer");
          }
        }
        else
        {
          THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "Input field needs to be a structured mesh (best would be a LatVol) with float values defined on mesh nodes. ");
        }
      }
    }

    //TODO dan: need run-time check for float or double field data
    static CleaverScalarField makeCleaver2FieldFromLatVol(FieldHandle field)
    {
      auto vmesh = field->vmesh();
      auto vfield = field->vfield();
      VMesh::dimension_type dims;
      vmesh->get_dimensions(dims);

      auto ptr = static_cast<float*>(vfield->fdata_pointer());

      auto cleaverField = boost::make_shared<cleaver2::ScalarField<float>>(ptr, dims[0], dims[1], dims[2]);
      cleaver2::BoundingBox bb(cleaver2::vec3::zero, cleaver2::vec3(dims[0], dims[1], dims[2]));
      cleaverField->setBounds(bb);
      const auto& transform = vmesh->get_transform();

      int x_spacing = fabs(transform.get_mat_val(0, 0)), y_spacing = fabs(transform.get_mat_val(1, 1)), z_spacing = fabs(transform.get_mat_val(2, 2));

      if (IsNan(x_spacing) || x_spacing <= 0) x_spacing = 1; /// dont allow negative or zero scaling of the bounding box
      if (IsNan(y_spacing) || y_spacing <= 0) y_spacing = 1;
      if (IsNan(z_spacing) || z_spacing <= 0) z_spacing = 1;

      cleaverField->setScale(cleaver2::vec3(x_spacing, y_spacing, z_spacing));

      return cleaverField;
    }

    static cleaver2::TetMesh* makeCleaver2TetVolFromSCIRunTetVol(FieldHandle field)
    {
      FieldInformation info(field);
      if (!info.is_tetvol())
        return nullptr;

      auto vmesh = field->vmesh();
      const int numVertices = vmesh->num_nodes();

      double xmin = 1.0e16, xmax = -1.0e16,
                 ymin = 1.0e16, ymax = -1.0e16,
                 zmin = 1.0e16, zmax = -1.0e16;

      using namespace cleaver2;
      std::vector<Vertex*> verts(numVertices);

      for(VMesh::Node::index_type i = 0; i < numVertices; ++i)
      {
        auto point = vmesh->get_point(i);
        auto x = static_cast<float>(point.x());
        auto y = static_cast<float>(point.y());
        auto z = static_cast<float>(point.z());

        if (x < xmin) xmin = x;
        if (x > xmax) xmax = x;

        if (y < ymin) ymin = y;
        if (y > ymax) ymax = y;

        if (z < zmin) zmin = z;
        if (z > zmax) zmax = z;

        verts[i] = new Vertex();
        verts[i]->pos() = vec3(x,y,z);
        verts[i]->tm_v_index = i;
      }

      std::unique_ptr<TetMesh> mesh(new TetMesh(verts, {}));

      mesh->bounds = BoundingBox(xmin, ymin, zmin, xmax - xmin, ymax - ymin, zmax - zmin);

      int numElements = vmesh->num_elems();

      for (VMesh::Elem::index_type i = 0; i < numElements; ++i)
      {
        VMesh::Elem::array_type tetidx;
        vmesh->get_elems( tetidx, i );
        mesh->createTet(
            verts[tetidx[0]],
            verts[tetidx[1]],
            verts[tetidx[2]],
            verts[tetidx[3]],
            0);
      }

      return mesh.release();
    }

    static Point toPoint(const cleaver2::vec3& v)
    {
      return Point(v.x, v.y, v.z);
    }

    static FieldHandle makeLatVolFromCleaver2Field(CleaverScalarField cfield)
    {
      FieldInformation lfi(LATVOLMESH_E, LINEARDATA_E, FLOAT_E);

      auto cbbox = cfield->bounds();
      auto cdatabbox = cfield->dataBounds();

      logInfo("Cleaver sizing field: CenteringType {}, bounds {},{}; dataBounds {},{}", cfield->getCenterType(),
        cbbox.origin.toString(), cbbox.size.toString(),
        cdatabbox.origin.toString(), cdatabbox.size.toString()
      );

      auto mesh = CreateMesh(lfi, cdatabbox.size.x, cdatabbox.size.y, cdatabbox.size.z, toPoint(cbbox.minCorner()), toPoint(cbbox.maxCorner()));
      auto field = CreateField(lfi, mesh);

      auto vfield = field->vfield();

      vfield->resize_values();
      vfield->set_values(cfield->data(), dataSize(cfield));

      // const auto& transform = vmesh->get_transform();
      //
      // int x_spacing = fabs(transform.get_mat_val(0, 0)), y_spacing = fabs(transform.get_mat_val(1, 1)), z_spacing = fabs(transform.get_mat_val(2, 2));
      //
      // if (IsNan(x_spacing) || x_spacing <= 0) x_spacing = 1; /// dont allow negative or zero scaling of the bounding box
      // if (IsNan(y_spacing) || y_spacing <= 0) y_spacing = 1;
      // if (IsNan(z_spacing) || z_spacing <= 0) z_spacing = 1;
      //
      // cleaverField->setScale(cleaver2::vec3(x_spacing, y_spacing, z_spacing));

      return field;
    }

    void addSizingFieldToVolume()
    {
      if (inputSizingField_)
      {
        double min;
        inputSizingField_->vfield()->min(min);
        if (min <= 0)
          THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, "Sizing field must contain only positive values.");

        //TODO: sloppy--need a const version
        resetLatVolConverter();
        sizingField_ = convertToCleaverFormat(inputSizingField_);
        outputSizingField_ = inputSizingField_;
      }
      else
      {
        sizingField_.reset(cleaver2::SizingFieldCreator::createSizingFieldFromVolume(
          volume_.get(),
          (float)(1.0 / params_.lipschitz),
          (float)params_.samplingRate,
          (float)params_.featureScaling,
          0, // padding--off
          (params_.mesh_mode != cleaver2::Regular),
          params_.verbose));
        outputSizingField_ = makeLatVolFromCleaver2Field(sizingField_);
      }
      volume_->setSizingField(sizingField_.get());
    }

    FieldHandle convertCleaverOutputToField(cleaver2::TetMesh* mesh)
    {
      auto nr_of_tets = mesh->tets.size();
      auto nr_of_verts = mesh->verts.size();

      if (nr_of_tets == 0 || nr_of_verts == 0)
      {
        THROW_ALGORITHM_INPUT_ERROR_WITH(algo_, " Number of resulting tetrahedral nodes or elements is 0. If you disabled padding enable it and execute again. ");
      }

      FieldInformation fi("TetVolMesh", 0, "double");   ///create output field

      auto output = CreateField(fi);
      auto omesh = output->vmesh();
      auto ofield = output->vfield();

      omesh->node_reserve(nr_of_verts);
      omesh->elem_reserve(nr_of_tets);

      for (auto i = 0; i < nr_of_verts; i++)
      {
        omesh->add_point(Point(mesh->verts[i]->pos().x, mesh->verts[i]->pos().y, mesh->verts[i]->pos().z));
      }

      VMesh::Node::array_type vdata;
      vdata.resize(4);
      std::vector<double> values(nr_of_tets);

      for (auto i = 0; i < nr_of_tets; i++)
      {
        vdata[0] = mesh->tets[i]->verts[0]->tm_v_index;
        vdata[1] = mesh->tets[i]->verts[1]->tm_v_index;
        vdata[2] = mesh->tets[i]->verts[2]->tm_v_index;
        vdata[3] = mesh->tets[i]->verts[3]->tm_v_index;
        omesh->add_elem(vdata);
        auto mat_label = mesh->tets[i]->mat_label;
        values[i] = mat_label;
      }
      ofield->resize_values();
      ofield->set_values(values);
      mesh->computeAngles();

      std::ostringstream ostr1, ostr2;
      ostr1 << "(nodes, elements, dims) - (" << nr_of_verts << " , " << nr_of_tets << " , " << volume_->size().toString() << ")" << std::endl;
      ostr2 << "(min angle, max angle) - (" << mesh->min_angle << " , " << mesh->max_angle << ")" << std::endl;

      algo_->remark(ostr1.str());
      algo_->remark(ostr2.str());
      return output;
    }

    FieldHandle sizingFieldUsed() const { return outputSizingField_; }
    FieldHandle backgroundMeshUsed() const { return outputBackgroundMesh_; }
  private:
    const AlgorithmBase* algo_;
    Cleaver2Parameters params_;
    FieldHandle inputSizingField_, outputSizingField_, inputBackgroundMesh_, outputBackgroundMesh_;
    CleaverScalarField sizingField_;
    boost::shared_ptr<cleaver2::TetMesh> backgroundMesh_;
    boost::shared_ptr<cleaver2::Volume> volume_;
    int x_ = 0, y_ = 0, z_ = 0;
  };
}

InterfaceWithCleaver2Algorithm::InterfaceWithCleaver2Algorithm()
{
  //Cleaver parameters: Scaling = Sampling Rate, Multiplier = Feature Scaling
  addParameter(Parameters::Verbose, true);
  addParameter(Parameters::SimpleMode, false);
  addParameter(Parameters::ReverseJacobians, true);
  addParameter(Parameters::VolumeScaling, detail::kDefaultSamplingRate);
  addParameter(Parameters::VolumeMultiplier, detail::kDefaultFeatureScaling);
  addParameter(Parameters::Lipschitz, detail::kDefaultLipschitz);
  //addParameter(Parameters::Padding, detail::kDefaultPadding);
  addParameter(Parameters::AlphaLong, detail::kDefaultAlphaLong);
  addParameter(Parameters::AlphaShort, detail::kDefaultAlphaShort);
  addParameter(Parameters::MeshMode, static_cast<int>(cleaver2::MeshType::Regular));
}

AlgorithmOutput InterfaceWithCleaver2Algorithm::runImpl(const FieldList& input, FieldHandle backgroundMesh, FieldHandle sizingField) const
{
  FieldList inputs;
  std::copy_if(input.begin(), input.end(), std::back_inserter(inputs), [](FieldHandle f) { return f; });

  if (inputs.empty())
  {
    THROW_ALGORITHM_INPUT_ERROR("No input fields given");
  }
  if (inputs.size() < 2)
  {
    THROW_ALGORITHM_INPUT_ERROR("At least 2 indicator functions stored as float values are needed to run cleaver!");
  }

  std::ostringstream ostr0;
  ostr0 << "Be aware that inside and outside of materials (to be meshed) need to be defined as positive and negative (e.g. surface distance) values across all module inputs. The zero crossings represents material boundaries." << std::endl;
  remark(ostr0.str());

  detail::Cleaver2Parameters params
  {
    cleaver2::MeshType(get(Parameters::MeshMode).toInt()),
    get(Parameters::AlphaLong).toDouble(),
    get(Parameters::AlphaShort).toDouble(),
    get(Parameters::VolumeScaling).toDouble(),
    get(Parameters::Lipschitz).toDouble(),
    get(Parameters::VolumeMultiplier).toDouble(),
    get(Parameters::Verbose).toBool(),
    get(Parameters::SimpleMode).toBool(),
    get(Parameters::ReverseJacobians).toBool()
  };
  detail::Cleaver2Impl impl(this, params, sizingField, backgroundMesh);
  auto tetmesh = impl.run(inputs);
  auto sizing = impl.sizingFieldUsed();
  auto background = impl.backgroundMeshUsed();

  AlgorithmOutput output;
  output[Variables::OutputField] = tetmesh;
  output[SizingFieldUsed] = sizing;
  output[BackgroundFieldUsed] = background;
  return output;
}

AlgorithmOutput InterfaceWithCleaver2Algorithm::run(const AlgorithmInput& input) const
{
  auto inputfields = input.getList<Field>(Variables::InputFields);
  auto sizingField = input.get<Field>(SizingField);

  return runImpl(inputfields, nullptr, sizingField);
}


/*
    Copyright (C) 2010,2011 Wei Dong <wdong.pku@gmail.com>. All Rights Reserved.

    DISTRIBUTION OF THIS PROGRAM IN EITHER BINARY OR SOURCE CODE FORM MUST BE
    PERMITTED BY THE AUTHOR.
*/

#include <iostream>
#include <boost/timer/timer.hpp>
#include <boost/program_options.hpp>
#include <flann/flann.h>
#include <kgraph.h>
#include <kgraph-data.h>

using namespace std;
using namespace boost;
namespace po = boost::program_options;

int main (int argc, char *argv[]) {
    flann::SearchParams params;
    params.cores = 0;
    string data_path;
    string index_path;
    string query_path;
    string output_path;
    string eval_path;
    unsigned K;

    po::options_description desc_visible("General options");
    desc_visible.add_options()
    ("help,h", "produce help message.")
    ("data", po::value(&data_path), "data path")
    ("index", po::value(&index_path), "index path")
    ("query", po::value(&query_path), "query path")
    ("output", po::value(&output_path), "output_path")
    ("eval", po::value(&eval_path), "eval path")
    (",K", po::value(&K)->default_value(10), "")
    ("checks,C", po::value(&params.checks), "")
    ("eps", po::value(&params.eps), "")
    ;

    po::options_description desc("Allowed options");
    desc.add(desc_visible);

    po::positional_options_description p;
    p.add("data", 1);
    p.add("index", 1);
    p.add("query", 1);
    p.add("output", 1);

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
    po::notify(vm);

    if (vm.count("help") || vm.count("data") == 0 || vm.count("index") == 0 || vm.count("query") == 0) {
        cout << "flann_search [...] <data> <index> <query> [output]" << endl;
        cout << desc_visible << endl;
        return 0;
    }

    kgraph::Matrix<float, 1> data;
    kgraph::Matrix<float, 1> query;
    data.load_lshkit(data_path);
    query.load_lshkit(query_path);

    kgraph::Matrix<unsigned,1> result(query.size(), K);
    kgraph::Matrix<float,1> dists(query.size(), K);

    flann::Matrix<float> fdata(data[0], data.size(), data.dim());
    flann::Matrix<float> fquery(query[0], query.size(), query.dim());
    flann::Matrix<int> fresult(reinterpret_cast<int *>(result[0]), query.size(), K);
    flann::Matrix<float> fdists(dists[0], query.size(), K);

    boost::timer::auto_cpu_timer timer;
    cerr << "Loading..." << endl;
    flann::Index<flann::L2<float>> index(fdata, flann::SavedIndexParams(index_path));
    timer.stop();
    timer.report();
    timer.start();
    cerr << "Searching..." << endl;
    index.knnSearch(fquery, fresult, fdists, K, params);
    timer.stop();
    timer.report();
    float time = timer.elapsed().wall / 1e9;
    if (output_path.size()) {
        result.save_lshkit(output_path);
    }
    float recall = 0;
    if (eval_path.size()) {
        flann::L2<float> l2;
        kgraph::Matrix<unsigned> gs; // gold standard
        gs.load_lshkit(eval_path);
        BOOST_VERIFY(gs.dim() >= K);
        BOOST_VERIFY(gs.size() >= query.size());
        kgraph::Matrix<float> gs_dist(query.size(), K);
        kgraph::Matrix<float> result_dist(query.size(), K);
#pragma omp parallel for
        for (unsigned i = 0; i < query.size(); ++i) {
            float *gs_dist_row = gs_dist[i];
            float *result_dist_row = result_dist[i];
            unsigned const *gs_row = gs[i];
            unsigned const *result_row = result[i];
            for (unsigned k = 0; k < K; ++k) {
                gs_dist_row[k] = l2(query[i], data[gs_row[k]], data.dim());
                result_dist_row[k] = l2(query[i], data[result_row[k]], data.dim());
            }
            sort(gs_dist_row, gs_dist_row + K);
            sort(result_dist_row, result_dist_row + K);
        }
        /*
        for (unsigned i = 0; i < 10; ++i) {
            for (unsigned j = 0;j < 10; ++j) {
                cout << ' ' << gs_dist[i][j] << ':' << result_dist[i][j];
            }
            cout << endl;
        }
        */
        recall = kgraph::AverageRecall(gs_dist, result_dist, K);
    }
    cout << "Time: " << time << " Recall: " << recall << endl;
    return 0;
}