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

#include <votca/xtp/sigma.h>
#include <cmath>
#include <boost/math/constants/constants.hpp>
#include <votca/tools/constants.h>
#include <votca/xtp/ppm.h>
#include <votca/xtp/threecenter.h>

namespace votca {
  namespace xtp {

    Eigen::MatrixXd Sigma::SetupFullQPHamiltonian() {

      // constructing full QP Hamiltonian
      Eigen::MatrixXd Hqp = _sigma_x + _sigma_c - (*_vxc);
      // diagonal elements are given by _qp_energies
      for (int m = 0; m < Hqp.rows(); m++) {
        Hqp(m, m) = _gwa_energies(m + _qpmin);
      }
      return Hqp;
    }

    void Sigma::X_diag(const TCMatrix_gwbse& Mmn){
      int gwsize = Mmn.getAuxDimension(); // size of the GW basis
      _sigma_x=Eigen::MatrixXd::Zero(_qptotal,_qptotal);
      #pragma omp parallel for
      for (int gw_level = 0; gw_level < _qptotal; gw_level++) {
        const MatrixXfd & Mmn1 = Mmn[ gw_level + _qpmin ];
        double sigma_x = 0;
        for (int i_gw = 0; i_gw < gwsize; i_gw++) {
          // loop over all occupied bands used in screening
          for (int i_occ = 0; i_occ <= _homo; i_occ++) {
            sigma_x -= Mmn1( i_occ,i_gw) * Mmn1( i_occ,i_gw);
          } // occupied bands
        } // gwbasis functions
        _sigma_x(gw_level, gw_level) = (1.0 - _ScaHFX) * sigma_x;
      }
    }

    void Sigma::C_diag(const TCMatrix_gwbse& Mmn, const PPM& ppm, const Eigen::VectorXd& qp_old){
      int levelsum = Mmn.get_ntot(); // total number of bands
      int gwsize = Mmn.getAuxDimension(); // size of the GW basis

      // loop over all GW levels
#pragma omp parallel for
      for (int gw_level = 0; gw_level < _qptotal; gw_level++) {
        const MatrixXfd & Mmn1 = Mmn[ gw_level + _qpmin ];
        const double qpmin = qp_old(gw_level + _qpmin);
        double sigma_c = 0.0;
        // loop over all functions in GW basis
        for (int i_gw = 0; i_gw < gwsize; i_gw++) {
          // the ppm_weights smaller 1.e-5 are set to zero in rpa.cc PPM_construct_parameters
          if (ppm.getPpm_weight()(i_gw) < 1.e-9) {
            continue;
          }
          const double ppm_freq = ppm.getPpm_freq()(i_gw);
          const double fac = 0.5*ppm.getPpm_weight()(i_gw) * ppm_freq;
          // loop over all bands
          double sigma_c_loc=0.0;
          for (int i = 0; i < _homo+1; i++) {
            const double factor=Stabilize(qpmin - qp_old(i) +ppm_freq);
            // sigma_c diagonal elements
            sigma_c_loc += factor * Mmn1(i, i_gw) * Mmn1( i,i_gw);
          }// bands
          for (int i = _homo+1; i < levelsum; i++) {
            const double factor=Stabilize(qpmin - qp_old(i) -ppm_freq);
            // sigma_c diagonal elements
            sigma_c_loc += factor * Mmn1(i,i_gw) * Mmn1(i,i_gw);
          }// bands
          sigma_c+=sigma_c_loc*fac;
        }// GW functions
        _sigma_c(gw_level, gw_level) = sigma_c;
        // update _qp_energies
        _gwa_energies(gw_level + _qpmin) = (*_dftenergies)(gw_level + _qpmin) + sigma_c + _sigma_x(gw_level, gw_level) - (*_vxc)(gw_level, gw_level);
      }// all bands
    }

    void Sigma::CalcdiagElements(const TCMatrix_gwbse& Mmn, const PPM & ppm) {
        X_diag(Mmn);
        if(_gwa_energies.size()<1){
            throw std::runtime_error("Sigma gwa_energies not set!");
        }
      _sigma_c=Eigen::MatrixXd::Zero(_qptotal,_qptotal);

      // initial _qp_energies are dft energies
      Eigen::VectorXd qp_old = _gwa_energies;
      // only diagonal elements except for in final iteration
      for (int g_iter = 0; g_iter < _g_sc_max_iterations; g_iter++) {

        C_diag(Mmn, ppm, qp_old);
        Eigen::VectorXd diff = qp_old - _gwa_energies;
        bool energies_converged = true;

        int state = 0;
        double diff_max = diff.cwiseAbs().maxCoeff(&state);
        if (diff_max > _g_sc_limit) {
          energies_converged = false;
        }

        if (tools::globals::verbose) {
          double _DFTgap = (*_dftenergies)(_homo + 1) - (*_dftenergies)(_homo);
          double _QPgap = _gwa_energies(_homo + 1) - _gwa_energies(_homo);
          CTP_LOG(ctp::logDEBUG, *_log) << ctp::TimeStamp() << " G_Iteration: " << g_iter + 1 << " shift=" << _QPgap - _DFTgap << " E_diff max=" << diff_max << " StateNo:" << state << std::flush;
        }
        double alpha = 0.0;
        _gwa_energies = (1 - alpha) * _gwa_energies + alpha*qp_old;

        if (energies_converged) {
          CTP_LOG(ctp::logDEBUG, *_log) << ctp::TimeStamp() << " Converged after " << g_iter + 1 << " G iterations." << std::flush;
          break;
        } else if (g_iter == _g_sc_max_iterations - 1) {
          CTP_LOG(ctp::logDEBUG, *_log) << ctp::TimeStamp() << " G-self-consistency cycle not converged after " << _g_sc_max_iterations << " iterations." << std::flush;
          break;

        } else {
          qp_old = _gwa_energies;
        }
      } // iterations
      return;
    }

    void Sigma::X_offdiag(const TCMatrix_gwbse& Mmn){
      int gwsize = Mmn.getAuxDimension();
      #pragma omp parallel for schedule(dynamic)
      for (int gw_level1 = 0; gw_level1 < _qptotal; gw_level1++) {
        const MatrixXfd& Mmn1 = Mmn[ gw_level1 + _qpmin ];
        for (int gw_level2 = gw_level1+1; gw_level2 < _qptotal; gw_level2++) {
          const MatrixXfd & Mmn2 = Mmn[ gw_level2 + _qpmin ];
          double sigma_x = 0;
          for (int i_gw = 0; i_gw < gwsize; i_gw++) {
            // loop over all occupied bands used in screening
            for (int i_occ = 0; i_occ <= _homo; i_occ++) {
              sigma_x -= Mmn1(i_occ,i_gw) * Mmn2(i_occ,i_gw);
            } // occupied bands
          } // gwbasis functions
          _sigma_x(gw_level1, gw_level2) = (1.0 - _ScaHFX) * sigma_x;
          _sigma_x(gw_level2, gw_level1) = (1.0 - _ScaHFX) * sigma_x;
        }
      }
      return;
    }

    double Sigma::Stabilize(double denom){
      const double fourpi = 4*boost::math::constants::pi<double>();
      double stab = 1.0;
      if (std::abs(denom) < 0.25) {
        stab = 0.5 * (1.0 - std::cos(fourpi * denom));
      }
      return stab / denom;
    }

    double Sigma::SumSymmetric(real_gwbse Mmn1xMmn2, double qpmin1, double qpmin2, double gwa_energy){
      double factor=Stabilize(qpmin1 - gwa_energy);
      factor+= Stabilize(qpmin2 - gwa_energy);
      return Mmn1xMmn2 * factor;
    }

    void Sigma::C_offdiag(const TCMatrix_gwbse& Mmn, const PPM& ppm){

      #pragma omp parallel
      {
        int lumo=_homo+1;
        const int levelsum = Mmn.get_ntot(); // total number of bands
        const int gwsize = Mmn.getAuxDimension(); // size of the GW basis
        const Eigen::VectorXd ppm_weight=ppm.getPpm_weight();
        const Eigen::VectorXd ppm_freqs=ppm.getPpm_freq();
        #pragma omp for schedule(dynamic)
        for (int gw_level1 = 0; gw_level1 < _qptotal; gw_level1++) {
        const MatrixXfd& Mmn1=Mmn[ gw_level1 + _qpmin ];
        for (int gw_level2 = gw_level1+1; gw_level2 < _qptotal; gw_level2++) {
          const MatrixXfd Mmn1xMmn2=Mmn[ gw_level2 + _qpmin ].cwiseProduct(Mmn1);
          const Eigen::VectorXd gwa_energies=_gwa_energies;
          const double qpmin1 = gwa_energies(gw_level1 + _qpmin);
          const double qpmin2 = gwa_energies(gw_level2 + _qpmin);

          double sigma_c=0;
          for (int i_gw = 0; i_gw < gwsize; i_gw++) {
            // the ppm_weights smaller 1.e-5 are set to zero in rpa.cc PPM_construct_parameters
            if (ppm_weight(i_gw) < 1.e-9) {
              continue;
            }
            const double ppm_freq= ppm_freqs(i_gw);
            const double fac = 0.25* ppm_weight(i_gw) * ppm_freq;
            double sigma_loc=0.0;
            // loop over occ screening levels
              for (int i = 0; i < lumo; i++) {
                const double gwa_energy = gwa_energies(i) - ppm_freq;
                sigma_loc+=SumSymmetric(Mmn1xMmn2(i,i_gw), qpmin1, qpmin2, gwa_energy);
              }
              // loop over unocc screening levels
              for (int i = lumo; i < levelsum; i++) {
                const double gwa_energy = gwa_energies(i) + ppm_freq;
                sigma_loc+=SumSymmetric(Mmn1xMmn2(i,i_gw), qpmin1, qpmin2, gwa_energy);
              }
              sigma_c += sigma_loc*fac;
            }
          _sigma_c(gw_level1, gw_level2) = sigma_c;
          _sigma_c(gw_level2, gw_level1) = sigma_c;
        }// GW row
      }//GW col
      }

      return;
    }

    void Sigma::CalcOffDiagElements(const TCMatrix_gwbse& Mmn, const PPM & ppm) {

      X_offdiag(Mmn);
      C_offdiag(Mmn, ppm);

      return;
    }

  }
};


#include "parameterparser.h"
#include "CBCT_acquisition.h"
#include "CBCT_binning.h"
#include "hoCuConebeamProjectionOperator.h"
#include "hoNDArray_fileio.h"
#include "hoCuNDArray_math.h"
#include "vector_td_utilities.h"
#include "hoNDArray_utils.h"
#include "GPUTimer.h"
#include "cuATrousOperator.h"
#include "cuEdgeATrousOperator.h"
#include "cuSbCgSolver.h"
#include <iostream>
#include <algorithm>
#include <sstream>
#include <boost/program_options.hpp>
#include <proton/hdf5_utils.h>
#include "cuDCTOperator.h"
#include "dicomWriter.h"
using namespace Gadgetron;
using namespace std;

namespace po = boost::program_options;
int main(int argc, char** argv)
{

  std::string acquisition_filename;
  std::string image_filename;
	floatd2 scale_factor;
	uintd3 imageSize;
	floatd3 is_dims_in_mm;
	int device;
 // Parse command line
  //
po::options_description desc("Allowed options");

  desc.add_options()
    ("help", "produce help message")
    ("acquisition,a", po::value<string>(&acquisition_filename)->default_value("acquisition.hdf5"), "Acquisition data")
    ("samples,n",po::value<unsigned int>(),"Number of samples per ray")
    ("output,f", po::value<string>(&image_filename)->default_value("fdk.real"), "Output filename")
    ("size,s",po::value<uintd3>(&imageSize)->default_value(uintd3(512,512,1)),"Image size in pixels")
    ("binning,b",po::value<string>(),"Binning file for 3d reconstruction (used to exclude projections)")
    ("SAG","Use exact SAG correction if present")
    ("dimensions,d",po::value<floatd3>(&is_dims_in_mm)->default_value(floatd3(256,256,256)),"Image dimensions in mm. Overwrites voxelSize.")
    ("device",po::value<int>(&device)->default_value(0),"Number of the device to use (0 indexed)")
            ("downsample,D",po::value<floatd2>(&scale_factor)->default_value(floatd2(1,1)),"Downsample projections this factor")

    ;

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  if (vm.count("help")) {
    cout << desc << "\n";
    return 1;
  }

  std::cout << "Command line options:" << std::endl;
  for (po::variables_map::iterator it = vm.begin(); it != vm.end(); ++it){
    boost::any a = it->second.value();
    std::cout << it->first << ": ";
    if (a.type() == typeid(std::string)) std::cout << it->second.as<std::string>();
    else if (a.type() == typeid(int)) std::cout << it->second.as<int>();
    else if (a.type() == typeid(unsigned int)) std::cout << it->second.as<unsigned int>();
    else if (a.type() == typeid(float)) std::cout << it->second.as<float>();
    else if (a.type() == typeid(vector_td<float,3>)) std::cout << it->second.as<vector_td<float,3> >();
    else if (a.type() == typeid(vector_td<int,3>)) std::cout << it->second.as<vector_td<int,3> >();
    else if (a.type() == typeid(vector_td<unsigned int,3>)) std::cout << it->second.as<vector_td<unsigned int,3> >();
    else std::cout << "Unknown type" << std::endl;
    std::cout << std::endl;
  }

  // Load acquisition data
  //

  boost::shared_ptr<CBCT_acquisition> acquisition( new CBCT_acquisition() );

  {
    GPUTimer timer("Loading projections");
    acquisition->load(acquisition_filename);
  }

	// Downsample projections if requested
	//

      if (scale_factor[0] != 1 || scale_factor[1] != 1) {
        GPUTimer timer("Downsampling projections");
        acquisition->downsample(scale_factor[0], scale_factor[1]);

      }

  // Load or generate binning data
  //

  boost::shared_ptr<CBCT_binning> binning( new CBCT_binning() );

  if (vm.count("binning")){
    std::string binningdata_filename = vm["binning"].as<string>();
    std::cout << "Using binning data file: " << binningdata_filename << std::endl;
    binning->load(binningdata_filename);
    binning = binning->get_3d_binning();
  }
  else
    binning->set_as_default_3d_bin(acquisition->get_projections()->get_size(2));

  // Configuring...
  //

  uintd2 ps_dims_in_pixels( acquisition->get_projections()->get_size(0),
			    acquisition->get_projections()->get_size(1) );

  floatd2 ps_dims_in_mm( acquisition->get_geometry()->get_FOV()[0],
			 acquisition->get_geometry()->get_FOV()[1] );

  float SDD = acquisition->get_geometry()->get_SDD();
  float SAD = acquisition->get_geometry()->get_SAD();

  // Allocate array to hold the result
  //

  std::vector<size_t> is_dims = {imageSize[0],imageSize[1],imageSize[2]};
  hoCuNDArray<float> fdk_3d(&is_dims);
  hoCuNDArray<float> projections(*acquisition->get_projections());

  // Define conebeam projection operator
  // - and configure based on input parameters
  //

  boost::shared_ptr< hoCuConebeamProjectionOperator > E( new hoCuConebeamProjectionOperator() );

  E->setup( acquisition, binning, is_dims_in_mm );

  E->set_use_filtered_backprojection(true);

  // Initialize the device
  // - just to report more accurate timings
  //

  cudaThreadSynchronize();

  //
  // Standard 3D FDK reconstruction
  //

  {
    GPUTimer timer("Running 3D FDK reconstruction");
    E->mult_MH( &projections, &fdk_3d );
    cudaThreadSynchronize();

    hoCuNDArray<float> tmp_proj(projections.get_dimensions());
    E->mult_M(&fdk_3d,&tmp_proj,false);
    float scaler = dot(&tmp_proj, &projections) / dot(&tmp_proj, &tmp_proj);
    fdk_3d *= scaler;

  }

//  write_nd_array<float>( &fdk_3d, image_filename.c_str() );

  std::string s = "";
  write_dicom(&fdk_3d,s,is_dims_in_mm);

  saveNDArray2HDF5(&fdk_3d,image_filename.c_str(),is_dims_in_mm,floatd3(0,0,0),"",0);

/*
  cuSbCgSolver<float> sb;
  auto id = boost::make_shared<identityOperator<cuNDArray<float>>>();
  id->set_domain_dimensions(&is_dims);
  id->set_codomain_dimensions(&is_dims);
  id->set_weight(5);

  auto wave = boost::make_shared<cuATrousOperator<float>>();
  wave->set_levels({4,4,4});
  wave->set_domain_dimensions(&is_dims);
  wave->set_weight(10);

  sb.set_encoding_operator(id);
  sb.set_max_outer_iterations(200);

  sb.add_regularization_operator(wave,1);
  sb.set_output_mode(cuSbCgSolver<float>::OUTPUT_VERBOSE);
  sb.get_inner_solver()->set_output_mode(cuCgSolver<float>::OUTPUT_VERBOSE);
  sb.get_inner_solver()->set_max_iterations(10);
  sb.get_inner_solver()->set_tc_tolerance(1e-8);
  cuNDArray<float> cu_fdk(fdk_3d);
  auto result = sb.solve(&cu_fdk);
*/
/*
  cuNDArray<float> cu_fdk(fdk_3d);

  cuDCTOperator<float> dctOp;
  dctOp.set_domain_dimensions(cu_fdk.get_dimensions().get());
  cuNDArray<float> tmp(dctOp.get_codomain_dimensions());
  dctOp.mult_M(&cu_fdk,&tmp,false);
  dctOp.inverse(&tmp,&cu_fdk,false);
  write_nd_array(&cu_fdk,"fdk_wavelet.real");
*/

  return 0;
}


#include <ripple/basics/contract.h>
#include <ripple/protocol/IOUAmount.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <algorithm>
#include <numeric>
#include <iterator>
#include <stdexcept>

namespace ripple {

static std::int64_t const minMantissa = 1000000000000000ull;
static std::int64_t const maxMantissa = 9999999999999999ull;

static int const minExponent = -96;
static int const maxExponent = 80;

void
IOUAmount::normalize ()
{
    if (mantissa_ == 0)
    {
        *this = beast::zero;
        return;
    }

    bool const negative = (mantissa_ < 0);

    if (negative)
        mantissa_ = -mantissa_;

    while ((mantissa_ < minMantissa) && (exponent_ > minExponent))
    {
        mantissa_ *= 10;
        --exponent_;
    }

    while (mantissa_ > maxMantissa)
    {
        if (exponent_ >= maxExponent)
            Throw<std::overflow_error> ("IOUAmount::normalize");

        mantissa_ /= 10;
        ++exponent_;
    }

    if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
    {
        *this = beast::zero;
        return;
    }

    if (exponent_ > maxExponent)
        Throw<std::overflow_error> ("value overflow");

    if (negative)
        mantissa_ = -mantissa_;
}

IOUAmount&
IOUAmount::operator+= (IOUAmount const& other)
{
    if (other == beast::zero)
        return *this;

    if (*this == beast::zero)
    {
        *this = other;
        return *this;
    }

    auto m = other.mantissa_;
    auto e = other.exponent_;

    while (exponent_ < e)
    {
        mantissa_ /= 10;
        ++exponent_;
    }

    while (e < exponent_)
    {
        m /= 10;
        ++e;
    }

    mantissa_ += m;

    if (mantissa_ >= -10 && mantissa_ <= 10)
    {
        *this = beast::zero;
        return *this;
    }

    normalize ();

    return *this;
}

bool
IOUAmount::operator<(IOUAmount const& other) const
{
    bool const lneg = mantissa_ < 0;
    bool const rneg = other.mantissa_ < 0;

    if (lneg != rneg)
        return lneg;

    if (mantissa_ == 0)
        return other.mantissa_ > 0;

    if (other.mantissa_ == 0)
        return false;

    if (exponent_ > other.exponent_)
        return lneg;
    if (exponent_ < other.exponent_)
        return !lneg;

    return mantissa_ < other.mantissa_;
}

std::string
to_string (IOUAmount const& amount)
{
    if (amount == beast::zero)
        return "0";

    int const exponent = amount.exponent ();
    auto mantissa = amount.mantissa ();

    if (((exponent != 0) && ((exponent < -25) || (exponent > -5))))
    {
        std::string ret = std::to_string (mantissa);
        ret.append (1, 'e');
        ret.append (std::to_string (exponent));
        return ret;
    }

    bool negative = false;

    if (mantissa < 0)
    {
        mantissa = -mantissa;
        negative = true;
    }

    assert (exponent + 43 > 0);

    size_t const pad_prefix = 27;
    size_t const pad_suffix = 23;

    std::string const raw_value (std::to_string (mantissa));
    std::string val;

    val.reserve (raw_value.length () + pad_prefix + pad_suffix);
    val.append (pad_prefix, '0');
    val.append (raw_value);
    val.append (pad_suffix, '0');

    size_t const offset (exponent + 43);

    auto pre_from (val.begin ());
    auto const pre_to (val.begin () + offset);

    auto const post_from (val.begin () + offset);
    auto post_to (val.end ());

    if (std::distance (pre_from, pre_to) > pad_prefix)
        pre_from += pad_prefix;

    assert (post_to >= post_from);

    pre_from = std::find_if (pre_from, pre_to,
        [](char c)
        {
            return c != '0';
        });

    if (std::distance (post_from, post_to) > pad_suffix)
        post_to -= pad_suffix;

    assert (post_to >= post_from);

    post_to = std::find_if(
        std::make_reverse_iterator (post_to),
        std::make_reverse_iterator (post_from),
        [](char c)
        {
            return c != '0';
        }).base();

    std::string ret;

    if (negative)
        ret.append (1, '-');

    if (pre_from == pre_to)
        ret.append (1, '0');
    else
        ret.append(pre_from, pre_to);

    if (post_to != post_from)
    {
        ret.append (1, '.');
        ret.append (post_from, post_to);
    }

    return ret;
}

IOUAmount
mulRatio (
    IOUAmount const& amt,
    std::uint32_t num,
    std::uint32_t den,
    bool roundUp)
{
    using namespace boost::multiprecision;

    if (!den)
        Throw<std::runtime_error> ("division by zero");

    static auto const powerTable = []
    {
        std::vector<uint128_t> result;
        result.reserve (30);
        uint128_t cur (1);
        for (int i = 0; i < 30; ++i)
        {
            result.push_back (cur);
            cur *= 10;
        };
        return result;
    }();

    static auto log10Floor = [](uint128_t const& v)
    {
        auto const l = std::lower_bound (powerTable.begin (), powerTable.end (), v);
        int index = std::distance (powerTable.begin (), l);
        if (*l != v)
            --index;
        return index;
    };

    static auto log10Ceil = [](uint128_t const& v)
    {
        auto const l = std::lower_bound (powerTable.begin (), powerTable.end (), v);
        return int(std::distance (powerTable.begin (), l));
    };

    static auto const fl64 =
        log10Floor (std::numeric_limits<std::int64_t>::max ());

    bool const neg = amt.mantissa () < 0;
    uint128_t const den128 (den);
    uint128_t const mul =
        uint128_t (neg ? -amt.mantissa () : amt.mantissa ()) * uint128_t (num);

    auto low = mul / den128;
    uint128_t rem (mul - low * den128);

    int exponent = amt.exponent ();

    if (rem)
    {
        auto const roomToGrow = fl64 - log10Ceil (low);
        if (roomToGrow > 0)
        {
            exponent -= roomToGrow;
            low *= powerTable[roomToGrow];
            rem *= powerTable[roomToGrow];
        }
        auto const addRem = rem / den128;
        low += addRem;
        rem = rem - addRem * den128;
    }

    bool hasRem = bool(rem);
    auto const mustShrink = log10Ceil (low) - fl64;
    if (mustShrink > 0)
    {
        uint128_t const sav (low);
        exponent += mustShrink;
        low /= powerTable[mustShrink];
        if (!hasRem)
            hasRem = bool(sav - low * powerTable[mustShrink]);
    }

    std::int64_t mantissa = low.convert_to<std::int64_t> ();

    if (neg)
        mantissa *= -1;

    IOUAmount result (mantissa, exponent);

    if (hasRem)
    {
        if (roundUp && !neg)
        {
            if (!result)
            {
                return IOUAmount (minMantissa, minExponent);
            }
            return IOUAmount (result.mantissa () + 1, result.exponent ());
        }

        if (!roundUp && neg)
        {
            if (!result)
            {
                return IOUAmount (-minMantissa, minExponent);
            }
            return IOUAmount (result.mantissa () - 1, result.exponent ());
        }
    }

    return result;
}

}


/*    Copyright (c) 2010-2018, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 */

#define BOOST_TEST_MAIN

#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/unit_test.hpp>

#include "Tudat/Basics/testMacros.h"

#include "Tudat/Astrodynamics/Ephemerides/approximatePlanetPositions.h"
#include "Tudat/Astrodynamics/Ephemerides/tabulatedEphemeris.h"
#include "Tudat/Basics/basicTypedefs.h"
#include "Tudat/Mathematics/Interpolators/cubicSplineInterpolator.h"

namespace tudat
{
namespace unit_tests
{

//! Test the functionality of the tabulated ephemeris
BOOST_AUTO_TEST_SUITE( test_tabulated_ephemeris )

//! Function to create state history map from any ephemeris.
/*!
 *  Function to create state history map from any ephemeris. Uses arbitrary hardcoded input times
 *  and is used for comparison of tabulated and other ephemerides.
 *  \param originalEphemeris Ephemeris from which table is to be generated
 *  \return State history map generated from given ephemeris
 */
std::map< double, Eigen::Vector6d > getStateHistoryMap(
        const std::shared_ptr< ephemerides::Ephemeris > originalEphemeris  )
{
    std::map< double, Eigen::Vector6d > stateHistoryMap;

    // Define time limits and step
    double startTime = 0.0;
    double finalTime = 1.0E7;
    double timeStep = 1000.0;

    // Create time history map.
    double currentTime = startTime;
    while( currentTime <= finalTime )
    {
        stateHistoryMap[ currentTime ] = originalEphemeris->getCartesianState(
                    currentTime);
        currentTime += timeStep;
    }

    return stateHistoryMap;
}

//! Test the functionality of the tabulated ephemeris
BOOST_AUTO_TEST_CASE( testTabulatedEphemeris )
{
    // Create ephemeris from which table is to be generated; used as input to tabulated ephemeris
    using namespace ephemerides;
    std::shared_ptr< ApproximatePlanetPositions > marsNominalEphemeris =
            std::make_shared< ApproximatePlanetPositions >(
                ApproximatePlanetPositionsBase::mars );

    // Generate state history map from ephemeris
    std::map< double, Eigen::Vector6d > marsStateHistoryMap = getStateHistoryMap(
                marsNominalEphemeris );

    // Create interpolator from state history map.
    std::shared_ptr< interpolators::OneDimensionalInterpolator
            < double, Eigen::Vector6d > > marsStateInterpolator =
            std::make_shared< interpolators::CubicSplineInterpolator
            < double, Eigen::Vector6d > >( marsStateHistoryMap );

    // Create tabulated epehemeris from interpolator
    std::shared_ptr< TabulatedCartesianEphemeris< > > tabulatedEphemeris =
            std::make_shared< TabulatedCartesianEphemeris< > >(
                marsStateInterpolator, "SSB", "J2000");

    // Compare interpolated and tabulated ephemeris state at dummy time.
    double testTime = 1.9337E5;
    Eigen::Vector6d interpolatorState = marsStateInterpolator->interpolate( testTime );
    Eigen::Vector6d ephemerisState = tabulatedEphemeris->getCartesianState(
                testTime);
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( interpolatorState, ephemerisState, 0.0 );

    // Compare direct and tabulated ephemeris state at dummy time (comparison not equal due to
    // interpolation errors).
    Eigen::Vector6d directState = marsNominalEphemeris->getCartesianState(
                testTime);
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( directState, ephemerisState, 1.0E-10 );
    testTime = 5.836392E6;

    // Compare interpolated and tabulated ephemeris state at second dummy time.
    interpolatorState = marsStateInterpolator->interpolate( testTime );
    ephemerisState = tabulatedEphemeris->getCartesianState(
                testTime);
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( interpolatorState, ephemerisState, 0.0 );

    // Compare direct and tabulated ephemeris state at second dummy time (comparison not equal due
    // to interpolation errors).
    directState = marsNominalEphemeris->getCartesianState(
                testTime);
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( directState, ephemerisState, 1.0E-10 );

    // Create new ephemeris from which table is to be generated; used to reset input to tabulated
    // ephemeris
    std::shared_ptr< ApproximatePlanetPositions > jupiterNominalEphemeris =
            std::make_shared< ApproximatePlanetPositions >(
                ApproximatePlanetPositionsBase::jupiter );

    // Reset tabulated ephemeris data.
    std::map< double, Eigen::Vector6d > jupiterStateHistoryMap = getStateHistoryMap(
                jupiterNominalEphemeris );
    std::shared_ptr< interpolators::OneDimensionalInterpolator
            < double, Eigen::Vector6d > > jupiterStateInterpolator =
            std::make_shared< interpolators::CubicSplineInterpolator
            < double, Eigen::Vector6d > >( jupiterStateHistoryMap );
    tabulatedEphemeris->resetInterpolator( jupiterStateInterpolator );

    // Test tabulated ephemeris with reset data.
    interpolatorState = jupiterStateInterpolator->interpolate( testTime );
    ephemerisState = tabulatedEphemeris->getCartesianState(
                testTime);
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( interpolatorState, ephemerisState, 0.0 );
    directState = jupiterNominalEphemeris->getCartesianState(
                testTime);
    TUDAT_CHECK_MATRIX_CLOSE_FRACTION( directState, ephemerisState, 1.0E-10 );

    // Check whether getting of interpolator is correct
    BOOST_CHECK_EQUAL( tabulatedEphemeris->getInterpolator( ), jupiterStateInterpolator );

}

BOOST_AUTO_TEST_SUITE_END( )

} // namespace unit_tests
} // namespace tudat


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

#include "sim/math/ode_solver_odeint.h"

#include <glog/logging.h>
#include <boost/numeric/odeint.hpp>
#include <boost/ref.hpp>

#include <algorithm>
#include <exception>
#include <vector>

#include "sim/math/ode_solver.h"

namespace odeint = boost::numeric::odeint;

namespace sim {

OdeSolverStatus OdeIntOdeSolver::Integrate(double t0, double tf,
                                           const std::vector<double> &x0,
                                           double *t_int,
                                           std::vector<double> *x) {
  std::copy(x0.begin(), x0.end(), x->begin());
  try {
    odeint::runge_kutta_cash_karp54<std::vector<double>> stepper;
    odeint::integrate_adaptive(
        odeint::make_controlled(params_.abs_tolerance, params_.rel_tolerance,
                                stepper),
        boost::ref(*this), *x, t0, tf, params_.initial_time_step);
    if (t_int != nullptr) {
      *t_int = tf;
    }
  } catch (std::exception &ex) {
    LOG(ERROR) << "odeint reported an error: " << ex.what();
    return OdeSolverStatus::kError;
  }
  return OdeSolverStatus::kSuccess;
}

void OdeIntOdeSolver::operator()(
    const std::vector<double> &x,
    std::vector<double> &dx,  // NOLINT(runtime/references)
    double t) {
  ode_system_.CalcDerivatives(t, x, &dx);
}

}  // namespace sim


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_EXPONENTIAL_CONSTANTS_MAXLOG10_HPP_INCLUDED
#define NT2_EXPONENTIAL_CONSTANTS_MAXLOG10_HPP_INCLUDED

#include <nt2/include/functor.hpp>
#include <boost/simd/constant/hierarchy.hpp>
#include <boost/simd/constant/register.hpp>

namespace nt2
{
  namespace tag
  {
    /*!
      @brief Maxlog10 generic tag

      Represents the Maxlog10 constant in generic contexts.

      @par Models:
      Hierarchy
    **/
    BOOST_SIMD_CONSTANT_REGISTER( Maxlog10, double
                                , 0, 0x4218ec59UL
                                , 0x40734413509f79feULL
                                )
  }
  namespace ext
  {
    template<class Site>
    BOOST_FORCEINLINE generic_dispatcher<tag::Maxlog10, Site> dispatching_Maxlog10(adl_helper, boost::dispatch::meta::unknown_<Site>, ...)
    {
      return generic_dispatcher<tag::Maxlog10, Site>();
    }
    template<class... Args>
    struct impl_Maxlog10;
  }
  /*!
    Generates constant Maxlog10 used in logarithm/exponential computations

    @par Semantic:

    @code
    T r = Maxlog10<T>();
    @endcode

    is similar to:

    @code
    if T is double
      r = 308.2547155599167;
    else if T is float
      r = 38.23080825805664;

    @endcode

  **/
  BOOST_SIMD_CONSTANT_IMPLEMENTATION(tag::Maxlog10, Maxlog10);
}

#endif


#define BOOST_TEST_MODULE "test_hybrid_forcefield"

#ifdef BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#else
#include <boost/test/included/unit_test.hpp>
#endif

#include <test/util/utility.hpp>

#include <mjolnir/core/BoundaryCondition.hpp>
#include <mjolnir/core/SimulatorTraits.hpp>
#include <mjolnir/forcefield/hybrid/HybridForceField.hpp>
#include <mjolnir/core/ForceField.hpp>
#include <mjolnir/forcefield/local/BondLengthInteraction.hpp>
#include <mjolnir/forcefield/local/HarmonicPotential.hpp>
#include <mjolnir/util/make_unique.hpp>

#include <random>

BOOST_AUTO_TEST_CASE(MultipleBasin_2Basin_numerical_difference)
{
    namespace test = mjolnir::test;

    mjolnir::LoggerManager::set_default_logger("test_multiple_basin_forcefield.log");

    using traits_type      = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coord_type       = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::HarmonicPotential<real_type>;
    using interaction_type = mjolnir::BondLengthInteraction<traits_type, potential_type>;

    const real_type k(10.0);
    const real_type native1(1.5);
    const real_type native2(2.0);

    mjolnir::LocalForceField<traits_type>      loc1, loc2;
    mjolnir::GlobalForceField<traits_type>     glo1, glo2;
    mjolnir::ExternalForceField<traits_type>   ext1, ext2;
    mjolnir::ConstraintForceField<traits_type> cst1, cst2;

    std::vector<std::pair<std::array<std::size_t, 2>, potential_type>> param1, param2;
    param1.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native1));
    param2.emplace_back(std::array<std::size_t, 2>{{0,1}}, potential_type(k, native2));

    loc1.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param1)));
    loc2.emplace(mjolnir::make_unique<interaction_type>("none", std::move(param2)));

    mjolnir::ForceField<traits_type> ff1(std::move(loc1), std::move(glo1), std::move(ext1), std::move(cst1));
    mjolnir::ForceField<traits_type> ff2(std::move(loc2), std::move(glo2), std::move(ext2), std::move(cst2));

    const real_type lambda = 0.5;
    mjolnir::HybridForceField<traits_type> forcefield(lambda,
        mjolnir::make_unique<mjolnir::ForceField<traits_type>>(ff1),
        mjolnir::make_unique<mjolnir::ForceField<traits_type>>(ff2));

    std::mt19937 rng(123456789);
    for(std::size_t i = 0; i < 1000; ++i)
    {
        system_type sys(2, boundary_type{});
        test::clear_everything(sys);

        sys.position(0) = coord_type( 0.0, 0.0, 0.0);
        sys.position(1) = coord_type( 1.0, 1.0, 1.0);

        test::apply_random_perturbation(sys, rng, 0.01);
        test::apply_random_rotation(sys, rng);

        forcefield.initialize(sys); // don't forget this

        constexpr real_type tol = 1e-4;
        constexpr real_type dr  = 1e-5;

        test::check_force(sys, forcefield, tol, dr);

        const auto Etest = forcefield.calc_energy(sys);
        const auto E1    = ff1.calc_energy(sys);
        const auto E2    = ff2.calc_energy(sys);

        BOOST_TEST(Etest == lambda * E1 + (1.0 - lambda) * E2, boost::test_tools::tolerance(tol));
    }
}


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

// Copyright (c) 2012-2014 Barend Gehrels, Amsterdam, the Netherlands.

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

#ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_BUFFER_JOIN_MITER_HPP
#define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_BUFFER_JOIN_MITER_HPP

#include <boost/assert.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/policies/compare.hpp>
#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/select_most_precise.hpp>

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

namespace boost { namespace geometry
{

namespace strategy { namespace buffer
{

/*!
\brief Let the buffer create sharp corners
\ingroup strategies
\details This strategy can be used as JoinStrategy for the buffer algorithm.
    It creates a sharp corners around each convex vertex. It can be applied
    for (multi)linestrings and (multi)polygons.
    If corners are sharp by themselves, the miters might become very long. Therefore
    there is a limit (miter_limit), in terms of the used distance, which limits
    their length. The miter is not changed to a bevel form (as done in some
    other software), it is just adapted to the specified miter_limit but keeps
    its miter form.
    If the buffer distance is 5.0, and the miter limit is 2.0, generated points
    will be located at a distance of at most 10.0 (2*5) units.
    This strategy is only applicable for Cartesian coordinate systems.

\qbk{
[heading Example]
[buffer_join_miter]
[heading Output]
[$img/strategies/buffer_join_miter.png]
[heading See also]
\* [link geometry.reference.algorithms.buffer.buffer_7_with_strategies buffer (with strategies)]
\* [link geometry.reference.strategies.strategy_buffer_join_round join_round]
}
 */
class join_miter
{
public:

    //! \brief Constructs the strategy
    //! \param miter_limit The miter limit, to avoid excessively long miters around sharp corners
    explicit inline join_miter(double miter_limit = 5.0)
        : m_miter_limit(valid_limit(miter_limit))
    {}

#ifndef DOXYGEN_SHOULD_SKIP_THIS
    //! Fills output_range with a sharp shape around a vertex
    template <typename Point, typename DistanceType, typename RangeOut>
    inline bool apply(Point const& ip, Point const& vertex,
                Point const& perp1, Point const& perp2,
                DistanceType const& buffer_distance,
                RangeOut& range_out) const
    {
        geometry::equal_to<Point> equals;
        if (equals(ip, vertex))
        {
            return false;
        }
        if (equals(perp1, perp2))
        {
            return false;
        }

        typedef typename coordinate_type<Point>::type coordinate_type;
        typedef typename geometry::select_most_precise
        <
            coordinate_type,
            double
        >::type promoted_type;

        Point p = ip;

        // Check the distance ip-vertex (= miter distance)
        // (We calculate it manually (not using Pythagoras strategy) to reuse
        //  dx and dy)
        coordinate_type const dx = get<0>(p) - get<0>(vertex);
        coordinate_type const dy = get<1>(p) - get<1>(vertex);

        promoted_type const distance = geometry::math::sqrt(dx * dx + dy * dy);

        promoted_type const max_distance
            = m_miter_limit * geometry::math::abs(buffer_distance);

        if (distance > max_distance)
        {
            BOOST_ASSERT(distance != 0.0);

            promoted_type const proportion = max_distance / distance;
            set<0>(p, get<0>(vertex) + dx * proportion);
            set<1>(p, get<1>(vertex) + dy * proportion);
        }

        range_out.push_back(perp1);
        range_out.push_back(p);
        range_out.push_back(perp2);
        return true;
    }

    template <typename NumericType>
    inline NumericType max_distance(NumericType const& distance) const
    {
        return distance * m_miter_limit;
    }

#endif // DOXYGEN_SHOULD_SKIP_THIS

private :
    double valid_limit(double miter_limit) const
    {
        if (miter_limit < 1.0)
        {
            // It should always exceed the buffer distance
            miter_limit = 1.0;
        }
        return miter_limit;
    }

    double m_miter_limit;
};

}} // namespace strategy::buffer

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_BUFFER_JOIN_MITER_HPP


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_SAFE_MAX_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SAFE_MAX_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-ieee
    This function object returns a safe maximal value relative to the input,  i.e. a
    value which will not overflow when multiplied by the input argument.

    @par Header <boost/simd/function/safe_max.hpp>

    @par Example:

      @snippet safe_max.cpp safe_max

    @par Possible output:

      @snippet safe_max.txt safe_max

  **/
  IEEEValue safe_max(IEEEValue const& x);
} }
#endif

#include <boost/simd/function/scalar/safe_max.hpp>
#include <boost/simd/function/simd/safe_max.hpp>

#endif


/**
 * @cond ___LICENSE___
 *
 * Copyright (c) 2016-2019 Zefiros Software.
 *
 * 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.
 *
 * @endcond
 */
#include "lineal/lineal.h"

#include <armadillo>

#include <cstdlib>
#include <iostream>
#include <chrono>

template<typename tRow, typename tCol>
void bench_op(const tRow &row, const tCol &col)
{
    auto row_op = (row * 2.0) / 3.0;
    auto col_op = col * 2.0;

    auto start = std::chrono::high_resolution_clock::now();

    double alpha = 0.0;

    constexpr size_t iterations = 2000000;

    std::cout << lineal::is_scalar_addition<decltype(col_op)> << std::endl;

    for (size_t i = 0; i < iterations; ++i)
    {
        alpha += arma::as_scalar(row_op * col_op);

    }

    auto end = std::chrono::high_resolution_clock::now();
    std::cout << alpha << std::endl;
    std::cout << "Took "
              << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() * 1.0 / iterations
              << "ns per dot-product" << std::endl;
}

void main(int argc, char **argv)
{
    {
        lineal::Row<double> row(1024, lineal::fill::ones);
        lineal::Col<double> col(1024, lineal::fill::ones);
        lineal::ConstCol<double> const_col(col.begin(), col.size());

        bench_op(row, const_col);
    }
    {
        arma::rowvec row(1024, arma::fill::ones);
        arma::vec col(1024, arma::fill::ones);
        bench_op(row, col);
    }
    // system("pause");
    //     auto row_op0 = 1.0 / (4.0 + row + 3.0);
    //     auto row_op = 2.0 / row_op0;
    //     auto col_op = (5.0 - (col + 2.0) - 1.0) * 2.0;

    //     auto row_op = row + 2.0;
    //     auto col_op = const_col / 2.0;
    //
    //     auto start = std::chrono::high_resolution_clock::now();
    //
    //     double alpha = 0.0;
    //
    //     constexpr size_t iterations = 2000000;
    //
    //     std::cout << lineal::is_scalar_addition<decltype(col_op)> << std::endl;
    //
    //     for (size_t i = 0; i < iterations; ++i)
    //     {
    //         alpha += row_op * col_op;
    //     }
    //
    //     auto end = std::chrono::high_resolution_clock::now();
    //     std::cout << alpha << std::endl;
    //     std::cout << "Took "
    //               << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() * 1.0 / iterations
    //               << "ns per dot-product" << std::endl;
}


#pragma once

#include <Eigen/Dense>
#include <concepts>
#include <type_traits>

namespace Vectored {

namespace Internal {

class SuperVectored {
private:
    Eigen::Vector3d m_vec;

public:
    SuperVectored()
        : m_vec(Eigen::Vector3d(0, 0, 0))
    {
    }

    SuperVectored(double x, double y, double z)
        : m_vec(Eigen::Vector3d(x, y, z))
    {
    }

    explicit SuperVectored(Eigen::Vector3d vector)
        : m_vec(std::move(vector))
    {
    }

    Eigen::Vector3d as_vec()
    {
        return this->m_vec;
    }

    template<typename T>
        requires(std::is_base_of<SuperVectored, T>::value)
    explicit SuperVectored(
        T& vector)
        : m_vec(vector.as_vec())
    {
    }

    template<typename T>
        requires(std::is_base_of<SuperVectored, T>::value)
    T as()
    {
        T result(this->m_vec);
        return result;
    }
};

template<typename Class>
concept VectoredQuantity = std::is_base_of<SuperVectored, Class>::value;

} // namespace Internal

} // namespace Vectored

namespace Scalar {

namespace Internal {

class SuperScalar {
private:
    double m_scalar;

public:
    SuperScalar()
        : m_scalar(1.0)
    {
    }

    SuperScalar(double value)
        : m_scalar(value)
    {
    }

    double as_value()
    {
        return this->m_scalar;
    }
};

template<typename Class>
concept ScalarQuantity = std::is_base_of<SuperScalar, Class>::value;

} // namespace Internal

} // namespace Scalar

#define VECTORED(name)                                          \
    class name : public Vectored::Internal::SuperVectored {     \
    public:                                                     \
        using Vectored::Internal::SuperVectored::SuperVectored; \
    }

#define SCALAR(name)                                      \
    class name : public Scalar::Internal::SuperScalar {   \
    public:                                               \
        using Scalar::Internal::SuperScalar::SuperScalar; \
    }

namespace Scalar {

namespace Internal {

template<typename T, ScalarQuantity U>
U operator+(T fst, U& other)
{
    U result(fst + other.as_value());
    return result;
}

template<ScalarQuantity T, typename U>
T operator+(T& fst, U other)
{
    T result(fst.as_value() + other);
    return result;
}

template<ScalarQuantity T, ScalarQuantity U>
T operator+(T& fst, U& other)
{
    T result(fst.as_value() + other.as_value());
    return result;
}

template<typename T, ScalarQuantity U>
U operator-(T fst, U& other)
{
    U result(fst - other.as_value());
    return result;
}

template<ScalarQuantity T, typename U>
T operator-(T fst, U other)
{
    T result(fst.as_value() - other);
    return result;
}

template<ScalarQuantity T, ScalarQuantity U>
T operator-(T& fst, U& other)
{
    T result(fst.as_value() - other.as_value());
    return result;
}

template<typename T, ScalarQuantity U>
U operator*(T fst, U& other)
{
    U result(fst * other.as_value());
    return result;
}

template<ScalarQuantity T, typename U>
T operator*(T& fst, U other)
{
    T result(fst.as_value() * other);
    return result;
}

template<ScalarQuantity T, ScalarQuantity U>
T operator*(T& fst, U& other)
{
    T result(fst.as_value() * other.as_value());
    return result;
}

template<ScalarQuantity T, Vectored::Internal::VectoredQuantity U>
U operator*(T& fst, U& other)
{
    U result(fst.as_value() * other.as_vec());
    return result;
}

template<typename T, ScalarQuantity U>
U operator/(T fst, U& other)
{
    U result(fst * other.as_value());
    return result;
}

template<ScalarQuantity T, typename U>
T operator/(T& fst, U other)
{
    T result(fst.as_value() * other);
    return result;
}

template<ScalarQuantity T, ScalarQuantity U>
T operator/(T& fst, U& other)
{
    T result(fst.as_value() * other.as_value());
    return result;
}

} // namespace Scalar

} // namespace Vectored

namespace Vectored {

namespace Internal {

template<VectoredQuantity T, VectoredQuantity U>
T operator+(T fst, U other)
{
    T result(fst.as_vec() + other.as_vec());
    return result;
}

template<VectoredQuantity T, VectoredQuantity U>
T operator-(T fst, U other)
{
    T result(fst.as_vec() - other.as_vec());
    return result;
}

template<typename T, VectoredQuantity U>
U operator*(T fst, U& other)
{
    U result(fst * other.as_vec());
    return result;
}

template<VectoredQuantity T, typename U>
T operator*(T& fst, U other)
{
    T result(fst.as_vec() * other);
    return result;
}

template<VectoredQuantity T, Scalar::Internal::ScalarQuantity U>
T operator*(T& fst, U& other)
{
    T result(fst.as_vec() * other.as_value());
    return result;
}

} // namespace Internal

} // namespace Vectored

namespace Vectored {
VECTORED(Acceleration);
VECTORED(Force);
VECTORED(Momentum);
VECTORED(Position);
VECTORED(Velocity);
} // namespace Vectored

namespace Scalar {
SCALAR(Time);
SCALAR(Mass);
} // namespace Scalar


//
//   Project Name:        Kratos
//   Last modified by:    $Author: rrossi $
//   Date:                $Date: 2008-12-09 20:20:55 $
//   Revision:            $Revision: 1.5 $
//
//

// System includes

#if defined(KRATOS_PYTHON)
// External includes
#include <boost/python.hpp>

//Trilinos includes
#include "mpi.h"
#include "Epetra_MpiComm.h"
#include "Epetra_Comm.h"
#include "Epetra_Map.h"
#include "Epetra_Vector.h"
#include "Epetra_FECrsGraph.h"
#include "Epetra_FECrsMatrix.h"
#include "Epetra_FEVector.h"
#include "Epetra_IntSerialDenseVector.h"
#include "Epetra_SerialDenseMatrix.h"

// Project includes
#include "includes/define.h"
#include "trilinos_application.h"
#include "trilinos_space.h"
#include "spaces/ublas_space.h"
// #include "add_trilinos_linear_solvers_to_python.h"
#include "includes/model_part.h"

//strategies
#include "solving_strategies/strategies/solving_strategy.h"
#include "solving_strategies/strategies/residualbased_linear_strategy.h"
#include "solving_strategies/strategies/residualbased_newton_raphson_strategy.h"

//schemes
#include "solving_strategies/schemes/scheme.h"
#include "custom_strategies/schemes/trilinos_residualbased_incrementalupdate_static_scheme.h"

//convergence criterias
#include "solving_strategies/convergencecriterias/convergence_criteria.h"
#include "solving_strategies/convergencecriterias/displacement_criteria.h"

//Builder And Solver
// #include "solving_strategies/builder_and_solvers/builder_and_solver.h"
#include "custom_strategies/builder_and_solvers/trilinos_residualbased_elimination_builder_and_solver.h"
#include "custom_strategies/convergencecriterias/trilinos_displacement_criteria.h"

//linear solvers
#include "linear_solvers/linear_solver.h"

//utilities
#include "python/pointer_vector_set_python_interface.h"

//teuchos parameter list
#include "Teuchos_ParameterList.hpp"

#include "external_includes/aztec_solver.h"
#include "external_includes/amesos_solver.h"
#include "external_includes/ml_solver.h"

namespace Kratos
{

namespace Python
{

using namespace boost::python;

void EraseAll(std::string& ThisString, std::string ToBeRemoved)
{
int position;
while( (position = ThisString.find_first_of(ToBeRemoved)) >= 0 )
{
	ThisString.erase(position ,ToBeRemoved.size() );
}

}

std::string ErrorCleaner(std::string const& Input)
{
std::string output(Input);

EraseAll(output, "boost::numeric::");

return output;
}

typedef TrilinosSpace<Epetra_FECrsMatrix, Epetra_FEVector> TrilinosSparseSpaceType;
typedef UblasSpace<double, Matrix, Vector> TrilinosLocalSpaceType;
typedef LinearSolver<TrilinosSparseSpaceType, TrilinosLocalSpaceType > TrilinosLinearSolverType;

typedef Epetra_FECrsMatrix FECrsMatrix;

	void prova(TrilinosSparseSpaceType& dummy, FECrsMatrix& rX)
	{    rX.PutScalar(0.0);}

	double Dot(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType& rX, TrilinosSparseSpaceType::VectorType& rY)
	{   return dummy.Dot(rX,rY);}

	void ScaleAndAdd(TrilinosSparseSpaceType& dummy, const double A, const TrilinosSparseSpaceType::VectorType& rX, const double B, TrilinosSparseSpaceType::VectorType& rY)
	// rY = (A * rX) + (B * rY)
	{	dummy.ScaleAndAdd(A,rX,B, rY);	}

	void Mult(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::MatrixType& rA, TrilinosSparseSpaceType::VectorType& rX, TrilinosSparseSpaceType::VectorType& rY)
	//rY=A*rX (the product is stored inside the rY)
	{   dummy.Mult(rA, rX, rY);}

 	void TransposeMult(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::MatrixType& rA, TrilinosSparseSpaceType::VectorType& rX, TrilinosSparseSpaceType::VectorType& rY)
 	{   dummy.TransposeMult(rA, rX, rY);}

	TrilinosSparseSpaceType::IndexType Size(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType const& rV)
	{return dummy.Size(rV);}

	void ResizeMatrix(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::MatrixType& A, unsigned int i1, unsigned int i2)
	{	dummy.Resize(A,i1,i2);	}

	void ResizeVector(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType& x, unsigned int i1)
	{	dummy.Resize(x,i1);	}

	void SetToZeroMatrix(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::MatrixType& A)
	{	dummy.SetToZero(A);	}

	void SetToZeroVector(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType& x)
	{	dummy.SetToZero(x);	}

	void ClearMatrix(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::MatrixType& A)
	{	dummy.Clear(A);	}

	void ClearVector(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType& x)
	{	dummy.Clear(x);	}

	double TwoNorm(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType& x)
	{	return dummy.TwoNorm(x);	}

	void UnaliasedAdd(TrilinosSparseSpaceType& dummy, TrilinosSparseSpaceType::VectorType& x, const double A, const TrilinosSparseSpaceType::VectorType& rY) // x+= a*Y
	{	dummy.UnaliasedAdd(x,A,rY);	}

// bool (TrilinosLinearSolverType::*pointer_to_solve)(TrilinosLinearSolverType::SparseMatrixType& rA, TrilinosLinearSolverType::VectorType& rX, TrilinosLinearSolverType::VectorType& rB) = &TrilinosLinearSolverType::Solve;

	//************************************************************************************************
	Epetra_MpiComm CreateCommunicator()
	{
		Epetra_MpiComm comm(MPI_COMM_WORLD);
		return comm;
	}

	//************************************************************************************************

	//************************************************************************************************
	TrilinosSparseSpaceType::MatrixPointerType CreateEmptyMatrixPointer(TrilinosSparseSpaceType& dummy, Epetra_MpiComm& Comm){ return dummy.CreateEmptyMatrixPointer(Comm);}
	TrilinosSparseSpaceType::VectorPointerType CreateEmptyVectorPointer(TrilinosSparseSpaceType& dummy, Epetra_MpiComm& Comm){ return dummy.CreateEmptyVectorPointer(Comm);}

 	Epetra_FECrsMatrix& GetMatRef(TrilinosSparseSpaceType::MatrixPointerType& dummy)
 	{	return *dummy;	}
 	Epetra_FEVector& GetVecRef(TrilinosSparseSpaceType::VectorPointerType& dummy)
 	{	return *dummy;	}
	//************************************************************************************************

	void MoveMesh( Scheme< TrilinosSparseSpaceType, TrilinosLocalSpaceType >& dummy, ModelPart::NodesContainerType& rNodes)
	{
		for(ModelPart::NodeIterator i = rNodes.begin() ; i != rNodes.end() ; ++i)
		{
			const array_1d<double,3>& disp = i->FastGetSolutionStepValue(DISPLACEMENT);
			(i)->X() = (i)->X0() + disp[0];
			(i)->Y() = (i)->Y0() + disp[1];
			(i)->Z() = (i)->Z0() + disp[2];
		}
	}

	//************************************************************************************************
	//************************************************************************************************
	//teuchos paramter list
	void 	SetDoubleValue(Teuchos::ParameterList& dummy, const string &name, double value)
	{   dummy.set(name,value);	}
	void 	SetIntValue(Teuchos::ParameterList& dummy, const string &name, int value)
	{   dummy.set(name,value);	}
	void 	SetCharValue(Teuchos::ParameterList& dummy, const string &name, const char value[])
	{   dummy.set(name,value);	}

	BOOST_PYTHON_MODULE(KratosTrilinosApplication)
	{

			class_< Epetra_MpiComm  >("Epetra_MpiComm", init< Epetra_MpiComm& >() )
			;

			class_< Epetra_FECrsMatrix >("Epetra_FECrsMatrix", init< Epetra_FECrsMatrix& >() )
			;

			class_< Epetra_FEVector >("Epetra_FEVector", init< Epetra_FEVector& >() )
			;

			class_< TrilinosSparseSpaceType::MatrixPointerType  >("TrilinosMatrixPointer", init< TrilinosSparseSpaceType::MatrixPointerType >() )
  				.def("GetReference", GetMatRef, return_value_policy<reference_existing_object>() )
			;

			class_< TrilinosSparseSpaceType::VectorPointerType >("TrilinosVectorPointer", init< TrilinosSparseSpaceType::VectorPointerType >() )
  				.def("GetReference", GetVecRef, return_value_policy<reference_existing_object>() )
			;

		class_<KratosTrilinosApplication,
				KratosTrilinosApplication::Pointer,
				bases<KratosApplication>, boost::noncopyable >("KratosTrilinosApplication")
				;

		class_<TrilinosLinearSolverType, TrilinosLinearSolverType::Pointer>("TrilinosLinearSolver")
/*		.def("Initialize",&TrilinosLinearSolverType::Initialize)
		.def("Solve",pointer_to_solve)
			//.def("",&LinearSolverType::)
			.def(self_ns::str(self))*/
			;

		typedef SolvingStrategy< TrilinosSparseSpaceType, TrilinosLocalSpaceType, TrilinosLinearSolverType > TrilinosBaseSolvingStrategyType;
		typedef Scheme< TrilinosSparseSpaceType, TrilinosLocalSpaceType > TrilinosBaseSchemeType;
		typedef TrilinosResidualBasedEliminationBuilderAndSolver< TrilinosSparseSpaceType, TrilinosLocalSpaceType, TrilinosLinearSolverType > TrilinosBuilderAndSolverType;

		//********************************************************************
		//********************************************************************
		class_< TrilinosBaseSchemeType, boost::noncopyable >
				("TrilinosScheme", init< >() )
				.def("Initialize", &TrilinosBaseSchemeType::Initialize )
				.def("SchemeIsInitialized", &TrilinosBaseSchemeType::SchemeIsInitialized )
				.def("ElementsAreInitialized", &TrilinosBaseSchemeType::ElementsAreInitialized )
				.def("InitializeElements", &TrilinosBaseSchemeType::InitializeElements )
				.def("InitializeSolutionStep", &TrilinosBaseSchemeType::InitializeSolutionStep )
				.def("FinalizeSolutionStep", &TrilinosBaseSchemeType::FinalizeSolutionStep )
				.def("InitializeNonLinIteration", &TrilinosBaseSchemeType::InitializeNonLinIteration )
				.def("FinalizeNonLinIteration", &TrilinosBaseSchemeType::FinalizeNonLinIteration )
				.def("Predict", &TrilinosBaseSchemeType::Predict )
				.def("Update", &TrilinosBaseSchemeType::Update )
				.def("CalculateOutputData", &TrilinosBaseSchemeType::CalculateOutputData )
				.def("Clean", &TrilinosBaseSchemeType::Clean )
				.def("MoveMesh", MoveMesh )
				;

		class_< TrilinosResidualBasedIncrementalUpdateStaticScheme< TrilinosSparseSpaceType, TrilinosLocalSpaceType>,
			bases< TrilinosBaseSchemeType >,  boost::noncopyable >
			(
				"TrilinosResidualBasedIncrementalUpdateStaticScheme", init< >()
			);

		//********************************************************************
		//********************************************************************
		//convergence criteria base class
		typedef ConvergenceCriteria< TrilinosSparseSpaceType, TrilinosLocalSpaceType > TrilinosConvergenceCriteria;
		class_< TrilinosConvergenceCriteria, boost::noncopyable >("TrilinosConvergenceCriteria", init<>() )
			.def("SetActualizeRHSFlag", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::SetActualizeRHSFlag )
			.def("GetActualizeRHSflag", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::GetActualizeRHSflag )
			.def("PreCriteria", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::PreCriteria )
			.def("PostCriteria", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::PostCriteria )
			.def("Initialize", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::Initialize )
			.def("InitializeSolutionStep", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::InitializeSolutionStep )
			.def("FinalizeSolutionStep", &ConvergenceCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >::FinalizeSolutionStep )
			;

		class_< TrilinosDisplacementCriteria<TrilinosSparseSpaceType, TrilinosLocalSpaceType >,
			bases< TrilinosConvergenceCriteria >,
			boost::noncopyable >
			("TrilinosDisplacementCriteria", init< double, double, Epetra_MpiComm& >() );

		//********************************************************************
		//********************************************************************
		//Builder and Solver

		class_< TrilinosBuilderAndSolverType::DofsArrayType, boost::noncopyable >("DofsArrayType",	init<>() );

		class_< TrilinosBuilderAndSolverType, boost::noncopyable >
			("TrilinosResidualBasedBuilderAndSolver",init<Epetra_MpiComm&, int, TrilinosLinearSolverType::Pointer >() )
			.def("SetCalculateReactionsFlag", &TrilinosBuilderAndSolverType::SetCalculateReactionsFlag )
			.def("GetCalculateReactionsFlag", &TrilinosBuilderAndSolverType::GetCalculateReactionsFlag )
			.def("SetDofSetIsInitializedFlag", &TrilinosBuilderAndSolverType::SetDofSetIsInitializedFlag )
			.def("GetDofSetIsInitializedFlag", &TrilinosBuilderAndSolverType::GetDofSetIsInitializedFlag )
			.def("SetReshapeMatrixFlag", &TrilinosBuilderAndSolverType::SetReshapeMatrixFlag )
			.def("GetReshapeMatrixFlag", &TrilinosBuilderAndSolverType::GetReshapeMatrixFlag )
			.def("GetEquationSystemSize", &TrilinosBuilderAndSolverType::GetEquationSystemSize )
			.def("BuildLHS", &TrilinosBuilderAndSolverType::BuildLHS )
			.def("BuildRHS", &TrilinosBuilderAndSolverType::BuildRHS )
			.def("Build", &TrilinosBuilderAndSolverType::Build )
			.def("SystemSolve", &TrilinosBuilderAndSolverType::SystemSolve )
			.def("BuildAndSolve", &TrilinosBuilderAndSolverType::BuildAndSolve )
			.def("BuildRHSAndSolve", &TrilinosBuilderAndSolverType::BuildRHSAndSolve )
			.def("ApplyDirichletConditions", &TrilinosBuilderAndSolverType::ApplyDirichletConditions )
			.def("SetUpDofSet", &TrilinosBuilderAndSolverType::SetUpDofSet )
			.def("GetDofSet", &TrilinosBuilderAndSolverType::GetDofSet, return_internal_reference<>() )
			.def("SetUpSystem", &TrilinosBuilderAndSolverType::SetUpSystem )
			.def("ResizeAndInitializeVectors", &TrilinosBuilderAndSolverType::ResizeAndInitializeVectors )
			.def("InitializeSolutionStep", &TrilinosBuilderAndSolverType::InitializeSolutionStep )
			.def("FinalizeSolutionStep", &TrilinosBuilderAndSolverType::FinalizeSolutionStep )
			.def("CalculateReactions", &TrilinosBuilderAndSolverType::CalculateReactions )
			.def("Clear", &TrilinosBuilderAndSolverType::Clear )
			.def("SetEchoLevel", &TrilinosBuilderAndSolverType::SetEchoLevel )
			.def("GetEchoLevel", &TrilinosBuilderAndSolverType::GetEchoLevel )
			;

		//********************************************************************
		//********************************************************************

		class_< TrilinosSparseSpaceType, boost::noncopyable >("TrilinosSparseSpace",	init<>() )
		.def("ClearMatrix", ClearMatrix )
		.def("ClearVector", ClearVector )
		.def("ResizeMatrix", ResizeMatrix )
			.def("ResizeVector", ResizeVector )
			.def("SetToZeroMatrix", SetToZeroMatrix )
			.def("SetToZeroVector", SetToZeroVector )
			.def("TwoNorm", TwoNorm )
			//the dot product of two vectors
			.def("Dot", Dot)
			//the matrix-vector multiplication
			.def("Mult", Mult)
	// 		 .def("TransposeMult", TransposeMult)
			.def("Size", Size)
			.def("UnaliasedAdd",UnaliasedAdd)
			.def("ScaleAndAdd",ScaleAndAdd)
			.def("prova",prova)
			.def("CreateEmptyMatrixPointer",CreateEmptyMatrixPointer)
			.def("CreateEmptyVectorPointer",CreateEmptyVectorPointer)
			;

		//********************************************************************
		//********************************************************************
		//strategy base class
// 		class_< TrilinosBaseSolvingStrategyType, boost::noncopyable >("TrilinosSolvingStrategy", init< ModelPart&, bool >() )
// 			.def("Predict", &TrilinosBaseSolvingStrategyType::Predict )
// 			.def("Solve", &TrilinosBaseSolvingStrategyType::Solve )
// 			.def("IsConverged", &TrilinosBaseSolvingStrategyType::IsConverged )
// 			.def("CalculateOutputData", &TrilinosBaseSolvingStrategyType::CalculateOutputData )
// 			.def("SetEchoLevel", &TrilinosBaseSolvingStrategyType::SetEchoLevel )
// 			.def("GetEchoLevel", &TrilinosBaseSolvingStrategyType::GetEchoLevel )
// 			.def("SetRebuildLevel", &TrilinosBaseSolvingStrategyType::SetRebuildLevel )
// 			.def("GetRebuildLevel", &TrilinosBaseSolvingStrategyType::GetRebuildLevel )
// 			.def("SetMoveMeshFlag", &TrilinosBaseSolvingStrategyType::SetMoveMeshFlag )
// 			.def("MoveMeshFlag", &TrilinosBaseSolvingStrategyType::MoveMeshFlag )
// 			.def("MoveMesh", &TrilinosBaseSolvingStrategyType::MoveMesh )
// 			.def("Clear", &TrilinosBaseSolvingStrategyType::Clear )
// 			;
//
// 		class_< ResidualBasedLinearStrategy<  TrilinosSparseSpaceType, TrilinosLocalSpaceType, TrilinosLinearSolverType >,
// 			bases< TrilinosBaseSolvingStrategyType >,  boost::noncopyable >
// 			("TrilinosResidualBasedLinearStrategy",
// 			init<ModelPart&,TrilinosBaseSchemeType::Pointer, TrilinosLinearSolverType::Pointer, bool, bool, bool, bool	>() )
// 			;
//
// 		class_< ResidualBasedNewtonRaphsonStrategy<  TrilinosSparseSpaceType, TrilinosLocalSpaceType, TrilinosLinearSolverType >,
// 			bases< TrilinosBaseSolvingStrategyType >,  boost::noncopyable >
// 			("TrilinosResidualBasedNewtonRaphsonStrategy")
// 			.def(init<ModelPart&, TrilinosBaseSchemeType::Pointer, TrilinosLinearSolverType::Pointer, TrilinosConvergenceCriteria::Pointer, int, bool, bool, bool >() )
// 			.def( init<ModelPart&, TrilinosBaseSchemeType::Pointer, TrilinosLinearSolverType::Pointer,TrilinosConvergenceCriteria::Pointer,TrilinosBuilderAndSolverType::Pointer, int, bool, bool, bool >() )
// 			;

	def("CreateCommunicator", CreateCommunicator );
	def("ErrorCleaner", ErrorCleaner);

		//********************************************************************
		//********************************************************************
		class_< Teuchos::ParameterList, boost::noncopyable >("ParameterList",	init<>() )
			.def("set", SetDoubleValue )
			.def("set", SetIntValue )
			.def("set", SetCharValue )
			.def(self_ns::str(self))
		  ;

	typedef AztecSolver<TrilinosSparseSpaceType, TrilinosLocalSpaceType > AztecSolverType;
	class_<AztecSolverType, bases<TrilinosLinearSolverType>, boost::noncopyable >
		( "AztecSolver",
			init<	Teuchos::ParameterList&, std::string, Teuchos::ParameterList& , double , int , int  >() );

	typedef AmesosSolver<TrilinosSparseSpaceType, TrilinosLocalSpaceType > AmesosSolverType;
	class_<AmesosSolverType, bases<TrilinosLinearSolverType>, boost::noncopyable >
		( "AmesosSolver",
			init<const std::string&, Teuchos::ParameterList& >() );

	typedef MultiLevelSolver<TrilinosSparseSpaceType, TrilinosLocalSpaceType > MLSolverType;
	class_<MLSolverType, bases<TrilinosLinearSolverType>, boost::noncopyable >
		( "MultiLevelSolver",
			init<Teuchos::ParameterList&, double, int >() );

}

}  // namespace Python.

}  // namespace Kratos.

#endif // KRATOS_PYTHON defined


//=======================================================================
// Copyright 2014-2015 David Simmons-Duffin.
// Distributed under the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#include "../sdp_solve.hxx"
#include "../set_stream_precision.hxx"

#include <El.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/filesystem.hpp>

Timers
solve(const Block_Info &block_info, const SDP_Solver_Parameters &parameters)
{
  // Read an SDP from sdpFile and create a solver for it
  El::Grid grid(block_info.mpi_comm.value);
  SDP sdp(parameters.sdp_directory, block_info, grid);
  SDP_Solver solver(parameters, block_info, grid,
                    sdp.dual_objective_b.Height());

  Timers timers(parameters.verbosity >= Verbosity::debug);
  SDP_Solver_Terminate_Reason reason
    = solver.run(parameters, block_info, sdp, grid, timers);

  if(parameters.verbosity >= Verbosity::regular && El::mpi::Rank() == 0)
    {
      set_stream_precision(std::cout);
      std::cout << "-----" << reason << "-----\n"
                << '\n'
                << "primalObjective = " << solver.primal_objective << '\n'
                << "dualObjective   = " << solver.dual_objective << '\n'
                << "dualityGap      = " << solver.duality_gap << '\n'
                << "primalError     = " << solver.primal_error() << '\n'
                << "dualError       = " << solver.dual_error << '\n'
                << '\n';
    }

  if(!parameters.no_final_checkpoint)
    {
      solver.save_checkpoint(parameters);
    }
  solver.save_solution(reason, timers.front(), parameters.out_directory,
                       parameters.write_solution,
                       block_info.block_indices,
                       parameters.verbosity);
  return timers;
}


// Copyright 2021 Anthony Paul Astolfi
//
#include <batteries/seq.hpp>
//
#include <batteries/seq.hpp>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <boost/range/irange.hpp>

#include <array>
#include <random>
#include <set>
#include <type_traits>
#include <utility>
#include <vector>

namespace {

using batt::as_seq;
using batt::make_optional;
using batt::None;
namespace seq = batt::seq;

template <typename T>
std::decay_t<T> force_copy(T&& src)
{
    std::array<char, sizeof(std::decay_t<T>)> buffer1;
    std::array<char, sizeof(std::decay_t<T>)> buffer2;

    auto* tmp1 = new (buffer1.data()) std::decay_t<T>(BATT_FORWARD(src));
    auto* tmp2 = new (buffer2.data()) std::decay_t<T>(std::move(*tmp1));

    tmp1->~T();
    buffer1.fill(0xff);

    std::decay_t<T> dst = *tmp2;

    tmp2->~T();
    buffer2.fill(0xff);

    return dst;
}

TEST(SeqTest, CollectVecTest)
{
    EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8}), as_seq(boost::irange(1, 9)) | seq::collect_vec());
}

TEST(SeqTest, TakeWhileTest)
{
    EXPECT_EQ((std::vector<int>{1, 2, 3}), as_seq(boost::irange(1, 9))  //
                                               | seq::take_while([](int i) {
                                                     return i < 4;
                                                 })  //
                                               | seq::collect_vec());

    EXPECT_EQ((std::vector<int>{}), as_seq(boost::irange(1, 9))  //
                                        | seq::take_while([](int i) {
                                              return i % 2 == 0;
                                          })  //
                                        | seq::collect_vec());

    EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8}), as_seq(boost::irange(1, 9))  //
                                                              | seq::take_while([](int i) {
                                                                    return i == i;
                                                                })  //
                                                              | seq::collect_vec());
}

TEST(SeqTest, FilterTest)
{
    EXPECT_EQ((std::vector<int>{1, 5, 7}), as_seq(boost::irange(0, 10))  //
                                               | seq::filter([](int i) {
                                                     return i % 2 != 0;
                                                 })  //
                                               | seq::filter([](int i) {
                                                     return i % 3 != 0;
                                                 })  //
                                               | seq::collect_vec());
}

TEST(SeqTest, MapTest)
{
    EXPECT_EQ((std::vector<int>{0, -2, -4, -6}), as_seq(boost::irange(0, 4))  //
                                                     | seq::map([](int i) {
                                                           return i * -2;
                                                       })  //
                                                     | seq::collect_vec());

    auto map_fn = [](int i) {
        return i * -2;
    };
    auto nums = as_seq(boost::irange(0, 4));

    EXPECT_EQ((std::vector<int>{0, -2, -4, -6}),
              batt::make_copy(nums)                    //
                  | seq::map(batt::make_copy(map_fn))  //
                  | seq::collect_vec());
}

TEST(SeqTest, ConcatTest)
{
    EXPECT_EQ((std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7}), as_seq(boost::irange(0, 3))                    //
                                                              | seq::chain(as_seq(boost::irange(3, 7)))  //
                                                              | seq::chain(as_seq(boost::irange(7, 8)))  //
                                                              | seq::collect_vec());

    EXPECT_EQ((std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7}), as_seq(boost::irange(0, 0))                    //
                                                              | seq::chain(as_seq(boost::irange(0, 7)))  //
                                                              | seq::chain(as_seq(boost::irange(7, 8)))  //
                                                              | seq::collect_vec());

    EXPECT_EQ((std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7}), as_seq(boost::irange(0, 0))                    //
                                                              | seq::chain(as_seq(boost::irange(0, 3)))  //
                                                              | seq::chain(as_seq(boost::irange(3, 3)))  //
                                                              | seq::chain(as_seq(boost::irange(3, 7)))  //
                                                              | seq::chain(as_seq(boost::irange(7, 8)))  //
                                                              | seq::collect_vec());

    EXPECT_EQ((std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7}), as_seq(boost::irange(0, 1))                    //
                                                              | seq::chain(as_seq(boost::irange(1, 3)))  //
                                                              | seq::chain(as_seq(boost::irange(3, 5)))  //
                                                              | seq::chain(as_seq(boost::irange(5, 8)))  //
                                                              | seq::chain(as_seq(boost::irange(8, 8)))  //
                                                              | seq::collect_vec());
}

TEST(SeqTest, FlattenTest)
{
    EXPECT_EQ((std::vector<int>{0, 1, 2,  //
                                1, 2, 3,  //
                                2, 3, 4,  //
                                3, 4, 5}  //
               ),
              as_seq(boost::irange(0, 4))  //
                  | seq::map([](int n) {
                        return as_seq(boost::irange(n, n + 3));
                    })              //
                  | seq::flatten()  //
                  | seq::collect_vec());

    EXPECT_EQ((std::vector<int>{0,           //
                                0, 1, 2, 3,  //
                                0, 1, 2,     //
                                0, 1}        //
               ),
              as_seq(std::vector<int>{0, 1, 4, 0, 0, 3, 2, 0}) | seq::map([](int n) {
                  return as_seq(boost::irange(0, n));
              })                    //
                  | seq::flatten()  //
                  | seq::collect_vec());
}

TEST(SeqTest, MergeTest)
{
    std::vector<int> nums{0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7};

    std::default_random_engine rng{0};
    std::uniform_int_distribution<int> pick_side(0, 1);

    std::set<std::pair<std::size_t, std::size_t>> sizes_tested;

    for (int i = 0; i < 1 * 1000; ++i) {
        std::vector<int> left, right;

        for (int n : nums) {
            if (pick_side(rng)) {
                left.emplace_back(n);
            } else {
                right.emplace_back(n);
            }
        }

        sizes_tested.emplace(left.size(), right.size());

        EXPECT_EQ(nums, as_seq(left)                     //
                            | seq::merge(as_seq(right))  //
                            | seq::decayed()             //
                            | seq::collect_vec());
    }

    // EXPECT_THAT(sizes_tested, ::testing::SizeIs(nums.size() + 1));
}

TEST(SeqTest, KMergeTest)
{
    std::vector<int> nums;
    for (int n = 0; n < 7; ++n) {
        for (int k = 0; k < 10; ++k) {
            nums.emplace_back(n);
        }
    }

    std::default_random_engine rng{0};
    std::uniform_int_distribution<int> pick_side(0, 9);

    std::set<std::vector<std::size_t>> sizes_tested;

    for (int i = 0; i < 1 * 1000; ++i) {
        std::vector<std::vector<int>> kseqs(10);

        for (int n : nums) {
            kseqs[pick_side(rng)].emplace_back(n);
        }

        sizes_tested.emplace(as_seq(kseqs)  //
                             | seq::map([](auto&& s) {
                                   return s.size();
                               })  //
                             | seq::collect_vec());

        EXPECT_EQ(nums, as_seq(kseqs)  //
                            | seq::map([](auto&& vec) {
                                  return as_seq(vec);
                              })              //
                            | seq::kmerge()   //
                            | seq::decayed()  //
                            | seq::collect_vec());
    }

    // EXPECT_THAT(sizes_tested, ::testing::SizeIs(::testing::Gt(1000ul)));
}

TEST(SeqTest, MapPairwiseTest)
{
    std::vector<std::string> strs{{"apple"}, {"banana"}, {"carrot"}};

    EXPECT_THAT(as_seq(strs)  //
                    | seq::map_pairwise(as_seq(boost::irange(1, 10)),
                                        [](const std::string& s, int n) {
                                            return s + std::string(n, '.');
                                        })  //
                    | seq::collect_vec(),
                ::testing::ElementsAre("apple.", "banana..", "carrot..."));
}

TEST(SeqTest, OutputTest)
{
    {
        std::ostringstream oss;

        as_seq(boost::irange(1, 5)) | seq::print_out(oss);

        EXPECT_THAT(oss.str(), ::testing::StrEq("1 2 3 4 "));
    }
    {
        std::ostringstream oss;

        as_seq(boost::irange(1, 5)) | seq::print_out(oss, " mississippi, ");

        EXPECT_THAT(oss.str(),
                    ::testing::StrEq("1 mississippi, 2 mississippi, 3 mississippi, 4 mississippi, "));
    }
    {
        std::ostringstream oss;

        (void)(as_seq(boost::irange(10, 16)) | seq::debug_out(oss));

        EXPECT_TRUE(oss.str().empty());

        as_seq(boost::irange(10, 16)) | seq::debug_out(oss) | seq::consume();

        EXPECT_THAT(oss.str(), ::testing::StrEq("10 11 12 13 14 15 "));
    }
}

TEST(SeqTest, GroupByTest)
{
    EXPECT_EQ((std::vector<std::vector<int>>{{0, 1, 2}, {3, 4, 5}, {6, 7, 8}, {9, 10, 11}}),
              force_copy(as_seq(boost::irange(12))  //
                         | seq::group_by([](int i, int j) {
                               return i / 3 == j / 3;
                           })  //
                         | seq::map([](auto&& sub_group) {
                               auto v = BATT_FORWARD(sub_group) | seq::collect_vec();
                               // std::cout << "collect_vec: ";
                               for (const auto& i : v) {
                                   // std::cout << i << ", ";
                                   (void)i;
                               }
                               // std::cout << std::endl;
                               return v;
                           }))  //
                  | seq::collect_vec());
}

template <typename Seq>
struct CountCopies {
    using Item = batt::SeqItem<Seq>;

    template <typename... Args>
    explicit CountCopies(int* count, Args&&... args) : count_{count}
                                                     , seq_(BATT_FORWARD(args)...)
    {
        *count_ = 1;
    }

    CountCopies(const CountCopies& that) : count_{that.count_}, seq_(that.seq_)
    {
        if (count_) {
            *count_ += 1;
        }
    }

    CountCopies(CountCopies&& that) noexcept : count_{that.count_}, seq_(BATT_FORWARD(that.seq_))
    {
        that.count_ = nullptr;
    }

    ~CountCopies() noexcept
    {
        if (count_) {
            *count_ -= 1;
        }
    }

    CountCopies& operator=(const CountCopies& that)
    {
        if (this != &that) {
            if (count_) {
                *count_ -= 1;
            }
            this->count_ = that.count_;
            this->seq_ = that.seq_;
            if (count_) {
                *count_ += 1;
            }
        }
        return *this;
    }

    CountCopies& operator=(CountCopies&& that) noexcept
    {
        if (this != &that) {
            if (count_) {
                *count_ -= 1;
            }
            this->count_ = that.count_;
            this->seq_ = BATT_FORWARD(that.seq_);
            that.count_ = nullptr;
        }
        return *this;
    }

    decltype(auto) peek()
    {
        return seq_.peek();
    }

    decltype(auto) next()
    {
        return seq_.next();
    }

    int* count_;
    Seq seq_;
};

template <typename Seq>
CountCopies<Seq> count_copies(int* count, Seq&& seq)
{
    return CountCopies<Seq>{count, BATT_FORWARD(seq)};
}

TEST(SeqTest, GroupBy_MoveConstruct)
{
    int copies = 0;

    auto groups = count_copies(&copies, as_seq(boost::irange(12)))  //
                  | seq::group_by([](int i, int j) {
                        return i / 3 == j / 3;
                    });

    EXPECT_EQ(copies, 1);

    auto subgroup1 = *groups.next();

    EXPECT_EQ(copies, 1);

    auto groups2 = std::move(groups);

    EXPECT_EQ(copies, 1);
    EXPECT_EQ((std::vector<int>{0, 1, 2}), std::move(subgroup1) | seq::collect_vec());

    auto subgroup2 = *groups2.next();

    EXPECT_EQ(copies, 1);

    auto groups3 = std::move(groups2);

    EXPECT_EQ(copies, 1);
    EXPECT_EQ((std::vector<int>{3, 4, 5}), std::move(subgroup2) | seq::collect_vec());

    auto subgroup3 = *groups3.next();

    EXPECT_EQ(copies, 1);

    auto groups4 = std::move(groups3);

    EXPECT_EQ(copies, 1);
    EXPECT_EQ((std::vector<int>{6, 7, 8}), std::move(subgroup3) | seq::collect_vec());

    auto subgroup4 = *groups4.next();

    EXPECT_EQ(copies, 1);
    {
        auto groups5 = std::move(groups4);

        EXPECT_EQ(copies, 1);
        EXPECT_EQ((std::vector<int>{9, 10, 11}), std::move(subgroup4) | seq::collect_vec());
        EXPECT_FALSE(groups5.next());
        EXPECT_EQ(copies, 1);
    }
    EXPECT_EQ(copies, 0);
}

TEST(SeqTest, GroupBy_MoveAssign)
{
    auto groups_a = as_seq(boost::irange(12))  //
                    | seq::group_by([](int i, int j) {
                          return i / 3 == j / 3;
                      });

    auto subgroup1 = *groups_a.next();
    auto groups_b = std::move(groups_a);

    EXPECT_EQ((std::vector<int>{0, 1, 2}), std::move(subgroup1) | seq::collect_vec());

    auto subgroup2 = *groups_b.next();
    EXPECT_EQ(&(groups_a = std::move(groups_b)), &groups_a);

    EXPECT_EQ((std::vector<int>{3, 4, 5}), std::move(subgroup2) | seq::collect_vec());

    auto subgroup3 = *groups_a.next();
    EXPECT_EQ(&(groups_b = std::move(groups_a)), &groups_b);

    EXPECT_EQ((std::vector<int>{6, 7, 8}), std::move(subgroup3) | seq::collect_vec());

    auto subgroup4 = *groups_b.next();
    EXPECT_EQ(&(groups_a = std::move(groups_b)), &groups_a);

    EXPECT_EQ((std::vector<int>{9, 10, 11}), std::move(subgroup4) | seq::collect_vec());
    EXPECT_FALSE(groups_a.next());
}

TEST(SeqTest, GroupBy_CopyAssign)
{
    int copies = 0;
    {
        auto groups_a = count_copies(&copies, as_seq(boost::irange(12)))  //
                        | seq::group_by([](int i, int j) {
                              return i / 3 == j / 3;
                          });

        EXPECT_EQ(copies, 1);

        auto subgroup1 = *groups_a.next();

        EXPECT_EQ(copies, 1);
        {
            auto groups_b = groups_a;

            EXPECT_EQ(copies, 2);
            EXPECT_EQ((std::vector<int>{0, 1, 2}), std::move(subgroup1) | seq::collect_vec());

            auto subgroup2 = *groups_b.next();

            EXPECT_EQ(copies, 2);
            EXPECT_EQ(&(groups_a = groups_b), &groups_a);
            EXPECT_EQ(copies, 2);
            EXPECT_EQ((std::vector<int>{3, 4, 5}), std::move(subgroup2) | seq::collect_vec());

            auto subgroup3 = *groups_a.next();

            EXPECT_EQ(copies, 2);
            EXPECT_EQ(&(groups_b = groups_a), &groups_b);
            EXPECT_EQ(copies, 2);
            EXPECT_EQ((std::vector<int>{6, 7, 8}), std::move(subgroup3) | seq::collect_vec());

            auto subgroup4 = *groups_b.next();

            EXPECT_EQ(copies, 2);
            EXPECT_EQ(&(groups_a = groups_b), &groups_a);
            EXPECT_EQ(copies, 2);

            EXPECT_EQ((std::vector<int>{9, 10, 11}), std::move(subgroup4) | seq::collect_vec());
            EXPECT_FALSE(groups_a.next());
            EXPECT_EQ(copies, 2);

        }  // ~groups_b

        EXPECT_EQ(copies, 1);

    }  // ~groups_a

    EXPECT_EQ(copies, 0);
}

TEST(SeqTest, GroupBy_CopySubGroup)
{
    int copies = 0;
    {
        auto groups = count_copies(&copies, as_seq(boost::irange(12))) | seq::group_by([](int i, int j) {
                          return i / 3 == j / 3;
                      });

        EXPECT_EQ((std::vector<int>{0, 1, 2}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ(copies, 1);

        auto subgroup1 = *groups.next();

        EXPECT_EQ((std::vector<int>{3, 4, 5}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ(copies, 1);

        auto subgroup_copy1 = subgroup1;

        EXPECT_EQ((std::vector<int>{3, 4, 5}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ(copies, 2);
        EXPECT_EQ(*subgroup1.next(), 0);

        auto subgroup_copy2 = subgroup1;

        EXPECT_EQ((std::vector<int>{3, 4, 5}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ(copies, 3);
        EXPECT_EQ(*subgroup1.next(), 1);
        EXPECT_EQ(*subgroup1.peek(), 2);

        auto subgroup2 = *groups.next();

        EXPECT_EQ(*subgroup1.peek(), 2);
        EXPECT_EQ(copies, 4);

        auto subgroup_copy3 = subgroup1;

        EXPECT_EQ((std::vector<int>{6, 7, 8}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ(copies, 5);
        EXPECT_EQ(*subgroup1.next(), 2);

        // `copies` drops because subgroup1 is consumed.
        //
        EXPECT_EQ(copies, 4);

        auto subgroup_copy4 = subgroup1;

        // Copying a consumed subgroup does not create a copy.
        //
        EXPECT_EQ(copies, 4);

        EXPECT_EQ((std::vector<int>{6, 7, 8}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ(copies, 4);
        EXPECT_FALSE(subgroup1.next());
        EXPECT_EQ(copies, 4);

        EXPECT_EQ((std::vector<int>{6, 7, 8}), *groups.peek() | seq::collect_vec());

        auto subgroup3 = *groups.next();

        EXPECT_EQ(copies, 5);
        EXPECT_EQ((std::vector<int>{0, 1, 2}), std::move(subgroup_copy1) | seq::collect_vec());

        // `copies` drops because subgroup_copy1 is consumed
        //
        EXPECT_EQ(copies, 4);

        EXPECT_EQ((std::vector<int>{9, 10, 11}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ((std::vector<int>{1, 2}), std::move(subgroup_copy2) | seq::collect_vec());

        // `copies` drops because subgroup_copy2 is consumed
        //
        EXPECT_EQ(copies, 3);

        EXPECT_EQ((std::vector<int>{9, 10, 11}), *groups.peek() | seq::collect_vec());
        EXPECT_EQ((std::vector<int>{2}), std::move(subgroup_copy3) | seq::collect_vec());

        // `copies` drops because subgroup_copy2 is consumed
        //
        EXPECT_EQ(copies, 2);

        auto subgroup4 = *groups.next();

        // subgroup4 is now current; copies of the seq are held by groups,
        // subgroup2, and subgroup3.
        //
        EXPECT_EQ(copies, 3);

        // Nothing comes after subgroup4, so peek should now return None.
        //
        EXPECT_FALSE(groups.peek());

        EXPECT_EQ((std::vector<int>{}), std::move(subgroup_copy4) | seq::collect_vec());
        EXPECT_EQ(copies, 3);

        EXPECT_EQ((std::vector<int>{6, 7, 8}), std::move(subgroup3) | seq::collect_vec());
        EXPECT_EQ(copies, 2);

        EXPECT_EQ((std::vector<int>{3, 4, 5}), std::move(subgroup2) | seq::collect_vec());
        EXPECT_EQ(copies, 1);

        EXPECT_EQ((std::vector<int>{9, 10, 11}), std::move(subgroup4) | seq::collect_vec());

        // `copies` doesn't drop, because subgroup4 wasn't detached when we consumed
        // it.
        //
        EXPECT_EQ(copies, 1);

    }  // ~groups

    EXPECT_EQ(copies, 0);
}

TEST(SeqTest, FirstTest)
{
    EXPECT_EQ(make_optional(0), as_seq(boost::irange(10)) | seq::first());
    EXPECT_EQ(make_optional(0), as_seq(boost::irange(1)) | seq::first());
    EXPECT_EQ(None, as_seq(boost::irange(0)) | seq::first());
}

TEST(SeqTest, LastTest)
{
    EXPECT_EQ(make_optional(9), as_seq(boost::irange(10)) | seq::last());
    EXPECT_EQ(make_optional(0), as_seq(boost::irange(1)) | seq::last());
    EXPECT_EQ(None, as_seq(boost::irange(0)) | seq::last());
}

TEST(SeqTest, ReduceTest)
{
    EXPECT_EQ(1 + 2 + 3 + 4 + 5, as_seq(boost::irange(6)) | seq::reduce(0, [](int a, int b) {
                                     return a + b;
                                 }));

    EXPECT_EQ(10 + 1 + 2 + 3 + 4 + 5, as_seq(boost::irange(6)) | seq::reduce(10, [](int a, int b) {
                                          return a + b;
                                      }));

    EXPECT_EQ(99, as_seq(boost::irange(0)) | seq::reduce(99, [](int a, int b) {
                      return a + b;
                  }));
}

TEST(SeqTest, ChainTest)
{
    // Empty -> NonEmpty
    //
    EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5}),
              as_seq(boost::irange(1, 1)) | seq::chain(as_seq(boost::irange(1, 6))) | seq::collect_vec());

    // Empty -> Empty
    //
    EXPECT_EQ((std::vector<int>{}),
              as_seq(boost::irange(1, 1)) | seq::chain(as_seq(boost::irange(5, 5))) | seq::collect_vec());

    // NonEmpty -> Empty
    //
    EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5}),
              as_seq(boost::irange(1, 6)) | seq::chain(as_seq(boost::irange(6, 6))) | seq::collect_vec());

    // NonEmpty -> NonEmpty
    //
    EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5}),
              as_seq(boost::irange(1, 3)) | seq::chain(as_seq(boost::irange(3, 6))) | seq::collect_vec());

    // Many nested chains.
    //
    EXPECT_EQ((std::vector<int>{1, 2, 3, 4, 5}),
              as_seq(boost::irange(1, 3)) | seq::chain(as_seq(boost::irange(3, 4))) |
                  seq::chain(as_seq(boost::irange(4, 5))) | seq::chain(as_seq(boost::irange(5, 6))) |
                  seq::collect_vec());
}

TEST(SeqTest, SpliceTest)
{
    // NonEmpty -> NonEmpty -> NonEmpty
    //
    EXPECT_EQ((std::vector<int>{2, 4, 1, 3, 5, 7, 9, 6, 8, 10}),
              as_seq(boost::irange(2, 12, 2)) | seq::splice(2, as_seq(boost::irange(1, 10, 2))) |
                  seq::collect_vec());

    // NonEmpty -> NonEmpty -> Empty
    //
    EXPECT_EQ((std::vector<int>{2, 4, 6, 8, 10, 1, 3, 5, 7, 9}),
              as_seq(boost::irange(2, 12, 2)) | seq::splice(5, as_seq(boost::irange(1, 10, 2))) |
                  seq::collect_vec());

    // NonEmpty -> Empty -> NonEmpty
    //
    EXPECT_EQ((std::vector<int>{2, 4, 6, 8, 10}), as_seq(boost::irange(2, 12, 2)) |
                                                      seq::splice(2, as_seq(boost::irange(10, 10, 2))) |
                                                      seq::collect_vec());

    // NonEmpty -> Empty -> Empty
    //
    EXPECT_EQ((std::vector<int>{2, 4, 6, 8, 10}), as_seq(boost::irange(2, 12, 2)) |
                                                      seq::splice(5, as_seq(boost::irange(10, 10, 2))) |
                                                      seq::collect_vec());

    // Empty -> NonEmpty -> NonEmpty
    //
    EXPECT_EQ((std::vector<int>{1, 3, 5, 7, 9, 2, 4, 6, 8, 10}),
              as_seq(boost::irange(2, 12, 2)) | seq::splice(0, as_seq(boost::irange(1, 10, 2))) |
                  seq::collect_vec());

    // Empty -> NonEmpty -> Empty
    //
    EXPECT_EQ((std::vector<int>{1, 3, 5, 7, 9}), as_seq(boost::irange(12, 12, 2)) |
                                                     seq::splice(0, as_seq(boost::irange(1, 10, 2))) |
                                                     seq::collect_vec());

    EXPECT_EQ((std::vector<int>{1, 3, 5, 7, 9}), as_seq(boost::irange(12, 12, 2)) |
                                                     seq::splice(5000, as_seq(boost::irange(1, 10, 2))) |
                                                     seq::collect_vec());

    // Empty -> Empty -> NonEmpty
    //
    EXPECT_EQ((std::vector<int>{2, 4, 6, 8, 10}), as_seq(boost::irange(2, 12, 2)) |
                                                      seq::splice(0, as_seq(boost::irange(10, 10, 2))) |
                                                      seq::collect_vec());

    // Empty -> Empty -> Empty
    //
    EXPECT_EQ((std::vector<int>{}), as_seq(boost::irange(12, 12, 2)) |
                                        seq::splice(0, as_seq(boost::irange(10, 10, 2))) |
                                        seq::collect_vec());

    EXPECT_EQ((std::vector<int>{}), as_seq(boost::irange(12, 12, 2)) |
                                        seq::splice(5000, as_seq(boost::irange(10, 10, 2))) |
                                        seq::collect_vec());
}

TEST(SeqTest, RunningTotalTest)
{
    EXPECT_EQ((std::vector<int>{0, 1, 3, 6, 10, 15, 21, 28}),
              as_seq(boost::irange(0, 8)) | seq::running_total() | seq::collect_vec());
}

}  // namespace


// Range control in wireless communication with reinforcement learning

#include <iostream>
#include <boost/program_options.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <chrono>

#include "rlnetwork.h"
#include "common.h"

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

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

    string svg_name = "test";
    bool bdraw = false;
    bool b_input = false;
    string i_file;
    double node_desnity = def_lambda_SBS;
    int nb_episode = 100;
    uint32_t Q_id = 1;
    bool bQ = false;
    bool bsync = true;

    po::options_description desc("Allowed options");
    desc.add_options()
        ("help", "Use the following flags to input the required parameters.")
        ("if", po::value<string>(), "input file")
        ("svg", po::value<string>(), "Topology SVG output file name")
        ("episode", po::value<double>(), "Number of training episodes")
        ("sync", po::value<bool>(), "Synchronous (1) or Asynchronous (0) learning")
        ("Q", po::value<uint32_t>(), "node id to print Q-function in softmax")
        ;

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    if(vm.count("help"))
    {
        cout << desc << endl;
        return 1;
    }

    if(vm.count("svg"))
    {
        svg_name = vm["svg"].as<string>()+".svg";
        bdraw = true;
    }

    if(vm.count("if"))
    {
        b_input = true;
        i_file = vm["if"].as<string>();
    }

    if(vm.count("episode"))
    {
        nb_episode = vm["episode"].as<double>();
    }

    if(vm.count("sync"))
    {
        bsync = vm["sync"].as<bool>();
        std::cout << "sync learning = " <<bsync << std::endl;
    }

    if(vm.count("Q"))
    {
        Q_id = vm["Q"].as<uint32_t>();
        bQ = true;
    }

    auto start = chrono::high_resolution_clock::now();

    RLNetwork* global_ENV = new RLNetwork();
    m_nextId = 0;
    global_ENV->create_svg(svg_name);
    double r = sqrt(def_Area/M_PI);
    global_ENV->set_center(r,r,r);

    if(b_input)
        global_ENV->load_nodes(i_file, false, 0, 0.);
    else
        global_ENV->generate_nodes(node_desnity, false, 0, 0.);

    global_ENV->train(bsync , nb_episode);

    while(!global_ENV->isNetworkReady())
    {
        std::this_thread::sleep_for(std::chrono::nanoseconds(10));
    }
    global_ENV->k_connect(10);

    global_ENV->draw_neighbors(bdraw);

    if(bQ) global_ENV->print_Q_function(Q_id, O_POLICY::softmax);

    auto finish = chrono::high_resolution_clock::now();
    chrono::duration<double> elapsed = finish-start;

    cout << "Duration time = " << elapsed.count() << " seconds for " << global_ENV->node_count() << " nodes!" << endl;

    //TODO not sure about this function!
    global_ENV->reset_pointers();
    return 0;
}