//! @file pfr1d.cpp
//! Provides an plug-flow reactor model in 1d.

// This file is part of Hetero_ct.
// Source is adapted from CanteraPFR codebase.
//#include <boost/math/interpolators/barycentric_rational_interpolation.hpp>
#include <boost/range/adaptors.hpp>

#include "pfr1d.h"
#include "cantera/numerics/ResidJacEval.h"

using namespace std;
using namespace Cantera;

namespace OpenMKM
{

PFR1d::PFR1d(IdealGasMix *gas, vector<InterfaceKinetics*> surf_kins,
             vector<SurfPhase*> surf_phases, double area, double cat_abyv,
             double inlet_gas_velocity)
   : ResidJacEval{}, m_gas(gas), m_surf_kins(surf_kins),
     m_surf_phases(surf_phases), m_Ac(area), m_cat_abyv(cat_abyv),
     m_u0(inlet_gas_velocity), m_T_interp(nullptr)
{

    suppress_thermo_warnings(SUPPRESS_WARNINGS);

    cout << boolalpha
         //<< setw(16) << "\nReactor Model:  " << "PFR"
         << setw(16) << "\nUsing Sundials? " << CT_SUNDIALS_VERSION
         << setw(16) << "\nUsing LAPACK?   " << bool(CT_SUNDIALS_USE_LAPACK)
         << endl;

    m_rho_ref = m_gas->density();
    cout << boolalpha << setw(16) << "Energy enabled? " << energyEnabled() << endl;
    m_nsp = m_gas->nSpecies();
    if (energyEnabled())
        m_neqs_extra = 4;
    else
        m_neqs_extra = 3;

    neq_ = m_nsp + m_neqs_extra;
    //cout << "neq " << neq_ << endl;
    for (const auto s_ph : m_surf_phases) {
        neq_ += s_ph->nSpecies();
    }

    m_W.resize(m_nsp);
    m_gas->getMolecularWeights(m_W.data());
    m_wdot.resize(m_nsp);
    fill(m_wdot.begin(), m_wdot.end(), 0.0);
    m_sdot.resize(m_nsp);
    fill(m_sdot.begin(), m_sdot.end(), 0.0);

    //m_var.resize(neq_ - m_neqs_extra);
    //m_var = m_gas->speciesNames();
    //m_var.resize(neq_);
    m_var.clear();
    m_var.push_back("Velocity(m/s)");
    m_var.push_back("Density");
    m_var.push_back("Pressure(Pa)");
    if (energyEnabled())
        m_var.push_back("Temperature(K)");
    auto sp_names = m_gas->speciesNames();
    for (auto i = 0; i < sp_names.size(); i++){
        m_var.push_back(sp_names[i]);
    }
    for (const auto s_ph : m_surf_phases) {
        auto sp_nm = s_ph->speciesNames();
        for (auto i = 0; i < sp_nm.size(); i++){
            m_var.push_back(sp_nm[i]);
        }
    }

    m_T0 = gas->temperature();
    m_P0 = gas->pressure();
    cout << setw(16) << "External heat supplied: " << getHeat(m_T0) << endl;
}

void PFR1d::reinit()
{

    m_rho_ref = m_gas->density();

    cout << boolalpha << "Energy enabled? " << energyEnabled() << endl;
    m_nsp = m_gas->nSpecies();
    if (energyEnabled())
        m_neqs_extra = 4;
    else
        m_neqs_extra = 3;
    //cout << "m_neqs_extra: " << m_neqs_extra << endl;

    neq_ = m_nsp + m_neqs_extra;
    //cout << "neq " << neq_ << endl;
    for (const auto s_ph : m_surf_phases) {
        neq_ += s_ph->nSpecies();
    }
    //cout << "neq " << neq_ << endl;

    m_W.resize(m_nsp);
    m_gas->getMolecularWeights(m_W.data());
    m_wdot.resize(m_nsp);
    fill(m_wdot.begin(), m_wdot.end(), 0.0);
    m_sdot.resize(m_nsp);
    fill(m_sdot.begin(), m_sdot.end(), 0.0);

    //m_var.resize(neq_);
    m_var.clear();
    m_var.push_back("Velocity(m/s)");
    m_var.push_back("Density");
    m_var.push_back("Pressure(Pa)");
    if (energyEnabled())
        m_var.push_back("Temperature(K)");
    auto sp_names = m_gas->speciesNames();
    //m_var.insert(m_var.end(), sp_names.begin(), sp_names.end());
    for (auto i = 0; i < sp_names.size(); i++){
        m_var.push_back(sp_names[i]);
    }
    for (const auto s_ph : m_surf_phases) {
        auto sp_nm = s_ph->speciesNames();
        for (auto i = 0; i < sp_nm.size(); i++){
            m_var.push_back(sp_nm[i]);
        }
    }

    m_T0 = m_gas->temperature();
    m_P0 = m_gas->pressure();
}

void PFR1d::setConstraints()
{
    cout << boolalpha << "Constraints Enabled: " << true << endl;
    constrain(0, c_GT_ZERO);
    constrain(1, c_GT_ZERO);
    constrain(2, c_GT_ZERO);
    if (energyEnabled()) {
        constrain(3, c_GT_ZERO);
    }
    for (size_t i = 0; i < m_nsp; i++){
        auto k = i + m_neqs_extra;
        constrain(k, c_GE_ZERO);
    }
    auto loc = m_nsp + m_neqs_extra;
    for (const auto s_ph : m_surf_phases) {
        auto nSpecies = s_ph->nSpecies();
        for (size_t i = 0; i < nSpecies; i++){
            auto k = i + loc;
            constrain(k, c_GE_ZERO);
        }
        loc += nSpecies;
    }
}

int PFR1d::getInitialConditions(const double t0,
                                double *const y,
                                double *const ydot)
{
    //const double P0 = m_gas->pressure();
    const double rho0 = m_gas->density();
    const double Wavg = m_gas->meanMolecularWeight();
    const double RT = m_T0 * GasConstant;
    //const double Rrho = rho0 * GasConstant;

    Eigen::MatrixXd A = Eigen::MatrixXd::Zero(m_nsp + m_neqs_extra,
                                              m_nsp + m_neqs_extra);
    Eigen::VectorXd b = Eigen::VectorXd::Zero(m_nsp + m_neqs_extra);

    y[0] = m_u0;
    y[1] = rho0;
    y[2] = m_P0;
    /*
    cout << 0 << " y " << y[0] << endl
         << 1 << " y " << y[1] << endl
         << 2 << " y " << y[2] << endl;*/
    //int gas_start_loc;
    if (energyEnabled()) {
        y[3] = m_T0;
        //cout << 3 << " y " << y[3] << endl;
    }

    m_gas->getMassFractions(y + m_neqs_extra);
    /*
    for (size_t i = 0; i < m_nsp; i++){
        cout << i << " y " << y[i + m_neqs_extra] << endl;
    }*/

    auto loc = m_nsp + m_neqs_extra;
    for (const auto s_ph : m_surf_phases) {
        s_ph->getCoverages(y + loc);
        loc += s_ph->nSpecies();
    }

    // Continuity equation elements.
    A(0, 0) = rho0;           // u'
    A(0, 1) = m_u0;           // rho'
    A(0, 2) = 0;              // p'
    if (energyEnabled())
        A(0, 3) = 0;          // T'
    for (unsigned k = m_neqs_extra; k < m_nsp + m_neqs_extra; ++k)
        A(0, k) = 0.0;

    // Momentum equation elements.
    A(1, 0) = 2* rho0 * m_u0; // u'
    A(1, 1) = m_u0 * m_u0;    // rho'
    A(1, 2) = 1;              // p'
    if (energyEnabled())
        A(1, 3) = 0;          // T'
    for (unsigned k = m_neqs_extra; k < m_nsp + m_neqs_extra; ++k)
        A(1, k) = 0.0;

    // State equation elements.
    A(2, 0) = 0;              // u'
    A(2, 1) = RT;             // rho'
    A(2, 2) = -Wavg;          // p'
    if (energyEnabled())
        //A(2, 3) = Rrho;       // T'   // This is the source of the difference
        A(2, 3) = 0;       // T'   // This is the source of the difference
    // Yk' for other equations, exceptionally here!
    for (unsigned k = m_neqs_extra; k < m_nsp + m_neqs_extra; ++k)
        A(2, k) = m_P0 * Wavg * Wavg / m_W[k - m_neqs_extra];

    double mdot_surf = evalSurfaces();
    m_gas->getNetProductionRates(&m_wdot[0]);

    if (energyEnabled()) {
        //vector<double> cpr_k(m_nsp);
        //m_gas->getCp_R(cpr_k.data());
        //double cpr = 0.0;
        //for (size_t i = 0; i < m_nsp; i++){
            //cpr += cpr_k[i] * y[i + m_neqs_extra];
        //}
        //cout << "cp" << cpr*GasConstant << endl;
        auto cp = m_gas->cp_mass();

        A(3,0) = 0;            // u'
        A(3,1) = 0;            // rho'
        A(3,2) = 0;            // p'
        A(3,3) = rho0 * m_u0 * cp;// * GasConstant;            // T'
        //cout << "A(3,3) " << A(3, 3) << endl;

        vector<double> H_rt(m_nsp);
        m_gas->getEnthalpy_RT(H_rt.data());
        b(3) = 0;                          // RHS energy
        for (size_t i = 0; i < m_nsp; i++){
            //b(3) -= (m_wdot[i] + m_sdot[i] * m_cat_abyv) * m_W[i] * cpr_k[i];
            b(3) -= (m_wdot[i] + m_sdot[i] * m_cat_abyv) *  H_rt[i];
            /*cout << i << " msdot " << m_sdot[i] << endl
                 //<< i << " m_W " << m_W[i] << endl
                 << i << " H_rt " <<  H_rt[i] << endl;
            cout << "Running b(3) " << b(3) << endl;
            cout << m_cat_abyv << endl;
            */
        }

        //cout << "b(3) before multiplication : " << b(3) << endl;
        //b(3) *= m_T0;
        b(3) *= RT;
        //cout << "b(3) after multiplication : " << b(3) << endl;
        b(3) += getHeat(m_T0);///GasConstant;
        //cout << "get heat: " << getHeat(m_T0) << endl;
        //cout << "b(3): " << b(3) << endl;
    }

    b(0) = m_cat_abyv * mdot_surf;          // RHS continuity
    //cout << "b(0): " << b(0) << endl;
    b(1) = 0;                               // RHS momentum
    b(2) = 0;                               // RHS state
    //if (energyEnabled()){
    //}

    // Gas phase species
    for (unsigned k = m_neqs_extra; k < m_nsp + m_neqs_extra; ++k)
    {
        //cout << " y[k] " << y[k] << endl;
        auto i = k - m_neqs_extra;
        // For species equations.
        A(k, k) = rho0 * m_u0;
        b(k) = (m_wdot[i] + m_sdot[i] * m_cat_abyv) * m_W[i] -
               y[k] * mdot_surf * m_cat_abyv;
        //cout << k << " b(k) " << b(k) << endl;

    }

    Eigen::VectorXd x = A.fullPivLu().solve(b);
    Eigen::VectorXd::Map(ydot, x.rows()) = x;
    /*
    for (size_t i = 0; i < neq_; i++)
        cout << "i " << i << " ydot[i] " << ydot[i] << endl;*/

    return 0;
}

int PFR1d::evalResidNJ(const double t, const double delta_t,
                       const double* const y, const double* const ydot,
                       double* const resid,
                       const Cantera::ResidEval_Type_Enum evalType,
                       const int id_x, const double delta_x)
{
    applySensitivity();//m_sens_params.data());
    const double u = y[0];       // Flow rate
    const double r = y[1];       // Density
    const double p = y[2];       // Pressure
    const double temp = energyEnabled() ? y[3] : getT(t);   // Temperature
    double RT = GasConstant * temp;

    const double dudz = ydot[0];
    const double drdz = ydot[1];
    const double dpdz = ydot[2];
    double dtempdz = energyEnabled() ? ydot[3] : 0;

    m_gas->setMassFractions_NoNorm(y + m_neqs_extra);
    m_gas->setState_TP(temp, p);

    auto loc = m_neqs_extra + m_nsp;
    for (auto s_ph : m_surf_phases) {
        s_ph->setState_TP(temp, p);
        s_ph->setCoveragesNoNorm(y + loc);
        loc += s_ph->nSpecies();
    }

    // Get species production rates
    m_gas->getNetProductionRates(&m_wdot[0]);
    //double mdot_surf = s();
    vector_fp work(m_nsp);
    fill(m_sdot.begin(), m_sdot.end(), 0.0);
    double mdot_surf = 0.0; // net mass flux from surface

    loc = m_nsp;
    for (size_t i = 0; i < m_surf_phases.size(); i++) {
        double cov_sum = 0.0;
        InterfaceKinetics* kin = m_surf_kins[i];
        SurfPhase* surf = m_surf_phases[i];
        work.resize(kin->nTotalSpecies());

        kin->getNetProductionRates(work.data());
        for (size_t k = m_nsp + 1; k < kin->nTotalSpecies() - 1; k++){ // Gas + bulk + surface species
            resid[m_neqs_extra + loc + k - m_nsp - 1] = work[k];
            cov_sum += y[loc + m_neqs_extra + k - m_nsp -1];
        }
        loc += surf->nSpecies();
        cov_sum += y[loc + m_neqs_extra - 1];
        resid[loc + m_neqs_extra - 1] = 1.0 - cov_sum;

        for (size_t k = 0; k < m_nsp; k++) {
            m_sdot[k] += work[k];
            mdot_surf += m_sdot[k] * m_W[k];
        }
    }

    resid[0] = u * drdz + r * dudz - m_cat_abyv * mdot_surf;
    resid[1] = 2 * r * u * dudz + u *u * drdz + dpdz;
    resid[2] = m_gas->density() - r;  // Density is set as pW/RT

    if (energyEnabled()){
        /*
        vector<double> cpr(m_nsp);
        m_gas->getCp_R(cpr.data());
        double cp;
        for (size_t i = 0; i < m_nsp; i++){
            cp += cpr[i] * y[i + m_neqs_extra];
        }
        */
        auto cp = m_gas->cp_mass();
        resid[3] = cp *  r * u * dtempdz;

        double h_term  = 0;
        vector<double> H_rt(m_nsp);
        m_gas->getEnthalpy_RT(H_rt.data());
        for (size_t i = 0; i < m_nsp; i++){
            //h_term += (m_wdot[i] + m_sdot[i] * m_cat_abyv) * m_W[i] * cpr[i];
            h_term += (m_wdot[i] + m_sdot[i] * m_cat_abyv)  * H_rt[i];
        }
        //resid[3] += h_term * temp;
        resid[3] += h_term * RT;
        resid[3] -= getHeat(temp);///GasConstant;
    }

    for (unsigned k = 0; k < m_nsp; ++k)
    {
        auto k1 = k + m_neqs_extra;
        resid[k1] = u * r * ydot[k1] + y[k1] * mdot_surf * m_cat_abyv -
                    (m_wdot[k] + m_sdot[k] * m_cat_abyv) * m_W[k];
    }

    resetSensitivity();
    return 0;
}

// This function is used to compute Fisher Information Matrix
// using all the reactions participating in the kinetics
int PFR1d::evalQuadRhs(const double t, const double* const y,
                       const double* const ydot, double* const rhsQ)
{
    size_t loc = 0;
    m_gas->getNetRatesOfProgress(rhsQ);
    loc += m_gas->nReactions();

    for (auto kin : m_surf_kins) {
        kin->getNetRatesOfProgress(rhsQ + loc);
        loc += kin->nReactions();
    }
    return 0;
}

/*
void PFR1d::setTProfile(map<double, double> T_profile)
{
    if (T_profile.begin()->first > 0)
        m_T_profile_iind = -1;
    else
        m_T_profile_iind = 0;

    m_T_profile.resize(T_profile.size());
    m_T_profile_ind.resize(T_profile.size());
    auto i = 0;
    for (const auto& dist_Tprof : T_profile) {
        m_T_profile_ind[i] = dist_Tprof.first;
        m_T_profile[i++] = dist_Tprof.second;
    }
}

double PFR1d::getT(double z)
{
    if (!m_T_profile.size()){
        return m_T0;
    }

    if (m_T_profile_iind >= 0 && z == m_T_profile_ind[m_T_profile_iind])
        return  m_T_profile[m_T_profile_iind];
    if (m_T_profile_iind < m_T_profile_ind.size() - 1  &&
        z == m_T_profile_ind[m_T_profile_iind + 1])
        return  m_T_profile[m_T_profile_iind + 1];
    if (m_T_profile_iind == m_T_profile_ind.size() - 1 &&
        z > m_T_profile_ind[m_T_profile_iind])
        return m_T_profile[m_T_profile_iind];

    if (m_T_profile_iind < m_T_profile_ind.size() - 1 &&
        z > m_T_profile_ind[m_T_profile_iind + 1]){
        m_T_profile_iind += 1;
    }

    double lz, lT, hz, hT;
    if (m_T_profile_iind < 0) {
        lz = 0;
        lT = m_T0;
    } else {
        lz = m_T_profile_ind[m_T_profile_iind];
        lT = m_T_profile[m_T_profile_iind];
    }
    hz = m_T_profile_ind[m_T_profile_iind + 1];
    hT = m_T_profile[m_T_profile_iind + 1];

    return lT + (hT - lT) / (hz - lz) * (z - lz);
}
*/

/* Even this complexity is not needed
void PFR1d::setTProfile(const map<double, double>& T_profile)
{
    // The profile given as map in the input is stored two
    // vectors for boost library interpolators
    // Clear the existing profile
    if (m_T_profile_ind.size())
        m_T_profile_ind.clear();
    if (m_T_profile.size())
        m_T_profile.clear();

    // If T @ z=0 is not given, use inlet T
    if (T_profile.begin()->first > 0){
        m_T_profile_ind.push_back(0);
        m_T_profile.push_back(m_T0);
    }
    for (const auto& dist_Tprof : T_profile) {
        m_T_profile_ind.push_back(dist_Tprof.first);
        m_T_profile.push_back(dist_Tprof.second);
    }

    m_T_interp = make_shared<boost::math::barycentric_rational<double>>(m_T_profile_ind, m_T_profile, m_T_profile.size());
}
*/

void PFR1d::setTProfile(const map<double, double>& T_profile)
{
    /*
    // The profile given as map in the input is temporarily stored into two
    // vectors for boost library interpolators
    // Clear the existing profile
    vector<double> Ts, Zs;

    // If T @ z=0 is not given, use inlet T
    if (T_profile.begin()->first > 0){
        Zs.push_back(0);
        Ts.push_back(m_T0);
    }
    for (const auto& dist_Tprof : T_profile) {
        Zs.push_back(dist_Tprof.first);
        Ts.push_back(dist_Tprof.second);
    }
    */
    auto Zs = boost::adaptors::keys(T_profile);
    auto Ts = boost::adaptors::values(T_profile);

    //if (m_T_interp != nullptr){
    //    m_T_interp = nullptr;
        m_T_interp = make_shared<boost::math::barycentric_rational<double>>(
                Zs.begin(), Zs.end(), Ts.begin());
    //}
}

double PFR1d::getT(double z)
{
    if (m_T_interp == nullptr){
        return m_T0;
    } else{
        return m_T_interp->operator()(z);
    }
}

double PFR1d::evalSurfaces()
{
    vector_fp work(m_nsp);
    fill(m_sdot.begin(), m_sdot.end(), 0.0);
    double mdot_surf = 0.0; // net mass flux from surface

    for (size_t i = 0; i < m_surf_phases.size(); i++) {
        InterfaceKinetics* kin = m_surf_kins[i];
        //SurfPhase* surf = m_surf_phases[i];
        work.resize(kin->nTotalSpecies());
        fill(work.begin(), work.end(), 0.0);

        kin->getNetProductionRates(work.data());

        for (size_t k = 0; k < m_nsp; k++) {
            m_sdot[k] += work[k];
            mdot_surf += m_sdot[k] * m_W[k];
        }
    }

    return mdot_surf;
}

void PFR1d::getSurfaceProductionRates(double* y)
{
    for (size_t i=0; i < m_nsp; i++) {
        y[i] = m_sdot[i];
    }
}

void PFR1d::getSurfaceInitialConditions(double* y)
{
    size_t loc = 0;
    for (const auto s_ph : m_surf_phases) {
        s_ph->getCoverages(y+loc);
        loc += s_ph->nSpecies();
    }
}

void PFR1d::setHeatTransfer(double htc, double Text, double wall_abyv)
{
    m_heat = true;
    m_htc = htc;
    m_Text = Text;
    m_surf_ext_abyv = wall_abyv;
}

double PFR1d::getHeat(double Tint) const
{
    if (m_heat)
        return m_htc * (m_Text - Tint) * m_surf_ext_abyv;
    else
        return 0;

}

void PFR1d::addSensitivityReaction(std::string& rxn_id)
{
    // Find the kinetics to which the reaction id belongs to
    // Start with GasKinetics
    int kin_no = -1, rxn_no = -1;
    cout << "rxn id " << rxn_id << " added to sensitivity list " << endl;
    for (size_t i = 0; i < m_gas->nReactions(); i++){
        if (m_gas->reaction(i)->id == rxn_id){
            kin_no = 0;
            rxn_no = i;
            break;
        }
    }
    if (kin_no < 0){
        for (size_t j = 0; j < m_surf_phases.size(); j++) {
            auto kin = m_surf_kins[j];
            for (size_t i = 0; i < kin->nReactions(); i++){
                if (kin->reaction(i)->id == rxn_id){
                    kin_no = j + 1;
                    rxn_no = i;
                    break;
                }
            }
            if (kin_no > 0)
                break;
        }
    }
    if (kin_no < 0){
        throw CanteraError("PFR1d::addSensitivityReaction",
                           "Reaction ({}) not found ", rxn_id);
    }
    addSensitivityReaction(kin_no, rxn_no);
    /*
     *
    if (!m_chem || rxn >= m_kin->nReactions()) {
    }

    size_t p = network().registerSensitivityParameter(
        name()+": "+m_kin->reactionString(rxn), 1.0, 1.0);
    m_sensParams.emplace_back(
        SensitivityParameter{rxn, p, 1.0, SensParameterType::reaction});
        */
}

void PFR1d::addSensitivityReaction(size_t kin_ind, size_t rxn)
{
    Kinetics* kin = nullptr;
    if (!kin_ind) {
        kin = m_gas;
    } else {
        kin = m_surf_kins[kin_ind-1];
    }

    //Chemistry enabling option not added to PFR
    //if (!m_chem || rxn >= kin->nReactions()) {
    if (rxn >= kin->nReactions()) {
        throw CanteraError("Reactor::addSensitivityReaction",
                           "Reaction number out of range ({})", rxn);
    }

    m_paramNames.push_back(kin->reactionString(rxn));
    m_sens_params.push_back(1.0);
    m_paramScales.push_back(1.0);

    if (kin_ind >= m_sensParams.size()){
        for (size_t i = 0; i <= kin_ind - m_sensParams.size() + 1; i++){
            vector<SensitivityParameter> sensParams;
            m_sensParams.emplace_back(sensParams);
        }
    }
    //vector<SensitivityParameter> curr_sensParams = m_sensParams[kin_ind];
    m_sensParams[kin_ind].emplace_back(
            SensitivityParameter{rxn, m_sens_params.size()-1, 1.0,
                                 SensParameterType::reaction});

    /*
    size_t p = network().registerSensitivityParameter(
        name()+": "+m_kin->reactionString(rxn), 1.0, 1.0);
    m_sensParams.emplace_back(
        SensitivityParameter{rxn, p, 1.0, SensParameterType::reaction});
    */
}

void PFR1d::addSensitivitySpecies(std::string& species_name)
{
    // Find the phase to which the species belongs to
    // Start with gas phase
    int ph_no = -1;
    cout << "species " << species_name << " added to sensitivity list " << endl;
    auto sp_ind = m_gas->speciesIndex(species_name);
    if (sp_ind < m_gas->nSpecies()){// Found in gas phase
        ph_no = 0;
        addSensitivitySpeciesEnthalpy(ph_no, sp_ind);
    } else {
        for (size_t j = 0; j < m_surf_phases.size(); j++) {
            auto phase = m_surf_phases[j];
            sp_ind = phase->speciesIndex(species_name);
            if (sp_ind < phase->nSpecies()){
                ph_no = j+1;
                addSensitivitySpeciesEnthalpy(ph_no, sp_ind);
                break;
            }
        }
    }
    if (ph_no < 0){
        throw CanteraError("PFR1d::addSensitivitySpecies",
                           "Species {} not found ", species_name);
    }
}

void PFR1d::addSensitivitySpeciesEnthalpy(size_t ph_ind, size_t sp_ind)
{
    ThermoPhase* ph = nullptr;
    if (!ph_ind) {
        ph = m_gas;
    } else {
        ph = m_surf_phases[ph_ind-1];
    }

    //Chemistry enabling option not added to PFR
    if (sp_ind >= ph->nSpecies()) {
        throw CanteraError("Reactor::addSensitivitySpeciesEnthalpy",
                           "Species number out of range ({})", sp_ind);
    }

    m_paramNames.push_back(ph->speciesName(sp_ind));
    m_sens_params.push_back(0.0);
    m_paramScales.push_back(GasConstant * 298.15);

    if (ph_ind >= m_sensParams.size()){
        for (size_t i = 0; i <= ph_ind - m_sensParams.size() + 1; i++){
            vector<SensitivityParameter> sensParams;
            m_sensParams.emplace_back(sensParams);
        }
    }
    //vector<SensitivityParameter> curr_sensParams = m_sensParams[kin_ind];
    m_sensParams[ph_ind].emplace_back(
            SensitivityParameter{sp_ind, m_sens_params.size()-1,
                                 ph->Hf298SS(sp_ind),
                                 SensParameterType::enthalpy});

    /*
    size_t p = network().registerSensitivityParameter(
        name()+": "+m_kin->reactionString(rxn), 1.0, 1.0);
    m_sensParams.emplace_back(
        SensitivityParameter{rxn, p, 1.0, SensParameterType::reaction});
    */
}

/*
void PFR1d::addSensitivitySpeciesEnthalpy(size_t k)
{
    if (k >= m_thermo->nSpecies()) {
        throw CanteraError("Reactor::addSensitivitySpeciesEnthalpy",
                           "Species index out of range ({})", k);
    }

    size_t p = network().registerSensitivityParameter(
        name() + ": " + m_thermo->speciesName(k) + " enthalpy",
        0.0, GasConstant * 298.15);
    m_sensParams.emplace_back(
        SensitivityParameter{k, p, m_thermo->Hf298SS(k),
                             SensParameterType::enthalpy});
}
*/

size_t PFR1d::nSensParams()
{
    return m_sensParams.size();
}

//void PFR1d::applySensitivity(double* params)
void PFR1d::applySensitivity()
{
    if (!nparams()) {
        return;
    }

    for (auto& p : m_sensParams[0]) {
        if (p.type == SensParameterType::reaction) {
            p.value = m_gas->multiplier(p.local);
            //double bias = ((params[p.global] == 1.0) ? 0.0 : 0.05);
            //double bias = 0.0 ;
            //m_gas->setMultiplier(p.local, p.value * (params[p.global] + bias));
            //m_gas->setMultiplier(p.local, p.value * params[p.global]);
            m_gas->setMultiplier(p.local, p.value * sensitivityParameter(p.global));
        } else if (p.type == SensParameterType::enthalpy) {
            m_gas->modifyOneHf298SS(p.local, p.value + sensitivityParameter(p.global));
            //m_gas->modifyOneHf298SS(p.local, p.value + params[p.global]);
        }
    }

    for (size_t i = 0; i < m_surf_kins.size(); i++){
        for (auto& p : m_sensParams[i+1]) {
            if (p.type == SensParameterType::reaction) {
                p.value = m_surf_kins[i]->multiplier(p.local);
                //double bias = ((params[p.global] == 1.0) ? 0.0 : 0.05);
                //double bias = 0.0 ;
                //m_surf_kins[i]->setMultiplier(p.local, p.value * (params[p.global] + bias));
                //m_surf_kins[i]->setMultiplier(p.local, p.value * params[p.global]);
                m_surf_kins[i]->setMultiplier(p.local, p.value * sensitivityParameter(p.global));
            } else if (p.type == SensParameterType::enthalpy) {
                m_gas->modifyOneHf298SS(p.local, p.value + sensitivityParameter(p.global));
            }
        }
    }

    (dynamic_cast<Phase *>(m_gas))->invalidateCache();
    //if (m_gas) {
        (dynamic_cast<Kinetics *>(m_gas))->invalidateCache();
    //}
}

void PFR1d::resetSensitivity()
{
    if (!nparams()) {
        return;
    }
    for (auto& p : m_sensParams[0]) {
        if (p.type == SensParameterType::reaction) {
            m_gas->setMultiplier(p.local, p.value);
        } else if (p.type == SensParameterType::enthalpy) {
            m_gas->resetHf298(p.local);
        }
    }

    for (size_t i = 0; i < m_surf_kins.size(); i++){
        for (auto& p : m_sensParams[i+1]) {
            m_surf_kins[i]->setMultiplier(p.local, p.value);
        }
    }

    (dynamic_cast<Phase *>(m_gas))->invalidateCache();
    //if (m_gas) {
        (dynamic_cast<Kinetics *>(m_gas))->invalidateCache();
    //}
}

}


// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY 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 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY 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 FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

// File generated at Thu 10 May 2018 14:30:07

/**
 * @file MSSMatMGUT_mAmu_mass_eigenstates.hpp
 *
 * @brief contains class for model with routines needed to solve boundary
 *        value problem using the two_scale solver by solving EWSB
 *        and determine the pole masses and mixings
 *
 * This file was generated at Thu 10 May 2018 14:30:07 with FlexibleSUSY
 * 2.0.1 (git commit: unknown) and SARAH 4.12.2 .
 */

#ifndef MSSMatMGUT_mAmu_MASS_EIGENSTATES_H
#define MSSMatMGUT_mAmu_MASS_EIGENSTATES_H

#include "MSSMatMGUT_mAmu_info.hpp"
#include "MSSMatMGUT_mAmu_physical.hpp"
#include "MSSMatMGUT_mAmu_soft_parameters.hpp"
#include "loop_corrections.hpp"
#include "threshold_corrections.hpp"
#include "error.hpp"
#include "problems.hpp"
#include "config.h"

#include <iosfwd>
#include <memory>
#include <string>

#include <Eigen/Core>

namespace flexiblesusy {

class MSSMatMGUT_mAmu_ewsb_solver_interface;
/**
 * @class MSSMatMGUT_mAmu_mass_eigenstates
 * @brief model class with routines for determing masses and mixinga and EWSB
 */
class MSSMatMGUT_mAmu_mass_eigenstates : public MSSMatMGUT_mAmu_soft_parameters {
public:
   explicit MSSMatMGUT_mAmu_mass_eigenstates(const MSSMatMGUT_mAmu_input_parameters& input_ = MSSMatMGUT_mAmu_input_parameters());
   MSSMatMGUT_mAmu_mass_eigenstates(const MSSMatMGUT_mAmu_mass_eigenstates&) = default;
   MSSMatMGUT_mAmu_mass_eigenstates(MSSMatMGUT_mAmu_mass_eigenstates&&) = default;
   virtual ~MSSMatMGUT_mAmu_mass_eigenstates() = default;
   MSSMatMGUT_mAmu_mass_eigenstates& operator=(const MSSMatMGUT_mAmu_mass_eigenstates&) = default;
   MSSMatMGUT_mAmu_mass_eigenstates& operator=(MSSMatMGUT_mAmu_mass_eigenstates&&) = default;

   /// number of EWSB equations
   static const int number_of_ewsb_equations = 2;

   void calculate_DRbar_masses();
   void calculate_pole_masses();
   void check_pole_masses_for_tachyons();
   virtual void clear() override;
   void clear_DRbar_parameters();
   Eigen::ArrayXd get_DRbar_masses() const;
   Eigen::ArrayXd get_DRbar_masses_and_mixings() const;
   Eigen::ArrayXd get_extra_parameters() const;
   void do_calculate_sm_pole_masses(bool);
   bool do_calculate_sm_pole_masses() const;
   void do_calculate_bsm_pole_masses(bool);
   bool do_calculate_bsm_pole_masses() const;
   void do_force_output(bool);
   bool do_force_output() const;
   void reorder_DRbar_masses();
   void reorder_pole_masses();
   void set_ewsb_iteration_precision(double);
   void set_ewsb_loop_order(int);
   void set_loop_corrections(const Loop_corrections&);
   const Loop_corrections& get_loop_corrections() const;
   void set_threshold_corrections(const Threshold_corrections&);
   const Threshold_corrections& get_threshold_corrections() const;
   void set_DRbar_masses(const Eigen::ArrayXd&);
   void set_DRbar_masses_and_mixings(const Eigen::ArrayXd&);
   void set_extra_parameters(const Eigen::ArrayXd&);
   void set_pole_mass_loop_order(int);
   int get_pole_mass_loop_order() const;
   void set_physical(const MSSMatMGUT_mAmu_physical&);
   double get_ewsb_iteration_precision() const;
   double get_ewsb_loop_order() const;
   const MSSMatMGUT_mAmu_physical& get_physical() const;
   MSSMatMGUT_mAmu_physical& get_physical();
   const Problems& get_problems() const;
   Problems& get_problems();
   void set_ewsb_solver(const std::shared_ptr<MSSMatMGUT_mAmu_ewsb_solver_interface>&);
   int solve_ewsb_tree_level();
   int solve_ewsb_one_loop();
   int solve_ewsb();            ///< solve EWSB at ewsb_loop_order level

   void calculate_spectrum();
   void clear_problems();
   std::string name() const;
   void run_to(double scale, double eps = -1.0) override;
   void print(std::ostream& out = std::cerr) const override;
   void set_precision(double);
   double get_precision() const;

   double get_lsp(MSSMatMGUT_mAmu_info::Particles&) const;

   double get_MVG() const { return MVG; }
   double get_MGlu() const { return MGlu; }
   const Eigen::Array<double,3,1>& get_MFv() const { return MFv; }
   double get_MFv(int i) const { return MFv(i); }
   const Eigen::Array<double,6,1>& get_MSd() const { return MSd; }
   double get_MSd(int i) const { return MSd(i); }
   const Eigen::Array<double,3,1>& get_MSv() const { return MSv; }
   double get_MSv(int i) const { return MSv(i); }
   const Eigen::Array<double,6,1>& get_MSu() const { return MSu; }
   double get_MSu(int i) const { return MSu(i); }
   const Eigen::Array<double,6,1>& get_MSe() const { return MSe; }
   double get_MSe(int i) const { return MSe(i); }
   const Eigen::Array<double,2,1>& get_Mhh() const { return Mhh; }
   double get_Mhh(int i) const { return Mhh(i); }
   const Eigen::Array<double,2,1>& get_MAh() const { return MAh; }
   double get_MAh(int i) const { return MAh(i); }
   const Eigen::Array<double,2,1>& get_MHpm() const { return MHpm; }
   double get_MHpm(int i) const { return MHpm(i); }
   const Eigen::Array<double,4,1>& get_MChi() const { return MChi; }
   double get_MChi(int i) const { return MChi(i); }
   const Eigen::Array<double,2,1>& get_MCha() const { return MCha; }
   double get_MCha(int i) const { return MCha(i); }
   const Eigen::Array<double,3,1>& get_MFe() const { return MFe; }
   double get_MFe(int i) const { return MFe(i); }
   const Eigen::Array<double,3,1>& get_MFd() const { return MFd; }
   double get_MFd(int i) const { return MFd(i); }
   const Eigen::Array<double,3,1>& get_MFu() const { return MFu; }
   double get_MFu(int i) const { return MFu(i); }
   double get_MVWm() const { return MVWm; }
   double get_MVP() const { return MVP; }
   double get_MVZ() const { return MVZ; }

   Eigen::Array<double,1,1> get_MChargedHiggs() const;

   Eigen::Array<double,1,1> get_MPseudoscalarHiggs() const;

   const Eigen::Matrix<double,6,6>& get_ZD() const { return ZD; }
   double get_ZD(int i, int k) const { return ZD(i,k); }
   const Eigen::Matrix<double,3,3>& get_ZV() const { return ZV; }
   double get_ZV(int i, int k) const { return ZV(i,k); }
   const Eigen::Matrix<double,6,6>& get_ZU() const { return ZU; }
   double get_ZU(int i, int k) const { return ZU(i,k); }
   const Eigen::Matrix<double,6,6>& get_ZE() const { return ZE; }
   double get_ZE(int i, int k) const { return ZE(i,k); }
   const Eigen::Matrix<double,2,2>& get_ZH() const { return ZH; }
   double get_ZH(int i, int k) const { return ZH(i,k); }
   const Eigen::Matrix<double,2,2>& get_ZA() const { return ZA; }
   double get_ZA(int i, int k) const { return ZA(i,k); }
   const Eigen::Matrix<double,2,2>& get_ZP() const { return ZP; }
   double get_ZP(int i, int k) const { return ZP(i,k); }
   const Eigen::Matrix<std::complex<double>,4,4>& get_ZN() const { return ZN; }
   std::complex<double> get_ZN(int i, int k) const { return ZN(i,k); }
   const Eigen::Matrix<std::complex<double>,2,2>& get_UM() const { return UM; }
   std::complex<double> get_UM(int i, int k) const { return UM(i,k); }
   const Eigen::Matrix<std::complex<double>,2,2>& get_UP() const { return UP; }
   std::complex<double> get_UP(int i, int k) const { return UP(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_ZEL() const { return ZEL; }
   std::complex<double> get_ZEL(int i, int k) const { return ZEL(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_ZER() const { return ZER; }
   std::complex<double> get_ZER(int i, int k) const { return ZER(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_ZDL() const { return ZDL; }
   std::complex<double> get_ZDL(int i, int k) const { return ZDL(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_ZDR() const { return ZDR; }
   std::complex<double> get_ZDR(int i, int k) const { return ZDR(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_ZUL() const { return ZUL; }
   std::complex<double> get_ZUL(int i, int k) const { return ZUL(i,k); }
   const Eigen::Matrix<std::complex<double>,3,3>& get_ZUR() const { return ZUR; }
   std::complex<double> get_ZUR(int i, int k) const { return ZUR(i,k); }
   const Eigen::Matrix<double,2,2>& get_ZZ() const { return ZZ; }
   double get_ZZ(int i, int k) const { return ZZ(i,k); }

   void set_PhaseGlu(std::complex<double> PhaseGlu_) { PhaseGlu = PhaseGlu_; }
   std::complex<double> get_PhaseGlu() const { return PhaseGlu; }

   double get_mass_matrix_VG() const;
   void calculate_MVG();
   double get_mass_matrix_Glu() const;
   void calculate_MGlu();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fv() const;
   void calculate_MFv();
   Eigen::Matrix<double,6,6> get_mass_matrix_Sd() const;
   void calculate_MSd();
   Eigen::Matrix<double,3,3> get_mass_matrix_Sv() const;
   void calculate_MSv();
   Eigen::Matrix<double,6,6> get_mass_matrix_Su() const;
   void calculate_MSu();
   Eigen::Matrix<double,6,6> get_mass_matrix_Se() const;
   void calculate_MSe();
   Eigen::Matrix<double,2,2> get_mass_matrix_hh() const;
   void calculate_Mhh();
   Eigen::Matrix<double,2,2> get_mass_matrix_Ah() const;
   void calculate_MAh();
   Eigen::Matrix<double,2,2> get_mass_matrix_Hpm() const;
   void calculate_MHpm();
   Eigen::Matrix<double,4,4> get_mass_matrix_Chi() const;
   void calculate_MChi();
   Eigen::Matrix<double,2,2> get_mass_matrix_Cha() const;
   void calculate_MCha();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fe() const;
   void calculate_MFe();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fd() const;
   void calculate_MFd();
   Eigen::Matrix<double,3,3> get_mass_matrix_Fu() const;
   void calculate_MFu();
   double get_mass_matrix_VWm() const;
   void calculate_MVWm();
   Eigen::Matrix<double,2,2> get_mass_matrix_VPVZ() const;
   void calculate_MVPVZ();

   double get_ewsb_eq_hh_1() const;
   double get_ewsb_eq_hh_2() const;

   std::complex<double> CpUSdconjUSdVZVZ(int gO1, int gO2) const;
   double CpUSdconjUSdconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUSdconjUSd(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CphhhhUSdconjUSd(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpHpmUSdconjHpmconjUSd(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpUSdSvconjUSdconjSv(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpChaFuconjUSdPR(int gI2, int gI1, int gO2) const;
   std::complex<double> CpChaFuconjUSdPL(int gI2, int gI1, int gO1) const;
   std::complex<double> CpChiFdconjUSdPR(int gI2, int gI1, int gO2) const;
   std::complex<double> CpChiFdconjUSdPL(int gI2, int gI1, int gO1) const;
   std::complex<double> CpUSdconjUSdconjSdSd(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUSdconjUSdconjSuSu(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUSdSeconjUSdconjSe(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpAhSdconjUSd(int gI2, int gI1, int gO2) const;
   std::complex<double> CphhSdconjUSd(int gI2, int gI1, int gO2) const;
   std::complex<double> CpHpmSuconjUSd(int gI2, int gI1, int gO2) const;
   std::complex<double> CpGluFdconjUSdPR(int gI2, int gO2) const;
   std::complex<double> CpGluFdconjUSdPL(int gI2, int gO1) const;
   std::complex<double> CpSdconjUSdVG(int gI2, int gO2) const;
   std::complex<double> CpSdconjUSdVP(int gI2, int gO2) const;
   std::complex<double> CpSdconjUSdVZ(int gI2, int gO2) const;
   std::complex<double> CpSuconjUSdVWm(int gI2, int gO2) const;
   std::complex<double> CpUSvconjUSvVZVZ(int gO1, int gO2) const;
   double CpUSvconjUSvconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUSvconjUSv(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CphhhhUSvconjUSv(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpHpmUSvconjHpmconjUSv(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpbarChaFeconjUSvPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarChaFeconjUSvPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpSeconjHpmconjUSv(int gI2, int gI1, int gO2) const;
   std::complex<double> CpSvUSvconjSvconjUSv(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CphhSvconjUSv(int gI2, int gI1, int gO2) const;
   double CpChiFvconjUSvPR(int , int , int ) const;
   std::complex<double> CpChiFvconjUSvPL(int gI2, int gI1, int gO1) const;
   std::complex<double> CpSdUSvconjSdconjUSv(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpSeUSvconjSeconjUSv(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpSuUSvconjSuconjUSv(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpSvconjUSvVZ(int gI2, int gO2) const;
   std::complex<double> CpSeconjUSvconjVWm(int gI2, int gO2) const;
   std::complex<double> CpUSuconjUSuVZVZ(int gO1, int gO2) const;
   double CpUSuconjUSuconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUSuconjUSu(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CphhhhUSuconjUSu(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpHpmUSuconjHpmconjUSu(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpbarChaFdconjUSuPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarChaFdconjUSuPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpSdconjHpmconjUSu(int gI2, int gI1, int gO2) const;
   std::complex<double> CpUSuSvconjUSuconjSv(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpChiFuconjUSuPR(int gI2, int gI1, int gO2) const;
   std::complex<double> CpChiFuconjUSuPL(int gI2, int gI1, int gO1) const;
   std::complex<double> CpSeUSuconjSeconjUSu(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpUSuconjUSuconjSdSd(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUSuconjUSuconjSuSu(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpAhSuconjUSu(int gI2, int gI1, int gO2) const;
   std::complex<double> CphhSuconjUSu(int gI2, int gI1, int gO2) const;
   std::complex<double> CpGluFuconjUSuPR(int gI2, int gO2) const;
   std::complex<double> CpGluFuconjUSuPL(int gI2, int gO1) const;
   std::complex<double> CpSdconjUSuconjVWm(int gI2, int gO2) const;
   std::complex<double> CpSuconjUSuVG(int gI2, int gO2) const;
   std::complex<double> CpSuconjUSuVP(int gI2, int gO2) const;
   std::complex<double> CpSuconjUSuVZ(int gI2, int gO2) const;
   std::complex<double> CpUSeconjUSeVZVZ(int gO1, int gO2) const;
   double CpUSeconjUSeconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUSeconjUSe(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CphhhhUSeconjUSe(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpHpmUSeconjHpmconjUSe(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpUSeSvconjUSeconjSv(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpHpmSvconjUSe(int gI2, int gI1, int gO2) const;
   double CpChaFvconjUSePR(int , int , int ) const;
   std::complex<double> CpChaFvconjUSePL(int gI2, int gI1, int gO1) const;
   std::complex<double> CpChiFeconjUSePR(int gI2, int gI1, int gO2) const;
   std::complex<double> CpChiFeconjUSePL(int gI2, int gI1, int gO1) const;
   std::complex<double> CpSdUSeconjSdconjUSe(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpSeUSeconjSeconjUSe(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpUSeSuconjUSeconjSu(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpAhSeconjUSe(int gI2, int gI1, int gO2) const;
   std::complex<double> CphhSeconjUSe(int gI2, int gI1, int gO2) const;
   std::complex<double> CpSvconjUSeVWm(int gI2, int gO2) const;
   std::complex<double> CpSeconjUSeVP(int gI2, int gO2) const;
   std::complex<double> CpSeconjUSeVZ(int gI2, int gO2) const;
   std::complex<double> CpbargWmgWmUhh(int gO1) const;
   std::complex<double> CpbargWmCgWmCUhh(int gO1) const;
   std::complex<double> CpbargZgZUhh(int gO1) const;
   std::complex<double> CpUhhVZVZ(int gO2) const;
   std::complex<double> CpUhhconjVWmVWm(int gO2) const;
   std::complex<double> CpUhhUhhVZVZ(int gO1, int gO2) const;
   std::complex<double> CpUhhUhhconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUhhUhh(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CphhhhUhhUhh(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpUhhUhhHpmconjHpm(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpAhAhUhh(int gI1, int gI2, int gO2) const;
   std::complex<double> CphhhhUhh(int gI1, int gI2, int gO2) const;
   std::complex<double> CpUhhHpmconjHpm(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarChaChaUhhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarChaChaUhhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpUhhUhhSvconjSv(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUhhSvconjSv(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFdFdUhhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFdFdUhhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpbarFeFeUhhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFeFeUhhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpbarFuFuUhhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFuFuUhhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpChiChiUhhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpChiChiUhhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpUhhUhhSdconjSd(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUhhUhhSeconjSe(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUhhUhhSuconjSu(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUhhSdconjSd(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUhhSeconjSe(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUhhSuconjSu(int gO2, int gI2, int gI1) const;
   std::complex<double> CpAhUhhVZ(int gI2, int gO2) const;
   std::complex<double> CpUhhHpmconjVWm(int gO2, int gI2) const;
   std::complex<double> CpbargWmgWmUAh(int gO1) const;
   std::complex<double> CpbargWmCgWmCUAh(int gO1) const;
   std::complex<double> CpUAhUAhVZVZ(int gO1, int gO2) const;
   std::complex<double> CpUAhUAhconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUAhUAh(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpUAhUAhhhhh(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUAhUAhHpmconjHpm(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpAhUAhhh(int gI2, int gO2, int gI1) const;
   std::complex<double> CpUAhHpmconjHpm(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarChaChaUAhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarChaChaUAhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpUAhUAhSvconjSv(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarFdFdUAhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFdFdUAhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpbarFeFeUAhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFeFeUAhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpbarFuFuUAhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFuFuUAhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpChiChiUAhPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpChiChiUAhPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpUAhUAhSdconjSd(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUAhUAhSeconjSe(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUAhUAhSuconjSu(int gO1, int gO2, int gI1, int gI2) const;
   std::complex<double> CpUAhSdconjSd(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUAhSeconjSe(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUAhSuconjSu(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUAhhhVZ(int gO2, int gI2) const;
   std::complex<double> CpUAhHpmconjVWm(int gO2, int gI2) const;
   std::complex<double> CpbargWmgZUHpm(int gO2) const;
   std::complex<double> CpbargZgWmconjUHpm(int gO1) const;
   std::complex<double> CpbargWmCgZconjUHpm(int gO1) const;
   std::complex<double> CpbargZgWmCUHpm(int gO2) const;
   std::complex<double> CpconjUHpmVPVWm(int gO2) const;
   std::complex<double> CpconjUHpmVWmVZ(int gO2) const;
   std::complex<double> CpUHpmconjUHpmVZVZ(int gO1, int gO2) const;
   std::complex<double> CpUHpmconjUHpmconjVWmVWm(int gO1, int gO2) const;
   std::complex<double> CpAhAhUHpmconjUHpm(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CphhhhUHpmconjUHpm(int gI1, int gI2, int gO1, int gO2) const;
   std::complex<double> CpHpmUHpmconjHpmconjUHpm(int gI1, int gO1, int gI2, int gO2) const;
   std::complex<double> CpAhHpmconjUHpm(int gI2, int gI1, int gO2) const;
   std::complex<double> CphhHpmconjUHpm(int gI2, int gI1, int gO2) const;
   std::complex<double> CpUHpmSvconjUHpmconjSv(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarFuFdconjUHpmPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpbarFuFdconjUHpmPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpbarFvFeconjUHpmPR(int gI1, int gI2, int gO2) const;
   double CpbarFvFeconjUHpmPL(int , int , int ) const;
   std::complex<double> CpSeconjUHpmconjSv(int gI2, int gO2, int gI1) const;
   std::complex<double> CpChiChaconjUHpmPR(int gI1, int gI2, int gO2) const;
   std::complex<double> CpChiChaconjUHpmPL(int gI1, int gI2, int gO1) const;
   std::complex<double> CpUHpmSdconjUHpmconjSd(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpUHpmSeconjUHpmconjSe(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpUHpmSuconjUHpmconjSu(int gO1, int gI1, int gO2, int gI2) const;
   std::complex<double> CpSdconjUHpmconjSu(int gI2, int gO2, int gI1) const;
   std::complex<double> CpAhconjUHpmVWm(int gI2, int gO2) const;
   std::complex<double> CphhconjUHpmVWm(int gI2, int gO2) const;
   std::complex<double> CpHpmconjUHpmVP(int gI2, int gO2) const;
   std::complex<double> CpHpmconjUHpmVZ(int gI2, int gO2) const;
   std::complex<double> CpVGVGVG() const;
   std::complex<double> CpbargGgGVG() const;
   double CpbarFdFdVGPL(int gI1, int gI2) const;
   double CpbarFdFdVGPR(int gI1, int gI2) const;
   double CpbarFuFuVGPL(int gI1, int gI2) const;
   double CpbarFuFuVGPR(int gI1, int gI2) const;
   double CpSdconjSdVGVG(int gI1, int gI2) const;
   double CpSuconjSuVGVG(int gI1, int gI2) const;
   double CpSdconjSdVG(int gI2, int gI1) const;
   double CpSuconjSuVG(int gI2, int gI1) const;
   std::complex<double> CpGluGluVGPL() const;
   std::complex<double> CpGluGluVGPR() const;
   double CpVGVGVGVG1() const;
   double CpVGVGVGVG2() const;
   double CpVGVGVGVG3() const;
   double CpbargWmgWmVP() const;
   double CpbargWmCgWmCVP() const;
   double CpconjVWmVPVWm() const;
   std::complex<double> CpHpmconjHpmVPVP(int gI1, int gI2) const;
   double CpHpmconjHpmVP(int gI2, int gI1) const;
   std::complex<double> CpbarChaChaVPPL(int gI1, int gI2) const;
   std::complex<double> CpbarChaChaVPPR(int gI1, int gI2) const;
   double CpbarFdFdVPPL(int gI1, int gI2) const;
   double CpbarFdFdVPPR(int gI1, int gI2) const;
   double CpbarFeFeVPPL(int gI1, int gI2) const;
   double CpbarFeFeVPPR(int gI1, int gI2) const;
   double CpbarFuFuVPPL(int gI1, int gI2) const;
   double CpbarFuFuVPPR(int gI1, int gI2) const;
   std::complex<double> CpSdconjSdVPVP(int gI1, int gI2) const;
   std::complex<double> CpSeconjSeVPVP(int gI1, int gI2) const;
   std::complex<double> CpSuconjSuVPVP(int gI1, int gI2) const;
   std::complex<double> CpSdconjSdVP(int gI2, int gI1) const;
   std::complex<double> CpSeconjSeVP(int gI2, int gI1) const;
   std::complex<double> CpSuconjSuVP(int gI2, int gI1) const;
   std::complex<double> CpHpmconjVWmVP(int gI2) const;
   double CpconjVWmVPVPVWm1() const;
   double CpconjVWmVPVPVWm2() const;
   double CpconjVWmVPVPVWm3() const;
   double CpbargWmgWmVZ() const;
   double CpbargWmCgWmCVZ() const;
   double CpconjVWmVWmVZ() const;
   std::complex<double> CpAhAhVZVZ(int gI1, int gI2) const;
   std::complex<double> CphhhhVZVZ(int gI1, int gI2) const;
   std::complex<double> CpHpmconjHpmVZVZ(int gI1, int gI2) const;
   std::complex<double> CpAhhhVZ(int gI2, int gI1) const;
   double CpHpmconjHpmVZ(int gI2, int gI1) const;
   std::complex<double> CpbarChaChaVZPL(int gI1, int gI2) const;
   std::complex<double> CpbarChaChaVZPR(int gI1, int gI2) const;
   std::complex<double> CpSvconjSvVZVZ(int gI1, int gI2) const;
   double CpSvconjSvVZ(int gI2, int gI1) const;
   double CpbarFdFdVZPL(int gI1, int gI2) const;
   double CpbarFdFdVZPR(int gI1, int gI2) const;
   double CpbarFeFeVZPL(int gI1, int gI2) const;
   double CpbarFeFeVZPR(int gI1, int gI2) const;
   double CpbarFuFuVZPL(int gI1, int gI2) const;
   double CpbarFuFuVZPR(int gI1, int gI2) const;
   double CpbarFvFvVZPL(int gI1, int gI2) const;
   double CpbarFvFvVZPR(int , int ) const;
   std::complex<double> CpChiChiVZPL(int gI1, int gI2) const;
   std::complex<double> CpChiChiVZPR(int gI1, int gI2) const;
   std::complex<double> CpSdconjSdVZVZ(int gI1, int gI2) const;
   std::complex<double> CpSeconjSeVZVZ(int gI1, int gI2) const;
   std::complex<double> CpSuconjSuVZVZ(int gI1, int gI2) const;
   std::complex<double> CpSdconjSdVZ(int gI2, int gI1) const;
   std::complex<double> CpSeconjSeVZ(int gI2, int gI1) const;
   std::complex<double> CpSuconjSuVZ(int gI2, int gI1) const;
   std::complex<double> CphhVZVZ(int gI2) const;
   std::complex<double> CpHpmconjVWmVZ(int gI2) const;
   double CpconjVWmVWmVZVZ1() const;
   double CpconjVWmVWmVZVZ2() const;
   double CpconjVWmVWmVZVZ3() const;
   double CpbargPgWmconjVWm() const;
   double CpbargWmCgPconjVWm() const;
   double CpbargWmCgZconjVWm() const;
   double CpbargZgWmconjVWm() const;
   std::complex<double> CpAhAhconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CphhhhconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CpHpmconjHpmconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CpAhHpmconjVWm(int gI2, int gI1) const;
   std::complex<double> CphhHpmconjVWm(int gI2, int gI1) const;
   double CpSvconjSvconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CpbarFuFdconjVWmPL(int gI1, int gI2) const;
   double CpbarFuFdconjVWmPR(int , int ) const;
   std::complex<double> CpbarFvFeconjVWmPL(int gI1, int gI2) const;
   double CpbarFvFeconjVWmPR(int , int ) const;
   std::complex<double> CpSeconjSvconjVWm(int gI2, int gI1) const;
   std::complex<double> CpChiChaconjVWmPL(int gI1, int gI2) const;
   std::complex<double> CpChiChaconjVWmPR(int gI1, int gI2) const;
   std::complex<double> CpSdconjSdconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CpSeconjSeconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CpSuconjSuconjVWmVWm(int gI1, int gI2) const;
   std::complex<double> CpSdconjSuconjVWm(int gI2, int gI1) const;
   std::complex<double> CphhconjVWmVWm(int gI2) const;
   double CpconjVWmconjVWmVWmVWm1() const;
   double CpconjVWmconjVWmVWmVWm2() const;
   double CpconjVWmconjVWmVWmVWm3() const;
   std::complex<double> CpbarChaUChiHpmPL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarChaUChiHpmPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpUChiChaconjHpmPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUChiChaconjHpmPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpChiUChihhPL(int gI2, int gO2, int gI1) const;
   std::complex<double> CpChiUChihhPR(int gI2, int gO1, int gI1) const;
   std::complex<double> CpbarChaUChiVWmPL(int gI1, int gO2) const;
   std::complex<double> CpbarChaUChiVWmPR(int gI1, int gO1) const;
   double CpbarFvUChiSvPL(int , int , int ) const;
   std::complex<double> CpbarFvUChiSvPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpUChiFvconjSvPL(int gO2, int gI2, int gI1) const;
   double CpUChiFvconjSvPR(int , int , int ) const;
   std::complex<double> CpbarFdUChiSdPL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarFdUChiSdPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpbarFeUChiSePL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarFeUChiSePR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpbarFuUChiSuPL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarFuUChiSuPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpChiUChiAhPL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpChiUChiAhPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpUChiFdconjSdPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUChiFdconjSdPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpUChiFeconjSePL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUChiFeconjSePR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpUChiFuconjSuPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpUChiFuconjSuPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpUChiChaconjVWmPR(int gO2, int gI2) const;
   std::complex<double> CpUChiChaconjVWmPL(int gO1, int gI2) const;
   std::complex<double> CpChiUChiVZPL(int gI2, int gO2) const;
   std::complex<double> CpChiUChiVZPR(int gI2, int gO1) const;
   std::complex<double> CpbarUChaChaAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarUChaChaAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarUChaChahhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUChaChahhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUChaChiHpmPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUChaChiHpmPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUChaFeconjSvPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUChaFeconjSvPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUChabarFuSdPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarUChabarFuSdPR(int gO1, int gI1, int gI2) const;
   double CpbarUChabarFvSePL(int , int , int ) const;
   std::complex<double> CpbarUChabarFvSePR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarUChaFdconjSuPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUChaFdconjSuPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUChaChaVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUChaChaVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUChaChaVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUChaChaVZPL(int gO1, int gI2) const;
   std::complex<double> CpbarUChaChiVWmPR(int gO2, int gI2) const;
   std::complex<double> CpbarUChaChiVWmPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFeFehhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFeFehhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFeFvHpmPL(int gO2, int gI2, int gI1) const;
   double CpbarUFeFvHpmPR(int , int , int ) const;
   std::complex<double> CpbarUFeChaSvPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFeChaSvPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFeFeAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarUFeFeAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarUFeChiSePL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFeChiSePR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFeFeVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFeFeVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFeFeVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFeFeVZPL(int gO1, int gI2) const;
   double CpbarUFeFvVWmPR(int , int ) const;
   double CpbarUFeFvVWmPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFdhhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFdFdhhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFdFuHpmPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFdFuHpmPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFdFdAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarUFdFdAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarUFdChaSuPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFdChaSuPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFdChiSdPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFdChiSdPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFdGluSdPL(int gO2, int gI1) const;
   std::complex<double> CpbarUFdGluSdPR(int gO1, int gI1) const;
   std::complex<double> CpbarUFdFdVGPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdVGPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFdVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFdFdVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFdFdVZPL(int gO1, int gI2) const;
   double CpbarUFdFuVWmPR(int , int ) const;
   std::complex<double> CpbarUFdFuVWmPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFdconjHpmPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFuFdconjHpmPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFuFuhhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFuFuhhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarChabarUFuSdPL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarChabarUFuSdPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarUFuFuAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarUFuChiSuPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarUFuChiSuPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarUFuGluSuPL(int gO2, int gI1) const;
   std::complex<double> CpbarUFuGluSuPR(int gO1, int gI1) const;
   double CpbarUFuFdconjVWmPR(int , int ) const;
   std::complex<double> CpbarUFuFdconjVWmPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuVGPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuVGPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuVPPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuVPPL(int gO1, int gI2) const;
   std::complex<double> CpbarUFuFuVZPR(int gO2, int gI2) const;
   std::complex<double> CpbarUFuFuVZPL(int gO1, int gI2) const;
   std::complex<double> CpbarFdGluSdPL(int gI1, int gI2) const;
   std::complex<double> CpbarFdGluSdPR(int gI1, int gI2) const;
   std::complex<double> CpbarFuGluSuPL(int gI1, int gI2) const;
   std::complex<double> CpbarFuGluSuPR(int gI1, int gI2) const;
   std::complex<double> CpGluFdconjSdPL(int gI2, int gI1) const;
   std::complex<double> CpGluFdconjSdPR(int gI2, int gI1) const;
   std::complex<double> CpGluFuconjSuPL(int gI2, int gI1) const;
   std::complex<double> CpGluFuconjSuPR(int gI2, int gI1) const;
   double CpbarFvFeconjHpmPL(int , int , int ) const;
   std::complex<double> CpbarFvFeconjHpmPR(int gO1, int gI2, int gI1) const;
   double CpbarChabarFvSePL(int , int , int ) const;
   std::complex<double> CpbarChabarFvSePR(int gI1, int gO1, int gI2) const;
   double CpbarFvChiSvPL(int , int , int ) const;
   std::complex<double> CpbarFvChiSvPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFeFehhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFeFehhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFeFvHpmPL(int gO2, int gI2, int gI1) const;
   double CpbarFeFvHpmPR(int , int , int ) const;
   std::complex<double> CpbarFeChaSvPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFeChaSvPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFeFeAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarFeFeAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarFeChiSePL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFeChiSePR(int gO1, int gI2, int gI1) const;
   double CpbarFeFvVWmPR(int , int ) const;
   std::complex<double> CpbarFeFvVWmPL(int gO1, int gI2) const;
   std::complex<double> CpbarFdFdhhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFdFdhhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFdFuHpmPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFdFuHpmPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFdFdAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarFdFdAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarFdChaSuPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFdChaSuPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFdChiSdPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFdChiSdPR(int gO1, int gI2, int gI1) const;
   double CpbarFdFuVWmPR(int , int ) const;
   std::complex<double> CpbarFdFuVWmPL(int gO1, int gI2) const;
   std::complex<double> CpbarFuFdconjHpmPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFuFdconjHpmPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarFuFuhhPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFuFuhhPR(int gO1, int gI2, int gI1) const;
   std::complex<double> CpbarChabarFuSdPL(int gI1, int gO2, int gI2) const;
   std::complex<double> CpbarChabarFuSdPR(int gI1, int gO1, int gI2) const;
   std::complex<double> CpbarFuFuAhPL(int gO2, int gI1, int gI2) const;
   std::complex<double> CpbarFuFuAhPR(int gO1, int gI1, int gI2) const;
   std::complex<double> CpbarFuChiSuPL(int gO2, int gI2, int gI1) const;
   std::complex<double> CpbarFuChiSuPR(int gO1, int gI2, int gI1) const;
   std::complex<double> self_energy_Sd_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,6,6> self_energy_Sd_1loop(double p) const;
   std::complex<double> self_energy_Sv_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Sv_1loop(double p) const;
   std::complex<double> self_energy_Su_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,6,6> self_energy_Su_1loop(double p) const;
   std::complex<double> self_energy_Se_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,6,6> self_energy_Se_1loop(double p) const;
   std::complex<double> self_energy_hh_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,2,2> self_energy_hh_1loop(double p) const;
   std::complex<double> self_energy_Ah_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,2,2> self_energy_Ah_1loop(double p) const;
   std::complex<double> self_energy_Hpm_1loop(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,2,2> self_energy_Hpm_1loop(double p) const;
   std::complex<double> self_energy_VG_1loop(double p ) const;
   std::complex<double> self_energy_VP_1loop(double p ) const;
   std::complex<double> self_energy_VZ_1loop(double p ) const;
   std::complex<double> self_energy_VWm_1loop(double p ) const;
   std::complex<double> self_energy_Chi_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,4,4> self_energy_Chi_1loop_1(double p) const;
   std::complex<double> self_energy_Chi_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,4,4> self_energy_Chi_1loop_PR(double p) const;
   std::complex<double> self_energy_Chi_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,4,4> self_energy_Chi_1loop_PL(double p) const;
   std::complex<double> self_energy_Cha_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,2,2> self_energy_Cha_1loop_1(double p) const;
   std::complex<double> self_energy_Cha_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,2,2> self_energy_Cha_1loop_PR(double p) const;
   std::complex<double> self_energy_Cha_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,2,2> self_energy_Cha_1loop_PL(double p) const;
   std::complex<double> self_energy_Fe_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_1(double p) const;
   std::complex<double> self_energy_Fe_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PR(double p) const;
   std::complex<double> self_energy_Fe_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PL(double p) const;
   std::complex<double> self_energy_Fd_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_1(double p) const;
   std::complex<double> self_energy_Fd_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PR(double p) const;
   std::complex<double> self_energy_Fd_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PL(double p) const;
   std::complex<double> self_energy_Fu_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_1(double p) const;
   std::complex<double> self_energy_Fu_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PR(double p) const;
   std::complex<double> self_energy_Fu_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PL(double p) const;
   std::complex<double> self_energy_Glu_1loop_1(double p ) const;
   std::complex<double> self_energy_Glu_1loop_PR(double p ) const;
   std::complex<double> self_energy_Glu_1loop_PL(double p ) const;
   std::complex<double> self_energy_Fv_1loop_1(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fv_1loop_1(double p) const;
   std::complex<double> self_energy_Fv_1loop_PR(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fv_1loop_PR(double p) const;
   std::complex<double> self_energy_Fv_1loop_PL(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fv_1loop_PL(double p) const;
   std::complex<double> self_energy_Fe_1loop_1_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_1_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fe_1loop_PR_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PR_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fe_1loop_PL_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fe_1loop_PL_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fd_1loop_1_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_1_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fd_1loop_PR_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PR_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fd_1loop_PL_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fd_1loop_PL_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_1_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_1_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_PR_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PR_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_PL_heavy_rotated(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PL_heavy_rotated(double p) const;
   std::complex<double> self_energy_Fu_1loop_1_heavy(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_1_heavy(double p) const;
   std::complex<double> self_energy_Fu_1loop_PR_heavy(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PR_heavy(double p) const;
   std::complex<double> self_energy_Fu_1loop_PL_heavy(double p , int gO1, int gO2) const;
   Eigen::Matrix<std::complex<double>,3,3> self_energy_Fu_1loop_PL_heavy(double p) const;
   std::complex<double> tadpole_hh_1loop(int gO1) const;

   /// calculates the tadpoles at current loop order
   void tadpole_equations(double[number_of_ewsb_equations]) const;
   /// calculates the tadpoles at current loop order
   Eigen::Matrix<double,number_of_ewsb_equations,1> tadpole_equations() const;
   /// calculates the tadpoles divided by VEVs at current loop order
   Eigen::Matrix<double,number_of_ewsb_equations,1> tadpole_equations_over_vevs() const;

   void calculate_MSu_2nd_generation(double&, double&, double&) const;
   void calculate_MSd_2nd_generation(double&, double&, double&) const;
   void calculate_MSv_2nd_generation(double&, double&, double&) const;
   void calculate_MSe_2nd_generation(double&, double&, double&) const;

   void calculate_MSu_3rd_generation(double&, double&, double&) const;
   void calculate_MSd_3rd_generation(double&, double&, double&) const;
   void calculate_MSv_3rd_generation(double&, double&, double&) const;
   void calculate_MSe_3rd_generation(double&, double&, double&) const;

   Eigen::Matrix<double,2,2> self_energy_hh_2loop() const;
   Eigen::Matrix<double,2,2> self_energy_Ah_2loop() const;

   Eigen::Matrix<double,2,1> tadpole_hh_2loop() const;

   void calculate_MVG_pole();
   void calculate_MGlu_pole();
   void calculate_MFv_pole();
   void calculate_MVP_pole();
   void calculate_MVZ_pole();
   void calculate_MSd_pole();
   void calculate_MSv_pole();
   void calculate_MSu_pole();
   void calculate_MSe_pole();
   void calculate_Mhh_pole();
   void calculate_MAh_pole();
   void calculate_MHpm_pole();
   void calculate_MChi_pole();
   void calculate_MCha_pole();
   void calculate_MFe_pole();
   void calculate_MFd_pole();
   void calculate_MFu_pole();
   void calculate_MVWm_pole();
   double calculate_MVWm_pole(double);
   double calculate_MVZ_pole(double);

   double calculate_MFv_DRbar(double, int) const;
   double calculate_MFe_DRbar(double, int) const;
   double calculate_MFu_DRbar(double, int) const;
   double calculate_MFd_DRbar(double, int) const;
   double calculate_MVP_DRbar(double);
   double calculate_MVZ_DRbar(double);
   double calculate_MVWm_DRbar(double);

   double v() const;
   double Betax() const;
   double Alpha() const;
   double ThetaW() const;

private:
   int ewsb_loop_order{2};           ///< loop order for EWSB
   int pole_mass_loop_order{2};      ///< loop order for pole masses
   bool calculate_sm_pole_masses{false};  ///< switch to calculate the pole masses of the Standard Model particles
   bool calculate_bsm_pole_masses{true};  ///< switch to calculate the pole masses of the BSM particles
   bool force_output{false};              ///< switch to force output of pole masses
   double precision{1.e-3};               ///< RG running precision
   double ewsb_iteration_precision{1.e-5};///< precision goal of EWSB solution
   MSSMatMGUT_mAmu_physical physical{}; ///< contains the pole masses and mixings
   Problems problems{MSSMatMGUT_mAmu_info::model_name,
                     &MSSMatMGUT_mAmu_info::particle_names_getter,
                     &MSSMatMGUT_mAmu_info::parameter_names_getter}; ///< problems
   Loop_corrections loop_corrections{}; ///< used pole mass corrections
   std::shared_ptr<MSSMatMGUT_mAmu_ewsb_solver_interface> ewsb_solver{};
   Threshold_corrections threshold_corrections{}; ///< used threshold corrections

   int get_number_of_ewsb_iterations() const;
   int get_number_of_mass_iterations() const;
   int solve_ewsb_tree_level_custom();
   void copy_DRbar_masses_to_pole_masses();

   // Passarino-Veltman loop functions
   double A0(double) const noexcept;
   double B0(double, double, double) const noexcept;
   double B1(double, double, double) const noexcept;
   double B00(double, double, double) const noexcept;
   double B22(double, double, double) const noexcept;
   double H0(double, double, double) const noexcept;
   double F0(double, double, double) const noexcept;
   double G0(double, double, double) const noexcept;

   // DR-bar masses
   double MVG{};
   double MGlu{};
   Eigen::Array<double,3,1> MFv{Eigen::Array<double,3,1>::Zero()};
   Eigen::Array<double,6,1> MSd{Eigen::Array<double,6,1>::Zero()};
   Eigen::Array<double,3,1> MSv{Eigen::Array<double,3,1>::Zero()};
   Eigen::Array<double,6,1> MSu{Eigen::Array<double,6,1>::Zero()};
   Eigen::Array<double,6,1> MSe{Eigen::Array<double,6,1>::Zero()};
   Eigen::Array<double,2,1> Mhh{Eigen::Array<double,2,1>::Zero()};
   Eigen::Array<double,2,1> MAh{Eigen::Array<double,2,1>::Zero()};
   Eigen::Array<double,2,1> MHpm{Eigen::Array<double,2,1>::Zero()};
   Eigen::Array<double,4,1> MChi{Eigen::Array<double,4,1>::Zero()};
   Eigen::Array<double,2,1> MCha{Eigen::Array<double,2,1>::Zero()};
   Eigen::Array<double,3,1> MFe{Eigen::Array<double,3,1>::Zero()};
   Eigen::Array<double,3,1> MFd{Eigen::Array<double,3,1>::Zero()};
   Eigen::Array<double,3,1> MFu{Eigen::Array<double,3,1>::Zero()};
   double MVWm{};
   double MVP{};
   double MVZ{};

   // DR-bar mixing matrices
   Eigen::Matrix<double,6,6> ZD{Eigen::Matrix<double,6,6>::Zero()};
   Eigen::Matrix<double,3,3> ZV{Eigen::Matrix<double,3,3>::Zero()};
   Eigen::Matrix<double,6,6> ZU{Eigen::Matrix<double,6,6>::Zero()};
   Eigen::Matrix<double,6,6> ZE{Eigen::Matrix<double,6,6>::Zero()};
   Eigen::Matrix<double,2,2> ZH{Eigen::Matrix<double,2,2>::Zero()};
   Eigen::Matrix<double,2,2> ZA{Eigen::Matrix<double,2,2>::Zero()};
   Eigen::Matrix<double,2,2> ZP{Eigen::Matrix<double,2,2>::Zero()};
   Eigen::Matrix<std::complex<double>,4,4> ZN{Eigen::Matrix<std::complex<double>,4,4>::Zero()};
   Eigen::Matrix<std::complex<double>,2,2> UM{Eigen::Matrix<std::complex<double>,2,2>::Zero()};
   Eigen::Matrix<std::complex<double>,2,2> UP{Eigen::Matrix<std::complex<double>,2,2>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> ZEL{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> ZER{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> ZDL{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> ZDR{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> ZUL{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<std::complex<double>,3,3> ZUR{Eigen::Matrix<std::complex<double>,3,3>::Zero()};
   Eigen::Matrix<double,2,2> ZZ{Eigen::Matrix<double,2,2>::Zero()};

   // phases
   std::complex<double> PhaseGlu{1.,0.};

   // extra parameters

};

std::ostream& operator<<(std::ostream&, const MSSMatMGUT_mAmu_mass_eigenstates&);

} // namespace flexiblesusy

#endif


#include <algorithm>
#include <chrono>
#include <iomanip>
#include <iostream>

#include <boost/asio/post.hpp>

#include <koinos/bigint.hpp>
#include <koinos/block_production/pow_producer.hpp>
#include <koinos/contracts/pow/pow.pb.h>
#include <koinos/crypto/elliptic.hpp>
#include <koinos/crypto/multihash.hpp>
#include <koinos/protocol/protocol.pb.h>
#include <koinos/rpc/chain/chain_rpc.pb.h>
#include <koinos/util/conversion.hpp>
#include <koinos/util/services.hpp>

const uint32_t get_difficulty_entrypoint = 1249216561;

using namespace std::chrono_literals;

namespace koinos::block_production {

namespace hashrate
{
   constexpr double terahash = 1.0e12;
   constexpr double gigahash = 1.0e9;
   constexpr double megahash = 1.0e6;
   constexpr double kilohash = 1.0e3;

   constexpr std::chrono::seconds update_interval = 2s;
}

pow_producer::pow_producer(
   crypto::private_key signing_key,
   boost::asio::io_context& main_context,
   boost::asio::io_context& production_context,
   std::shared_ptr< mq::client > rpc_client,
   int64_t production_threshold,
   uint64_t resources_lower_bound,
   uint64_t resources_upper_bound,
   uint64_t max_inclusion_attempts,
   contract_id_type pow_contract_id,
   std::size_t worker_groups ) :
      block_producer(
         signing_key,
         main_context,
         production_context,
         rpc_client,
         production_threshold,
         resources_lower_bound,
         resources_upper_bound,
         max_inclusion_attempts
      ),
      _pow_contract_id( pow_contract_id ),
      _update_timer( _main_context ),
      _error_timer( _production_context ),
      _num_worker_groups( worker_groups )
{
   constexpr uint512_t max_nonce = std::numeric_limits< uint256_t >::max();
   for ( std::size_t worker_index = 0; worker_index < _num_worker_groups; worker_index++ )
   {
      uint512_t start = max_nonce * worker_index / _num_worker_groups;
      uint512_t end   = max_nonce * ( worker_index + 1 ) / _num_worker_groups;

      _worker_groups.emplace_back( start.convert_to< uint256_t >(), end.convert_to< uint256_t >() );
      _worker_hashrate[ worker_index ].store( 0 );

      LOG(info) << "Work group " << worker_index << ": [" << start.convert_to< uint256_t >() << ", " << end.convert_to< uint256_t >() << "]";
   }
}

pow_producer::~pow_producer() = default;

void pow_producer::display_hashrate( const boost::system::error_code& ec )
{
   if ( ec == boost::asio::error::operation_aborted )
      return;

   if ( _hashing )
   {
      double total_hashes = 0;
      for ( auto it = _worker_hashrate.begin(); it != _worker_hashrate.end(); ++it )
         total_hashes += it->second.load();

      total_hashes /= hashrate::update_interval.count();

      LOG(info) << "Hashrate: " << hashrate_to_string( total_hashes );
   }

   _update_timer.expires_from_now( hashrate::update_interval );
   _update_timer.async_wait( std::bind( &pow_producer::display_hashrate, this, std::placeholders::_1 ) );
}

void pow_producer::produce( const boost::system::error_code& ec )
{
   if ( ec == boost::asio::error::operation_aborted )
      return;

   auto done  = std::make_shared< std::atomic< bool > >( false );
   auto nonce = std::make_shared< std::optional< uint256_t > >();

   try
   {
      auto block = next_block();

      do
      {
         auto diff_meta = get_difficulty_meta();
         auto target = util::converter::to< uint256_t >( diff_meta.target() );

         block.set_id( util::converter::as< std::string >( crypto::hash( crypto::multicodec::sha2_256, block.header() ) ) );

         LOG(info) << "Difficulty target: 0x" << std::setfill( '0' ) << std::setw( 64 ) << std::hex << target;
         LOG(info) << "Network hashrate: " << compute_network_hashrate( diff_meta );

         for ( std::size_t worker_index = 0; worker_index < _worker_groups.size(); worker_index++ )
         {
            const auto& [ start, end ] = _worker_groups.at( worker_index );
            boost::asio::post(
               _production_context,
               std::bind(
                  &pow_producer::find_nonce,
                  this,
                  worker_index++,
                  block,
                  target,
                  start,
                  end,
                  nonce,
                  done
               )
            );
         }

         {
            auto lock = std::unique_lock< std::mutex >( _cv_mutex );

            bool service_was_halted = false;
            bool block_is_stale     = false;

            _hashing = true;

            while ( !_cv.wait_for( lock, 1s, [&]()
            {
               service_was_halted = _production_context.stopped() || _halted;
               block_is_stale     = _last_known_height >= block.header().height();

               return *done || service_was_halted || block_is_stale;
            } ) );

            _hashing = false;

            if ( service_was_halted )
               return;

            if ( block_is_stale )
            {
               LOG(info) << "Block is stale, retrieving new head";
               *done = true;
               boost::asio::post( _production_context, std::bind( &pow_producer::produce, this, boost::system::error_code{} ) );
               return;
            }
         }

         KOINOS_ASSERT( nonce->has_value(), nonce_failure, "expected nonce to contain a value" );

         auto block_nonce = nonce->value();

         LOG(info) << "Found nonce: 0x" << std::setfill( '0' ) << std::setw( 64 ) << std::hex << block_nonce;
         LOG(info) << "Proof: " << crypto::hash( crypto::multicodec::sha2_256, block_nonce, block.id() );

         contracts::pow::pow_signature_data pow_data;
         pow_data.set_nonce( util::converter::as< std::string >( block_nonce ) );
         pow_data.set_recoverable_signature( util::converter::as< std::string >( _signing_key.sign_compact( util::converter::to< crypto::multihash >( block.id() ) ) ) );

         block.set_signature( util::converter::as< std::string >( pow_data ) );
      }
      while( submit_block( block ) );

      _error_wait_time = 5s;
   }
   catch ( const std::exception& e )
   {
      *done = true;
      _hashing = false;

      LOG(warning) << e.what() << ", retrying in " << _error_wait_time.load().count() << "s";

      _error_timer.expires_from_now( _error_wait_time.load() );
      _error_timer.async_wait( std::bind( &pow_producer::produce, this, std::placeholders::_1 ) );

      // Exponential backoff, max wait time 30 seconds
      auto next_wait_time = std::min( uint64_t( _error_wait_time.load().count() * 2 ), uint64_t( 30 ) );
      _error_wait_time = std::chrono::seconds( next_wait_time );
      return;
   }

   boost::asio::post( _production_context, std::bind( &pow_producer::produce, this, boost::system::error_code{} ) );
}

void pow_producer::find_nonce(
   std::size_t worker_index,
   const protocol::block& block,
   uint256_t target,
   uint256_t start,
   uint256_t end,
   std::shared_ptr< std::optional< uint256_t > > nonce,
   std::shared_ptr< std::atomic< bool > > done )
{
   auto begin_time  = std::chrono::steady_clock::now();
   auto begin_nonce = start;
   auto id = util::converter::to< crypto::multihash >( block.id() );

   for ( uint256_t current_nonce = start; current_nonce < end; current_nonce++ )
   {
      if ( *done || _production_context.stopped() || _halted )
         break;

      auto proof = hash( crypto::multicodec::sha2_256, current_nonce, id );

      if ( target_met( proof, target ) )
      {
         std::unique_lock< std::mutex > lock( _cv_mutex );
         if ( !*done )
         {
            *nonce = current_nonce;
            *done  = true;
            _cv.notify_one();
         }
      }

      if ( auto now = std::chrono::steady_clock::now(); now - begin_time > hashrate::update_interval )
      {
         auto hashes = current_nonce - begin_nonce;
         begin_time  = now;
         begin_nonce = current_nonce;
         _worker_hashrate[ worker_index ] = hashes.convert_to< uint64_t >();
      }
   }
}

contracts::pow::difficulty_metadata pow_producer::get_difficulty_meta()
{
   rpc::chain::chain_request req;
   auto read_contract = req.mutable_read_contract();
   read_contract->set_contract_id( _pow_contract_id );
   read_contract->set_entry_point( get_difficulty_entrypoint );

   auto future = _rpc_client->rpc( util::service::chain, req.SerializeAsString() );

   rpc::chain::chain_response resp;
   resp.ParseFromString( future.get() );

   if ( resp.has_error() )
   {
      KOINOS_THROW( rpc_failure, "error while retrieving difficulty from the pow contract: ${e}", ("e", resp.error().message()) );
   }

   KOINOS_ASSERT( resp.has_read_contract(), rpc_failure, "unexpected RPC response when retrieving difficulty: ${r}", ("r", resp) );

   contracts::pow::get_difficulty_metadata_result meta;
   meta.ParseFromString( resp.read_contract().result() );
   return meta.value();
}

bool pow_producer::target_met( const crypto::multihash& hash, uint256_t target )
{
   if ( util::converter::to< uint256_t >( hash.digest() ) <= target )
      return true;

   return false;
}

void pow_producer::on_block_accept( const protocol::block& b )
{
   block_producer::on_block_accept( b );

   {
      std::unique_lock< std::mutex > lock( _cv_mutex );
      _last_known_height = b.header().height();
      _cv.notify_one();
   }
}

std::string pow_producer::hashrate_to_string( double hashrate )
{
   std::string suffix = "H/s";

   if ( hashrate > hashrate::terahash )
   {
      hashrate /= hashrate::terahash;
      suffix = "TH/s";
   }
   else if ( hashrate > hashrate::gigahash )
   {
      hashrate /= hashrate::gigahash;
      suffix = "GH/s";
   }
   else if ( hashrate > hashrate::megahash )
   {
      hashrate /= hashrate::megahash;
      suffix = "MH/s";
   }
   else if ( hashrate > hashrate::kilohash )
   {
      hashrate /= hashrate::kilohash;
      suffix = "KH/s";
   }

   return std::to_string( hashrate ) + " " + suffix;
}

std::string pow_producer::compute_network_hashrate( const contracts::pow::difficulty_metadata& meta )
{
   auto hashrate = util::converter::to< uint256_t >( meta.difficulty() ) / meta.target_block_interval();
   return hashrate_to_string( double( hashrate ) );
}

void pow_producer::commence()
{
   boost::asio::post( _production_context, std::bind( &pow_producer::produce, this, boost::system::error_code{} ) );
   _update_timer.expires_from_now( hashrate::update_interval + 2500ms );
   _update_timer.async_wait( std::bind( &pow_producer::display_hashrate, this, std::placeholders::_1 ) );
}

void pow_producer::halt()
{
   _update_timer.cancel();
}

} // koinos::block_production


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2020 Mikhail Komarov <nemo@nil.foundation>
//
// 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_0.txt
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_TIGER_POLICY_HPP
#define CRYPTO3_TIGER_POLICY_HPP

#include <boost/crypto3/hash/detail/tiger/tiger_functions.hpp>

namespace boost {
    namespace crypto3 {
        namespace hashes {
            namespace detail {
                template<std::size_t DigestBits, std::size_t Passes>
                struct tiger_policy : public tiger_functions<DigestBits> {
                    typedef typename tiger_functions<DigestBits>::byte_type byte_type;

                    constexpr static const std::size_t word_bits = tiger_functions<DigestBits>::word_bits;
                    typedef typename tiger_functions<DigestBits>::word_type word_type;

                    constexpr static const std::size_t passes = Passes;

                    typedef typename stream_endian::little_octet_big_bit digest_endian;

                    constexpr static const std::size_t digest_bits = DigestBits;
                    typedef static_digest<DigestBits> digest_type;

                    constexpr static const std::size_t state_bits = tiger_functions<DigestBits>::state_bits;
                    constexpr static const std::size_t state_words = tiger_functions<DigestBits>::state_words;
                    typedef typename tiger_functions<DigestBits>::state_type state_type;

                    struct iv_generator {
                        state_type const &operator()() const {
                            constexpr static const state_type H0 = {
                                {0x0123456789ABCDEF, 0xFEDCBA9876543210, 0xF096A5B4C3B2E187}};
                            return H0;
                        }
                    };
                };
            }    // namespace detail
        }        // namespace hashes
    }            // namespace crypto3
}    // namespace boost

#endif    // CRYPTO3_TIGER_POLICY_HPP