/**
 * @author Andre Anjos <andre.anjos@idiap.ch>
 * @date Sun  4 Mar 10:02:45 2012 CET
 *
 * @brief Implementation of the SVM training methods
 *
 * Copyright (C) 2011-2014 Idiap Research Institute, Martigny, Switzerland
 */

#include <bob.learn.libsvm/trainer.h>
#include <boost/format.hpp>
#include <boost/make_shared.hpp>
#include <bob.core/logging.h>

#ifdef BOB_DEBUG
//remove newline
#include <boost/algorithm/string/trim.hpp>
static std::string strip(const char* s) {
  std::string t(s);
  boost::algorithm::trim(t);
  return t;
}
#endif

static void debug_libsvm(const char* s) {
  TDEBUG1("[libsvm-" << LIBSVM_VERSION << "] " << strip(s));
}

bob::learn::libsvm::Trainer::Trainer(
    bob::learn::libsvm::machine_t machine_type,
    bob::learn::libsvm::kernel_t kernel_type,
    double cache_size,
    double eps,
    bool shrinking,
    bool probability
    )
{
  m_param.svm_type = machine_type;
  m_param.kernel_type = kernel_type;
  m_param.degree = 3;
  m_param.gamma = 0.;
  m_param.coef0 = 0.;
  m_param.cache_size = cache_size;
  m_param.eps = eps;
  m_param.C = 1;
  m_param.nu = 0.5;
  m_param.p = 0.1;
  m_param.shrinking = shrinking;
  m_param.probability = probability;

  //extracted from the data
  m_param.nr_weight = 0;
  m_param.weight_label = 0;
  m_param.weight = 0;
}

bob::learn::libsvm::Trainer::~Trainer() { }

/**
 * Erases an SVM problem:
 *
 * struct svm_problem {
 *   int l; //number of entries
 *   double* y; //labels
 *   svm_node** x; //each set terminated with a -1 index entry
 * };
 *
 * At svm-train the nodes for each entry are allocated globally, what is
 * probably more efficient from the allocation perspective. It still requires
 * libsvm to scan the data twice to understand how many nodes need to be
 * allocated globally.
 */
static void delete_problem(svm_problem* p) {
  delete[] p->y; //all labels
  delete[] p->x[0]; //all entries
  delete[] p->x; //entry pointers
  delete p;
}

/**
 * Allocates an svm_problem matrix
 */
static svm_problem* new_problem(size_t entries) {
  svm_problem* retval = new svm_problem;
  retval->l = (int)entries;
  retval->y = new double[entries];
  typedef svm_node* svm_node_ptr;
  retval->x = new svm_node_ptr[entries];
  for (size_t k=0; k<entries; ++k) retval->x[k] = 0;
  return retval;
}

/**
 * Converts the input arrayset data into an svm_problem matrix, used by libsvm
 * training routines. Updates "gamma" at the svm_parameter's.
 */
static boost::shared_ptr<svm_problem> data2problem
(const std::vector<blitz::Array<double, 2> >& data,
 const blitz::Array<double,1>& sub, const blitz::Array<double,1>& div,
 svm_parameter& param) {

  //counts the number of samples required
  size_t entries = 0;
  for (size_t k=0; k<data.size(); ++k)
    entries += data[k].extent(blitz::firstDim);

  //allocates the container that will represent the problem; at this stage, we
  //allocate entries for each vector, but not the space in which feature will
  //be put at. This will come next.
  boost::shared_ptr<svm_problem> problem(new_problem(entries),
      std::ptr_fun(delete_problem));

  //choose labels.
  if(param.svm_type==ONE_CLASS)
  {
    if ((data.size() != 1)) {
      boost::format m("Only support a singular entry for one class. Your are training ONE_CLASS svm classifier. You passed me a list of %d arraysets.");
      m % data.size();
      throw std::runtime_error(m.str());
    }
  }
  else {
    if ((data.size() <= 1) | (data.size() > 16)) {
      boost::format m("Only supports SVMs for binary or multi-class classification problems (up to 16 classes). You passed me a list of %d arraysets.");
      m % data.size();
      throw std::runtime_error(m.str());
    }
  }

  std::vector<double> labels;
  labels.reserve(data.size());
  if (data.size() == 1) {
    //oc-svm only support one class.
    labels.push_back(+1.);
  }
  else if (data.size() == 2) {
    //keep libsvm ordering
    labels.push_back(+1.);
    labels.push_back(-1.);
  }
  else { //data.size() == 3, 4, ..., 16
    for (size_t k=0; k<data.size(); ++k) labels.push_back(k+1);
  }

  //just count how many nodes we need; unfortunately we have no other choice
  //than doing a 2-pass instantiation here as libsvm has a very weird way to
  //optimize data access in which it requires all nodes to be allocated in a
  //single shot.
  size_t nodes = 0; //total number of nodes to be allocated
  blitz::Range all=blitz::Range::all();
  int n_features = data[0].extent(blitz::secondDim);
  blitz::Array<double,1> d(n_features); //for temporary feature manipulation

  for (size_t k=0; k<data.size(); ++k) {
    for (int i=0; i<data[k].extent(blitz::firstDim); ++i) {
      d = (data[k](i,all)-sub)/div; //eval and copy in 1 instruction
      for (int p=0; p<d.extent(blitz::firstDim); ++p) {
        if (d(p)) {
          ++nodes;
        }
      }
      ++nodes; //one extra for the termination node "index == -1"
    }
  }

  //allocates all the nodes, set first entry, a la libsvm
  svm_node* all_nodes = new svm_node[nodes];

  //iterates over each class data and fills the svm_node's
  int max_index = 0; //data width
  size_t sample = 0; //sample counter
  size_t node = 0; //node counter

  for (size_t k=0; k<data.size(); ++k) {
    for (int i=0; i<data[k].extent(blitz::firstDim); ++i) {
      problem->x[sample] = &all_nodes[node]; //setup current sample base pointer
      d = (data[k](i,all)-sub)/div; //eval and copy in 1 instruction
      for (blitz::sizeType p=0; p<d.size(); ++p) {
        if (d(p)) {
          int index = p+1; //starts indexing at 1
          all_nodes[node].index = index;
          all_nodes[node].value = d(p);
          if ( index > max_index ) max_index = index;
          ++node; //index within the current sample
        }
      }
      //marks end of sequence
      all_nodes[node].index = -1;
      all_nodes[node].value = 0;
      problem->y[sample] = labels[k];
      ++node;
      ++sample;
    }
  }

  //extracted from svm-train.c
  if (param.gamma == 0. && max_index > 0) {
    param.gamma = 1.0/max_index;
  }

  //do not support pre-computed kernels...
  if (param.kernel_type == PRECOMPUTED) {
    throw std::runtime_error("We currently dod not support PRECOMPUTED kernels in these bindings to libsvm");
  }

  return problem;
}

/**
 * A wrapper, to standardize the freeing of the svm_model
 */
static void svm_model_free(svm_model*& m) {
#if LIBSVM_VERSION >= 300
  svm_free_and_destroy_model(&m);
#else
  svm_destroy_model(m);
#endif
}

bob::learn::libsvm::Machine* bob::learn::libsvm::Trainer::train
(const std::vector<blitz::Array<double, 2> >& data,
 const blitz::Array<double,1>& input_subtraction,
 const blitz::Array<double,1>& input_division) const {

  //sanity check of input arraysets
  int n_features = data[0].extent(blitz::secondDim);

  for (size_t cl=0; cl<data.size(); ++cl) {
    if (data[cl].extent(blitz::secondDim) != n_features) {
      boost::format m("number of features (columns) of array for class %u (%d) does not match that of array for class 0 (%d)");
      m % cl % data[cl].extent(blitz::secondDim) % n_features;
      throw std::runtime_error(m.str());
    }
  }

  //converts the input arraysets into something libsvm can digest
  double save_gamma = m_param.gamma; ///< the next method may update it!
  boost::shared_ptr<svm_problem> problem =
    data2problem(data, input_subtraction, input_division,
        const_cast<svm_parameter&>(m_param) ///< temporary cast
        );

  //checks parametrization to make sure all is alright.
  const char* error_msg = svm_check_parameter(problem.get(), &m_param);

  if (error_msg) {
    const_cast<double&>(m_param.gamma) = save_gamma;
    boost::format m("libsvm-%d reports: %s");
    m % libsvm_version % error_msg;
    std::runtime_error(m.str());
  }

  //do the training, returns the new machine
#if LIBSVM_VERSION >= 291
  svm_set_print_string_function(debug_libsvm);
#else
  boost::format m("libsvm-%d does not support debugging stream setting");
  m % libsvm_version;
  debug_libsvm(m.str().c_str());
#endif
  boost::shared_ptr<svm_model> model(svm_train(problem.get(), &m_param),
      std::ptr_fun(svm_model_free));

  const_cast<double&>(m_param.gamma) = save_gamma;

  //save newly created machine to file, reload from there to get rid of memory
  //dependencies due to the poorly implemented memory model in libsvm
  boost::shared_ptr<svm_model> new_model =
    bob::learn::libsvm::svm_unpickle(bob::learn::libsvm::svm_pickle(model));

  auto retval = new bob::learn::libsvm::Machine(new_model);

  //sets up the scaling parameters given as input
  retval->setInputSubtraction(input_subtraction);
  retval->setInputDivision(input_division);

  return retval;
}

bob::learn::libsvm::Machine* bob::learn::libsvm::Trainer::train
(const std::vector<blitz::Array<double,2> >& data) const {
  int n_features = data[0].extent(blitz::secondDim);

  blitz::Array<double,1> sub(n_features);
  sub = 0.;
  blitz::Array<double,1> div(n_features);
  div = 1.;
  return train(data, sub, div);
}


/*!
 * @file
 * Defines `boost::mpl11::detail::count_if::variadic_sizeof`.
 *
 *
 * @copyright Louis Dionne 2014
 * Distributed under the Boost Software License, Version 1.0.
 *         (See accompanying file LICENSE.md or copy at
 *             http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_MPL11_DETAIL_COUNT_IF_VARIADIC_SIZEOF_HPP
#define BOOST_MPL11_DETAIL_COUNT_IF_VARIADIC_SIZEOF_HPP

#include <boost/mpl11/detail/std_index_sequence.hpp>
#include <boost/mpl11/detail/std_size_t.hpp>
#include <boost/mpl11/integer.hpp>

namespace boost { namespace mpl11 { namespace detail { namespace count_if {
    template <std_size_t, bool> struct count_helper { };
    template <std_size_t n> struct count_helper<n, true> { char c[10]; };

    template <
        template <typename ...> class pred,
        typename index,
        typename ...xs>
    struct sizeof_impl;

    template <
        template <typename ...> class pred,
        std_size_t ...index,
        typename ...xs>
    struct sizeof_impl<pred, std_index_sequence<index...>, xs...>
        : count_helper<index, (bool)pred<xs>::type::value>...
    { };

    /*!
     * @ingroup details
     *
     * `count_if` algorithm using a trick with the `sizeof` operator.
     */
    template <template <typename ...> class predicate, typename ...xs>
    using variadic_sizeof = size_t<sizeof...(xs) == 0 ? 0 :
        sizeof(
            sizeof_impl<
                predicate,
                typename make_std_index_sequence<sizeof...(xs)>::type,
                xs...
            >
        ) / sizeof(count_helper<0, true>)
    >;
}}}} // end namespace boost::mpl11::detail::count_if

#endif // !BOOST_MPL11_DETAIL_COUNT_IF_VARIADIC_SIZEOF_HPP


// Copyright (c) 2015 Elements of Programming Interviews. All rights reserved.

#include <boost/thread.hpp>
#include <boost/thread/sync_bounded_queue.hpp>
#include <cassert>
#include <chrono>
#include <chrono>
#include <functional>
#include <iostream>
#include <string>
#include <unordered_set>

using std::stoi;
using std::bind;
using std::ref;
using std::pair;
using std::make_pair;
using std::unordered_set;
using std::cout;
using std::endl;
using std::chrono::system_clock;
using std::chrono::milliseconds;
using std::chrono::duration_cast;
using boost::thread_group;
using boost::sync_bounded_queue;
using boost::queue_op_status;

typedef unsigned long long CollatzInt;
typedef sync_bounded_queue<pair<CollatzInt, CollatzInt>> QueueType;
void Worker(CollatzInt lower, CollatzInt upper);
bool CollatzCheck(CollatzInt x, unordered_set<CollatzInt>& visited);

// @include
void ThreadFunc(QueueType& q) {
  pair<CollatzInt, CollatzInt> args;
  while (q.wait_pull_front(args) == queue_op_status::success) {
    Worker(args.first, args.second);
  }
}

// Performs basic unit of work
void Worker(CollatzInt lower, CollatzInt upper) {
  for (CollatzInt i = lower; i <= upper; ++i) {
    assert(CollatzCheck(i, unordered_set<CollatzInt>()));
  }
  cout << '(' << lower << ',' << upper << ')' << endl;
}

// Checks an individual number
bool CollatzCheck(CollatzInt x, unordered_set<CollatzInt>& visited) {
  if (x == 1) {
    return true;
  }
  if (!visited.emplace(x).second) {
    return false;
  }
  if (x & 1) {  // odd number
    return CollatzCheck(3 * x + 1, visited);
  } else {  // even number
    return CollatzCheck(x >> 1, visited);  // divide by 2
  }
}
// @exclude

// @include

int main(int argc, char* argv[]) {
  CollatzInt N = 10000000;
  CollatzInt RANGESIZE = 1000000;
  int NTHREADS = 4;
  // @exclude
  if (argc > 1) {
    N = stoi(argv[1]);
  }
  if (argc > 2) {
    RANGESIZE = stoi(argv[2]);
  }
  if (argc > 3) {
    NTHREADS = stoi(argv[3]);
  }

  assert(CollatzCheck(1, unordered_set<CollatzInt>()));
  assert(CollatzCheck(3, unordered_set<CollatzInt>()));
  assert(CollatzCheck(8, unordered_set<CollatzInt>()));
  auto start_time = system_clock::now();

  // @include
  // Uses synchronized bounded queue for task assignment and load balancing
  QueueType q(NTHREADS);
  thread_group threads;
  for (int i = 0; i < NTHREADS; ++i) {
    threads.create_thread(bind(ThreadFunc, ref(q)));
  }
  for (CollatzInt i = 0; i < N / RANGESIZE; ++i) {
    q << make_pair(i * RANGESIZE + 1, (i + 1) * RANGESIZE);
  }
  q.close();
  threads.join_all();

  // @exclude
  cout << "Finished all threads" << endl;
  auto running_time =
      duration_cast<milliseconds>(system_clock::now() - start_time).count();
  cout << "time in milliseconds for checking to " << N << " is "
       << running_time << '(' << N / running_time << " per ms)";

  // @include
  return 0;
}
// @exclude


#define BOOST_TEST_MODULE pcraster dal raster_dimensions
#include <boost/test/unit_test.hpp>
#include "dal_MathUtils.h"
#include "dal_RasterDimensions.h"

BOOST_AUTO_TEST_CASE(test_)
{
  using namespace dal;

  // Default.
  {
    RasterDimensions raster;

    BOOST_CHECK(dal::comparable(raster.cellSize(), 1.0));
    BOOST_CHECK(dal::comparable(raster.west(), 0.0));
    BOOST_CHECK(dal::comparable(raster.north(), 0.0));
    BOOST_CHECK(dal::comparable(raster.east(), 1.0));
    BOOST_CHECK(dal::comparable(raster.south(), -1.0));
  }

  // Non-default.
  {
    RasterDimensions raster(3, 4);

    BOOST_CHECK(dal::comparable(raster.cellSize(), 1.0));
    BOOST_CHECK(dal::comparable(raster.west(), 0.0));
    BOOST_CHECK(dal::comparable(raster.north(), 0.0));
    BOOST_CHECK(dal::comparable(raster.east(), 4.0));
    BOOST_CHECK(dal::comparable(raster.south(), -3.0));
  }

  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);

    BOOST_CHECK(dal::comparable(raster.cellSize(), 5.0));
    BOOST_CHECK(dal::comparable(raster.west(), 1.0));
    BOOST_CHECK(dal::comparable(raster.north(), 2.0));
    BOOST_CHECK(dal::comparable(raster.east(), 21.0));
    BOOST_CHECK(dal::comparable(raster.south(), -13.0));
  }

  // Copy.
  {
    RasterDimensions
         raster1(3, 4, 5.0, 1.0, 2.0),
         raster2(3, 4, 5.0, 1.0, 3.0),
         raster3(3, 4, 5.0, 1.0, 2.0),
         raster4(3, 5, 5.0, 1.0, 2.0);

    BOOST_CHECK(raster1 == raster1);
    BOOST_CHECK(raster1 != raster2);
    BOOST_CHECK(raster1 == raster3);
    BOOST_CHECK(raster1 != raster4);
  }
}

BOOST_AUTO_TEST_CASE(assignment)
{
  using namespace dal;

  {
    RasterDimensions raster1(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions raster2(3, 5, 5.0, 1.0, 2.0);
    RasterDimensions raster;

    raster = raster1;
    BOOST_CHECK(raster == raster1);
    BOOST_CHECK(raster != raster2);

    raster = raster2;
    BOOST_CHECK(raster != raster1);
    BOOST_CHECK(raster == raster2);
  }
}

BOOST_AUTO_TEST_CASE(index_)
{
  using namespace dal;

  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);

    BOOST_CHECK_EQUAL(raster.index(size_t(0), size_t(0)), size_t(0));
    BOOST_CHECK_EQUAL(raster.index(size_t(0), size_t(1)), size_t(1));
    BOOST_CHECK_EQUAL(raster.index(size_t(1), size_t(0)), raster.nrCols());

    BOOST_CHECK_EQUAL(raster.index(1.0, 2.0), size_t(0));
    BOOST_CHECK_EQUAL(raster.index(20.9, 2.0), size_t(3));
    BOOST_CHECK_EQUAL(raster.index(1.0, -12.9), size_t(8));
    BOOST_CHECK_EQUAL(raster.index(20.9, -12.9), size_t(11));
  }
}

BOOST_AUTO_TEST_CASE(coordinates)
{
  using namespace dal;

  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);
    double x, y;

    raster.coordinates(0u, x, y);
    BOOST_CHECK_CLOSE(x, 3.5, 0.001);
    BOOST_CHECK_CLOSE(y, -0.5, 0.001);

    raster.coordinates(11u, x, y);
    BOOST_CHECK_CLOSE(x, 18.5, 0.001);
    BOOST_CHECK_CLOSE(y, -10.5, 0.001);
  }
}

BOOST_AUTO_TEST_CASE(area_dimensions)
{
  using namespace dal;

  // Empty area.
  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions area(raster.areaDimensions(1.0, 2.0, 1.0, 2.0));
    BOOST_CHECK_EQUAL(area.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area.cellSize(), raster.cellSize());
    BOOST_CHECK_EQUAL(area.west(), raster.west());
    BOOST_CHECK_EQUAL(area.north(), raster.north());
  }

  // Small area.
  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions area(raster.areaDimensions(1.0, 2.0, 1.1, 1.9));
    BOOST_CHECK_EQUAL(area.nrRows(), size_t(1));
    BOOST_CHECK_EQUAL(area.nrCols(), size_t(1));
    BOOST_CHECK_EQUAL(area.cellSize(), raster.cellSize());
    BOOST_CHECK_EQUAL(area.west(), raster.west());
    BOOST_CHECK_EQUAL(area.north(), raster.north());
  }

  // Area on cell border.
  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions area(raster.areaDimensions(1.0, 2.0, 6.0, -3.0));
    BOOST_CHECK_EQUAL(area.nrRows(), size_t(1));
    BOOST_CHECK_EQUAL(area.nrCols(), size_t(1));
    BOOST_CHECK_EQUAL(area.cellSize(), raster.cellSize());
    BOOST_CHECK_EQUAL(area.west(), raster.west());
    BOOST_CHECK_EQUAL(area.north(), raster.north());
  }

  // Area just over cell border.
  {
    RasterDimensions raster(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions area(raster.areaDimensions(1.0, 2.0, 6.1, -3.1));
    BOOST_CHECK_EQUAL(area.nrRows(), size_t(2));
    BOOST_CHECK_EQUAL(area.nrCols(), size_t(2));
    BOOST_CHECK_EQUAL(area.cellSize(), raster.cellSize());
    BOOST_CHECK_EQUAL(area.west(), raster.west());
    BOOST_CHECK_EQUAL(area.north(), raster.north());
  }
}

BOOST_AUTO_TEST_CASE(overlap)
{
  using namespace dal;

  RasterDimensions area1, area2;

  // Empty raster dimensions.
  {
    RasterDimensions raster(size_t(0), size_t(0));

    boost::tie(area1, area2) = RasterDimensions::overlap(raster, raster);
    BOOST_CHECK_EQUAL(area1.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area1.nrCols(), size_t(0));
    BOOST_CHECK(area1 == area2);
  }

  // One empty, one non-empty raster dimension.
  {
    RasterDimensions raster1(size_t(0), size_t(0));
    RasterDimensions raster2(3, 4, 5.0, 1.0, 2.0);

    boost::tie(area1, area2) = RasterDimensions::overlap(raster1, raster2);
    BOOST_CHECK_EQUAL(area1.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area1.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area1.cellSize(), raster1.cellSize());
    BOOST_CHECK_EQUAL(area1.west(), raster1.west());
    BOOST_CHECK_EQUAL(area1.north(), raster1.north());
    BOOST_CHECK_EQUAL(area2.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area2.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area2.cellSize(), raster2.cellSize());
    BOOST_CHECK_EQUAL(area2.west(), raster2.west());
    BOOST_CHECK_EQUAL(area2.north(), raster2.north());

    // Switch arguments.
    boost::tie(area2, area1) = RasterDimensions::overlap(raster2, raster1);
    BOOST_CHECK_EQUAL(area2.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area2.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area2.cellSize(), raster2.cellSize());
    BOOST_CHECK_EQUAL(area2.west(), raster2.west());
    BOOST_CHECK_EQUAL(area2.north(), raster2.north());
    BOOST_CHECK_EQUAL(area1.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area1.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area1.cellSize(), raster1.cellSize());
    BOOST_CHECK_EQUAL(area1.west(), raster1.west());
    BOOST_CHECK_EQUAL(area1.north(), raster1.north());
  }

  // Non-overlapping raster dimensions.
  {
    RasterDimensions raster1(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions raster2(3, 4, 5.0, 22, -14.0);

    boost::tie(area1, area2) = RasterDimensions::overlap(raster1, raster2);
    BOOST_CHECK_EQUAL(area1.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area1.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area1.cellSize(), raster1.cellSize());
    BOOST_CHECK_EQUAL(area1.west(), raster1.west());
    BOOST_CHECK_EQUAL(area1.north(), raster1.north());
    BOOST_CHECK_EQUAL(area2.nrRows(), size_t(0));
    BOOST_CHECK_EQUAL(area2.nrCols(), size_t(0));
    BOOST_CHECK_EQUAL(area2.cellSize(), raster2.cellSize());
    BOOST_CHECK_EQUAL(area2.west(), raster2.west());
    BOOST_CHECK_EQUAL(area2.north(), raster2.north());
  }

  // Equal raster dimensions.
  {
    RasterDimensions raster1(3, 4, 5.0, 1.0, 2.0);
    RasterDimensions raster2(3, 4, 5.0, 1.0, 2.0);

    assert(raster1 == raster2);
    boost::tie(area1, area2) = RasterDimensions::overlap(raster1, raster2);
    BOOST_CHECK(area1 == raster1);
    BOOST_CHECK(area2 == raster2);
  }

  // Overlapping raster dimensions.
  {
    {
      // Second raster is offset to south-east by one row and column.
      RasterDimensions raster1(3, 4, 5.0, 1.0, 2.0);
      RasterDimensions raster2(3, 4, 5.0, 6.0, -3.0);

      boost::tie(area1, area2) = RasterDimensions::overlap(raster1, raster2);
      BOOST_CHECK_EQUAL(area1.nrRows(), size_t(2));
      BOOST_CHECK_EQUAL(area1.nrCols(), size_t(3));
      BOOST_CHECK_EQUAL(area1.cellSize(), raster1.cellSize());
      BOOST_CHECK_EQUAL(area1.west(), 6.0);
      BOOST_CHECK_EQUAL(area1.north(), -3.0);
      BOOST_CHECK(area2 == area1);
    }

    {
      // Second raster is offset to north-west by one row and column.
      RasterDimensions raster1(3, 4, 5.0, 1.0, 2.0);
      RasterDimensions raster2(3, 4, 5.0, -4.0, 7.0);

      boost::tie(area1, area2) = RasterDimensions::overlap(raster1, raster2);
      BOOST_CHECK_EQUAL(area1.nrRows(), size_t(2));
      BOOST_CHECK_EQUAL(area1.nrCols(), size_t(3));
      BOOST_CHECK_EQUAL(area1.cellSize(), raster1.cellSize());
      BOOST_CHECK_EQUAL(area1.west(), 1.0);
      BOOST_CHECK_EQUAL(area1.north(), 2.0);
      BOOST_CHECK(area2 == area1);
    }
  }
}


//
// Copyright 2019 Mateusz Loskot <mateusz at loskot dot net>
//
// 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
//
// Benchmark of various pixel iteration and access methods for a contiguous
// images composed of homogeneous pixels with single 8-bit integer channel
// (grayscale).
//
#include <algorithm>
#include <array>
#include <boost/gil.hpp>
#include <cassert>
#include <celero/Celero.h>
#include <cmath>
#include <cstdint>
#include <random>
#include <vector>
namespace gil = boost::gil;

class RandomImageGray8Fixture : public celero::TestFixture {
public:
  RandomImageGray8Fixture() : gen_(rd_()), uid_(0, 255), histogram_{0} {}

  std::vector<celero::TestFixture::ExperimentValue>
  getExperimentValues() const final {
    // Generate sequence of square image sizes
    int const initial_power = static_cast<int>(
        std::ceil(std::log(initial_image_size_) / std::log(2)));

    std::vector<celero::TestFixture::ExperimentValue> problem_space;
    problem_space.resize(this->number_of_images_);
    for (int i = 0; i < this->number_of_images_; i++) {
      problem_space[i] = {int64_t(pow(2, initial_power + i))};
    }
    return problem_space;
  }

  void
  setUp(const celero::TestFixture::ExperimentValue &experimentValue) final {
    auto const size = static_cast<std::ptrdiff_t>(experimentValue.Value);
    this->image_.recreate(size, size);
    auto view = gil::view(this->image_);
    for (auto it = view.begin(); it != view.end(); ++it)
      *it = static_cast<std::uint8_t>(uid_(gen_));
  }

  void clear_histogram() { this->histogram_.fill(0); }

  void assert_histogram() {
#ifndef NDEBUG
    std::size_t const total =
        std::accumulate(this->histogram_.cbegin(), this->histogram_.cend(), 0U);
    assert(total == gil::view(this->image_).size());
#endif
  }

  std::random_device rd_;
  std::mt19937 gen_;
  std::uniform_int_distribution<int> uid_;

  std::array<std::uint8_t, 256> histogram_;
  gil::gray8_image_t image_;
#ifdef NDEBUG
  int const initial_image_size_{64};
  int const number_of_images_{6};
#else
  int const initial_image_size_{2};
  int const number_of_images_{1};
#endif
};

#ifdef NDEBUG
constexpr int samples = 30; // Celero default minimum
constexpr int iterations = 100;
#else
constexpr int samples = 1;
constexpr int iterations = 1;
#endif

BASELINE_F(HistogramGray8, 1d_raw_pointer, RandomImageGray8Fixture, samples,
           iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::view(this->image_);
  std::uint8_t *p = gil::interleaved_view_get_raw_data(view);
  for (std::size_t i = 0, size = view.size(); i < size; i++)
    ++h[p[i]];

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 1d_index_operator, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::view(this->image_);
  std::ptrdiff_t const pixel_count = view.width() * view.height();
  for (std::ptrdiff_t i = 0; i < pixel_count; i++)
    ++h[view[i]];

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 1d_index_at, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::view(this->image_);
  std::ptrdiff_t const pixel_count = view.width() * view.height();
  for (std::ptrdiff_t i = 0; i < pixel_count; i++)
    ++h[*view.at(i)];

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 1d_iterator, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  for (auto it = view.begin(), end = view.end(); it != end; ++it)
    ++h[*it];

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 1d_reverse_iterator, RandomImageGray8Fixture,
            samples, iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  for (auto it = view.rbegin(), end = view.rend(); it != end; ++it)
    ++h[*it];

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 1d_for_each_pixel, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  assert(view.is_1d_traversable());

  gil::for_each_pixel(view,
                      [&h](gil::gray8c_pixel_t const &pixel) { ++h[pixel]; });

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 2d_call_operator, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  auto const view_h = view.height();
  auto const view_w = view.width();
  for (std::ptrdiff_t y = 0; y < view_h; y++) {
    for (std::ptrdiff_t x = 0; x < view_w; x++) {
      // The binary call operator() computes the location of the pixel in a 2D
      // grid, which involves addition and multiplication, thus it costs
      // performance.
      ++h[view(x, y)];
    }
  }

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 2d_xy_locator, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  auto loc = view.xy_at(0, 0);
  auto const view_h = view.height();
  auto const view_w = view.width();
  for (std::ptrdiff_t y = 0; y < view_h; y++) {
    for (std::ptrdiff_t x = 0; x < view_w; x++) {
      ++h[loc(0, 0)];
      ++loc.x();
    }
    // carriage return, equivalent of: loc.x() -= view.width(); ++loc.y();
    loc += gil::point_t{-view.width(), 1};
  }

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 2d_x_iterator, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  auto const view_h = view.height();
  for (std::ptrdiff_t y = 0; y < view_h; y++) {
    // The two iterator types are raw C pointers, so the dereferencing
    // and arithmetic is a fast pointer indexing operator.
    gil::gray8c_view_t::x_iterator it = view.row_begin(y);
    gil::gray8c_view_t::x_iterator end = view.row_end(y);
    while (it != end)
      ++h[*it++];
  }

  this->assert_histogram();
}

BENCHMARK_F(HistogramGray8, 2d_y_iterator, RandomImageGray8Fixture, samples,
            iterations) {
  this->clear_histogram();
  auto &h = this->histogram_;

  auto view = gil::const_view(this->image_);
  auto const view_w = view.width();
  for (std::ptrdiff_t x = 0; x < view_w; x++) {
    gil::gray8c_view_t::y_iterator it = view.col_begin(x);
    gil::gray8c_view_t::y_iterator end = view.col_end(x);
    while (it != end)
      ++h[*it++];
  }

  this->assert_histogram();
}

CELERO_MAIN


#include <boost/python.hpp>
#include <map>
#include <string>
#include <vector>
#include <tuple>
#include <stdlib.h>
#include <sstream>
#include <algorithm>
#include <iostream>
using namespace std;
typedef map<std::string,double> strMap;
typedef map<std::string,strMap> MyMap;
typedef std::vector<std::string> strVec;
typedef std::tuple<std::vector<strVec>, std::vector<double>> mytuple;
mytuple conv(MyMap*a, float min_prob)
{
	MyMap comb;
	MyMap tmp_dic;
	strMap first_dict;
	tmp_dic = *a;
	strVec key_list;
	std::vector<strVec> return_gt;
	std::vector<double> return_prob;
	for(auto iter=tmp_dic.begin();iter != tmp_dic.end();iter++){
		key_list.push_back(iter->first);
	}
	std::sort(key_list.begin(),key_list.end(),std::greater<std::string>());
	string first_key = key_list.front();
	key_list.pop_back();
	MyMap::iterator it=tmp_dic.find(first_key);
	for (auto inner_it=it->second.begin();inner_it!=it->second.end();inner_it++){
		first_dict[inner_it->first]=inner_it->second;
	}
	tmp_dic.erase(it);
	if(tmp_dic.empty()){
		//last one
		for (auto gene_it=first_dict.begin();gene_it!=first_dict.end();gene_it++){
			if (gene_it->second > min_prob){
				strVec new_gene_config;
				new_gene_config.push_back(gene_it->first);
				return_gt.push_back(new_gene_config);
				return_prob.push_back(gene_it->second);
			}
		}
	}
	else{
		std::vector<strVec> pre_gt;
		std::vector<double> pre_prob;
		mytuple pre_tuple=conv(&tmp_dic, min_prob);
		pre_gt=std::get<0>(pre_tuple);
		pre_prob=std::get<1>(pre_tuple);
		for(auto pre_it=pre_gt.begin();pre_it!=pre_gt.end();pre_it++){
			for(auto first_it=first_dict.begin();first_it!=first_dict.end();first_it++){
				double comb_prob;
				int pos = pre_it-pre_gt.begin();
				comb_prob=pre_prob.at(pos)*first_it->second;
				if(comb_prob>min_prob){
					strVec combined_gt=pre_gt.at(pos);
					combined_gt.push_back(first_it->first);
					return_gt.push_back(combined_gt);
					return_prob.push_back(comb_prob);
				}
			}
		}
	}
	double sum_prob=0;
	for(auto prob_it=return_prob.begin();prob_it!=return_prob.end();prob_it++){
		sum_prob+=*prob_it;
	}
	//cout<<"sum_prob"<<sum_prob<<endl;
	/*if(sum_prob!=1){
		for(auto prob_it=return_prob.begin();prob_it!=return_prob.end();prob_it++){
			int pos=prob_it-return_prob.begin();
			return_prob.at(pos)=*prob_it/sum_prob;
		}
	}*/
	mytuple returns = std::make_tuple(return_gt,return_prob);
	return returns;
}

int main(){
	strMap off1 = {
		{"12",0.2},{"11",0.7},{"13",0.1}
	};
	strMap off2 = {
		{"12",0.3},{"11",0.6},{"23",0.1}
	};
	strMap off3 = {
		{"12",0.3},{"11",0.6},{"22",0.1}
	};
	MyMap dic = {
		{"off1",off1},{"off2",off2},{"off3",off3}
	};
	for(auto it=dic.begin();it!=dic.end();it++){
		for(auto inner_it=it->second.begin();inner_it!=it->second.end();inner_it++){
		}
	}
	mytuple combined=conv(&dic, 1E-5);
	std::vector<strVec> combined_gt=std::get<0>(combined);
	std::vector<double> comb_prob=std::get<1>(combined);
	for(auto gt_it=combined_gt.begin();gt_it!=combined_gt.end();gt_it++){
		int pos = gt_it-combined_gt.begin();
		cout<<comb_prob.at(pos)<<endl;
		for(auto inner_it=gt_it->begin();inner_it!=gt_it->end();inner_it++){
			cout<<*inner_it;
		}
		cout<<endl;
	}
	return 0;
}

strMap tomap(boost::python::dict mydict){
	strMap convert_map;
	//boost::python::extract<boost::python::dict> cppdict_extract(pre_dic);
	boost::python::list keys = mydict.keys();
	for(int i = 0; i<len(keys);++i){
		std::string gt=boost::python::extract<std::string>(keys[i]);
		double prob=boost::python::extract<double>(mydict[keys[i]]);
		convert_map[gt]=prob;
	}

	return convert_map;
}

boost::python::list togtlist(std::vector<strVec> *gt){
  	std::vector<strVec> tmpgt = *gt;
	boost::python::list gt_list;
	for (auto iter=tmpgt.begin();iter != tmpgt.end();iter++){
		boost::python::list tmp_list;
		for (auto inner_it=iter->begin();inner_it!=iter->end();inner_it++){
			tmp_list.append(*inner_it);
		}
		gt_list.append(tmp_list);
	}
	return gt_list;
}

boost::python::list toproblist(std::vector<double> *prob){
  	std::vector<double> tmpprob = *prob;
	boost::python::list prob_list;
	for (auto iter=tmpprob.begin();iter != tmpprob.end();iter++){
		prob_list.append(*iter);
	}
	return prob_list;
}

boost::python::tuple applyconv(boost::python::dict dic, float min_prob = 1E-5){
	MyMap map1;
	boost::python::dict mydict;
	mytuple returns;
	std::vector<strVec> return_gt;
	std::vector<double> return_prob;
	boost::python::list py_return_gt;
	boost::python::list py_return_prob;
	boost::python::list keys = dic.keys();
	for(int i=0; i<len(keys);i++){
		std::string dkey=boost::python::extract<std::string>(keys[i]);
		strMap tmp_dic;
		boost::python::extract<boost::python::dict> inner_dic1(dic[dkey]);
		if(inner_dic1.check()){
			tmp_dic=tomap(inner_dic1);
			map1[dkey]=tmp_dic;
		}
	}
	returns = conv(&map1, min_prob);
	return_gt = std::get<0>(returns);
	return_prob=std::get<1>(returns);
	py_return_gt = togtlist(&return_gt);
	py_return_prob = toproblist(&return_prob);
	return boost::python::make_tuple(py_return_gt,py_return_prob);
}

BOOST_PYTHON_MODULE(cconv2){
	using namespace boost::python;
	def("conv",conv);
	def("applyconv",applyconv);
}


#ifndef BOOST_METAPARSE_GETTING_STARTED_7_2_HPP
#define BOOST_METAPARSE_GETTING_STARTED_7_2_HPP

// Automatically generated header file

// Definitions before section 7.1
#include "7_1.hpp"

// Definitions of section 7.1
#include <boost/mpl/times.hpp>

template <class L, class R> struct eval_binary_op<L, '*', R> : boost::mpl::times<L, R>::type {};

// query:
//    eval_binary_op<boost::mpl::int_<3>, '*', boost::mpl::int_<4>>::type

using times_token = token<lit_c<'*'>>;

using exp_parser14 =
 build_parser<
   foldl_start_with_parser<
     sequence<one_of<plus_token, minus_token, times_token>, int_token>,
     int_token,
     boost::mpl::quote2<binary_op>
   >
 >;

// query:
//    exp_parser14::apply<BOOST_METAPARSE_STRING("2 * 3")>::type

// query:
//    exp_parser14::apply<BOOST_METAPARSE_STRING("1 + 2 * 3")>::type

#endif


#ifndef STAN_MATH_PRIM_MAT_FUN_REP_VECTOR_HPP
#define STAN_MATH_PRIM_MAT_FUN_REP_VECTOR_HPP

#include <boost/math/tools/promotion.hpp>
#include <stan/math/prim/scal/err/check_nonnegative.hpp>
#include <stan/math/prim/mat/fun/Eigen.hpp>

namespace stan {
  namespace math {

    template <typename T>
    inline
    Eigen::Matrix<typename boost::math::tools::promote_args<T>::type,
                  Eigen::Dynamic, 1>
    rep_vector(const T& x, int n) {
      check_nonnegative("rep_vector", "n", n);
      return Eigen::Matrix<typename boost::math::tools::promote_args<T>::type,
                           Eigen::Dynamic, 1>::Constant(n, x);
    }

  }
}

#endif


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<boost::uint8_t, 1, Eigen::Dynamic> test_uchar_1_D(const Eigen::Matrix<boost::uint8_t, 1, Eigen::Dynamic> & M)
{
	return M;
}
void export_uchar_1_D()
{
	boost::python::def("test_uchar_1_D",test_uchar_1_D);
}


//
//  Copyright (C) 2014 Greg Landrum
//
//   @@ 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 <GraphMol/new_canon.h>
#include <RDGeneral/RDLog.h>
#include <RDGeneral/Invariant.h>
#include <RDGeneral/hanoiSort.h>

#include <GraphMol/RDKitBase.h>
#include <GraphMol/SmilesParse/SmilesParse.h>
#include <GraphMol/SmilesParse/SmilesWrite.h>
#include <GraphMol/FileParsers/FileParsers.h>

#include <iostream>
#include <vector>
#include <boost/random.hpp>
#include <cstdlib>

using namespace RDKit;

int pcmp(const void *a, const void *b) {
  if ((*(int *)a) < (*(int *)b)) {
    return -1;
  } else if ((*(int *)a) > (*(int *)b)) {
    return 1;
  }
  return 0;
}
int icmp(int a, int b) {
  if (a < b) {
    return -1;
  } else if (a > b) {
    return 1;
  }
  return 0;
}

class int_compare_ftor {
  const int *dp_ints;

 public:
  int_compare_ftor() : dp_ints(NULL){};
  int_compare_ftor(const int *ints) : dp_ints(ints){};
  int operator()(int i, int j) const {
    PRECONDITION(dp_ints, "no ints");
    unsigned int ivi = dp_ints[i];
    unsigned int ivj = dp_ints[j];
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;
    else
      return 0;
  }
};

void qs1(const std::vector<std::vector<int> > &vects) {
  BOOST_LOG(rdInfoLog) << "sorting (qsort) vectors" << std::endl;
  for (unsigned int i = 0; i < vects.size(); ++i) {
    std::vector<int> tv = vects[i];
    int *data = &tv.front();
    qsort(data, tv.size(), sizeof(int), pcmp);
    for (unsigned int j = 1; j < tv.size(); ++j) {
      TEST_ASSERT(tv[j] >= tv[j - 1]);
    }
  }
  BOOST_LOG(rdInfoLog) << "done: " << vects.size() << std::endl;
}

void hs1(const std::vector<std::vector<int> > &vects) {
  BOOST_LOG(rdInfoLog) << "sorting (hanoi sort) vectors" << std::endl;
  for (unsigned int i = 0; i < vects.size(); ++i) {
    const int *data = &vects[i].front();
    int_compare_ftor icmp(data);
    int *indices = (int *)malloc(vects[i].size() * sizeof(int));
    for (unsigned int j = 0; j < vects[i].size(); ++j) indices[j] = j;
    int *count = (int *)malloc(vects[i].size() * sizeof(int));
    int *changed = (int *)malloc(vects[i].size() * sizeof(int));
    memset(changed, 1, vects[i].size() * sizeof(int));
    RDKit::hanoisort(indices, vects[i].size(), count, changed, icmp);
    for (unsigned int j = 1; j < vects[i].size(); ++j) {
      TEST_ASSERT(data[indices[j]] >= data[indices[j - 1]]);
    }
    free(count);
    free(indices);
  }
  BOOST_LOG(rdInfoLog) << "done: " << vects.size() << std::endl;
}

void test1() {
  BOOST_LOG(rdInfoLog) << "Testing the hanoi sort" << std::endl;

  typedef boost::random::mersenne_twister<boost::uint32_t, 32, 4, 2, 31,
                                          0x9908b0df, 11, 7, 0x9d2c5680, 15,
                                          0xefc60000, 18, 3346425566U> rng_type;
  typedef boost::uniform_int<> distrib_type;
  typedef boost::variate_generator<rng_type &, distrib_type> source_type;
  rng_type generator(42u);

  const unsigned int nVects = 500000;
  const unsigned int vectSize = 50;
  const unsigned int nClasses = 15;

  distrib_type dist(0, nClasses);
  source_type randomSource(generator, dist);

  BOOST_LOG(rdInfoLog) << "populating vectors" << std::endl;
  std::vector<std::vector<int> > vects(nVects);
  for (unsigned int i = 0; i < nVects; ++i) {
    vects[i] = std::vector<int>(vectSize);
    for (unsigned int j = 0; j < vectSize; ++j) {
      vects[i][j] = randomSource();
    }
  }

  // qs1(vects);
  hs1(vects);
  BOOST_LOG(rdInfoLog) << "Done" << std::endl;
};

class atomcomparefunctor {
  Canon::canon_atom *d_atoms;

 public:
  atomcomparefunctor() : d_atoms(NULL){};
  atomcomparefunctor(Canon::canon_atom *atoms) : d_atoms(atoms){};
  int operator()(int i, int j) const {
    PRECONDITION(d_atoms, "no atoms");
    unsigned int ivi, ivj;

    // always start with the current class:
    ivi = d_atoms[i].index;
    ivj = d_atoms[j].index;
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    ivi = d_atoms[i].atom->getAtomicNum();
    ivj = d_atoms[j].atom->getAtomicNum();
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    return 0;
  }
};
class atomcomparefunctor2 {
  Canon::canon_atom *d_atoms;

 public:
  atomcomparefunctor2() : d_atoms(NULL){};
  atomcomparefunctor2(Canon::canon_atom *atoms) : d_atoms(atoms){};
  int operator()(int i, int j) const {
    PRECONDITION(d_atoms, "no atoms");
    unsigned int ivi, ivj;

    // always start with the current class:
    ivi = d_atoms[i].index;
    ivj = d_atoms[j].index;
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    // start by comparing degree
    ivi = d_atoms[i].atom->getDegree();
    ivj = d_atoms[j].atom->getDegree();
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    // move onto atomic number
    ivi = d_atoms[i].atom->getAtomicNum();
    ivj = d_atoms[j].atom->getAtomicNum();
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    return 0;
  }
};

void test2() {
  BOOST_LOG(rdInfoLog) << "Testing hanoi with a functor." << std::endl;
  // make sure that hanoi works with a functor and "molecule data"
  {
    std::string smi = "FC1C(Cl)C1C";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    std::vector<int> indices(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      atoms[i].atom = m->getAtomWithIdx(i);
      atoms[i].index = 0;
      indices[i] = i;
    }
    atomcomparefunctor ftor(&atoms.front());

    int *data = &indices.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    RDKit::hanoisort(data, atoms.size(), count, changed, ftor);

    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<indices[i]<<" "<<" index: "<<atoms[indices[i]].index<<"
      // count: "<<count[indices[i]]<<std::endl;
      if (i > 0) {
        TEST_ASSERT(atoms[indices[i]].atom->getAtomicNum() >=
                    atoms[indices[i - 1]].atom->getAtomicNum());
        if (atoms[indices[i]].atom->getAtomicNum() !=
            atoms[indices[i - 1]].atom->getAtomicNum()) {
          TEST_ASSERT(count[indices[i]] != 0);
        } else {
          TEST_ASSERT(count[indices[i]] == 0);
        }
      } else {
        TEST_ASSERT(count[indices[i]] != 0);
      }
    }
  }
  BOOST_LOG(rdInfoLog) << "Done" << std::endl;
};

void test3() {
  BOOST_LOG(rdInfoLog) << "Testing basic partition refinement." << std::endl;
  // basic partition refinement
  {
    std::string smi = "FC1C(Cl)CCC1C";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    initCanonAtoms(*m, atoms, true);
    atomcomparefunctor ftor(&atoms.front());

    RDKit::Canon::canon_atom *data = &atoms.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *order = (int *)malloc(atoms.size() * sizeof(int));
    int activeset;
    int *next = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    char *touched = (char *)malloc(atoms.size() * sizeof(char));

    RDKit::Canon::CreateSinglePartition(atoms.size(), order, count, data);
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<i<<" "<<atoms[i].index<<" "<<count[i]<<" "<<next[i]<<"
    //   "<<order[i]<<std::endl;
    // }

    RDKit::Canon::RefinePartitions(*m, data, ftor, false, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<i<<" "<<atoms[i].index<<" "<<count[i]<<" "<<next[i]<<"
    //   "<<order[i]<<std::endl;
    // }

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<order[i]<<" "<<atoms[order[i]].invar<<" index:
    //   "<<atoms[order[i]].index<<" count: "<<count[order[i]]<<std::endl;
    // }

    TEST_ASSERT(order[0] == 1);
    TEST_ASSERT(order[6] == 0);
    TEST_ASSERT(order[7] == 3);
    TEST_ASSERT(count[order[0]] == 6);
    TEST_ASSERT(count[order[1]] == 0);
    TEST_ASSERT(count[order[6]] == 1);
    TEST_ASSERT(count[order[7]] == 1);
  }
  {
    // this time with smarter invariants
    std::string smi = "FC1C(Cl)CCC1C";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    initCanonAtoms(*m, atoms, true);
    atomcomparefunctor2 ftor(&atoms.front());

    RDKit::Canon::canon_atom *data = &atoms.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *order = (int *)malloc(atoms.size() * sizeof(int));
    int activeset;
    int *next = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    char *touched = (char *)malloc(atoms.size() * sizeof(char));

    RDKit::Canon::CreateSinglePartition(atoms.size(), order, count, data);
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);

    RDKit::Canon::RefinePartitions(*m, data, ftor, false, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
    //   "<<count[order[i]]<<std::endl;
    // }

    TEST_ASSERT(order[0] == 7);
    TEST_ASSERT(order[1] == 0);
    TEST_ASSERT(order[2] == 3);
    TEST_ASSERT(order[3] == 4);
    TEST_ASSERT(order[5] == 1);
    TEST_ASSERT(count[order[0]] == 1);
    TEST_ASSERT(count[order[1]] == 1);
    TEST_ASSERT(count[order[2]] == 1);
    TEST_ASSERT(count[order[3]] == 2);
    TEST_ASSERT(count[order[4]] == 0);
    TEST_ASSERT(count[order[5]] == 3);
    TEST_ASSERT(count[order[6]] == 0);
  }
  BOOST_LOG(rdInfoLog) << "Done" << std::endl;
};

class atomcomparefunctor3 {
  Canon::canon_atom *dp_atoms;
  const ROMol *dp_mol;
  unsigned int getAtomNeighborhood(unsigned int i) const {
    unsigned int res = 0;
    const Atom *at = dp_mol->getAtomWithIdx(i);
    std::vector<unsigned int> nbrs(at->getDegree());
    unsigned int nbridx = 0;
    ROMol::OEDGE_ITER beg, end;
    boost::tie(beg, end) = dp_mol->getAtomBonds(at);
    while (beg != end) {
      const BOND_SPTR bond = (*dp_mol)[*beg];
      nbrs[nbridx] =
          static_cast<unsigned int>(100 * bond->getBondTypeAsDouble()) +
          dp_atoms[bond->getOtherAtomIdx(i)].index;
      ++beg;
      ++nbridx;
    }
    std::sort(nbrs.begin(), nbrs.end());
    for (nbridx = 0; nbridx < at->getDegree(); ++nbridx) {
      res += (nbridx + 1) * 1000 + nbrs[nbridx];
    }
    return res;
  }
  int basecomp(int i, int j) const {
    PRECONDITION(dp_atoms, "no atoms");
    unsigned int ivi, ivj;

    // always start with the current class:
    ivi = dp_atoms[i].index;
    ivj = dp_atoms[j].index;
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    // start by comparing degree
    ivi = dp_atoms[i].atom->getDegree();
    ivj = dp_atoms[j].atom->getDegree();
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    // move onto atomic number
    ivi = dp_atoms[i].atom->getAtomicNum();
    ivj = dp_atoms[j].atom->getAtomicNum();
    if (ivi < ivj)
      return -1;
    else if (ivi > ivj)
      return 1;

    return 0;
  }

 public:
  bool df_useNbrs;
  atomcomparefunctor3() : dp_atoms(NULL), dp_mol(NULL), df_useNbrs(false){};
  atomcomparefunctor3(Canon::canon_atom *atoms, const ROMol &m)
      : dp_atoms(atoms), dp_mol(&m), df_useNbrs(false){};
  int operator()(int i, int j) const {
    PRECONDITION(dp_atoms, "no atoms");
    PRECONDITION(dp_mol, "no molecule");
    int v = basecomp(i, j);
    if (v) return v;
    unsigned int ivi, ivj;
    if (df_useNbrs) {
      ivi = dp_atoms[i].index + 1 + getAtomNeighborhood(i);
      ivj = dp_atoms[j].index + 1 + getAtomNeighborhood(j);
      // std::cerr<<"               "<<i<<"-"<<j<<": "<<ivi<<"
      // "<<ivj<<std::endl;
      if (ivi < ivj)
        return -1;
      else if (ivi > ivj)
        return 1;
    }
    return 0;
  }
};

void test4() {
  BOOST_LOG(rdInfoLog) << "Testing partition refinement with neighbors."
                       << std::endl;
  // partition refinement with neighbors
  {
    std::string smi = "FC1C(Cl)CCC1C";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    initCanonAtoms(*m, atoms, true);
    atomcomparefunctor3 ftor(&atoms.front(), *m);
    RDKit::Canon::canon_atom *data = &atoms.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *order = (int *)malloc(atoms.size() * sizeof(int));
    int activeset;
    int *next = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    char *touched = (char *)malloc(atoms.size() * sizeof(char));

    RDKit::Canon::CreateSinglePartition(atoms.size(), order, count, data);
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);
    // std::cerr<<"1----------------------------------"<<std::endl;
    //  for(unsigned int i=0;i<m->getNumAtoms();++i){
    //    std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
    //    "<<count[order[i]]<<" next: "<<next[order[i]]<<" changed:
    //    "<<changed[order[i]]<<std::endl;
    // }
    RDKit::Canon::RefinePartitions(*m, data, ftor, false, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"2----------------------------------"<<std::endl;
    //  for(unsigned int i=0;i<m->getNumAtoms();++i){
    //    std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
    //    "<<count[order[i]]<<" next: "<<next[order[i]]<<" changed:
    //    "<<changed[order[i]]<<std::endl;
    // }

    // std::cerr<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<std::endl;
    ftor.df_useNbrs = true;
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);
    // std::cerr<<"3----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //    std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
    //    "<<count[order[i]]<<" next: "<<next[order[i]]<<" changed:
    //    "<<changed[order[i]]<<std::endl;
    // }
    RDKit::Canon::RefinePartitions(*m, data, ftor, true, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"----------------------------------"<<std::endl;
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
      // "<<count[order[i]]<<std::endl;
      TEST_ASSERT(count[order[i]] == 1);
      if (i > 0) {
        TEST_ASSERT(ftor(order[i], order[i - 1]) >= 0);
      }
    }
    delete m;
  }

  {
    std::string smi = "FC1C(CO)CCC1CC";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    initCanonAtoms(*m, atoms, true);
    atomcomparefunctor3 ftor(&atoms.front(), *m);

    RDKit::Canon::canon_atom *data = &atoms.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *order = (int *)malloc(atoms.size() * sizeof(int));
    int activeset;
    int *next = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    char *touched = (char *)malloc(atoms.size() * sizeof(char));

    RDKit::Canon::CreateSinglePartition(atoms.size(), order, count, data);
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);

    RDKit::Canon::RefinePartitions(*m, data, ftor, false, order, count,
                                   activeset, next, changed, touched);
    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
    //   "<<count[order[i]]<<std::endl;
    // }

    ftor.df_useNbrs = true;
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);
    RDKit::Canon::RefinePartitions(*m, data, ftor, true, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"----------------------------------"<<std::endl;
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
      // "<<count[order[i]]<<std::endl;
      TEST_ASSERT(count[order[i]] == 1);
      if (i > 0) {
        // std::cerr<<"  ftor: "<<ftor(order[i],order[i-1])<<std::endl;
        TEST_ASSERT(ftor(order[i], order[i - 1]) >= 0);
      }
    }
    delete m;
  }

  {
    std::string smi = "FC1C(CC)CCC1CC";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    initCanonAtoms(*m, atoms, true);
    atomcomparefunctor3 ftor(&atoms.front(), *m);

    RDKit::Canon::canon_atom *data = &atoms.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *order = (int *)malloc(atoms.size() * sizeof(int));
    int activeset;
    int *next = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    char *touched = (char *)malloc(atoms.size() * sizeof(char));

    RDKit::Canon::CreateSinglePartition(atoms.size(), order, count, data);
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<order[i]<<" "<<atoms[order[i]].invar<<" index:
    //   "<<atoms[order[i]].index<<std::endl;
    // }

    RDKit::Canon::RefinePartitions(*m, data, ftor, false, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<std::endl;
    ftor.df_useNbrs = true;

    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);
    RDKit::Canon::RefinePartitions(*m, data, ftor, true, order, count,
                                   activeset, next, changed, touched);
    // std::cerr<<"----------------------------------"<<std::endl;

    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
      // "<<count[order[i]]<<std::endl;
      if (i > 0) {
        // std::cerr<<"  ftor: "<<ftor(order[i],order[i-1])<<std::endl;
        TEST_ASSERT(ftor(order[i], order[i - 1]) >= 0);
      }
    }

    // here we can't manage to get everything  unique
    TEST_ASSERT(order[0] == 4 && count[4] == 2);
    TEST_ASSERT(order[1] == 9 && count[9] == 0);
    TEST_ASSERT(order[2] == 0 && count[0] == 1);
    TEST_ASSERT(order[3] == 3 && count[3] == 2);
    TEST_ASSERT(order[4] == 8 && count[8] == 0);
    TEST_ASSERT(order[5] == 5 && count[5] == 2);
    TEST_ASSERT(order[6] == 6 && count[6] == 0);
    TEST_ASSERT(order[7] == 2 && count[2] == 2);
    TEST_ASSERT(order[8] == 7 && count[7] == 0);
    TEST_ASSERT(order[9] == 1 && count[1] == 1);

    delete m;
  }

  BOOST_LOG(rdInfoLog) << "Done" << std::endl;
};

void test5() {
  BOOST_LOG(rdInfoLog) << "testing canonicalization via tie breaking."
                       << std::endl;
  // canonicalization via tie breaking
  {
    std::string smi = "FC1C(CC)CCC1CC";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<Canon::canon_atom> atoms(m->getNumAtoms());
    initCanonAtoms(*m, atoms, true);
    atomcomparefunctor3 ftor(&atoms.front(), *m);

    RDKit::Canon::canon_atom *data = &atoms.front();
    int *count = (int *)malloc(atoms.size() * sizeof(int));
    int *order = (int *)malloc(atoms.size() * sizeof(int));
    int activeset;
    int *next = (int *)malloc(atoms.size() * sizeof(int));
    int *changed = (int *)malloc(atoms.size() * sizeof(int));
    memset(changed, 1, atoms.size() * sizeof(int));
    char *touched = (char *)malloc(atoms.size() * sizeof(char));

    RDKit::Canon::CreateSinglePartition(atoms.size(), order, count, data);
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<order[i]<<" "<<atoms[order[i]].invar<<" index:
    //   "<<atoms[order[i]].index<<std::endl;
    // }

    RDKit::Canon::RefinePartitions(*m, data, ftor, false, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<std::endl;
    ftor.df_useNbrs = true;
    RDKit::Canon::ActivatePartitions(atoms.size(), order, count, activeset,
                                     next, changed);
    RDKit::Canon::RefinePartitions(*m, data, ftor, true, order, count,
                                   activeset, next, changed, touched);

    // std::cerr<<"----------------------------------"<<std::endl;
    // for(unsigned int i=0;i<m->getNumAtoms();++i){
    //   std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
    //   "<<count[order[i]]<<std::endl;
    // }

    // here we can't manage to get everything  unique
    TEST_ASSERT(order[0] == 4 && count[4] == 2);
    TEST_ASSERT(order[1] == 9 && count[9] == 0);
    TEST_ASSERT(order[2] == 0 && count[0] == 1);
    TEST_ASSERT(order[3] == 3 && count[3] == 2);
    TEST_ASSERT(order[4] == 8 && count[8] == 0);
    TEST_ASSERT(order[5] == 5 && count[5] == 2);
    TEST_ASSERT(order[6] == 6 && count[6] == 0);
    TEST_ASSERT(order[7] == 2 && count[2] == 2);
    TEST_ASSERT(order[8] == 7 && count[7] == 0);
    TEST_ASSERT(order[9] == 1 && count[1] == 1);

    RDKit::Canon::BreakTies(*m, data, ftor, true, order, count, activeset, next,
                            changed, touched);
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<order[i]<<" "<<" index: "<<atoms[order[i]].index<<" count:
      // "<<count[order[i]]<<std::endl;
      TEST_ASSERT(count[order[i]] == 1);
    }
    delete m;
  }
  BOOST_LOG(rdInfoLog) << "Done" << std::endl;
};

void test6() {
  BOOST_LOG(rdInfoLog) << "testing canonicalization using the wrapper."
                       << std::endl;
// canonicalization using the wrapper
#if 1
  {
    std::string smi = "FC1C(CC)CCC1CC";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);

    std::vector<unsigned int> atomRanks;
    RDKit::Canon::rankMolAtoms(*m, atomRanks);
    boost::dynamic_bitset<> seen(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      TEST_ASSERT(!seen[atomRanks[i]]);
      seen.set(atomRanks[i], 1);
    }
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[0] == 2);
    TEST_ASSERT(atomRanks[1] == 9);
    TEST_ASSERT(atomRanks[2] == 7);
    TEST_ASSERT(atomRanks[3] == 3);
    TEST_ASSERT(atomRanks[4] == 0);
    TEST_ASSERT(atomRanks[5] == 5);
    TEST_ASSERT(atomRanks[6] == 6);
    TEST_ASSERT(atomRanks[7] == 8);
    TEST_ASSERT(atomRanks[8] == 4);
    TEST_ASSERT(atomRanks[9] == 1);
    delete m;
  }

  {
    std::string smi = "CC[C@@H]1CCC[C@@H](O1)C(=O)O";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);

    std::vector<unsigned int> atomRanks;
    RDKit::Canon::rankMolAtoms(*m, atomRanks);
    boost::dynamic_bitset<> seen(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<i<<" "<<atomRanks[i]<<std::endl;
      TEST_ASSERT(!seen[atomRanks[i]]);
      seen.set(atomRanks[i], 1);
    }

    // for(unsigned int ii=0;ii<atomRanks.size();++ii){
    //    std::cerr<<ii<<":"<<atomRanks[ii]<<std::endl;
    // }
    TEST_ASSERT(atomRanks[0] == 0);
    TEST_ASSERT(atomRanks[1] == 4);
    TEST_ASSERT(atomRanks[2] == 9);
    TEST_ASSERT(atomRanks[3] == 5);
    TEST_ASSERT(atomRanks[4] == 3);
    TEST_ASSERT(atomRanks[5] == 6);
    TEST_ASSERT(atomRanks[6] == 10);
    TEST_ASSERT(atomRanks[7] == 7);
    TEST_ASSERT(atomRanks[8] == 8);
    TEST_ASSERT(atomRanks[9] == 1);
    TEST_ASSERT(atomRanks[10] == 2);

    delete m;
  }

  {
    std::string smi =
        "N[C@@H](Cc1c[nH]c2ccccc12)C(=O)N[C@@H](CCCN=C(N)N)C(=O)N[C@@H](Cc3c["
        "nH]c4ccccc34)C(=O)OCc5ccccc5";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);

    std::vector<unsigned int> atomRanks;
    RDKit::Canon::rankMolAtoms(*m, atomRanks);
    boost::dynamic_bitset<> seen(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<i<<" "<<atomRanks[i]<<std::endl;
      TEST_ASSERT(!seen[atomRanks[i]]);
      seen.set(atomRanks[i], 1);
    }

    // for(unsigned int ii=0;ii<atomRanks.size();++ii){
    //   std::cerr<<ii<<":"<<atomRanks[ii]<<std::endl;
    // }
    delete m;
  }
#endif
  {
    std::string smi = "BrC=C1CCC(C(=O)O1)c2cccc3ccccc23";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);

    std::vector<unsigned int> atomRanks;
    RDKit::Canon::rankMolAtoms(*m, atomRanks);
    boost::dynamic_bitset<> seen(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      // std::cerr<<i<<" "<<atomRanks[i]<<std::endl;
      TEST_ASSERT(!seen[atomRanks[i]]);
      seen.set(atomRanks[i], 1);
    }

    // for(unsigned int ii=0;ii<atomRanks.size();++ii){
    //   std::cerr<<ii<<":"<<atomRanks[ii]<<std::endl;
    // }
    delete m;
  }

  {
    std::string smi = "CC12CCCC1CCCC2";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);

    // start w/o tie breaking here; we shouldn't need it.
    std::vector<unsigned int> atomRanks;
    RDKit::Canon::rankMolAtoms(*m, atomRanks, false);
    boost::dynamic_bitset<> seen(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      //      std::cerr<<"      "<<i<<" "<<atomRanks[i]<<std::endl;
      TEST_ASSERT(!seen[atomRanks[i]]);
      seen.set(atomRanks[i], 1);
    }
    delete m;
  }

  {
    std::string smi = "CC12CCCC1C1CCC3CC(O)CCC3(C)C1CC2";
    RWMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);

    // start w/o tie breaking here; we shouldn't need it.
    std::vector<unsigned int> atomRanks;
    RDKit::Canon::rankMolAtoms(*m, atomRanks, false);
    boost::dynamic_bitset<> seen(m->getNumAtoms());
    for (unsigned int i = 0; i < m->getNumAtoms(); ++i) {
      TEST_ASSERT(!seen[atomRanks[i]]);
      seen.set(atomRanks[i], 1);
    }
    delete m;
  }

  BOOST_LOG(rdInfoLog) << "Done" << std::endl;
};

namespace {

ROMol *_renumber(const ROMol *m, std::vector<unsigned int> &nVect,
                 std::string inSmiles) {
  ROMol *nm = MolOps::renumberAtoms(*m, nVect);
  TEST_ASSERT(nm);
  TEST_ASSERT(nm->getNumAtoms() == m->getNumAtoms());
  TEST_ASSERT(nm->getNumBonds() == m->getNumBonds());
  MolOps::assignStereochemistry(*nm, true, true);
  for (unsigned int ii = 0; ii < nm->getNumAtoms(); ++ii) {
    if (nm->getAtomWithIdx(ii)->hasProp("_CIPCode")) {
      TEST_ASSERT(m->getAtomWithIdx(nVect[ii])->hasProp("_CIPCode"));
      std::string ocip =
          m->getAtomWithIdx(nVect[ii])->getProp<std::string>("_CIPCode");
      std::string ncip =
          nm->getAtomWithIdx(ii)->getProp<std::string>("_CIPCode");
      if (ocip != ncip) {
        std::cerr << "  cip mismatch: " << inSmiles << std::endl;
        std::cerr << "      " << nVect[ii] << ": " << ocip << " -> " << ii
                  << ": " << ncip << std::endl;
        std::cerr << "      " << MolToSmiles(*nm, true) << std::endl;
      }
      TEST_ASSERT(ocip == ncip);
    }
  }
  return nm;
}

void _renumberTest(const ROMol *m, std::string inSmiles,
                   unsigned int numRenumbers) {
  PRECONDITION(m, "no molecule");
  //    std::cerr<<">>>>>>>>>>>>>>>>>>>>>>>>>>>"<<std::endl;
  std::string osmi = MolToSmiles(*m, true);
  std::vector<unsigned int> idxV(m->getNumAtoms());
  for (unsigned int i = 0; i < m->getNumAtoms(); ++i) idxV[i] = i;

  std::srand(0xF00D);
  for (unsigned int i = 0; i < numRenumbers; ++i) {
    //      std::cerr<<"---------------------------------------------------"<<std::endl;
    std::vector<unsigned int> nVect(idxV);
    std::random_shuffle(nVect.begin(), nVect.end());
    //      for(unsigned int j=0;j<m->getNumAtoms();++j){
    //        std::cerr<<"Renumber: "<<nVect[j]<<"->"<<j<<std::endl;
    //      }
    ROMol *nm = _renumber(m, nVect, inSmiles);

    std::string smi = MolToSmiles(*nm, true);
    if (smi != osmi) {
      std::cerr << "  input: " << inSmiles << ", Renumbering round: " << i
                << std::endl;
      std::cerr << osmi << std::endl;
      std::cerr << smi << std::endl;
      m->setProp("_Name", "orig");
      std::cerr << MolToMolBlock(*m) << std::endl;
      nm->setProp("_Name", "renumber");
      std::cerr << MolToMolBlock(*nm) << std::endl;
      for (unsigned int j = 0; j < m->getNumAtoms(); ++j) {
        std::cerr << "Renumber: " << nVect[j] << "->" << j << std::endl;
      }
    }
    TEST_ASSERT(smi == osmi);
    delete nm;
  }
}

void _renumberTest2(const ROMol *m, std::string inSmiles,
                    unsigned int numRenumbers) {
  PRECONDITION(m, "no molecule");

  unsigned int nAtoms = m->getNumAtoms();
  std::vector<unsigned int> idxV(m->getNumAtoms());
  for (unsigned int i = 0; i < m->getNumAtoms(); ++i) idxV[i] = i;

  std::srand(0xF00D);
  for (unsigned int i = 0; i < numRenumbers; ++i) {
    std::vector<unsigned int> nVect(idxV);
    std::random_shuffle(nVect.begin(), nVect.end());

    ROMol *nm = _renumber(m, nVect, inSmiles);

    UINT_VECT ranks(nAtoms);
    Canon::rankMolAtoms(*nm, ranks, true);
    char *ranksSet = (char *)malloc(nAtoms * sizeof(char));
    memset(ranksSet, 0, nAtoms * sizeof(char));
    for (unsigned int i = 0; i < ranks.size(); i++) {
      ranksSet[ranks[i]] = 1;
    }
    for (unsigned int i = 0; i < nAtoms; i++) {
      if (ranksSet[i] != 1) {
        std::cerr << "Molecule has non unique ranks: " << MolToSmiles(*nm, true)
                  << ", Renumbering round: " << i << std::endl;
        for (unsigned int i = 0; i < nAtoms; i++) {
          std::cerr << "AtomIdx: " << i << " Rank: " << ranks[i] << std::endl;
        }
      }
      TEST_ASSERT(ranksSet[i] == 1);
    }
    delete nm;
  }
}
}

void test7a() {
  BOOST_LOG(rdInfoLog) << "testing some specific ordering problems"
                       << std::endl;
  std::string rdbase = getenv("RDBASE");
  std::string smi1, smi2;
  {
    std::string fName = rdbase + "/Code/GraphMol/test_data/canon_reorder1.mol";
    RWMol *m = MolFileToMol(fName, false, false);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    //    std::cerr <<"\n\n\n\n\n\n\n\n\n\n\n\n>--------------" << std::endl;
    RDKit::Canon::rankMolAtoms(*m, atomRanks, false);
    //    std::cerr <<"---------------" << std::endl;
    //    for(unsigned int i=0;i<m->getNumAtoms();++i){
    //      std::cerr<<" "<<i+1<<" "<<atomRanks[i]<<std::endl;
    //    }
    //    std::cerr <<"---------------" << std::endl;
    smi1 = MolToSmiles(*m, true);
    delete m;
  }
  {
    std::string fName = rdbase + "/Code/GraphMol/test_data/canon_reorder2.mol";
    RWMol *m = MolFileToMol(fName, false, false);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    //    std::cerr <<">--------------" << std::endl;
    RDKit::Canon::rankMolAtoms(*m, atomRanks, false);
    //    std::cerr <<"---------------" << std::endl;
    //    for(unsigned int i=0;i<m->getNumAtoms();++i){
    //      std::cerr<<" "<<i+1<<" "<<atomRanks[i]<<std::endl;
    //    }
    //    std::cerr <<"---------------" << std::endl;
    smi2 = MolToSmiles(*m, true);
    delete m;
  }
  if (smi1 != smi2) {
    std::cerr << smi1 << "\n" << smi2 << std::endl;
  }
  TEST_ASSERT(smi1 == smi2);
  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

std::string smis[] = {
    "C[C@@H]1CCC[C@H](C)[C@H]1C", "N[C@@]1(C[C@H]([18F])C1)C(=O)O",
    "CC12CCCC1C1CCC3CC(O)CCC3(C)C1CC2",
    "CC(C)CCCC[C@@H]1C[C@H](/C=C/[C@]2(C)CC[C@H](O)CC2)[C@@H](O)[C@H]1O",
    "C[C@@]12CCC[C@H]1[C@@H]1CC[C@H]3C[C@@H](O)CC[C@]3(C)[C@H]1CC2",
    "CCCN[C@H]1CC[C@H](NC)CC1",
    "O=S(=O)(NC[C@H]1CC[C@H](CNCc2ccc3ccccc3c2)CC1)c1ccc2ccccc2c1",
    "CC(C)[C@H]1CC[C@H](C(=O)N[C@H](Cc2ccccc2)C(=O)O)CC1",
    "O=[N+]([O-])c1ccccc1S(=O)(=O)NC[C@H]1CC[C@H](CNCC2Cc3ccccc3CC2)CC1",
    "Oc1ccc2c(Cc3ccc(OCCN4CCCCC4)cc3)c([C@H]3CC[C@H](O)CC3)sc2c1",
    "O=C(c1ccc(OCCN2CCCCC2)cc1)c1c2ccc(O)cc2sc1[C@H]1CC[C@H](O)CC1",
    "N#Cc1ccc2c(c1)CCN(CC[C@@H]1CC[C@@H](NC(=O)c3ccnc4ccccc34)CC1)C2",
    "COCCOC[C@H](CC1(C(=O)N[C@H]2CC[C@@H](C(=O)O)CC2)CCCC1)C(=O)O",
    "c1ccc(CN[C@H]2CC[C@H](Nc3ccc4[nH]ncc4c3)CC2)cc1",
    "CCC1=C(C)CN(C(=O)NCCc2ccc(S(=O)(=O)NC(=O)N[C@H]3CC[C@H](C)CC3)cc2)C1=O",
    "C[C@H]1C[C@H](C1)N1CCC1", "C[C@H]1C[C@H](C1)N1CCN(C)CC1",
    "CN1CCN(CC1)[C@H]1C[C@H](C1)c1ncc2c(N)nccn12",
    "CN1CCN(CC1)[C@H]1C[C@H](C1)c1nc(-c2ccc3ccc(nc3c2)-c2ccccc2)c2c(N)nccn12",
    "C12C3C1C3C4C5C4C52", "N[C@H]1C2CC3CC1C[C@](O)(C3)C2",
    "O=C(CN1CCN(c2ccc(C(F)(F)F)cn2)CC1)N[C@H]1C2CC3CC1C[C@](O)(C3)C2",
    "COc1cc([C@H]2[C@H](C)[C@H](C)[C@H]2c2ccc(O)c(OC)c2)ccc1O",
    "N[C@@H]1[C@H]2CNC[C@@H]12",
    "N[C@@H]1[C@H]2CN(c3nc4c(cc3F)c(=O)c(C(=O)O)cn4C3CC3)C[C@@H]12",
    // some examples that came up while doing a torture test in ZINC
    "CN1CCCNCCN(C)CCC[NH2+]CC1", "CN1CCC[NH2+]CCN(C)CCC[NH+](C)CC1",
    "O=P([O-])([O-])C[NH+]1CCCN(CP(=O)([O-])O)CC[NH+](CP(=O)([O-])[O-])CCC[NH+]"
    "(CP(=O)([O-])[O-])CC1",
    "O=C(CCNC(=O)Cn1cnc2ccccc2c1=O)NCCc1c[nH]c2ccccc12",
    "C1CNCC[NH2+]CC(C2CNCC[NH2+]CCC[NH2+]CCNC2)CNCC[NH2+]C1",
    // C60
    "C12=C3C4=C5C6=C1C7=C8C9=C1C%10=C%11C(=C29)C3=C2C3=C4C4=C5C5=C9C6=C7C6="
    "C7C8=C1C1=C8C%10=C%10C%11=C2C2=C3C3=C4C4=C5C5=C%11C%12=C(C6=C95)C7=C1C1=C%"
    "12C5=C%11C4=C3C3=C5C(=C81)C%10=C23",
    // C70
    "C12=C3C4=C5C6=C7C8=C9C%10=C%11C%12=C%13C%10=C%10C8=C5C1=C%10C1=C%13C5="
    "C8C1=C2C1=C3C2=C3C%10=C%13C%14=C3C1=C8C1=C3C5=C%12C5=C8C%11=C%11C9=C7C7="
    "C9C6=C4C2=C2C%10=C4C(=C29)C2=C6C(=C8C8=C9C6=C4C%13=C9C(=C%141)C3=C85)C%11="
    "C27",
    // Bernd's example1
    "C12C3C4C3C3C1C2C43",
    // Bernd's example2
    "C12C3C1C1C4C5C4C4C6C(C6C3C3C1C43)C25",
    // doubled house
    "C12C3C45C67C8C9C66C14C21C35C78C961", "C12C3C45C6C7C11C27C41C356",
    // Problematic round-tripping
    "COC(=O)/C=C/C(C)=C/C=C/C(C)=C/C=C/C=C(/C)/C=C/C=C(\\C)/C=C/C(=O)[O-]",
    "COC(=O)/C=C/C(C)=C/C=C/C(C)=C/C=C/C=C(\\C)/C=C/C=C(\\C)/C=C/C(=O)[O-]",
    "c1cc2ccc(ccc3ccc1cc3)cc2", "C13C6C1C2C4C2C3C5C4C56",
    "C45C1C6C3C6C5C4C2C3C12", "C45C2C6C3C6C5C4C1C3C12",
    "Cl[C@H]1[C@@H](Cl)[C@H](Cl)[C@@H](Cl)[C@H](Cl)[C@@H]1Cl",
    "N[C@]1(C(=O)O)C[C@H](n2oc(=O)[nH]c2=O)C1", "CC1CC(C)CC(C)C1",
    "C[C@H]1C[C@@H](C)C[C@H](C)C1", "C[C@H]1C[C@@H](C)C[C@@H](C)C1",
    // Stereochemistry in large rings
    "C1[C@@H](C(C)=O)[C@H]2[C@H](C(C)=C1)[C@@H]1O[C@H]2[C@@H](O)C/C=C\\CC1",
    // Chembl 20 examples
    "COC(=O)CC[C@]12C[C@]13CC[C@]1(C)[C@@H]4[C@@H](C[C@@]1(C)[C@@H]3CC[C@H]2C("
    "C)(C)O)O[C@@]1(C[C@@H](C)C(=O)O1)C[C@H]4C",
    "C/C=C/"
    "[C@H](O)C(C)(C)[C@@H]1CC=CC=CC=C[C@H](OC)Cc2nc(co2)C(=O)O[C@H](C(C)(C)[C@@"
    "H](O)/C=C/C)CC=C[C@H]2O[C@H]2C=CC=Cc2nc(co2)C(=O)O1",
    "N[C@]1(C(=O)O)C[C@@H](n2oc(=O)[nH]c2=O)C1",
    "COc1ccc([C@H]2C3(CO)C4[N@](C)C5C2(CO)C2[N@](C)C3C4(CO)[C@H](c3ccc(OC)cc3)"
    "C52CO)cc1",
    "NCCNC(=O)[C@]1(O)C[C@@H](O)[C@H](O)[C@H](O)C1",
    // CIP renumber problems
    //  "O=P(O)(O)O[C@@H]1[C@H](OP(=O)(O)O)[C@@H](OP(=O)(O)O)[C@H](OP(=O)(O)O)[C@@H](OP(=O)(O)O[32P](=O)(O)O)[C@@H]1OP(=O)(O)O",
    //  "O=C(NCCO[C@H]1[C@@H](O)[C@H](OP(=O)(O)O)[C@@H](OP(=O)(O)O)[C@H](O)[C@H]1OP(=O)(O)O)NCCO[C@H]1[C@@H](O)[C@H](OP(=O)(O)O)[C@@H](O[PH](O)(O)O)[C@H](O)[C@H]1OP(=O)(O)O",
    //  "O=C(NCCO[C@@H]1[C@H](OP(=O)(O)O)[C@@H](OP(=O)(O)O)[C@H](O[PH](O)(O)O)[C@@H](OP(=O)(O)O)[C@@H]1OP(=O)(O)O)NCCO[C@H]1[C@@H](OP(=O)(O)O)[C@H](O)[C@@H](OP(=O)(O)O)[C@H](OP(=O)(O)O)[C@H]1O",
    //  "O=C(O)[C@H]1[C@@H](C(=O)O)[C@@H](C(=O)O)[C@@H]1C(=O)O",
    "C[C@H]1[C@H](C)[C@@H](C)[C@H]1C",
    "COc1cc([C@H]2[C@@](NC(=O)c3ccc(NC(=O)C4CCCC4)cc3)(C(=O)O)[C@@H](c3ccc(OC(="
    "O)c4cccs4)c(OC)c3)[C@]2(NC(=O)c2ccc(NC(=O)C3CCCC3)cc2)C(=O)O)ccc1OC(=O)"
    "c1cccs1",
    "COc1cc([C@H]2[C@@](NC(=O)c3ccc(NC(=O)OC(C)(C)C)cc3)(C(=O)O)[C@@H](c3ccc("
    "OC(=O)c4cccs4)c(OC)c3)[C@]2(NC(=O)c2ccc(NC(=O)OC(C)(C)C)cc2)C(=O)O)ccc1OC("
    "=O)c1cccs1",
    "COc1cc([C@H]2[C@](NC(=O)c3ccc(NC(=O)OC(C)(C)C)cc3)(C(=O)O)[C@H](c3ccc(OC(="
    "O)c4cccs4)c(OC)c3)[C@]2(NC(=O)c2ccc(NC(=O)OC(C)(C)C)cc2)C(=O)O)ccc1OC(=O)"
    "c1cccs1",
    "CCC[C@H]1CC[C@H]([C@H]2CC[C@H](OC(=O)[C@H]3[C@@H](c4ccc(O)cc4)[C@H](C(=O)"
    "O[C@H]4CC[C@H]([C@H]5CC[C@H](CCC)CC5)CC4)[C@@H]3c3ccc(O)cc3)CC2)CC1",
    // test molecules with atom-mapping numbers
    "[O:1]=[C:2]([CH2:3][C:4]1=[CH:5][CH:6]=[CH:7][CH:8]=[CH:9]1)[NH2:10]",
    // chembl molecules with multiple fragments
    // CHEMBL439119
    "CCCCCCC1C23C4=c5c6c7c8c9c%10c%11c%12c%13c%14c%15c%16c%17c%18c%19c%20c%21c%"
    "22c%23c(c5c5c6c6c8c%11c8c%11c%12c%15c%12c(c%20%16)c%21c%15c%23c5c(c68)c%"
    "15c%12%11)C2(C[N+]1(C)C)C%22C%19c1c-%18c2c5c(c13)C4C7C9=C5C1(C2C%17%14)C("
    "CCCCCC)[N+](C)(C)CC%10%131.[I-].[I-]",
    // CHEMBL1203199
    "C[C@H](NC(=O)[C@H]1Cc2c(sc3ccccc23)CN1)c1ccccc1.Cl",
    // CHEMBL501667
    "CCn1c2ccc3cc2c2cc(ccc21)C(=O)c1ccc(cc1)Cn1cc[n+](c1)Cc1ccc(cc1)-c1cccc(-"
    "c2ccc(cc2)C[n+]2ccn(c2)Cc2ccc(cc2)C3=O)c1C(=O)O.[Br-].[Br-]",
    // CHEMBL12438
    "CCCCCCCCCCCCCCCCCCNC(=O)OC[C@H]1C[C@H]([C@@H2]OC(=O)N(Cc2cccc[n+]2CC)C(C)="
    "O)C1.[I-]",
    // CHEMBL1172371
    "CC.CCCCCCCCCC(C(=O)NCCc1ccc(OP(=S)(Oc2ccc(CCNC(=O)C(CCCCCCCCC)P(=O)([O-])"
    "O)cc2)N(C)/N=C/c2ccc(OP3(Oc4ccc(/C=N/"
    "N(C)P(=S)(Oc5ccc(CCNC(=O)C(CCCCCCCCC)P(=O)([O-])O)cc5)Oc5ccc(CCNC(=O)C("
    "CCCCCCCCC)P(=O)([O-])O)cc5)cc4)=NP(Oc4ccc(/C=N/"
    "N(C)P(=S)(Oc5ccc(CCNC(=O)C(CCCCCCCCC)P(=O)([O-])O)cc5)Oc5ccc(CCNC(=O)C("
    "CCCCCCCCC)P(=O)([O-])O)cc5)cc4)(Oc4ccc(/C=N/"
    "N(C)P(=S)(Oc5ccc(CCNC(=O)C(CCCCCCCCC)P(=O)([O-])O)cc5)Oc5ccc(CCNC(=O)C("
    "CCCCCCCCC)P(=O)([O-])O)cc5)cc4)=NP(Oc4ccc(/C=N/"
    "N(C)P(=S)(Oc5ccc(CCNC(=O)C(CCCCCCCCC)P(=O)([O-])O)cc5)Oc5ccc(CCNC(=O)C("
    "CCCCCCCCC)P(=O)([O-])O)cc5)cc4)(Oc4ccc(/C=N/"
    "N(C)P(=S)(Oc5ccc(CCNC(=O)C(CCCCCCCCC)P(=O)([O-])O)cc5)Oc5ccc(CCNC(=O)C("
    "CCCCCCCCC)P(=O)([O-])O)cc5)cc4)=N3)cc2)cc1)P(=O)([O-])O.CCCCCCCCCCCCCCCC["
    "NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO.CCCCCCCCCCCCCCCC[NH2+]OC(CO)"
    "C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO.CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC("
    "CO)C(O)C(O)C1O)C(O)CO.CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)"
    "C1O)C(O)CO.CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO."
    "CCCCCCCCCCCCCCCC[NH2+]OC(CO)C(O)C(OC1OC(CO)C(O)C(O)C1O)C(O)CO",
    // Examples first reviewer
    "C12C3C4C1C3C1C3C4C1C23",  // does not initially work
    "C12C3C1C1C4C5C(C23)C5C14", "C12C3C4C5C1C1C4C5C3C21",
    "C12C3C4C1C2C1C2C4C2C31", "C12C3C4C5C2C2C6C1C(C5C36)C42",
    "C12C3C4C5C1C1C3C3C5C1C2C43",
    "C12C3C4C5C6C1C1C7C3C3C5C1C1C6C3C2C7C41",  // does not initially work
    "C12C3C4C5C6C1C1C7C3C3C6C6C4C7C2C3C5C16",
    "C12C3C4C5C6C7C8C9C1C6C1C(C37)C9C5C2C8C41",
    "C12C3C4C5C6C7C1C1C8C4C4C9C2C2C5C5C1C1C3C(C7C45)C2C8C6C91",  // does not
                                                                 // initially
                                                                 // work
    "C12C3C4C5C6C7C1C1C8C4C4C7C7C3C3C8C8C2C2C5C3C4C(C1C72)C68",
    "C12C3C4C5C6C7C8C1C1C9C5C5C%10C2C2C%11C%12C%13C3C3C7C%10C7C4C%11C1C3C(C5C8%"
    "12)C(C62)C7C9%13",  // does not initially work
    // drawn examples first reviewer
    "C12C3C4C1CC5C46C7C5C1C57C6C53C1C2",
    "C1C2C3C4CC5C6C1C17C8C61C5C48C3C27", "EOS"};

void test7() {
  BOOST_LOG(rdInfoLog) << "testing stability w.r.t. renumbering." << std::endl;
  unsigned int i = 0;
  while (smis[i] != "EOS") {
    std::string smiles = smis[i++];
    ROMol *m = SmilesToMol(smiles);
    TEST_ASSERT(m);
    MolOps::assignStereochemistry(*m, true);
    _renumberTest(m, smiles, 500);
    delete m;
  }
  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

std::string molbl1 =
    "CHEMBL1950780                                                        \n"
    "     RDKit          2D                                               \n"
    "                                                                     \n"
    " 12 12  0  0  0  0  0  0  0  0999 V2000                              \n"
    "   16.2083   -6.1750    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   16.2083   -7.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   16.9204   -7.4083    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   17.6324   -7.0000    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   17.6324   -6.1750    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   16.9204   -5.7583    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   16.9204   -4.9333    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   18.3480   -5.7646    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   18.3462   -7.4135    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   16.9204   -8.2333    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   15.4945   -7.4135    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   15.4927   -5.7646    0.0000 O   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "  5  6  1  0                                                         \n"
    "  6  7  1  6                                                         \n"
    "  1  2  1  0                                                         \n"
    "  5  8  1  6                                                         \n"
    "  1  6  1  0                                                         \n"
    "  4  9  1  6                                                         \n"
    "  2  3  1  0                                                         \n"
    "  3 10  1  1                                                         \n"
    "  3  4  1  0                                                         \n"
    "  2 11  1  6                                                         \n"
    "  4  5  1  0                                                         \n"
    "  1 12  1  6                                                         \n"
    "M  END";

std::string molbl2 =
    "CHEMBL1874247                                                        \n"
    "     RDKit          2D                                               \n"
    "                                                                     \n"
    " 12 12  0  0  0  0  0  0  0  0999 V2000                              \n"
    "    0.0000    1.6500    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    1.4289    0.8250    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   -1.4289    0.8250    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    1.4289   -0.8250    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   -1.4289   -0.8250    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    0.0000   -1.6500    0.0000 Cl  0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    0.0000    0.8250    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    0.7145    0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   -0.7145    0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    0.7145   -0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "   -0.7145   -0.4125    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "    0.0000   -0.8250    0.0000 C   0  0  0  0  0  0  0  0  0  0  0  0\n"
    "  7  1  1  1                                                         \n"
    "  8  2  1  1                                                         \n"
    "  9  3  1  1                                                         \n"
    " 10  4  1  6                                                         \n"
    " 11  5  1  6                                                         \n"
    " 12  6  1  1                                                         \n"
    "  7  8  1  0                                                         \n"
    "  7  9  1  0                                                         \n"
    "  8 10  1  0                                                         \n"
    "  9 11  1  0                                                         \n"
    " 10 12  1  0                                                         \n"
    " 11 12  1  0                                                         \n"
    "M  END";

void test8() {
  BOOST_LOG(rdInfoLog) << "testing smiles round-tripping." << std::endl;
  std::string rdbase = getenv("RDBASE");
  {
    std::string fName = rdbase + "/Code/GraphMol/test_data/iChi1b.mol";
    RWMol *m = MolFileToMol(fName);
    TEST_ASSERT(m);
    std::string smi1 = MolToSmiles(*m, true);
    delete m;
    m = SmilesToMol(smi1);
    TEST_ASSERT(m);
    std::string smi2 = MolToSmiles(*m, true);
    if (smi1 != smi2) std::cerr << smi1 << "\n" << smi2 << std::endl;
    TEST_ASSERT(smi1 == smi2);
    delete m;
  }

  {
    unsigned int i = 0;
    while (smis[i] != "EOS") {
      std::string smiles = smis[i++];
      ROMol *m = SmilesToMol(smiles);
      TEST_ASSERT(m);
      //      std::cerr<<"MolToSMILES 1"<<std::endl;
      std::string smi1 = MolToSmiles(*m, true);
      delete m;

      m = SmilesToMol(smi1);
      TEST_ASSERT(m);
      //      std::cerr<<"MolToSMILES 2"<<std::endl;
      std::string smi2 = MolToSmiles(*m, true);
      delete m;
      if (smi1 != smi2) {
        std::cerr << "Input smiles: " << smiles << "\n1. Iter: " << smi1
                  << "\n2. Iter: " << smi2 << std::endl;
      }
      TEST_ASSERT(smi1 == smi2);
    }
    {
      ROMol *m = MolBlockToMol(molbl1);
      TEST_ASSERT(m);
      std::string smiles = MolToSmiles(*m, true);
      delete m;

      m = SmilesToMol(smiles);
      TEST_ASSERT(m);
      //      std::cerr<<"MolToSMILES 1"<<std::endl;
      std::string smi1 = MolToSmiles(*m, true);
      delete m;

      if (smiles != smi1) {
        std::cerr << smiles << "\n" << smi1 << std::endl;
      }
      TEST_ASSERT(smiles == smi1);
    }
    {
      ROMol *m = MolBlockToMol(molbl2);
      TEST_ASSERT(m);
      std::string smiles = MolToSmiles(*m, true);
      delete m;

      m = SmilesToMol(smiles);
      TEST_ASSERT(m);
      //      std::cerr<<"MolToSMILES 1"<<std::endl;
      std::string smi1 = MolToSmiles(*m, true);
      delete m;

      if (smiles != smi1) {
        std::cerr << smiles << "\n" << smi1 << std::endl;
      }
      TEST_ASSERT(smiles == smi1);
    }
  }

  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

void test9() {
  BOOST_LOG(rdInfoLog) << "testing chiral invariants." << std::endl;
  std::string rdbase = getenv("RDBASE");

  {
    std::string smi = "C[C@](F)(Cl)I";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[0] < atomRanks[2]);
    TEST_ASSERT(atomRanks[0] < atomRanks[3]);
    TEST_ASSERT(atomRanks[0] < atomRanks[4]);
    TEST_ASSERT(atomRanks[2] < atomRanks[3]);
    TEST_ASSERT(atomRanks[2] < atomRanks[4]);
    TEST_ASSERT(atomRanks[3] < atomRanks[4]);
  }

  {
    std::string smi = "CC[C@](F)(Cl)C=C";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[1] < atomRanks[3]);
    TEST_ASSERT(atomRanks[1] < atomRanks[4]);
    TEST_ASSERT(atomRanks[1] < atomRanks[5]);
    TEST_ASSERT(atomRanks[3] < atomRanks[4]);
    TEST_ASSERT(atomRanks[4] > atomRanks[5]);
    TEST_ASSERT(atomRanks[4] > atomRanks[5]);
  }

  {
    // make sure we aren't breaking ties
    std::string smi = "C[C@](C)(Cl)I";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[0] == atomRanks[2]);
    TEST_ASSERT(atomRanks[0] < atomRanks[3]);
    TEST_ASSERT(atomRanks[0] < atomRanks[4]);
    TEST_ASSERT(atomRanks[2] < atomRanks[3]);
    TEST_ASSERT(atomRanks[2] < atomRanks[4]);
    TEST_ASSERT(atomRanks[3] < atomRanks[4]);
  }

  {
    std::string smi = "N[C@H]1C2CC3CC1C[C@](O)(C3)C2";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[0] > atomRanks[1]);
    TEST_ASSERT(atomRanks[0] < atomRanks[9]);
    TEST_ASSERT(atomRanks[2] == atomRanks[6]);
    TEST_ASSERT(atomRanks[7] == atomRanks[11]);
    TEST_ASSERT(atomRanks[3] == atomRanks[5]);
    TEST_ASSERT(atomRanks[2] > atomRanks[3]);
    TEST_ASSERT(atomRanks[2] > atomRanks[11]);
    TEST_ASSERT(atomRanks[3] < atomRanks[11]);
  }

  {
    // this one was a chiral ranking problem
    std::string smi = "COC(C)CC(C)(C)O";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[1] > atomRanks[8]);
    TEST_ASSERT(atomRanks[5] > atomRanks[2]);
  }

  {
    // are double bonds being handled correctly?
    std::string smi = "OC[C@H](F)C=O";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[0] < atomRanks[5]);
    TEST_ASSERT(atomRanks[1] < atomRanks[4]);
  }

  {
    // are double bonds being handled correctly?
    std::string smi = "O=C[C@H](F)CO";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[0] > atomRanks[5]);
    TEST_ASSERT(atomRanks[1] > atomRanks[4]);
  }

  {
    // are double bonds being handled correctly?
    std::string smi = "CC[C@](C)(CF)C=O";
    RWMol *m = SmilesToMol(smi, 0, 0);
    TEST_ASSERT(m);
    MolOps::sanitizeMol(*m);
    std::vector<unsigned int> atomRanks;
    // std::cerr<<smi<<std::endl;
    RDKit::Canon::chiralRankMolAtoms(*m, atomRanks);
    // std::copy(atomRanks.begin(),atomRanks.end(),std::ostream_iterator<unsigned
    // int>(std::cerr," "));
    // std::cerr<<std::endl;
    TEST_ASSERT(atomRanks[4] > atomRanks[6]);
    TEST_ASSERT(atomRanks[1] < atomRanks[4]);
  }

  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

void test10() {
  BOOST_LOG(rdInfoLog) << "testing unique ranks in w.r.t. renumbering."
                       << std::endl;
  unsigned int i = 0;
  while (smis[i] != "EOS") {
    std::string smiles = smis[i++];
    //    std::cerr<< ">>>Molecule: " << smiles << std::endl;
    ROMol *m = SmilesToMol(smiles);
    TEST_ASSERT(m);
    MolOps::assignStereochemistry(*m, true);
    _renumberTest2(m, smiles, 1);
    delete m;
  }
  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

void test11() {
  BOOST_LOG(rdInfoLog) << "testing mol fragments." << std::endl;
  {
    std::string smi =
        "C[C@H]([C@H](c1ccccc1)O)N2CCCCC2.C[C@@H]([C@H](c1ccccc1)O)N2CCCCC2";
    ROMol *m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::vector<std::string> vfragsmi;
    std::vector<std::vector<int> > frags;
    unsigned int numFrag = MolOps::getMolFrags(*m, frags);
    for (unsigned i = 0; i < numFrag; ++i) {
      std::string smii = MolFragmentToSmiles(*m, frags[i], 0, 0, 0, true);
      // std::cout << "Test "<< smii << std::endl;
      vfragsmi.push_back(smii);
    }
    std::string smi1 = MolToSmiles(*m, true);
    delete m;

    smi = "C[C@@H]([C@H](c1ccccc1)O)N2CCCCC2.C[C@H]([C@H](c1ccccc1)O)N2CCCCC2";
    m = SmilesToMol(smi);
    TEST_ASSERT(m);
    std::string smi2 = MolToSmiles(*m, true);
    delete m;
    // std::cout << smi1 << "\n" << smi2 << std::endl;
    TEST_ASSERT(smi1 == smi2);
  }
  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

void test12() {
  BOOST_LOG(rdInfoLog) << "testing protein round-tripping." << std::endl;
  std::string rdbase = getenv("RDBASE");
  {
    std::string fName =
        rdbase + "/Code/GraphMol/FileParsers/test_data/2FVD.pdb";
    ROMol *m = PDBFileToMol(fName);
    TEST_ASSERT(m);
    std::string smi1 = MolToSmiles(*m, true);
    delete m;

    m = SmilesToMol(smi1);
    TEST_ASSERT(m);
    std::string smi2 = MolToSmiles(*m, true);
    delete m;
    // std::cout << smi1 << "\n" << smi2 << std::endl;
    TEST_ASSERT(smi1 == smi2);
  }
  BOOST_LOG(rdInfoLog) << "Finished" << std::endl;
}

int main() {
  RDLog::InitLogs();
#if 1
  test1();
  test2();
  test3();
  test4();
  test5();
  test6();
  test7a();
  test9();
  test10();
  test11();
  test12();
  test7();
  test8();
#endif

  return 0;
}


#ifndef SAC_SEGMENTATION_HPP
#define SAC_SEGMENTATION_HPP

#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/filters/project_inliers.h>
#include <boost/make_shared.hpp>

#include <pcl_ros_wrapper/common/types.hpp>
#include <pcl_ros_wrapper/common/type_traits.hpp>
#include <pcl_ros_wrapper/filters/crop_box.hpp>

namespace pcl_ros_wrapper {
namespace segmentation {

  /**
   * @brief Information obtained after plane detection using sample consensus methods.
   *
   */
  struct plane_info
  {
    pcl::PointIndices::Ptr      inliers;
    pcl::ModelCoefficients::Ptr coefficients;
  };

  /**
   * @brief Parameters used for plane detection methods.
   *
   */
  struct plane_detection_params
  {
    int    ransac_iterations;
    double distance_threshold;
  };

  /**
   * @brief Estimate plane information from the given point cloud.
   *
   * @tparam T Type of the given point cloud.
   * @param input Given point cloud.
   * @param params Plane detection parameters.
   * @return Returns detected plane information.
   */
  template<typename T>
  plane_info do_plane_detection(const T& input, const plane_detection_params& params)
  {
    using CloudType = RemoveConst<element_type_t<T>>;
    using PointType = typename CloudType::value_type;

    pcl::SACSegmentation<PointType> seg;
    seg.setOptimizeCoefficients(true);
    seg.setModelType(pcl::SACMODEL_PLANE);
    seg.setMethodType(pcl::SAC_RANSAC);
    seg.setMaxIterations(params.ransac_iterations);
    seg.setDistanceThreshold(params.distance_threshold);

    if constexpr (std::is_same<T, element_type_t<T>>::value) {
      seg.setInputCloud(boost::make_shared<element_type_t<T>>(input));
    } else {
      seg.setInputCloud(input);
    }

    auto inliers      = boost::make_shared<pcl::PointIndices>();
    auto coefficients = boost::make_shared<pcl::ModelCoefficients>();
    seg.segment(*inliers, *coefficients);
    return plane_info{ inliers, coefficients };
  }

  /**
   * @brief Crop the largest ground plane from the given input cloud.
   *
   * @param input Given input cloud.
   * @param params Plane detection parameters.
   * @param ground_depth Depth of the cropped ground cloud.
   * @param verbose Output shown if true.
   */
  void crop_ground_plane(PointCloudT::Ptr&             input,
                         const plane_detection_params& params,
                         double                        ground_depth = 1.5,
                         bool                          verbose      = false);

  /**
   * @brief Iteratively detect and remove ground planes from the given point cloud.
   *
   * @param input Given point cloud.
   * @param params Plane detection parameters.
   * @param max_iters_plane_removal Maximum iteration for plane removal.
   * @param verbose Output shown if true.
   */
  void iterative_ground_plane_filter(PointCloudT::Ptr&             input,
                                     const plane_detection_params& params,
                                     int  max_iters_plane_removal,
                                     bool verbose = false);

  /**
   * @brief Parameters used for pointcloud clustering.
   *
   */
  struct cluster_params
  {
    int    min_cluster_size;
    int    max_cluster_size;
    double cluster_tolerance;
  };

  /**
   * @brief Perform distance-based (euclidean) clustering on the given input cloud.
   *
   * @param input Given point cloud input.
   * @param params Clustering parameters.
   * @param verbose Output shown if true.
   * @return Vector of pointers to pointcloud clusters.
   */
  std::vector<PointCloudT::Ptr> do_euclidean_clustering(
    const PointCloudT::ConstPtr& input,
    const cluster_params&        params,
    bool                         verbose = false);

  /**
   * @brief Project point cloud to a plane.
   *
   * @param cloud Given point cloud.
   * @param coefficients Given plane coefficients.
   * @return A Pointer to the projected point cloud.
   */
  PointCloudT::Ptr project_to_plane(const PointCloudT::Ptr&            cloud,
                                    const pcl::ModelCoefficients::Ptr& coefficients);
}// namespace segmentation
}// namespace pcl_ros_wrapper

#endif /* SAC_SEGMENTATION_HPP */