/* ----------------------------------------------------------------------------

 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)

 * See LICENSE for the license information

 * -------------------------------------------------------------------------- */

/**
 * @file    JacobianFactor.cpp
 * @author  Richard Roberts
 * @date    Dec 8, 2010
 */

#include <gtsam/linear/linearExceptions.h>
#include <gtsam/linear/GaussianConditional.h>
#include <gtsam/linear/JacobianFactor.h>
#include <gtsam/linear/HessianFactor.h>
#include <gtsam/linear/GaussianFactorGraph.h>
#include <gtsam/inference/VariableSlots.h>
#include <gtsam/base/debug.h>
#include <gtsam/base/timing.h>
#include <gtsam/base/Matrix.h>
#include <gtsam/base/FastMap.h>
#include <gtsam/base/cholesky.h>

#include <boost/foreach.hpp>
#include <boost/format.hpp>
#include <boost/make_shared.hpp>
#include <boost/bind.hpp>
//#include <boost/lambda/bind.hpp>
//#include <boost/lambda/lambda.hpp>

#include <cmath>
#include <sstream>
#include <stdexcept>

using namespace std;
//using namespace boost::lambda;

namespace gtsam {

  /* ************************************************************************* */
  void JacobianFactor::assertInvariants() const {
#ifndef NDEBUG
    GaussianFactor::assertInvariants(); // The base class checks for unique keys
    assert((size() == 0 && Ab_.rows() == 0 && Ab_.nBlocks() == 0) || size()+1 == Ab_.nBlocks());
#endif
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const JacobianFactor& gf) :
      GaussianFactor(gf), model_(gf.model_), Ab_(matrix_) {
    Ab_.assignNoalias(gf.Ab_);
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const GaussianFactor& gf) : Ab_(matrix_) {
    // Copy the matrix data depending on what type of factor we're copying from
    if(const JacobianFactor* rhs = dynamic_cast<const JacobianFactor*>(&gf))
      *this = JacobianFactor(*rhs);
    else if(const HessianFactor* rhs = dynamic_cast<const HessianFactor*>(&gf))
      *this = JacobianFactor(*rhs);
    else
      throw std::invalid_argument("In JacobianFactor(const GaussianFactor& rhs), rhs is neither a JacobianFactor nor a HessianFactor");
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor() : Ab_(matrix_) { assertInvariants(); }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const Vector& b_in) : Ab_(matrix_) {
    size_t dims[] = { 1 };
    Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+1, b_in.size()));
    getb() = b_in;
    model_ = noiseModel::Unit::Create(this->rows());
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(Index i1, const Matrix& A1,
      const Vector& b, const SharedDiagonal& model) :
      GaussianFactor(i1), model_(model), Ab_(matrix_) {

    if(model->dim() != (size_t) b.size())
      throw InvalidNoiseModel(b.size(), model->dim());

    size_t dims[] = { A1.cols(), 1};
    Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+2, b.size()));
    Ab_(0) = A1;
    getb() = b;
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(Index i1, const Matrix& A1, Index i2, const Matrix& A2,
      const Vector& b, const SharedDiagonal& model) :
      GaussianFactor(i1,i2), model_(model), Ab_(matrix_) {

    if(model->dim() != (size_t) b.size())
      throw InvalidNoiseModel(b.size(), model->dim());

    size_t dims[] = { A1.cols(), A2.cols(), 1};
    Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+3, b.size()));
    Ab_(0) = A1;
    Ab_(1) = A2;
    getb() = b;
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(Index i1, const Matrix& A1, Index i2, const Matrix& A2,
      Index i3, const Matrix& A3, const Vector& b, const SharedDiagonal& model) :
      GaussianFactor(i1,i2,i3), model_(model), Ab_(matrix_) {

    if(model->dim() != (size_t) b.size())
      throw InvalidNoiseModel(b.size(), model->dim());

    size_t dims[] = { A1.cols(), A2.cols(), A3.cols(), 1};
    Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+4, b.size()));
    Ab_(0) = A1;
    Ab_(1) = A2;
    Ab_(2) = A3;
    getb() = b;
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const std::vector<std::pair<Index, Matrix> > &terms,
  		const Vector &b, const SharedDiagonal& model) :
  	GaussianFactor(GetKeys(terms.size(), terms.begin(), terms.end())),
		model_(model), Ab_(matrix_)
  {

    if(model->dim() != (size_t) b.size())
      throw InvalidNoiseModel(b.size(), model->dim());

    size_t* dims = (size_t*)alloca(sizeof(size_t)*(terms.size()+1)); // FIXME: alloca is bad, just ask Google.
    for(size_t j=0; j<terms.size(); ++j)
      dims[j] = terms[j].second.cols();
    dims[terms.size()] = 1;
    Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+terms.size()+1, b.size()));
    for(size_t j=0; j<terms.size(); ++j)
      Ab_(j) = terms[j].second;
    getb() = b;
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const std::list<std::pair<Index, Matrix> > &terms,
      const Vector &b, const SharedDiagonal& model) :
      GaussianFactor(GetKeys(terms.size(), terms.begin(), terms.end())),
    model_(model), Ab_(matrix_)
  {

    if(model->dim() != (size_t) b.size())
      throw InvalidNoiseModel(b.size(), model->dim());

    size_t* dims=(size_t*)alloca(sizeof(size_t)*(terms.size()+1)); // FIXME: alloca is bad, just ask Google.
    size_t j=0;
    std::list<std::pair<Index, Matrix> >::const_iterator term=terms.begin();
    for(; term!=terms.end(); ++term,++j)
      dims[j] = term->second.cols();
    dims[j] = 1;
    Ab_.copyStructureFrom(BlockAb(matrix_, dims, dims+terms.size()+1, b.size()));
    j = 0;
    for(term=terms.begin(); term!=terms.end(); ++term,++j)
      Ab_(j) = term->second;
    getb() = b;
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const GaussianConditional& cg) :
		GaussianFactor(cg),
		model_(noiseModel::Diagonal::Sigmas(cg.get_sigmas(), true)),
		Ab_(matrix_) {
    Ab_.assignNoalias(cg.rsd_);
    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor::JacobianFactor(const HessianFactor& factor) : Ab_(matrix_) {
    keys_ = factor.keys_;
    Ab_.assignNoalias(factor.info_);

		// Do Cholesky to get a Jacobian
    size_t maxrank;
		bool success;
		boost::tie(maxrank, success) = choleskyCareful(matrix_);

		// Check for indefinite system
		if(!success)
			throw IndeterminantLinearSystemException(factor.keys().front());

    // Zero out lower triangle
    matrix_.topRows(maxrank).triangularView<Eigen::StrictlyLower>() =
        Matrix::Zero(maxrank, matrix_.cols());
    // FIXME: replace with triangular system
    Ab_.rowEnd() = maxrank;
    model_ = noiseModel::Unit::Create(maxrank);

    assertInvariants();
  }

  /* ************************************************************************* */
  JacobianFactor& JacobianFactor::operator=(const JacobianFactor& rhs) {
    this->Base::operator=(rhs); // Copy keys
    model_ = rhs.model_;        // Copy noise model
    Ab_.assignNoalias(rhs.Ab_); // Copy matrix and block structure
    assertInvariants();
    return *this;
  }

  /* ************************************************************************* */
  void JacobianFactor::print(const string& s, const IndexFormatter& formatter) const {
    cout << s << "\n";
    if (empty()) {
      cout << " empty, keys: ";
      BOOST_FOREACH(const Index& key, keys()) { cout << formatter(key) << " "; }
      cout << endl;
    } else {
      for(const_iterator key=begin(); key!=end(); ++key)
      	cout << boost::format("A[%1%]=\n")%formatter(*key) << getA(key) << endl;
      cout << "b=" << getb() << endl;
      model_->print("model");
    }
  }

  /* ************************************************************************* */
  // Check if two linear factors are equal
  bool JacobianFactor::equals(const GaussianFactor& f_, double tol) const {
    if(!dynamic_cast<const JacobianFactor*>(&f_))
      return false;
    else {
      const JacobianFactor& f(static_cast<const JacobianFactor&>(f_));
      if (empty()) return (f.empty());
      if(keys()!=f.keys() /*|| !model_->equals(lf->model_, tol)*/)
        return false;

      if (!(Ab_.rows() == f.Ab_.rows() && Ab_.cols() == f.Ab_.cols()))
      	return false;

      constABlock Ab1(Ab_.range(0, Ab_.nBlocks()));
      constABlock Ab2(f.Ab_.range(0, f.Ab_.nBlocks()));
      for(size_t row=0; row< (size_t) Ab1.rows(); ++row)
        if(!equal_with_abs_tol(Ab1.row(row), Ab2.row(row), tol) &&
            !equal_with_abs_tol(-Ab1.row(row), Ab2.row(row), tol))
          return false;

      return true;
    }
  }

  /* ************************************************************************* */
  Vector JacobianFactor::unweighted_error(const VectorValues& c) const {
    Vector e = -getb();
    if (empty()) return e;
    for(size_t pos=0; pos<size(); ++pos)
      e += Ab_(pos) * c[keys_[pos]];
    return e;
  }

  /* ************************************************************************* */
  Vector JacobianFactor::error_vector(const VectorValues& c) const {
    if (empty()) return model_->whiten(-getb());
    return model_->whiten(unweighted_error(c));
  }

  /* ************************************************************************* */
  double JacobianFactor::error(const VectorValues& c) const {
    if (empty()) return 0;
    Vector weighted = error_vector(c);
    return 0.5 * weighted.dot(weighted);
  }

  /* ************************************************************************* */
  Matrix JacobianFactor::computeInformation() const {
    Matrix AbWhitened = Ab_.full();
    model_->WhitenInPlace(AbWhitened);
    return AbWhitened.transpose() * AbWhitened;
  }

  /* ************************************************************************* */
  Vector JacobianFactor::operator*(const VectorValues& x) const {
    Vector Ax = zero(Ab_.rows());
    if (empty()) return Ax;

    // Just iterate over all A matrices and multiply in correct config part
    for(size_t pos=0; pos<size(); ++pos)
      Ax += Ab_(pos) * x[keys_[pos]];

    return model_->whiten(Ax);
  }

  /* ************************************************************************* */
  void JacobianFactor::transposeMultiplyAdd(double alpha, const Vector& e,
      VectorValues& x) const {
    Vector E = alpha * model_->whiten(e);
    // Just iterate over all A matrices and insert Ai^e into VectorValues
    for(size_t pos=0; pos<size(); ++pos)
      gtsam::transposeMultiplyAdd(1.0, Ab_(pos), E, x[keys_[pos]]);
  }

  /* ************************************************************************* */
  pair<Matrix,Vector> JacobianFactor::matrix(bool weight) const {
    Matrix A(Ab_.range(0, size()));
    Vector b(getb());
    // divide in sigma so error is indeed 0.5*|Ax-b|
    if (weight) model_->WhitenSystem(A,b);
    return make_pair(A, b);
  }

  /* ************************************************************************* */
  Matrix JacobianFactor::matrix_augmented(bool weight) const {
    if (weight) { Matrix Ab(Ab_.range(0,Ab_.nBlocks())); model_->WhitenInPlace(Ab); return Ab; }
    else return Ab_.range(0, Ab_.nBlocks());
  }

  /* ************************************************************************* */
  std::vector<boost::tuple<size_t, size_t, double> >
  JacobianFactor::sparse(const std::vector<size_t>& columnIndices) const {

    std::vector<boost::tuple<size_t, size_t, double> > entries;

    // iterate over all variables in the factor
    for(const_iterator var=begin(); var<end(); ++var) {
      Matrix whitenedA(model_->Whiten(getA(var)));
      // find first column index for this key
      size_t column_start = columnIndices[*var];
      for (size_t i = 0; i < (size_t) whitenedA.rows(); i++)
        for (size_t j = 0; j < (size_t) whitenedA.cols(); j++) {
        	double s = whitenedA(i,j);
          if (std::abs(s) > 1e-12) entries.push_back(
							boost::make_tuple(i, column_start + j, s));
        }
    }

    Vector whitenedb(model_->whiten(getb()));
    size_t bcolumn = columnIndices.back();
    for (size_t i = 0; i < (size_t) whitenedb.size(); i++)
      entries.push_back(boost::make_tuple(i, bcolumn, whitenedb(i)));

    // return the result
    return entries;
  }

  /* ************************************************************************* */
  JacobianFactor JacobianFactor::whiten() const {
    JacobianFactor result(*this);
    result.model_->WhitenInPlace(result.matrix_);
    result.model_ = noiseModel::Unit::Create(result.model_->dim());
    return result;
  }

  /* ************************************************************************* */
  GaussianFactor::shared_ptr JacobianFactor::negate() const {
  	HessianFactor hessian(*this);
  	return hessian.negate();
  }

  /* ************************************************************************* */
  GaussianConditional::shared_ptr JacobianFactor::eliminateFirst() {
    return this->eliminate(1);
  }

  /* ************************************************************************* */
  GaussianConditional::shared_ptr JacobianFactor::splitConditional(size_t nrFrontals) {
  	assert(Ab_.rowStart() == 0 && Ab_.rowEnd() == (size_t) matrix_.rows() && Ab_.firstBlock() == 0);
  	assert(size() >= nrFrontals);
  	assertInvariants();

  	const bool debug = ISDEBUG("JacobianFactor::splitConditional");

  	if(debug) cout << "Eliminating " << nrFrontals << " frontal variables" << endl;
  	if(debug) this->print("Splitting JacobianFactor: ");

  	size_t frontalDim = Ab_.range(0,nrFrontals).cols();

  	// Check for singular factor
  	if(model_->dim() < frontalDim)
			throw IndeterminantLinearSystemException(this->keys().front());

  	// Extract conditional
  	tic(3, "cond Rd");

  	// Restrict the matrix to be in the first nrFrontals variables
  	Ab_.rowEnd() = Ab_.rowStart() + frontalDim;
  	const Eigen::VectorBlock<const Vector> sigmas = model_->sigmas().segment(Ab_.rowStart(), Ab_.rowEnd()-Ab_.rowStart());
  	GaussianConditional::shared_ptr conditional(new GaussianConditional(begin(), end(), nrFrontals, Ab_, sigmas));
  	if(debug) conditional->print("Extracted conditional: ");
  	Ab_.rowStart() += frontalDim;
  	Ab_.firstBlock() += nrFrontals;
  	toc(3, "cond Rd");

  	if(debug) conditional->print("Extracted conditional: ");

  	tic(4, "remaining factor");
  	// Take lower-right block of Ab to get the new factor
  	Ab_.rowEnd() = model_->dim();
  	keys_.erase(begin(), begin() + nrFrontals);
  	// Set sigmas with the right model
  	if (model_->isConstrained())
  		model_ = noiseModel::Constrained::MixedSigmas(sub(model_->sigmas(), frontalDim, model_->dim()));
  	else
  		model_ = noiseModel::Diagonal::Sigmas(sub(model_->sigmas(), frontalDim, model_->dim()));
  	if(debug) this->print("Eliminated factor: ");
  	assert(Ab_.rows() <= Ab_.cols()-1);
  	toc(4, "remaining factor");

  	if(debug) print("Eliminated factor: ");

  	assertInvariants();

  	return conditional;
  }

  /* ************************************************************************* */
  GaussianConditional::shared_ptr JacobianFactor::eliminate(size_t nrFrontals) {

    assert(Ab_.rowStart() == 0 && Ab_.rowEnd() == (size_t) matrix_.rows() && Ab_.firstBlock() == 0);
    assert(size() >= nrFrontals);
    assertInvariants();

    const bool debug = ISDEBUG("JacobianFactor::eliminate");

    if(debug) cout << "Eliminating " << nrFrontals << " frontal variables" << endl;
    if(debug) this->print("Eliminating JacobianFactor: ");
    if(debug) gtsam::print(matrix_, "Augmented Ab: ");

    size_t frontalDim = Ab_.range(0,nrFrontals).cols();

    if(debug) cout << "frontalDim = " << frontalDim << endl;

    // Use in-place QR dense Ab appropriate to NoiseModel
    tic(2, "QR");
    SharedDiagonal noiseModel = model_->QR(matrix_);
    toc(2, "QR");

    // Zero the lower-left triangle.  todo: not all of these entries actually
    // need to be zeroed if we are careful to start copying rows after the last
    // structural zero.
    if(matrix_.rows() > 0)
      for(size_t j=0; j<(size_t) matrix_.cols(); ++j)
        for(size_t i=j+1; i<noiseModel->dim(); ++i)
          matrix_(i,j) = 0.0;

    if(debug) gtsam::print(matrix_, "QR result: ");
    if(debug) noiseModel->print("QR result noise model: ");

    // Start of next part
    model_ = noiseModel;
    return splitConditional(nrFrontals);
  }

  /* ************************************************************************* */
  void JacobianFactor::allocate(const VariableSlots& variableSlots, vector<
			size_t>& varDims, size_t m) {
		keys_.resize(variableSlots.size());
		std::transform(variableSlots.begin(), variableSlots.end(), begin(),
				boost::bind(&VariableSlots::const_iterator::value_type::first, _1));
		varDims.push_back(1);
		Ab_.copyStructureFrom(BlockAb(matrix_, varDims.begin(), varDims.end(), m));
	}

  /* ************************************************************************* */
	void JacobianFactor::setModel(bool anyConstrained, const Vector& sigmas) {
    if((size_t) sigmas.size() != this->rows())
      throw InvalidNoiseModel(this->rows(), sigmas.size());
		if (anyConstrained)
			model_ = noiseModel::Constrained::MixedSigmas(sigmas);
		else
			model_ = noiseModel::Diagonal::Sigmas(sigmas);
	}

  /* ************************************************************************* */
  const char* JacobianFactor::InvalidNoiseModel::what() const throw() {
    if(description_.empty())
      description_ = (boost::format(
        "A JacobianFactor was attempted to be constructed or modified to use a\n"
        "noise model of incompatible dimension.  The JacobianFactor has\n"
        "dimensionality (i.e. length of error vector) %d but the provided noise\n"
        "model has dimensionality %d.") % factorDims % noiseModelDims).str();
    return description_.c_str();
  }

}


/* Copyright 2019, LAAS-CNRS
 *
 * Olivier Stasse
 *
 */
#include <sstream>
#include <iostream>

#include <dynamic-graph/factory.h>

// Needed for sendMsg.
#define ENABLE_RT_LOG
#include <dynamic-graph/real-time-logger.h>

#include <sot/torque_control/control-manager.hh>

#include <sot/core/robot-utils.hh>
#include <sot/core/debug.hh>

#define BOOST_TEST_MODULE test-control-manager

#include <boost/test/unit_test.hpp>
#include <boost/test/output_test_stream.hpp>

using boost::test_tools::output_test_stream;
namespace dyn_sot_tc = dynamicgraph::sot::torque_control;

BOOST_AUTO_TEST_CASE(testControlManager)
{
  dgADD_OSTREAM_TO_RTLOG (std::cout);

  dyn_sot_tc::ControlManager &a_control_manager =
    *(dynamic_cast<dyn_sot_tc::ControlManager *>
      (dynamicgraph::FactoryStorage::
       getInstance()->newEntity("ControlManager",
				"a_control_manager")));

  if (boost::unit_test::framework::master_test_suite().argc!=3)
    BOOST_CHECK(false);

  a_control_manager.setLoggerVerbosityLevel(dynamicgraph::VERBOSITY_ALL);

  std::string urdfFile =  boost::unit_test::framework::
    master_test_suite().argv[1];
  std::string robotRef("simple_humanoid_description");
  a_control_manager.init(0.001,urdfFile,robotRef);

  dynamicgraph::Vector av;
  a_control_manager.m_uSOUT.needUpdate(6);

  RealTimeLogger::destroy();
}


#include "particle_engine.h"
#include "counter.h"
#include "shader_manager.h"
#include "vao_base.h"
#include "font.h"
#include "i_render.h"
#include "sprite.h"
#include "particle_template_repo.h"
#include "particle_layer_repo.h"
#include <boost/algorithm/clamp.hpp>

namespace render {
namespace {
struct Particle
{
    ParticleTemplate const* Template;
    glm::vec4 Color;
    glm::vec2 Pos;
    glm::vec2 Speed;
    glm::vec2 Acceleration;
    float Heading;
    float RotationSpeed;
    float RotationAcceleration;
    float Lifetime;
    float InitialLifetime;
    float Radius;
    Particle( ParticleTemplate const* pt, glm::vec2 const& pos, double ori );
};
Particle::Particle( ParticleTemplate const* ppt, glm::vec2 const& pos, double ori )
    : Template( ppt )
{
    ParticleTemplate const& pt = *ppt;
#define COLOR( channel ) \
    std::max<double>( 0.0, std::min<double>( 1.0, ( ( pt.Color.channel - pt.ColorVariance.channel / 2. ) + pt.ColorVariance.channel * ( RandomGenerator::global()() % 100 ) / 100. ) ) )
    Color = glm::vec4( COLOR( x ), COLOR( y ), COLOR( z ), COLOR( w ) );
#define INIT( member ) \
    member = ( pt.member - pt.member##Variance / 2. ) + ( pt.member##Variance * ( RandomGenerator::global()() % 100 ) / 100. )
    float AbsAcceleration;
    INIT( AbsAcceleration );
    INIT( RotationAcceleration );
#define ROLL_DIR( dir, opa, opb, val ) \
    if( pt.dir == ParticleTemplate::opa ) \
    { \
        val = - std::abs( val ); \
    } \
    else if( pt.dir == ParticleTemplate::opb ) \
    { \
        val = std::abs( val ); \
    } \
    else if( RandomGenerator::global()() % 2 == 1 ) \
    { \
        val = - val; \
    }
    ROLL_DIR( RotAccelerationDir, Rot_N, Rot_P, RotationAcceleration );
    INIT( RotationSpeed );
    ROLL_DIR( RotDir, Rot_N, Rot_P, RotationSpeed );
    float AbsSpeed;
    INIT( AbsSpeed );
    ROLL_DIR( SpeedDir, Towards, Away, AbsSpeed );
    ROLL_DIR( AccelerationDir, Towards, Away, AbsAcceleration );
    static const double pi = boost::math::constants::pi<double>();
    float dir = pi * 2 * ( RandomGenerator::global()() % 100 / 100. );
    Pos = pos + pt.PosVariance * ( ( RandomGenerator::global()() % 100 ) / 100.f ) * glm::vec2( cos( dir ), sin( dir ) );
    if( Pos != pos )
    {
        Speed = AbsSpeed * glm::normalize( Pos - pos );
        Acceleration = AbsAcceleration * glm::normalize( Pos - pos );
    }
    else
    {
        Speed = Acceleration = glm::vec2( 0, 0 );
    }
    Pos = glm::vec2( Pos.x, Pos.y );
    if( pt.Heading == ParticleTemplate::H_Actor )
    {
        Heading = ori;
    }
    else
    {
        Heading = pi * 2. * ( RandomGenerator::global()() % 101 ) / 100.;
    }
    INIT( Lifetime );
    InitialLifetime = Lifetime;
    INIT( Radius );
#undef INIT
#undef COLOR
#undef ROLL_DIR
}
typedef std::vector<Particle> Particles;
typedef std::vector<glm::vec2> Vec2s_t;
typedef std::vector<glm::vec4> Vec4s_t;
typedef std::vector<GLfloat> Floats_t;
bool getNextTextId( Particles::const_iterator& i, Particles::const_iterator e,
                    Vec2s_t& Positions, Vec4s_t& Colors, Vec4s_t& TexCoords,
                    Floats_t& Headings, Floats_t& Radii, Floats_t& Lifetimes,
                    render::RenderBatch& batch )
{
    // skip dead particles
    while( i != e && i->Lifetime < 0.0f )
    {
        ++i;
    }
    if( i == e )
    {
        return false;
    }
    Particle const& p = *i;
    Positions.push_back( p.Pos );
    SpritePhase const& Phase = p.Template->Spr->operator()( 100 * p.Lifetime / p.InitialLifetime );
    TexCoords.push_back( glm::vec4( Phase.Left, Phase.Right, Phase.Bottom, Phase.Top ) );
    Colors.push_back( p.Color );
    Headings.push_back( p.Heading );
    Radii.push_back( p.Radius );
    float lifetimeDivisor = p.InitialLifetime <= std::numeric_limits<float>::epsilon() ? 1.0f : p.InitialLifetime;
    Lifetimes.push_back( p.InitialLifetime >= 1.0f ? std::min( 1.0f, p.Lifetime ) : ( p.Lifetime / lifetimeDivisor ) );
    batch.TexId = Phase.TexId;
    ++i;
    return true;
}

}
struct ParticleEngineImpl
{
    typedef std::map<int32_t, Particles> ParticlesByLayer;
    ParticlesByLayer mParticlesByLayer;
    float mCycle;
    VaoBase mVAO;
    mutable size_t mPrevParticlesSize;
    ParticleEngineImpl();
    void Update( float dt );
    void Draw() const;
    void Draw( Particles const& particles ) const;
    void AddParticle( int32_t type, glm::vec2 const& pos, glm::vec2 const& distance, double ori );
};

ParticleEngineImpl::ParticleEngineImpl()
    : mCycle( 0.0f )
    , mPrevParticlesSize( 0 )
{
    mVAO.Init();
    ShaderManager& ShaderMgr( ShaderManager::Get() );
    static int32_t def( AutoId( "particle" ) );
    ShaderMgr.ActivateShader( def );
    ShaderMgr.UploadData( "spriteTexture", GLuint( 3 ) );
}

void ParticleEngineImpl::Update( float dt )
{
    mCycle += dt;
    for( ParticlesByLayer::iterator li = mParticlesByLayer.begin(), le = mParticlesByLayer.end(); li != le; ++li )
    {
        Particles& particles = li->second;
        for( Particles::iterator i = particles.begin(), e = particles.end(); i != e; ++i )
        {
            Particle& p = *i;
            p.Lifetime -= dt;
            if( p.Lifetime < 0 )
            {
                continue;
            }
            p.Pos += dt * p.Speed;
            p.Speed += dt * p.Acceleration;
            p.Heading += dt * p.RotationSpeed;
            p.RotationSpeed += dt * p.RotationAcceleration;
            p.Radius = boost::algorithm::clamp(
                p.Radius + dt * p.Template->ScaleSpeed,
                p.Template->MinRadius,
                p.Template->MaxRadius);

        }
        if( mCycle >= 10.f )
        {
            mCycle = 0.0f;
            particles.erase( std::remove_if( particles.begin(), particles.end(), [](Particle const& part){ return part.Lifetime < 0.; }), particles.end() );
        }
    }
}

/*
//---shader spec
layout(location=0) in vec4 TexCoord;
layout(location=1) in vec2 SpriteCenter;
layout(location=2) in float Heading;
layout(location=3) in vec4 Color;
layout(location=4) in float Radius;
layout(location=5) in float Lifetime;
*/
void ParticleEngineImpl::Draw() const
{
    glDepthMask( GL_FALSE );

    for( ParticlesByLayer::const_iterator li = mParticlesByLayer.begin(), le = mParticlesByLayer.end(); li != le; ++li )
    {
        static ParticleLayerRepo& plr = ParticleLayerRepo::Get();
        ParticleLayer const& pl = plr( li->first );
        glBlendFunc( pl.sfactor, pl.dfactor );
        Draw( li->second );
    }

    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    glDepthMask( GL_TRUE );
}

void ParticleEngineImpl::Draw( Particles const& particles ) const
{
    size_t CurSize = particles.size();
    if ( CurSize == 0 )
    {
        return;
    }

    Vec2s_t Positions;
    Vec4s_t Colors;
    Vec4s_t TexCoords;
    Floats_t Headings;
    Floats_t Radii;
    Floats_t Lifetimes;
    Positions.reserve( CurSize );
    Colors.reserve( CurSize );
    TexCoords.reserve( CurSize );
    Headings.reserve( CurSize );
    Radii.reserve( CurSize );
    Lifetimes.reserve( CurSize );

    Particles::const_iterator i = particles.begin();
    render::Counts_t const& Counts = render::count(
                                         std::bind( &getNextTextId, std::ref( i ), particles.end(),
                                                 std::ref( Positions ), std::ref( Colors ), std::ref( TexCoords ),
                                                 std::ref( Headings ), std::ref( Radii ), std::ref( Lifetimes ),
                                                 std::placeholders::_1 )
                                     );

    CurSize = Positions.size();
    if( CurSize == 0 )
    {
        return;
    }

    mVAO.Bind();

    if( CurSize != mPrevParticlesSize )
    {
        size_t TotalSize = CurSize * ( 2 * sizeof( glm::vec4 ) + sizeof( glm::vec2 ) + 3 * sizeof( GLfloat ) );
        glBufferData( GL_ARRAY_BUFFER, TotalSize, NULL, GL_DYNAMIC_DRAW );
        mPrevParticlesSize = CurSize;
    }

    size_t CurrentOffset = 0;
    size_t CurrentSize = 0;
    GLuint CurrentAttribIndex = 0;

    CurrentSize = CurSize * sizeof( glm::vec4 );
    glBufferSubData( GL_ARRAY_BUFFER, CurrentOffset, CurrentSize, &TexCoords[0] );
    glEnableVertexAttribArray( CurrentAttribIndex );
    size_t const TexIndex = CurrentOffset;
    ++CurrentAttribIndex;
    CurrentOffset += CurrentSize;

    CurrentSize = CurSize * sizeof( glm::vec2 );
    glBufferSubData( GL_ARRAY_BUFFER, CurrentOffset, CurrentSize, &Positions[0] );
    glEnableVertexAttribArray( CurrentAttribIndex );
    size_t const PosIndex = CurrentOffset;
    ++CurrentAttribIndex;
    CurrentOffset += CurrentSize;

    CurrentSize = CurSize * sizeof( GLfloat );
    glBufferSubData( GL_ARRAY_BUFFER, CurrentOffset, CurrentSize, &Headings[0] );
    glEnableVertexAttribArray( CurrentAttribIndex );
    size_t const HeadingIndex = CurrentOffset;
    ++CurrentAttribIndex;
    CurrentOffset += CurrentSize;

    CurrentSize = CurSize * sizeof( glm::vec4 );
    glBufferSubData( GL_ARRAY_BUFFER, CurrentOffset, CurrentSize, &Colors[0] );
    glEnableVertexAttribArray( CurrentAttribIndex );
    size_t const ColorIndex = CurrentOffset;
    ++CurrentAttribIndex;
    CurrentOffset += CurrentSize;

    CurrentSize = CurSize * sizeof( GLfloat );
    glBufferSubData( GL_ARRAY_BUFFER, CurrentOffset, CurrentSize, &Radii[0] );
    glEnableVertexAttribArray( CurrentAttribIndex );
    size_t const RadiusIndex = CurrentOffset;
    ++CurrentAttribIndex;
    CurrentOffset += CurrentSize;

    CurrentSize = CurSize * sizeof( GLfloat );
    glBufferSubData( GL_ARRAY_BUFFER, CurrentOffset, CurrentSize, &Lifetimes[0] );
    glEnableVertexAttribArray( CurrentAttribIndex );
    size_t const LifeIndex = CurrentOffset;
    ++CurrentAttribIndex;
    CurrentOffset += CurrentSize;

    ShaderManager& ShaderMgr( ShaderManager::Get() );
    static int32_t def( AutoId( "particle" ) );
    ShaderMgr.ActivateShader( def );
    glActiveTexture( GL_TEXTURE0 + 3 );
    for( render::Counts_t::const_iterator i = Counts.begin(), e = Counts.end(); i != e; ++i )
    {
        render::CountByTexId const& Part = *i;
        CurrentAttribIndex = 0;
        glVertexAttribPointer( CurrentAttribIndex, 4, GL_FLOAT, GL_FALSE, 0, ( GLvoid* )( TexIndex + sizeof( glm::vec4 )*Part.Start ) );
        glVertexAttribDivisor( CurrentAttribIndex, 1 );
        ++CurrentAttribIndex;
        glVertexAttribPointer( CurrentAttribIndex, 2, GL_FLOAT, GL_FALSE, 0, ( GLvoid* )( PosIndex + sizeof( glm::vec2 )*Part.Start ) );
        glVertexAttribDivisor( CurrentAttribIndex, 1 );
        ++CurrentAttribIndex;
        glVertexAttribPointer( CurrentAttribIndex, 1, GL_FLOAT, GL_FALSE, 0, ( GLvoid* )( HeadingIndex + sizeof( GLfloat )*Part.Start ) );
        glVertexAttribDivisor( CurrentAttribIndex, 1 );
        ++CurrentAttribIndex;
        glVertexAttribPointer( CurrentAttribIndex, 4, GL_FLOAT, GL_FALSE, 0, ( GLvoid* )( ColorIndex + sizeof( glm::vec4 )*Part.Start ) );
        glVertexAttribDivisor( CurrentAttribIndex, 1 );
        ++CurrentAttribIndex;
        glVertexAttribPointer( CurrentAttribIndex, 1, GL_FLOAT, GL_FALSE, 0, ( GLvoid* )( RadiusIndex + sizeof( GLfloat )*Part.Start ) );
        glVertexAttribDivisor( CurrentAttribIndex, 1 );
        ++CurrentAttribIndex;
        glVertexAttribPointer( CurrentAttribIndex, 1, GL_FLOAT, GL_FALSE, 0, ( GLvoid* )( LifeIndex + sizeof( GLfloat )*Part.Start ) );
        glVertexAttribDivisor( CurrentAttribIndex, 1 );
        if( Part.Batch.TexId != -1 )
        {
            glBindTexture( GL_TEXTURE_2D, Part.Batch.TexId );
        }
        glDrawArraysInstanced( GL_TRIANGLE_STRIP, 0, 4, Part.Count );
    }
    mVAO.Unbind();
}

void ParticleEngineImpl::AddParticle( int32_t type, glm::vec2 const& pos, glm::vec2 const& distance, double ori )
{
    static ParticleTemplateRepo& ptr( ParticleTemplateRepo::Get() );
    ParticleTemplate const& pt = ptr( type );
    if( NULL == pt.Spr )
    {
        return;
    }
    Particles& particles = mParticlesByLayer[ pt.ParticleLayer ];
    for( int32_t i = 0, e = std::max<int>( 0, ( pt.Num - pt.NumVariance / 2 + ( pt.NumVariance * ( RandomGenerator::global()() % 100 ) ) / 100 ) ); i != e; ++i )
    {
        Particle p( &pt, pos, ori );
        if( p.Lifetime <= 0.0 )
        {
            continue;
        }
        if (pt.Interpolate)
        {
            p.Pos.x -= distance.x*i / e;
            p.Pos.y -= distance.y*i / e;
        }
        particles.push_back( p );
    }
}

ParticleEngine::ParticleEngine()
    : mImpl( new ParticleEngineImpl() )
{
}

ParticleEngine::~ParticleEngine()
{
}

void ParticleEngine::Update( float dt )
{
    mImpl->Update( dt );
}

void ParticleEngine::Draw() const
{
    mImpl->Draw();
}

void ParticleEngine::AddParticle( int32_t type, glm::vec2 const& pos, glm::vec2 const& distance, double ori )
{
    mImpl->AddParticle( type, pos, distance, ori );
}

} // namespace render


/*

Project: plane detection
Date: 20/09/09
@Author: Wang.
Detail: 1. refinement needed and test needed,
           [Doing on 20/09/09], the performance was just fine, further test needed, to debug.
        2. socket communication
Scenario: obstacle, contaminant detection

*/
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>

#include <pcl/filters/extract_indices.h>
#include <pcl/filters/project_inliers.h>
#include <pcl/filters/filter.h>
#include <pcl/filters/radius_outlier_removal.h>
#include <pcl/filters/statistical_outlier_removal.h>

#include <pcl/ModelCoefficients.h>
#include <pcl/features/normal_3d.h>
#include <pcl/kdtree/kdtree.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>
#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/segmentation/extract_clusters.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <boost/make_shared.hpp>

#include <string>
#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <cstdlib>
#include <sstream>
#include <cstring>

//socket communication related
#define DEST_PORT  8000
#define DEST_IP_ADDRESS "127.0.0.1"

using namespace std;
typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;

//remove singel func
//data acquired: Cloud's ptr for processing.
void remove_single (PointCloud::Ptr cloud)
{
	PointCloud::Ptr cloud_filtered(new PointCloud);
	pcl::RadiusOutlierRemoval<PointT> RS;
	RS.setInputCloud(cloud);
	RS.setRadiusSearch(0.5);
	RS.setMinNeighborsInRadius(4);
	RS.filter(*cloud_filtered);
        *cloud = *cloud_filtered;
}

//another new filter to remove the point faraway from the origin
//added on 7/30 by Wang
//data acquired: pcl::PointCloud<PointNormal> PointCloudN
void remove_distant (PointCloud::Ptr cloud)
{
    PointCloud::Ptr cloud_filtered(new PointCloud);
    pcl::StatisticalOutlierRemoval<PointT> RD;
    RD.setInputCloud(cloud);
    RD.setMeanK(50);
    RD.setStddevMulThresh(1.0);
    RD.filter(*cloud_filtered);
    *cloud = *cloud_filtered;

}

//a func to find out the centre of the cluster
void calcuCloudCentre(PointCloud::Ptr cloud, double *x_cen, double *y_cen, double *z_cen)
{
    double x, y, z = 0;
    int size = cloud->points.size();
    for(size_t i = 0; i < size; i++)
    {
        x = x + cloud->points[i].x;
        y = y + cloud->points[i].y;
        z = z + cloud->points[i].z;
    }
    *x_cen = x / size;
    *y_cen = y / size;
    *z_cen = z / size;
}

//a func to find out the border limitations of the cluster
void calcuCloudBorder(PointCloud::Ptr cloud,
                      double *x_left, double *y_bottom, double *z_close,
                      double *x_right, double *y_up, double *z_far)
{
    double x_l, x_r, y_b, y_u, z_c, z_f = 0;
    int size = cloud->points.size();
    for(size_t i = 0; i < size; i++)
    {
        if(cloud->points[i].x > x_r)  x_r = cloud->points[i].x;
        if(cloud->points[i].y > y_u)  y_u = cloud->points[i].y;
        if(cloud->points[i].z > z_f)  z_f = cloud->points[i].z;
        if(cloud->points[i].x < x_l)  x_l = cloud->points[i].x;
        if(cloud->points[i].y < y_b)  y_b = cloud->points[i].y;
        if(cloud->points[i].z < z_c)  z_c = cloud->points[i].z;
    }
    *x_left = x_l;
    *y_bottom = y_b;
    *z_close = z_c;
    *x_right = x_r;
    *y_up = y_u;
    *z_far = z_f;
}

//a func for the socket communication
void socketCommu(double x_cen, double z_cen, int flag,
                 double x_left, double x_right,
                 double y_bottom, double y_up)
{
    int sock_fd;
    sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock_fd < 0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in addr_serv;
    int len;
    memset(&addr_serv, 0, sizeof(addr_serv));
    addr_serv.sin_family = AF_INET;
    addr_serv.sin_addr.s_addr = inet_addr(DEST_IP_ADDRESS);
    addr_serv.sin_port = htons(DEST_PORT);
    len = sizeof(addr_serv);
    int send_num1;
    char send_coord[100] = {0};

    sprintf(send_coord, "Coordinate: %f, %f, Property: %d", x_cen, z_cen, flag);
    send_num1 = sendto(sock_fd, send_coord, strlen(send_coord), 0, (struct sockaddr *)&addr_serv, len);
    if(send_num1 < 0)
    {
        perror("sendto error: package I");
        exit(1);
    }
    int send_num2;
    char send_size[100] = {0};

    sprintf(send_size, "Length: %f, Height: %f",  x_right - x_left,  y_up - y_bottom);
    send_num2 = sendto(sock_fd, send_size, strlen(send_size), 0, (struct sockaddr *)&addr_serv, len);
    if(send_num2 < 0)
    {
        perror("sendto error: package II");
        exit(1);
    }

    close(sock_fd);
}

int main(int argc, char** argv)
{
    //cloud data in
    PointCloud::Ptr cloud(new PointCloud), cloud_f(new PointCloud);
    if (pcl::io::loadPCDFile<PointT>(argv[1], *cloud) == -1)
    {
        PCL_ERROR("Couldn't read file .pcd\n");
        return(-1);
    }

    //pre process
	remove_distant(cloud);
	remove_single(cloud);

    //segment parameter set
    pcl::SACSegmentation<PointT> segmentation;
    pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
    pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
    PointCloud::Ptr cloud_plane(new PointCloud);
    pcl::PCDWriter writer;
    segmentation.setOptimizeCoefficients(true);
    segmentation.setModelType(pcl::SACMODEL_PLANE);
    segmentation.setMethodType(pcl::SAC_RANSAC);
    segmentation.setMaxIterations(100);
    segmentation.setDistanceThreshold(0.02);

    int i = 0, nr_points = (int)cloud->points.size();
    while (cloud->points.size() > 0.5 * nr_points)
    {
        //Segment the largest planar component from the remaining cloud
        segmentation.setInputCloud(cloud);
        segmentation.segment(*inliers, *coefficients);
        //extract the points
        pcl::ExtractIndices<PointT> extract;
        extract.setInputCloud(cloud);
        extract.setIndices(inliers);
        extract.setNegative(false);
        //save to cloud_plane
        extract.filter(*cloud_plane);
        //std::cout <<cloud_plane->points.size() <<std::endl;
        //Remove the planar inliers, extract the rest
        extract.setNegative(true);
        extract.filter(*cloud_f);
        cloud = cloud_f;
    }

    //the creation of the KD tree
    pcl::search::KdTree<PointT>::Ptr tree(new pcl::search::KdTree<PointT>);
    tree->setInputCloud(cloud);
    std::vector<pcl::PointIndices> cluster_indices;
    pcl ::EuclideanClusterExtraction<PointT> ec;       //mean
    ec.setClusterTolerance(2);                         //2.5m search tolerance
    ec.setMinClusterSize(20);                          //the smallest number for clustering
    ec.setMaxClusterSize(25000);                       //the largest number for clustering
    ec.setSearchMethod(tree);                          //set method
    ec.setInputCloud(cloud);                           //set the cloud
    ec.extract(cluster_indices);                       //go.

    //for visualization and the others
    pcl::visualization::PCLVisualizer viewer("object segmention result");
    int j = 0;
    for (std::vector<pcl::PointIndices>::const_iterator it = cluster_indices.begin(); it != cluster_indices.end(); ++it)
    {

        PointCloud::Ptr cloud_cluster(new PointCloud);
        for (std::vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); pit++)
        {
            cloud_cluster->points.push_back(cloud->points[*pit]);
        }
        cloud_cluster->width = cloud_cluster->points.size();
        cloud_cluster->height = 1;
        cloud_cluster->is_dense = true;
        //std::cout << "PointCloud representing the Cluster: " << cloud_cluster->points.size() << " data points." << std::endl;

        double x_cen, y_cen, z_cen = 0;
        double x_left, y_bottom, z_close, x_right, y_up, z_far = 0;
        double distance = 0;
        int flag = 0;

        calcuCloudCentre(cloud_cluster, &x_cen, &y_cen, &z_cen);
        calcuCloudBorder(cloud_cluster, &x_left, &y_bottom, &z_close, &x_right, &y_up, &z_far);
        distance = hypot(x_cen, z_cen);
        //cout << "#######################################" << endl;
        //cout << x_cen << ", " << y_cen << ", " << z_cen << ", " << endl;
        //cout << "#######################################" << endl;
        //cout << x_left << ", " << y_bottom << ", "<< z_close << ", "
         //    << x_right << ", " << y_up << ", " << z_far << endl;

        if (y_up < 0.25 && distance < 20)
        {
            cout << "Be Advised! Found a possible contaminant object locate in " << x_cen << ", " << z_cen << endl;
            flag = 1;
        }
        else
        {
            cout << "Be Cautious! Found a potential obstacle locate in " << x_cen << ", " << z_cen << endl;
            flag = -1;
        }
        socketCommu(x_cen, z_cen, flag, x_left, x_right, y_bottom, y_up);
        //std::stringstream ss;
        //ss << "cloud_cluster_" << j << ".pcd";
        pcl::visualization::PointCloudColorHandlerRandom<PointT> cloud_in_color_h(cloud);//randomly tint some color
        viewer.addPointCloud(cloud_cluster, cloud_in_color_h, std::to_string(j));
        j++;
    }

    //wait
    while (!viewer.wasStopped())
    {
        viewer.spinOnce(100);

    }

    return (0);
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_PLUS_S_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_PLUS_S_HPP_INCLUDED

#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/detail/nsm.hpp>
#include <boost/simd/function/min.hpp>
#include <boost/simd/function/saturate.hpp>
#include <boost/simd/function/saturated.hpp>
#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <boost/simd/detail/dispatch/hierarchy.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>
#include <boost/simd/detail/dispatch/meta/as_unsigned.hpp>
#include <boost/simd/detail/dispatch/meta/upgrade.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
 BOOST_DISPATCH_OVERLOAD ( plus_
                         , (typename A0)
                         , bd::cpu_
                         , bs::saturated_tag
                         , bd::scalar_< bd::floating_<A0> >
                         , bd::scalar_< bd::floating_<A0> >
                         )
 {
   BOOST_FORCEINLINE A0 operator() (const saturated_tag &,  A0 a0, A0 a1
                                   ) const BOOST_NOEXCEPT
   {
     return plus(a0, a1);
   }
 };
  BOOST_DISPATCH_OVERLOAD ( plus_
                          , (typename A0)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_< bd::int_<A0> >
                          , bd::scalar_< bd::int_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const saturated_tag &,  A0 a0, A0 a1
                                   ) const BOOST_NOEXCEPT
    {
      using sz_t = typename nsm::bool_<sizeof(A0) == 4 || sizeof(A0) == 8>::type;
      return impl(a0, a1, sz_t());
    }

    static BOOST_FORCEINLINE A0 impl( A0 a0, A0 a1
                                    , const tt::false_type &) BOOST_NOEXCEPT
    {
      using utype = bd::upgrade_t<A0>;
      return static_cast<A0>(saturate<A0>(utype(a0)+utype(a1)));
    }

    static BOOST_FORCEINLINE A0 impl( A0  a0, A0  a1
                                    , const tt::true_type &) BOOST_NOEXCEPT
    {
      using utype = bd::as_unsigned_t<A0>;

      utype ux = a0;
      utype uy = a1;
      utype res = ux + uy;
      ux = (ux >> (sizeof(A0)*CHAR_BIT-1)) + Valmax<A0>();
      if(A0((ux ^ uy) | ~(uy ^ res)) >= Zero<A0>()) res = ux;
      return res;
    }
  };
  BOOST_DISPATCH_OVERLOAD ( plus_
                          , (typename A0)
                          , bd::cpu_
                          , bs::saturated_tag
                          , bd::scalar_< bd::uint_<A0> >
                          , bd::scalar_< bd::uint_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const saturated_tag &,  A0 a0, A0 a1
                                    ) const BOOST_NOEXCEPT
    {
      using sz_t = typename nsm::bool_<sizeof(A0) == 4 || sizeof(A0) == 8>::type;
      return impl(a0, a1, sz_t());
    }

    static BOOST_FORCEINLINE A0 impl( A0 a0, A0 a1
                                      , const tt::false_type &) BOOST_NOEXCEPT
    {
      typedef typename bd::upgrade<A0>::type utype;
      return static_cast<A0>(boost::simd::min(utype(boost::simd::Valmax<A0>()), utype(a0+a1)));
    }
    static BOOST_FORCEINLINE A0 impl( A0 a0, A0 a1
                                      , const tt::true_type &) BOOST_NOEXCEPT
    {
      A0 res = a0 + a1;
      res |= -(res < a0);
      return res;
    }
  };
} } }

#endif


// Copyright (c) 2009-2010  INRIA Sophia-Antipolis (France).
// All rights reserved.
//
//This file is part of ESBTL.
//
//ESBTL is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//ESBTL is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with ESBTL.  If not, see <http://www.gnu.org/licenses/>.
//
//
//Additional permission under GNU GPL version 3 section 7
//
//If you modify this Library, or any covered work, by linking or
//combining it with CGAL (or a modified version of that library), the
//licensors of this Library grant you additional permission to convey
//the resulting work. Corresponding Source for a non-source form of
//such a combination shall include the source code for the parts of CGAL
//used as well as that of the covered work.
//
//
//
// Author(s)     :  Sébastien Loriot

#include <iostream>
#include <cstring>
#include <list>
#include <vector>

#include <ESBTL/constants.h>
#include <ESBTL/coarse_grain.h>
#include <ESBTL/coarse_creators.h>
#include <ESBTL/PDB.h>
#include <ESBTL/line_selectors.h>
#include <ESBTL/builder.h>
#include <ESBTL/line_reader.h>
#include <ESBTL/coarse_classifier.h>
#include <ESBTL/selected_atom_iterator.h>
#include <ESBTL/occupancy_handlers.h>
#include <ESBTL/global_functions.h>
#include <ESBTL/CGAL/EPIC_kernel_with_atom.h>

//alternative for selection
#include <boost/iterator/filter_iterator.hpp>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_3.h>

typedef ESBTL::CGAL::EPIC_kernel_with_coarse_atom                       Kernel;
typedef ESBTL::CGAL::System_with_coarse_grain                           My_system;
typedef ESBTL::Accept_none_occupancy_policy<ESBTL::PDB::Line_format<> > Accept_none_occupancy_policy;

typedef CGAL::Delaunay_triangulation_3<Kernel>                       Delaunay;

int main(int argc, char** argv){

  if (argc != 2 ){
    std::cerr << "Please provide a filename"  << std::endl;
    return EXIT_FAILURE;
  }

  ESBTL::PDB_line_selector_two_systems sel;

  std::vector<My_system> systems;
  //indicate the number of system to be created (+1 for solvant manually added)
  unsigned nb_systems=sel.max_nb_systems()+1;

  ESBTL::All_atom_system_builder<My_system> builder(systems,nb_systems);

  ESBTL::Coarse_creator_closest_to_barycenter<My_system::Residue,Kernel::FT> creator;

  if (ESBTL::read_a_pdb_file(argv[1],sel,builder,Accept_none_occupancy_policy())){

    assert(systems.size()==3);
    //generate fake solvant
    std::vector<Kernel::Point_3> solvant;
    solvant.reserve(1000);
    for (double i=0;i<10;++i)
      for (double j=0;j<10;++j)
        for (double k=0;k<10;++k)
          solvant.push_back(Kernel::Point_3(i,j,k));
    insert_coarse_atoms(solvant.begin(),solvant.end(),systems[2],1);

    for (My_system::Models_iterator it_model=systems[0].models_begin();it_model!=systems[0].models_end();++it_model){
      unsigned nb_atom=0;
      unsigned nb_residue=0;
      unsigned nb_coarse_created=0;
      {
        My_system::Model& model=*it_model;

        //create coarse atoms
        for (My_system::Model::Residues_iterator it_res=model.residues_begin();it_res!=model.residues_end();++it_res){
          nb_coarse_created+=it_res->create_coarse_atoms(creator);
          ++nb_residue;
        }
      }

      const My_system::Model& model=*it_model;

      typedef ESBTL::Generic_classifier<ESBTL::Radius_of_coarse_atom<double,My_system::Residue::Coarse_atom> > T_Coarse_atom_classifier;
      T_Coarse_atom_classifier classifier;

      for (My_system::Model::Atoms_const_iterator it_atm=model.atoms_begin();it_atm!=model.atoms_end();++it_atm){
        ++nb_atom;
      }
      std::cout << "nb atoms " << nb_atom << std::endl;
      std::cout << "Nb res "<< nb_residue << std::endl;

      typedef ESBTL::Coarse_atoms_iterators<My_system::Model>::const_iterator Coarse_atoms_iterator;

      unsigned nb_coarse=0;
      for (Coarse_atoms_iterator itc=coarse_atoms_begin(model);itc!=coarse_atoms_end(model);++itc){
        ++nb_coarse;
        Kernel::FT radius=classifier.get_properties(*itc).value();
        assert(radius > 0);
      }
      const My_system::Model& solvant=*systems[2].models_begin();
      unsigned nb_fake_sol=0;
      for (Coarse_atoms_iterator itc=coarse_atoms_begin(solvant);itc!=coarse_atoms_end(solvant);++itc){
        ++nb_fake_sol;
      }
      assert( nb_coarse_created==nb_coarse );
      std::cout << "nb coarse " << nb_coarse << std::endl;
      std::cout << "nb_fake_sol " << nb_fake_sol << std::endl;

      Delaunay dt(coarse_atoms_begin(model),coarse_atoms_end(model));
      dt.insert(coarse_atoms_begin(solvant),coarse_atoms_end(solvant));

      for (Delaunay::Finite_vertices_iterator it=dt.finite_vertices_begin();it!=dt.finite_vertices_end();++it)
        assert(it->point().index()==0);

      std::cout << "Delaunay " << dt.number_of_vertices()<< std::endl;

      write_to_cgo("/tmp/cgo.py",coarse_atoms_begin(model),coarse_atoms_end(model),classifier,ESBTL::Color_of_atom<My_system::Residue::Coarse_atom>());
      std::cout << "CGO file created for first model of system 0 in /tmp/cgo.py\n";
    }
  }
  else
    return EXIT_FAILURE;

  return EXIT_SUCCESS;
}


/**
*   UnionOperation.hpp
*
*   Copyright 2015 The Climate Corporation
*   Copyright 2015 Tony Stone
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*
*   Created by Tony Stone on 6/11/15.
*
*   MODIFIED 2015 BY Tony Stone. Modifications licensed under Apache License, Version 2.0.
*
*/

#ifndef __UnionOperation_HPP_
#define __UnionOperation_HPP_

#include <boost/variant/variant.hpp>
#include <boost/geometry/strategies/strategies.hpp>
#include <boost/geometry/algorithms/union.hpp>

#include "GeometryVariant.hpp"

namespace geofeatures {
    namespace operators {

        class UnionOperation : public  boost::static_visitor<GeometryVariant> {

        public:
            GeometryVariant operator()(const Point * lhs, const Point * rhs) const {
                return singleTypeUnion<MultiPoint>(lhs, rhs);
            }

            GeometryVariant operator()(const Point * lhs, const MultiPoint * rhs) const {
                return multiTypeUnion<MultiPoint>(lhs, rhs);
            }

            GeometryVariant operator()(const MultiPoint * lhs, const Point * rhs) const {
                return multiTypeUnion<MultiPoint>(lhs, rhs);
            }

            GeometryVariant operator()(const MultiPoint * lhs, const MultiPoint * rhs) const {
                return singleTypeUnion<MultiPoint>(lhs, rhs);
            }

            GeometryVariant operator()(const LineString * lhs, const LineString * rhs) const {
                return singleTypeUnion<MultiLineString>(lhs, rhs);
            }

            GeometryVariant operator()(const LineString * lhs, const MultiLineString * rhs) const {
                return multiTypeUnion<MultiLineString>(lhs, rhs);
            }

            GeometryVariant operator()(const MultiLineString * lhs, const LineString * rhs) const {
                return multiTypeUnion<MultiLineString>(lhs, rhs);
            }

            GeometryVariant operator()(const MultiLineString * lhs, const MultiLineString * rhs) const {
                return singleTypeUnion<MultiLineString>(lhs, rhs);
            }

            GeometryVariant operator()(const Polygon * lhs, const Polygon * rhs) const {
                return singleTypeUnion<MultiPolygon>(lhs, rhs);
            }

            GeometryVariant operator()(const Polygon * lhs, const MultiPolygon * rhs) const {
                return multiTypeUnion<MultiPolygon>(lhs, rhs);
            }

            GeometryVariant operator()(const MultiPolygon * lhs, const Polygon * rhs) const {
                return multiTypeUnion<MultiPolygon>(lhs, rhs);
            }

            GeometryVariant operator()(const MultiPolygon * lhs, const MultiPolygon * rhs) const {
                return singleTypeUnion<MultiPolygon>(lhs, rhs);
            }

            GeometryVariant operator()(const Ring * lhs, const Ring * rhs) const {
                std::vector<Ring> tmp;

                boost::geometry::union_(*lhs, *rhs, tmp);

                if (tmp.size() == 1) {
                    return tmp.front();
                }

                GeometryCollection<> output;
                for (auto it = tmp.begin(); it != tmp.end(); ++it) {
                    output.push_back(*it);
                }
                return output;
            }

            //
            // For GeometryCollections the initial implemention of these
            // is a simple combination of the right and left hand sides.
            //
            GeometryVariant operator()( const GeometryCollection<> * lhs, const GeometryCollection<> * rhs) const {
                GeometryCollection<> output(*lhs);

                std::for_each(rhs->begin(), rhs->end(), [&output](const GeometryCollection<>::value_type & item) {
                                  output.push_back(item);
                              });
                return output;
            }

            template <typename T>
            GeometryVariant operator()( const T * lhs, const GeometryCollection<> * rhs) const {
                return this->operator()(rhs, lhs);  // Reverse order and chain to reverse order method
            }

            template <typename T>
            GeometryVariant operator()( const GeometryCollection<> * lhs, const T * rhs) const {
                GeometryCollection<> output(*lhs);

                output.push_back(*rhs);

                return output;
            }

            template <typename T, typename U>
            GeometryVariant operator()( const T * lhs, const U * rhs) const {
                GeometryCollection<> collection;

                collection.push_back(*lhs);
                collection.push_back(*rhs);

                return collection;
            }

        private:
            template <typename OT, typename T>
            GeometryVariant singleTypeUnion( const T * lhs, const T * rhs) const {
                OT output{};

                boost::geometry::union_(*lhs, *rhs, output);

                if (output.size() == 1)
                    return output[0];
                else if (output.size() > 1)
                    return output;

                return T(*lhs);
            }

            template <typename OT, typename T, typename U>
            GeometryVariant multiTypeUnion( const T * lhs, const U * rhs) const {
                OT output{};

                boost::geometry::union_(*lhs, *rhs, output);

                return output;
            }

        };

    }   // namespace operators
}   // namespace geofeatures

#endif //__UnionOperator_HPP_


//==============================================================================
//         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
//==============================================================================
#ifndef BOOST_SIMD_SWAR_FUNCTIONS_SPLIT_MULTIPLIES_HPP_INCLUDED
#define BOOST_SIMD_SWAR_FUNCTIONS_SPLIT_MULTIPLIES_HPP_INCLUDED
#include <boost/simd/include/functor.hpp>
#include <boost/dispatch/include/functor.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    /// @brief Hierarchy tag for split_multiplies function
    struct split_multiplies_ : ext::elementwise_<split_multiplies_>
    {
      typedef ext::elementwise_<split_multiplies_> parent;
      template<class... Args>
      static BOOST_FORCEINLINE BOOST_AUTO_DECLTYPE dispatch(Args&&... args)
      BOOST_AUTO_DECLTYPE_BODY( dispatching_split_multiplies_( ext::adl_helper(), static_cast<Args&&>(args)... ) )
    };
  }
  namespace ext
  {
    template<class Site>
    BOOST_FORCEINLINE generic_dispatcher<tag::split_multiplies_, Site> dispatching_split_multiplies_(adl_helper, boost::dispatch::meta::unknown_<Site>, ...)
    {
      return generic_dispatcher<tag::split_multiplies_, Site>();
    }
    template<class... Args>
    struct impl_split_multiplies_;
  }

  /*!
    @brief SIMD register type-based multiplies and split

    @c split_multiplies multiplies two x-bit SIMD registers and returns two 2x-bit registers
    each having half the cardinal of the original inputs.

    @param a0 LHS of multiplication
    @param a1 RHS of multiplication

    @return A Fusion Sequence containing the result of @c a0 * @c a1
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::split_multiplies_, split_multiplies, 2)
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::split_multiplies_, full_mul, 2)
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::split_multiplies_, wide_mul, 2)

  /*!
    @brief SIMD register type-based multiplies and split

    @c split_multiplies multiplies two x-bit SIMD registers and returns two 2x-bit registers
    each having half the cardinal of the original inputs.

    @param a0 LHS of multiplication
    @param a1 RHS of multiplication
    @param a2 L-Value that will receive the second sub-part of @c a0 * @c a1

    @return The first sub-part of @c a0 * @c a1
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION_TPL( tag::split_multiplies_, split_multiplies
                                            , (A0 const &)(A0 const&)(A1&)
                                            , 2
                                            )

  /*!
    @brief SIMD register type-based multiplies and split

    @c split_multiplies multiplies two x-bit SIMD registers and returns two 2x-bit registers
    each having half the cardinal of the original inputs.

    @param a0 LHS of multiplication
    @param a1 RHS of multiplication
    @param a2 L-Value that will receive the first sub-part of @c a0 * @c a1
    @param a3 L-Value that will receive the second sub-part of @c a0 * @c a1
  **/
  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION_TPL( tag::split_multiplies_, split_multiplies
                                            , (A0 const &)(A0 const&)(A1&)(A1&)
                                            , 2
                                            )
} }

#endif


// -*- C++ -*-
//
// Package:    RecoBTag/SecondaryVertex
// Class:      DeepFlavourJetTagsProducer
//
/**\class DeepFlavourJetTagsProducer DeepFlavourJetTagsProducer.cc RecoBTag/DeepFlavour/plugins/DeepFlavourJetTagsProducer.cc
 *
 * Description: EDProducer that produces collection of ShallowTagInfos
 *
 * Implementation:
 *    A collection of CandIPTagInfo and CandSecondaryVertexTagInfo and a CombinedSVComputer ESHandle is taken as input and a collection of ShallowTagInfos
 *    is produced as output.
 */
//
// Original Author:  Mauro Verzetti (U. Rochester)
//
//

// system include files
#include <memory>

// user include files
#include "FWCore/Framework/interface/Frameworkfwd.h"
#include "FWCore/Framework/interface/stream/EDProducer.h"

#include "FWCore/Framework/interface/Event.h"
#include "FWCore/Framework/interface/MakerMacros.h"

#include "FWCore/ParameterSet/interface/ParameterSet.h"
#include "FWCore/Utilities/interface/StreamID.h"

#include "FWCore/Framework/interface/makeRefToBaseProdFrom.h"
#include "DataFormats/Common/interface/RefToBase.h"
#include "DataFormats/BTauReco/interface/ShallowTagInfo.h"
#include "DataFormats/BTauReco/interface/TaggingVariable.h"
#include "DataFormats/BTauReco/interface/JetTag.h"

//from lwtnn
#include "lwtnn/LightweightNeuralNetwork.hh"
#include "lwtnn/parse_json.hh"

#include <fstream>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <iostream>

#include <boost/algorithm/string.hpp>
using namespace std;
using namespace reco;
//
// class declaration
//

class DeepFlavourJetTagsProducer : public edm::stream::EDProducer<> {
public:
	explicit DeepFlavourJetTagsProducer(const edm::ParameterSet&);
	~DeepFlavourJetTagsProducer();

	static void fillDescriptions(edm::ConfigurationDescriptions& descriptions);

	struct MVAVar {
		std::string name;
		reco::btau::TaggingVariableName id;
		int index;
		double default_value;
	};

private:
	typedef std::vector<reco::ShallowTagInfo> INFOS;
	virtual void beginStream(edm::StreamID) override {}
	virtual void produce(edm::Event&, const edm::EventSetup&) override;
	virtual void endStream() override {}

	// ----------member data ---------------------------
	const edm::EDGetTokenT< INFOS > src_;
	edm::FileInPath nnconfig_;
	bool check_sv_for_defaults_;
	bool mean_padding_;
	lwt::LightweightNeuralNetwork *neural_network_;
	lwt::ValueMap inputs_; //typedef of unordered_map<string, float>
	vector<string> outputs_;
	vector<MVAVar> variables_;
	map<string, string> toadd_;
};

//
// constants, enums and typedefs
//

//
// static data member definitions
//

//
// constructors and destructor
//
DeepFlavourJetTagsProducer::DeepFlavourJetTagsProducer(const edm::ParameterSet& iConfig) :
  src_( consumes< INFOS >(iConfig.getParameter<edm::InputTag>("src")) ),
	nnconfig_(iConfig.getParameter<edm::FileInPath>("NNConfig")),
	check_sv_for_defaults_(iConfig.getParameter<bool>("checkSVForDefaults")),
	mean_padding_(iConfig.getParameter<bool>("meanPadding")),
	neural_network_(NULL),
	inputs_(),
	outputs_(),
	variables_()
{
	//parse json
	ifstream jsonfile(nnconfig_.fullPath());
	auto config = lwt::parse_json(jsonfile);

	//create NN and store the output names for the future
	neural_network_ = new lwt::LightweightNeuralNetwork(config.inputs, config.layers, config.outputs);
	outputs_ = config.outputs;
	set<string> outset(outputs_.begin(), outputs_.end());

	//in case we want to merge some different outputs together
	edm::ParameterSet toadd = iConfig.getParameter<edm::ParameterSet>("toAdd");
	for(auto output : toadd.getParameterNamesForType<string>()) {
		string target = toadd.getParameter<string>(output);
		if(outset.find(output) == outset.end())
			throw cms::Exception("RuntimeError") << "The required output: " << output << " to be added to " << target << " could not be found among the NN outputs" << endl;
		if(outset.find(target) == outset.end())
			throw cms::Exception("RuntimeError") << "The required output: " << target << ", target of addition of " << output << " could not be found among the NN outputs" << endl;
		toadd_[output] = target;
	}

	//produce one output kind per node
	for(auto outnode : config.outputs)	{
		if(toadd_.find(outnode) == toadd_.end()){ //produce output only if does not get added
			produces<JetTagCollection>(outnode);
		}
	}

	//get the set-up for the inputs
	for(auto& input : config.inputs) {
		MVAVar var;
		var.name = input.name;
		//two paradigms
		vector<string> tokens;
		if (var.name != "Jet_JP" && var.name != "Jet_JBP" && var.name != "Jet_SoftMu" && var.name != "Jet_SoftEl"){boost::split(tokens,var.name,boost::is_any_of("_"));}
		else {tokens.push_back(var.name);}
		if(!tokens.size()) {
			throw cms::Exception("RuntimeError") << "I could not parse properly " << input.name << " as input feature" << std::endl;
		}
		var.id = reco::getTaggingVariableName(tokens.at(0));
		//die grafully if the tagging variable is not found!
		if(var.id == reco::btau::lastTaggingVariable) {
			throw cms::Exception("ValueError") << "I could not find the TaggingVariable named " << tokens.at(0)
																				 << " from the NN input variable: " << input.name
																				 << ". Please check the spelling" <<  std::endl;
		}
		var.index = (tokens.size() == 2) ? stoi(tokens.at(1)) : -1;
		var.default_value = (mean_padding_) ? 0. : -1*input.offset; //set default to -offset so that when scaling (val+offset)*scale the outcome is 0
		//for mean padding it is set to zero so that undefined values are assigned -mean/scale

		variables_.push_back(var);
	}
}

DeepFlavourJetTagsProducer::~DeepFlavourJetTagsProducer()
{
	delete neural_network_;
}

//
// member functions
//

// ------------ method called to produce the data  ------------
void
DeepFlavourJetTagsProducer::produce(edm::Event& iEvent, const edm::EventSetup& iSetup)
{
	// get input TagInfos
	edm::Handle<INFOS> taginfos;
	iEvent.getByToken(src_, taginfos);

	// create the output collection
	// which is a "map" RefToBase<Jet> --> float
	vector< std::unique_ptr<JetTagCollection> > output_tags;
	output_tags.reserve(outputs_.size());
	for(size_t i=0; i<outputs_.size(); ++i) {
		if(taginfos->size() > 0) {
			edm::RefToBase<Jet> jj = taginfos->begin()->jet();
			output_tags.push_back(
				std::make_unique<JetTagCollection>(
					edm::makeRefToBaseProdFrom(jj, iEvent)
					)
				);
		} else {
			output_tags.push_back(
				std::make_unique<JetTagCollection>()
				);
		}
	}

	// loop over TagInfos
	for(auto& info : *(taginfos)) {
		//convert the taginfo into the value map in the appropriate way
		TaggingVariableList vars = info.taggingVariables();
    //if there are no tracks there's no point in doing it
		bool notracks = (vars.get(reco::btau::jetNSelectedTracks) == 0);
		bool novtx = (vars.get(reco::btau::jetNSecondaryVertices) == 0);
		bool defaulted = (check_sv_for_defaults_) ? (notracks && novtx) : notracks;
		lwt::ValueMap nnout; //returned value

		if(!defaulted) {
			for(auto& var : variables_) {
				if(var.index >= 0){
					std::vector<float> vals = vars.getList(var.id, false);
					inputs_[var.name] = (((int) vals.size()) > var.index) ? vals.at(var.index) : var.default_value;
				}
				//single value tagging var
				else {
					inputs_[var.name] = vars.get(var.id, var.default_value);
				}
			}

			//compute NN output(s)
			nnout = neural_network_->compute(inputs_);

			//merge outputs
			for(auto entry : toadd_) {
				nnout[entry.second] += nnout[entry.first];
			}
		}

		//ket the maps key
		edm::RefToBase<Jet> key = info.jet();

		//dump the NN output(s)
		for(size_t i=0; i<outputs_.size(); ++i) {
			(*output_tags[i])[key] = (defaulted) ? -1 : nnout[outputs_[i]];
		}
	}

	// put the output in the event
	for(size_t i=0; i<outputs_.size(); ++i) {
		if(toadd_.find(outputs_[i]) == toadd_.end()) {
			iEvent.put(std::move(output_tags[i]), outputs_[i]);
		}
	}
}

// ------------ method fills 'descriptions' with the allowed parameters for the module  ------------
void
DeepFlavourJetTagsProducer::fillDescriptions(edm::ConfigurationDescriptions& descriptions) {
  //The following says we do not know what parameters are allowed so do no validation
  // Please change this to state exactly what you do use, even if it is no parameters
  edm::ParameterSetDescription desc;
  desc.setUnknown();
  descriptions.addDefault(desc);
}

//define this as a plug-in
DEFINE_FWK_MODULE(DeepFlavourJetTagsProducer);


// Author: Armin Töpfer

#include "AlignSettings.h"

#include <unistd.h>

#include <cmath>
#include <iterator>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#include <pbcopper/cli2/internal/BuiltinOptions.h>
#include <boost/algorithm/string.hpp>

#include "AbortException.h"

#include <Pbmm2Version.h>

namespace PacBio {
namespace minimap2 {
namespace {

int64_t SizeStringToIntMG(const std::string& s)
{
    if (isalpha(s[s.size() - 1])) {
        int64_t size = std::stoll(s.substr(0, s.size() - 1));
        switch (s[s.size() - 1]) {
            case 'm':
            case 'M':
                size <<= 20;
                break;
            case 'g':
            case 'G':
                size <<= 30;
                break;
            default:
                std::ostringstream os;
                os << "Unknown size multiplier " << s[s.size() - 1];
                throw AbortException(os.str());
        }
        return size;
    } else {
        return std::stoll(s);
    }
}

}  // namespace

namespace OptionNames {
// clang-format off

const CLI_v2::Option MinPercConcordance{
R"({
    "names" : ["c", "min-concordance-perc"],
    "description" : "Minimum alignment concordance in percent.",
    "type" : "double",
    "default" : 70
})"};

const CLI_v2::Option MinPercIdentity{
R"({
    "names" : ["x", "min-id-perc"],
    "description" : "Minimum sequence identity in percent.",
    "type" : "double",
    "default" : 0
})"};

const CLI_v2::Option MinPercIdentityGapComp{
R"({
    "names" : ["y", "min-gap-comp-id-perc"],
    "description" : "Minimum gap compressed sequence identity in percent.",
    "type" : "double",
    "default" : 0
})"};

const CLI_v2::Option MinAlignmentLength{
R"({
    "names" : ["l", "min-length"],
    "description" : "Minimum mapped read length in basepairs.",
    "type" : "int",
    "default" : 50
})"};

const CLI_v2::Option SampleName{
R"({
    "names" : ["sample"],
    "description" : [
        "Sample name for all read groups. Defaults, in order of precedence:",
        " SM field in input read group, biosample name, well sample name, \"UnnamedSample\"."
    ],
    "type" : "string"
})"};

const CLI_v2::Option AlignModeOpt{
R"({
    "names" : ["preset"],
    "description" : "Set alignment mode. See below for preset parameter details.",
    "type" : "string",
    "choices" : ["SUBREAD", "CCS", "HIFI", "ISOSEQ", "UNROLLED"],
    "default" : "SUBREAD"
})"};

const CLI_v2::Option ChunkSize{
R"({
    "names" : ["chunk-size"],
    "description" : "Process N records per chunk.",
    "type" : "int",
    "default" : 100
})"};

const CLI_v2::Option Kmer{
R"({
    "names" : ["k"],
    "description" : "k-mer size (no larger than 28).",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option MinimizerWindowSize{
R"({
    "names" : ["w"],
    "description" : "Minimizer window size.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option GapOpen1{
R"({
    "names" : ["o", "gap-open-1"],
    "description" : "Gap open penalty 1.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option GapOpen2{
R"({
    "names" : ["O", "gap-open-2"],
    "description" : "Gap open penalty 2.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option GapExtension1{
R"({
    "names" : ["e", "gap-extend-1"],
    "description" : "Gap extension penalty 1.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option GapExtension2{
R"({
    "names" : ["E", "gap-extend-2"],
    "description" : "Gap extension penalty 2.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option MatchScore{
R"({
    "names" : ["A"],
    "description" : "Matching score.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option MismatchPenalty{
R"({
    "names" : ["B"],
    "description" : "Mismatch penalty.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option Zdrop{
R"({
    "names" : ["z"],
    "description" : "Z-drop score.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option ZdropInv{
R"({
    "names" : ["Z"],
    "description" : "Z-drop inversion score.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option Bandwidth{
R"({
    "names" : ["r"],
    "description" : "Bandwidth used in chaining and DP-based alignment.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option MaxIntronLength{
R"({
    "names" : ["G"],
    "description" : "Max intron length (changes -r).",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option NonCanon{
R"({
    "names" : ["C"],
    "description" : "Cost for a non-canonical GT-AG splicing (effective in ISOSEQ preset).",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option NoSpliceFlank{
R"({
    "names" : ["no-splice-flank"],
    "description" : "Do not prefer splice flanks GT-AG (effective in ISOSEQ preset)."
})"};

const CLI_v2::Option MedianFilter{
R"({
    "names" : ["median-filter"],
    "description" : "Pick one read per ZMW of median length."
})"};

const CLI_v2::Option Sort{
R"({
    "names" : ["sort"],
    "description" : "Generate sorted BAM file."
})"};

const CLI_v2::Option SortThreadsTC{
R"({
    "names" : ["sort-threads-perc"],
    "description" : [
        "Percentage of threads used exclusively for sorting (absolute number of ",
        "sort threads is capped at 8)."
    ],
    "type" : "int",
    "default" : 25,
    "hidden" : true
})"};

const CLI_v2::Option SortThreads{
R"({
    "names" : ["J", "sort-threads"],
    "description" : "Number of threads used for sorting; 0 means 25% of -j, maximum 8.",
    "type" : "int",
    "default" : 0
})"};

const CLI_v2::Option SortMemory{
R"({
    "names" : ["m", "sort-memory"],
    "description" : "Memory per thread for sorting.",
    "type" : "string",
    "default" : "768M"
})"};

const CLI_v2::Option SortMemoryTC{
R"({
    "names" : ["sort-memory-tc"],
    "description" : "Memory per thread for sorting.",
    "type" : "string",
    "default" : "4G",
    "hidden" : true
})"};

const CLI_v2::Option DisableHPC{
R"({
    "names" : ["u", "no-kmer-compression"],
    "description" : "Disable homopolymer-compressed k-mer (compression is active for SUBREAD & UNROLLED presets)."
})"};

const CLI_v2::Option ZMW{
R"({
    "names" : ["zmw"],
    "description" : "Process ZMW Reads, subreadset.xml input required (activates UNROLLED preset)."
})"};

const CLI_v2::Option HQRegion{
R"({
    "names" : ["hqregion"],
    "description" : "Process HQ region of each ZMW, subreadset.xml input required (activates UNROLLED preset)."
})"};

const CLI_v2::Option Strip{
R"({
    "names" : ["strip"],
    "description" : "Remove all kinetic and extra QV tags. Output cannot be polished."
})"};

const CLI_v2::Option SplitBySample{
R"({
    "names" : ["split-by-sample"],
    "description" : "One output BAM per sample."
})"};

const CLI_v2::Option Rg{
R"({
    "names" : ["rg"],
    "description" : "Read group header line such as '@RG\\tID:xyz\\tSM:abc'. Only for FASTA/Q inputs.",
    "type" : "string"
})"};

const CLI_v2::Option CreatePbi{
R"({
    "names" : ["pbi"],
    "description" : "Generate PBI for BAM only output.",
    "hidden" : true
})"};

const CLI_v2::Option LongJoinFlankRatio{
R"({
    "names" : ["L", "lj-min-ratio"],
    "description" : "Long join flank ratio.",
    "type" : "float",
    "default" : -1
})"};

const CLI_v2::Option NoBAI{
R"({
    "names" : ["no-bai"],
    "description" : "Omit BAI generation for sorted output.",
    "hidden" : true
})"};

const CLI_v2::Option NoTrimming{
R"({
    "names" : ["no-rmt"],
    "description" : "Disable repeated matches trimming.",
    "hidden" : true
})"};

const CLI_v2::Option OutputUnmapped{
R"({
    "names" : ["unmapped"],
    "description" : "Include unmapped records in output."
})"};

const CLI_v2::Option MaxNumAlns{
R"({
    "names" : ["N", "best-n"],
    "description" : "Output at maximum N alignments for each read, 0 means no maximum.",
    "type" : "int",
    "default" : 0
})"};

const CLI_v2::Option CompressSequenceHomopolymers{
R"({
    "names" : ["collapse-homopolymers"],
    "description" : "Collapse homopolymers in reads and reference."
})"};

const CLI_v2::Option MaxGap{
R"({
    "names" : ["g"],
    "description" : "Stop chain enlongation if there are no minimizers in N bp.",
    "type" : "int",
    "default" : -1
})"};

const CLI_v2::Option BamIndexInput{
R"({
    "names" : ["bam-index"],
    "description" : "Generate index for sorted BAM output.",
    "type" : "string",
    "choices" : ["NONE", "BAI", "CSI"],
    "default" : "BAI"
})"};

const CLI_v2::PositionalArgument Reference {
R"({
    "name" : "ref.fa|xml|mmi",
    "description" : "Reference FASTA, ReferenceSet XML, or Reference Index"
})"};

const CLI_v2::PositionalArgument Input {
R"({
    "name" : "in.bam|xml|fa|fq|gz|fofn",
    "description" : "Input BAM, DataSet XML, FASTA, or FASTQ"
})"};

const CLI_v2::PositionalArgument Output {
R"({
    "name" : "out.aligned.bam|xml",
    "description" : "Output BAM or DataSet XML",
    "required" : false
})"};

// clang-format on
}  // namespace OptionNames

AlignSettings::AlignSettings(const PacBio::CLI_v2::Results& options)
    : CLI(options.InputCommandLine())
    , InputFiles(options.PositionalArguments())
    , MinPercConcordance(options[OptionNames::MinPercConcordance])
    , MinPercIdentity(options[OptionNames::MinPercIdentity])
    , MinPercIdentityGapComp(options[OptionNames::MinPercIdentityGapComp])
    , MinAlignmentLength(options[OptionNames::MinAlignmentLength])
    , SampleName(options[OptionNames::SampleName])
    , ChunkSize(options[OptionNames::ChunkSize])
    , MedianFilter(options[OptionNames::MedianFilter])
    , Sort(options[OptionNames::Sort])
    , ZMW(options[OptionNames::ZMW])
    , HQRegion(options[OptionNames::HQRegion])
    , Strip(options[OptionNames::Strip])
    , SplitBySample(options[OptionNames::SplitBySample])
    , Rg(options[OptionNames::Rg])
    , CreatePbi(options[OptionNames::CreatePbi])
    , OutputUnmapped(options[OptionNames::OutputUnmapped])
    , CompressSequenceHomopolymers(options[OptionNames::CompressSequenceHomopolymers])
{
    MM2Settings::Kmer = options[OptionNames::Kmer];
    MM2Settings::MinimizerWindowSize = options[OptionNames::MinimizerWindowSize];
    MM2Settings::GapOpen1 = options[OptionNames::GapOpen1];
    MM2Settings::GapOpen2 = options[OptionNames::GapOpen2];
    MM2Settings::GapExtension1 = options[OptionNames::GapExtension1];
    MM2Settings::GapExtension2 = options[OptionNames::GapExtension2];
    MM2Settings::MatchScore = options[OptionNames::MatchScore];
    MM2Settings::MismatchPenalty = options[OptionNames::MismatchPenalty];
    MM2Settings::Zdrop = options[OptionNames::Zdrop];
    MM2Settings::ZdropInv = options[OptionNames::ZdropInv];
    MM2Settings::Bandwidth = options[OptionNames::Bandwidth];
    MM2Settings::MaxIntronLength = options[OptionNames::MaxIntronLength];
    MM2Settings::NonCanon = options[OptionNames::NonCanon];
    MM2Settings::NoSpliceFlank = options[OptionNames::NoSpliceFlank];
    MM2Settings::DisableHPC = options[OptionNames::DisableHPC];
    MM2Settings::LongJoinFlankRatio = options[OptionNames::LongJoinFlankRatio];
    MM2Settings::NoTrimming = options[OptionNames::NoTrimming];
    MM2Settings::MaxNumAlns = options[OptionNames::MaxNumAlns];
    MM2Settings::MaxGap = options[OptionNames::MaxGap];

    const bool noBai = options[OptionNames::NoBAI];
    const std::string bamIdx = options[OptionNames::BamIndexInput];

    int numAvailableCores = std::thread::hardware_concurrency();
    const unsigned int rawRequestedNThreads = options[PacBio::CLI_v2::Builtin::NumThreads];
    int requestedNThreads =
        static_cast<int>(rawRequestedNThreads);  // since we're doing subtractions here
    std::string requestedMemory = options[OptionNames::SortMemory];
    int sortThreadPerc = 25;
    SortThreads = options[OptionNames::SortThreads];

    if (sortThreadPerc > 50)
        PBLOG_WARN << "Please allocate less than 50% of threads for sorting. Currently allocated: "
                   << sortThreadPerc << "%!";

    if (requestedNThreads > numAvailableCores) {
        PBLOG_WARN << "Requested more threads for alignment (" << requestedNThreads
                   << ") than system-wide available (" << numAvailableCores << ")";
    }

    int availableThreads = ThreadCount(requestedNThreads);
    if (Sort) {
        if (SortThreads == 0) {
            SortThreads = std::min(
                std::max(static_cast<int>(std::round(availableThreads * sortThreadPerc / 100.0)),
                         1),
                8);
            MM2Settings::NumThreads = std::max(availableThreads - SortThreads, 1);
        } else if (SortThreads != 0) {
            if (requestedNThreads == 0)
                availableThreads = std::max(availableThreads - SortThreads, 1);
            if (availableThreads + SortThreads > numAvailableCores) {
                PBLOG_WARN << "Requested more threads for sorting (" << SortThreads
                           << ") and alignment (" << availableThreads
                           << ") than system-wide available (" << numAvailableCores << ")";
            }
            MM2Settings::NumThreads = availableThreads;
            if (SortThreads > numAvailableCores) {
                PBLOG_WARN << "Requested more threads for sorting (" << SortThreads
                           << ") than system-wide available (" << numAvailableCores << ")!";
                SortThreads = ThreadCount(SortThreads);
            }
            while (MM2Settings::NumThreads + SortThreads > numAvailableCores ||
                   (MM2Settings::NumThreads == 1 && SortThreads == 1)) {
                SortThreads = std::max(SortThreads - 1, 1);
                if (MM2Settings::NumThreads + SortThreads <= numAvailableCores ||
                    (MM2Settings::NumThreads == 1 && SortThreads == 1))
                    break;
                MM2Settings::NumThreads = std::max(MM2Settings::NumThreads - 1, 1);
            }
        }
    } else {
        MM2Settings::NumThreads = availableThreads;
    }
    SortMemory = SizeStringToIntMG(requestedMemory);

    if (!Sort) {
        if (SortThreads != 0)
            PBLOG_WARN
                << "Requested " << SortThreads
                << " threads for sorting, without specifying --sort. Please check your input.";
        const std::string pureMemory = options[OptionNames::SortMemory];
        if (pureMemory != "768M")
            PBLOG_WARN
                << "Requested " << pureMemory
                << " memory for sorting, without specifying --sort. Please check your input.";
    }

    if (Sort) {
        std::string suffix;
        const auto MemoryToHumanReadable = [](int64_t memInBytes, float* roundedMemory,
                                              std::string* suffix) {
            static constexpr int64_t ninek = 1000;
            *roundedMemory = memInBytes;
            if (memInBytes >> 10 < ninek) {
                *roundedMemory /= 1000;
                *suffix = "K";
            } else if (memInBytes >> 20 < ninek) {
                *roundedMemory = (memInBytes >> 10) / 1024.0;
                *suffix = "M";
            } else if (memInBytes >> 30 < ninek) {
                *roundedMemory = (memInBytes >> 20) / 1024.0;
                *suffix = "G";
            }
        };
        int64_t maxMem = SortMemory * SortThreads;
        float maxMemSortFloat;
        std::string maxMemSortSuffix;
        MemoryToHumanReadable(SortMemory * SortThreads, &maxMemSortFloat, &maxMemSortSuffix);
        PBLOG_INFO << "Using " << MM2Settings::NumThreads << " threads for alignments, "
                   << SortThreads << " threads for sorting, and " << maxMemSortFloat
                   << maxMemSortSuffix << " bytes RAM for sorting.";

        BamIdx = BamIndex::_from_string(bamIdx.c_str());

        if (noBai) {
            PBLOG_WARN << "Overriding --bam-index with --no-bai!";
            BamIdx = BamIndex::NONE;
        }
    } else {
        PBLOG_INFO << "Using " << MM2Settings::NumThreads << " threads for alignments.";
    }

    const std::map<std::string, AlignmentMode> alignModeMap{{"SUBREAD", AlignmentMode::SUBREADS},
                                                            {"ISOSEQ", AlignmentMode::ISOSEQ},
                                                            {"CCS", AlignmentMode::CCS},
                                                            {"HIFI", AlignmentMode::CCS},
                                                            {"UNROLLED", AlignmentMode::UNROLLED}};

    const std::string alignModeUsr = options[OptionNames::AlignModeOpt];
    const std::string alingModeUpr = boost::to_upper_copy(alignModeUsr);
    if (alignModeMap.find(alingModeUpr) == alignModeMap.cend()) {
        throw AbortException("Could not find --preset " + alignModeUsr);
    }
    MM2Settings::AlignMode = alignModeMap.at(alingModeUpr);
    int inputFilterCounts = ZMW + MedianFilter + HQRegion;
    if (inputFilterCounts > 1) {
        throw AbortException(
            "Options --zmw, --hqregion and --median-filter are mutually exclusive.");
    }
    if (ZMW || HQRegion) {
        if (ChunkSize != 100)
            PBLOG_WARN << "Cannot change --chunk-size in --zmw/--hqregion mode. Parameters "
                          "--chunk-size is forced to 1.";
        ChunkSize = 1;
        MM2Settings::AlignMode = AlignmentMode::UNROLLED;
    }

    if (!Rg.empty() && !boost::contains(Rg, "ID") && !boost::starts_with(Rg, "@RG\t")) {
        throw AbortException(
            "Invalid @RG line. Missing ID field. Please provide following format: "
            "'@RG\\tID:xyz\\tSM:abc'");
    }
    if (MM2Settings::LongJoinFlankRatio > 1) {
        throw AbortException("Option -L,--lj-min-ratio has to be between a ratio betweem 0 and 1.");
    }

    if (!Sort && noBai) {
        PBLOG_WARN << "Option --no-bai has no effect without option --sort!";
    }

    if (MM2Settings::GapOpen1 < -1 || MM2Settings::GapOpen2 < -1 ||
        MM2Settings::GapExtension1 < -1 || MM2Settings::GapExtension2 < -1) {
        throw AbortException("Gap options have to be strictly positive.");
    }
    if (MM2Settings::Kmer < -1 || MM2Settings::Kmer == 0 || MM2Settings::MinimizerWindowSize < -1 ||
        MM2Settings::MinimizerWindowSize == 0) {
        throw AbortException("Index parameter -k and -w must be positive.");
    }

    if (MM2Settings::MaxNumAlns < 0) {
        throw AbortException("Parameter --best-n, -N must be positive.");
    }

    // Override Sample Name for all Read Groups, disable SplitBySample.
    if (!SampleName.empty() && SplitBySample) {
        PBLOG_WARN << "Options --split-by-sample and --sample are mutually exclusive. Option "
                      "--sample will be applied and --split-by-sample is ignored!";
        SplitBySample = false;
    }
}

int32_t AlignSettings::ThreadCount(int32_t n)
{
    const int32_t m = std::thread::hardware_concurrency();
    if (n <= 0) n = m + n;  // permit n <= 0 to subtract from max threads
    return std::max(1, std::min(m, n));
}

PacBio::CLI_v2::Interface AlignSettings::CreateCLI()
{
    PacBio::CLI_v2::Interface i{"pbmm2 align", "Align PacBio reads to reference sequences",
                                PacBio::Pbmm2FormattedVersion()};

    i.Example("pbmm2 align ref.referenceset.xml movie.subreadset.xml ref.movie.alignmentset.xml");

    // clang-format off
    i.AddPositionalArguments({
        OptionNames::Reference,
        OptionNames::Input,
        OptionNames::Output
    });

    i.AddOptionGroup("Basic Options", {
        OptionNames::ChunkSize,
        OptionNames::NoTrimming,

        // hidden
        OptionNames::SortMemoryTC,
        OptionNames::CreatePbi,
    });

    i.AddOptionGroup("Sorting Options", {
        OptionNames::Sort,
        OptionNames::SortMemory,
        OptionNames::SortThreads,
        OptionNames::SortThreadsTC,
    });

    i.AddOptionGroup("Parameter Set Options", {
        OptionNames::AlignModeOpt,
    });

    i.AddOptionGroup("General Parameter Override Options", {
        OptionNames::Kmer,
        OptionNames::MinimizerWindowSize,
        OptionNames::DisableHPC,
        OptionNames::MatchScore,
        OptionNames::MismatchPenalty,
        OptionNames::Zdrop,
        OptionNames::ZdropInv,
        OptionNames::Bandwidth,
        OptionNames::MaxGap,
    });

    i.AddOptionGroup("Gap Parameter Override Options (a k-long gap costs min{o+k*e,O+k*E})", {
        OptionNames::GapOpen1,
        OptionNames::GapOpen2,
        OptionNames::GapExtension1,
        OptionNames::GapExtension2,
        OptionNames::LongJoinFlankRatio,
    });

    i.AddOptionGroup("IsoSeq Parameter Override Options", {
        OptionNames::MaxIntronLength,
        OptionNames::NonCanon,
        OptionNames::NoSpliceFlank,
    });

    i.AddOptionGroup("Read Group Options", {
        OptionNames::SampleName,
        OptionNames::Rg,
    });

    i.AddOptionGroup("Identity Filter Options (combined with AND)", {
        OptionNames::MinPercConcordance,
        OptionNames::MinPercIdentity,
        OptionNames::MinPercIdentityGapComp,
    });

    i.AddOptionGroup("Output Options", {
        OptionNames::MinAlignmentLength,
        OptionNames::MaxNumAlns,
        OptionNames::Strip,
        OptionNames::SplitBySample,
        OptionNames::OutputUnmapped,
        OptionNames::BamIndexInput,
        OptionNames::NoBAI,
    });

    i.AddOptionGroup("Input Manipulation Options (mutually exclusive)", {
        OptionNames::MedianFilter,
        OptionNames::ZMW,
        OptionNames::HQRegion,
    });

    i.AddOptionGroup("Sequence Manipulation Options", {
        OptionNames::CompressSequenceHomopolymers
    });

    i.HelpFooter(R"(Alignment modes of --preset:
    SUBREAD     : -k 19 -w 10    -o 5 -O 56 -e 4 -E 1 -A 2 -B 5 -z 400 -Z 50  -r 2000   -L 0.5 -g 5000
    CCS or HiFi : -k 19 -w 10 -u -o 5 -O 56 -e 4 -E 1 -A 2 -B 5 -z 400 -Z 50  -r 2000   -L 0.5 -g 5000
    ISOSEQ      : -k 15 -w 5  -u -o 2 -O 32 -e 1 -E 0 -A 1 -B 2 -z 200 -Z 100 -r 200000 -L 0.5 -g 2000 -C 5 -G 200000
    UNROLLED    : -k 15 -w 15    -o 2 -O 32 -e 1 -E 0 -A 1 -B 2 -z 200 -Z 100 -r 2000   -L 0.5 -g 10000
    )");

    // clang-format on
    return i;
}
}  // namespace minimap2
}  // namespace PacBio