#include <cvecmat.h>
#include <ccp4_spg.h>
#include <csymlib.h>
#include <scitbx/math/utils.h>
#include <cctbx/miller/asu.h>
#include <iotbx/mtz/batch.h>
#include <iotbx/mtz/column.h>
#include <iotbx/error.h>
#include <boost/scoped_array.hpp>
#include <boost/optional.hpp>

namespace iotbx { namespace mtz {

  af::shared<std::size_t>
  cmtz_struct_sizes()
  {
    af::shared<std::size_t> result;
    result.push_back(sizeof(CMtz::MTZCOL));
    result.push_back(sizeof(CMtz::MTZSET));
    result.push_back(sizeof(CMtz::MTZXTAL));
    result.push_back(sizeof(CMtz::MTZBAT));
    result.push_back(sizeof(CMtz::SYMGRP));
    result.push_back(sizeof(CMtz::MTZ));
    return result;
  }

  object::object()
  :
    ptr_(CMtz::MtzMalloc(0, 0), ptr_deleter)
  {
    if (ptr_.get() == 0) throw cctbx::error("MtzMalloc failed.");
    ptr_->refs_in_memory = true;
    init_not_a_number_value();
  }

  object::object(const char* file_name)
  {
    IOTBX_ASSERT(file_name != 0);
    ptr_ = boost::shared_ptr<CMtz::MTZ>(
      CMtz::MtzGetUserCellTolerance(file_name, true, 0), ptr_deleter);
    if (ptr_.get() == 0) {
      throw cctbx::error(std::string("MTZ file read error: ") + file_name);
    }
    init_not_a_number_value();
  }

  void
  object::init_not_a_number_value()
  {
    // based on code from MtzAddColumn()
    if (strncmp(ptr()->mnf.amnf, "NAN", 3U) == 0) {
      not_a_number_value_ = CCP4::ccp4_nan();
    }
    else {
      not_a_number_value_.f = ptr()->mnf.fmnf;
    }
  }

  std::string
  object::title() const
  {
    char result[sizeof(ptr()->title)];
    int title_length = CMtz::ccp4_lrtitl(ptr(), result);
    return std::string(result, title_length);
  }

  object&
  object::set_title(const char* title, bool append)
  {
    IOTBX_ASSERT(title != 0);
    int set_title_success = CMtz::ccp4_lwtitl(ptr(), title, append);
    IOTBX_ASSERT(set_title_success);
    return *this;
  }

  af::shared<std::string>
  object::history() const
  {
    CMtz::MTZ* p = ptr();
    af::shared<std::string> result((af::reserve(p->histlines)));
    for(int i=0;i<p->histlines;i++) {
      const char* line = p->hist+MTZRECORDLENGTH*i;
      int j = 0;
      for(;j<MTZRECORDLENGTH;j++) {
        if (line[j] == '\0') break;
      }
      result.push_back(std::string(line, j));
    }
    return result;
  }

  object&
  object::add_history(af::const_ref<std::string> const& lines)
  {
    boost::shared_ptr<char> buffer(
      CMtz::MtzCallocHist(lines.size()), CMtz::MtzFreeHist);
    for(std::size_t i=0;i<lines.size();i++) {
      strncpy(
        buffer.get()+i*MTZRECORDLENGTH,
        lines[i].c_str(),
        std::min(
          static_cast<std::size_t>(MTZRECORDLENGTH),
          lines[i].size()));
    }
    int add_history_success = CMtz::MtzAddHistory(
      ptr(),
      reinterpret_cast<char (*)[MTZRECORDLENGTH]>(buffer.get()),
      lines.size());
    IOTBX_ASSERT(add_history_success);
    return *this;
  }

  object&
  object::add_history(const char* line)
  {
    IOTBX_ASSERT(line != 0);
    return add_history(af::tiny<std::string, 1>(line).const_ref());
  }

  object&
  object::set_space_group_name(const char* name)
  {
    IOTBX_ASSERT(name != 0);
    char* target = ptr()->mtzsymm.spcgrpname;
    const unsigned target_size = sizeof(ptr()->mtzsymm.spcgrpname);
    strncpy(target, name, target_size-1);
    target[target_size-1] = '\0';
    return *this;
  }

  object&
  object::set_point_group_name(const char* name)
  {
    IOTBX_ASSERT(name != 0);
    char* target = ptr()->mtzsymm.pgname;
    const unsigned target_size = sizeof(ptr()->mtzsymm.pgname);
    strncpy(target, name, target_size-1);
    target[target_size-1] = '\0';
    return *this;
  }

  cctbx::sgtbx::space_group
  object::space_group() const
  {
    CMtz::MTZ* p = ptr();
    cctbx::sgtbx::space_group result;
    scitbx::mat3<double> rm;
    scitbx::vec3<double> tv;
    for(int im=0;im<p->mtzsymm.nsym;im++) {
      for (int ir=0;ir<3;ir++) {
        for (int ic=0;ic<3;ic++) {
          rm(ir,ic) = p->mtzsymm.sym[im][ir][ic];
        }
        tv[ir] = p->mtzsymm.sym[im][ir][3];
      }
      result.expand_smx(cctbx::sgtbx::rt_mx(rm, tv));
    }
    return result;
  }

  object&
  object::set_space_group(cctbx::sgtbx::space_group const& space_group)
  {
    CMtz::MTZ* p = ptr();
    IOTBX_ASSERT(sizeof(p->mtzsymm.sym) / sizeof(*p->mtzsymm.sym)
              >= space_group.order_z());
    p->mtzsymm.nsymp = static_cast<int>(space_group.order_p());
    p->mtzsymm.nsym = static_cast<int>(space_group.order_z());
    for (int im=0;im<p->mtzsymm.nsym;im++) {
      cctbx::sgtbx::rt_mx sm = space_group(im).mod_positive();
      cctbx::sgtbx::rot_mx rm = sm.r();
      cctbx::sgtbx::tr_vec tv = sm.t();
      scitbx::mat3<int> rm_num = rm.num();
      float rm_den = rm.den();
      scitbx::vec3<int> tv_num = tv.num();
      float tv_den = tv.den();
      for (int ir=0;ir<3;ir++) {
        for (int ic=0;ic<3;ic++) {
          p->mtzsymm.sym[im][ir][ic] = rm_num(ir,ic)/rm_den;
        }
        p->mtzsymm.sym[im][ir][3] = tv_num[ir]/tv_den;
      }
      for (int ic=0;ic<3;ic++) {
        p->mtzsymm.sym[im][3][ic] = 0.;
      }
      p->mtzsymm.sym[im][3][3] = 1.;
    }
    return *this;
  }

  void
  object::reserve(int capacity)
  {
    CMtz::MTZ* p = ptr();
    if (!p->refs_in_memory) return;
    for(int i=0;i<p->nxtal;i++) {
      for(int j=0;j<p->xtal[i]->nset;j++) {
        for(int k=0;k<p->xtal[i]->set[j]->ncol;k++) {
          capacity = std::max(
            capacity,
            column_array_capacity(p->xtal[i]->set[j]->col[k]));
        }
      }
    }
    for(int i=0;i<p->nxtal;i++) {
      for(int j=0;j<p->xtal[i]->nset;j++) {
        for(int k=0;k<p->xtal[i]->set[j]->ncol;k++) {
          ccp4array_reserve(p->xtal[i]->set[j]->col[k]->ref, capacity);
        }
      }
    }
  }

  void
  object::adjust_column_array_sizes(int new_nref)
  {
    CMtz::MTZ* p = ptr();
    if (!p->refs_in_memory) return;
    if (new_nref > p->nref) {
      reserve(new_nref);
      for(int i=0;i<p->nxtal;i++) {
        for(int j=0;j<p->xtal[i]->nset;j++) {
          for(int k=0;k<p->xtal[i]->set[j]->ncol;k++) {
            CMtz::MTZCOL* col_k = p->xtal[i]->set[j]->col[k];
            int old_size = column_array_size(col_k);
            if (new_nref > old_size) {
              ccp4array_resize(col_k->ref, new_nref);
              for(int iref=old_size;iref<new_nref;iref++) {
                *(reinterpret_cast<union float_uint_uchar*>(
                  &col_k->ref[iref])) = not_a_number_value_;
              }
            }
          }
        }
      }
    }
  }

  af::shared<batch>
  object::batches() const
  {
    af::shared<batch> result((af::reserve(n_batches())));
    for(int i_batch=0;i_batch<n_batches();i_batch++) {
      result.push_back(batch(*this, i_batch));
    }
    return result;
  }

  batch
  object::add_batch()
  {
    CMtz::MTZBAT* p = ptr()->batch;
    CMtz::MTZBAT* p_tail = p;
    int max_batch_number = 0;
    int i_batch = 0;
    for(;;i_batch++) {
      if (p == 0) break;
      max_batch_number = std::max(max_batch_number, p->num);
      p_tail = p;
      p = p->next;
    }
    boost::scoped_array<float> buf(new float[NBATCHINTEGERS+NBATCHREALS]);
    std::fill_n(buf.get(), NBATCHINTEGERS+NBATCHREALS, static_cast<float>(0));
#if defined(__DECCXX_VER)
    BOOST_STATIC_ASSERT(sizeof(float) == sizeof(int));
#else
    IOTBX_ASSERT(sizeof(float) == sizeof(int));
#endif
    IOTBX_ASSERT(CMtz::ccp4_lwbat(
      ptr(), 0, max_batch_number+1, buf.get(), "") == 1);
    p = (p_tail == 0 ? ptr()->batch : p_tail->next);
    IOTBX_ASSERT(p != 0);
    IOTBX_ASSERT(p->next == 0);
    IOTBX_ASSERT(p->num == max_batch_number+1);
    return batch(*this, i_batch);
  }

  af::shared<crystal>
  object::crystals() const
  {
    af::shared<crystal> result((af::reserve(n_crystals())));
    for(int i_crystal=0;i_crystal<n_crystals();i_crystal++) {
      result.push_back(crystal(*this, i_crystal));
    }
    return result;
  }

  crystal
  object::add_crystal(
    const char* name,
    const char* project_name,
    af::double6 const& unit_cell_parameters)
  {
    IOTBX_ASSERT(name != 0);
    IOTBX_ASSERT(project_name != 0);
    float uc_params[6];
    for(int i=0;i<6;i++) uc_params[i] = unit_cell_parameters[i];
    int i_crystal = n_crystals();
    CMtz::MTZXTAL* crystal_ptr = CMtz::MtzAddXtal(
      ptr(), name, project_name, uc_params);
    IOTBX_ASSERT(crystal_ptr != 0);
    IOTBX_ASSERT(n_crystals() == i_crystal+1);
    crystal result(*this, i_crystal);
    IOTBX_ASSERT(result.ptr() == crystal_ptr);
    return result;
  }

  crystal
  object::add_crystal(
    const char* name,
    const char* project_name,
    cctbx::uctbx::unit_cell const& unit_cell)
  {
    return add_crystal(name, project_name, unit_cell.parameters());
  }

  bool
  object::has_crystal(const char* name) const
  {
    IOTBX_ASSERT(name != 0);
    for(int i_crystal=0;i_crystal<n_crystals();i_crystal++) {
      crystal x(*this, i_crystal);
      if (std::strcmp(x.name(), name) == 0) {
        return true;
      }
    }
    return false;
  }

  bool
  object::has_column(const char* label) const
  {
    IOTBX_ASSERT(label != 0);
    for(int i_crystal=0;i_crystal<n_crystals();i_crystal++) {
      crystal x(*this, i_crystal);
      for(int i_dataset=0;i_dataset<x.n_datasets();i_dataset++) {
        dataset s(x, i_dataset);
        for(int i_column=0;i_column<s.n_columns();i_column++) {
          column c(s, i_column);
          if (CMtz::MtzPathMatch(label, c.label())) {
            return true;
          }
        }
      }
    }
    return false;
  }

  column
  object::get_column(const char* label) const
  {
    IOTBX_ASSERT(label != 0);
    for(int i_crystal=0;i_crystal<n_crystals();i_crystal++) {
      crystal x(*this, i_crystal);
      for(int i_dataset=0;i_dataset<x.n_datasets();i_dataset++) {
        dataset s(x, i_dataset);
        for(int i_column=0;i_column<s.n_columns();i_column++) {
          column c(s, i_column);
          if (CMtz::MtzPathMatch(label, c.label())) {
            return c;
          }
        }
      }
    }
    throw cctbx::error(std::string("Unknown MTZ column label: ") + label);
  }

  hkl_columns
  object::get_hkl_columns() const
  {
    return hkl_columns(
      get_column("H"),
      get_column("K"),
      get_column("L"));
  }

  af::double2
  object::max_min_resolution() const
  {
    double d_star_sq_min = -1;
    double d_star_sq_max = -1;
    int n_refl = n_reflections();
    int n_crys = n_crystals();
    if (n_refl > 0 && n_crys > 0) {
      hkl_columns hkl = get_hkl_columns();
      std::vector<cctbx::uctbx::unit_cell> unit_cells;
      for(int i_crystal=0;i_crystal<n_crys;i_crystal++) {
        unit_cells.push_back(crystal(*this, i_crystal).unit_cell());
      }
      for(int i_refl=0;i_refl<n_refl;i_refl++) {
        for(int i_crystal=0;i_crystal<n_crys;i_crystal++) {
          double d_star_sq = unit_cells[i_crystal].d_star_sq(
            hkl.get_miller_index(i_refl));
          if (d_star_sq_min > d_star_sq || d_star_sq_min < 0) {
              d_star_sq_min = d_star_sq;
          }
          if (d_star_sq_max < d_star_sq) {
              d_star_sq_max = d_star_sq;
          }
        }
      }
    }
    return af::double2(
      d_star_sq_min <= 0 ? -1 : 1/std::sqrt(d_star_sq_min),
      d_star_sq_max <= 0 ? -1 : 1/std::sqrt(d_star_sq_max));
  }

  namespace {
    inline
    std::complex<double>
    polar_deg(double ampl, double phi)
    {
      return ampl * scitbx::math::unit_complex(phi*scitbx::constants::pi_180);
    }
  }

  af::shared<cctbx::miller::index<> >
  object::extract_miller_indices() const
  {
    int n_refl = n_reflections();
    af::shared<cctbx::miller::index<> > result((af::reserve(n_refl)));
    hkl_columns hkl = get_hkl_columns();
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      result.push_back(hkl.get_miller_index(i_refl));
    }
    return result;
  }

  void
  object::replace_miller_indices(
    af::const_ref<cctbx::miller::index<> > const& miller_indices)
  {
    IOTBX_ASSERT(miller_indices.size() == n_reflections());
    hkl_columns hkl = get_hkl_columns();
    for(int i_refl=0;i_refl<miller_indices.size();i_refl++) {
      hkl.replace_miller_index(i_refl, miller_indices[i_refl]);
    }
  }

  CSym::ccp4_symop Qmat4_to_rotandtrn(const float rsm[4][4]) {

    int i,j;
    CSym::ccp4_symop symop;

    for (i = 0; i < 3; ++i) {
      for (j = 0; j < 3; ++j)
        symop.rot[i][j]=rsm[i][j];
      symop.trn[i]=rsm[i][3];
    }

    return (symop);
  }

  void Qrotandtrn_to_mat4(float rsm[4][4], const CSym::ccp4_symop symop) {

    int i,j;

    for (i = 0; i < 3; ++i) {
      for (j = 0; j < 3; ++j)
        rsm[i][j]=symop.rot[i][j];
      rsm[i][3]=symop.trn[i];
      rsm[3][i]=0.0;
    }
    rsm[3][3]=1.0;
  }

  //copied from csymlib.c to avoid windows compilation error, unresolved symbol ccp4printf
  CSym::ccp4_symop ccp4_symop_invert_( const CSym::ccp4_symop op1 )
  {
    float rot1[4][4],rot2[4][4];

    Qrotandtrn_to_mat4(rot1,op1);
    invert4matrix((const float (*)[4])rot1,rot2);
    return (Qmat4_to_rotandtrn((const float (*)[4])rot2));
  }

  int
  iround(float x) { return scitbx::math::float_int_conversions<float,int>::iround(x); }

  af::shared<cctbx::miller::index<> >
  object::extract_original_index_miller_indices(
    const char* column_label_m_isym) const
  {
    integer_group m_isym( extract_integers(column_label_m_isym) );
    int n_refl = n_reflections();
    af::shared<cctbx::miller::index<> > result;
    // Use of the CCP4 library suggested by David Waterman;
    // code example documented at ftp://ftp.ccp4.ac.uk/mdw/csym/cmtz_csym_eg.c

    CMtz::MTZ* p = ptr();
    using CSym::ccp4_symop;

    scitbx::af::shared<ccp4_symop> op1(p->mtzsymm.nsym);
    for(int i = 0 ; i < p->mtzsymm.nsym; ++i) {
      for (int k = 0; k < 3; ++k) {
        for (int l=0; l<3; ++l) {
          op1[i].rot[k][l] = p->mtzsymm.sym[i][k][l];
        }
        op1[i].trn[k] = p->mtzsymm.sym[i][k][3];
      }
    }

    scitbx::af::shared<ccp4_symop> invsymop(p->mtzsymm.nsym);
    for(int i = 0 ; i < p->mtzsymm.nsym; ++i) {
      invsymop[i] = ccp4_symop_invert_(op1[i]);
    }

    for (int i_refl=0; i_refl<n_refl; ++i_refl){
      // Take the CCP4 definition M/ISYM = 256M+ISYM, where M=0 (full) or M=1 (partial).
      // Therefore we discard the partiality flag M, just getting ISYM.
      int isym_value = m_isym.data[i_refl] % 256;
      int jsym = (isym_value - 1) / 2;
      int isign = (isym_value % 2) ? 1 : -1 ;

      cctbx::miller::index<> asu_hkl = m_isym.indices[i_refl];

      //algorithm borrowed from CSym::ccp4spg_generate_indices(...)
      result.push_back(cctbx::miller::index<>(
         isign *     iround(asu_hkl[0]*invsymop[jsym].rot[0][0] +
                            asu_hkl[1]*invsymop[jsym].rot[1][0] +
                            asu_hkl[2]*invsymop[jsym].rot[2][0]),
         isign *     iround(asu_hkl[0]*invsymop[jsym].rot[0][1] +
                            asu_hkl[1]*invsymop[jsym].rot[1][1] +
                            asu_hkl[2]*invsymop[jsym].rot[2][1]),
         isign *     iround(asu_hkl[0]*invsymop[jsym].rot[0][2] +
                            asu_hkl[1]*invsymop[jsym].rot[1][2] +
                            asu_hkl[2]*invsymop[jsym].rot[2][2])

      ));
    }
    return result;
  }

  void
  object::replace_original_index_miller_indices(
    af::const_ref<cctbx::miller::index<> > const &indices,
    const char* column_label_m_isym)
  {
    column m_isym(get_column(column_label_m_isym));
    hkl_columns hkl = get_hkl_columns();
    int n_refl = n_reflections();

    CMtz::MTZ* p = ptr();

    // no_expand = true, since we need the symmetry operators in the same
    // order as in the mtz file
    cctbx::sgtbx::space_group sg(true /* no_expand */);
    scitbx::mat3<double> rm;
    scitbx::vec3<double> tv;
    for(int im=0;im<p->mtzsymm.nsym;im++) {
      for (int ir=0;ir<3;ir++) {
        for (int ic=0;ic<3;ic++) {
          rm(ir,ic) = p->mtzsymm.sym[im][ir][ic];
        }
        tv[ir] = p->mtzsymm.sym[im][ir][3];
      }
      sg.expand_smx(cctbx::sgtbx::rt_mx(rm, tv));
    }

    cctbx::sgtbx::reciprocal_space::asu asu(sg.type());

    // http://www.ccp4.ac.uk/html/mtzformat.html#storage
    // Column contains a combination of the partiality flag M and the symmetry
    // number ISYM: 256M+ISYM. M is 0 for fully recorded reflections or 1 for
    // partials. ISYM = 2*isymop - 1 for reflections placed in the positive
    // asu, i.e. I+ of a Friedel pair, and ISYM = 2*isymop for reflections
    // placed in the negative asu, i.e. I- of a Friedel pair. Here "isymop"
    // is the number of the symmetry operator used.

    // also see cctbx/miller/asu.cpp:
    //   asym_index::asym_index()
    for (int i_refl=0; i_refl<n_refl; ++i_refl){
      int m_value = m_isym.int_datum(i_refl) / 256;
      int isym = 0;
      cctbx::miller::index<> h = indices[i_refl];
      cctbx::miller::index<> hr_;
      //int  ht_;
      for(std::size_t i_smx=0;i_smx<sg.n_smx();i_smx++) {
        cctbx::sgtbx::rt_mx s = sg(0, 0, i_smx);
        hr_ = h * s.r();
        if (asu.is_inside(hr_)) {
          //ht_ = cctbx::sgtbx::ht_mod_1(h, s.t());
          isym = (i_smx + 1) * 2 - 1;
          break;
        }
        else if (asu.is_inside(-hr_)) {
          hr_ = -hr_;
          //ht_ = cctbx::sgtbx::ht_mod_1(h, s.t());
          isym = (i_smx + 1) * 2;
          break;
        }
      }
      hkl.replace_miller_index(i_refl, hr_);
      m_isym.float_datum(i_refl) = static_cast<float>(m_value + isym);
    }
  }

  integer_group
  object::extract_integers(
    const char* column_label) const
  {
    int n_refl = n_reflections();
    integer_group result(false, n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data(get_column(column_label));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      if (!data.is_ccp4_nan(i_refl)) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(data.int_datum(i_refl));
      }
    }
    return result;
  }

  af::shared<int>
  object::extract_integers(
    af::const_ref<int> const& mtz_reflection_indices,
    const char* column_label) const
  {
    int n_refl = n_reflections();
    af::shared<int> result((af::reserve(mtz_reflection_indices.size())));
    column data(get_column(column_label));
    for(int i=0;i<mtz_reflection_indices.size();i++) {
      int i_refl = mtz_reflection_indices[i];
      IOTBX_ASSERT(i_refl >= 0 && i_refl < n_refl);
      IOTBX_ASSERT(!data.is_ccp4_nan(i_refl));
      result.push_back(data.int_datum(i_refl));
    }
    return result;
  }

  integer_group
  object::extract_integers_anomalous(
    const char* column_label_plus,
    const char* column_label_minus) const
  {
    int n_refl = n_reflections();
    integer_group result(true, 2*n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data_plus(get_column(column_label_plus));
    column data_minus(get_column(column_label_minus));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      if (!data_plus.is_ccp4_nan(i_refl)) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(data_plus.int_datum(i_refl));
      }
      if (!data_minus.is_ccp4_nan(i_refl)) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(-hkl.get_miller_index(i_refl));
        result.data.push_back(data_minus.int_datum(i_refl));
      }
    }
    return result;
  }

  real_group
  object::extract_reals(
    const char* column_label) const
  {
    int n_refl = n_reflections();
    real_group result(false, n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data(get_column(column_label));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      if (!data.is_ccp4_nan(i_refl)) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(data.float_datum(i_refl));
      }
    }
    return result;
  }

  af::shared<double>
  object::extract_reals(
    af::const_ref<int> const& mtz_reflection_indices,
    const char* column_label) const
  {
    int n_refl = n_reflections();
    af::shared<double> result((af::reserve(mtz_reflection_indices.size())));
    column data(get_column(column_label));
    for(int i=0;i<mtz_reflection_indices.size();i++) {
      int i_refl = mtz_reflection_indices[i];
      IOTBX_ASSERT(i_refl >= 0 && i_refl < n_refl);
      IOTBX_ASSERT(!data.is_ccp4_nan(i_refl));
      result.push_back(data.float_datum(i_refl));
    }
    return result;
  }

  real_group
  object::extract_reals_anomalous(
    const char* column_label_plus,
    const char* column_label_minus) const
  {
    int n_refl = n_reflections();
    real_group result(true, 2*n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data_plus(get_column(column_label_plus));
    column data_minus(get_column(column_label_minus));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      if (!data_plus.is_ccp4_nan(i_refl)) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(data_plus.float_datum(i_refl));
      }
      if (!data_minus.is_ccp4_nan(i_refl)) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(-hkl.get_miller_index(i_refl));
        result.data.push_back(data_minus.float_datum(i_refl));
      }
    }
    return result;
  }

  namespace {

    struct nan_and_non_zero_counts
    {
      nan_and_non_zero_counts();

      template <std::size_t N>
      nan_and_non_zero_counts(
        af::tiny<column, N> const& columns,
        int i_refl)
      {
        compute(columns, i_refl);
      }

      nan_and_non_zero_counts(
        column const& column_0,
        column const& column_1,
        int i_refl)
      {
        compute(af::tiny<column, 2>(column_0, column_1), i_refl);
      }

      template <std::size_t N>
      void
      compute(
        af::tiny<column, N> const& columns,
        int i_refl)
      {
        n_nan = 0;
        n_non_zero = 0;
        for(unsigned i=0;i<N;i++) {
          if      (columns[i].is_ccp4_nan(i_refl)) n_nan++;
          else if (columns[i].float_datum(i_refl)) n_non_zero++;
        }
      }

      bool
      are_consistent() const { return (n_nan == 0 || n_non_zero == 0); }

      unsigned n_nan;
      unsigned n_non_zero;
    };

    struct observation_pair_evaluator
    {
      observation_pair_evaluator() {}

      observation_pair_evaluator(
        column const& data,
        column const& sigmas,
        int i_refl)
      :
        datum(0),
        sigma(0),
        is_consistent(true),
        is_usable(true)
      {
        if (data.is_ccp4_nan(i_refl)) {
          is_usable = false;
          if (!sigmas.is_ccp4_nan(i_refl)) {
            sigma = sigmas.float_datum(i_refl);
            if (sigma != 0 && sigma != 1) {
              is_consistent = false;
            }
          }
        }
        else {
          datum = data.float_datum(i_refl);
          if (!sigmas.is_ccp4_nan(i_refl)) {
            sigma = sigmas.float_datum(i_refl);
            if (sigma == 0 && (datum == 0 || datum == 1)) {
              is_usable = false;
            }
          }
          else {
            is_usable = false;
            if (datum != 0 && datum != 1) {
              is_consistent = false;
            }
          }
        }
      }

      double datum;
      double sigma;
      bool is_consistent;
      bool is_usable;
    };

    static const char* phenix_mtz_dump_tip =
      " [tip: \"phenix.mtz.dump --show_column_data file_name\""
      " is available for inspecting the MTZ file]";
  }

  hl_group
  object::extract_hendrickson_lattman(
    const char* column_label_a,
    const char* column_label_b,
    const char* column_label_c,
    const char* column_label_d) const
  {
    int n_refl = n_reflections();
    hl_group result(false, n_refl);
    hkl_columns hkl = get_hkl_columns();
    af::tiny<column, 4> data(
      get_column(column_label_a),
      get_column(column_label_b),
      get_column(column_label_c),
      get_column(column_label_d));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      nan_and_non_zero_counts counts(data, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting Hendrickson-Lattman array"
          " from columns: ")
          + column_label_a + ", "
          + column_label_b + ", "
          + column_label_c + ", "
          + column_label_d + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(cctbx::hendrickson_lattman<>(
          data[0].float_datum(i_refl),
          data[1].float_datum(i_refl),
          data[2].float_datum(i_refl),
          data[3].float_datum(i_refl)));
      }
    }
    return result;
  }

  hl_group
  object::extract_hendrickson_lattman_ab_only(
    const char* column_label_a,
    const char* column_label_b) const
  {
    int n_refl = n_reflections();
    hl_group result(false, n_refl);
    hkl_columns hkl = get_hkl_columns();
    af::tiny<column, 2> data(
      get_column(column_label_a),
      get_column(column_label_b));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      nan_and_non_zero_counts counts(data, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting Hendrickson-Lattman array"
          " from columns: ")
          + column_label_a + ", "
          + column_label_b + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(cctbx::hendrickson_lattman<>(
          data[0].float_datum(i_refl),
          data[1].float_datum(i_refl),
          0., 0.));
      }
    }
    return result;
  }

  hl_group
  object::extract_hendrickson_lattman_anomalous(
    const char* column_label_a_plus,
    const char* column_label_b_plus,
    const char* column_label_c_plus,
    const char* column_label_d_plus,
    const char* column_label_a_minus,
    const char* column_label_b_minus,
    const char* column_label_c_minus,
    const char* column_label_d_minus) const
  {
    int n_refl = n_reflections();
    hl_group result(true, n_refl);
    hkl_columns hkl = get_hkl_columns();
    af::tiny<column, 4> data_p(
      get_column(column_label_a_plus),
      get_column(column_label_b_plus),
      get_column(column_label_c_plus),
      get_column(column_label_d_plus));
    af::tiny<column, 4> data_m(
      get_column(column_label_a_minus),
      get_column(column_label_b_minus),
      get_column(column_label_c_minus),
      get_column(column_label_d_minus));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      nan_and_non_zero_counts counts(data_p, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting Hendrickson-Lattman array"
          " from columns: ")
          + column_label_a_plus + ", "
          + column_label_b_plus + ", "
          + column_label_c_plus + ", "
          + column_label_d_plus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(cctbx::hendrickson_lattman<>(
          data_p[0].float_datum(i_refl),
          data_p[1].float_datum(i_refl),
          data_p[2].float_datum(i_refl),
          data_p[3].float_datum(i_refl)));
      }
      counts = nan_and_non_zero_counts(data_m, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting Hendrickson-Lattman array"
          " from columns: ")
          + column_label_a_minus + ", "
          + column_label_b_minus + ", "
          + column_label_c_minus + ", "
          + column_label_d_minus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(-hkl.get_miller_index(i_refl));
        result.data.push_back(cctbx::hendrickson_lattman<>(
          data_m[0].float_datum(i_refl),
          data_m[1].float_datum(i_refl),
          data_m[2].float_datum(i_refl),
          data_m[3].float_datum(i_refl)));
      }
    }
    return result;
  }

  hl_group
  object::extract_hendrickson_lattman_anomalous_ab_only(
    const char* column_label_a_plus,
    const char* column_label_b_plus,
    const char* column_label_a_minus,
    const char* column_label_b_minus) const
  {
    int n_refl = n_reflections();
    hl_group result(true, n_refl);
    hkl_columns hkl = get_hkl_columns();
    af::tiny<column, 2> data_p(
      get_column(column_label_a_plus),
      get_column(column_label_b_plus));
    af::tiny<column, 2> data_m(
      get_column(column_label_a_minus),
      get_column(column_label_b_minus));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      nan_and_non_zero_counts counts(data_p, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting Hendrickson-Lattman array"
          " from columns: ")
          + column_label_a_plus + ", "
          + column_label_b_plus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(cctbx::hendrickson_lattman<>(
          data_p[0].float_datum(i_refl),
          data_p[1].float_datum(i_refl),
          0., 0.));
      }
      counts = nan_and_non_zero_counts(data_m, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting Hendrickson-Lattman array"
          " from columns: ")
          + column_label_a_minus + ", "
          + column_label_b_minus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(-hkl.get_miller_index(i_refl));
        result.data.push_back(cctbx::hendrickson_lattman<>(
          data_m[0].float_datum(i_refl),
          data_m[1].float_datum(i_refl),
          0., 0.));
      }
    }
    return result;
  }

  observations_group
  object::extract_observations(
    const char* column_label_data,
    const char* column_label_sigmas) const
  {
    int n_refl = n_reflections();
    observations_group result(false, n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data(get_column(column_label_data));
    column sigmas(get_column(column_label_sigmas));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      observation_pair_evaluator pair_evaluation(data, sigmas, i_refl);
      if (!pair_evaluation.is_consistent) {
        throw cctbx::error(std::string(
          "Inconsistent observation/sigma pair in columns: ")
          + column_label_data + ", " + column_label_sigmas + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (pair_evaluation.is_usable) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(pair_evaluation.datum);
        result.sigmas.push_back(pair_evaluation.sigma);
      }
    }
    return result;
  }

  observations_group
  object::extract_observations_anomalous(
    const char* column_label_data_plus,
    const char* column_label_sigmas_plus,
    const char* column_label_data_minus,
    const char* column_label_sigmas_minus) const
  {
    int n_refl = n_reflections();
    observations_group result(true, 2*n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data_plus(get_column(column_label_data_plus));
    column sigmas_plus(get_column(column_label_sigmas_plus));
    column data_minus(get_column(column_label_data_minus));
    column sigmas_minus(get_column(column_label_sigmas_minus));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      observation_pair_evaluator
        pair_evaluation(data_plus, sigmas_plus, i_refl);
      if (!pair_evaluation.is_consistent) {
        throw cctbx::error(std::string(
          "Inconsistent observation/sigma pair in columns: ")
          + column_label_data_plus + ", " + column_label_sigmas_plus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (pair_evaluation.is_usable) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(pair_evaluation.datum);
        result.sigmas.push_back(pair_evaluation.sigma);
      }
      pair_evaluation = observation_pair_evaluator(
        data_minus, sigmas_minus, i_refl);
      if (!pair_evaluation.is_consistent) {
        throw cctbx::error(std::string(
          "Inconsistent observation/sigma pair in columns: ")
          + column_label_data_minus + ", " + column_label_sigmas_minus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (pair_evaluation.is_usable) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(-hkl.get_miller_index(i_refl));
        result.data.push_back(pair_evaluation.datum);
        result.sigmas.push_back(pair_evaluation.sigma);
      }
    }
    return result;
  }

  observations_group
  object::extract_delta_anomalous(
    const char* column_label_f_data,
    const char* column_label_f_sigmas,
    const char* column_label_d_data,
    const char* column_label_d_sigmas,
    const char* column_label_isym,
    bool skip_incompatible_values=false) const
  {
    int n_refl = n_reflections();
    observations_group result(true, 2*n_refl);
    hkl_columns hkl = get_hkl_columns();
    column f_data(get_column(column_label_f_data));
    column f_sigmas(get_column(column_label_f_sigmas));
    column d_data(get_column(column_label_d_data));
    column d_sigmas(get_column(column_label_d_sigmas));
    boost::optional<column> isym;
    if (column_label_isym != 0) {
      isym = get_column(column_label_isym);
    }
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      cctbx::miller::index<> const& h = hkl.get_miller_index(i_refl);
      observation_pair_evaluator
        pair_evaluation_f(f_data, f_sigmas, i_refl);
      if (!pair_evaluation_f.is_consistent) {
        if (skip_incompatible_values) {
          continue;
        } else {
          throw cctbx::error(std::string(
            "Inconsistent observation/sigma pair in columns: ")
            + column_label_f_data + ", " + column_label_f_sigmas + ", "
            + "hkl=" + h.as_string()
            + phenix_mtz_dump_tip);
        }
      }
      observation_pair_evaluator
        pair_evaluation_d(d_data, d_sigmas, i_refl);
      if (!pair_evaluation_d.is_consistent) {
        if (skip_incompatible_values) {
          continue;
        } else {
          throw cctbx::error(std::string(
            "Inconsistent observation/sigma pair in columns: ")
            + column_label_d_data + ", " + column_label_d_sigmas + ", "
            + "hkl=" + h.as_string()
            + phenix_mtz_dump_tip);
        }
      }
      if (    pair_evaluation_d.is_usable
          &&  pair_evaluation_d.datum != 0
          && !pair_evaluation_f.is_usable) {
        if (skip_incompatible_values) {
          continue;
        } else {
          throw cctbx::error(std::string(
            "Invalid combination of values while extracting anomalous array"
            " from columns: ")
            + column_label_f_data   + ", "
            + column_label_f_sigmas + ", "
            + column_label_d_data   + ", "
            + column_label_d_sigmas + ", "
            + "hkl=" + h.as_string()
            + phenix_mtz_dump_tip);
        }
      }
      if (pair_evaluation_f.is_usable) {
        result.mtz_reflection_indices.push_back(i_refl);
        if (!pair_evaluation_d.is_usable) {
          if (   !isym
              || isym->is_ccp4_nan(i_refl)
              || isym->int_datum(i_refl) != 2) {
            result.indices.push_back(h);
          }
          else {
            result.indices.push_back(-h);
          }
          result.data.push_back(pair_evaluation_f.datum);
          result.sigmas.push_back(pair_evaluation_f.sigma);
        }
        else {
          double ddh = pair_evaluation_d.datum * .5;
          double dsh = pair_evaluation_d.sigma * .5;
          double s = std::sqrt(
            pair_evaluation_f.sigma*pair_evaluation_f.sigma + dsh*dsh);
          result.mtz_reflection_indices.push_back(i_refl);
          result.indices.push_back(h);
          result.indices.push_back(-h);
          result.data.push_back(pair_evaluation_f.datum + ddh);
          result.data.push_back(pair_evaluation_f.datum - ddh);
          result.sigmas.push_back(s);
          result.sigmas.push_back(s);
        }
      }
    }
    return result;
  }

  complex_group
  object::extract_complex(
    const char* column_label_ampl,
    const char* column_label_phi) const
  {
    int n_refl = n_reflections();
    complex_group result(false, n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data_ampl(get_column(column_label_ampl));
    column data_phi(get_column(column_label_phi));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      nan_and_non_zero_counts counts(data_ampl, data_phi, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting complex array from columns: ")
          + column_label_ampl + ", " + column_label_phi + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(polar_deg(
          data_ampl.float_datum(i_refl), data_phi.float_datum(i_refl)));
      }
    }
    return result;
  }

  complex_group
  object::extract_complex_anomalous(
    const char* column_label_ampl_plus,
    const char* column_label_phi_plus,
    const char* column_label_ampl_minus,
    const char* column_label_phi_minus) const
  {
    int n_refl = n_reflections();
    complex_group result(true, n_refl);
    hkl_columns hkl = get_hkl_columns();
    column data_ampl_plus(get_column(column_label_ampl_plus));
    column data_phi_plus(get_column(column_label_phi_plus));
    column data_ampl_minus(get_column(column_label_ampl_minus));
    column data_phi_minus(get_column(column_label_phi_minus));
    for(int i_refl=0;i_refl<n_refl;i_refl++) {
      nan_and_non_zero_counts counts(data_ampl_plus, data_phi_plus, i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting complex array from columns: ")
          + column_label_ampl_plus + ", " + column_label_phi_plus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(hkl.get_miller_index(i_refl));
        result.data.push_back(polar_deg(
          data_ampl_plus.float_datum(i_refl),
          data_phi_plus.float_datum(i_refl)));
      }
      counts = nan_and_non_zero_counts(data_ampl_minus,data_phi_minus,i_refl);
      if (!counts.are_consistent()) {
        throw cctbx::error(std::string(
          "Unexpected NAN while extracting complex array from columns: ")
          + column_label_ampl_minus + ", " + column_label_phi_minus + ", "
          + "hkl=" + hkl.get_miller_index(i_refl).as_string()
          + phenix_mtz_dump_tip);
      }
      if (counts.n_nan == 0) {
        result.mtz_reflection_indices.push_back(i_refl);
        result.indices.push_back(-hkl.get_miller_index(i_refl));
        result.data.push_back(polar_deg(
          data_ampl_minus.float_datum(i_refl),
          data_phi_minus.float_datum(i_refl)));
      }
    }
    return result;
  }

  void
  object::write(const char* file_name)
  {
    IOTBX_ASSERT(file_name != 0);
    if (!CMtz::MtzPut(ptr(), file_name)) {
      throw cctbx::error("MTZ write failed.");
    }
  }

  void
  object::ptr_deleter(CMtz::MTZ* ptr)
  {
    if (ptr != 0) {
      if (ptr->batch != 0 && ptr->n_orig_bat == 0) {
        ptr->n_orig_bat = 1; // force MtzFreeBatch
      }
      IOTBX_ASSERT(CMtz::MtzFree(ptr));
    }
  }

}} // namespace iotbx::mtz


#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <climits>
#include <fstream>
#include <sstream>
#include "common.hpp"
#include "io.hpp"

#ifndef NOBOOST
    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/ini_parser.hpp>
#endif

using namespace std;

void ReadICsASCII(string Filename, int N, Particle **P_h, int *FileSnapshotNum, Real *FileTime) {
    *P_h = new Particle[N];
    string Line;
    ifstream InputFile(Filename.c_str());
    int LineNum;

    int i = 0;
    if (InputFile.is_open()) {
        getline(InputFile, Line); // First line is the snapshot number.
        int Count = sscanf(Line.c_str(), "%d", FileSnapshotNum);
        if (Count != 1) {
            cerr << "Problem in input file, line 1." << endl;
            exit(1);
        }
        getline(InputFile, Line); // Second line is the expected number of particles.
        int ExpectedN;
        Count = sscanf(Line.c_str(), "%d", &ExpectedN); // We ignore it actually.
        if (Count != 1) {
            cerr << "Problem in input file, line 2." << endl;
            exit(1);
        }
        getline(InputFile, Line);
        double T0;
        Count = sscanf(Line.c_str(), "%lf", &T0);
        if (Count != 1) {
            cerr << "Problem in input file, line 3." << endl;
            exit(1);
        }
        *FileTime = (Real)T0;
        LineNum = 3;
        while (getline(InputFile, Line))
        {
            double m, x, y, z, vx, vy, vz;
            int id;
            Particle p;
            Count = sscanf(Line.c_str(), "%d %lf %lf %lf %lf %lf %lf %lf", &id, &m, &x, &y, &z, &vx, &vy, &vz);
            if (Count != 8) {
                cerr << "Problem in input file, line " << LineNum + 1 << "." << endl;
                exit(1);
            }
            p.ID = id;
            p.m = (Real)m;
            p.pos = vec3((Real)x, (Real)y, (Real)z);    // This is to ensure proper casting in the case of single prec.
            p.vel = vec3((Real)vx, (Real)vy, (Real)vz);
            p.Status = 0;
            p.CalculateR2();
            (*P_h)[i] = p;
            i++;
            LineNum++;
            if (i == N) break;
        }
        InputFile.close();
    } else {
        cerr << "Can't open file" << Filename << "." << endl;
        exit(1);
    }
    if (i < N) {
        cerr << "Was only able to read " << i << " particles while " << N << " were requested." << endl;
        exit(1);
    }
}

void WriteSnapshotASCII(string Prefix, int SnapNumber, Particle *P_h, int N, Real T) {
    char S[512];
    sprintf(S, "%s%04d.dat", Prefix.c_str(), SnapNumber);
    ofstream SnapshotFile;
    SnapshotFile.open(S);
    sprintf(S, "%06d\n", SnapNumber); SnapshotFile << S;
    sprintf(S, "%06d\n", N); SnapshotFile << S;
    sprintf(S, "%.16E\n", T); SnapshotFile << S;
    for (int i = 0; i < N; i++) {
        sprintf(S, "%06d%14.6e%14.6e%14.6e%14.6e%14.6e%14.6e%14.6e\n", P_h[i].ID, P_h[i].m, P_h[i].pos.x, P_h[i].pos.y, P_h[i].pos.z, P_h[i].vel.x, P_h[i].vel.y, P_h[i].vel.z); SnapshotFile << S;
    }
    SnapshotFile.close();
}

#define THROW_EXCEPTION(str, stat) {cerr << str << endl; exit((stat));}
void ParseInput(int argc, char *argv[], ParametersStruct *Params) {
    ParametersStruct P;
#ifndef NOBOOST
    if (argc < 2) THROW_EXCEPTION("Usage: etics [FILE]...", 1)
    ifstream TestFileObject(argv[1]);
    if (!TestFileObject) THROW_EXCEPTION("Problem reading file...", 1)
    boost::property_tree::ptree pt;
    boost::property_tree::ini_parser::read_ini(argv[1], pt); // what if file doesn't exist?

    P.N = pt.get<int>("N", 0);
    if (P.N <= 0) THROW_EXCEPTION("Could not read number of particles (N) from ini file.", 1)

    P.Tcrit = pt.get<Real>("Tcrit", -1);
    if (P.Tcrit < 0) THROW_EXCEPTION("Could not read finish time (Tcrit) from ini file.", 1)

    P.dT1 = pt.get<Real>("dT1", 0);
    if (P.dT1 <= 0) THROW_EXCEPTION("Could output time interval (dT1) from ini file.", 1)

    P.dT2 = pt.get<Real>("dT2", 0);
    if (P.dT2 <= 0) THROW_EXCEPTION("Could snapshot time interval (dT2) from ini file.", 1)

    P.ConstantStep = pt.get<Real>("StepSize", -1);
    if (P.ConstantStep < 0) THROW_EXCEPTION("Could not read step size (StepSize) from ini file.", 1)

    P.Filename = pt.get<string>("Filename", "\n");
    if (P.Filename == "\n") THROW_EXCEPTION("Could not read initial condition file name (Filename) from ini file.", 1)

    P.Prefix = pt.get<string>("Prefix", "");
    P.OutputFormat = pt.get<string>("OutputFormat", "ascii");
    P.DeviceID = pt.get<int>("device", -1);
    if (argc >= 3) { // If there is a argument after the file, it must be either --device or -d
        int Version1 = strncmp(argv[2], "--device", 8);
        int Version2 = strncmp(argv[2], "-d", 2);
        if ((Version1!=0) && (Version2!=0)) THROW_EXCEPTION("Commandline argument after input file must be device number.", 1)
        char *DeviceIDStr = strchr(argv[2], '=');
        if (DeviceIDStr!=NULL) DeviceIDStr = DeviceIDStr + 1;
        else {
            if (argc <= 3) THROW_EXCEPTION("Device number must follow.", 1)
            DeviceIDStr = argv[3];
        }
        P.DeviceID = atoi(DeviceIDStr);
        if ((P.DeviceID==0) && (strcmp(DeviceIDStr, "0")!=0)) THROW_EXCEPTION("Error understanding device number.", 1)
    }
#else
    // If no BOOST, we include a file with the parameters and compile it.
    int N, DeviceID = -1;
    Real dT1, dT2, Tcrit, StepSize;
    std::string Filename, Prefix = "";
    #include "noboost.inc"
    P.N = N;
    P.Filename = Filename;
    P.dT1 = dT1;
    P.dT2 = dT2;
    P.Tcrit = Tcrit;
    P.ConstantStep = StepSize;
    P.DeviceID = DeviceID;
#endif
    P.Seed = INT_MIN;
    if ((P.Filename[0]=='_') && (P.Filename.rfind("_") > 0)) {
        int Break = P.Filename.rfind("_") + 1;
        string SeedStr = P.Filename.substr(Break, P.Filename.length() - Break);
        int SeedInt;
        istringstream iss(SeedStr);
        iss >> ws >> P.Seed >> ws;
        if(!iss.eof()) THROW_EXCEPTION("Could not understand random seed (in Filename).", 1)
        P.Filename = P.Filename.substr(0, Break);
    }
    if (P.Seed == INT_MIN) P.Seed = (int)time(NULL);
    *Params = P;
}

// make safety: if single precision, fail to compile
#ifdef ETICS_HDF5
#include "H5Cpp.h"

int H5IterNum;
int *H5IterSnapNumbers;
string *H5IterGroupNames;

herr_t file_info(hid_t loc_id, const char *name, void *opdata) {
    int res = sscanf(name, "Step#%d", H5IterSnapNumbers + H5IterNum);
    if (res != 1) {
        cerr << "Problem understanding group \"" << name << "\" in HDF5 file" << endl;
        exit(1);
    }
    H5IterGroupNames[H5IterNum] = name;
    H5IterNum++;
    return 0;
}

void ReadICsHDF5(string Filename, int N, Particle **P_h, int *FileSnapshotNum, Real *FileTime) {
    double *Mass, *X, *Y, *Z, *VX, *VY, *VZ;
    int *ID;
    Mass  = new double[N];
    X  = new double[N];
    Y  = new double[N];
    Z  = new double[N];
    VX = new double[N];
    VY = new double[N];
    VZ = new double[N];
    ID = new int[N];

    H5::H5File file;
    file = H5::H5File(Filename, H5F_ACC_RDONLY);

    H5::Group group = file.openGroup("/");
    int NumberOfGroups = group.getNumObjs();
    H5IterSnapNumbers = new int[NumberOfGroups];
    H5IterGroupNames = new string[NumberOfGroups];
    H5IterNum = 0;
    int ret = file.iterateElems("/", NULL, file_info, NULL);
    int MaxSnapIndex = 0;
    for (int i=1; i < NumberOfGroups; i++) MaxSnapIndex = (H5IterSnapNumbers[i]>H5IterSnapNumbers[MaxSnapIndex])?i:MaxSnapIndex;
    string GroupName = H5IterGroupNames[MaxSnapIndex];

    group = file.openGroup("/" + GroupName);
    H5::Attribute attribute = group.openAttribute("Time");
    attribute.read(H5::PredType::NATIVE_DOUBLE, FileTime);
    *FileSnapshotNum = H5IterSnapNumbers[MaxSnapIndex];

    H5::DataSet dataset = file.openDataSet("/" + GroupName + "/ID");
    int NfromFile = dataset.getStorageSize()/sizeof(int);
    if (NfromFile < N) {
        cerr << "Was only able to read " << NfromFile << " particles while " << N << " were requested." << endl;
        exit(1);
    }
    dataset.read(ID, H5::PredType::NATIVE_UINT32);

    dataset = file.openDataSet("/" + GroupName + "/Mass");  dataset.read(Mass,  H5::PredType::NATIVE_DOUBLE);
    dataset = file.openDataSet("/" + GroupName + "/X");     dataset.read(X,  H5::PredType::NATIVE_DOUBLE);
    dataset = file.openDataSet("/" + GroupName + "/Y");     dataset.read(Y,  H5::PredType::NATIVE_DOUBLE);
    dataset = file.openDataSet("/" + GroupName + "/Z");     dataset.read(Z,  H5::PredType::NATIVE_DOUBLE);
    dataset = file.openDataSet("/" + GroupName + "/VX");    dataset.read(VX, H5::PredType::NATIVE_DOUBLE);
    dataset = file.openDataSet("/" + GroupName + "/VY");    dataset.read(VY, H5::PredType::NATIVE_DOUBLE);
    dataset = file.openDataSet("/" + GroupName + "/VZ");    dataset.read(VZ, H5::PredType::NATIVE_DOUBLE);

    dataset.close();
    group.close();
    file.close();

    *P_h = new Particle[N];
    for (int i = 0; i < N; i++) {
        Particle p;
        p.ID = ID[i];
        p.m = (Real)Mass[i];
        p.pos = vec3((Real)X[i], (Real)Y[i], (Real)Z[i]);    // This is to ensure proper casting in the case of single prec.
        p.vel = vec3((Real)VX[i], (Real)VY[i], (Real)VZ[i]);
        p.Status = 0;
        p.CalculateR2();
        (*P_h)[i] = p;
    }
}

bool H5FirstSnapshot = true;

void WriteSnapshotHDF5(string Prefix, int SnapNumber, Particle *P_h, int N, Real T) {
    string Filename = Prefix + ".h5part";

    H5::H5File file;

    if (H5FirstSnapshot && std::ifstream(Filename.c_str())) {
        cerr << "File \"" << Filename << "\" already exist!" << endl;
        exit(1);
    }
    H5FirstSnapshot = false;

    if (std::ifstream(Filename.c_str())) {
        file = H5::H5File(Filename, H5F_ACC_RDWR);
    }
    else file = H5::H5File(Filename, H5F_ACC_TRUNC);

    char GroupName[64];
    sprintf(GroupName, "/Step#%d", SnapNumber);

    H5::Group group;
    try {
        H5::Exception::dontPrint();
        group = H5::Group(file.createGroup(GroupName));
    }
    catch (  H5::FileIException error ) {
        cerr << "Couldn't create group \"" << GroupName << "\" in HDF5 file, maybe it already exists?"  << endl;
        exit(1);
    }

    double *Mass, *X, *Y, *Z, *VX, *VY, *VZ, *AX, *AY, *AZ;
    Mass  = new double[N];
    X  = new double[N];
    Y  = new double[N];
    Z  = new double[N];
    VX = new double[N];
    VY = new double[N];
    VZ = new double[N];
    AX = new double[N];
    AY = new double[N];
    AZ = new double[N];
    int *ID;
    ID = new int[N];

    for (int i = 0; i < N; i++) {
        Particle p = P_h[i];
        ID[i] = p.ID;
        Mass[i]  = p.m;
        X[i]  = p.pos.x;
        Y[i]  = p.pos.y;
        Z[i]  = p.pos.z;
        VX[i] = p.vel.x;
        VY[i] = p.vel.y;
        VZ[i] = p.vel.z;
        AX[i] = p.acc.x;
        AY[i] = p.acc.y;
        AZ[i] = p.acc.z;
    }

    H5::DataSpace attr_dataspace = H5::DataSpace(H5S_SCALAR);

    H5::Attribute attribute = group.createAttribute("Time", H5::PredType::NATIVE_DOUBLE, attr_dataspace);
    attribute.write( H5::PredType::NATIVE_DOUBLE, &T);

    attribute = group.createAttribute("TotalN", H5::PredType::NATIVE_UINT32, attr_dataspace);
    attribute.write( H5::PredType::NATIVE_UINT32, &N);

    hsize_t dimsxxx = N;
    H5::DataSpace dataspacexxx(1, &dimsxxx);
    H5::DataSet dataset3;
    dataset3 = H5::DataSet(group.createDataSet("ID", H5::PredType::NATIVE_UINT32, dataspacexxx));
    dataset3.write(ID, H5::PredType::NATIVE_UINT32);
    dataset3 = H5::DataSet(group.createDataSet("Mass",  H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(Mass, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("X",  H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(X, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("Y",  H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(Y, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("Z",  H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(Z, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("VX", H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(VX, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("VY", H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(VY, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("VZ", H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(VZ, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("AX", H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(AX, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("AY", H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(AY, H5::PredType::NATIVE_DOUBLE);
    dataset3 = H5::DataSet(group.createDataSet("AZ", H5::PredType::NATIVE_DOUBLE, dataspacexxx));
    dataset3.write(AZ, H5::PredType::NATIVE_DOUBLE);

    dataset3.close();
    dataspacexxx.close();

    attr_dataspace.close();
    attribute.close();
    group.close();
    file.close();

    delete [] Mass;
    delete [] X;
    delete [] Y;
    delete [] Z;
    delete [] VX;
    delete [] VY;
    delete [] VZ;
    delete [] AX;
    delete [] AY;
    delete [] AZ;
    delete [] ID;
}

#endif


//==============================================================================
//         Copyright 2014        LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2014        NumScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_SDK_EXHAUSTIVE_EXHAUSTIVE_HPP_INCLUDED
#define NT2_SDK_EXHAUSTIVE_EXHAUSTIVE_HPP_INCLUDED

#include <nt2/include/functions/simd/load.hpp>
#include <nt2/include/functions/simd/extract.hpp>
#include <nt2/include/functions/simd/successor.hpp>
#include <nt2/include/functions/simd/splat.hpp>
#include <nt2/include/functions/scalar/is_invalid.hpp>
#include <nt2/include/functions/scalar/ulpdist.hpp>
#include <nt2/include/functions/scalar/iround.hpp>
#include <nt2/include/functions/scalar/ilog2.hpp>
#include <nt2/include/functions/scalar/min.hpp>
#include <nt2/include/functions/scalar/abs.hpp>
#include <nt2/include/functions/scalar/bitwise_cast.hpp>
#include <nt2/include/constants/valmin.hpp>
#include <nt2/include/constants/valmax.hpp>
#include <nt2/include/constants/nan.hpp>
#include <boost/simd/sdk/meta/cardinal_of.hpp>
#include <nt2/sdk/meta/type_id.hpp>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cstddef>
#include "omp.hpp"

namespace nt2
{
  /*!
    @brief Exhaustive precision test for single precision

    Perform a ULP test on every representable single precision value
    in a given interval. Results are reported using a bucket histogram that
    gives hint on how many values fall in a given range of ULPs,
    the smallest and greatest inputs in the chosen range
    leading to this precision and  the result from this minimum input
    against the awaited result.

    @par Note:
    Currently this function is designed to take care of single precision only as
    running such a test on double precision values take an absurd amount of time.

    @tparam Type          Data type used for computation
    @param  mini Lower    bound of the test interval
    @param  maxi Upper    bound of the test interval
    @param  test_f        Function to test
    @param  reference_f   Reference function to compare to

    @par Example:

    Here is an example to test SIMD single-precision nt2::log against scalar double-precision std::log.

    @code
    #include <boost/simd/sdk/simd/native.hpp>
    #include <nt2/include/functions/log.hpp>
    #include <nt2/include/constants/zero.hpp>
    #include <nt2/include/constants/valmax.hpp>

    #include <nt2/sdk/exhaustive/exhaustive.hpp>

    // specific to nt2 tests: specify assert handling
    #define NT2_ASSERTS_AS_TRAP
    #include <nt2/sdk/error/assert_as_trap.hpp>

    #include <cmath>
    #include <cstdlib>

    // function object used for the reference
    struct std_log
    {
      float operator()(float x) const
      {
        return float(std::log(double(x)));
      }
    };

    int main(int argc, char* argv[])
    {
      // define boundaries from command-line arguments,
      // fallback to default values if not provided
      float mini = argc >= 2 ? std::atof(argv[1]) : nt2::Zero<float>();
      float mini = argc >= 3 ? std::atof(argv[2]) : nt2::Valmin<float>();

      // type to call the function object to test with
      typedef boost::simd::native<float, BOOST_SIMD_DEFAULT_EXTENSION> n_t;

      // run the test
      nt2::exhaustive_test<n_t>( mini
                               , maxi
                               , nt2::functor<nt2::tag::log_>() // function object to test
                               , std_log()
                               );
    }
    @endcode
  **/
  template<typename Type, typename TestF, typename RefF> inline
  void exhaustive_test(float mini, float maxi, TestF test_f, RefF reference_f)
  {
    typedef Type                                                    n_t;
    typedef typename boost::dispatch::meta::as_integer<Type>::type  in_t;

    static const nt2::uint32_t M = 128;
    static const nt2::uint32_t N = boost::simd::meta::cardinal_of<n_t>::value;
    in_t vN = nt2::splat<in_t>(N);

    nt2::uint32_t histo[M+2];
    float maxin[M+2], minin[M+2], minref[M+2], minval[M+2];

    for(auto& e : histo ) e = 0;
    for(auto& e : minin ) e = nt2::Valmax<float>();
    for(auto& e : maxin ) e = nt2::Valmin<float>();
    for(auto& e : minval) e = nt2::Nan<float>();
    for(auto& e : minref) e = nt2::Valmax<float>();

    auto t_min = nt2::bitwise_cast<nt2::uint32_t>(nt2::abs(mini));
    auto t_max = nt2::bitwise_cast<nt2::uint32_t>(nt2::abs(maxi));

    auto diff  = (mini*maxi < 0)  ? std::max(t_max,t_min) + std::min(t_max,t_min)
                                  : std::max(t_max,t_min) - std::min(t_max,t_min);

    std::cout << "Exhaustive test for: " << nt2::type_id(test_f)      << "\n"
              << "             versus: " << nt2::type_id(reference_f) << "\n"
              << "             With T: " << nt2::type_id<Type>()      << "\n"
              << "           in range: [" << mini << ", " << maxi << "]" << "\n"
              << "         # of tests: " << diff << "\n";
    std::cout << std::endl;

    nt2::uint32_t k = 0;

    #ifdef _OPENMP
    #pragma omp parallel firstprivate(mini,maxi,diff)
    #endif
    {
      bool  hit_loc[M+2];
      nt2::uint32_t histo_loc[M+2];
      float maxin_loc[M+2] , minin_loc[M+2], minref_loc[M+2], minval_loc[M+2];

      for(auto& e : hit_loc   ) e = false;
      for(auto& e : histo_loc ) e = 0;
      for(auto& e : minin_loc ) e = nt2::Valmax<float>();
      for(auto& e : maxin_loc ) e = nt2::Valmin<float>();
      for(auto& e : minval_loc) e = nt2::Nan<float>();
      for(auto& e : minref_loc) e = nt2::Valmax<float>();

      std::size_t numThreads  = get_num_threads();
      std::size_t my_id       = get_thread_num();
      std::size_t num_inc     = diff / numThreads;

      float my_mini = nt2::successor(mini,my_id*num_inc);
      float my_maxi = nt2::successor(my_mini,num_inc);
      if (my_id == numThreads -1) my_maxi = maxi;

      // Fill up the reference SIMD register data
      float a[N];
      a[0] = my_mini;
      for(std::size_t i = 1; i < N; i++) a[i] = nt2::successor(a[i-1], 1);
      n_t a0 = nt2::load<n_t>(&a[0],0);

      nt2::uint32_t k_loc = 0, j = 0;

      while( nt2::extract(a0,1) < my_maxi )
      {
        n_t z = test_f(a0);

        for(std::size_t i = 0; i < N; i++)
        {
          if (nt2::extract(a0,i)> my_maxi) break;
          float v = reference_f(nt2::extract(a0,i));
          float sz = nt2::extract(z,i);

          double u = nt2::ulpdist(v, sz)*2.0+1.0;

          if(nt2::is_invalid(u))
          {
            ++histo_loc[M+1];
            if (!hit_loc[M+1])
            {
              maxin_loc [M+1] = minin_loc [M+1] = nt2::extract(a0,i);
              minref_loc[M+1] = v;
              minval_loc[M+1] = nt2::extract(z,i);
              hit_loc[M+1] = true;
            }
            else
            {
              maxin_loc [M+1] = nt2::extract(a0,i);
            }
          }
          else
          {
            int p = nt2::min(int(M), int(nt2::ilog2(nt2::iround(u))));

            if (!hit_loc[p])
            {
              maxin_loc [p] = minin_loc [p] = nt2::extract(a0,i);
              minref_loc[p] = v;
              minval_loc[p] = nt2::extract(z,i);
              hit_loc[p] = true;
            }
            else
            {
              maxin_loc [p] = nt2::extract(a0,i);
            }
            ++histo_loc[p];
          }

          ++k_loc;
        }
        a0 = nt2::successor(a0, vN);
      }

      #ifdef _OPENMP
      #pragma omp critical
      #endif
      {
        for (std::size_t kk=0;kk<M+2;kk++)
        {
          maxin[kk] = std::max(maxin_loc[kk],maxin[kk]);

          if (minin_loc[kk]<minin[kk])
          {
            minin[kk] = std::min(minin_loc[kk],minin[kk]);
            minval[kk] = minval_loc[kk];
          }

          minref[kk] = std::min(minref_loc[kk],minref[kk]);
          histo[kk] += histo_loc[kk];
        }
        k += k_loc;
      }
    } //end omp parallel

    std::cout << k << "/" << diff << " values computed.\n";

    double d = 1;
    for(std::size_t i = 0; i < M+1; i++, d*= 2.0)
    {
      if(histo[i])
      {
        printf("%10u values (%.2f%%)\twithin %1.1f ULPs\t"
              , histo[i], (histo[i]*100.0/k), (d < 2 ? 0 : d/4)
              );
        if(i)
          std::cout << std::scientific << std::setprecision(9)
                    << "in range [" << minin[i] << ", "<< maxin[i] << "]" << "."
                    << " Example: "<< minin[i] << " returns " << minval[i]
                    << " instead of " << minref[i];
        std::cout << std::endl;
      }
    }

    if( histo[M+1])
    {
      printf("%10u values (%.2f%%)\twith invalid result.\t"
            , histo[M+1], (histo[M+1]*100.0/k)
            );
      std::cout << std::scientific << std::setprecision(9)
                << "in range [" << minin[M+1] << ", "<< maxin[M+1] << "]" << "."
                << " Example: "<< minin[M+1] << " returns " << minval[M+1]
                << " instead of " << minref[M+1]
                << std::endl;
    }
  }
}

#endif


//-*-c++-*-
//=======================================================================
// Copyright 1997-2001 University of Notre Dame.
// Authors: Lie-Quan Lee, Jeremy Siek
//
// This file is part of the Boost Graph Library
//
// You should have received a copy of the License Agreement for the
// Generic Graph Component Library along with the software;  see the
// file LICENSE.  If not, contact Office of Research, University of Notre
// Dame, Notre Dame, IN  46556.
//
// Permission to modify the code and to distribute modified code is
// granted, provided the text of this NOTICE is retained, a notice that
// the code was modified is included with the above COPYRIGHT NOTICE and
// with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
// file is distributed with the modified code.
//
// LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
// By way of example, but not limitation, Licensor MAKES NO
// REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
// PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
// OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
// OR OTHER RIGHTS.
//=======================================================================
//
#ifndef MINIMUM_DEGREE_ORDERING_HPP
#define MINIMUM_DEGREE_ORDERING_HPP

#include <vector>
#include <cassert>
#include <boost/config.hpp>
#include <boost/pending/bucket_sorter.hpp>
#include <boost/detail/numeric_traits.hpp> // for integer_traits

namespace boost {

  namespace detail {

    //
    // Given a set of n integers (where the integer values range from
    // zero to n-1), we want to keep track of a collection of stacks
    // of integers. It so happens that an integer will appear in at
    // most one stack at a time, so the stacks form disjoint sets.
    // Because of these restrictions, we can use one big array to
    // store all the stacks, intertwined with one another.
    // No allocation/deallocation happens in the push()/pop() methods
    // so this is faster than using std::stack's.
    //
    template <class SignedInteger>
    class Stacks {
      typedef SignedInteger value_type;
      typedef typename std::vector<value_type>::size_type size_type;
    public:
      Stacks(size_type n) : data(n) {}

      //: stack
      class stack {
        typedef typename std::vector<value_type>::iterator Iterator;
      public:
        stack(Iterator _data, const value_type& head)
          :  data(_data), current(head) {}

        // did not use default argument here to avoid internal compiler error
        // in g++.
        stack(Iterator _data)
          : data(_data), current(-(std::numeric_limits<value_type>::max)()) {}

        void pop() {
          assert(! empty());
          current = data[current];
        }
        void push(value_type v) {
          data[v] = current;
          current = v;
        }
        bool empty() {
          return current == -(std::numeric_limits<value_type>::max)();
        }
        value_type& top() { return current; }
      private:
        Iterator data;
        value_type current;
      };

      // To return a stack object
      stack make_stack()
        { return stack(data.begin()); }
    protected:
      std::vector<value_type> data;
    };

    // marker class, a generalization of coloring.
    //
    // This class is to provide a generalization of coloring which has
    // complexity of amortized constant time to set all vertices' color
    // back to be untagged. It implemented by increasing a tag.
    //
    // The colors are:
    //   not tagged
    //   tagged
    //   multiple_tagged
    //   done
    //
    template <class SignedInteger, class Vertex, class VertexIndexMap>
    class Marker {
      typedef SignedInteger value_type;
      typedef typename std::vector<value_type>::size_type size_type;

      static value_type done()
      { return (std::numeric_limits<value_type>::max)()/2; }
    public:
      Marker(size_type _num, VertexIndexMap index_map)
        : tag(1 - (std::numeric_limits<value_type>::max)()),
          data(_num, - (std::numeric_limits<value_type>::max)()),
          id(index_map) {}

      void mark_done(Vertex node) { data[id[node]] = done(); }

      bool is_done(Vertex node) { return data[id[node]] == done(); }

      void mark_tagged(Vertex node) { data[id[node]] = tag; }

      void mark_multiple_tagged(Vertex node) { data[id[node]] = multiple_tag; }

      bool is_tagged(Vertex node) const { return data[id[node]] >= tag; }

      bool is_not_tagged(Vertex node) const { return data[id[node]] < tag; }

      bool is_multiple_tagged(Vertex node) const
        { return data[id[node]] >= multiple_tag; }

      void increment_tag() {
        const size_type num = data.size();
        ++tag;
        if ( tag >= done() ) {
          tag = 1 - (std::numeric_limits<value_type>::max)();
          for (size_type i = 0; i < num; ++i)
            if ( data[i] < done() )
              data[i] = - (std::numeric_limits<value_type>::max)();
        }
      }

      void set_multiple_tag(value_type mdeg0)
      {
        const size_type num = data.size();
        multiple_tag = tag + mdeg0;

        if ( multiple_tag >= done() ) {
          tag = 1-(std::numeric_limits<value_type>::max)();

          for (size_type i=0; i<num; i++)
            if ( data[i] < done() )
              data[i] = -(std::numeric_limits<value_type>::max)();

          multiple_tag = tag + mdeg0;
        }
      }

      void set_tag_as_multiple_tag() { tag = multiple_tag; }

    protected:
      value_type tag;
      value_type multiple_tag;
      std::vector<value_type> data;
      VertexIndexMap id;
    };

    template< class Iterator, class SignedInteger,
       class Vertex, class VertexIndexMap, int offset = 1 >
    class Numbering {
      typedef SignedInteger number_type;
      number_type num; //start from 1 instead of zero
      Iterator   data;
      number_type max_num;
      VertexIndexMap id;
    public:
      Numbering(Iterator _data, number_type _max_num, VertexIndexMap id)
        : num(1), data(_data), max_num(_max_num), id(id) {}
      void operator()(Vertex node) { data[id[node]] = -num; }
      bool all_done(number_type i = 0) const { return num + i > max_num; }
      void increment(number_type i = 1) { num += i; }
      bool is_numbered(Vertex node) const {
        return data[id[node]] < 0;
      }
      void indistinguishable(Vertex i, Vertex j) {
        data[id[i]] = - (id[j] + offset);
      }
    };

    template <class SignedInteger, class Vertex, class VertexIndexMap>
    class degreelists_marker {
    public:
      typedef SignedInteger value_type;
      typedef typename std::vector<value_type>::size_type size_type;
      degreelists_marker(size_type n, VertexIndexMap id)
        : marks(n, 0), id(id) {}
      void mark_need_update(Vertex i) { marks[id[i]] = 1;  }
      bool need_update(Vertex i) { return marks[id[i]] == 1; }
      bool outmatched_or_done (Vertex i) { return marks[id[i]] == -1; }
      void mark(Vertex i) { marks[id[i]] = -1; }
      void unmark(Vertex i) { marks[id[i]] = 0; }
    private:
      std::vector<value_type> marks;
      VertexIndexMap id;
    };

    // Helper function object for edge removal
    template <class Graph, class MarkerP, class NumberD, class Stack,
      class VertexIndexMap>
    class predicateRemoveEdge1 {
      typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
      typedef typename graph_traits<Graph>::edge_descriptor edge_t;
    public:
      predicateRemoveEdge1(Graph& _g, MarkerP& _marker,
                           NumberD _numbering, Stack& n_e, VertexIndexMap id)
        : g(&_g), marker(&_marker), numbering(_numbering),
          neighbor_elements(&n_e), id(id) {}

      bool operator()(edge_t e) {
        vertex_t dist = target(e, *g);
        if ( marker->is_tagged(dist) )
          return true;
        marker->mark_tagged(dist);
        if (numbering.is_numbered(dist)) {
          neighbor_elements->push(id[dist]);
          return true;
        }
        return false;
      }
    private:
      Graph*   g;
      MarkerP* marker;
      NumberD  numbering;
      Stack*   neighbor_elements;
      VertexIndexMap id;
    };

    // Helper function object for edge removal
    template <class Graph, class MarkerP>
    class predicate_remove_tagged_edges
    {
      typedef typename graph_traits<Graph>::vertex_descriptor vertex_t;
      typedef typename graph_traits<Graph>::edge_descriptor edge_t;
    public:
      predicate_remove_tagged_edges(Graph& _g, MarkerP& _marker)
        : g(&_g), marker(&_marker) {}

      bool operator()(edge_t e) {
        vertex_t dist = target(e, *g);
        if ( marker->is_tagged(dist) )
          return true;
        return false;
      }
    private:
      Graph*   g;
      MarkerP* marker;
    };

    template<class Graph, class DegreeMap,
             class InversePermutationMap,
             class PermutationMap,
             class SuperNodeMap,
             class VertexIndexMap>
    class mmd_impl
    {
      // Typedefs
      typedef graph_traits<Graph> Traits;
      typedef typename Traits::vertices_size_type size_type;
      typedef typename detail::integer_traits<size_type>::difference_type
        diff_t;
      typedef typename Traits::vertex_descriptor vertex_t;
      typedef typename Traits::adjacency_iterator adj_iter;
      typedef iterator_property_map<vertex_t*,
        identity_property_map, vertex_t, vertex_t&> IndexVertexMap;
      typedef detail::Stacks<diff_t> Workspace;
      typedef bucket_sorter<size_type, vertex_t, DegreeMap, VertexIndexMap>
        DegreeLists;
      typedef Numbering<InversePermutationMap, diff_t, vertex_t,VertexIndexMap>
        NumberingD;
      typedef degreelists_marker<diff_t, vertex_t, VertexIndexMap>
        DegreeListsMarker;
      typedef Marker<diff_t, vertex_t, VertexIndexMap> MarkerP;

      // Data Members

      // input parameters
      Graph& G;
      int delta;
      DegreeMap degree;
      InversePermutationMap inverse_perm;
      PermutationMap perm;
      SuperNodeMap supernode_size;
      VertexIndexMap vertex_index_map;

      // internal data-structures
      std::vector<vertex_t> index_vertex_vec;
      size_type n;
      IndexVertexMap index_vertex_map;
      DegreeLists degreelists;
      NumberingD numbering;
      DegreeListsMarker degree_lists_marker;
      MarkerP marker;
      Workspace work_space;
    public:
      mmd_impl(Graph& g, size_type n_, int delta, DegreeMap degree,
               InversePermutationMap inverse_perm,
               PermutationMap perm,
               SuperNodeMap supernode_size,
               VertexIndexMap id)
        : G(g), delta(delta), degree(degree),
        inverse_perm(inverse_perm),
        perm(perm),
        supernode_size(supernode_size),
        vertex_index_map(id),
        index_vertex_vec(n_),
        n(n_),
        degreelists(n_ + 1, n_, degree, id),
        numbering(inverse_perm, n_, vertex_index_map),
        degree_lists_marker(n_, vertex_index_map),
        marker(n_, vertex_index_map),
        work_space(n_)
      {
        typename graph_traits<Graph>::vertex_iterator v, vend;
        size_type vid = 0;
        for (tie(v, vend) = vertices(G); v != vend; ++v, ++vid)
          index_vertex_vec[vid] = *v;
        index_vertex_map = IndexVertexMap(&index_vertex_vec[0]);

        // Initialize degreelists.  Degreelists organizes the nodes
        // according to their degree.
        for (tie(v, vend) = vertices(G); v != vend; ++v) {
          put(degree, *v, out_degree(*v, G));
          degreelists.push(*v);
        }
      }

      void do_mmd()
      {
        // Eliminate the isolated nodes -- these are simply the nodes
        // with no neighbors, which are accessible as a list (really, a
        // stack) at location 0.  Since these don't affect any other
        // nodes, we can eliminate them without doing degree updates.
        typename DegreeLists::stack list_isolated = degreelists[0];
        while (!list_isolated.empty()) {
          vertex_t node = list_isolated.top();
          marker.mark_done(node);
          numbering(node);
          numbering.increment();
          list_isolated.pop();
        }
        size_type min_degree = 1;
        typename DegreeLists::stack list_min_degree = degreelists[min_degree];

        while (list_min_degree.empty()) {
          ++min_degree;
          list_min_degree = degreelists[min_degree];
        }

        // check if the whole eliminating process is done
        while (!numbering.all_done()) {

          size_type min_degree_limit = min_degree + delta; // WARNING
          typename Workspace::stack llist = work_space.make_stack();

          // multiple elimination
          while (delta >= 0) {

            // Find the next non-empty degree
            for (list_min_degree = degreelists[min_degree];
                 list_min_degree.empty() && min_degree <= min_degree_limit;
                 ++min_degree, list_min_degree = degreelists[min_degree])
              ;
            if (min_degree > min_degree_limit)
              break;

            const vertex_t node = list_min_degree.top();
            const size_type node_id = vertex_index_map[node];
            list_min_degree.pop();
            numbering(node);

            // check if node is the last one
            if (numbering.all_done(supernode_size[node])) {
              numbering.increment(supernode_size[node]);
              break;
            }
            marker.increment_tag();
            marker.mark_tagged(node);

            this->eliminate(node);

            numbering.increment(supernode_size[node]);
            llist.push(node_id);
          } // multiple elimination

          if (numbering.all_done())
            break;

          this->update( llist, min_degree);
        }

      } // do_mmd()

      void eliminate(vertex_t node)
      {
        typename Workspace::stack element_neighbor = work_space.make_stack();

        // Create two function objects for edge removal
        typedef typename Workspace::stack WorkStack;
        predicateRemoveEdge1<Graph, MarkerP, NumberingD,
                             WorkStack, VertexIndexMap>
          p(G, marker, numbering, element_neighbor, vertex_index_map);

        predicate_remove_tagged_edges<Graph, MarkerP> p2(G, marker);

        // Reconstruct the adjacent node list, push element neighbor in a List.
        remove_out_edge_if(node, p, G);
        //during removal element neighbors are collected.

        while (!element_neighbor.empty()) {
          // element absorb
          size_type e_id = element_neighbor.top();
          vertex_t element = index_vertex_map[e_id];
          adj_iter i, i_end;
          for (tie(i, i_end) = adjacent_vertices(element, G); i != i_end; ++i){
            vertex_t i_node = *i;
            if (!marker.is_tagged(i_node) && !numbering.is_numbered(i_node)) {
              marker.mark_tagged(i_node);
              add_edge(node, i_node, G);
            }
          }
          element_neighbor.pop();
        }
        adj_iter v, ve;
        for (tie(v, ve) = adjacent_vertices(node, G); v != ve; ++v) {
          vertex_t v_node = *v;
          if (!degree_lists_marker.need_update(v_node)
              && !degree_lists_marker.outmatched_or_done(v_node)) {
            degreelists.remove(v_node);
          }
          //update out edges of v_node
          remove_out_edge_if(v_node, p2, G);

          if ( out_degree(v_node, G) == 0 ) { // indistinguishable nodes
            supernode_size[node] += supernode_size[v_node];
            supernode_size[v_node] = 0;
            numbering.indistinguishable(v_node, node);
            marker.mark_done(v_node);
            degree_lists_marker.mark(v_node);
          } else {                           // not indistinguishable nodes
            add_edge(v_node, node, G);
            degree_lists_marker.mark_need_update(v_node);
          }
        }
      } // eliminate()

      template <class Stack>
      void update(Stack llist, size_type& min_degree)
      {
        size_type min_degree0 = min_degree + delta + 1;

        while (! llist.empty()) {
          size_type deg, deg0 = 0;

          marker.set_multiple_tag(min_degree0);
          typename Workspace::stack q2list = work_space.make_stack();
          typename Workspace::stack qxlist = work_space.make_stack();

          vertex_t current = index_vertex_map[llist.top()];
          adj_iter i, ie;
          for (tie(i,ie) = adjacent_vertices(current, G); i != ie; ++i) {
            vertex_t i_node = *i;
            const size_type i_id = vertex_index_map[i_node];
            if (supernode_size[i_node] != 0) {
              deg0 += supernode_size[i_node];
              marker.mark_multiple_tagged(i_node);
              if (degree_lists_marker.need_update(i_node)) {
                if (out_degree(i_node, G) == 2)
                  q2list.push(i_id);
                else
                  qxlist.push(i_id);
              }
            }
          }

          while (!q2list.empty()) {
            const size_type u_id = q2list.top();
            vertex_t u_node = index_vertex_map[u_id];
            // if u_id is outmatched by others, no need to update degree
            if (degree_lists_marker.outmatched_or_done(u_node)) {
              q2list.pop();
              continue;
            }
            marker.increment_tag();
            deg = deg0;

            adj_iter nu = adjacent_vertices(u_node, G).first;
            vertex_t neighbor = *nu;
            if (neighbor == u_node) {
              ++nu;
              neighbor = *nu;
            }
            if (numbering.is_numbered(neighbor)) {
              adj_iter i, ie;
              for (tie(i,ie) = adjacent_vertices(neighbor, G);
                   i != ie; ++i) {
                const vertex_t i_node = *i;
                if (i_node == u_node || supernode_size[i_node] == 0)
                  continue;
                if (marker.is_tagged(i_node)) {
                  if (degree_lists_marker.need_update(i_node)) {
                    if ( out_degree(i_node, G) == 2 ) { // is indistinguishable
                      supernode_size[u_node] += supernode_size[i_node];
                      supernode_size[i_node] = 0;
                      numbering.indistinguishable(i_node, u_node);
                      marker.mark_done(i_node);
                      degree_lists_marker.mark(i_node);
                    } else                        // is outmatched
                      degree_lists_marker.mark(i_node);
                  }
                } else {
                  marker.mark_tagged(i_node);
                  deg += supernode_size[i_node];
                }
              }
            } else
              deg += supernode_size[neighbor];

            deg -= supernode_size[u_node];
            degree[u_node] = deg; //update degree
            degreelists[deg].push(u_node);
            //u_id has been pushed back into degreelists
            degree_lists_marker.unmark(u_node);
            if (min_degree > deg)
              min_degree = deg;
            q2list.pop();
          } // while (!q2list.empty())

          while (!qxlist.empty()) {
            const size_type u_id = qxlist.top();
            const vertex_t u_node = index_vertex_map[u_id];

            // if u_id is outmatched by others, no need to update degree
            if (degree_lists_marker.outmatched_or_done(u_node)) {
              qxlist.pop();
              continue;
            }
            marker.increment_tag();
            deg = deg0;
            adj_iter i, ie;
            for (tie(i, ie) = adjacent_vertices(u_node, G); i != ie; ++i) {
              vertex_t i_node = *i;
              if (marker.is_tagged(i_node))
                continue;
              marker.mark_tagged(i_node);

              if (numbering.is_numbered(i_node)) {
                adj_iter j, je;
                for (tie(j, je) = adjacent_vertices(i_node, G); j != je; ++j) {
                  const vertex_t j_node = *j;
                  if (marker.is_not_tagged(j_node)) {
                    marker.mark_tagged(j_node);
                    deg += supernode_size[j_node];
                  }
                }
              } else
                deg += supernode_size[i_node];
            } // for adjacent vertices of u_node
            deg -= supernode_size[u_node];
            degree[u_node] = deg;
            degreelists[deg].push(u_node);
            // u_id has been pushed back into degreelists
            degree_lists_marker.unmark(u_node);
            if (min_degree > deg)
              min_degree = deg;
            qxlist.pop();
          } // while (!qxlist.empty()) {

          marker.set_tag_as_multiple_tag();
          llist.pop();
        } // while (! llist.empty())

      } // update()

      void build_permutation(InversePermutationMap next,
                             PermutationMap prev)
      {
        // collect the permutation info
        size_type i;
        for (i = 0; i < n; ++i) {
          diff_t size = supernode_size[index_vertex_map[i]];
          if ( size <= 0 ) {
            prev[i] = next[i];
            supernode_size[index_vertex_map[i]]
              = next[i] + 1;  // record the supernode info
          } else
            prev[i] = - next[i];
        }
        for (i = 1; i < n + 1; ++i) {
          if ( prev[i-1] > 0 )
            continue;
          diff_t parent = i;
          while ( prev[parent - 1] < 0 ) {
            parent = - prev[parent - 1];
          }

          diff_t root = parent;
          diff_t num = prev[root - 1] + 1;
          next[i-1] = - num;
          prev[root-1] = num;

          parent = i;
          diff_t next_node = - prev[parent - 1];
          while (next_node > 0) {
            prev[parent-1] = - root;
            parent = next_node;
            next_node = - prev[parent - 1];
          }
        }
        for (i = 0; i < n; i++) {
          diff_t num = - next[i] - 1;
          next[i] = num;
          prev[num] = i;
        }
      } // build_permutation()
    };

  } //namespace detail

  // MMD algorithm
  //
  //The implementation presently includes the enhancements for mass
  //elimination, incomplete degree update, multiple elimination, and
  //external degree.
  //
  //Important Note: This implementation requires the BGL graph to be
  //directed.  Therefore, nonzero entry (i, j) in a symmetrical matrix
  //A coresponds to two directed edges (i->j and j->i).
  //
  //see Alan George and Joseph W. H. Liu, The Evolution of the Minimum
  //Degree Ordering Algorithm, SIAM Review, 31, 1989, Page 1-19
  template<class Graph, class DegreeMap,
           class InversePermutationMap,
           class PermutationMap,
           class SuperNodeMap, class VertexIndexMap>
  void minimum_degree_ordering
    (Graph& G,
     DegreeMap degree,
     InversePermutationMap inverse_perm,
     PermutationMap perm,
     SuperNodeMap supernode_size,
     int delta,
     VertexIndexMap vertex_index_map)
  {
    detail::mmd_impl<Graph,DegreeMap,InversePermutationMap,
      PermutationMap, SuperNodeMap, VertexIndexMap>
      impl(G, num_vertices(G), delta, degree, inverse_perm,
           perm, supernode_size, vertex_index_map);
    impl.do_mmd();
    impl.build_permutation(inverse_perm, perm);
  }

} // namespace boost

#endif // MINIMUM_DEGREE_ORDERING_HPP