#ifndef TRIUMF_SUPERCONDUCTIVITY_BCS_HPP
#define TRIUMF_SUPERCONDUCTIVITY_BCS_HPP

#include <cmath>
#include <limits>
#include <tuple>

#include <boost/math/constants/constants.hpp>
#include <boost/math/quadrature/ooura_fourier_integrals.hpp>
#include <boost/math/tools/roots.hpp>

#include <triumf/constants/codata_2018.hpp>
#include <triumf/superconductivity/phenomenology.hpp>

// TRIUMF: Canada's particle accelerator centre
namespace triumf {

//
namespace superconductivity {

// Bardeen-Cooper-Schrieffer (BCS) theory of superconductivity
namespace bcs {

/// Find the reduced gap Delta(t) for a given reduced temperature x = T / T_c.
template <typename T = double> T reduced_gap_solver(T x) {
  // Use 1 as the initial guess for the Delta(t).
  // This ensures that the first "root" is found as Delta(t) -> 0.0,
  // otherwise the wrong solution is found when t >= 0.9.
  T guess = 1.0;
  // T guess = std::cos(boost::math::constants::half_pi<T>() * std::pow(x, 2));

  // Bound the possible values for the gap.
  // The adjustments by machine epsilon needed for "exact" results!?
  T min = 0.0 - std::numeric_limits<T>::epsilon();
  T max = 1.0 + std::numeric_limits<T>::epsilon();

  // Maximum possible binary digits accuracy for type T.
  const int digits = std::numeric_limits<T>::digits;

  // Digits used to control how accurate to try to make the result.
  // Accuracy triples with each step, so stop when just over one third of the
  // digits are correct.
  int get_digits = static_cast<int>(0.4 * digits);

  // Limit the number of iterations taken to find the "root" of the expression.
  // boost::uintmax_t max_iterations = 20;
  boost::uintmax_t max_iterations =
      std::numeric_limits<boost::uintmax_t>::max();

  // Return the "root" of Thouless' Eqn. using both its 1st and 2nd derivatives.
  T result = boost::math::tools::halley_iterate(
      [x](const T &delta) {
        // Return a tuple containing: f(delta), f'(delta), and  f''(delta).
        T f = std::tanh(delta / x) - delta;
        T df_dx = 1.0 / (x * std::pow(std::cosh(delta / x), 2)) - 1.0;
        T df2_dx2 =
            -2.0 * std::tanh(delta / x) / std::pow(x * std::cosh(delta / x), 2);
        return std::make_tuple(f, df_dx, df2_dx2);
      },
      guess, min, max, get_digits, max_iterations);
  //
  return result;
}

/// temperature dependence of the (reduced) energy gap
template <typename T = double> T reduced_gap(T reduced_temperature) {
  if (reduced_temperature >= 1.0) {
    return 0.0;
  } else if (reduced_temperature <= 0.0) {
    return 1.0;
  } else {
    return reduced_gap_solver<T>(reduced_temperature);
  }
}

/// temperature dependence of the (reduced) energy gap
template <typename T = double>
T reduced_gap(T temperature, T critical_temperature) {
  T reduced_temperature = temperature / critical_temperature;
  return reduced_gap<T>(reduced_temperature);
}

/// temperature dependence of the energy gap
template <typename T = double>
T gap(T temperature, T critical_temperature, T gap_meV) {
  return gap_meV * reduced_gap<T>(temperature, critical_temperature);
}

/// energy gap at absolute zero
template <typename T = double> T gap_meV(T critical_temperature) {
  // Boltzmann constant (meV / K)
  constexpr T k_B_meV_per_K =
      1e3 *
      triumf::constants::codata_2018::Boltzmann_constant_in_eV_K<T>::value();
  //
  return boost::math::constants::pi<T>() *
         std::exp(-boost::math::constants::euler<T>()) * k_B_meV_per_K *
         critical_temperature;
}

// energy gap ratio (at absolute zero)
template <typename T = double> T gap_ratio(T critical_temperature, T gap_meV) {
  // Boltzmann constant (meV / K)
  constexpr T k_B_meV_per_K =
      1e3 *
      triumf::constants::codata_2018::Boltzmann_constant_in_eV_K<T>::value();
  //
  return (2.0 * gap_meV) / (k_B_meV_per_K * critical_temperature);
}

/// helper function for BCS Kernel
template <typename T = double>
T a(T temperature, T critical_temperature, T gap_meV) {
  return boost::math::constants::pi<T>() * 1e3 *
         triumf::constants::codata_2018::Boltzmann_constant_in_eV_K<
             T>::value() *
         temperature / gap<T>(temperature, critical_temperature, gap_meV);
}

/// helper function for BCS Kernel
template <typename T = double>
T f(T temperature, T critical_temperature, T gap_meV, T n) {
  return std::sqrt(1.0 +
                   std::pow(a<T>(temperature, critical_temperature, gap_meV) *
                                (2.0 * n + 1.0),
                            2));
}

/// temperature dependence of the BCS coherence length (helper function)
template <typename T = double>
T coherence_length(T temperature, T critical_temperature, T gap_meV, T xi_0,
                   T mean_free_path, T n) {
  T fraction_1 = boost::math::constants::two_div_pi<T>() *
                 f<T>(temperature, critical_temperature, gap_meV, n) *
                 reduced_gap<T>(temperature, critical_temperature) / xi_0;
  T fraction_2 = 1.0 / mean_free_path;
  T fraction = fraction_1 + fraction_2;
  // handle a some edge cases for very big/small args
  if (std::isinf(fraction)) {
    return 0.0;
  } else if (fraction == 0.0) {
    return std::numeric_limits<T>::infinity();
  } else {
    return 1.0 / fraction;
  }
}

/// helper function for BCS Kernel
template <typename T = double>
T Lambda(T temperature, T critical_temperature, T gap_meV, T xi_0,
         T mean_free_path, T lambda_0, T exponent, T n) {
  return std::pow(
             lambda_0,
             // triumf::superconductivity::phenomenology::penetration_depth<T>(
             //    temperature, critical_temperature, exponent, lambda_0),
             2) *
         std::pow(f<T>(temperature, critical_temperature, gap_meV, n), 3) *
         (1.0 + coherence_length<T>(temperature, critical_temperature, gap_meV,
                                    xi_0, mean_free_path, n) /
                    mean_free_path) /
         (2.0 * a<T>(temperature, critical_temperature, gap_meV));
}

/// helper function for the BCS Kernel
template <typename T = double> T g(T x) {
  // return correct result when x ~ 0.
  if (x < 1.0e-4) {
    return 1.0;
  } else {
    return (3.0 / 2.0) * ((1.0 + x * x) * std::atan(x) - x) / (x * x * x);
  }
}

/// BCS Kernel
template <typename T = double>
T kernel(T q, T temperature, T critical_temperature, T gap_meV, T xi_0,
         T mean_free_path, T lambda_0, T exponent) {
  //
  T sum = 0.0;
  T change = 0.0;
  // const T precision = std::sqrt(std::numeric_limits<T>::epsilon());
  const T precision = std::numeric_limits<T>::epsilon();
  const T max_iterations = 100;
  T n = 0.0;
  do {
    //
    T x = q * coherence_length<T>(temperature, critical_temperature, gap_meV,
                                  xi_0, mean_free_path, n);
    //
    change = g<T>(x) / Lambda<T>(temperature, critical_temperature, gap_meV,
                                 xi_0, mean_free_path, lambda_0, exponent, n);
    //
    sum += change;
    n += 1.0;
  } while ((std::abs(change) > precision) and (n < max_iterations));
  //
  return sum;
}

/// (reduced) BCS Kernel
template <typename T = double>
T reduced_kernel(T q, T temperature, T critical_temperature, T gap_meV, T xi_0,
                 T mean_free_path, T lambda_0, T exponent) {
  return kernel<T>(q, temperature, critical_temperature, gap_meV, xi_0,
                   mean_free_path, lambda_0, exponent) /
         kernel<T>(0.0, temperature, critical_temperature, gap_meV, xi_0,
                   mean_free_path, lambda_0, exponent);
}

/// BCS magnetic penetration depth
template <typename T = double>
T penetration_depth(T temperature, T critical_temperature, T gap_meV, T xi_0,
                    T mean_free_path, T lambda_0, T exponent) {
  T K_0 = kernel<T>(0.0, temperature, critical_temperature, gap_meV, xi_0,
                    mean_free_path, lambda_0, exponent);
  return std::sqrt(1.0 / K_0);
}

/// (reduced) BCS magnetic field penetration profile
template <typename T = double>
T reduced_field_penetration(T z, T temperature, T critical_temperature,
                            T gap_meV, T xi_0, T mean_free_path, T lambda_0,
                            T exponent) {
  //
  if (z <= 0.0) {
    return 1.0;
  } else {
    //
    auto bcs_integrand = [&](T q) -> T {
      T K = kernel<T>(q, temperature, critical_temperature, gap_meV, xi_0,
                      mean_free_path, lambda_0, exponent);
      return q / (q * q + K);
    };
    // create the integrator w/ default tolerance and evaluation levels
    // (root_epsilon and eight levels for type double).
    static boost::math::quadrature::ooura_fourier_sin<T> bcs_integrator =
        boost::math::quadrature::ooura_fourier_sin<T>();
    // evaluate the integral, which returns a pair
    // (first = integral, second = relative error)
    std::pair<T, T> result = bcs_integrator.integrate(bcs_integrand, z);
    // return the integral multiplied by the prefactors to get B vs. z
    return boost::math::constants::two_div_pi<T>() * result.first;
  }
}

/// BCS magnetic field penetration proifile
template <typename T = double>
T field_penetration(T z, T temperature, T critical_temperature, T gap_meV,
                    T xi_0, T mean_free_path, T lambda_0, T exponent,
                    T applied_field) {
  return applied_field * reduced_field_penetration<T>(
                             z, temperature, critical_temperature, gap_meV,
                             xi_0, mean_free_path, lambda_0, exponent);
}

} // namespace bcs

} // namespace superconductivity

} // namespace triumf

#endif // TRIUMF_SUPERCONDUCTIVITY_BCS_HPP


#pragma once

#include <Eigen/Core>
#include "math_utils.hpp"
#undef far
#undef near

namespace vgl {
template <typename T>
Eigen::Matrix<T, 4, 4> orthographic_projection(double const left, double const right, double const top,
                                             double const bottom, double const near, double const far) {
    Eigen::Matrix<T, 4, 4> proj_mat = Eigen::Matrix<T, 4, 4>::Identity();
    auto const width = right - left;
    auto const height = top - bottom;
    auto const depth = far - near;
    proj_mat(0, 0) = static_cast<T>(2.0 / width);
    proj_mat(1, 1) = static_cast<T>(2.0 / height);
    proj_mat(2, 2) = static_cast<T>(-2.0 / depth);
    proj_mat(0, 3) = static_cast<T>(-(right + left) / width);
    proj_mat(1, 3) = static_cast<T>(-(top + bottom) / height);
    proj_mat(2, 3) = static_cast<T>(-(far + near) / depth);
    return proj_mat;
}

template <typename T>
Eigen::Matrix<T, 4, 4> orthographic_projection(double const width, double const height, double const near,
                                             double const far) {
    Eigen::Matrix<T, 4, 4> proj_mat = Eigen::Matrix<T, 4, 4>::Identity();
    proj_mat(0, 0) = static_cast<T>(2.0 / width);
    proj_mat(1, 1) = static_cast<T>(2.0 / height);
    auto const depth = far - near;
    proj_mat(2, 2) = static_cast<T>(-2.0 / depth);
    proj_mat(2, 3) = static_cast<T>(-(far + near) / depth);
    return proj_mat;
}

template <typename T>
Eigen::Matrix<T, 4, 4> frustum_projection(double const left, double const right, double const top, double const bottom,
                                        double const near, double const far) {
    Eigen::Matrix<T, 4, 4> proj_mat = Eigen::Matrix<T, 4, 4>::Identity();
    auto const width = right - left;
    auto const height = top - bottom;
    auto const depth = far - near;
    proj_mat(0, 0) = static_cast<T>(2.0 * near / width);
    proj_mat(1, 1) = static_cast<T>(2.0 * near / height);
    proj_mat(0, 2) = static_cast<T>((right + left) / width);
    proj_mat(1, 2) = static_cast<T>((top + bottom) / height);
    proj_mat(2, 2) = static_cast<T>(-(far + near) / depth);
    proj_mat(2, 3) = static_cast<T>(-2.0 * far * near / depth);
    proj_mat(3, 2) = static_cast<T>(-1.0);
    return proj_mat;
}

template <typename T>
Eigen::Matrix<T, 4, 4> perspective_projection(double const aspect_ratio, double const fovy, double const near,
                                            double const far) {
    Eigen::Matrix<T, 4, 4> proj_mat = Eigen::Matrix<T, 4, 4>::Identity();
    auto const tan_half_fovy = math::to_radians(fovy / 2.0);
    auto const depth = far - near;
    proj_mat(0, 0) = static_cast<T>(1.0 / (aspect_ratio * tan_half_fovy));
    proj_mat(1, 1) = static_cast<T>(1.0 / tan_half_fovy);
    proj_mat(2, 2) = static_cast<T>(-(far + near) / depth);
    proj_mat(2, 3) = static_cast<T>(-2.0 * far * near / depth);
    proj_mat(3, 2) = static_cast<T>(-1.0);
    return proj_mat;
}

template <typename T>
void change_aspect_ratio(Eigen::Matrix<T, 4, 4>& proj, double aspect_ratio) {
    proj(0, 0) = static_cast<T>(proj(1, 1) / aspect_ratio);
}

template <typename T>
void change_fovy(Eigen::Matrix<T, 4, 4>& proj, double fovy) {
    auto old = proj(1, 1);
    auto tan_half = std::tan(fovy / 2.0);
    proj(0, 0) = static_cast<T>(proj(0, 0) / old / tan_half);
    proj(1, 1) = static_cast<T>(1.0 / tan_half);
}
} // namespace vgl


#ifndef STAN_MATH_PRIM_SCAL_FUN_LOG_DIFF_EXP_HPP
#define STAN_MATH_PRIM_SCAL_FUN_LOG_DIFF_EXP_HPP

#include <stan/math/prim/scal/fun/log1m_exp.hpp>
#include <boost/math/tools/promotion.hpp>
#include <boost/throw_exception.hpp>
#include <limits>
#include <stdexcept>

namespace stan {
  namespace math {

    /**
     * The natural logarithm of the difference of the natural exponentiation
     * of x1 and the natural exponentiation of x2
     *
     * This function is only defined for x<0
     *
     *
       \f[
       \mbox{log\_diff\_exp}(x, y) =
       \begin{cases}
         \textrm{NaN} & \mbox{if } x \leq y\\
         \ln(\exp(x)-\exp(y)) & \mbox{if } x > y \\[6pt]
         \textrm{NaN} & \mbox{if } x = \textrm{NaN or } y = \textrm{NaN}
       \end{cases}
       \f]

       \f[
       \frac{\partial\, \mbox{log\_diff\_exp}(x, y)}{\partial x} =
       \begin{cases}
         \textrm{NaN} & \mbox{if } x \leq y\\
         \frac{\exp(x)}{\exp(x)-\exp(y)} & \mbox{if } x > y \\[6pt]
         \textrm{NaN} & \mbox{if } x = \textrm{NaN or } y = \textrm{NaN}
       \end{cases}
       \f]

       \f[
       \frac{\partial\, \mbox{log\_diff\_exp}(x, y)}{\partial y} =
       \begin{cases}
         \textrm{NaN} & \mbox{if } x \leq y\\
         -\frac{\exp(y)}{\exp(x)-\exp(y)} & \mbox{if } x > y \\[6pt]
         \textrm{NaN} & \mbox{if } x = \textrm{NaN or } y = \textrm{NaN}
       \end{cases}
       \f]
     *
     */
    template <typename T1, typename T2>
    inline typename boost::math::tools::promote_args<T1, T2>::type
    log_diff_exp(const T1 x, const T2 y) {
      if (x <= y)
        return std::numeric_limits<double>::quiet_NaN();
      return x + log1m_exp(y - x);
    }

  }
}

#endif


#ifndef HYPSYS1D_MODEL_HPP
#define HYPSYS1D_MODEL_HPP

#include <cmath>
#include <memory>

#include <Eigen/Dense>
#include <ancse/config.hpp>

/// Interface for implementing different models,
/// eg. Euler equations, Shallow-water equations
///
/// Add more functions to this interface if needed.
class Model {
  public:
    virtual ~Model() = default;

    // Pure virtual functions:
    virtual Eigen::VectorXd flux(const Eigen::VectorXd& u) const = 0;
    virtual Eigen::VectorXd eigenvalues(const Eigen::VectorXd& u) const = 0;
    virtual Eigen::MatrixXd eigenvectors(const Eigen::VectorXd& u) const = 0;
    virtual double max_eigenvalue(const Eigen::VectorXd& u) const = 0;

    virtual Eigen::VectorXd cons_to_prim(const Eigen::VectorXd& u) const = 0;
    virtual Eigen::VectorXd prim_to_cons(const Eigen::VectorXd& u) const = 0;

    virtual int get_nvars() const = 0;
    virtual std::string get_name() const = 0;

    // Virtual functions:
    virtual std::pair<double, double> lo_hi_eigenvalues(const Eigen::VectorXd& u) const {}

    virtual double rho(const Eigen::VectorXd& u_prim) const {}
    virtual double v(const Eigen::VectorXd& u_prim) const {}
    virtual double p(const Eigen::VectorXd& u_prim) const {}
    virtual double m(const Eigen::VectorXd& u_prim) const {}
    virtual double E(const Eigen::VectorXd& u_prim) const {}
    virtual double c(const Eigen::VectorXd& u_prim) const {}
    virtual double H(const Eigen::VectorXd& u_prim) const {}

    virtual void set_gamma(const double gamma_) {}
    virtual double get_gamma() const {}
};

class Burgers : public Model {
  public:

    Eigen::VectorXd flux(const Eigen::VectorXd &u) const override
    {
        Eigen::VectorXd f(n_vars);
        f(0) = 0.5*u(0)*u(0);

        return f;
    }

    Eigen::VectorXd eigenvalues(const Eigen::VectorXd &u) const override
    {
        Eigen::VectorXd eigvals(n_vars);
        eigvals(0) = u(0);

        return eigvals;
    }

    Eigen::MatrixXd eigenvectors(const Eigen::VectorXd &) const override
    {
        Eigen::MatrixXd eigvecs(n_vars, n_vars);
        eigvecs (0,0) = 1;

        return eigvecs;
    }

    double max_eigenvalue(const Eigen::VectorXd &u) const override {
        return (eigenvalues(u).cwiseAbs()).maxCoeff();
    }

    Eigen::VectorXd cons_to_prim(const Eigen::VectorXd &u) const override {
        return u;
    }

    Eigen::VectorXd prim_to_cons(const Eigen::VectorXd &u) const override {
        return u;
    }

    int get_nvars() const override
    {
        return n_vars;
    }

    std::string get_name() const override
    {
        return "burgers";
    }

  private:
    int n_vars = 1;
};

/// Euler equations
class Euler : public Model {
    public:

        Eigen::VectorXd flux(const Eigen::VectorXd &u) const override
        {
            Eigen::VectorXd f(n_vars);

            f << rho(u) * v(u),
                 rho(u) * v(u) * v(u) + p(u),
                 (E(u) + p(u)) * v(u);

            return f;
        }

        Eigen::VectorXd eigenvalues(const Eigen::VectorXd &u) const override
        {
            Eigen::VectorXd eigvals(n_vars);

            eigvals << v(u) - c(u),
                       v(u),
                       v(u) + c(u);

            return eigvals;
        }

        Eigen::MatrixXd eigenvectors(const Eigen::VectorXd &u) const override
        {
            Eigen::MatrixXd eigvecs(n_vars, n_vars);

            eigvecs << 1.0,                    1.0,                 1.0,
                       v(u) - c(u),            v(u),                v(u) + c(u),
                       H(u) - v(u) * c(u),     0.5 * v(u) * v(u),   H(u) + (v(u) * c(u));

            return eigvecs;
        }

        double max_eigenvalue(const Eigen::VectorXd &u) const override
        {
            // actually max(abs(eigenvalues)):
            return (eigenvalues(u).cwiseAbs()).maxCoeff();
        }

        Eigen::VectorXd cons_to_prim(const Eigen::VectorXd &u_cons) const override
        {
            Eigen::VectorXd u_prim(n_vars);

            u_prim << rho(u_cons),
                      v(u_cons),
                      p(u_cons);

            return u_prim;
        }

        Eigen::VectorXd prim_to_cons(const Eigen::VectorXd &u_prim) const override
        {
            Eigen::VectorXd u_cons(n_vars);

            u_cons << u_prim(0),
                      u_prim(0) * u_prim(1),
                      u_prim(2) / (gamma - 1) + 0.5 * u_prim(0) * u_prim(1) * u_prim(1);

            return u_cons;
        }

        inline void set_gamma(const double gamma_) override
        {
            gamma= gamma_;
        }

        inline double get_gamma() const override
        {
            return gamma;
        }

        inline int get_nvars() const override
        {
            return n_vars;
        }

        std::string get_name() const override
        {
            return name;
        }

    private:
        // 3D:
        const int n_vars= 3;
        // Monatomic gas:
        double gamma= 5./3.;

        inline static const std::string name= "euler";

        std::pair<double, double> lo_hi_eigenvalues(const Eigen::VectorXd &u) const override
        {
            // check if we want cwiseAbs here:
            // return (eigenvalues(u).cwiseAbs()).minCoeff();
            Eigen::VectorXd eigenvals= eigenvalues(u);
            return {eigenvals.minCoeff(), eigenvals.maxCoeff()};
        }

        /// Helper functions to deal w/ common names for Euler eqn expressions
        /// Vector u must contain conserved variables: u = (rho, m, E)
        /// as given in tasks initial conditions
        /// ------------------------------------------------------------------

        /// Conserved variables:
        // rho : density
        inline double rho(const Eigen::VectorXd& u) const override
        {
            return u(0);
        }

        // m : momentum
        inline double m(const Eigen::VectorXd& u) const override
        {
            return u(1);
        }

        // E : total energy for ideal polytropic gas (internal energy + kinetic energy)
        inline double E(const Eigen::VectorXd& u) const override
        {
            return u(2);
        }

        /// Primitive variables:
        // rho : density
        // same as for conserved variables

        // v : velocity
        inline double v(const Eigen::VectorXd& u) const override
        {
            return m(u) / rho(u);
        }

        // p : pressure
        inline double p(const Eigen::VectorXd& u) const override
        {
            return (E(u) - 0.5 * m(u) * m(u) / rho(u)) * (gamma - 1);
        }

        /// c : speed of sound
        inline double c(const Eigen::VectorXd& u) const override
        {
            return std::sqrt(gamma * p(u) / rho(u));
        }

        /// H : total specific enthalpy
        inline double H(const Eigen::VectorXd& u) const override
        {
            return (E(u) + p(u)) / rho(u);
        }
};

std::shared_ptr<Model> make_model (const nlohmann::json &config);

#endif // HYPSYS1D_MODEL_HPP


#include <ql/quantlib.hpp>

#include <iostream>

#include <iomanip>

#include <boost/timer.hpp>

using namespace QuantLib;

using namespace std;

#if defined(QL_ENABLE_SESSIONS)

{

        namespace QuantLib

        {

               Integer sessionId() { return 0; }

        }

#endif

int main(int, char*[])

{

        try

        {

               boost::timer timer;

               std::cout << std::endl;

               Calendar calendar = TARGET();

               Date settlementDate(6, Aug, 2021);

               settlementDate = calendar.adjust(settlementDate);

               Integer fixingDays = 1;

               Natural settlementDays = 1;

               Date todaysDate = calendar..advance(settlementDate, -fixingDays, Days);

               Settings::instance().evaluationDate() = todaysDate;

               // Treasury Bond Setting //

               Real faceAmount = 100;

               Real redemption = 100;

               Date issueDate(17, May, 2021);

               Date maturity(5, May, 2031);

               Real couponRate = 0.01625; // 1 5/8

               Real yield = 0.0165;

               RelinkableHandle<YieldTermStructure> discountingTermStructure;

               boost::shared_ptr<YieldTermStructure> flatTermStructure(new FlatForward(settlementDate, yield, ActualActual(ActualActual::Bond), Compounded, Semiannual));

               discountingTermStructure.linkTo(flatTermStructure);

               boost::shared_ptr<PricingEngine> bondEngine(new DiscountingBondEngine(discountingTermStructure));

               Schedule fixedBondSchedule(issueDate, maturity, Period(Semiannual), UnitedStates(UnitedStates::GovernmentBond), Unadjusted, Unadjusted, DateGeneration::Rule::Backward, false);

               FixedRateBond fixedRateBond(settlementDays, faceAmount, fixedBondSchedule, std::vector<Rate>(1, couponRate), ActualActual(ActualActual::Bond), Unadjusted, redemption, issueDate);

               fixedRateBond.setPricingEngine(bondEngine);

               std::cout << "****** Inputs ******" << std::endl;

               //std::setprecision(20);

               std::cout << " Principal = " << faceAmount << std::endl;

               std::cout << "Issue Date = " << issueDate << std::endl;

               std::cout << " Maturity = " << maturity << std::endl;

               std::cout << " Coupon  = " << io::percent(couponRate) << std::endl;

               std::cout << " Yield To Maturity = " << io::percent(yield) << std::endl;

               std::cout << "****** Result ******" << std::endl;

               std::cout << " Fair Value = " << fixedRateBond.NPV() << std::endl;

               std::cout << " Clean Price = " << fixedRateBond.cleanPrice() << std::endl;

               std::cout << " Dirty Price = " << fixedRateBond.dirtyPrice() << std::endl;

               std::cout << " Accured Coupon = " << fixedRateBond.accruedAmount() << std::endl;

               system("pause");

               return 0;

        }

        catch (std::exception& e)

        {

               std::cerr << e.what() << std::endl;

               return 1;

        }

        catch (...)

        {

               std::cerr << "Unknown Error" << std::endl;

               return 1;

        }

}


//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// 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)
//=======================================================================
#include <iostream>
#include <boost/graph/edge_list.hpp>
#include <boost/graph/bellman_ford_shortest_paths.hpp>

int
main()
{
  using namespace boost;
  // ID numbers for the routers (vertices).
  enum
  { A, B, C, D, E, F, G, H, n_vertices };
  const int n_edges = 11;
  typedef std::pair < int, int >Edge;

  // The list of connections between routers stored in an array.
  Edge edges[] = {
  Edge(A, B), Edge(A, C),
        Edge(B, D), Edge(B, E), Edge(C, E), Edge(C, F), Edge(D, H),
        Edge(D, E), Edge(E, H), Edge(F, G), Edge(G, H)
  };

  // Specify the graph type and declare a graph object
  typedef edge_list < Edge*, Edge, std::ptrdiff_t, std::random_access_iterator_tag> Graph;
  Graph g(edges, edges + n_edges);

  // The transmission delay values for each edge.
  float delay[] =
    {5.0, 1.0, 1.3, 3.0, 10.0, 2.0, 6.3, 0.4, 1.3, 1.2, 0.5};

  // Declare some storage for some "external" vertex properties.
  char name[] = "ABCDEFGH";
  int parent[n_vertices];
  for (int i = 0; i < n_vertices; ++i)
    parent[i] = i;
  float distance[n_vertices];
  std::fill(distance, distance + n_vertices, (std::numeric_limits < float >::max)());
  // Specify A as the source vertex
  distance[A] = 0;

  bool r = bellman_ford_shortest_paths(g, int (n_vertices),
                                       weight_map(make_iterator_property_map
                                                  (&delay[0],
                                                   get(edge_index, g),
                                                   delay[0])).
                                       distance_map(&distance[0]).
                                       predecessor_map(&parent[0]));

  if (r)
    for (int i = 0; i < n_vertices; ++i)
      std::cout << name[i] << ": " << distance[i]
        << " " << name[parent[i]] << std::endl;
  else
    std::cout << "negative cycle" << std::endl;

  return EXIT_SUCCESS;
}


#include <iostream>
#include <chrono>
#include <random>
#include <map>

#include <opencv2/opencv.hpp>
#include <Eigen/Core>
#include <Eigen/Dense>

#include "matplotlibcpp.h"

using namespace std;
using namespace Eigen;
namespace plt = matplotlibcpp;

double g(double x, double a, double b, double c)
{
  // return a*x*x+b*x+c;
  return std::exp(a * x * x + b * x + c);
}

int main(int argc, char **argv) {
  std::cout << "Steepest-Descent \n";

  double aa = 1.0, bb = 2.0, cc = 1.0;
  double a = 2.0, b = -1.0, c = -5.0;

  double obs_sigma = 1.0;
  std::default_random_engine generator;
  std::normal_distribution<double> obs_noise_distrib(0.0, obs_sigma);

  int N = 100;
  std::vector<double> x_data(N), y_data(N);
  for (int i = 0; i < N; ++i)
  {
    x_data[i] = static_cast<double>(i) / 100.0;
    y_data[i] = g(x_data[i], aa, bb, cc) + obs_noise_distrib(generator);

  }

  // Optimize
  int max_iter = 100000;
  for (int it = 0; it < max_iter; ++it)
  {
    std::cout << "iter " << it << " : ";
    std::cout << "abc = " << a << " " << b << " " << c << "\n";
    Eigen::Vector3d J(0.0, 0.0, 0.0);
    double total_err = 0.0;
    for (int i = 0; i < N; ++i)
    {
      double err = y_data[i] - g(x_data[i], a, b, c);
      total_err += err*err;

      // Compute derivative of F(x) = 0.5 * sum(f(x)^2) = 0.5 * sum((y-g(x))^2)
      J[0] += -x_data[i] * x_data[i] * g(x_data[i], a, b, c) * err;
      J[1] += -x_data[i] * g(x_data[i], a, b, c) * err;
      J[2] += -g(x_data[i], a, b, c) * err;
    }

    std::cout << "J = " << J.transpose() << "\n";
    std::cout << "total error: " << total_err << "\n";

    if (J.norm() < 0.01)
      break;

    // Very small step
    double S = 100000;
    a -= J[0] / S;
    b -= J[1] / S;
    c -= J[2] / S;
  }
  std::cout << "final = " << a << " " << b << " " << c << "\n";

  vector<double> final_y_data(N);
  for (int i = 0; i < N; ++i)
  {
    final_y_data[i] = g(x_data[i], a, b, c);
  }

  plt::scatter(x_data, y_data);
  std::map<std::string, std::string> parameters;
  parameters["c"] = "red";
  plt::scatter(x_data, final_y_data, 1.0, parameters);
  plt::show();

  return 0;
}


/**
 * @file advectionfv2d_main.cc
 * @brief NPDE homework AdvectionFV2D code
 * @author Philipp Egg
 * @date 21.06.2020
 * @copyright Developed at ETH Zurich
 */

#include <lf/assemble/assemble.h>
#include <lf/base/base.h>
#include <lf/geometry/geometry.h>
#include <lf/io/io.h>
#include <lf/mesh/mesh.h>
#include <lf/mesh/test_utils/test_meshes.h>
#include <lf/mesh/utils/utils.h>
#include <lf/refinement/refinement.h>

#include <Eigen/Core>
#include <array>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include "advectionfv2d.h"

// Use this function to plot your solution
void write_vtk(const lf::assemble::DofHandler &dofh,
               const Eigen::VectorXd &solution, std::string name) {
  std::shared_ptr<const lf::mesh::Mesh> mesh_p = dofh.Mesh();
  lf::io::VtkWriter vtk_writer(mesh_p, name + ".vtk");
  auto cell_data_ref =
      lf::mesh::utils::make_CodimMeshDataSet<double>(mesh_p, 0);
  for (const lf::mesh::Entity *cell : mesh_p->Entities(0)) {
    int row = dofh.GlobalDofIndices(*cell)[0];
    cell_data_ref->operator()(*cell) = solution[row];
  }
  vtk_writer.WriteCellData(name, *cell_data_ref);
}

int main() {
  /* SAM_LISTING_BEGIN_1 */
  // Define velocity field beta
  // Note that the problem description requires ||B|| <= 1
  auto beta = [](Eigen::Vector2d x) -> Eigen::Vector2d {
    return Eigen::Vector2d(-x[1], x[0]) / std::sqrt(2.0);
  };

  // Functor for initial bump
  auto u0 = [](Eigen::Vector2d x) -> double {
    Eigen::Vector2d x0(0.8, 0.2);
    double d = 0.2;
    double dist = (x - x0).norm();
    if (dist < d) {
      double cos = std::cos(M_PI / (2.0 * d) * dist);
      return cos * cos;
    } else {
      return 0.0;
    }
  };

  // Task 8-8.o
  // Generate a mesh hierarchy
  double T = 1.0;

  //////////////////////////////////////////////////////////////////////////////
  // TODO inconsistancy: g vs. G
  //////////////////////////////////////////////////////////////////////////////
  auto mesh_p = lf::mesh::test_utils::GenerateHybrid2DTestMesh(0, 1.0 / 3.0);

  auto mesh_seq_p{
      lf::refinement::GenerateMeshHierarchyByUniformRefinemnt(mesh_p, 6)};

  std::vector<int> vector_num_cells;
  std::vector<double> vector_l2error;

  // Iterate over mesh levels starting from thrid refinement
  int num_meshes = mesh_seq_p->NumLevels();
  for (int level = 3; level < num_meshes; ++level) {
    std::cout << "Computing L2Error for level: " << level << std::endl;

#if SOLUTION
    // Get the current mesh
    auto cur_mesh = mesh_seq_p->getMesh(level);

    // Create a DOF Hander for the current mesh
    const lf::assemble::UniformFEDofHandler cur_dofh(
        cur_mesh, {{lf::base::RefEl::kPoint(), 0},
                   {lf::base::RefEl::kSegment(), 0},
                   {lf::base::RefEl::kTria(), 1},
                   {lf::base::RefEl::kQuad(), 1}});
    int N = cur_dofh.NumDofs();

    // Compute cell normals
    std::shared_ptr<lf::mesh::utils::CodimMeshDataSet<
        Eigen::Matrix<double, 2, Eigen::Dynamic>>>
        normal_vectors = AdvectionFV2D::computeCellNormals(cur_dofh.Mesh());

    // Compute adjecent cells
    std::shared_ptr<lf::mesh::utils::CodimMeshDataSet<
        std::array<const lf::mesh::Entity *, 4>>>
        adjacentCells = AdvectionFV2D::getAdjacentCellPointers(cur_dofh.Mesh());

    // Get approximate solution from simulation
    Eigen::VectorXd mu_approx = AdvectionFV2D::simulateAdvection(
        cur_dofh, beta, u0, adjacentCells, normal_vectors, T);
    write_vtk(cur_dofh, mu_approx, "approx" + std::to_string(level));

    // Get exact solution at barycenters of cells
    Eigen::VectorXd mu_exact = AdvectionFV2D::refSolution(cur_dofh, u0, T);
    write_vtk(cur_dofh, mu_exact, "exact" + std::to_string(level));

    // Compute L2 error in barycenter
    double l2_error = 0;
    for (const lf::mesh::Entity *cell : cur_mesh->Entities(0)) {
      const lf::geometry::Geometry *geo_p = cell->Geometry();
      double area = lf::geometry::Volume(*geo_p);
      int idx = cur_dofh.GlobalDofIndices(*cell)[0];
      l2_error += std::pow((mu_approx[idx] - mu_exact[idx]), 2) * area;
    }
    l2_error = std::sqrt(l2_error);
#else
    //====================
    // Your code goes here
    // Compute the number N of DOFs and the L2-error,
    // and replace the lines below:
    int N = 1;
    double l2_error = 1.0;
    // If you want, you can use the function write_vtk(...),
    // defined in this file, to plot your solution.
    //====================
#endif

    vector_num_cells.push_back(N);
    vector_l2error.push_back(l2_error);
  }

  // Write Output file of Task 8-8.o
  std::ofstream csv_file;
  csv_file.open("advectionfv2d.csv");
  for (int i = 0; i < vector_num_cells.size(); ++i) {
    std::cout << "Cells: " << vector_num_cells.at(i)
              << " | L2Error: " << vector_l2error.at(i) << std::endl;
    csv_file << vector_num_cells.at(i) << "," << vector_l2error.at(i) << "\n";
  }
  csv_file.close();

  std::system("python3 " CURRENT_SOURCE_DIR
              "/advectionfv2d.py " CURRENT_BINARY_DIR
              "/advectionfv2d.csv " CURRENT_BINARY_DIR "/solution.eps");
  /* SAM_LISTING_END_1 */

  /* SAM_LISTING_BEGIN_2 */
  // Task 8-8.q
  // Compute threshold for fourth refinement level
  int level = 4;
  auto mesh = mesh_seq_p->getMesh(level);

#if SOLUTION
  // Create a DOF Hander for the current mesh
  const lf::assemble::UniformFEDofHandler dofh(
      mesh, {{lf::base::RefEl::kPoint(), 0},
             {lf::base::RefEl::kSegment(), 0},
             {lf::base::RefEl::kTria(), 1},
             {lf::base::RefEl::kQuad(), 1}});

  int threshold = AdvectionFV2D::findCFLthreshold(dofh, beta, T);
  int cfl_thres = int((T / AdvectionFV2D::computeHmin(mesh) + 1));
#else
  //====================
  // Your code goes here
  // Replace the two variables below:
  int threshold = 0.0;  // Threshold computed by findCFLthreshold(...)
  int cfl_thres = 0.0;  // Threshold obtained from CLF using computeHmin(mesh)
                        //====================
#endif

  std::cout << "Threshold for level " << level << " is " << threshold
            << " | Threshold from CFL is " << cfl_thres << std::endl;
  /* SAM_LISTING_END_2 */

  return 0;
}


#include "Classes.h"
#include <Eigen/Dense>
using namespace Eigen;

int PlaneProjection::rotatePlane() {
    ///
    /// Function to rotate the PlaneProjection instance so as to get the normal parallel to z-axis
    ///
    Vector3d zaxis(0,0,1);
    Vector3d norml(normal[0],normal[1],normal[2]);
    // cout << "Norml: " << endl << norml << endl;
    double costheta = zaxis.dot(norml);
    double sintheta = zaxis.cross(norml).norm();
    if(costheta < 0) {
        costheta = 0-costheta;
    }
    Vector3d axis = zaxis.cross(norml);
    Matrix3d id = Matrix3d::Identity();
    // cout << "Identity Matrix: " << endl << id << endl;
    Matrix3d ux;
    ux << 0, -axis.z(), axis.y(),
                axis.z(), 0, -axis.x(),
                -axis.y(), axis.x(), 0;
    // cout << "UX Matrix: " << endl << ux << endl;
    Matrix3d tensorprod = axis*axis.transpose();
    // cout << "Tensor Product Matrix: " << endl << tensorprod << endl;
    Matrix3d rotmax = costheta*id + sintheta*ux + (1-costheta)*tensorprod;
    for(auto i = 0; i < visibleEdges.size(); i++) {
        Vector3d point1(visibleEdges[i].p1.x,visibleEdges[i].p1.y,visibleEdges[i].p1.z);
        Vector3d point2(visibleEdges[i].p2.x,visibleEdges[i].p2.y,visibleEdges[i].p2.z);
        point1 = rotmax*point1;
        point2 = rotmax*point2;
        visibleEdges[i].p1.x = point1.x();
        visibleEdges[i].p1.y = point1.y();
        visibleEdges[i].p1.z = point1.z();
        visibleEdges[i].p2.x = point2.x();
        visibleEdges[i].p2.y = point2.y();
        visibleEdges[i].p2.z = point2.z();
    }
    for(auto i = 0; i < hiddenEdges.size(); i++) {
        Vector3d point1(hiddenEdges[i].p1.x,hiddenEdges[i].p1.y,hiddenEdges[i].p1.z);
        Vector3d point2(hiddenEdges[i].p2.x,hiddenEdges[i].p2.y,hiddenEdges[i].p2.z);
        point1 = rotmax*point1;
        point2 = rotmax*point2;
        hiddenEdges[i].p1.x = point1.x();
        hiddenEdges[i].p1.y = point1.y();
        hiddenEdges[i].p1.z = point1.z();
        hiddenEdges[i].p2.x = point2.x();
        hiddenEdges[i].p2.y = point2.y();
        hiddenEdges[i].p2.z = point2.z();
    }
}


// (C) Copyright Andrew Sutton 2007
//
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0 (See accompanying file
// LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)

//[inclusive_mean_geodesic_example
#include <iostream>
#include <iomanip>

#include <boost/graph/directed_graph.hpp>
#include <boost/graph/exterior_property.hpp>
#include <boost/graph/floyd_warshall_shortest.hpp>
#include <boost/graph/geodesic_distance.hpp>
#include "helper.hpp"

using namespace std;
using namespace boost;

// This template structure defines the function that we will apply
// to compute both the per-vertex mean geodesic distances and the
// graph's mean geodesic distance.
template < typename Graph, typename DistanceType, typename ResultType,
    typename Divides = divides< ResultType > >
struct inclusive_average
{
    typedef DistanceType distance_type;
    typedef ResultType result_type;

    result_type operator()(distance_type d, const Graph& g)
    {
        if (d == numeric_values< distance_type >::infinity())
        {
            return numeric_values< result_type >::infinity();
        }
        else
        {
            return div(result_type(d), result_type(num_vertices(g)));
        }
    }
    Divides div;
};

// The Page type stores the name of each vertex in the graph and
// represents web pages that can be navigated to.
struct WebPage
{
    string name;
};

// The Link type stores an associated probability of traveling
// from one page to another.
struct Link
{
    float probability;
};

// Declare the graph type and its vertex and edge types.
typedef directed_graph< WebPage, Link > Graph;
typedef graph_traits< Graph >::vertex_descriptor Vertex;
typedef graph_traits< Graph >::edge_descriptor Edge;

// The name map provides an abstract accessor for the names of
// each vertex. This is used during graph creation.
typedef property_map< Graph, string WebPage::* >::type NameMap;

// Declare a matrix type and its corresponding property map that
// will contain the distances between each pair of vertices.
typedef exterior_vertex_property< Graph, float > DistanceProperty;
typedef DistanceProperty::matrix_type DistanceMatrix;
typedef DistanceProperty::matrix_map_type DistanceMatrixMap;

// Declare the weight map as an accessor into the bundled
// edge property.
typedef property_map< Graph, float Link::* >::type WeightMap;

// Declare a container and its corresponding property map that
// will contain the resulting mean geodesic distances of each
// vertex in the graph.
typedef exterior_vertex_property< Graph, float > GeodesicProperty;
typedef GeodesicProperty::container_type GeodesicContainer;
typedef GeodesicProperty::map_type GeodesicMap;

static float exclusive_geodesics(const Graph&, DistanceMatrixMap, GeodesicMap);
static float inclusive_geodesics(const Graph&, DistanceMatrixMap, GeodesicMap);

int main(int argc, char* argv[])
{
    // Create the graph, a name map that providse abstract access
    // to the web page names, and the weight map as an accessor to
    // the edge weights (or probabilities).
    Graph g;
    NameMap nm(get(&WebPage::name, g));
    WeightMap wm(get(&Link::probability, g));

    // Read the weighted graph from standard input.
    read_weighted_graph(g, nm, wm, cin);

    // Compute the distances between all pairs of vertices using
    // the Floyd-Warshall algorithm. The weight map was created
    // above so it could be populated when the graph was read in.
    DistanceMatrix distances(num_vertices(g));
    DistanceMatrixMap dm(distances, g);
    floyd_warshall_all_pairs_shortest_paths(g, dm, weight_map(wm));

    // Create the containers and the respective property maps that
    // will contain the mean geodesics averaged both including
    // self-loop distances and excluding them.
    GeodesicContainer exclude(num_vertices(g));
    GeodesicContainer include(num_vertices(g));
    GeodesicMap exmap(exclude, g);
    GeodesicMap inmap(include, g);

    float ex = exclusive_geodesics(g, dm, exmap);
    float in = inclusive_geodesics(g, dm, inmap);

    // Print the mean geodesic distance of each vertex and finally,
    // the graph itself.
    cout << setw(12) << setiosflags(ios::left) << "vertex";
    cout << setw(12) << setiosflags(ios::left) << "excluding";
    cout << setw(12) << setiosflags(ios::left) << "including" << endl;
    graph_traits< Graph >::vertex_iterator i, end;
    for (boost::tie(i, end) = vertices(g); i != end; ++i)
    {
        cout << setw(12) << setiosflags(ios::left) << g[*i].name << setw(12)
             << get(exmap, *i) << setw(12) << get(inmap, *i) << endl;
    }
    cout << "small world (excluding self-loops): " << ex << endl;
    cout << "small world (including self-loops): " << in << endl;

    return 0;
}

float exclusive_geodesics(const Graph& g, DistanceMatrixMap dm, GeodesicMap gm)
{
    // Compute the mean geodesic distances, which excludes distances
    // of self-loops by default. Return the measure for the entire graph.
    return all_mean_geodesics(g, dm, gm);
}

float inclusive_geodesics(const Graph& g, DistanceMatrixMap dm, GeodesicMap gm)
{
    // Create a new measure object for computing the mean geodesic
    // distance of all vertices. This measure will actually be used
    // for both averages.
    inclusive_average< Graph, float, float > m;

    // Compute the mean geodesic distance using the inclusive average
    // to account for self-loop distances. Return the measure for the
    // entire graph.
    return all_mean_geodesics(g, dm, gm, m);
}
//]


#ifndef BAOBZI_TEMPLATE_HPP
#define BAOBZI_TEMPLATE_HPP

#include <fstream>
#include <iostream>
#include <mutex>
#include <queue>
#include <vector>

#include <msgpack.hpp>
#define EIGEN_MATRIX_PLUGIN "baobzi/eigen_matrix_plugin.h"

#define EIGEN_MAX_ALIGN_BYTES 64
#include <Eigen/Core>
#include <Eigen/Dense>
#include <Eigen/LU>
#include <unsupported/Eigen/CXX11/Tensor>

#include <baobzi/header.h>

/// Namespace for baobzi
namespace baobzi {

template <int DIM, int ORDER, int ISET>
class Function;

/// @brief Structure to represent geometric portion of Baobzi nodes
/// @tparam D number of dimensions of box
/// @tparam ISET Instruction set index (dummy variable to force alignment for different instruction sets)
template <int D, int ISET>
struct Box {
    using VEC = Eigen::Vector<double, D>;
    VEC center;      ///< Center of box
    VEC half_length; ///< Half the dimension of the box
    VEC inv_half_length; ///< 1.0 / half the dimension of the box

    Box<D, ISET>() = default; ///< default constructor for msgpack happiness
    /// @brief Constructor, just copies x, hl over
    Box<D, ISET>(const VEC &x, const VEC &hl)
        : center(x), half_length(hl), inv_half_length(VEC::Ones().array() / hl.array()) {}

    /// @brief Check if point lies inside box
    /// @param[in] x point to check
    /// @returns true if point in box, false otherwise
    bool contains(const VEC &x) const {
        VEC dx = (x - center).array().abs();
        return !((dx > half_length).any());
    }

    /// @brief MSGPACK serialization magic
    MSGPACK_DEFINE(center, half_length, inv_half_length);
};

/// @brief Return an estimate of the error for a given set of coefficientsj
/// @param[in] coeffs one or two dimensional Vector/Matrix of coefficients
/// @returns estimation of error given those coefficients
inline double standard_error(const Eigen::Ref<Eigen::MatrixXd> &coeffs) {
    double maxcoeff = 0.0;
    double scaling_factor = 1.0;
    if (coeffs.cols() == 1) {
        int n = coeffs.size();
        for (auto i = n - 2; i < n; ++i)
            maxcoeff = std::max(std::abs(coeffs(i, 0)), maxcoeff);
        scaling_factor = std::max(scaling_factor, std::abs(coeffs(0, 0)));
    } else {
        int n = coeffs.rows();
        for (auto i = 0; i < n; ++i)
            maxcoeff = std::max(std::abs(coeffs(i, n - i - 1)), maxcoeff);

        scaling_factor = std::max(scaling_factor, std::abs(coeffs(n - 1, 0)));
        scaling_factor = std::max(scaling_factor, std::abs(coeffs(0, n - 1)));
    }

    return maxcoeff / scaling_factor;
}

/// @brief Evaluate chebyshev polynomial given a box and a point inside that box
/// @tparam DIM dim of chebyshev polynomial to evaluate
/// @tparam ORDER order of chebyshev polynomial to evaluate
/// @tparam ISET Instruction set index (dummy variable to force alignment for different instruction sets)
/// @param[in] x position of point to evaluate
/// @param[in] box box that x lives in
/// @param[in] coeffs_raw flat column-major vector of coefficients
/// @returns value of interpolating function at x
template <int DIM, int ORDER, int ISET>
inline double cheb_eval(const Eigen::Vector<double, DIM> &x, const Box<DIM, ISET> &box,
                        const std::vector<double, Eigen::aligned_allocator<double>> &coeffs_raw);

template <int ORDER, int ISET>
inline double cheb_eval(const Eigen::Vector<double, 1> &x, const Box<1, ISET> &box,
                        const std::vector<double, Eigen::aligned_allocator<double>> &coeffs_raw) {
    double xd = (x[0] - box.center[0]) * box.inv_half_length[0];

    Eigen::Vector<double, ORDER> Tn;
    Tn[0] = 1.0;
    Tn[1] = xd;
    xd *= 2.0;
    for (int i = 2; i < ORDER; ++i)
        Tn[i] = xd * Tn[i - 1] - Tn[i - 2];

    Eigen::Map<const Eigen::Vector<double, ORDER>> coeffs(coeffs_raw.data());

    return coeffs.dot(Tn);
}

template <int ORDER, int ISET>
inline double cheb_eval(const Eigen::Vector2d &x, const Box<2, ISET> &box,
                        const std::vector<double, Eigen::aligned_allocator<double>> &coeffs_raw) {
    Eigen::Vector2d xinterp = (x - box.center).array() * box.inv_half_length.array();
    Eigen::Matrix<double, 2, ORDER> Tns;
    Tns.col(0).setOnes();
    Tns.col(1) = xinterp;
    xinterp *= 2.0;
    for (int i = 2; i < ORDER; ++i)
        Tns.col(i) = xinterp.array() * Tns.col(i - 1).array() - Tns.col(i - 2).array();

    Eigen::Map<const Eigen::Matrix<double, ORDER, ORDER>> coeffs(coeffs_raw.data());

    return Tns.row(0).transpose().dot(coeffs * Tns.row(1).transpose());
}

template <int ORDER, int ISET>
inline double cheb_eval(const Eigen::Vector3d &x, const Box<3, ISET> &box,
                        const std::vector<double, Eigen::aligned_allocator<double>> &coeffs_raw) {

    Eigen::Vector3d xinterp = (x - box.center).array() * box.inv_half_length.array();

    Eigen::Vector<double, ORDER> Tn[3];
    Tn[0][0] = Tn[1][0] = Tn[2][0] = 1.0;
    for (int i = 0; i < 3; ++i) {
        Tn[i][1] = xinterp[i];
        xinterp[i] *= 2.0;
        for (int j = 2; j < ORDER; ++j)
            Tn[i][j] = xinterp[i] * Tn[i][j - 1] - Tn[i][j - 2];
    }

    double res = 0.0;
    using map_t = Eigen::Map<const Eigen::Matrix<double, ORDER, ORDER>>;
    for (int i = 0; i < ORDER; ++i)
        res += Tn[0][i] * Tn[1].dot(map_t(coeffs_raw.data() + i * ORDER * ORDER) * Tn[2]);

    return res;
}

/// @brief Node in baobzi::FunctionTree. If leaf, contains evaluation data, otherwise children
/// @tparam D dimension of function
/// @tparam ORDER order of evaluation polynomial
/// @tparam ISET instruction set index (dummy variable to force alignment for different instruction sets)
template <int D, int ORDER, int ISET>
class Node {
  public:
    using VEC = Eigen::Vector<double, D>; ///< D dimensional vector type
    using CoeffVec = Eigen::Vector<double, ORDER>; ///< ORDER dimensional vector type
    std::vector<double, Eigen::aligned_allocator<double>> coeffs_; ///< Flattened chebyshev coeffs
    using Func = Function<D, ORDER, ISET>; ///< Type of boabzi function this belongs to
    Box<D, ISET> box_;             ///< Geometric position/size of this node
    uint64_t first_child_idx = -1; ///< First child's index in a flattened list of all nodes
    bool leaf_ = false;            ///< Helper variable to determine if node is a leaf

    Node<D, ORDER, ISET>() = default; ///< Default constructor for msgpack happiness

    /// @brief Construct node from box (without fitting)
    /// @param [in] box box this node represents
    Node<D, ORDER, ISET>(const Box<D, ISET> &box) : box_(box) {}

    /// @brief check if node is leaf
    /// @return true if leaf, false otherwise
    inline bool is_leaf() const { return leaf_; }

    /// @brief Fit node to a given tolerance. If fit succeeds, set leaf and coeffs, otherwise ... don't
    ///
    /// Modifies: Node::leaf_, Node::coeffs_
    /// @param[in] input parameters for fit (function, tol, etc)
    /// @returns true if fit successful, false if not good enough
    bool fit(const baobzi_input_t *input) {
        if constexpr (D == 1) {
            Eigen::Vector<double, ORDER> F;
            CoeffVec xvec =
                Func::get_cheb_nodes(box_.center[0] - box_.half_length[0], box_.center[0] + box_.half_length[0]);

            for (int i = 0; i < ORDER; ++i)
                F(i) = input->func(&xvec[i], input->data);

            Eigen::Vector<double, ORDER> coeffs = Func::VLU_.solve(F);

            if (standard_error(coeffs) > input->tol)
                return false;

            coeffs_.resize(coeffs.size());
            for (int i = 0; i < coeffs.size(); ++i)
                coeffs_[i] = coeffs(i);

            leaf_ = true;
            return true;
        }
        if constexpr (D == 2) {
            Eigen::Matrix<double, ORDER, ORDER> F;
            CoeffVec xvec =
                Func::get_cheb_nodes(box_.center[0] - box_.half_length[0], box_.center[0] + box_.half_length[0]);
            CoeffVec yvec =
                Func::get_cheb_nodes(box_.center[1] - box_.half_length[1], box_.center[1] + box_.half_length[1]);

            for (int i = 0; i < ORDER; ++i) {
                for (int j = 0; j < ORDER; ++j) {
                    double x[2] = {xvec[i], yvec[j]};
                    F(i, j) = input->func(x, input->data);
                }
            }

            Eigen::Matrix<double, ORDER, ORDER> coeffs = Func::VLU_.solve(F);
            coeffs = Func::VLU_.solve(coeffs.transpose()).transpose();

            if (standard_error(coeffs) > input->tol)
                return false;

            coeffs_.resize(coeffs.size());
            for (int i = 0; i < coeffs.size(); ++i)
                coeffs_[i] = coeffs(i);

            leaf_ = true;
            return true;
        }
        if constexpr (D == 3) {
            Eigen::Tensor<double, 3> F(ORDER, ORDER, ORDER);

            CoeffVec xvec =
                Func::get_cheb_nodes(box_.center[0] - box_.half_length[0], box_.center[0] + box_.half_length[0]);
            CoeffVec yvec =
                Func::get_cheb_nodes(box_.center[1] - box_.half_length[1], box_.center[1] + box_.half_length[1]);
            CoeffVec zvec =
                Func::get_cheb_nodes(box_.center[2] - box_.half_length[2], box_.center[2] + box_.half_length[2]);

            for (int i = 0; i < ORDER; ++i) {
                for (int j = 0; j < ORDER; ++j) {
                    for (int k = 0; k < ORDER; ++k) {
                        double x[3] = {xvec[i], yvec[j], zvec[k]};
                        F(i, j, k) = input->func(x, input->data);
                    }
                }
            }

            coeffs_.resize(ORDER * ORDER * ORDER);
            Eigen::Tensor<double, 3> coeffs_tensor(ORDER, ORDER, ORDER);
            using matrix_t = Eigen::Matrix<double, ORDER, ORDER>;
            using map_t = Eigen::Map<matrix_t>;
            using tensor_t = Eigen::Tensor<double, 2>;
            for (int block = 0; block < ORDER; ++block) {
                tensor_t F_block_tensor = F.chip(block, 2);
                map_t F_block(F_block_tensor.data());

                matrix_t coeffs_tmp = Func::VLU_.solve(F_block);
                coeffs_tmp = Func::VLU_.solve(coeffs_tmp.transpose()).transpose();
                coeffs_tensor.chip(block, 2) = Eigen::TensorMap<tensor_t>(coeffs_tmp.data(), ORDER, ORDER);
            }
            for (int block = 0; block < ORDER; ++block) {
                Eigen::Tensor<double, 2> coeffs_tmp = coeffs_tensor.chip(block, 0);
                map_t coeffs_ysolve(coeffs_tmp.data());
                map_t(coeffs_.data() + block * ORDER * ORDER) = Func::VLU_.solve(coeffs_ysolve.transpose()).transpose();
            }

            for (int i = 0; i < ORDER; ++i) {
                for (int j = 0; j < ORDER; ++j) {
                    for (int k = 0; k < ORDER; ++k) {
                        VEC point =
                            (box_.center - box_.half_length).array() +
                            2.0 * VEC{(double)i, (double)j, (double)k}.array() * box_.half_length.array() / ORDER;

                        const double test_val = eval(point);
                        const double actual_val = input->func(point.data(), input->data);
                        const double rel_error = std::abs((actual_val - test_val) / actual_val);

                        if (fabs(actual_val) > 1E-16 && rel_error > input->tol) {
                            coeffs_.clear();
                            coeffs_.shrink_to_fit();
                            return false;
                        }
                    }
                }
            }

            leaf_ = true;
            return true;
        }
    }

    /// @brief eval node at point x
    /// @param[in] x point to evaluate at
    /// @returns function approximation at x
    inline double eval(const VEC &x) const { return cheb_eval<ORDER, ISET>(x, box_, coeffs_); }

    /// @brief MSGPACK serialization magic
    MSGPACK_DEFINE(box_, first_child_idx, leaf_, coeffs_);
};

/// @brief Represent a function in some domain as a tree of chebyshev nodes
/// @tparam DIM dimension of function
/// @tparam ORDER order of evaluation polynomial
/// @tparam ISET instruction set index (dummy variable to force alignment for different instruction sets)
template <int DIM, int ORDER, int ISET>
struct FunctionTree {
    static constexpr int NChild = 1 << DIM; ///< Number of children each node potentially has (2^D)
    static constexpr int Dim = DIM; ///< Dimension of tree
    static constexpr int Order = ORDER; ///< Order of tree

    using VEC = Eigen::Vector<double, DIM>; ///< D dimensional vector type
    std::vector<Node<DIM, ORDER, ISET>> nodes_; ///< Flat list of all nodes in Tree (leaf or otherwise)

    /// @brief Construct tree
    /// @param[in] input parameters for fit (function, tol, etc)
    /// @param[in] box box that this tree lives in
    FunctionTree<DIM, ORDER, ISET>(const baobzi_input_t *input, const Box<DIM, ISET> &box) {
        std::queue<Box<DIM, ISET>> q;
        VEC half_width = box.half_length * 0.5;
        q.push(box);

        uint64_t curr_child_idx = 1;
        while (!q.empty()) {
            int n_next = q.size();
            int node_index = nodes_.size();
            for (int i = 0; i < n_next; ++i) {
                Box<DIM, ISET> box = q.front();
                q.pop();

                nodes_.push_back(Node<DIM, ORDER, ISET>(box));
            }

#pragma omp parallel for
            for (size_t i = 0; i < n_next; ++i) {
                auto &node = nodes_[i + node_index];
                node.fit(input);
            }

            for (int i = 0; i < n_next; ++i) {
                auto &node = nodes_[i + node_index];
                if (!node.is_leaf()) {
                    node.first_child_idx = curr_child_idx;
                    curr_child_idx += NChild;

                    VEC &center = node.box_.center;
                    for (uint64_t child = 0; child < NChild; ++child) {
                        VEC offset;

                        // Extract sign of each offset component from the bits of child
                        // Basically: permute all possible offsets
                        for (int j = 0; j < DIM; ++j) {
                            double signed_hw[2] = {-half_width[j], half_width[j]};
                            offset[j] = signed_hw[(child >> j) & 1];
                        }

                        q.push(Box<DIM, ISET>(center + offset, half_width));
                    }
                }
            }

            half_width *= 0.5;
        }
    }

    FunctionTree<DIM, ORDER, ISET>() = default; ///< Default constructor for msgpack happiness

    /// @brief Find leaf node containing a point via standard pointer traversal
    /// @param[in] x point that the node will contain
    /// @return leaf node containing point x
    inline const Node<DIM, ORDER, ISET> &find_node_traverse(const VEC &x) const {
        auto *node = &nodes_[0];
        while (!node->is_leaf()) {
            uint64_t child_idx = 0;
            for (int i = 0; i < DIM; ++i)
                child_idx = child_idx | ((x[i] > node->box_.center[i]) << i);

            node = &nodes_[node->first_child_idx + child_idx];
        }

        return *node;
    }

    /// @brief eval function approximation at point
    /// @param[in] x point to evaluate function at
    /// @returns function approximation at point x
    inline double eval(const VEC &x) const { return find_node_traverse(x).eval(x); }

    /// @brief msgpack serialization magic
    MSGPACK_DEFINE(nodes_);
};

/// @brief Represents a function in some domain as a grid of baobzi::FunctionTree objects
/// @tparam DIM dimension of function
/// @tparam ORDER order of evaluation polynomial
/// @tparam ISET instruction set index (dummy variable to force alignment for different instruction sets)
template <int DIM, int ORDER, int ISET = 0>
class Function {
  public:
    static constexpr int NChild = 1 << DIM; ///< Number of children each node potentially has (2^D)
    static constexpr int Dim = DIM; ///< Input dimension of function
    static constexpr int Order = ORDER; ///< Order of polynomial representation
    static constexpr int ISet = ISET; ///< Instruction set (dummy param)
    static std::mutex statics_mutex; ///< mutex for locking vandermonde/chebyshev initialization

    using VEC = Eigen::Vector<double, DIM>; ///< D dimensional vector type
    using CoeffVec = Eigen::Vector<double, ORDER>; ///< Order dimensional vector type
    using VanderMat = Eigen::Matrix<double, ORDER, ORDER>; ///< VanderMonde Matrix type

    using DBox = Box<DIM, ISET>; ///< D dimensional box type

    static CoeffVec cosarray_;                  ///< Cached array of cosine values at chebyshev nodes
    static Eigen::PartialPivLU<VanderMat> VLU_; ///< Cached LU decomposition of Vandermonde matrix

    DBox box_;       ///< box representing the domain of our function
    double tol_;     ///< Desired relative tolerance of our approximation
    VEC lower_left_; ///< Bottom 'corner' of our domain

    std::vector<FunctionTree<DIM, ORDER, ISET>> subtrees_; ///< Grid of FunctionTree objects that do the work
    Eigen::Vector<int, DIM> n_subtrees_;                   ///< Number of subtrees in each linear dimension of our space
    VEC bin_size_;                                         ///< Linear dimensions of the bins that our subtrees live

    /// @brief calculate vandermonde matrix
    /// @return Vandermonde matrix for chebyshev polynomials with order=ORDER
    static VanderMat calc_vandermonde() {
        VanderMat V;

        for (int j = 0; j < ORDER; ++j) {
            V(0, j) = 1;
            V(1, j) = cosarray_(j);
        }

        for (int i = 2; i < ORDER; ++i) {
            for (int j = 0; j < ORDER; ++j) {
                V(i, j) = double(2) * V(i - 1, j) * cosarray_(j) - V(i - 2, j);
            }
        }

        return V.transpose();
    }

    /// @brief calculate chebyshev nodes on bounds [lb, ub]
    /// @param[in] lb lower bound
    /// @param[in] ub upper bound
    /// @returns vector of chebyshev nodes scaled within [lb, ub]
    static inline CoeffVec get_cheb_nodes(double lb, double ub) {
        return 0.5 * ((lb + ub) + (ub - lb) * cosarray_.array());
    }

    /// @brief initialize static class variables
    ///
    /// Modifies baobzi::Function::cosarray_, baobzi::Function::VLU_
    static void init_statics() {
        static bool is_initialized = false;
        std::lock_guard<std::mutex> lock(statics_mutex);
        if (is_initialized)
            return;

        for (int i = 0; i < ORDER; ++i)
            cosarray_[ORDER - i - 1] = cos(M_PI * (i + 0.5) / ORDER);
        VLU_ = Eigen::PartialPivLU<VanderMat>(calc_vandermonde());
        is_initialized = true;
    }

    /// @brief Construct our Function object (fits recursively, can be slow)
    /// @param[in] input parameters for fit (function, tol, etc)
    /// @param[in] xp [dim] center of function domain
    /// @param[in] lp [dim] half length of function domain
    Function<DIM, ORDER, ISET>(const baobzi_input_t *input, const double *xp, const double *lp)
        : box_(VEC(xp), VEC(lp)), tol_(input->tol) {
        init_statics();

        VEC l(lp);
        VEC x(xp);
        std::queue<DBox> q;

        for (int i = 0; i < DIM; ++i)
            n_subtrees_[i] = l[i] / l.minCoeff();

        uint8_t max_depth_ = 0;
        q.push(DBox(x, l));

        // Half-width of next children
        VEC half_width = l * 0.5;

        // Breadth first search. Step through each level of the tree and test fit all of the nodes
        // We exit when a level isn't completely filled with parent nodes (rather than leaves)
        // This way we can always avoid redundant traversals by jumping straight to a root node of a subtree
        while (!q.empty()) {
            int n_next = q.size();

            std::vector<Node<DIM, ORDER, ISET>> nodes;
            for (int i = 0; i < n_next; ++i) {
                DBox box = q.front();
                q.pop();

                nodes.emplace_back(Node<DIM, ORDER, ISET>(box));
            }

#pragma omp parallel for
            for (int i = 0; i < nodes.size(); ++i)
                nodes[i].fit(input);

            for (auto &node : nodes) {
                if (!node.is_leaf()) {
                    VEC &center = node.box_.center;
                    for (unsigned child = 0; child < NChild; ++child) {
                        VEC offset;

                        // Extract sign of each offset component from the bits of child
                        // Basically: permute all possible offsets
                        for (int j = 0; j < DIM; ++j) {
                            double signed_hw[2] = {-half_width[j], half_width[j]};
                            offset[j] = signed_hw[(child >> j) & 1];
                        }

                        q.push(DBox(center + offset, half_width));
                    }
                }
            }

            if (!q.empty())
                max_depth_++;

            half_width *= 0.5;
            if ((1 << (DIM * max_depth_)) == q.size())
                n_subtrees_ *= 2;
            else
                break;
        }

        for (int j = 0; j < DIM; ++j)
            bin_size_[j] = 2.0 * box_.half_length[j] / n_subtrees_[j];
        lower_left_ = box_.center - box_.half_length;

        subtrees_.reserve(n_subtrees_.prod());
        for (int i_bin = 0; i_bin < n_subtrees_.prod(); ++i_bin) {
            Eigen::Vector<int, DIM> bins = get_bins(i_bin);

            VEC parent_center = (bins.template cast<double>().array() + 0.5) * bin_size_.array() + lower_left_.array();

            Box<DIM, ISET> root_box = {parent_center, 0.5 * bin_size_};
            subtrees_.push_back(FunctionTree<DIM, ORDER, ISET>(input, root_box));
        }
    }

    /// @brief default constructor for msgpack magic
    Function<DIM, ORDER, ISET>() { init_statics(); };

    /// @brief convert linear bin index to [dim] bin vector
    /// @param[in] i_bin linear index
    /// @returns [dim] bin vector
    inline Eigen::Vector<int, DIM> get_bins(const int i_bin) const {
        if constexpr (DIM == 1)
            return Eigen::Vector<int, DIM>{i_bin};
        else if constexpr (DIM == 2)
            return Eigen::Vector<int, DIM>{i_bin % n_subtrees_[0], i_bin / n_subtrees_[0]};
        else if constexpr (DIM == 3)
            return Eigen::Vector<int, DIM>{i_bin % n_subtrees_[0], (i_bin / n_subtrees_[0]) % n_subtrees_[1],
                                           i_bin / (n_subtrees_[0] * n_subtrees_[1])};
    }

    /// @brief find linear index of bin at a point
    /// @param[in] x [1] position to find bin
    /// @returns linear index of bin that x lives in
    inline int get_linear_bin(const Eigen::Vector<double, 1> &x) const {
        const double x_bin = x[0] - lower_left_[0];
        return x_bin / bin_size_[0];
    }

    /// @brief find linear index of bin at a point
    /// @param[in] x [2] position to find bin
    /// @returns linear index of bin that x lives in
    inline int get_linear_bin(const Eigen::Vector2d &x) const {
        const VEC x_bin = x - lower_left_;
        const Eigen::Vector<int, DIM> bin = (x_bin.array() / bin_size_.array()).template cast<int>();
        return bin[0] + n_subtrees_[0] * bin[1];
    }

    /// @brief find linear index of bin at a point
    /// @param[in] x [3] position to find bin
    /// @returns linear index of bin that x lives in
    inline int get_linear_bin(const Eigen::Vector3d &x) const {
        const VEC x_bin = x - lower_left_;
        const Eigen::Vector<int, DIM> bin = (x_bin.array() / bin_size_.array()).template cast<int>();
        return bin[0] + n_subtrees_[0] * bin[1] + n_subtrees_[0] * n_subtrees_[1] * bin[2];
    }

    /// @brief get constant reference to leaf node that contains a point
    /// @param[in] x point of interest
    /// @returns constant reference to leaf node that contains x
    inline const Node<DIM, ORDER, ISET> &find_node(const VEC &x) const {
        return subtrees_[get_linear_bin(x)].find_node_traverse(x);
    }

    /// @brief eval function approximation at point
    /// @param[in] x point to evaluate function at
    /// @returns function approximation at point x
    inline double eval(const VEC &x) const { return find_node(x).eval(x); }

    /// @brief eval function approximation at point
    /// @param[in] xp [DIM] point to evaluate function at
    /// @returns function approximation at point xp
    inline double eval(const double *xp) const { return eval(VEC(xp)); }

    /// @brief eval function approximation at point
    /// @param[in] x [DIM] point to evaluate function at
    /// @returns function approximation at point x
    inline double operator()(const VEC &x) const { return eval(x); }

    /// @brief eval function approximation at point
    /// @param[in] x point to evaluate function at
    /// @returns function approximation at point x
    inline double operator()(const double *x) const { return eval(x); }

    /// @brief save function approximation to file
    /// @param[in] filename path to save file at
    void save(const char *filename) {
        std::ofstream ofs(filename, std::ofstream::binary | std::ofstream::out);
        baobzi_header_t params{Dim, Order, BAOBZI_HEADER_VERSION};
        msgpack::pack(ofs, params);
        msgpack::pack(ofs, *this);
    }

    /// @brief msgpack serialization magic
    MSGPACK_DEFINE_MAP(box_, subtrees_, n_subtrees_, tol_, lower_left_, bin_size_);
};

template <int DIM, int ORDER, int ISET>
std::mutex Function<DIM, ORDER, ISET>::statics_mutex;

template <int DIM, int ORDER, int ISET>
typename Function<DIM, ORDER, ISET>::CoeffVec Function<DIM, ORDER, ISET>::cosarray_;

template <int DIM, int ORDER, int ISET>
Eigen::PartialPivLU<typename Function<DIM, ORDER, ISET>::VanderMat> Function<DIM, ORDER, ISET>::VLU_;
} // namespace baobzi

#endif


#include "project/ExponentialSmoothing.hpp"
#include "project/vecPush.hpp"
#include <armadillo>
#include <math.h>

ExponentialSmoothing::ExponentialSmoothing(arma::vec &time_series, float alpha){
    param_alpha = alpha;
    ts = time_series;
}

arma::vec calculateWeigths(float alpha, int length) {
    arma::vec weigths = arma::zeros(length);
    weigths(0) = alpha;
    for (int ii = 1; ii < length; ii++){
        weigths(ii) = alpha*pow((1-alpha),ii);
    };

    return weigths;
}

void ExponentialSmoothing::fit(){
    weigths = calculateWeigths(param_alpha, ts.n_rows);
}

float weigthedMean(arma::vec &x, arma::vec &weigths){
    float weigthed_mean = arma::sum(x%weigths)/arma::sum(weigths);
    return weigthed_mean;
}

void vec_push(arma::vec &v, float value) {
    arma::vec av(1);
    av.at(0) = value;
    v.insert_rows(v.n_rows, av.row(0));
}

arma::vec ExponentialSmoothing::forecast(int horizon){
    arma::vec forecast = arma::zeros(horizon);
    arma::vec tmp_ts = ts;
    for (int ii = 0; ii < horizon; ii++){
        float point_forecast = weigthedMean(tmp_ts, weigths);
        forecast(ii) = point_forecast;
        vec_push(tmp_ts, )
    }
}


#include "problemes.h"
#include "arithmetique.h"

#include <fstream>
#include <boost/algorithm/string.hpp>

typedef unsigned long long nombre;
typedef std::vector<nombre> vecteur;
typedef std::vector<vecteur> matrice;

ENREGISTRER_PROBLEME(99, "Largest exponential") {
    // Comparing two numbers written in index form like 211 and 37 is not difficult, as any calculator would confirm
    // that 2^11 = 2048 < 3^7 = 2187.
    //
    // However, confirming that 632382^518061 > 519432^525806 would be much more difficult, as both numbers contain over
    // three million digits.
    //
    // Using base_exp.txt (right click and 'Save Link/Target As...'), a 22K text file containing one thousand lines with
    // a base/exponent pair on each line, determine which line number has the greatest numerical value.
    //
    // NOTE: The first two lines in the file represent the numbers in the example given above.
    std::ifstream ifs("data/p099_base_exp.txt");
    nombre resultat = 0;
    nombre numero_ligne = 1;
    std::string ligne;
    long double maximum = 0;
    while (ifs >> ligne) {
        std::vector<std::string> v;
        boost::split(v, ligne, boost::is_any_of(","));
        const long double log = std::log(std::stold(v.front())) * std::stold(v.back());
        if (log > maximum) {
            resultat = numero_ligne;
            maximum = log;
        }
        ++numero_ligne;
    }
    return std::to_string(resultat);
}


#ifndef IP3_DET_MODEL_HPP_INCLUDED
#define IP3_DET_MODEL_HPP_INCLUDED
#include <vector>
#include <iostream>
#include <cmath>
#include <boost/math/constants/constants.hpp>
#include "astron_utility_functions.hpp"
//const double pi = boost::math::constants::pi<double>();

//DECLATATIONS FOR IP3_SYSTEM
class IP3
{
   private:

      double a2 = 0.2E-03;     //    0.2         1/(micro M * sec)
      double d1 = 0.13;   //     0.13        micro M
      double d2 = 1.049;  //     1.049       micro M
      double d3 = 0.9434;   //   0.9434      micro M
      double d5 = 0.08234; //    0.08234     micro M

      double c0 = 2.0;  //       2.0         micro M
      double c1 = 0.185;   //                Dimensionless
      double v1 = 6.0E-03; //        6.0         1/sec
      double v2 = 0.11E-03;//        0.11        1/sec
      double v3 = 0.9E-03;     //    0.9         1/ (micro M * sec)
      double k3 = 0.1;   //      0.1         micro M

   public:

      double CaER = 0.0;      // micro M (Calcium concentration in the ER)
      double ip3_conc = 0.0;      // micro M (Given IP3 concentration in the cytosol)
      double ca_conc = 0.0;

      double ip3_tau = 0.0;
      double ip3_rate = 0.0;
      double ip3_thres = 0.0;
      double ip3_gen = 0.0;
      double ip3_equ = 0.0;

//------------------ CONSTRUCTORS

   /* IP3 class implicit constructor */
   IP3(): CaER(0.0), ca_conc(0.0), ip3_conc(0.0)
   {
   };

  /* ASTRO class explicit constructor */
   IP3( double CaER_, double ca_conc_, double ip3_conc_ ): CaER(CaER_), ca_conc(ca_conc_), ip3_conc(ip3_conc_)
   {
   };
//------------------ Function declarations
   void set_CaER();

   double m_inf();
   double h_inf();
   double h_tau();

   template <class State, class Deriv >
   void operator() ( const State &x, Deriv &dxdt , const double  t );
};
//--------------------m_inf Function
double IP3::m_inf()
{
   double value =  ( ip3_conc / ( ip3_conc + d1 ) ) * ( ca_conc / (ca_conc + d5) ) ;
   return value;
};
//--------------------h_tau Function
double IP3::h_tau()
{
   double Q2 = d2 * ((ip3_conc + d1)/(ip3_conc + d3));
   double value  = 1 / (a2 * (Q2 + ca_conc) );
   return value;
}

//--------------------h_inf Function
double IP3::h_inf()
{
   double Q2 = d2 * ((ip3_conc + d1)/(ip3_conc + d3));
   double value = Q2/(Q2+ca_conc);
   return value;
};
//--------------------Set CaER Function
void IP3::set_CaER()
{
   this->CaER = (c0 - ca_conc) / c1;
   //std::cout << "Set_CaER = " << (c0 - ca_conc)/c1 << "\t" << CaER << "\n";
};
//------- ASTRO class ODE Function
template <class State, class Deriv >
void IP3::operator() ( const State &x_, Deriv &dxdt_ , const double t )
{
   typename boost::range_iterator< const State >::type x = boost::begin( x_ );
   typename boost::range_iterator< Deriv >::type dxdt = boost::begin( dxdt_ );

   //std::cout << CaER << "\n";

   ca_conc = x[1]; // Set Calcium concentration
   ip3_conc = x[2]; // Set ip3 concentration

   set_CaER(); // Update ER calcium level

   dxdt[0] = ( h_inf() - x[0] ) / h_tau() ; // dh/dt

   double JCh = ( c1 * v1 * pow(m_inf(),3) * pow(x[0],3) * (x[1] - CaER) ); //   J_channel
   double JPump = ( v3 * pow(x[1],2) ) / ( pow(k3,2) + pow(x[1],2) ) ;   //    J_Pump
   double JLeak = c1 * v2 * ( x[1] - CaER );   //    J_Leak

   dxdt[1] = - (JCh + JPump + JLeak) ;// dCa/dt
   dxdt[2] =  (1/ip3_tau) * (ip3_equ - x[2])  + (ip3_rate * heaviside(ip3_gen,ip3_thres)) ;//dip3/dt

};

#endif // IP3_DET_MODEL_HPP_INCLUDED