//---------------------------------------------------------------------------
//
//    FCST: Fuel Cell Simulation Toolbox
//
//    Copyright (C) 2009-13 by Energy Systems Design Laboratory, University of Alberta
//
//    This software is distributed under the MIT License.
//    For more information, see the README file in /doc/LICENSE
//
//    - Class: newton_w_line_search.cc
//    - Description: Variant of the Newton-Raphson solver
//    - Developers: Marc Secanell
//
//---------------------------------------------------------------------------

#include <solvers/newton_w_line_search.h>
#include <deal.II/base/data_out_base.h>
#include <deal.II/lac/block_vector.h>

#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <sstream>
#include <cmath>

using namespace FuelCell::ApplicationCore;

//---------------------------------------------------------------------------
const Event NewtonLineSearch::bad_derivative = Event::assign("Newton");

//---------------------------------------------------------------------------
NewtonLineSearch::NewtonLineSearch(ApplicationBase& app)
    : newtonBase(app)
{
  FcstUtilities::log << "->NewtonLineSearch";
}

//---------------------------------------------------------------------------
void
NewtonLineSearch::declare_parameters(ParameterHandler& param)
{
    param.enter_subsection("Newton");
    {
        param.declare_entry("Line search",
                            "false",
                            Patterns::Bool(),
                            "A line search is performed at each step.");
        param.declare_entry("Initial Overrelaxation",
                            "1.",
                            Patterns::Double(),
                            "This value multiplies the step size in the first iteration, e.g., if the Newton update is 0.25, "
                            "the applied update would be the value in Initial Overrelaxation times 0.25");
        param.declare_entry("Number of iterations with overrelaxation",
                            "1",
                            Patterns::Integer(),
                            "Step during which overrelaxation is applied. At each step the Netwon step is multiplied by"
                            "the step times Initial overrelaxation, e.g., step one is 0.1, then step to is 0.2 and so on until 1 (no overrelaxation).");
        param.declare_entry("Solution variable not allowed to be negative",
                            "0",
                            Patterns::Integer(),
                            "This enforces a given solution variable to be positive. If it goes negative, step size is reduced.");
    }
    param.leave_subsection();

    newtonBase::declare_parameters(param);
}

//---------------------------------------------------------------------------
void
NewtonLineSearch::_initialize (ParameterHandler& param)
{
    param.enter_subsection("Newton");
    line_search = param.get_bool("Line search");
    overrelax = param.get_double("Initial Overrelaxation");
    overrelax_steps = param.get_integer("Number of iterations with overrelaxation");
    block_to_fix = param.get_integer("Solution variable not allowed to be negative");
    param.leave_subsection ();
}

//---------------------------------------------------------------------------
void
NewtonLineSearch::initialize (ParameterHandler& param)
{
    newtonBase::initialize(param);
    _initialize(param);
}

//---------------------------------------------------------------------------
void
NewtonLineSearch::solve (FuelCell::ApplicationCore::FEVector& u, const FuelCell::ApplicationCore::FEVectors& in_vectors)
{
    this->step = 0;

    if (debug>2)
        FcstUtilities::log << "u: " << u.l2_norm() << std::endl;

    FEVector Du;
    FEVector res;

    res.reinit(u);
    Du.reinit(u);
    FEVectors src1;
    FEVectors src2;
    src1.add_vector(u, "Newton iterate");
    src1.merge(in_vectors);
    src2.add_vector(res, "Newton residual");
    src2.merge(src1);

    // fill res with (f(u), v)
    double residual = app->residual(res, src1);
    FcstUtilities::log << "Overall residual at iteration "<<this->step<<" = " << residual << std::endl;
    double old_residual = residual;

    // Output the solution at the Newton iteration if residual debug is on
    this->debug_output(u, Du, res);

    //
    while (control.check(this->step++, residual) == SolverControl::iterate)
    {
        // assemble (Df(u), v)
        if (residual/old_residual >= assemble_threshold)
            app->notify (bad_derivative);

        Du.reinit(u);

        try
        {
            app->solve (Du, src2);
        }
        catch (SolverControl::NoConvergence& e)
        {
            FcstUtilities::log << "Inner iteration failed after "
            << e.last_step << " steps with residual "
            << e.last_residual << std::endl;
        }

        ////////////////////////////////////////////////////////////
        //-- Step size control
        ////////////////////////////////////////////////////////////
        if (line_search)
        {
            FcstUtilities::log << "Line Search" << std::endl;
            // -- Use a line search to increase robustness
            double min_residual = residual;
            double opt_alpha = -0.25;
            unsigned int num_points = 1;
            const double* ref = get_data()->scalar("Refinement");
            if (this->step < 10 &&  *ref < 1)
                num_points = 20;
            else if ((this->step >=10 && this->step < 20) &&  *ref < 1)
                num_points = 10;
            else
                num_points = 5;

            // ------------ GEOMETRIC LINE SEARCH

            // Scale: I will look at 2^0, 2^-1, 2^-2, ..., 2^{-num_points}
            double scale = 2;
            for (unsigned int alpha = 1; alpha < num_points + 1; ++alpha)
            {
                // scale u
                u.add(-pow(scale,-double(alpha-1)), Du);
                residual = app->residual(res, src1);

                if ((residual < min_residual) && !(std::isnan(residual)))
                {
                    opt_alpha = -pow(scale,-double(alpha-1));
                    min_residual = residual;
                }
                //  FcstUtilities::log<<"Residual for "<<pow(scale,-double(alpha-1))<<" is: "<<residual<<std::endl;
                //
                //  scale back u
                u.add(pow(scale,-double(alpha-1)), Du);
            }
            /*
            // ------ LINEAR LINE SEARCH (Not tested) -------------
            for (unsigned int alpha = 1; alpha < num_points; ++alpha)
            {
                u.add(-1.0/num_points, *Du);
                residual = app->residual(*res, u);
                if (residual < min_residual && !std::isnan(residual))
                {
                    opt_alpha = alpha*(1.0/num_points);
                    min_residual = residual;
                }
            }
            // unscale u
            u.add(1.0, *Du);
            */
            //FcstUtilities::log<<"Norm of change in solution: "<<Du->l2_norm()<<std::endl;
            //FcstUtilities::log<<"Line search with "<<num_points<<" number of points. Optimal alpha: "<<opt_alpha<<std::endl;
            FcstUtilities::log << "Step size = " << -opt_alpha << std::endl;
            u.add(opt_alpha, Du);
            residual = app->residual(res, src1);

        }
        else // No line search
        {
            ////////////////////////////////////////////////////////////
            // a) First, use overrelaxation during the initial
            // iterations because Newton's algorithm tends to overshoot:
            double alpha = 1.0;
            const double* ref = get_data()->scalar("Refinement");

            if (this->step < overrelax_steps &&  *ref < 1)
            {
                alpha = overrelax*this->step;
                alpha = (alpha <= 1) ? alpha : 1;
                FcstUtilities::log << "Step size = " << alpha << std::endl;
            }

            // Update solution
            u.add(-alpha,Du);

            // Update residual
            old_residual = residual;
            residual = app->residual(res, src1);

            // b) Next, make sure that the chosen step reduces the residual
            // overwise reduce the residual further:
            unsigned int step_size = 0;

            //-- Make sure none of the values are negative:
            bool negative_values = find_negative_values(u);

            //--
            while (residual >= old_residual || std::isnan(residual) || negative_values)
            {
                ++step_size;

                u.add(alpha*pow(2.0,-double(step_size)), Du);
                residual = app->residual(res, src1);

                //-- Make sure none of the values are negative:
                negative_values = find_negative_values(u);

                // -- Some output:
                if (control.log_history())
                    FcstUtilities::log << "Step size: 2^{-" << step_size << "}" << std::endl;
                if (step_size > 15)
                {
                    if (control.log_history())
                        FcstUtilities::log << "Step size too small!" << std::endl;
                    break;
                }
            }
        }

        // Output the global residual and the equation specific residual:
        for (unsigned int i = 0; i<res.n_blocks(); i++)
            FcstUtilities::log << "Residual for equation "<<i<<" is: "<<res.block(i).l2_norm() << std::endl;
        FcstUtilities::log << "Overall residual at iteration "<<this->step<<" = " << residual << std::endl;

        // Debug output options:
        this->debug_output(u, Du, res);

    }

    // in case of failure: throw exception
    if (control.last_check() != SolverControl::success)
        AssertThrow (false, ExcMessage ("No convergence in Newton solver"));

}

//---------------------------------------------------------------------------
bool
NewtonLineSearch::find_negative_values(const FEVector& u)
{
    bool negative_values = false;
    /*
    if (block_to_fix >= -1.0e-5)
    {
        for(unsigned int i = 0; i < u.block(block_to_fix).size(); ++i)
            if( u.block(block_to_fix)(i) < -1.0e-6)
            {
                negative_values = true;
                FcstUtilities::log << "Negative value... u("<<i<<")="<<u.block(block_to_fix)(i)<<"resize..."<<std::endl;
                break;
            }
    }
    */
    return negative_values;

}


/*!
  * \author     Zakwan Zainuddin (zz260) && Gustavo Leon (gl413)
  * \file       swp_kmc_simulator.cpp
  *
  * \brief      Implementation for swp_kmc_simulator.h
  *
  Project:      sweep (gas-phase chemistry solver).
  Sourceforge:  http://sourceforge.net/projects/mopssuite
  Copyright (C) 2010 Zakwan Zainuddin 2020 Gustavo Leon

  File purpose:
    Simulator for the kMC Model.

  Licence:
    This file is part of "sweep".

    Sweep is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  Contact:
    Dr Markus Kraft
    Dept of Chemical Engineering
    University of Cambridge
    New Museums Site
    Pembroke Street
    Cambridge
    CB2 3RA
    UK

    Email:       mk306@cam.ac.uk
    Website:     http://como.cheng.cam.ac.uk
*/

#include "swp_cell.h"

#include "gpc_mech.h"
#include "gpc_mech_io.h"

#include "csv_io.h"

#include <string>
#include <iostream>
#include <sstream>
#include <time.h>
#include <math.h>
#include <cstdlib>
#include <boost/random/exponential_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/filesystem.hpp>

#include "string_functions.h"
#include "swp_kmc_simulator.h"

using namespace std;
using namespace Sweep;
using namespace Sweep::KMC_ARS;
using namespace Strings;

// Default names for CSV outputs if not specified
std::string default_timer_csv = "KMC_Model/PAH_loop_timer.csv";
std::string default_rxncount_csv = "KMC_Model/PAH_reaction_count.csv";
std::string default_pahlist_csv = "KMC_Model/PAH_CH_site_list.csv";
std::string default_pahlist_after_csv = "KMC_Model/PAH_CH_site_list_after.csv";
std::string default_rates_csv = "KMC_Model/PAH_jump_process_rates.csv";
std::string default_testrates_csv = "KMC_Model/PAH_jump_process_testrates.csv";

// Vector of all site types
static std::vector<kmcSiteType> allSiteType = vectSiteType();

//! Default Constructor
KMCSimulator::KMCSimulator():
     m_gasprof(), m_mech(), m_gas(), m_simPAH(), m_t(), m_fromfile(false), m_kmcmech(),m_simPAHp()
{
}

//! Constructor from chemkin and gasphase files
KMCSimulator::KMCSimulator(const std::string gasphase, const std::string chemfile, const std::string thermfile)
{
    m_t=0;
    m_gasprof = new Sweep::GasProfile();
    LoadGasProfiles(gasphase, chemfile, thermfile);
    m_fromfile = true;
}
//! Constructor from a GasProfile object
KMCSimulator::KMCSimulator(Sweep::GasProfile& gprofile):
	m_gasprof(), m_mech(), m_gas(), m_simPAH(), m_t(0.0), m_fromfile(false), m_kmcmech(), m_simPAHp()
{
    std::cout << this << endl;
    m_gasprof = &gprofile;
    m_gas = new KMCGasPoint(gprofile, *gprofile[0].Gas.Species());
    m_mech = NULL;
}

//! Copy Constructor
KMCSimulator::KMCSimulator(KMCSimulator& s):
		m_gasprof(), m_mech(), m_gas(), m_simPAH(), m_t(s.m_t), m_fromfile(false),
		m_kmcmech(s.m_kmcmech),m_simPAHp()

{
    m_gasprof = s.m_gasprof;
    m_gas = new KMCGasPoint(*s.m_gas);
    //m_simPAH = new PAHStructure(*(s.m_simPAH));
    m_simPAHp = PAHProcess(*m_simPAH);
}

//! Default Destructor
KMCSimulator::~KMCSimulator() {
    //delete m_simPAH;
    if(m_fromfile) {
        delete m_gasprof;
        delete m_mech;
    }
    delete m_gas;
}

//! Set PAH to be simulated
void KMCSimulator::targetPAH(PAHStructure& pah) {
    m_simPAH = &pah;
    m_simPAHp = PAHProcess(*m_simPAH);
	setDebugPAH(save_pah_detail);
}

/*!
 * @param[in,out]    pah             PAH structure KMC-ARS jump process will be performed on.
 * @param[in]        tsart           The latest time the PAH was updated.
 * @param[in]        dt              The time over which the PAH is to be updated.
 * @param[in]        waitingSteps    Adjusts the size of dt. The larger it is the smaller dt is. Results were found to be insensitive to waitingSteps therefore it was hardcoded as 1 (tests by Dongping, dc516@cam.ac.uk).
 * @param[in]        rng             Random number generator.
 * @param[in]        r_factor        Model parameter: Growth factor, a multiplier that is applied to the growth rate of PAHs within primary particles when the number of PAHs exceeds a critical number of PAHs.
 * @param[in]        PAH_ID          "Unique" identification number attached to this PAH.
 */
double KMCSimulator::updatePAH(PAHStructure* pah,
                            const double tstart,
                            const double dt,
                            const int waitingSteps,
							const int maxloops,
                            rng_type &rng,
                            double r_factor,
                            int PAH_ID,
							bool calcrates,
							double ratefactor) {
	// wjm34: remove call to initReaction count to save time in updating.
	m_t = tstart;
	double t_max = m_t + dt;
    targetPAH(*pah);
	if (m_rxn_count.size() == 0) {
		//initCSVIO();
		initReactionCount();
		readTrackedPAH();
	};

    // Checks if information will be saved for this PAH.
    auto finder = std::find(std::begin(m_tracked_pahs), std::end(m_tracked_pahs), PAH_ID);
    bool tracked_csv = false;
    auto fix_finder = std::find(std::begin(m_tracked_pahs_fixed), std::end(m_tracked_pahs_fixed), PAH_ID);
    if (fix_finder != m_tracked_pahs_fixed.end()) {
        opentrackedPAHCSV(PAH_ID); //Opens the csv file for tracked PAH
        tracked_csv = true;
    }

    /*if(m_simPAHp.checkCoordinates())
        cout<<"Coordinates of structure OK. Commencing updatePAH..\n";
    else {
        cout<<"Coordinates of structure did not pass test. Aborting..\n";
        std::ostringstream msg;
        msg << "ERROR: Trying updatePAH on a structure which did not pass PAHProcess::checkCoordinates."
            << " (Sweep::KMC_ARS::KMCSimulator::updatePAH)";
        throw std::runtime_error(msg.str());
        assert(false);
    }
    if(m_simPAHp.checkSiteContinuity())
        cout<<"Site continuity of structure OK. Commencing updatePAH..\n";
    else {
        cout<<"Structure has uncontinuous site. Aborting..\n";
        std::ostringstream msg;
        msg << "ERROR: Trying updatePAH on a structure which did not pass PAHProcess::checkSiteContinuity."
            << " (Sweep::KMC_ARS::KMCSimulator::updatePAH)";
        throw std::runtime_error(msg.str());
        assert(false);
    }*/
    double t_next = m_t;
    double t_step_max = dt/waitingSteps;
    //double oldtnext;
    int loopcount=0;
	bool proceed = true;
	calcrates = true;
    //Local variable to control the number of migration steps between non-migration steps.
    int migr_steps = 0;

    //double time_migration = 0.0;
    //clock_t timerStart, timerEnd;

    while (m_t < t_max && proceed) {
        //this->m_simPAHp.printStruct();// print out structure of this pah on the screen
        //m_simGas.interpolateProfiles(m_t, true, r_factor);
        loopcount++;

        // Calculate rates of each jump process
		if (calcrates){
			//m_gas->Interpolate(m_t, r_factor);
			//m_kmcmech.calculateRates(*m_gas, m_simPAHp, m_t);
			//rates = m_kmcmech.Rates();
			//writeRatesCSV(m_t, rates);
			//writeCHSiteCountCSV();
			//writetimestep(m_t);
			//writeRxnCountCSV();
		}

        // Calculate time step, update time
		//Interpolate gas phase species and temperature.

		m_gas->Interpolate(m_t, r_factor);

		//Calculate jump process rates
		m_kmcmech.calculateRates(*m_gas, m_simPAHp, m_t);
        typedef boost::exponential_distribution<double> exponential_distrib;

		//Generate exponentially distributed waiting time
        exponential_distrib waitingTimeDistrib(m_kmcmech.TotalRate());
        boost::variate_generator<rng_type &, exponential_distrib> waitingTimeGenerator(rng, waitingTimeDistrib);
        double t_step = waitingTimeGenerator();
        t_next = m_t+t_step;

        //Artificially fix that a PAH with 4 or less sites has rate 0
		size_t site_size = m_simPAHp.SiteListSize();
		if ( (int)site_size <=6 && std::get<0>(m_simPAHp.getRingsCount()) > 1) {
            std::cout << "PAH " << PAH_ID << " reached to " << site_size << " sites. Freezing and saving structure." << std::endl;
            addTrackedPAH(PAH_ID);
            //m_simPAHp.printSites();
            std::string xyzname = ("KMC_DEBUG/");
            xyzname.append(std::to_string(PAH_ID));
            xyzname.append("/");
            xyzname.append(std::to_string(m_t*1000.0));
            xyzname.append("_frozen");
            savePAH(PAH_ID, xyzname);
            t_next = t_max;
        }

		//If time + waiting time < time at the next grid element then perform process.
        if(t_next < t_max && t_step < t_step_max) {

            // Choose jump according to rates
            ChosenProcess jp_perf = m_kmcmech.chooseReaction(rng);

			/*if (save_pah_detail){
				//Add PAH to tracked list on the fly. These are the conditions in which the user wants to save files. They need to be adjusted manually.
				int R5R7 = (m_simPAHp.getR5EmbeddedCount() + m_simPAHp.getR7EmbeddedCount());
				if (R5R7 >= 2) addTrackedPAH(PAH_ID);
				//if (R5R7 >= 1 || std::get<0>(m_simPAHp.getRingsCount()) >= 7) addTrackedPAH(PAH_ID);
				else if (jp_perf.first->getID() == 23 || jp_perf.first->getID() == 35 || jp_perf.first->getID() == 36 || jp_perf.first->getID() == 38
						|| jp_perf.first->getID() == 41 || (jp_perf.first->getID() >= 44 && jp_perf.first->getID() < 54) || m_simPAHp.numberOfMethyl() >= 3) addTrackedPAH(PAH_ID);
            }*/
            //Save information for a single PAH
            if (finder != m_tracked_pahs.end() && !m_migrate){
                std::string xyzname = ("KMC_DEBUG/");
                xyzname.append(std::to_string(PAH_ID));
                xyzname.append("/");
                xyzname.append(std::to_string(m_t*1000000.0));
                xyzname.append("_A");
                savePAH(PAH_ID, xyzname);
                cout << "PAH ID = " << PAH_ID << ", Jump process -> " << jp_perf.first->getName()<< ", Time = " << m_t<<"\n";
                //m_simPAHp.printSites();
                //printRates(m_t, m_kmcmech.Rates());
                if (tracked_csv) writetrackedPAHCSV();
            }

            m_rxn_count[jp_perf.second]++;
            //Commented out and moved to the end of the simulation loop.
			/*writeRxnCountCSV();
			writeCHSiteCountCSV(PAH_ID);
			//writeTimerCSV();
			rates = m_kmcmech.Rates();
			writeRatesCSV(m_t, rates);*/

            //Saves the site list to file. Used to verify that migration by both methods gives same results
            /*std::vector<std::string> temp = m_simPAHp.SiteVectorString();
            temp.push_back(std::to_string(PAH_ID));
            std::ostringstream streamObj;
            streamObj << std::setprecision(7);
            streamObj << m_t;
            std::string streamObj_string = streamObj.str();
            temp.push_back(streamObj_string);
            m_pah_sitelist_csv.Write(temp);*/
            // Update data structure -- Perform jump process
			//printRates(m_t, m_kmcmech.Rates());
            //cout << m_t << std::endl;
            if (jp_perf.first->getID() == 24 || jp_perf.first->getID() == 34 || jp_perf.first->getID() == 66 ) {
                //timerStart = clock();
                //Migration jump processes. Set flag m_migrate to true.
                if (!m_migrate) {
                    m_simPAHp.startMigrationProcess();
                    migr_steps = 0;
                    m_migrate = true;
                }
                //savePAH(PAH_ID,"KMC_DEBUG/BEFOREJPPERFORM");
                m_simPAHp.performProcess(*jp_perf.first, rng, PAH_ID);
                migr_steps++;
                //savePAH(PAH_ID,"KMC_DEBUG/AFTERJPPERFORM");
            }
            else {
                if (m_migrate){
                    //First update the multiple migration transformation
                    //savePAH(PAH_ID,"KMC_DEBUG/BEFOREMIGRATION");
                    m_simPAHp.performMigrationProcess();
                    //savePAH(PAH_ID,"KMC_DEBUG/AFTERMIGRATION");
                    m_migrate = false;
                    //timerEnd = clock();
                    //time_migration += (timerEnd - timerStart) / double(CLOCKS_PER_SEC);
                }
                if (finder != m_tracked_pahs.end()) std::cout << "PAH ID = " << PAH_ID << ", Jump process -> " << jp_perf.first->getName()<< ", Time = " << m_t<<" Migr. steps = "<<migr_steps<<std::endl;
                migr_steps = 0;
                if (finder != m_tracked_pahs.end() && !m_migrate){
                    std::string xyzname = ("KMC_DEBUG/");
                    xyzname.append(std::to_string(PAH_ID));
                    xyzname.append("/");
                    xyzname.append(std::to_string(m_t*1000000.0));
                    xyzname.append("_B");
                    savePAH(PAH_ID, xyzname);
                    //cout << "PAH ID = " << PAH_ID << ", Jump process -> " << jp_perf.first->getName()<< ", Time = " << m_t<<"\n";
                }
                m_simPAHp.performProcess(*jp_perf.first, rng, PAH_ID);
                if (finder != m_tracked_pahs.end() && !m_migrate){
                    std::string xyzname = ("KMC_DEBUG/");
                    xyzname.append(std::to_string(PAH_ID));
                    xyzname.append("/");
                    xyzname.append(std::to_string(m_t*1000000.0));
                    xyzname.append("_C");
                    savePAH(PAH_ID, xyzname);
                }
            }
			//writeCHSiteCountCSV_after(PAH_ID);

			//Hard cut-off for PAHs. Cannot have less than one ring. Set number of carbons to 1 so that it will be invalidated
			//Set t_next to t_max so the updatePAH routine will be exited
			//if (pah->numofRings() < 4){
			//	proceed = false;
			//	//t_next = t_max;
			//}

            //oldtnext = t_next;
            //t_next = t_max;
            if (loopcount == maxloops) proceed = false; //If maxloops is set to 0, this condition will never be true
            m_t = t_next;
        }else{
            if (m_migrate){
                //savePAH(PAH_ID,"KMC_DEBUG/BEFOREMIGRATION");
                m_simPAHp.performMigrationProcess();
                //savePAH(PAH_ID,"KMC_DEBUG/AFTERMIGRATION");
                m_migrate = false;
            }
            t_next = t_max;
            m_t = t_next;
        }
    }

    //writeRxnCountCSV();
    writeCHSiteCountCSV(PAH_ID);
    //std::cout << "Wall time used for PAH_ID " << PAH_ID << " = " << time_migration << "s." << std::endl;

    if (tracked_csv) closetrackedPAHCSV();
    if (finder != m_tracked_pahs.end()){
        std::string xyzname = ("KMC_DEBUG/");
        xyzname.append(std::to_string(PAH_ID));
        xyzname.append("/");
        xyzname.append(std::to_string(m_t*1000000.0));
        xyzname.append("_D");
        savePAH(PAH_ID, xyzname);
    }
	return m_t;
}

//! Outputs rates into a csv file (assuming all site counts as 1)
void KMCSimulator::TestRates(const double tstart, const double tstop, const int intervals) {
    // set name of output file
    //setCSVratesName(filename);
    //std::cout << "Saving Rates...\n";
    rvector rates(m_kmcmech.JPList().size(), 0);
    double dt = (tstop-tstart)/intervals;
    m_simPAHp.m_rates_save = true;
    // for each interval
    for(double t=tstart+dt; t<= tstop; t+=dt) {
        // interpolate & calculate rates
        m_gas->Interpolate(t);
        m_kmcmech.calculateRates(*m_gas, m_simPAHp, t);
        rates = m_kmcmech.Rates();
        writeTestRatesCSV(t, rates);
    }
    m_simPAHp.m_rates_save = false;
    //std::cout<<"Finished calculating rates for kMC mechanism. Results are saved in "
    //    <<m_testrates_name<<"\n\n";
}

//! Obtains rates of PAH reactions with the current structure
rvector KMCSimulator::CurrentRates(PAHStructure* pah, double t) {
    m_simPAHp.setPAH(*pah);
    rvector rates(m_kmcmech.JPList().size(), 0);
    m_gas->Interpolate(t);
    m_kmcmech.calculateRates(*m_gas, m_simPAHp, t);
    rates = m_kmcmech.Rates();
    return rates;
}
//! Outputs gas concentrations into a csv file
void KMCSimulator::TestConc(const double& t_start, const double& t_stop, const int intervals, const std::string& filename) {
    CSV_IO csvio(filename, true);
    double dt = (t_stop-t_start)/intervals;
    std::vector<string> species(m_gas->m_total-2);
    rvector temp(m_gas->m_total-2); //exclude T & P
    for(size_t i=1; i<(temp.size()); i++) {
        species[i] = m_gas->SpNames()[i+1];
    }
    csvio.Write(species);
    for(double t=t_start; t<=t_stop; t+=dt) {
        temp[0] = t;
        m_gas->Interpolate(t);
        for(size_t i=1; i<(temp.size()-1); i++) {
            temp[i] = (*m_gas)[i+1];
        }
        csvio.Write(temp);

    }
}

/*void KMCSimulator::testSimulation(PAHStructure& pah, const unsigned long seed, int totalruns) {
    for(int run=1; run<=totalruns; run++) {
        clock_t timerStart = clock();
        int total_interval = 10;
        double maxtime = 0.0222029;
        double step = maxtime/total_interval;
        double tnow=0;
        // loop counts
        int count = 0;
        // Start timer
        initReactionCount();
        m_simPAHp.initialise(PYRENE);
        while(tnow<maxtime) {
            //tnow = i*step;
            tnow = updatePAH(pah, tnow, step, m_simGas, 10, count);
        }
        //updatePAH(pah, tnow, maxtime, m_simGas);
        m_simPAHp.saveDOT(std::string("DOT files/Test_update_PAH.dot"));
        // calculate time elapsed
        clock_t timerStop = clock();
        double timerElapsed = double(timerStop-timerStart)/CLOCKS_PER_SEC;
        writeTimerCSV(count, timerElapsed);
        writeRxnCountCSV(m_rxn_count);
        writeCHSiteCountCSV();
        cout<<"\nSimulator ran for run "<<run<<" looping "<<count<<" number of times...\n";
        cout<<"Ran for "<<timerElapsed<<" seconds.\n\n";
    }

    m_timer_csv.Close();
    m_rxn_csv.Close();
    m_pah_csv.Close();
}*/

//! Set csv filename for gas profiles
void KMCSimulator::setCSVinputName(const std::string& filename) {
    m_csv_in = filename;
}

//! Set output DOT file name "filename"_runs_finalloopnum.dot
void KMCSimulator::setDOToutputName(const std::string& filename) {
    m_dot_out = filename;
}
//! Set output CSV file name to keep track of timer counts
void KMCSimulator::setCSVtimerName(const std::string& filename) {
    m_timer_name = filename;
}
//! Set output CSV file name to keep track of reaction counts
void KMCSimulator::setCSVreactioncountName(const std::string& filename) {
    m_rxncount_name = filename;
}
//! Set output CSV file name to keep track of CH and site counts
void KMCSimulator::setCSVpahlistName(const std::string &filename) {
    m_pahlist_name = filename;
}
//! Set output CSV file name to keep track of CH and site counts after jump process
void KMCSimulator::setCSVpahlist_afterName(const std::string &filename) {
    m_pahlist_after_name = filename;
}

//! Set output CSV file name to keep track of time step//##
void KMCSimulator::setCSVtimestep(const std::string &filename) {
    m_timestep_name = filename;
}
//! Set output CSV file name to keep track of jump process rates
void KMCSimulator::setCSVratesName(const std::string &filename) {
    m_rates_name = filename;
}
//! Set output CSV file name to keep track of jump process rates assuming 1 of each site
void KMCSimulator::setCSVtestratesName(const std::string &filename) {
    m_testrates_name = filename;
}
//! Writes data for timeCount.csv
void KMCSimulator::writeTimerCSV(const int& loop, const double& elapsedTime) {
    vector<double> temp;
    // writes: | Total Loop | Total time elapsed |
    temp.push_back((double)loop);
    temp.push_back(elapsedTime);
    m_timer_csv.Write(temp);
}
void KMCSimulator::writetimestep(const std::vector<double>& timestep){
    m_timestep_csv.Write(timestep);
}
//! Writes data for reaction_count.csv
void KMCSimulator::writeRxnCountCSV() {
    // change int vector to float
    std::vector<std::string> temp;

    for(size_t i=0; i<m_rxn_count.size(); i++)
        temp.push_back(Strings::cstr(m_rxn_count[i]));
    m_rxn_csv.Write(temp);
    m_rxn_csv.flush_file();
}
//! Writes data for CH_site_list.csv
void KMCSimulator::writeCHSiteCountCSV(int ID) {
    std::vector<int> temp;
	// write PAH_ID number
	temp.push_back(ID);
    // get CH count
    intpair CH = m_simPAHp.getCHCount();
    temp.push_back(CH.first);
    temp.push_back(CH.second);
    // get counts for all site types
    for(int i=0; i<(int)allSiteType.size(); i++) {
        int scount = m_simPAHp.getSiteCount(allSiteType[i]);
        temp.push_back(scount);
    }
	std::tuple <int, int, int> rings = m_simPAHp.getRingsCount();
	temp.push_back(std::get<0>(rings));
	temp.push_back((std::get<1>(rings) - m_simPAHp.getR5EmbeddedCount()));
	temp.push_back(m_simPAHp.getR5EmbeddedCount());
	temp.push_back((std::get<2>(rings) - m_simPAHp.getR7EmbeddedCount()));
	temp.push_back(m_simPAHp.getR7EmbeddedCount());
    m_pah_csv.Write(temp);
}
//! Writes data for CH_site_list.csv
void KMCSimulator::writeCHSiteCountCSV_after(int ID) {
    std::vector<int> temp;
	// write PAH_ID number
	temp.push_back(ID);
    // get CH count
    intpair CH = m_simPAHp.getCHCount();
    temp.push_back(CH.first);
    temp.push_back(CH.second);
    // get counts for all site types
    for(int i=0; i<(int)allSiteType.size(); i++) {
        int scount = m_simPAHp.getSiteCount(allSiteType[i]);
        temp.push_back(scount);
    }
	std::tuple <int, int, int> rings = m_simPAHp.getRingsCount();
	temp.push_back(std::get<0>(rings));
	temp.push_back((std::get<1>(rings) - m_simPAHp.getR5EmbeddedCount()));
	temp.push_back(m_simPAHp.getR5EmbeddedCount());
	temp.push_back((std::get<2>(rings) - m_simPAHp.getR7EmbeddedCount()));
	temp.push_back(m_simPAHp.getR7EmbeddedCount());
    m_pah_after_csv.Write(temp);
}
//! Writes data for rates count (csv)
void KMCSimulator::writeRatesCSV(double& time, rvector& v_rates) {
	std::vector<std::string> temp;
	temp.push_back(Strings::cstr(time));
	std::string temperature = Strings::cstr(((*m_gas)[m_gas->T]));
	temp.push_back(temperature);
	for (size_t i = 0; i<v_rates.size(); i++)
		temp.push_back(Strings::cstr(v_rates[i]));
	m_rates_csv.Write(temp);
    /*int convC[] = {1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35, 36};
    //int convM[] = {1, 2,14,15, 8,10,11,12,13, 3, 7, 9, 5, 4, 6,22,24,16,21};
    int ID;
    //if(runNo==1) {
    int total_jp = 35;
    std::vector<double> temp(total_jp+1,0);
    temp[0] = time;
    for(int i=0; i!=(int)v_rates.size(); i++) {
        ID = m_kmcmech.JPList()[i]->getID();
        temp[convC[ID-1]] = v_rates[i];
    }
    m_rates_csv.Write(temp);
    //}*/
}
//! Writes data for rates count (csv)
void KMCSimulator::writeTestRatesCSV(double& time, rvector& v_rates) {
	std::vector<std::string> temp;
	temp.push_back(Strings::cstr(time));
	std::string temperature = Strings::cstr(((*m_gas)[m_gas->T]));
	temp.push_back(temperature);
	for (size_t i = 0; i<v_rates.size(); i++)
		temp.push_back(Strings::cstr(v_rates[i]));
	m_testrates_csv.Write(temp);
}

//! Initialise CSV_IOs
void KMCSimulator::initCSVIO() {
    // Check if CSV file names specified, if not put them as default
    if(m_timer_name.length() == 0) {
        cout<<"WARNING: Output CSV name for time count is not specified. Defaulting to "<<default_timer_csv<<"\n";
        m_timer_name = default_timer_csv;
    }
    if(m_rxncount_name.length() == 0) {
        cout<<"WARNING: Output CSV name for reaction count is not specified. Defaulting to "<<default_rxncount_csv<<"\n";
        m_rxncount_name = default_rxncount_csv;
    }
    if(m_pahlist_name.length() == 0) {
        cout<<"WARNING: Output CSV name for CH and site counts is not specified. Defaulting to "<<default_pahlist_csv<<"\n";
        m_pahlist_name = default_pahlist_csv;
    }
	if(m_pahlist_after_name.length() == 0) {
        cout<<"WARNING: Output CSV name for CH and site counts after is not specified. Defaulting to "<<default_pahlist_after_csv<<"\n";
        m_pahlist_after_name = default_pahlist_after_csv;
    }
	if (m_rates_name.length() == 0) {
		cout << "WARNING: Output CSV name for PAH rates is not specified. Defaulting to " << default_rates_csv << "\n";
		m_rates_name = default_rates_csv;
	}
	if (m_testrates_name.length() == 0) {
		cout << "WARNING: Output CSV name for PAH rates is not specified. Defaulting to " << default_testrates_csv << "\n";
		m_testrates_name = default_testrates_csv;
	}

    // Open csv file
    m_timer_csv.Open(m_timer_name, true);
    m_rxn_csv.Open(m_rxncount_name, true);
    m_pah_csv.Open(m_pahlist_name, true);
	m_pah_after_csv.Open(m_pahlist_after_name, true);
    m_rates_csv.Open(m_rates_name, true);
	m_testrates_csv.Open(m_testrates_name, true);
    m_timestep_csv.Open(m_timestep_name, true);//##
    //m_pah_sitelist_csv.Open("KMC_Model/Site_list_arrange.csv",true);
    // Write column headings for CSV files
    writeCSVlabels();
}
//! Initialise reaction count
void KMCSimulator::initReactionCount() {
    m_rxn_count.assign(m_kmcmech.JPList().size(), 0);
}

//! Prints rates to command line
void KMCSimulator::printRates(double& time, const std::vector<double>& v_rates) {
	cout << "Rates calculated at Time = " << time << "\n";
    for(size_t i=0; i<m_kmcmech.JPList().size(); i++) {
        // gets name of each jump process and puts them in a row
		cout << (m_kmcmech.JPList()[i]->getName()) << "\t ->" << v_rates[i] << "\n";
    }
	cout << "\n";
}

//! Reads chemical mechanism / profile (if not obtained from Mops)
//! Similar function as Sweep::Flamesolver::LoadGasProfile
void KMCSimulator::LoadGasProfiles(const std::string gasphase, const std::string chemfile, const std::string thermfile) {
    m_mech = new Sprog::Mechanism();
    Sprog::IO::MechanismParser::ReadChemkin(chemfile, *m_mech, thermfile, 0);
    map<double,double> alpha_prof;

    // Clear the current gas-phase profile.
    m_gasprof->clear();

    // Open the file to read.
    ifstream fin(gasphase.c_str(), ios::in);
    if (!fin.good()) {
        throw runtime_error("Unable to open gas profile input "
                            "file (Sweep, KMCSimulator::LoadGasProfile).");
    }

    // Variables read from file.
    vector<string> subs;
    string delim = ",\t \r"; // Possible file delimiters (comma, tab and space).
    string line;

    // Get the first line (should be the header defining the columns).
    if (!getline(fin, line).eof()) {

        // Split the line to get the column headings.
        split(line, subs, delim);

        // Get important column indices (time, temperature and pressure).
        int tcol=-1, Tcol=-1, Pcol=-1;
        tcol = findinlist(string("Time"), subs);

        Tcol = findinlist(string("T"), subs);
        if(Tcol < 0)
            Tcol = findinlist(string("T[K]"), subs);

        Pcol = findinlist(string("P"), subs);

        // Columns to ignore, but which are useful to have in files for brush compatibility
        int Xcol = findinlist(string("X[cm]"), subs);
        int Dcol = findinlist(string("RHO[g/cm3]"), subs);
        int Vcol = findinlist(string("V[cm/s]"), subs);
        int Gcol = findinlist(string("GradT"), subs);
        int Acol = findinlist(string("Alpha"), subs);
        int Rcol = findinlist(string("wdotA4"), subs);

        // Check that the file contains required columns.
        if (tcol < 0) {
            fin.close();
            throw runtime_error("Gas-phase profile contains no Time "
                                "column (Sweep::KMCSimulator::LoadGasProfiles).");
        }
        if (Tcol < 0) {
            fin.close();
            throw runtime_error("Gas-phase profile contains no temperature "
                                "column (Sweep::KMCSimulator::LoadGasProfiles).");
        }
        if (Pcol < 0) {
            fin.close();
            throw runtime_error("Gas-phase profile contains no pressure "
                                "column (Sweep::KMCSimulator::LoadGasProfiles).");
        }

        // All other columns are chemical species.  Add them, and note
        // their columns.
        map<unsigned int,int> spcols;
        for (int i=0; (unsigned)i!=subs.size(); ++i) {
            if ((i!=tcol) && (i!=Tcol) && (i!=Pcol) && (i!=Acol) && (i!=Rcol) &&
                (i!=Xcol) && (i!=Dcol) && (i!=Vcol) && (i!=Gcol)) {
                // Try to find this species in the mechanism
                const int speciesMechIndex = m_mech->FindSpecies(subs[i]);

                if(speciesMechIndex < 0) {
                    std::ostringstream msg("Failed to find species ");
                    msg << subs[i] << " in mechanism (Sweep::KMCSimulator::LoadGasProfiles).";
                    throw std::runtime_error(msg.str());
                }
                // Found species
                spcols[i] = speciesMechIndex;
            }
        }

        // Now we can read the profile.
        while(!getline(fin, line).eof()) {
            // Set t=0 and create a new IdealGas object.
            double t = 0.0;
            double T = 0.0;
            double P = 0.0;
            double alpha = 0.0;
            double PAHRate = 0.0;
            GasPoint gpoint(m_mech->Species());

            // Split the line by columns.
            split(line, subs, delim);

            // Check that the mole fractions sum to 1
            double checkSum = 0.0;

            // Loop over all the elements in the line and save them
            // to the correct gas-phase variable.
            for (int i=0; (unsigned)i!=subs.size(); ++i) {
                if (i==tcol) {
                    // This is the Time column.
                    t = cdble(subs[i]);
                    gpoint.Time = t;
                } else if (i==Tcol) {
                    // This is the temperature column.
                    T = cdble(subs[i]);
                } else if (i==Pcol) {
                    // This is the pressure column.
                    P = cdble(subs[i]);
                } else if (i==Acol) {
                    alpha = cdble(subs[i]);
                } else if (i==Rcol) {
                    PAHRate = cdble(subs[i]);
                } else {
                    // This is a gas-phase species column.
                    map<unsigned int,int>::iterator isp = spcols.find(i);
                    if (isp != spcols.end()) {
                        const double frac = cdble(subs[i]);
                        assert(isp->second >= 0);
                        gpoint.Gas.RawData()[isp->second] = frac;
                        checkSum += frac;
                    }
                }
            }

            if((checkSum < 0.997) || checkSum > 1.003) {
                std::ostringstream msg;
                msg << "Mole fractions sum to " << checkSum
                    << ", but should sum to 1.000 (KMCSimulator::LoadGasProfiles)";
                throw std::runtime_error(msg.str());
            }

            // Set up the gas-phase by setting temperature, pressure and
            // normalising the mixture fractions.
            // TODO:  This will give the wrong component densities
            //        unless all species are specified!
            gpoint.Gas.SetTemperature(T);
            gpoint.Gas.SetPressure(P*1.0e5);
            gpoint.Gas.Normalise();
            gpoint.Gas.SetPAHFormationRate(PAHRate*1E6);

            // Add the profile point.
            alpha_prof[t] = alpha;
            m_gasprof->push_back(gpoint);
        }

        // Close the input file.
        fin.close();

        // Sort the profile by time.
        SortGasProfile(*m_gasprof);
        m_gas = new KMCGasPoint(*m_gasprof, m_mech->Species());
    } else {
        // There was no data in the file.
        fin.close();
        throw runtime_error("Input file contains no data "
                            "(Sweep::KMCSimulator::LoadGasProfiles).");
    }
}
//! Write column headings for CSV files
void KMCSimulator::writeCSVlabels() {
    // Write headings for timer
    std::vector<string> timer_headings;
    timer_headings.push_back("Total Loops"); timer_headings.push_back("Time Elapsed");
    m_timer_csv.Write(timer_headings);
    // Write headings for reaction count
    std::vector<string> rxn_headings;
	rxn_headings.push_back("Time");
	rxn_headings.push_back("Temperature");
	std::vector<string> rxn_count_headings;
    for(size_t i=0; i<m_kmcmech.JPList().size(); i++) {
        // gets name of each jump process and puts them in a row
        rxn_headings.push_back(m_kmcmech.JPList()[i]->getName());
		rxn_count_headings.push_back(m_kmcmech.JPList()[i]->getName());
    }
    m_rxn_csv.Write(rxn_count_headings);
    // Write headings for CH and site list
    std::vector<string> pah_headings;
	// write headings for PAH number
	pah_headings.push_back("PAH_ID");
    // write headings for N_C and N_H
    pah_headings.push_back("N_C");
    pah_headings.push_back("N_H");
    // write headings for number of sites for all site types "N(sitetype)"
    for(int i=0; i<(int) allSiteType.size(); i++) {
        std::string header = "N(";
        header = header.append(kmcSiteName(allSiteType[i]));
        header = header.append(")");
        pah_headings.push_back(header);
    }
	pah_headings.push_back("R6");
	pah_headings.push_back("Edge R5");
	pah_headings.push_back("Embedded R5");
	pah_headings.push_back("Edge R7");
	pah_headings.push_back("Embedded R7");
    m_pah_csv.Write(pah_headings);
	m_pah_after_csv.Write(pah_headings);
    // Write headings for rates
    std::vector<std::string> rates_header;
    rates_header.push_back("Time");
    /*for(size_t i=0; i<m_kmcmech.JPList().size()+1; i++) {
        std::ostringstream ID_name("ID");
        ID_name << (i+1);
        rates_header.push_back(ID_name.str());
    }*/
	//m_rates_csv.Write(rates_header);
	m_rates_csv.Write(rxn_headings);
	//m_testrates_csv.Write(rates_header);
	m_testrates_csv.Write(rxn_headings);
}
//! Save the structure DOT file after every X loops
void KMCSimulator::saveDOTperXLoops(int X, int &loopcount, int& runcount) {
    int m = loopcount % X;
    if(m==0) {
        string filename = "KMC_DEBUG/Run_";
        filename.append(Strings::cstr(runcount));
        filename.append("_Loop_");
        filename.append(Strings::cstr(loopcount));
        filename.append(".dot");
        m_simPAHp.saveDOT(filename);
    }
}

void KMCSimulator::saveDOTperLoop(int LOOPcount,int loopcount, int PAH_ID) {
        string filename = "KMC_DEBUG/ID_";
        filename.append(Strings::cstr(PAH_ID));
        filename.append("_Run_");
        filename.append(Strings::cstr(LOOPcount));
        filename.append("_Loop_");
        filename.append(Strings::cstr(loopcount));
        filename.append(".dot");
        m_simPAHp.saveDOT(filename);
}
//! Save the structure DOT file after every X simulation sec interval
void KMCSimulator::saveDOTperXsec(const double& X, const int& seed, const double &time, const double &time_max, KMCMechanism& copyMod, int& intervalcount) {
    int interval = (int) ceil(time/X);
    std::string graphTitle;
    if(intervalcount == -1) {
        m_gas->Interpolate(0, 0);
        std::string temp = Strings::cstr((int)ceil((*m_gas)[m_gas->T]))+"K";
        string filename = "KMC_DEBUG/";
        //filename.append(Strings::cstr(runcount));
        filename = filename+Strings::cstr(seed)+"-0.00000_s__"+temp+".dot";
        //graphTitle = "0.00000s";
        m_simPAHp.saveDOT(filename);
        intervalcount = 0;
    }
    while(interval > intervalcount || time == time_max) {
        double timenow = intervalcount * X;
        int sec = (int) floor(timenow);
        int dec = (int) (floor((timenow - (double) sec)*100000));
        std::string dec_str;
        if(dec<10) dec_str = "0000";
        else if(dec<100) dec_str = "000";
        else if(dec<1000) dec_str = "00";
        else if(dec<10000) dec_str = "0";
        else dec_str = "";
        dec_str = dec_str.append(Strings::cstr(dec));
        string filename = "KMC_DEBUG/";
        //filename.append(Strings::cstr(runcount));
        //filename.append("_");
        m_gas->Interpolate(timenow, 0);
        std::string temp = Strings::cstr((int)ceil((*m_gas)[m_gas->T]))+"K";
        filename = filename+Strings::cstr(seed)+"-"+Strings::cstr(sec)+"."+dec_str+"_s__"+temp+".dot";
        //graphTitle = Strings::cstr(sec)+"."+dec_str+"s";
        m_simPAHp.saveDOT(filename);
        intervalcount++;
        if(time==time_max) break;
    }
}

// CSV data ----
CSV_data::CSV_data(KMCSimulator& st):
		m_sim(NULL), m_name(), m_dataC(), m_dataH(), m_time(), m_T(),
		m_intervalcount(0), m_dt(0.0) {
    m_sim = &st;
}
CSV_data::~CSV_data() {}
void CSV_data::initData(int max_runs, int no_of_interv, double max_time, intpair N_CH_initial, KMCGasPoint& gp) {
    cout<<"Initialising CH_data vector...\n";
    // vector of zeros for each run
    intvector zeros(no_of_interv+1, 0);
    m_time.clear();
    m_T.clear();
    m_dataC.clear();
    m_dataH.clear();
    // calculate length of an interval
    m_dt = max_time/no_of_interv;
    // initialising time values
    for(int i=0; i<=no_of_interv; i++){
        double timetemp = m_dt*i;
        m_time.push_back(timetemp);
        gp.Interpolate(timetemp);
        double Ttemp = gp[gp.T];
        m_T.push_back(Ttemp);
    }
    // setting all values to zero for all runs
    for(int i=0; i<max_runs; i++) {
        m_dataC.push_back(zeros);
        m_dataH.push_back(zeros);
        // to include t = 0
        m_dataC[i][0] = N_CH_initial.first;
        m_dataH[i][0] = N_CH_initial.second;
    }
    m_intervalcount = 0;

    cout<<"CH_data initialised!!\n";
}
// Compares time and adds data if interval reached
void CSV_data::addData(intpair N_CH, double time, int runNo, PAHProcess& pp, bool savedot) {
    // Zakwan's code
    int& c = m_intervalcount;
    // how many intervals have passed
    int interv_now = (int) floor(time/m_dt);
    // check if new interval has reached
    if(interv_now > c) {
        // to find how many data points skipped if time step larger than interval
        int intervals_jumped = 0;
        double Temp;
        if((interv_now-c) > 1) {

            intervals_jumped = interv_now - c -1;
            // fill skipped data points with last data point before jump
            for(int i=1; i<=intervals_jumped; i++) {
                Temp = m_T[c+i];
                m_dataC[runNo-1][c+i] = m_dataC[runNo-1][c+i-1];
                m_dataH[runNo-1][c+i] = m_dataH[runNo-1][c+i-1];
                if(savedot) {
                std::string filename = "KMC_DEBUG/";
                double timenow = (c+i)*m_dt;
                int sec = (int) floor(timenow);
                int dec = (int) (floor((timenow - (double) sec)*100000));
                std::string dec_str;
                if(dec<10) dec_str = "0000";
                else if(dec<100) dec_str = "000";
                else if(dec<1000) dec_str = "00";
                else if(dec<10000) dec_str = "0";
                else dec_str = "";
                dec_str = dec_str.append(Strings::cstr(dec));
                filename = filename+Strings::cstr(sec)+"."+dec_str+"s__"+Strings::cstr(Temp)+"K.dot";
                pp.saveDOT(filename);
                }
            }
            // update current data point
            c = interv_now;

            Temp = m_T[c];
            m_dataC[runNo-1][c] = N_CH.first;
            m_dataH[runNo-1][c] = N_CH.second;
            if(savedot){
            std::string filename = "KMC_DEBUG/";
            int sec = (int) floor(time);
            int dec = (int) (floor((time - (double) sec)*100000));
            std::string dec_str;
            if(dec<10) dec_str = "0000";
            else if(dec<100) dec_str = "000";
            else if(dec<1000) dec_str = "00";
            else if(dec<10000) dec_str = "0";
            else dec_str = "";
            dec_str = dec_str.append(Strings::cstr(dec));
            filename = filename+Strings::cstr(sec)+"."+dec_str+"s__"+Strings::cstr(Temp)+"K.dot";
                pp.saveDOT(filename);
            }
        }else {
            // update current data point
            c = interv_now;

            Temp = m_T[c];
            m_dataC[runNo-1][c] = N_CH.first;
            m_dataH[runNo-1][c] = N_CH.second;
            if(savedot) {
            std::string filename = "KMC_DEBUG/";
            int sec = (int) floor(time);
            int dec = (int) (floor((time - (double) sec)*100000));
            std::string dec_str;
            if(dec<10) dec_str = "0000";
            else if(dec<100) dec_str = "000";
            else if(dec<1000) dec_str = "00";
            else if(dec<10000) dec_str = "0";
            else dec_str = "";
            dec_str = dec_str.append(Strings::cstr(dec));
            filename = filename+Strings::cstr(sec)+"."+dec_str+"s__"+Strings::cstr(Temp)+"K.dot";
                pp.saveDOT(filename);
            }
        }
    }else return;
    /* Abhijeet's
    double c = m_intervalcount*m_dt;
    // how many intervals have passed
    //int interv_now = (int) floor(time/m_dt);
    if(time >= c) {
        m_dataC[runNo-1][m_intervalcount] = N_CH.first;
        m_dataH[runNo-1][m_intervalcount] = N_CH.second;
        m_intervalcount++;
    }*/
}
// delete data of run
void CSV_data::delData(int runNo) {
    int s = (int) m_dataC[runNo-1].size();
    for(int i=1; i!=s; i++) { // exclude first data point
        m_dataC[runNo-1][i] = 0;
        m_dataH[runNo-1][i] = 0;
    }
}
// Set name of output csv file containing C-H values
void CSV_data::setName(const std::string filename){
    m_name = filename;
    //std::string num = Strings::cstr(runNo);
    //m_name = m_name.append(num);
    //m_name = m_name.append(".csv");
}
// write CSV file of data. Option is given to write PAH data in columns
// or rows (since prior to Excel 2007 only 256 columns can be viewed in an Excel spreadsheet)
void CSV_data::writeCSV(bool col, bool keep_data) {
    CSV_IO csvfile(m_name, true);
    std::vector<std::string> tempLine;
    // writes data in columns
    if(col) {
        for(int i=0; i<(int)m_time.size(); i++) {
            tempLine.clear();
            tempLine.push_back(Strings::cstr(m_time[i]));
            tempLine.push_back(Strings::cstr(m_T[i]));
            //m_time.erase(m_time.begin());
            for(int runNo=0; runNo<(int) m_dataC.size(); runNo++) {
                tempLine.push_back(Strings::cstr(m_dataC[runNo][i]));
                tempLine.push_back(Strings::cstr(m_dataH[runNo][i]));
                //m_dataC[runNo].erase(m_dataC[runNo].begin());
                //m_dataH[runNo].erase(m_dataH[runNo].begin());
            }
            csvfile.Write(tempLine);
        }
    } else{ // writes data in rows
        std::vector<std::string> tempLine2;
        csvfile.Write(m_time);
        csvfile.Write(m_T);
        for(int runNo=0; runNo<(int) m_dataC.size(); runNo++) {
            tempLine.clear();
            tempLine2.clear();
            // CSV_IO class is not able to write ints (maybe an addition should be made to the template?)
            // so convert ints into strings first
            for(int i=0; i<(int) m_dataC[runNo].size(); i++) {
                tempLine.push_back(Strings::cstr(m_dataC[runNo][i]));
                tempLine2.push_back(Strings::cstr(m_dataH[runNo][i]));
            }
            csvfile.Write(tempLine);
            csvfile.Write(tempLine2);
        }
    }
    // option to keep C-H data after completed writing
    if(!keep_data) {
        m_time.clear();
        m_dataC.clear();
        m_dataH.clear();
    }
}

// Test KMCGasPoint object. Linear interpolates values at 5 points from t = 0 to 0.005s
// and writes values of profile on console.
void KMCSimulator::TestGP() {
    cout<<endl<<"---(Sweep, KMC_ARS::KMCSimulator) Testing KMCGasPoint---"<<endl;
    for(double t=0; t<0.005; t+=0.001) {
        cout << "--At time "<<t <<"--"<<endl;
        m_gas->Interpolate(t);
        for(int i=0; i<m_gas->m_total; i++) {
            cout<<m_gas->SpNames()[i]<<"\t"<<(*m_gas)[i]<<endl;
        }
        cout<<endl;
    }
    cout<<"---(Sweep, KMC_ARS::KMCSimulator) Finished testing..."<<endl<<endl;
}

//Save DEBUG information for a single PAH
void KMCSimulator::savePAH(int PAH_number, const std::string &filename, bool optimise){
	std::string xyzname = filename;
	m_simPAHp.saveXYZ(xyzname, optimise);
    //m_simPAHp.save_formatfile(xyzname, "pdb", optimise);
	//m_simPAHp.printSites();
}

//Read PAHs to be tracked through the simulation from file.
void KMCSimulator::readTrackedPAH(const std::string &filename){
	std::ifstream src(filename);
	int PAH_number;
	while (src >> PAH_number){
		addTrackedPAH(PAH_number);
		m_tracked_pahs_fixed.push_back(PAH_number);
		//m_tracked_pahs.push_back(PAH_number);
        std::string temp_name = "KMC_DEBUG/";
        temp_name.append(std::to_string(PAH_number));
        temp_name.append("/");
        temp_name.append(std::to_string(PAH_number));
        temp_name.append("_tracker.csv");
        CSV_IO temp_file;
        temp_file.Open(temp_name,true);
        // Write headings for CH and site list
        std::vector<string> pah_headings;
        // write headings for PAH number
        pah_headings.push_back("Time");
        // write headings for N_C and N_H
        pah_headings.push_back("N_C");
        pah_headings.push_back("N_H");
        pah_headings.push_back("R6");
        pah_headings.push_back("Edge R5");
        pah_headings.push_back("Embedded R5");
        pah_headings.push_back("Edge R7");
        pah_headings.push_back("Embedded R7");
        // write headings for number of sites for all site types "N(sitetype)"
        for(int i=0; i<(int) allSiteType.size(); i++) {
            std::string header = "N(";
            header = header.append(kmcSiteName(allSiteType[i]));
            header = header.append(")");
            pah_headings.push_back(header);
        }
        temp_file.Write(pah_headings);
        temp_file.Close();
	}
}

//! Open csv file for tracked PAH
void KMCSimulator::opentrackedPAHCSV(int ID) {
    std::string temp_name = "KMC_DEBUG/";
    temp_name.append(std::to_string(ID));
    temp_name.append("/");
    temp_name.append(std::to_string(ID));
    temp_name.append("_tracker.csv");
    m_trackedpah_csv.Open(temp_name, false);
    std::vector<std::string> n_vec;
    n_vec.push_back(" ");
    m_trackedpah_csv.Write(n_vec);
}

//! Writes data for tracked PAH to csv file
void KMCSimulator::writetrackedPAHCSV() {
    //Values to write
    std::vector<double> temp;
	// write PAH_ID number
	temp.push_back(m_t);
    // get CH count
    intpair CH = m_simPAHp.getCHCount();
    temp.push_back(CH.first);
    temp.push_back(CH.second);
    // get rings count
    std::tuple <int, int, int> rings = m_simPAHp.getRingsCount();
	temp.push_back(std::get<0>(rings));
	temp.push_back((std::get<1>(rings) - m_simPAHp.getR5EmbeddedCount()));
	temp.push_back(m_simPAHp.getR5EmbeddedCount());
	temp.push_back((std::get<2>(rings) - m_simPAHp.getR7EmbeddedCount()));
	temp.push_back(m_simPAHp.getR7EmbeddedCount());
    // get counts for all site types
    for(int i=0; i<(int)allSiteType.size(); i++) {
        int scount = m_simPAHp.getSiteCount(allSiteType[i]);
        temp.push_back(scount);
    }
    m_trackedpah_csv.Write(temp);
}

//! Writes data for tracked PAH to csv file
void KMCSimulator::closetrackedPAHCSV() {
    m_trackedpah_csv.Close();
}

//Add PAH to the tracked list on the fly.
void KMCSimulator::addTrackedPAH(int PAH_number){
	// Check if PAH_number is already tracked.
	auto finder = std::find(std::begin(m_tracked_pahs), std::end(m_tracked_pahs), PAH_number);
	if (finder == m_tracked_pahs.end()){
		std::cout << "Adding PAH number " << PAH_number << " to tracked list. \n";
		m_tracked_pahs.push_back(PAH_number);
	}
	else{
		std::cout << "PAH number " << PAH_number << " already existed in tracked list. \n";
	}
	// Check if saving folder exists.
	std::string dir_path = "KMC_DEBUG/";
	dir_path.append(std::to_string(PAH_number));
	boost::filesystem::path dir(dir_path);
	if (boost::filesystem::exists(dir)){
		// Folder already existed.
		std::cout << "Folder " << dir << " already existed. \n";
	}
	else {
		if(boost::filesystem::create_directory(dir)) {
			std::cout << "Creating folder " << dir << ". \n";
		}
		else{
			std::cout << "Error creating folder " << dir << ". \n Continuing simulation. PAH coordinates may not be saved. \n";
		}
	}
}

//Remove PAH from the tracked list on the fly.
void KMCSimulator::removeTrackedPAH(int PAH_number){
	// Find PAH_number.
	auto finder = std::find(std::begin(m_tracked_pahs), std::end(m_tracked_pahs), PAH_number);
	if (finder == std::end(m_tracked_pahs)){
		std::cout << "Trying to remove PAH number " << PAH_number << ", but it was not found in tracked list. \n";
	}
	else{
		//Checks that PAH is not in the fixed tracked PAH list.
		auto fix_finder = std::find(std::begin(m_tracked_pahs_fixed), std::end(m_tracked_pahs_fixed), PAH_number);
		if (fix_finder == std::end(m_tracked_pahs_fixed)){
			std::cout << "Removing PAH number " << PAH_number << " from tracked list. \n";
			m_tracked_pahs.erase(finder);
			// Check if saving folder exists.
			std::string dir_path = "KMC_DEBUG/";
			dir_path.append(std::to_string(PAH_number));
			boost::filesystem::path dir(dir_path);
			if (boost::filesystem::exists(dir)){
				// Folder already existed.
				std::cout << "Deleting folder " << dir << ". \n";
				boost::filesystem::remove_all(dir);
			}
			else {
				std::cout << "Trying to remove folder " << dir << ", but it did not exist. Continuing simulation. \n";
			}
		}
	}
}

//! Sets the debug flag for PAHProcess.
void KMCSimulator::setDebugPAH(const bool debug_pah) {
    m_simPAHp.m_debug_pah = debug_pah;
}


/*
 * Copyright (c) 2011, Mattia Penati <mattia.penati@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Politecnico di Milano nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef AMA_TENSOR_DETAIL_TENSOR_CWISE_UNARY_HPP
#define AMA_TENSOR_DETAIL_TENSOR_CWISE_UNARY_HPP 1

#include <ama/tensor/detail/tensor_base.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/mpl/if.hpp>

namespace ama
{
  namespace tensor_
  {

    /* this class represent a unary component-wise operation */
    template <typename OPERAND, typename OPERATOR> class tensor_cwise_unary;

    /* specialization of tensor_traits */
    template <typename OPERAND, typename OPERATOR>
    struct tensor_traits< tensor_cwise_unary<OPERAND, OPERATOR> >
    {
      typedef typename OPERATOR::result_type value_type;

      typedef typename OPERAND::dimension_type dimension_type;

      typedef typename OPERAND::controvariant_type controvariant_type;
      typedef typename OPERAND::covariant_type covariant_type;

      typedef ::boost::mpl::false_ is_assignable;
      typedef ::boost::mpl::true_ is_temporary;
    };

    /* class declaration */
    template <typename OPERAND, typename OPERATOR>
    class tensor_cwise_unary:
        public tensor_base< tensor_cwise_unary<OPERAND, OPERATOR> >
    {
    protected:
      typedef tensor_base< tensor_cwise_unary<OPERAND, OPERATOR> > base_type;
      typedef tensor_cwise_unary<OPERAND, OPERATOR> derived_type;

    protected:
      typedef OPERAND operand_type;
      typedef OPERATOR operator_type;

    public:
      typedef typename base_type::value_type value_type;

    public:
      /* constructor */
      explicit
      tensor_cwise_unary(operand_type const & operand,
                         operator_type const & op = operator_type())
          : m_operand(operand),
            m_operator(op) { }

    public:
      /* retrieve the value */
      template <typename ILIST>
      value_type at() const { return m_operator(m_operand.template at<ILIST>()); }

    protected:
      /* if the operand is temporary we save a copy, otherwise a reference */
      typedef typename OPERAND::is_temporary operand_is_temporary;
      typedef typename ::boost::mpl::if_<
            operand_is_temporary
          , operand_type const
          , operand_type const &
          >::type const_operand_type;

      /* members */
      const_operand_type m_operand;
      operator_type const m_operator;
    };

  }
}

#endif /* AMA_TENSOR_DETAIL_TENSOR_CWISE_UNARY_HPP */


/** $Id: MuonPropagator.cxx 176172 2019-10-16 16:45:58Z jsoedingrekso $
 * @file
 * @author Jakob van Santen <vansanten@wisc.edu>
 *
 * $Revision: 176172 $
 * $Date: 2019-10-16 10:45:58 -0600 (Wed, 16 Oct 2019) $
 */

#include <boost/assign.hpp>
#include <boost/foreach.hpp>
#include <boost/bimap.hpp>

#include "MuonGun/MuonPropagator.h"

namespace I3MuonGun {

MuonPropagator::MuonPropagator(const std::string& medium, double ecut, double vcut, double rho)
    : propagator_(NULL)
{
    PROPOSAL::Sector::Definition sec_def;

    sec_def.cut_settings.SetEcut(ecut);
    sec_def.cut_settings.SetVcut(vcut);
    bool cont_rand = false;
    if (ecut < 0 && vcut > 0)
    {
        cont_rand = true;
    }
    sec_def.do_continuous_randomization = cont_rand;

    sec_def.SetMedium(*PROPOSAL::MediumFactory::Get().CreateMedium(medium, rho));

    PROPOSAL::Sphere detector(PROPOSAL::Vector3D(0.0, 0.0, 0.0), 1e18, 0.0);
    sec_def.SetGeometry(detector);

    std::vector<PROPOSAL::Sector::Definition> sector_definitions;
    sector_definitions.push_back(sec_def);

    PROPOSAL::InterpolationDef interpol_def;

    std::ostringstream prefix;
    prefix << getenv("I3_BUILD") << "/MuonGun/resources/tables/";

    interpol_def.path_to_tables = prefix.str();

    propagator_ = new PROPOSAL::Propagator(PROPOSAL::MuMinusDef::Get(),
                                           sector_definitions,
                                           detector,
                                           interpol_def);
}

MuonPropagator::~MuonPropagator()
{
    delete propagator_;
}

void MuonPropagator::SetSeed(int seed)
{
    PROPOSAL::RandomGenerator::Get().SetSeed(seed);
}

inline std::string GetMMCName(I3Particle::ParticleType pt)
{
    std::string name;

    if (pt == I3Particle::MuMinus)
        name = "mu-";
    else if (pt == I3Particle::MuPlus)
        name = "mu+";

    return name;
}

std::string MuonPropagator::GetName(const I3Particle& p)
{
    return GetMMCName(p.GetType());
}

/** Differential stochastic rate: d^2N/dv/dx [1/m] */
double MuonPropagator::GetStochasticRate(double energy, double fraction, I3Particle::ParticleType type) const
{
    // the propagator_ is always initialised with a Muon
    // there is no need to specify the type of the particle
    // this input parameter is historic and can be vanished
    // it just exist for backward compatibility
    (void)type;

    // Check kinematics
    if (fraction <= 0 || energy*(1-fraction) <= propagator_->GetParticle().GetMass()*I3Units::MeV)
        return 0.;

    double contrib;
    double rate = 0.0;

    const std::vector<PROPOSAL::CrossSection*>& crosssections = propagator_->GetCurrentSector()->GetUtility().GetCrosssections();

    for (std::vector<PROPOSAL::CrossSection*>::const_iterator iter = crosssections.begin(); iter != crosssections.end(); ++iter)
    {
        if((*iter)->GetTypeId() == PROPOSAL::DynamicData::Brems ||
            (*iter)->GetTypeId() == PROPOSAL::DynamicData::Epair ||
            (*iter)->GetTypeId() == PROPOSAL::DynamicData::NuclInt)
        {
            for (int i = 0; i < propagator_->GetCurrentSector()->GetUtility().GetMedium().GetNumComponents(); ++i)
            {
                (*iter)->GetParametrization().SetCurrentComponent(i);
                contrib = (*iter)->GetParametrization().DifferentialCrossSection(energy, fraction);
                if(std::isfinite(contrib) && contrib > 0)
                    rate += contrib;
            }
        }
        else // if((*iter)->GetTypeId() == PROPOSAL::DynamicData::DeltaE)
        {
            contrib = (*iter)->GetParametrization().DifferentialCrossSection(energy, fraction);
            if(std::isfinite(contrib) && contrib > 0)
                rate += contrib;
        }
    }
    return rate / I3Units::cm;
}

/** total stochastic rate: dN/dx [1/m] */
double MuonPropagator::GetTotalStochasticRate(double energy, I3Particle::ParticleType type) const
{
    // the propagator_ is always initialised with a Muon
    // there is no need to specify the type of the particle
    // this input parameter is historic and can be vanished
    // it just exist for backward compatibility
    (void)type;

    double rate = 0.0;
    const std::vector<PROPOSAL::CrossSection*>& crosssections = propagator_->GetCurrentSector()->GetUtility().GetCrosssections();

    for (std::vector<PROPOSAL::CrossSection*>::const_iterator iter = crosssections.begin(); iter != crosssections.end(); ++iter)
    {
        rate += (*iter)->CalculatedNdx(energy);
    }
    return rate / I3Units::cm;
}

I3Particle MuonPropagator::propagate(const I3Particle& p,
                                     double distance,
                                     boost::shared_ptr<std::vector<I3Particle> > losses)
{
    I3Particle endpoint(p);

    double x, y, z, theta, phi;

    PROPOSAL::Particle& pp = propagator_->GetParticle();
    pp.SetParentParticleId(0);
    pp.SetParticleId(0);

    x = p.GetPos().GetX() / I3Units::cm;
    y = p.GetPos().GetY() / I3Units::cm;
    z = p.GetPos().GetZ() / I3Units::cm;

    theta = p.GetDir().CalcTheta() / I3Units::radian;
    phi   = p.GetDir().CalcPhi() / I3Units::radian;

    pp.SetPosition(PROPOSAL::Vector3D(x, y, z));

    PROPOSAL::Vector3D direction;
    direction.SetSphericalCoordinates(1.0, phi, theta);
    direction.CalculateCartesianFromSpherical();
    pp.SetDirection(direction);

    pp.SetEnergy(p.GetEnergy() / I3Units::MeV);
    pp.SetTime(p.GetTime() / I3Units::second);

    double length = p.GetLength() / I3Units::cm;
    if (!std::isfinite(length))
    {
        length = 0.;
    }
    pp.SetPropagatedDistance(length);

    propagator_->Propagate(distance / I3Units::cm);

    x = pp.GetPosition().GetX() * I3Units::cm;
    y = pp.GetPosition().GetY() * I3Units::cm;
    z = pp.GetPosition().GetZ() * I3Units::cm;

    theta = pp.GetDirection().GetTheta() * I3Units::radian;
    phi   = pp.GetDirection().GetPhi() * I3Units::radian;

    endpoint.SetPos(x, y, z);
    endpoint.SetThetaPhi(theta, phi);

    endpoint.SetEnergy(pp.GetEnergy() * I3Units::MeV);
    endpoint.SetLength(pp.GetPropagatedDistance() * I3Units::cm);
    endpoint.SetTime(pp.GetTime() * I3Units::second);

    if (losses)
    {
        std::vector<PROPOSAL::DynamicData*> history = PROPOSAL::Output::getInstance().GetSecondarys();
        for (unsigned int i = 0; i < history.size(); i++)
        {
            losses->push_back(GenerateI3Particle(*history[i]));
        }
    }

    PROPOSAL::Output::getInstance().ClearSecondaryVector();

    return endpoint;
}

typedef boost::bimap<I3Particle::ParticleType, std::string> bimap_ParticleType;
static const bimap_ParticleType I3_PROPOSAL_ParticleType_bimap =
    boost::assign::list_of<bimap_ParticleType::relation>
        (I3Particle::MuMinus, "MuMinus")
        (I3Particle::MuPlus, "MuPlus")
        (I3Particle::TauMinus, "TauMinus")
        (I3Particle::TauPlus, "TauPlus")
        (I3Particle::EMinus, "EMinus")
        (I3Particle::EPlus, "EPlus")
        (I3Particle::NuMu, "NuMu")
        (I3Particle::NuMuBar, "NuMuBar")
        (I3Particle::NuE, "NuE")
        (I3Particle::NuEBar, "NuEBar")
        (I3Particle::NuTau, "NuTau")
        (I3Particle::NuTauBar, "NuTauBar")
        (I3Particle::Brems, "Brems")
        (I3Particle::DeltaE, "DeltaE")
        (I3Particle::PairProd, "EPair")
        (I3Particle::NuclInt, "NuclInt")
        (I3Particle::MuPair, "MuPair")
        (I3Particle::Hadrons, "Hadrons")
        (I3Particle::Monopole, "Monopole")
        (I3Particle::STauMinus, "STauMinus")
        (I3Particle::STauPlus, "STauPlus")
        (I3Particle::Gamma, "Gamma")
        (I3Particle::Pi0, "Pi0")
        (I3Particle::PiPlus, "PiPlus")
        (I3Particle::PiMinus, "PiMinus")
        (I3Particle::K0_Short, "K0") // TODO(mario):  Fri 2017/11/17
        (I3Particle::KPlus, "KPlus")
        (I3Particle::KMinus, "KMinus")
        (I3Particle::PPlus, "PPlus")
        (I3Particle::PMinus, "PMinus");

// ------------------------------------------------------------------------- //
I3Particle::ParticleType MuonPropagator::GenerateI3Type(const PROPOSAL::DynamicData& secondary)
{
    PROPOSAL::DynamicData::Type type = secondary.GetTypeId();

    switch (type)
    {
        case PROPOSAL::DynamicData::Particle:
        {
            const PROPOSAL::Particle& particle = static_cast<const PROPOSAL::Particle&>(secondary);
            PROPOSAL::ParticleDef particle_def = particle.GetParticleDef();

            I3Particle::ParticleType ptype_I3;

            bimap_ParticleType::right_const_iterator proposal_iterator =
                I3_PROPOSAL_ParticleType_bimap.right.find(particle_def.name);
            if (proposal_iterator == I3_PROPOSAL_ParticleType_bimap.right.end())
            {
                log_fatal("The PROPOSALParticle '%s' can not be converted to a I3Particle", particle_def.name.c_str());
            } else
            {
                return proposal_iterator->second;
            }
            break;
        }
        case PROPOSAL::DynamicData::Brems:
            return I3Particle::Brems;
            break;
        case PROPOSAL::DynamicData::Epair:
            return I3Particle::PairProd;
            break;
        case PROPOSAL::DynamicData::DeltaE:
            return I3Particle::DeltaE;
            break;
        case PROPOSAL::DynamicData::NuclInt:
            return I3Particle::NuclInt;
            break;
        case PROPOSAL::DynamicData::ContinuousEnergyLoss:
            return I3Particle::ContinuousEnergyLoss;
            break;
        default:
            log_fatal("PROPOSAL Particle can not be converted to a I3Particle");
    }
    return I3Particle::unknown;
}

// ------------------------------------------------------------------------- //
I3Particle MuonPropagator::GenerateI3Particle(const PROPOSAL::DynamicData& pp)
{
    double x = pp.GetPosition().GetX() * I3Units::cm;
    double y = pp.GetPosition().GetY() * I3Units::cm;
    double z = pp.GetPosition().GetZ() * I3Units::cm;

    double theta = pp.GetDirection().GetTheta() * I3Units::radian;
    double phi   = pp.GetDirection().GetPhi() * I3Units::radian;

    I3Particle i3_particle;
    i3_particle.SetType(GenerateI3Type(pp));
    i3_particle.SetLocationType(I3Particle::InIce);

    i3_particle.SetPos(x, y, z);
    i3_particle.SetThetaPhi(theta, phi);

    i3_particle.SetLength(pp.GetPropagatedDistance() * I3Units::cm);
    i3_particle.SetTime(pp.GetTime() * I3Units::second);
    i3_particle.SetEnergy(pp.GetEnergy() * I3Units::MeV);

    log_trace("MMC DEBUG SEC \n  pos=(%g,%g,%g) ang=(%g,%g)  e=%g t=%g  l=%g",
        x, y, z, theta, phi,
        pp.GetEnergy() * I3Units::MeV,
        pp.GetTime() * I3Units::second,
        pp.GetPropagatedDistance() * I3Units::cm);

    return i3_particle;
}

void Crust::AddLayer(I3Surfaces::SurfacePtr s, boost::shared_ptr<MuonPropagator> p)
{
    boundaries_.push_back(s);
    propagators_.push_back(p);
}

I3Particle Crust::Ingest(const I3Particle& p)
{
    I3Particle propped(p);
    double l = 0;
    for (unsigned i = 0; (propped.GetEnergy() > 0) && (i < boundaries_.size()); i++)
    {
        double dx = boundaries_[i]->GetIntersection(propped.GetPos(), propped.GetDir()).first;
        if (dx > 0)
            propped = (i > 0 ? propagators_[i - 1] : defaultPropagator_)->propagate(propped, dx);
        // Force lengths to measure the distance back to the outermost surface
        if (i > 0)
            l += std::min(dx, propped.GetLength());
    }
    propped.SetLength(l);

    return propped;
}

} // namespace I3MuonGun


///////////////////////////////////////////////////////////////
//  Copyright 2012 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_

#ifndef BOOST_MATH_FLOAT_BACKEND_HPP
#define BOOST_MATH_FLOAT_BACKEND_HPP

#include <iostream>
#include <iomanip>
#include <sstream>
#include <boost/cstdint.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/math/concepts/real_concept.hpp>
#include <boost/multiprecision/number.hpp>
#include <boost/math/common_factor_rt.hpp>

namespace boost{
namespace multiprecision{
namespace backends{

#ifdef BOOST_MSVC
#  pragma warning(push)
#  pragma warning(disable:4389 4244 4018 4244 4127)
#endif

template <class Arithmetic>
struct arithmetic_backend
{
   typedef mpl::list<short, int, long, long long>                                      signed_types;
   typedef mpl::list<unsigned short, unsigned, unsigned long, unsigned long long>      unsigned_types;
   typedef mpl::list<float, double, long double>                                       float_types;
   typedef int                                                                         exponent_type;

   arithmetic_backend(){}
   arithmetic_backend(const arithmetic_backend& o)
   {
      m_value = o.m_value;
   }
   template <class A>
   arithmetic_backend(const A& o, const typename enable_if<is_arithmetic<A> >::type* = 0) : m_value(o) {}
   template <class A>
   arithmetic_backend(const arithmetic_backend<A>& o) : m_value(o.data()) {}
   arithmetic_backend& operator = (const arithmetic_backend& o)
   {
      m_value = o.m_value;
      return *this;
   }
   template <class A>
   typename enable_if<is_arithmetic<A>, arithmetic_backend&>::type operator = (A i)
   {
      m_value = i;
      return *this;
   }
   template <class A>
   arithmetic_backend& operator = (const arithmetic_backend<A>& i)
   {
      m_value = i.data();
      return *this;
   }
   arithmetic_backend& operator = (const char* s)
   {
#ifndef BOOST_NO_EXCEPTIONS
      try
      {
#endif
         m_value = boost::lexical_cast<Arithmetic>(s);
#ifndef BOOST_NO_EXCEPTIONS
      }
      catch(const bad_lexical_cast&)
      {
         throw std::runtime_error(std::string("Unable to interpret the string provided: \"") + s + std::string("\" as a compatible number type."));
      }
#endif
      return *this;
   }
   void swap(arithmetic_backend& o)
   {
      std::swap(m_value, o.m_value);
   }
   std::string str(std::streamsize digits, std::ios_base::fmtflags f)const
   {
      std::stringstream ss;
      ss.flags(f);
      ss << std::setprecision(digits ? digits : std::numeric_limits<Arithmetic>::digits10 + 4) << m_value;
      return ss.str();
   }
   void do_negate(const mpl::true_&)
   {
      m_value = 1 + ~m_value;
   }
   void do_negate(const mpl::false_&)
   {
      m_value = -m_value;
   }
   void negate()
   {
      do_negate(is_unsigned<Arithmetic>());
   }
   int compare(const arithmetic_backend& o)const
   {
      return m_value > o.m_value ? 1 : (m_value < o.m_value ? -1 : 0);
   }
   template <class A>
   typename enable_if<is_arithmetic<A>, int>::type compare(A i)const
   {
      return m_value > static_cast<Arithmetic>(i) ? 1 : (m_value < static_cast<Arithmetic>(i) ? -1 : 0);
   }
   Arithmetic& data() { return m_value; }
   const Arithmetic& data()const { return m_value; }
private:
   Arithmetic m_value;
};

template <class R, class Arithmetic>
inline void eval_convert_to(R* result, const arithmetic_backend<Arithmetic>& backend)
{
   *result = backend.data();
}

template <class Arithmetic>
inline bool eval_eq(const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   return a.data() == b.data();
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2>, bool>::type eval_eq(const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   return a.data() == static_cast<Arithmetic>(b);
}
template <class Arithmetic>
inline bool eval_lt(const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   return a.data() < b.data();
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2>, bool>::type eval_lt(const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   return a.data() < static_cast<Arithmetic>(b);
}
template <class Arithmetic>
inline bool eval_gt(const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   return a.data() > b.data();
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2>, bool>::type eval_gt(const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   return a.data() > static_cast<Arithmetic>(b);
}

template <class Arithmetic>
inline void eval_add(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   result.data() += o.data();
}
template <class Arithmetic>
inline void eval_subtract(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   result.data() -= o.data();
}
template <class Arithmetic>
inline void eval_multiply(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   result.data() *= o.data();
}
template <class Arithmetic>
inline typename enable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   result.data() /= o.data();
}
template <class Arithmetic>
inline typename disable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   if(!o.data())
      BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
   result.data() /= o.data();
}

template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2> >::type eval_add(arithmetic_backend<Arithmetic>& result, const A2& o)
{
   result.data() += o;
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2> >::type eval_subtract(arithmetic_backend<Arithmetic>& result, const A2& o)
{
   result.data() -= o;
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2> >::type eval_multiply(arithmetic_backend<Arithmetic>& result, const A2& o)
{
   result.data() *= o;
}
template <class Arithmetic, class A2>
inline typename enable_if_c<(is_arithmetic<A2>::value && !std::numeric_limits<Arithmetic>::has_infinity)>::type
   eval_divide(arithmetic_backend<Arithmetic>& result, const A2& o)
{
   if(!o)
      BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
   result.data() /= o;
}
template <class Arithmetic, class A2>
inline typename enable_if_c<(is_arithmetic<A2>::value && std::numeric_limits<Arithmetic>::has_infinity)>::type
   eval_divide(arithmetic_backend<Arithmetic>& result, const A2& o)
{
   result.data() /= o;
}

template <class Arithmetic>
inline void eval_add(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   result.data() = a.data() + b.data();
}
template <class Arithmetic>
inline void eval_subtract(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   result.data() = a.data() - b.data();
}
template <class Arithmetic>
inline void eval_multiply(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   result.data() = a.data() * b.data();
}
template <class Arithmetic>
inline typename enable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   result.data() = a.data() / b.data();
}
template <class Arithmetic>
inline typename disable_if_c<std::numeric_limits<Arithmetic>::has_infinity>::type eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   if(!b.data())
      BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
   result.data() = a.data() / b.data();
}

template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2> >::type eval_add(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   result.data() = a.data() + b;
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2> >::type eval_subtract(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   result.data() = a.data() - b;
}
template <class Arithmetic, class A2>
inline typename enable_if<is_arithmetic<A2> >::type eval_multiply(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   result.data() = a.data() * b;
}
template <class Arithmetic, class A2>
inline typename enable_if_c<(is_arithmetic<A2>::value && !std::numeric_limits<Arithmetic>::has_infinity)>::type
   eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   if(!b)
      BOOST_THROW_EXCEPTION(std::overflow_error("Divide by zero"));
   result.data() = a.data() / b;
}
template <class Arithmetic, class A2>
inline typename enable_if_c<(is_arithmetic<A2>::value && std::numeric_limits<Arithmetic>::has_infinity)>::type
   eval_divide(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const A2& b)
{
   result.data() = a.data() / b;
}

template <class Arithmetic>
inline bool eval_is_zero(const arithmetic_backend<Arithmetic>& val)
{
   return val.data() == 0;
}

template <class Arithmetic>
inline typename enable_if_c<
      (!std::numeric_limits<Arithmetic>::is_specialized
      || std::numeric_limits<Arithmetic>::is_signed), int>::type
   eval_get_sign(const arithmetic_backend<Arithmetic>& val)
{
   return val.data() == 0 ? 0 : val.data() < 0 ? -1 : 1;
}
template <class Arithmetic>
inline typename disable_if_c<
      (std::numeric_limits<Arithmetic>::is_specialized
      || std::numeric_limits<Arithmetic>::is_signed), int>::type
   eval_get_sign(const arithmetic_backend<Arithmetic>& val)
{
   return val.data() == 0 ? 0 : 1;
}

template <class T>
inline typename enable_if<is_unsigned<T>, T>::type abs(T v) { return v; }

template <class Arithmetic>
inline void eval_abs(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   using std::abs;
   using boost::multiprecision::backends::abs;
   result.data() = abs(o.data());
}

template <class Arithmetic>
inline void eval_fabs(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   result.data() = std::abs(o.data());
}

template <class Arithmetic>
inline void eval_floor(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = floor(o.data());
}

template <class Arithmetic>
inline void eval_ceil(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = ceil(o.data());
}

template <class Arithmetic>
inline void eval_sqrt(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = sqrt(o.data());
}

template <class Arithmetic>
inline int eval_fpclassify(const arithmetic_backend<Arithmetic>& o)
{
   return (boost::math::fpclassify)(o.data());
}

template <class Arithmetic>
inline void eval_trunc(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = trunc(o.data());
}

template <class Arithmetic>
inline void eval_round(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = round(o.data());
}

template <class Arithmetic>
inline void eval_frexp(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, int* v)
{
   BOOST_MATH_STD_USING
   result.data() = frexp(a.data(), v);
}

template <class Arithmetic>
inline void eval_ldexp(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, int v)
{
   BOOST_MATH_STD_USING
   result.data() = ldexp(a.data(), v);
}

template <class Arithmetic>
inline void eval_exp(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = exp(o.data());
}

template <class Arithmetic>
inline void eval_log(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = log(o.data());
}

template <class Arithmetic>
inline void eval_log10(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = log10(o.data());
}

template <class Arithmetic>
inline void eval_sin(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = sin(o.data());
}

template <class Arithmetic>
inline void eval_cos(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = cos(o.data());
}

template <class Arithmetic>
inline void eval_tan(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = tan(o.data());
}

template <class Arithmetic>
inline void eval_acos(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = acos(o.data());
}

template <class Arithmetic>
inline void eval_asin(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = asin(o.data());
}

template <class Arithmetic>
inline void eval_atan(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = atan(o.data());
}

template <class Arithmetic>
inline void eval_sinh(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = sinh(o.data());
}

template <class Arithmetic>
inline void eval_cosh(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = cosh(o.data());
}

template <class Arithmetic>
inline void eval_tanh(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& o)
{
   BOOST_MATH_STD_USING
   result.data() = tanh(o.data());
}

template <class Arithmetic>
inline void eval_fmod(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   BOOST_MATH_STD_USING
   result.data() = fmod(a.data(), b.data());
}

template <class Arithmetic>
inline void eval_pow(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   BOOST_MATH_STD_USING
   result.data() = pow(a.data(), b.data());
}

template <class Arithmetic>
inline void eval_atan2(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   BOOST_MATH_STD_USING
   result.data() = atan2(a.data(), b.data());
}

template <class Arithmetic, class I>
inline void eval_left_shift(arithmetic_backend<Arithmetic>& result, I val)
{
   result.data() <<= val;
}

template <class Arithmetic, class I>
inline void eval_right_shift(arithmetic_backend<Arithmetic>& result, I val)
{
   result.data() >>= val;
}

template <class Arithmetic>
inline void eval_modulus(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a)
{
   result.data() %= a.data();
}

template <class Arithmetic>
inline void eval_bitwise_and(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a)
{
   result.data() &= a.data();
}

template <class Arithmetic>
inline void eval_bitwise_or(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a)
{
   result.data() |= a.data();
}

template <class Arithmetic>
inline void eval_bitwise_xor(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a)
{
   result.data() ^= a.data();
}

template <class Arithmetic>
inline void eval_complement(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a)
{
   result.data() = ~a.data();
}

template <class Arithmetic>
inline void eval_gcd(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   result.data() = boost::math::gcd(a.data(), b.data());
}

template <class Arithmetic>
inline void eval_lcm(arithmetic_backend<Arithmetic>& result, const arithmetic_backend<Arithmetic>& a, const arithmetic_backend<Arithmetic>& b)
{
   result.data() = boost::math::lcm(a.data(), b.data());
}

#ifdef BOOST_MSVC
#  pragma warning(pop)
#endif

} // namespace backends

using boost::multiprecision::backends::arithmetic_backend;

template <class Arithmetic>
struct number_category<arithmetic_backend<Arithmetic> > : public mpl::int_<is_integral<Arithmetic>::value ? number_kind_integer : number_kind_floating_point>{};

namespace detail{

template <class Backend>
struct double_precision_type;

template<class Arithmetic, boost::multiprecision::expression_template_option ET>
struct double_precision_type<number<arithmetic_backend<Arithmetic>, ET> >
{
   typedef number<arithmetic_backend<typename double_precision_type<Arithmetic>::type>, ET> type;
};
template<>
struct double_precision_type<arithmetic_backend<boost::int32_t> >
{
   typedef arithmetic_backend<boost::int64_t> type;
};

}

}} // namespaces
#if !(defined(__SGI_STL_PORT) || defined(BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS))
//
// We shouldn't need these to get code to compile, however for the sake of
// "level playing field" performance comparisons they avoid the very slow
// lexical_cast's that would otherwise take place.  Definition has to be guarded
// by the inverse of pp-logic in real_concept.hpp which defines these as a workaround
// for STLPort plus some other old/broken standartd libraries.
//
namespace boost{ namespace math{ namespace tools{

   template <>
   inline unsigned int real_cast<unsigned int, concepts::real_concept>(concepts::real_concept r)
   {
      return static_cast<unsigned int>(r.value());
   }

   template <>
   inline int real_cast<int, concepts::real_concept>(concepts::real_concept r)
   {
      return static_cast<int>(r.value());
   }

   template <>
   inline long real_cast<long, concepts::real_concept>(concepts::real_concept r)
   {
      return static_cast<long>(r.value());
   }

   // Converts from T to narrower floating-point types, float, double & long double.

   template <>
   inline float real_cast<float, concepts::real_concept>(concepts::real_concept r)
   {
      return static_cast<float>(r.value());
   }
   template <>
   inline double real_cast<double, concepts::real_concept>(concepts::real_concept r)
   {
      return static_cast<double>(r.value());
   }
   template <>
   inline long double real_cast<long double, concepts::real_concept>(concepts::real_concept r)
   {
      return r.value();
   }

}}}
#endif

namespace std{

template <class Arithmetic, boost::multiprecision::expression_template_option ExpressionTemplates>
class numeric_limits<boost::multiprecision::number<boost::multiprecision::arithmetic_backend<Arithmetic>, ExpressionTemplates > > : public std::numeric_limits<Arithmetic>
{
   typedef std::numeric_limits<Arithmetic> base_type;
   typedef boost::multiprecision::number<boost::multiprecision::arithmetic_backend<Arithmetic>, ExpressionTemplates> number_type;
public:
   BOOST_STATIC_CONSTEXPR number_type (min)() BOOST_NOEXCEPT { return (base_type::min)(); }
   BOOST_STATIC_CONSTEXPR number_type (max)() BOOST_NOEXCEPT { return (base_type::max)(); }
   BOOST_STATIC_CONSTEXPR number_type lowest() BOOST_NOEXCEPT { return -(max)(); }
   BOOST_STATIC_CONSTEXPR number_type epsilon() BOOST_NOEXCEPT { return base_type::epsilon(); }
   BOOST_STATIC_CONSTEXPR number_type round_error() BOOST_NOEXCEPT { return epsilon() / 2; }
   BOOST_STATIC_CONSTEXPR number_type infinity() BOOST_NOEXCEPT { return base_type::infinity(); }
   BOOST_STATIC_CONSTEXPR number_type quiet_NaN() BOOST_NOEXCEPT { return base_type::quiet_NaN(); }
   BOOST_STATIC_CONSTEXPR number_type signaling_NaN() BOOST_NOEXCEPT { return base_type::signaling_NaN(); }
   BOOST_STATIC_CONSTEXPR number_type denorm_min() BOOST_NOEXCEPT { return base_type::denorm_min(); }
};

template<>
class numeric_limits<boost::math::concepts::real_concept> : public std::numeric_limits<long double>
{
   typedef std::numeric_limits<long double> base_type;
   typedef boost::math::concepts::real_concept number_type;
public:
   static const number_type (min)() BOOST_NOEXCEPT { return (base_type::min)(); }
   static const number_type (max)() BOOST_NOEXCEPT { return (base_type::max)(); }
   static const number_type lowest() BOOST_NOEXCEPT { return -(max)(); }
   static const number_type epsilon() BOOST_NOEXCEPT { return base_type::epsilon(); }
   static const number_type round_error() BOOST_NOEXCEPT { return epsilon() / 2; }
   static const number_type infinity() BOOST_NOEXCEPT { return base_type::infinity(); }
   static const number_type quiet_NaN() BOOST_NOEXCEPT { return base_type::quiet_NaN(); }
   static const number_type signaling_NaN() BOOST_NOEXCEPT { return base_type::signaling_NaN(); }
   static const number_type denorm_min() BOOST_NOEXCEPT { return base_type::denorm_min(); }
};

}

#include <boost/multiprecision/detail/integer_ops.hpp>

#endif