/**
 * $Id$
 *
 * Copyright (C)
 * 2015 - $Date$
 *     Martin Wolf <ndhist@martin-wolf.org>
 *
 * This file is distributed under the BSD 2-Clause Open Source License
 * (See LICENSE file).
 *
 */
#include <sstream>
#include <vector>

#include <boost/preprocessor/seq/elem.hpp>
#include <boost/preprocessor/seq/for_each_product.hpp>
#include <boost/python.hpp>

#include <boost/numpy/ndarray.hpp>
#include <boost/numpy/iterators/flat_iterator.hpp>
#include <boost/numpy/iterators/multi_flat_iterator.hpp>
#include <boost/numpy/python/make_tuple_from_container.hpp>

#include <ndhist/detail/bin_iter_value_type_traits.hpp>
#include <ndhist/type_support.hpp>
#include <ndhist/detail/utils.hpp>
#include <ndhist/stats/median.hpp>

namespace ndhist {
namespace stats {

namespace detail {

template <typename AxisValueType, typename WeightValueType>
AxisValueType
calc_axis_median_impl(
    ndhist const & h
  , intptr_t const axis
)
{
    // Project the given histogram to the given axis (if nd > 1).
    ndhist const proj = (h.get_nd() == 1 ? h : h.project(bp::object(axis)));

    // Iterate over the bins (which are along the given axis) and exclude
    // possible under- and overflow bins.
    Axis const & theaxis = *proj.get_axes()[0];
    intptr_t nbins = theaxis.get_n_bins();
    if(theaxis.has_underflow_bin()) --nbins;
    if(theaxis.has_overflow_bin()) --nbins;
    bn::ndarray proj_bc_arr = proj.bc_.construct_ndarray(proj.bc_.get_dtype(), 0, /*owner=*/NULL, /*set_owndata_flag=*/false);

    // First calculate the median sum of weights sum.
    typedef bn::iterators::flat_iterator<
                ::ndhist::detail::bin_iter_value_type_traits<WeightValueType>
            >
            bin_iter_t;
    bin_iter_t bin_iter(
        proj_bc_arr
      , bn::detail::iter_operand::flags::READONLY::value
    );
    // Skip the underflow bin.
    if(theaxis.has_underflow_bin()) ++bin_iter;
    bool all_bins_are_zero = true;
    double median_sow_sum = 0;
    for(intptr_t i=0; i<nbins; ++i)
    {
        typename bin_iter_t::value_ref_type bin = *bin_iter;
        median_sow_sum += *bin.sow_;
        if(median_sow_sum != 0)
        {
            all_bins_are_zero = false;
        }
        ++bin_iter;
    }
    median_sow_sum *= 0.5;
    if(all_bins_are_zero)
    {
        std::stringstream ss;
        ss << "All bin content values are zero. The median value is ambiguous "
           << "in that case!";
        throw ValueError(ss.str());
    }

    // At least one bin content value is unequal than zero, search for the
    // median axis value.
    typedef bn::iterators::multi_flat_iterator<3>::impl<
                bn::iterators::single_value<AxisValueType>
              , bn::iterators::single_value<AxisValueType>
              , ::ndhist::detail::bin_iter_value_type_traits<WeightValueType>
            >
            multi_iter_t;
    bn::ndarray axis_bincenters_arr = theaxis.get_bincenters_ndarray();
    bn::ndarray axis_upper_binedges_arr = theaxis.get_upper_binedges_ndarray();
    multi_iter_t iter(
        axis_bincenters_arr
      , axis_upper_binedges_arr
      , proj_bc_arr
      , bn::detail::iter_operand::flags::READONLY::value
      , bn::detail::iter_operand::flags::READONLY::value
      , bn::detail::iter_operand::flags::READONLY::value
    );
    // Skip the underflow bin.
    if(theaxis.has_underflow_bin()) ++iter;
    WeightValueType curr_sow_sum = 0;
    for(intptr_t i=0; i<nbins; ++i)
    {
        typename multi_iter_t::multi_references_type multi_value = *iter;
        typename multi_iter_t::value_ref_type_0 axis_bincenter_value     = multi_value.value_0;
        typename multi_iter_t::value_ref_type_1 axis_upper_binedge_value = multi_value.value_1;
        typename multi_iter_t::value_ref_type_2 bin                      = multi_value.value_2;

        curr_sow_sum += *bin.sow_;
        if(curr_sow_sum == median_sow_sum)
        {
            return axis_upper_binedge_value;
        }
        else if(curr_sow_sum > median_sow_sum)
        {
            return axis_bincenter_value;
        }

        ++iter;
    }

    std::stringstream ss;
    ss << "The median value for axis '"<<axis<<"' could not be determined. "
       << "This is a BUG!";
    throw RuntimeError(ss.str());
}

}// namespace detail

namespace py {

namespace detail {

bp::object
calc_axis_median(
    ndhist const & h
  , intptr_t const axis
)
{
    // Determine the correct axis index.
    intptr_t const axis_idx = ::ndhist::detail::adjust_axis_index(h.get_nd(), axis);

    // Check that the axis and weight types are not bp::object.
    if(   h.get_axes()[axis_idx]->has_object_value_dtype()
       || h.has_object_weight_dtype()
    )
    {
        std::stringstream ss;
        ss << "The axis and weight data types must be POD types. Non-POD types "
           << "are not supported by the median function!";
        throw TypeError(ss.str());
    }

    #define NDHIST_MULTPLEX(r, seq)                                             \
        if(   bn::dtype::equivalent(h.get_axes()[axis_idx]->get_dtype(), bn::dtype::get_builtin<BOOST_PP_SEQ_ELEM(0,seq)>())\
           && bn::dtype::equivalent(h.get_weight_dtype(), bn::dtype::get_builtin<BOOST_PP_SEQ_ELEM(1,seq)>())\
          )                                                                     \
        {                                                                       \
            BOOST_PP_SEQ_ELEM(0,seq) median = ::ndhist::stats::detail::calc_axis_median_impl<BOOST_PP_SEQ_ELEM(0,seq), BOOST_PP_SEQ_ELEM(1,seq)>(h, axis_idx);\
            return bp::object(median);                                          \
        }
    BOOST_PP_SEQ_FOR_EACH_PRODUCT(NDHIST_MULTPLEX, (NDHIST_TYPE_SUPPORT_AXIS_VALUE_TYPES_WITHOUT_OBJECT)(NDHIST_TYPE_SUPPORT_WEIGHT_VALUE_TYPES_WITHOUT_OBJECT))
    #undef NDHIST_MULTPLEX

    std::stringstream ss;
    ss << "The combination of axis value type and weight value type of this "
       << "ndhist object is not supported for the median function!";
    throw TypeError(ss.str());
}

}// namespace detail

bp::object
median(
    ndhist const & h
  , bp::object const & axis
)
{
    if(axis != bp::object())
    {
        // A particular axis is given. So calculate the median only for that
        // axis and return a scalar.
        intptr_t const axis_idx = bp::extract<intptr_t>(axis);
        return detail::calc_axis_median(h, axis_idx);
    }

    // No axis was specified, so calculate the median for all axes.
    intptr_t const nd = h.get_nd();

    // Return a scalar value if the dimensionality of the histogram is 1.
    if(nd == 1)
    {
        return detail::calc_axis_median(h, 0);
    }

    // Return a tuple holding the moment values for each single axis.
    std::vector<bp::object> medians;
    medians.reserve(nd);
    for(intptr_t i=0; i<nd; ++i)
    {
        medians.push_back(detail::calc_axis_median(h, i));
    }
    return boost::python::make_tuple_from_container(medians.begin(), medians.end());
}

}// namespace py
}// namespace stats
}// namespace ndhist


//
//   Copyright (C) 2003-2016 Greg Landrum and Rational Discovery LLC
//
//   @@ All Rights Reserved @@
//  This file is part of the RDKit.
//  The contents are covered by the terms of the BSD license
//  which is included in the file license.txt, found at the root
//  of the RDKit source tree.
//
#include "MolTransforms.h"
#include <GraphMol/RDKitBase.h>
#include <GraphMol/QueryOps.h>
#include <Numerics/EigenSolvers/PowerEigenSolver.h>
#include <Numerics/SymmMatrix.h>
#include <Numerics/Matrix.h>
#include <Geometry/Transform3D.h>
#include <stack>
#include <boost/dynamic_bitset.hpp>
#include <RDGeneral/Exceptions.h>

#define EIGEN_TOLERANCE 5.0e-2
namespace MolTransforms {

using namespace RDKit;
void transformAtom(Atom *atom, RDGeom::Transform3D &tform) {
  PRECONDITION(atom, "no atom");
  ROMol &mol = atom->getOwningMol();
  for (ROMol::ConstConformerIterator ci = mol.beginConformers();
       ci != mol.endConformers(); ci++) {
    RDGeom::Point3D &pos = (*ci)->getAtomPos(atom->getIdx());
    tform.TransformPoint(pos);
  }
  // atom->setPos(pos);
}
void transformMolsAtoms(ROMol *mol, RDGeom::Transform3D &tform) {
  PRECONDITION(mol, "no molecule");

  ROMol::AtomIterator atomIt;
  for (atomIt = mol->beginAtoms(); atomIt != mol->endAtoms(); atomIt++) {
    transformAtom(*atomIt, tform);
  }
}

RDGeom::Point3D computeCentroid(const Conformer &conf, bool ignoreHs) {
  RDGeom::Point3D res(0.0, 0.0, 0.0);
  const ROMol &mol = conf.getOwningMol();
  ROMol::ConstAtomIterator cai;
  unsigned int nAtms = 0;

  for (cai = mol.beginAtoms(); cai != mol.endAtoms(); cai++) {
    if (((*cai)->getAtomicNum() == 1) && (ignoreHs)) {
      continue;
    }
    res += conf.getAtomPos((*cai)->getIdx());
    nAtms++;
  }
  res /= nAtms;
  return res;
}
namespace {
void computeCovarianceTerms(const Conformer &conf,
                            const RDGeom::Point3D &center, double &xx,
                            double &xy, double &xz, double &yy, double &yz,
                            double &zz, bool normalize, bool ignoreHs,
                            const std::vector<double> *weights) {
  PRECONDITION(!weights || weights->size() >= conf.getNumAtoms(),
               "bad weights vector");

  xx = xy = xz = yy = yz = zz = 0.0;
  const ROMol &mol = conf.getOwningMol();
  double wSum = 0.0;
  for (ROMol::ConstAtomIterator cai = mol.beginAtoms(); cai != mol.endAtoms();
       cai++) {
    if (((*cai)->getAtomicNum() == 1) && (ignoreHs)) {
      continue;
    }
    RDGeom::Point3D loc = conf.getAtomPos((*cai)->getIdx());
    loc -= center;
    double w = 1.0;
    if (weights) {
      w = (*weights)[(*cai)->getIdx()];
    }
    wSum += w;
    xx += w * loc.x * loc.x;
    xy += w * loc.x * loc.y;
    xz += w * loc.x * loc.z;
    yy += w * loc.y * loc.y;
    yz += w * loc.y * loc.z;
    zz += w * loc.z * loc.z;
  }
  if (normalize) {
    xx /= wSum;
    xy /= wSum;
    xz /= wSum;
    yy /= wSum;
    yz /= wSum;
    zz /= wSum;
  }
}

RDNumeric::DoubleSymmMatrix *computeCovarianceMatrix(
    const Conformer &conf, const RDGeom::Point3D &center, bool normalize,
    bool ignoreHs) {
  double xx, xy, xz, yy, yz, zz;
  computeCovarianceTerms(conf, center, xx, xy, xz, yy, yz, zz, normalize,
                         ignoreHs, nullptr);
  auto *res = new RDNumeric::DoubleSymmMatrix(3, 3);
  res->setVal(0, 0, xx);
  res->setVal(0, 1, xy);
  res->setVal(0, 2, xz);
  res->setVal(1, 1, yy);
  res->setVal(1, 2, yz);
  res->setVal(2, 2, zz);
  return res;
}

void computeInertiaTerms(const Conformer &conf, const RDGeom::Point3D &center,
                         double &xx, double &xy, double &xz, double &yy,
                         double &yz, double &zz, bool ignoreHs,
                         const std::vector<double> *weights) {
  PRECONDITION(!weights || weights->size() >= conf.getNumAtoms(),
               "bad weights vector");

  xx = xy = xz = yy = yz = zz = 0.0;
  const ROMol &mol = conf.getOwningMol();
  for (ROMol::ConstAtomIterator cai = mol.beginAtoms(); cai != mol.endAtoms();
       cai++) {
    if (((*cai)->getAtomicNum() == 1) && (ignoreHs)) {
      continue;
    }
    RDGeom::Point3D loc = conf.getAtomPos((*cai)->getIdx());
    loc -= center;
    double w = 1.0;
    if (weights) {
      w = (*weights)[(*cai)->getIdx()];
    }
    xx += w * (loc.y * loc.y + loc.z * loc.z);
    yy += w * (loc.x * loc.x + loc.z * loc.z);
    zz += w * (loc.y * loc.y + loc.x * loc.x);
    xy -= w * loc.x * loc.y;
    xz -= w * loc.x * loc.z;
    yz -= w * loc.z * loc.y;
  }
}
}
#ifdef RDK_HAS_EIGEN3
#include <Eigen/Dense>

bool computePrincipalAxesAndMoments(const RDKit::Conformer &conf,
                                    Eigen::Matrix3d &axes,
                                    Eigen::Vector3d &moments, bool ignoreHs,
                                    bool force,
                                    const std::vector<double> *weights) {
  PRECONDITION((!weights || weights->size() >= conf.getNumAtoms()),
               "bad weights vector");
  const char *axesPropName = ignoreHs ? "_principalAxes_noH" : "_principalAxes";
  const char *momentsPropName =
      ignoreHs ? "_principalMoments_noH" : "_principalMoments";
  if (!weights && !force && conf.getOwningMol().hasProp(axesPropName) &&
      conf.getOwningMol().hasProp(momentsPropName)) {
    conf.getOwningMol().getProp(axesPropName, axes);
    conf.getOwningMol().getProp(momentsPropName, moments);
    return true;
  }
  const ROMol &mol = conf.getOwningMol();
  RDGeom::Point3D origin(0, 0, 0);
  double wSum = 0.0;
  for (unsigned int i = 0; i < conf.getNumAtoms(); ++i) {
    if (ignoreHs && mol.getAtomWithIdx(i)->getAtomicNum() == 1) continue;
    double w = 1.0;
    if (weights) {
      w = (*weights)[i];
    }
    wSum += w;
    origin += conf.getAtomPos(i) * w;
  }
  // std::cerr<<"  origin: "<<origin<<" "<<wSum<<std::endl;
  origin /= wSum;

  double sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ;
  computeInertiaTerms(conf, origin, sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ,
                      ignoreHs, weights);

  Eigen::Matrix3d mat;
  mat << sumXX, sumXY, sumXZ, sumXY, sumYY, sumYZ, sumXZ, sumYZ, sumZZ;
  // std::cerr<<"  matrix: "<<mat<<std::endl;
  Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(mat);
  if (eigensolver.info() != Eigen::Success) {
    BOOST_LOG(rdErrorLog) << "eigenvalue calculation did not converge"
                          << std::endl;
    return false;
  }

  axes = eigensolver.eigenvectors();
  moments = eigensolver.eigenvalues();
  if (!weights) {
    conf.getOwningMol().setProp(axesPropName, axes, true);
    conf.getOwningMol().setProp(momentsPropName, moments, true);
  }
  return true;
}
bool computePrincipalAxesAndMomentsFromGyrationMatrix(
    const RDKit::Conformer &conf, Eigen::Matrix3d &axes,
    Eigen::Vector3d &moments, bool ignoreHs, bool force,
    const std::vector<double> *weights) {
  PRECONDITION((!weights || weights->size() >= conf.getNumAtoms()),
               "bad weights vector");
  const char *axesPropName =
      ignoreHs ? "_principalAxes_noH_cov" : "_principalAxes_cov";
  const char *momentsPropName =
      ignoreHs ? "_principalMoments_noH_cov" : "_principalMoments_cov";
  if (!weights && !force && conf.getOwningMol().hasProp(axesPropName) &&
      conf.getOwningMol().hasProp(momentsPropName)) {
    conf.getOwningMol().getProp(axesPropName, axes);
    conf.getOwningMol().getProp(momentsPropName, moments);
    return true;
  }
  const ROMol &mol = conf.getOwningMol();
  RDGeom::Point3D origin(0, 0, 0);
  double wSum = 0.0;
  for (unsigned int i = 0; i < conf.getNumAtoms(); ++i) {
    if (ignoreHs && mol.getAtomWithIdx(i)->getAtomicNum() == 1) continue;
    double w = 1.0;
    if (weights) {
      w = (*weights)[i];
    }
    wSum += w;
    origin += conf.getAtomPos(i) * w;
  }
  // std::cerr<<"  origin: "<<origin<<" "<<wSum<<std::endl;
  origin /= wSum;

  double sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ;
  computeCovarianceTerms(conf, origin, sumXX, sumXY, sumXZ, sumYY, sumYZ, sumZZ,
                         true, ignoreHs, weights);

  Eigen::Matrix3d mat;
  mat << sumXX, sumXY, sumXZ, sumXY, sumYY, sumYZ, sumXZ, sumYZ, sumZZ;
  // std::cerr<<"  matrix: "<<mat<<std::endl;
  Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(mat);
  if (eigensolver.info() != Eigen::Success) {
    BOOST_LOG(rdErrorLog) << "eigenvalue calculation did not converge"
                          << std::endl;
    return false;
  }

  axes = eigensolver.eigenvectors();
  moments = eigensolver.eigenvalues();
  if (!weights) {
    conf.getOwningMol().setProp(axesPropName, axes, true);
    conf.getOwningMol().setProp(momentsPropName, moments, true);
  }
  return true;
}
#endif

RDGeom::Transform3D *computeCanonicalTransform(const Conformer &conf,
                                               const RDGeom::Point3D *center,
                                               bool normalizeCovar,
                                               bool ignoreHs) {
  RDGeom::Point3D origin;
  if (!center) {
    origin = computeCentroid(conf, ignoreHs);
  } else {
    origin = (*center);
  }
  RDNumeric::DoubleSymmMatrix *covMat =
      computeCovarianceMatrix(conf, origin, normalizeCovar, ignoreHs);
  // find the eigen values and eigen vectors for the covMat
  RDNumeric::DoubleMatrix eigVecs(3, 3);
  RDNumeric::DoubleVector eigVals(3);
  // if we have a single atom system we don't need to do anyhting other than
  // setting translation
  // translation
  unsigned int nAtms = conf.getNumAtoms();
  auto *trans = new RDGeom::Transform3D;

  // set the translation
  origin *= -1.0;
  // trans->SetTranslation(origin);
  // if we have a single atom system we don't need to do anyhting setting
  // translation is sufficient
  if (nAtms > 1) {
    RDNumeric::EigenSolvers::powerEigenSolver(3, *covMat, eigVals, eigVecs,
                                              conf.getNumAtoms());
    // deal with zero eigen value systems
    unsigned int i, j, dim = 3;
    for (i = 0; i < 3; ++i) {
      // std::cerr<<"  ev: "<<i<<": "<<eigVals.getVal(i)<<std::endl;
      if (fabs(eigVals.getVal(i)) < EIGEN_TOLERANCE) {
        dim--;
      }
    }
    CHECK_INVARIANT(dim >= 1, "");
    if (dim < 3) {
      RDGeom::Point3D first(eigVecs.getVal(0, 0), eigVecs.getVal(0, 1),
                            eigVecs.getVal(0, 2));
      if (dim == 1) {
        // pick an arbitrary eigen vector perpendicular to the first vector
        RDGeom::Point3D second(first.getPerpendicular());
        eigVecs.setVal(1, 0, second.x);
        eigVecs.setVal(1, 1, second.y);
        eigVecs.setVal(1, 2, second.z);
        if (eigVals.getVal(0) > 1.0) {
          eigVals.setVal(1, 1.0);
        } else {
          eigVals.setVal(1, eigVals.getVal(0) / 2.0);
        }
      }
      RDGeom::Point3D second(eigVecs.getVal(1, 0), eigVecs.getVal(1, 1),
                             eigVecs.getVal(1, 2));
      // pick the third eigen vector perpendicular to the first two
      RDGeom::Point3D third = first.crossProduct(second);
      eigVecs.setVal(2, 0, third.x);
      eigVecs.setVal(2, 1, third.y);
      eigVecs.setVal(2, 2, third.z);
      if (eigVals.getVal(1) > 1.0) {
        eigVals.setVal(2, 1.0);
      } else {
        eigVals.setVal(2, eigVals.getVal(1) / 2.0);
      }
    }
    // now set the transformation
    for (i = 0; i < 3; ++i) {
      for (j = 0; j < 3; ++j) {
        trans->setVal(i, j, eigVecs.getVal(i, j));
      }
    }
  }  // end of multiple atom system
  trans->TransformPoint(origin);
  trans->SetTranslation(origin);
  delete covMat;

  return trans;
}

void transformConformer(Conformer &conf, const RDGeom::Transform3D &trans) {
  RDGeom::POINT3D_VECT &positions = conf.getPositions();
  RDGeom::POINT3D_VECT_I pi;
  for (pi = positions.begin(); pi != positions.end(); ++pi) {
    trans.TransformPoint(*pi);
  }
}

void canonicalizeConformer(Conformer &conf, const RDGeom::Point3D *center,
                           bool normalizeCovar, bool ignoreHs) {
  RDGeom::Transform3D *trans =
      computeCanonicalTransform(conf, center, normalizeCovar, ignoreHs);
  transformConformer(conf, *trans);
  delete trans;
}

void canonicalizeMol(RDKit::ROMol &mol, bool normalizeCovar, bool ignoreHs) {
  ROMol::ConformerIterator ci;
  for (ci = mol.beginConformers(); ci != mol.endConformers(); ci++) {
    canonicalizeConformer(*(*ci), nullptr, normalizeCovar, ignoreHs);
  }
}

namespace {
void _toBeMovedIdxList(const ROMol &mol, unsigned int iAtomId,
                       unsigned int jAtomId, std::list<unsigned int> &alist) {
  unsigned int nAtoms = mol.getNumAtoms();
  boost::dynamic_bitset<> visitedIdx(nAtoms);
  std::stack<unsigned int> stack;
  stack.push(jAtomId);
  visitedIdx[iAtomId] = 1;
  visitedIdx[jAtomId] = 1;
  unsigned int tIdx;
  unsigned int wIdx;
  ROMol::ADJ_ITER nbrIdx;
  ROMol::ADJ_ITER endNbrs;
  bool doMainLoop;
  while (stack.size()) {
    doMainLoop = false;
    tIdx = stack.top();
    const Atom *tAtom = mol.getAtomWithIdx(tIdx);
    boost::tie(nbrIdx, endNbrs) = mol.getAtomNeighbors(tAtom);
    unsigned int eIdx;
    for (eIdx = 0; nbrIdx != endNbrs; ++nbrIdx, ++eIdx) {
      wIdx = (mol[*nbrIdx])->getIdx();
      if (!visitedIdx[wIdx]) {
        visitedIdx[wIdx] = 1;
        stack.push(wIdx);
        doMainLoop = true;
        break;
      }
    }
    if (doMainLoop) {
      continue;
    }
    visitedIdx[tIdx] = 1;
    stack.pop();
  }
  alist.clear();
  for (unsigned int i = 0; i < nAtoms; ++i) {
    if (visitedIdx[i] && (i != iAtomId)) {
      alist.push_back(i);
    }
  }
}
}
double getBondLength(const Conformer &conf, unsigned int iAtomId,
                     unsigned int jAtomId) {
  const RDGeom::POINT3D_VECT &pos = conf.getPositions();
  URANGE_CHECK(iAtomId, pos.size());
  URANGE_CHECK(jAtomId, pos.size());

  return (pos[iAtomId] - pos[jAtomId]).length();
}

void setBondLength(Conformer &conf, unsigned int iAtomId, unsigned int jAtomId,
                   double value) {
  RDGeom::POINT3D_VECT &pos = conf.getPositions();
  URANGE_CHECK(iAtomId, pos.size());
  URANGE_CHECK(jAtomId, pos.size());
  ROMol &mol = conf.getOwningMol();
  Bond *bond = mol.getBondBetweenAtoms(iAtomId, jAtomId);
  if (!bond) throw ValueErrorException("atoms i and j must be bonded");
  if (queryIsBondInRing(bond))
    throw ValueErrorException("bond (i,j) must not belong to a ring");
  RDGeom::Point3D v = pos[iAtomId] - pos[jAtomId];
  double origValue = v.length();
  if (origValue <= 1.e-8)
    throw ValueErrorException("atoms i and j have identical 3D coordinates");

  // get all atoms bonded to j
  std::list<unsigned int> alist;
  _toBeMovedIdxList(mol, iAtomId, jAtomId, alist);
  v *= (value / origValue - 1.);
  for (unsigned int &it : alist) {
    pos[it] -= v;
  }
}

double getAngleRad(const Conformer &conf, unsigned int iAtomId,
                   unsigned int jAtomId, unsigned int kAtomId) {
  const RDGeom::POINT3D_VECT &pos = conf.getPositions();
  URANGE_CHECK(iAtomId, pos.size());
  URANGE_CHECK(jAtomId, pos.size());
  URANGE_CHECK(kAtomId, pos.size());
  RDGeom::Point3D rJI = pos[iAtomId] - pos[jAtomId];
  double rJISqLength = rJI.lengthSq();
  if (rJISqLength <= 1.e-16)
    throw ValueErrorException("atoms i and j have identical 3D coordinates");
  RDGeom::Point3D rJK = pos[kAtomId] - pos[jAtomId];
  double rJKSqLength = rJK.lengthSq();
  if (rJKSqLength <= 1.e-16)
    throw ValueErrorException("atoms j and k have identical 3D coordinates");
  return rJI.angleTo(rJK);
}

void setAngleRad(Conformer &conf, unsigned int iAtomId, unsigned int jAtomId,
                 unsigned int kAtomId, double value) {
  RDGeom::POINT3D_VECT &pos = conf.getPositions();
  URANGE_CHECK(iAtomId, pos.size());
  URANGE_CHECK(jAtomId, pos.size());
  URANGE_CHECK(kAtomId, pos.size());
  ROMol &mol = conf.getOwningMol();
  Bond *bondJI = mol.getBondBetweenAtoms(jAtomId, iAtomId);
  if (!bondJI) throw ValueErrorException("atoms i and j must be bonded");
  Bond *bondJK = mol.getBondBetweenAtoms(jAtomId, kAtomId);
  if (!bondJK) throw ValueErrorException("atoms j and k must be bonded");
  if (queryIsBondInRing(bondJI) && queryIsBondInRing(bondJK))
    throw ValueErrorException(
        "bonds (i,j) and (j,k) must not both belong to a ring");

  RDGeom::Point3D rJI = pos[iAtomId] - pos[jAtomId];
  double rJISqLength = rJI.lengthSq();
  if (rJISqLength <= 1.e-16)
    throw ValueErrorException("atoms i and j have identical 3D coordinates");
  RDGeom::Point3D rJK = pos[kAtomId] - pos[jAtomId];
  double rJKSqLength = rJK.lengthSq();
  if (rJKSqLength <= 1.e-16)
    throw ValueErrorException("atoms j and k have identical 3D coordinates");

  // we only need to rotate by delta with respect to the current angle value
  value -= rJI.angleTo(rJK);
  RDGeom::Point3D &rotAxisBegin = pos[jAtomId];
  // our rotation axis is the normal to the plane of atoms i, j, k
  RDGeom::Point3D rotAxisEnd = rJI.crossProduct(rJK) + pos[jAtomId];
  RDGeom::Point3D rotAxis = rotAxisEnd - rotAxisBegin;
  rotAxis.normalize();
  // get all atoms bonded to j and loop through them
  std::list<unsigned int> alist;
  _toBeMovedIdxList(mol, jAtomId, kAtomId, alist);
  for (unsigned int &it : alist) {
    // translate atom so that it coincides with the origin of rotation
    pos[it] -= rotAxisBegin;
    // rotate around our rotation axis
    RDGeom::Transform3D rotByAngle;
    rotByAngle.SetRotation(value, rotAxis);
    rotByAngle.TransformPoint(pos[it]);
    // translate atom back
    pos[it] += rotAxisBegin;
  }
}

double getDihedralRad(const Conformer &conf, unsigned int iAtomId,
                      unsigned int jAtomId, unsigned int kAtomId,
                      unsigned int lAtomId) {
  const RDGeom::POINT3D_VECT &pos = conf.getPositions();
  URANGE_CHECK(iAtomId, pos.size());
  URANGE_CHECK(jAtomId, pos.size());
  URANGE_CHECK(kAtomId, pos.size());
  URANGE_CHECK(lAtomId, pos.size());
  RDGeom::Point3D rIJ = pos[jAtomId] - pos[iAtomId];
  double rIJSqLength = rIJ.lengthSq();
  if (rIJSqLength <= 1.e-16)
    throw ValueErrorException("atoms i and j have identical 3D coordinates");
  RDGeom::Point3D rJK = pos[kAtomId] - pos[jAtomId];
  double rJKSqLength = rJK.lengthSq();
  if (rJKSqLength <= 1.e-16)
    throw ValueErrorException("atoms j and k have identical 3D coordinates");
  RDGeom::Point3D rKL = pos[lAtomId] - pos[kAtomId];
  double rKLSqLength = rKL.lengthSq();
  if (rKLSqLength <= 1.e-16)
    throw ValueErrorException("atoms k and l have identical 3D coordinates");

  RDGeom::Point3D nIJK = rIJ.crossProduct(rJK);
  double nIJKSqLength = nIJK.lengthSq();
  RDGeom::Point3D nJKL = rJK.crossProduct(rKL);
  double nJKLSqLength = nJKL.lengthSq();
  RDGeom::Point3D m = nIJK.crossProduct(rJK);
  // we want a signed dihedral, that's why we use atan2 instead of acos
  return -atan2(m.dotProduct(nJKL) / sqrt(nJKLSqLength * m.lengthSq()),
                nIJK.dotProduct(nJKL) / sqrt(nIJKSqLength * nJKLSqLength));
}

void setDihedralRad(Conformer &conf, unsigned int iAtomId, unsigned int jAtomId,
                    unsigned int kAtomId, unsigned int lAtomId, double value) {
  RDGeom::POINT3D_VECT &pos = conf.getPositions();
  URANGE_CHECK(iAtomId, pos.size());
  URANGE_CHECK(jAtomId, pos.size());
  URANGE_CHECK(kAtomId, pos.size());
  URANGE_CHECK(lAtomId, pos.size());
  ROMol &mol = conf.getOwningMol();
  Bond *bondJK = mol.getBondBetweenAtoms(jAtomId, kAtomId);
  if (!bondJK) throw ValueErrorException("atoms j and k must be bonded");

  if (queryIsBondInRing(bondJK))
    throw ValueErrorException("bond (j,k) must not belong to a ring");
  RDGeom::Point3D rIJ = pos[jAtomId] - pos[iAtomId];
  double rIJSqLength = rIJ.lengthSq();
  if (rIJSqLength <= 1.e-16)
    throw ValueErrorException("atoms i and j have identical 3D coordinates");
  RDGeom::Point3D rJK = pos[kAtomId] - pos[jAtomId];
  double rJKSqLength = rJK.lengthSq();
  if (rJKSqLength <= 1.e-16)
    throw ValueErrorException("atoms j and k have identical 3D coordinates");
  RDGeom::Point3D rKL = pos[lAtomId] - pos[kAtomId];
  double rKLSqLength = rKL.lengthSq();
  if (rKLSqLength <= 1.e-16)
    throw ValueErrorException("atoms k and l have identical 3D coordinates");

  RDGeom::Point3D nIJK = rIJ.crossProduct(rJK);
  double nIJKSqLength = nIJK.lengthSq();
  RDGeom::Point3D nJKL = rJK.crossProduct(rKL);
  double nJKLSqLength = nJKL.lengthSq();
  RDGeom::Point3D m = nIJK.crossProduct(rJK);
  // we only need to rotate by delta with respect to the current dihedral value
  value -= -atan2(m.dotProduct(nJKL) / sqrt(nJKLSqLength * m.lengthSq()),
                  nIJK.dotProduct(nJKL) / sqrt(nIJKSqLength * nJKLSqLength));
  // our rotation axis is the (j,k) bond
  RDGeom::Point3D &rotAxisBegin = pos[jAtomId];
  RDGeom::Point3D &rotAxisEnd = pos[kAtomId];
  RDGeom::Point3D rotAxis = rotAxisEnd - rotAxisBegin;
  rotAxis.normalize();
  // get all atoms bonded to k and loop through them
  std::list<unsigned int> alist;
  _toBeMovedIdxList(mol, jAtomId, kAtomId, alist);
  for (unsigned int &it : alist) {
    // translate atom so that it coincides with the origin of rotation
    pos[it] -= rotAxisBegin;
    // rotate around our rotation axis
    RDGeom::Transform3D rotByAngle;
    rotByAngle.SetRotation(value, rotAxis);
    rotByAngle.TransformPoint(pos[it]);
    // translate atom back
    pos[it] += rotAxisBegin;
  }
}
}


/*
 Copyright (C) 2019 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <ored/marketdata/strike.hpp>
#include <ored/utilities/parsers.hpp>
#include <ored/utilities/to_string.hpp>

#include <boost/algorithm/string.hpp>
#include <boost/serialization/export.hpp>

using namespace QuantLib;
using std::ostream;
using std::ostringstream;
using std::string;
using std::vector;

namespace ore {
namespace data {

bool operator==(const BaseStrike& lhs, const BaseStrike& rhs) { return lhs.equal_to(rhs); }

AbsoluteStrike::AbsoluteStrike() {}

AbsoluteStrike::AbsoluteStrike(Real strike) : strike_(strike) {}

Real AbsoluteStrike::strike() const { return strike_; }

void AbsoluteStrike::fromString(const string& strStrike) { strike_ = parseReal(strStrike); }

string AbsoluteStrike::toString() const { return to_string(strike_); }

bool AbsoluteStrike::equal_to(const BaseStrike& other) const {
    if (const AbsoluteStrike* p = dynamic_cast<const AbsoluteStrike*>(&other)) {
        return close(strike_, p->strike());
    } else {
        return false;
    }
}

DeltaStrike::DeltaStrike() : deltaType_(DeltaVolQuote::Spot), optionType_(Option::Call) {}

DeltaStrike::DeltaStrike(DeltaVolQuote::DeltaType deltaType, Option::Type optionType, Real delta)
    : deltaType_(deltaType), optionType_(optionType), delta_(delta) {}

DeltaVolQuote::DeltaType DeltaStrike::deltaType() const { return deltaType_; }

Option::Type DeltaStrike::optionType() const { return optionType_; }

Real DeltaStrike::delta() const { return delta_; }

void DeltaStrike::fromString(const string& strStrike) {

    // Expect strStrike of form: DEL / Spot|Fwd|PaSpot|PaFwd / Call|Put / DELTA_VALUE
    vector<string> tokens;
    boost::split(tokens, strStrike, boost::is_any_of("/"));
    QL_REQUIRE(tokens.size() == 4, "DeltaStrike::fromString expects 4 tokens.");
    QL_REQUIRE(tokens[0] == "DEL", "DeltaStrike::fromString expects 1st token to equal 'DEL'.");

    deltaType_ = parseDeltaType(tokens[1]);
    optionType_ = parseOptionType(tokens[2]);
    delta_ = parseReal(tokens[3]);
}

string DeltaStrike::toString() const {

    // Write string of form: DEL / Spot|Fwd|PaSpot|PaFwd / Call|Put / DELTA_VALUE
    ostringstream oss;
    oss << "DEL/" << deltaType_ << "/" << optionType_ << "/" << to_string(delta_);
    return oss.str();
}

bool DeltaStrike::equal_to(const BaseStrike& other) const {
    if (const DeltaStrike* p = dynamic_cast<const DeltaStrike*>(&other)) {
        return deltaType_ == p->deltaType() && optionType_ == p->optionType() && close(delta_, p->delta());
    } else {
        return false;
    }
}

AtmStrike::AtmStrike() : atmType_(DeltaVolQuote::AtmSpot) {}

AtmStrike::AtmStrike(DeltaVolQuote::AtmType atmType, boost::optional<DeltaVolQuote::DeltaType> deltaType)
    : atmType_(atmType), deltaType_(deltaType) {
    check();
}

DeltaVolQuote::AtmType AtmStrike::atmType() const { return atmType_; }

boost::optional<DeltaVolQuote::DeltaType> AtmStrike::deltaType() const { return deltaType_; }

void AtmStrike::fromString(const string& strStrike) {

    // Expect strStrike of form:
    // "ATM / AtmSpot|AtmFwd|AtmDeltaNeutral|AtmVegaMax|AtmGammaMax|AtmPutCall50"
    // with an optional following "/ DEL / Spot|Fwd|PaSpot|PaFwd"
    vector<string> tokens;
    boost::split(tokens, strStrike, boost::is_any_of("/"));
    QL_REQUIRE(tokens.size() == 2 || tokens.size() == 4, "AtmStrike::fromString expects 2 or 4 tokens.");
    QL_REQUIRE(tokens[0] == "ATM", "AtmStrike::fromString expects 1st token to equal 'ATM'.");

    atmType_ = parseAtmType(tokens[1]);

    deltaType_ = boost::none;
    if (tokens.size() == 4) {
        QL_REQUIRE(tokens[2] == "DEL", "AtmStrike::fromString expects 3rd token to equal 'DEL'.");
        deltaType_ = parseDeltaType(tokens[3]);
    }

    check();
}

string AtmStrike::toString() const {

    // Write strike of form:
    // "ATM / AtmSpot|AtmFwd|AtmDeltaNeutral|AtmVegaMax|AtmGammaMax|AtmPutCall50"
    // with an optional following "/ DEL / Spot|Fwd|PaSpot|PaFwd" if delta type is populated.
    ostringstream oss;
    oss << "ATM/" << atmType_;

    if (deltaType_) {
        oss << "/DEL/" << (*deltaType_);
    }

    return oss.str();
}

bool AtmStrike::equal_to(const BaseStrike& other) const {
    if (const AtmStrike* p = dynamic_cast<const AtmStrike*>(&other)) {
        return !(atmType_ != p->atmType() || (deltaType_ && !p->deltaType()) || (!deltaType_ && p->deltaType()) ||
                 *deltaType_ != *p->deltaType());
    } else {
        return false;
    }
}

void AtmStrike::check() const {
    QL_REQUIRE(atmType_ != DeltaVolQuote::AtmNull, "AtmStrike type must not be AtmNull.");
    if (atmType_ == DeltaVolQuote::AtmDeltaNeutral) {
        QL_REQUIRE(deltaType_, "If AtmStrike type is AtmDeltaNeutral, we need a delta type.");
    } else {
        QL_REQUIRE(!deltaType_, "If AtmStrike type is not AtmDeltaNeutral, delta type should not be given.");
    }
    if (atmType_ == DeltaVolQuote::AtmPutCall50) {
        QL_REQUIRE(deltaType_ && *deltaType_ == DeltaVolQuote::Fwd,
                   "If AtmStrike type is AtmPutCall50, delta type must be AtmFwd.");
    }
}

MoneynessStrike::MoneynessStrike() : type_(Type::Spot) {}

MoneynessStrike::MoneynessStrike(Type type, Real moneyness) : type_(type), moneyness_(moneyness) {}

MoneynessStrike::Type MoneynessStrike::type() const { return type_; }

Real MoneynessStrike::moneyness() const { return moneyness_; }

void MoneynessStrike::fromString(const string& strStrike) {

    // Expect strStrike of form "MNY / Spot|Fwd / MONEYNESS_VALUE"
    vector<string> tokens;
    boost::split(tokens, strStrike, boost::is_any_of("/"));
    QL_REQUIRE(tokens.size() == 3, "MoneynessStrike::fromString expects 3 tokens.");
    QL_REQUIRE(tokens[0] == "MNY", "MoneynessStrike::fromString expects 1st token to equal 'MNY'.");

    type_ = parseMoneynessType(tokens[1]);
    moneyness_ = parseReal(tokens[2]);
}

string MoneynessStrike::toString() const {

    // Write strike of form "MNY / Spot|Fwd / MONEYNESS_VALUE"
    ostringstream oss;
    oss << "MNY/" << type_ << "/" << to_string(moneyness_);
    return oss.str();
}

bool MoneynessStrike::equal_to(const BaseStrike& other) const {
    if (const MoneynessStrike* p = dynamic_cast<const MoneynessStrike*>(&other)) {
        return type_ == p->type() && close(moneyness_, p->moneyness());
    } else {
        return false;
    }
}

ostream& operator<<(ostream& os, const BaseStrike& strike) { return os << strike.toString(); }

ostream& operator<<(ostream& os, DeltaVolQuote::DeltaType type) {
    switch (type) {
    case DeltaVolQuote::Spot:
        return os << "Spot";
    case DeltaVolQuote::Fwd:
        return os << "Fwd";
    case DeltaVolQuote::PaSpot:
        return os << "PaSpot";
    case DeltaVolQuote::PaFwd:
        return os << "PaFwd";
    default:
        QL_FAIL("Unknown delta type");
    }
}

ostream& operator<<(ostream& os, DeltaVolQuote::AtmType type) {
    switch (type) {
    case DeltaVolQuote::AtmNull:
        return os << "AtmNull";
    case DeltaVolQuote::AtmSpot:
        return os << "AtmSpot";
    case DeltaVolQuote::AtmFwd:
        return os << "AtmFwd";
    case DeltaVolQuote::AtmDeltaNeutral:
        return os << "AtmDeltaNeutral";
    case DeltaVolQuote::AtmVegaMax:
        return os << "AtmVegaMax";
    case DeltaVolQuote::AtmGammaMax:
        return os << "AtmGammaMax";
    case DeltaVolQuote::AtmPutCall50:
        return os << "AtmPutCall50";
    default:
        QL_FAIL("Unknown atm type");
    }
}

ostream& operator<<(ostream& os, MoneynessStrike::Type type) {
    switch (type) {
    case MoneynessStrike::Type::Spot:
        return os << "Spot";
    case MoneynessStrike::Type::Forward:
        return os << "Fwd";
    default:
        QL_FAIL("Unknown moneyness type");
    }
}

MoneynessStrike::Type parseMoneynessType(const string& type) {
    if (type == "Spot") {
        return MoneynessStrike::Type::Spot;
    } else if (type == "Fwd") {
        return MoneynessStrike::Type::Forward;
    } else {
        QL_FAIL("Moneyness type '" << type << "' not recognized");
    }
}

boost::shared_ptr<BaseStrike> parseBaseStrike(const string& strStrike) {

    boost::shared_ptr<BaseStrike> strike;

    // Expect strStrike to either:
    // 1. have a single token which means that we have an absolute strike, or
    // 2. have multiple tokens beginning with one of DEL, ATM or MNY
    vector<string> tokens;
    boost::split(tokens, strStrike, boost::is_any_of("/"));

    if (tokens.size() == 1) {
        strike = boost::make_shared<AbsoluteStrike>();
    } else if (tokens[0] == "DEL") {
        strike = boost::make_shared<DeltaStrike>();
    } else if (tokens[0] == "ATM") {
        strike = boost::make_shared<AtmStrike>();
    } else if (tokens[0] == "MNY") {
        strike = boost::make_shared<MoneynessStrike>();
    } else {
        QL_FAIL("Could not parse strike string '" << strStrike << "'.");
    }

    strike->fromString(strStrike);

    return strike;
}

} // namespace data
} // namespace ore

BOOST_CLASS_EXPORT_GUID(ore::data::AbsoluteStrike, "AbsoluteStrike");
BOOST_CLASS_EXPORT_GUID(ore::data::DeltaStrike, "DeltaStrike");
BOOST_CLASS_EXPORT_GUID(ore::data::AtmStrike, "AtmStrike");
BOOST_CLASS_EXPORT_GUID(ore::data::MoneynessStrike, "MoneynessStrike");


/*****************************************************************************
*
* Copyright (c) 2003-2020 by The University of Queensland
* http://www.uq.edu.au
*
* Primary Business: Queensland, Australia
* Licensed under the Apache License, version 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Development until 2012 by Earth Systems Science Computational Center (ESSCC)
* Development 2012-2013 by School of Earth Sciences
* Development from 2014-2017 by Centre for Geoscience Computing (GeoComp)
* Development from 2019 by School of Earth and Environmental Sciences
**
*****************************************************************************/

#include "Data.h"
#include "WrappedArray.h"
#include "DataException.h"

#if ESYS_HAVE_NUMPY_H
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/ndarrayobject.h>
#endif

#include <iostream>

#include <boost/python/tuple.hpp>

using namespace escript;
using namespace boost::python;
using DataTypes::cplx_t;
using DataTypes::real_t;

namespace
{

void checkFeatures(const boost::python::object& obj)
{
	using namespace std;
	boost::python::object o2;
	try
	{
	   /*int len=*/ extract<int>(obj.attr("__len__")());
	}
	catch (...)
	{
	   PyErr_Clear();
	   throw DataException("Object passed to WrappedArray must support __len__");
	}
	try
	{
	   o2=obj.attr("__getitem__");
	}
	catch (...)
	{
	   PyErr_Clear();
	   throw DataException("Object passed to WrappedArray must support __getitem__");
	}
}

// This should not be called on anything which does
// not have a __len__
bool checkForComplex(const boost::python::object& obj)
{
    try
    {
	int len=extract<int>(obj.attr("__len__")());
	for (int i=0;i<len;++i)
	{
	    const boost::python::object t=obj[i];
	    bool haslen=false;
	    try
            {
	        extract<int>(t.attr("__len__")());
	        haslen=true;
	    }
	    catch(...)
	    {
		PyErr_Clear();
	    }
	    	// If it has a length, we dig down
		// if not, we test for complex
	    if (haslen)
	    {
                if (checkForComplex(t))
		{
		    return true;
		}
	    }
	    else
	    {
		extract<DataTypes::real_t> er(t);
		if (!er.check())
		{
		    // unfortunately, if this was a numpy object, that check my fail
		    // even if it should succeed (eg numpy.int64 on python3)
		    // instead, we will try to call __float__ and see what happens
		    try
		    {
			t.attr("__float__")();
			return false;			// if this check succeeds it isn't complex
		    }
		    catch (...)
		    {
			PyErr_Clear();
			// at this point, we have no apparent way to get a real out so
			// we assume it must be complex
			return true;
		    }
		}
	    }
	}
	return false;
    }
    catch(...)
    {
        PyErr_Clear();
	return false;
    }
    return false;
}

void getObjShape(const boost::python::object& obj, DataTypes::ShapeType& s)
{
	int len=0;
	try
	{
	   len=extract<int>(obj.attr("__len__")());
	}
	catch(...)
	{
	   PyErr_Clear();		// tell python the error isn't there anymore
	   return;
	}
	if (len<1)
	{
	   throw DataException("Array filter - no empty components in arrays please.");
	}
	s.push_back(len);

	if (s.size()>ESCRIPT_MAX_DATA_RANK)
	{
	   throw DataException("Array filter - Maximum rank exceeded in array");
	}
	getObjShape(obj[0],s);
}

}

WrappedArray::WrappedArray(const boost::python::object& obj_in)
:obj(obj_in),converted(false),iscomplex(false),scalar_r(nan("")),scalar_c(nan(""))
{
	dat_r=0;
	dat_c=0;
	// First we check for scalars
	try
	{
	   extract<DataTypes::cplx_t> ec(obj_in);
	   extract<real_t> er(obj_in);
	   if (er.check())		// check for real_t first because complex will fail this
	   {
	      scalar_r=er();
	   }
	   else
	   {
	      scalar_c=ec();
	      iscomplex=true;

	   }
	   rank=0;
	   return;
	}
	catch (...)
	{		// so we clear the failure
	   PyErr_Clear();
	}
	try
	{
	   const boost::python::object obj_in_t=obj_in[make_tuple()];
	   extract<DataTypes::cplx_t> ec(obj_in_t);
	   extract<real_t> er(obj_in_t);
	   if (er.check())
	   {
	      scalar_r=er();

	   }
	   else
	   {
	      scalar_c=ec();
	      iscomplex=true;
	   }
	   rank=0;
	   return;
	}
	catch (...)
	{		// so we clear the failure
	   PyErr_Clear();
	}

	scalar_c=0;
	scalar_r=0;
	checkFeatures(obj_in);
	getObjShape(obj,shape);
	rank=shape.size();
	iscomplex=checkForComplex(obj_in);

#if ESYS_HAVE_NUMPY_H
	// if obj is a numpy array it is much faster to copy the array through the
	// __array_struct__ interface instead of extracting single values from the
	// components via getElt(). For this to work we check below that
	// (1) this is a valid PyArrayInterface instance
	// (2) the data is stored as a contiguous C array
	// (3) the data type is suitable (correct type and byte size)
	try
	{
		object o = (extract<object>(obj.attr("__array_struct__")));
#ifdef ESPYTHON3
		if (PyCapsule_CheckExact(o.ptr()))
#else
		if (PyCObject_Check(o.ptr()))
#endif
		{
			PyObject* cobj=(PyObject*)o.ptr();
#ifdef ESPYTHON3
            const char* name = PyCapsule_GetName(cobj);
			PyArrayInterface* arr=(PyArrayInterface*)PyCapsule_GetPointer(cobj, name);
#else
			PyArrayInterface* arr=(PyArrayInterface*)PyCObject_AsVoidPtr(cobj);
#endif
#ifndef NPY_1_7_API_VERSION
  #define NPY_ARRAY_IN_ARRAY NPY_IN_ARRAY
  #define NPY_ARRAY_NOTSWAPPED NPY_NOTSWAPPED
#endif
			if (arr->two==2 && arr->flags&NPY_ARRAY_IN_ARRAY && arr->flags&NPY_ARRAY_NOTSWAPPED)
			{
				std::vector<int> strides;
				// convert #bytes to #elements
				for (int i=0; i<arr->nd; i++)
				{
					strides.push_back(arr->strides[i]/arr->itemsize);
				}

				if (arr->typekind == 'f')
				{
					if (arr->itemsize==sizeof(real_t))
					{
						convertNumpyArray<real_t>((const real_t*)arr->data, strides);
					}
					else if (arr->itemsize==sizeof(float))
			   		{
						convertNumpyArray<float>((const float*)arr->data, strides);
					}
				}
				else if (arr->typekind == 'i')
				{
					if (arr->itemsize==sizeof(int))
				   	{
						convertNumpyArray<int>((const int*)arr->data, strides);
					}
					else if (arr->itemsize==sizeof(long))
				   	{
						convertNumpyArray<long>((const long*)arr->data, strides);
					}
				}
				else if (arr->typekind == 'u')
				{
					if (arr->itemsize==sizeof(unsigned))
				   	{
						convertNumpyArray<unsigned>((const unsigned*)arr->data, strides);
					}
					else if (arr->itemsize==sizeof(unsigned long))
				   	{
						convertNumpyArray<unsigned long>((const unsigned long*)arr->data, strides);
					}
				}
				else if (arr->typekind == 'c')
				{
					if (arr->itemsize==sizeof(cplx_t))
				   	{
						convertNumpyArrayC<DataTypes::cplx_t>((const cplx_t*)arr->data, strides);
						iscomplex=true;
					}
					// not accomodating other types of complex values
				}
			}
		}
	} catch (...)
	{
		PyErr_Clear();
	}
#endif
}

template<typename T>
void WrappedArray::convertNumpyArrayC(const T* array, const std::vector<int>& strides) const
{
	// this method is only called by the constructor above which does the
	// necessary checks and initialisations
	int size=DataTypes::noValues(shape);
	dat_c=new cplx_t[size];
	switch (rank)
	{
		case 1:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				dat_c[i]=array[i*strides[0]];
			}
		break;
		case 2:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				for (int j=0;j<shape[1];j++)
				{
					dat_c[DataTypes::getRelIndex(shape,i,j)]=array[i*strides[0]+j*strides[1]];
				}
			}
		break;
		case 3:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				for (int j=0;j<shape[1];j++)
				{
					for (int k=0;k<shape[2];k++)
					{
						dat_c[DataTypes::getRelIndex(shape,i,j,k)]=array[i*strides[0]+j*strides[1]+k*strides[2]];
					}
				}
			}
		break;
		case 4:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				for (int j=0;j<shape[1];j++)
				{
					for (int k=0;k<shape[2];k++)
					{
						for (int m=0;m<shape[3];m++)
						{
							dat_c[DataTypes::getRelIndex(shape,i,j,k,m)]=array[i*strides[0]+j*strides[1]+k*strides[2]+m*strides[3]];
						}
					}
				}
			}
		break;
	}
}

template<typename T>
void WrappedArray::convertNumpyArray(const T* array, const std::vector<int>& strides) const
{
	// this method is only called by the constructor above which does the
	// necessary checks and initialisations
	int size=DataTypes::noValues(shape);
	dat_r=new real_t[size];
	switch (rank)
	{
		case 1:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				dat_r[i]=array[i*strides[0]];
			}
		break;
		case 2:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				for (int j=0;j<shape[1];j++)
				{
					dat_r[DataTypes::getRelIndex(shape,i,j)]=array[i*strides[0]+j*strides[1]];
				}
			}
		break;
		case 3:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				for (int j=0;j<shape[1];j++)
				{
					for (int k=0;k<shape[2];k++)
					{
						dat_r[DataTypes::getRelIndex(shape,i,j,k)]=array[i*strides[0]+j*strides[1]+k*strides[2]];
					}
				}
			}
		break;
		case 4:
#pragma omp parallel for
			for (int i=0;i<shape[0];i++)
			{
				for (int j=0;j<shape[1];j++)
				{
					for (int k=0;k<shape[2];k++)
					{
						for (int m=0;m<shape[3];m++)
						{
							dat_r[DataTypes::getRelIndex(shape,i,j,k,m)]=array[i*strides[0]+j*strides[1]+k*strides[2]+m*strides[3]];
						}
					}
				}
			}
		break;
	}
}

void WrappedArray::convertArrayR() const
{
	if ((converted) || (rank<=0) || (rank>4))	// checking illegal rank here to avoid memory issues later
	{					// yes the failure is silent here but not doing the copy
	    return;				// will just cause an error to be raised later
	}
	int size=DataTypes::noValues(shape);
	real_t* tdat=new real_t[size];
	switch (rank)
	{
	case 1: for (int i=0;i<shape[0];i++)
		{
			tdat[i]=getElt(i);
		}
		break;
	case 2: for (int i=0;i<shape[0];i++)
		{
		    for (int j=0;j<shape[1];j++)
		    {
			tdat[DataTypes::getRelIndex(shape,i,j)]=getElt(i,j);
		    }
		}
		break;
	case 3: for (int i=0;i<shape[0];i++)
		{
		    for (int j=0;j<shape[1];j++)
		    {
			for (int k=0;k<shape[2];k++)
			{
			    tdat[DataTypes::getRelIndex(shape,i,j,k)]=getElt(i,j,k);
			}
		    }
		}
		break;
	case 4: for (int i=0;i<shape[0];i++)
		{
		    for (int j=0;j<shape[1];j++)
		    {
			for (int k=0;k<shape[2];k++)
			{
			    for (int m=0;m<shape[3];m++)
			    {
			    	tdat[DataTypes::getRelIndex(shape,i,j,k,m)]=getElt(i,j,k,m);
			    }
			}
		    }
		}
		break;
	default:
		;  // do nothing
		// can't happen. We've already checked the bounds above
	}
	dat_r=tdat;
	converted=true;
}

void WrappedArray::convertArrayC() const
{
	if ((converted) || (rank<=0) || (rank>4))	// checking illegal rank here to avoid memory issues later
	{					// yes the failure is silent here but not doing the copy
	    return;				// will just cause an error to be raised later
	}
	int size=DataTypes::noValues(shape);
	cplx_t* tdat=new cplx_t[size];
	switch (rank)
	{
	case 1: for (int i=0;i<shape[0];i++)
		{
			tdat[i]=getElt(i);
		}
		break;
	case 2: for (int i=0;i<shape[0];i++)
		{
		    for (int j=0;j<shape[1];j++)
		    {
			tdat[DataTypes::getRelIndex(shape,i,j)]=getElt(i,j);
		    }
		}
		break;
	case 3: for (int i=0;i<shape[0];i++)
		{
		    for (int j=0;j<shape[1];j++)
		    {
			for (int k=0;k<shape[2];k++)
			{
			    tdat[DataTypes::getRelIndex(shape,i,j,k)]=getElt(i,j,k);
			}
		    }
		}
		break;
	case 4: for (int i=0;i<shape[0];i++)
		{
		    for (int j=0;j<shape[1];j++)
		    {
			for (int k=0;k<shape[2];k++)
			{
			    for (int m=0;m<shape[3];m++)
			    {
			    	tdat[DataTypes::getRelIndex(shape,i,j,k,m)]=getElt(i,j,k,m);
			    }
			}
		    }
		}
		break;
	default:
		;  // do nothing
		// can't happen. We've already checked the bounds above
	}
	dat_c=tdat;
	converted=true;
}

void WrappedArray::convertArray() const
{
    if (iscomplex)
    {
	convertArrayC();
    }
    else
    {
	convertArrayR();
    }
}

WrappedArray::~WrappedArray()
{
    if (dat_r!=0)
    {
	delete[] dat_r;
    }
    if (dat_c!=0)
    {
	delete[] dat_c;
    }
}


/*
 boost/numeric/odeint/stepper/detail/pid_step_adjuster.hpp

 [begin_description]
 Implementation of the stepsize controller for the controlled adams bashforth moulton stepper.
 [end_description]

 Copyright 2017 Valentin Noah Hartmann

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_NUMERIC_ODEINT_STEPPER_DETAIL_PID_STEP_ADJUSTER_HPP_INCLUDED
#define BOOST_NUMERIC_ODEINT_STEPPER_DETAIL_PID_STEP_ADJUSTER_HPP_INCLUDED

#include <boost/numeric/odeint/stepper/detail/rotating_buffer.hpp>
#include <boost/numeric/odeint/stepper/detail/pid_step_adjuster_coefficients.hpp>

#include <boost/numeric/odeint/algebra/algebra_dispatcher.hpp>
#include <boost/numeric/odeint/algebra/operations_dispatcher.hpp>

#include <math.h>

namespace boost {
namespace numeric {
namespace odeint {
namespace detail {

template<
class Value = double,
class Time = double
>
struct pid_op
{
public:
    typedef Value value_type;
    typedef Time time_type;

    const double beta1;
    const double beta2;
    const double beta3;
    const double alpha1;
    const double alpha2;

    const time_type dt1;
    const time_type dt2;
    const time_type dt3;

    const size_t m_steps;

    pid_op(const size_t steps, const double _dt1, const double _dt2, const double _dt3,
        const double b1 = 1, const double b2 = 0, const double b3 = 0, const double a1 = 0, const double a2 = 0)
    :beta1(b1), beta2(b2), beta3(b3), alpha1(a1), alpha2(a2),
    dt1(_dt1), dt2(_dt2), dt3(_dt3),
    m_steps(steps)
    {};

    template<class T1, class T2, class T3, class T4>
    void operator()(T1 &t1, const T2 &t2, const T3 &t3, const T4 &t4)
    {
        using std::abs;

        t1 = adapted_pow(abs(t2), -beta1/(m_steps + 1)) *
            adapted_pow(abs(t3), -beta2/(m_steps + 1)) *
            adapted_pow(abs(t4), -beta3/(m_steps + 1)) *
            adapted_pow(abs(dt1/dt2), -alpha1/(m_steps + 1))*
            adapted_pow(abs(dt2/dt3), -alpha2/(m_steps + 1));

        t1 = 1/t1;
    };

    template<class T1, class T2>
    void operator()(T1 &t1, const T2 &t2)
    {
        using std::abs;

        t1 = adapted_pow(abs(t2), -beta1/(m_steps + 1));

        t1 = 1/t1;
    };

private:
    template<class T>
    inline value_type adapted_pow(T base, double exp)
    {
        if(exp == 0)
        {
            return 1;
        }
        else if (exp > 0)
        {
            return pow(base, exp);
        }
        else
        {
            return 1/pow(base, -exp);
        }
    };
};

template<
class State,
class Value = double,
class Deriv = State,
class Time = double,
class Algebra = typename algebra_dispatcher< State >::algebra_type,
class Operations = typename operations_dispatcher< Deriv >::operations_type,
size_t Type = BASIC
>
struct pid_step_adjuster
{
public:
    static double threshold() { return 0.9; };

    typedef State state_type;
    typedef Value value_type;
    typedef Deriv deriv_type;
    typedef Time time_type;

    typedef Algebra algebra_type;
    typedef Operations operations_type;

    typedef rotating_buffer<state_type, 3> error_storage_type;
    typedef rotating_buffer<time_type, 3> time_storage_type;
    typedef pid_step_adjuster_coefficients<Type> coeff_type;

    pid_step_adjuster(double abs_tol = 1e-6, double rel_tol = 1e-6, time_type dtmax = 1.0)
    :m_dtmax(dtmax), m_error_storage(), m_dt_storage(), m_init(0),
    m_abs_tol(abs_tol), m_rel_tol(rel_tol)
    {};

    time_type adjust_stepsize(const size_t steps, time_type dt, state_type &err, const state_type &x, const deriv_type &dxdt)
    {
        using std::abs;
        m_algebra.for_each3( err , x , dxdt ,
                typename operations_type::template rel_error< value_type >( m_abs_tol , m_rel_tol , 1.0 , 1.0 * abs(get_unit_value( dt )) ) );

        m_error_storage[0] = err;
        m_dt_storage[0] = dt;

        if(m_init >= 2)
        {
            m_algebra.for_each4(err, m_error_storage[0], m_error_storage[1], m_error_storage[2],
                pid_op<>(steps, m_dt_storage[0], m_dt_storage[1], m_dt_storage[2],
                    m_coeff[0], m_coeff[1], m_coeff[2], m_coeff[3], m_coeff[4]));
        }
        else
        {
            m_algebra.for_each2(err, m_error_storage[0],
                pid_op<>(steps, m_dt_storage[0], m_dt_storage[1], m_dt_storage[2], 0.7));
        }

        value_type ratio = 1 / m_algebra.norm_inf(err);

        value_type kappa = 1.0;
        ratio = 1.0 + kappa*atan((ratio - 1) / kappa);

        if(ratio*dt >= m_dtmax)
        {
            ratio = m_dtmax / dt;
        }

        if(ratio >= threshold())
        {
            m_error_storage.rotate();
            m_dt_storage.rotate();

            ++m_init;
        }
        else
        {
            m_init = 0;
        }

        return dt * static_cast<time_type>(ratio);
    };

private:
    algebra_type m_algebra;

    time_type m_dtmax;
    error_storage_type m_error_storage;
    time_storage_type m_dt_storage;

    size_t m_init;
    double m_abs_tol;
    double m_rel_tol;

    coeff_type m_coeff;
};

} // detail
} // odeint
} // numeric
} // boost

#endif


#if 0
// This is currently not used but left here in case it becomes useful.

#ifndef SHASTA_DEBRUIJN_GRAPH_HPP
#define SHASTA_DEBRUIJN_GRAPH_HPP

// A general purpose templated class representing a De Bruijn graph.
// Each vertex represents a sequence of k symbols occurring in
// sequences of symbols.
// Template arguments:
// - Symbol: the symbols that the sequences are made of.
// - k: the number of symbols in the sequence represented by a vertex.
// - SequenceId: the type used to identify the sequences.

// Boost libraries.
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/iteration_macros.hpp>

// Standard library.
#include "fstream.hpp"
#include <set>
#include "string.hpp"
#include "utility.hpp"
#include "vector.hpp"

namespace shasta {

    template<class Symbol, uint64_t k, class SequenceId> class DeBruijnGraph;
    template<class Symbol, uint64_t k, class SequenceId> class DeBruijnGraphVertex;
    template<class Symbol, uint64_t k, class SequenceId> class DeBruijnGraphEdge;

    template<class Symbol, uint64_t k, class SequenceId> using DeBruijnGraphBaseClass =
        boost::adjacency_list<
        boost::listS,
        boost::listS,
        boost::bidirectionalS,
        DeBruijnGraphVertex<Symbol, k, SequenceId>,
        DeBruijnGraphEdge<Symbol, k, SequenceId>
        >;

}

template<class Symbol, uint64_t k, class SequenceId> class shasta::DeBruijnGraphVertex {
public:

    // The sequence of k symbols represented by this vertex.
    using VertexSequence = array<Symbol, k>;
    VertexSequence vertexSequence;

    // The occurrences of the k symbols represented by this vertex
    // in the input sequences.
    // The second element of each pair is the position in the sequence
    // of the first of the k symbols.
    vector< pair<SequenceId, uint64_t > > occurrences;

    uint64_t vertexId;

    DeBruijnGraphVertex(
        const VertexSequence& vertexSequence,
        uint64_t vertexId) :
        vertexSequence(vertexSequence),
        vertexId(vertexId) {}

};

template<class Symbol, uint64_t k, class SequenceId> class shasta::DeBruijnGraphEdge {
public:

    // The k-1 symbols associated with this edge.
    // This are the last k-1 symbols of the source vertex
    // and also the first k-1 symbols of the target vertex.
    using EdgeSequence = array<Symbol, k-1>;
    EdgeSequence edgeSequence;

    DeBruijnGraphEdge(const EdgeSequence& edgeSequence) :
        edgeSequence(edgeSequence) {}

};

template<class Symbol, uint64_t k, class SequenceId> class shasta::DeBruijnGraph :
    public DeBruijnGraphBaseClass<Symbol, k, SequenceId> {
public:

    using Graph = DeBruijnGraph;
    using Vertex = DeBruijnGraphVertex<Symbol, k, SequenceId>;
    using Edge = DeBruijnGraphEdge<Symbol, k, SequenceId>;
    using BaseClass = DeBruijnGraphBaseClass<Symbol, k, SequenceId>;
    using vertex_descriptor = typename BaseClass::vertex_descriptor;
    using edge_descriptor = typename BaseClass::edge_descriptor;
    using Sequence = vector<Symbol>;
    using VertexSequence = array<Symbol, k>;
    using EdgeSequence = array<Symbol, k-1>;

    // The vertices, keyed by the sequence they contain.
    std::map<VertexSequence, vertex_descriptor> vertexMap;
    uint64_t nextVertexId = 0;

    // Add a sequence to the graph.
    void addSequence(
        SequenceId sequenceId,
        const Sequence& sequence)
    {
        Graph& graph = *this;

        // Loop over possible starting positions.
        for(uint64_t startPosition=0; startPosition + k <= sequence.size(); startPosition++) {

            // Extract the k symbols starting here.
            VertexSequence vertexSequence;
            for(uint64_t i=0; i<k; i++) {
                vertexSequence[i] = sequence[startPosition + i];
            }

            // Get the corresponding vertex, creating it if necessary.
            vertex_descriptor v;
            auto it = vertexMap.find(vertexSequence);
            if(it == vertexMap.end()) {
                v = add_vertex(Vertex(vertexSequence, nextVertexId++), graph);
                vertexMap.insert(make_pair(vertexSequence, v));
            } else {
                v = it->second;
            }

            // Store this occurrence of the k symbols.
            graph[v].occurrences.push_back(make_pair(sequenceId, startPosition));
        }
    }

    void removeLowCoverageVertices(uint64_t minCoverage)
    {
        Graph& graph = *this;

        vector<vertex_descriptor> verticesTobeRemoved;
        BGL_FORALL_VERTICES_T(v, graph, Graph) {
            if(graph[v].occurrences.size() < minCoverage) {
                verticesTobeRemoved.push_back(v);
            }
        }

        for(const vertex_descriptor v: verticesTobeRemoved) {
            clear_vertex(v, graph);
            remove_vertex(v, graph);
        }

    }

    // This remove vertices with more than one occurrences of the same sequence.
    void removeAmbiguousVertices()
    {
        Graph& graph = *this;

        vector<vertex_descriptor> verticesTobeRemoved;
        vector<SequenceId> sequenceIds;
        BGL_FORALL_VERTICES_T(v, graph, Graph) {
            const auto& occurrences = graph[v].occurrences;
            sequenceIds.clear();
            for(const auto& p: occurrences) {
                sequenceIds.push_back(p.first);
            }
            sort(sequenceIds.begin(), sequenceIds.end());
            for(uint64_t i=1; i<sequenceIds.size(); i++) {
                if(sequenceIds[i-1] == sequenceIds[i]) {
                    verticesTobeRemoved.push_back(v);
                    break;
                }
            }
        }

        for(const vertex_descriptor v: verticesTobeRemoved) {
            clear_vertex(v, graph);
            remove_vertex(v, graph);
        }

    }

    // Create the edges of the De Bruijn graph.
    // This should be called after all sequences are added.
    void createEdges()
    {
        Graph& graph = *this;

        // Index the vertices by their first k-1 symbols.
        std::map<EdgeSequence, vector<vertex_descriptor> > vertexIndex;
        BGL_FORALL_VERTICES_T(v, graph, Graph) {
            const VertexSequence& vertexSequence = graph[v].vertexSequence;
            EdgeSequence edgeSequence;
            const auto begin = vertexSequence.begin();
            const auto end = begin + (k-1);
            copy(begin, end, edgeSequence.begin());
            vertexIndex[edgeSequence].push_back(v);
        }

        // Use the index to create the edges.
        BGL_FORALL_VERTICES_T(v0, graph, Graph) {
            const VertexSequence& vertexSequence0 = graph[v0].vertexSequence;
            EdgeSequence edgeSequence;
            const auto begin = vertexSequence0.begin() + 1;
            const auto end = vertexSequence0.end();
            copy(begin, end, edgeSequence.begin());

            for(const vertex_descriptor v1: vertexIndex[edgeSequence]) {
                edge_descriptor e;
                tie(e, ignore) = add_edge(v0, v1, Edge(edgeSequence), graph);
            }
        }
    }

    // Find sets of incompatible vertices.
    // A set of incompatible vertex can be described as the vertices
    // immediately preceding or following a branch in the graph.
    // In a set of incompatible vertices, at least one of the following is true:
    // - All vertices in the set have in-degree 1, and the same parent.
    // - OR: All vertices in the set have out-degree 1, and the same child.
    void findIncompatibleVertexSets(
        std::set< std::set<vertex_descriptor> >& incompatibleSets) const
    {
        const Graph& graph = *this;

        BGL_FORALL_VERTICES_T(v0, graph, Graph) {

            // Check the children.
            std::set<vertex_descriptor> children;
            if(out_degree(v0, graph) > 1) {
                bool ok = true;
                BGL_FORALL_OUTEDGES_T(v0, e, graph, Graph) {
                    const vertex_descriptor v1 = target(e, graph);
                    if(in_degree(v1, graph) != 1) {
                        ok = false;
                        break;
                    }
                    children.insert(v1);
                }
                if(ok and not isAmbiguous(children)) {
                    incompatibleSets.insert(children);
                }
            }

            // Check the parents.
            std::set<vertex_descriptor> parents;
            if(in_degree(v0, graph) > 1) {
                bool ok = true;
                BGL_FORALL_INEDGES_T(v0, e, graph, Graph) {
                    const vertex_descriptor v1 = source(e, graph);
                    if(out_degree(v1, graph) != 1) {
                        ok = false;
                        break;
                    }
                    parents.insert(v1);
                }
                if(ok and not isAmbiguous(parents)) {
                    incompatibleSets.insert(parents);
                }
            }

        }

    }

    // Remove true if a vertex set is ambiguous -
    // that is, if the same SequenceId appears more than once in
    // the vertices in the set.
    bool isAmbiguous(std::set<vertex_descriptor>& vertexSet) const
    {
        const Graph& graph = *this;

        std::set<SequenceId> alreadyEncountered;
        for(const vertex_descriptor v: vertexSet) {
            for(const auto& p: graph[v].occurrences) {
                const SequenceId sequenceId = p.first;
                if(alreadyEncountered.find(sequenceId) == alreadyEncountered.end()) {
                    alreadyEncountered.insert(sequenceId);
                } else {

                    // We already encountered this sequence.
                    return true;
                }
            }
        }

        // If we get here, we never encountered a SequenceId more than once.
        return false;
    }

    void writeGraphviz(const string& fileName) const
    {
        const Graph& graph = *this;
        ofstream s(fileName);

        s << "digraph DeBruijnGraph {\n";

        BGL_FORALL_VERTICES_T(v, graph, Graph) {
            s << graph[v].vertexId <<
                "[label=\""  <<
                graph[v].occurrences.size() <<
                "\"];\n";
        }

        BGL_FORALL_EDGES_T(e, graph, Graph) {
            const vertex_descriptor v0 = source(e, graph);
            const vertex_descriptor v1 = target(e, graph);
            s << graph[v0].vertexId << "->";
            s << graph[v1].vertexId << ";\n";
        }

        s << "}\n";

    }

    void createVertexCoverageHistogram(vector<uint64_t>& histogram) const
    {
        const Graph& graph = *this;

        histogram.clear();
        BGL_FORALL_VERTICES_T(v, graph, Graph) {
            const uint64_t coverage = graph[v].occurrences.size();
            if(coverage >= histogram.size()) {
                histogram.resize(coverage + 1, 0);
            }
            ++histogram[coverage];
        }

    }
};

#endif

#endif


#include <iostream>
#include <fstream>
#include <algorithm>
#include <numeric>

#include <boost/filesystem.hpp>

#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
namespace logging = boost::log;

#include <boost/program_options.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
using namespace boost::multiprecision;

#include <libArrhenius/version.h>
#include <libArrhenius/Arrhenius.hpp>
#include <libArrhenius/Utils/GenerateOutputFilename.hpp>

namespace po = boost::program_options;
using namespace std;

typedef cpp_dec_float_100 DataType;
typedef cpp_dec_float_100 HPDataType; // high precision data type

namespace std {
  /* std::string to_string(cpp_dec_float_100 val) */
  /* {return static_cast<std::string>( val ); } */

  /* std::string to_string(cpp_dec_float_50 val) */
  /* {return static_cast<std::string>( val ); } */
}

std::vector<std::pair<std::string,std::string>> cmds = { {"help","Print usage and exit."}
                                                       , {"calc-threshold","Calculate the threshold scaling factor for a thermal profile(s)."}
                                                       , {"calc-rate","Calculate the damage rate for a thermal profile(s)"}
                                                       , {"calc-damage","Calculate the damage parameter for a thermal profile(s)"}
                                                       , {"fit","Fit Arrhenius coefficients to a set of thermal profile data."}
                                                       };

void print_usage(std::string prog, po::options_description& opts)
{
  std::cout << "Usage: " << prog << " [global options] cmd [cmd options]\n" << std::endl;
  std::cout << opts << std::endl;
  std::cout << "Commands:" << std::endl;
  for( auto c : cmds )
  {
    std::cout << "  " << c.first << "\t\t- " << c.second << std::endl;
  }
  std::cout << std::endl;
}
void print_manual()
{
  // we are using a raw string literal here (the R"()" syntax, which is a C++11 feature.
  // it allows for a multi-line string to be written directly, instead of needing to quote
  // each line or insert \n characters.
  std::cout <<
  R"(
  Under construction.
    )"
  << std::endl;
}

void calc_threshold_help(std::string prog, std::string cmd, po::options_description& opts)
{
  std::cout << "Usage: " << prog << " [global options] "<< cmd << " ["<< cmd <<" options]\n" << std::endl;
  std::cout << opts << std::endl;
  std::cout << std::endl;
}
int calc_threshold_cmd( std::string prog, std::string cmd, std::vector<std::string> &cmd_args )
{
    po::options_description opt_options("Options");
    opt_options.add_options()
      // these are simple flag options, they do not have an argument.
      ("help,h",  "print help message.")
      ("Ea", po::value<DataType>()->default_value(6.28e5), "Activation energy.")
      ("A",  po::value<DataType>()->default_value(3.1e99), "Frequency factor.")
      ("n",  po::value<DataType>()->default_value(0), "Temperature pre-factor exponent for modified Arrhenius equation.")
      ("T0", po::value<DataType>()->default_value(0), "Offset temperature that will be added to all thermal profiles.")
      ("Omega", po::value<DataType>()->default_value(1), "Compute threshold corresponding to the value of Omega.")
      ("write-threshold-profiles,w", "Write the threshold thermal profile to disk.")
      ("output-filename,o", po::value<std::string>()->default_value("fmt:{ifn}.threshold"), "Output filename.")
      ;
    po::options_description arg_options("Arguments");
    arg_options.add_options()
      ("files"  , po::value<std::vector<std::string>>()->composing(), "Thermal profile files to analyze.") // an option that can be given multiple times with each argument getting stored in a vector.
      ;

    po::options_description all_options("Options");
    all_options.add(opt_options).add(arg_options);

    // tell boost how to translate positional options to named options
    po::positional_options_description args;
    args.add("files"  , -1);

    // now actually parse them
    po::variables_map vm;
    po::store(po::command_line_parser(cmd_args).  options(all_options).positional(args).run(), vm);
    po::notify(vm);

    if( vm.count("help") || vm.count("files") == 0 )
    {
      calc_threshold_help(prog,cmd,opt_options);
      return 0;
    }

    libArrhenius::ThresholdCalculator< libArrhenius::ModifiedArrheniusIntegral<DataType> > calc;
    calc.setA( vm["A"].as<DataType>() );
    calc.setEa( vm["Ea"].as<DataType>() );
    calc.setExponent( vm["n"].as<DataType>() );
    calc.setThresholdOmega( vm["Omega"].as<DataType>() );

    std::cout<< "filename | Omega | threshold" << std::endl;
    for( auto file : vm["files"].as<std::vector<std::string>>() )
    {
      int n;
      DataType *t, *T;

      if( !boost::filesystem::exists(file) )
        throw std::runtime_error("ERROR: '"+file+"' does not exist.");
      std::ifstream in(file.c_str());
      RUC::ReadFunction(in, t, T, n);
      in.close();
      // add offset temp
      std::transform( T, T+n, T, std::bind2nd(std::plus<DataType>(), vm["T0"].as<DataType>()) );

      auto Omega = calc.Omega(n,t,T);
      auto Threshold = calc(n,t,T);

      std::cout << file << " | " << Omega << " | " << Threshold << std::endl;

      if( vm.count("write-threshold-profiles") )
      {
        DataType *TT = new DataType[n];
        for(size_t i = 0; i < n; ++i)
          TT[i] = Threshold*(T[i] - T[0]) + T[0];

        std::string ofn = RUC::GenerateOutputFilename(file,vm["output-filename"].as<std::string>());
        std::ofstream out( ofn );
        for(size_t i = 0; i < n; i++)
          out << t[i] << " " << TT[i] << "\n";
        out.close();

        delete[] TT;

      }

      delete[] t;
      delete[] T;
    }

    return 0;

}

void calc_rate_help(std::string prog, std::string cmd, po::options_description& opts)
{
  std::cout << "Usage: " << prog << " [global options] "<< cmd << " ["<< cmd <<" options]\n" << std::endl;
  std::cout << opts << std::endl;
  std::cout << "Use this command to calculate some statistics on the damage rate for a set of thermal profiles." << std::endl;
  std::cout << "For example, this command will print the minimum, maximum, and average damage rates." << std::endl;
  std::cout << "If the --write-rate-profiles option is given, the damage rate at each point in the thermal profile will be written to file." << std::endl;
  std::cout << std::endl;
}
int calc_rate_cmd( std::string prog, std::string cmd, std::vector<std::string> &cmd_args )
{
    po::options_description opt_options("Options");
    opt_options.add_options()
      // these are simple flag options, they do not have an argument.
      ("help,h",  "print help message.")
      ("Ea", po::value<DataType>()->default_value(6.28e5), "Activation energy.")
      ("A",  po::value<DataType>()->default_value(3.1e99), "Frequency factor.")
      ("n",  po::value<DataType>()->default_value(0), "Temperature pre-factor exponent for modified Arrhenius equation.")
      ("T0", po::value<DataType>()->default_value(0), "Offset temperature that will be added to all thermal profiles.")
      ("log", "Calculate the log of the rate instead.")
      ("output-filename,o", po::value<std::string>()->default_value("fmt:{ifn}.rate"), "Output filename.")
      ("write-rate-profiles,w", "Write the rate profile to disk.")
      ;
    po::options_description arg_options("Arguments");
    arg_options.add_options()
      ("files"  , po::value<std::vector<std::string>>()->composing(), "Thermal profile files to analyze.") // an option that can be given multiple times with each argument getting stored in a vector.
      ;

    po::options_description all_options("Options");
    all_options.add(opt_options).add(arg_options);

    // tell boost how to translate positional options to named options
    po::positional_options_description args;
    args.add("files"  , -1);

    // now actually parse them
    po::variables_map vm;
    po::store(po::command_line_parser(cmd_args).  options(all_options).positional(args).run(), vm);
    po::notify(vm);

    if( vm.count("help") || vm.count("files") == 0 )
    {
      calc_rate_help(prog,cmd,opt_options);
      return 0;
    }

    libArrhenius::ModifiedArrheniusIntegral<DataType> integrate;
    integrate.setA( vm["A"].as<DataType>() );
    integrate.setEa( vm["Ea"].as<DataType>() );
    integrate.setExponent( vm["n"].as<DataType>() );

    for( auto file : vm["files"].as<std::vector<std::string>>() )
    {
      int n;
      DataType *t, *T;

      if( !boost::filesystem::exists(file) )
        throw std::runtime_error("ERROR: '"+file+"' does not exist.");
      std::ifstream in(file.c_str());
      RUC::ReadFunction(in, t, T, n);
      in.close();
      // add offset temp
      std::transform( T, T+n, T, std::bind2nd(std::plus<DataType>(), vm["T0"].as<DataType>()) );

      // just reuse temperature array for rate
      for(int i = 0; i < n; i++)
      {
        if( vm.count("log") )
          T[i] = log(integrate.rate(T[i]));
        else
          T[i] = integrate.rate(T[i]);
      }

      std::cout << "file: " << file << std::endl;
      // min rate
      std::cout << "min rate: " << *std::min_element(T, T+n) << std::endl;
      // max rate
      std::cout << "max rate: " << *std::max_element(T, T+n) << std::endl;
      // avg rate
      std::cout << "avg rate: " << std::accumulate(T, T+n, DataType(0))/n << std::endl;
      // critical temperature
      DataType Tcrit = integrate.getCriticalTemperature();
      std::cout << "T crit: " <<  Tcrit << std::endl;
      // time above critical temp
      DataType taucrit = 0;
      for(int i = 1; i < n; i++)
      {
        if( (T[i] + T[i-1])/2 > 1 )
          taucrit += t[i] - t[i-1];
      }
      std::cout << "tau crit: " <<  taucrit<< std::endl;

      std::cout << std::endl;

      if( vm.count("write-rate-profiles") )
      {
        std::string ofn = RUC::GenerateOutputFilename(file,vm["output-filename"].as<std::string>());
        std::ofstream out( ofn );
        for(size_t i = 0; i < n; i++)
          out << t[i] << " " << T[i] << "\n";
        out.close();
      }

      delete[] t;
      delete[] T;
    }

    return 0;

}

void calc_damage_help(std::string prog, std::string cmd, po::options_description& opts)
{
  std::cout << "Usage: " << prog << " [global options] "<< cmd << " ["<< cmd <<" options]\n" << std::endl;
  std::cout << opts << std::endl;
  std::cout << "Use this command to evaluate the Arrhenius integral for on a set of thermal profiles." << std::endl;
  std::cout << "If the --write-damage-profiles option is given, the damage accumulation for each time point in the profile," << std::endl;
  std::cout << "i.e. Omega(t), will be written to a file." << std::endl;
  std::cout << std::endl;
}
int calc_damage_cmd( std::string prog, std::string cmd, std::vector<std::string> &cmd_args )
{
    po::options_description opt_options("Options");
    opt_options.add_options()
      // these are simple flag options, they do not have an argument.
      ("help,h",  "print help message.")
      ("Ea", po::value<DataType>()->default_value(6.28e5), "Activation energy.")
      ("A",  po::value<DataType>()->default_value(3.1e99), "Frequency factor.")
      ("n",  po::value<DataType>()->default_value(0), "Temperature pre-factor exponent for modified Arrhenius equation.")
      ("T0", po::value<DataType>()->default_value(0), "Offset temperature that will be added to all thermal profiles.")
      ("output-filename,o", po::value<std::string>()->default_value("fmt:{ifn}.damage"), "Output filename.")
      ("write-damage-profiles,w", "Write the damage profiles to disk.")
      ;
    po::options_description arg_options("Arguments");
    arg_options.add_options()
      ("files"  , po::value<std::vector<std::string>>()->composing(), "Thermal profile files to analyze.") // an option that can be given multiple times with each argument getting stored in a vector.
      ;

    po::options_description all_options("Options");
    all_options.add(opt_options).add(arg_options);

    // tell boost how to translate positional options to named options
    po::positional_options_description args;
    args.add("files"  , -1);

    // now actually parse them
    po::variables_map vm;
    po::store(po::command_line_parser(cmd_args).  options(all_options).positional(args).run(), vm);
    po::notify(vm);

    if( vm.count("help") || vm.count("files") == 0 )
    {
      calc_damage_help(prog,cmd,opt_options);
      return 0;
    }

    libArrhenius::ModifiedArrheniusIntegral<DataType> integrate;
    integrate.setA( vm["A"].as<DataType>() );
    integrate.setEa( vm["Ea"].as<DataType>() );
    integrate.setExponent( vm["n"].as<DataType>() );

    std::cout<< "filename | Omega" << std::endl;
    for( auto file : vm["files"].as<std::vector<std::string>>() )
    {
      int n;
      DataType *t, *T;

      if( !boost::filesystem::exists(file) )
        throw std::runtime_error("ERROR: '"+file+"' does not exist.");
      std::ifstream in(file.c_str());
      RUC::ReadFunction(in, t, T, n);
      in.close();
      // add offset temp
      std::transform( T, T+n, T, std::bind2nd(std::plus<DataType>(), vm["T0"].as<DataType>()) );

      auto Omega = integrate(n,t,T);
      std::cout << file << " | " << Omega << std::endl;

      if( vm.count("write-damage-profiles") )
      {
        std::string ofn = RUC::GenerateOutputFilename(file,vm["output-filename"].as<std::string>());
        std::ofstream out( ofn );
        // this is NOT efficient
        for(size_t i = 0; i < n; i++)
          out << t[i] << " " << integrate(i,t,T) << "\n";
        out.close();
      }

      delete[] t;
      delete[] T;
    }

    return 0;

}

void fit_help(std::string prog, std::string cmd, po::options_description& opts)
{
  std::cout << "Usage: " << prog << " [global options] "<< cmd << " ["<< cmd <<" options]\n" << std::endl;
  std::cout << opts << std::endl;
  std::cout << std::endl;
}
int fit_cmd( std::string prog, std::string cmd, std::vector<std::string> &cmd_args )
{
    po::options_description opt_options("Options");
    opt_options.add_options()
      // these are simple flag options, they do not have an argument.
      ("help,h",  "print help message.")
      ("methods,m"  , po::value<std::vector<std::string>>()->composing(), "List of fitting methods to use. Coefficients for each method will be printed.")
      ("list-methods,l",  "print list of available fit methods.")
      ("T0", po::value<HPDataType>()->default_value(0), "Offset temperature (in K) that will be added to all thermal profiles.")
      ("T0-uncertainty", po::value<HPDataType>(), "Uncertainty in baseline temperature (in K).")
      ("dT-uncertainty", po::value<HPDataType>(), "Uncertainty in temperature rise (in K).")
      ("Ea-min", po::value<HPDataType>(), "Minimum bound on Ea for methods that perform a search.")
      ("Ea-max", po::value<HPDataType>(), "Maximum bound on Ea for methods that perform a search.")
      ;
    po::options_description arg_options("Arguments");
    arg_options.add_options()
      ("files"  , po::value<std::vector<std::string>>()->composing(), "Thermal profile (in K vs. s) files to fit. These are ASSUMED to be threshold profiles.")
      ;

    po::options_description all_options("Options");
    all_options.add(opt_options).add(arg_options);

    // tell boost how to translate positional options to named options
    po::positional_options_description args;
    args.add("files"  , -1);

    // now actually parse them
    po::variables_map vm;
    po::store(po::command_line_parser(cmd_args).  options(all_options).positional(args).run(), vm);
    po::notify(vm);

    std::vector<std::pair<std::string,std::string>> supported_methods = {
     {"Minimize log(A) Variance and Scaling Factors"," - Finds Ea that minimizes variance in log(A), then finds A that minimizes required scaling factor errors."}
    ,{"Effective Exposures","                          - Computes an 'effective exposure' for each thermal profile and performs the standard linear regression method on them."}
    };

    if( vm.count("list-methods") )
    {
      std::cout << "\n\tMethods:\n" << std::endl;
      for( auto m : supported_methods )
      {
        std::cout << "\t\t" << m.first << m.second << std::endl;
      }
      return 0;
    }

    if( vm.count("help") || vm.count("files") == 0 )
    {
      calc_threshold_help(prog,cmd,opt_options);
      return 0;
    }

    // read in thermal profiles
    std::cout << "Loading thermal profiles." << std::endl;
    std::vector<std::shared_ptr<HPDataType>> ts,Ts;
    std::vector<size_t> Ns;
    for( auto file : vm["files"].as<std::vector<std::string>>() )
    {
      int n;
      HPDataType *t, *T;

      if( !boost::filesystem::exists(file) )
        throw std::runtime_error("ERROR: '"+file+"' does not exist.");
      std::ifstream in(file.c_str());
      RUC::ReadFunction(in, t, T, n);
      in.close();
      // add offset temp
      std::transform( T, T+n, T, std::bind2nd(std::plus<HPDataType>(), vm["T0"].as<HPDataType>()) );

      // we need to wrap these in shared_ptr's so we don't leak memory
      Ns.push_back(n);
      ts.push_back( std::shared_ptr<HPDataType>( t ) );
      Ts.push_back( std::shared_ptr<HPDataType>( T ) );

    }

    std::vector<std::string> methods;
    if( vm.count("methods") )
    {
      for( auto m : vm["methods"].as<std::vector<std::string>>() )
        methods.push_back(m);
    }
    else
    {
      for( auto m : supported_methods )
        methods.push_back(m.first);
    }

    // loop through various fit methods
    for( auto m : methods )
    {
      std::transform( m.begin(), m.end(), m.begin(), ::tolower);

      std::cout << "Running " << m << " method." << std::endl;

      typedef libArrhenius::ArrheniusFitInterface<HPDataType>::Return Coeffs;
      Coeffs coefficients;
      Coeffs coefficients_err;

      std::shared_ptr< libArrhenius::ArrheniusFitInterface< HPDataType> > fit;

      // support aliases
      if(m == "minimize log(a) variance and scaling factors")
        m = "clark";

      if(m == "effective exposures")
        m = "denton";

      if(m == "effective exposures linear regression")
        m = "denton";

      // get the correct fitter
      if(m == "clark")
        fit.reset( new libArrhenius::ArrheniusFit< HPDataType, libArrhenius::MinimizeLogAVarianceAndScalingFactors >());

      if(m == "denton")
        fit.reset( new libArrhenius::ArrheniusFit< HPDataType, libArrhenius::EffectiveExposuresLinearRegression>());

      // now fit the profiles
      for( int i = 0; i < Ns.size(); ++i )
        fit->addProfile( Ns[i], ts[i].get(), Ts[i].get() );

      if(vm.count("Ea-min"))
        fit->setMinEa( vm["Ea-min"].as<HPDataType>() );
      if(vm.count("Ea-max"))
        fit->setMaxEa( vm["Ea-max"].as<HPDataType>() );

      coefficients = fit->exec();

      std::vector< Coeffs > errors;

      // propagate error for uncertainty in baseline temperature if given
      if( vm.count("T0-uncertainty") )
      {
        fit->clear();
        // add uncertainty to the thermal profiles and add them to the fitter
        for( int i = 0; i < Ns.size(); ++i )
        {
          std::transform( Ts[i].get(), Ts[i].get()+Ns[i], Ts[i].get(), std::bind2nd(std::plus<HPDataType>(), vm["T0-uncertainty"].as<HPDataType>()) );
          fit->addProfile( Ns[i], ts[i].get(), Ts[i].get() );
        }

        errors.push_back(fit->exec());

        // subtract the uncertainty back off the thermal profiles
        for( int i = 0; i < Ns.size(); ++i )
          std::transform( Ts[i].get(), Ts[i].get()+Ns[i], Ts[i].get(), std::bind2nd(std::minus<HPDataType>(), vm["T0-uncertainty"].as<HPDataType>()) );
      }

      // propagate error for peak temperature rise if given
      if( vm.count("dT-uncertainty") )
      {
        fit->clear();
        // add uncertainty to the thermal profiles and add them to the fitter
        for( int i = 0; i < Ns.size(); ++i )
        {
          HPDataType Tmax = *std::max_element( Ts[i].get(), Ts[i].get() + Ns[i] );
          HPDataType scale = 1 + vm["dT-uncertainty"].as<HPDataType>()/(Tmax - Ts[i].get()[0]);
          for( int j = 0; j < Ns[i]; j++)
            Ts[i].get()[j] = Ts[i].get()[0] + scale*(Ts[i].get()[j] - Ts[i].get()[0]);

          fit->addProfile( Ns[i], ts[i].get(), Ts[i].get() );
        }

        errors.push_back(fit->exec());

        // subtract the uncertainty back off the thermal profiles
        for( int i = 0; i < Ns.size(); ++i )
        {
          HPDataType Tmax = *std::max_element( Ts[i].get(), Ts[i].get() + Ns[i] );
          HPDataType scale = 1 - vm["dT-uncertainty"].as<HPDataType>()/(Tmax - Ts[i].get()[0]);
          for( int j = 0; j < Ns[i]; j++)
            Ts[i].get()[j] = Ts[i].get()[0] + scale*(Ts[i].get()[j] - Ts[i].get()[0]);
        }
      }

      // compute total error
      coefficients_err.A = 0;
      coefficients_err.Ea = 0;
      for( auto e : errors )
      {
        auto dA  = e.A.get() - coefficients.A.get();
        auto dEa = e.Ea.get() - coefficients.Ea.get();
        coefficients_err.A  = coefficients_err.A.get()  + dA*dA;
        coefficients_err.Ea = coefficients_err.Ea.get() + dEa*dEa;
      }
      coefficients_err.A  = sqrt(coefficients_err.A.get());
      coefficients_err.Ea = sqrt(coefficients_err.Ea.get());

      std::cout << "A: " << coefficients.A.get()   << " +/- " << coefficients_err.A.get() << std::endl;
      std::cout << "Ea: " << coefficients.Ea.get() << " +/- " << coefficients_err.Ea.get() << std::endl;

      std::cout<< "filename | Omega | threshold" << std::endl;

      // todo: should we add support for modified arrhenius?
      libArrhenius::ThresholdCalculator< libArrhenius::ArrheniusIntegral<DataType> > calc;
      calc.setA( coefficients.A.get() );
      calc.setEa( coefficients.Ea.get() );
      //calc.setExponent( vm["n"].as<DataType>() );
      // what about support for omega != 1?
      //calc.setThresholdOmega( vm["Omega"].as<DataType>() );
      std::vector<HPDataType> Thresholds(Ns.size());
      for( int i = 0; i < vm["files"].as<std::vector<std::string>>().size(); i++ )
      {
        auto file = vm["files"].as<std::vector<std::string>>()[i];
        auto Omega = calc.Omega(Ns[i],ts[i].get(),Ts[i].get());
        auto Threshold = calc(Ns[i],ts[i].get(),Ts[i].get());
        std::cout << file << " | " << Omega << " | " << Threshold << std::endl;
        Thresholds[i] = Threshold;
      }
      // calc sum of residuals
      std::cout << "R^2: " << std::accumulate(Thresholds.begin(), Thresholds.end(), HPDataType(0), []( HPDataType a, HPDataType b ){ return HPDataType(a + (b-1)*(b-1)); } ) << std::endl;

      std::cout << std::endl;

    }

    return 0;

}

int main(int argc, const char** argv)
{
    std::vector<std::string> global_args;
    std::vector<std::string> cmd_args;

    bool cmd_found = false;
    for( int i = 1; i < argc; i++ )
    {
      std::string arg( argv[i] );
      if(!cmd_found)
        global_args.push_back( arg );

      if(cmd_found)
        cmd_args.push_back( arg );

      for( auto c : cmds )
      {
        if( c.first == arg )
        {
          cmd_found = true;
          break;
        }
      }
    }

    // setup command line parser for the global arguments.
    //
    // note that boost is a little different...
    // we usually say that a command line program takes options and arguments.
    // boost calls arguments "positional options", which are just options that can
    // be given without their option name. to create the argument, we define it as
    // an option first, and then tell boost that it is "positional"
    //
    // so to define our command line interface
    // we declare options and arguments that our application will accept.

    // define our regular options. these will be given with a '--' or '-' in front. '--' is used
    // for the long name, '-' is used for the short name.
    po::options_description opt_options("Options");
    opt_options.add_options()
      // these are simple flag options, they do not have an argument.
      ("help,h",  "print help message.")
      ("version", "print library version.")
      ("manual",  "print manual.")
      ("verbose,v", po::value<int>()->default_value(0), "verbose level.") // an option that takes an argument, but has a default value.
      ;

    // now define our arguments.
    po::options_description arg_options("Arguments");
    arg_options.add_options()
      ("command"  , po::value<std::string>()->default_value("help"),   "Subcommand.");

    // combine the options and arguments into one option list.
    // this is what we will use to parse the command line, but
    // when we output a description of the options, we will just use
    // opt_options
    po::options_description all_options("Options");
    all_options.add(opt_options).add(arg_options);

    // tell boost how to translate positional options to named options
    po::positional_options_description args;
    args.add("command", 1);

    // if no subcommand was found, we need to print the help before the parser
    // tries to parse because it will likely just fail.
    // note that we can't do this above because we need the options descriptor
    if( !cmd_found )
    {
      // print out a usage statement and summary of command line options
      std::cout << "ERROR: No subcommand found." << std::endl;
      print_usage(argv[0],opt_options);
      return 1;
    }

    po::variables_map vm;
    po::store(po::command_line_parser(global_args).  options(all_options).positional(args).run(), vm);
    po::notify(vm);

    // boiler plate stuff

    if( vm.count("help") || vm["command"].as<std::string>() == "help" )
    {
      // print out a usage statement and summary of command line options
      print_usage(argv[0],opt_options);
      return 0;
    }

    if( vm.count("manual") )
    {
      // print the manual
      print_manual();
      return 0;
    }

    if( vm.count("version") )
    {
      // print the version number for the library
      std::cout << "libArrhenius "<<libArrhenius_VERSION_FULL << std::endl;
      return 0;
    }

    logging::core::get()->set_filter( logging::trivial::severity >= logging::trivial::error );
    if( vm["verbose"].as<int>() > 0 )
      logging::core::get()->set_filter( logging::trivial::severity >= logging::trivial::warning );
    if( vm["verbose"].as<int>() > 1 )
      logging::core::get()->set_filter( logging::trivial::severity >= logging::trivial::info );
    if( vm["verbose"].as<int>() > 2 )
      logging::core::get()->set_filter( logging::trivial::severity >= logging::trivial::debug );
    if( vm["verbose"].as<int>() > 3 )
      logging::core::get()->set_filter( logging::trivial::severity >= logging::trivial::trace );

    if( vm["command"].as<std::string>() == "calc-threshold" )
      return calc_threshold_cmd( argv[0], "calc-threshold", cmd_args );

    if( vm["command"].as<std::string>() == "calc-rate" )
      return calc_rate_cmd( argv[0], "calc-rate", cmd_args );

    if( vm["command"].as<std::string>() == "calc-damage" )
      return calc_damage_cmd( argv[0], "calc-damage", cmd_args );

    if( vm["command"].as<std::string>() == "fit" )
      return fit_cmd( argv[0], "fit", cmd_args );

    return 0;
}