/*
 * Copyright 2015-2021 CNRS-UM LIRMM, CNRS-AIST JRL
 */

#define EIGEN_RUNTIME_NO_MALLOC

#include <mc_rtc/log/FlatLog.h>
#include <mc_rtc/log/Logger.h>

#include <boost/filesystem.hpp>
namespace bfs = boost::filesystem;

#include <boost/test/unit_test.hpp>

#include "utils.h"

bool operator==(const Eigen::Quaterniond & lhs, const Eigen::Quaterniond & rhs)
{
  return lhs.vec() == rhs.vec();
}

/** Check one iteration of the logger */
template<bool malloc_allowed = false, typename Callback>
void check(mc_rtc::Logger & logger, Callback && cb)
{
  Eigen::internal::set_is_malloc_allowed(malloc_allowed);
  logger.log();
  Eigen::internal::set_is_malloc_allowed(true);
  logger.flush();
  mc_rtc::log::FlatLog log(logger.path());
  cb(log);
}

/** Check the value in a FlatLog at a given index */
template<typename T>
void check(const mc_rtc::log::FlatLog & log, const std::string & entry, size_t idx, const T & member)
{
  const T * raw = log.getRaw<T>(entry, idx);
  BOOST_REQUIRE(raw && *raw == member);
}

struct LogData
{
private:
  /** Some data members supported by the logger */
  bool b = random_bool();
  double d = rnd();
  std::string s = random_string();
  mutable Eigen::Vector2d v2d = Eigen::Vector2d::Random();
  mutable Eigen::Vector3d v3d = Eigen::Vector3d::Random();
  mutable Eigen::Vector6d v6d = Eigen::Vector6d::Random();
  mutable Eigen::VectorXd vxd = Eigen::VectorXd::Random(static_cast<Eigen::DenseIndex>(random_size()));
  Eigen::Quaterniond q = random_quat();
  sva::PTransformd pt = random_pt();
  sva::ForceVecd fv = random_fv();
  sva::MotionVecd mv = random_mv();
  std::vector<double> v = random_vector();

public:
#define DEFINE_GETTER(MEMBER)                               \
  auto get_##MEMBER() const->const decltype(this->MEMBER) & \
  {                                                         \
    return this->MEMBER;                                    \
  }
  DEFINE_GETTER(b)
  DEFINE_GETTER(d)
  DEFINE_GETTER(s)
  DEFINE_GETTER(v2d)
  DEFINE_GETTER(v3d)
  DEFINE_GETTER(v6d)
  DEFINE_GETTER(vxd)
  DEFINE_GETTER(q)
  DEFINE_GETTER(pt)
  DEFINE_GETTER(fv)
  DEFINE_GETTER(mv)
  DEFINE_GETTER(v)
#undef DEFINE_GETTER

#define DEFINE_GET_AS_REF(MEMBER)                                               \
  auto get_##MEMBER##_as_ref() const->Eigen::Ref<decltype(this->MEMBER)>        \
  {                                                                             \
    return this->MEMBER;                                                        \
  }                                                                             \
  auto get_##MEMBER##_as_cref() const->Eigen::Ref<const decltype(this->MEMBER)> \
  {                                                                             \
    return this->MEMBER;                                                        \
  }
  DEFINE_GET_AS_REF(v2d)
  DEFINE_GET_AS_REF(v3d)
  DEFINE_GET_AS_REF(v6d)
  DEFINE_GET_AS_REF(vxd)
#undef DEFINE_GET_AS_REF

  /** Change data */
  void refresh()
  {
    *this = LogData{};
  }

  void addToLogger(mc_rtc::Logger & logger, bool withSource)
  {
#define ADD_LOG_ENTRY(NAME, MEMBER)                                        \
  withSource ? logger.addLogEntry(NAME, this, [this]() { return MEMBER; }) \
             : logger.addLogEntry(NAME, [this]() { return MEMBER; });
    ADD_LOG_ENTRY("bool", b);
    ADD_LOG_ENTRY("double", d);
    ADD_LOG_ENTRY("std::string", s);
    ADD_LOG_ENTRY("Eigen::Vector2d", v2d);
    ADD_LOG_ENTRY("Eigen::Vector3d", v3d);
    ADD_LOG_ENTRY("Eigen::Vector6d", v6d);
    ADD_LOG_ENTRY("Eigen::VectorXd", vxd);
    ADD_LOG_ENTRY("Eigen::Quaterniond", q);
    ADD_LOG_ENTRY("sva::PTransformd", pt);
    ADD_LOG_ENTRY("sva::ForceVecd", fv);
    ADD_LOG_ENTRY("sva::MotionVecd", mv);
    ADD_LOG_ENTRY("std::vector<double>", v);
#undef ADD_LOG_ENTRY
#define ADD_LOG_ENTRY_AS_REF(NAME, MEMBER)                                                       \
  {                                                                                \
    using RefT = Eigen::Ref<decltype(MEMBER)>;                                                   \
    using CRefT = Eigen::Ref<const decltype(MEMBER)>;                                            \
    withSource ? logger.addLogEntry(NAME "_as_ref", this, [this]() -> RefT { return MEMBER; })   \
               : logger.addLogEntry(NAME "_as_ref", [this]() -> RefT { return MEMBER; });        \
    withSource ? logger.addLogEntry(NAME "_as_cref", this, [this]() -> CRefT { return MEMBER; }) \
               : logger.addLogEntry(NAME "_as_cref", [this]() -> RefT { return MEMBER; });       \
  }
    ADD_LOG_ENTRY_AS_REF("v2d", v2d);
    ADD_LOG_ENTRY_AS_REF("v3d", v3d);
    ADD_LOG_ENTRY_AS_REF("v6d", v6d);
    ADD_LOG_ENTRY_AS_REF("vxd", vxd);
#undef ADD_LOG_ENTRY_AS_REF
  }

  void addToLoggerWithMemberPointer(mc_rtc::Logger & logger)
  {
    logger.addLogEntry<decltype(&LogData::b), &LogData::b>("bool", this);
    logger.addLogEntry<decltype(&LogData::d), &LogData::d>("double", this);
    logger.addLogEntry<decltype(&LogData::s), &LogData::s>("std::string", this);
    logger.addLogEntry<decltype(&LogData::v2d), &LogData::v2d>("Eigen::Vector2d", this);
    logger.addLogEntry<decltype(&LogData::v3d), &LogData::v3d>("Eigen::Vector3d", this);
    logger.addLogEntry<decltype(&LogData::v6d), &LogData::v6d>("Eigen::Vector6d", this);
    logger.addLogEntry<decltype(&LogData::vxd), &LogData::vxd>("Eigen::VectorXd", this);
    logger.addLogEntry<decltype(&LogData::q), &LogData::q>("Eigen::Quaterniond", this);
    logger.addLogEntry<decltype(&LogData::pt), &LogData::pt>("sva::PTransformd", this);
    logger.addLogEntry<decltype(&LogData::fv), &LogData::fv>("sva::ForceVecd", this);
    logger.addLogEntry<decltype(&LogData::mv), &LogData::mv>("sva::MotionVecd", this);
    logger.addLogEntry<decltype(&LogData::v), &LogData::v>("std::vector<double>", this);
#define ADD_LOG_ENTRY_AS_REF(NAME, MEMBER)                                           \
  {                                                                                \
    using RefT = Eigen::Ref<decltype(MEMBER)>;                                       \
    using CRefT = Eigen::Ref<const decltype(MEMBER)>;                                \
    logger.addLogEntry(NAME "_as_ref", this, [this]() -> RefT { return MEMBER; });   \
    logger.addLogEntry(NAME "_as_cref", this, [this]() -> CRefT { return MEMBER; }); \
  }
    // Avoid special casing the check
    ADD_LOG_ENTRY_AS_REF("v2d", v2d);
    ADD_LOG_ENTRY_AS_REF("v3d", v3d);
    ADD_LOG_ENTRY_AS_REF("v6d", v6d);
    ADD_LOG_ENTRY_AS_REF("vxd", vxd);
#undef ADD_LOG_ENTRY_AS_REF
  }

  void addToLoggerWithMemberPointerMacro(mc_rtc::Logger & logger)
  {
    MC_RTC_LOG_HELPER("bool", b);
    MC_RTC_LOG_HELPER("double", d);
    MC_RTC_LOG_HELPER("std::string", s);
    MC_RTC_LOG_HELPER("Eigen::Vector2d", v2d);
    MC_RTC_LOG_HELPER("Eigen::Vector3d", v3d);
    MC_RTC_LOG_HELPER("Eigen::Vector6d", v6d);
    MC_RTC_LOG_HELPER("Eigen::VectorXd", vxd);
    MC_RTC_LOG_HELPER("Eigen::Quaterniond", q);
    MC_RTC_LOG_HELPER("sva::PTransformd", pt);
    MC_RTC_LOG_HELPER("sva::ForceVecd", fv);
    MC_RTC_LOG_HELPER("sva::MotionVecd", mv);
    MC_RTC_LOG_HELPER("std::vector<double>", v);
#define ADD_LOG_ENTRY_AS_REF(NAME, MEMBER)                                           \
  {                                                                                \
    using RefT = Eigen::Ref<decltype(MEMBER)>;                                       \
    using CRefT = Eigen::Ref<const decltype(MEMBER)>;                                \
    logger.addLogEntry(NAME "_as_ref", this, [this]() -> RefT { return MEMBER; });   \
    logger.addLogEntry(NAME "_as_cref", this, [this]() -> CRefT { return MEMBER; }); \
  }
    // Avoid special casing the check
    ADD_LOG_ENTRY_AS_REF("v2d", v2d);
    ADD_LOG_ENTRY_AS_REF("v3d", v3d);
    ADD_LOG_ENTRY_AS_REF("v6d", v6d);
    ADD_LOG_ENTRY_AS_REF("vxd", vxd);
#undef ADD_LOG_ENTRY_AS_REF
  }

  void addToLoggerWithGetter(mc_rtc::Logger & logger)
  {
    logger.addLogEntry<decltype(&LogData::get_b), &LogData::get_b>("bool", this);
    logger.addLogEntry<decltype(&LogData::get_d), &LogData::get_d>("double", this);
    logger.addLogEntry<decltype(&LogData::get_s), &LogData::get_s>("std::string", this);
    logger.addLogEntry<decltype(&LogData::get_v2d), &LogData::get_v2d>("Eigen::Vector2d", this);
    logger.addLogEntry<decltype(&LogData::get_v3d), &LogData::get_v3d>("Eigen::Vector3d", this);
    logger.addLogEntry<decltype(&LogData::get_v6d), &LogData::get_v6d>("Eigen::Vector6d", this);
    logger.addLogEntry<decltype(&LogData::get_vxd), &LogData::get_vxd>("Eigen::VectorXd", this);
    logger.addLogEntry<decltype(&LogData::get_q), &LogData::get_q>("Eigen::Quaterniond", this);
    logger.addLogEntry<decltype(&LogData::get_pt), &LogData::get_pt>("sva::PTransformd", this);
    logger.addLogEntry<decltype(&LogData::get_fv), &LogData::get_fv>("sva::ForceVecd", this);
    logger.addLogEntry<decltype(&LogData::get_mv), &LogData::get_mv>("sva::MotionVecd", this);
    logger.addLogEntry<decltype(&LogData::get_v), &LogData::get_v>("std::vector<double>", this);
    logger.addLogEntry<decltype(&LogData::get_v2d_as_ref), &LogData::get_v2d_as_ref>("v2d_as_ref", this);
    logger.addLogEntry<decltype(&LogData::get_v2d_as_cref), &LogData::get_v2d_as_cref>("v2d_as_cref", this);
    logger.addLogEntry<decltype(&LogData::get_v3d_as_ref), &LogData::get_v3d_as_ref>("v3d_as_ref", this);
    logger.addLogEntry<decltype(&LogData::get_v3d_as_cref), &LogData::get_v3d_as_cref>("v3d_as_cref", this);
    logger.addLogEntry<decltype(&LogData::get_v6d_as_ref), &LogData::get_v6d_as_ref>("v6d_as_ref", this);
    logger.addLogEntry<decltype(&LogData::get_v6d_as_cref), &LogData::get_v6d_as_cref>("v6d_as_cref", this);
    logger.addLogEntry<decltype(&LogData::get_vxd_as_ref), &LogData::get_vxd_as_ref>("vxd_as_ref", this);
    logger.addLogEntry<decltype(&LogData::get_vxd_as_cref), &LogData::get_vxd_as_cref>("vxd_as_cref", this);
  }

  void addToLoggerWithGetterMacro(mc_rtc::Logger & logger)
  {
    MC_RTC_LOG_HELPER("bool", get_b);
    MC_RTC_LOG_HELPER("double", get_d);
    MC_RTC_LOG_HELPER("std::string", get_s);
    MC_RTC_LOG_HELPER("Eigen::Vector2d", get_v2d);
    MC_RTC_LOG_HELPER("Eigen::Vector3d", get_v3d);
    MC_RTC_LOG_HELPER("Eigen::Vector6d", get_v6d);
    MC_RTC_LOG_HELPER("Eigen::VectorXd", get_vxd);
    MC_RTC_LOG_HELPER("Eigen::Quaterniond", get_q);
    MC_RTC_LOG_HELPER("sva::PTransformd", get_pt);
    MC_RTC_LOG_HELPER("sva::ForceVecd", get_fv);
    MC_RTC_LOG_HELPER("sva::MotionVecd", get_mv);
    MC_RTC_LOG_HELPER("std::vector<double>", get_v);
    MC_RTC_LOG_HELPER("v2d_as_ref", get_v2d_as_ref);
    MC_RTC_LOG_HELPER("v2d_as_cref", get_v2d_as_cref);
    MC_RTC_LOG_HELPER("v3d_as_ref", get_v3d_as_ref);
    MC_RTC_LOG_HELPER("v3d_as_cref", get_v3d_as_cref);
    MC_RTC_LOG_HELPER("v6d_as_ref", get_v6d_as_ref);
    MC_RTC_LOG_HELPER("v6d_as_cref", get_v6d_as_cref);
    MC_RTC_LOG_HELPER("vxd_as_ref", get_vxd_as_ref);
    MC_RTC_LOG_HELPER("vxd_as_cref", get_vxd_as_cref);
  }

  void removeFromLogger(mc_rtc::Logger & logger)
  {
    logger.removeLogEntry("bool");
    logger.removeLogEntry("double");
    logger.removeLogEntry("std::string");
    logger.removeLogEntry("Eigen::Vector2d");
    logger.removeLogEntry("Eigen::Vector3d");
    logger.removeLogEntry("Eigen::Vector6d");
    logger.removeLogEntry("Eigen::VectorXd");
    logger.removeLogEntry("Eigen::Quaterniond");
    logger.removeLogEntry("sva::PTransformd");
    logger.removeLogEntry("sva::ForceVecd");
    logger.removeLogEntry("sva::MotionVecd");
    logger.removeLogEntry("std::vector<double>");
    logger.removeLogEntry("v2d_as_ref");
    logger.removeLogEntry("v2d_as_cref");
    logger.removeLogEntry("v3d_as_ref");
    logger.removeLogEntry("v3d_as_cref");
    logger.removeLogEntry("vxd_as_ref");
    logger.removeLogEntry("v6d_as_ref");
    logger.removeLogEntry("v6d_as_cref");
    logger.removeLogEntry("vxd_as_cref");
  }

  /** Check that the latest entry in the FlatLog has the same values as in the class */
  void check(const mc_rtc::log::FlatLog & log)
  {
    BOOST_REQUIRE(log.size() >= 1);
    size_t idx = log.size() - 1;
    ::check(log, "bool", idx, b);
    ::check(log, "double", idx, d);
    ::check(log, "std::string", idx, s);
    ::check(log, "Eigen::Vector2d", idx, v2d);
    ::check(log, "Eigen::Vector3d", idx, v3d);
    ::check(log, "Eigen::Vector6d", idx, v6d);
    ::check(log, "Eigen::VectorXd", idx, vxd);
    ::check(log, "Eigen::Quaterniond", idx, q);
    ::check(log, "sva::PTransformd", idx, pt);
    ::check(log, "sva::ForceVecd", idx, fv);
    ::check(log, "sva::MotionVecd", idx, mv);
    ::check(log, "std::vector<double>", idx, v);
    ::check(log, "v2d_as_ref", idx, v2d);
    ::check(log, "v2d_as_cref", idx, v2d);
    ::check(log, "v3d_as_ref", idx, v3d);
    ::check(log, "v3d_as_cref", idx, v3d);
    ::check(log, "v6d_as_ref", idx, v6d);
    ::check(log, "v6d_as_cref", idx, v6d);
    ::check(log, "vxd_as_ref", idx, vxd);
    ::check(log, "vxd_as_cref", idx, vxd);
  }

  /** Check that the entry at the given index has none of the class data */
  void check_empty(const mc_rtc::log::FlatLog & log, size_t idx)
  {
    BOOST_REQUIRE(log.getRaw<bool>("bool", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<double>("double", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<std::string>("std::string", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector2d>("Eigen::Vector2d", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector3d>("Eigen::Vector3d", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector6d>("Eigen::Vector6d", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::VectorXd>("Eigen::VectorXd", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Quaterniond>("Eigen::Quaterniond", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<sva::PTransformd>("sva::PTransformd", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<sva::ForceVecd>("sva::ForceVecd", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<sva::MotionVecd>("sva::MotionVecd", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<std::vector<double>>("std::vector<double>", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector2d>("v2d_as_ref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector2d>("v2d_as_cref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector3d>("v3d_as_ref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector3d>("v3d_as_cref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector6d>("v6d_as_ref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::Vector6d>("v6d_as_cref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::VectorXd>("vxd_as_ref", idx) == nullptr);
    BOOST_REQUIRE(log.getRaw<Eigen::VectorXd>("vxd_as_cref", idx) == nullptr);
  }

  /** Shortcut to check the latest iteration is empty */
  void check_empty(const mc_rtc::log::FlatLog & log)
  {
    BOOST_REQUIRE(log.size() >= 1);
    check_empty(log, log.size() - 1);
  }
};

BOOST_AUTO_TEST_CASE(TestLogger)
{
  std::string path;
  {
    using Policy = mc_rtc::Logger::Policy;
    mc_rtc::Logger logger(Policy::NON_THREADED, bfs::temp_directory_path().string(), "mc-rtc-test");
    logger.start("logger", 1.0);
    size_t iter = 1;
    /** Iteration 1 only time */
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.entries() == std::set<std::string>{"t"});
      BOOST_REQUIRE(log.size() == iter);
      auto t0 = log.getRaw<double>("t", 0);
      BOOST_REQUIRE(t0 && *t0 == 0.0);
      iter++;
    });
    /** Iteration 2, add callbacks without a source */
    LogData data;
    data.addToLogger(logger, false);
    /** Malloc is allowed in this test because we provided naive callbacks */
    check<true>(logger, [&](const mc_rtc::log::FlatLog & log) {
      std::set<std::string> entries = {"t",
                                       "bool",
                                       "double",
                                       "std::string",
                                       "Eigen::Vector2d",
                                       "Eigen::Vector3d",
                                       "Eigen::Vector6d",
                                       "Eigen::VectorXd",
                                       "Eigen::Quaterniond",
                                       "sva::PTransformd",
                                       "sva::ForceVecd",
                                       "sva::MotionVecd",
                                       "std::vector<double>",
                                       "v2d_as_ref",
                                       "v2d_as_cref",
                                       "v3d_as_ref",
                                       "v3d_as_cref",
                                       "v6d_as_ref",
                                       "v6d_as_cref",
                                       "vxd_as_ref",
                                       "vxd_as_cref"};
      BOOST_REQUIRE(log.entries() == entries);
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log, 0);
      data.check(log);
      iter++;
    });
    /** Iteration 3, remove everything "manually" */
    data.removeFromLogger(logger);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    /** Iteration 4, add everything with a source */
    data.addToLogger(logger, true);
    /** Malloc is allowed in this test because we provided naive callbacks */
    check<true>(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check(log);
      iter++;
    });
    /** Iteration 5, remove everything via the source */
    logger.removeLogEntries(&data);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    /** Iteration 6 to 100, refresh data and check everything is ok */
    data.addToLogger(logger, true);
    for(iter = 6; iter <= 100; ++iter)
    {
      /** Malloc is allowed in this test because we provided naive callbacks */
      check<true>(logger, [&](const mc_rtc::log::FlatLog & log) {
        BOOST_REQUIRE(log.size() == iter);
        data.check(log);
        data.refresh();
      });
    }
    /** Iteration 101, remove the data once more */
    logger.removeLogEntries(&data);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    /** Iteration 102 to 150, add from pointer to member, refresh data and check everything is ok */
    data.addToLoggerWithMemberPointer(logger);
    for(iter = 102; iter <= 150; ++iter)
    {
      check(logger, [&](const mc_rtc::log::FlatLog & log) {
        BOOST_REQUIRE(log.size() == iter);
        data.check(log);
        data.refresh();
      });
    }
    /** Iteration 151, remove the data once more */
    logger.removeLogEntries(&data);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    /** Iteration 152 to 200, add from pointer to method, refresh data and check everything is ok */
    data.addToLoggerWithGetter(logger);
    for(iter = 152; iter <= 200; ++iter)
    {
      check(logger, [&](const mc_rtc::log::FlatLog & log) {
        BOOST_REQUIRE(log.size() == iter);
        data.check(log);
        data.refresh();
      });
    }
    /** Iteration 201, remove the data once more */
    logger.removeLogEntries(&data);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    /** Iteration 202 to 250, add from fast pointer to member, refresh data and check everything is ok */
    data.addToLoggerWithMemberPointerMacro(logger);
    for(iter = 202; iter <= 250; ++iter)
    {
      check(logger, [&](const mc_rtc::log::FlatLog & log) {
        BOOST_REQUIRE(log.size() == iter);
        data.check(log);
        data.refresh();
      });
    }
    /** Iteration 251, remove the data once more */
    logger.removeLogEntries(&data);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    /** Iteration 252 to 300, add from fast pointer to method, refresh data and check everything is ok */
    data.addToLoggerWithGetterMacro(logger);
    for(iter = 252; iter <= 200; ++iter)
    {
      check(logger, [&](const mc_rtc::log::FlatLog & log) {
        BOOST_REQUIRE(log.size() == iter);
        data.check(log);
        data.refresh();
      });
    }
    /** Iteration 301, remove the data once more */
    logger.removeLogEntries(&data);
    check(logger, [&](const mc_rtc::log::FlatLog & log) {
      BOOST_REQUIRE(log.size() == iter);
      data.check_empty(log);
      iter++;
    });
    path = logger.path();
  }
  auto latest = bfs::temp_directory_path() / "mc-rtc-test-logger-latest.bin";
  if(bfs::exists(latest))
  {
    bfs::remove(latest);
  }
  if(bfs::exists(path))
  {
    bfs::remove(path);
  }
}


//////////////////////////////////////////////////////////////////////////////////
// distribution::common::meta::random::generator.hpp                            //
//                                                                              //
//  (C) Copyright 2009 Erwann Rogard                                            //
//  Use, modification and distribution are subject to the                       //
//  Boost Software License, Version 1.0. (See accompanying file                 //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)            //
//////////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DISTRIBUTION_COMMON_META_RANDOM_GENERATOR_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_COMMON_META_RANDOM_GENERATOR_HPP_ER_2009
#include <boost/random/variate_generator.hpp>
#include <boost/statistics/detail/distribution_common/meta/random/distribution.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace distribution{
namespace meta{

    // U = V or U = V& allowed where V models UniformRandomNumberGenerator
    template<typename D,typename U>
    struct random_generator{
        typedef meta::random_distribution<D> meta_;
        typedef typename meta_::type r_;
        typedef boost::variate_generator<U,r_> type;

        static type call(U urng,const D& d){
            return type(urng,make_random_distribution(d));
        }
    };

}// meta

    template<typename D,typename U>
    typename meta::random_generator<D,U&>::type
    make_random_generator(U& urng,const D& dist){
        typedef meta::random_generator<D,U&> meta_;
        return meta_::call(urng,dist);
    }

}// distribution
}// detail
}// statistics
}// boost

#endif


#include "BinArray.h"
#include <iostream>
#include <tr1/functional>
#include <boost/bind.hpp>

void Bin::init(const LAABB &bounds)
{
  m_bounds = bounds;
}

void Bin::add(const LPoint &p)
{
  LAssert(m_bounds.contains(p));
  m_items.push_back(p);
}

BinArray::BinArray(int xBins, int yBins, const std::vector<LPoint> &points):
  m_bins(xBins, yBins)
{
  LAssert(xBins > 0 && yBins > 0);
  std::for_each(points.begin(), points.end(),
                boost::bind(&LAABB::addPoint, boost::ref(m_bounds), _1));
}

bool BinArray::addPoint(const LPoint &item)
{
  // TODO Expand the bins?
  if (!m_bounds.contains(item))
  {
    std::cerr << "addPoint: " << item << " failed, outside bounds: " << m_bounds << "\n";
    return false;
  }
  Bin *bin = binForPoint(item);
  if (!bin) return false;
  bin->add(item);
  return true;
}

Bin * BinArray::binForPoint(const LPoint &p)
{
  if (!m_bounds.contains(p)) return NULL;
  int x = LFloatToInt((p.x() - m_bounds.min().x()) / binWidth());
  int y = LFloatToInt((p.y() - m_bounds.min().y()) / binHeight());
  return &m_bins(x,y);
}

float BinArray::binWidth()const
{
  return m_bins(0,0).bounds().xSize();
}

float BinArray::binHeight()const
{
  return m_bins(0,0).bounds().ySize();
}

void BinArray::draw()
{
}


//          Copyright Juan Lucas Rey 2015 - 2016.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#include "bdouble.h"

#ifdef _WIN32
	#define _USE_MATH_DEFINES
#endif

#include <cmath>
#include <algorithm>
#include <stack>
#include <boost/math/special_functions/polygamma.hpp>
#include "partitionGenerator.h"

size_t bdouble::mDefaultOrder = 1;

//tape
vector<size_t> bdouble::op_trace(0,0);
vector<size_t> bdouble::index_trace(0,0);
vector<double> bdouble::val_trace;
size_t bdouble::indexcount = 0;

bdouble::bdouble(const double& rhs)
{
    mThisId = indexcount;
    indexcount += 1;

    mValue = rhs;
}

bdouble::bdouble(const bdouble& rhs)
{
    mValue = rhs.mValue;
    mThisId = rhs.mThisId;
}

bdouble& bdouble::operator= (const bdouble& rhs)
{
    mValue = rhs.mValue;
    mThisId = rhs.mThisId;

    return *this;
}

bdouble& bdouble::operator=(bdouble&& rhs)
{
    *this = rhs;
    return rhs;
}

bdouble bdouble::operator+(const bdouble& rhs) const
{
    const bdouble& lhs = (*this);

    //optimization: if the id is the same
    //then this is effectively a function
    //of one parameter
    if(lhs.id() == rhs.id())
        return rhs*2;
    else
    {
        bdouble res(lhs.mValue + rhs.mValue);

        bdouble::op_trace.push_back(bplusv);
        bdouble::index_trace.push_back(rhs.mThisId);
        bdouble::index_trace.push_back(lhs.mThisId);
        bdouble::index_trace.push_back(res.mThisId);

        return res;
    }
}

bdouble bdouble::operator-(const bdouble& rhs) const
{
    const bdouble& lhs = (*this);

    //optimization: if the id is the same
    //then this is effectively a function
    //of one parameter
    if(lhs.id() == rhs.id())
        return rhs*0;
    else
    {
        bdouble res(lhs.mValue - rhs.mValue);

        bdouble::op_trace.push_back(bminusv);
        bdouble::index_trace.push_back(rhs.mThisId);
        bdouble::index_trace.push_back(lhs.mThisId);
        bdouble::index_trace.push_back(res.mThisId);

        return res;
    }
}

bdouble bdouble::operator*(const bdouble& rhs) const
{
    const bdouble& lhs = (*this);

    //optimization: if the id is the same
    //then this is effectively a function
    //of one parameter
    if(lhs.id() == rhs.id())
        return pow(rhs,2);
    else
    {
        bdouble res(lhs.mValue * rhs.mValue);

        bdouble::op_trace.push_back(bmultv);
        bdouble::index_trace.push_back(rhs.mThisId);
        bdouble::index_trace.push_back(lhs.mThisId);
        bdouble::index_trace.push_back(res.mThisId);
        bdouble::val_trace.push_back(rhs.mValue);
        bdouble::val_trace.push_back(lhs.mValue);

        return res;
    }
}

bdouble bdouble::operator/(const bdouble& rhs) const
{
    return (*this)*inv(rhs);
}

#define function_define_store_value(name)               \
bdouble name (const bdouble& in)                        \
{                                                       \
    bdouble::val_trace.push_back(in.mValue);            \
    bdouble::val_trace.push_back( name (in.mValue));    \
                                                        \
    bdouble res(bdouble::val_trace.back());             \
                                                        \
    bdouble::op_trace.push_back(name##v);               \
    bdouble::index_trace.push_back(in.mThisId);         \
    bdouble::index_trace.push_back(res.mThisId);        \
                                                        \
    return res;                                         \
}

function_define_store_value(cos);
function_define_store_value(sin);
function_define_store_value(expm1);
function_define_store_value(log);
function_define_store_value(log1p);
function_define_store_value(log10);
function_define_store_value(log2);
function_define_store_value(cosh);
function_define_store_value(sinh);
function_define_store_value(erf);
function_define_store_value(erfc);
function_define_store_value(acos);
function_define_store_value(asin);
function_define_store_value(atan);
function_define_store_value(acosh);
function_define_store_value(asinh);
function_define_store_value(atanh);
function_define_store_value(lgamma);
function_define_store_value(tgamma);

#undef function_define_store_value

#define function_define_no_store_value(name)            \
bdouble name (const bdouble& in)                        \
{                                                       \
    bdouble::val_trace.push_back( name (in.mValue));    \
                                                        \
    bdouble res(bdouble::val_trace.back());             \
                                                        \
    bdouble::op_trace.push_back(name##v);               \
    bdouble::index_trace.push_back(in.mThisId);         \
    bdouble::index_trace.push_back(res.mThisId);        \
                                                        \
    return res;                                         \
}

function_define_no_store_value(exp);
function_define_no_store_value(exp2);
function_define_no_store_value(tan);
function_define_no_store_value(tanh);

#undef function_define_no_store_value

bdouble inv(const bdouble& in)
{
    bdouble::val_trace.push_back(1.0/in.mValue);

    bdouble res(bdouble::val_trace.back());

    bdouble::op_trace.push_back(invv);
    bdouble::index_trace.push_back(in.mThisId);
    bdouble::index_trace.push_back(res.mThisId);

    return res;
}

bdouble pow(const bdouble& in, const int& deg)
{
    bdouble::val_trace.push_back(in.mValue);
    bdouble::val_trace.push_back(deg);
    bdouble::val_trace.push_back(pow(in.mValue,deg));

    bdouble res(bdouble::val_trace.back());

    bdouble::op_trace.push_back(powintv);
    bdouble::index_trace.push_back(in.mThisId);
    bdouble::index_trace.push_back(res.mThisId);

    return res;

}

bdouble pow(const bdouble& in, const double& deg)
{
    bdouble::val_trace.push_back(in.mValue);
    bdouble::val_trace.push_back(deg);
    bdouble::val_trace.push_back(pow(in.mValue,deg));

    bdouble res(bdouble::val_trace.back());

    bdouble::op_trace.push_back(powv);
    bdouble::index_trace.push_back(in.mThisId);
    bdouble::index_trace.push_back(res.mThisId);

    return res;
}

bdouble pow(const double& val, const bdouble& in)
{
    return exp(log(val)*in);
}

bdouble pow(const bdouble& lhs, const bdouble& rhs)
{
    return exp(log(lhs)*rhs);
}

bdouble sqrt(const bdouble& in)
{
    bdouble::val_trace.push_back(in.mValue);
    bdouble::val_trace.push_back(0.5);
    bdouble::val_trace.push_back(sqrt(in.mValue));

    bdouble res(bdouble::val_trace.back());

    bdouble::op_trace.push_back(powv);
    bdouble::index_trace.push_back(in.mThisId);
    bdouble::index_trace.push_back(res.mThisId);

    return res;
}

bdouble cbrt(const bdouble& in)
{
    bdouble::val_trace.push_back(in.mValue);
    bdouble::val_trace.push_back(0.3333333333333333333333333333333333333333333333333333333333333333333333);
    bdouble::val_trace.push_back(cbrt(in.mValue));

    bdouble res(bdouble::val_trace.back());

    bdouble::op_trace.push_back(powv);
    bdouble::index_trace.push_back(in.mThisId);
    bdouble::index_trace.push_back(res.mThisId);

    return res;
}

bdouble N(const bdouble& in)
{
    bdouble::val_trace.push_back(in.mValue);
    bdouble::val_trace.push_back(0.5*(1.0+std::erf(in.mValue*M_SQRT1_2)));

    bdouble res(bdouble::val_trace.back());

    bdouble::op_trace.push_back(nv);
    bdouble::index_trace.push_back(in.mThisId);
    bdouble::index_trace.push_back(res.mThisId);

    return res;
}

bdouble ldexp(const bdouble& in, const int& exp)
{
    return in*ldexp(1.0,exp);
}

void TE_multconst(const double& value,const double& coeff,vector<double>& output,size_t loc = 0)
{
    while(loc < 2)
    {
        if(loc == 0)
            output[loc] = value*coeff;
        else if(loc == 1)
            output[loc] = coeff;

        loc++;
    }
}

void TE_sumconst(const double& value,const double& coeff,vector<double>& output,size_t loc = 0)
{
    while(loc < 2)
    {
        if(loc == 0)
            output[loc] = value + coeff;
        else if(loc == 1)
            output[loc] = 1.0;

        loc++;
    }
}

void TE_minusconst(const double& value,const double& coeff,vector<double>& output,size_t loc = 0)
{
    while(loc < 2)
    {
        if(loc == 0)
            output[loc] = coeff - value;
        else if(loc == 1)
            output[loc] = -1.0;

        loc++;
    }
}

void TE_cos(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::cos(value);
        else if(loc == 1)
            output[loc] = -std::sin(value);
        else if (loc <4)
            output[loc] = -output[loc-2];
        else
            output[loc] = output[loc-4];

        loc++;
    }
}

void TE_sin(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::sin(value);
        else if(loc == 1)
            output[loc] = std::cos(value);
        else if (loc <4)
            output[loc] = -output[loc-2];
        else
            output[loc] = output[loc-4];

        loc++;
    }
}

void TE_exp(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::exp(value);
        else
            output[loc] = output[0];

        loc++;
    }
}

void TE_exp2(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        double ratio = std::log(2.0);
        if(loc == 0)
            output[loc] = std::exp2(value);
        else
            output[loc] = ratio*output[loc-1];

        loc++;
    }
}

void TE_expm1(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::expm1(value);
        else if(loc == 1)
            output[loc] = std::exp(value);
        else
            output[loc] = output[1];

        loc++;
    }
}

void TE_pow(const double& value,const double& deg,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::pow(value,deg);
        loc++;
    }

    double newDeg = deg - (double)loc;
    double coeff = 1.0 + newDeg;
    while(loc != output.size())
    {
        if(coeff)
            output[loc] = coeff*std::pow(value,newDeg);

        coeff *= newDeg;
        newDeg -= 1.0;
        loc++;
    }
}

void TE_powint(const double& value,const double& deg,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::pow(value,deg);
        loc++;
    }

    int newDeg = (int)deg - (int)loc;
    double coeff = 1.0 + newDeg;
    while(loc != output.size())
    {
        if(coeff)
            output[loc] = coeff*std::pow(value,newDeg);

        coeff *= newDeg;
        newDeg -= 1;
        loc++;
    }

    /*int degint = (int)deg - (int)loc + 1;
    double value_inv = 1/value;

    while(loc != output.size())
    {
        if(loc == 0)
        {
            output[loc] = std::pow(value,degint);
            degint--;
        }
        else
        {
            if(degint)
            {
                if(output[loc-1])
                    output[loc] = degint*output[loc-1]*value_inv;

                degint--;
            }
            else
                output[loc] = 0;
        }

        loc++;
    }*/
}

void TE_inv(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = 1/value;
        else
            output[loc] = -((int)loc)*output[loc-1]*output[0];

        loc++;
    }
}

void TE_log(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::log(value);
        else if(loc == 1)
            output[loc] = 1/value;
        else
            output[loc] = -((double)loc-1)*output[loc-1]*output[1];

        loc++;
    }
}

void TE_log1p(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::log1p(value);
        else if(loc == 1)
            output[loc] = 1/(value+1.0);
        else
            output[loc] = -((double)loc-1)*output[loc-1]*output[1];

        loc++;
    }
}

void TE_log10(const double& value,vector<double>& output,size_t loc = 0)
{
    const double value_inv = 1/value;
    const double ratio = 1/M_LN10;

    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::log10(value);
        else
        {
            if(loc == 1)
                output[loc] = ratio*value_inv;
            else
                output[loc] = -((double)loc-1)*output[loc-1]*value_inv;
        }

        loc++;
    }
}

void TE_log2(const double& value,vector<double>& output,size_t loc = 0)
{
    const double value_inv = 1/value;
    const double ratio = 1/M_LN2;

    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::log10(value);
        else
        {
            if(loc == 1)
                output[loc] = ratio*value_inv;
            else
                output[loc] = -((double)loc-1)*output[loc-1]*value_inv;
        }

        loc++;
    }
}

void TE_cosh(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::cosh(value);
        else if(loc == 1)
            output[loc] = std::sinh(value);
        else
            output[loc] = output[loc-2];

        loc++;
    }
}

void TE_sinh(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::sinh(value);
        else if(loc == 1)
            output[loc] = std::cosh(value);
        else
            output[loc] = output[loc-2];

        loc++;
    }
}

//we use f''(X) + 2Xf'(X) = 0
void TE_erf(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::erf(value);
        else if(loc == 1)
            output[loc] = exp(-value*value)*M_2_SQRTPI;
        else if(loc == 2)
            output[loc] = -2*value*output[loc-1];
        else
            output[loc] = -2*value*output[loc-1] - 2*((int)loc-2)*output[loc-2];

        loc++;
    }
}

//we use f''(X) + 2Xf'(X) = 0
void TE_erfc(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = std::erfc(value);
        else if(loc == 1)
            output[loc] = -exp(-value*value)*M_2_SQRTPI;
        else if(loc == 2)
            output[loc] = -2*value*output[loc-1];
        else
            output[loc] = -2*value*output[loc-1] - 2*((int)loc-2)*output[loc-2];

        loc++;
    }
}

//we use f''(X) + Xf'(X) = 0
void TE_N(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = 0.5*(1.0+std::erf(value*M_SQRT1_2));
        else if(loc == 1)
            output[loc] = 0.5*M_2_SQRTPI*M_SQRT1_2*exp(-value*value*0.5);
        else if(loc == 2)
            output[loc] = -value*output[loc-1];
        else
            output[loc] = -value*output[loc-1] - ((int)loc-2)*output[loc-2];

        loc++;
    }
}

//we use (X^2-1)f''(X) + Xf'(X) = 0
void TE_acos(const double& value,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::acos(value);
        loc++;
    }

    if(loc != output.size())
    {
        double denom = value*value - 1;
        double one_over_denom = 1/denom;

        while(loc != output.size())
        {
            if(loc == 1)
                output[loc] = -sqrt(-one_over_denom);
            else if(loc==2)
                output[loc] = -value*output[loc-1]*one_over_denom;
            else if(loc==3)
                output[loc] = (-3*value*output[loc-1] -output[loc-2])*one_over_denom;
            else
            {
                int n = (int)loc-2;
                int n_pl_one = (int)loc-1;
                output[loc] = (-(2*n_pl_one-1)*value*output[loc-1]-n*n*output[loc-2])*one_over_denom;
            }

            loc++;
        }
    }
}

//we use (X^2-1)f''(X) + Xf'(X) = 0
void TE_asin(const double& value,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::asin(value);
        loc++;
    }

    if(loc != output.size())
    {
        double denom = value*value - 1;
        double one_over_denom = 1/denom;

        while(loc != output.size())
        {
            if(loc == 1)
                output[loc] = sqrt(-one_over_denom);
            else if(loc==2)
                output[loc] = -value*output[loc-1]*one_over_denom;
            else if(loc==3)
                output[loc] = (-3*value*output[loc-1] -output[loc-2])*one_over_denom;
            else
            {
                int n = (int)loc-2;
                int n_pl_one = (int)loc-1;
                output[loc] = (-(2*n_pl_one-1)*value*output[loc-1]-n*n*output[loc-2])*one_over_denom;
            }

            loc++;
        }
    }
}

//we use (X^2-1)f''(X) + Xf'(X) = 0
void TE_acosh(const double& value,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::acosh(value);
        loc++;
    }

    if(loc != output.size())
    {
        double denom = value*value - 1;
        double one_over_denom = 1/denom;

        while(loc != output.size())
        {
            if(loc == 1)
                output[loc] = sqrt(one_over_denom);
            else if(loc==2)
                output[loc] = -value*output[loc-1]*one_over_denom;
            else if(loc==3)
                output[loc] = (-3*value*output[loc-1] -output[loc-2])*one_over_denom;
            else
            {
                int n = (int)loc-2;
                int n_pl_one = (int)loc-1;
                output[loc] = (-(2*n_pl_one-1)*value*output[loc-1]-n*n*output[loc-2])*one_over_denom;
            }

            loc++;
        }
    }
}

//we use (X^2+1)f''(X) + 2Xf'(X) = 0
void TE_atan(const double& value,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::atan(value);
        loc++;
    }

    if(loc != output.size())
    {
        double denom = value*value + 1;
        double one_over_denom = 1/denom;

        while(loc != output.size())
        {
            if(loc == 1)
                output[loc] = one_over_denom;
            else if(loc==2)
                output[loc] = -2*value*output[loc-1]*one_over_denom;
            else if(loc==3)
                output[loc] = (-4*value*output[loc-1] - 2*output[loc-2])*one_over_denom;
            else
            {
                int n = (int)loc-2;
                int n_pl_one = (int)loc-1;
                output[loc] = (-2*value*n_pl_one*output[loc-1]-n*n_pl_one*output[loc-2])*one_over_denom;
            }

            loc++;
        }
    }
}

//we use (X^2+1)f''(X) + Xf'(X) = 0
void TE_asinh(const double& value,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::asinh(value);
        loc++;
    }

    if(loc != output.size())
    {
        double denom = value*value + 1;
        double one_over_denom = 1/denom;

        while(loc != output.size())
        {
            if(loc == 1)
                output[loc] = sqrt(one_over_denom);
            else if(loc==2)
                output[loc] = -value*output[loc-1]*one_over_denom;
            else if(loc==3)
                output[loc] = (-3*value*output[loc-1] - output[loc-2])*one_over_denom;
            else
            {
                int n = (int)loc-2;
                int n_pl_one = (int)loc-1;
                output[loc] = (-(2*n_pl_one-1)*value*output[loc-1]-n*n*output[loc-2])*one_over_denom;
            }

            loc++;
        }
    }
}

//we use (X^2-1)f''(X) + 2Xf'(X) = 0
void TE_atanh(const double& value,vector<double>& output,size_t loc = 0)
{
    if(output.size() && loc == 0)
    {
        output[0] = std::atanh(value);
        loc++;
    }

    if(loc != output.size())
    {
        double denom = value*value - 1;
        double one_over_denom = 1/denom;

        while(loc != output.size())
        {
            if(loc == 1)
                output[loc] = -one_over_denom;
            else if(loc==2)
                output[loc] = -2*value*output[loc-1]*one_over_denom;
            else if(loc==3)
                output[loc] = (-4*value*output[loc-1] - 2*output[loc-2])*one_over_denom;
            else
            {
                int n = (int)loc-2;
                int n_pl_one = (int)loc-1;
                output[loc] = (-2*value*n_pl_one*output[loc-1]-n*n_pl_one*output[loc-2])*one_over_denom;
            }

            loc++;
        }
    }
}

//we use f' = 1 + f^2
void TE_tan(const double& value,vector<double>& output,size_t loc = 0)
{
    if(loc == 0)
    {
        output[loc] = tan(value);
        loc++;
    }

    double fval = output[0];
    double fval_sq = fval*fval;

    vector<int> fval_coeffs(output.size()+1,0);
    fval_coeffs[1] = 1;

    //highest coefficient (1 at the moment) is an odd power
    bool odd = true;
    while(loc != output.size())
    {
        for(size_t i = odd; i<= (loc+1); i+=2)
        {
            int current_coeff = fval_coeffs[i];
            fval_coeffs[i] = 0;
            if(i)
            {
                int temp = current_coeff*(int)i;
                fval_coeffs[i-1] += temp;
                fval_coeffs[i+1] += temp;
            }
        }
        odd = !odd;

        double fval_der = 0;
        for(size_t i = (loc+1); i > (size_t)odd; i-=2)
        {
            fval_der += fval_coeffs[i];
            fval_der *= fval_sq;
        }

        fval_der += fval_coeffs[odd];

        if(odd)
            fval_der *= fval;

        output[loc] = fval_der;
        loc++;
    }
}

//we use f' = 1 - f^2
void TE_tanh(const double& value,vector<double>& output,size_t loc = 0)
{
    if(loc == 0)
    {
        output[loc] = tanh(value);
        loc++;
    }

    double fval = output[0];
    double fval_sq = fval*fval;

    vector<int> fval_coeffs(output.size()+1,0);
    fval_coeffs[1] = 1;

    //highest coefficient (1 at the moment) is an odd power
    bool odd = true;
    while(loc != output.size())
    {
        for(size_t i = odd; i<= (loc+1); i+=2)
        {
            int current_coeff = fval_coeffs[i];
            fval_coeffs[i] = 0;
            if(i)
            {
                int temp = current_coeff*(int)i;
                fval_coeffs[i-1] += temp;
                fval_coeffs[i+1] -= temp;
            }
        }
        odd = !odd;

        double fval_der = 0;
        for(size_t i = (loc+1); i > (size_t)odd; i-=2)
        {
            fval_der += fval_coeffs[i];
            fval_der *= fval_sq;
        }

        fval_der += fval_coeffs[odd];

        if(odd)
            fval_der *= fval;

        output[loc] = fval_der;
        loc++;
    }
}

void TE_lgamma(const double& value,vector<double>& output,size_t loc = 0)
{
    while(loc != output.size())
    {
        if(loc == 0)
            output[loc] = lgamma(value);
        else
            output[loc] = boost::math::polygamma(((int)loc-1),value);

        loc++;
    }
}

void TE_tgamma(const double& value,vector<double>& output,size_t loc = 0)
{
    if(loc == 0)
    {
        output[loc] = tgamma(value);
        loc++;
    }

    TE_lgamma(value,output,loc);

    for(size_t i = (output.size()-1); i >= loc; i--)
    {
        double der = 0;
        vector<size_t> part;
        partitionGenerator pg(i);
        while(pg.next(part))
        {
            double temp = pg.multiplicity(part);
            for(size_t j = 0; j < part.size(); j++)
                temp *= output[part[j]];

            der += temp;
        }

        output[i] = der*output[0];
    }
}

const double& bdouble::get(const vector<size_t>& idxes) const
{
    return mCoeff[multisetcount(idxes, mOrder)];
}

double& bdouble::get(const vector<size_t>& idxes)
{
    return mCoeff[multisetcount(idxes, mOrder)];
}

double bdouble::der(const vector<size_t>& id,const vector<size_t>& order)
{
    if(mCoeff.empty())
        run_tape();

    //they should be the same size
    if(order.size() != id.size())
        throw;

    vector<size_t> idxes(mId.size()+1,0);
    idxes[0] = mOrder;

    for(size_t i = 0; i < order.size(); i++)
        if (!addDer(idxes,id[i],order[i])) return 0;

    return get(idxes);
}

bool bdouble::addDer(vector<size_t>& idxes,const size_t& var_id,const size_t& order) const
{
    vector<size_t>::const_iterator it;
    it = find(mId.begin(), mId.end(), var_id);
    if (it != mId.end())
    {
        if(order > idxes[0])
            throw;

        idxes[distance(mId.begin(),it)+1] += order;
        idxes[0] -= order;

        return true;
    }
    else
        return false;
}

double bdouble::der_aux(size_t& var_id, vector<size_t>& idxes) const
{
    //this means that the previous argument had an implied order of 1
    if(var_id != -1)
        if (!addDer(idxes,var_id,1)) return 0;

    return get(idxes);
}

void bdouble::run_tape(const size_t orderOverride)
{
    if(orderOverride == -1)
        mOrder = mDefaultOrder;
    else
        mOrder = orderOverride;

    if(mOrder == 0)
        throw;

    //initially I overestimate the number of free slots
    //I will need for my calculation at the number
    //of variables used.
    deque<size_t> free_slots(indexcount);
    for(size_t i=0;i<indexcount;i++)
        free_slots[i] = i;

    vector<bool> op_relevant(op_trace.size(),false);
    vector<bool>::reverse_iterator op_relevant_rev_it = op_relevant.rbegin();
    vector<size_t>::const_reverse_iterator op_trace_rev_it = op_trace.rbegin();
    vector<size_t>::const_reverse_iterator index_trace_rev_it = index_trace.rbegin();

    vector<size_t> id_slot_map(indexcount,-1);
    vector<size_t> slot_id_map(indexcount,-1);

    size_t max_nvar = 1;
    size_t current_nvar = 0;

    vector<bool> var_concerned(indexcount,false);
    var_concerned[mThisId] = true;

    id_slot_map[mThisId] = free_slots.front();
    slot_id_map[free_slots.front()] = mThisId;
    free_slots.pop_front();
    current_nvar++;

    size_t res,arg1,arg2;

    for (; op_trace_rev_it!= op_trace.rend(); ++op_trace_rev_it,++op_relevant_rev_it)
    {
        res = *index_trace_rev_it++;
        if(var_concerned[res])
        {
            *op_relevant_rev_it = true;

            size_t res_pos = id_slot_map[res];
            free_slots.push_front(res_pos);
            slot_id_map[res_pos] = -1;
            id_slot_map[res] = -1;
            current_nvar--;

            arg1 = *index_trace_rev_it++;
            var_concerned[arg1] = true;
            if(id_slot_map[arg1] == -1)
            {
                id_slot_map[arg1] = free_slots.front();
                slot_id_map[free_slots.front()] = arg1;
                free_slots.pop_front();
                current_nvar++;
            }

            //if it's a function of 2 variables,
            //we have to take into account the second variable
            if(*op_trace_rev_it == bmultv ||
               *op_trace_rev_it == bplusv ||
               *op_trace_rev_it == bminusv)
            {
                arg1 = *index_trace_rev_it++;
                var_concerned[arg1] = true;
                if(id_slot_map[arg1] == -1)
                {
                    id_slot_map[arg1] = free_slots.front();
                    slot_id_map[free_slots.front()] = arg1;
                    free_slots.pop_front();
                    current_nvar++;
                }
            }

            max_nvar = max(current_nvar,max_nvar);
        }
        else
        {
            index_trace_rev_it++;

            //if it's a function of 2 variables,
            //we have to take into account the second variable
            if(*op_trace_rev_it == bmultv ||
               *op_trace_rev_it == bplusv ||
               *op_trace_rev_it == bminusv)
                index_trace_rev_it++;
        }
    }

    //we are now going to fill the free slots so that
    //at the end of the run all the initial ids will
    //fall ordered in the first mCoeff slots
    free_slots.clear();
    free_slots.resize(max_nvar);

    slot_id_map.erase (slot_id_map.begin()+max_nvar,slot_id_map.end());

    //here we fill the slots that correspond to initial variables
    //starting from 0
    size_t slot = 0;
    for(vector<size_t>::const_iterator id_slot_map_it = id_slot_map.begin(); id_slot_map_it != id_slot_map.end(); id_slot_map_it++)
    {
        if(*id_slot_map_it != -1)
        {
            free_slots[*id_slot_map_it] = slot;
            slot++;
        }
    }

    //here we fill the rest of the slots so that we have
    //max_nvar slots from 0 to (max_nvar-1)
    for(vector<size_t>::iterator slot_id_map_it = slot_id_map.begin(); slot_id_map_it != slot_id_map.end(); slot_id_map_it++)
    {
        if(*slot_id_map_it == -1)
        {
            free_slots[distance(slot_id_map.begin(),slot_id_map_it)] = slot;
            slot++;
        }
    }

    slot_id_map.clear();
    std::fill (id_slot_map.begin(),id_slot_map.end(),-1);

    mCoeff.resize(multisetcoeff(max_nvar+1,mOrder));
    mCoeff[0] = mValue;

    vector<size_t> idxes;
    idxes.clear();
    idxes.resize(free_slots.size()+1,0);
    idxes[0] = mOrder;

    mId.clear();
    mId.resize(free_slots.size(),-1);

    id_slot_map[mThisId] = free_slots.front();
    mId[free_slots.front()] = mThisId;
    free_slots.pop_front();
    idxes[0]--;
    idxes[id_slot_map[mThisId]+1]++;
    get(idxes) = 1.0;
    idxes[0]++;
    idxes[id_slot_map[mThisId]+1]--;

    op_relevant_rev_it = op_relevant.rbegin();
    op_trace_rev_it = op_trace.rbegin();
    index_trace_rev_it = index_trace.rbegin();
    vector<double>::const_reverse_iterator val_trace_rev_it = val_trace.rbegin();

    vector<size_t> idxes_sparse;

    for (; op_trace_rev_it!= op_trace.rend(); ++op_trace_rev_it,++op_relevant_rev_it)
        switch(*op_trace_rev_it)
    {
        case multconstv:
        case sumconstv:
        case minusconstv:
        case cosv:
        case sinv:
        case expv:
        case exp2v:
        case expm1v:
        case powv:
        case powintv:
        case invv:
        case logv:
        case log1pv:
        case log10v:
        case log2v:
        case coshv:
        case sinhv:
        case erfv:
        case erfcv:
        case nv:
        case tanv:
        case tanhv:
        case acosv:
        case asinv:
        case atanv:
        case acoshv:
        case asinhv:
        case atanhv:
        case lgammav:
        case tgammav:

            if(*op_relevant_rev_it)
            {
                res = *index_trace_rev_it++;
                size_t res_pos = id_slot_map[res];

                free_slots.push_front(res_pos);
                mId[res_pos] = -1;
                id_slot_map[res] = -1;

                arg1 = *index_trace_rev_it++;

                //second case works always, first case is faster
                if (mOrder == 1)
                {
                    if(id_slot_map[arg1] == -1)
                    {
                        id_slot_map[arg1] = free_slots.front();
                        mId[free_slots.front()] = arg1;
                        free_slots.pop_front();
                    }
                    size_t arg_pos = id_slot_map[arg1];

                    double temp = mCoeff[res_pos+1];
                    mCoeff[res_pos+1] = 0;

                    double result = *val_trace_rev_it++;

                    switch(*op_trace_rev_it)
                    {
                        case multconstv:
                        {
                            double coeff = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp*coeff;
                            break;
                        }
                        case sumconstv:
                        {
                            mCoeff[arg_pos+1] += temp;
                            break;
                        }
                        case minusconstv:
                        {
                            mCoeff[arg_pos+1] -= temp;
                            break;
                        }
                        case cosv:
                            mCoeff[arg_pos+1] -= temp*sin((*val_trace_rev_it++));
                            break;
                        case sinv:
                            mCoeff[arg_pos+1] += temp*cos((*val_trace_rev_it++));
                            break;
                        case expv:
                            mCoeff[arg_pos+1] += temp*result;
                            break;
                        case exp2v:
                            mCoeff[arg_pos+1] += temp*std::log(2.0)*result;
                            break;
                        case expm1v:
                            mCoeff[arg_pos+1] += temp*exp((*val_trace_rev_it++));
                            break;
                        case powv:
                        case powintv:
                        {
                            double deg = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp*deg*pow((*val_trace_rev_it++),deg-1);
                            break;
                        }
                        case invv:
                            mCoeff[arg_pos+1] -= temp*result*result;
                            break;
                        case logv:
                            mCoeff[arg_pos+1] += temp/(*val_trace_rev_it++);
                            break;
                        case log1pv:
                            mCoeff[arg_pos+1] += temp/((*val_trace_rev_it++)+1);
                            break;
                        case log10v:
                            mCoeff[arg_pos+1] += temp/(M_LN10*(*val_trace_rev_it++));
                            break;
                        case log2v:
                            mCoeff[arg_pos+1] += temp/(M_LN2*(*val_trace_rev_it++));
                            break;
                        case coshv:
                            mCoeff[arg_pos+1] += temp*sinh((*val_trace_rev_it++));
                            break;
                        case sinhv:
                            mCoeff[arg_pos+1] += temp*cosh((*val_trace_rev_it++));
                            break;
                        case erfv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp*M_2_SQRTPI*exp(-value*value);
                            break;
                        }
                        case erfcv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] -= temp*M_2_SQRTPI*exp(-value*value);
                            break;
                        }
                        case nv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp*0.5*M_2_SQRTPI*M_SQRT1_2*exp(-value*value*0.5);
                            break;
                        }
                        case tanv:
                            mCoeff[arg_pos+1] += temp*(1 + result*result);
                            break;
                        case tanhv:
                            mCoeff[arg_pos+1] += temp*(1 - result*result);
                            break;
                        case acosv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] -= temp/sqrt(1-value*value);
                            break;
                        }
                        case asinv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp/sqrt(1-value*value);
                            break;
                        }
                        case atanv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp/(1+value*value);
                            break;
                        }
                        case acoshv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp/sqrt(value*value-1);
                            break;
                        }
                        case asinhv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp/sqrt(1+value*value);
                            break;
                        }
                        case atanhv:
                        {
                            double value = *val_trace_rev_it++;
                            mCoeff[arg_pos+1] += temp/(1-value*value);
                            break;
                        }
                        case lgammav:
                            mCoeff[arg_pos+1] += temp*boost::math::polygamma(0,*val_trace_rev_it++);
                            break;
                        case tgammav:
                            mCoeff[arg_pos+1] += temp*result*boost::math::polygamma(0,*val_trace_rev_it++);
                            break;
                    }
                }
                else
                {
                    bool arg1_alive = (id_slot_map[arg1] != -1);

                    vector<size_t> sparse_to_dense;
                    sparse_to_dense.reserve(mId.size()-free_slots.size());
                    for(size_t i = 0; i < mId.size(); i++)
                        if((mId[i] != -1) && (!arg1_alive || i != id_slot_map[arg1]))
                            sparse_to_dense.push_back(i);

                    if(id_slot_map[arg1] == -1)
                    {
                        id_slot_map[arg1] = free_slots.front();
                        mId[free_slots.front()] = arg1;
                        free_slots.pop_front();
                    }
                    size_t arg_pos = id_slot_map[arg1];

                    vector<double> taylorexp(mOrder+1);
                    taylorexp[0] = *val_trace_rev_it++;

                    switch(*op_trace_rev_it)
                    {
                        case multconstv:
                        {
                            double coeff = *val_trace_rev_it++;
                            TE_multconst(0,coeff,taylorexp,1);
                            break;
                        }
                        case sumconstv:
                        {
                            TE_sumconst(0,0,taylorexp,1);
                            break;
                        }
                        case minusconstv:
                        {
                            TE_minusconst(0,0,taylorexp,1);
                            break;
                        }
                        case cosv:
                            TE_cos(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case sinv:
                            TE_sin(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case expv:
                            TE_exp(0,taylorexp,1);
                            break;
                        case exp2v:
                            TE_exp2(0,taylorexp,1);
                            break;
                        case expm1v:
                            TE_expm1(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case powv:
                        {
                            double deg = *val_trace_rev_it++;
                            TE_pow(*val_trace_rev_it++,deg,taylorexp,1);
                            break;
                        }
                        case powintv:
                        {
                            double deg = *val_trace_rev_it++;
                            TE_powint(*val_trace_rev_it++,deg,taylorexp,1);
                            break;
                        }
                        case invv:
                            TE_inv(0,taylorexp,1);
                            break;
                        case logv:
                            TE_log(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case log1pv:
                            TE_log1p(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case log10v:
                            TE_log10(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case log2v:
                            TE_log2(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case coshv:
                            TE_cosh(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case sinhv:
                            TE_sinh(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case erfv:
                            TE_erf(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case erfcv:
                            TE_erfc(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case nv:
                            TE_N(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case tanv:
                            TE_tan(0,taylorexp,1);
                            break;
                        case tanhv:
                            TE_tanh(0,taylorexp,1);
                            break;
                        case acosv:
                            TE_acos(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case asinv:
                            TE_asin(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case atanv:
                            TE_atan(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case acoshv:
                            TE_acosh(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case asinhv:
                            TE_asinh(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case atanhv:
                            TE_atanh(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case lgammav:
                            TE_lgamma(*val_trace_rev_it++,taylorexp,1);
                            break;
                        case tgammav:
                            TE_tgamma(*val_trace_rev_it++,taylorexp,1);
                            break;
                    }

                    vector<vector<double> > der_mult_cache(mOrder);
                    for(size_t i = 1; i <= der_mult_cache.size(); i++)
                    {
                        der_mult_cache[i-1].resize(i,0);

                        vector<size_t> part;
                        partitionGenerator pg(i);
                        while(pg.next(part))
                        {
                            double temp = pg.multiplicity(part);
                            for(size_t j = 0; j < part.size(); j++)
                                temp *= taylorexp[part[j]];

                            der_mult_cache[i-1][part.size()-1] += temp;
                        }
                    }

                    idxes_sparse.clear();
                    idxes_sparse.resize(sparse_to_dense.size()+1,0);

                    idxes.clear();
                    idxes.resize(mId.size()+1,0);

                    //we need to keep at least one derivative for res
                    //hence the -1
                    multisetGenerator mg(idxes_sparse.size(),mOrder - 1);

                    while(mg.next(idxes_sparse))
                    {
                        for(size_t i = 0; i < sparse_to_dense.size(); i++)
                            idxes[sparse_to_dense[i]+1] = idxes_sparse[i+1];

                        size_t order = 1+idxes_sparse[0];

                        if(!arg1_alive)
                        {
                            //we cache the derivs values first as we
                            //want to avoid calling them multiple times
                            vector<double> derivstemp(order);
                            for(size_t i = 0; i<order; i++)
                            {
                                idxes[0] = order - (i+1);
                                idxes[res_pos+1] = (i+1);
                                derivstemp[i] = get(idxes);
                            }

                            //reset it again
                            order = 1+idxes_sparse[0];
                            idxes[0] = 0;

                            //we need to go from higher to lower order here
                            //as the high order derivatives can be overriden
                            //before the lower order derivatives
                            while(order > 0)
                            {
                                double temp = 0;
                                for (size_t i = 0; i<order; i++)
                                    temp += derivstemp[i] * der_mult_cache[order-1][i];

                                //because we know that arg1 wasn't included
                                //res_pos is the same as arg_pos
                                idxes[res_pos+1] = order;
                                get(idxes)= temp;
                                idxes[res_pos+1] = 0;

                                idxes[0]++;
                                order--;
                            }
                        }
                        else
                        {
                            //we cache the derivs values first as we
                            //want to avoid calling them multiple times.
                            //in this case we cache all partial derivs
                            //in res and arg
                            vector<vector<double> > derivstemp(order);
                            for(size_t i = 1; i <= derivstemp.size(); i++)
                            {
                                derivstemp[i-1].resize(i,0);
                                idxes[arg_pos+1] = order - i;

                                for (size_t j = 1; j<=i; j++)
                                {
                                    idxes[res_pos+1] = j;
                                    idxes[0] = i - j;
                                    derivstemp[i-1][j-1] = get(idxes);
                                }
                            }

                            //reset it again
                            order = 1+idxes_sparse[0];
                            idxes[0] = 0;
                            size_t offset = 0;

                            //we need to go from higher to lower order here
                            //as the high order derivatives can be overriden
                            //before the lower order derivatives
                            while(order > 0)
                            {
                                double temp = 0;

                                for (size_t i = 0; i<order; i++)
                                {
                                    double temp2 = 0;
                                    for (size_t j = 0; j<der_mult_cache[i].size(); j++)
                                        temp2 += derivstemp[i+offset][j] * der_mult_cache[i][j];

                                    size_t multiplicity = combins(i+1,order,true);
                                    temp += temp2 * multiplicity;
                                }

                                //we reset partial derivs that are now 0
                                for (size_t i = 0; i<order; i++)
                                {
                                    idxes[res_pos+1] = order - i;
                                    idxes[arg_pos+1] = i;
                                    get(idxes) = 0;
                                }

                                //then we add up the new derivative
                                idxes[res_pos+1] = 0;
                                idxes[arg_pos+1] = order;
                                get(idxes) += temp;
                                idxes[arg_pos+1] = 0;

                                idxes[0]++;
                                order--;
                                offset++;
                            }
                        }
                    }
                }
            }
            else
            {
                index_trace_rev_it += 2;
                switch(*op_trace_rev_it)
                {
                    case sumconstv:
                    case minusconstv:
                    case expv:
                    case exp2v:
                    case invv:
                    case tanv:
                    case tanhv:
                        val_trace_rev_it += 1;
                        break;
                    case powv:
                    case powintv:
                        val_trace_rev_it += 3;
                        break;
                    default:
                        val_trace_rev_it += 2;
                }
            }

            break;
        case bplusv:
        case bminusv:

            if(*op_relevant_rev_it)
            {
                res = *index_trace_rev_it++;
                size_t res_pos = id_slot_map[res];

                free_slots.push_front(res_pos);
                mId[res_pos] = -1;
                id_slot_map[res] = -1;

                arg1 = *index_trace_rev_it++;
                arg2 = *index_trace_rev_it++;

                //second case works always, first case is faster
                if (mOrder == 1)
                {
                    if(id_slot_map[arg1] == -1)
                    {
                        id_slot_map[arg1] = free_slots.front();
                        mId[free_slots.front()] = arg1;
                        free_slots.pop_front();
                    }

                    if(id_slot_map[arg2] == -1)
                    {
                        id_slot_map[arg2] = free_slots.front();
                        mId[free_slots.front()] = arg2;
                        free_slots.pop_front();
                    }

                    double temp = mCoeff[res_pos+1];
                    mCoeff[res_pos+1] = 0;
                    mCoeff[id_slot_map[arg1]+1] += temp;
                    if(*op_trace_rev_it == bplusv)
                        mCoeff[id_slot_map[arg2]+1] += temp;
                    else if(*op_trace_rev_it == bminusv)
                        mCoeff[id_slot_map[arg2]+1] -= temp;
                }
                else
                {
                    bool arg1_alive = (id_slot_map[arg1] != -1);
                    bool arg2_alive = (id_slot_map[arg2] != -1);

                    vector<size_t> sparse_to_dense;
                    sparse_to_dense.reserve(mId.size()-free_slots.size());
                    for(size_t i = 0; i < mId.size(); i++)
                        if(mId[i] != -1 && i!=id_slot_map[arg1] && i!=id_slot_map[arg2])
                            sparse_to_dense.push_back(i);

                    if(id_slot_map[arg1] == -1)
                    {
                        id_slot_map[arg1] = free_slots.front();
                        mId[free_slots.front()] = arg1;
                        free_slots.pop_front();
                    }

                    if(id_slot_map[arg2] == -1)
                    {
                        id_slot_map[arg2] = free_slots.front();
                        mId[free_slots.front()] = arg2;
                        free_slots.pop_front();
                    }

                    size_t arg1_pos = id_slot_map[arg1];
                    size_t arg2_pos = id_slot_map[arg2];

                    idxes_sparse.clear();
                    idxes_sparse.resize(sparse_to_dense.size()+1,0);

                    idxes.clear();
                    idxes.resize(mId.size()+1,0);

                    //we need to keep at least one derivative for res
                    //hence the -1
                    multisetGenerator mg(idxes_sparse.size(),mOrder - 1);

                    while(mg.next(idxes_sparse))
                    {
                        for(size_t i = 0; i < sparse_to_dense.size(); i++)
                            idxes[sparse_to_dense[i]+1] = idxes_sparse[i+1];

                        size_t order = 1+idxes_sparse[0];
                        idxes[0] = 0;

                        //we need to go from higher to lower order here
                        //as the high order derivatives can be overriden
                        //before the lower order derivatives
                        while(order > 0)
                        {
                            idxes[res_pos+1] = order;
                            vector<vector<double> > derivsarg1and2;
                            derivsarg1and2.resize(order);
                            for(size_t i = 0; i < order; i++)
                            {
                                bool icondition = (i==0) || arg1_alive;
                                if(icondition)
                                {
                                    idxes[arg1_pos+1] = i;
                                    derivsarg1and2[i].resize(order-i,0);
                                    for(size_t j = 0; j < (order-i); j++)
                                    {
                                        bool jcondition = (j==0) || arg2_alive;
                                        if(jcondition)
                                        {
                                            idxes[arg2_pos+1] = j;
                                            idxes[res_pos+1] = order - i - j;
                                            std::swap(derivsarg1and2[i][j],get(idxes));
                                        }
                                    }
                                }
                            }

                            vector<size_t> idxarg1arg2;
                            multisetGenerator mg2(2,order);

                            idxes[res_pos+1]= 0;
                            while(mg2.next(idxarg1arg2))
                            {
                                double temp = 0;

                                for(size_t i = 0; i <= idxarg1arg2[0]; i++)
                                {
                                    bool icondition = (i==0) || arg1_alive;
                                    if(icondition)
                                    {
                                        double temp2 = combins(i,idxarg1arg2[0],true);
                                        for(size_t j = 0; j <= idxarg1arg2[1]; j++)
                                        {
                                            bool notlast = (i != idxarg1arg2[0]) || (j != idxarg1arg2[1]);
                                            bool jcondition = (j==0) || arg2_alive;
                                            if(notlast && jcondition)
                                            {
                                                if(*op_trace_rev_it == bplusv)
                                                    temp += temp2 * combins(j,idxarg1arg2[1],true) * derivsarg1and2[i][j];
                                                else if(*op_trace_rev_it == bminusv)
                                                {
                                                    if ( idxarg1arg2[1] % 2== 0 )
                                                        temp += temp2 * combins(j,idxarg1arg2[1],true) * derivsarg1and2[i][j];
                                                    else
                                                        temp -= temp2 * combins(j,idxarg1arg2[1],true) * derivsarg1and2[i][j];
                                                }
                                            }
                                        }
                                    }
                                }

                                idxes[arg1_pos+1] = 0;
                                idxes[arg2_pos+1] = 0;
                                idxes[arg1_pos+1] += idxarg1arg2[0];
                                idxes[arg2_pos+1] += idxarg1arg2[1];
                                get(idxes) += temp;
                            }

                            idxes[arg1_pos+1] = 0;
                            idxes[arg2_pos+1] = 0;
                            idxes[0]++;
                            order--;
                        }
                    }
                }
            }
            else
                index_trace_rev_it += 3;

            break;

        case bmultv:

            if(*op_relevant_rev_it)
            {
                res = *index_trace_rev_it++;
                size_t res_pos = id_slot_map[res];

                //slot_id_map.push_front(res_pos);
                free_slots.push_front(res_pos);
                mId[res_pos] = -1;
                id_slot_map[res] = -1;

                arg1 = *index_trace_rev_it++;
                arg2 = *index_trace_rev_it++;

                //second case works always, first case is faster
                if (mOrder == 1)
                {
                    if(id_slot_map[arg1] == -1)
                    {
                        id_slot_map[arg1] = free_slots.front();
                        mId[free_slots.front()] = arg1;
                        free_slots.pop_front();
                    }

                    if(id_slot_map[arg2] == -1)
                    {
                        id_slot_map[arg2] = free_slots.front();
                        mId[free_slots.front()] = arg2;
                        free_slots.pop_front();
                    }

                    double temp = mCoeff[res_pos+1];
                    mCoeff[res_pos+1] = 0;
                    mCoeff[id_slot_map[arg2]+1] += temp * (*val_trace_rev_it++);
                    mCoeff[id_slot_map[arg1]+1] += temp * (*val_trace_rev_it++);
                }
                else
                {
                    bool arg1_alive = (id_slot_map[arg1] != -1);
                    bool arg2_alive = (id_slot_map[arg2] != -1);

                    vector<size_t> sparse_to_dense;
                    sparse_to_dense.reserve(mId.size()-free_slots.size());
                    for(size_t i = 0; i < mId.size(); i++)
                        if(mId[i] != -1 && i!=id_slot_map[arg1] && i!=id_slot_map[arg2])
                            sparse_to_dense.push_back(i);

                    if(id_slot_map[arg1] == -1)
                    {
                        id_slot_map[arg1] = free_slots.front();
                        mId[free_slots.front()] = arg1;
                        free_slots.pop_front();
                    }

                    if(id_slot_map[arg2] == -1)
                    {
                        id_slot_map[arg2] = free_slots.front();
                        mId[free_slots.front()] = arg2;
                        free_slots.pop_front();
                    }

                    size_t arg1_pos = id_slot_map[arg1];
                    size_t arg2_pos = id_slot_map[arg2];

                    double x1 = (*val_trace_rev_it++);
                    double x2 = (*val_trace_rev_it++);
                    vector<vector<double> > d_g_x1_x2;

                    //create the triangle
                    d_g_x1_x2.resize(mOrder+1);
                    for(size_t i = 0; i <= mOrder; i++)
                        d_g_x1_x2[i].resize(mOrder+1-i);

                    //fill the borders
                    d_g_x1_x2[0][0] = 1;
                    for(size_t i = 1; i <= mOrder; i++)
                    {
                        d_g_x1_x2[0][i] = d_g_x1_x2[0][i-1] * x1;
                        d_g_x1_x2[i][0] = d_g_x1_x2[i-1][0] * x2;
                    }

                    //fill the inside
                    for(size_t i = 1; i <= mOrder; i++)
                        for(size_t j = 1; j <= mOrder-i; j++)
                            d_g_x1_x2[i][j] = d_g_x1_x2[0][j] * d_g_x1_x2[i][0];

                    idxes_sparse.clear();
                    idxes_sparse.resize(sparse_to_dense.size()+1,0);

                    idxes.clear();
                    idxes.resize(mId.size()+1,0);

                    //we need to keep at least one derivative for res
                    //hence the -1
                    multisetGenerator mg(idxes_sparse.size(),mOrder - 1);

                    while(mg.next(idxes_sparse))
                    {
                        for(size_t i = 0; i < sparse_to_dense.size(); i++)
                            idxes[sparse_to_dense[i]+1] = idxes_sparse[i+1];

                        size_t order = 1+idxes_sparse[0];
                        idxes[0] = 0;

                        vector<vector<vector<double> > > d_f_x1_x2_y;
                        d_f_x1_x2_y.resize(order);
                        for(size_t i = 0; i < order; i++)
                        {
                            bool icondition = (i==0) || arg1_alive;
                            if(icondition)
                            {
                                idxes[arg1_pos+1] = i;
                                d_f_x1_x2_y[i].resize(order-i);
                                for(size_t j = 0; j < (order-i); j++)
                                {
                                    bool jcondition = (j==0) || arg2_alive;
                                    if(jcondition)
                                    {
                                        idxes[arg2_pos+1] = j;
                                        d_f_x1_x2_y[i][j].resize(order-i-j);
                                        for(size_t k = 0; k < (order-i-j); k++)
                                        {
                                            idxes[res_pos+1] = k+1;
                                            idxes[0] = order - i - j - (k+1);

                                            std::swap(d_f_x1_x2_y[i][j][k],get(idxes));
                                        }
                                    }
                                }
                            }
                        }

                        //we need to go from higher to lower order here
                        //as the high order derivatives can be overriden
                        //before the lower order derivatives
                        idxes[res_pos+1]= 0;
                        while(order > 0)
                        {
                            vector<size_t> idxarg1arg2;
                            multisetGenerator mg2(2,order);

                            while(mg2.next(idxarg1arg2))
                            {
                                double temp = 0;

                                size_t cross_ders = min(idxarg1arg2[0],idxarg1arg2[1]);

                                for(size_t k = 0; k <= cross_ders; k++)
                                {
                                    double temp3 = combins(k,idxarg1arg2[0],true) * combins(k,idxarg1arg2[1],true);
                                    for(size_t i = 0; i <= (idxarg1arg2[0]-k); i++)
                                    {
                                        bool icondition = (i==0) || arg1_alive;
                                        if(icondition)
                                        {
                                            double temp2 = combins(i,idxarg1arg2[0]-k,true) * temp3;
                                            for(size_t j = 0; j <= (idxarg1arg2[1]-k); j++)
                                            {
                                                bool notlast = (k!=0) || (i != idxarg1arg2[0]) || (j != idxarg1arg2[1]);
                                                bool jcondition = (j==0) || arg2_alive;
                                                if(notlast && jcondition)
                                                    temp += temp2 * combins(j,idxarg1arg2[1]-k,true) * d_f_x1_x2_y[i][j][order-i-j-1-k] * d_g_x1_x2[idxarg1arg2[0]-i-k][idxarg1arg2[1]-j-k];
                                            }
                                        }
                                    }
                                }

                                idxes[arg1_pos+1] = 0;
                                idxes[arg2_pos+1] = 0;
                                idxes[arg1_pos+1] += idxarg1arg2[0];
                                idxes[arg2_pos+1] += idxarg1arg2[1];
                                get(idxes) += temp;
                            }

                            idxes[arg1_pos+1] = 0;
                            idxes[arg2_pos+1] = 0;
                            idxes[0]++;
                            order--;
                        }
                    }
                }
            }
            else
            {
                index_trace_rev_it += 3;
                val_trace_rev_it += 2;
            }

            break;
    }

    vector<size_t>::const_iterator cutoff = mId.begin();

    while(cutoff != mId.end() && *cutoff != -1)
        cutoff++;

    //we need to clean up data from mId and mCoeff
    if(cutoff != mId.end())
    {
        if(mOrder != 1)
        {
            idxes.clear();
            idxes.resize(mId.size()+1,0);
            multisetGenerator mg(idxes.size(),mOrder);

            vector<double>::iterator coarse = mCoeff.begin();
            vector<double>::iterator fine   = mCoeff.begin();

            vector<size_t>::const_iterator id_it;
            vector<size_t>::const_iterator id_it_begin = idxes.begin() + 1 + (cutoff - mId.begin());
            while(mg.next(idxes))
            {
                id_it = id_it_begin;

                while(id_it != idxes.end() && *id_it == 0)
                    id_it++;

                if(id_it == idxes.end())
                {
                    if(coarse != fine)
                        swap(*coarse,*fine);

                    coarse++;
                }

                fine++;
            }
        }

        mId.erase(cutoff,mId.end());
        mCoeff.resize(multisetcoeff(mId.size()+1,mOrder));
    }
}

void bdouble::clear_tape()
{
    op_trace.clear();
    index_trace.clear();
    val_trace.clear();
    indexcount = 0;
}


//=======================================================================
// Copyright Baptiste Wicht 2011-2016.
// Distributed under the MIT License.
// (See accompanying file LICENSE or copy at
//  http://opensource.org/licenses/MIT)
//=======================================================================

#ifndef LTAC_BIT_MATRIX_H
#define LTAC_BIT_MATRIX_H

#include <boost/dynamic_bitset.hpp>

namespace eddic {

namespace ltac {

class bit_matrix;

class sub_bit_matrix {
    public:
        sub_bit_matrix(bit_matrix& matrix, std::size_t i);
        bool operator[](std::size_t j);

    private:
        bit_matrix& matrix;
        std::size_t i;
};

class bit_matrix {
    public:
        bit_matrix(std::size_t size);

        void set(std::size_t i, std::size_t j);
        void clear(std::size_t i, std::size_t j);
        bool is_set(std::size_t i, std::size_t j);

        sub_bit_matrix operator[](std::size_t i);

    private:
        std::size_t size;
        boost::dynamic_bitset<> bitset;
};

} //end of ltac

} //end of eddic

#endif