//
//  Copyright (C) 2016 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/RDKitBase.h>
#include <GraphMol/MolTransforms/MolTransforms.h>
#include <Geometry/point.h>

#include "PMI.h"

#include <Eigen/Dense>

namespace RDKit {
namespace Descriptors {
namespace {

bool getMoments(const ROMol& mol, int confId, bool useAtomicMasses, double& pm1,
                double& pm2, double& pm3, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  const char* pn1 = useAtomicMasses ? "_PMI1_mass" : "_PMI1";
  const char* pn2 = useAtomicMasses ? "_PMI2_mass" : "_PMI2";
  const char* pn3 = useAtomicMasses ? "_PMI3_mass" : "_PMI3";

  if (!force && mol.hasProp(pn1) && mol.hasProp(pn2) && mol.hasProp(pn3)) {
    mol.getProp(pn1, pm1);
    mol.getProp(pn2, pm2);
    mol.getProp(pn3, pm3);
    return true;
  }

  const Conformer& conf = mol.getConformer(confId);

  Eigen::Matrix3d axes;
  Eigen::Vector3d moments;
  bool res;
  bool ignoreHs = false;
  if (useAtomicMasses) {
    std::vector<double> weights;
    weights.resize(mol.getNumAtoms());
    for (ROMol::ConstAtomIterator cai = mol.beginAtoms(); cai != mol.endAtoms();
         ++cai) {
      weights[(*cai)->getIdx()] = (*cai)->getMass();
    }
    res = MolTransforms::computePrincipalAxesAndMoments(
        conf, axes, moments, ignoreHs, false, &weights);
  } else {
    res = MolTransforms::computePrincipalAxesAndMoments(conf, axes, moments,
                                                        ignoreHs);
  }
  if (res) {
    pm1 = moments(0);
    pm2 = moments(1);
    pm3 = moments(2);
    mol.setProp(pn1, pm1, true);
    mol.setProp(pn2, pm2, true);
    mol.setProp(pn3, pm3, true);
  }
  return res;
}
bool getMomentsFromGyration(const ROMol& mol, int confId, bool useAtomicMasses,
                            double& pm1, double& pm2, double& pm3, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  const char* pn1 = useAtomicMasses ? "_PMI1_mass_cov" : "_PMI1_cov";
  const char* pn2 = useAtomicMasses ? "_PMI2_mass_cov" : "_PMI2_cov";
  const char* pn3 = useAtomicMasses ? "_PMI3_mass_cov" : "_PMI3_cov";

  if (!force && mol.hasProp(pn1) && mol.hasProp(pn2) && mol.hasProp(pn3)) {
    mol.getProp(pn1, pm1);
    mol.getProp(pn2, pm2);
    mol.getProp(pn3, pm3);
    return true;
  }

  const Conformer& conf = mol.getConformer(confId);

  Eigen::Matrix3d axes;
  Eigen::Vector3d moments;
  bool res;
  bool ignoreHs = false;
  if (useAtomicMasses) {
    std::vector<double> weights;
    weights.resize(mol.getNumAtoms());
    for (ROMol::ConstAtomIterator cai = mol.beginAtoms(); cai != mol.endAtoms();
         ++cai) {
      weights[(*cai)->getIdx()] = (*cai)->getMass();
    }
    res = MolTransforms::computePrincipalAxesAndMomentsFromGyrationMatrix(
        conf, axes, moments, ignoreHs, false, &weights);
  } else {
    res = MolTransforms::computePrincipalAxesAndMomentsFromGyrationMatrix(
        conf, axes, moments, ignoreHs);
  }
  if (res) {
    pm1 = moments(0);
    pm2 = moments(1);
    pm3 = moments(2);
    mol.setProp(pn1, pm1, true);
    mol.setProp(pn2, pm2, true);
    mol.setProp(pn3, pm3, true);
  }
  return res;
}

}  // end of anonymous namespace

double NPR1(const ROMol& mol, int confId, bool useAtomicMasses, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  if (pm3 < 1e-8) {
    return 0.0;
  }
  return pm1 / pm3;
}
double NPR2(const ROMol& mol, int confId, bool useAtomicMasses, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  if (pm3 < 1e-8) {
    return 0.0;
  }
  return pm2 / pm3;
}
double PMI1(const ROMol& mol, int confId, bool useAtomicMasses, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  return pm1;
}
double PMI2(const ROMol& mol, int confId, bool useAtomicMasses, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  return pm2;
}
double PMI3(const ROMol& mol, int confId, bool useAtomicMasses, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  return pm3;
}

double radiusOfGyration(const ROMol& mol, int confId, bool useAtomicMasses,
                        bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMomentsFromGyration(mol, confId, useAtomicMasses, pm1, pm2, pm3,
                              force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  return sqrt(pm1 + pm2 + pm3);
}

double inertialShapeFactor(const ROMol& mol, int confId, bool useAtomicMasses,
                           bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  if (pm1 < 1e-4 || pm3 < 1e-4) {
    // planar or no coordinates
    return 0.0;
  } else {
    return pm2 / (pm1 * pm3);
  }
}
double eccentricity(const ROMol& mol, int confId, bool useAtomicMasses,
                    bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMoments(mol, confId, useAtomicMasses, pm1, pm2, pm3, force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  if (pm3 < 1e-4) {
    // no coordinates
    return 0.0;
  } else {
    return sqrt(pm3 * pm3 - pm1 * pm1) / pm3;
  }
}

double asphericity(const ROMol& mol, int confId, bool useAtomicMasses,
                   bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  double pm1, pm2, pm3;
  if (!getMomentsFromGyration(mol, confId, useAtomicMasses, pm1, pm2, pm3,
                              force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  if (pm3 < 1e-4) {
    // no coordinates
    return 0.0;
  } else {
    double denom = pm1 + pm2 + pm3;

    return 0.5 * (pow(pm1 - pm2, 2) + pow(pm1 - pm3, 2) + pow(pm2 - pm3, 2)) /
           (denom * denom);
  }
}
double spherocityIndex(const ROMol& mol, int confId, bool force) {
  PRECONDITION(mol.getNumConformers() >= 1, "molecule has no conformers");
  bool useAtomicMasses = false;
  double pm1, pm2, pm3;
  if (!getMomentsFromGyration(mol, confId, useAtomicMasses, pm1, pm2, pm3,
                              force)) {
    // the eigenvector calculation failed
    return 0.0;  // FIX: throw an exception here?
  }
  if (pm3 < 1e-4) {
    // no coordinates
    return 0.0;
  } else {
    return 3. * pm1 / (pm1 + pm2 + pm3);
  }
}

}  // namespace Descriptors
}  // namespace RDKit


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          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
//==============================================================================
#include <nt2/include/functions/roots.hpp>
#include <nt2/include/functions/complexify.hpp>
#include <nt2/include/functions/eye.hpp>
#include <nt2/include/functions/ones.hpp>
#include <boost/type_traits/is_same.hpp>
#include <nt2/sdk/functor/meta/call.hpp>
#include <nt2/sdk/unit/tests/ulp.hpp>
#include <nt2/sdk/unit/module.hpp>

#include <nt2/table.hpp>

NT2_TEST_CASE_TPL ( roots,  NT2_REAL_TYPES)
{

  using nt2::roots;
  using nt2::tag::roots_;
  typedef std::complex<T> cT;
  nt2::table<T> p =  nt2::_(T(1), T(3));
  p(2) = T(-3); p(3) = T(2);
  nt2::table<cT> c = nt2::complexify(nt2::_(T(2), T(-1), T(1)));
  NT2_TEST_ULP_EQUAL(roots(p), c, 5.0);
}


#include "pitts_parallel.hpp"
#include "pitts_common.hpp"
#include "pitts_qubit_simulator.hpp"
#include <iostream>
#pragma GCC push_options
#pragma GCC optimize("no-unsafe-math-optimizations")
#include <Eigen/Dense>
#pragma GCC pop_options
#include <unsupported/Eigen/MatrixFunctions>

namespace
{
  std::ostream& operator<<(std::ostream& s, const std::vector<int>& v)
  {
    for(auto i: v)
      s << " " << i;
    return s;
  }
}

int main(int argc, char* argv[])
{
  PITTS::initialize(&argc, &argv);

  PITTS::QubitSimulator::Matrix2 phaseGate;
  phaseGate[0][0] = 1.;
  phaseGate[0][1] = 0.;
  phaseGate[1][0] = 0.;
  phaseGate[1][1] = std::complex<double>(std::sin(0.11), std::cos(0.11));

  PITTS::QubitSimulator::Matrix2 hadamardGate;
  hadamardGate[0][0] = 1./std::sqrt(2.);
  hadamardGate[0][1] = 1./std::sqrt(2.);
  hadamardGate[1][0] = 1./std::sqrt(2.);
  hadamardGate[1][1] = -1./std::sqrt(2.);

  PITTS::QubitSimulator::Matrix4 cnotGate = {};
  cnotGate[0][0] = 1;
  cnotGate[1][1] = 1;
  cnotGate[2][3] = 1;
  cnotGate[3][2] = 1;

  PITTS::QubitSimulator qsim;
  using QubitId = PITTS::QubitSimulator::QubitId;
  const QubitId nQ = 100;
  for(QubitId iQ = 0; iQ < nQ; iQ++)
    qsim.allocateQubit(iQ);

  double wtime = omp_get_wtime();

  // just single qubit gates
  const int nIter = 15;
  for(int iter = 0; iter < nIter; iter++)
  {
    if( iter % 2 == 0 )
      for(QubitId iQ = 0; iQ < nQ; iQ+=2)
        qsim.applySingleQubitGate(iQ, hadamardGate);
    else
      for(QubitId iQ = 0; iQ < nQ; iQ+=3)
        qsim.applySingleQubitGate(iQ, phaseGate);

    for(QubitId iQ = 1; iQ+1 < nQ; iQ+=2)
      qsim.applyTwoQubitGate(iQ, 0, cnotGate);

    for(QubitId iQ = 0; iQ+1 < nQ; iQ+=5)
      qsim.applyTwoQubitGate(iQ+1, 7, cnotGate);

    std::cout << "TT ranks: " << qsim.getWaveFunction().getTTranks() << "\n";
  }

  wtime = omp_get_wtime() - wtime;
  std::cout << "Run time: " << wtime << "\n";

  PITTS::finalize();

  return 0;
}


// MIT License
//
// Copyright (c) 2019 Jelle Spijker
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#include "../include/fluids/Pipes.h"

#include <math.h>

#include <boost/units/cmath.hpp>
#include <fluids/Pipes.h>

namespace Fluids {
Pipes::Pipes() :
    FluidComponents(std::make_shared<Liquid>(), std::make_shared<Liquid>()),
    m_diameter(new quantity<si::length>(0. * si::meter)),
    m_length(new quantity<si::length>(0. * si::meter)),
    m_roughness(new quantity<si::length>(0. * si::meter)),
    m_relative_roughness(new quantity<si::dimensionless>(0.)) {

}

Pipes::Pipes(const std::shared_ptr<Liquid> &liquid_u, const std::shared_ptr<Liquid> &liquid_v) :
    FluidComponents(liquid_u, liquid_v),
    m_diameter(new quantity<si::length>(0. * si::meter)),
    m_length(new quantity<si::length>(0. * si::meter)),
    m_roughness(new quantity<si::length>(0. * si::meter)),
    m_relative_roughness(new quantity<si::dimensionless>(0.)) {

}

Pipes::Pipes(const Fluids::Pipes &other) :
    FluidComponents(other),
    m_diameter(new quantity<si::length>(*other.m_diameter)),
    m_length(new quantity<si::length>(*other.m_length)),
    m_roughness(new quantity<si::length>(*other.m_roughness)),
    m_relative_roughness(new quantity<si::dimensionless>(*other.m_relative_roughness)) {

}

Pipes::Pipes(quantity<si::length> diameter, quantity<si::length> length, quantity<si::length> roughness) :
    FluidComponents(std::make_shared<Liquid>(), std::make_shared<Liquid>()),
    m_diameter(new quantity<si::length>(diameter)),
    m_length(new quantity<si::length>(length)),
    m_roughness(new quantity<si::length>(roughness)),
    m_relative_roughness(new quantity<si::dimensionless>(0.)) {

}

Pipes &Pipes::operator=(const Fluids::Pipes &other) {
  if (this == &other)
    return *this;
  FluidComponents::operator=(other);
  m_diameter = std::make_shared<quantity<si::length>>(*other.m_diameter);
  m_length = std::make_shared<quantity<si::length>>(*other.m_length);
  m_roughness = std::make_shared<quantity<si::length>>(*other.m_roughness);
  m_relative_roughness = std::make_shared<quantity<si::dimensionless>>(*other.m_relative_roughness);
  return *this;
}

std::shared_ptr<quantity<si::pressure>> Pipes::Get_DeltaPressure() {
  quantity<si::dimensionless> re = Pipes::Reynolds(*this->Get_Liquid(Vertex::u)->Get_Speed(),
                                                   *this->Get_Diameter(),
                                                   *this->Get_Liquid(Vertex::u)->Get_Density(),
                                                   *this->Get_Liquid(Vertex::u)->Get_Dynamic_viscosity());
  quantity<si::dimensionless> f = Pipes::Haaland(re, *this->Get_Relative_roughness());
  *Pipes::m_deltapressure =
      0.81056946914 * f * *this->Get_Length() * pow<2>(*this->Get_Volumetricflow())
          * *this->Get_Liquid(Vertex::u)->Get_Density().get()
          / pow<5>(*this->Get_Diameter());
  return m_deltapressure;
}

const std::shared_ptr<quantity<si::area>> &Pipes::Get_CrossSection() const {
  *m_crosssection = M_PI_4 * pow<2>(*this->Get_Diameter());
  return FluidComponents::Get_CrossSection();
}

void Pipes::Set_CrossSection(const std::shared_ptr<quantity<si::area>> &crosssection) {
  *m_diameter = sqrt(*crosssection / M_PI_4);
  FluidComponents::Set_CrossSection(crosssection);
}

const std::shared_ptr<quantity<si::length>> &Pipes::Get_Diameter() const {
  return m_diameter;
}

void Pipes::Set_Diameter(const std::shared_ptr<quantity<si::length>> &diameter) {
  Pipes::m_diameter = diameter;
}

const std::shared_ptr<quantity<si::length>> &Pipes::Get_Length() const {
  return m_length;
}

void Pipes::Set_Length(const std::shared_ptr<quantity<si::length>> &length) {
  Pipes::m_length = length;
}

const std::shared_ptr<quantity<si::length>> &Pipes::Get_Roughness() const {
  return m_roughness;
}

void Pipes::Set_Roughness(const std::shared_ptr<quantity<si::length>> &roughness) {
  Pipes::m_roughness = roughness;
}

const std::shared_ptr<quantity<si::dimensionless>> &Pipes::Get_Relative_roughness() const {
  *m_relative_roughness = *this->Get_Roughness() / *this->Get_Diameter();
  return m_relative_roughness;
}

quantity<si::dimensionless> Pipes::Reynolds(const quantity<si::velocity> &speed,
                                            const quantity<si::length> &diameter,
                                            const quantity<si::mass_density> &density,
                                            const quantity<si::dynamic_viscosity> &dynamic_viscosity) {
  return speed * diameter * density / dynamic_viscosity;
}

quantity<si::dimensionless> Pipes::Haaland(const quantity<si::dimensionless> &reynolds,
                                           const quantity<si::dimensionless> &relative_roughness) {
  quantity<si::dimensionless> A{pow(relative_roughness / 3.7, 1.11)};
  quantity<si::dimensionless> B{6.9 / reynolds};
  quantity<si::dimensionless> haaland{1.};
  haaland /= -1.8 * log10(A + B);
  return pow(haaland, 2);
}
}


#include "compi.hpp"

#include <array>
#include <limits>

#include <boost/version.hpp>
#include <boost/math/quadrature/exp_sinh.hpp>

extern "C" {
    #include "integration_routines.h"
}

#include "integration_routines_template.hpp"

struct ExpSinhParameters: public RoutineParametersBase {
    Real interval_end = 0.0;
    bool positive_axis;

    ExpSinhParameters(PyObject* routine_args,PyObject* routine_kwargs){
        using std::array;
        constexpr array<const char*,0> dumby {};
        constexpr auto keywords = generate_keyword_list<IntegralRange::semi_infinite>(dumby,array<const char*,1>{"interval_infinity"});

        float sign = 1.0;

        if(!PyArg_ParseTupleAndKeywords(routine_args,routine_kwargs,"Od|OOf$pId",const_cast<char**>(keywords.data()),
                &integrand,&interval_end,
                &args,&kw,&sign,
                &full_output, &max_levels,&tolerance)){
            throw could_not_parse_arguments("Unable to parse python arguments to C variables");
        }

        if(sign == 0.0){
            PyErr_SetString(PyExc_ValueError, "interval_infinity must be either a psitive or a negative value. It cannot be 0.");
            throw could_not_parse_arguments("interval_infinity must be either a positive or a negative value. It cannot be 0.");
        }
        positive_axis = sign > 0;
    }

    struct result_type: public RoutineParametersBase::result_type {
        size_t levels;
    };
};

ExpSinhParameters::result_type run_integration_routine(const compi_internal::IntegrandFunctionWrapper& f, const ExpSinhParameters& parameters){
    static_assert(std::numeric_limits<Real>::has_infinity, "Real type does not have infinity");
    using std::complex;
    boost::math::quadrature::exp_sinh<Real> integrator(parameters.max_levels);

    ExpSinhParameters::result_type result;

    // Work around bug in early versions of boost where exp_sinh.integrate will not compile when used with a complex valued function over a non-native range
    #if BOOST_VERSION >= 107300 // bug fixed in version 1.73.00
        Real lower_bound, upper_bound;
        if(parameters.positive_axis){
            lower_bound = parameters.interval_end;
            upper_bound = std::numeric_limits<Real>::infinity();
        }
        else{
            upper_bound = parameters.interval_end;
            lower_bound = -std::numeric_limits<Real>::infinity();
        }
        result.result = integrator.integrate(f,lower_bound, upper_bound,parameters.tolerance,&(result.err),&(result.l1),&(result.levels));
    #else
        // maps the function f onto the native range of the exp_sinh integrator (0,oo)
        auto f_shifted = [
                            &f,
                            sign=(parameters.positive_axis ? static_cast<Real>(1):static_cast<Real>(-1)),
                            shift=parameters.interval_end
                         ](Real x){
                             return f(sign*x + shift);
                         };

        result.result = integrator.integrate(f_shifted,parameters.tolerance,&(result.err),&(result.l1),&(result.levels));
    #endif
    return result;
}

extern "C" PyObject* exp_sinh(PyObject* self, PyObject* args, PyObject* kwargs){
    return integration_routine<ExpSinhParameters>(args,kwargs);
}


#ifndef HOPS_RUN_HPP
#define HOPS_RUN_HPP

#include <hops/LinearProgram/LinearProgramClpImpl.hpp>
#include <hops/LinearProgram/LinearProgramGurobiImpl.hpp>
#include <hops/MarkovChain/MarkovChain.hpp>
#include <hops/MarkovChain/MarkovChainFactory.hpp>
#include <hops/MarkovChain/MarkovChainType.hpp>
#include <hops/MarkovChain/Tuning/AcceptanceRateTuner.hpp>
#include <hops/MarkovChain/Tuning/ExpectedSquaredJumpDistanceTuner.hpp>
#include <hops/MarkovChain/Tuning/SimpleExpectedSquaredJumpDistanceTuner.hpp>
#include <hops/Polytope/MaximumVolumeEllipsoid.hpp>
#include <hops/RandomNumberGenerator/RandomNumberGenerator.hpp>
#include <hops/Utility/Data.hpp>
#include <hops/Utility/Exceptions.hpp>
#include <hops/Utility/Problem.hpp>

#include <Eigen/Core>

#include <memory>
#include <random>
#include <stdexcept>
#include <chrono>

#ifdef _OPENMP
#include <omp.h>
#endif

namespace hops {
    struct NoProposal {
        void setState(Eigen::VectorXd) {
            throw std::runtime_error("NoProposal.setState was called, but this class is not actually supposed to be used.");
        }
    };

    template<typename Model, typename Proposal>
    class RunBase;

    template<typename Model, typename Proposal>
    void tune(RunBase<Model, Proposal>& run, AcceptanceRateTuner::param_type& parameters);

    template<typename Model, typename Proposal>
    void tune(RunBase<Model, Proposal>& run, ExpectedSquaredJumpDistanceTuner::param_type& parameters);

    template<typename Model, typename Proposal>
    void tune(RunBase<Model, Proposal>& run, SimpleExpectedSquaredJumpDistanceTuner::param_type& parameters);

    template<typename Model, typename Proposal>
    class RunBase {
    public:
        RunBase (MarkovChainType markovChainType,
                 unsigned long numberOfSamples = 1000,
                 unsigned long numberOfChains = 1) :
                markovChainType(markovChainType),
                numberOfChains(numberOfChains),
                numberOfSamples(numberOfSamples) {
            //
        }

        RunBase (const Problem<Model>& problem,
                 MarkovChainType markovChainType = MarkovChainType::HitAndRun,
                 unsigned long numberOfSamples = 1000,
                 unsigned long numberOfChains = 1) :
                problem(problem),
                markovChainType(markovChainType),
                numberOfChains(numberOfChains),
                numberOfSamples(numberOfSamples) {
            //
        }

        RunBase (Proposal proposal,
                 unsigned long numberOfSamples = 1000,
                 unsigned long numberOfChains = 1) :
                proposal(proposal),
                numberOfChains(numberOfChains),
                numberOfSamples(numberOfSamples) {
            //
        }

        RunBase (const Problem<Model>& problem,
                 Proposal proposal,
                 unsigned long numberOfSamples = 1000,
                 unsigned long numberOfChains = 1) :
                problem(problem),
                proposal(proposal),
                numberOfChains(numberOfChains),
                numberOfSamples(numberOfSamples) {
            //
        }

		RunBase() = default;

		void setProblem(const Problem<Model>& problem);
		const Problem<Model>& getProblem();

		void setStartingPoints(const std::vector<Eigen::VectorXd>& startingPoints);
		const std::vector<Eigen::VectorXd>& getStartingPoints();

		void setMarkovChainType(MarkovChainType markovChainType);
		MarkovChainType getMarkovChainType();

		void setNumberOfChains(unsigned long numberOfChains);
		unsigned long getNumberOfChains();

		void setNumberOfSamples(unsigned long numberOfSamples);
		unsigned long getNumberOfSamples();

		void setThinning(unsigned long thinning);
		unsigned long getThinning();

        void setUseRounding(bool useRounding);
        bool getUseRounding();

		void setStepSize(double stepSize);
		double getStepSize();

		void setFisherWeight(double fisherWeight);
		double getFisherWeight();

		void setRandomSeed(double randomSeed);
		double getRandomSeed();

		void setSamplingUntilConvergence(bool sampleUntilConvergence);
		bool getSamplingUntilConvergence();

		void setConvergenceThreshold(double convergenceThreshold);
		double getConvergenceThreshold();

		void setMaxRepetitions(double maxRepetitions);
		double getMaxRepetitions();

		Data& getData();

        /**
         *
         */
        void init() {
            // create new data object to not mix up possible previous data
            data = std::make_shared<Data>(problem.dimension);

            if (!isRandomGeneratorInitialized) {
            	isRandomGeneratorInitialized = true;
            	// initialize random number generator for each chain
            	for (unsigned long i = 0; i < numberOfChains; ++i) {
            		randomNumberGenerators.push_back(RandomNumberGenerator(randomSeed, i));
            	}
            }

            // initialize missing starting points with the chebyshev center or the starting point passed
            // by the problem.
            if (startingPoints.size() < numberOfChains) {
                Eigen::VectorXd chebyshev;
                if (!problem.useStartingPoint) {
#if defined(HOPS_GUROBI_FOUND) || defined(HOPS_CLP_FOUND)
                    try {
                        LinearProgramGurobiImpl linearProgram(problem.A, problem.b);
                        chebyshev = linearProgram.computeChebyshevCenter().optimalParameters;

                    // either std::runtime_error, if Gurobi wasn't found or GRBException if no license
                    } catch (...) {
                        LinearProgramClpImpl linearProgram(problem.A, problem.b);
                        chebyshev = linearProgram.computeChebyshevCenter().optimalParameters;
                    }
#else
                    throw MissingStartingPointsException(
                            "No default starting point was provided in problem and no "
                            "LP solver is available for computing the Chebyshev center, "
                            "can thus not intialize missing starting points.");
#endif
                }

                // initialize all missing starting points
                for (unsigned long i = startingPoints.size(); i < numberOfChains; ++i) {
                    if (problem.useStartingPoint) {
                        startingPoints.push_back(problem.startingPoint);
                    } else {
                        startingPoints.push_back(chebyshev);
                    }
                }
            }

            // if rounding was specified, then compute the round transformation
			Eigen::MatrixXd roundingTransformation;
			if (useRounding) {
				roundingTransformation =
					hops::MaximumVolumeEllipsoid<double>::construct(problem.A, problem.b, 10000)
					.getRoundingTransformation();
				// next transform of startingPoint assumes roundingTransformation is lower triangular
				if (!roundingTransformation.isLowerTriangular()) {
					throw std::runtime_error("Error while rounding starting point, check code.");
				}
			}

            // set up the chains with the problem specifications
            markovChains.resize(numberOfChains);
            for (unsigned long i = 0; i < numberOfChains; ++i) {
                if (problem.unround) {
					if constexpr(std::is_same<Proposal, NoProposal>::value) {
                        markovChains[i] = std::move(MarkovChainFactory::createMarkovChain(markovChainType,
                                                                                problem.A,
                                                                                problem.b,
                                                                                startingPoints[i],
                                                                                problem.unroundingTransformation,
                                                                                problem.unroundingShift,
                                                                                problem.model));
                    } else {
                        proposal.setState(startingPoints[i]);
                        markovChains[i] = std::move(MarkovChainFactory::createMarkovChain(proposal,
                                                                                problem.unroundingTransformation,
                                                                                problem.unroundingShift,
                                                                                problem.model));
                    }
                } else if (useRounding) {
                    Eigen::VectorXd roundedStartingPoint = roundingTransformation.triangularView<Eigen::Lower>().solve(startingPoints[i]);
					if constexpr(std::is_same<Proposal, NoProposal>::value) {
                        markovChains[i] = std::move(MarkovChainFactory::createMarkovChain(markovChainType,
                                                                                Eigen::MatrixXd(problem.A * roundingTransformation),
                                                                                problem.b,
                                                                                roundedStartingPoint,
                                                                                roundingTransformation,
                                                                                Eigen::VectorXd(Eigen::VectorXd::Zero(problem.dimension)),
                                                                                problem.model));
					} else {
                        proposal.setState(roundedStartingPoint);
                        markovChains[i] = std::move(MarkovChainFactory::createMarkovChain(proposal,
                                                                                roundingTransformation,
                                                                                Eigen::VectorXd(Eigen::VectorXd::Zero(problem.dimension)),
                                                                                problem.model));
                    }
				} else {
					if constexpr(std::is_same<Proposal, NoProposal>::value) {
                        markovChains[i] = std::move(MarkovChainFactory::createMarkovChain(markovChainType,
                                                                                problem.A,
                                                                                problem.b,
                                                                                startingPoints[i],
                                                                                problem.model));
					} else {
                        proposal.setState(startingPoints[i]);
                        markovChains[i] = std::move(
                                MarkovChainFactory::createMarkovChain<Eigen::MatrixXd, Eigen::VectorXd, Model, Proposal>(proposal,
                                                                                problem.model));
                    }
                }

                // preallocate records vector to avoid reallocation
                markovChains[i]->clearHistory();
                markovChains[i]->reserveStateRecords(maxRepetitions * numberOfSamples);

                try {
                    markovChains[i]->setAttribute(hops::MarkovChainAttribute::STEP_SIZE, stepSize);
                } catch (...) {
                    //
                }

                try {
                    markovChains[i]->setAttribute(hops::MarkovChainAttribute::FISHER_WEIGHT, fisherWeight);
                } catch (...) {
                    //
                }
            }

            // register data object with chains. this sets the pointers in the data
            // object to the chain's data vectors, making the data outlive the chain.
            data->linkWithChains(markovChains);
            data->setDimension(problem.dimension);

            isInitialized = true;
        }

        /**
         *
         */
        void sample() {
			sample(numberOfSamples, thinning);
		}

        /**
         *
         */
        void sample(unsigned long numberOfSamples, unsigned long thinning = 1) {
            if (!isInitialized) {
                init();
            }

            unsigned long k = 0;
            double convergenceStatistics = 0;
            do {
                #pragma omp parallel for
                for (unsigned long i = 0; i < numberOfChains; ++i) {
                    markovChains[i]->draw(randomNumberGenerators[i], numberOfSamples, thinning);
                }
                // end pragma omp parallel for

                if (sampleUntilConvergence && numberOfChains >= 2) {
                    convergenceStatistics = computePotentialScaleReductionFactor(*data).maxCoeff();
                }

                //numSeen += conf.numSamples;
                ++k;
            } while(sampleUntilConvergence &&
                    // if threshold was not met or if psrf is nan, keep going
                    (convergenceStatistics > convergenceThreshold || std::isnan(convergenceStatistics))  &&
                    // though only if we have not yet reached the maximum number of repetitions
                    k < maxRepetitions);
        }

    private:
        Problem<Model> problem;
        std::shared_ptr<Data> data = nullptr;

        Proposal proposal;

        bool isInitialized = false;
        bool isRandomGeneratorInitialized = false;

        MarkovChainType markovChainType = MarkovChainType::HitAndRun;

        std::vector<std::shared_ptr<hops::MarkovChain>> markovChains;
        std::vector<hops::RandomNumberGenerator> randomNumberGenerators;

        std::vector<Eigen::VectorXd> startingPoints;

        unsigned long numberOfChains = 1;
        unsigned long numberOfSamples = 1000;
        unsigned long thinning = 1;

        bool useRounding = false;

        bool sampleUntilConvergence = false;
        double convergenceThreshold = 1.05;
        unsigned long maxRepetitions = 1;

        double stepSize = 1;
        double fisherWeight = 0.5;

        double randomSeed = 0;

        //friend void tune(RunBase<Model, Proposal>& run, const ExpectedSquaredJumpDistanceTuner::param_type& parameters);
        friend void tune<>(RunBase& run, AcceptanceRateTuner::param_type& parameters);
        friend void tune<>(RunBase& run, ExpectedSquaredJumpDistanceTuner::param_type& parameters);
        friend void tune<>(RunBase& run, SimpleExpectedSquaredJumpDistanceTuner::param_type& parameters);
    };

    template <typename Model>
    using Run = RunBase<Model, NoProposal>;

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setProblem(const Problem<Model>& problem) {
		this->isInitialized = false;
		this->problem = problem;
	}

	template<typename Model, typename Proposal>
	const Problem<Model>& RunBase<Model, Proposal>::getProblem() {
		return problem;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setStartingPoints(const std::vector<Eigen::VectorXd>& startingPoints) {
		this->isInitialized = false;
		this->startingPoints = startingPoints;
	}

	template<typename Model, typename Proposal>
	const std::vector<Eigen::VectorXd>& RunBase<Model, Proposal>::getStartingPoints() {
		return startingPoints;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setMarkovChainType(MarkovChainType markovChainType) {
		this->isInitialized = false;
		this->markovChainType = markovChainType;
	}

	template<typename Model, typename Proposal>
	MarkovChainType RunBase<Model, Proposal>::getMarkovChainType() {
		return markovChainType;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setNumberOfChains(unsigned long numberOfChains) {
		this->isInitialized = false;
		this->numberOfChains = numberOfChains;
	}

	template<typename Model, typename Proposal>
	unsigned long RunBase<Model, Proposal>::getNumberOfChains() {
		return numberOfChains;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setNumberOfSamples(unsigned long numberOfSamples) {
		this->numberOfSamples = numberOfSamples;
	}

	template<typename Model, typename Proposal>
	unsigned long RunBase<Model, Proposal>::getNumberOfSamples() {
		return numberOfSamples;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setThinning(unsigned long thinning) {
		this->thinning = thinning;
	}

	template<typename Model, typename Proposal>
	unsigned long RunBase<Model, Proposal>::getThinning() {
		return thinning;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setUseRounding(bool useRounding) {
		this->isInitialized = false;
        this->useRounding = useRounding;
	}

	template<typename Model, typename Proposal>
	bool RunBase<Model, Proposal>::getUseRounding() {
        return this->useRounding;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setStepSize(double stepSize) {
		this->isInitialized = false;
		this->stepSize = stepSize;
	}

	template<typename Model, typename Proposal>
	double RunBase<Model, Proposal>::getStepSize() {
		return stepSize;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setFisherWeight(double fisherWeight) {
		this->isInitialized = false;
		this->fisherWeight = fisherWeight;
	}

	template<typename Model, typename Proposal>
	double RunBase<Model, Proposal>::getFisherWeight() {
		return fisherWeight;
	}

	template<typename Model, typename Proposal>
	void RunBase<Model, Proposal>::setRandomSeed(double randomSeed) {
		this->isInitialized = false;
		this->isRandomGeneratorInitialized = false;
		this->randomSeed = randomSeed;
	}

	template<typename Model, typename Proposal>
	double RunBase<Model, Proposal>::getRandomSeed() {
		return randomSeed;
	}

	template<typename Model, typename Proposal>
    void RunBase<Model, Proposal>::setSamplingUntilConvergence(bool sampleUntilConvergence) {
		this->sampleUntilConvergence = sampleUntilConvergence;
    }

	template<typename Model, typename Proposal>
    bool RunBase<Model, Proposal>::getSamplingUntilConvergence() {
        return this->sampleUntilConvergence;
    }

	template<typename Model, typename Proposal>
    void RunBase<Model, Proposal>::setConvergenceThreshold(double convergenceThreshold) {
		this->convergenceThreshold = convergenceThreshold;
	}

	template<typename Model, typename Proposal>
	double RunBase<Model, Proposal>::getConvergenceThreshold() {
		return convergenceThreshold;
	}

	template<typename Model, typename Proposal>
    void RunBase<Model, Proposal>::setMaxRepetitions(double maxRepetitions) {
        this->maxRepetitions = maxRepetitions;
        if (this->isInitialized) {
            for (unsigned long i = 0; i < numberOfChains; ++i) {
                markovChains[i]->reserveStateRecords(maxRepetitions * numberOfSamples);
            }
        }
	}

	template<typename Model, typename Proposal>
	double RunBase<Model, Proposal>::getMaxRepetitions() {
		return maxRepetitions;
	}

	template<typename Model, typename Proposal>
	Data& RunBase<Model, Proposal>::getData() {
		return *data;
	}

    template<typename Model, typename Proposal>
    void tune(RunBase<Model, Proposal>& run, AcceptanceRateTuner::param_type& parameters) {
        if (!run.isInitialized) {
            run.init();
        }

        double tunedStepSize, deltaAcceptanceRate;
        Eigen::MatrixXd data, posterior;

        // record tuning time
        double time = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
        ).count();

        AcceptanceRateTuner::tune(tunedStepSize,
                                  deltaAcceptanceRate,
                                  run.markovChains,
                                  run.randomNumberGenerators,
                                  parameters,
                                  data,
                                  posterior);

        time = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
        ).count() - time;

        for (size_t i = 0; i < run.markovChains.size(); ++i) {
            try {
                run.markovChains[i]->setAttribute(hops::MarkovChainAttribute::STEP_SIZE, tunedStepSize);
            } catch (...) {
                //
            }
        }

        run.stepSize = tunedStepSize;
        unsigned long totalNumberOfTuningSamples =
                run.markovChains.size() * parameters.iterationsToTestStepSize * parameters.posteriorUpdateIterationsNeeded * parameters.pureSamplingIterations;
        // reset stored states
        run.data->reset();

        run.data->setTuningMethod("ThompsonSamplingESJD");
        run.data->setTotalNumberOfTuningSamples(totalNumberOfTuningSamples);
        run.data->setTunedStepSize(tunedStepSize);
        run.data->setTunedObjectiveValue(deltaAcceptanceRate);
        run.data->setTotalTuningTimeTaken(time);

        run.data->setTuningData(data);
        run.data->setTuningPosterior(posterior);
    }

    template<typename Model, typename Proposal>
    void tune(RunBase<Model, Proposal>& run, ExpectedSquaredJumpDistanceTuner::param_type& parameters) {
        if (!run.isInitialized) {
            run.init();
        }

        double tunedStepSize, maximumExpectedSquaredJumpDistance;
        Eigen::MatrixXd data, posterior;

        // record tuning time
        double time = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
        ).count();

        ExpectedSquaredJumpDistanceTuner::tune(tunedStepSize,
                                               maximumExpectedSquaredJumpDistance,
                                               run.markovChains,
                                               run.randomNumberGenerators,
                                               parameters,
                                               data,
                                               posterior);

        time = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
        ).count() - time;

        for (size_t i = 0; i < run.markovChains.size(); ++i) {
            try {
                run.markovChains[i]->setAttribute(hops::MarkovChainAttribute::STEP_SIZE, tunedStepSize);
            } catch (...) {
                //
            }
        }

        run.stepSize = tunedStepSize;
        unsigned long totalNumberOfTuningSamples =
                run.markovChains.size() * parameters.iterationsToTestStepSize * parameters.posteriorUpdateIterationsNeeded * parameters.pureSamplingIterations;
        // reset stored states
        run.data->reset();

        run.data->setTuningMethod("ThompsonSamplingAcceptanceRate");
        run.data->setTotalNumberOfTuningSamples(totalNumberOfTuningSamples);
        run.data->setTunedStepSize(tunedStepSize);
        run.data->setTunedObjectiveValue(maximumExpectedSquaredJumpDistance);
        run.data->setTotalTuningTimeTaken(time);

        run.data->setTuningData(data);
        run.data->setTuningPosterior(posterior);
    }

    template<typename Model, typename Proposal>
    void tune(RunBase<Model, Proposal>& run, SimpleExpectedSquaredJumpDistanceTuner::param_type& parameters) {
        if (!run.isInitialized) {
            run.init();
        }

        double tunedStepSize, maximumExpectedSquaredJumpDistance;
        Eigen::MatrixXd data, posterior;

        // record tuning time
        double time = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
        ).count();

        SimpleExpectedSquaredJumpDistanceTuner::tune(tunedStepSize,
                                                      maximumExpectedSquaredJumpDistance,
                                                      run.markovChains,
                                                      run.randomNumberGenerators,
                                                      parameters);

        time = std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now().time_since_epoch()
        ).count() - time;

        for (size_t i = 0; i < run.markovChains.size(); ++i) {
            try {
                run.markovChains[i]->setAttribute(hops::MarkovChainAttribute::STEP_SIZE, tunedStepSize);
            } catch (...) {
                //
            }
        }

        run.stepSize = tunedStepSize;
        unsigned long totalNumberOfTuningSamples =
                run.markovChains.size() * parameters.iterationsToTestStepSize * parameters.stepSizeGridSize;
        // reset stored states
        run.data->reset();

        run.data->setTuningMethod("GridSearchESJD");
        run.data->setTotalNumberOfTuningSamples(totalNumberOfTuningSamples);
        run.data->setTunedStepSize(tunedStepSize);
        run.data->setTunedObjectiveValue(maximumExpectedSquaredJumpDistance);
        run.data->setTotalTuningTimeTaken(time);

        run.data->setTuningData(data);
        run.data->setTuningPosterior(posterior);
    }
}

#endif // HOPS_RUN_HPP


// This file is distributed under the MIT License. For full text of the license see file named "LICENSE" in project's root directory.
// Copyright (c) 2014 nabijaczleweli
//
// Designed for gcc 4.8.1-dw2 - works flawlessly there. For crashes / compilation faults on other compilers please contact the author.
// Contributors: nabijaczleweli (nabijaczleweli@gmail.com)

#include <iostream>
#include <random>
#include <ctime>

#include <armadillo>

using namespace std;
using namespace arma;

template<class T>
void tick(Mat<T> &, const unsigned int &, const unsigned int &);
template<class T>
void pre_tick(Mat<T> &, unsigned int &, unsigned int &);
template<class T>
unsigned int get_alive_neighbours(const Mat<T> &, const unsigned int &, const unsigned int &);
pair<unsigned int, unsigned int> get_level_size(const string &);
bool is_level_an_actual_level(const string &);
void print_bad_information(const string &, unsigned int, unsigned int, const char * const);

int main(int, char * argv[]) {
	cout.sync_with_stdio(false);
	cin.sync_with_stdio(false);

	string filename;
	for(unsigned int idx = 1; argv[idx]; ++idx)
		filename = argv[idx];
	if(filename.size())
		if(!is_level_an_actual_level(filename)) {
			cout << '\"' << filename << "\" is not an actual level! Switching to auto-generate!\n\n";
			filename.erase();
			filename.shrink_to_fit();
		}

	unsigned int life_x = 60, life_y = 30;
	if(filename.size()) {
		auto size = get_level_size(filename);
		life_x = size.first;
		life_y = size.second;
	}
	Mat<char> the_map(life_y, life_x, fill::zeros);

	if(filename.size()) {
		ifstream input_file(filename);
		input_file.ignore(numeric_limits<streamsize>::max(), '\n');
		input_file.ignore(numeric_limits<streamsize>::max(), '\n');
		//  This may be unsafe! Protect it? Or just ignore it?
		for(unsigned int x = 0; x < life_x; ++x) {
			for(unsigned int y = 0; y < life_y; ++y)
				the_map(y, x) = input_file.get() - '0';
			input_file.ignore();
		}
	} else {
		srand(time(NULL));
		for(unsigned int y = 0; y < life_y; ++y)
			for(unsigned int x = 0; x < life_x; ++x)
				the_map(y, x) = !(rand() % 9);
	}
	/*the_map(7, 2) = false; // Lightweight spaceship
	the_map(7, 3) = true;
	the_map(7, 4) = true;
	the_map(7, 5) = true;
	the_map(7, 6) = true;

	the_map(8, 2) = true;
	the_map(8, 3) = false;
	the_map(8, 4) = false;
	the_map(8, 5) = false;
	the_map(8, 6) = true;

	the_map(9, 2) = false;
	the_map(9, 3) = false;
	the_map(9, 4) = false;
	the_map(9, 5) = false;
	the_map(9, 6) = true;

	the_map(10, 2) = true;
	the_map(10, 3) = false;
	the_map(10, 4) = false;
	the_map(10, 5) = true;
	the_map(10, 6) = false;*//* // 1-line replicator
	the_map(10, 4) = true;
	the_map(10, 5) = true;
	the_map(10, 6) = true;
	the_map(10, 7) = true;
	the_map(10, 8) = true;
	the_map(10, 9) = true;
	the_map(10, 10) = true;
	the_map(10, 11) = true;

	the_map(10, 12) = false;

	the_map(10, 13) = true;
	the_map(10, 14) = true;
	the_map(10, 15) = true;
	the_map(10, 16) = true;
	the_map(10, 17) = true;

	the_map(10, 18) = false;
	the_map(10, 19) = false;
	the_map(10, 20) = false;

	the_map(10, 21) = true;
	the_map(10, 22) = true;
	the_map(10, 23) = true;

	the_map(10, 24) = false;
	the_map(10, 25) = false;
	the_map(10, 26) = false;
	the_map(10, 27) = false;
	the_map(10, 28) = false;
	the_map(10, 29) = false;

	the_map(10, 30) = true;
	the_map(10, 31) = true;
	the_map(10, 32) = true;
	the_map(10, 33) = true;
	the_map(10, 34) = true;
	the_map(10, 35) = true;
	the_map(10, 36) = true;

	the_map(10, 37) = false;

	the_map(10, 38) = true;
	the_map(10, 39) = true;
	the_map(10, 40) = true;
	the_map(10, 41) = true;
	the_map(10, 42) = true;*/
	pre_tick(the_map, life_x, life_y);
	for(unsigned int y = 0; y < life_y; ++y) {
		for(unsigned int x = 0; x < life_x; ++x)
			cout << (the_map(y, x) ? '\262' : '\260');
		cout << '\n';
	}
	string line;
	while(true) {
		getline(cin, line);
		if(!line.find("q"))
			break;
		else if(!line.find("x+")) {
			if(line.size() == 2)
				line += '1';
			the_map.resize(life_y, life_x += atoi(line.substr(2).c_str()));
			cout << "\nNew dimensions: x = " << life_x << ", y = " << life_y << ".\n";
			continue;
		} else if(!line.find("y+")) {
			if(line.size() == 2)
				line += '1';
			the_map.resize(life_y += atoi(line.substr(2).c_str()), life_x);
			cout << "\nNew dimensions: x = " << life_x << ", y = " << life_y << ".\n";
			continue;
		} else if(!line.find("t")) {
			if(line.size() == 1)
				line += "10";
			unsigned int got = atoi(line.substr(1).c_str());
			for(unsigned int i = 1; i < got; ++i) {
				pre_tick(the_map, life_x, life_y);
				tick(the_map, life_x, life_y);
			}
		} else if(!line.find("fs")) {
			line.erase(remove_if(line.begin(), line.end(), [](const char & x){return isspace(x);}), line.end());
			if(line.size() == 2)
				line += "10,10";
			else if(!line.find(","))
				line += ",10";
			else if(!isdigit(line[2])) {
				line = line.substr(3);
				line = "fs10" + line;
			} else if(!isdigit(line[line.find(",") + 1]))
				line = line.substr(0, line.find(",") + 1) + "10";

			unsigned int y = atoi(line.substr(line.find(",") + 1).c_str()),
			             x = atoi(line.substr(2).c_str());
			if(life_y <= y)
				the_map.resize(life_y = (y + 1), life_x);
			if(life_x <= x)
				the_map.resize(life_y, life_x = (x + 1));
			the_map(y, x) = true;
			continue;
		} else if(!line.find("fr")) {
			line.erase(remove_if(line.begin(), line.end(), [](const char & x){return isspace(x);}), line.end());
			if(line.size() == 2)
				line += "10,10";
			else if(!line.find(","))
				line += ",10";
			else if(!isdigit(line[2])) {
				line = line.substr(3);
				line = "fr10" + line;
			} else if(!isdigit(line[line.find(",") + 1]))
				line = line.substr(0, line.find(",") + 1) + "10";

			unsigned int y = atoi(line.substr(line.find(",") + 1).c_str()),
			             x = atoi(line.substr(2).c_str());
			if(life_y < y)
				the_map.resize(life_y = y, life_x);
			if(life_x < x)
				the_map.resize(life_y, life_x = x);
			the_map(y, x) = false;
			continue;
		} else if(!line.find("fx")) {
			line.erase(remove_if(line.begin(), line.end(), [](const char & x){return isspace(x);}), line.end());
			if(line.size() == 2)
				line += "10,10";
			else if(!line.find(","))
				line += ",10";
			else if(!isdigit(line[2])) {
				line = line.substr(3);
				line = "fr10" + line;
			} else if(!isdigit(line[line.find(",") + 1]))
				line = line.substr(0, line.find(",") + 1) + "10";

			unsigned int y = atoi(line.substr(line.find(",") + 1).c_str()),
			             x = atoi(line.substr(2).c_str());
			if(life_y < y)
				the_map.resize(life_y = y, life_x);
			if(life_x < x)
				the_map.resize(life_y, life_x = x);
			the_map(y, x) = !the_map(y, x);
			continue;
		} else if(!line.find("p")) {
			for(unsigned int y = 0; y < life_y; ++y) {
				for(unsigned int x = 0; x < life_x; ++x)
					cout << (the_map(y, x) ? '\262' : '\260');
				cout << '\n';
			}
			continue;
		} else if(!line.find("load")) {
			line.erase(remove_if(line.begin(), line.end(), [](const char & x){return isspace(x);}), line.end());
			if(line.size() == 4)
				line += filename;
			if(line.size() == 4) {
				cout << "No filename specifed!\n\n" << flush;
				continue;
			}
			if(!is_level_an_actual_level(line.c_str() + 4)) {
				cout << '\"' << line.c_str() + 4 << "\" is not an actual level! Switching to auto-generate!\n\n" << flush;
				filename.erase();
				filename.shrink_to_fit();
				continue;
			}
			filename = line.c_str() + 4;
			auto size = get_level_size(filename);
			life_x = size.first;
			life_y = size.second;
			typename remove_reference<decltype(the_map)>::type new_map(life_y, life_x, fill::zeros);
			the_map.~Mat<typename remove_reference<decltype(the_map(0, 0))>::type>();
			the_map = new_map;
			ifstream input_file(filename);
			input_file.ignore(numeric_limits<streamsize>::max(), '\n');
			input_file.ignore(numeric_limits<streamsize>::max(), '\n');
			//  This may be unsafe! Protect it? Or just ignore it?
			for(unsigned int x = 0; x < life_x; ++x) {
				for(unsigned int y = 0; y < life_y; ++y)
					the_map(y, x) = input_file.get() - '0';
				input_file.ignore();
			}
			pre_tick(the_map, life_x + 2, life_y + 2);
		} else if(!line.find("?") || !line.find("help")) {
			cout << "Conway's Game of Life:\n\t"
			           "Help:\n\t\t" // TODO : Maybe some kinda table with commands or somethin'?
								   "\"q\" Does the cleanup an exits.\n\t\t"
									 "\"x+[NUMBER]\" Extends number of columns of the gamefield by NUMBER. NUMBER defaults to 10.\n\t\t"
									 "\"y+[NUMBER]\" Extends number of rows of the gamefield by NUMBER. NUMBER defaults to 10.\n\t\t"
									 "\"t[NUMBER]\" Ticks gamefield NUMBER times, then prints it. NUMBER defaults to 10.\n\t\t"
									 "\"fs[[NUMBER1][, NUMBER2]]\" Sets cell at {x = NUMBER1, y = NUMBER2} to alive. Both NUMBER1 and NUMBER2 default to 10.\n\t\t"
									 "\"fr[[NUMBER1][, NUMBER2]]\" Sets cell at {x = NUMBER1, y = NUMBER2} to dead. Both NUMBER1 and NUMBER2 default to 10.\n\t\t"
									 "\"fx[[NUMBER1][, NUMBER2]]\" Sets cell at {x = NUMBER1, y = NUMBER2} it's one's complement. Both NUMBER1 and NUMBER2 default to 10.\n\t\t"
									 "\"p\" Shows gamefield without ticking.\n\t\t"
									 "\"load[filename]\" Loads gamefield from [filename], or if not specified reloads the gamefield."
									 "\"<any other expression>\" Ticks the gamefield, then prints it.\n\t"
								"Info:\n\t\t"
								  "Follows standard Conway's rules of the Conway's game of life.\n\t\t"
									"Field is randomly populated at the beginning.\n\t\t"
									"Field automatically resizes itself as needed.\n\t"
								"Authors:\n\t\t"
								  "Nabijaczleweli: original authorism.\n\t\t"
									"More to come?\n\t"
								"Report bugs to <nabijaczlewei@gmail.com>\n";
			continue;
		}
		pre_tick(the_map, life_x, life_y);
		tick(the_map, life_x, life_y);
		for(unsigned int i = 0; i < (life_y * .75); ++i)
			cout << '\n';
		for(unsigned int y = 0; y < life_y; ++y) {
			for(unsigned int x = 0; x < life_x; ++x)
				cout << (the_map(y, x) ? '\262' : '\260');
			cout << '\n';
		}
	}
}

template<class T>
void tick(Mat<T> & map, const unsigned int & x_size, const unsigned int & y_size) {
	typename remove_reference<decltype(map)>::type new_map(map);

	for(unsigned int y = 1; y < (y_size - 1); ++y)
		for(unsigned int x = 1; x < (x_size - 1); ++x)
			switch(get_alive_neighbours(map, y, x)) {
				case 0 :
				case 1 :
				case 4 :
				case 5 :
				case 6 :
				case 7 :
				case 8 : new_map(y, x) = false; break;
				case 2 : break;
				case 3 : new_map(y, x) = true; break;
			}

	map = new_map;
}
template<class T>
unsigned int get_alive_neighbours(const Mat<T> & map, const unsigned int & posY, const unsigned int & posX) {
	return map(posY - 1, posX - 1) +
	       map(posY - 1, posX    ) +
	       map(posY - 1, posX + 1) +
	       map(posY,     posX - 1) +
	       map(posY,     posX + 1) +
	       map(posY + 1, posX - 1) +
	       map(posY + 1, posX    ) +
	       map(posY + 1, posX + 1);
}
template<class T>
void pre_tick(Mat<T> & map, unsigned int & x_size, unsigned int & y_size) {
	for(unsigned int y = 0; y < y_size; ++y)
		if(map(y, x_size - 2)) {
			map.resize(y_size, ++x_size);
			return;
		} else if(map(y, x_size - 1)) {
			map.resize(y_size, x_size += 2);
			return;
		}
	for(unsigned int x = 0; x < x_size; ++x)
		if(map(y_size - 2, x)) {
			map.resize(++y_size, x_size);
			return;
		} else if(map(y_size - 1, x)) {
			map.resize(y_size += 2, x_size);
			return;
		}

	for(unsigned int y = 0; y < y_size; ++y)
		if(map(y, 1)) {
			typename remove_reference<decltype(map)>::type new_map(y_size, ++x_size, fill::zeros);
			for(unsigned int sx = 1; sx < x_size; ++sx)
				new_map.col(sx) = map.col(sx - 1);
			map.~Mat<typename remove_reference<decltype(map(0, 0))>::type>();
			map = new_map;
			return;
		} else if(map(y, 0)) {
			typename remove_reference<decltype(map)>::type new_map(y_size, x_size += 2, fill::zeros);
			for(unsigned int sx = 2; sx < x_size; ++sx)
				new_map.col(sx) = map.col(sx - 2);
			map.~Mat<typename remove_reference<decltype(map(0, 0))>::type>();
			map = new_map;
			return;
		}
	for(unsigned int x = 0; x < x_size; ++x)
		if(map(1, x)) {
			typename remove_reference<decltype(map)>::type new_map(++y_size, x_size, fill::zeros);
			for(unsigned int sy = 1; sy < y_size; ++sy)
				new_map.row(sy) = map.row(sy - 1);
			map.~Mat<typename remove_reference<decltype(map(0, 0))>::type>();
			map = new_map;
			return;
		} else if(map(0, x)) {
			typename remove_reference<decltype(map)>::type new_map(y_size += 2, x_size, fill::zeros);
			for(unsigned int sy = 2; sy < y_size; ++sy)
				new_map.row(sy) = map.row(sy - 2);
			map.~Mat<typename remove_reference<decltype(map(0, 0))>::type>();
			map = new_map;
			return;
		}
}

pair<unsigned int, unsigned int> get_level_size(const string & filename) {
	ifstream input_file(filename);

	input_file.ignore(6);
	string temporary_input;
	getline(input_file, temporary_input);
	unsigned int x_size = atoi(temporary_input.c_str());

	input_file.ignore(7);
	getline(input_file, temporary_input);
	unsigned int y_size = atoi(temporary_input.c_str());

	return make_pair(x_size, y_size);
}

void print_bad_information(const string & filename, unsigned int line, unsigned int character, const char * const message) {
	cerr << filename << ':' << line << ':' << character << ": " << message << '\n';
	ifstream input_file(filename);
	for(unsigned int curline = 0; curline < line; ++curline)
		input_file.ignore(numeric_limits<streamsize>::max(), '\n');
	string output_line;
	getline(input_file, output_line);
	cerr << output_line << '\n';
	for(unsigned int i = 0; i < character; ++i)
		cerr << ' ';
	cerr << '^' << endl;
}

bool is_level_an_actual_level(const string & filename) {
#define TESTFILEFORCHARACTER(character_to_test_against)\
 if((chr = input_file.get()) != character_to_test_against) {\
	 char * buffer = new char[35 + ((chr == '\n') * 4) + ((character_to_test_against == '\n') * 4)];\
	 if(character_to_test_against != '\n') {\
	 	memcpy(buffer, "error: \'", 8);\
	 	buffer[8] = character_to_test_against;\
	 	buffer[9] = '\'';\
	 } else\
	 	memcpy(buffer, "error: newline", 14);\
	 memcpy(buffer + 10 + ((character_to_test_against == '\n') * 4), " expected, but \'", 16);\
	 if(chr != '\n') {\
	 	buffer[26 + ((character_to_test_against == '\n') * 4)] = chr;\
	 	memcpy(buffer + 27 + ((character_to_test_against == '\n') * 4), "\' found", 8);\
	 } else\
	 	memcpy(buffer + 25 + ((character_to_test_against == '\n') * 4), "newline found", 14);\
	 print_bad_information(filename, curline, curchar, buffer);\
	 delete[] buffer;\
	 if(chr != '\n')\
	  ++curchar;\
	 else {\
		++curline;\
		curchar = 0;\
	 }\
	 not_ok = true;\
 }
#define TESTFILEFORCHARACTERS(characters_to_test_against)\
 {\
	 string str = characters_to_test_against;\
	 for(unsigned int i = 0; i < str.size(); ++i)\
	 	TESTFILEFORCHARACTER(str[i]);\
 }

	ifstream input_file(filename);
	unsigned int curline = 0, curchar = 0;
	bool not_ok = false;
	int chr;

	TESTFILEFORCHARACTERS("width=")
	while(input_file.peek() != '\n' && input_file.peek() != ifstream::traits_type::eof()) {
		if(!isdigit(chr = input_file.get())) {
			char * buffer = new char[49];
			memcpy(buffer, "error: decimal character expected, but \'", 40);
			buffer[40] = chr;
			memcpy(buffer + 41, "\' found", 9);
			print_bad_information(filename, curline, curchar, buffer);
			delete[] buffer;
			not_ok = true;
		}
		++curchar;
	}
	TESTFILEFORCHARACTER('\n')
	++curline;
	curchar = 0;

	TESTFILEFORCHARACTERS("height=")
	while(input_file.peek() != '\n' && input_file.peek() != ifstream::traits_type::eof()) {
		if(!isdigit(chr = input_file.get())) {
			char * buffer = new char[49];
			memcpy(buffer, "error: decimal character expected, but \'", 40);
			buffer[40] = chr;
			memcpy(buffer + 41, "\' found", 9);
			print_bad_information(filename, curline, curchar, buffer);
			delete[] buffer;
			not_ok = true;
		}
		++curchar;
	}
	TESTFILEFORCHARACTER('\n')
	++curline;
	curchar = 0;

	while(input_file.peek() != ifstream::traits_type::eof()) {
		chr = input_file.get();
		if(!(chr == '0' || chr == '1' || chr == '\n')) {
			char * buffer = new char[59];
			memcpy(buffer, "error: binary character or newline expected, but \'", 50);
			buffer[50] = chr;
			memcpy(buffer + 51, "\' found", 9);
			print_bad_information(filename, curline, curchar, buffer);
			delete[] buffer;
			not_ok = true;
		} else if(chr == '\n') {
			++curline;
			curchar = 0;
		} else
			++curchar;
	}

	return !not_ok;

#undef TESTFILEFORCHARACTER
#undef TESTFILEFORCHARACTERS
}


#include <Eigen/Core>
#include <benchmark/benchmark.h>
#include <type_traits>
#include <variant>
#include <vector>
#include <cstdint>
#include <random>

// must have size divisible by size of float and should be trivial for performance
struct Particle {
  std::int32_t& pid() { return pid_; }

  float& px() { return px_; }
  float& py() { return py_; }
  float& pz() { return pz_; }
  float& e() { return e_; }

  float& x() { return x_; }
  float& y() { return y_; }
  float& z() { return z_; }
  float& t() { return t_; }

  // "private" variables
  std::int32_t pid_;
  float px_, py_, pz_, e_;
  float x_, y_, z_, t_;
};

// quantity does not have a trivial default constructor, this may be a performance issue
static_assert(std::is_trivial<Particle>::value);

// size of Particle must be multiple of size of float for Eigen::Map to work
static_assert(sizeof(Particle) % sizeof(float) == 0);

template <class T>
using ArrayView = Eigen::Map<
    Eigen::Array<T, Eigen::Dynamic, 1>,
    Eigen::Unaligned,
    Eigen::InnerStride<(sizeof(Particle) / sizeof(float))>
>;

class ParticleSpan {
public:
    using pointer = Particle*;
    using iterator = pointer;
    using ArrayFView = ArrayView<float>;
    using ArrayIView = ArrayView<std::int32_t>;

    ParticleSpan(pointer b, pointer e) : begin_(b), end_(e) {};

    iterator begin() { return begin_; }
    iterator end() { return end_; }

    std::size_t size() { return end_ - begin_; }

    ArrayIView pid() { return ArrayIView(&begin_->pid_, size()); }
    ArrayFView px() { return ArrayFView(&begin_->px_, size()); }
    ArrayFView py() { return ArrayFView(&begin_->py_, size()); }
    ArrayFView pz() { return ArrayFView(&begin_->pz_, size()); }
    ArrayFView e() { return ArrayFView(&begin_->e_, size()); }
    ArrayFView x() { return ArrayFView(&begin_->x_, size()); }
    ArrayFView y() { return ArrayFView(&begin_->y_, size()); }
    ArrayFView z() { return ArrayFView(&begin_->z_, size()); }
    ArrayFView t() { return ArrayFView(&begin_->t_, size()); }

private:
    iterator begin_, end_;
};

// setup the particle stack for the benchmarks
auto setup_stack() {
  std::vector<Particle> stack(100000);
  int i = 0;
  // make 1/3 of particles neutral
  for (auto&& part : stack)
    part.pid() = (++i % 3 - 1);
  return stack;
}

template <class T>
decltype(auto) sqr(const T& x) {
  return x * x;
}

template <class T>
decltype(auto) momentum_squared(T& part) {
  return sqr(part.px()) + sqr(part.py()) + sqr(part.pz());
}

template <class T>
decltype(auto) charge(T& part) {
  // this could be a complicated function or a lookup table
  return part.pid() != 0;
}

decltype(auto) charge(ParticleSpan& part) {
  // this could be a complicated function or a lookup table
  return (part.pid() != 0).template cast<float>();
}

// note: this function body looks the same whether we pass one particle or a span!
template <class T>
void energy_loss(T& part) {
  decltype(auto) beta_2 = momentum_squared(part) / sqr(part.e());
  // compute energy loss, ignoring all constants
  using std::log; // allow Eigen to find its own log via ADL
  decltype(auto) energy_loss = charge(part) * (log(beta_2 / (1.0 - beta_2)) / beta_2 - 1.0);
  part.e() -= energy_loss;
}

// ... nevertheless we try a special one particle version for comparison
void energy_loss(Particle& part) {
  auto beta_2 = momentum_squared(part) / sqr(part.e());
  const auto c = charge(part);
  if (c != 0) {
    // compute energy loss, ignoring all constants
    const auto energy_loss = c * (std::log(beta_2 / (1.0 - beta_2)) / beta_2 - 1.0);
    part.e() -= energy_loss;
  }
}

// note: again function body looks the same whether we pass one particle or a span
template <class T>
void move_particle(T& p) {
  const auto dt = 0.1;
  p.x() += p.px() * dt;
  p.y() += p.py() * dt;
  p.z() += p.pz() * dt;
  p.t() += dt;
}

struct ContinuousEnergyLoss {
  template <class T>
  void operator()(T& p) const { energy_loss(p); }
};

struct ContinuousEnergyLossNoEigen {
  void operator()(ParticleSpan& span) const {
    for (auto&& p : span)
      energy_loss(p);
  }
};

struct MoveParticle {
  template <class T>
  void operator()(T& p) const { move_particle(p); }
};

struct MoveParticleNoEigen {
  void operator()(ParticleSpan& span) const {
    for (auto&& p : span)
      move_particle(p);
  }
};

using ProcessVariant = std::variant<ContinuousEnergyLoss, ContinuousEnergyLossNoEigen,
                                    MoveParticle, MoveParticleNoEigen>;
using ProcessList = std::vector<ProcessVariant>;

// Method 1: process one particle at once
static void process_one(benchmark::State& state) {
  auto stack = setup_stack();

  ParticleSpan span(stack.data(), stack.data() + state.range(0));

  for (auto _ : state) {
    for (auto&& p : span) {
      energy_loss(p);
      move_particle(p);
    }
  }
}

// Method 2: process block of particles at once
static void process_span(benchmark::State& state) {
  auto stack = setup_stack();

  ParticleSpan span(stack.data(), stack.data() + state.range(0));

  for (auto _ : state) {
    energy_loss(span);
    move_particle(span);
  }
}

// Method 3: like Method 2, but don't use Eigen
static void process_span_no_eigen(benchmark::State& state) {
  auto stack = setup_stack();

  ParticleSpan span(stack.data(), stack.data() + state.range(0));

  for (auto _ : state) {
    for (auto&& p : span) {
      energy_loss(p);
      move_particle(p);
    }
  }
}

// Method 1A: process one particle at once using std::variant of processes
static void variant_process_one(benchmark::State& state) {
  auto stack = setup_stack();

  ParticleSpan span(stack.data(), stack.data() + state.range(0));

  ProcessList process_list;
  process_list.emplace_back(ContinuousEnergyLoss());
  process_list.emplace_back(MoveParticle());

  for (auto _ : state) {
    for (auto&& p : span) {
      for (const auto& process : process_list) {
        visit([&span](auto& proc) { proc(span); }, process);
      }
    }
  }
}

// Method 2A: process block of particles at once using std::variant of processes
static void variant_process_span(benchmark::State& state) {
  auto stack = setup_stack();

  ParticleSpan span(stack.data(), stack.data() + state.range(0));

  ProcessList process_list;
  process_list.emplace_back(ContinuousEnergyLoss());
  process_list.emplace_back(MoveParticle());

  for (auto _ : state)
    for (const auto& process : process_list)
      visit([&span](auto& proc) { proc(span); }, process);
}

// Method 3A: like Method 2A, but don't use eigen
static void variant_process_span_no_eigen(benchmark::State& state) {
  auto stack = setup_stack();

  ParticleSpan span(stack.data(), stack.data() + state.range(0));

  ProcessList process_list;
  process_list.emplace_back(ContinuousEnergyLossNoEigen());
  process_list.emplace_back(MoveParticleNoEigen());

  for (auto _ : state)
    for (const auto& process : process_list)
      visit([&span](auto& proc) { proc(span); }, process);
}

BENCHMARK(process_one)->RangeMultiplier(2)->Range(1, 10000);
BENCHMARK(process_span)->RangeMultiplier(2)->Range(1, 10000);
BENCHMARK(process_span_no_eigen)->RangeMultiplier(2)->Range(1, 10000);
BENCHMARK(variant_process_one)->RangeMultiplier(2)->Range(1, 10000);
BENCHMARK(variant_process_span)->RangeMultiplier(2)->Range(1, 10000);
BENCHMARK(variant_process_span_no_eigen)->RangeMultiplier(2)->Range(1, 10000);


//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
//
#ifndef BOOST_GRAPH_MST_PRIM_HPP
#define BOOST_GRAPH_MST_PRIM_HPP

#include <functional>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>

namespace boost
{

namespace detail
{
    // this should be somewhere else in boost...
    template < class U, class V > struct _project2nd
    {
        V operator()(U, V v) const { return v; }
    };
}

namespace detail
{

    // This is Prim's algorithm to calculate the Minimum Spanning Tree
    // for an undirected graph with weighted edges.

    template < class Graph, class P, class T, class R, class Weight >
    inline void prim_mst_impl(const Graph& G,
        typename graph_traits< Graph >::vertex_descriptor s,
        const bgl_named_params< P, T, R >& params, Weight)
    {
        typedef typename property_traits< Weight >::value_type W;
        std::less< W > compare;
        detail::_project2nd< W, W > combine;
        dijkstra_shortest_paths(
            G, s, params.distance_compare(compare).distance_combine(combine));
    }
} // namespace detail

template < class VertexListGraph, class DijkstraVisitor, class PredecessorMap,
    class DistanceMap, class WeightMap, class IndexMap >
inline void prim_minimum_spanning_tree(const VertexListGraph& g,
    typename graph_traits< VertexListGraph >::vertex_descriptor s,
    PredecessorMap predecessor, DistanceMap distance, WeightMap weight,
    IndexMap index_map, DijkstraVisitor vis)
{
    typedef typename property_traits< WeightMap >::value_type W;
    std::less< W > compare;
    detail::_project2nd< W, W > combine;
    dijkstra_shortest_paths(g, s, predecessor, distance, weight, index_map,
        compare, combine, (std::numeric_limits< W >::max)(), 0, vis);
}

template < class VertexListGraph, class PredecessorMap, class P, class T,
    class R >
inline void prim_minimum_spanning_tree(const VertexListGraph& g,
    PredecessorMap p_map, const bgl_named_params< P, T, R >& params)
{
    detail::prim_mst_impl(g,
        choose_param(get_param(params, root_vertex_t()), *vertices(g).first),
        params.predecessor_map(p_map),
        choose_const_pmap(get_param(params, edge_weight), g, edge_weight));
}

template < class VertexListGraph, class PredecessorMap >
inline void prim_minimum_spanning_tree(
    const VertexListGraph& g, PredecessorMap p_map)
{
    detail::prim_mst_impl(g, *vertices(g).first,
        predecessor_map(p_map).weight_map(get(edge_weight, g)),
        get(edge_weight, g));
}

} // namespace boost

#endif // BOOST_GRAPH_MST_PRIM_HPP


#include <cctbx/boost_python/flex_fwd.h>

#include <cctbx/hendrickson_lattman.h>
#include <cctbx/error.h>
#include <scitbx/array_family/boost_python/flex_wrapper.h>
#include <scitbx/serialization/single_buffered.h>
#include <boost/python/args.hpp>
#include <boost/python/make_constructor.hpp>

namespace scitbx { namespace serialization { namespace single_buffered {

  inline
  char* to_string(char* start, cctbx::hendrickson_lattman<> const& value)
  {
    return to_string(to_string(to_string(to_string(start,
      value[0]), value[1]), value[2]), value[3]);
  }

  template <>
  struct from_string<cctbx::hendrickson_lattman<> >
  {
    from_string(const char* start)
    {
      end = start;
      for(std::size_t i=0;i<4;i++) {
        from_string<double> proxy(end);
        value[i] = proxy.value;
        end = proxy.end;
      }
    }

    cctbx::hendrickson_lattman<> value;
    const char* end;
  };

}}} // namespace scitbx::serialization::single_buffered

#include <scitbx/array_family/boost_python/flex_pickle_single_buffered.h>

namespace scitbx { namespace af { namespace boost_python {

namespace {

  flex<cctbx::hendrickson_lattman<> >::type*
  from_a_b(
    af::const_ref<double> const& a,
    af::const_ref<double> const& b)
  {
    CCTBX_ASSERT(a.size() == b.size());
    af::shared<cctbx::hendrickson_lattman<> > result;
    result.reserve(a.size());
    for(std::size_t i=0;i<a.size();i++) {
      result.push_back(cctbx::hendrickson_lattman<>(a[i], b[i], 0, 0));
    }
    return new flex<cctbx::hendrickson_lattman<> >::type(
      result, result.size());
  }

  flex<cctbx::hendrickson_lattman<> >::type*
  from_a_b_c_d(
    af::const_ref<double> const& a,
    af::const_ref<double> const& b,
    af::const_ref<double> const& c,
    af::const_ref<double> const& d)
  {
    CCTBX_ASSERT(a.size() == b.size());
    CCTBX_ASSERT(a.size() == c.size());
    CCTBX_ASSERT(c.size() == d.size());
    af::shared<cctbx::hendrickson_lattman<> > result;
    result.reserve(a.size());
    for(std::size_t i=0;i<a.size();i++) {
      result.push_back(cctbx::hendrickson_lattman<>(a[i], b[i], c[i], d[i]));
    }
    return new flex<cctbx::hendrickson_lattman<> >::type(
      result, result.size());
  }

  af::tiny<af::shared<double>, 4>
  to_a_b_c_d(
    af::const_ref<cctbx::hendrickson_lattman<> > const& hl)
  {
    af::shared<double> a;
    af::shared<double> b;
    af::shared<double> c;
    af::shared<double> d;
    a.reserve(hl.size());
    b.reserve(hl.size());
    c.reserve(hl.size());
    d.reserve(hl.size());
    for(std::size_t i=0;i<hl.size();i++) {
      a.push_back(hl[i].a());
      b.push_back(hl[i].b());
      c.push_back(hl[i].c());
      d.push_back(hl[i].d());
    }
    af::tiny<af::shared<double>, 4> result(a, b, c, d);
    return result;
  }

  flex<cctbx::hendrickson_lattman<> >::type*
  from_phase_integrals(
    af::const_ref<bool> const& centric_flags,
    af::const_ref<std::complex<double> > const& phase_integrals,
    double max_figure_of_merit)
  {
    CCTBX_ASSERT(phase_integrals.size() == centric_flags.size());
    af::shared<cctbx::hendrickson_lattman<> > result;
    result.reserve(centric_flags.size());
    for(std::size_t i=0;i<centric_flags.size();i++) {
      result.push_back(cctbx::hendrickson_lattman<>(
        centric_flags[i],
        phase_integrals[i],
        max_figure_of_merit));
    }
    return new flex<cctbx::hendrickson_lattman<> >::type(
      result, result.size());
  }

  af::shared<double>
  slice(
    af::const_ref<cctbx::hendrickson_lattman<> > const& self,
    unsigned i_param)
  {
    CCTBX_ASSERT(i_param < 4);
    af::shared<double> result((af::reserve(self.size())));
    for(std::size_t i=0;i<self.size();i++) {
      result.push_back(self[i][i_param]);
    }
    return result;
  }

  af::shared<cctbx::hendrickson_lattman<> >
  conj(af::shared<cctbx::hendrickson_lattman<> > const& self)
  {
    af::shared<cctbx::hendrickson_lattman<> >
      result((af::reserve(self.size())));
    for(std::size_t i=0;i<self.size();i++) {
      result.push_back(self[i].conj());
    }
    return result;
  }

  af::shared<cctbx::hendrickson_lattman<> >
  multiply_by_constant(
    af::shared<cctbx::hendrickson_lattman<> > const& self,
    double const& rhs)
  {
    af::shared<cctbx::hendrickson_lattman<> >
      result((af::reserve(self.size())));
    for(std::size_t i=0;i<self.size();i++) {
      result.push_back(self[i] * rhs);
    }
    return result;
  }

} // namespace <anonymous>

  template <>
  struct flex_default_element<cctbx::hendrickson_lattman<> >
  {
    static cctbx::hendrickson_lattman<>
    get() { return cctbx::hendrickson_lattman<>(0,0,0,0); }
  };

  void wrap_flex_hendrickson_lattman()
  {
    using namespace boost::python;
    using boost::python::arg;
    typedef flex_wrapper<cctbx::hendrickson_lattman<> > f_w;
    f_w::plain("hendrickson_lattman")
      .def_pickle(flex_pickle_single_buffered<cctbx::hendrickson_lattman<>,
        4*pickle_size_per_element<
          cctbx::hendrickson_lattman<>::base_type::value_type>::value>())
      .def("__init__", make_constructor(
        from_a_b,
        default_call_policies(),
        (arg("a"),
         arg("b"))))
      .def("__init__", make_constructor(
        from_a_b_c_d,
        default_call_policies(),
        (arg("a"),
         arg("b"),
         arg("c"),
         arg("d"))))
      .def("__init__", make_constructor(
        from_phase_integrals,
        default_call_policies(),
        (arg("centric_flags"),
         arg("phase_integrals"),
         arg("max_figure_of_merit"))))
      .def("count", f_w::count)
      .def("__add__", f_w::add_a_a)
      .def("__iadd__", f_w::iadd_a_a)
      .def("__mul__", multiply_by_constant)
      .def("all_eq", f_w::all_eq_a_s)
      .def("__eq__", f_w::eq_a_a)
      .def("__ne__", f_w::ne_a_a)
      .def("__eq__", f_w::eq_a_s)
      .def("__ne__", f_w::ne_a_s)
      .def("slice", slice, (arg("self"), arg("i_param")))
      .def("conj", conj)
      .def("as_abcd", to_a_b_c_d)
    ;
  }

}}} // namespace scitbx::af::boost_python