#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <Eigen/Eigen>

// http://ankokudan.org/d/dl/pdf/pdf-eigennote.pdf

Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> exp( Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> &output)
{
    std::cout << "output.rows() = " << output.rows() << std::endl;
    std::cout << "output.cols() = " << output.cols() << std::endl;
    Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> o = output;
    for(int i=0; i<output.cols(); i++)
      {
        o(0, i) = 1.0/(1.0 + exp(o(0, i)));
      }
    return o;
}

int main(void)
{
  Eigen::Matrix<float, 2,2> m1 = Eigen::Matrix<float, 2, 2>::Random(2,2);
  Eigen::Matrix<float, 2,2> m2;
  Eigen::Matrix<float, 2,2> m3;
  Eigen::Matrix<float, 2, 1> v1;
  Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> A;
  Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> B;

  m2 <<
    1, 0,
    1, 2;

  m3 <<
    0, 1,
    2, 0;

  v1 << 1, 4;

  std::cout << "m1 = " << m1 << std::endl;
  std::cout << "m2 = " << m2 << std::endl;
  std::cout << "m3 = " << m3 << std::endl;
  std::cout << "m2 - m3 = " << m2 - m3 << std::endl;
  std::cout << "m2 * m3 = " << m2 * m3 << std::endl;
  std::cout << "v1 = " << v1 << std::endl;

  std::cout << "m2.size() = " << m2.size() << std::endl;
  std::cout << "v1.rows() = " << v1.rows() << std::endl;
  std::cout << "v1.cols() = " << v1.cols() << std::endl;

  std::cout << "v1 * m3 = " << v1.transpose() * m3 << std::endl;
  A = v1.transpose() * m3;

  std::cout << "v1.rows() = " << v1.rows() << std::endl;
  std::cout << "v1.cols() = " << v1.cols() << std::endl;
  std::cout << "A.rows() = " << A.rows() << std::endl;
  std::cout << "A.cols() = " << A.cols() << std::endl;

  B = exp(A);

  std::cout << "B.rows() = " << B.rows() << std::endl;
  std::cout << "B.cols() = " << B.cols() << std::endl;
  std::cout << "B = " << B << std::endl;

  return 0;
}


#ifndef LATTICE_BETHELATTICE_HPP
#define LATTICE_BETHELATTICE_HPP

#include <string>
#include <vector>
#include <boost/lexical_cast.hpp>

namespace lattice {

class BetheLattice {
public:
  static std::string name() { return "Bethe Lattice";}
  BetheLattice(unsigned int LatticeSize , unsigned int NeighboringNumber) : num_stage(LatticeSize), n_adj(NeighboringNumber) {}
  void create_table(std::vector<std::vector < int > >& table){
    for(int stage = 0; stage < num_stage+1 ; ++stage){
      int num_each_stage = calc_num_each_stage(stage);
      for(int tagg = 0; tagg < num_each_stage ; ++tagg){
        int number  = numberize(stage,tagg);
        if(stage == num_stage){
          for(int i = 0; i < n_adj - 1; ++i){
            table[number][i] = number;
          }
        }
        else if(stage == 0){
          for(int i = 0; i < n_adj; ++i){
            table[number][i] = i+1;
            table[i+1][n_adj-1] = number;
          }
        }
        else{
          for(int i = 0; i < n_adj - 1; ++i){
            int pair_number = numberize(stage+1, tagg*(n_adj-1)+i);
            table[number][i] = pair_number;
            table[pair_number][n_adj-1] = number;
          }
        }
      }
    }
  }

  int numberize(const int stage, const int tagg){// j <= std::pow(n_adj-1,i-1)* n_adj - 1
    if(stage ==0) return 0;
    else return BetheCalc(stage) + 1 + tagg;
  }

  int latticize(int d, const int l){ //d selects stage_number or tagg_number (0, 1) ex. 0 denotes stage, 1 denotes tagg_number
    int stage = num_stage;
    int tagg = 1;
    int result = 0;
    while(tagg != 0){
      tagg = l % BetheCalc(stage);
      --stage;
      if(stage < 2) break;
    }
    if(stage < 2){
      if(l == 0){
        if(d==0) result = 0;
        else result = 0;
      }
      else{
        if(d==0)result = stage;
        else result= l;
      }
    }
   return result;
  }

  int set_num_particles(int Ns){
    if(Ns == 0) return  1;
    else return BetheCalc(Ns+1) + 1;
  }

  int BetheCalc(int stage ){ //N_t => 0
    int num_t = 0;
    for(int k = 1 ; k <= stage-1; ++k){
     num_t += calc_num_each_stage(k);
    }
    return num_t;
  }

  int calc_num_each_stage(int stage){
    double temp;
    double stage_t = boost::lexical_cast<double>(stage);
    double n_adj_t = boost::lexical_cast<double>(n_adj);
    if(stage == 0) temp = 1;
    else temp = n_adj_t * std::pow(n_adj_t-1, stage_t-1) ;
    return boost::lexical_cast<int>(temp);
 }

  int number_adjacent() {return n_adj;}

private:
  unsigned int num_stage;
  unsigned int n_adj;

};//Bethe lattice end

} // end namespace

#endif //LATTICE_BETHELATTICE_HPP


#pragma once

/**
 * Reduced product of a numerical domain and the congruence domain.
 **/

#include <crab/domains/abstract_domain.hpp>
#include <crab/domains/abstract_domain_specialized_traits.hpp>
#include <crab/domains/combined_domains.hpp>
#include <crab/domains/congruences.hpp>
#include <crab/domains/interval.hpp>
#include <crab/support/stats.hpp>

#include <boost/optional.hpp>

namespace crab {
namespace domains {

/*
 *  The reduce operator based on "Static Analysis of Arithmetical
 *  Congruences" by P. Granger published in International Journal of
 *  Computer Mathematics, 1989.
 */
template <typename Number> class interval_congruence {
public:
  using interval_congruence_t = interval_congruence<Number>;

private:
  using interval_t = ikos::interval<Number>;
  using congruence_t = ikos::congruence<Number>;
  using bound_t = ikos::bound<Number>;

private:
  interval_t m_first;
  congruence_t m_second;

private:
  interval_congruence(bool is_bottom)
      : m_first(is_bottom ? interval_t::bottom() : interval_t::top()),
        m_second(is_bottom ? congruence_t::bottom() : congruence_t::top()) {}

public:
  static interval_congruence_t top() { return interval_congruence(false); }

  static interval_congruence_t bottom() { return interval_congruence(true); }

private:
  inline Number abs(Number x) { return x < 0 ? -x : x; }

  // operator % can return a negative number
  // mod(a, b) always returns a positive number
  inline Number mod(Number a, Number b) {
    Number m = a % b;
    if (m < 0)
      return m + b;
    else
      return m;
  }

  // R(c,a) is the least element of c greater or equal than a
  inline Number R(congruence_t c, Number a) {
    Number m = c.get_modulo();
    Number p = c.get_remainder();
    return a + mod(p - a, abs(m));
  }

  // L(c,a) is the greatest element of c smaller or equal than a
  inline Number L(congruence_t c, Number a) {
    Number m = c.get_modulo();
    Number p = c.get_remainder();
    return a - mod(a - p, abs(m));
  }

public:
  interval_congruence(Number n)
      : m_first(interval_t(n)), m_second(congruence_t(n)) {}

  interval_congruence(interval_t i, congruence_t c) : m_first(i), m_second(c) {
    reduce();
  }

  interval_congruence(interval_t i)
      : m_first(i), m_second(congruence_t::top()) {
    reduce();
  }

  interval_congruence(congruence_t c)
      : m_first(interval_t::top()), m_second(c) {
    reduce();
  }

  interval_congruence(const interval_congruence &other)
      : m_first(other.m_first), m_second(other.m_second) {}

  interval_congruence(interval_congruence &&other)
      : m_first(std::move(other.m_first)), m_second(std::move(other.m_second)) {
  }

  interval_congruence_t &operator=(const interval_congruence_t &other) {
    if (this != &other) {
      m_first = other.m_first;
      m_second = other.m_second;
    }
    return *this;
  }

  interval_congruence_t &operator=(interval_congruence_t &&other) {
    if (this != &other) {
      m_first = std::move(other.m_first);
      m_second = std::move(other.m_second);
    }
    return *this;
  }

  bool is_bottom() { return m_first.is_bottom() || m_second.is_bottom(); }

  bool is_top() { return m_first.is_top() && m_second.is_top(); }

  interval_t &first() { return m_first; }
  const interval_t &first() const { return m_first; }

  congruence_t &second() { return m_second; }
  const congruence_t &second() const { return m_second; }

  /*
     Let (i,c) be a pair of interval and congruence these are the
     main rules described by Granger:

     if (c.is_bottom() || i.is_bottom()) (bottom(), bottom());
     if (c = 0Z+a and a \notin i)        (bottom(), bottom());
     if (c = 0Z+a)                       ([a,a]   , c);
     if (i=[a,b] and R(c,a) > L(c,b))    (bottom(), bottom());
     if (i=[a,b])                        ([R(c,a), L(c,b)], c);
     if (i=[a,+oo])                      ([R(c,a), +oo], c);
     if (i=[-oo,b])                      ([-oo, L(c,b)], c);
     otherwise                           (i,c)
   */

  void reduce() {
    interval_t &i = first();
    congruence_t &c = second();

    if (i.is_bottom() || c.is_bottom()) {
      i = interval_t::bottom();
      c = congruence_t::bottom();
    }

    // congruence is top and interval is a singleton
    if (c.is_top()) {
      boost::optional<Number> n = i.singleton();
      if (n) {
        c = congruence_t(*n);
      }
      return;
    }

    Number modulo = c.get_modulo();
    if (modulo == 0) {
      // congruence is a singleton so we refine the interval
      interval_t a(c.get_remainder());
      if (!(a <= i)) {
        i = interval_t::bottom();
        c = congruence_t::bottom();
      } else {
        i = a;
      }
    } else {
      // refine lower and upper bounds of the interval using
      // congruences
      bound_t lb = i.lb();
      bound_t ub = i.ub();

      if (lb.is_finite() && ub.is_finite()) {
        Number x = R(c, *(lb.number()));
        Number y = L(c, *(ub.number()));
        if (x > y) {
          i = interval_t::bottom();
          c = congruence_t::bottom();
        } else if (x == y) {
          i = interval_t(x);
          c = congruence_t(x);
        } else {
          i = interval_t(bound_t(x), bound_t(y));
        }
      } else if (lb.is_finite()) {
        Number x = R(c, *(lb.number()));
        i = interval_t(bound_t(x), bound_t::plus_infinity());
      } else if (ub.is_finite()) {
        Number y = L(c, *(ub.number()));
        i = interval_t(bound_t::minus_infinity(), bound_t(y));
      } else {
        // interval is top
      }
    }
  }

  void write(crab_os &o) const {
    o << "(" << m_first << ", " << m_second << ")";
  }

public:
  interval_congruence_t operator+(interval_congruence_t x) {
    return interval_congruence_t(m_first.operator+(x.first()),
                                 m_second.operator+(x.second()));
  }

  interval_congruence_t operator-(interval_congruence_t x) {
    return interval_congruence_t(m_first.operator-(x.first()),
                                 m_second.operator-(x.second()));
  }

  interval_congruence_t operator*(interval_congruence_t x) {
    return interval_congruence_t(m_first.operator*(x.first()),
                                 m_second.operator*(x.second()));
  }

  interval_congruence_t operator/(interval_congruence_t x) {
    return interval_congruence_t(m_first.operator/(x.first()),
                                 m_second.operator/(x.second()));
  }

  interval_congruence_t operator|(interval_congruence_t other) {
    return interval_congruence_t(m_first | other.m_first,
                                 m_second | other.m_second);
  }

  interval_congruence_t operator&(interval_congruence_t other) {
    return interval_congruence_t(m_first & other.m_first,
                                 m_second & other.m_second);
  }

public:
  // division and remainder operations

  interval_congruence_t SDiv(interval_congruence_t x) {
    return interval_congruence_t(m_first.SDiv(x.first()),
                                 m_second.SDiv(x.second()));
  }

  interval_congruence_t UDiv(interval_congruence_t x) {
    return interval_congruence_t(m_first.UDiv(x.first()),
                                 m_second.UDiv(x.second()));
  }

  interval_congruence_t SRem(interval_congruence_t x) {
    return interval_congruence_t(m_first.SRem(x.first()),
                                 m_second.SRem(x.second()));
  }

  interval_congruence_t URem(interval_congruence_t x) {
    return interval_congruence_t(m_first.URem(x.first()),
                                 m_second.URem(x.second()));
  }

  // bitwise operations

  interval_congruence_t Trunc(unsigned width) {
    return interval_congruence_t(m_first.Trunc(width), m_second.Trunc(width));
  }

  interval_congruence_t ZExt(unsigned width) {
    return interval_congruence_t(m_first.ZExt(width), m_second.ZExt(width));
  }

  interval_congruence_t SExt(unsigned width) {
    return interval_congruence_t(m_first.SExt(width), m_second.SExt(width));
  }

  interval_congruence_t And(interval_congruence_t x) {
    return interval_congruence_t(m_first.And(x.first()),
                                 m_second.And(x.second()));
  }

  interval_congruence_t Or(interval_congruence_t x) {
    return interval_congruence_t(m_first.Or(x.first()),
                                 m_second.Or(x.second()));
  }

  interval_congruence_t Xor(interval_congruence_t x) {
    return interval_congruence_t(m_first.Xor(x.first()),
                                 m_second.Xor(x.second()));
  }

  interval_congruence_t Shl(interval_congruence_t x) {
    return interval_congruence_t(m_first.Shl(x.first()),
                                 m_second.Shl(x.second()));
  }

  interval_congruence_t LShr(interval_congruence_t x) {
    return interval_congruence_t(m_first.LShr(x.first()),
                                 m_second.LShr(x.second()));
  }

  interval_congruence_t AShr(interval_congruence_t x) {
    return interval_congruence_t(m_first.AShr(x.first()),
                                 m_second.AShr(x.second()));
  }
};

template <typename Number>
inline crab::crab_os &operator<<(crab::crab_os &o,
                                 const interval_congruence<Number> &v) {
  v.write(o);
  return o;
}

// Reduced product of a numerical domain with interval x congruences.
template <typename NumAbsDom>
class numerical_congruence_domain final
    : public abstract_domain_api<numerical_congruence_domain<NumAbsDom>> {

  using rnc_domain_t = numerical_congruence_domain<NumAbsDom>;
  using abstract_domain_t = abstract_domain_api<rnc_domain_t>;

public:
  using typename abstract_domain_t::disjunctive_linear_constraint_system_t;
  using typename abstract_domain_t::interval_t;
  using typename abstract_domain_t::linear_constraint_system_t;
  using typename abstract_domain_t::linear_constraint_t;
  using typename abstract_domain_t::linear_expression_t;
  using typename abstract_domain_t::reference_constraint_t;
  using typename abstract_domain_t::variable_or_constant_t;
  using typename abstract_domain_t::variable_t;
  using typename abstract_domain_t::variable_vector_t;
  using typename abstract_domain_t::variable_or_constant_vector_t;
  using number_t = typename NumAbsDom::number_t;
  using varname_t = typename NumAbsDom::varname_t;

  using congruence_domain_t = ikos::congruence_domain<number_t, varname_t>;
  using interval_congruence_t = interval_congruence<number_t>;

private:
  using reduced_domain_product2_t =
      reduced_domain_product2<number_t, varname_t, NumAbsDom, congruence_domain_t>;

  reduced_domain_product2_t m_product;

  numerical_congruence_domain(const reduced_domain_product2_t &product)
      : m_product(product) {}

  void reduce_variable(const variable_t &v) {
    crab::CrabStats::count(domain_name() + ".count.reduce");
    crab::ScopedCrabStats __st__(domain_name() + ".reduce");

    if (is_bottom()) {
      return;
    }

    auto i = m_product.first()[v]; // project on intervals
    auto c = m_product.second().to_congruence(v);
    interval_congruence_t val(i, c);

    if (val.is_bottom()) {
      set_to_bottom();
    } else {
      if (val.first() != i) {
        // FIXME: method set is not part of the abstract_domain API so
        // it might not compile.
        m_product.first().set(v, val.first());
      }

      if (val.second() != c) {
        // FIXME: method set is not part of the abstract_domain API so
        // it might not compile.
        m_product.second().set(v, val.second());
      }
    }
  }

public:
  rnc_domain_t make_top() const override {
    reduced_domain_product2_t dom_prod;
    return rnc_domain_t(dom_prod.make_top());
  }

  rnc_domain_t make_bottom() const override {
    reduced_domain_product2_t dom_prod;
    return rnc_domain_t(dom_prod.make_bottom());
  }

  void set_to_top() override {
    reduced_domain_product2_t dom_prod;
    rnc_domain_t abs(dom_prod.make_top());
    std::swap(*this, abs);
  }

  void set_to_bottom() override {
    reduced_domain_product2_t dom_prod;
    rnc_domain_t abs(dom_prod.make_bottom());
    std::swap(*this, abs);
  }

  numerical_congruence_domain() : m_product() {}

  numerical_congruence_domain(const rnc_domain_t &other)
      : m_product(other.m_product) {}

  rnc_domain_t &operator=(const rnc_domain_t &other) {
    if (this != &other)
      m_product = other.m_product;

    return *this;
  }

  bool is_bottom() const override { return m_product.is_bottom(); }

  bool is_top() const override { return m_product.is_top(); }

  bool operator<=(const rnc_domain_t &other) const override {
    return m_product <= other.m_product;
  }

  bool operator==(const rnc_domain_t &other) const {
    return m_product == other.m_product;
  }

  void operator|=(const rnc_domain_t &other) override {
    m_product |= other.m_product;
  }

  rnc_domain_t operator|(const rnc_domain_t &other) const override {
    return rnc_domain_t(m_product | other.m_product);
  }

  rnc_domain_t operator&(const rnc_domain_t &other) const override {
    return rnc_domain_t(m_product & other.m_product);
  }

  rnc_domain_t operator||(const rnc_domain_t &other) const override {
    return rnc_domain_t(m_product || other.m_product);
  }

  rnc_domain_t widening_thresholds(
      const rnc_domain_t &other,
      const iterators::thresholds<number_t> &ts) const override {
    return rnc_domain_t(m_product.widening_thresholds(other.m_product, ts));
  }

  rnc_domain_t operator&&(const rnc_domain_t &other) const override {
    return rnc_domain_t(m_product && other.m_product);
  }

  // pre: x is already reduced
  void set(const variable_t &v, interval_congruence_t x) {
    m_product.first().set(v, x.first());
    m_product.second().set(v, x.second());
  }

  interval_congruence_t get(const variable_t &v) {
    return interval_congruence_t(m_product.first()[v],
                                 m_product.second().to_congruence(v));
  }

  interval_t operator[](const variable_t &v) override {
    interval_congruence_t x = get(v);
    return x.first();
  }

  void operator+=(const linear_constraint_system_t &csts) override {
    m_product += csts;

    if (!is_bottom()) {
      for (auto const &cst : csts) {
        for (auto const &v : cst.variables()) {
          reduce_variable(v);
          if (is_bottom()) {
            return;
          }
        }
      }
    }
  }

  void operator-=(const variable_t &v) override { m_product -= v; }

  void assign(const variable_t &x, const linear_expression_t &e) override {
    m_product.assign(x, e);
    reduce_variable(x);
  }

  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    m_product.apply(op, x, y, z);
    reduce_variable(x);
  }

  void apply(arith_operation_t op, const variable_t &x, const variable_t &y,
             number_t k) override {
    m_product.apply(op, x, y, k);
    reduce_variable(x);
  }

  void backward_assign(const variable_t &x, const linear_expression_t &e,
                       const rnc_domain_t &invariant) override {
    m_product.backward_assign(x, e, invariant.m_product);
    // reduce the variables in the right-hand side
    for (auto const &v : e.variables())
      reduce_variable(v);
  }

  void backward_apply(arith_operation_t op, const variable_t &x,
                      const variable_t &y, number_t k,
                      const rnc_domain_t &invariant) override {
    m_product.backward_apply(op, x, y, k, invariant.m_product);
    // reduce the variables in the right-hand side
    reduce_variable(y);
  }

  void backward_apply(arith_operation_t op, const variable_t &x,
                      const variable_t &y, const variable_t &z,
                      const rnc_domain_t &invariant) override {
    m_product.backward_apply(op, x, y, z, invariant.m_product);
    // reduce the variables in the right-hand side
    reduce_variable(y);
    reduce_variable(z);
  }

  // cast operators

  void apply(int_conv_operation_t op, const variable_t &dst,
             const variable_t &src) override {
    m_product.apply(op, dst, src);
    reduce_variable(dst);
  }

  // bitwise operators

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             const variable_t &z) override {
    m_product.apply(op, x, y, z);
    reduce_variable(x);
  }

  void apply(bitwise_operation_t op, const variable_t &x, const variable_t &y,
             number_t k) override {
    m_product.apply(op, x, y, k);
    reduce_variable(x);
  }

  void select(const variable_t &lhs, const linear_constraint_t &cond,
              const linear_expression_t &e1,
              const linear_expression_t &e2) override {
    m_product.select(lhs, cond, e1, e2);
    reduce_variable(lhs);
  }

  /// numerical_congruence_domain implements only standard abstract
  /// operations of a numerical domain so it is intended to be used as
  /// a leaf domain in the hierarchy of domains.
  BOOL_OPERATIONS_NOT_IMPLEMENTED(rnc_domain_t)
  ARRAY_OPERATIONS_NOT_IMPLEMENTED(rnc_domain_t)
  REGION_AND_REFERENCE_OPERATIONS_NOT_IMPLEMENTED(rnc_domain_t)

  void forget(const variable_vector_t &variables) override {
    m_product.forget(variables);
  }

  void project(const variable_vector_t &variables) override {
    m_product.project(variables);
  }

  void expand(const variable_t &var, const variable_t &new_var) override {
    m_product.expand(var, new_var);
  }

  void normalize() override { m_product.normalize(); }

  void minimize() override { m_product.minimize(); }

  void write(crab_os &o) const override { m_product.write(o); }

  linear_constraint_system_t to_linear_constraint_system() const override {
    return m_product.to_linear_constraint_system();
  }

  disjunctive_linear_constraint_system_t
  to_disjunctive_linear_constraint_system() const override {
    return m_product.to_disjunctive_linear_constraint_system();
  }

  std::string domain_name() const override { return m_product.domain_name(); }

  void rename(const variable_vector_t &from,
              const variable_vector_t &to) override {
    m_product.rename(from, to);
  }

  /* begin intrinsics operations */
  void intrinsic(std::string name,
		 const variable_or_constant_vector_t &inputs,
                 const variable_vector_t &outputs) override {
    m_product.intrinsic(name, inputs, outputs);
  }

  void backward_intrinsic(std::string name,
			  const variable_or_constant_vector_t &inputs,
                          const variable_vector_t &outputs,
                          const rnc_domain_t &invariant) override {
    m_product.backward_intrinsic(name, inputs, outputs, invariant.m_product);
  }
  /* end intrinsics operations */

}; // class numerical_congruence_domain

template <typename NumAbsDom>
struct abstract_domain_traits<numerical_congruence_domain<NumAbsDom>> {
  using number_t = typename NumAbsDom::number_t;
  using varname_t = typename NumAbsDom::varname_t;
};

} // end namespace domains
} // namespace crab


#include "multivariate_guassian.hpp"

#include "multinomial.hpp"
#include "utils.hpp"

#include <Eigen/Dense>
#include <vector>

namespace FilterModel {

MultivariateGuassian::MultivariateGuassian(const std::vector<double> mean_in,
                                           const std::vector<std::vector<double>> covariance_in)
    : mean(mean_in.size()), covariance(covariance_in.size(), covariance_in.size()) {
    for (int i = 0; i < mean_in.size(); ++i) {
        mean[i] = mean_in.at(i);
    }

    for (int i = 0; i < covariance_in.size(); ++i) {
        const std::vector<double>& row = covariance_in.at(i);
        for (int j = 0; j < row.size(); ++j) {
            covariance(i, j) = row.at(j);
            covariance(j, i) = row.at(j);
        }
    }

    covariance_inverse = covariance.inverse();
};

MultivariateGuassian MultivariateGuassian::from_multinomial(int n, const std::vector<double>& p) {
    std::vector<double> mean;
    for (double pi : p) {
        mean.push_back(n * pi);
    }

    std::vector<std::vector<double>> covariance;
    for (int row = 0; row < p.size(); ++row) {
        std::vector<double> covariance_row;
        for (int column = 0; column < row; ++column) {
            covariance_row.push_back(-n * p.at(row) * p.at(column));
        }
        covariance_row.push_back(n * p.at(row) * (1.0 - p.at(row)));
        covariance.push_back(covariance_row);
    }

    MultivariateGuassian out(mean, covariance);

    return out;
}

MultivariateGuassian MultivariateGuassian::from_multinomial(const Multinomial& m) {
    std::vector<double> new_p(m.p);
    new_p.pop_back();
    return MultivariateGuassian::from_multinomial(m.n, new_p);
}

double MultivariateGuassian::density(std::vector<double> point) const {
    if (point.size() != mean.size()) {
        BOOST_LOG_TRIVIAL(fatal) << "MultivariateGuassian density input must have the same number "
                                    "of dimensions as the distribution.";
        assert(false);
    }

    // TODO(joschnei): There has to be a better way to do this copy.
    Eigen::VectorXd x(point.size());
    for (int i = 0; i < point.size(); ++i) {
        x[i] = point.at(i);
    }

    Eigen::VectorXd diff = (x - mean).eval();
    Eigen::RowVectorXd mult1 = (diff.transpose() * covariance_inverse).eval();
    double mult2 = (mult1 * diff).eval()(0, 0);
    double det = covariance.determinant();
    double val = std::exp(-1.0 / 2.0 * mult2) / std::sqrt(std::pow(2 * M_PI, mean.size()) * det);
    return val;
}

void MultivariateGuassian::shift_hyperplanes(std::vector<std::vector<double>>& hyperplanes) {
    for (std::vector<double>& hyperplane : hyperplanes) {
        double constant = hyperplane.back();
        for (int i = 0; i < hyperplane.size() - 1; ++i) {
            constant -= hyperplane.at(i) * mean[i];
        }
        hyperplane[hyperplane.size() - 1] = constant;
    }
}

std::vector<std::vector<double>> MultivariateGuassian::get_covariance() const {
    std::vector<std::vector<double>> out;
    for (int row_index = 0; row_index < covariance.rows(); ++row_index) {
        std::vector<double> row;
        for (int column_index = 0; column_index < covariance.cols(); ++column_index) {
            row.push_back(covariance(row_index, column_index));
        }
        out.push_back(row);
    }
    return out;
}

std::vector<double> MultivariateGuassian::get_mean() const {
    std::vector<double> out;
    for (int i = 0; i < mean.size(); ++i) {
        out.push_back(mean[i]);
    }
    return out;
}

}  // namespace FilterModel


#pragma once
#include "profile.hpp"
#include "mesh.hpp"
#include <Eigen/Sparse>
#include <Eigen/Core>
#include <Eigen/Eigen>
#include "transformation.hpp"
#undef TEST_SPARSE

namespace gd {

USING_PART_OF_NAMESPACE_EIGEN;

template<class Mesh>
class PoissonSolver1d;

const double ProfileNumerical1d_epsilon = 1e-5;
template<class Mesh>
	class ProfileNumerical1d : public Profile {
	int n;
	Mesh mesh;
	Density* density;
	PoissonSolver1d<Mesh> solver;
	VectorXd solution;
	Transformation1d_in_3d* transformation;
	double G;
public:

	ProfileNumerical1d(int n, Density* density, Transformation1d_in_3d* transformation, double G, double u1, double u2) : mesh(u1, u2, n, transformation), density(density), solver(density, &mesh, G), solution(mesh.get_dof()), transformation(transformation), G(G) {
		solver.solve_(solution, 1., 1., 0.);
	}
	virtual double densityr(double r) {
		return density->densityr(r);
	}
	virtual double densityR(double) {
		return 0;
	}
	virtual double I(double, double) {
		return 0;
	}
	virtual double dphidr(double r) {
		double u = transformation->inverse_transform(r);
		//double u = atan(r) * 2 / M_PI;
		//double jacobian = M_PI/2 / pow(cos(u*M_PI/2), 2);
		//double du
		//return mesh.gradient(solution, u) / jacobian;
		return mesh.gradient(solution, u) / transformation->drdu(u); //jacobian;
	}
	virtual double potentialr(double r) {
		//double u = atan(r) * 2 / M_PI;
		double u = transformation->inverse_transform(r);
		return mesh.eval(solution, u);
	}
};

template<class Mesh>
class PoissonSolver1d {
public:
	typedef Mesh mesh_type;
	Density* density;
	Mesh* mesh;
	VectorXd lasta;
	double G;

	PoissonSolver1d(Density* density, Mesh* mesh, double G) : density(density), mesh(mesh), lasta(mesh->get_dof()), G(G) {
	}

	double operator()(double x) {
		return mesh->eval(lasta, x);
	}

	double gradient(double x) {
		return mesh->gradient(lasta, x);
	}

	void solve(double_vector v, double scale1, double scale2, double boundary_value=0) {
		VectorXd v_copy = VectorXd::Map(v.data().begin(), v.size());
		solve_(v_copy, scale1, scale2, boundary_value);
	}
	void solve_(VectorXd& v, double scale1, double scale2, double boundary_value=0) {

		/*
		solve 'a' from the linear system Ma=x using FEM (Galerkin method)
		such that \Phi(r) = \sum_i a_i \phi_i(r) is the solution to the
		poisson eq: \delta^2 \Phi(r) = 4 pi G rho(r)
		*/
		int dof = mesh->get_dof();
#ifdef TEST_SPARSE
		Eigen::DynamicSparseMatrix<double> Ms(dof, dof);
#else
		MatrixXd M = MatrixXd::Zero(dof, dof);
#endif
		VectorXd x = VectorXd::Zero(dof);

		int dof_per_cell = Mesh::dof_per_cell;
		for(int cell_index = 0; cell_index < mesh->get_n_cells(); cell_index++) {
			for(int i = 0; i < dof_per_cell; i++) {
				for(int j = 0; j < dof_per_cell; j++) {
					//M(cell_index*(dof_per_cell-1)+i, cell_index*(dof_per_cell-1)+j) = integrate dphi_i * dphi_j
#ifdef TEST_SPARSE
					Ms.coeffRef(mesh->dof_index(cell_index, i), mesh->dof_index(cell_index, j)) +=
#else
					M(mesh->dof_index(cell_index, i), mesh->dof_index(cell_index, j)) +=
#endif
						mesh->integrate_gradshape(cell_index, i, j) * scale1; // * r * r;
						/*integrate dphi_i * dphi_j*/
				}
				//x(mesh->index(cell_index, i)) += integrate phi_i * 4 * M_PI * density->densityr(r) dr;
				//auto f = [&](double r) { return this->density->densityr(r) * r * r; }; //*/ };
				auto f = [&](double u) {
					double r = this->mesh->transformation->transform(u);
					//double r = tan(u*M_PI/2);
					//double s = sin(u*M_PI/2);
					//double c = cos(u*M_PI/2);
					//return this->density->densityr(r) * 2 * M_PI*M_PI * s*s/pow(c,4);
					return this->density->densityr(r) * this->mesh->transformation->d3xdu(u);
				};
				//double G = 1;
				//cout << mesh->dof_index(cell_index, i) << " = " << (-4 * M_PI * G * mesh->integrate_shape(cell_index, i, f)) << endl;
				x(mesh->dof_index(cell_index, i)) += -(4 * M_PI) * G * mesh->integrate_shape(cell_index, i, f) * scale2;
			}
		}
		// set boundary condition, Phi(r_end) = 0
		for(int i = 1; i < Mesh::dof_per_cell; i++) {
#ifdef TEST_SPARSE
			Ms.coeffRef(dof-1-i,dof-1) = boundary_value;
			Ms.coeffRef(dof-1,dof-1-i) = boundary_value;
#else
			M(dof-1-i,dof-1) = boundary_value;
			M(dof-1,dof-1-i) = boundary_value;
#endif
		}

#ifdef TEST_SPARSE
		Ms.coeffRef(dof-1,dof-1) = 1;
#else
		M(dof-1,dof-1) = 1;
#endif

#ifdef TEST_SPARSE
		typedef Eigen::SparseMatrix<double> SparseMatrixType;
		SparseMatrixType M(Ms);
#endif
		x(dof-1) = 0;

		//cout << M << endl;
		//cout << "next" << endl << x << endl;
		// solve 'a'
#ifdef TEST_SPARSE
		VectorXd a = x;
		Eigen::SparseLLT<SparseMatrixType,Eigen::Cholmod> sparseLLT(M);
		sparseLLT.solveInPlace(a);
#else
		//VectorXd a = M.inverse() * x;
		VectorXd a(dof);
		M.llt().solve(x, &a);
#endif
		lasta = a;
		// copy to v
		//VectorXd::Map(v.data().begin(), v.size()) = a;
		v = a;
		//cout << "x: " << endl <<  (x) << endl;

		//cout << "solution: " << endl <<  (a) << endl;
		//cout << "test" << endl <<  (M * a) << endl;
	}
};

}


#include <iostream>
#include <vector>
#include <cmath>
#include <chrono>
#include <NTL/ZZ_pX.h> // contains ZZ_p.h and ZZ.h

using namespace std;
using namespace std::chrono;
using namespace NTL;

void check_p(ZZ_p &ans, long p, vector<ZZ_p> coeffs);
void findF(ZZ_pX &F, ZZ_pX &f, long rtp);
void poly_eval(ZZ_pX &h, ZZ_pX &f, ZZ_pX &g);
void evalF(vector<ZZ_p> &FVals, ZZ_pX &F, long rtp, long prtp);

ZZ p;
ZZ mod;
uint64_t start;
bool stopchecking;

void dumb_check(ZZ_p &ans, long p){
    for(long i = 0; i < p; i++){
        mul(ans, ans, i);
        if(i % 1024 == 0){
            if(80000 < duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() - start){
                stopchecking = true;
                return;
            }
        }
    }
}
int main(){
    stopchecking = false;
    for(long i = 1024; i <= 1099511627776; i*=2){
        NextPrime(p, ZZ(i));
        long p1;
        conv(p1, p);
        mul(mod, p, p);
        ZZ_p ans;
        ans.init(mod);
        vector<ZZ_p> coeffs(2);
        coeffs[0].init(mod);
        coeffs[1].init(mod);
        coeffs[0] = 0;
        coeffs[1] = 1;
        start = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
        dumb_check(ans, p1);
        uint64_t time = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() - start;
        if(stopchecking){
            break;
        }
        //cout << "final answer: " << answer << endl;
        cout << time << ", ";
    }
    cout << endl;
}

// TODO: init all ZZ_p variables
void check_p(ZZ_p &ans, long p, vector<ZZ_p> coeffs){
    ZZ_pX f;
    for(int i = 0; i < coeffs.size(); i++){
        SetCoeff(f, i, coeffs[i]);
    }

    long rtp = sqrt(p-1); // rtp = floor(sqrt(p-1))
    ZZ_pX F;
    findF(F, f, rtp);

    long prtp = (p-1)/rtp;
    vector<ZZ_p> FVals(prtp);
    evalF(FVals, F, rtp, prtp); // prtp := floor((p-1)/floor(sqrt(p-1)))

    ZZ_p out(1);
    out.init(mod);
    for(long i = rtp*prtp+1; i < p; i++){
        mul(out, out, eval(f, ZZ_p(i)));
    }
    for(long i = 0; i < FVals.size(); i++){
        mul(out, out, FVals[i]);
    }

    ans = out;
}

// return F = f(X+1)f(X+2)...f(X+rtp) mod p
void findF(ZZ_pX &F, ZZ_pX &f, long rtp){
    vector<ZZ_pX> FTree(2*rtp); // rtp leaves -> 2*rtp nodes

    long leftmost = 1 << ((int)ceil(log2(rtp))); // bottom leftmost node in tree

    // Initialize the leaves in FTree: [X+1, X+2, ..., X+rtp]
    for (long i = leftmost; i < 2 * rtp; i++) { // leaves on lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, i - leftmost + 1);
        SetCoeff(leaf, 1, 1);
        poly_eval(leaf, f, leaf); // leaf = f(leaf())
        FTree[i] = leaf;
    }
    for (long i = rtp; i < leftmost; i++) { // leaves on second lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, i + rtp - leftmost + 1);
        SetCoeff(leaf, 1, 1);
        poly_eval(leaf, f, leaf); // leaf = f(leaf())
        FTree[i] = leaf;
    }

    // Calculate the rest of the product tree FTree
    for (long i = rtp - 1; i > 0; i--) {
        FTree[i] = FTree[2*i] * FTree[2*i+1]; // parent is product of leaves
        // TODO doesn't work:
        // delete FTree[2*i];
        // delete FTree[2*i+1];
    }

    F = FTree[1];
}

// calculate h = f(g(x))
void poly_eval(ZZ_pX &h, ZZ_pX &f, ZZ_pX &g){
    ZZ_pX out(LeadCoeff(f));
    for(int i = deg(f)-1; i >= 0; i--){
        mul(out, out, g);
        add(out, out, coeff(f, i));
    }
    h = out;
}

// evaluate F at 0, rtp, ..., (prtp-1)*rtp
void evalF(vector<ZZ_p> &FVals, ZZ_pX &F, long rtp, long prtp){
    vector<ZZ_pX> FValTree(2*prtp);

    long leftmost = 1 << ((int)ceil(log2(prtp))); // bottom leftmost node in tree

    // Initialize the leaves in FValTree: [X, X-rtp, ..., X-rtp*(prtp-1)]
    for (long i = leftmost; i < 2 * prtp; i++) { // leaves on lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, -rtp*(i - leftmost));
        SetCoeff(leaf, 1, 1);
        FValTree[i] = leaf;
    }
    for (long i = prtp; i < leftmost; i++) { // leaves on second lowest layer
        ZZ_pX leaf;
        SetCoeff(leaf, 0, -rtp*(i + prtp - leftmost));
        SetCoeff(leaf, 1, 1);
        FValTree[i] = leaf;
    }

    // Calculate the rest of the product tree FValTree
    for (long i = prtp - 1; i > 0; i--) {
        FValTree[i] = FValTree[2*i] * FValTree[2*i+1]; // parent is product of leaves
    }

    // Reduce F mod polynomials in FValTree
    rem(FValTree[1], F, FValTree[1]);
    for (long i = 1; i < prtp; i++) {
        rem(FValTree[2*i], FValTree[i], FValTree[2*i]);
        rem(FValTree[2*i+1], FValTree[i], FValTree[2*i+1]);
    }

    for (long i = leftmost; i < 2 * prtp; i++) { // leaves on lowest layer
        FVals[i - leftmost].init(mod);
        FVals[i - leftmost] = ConstTerm(FValTree[i]);
    }
    for (long i = prtp; i < leftmost; i++) { // leaves on second lowest layer
        FVals[i + prtp - leftmost].init(mod);
        FVals[i + prtp - leftmost] = ConstTerm(FValTree[i]);
    }

}


/**
 * @file sspdriver_main.cc
 * @brief NPDE homework ExtendedMUSCL code
 * @author Oliver Rietmann
 * @date 04.08.2019
 * @copyright Developed at ETH Zurich
 */

#include <Eigen/Core>
#include <cmath>
#include <iostream>

#include "extendedmuscl.h"

using namespace ExtendedMUSCL;

// Bump function, raised by 1
static auto bump = [](double x) {
  return ((x >= 0.25) && (x <= 0.75))
             ? (2.0 - std::pow(std::cos(M_PI * (2 * (x - 0.25))), 2))
             : 1.0;
};

int main() {
  // First run: Solve ODE
  // Settings for the ODE
  double T = 1.0;
  double y0 = 1.0;
  double yT_exact = std::exp(T);
  auto f = [](double y) { return y; };

  // Choose time-steps 2^(-4), ..., 2^(-10)
  Eigen::VectorXd tau(7);
  tau << 0x1p-4, 0x1p-5, 0x1p-6, 0x1p-7, 0x1p-8, 0x1p-9, 0x1p-10;

  // Compute error of approx. solution at time T for all timestep-sizes in tau
  int N = tau.size();
  Eigen::VectorXd error(N);
  for (int n = 0; n < N; ++n) {
    int steps = (int)(T / tau(n) + 0.5);
    double y = y0;
    for (int i = 0; i < steps; ++i) y = sspEvolop(f, y, tau(n));
    error(n) = std::abs(yT_exact - y);
  }

  // Print the errors at each timestep
  Eigen::MatrixXd table(3, N);
  table.row(0) = tau;
  table.row(1) = error;
  table.row(2) = error.unaryExpr([](double x) { return std::log2(x); });
  Eigen::IOFormat tableFormat(2, 0, " ", "\n", " ", " ", " ", " ");
  std::cout << "tau \t error \t log_2(error)" << std::endl;
  std::cout << table.transpose().format(tableFormat) << std::endl;

  // Second run: Write solution for bump initial data to file
  std::cout << "Writing MUSCL FV solution at t=0.2 to file 'musclsol_02.csv'"
            << std::endl;
  storeMUSCLSolution("musclsol_02.csv", bump, 0.2, 100);
  std::cout << "Generated " CURRENT_BINARY_DIR "/musclsol_02.csv" << std::endl;
  std::system("python3 " CURRENT_SOURCE_DIR
              "/plot_musclsolution.py " CURRENT_BINARY_DIR
              "/musclsol_02.csv " CURRENT_BINARY_DIR "/musclsol_02.eps");
  std::cout << "Writing MUSCL FV solution at t = 1.0 to file 'musclsol_10.csv'"
            << std::endl;
  storeMUSCLSolution("musclsol_10.csv", bump, 1.0, 100);
  std::cout << "Generated " CURRENT_BINARY_DIR "/musclsol_10.csv" << std::endl;
  std::system("python3 " CURRENT_SOURCE_DIR
              "/plot_musclsolution.py " CURRENT_BINARY_DIR
              "/musclsol_10.csv " CURRENT_BINARY_DIR "/musclsol_10.eps");

  // Third run: convergence study
  studyCvgMUSCLSolution(bump, 0.2);
  studyCvgMUSCLSolution(bump, 1.0);

  return 0;
}


//           Copyright Matthew Pulver 2018 - 2019.
// Distributed under the Boost Software License, Version 1.0.
//      (See accompanying file LICENSE_1_0.txt or copy at
//           https://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MATH_DIFFERENTIATION_AUTODIFF_HPP
#define BOOST_MATH_DIFFERENTIATION_AUTODIFF_HPP

#include <boost/config.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions.hpp>
#include <boost/math/tools/promotion.hpp>

#include <algorithm>
#include <array>
#include <cmath>
#include <functional>
#include <limits>
#include <numeric>
#include <ostream>
#include <type_traits>

// Automatic Differentiation v1
namespace boost { namespace math { namespace differentiation { inline namespace autodiff_v1 {

namespace detail {

template<typename RealType, typename... RealTypes>
struct promote_args_n { using type = typename boost::math::tools::promote_args_2<RealType,
    typename promote_args_n<RealTypes...>::type>::type; };

template<typename RealType>
struct promote_args_n<RealType> { using type = typename boost::math::tools::promote_arg<RealType>::type; };

} // namespace detail

template<typename RealType, typename... RealTypes>
using promote = typename detail::promote_args_n<RealType,RealTypes...>::type;

namespace detail {

template<typename RealType, size_t Order>
class fvar;

template <typename>
struct get_depth : std::integral_constant<size_t, 0> {};

template <typename RealType, size_t Order>
struct get_depth<fvar<RealType,Order>> : std::integral_constant<size_t,get_depth<RealType>::value+1> {};

template <typename>
struct get_order_sum : std::integral_constant<size_t, 0> {};

template <typename RealType, size_t Order>
struct get_order_sum<fvar<RealType,Order>> : std::integral_constant<size_t,get_order_sum<RealType>::value+Order> {};

// Get non-fvar<> root type T of autodiff_fvar<T,O0,O1,O2,...>.
template<typename RealType>
struct get_root_type { using type = RealType; };

template<typename RealType, size_t Order>
struct get_root_type<fvar<RealType,Order>> { using type = typename get_root_type<RealType>::type; };

// Get type from descending Depth levels into fvar<>.
template<typename RealType, size_t Depth>
struct type_at { using type = RealType; };

template<typename RealType, size_t Order, size_t Depth>
struct type_at<fvar<RealType,Order>,Depth> { using type =
    typename std::conditional<Depth==0, fvar<RealType,Order>, typename type_at<RealType,Depth-1>::type>::type; };

template<typename RealType, size_t Depth>
using get_type_at = typename type_at<RealType,Depth>::type;

// Satisfies Boost's Conceptual Requirements for Real Number Types.
// https://www.boost.org/libs/math/doc/html/math_toolkit/real_concepts.html
template<typename RealType, size_t Order>
class fvar
{
    std::array<RealType,Order+1> v;

  public:

    using root_type = typename get_root_type<RealType>::type; // RealType in the root fvar<RealType,Order>.

    fvar() = default;

    // Initialize a variable or constant.
    fvar(const root_type&, const bool is_variable);

    // RealType(cr) | RealType | RealType is copy constructible.
    fvar(const fvar&) = default;

    // Be aware of implicit casting from one fvar<> type to another by this copy constructor.
    template<typename RealType2, size_t Order2>
    fvar(const fvar<RealType2,Order2>&);

    // RealType(ca) | RealType | RealType is copy constructible from the arithmetic types.
    explicit fvar(const root_type&); // Initialize a constant. (No epsilon terms.)

    template<typename RealType2>
    fvar(const RealType2& ca); // Supports any RealType2 for which static_cast<root_type>(ca) compiles.

    // r = cr | RealType& | Assignment operator.
    fvar& operator=(const fvar&) = default;

    // r = ca | RealType& | Assignment operator from the arithmetic types.
    // Handled by constructor that takes a single parameter of generic type.
    //fvar& operator=(const root_type&); // Set a constant.

    // r += cr | RealType& | Adds cr to r.
    template<typename RealType2, size_t Order2>
    fvar& operator+=(const fvar<RealType2,Order2>&);

    // r += ca | RealType& | Adds ar to r.
    fvar& operator+=(const root_type&);

    // r -= cr | RealType& | Subtracts cr from r.
    template<typename RealType2, size_t Order2>
    fvar& operator-=(const fvar<RealType2,Order2>&);

    // r -= ca | RealType& | Subtracts ca from r.
    fvar& operator-=(const root_type&);

    // r *= cr | RealType& | Multiplies r by cr.
    template<typename RealType2, size_t Order2>
    fvar& operator*=(const fvar<RealType2,Order2>&);

    // r *= ca | RealType& | Multiplies r by ca.
    fvar& operator*=(const root_type&);

    // r /= cr | RealType& | Divides r by cr.
    template<typename RealType2, size_t Order2>
    fvar& operator/=(const fvar<RealType2,Order2>&);

    // r /= ca | RealType& | Divides r by ca.
    fvar& operator/=(const root_type&);

    // -r | RealType | Unary Negation.
    fvar operator-() const;

    // +r | RealType& | Identity Operation.
    const fvar& operator+() const;

    // cr + cr2 | RealType | Binary Addition
    template<typename RealType2, size_t Order2>
    promote<fvar,fvar<RealType2,Order2>> operator+(const fvar<RealType2,Order2>&) const;

    // cr + ca | RealType | Binary Addition
    fvar operator+(const root_type&) const;

    // ca + cr | RealType | Binary Addition
    template<typename RealType2, size_t Order2>
    friend fvar<RealType2,Order2>
        operator+(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr - cr2 | RealType | Binary Subtraction
    template<typename RealType2, size_t Order2>
    promote<fvar,fvar<RealType2,Order2>> operator-(const fvar<RealType2,Order2>&) const;

    // cr - ca | RealType | Binary Subtraction
    fvar operator-(const root_type&) const;

    // ca - cr | RealType | Binary Subtraction
    template<typename RealType2, size_t Order2>
    friend fvar<RealType2,Order2>
        operator-(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr * cr2 | RealType | Binary Multiplication
    template<typename RealType2, size_t Order2>
    promote<fvar,fvar<RealType2,Order2>> operator*(const fvar<RealType2,Order2>&) const;

    // cr * ca | RealType | Binary Multiplication
    fvar operator*(const root_type&) const;

    // ca * cr | RealType | Binary Multiplication
    template<typename RealType2, size_t Order2>
    friend fvar<RealType2,Order2>
        operator*(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr / cr2 | RealType | Binary Subtraction
    template<typename RealType2, size_t Order2>
    promote<fvar,fvar<RealType2,Order2>> operator/(const fvar<RealType2,Order2>&) const;

    // cr / ca | RealType | Binary Subtraction
    fvar operator/(const root_type&) const;

    // ca / cr | RealType | Binary Subtraction
    template<typename RealType2, size_t Order2>
    friend fvar<RealType2,Order2>
        operator/(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr == cr2 | bool | Equality Comparison
    template<typename RealType2, size_t Order2> // This only compares the root term. All other terms are ignored.
    bool operator==(const fvar<RealType2,Order2>&) const;

    // cr == ca | bool | Equality Comparison
    bool operator==(const root_type&) const;

    // ca == cr | bool | Equality Comparison
    template<typename RealType2, size_t Order2> // This only compares the root term. All other terms are ignored.
    friend bool operator==(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr != cr2 | bool | Inequality Comparison
    template<typename RealType2, size_t Order2>
    bool operator!=(const fvar<RealType2,Order2>&) const;

    // cr != ca | bool | Inequality Comparison
    bool operator!=(const root_type&) const;

    // ca != cr | bool | Inequality Comparison
    template<typename RealType2, size_t Order2>
    friend bool operator!=(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr <= cr2 | bool | Less than equal to.
    template<typename RealType2, size_t Order2>
    bool operator<=(const fvar<RealType2,Order2>&) const;

    // cr <= ca | bool | Less than equal to.
    bool operator<=(const root_type&) const;

    // ca <= cr | bool | Less than equal to.
    template<typename RealType2, size_t Order2>
    friend bool operator<=(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr >= cr2 | bool | Greater than equal to.
    template<typename RealType2, size_t Order2>
    bool operator>=(const fvar<RealType2,Order2>&) const;

    // cr >= ca | bool | Greater than equal to.
    bool operator>=(const root_type&) const;

    // ca >= cr | bool | Greater than equal to.
    template<typename RealType2, size_t Order2>
    friend bool operator>=(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr < cr2 | bool | Less than comparison.
    template<typename RealType2, size_t Order2>
    bool operator<(const fvar<RealType2,Order2>&) const;

    // cr < ca | bool | Less than comparison.
    bool operator<(const root_type&) const;

    // ca < cr | bool | Less than comparison.
    template<typename RealType2, size_t Order2>
    friend bool operator<(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // cr > cr2 | bool | Greater than comparison.
    template<typename RealType2, size_t Order2>
    bool operator>(const fvar<RealType2,Order2>&) const;

    // cr > ca | bool | Greater than comparison.
    bool operator>(const root_type&) const;

    // ca > cr | bool | Greater than comparison.
    template<typename RealType2, size_t Order2>
    friend bool operator>(const typename fvar<RealType2,Order2>::root_type&, const fvar<RealType2,Order2>&);

    // Will throw std::out_of_range if Order < order.
    template<typename... Orders>
    get_type_at<RealType, sizeof...(Orders)> at(size_t order, Orders... orders) const;

    template<typename... Orders>
    get_type_at<fvar, sizeof...(Orders)> derivative(Orders... orders) const;

    fvar inverse() const; // Multiplicative inverse.

    static constexpr size_t depth = get_depth<fvar>::value; // Number of nested std::array<RealType,Order>.

    static constexpr size_t order_sum = get_order_sum<fvar>::value;

    explicit operator root_type() const; // Must be explicit, otherwise overloaded operators are ambiguous.

    fvar& set_root(const root_type&);

    // Use when function returns derivatives.
    fvar apply(const std::function<root_type(size_t)>&) const;

    // Use when function returns derivative(i)/factorial(i) (slightly more efficient than apply().)
    fvar apply_with_factorials(const std::function<root_type(size_t)>&) const;

    // Same as apply() but uses horner method. May be more accurate in some cases but not as good with inf derivatives.
    fvar apply_with_horner(const std::function<root_type(size_t)>&) const;

    // Same as apply_with_factorials() but uses horner method.
    fvar apply_with_horner_factorials(const std::function<root_type(size_t)>&) const;

private:

    RealType epsilon_inner_product(size_t z0, size_t isum0, size_t m0,
        const fvar& cr, size_t z1, size_t isum1, size_t m1, size_t j) const;

    fvar epsilon_multiply(size_t z0, size_t isum0, const fvar& cr, size_t z1, size_t isum1) const;

    fvar epsilon_multiply(size_t z0, size_t isum0, const root_type& ca) const;

    fvar inverse_apply() const;

    fvar& multiply_assign_by_root_type(bool is_root, const root_type&);

    template<typename RealType2, size_t Orders2>
    friend class fvar;

    template<typename RealType2, size_t Order2>
    friend std::ostream& operator<<(std::ostream&, const fvar<RealType2,Order2>&);

// C++11 Compatibility
#ifdef BOOST_NO_CXX17_IF_CONSTEXPR
    template<typename RootType>
    void fvar_cpp11(std::true_type, const RootType& ca, const bool is_variable);

    template<typename RootType>
    void fvar_cpp11(std::false_type, const RootType& ca, const bool is_variable);

    template<typename... Orders>
    get_type_at<RealType, sizeof...(Orders)> at_cpp11(std::true_type, size_t order, Orders... orders) const;

    template<typename... Orders>
    get_type_at<RealType, sizeof...(Orders)> at_cpp11(std::false_type, size_t order, Orders... orders) const;

    template<typename SizeType>
    fvar epsilon_multiply_cpp11(std::true_type,
        SizeType z0, size_t isum0, const fvar& cr, size_t z1, size_t isum1) const;

    template<typename SizeType>
    fvar epsilon_multiply_cpp11(std::false_type,
        SizeType z0, size_t isum0, const fvar& cr, size_t z1, size_t isum1) const;

    template<typename SizeType>
    fvar epsilon_multiply_cpp11(std::true_type, SizeType z0, size_t isum0, const root_type& ca) const;

    template<typename SizeType>
    fvar epsilon_multiply_cpp11(std::false_type, SizeType z0, size_t isum0, const root_type& ca) const;

    template<typename RootType>
    fvar& multiply_assign_by_root_type_cpp11(std::true_type, bool is_root, const RootType& ca);

    template<typename RootType>
    fvar& multiply_assign_by_root_type_cpp11(std::false_type, bool is_root, const RootType& ca);

    template<typename RootType>
    fvar& set_root_cpp11(std::true_type, const RootType& root);

    template<typename RootType>
    fvar& set_root_cpp11(std::false_type, const RootType& root);
#endif
};

// C++11 compatibility
#ifdef BOOST_NO_CXX17_IF_CONSTEXPR
#  define BOOST_AUTODIFF_IF_CONSTEXPR
#else
#  define BOOST_AUTODIFF_IF_CONSTEXPR constexpr
#endif

// Standard Library Support Requirements

// fabs(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> fabs(const fvar<RealType,Order>&);

// abs(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> abs(const fvar<RealType,Order>&);

// ceil(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> ceil(const fvar<RealType,Order>&);

// floor(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> floor(const fvar<RealType,Order>&);

// exp(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> exp(const fvar<RealType,Order>&);

// pow(cr, ca) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> pow(const fvar<RealType,Order>&,const typename fvar<RealType,Order>::root_type&);

// pow(ca, cr) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> pow(const typename fvar<RealType,Order>::root_type&,const fvar<RealType,Order>&);

// pow(cr1, cr2) | RealType
template<typename RealType1, size_t Order1, typename RealType2, size_t Order2>
promote<fvar<RealType1,Order1>,fvar<RealType2,Order2>>
    pow(const fvar<RealType1,Order1>&, const fvar<RealType2,Order2>&);

// sqrt(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> sqrt(const fvar<RealType,Order>&);

// log(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> log(const fvar<RealType,Order>&);

// frexp(cr1, &i) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> frexp(const fvar<RealType,Order>&, int*);

// ldexp(cr1, i) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> ldexp(const fvar<RealType,Order>&, int);

// cos(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> cos(const fvar<RealType,Order>&);

// sin(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> sin(const fvar<RealType,Order>&);

// asin(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> asin(const fvar<RealType,Order>&);

// tan(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> tan(const fvar<RealType,Order>&);

// atan(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> atan(const fvar<RealType,Order>&);

// fmod(cr1,cr2) | RealType
template<typename RealType1, size_t Order1, typename RealType2, size_t Order2>
promote<fvar<RealType1,Order1>,fvar<RealType2,Order2>>
    fmod(const fvar<RealType1,Order1>&, const fvar<RealType2,Order2>&);

// round(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> round(const fvar<RealType,Order>&);

// iround(cr1) | int
template<typename RealType, size_t Order>
int iround(const fvar<RealType,Order>&);

// trunc(cr1) | RealType
template<typename RealType, size_t Order>
fvar<RealType,Order> trunc(const fvar<RealType,Order>&);

// itrunc(cr1) | int
template<typename RealType, size_t Order>
int itrunc(const fvar<RealType,Order>&);

// Additional functions
template<typename RealType, size_t Order>
fvar<RealType,Order> acos(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> acosh(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> asinh(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> atanh(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> cosh(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> erf(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> erfc(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> lambert_w0(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> sinc(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> sinh(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
fvar<RealType,Order> tanh(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
long lround(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
long long llround(const fvar<RealType,Order>&);

template<typename RealType, size_t Order>
long double truncl(const fvar<RealType,Order>&);

// Compile-time test for fvar<> type.
template<typename>
struct is_fvar : std::false_type {};

template<typename RealType, size_t Order>
struct is_fvar<fvar<RealType,Order>> : std::true_type {};

template<typename RealType, size_t Order, size_t... Orders> // specialized for fvar<> below.
struct nest_fvar { using type = fvar<typename nest_fvar<RealType,Orders...>::type,Order>; };

template<typename RealType, size_t Order>
struct nest_fvar<RealType,Order> { using type = fvar<RealType,Order>; };

} // namespace detail

template<typename RealType, size_t Order, size_t... Orders>
using autodiff_fvar = typename detail::nest_fvar<RealType,Order,Orders...>::type;

template<typename RealType, size_t Order, size_t... Orders>
autodiff_fvar<RealType,Order,Orders...> make_fvar(const RealType& ca)
{
    return autodiff_fvar<RealType,Order,Orders...>(ca, true);
}

namespace detail {

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
template<typename RealType, size_t Order>
fvar<RealType,Order>::fvar(const root_type& ca, const bool is_variable)
{
    if constexpr (is_fvar<RealType>::value)
    {
        v.front() = RealType(ca, is_variable);
        if constexpr (0 < Order)
            std::fill(v.begin()+1, v.end(), static_cast<RealType>(0));
    }
    else
    {
        v.front() = ca;
        if constexpr (0 < Order)
            v[1] = static_cast<root_type>(static_cast<int>(is_variable));
        if constexpr (1 < Order)
            std::fill(v.begin()+2, v.end(), static_cast<RealType>(0));
    }
}
#endif

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
fvar<RealType,Order>::fvar(const fvar<RealType2,Order2>& cr)
{
    for (size_t i=0 ; i<=std::min(Order,Order2) ; ++i)
        v[i] = static_cast<RealType>(cr.v[i]);
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order2 < Order)
        std::fill(v.begin()+(Order2+1), v.end(), static_cast<RealType>(0));
}

template<typename RealType, size_t Order>
fvar<RealType,Order>::fvar(const root_type& ca)
:    v{{static_cast<RealType>(ca)}}
{
}

template<typename RealType, size_t Order>
template<typename RealType2>
fvar<RealType,Order>::fvar(const RealType2& ca)
:    v{{static_cast<RealType>(ca)}} // Can cause compiler error if RealType2 cannot be cast to root_type.
{
}

/*
template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::operator=(const root_type& ca)
{
    v.front() = static_cast<RealType>(ca);
    if BOOST_AUTODIFF_IF_CONSTEXPR (0 < Order)
        std::fill(v.begin()+1, v.end(), static_cast<RealType>(0));
    return *this;
}
*/

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
fvar<RealType,Order>& fvar<RealType,Order>::operator+=(const fvar<RealType2,Order2>& cr)
{
    for (size_t i=0 ; i<=std::min(Order,Order2) ; ++i)
        v[i] += cr.v[i];
    return *this;
}

template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::operator+=(const root_type& ca)
{
    v.front() += ca;
    return *this;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
fvar<RealType,Order>& fvar<RealType,Order>::operator-=(const fvar<RealType2,Order2>& cr)
{
    for (size_t i=0 ; i<=Order ; ++i)
        v[i] -= cr.v[i];
    return *this;
}

template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::operator-=(const root_type& ca)
{
    v.front() -= ca;
    return *this;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
fvar<RealType,Order>& fvar<RealType,Order>::operator*=(const fvar<RealType2,Order2>& cr)
{
    const promote<RealType,RealType2> zero(0);
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order <= Order2)
        for (size_t i=0, j=Order ; i<=Order ; ++i, --j)
            v[j] = std::inner_product(v.cbegin(), v.cend()-i, cr.v.crbegin()+i, zero);
    else
    {
        for (size_t i=0, j=Order ; i<=Order-Order2 ; ++i, --j)
            v[j] = std::inner_product(cr.v.cbegin(), cr.v.cend(), v.crbegin()+i, zero);
        for (size_t i=Order-Order2+1, j=Order2-1 ; i<=Order ; ++i, --j)
            v[j] = std::inner_product(cr.v.cbegin(), cr.v.cbegin()+(j+1), v.crbegin()+i, zero);
    }
    return *this;
}

template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::operator*=(const root_type& ca)
{
    return multiply_assign_by_root_type(true, ca);
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
fvar<RealType,Order>& fvar<RealType,Order>::operator/=(const fvar<RealType2,Order2>& cr)
{
    const RealType zero(0);
    v.front() /= cr.v.front();
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order < Order2)
        for (size_t i=1, j=Order2-1, k=Order ; i<=Order ; ++i, --j, --k)
            (v[i] -= std::inner_product(cr.v.cbegin()+1, cr.v.cend()-j, v.crbegin()+k, zero)) /= cr.v.front();
    else if BOOST_AUTODIFF_IF_CONSTEXPR (0 < Order2)
        for (size_t i=1, j=Order2-1, k=Order ; i<=Order ; ++i, j&&--j, --k)
            (v[i] -= std::inner_product(cr.v.cbegin()+1, cr.v.cend()-j, v.crbegin()+k, zero)) /= cr.v.front();
    else
        for (size_t i=1 ; i<=Order ; ++i)
            v[i] /= cr.v.front();
    return *this;
}

template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::operator/=(const root_type& ca)
{
    std::for_each(v.begin(), v.end(), [&ca](RealType& x) { x /= ca; });
    return *this;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::operator-() const
{
    fvar<RealType,Order> retval;
    for (size_t i=0 ; i<=Order ; ++i)
        retval.v[i] = -v[i];
    return retval;
}

template<typename RealType, size_t Order>
const fvar<RealType,Order>& fvar<RealType,Order>::operator+() const
{
    return *this;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
promote<fvar<RealType,Order>,fvar<RealType2,Order2>>
    fvar<RealType,Order>::operator+(const fvar<RealType2,Order2>& cr) const
{
    promote<fvar<RealType,Order>,fvar<RealType2,Order2>> retval;
    for (size_t i=0 ; i<=std::min(Order,Order2) ; ++i)
        retval.v[i] = v[i] + cr.v[i];
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order < Order2)
        for (size_t i=Order+1 ; i<=Order2 ; ++i)
            retval.v[i] = cr.v[i];
    else if BOOST_AUTODIFF_IF_CONSTEXPR (Order2 < Order)
        for (size_t i=Order2+1 ; i<=Order ; ++i)
            retval.v[i] = v[i];
    return retval;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::operator+(const root_type& ca) const
{
    fvar<RealType,Order> retval(*this);
    retval.v.front() += ca;
    return retval;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> operator+(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return cr + ca;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
promote<fvar<RealType,Order>,fvar<RealType2,Order2>>
    fvar<RealType,Order>::operator-(const fvar<RealType2,Order2>& cr) const
{
    promote<fvar<RealType,Order>,fvar<RealType2,Order2>> retval;
    for (size_t i=0 ; i<=std::min(Order,Order2) ; ++i)
        retval.v[i] = v[i] - cr.v[i];
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order < Order2)
        for (size_t i=Order+1 ; i<=Order2 ; ++i)
            retval.v[i] = -cr.v[i];
    else if BOOST_AUTODIFF_IF_CONSTEXPR (Order2 < Order)
        for (size_t i=Order2+1 ; i<=Order ; ++i)
            retval.v[i] = v[i];
    return retval;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::operator-(const root_type& ca) const
{
    fvar<RealType,Order> retval(*this);
    retval.v.front() -= ca;
    return retval;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> operator-(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return -cr += ca;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
promote<fvar<RealType,Order>,fvar<RealType2,Order2>>
    fvar<RealType,Order>::operator*(const fvar<RealType2,Order2>& cr) const
{
    const promote<RealType,RealType2> zero(0);
    promote<fvar<RealType,Order>,fvar<RealType2,Order2>> retval;
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order < Order2)
        for (size_t i=0, j=Order, k=Order2 ; i<=Order2 ; ++i, j&&--j, --k)
            retval.v[i] = std::inner_product(v.cbegin(), v.cend()-j, cr.v.crbegin()+k, zero);
    else
        for (size_t i=0, j=Order2, k=Order ; i<=Order ; ++i, j&&--j, --k)
            retval.v[i] = std::inner_product(cr.v.cbegin(), cr.v.cend()-j, v.crbegin()+k, zero);
    return retval;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::operator*(const root_type& ca) const
{
    return fvar<RealType,Order>(*this) *= ca;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> operator*(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return cr * ca;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
promote<fvar<RealType,Order>,fvar<RealType2,Order2>>
    fvar<RealType,Order>::operator/(const fvar<RealType2,Order2>& cr) const
{
    const promote<RealType,RealType2> zero(0);
    promote<fvar<RealType,Order>,fvar<RealType2,Order2>> retval;
    retval.v.front() = v.front() / cr.v.front();
    if BOOST_AUTODIFF_IF_CONSTEXPR (Order < Order2)
    {
        for (size_t i=1, j=Order2-1 ; i<=Order ; ++i, --j)
            retval.v[i] = (v[i] -
                std::inner_product(cr.v.cbegin()+1, cr.v.cend()-j, retval.v.crbegin()+(j+1), zero)) / cr.v.front();
        for (size_t i=Order+1, j=Order2-Order-1 ; i<=Order2 ; ++i, --j)
            retval.v[i] =
                -std::inner_product(cr.v.cbegin()+1, cr.v.cend()-j, retval.v.crbegin()+(j+1), zero) / cr.v.front();
    }
    else if BOOST_AUTODIFF_IF_CONSTEXPR (0 < Order2)
        for (size_t i=1, j=Order2-1, k=Order ; i<=Order ; ++i, j&&--j, --k)
            retval.v[i] =
                (v[i] - std::inner_product(cr.v.cbegin()+1, cr.v.cend()-j, retval.v.crbegin()+k, zero)) / cr.v.front();
    else
        for (size_t i=1 ; i<=Order ; ++i)
            retval.v[i] = v[i] / cr.v.front();
    return retval;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::operator/(const root_type& ca) const
{
    return fvar<RealType,Order>(*this) /= ca;
}

template<typename RealType, size_t Order>
fvar<RealType,Order> operator/(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    fvar<RealType,Order> retval;
    retval.v.front() = ca / cr.v.front();
    if BOOST_AUTODIFF_IF_CONSTEXPR (0 < Order)
    {
        const RealType zero(0);
        for (size_t i=1, j=Order-1 ; i<=Order ; ++i, --j)
            retval.v[i] = -std::inner_product(cr.v.cbegin()+1, cr.v.cend()-j, retval.v.crbegin()+(j+1), zero)
                / cr.v.front();
    }
    return retval;
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
bool fvar<RealType,Order>::operator==(const fvar<RealType2,Order2>& cr) const
{
    return v.front() == cr.v.front();
}

template<typename RealType, size_t Order>
bool fvar<RealType,Order>::operator==(const root_type& ca) const
{
    return v.front() == ca;
}

template<typename RealType, size_t Order>
bool operator==(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return ca == cr.v.front();
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
bool fvar<RealType,Order>::operator!=(const fvar<RealType2,Order2>& cr) const
{
    return v.front() != cr.v.front();
}

template<typename RealType, size_t Order>
bool fvar<RealType,Order>::operator!=(const root_type& ca) const
{
    return v.front() != ca;
}

template<typename RealType, size_t Order>
bool operator!=(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return ca != cr.v.front();
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
bool fvar<RealType,Order>::operator<=(const fvar<RealType2,Order2>& cr) const
{
    return v.front() <= cr.v.front();
}

template<typename RealType, size_t Order>
bool fvar<RealType,Order>::operator<=(const root_type& ca) const
{
    return v.front() <= ca;
}

template<typename RealType, size_t Order>
bool operator<=(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return ca <= cr.v.front();
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
bool fvar<RealType,Order>::operator>=(const fvar<RealType2,Order2>& cr) const
{
    return v.front() >= cr.v.front();
}

template<typename RealType, size_t Order>
bool fvar<RealType,Order>::operator>=(const root_type& ca) const
{
    return v.front() >= ca;
}

template<typename RealType, size_t Order>
bool operator>=(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return ca >= cr.v.front();
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
bool fvar<RealType,Order>::operator<(const fvar<RealType2,Order2>& cr) const
{
    return v.front() < cr.v.front();
}

template<typename RealType, size_t Order>
bool fvar<RealType,Order>::operator<(const root_type& ca) const
{
    return v.front() < ca;
}

template<typename RealType, size_t Order>
bool operator<(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return ca < cr.v.front();
}

template<typename RealType, size_t Order>
template<typename RealType2, size_t Order2>
bool fvar<RealType,Order>::operator>(const fvar<RealType2,Order2>& cr) const
{
    return v.front() > cr.v.front();
}

template<typename RealType, size_t Order>
bool fvar<RealType,Order>::operator>(const root_type& ca) const
{
    return v.front() > ca;
}

template<typename RealType, size_t Order>
bool operator>(const typename fvar<RealType,Order>::root_type& ca, const fvar<RealType,Order>& cr)
{
    return ca > cr.v.front();
}

/*** Other methods and functions ***/

// f : order -> derivative(order)
template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::apply(const std::function<root_type(size_t)>& f) const
{
    const fvar<RealType,Order> epsilon = fvar<RealType,Order>(*this).set_root(0);
    fvar<RealType,Order> epsilon_i = fvar<RealType,Order>(1); // epsilon to the power of i
    fvar<RealType,Order> accumulator = fvar<RealType,Order>(f(0));
    for (size_t i=1 ; i<=order_sum ; ++i)
    {    // accumulator += (epsilon_i *= epsilon) * (f(i) / boost::math::factorial<root_type>(i));
        epsilon_i = epsilon_i.epsilon_multiply(i-1, 0, epsilon, 1, 0);
        accumulator += epsilon_i.epsilon_multiply(i, 0, f(i) / boost::math::factorial<root_type>(i));
    }
    return accumulator;
}

// f : order -> derivative(order)/factorial(order)
// Use this when the computation of the derivatives already includes the factorial terms. E.g. See atan().
template<typename RealType, size_t Order>
fvar<RealType,Order>
    fvar<RealType,Order>::apply_with_factorials(const std::function<root_type(size_t)>& f) const
{
    const fvar<RealType,Order> epsilon = fvar<RealType,Order>(*this).set_root(0);
    fvar<RealType,Order> epsilon_i = fvar<RealType,Order>(1); // epsilon to the power of i
    fvar<RealType,Order> accumulator = fvar<RealType,Order>(f(0));
    for (size_t i=1 ; i<=order_sum ; ++i)
    {    // accumulator += (epsilon_i *= epsilon) * f(i);
        epsilon_i = epsilon_i.epsilon_multiply(i-1, 0, epsilon, 1, 0);
        accumulator += epsilon_i.epsilon_multiply(i, 0, f(i));
    }
    return accumulator;
}

// f : order -> derivative(order)
template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::apply_with_horner(const std::function<root_type(size_t)>& f) const
{
    const fvar<RealType,Order> epsilon = fvar<RealType,Order>(*this).set_root(0);
    fvar<RealType,Order> accumulator(static_cast<root_type>(f(order_sum)/boost::math::factorial<root_type>(order_sum)));
    for (size_t i=order_sum ; i-- ;)
        (accumulator *= epsilon) += f(i) / boost::math::factorial<root_type>(i);
    return accumulator;
}

// f : order -> derivative(order)/factorial(order)
// Use this when the computation of the derivatives already includes the factorial terms. E.g. See atan().
template<typename RealType, size_t Order>
fvar<RealType,Order>
    fvar<RealType,Order>::apply_with_horner_factorials(const std::function<root_type(size_t)>& f) const
{
    const fvar<RealType,Order> epsilon = fvar<RealType,Order>(*this).set_root(0);
    fvar<RealType,Order> accumulator(f(order_sum));
    for (size_t i=order_sum ; i-- ;)
        (accumulator *= epsilon) += f(i);
    return accumulator;
}

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
// Can throw "std::out_of_range: array::at: __n (which is 7) >= _Nm (which is 7)"
template<typename RealType, size_t Order>
template<typename... Orders>
get_type_at<RealType,sizeof...(Orders)> fvar<RealType,Order>::at(size_t order, Orders... orders) const
{
    if constexpr (0 < sizeof...(Orders))
        return v.at(order).at(orders...);
    else
        return v.at(order);
}
#endif

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
// Can throw "std::out_of_range: array::at: __n (which is 7) >= _Nm (which is 7)"
template<typename RealType, size_t Order>
template<typename... Orders>
get_type_at<fvar<RealType,Order>,sizeof...(Orders)> fvar<RealType,Order>::derivative(Orders... orders) const
{
    static_assert(sizeof...(Orders) <= depth, "Number of parameters to derivative(...) cannot exceed fvar::depth.");
    return at(orders...) * (... * boost::math::factorial<root_type>(orders));
}
#endif

template<typename RealType, size_t Order>
RealType fvar<RealType,Order>::epsilon_inner_product(size_t z0, size_t isum0, size_t m0,
    const fvar<RealType,Order>& cr, size_t z1, size_t isum1, size_t m1, size_t j) const
{
    static_assert(is_fvar<RealType>::value, "epsilon_inner_product() must have 1 < depth.");
    RealType accumulator = RealType();
    const size_t i0_max = m1 < j ? j-m1 : 0;
    for (size_t i0=m0, i1=j-m0 ; i0<=i0_max ; ++i0, --i1)
        accumulator += v.at(i0).epsilon_multiply(z0, isum0+i0, cr.v.at(i1), z1, isum1+i1);
    return accumulator;
}

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::epsilon_multiply(size_t z0, size_t isum0,
    const fvar<RealType,Order>& cr, size_t z1, size_t isum1) const
{
    const RealType zero(0);
    const size_t m0 = order_sum + isum0 < Order + z0 ? Order + z0 - (order_sum + isum0) : 0;
    const size_t m1 = order_sum + isum1 < Order + z1 ? Order + z1 - (order_sum + isum1) : 0;
    const size_t i_max = m0 + m1 < Order ? Order - (m0 + m1) : 0;
    fvar<RealType,Order> retval = fvar<RealType,Order>();
    if constexpr (is_fvar<RealType>::value)
        for (size_t i=0, j=Order ; i<=i_max ; ++i, --j)
            retval.v[j] = epsilon_inner_product(z0, isum0, m0, cr, z1, isum1, m1, j);
    else
        for (size_t i=0, j=Order ; i<=i_max ; ++i, --j)
            retval.v[j] = std::inner_product(v.cbegin()+m0, v.cend()-(i+m1), cr.v.crbegin()+(i+m0), zero);
    return retval;
}
#endif

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
// When called from outside this method, z0 should be non-zero. Otherwise if z0=0 then it will give an
// incorrect result of 0 when the root value is 0 and ca=inf, when instead the correct product is nan.
// If z0=0 then use the regular multiply operator*() instead.
template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::epsilon_multiply(size_t z0, size_t isum0,
    const root_type& ca) const
{
    fvar<RealType,Order> retval(*this);
    const size_t m0 = order_sum + isum0 < Order + z0 ? Order + z0 - (order_sum + isum0) : 0;
    if constexpr (is_fvar<RealType>::value)
        for (size_t i=m0 ; i<=Order ; ++i)
            retval.v[i] = retval.v[i].epsilon_multiply(z0, isum0+i, ca);
    else
        for (size_t i=m0 ; i<=Order ; ++i)
            if (retval.v[i] != static_cast<RealType>(0))
                retval.v[i] *= ca;
    return retval;
}
#endif

template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::inverse() const
{
    return operator root_type() == 0 ? inverse_apply() : 1 / *this;
}

// This gives log(0.0) = depth(1)(-inf,inf,-inf,inf,-inf,inf)
// 1 / *this: log(0.0) = depth(1)(-inf,inf,-inf,-nan,-nan,-nan)
template<typename RealType, size_t Order>
fvar<RealType,Order> fvar<RealType,Order>::inverse_apply() const
{
    root_type derivatives[order_sum+1]; // LCOV_EXCL_LINE This causes a false negative on lcov coverage test.
    const root_type x0 = static_cast<root_type>(*this);
    *derivatives = 1 / x0;
    for (size_t i=1 ; i<=order_sum ; ++i)
        derivatives[i] = -derivatives[i-1] * i / x0;
    return apply([&derivatives](size_t j) { return derivatives[j]; });
}

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::multiply_assign_by_root_type(bool is_root, const root_type& ca)
{
    auto itr = v.begin();
    if constexpr (is_fvar<RealType>::value)
    {
        itr->multiply_assign_by_root_type(is_root, ca);
        for (++itr ; itr!=v.end() ; ++itr)
            itr->multiply_assign_by_root_type(false, ca);
    }
    else
    {
        if (is_root || *itr != 0)
            *itr *= ca; // Skip multiplication of 0 by ca=inf to avoid nan. Exception: root value is always multiplied.
        for (++itr ; itr!=v.end() ; ++itr)
            if (*itr != 0)
                *itr *= ca;
    }
    return *this;
}
#endif

template<typename RealType, size_t Order>
fvar<RealType,Order>::operator root_type() const
{
    return static_cast<root_type>(v.front());
}

#ifndef BOOST_NO_CXX17_IF_CONSTEXPR
template<typename RealType, size_t Order>
fvar<RealType,Order>& fvar<RealType,Order>::set_root(const root_type& root)
{
    if constexpr (is_fvar<RealType>::value)
        v.front().set_root(root);
    else
        v.front() = root;
    return *this;
}
#endif

// Standard Library Support Requirements

template<typename RealType, size_t Order>
fvar<RealType,Order> fabs(const fvar<RealType,Order>& cr)
{
    const typename fvar<RealType,Order>::root_type zero(0);
    return cr < zero ? -cr
        : cr == zero ? fvar<RealType,Order>() // Canonical fabs'(0) = 0.
        : cr; // Propagate NaN.
}

template<typename RealType, size_t Order>
fvar<RealType,Order> abs(const fvar<RealType,Order>& cr)
{
    return fabs(cr);
}

template<typename RealType, size_t Order>
fvar<RealType,Order> ceil(const fvar<RealType,Order>& cr)
{
    using std::ceil;
    return fvar<RealType,Order>(ceil(static_cast<typename fvar<RealType,Order>::root_type>(cr)));
}

template<typename RealType, size_t Order>
fvar<RealType,Order> floor(const fvar<RealType,Order>& cr)
{
    using std::floor;
    return fvar<RealType,Order>(floor(static_cast<typename fvar<RealType,Order>::root_type>(cr)));
}

template<typename RealType, size_t Order>
fvar<RealType,Order> exp(const fvar<RealType,Order>& cr)
{
    using std::exp;
    using root_type = typename fvar<RealType,Order>::root_type;
    const root_type d0 = exp(static_cast<root_type>(cr));
    return cr.apply_with_horner([&d0](size_t) { return d0; });
}

template<typename RealType, size_t Order>
fvar<RealType,Order> pow(const fvar<RealType,Order>& x,const typename fvar<RealType,Order>::root_type& y)
{
    using std::pow;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    root_type derivatives[order+1];
    const root_type x0 = static_cast<root_type>(x);
    size_t i = 0;
    root_type coef = 1;
    for (; i<=order && coef!=0 ; ++i)
    {
        derivatives[i] = coef * pow(x0, y-i);
        coef *= y - i;
    }
    return x.apply([&derivatives,i](size_t j) { return j < i ? derivatives[j] : 0; });
}

template<typename RealType, size_t Order>
fvar<RealType,Order> pow(const typename fvar<RealType,Order>::root_type& x,const fvar<RealType,Order>& y)
{
    using std::log;
    return exp(y*log(x));
}

template<typename RealType1, size_t Order1, typename RealType2, size_t Order2>
promote<fvar<RealType1,Order1>,fvar<RealType2,Order2>>
    pow(const fvar<RealType1,Order1>& x, const fvar<RealType2,Order2>& y)
{
    return exp(y*log(x));
}

template<typename RealType, size_t Order>
fvar<RealType,Order> sqrt(const fvar<RealType,Order>& cr)
{
    using std::sqrt;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    root_type derivatives[order+1];
    const root_type x = static_cast<root_type>(cr);
    *derivatives = sqrt(x);
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(*derivatives);
    else
    {
        root_type numerator = 0.5;
        root_type powers = 1;
        derivatives[1] = numerator / *derivatives;
        for (size_t i=2 ; i<=order ; ++i)
        {
            numerator *= -0.5 * ((i<<1)-3);
            powers *= x;
            derivatives[i] = numerator / (powers * *derivatives);
        }
        return cr.apply([&derivatives](size_t i) { return derivatives[i]; });
    }
}

// Natural logarithm. If cr==0 then derivative(i) may have nans due to nans from inverse().
template<typename RealType, size_t Order>
fvar<RealType,Order> log(const fvar<RealType,Order>& cr)
{
    using std::log;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = log(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        const auto d1 = make_fvar<root_type,order-1>(static_cast<root_type>(cr)).inverse(); // log'(x) = 1 / x
        return cr.apply_with_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> frexp(const fvar<RealType,Order>& cr, int* exp)
{
    using std::frexp;
    using root_type = typename fvar<RealType,Order>::root_type;
    frexp(static_cast<root_type>(cr), exp);
    return cr * std::exp2(-*exp);
}

template<typename RealType, size_t Order>
fvar<RealType,Order> ldexp(const fvar<RealType,Order>& cr, int exp)
{
    return cr * std::exp2(exp);
}

template<typename RealType, size_t Order>
fvar<RealType,Order> cos(const fvar<RealType,Order>& cr)
{
    using std::cos;
    using std::sin;
    using root_type = typename fvar<RealType,Order>::root_type;
    const root_type d0 = cos(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (fvar<RealType,Order>::order_sum == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        const root_type d1 = -sin(static_cast<root_type>(cr));
        const root_type derivatives[4] { d0, d1, -d0, -d1 };
        return cr.apply_with_horner([&derivatives](size_t i) { return derivatives[i&3]; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> sin(const fvar<RealType,Order>& cr)
{
    using std::sin;
    using std::cos;
    using root_type = typename fvar<RealType,Order>::root_type;
    const root_type d0 = sin(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (fvar<RealType,Order>::order_sum == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        const root_type d1 = cos(static_cast<root_type>(cr));
        const root_type derivatives[4] { d0, d1, -d0, -d1 };
        return cr.apply_with_horner([&derivatives](size_t i) { return derivatives[i&3]; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> asin(const fvar<RealType,Order>& cr)
{
    using std::asin;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = asin(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto d1 = make_fvar<root_type,order-1>(static_cast<root_type>(cr)); // asin'(x) = 1 / sqrt(1-x*x).
        d1 = sqrt(1-(d1*=d1)).inverse(); // asin(1): d1 = depth(1)(inf,inf,-nan,-nan,-nan)
        //d1 = sqrt((1-(d1*=d1)).inverse()); // asin(1): d1 = depth(1)(inf,-nan,-nan,-nan,-nan)
        return cr.apply_with_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> tan(const fvar<RealType,Order>& cr)
{
    return sin(cr) / cos(cr);
}

template<typename RealType, size_t Order>
fvar<RealType,Order> atan(const fvar<RealType,Order>& cr)
{
    using std::atan;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = atan(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto d1 = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        d1 = ((d1*=d1)+=1).inverse(); // atan'(x) = 1 / (x*x+1).
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType1, size_t Order1, typename RealType2, size_t Order2>
promote<fvar<RealType1,Order1>,fvar<RealType2,Order2>>
    fmod(const fvar<RealType1,Order1>& cr1, const fvar<RealType2,Order2>& cr2)
{
    using std::trunc;
    const auto numer = static_cast<typename fvar<RealType1,Order1>::root_type>(cr1);
    const auto denom = static_cast<typename fvar<RealType2,Order2>::root_type>(cr2);
    return cr1 - cr2 * trunc(numer/denom);
}

template<typename RealType, size_t Order>
fvar<RealType,Order> round(const fvar<RealType,Order>& cr)
{
    using std::round;
    return fvar<RealType,Order>(round(static_cast<typename fvar<RealType,Order>::root_type>(cr)));
}

template<typename RealType, size_t Order>
int iround(const fvar<RealType,Order>& cr)
{
    using boost::math::iround;
    return iround(static_cast<typename fvar<RealType,Order>::root_type>(cr));
}

template<typename RealType, size_t Order>
fvar<RealType,Order> trunc(const fvar<RealType,Order>& cr)
{
    using std::trunc;
    return fvar<RealType,Order>(trunc(static_cast<typename fvar<RealType,Order>::root_type>(cr)));
}

template<typename RealType, size_t Order>
int itrunc(const fvar<RealType,Order>& cr)
{
    using boost::math::itrunc;
    return itrunc(static_cast<typename fvar<RealType,Order>::root_type>(cr));
}

template<typename RealType, size_t Order>
std::ostream& operator<<(std::ostream& out, const fvar<RealType,Order>& cr)
{
    out << "depth(" << cr.depth << ')';
    for (size_t i=0 ; i<cr.v.size() ; ++i)
        out << (i?',':'(') << cr.v[i];
    return out << ')';
}

// Additional functions

template<typename RealType, size_t Order>
fvar<RealType,Order> acos(const fvar<RealType,Order>& cr)
{
    using std::acos;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = acos(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto x = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        const auto d1 = -sqrt(1-(x*=x)).inverse(); // acos'(x) = -1 / sqrt(1-x*x).
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> acosh(const fvar<RealType,Order>& cr)
{
    using std::acosh;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = acosh(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto x = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        const auto d1 = sqrt((x*=x)-1).inverse(); // acosh'(x) = 1 / sqrt(x*x-1).
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> asinh(const fvar<RealType,Order>& cr)
{
    using std::asinh;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = asinh(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto x = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        const auto d1 = sqrt((x*=x)+1).inverse(); // asinh'(x) = 1 / sqrt(x*x+1).
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> atanh(const fvar<RealType,Order>& cr)
{
    using std::atanh;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = atanh(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto x = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        const auto d1 = (1-(x*=x)).inverse(); // atanh'(x) = 1 / (1-x*x)
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> cosh(const fvar<RealType,Order>& cr)
{
    using std::cosh;
    using std::sinh;
    using root_type = typename fvar<RealType,Order>::root_type;
    const root_type d0 = cosh(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (fvar<RealType,Order>::order_sum == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        const root_type derivatives[2] { d0, sinh(static_cast<root_type>(cr)) };
        return cr.apply_with_horner([&derivatives](size_t i) { return derivatives[i&1]; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> erf(const fvar<RealType,Order>& cr)
{
    using std::erf;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = erf(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto x = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        const auto d1 = 2*boost::math::constants::one_div_root_pi<root_type>()*exp(-(x*=x)); // 2/sqrt(pi)*exp(-x*x)
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> erfc(const fvar<RealType,Order>& cr)
{
    using std::erfc;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    const root_type d0 = erfc(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        auto x = make_fvar<root_type,order-1>(static_cast<root_type>(cr));
        const auto d1 = -2*boost::math::constants::one_div_root_pi<root_type>()*exp(-(x*=x)); // erfc'(x)=-erf'(x)
        return cr.apply_with_horner_factorials([&d0,&d1](size_t i) { return i ? d1.at(i-1)/i : d0; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> lambert_w0(const fvar<RealType,Order>& cr)
{
    using boost::math::lambert_w0;
    using std::exp;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    root_type derivatives[order+1];
    *derivatives = lambert_w0(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(*derivatives);
    else
    {
        const root_type expw = exp(*derivatives);
        derivatives[1] = 1 / (static_cast<root_type>(cr) + expw);
        if BOOST_AUTODIFF_IF_CONSTEXPR (order == 1)
            return cr.apply([&derivatives](size_t i) { return derivatives[i]; });
        else
        {
            root_type d1powers = derivatives[1] * derivatives[1];
            const root_type x = derivatives[1] * expw;
            derivatives[2] = d1powers * (-1 - x);
            std::array<root_type,order> coef {{ -1, -1 }}; // as in derivatives[2].
            for (size_t n=3 ; n<=order ; ++n)
            {
                coef[n-1] = coef[n-2] * -static_cast<root_type>(2*n-3);
                for (size_t j=n-2 ; j!=0 ; --j)
                    (coef[j] *= -static_cast<root_type>(n-1)) -= (n+j-2) * coef[j-1];
                coef[0] *= -static_cast<root_type>(n-1);
                d1powers *= derivatives[1];
                derivatives[n] = d1powers * std::accumulate(coef.crend()-(n-1), coef.crend(), coef[n-1],
                    [&x](const root_type& a, const root_type& b) { return a*x + b; });
            }
            return cr.apply([&derivatives](size_t i) { return derivatives[i]; });
        }
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> sinc(const fvar<RealType,Order>& cr)
{
    if (cr != 0)
        return sin(cr) / cr;
    using root_type = typename fvar<RealType,Order>::root_type;
    constexpr size_t order = fvar<RealType,Order>::order_sum;
    root_type taylor[order+1] { 1 }; // sinc(0) = 1
    if BOOST_AUTODIFF_IF_CONSTEXPR (order == 0)
        return fvar<RealType,Order>(*taylor);
    else
    {
        for (size_t n=2 ; n<=order ; n+=2)
            taylor[n] = (1-static_cast<int>(n&2)) / boost::math::factorial<root_type>(n+1);
        return cr.apply_with_factorials([&taylor](size_t i) { return taylor[i]; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> sinh(const fvar<RealType,Order>& cr)
{
    using std::sinh;
    using std::cosh;
    using root_type = typename fvar<RealType,Order>::root_type;
    const root_type d0 = sinh(static_cast<root_type>(cr));
    if BOOST_AUTODIFF_IF_CONSTEXPR (fvar<RealType,Order>::order_sum == 0)
        return fvar<RealType,Order>(d0);
    else
    {
        const root_type derivatives[2] { d0, cosh(static_cast<root_type>(cr)) };
        return cr.apply_with_horner([&derivatives](size_t i) { return derivatives[i&1]; });
    }
}

template<typename RealType, size_t Order>
fvar<RealType,Order> tanh(const fvar<RealType,Order>& cr)
{
    const fvar<RealType,Order> exp2cr = exp(cr*2);
    return (exp2cr - 1) /= (exp2cr + 1);
}

template<typename RealType, size_t Order>
long lround(const fvar<RealType,Order>& cr)
{
    using std::lround;
    return lround(static_cast<typename fvar<RealType,Order>::root_type>(cr));
}

template<typename RealType, size_t Order>
long long llround(const fvar<RealType,Order>& cr)
{
    using std::llround;
    return llround(static_cast<typename fvar<RealType,Order>::root_type>(cr));
}

template<typename RealType, size_t Order>
long double truncl(const fvar<RealType,Order>& cr)
{
    using std::truncl;
    return truncl(static_cast<typename fvar<RealType,Order>::root_type>(cr));
}

} } } } } // namespace boost::math::differentiation::autodiff_v1::detail

namespace std {

/// boost::math::tools::digits<RealType>() is handled by this std::numeric_limits<> specialization,
/// and similarly for max_value, min_value, log_max_value, log_min_value, and epsilon.
template <typename RealType, size_t Order>
class numeric_limits<boost::math::differentiation::detail::fvar<RealType,Order>>
    : public numeric_limits<typename boost::math::differentiation::detail::fvar<RealType,Order>::root_type>
{ };

} // namespace std

namespace boost { namespace math { namespace tools {

// See boost/math/tools/promotion.hpp
template <typename RealType0, size_t Order0, typename RealType1, size_t Order1>
struct promote_args_2<differentiation::detail::fvar<RealType0,Order0>,differentiation::detail::fvar<RealType1,Order1>>
{
    using type = differentiation::detail::fvar<typename promote_args_2<RealType0,RealType1>::type,
#ifndef BOOST_NO_CXX14_CONSTEXPR
        std::max(Order0,Order1)>;
#else
        Order0 < Order1 ? Order1 : Order0>;
#endif
};

template <typename RealType0, size_t Order0, typename RealType1>
struct promote_args_2<differentiation::detail::fvar<RealType0,Order0>,RealType1>
{
    using type = differentiation::detail::fvar<typename promote_args_2<RealType0,RealType1>::type,Order0>;
};

template <typename RealType0, typename RealType1, size_t Order1>
struct promote_args_2<RealType0,differentiation::detail::fvar<RealType1,Order1>>
{
    using type = differentiation::detail::fvar<typename promote_args_2<RealType0,RealType1>::type,Order1>;
};

} } } // namespace boost::math::tools

#ifdef BOOST_NO_CXX17_IF_CONSTEXPR
#include "autodiff_cpp11.hpp"
#endif

#endif // BOOST_MATH_DIFFERENTIATION_AUTODIFF_HPP


#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <boost/multiprecision/cpp_int.hpp>
using namespace boost::multiprecision;
using namespace std;
int main() {
    cpp_int a, b, c, d; cin >> a >> b >> c >> d;
    cpp_int x = (b / d) + (b / c) - (b / lcm(c, d)), y = ((a - 1) / d) + ((a - 1) / c) - ((a - 1) / lcm(c, d));
    cout << (b - a + 1) - (x - y) << endl;
}