diff --git a/FixedOrderGen/include/EventGenerator.hh b/FixedOrderGen/include/EventGenerator.hh
index b89d640..899a72c 100644
--- a/FixedOrderGen/include/EventGenerator.hh
+++ b/FixedOrderGen/include/EventGenerator.hh
@@ -1,67 +1,67 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/optional.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/RNG.hh"
 
 #include "Beam.hh"
 #include "Decay.hh"
 #include "JetParameters.hh"
 #include "Process.hh"
 #include "Status.hh"
 
 namespace HEJ{
   class Event;
   class HiggsCouplingSettings;
   class ScaleGenerator;
   class EWConstants;
 }
 
 //! Namespace for HEJ Fixed Order Generator
 namespace HEJFOG{
   class EventGenerator{
   public:
     EventGenerator(
         Process process,
         Beam beam,
         HEJ::ScaleGenerator scale_gen,
         JetParameters jets,
         int pdf_id,
         double subl_change,
         unsigned int subl_channels,
         ParticlesDecayMap particle_decays,
         HEJ::HiggsCouplingSettings Higgs_coupling,
         HEJ::EWConstants ew_parameters,
         std::shared_ptr<HEJ::RNG> ran
     );
 
     HEJ::optional<HEJ::Event> gen_event();
 
     Status status() const {
       return status_;
     }
 
   private:
     HEJ::PDF pdf_;
     HEJ::MatrixElement ME_;
     HEJ::ScaleGenerator scale_gen_;
     Process process_;
     JetParameters jets_;
     Beam beam_;
     Status status_;
     double subl_change_;
     unsigned int subl_channels_;
     ParticlesDecayMap particle_decays_;
     HEJ::EWConstants ew_parameters_;
     std::shared_ptr<HEJ::RNG> ran_;
   };
 
 }
diff --git a/FixedOrderGen/include/PhaseSpacePoint.hh b/FixedOrderGen/include/PhaseSpacePoint.hh
index 66642e0..a6eef97 100644
--- a/FixedOrderGen/include/PhaseSpacePoint.hh
+++ b/FixedOrderGen/include/PhaseSpacePoint.hh
@@ -1,233 +1,233 @@
 /** \file      PhaseSpacePoint.hh
  *  \brief     Contains the PhaseSpacePoint Class
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <bitset>
 #include <vector>
 
 #include "HEJ/Event.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/RNG.hh"
 
 #include "JetParameters.hh"
 #include "Decay.hh"
 #include "Status.hh"
 
 namespace HEJ{
   class EWConstants;
 }
 
 namespace HEJFOG{
   class Process;
 
   using HEJ::Particle;
   //! A point in resummation phase space
   class PhaseSpacePoint{
   public:
     //! Default PhaseSpacePoint Constructor
     PhaseSpacePoint() = delete;
 
     //! PhaseSpacePoint Constructor
     /**
      * @param proc                  The process to generate
      * @param jet_properties        Jet defintion & cuts
      * @param pdf                   The pdf set (used for sampling)
      * @param E_beam                Energie of the beam
      * @param subl_chance           Chance to turn a potentially unordered
      *                              emission into an actual one
      * @param subl_channels         Possible subleading channels.
      *                              see HEJFOG::Subleading
      * @param particle_properties   Properties of producted boson
      *
      * Initially, only FKL phase space points are generated. subl_chance gives
      * the change of turning one emissions into a subleading configuration,
      * i.e. either unordered or central quark/anti-quark pair. Unordered
      * emissions require that the most extremal emission in any direction is
      * a quark or anti-quark and the next emission is a gluon. Quark/anti-quark
      * pairs are only generated for W processes. At most one subleading
      * emission will be generated in this way.
      */
     PhaseSpacePoint(
       Process const & proc,
       JetParameters const & jet_properties,
       HEJ::PDF & pdf, double E_beam,
       double subl_chance,
       unsigned int subl_channels,
       ParticlesDecayMap const & particle_decays,
       HEJ::EWConstants const & ew_parameters,
       HEJ::RNG & ran
     );
 
     //! Get Weight Function
     /**
      * @returns        Weight of Event
      */
     double weight() const{
       return weight_;
     }
 
     Status status() const{
       return status_;
     }
 
     //! Get Incoming Function
     /**
      * @returns        Incoming Particles
      */
     std::array<Particle, 2> const & incoming() const{
       return incoming_;
     }
 
     //! Get Outgoing Function
     /**
      * @returns        Outgoing Particles
      */
     std::vector<Particle> const & outgoing() const{
       return outgoing_;
     }
 
     std::unordered_map<size_t, std::vector<Particle>> const & decays() const{
       return decays_;
     }
 
   private:
     friend HEJ::Event::EventData to_EventData(PhaseSpacePoint psp);
     /**
      * @internal
      * @brief Generate LO parton momentum
      *
      * @param count             Number of partons to generate
      * @param is_pure_jets      If true ensures momentum conservation in x and y
      * @param jet_param         Jet properties to fulfil
      * @param max_pt            max allowed pt for a parton (typically E_CMS)
      * @param ran               Random Number Generator
      *
      * @returns                 Momentum of partons
      *
      * Ensures that each parton is in its own jet.
      * Generation is independent of parton flavour. Output is sorted in rapidity.
      */
     std::vector<fastjet::PseudoJet> gen_LO_partons(
         int count, bool is_pure_jets,
         JetParameters const & jet_param,
         double max_pt,
         HEJ::RNG & ran
     );
     Particle gen_boson(
         HEJ::ParticleID bosonid, double mass, double width,
         HEJ::RNG & ran
     );
     template<class ParticleMomenta>
     fastjet::PseudoJet gen_last_momentum(
         ParticleMomenta const & other_momenta,
         double mass_square, double y
     ) const;
 
     bool jets_ok(
         std::vector<fastjet::PseudoJet> const & Born_jets,
         std::vector<fastjet::PseudoJet> const & partons
     ) const;
     /**
      * @internal
      * @brief Generate incoming partons according to the PDF
      *
      * @param uf                Scale used in the PDF
      */
     void reconstruct_incoming(
         Process const & proc, unsigned int subl_channels,
         HEJ::PDF & pdf, double E_beam,
         double uf,
         HEJ::RNG & ran
     );
     /**
      * @internal
      * @brief Returns list of all allowed initial states partons
      */
     std::array<std::bitset<11>,2> filter_partons(
         Process const & proc, unsigned int const subl_channels,
         HEJ::RNG & ran
     );
     HEJ::ParticleID generate_incoming_id(
         size_t beam_idx, double x, double uf, HEJ::PDF & pdf,
         std::bitset<11> allowed_partons, HEJ::RNG & ran
     );
 
     bool momentum_conserved(double ep) const;
 
     HEJ::Particle const & most_backward_FKL(
         std::vector<HEJ::Particle> const & partons
     ) const;
     HEJ::Particle const & most_forward_FKL(
         std::vector<HEJ::Particle> const & partons
     ) const;
     HEJ::Particle & most_backward_FKL(std::vector<HEJ::Particle> & partons) const;
     HEJ::Particle & most_forward_FKL(std::vector<HEJ::Particle> & partons) const;
     bool extremal_FKL_ok(
         std::vector<fastjet::PseudoJet> const & partons
     ) const;
     double random_normal(double stddev, HEJ::RNG & ran);
     /**
      * @internal
      * @brief Turns a FKL configuration into a subleading one
      *
      * @param chance            Change to switch to subleading configuration
      * @param channels          Allowed channels for subleading process
      * @param proc              Process to decide which subleading
      *                          configurations are allowed
      *
      * With a chance of "chance" the FKL configuration is either turned into
      * a unordered configuration or, for A/W/Z bosons, a configuration with
      * a central quark/anti-quark pair.
      */
     void maybe_turn_to_subl(double chance, unsigned int channels,
         Process const & proc, HEJ::RNG & ran);
     void turn_to_uno(bool can_be_uno_backward, bool can_be_uno_forward, HEJ::RNG & ran);
     void turn_to_qqx(bool allow_strange, HEJ::RNG & ran);
     //! decay where we select the decay channel
     std::vector<Particle> decay_boson(
         HEJ::Particle const & parent,
         std::vector<Decay> const & decays,
         HEJ::RNG & ran
     );
     //! generate decay products of a boson
     std::vector<Particle> decay_boson(
         HEJ::Particle const & parent,
         std::vector<HEJ::ParticleID> const & decays,
         HEJ::RNG & ran
     );
     /// @brief setup outgoing partons to ensure correct coupling to boson
     void couple_boson(HEJ::ParticleID boson, HEJ::RNG & ran);
     Decay select_decay_channel(
         std::vector<Decay> const & decays,
         HEJ::RNG & ran
     );
     double gen_hard_pt(
         int np, double ptmin, double ptmax, double y,
         HEJ::RNG & ran
     );
     double gen_soft_pt(int np, double ptmax, HEJ::RNG & ran);
     double gen_parton_pt(
         int count, JetParameters const & jet_param, double ptmax, double y,
         HEJ::RNG & ran
     );
 
     double weight_;
 
     Status status_;
 
     std::array<Particle, 2> incoming_;
     std::vector<Particle> outgoing_;
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays_;
   };
 
   //! Extract HEJ::Event::EventData from PhaseSpacePoint
   HEJ::Event::EventData to_EventData(PhaseSpacePoint psp);
 }
diff --git a/FixedOrderGen/include/config.hh b/FixedOrderGen/include/config.hh
index 7a55cb7..df5b36b 100644
--- a/FixedOrderGen/include/config.hh
+++ b/FixedOrderGen/include/config.hh
@@ -1,46 +1,46 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include "yaml-cpp/yaml.h"
 
 #include "HEJ/HiggsCouplingSettings.hh"
 #include "HEJ/optional.hh"
 #include "HEJ/Config.hh"
 #include "HEJ/output_formats.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/Fraction.hh"
 
 #include "Beam.hh"
 #include "Decay.hh"
 #include "JetParameters.hh"
 #include "Process.hh"
 #include "UnweightSettings.hh"
 
 namespace HEJFOG{
 
   struct Config{
     Process process;
     size_t events;
     JetParameters jets;
     Beam beam;
     int pdf_id;
     HEJ::Fraction<double> subleading_fraction;
     unsigned int subleading_channels; //! < see HEJFOG::Subleading
     ParticlesDecayMap particle_decays;
     std::vector<YAML::Node> analyses_parameters;
     HEJ::ScaleConfig scales;
     std::vector<HEJ::OutputFile> output;
     HEJ::RNGConfig rng;
     HEJ::HiggsCouplingSettings Higgs_coupling;
     HEJ::EWConstants ew_parameters;
     HEJ::optional<UnweightSettings> unweight;
   };
 
   Config load_config(std::string const & config_file);
 
 }
diff --git a/FixedOrderGen/src/EventGenerator.cc b/FixedOrderGen/src/EventGenerator.cc
index 58a5d62..852d462 100644
--- a/FixedOrderGen/src/EventGenerator.cc
+++ b/FixedOrderGen/src/EventGenerator.cc
@@ -1,95 +1,95 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "EventGenerator.hh"
 
 #include <utility>
 
 #include "Process.hh"
 #include "Beam.hh"
 #include "JetParameters.hh"
 #include "PhaseSpacePoint.hh"
 
 #include "HEJ/Config.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/EWConstants.hh"
 
 namespace HEJFOG{
   EventGenerator::EventGenerator(
       Process process,
       Beam beam,
       HEJ::ScaleGenerator scale_gen,
       JetParameters jets,
       int pdf_id,
       double subl_change,
       unsigned int subl_channels,
       ParticlesDecayMap particle_decays,
       HEJ::HiggsCouplingSettings Higgs_coupling,
       HEJ::EWConstants ew_parameters,
       std::shared_ptr<HEJ::RNG> ran
   ):
     pdf_{pdf_id, beam.particles[0], beam.particles[1]},
     ME_{
       [this](double mu){ return pdf_.Halphas(mu); },
       HEJ::MatrixElementConfig{
         false,
         std::move(Higgs_coupling),
         ew_parameters
       }
     },
     scale_gen_{std::move(scale_gen)},
     process_{std::move(process)},
     jets_{std::move(jets)},
     beam_{std::move(beam)},
     subl_change_{subl_change},
     subl_channels_{subl_channels},
     particle_decays_{std::move(particle_decays)},
     ew_parameters_{ew_parameters},
     ran_{std::move(ran)}
   {
   }
 
   HEJ::optional<HEJ::Event> EventGenerator::gen_event(){
     HEJFOG::PhaseSpacePoint psp{
       process_,
       jets_,
       pdf_, beam_.energy,
       subl_change_, subl_channels_,
       particle_decays_,
       ew_parameters_,
       *ran_
     };
     status_ = psp.status();
     if(status_ != good) return {};
 
     HEJ::Event ev = scale_gen_(
         HEJ::Event{
           to_EventData( std::move(psp) ).cluster( jets_.def, jets_.min_pt)
         }
     );
     if(!is_resummable(ev.type()))
       throw HEJ::not_implemented("Tried to generate a event type, "
         "which is not yet implemented in HEJ.");
 
     ev.generate_colours(*ran_);
 
     const double shat = HEJ::shat(ev);
     const double xa = (ev.incoming()[0].E()-ev.incoming()[0].pz())/(2.*beam_.energy);
     const double xb = (ev.incoming()[1].E()+ev.incoming()[1].pz())/(2.*beam_.energy);
 
     // evaluate matrix element
     ev.parameters() *= ME_.tree(ev)/(shat*shat);
     // and PDFs
     ev.central().weight *= pdf_.pdfpt(0,xa,ev.central().muf, ev.incoming()[0].type);
     ev.central().weight *= pdf_.pdfpt(0,xb,ev.central().muf, ev.incoming()[1].type);
     for(size_t i = 0; i < ev.variations().size(); ++i){
       auto & var = ev.variations(i);
       var.weight *= pdf_.pdfpt(0,xa,var.muf, ev.incoming()[0].type);
       var.weight *= pdf_.pdfpt(0,xb,var.muf, ev.incoming()[1].type);
     }
     return ev;
   }
 
 }
diff --git a/FixedOrderGen/src/PhaseSpacePoint.cc b/FixedOrderGen/src/PhaseSpacePoint.cc
index 64ce55a..bd85e39 100644
--- a/FixedOrderGen/src/PhaseSpacePoint.cc
+++ b/FixedOrderGen/src/PhaseSpacePoint.cc
@@ -1,695 +1,695 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "PhaseSpacePoint.hh"
 
 #include <algorithm>
 
 #include "CLHEP/Vector/LorentzVector.h"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/kinematics.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/utility.hh"
 
 #include "Process.hh"
 #include "Subleading.hh"
 
 using namespace HEJ;
 
 namespace HEJFOG{
 
   namespace {
     static_assert(
         std::numeric_limits<double>::has_quiet_NaN,
         "no quiet NaN for double"
     );
     constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
   } // namespace anonymous
 
   HEJ::Event::EventData to_EventData(PhaseSpacePoint psp){
   //! @TODO Same function already in HEJ
     HEJ::Event::EventData result;
     result.incoming = std::move(psp).incoming_;
     assert(result.incoming.size() == 2);
     result.outgoing = std::move(psp).outgoing_;
     // technically Event::EventData doesn't have to be sorted,
     // but PhaseSpacePoint should be anyway
     assert(
         std::is_sorted(
             begin(result.outgoing), end(result.outgoing),
             HEJ::rapidity_less{}
         )
     );
     assert(result.outgoing.size() >= 2);
     result.decays = std::move(psp).decays_;
     result.parameters.central = {NaN, NaN, psp.weight()};
     return result;
   }
 
   namespace{
     bool can_swap_to_uno(
         HEJ::Particle const & p1, HEJ::Particle const & p2
     ){
       return is_parton(p1)
         && p1.type != pid::gluon
         && p2.type == pid::gluon;
     }
 
     size_t count_gluons(std::vector<Particle>::const_iterator first,
       std::vector<Particle>::const_iterator last){
       return std::count_if(first, last, [](Particle const & p)
         {return p.type == pid::gluon;});
     }
 
     /** assumes FKL configurations between first and last,
      * else there can be a quark in a non-extreme position
      * e.g. uno configuration gqg would pass
      */
     bool can_change_to_qqx(
       std::vector<Particle>::const_iterator first,
       std::vector<Particle>::const_iterator last){
       return 1 < count_gluons(first,last);
     }
     bool is_AWZ_proccess(Process const & proc){
       return proc.boson && is_AWZ_boson(*proc.boson);
     }
 
     bool is_up_type(Particle const & part){
       return HEJ::is_anyquark(part) && !(abs(part.type)%2);
     }
     bool is_down_type(Particle const & part){
       return HEJ::is_anyquark(part) && abs(part.type)%2;
     }
     bool can_couple_to_W(Particle const & part, pid::ParticleID const W_id){
       const int W_charge = W_id>0?1:-1;
       return abs(part.type)<5
         && ( (W_charge*part.type > 0 && is_up_type(part))
           || (W_charge*part.type < 0 && is_down_type(part)) );
     }
   }
 
   void PhaseSpacePoint::maybe_turn_to_subl(
       double chance,
       unsigned int const channels,
       Process const & proc,
       HEJ::RNG & ran
   ){
     if(proc.njets <= 2) return;
     assert(outgoing_.size() >= 2);
 
     // decide what kind of subleading process is allowed
     bool allow_uno = false;
     bool allow_strange = true;
     const size_t nout = outgoing_.size();
     const bool can_be_uno_backward = (channels&Subleading::uno)
         && can_swap_to_uno(outgoing_[0], outgoing_[1]);
     const bool can_be_uno_forward = (channels&Subleading::uno)
         && can_swap_to_uno(outgoing_[nout-1], outgoing_[nout-2]);
     allow_uno = can_be_uno_backward || can_be_uno_forward;
 
     bool allow_qqx = (channels&Subleading::qqx)
         && can_change_to_qqx(outgoing_.cbegin(), outgoing_.cend());
     if(is_AWZ_proccess(proc)) {
       if(std::none_of(outgoing_.cbegin(), outgoing_.cend(),
           [&proc](Particle const & p){ return can_couple_to_W(p, *proc.boson);})) {
         // enforce qqx if A/W/Z can't couple somewhere else
         assert(allow_qqx);
         allow_uno = false;
         chance = 1.;
         // strange not allowed for W
         if(abs(*proc.boson)== pid::Wp) allow_strange = false;
       }
     }
 
     if(!allow_uno && !allow_qqx) return;
     if(ran.flat() < chance){
       weight_ /= chance;
       if(allow_uno && !allow_qqx){
           turn_to_uno(can_be_uno_backward, can_be_uno_forward, ran);
       } else if (!allow_uno && allow_qqx) {
         turn_to_qqx(allow_strange, ran);
       } else {
         assert( allow_uno && allow_qqx);
         if(ran.flat() < 0.5) turn_to_uno(can_be_uno_backward, can_be_uno_forward, ran);
         else turn_to_qqx(allow_strange, ran);
         weight_ *= 2.;
       }
     } else weight_ /= 1 - chance;
 
   }
 
   void PhaseSpacePoint::turn_to_uno(
       const bool can_be_uno_backward, const bool can_be_uno_forward,
       HEJ::RNG & ran
   ){
     if(!can_be_uno_backward && !can_be_uno_forward) return;
     const size_t nout = outgoing_.size();
     if(can_be_uno_backward && can_be_uno_forward){
       if(ran.flat() < 0.5){
         std::swap(outgoing_[0].type, outgoing_[1].type);
       } else {
         std::swap(outgoing_[nout-1].type, outgoing_[nout-2].type);
       }
       weight_ *= 2.;
     } else if(can_be_uno_backward){
       std::swap(outgoing_[0].type, outgoing_[1].type);
     } else {
       assert(can_be_uno_forward);
       std::swap(outgoing_[nout-1].type, outgoing_[nout-2].type);
     }
   }
 
   void PhaseSpacePoint::turn_to_qqx(const bool allow_strange, HEJ::RNG & ran){
     /// find first and last gluon in FKL chain
     auto first = std::find_if(outgoing_.begin(), outgoing_.end(),
       [](Particle const & p){return p.type == pid::gluon;});
     std::vector<Particle*> FKL_gluons;
     for(auto p = first; p!=outgoing_.end(); ++p){
       if(p->type == pid::gluon) FKL_gluons.push_back(&*p);
       else if(is_anyquark(*p)) break;
     }
     const size_t ng = FKL_gluons.size();
     if(ng < 2)
       throw std::logic_error("not enough gluons to create qqx");
     // select flavour of quark
     const double r1 = 2.*ran.flat()-1.;
     const double max_flavour = allow_strange?n_f:n_f-1;
     weight_ *= max_flavour*2;
     int flavour = pid::down + std::floor(std::abs(r1)*max_flavour);
     flavour*=r1<0.?-1:1;
     // select gluon for switch
     const size_t idx = floor((ng-1) * ran.flat());
     weight_ *= (ng-1);
     FKL_gluons[idx]->type = ParticleID(flavour);
     FKL_gluons[idx+1]->type = ParticleID(-flavour);
   }
 
   template<class ParticleMomenta>
   fastjet::PseudoJet PhaseSpacePoint::gen_last_momentum(
       ParticleMomenta const & other_momenta,
       const double mass_square, const double y
   ) const {
     std::array<double,2> pt{0.,0.};
     for (auto const & p: other_momenta) {
       pt[0]-= p.px();
       pt[1]-= p.py();
     }
 
     const double mperp = sqrt(pt[0]*pt[0]+pt[1]*pt[1]+mass_square);
     const double pz=mperp*sinh(y);
     const double E=mperp*cosh(y);
 
     return {pt[0], pt[1], pz, E};
   }
 
   namespace {
     //! adds a particle to target (in correct rapidity ordering)
     //! @returns    positon of insertion
     auto insert_particle(std::vector<HEJ::Particle> & target,
       HEJ::Particle && particle
     ){
       const auto pos = std::upper_bound(
           begin(target),end(target),particle,rapidity_less{}
       );
       target.insert(pos, std::move(particle));
       return pos;
     }
   }
 
   PhaseSpacePoint::PhaseSpacePoint(
     Process const & proc,
     JetParameters const & jet_param,
     HEJ::PDF & pdf, double E_beam,
     double const subl_chance,
     unsigned int const subl_channels,
     ParticlesDecayMap const & particle_decays,
     HEJ::EWConstants const & ew_parameters,
     HEJ::RNG & ran
   )
   {
     assert(proc.njets >= 2);
     status_ = good;
     weight_ = 1;
 
     const int nout = proc.njets + (proc.boson?1:0) + proc.boson_decay.size();
     outgoing_.reserve(nout);
 
     // generate parton momenta
     const bool is_pure_jets = (nout == proc.njets);
     auto partons = gen_LO_partons(
         proc.njets, is_pure_jets, jet_param, E_beam, ran
     );
     // pre fill flavour with gluons
     for(auto&& p_out: partons) {
       outgoing_.emplace_back(Particle{pid::gluon, std::move(p_out), {}});
     }
     if(status_ != good) return;
 
     if(proc.boson){ // decay boson
       const auto & boson_prop = ew_parameters.prop(*proc.boson) ;
       auto boson{ gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran) };
       const auto pos{insert_particle(outgoing_, std::move(boson))};
       const size_t boson_idx = std::distance(begin(outgoing_), pos);
       const auto & boson_decay = particle_decays.find(*proc.boson);
       if( !proc.boson_decay.empty() ){ // decay given in proc
         decays_.emplace(
             boson_idx,
             decay_boson(outgoing_[boson_idx], proc.boson_decay, ran)
         );
       } else if( boson_decay != particle_decays.end()
                 && !boson_decay->second.empty() ){ // decay given explicitly
         decays_.emplace(
             boson_idx,
             decay_boson(outgoing_[boson_idx], boson_decay->second, ran)
         );
       }
     }
     // normalisation of momentum-conserving delta function
     weight_ *= pow(2*M_PI, 4);
 
     /** @TODO
      *  uf (jet_param.min_pt) doesn't correspond to our final scale choice.
      *  The HEJ scale generators currently expect a full event as input,
      *  so fixing this is not completely trivial
      */
     reconstruct_incoming(proc, subl_channels, pdf, E_beam, jet_param.min_pt, ran);
     if(status_ != good) return;
     // set outgoing states
     most_backward_FKL(outgoing_).type = incoming_[0].type;
     most_forward_FKL(outgoing_).type = incoming_[1].type;
 
     maybe_turn_to_subl(subl_chance, subl_channels, proc, ran);
 
     if(proc.boson) couple_boson(*proc.boson, ran);
   }
 
   double PhaseSpacePoint::gen_hard_pt(
       int np , double ptmin, double ptmax, double y,
       HEJ::RNG & ran
   ) {
     // heuristic parameters for pt sampling
     const double ptpar = ptmin + np/5.;
     const double arg_small_y = atan((ptmax - ptmin)/ptpar);
     const double y_cut = 3.;
 
     const double r1 = ran.flat();
     if(y < y_cut){
       const double pt = ptmin + ptpar*tan(r1*arg_small_y);
       const double temp = cos(r1*arg_small_y);
       weight_ *= pt*ptpar*arg_small_y/(temp*temp);
       return pt;
     }
 
     const double ptpar2 = ptpar/(1 + 5*(y-y_cut));
     const double temp = 1. - std::exp((ptmin-ptmax)/ptpar2);
     const double pt = ptmin - ptpar2*std::log(1-r1*temp);
     weight_ *= pt*ptpar2*temp/(1-r1*temp);
     return pt;
   }
 
   double PhaseSpacePoint::gen_soft_pt(int np, double max_pt, HEJ::RNG & ran) {
     constexpr double ptpar = 4.;
 
     const double r = ran.flat();
     const double pt = max_pt + ptpar/np*std::log(r);
     weight_ *= pt*ptpar/(np*r);
     return pt;
   }
 
   double PhaseSpacePoint::gen_parton_pt(
       int count, JetParameters const & jet_param, double max_pt, double y,
       HEJ::RNG & ran
   ) {
     constexpr double p_small_pt = 0.02;
 
     if(! jet_param.peak_pt) {
        return gen_hard_pt(count, jet_param.min_pt, max_pt, y, ran);
     }
     const double r = ran.flat();
     if(r > p_small_pt) {
       weight_ /= 1. - p_small_pt;
       return gen_hard_pt(count, *jet_param.peak_pt, max_pt, y, ran);
     }
     weight_ /= p_small_pt;
     const double pt =  gen_soft_pt(count, *jet_param.peak_pt, ran);
     if(pt < jet_param.min_pt) {
       weight_=0.0;
       status_ = not_enough_jets;
       return jet_param.min_pt;
     }
     return pt;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_LO_partons(
       int np, bool is_pure_jets,
       JetParameters const & jet_param,
       double max_pt,
       HEJ::RNG & ran
   ){
     if (np<2) throw std::invalid_argument{"Not enough partons in gen_LO_partons"};
 
     weight_ /= pow(16.*pow(M_PI,3),np);
     weight_ /= std::tgamma(np+1); //remove rapidity ordering
     std::vector<fastjet::PseudoJet> partons;
     partons.reserve(np);
     for(int i = 0; i < np; ++i){
       const double y = -jet_param.max_y + 2*jet_param.max_y*ran.flat();
       weight_ *= 2*jet_param.max_y;
 
       const bool is_last_parton = i+1 == np;
       if(is_pure_jets && is_last_parton) {
         constexpr double parton_mass_sq = 0.;
         partons.emplace_back(gen_last_momentum(partons, parton_mass_sq, y));
         break;
       }
 
       const double phi = 2*M_PI*ran.flat();
       weight_ *= 2.0*M_PI;
 
       const double pt = gen_parton_pt(np, jet_param, max_pt, y, ran);
       if(weight_ == 0.0) return {};
 
       partons.emplace_back(fastjet::PtYPhiM(pt, y, phi));
       assert(jet_param.min_pt <= partons[i].pt());
       assert(partons[i].pt() <= max_pt+1e-5);
     }
     // Need to check that at LO, the number of jets = number of partons;
     fastjet::ClusterSequence cs(partons, jet_param.def);
     auto cluster_jets=cs.inclusive_jets(jet_param.min_pt);
     if (cluster_jets.size()!=unsigned(np)){
       weight_=0.0;
       status_ = not_enough_jets;
       return {};
     }
 
     std::sort(begin(partons), end(partons), rapidity_less{});
 
     return partons;
   }
 
   Particle PhaseSpacePoint::gen_boson(
       HEJ::ParticleID bosonid, double mass, double width,
       HEJ::RNG & ran
   ){
     // Usual phase space measure
     weight_ /= 16.*pow(M_PI, 3);
 
     // Generate a y Gaussian distributed around 0
     /// @TODO check magic numbers for different boson Higgs
     /// @TODO better sampling for W
     const double stddev_y = 1.6;
     const double y = random_normal(stddev_y, ran);
     const double r1 = ran.flat();
     const double s_boson = mass*(
         mass + width*tan(M_PI/2.*r1 + (r1-1.)*atan(mass/width))
     );
     // off-shell s_boson sampling, compensates for Breit-Wigner
     /// @TODO use a flag instead
     if(abs(bosonid) == pid::Wp){
       weight_/=M_PI*M_PI*8.;
       weight_*= mass*width*( M_PI+2.*atan(mass/width) )
               / ( 1. + cos( M_PI*r1 + 2.*(r1-1.)*atan(mass/width) ) );
     }
 
     auto p = gen_last_momentum(outgoing_, s_boson, y);
 
     return Particle{bosonid, std::move(p), {}};
   }
 
   Particle const & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> const & partons
   ) const{
     if(!HEJ::is_parton(partons[0])) return partons[1];
     return partons[0];
   }
 
   Particle const & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> const & partons
   ) const{
     const size_t last_idx = partons.size() - 1;
     if(!HEJ::is_parton(partons[last_idx])) return partons[last_idx-1];
     return partons[last_idx];
   }
 
   Particle & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> & partons
   ) const{
     if(!HEJ::is_parton(partons[0])) return partons[1];
     return partons[0];
   }
 
   Particle & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> & partons
   ) const{
     const size_t last_idx = partons.size() - 1;
     if(!HEJ::is_parton(partons[last_idx])) return partons[last_idx-1];
     return partons[last_idx];
   }
 
   namespace {
     /// partons are ordered: even = anti, 0 = gluon
     ParticleID index_to_pid(size_t i){
       if(!i) return pid::gluon;
       return static_cast<ParticleID>( i%2 ? (i+1)/2 : -i/2 );
     }
 
     /// partons are ordered: even = anti, 0 = gluon
     size_t pid_to_index(ParticleID id){
       if(id==pid::gluon) return 0;
       return id>0 ? id*2-1 : abs(id)*2;
     }
 
     std::bitset<11> init_allowed(ParticleID const id){
       if(abs(id) == pid::proton)
         return ~0;
       std::bitset<11> out{0};
       if(is_parton(id))
         out[pid_to_index(id)] = 1;
       return out;
     }
 
     /// decides which "index" (see index_to_pid) are allowed for process
     std::bitset<11> allowed_quarks(ParticleID const boson){
       std::bitset<11> allowed = ~0;
       if(abs(boson) == pid::Wp){
         // special case W:
         // Wp: anti-down or up-type quark, no b/t
         // Wm: down or anti-up-type quark, no b/t
         allowed = boson>0? 0b00011001101
                           :0b00100110011;
       }
       return allowed;
     }
   }
 
   /**
    * checks which partons are allowed as initial state:
    * 1. only allow what is given in the Runcard (p -> all)
    * 2. A/W/Z require something to couple to
    *  a) no qqx => no incoming gluon
    *  b) 2j     => no incoming gluon
    *  c) 3j     => can couple OR is gluon => 2 gluons become qqx later
    */
   std::array<std::bitset<11>,2> PhaseSpacePoint::filter_partons(
       Process const & proc, unsigned int const subl_channels, HEJ::RNG & ran
   ){
     std::array<std::bitset<11>,2> allowed_partons{
         init_allowed(proc.incoming[0]),
         init_allowed(proc.incoming[1])
       };
     bool const allow_qqx = subl_channels&Subleading::qqx;
     // special case A/W/Z
     if(is_AWZ_proccess(proc) && ((proc.njets < 4) || !allow_qqx)){
       // all possible incoming states
       auto allowed(allowed_quarks(*proc.boson));
       if(proc.njets == 2 || !allow_qqx) allowed[0]=0;
 
       // possible states per leg
       std::array<std::bitset<11>,2> const maybe_partons{
         allowed_partons[0]&allowed, allowed_partons[1]&allowed};
 
       if(maybe_partons[0].any() && maybe_partons[1].any()){
         // two options to get allowed initial state => choose one at random
         const size_t idx = ran.flat() < 0.5;
         allowed_partons[idx] = maybe_partons[idx];
         // else choose the possible
       } else if(maybe_partons[0].any()) {
         allowed_partons[0] = maybe_partons[0];
       } else if(maybe_partons[1].any()) {
         allowed_partons[1] = maybe_partons[1];
       } else{
         throw std::invalid_argument{"Incoming state not allowed."};
       }
     }
     return allowed_partons;
   }
 
   void PhaseSpacePoint::reconstruct_incoming(
       Process const & proc, unsigned int const subl_channels,
       HEJ::PDF & pdf, double E_beam,
       double uf,
       HEJ::RNG & ran
   ){
     std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_);
     // calculate xa, xb
     const double sqrts=2*E_beam;
     const double xa=(incoming_[0].E()-incoming_[0].pz())/sqrts;
     const double xb=(incoming_[1].E()+incoming_[1].pz())/sqrts;
     // abort if phase space point is outside of collider energy reach
     if (xa>1. || xb>1.){
       weight_=0;
       status_ = too_much_energy;
       return;
     }
     auto const & ids = proc.incoming;
     std::array<std::bitset<11>,2> allowed_partons(
       filter_partons(proc, subl_channels, ran));
     for(size_t i = 0; i < 2; ++i){
       if(ids[i] == pid::proton || ids[i] == pid::p_bar){
       // pick ids according to pdfs
         incoming_[i].type =
           generate_incoming_id(i, i?xb:xa, uf, pdf, allowed_partons[i], ran);
       } else {
         assert(allowed_partons[i][pid_to_index(ids[i])]);
         incoming_[i].type = ids[i];
       }
     }
     assert(momentum_conserved(1e-7));
   }
 
   HEJ::ParticleID PhaseSpacePoint::generate_incoming_id(
       size_t const beam_idx, double const x, double const uf,
       HEJ::PDF & pdf, std::bitset<11> allowed_partons, HEJ::RNG & ran
   ){
     std::array<double,11> pdf_wt;
     pdf_wt[0] = allowed_partons[0]?fabs(pdf.pdfpt(beam_idx,x,uf,pid::gluon)):0.;
     double pdftot = pdf_wt[0];
     for(size_t i = 1; i < pdf_wt.size(); ++i){
       pdf_wt[i] = allowed_partons[i]?4./9.*fabs(pdf.pdfpt(beam_idx,x,uf,index_to_pid(i))):0;
       pdftot += pdf_wt[i];
     }
     const double r1 = pdftot * ran.flat();
     double sum = 0;
     for(size_t i=0; i < pdf_wt.size(); ++i){
       if (r1 < (sum+=pdf_wt[i])){
         weight_*= pdftot/pdf_wt[i];
         return index_to_pid(i);
       }
     }
     std::cerr << "Error in choosing incoming parton: "<<x<<" "<<uf<<" "
       <<sum<<" "<<pdftot<<" "<<r1<<std::endl;
     throw std::logic_error{"Failed to choose parton flavour"};
   }
 
   void PhaseSpacePoint::couple_boson(
     HEJ::ParticleID const boson, HEJ::RNG & ran
   ){
     if(abs(boson) != pid::Wp) return; // only matters for W
     /// @TODO this could be use to sanity check gamma and Z
 
     // find all possible quarks
     std::vector<Particle*> allowed_parts;
     for(auto & part: outgoing_){
       // Wp -> up OR anti-down, Wm -> anti-up OR down, no bottom
       if ( can_couple_to_W(part, boson) )
         allowed_parts.push_back(&part);
     }
     if(allowed_parts.size() == 0){
       throw std::logic_error{"Found no parton for coupling with boson"};
     }
 
     // select one and flip it
     size_t idx = 0;
     if(allowed_parts.size() > 1){
       /// @TODO more efficient sampling
       ///       old code: probability[i] = exp(parton[i].y - W.y)
       idx = floor(ran.flat()*allowed_parts.size());
       weight_ *= allowed_parts.size();
     }
     const int W_charge = boson>0?1:-1;
     allowed_parts[idx]->type =
       static_cast<ParticleID>( allowed_parts[idx]->type - W_charge );
   }
 
   double PhaseSpacePoint::random_normal(
       double stddev,
       HEJ::RNG & ran
   ){
     const double r1 = ran.flat();
     const double r2 = ran.flat();
     const double lninvr1 = -log(r1);
     const double result = stddev*sqrt(2.*lninvr1)*cos(2.*M_PI*r2);
     weight_ *= exp(result*result/(2*stddev*stddev))*sqrt(2.*M_PI)*stddev;
     return result;
   }
 
   bool PhaseSpacePoint::momentum_conserved(double ep) const{
     fastjet::PseudoJet diff;
     for(auto const & in: incoming()) diff += in.p;
     for(auto const & out: outgoing()) diff -= out.p;
     return nearby_ep(diff, fastjet::PseudoJet{}, ep);
   }
 
   Decay PhaseSpacePoint::select_decay_channel(
       std::vector<Decay> const & decays,
       HEJ::RNG & ran
   ){
     double br_total = 0.;
     for(auto const & decay: decays) br_total += decay.branching_ratio;
     // adjust weight
     // this is given by (channel branching ratio)/(chance to pick channel)
     // where (chance to pick channel) =
     //       (channel branching ratio)/(total branching ratio)
     weight_ *= br_total;
     if(decays.size()==1) return decays.front();
     const double r1 = br_total*ran.flat();
     double br_sum = 0.;
     for(auto const & decay: decays){
       br_sum += decay.branching_ratio;
       if(r1 < br_sum) return decay;
     }
     throw std::logic_error{"unreachable"};
   }
 
   std::vector<Particle> PhaseSpacePoint::decay_boson(
       HEJ::Particle const & parent,
       std::vector<Decay> const & decays,
       HEJ::RNG & ran
   ){
     const auto channel = select_decay_channel(decays, ran);
     return decay_boson(parent, channel.products, ran);
   }
 
   std::vector<Particle> PhaseSpacePoint::decay_boson(
       HEJ::Particle const & parent,
       std::vector<HEJ::ParticleID> const & decays,
       HEJ::RNG & ran
   ){
     if(decays.size() != 2){
       throw HEJ::not_implemented{
         "only decays into two particles are implemented"
       };
     }
     std::vector<Particle> decay_products(decays.size());
     for(size_t i = 0; i < decays.size(); ++i){
       decay_products[i].type = decays[i];
     }
     // choose polar and azimuth angle in parent rest frame
     const double E = parent.m()/2;
     const double theta = 2.*M_PI*ran.flat();
     const double cos_phi = 2.*ran.flat()-1.;
     const double sin_phi = sqrt(1. - cos_phi*cos_phi);  // Know 0 < phi < pi
     const double px = E*cos(theta)*sin_phi;
     const double py = E*sin(theta)*sin_phi;
     const double pz = E*cos_phi;
     decay_products[0].p.reset(px, py, pz, E);
     decay_products[1].p.reset(-px, -py, -pz, E);
     for(auto & particle: decay_products) particle.p.boost(parent.p);
     return decay_products;
   }
 }
diff --git a/FixedOrderGen/src/config.cc b/FixedOrderGen/src/config.cc
index 326bb73..0f51c49 100644
--- a/FixedOrderGen/src/config.cc
+++ b/FixedOrderGen/src/config.cc
@@ -1,432 +1,432 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "config.hh"
 
 #include <cctype>
 
 #include "Subleading.hh"
 
 #include "HEJ/Config.hh"
 #include "HEJ/YAMLreader.hh"
 
 namespace HEJFOG{
   using HEJ::set_from_yaml;
   using HEJ::set_from_yaml_if_defined;
   using HEJ::pid::ParticleID;
 
   namespace{
     //! Get YAML tree of supported options
     /**
      * The configuration file is checked against this tree of options
      * in assert_all_options_known.
      */
     YAML::Node const & get_supported_options(){
       const static YAML::Node supported = [](){
         YAML::Node supported;
         static const auto opts = {
           "process", "events", "subleading fraction","subleading channels",
           "scales", "scale factors", "max scale ratio", "pdf", "vev",
           "event output", "analyses", "analysis", "import scales"
         };
         // add subnodes to "supported" - the assigned value is irrelevant
         for(auto && opt: opts) supported[opt] = "";
         for(auto && jet_opt: {"min pt", "peak pt", "algorithm", "R", "max rapidity"}){
           supported["jets"][jet_opt] = "";
         }
         for(auto && particle_type: {"Higgs", "W", "Z"}){
           for(auto && particle_opt: {"mass", "width"}){
             supported["particle properties"][particle_type][particle_opt] = "";
           }
         }
         for(auto && particle_type: {"Higgs", "Wp", "W+", "Wm", "W-", "Z"}){
           for(auto && particle_opt: {"into", "branching ratio"}){
             supported["decays"][particle_type][particle_opt] = "";
           }
         }
         for(auto && opt: {"mt", "use impact factors", "include bottom", "mb"}){
           supported["Higgs coupling"][opt] = "";
         }
         for(auto && beam_opt: {"energy", "particles"}){
           supported["beam"][beam_opt] = "";
         }
         for(auto && unweight_opt: {"sample size", "max deviation"}){
           supported["unweight"][unweight_opt] = "";
         }
         for(auto && opt: {"name", "seed"}){
           supported["random generator"][opt] = "";
         }
         return supported;
       }();
       return supported;
     }
 
     JetParameters get_jet_parameters(
         YAML::Node const & node, std::string const & entry
     ){
       const auto p = HEJ::get_jet_parameters(node, entry);
       JetParameters result;
       result.def = p.def;
       result.min_pt = p.min_pt;
       set_from_yaml(result.max_y, node, entry, "max rapidity");
       set_from_yaml_if_defined(result.peak_pt, node, entry, "peak pt");
       if(result.peak_pt && *result.peak_pt <= result.min_pt)
         throw std::invalid_argument{
           "Value of option 'peak pt' has to be larger than 'min pt'."
         };
       return result;
     }
 
     Beam get_Beam(
         YAML::Node const & node, std::string const & entry
     ){
       Beam beam;
       std::vector<HEJ::ParticleID> particles;
       set_from_yaml(beam.energy, node, entry, "energy");
       set_from_yaml_if_defined(particles, node, entry, "particles");
       if(! particles.empty()){
         for(HEJ::ParticleID particle: particles){
           if(particle != HEJ::pid::p && particle != HEJ::pid::p_bar){
             throw std::invalid_argument{
               "Unsupported value in option " + entry + ": particles:"
               " only proton ('p') and antiproton ('p_bar') beams are supported"
             };
           }
         }
         if(particles.size() != 2){
           throw std::invalid_argument{"Not exactly two beam particles"};
         }
         beam.particles.front() = particles.front();
         beam.particles.back() = particles.back();
       }
       return beam;
     }
 
     std::vector<std::string> split(
         std::string const & str, std::string const & delims
     ){
       std::vector<std::string> result;
       for(size_t begin, end = 0; end != str.npos;){
         begin = str.find_first_not_of(delims, end);
         if(begin == str.npos) break;
         end = str.find_first_of(delims, begin + 1);
         result.emplace_back(str.substr(begin, end - begin));
       }
       return result;
     }
 
     std::invalid_argument invalid_incoming(std::string const & what){
       return std::invalid_argument{
         "Incoming particle type " + what + " not supported,"
         " incoming particles have to be 'p', 'p_bar' or partons"
       };
     }
 
     std::invalid_argument invalid_outgoing(std::string const & what){
       return std::invalid_argument{
         "Outgoing particle type " + what + " not supported,"
         " outgoing particles have to be 'j', 'photon', 'H', 'Wm', 'Wp', 'e-', 'e+', 'nu_e', 'nu_e_bar'"
       };
     }
 
     HEJ::ParticleID reconstruct_boson_id(
       std::vector<HEJ::ParticleID> const & ids
     ){
       assert(ids.size()==2);
       const int pidsum = ids[0] + ids[1];
       if(pidsum == +1) {
         assert(HEJ::is_antilepton(ids[0]));
         if(HEJ::is_antineutrino(ids[0])) {
           throw HEJ::not_implemented{"lepton-flavour violating final state"};
         }
         assert(HEJ::is_neutrino(ids[1]));
         // charged antilepton + neutrino means we had a W+
         return HEJ::pid::Wp;
       }
       if(pidsum == -1) {
         assert(HEJ::is_antilepton(ids[0]));
         if(HEJ::is_neutrino(ids[1])) {
           throw HEJ::not_implemented{"lepton-flavour violating final state"};
         }
         assert(HEJ::is_antineutrino(ids[0]));
         // charged lepton + antineutrino means we had a W-
         return HEJ::pid::Wm;
       }
       throw HEJ::not_implemented{
         "final state with leptons "+HEJ::name(ids[0])+" and "+HEJ::name(ids[1])
           +" not supported"
       };
     }
 
     Process get_process(
         YAML::Node const & node, std::string const & entry
     ){
       Process result;
 
       std::string process_string;
       set_from_yaml(process_string, node, entry);
       assert(! process_string.empty());
       const auto particles = split(process_string, " \n\t\v=>");
       if(particles.size() < 3){
         throw std::invalid_argument{
           "Bad format in option process: '" + process_string
           + "', expected format is 'in1 in2 => out1 ...'"
         };
       }
       result.incoming.front() = HEJ::to_ParticleID(particles[0]);
       result.incoming.back() = HEJ::to_ParticleID(particles[1]);
 
       for(size_t i = 0; i < result.incoming.size(); ++i){
         const HEJ::ParticleID in = result.incoming[i];
         if(
             in != HEJ::pid::proton && in != HEJ::pid::p_bar
             && !HEJ::is_parton(in)
         ){
           throw invalid_incoming(particles[i]);
         }
       }
       result.njets = 0;
       for(size_t i = result.incoming.size(); i < particles.size(); ++i){
         assert(! particles[i].empty());
         if(particles[i] == "j") ++result.njets;
         else if(std::isdigit(particles[i].front())
                 && particles[i].back() == 'j')
           result.njets += std::stoi(particles[i]);
         else{
           const auto pid = HEJ::to_ParticleID(particles[i]);
           if(pid==HEJ::pid::Higgs || pid==HEJ::pid::Wp || pid==HEJ::pid::Wm){
             if(result.boson)
               throw std::invalid_argument{
                 "More than one outgoing boson is not supported"
                   };
             if(!result.boson_decay.empty())
               throw std::invalid_argument{
                 "Production of a boson together with a lepton is not supported"
               };
             result.boson = pid;
           } else if (HEJ::is_anylepton(pid)){
             // Do not accept more leptons, if two leptons are already mentioned
             if( result.boson_decay.size()>=2 )
               throw std::invalid_argument{"Too many leptons provided"};
             if(result.boson)
               throw std::invalid_argument{
                 "Production of a lepton together with a boson is not supported"
               };
             result.boson_decay.emplace_back(pid);
           } else {
             throw invalid_outgoing(particles[i]);
           }
         }
       }
       if(result.njets < 2){
         throw std::invalid_argument{
           "Process has to include at least two jets ('j')"
         };
       }
       if(!result.boson_decay.empty()){
         std::sort(begin(result.boson_decay),end(result.boson_decay));
         assert(!result.boson);
         result.boson = reconstruct_boson_id(result.boson_decay);
       }
       return result;
     }
 
     HEJFOG::Subleading to_subleading_channel(YAML::Node const & yaml){
       std::string name;
       using namespace HEJFOG::channels;
       set_from_yaml(name, yaml);
       if(name == "none")
         return none;
       if(name == "all")
         return all;
       if(name == "unordered" || name == "uno")
         return uno;
       if(name == "qqx")
         return qqx;
       throw HEJ::unknown_option("Unknown subleading channel '"+name+"'");
     }
 
     unsigned int get_subleading_channels(YAML::Node const & node){
       using YAML::NodeType;
       using namespace HEJFOG::channels;
       // all channels allowed by default
       if(!node) return all;
       switch(node.Type()){
       case NodeType::Undefined:
         return all;
       case NodeType::Null:
         return none;
       case NodeType::Scalar:
         return to_subleading_channel(node);
       case NodeType::Map:
         throw HEJ::invalid_type{"map is not a valid option for subleading channels"};
       case NodeType::Sequence:
         unsigned int channels = HEJFOG::Subleading::none;
         for(auto && channel_node: node){
           channels |= get_subleading_channels(channel_node);
         }
         return channels;
       }
       throw std::logic_error{"unreachable"};
     }
 
     Decay get_decay(YAML::Node const & node,
       std::string const & entry, std::string const & boson
     ){
       Decay decay;
       set_from_yaml(decay.products, node, entry, boson, "into");
       decay.branching_ratio=1;
       set_from_yaml_if_defined(decay.branching_ratio, node, entry, boson,
         "branching ratio");
       return decay;
     }
 
     std::vector<Decay> get_decays(YAML::Node const & node,
       std::string const & entry, std::string const & boson
     ){
       using YAML::NodeType;
       if(!node[entry][boson]) return {};
       switch(node[entry][boson].Type()){
       case NodeType::Null:
       case NodeType::Undefined:
         return {};
       case NodeType::Scalar:
         throw HEJ::invalid_type{"value is not a list of decays"};
       case NodeType::Map:
         return {get_decay(node, entry, boson)};
       case NodeType::Sequence:
         std::vector<Decay> result;
         for(auto && decay_str: node[entry][boson]){
           result.emplace_back(get_decay(decay_str, entry, boson));
         }
         return result;
       }
       throw std::logic_error{"unreachable"};
     }
 
     ParticlesDecayMap get_all_decays(YAML::Node const & node,
                                            std::string const & entry
     ){
       if(!node[entry]) return {};
       if(!node[entry].IsMap())
         throw HEJ::invalid_type{entry + " have to be a map"};
       ParticlesDecayMap result;
       for(auto const & sub_node: node[entry]) {
         const auto boson = sub_node.first.as<std::string>();
         const auto id = HEJ::to_ParticleID(boson);
         result.emplace(id, get_decays(node, entry, boson));
       }
       return result;
     }
 
     HEJ::ParticleProperties get_particle_properties(
         YAML::Node const & node, std::string const & entry,
         std::string const & boson
     ){
       HEJ::ParticleProperties result;
       set_from_yaml(result.mass, node, entry, boson, "mass");
       set_from_yaml(result.width, node, entry, boson, "width");
       return result;
     }
 
     HEJ::EWConstants get_ew_parameters(YAML::Node const & node){
       HEJ::EWConstants result;
       double vev;
       set_from_yaml(vev, node, "vev");
       result.set_vevWZH(vev,
         get_particle_properties(node, "particle properties", "W"),
         get_particle_properties(node, "particle properties", "Z"),
         get_particle_properties(node, "particle properties", "Higgs")
       );
       return result;
     }
 
     UnweightSettings get_unweight(
         YAML::Node const & node, std::string const & entry
     ){
       UnweightSettings result;
       set_from_yaml(result.sample_size, node, entry, "sample size");
       if(result.sample_size <= 0){
         throw std::invalid_argument{
           "negative sample size " + std::to_string(result.sample_size)
         };
       }
       set_from_yaml(result.max_dev, node, entry, "max deviation");
       return result;
     }
 
     Config to_Config(YAML::Node const & yaml){
       try{
         HEJ::assert_all_options_known(yaml, get_supported_options());
       }
       catch(HEJ::unknown_option const & ex){
         throw HEJ::unknown_option{std::string{"Unknown option '"} + ex.what() + "'"};
       }
 
       Config config;
       config.process = get_process(yaml, "process");
       set_from_yaml(config.events, yaml, "events");
       config.jets = get_jet_parameters(yaml, "jets");
       config.beam = get_Beam(yaml, "beam");
       for(size_t i = 0; i < config.process.incoming.size(); ++i){
         const auto & in = config.process.incoming[i];
         using namespace HEJ::pid;
         if( (in == p || in == p_bar) && in != config.beam.particles[i]){
           throw std::invalid_argument{
             "Particle type of beam " + std::to_string(i+1) + " incompatible"
             + " with type of incoming particle " + std::to_string(i+1)
           };
         }
       }
       set_from_yaml(config.pdf_id, yaml, "pdf");
 
       set_from_yaml(config.subleading_fraction, yaml, "subleading fraction");
       if(config.subleading_fraction == 0)
         config.subleading_channels = Subleading::none;
       else
         config.subleading_channels = get_subleading_channels(yaml["subleading channels"]);
 
       config.ew_parameters = get_ew_parameters(yaml);
       config.particle_decays = get_all_decays(yaml, "decays");
       if(config.process.boson // check that Ws always decay
           && std::abs(*config.process.boson) == HEJ::ParticleID::Wp
           && config.process.boson_decay.empty()
       ){
         auto const & decay = config.particle_decays.find(*config.process.boson);
         if(decay == config.particle_decays.end() || decay->second.empty())
           throw std::invalid_argument{
             "Decay for "+HEJ::name(*config.process.boson)+" is required"};
       }
       set_from_yaml_if_defined(config.analyses_parameters, yaml, "analyses");
       if(yaml["analysis"]){
         std::cerr <<
           "WARNING: Configuration entry 'analysis' is deprecated. "
           " Use 'analyses' instead.\n";
         YAML::Node ana;
         set_from_yaml(ana, yaml, "analysis");
         if(!ana.IsNull()){
           config.analyses_parameters.push_back(ana);
         }
       }
       config.scales = HEJ::to_ScaleConfig(yaml);
       set_from_yaml_if_defined(config.output, yaml, "event output");
       config.rng = HEJ::to_RNGConfig(yaml, "random generator");
       config.Higgs_coupling = HEJ::get_Higgs_coupling(yaml, "Higgs coupling");
       if(yaml["unweight"]) config.unweight = get_unweight(yaml, "unweight");
       return config;
     }
 
   } // namespace anonymous
 
   Config load_config(std::string const & config_file){
     try{
       return to_Config(YAML::LoadFile(config_file));
     }
     catch(...){
       std::cerr << "Error reading " << config_file << ":\n  ";
       throw;
     }
   }
 }
diff --git a/FixedOrderGen/src/main.cc b/FixedOrderGen/src/main.cc
index aab69a3..4595f0c 100644
--- a/FixedOrderGen/src/main.cc
+++ b/FixedOrderGen/src/main.cc
@@ -1,277 +1,277 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <algorithm>
 #include <chrono>
 #include <fstream>
 #include <iostream>
 #include <map>
 #include <memory>
 #include <cstdint>
 
 #include "LHEF/LHEF.h"
 
 #include "yaml-cpp/yaml.h"
 
 #include <boost/iterator/filter_iterator.hpp>
 
 #include "HEJ/CombinedEventWriter.hh"
 #include "HEJ/CrossSectionAccumulator.hh"
 #include "HEJ/get_analysis.hh"
 #include "HEJ/LesHouchesWriter.hh"
 #include "HEJ/make_RNG.hh"
 #include "HEJ/ProgressBar.hh"
 #include "HEJ/stream.hh"
 #include "HEJ/Unweighter.hh"
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "PhaseSpacePoint.hh"
 #include "Version.hh"
 
 namespace{
   constexpr auto banner =
     "     __  ___       __       ______                                   __   "
     " __                               \n    / / / (_)___ _/ /_     / ____/___ "
     " ___  _________ ___  __       / /__  / /______                         \n "
     "  / /_/ / / __ `/ __ \\   / __/ / __ \\/ _ \\/ ___/ __ `/ / / /  __  / / _"
     " \\/ __/ ___/                         \n  / __  / / /_/ / / / /  / /___/ /"
     " / /  __/ /  / /_/ / /_/ /  / /_/ /  __/ /_(__  )                         "
     " \n /_/ /_/_/\\__, /_/ /_/  /_____/_/ /_/\\___/_/   \\__, /\\__, /   \\___"
     "_/\\___/\\__/____/                           \n     ____///__/            "
     "__   ____          ///__//____/    ______                           __    "
     "        \n    / ____(_)  _____  ____/ /  / __ \\_________/ /__  _____   / "
     "____/__  ____  ___  _________ _/ /_____  _____\n   / /_  / / |/_/ _ \\/ __"
     "  /  / / / / ___/ __  / _ \\/ ___/  / / __/ _ \\/ __ \\/ _ \\/ ___/ __ `/ "
     "__/ __ \\/ ___/\n  / __/ / />  </  __/ /_/ /  / /_/ / /  / /_/ /  __/ /   "
     "  / /_/ /  __/ / / /  __/ /  / /_/ / /_/ /_/ / /    \n /_/   /_/_/|_|\\___"
     "/\\__,_/   \\____/_/   \\__,_/\\___/_/      \\____/\\___/_/ /_/\\___/_/   "
     "\\__,_/\\__/\\____/_/     \n";
 
   constexpr double invGeV2_to_pb = 389379292.;
 }
 
 HEJFOG::Config load_config(char const * filename){
   try{
     return HEJFOG::load_config(filename);
   }
   catch(std::exception const & exc){
     std::cerr << "Error: " << exc.what() << '\n';
     std::exit(EXIT_FAILURE);
   }
 }
 
 std::vector<std::unique_ptr<HEJ::Analysis>> get_analyses(
     std::vector<YAML::Node> const & parameters, LHEF::HEPRUP const & heprup
 ){
   try{
     return HEJ::get_analyses(parameters, heprup);
   }
   catch(std::exception const & exc){
     std::cerr << "Failed to load analysis: " << exc.what() << '\n';
     std::exit(EXIT_FAILURE);
   }
 }
 
 template<class Iterator>
 auto make_lowpt_filter(Iterator begin, Iterator end, HEJ::optional<double> peak_pt){
   return boost::make_filter_iterator(
           [peak_pt](HEJ::Event const & ev){
             assert(! ev.jets().empty());
             double min_pt = peak_pt?(*peak_pt):0.;
             const auto softest_jet = fastjet::sorted_by_pt(ev.jets()).back();
             return softest_jet.pt() > min_pt;
           },
           begin, end
         );
 }
 
 int main(int argn, char** argv) {
   using namespace std::string_literals;
   if (argn < 2) {
     std::cerr << "\n# Usage:\n." << argv[0] << " config_file\n";
     return EXIT_FAILURE;
   }
   std::cout << banner;
   std::cout << "Version " << HEJFOG::Version::String()
              << ", revision " << HEJFOG::Version::revision() << std::endl;
   fastjet::ClusterSequence::print_banner();
   using clock = std::chrono::system_clock;
 
   const auto start_time = clock::now();
 
   // read configuration
   auto config = load_config(argv[1]);
 
   std::shared_ptr<HEJ::RNG> ran{
     HEJ::make_RNG(config.rng.name, config.rng.seed)};
   assert(ran != nullptr);
 
   HEJ::ScaleGenerator scale_gen{
     config.scales.base,
     config.scales.factors,
     config.scales.max_ratio
   };
 
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     std::move(scale_gen),
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   // prepare process information for output
   LHEF::HEPRUP heprup;
   heprup.IDBMUP=std::pair<long,long>(config.beam.particles[0], config.beam.particles[1]);
   heprup.EBMUP=std::make_pair(config.beam.energy, config.beam.energy);
   heprup.PDFGUP=std::make_pair(0,0);
   heprup.PDFSUP=std::make_pair(config.pdf_id,config.pdf_id);
   heprup.NPRUP=1;
   heprup.XSECUP=std::vector<double>(1.);
   heprup.XERRUP=std::vector<double>(1.);
   heprup.LPRUP=std::vector<int>{1};
   heprup.generators.emplace_back(LHEF::XMLTag{});
   heprup.generators.back().name = HEJFOG::Version::package_name();
   heprup.generators.back().version = HEJFOG::Version::String();
 
   HEJ::CombinedEventWriter writer{config.output, heprup};
 
   std::vector<std::unique_ptr<HEJ::Analysis>> analyses = get_analyses(
       config.analyses_parameters, heprup
   );
   assert(analyses.empty() || analyses.front() != nullptr);
 
   // warm-up phase to train unweighter
   HEJ::optional<HEJ::Unweighter> unweighter{};
   std::map<HEJFOG::Status, std::uint64_t> status_counter;
 
   std::vector<HEJ::Event> events;
   std::uint64_t trials = 0;
   if(config.unweight) {
     std::cout << "Calibrating unweighting ...\n";
     const auto warmup_start = clock::now();
     const size_t warmup_events = config.unweight->sample_size;
     HEJ::ProgressBar<size_t> warmup_progress{std::cout, warmup_events};
     for(; events.size() < warmup_events; ++trials){
       auto ev = generator.gen_event();
       ++status_counter[generator.status()];
       assert( (generator.status() == HEJFOG::good) == bool(ev) );
       if(generator.status() != HEJFOG::good) continue;
       const bool pass_cuts = analyses.empty() || std::any_of(
         begin(analyses), end(analyses),
         [&ev](auto const & analysis) { return analysis->pass_cuts(*ev, *ev); }
       );
       if(pass_cuts) {
         events.emplace_back(std::move(*ev));
         ++warmup_progress;
       }
     }
     std::cout << std::endl;
     unweighter = HEJ::Unweighter();
     unweighter->set_cut_to_peakwt(
       make_lowpt_filter(events.cbegin(), events.cend(), config.jets.peak_pt),
       make_lowpt_filter(events.cend(),   events.cend(), config.jets.peak_pt),
       config.unweight->max_dev
     );
     std::vector<HEJ::Event> unweighted_events;
     for(auto && ev: events) {
       auto unweighted = unweighter->unweight(std::move(ev), *ran);
       if(unweighted) {
         unweighted_events.emplace_back(std::move(*unweighted));
       }
     }
     events = std::move(unweighted_events);
     if(events.empty()) {
       std::cerr <<
         "Failed to generate events. Please increase \"unweight: sample size\""
         " or reduce \"unweight: max deviation\"\n";
       return EXIT_FAILURE;
     }
     const auto warmup_end = clock::now();
     const double completion = static_cast<double>(events.size())/config.events;
     const std::chrono::duration<double> remaining_time =
       (warmup_end- warmup_start)*(1./completion - 1);
     const auto finish = clock::to_time_t(
         std::chrono::time_point_cast<std::chrono::seconds>(warmup_end + remaining_time)
     );
     std::cout
       << "Generated " << events.size() << "/" << config.events << " events ("
       << static_cast<int>(std::round(100*completion)) << "%)\n"
       << "Estimated remaining generation time: "
       << remaining_time.count() << " seconds ("
       << std::put_time(std::localtime(&finish), "%c") << ")\n\n";
   } // end unweighting warm-up
 
   // main generation loop
   // event weight is wrong, need to divide by "total number of trials" afterwards
   HEJ::ProgressBar<size_t> progress{std::cout, config.events};
   progress.increment(events.size());
   events.reserve(config.events);
 
   for(; events.size() < config.events; ++trials){
     auto ev = generator.gen_event();
     ++status_counter[generator.status()];
     assert( (generator.status() == HEJFOG::good) == bool(ev) );
     if(generator.status() != HEJFOG::good) continue;
     const bool pass_cuts = analyses.empty() || std::any_of(
       begin(analyses), end(analyses),
       [&ev](auto const & analysis) { return analysis->pass_cuts(*ev, *ev); }
     );
     if(pass_cuts) {
       if(unweighter) {
         auto unweighted = unweighter->unweight(std::move(*ev), *ran);
         if(! unweighted) continue;
         ev = std::move(unweighted);
       }
       events.emplace_back(std::move(*ev));
       ++progress;
     }
   }
   std::cout << std::endl;
 
   // final run though events with correct weight
   HEJ::CrossSectionAccumulator xs;
   for(auto & ev: events){
     ev.parameters() *= invGeV2_to_pb/trials;
     for(auto const & analysis: analyses) {
       if(analysis->pass_cuts(ev, ev)) {
         analysis->fill(ev, ev);
       }
     }
     writer.write(ev);
     xs.fill(ev);
   }
   for(auto const & analysis: analyses) {
     analysis->finalise();
   }
 
   // Print final informations
   const std::chrono::duration<double> run_time = (clock::now() - start_time);
   std::cout << "\nTask Runtime: " << run_time.count() << " seconds for "
             << events.size() << " Events (" << events.size()/run_time.count()
             << " evts/s)\n" << std::endl;
 
   std::cout << xs << "\n";
 
   for(auto && entry: status_counter){
     const double fraction = static_cast<double>(entry.second)/trials;
     const int percent = std::round(100*fraction);
     std::cout << "status "
               << std::left << std::setw(16) << (to_string(entry.first) + ":")
               << " [";
     for(int i = 0; i < percent/2; ++i) std::cout << '#';
     for(int i = percent/2; i < 50; ++i) std::cout << ' ';
     std::cout << "] " << percent << "%" << std::endl;
   }
 
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/2j.cc b/FixedOrderGen/t/2j.cc
index 498e352..a95c0f6 100644
--- a/FixedOrderGen/t/2j.cc
+++ b/FixedOrderGen/t/2j.cc
@@ -1,67 +1,67 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Mixmax.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 86.42031848*1e6; //calculated with "combined" HEJ svn r3480
 
   auto config = load_config("config_2j.yml");
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Mixmax>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.01*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/4j.cc b/FixedOrderGen/t/4j.cc
index 5ac5493..6329d71 100644
--- a/FixedOrderGen/t/4j.cc
+++ b/FixedOrderGen/t/4j.cc
@@ -1,70 +1,70 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Mixmax.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   // calculated with 13207b5f67a5f40a2141aa7ee515b022bd4efb65
   constexpr double xs_ref = 915072; //+- 7227.72
 
   auto config = load_config("config_2j.yml");
   config.process.njets = 4;
   config.events *= 1.5;
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Mixmax>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.04*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/W_reconstruct_enu.cc b/FixedOrderGen/t/W_reconstruct_enu.cc
index 150e3c3..7b655ec 100644
--- a/FixedOrderGen/t/W_reconstruct_enu.cc
+++ b/FixedOrderGen/t/W_reconstruct_enu.cc
@@ -1,72 +1,72 @@
 /**
  *  \brief     that the reconstruction of the W works
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/Mixmax.hh"
 
 using namespace HEJFOG;
 using namespace HEJ;
 namespace {
   constexpr size_t num_events = 1000;
   constexpr double invGeV2_to_pb = 389379292.;
 }
 
 double get_xs(std::string config_name){
   auto config { load_config(config_name) };
   config.events = num_events;
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Mixmax>(11)};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
     xs += ev->central().weight;
   }
   return xs;
 }
 
 int main(){
 
   double xs_W{   get_xs("config_Wp_2j.yml")};
   double xs_enu{ get_xs("config_Wp_2j_decay.yml")};
   if(std::abs(xs_W/xs_enu-1.)>1e-6){
     std::cerr << "Reconstructing the W in the runcard gave a different results ("
       << xs_W << " vs. "<< xs_enu << " -> " << std::abs(xs_W/xs_enu-1.)*100 << "%)\n";
     return EXIT_FAILURE;
   }
 
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/h_2j.cc b/FixedOrderGen/t/h_2j.cc
index 84829b8..1a33407 100644
--- a/FixedOrderGen/t/h_2j.cc
+++ b/FixedOrderGen/t/h_2j.cc
@@ -1,75 +1,75 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Mixmax.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 2.04928; // +- 0.00377252
   //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Mixmax>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     const auto the_Higgs = std::find_if(
         begin(ev->outgoing()), end(ev->outgoing()),
         [](HEJ::Particle const & p){ return p.type == HEJ::ParticleID::h; }
     );
     assert(the_Higgs != end(ev->outgoing()));
     if(std::abs(the_Higgs->rapidity()) > 5.) continue;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.01*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/h_2j_decay.cc b/FixedOrderGen/t/h_2j_decay.cc
index 163e74d..72be5e4 100644
--- a/FixedOrderGen/t/h_2j_decay.cc
+++ b/FixedOrderGen/t/h_2j_decay.cc
@@ -1,94 +1,94 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/Ranlux64.hh"
 #include "HEJ/utility.hh"
 
 using namespace HEJFOG;
 
 bool pass_dR_cut(
     std::vector<fastjet::PseudoJet> const & jets,
     std::vector<HEJ::Particle> const & photons
 ){
   constexpr double delta_R_min = 0.7;
   for(auto const & jet: jets){
     for(auto const & photon: photons){
       if(jet.delta_R(photon.p) < delta_R_min) return false;
     }
   }
   return true;
 }
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.00429198; // +- 1.0488e-05
   //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j_decay.yml");
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Ranlux64>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     assert(ev->decays().size() == 1);
     const auto decay = begin(ev->decays());
     assert(ev->outgoing().size() > decay->first);
     const auto & the_Higgs = ev->outgoing()[decay->first];
     assert(the_Higgs.type == HEJ::pid::Higgs);
     assert(decay->second.size() == 2);
     auto const & gamma = decay->second;
     assert(gamma[0].type == HEJ::pid::photon);
     assert(gamma[1].type == HEJ::pid::photon);
     assert(HEJ::nearby_ep(gamma[0].p + gamma[1].p, the_Higgs.p, 1e-6));
     if(!pass_dR_cut(ev->jets(), gamma)) continue;
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.012*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/h_3j.cc b/FixedOrderGen/t/h_3j.cc
index 16a6670..7d32f28 100644
--- a/FixedOrderGen/t/h_3j.cc
+++ b/FixedOrderGen/t/h_3j.cc
@@ -1,76 +1,76 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/PDF.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 1.07807; // +- 0.0071
   //calculated with HEJ revision 93efdc851b02a907a6fcc63956387f9f4c1111c2 +1
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 3;
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Ranlux64>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     const auto the_Higgs = std::find_if(
         begin(ev->outgoing()), end(ev->outgoing()),
         [](HEJ::Particle const & p){ return p.type == HEJ::ParticleID::h; }
     );
     assert(the_Higgs != end(ev->outgoing()));
     if(std::abs(the_Higgs->rapidity()) > 5.) continue;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.02*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/h_3j_uno1.cc b/FixedOrderGen/t/h_3j_uno1.cc
index 142107d..df5aae2 100644
--- a/FixedOrderGen/t/h_3j_uno1.cc
+++ b/FixedOrderGen/t/h_3j_uno1.cc
@@ -1,81 +1,81 @@
 /**
  *  check that adding uno emissions doesn't change the FKL cross section
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cassert>
 #include <cmath>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 #include "Subleading.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/PDF.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.0243548; // +- 0.000119862
   //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 3;
   config.process.incoming = {HEJ::pid::u, HEJ::pid::u};
   config.subleading_channels = HEJFOG::Subleading::uno;
   config.events *= 1.5;
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Ranlux64>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   size_t uno_found = 0;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     if(ev->type() != HEJ::event_type::FKL){
       ++uno_found;
       continue;
     }
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << '\n';
   std::cout << uno_found << " events with unordered emission" << std::endl;
   assert(uno_found > 0);
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.05*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/h_3j_uno2.cc b/FixedOrderGen/t/h_3j_uno2.cc
index b93f1ad..13e1ae9 100644
--- a/FixedOrderGen/t/h_3j_uno2.cc
+++ b/FixedOrderGen/t/h_3j_uno2.cc
@@ -1,75 +1,75 @@
 /**
  * check uno cross section
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cassert>
 #include <cmath>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 #include "Subleading.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/PDF.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.00347538; // +- 3.85875e-05
   //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 3;
   config.process.incoming = {HEJ::pid::u, HEJ::pid::u};
   config.subleading_fraction = 1.;
   config.subleading_channels = HEJFOG::Subleading::uno;
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Ranlux64>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     if(ev->type() == HEJ::event_type::FKL) continue;
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.05*xs);
   return EXIT_SUCCESS;
 }
diff --git a/FixedOrderGen/t/h_5j.cc b/FixedOrderGen/t/h_5j.cc
index 0e0948f..e852111 100644
--- a/FixedOrderGen/t/h_5j.cc
+++ b/FixedOrderGen/t/h_5j.cc
@@ -1,72 +1,72 @@
 /**
  *  This is a regression test
  *  the reference cross section has not been checked against any other program
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 #include <algorithm>
 #include <cassert>
 #include <cmath>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/PDF.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.252273; // +- 0.00657742
   //calculated with HEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 5;
 
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Ranlux64>()};
   HEJFOG::EventGenerator generator{
     config.process,
     config.beam,
     HEJ::ScaleGenerator{
       config.scales.base,
       config.scales.factors,
       config.scales.max_ratio
     },
     config.jets,
     config.pdf_id,
     config.subleading_fraction,
     config.subleading_channels,
     config.particle_decays,
     config.Higgs_coupling,
     config.ew_parameters,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (size_t trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev);
     ev->central().weight *= invGeV2_to_pb;
     ev->central().weight /= config.events;
 
     xs += ev->central().weight;
     xs_err += ev->central().weight*ev->central().weight;
   }
   xs_err = std::sqrt(xs_err);
   std::cout << xs_ref << " ~ " << xs << " +- " << xs_err << std::endl;
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.06*xs);
   return EXIT_SUCCESS;
 }
diff --git a/current_generator/jWuno_j.frm b/current_generator/jWuno_j.frm
index e2148f0..13e4198 100644
--- a/current_generator/jWuno_j.frm
+++ b/current_generator/jWuno_j.frm
@@ -1,101 +1,101 @@
 */**
 *  \brief Contraction of W unordered current with FKL current
 *
 *  TODO: unify conventions with developer manual
 *  the current dictionary is as follows:
 *
 *  code  | manual
 *  pg    | p_1
 *  p1    | p_2
 *  pa    | p_a
 *
 *  \authors   The HEJ collaboration (see AUTHORS for details)
-*  \date      2019
+*  \date      2019-2020
 *  \copyright GPLv2 or later
 */
 #include- include/helspin.frm
 #include- include/write.frm
 
 s h,s2g,sbg,taW1;
 v p,p1,p2,pa,pb,pg,pl,plbar,pW,pr,q1,q1g,q2;
 i mu,nu,rho,sigma;
 cf m2inv;
 
 #do h1={+,-}
 *  eq:U1tensor in developer manual, up to factors 1/sij, 1/tij
    l [U1 `h1'] = (
       + Current(`h1'1, p1, nu, p1+pg, mu, pa-pW, rho, pa)
       + Current(`h1'1, p1, nu, p1+pg, rho, p1+pg+pW, mu, pa)
       + Current(`h1'1, p1, rho, p1+pW, nu, p1+pg+pW, mu, pa)
    );
 
 *  eq:U2tensor in developer manual, up to factors 1/sij, 1/tij
    l [U2 `h1'] = (
       + Current(`h1'1, p1, mu, pa - pW - pg, nu, pa - pW, rho, pa)
       + Current(`h1'1, p1, mu, pa - pW - pg, rho, pa - pg, nu, pa)
       + Current(`h1'1, p1, rho, p1 + pW, mu, pa - pg, nu, pa)
    );
 
 *  eq:Ltensor in developer manual, up to factors 1/sij, 1/tij
    l [L `h1'] = (
       Current(`h1'1, p1, sigma, pa - pW, rho, pa) +
       Current(`h1'1, p1, rho, p1 + pW, sigma, pa)
    )*(
       ((pb(nu)/sbg + p2(nu)/s2g)*m2(q1g) + 2*q1(nu) - pg(nu))*d_(mu, sigma)
       - 2*pg(mu)*d_(nu, sigma)
       + (2*pg(sigma) - q1(sigma))*d_(mu, nu)
    )/taW1;
 #enddo
 .sort
 * restore kinematic factors
 id Current(h?, p1, mu?, q1?, nu?, q2?, rho?, pa) = (
    Current(h, p1, mu, q1, nu, q2, rho, pa)*m2inv(q1)*m2inv(q2)
 );
 id Current(h?, p1, mu?, q1?, nu?, pa) = (
    Current(h, p1, mu, q1, nu, pa)*m2inv(q1)
 );
 .sort
 drop;
 
 * multiply with polarisation vector and other currents
 #do h1={+,-}
    #do h2={+,-}
       #do hg={+,-}
          #do TENSOR={U1,U2,L}
             l [`TENSOR' `h1'`h2'`hg'] = (
                [`TENSOR' `h1']
                *Eps(`hg'1, nu)
                *Current(`h2'1, p2, mu, pb)
                *Current(-1, pl, rho, plbar)
             );
          #enddo
       #enddo
    #enddo
 #enddo
 
 * choice of best reference vector (p2 or pb)
 id Eps(h?, nu?)*Current(h?, p2, mu?, pb) = Eps(h, nu, pg, p2)*Current(h, p2, mu, pb);
 also Eps(h?, mu?) = Eps(h, mu, pg, pb);
 
 multiply replace_(q1g,q1-pg);
 multiply replace_(q1,pa-p1-pW);
 multiply replace_(pW,pl+plbar);
 
 .sort
 #call ContractCurrents
 multiply replace_(
    s2g,m2(p2+pg),
    sbg,m2(pb+pg),
    taW1,m2(pa-pl-plbar-p1)
 );
 id m2inv(q1?) = 1/m2(q1);
 multiply replace_(pW,pl+plbar);
 .sort
 format O4;
 format c;
 #call WriteHeader(`OUTPUT')
 #call WriteOptimised(`OUTPUT',U1,3,p1,p2,pa,pb,pg,pl,plbar)
 #call WriteOptimised(`OUTPUT',U2,3,p1,p2,pa,pb,pg,pl,plbar)
 #call WriteOptimised(`OUTPUT',L,3,p1,p2,pa,pb,pg,pl,plbar)
 #call WriteFooter(`OUTPUT')
 .end
diff --git a/include/HEJ/Config.hh b/include/HEJ/Config.hh
index f9d1a7c..84fdaaf 100644
--- a/include/HEJ/Config.hh
+++ b/include/HEJ/Config.hh
@@ -1,233 +1,233 @@
 /** \file
  *  \brief HEJ 2 configuration parameters
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <map>
 #include <string>
 #include <vector>
 
 #include "fastjet/JetDefinition.hh"
 #include "yaml-cpp/yaml.h"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/Fraction.hh"
 #include "HEJ/HiggsCouplingSettings.hh"
 #include "HEJ/optional.hh"
 #include "HEJ/output_formats.hh"
 #include "HEJ/ScaleFunction.hh"
 
 namespace HEJ{
 
   //! Jet parameters
   struct JetParameters{
     fastjet::JetDefinition def;          /**< Jet Definition */
     double min_pt;                       /**< Minimum Jet Transverse Momentum */
   };
 
   //! Settings for scale variation
   struct ScaleConfig{
     //! Base scale choices
     std::vector<ScaleFunction> base;
     //! Factors for multiplicative scale variation
     std::vector<double> factors;
     //! Maximum ratio between renormalisation and factorisation scale
     double max_ratio;
   };
 
   //! Settings for random number generator
   struct RNGConfig {
     //! Random number generator name
     std::string name;
     //! Optional initial seed
     optional<std::string> seed;
   };
 
   //! Settings for partial unweighting
   struct PartialUnweightConfig {
     //! Number of trials for training
     size_t trials;
     //! Maximum distance in standard deviations from mean logarithmic weight
     double max_dev;
   };
 
   /**! Possible treatments for fixed-order input events.
    *
    *  The program will decide on how to treat an event based on
    *  the value of this enumeration.
    */
   enum class EventTreatment{
     reweight,                                  /**< Perform resummation */
     keep,                                      /**< Keep the event */
     discard,                                   /**< Discard the event */
   };
 
   //! Container to store the treatments for various event types
   using EventTreatMap = std::map<event_type::EventType, EventTreatment>;
 
   //! Possible setting for the event weight
   enum class WeightType{
     weighted,             //!< weighted events
     unweighted_resum,     //!< unweighted only resummation part
     partially_unweighted  //!< mixed weighted and unweighted
   };
 
   /**! Input parameters.
    *
    * This struct handles stores all configuration parameters
    * needed in a HEJ 2 run.
    *
    * \internal To add a new option:
    *           1. Add a member to the Config struct.
    *           2. Inside "src/YAMLreader.cc":
    *              - Add the option name to the "supported" Node in
    *                get_supported_options.
    *              - Initialise the new Config member in to_Config.
    *                The functions set_from_yaml (for mandatory options) and
    *                set_from_yaml_if_defined (non-mandatory) may be helpful.
    *           3. Add a new entry (with short description) to config.yaml
    *           4. Update the user documentation in "doc/Sphinx/"
    */
   struct Config {
     //! %Parameters for scale variation
     ScaleConfig scales;
     //! Resummation jet properties
     JetParameters resummation_jets;
     //! Fixed-order jet properties
     JetParameters fixed_order_jets;
     //! Minimum transverse momentum for extremal partons
     //! \deprecated This will be removed in future versions.
     //!             Use \ref max_ext_soft_pt_fraction instead.
     double min_extparton_pt = 0;
     //! Maximum transverse momentum fraction from soft radiation in extremal jets
     Fraction<double> max_ext_soft_pt_fraction;
     //! The regulator lambda for the subtraction terms
     double regulator_lambda = CLAMBDA;
     //! Number of resummation configurations to generate per fixed-order event
     size_t trials;
     //! Maximal number of events
     optional<size_t> max_events;
     //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running
     bool log_correction;
     //! Event output files names and formats
     std::vector<OutputFile> output;
     //! Parameters for random number generation
     RNGConfig rng;
     //! Map to decide what to do for different event types
     EventTreatMap treat;
     //! %Parameters for custom analysis
     //! @deprecated use analyses_parameters instead
     YAML::Node analysis_parameters;
     //! %Parameters for custom analyses
     std::vector<YAML::Node> analyses_parameters;
     //! Settings for effective Higgs-gluon coupling
     HiggsCouplingSettings Higgs_coupling;
     //! elector weak parameters
     EWConstants ew_parameters;
     //! Type of event weight e.g. (un)weighted
     WeightType weight_type;
     //! Settings for partial unweighting
     HEJ::optional<PartialUnweightConfig> unweight_config;
   };
 
   //! Configuration options for the PhaseSpacePoint class
   struct PhaseSpacePointConfig {
     //! Properties of resummation jets
     JetParameters jet_param;
     //! Minimum transverse momentum for extremal partons
     //! \deprecated This will be removed in future versions.
     //!             Use \ref max_ext_soft_pt_fraction instead.
     double min_extparton_pt = 0;
     //! Maximum transverse momentum fraction from soft radiation in extremal jets
     Fraction<double> max_ext_soft_pt_fraction;
   };
 
   //! Configuration options for the MatrixElement class
   struct MatrixElementConfig {
     MatrixElementConfig() = default;
     MatrixElementConfig(
         bool log_correction,
         HiggsCouplingSettings Higgs_coupling,
         EWConstants ew_parameters,
         double regulator_lambda = CLAMBDA
     ):
     log_correction{log_correction},
     Higgs_coupling{Higgs_coupling},
     ew_parameters{ew_parameters},
     regulator_lambda{regulator_lambda}
     {}
 
     //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running
     bool log_correction;
     //! Settings for effective Higgs-gluon coupling
     HiggsCouplingSettings Higgs_coupling;
     //! elector weak parameters
     EWConstants ew_parameters;
     //! The regulator lambda for the subtraction terms
     double regulator_lambda = CLAMBDA;
   };
 
   //! Configuration options for the EventReweighter class
   struct EventReweighterConfig {
     //! Settings for phase space point generation
     PhaseSpacePointConfig psp_config;
     //! Settings for matrix element calculation
     MatrixElementConfig ME_config;
     //! Access properties of resummation jets
     JetParameters & jet_param() {
       return psp_config.jet_param;}
     //! Access properties of resummation jets (const version)
     JetParameters const & jet_param() const {
       return psp_config.jet_param;}
     //! Treatment of the various event types
     EventTreatMap treat;
   };
 
   /**! Extract PhaseSpacePointConfig from Config
    *
    * \internal We do not provide a PhaseSpacePointConfig constructor from Config
    * so that PhaseSpacePointConfig remains an aggregate.
    * This faciliates writing client code (e.g. the HEJ fixed-order generator)
    * that creates a PhaseSpacePointConfig *without* a Config object.
    *
    * @see to_MatrixElementConfig, to_EventReweighterConfig
    */
   inline
   PhaseSpacePointConfig to_PhaseSpacePointConfig(Config const & conf) {
     return {
       conf.resummation_jets,
       conf.min_extparton_pt,
       conf.max_ext_soft_pt_fraction
     };
   }
 
   /**! Extract MatrixElementConfig from Config
    *
    * @see to_PhaseSpacePointConfig, to_EventReweighterConfig
    */
   inline
   MatrixElementConfig to_MatrixElementConfig(Config const & conf) {
     return {conf.log_correction, conf.Higgs_coupling,
             conf.ew_parameters, conf.regulator_lambda};
   }
 
   /**! Extract EventReweighterConfig from Config
    *
    * @see to_PhaseSpacePointConfig, to_MatrixElementConfig
    */
   inline
   EventReweighterConfig to_EventReweighterConfig(Config const & conf) {
     return {
       to_PhaseSpacePointConfig(conf),
       to_MatrixElementConfig(conf),
       conf.treat
     };
   }
 
 } // namespace HEJ
diff --git a/include/HEJ/CrossSectionAccumulator.hh b/include/HEJ/CrossSectionAccumulator.hh
index c6b2fb0..c5ca18f 100644
--- a/include/HEJ/CrossSectionAccumulator.hh
+++ b/include/HEJ/CrossSectionAccumulator.hh
@@ -1,99 +1,99 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <iterator>
 #include <map>
 #include <ostream>
 #include <vector>
 
 #include "HEJ/event_types.hh"
 
 namespace HEJ {
   class Event;
 
   //! Collection of Cross Section with its uncertainty
   template<typename T>
   struct XSWithError {
     T value = T{}; //!< Cross Section
     T error = T{}; //!< Error
   };
 
   /**
    * @brief Sum of Cross Section for different subproccess
    */
   class CrossSectionAccumulator {
   public:
     //! Fill with single event
     //! @note for multiple resummation events use fill_correlated() instead
     void fill(Event const & ev);
     //! Fill by weight and type
     //! @note for multiple resummation events use fill_correlated() instead
     void fill(double weight, event_type::EventType type);
     //! Fill by weight, error and type
     //! @note The error will be _added_ to the current error
     void fill(double weight, double delta_error, event_type::EventType type);
 
     /**
      * @brief   Fill with multiple correlated weights
      * @details This should be used to fill multiple reweighted events,
      *          coming from the same fixed order point.
      *          Total error for \f$N\f$ fixed order points each giving \f$M_i\f$
      *          resummation events is:
      *          \f[
      *          \delta^2=\sum_i \left(\sum_j w_{i,j}\right)^2
      *                   +\sum_{i,j} \left(w_{i,j}\right)^2,
      *          \f]
      * @note    This is equivalent to fill() for only one reweighted event
      *          coming from each fixed order point (\f$M_i=1\f$)
      */
     void fill_correlated(std::vector<Event> const & evts);
     //! iterator implementation of fill_correlated()
     template<class ConstIt>
     void fill_correlated(ConstIt begin, ConstIt end);
     //! explicit version of fill_correlated() by giving sum(wt) and sum(wt^2)
     void fill_correlated(double sum, double sum2, event_type::EventType type);
 
     //! begin of Cross Section and error for subprocesses
     auto begin() const {
       return std::begin(xs_);
     }
     //! end of Cross Section and error for subprocesses
     auto end() const {
       return std::end(xs_);
     }
     //! total Cross Section and error
     XSWithError<double> const & total() const {
       return total_;
     }
 
   private:
     std::map<HEJ::event_type::EventType, XSWithError<double>> xs_;
     XSWithError<double> total_;
   };
 
   //! Print CrossSectionAccumulator to stream
   std::ostream& operator<<(std::ostream& os, const CrossSectionAccumulator& xs);
 
 // ------------ Implementation ------------
 
   template<class ConstIt>
   void CrossSectionAccumulator::fill_correlated(ConstIt begin, ConstIt end){
     if(std::distance(begin, end) < 2){ // only one event
       fill(*begin);
       return;
     }
     double sum = 0.;
     double sum2 = 0.;
     const auto type = begin->type();
     for(; begin != end; ++begin){
       double const wt = begin->central().weight;
       sum += wt;
       sum2 += wt*wt;
     }
     fill_correlated(sum, sum2, type);
   }
 } // namespace HEJ
diff --git a/include/HEJ/EWConstants.hh b/include/HEJ/EWConstants.hh
index 709bac5..6533e7d 100644
--- a/include/HEJ/EWConstants.hh
+++ b/include/HEJ/EWConstants.hh
@@ -1,90 +1,90 @@
 /** \file
  *  \brief Defines the electro weak parameters
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <cmath>
 
 #include "HEJ/exceptions.hh"
 #include "HEJ/PDG_codes.hh"
 
 namespace HEJ {
   //! collection of basic particle properties
   struct ParticleProperties {
     double mass;  //!< Mass
     double width; //!< Decay width
   };
 
   //! Collection of electro-weak constants
   class EWConstants {
   public:
     EWConstants() = default;
     //! initialise by Vacuum expectation value & boson properties
     EWConstants(
       double vev, //!< vacuum expectation value
       ParticleProperties const & Wprop, //!< W boson mass & width
       ParticleProperties const & Zprop, //!< Z boson mass & width
       ParticleProperties const & Hprop  //!< Higgs boson mass & width
     ): set{true}, vev_{vev}, Wprop_{Wprop}, Zprop_{Zprop}, Hprop_{Hprop} {}
     //! set constants by Vacuum expectation value & boson properties
     void set_vevWZH(
       double vev, //!< vacuum expectation value
       ParticleProperties const & Wprop, //!< W boson mass & width
       ParticleProperties const & Zprop, //!< Z boson mass & width
       ParticleProperties const & Hprop  //!< Higgs boson mass & width
     ){
       set = true; vev_= vev; Wprop_= Wprop; Zprop_= Zprop; Hprop_= Hprop;
     }
     //! vacuum expectation value
     double vev()     const {check_set(); return vev_;}
     //! Properties of the W boson
     ParticleProperties const & Wprop() const {check_set(); return Wprop_;}
     //! Properties of the Z boson
     ParticleProperties const & Zprop() const {check_set(); return Zprop_;}
     //! Properties of the Higgs boson
     ParticleProperties const & Hprop() const {check_set(); return Hprop_;}
     //! access Properties by boson id
     ParticleProperties const & prop(ParticleID const id) const {
       using namespace pid;
       switch(id){
       case Wp:
       case Wm:
         return Wprop();
       case Z:
         return Zprop();
       case h:
         return Hprop();
       default:
         throw std::invalid_argument("No properties available for particle "+name(id));
       }
     }
     //! cosine of Weinberg angle
     double cos_tw()   const {return Wprop().mass/Zprop().mass;}
     //! cosine square of Weinberg angle
     double cos2_tw()  const {return cos_tw()*cos_tw();}
     //! sinus Weinberg angle
     double sin_tw()   const {return sqrt(sin2_tw());}
     //! sinus square of Weinberg angle
     double sin2_tw()  const {return 1. - cos2_tw();}
     //! elector magnetic coupling
     double alpha_em() const {return e2()/4./M_PI;}
     //! weak coupling
     double alpha_w()  const {return gw2()/2.;}
 
   private:
     double gw2() const {return 4*Wprop().mass/vev()*Wprop().mass/vev();}
     double e2()  const {return gw2()*sin2_tw();}
     void check_set() const {
       if(!set) throw std::invalid_argument("EW constants not specified");
     }
     bool set{false};
     double vev_;
     ParticleProperties Wprop_;
     ParticleProperties Zprop_;
     ParticleProperties Hprop_;
   };
 } // namespace HEJ
diff --git a/include/HEJ/EmptyAnalysis.hh b/include/HEJ/EmptyAnalysis.hh
index cbecbdf..7715a63 100644
--- a/include/HEJ/EmptyAnalysis.hh
+++ b/include/HEJ/EmptyAnalysis.hh
@@ -1,49 +1,49 @@
 /** \file
  *  \brief Declaration of the trivial (empty) analysis
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 
 #include "HEJ/Analysis.hh"
 
 //! YAML Namespace
 namespace YAML {
   class Node;
 }
 
 namespace HEJ {
   /** An analysis that does nothing
    *
    *  This analysis is used by default if no user analysis is specified.
    *  The member functions don't do anything and events passed to the
    *  analysis are simply ignored.
    */
   struct EmptyAnalysis: Analysis{
     //! Create EmptyAnalysis
     static std::unique_ptr<Analysis> create(
         YAML::Node const & parameters, LHEF::HEPRUP const &);
 
     //! Fill event into analysis (e.g. to histograms)
     /**
      *  This function does nothing
      */
     virtual void fill(Event const &, Event const &) override;
     //! Whether a resummation event passes all cuts
     /**
      *  There are no cuts, so all events pass
      */
     virtual bool pass_cuts(Event const &, Event const &) override;
     //! Finalise analysis
     /**
      * This function does nothing
      */
     virtual void finalise() override;
 
     virtual ~EmptyAnalysis() override = default;
   };
 } // namespace HEJ
diff --git a/include/HEJ/Event.hh b/include/HEJ/Event.hh
index ebafcad..a15dbe4 100644
--- a/include/HEJ/Event.hh
+++ b/include/HEJ/Event.hh
@@ -1,369 +1,369 @@
 /** \file
  *  \brief Declares the Event class and helpers
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
 #include "boost/iterator/filter_iterator.hpp"
 
 #include "fastjet/ClusterSequence.hh"
 
 #include "HEJ/event_types.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/RNG.hh"
 
 namespace LHEF {
   class HEPEUP;
   class HEPRUP;
 }
 
 namespace fastjet {
   class JetDefinition;
 }
 
 namespace HEJ {
 
   struct UnclusteredEvent;
 
   /** @brief An event with clustered jets
     *
     * This is the main HEJ 2 event class.
     * It contains kinematic information including jet clustering,
     * parameter (e.g. scale) settings and the event weight.
     */
   class Event {
   public:
     class EventData;
 
     //! Iterator over partons
     using ConstPartonIterator = boost::filter_iterator<
       bool (*)(Particle const &),
       std::vector<Particle>::const_iterator
       >;
     //! Reverse Iterator over partons
     using ConstReversePartonIterator = std::reverse_iterator<
                                                 ConstPartonIterator>;
     //! No default Constructor
     Event() = delete;
     //! Event Constructor adding jet clustering to an unclustered event
     //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.2.0
     [[deprecated("UnclusteredEvent will be replaced by EventData")]]
     Event(
       UnclusteredEvent const & ev,
       fastjet::JetDefinition const & jet_def, double min_jet_pt
     );
 
     //! @name Particle Access
     //! @{
 
     //! Incoming particles
     std::array<Particle, 2> const &  incoming() const{
       return incoming_;
     }
     //! Outgoing particles
     std::vector<Particle> const &  outgoing() const{
       return outgoing_;
     }
     //! Iterator to the first outgoing parton
     ConstPartonIterator begin_partons() const;
     //! Iterator to the first outgoing parton
     ConstPartonIterator cbegin_partons() const;
 
     //! Iterator to the end of the outgoing partons
     ConstPartonIterator end_partons() const;
     //! Iterator to the end of the outgoing partons
     ConstPartonIterator cend_partons() const;
 
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator rbegin_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator crbegin_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator rend_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator crend_partons() const;
 
     //! Particle decays
     /**
      *  The key in the returned map corresponds to the index in the
      *  vector returned by outgoing()
      */
     std::unordered_map<size_t, std::vector<Particle>> const &  decays() const{
       return decays_;
     }
     //! The jets formed by the outgoing partons, sorted in rapidity
     std::vector<fastjet::PseudoJet> const & jets() const{
       return jets_;
     }
     //! @}
 
     //! @name Weight variations
     //! @{
 
     //! All chosen parameter, i.e. scale choices (const version)
     Parameters<EventParameters> const & parameters() const{
       return parameters_;
     }
     //! All chosen parameter, i.e. scale choices
     Parameters<EventParameters> & parameters(){
       return parameters_;
     }
 
     //! Central parameter choice (const version)
     EventParameters const & central() const{
       return parameters_.central;
     }
     //! Central parameter choice
     EventParameters & central(){
       return parameters_.central;
     }
 
     //! Parameter (scale) variations (const version)
     std::vector<EventParameters> const & variations() const{
       return parameters_.variations;
     }
     //! Parameter (scale) variations
     std::vector<EventParameters> & variations(){
       return parameters_.variations;
     }
 
     //! Parameter (scale) variation (const version)
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters const & variations(size_t i) const{
       return parameters_.variations.at(i);
     }
     //! Parameter (scale) variation
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters & variations(size_t i){
       return parameters_.variations.at(i);
     }
     //! @}
 
     //! Indices of the jets the outgoing partons belong to
     /**
      *  @param jets   Jets to be tested
      *  @returns      A vector containing, for each outgoing parton,
      *                the index in the vector of jets the considered parton
      *                belongs to. If the parton is not inside any of the
      *                passed jets, the corresponding index is set to -1.
      */
     std::vector<int> particle_jet_indices(
         std::vector<fastjet::PseudoJet> const & jets
     ) const {
       return cs_.particle_jet_indices(jets);
     }
     //! particle_jet_indices() of the Event jets()
     std::vector<int> particle_jet_indices() const {
       return particle_jet_indices(jets());
     }
 
     //! Jet definition used for clustering
     fastjet::JetDefinition const & jet_def() const{
       return cs_.jet_def();
     }
 
     //! Minimum jet transverse momentum
     double min_jet_pt() const{
       return min_jet_pt_;
     }
 
     //! Event type
     event_type::EventType type() const{
       return type_;
     }
 
     //! Give colours to each particle
     /**
      * @returns true if new colours are generated, i.e. same as is_resummable()
      * @details Colour ordering is done according to leading colour in the MRK
      *          limit, see \cite Andersen:2011zd. This only affects \ref
      *          is_resummable() "HEJ" configurations, all other \ref event_type
      *          "EventTypes" will be ignored.
      * @note    This overwrites all previously set colours.
      */
     bool generate_colours(HEJ::RNG &);
 
     //! Check that current colours are leading in the high energy limit
     /**
      * @details Checks that the colour configuration can be split up in
      *          multiple, rapidity ordered, non-overlapping ladders. Such
      *          configurations are leading in the MRK limit, see
      *          \cite Andersen:2011zd
      *
      * @note This is _not_ to be confused with \ref is_resummable(), however
      *       for all resummable states it is possible to create a leading colour
      *       configuration, see generate_colours()
      */
     bool is_leading_colour() const;
 
     /**
      * @brief Check if given event could have been produced by HEJ
      * @details A HEJ state has to fulfil:
      *          1. type() has to be \ref is_resummable() "resummable"
      *          2. Soft radiation in the tagging jets contributes at most to
      *             `max_ext_soft_pt_fraction` of the total jet \f$ p_\perp \f$
      *
      * @note This is true for any resummed stated produced by the
      *       EventReweighter or any \ref is_resummable() "resummable" Leading
      *       Order state.
      *
      * @param max_ext_soft_pt_fraction Maximum transverse momentum fraction from
      *                                 soft radiation in extremal jets
      * @param min_extparton_pt         Absolute minimal \f$ p_\perp \f$,
      *                                 \b deprecated use max_ext_soft_pt_fraction
      *                                 instead
      * @return True if this state could have been produced by HEJ
      */
     bool valid_hej_state(
       double max_ext_soft_pt_fraction, double min_extparton_pt = 0.) const;
 
   private:
     //! \internal
     //! @brief Construct Event explicitly from input.
     /** This is only intended to be called from EventData.
      *
      * \warning The input is taken _as is_, sorting and classification has to be
      *          done externally, i.e. by EventData
      */
     Event(
       std::array<Particle, 2> && incoming,
       std::vector<Particle> && outgoing,
       std::unordered_map<size_t, std::vector<Particle>> && decays,
       Parameters<EventParameters> && parameters,
       fastjet::JetDefinition const & jet_def,
       double const min_jet_pt
     );
 
     //! Iterator over partons (non-const)
     using PartonIterator = boost::filter_iterator<
       bool (*)(Particle const &),
       std::vector<Particle>::iterator
       >;
     //! Reverse Iterator over partons (non-const)
     using ReversePartonIterator = std::reverse_iterator<PartonIterator>;
 
     //! Iterator to the first outgoing parton (non-const)
     PartonIterator begin_partons();
     //! Iterator to the end of the outgoing partons (non-const)
     PartonIterator end_partons();
 
     //! Reverse Iterator to the first outgoing parton (non-const)
     ReversePartonIterator rbegin_partons();
     //! Reverse Iterator to the first outgoing parton (non-const)
     ReversePartonIterator rend_partons();
 
     std::array<Particle, 2> incoming_;
     std::vector<Particle> outgoing_;
     std::unordered_map<size_t, std::vector<Particle>> decays_;
     std::vector<fastjet::PseudoJet> jets_;
     Parameters<EventParameters> parameters_;
     fastjet::ClusterSequence cs_;
     double min_jet_pt_;
     event_type::EventType type_;
   }; // end class Event
 
   //! Class to store general Event setup, i.e. Phase space and weights
   class Event::EventData {
   public:
     //! Default Constructor
     EventData() = default;
     //! Constructor from LesHouches event information
     EventData(LHEF::HEPEUP const & hepeup);
     //! Constructor with all values given
     EventData(
       std::array<Particle, 2> incoming,
       std::vector<Particle> outgoing,
       std::unordered_map<size_t, std::vector<Particle>> decays,
       Parameters<EventParameters> parameters
     ):
       incoming(std::move(incoming)), outgoing(std::move(outgoing)),
       decays(std::move(decays)), parameters(std::move(parameters))
     {}
 
     //! Generate an Event from the stored EventData.
     /**
      * @details          Do jet clustering and classification.
      *                   Use this to generate an Event.
      *
      * @note             Calling this function destroys EventData
      *
      * @param jet_def    Jet definition
      * @param min_jet_pt minimal \f$p_T\f$ for each jet
      *
      * @returns          Full clustered and classified event.
      */
     Event cluster(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt);
 
     //! Alias for cluster()
     Event operator()(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt){
       return cluster(jet_def, min_jet_pt);
     }
 
     //! Sort particles in rapidity
     void sort();
 
     //! Reconstruct intermediate particles from final-state leptons
     /**
      *  Final-state leptons are created from virtual photons, W, or Z bosons.
      *  This function tries to reconstruct such intermediate bosons if they
      *  are not part of the event record.
      */
     void reconstruct_intermediate();
 
     //! Incoming particles
     std::array<Particle, 2> incoming;
     //! Outcoing particles
     std::vector<Particle> outgoing;
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays;
     //! Parameters, e.g. scale or inital weight
     Parameters<EventParameters> parameters;
   }; // end class EventData
 
   //! Print Event
   std::ostream& operator<<(std::ostream & os, Event const & ev);
 
   //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$
   double shat(Event const & ev);
 
   //! Convert an event to a LHEF::HEPEUP
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP *);
 
   // put deprecated warning at the end, so don't get the warning inside Event.hh,
   // additionally doxygen can not identify [[deprecated]] correctly
   struct [[deprecated("UnclusteredEvent will be replaced by EventData")]]
     UnclusteredEvent;
   //! An event before jet clustering
   //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.2.0
   struct UnclusteredEvent{
     //! Default Constructor
     UnclusteredEvent() = default;
     //! Constructor from LesHouches event information
     UnclusteredEvent(LHEF::HEPEUP const & hepeup);
 
     std::array<Particle, 2> incoming;          /**< Incoming Particles */
     std::vector<Particle> outgoing;            /**< Outgoing Particles */
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays;
     //! Central parameter (e.g. scale) choice
     EventParameters central;
     std::vector<EventParameters> variations;    /**< For parameter variation */
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/EventReader.hh b/include/HEJ/EventReader.hh
index e816a02..5a81752 100644
--- a/include/HEJ/EventReader.hh
+++ b/include/HEJ/EventReader.hh
@@ -1,57 +1,57 @@
 /** \file
  *  \brief Header file for event reader interface
  *
  *  This header defines an abstract base class for reading events from files.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/optional.hh"
 
 namespace HEJ {
   class EventData;
 
   //! Abstract base class for reading events from files
   struct EventReader {
     //! Read an event
     virtual bool read_event() = 0;
 
     //! Access header text
     virtual std::string const & header() const = 0;
 
     //! Access run information
     virtual LHEF::HEPRUP const & heprup() const = 0;
 
     //! Access last read event
     virtual LHEF::HEPEUP const & hepeup() const = 0;
 
     //! Guess number of events from header
     virtual HEJ::optional<size_t> number_events() const {
       size_t start = header().rfind("Number of Events");
       start = header().find_first_of("123456789", start);
       if(start == std::string::npos) {
         return {};
       }
       const size_t end = header().find_first_not_of("0123456789", start);
       return std::stoi(header().substr(start, end - start));
     }
 
     virtual ~EventReader() = default;
   };
 
   //! Factory function for event readers
   /**
    *  @param filename   The name of the input file
    *  @returns          A pointer to an instance of an EventReader
    *                    for the input file
    */
   std::unique_ptr<EventReader> make_reader(std::string const & filename);
 } // namespace HEJ
diff --git a/include/HEJ/EventReweighter.hh b/include/HEJ/EventReweighter.hh
index 969311d..59a69a1 100644
--- a/include/HEJ/EventReweighter.hh
+++ b/include/HEJ/EventReweighter.hh
@@ -1,196 +1,196 @@
 /** \file
  *  \brief Declares the EventReweighter class
  *
  *  EventReweighter is the main class used within HEJ 2. It reweights the
  *  resummation events.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <memory>
 #include <vector>
 
 #include "HEJ/Config.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/StatusCode.hh"
 
 namespace LHEF {
   class HEPRUP;
 }
 
 namespace HEJ {
   class Event;
   class RNG;
 
   //! Beam parameters
   /**
    *  Currently, only symmetric beams are supported,
    *  so there is a single beam energy.
    */
   struct Beam{
     double E;                                /**< Beam energy */
     std::array<ParticleID, 2> type;          /**< Beam particles */
   };
 
   //! Main class for reweighting events in HEJ.
   class EventReweighter{
     using EventType = event_type::EventType;
 
   public:
     EventReweighter(
         Beam beam,                            /**< Beam Energy */
         int pdf_id,                           /**< PDF ID */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         std::shared_ptr<RNG> ran              /**< Random number generator */
     );
 
     EventReweighter(
         LHEF::HEPRUP const & heprup,          /**< LHEF event header */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         std::shared_ptr<RNG> ran              /**< Random number generator */
     );
 
     //! Get the used pdf
     PDF const & pdf() const;
 
     //! Get event treatment
     EventTreatment treatment(EventType type) const;
 
     //! Generate resummation events for a given fixed-order event
     /**
      *  @param ev             Fixed-order event corresponding
      *                        to the resummation events
      *  @param num_events     Number of trial resummation configurations.
      *  @returns              A vector of resummation events.
      *
      *  The result vector depends on the type of the input event and the
      *  \ref EventTreatment of different types as specified in the constructor:
      *
      *  - EventTreatment::reweight: The result vector contains between 0 and
      *                              num_events resummation events.
      *  - EventTreatment::keep:     If the input event passes the resummation
      *                              jet cuts the result vector contains one
      *                              event. Otherwise it is empty.
      *  - EventTreatment::discard:  The result vector is empty
      */
     std::vector<Event> reweight(
         Event const & ev,
         size_t num_events
     );
 
     //! Gives all StatusCodes of the last reweight()
     /**
      * Each StatusCode corresponds to one tried generation. Only good
      * StatusCodes generated an event.
      */
     std::vector<StatusCode> const & status() const {
         return status_;
     }
 
   private:
     template<typename... T>
     PDF const & pdf(T&& ...);
 
     /** \internal
      * \brief main generation/reweighting function:
      * generate phase space points and divide out Born factors
      */
     std::vector<Event> gen_res_events(
         Event const & ev, size_t num_events
     );
     std::vector<Event> rescale(
         Event const & Born_ev, std::vector<Event> events
     ) const;
 
     /** \internal
      * \brief Do the Jets pass the resummation Cuts?
      *
      * @param ev               Event in Question
      * @returns                0 or 1 depending on if ev passes Jet Cuts
      */
     bool jets_pass_resummation_cuts(Event const & ev) const;
 
     /** \internal
      * \brief pdf_factors Function
      *
      * @param ev         Event in Question
      * @returns          EventFactor due to PDFs
      *
      * Calculates the Central value and the variation due
      * to the PDF choice made.
      */
     Weights pdf_factors(Event const & ev) const;
 
     /** \internal
      * \brief matrix_elements Function
      *
      * @param ev         Event in question
      * @returns          EventFactor due to MatrixElements
      *
      * Calculates the Central value and the variation due
      * to the Matrix Element.
      */
     Weights matrix_elements(Event const & ev) const;
 
     /** \internal
      * \brief Scale-dependent part of fixed-order matrix element
      *
      * @param ev         Event in question
      * @returns          EventFactor scale variation due to FO-ME.
      *
      * This is only called to compute the scale variation for events where
      * we don't do resummation (e.g. non-FKL).
      * Since at tree level the scale dependence is just due to alpha_s,
      * it is enough to return the alpha_s(mur) factors in the matrix element.
      * The rest drops out in the ratio of (output event ME)/(input event ME),
      * so we never have to compute it.
      */
     Weights fixed_order_scale_ME(Event const & ev) const;
 
     /** \internal
      * \brief Computes the tree level matrix element
      *
      * @param ev                Event in Question
      * @returns                 HEJ approximation to Tree level Matrix Element
      *
      * This computes the HEJ approximation to the tree level FO
      * Matrix element which is used within the LO weighting process.
      */
     double tree_matrix_element(Event const & ev) const;
 
     //! \internal General parameters
     EventReweighterConfig param_;
 
     //! \internal Beam energy
     double E_beam_;
 
     //! \internal PDF
     PDF pdf_;
 
     //! \internal Object to calculate the square of the matrix element
     MatrixElement MEt2_;
     //! \internal Object to calculate event renormalisation and factorisation scales
     ScaleGenerator scale_gen_;
     //! \internal random number generator
     std::shared_ptr<RNG> ran_;
     //! \internal StatusCode of each attempt
     std::vector<StatusCode> status_;
   };
 
   template<typename... T>
   PDF const & EventReweighter::pdf(T&&... t){
     return pdf_ = PDF{std::forward<T>(t)...};
   }
 
 } // namespace HEJ
diff --git a/include/HEJ/HDF5Reader.hh b/include/HEJ/HDF5Reader.hh
index 66efc75..5e8a29b 100644
--- a/include/HEJ/HDF5Reader.hh
+++ b/include/HEJ/HDF5Reader.hh
@@ -1,50 +1,50 @@
 /** \file
  *  \brief Header file for reading events in the HDF5 event format.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "HEJ/EventReader.hh"
 
 namespace HEJ{
 
   //! Class for reading events from a file in the HDF5 file format
   /**
    * @details This format is specified in \cite Hoeche:2019rti.
    */
   class HDF5Reader : public EventReader{
   public:
     HDF5Reader() = delete;
 
     //! Contruct object reading from the given file
     explicit HDF5Reader(std::string const & filename);
 
     //! Read an event
     bool read_event() override;
 
     //! Access header text
     std::string const & header() const override;
 
     //! Access run information
     LHEF::HEPRUP const & heprup() const override;
 
     //! Access last read event
     LHEF::HEPEUP const & hepeup() const override;
 
     //! Get number of events
     HEJ::optional<size_t> number_events() const override;
 
     ~HDF5Reader() override;
 
   private:
     struct HDF5ReaderImpl;
 
     std::unique_ptr<HDF5ReaderImpl> impl_;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/HDF5Writer.hh b/include/HEJ/HDF5Writer.hh
index f74464c..917eed2 100644
--- a/include/HEJ/HDF5Writer.hh
+++ b/include/HEJ/HDF5Writer.hh
@@ -1,54 +1,54 @@
 /** \file
  *  \brief Contains the EventWriter for HDF5 Output.
  *
  *  The output format is specified in arXiv:1905.05120.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 
 #include "HEJ/EventWriter.hh"
 
 namespace LHEF {
   class HEPRUP;
 }
 
 namespace HEJ {
   class Event;
 
   //! This is an event writer specifically for HDF5 output.
   /**
    * \internal Implementation note: This uses the pimpl ("pointer to
    * implementation") idiom. HDF5 support is optional. Without pimpl,
    * we would have to specify whether HDF5 is available via the
    * preprocessor whenever this header is included. We don't want to
    * burden users of the HEJ library (for example the HEJ fixed-order
    * generator) with those details
    */
   class HDF5Writer: public EventWriter{
   public:
     //! Constructor
     /**
      * @param file      name of the output file
      * @param heprup    general process information
      */
     HDF5Writer(std::string const & file, LHEF::HEPRUP heprup);
     HDF5Writer() = delete;
 
     //! Write an event to the output file
     void write(Event const & ev) override;
 
     ~HDF5Writer() override;
 
   private:
     struct HDF5WriterImpl;
 
     std::unique_ptr<HDF5WriterImpl> impl_;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/HepMC2Writer.hh b/include/HEJ/HepMC2Writer.hh
index 7f2deae..004c08a 100644
--- a/include/HEJ/HepMC2Writer.hh
+++ b/include/HEJ/HepMC2Writer.hh
@@ -1,53 +1,53 @@
 /** \file
  *  \brief Contains the EventWriter for HepMC Output.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 
 #include "HEJ/EventWriter.hh"
 
 namespace LHEF {
   class HEPRUP;
 }
 
 namespace HEJ {
   class Event;
 
   //! This is an event writer specifically for HepMC output.
   /**
    * \internal Implementation note:
    * This uses the pimpl ("pointer to implementation") idiom.
    * HepMC support is optional and the implementation depends on the
    * HepMC version. Without pimpl, we would have to specify the HepMC version
    * via the preprocessor whenever this header is included. We don't want to
    * burden users of the HEJ library (for example the HEJ fixed-order generator)
    * with those details
    */
   class HepMC2Writer: public EventWriter{
   public:
     //! Constructor
     /**
      * @param file      name of the output file
      * @param heprup    general process information
      */
     HepMC2Writer(std::string const & file, LHEF::HEPRUP heprup);
     HepMC2Writer() = delete;
 
     //! Write an event to the output file
     void write(Event const & ev) override;
 
     ~HepMC2Writer() override;
 
   private:
     struct HepMC2WriterImpl;
 
     std::unique_ptr<HepMC2WriterImpl> impl_;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/HepMC3Writer.hh b/include/HEJ/HepMC3Writer.hh
index e1d15bc..9fa6b53 100644
--- a/include/HEJ/HepMC3Writer.hh
+++ b/include/HEJ/HepMC3Writer.hh
@@ -1,52 +1,52 @@
 /** \file
  *  \brief Contains the EventWriter for HepMC3 Output.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 
 #include "HEJ/EventWriter.hh"
 
 namespace LHEF {
   class HEPRUP;
 }
 
 namespace HEJ {
   class Event;
 
   //! This is an event writer specifically for HepMC3 output.
   /**
    * \internal Implementation note:
    * This uses the pimpl ("pointer to implementation") idiom.
    * HepMC3 support is optional and the implementation depends on the
    * HepMC3 version. Without pimpl, we would have to specify the HepMC3 version
    * via the preprocessor whenever this header is included. We don't want to
    * burden users of the HEJ library (for example the HEJ fixed-order generator)
    * with those details
    */
   class HepMC3Writer: public EventWriter{
   public:
     //! Constructor
     /**
      * @param file      name of the output file
      * @param heprup    general process information
      */
     HepMC3Writer(std::string const & file, LHEF::HEPRUP heprup);
     HepMC3Writer() = delete;
 
     //! Write an event to the output file
     void write(Event const & ev) override;
 
     ~HepMC3Writer() override;
   private:
     struct HepMC3WriterImpl;
 
     std::unique_ptr<HepMC3WriterImpl> impl_;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/Hjets.hh b/include/HEJ/Hjets.hh
index 5be9257..0a6a3b5 100644
--- a/include/HEJ/Hjets.hh
+++ b/include/HEJ/Hjets.hh
@@ -1,403 +1,403 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 /** \file
  *  \brief Functions computing the square of current contractions in H+Jets.
  *
  *  This file contains all the H+Jet specific components to compute
  *  the current contractions for valid HEJ processes, to form a full
  *  H+Jets ME, currently one would have to use functions from the
  *  jets.hh header also. We have FKL and also unordered components for
  *  H+Jets.
  *
  *  @TODO add a namespace
  */
 #pragma once
 
 #include "CLHEP/Vector/LorentzVector.h"
 
 typedef CLHEP::HepLorentzVector HLV;
 
 //! Square of gg->gg Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state gluon
  *  @param p1in             Momentum of initial state gluon
  *  @param p2out            Momentum of final state gluon
  *  @param p2in             Momentum of intial state gluon
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for gg->gg Scattering
  *
  *  g~p1 g~p2
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if g is backward, q1 is forward)
  */
 double ME_H_gg(HLV p1out, HLV p1in,
               HLV p2out, HLV p2in,
               HLV q1, HLV qH2,
               double mt,
               bool include_bottom, double mb, double vev);
 
 //! Square of gq->gq Higgs+Jets Scattering Current with Higgs before Gluon
 /**
  *  @param p1out            Momentum of final state gluon
  *  @param p1in             Momentum of initial state gluon
  *  @param p2out            Momentum of final state gluon
  *  @param p2in             Momentum of intial state gluon
  *  @param pH               Momentum of Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contraction
  */
 double ME_Houtside_gq(HLV p1out, HLV p1in,
                       HLV p2out, HLV p2in,
                       HLV pH,
                       double mt,
                       bool include_bottom, double mb, double vev);
 
 //! Square of qg->qg Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state quark
  *  @param p1in             Momentum of initial state quark
  *  @param p2out            Momentum of final state gluon
  *  @param p2in             Momentum of intial state gluon
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qg->qg Scattering
  *
  *  q~p1 g~p2 (i.e. ALWAYS p1 for quark, p2 for gluon)
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if g is backward, q1 is forward)
  */
 double ME_H_qg(HLV p1out, HLV p1in,
               HLV p2out, HLV p2in,
               HLV q1, HLV qH2,
               double mt,
               bool include_bottom, double mb, double vev);
 
 //! Square of qbarg->qbarg Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state anti-quark
  *  @param p1in             Momentum of initial state anti-quark
  *  @param p2out            Momentum of final state gluon
  *  @param p2in             Momentum of intial state gluon
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qbarg->qbarg Scattering
  *
  *  qbar~p1 g~p2 (i.e. ALWAYS p1 for anti-quark, p2 for gluon)
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if g is backward, q1 is forward)
  */
 double ME_H_qbarg(HLV p1out, HLV p1in,
                  HLV p2out, HLV p2in,
                  HLV q1, HLV qH2,
                  double mt,
                  bool include_bottom, double mb, double vev);
 
 //! Square of qQ->qQ Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state quark
  *  @param p1in             Momentum of initial state quark
  *  @param p2out            Momentum of final state quark
  *  @param p2in             Momentum of intial state quark
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qQ->qQ Scattering
  *
  *  q~p1 Q~p2 (i.e. ALWAYS p1 for quark, p2 for quark)
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if Q is backward, q1 is forward)
  */
 double ME_H_qQ(HLV p1out, HLV p1in,
               HLV p2out, HLV p2in,
               HLV q1, HLV qH2,
               double mt,
               bool include_bottom, double mb, double vev);
 
 //! Square of qQbar->qQbar Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state quark
  *  @param p1in             Momentum of initial state quark
  *  @param p2out            Momentum of final state anti-quark
  *  @param p2in             Momentum of intial state anti-quark
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qQ->qQ Scattering
  *
  *  q~p1 Qbar~p2 (i.e. ALWAYS p1 for quark, p2 for anti-quark)
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if Qbar is backward, q1 is forward)
  */
 double ME_H_qQbar(HLV p1out, HLV p1in,
                  HLV p2out, HLV p2in,
                  HLV q1, HLV qH2,
                  double mt,
                  bool include_bottom, double mb, double vev);
 
 //! Square of qbarQ->qbarQ Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state anti-quark
  *  @param p1in             Momentum of initial state anti-quark
  *  @param p2out            Momentum of final state quark
  *  @param p2in             Momentum of intial state quark
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qbarQ->qbarQ Scattering
  *
  *  qbar~p1 Q~p2 (i.e. ALWAYS p1 for anti-quark, p2 for quark)
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if Q is backward, q1 is forward)
  */
 double ME_H_qbarQ(HLV p1out, HLV p1in,
                  HLV p2out, HLV p2in,
                  HLV q1, HLV qH2,
                  double mt,
                  bool include_bottom, double mb, double vev);
 
 //! Square of qbarQbar->qbarQbar Higgs+Jets Scattering Current
 /**
  *  @param p1out            Momentum of final state anti-quark
  *  @param p1in             Momentum of initial state anti-quark
  *  @param p2out            Momentum of final state anti-quark
  *  @param p2in             Momentum of intial state anti-quark
  *  @param q1               Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qbarQbar->qbarQbar Scattering
  *
  *  qbar~p1 Qbar~p2 (i.e. ALWAYS p1 for anti-quark, p2 for anti-quark)
  *  should be called with q1 meant to be contracted with p2 in first part of vertex
  *  (i.e. if Qbar is backward, q1 is forward)
  */
 double ME_H_qbarQbar(HLV p1out, HLV p1in,
                     HLV p2out, HLV p2in,
                     HLV q1, HLV qH2,
                     double mt,
                     bool include_bottom, double mb, double vev);
 
 //! @name Unordered backwards
 //! @{
 
 //! Square of qbarQ->qbarQg Higgs+Jets Unordered b Scattering Current
 /**
  *  @param p1out            Momentum of final state anti-quark
  *  @param p1in             Momentum of initial state anti-quark
  *  @param pg               Momentum of unordered b gluon
  *  @param p2out            Momentum of final state quark
  *  @param p2in             Momentum of intial state quark
  *  @param qH1              Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qbarQ->qbarQg Scattering
  *
  *  This construction is taking rapidity order: p1out >> p2out > pg
  */
 double ME_H_unob_qbarQ(HLV p1out, HLV p1in,
                        HLV pg, HLV p2out,
                        HLV p2in, HLV qH1,
                        HLV qH2,
                        double mt,
                        bool include_bottom, double mb, double vev);
 
 //! Square of qQ->qQg Higgs+Jets Unordered b Scattering Current
 /**
  *  @param p1out            Momentum of final state quark
  *  @param p1in             Momentum of initial state quark
  *  @param pg               Momentum of unordered b gluon
  *  @param p2out            Momentum of final state quark
  *  @param p2in             Momentum of intial state quark
  *  @param qH1              Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qQ->qQg Scattering
  *
  *  This construction is taking rapidity order: p1out >> p2out > pg
  */
 double ME_H_unob_qQ(HLV p1out, HLV p1in,
                     HLV pg, HLV p2out,
                     HLV p2in, HLV qH1,
                     HLV qH2,
                     double mt,
                     bool include_bottom, double mb, double vev);
 
 //! Square of qQbar->qQbarg Higgs+Jets Unordered b Scattering Current
 /**
  *  @param p1out            Momentum of final state quark
  *  @param p1in             Momentum of initial state quark
  *  @param pg               Momentum of unordered b gluon
  *  @param p2out            Momentum of final state anti-quark
  *  @param p2in             Momentum of intial state anti-quark
  *  @param qH1              Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qQbar->qQbarg Scattering
  *
  *  This construction is taking rapidity order: p1out >> p2out > pg
  */
 double ME_H_unob_qQbar(HLV p1out, HLV p1in,
                        HLV pg, HLV p2out,
                        HLV p2in, HLV qH1,
                        HLV qH2,
                        double mt,
                        bool include_bottom, double mb, double vev);
 
 //! Square of qbarQbar->qbarQbarg Higgs+Jets Unordered b Scattering Current
 /**
  *  @param p1out            Momentum of final state anti-quark
  *  @param p1in             Momentum of initial state anti-quark
  *  @param pg               Momentum of unordered b gluon
  *  @param p2out            Momentum of final state anti-quark
  *  @param p2in             Momentum of intial state anti-quark
  *  @param qH1              Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for qbarQbar->qbarQbarg Scattering
  *
  *  This construction is taking rapidity order: p1out >> p2out > pg
  */
 
 double ME_H_unob_qbarQbar(HLV p1out, HLV p1in,
                           HLV pg, HLV p2out,
                           HLV p2in, HLV qH1,
                           HLV qH2,
                           double mt,
                           bool include_bottom, double mb, double vev);
 
 //! Square of gQbar->gQbarg Higgs+Jets Unordered b Scattering Current
 /**
  *  @param p1out            Momentum of final state gluon
  *  @param p1in             Momentum of initial state gluon
  *  @param pg               Momentum of unordered b gluon
  *  @param p2out            Momentum of final state anti-quark
  *  @param p2in             Momentum of intial state anti-quark
  *  @param qH1              Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for gQbar->gQbarg Scattering
  *
  *  This construction is taking rapidity order: p1out >> p2out > pg
  */
 double ME_H_unob_gQbar(HLV p1out, HLV p1in,
                        HLV pg, HLV p2out,
                        HLV p2in, HLV qH1,
                        HLV qH2,
                        double mt,
                        bool include_bottom, double mb, double vev);
 
 //! Square of gQ->gQg Higgs+Jets Unordered b Scattering Current
 /**
  *  @param p1out            Momentum of final state gluon
  *  @param p1in             Momentum of initial state gluon
  *  @param pg               Momentum of unordered b gluon
  *  @param p2out            Momentum of final state quark
  *  @param p2in             Momentum of intial state quark
  *  @param qH1              Momentum of t-channel propagator before Higgs
  *  @param qH2              Momentum of t-channel propagator after Higgs
  *  @param mt               Top quark mass
  *  @param include_bottom   Specifies whether bottom corrections are included
  *  @param mb               Bottom quark mass
  *  @param vev              Vacuum expectation value
  *  @returns                Square of the current contractions for gQ->gQg Scattering
  *
  *  This construction is taking rapidity order: p1out >> p2out > pg
  */
 double ME_H_unob_gQ(HLV p1out, HLV p1in,
                     HLV pg, HLV p2out,
                     HLV p2in, HLV qH1,
                     HLV qH2,
                     double mt,
                     bool include_bottom, double mb, double vev);
 
 //! @}
 //! @name impact factors for Higgs + jet
 //! @{
 
 //! Implements Eq. (4.22) in \cite DelDuca:2003ba with modifications to incoming plus momenta
 /**
  * @param p2               Momentum of Particle 2
  * @param p1               Momentum of Particle 1
  * @param pH               Momentum of Higgs
  * @param vev              Vacuum expectation value
  * @returns                Value of Eq. (4.22) in \cite DelDuca:2003ba with modifications
  *
  *  This gives the impact factor. First it determines whether this is the
  *  case \f$p1p\sim php\gg p3p\f$ or the opposite
  */
 double C2gHgm(HLV p2, HLV p1, HLV pH, double vev);
 
 //! Implements Eq. (4.23) in \cite DelDuca:2003ba with modifications to incoming plus momenta
 /**
  * @param p2               Momentum of Particle 2
  * @param p1               Momentum of Particle 1
  * @param pH               Momentum of Higgs
  * @param vev              Vacuum expectation value
  * @returns                Value of Eq. (4.23) in \cite DelDuca:2003ba
  *
  *  This gives the impact factor. First it determines whether this is the
  *  case \f$p1p\sim php\gg p3p\f$ or the opposite
  */
 double C2gHgp(HLV p2, HLV p1, HLV pH, double vev);
 
 //! Implements Eq. (4.21) in \cite DelDuca:2003ba
 /**
  * @param p2               Momentum of Particle 2
  * @param p1               Momentum of Particle 1
  * @param pH               Momentum of Higgs
  * @param vev              Vacuum expectation value
  * @returns                Value of Eq. (4.22) in \cite DelDuca:2003ba
  *
  *  This gives the impact factor. First it determines whether this is the
  *  case \f$p1p\sim php\gg p3p\f$ or the opposite
  *
  *  @TODO remove this function is not used
  */
 double C2qHqm(HLV p2, HLV p1, HLV pH, double vev);
 
 //! @}
diff --git a/include/HEJ/JetSplitter.hh b/include/HEJ/JetSplitter.hh
index 2587de5..69a2985 100644
--- a/include/HEJ/JetSplitter.hh
+++ b/include/HEJ/JetSplitter.hh
@@ -1,71 +1,71 @@
 /**
  * \file
  * \brief Declaration of the JetSplitter class
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <vector>
 
 #include "fastjet/JetDefinition.hh"
 
 namespace fastjet {
   class PseudoJet;
 }
 
 namespace HEJ {
   class RNG;
 
   //! Class to split jets into their constituents
   class JetSplitter {
   public:
     //! Wrapper for return values
     struct SplitResult {
       std::vector<fastjet::PseudoJet> constituents;
       double weight;
     };
 
     //! Constructor
     /**
      * @param jet_def   Jet definition
      * @param min_pt    Minimum jet transverse momentum
      */
     JetSplitter(fastjet::JetDefinition jet_def, double min_pt);
 
     //! Split a get into constituents
     /**
      * @param j2split   Jet to be split
      * @param ncons     Number of constituents
      * @param ran       Random number generator
      * @returns         The constituent momenta together with the associated
      *                  weight
      */
     SplitResult split(
         fastjet::PseudoJet const & j2split, int ncons, RNG & ran
     ) const;
 
     //! Maximum distance of constituents to jet axis
     static constexpr double R_factor = 5./3.;
 
   private:
     //! \internal split jet into two partons
     SplitResult Split2(fastjet::PseudoJet const & j2split, RNG & ran) const;
 
     /** \internal
      * @brief sample y-phi distance to jet pt axis for a jet splitting into two
      *        partons
      *
      * @param wt    Multiplied by the weight of the sampling point
      * @param ran   Random number generator
      * @returns     The distance in units of the jet radius
      */
     double sample_distance_2p(double & wt, RNG & ran) const;
 
     double min_jet_pt_;
     fastjet::JetDefinition jet_def_;
   };
 } // namespace HEJ
diff --git a/include/HEJ/LesHouchesReader.hh b/include/HEJ/LesHouchesReader.hh
index 2439b75..83732d3 100644
--- a/include/HEJ/LesHouchesReader.hh
+++ b/include/HEJ/LesHouchesReader.hh
@@ -1,83 +1,83 @@
 /** \file
  *  \brief Header file for reading events in the Les Houches Event File format.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Event.hh"
 #include "HEJ/EventReader.hh"
 #include "HEJ/stream.hh"
 
 namespace HEJ{
 
   //! Class for reading events from a file in the Les Houches Event File format
   class LesHouchesReader : public EventReader{
   public:
     //! Contruct object reading from the given file
     explicit LesHouchesReader(std::string const & filename):
       stream_{filename},
       reader_{stream_}
     {
       // always use the newest LHE version
       reader_.heprup.version = LHEF::HEPRUP().version;
     }
 
     //! Read an event
     bool read_event() override {
       return reader_.readEvent();
     }
 
     //! Access header text
     std::string const & header() const override {
       return reader_.headerBlock;
     }
 
     //! Access run information
     LHEF::HEPRUP const & heprup() const override {
       return reader_.heprup;
     }
 
     //! Access last read event
     LHEF::HEPEUP const & hepeup() const override {
       return reader_.hepeup;
     }
 
   private:
     HEJ::istream stream_;
 
   protected:
     //! Underlying reader
     LHEF::Reader reader_;
   };
 
   /**
    * @brief   Les Houches Event file reader for LHE files created by Sherpa
    * @details In Sherpa the cross section is given by
    *          sum(weights)/(number of trials). This EventReader converts the
    *          weights such that cross section=sum(weights)
    *  @note   Reading from a pipe is not possible!
    */
   class SherpaLHEReader : public LesHouchesReader{
   public:
     //! Inialise Reader for a Sherpa LHE file
     explicit SherpaLHEReader(std::string const & filename);
 
     bool read_event() override;
 
     HEJ::optional<size_t> number_events() const override {
       return num_events;
     }
 
   private:
     double num_trials;
     size_t num_events;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/LorentzVector.hh b/include/HEJ/LorentzVector.hh
index c3b1bdb..7f460f8 100644
--- a/include/HEJ/LorentzVector.hh
+++ b/include/HEJ/LorentzVector.hh
@@ -1,55 +1,55 @@
 /** \file
  *  \brief Auxiliary functions for Lorentz vectors
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <complex>
 
 #include "CLHEP/Vector/LorentzVector.h"
 
 namespace HEJ {
   //! "dot" product
   inline
   auto dot(
     CLHEP::HepLorentzVector const & pi,
     CLHEP::HepLorentzVector const & pj
   ) {
     return pi.dot(pj);
   }
 
   //! "angle" product angle(pi, pj) = \<i j\>
   std::complex<double> angle(
     CLHEP::HepLorentzVector const & pi,
     CLHEP::HepLorentzVector const & pj
   );
 
   //! "square" product square(pi, pj) = [i j]
   std::complex<double> square(
     CLHEP::HepLorentzVector const & pi,
     CLHEP::HepLorentzVector const & pj
   );
 
   //! Invariant mass
   inline
   auto m2(CLHEP::HepLorentzVector const & h1) {
     return h1.m2();
   }
 
   //! Split a single Lorentz vector into two lightlike Lorentz vectors
   /**
    *  @param P   Lorentz vector to be split
    *  @returns   A pair (p, q) of Lorentz vectors with P = p + q and p^2 = q^2 = 0
    *
    *  P.perp() has to be positive.
    *
    *  p.e() is guaranteed to be positive.
    *  In addition, if either of P.plus() or P.minus() is positive,
    *  q.e() has the same sign as P.m2()
    */
   std::pair<CLHEP::HepLorentzVector, CLHEP::HepLorentzVector>
   split_into_lightlike(CLHEP::HepLorentzVector const & P);
 }
diff --git a/include/HEJ/Mixmax.hh b/include/HEJ/Mixmax.hh
index 03d229f..029677e 100644
--- a/include/HEJ/Mixmax.hh
+++ b/include/HEJ/Mixmax.hh
@@ -1,36 +1,36 @@
 /** \file
  * \brief The Mixmax random number generator
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include "CLHEP/Random/MixMaxRng.h"
 #include "CLHEP/Random/Randomize.h"
 
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
 
   //! MIXMAX random number generator
   /**
    *  For details on MIXMAX, see \cite Savvidy:2014ana
    */
   class Mixmax : public DefaultRNG {
   public:
     Mixmax() = default;
     //! Constructor with explicit seed
     Mixmax(long seed): ran_{seed} {}
 
     //! Generate pseudorandom number between 0 and 1
     double flat() override {
       return ran_.flat();
     }
 
   private:
     CLHEP::MixMaxRng ran_;
   };
 
 }
diff --git a/include/HEJ/Parameters.hh b/include/HEJ/Parameters.hh
index 84a640a..d10bed3 100644
--- a/include/HEJ/Parameters.hh
+++ b/include/HEJ/Parameters.hh
@@ -1,164 +1,164 @@
 /** \file
  *  \brief Containers for Parameter variations, e.g. different Weights
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "HEJ/exceptions.hh"
 
 namespace HEJ{
   //! Collection of parameters, e.g. Weights, assigned to a single event
   /**
    * A number of member functions of the MatrixElement class return Parameters
    * objects containing the squares of the matrix elements for the various
    * scale choices.
    */
   template<class T>
   struct Parameters {
     T central;
     std::vector<T> variations;
 
     template<class T_ext>
     Parameters<T>& operator*=(Parameters<T_ext> const & other);
     Parameters<T>& operator*=(double factor);
     template<class T_ext>
     Parameters<T>& operator/=(Parameters<T_ext> const & other);
     Parameters<T>& operator/=(double factor);
   };
 
   template<class T1, class T2> inline
   Parameters<T1> operator*(Parameters<T1> a, Parameters<T2> const & b) {
     a*=b;
     return a;
   }
   template<class T> inline
   Parameters<T> operator*(Parameters<T> a, double b) {
     a*=b;
     return a;
   }
   template<class T> inline
   Parameters<T> operator*(double b, Parameters<T> a) {
     a*=b;
     return a;
   }
   template<class T1, class T2> inline
   Parameters<T1> operator/(Parameters<T1> a, Parameters<T2> const & b) {
     a/=b;
     return a;
   }
   template<class T> inline
   Parameters<T> operator/(Parameters<T> a, double b) {
     a/=b;
     return a;
   }
 
   //! Alias for weight container, e.g. used by the MatrixElement
   using Weights = Parameters<double>;
 
   //! Description of event parameters, see also EventParameters
   struct ParameterDescription {
     //! Name of central scale choice (e.g. "H_T/2")
     std::string scale_name;
     //! Actual renormalisation scale divided by central scale
     double mur_factor;
     //! Actual factorisation scale divided by central scale
     double muf_factor;
 
     ParameterDescription() = default;
     ParameterDescription(
       std::string scale_name, double mur_factor, double muf_factor
     ):
       scale_name{std::move(scale_name)},
       mur_factor{mur_factor}, muf_factor{muf_factor}
     {}
   };
 
   //! generate human readable string name
   std::string to_string(ParameterDescription const & p);
 
   //! generate simplified string, intended for easy parsing
   //! Format: Scale_SCALENAME_MuRxx_MuFyy
   std::string to_simple_string(ParameterDescription const & p);
 
   //! Event parameters
   struct EventParameters{
     double mur;              /**< Value of the Renormalisation Scale */
     double muf;              /**< Value of the Factorisation Scale */
     double weight;           /**< Event Weight */
     //! Optional description
     std::shared_ptr<ParameterDescription> description = nullptr;
 
     //! multiply weight by factor
     EventParameters& operator*=(double factor){
       weight*=factor;
       return *this;
     }
     //! divide weight by factor
     EventParameters& operator/=(double factor){
       weight/=factor;
       return *this;
     }
   };
   inline EventParameters operator*(EventParameters a, double b){
     a*=b;
     return a;
   }
   inline EventParameters operator*(double b, EventParameters a){
     a*=b;
     return a;
   }
   inline EventParameters operator/(EventParameters a, double b){
     a/=b;
     return a;
   }
 
   //! @{
   //! @internal Implementation of template functions
   template<class T>
   template<class T_ext>
   Parameters<T>& Parameters<T>::operator*=(Parameters<T_ext> const & other) {
     if(other.variations.size() != variations.size()) {
       throw std::invalid_argument{"Wrong number of Parameters"};
     }
     central *= other.central;
     for(std::size_t i = 0; i < variations.size(); ++i) {
       variations[i] *= other.variations[i];
     }
     return *this;
   }
 
   template<class T>
   Parameters<T>& Parameters<T>::operator*=(double factor) {
     central *= factor;
     for(auto & wt: variations) wt *= factor;
     return *this;
   }
 
   template<class T>
   template<class T_ext>
   Parameters<T>& Parameters<T>::operator/=(Parameters<T_ext> const & other) {
     if(other.variations.size() != variations.size()) {
       throw std::invalid_argument{"Wrong number of Parameters"};
     }
     central /= other.central;
     for(std::size_t i = 0; i < variations.size(); ++i) {
       variations[i] /= other.variations[i];
     }
     return *this;
   }
 
   template<class T>
   Parameters<T>& Parameters<T>::operator/=(double factor) {
     central /= factor;
     for(auto & wt: variations) wt /= factor;
     return *this;
   }
   //! @}
 } // namespace HEJ
diff --git a/include/HEJ/PhaseSpacePoint.hh b/include/HEJ/PhaseSpacePoint.hh
index fb42718..0359e2b 100644
--- a/include/HEJ/PhaseSpacePoint.hh
+++ b/include/HEJ/PhaseSpacePoint.hh
@@ -1,202 +1,202 @@
 /** \file
  *  \brief Contains the PhaseSpacePoint Class
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <unordered_map>
 #include <vector>
 
 #include "HEJ/Config.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/StatusCode.hh"
 
 namespace HEJ{
   class RNG;
 
   //! Generated point in resummation phase space
   class PhaseSpacePoint{
   public:
     //! No default PhaseSpacePoint Constructor
     PhaseSpacePoint() = delete;
 
     //! PhaseSpacePoint Constructor
     /**
      * @param ev               Clustered Jet Event
      * @param conf             Configuration parameters
      * @param ran              Random number generator
      */
     PhaseSpacePoint(
         Event const & ev,
         PhaseSpacePointConfig conf,
         RNG & ran
     );
 
     //! Get phase space point weight
     double weight() const{
       return weight_;
     }
 
     //! Access incoming particles
     std::array<Particle, 2> const & incoming() const{
       return incoming_;
     }
 
     //! Access outgoing particles
     std::vector<Particle> const & outgoing() const{
       return outgoing_;
     }
 
     //! Particle decays
     /**
      *  The key in the returned map corresponds to the index in the
      *  vector returned by outgoing()
      */
     std::unordered_map<size_t, std::vector<Particle>> const &  decays() const{
       return decays_;
     }
 
     //! Status code of generation
     StatusCode status() const{
         return status_;
     }
 
     static constexpr int ng_max = 1000;    //!< maximum number of extra gluons
 
   private:
     friend Event::EventData to_EventData(PhaseSpacePoint psp);
     //! /internal returns the clustered jets sorted in rapidity
     std::vector<fastjet::PseudoJet> cluster_jets(
         std::vector<fastjet::PseudoJet> const & partons
     ) const;
     bool pass_resummation_cuts(
         std::vector<fastjet::PseudoJet> const & jets
     ) const;
     bool pass_extremal_cuts(
         fastjet::PseudoJet const & ext_parton,
         fastjet::PseudoJet const & jet
     ) const;
     int sample_ng(std::vector<fastjet::PseudoJet> const & Born_jets, RNG & ran);
     int sample_ng_jets(
       int ng, std::vector<fastjet::PseudoJet> const & Born_jets, RNG & ran
     );
     double probability_in_jet(
         std::vector<fastjet::PseudoJet> const & Born_jets
     ) const;
     std::vector<fastjet::PseudoJet> gen_non_jet(
         int ng_non_jet,
         double ptmin, double ptmax,
         RNG & ran
     );
     void rescale_qqx_rapidities(
       std::vector<fastjet::PseudoJet> & out_partons,
       std::vector<fastjet::PseudoJet> const & jets,
       const double ymin1, const double ymax2,
       const int qqxbackjet
     );
     void rescale_rapidities(
       std::vector<fastjet::PseudoJet> & partons,
       double ymin, double ymax
     );
     std::vector<fastjet::PseudoJet> reshuffle(
         std::vector<fastjet::PseudoJet> const & Born_jets,
         fastjet::PseudoJet const & q
     );
     /** \interal final jet test
      *   - number of jets must match Born kinematics
      *   - no partons designated as nonjet may end up inside jets
      *   - all other outgoing partons *must* end up inside jets
      *   - the extremal (in rapidity) partons must be inside the extremal jets
      *   - rapidities must be the same (by construction)
      */
     bool jets_ok(
         std::vector<fastjet::PseudoJet> const & Born_jets,
         std::vector<fastjet::PseudoJet> const & partons
     ) const;
     void reconstruct_incoming(std::array<Particle, 2> const & Born_incoming);
     double phase_space_normalisation(
         int num_Born_jets,
         int num_res_partons
     ) const;
     /** \interal Distribute gluon in jet
      * @param jets        jets to distribute gluon in
      * @param ng_jets     number of gluons
      * @param qqxbackjet  position of first (backwards) qqx jet
      *
      * relies on JetSplitter
      */
     std::vector<fastjet::PseudoJet> split(
       std::vector<fastjet::PseudoJet> const & jets,
       int ng_jets, size_t qqxbackjet, RNG & ran
     );
     std::vector<int> distribute_jet_partons(
         int ng_jets, std::vector<fastjet::PseudoJet> const & jets, RNG & ran
     );
     std::vector<fastjet::PseudoJet> split(
         std::vector<fastjet::PseudoJet> const & jets,
         std::vector<int> const & np_in_jet,
         size_t qqxbackjet,
         RNG & ran
     );
     bool split_preserved_jets(
         std::vector<fastjet::PseudoJet> const & jets,
         std::vector<fastjet::PseudoJet> const & jet_partons
     ) const;
     template<class Particle>
     Particle const & most_backward_FKL(
         std::vector<Particle> const & partons
     ) const;
     template<class Particle>
     Particle const & most_forward_FKL(
         std::vector<Particle> const & partons
     ) const;
     template<class Particle>
     Particle & most_backward_FKL(std::vector<Particle> & partons) const;
     template<class Particle>
     Particle & most_forward_FKL(std::vector<Particle> & partons) const;
     bool extremal_ok(
         std::vector<fastjet::PseudoJet> const & partons
     ) const;
     /** \internal
      * Assigns PDG IDs to outgoing partons, i.e. labels them as quarks
      *
      * \note This function assumes outgoing_ to be pure partonic when called,
      *       i.e.  A/W/Z/h bosons should _not be set_ at this stage
      */
     void label_quarks(Event const & event);
     /** \internal
      * This function will label the qqx pair in a qqx event back to their
      * original types from the input event.
      */
     void label_qqx(Event const & event);
     void copy_AWZH_boson_from(Event const & event);
 
     bool momentum_conserved() const;
 
     bool contains_idx(
         fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton
     ) const;
 
     bool unob_, unof_, qqxb_, qqxf_, qqxmid_;
 
     double weight_;
 
     PhaseSpacePointConfig param_;
 
     std::array<Particle, 2> incoming_;
     std::vector<Particle> outgoing_;
     //! \internal Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays_;
 
     StatusCode status_;
   };
 
   //! Extract Event::EventData from PhaseSpacePoint
   Event::EventData to_EventData(PhaseSpacePoint psp);
 
 } // namespace HEJ
diff --git a/include/HEJ/RNG.hh b/include/HEJ/RNG.hh
index bd19749..63e4ceb 100644
--- a/include/HEJ/RNG.hh
+++ b/include/HEJ/RNG.hh
@@ -1,48 +1,48 @@
 /** \file
  *  \brief Interface for pseudorandom number generators
  *
  *  We select our random number generator at runtime according to the
  *  configuration file. This interface guarantees that we can use all
  *  generators in the same way.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <limits>
 
 namespace HEJ {
 
   //! Interface for random number generator
   struct RNG {
     //! Random number type, see std::RandomNumberDistribution
     using result_type = unsigned;
 
     //! Generate random number in [0,1)
     virtual double flat() = 0;
 
     //! Minimum number that can be generated
     virtual result_type min() const = 0;
     //! Maximum number that can be generated
     virtual result_type max() const = 0;
     //! Generate random number in [min(), max()]
     virtual result_type operator()() = 0;
 
     virtual ~RNG() = default;
   };
 
   //! Helper struct with default implementations
   struct DefaultRNG : virtual RNG {
     result_type min() const override {
       return 0u;
     }
     result_type max() const override {
       return std::numeric_limits<result_type>::max() - 1;
     }
     result_type operator()() override {
       return flat()*std::numeric_limits<result_type>::max();
     }
   };
 } // namespace HEJ
diff --git a/include/HEJ/Ranlux64.hh b/include/HEJ/Ranlux64.hh
index 50323d9..483ab07 100644
--- a/include/HEJ/Ranlux64.hh
+++ b/include/HEJ/Ranlux64.hh
@@ -1,35 +1,35 @@
 /** \file
  * \brief Contains a class for the ranlux64 random number generator
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "CLHEP/Random/Ranlux64Engine.h"
 
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
 
   //! Ranlux64 random number generator
   /**
    *  For details on ranlux64, see \cite Luscher:1993dy, \cite James:1993np
    */
   class Ranlux64 : public DefaultRNG {
   public:
     Ranlux64();
     //! Constructor with a file as seed
     Ranlux64(std::string const & seed_file);
 
     //! Generate pseudorandom number between 0 and 1
     double flat() override;
 
   private:
     CLHEP::Ranlux64Engine ran_;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/RivetAnalysis.hh b/include/HEJ/RivetAnalysis.hh
index 9570f92..9f28c78 100644
--- a/include/HEJ/RivetAnalysis.hh
+++ b/include/HEJ/RivetAnalysis.hh
@@ -1,70 +1,70 @@
 /** \file
  *  \brief HEJ 2 interface to rivet analyses
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Analysis.hh"
 #include "HEJ/optional.hh"
 
 namespace YAML {
   class Node;
 }
 
 namespace HEJ {
   /**
     * @brief Class representing a Rivet analysis
     *
     * This class inherits from Analysis and can therefore be used
     * like any other HEJ 2 analysis.
     */
   class RivetAnalysis: public HEJ::Analysis {
   public:
     //! Create RivetAnalysis
     static std::unique_ptr<Analysis> create(
         YAML::Node const & config, LHEF::HEPRUP const & heprup);
 
     //! Constructor
     /**
      *  @param config    Configuration parameters
      *  @param heprup    General run informations
      *
      * config["rivet"] should be the name of a single Rivet analysis or
      * a list of Rivet analyses. config["output"] is the prefix for
      * the .yoda output files.
      */
     RivetAnalysis(YAML::Node const & config, LHEF::HEPRUP const & heprup);
     ~RivetAnalysis() override;
     //! Pass an event to the underlying Rivet analysis
     void fill(HEJ::Event const & event, HEJ::Event const &) override;
     bool pass_cuts(HEJ::Event const &, HEJ::Event const &) override
        {return true;} //!< no additional cuts are applied
     void finalise() override;
 
   private:
     std::vector<std::string> analyses_names_;
     std::string output_name_;
     LHEF::HEPRUP heprup_;
 
     /// struct to organise the infos per rivet run/scale setting
     struct rivet_info;
     std::vector<rivet_info> rivet_runs_;
 
     /**
      *  \internal
      * @brief Calculates the scale variation from the first event for the output
      *        file
      */
     void init(HEJ::Event const & event);
     bool first_event_;
   };
 } // namespace HEJ
diff --git a/include/HEJ/StatusCode.hh b/include/HEJ/StatusCode.hh
index 03dd94d..bfeff84 100644
--- a/include/HEJ/StatusCode.hh
+++ b/include/HEJ/StatusCode.hh
@@ -1,47 +1,47 @@
 /** \file
  *  \brief Header file for status codes of event generation
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "HEJ/exceptions.hh"
 
 namespace HEJ {
   //! Possible status codes from the event generation
   enum StatusCode{
     good,
     discard,
     empty_jets,
     failed_reshuffle,
     failed_resummation_cuts,
     failed_split,
     too_much_energy,
     gluon_in_qqx,
     wrong_jets,
     unspecified // should never appear
   };
 
   //! Get name of StatusCode
   //! @TODO better names
   inline std::string to_string(StatusCode s){
     switch(s){
       case good:                    return "good";
       case discard:                 return "discard";
       case empty_jets:              return "empty jets";
       case failed_reshuffle:        return "failed reshuffle";
       case failed_resummation_cuts: return "below cuts";
       case failed_split:            return "failed split";
       case too_much_energy:         return "too much energy";
       case gluon_in_qqx:            return "gluon inside qqx";
       case wrong_jets:              return "wrong jets";
       case unspecified:             return "unspecified";
       default:{}
     }
     throw std::logic_error{"unreachable"};
   }
 } // namespace HEJ
diff --git a/include/HEJ/Unweighter.hh b/include/HEJ/Unweighter.hh
index 1fea8f6..67f5ce6 100644
--- a/include/HEJ/Unweighter.hh
+++ b/include/HEJ/Unweighter.hh
@@ -1,84 +1,84 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <functional>
 #include <vector>
 
 #include "HEJ/optional.hh"
 
 namespace HEJ {
   class Event;
   class RNG;
 
   /**
    * @brief Unweight events
    * @details Throws away events below with abs(weight)<cut with probability
    *          wt/cut
    */
   class Unweighter {
   public:
     //! Constructor
     //! @param cut Initial value of cut, negative values for no cut
     Unweighter(double cut = -1.):
       cut_{cut}{}
 
     //! Explicitly set cut
     void set_cut(double max_weight){
       cut_ = max_weight;
     }
     //! Set cut as max(weight) of events
     void set_cut_to_maxwt(std::vector<Event> const & events){
       set_cut_to_maxwt(events.cbegin(), events.cend());
     }
     //! Iterator version of set_max()
     template<class ConstIt>
     void set_cut_to_maxwt(ConstIt begin, ConstIt end);
 
     //! Estimate some reasonable cut for partial unweighting
     /**
      * @param events            Events used for the estimation
      * @param max_dev           Standard derivation to include above mean weight
      */
     void set_cut_to_peakwt(std::vector<Event> const & events, double max_dev){
       set_cut_to_peakwt(events.cbegin(), events.cend(), max_dev);
     }
     //! Iterator version of set_cut_to_peakwt()
     template<class ConstIt>
     void set_cut_to_peakwt(ConstIt begin, ConstIt end, double max_dev);
 
     //! Returns current value of the cut
     double get_cut() const {
       return cut_;
     }
 
     //! Unweight one event, returns original event if weight > get_cut()
     optional<Event> unweight(Event ev, RNG & ran) const;
     //! Unweight for multiple events at once
     std::vector<Event> unweight(
       std::vector<Event> events, RNG & ran
     ) const;
     //! @brief Iterator implementation of unweight(),
     /**
      * Usage similar to std::remove(), i.e. use with erase()
      *
      * @return Beginning of "discarded" range
      */
     template<class Iterator>
     Iterator unweight(
       Iterator begin, Iterator end, RNG & ran
     ) const;
 
   private:
     double cut_;
     //! Returns true if element can be removed/gets discarded
     //! directly corrects weight if is accepted (not removed)
     bool discard(RNG & ran, Event & ev) const;
   };
 } // namespace HEJ
 
 // implementation of template functions
 #include "HEJ/detail/Unweighter_impl.hh"
diff --git a/include/HEJ/YAMLreader.hh b/include/HEJ/YAMLreader.hh
index c4b5641..1bb75d8 100644
--- a/include/HEJ/YAMLreader.hh
+++ b/include/HEJ/YAMLreader.hh
@@ -1,267 +1,267 @@
 /** \file
  *  \brief The file which handles the configuration file parameters
  *
  *  The configuration files parameters are read and then stored
  *  within this objects.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 #include <utility>
 #include <vector>
 
 #include "yaml-cpp/yaml.h"
 
 #include "fastjet/JetDefinition.hh"
 
 #include "HEJ/Config.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/Fraction.hh"
 #include "HEJ/optional.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/utility.hh"
 
 namespace HEJ{
   class OutputFile;
   //! Load configuration from file
   /**
    *  @param config_file   Name of the YAML configuration file
    *  @returns             The HEJ 2 configuration
    */
   Config load_config(std::string const & config_file);
 
   //! Set option using the corresponding YAML entry
   /**
    *  @param setting      Option variable to be set
    *  @param yaml         Root of the YAML configuration
    *  @param names        Name of the entry
    *
    *  If the entry does not exist or has the wrong type or format
    *  an exception is thrown.
    *
    *  For example
    *  @code
    *  set_from_yaml(foobar, yaml, "foo", "bar")
    *  @endcode
    *  is equivalent to
    *  @code
    *  foobar = yaml["foo"]["bar"].as<decltype(foobar)>()
    *  @endcode
    *  with improved diagnostics on errors.
    *
    * @see set_from_yaml_if_defined
    */
   template<typename T, typename... YamlNames>
   void set_from_yaml(
       T & setting,
       YAML::Node const & yaml, YamlNames const & ... names
   );
 
   //! Set option using the corresponding YAML entry, if present
   /**
    *  @param setting      Option variable to be set
    *  @param yaml         Root of the YAML configuration
    *  @param names        Name of the entry
    *
    *  This function works similar to set_from_yaml, but does not
    *  throw any exception if the requested YAML entry does not exist.
    *
    *  @see set_from_yaml
    */
   template<typename T, typename... YamlNames>
   void set_from_yaml_if_defined(
       T & setting,
       YAML::Node const & yaml, YamlNames const & ... names
   );
 
   //! Extract jet parameters from YAML configuration
   JetParameters get_jet_parameters(
       YAML::Node const & node, std::string const & entry
   );
 
   //! Extract Higgs coupling settings from YAML configuration
   HiggsCouplingSettings get_Higgs_coupling(
       YAML::Node const & node, std::string const & entry
   );
 
   //! Extract scale setting parameters from YAML configuration
   ScaleConfig to_ScaleConfig(YAML::Node const & yaml);
 
   //! Extract random number generator settings from YAML configuration
   RNGConfig to_RNGConfig(YAML::Node const & node, std::string const & entry);
 
   //! Check whether all options in configuration are supported
   /**
    *  @param conf       Configuration to be checked
    *  @param supported  Tree of supported options
    *
    *  If conf contains an entry that does not appear in supported
    *  an unknown_option exception is thrown. Sub-entries of "analysis"
    *  (if present) are not checked.
    *
    *  @see unknown_option
    */
   void assert_all_options_known(
       YAML::Node const & conf, YAML::Node const & supported
   );
 
   namespace detail{
     void set_from_yaml(fastjet::JetAlgorithm & setting, YAML::Node const & yaml);
     void set_from_yaml(EventTreatment & setting, YAML::Node const & yaml);
     void set_from_yaml(ParticleID & setting, YAML::Node const & yaml);
     void set_from_yaml(OutputFile & setting, YAML::Node const & yaml);
     void set_from_yaml(WeightType & setting, YAML::Node const & yaml);
 
     inline
     void set_from_yaml(YAML::Node & setting, YAML::Node const & yaml){
       setting = yaml;
     }
 
     template<typename Scalar>
     void set_from_yaml(Scalar & setting, YAML::Node const & yaml){
       assert(yaml);
       if(!yaml.IsScalar()){
         throw invalid_type{"value is not a scalar"};
       }
       try{
         setting = yaml.as<Scalar>();
       }
       catch(...){
         throw invalid_type{
           "value " + yaml.as<std::string>()
           + " cannot be converted to a " + type_string(setting)
         };
       }
     }
 
     template<typename T>
     void set_from_yaml(optional<T> & setting, YAML::Node const & yaml){
       T tmp;
       set_from_yaml(tmp, yaml);
       setting = tmp;
     }
 
     template<typename T>
     void set_from_yaml(std::vector<T> & setting, YAML::Node const & yaml){
       assert(yaml);
       // special case: treat a single value like a vector with one element
       if(yaml.IsScalar()){
         setting.resize(1);
         return set_from_yaml(setting.front(), yaml);
       }
       if(yaml.IsSequence()){
         setting.resize(yaml.size());
         for(size_t i = 0; i < setting.size(); ++i){
           set_from_yaml(setting[i], yaml[i]);
         }
         return;
       }
       throw invalid_type{""};
     }
 
     template<typename T, typename FirstName, typename... YamlNames>
     void set_from_yaml(
         T & setting,
         YAML::Node const & yaml, FirstName const & name,
         YamlNames && ... names
     ){
       if(!yaml[name]) throw missing_option{""};
       set_from_yaml(
           setting,
           yaml[name], std::forward<YamlNames>(names)...
       );
     }
 
     template<typename T>
     void set_from_yaml_if_defined(T & setting, YAML::Node const & yaml){
       return set_from_yaml(setting, yaml);
     }
 
     template<typename T, typename FirstName, typename... YamlNames>
     void set_from_yaml_if_defined(
         T & setting,
         YAML::Node const & yaml, FirstName const & name,
         YamlNames && ... names
     ){
       if(!yaml[name]) return;
       set_from_yaml_if_defined(
           setting,
           yaml[name], std::forward<YamlNames>(names)...
       );
     }
   } // namespace detail
 
   template<typename T, typename... YamlNames>
   void set_from_yaml(
       T & setting,
       YAML::Node const & yaml, YamlNames const & ... names
   ){
     try{
       detail::set_from_yaml(setting, yaml, names...);
     }
     catch(invalid_type const & ex){
       throw invalid_type{
         "In option " + join(": ", names...) + ": " + ex.what()
       };
     }
     catch(missing_option const &){
       throw missing_option{
         "No entry for mandatory option " + join(": ", names...)
       };
     }
     catch(std::invalid_argument const & ex){
       throw missing_option{
         "In option " + join(": ", names...) + ":"
           " invalid value " + ex.what()
       };
     }
   }
 
   template<typename T, typename... YamlNames>
   void set_from_yaml_if_defined(
       T & setting,
       YAML::Node const & yaml, YamlNames const & ... names
   ){
     try{
       detail::set_from_yaml_if_defined(setting, yaml, names...);
     }
     catch(invalid_type const & ex){
       throw invalid_type{
         "In option " + join(": ", names...) + ": " + ex.what()
       };
     }
     catch(std::invalid_argument const & ex){
       throw missing_option{
         "In option " + join(": ", names...) + ":"
           " invalid value " + ex.what()
       };
     }
   }
 
 } // namespace HEJ
 
 namespace YAML {
 
   template<>
   struct convert<HEJ::OutputFile> {
     static Node encode(HEJ::OutputFile const & outfile);
     static bool decode(Node const & node, HEJ::OutputFile & out);
   };
 
   template<class Real>
   struct convert<HEJ::Fraction<Real>> {
     static Node encode(HEJ::Fraction<Real> const & f) {
       return encode(Real{f});
     }
     static bool decode(Node const & node, HEJ::Fraction<Real> & f) {
       Real r;
       if(!convert<Real>::decode(node, r)) return false;
       f = r;
       return true;
     }
   };
 }
diff --git a/include/HEJ/exceptions.hh b/include/HEJ/exceptions.hh
index 29dab25..a63cd8f 100644
--- a/include/HEJ/exceptions.hh
+++ b/include/HEJ/exceptions.hh
@@ -1,58 +1,58 @@
 /** \file
  *  \brief Custom exception classes
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <stdexcept>
 #include <string>
 
 namespace HEJ {
   //! Exception indicating wrong option type
   /**
    * This exception is thrown if a configuration option has
    * the wrong type (e.g. 'trials' is not set to a number)
    */
   struct invalid_type: std::invalid_argument {
     explicit invalid_type(std::string const & what):
       std::invalid_argument{what} {}
     explicit invalid_type(char const * what):
       std::invalid_argument{what} {}
   };
 
   //! Exception indicating unknown option
   /**
    * This exception is thrown if an unknown configuration option
    * is set (e.g. the 'trials' setting is misspelt as 'trails')
    */
   struct unknown_option: std::invalid_argument {
     explicit unknown_option(std::string const & what):
       std::invalid_argument{what} {}
     explicit unknown_option(char const * what):
       std::invalid_argument{what} {}
   };
 
   //! Exception indicating missing option setting
   /**
    * This exception is thrown if a mandatory configuration option
    * (e.g. 'trials') is not set.
    */
   struct missing_option: std::logic_error {
     explicit missing_option(std::string const & what):
       std::logic_error{what} {}
     explicit missing_option(char const * what):
       std::logic_error{what} {}
   };
 
   //! Exception indicating functionality that has not been implemented yet
   struct not_implemented: std::logic_error {
     explicit not_implemented(std::string const & what):
       std::logic_error{what} {}
     explicit not_implemented(char const * what):
       std::logic_error{what} {}
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/get_analysis.hh b/include/HEJ/get_analysis.hh
index 69196d9..4d0d727 100644
--- a/include/HEJ/get_analysis.hh
+++ b/include/HEJ/get_analysis.hh
@@ -1,43 +1,43 @@
 /** \file
  *  \brief Contains the get_analysis function
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <vector>
 
 #include "HEJ/Analysis.hh"
 
 namespace YAML {
   class Node;
 }
 
 namespace HEJ {
   //!  Load an analysis
   /**
    *  @param parameters    Analysis parameters
    *  @param heprup        General run informations
    *  @returns             A pointer to an Analysis instance
    *
    *  If parameters["plugin"] exists, an analysis (deriving from the
    *  \ref Analysis class) will be loaded from the library parameters["plugin"].
    *  Otherwise, if parameters["rivet"] exists, the corresponding RivetAnalysis
    *  will be loaded. If none of these parameters are specified, a pointer to
    *  the default EmptyAnalysis is returned.
    */
   std::unique_ptr<Analysis> get_analysis(
       YAML::Node const & parameters, LHEF::HEPRUP const & heprup);
 
   //! Loads multiple analysis, vector version of get_analysis()
   /**
    *  @param parameters    Vector of Analysis parameters
    *  @param heprup        General run informations
    *  @returns             Vector of pointers to an Analysis instance
    */
   std::vector<std::unique_ptr<Analysis>> get_analyses(
       std::vector<YAML::Node> const & parameters, LHEF::HEPRUP const & heprup);
 }
diff --git a/include/HEJ/jets.hh b/include/HEJ/jets.hh
index ab361c0..62d8591 100644
--- a/include/HEJ/jets.hh
+++ b/include/HEJ/jets.hh
@@ -1,403 +1,403 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 /** \file
  *  \brief Functions computing the square of current contractions in pure jets.
  *
  *  This file contains all the necessary functions to compute the
  *  current contractions for all valid pure jet HEJ processes, which
  *  so far is FKL and unordered processes. It will also contain some
  *  pure jet ME components used in other process ME calculations
  *
  *  @TODO add a namespace
  */
 #pragma once
 
 #include <complex>
 #include <ostream>
 #include <vector>
 #include "CLHEP/Vector/LorentzVector.h"
 
 typedef std::complex<double> COM;
 typedef COM current[4];
 typedef CLHEP::HepLorentzVector HLV;
 
 //! Square of qQ->qQ Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state quark
  *  @param p1in      Momentum of initial state quark
  *  @param p2out     Momentum of final state quark
  *  @param p2in      Momentum of intial state quark
  *  @returns         Square of the current contractions for qQ->qQ Scattering
  */
 double ME_qQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qQbar->qQbar Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state quark
  *  @param p1in      Momentum of initial state quark
  *  @param p2out     Momentum of final state anti-quark
  *  @param p2in      Momentum of intial state anti-quark
  *  @returns         Square of the current contractions for qQbar->qQbar Scattering
  *
  *  @note this can be used for qbarQ->qbarQ Scattering by inputting arguments
  *        appropriately.
  */
 double ME_qQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qbarQbar->qbarQbar Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state anti-quark
  *  @param p1in      Momentum of initial state anti-quark
  *  @param p2out     Momentum of final state anti-quark
  *  @param p2in      Momentum of intial state anti-quark
  *  @returns         Square of the current contractions for qbarQbar->qbarQbar Scattering
  */
 double ME_qbarQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qg->qg Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state quark
  *  @param p1in      Momentum of initial state quark
  *  @param p2out     Momentum of final state gluon
  *  @param p2in      Momentum of intial state gluon
  *  @returns         Square of the current contractions for qg->qg Scattering
  *
  *  @note this can be used for gq->gq Scattering by inputting arguments
  *        appropriately.
  */
 double ME_qg(HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qbarg->qbarg Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state anti-quark
  *  @param p1in      Momentum of initial state anti-quark
  *  @param p2out     Momentum of final state gluon
  *  @param p2in      Momentum of intial state gluon
  *  @returns         Square of the current contractions for qbarg->qbarg Scattering
  *
  *  @note this can be used for gqbar->gqbar Scattering by inputting arguments
  *        appropriately.
  */
 double ME_qbarg(HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of gg->gg Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state gluon
  *  @param p1in      Momentum of initial state gluon
  *  @param p2out     Momentum of final state gluon
  *  @param p2in      Momentum of intial state gluon
  *  @returns         Square of the current contractions for gg->gg Scattering
  */
 double ME_gg(HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 // Unordered Backwards contributions:
 //! Square of qQ->qQ Pure Jets Scattering Current
 /**
  *  @param p1out     Momentum of final state quark
  *  @param p1in      Momentum of initial state quark
  *  @param pg        Momentum of unordered gluon
  *  @param p2out     Momentum of final state quark
  *  @param p2in      Momentum of intial state quark
  *  @returns         Square of the current contractions for qQ->qQ Scattering
  */
 double ME_unob_qQ(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qbarQ->qbarQ Pure Jets Unordered backwards Scattering Current
 /**
  *  @param p1out     Momentum of final state anti-quark
  *  @param p1in      Momentum of initial state anti-quark
  *  @param pg        Momentum of unordered gluon
  *  @param p2out     Momentum of final state quark
  *  @param p2in      Momentum of intial state quark
  *  @returns         Square of the current contractions for qbarQ->qbarQ Scattering
  *
  *  @note this can be used for unof contributions by inputting
  *        arguments appropriately.
  */
 double ME_unob_qbarQ(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qQbar->qQbar Pure Jets Unordered backwards Scattering Current
 /**
  *  @param p1out     Momentum of final state quark
  *  @param p1in      Momentum of initial state quark
  *  @param pg        Momentum of unordered gluon
  *  @param p2out     Momentum of final state anti-quark
  *  @param p2in      Momentum of intial state anti-quark
  *  @returns         Square of the current contractions for qQbar->qQbar Scattering
  *
  *  @note this can be used for unof contributions by inputting
  *        arguments appropriately.
  */
 double ME_unob_qQbar(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qbarQbar->qbarQbar Pure Jets Unordered backwards Scattering Current
 /**
  *  @param p1out     Momentum of final state anti-quark
  *  @param p1in      Momentum of initial state anti-quark
  *  @param pg        Momentum of unordered gluon
  *  @param p2out     Momentum of final state anti-quark
  *  @param p2in      Momentum of intial state anti-quark
  *  @returns         Square of the current contractions for qbarQbar->qbarQbar Scattering
  *
  *  @note this can be used for unof contributions by inputting
  *        arguments appropriately.
  */
 double ME_unob_qbarQbar(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qg->qg Pure Jets Unordered backwards Scattering Current
 /**
  *  @param p1out     Momentum of final state gluon
  *  @param p1in      Momentum of initial state gluon
  *  @param pg        Momentum of unordered gluon
  *  @param p2out     Momentum of final state quark
  *  @param p2in      Momentum of intial state quark
  *  @returns         Square of the current contractions for qg->qg Scattering
  *
  *  @note this can be used for unof contributions by inputting
  *        arguments appropriately.
  */
 double ME_unob_qg(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of qbarg->qbarg Pure Jets Unordered backwards Scattering Current
 /**
  *  @param p1out     Momentum of final state gluon
  *  @param p1in      Momentum of initial state gluon
  *  @param pg        Momentum of unordered gluon
  *  @param p2out     Momentum of final state anti-quark
  *  @param p2in      Momentum of intial state anti-quark
  *  @returns         Square of the current contractions for qbarg->qbarg Scattering
  *
  *  @note this can be used for unof contributions by inputting
  *        arguments appropriately.
  */
 double ME_unob_qbarg(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in);
 
 //! Square of gQ->qbarqQ Pure Jets Extremal qqx backwards Scattering Current
 /**
  *  @param pgin      Momentum of incoming gluon
  *  @param pqout     Momentum of Quark from split
  *  @param pqbarout  Momentum of Anti-quark from split
  *  @param p2out     Momentum of Outgoing forwards leg
  *  @param p2in      Momentum of Incoming forwards leg
  *  @returns         Square of the current contractions for gQ->qbarqg Scattering
  *
  *  @note this can be used for Exqqxf contributions by inputting
  *        arguments appropriately.
  */
 double ME_Exqqx_qbarqQ(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in);
 
 //! Square of gQ->qqbarQ Pure Jets Extremal qqx backwards Scattering Current
 /**
  *  @param pgin      Momentum of incoming gluon
  *  @param pqout     Momentum of Quark from split
  *  @param pqbarout  Momentum of Anti-quark from split
  *  @param p2out     Momentum of Outgoing forwards leg
  *  @param p2in      Momentum of Incoming forwards leg
  *  @returns         Square of the current contractions for gQ->qqbarg Scattering
  *
  *  @note this can be used for Exqqxf contributions by inputting
  *        arguments appropriately.
  */
 double ME_Exqqx_qqbarQ(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in);
 
 //! Square of gg->qbarqg Pure Jets Extremal qqx backwards Scattering Current
 /**
  *  @param pgin      Momentum of incoming gluon
  *  @param pqout     Momentum of Quark from split
  *  @param pqbarout  Momentum of Anti-quark from split
  *  @param p2out     Momentum of Outgoing forwards leg
  *  @param p2in      Momentum of Incoming forwards leg
  *  @returns         Square of the current contractions for gg->qbarqg Scattering
  *
  *  @note this can be used for Exqqxf contributions by inputting
  *        arguments appropriately.
  */
 double ME_Exqqx_qbarqg(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in);
 
 //! Square of gg->qqbarg Pure Jets Extremal qqx backwards Scattering Current
 /**
  *  @param pgin      Momentum of incoming gluon
  *  @param pqout     Momentum of Quark from split
  *  @param pqbarout  Momentum of Anti-quark from split
  *  @param p2out     Momentum of Outgoing forwards leg
  *  @param p2in      Momentum of Incoming forwards leg
  *  @returns         Square of the current contractions for gg->qqbarg Scattering
  *
  *  @note this can be used for Exqqxf contributions by inputting
  *        arguments appropriately.
  */
 double ME_Exqqx_qqbarg(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in);
 
 //! Square of qq->qQQbarq Pure Jets Central qqx Scattering Current
 /**
  *  @param ka        Momentum of incoming leg a
  *  @param kb        Momentum of incoming leg b
  *  @param partons   std::vector<HLV> outgoing partons
  *  @param aqlinepa  Is leg a an anti-quark?
  *  @param aqlinepb  Is leg b an anti-quark?
  *  @param qqxmarker Is anti-quark further back in rapidity than quark (qqx pair)
  *  @param nabove    Number of gluons emitted above qqx pair (back in rap)
  *  @returns         Square of the current contractions for qq->qQQxq Scattering
  */
 double ME_Cenqqx_qq(HLV ka, HLV kb, std::vector<HLV> partons, bool aqlinepa,
                     bool aqlinepb, bool qqxmarker, int nabove);
 
 /** \class CCurrent jets.hh "include/HEJ/jets.hh"
  *  \brief This is the a new class structure for currents.
  */
 class CCurrent {
 public:
     CCurrent(COM sc0, COM sc1, COM sc2, COM sc3)
     :c0(sc0), c1(sc1), c2(sc2), c3(sc3)
     {}
     CCurrent(const HLV p) {
         c0=p.e();
         c1=p.px();
         c2=p.py();
         c3=p.pz();
     }
     CCurrent() {}
     CCurrent operator+(const CCurrent& other);
     CCurrent operator-(const CCurrent& other);
     CCurrent operator*(const double x);
     CCurrent operator*(const COM x);
     CCurrent operator/(const double x);
     CCurrent operator/(const COM x);
 
     friend std::ostream& operator<<(std::ostream& os, const CCurrent& cur);
     COM dot(HLV p1);
     COM dot(CCurrent p1);
     COM c0, c1, c2, c3;
 };
 
 /* std::ostream& operator <<(std::ostream& os, const CCurrent& cur); */
 CCurrent operator*(double x, CCurrent& m);
 CCurrent operator*(COM x, CCurrent& m);
 CCurrent operator/(double x, CCurrent& m);
 CCurrent operator/(COM x, CCurrent& m);
 
 //! Current <incoming state | mu | outgoing state>
 /**
  * This is a wrapper function around \see joi() note helicity flip to
  * give same answer.
  */
 void jio(HLV pin, bool helin, HLV pout, bool helout, current &cur);
 
 //! Current <outgoing state | mu | outgoing state>
 /**
  * @param pi              bra state momentum
  * @param heli            helicity of pi
  * @param pj              ket state momentum
  * @param helj            helicity of pj. (must be same as heli)
  * @param cur             reference to current which is saved.
  *
  * This function is for building <i (out)| mu |j (out)> currents. It
  * must be called with pi as the bra, and pj as the ket.
  *
  * @TODO Remove heli/helj and just have helicity of current as argument.
  */
 void joo(HLV pi, bool heli, HLV pj, bool helj, current &cur);
 
 //! Current <outgoing state | mu | incoming state>
 /**
  * @param pout            bra state momentum
  * @param helout          helicity of pout
  * @param pin             ket state momentum
  * @param helin           helicity of pin. (must be same as helout)
  * @param cur             reference to current which is saved.
  *
  * This function is for building <out| mu |in> currents. It must be
  * called with pout as the bra, and pin as the ket. jio calls this
  * with flipped helicity
  *
  * @TODO Remove helout/helin and just have helicity of current as argument.
  */
 void joi(HLV pout, bool helout, HLV pin, bool helin, current &cur);
 
 //! Current <outgoing state | mu | incoming state>
 /**
  * This is a wrapper function around the void function of the same name. \see joi
  *
  * @TODO This is never used
  */
 CCurrent joi(HLV pout, bool helout, HLV pin, bool helin);
 
 //! Current <incoming state | mu | outgoing state>
 /**
  * This is a wrapper function around the void function of the same name. \see jio
  */
 CCurrent jio(HLV pout, bool helout, HLV pin, bool helin);
 
 //! Current <outgoing state | mu | outgoing state>
 /**
  * This is a wrapper function around the void function of the same name. \see joo
  */
 CCurrent joo(HLV pout, bool helout, HLV pin, bool helin);
 
 inline COM cdot(const current & j1, const current & j2) {
   return j1[0]*j2[0]-j1[1]*j2[1]-j1[2]*j2[2]-j1[3]*j2[3];
 }
 
 inline COM cdot(const HLV & p, const current & j1) {
   return j1[0]*p.e()-j1[1]*p.x()-j1[2]*p.y()-j1[3]*p.z();
 }
 
 inline void cmult(const COM & factor, const current & j1, current &cur) {
   cur[0]=factor*j1[0];
   cur[1]=factor*j1[1];
   cur[2]=factor*j1[2];
   cur[3]=factor*j1[3];
 }
 
 // WHY!?!
 inline void cadd(const current & j1, const current & j2, const current & j3,
           const current & j4, const current & j5, current &sum
 ) {
   sum[0]=j1[0]+j2[0]+j3[0]+j4[0]+j5[0];
   sum[1]=j1[1]+j2[1]+j3[1]+j4[1]+j5[1];
   sum[2]=j1[2]+j2[2]+j3[2]+j4[2]+j5[2];
   sum[3]=j1[3]+j2[3]+j3[3]+j4[3]+j5[3];
 }
 
 inline void cadd(const current & j1, const current & j2, const current & j3,
           const current & j4, current &sum
 ) {
   sum[0] = j1[0] + j2[0] + j3[0] + j4[0];
   sum[1] = j1[1] + j2[1] + j3[1] + j4[1];
   sum[2] = j1[2] + j2[2] + j3[2] + j4[2];
   sum[3] = j1[3] + j2[3] + j3[3] + j4[3];
 }
 
 inline void cadd(const current & j1, const current & j2, const current & j3,
          current &sum
 ) {
   sum[0]=j1[0]+j2[0]+j3[0];
   sum[1]=j1[1]+j2[1]+j3[1];
   sum[2]=j1[2]+j2[2]+j3[2];
   sum[3]=j1[3]+j2[3]+j3[3];
 }
 
 inline void cadd(const current & j1, const current & j2, current &sum) {
   sum[0]=j1[0]+j2[0];
   sum[1]=j1[1]+j2[1];
   sum[2]=j1[2]+j2[2];
   sum[3]=j1[3]+j2[3];
 }
 
 inline double abs2(const COM & a) {
     return (a*conj(a)).real();
 }
 
 inline double vabs2(const CCurrent & cur) {
     return abs2(cur.c0)-abs2(cur.c1)-abs2(cur.c2)-abs2(cur.c3);
 }
 
 inline double vre(const CCurrent & a, const CCurrent & b) {
   return real(a.c0*conj(b.c0)-a.c1*conj(b.c1)-a.c2*conj(b.c2)-a.c3*conj(b.c3));
 }
 //! @TODO These are not currents and should be moved elsewhere.
 double K_g(double p1minus, double paminus);
 double K_g(HLV const & pout, HLV const & pin);
diff --git a/include/HEJ/make_RNG.hh b/include/HEJ/make_RNG.hh
index 814b6c2..64a6559 100644
--- a/include/HEJ/make_RNG.hh
+++ b/include/HEJ/make_RNG.hh
@@ -1,32 +1,32 @@
 /** \file
  *  \brief Declares a factory function for random number generators
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 
 #include "HEJ/optional.hh"
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
   //! Factory function for random number generators
   /**
    *  @param name      Name of the random number generator
    *  @param seed      Optional seed
    *  @returns         A pointer to an instance of a random number generator
    *
    *  At present, name should be one of "ranlux64" or "mixmax" (case insensitive).
    *  The interpretation of the seed depends on the random number generator.
    *  For ranlux64, it is the name of a seed file. For mixmax it should be a
    *  string convertible to a long integer.
    */
   std::unique_ptr<RNG> make_RNG(
       std::string const & name,
       optional<std::string> const & seed
   );
 }
diff --git a/src/Event.cc b/src/Event.cc
index ec62998..b0a569d 100644
--- a/src/Event.cc
+++ b/src/Event.cc
@@ -1,1130 +1,1130 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/Event.hh"
 
 #include <algorithm>
 #include <assert.h>
 #include <iterator>
 #include <numeric>
 #include <unordered_set>
 #include <utility>
 
 #include "LHEF/LHEF.h"
 
 #include "fastjet/JetDefinition.hh"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/PDG_codes.hh"
 
 namespace HEJ{
 
   namespace {
     constexpr int status_in = -1;
     constexpr int status_decayed = 2;
     constexpr int status_out = 1;
 
     //! true if leptonic W decay
     bool valid_W_decay( int const w_type, // sign of W
                         std::vector<Particle> const & decays
     ){
       if(decays.size() != 2) // no 1->2 decay
         return false;
       const int pidsum = decays[0].type + decays[1].type;
       if( std::abs(pidsum) != 1 || pidsum != w_type ) // correct charge
         return false;
       // leptonic decay (only check first, second follows from pidsum)
       if( w_type == 1 ) // W+
         return is_antilepton(decays[0]) || is_neutrino(decays[0]);
       // W-
       return is_lepton(decays[0]) || is_antineutrino(decays[0]);
     }
 
     /// @name helper functions to determine event type
     //@{
 
     /**
      * \brief check if final state valid for HEJ
      *
      * check if there is at most one photon, W, H, Z in the final state
      * and all the rest are quarks or gluons
      */
     bool final_state_ok(Event const & ev){
       std::vector<Particle> const & outgoing = ev.outgoing();
       if(ev.decays().size() > 1) // at most one decay
         return false;
       bool has_AWZH_boson = false;
       for( size_t i=0; i<outgoing.size(); ++i ){
         auto const & out{ outgoing[i] };
         if(is_AWZH_boson(out.type)){
           // at most one boson
           if(has_AWZH_boson) return false;
           has_AWZH_boson = true;
 
           // valid decay for W
           if(std::abs(out.type) == ParticleID::Wp){
             // exactly 1 decay of W
             if( ev.decays().size() != 1 || ev.decays().cbegin()->first != i )
               return false;
             if( !valid_W_decay(out.type>0?+1:-1, ev.decays().cbegin()->second) )
               return false;
           }
         }
         else if(! is_parton(out.type)) return false;
       }
       return true;
     }
 
     /**
      * returns all EventTypes implemented in HEJ
      */
     size_t implemented_types(std::vector<Particle> const & bosons){
       using namespace event_type;
       if(bosons.empty()) return FKL | unob | unof | qqxexb | qqxexf | qqxmid;
       if(bosons.size()>1) return non_resummable; // multi boson
       switch (bosons[0].type) {
         case ParticleID::Wp:
         case ParticleID::Wm:
           return FKL | unob | unof | qqxexb | qqxexf | qqxmid;
         case ParticleID::h:
           return FKL | unob | unof;
         default:
           return non_resummable;
       }
     }
 
     /**
      * \brief function which determines if type change is consistent with Wp emission.
      * @param in                      incoming Particle id
      * @param out                     outgoing Particle id
      * @param qqx                     Current both incoming/both outgoing?
      *
      * \see is_Wm_Change
      */
     bool is_Wp_Change(ParticleID in, ParticleID out, bool qqx){
       if(!qqx && (in==-1 || in== 2 || in==-3 || in== 4)) return out== (in-1);
       if( qqx && (in== 1 || in==-2 || in== 3 || in==-4)) return out==-(in+1);
       return false;
     }
 
     /**
      * \brief function which determines if type change is consistent with Wm emission.
      * @param in                      incoming Particle id
      * @param out                     outgoing Particle id
      * @param qqx                     Current both incoming/both outgoing?
      *
      * Ensures that change type of quark line is possible by a flavour changing
      * Wm emission. Allows checking of qqx currents also.
      */
     bool is_Wm_Change(ParticleID in, ParticleID out, bool qqx){
       if(!qqx && (in== 1 || in==-2 || in== 3 || in==-4)) return out== (in+1);
       if( qqx && (in==-1 || in== 2 || in==-3 || in== 4)) return out==-(in-1);
       return false;
     }
 
     /**
      * \brief checks if particle type remains same from incoming to outgoing
      * @param in                      incoming Particle
      * @param out                     outgoing Particle
      * @param qqx                     Current both incoming/outgoing?
      */
     bool no_flavour_change(ParticleID in, ParticleID out, bool qqx){
       const int qqxCurrent = qqx?-1:1;
       if(abs(in)<=6 || in==pid::gluon) return (in==out*qqxCurrent);
       else return false;
     }
 
     bool has_2_jets(Event const & event){
       return event.jets().size() >= 2;
     }
 
     /**
      * \brief check if we have a valid Impact factor
      * @param in                      incoming Particle
      * @param out                     outgoing Particle
      * @param qqx                     Current both incoming/outgoing?
      * @param W_change                returns +1 if Wp, -1 if Wm, else 0
      */
     bool is_valid_impact_factor(
       ParticleID in, ParticleID out, bool qqx, int & W_change
     ){
       if( no_flavour_change(in, out, qqx) ){
         return true;
       }
       if( is_Wp_Change(in, out, qqx) ) {
         W_change+=1;
         return true;
       }
       if( is_Wm_Change(in, out, qqx) ) {
         W_change-=1;
         return true;
       }
       return false;
     }
 
     //! Returns all possible classifications from the impact factors
     // the beginning points are changed s.t. after the the classification they
     // point to the beginning of the (potential) FKL chain
     // sets W_change: + if Wp change
     //                0 if no change
     //                - if Wm change
     // This function can be used with forward & backwards iterators
     template<class OutIterator>
     size_t possible_impact_factors(
       ParticleID incoming_id,                                   // incoming
       OutIterator   & begin_out, OutIterator   const & end_out, // outgoing
       int & W_change, std::vector<Particle> const & boson,
       bool const backward                                       // backward?
     ){
       using namespace event_type;
       assert(boson.size() < 2);
       // keep track of all states that we don't test
       size_t not_tested = qqxmid;
       if(backward)
         not_tested |= unof | qqxexf;
       else
         not_tested |= unob | qqxexb;
 
       // Is this LL current?
       if( is_valid_impact_factor(incoming_id, begin_out->type, false, W_change) ){
         ++begin_out;
         return not_tested | FKL;
       }
 
       // or NLL current?
       // -> needs two partons in two different jets
       if( std::distance(begin_out, end_out)>=2
       ){
         auto next = std::next(begin_out);
         // Is this unordered emisson?
         if( incoming_id!=pid::gluon && begin_out->type==pid::gluon ){
           if( is_valid_impact_factor(
                 incoming_id, next->type, false, W_change )
           ){
             // veto Higgs inside uno
             assert(next!=end_out);
             if( !boson.empty() && boson.front().type == ParticleID::h
             ){
               if(  (backward && boson.front().rapidity() < next->rapidity())
                 ||(!backward && boson.front().rapidity() > next->rapidity()))
               return non_resummable;
             }
             begin_out = std::next(next);
             return not_tested | (backward?unob:unof);
           }
         }
         // Is this QQbar?
         else if( incoming_id==pid::gluon ){
           if( is_valid_impact_factor(
                 begin_out->type, next->type, true, W_change )
           ){
             // veto Higgs inside qqx
             assert(next!=end_out);
             if( !boson.empty() && boson.front().type == ParticleID::h
             ){
               if(  (backward && boson.front().rapidity() < next->rapidity())
                 ||(!backward && boson.front().rapidity() > next->rapidity()))
               return non_resummable;
             }
             begin_out = std::next(next);
             return not_tested | (backward?qqxexb:qqxexf);
           }
         }
       }
       return non_resummable;
     }
 
     //! Returns all possible classifications from central emissions
     // the beginning points are changed s.t. after the the classification they
     // point to the end of the emission chain
     // sets W_change: + if Wp change
     //               0 if no change
     //               - if Wm change
     template<class OutIterator>
     size_t possible_central(
       OutIterator & begin_out, OutIterator const & end_out,
       int & W_change, std::vector<Particle> const & boson
     ){
       using namespace event_type;
       assert(boson.size() < 2);
       // if we already passed the central chain,
       // then it is not a valid all-order state
       if(std::distance(begin_out, end_out) < 0) return non_resummable;
       // keep track of all states that we don't test
       size_t possible = unob | unof
                           | qqxexb | qqxexf;
 
       // Find the first non-gluon/non-FKL
       while( (begin_out->type==pid::gluon) && (begin_out!=end_out) ){
         ++begin_out;
       }
       // end of chain -> FKL
       if( begin_out==end_out ){
         return possible | FKL;
       }
 
       // is this a qqbar-pair?
       // needs two partons in two separate jets
       auto next = std::next(begin_out);
       if( is_valid_impact_factor(
             begin_out->type, next->type, true, W_change )
       ){
         // veto Higgs inside qqx
         if( !boson.empty() && boson.front().type == ParticleID::h
             && boson.front().rapidity() > begin_out->rapidity()
             && boson.front().rapidity() < next->rapidity()
         ){
           return non_resummable;
         }
         begin_out = std::next(next);
         // remaining chain should be pure gluon/FKL
         for(; begin_out!=end_out; ++begin_out){
           if(begin_out->type != pid::gluon) return non_resummable;
         }
         return possible | qqxmid;
       }
       return non_resummable;
     }
 
     /**
      * \brief Checks for all event types
      * @param ev          Event
      * @returns           Event Type
      *
      */
     event_type::EventType classify(Event const & ev){
       using namespace event_type;
       if(! has_2_jets(ev))
         return no_2_jets;
       // currently we can't handle multiple boson states in the ME. So they are
       // considered "bad_final_state" even though the "classify" could work with
       // them.
       if(! final_state_ok(ev))
         return bad_final_state;
 
       // initialise variables
       auto const & in = ev.incoming();
 
       // range for current checks
       auto begin_out{ev.cbegin_partons()};
       auto end_out{ev.crbegin_partons()};
 
       assert(std::distance(begin(in), end(in)) == 2);
       assert(std::distance(begin_out, end_out.base()) >= 2);
       assert(std::is_sorted(begin_out, end_out.base(), rapidity_less{}));
 
       auto const boson{ filter_AWZH_bosons(ev.outgoing()) };
       // we only allow one boson through final_state_ok
       assert(boson.size()<=1);
 
       // keep track of potential W couplings, at the end the sum should be 0
       int remaining_Wp = 0;
       int remaining_Wm = 0;
       if(!boson.empty() && abs(boson.front().type) == ParticleID::Wp ){
         if(boson.front().type>0) ++remaining_Wp;
         else ++remaining_Wm;
       }
       int W_change = 0;
 
       size_t final_type = ~(no_2_jets | bad_final_state);
 
       // check forward impact factor
       final_type &= possible_impact_factors(
         in.front().type,
         begin_out, end_out.base(),
         W_change, boson, true );
       if( final_type == non_resummable )
         return non_resummable;
       if(W_change>0) remaining_Wp-=W_change;
       else if(W_change<0) remaining_Wm+=W_change;
       W_change = 0;
 
       // check backward impact factor
       final_type &= possible_impact_factors(
         in.back().type,
         end_out, std::make_reverse_iterator(begin_out),
         W_change, boson, false );
       if( final_type == non_resummable )
         return non_resummable;
       if(W_change>0) remaining_Wp-=W_change;
       else if(W_change<0) remaining_Wm+=W_change;
       W_change = 0;
 
       // check central emissions
       final_type &= possible_central(
         begin_out, end_out.base(), W_change, boson );
       if( final_type == non_resummable )
         return non_resummable;
       if(W_change>0) remaining_Wp-=W_change;
       else if(W_change<0) remaining_Wm+=W_change;
 
       // Check whether the right number of Ws are present
       if( remaining_Wp != 0 || remaining_Wm != 0 ) return non_resummable;
 
       // result has to be unique
       if( (final_type & (final_type-1)) != 0) return non_resummable;
 
       // check that each sub processes is implemented
       // (has to be done at the end)
       if( (final_type & ~implemented_types(boson)) != 0 )
         return non_resummable;
 
       return static_cast<EventType>(final_type);
     }
     //@}
 
     Particle extract_particle(LHEF::HEPEUP const & hepeup, size_t i){
       const ParticleID id = static_cast<ParticleID>(hepeup.IDUP[i]);
       const fastjet::PseudoJet momentum{
         hepeup.PUP[i][0], hepeup.PUP[i][1],
         hepeup.PUP[i][2], hepeup.PUP[i][3]
       };
       if(is_parton(id))
         return Particle{ id, std::move(momentum), hepeup.ICOLUP[i] };
       return Particle{ id, std::move(momentum), {} };
     }
 
     bool is_decay_product(std::pair<int, int> const & mothers){
       if(mothers.first == 0) return false;
       return mothers.second == 0 || mothers.first == mothers.second;
     }
 
   } // namespace anonymous
 
   Event::EventData::EventData(LHEF::HEPEUP const & hepeup){
     parameters.central = EventParameters{
       hepeup.scales.mur, hepeup.scales.muf, hepeup.XWGTUP
     };
     size_t in_idx = 0;
     for (int i = 0; i < hepeup.NUP; ++i) {
       // skip decay products
       // we will add them later on, but we have to ensure that
       // the decayed particle is added before
       if(is_decay_product(hepeup.MOTHUP[i])) continue;
 
       auto particle = extract_particle(hepeup, i);
       // needed to identify mother particles for decay products
       particle.p.set_user_index(i+1);
 
       if(hepeup.ISTUP[i] == status_in){
         if(in_idx > incoming.size()) {
           throw std::invalid_argument{
             "Event has too many incoming particles"
           };
         }
         incoming[in_idx++] = std::move(particle);
       }
       else outgoing.emplace_back(std::move(particle));
     }
 
     // add decay products
     for (int i = 0; i < hepeup.NUP; ++i) {
       if(!is_decay_product(hepeup.MOTHUP[i])) continue;
       const int mother_id = hepeup.MOTHUP[i].first;
       const auto mother = std::find_if(
           begin(outgoing), end(outgoing),
           [mother_id](Particle const & particle){
             return particle.p.user_index() == mother_id;
           }
       );
       if(mother == end(outgoing)){
         throw std::invalid_argument{"invalid decay product parent"};
       }
       const int mother_idx = std::distance(begin(outgoing), mother);
       assert(mother_idx >= 0);
       decays[mother_idx].emplace_back(extract_particle(hepeup, i));
     }
   }
 
   Event::Event(
     UnclusteredEvent const & ev,
     fastjet::JetDefinition const & jet_def, double const min_jet_pt
   ):
     Event( Event::EventData{
       ev.incoming, ev.outgoing, ev.decays,
       Parameters<EventParameters>{ev.central, ev.variations}
     }.cluster(jet_def, min_jet_pt) )
   {}
 
   //! @TODO remove in HEJ 2.2.0
   UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup){
       Event::EventData const evData{hepeup};
       incoming = evData.incoming;
       outgoing = evData.outgoing;
       decays = evData.decays;
       central = evData.parameters.central;
       variations = evData.parameters.variations;
   }
 
   void Event::EventData::sort(){
     // sort particles
     std::sort(
         begin(incoming), end(incoming),
         [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();}
     );
 
     auto old_outgoing = std::move(outgoing);
     std::vector<size_t> idx(old_outgoing.size());
     std::iota(idx.begin(), idx.end(), 0);
     std::sort(idx.begin(), idx.end(), [&old_outgoing](size_t i, size_t j){
       return old_outgoing[i].rapidity() < old_outgoing[j].rapidity();
     });
     outgoing.clear();
     outgoing.reserve(old_outgoing.size());
     for(size_t i: idx) {
       outgoing.emplace_back(std::move(old_outgoing[i]));
     }
 
     // find decays again
     if(!decays.empty()){
       auto old_decays = std::move(decays);
       decays.clear();
       for(size_t i=0; i<idx.size(); ++i) {
         auto decay = old_decays.find(idx[i]);
         if(decay != old_decays.end())
           decays.emplace(i, std::move(decay->second));
       }
       assert(old_decays.size() == decays.size());
     }
   }
 
   namespace {
     Particle reconstruct_boson(std::vector<Particle> const & leptons) {
       Particle decayed_boson;
       decayed_boson.p = leptons[0].p + leptons[1].p;
       const int pidsum = leptons[0].type + leptons[1].type;
       if(pidsum == +1) {
         assert(is_antilepton(leptons[0]));
         if(is_antineutrino(leptons[0])) {
           throw not_implemented{"lepton-flavour violating final state"};
         }
         assert(is_neutrino(leptons[1]));
         // charged antilepton + neutrino means we had a W+
         decayed_boson.type = pid::Wp;
       }
       else if(pidsum == -1) {
         assert(is_antilepton(leptons[0]));
         if(is_neutrino(leptons[1])) {
           throw not_implemented{"lepton-flavour violating final state"};
         }
         assert(is_antineutrino(leptons[0]));
         // charged lepton + antineutrino means we had a W-
         decayed_boson.type = pid::Wm;
       }
       else {
         throw not_implemented{
           "final state with leptons "
             + name(leptons[0].type)
             + " and "
             + name(leptons[1].type)
         };
       }
       return decayed_boson;
     }
   }
 
   void Event::EventData::reconstruct_intermediate() {
     const auto begin_leptons = std::partition(
         begin(outgoing), end(outgoing),
         [](Particle const & p) {return !is_anylepton(p);}
     );
     if(begin_leptons == end(outgoing)) return;
     assert(is_anylepton(*begin_leptons));
     std::vector<Particle> leptons(begin_leptons, end(outgoing));
     outgoing.erase(begin_leptons, end(outgoing));
     if(leptons.size() != 2) {
       throw not_implemented{"Final states with one or more than two leptons"};
     }
     std::sort(
         begin(leptons), end(leptons),
         [](Particle const & p0, Particle const & p1) {
           return p0.type < p1.type;
         }
     );
     outgoing.emplace_back(reconstruct_boson(leptons));
     decays.emplace(outgoing.size()-1, std::move(leptons));
   }
 
   Event Event::EventData::cluster(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt
   ){
     sort();
     Event ev{ std::move(incoming), std::move(outgoing), std::move(decays),
       std::move(parameters),
       jet_def, min_jet_pt
     };
     assert(std::is_sorted(begin(ev.outgoing_), end(ev.outgoing_),
       rapidity_less{}));
     ev.type_ = classify(ev);
     return ev;
   }
 
   Event::Event(
       std::array<Particle, 2> && incoming,
       std::vector<Particle> && outgoing,
       std::unordered_map<size_t, std::vector<Particle>> && decays,
       Parameters<EventParameters> && parameters,
       fastjet::JetDefinition const & jet_def,
       double const min_jet_pt
     ): incoming_{std::move(incoming)},
        outgoing_{std::move(outgoing)},
        decays_{std::move(decays)},
        parameters_{std::move(parameters)},
        cs_{ to_PseudoJet( filter_partons(outgoing_) ), jet_def },
        min_jet_pt_{min_jet_pt}
     {
       jets_ = sorted_by_rapidity(cs_.inclusive_jets(min_jet_pt_));
     }
 
   namespace {
     //! check that Particles have a reasonable colour
     bool correct_colour(Particle const & part){
       ParticleID id{ part.type };
       if(!is_parton(id))
         return !part.colour;
 
       if(!part.colour)
         return false;
 
       Colour const & col{ *part.colour };
       if(is_quark(id))
         return col.first != 0 && col.second == 0;
       if(is_antiquark(id))
         return col.first == 0 && col.second != 0;
       assert(id==ParticleID::gluon);
       return col.first != 0 && col.second != 0 && col.first != col.second;
     }
 
     //! Connect parton to t-channel colour line & update the line
     //! returns false if connection not possible
     template<class OutIterator>
     bool try_connect_t(OutIterator const & it_part, Colour & line_colour){
       if( line_colour.first == it_part->colour->second ){
         line_colour.first = it_part->colour->first;
         return true;
       }
       if( line_colour.second == it_part->colour->first ){
         line_colour.second = it_part->colour->second;
         return true;
       }
       return false;
     }
 
     //! Connect parton to u-channel colour line & update the line
     //! returns false if connection not possible
     template<class OutIterator>
     bool try_connect_u(OutIterator & it_part, Colour & line_colour){
       auto it_next = std::next(it_part);
       if( try_connect_t(it_next, line_colour)
           && try_connect_t(it_part, line_colour)
       ){
         it_part=it_next;
         return true;
       }
       return false;
     }
   } // namespace anonymous
 
   bool Event::is_leading_colour() const {
     if( !correct_colour(incoming()[0]) || !correct_colour(incoming()[1]) )
       return false;
 
     Colour line_colour = *incoming()[0].colour;
     std::swap(line_colour.first, line_colour.second);
 
     // reasonable colour
     if(!std::all_of(outgoing().cbegin(), outgoing().cend(), correct_colour))
       return false;
 
     for(auto it_part = cbegin_partons(); it_part!=cend_partons(); ++it_part){
 
       switch (type()) {
       case event_type::FKL:
         if( !try_connect_t(it_part, line_colour) )
           return false;
         break;
       case event_type::unob:
       case event_type::qqxexb: {
         if( !try_connect_t(it_part, line_colour)
             // u-channel only allowed at impact factor
             && (std::distance(cbegin_partons(), it_part)!=0
                 || !try_connect_u(it_part, line_colour)))
           return false;
         break;
       }
       case event_type::unof:
       case event_type::qqxexf: {
         if( !try_connect_t(it_part, line_colour)
             // u-channel only allowed at impact factor
             && (std::distance(it_part, cend_partons())!=2
                 || !try_connect_u(it_part, line_colour)))
           return false;
         break;
       }
       case event_type::qqxmid:{
         auto it_next = std::next(it_part);
         if( !try_connect_t(it_part, line_colour)
             // u-channel only allowed at qqx/qxq pair
             && ( (   !(is_quark(*it_part) && is_antiquark(*it_next))
                   && !(is_antiquark(*it_part) && is_quark(*it_next)))
                 || !try_connect_u(it_part, line_colour))
           )
           return false;
         break;
       }
       default:
         throw std::logic_error{"unreachable"};
       }
 
       // no colour singlet exchange/disconnected diagram
       if(line_colour.first == line_colour.second)
         return false;
     }
 
     return (incoming()[1].colour->first == line_colour.first)
         && (incoming()[1].colour->second == line_colour.second);
   }
 
   namespace {
     //! connect incoming Particle to colour flow
     void connect_incoming(Particle & in, int & colour, int & anti_colour){
       in.colour = std::make_pair(anti_colour, colour);
       // gluon
       if(in.type == pid::gluon)
         return;
       if(in.type > 0){
         // quark
         assert(is_quark(in));
         in.colour->second = 0;
         colour*=-1;
         return;
       }
       // anti-quark
       assert(is_antiquark(in));
       in.colour->first = 0;
       anti_colour*=-1;
       return;
     }
 
     //! connect outgoing Particle to t-channel colour flow
     template<class OutIterator>
     void connect_tchannel(
         OutIterator & it_part, int & colour, int & anti_colour, RNG & ran
     ){
       assert(colour>0 || anti_colour>0);
       if(it_part->type == ParticleID::gluon){
         // gluon
         if(colour>0 && anti_colour>0){
           // on g line => connect to colour OR anti-colour (random)
           if(ran.flat() < 0.5){
             it_part->colour = std::make_pair(colour+2,colour);
             colour+=2;
           } else {
             it_part->colour = std::make_pair(anti_colour, anti_colour+2);
             anti_colour+=2;
           }
         } else if(colour > 0){
           // on q line => connect to available colour
             it_part->colour = std::make_pair(colour+2, colour);
             colour+=2;
         } else {
           assert(colour<0 && anti_colour>0);
           // on qx line => connect to available anti-colour
           it_part->colour = std::make_pair(anti_colour, anti_colour+2);
           anti_colour+=2;
         }
       } else if(is_quark(*it_part)) {
         // quark
         assert(anti_colour>0);
         if(colour>0){
           // on g line => connect and remove anti-colour
           it_part->colour = std::make_pair(anti_colour, 0);
           anti_colour+=2;
           anti_colour*=-1;
         } else {
           // on qx line => new colour
           colour*=-1;
           it_part->colour = std::make_pair(colour, 0);
         }
       } else if(is_antiquark(*it_part)) {
         // anti-quark
         assert(colour>0);
         if(anti_colour>0){
           // on g line => connect and remove colour
           it_part->colour = std::make_pair(0, colour);
           colour+=2;
           colour*=-1;
         } else {
           // on q line => new anti-colour
           anti_colour*=-1;
           it_part->colour = std::make_pair(0, anti_colour);
         }
       } else { // not a parton
         assert(!is_parton(*it_part));
         it_part->colour = {};
       }
     }
 
     //! connect to t- or u-channel colour flow
     template<class OutIterator>
     void connect_utchannel(
         OutIterator & it_part, int & colour, int & anti_colour, RNG & ran
     ){
       OutIterator it_first = it_part++;
       if(ran.flat()<.5) {// t-channel
         connect_tchannel(it_first, colour, anti_colour, ran);
         connect_tchannel(it_part, colour, anti_colour, ran);
       }
       else { // u-channel
         connect_tchannel(it_part, colour, anti_colour, ran);
         connect_tchannel(it_first, colour, anti_colour, ran);
       }
     }
   } // namespace anonymous
 
   bool Event::generate_colours(RNG & ran){
     // generate only for HEJ events
     if(!event_type::is_resummable(type()))
       return false;
     assert(std::is_sorted(
       begin(outgoing()), end(outgoing()), rapidity_less{}));
     assert(incoming()[0].pz() < incoming()[1].pz());
 
     // positive (anti-)colour -> can connect
     // negative (anti-)colour -> not available/used up by (anti-)quark
     int colour = COLOUR_OFFSET;
     int anti_colour = colour+1;
     // initialise first
     connect_incoming(incoming_[0], colour, anti_colour);
 
     // reset outgoing colours
     std::for_each(outgoing_.begin(), outgoing_.end(),
       [](Particle & part){ part.colour = {};});
 
     for(auto it_part = begin_partons(); it_part!=end_partons(); ++it_part){
         switch (type()) {
         // subleading can connect to t- or u-channel
         case event_type::unob:
         case event_type::qqxexb: {
           if( std::distance(begin_partons(), it_part)==0)
             connect_utchannel(it_part, colour, anti_colour, ran);
           else
             connect_tchannel(it_part, colour, anti_colour, ran);
           break;
         }
         case event_type::unof:
         case event_type::qqxexf: {
           if( std::distance(it_part, end_partons())==2)
             connect_utchannel(it_part, colour, anti_colour, ran);
           else
             connect_tchannel(it_part, colour, anti_colour, ran);
           break;
         }
         case event_type::qqxmid:{
           auto it_next = std::next(it_part);
           if( std::distance(begin_partons(), it_part)>0
               && std::distance(it_part, end_partons())>2
               && ( (is_quark(*it_part) && is_antiquark(*it_next))
                 || (is_antiquark(*it_part) && is_quark(*it_next)) )
           )
             connect_utchannel(it_part, colour, anti_colour, ran);
           else
             connect_tchannel(it_part, colour, anti_colour, ran);
           break;
         }
         default: // rest has to be t-channel
           connect_tchannel(it_part, colour, anti_colour, ran);
         }
     }
     // Connect last
     connect_incoming(incoming_[1], anti_colour, colour);
     assert(is_leading_colour());
     return true;
   } // generate_colours
 
   namespace {
     bool valid_parton(
       std::vector<fastjet::PseudoJet> const & jets,
       Particle const & parton, int const idx,
       double const max_ext_soft_pt_fraction, double const min_extparton_pt
     ){
       // TODO code overlap with PhaseSpacePoint::pass_extremal_cuts
       if(min_extparton_pt > parton.pt()) return false;
       if(idx<0) return false;
       assert(static_cast<int>(jets.size())>=idx);
       auto const & jet{ jets[idx] };
       if( (parton.p - jet).pt()/jet.pt() > max_ext_soft_pt_fraction)
         return false;
       return true;
     }
   }
 
   // this should work with multiple types
   bool Event::valid_hej_state(double const max_frac,
                               double const min_pt
   ) const {
     using namespace event_type;
     if(!is_resummable(type()))
       return false;
 
     auto const & jet_idx{ particle_jet_indices() };
     auto idx_begin{ jet_idx.cbegin() };
     auto idx_end{  jet_idx.crbegin() };
 
     auto part_begin{ cbegin_partons() };
     auto part_end{  crbegin_partons() };
 
     // always seperate extremal jets
     if( !valid_parton(jets(), *part_begin, *idx_begin, max_frac, min_pt) )
       return false;
     ++part_begin;
     ++idx_begin;
     if( !valid_parton(jets(), *part_end,   *idx_end,   max_frac, min_pt) )
       return false;
     ++part_end;
     ++idx_end;
 
     // unob -> second parton in own jet
     if( type() & (unob | qqxexb) ){
       if( !valid_parton(jets(), *part_begin, *idx_begin, max_frac, min_pt) )
         return false;
       ++part_begin;
       ++idx_begin;
     }
 
     if( type() & (unof | qqxexf) ){
       if( !valid_parton(jets(), *part_end,   *idx_end,   max_frac, min_pt) )
         return false;
       ++part_end;
       ++idx_end;
     }
 
     if( type() & qqxmid ){
       // find qqx pair
       auto begin_qqx{ std::find_if( part_begin, part_end.base(),
         [](Particle const & part) -> bool {
           return part.type != ParticleID::gluon;
         }
       )};
       assert(begin_qqx != part_end.base());
       long int qqx_pos{ std::distance(part_begin, begin_qqx) };
       assert(qqx_pos >= 0);
       idx_begin+=qqx_pos;
       if( !( valid_parton(jets(),*begin_qqx,    *idx_begin,    max_frac,min_pt)
           && valid_parton(jets(),*(++begin_qqx),*(++idx_begin),max_frac,min_pt)
       ))
         return false;
     }
     return true;
   }
 
   Event::ConstPartonIterator Event::begin_partons() const {
     return cbegin_partons();
   }
   Event::ConstPartonIterator Event::cbegin_partons() const {
     return boost::make_filter_iterator(
         static_cast<bool (*)(Particle const &)>(is_parton),
         cbegin(outgoing()),
         cend(outgoing())
     );
   }
 
   Event::ConstPartonIterator Event::end_partons() const {
     return cend_partons();
   }
   Event::ConstPartonIterator Event::cend_partons() const {
     return boost::make_filter_iterator(
         static_cast<bool (*)(Particle const &)>(is_parton),
         cend(outgoing()),
         cend(outgoing())
     );
   }
 
   Event::ConstReversePartonIterator Event::rbegin_partons() const {
     return crbegin_partons();
   }
   Event::ConstReversePartonIterator Event::crbegin_partons() const {
     return std::reverse_iterator<ConstPartonIterator>( cend_partons() );
   }
 
   Event::ConstReversePartonIterator Event::rend_partons() const {
     return crend_partons();
   }
   Event::ConstReversePartonIterator Event::crend_partons() const {
     return std::reverse_iterator<ConstPartonIterator>( cbegin_partons() );
   }
 
   Event::PartonIterator Event::begin_partons() {
     return boost::make_filter_iterator(
         static_cast<bool (*)(Particle const &)>(is_parton),
         begin(outgoing_),
         end(outgoing_)
     );
   }
 
   Event::PartonIterator Event::end_partons() {
     return boost::make_filter_iterator(
         static_cast<bool (*)(Particle const &)>(is_parton),
         end(outgoing_),
         end(outgoing_)
     );
   }
 
   Event::ReversePartonIterator Event::rbegin_partons() {
     return std::reverse_iterator<PartonIterator>( end_partons() );
   }
 
   Event::ReversePartonIterator Event::rend_partons() {
     return std::reverse_iterator<PartonIterator>( begin_partons() );
   }
 
   namespace {
     void print_momentum(std::ostream & os, fastjet::PseudoJet const & part){
     const std::streamsize orig_prec = os.precision();
       os <<std::scientific<<std::setprecision(6) << "["
         <<std::setw(13)<<std::right<< part.px() << ", "
         <<std::setw(13)<<std::right<< part.py() << ", "
         <<std::setw(13)<<std::right<< part.pz() << ", "
         <<std::setw(13)<<std::right<< part.E() << "]"<< std::fixed;
       os.precision(orig_prec);
     }
 
     void print_colour(std::ostream & os, optional<Colour> const & col){
       if(!col)
         os << "(no color)"; // American spelling for better alignment
       else
         os << "(" <<std::setw(3)<<std::right<< col->first
            << ", " <<std::setw(3)<<std::right<< col->second << ")";
     }
   }
 
   std::ostream& operator<<(std::ostream & os, Event const & ev){
     const std::streamsize orig_prec = os.precision();
     os <<std::setprecision(4)<<std::fixed;
     os << "########## " << event_type::name(ev.type()) << " ##########" << std::endl;
     os << "Incoming particles:\n";
     for(auto const & in: ev.incoming()){
       os <<std::setw(3)<< in.type << ": ";
       print_colour(os, in.colour);
       os << " ";
       print_momentum(os, in.p);
       os << std::endl;
     }
     os << "\nOutgoing particles: " << ev.outgoing().size() << "\n";
     for(auto const & out: ev.outgoing()){
       os <<std::setw(3)<< out.type << ": ";
       print_colour(os, out.colour);
       os << " ";
       print_momentum(os, out.p);
       os << " => rapidity="
         <<std::setw(7)<<std::right<< out.rapidity() << std::endl;
     }
     os << "\nForming Jets: " << ev.jets().size() << "\n";
     for(auto const & jet: ev.jets()){
       print_momentum(os, jet);
       os << " => rapidity="
         <<std::setw(7)<<std::right<< jet.rapidity() << std::endl;
     }
     if(ev.decays().size() > 0 ){
       os << "\nDecays: " << ev.decays().size() << "\n";
       for(auto const & decay: ev.decays()){
         os <<std::setw(3)<< ev.outgoing()[decay.first].type
           << " (" << decay.first << ") to:\n";
         for(auto const & out: decay.second){
           os <<"  "<<std::setw(3)<< out.type << ": ";
           print_momentum(os, out.p);
           os << " => rapidity="
             <<std::setw(7)<<std::right<< out.rapidity() << std::endl;
         }
       }
 
     }
     os << std::defaultfloat;
     os.precision(orig_prec);
     return os;
   }
 
   double shat(Event const & ev){
     return (ev.incoming()[0].p + ev.incoming()[1].p).m2();
   }
 
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * heprup){
     LHEF::HEPEUP result;
     result.heprup = heprup;
     result.weights = {{event.central().weight, nullptr}};
     for(auto const & var: event.variations()){
       result.weights.emplace_back(var.weight, nullptr);
     }
     size_t num_particles = event.incoming().size() + event.outgoing().size();
     for(auto const & decay: event.decays()) num_particles += decay.second.size();
     result.NUP = num_particles;
     // the following entries are pretty much meaningless
     result.IDPRUP = event.type();  // event type
     result.AQEDUP = 1./128.;  // alpha_EW
     //result.AQCDUP = 0.118 // alpha_QCD
     // end meaningless part
     result.XWGTUP = event.central().weight;
     result.SCALUP = event.central().muf;
     result.scales.muf = event.central().muf;
     result.scales.mur = event.central().mur;
     result.scales.SCALUP = event.central().muf;
     result.pdfinfo.p1 = event.incoming().front().type;
     result.pdfinfo.p2 = event.incoming().back().type;
     result.pdfinfo.scale = event.central().muf;
 
     result.IDUP.reserve(num_particles);   // PID
     result.ISTUP.reserve(num_particles);  // status (in, out, decay)
     result.PUP.reserve(num_particles);    // momentum
     result.MOTHUP.reserve(num_particles); // index mother particle
     result.ICOLUP.reserve(num_particles); // colour
     // incoming
     std::array<Particle, 2> incoming{ event.incoming() };
     // First incoming should be positive pz according to LHE standard
     // (or at least most (everyone?) do it this way, and Pythia assumes it)
     if(incoming[0].pz() < incoming[1].pz())
       std::swap(incoming[0], incoming[1]);
     for(Particle const & in: incoming){
       result.IDUP.emplace_back(in.type);
       result.ISTUP.emplace_back(status_in);
       result.PUP.push_back({in.p[0], in.p[1], in.p[2], in.p[3], in.p.m()});
       result.MOTHUP.emplace_back(0, 0);
       assert(in.colour);
       result.ICOLUP.emplace_back(*in.colour);
     }
     // outgoing
     for(size_t i = 0; i < event.outgoing().size(); ++i){
       Particle const & out = event.outgoing()[i];
       result.IDUP.emplace_back(out.type);
       const int status = event.decays().count(i)?status_decayed:status_out;
       result.ISTUP.emplace_back(status);
       result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()});
       result.MOTHUP.emplace_back(1, 2);
       if(out.colour)
         result.ICOLUP.emplace_back(*out.colour);
       else{
         result.ICOLUP.emplace_back(std::make_pair(0,0));
       }
     }
     // decays
     for(auto const & decay: event.decays()){
       for(auto const & out: decay.second){
         result.IDUP.emplace_back(out.type);
         result.ISTUP.emplace_back(status_out);
         result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()});
         const size_t mother_idx = 1 + event.incoming().size() + decay.first;
         result.MOTHUP.emplace_back(mother_idx, mother_idx);
         result.ICOLUP.emplace_back(0,0);
       }
     }
 
     assert(result.ICOLUP.size() == num_particles);
     static constexpr double unknown_spin = 9.;     //per Les Houches accord
     result.VTIMUP = std::vector<double>(num_particles, unknown_spin);
     result.SPINUP = result.VTIMUP;
     return result;
   }
 
 }
diff --git a/src/EventReader.cc b/src/EventReader.cc
index 8d10e6b..dccff81 100644
--- a/src/EventReader.cc
+++ b/src/EventReader.cc
@@ -1,73 +1,73 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/EventReader.hh"
 
 #include "HEJ/HDF5Reader.hh"
 #include "HEJ/ConfigFlags.hh"
 #include "HEJ/LesHouchesReader.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/Version.hh"
 
 namespace {
   enum class generator{
     HEJ,
     HEJFOG,
     Sherpa,
     MG,
     unknown
   };
 
   generator get_generator(
     LHEF::HEPRUP const & heprup, std::string const & header
   ){
     // try getting generator name from specific tag
     if(heprup.generators.size()>0){
       std::string const & gen_name = heprup.generators.back().name;
       if(gen_name == "HEJ" || gen_name == HEJ::Version::String())
         return generator::HEJ;
       if(gen_name == "HEJ Fixed Order Generation")
         return generator::HEJFOG;
       if(gen_name == "MadGraph5_aMC@NLO") return generator::MG;
       std::cerr << "Unknown LHE Generator " << gen_name
         << " using default LHE interface.\n";
       return generator::unknown;
     }
     // The generator tag is not always used -> check by hand
     if(header.find("generated with HEJ")!=std::string::npos)
       return generator::HEJ;
     if(header.find("# created by SHERPA")!=std::string::npos)
       return generator::Sherpa;
     if(header.find("<MGVersion>")!=std::string::npos)
       return generator::MG;
     std::cerr<<"Could not determine LHE Generator using default LHE interface.\n";
     return generator::unknown;
   }
 }
 
 namespace HEJ {
   std::unique_ptr<EventReader> make_reader(std::string const & filename) {
     try {
       auto reader{ std::make_unique<LesHouchesReader>(filename) };
       switch( get_generator(reader->heprup(), reader->header()) ){
         case generator::Sherpa:
           return std::make_unique<SherpaLHEReader>(filename);
         case generator::HEJ:
         case generator::HEJFOG:
         case generator::MG:
           //! @TODO we could directly fix the MG weights here similar to Sherpa
         default:
           return reader;
       }
     }
     catch(std::runtime_error&) {
 #ifdef HEJ_BUILD_WITH_HDF5
       return std::make_unique<HDF5Reader>(filename);
 #else
       throw;
 #endif
     }
   }
 }
diff --git a/src/EventReweighter.cc b/src/EventReweighter.cc
index 4d2f302..54b8491 100644
--- a/src/EventReweighter.cc
+++ b/src/EventReweighter.cc
@@ -1,256 +1,256 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/EventReweighter.hh"
 
 #include <algorithm>
 #include <assert.h>
 #include <limits>
 #include <math.h>
 #include <stddef.h>
 #include <string>
 #include <unordered_map>
 #include <utility>
 
 #include "fastjet/ClusterSequence.hh"
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Event.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/PhaseSpacePoint.hh"
 #include "HEJ/RNG.hh"
 
 namespace HEJ{
 
   EventReweighter::EventReweighter(
       LHEF::HEPRUP const & heprup,
       ScaleGenerator scale_gen,
       EventReweighterConfig conf,
       std::shared_ptr<RNG> ran
   ):
     EventReweighter{
       HEJ::Beam{
         heprup.EBMUP.first,
         {{
           static_cast<HEJ::ParticleID>(heprup.IDBMUP.first),
           static_cast<HEJ::ParticleID>(heprup.IDBMUP.second)
         }}
       },
       heprup.PDFSUP.first,
       std::move(scale_gen),
       std::move(conf),
       std::move(ran)
     }
   {
     if(heprup.EBMUP.second != E_beam_){
       throw std::invalid_argument(
           "asymmetric beam: " + std::to_string(E_beam_)
           + " ---> <--- " + std::to_string(heprup.EBMUP.second)
       );
     }
     if(heprup.PDFSUP.second != pdf_.id()){
       throw std::invalid_argument(
           "conflicting PDF ids: " + std::to_string(pdf_.id())
           + " vs. " + std::to_string(heprup.PDFSUP.second)
       );
     }
   }
 
   EventReweighter::EventReweighter(
       Beam beam,
       int pdf_id,
       ScaleGenerator scale_gen,
       EventReweighterConfig conf,
       std::shared_ptr<RNG> ran
   ):
     param_{std::move(conf)},
     E_beam_{beam.E},
     pdf_{pdf_id, beam.type.front(), beam.type.back()},
     MEt2_{
       [this](double mu){ return pdf_.Halphas(mu); },
       param_.ME_config
     },
     scale_gen_{std::move(scale_gen)},
     ran_{std::move(ran)}
   {
     assert(ran_);
   }
 
   PDF const & EventReweighter::pdf() const{
     return pdf_;
   }
 
   std::vector<Event> EventReweighter::reweight(
       Event const & input_ev, size_t num_events
   ){
     auto res_events{ gen_res_events(input_ev, num_events) };
     if(res_events.empty()) return {};
     for(auto & event: res_events) event = scale_gen_(std::move(event));
     return rescale(input_ev, std::move(res_events));
   }
 
   EventTreatment EventReweighter::treatment(EventType type) const {
     return param_.treat.at(type);
   }
 
   std::vector<Event> EventReweighter::gen_res_events(
       Event const & ev,
       size_t phase_space_points
   ){
     assert(ev.variations().empty());
     status_.clear();
 
     switch(treatment(ev.type())){
     case EventTreatment::discard: {
       status_.emplace_back(StatusCode::discard);
       return {};
     }
     case EventTreatment::keep:
       if(! jets_pass_resummation_cuts(ev)) {
         status_.emplace_back(StatusCode::failed_resummation_cuts);
         return {};
       }
       else {
         status_.emplace_back(StatusCode::good);
         return {ev};
       }
     default:;
     }
     const double Born_shat = shat(ev);
 
     std::vector<Event> resummation_events;
     status_.reserve(phase_space_points);
     for(size_t psp_number = 0; psp_number < phase_space_points; ++psp_number){
       PhaseSpacePoint psp{ev, param_.psp_config, *ran_};
       status_.emplace_back(psp.status());
       assert(psp.status() != StatusCode::unspecified);
       if(psp.status() != StatusCode::good) continue;
       assert(psp.weight() != 0.);
       if(psp.incoming()[0].E() > E_beam_ || psp.incoming()[1].E() > E_beam_) {
         status_.back() = StatusCode::too_much_energy;
         continue;
       }
 
       resummation_events.emplace_back(
         to_EventData( std::move(psp) ).cluster(
           param_.jet_param().def, param_.jet_param().min_pt
         )
       );
       auto & new_event = resummation_events.back();
       assert( new_event.valid_hej_state(
         param_.psp_config.max_ext_soft_pt_fraction,
         param_.psp_config.min_extparton_pt ) );
       if( new_event.type() != ev.type() )
         throw std::logic_error{"Resummation Event does not match Born event"};
       new_event.generate_colours(*ran_);
       assert(new_event.variations().empty());
       new_event.central().mur = ev.central().mur;
       new_event.central().muf = ev.central().muf;
       const double resum_shat = shat(new_event);
       new_event.central().weight *= ev.central().weight*Born_shat*Born_shat/
         (phase_space_points*resum_shat*resum_shat);
     }
     return resummation_events;
   }
 
   std::vector<Event> EventReweighter::rescale(
       Event const & Born_ev,
       std::vector<Event> events
   ) const{
     const double Born_pdf = pdf_factors(Born_ev).central;
     const double Born_ME = tree_matrix_element(Born_ev);
 
     for(auto & cur_event: events){
       const auto pdf = pdf_factors(cur_event);
       assert(pdf.variations.size() == cur_event.variations().size());
       const auto ME = matrix_elements(cur_event);
       assert(ME.variations.size() == cur_event.variations().size());
       cur_event.parameters() *= pdf*ME/(Born_pdf*Born_ME);
     }
     return events;
   }
 
   bool EventReweighter::jets_pass_resummation_cuts(
       Event const & ev
   ) const{
     const auto out_as_PseudoJet = to_PseudoJet(filter_partons(ev.outgoing()));
     fastjet::ClusterSequence cs{out_as_PseudoJet, param_.jet_param().def};
     return cs.inclusive_jets(param_.jet_param().min_pt).size() == ev.jets().size();
   }
 
   Weights EventReweighter::pdf_factors(Event const & ev) const{
     auto const & a = ev.incoming().front();
     auto const & b = ev.incoming().back();
     const double xa = a.p.e()/E_beam_;
     const double xb = b.p.e()/E_beam_;
 
     Weights result;
     std::unordered_map<double, double> known_pdf;
     result.central =
       pdf_.pdfpt(0,xa,ev.central().muf,a.type)*
       pdf_.pdfpt(1,xb,ev.central().muf,b.type);
     known_pdf.emplace(ev.central().muf, result.central);
 
     result.variations.reserve(ev.variations().size());
     for(auto const & ev_param: ev.variations()){
       const double muf = ev_param.muf;
       auto cur_pdf = known_pdf.find(muf);
       if(cur_pdf == known_pdf.end()){
         cur_pdf = known_pdf.emplace(
             muf,
             pdf_.pdfpt(0,xa,muf,a.type)*pdf_.pdfpt(1,xb,muf,b.type)
         ).first;
       }
       result.variations.emplace_back(cur_pdf->second);
     }
     assert(result.variations.size() == ev.variations().size());
     return result;
   }
 
   Weights
   EventReweighter::matrix_elements(Event const & ev) const{
     assert(param_.treat.count(ev.type()) > 0);
     if(param_.treat.find(ev.type())->second == EventTreatment::keep){
       return fixed_order_scale_ME(ev);
     }
 
     return MEt2_(ev);
   }
 
   double EventReweighter::tree_matrix_element(Event const & ev) const{
     assert(ev.variations().empty());
     assert(param_.treat.count(ev.type()) > 0);
     if(param_.treat.find(ev.type())->second == EventTreatment::keep){
       return fixed_order_scale_ME(ev).central;
     }
     return MEt2_.tree(ev).central;
   }
 
   Weights
   EventReweighter::fixed_order_scale_ME(Event const & ev) const{
     int alpha_s_power = 0;
     for(auto const & part: ev.outgoing()){
       if(is_parton(part))
         ++alpha_s_power;
       else if(part.type == pid::Higgs) {
         alpha_s_power += 2;
       }
       // nothing to do for other uncoloured particles
     }
 
     Weights result;
     result.central = pow(pdf_.Halphas(ev.central().mur), alpha_s_power);
     for(auto const & var: ev.variations()){
       result.variations.emplace_back(
           pow(pdf_.Halphas(var.mur), alpha_s_power)
       );
     }
     return result;
   }
 
 } // namespace HEJ
diff --git a/src/HDF5Reader.cc b/src/HDF5Reader.cc
index c4fec3d..32c73ac 100644
--- a/src/HDF5Reader.cc
+++ b/src/HDF5Reader.cc
@@ -1,306 +1,306 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/HDF5Reader.hh"
 
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_HDF5
 
 #include <numeric>
 #include <iterator>
 
 #include "highfive/H5File.hpp"
 
 namespace HEJ {
   namespace {
     // buffer size for reader
     // each "reading from disk" reads "chunk_size" many event at once
     constexpr std::size_t chunk_size = 10000;
 
     struct ParticleData {
       std::vector<int> id;
       std::vector<int> status;
       std::vector<int> mother1;
       std::vector<int> mother2;
       std::vector<int> color1;
       std::vector<int> color2;
       std::vector<double> px;
       std::vector<double> py;
       std::vector<double> pz;
       std::vector<double> e;
       std::vector<double> m;
       std::vector<double> lifetime;
       std::vector<double> spin;
     };
 
     struct EventRecords {
       std::vector<int> particle_start;
       std::vector<int> nparticles;
       std::vector<int> pid;
       std::vector<double> weight;
       std::vector<double> scale;
       std::vector<double> fscale;
       std::vector<double> rscale;
       std::vector<double> aqed;
       std::vector<double> aqcd;
       double trials;
       ParticleData particles;
     };
 
 class ConstEventIterator {
    public:
       // iterator traits
       using iterator_category = std::bidirectional_iterator_tag;
       using value_type = LHEF::HEPEUP;
       using difference_type = std::ptrdiff_t;
       using pointer = const LHEF::HEPEUP*;
       using reference = LHEF::HEPEUP const &;
 
       using iterator = ConstEventIterator;
       friend iterator cbegin(EventRecords const & records) noexcept;
       friend iterator cend(EventRecords const & records) noexcept;
 
       iterator& operator++() {
         particle_offset_ += records_.get().nparticles[idx_];
         ++idx_;
         return *this;
       }
       iterator& operator--() {
         --idx_;
         particle_offset_ -= records_.get().nparticles[idx_];
         return *this;
       }
       iterator operator--(int) {
         auto res = *this;
         --(*this);
         return res;
       }
       bool operator==(iterator const & other) const {
         return idx_ == other.idx_;
       }
       bool operator!=(iterator other) const {
         return !(*this == other);
       }
       value_type operator*() const {
         value_type hepeup{};
         auto const & r = records_.get();
         hepeup.NUP        = r.nparticles[idx_];
         hepeup.IDPRUP     = r.pid[idx_];
         hepeup.XWGTUP     = r.weight[idx_]/r.trials;
         hepeup.weights.emplace_back(hepeup.XWGTUP, nullptr);
         hepeup.SCALUP     = r.scale[idx_];
         hepeup.scales.muf = r.fscale[idx_];
         hepeup.scales.mur = r.rscale[idx_];
         hepeup.AQEDUP     = r.aqed[idx_];
         hepeup.AQCDUP     = r.aqcd[idx_];
         const size_t start = particle_offset_;
         const size_t end   = start + hepeup.NUP;
         auto const & p = r.particles;
         hepeup.IDUP    = std::vector<long>(   begin(p.id)+start,       begin(p.id)+end );
         hepeup.ISTUP   = std::vector<int>(    begin(p.status)+start,   begin(p.status)+end );
         hepeup.VTIMUP  = std::vector<double>( begin(p.lifetime)+start, begin(p.lifetime)+end );
         hepeup.SPINUP  = std::vector<double>( begin(p.spin)+start,     begin(p.spin)+end );
         hepeup.MOTHUP.resize(hepeup.NUP);
         hepeup.ICOLUP.resize(hepeup.NUP);
         hepeup.PUP.resize(hepeup.NUP);
         for(size_t i = 0; i < hepeup.MOTHUP.size(); ++i) {
           const size_t idx = start + i;
           assert(idx < end);
           hepeup.MOTHUP[i] = std::make_pair(p.mother1[idx], p.mother2[idx]);
           hepeup.ICOLUP[i] = std::make_pair(p.color1[idx], p.color2[idx]);
           hepeup.PUP[i]    = std::vector<double>{
             p.px[idx], p.py[idx], p.pz[idx], p.e[idx], p.m[idx]
           };
         }
         return hepeup;
       }
 
     private:
       explicit ConstEventIterator(EventRecords const & records):
         records_{records} {}
 
       std::reference_wrapper<const EventRecords> records_;
       size_t idx_ = 0;
       size_t particle_offset_ = 0;
     }; // end ConstEventIterator
 
     ConstEventIterator cbegin(EventRecords const & records) noexcept {
       return ConstEventIterator{records};
     }
 
     ConstEventIterator cend(EventRecords const & records) noexcept {
       auto it =ConstEventIterator{records};
       it.idx_ = records.aqcd.size(); // or size of any other records member
       return it;
     }
 
   } // end anonymous namespace
 
   struct HDF5Reader::HDF5ReaderImpl{
     HighFive::File file;
     std::size_t event_idx;
     std::size_t particle_idx;
     std::size_t nevents;
 
     EventRecords records;
     ConstEventIterator cur_event;
 
     LHEF::HEPRUP heprup;
     LHEF::HEPEUP hepeup;
 
     explicit HDF5ReaderImpl(std::string const & filename):
       file{filename},
       event_idx{0},
       particle_idx{0},
       nevents{
         file.getGroup("event")
         .getDataSet("nparticles") // or any other dataset
         .getSpace().getDimensions().front()
       },
       records{},
       cur_event{cbegin(records)},
       heprup{},
       hepeup{}
     {
       read_heprup();
       read_event_records(chunk_size);
     }
 
     void read_heprup() {
       const auto init = file.getGroup("init");
       init.getDataSet( "PDFgroupA"         ).read(heprup.PDFGUP.first);
       init.getDataSet( "PDFgroupB"         ).read(heprup.PDFGUP.second);
       init.getDataSet( "PDFsetA"           ).read(heprup.PDFSUP.first);
       init.getDataSet( "PDFsetB"           ).read(heprup.PDFSUP.second);
       init.getDataSet( "beamA"             ).read(heprup.IDBMUP.first);
       init.getDataSet( "beamB"             ).read(heprup.IDBMUP.second);
       init.getDataSet( "energyA"           ).read(heprup.EBMUP.first);
       init.getDataSet( "energyB"           ).read(heprup.EBMUP.second);
       init.getDataSet( "numProcesses"      ).read(heprup.NPRUP);
       init.getDataSet( "weightingStrategy" ).read(heprup.IDWTUP);
       const auto proc_info = file.getGroup("procInfo");
       proc_info.getDataSet( "procId"     ).read(heprup.LPRUP);
       proc_info.getDataSet( "xSection"   ).read(heprup.XSECUP);
       proc_info.getDataSet( "error"      ).read(heprup.XERRUP);
       // TODO: is this identification correct?
       proc_info.getDataSet( "unitWeight" ).read(heprup.XMAXUP);
       std::vector<double> trials;
       file.getGroup("event").getDataSet("trials").read(trials);
       records.trials = std::accumulate(begin(trials), end(trials), 0.);
     }
 
     std::size_t read_event_records(std::size_t count) {
       count = std::min(count, nevents-event_idx);
 
       auto events = file.getGroup("event");
       events.getDataSet("nparticles").select({event_idx}, {count}).read(records.nparticles);
       assert(records.nparticles.size() == count);
       events.getDataSet("pid").select(    {event_idx}, {count} ).read( records.pid );
       events.getDataSet("weight").select( {event_idx}, {count} ).read( records.weight );
       events.getDataSet("scale").select(  {event_idx}, {count} ).read( records.scale );
       events.getDataSet("fscale").select( {event_idx}, {count} ).read( records.fscale );
       events.getDataSet("rscale").select( {event_idx}, {count} ).read( records.rscale );
       events.getDataSet("aqed").select(   {event_idx}, {count} ).read( records.aqed );
       events.getDataSet("aqcd").select(   {event_idx}, {count} ).read( records.aqcd );
       const std::size_t particle_count = std::accumulate(
           begin(records.nparticles), end(records.nparticles), 0
       );
       auto pdata = file.getGroup("particle");
       auto & particles = records.particles;
       pdata.getDataSet("id").select(       {particle_idx}, {particle_count} ).read( particles.id );
       pdata.getDataSet("status").select(   {particle_idx}, {particle_count} ).read( particles.status );
       pdata.getDataSet("mother1").select(  {particle_idx}, {particle_count} ).read( particles.mother1 );
       pdata.getDataSet("mother2").select(  {particle_idx}, {particle_count} ).read( particles.mother2 );
       pdata.getDataSet("color1").select(   {particle_idx}, {particle_count} ).read( particles.color1 );
       pdata.getDataSet("color2").select(   {particle_idx}, {particle_count} ).read( particles.color2 );
       pdata.getDataSet("px").select(       {particle_idx}, {particle_count} ).read( particles.px );
       pdata.getDataSet("py").select(       {particle_idx}, {particle_count} ).read( particles.py );
       pdata.getDataSet("pz").select(       {particle_idx}, {particle_count} ).read( particles.pz );
       pdata.getDataSet("e").select(        {particle_idx}, {particle_count} ).read( particles.e );
       pdata.getDataSet("m").select(        {particle_idx}, {particle_count} ).read( particles.m );
       pdata.getDataSet("lifetime").select( {particle_idx}, {particle_count} ).read( particles.lifetime );
       pdata.getDataSet("spin").select(     {particle_idx}, {particle_count} ).read( particles.spin );
 
       event_idx += count;
       particle_idx += particle_count;
       return count;
     }
   };
 
   HDF5Reader::HDF5Reader(std::string const & filename):
     impl_{std::make_unique<HDF5ReaderImpl>(filename)}
   {}
 
   bool HDF5Reader::read_event() {
     if(impl_->cur_event == cend(impl_->records)) {
       // end of active chunk, read new events from file
       const auto events_read = impl_->read_event_records(chunk_size);
       impl_->cur_event = cbegin(impl_->records);
       if(events_read == 0) return false;
     }
     impl_->hepeup = *impl_->cur_event;
     ++impl_->cur_event;
     return true;
   }
 
   namespace {
     static const std::string nothing = "";
   }
 
   std::string const & HDF5Reader::header() const {
     return nothing;
   }
 
   LHEF::HEPRUP const & HDF5Reader::heprup() const {
     return impl_->heprup;
   }
 
   LHEF::HEPEUP const & HDF5Reader::hepeup() const {
     return impl_->hepeup;
   }
   HEJ::optional<size_t> HDF5Reader::number_events() const {
     return impl_->nevents;
   }
 }
 
 #else // no HDF5 support
 
 namespace HEJ {
   class HDF5Reader::HDF5ReaderImpl{};
 
   HDF5Reader::HDF5Reader(std::string const &){
     throw std::invalid_argument{
           "Failed to create HDF5 reader: "
           "HEJ 2 was built without HDF5 support"
     };
   }
 
   bool HDF5Reader::read_event() {
     throw std::logic_error{"unreachable"};
   }
 
   std::string const & HDF5Reader::header() const {
     throw std::logic_error{"unreachable"};
   }
 
   LHEF::HEPRUP const & HDF5Reader::heprup() const {
     throw std::logic_error{"unreachable"};
   }
 
   LHEF::HEPEUP const & HDF5Reader::hepeup() const {
     throw std::logic_error{"unreachable"};
   }
 
   HEJ::optional<size_t> HDF5Reader::number_events() const {
     throw std::logic_error{"unreachable"};
   }
 }
 
 #endif
 
 namespace HEJ {
   HDF5Reader::~HDF5Reader() = default;
 }
diff --git a/src/HDF5Writer.cc b/src/HDF5Writer.cc
index 1ff3525..ec9640f 100644
--- a/src/HDF5Writer.cc
+++ b/src/HDF5Writer.cc
@@ -1,403 +1,403 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/HDF5Writer.hh"
 
 #include <cassert>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_HDF5
 
 #include <type_traits>
 #include <iterator>
 
 #include "HEJ/event_types.hh"
 #include "HEJ/Event.hh"
 
 #include "highfive/H5File.hpp"
 
 namespace HEJ{
 
   using HighFive::File;
   using HighFive::DataSpace;
 
   namespace{
     constexpr std::size_t chunk_size = 1000;
     constexpr unsigned compression_level = 3;
 
     size_t to_index(event_type::EventType const type){
       return type==0?0:floor(log2(type))+1;
     }
 
     template<typename T>
     void write_dataset(HighFive::Group & group, std::string const & name, T val) {
       using data_t = std::decay_t<T>;
       group.createDataSet<data_t>(name, DataSpace::From(val)).write(val);
     }
 
     template<typename T>
     void write_dataset(
         HighFive::Group & group, std::string const & name,
         std::vector<T> const & val
     ) {
       using data_t = std::decay_t<T>;
       group.createDataSet<data_t>(name, DataSpace::From(val)).write(val);
     }
 
     struct Cache {
       explicit Cache(size_t capacity):
         capacity{capacity}
       {
         nparticles.reserve(capacity);
         start.reserve(capacity);
         pid.reserve(capacity);
         weight.reserve(capacity);
         scale.reserve(capacity);
         fscale.reserve(capacity);
         rscale.reserve(capacity);
         aqed.reserve(capacity);
         aqcd.reserve(capacity);
         trials.reserve(capacity);
         npLO.reserve(capacity);
         npNLO.reserve(capacity);
       }
 
       void fill(HEJ::Event ev) {
         const auto hepeup = to_HEPEUP(ev, nullptr);
 
         // HEJ event to get nice wrapper
         const auto num_partons = std::distance(ev.cbegin_partons(),
                                                ev.cend_partons());
         assert(num_partons>0);
         // Number of partons for LO matching, HEJ requires at least 2 partons
         npLO.emplace_back(num_partons>1?num_partons-2:num_partons);
         // Number of real emissions in  NLO, HEJ is LO -> -1
         npNLO.emplace_back(-1);
 
         fill_event_params(hepeup);
         fill_event_particles(hepeup);
       }
 
       void fill_event_params(LHEF::HEPEUP const & ev) {
         nparticles.emplace_back(ev.NUP);
         start.emplace_back(particle_pos);
         pid.emplace_back(ev.IDPRUP);
         weight.emplace_back(ev.XWGTUP);
         scale.emplace_back(ev.SCALUP);
         fscale.emplace_back(ev.scales.muf);
         rscale.emplace_back(ev.scales.mur);
         aqed.emplace_back(ev.AQEDUP);
         aqcd.emplace_back(ev.AQCDUP);
         // set first trial=1 for first event
         // -> sum(trials) = 1 -> xs=sum(weights)/sum(trials) as in Sherpa
         if(particle_pos == 0){
           trials.emplace_back(1.);
         } else {
           trials.emplace_back(0.);
         }
         particle_pos += ev.NUP;
       }
 
       void fill_event_particles(LHEF::HEPEUP const & ev) {
         id.insert(end(id), begin(ev.IDUP), end(ev.IDUP));
         status.insert(end(status), begin(ev.ISTUP), end(ev.ISTUP));
         lifetime.insert(end(lifetime), begin(ev.VTIMUP), end(ev.VTIMUP));
         spin.insert(end(spin), begin(ev.SPINUP), end(ev.SPINUP));
         for(int i = 0; i < ev.NUP; ++i) {
           mother1.emplace_back(ev.MOTHUP[i].first);
           mother2.emplace_back(ev.MOTHUP[i].second);
           color1.emplace_back(ev.ICOLUP[i].first);
           color2.emplace_back(ev.ICOLUP[i].second);
           px.emplace_back(ev.PUP[i][0]);
           py.emplace_back(ev.PUP[i][1]);
           pz.emplace_back(ev.PUP[i][2]);
           e.emplace_back(ev.PUP[i][3]);
           m.emplace_back(ev.PUP[i][4]);
         }
       }
 
       bool is_full() const {
         return nparticles.size() >= capacity;
       }
 
       void clear() {
         nparticles.clear();
         start.clear();
         pid.clear();
         id.clear();
         status.clear();
         mother1.clear();
         mother2.clear();
         color1.clear();
         color2.clear();
         weight.clear();
         scale.clear();
         fscale.clear();
         rscale.clear();
         aqed.clear();
         aqcd.clear();
         trials.clear();
         npLO.clear();
         npNLO.clear();
         px.clear();
         py.clear();
         pz.clear();
         e.clear();
         m.clear();
         lifetime.clear();
         spin.clear();
       }
 
       size_t capacity;
       std::vector<int> nparticles, start, pid, id, status,
         mother1, mother2, color1, color2, npLO, npNLO;
       std::vector<double> weight, scale, fscale, rscale, aqed,
         aqcd, trials, px, py, pz, e, m, lifetime, spin;
 
     private:
       size_t particle_pos = 0;
     };
 
   }
 
   struct HDF5Writer::HDF5WriterImpl{
     File file;
     LHEF::HEPRUP heprup;
     Cache cache{chunk_size};
     size_t event_idx = 0;
     size_t particle_idx = 0;
 
     HDF5WriterImpl(std::string const & filename, LHEF::HEPRUP && hepr):
       file{filename, File::ReadWrite | File::Create | File::Truncate},
       heprup{std::move(hepr)}
     {
       // TODO: code duplication with Les Houches Writer
       const int max_number_types = to_index(event_type::last_type)+1;
       heprup.NPRUP = max_number_types;
       // ids of event types
       heprup.LPRUP.clear();
       heprup.LPRUP.reserve(max_number_types);
       heprup.LPRUP.emplace_back(0);
       for(size_t i=event_type::first_type+1; i<=event_type::last_type; i*=2) {
         heprup.LPRUP.emplace_back(i);
       }
 
       heprup.XSECUP = std::vector<double>(max_number_types, 0.);
       heprup.XERRUP = std::vector<double>(max_number_types, 0.);
       heprup.XMAXUP = std::vector<double>(max_number_types, 0.);
 
       write_init();
       create_event_group();
       create_particle_group();
     }
 
     void write_init() {
       auto init = file.createGroup("init");
 
       write_dataset(init, "PDFgroupA"        , heprup.PDFGUP.first);
       write_dataset(init, "PDFgroupB"        , heprup.PDFGUP.second);
       write_dataset(init, "PDFsetA"          , heprup.PDFSUP.first);
       write_dataset(init, "PDFsetB"          , heprup.PDFSUP.second);
       write_dataset(init, "beamA"            , heprup.IDBMUP.first);
       write_dataset(init, "beamB"            , heprup.IDBMUP.second);
       write_dataset(init, "energyA"          , heprup.EBMUP.first);
       write_dataset(init, "energyB"          , heprup.EBMUP.second);
       write_dataset(init, "numProcesses"     , heprup.NPRUP);
       write_dataset(init, "weightingStrategy", heprup.IDWTUP);
 
       auto proc_info = file.createGroup("procInfo");
       write_dataset(proc_info, "procId", heprup.LPRUP);
     }
 
     static HighFive::DataSetCreateProps const & hdf5_chunk() {
       static const auto props = [](){
         HighFive::DataSetCreateProps props;
         props.add(HighFive::Chunking({chunk_size}));
         props.add(HighFive::Deflate(compression_level));
         return props;
       }();
       return props;
     }
 
     void create_event_group() {
       static const auto dim = DataSpace({0}, {DataSpace::UNLIMITED});
       auto events = file.createGroup("event");
       events.createDataSet<int>("nparticles", dim, hdf5_chunk());
       events.createDataSet<int>("start", dim, hdf5_chunk());
       events.createDataSet<int>("pid", dim, hdf5_chunk());
       events.createDataSet<double>("weight", dim, hdf5_chunk());
       events.createDataSet<double>("scale", dim, hdf5_chunk());
       events.createDataSet<double>("fscale", dim, hdf5_chunk());
       events.createDataSet<double>("rscale", dim, hdf5_chunk());
       events.createDataSet<double>("aqed", dim, hdf5_chunk());
       events.createDataSet<double>("aqcd", dim, hdf5_chunk());
       events.createDataSet<double>("trials", dim, hdf5_chunk());
       events.createDataSet<int>("npLO", dim, hdf5_chunk());
       events.createDataSet<int>("npNLO", dim, hdf5_chunk());
     }
 
     void resize_event_group(size_t new_size) {
       auto events = file.getGroup("event");
       events.getDataSet("nparticles").resize({new_size});
       events.getDataSet("start").resize({new_size});
       events.getDataSet("pid").resize({new_size});
       events.getDataSet("weight").resize({new_size});
       events.getDataSet("scale").resize({new_size});
       events.getDataSet("fscale").resize({new_size});
       events.getDataSet("rscale").resize({new_size});
       events.getDataSet("aqed").resize({new_size});
       events.getDataSet("aqcd").resize({new_size});
       events.getDataSet("trials").resize({new_size});
       events.getDataSet("npLO").resize({new_size});
       events.getDataSet("npNLO").resize({new_size});
     }
 
     void create_particle_group() {
       static const auto dim = DataSpace({0}, {DataSpace::UNLIMITED});
       auto particles = file.createGroup("particle");
       particles.createDataSet<int>("id", dim, hdf5_chunk());
       particles.createDataSet<int>("status", dim, hdf5_chunk());
       particles.createDataSet<int>("mother1", dim, hdf5_chunk());
       particles.createDataSet<int>("mother2", dim, hdf5_chunk());
       particles.createDataSet<int>("color1", dim, hdf5_chunk());
       particles.createDataSet<int>("color2", dim, hdf5_chunk());
       particles.createDataSet<double>("px", dim, hdf5_chunk());
       particles.createDataSet<double>("py", dim, hdf5_chunk());
       particles.createDataSet<double>("pz", dim, hdf5_chunk());
       particles.createDataSet<double>("e", dim, hdf5_chunk());
       particles.createDataSet<double>("m", dim, hdf5_chunk());
       particles.createDataSet<double>("lifetime", dim, hdf5_chunk());
       particles.createDataSet<double>("spin", dim, hdf5_chunk());
     }
 
     void resize_particle_group(size_t new_size) {
       auto particles = file.getGroup("particle");
       particles.getDataSet("id").resize({new_size});
       particles.getDataSet("status").resize({new_size});
       particles.getDataSet("mother1").resize({new_size});
       particles.getDataSet("mother2").resize({new_size});
       particles.getDataSet("color1").resize({new_size});
       particles.getDataSet("color2").resize({new_size});
       particles.getDataSet("px").resize({new_size});
       particles.getDataSet("py").resize({new_size});
       particles.getDataSet("pz").resize({new_size});
       particles.getDataSet("e").resize({new_size});
       particles.getDataSet("m").resize({new_size});
       particles.getDataSet("lifetime").resize({new_size});
       particles.getDataSet("spin").resize({new_size});
     }
 
     void write(Event const & ev){
       cache.fill(ev);
       if(cache.is_full()) {
         dump_cache();
       }
       const double wt = ev.central().weight;
       const size_t idx = to_index(ev.type());
       heprup.XSECUP[idx] += wt;
       heprup.XERRUP[idx] += wt*wt;
       if(wt > heprup.XMAXUP[idx]){
         heprup.XMAXUP[idx] = wt;
       }
     }
 
     void dump_cache() {
       write_event_params();
       write_event_particles();
       cache.clear();
     }
 
     void write_event_params() {
       auto events = file.getGroup("event");
       // choose arbitrary dataset to find size
       const auto dataset = events.getDataSet("nparticles");
       const size_t size = dataset.getSpace().getDimensions().front();
       resize_event_group(size + cache.nparticles.size());
 
 #define WRITE_FROM_CACHE(GROUP, PROPERTY)                               \
       GROUP.getDataSet(#PROPERTY).select({size}, {cache.PROPERTY.size()}).write(cache.PROPERTY)
 
       WRITE_FROM_CACHE(events, nparticles);
       WRITE_FROM_CACHE(events, start);
       WRITE_FROM_CACHE(events, pid);
       WRITE_FROM_CACHE(events, weight);
       WRITE_FROM_CACHE(events, scale);
       WRITE_FROM_CACHE(events, fscale);
       WRITE_FROM_CACHE(events, rscale);
       WRITE_FROM_CACHE(events, aqed);
       WRITE_FROM_CACHE(events, aqcd);
       WRITE_FROM_CACHE(events, trials);
       WRITE_FROM_CACHE(events, npLO);
       WRITE_FROM_CACHE(events, npNLO);
     }
 
     void write_event_particles() {
       auto particles = file.getGroup("particle");
       // choose arbitrary dataset to find size
       const auto dataset = particles.getDataSet("id");
       const size_t size = dataset.getSpace().getDimensions().front();
       resize_particle_group(size + cache.id.size());
       WRITE_FROM_CACHE(particles, id);
       WRITE_FROM_CACHE(particles, status);
       WRITE_FROM_CACHE(particles, lifetime);
       WRITE_FROM_CACHE(particles, spin);
       WRITE_FROM_CACHE(particles, mother1);
       WRITE_FROM_CACHE(particles, mother2);
       WRITE_FROM_CACHE(particles, color1);
       WRITE_FROM_CACHE(particles, color2);
       WRITE_FROM_CACHE(particles, px);
       WRITE_FROM_CACHE(particles, py);
       WRITE_FROM_CACHE(particles, pz);
       WRITE_FROM_CACHE(particles, e);
       WRITE_FROM_CACHE(particles, m);
     }
 #undef WRITE_FROM_CACHE
 
     ~HDF5WriterImpl(){
       dump_cache();
       auto proc_info = file.getGroup("procInfo");
       write_dataset(proc_info, "xSection", heprup.XSECUP);
       write_dataset(proc_info, "error", heprup.XERRUP);
       write_dataset(proc_info, "unitWeight", heprup.XMAXUP);
     }
 
   };
 
   HDF5Writer::HDF5Writer(std::string const & filename, LHEF::HEPRUP heprup):
     impl_{new HDF5Writer::HDF5WriterImpl{filename, std::move(heprup)}}
   {}
 
   void HDF5Writer::write(Event const & ev){
     impl_->write(ev);
   }
 }
 
 #else // no HDF5 support
 
 namespace HEJ{
 
   class HDF5Writer::HDF5WriterImpl{};
 
   HDF5Writer::HDF5Writer(std::string const &, LHEF::HEPRUP){
     throw std::invalid_argument{
       "Failed to create HDF5 writer: "
       "HEJ 2 was built without HDF5 support"
     };
   }
 
   void HDF5Writer::write(Event const &){
     assert(false);
   }
 
 }
 
 #endif
 
 namespace HEJ {
   HDF5Writer::~HDF5Writer() = default;
 }
diff --git a/src/HepMC2Interface.cc b/src/HepMC2Interface.cc
index 9e11d78..e63f1d1 100644
--- a/src/HepMC2Interface.cc
+++ b/src/HepMC2Interface.cc
@@ -1,156 +1,156 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/HepMC2Interface.hh"
 
 #include "HEJ/exceptions.hh"
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_HepMC2
 
 #include <math.h>
 #include <utility>
 
 #include "HEJ/detail/HepMCInterface_common.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/Particle.hh"
 
 #include "LHEF/LHEF.h"
 
 #include "HepMC/GenCrossSection.h"
 #include "HepMC/GenEvent.h"
 #include "HepMC/GenParticle.h"
 #include "HepMC/GenVertex.h"
 
 namespace HEJ{
 
   namespace detail_HepMC {
     template<>
     struct HepMCVersion<2> {
       using GenEvent = HepMC::GenEvent;
       using Beam = std::array<HepMC::GenParticle*,2>;
     };
 
     template<>
     auto make_particle_ptr<2> (
         Particle const & sp, int status
     ) {
       return new HepMC::GenParticle(
           to_FourVector<HepMC::FourVector>(sp),
           static_cast<int> (sp.type),
           status
       );
     }
 
     template<>
     auto make_vx_ptr<2>() {
       return new HepMC::GenVertex();
     }
   }
 
   HepMC2Interface::HepMC2Interface(LHEF::HEPRUP const & heprup):
     event_count_(0.), tot_weight_(0.), tot_weight2_(0.)
     {
       beam_particle_[0] = static_cast<ParticleID>(heprup.IDBMUP.first);
       beam_particle_[1] = static_cast<ParticleID>(heprup.IDBMUP.second);
       beam_energy_[0] = heprup.EBMUP.first;
       beam_energy_[1] = heprup.EBMUP.second;
     }
 
   HepMC::GenCrossSection HepMC2Interface::cross_section() const {
     HepMC::GenCrossSection xs;
     xs.set_cross_section(tot_weight_, sqrt(tot_weight2_));
     return xs;
   }
 
   HepMC::GenEvent HepMC2Interface::init_event(Event const & event) const {
 
     const std::array<HepMC::GenParticle*,2> beam {
       new HepMC::GenParticle(
         HepMC::FourVector(0,0,-beam_energy_[0],beam_energy_[0]),
         beam_particle_[0], detail_HepMC::status_beam ),
       new HepMC::GenParticle(
         HepMC::FourVector(0,0, beam_energy_[1],beam_energy_[1]),
         beam_particle_[1], detail_HepMC::status_beam )
     };
 
     auto hepmc_ev{ detail_HepMC::HepMC_init_kinematics<2>(
         event, beam, HepMC::GenEvent{ HepMC::Units::GEV, HepMC::Units::MM }
     ) };
     hepmc_ev.weights().push_back( event.central().weight );
     for(auto const & var: event.variations()){
       hepmc_ev.weights().push_back( var.weight );
       // no weight name for HepMC2 since rivet3 seem to mix them up
       // (could be added via hepmc_ev.weights()[name]=weight)
     }
     return hepmc_ev;
   }
 
   void HepMC2Interface::set_central(
     HepMC::GenEvent & out_ev, Event const & event, ssize_t const weight_index
   ){
     EventParameters event_param;
     if(weight_index < 0)
       event_param = event.central();
     else if ( static_cast<size_t>(weight_index) < event.variations().size())
       event_param = event.variations(weight_index);
     else
       throw std::invalid_argument{
          "HepMC2Interface tried to access a weight outside of the variation range."
       };
     const double wt = event_param.weight;
     tot_weight_ += wt;
     tot_weight2_ += wt * wt;
     ++event_count_;
 
     // central always on first
     assert(out_ev.weights().size() == event.variations().size()+1);
     out_ev.weights()[0] = wt;
 
     out_ev.set_cross_section( cross_section() );
     out_ev.set_signal_process_id(event.type());
     out_ev.set_event_scale(event_param.mur);
 
     out_ev.set_event_number(event_count_);
 
     /// @TODO add alphaQCD (need function) and alphaQED
     /// @TODO output pdf (currently not avaiable from event alone)
   }
 
   HepMC::GenEvent HepMC2Interface::operator()(Event const & event,
       ssize_t const weight_index
   ){
     HepMC::GenEvent out_ev(init_event(event));
     set_central(out_ev, event, weight_index);
     return out_ev;
   }
 
 }
 #else // no HepMC2 => empty class
 namespace HepMC {
   class GenEvent {};
   class GenCrossSection {};
 }
 namespace HEJ{
   HepMC2Interface::HepMC2Interface(LHEF::HEPRUP const &){
       throw std::invalid_argument(
           "Failed to create HepMC2Interface: "
           "HEJ 2 was built without HepMC2 support"
       );
   }
 
   HepMC::GenEvent HepMC2Interface::operator()(Event const &, ssize_t)
   {return HepMC::GenEvent();}
   HepMC::GenEvent HepMC2Interface::init_event(Event const &) const
   {return HepMC::GenEvent();}
   void HepMC2Interface::set_central(HepMC::GenEvent &, Event const &, ssize_t){}
   HepMC::GenCrossSection HepMC2Interface::cross_section() const
   {return HepMC::GenCrossSection();}
 }
 #endif
 
 namespace HEJ{
   HepMC2Interface::~HepMC2Interface() = default;
 }
diff --git a/src/HepMC2Writer.cc b/src/HepMC2Writer.cc
index 8cf5076..78824d3 100644
--- a/src/HepMC2Writer.cc
+++ b/src/HepMC2Writer.cc
@@ -1,82 +1,82 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/HepMC2Writer.hh"
 
 #include <cassert>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_HepMC2
 
 #include "HepMC/IO_GenEvent.h"
 
 #include <utility>
 
 #include "HepMC/GenParticle.h"
 #include "HepMC/GenVertex.h"
 
 #include "HEJ/Event.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/HepMC2Interface.hh"
 
 namespace HEJ{
 
   struct HepMC2Writer::HepMC2WriterImpl{
     HepMC2Interface hepmc_;
 
     HepMC2WriterImpl & operator=(HepMC2WriterImpl const & other) = delete;
     HepMC2WriterImpl(HepMC2WriterImpl const & other) = delete;
     HepMC2WriterImpl & operator=(HepMC2WriterImpl && other) = delete;
     HepMC2WriterImpl(HepMC2WriterImpl && other) = delete;
     HepMC::IO_GenEvent writer_;
 
     HepMC2WriterImpl(
         std::string const & file, LHEF::HEPRUP && heprup
     ):
       hepmc_(heprup),
       writer_{file}
     {}
 
     void write(Event const & ev){
       auto out_ev = hepmc_(ev);
       writer_.write_event(&out_ev);
     }
   };
 
   HepMC2Writer::HepMC2Writer(std::string const & file, LHEF::HEPRUP heprup):
     impl_{new HepMC2WriterImpl{file, std::move(heprup)}}
   {}
 
   void HepMC2Writer::write(Event const & ev){
     impl_->write(ev);
   }
 } // namespace HEJ
 
 #else // no HepMC2
 
 namespace HEJ{
 
   class HepMC2Writer::HepMC2WriterImpl{};
 
   HepMC2Writer::HepMC2Writer(std::string const &, LHEF::HEPRUP){
       throw std::invalid_argument(
           "Failed to create HepMC writer: "
           "HEJ 2 was built without HepMC2 support"
       );
   }
 
   void HepMC2Writer::write(Event const &){
     assert(false);
   }
 
 }
 #endif
 
 namespace HEJ{
   HepMC2Writer::~HepMC2Writer() = default;
 }
diff --git a/src/HepMC3Interface.cc b/src/HepMC3Interface.cc
index 91e9405..84beaee 100644
--- a/src/HepMC3Interface.cc
+++ b/src/HepMC3Interface.cc
@@ -1,203 +1,203 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/HepMC3Interface.hh"
 
 #include "HEJ/exceptions.hh"
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_HepMC3
 
 #include <math.h>
 #include <utility>
 
 #include "HEJ/detail/HepMCInterface_common.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/Particle.hh"
 
 #include "LHEF/LHEF.h"
 
 #include "HepMC3/GenCrossSection.h"
 #include "HepMC3/GenEvent.h"
 #include "HepMC3/GenParticle.h"
 #include "HepMC3/GenRunInfo.h"
 #include "HepMC3/GenVertex.h"
 #include "HepMC3/LHEFAttributes.h"
 
 namespace HEJ{
 
   namespace detail_HepMC {
     template<>
     struct HepMCVersion<3> {
       using GenEvent = HepMC3::GenEvent;
       using Beam = std::array<HepMC3::GenParticlePtr,2>;
     };
 
     template<>
     auto make_particle_ptr<3> (
         Particle const & sp, int status
     ) {
       return HepMC3::make_shared<HepMC3::GenParticle>(
           to_FourVector<HepMC3::FourVector>(sp),
           static_cast<int> (sp.type),
           status
       );
     }
 
     template<>
     auto make_vx_ptr<3>() {
       return HepMC3::make_shared<HepMC3::GenVertex>();
     }
   }
 
   namespace {
     void reset_weight_info(LHEF::HEPRUP & heprup){
       heprup.IDWTUP = 2;
       // use placeholders for unknown init block values
       // we can overwrite them after processing all events
       heprup.XSECUP = {0.};
       heprup.XERRUP = {0.};
       heprup.XMAXUP = {0.};
     }
 
     HepMC3::shared_ptr<HepMC3::GenRunInfo> init_runinfo(LHEF::HEPRUP heprup){
       reset_weight_info(heprup);
       auto runinfo{ HepMC3::make_shared<HepMC3::GenRunInfo>() };
 
       auto hepr{ HepMC3::make_shared<HepMC3::HEPRUPAttribute>() };
       hepr->heprup = heprup;
       runinfo->add_attribute(std::string("HEPRUP"), hepr);
       for(auto const & gen: heprup.generators){
         runinfo->tools().emplace_back(
           HepMC3::GenRunInfo::ToolInfo{gen.name, gen.version, gen.contents} );
       }
       return runinfo;
     }
 
     std::vector<std::string> get_weight_names(Event const & ev){
       std::vector<std::string> names;
       names.reserve(ev.variations().size()+1); // +1 from central
       names.emplace_back(""); // rivet assumes central band to have no name
       for( size_t i=0; i<ev.variations().size(); ++i ){
         auto const & var{ ev.variations()[i] };
         if(var.description){
           names.emplace_back( to_simple_string(*var.description) );
         } else {
           names.emplace_back( "" );
         }
       }
       assert(names.size() == ev.variations().size()+1);
       return names;
     }
   } // namespace anonymous
 
   HepMC3Interface::HepMC3Interface(LHEF::HEPRUP const & heprup):
     run_info{ init_runinfo(heprup) },
     event_count_(0.), tot_weight_(0.), tot_weight2_(0.),
     xs_{std::make_shared<HepMC3::GenCrossSection>()}
   {
     beam_particle_[0] = static_cast<ParticleID>(heprup.IDBMUP.first);
     beam_particle_[1] = static_cast<ParticleID>(heprup.IDBMUP.second);
     beam_energy_[0] = heprup.EBMUP.first;
     beam_energy_[1] = heprup.EBMUP.second;
   }
 
   HepMC3::GenEvent HepMC3Interface::init_event(Event const & event) const {
 
     const std::array<HepMC3::GenParticlePtr,2> beam {
       HepMC3::make_shared<HepMC3::GenParticle>(
         HepMC3::FourVector(0,0,-beam_energy_[0],beam_energy_[0]),
         beam_particle_[0], detail_HepMC::status_beam ),
       HepMC3::make_shared<HepMC3::GenParticle>(
         HepMC3::FourVector(0,0, beam_energy_[1],beam_energy_[1]),
         beam_particle_[1], detail_HepMC::status_beam )
     };
     auto hepmc_ev{ detail_HepMC::HepMC_init_kinematics<3>(
         event, beam, HepMC3::GenEvent{ HepMC3::Units::GEV, HepMC3::Units::MM }
     ) };
     // set up run specific informations
     if( run_info->weight_names().size() != event.variations().size()+1 ){
       run_info->set_weight_names( get_weight_names(event) );
     }
     // order matters: weights in hepmc_ev initialised when registering run_info
     hepmc_ev.set_run_info(run_info);
     assert(hepmc_ev.weights().size() == event.variations().size()+1);
     for(size_t i=0; i<event.variations().size(); ++i){
       hepmc_ev.weights()[i+1] = event.variations()[i].weight;
       //! @TODO set variation specific cross section
       //!       the problem is that set_cross_section overwrites everything
     }
     return hepmc_ev;
   }
 
   void HepMC3Interface::set_central(HepMC3::GenEvent & out_ev, Event const & event,
     ssize_t const weight_index
   ){
     EventParameters event_param;
     if(weight_index < 0)
       event_param = event.central();
     else if ( static_cast<size_t>(weight_index) < event.variations().size())
       event_param = event.variations(weight_index);
     else
       throw std::invalid_argument{
          "HepMC3Interface tried to access a weight outside of the variation range."
       };
     const double wt = event_param.weight;
     tot_weight_ += wt;
     tot_weight2_ += wt * wt;
     ++event_count_;
 
     // central always on first
     assert(out_ev.weights().size() == event.variations().size()+1);
     out_ev.weights()[0] = wt;
 
     // out_ev can be setup with a different central scale -> save xs manually
     out_ev.set_cross_section(xs_);
     assert(out_ev.cross_section() && out_ev.cross_section() == xs_);
     // overwrites all previously set xs ...
     xs_->set_cross_section(tot_weight_,sqrt(tot_weight2_));
 
     out_ev.set_event_number(event_count_);
     /// @TODO add number of attempted events
     xs_->set_accepted_events(event_count_);
 
     /// @TODO add alphaQCD (need function) and alphaQED
     /// @TODO output pdf (currently not avaiable from event alone)
   }
 
   HepMC3::GenEvent HepMC3Interface::operator()(Event const & event,
       ssize_t const weight_index
   ){
     HepMC3::GenEvent out_ev(init_event(event));
     set_central(out_ev, event, weight_index);
     return out_ev;
   }
 
 }
 #else // no HepMC3 => empty class
 namespace HepMC3 {
   class GenEvent {};
   class GenCrossSection {};
   class GenRunInfo {};
 }
 namespace HEJ{
   HepMC3Interface::HepMC3Interface(LHEF::HEPRUP const &){
       throw std::invalid_argument(
           "Failed to create HepMC3Interface: "
           "HEJ 2 was built without HepMC3 support"
       );
   }
 
   HepMC3::GenEvent HepMC3Interface::operator()(Event const &, ssize_t)
   {return HepMC3::GenEvent();}
   HepMC3::GenEvent HepMC3Interface::init_event(Event const &) const
   {return HepMC3::GenEvent();}
   void HepMC3Interface::set_central(HepMC3::GenEvent &, Event const &, ssize_t){}
 }
 #endif
 
 namespace HEJ{
   HepMC3Interface::~HepMC3Interface() = default;
 }
diff --git a/src/HepMC3Writer.cc b/src/HepMC3Writer.cc
index 6657d87..b8ae99a 100644
--- a/src/HepMC3Writer.cc
+++ b/src/HepMC3Writer.cc
@@ -1,94 +1,94 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/HepMC3Writer.hh"
 
 #include <cassert>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/exceptions.hh"
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_HepMC3
 
 #include "HepMC3/WriterAscii.h"
 
 #include <utility>
 
 #include "HEJ/Event.hh"
 #include "HEJ/HepMC3Interface.hh"
 
 namespace HEJ{
 
   struct HepMC3Writer::HepMC3WriterImpl{
     HepMC3Interface HepMC3_;
 
     HepMC3WriterImpl & operator=(HepMC3WriterImpl const & other) = delete;
     HepMC3WriterImpl(HepMC3WriterImpl const & other) = delete;
     HepMC3WriterImpl & operator=(HepMC3WriterImpl && other) = delete;
     HepMC3WriterImpl(HepMC3WriterImpl && other) = delete;
 
     std::unique_ptr<HepMC3::WriterAscii> writer_;
     std::string const file_;
 
     HepMC3WriterImpl(
         std::string const & file, LHEF::HEPRUP && heprup
     ):
       HepMC3_{std::move(heprup)},
       file_{file}
     {}
 
     void init_writer(){
       writer_ = std::make_unique<HepMC3::WriterAscii>(file_, HepMC3_.run_info);
     }
 
     ~HepMC3WriterImpl(){
       if(!writer_) // make sure that we always write something
         init_writer();
       writer_->close();
     }
 
     void write(Event const & ev){
       auto out_ev = HepMC3_(ev);
       //! weight names are only available after first event
       if(!writer_)
         init_writer();
       writer_->write_event(out_ev);
     }
   };
 
   HepMC3Writer::HepMC3Writer(std::string const & file, LHEF::HEPRUP heprup):
     impl_{ std::make_unique<HepMC3WriterImpl>(file, std::move(heprup)) }
   {}
 
   void HepMC3Writer::write(Event const & ev){
     impl_->write(ev);
   }
 } // namespace HEJ
 
 #else // no HepMC3
 
 namespace HEJ{
 
   class HepMC3Writer::HepMC3WriterImpl{};
 
   HepMC3Writer::HepMC3Writer(std::string const &, LHEF::HEPRUP){
       throw std::invalid_argument(
           "Failed to create HepMC3 writer: "
           "HEJ 2 was built without HepMC3 support"
       );
   }
 
   void HepMC3Writer::write(Event const &){
     assert(false);
   }
 
 }
 #endif
 
 namespace HEJ{
   HepMC3Writer::~HepMC3Writer() = default;
 }
diff --git a/src/Hjets.cc b/src/Hjets.cc
index 568eeae..82ff293 100644
--- a/src/Hjets.cc
+++ b/src/Hjets.cc
@@ -1,1087 +1,1087 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/jets.hh"
 #include "HEJ/Hjets.hh"
 
 #include <assert.h>
 #include <limits>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_QCDLOOP
 #include "qcdloop/qcdloop.h"
 #endif
 
 const COM looprwfactor = (COM(0.,1.)*M_PI*M_PI)/pow((2.*M_PI),4);
 constexpr double infinity = std::numeric_limits<double>::infinity();
 
 namespace {
   // Loop integrals
   #ifdef HEJ_BUILD_WITH_QCDLOOP
 
   COM B0DD(HLV q, double mq)
   {
     static std::vector<std::complex<double>> result(3);
     static auto ql_B0 = [](){
       ql::Bubble<std::complex<double>,double,double> ql_B0;
       ql_B0.setCacheSize(100);
       return ql_B0;
     }();
     static std::vector<double> masses(2);
     static std::vector<double> momenta(1);
     for(auto & m: masses) m = mq*mq;
     momenta.front() = q.m2();
     ql_B0.integral(result, 1, masses, momenta);
     return result[0];
   }
   COM C0DD(HLV q1, HLV q2, double mq)
   {
     static std::vector<std::complex<double>> result(3);
     static auto ql_C0 = [](){
       ql::Triangle<std::complex<double>,double,double> ql_C0;
       ql_C0.setCacheSize(100);
       return ql_C0;
     }();
     static std::vector<double> masses(3);
     static std::vector<double> momenta(3);
     for(auto & m: masses) m = mq*mq;
     momenta[0] = q1.m2();
     momenta[1] = q2.m2();
     momenta[2] = (q1+q2).m2();
     ql_C0.integral(result, 1, masses, momenta);
     return result[0];
   }
   COM D0DD(HLV q1,HLV q2, HLV q3, double mq)
   {
     static std::vector<std::complex<double>> result(3);
     static auto ql_D0 = [](){
       ql::Box<std::complex<double>,double,double> ql_D0;
       ql_D0.setCacheSize(100);
       return ql_D0;
     }();
     static std::vector<double> masses(4);
     static std::vector<double> momenta(6);
     for(auto & m: masses) m = mq*mq;
     momenta[0] = q1.m2();
     momenta[1] = q2.m2();
     momenta[2] = q3.m2();
     momenta[3] = (q1+q2+q3).m2();
     momenta[4] = (q1+q2).m2();
     momenta[5] = (q2+q3).m2();
     ql_D0.integral(result, 1, masses, momenta);
     return result[0];
   }
 
   COM A1(HLV q1, HLV q2, double mt)
   // As given in Eq. (B.2) of VDD
   {
     double q12,q22,Q2;
     HLV Q;
     double Delta3,mt2;
     COM ans(COM(0.,0.));
 
     q12=q1.m2();
     q22=q2.m2();
     Q=-q1-q2; // Define all momenta ingoing as in appendix of VDD
     Q2=Q.m2();
 
     Delta3=q12*q12+q22*q22+Q2*Q2-2*q12*q22-2*q12*Q2-2*q22*Q2;
 
     assert(mt > 0.);
 
     mt2=mt*mt;
 
     ans=looprwfactor*COM(0,-1)*C0DD(q1,q2,mt)*( 4.*mt2/Delta3*(Q2-q12-q22)
         -1.-4.*q12*q22/Delta3-12.*q12*q22*Q2/Delta3/Delta3*(q12+q22-Q2) )
       - looprwfactor*COM(0,-1)*( B0DD(q2,mt)-B0DD(Q,mt) )
         * ( 2.*q22/Delta3+12.*q12*q22/Delta3/Delta3*(q22-q12+Q2) )
       - looprwfactor*COM(0,-1)*( B0DD(q1,mt)-B0DD(Q,mt) )
         * ( 2.*q12/Delta3+12.*q12*q22/Delta3/Delta3*(q12-q22+Q2) )
       - 2./Delta3/16/M_PI/M_PI*(q12+q22-Q2);
 
     return ans;
 
   }
 
   COM A2(HLV q1, HLV q2, double mt)
   // As given in Eq. (B.2) of VDD, but with high energy limit
   // of invariants taken.
   {
     double q12,q22,Q2;
     HLV Q;
     double Delta3,mt2;
     COM ans(COM(0.,0.));
 
     assert(mt > 0.);
 
     mt2=mt*mt;
 
     q12=q1.m2();
     q22=q2.m2();
     Q=-q1-q2; // Define all momenta ingoing as in appendix of VDD
     Q2=Q.m2();
 
     Delta3=q12*q12+q22*q22+Q2*Q2-2*q12*q22-2*q12*Q2-2*q22*Q2;
     ans=looprwfactor*COM(0,-1)*C0DD(q1,q2,mt)*( 2.*mt2+1./2.*(q12+q22-Q2)
         +2.*q12*q22*Q2/Delta3 )
       +looprwfactor*COM(0,-1)*(B0DD(q2,mt)-B0DD(Q,mt))
         *q22*(q22-q12-Q2)/Delta3
       +looprwfactor*COM(0,-1)*(B0DD(q1,mt)-B0DD(Q,mt))
         *q12*(q12-q22-Q2)/Delta3+1./16/M_PI/M_PI;
 
     return ans;
   }
 
 #else // no QCDloop
 
   COM A1(HLV, HLV, double) {
     throw std::logic_error{"A1 called without QCDloop support"};
   }
 
   COM A2(HLV, HLV, double) {
     throw std::logic_error{"A2 called without QCDloop support"};
   }
 
 #endif
 
   void to_current(const HLV & q, current & ret){
     ret[0]=q.e();
     ret[1]=q.x();
     ret[2]=q.y();
     ret[3]=q.z();
   }
 
   /**
    * @brief Higgs vertex contracted with current @param C1 and @param C2
    */
   COM cHdot(const current & C1, const current & C2, const current & q1,
             const current & q2, double mt, bool incBot, double mb, double vev)
   {
     if (mt == infinity) {
       return (cdot(C1,C2)*cdot(q1,q2)-cdot(C1,q2)*cdot(C2,q1))/(3*M_PI*vev);
     }
     else {
       HLV vq1,vq2;
       vq1.set(q1[1].real(),q1[2].real(),q1[3].real(),q1[0].real());
       vq2.set(q2[1].real(),q2[2].real(),q2[3].real(),q2[0].real());
       // first minus sign obtained because of q1-difference to VDD
       // Factor is because 4 mt^2 g^2/vev A1 -> 16 pi mt^2/vev alphas,
       if(!(incBot))
         return 16.*M_PI*mt*mt/vev*(-cdot(C1,q2)*cdot(C2,q1)*A1(-vq1,vq2,mt)
                                         -cdot(C1,C2)*A2(-vq1,vq2,mt));
       else
         return 16.*M_PI*mt*mt/vev*(-cdot(C1,q2)*cdot(C2,q1)*A1(-vq1,vq2,mt)
                                         -cdot(C1,C2)*A2(-vq1,vq2,mt))
              + 16.*M_PI*mb*mb/vev*(-cdot(C1,q2)*cdot(C2,q1)*A1(-vq1,vq2,mb)
                                         -cdot(C1,C2)*A2(-vq1,vq2,mb));
     }
   }
 //@{
 /**
  * @brief Higgs+Jets FKL Contributions, function to handle all incoming types.
  * @param p1out             Outgoing Particle 1. (W emission)
  * @param p1in              Incoming Particle 1. (W emission)
  * @param p2out             Outgoing Particle 2 (Quark, unordered emission this side.)
  * @param p2in              Incoming Particle 2 (Quark, unordered emission this side.)
  * @param q1                t-channel momenta into higgs vertex
  * @param q2                t-channel momenta out of higgs vertex
  * @param mt                top mass (inf or value)
  * @param incBot            Bool, to include bottom mass (true) or not (false)?
  * @param mb                bottom mass (value)
  * @param pg                Unordered Gluon momenta
  *
  * Calculates j^\mu  H  j_\mu. FKL with higgs vertex somewhere in the FKL chain.
  * Handles all possible incoming states.
  */
   double j_h_j(HLV const & p1out, HLV const & p1in, HLV const & p2out,
                HLV const &  p2in, HLV const & q1, HLV const & q2,
                double mt, bool incBot, double mb, double vev
   ){
     current j1p,j1m,j2p,j2m, q1v, q2v;
 
     // Note need to flip helicities in anti-quark case.
     joi(p1out, false, p1in, false, j1p);
     joi(p1out,  true, p1in,  true, j1m);
     joi(p2out, false, p2in, false, j2p);
     joi(p2out,  true, p2in,  true, j2m);
 
     to_current(q1, q1v);
     to_current(q2, q2v);
 
     COM Mmp=cHdot(j1m,j2p,q1v,q2v,mt, incBot, mb, vev);
     COM Mmm=cHdot(j1m,j2m,q1v,q2v,mt, incBot, mb, vev);
     COM Mpp=cHdot(j1p,j2p,q1v,q2v,mt, incBot, mb, vev);
     COM Mpm=cHdot(j1p,j2m,q1v,q2v,mt, incBot, mb, vev);
 
     // average over helicities
     const double sst=(abs2(Mmp)+abs2(Mmm)+abs2(Mpp)+abs2(Mpm))/4.;
 
     return sst/((p1in-p1out).m2()*(p2in-p2out).m2()*q1.m2()*q2.m2());
   }
 } // namespace anonymous
 
 double ME_H_qQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
               double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev);
 }
 
 double ME_H_qQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
                  double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev);
 }
 
 double ME_H_qbarQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
                  double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev);
 }
 
 double ME_H_qbarQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
                     double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev);
 }
 
 double ME_H_qg(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
               double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev)
           * K_g(p2out,p2in)/HEJ::C_A;
 }
 
 double ME_H_qbarg(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
                  double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev)
           * K_g(p2out,p2in)/HEJ::C_A;
 }
 
 double ME_H_gg(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV q1, HLV q2,
               double mt, bool incBot, double mb, double vev){
   return j_h_j(p1out, p1in, p2out, p2in, q1, q2, mt, incBot, mb, vev)
           * K_g(p2out,p2in)/HEJ::C_A * K_g(p1out,p1in)/HEJ::C_A;
 }
 //@}
 namespace {
 
   //@{
   /// @brief Higgs vertex contracted with one current
 
   CCurrent jH(HLV const & pout, bool helout, HLV const & pin,
               bool helin, HLV const & q1, HLV const & q2,
               double mt, bool incBot, double mb, double vev)
   {
     CCurrent j2 = joi(pout,helout,pin,helin);
     CCurrent jq2(q2.e(),q2.px(),q2.py(),q2.pz());
 
     if(mt == infinity)
       return ((q1.dot(q2))*j2 - j2.dot(q1)*jq2)/(3*M_PI*vev);
     else
     {
       if(incBot)
         return (-16.*M_PI*mb*mb/vev*j2.dot(q1)*jq2*A1(-q1,q2,mb)
                 -16.*M_PI*mb*mb/vev*j2*A2(-q1,q2,mb))
           + (-16.*M_PI*mt*mt/vev*j2.dot(q1)*jq2*A1(-q1,q2,mt)
              -16.*M_PI*mt*mt/vev*j2*A2(-q1,q2,mt));
       else
         return (-16.*M_PI*mt*mt/vev*j2.dot(q1)*jq2*A1(-q1,q2,mt)
                 -16.*M_PI*mt*mt/vev*j2*A2(-q1,q2,mt));
     }
   }
   //@}
 
 //@{
 /**
  * @brief Higgs+Jets Unordered Contributions, function to handle all incoming types.
  * @param pg                Unordered Gluon momenta
  * @param p1out             Outgoing Particle 1. (W emission)
  * @param p1in              Incoming Particle 1. (W emission)
  * @param p2out             Outgoing Particle 2 (Quark, unordered emission this side.)
  * @param p2in              Incoming Particle 2 (Quark, unordered emission this side.)
  * @param q1                t-channel momenta into higgs vertex
  * @param q2                t-channel momenta out of higgs vertex
  * @param mt                top mass (inf or value)
  * @param incBot            Bool, to include bottom mass (true) or not (false)?
  * @param mb                bottom mass (value)
  *
  * Calculates j_{uno}^\mu H j_\mu. Unordered with higgs vertex
  * somewhere in the FKL chain.  Handles all possible incoming states.
  */
   double juno_h_j(HLV const & pg, HLV const & p1out, HLV const & p1in,
                   HLV const & p2out, HLV const & p2in,
                   HLV const & qH1,  HLV const & qH2,
                   double mt, bool incBot, double mb, double vev
   ){
     //  This construction is taking rapidity order: pg > p1out >> p2out
     HLV q1=p1in-p1out;  // Top End
     HLV q2=-(p2in-p2out);   // Bottom End
     HLV qg=p1in-p1out-pg;  // Extra bit post-gluon
 
     // Note <p1|eps|pa> current split into two by gauge choice.
     // See James C's Thesis (p72). <p1|eps|pa> -> <p1|pg><pg|pa>
     CCurrent mj1p=joi(p1out,false, p1in,false);
     CCurrent mj1m=joi(p1out, true, p1in, true);
     CCurrent jgap=joi(pg,   false, p1in,false);
     CCurrent jgam=joi(pg,    true, p1in, true);
 
     // Note for function joo(): <p1+|pg+> = <pg-|p1->.
     CCurrent j2gp=joo(p1out, false, pg, false);
     CCurrent j2gm=joo(p1out,  true, pg,  true);
 
     CCurrent mjH2p=jH(p2out, true,p2in, true,qH1,qH2,mt,incBot,mb, vev);
     CCurrent mjH2m=jH(p2out,false,p2in,false,qH1,qH2,mt,incBot,mb, vev);
 
     // Dot products of these which occur again and again
     COM MHmp=mj1m.dot(mjH2p);
     COM MHmm=mj1m.dot(mjH2m);
     COM MHpp=mj1p.dot(mjH2p);
     COM MHpm=mj1p.dot(mjH2m);
 
     CCurrent p2o(p2out), p2i(p2in), p1o(p1out), p1i(p1in), qsum(q1+qg);
 
     CCurrent Lmm=(qsum*(MHmm) + (-2.*mjH2m.dot(pg))*mj1m + 2.*mj1m.dot(pg)*mjH2m
          + ( p2o/pg.dot(p2out) + p2i/pg.dot(p2in) )*( qg.m2()*MHmm/2.) )/q1.m2();
     CCurrent Lmp=(qsum*(MHmp) + (-2.*mjH2p.dot(pg))*mj1m + 2.*mj1m.dot(pg)*mjH2p
          + ( p2o/pg.dot(p2out) + p2i/pg.dot(p2in) )*( qg.m2()*MHmp/2.) )/q1.m2();
     CCurrent Lpm=(qsum*(MHpm) + (-2.*mjH2m.dot(pg))*mj1p + 2.*mj1p.dot(pg)*mjH2m
          + ( p2o/pg.dot(p2out) + p2i/pg.dot(p2in) )*( qg.m2()*MHpm/2.) )/q1.m2();
     CCurrent Lpp=(qsum*(MHpp) + (-2.*mjH2p.dot(pg))*mj1p + 2.*mj1p.dot(pg)*mjH2p
          + ( p2o/pg.dot(p2out) + p2i/pg.dot(p2in) )*( qg.m2()*MHpp/2.) )/q1.m2();
 
     CCurrent U1mm=(jgam.dot(mjH2m)*j2gm+2.*p1o*MHmm)/(p1out+pg).m2();
     CCurrent U1mp=(jgam.dot(mjH2p)*j2gm+2.*p1o*MHmp)/(p1out+pg).m2();
     CCurrent U1pm=(jgap.dot(mjH2m)*j2gp+2.*p1o*MHpm)/(p1out+pg).m2();
     CCurrent U1pp=(jgap.dot(mjH2p)*j2gp+2.*p1o*MHpp)/(p1out+pg).m2();
     CCurrent U2mm=((-1.)*j2gm.dot(mjH2m)*jgam+2.*p1i*MHmm)/(p1in-pg).m2();
     CCurrent U2mp=((-1.)*j2gm.dot(mjH2p)*jgam+2.*p1i*MHmp)/(p1in-pg).m2();
     CCurrent U2pm=((-1.)*j2gp.dot(mjH2m)*jgap+2.*p1i*MHpm)/(p1in-pg).m2();
     CCurrent U2pp=((-1.)*j2gp.dot(mjH2p)*jgap+2.*p1i*MHpp)/(p1in-pg).m2();
 
     constexpr double cf=HEJ::C_F;
 
     double amm=cf*(2.*vre(Lmm-U1mm,Lmm+U2mm))+2.*cf*cf/3.*vabs2(U1mm+U2mm);
     double amp=cf*(2.*vre(Lmp-U1mp,Lmp+U2mp))+2.*cf*cf/3.*vabs2(U1mp+U2mp);
     double apm=cf*(2.*vre(Lpm-U1pm,Lpm+U2pm))+2.*cf*cf/3.*vabs2(U1pm+U2pm);
     double app=cf*(2.*vre(Lpp-U1pp,Lpp+U2pp))+2.*cf*cf/3.*vabs2(U1pp+U2pp);
     double ampsq=-(amm+amp+apm+app)/(q2.m2()*qH2.m2());
 
     // Now add the t-channels for the Higgs
     ampsq/=qH1.m2()*qg.m2();
     ampsq/=16.;
     // Factor of (Cf/Ca) for each quark to match ME_H_qQ.
     ampsq*=HEJ::C_F*HEJ::C_F/HEJ::C_A/HEJ::C_A;
 
     return ampsq;
   }
 } // namespace anonymous
 
 double ME_H_unob_qQ(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV qH1,
                    HLV qH2, double mt, bool incBot, double mb, double vev){
   return juno_h_j(pg, p1out, p1in, p2out, p2in, qH1, qH2, mt, incBot, mb, vev);
 }
 
 double ME_H_unob_qbarQ(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                       HLV qH1, HLV qH2, double mt, bool incBot, double mb, double vev){
   return juno_h_j(pg, p1out, p1in, p2out, p2in, qH1, qH2, mt, incBot, mb, vev);
 }
 
 double ME_H_unob_qQbar(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                       HLV qH1, HLV qH2, double mt, bool incBot, double mb, double vev){
   return juno_h_j(pg, p1out, p1in, p2out, p2in, qH1, qH2, mt, incBot, mb, vev);
 }
 
 double ME_H_unob_qbarQbar(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                          HLV qH1, HLV qH2, double mt, bool incBot, double mb, double vev){
   return juno_h_j(pg, p1out, p1in, p2out, p2in, qH1, qH2, mt, incBot, mb, vev);
 }
 
 double ME_H_unob_gQ(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                    HLV qH1, HLV qH2, double mt, bool incBot, double mb, double vev){
   return juno_h_j(pg, p1out, p1in, p2out, p2in, qH1, qH2, mt, incBot, mb, vev)
           *K_g(p2out,p2in)/HEJ::C_F;
 }
 
 double ME_H_unob_gQbar(HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                       HLV qH1, HLV qH2, double mt, bool incBot, double mb, double vev){
   return juno_h_j(pg, p1out, p1in, p2out, p2in, qH1, qH2, mt, incBot, mb, vev)
           *K_g(p2out,p2in)/HEJ::C_F;
 }
 //@}
 
 // Begin finite mass stuff
 #ifdef HEJ_BUILD_WITH_QCDLOOP
 namespace {
 
   // All the stuff needed for the box functions in qg->qgH now...
   COM E1(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2=-(k1+k2+kh);
     double Delta, Sigma, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
     Sigma = 4.*s12*s34 - pow(S1+S2,2);
 
     return looprwfactor*(-s12*D0DD(k2, k1, q2, mq)*(1 - 8.*mq*mq/s12 + S2/(2.*s12) +
           S2*(s12 - 8.*mq*mq)*(s34 + S1)/(2.*s12*Delta) +
           2.*(s34 + S1)*(s34 + S1)/Delta +
           S2*pow((s34 + S1),3)/Delta/Delta) - ((s12 + S2)*C0DD(k2,
             k1 + q2, mq) -
           s12*C0DD(k1, k2, mq) + (S1 - S2)*C0DD(k1 + k2, q2, mq) -
           S1*C0DD(k1, q2,
             mq))*(S2*(s12 - 4.*mq*mq)/(2.*s12*Delta) +
           2.*(s34 + S1)/Delta +
           S2*pow((s34 + S1),2)/Delta/Delta) + (C0DD(k1, q2, mq) -
           C0DD(k1 + k2, q2, mq))*(1. - 4.*mq*mq/s12) -
        C0DD(k1 + k2, q2, mq)*2.*s34/
          S1 - (B0DD(k1 + q2, mq) -
           B0DD(k1 + k2 + q2, mq))*2.*s34*(s34 +
            S1)/(S1*Delta) + (B0DD(q2, mq) -
           B0DD(k1 + k2 + q2, mq) +
           s12*C0DD(k1 + k2, q2,
             mq))*(2.*s34*(s34 +
              S1)*(S1 - S2)/(Delta*Sigma) +
           2.*s34*(s34 + S1)/(S1*Delta)) + (B0DD(k1 + k2, mq) -
           B0DD(k1 + k2 + q2,
            mq) - (s34 + S1 + S2)*C0DD(k1 + k2, q2, mq))*2.*(s34 +
           S1)*(2.*s12*s34 -
            S2*(S1 + S2))/(Delta*Sigma));
   }
 
   COM F1(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, Sigma, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
     Sigma = 4.*s12*s34 - pow(S1+S2,2);
 
     return looprwfactor*(-S2*D0DD(k1, k2, q2,
          mq)*(0.5 - (s12 - 8.*mq*mq)*(s34 + S2)/(2.*Delta) -
           s12*pow((s34 + S2),3)/Delta/Delta) + ((s12 + S1)*C0DD(k1,
             k2 + q2, mq) -
           s12*C0DD(k1, k2, mq) - (S1 - S2)*C0DD(k1 + k2, q2, mq) -
           S2*C0DD(k2, q2,
             mq))*(S2*(s12 - 4.*mq*mq)/(2.*s12*Delta) +
           S2*pow((s34 + S2),2)/Delta/Delta)
           - (C0DD(k1 + k2, q2, mq) - C0DD(k1, k2 + q2, mq))*(1. - 4.*mq*mq/s12)
           - C0DD(k1, k2 + q2, mq) + (B0DD(k2 + q2, mq) -
           B0DD(k1 + k2 + q2,
            mq))*2.*pow((s34 + S2),2)/((s12 + S1)*Delta) - (B0DD(
            q2, mq) - B0DD(k1 + k2 + q2, mq) +
           s12*C0DD(k1 + k2, q2, mq))*2.*s34*(s34 +
           S2)*(S2 - S1)/(Delta*Sigma) + (B0DD(
            k1 + k2, mq) -
           B0DD(k1 + k2 + q2,
            mq) - (s34 + S1 + S2)*C0DD(k1 + k2, q2, mq))*2.*(s34 +
           S2)*(2.*s12*s34 -
            S2*(S1 + S2))/(Delta*Sigma));
   }
 
   COM G1(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
 
     return looprwfactor*(S2*D0DD(k1, q2, k2,
          mq)*(Delta/s12/s12 - 4.*mq*mq/s12) -
        S2*((s12 + S1)*C0DD(k1, k2 + q2, mq) -
           S1*C0DD(k1, q2, mq))*(1./
            s12/s12 - (s12 - 4.*mq*mq)/(2.*s12*Delta)) -
        S2*((s12 + S2)*C0DD(k1 + q2, k2, mq) -
           S2*C0DD(k2, q2, mq))*(1./
            s12/s12 + (s12 - 4.*mq*mq)/(2.*s12*Delta)) -
        C0DD(k1, q2, mq) - (C0DD(k1, k2 + q2, mq) -
           C0DD(k1, q2, mq))*4.*mq*mq/
          s12 + (B0DD(k1 + q2, mq) - B0DD(k1 + k2 + q2, mq))*2./
          s12 + (B0DD(k1 + q2, mq) -
           B0DD(q2, mq))*2.*s34/(s12*S1) + (B0DD(k2 + q2, mq) -
           B0DD(k1 + k2 + q2, mq))*2.*(s34 + S2)/(s12*(s12 + S1)));
   }
 
   COM E4(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, Sigma, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
     Sigma = 4.*s12*s34 - pow(S1+S2,2);
 
     return looprwfactor* (-s12*D0DD(k2, k1, q2,
          mq)*(0.5 - (S1 - 8.*mq*mq)*(s34 + S1)/(2.*Delta) -
           s12*pow((s34 + S1),3)/Delta/Delta) + ((s12 + S2)*C0DD(k2,
             k1 + q2, mq) -
           s12*C0DD(k1, k2, mq) + (S1 - S2)*C0DD(k1 + k2, q2, mq) -
           S1*C0DD(k1, q2, mq))*((S1 - 4.*mq*mq)/(2.*Delta) +
            s12*pow((s34 + S1),2)/Delta/Delta) -
        C0DD(k1 + k2, q2, mq) + (B0DD(k1 + q2, mq) -
           B0DD(k1 + k2 + q2, mq))*(2.*s34/Delta +
           2.*s12*(s34 + S1)/((s12 + S2)*Delta)) - (B0DD(
            q2, mq) - B0DD(k1 + k2 + q2, mq) +
           s12*C0DD(k1 + k2, q2,
             mq))*((2.*s34*(2.*s12*s34 - S2*(S1 + S2) +
               s12*(S1 - S2)))/(Delta*Sigma)) + (B0DD(k1 + k2, mq) -
                 B0DD(k1 + k2 + q2, mq) - (s34 + S1 + S2)*C0DD(k1 + k2, q2, mq))
               *((2.*s12*(2.*s12*s34 - S1*(S1 + S2) + s34*(S2 - S1)))/(Delta*Sigma)));
   }
 
   COM F4(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, Sigma, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
     Sigma = 4.*s12*s34 - pow(S1+S2,2);
 
     return looprwfactor* (-s12*D0DD(k1, k2, q2,
          mq)*(0.5 + (S1 - 8.*mq*mq)*(s34 + S2)/(2.*Delta) +
           s12*pow((s34 + S2),3)/Delta/Delta) - ((s12 + S1)*C0DD(k1,
             k2 + q2, mq) -
           s12*C0DD(k1, k2, mq) - (S1 - S2)*C0DD(k1 + k2, q2, mq) -
           S2*C0DD(k2, q2, mq))*((S1 - 4.*mq*mq)/(2.*Delta) +
            s12*pow((s34 + S2),2)/Delta/Delta) -
        C0DD(k1 + k2, q2, mq) - (B0DD(k2 + q2, mq) -
           B0DD(k1 + k2 + q2, mq))*2.*(s34 +
            S2)/Delta + (B0DD(q2, mq) -
           B0DD(k1 + k2 + q2, mq) +
           s12*C0DD(k1 + k2, q2, mq))*2.*s34*(2.*s12*s34 -
            S1*(S1 + S2) +
            s12*(S2 - S1))/(Delta*Sigma) - (B0DD(k1 + k2, mq) -
             B0DD(k1 + k2 + q2, mq) - (s34 + S1 + S2)*C0DD(k1 + k2, q2, mq))
             *(2.*s12*(2.*s12*s34 - S2*(S1 + S2) + s34*(S1 - S2))/(Delta*Sigma)));
   }
 
   COM G4(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
 
     return looprwfactor* (-D0DD(k1, q2, k2,
           mq)*(Delta/s12 + (s12 + S1)/2. -
           4.*mq*mq) + ((s12 + S1)*C0DD(k1, k2 + q2, mq) -
           S1*C0DD(k1, q2, mq))*(1./
            s12 - (S1 - 4.*mq*mq)/(2.*Delta)) + ((s12 + S2)*C0DD(
             k1 + q2, k2, mq) -
           S2*C0DD(k2, q2, mq))*(1./
            s12 + (S1 - 4.*mq*mq)/(2.*Delta)) + (B0DD(
            k1 + k2 + q2, mq) -
           B0DD(k1 + q2, mq))*2./(s12 + S2));
   }
 
   COM E10(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, Sigma, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
     Sigma = 4.*s12*s34 - pow(S1+S2,2);
 
     return looprwfactor*(-s12*D0DD(k2, k1, q2, mq)*((s34 + S1)/Delta +
            12.*mq*mq*S1*(s34 + S1)/Delta/Delta -
            4.*s12*S1*pow((s34 + S1),3)/Delta/Delta/Delta) - ((s12 + S2)*C0DD(k2, k1 + q2, mq) -
            s12*C0DD(k1, k2, mq) + (S1 - S2)*C0DD(k1 + k2, q2, mq) -
             S1*C0DD(k1, q2, mq))*(1./Delta +
            4.*mq*mq*S1/Delta/Delta -
            4.*s12*S1*pow((s34 + S1),2)/Delta/Delta/Delta) +
         C0DD(k1 + k2, q2, mq)*(4.*s12*s34*(S1 - S2)/(Delta*Sigma) -
            4.*(s12 -
               2.*mq*mq)*(2.*s12*s34 -
                S1*(S1 + S2))/(Delta*Sigma)) + (B0DD(k1 + q2, mq) -
            B0DD(k1 + k2 + q2, mq))*(4.*(s34 + S1)/((s12 + S2)*Delta) +
            8.*S1*(s34 + S1)/Delta/Delta) + (B0DD(q2, mq) -
            B0DD(k1 + k2 + q2, mq) +
            s12*C0DD(k1 + k2, q2, mq))*(12.*s34*(2.*s12 + S1 +
               S2)*(2.*s12*s34 -
                S1*(S1 + S2))/(Delta*Sigma*Sigma) -
            4.*s34*(4.*s12 + 3.*S1 +
                S2)/(Delta*Sigma) +
            8.*s12*s34*(s34*(s12 + S2) -
                S1*(s34 +
                   S1))/(Delta*Delta*Sigma)) + (B0DD(k1 + k2, mq) -
            B0DD(k1 + k2 + q2, mq) - (s34 + S1 + S2)*C0DD(k1 + k2, q2,
              mq))*(12.*s12*(2.*s34 + S1 +
               S2)*(2.*s12*s34 -
                S1*(S1 + S2))/(Delta*Sigma*Sigma) +
            8.*s12*S1*(s34*(s12 + S2) -
                S1*(s34 +
                   S1))/(Delta*Delta*Sigma))) + (COM(0.,1.)/(4.*M_PI*M_PI))*((2.*s12*s34 -
           S1*(S1 + S2))/(Delta*Sigma));
   }
 
   COM F10(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, Sigma, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
     Sigma = 4.*s12*s34 - pow(S1+S2,2);
 
     return looprwfactor* (s12*D0DD(k1, k2, q2,
           mq)*((s34 + S2)/Delta - 4.*mq*mq/Delta +
            12.*mq*mq*s34*(s12 + S1)/Delta/Delta -
            4.*s12*pow((s34 + S2),2)/Delta/Delta -
            4.*s12*S1*pow((s34 + S2),3)/Delta/Delta/Delta) + ((s12 + S1)*C0DD(k1, k2 + q2, mq) -
            s12*C0DD(k1, k2, mq) - (S1 - S2)*C0DD(k1 + k2, q2, mq) -
             S2*C0DD(k2, q2, mq))*(1./Delta +
            4.*mq*mq*S1/Delta/Delta -
            4.*s12*(s34 + S2)/Delta/Delta -
            4.*s12*S1*pow((s34 + S2),2)/Delta/Delta/Delta) -
         C0DD(k1 + k2, q2, mq)*(4.*s12*s34/(S2*Delta) +
            4.*s12*s34*(S2 - S1)/(Delta*Sigma) +
            4.*(s12 -
               2.*mq*mq)*(2.*s12*s34 -
                S1*(S1 + S2))/(Delta*Sigma)) - (B0DD(
             k2 + q2, mq) -
            B0DD(k1 + k2 + q2, mq))*(4.*s34/(S2*Delta) +
            8.*s34*(s12 + S1)/Delta/Delta) - (B0DD(q2, mq) -
            B0DD(k1 + k2 + q2, mq) +
            s12*C0DD(k1 + k2, q2,
              mq))*(-12*s34*(2*s12 + S1 +
               S2)*(2.*s12*s34 -
                S1*(S1 + S2))/(Delta*Sigma*Sigma) -
            4.*s12*s34*s34/(S2*Delta*Delta) +
            4.*s34*S1/(Delta*Sigma) -
            4.*s34*(s12*s34*(2.*s12 + S2) -
                S1*S1*(2.*s12 +
                   S1))/(Delta*Delta*Sigma)) - (B0DD(k1 + k2, mq) -
            B0DD(k1 + k2 + q2, mq) - (s34 + S1 + S2)*C0DD(k1 + k2, q2, mq))*(-12.*s12*(2.*s34 + S1 +
               S2)*(2.*s12*s34 -
                S1*(S1 + S2))/(Delta*Sigma*Sigma) +
            8.*s12*(2.*s34 + S1)/(Delta*Sigma) -
            8.*s12*s34*(2.*s12*s34 - S1*(S1 + S2) +
                s12*(S2 -
                   S1))/(Delta*Delta*Sigma))) + (COM(0.,1.)/(4.*M_PI*M_PI))*((2.*s12*s34 -
           S1*(S1 + S2))/(Delta*Sigma));
 
   }
 
   COM G10(HLV k1, HLV k2, HLV kh, double mq){
     HLV q2 = -(k1+k2+kh);
     double Delta, S1, S2, s12, s34;
     S1 = 2.*k1.dot(q2);
     S2 = 2.*k2.dot(q2);
     s12 = 2.*k1.dot(k2);
     s34 = q2.m2();
     Delta = s12*s34 - S1*S2;
 
     return looprwfactor* (-D0DD(k1, q2, k2, mq)*(1. +
           4.*S1*mq*mq/Delta) + ((s12 + S1)*C0DD(k1,
             k2 + q2, mq) -
           S1*C0DD(k1, q2, mq))*(1./Delta +
           4.*S1*mq*mq/Delta/Delta) - ((s12 + S2)*C0DD(k1 + q2,
             k2, mq) - S2*C0DD(k2, q2, mq))*(1./Delta +
           4.*S1*mq*mq/Delta/Delta) + (B0DD(k1 + k2 + q2, mq) -
           B0DD(k1 + q2, mq))*4.*(s34 +
            S1)/(Delta*(s12 + S2)) + (B0DD(q2, mq) -
           B0DD(k2 + q2, mq))*4.*s34/(Delta*S2));
   }
 
   COM H1(HLV k1, HLV k2, HLV kh, double mq){
     return E1(k1,k2,kh,mq)+F1(k1,k2,kh,mq)+G1(k1,k2,kh,mq);
   }
 
   COM H4(HLV k1, HLV k2, HLV kh, double mq){
     return E4(k1,k2,kh,mq)+F4(k1,k2,kh,mq)+G4(k1,k2,kh,mq);
   }
 
   COM H10(HLV k1, HLV k2, HLV kh, double mq){
     return E10(k1,k2,kh,mq)+F10(k1,k2,kh,mq)+G10(k1,k2,kh,mq);
   }
 
   COM H2(HLV k1, HLV k2, HLV kh, double mq){
     return -1.*H1(k2,k1,kh,mq);
   }
 
   COM H5(HLV k1, HLV k2, HLV kh, double mq){
     return -1.*H4(k2,k1,kh,mq);
   }
 
   COM H12(HLV k1, HLV k2, HLV kh, double mq){
     return -1.*H10(k2,k1,kh,mq);
   }
 
   // FL and FT functions
   COM FL(HLV q1, HLV q2, double mq){
     HLV Q = q1 + q2;
     double detQ2 = q1.m2()*q2.m2() - q1.dot(q2)*q1.dot(q2);
     return -1./(2.*detQ2)*((2.-
            3.*q1.m2()*q2.dot(Q)/detQ2)*(B0DD(q1, mq) -
            B0DD(Q, mq)) + (2. -
            3.*q2.m2()*q1.dot(Q)/detQ2)*(B0DD(q2, mq) -
            B0DD(Q, mq)) - (4.*mq*mq + q1.m2() + q2.m2() +
            Q.m2() - 3.*q1.m2()*q2.m2()*Q.m2()/detQ2)*C0DD(
           q1, q2, mq) - 2.);
   }
 
   COM FT(HLV q1, HLV q2, double mq){
     HLV Q = q1 + q2;
     double detQ2 = q1.m2()*q2.m2() - q1.dot(q2)*q1.dot(q2);
     return -1./(2.*detQ2)*(Q.m2()*(B0DD(q1, mq) + B0DD(q2, mq) - 2.*B0DD(Q, mq) -
             2.*q1.dot(q2)*C0DD(q1, q2, mq)) + (q1.m2() -
             q2.m2()) *(B0DD(q1, mq) - B0DD(q2, mq))) -
       q1.dot(q2)*FL(q1, q2, mq);
   }
 
   HLV ParityFlip(HLV p){
     HLV flippedVector;
     flippedVector.setE(p.e());
     flippedVector.setX(-p.x());
     flippedVector.setY(-p.y());
     flippedVector.setZ(-p.z());
     return flippedVector;
   }
 
   /// @brief HC amp for qg->qgH with finite top (i.e. j^{++}_H)
   void g_gH_HC(HLV pa, HLV p1,
     HLV pH, double mq, double vev, current &retAns)
   {
     current cura1,pacur,p1cur,pHcur,conjeps1,conjepsH1,epsa,epsHa,epsHapart1,
       epsHapart2,conjepsH1part1,conjepsH1part2;
     COM ang1a,sqa1;
 
     const double F = 4.*mq*mq/vev;
     // Easier to have the whole thing as current object so I can use cdot functionality.
     // Means I need to write pa,p1 as current objects
     to_current(pa, pacur);
     to_current(p1,p1cur);
     to_current(pH,pHcur);
     bool gluonforward = true;
     if(pa.z() < 0)
       gluonforward = false;
     //HEJ gauge
     jio(pa,false,p1,false,cura1);
 
     if(gluonforward){
       // sqrt(2pa_-/p1_-)*p1_perp/abs(p1_perp)
       ang1a = sqrt(pa.plus()*p1.minus())*(p1.x()+COM(0.,1.)*p1.y())/p1.perp();
       // sqrt(2pa_-/p1_-)*p1_perp*/abs(p1_perp)
       sqa1 = sqrt(pa.plus()*p1.minus())*(p1.x()-COM(0.,1.)*p1.y())/p1.perp();
     } else {
       ang1a = sqrt(pa.minus()*p1.plus());
       sqa1 = sqrt(pa.minus()*p1.plus());
     }
 
     const double prop = (pa-p1-pH).m2();
 
     cmult(-1./sqrt(2)/ang1a,cura1,conjeps1);
     cmult(1./sqrt(2)/sqa1,cura1,epsa);
 
     const COM Fta = FT(-pa,pa-pH,mq)/(pa-pH).m2();
     const COM Ft1 = FT(-p1-pH,p1,mq)/(p1+pH).m2();
 
     const COM h4 = H4(p1,-pa,pH,mq);
     const COM h5 = H5(p1,-pa,pH,mq);
     const COM h10 = H10(p1,-pa,pH,mq);
     const COM h12 = H12(p1,-pa,pH,mq);
 
     cmult(Fta*pa.dot(pH), epsa, epsHapart1);
     cmult(-1.*Fta*cdot(pHcur,epsa), pacur, epsHapart2);
     cmult(Ft1*cdot(pHcur,conjeps1), p1cur, conjepsH1part1);
     cmult(-Ft1*p1.dot(pH), conjeps1, conjepsH1part2);
     cadd(epsHapart1, epsHapart2, epsHa);
     cadd(conjepsH1part1, conjepsH1part2, conjepsH1);
     const COM aH1 = cdot(pHcur, cura1);
 
     current T1,T2,T3,T4,T5,T6,T7,T8,T9,T10;
 
     if(gluonforward){
       cmult(sqrt(2.)*sqrt(p1.plus()/pa.plus())*prop/sqa1, conjepsH1, T1);
       cmult(-sqrt(2.)*sqrt(pa.plus()/p1.plus())*prop/ang1a, epsHa, T2);
     }
     else{
       cmult(-sqrt(2.)*sqrt(p1.minus()/pa.minus())
           *((p1.x()-COM(0.,1.)*p1.y())/p1.perp())*prop/sqa1, conjepsH1, T1);
       cmult(sqrt(2.)*sqrt(pa.minus()/p1.minus())
           *((p1.x()-COM(0.,1.)*p1.y())/p1.perp())*prop/ang1a, epsHa, T2);
     }
 
     cmult(sqrt(2.)/ang1a*aH1, epsHa, T3);
     cmult(sqrt(2.)/sqa1*aH1, conjepsH1, T4);
 
     cmult(-sqrt(2.)*Fta*pa.dot(p1)*aH1/sqa1, conjeps1, T5);
     cmult(-sqrt(2.)*Ft1*pa.dot(p1)*aH1/ang1a, epsa, T6);
 
     cmult(-aH1/sqrt(2.)/sqa1*h4*8.*COM(0.,1.)*M_PI*M_PI, conjeps1, T7);
     cmult(aH1/sqrt(2.)/ang1a*h5*8.*COM(0.,1.)*M_PI*M_PI, epsa, T8);
     cmult(aH1*aH1/2./ang1a/sqa1*h10*8.*COM(0.,1.)*M_PI*M_PI, pacur, T9);
     cmult(-aH1*aH1/2./ang1a/sqa1*h12*8.*COM(0.,1.)*M_PI*M_PI, p1cur, T10);
 
     current ans;
     for(int i=0;i<4;++i)
     {
         ans[i] = T1[i]+T2[i]+T3[i]+T4[i]+T5[i]+T6[i]+T7[i]+T8[i]+T9[i]+T10[i];
     }
 
     retAns[0] = F/prop*ans[0];
     retAns[1] = F/prop*ans[1];
     retAns[2] = F/prop*ans[2];
     retAns[3] = F/prop*ans[3];
   }
 
   /// @brief HNC amp for qg->qgH with finite top (i.e. j^{+-}_H)
   void g_gH_HNC(HLV pa, HLV p1, HLV pH, double mq, double vev, current &retAns)
   {
     const double F = 4.*mq*mq/vev;
     COM ang1a,sqa1;
     current conjepsH1,epsHa,p1cur,pacur,pHcur,conjeps1,epsa,paplusp1cur,
       p1minuspacur,cur1a,cura1,epsHapart1,epsHapart2,conjepsH1part1,
       conjepsH1part2;
     // Find here if pa, meaning the gluon, is forward or backward
     bool gluonforward = true;
     if(pa.z() < 0)
       gluonforward = false;
 
     jio(pa,true,p1,true,cura1);
     joi(p1,true,pa,true,cur1a);
 
     to_current(pa,pacur);
     to_current(p1,p1cur);
     to_current(pH,pHcur);
     to_current(pa+p1,paplusp1cur);
     to_current(p1-pa,p1minuspacur);
     const COM aH1 = cdot(pHcur,cura1);
     const COM oneHa = std::conj(aH1); // = cdot(pHcur,cur1a)
 
     if(gluonforward){
       // sqrt(2pa_-/p1_-)*p1_perp/abs(p1_perp)
       ang1a = sqrt(pa.plus()*p1.minus())*(p1.x()+COM(0.,1.)*p1.y())/p1.perp();
       // sqrt(2pa_-/p1_-)*p1_perp*/abs(p1_perp)
       sqa1 = sqrt(pa.plus()*p1.minus())*(p1.x()-COM(0.,1.)*p1.y())/p1.perp();
       }
     else {
       ang1a = sqrt(pa.minus()*p1.plus());
       sqa1 = sqrt(pa.minus()*p1.plus());
     }
 
     const double prop = (pa-p1-pH).m2();
 
     cmult(1./sqrt(2)/sqa1, cur1a, epsa);
     cmult(-1./sqrt(2)/sqa1, cura1, conjeps1);
     const COM phase = cdot(conjeps1, epsa);
     const COM Fta = FT(-pa,pa-pH,mq)/(pa-pH).m2();
     const COM Ft1 = FT(-p1-pH,p1,mq)/(p1+pH).m2();
     const COM Falpha = FT(p1-pa,pa-p1-pH,mq);
     const COM Fbeta = FL(p1-pa,pa-p1-pH,mq);
 
     const COM h1 = H1(p1,-pa, pH, mq);
     const COM h2 = H2(p1,-pa, pH, mq);
     const COM h4 = H4(p1,-pa, pH, mq);
     const COM h5 = H5(p1,-pa, pH, mq);
     const COM h10 = H10(p1,-pa, pH, mq);
     const COM h12 = H12(p1,-pa, pH, mq);
 
     cmult(Fta*pa.dot(pH), epsa, epsHapart1);
     cmult(-1.*Fta*cdot(pHcur,epsa), pacur, epsHapart2);
     cmult(Ft1*cdot(pHcur,conjeps1), p1cur, conjepsH1part1);
     cmult(-Ft1*p1.dot(pH), conjeps1, conjepsH1part2);
     cadd(epsHapart1, epsHapart2, epsHa);
     cadd(conjepsH1part1, conjepsH1part2, conjepsH1);
 
     current T1,T2,T3,T4,T5a,T5b,T6,T7,T8a,T8b,T9,T10,T11a,
       T11b,T12a,T12b,T13;
 
     if(gluonforward){
       cmult(sqrt(2.)*sqrt(p1.plus()/pa.plus())*prop/sqa1, conjepsH1, T1);
       cmult(-sqrt(2.)*sqrt(pa.plus()/p1.plus())*prop/sqa1, epsHa, T2);
     }
     else{
       cmult(-sqrt(2.)*sqrt(p1.minus()/pa.minus())*((p1.x()-COM(0.,1.)*p1.y())/p1.perp())
           *prop/sqa1, conjepsH1, T1);
       cmult(sqrt(2.)*sqrt(pa.minus()/p1.minus())*((p1.x()+COM(0.,1.)*p1.y())/p1.perp())
           *prop/sqa1, epsHa, T2);
     }
 
     const COM boxdiagFact = 8.*COM(0.,1.)*M_PI*M_PI;
 
     cmult(aH1*sqrt(2.)/sqa1, epsHa, T3);
     cmult(oneHa*sqrt(2.)/sqa1, conjepsH1, T4);
     cmult(-2.*phase*Fta*pa.dot(pH), p1cur, T5a);
     cmult(2.*phase*Ft1*p1.dot(pH), pacur, T5b);
     cmult(-sqrt(2.)*Fta*p1.dot(pa)*oneHa/sqa1, conjeps1, T6);
     cmult(-sqrt(2.)*Ft1*pa.dot(p1)*aH1/sqa1, epsa, T7);
 
     cmult(-boxdiagFact*phase*h2, pacur, T8a);
     cmult(boxdiagFact*phase*h1, p1cur, T8b);
     cmult(boxdiagFact*aH1/sqrt(2.)/sqa1*h5, epsa, T9);
     cmult(-boxdiagFact*oneHa/sqrt(2.)/sqa1*h4, conjeps1, T10);
     cmult(boxdiagFact*aH1*oneHa/2./sqa1/sqa1*h10, pacur, T11a);
     cmult(-boxdiagFact*aH1*oneHa/2./sqa1/sqa1*h12, p1cur, T11b);
 
     cmult(-phase/(pa-p1).m2()*Falpha*(p1-pa).dot(pa-p1-pH), paplusp1cur, T12a);
     cmult(phase/(pa-p1).m2()*Falpha*(pa+p1).dot(pa-p1-pH), p1minuspacur, T12b);
     cmult(-phase*Fbeta*(pa-p1-pH).m2(), paplusp1cur, T13);
 
     current ans;
     for(int i=0;i<4;++i)
     {
       ans[i] = T1[i]+T2[i]+T3[i]+T4[i]+T5a[i]+T5b[i]+T6[i]+T7[i]+T8a[i]+T8b[i]
               +T9[i]+T10[i]+T11a[i]+T11b[i]+T12a[i]+T12b[i]+T13[i];
     }
 
     retAns[0] = F/prop*ans[0];
     retAns[1] = F/prop*ans[1];
     retAns[2] = F/prop*ans[2];
     retAns[3] = F/prop*ans[3];
   }
 
 } // namespace anonymous
 
 // JDC - new amplitude with Higgs emitted close to gluon with full mt effects.
 //       Keep usual HEJ-style function call
 double ME_Houtside_gq(HLV p1out, HLV p1in, HLV p2out, HLV p2in, HLV pH,
                       double mq, bool includeBottom, double mq2, double vev
 ){
 
   current cur2bplus,cur2bminus, cur2bplusFlip, cur2bminusFlip;
   current retAns,retAnsb;
   joi(p2out,true,p2in,true,cur2bplus);
   joi(p2out,false,p2in,false,cur2bminus);
   joi(ParityFlip(p2out),true,ParityFlip(p2in),true,cur2bplusFlip);
   joi(ParityFlip(p2out),false,ParityFlip(p2in),false,cur2bminusFlip);
 
   COM app1,app2,apm1,apm2;
   COM app3, app4, apm3, apm4;
 
   if(!includeBottom)
   {
     g_gH_HC(p1in,p1out,pH,mq,vev,retAns);
     app1=cdot(retAns,cur2bplus);
     app2=cdot(retAns,cur2bminus);
 
     g_gH_HC(ParityFlip(p1in),ParityFlip(p1out),ParityFlip(pH),mq,vev,retAns);
     app3=cdot(retAns,cur2bplusFlip);
     app4=cdot(retAns,cur2bminusFlip);
 
     // And non-conserving bits
     g_gH_HNC(p1in,p1out,pH,mq,vev,retAns);
     apm1=cdot(retAns,cur2bplus);
     apm2=cdot(retAns,cur2bminus);
 
     g_gH_HNC(ParityFlip(p1in),ParityFlip(p1out),ParityFlip(pH),mq,vev,retAns);
     apm3=cdot(retAns,cur2bplusFlip);
     apm4=cdot(retAns,cur2bminusFlip);
   } else {
     g_gH_HC(p1in,p1out,pH,mq,vev,retAns);
     g_gH_HC(p1in,p1out,pH,mq2,vev,retAnsb);
     app1=cdot(retAns,cur2bplus) + cdot(retAnsb,cur2bplus);
     app2=cdot(retAns,cur2bminus) + cdot(retAnsb,cur2bminus);
 
     g_gH_HC(ParityFlip(p1in),ParityFlip(p1out),ParityFlip(pH),mq,vev,retAns);
     g_gH_HC(ParityFlip(p1in),ParityFlip(p1out),ParityFlip(pH),mq2,vev,retAnsb);
     app3=cdot(retAns,cur2bplusFlip) + cdot(retAnsb,cur2bplusFlip);
     app4=cdot(retAns,cur2bminusFlip) + cdot(retAnsb,cur2bminusFlip);
 
     // And non-conserving bits
     g_gH_HNC(p1in,p1out,pH,mq,vev,retAns);
     g_gH_HNC(p1in,p1out,pH,mq2,vev,retAnsb);
     apm1=cdot(retAns,cur2bplus) + cdot(retAnsb,cur2bplus);
     apm2=cdot(retAns,cur2bminus) + cdot(retAnsb,cur2bminus);
 
     g_gH_HNC(ParityFlip(p1in),ParityFlip(p1out),ParityFlip(pH),mq,vev,retAns);
     g_gH_HNC(ParityFlip(p1in),ParityFlip(p1out),ParityFlip(pH),mq2,vev,retAnsb);
     apm3=cdot(retAns,cur2bplusFlip) + cdot(retAnsb,cur2bplusFlip);
     apm4=cdot(retAns,cur2bminusFlip) + cdot(retAnsb,cur2bminusFlip);
   }
 
   return abs2(app1) + abs2(app2) + abs2(app3) + abs2(app4) + abs2(apm1)
     + abs2(apm2) + abs2(apm3) + abs2(apm4);
 }
 #endif // HEJ_BUILD_WITH_QCDLOOP
 
 double C2gHgm(HLV p2, HLV p1, HLV pH, double vev)
 {
   const double A=1./(3.*M_PI*vev);
   // Implements Eq. (4.22) in hep-ph/0301013 with modifications to incoming plus momenta
   double s12,p1p,p2p;
   COM p1perp,p3perp,phperp;
   // Determine first whether this is the case p1p\sim php>>p3p or the opposite
   s12=p1.invariantMass2(-p2);
   if (p2.pz()>0.) { // case considered in hep-ph/0301013
     p1p=p1.plus();
     p2p=p2.plus();
   } else { // opposite case
     p1p=p1.minus();
     p2p=p2.minus();
   }
   p1perp=p1.px()+COM(0,1)*p1.py();
   phperp=pH.px()+COM(0,1)*pH.py();
   p3perp=-(p1perp+phperp);
 
   COM temp=COM(0,1)*A/(2.*s12)*(p2p/p1p*conj(p1perp)*p3perp
           +p1p/p2p*p1perp*conj(p3perp));
   temp=temp*conj(temp);
   return temp.real();
 }
 
 double C2gHgp(HLV p2, HLV p1, HLV pH, double vev)
 {
   const double A=1./(3.*M_PI*vev);
   // Implements Eq. (4.23) in hep-ph/0301013
   double s12,php,p1p,phm;
   COM p1perp,p3perp,phperp;
   // Determine first whether this is the case p1p\sim php>>p3p or the opposite
   s12=p1.invariantMass2(-p2);
   if (p2.pz()>0.) { // case considered in hep-ph/0301013
     php=pH.plus();
     phm=pH.minus();
     p1p=p1.plus();
   } else { // opposite case
     php=pH.minus();
     phm=pH.plus();
     p1p=p1.minus();
   }
   p1perp=p1.px()+COM(0,1)*p1.py();
   phperp=pH.px()+COM(0,1)*pH.py();
   p3perp=-(p1perp+phperp);
 
   COM temp=-COM(0,1)*A/(2.*s12)*( conj(p1perp*p3perp)*pow(php/p1p,2)/(1.+php/p1p)
     +s12*(pow(conj(phperp),2)/(pow(abs(phperp),2)+p1p*phm)
       -pow(conj(p3perp)+(1.+php/p1p)*conj(p1perp),2)
         /((1.+php/p1p)*(pH.m2()+2.*p1.dot(pH)))) );
   temp=temp*conj(temp);
   return temp.real();
 }
 
 double C2qHqm(HLV p2, HLV p1, HLV pH, double vev)
 {
   const double A=1./(3.*M_PI*vev);
   // Implements Eq. (4.21) in hep-ph/0301013
   double s12,p2p,p1p;
   COM p1perp,p3perp,phperp;
   // Determine first whether this is the case p1p\sim php>>p3p or the opposite
   s12=p1.invariantMass2(-p2);
   if (p2.pz()>0.) { // case considered in hep-ph/0301013
     p2p=p2.plus();
     p1p=p1.plus();
   } else { // opposite case
     p2p=p2.minus();
     p1p=p1.minus();
   }
   p1perp=p1.px()+COM(0,1)*p1.py();
   phperp=pH.px()+COM(0,1)*pH.py();
   p3perp=-(p1perp+phperp);
 
   COM temp=A/(2.*s12)*( sqrt(p2p/p1p)*p3perp*conj(p1perp)
     +sqrt(p1p/p2p)*p1perp*conj(p3perp) );
   temp=temp*conj(temp);
   return temp.real();
 }
diff --git a/src/JetSplitter.cc b/src/JetSplitter.cc
index 96a9162..59138fb 100644
--- a/src/JetSplitter.cc
+++ b/src/JetSplitter.cc
@@ -1,188 +1,188 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/JetSplitter.hh"
 
 #include <array>
 #include <assert.h>
 #include <numeric>
 #include <utility>
 
 #include "fastjet/ClusterSequence.hh"
 #include "fastjet/PseudoJet.hh"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
   namespace{
     constexpr double ccut=HEJ::CMINPT; // min parton pt
 
     template<class Iterator>
       bool same_pt_and_rapidity(
           Iterator begin, Iterator end,
           fastjet::PseudoJet const & jet
     ){
       constexpr double ep = 1e-2;
       const fastjet::PseudoJet reconstructed_jet = std::accumulate(
           begin, end, fastjet::PseudoJet{}
       );
       return
         (std::abs(reconstructed_jet.pt() - jet.pt()) < ep)
         && (std::abs(reconstructed_jet.rapidity() - jet.rapidity()) < ep)
         ;
     }
 
     bool all_in_one_jet(
         std::vector<fastjet::PseudoJet> const & partons,
         fastjet::JetDefinition jet_def, double min_jet_pt
     ){
       fastjet::ClusterSequence ev(partons, jet_def);
       const std::vector<fastjet::PseudoJet> testjet = ev.inclusive_jets(min_jet_pt);
       return testjet.size() == 1u
         && testjet[0].constituents().size() == partons.size();
     }
   }
     JetSplitter::JetSplitter(
         fastjet::JetDefinition jet_def, double min_pt
     ):
       min_jet_pt_{min_pt},
       jet_def_{std::move(jet_def)}
     {}
 
   using SplitResult = JetSplitter::SplitResult;
 
   SplitResult JetSplitter::split(
       fastjet::PseudoJet const & j2split, int ncons, RNG & ran
   ) const{
     if(ncons <= 0) {
       throw std::invalid_argument{
         "number of requested jet constituents less than 1"
           };
     }
     double swt = 1.;
 
     std::vector<fastjet::PseudoJet> jcons;
     if(ncons == 1){
       jcons.emplace_back(j2split);
       jcons.back().set_user_index(0);
       return {jcons, swt};
     }
     if(ncons == 2){
       return Split2(j2split, ran);
     }
     const double R_max = R_factor*jet_def_.R();
     assert(R_max < M_PI);
 
     double pt_remaining = j2split.pt();
     const double phi_jet = j2split.phi();
     const double y_jet = j2split.rapidity();
     for(int i = 0; i < ncons - 1; ++i){
       /**
        * Generate rapidity and azimuthal angle with a distance
        * R = sqrt(delta_y^2 + delta_phi^2) < R_max
        * from the jet centre
        */
       const double R = R_max*ran.flat();
       const double theta = 2*M_PI*ran.flat();
       const double delta_phi = R*cos(theta);
       const double delta_y = R*sin(theta);
 
       /**
        * Generate pt such that the total contribution of all partons
        * along the jet pt axis does not exceed the jet pt
        */
       const double pt_max = pt_remaining/cos(delta_phi);
       assert(pt_max > 0);
       if(pt_max < ccut) return {}; // no pt remaining for this parton
       const double pt = (pt_max - ccut)*ran.flat() + ccut;
       pt_remaining -= pt*cos(delta_phi);
 
       jcons.emplace_back(
           pt*cos(phi_jet + delta_phi), pt*sin(phi_jet + delta_phi),
           pt*sinh(y_jet + delta_y), pt*cosh(y_jet + delta_y)
       );
       jcons.back().set_user_index(i);
       swt *= 2*M_PI*R*R_max*pt*(pt_max - ccut);
     }
 
     const fastjet::PseudoJet p_total = std::accumulate(
         jcons.begin(), jcons.end(), fastjet::PseudoJet{}
     );
 
     // Calculate the pt of the last parton
     const double last_px = j2split.px() - p_total.px();
     const double last_py = j2split.py() - p_total.py();
     const double last_pt = sqrt(last_px*last_px + last_py*last_py);
     if(last_pt < ccut) return {};
 
     // Calculate the rapidity of the last parton using the requirement that the
     // new jet must have the same rapidity as the LO jet.
     const double exp_2y_jet = (j2split.e() + j2split.pz())/(j2split.e() - j2split.pz());
     const double bb = (p_total.e()+p_total.pz()) - exp_2y_jet*(p_total.e()-p_total.pz());
     const double lasty = log((-bb+sqrt(bb*bb+4.*exp_2y_jet*last_pt*last_pt))/(2.*last_pt));
 
     jcons.emplace_back(
         last_px, last_py, last_pt*sinh(lasty), last_pt*cosh(lasty)
     );
     jcons.back().set_user_index(ncons-1);
     assert(same_pt_and_rapidity(begin(jcons), end(jcons), j2split));
 
     // Test that the last parton is not too far away from the jet centre.
     if (jcons.back().delta_R(j2split) > R_max) return {};
 
     if(! all_in_one_jet(jcons, jet_def_, min_jet_pt_)) return {};
 
     return {jcons, swt};
   }
 
   double JetSplitter::sample_distance_2p(double & wt, RNG & ran) const{
     static constexpr double x_small = 0.1;
     static constexpr double p_small = 0.4;
 
     const double pR = ran.flat();
     if(pR < p_small){
       wt *= x_small/p_small;
       return x_small/p_small*pR;
     }
     wt *= (1-x_small)/(1-p_small);
     return (1-x_small)/(1-p_small)*(pR-p_small) + x_small;
   }
 
   SplitResult JetSplitter::Split2(
     fastjet::PseudoJet const & j2split, RNG & ran
   ) const{
     static constexpr size_t ncons = 2;
     std::vector<fastjet::PseudoJet> jcons(ncons);
     std::array<double, ncons> R, phi, y, pt;
     double wt = 1;
 
     const double theta = 2*M_PI*ran.flat(); // angle in y-phi plane
     // empiric observation: we are always within the jet radius
     R[0] = sample_distance_2p(wt, ran)*jet_def_.R();
     R[1] = -sample_distance_2p(wt, ran)*jet_def_.R();
     for(size_t i = 0; i <= 1; ++i){
       phi[i] = j2split.phi() + R[i]*cos(theta);
       y[i] = j2split.rapidity() + R[i]*sin(theta);
     }
     for(size_t i = 0; i <= 1; ++i){
       pt[i] = (j2split.py() - tan(phi[1-i])*j2split.px())/
         (sin(phi[i]) - tan(phi[1-i])*cos(phi[i]));
       if(pt[i] < ccut) return {};
       jcons[i].reset_PtYPhiM(pt[i], y[i], phi[i]);
       jcons[i].set_user_index(i);
     }
     assert(same_pt_and_rapidity(begin(jcons), end(jcons), j2split));
 
     if(! all_in_one_jet(jcons, jet_def_, min_jet_pt_)) return {};
     wt *= 2*M_PI*pt[0]*R[0]*jet_def_.R()*jet_def_.R();
     // from transformation of delta(R[1] - ...) to delta(pt[0] - ...)
     const double dphi0 = phi[0] - j2split.phi();
     const double ptJ = j2split.pt();
     const double jacobian = cos(theta)*pt[1]*pt[1]/(ptJ*sin(dphi0));
     return {jcons, jacobian*wt};
   }
 }
diff --git a/src/LesHouchesWriter.cc b/src/LesHouchesWriter.cc
index e96ee0e..51ea535 100644
--- a/src/LesHouchesWriter.cc
+++ b/src/LesHouchesWriter.cc
@@ -1,127 +1,127 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <cassert>
 #include <utility>
 #include <vector>
 
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/LesHouchesWriter.hh"
 #include "HEJ/utility.hh"
 
 namespace HEJ{
   namespace{
     template<class T, class... Args>
       std::unique_ptr<T> make_unique(Args&&... a){
       return std::unique_ptr<T>{new T{std::forward<Args>(a)...}};
     }
 
     size_t to_index(event_type::EventType const type){
       return type==0?0:floor(log2(type))+1;
     }
   }
 
   LesHouchesWriter::LesHouchesWriter(
       std::string const & file, LHEF::HEPRUP heprup
   ):
     out_{file, std::fstream::in | std::fstream::out | std::fstream::trunc},
     writer_{HEJ::make_unique<LHEF::Writer>(out_)}
   {
     if(! out_.is_open()){
       throw std::ios_base::failure("Failed to open " + file);
     };
     // scientific style is needed to allow rewriting the init block
     out_ << std::scientific;
     writer_->heprup = std::move(heprup);
     // lhe Standard: IDWTUP (negative => weights = +/-)
     // IDWTUP: HEJ -> SHG/Pythia/next program
     // 1: weighted->unweighted, xs = mean(weight), XMAXUP given
     // 2: weighted->unweighted, xs = XSECUP, XMAXUP given
     // 3: unweighted (weight=+1)->unweighted, no additional information
     // 4: weighted->weighted, xs = mean(weight)
     //
     // None of these codes actually match what we want:
     // 1 and 4 require xs = mean(weight), which is impossible until after generation
     // 2 tells the SHG to unweight our events, which is wasteful
     // 3 claims we produce unweighted events, which is both wasteful _and_
     //   impossible until after generation (we don't know the maximum weight before)
     //
     // For the time being, we choose -3. If the consumer (like Pythia) assumes
     // weight=+1, the final weights have to be corrected by multiplying with
     // the original weight we provided. We are also often use NLO-PDFs which can
     // give negative weights, hence the native IDWTUP.
     //
     writer_->heprup.IDWTUP = -3;
     // always use the newest LHE version
     // Pythia only saves weights (hepeup.XWGTUP) for version >=2
     writer_->heprup.version = LHEF::HEPRUP().version;
 
     const int max_number_types = to_index(event_type::last_type)+1;
     writer_->heprup.NPRUP = max_number_types;
     // ids of event types
     writer_->heprup.LPRUP.clear();
     writer_->heprup.LPRUP.reserve(max_number_types);
     writer_->heprup.LPRUP.emplace_back(0);
     for(size_t i=event_type::first_type+1; i<=event_type::last_type; i*=2)
       writer_->heprup.LPRUP.emplace_back(i);
 
     // use placeholders for unknown init block values
     // we can overwrite them after processing all events
     writer_->heprup.XSECUP = std::vector<double>(max_number_types, 0.);
     writer_->heprup.XERRUP = std::vector<double>(max_number_types, 0.);
     writer_->heprup.XMAXUP = std::vector<double>(max_number_types, 0.);
     write_init();
   }
 
   void LesHouchesWriter::write(Event const &  ev){
     assert(writer_ && out_.is_open());
 
     const double wt = ev.central().weight;
     writer_->hepeup = HEJ::to_HEPEUP(std::move(ev), &heprup());
     writer_->writeEvent();
     assert(heprup().XSECUP.size() > to_index(ev.type()));
     heprup().XSECUP[to_index(ev.type())] += wt;
     heprup().XERRUP[to_index(ev.type())] += wt*wt;
     if(wt > heprup().XMAXUP[to_index(ev.type())]){
       heprup().XMAXUP[to_index(ev.type())] = wt;
     }
 
   }
 
   // this function is called after overwritting the Les Houches init block
   // assert that we have overwritten *exactly* the init block,
   // i.e. we are at the end of the file or an intact event block is next
   void assert_next_event_intact(std::istream & out){
     (void) out; // suppress compiler warnings if not in debug mode
 #ifndef NDEBUG
     std::string line;
     getline(out, line);
     assert(out.eof() || line.rfind("<event", 0) == 0);
 #endif
   }
 
   void LesHouchesWriter::rewrite_init(){
     assert(writer_ && out_.is_open());
 
     // replace placeholder entries
     const auto pos = out_.tellp();
     out_.seekp(0);
     write_init();
     assert_next_event_intact(out_);
     out_.seekp(pos);
   }
 
   LesHouchesWriter::~LesHouchesWriter(){
     assert(writer_ && out_.is_open());
 
     for(auto & xs_err: heprup().XERRUP)
     {
       xs_err = sqrt(xs_err);
     }
     rewrite_init();
   }
 
 }
diff --git a/src/MatrixElement.cc b/src/MatrixElement.cc
index c04e705..93111e2 100644
--- a/src/MatrixElement.cc
+++ b/src/MatrixElement.cc
@@ -1,1712 +1,1712 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/MatrixElement.hh"
 
 #include <algorithm>
 #include <assert.h>
 #include <limits>
 #include <math.h>
 #include <stddef.h>
 #include <unordered_map>
 #include <utility>
 
 #include "CLHEP/Vector/LorentzVector.h"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/ConfigFlags.hh"
 #include "HEJ/Hjets.hh"
 #include "HEJ/jets.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/Wjets.hh"
 
 namespace HEJ{
   double MatrixElement::omega0(
       double alpha_s, double mur,
       fastjet::PseudoJet const & q_j
   ) const {
     const double lambda = param_.regulator_lambda;
     const double result = - alpha_s*N_C/M_PI*log(q_j.perp2()/(lambda*lambda));
     if(! param_.log_correction) return result;
     return (
         1. + alpha_s/(4.*M_PI)*beta0*log(mur*mur/(q_j.perp()*lambda))
     )*result;
   }
 
   Weights MatrixElement::operator()(Event const & event) const {
     return tree(event)*virtual_corrections(event);
   }
 
   Weights MatrixElement::tree(Event const & event) const {
     return tree_param(event)*tree_kin(event);
   }
 
   Weights MatrixElement::tree_param(Event const & event) const {
     if(! is_resummable(event.type())) {
       return Weights{0., std::vector<double>(event.variations().size(), 0.)};
     }
     Weights result;
     // only compute once for each renormalisation scale
     std::unordered_map<double, double> known;
     result.central = tree_param(event, event.central().mur);
     known.emplace(event.central().mur, result.central);
     for(auto const & var: event.variations()) {
       const auto ME_it = known.find(var.mur);
       if(ME_it == end(known)) {
         const double wt = tree_param(event, var.mur);
         result.variations.emplace_back(wt);
         known.emplace(var.mur, wt);
       }
       else {
         result.variations.emplace_back(ME_it->second);
       }
     }
     return result;
   }
 
   Weights MatrixElement::virtual_corrections(Event const & event) const {
     if(! is_resummable(event.type())) {
       return Weights{0., std::vector<double>(event.variations().size(), 0.)};
     }
     Weights result;
     // only compute once for each renormalisation scale
     std::unordered_map<double, double> known;
     result.central = virtual_corrections(event, event.central().mur);
     known.emplace(event.central().mur, result.central);
     for(auto const & var: event.variations()) {
       const auto ME_it = known.find(var.mur);
       if(ME_it == end(known)) {
         const double wt = virtual_corrections(event, var.mur);
         result.variations.emplace_back(wt);
         known.emplace(var.mur, wt);
       }
       else {
         result.variations.emplace_back(ME_it->second);
       }
     }
     return result;
   }
 
   double MatrixElement::virtual_corrections_W(
       Event const & event,
       const double mur,
       Particle const & WBoson
   ) const{
     auto const & in = event.incoming();
     const auto partons = filter_partons(event.outgoing());
     fastjet::PseudoJet const & pa = in.front().p;
 #ifndef NDEBUG
     fastjet::PseudoJet const & pb = in.back().p;
     double const norm = (in.front().p + in.back().p).E();
 #endif
 
     assert(std::is_sorted(partons.begin(), partons.end(), rapidity_less{}));
     assert(partons.size() >= 2);
     assert(pa.pz() < pb.pz());
 
     fastjet::PseudoJet q = pa - partons[0].p;
     size_t first_idx = 0;
     size_t last_idx = partons.size() - 1;
 
 #ifndef NDEBUG
     bool wc = true;
 #endif
     bool wqq = false;
 
     // With extremal qqx or unordered gluon outside the extremal
     // partons then it is not part of the FKL ladder and does not
     // contribute to the virtual corrections. W emitted from the
     // most backward leg must be taken into account in t-channel
 
     if (event.type() == event_type::unob) {
       q -= partons[1].p;
       ++first_idx;
       if (in[0].type != partons[1].type ){
         q -= WBoson.p;
 #ifndef NDEBUG
         wc=false;
 #endif
       }
     }
 
     else if (event.type() == event_type::qqxexb) {
       q -= partons[1].p;
       ++first_idx;
       if (abs(partons[0].type) != abs(partons[1].type)){
         q -= WBoson.p;
 #ifndef NDEBUG
         wc=false;
 #endif
       }
     }
     else {
       if(event.type() == event_type::unof
          || event.type() == event_type::qqxexf){
         --last_idx;
       }
       if (in[0].type != partons[0].type ){
         q -= WBoson.p;
 #ifndef NDEBUG
         wc=false;
 #endif
       }
     }
 
 
     size_t first_idx_qqx = last_idx;
     size_t last_idx_qqx = last_idx;
 
     //if qqxMid event, virtual correction do not occur between
     //qqx pair.
     if(event.type() == event_type::qqxmid){
       const auto backquark = std::find_if(
         begin(partons) + 1, end(partons) - 1 ,
         [](Particle const & s){ return (s.type != pid::gluon); }
       );
       if(backquark == end(partons) || (backquark+1)->type==pid::gluon) return 0;
       if(abs(backquark->type) != abs((backquark+1)->type)) {
         wqq=true;
 #ifndef NDEBUG
         wc=false;
 #endif
       }
       last_idx = std::distance(begin(partons), backquark);
       first_idx_qqx = last_idx+1;
     }
     double exponent = 0;
     const double alpha_s = alpha_s_(mur);
     for(size_t j = first_idx; j < last_idx; ++j){
       exponent += omega0(alpha_s, mur, q)*(
           partons[j+1].rapidity() - partons[j].rapidity()
       );
       q -=partons[j+1].p;
     } // End Loop one
 
     if (last_idx != first_idx_qqx) q -= partons[last_idx+1].p;
     if (wqq)  q -= WBoson.p;
 
     for(size_t j = first_idx_qqx; j < last_idx_qqx; ++j){
       exponent += omega0(alpha_s, mur, q)*(
           partons[j+1].rapidity() - partons[j].rapidity()
       );
       q -= partons[j+1].p;
     }
 
 #ifndef NDEBUG
     if (wc) q -= WBoson.p;
     assert(
         nearby(q, -1*pb, norm)
         || is_AWZH_boson(partons.back().type)
         || event.type() == event_type::unof
         || event.type() == event_type::qqxexf
     );
 #endif
 
     return exp(exponent);
   }
 
   double MatrixElement::virtual_corrections(
       Event const & event,
       const double mur
   ) const{
     auto const & in = event.incoming();
     auto const & out = event.outgoing();
     fastjet::PseudoJet const & pa = in.front().p;
 #ifndef NDEBUG
     fastjet::PseudoJet const & pb = in.back().p;
     double const norm = (in.front().p + in.back().p).E();
 #endif
 
     const auto AWZH_boson = std::find_if(
         begin(out), end(out),
         [](Particle const & p){ return is_AWZH_boson(p); }
     );
 
     if(AWZH_boson != end(out) && abs(AWZH_boson->type) == pid::Wp){
       return virtual_corrections_W(event, mur, *AWZH_boson);
     }
 
     assert(std::is_sorted(out.begin(), out.end(), rapidity_less{}));
     assert(out.size() >= 2);
     assert(pa.pz() < pb.pz());
 
     fastjet::PseudoJet q = pa - out[0].p;
     size_t first_idx = 0;
     size_t last_idx = out.size() - 1;
 
     // if there is a Higgs boson, extremal qqx or unordered gluon
     // outside the extremal partons then it is not part of the FKL
     // ladder and does not contribute to the virtual corrections
     if((out.front().type == pid::Higgs)
        || event.type() == event_type::unob
        || event.type() == event_type::qqxexb){
       q -= out[1].p;
       ++first_idx;
     }
     if((out.back().type == pid::Higgs)
        || event.type() == event_type::unof
        || event.type() == event_type::qqxexf){
       --last_idx;
     }
 
     size_t first_idx_qqx = last_idx;
     size_t last_idx_qqx = last_idx;
 
     //if qqxMid event, virtual correction do not occur between
     //qqx pair.
     if(event.type() == event_type::qqxmid){
       const auto backquark = std::find_if(
         begin(out) + 1, end(out) - 1 ,
         [](Particle const & s){ return (s.type != pid::gluon && is_parton(s.type)); }
       );
       if(backquark == end(out) || (backquark+1)->type==pid::gluon) return 0;
       last_idx = std::distance(begin(out), backquark);
       first_idx_qqx = last_idx+1;
     }
     double exponent = 0;
     const double alpha_s = alpha_s_(mur);
     for(size_t j = first_idx; j < last_idx; ++j){
       exponent += omega0(alpha_s, mur, q)*(
           out[j+1].rapidity() - out[j].rapidity()
       );
       q -= out[j+1].p;
     }
 
     if (last_idx != first_idx_qqx) q -= out[last_idx+1].p;
 
     for(size_t j = first_idx_qqx; j < last_idx_qqx; ++j){
       exponent += omega0(alpha_s, mur, q)*(
           out[j+1].rapidity() - out[j].rapidity()
       );
       q -= out[j+1].p;
     }
     assert(
         nearby(q, -1*pb, norm)
         || out.back().type == pid::Higgs
         || event.type() == event_type::unof
         || event.type() == event_type::qqxexf
     );
     return exp(exponent);
   }
 
 namespace {
   //! Lipatov vertex for partons emitted into extremal jets
   double C2Lipatov(
       CLHEP::HepLorentzVector const & qav,
       CLHEP::HepLorentzVector const & qbv,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & p2
   ){
     const CLHEP::HepLorentzVector temptrans=-(qav+qbv);
     const CLHEP::HepLorentzVector p5=qav-qbv;
     const CLHEP::HepLorentzVector CL=temptrans
       + p1*(qav.m2()/p5.dot(p1) + 2.*p5.dot(p2)/p1.dot(p2))
       - p2*(qbv.m2()/p5.dot(p2) + 2.*p5.dot(p1)/p1.dot(p2));
     return -CL.dot(CL);
   }
 
   //! Lipatov vertex with soft subtraction for partons emitted into extremal jets
   double C2Lipatovots(
       CLHEP::HepLorentzVector const & qav,
       CLHEP::HepLorentzVector const & qbv,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & p2,
       const double lambda
   ) {
     const double Cls=(C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2()));
 
     const double kperp=(qav-qbv).perp();
     if (kperp>lambda)
       return Cls;
 
     return Cls-4./(kperp*kperp);
   }
 
   //! Lipatov vertex
   double C2Lipatov( // B
       CLHEP::HepLorentzVector const & qav,
       CLHEP::HepLorentzVector const & qbv,
       CLHEP::HepLorentzVector const & pim,
       CLHEP::HepLorentzVector const & pip,
       CLHEP::HepLorentzVector const & pom,
       CLHEP::HepLorentzVector const & pop
   ){
     const CLHEP::HepLorentzVector temptrans=-(qav+qbv);
     const CLHEP::HepLorentzVector p5=qav-qbv;
     const CLHEP::HepLorentzVector CL=temptrans
       + qav.m2()*(1./p5.dot(pip)*pip + 1./p5.dot(pop)*pop)/2.
       - qbv.m2()*(1./p5.dot(pim)*pim + 1./p5.dot(pom)*pom)/2.
       + ( pip*(p5.dot(pim)/pip.dot(pim) + p5.dot(pom)/pip.dot(pom))
         + pop*(p5.dot(pim)/pop.dot(pim) + p5.dot(pom)/pop.dot(pom))
         - pim*(p5.dot(pip)/pip.dot(pim) + p5.dot(pop)/pop.dot(pim))
         - pom*(p5.dot(pip)/pip.dot(pom) + p5.dot(pop)/pop.dot(pom)) )/2.;
 
     return -CL.dot(CL);
   }
 
   //! Lipatov vertex with soft subtraction
   double C2Lipatovots(
       CLHEP::HepLorentzVector const & qav,
       CLHEP::HepLorentzVector const & qbv,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & p2,
       const double lambda
   ) {
     const double Cls=(C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2()));
 
     const double kperp=(qav-qbv).perp();
     if (kperp>lambda)
       return Cls;
     return Cls-4./(kperp*kperp);
   }
 
   /** Matrix element squared for tree-level current-current scattering
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param pg              Unordered gluon momentum
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param p1              Particle 1 Momentum
    *  @param pa              Particle a Momentum
    *  @returns               ME Squared for Tree-Level Current-Current Scattering
    *
    *  @note The unof contribution can be calculated by reversing the argument ordering.
    */
   double ME_uno_current(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pg,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa
   ){
     assert(aptype!=pid::gluon); // aptype cannot be gluon
     if (bptype==pid::gluon) {
       if (is_quark(aptype))
         return ME_unob_qg(pg,p1,pa,pn,pb);
       else
         return ME_unob_qbarg(pg,p1,pa,pn,pb);
     }
     else if (is_antiquark(bptype)) {
       if (is_quark(aptype))
         return ME_unob_qQbar(pg,p1,pa,pn,pb);
       else
         return ME_unob_qbarQbar(pg,p1,pa,pn,pb);
     }
     else { //bptype == quark
       if (is_quark(aptype))
         return ME_unob_qQ(pg,p1,pa,pn,pb);
       else
         return ME_unob_qbarQ(pg,p1,pa,pn,pb);
     }
     throw std::logic_error("unreachable");
   }
 
   /** Matrix element squared for tree-level current-current scattering
    *  @param bptype          Particle b PDG ID
    *  @param pgin            Incoming gluon momentum
    *  @param pq              Quark from splitting Momentum
    *  @param pqbar           Anti-quark from splitting Momentum
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param swap_q_qx       Boolean. Ordering of qqbar pair. False: pqbar extremal.
    *  @returns               ME Squared for Tree-Level Current-Current Scattering
    *
    *  @note The qqxf contribution can be calculated by reversing the argument ordering.
    */
   double ME_qqx_current(
       ParticleID bptype,
       CLHEP::HepLorentzVector const & pgin,
       CLHEP::HepLorentzVector const & pq,
       CLHEP::HepLorentzVector const & pqbar,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       bool const swap_q_qx
   ){
     if (bptype==pid::gluon) {
       if (swap_q_qx) // pq extremal
         return ME_Exqqx_qqbarg(pgin,pq,pqbar,pn,pb);
       else // pqbar extremal
         return ME_Exqqx_qbarqg(pgin,pq,pqbar,pn,pb);
     }
     else { // b leg quark line
       if (swap_q_qx) //extremal pq
         return ME_Exqqx_qqbarQ(pgin,pq,pqbar,pn,pb);
       else
         return ME_Exqqx_qbarqQ(pgin,pq,pqbar,pn,pb);
     }
     throw std::logic_error("unreachable");
   }
 
   /*  \brief Matrix element squared for central qqx tree-level current-current
    *         scattering
    *
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param nabove          Number of gluons emitted before central qqxpair
    *  @param nbelow          Number of gluons emitted after central qqxpair
    *  @param pa              Initial state a Momentum
    *  @param pb              Initial state b Momentum
    *  @param pq              Final state qbar Momentum
    *  @param pqbar           Final state q Momentum
    *  @param partons         Vector of all outgoing partons
    *  @returns               ME Squared for qqxmid Tree-Level Current-Current Scattering
    */
   double ME_qqxmid_current(
       ParticleID aptype, ParticleID bptype, int nabove,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & pq,
       CLHEP::HepLorentzVector const & pqbar,
       std::vector<HLV> const & partons){
     // CAM factors for the qqx amps, and qqbar ordering (default, pq backwards)
     const bool swap_q_qx=pqbar.rapidity() < pq.rapidity();
     double wt=1.;
 
     if (aptype==pid::gluon)  wt*=K_g(partons.front(),pa)/HEJ::C_F;
     if (bptype==pid::gluon)  wt*=K_g(partons.back(),pb)/HEJ::C_F;
 
     return wt*ME_Cenqqx_qq(pa, pb, partons,is_antiquark(bptype),is_antiquark(aptype), swap_q_qx, nabove);
   }
 
 
   /** Matrix element squared for tree-level current-current scattering
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param p1              Particle 1 Momentum
    *  @param pa              Particle a Momentum
    *  @returns               ME Squared for Tree-Level Current-Current Scattering
    */
   double ME_current(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa
   ){
     if (aptype==pid::gluon && bptype==pid::gluon) {
       return ME_gg(pn,pb,p1,pa);
     } else if (aptype==pid::gluon && bptype!=pid::gluon) {
       if (is_quark(bptype))
         return ME_qg(pn,pb,p1,pa);
       else
         return ME_qbarg(pn,pb,p1,pa);
     }
     else if (bptype==pid::gluon && aptype!=pid::gluon) {
       if (is_quark(aptype))
         return ME_qg(p1,pa,pn,pb);
       else
         return ME_qbarg(p1,pa,pn,pb);
     }
     else { // they are both quark
       if (is_quark(bptype)) {
         if (is_quark(aptype))
           return ME_qQ(pn,pb,p1,pa);
         else
           return ME_qQbar(pn,pb,p1,pa);
       }
       else {
         if (is_quark(aptype))
           return ME_qQbar(p1,pa,pn,pb);
         else
           return ME_qbarQbar(pn,pb,p1,pa);
       }
     }
     throw std::logic_error("unreachable");
   }
 
   /** Matrix element squared for tree-level current-current scattering With W+Jets
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param p1              Particle 1 Momentum
    *  @param pa              Particle a Momentum
    *  @param wc              Boolean. True->W Emitted from b. Else; emitted from leg a
    *  @returns               ME Squared for Tree-Level Current-Current Scattering
    */
   double ME_W_current(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & plbar,
       CLHEP::HepLorentzVector const & pl,
       bool const wc, ParticleProperties const & Wprop
   ){
     // We know it cannot be gg incoming.
     assert(!(aptype==pid::gluon && bptype==pid::gluon));
     if (aptype==pid::gluon && bptype!=pid::gluon) {
       if (is_quark(bptype))
         return ME_W_qg(pn,plbar,pl,pb,p1,pa,Wprop);
       else
         return ME_W_qbarg(pn,plbar,pl,pb,p1,pa,Wprop);
     }
     else if (bptype==pid::gluon && aptype!=pid::gluon) {
       if (is_quark(aptype))
         return ME_W_qg(p1,plbar,pl,pa,pn,pb,Wprop);
       else
         return ME_W_qbarg(p1,plbar,pl,pa,pn,pb,Wprop);
     }
     else { // they are both quark
       if (wc==true){ // emission off b, (first argument pbout)
         if (is_quark(bptype)) {
           if (is_quark(aptype))
             return ME_W_qQ(pn,plbar,pl,pb,p1,pa,Wprop);
           else
             return ME_W_qQbar(pn,plbar,pl,pb,p1,pa,Wprop);
         }
         else {
           if (is_quark(aptype))
             return ME_W_qbarQ(pn,plbar,pl,pb,p1,pa,Wprop);
           else
             return ME_W_qbarQbar(pn,plbar,pl,pb,p1,pa,Wprop);
         }
       }
       else{ // emission off a, (first argument paout)
         if (is_quark(aptype)) {
           if (is_quark(bptype))
             return ME_W_qQ(p1,plbar,pl,pa,pn,pb,Wprop);
           else
             return ME_W_qQbar(p1,plbar,pl,pa,pn,pb,Wprop);
         }
         else {  // a is anti-quark
           if (is_quark(bptype))
             return ME_W_qbarQ(p1,plbar,pl,pa,pn,pb,Wprop);
           else
             return ME_W_qbarQbar(p1,plbar,pl,pa,pn,pb,Wprop);
         }
 
       }
     }
     throw std::logic_error("unreachable");
   }
 
   /** Matrix element squared for backwards uno tree-level current-current
    *  scattering With W+Jets
    *
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param p1              Particle 1 Momentum
    *  @param pa              Particle a Momentum
    *  @param pg              Unordered gluon momentum
    *  @param wc              Boolean. True->W Emitted from b. Else; emitted from leg a
    *  @returns               ME Squared for unob Tree-Level Current-Current Scattering
    *
    *  @note The unof contribution can be calculated by reversing the argument ordering.
    */
   double ME_W_uno_current(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & pg,
       CLHEP::HepLorentzVector const & plbar,
       CLHEP::HepLorentzVector const & pl,
       bool const wc, ParticleProperties const & Wprop
   ){
     // we know they are not both gluons
     assert(bptype != pid::gluon || aptype != pid::gluon);
     if (bptype == pid::gluon && aptype != pid::gluon) {
       // b gluon => W emission off a
       if (is_quark(aptype))
         return ME_Wuno_qg(p1,pa,pn,pb,pg,plbar,pl,Wprop);
       else
         return ME_Wuno_qbarg(p1,pa,pn,pb,pg,plbar,pl,Wprop);
     }
     else { // they are both quark
       if (wc) {// emission off b, i.e. b is first current
         if (is_quark(bptype)){
           if (is_quark(aptype))
             return ME_W_unob_qQ(p1,pa,pn,pb,pg,plbar,pl,Wprop);
           else
             return ME_W_unob_qQbar(p1,pa,pn,pb,pg,plbar,pl,Wprop);
         }
         else{
           if (is_quark(aptype))
             return ME_W_unob_qbarQ(p1,pa,pn,pb,pg,plbar,pl,Wprop);
           else
             return ME_W_unob_qbarQbar(p1,pa,pn,pb,pg,plbar,pl,Wprop);
         }
       }
       else {// wc == false, emission off a, i.e. a is first current
         if (is_quark(aptype)) {
           if (is_quark(bptype)) //qq
             return ME_Wuno_qQ(p1,pa,pn,pb,pg,plbar,pl,Wprop);
           else //qqbar
             return ME_Wuno_qQbar(p1,pa,pn,pb,pg,plbar,pl,Wprop);
         }
         else {  // a is anti-quark
           if (is_quark(bptype)) //qbarq
             return ME_Wuno_qbarQ(p1,pa,pn,pb,pg,plbar,pl,Wprop);
           else //qbarqbar
             return ME_Wuno_qbarQbar(p1,pa,pn,pb,pg,plbar,pl,Wprop);
         }
       }
     }
     throw std::logic_error("unreachable");
   }
 
   /** \brief Matrix element squared for backward qqx tree-level current-current
    *         scattering With W+Jets
    *
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param pa              Initial state a Momentum
    *  @param pb              Initial state b Momentum
    *  @param pq              Final state q Momentum
    *  @param pqbar           Final state qbar Momentum
    *  @param pn              Final state n Momentum
    *  @param plbar           Final state anti-lepton momentum
    *  @param pl              Final state lepton momentum
    *  @param swap_q_qx       Boolean. Ordering of qqbar pair. False: pqbar extremal.
    *  @param wc              Boolean. True->W Emitted from b. Else; emitted from leg a
    *  @returns               ME Squared for qqxb Tree-Level Current-Current Scattering
    *
    *  @note calculate forwards qqx contribution by reversing argument ordering.
    */
   double ME_W_qqx_current(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & pq,
       CLHEP::HepLorentzVector const & pqbar,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & plbar,
       CLHEP::HepLorentzVector const & pl,
       bool const swap_q_qx, bool const wc,
       ParticleProperties const & Wprop
   ){
     // CAM factors for the qqx amps, and qqbar ordering (default, qbar extremal)
     const double CFbackward = K_g( (swap_q_qx)?pq:pqbar ,pa)/HEJ::C_F;
 
     // With qqbar we could have 2 incoming gluons and W Emission
     if (aptype==pid::gluon && bptype==pid::gluon) {
       //a gluon, b gluon gg->qqbarWg
       // This will be a wqqx emission as there is no other possible W Emission
       // Site.
       if (swap_q_qx)
         return ME_WExqqx_qqbarg(pa, pqbar, plbar, pl, pq, pn, pb, Wprop)*CFbackward;
       else
         return ME_WExqqx_qbarqg(pa, pq, plbar, pl, pqbar, pn, pb, Wprop)*CFbackward;
     }
     else {
       assert(aptype==pid::gluon && bptype!=pid::gluon );
       //a gluon => W emission off b leg or qqx
       if (!wc){ // W Emitted from backwards qqx
         if (swap_q_qx)
           return ME_WExqqx_qqbarQ(pa, pqbar, plbar, pl, pq, pn, pb, Wprop)*CFbackward;
         else
           return ME_WExqqx_qbarqQ(pa, pq, plbar, pl, pqbar, pn, pb, Wprop)*CFbackward;
       }
       else {   // W Must be emitted from forwards leg.
         if (swap_q_qx)
           return ME_W_Exqqx_QQq(pb, pa, pn, pqbar, pq, plbar, pl, is_antiquark(bptype), Wprop)*CFbackward;
         else
           return ME_W_Exqqx_QQq(pb, pa, pn, pq, pqbar, plbar, pl, is_antiquark(bptype), Wprop)*CFbackward;
       }
     }
     throw std::logic_error("unreachable");
   }
 
   /*  \brief Matrix element squared for central qqx tree-level current-current
    *         scattering With W+Jets
    *
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param nabove          Number of gluons emitted before central qqxpair
    *  @param nbelow          Number of gluons emitted after central qqxpair
    *  @param pa              Initial state a Momentum
    *  @param pb              Initial state b Momentum\
    *  @param pq              Final state qbar Momentum
    *  @param pqbar           Final state q Momentum
    *  @param partons         Vector of all outgoing partons
    *  @param plbar           Final state anti-lepton momentum
    *  @param pl              Final state lepton momentum
    *  @param wqq             Boolean. True siginfies W boson is emitted from Central qqx
    *  @param wc              Boolean. wc=true signifies w boson emitted from leg b; if wqq=false.
    *  @returns               ME Squared for qqxmid Tree-Level Current-Current Scattering
    */
   double ME_W_qqxmid_current(
       ParticleID aptype, ParticleID bptype,
       int nabove, int nbelow,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & pq,
       CLHEP::HepLorentzVector const & pqbar,
       std::vector<HLV> const & partons,
       CLHEP::HepLorentzVector const & plbar,
       CLHEP::HepLorentzVector const & pl,
       bool const wqq, bool const wc,
       ParticleProperties const & Wprop
   ){
     // CAM factors for the qqx amps, and qqbar ordering (default, pq backwards)
     const bool swap_q_qx=pqbar.rapidity() < pq.rapidity();
     double wt=1.;
 
     if (aptype==pid::gluon) wt*=K_g(partons.front(),pa)/HEJ::C_F;
     if (bptype==pid::gluon) wt*=K_g(partons.back(),pb)/HEJ::C_F;
 
     if(wqq)
       return wt*ME_WCenqqx_qq(pa, pb, pl, plbar, partons,(is_antiquark(bptype)),(is_antiquark(aptype)),
                               swap_q_qx, nabove, Wprop);
     return wt*ME_W_Cenqqx_qq(pa, pb, pl, plbar, partons, (is_antiquark(bptype)), (is_antiquark(aptype)),
                              swap_q_qx, nabove, nbelow, wc, Wprop);
   }
 
   /** \brief Matrix element squared for tree-level current-current scattering with Higgs
    *  @param aptype          Particle a PDG ID
    *  @param bptype          Particle b PDG ID
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param p1              Particle 1 Momentum
    *  @param pa              Particle a Momentum
    *  @param qH              t-channel momentum before Higgs
    *  @param qHp1            t-channel momentum after Higgs
    *  @returns               ME Squared for Tree-Level Current-Current Scattering with Higgs
    */
   double ME_Higgs_current(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & qH,  // t-channel momentum before Higgs
       CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs
       double mt, bool include_bottom, double mb, double vev
   ){
     if (aptype==pid::gluon && bptype==pid::gluon)
       // gg initial state
       return ME_H_gg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev);
     else if (aptype==pid::gluon&&bptype!=pid::gluon) {
       if (is_quark(bptype))
         return ME_H_qg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev)*4./9.;
       else
         return ME_H_qbarg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev)*4./9.;
     }
     else if (bptype==pid::gluon && aptype!=pid::gluon) {
       if (is_quark(aptype))
         return ME_H_qg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev)*4./9.;
       else
         return ME_H_qbarg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev)*4./9.;
     }
     else { // they are both quark
       if (is_quark(bptype)) {
         if (is_quark(aptype))
           return ME_H_qQ(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev)*4.*4./(9.*9.);
         else
           return ME_H_qQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev)*4.*4./(9.*9.);
       }
       else {
         if (is_quark(aptype))
           return ME_H_qQbar(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev)*4.*4./(9.*9.);
         else
           return ME_H_qbarQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb,vev)*4.*4./(9.*9.);
       }
     }
     throw std::logic_error("unreachable");
   }
 
   /** \brief Current matrix element squared with Higgs and unordered backward emission
    *  @param aptype          Particle A PDG ID
    *  @param bptype          Particle B PDG ID
    *  @param pn              Particle n Momentum
    *  @param pb              Particle b Momentum
    *  @param pg              Unordered back Particle Momentum
    *  @param p1              Particle 1 Momentum
    *  @param pa              Particle a Momentum
    *  @param qH              t-channel momentum before Higgs
    *  @param qHp1            t-channel momentum after Higgs
    *  @returns               ME Squared with Higgs and unordered backward emission
    *
    *  @note This function assumes unordered gluon backwards from pa-p1 current.
    *        For unof, reverse call order
    */
   double ME_Higgs_current_uno(
       ParticleID aptype, ParticleID bptype,
       CLHEP::HepLorentzVector const & pg,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa,
       CLHEP::HepLorentzVector const & qH,  // t-channel momentum before Higgs
       CLHEP::HepLorentzVector const & qHp1, // t-channel momentum after Higgs
       double mt, bool include_bottom, double mb, double vev
   ){
     if (bptype==pid::gluon && aptype!=pid::gluon) {
       if (is_quark(aptype))
         return ME_H_unob_gQ(pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev);
       else
         return ME_H_unob_gQbar(pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev);
     }
     else { // they are both quark
       if (is_quark(aptype)) {
         if (is_quark(bptype))
           return ME_H_unob_qQ(pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev);
         else
           return ME_H_unob_qbarQ(pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev);
       }
       else {
         if (is_quark(bptype))
           return ME_H_unob_qQbar(pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev);
         else
           return ME_H_unob_qbarQbar(pg,p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb,vev);
       }
     }
     throw std::logic_error("unreachable");
   }
 
   CLHEP::HepLorentzVector to_HepLorentzVector(HEJ::Particle const & particle){
     return {particle.p.px(), particle.p.py(), particle.p.pz(), particle.p.E()};
   }
 
   void validate(HEJ::MatrixElementConfig const & config) {
 #ifndef HEJ_BUILD_WITH_QCDLOOP
     if(!config.Higgs_coupling.use_impact_factors) {
       throw std::invalid_argument{
         "Invalid Higgs coupling settings.\n"
         "HEJ without QCDloop support can only use impact factors.\n"
         "Set use_impact_factors to true or recompile HEJ.\n"
        };
     }
 #endif
     if(config.Higgs_coupling.use_impact_factors
       && config.Higgs_coupling.mt != std::numeric_limits<double>::infinity()) {
       throw std::invalid_argument{
         "Conflicting settings: "
           "impact factors may only be used in the infinite top mass limit"
       };
     }
   }
 } // namespace anonymous
 
   MatrixElement::MatrixElement(
       std::function<double (double)> alpha_s,
       MatrixElementConfig conf
   ):
     alpha_s_{std::move(alpha_s)},
     param_{std::move(conf)}
   {
     validate(param_);
   }
 
   double MatrixElement::tree_kin(
       Event const & ev
   ) const {
     if(! is_resummable(ev.type())) return 0.;
 
     auto AWZH_boson = std::find_if(
         begin(ev.outgoing()), end(ev.outgoing()),
         [](Particle const & p){return is_AWZH_boson(p);}
     );
 
     if(AWZH_boson == end(ev.outgoing()))
       return tree_kin_jets(ev);
 
     switch(AWZH_boson->type){
     case pid::Higgs:
       return tree_kin_Higgs(ev);
     case pid::Wp:
     case pid::Wm:
       return tree_kin_W(ev);
     // TODO
     case pid::photon:
     case pid::Z:
     default:
       throw not_implemented("Emission of boson of unsupported type");
     }
   }
 
   namespace{
     constexpr int extremal_jet_idx = 1;
     constexpr int no_extremal_jet_idx = 0;
 
     bool treat_as_extremal(Particle const & parton){
       return parton.p.user_index() == extremal_jet_idx;
     }
 
     template<class InputIterator>
       double FKL_ladder_weight(
           InputIterator begin_gluon, InputIterator end_gluon,
           CLHEP::HepLorentzVector const & q0,
           CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb,
           CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pn,
           double lambda
       ){
       double wt = 1;
       auto qi = q0;
       for(auto gluon_it = begin_gluon; gluon_it != end_gluon; ++gluon_it){
         assert(gluon_it->type == pid::gluon);
         const auto g = to_HepLorentzVector(*gluon_it);
         const auto qip1 = qi - g;
 
         if(treat_as_extremal(*gluon_it)){
           wt *= C2Lipatovots(qip1, qi, pa, pb, lambda)*C_A;
         } else{
           wt *= C2Lipatovots(qip1, qi, pa, pb, p1, pn, lambda)*C_A;
         }
 
         qi = qip1;
       }
       return wt;
     }
 
   }  // namespace anonymous
 
   std::vector<Particle> MatrixElement::tag_extremal_jet_partons(
       Event const & ev
   ) const{
     auto out_partons = filter_partons(ev.outgoing());
     if(out_partons.size() == ev.jets().size()){
       // no additional emissions in extremal jets, don't need to tag anything
       for(auto & parton: out_partons){
         parton.p.set_user_index(no_extremal_jet_idx);
       }
       return out_partons;
     }
     const auto & jets = ev.jets();
     assert(jets.size() >= 2);
     auto most_backward = begin(jets);
     auto most_forward = end(jets) - 1;
     // skip jets caused by unordered emission or qqx
     if(ev.type() == event_type::unob || ev.type() == event_type::qqxexb){
       assert(jets.size() >= 3);
       ++most_backward;
     }
     else if(ev.type() == event_type::unof || ev.type() == event_type::qqxexf){
       assert(jets.size() >= 3);
       --most_forward;
     }
     const auto extremal_jet_indices = ev.particle_jet_indices(
         {*most_backward, *most_forward}
     );
     assert(extremal_jet_indices.size() == out_partons.size());
     for(size_t i = 0; i < out_partons.size(); ++i){
       assert(HEJ::is_parton(out_partons[i]));
       const int idx = (extremal_jet_indices[i]>=0)?
         extremal_jet_idx:
         no_extremal_jet_idx;
       out_partons[i].p.set_user_index(idx);
     }
     return out_partons;
   }
 
   namespace {
     double tree_kin_jets_qqxmid(
         ParticleID aptype, ParticleID bptype, HLV pa, HLV pb,
         std::vector<Particle> const & partons,
         double lambda
     ){
      HLV pq,pqbar;
       const auto backmidquark = std::find_if(
           begin(partons)+1, end(partons)-1,
           [](Particle const & s){ return s.type != pid::gluon; }
       );
 
       assert(backmidquark!=end(partons)-1);
 
       if (is_quark(backmidquark->type)){
         pq = to_HepLorentzVector(*backmidquark);
         pqbar = to_HepLorentzVector(*(backmidquark+1));
       }
       else {
         pqbar = to_HepLorentzVector(*backmidquark);
         pq = to_HepLorentzVector(*(backmidquark+1));
       }
 
       auto p1 = to_HepLorentzVector(partons[0]);
       auto pn = to_HepLorentzVector(partons[partons.size() - 1]);
 
       auto q0 = pa - p1;
       // t-channel momentum after qqx
       auto qqxt = q0;
 
       const auto begin_ladder = cbegin(partons) + 1;
       const auto end_ladder_1 = (backmidquark);
       const auto begin_ladder_2 = (backmidquark+2);
       const auto end_ladder = cend(partons) - 1;
       for(auto parton_it = begin_ladder; parton_it < begin_ladder_2; ++parton_it){
         qqxt -= to_HepLorentzVector(*parton_it);
       }
 
       const int nabove = std::distance(begin_ladder, backmidquark);
 
       std::vector<HLV> partonsHLV;
       partonsHLV.reserve(partons.size());
       for (size_t i = 0; i != partons.size(); ++i) {
         partonsHLV.push_back(to_HepLorentzVector(partons[i]));
       }
 
       const double current_factor = ME_qqxmid_current(
           aptype, bptype, nabove, pa, pb,
           pq, pqbar, partonsHLV
       );
 
       const double ladder_factor = FKL_ladder_weight(
           begin_ladder, end_ladder_1,
           q0, pa, pb, p1, pn,
           lambda
       )*FKL_ladder_weight(
           begin_ladder_2, end_ladder,
           qqxt, pa, pb, p1, pn,
           lambda
         );
       return current_factor*ladder_factor;
     }
 
 
     template<class InIter, class partIter>
     double tree_kin_jets_qqx(InIter BeginIn, InIter EndIn, partIter BeginPart,
                              partIter EndPart, double lambda){
       const bool swap_q_qx = is_quark(*BeginPart);
       const auto pgin  = to_HepLorentzVector(*BeginIn);
       const auto pb    = to_HepLorentzVector(*(EndIn-1));
       const auto pq    = to_HepLorentzVector(*(BeginPart+(swap_q_qx?0:1)));
       const auto pqbar = to_HepLorentzVector(*(BeginPart+(swap_q_qx?1:0)));
       const auto p1    = to_HepLorentzVector(*(BeginPart));
       const auto pn    = to_HepLorentzVector(*(EndPart-1));
 
       assert((BeginIn)->type==pid::gluon); // Incoming a must be gluon.
       const double current_factor = ME_qqx_current(
         (EndIn-1)->type, pgin, pq, pqbar, pn, pb, swap_q_qx
         )/(4.*(N_C*N_C - 1.));
       const double ladder_factor = FKL_ladder_weight(
           (BeginPart+2), (EndPart-1),
           pgin-pq-pqbar, pgin, pb, p1, pn, lambda
           );
 
       return current_factor*ladder_factor;
     }
 
   template<class InIter, class partIter>
     double tree_kin_jets_uno(InIter BeginIn, InIter EndIn, partIter BeginPart,
                              partIter EndPart, double lambda){
 
       const auto pa = to_HepLorentzVector(*BeginIn);
       const auto pb = to_HepLorentzVector(*(EndIn-1));
 
       const auto pg = to_HepLorentzVector(*BeginPart);
       const auto p1 = to_HepLorentzVector(*(BeginPart+1));
       const auto pn = to_HepLorentzVector(*(EndPart-1));
 
       const double current_factor = ME_uno_current(
         (BeginIn)->type, (EndIn-1)->type, pg, pn, pb, p1, pa
         )/(4.*(N_C*N_C - 1.));
       const double ladder_factor = FKL_ladder_weight(
           (BeginPart+2), (EndPart-1),
           pa-p1-pg, pa, pb, p1, pn, lambda
           );
 
       return current_factor*ladder_factor;
     }
   }
 
   double MatrixElement::tree_kin_jets(Event const & ev) const {
     auto const & incoming = ev.incoming();
     const auto partons = tag_extremal_jet_partons(ev);
 
     if (ev.type()==HEJ::event_type::FKL){
       const auto pa = to_HepLorentzVector(incoming[0]);
       const auto pb = to_HepLorentzVector(incoming[1]);
 
       const auto p1 = to_HepLorentzVector(partons.front());
       const auto pn = to_HepLorentzVector(partons.back());
       return ME_current(
         incoming[0].type, incoming[1].type,
         pn, pb, p1, pa
         )/(4.*(N_C*N_C - 1.))*FKL_ladder_weight(
           begin(partons) + 1, end(partons) - 1,
           pa - p1, pa, pb, p1, pn,
           param_.regulator_lambda
         );
     }
     else if (ev.type()==HEJ::event_type::unordered_backward){
       return tree_kin_jets_uno(incoming.begin(), incoming.end(),
                                  partons.begin(), partons.end(),
                                  param_.regulator_lambda);
     }
     else if (ev.type()==HEJ::event_type::unordered_forward){
       return tree_kin_jets_uno(incoming.rbegin(), incoming.rend(),
                                partons.rbegin(), partons.rend(),
                                param_.regulator_lambda);
     }
     else if (ev.type()==HEJ::event_type::extremal_qqxb){
       return tree_kin_jets_qqx(incoming.begin(), incoming.end(),
                                  partons.begin(), partons.end(),
                                  param_.regulator_lambda);
     }
     else if (ev.type()==HEJ::event_type::extremal_qqxf){
       return tree_kin_jets_qqx(incoming.rbegin(), incoming.rend(),
                                partons.rbegin(), partons.rend(),
                                param_.regulator_lambda);
     }
     else if (ev.type()==HEJ::event_type::central_qqx){
     return tree_kin_jets_qqxmid(incoming[0].type, incoming[1].type,
                            to_HepLorentzVector(incoming[0]),
                            to_HepLorentzVector(incoming[1]),
                            partons, param_.regulator_lambda);
   }
     else {
       throw std::logic_error("Cannot reweight non-resummable processes in Pure Jets");
     }
   }
 
   namespace{
     double tree_kin_W_FKL(
         ParticleID aptype, ParticleID bptype, HLV pa, HLV pb,
         std::vector<Particle> const & partons,
         HLV plbar, HLV pl,
         double lambda, ParticleProperties const & Wprop
     ){
       auto p1 = to_HepLorentzVector(partons[0]);
       auto pn = to_HepLorentzVector(partons[partons.size() - 1]);
 
       const auto begin_ladder = cbegin(partons) + 1;
       const auto end_ladder = cend(partons) - 1;
 
       bool wc = aptype==partons[0].type; //leg b emits w
       auto q0 = pa - p1;
       if(!wc)
         q0 -= pl + plbar;
 
       const double current_factor = ME_W_current(
           aptype, bptype, pn, pb,
           p1, pa, plbar, pl, wc, Wprop
       );
 
       const double ladder_factor = FKL_ladder_weight(
           begin_ladder, end_ladder,
           q0, pa, pb, p1, pn,
           lambda
       );
       return current_factor*ladder_factor;
     }
 
 
     template<class InIter, class partIter>
     double tree_kin_W_uno(InIter BeginIn, partIter BeginPart,
                           partIter EndPart, const HLV & plbar, const HLV & pl,
                           double lambda, ParticleProperties const & Wprop){
       const auto pa = to_HepLorentzVector(*BeginIn);
       const auto pb = to_HepLorentzVector(*(BeginIn+1));
 
       const auto pg = to_HepLorentzVector(*BeginPart);
       const auto p1 = to_HepLorentzVector(*(BeginPart+1));
       const auto pn = to_HepLorentzVector(*(EndPart-1));
 
       bool wc = (BeginIn)->type==(BeginPart+1)->type; //leg b emits w
       auto q0 = pa - p1 - pg;
       if(!wc)
         q0 -= pl + plbar;
 
       const double current_factor = ME_W_uno_current(
           (BeginIn)->type, (BeginIn+1)->type, pn, pb,
           p1, pa, pg, plbar, pl, wc, Wprop
       );
 
       const double ladder_factor = FKL_ladder_weight(
           BeginPart+2, EndPart-1,
           q0, pa, pb, p1, pn,
           lambda
       );
       return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor;
     }
 
     template<class InIter, class partIter>
     double tree_kin_W_qqx(InIter BeginIn, partIter BeginPart,
                           partIter EndPart, const HLV & plbar, const HLV & pl,
                           double lambda, ParticleProperties const & Wprop){
       const bool swap_q_qx=is_quark(*BeginPart);
       const auto pa = to_HepLorentzVector(*BeginIn);
       const auto pb = to_HepLorentzVector(*(BeginIn+1));
       const auto pq = to_HepLorentzVector(*(BeginPart+(swap_q_qx?0:1)));
       const auto pqbar = to_HepLorentzVector(*(BeginPart+(swap_q_qx?1:0)));
       const auto p1 = to_HepLorentzVector(*(BeginPart));
       const auto pn = to_HepLorentzVector(*(EndPart-1));
 
       const bool wc = (BeginIn+1)->type!=(EndPart-1)->type; //leg b emits w
       auto q0 = pa - pq - pqbar;
       if(!wc)
         q0 -= pl + plbar;
 
       const double current_factor = ME_W_qqx_current(
         (BeginIn)->type, (BeginIn+1)->type, pa, pb,
         pq, pqbar, pn, plbar, pl, swap_q_qx, wc, Wprop
       );
 
       const double ladder_factor = FKL_ladder_weight(
           BeginPart+2, EndPart-1,
           q0, pa, pb, p1, pn,
           lambda
       );
       return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor;
     }
 
     double tree_kin_W_qqxmid(
         ParticleID aptype, ParticleID bptype, HLV pa, HLV pb,
         std::vector<Particle> const & partons,
         HLV plbar, HLV pl,
         double lambda, ParticleProperties const & Wprop
     ){
      HLV pq,pqbar;
       const auto backmidquark = std::find_if(
           begin(partons)+1, end(partons)-1,
           [](Particle const & s){ return s.type != pid::gluon; }
       );
 
       assert(backmidquark!=end(partons)-1);
 
       if (is_quark(backmidquark->type)){
         pq = to_HepLorentzVector(*backmidquark);
         pqbar = to_HepLorentzVector(*(backmidquark+1));
       }
       else {
         pqbar = to_HepLorentzVector(*backmidquark);
         pq = to_HepLorentzVector(*(backmidquark+1));
       }
 
       auto p1 = to_HepLorentzVector(partons.front());
       auto pn = to_HepLorentzVector(partons.back());
 
       auto q0 = pa - p1;
       // t-channel momentum after qqx
       auto qqxt = q0;
 
       bool wqq = backmidquark->type != -(backmidquark+1)->type; // qqx emit W
       bool wc = !wqq & (aptype==partons.front().type); //leg b emits w
       assert(!wqq || (wqq && !wc));
       if(wqq){ // emission from qqx
         qqxt -= pl + plbar;
       } else if(!wc) { // emission from leg a
         q0 -= pl + plbar;
         qqxt -= pl + plbar;
       }
 
       const auto begin_ladder = cbegin(partons) + 1;
       const auto end_ladder_1 = (backmidquark);
       const auto begin_ladder_2 = (backmidquark+2);
       const auto end_ladder = cend(partons) - 1;
       for(auto parton_it = begin_ladder; parton_it < begin_ladder_2; ++parton_it){
         qqxt -= to_HepLorentzVector(*parton_it);
       }
 
       const int nabove = std::distance(begin_ladder, backmidquark);
       const int nbelow = std::distance(begin_ladder_2, end_ladder);
 
       std::vector<HLV> partonsHLV;
       partonsHLV.reserve(partons.size());
       for (size_t i = 0; i != partons.size(); ++i) {
         partonsHLV.push_back(to_HepLorentzVector(partons[i]));
       }
 
       const double current_factor = ME_W_qqxmid_current(
           aptype, bptype, nabove, nbelow, pa, pb,
           pq, pqbar, partonsHLV, plbar, pl, wqq, wc, Wprop
       );
 
       const double ladder_factor = FKL_ladder_weight(
           begin_ladder, end_ladder_1,
           q0, pa, pb, p1, pn,
           lambda
       )*FKL_ladder_weight(
           begin_ladder_2, end_ladder,
           qqxt, pa, pb, p1, pn,
           lambda
         );
       return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor;
     }
   } // namespace anonymous
 
   double MatrixElement::tree_kin_W(Event const & ev) const {
     using namespace event_type;
     auto const & incoming(ev.incoming());
 
   #ifndef NDEBUG
     // assert that there is exactly one decay corresponding to the W
     assert(ev.decays().size() == 1);
     auto const & w_boson{
       std::find_if(ev.outgoing().cbegin(), ev.outgoing().cend(),
         [] (Particle const & p) -> bool {
           return std::abs(p.type) == ParticleID::Wp;
         }) };
     assert(w_boson != ev.outgoing().cend());
     assert( static_cast<long int>(ev.decays().cbegin()->first)
         == std::distance(ev.outgoing().cbegin(), w_boson) );
   #endif
 
     // find decay products of W
     auto const & decay{ ev.decays().cbegin()->second };
     assert(decay.size() == 2);
     assert( ( is_anylepton(decay.at(0)) && is_anyneutrino(decay.at(1)) )
         || ( is_anylepton(decay.at(1)) && is_anyneutrino(decay.at(0)) ) );
 
     // get lepton & neutrino
     HLV plbar, pl;
     if (decay.at(0).type < 0){
       plbar = to_HepLorentzVector(decay.at(0));
       pl = to_HepLorentzVector(decay.at(1));
     }
     else{
       pl = to_HepLorentzVector(decay.at(0));
       plbar = to_HepLorentzVector(decay.at(1));
     }
 
     const auto pa = to_HepLorentzVector(incoming[0]);
     const auto pb = to_HepLorentzVector(incoming[1]);
 
     const auto partons = tag_extremal_jet_partons(ev);
 
     if(ev.type() == FKL){
       return tree_kin_W_FKL(incoming[0].type, incoming[1].type,
                             pa, pb, partons, plbar, pl,
                             param_.regulator_lambda,
                             param_.ew_parameters.Wprop());
     }
     if(ev.type() == unordered_backward){
       return tree_kin_W_uno(cbegin(incoming), cbegin(partons),
                             cend(partons), plbar, pl,
                             param_.regulator_lambda,
                             param_.ew_parameters.Wprop());
     }
     if(ev.type() == unordered_forward){
       return tree_kin_W_uno(crbegin(incoming), crbegin(partons),
                             crend(partons), plbar, pl,
                             param_.regulator_lambda,
                             param_.ew_parameters.Wprop());
     }
     if(ev.type() == extremal_qqxb){
       return tree_kin_W_qqx(cbegin(incoming), cbegin(partons),
                             cend(partons), plbar, pl,
                             param_.regulator_lambda,
                             param_.ew_parameters.Wprop());
     }
     if(ev.type() == extremal_qqxf){
       return tree_kin_W_qqx(crbegin(incoming), crbegin(partons),
                             crend(partons), plbar, pl,
                             param_.regulator_lambda,
                             param_.ew_parameters.Wprop());
     }
     assert(ev.type() == central_qqx);
     return tree_kin_W_qqxmid(incoming[0].type, incoming[1].type,
                              pa, pb, partons, plbar, pl,
                              param_.regulator_lambda,
                              param_.ew_parameters.Wprop());
   }
 
   double MatrixElement::tree_kin_Higgs(Event const & ev) const {
     if(is_uno(ev.type())){
       return tree_kin_Higgs_between(ev);
     }
     if(ev.outgoing().front().type == pid::Higgs){
       return tree_kin_Higgs_first(ev);
     }
     if(ev.outgoing().back().type == pid::Higgs){
       return tree_kin_Higgs_last(ev);
     }
     return tree_kin_Higgs_between(ev);
   }
 
   namespace {
     // Colour acceleration multipliers, for gluons see eq. (7) in arXiv:0910.5113
 #ifdef HEJ_BUILD_WITH_QCDLOOP
     // TODO: code duplication with jets.cc
     double K_g(double p1minus, double paminus) {
       return 1./2.*(p1minus/paminus + paminus/p1minus)*(C_A - 1./C_A) + 1./C_A;
     }
     double K_g(
         CLHEP::HepLorentzVector const & pout,
         CLHEP::HepLorentzVector const & pin
     ) {
       if(pin.z() > 0) return K_g(pout.plus(), pin.plus());
       return K_g(pout.minus(), pin.minus());
     }
     double K(
         ParticleID type,
         CLHEP::HepLorentzVector const & pout,
         CLHEP::HepLorentzVector const & pin
     ) {
       if(type == pid::gluon) return K_g(pout, pin);
       return C_F;
     }
 #endif
     // Colour factor in strict MRK limit
     double K_MRK(ParticleID type) {
       return (type == pid::gluon)?C_A:C_F;
     }
   }
 
   double MatrixElement::MH2_forwardH(
       CLHEP::HepLorentzVector const & p1out,
       CLHEP::HepLorentzVector const & p1in,
       ParticleID type2,
       CLHEP::HepLorentzVector const & p2out,
       CLHEP::HepLorentzVector const & p2in,
       CLHEP::HepLorentzVector const & pH,
       double t1, double t2
   ) const{
     ignore(p2out, p2in);
     const double shat = p1in.invariantMass2(p2in);
     const double vev = param_.ew_parameters.vev();
     // gluon case
 #ifdef HEJ_BUILD_WITH_QCDLOOP
     if(!param_.Higgs_coupling.use_impact_factors){
       return K(type2, p2out, p2in)*C_A*1./(16*M_PI*M_PI)*t1/t2*ME_Houtside_gq(
           p1out, p1in, p2out, p2in, pH,
           param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom,
           param_.Higgs_coupling.mb, vev
       )/(4*(N_C*N_C - 1));
     }
 #endif
     return K_MRK(type2)/C_A*9./2.*shat*shat*(
         C2gHgp(p1in,p1out,pH,vev) + C2gHgm(p1in,p1out,pH,vev)
     )/(t1*t2);
   }
 
   double MatrixElement::tree_kin_Higgs_first(Event const & ev) const {
     auto const & incoming = ev.incoming();
     auto const & outgoing = ev.outgoing();
     assert(outgoing.front().type == pid::Higgs);
     if(outgoing[1].type != pid::gluon) {
       assert(incoming.front().type == outgoing[1].type);
       return tree_kin_Higgs_between(ev);
     }
     const auto pH = to_HepLorentzVector(outgoing.front());
     const auto partons = tag_extremal_jet_partons(
         ev
     );
 
     const auto pa = to_HepLorentzVector(incoming[0]);
     const auto pb = to_HepLorentzVector(incoming[1]);
 
     const auto p1 = to_HepLorentzVector(partons.front());
     const auto pn = to_HepLorentzVector(partons.back());
 
     const auto q0 = pa - p1 - pH;
 
     const double t1 = q0.m2();
     const double t2 = (pn - pb).m2();
 
     return MH2_forwardH(
         p1, pa, incoming[1].type, pn, pb, pH,
         t1, t2
     )*FKL_ladder_weight(
         begin(partons) + 1, end(partons) - 1,
         q0, pa, pb, p1, pn,
         param_.regulator_lambda
     );
   }
 
   double MatrixElement::tree_kin_Higgs_last(Event const & ev) const {
     auto const & incoming = ev.incoming();
     auto const & outgoing = ev.outgoing();
     assert(outgoing.back().type == pid::Higgs);
     if(outgoing[outgoing.size()-2].type != pid::gluon) {
       assert(incoming.back().type == outgoing[outgoing.size()-2].type);
       return tree_kin_Higgs_between(ev);
     }
     const auto pH = to_HepLorentzVector(outgoing.back());
     const auto partons = tag_extremal_jet_partons(
         ev
     );
 
     const auto pa = to_HepLorentzVector(incoming[0]);
     const auto pb = to_HepLorentzVector(incoming[1]);
 
     auto p1 = to_HepLorentzVector(partons.front());
     const auto pn = to_HepLorentzVector(partons.back());
 
     auto q0 = pa - p1;
 
     const double t1 = q0.m2();
     const double t2 = (pn + pH - pb).m2();
 
     return MH2_forwardH(
         pn, pb, incoming[0].type, p1, pa, pH,
         t2, t1
     )*FKL_ladder_weight(
         begin(partons) + 1, end(partons) - 1,
         q0, pa, pb, p1, pn,
         param_.regulator_lambda
     );
   }
 
   namespace {
     template<class InIter, class partIter>
     double tree_kin_Higgs_uno(InIter BeginIn, InIter EndIn, partIter BeginPart,
                               partIter EndPart, const HLV & qH, const HLV & qHp1,
                               double mt, bool inc_bot, double mb, double vev){
 
       const auto pa = to_HepLorentzVector(*BeginIn);
       const auto pb = to_HepLorentzVector(*(EndIn-1));
 
       const auto pg = to_HepLorentzVector(*BeginPart);
       const auto p1 = to_HepLorentzVector(*(BeginPart+1));
       const auto pn = to_HepLorentzVector(*(EndPart-1));
 
       return ME_Higgs_current_uno(
         (BeginIn)->type, (EndIn-1)->type, pg, pn, pb, p1, pa,
         qH, qHp1, mt, inc_bot, mb, vev
         );
     }
   }
 
 
   double MatrixElement::tree_kin_Higgs_between(Event const & ev) const {
     using namespace event_type;
     auto const & incoming = ev.incoming();
     auto const & outgoing = ev.outgoing();
 
     const auto the_Higgs = std::find_if(
         begin(outgoing), end(outgoing),
         [](Particle const & s){ return s.type == pid::Higgs; }
     );
     assert(the_Higgs != end(outgoing));
     const auto pH = to_HepLorentzVector(*the_Higgs);
     const auto partons = tag_extremal_jet_partons(ev);
 
     const auto pa = to_HepLorentzVector(incoming[0]);
     const auto pb = to_HepLorentzVector(incoming[1]);
 
     auto p1 = to_HepLorentzVector(
         partons[(ev.type() == unob)?1:0]
     );
     auto pn = to_HepLorentzVector(
         partons[partons.size() - ((ev.type() == unof)?2:1)]
     );
 
     auto first_after_Higgs = begin(partons) + (the_Higgs-begin(outgoing));
     assert(
         (first_after_Higgs == end(partons) && (
             (ev.type() == unob)
             || partons.back().type != pid::gluon
         ))
         || first_after_Higgs->rapidity() >= the_Higgs->rapidity()
     );
     assert(
         (first_after_Higgs == begin(partons) && (
             (ev.type() == unof)
             || partons.front().type != pid::gluon
         ))
         || (first_after_Higgs-1)->rapidity() <= the_Higgs->rapidity()
     );
     // always treat the Higgs as if it were in between the extremal FKL partons
     if(first_after_Higgs == begin(partons)) ++first_after_Higgs;
     else if(first_after_Higgs == end(partons)) --first_after_Higgs;
 
     // t-channel momentum before Higgs
     auto qH = pa;
     for(auto parton_it = begin(partons); parton_it != first_after_Higgs; ++parton_it){
       qH -= to_HepLorentzVector(*parton_it);
     }
 
     auto q0 = pa - p1;
     auto begin_ladder = begin(partons) + 1;
     auto end_ladder = end(partons) - 1;
 
     double current_factor;
     if(ev.type() == FKL){
       current_factor = ME_Higgs_current(
           incoming[0].type, incoming[1].type,
           pn, pb, p1, pa, qH, qH - pH,
           param_.Higgs_coupling.mt,
           param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb,
           param_.ew_parameters.vev()
       );
     }
     else if(ev.type() == unob){
       current_factor = HEJ::C_A*HEJ::C_A/2*tree_kin_Higgs_uno(
         begin(incoming), end(incoming), begin(partons),
         end(partons), qH, qH-pH, param_.Higgs_coupling.mt,
         param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb,
         param_.ew_parameters.vev()
         );
       const auto p_unob = to_HepLorentzVector(partons.front());
       q0 -= p_unob;
       p1 += p_unob;
       ++begin_ladder;
     }
     else if(ev.type() == unof){
       current_factor = HEJ::C_A*HEJ::C_A/2*tree_kin_Higgs_uno(
         rbegin(incoming), rend(incoming), rbegin(partons),
         rend(partons), qH-pH, qH, param_.Higgs_coupling.mt,
         param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb,
         param_.ew_parameters.vev()
         );
       pn += to_HepLorentzVector(partons.back());
       --end_ladder;
     }
     else{
       throw std::logic_error("Can only reweight FKL or uno processes in H+Jets");
     }
 
     const double ladder_factor = FKL_ladder_weight(
         begin_ladder, first_after_Higgs,
         q0, pa, pb, p1, pn,
         param_.regulator_lambda
     )*FKL_ladder_weight(
         first_after_Higgs, end_ladder,
         qH - pH, pa, pb, p1, pn,
         param_.regulator_lambda
     );
     return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor;
   }
 
   namespace {
     double get_AWZH_coupling(Event const & ev, double alpha_s, double alpha_w) {
       const auto AWZH_boson = std::find_if(
           begin(ev.outgoing()), end(ev.outgoing()),
           [](auto const & p){return is_AWZH_boson(p);}
       );
       if(AWZH_boson == end(ev.outgoing())) return 1.;
       switch(AWZH_boson->type){
       case pid::Higgs:
         return alpha_s*alpha_s;
       case pid::Wp:
       case pid::Wm:
         return alpha_w*alpha_w;
         // TODO
       case pid::photon:
       case pid::Z:
       default:
         throw not_implemented("Emission of boson of unsupported type");
       }
     }
   }
 
   double MatrixElement::tree_param(Event const & ev, double mur) const {
     assert(is_resummable(ev.type()));
 
     const auto begin_partons = ev.begin_partons();
     const auto end_partons = ev.end_partons();
     const auto num_partons = std::distance(begin_partons, end_partons);
     const double alpha_s = alpha_s_(mur);
     const double gs2 = 4.*M_PI*alpha_s;
     double res = std::pow(gs2, num_partons);
     if(param_.log_correction){
       // use alpha_s(q_perp), evolved to mur
       assert(num_partons >= 2);
       const auto first_emission = std::next(begin_partons);
       const auto last_emission = std::prev(end_partons);
       for(auto parton = first_emission; parton != last_emission; ++parton){
         res *= 1. + alpha_s/(2.*M_PI)*beta0*log(mur/parton->perp());
       }
     }
     return get_AWZH_coupling(ev, alpha_s, param_.ew_parameters.alpha_w())*res;
   }
 
 } // namespace HEJ
diff --git a/src/PhaseSpacePoint.cc b/src/PhaseSpacePoint.cc
index d3b8dbc..b1c29c1 100644
--- a/src/PhaseSpacePoint.cc
+++ b/src/PhaseSpacePoint.cc
@@ -1,840 +1,840 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/PhaseSpacePoint.hh"
 
 #include <algorithm>
 #include <assert.h>
 #include <numeric>
 #include <random>
 
 #include "fastjet/ClusterSequence.hh"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/JetSplitter.hh"
 #include "HEJ/kinematics.hh"
 #include "HEJ/resummation_jet.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/event_types.hh"
 
 namespace HEJ{
   namespace {
     constexpr int max_jet_user_idx = PhaseSpacePoint::ng_max;
 
     bool is_nonjet_parton(fastjet::PseudoJet const & parton){
       assert(parton.user_index() != -1);
       return parton.user_index() > max_jet_user_idx;
     }
 
     bool is_jet_parton(fastjet::PseudoJet const & parton){
       assert(parton.user_index() != -1);
       return parton.user_index() <= max_jet_user_idx;
     }
 
     // user indices for partons with extremal rapidity
     constexpr int qqxmid1_uid = -9;
     constexpr int qqxmid2_uid = -8;
     constexpr int qqxb_uid = -7;
     constexpr int qqxf_uid = -6;
     constexpr int unob_uid = -5;
     constexpr int unof_uid = -4;
     constexpr int backward_FKL_uid = -3;
     constexpr int forward_FKL_uid = -2;
 
     double estimate_ng_mean(std::vector<fastjet::PseudoJet> const & Born_jets){
       const double delta_y =
         Born_jets.back().rapidity() - Born_jets.front().rapidity();
       assert(delta_y > 0);
       // Formula derived from fit in arXiv:1805.04446 (see Fig. 2)
       return 0.975052*delta_y;
     }
 
     static_assert(
         std::numeric_limits<double>::has_quiet_NaN,
         "no quiet NaN for double"
     );
     constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
 
   } // namespace anonymous
 
   Event::EventData to_EventData(PhaseSpacePoint psp){
     Event::EventData result;
     result.incoming = std::move(psp).incoming_;
     assert(result.incoming.size() == 2);
     result.outgoing = std::move(psp).outgoing_;
     // technically Event::EventData doesn't have to be sorted,
     // but PhaseSpacePoint should be anyway
     assert(
         std::is_sorted(
             begin(result.outgoing), end(result.outgoing),
             HEJ::rapidity_less{}
         )
     );
     assert(result.outgoing.size() >= 2);
     result.decays = std::move(psp).decays_;
     result.parameters.central = {NaN, NaN, psp.weight()};
     return result;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::cluster_jets(
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     fastjet::ClusterSequence cs(partons, param_.jet_param.def);
     return sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt));
   }
 
   bool PhaseSpacePoint::pass_resummation_cuts(
       std::vector<fastjet::PseudoJet> const & jets
   ) const{
     return cluster_jets(jets).size() == jets.size();
   }
 
   int PhaseSpacePoint::sample_ng(
     std::vector<fastjet::PseudoJet> const & Born_jets, RNG & ran
   ){
     const double ng_mean = estimate_ng_mean(Born_jets);
     std::poisson_distribution<int> dist(ng_mean);
     const int ng = dist(ran);
     assert(ng >= 0);
     assert(ng < ng_max);
     weight_ *= std::tgamma(ng + 1)*std::exp(ng_mean)*std::pow(ng_mean, -ng);
     return ng;
   }
 
   void PhaseSpacePoint::copy_AWZH_boson_from(Event const & event){
     auto const & from = event.outgoing();
 
     const auto AWZH_boson = std::find_if(
         begin(from), end(from),
         [](Particle const & p){ return is_AWZH_boson(p); }
     );
     if(AWZH_boson == end(from)) return;
     auto insertion_point = std::lower_bound(
         begin(outgoing_), end(outgoing_), *AWZH_boson, rapidity_less{}
     );
     outgoing_.insert(insertion_point, *AWZH_boson);
 
     // copy decay products
     const int idx = std::distance(begin(from), AWZH_boson);
     assert(idx >= 0);
     const auto decay_it = event.decays().find(idx);
     if(decay_it != end(event.decays())){
       const int new_idx = std::distance(begin(outgoing_), insertion_point);
       assert(new_idx >= 0);
       assert(outgoing_[new_idx].type == AWZH_boson->type);
       decays_.emplace(new_idx, decay_it->second);
     }
 
     assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{}));
   }
 
   namespace {
 
     auto get_first_anyquark_emission(Event const & ev) {
       // find born quarks (ignore extremal partons)
       auto const firstquark = std::find_if(
           std::next(ev.begin_partons()), std::prev(ev.end_partons(), 2),
           [](Particle const & s){ return (is_anyquark(s)); }
       );
       // assert that it is a q-q_bar pair.
       assert(std::distance(firstquark, ev.end_partons()) != 2);
       assert(
           ( is_quark(*firstquark) && is_antiquark(*std::next(firstquark)) )
           || ( is_antiquark(*firstquark) && is_quark(*std::next(firstquark)) )
       );
       return firstquark;
     }
 
     //! returns index of most backward q-qbar jet
     template<class Iterator>
     int get_back_quark_jet(Event const & ev, Iterator firstquark){
       // find jets at FO corresponding to the quarks
       // technically this isn't necessary for LO
       std::vector<int> const born_indices{ ev.particle_jet_indices() };
       const auto firstquark_idx = std::distance(ev.begin_partons(), firstquark);
       int const firstjet_idx = born_indices[firstquark_idx];
       assert(firstjet_idx>0);
       assert( born_indices[firstquark_idx+1] == firstjet_idx+1 );
 
       return firstjet_idx;
     }
 
     //! returns index of most backward q-qbar jet
     int getBackQuarkJet(Event const & ev){
       const auto firstquark = get_first_anyquark_emission(ev);
       return get_back_quark_jet(ev, firstquark);
     }
 
     template<class ConstIterator, class Iterator>
     void label_extremal_qqx(
       ConstIterator born_begin, ConstIterator born_end,
       Iterator first_out
     ){
       // find born quarks
       const auto firstquark = std::find_if(
         born_begin, born_end-1,
           [](Particle const & s){ return (is_anyquark(s)); }
       );
       assert(firstquark != born_end-1);
       const auto secondquark = std::find_if(
         firstquark+1, born_end,
           [](Particle const & s){ return (is_anyquark(s)); }
       );
       assert(secondquark != born_end);
       assert( ( is_quark(*firstquark) && is_antiquark(*secondquark) )
             || ( is_antiquark(*firstquark) && is_quark(*secondquark) ));
       assert(first_out->type     == ParticleID::gluon);
       assert((first_out+1)->type == ParticleID::gluon);
 
       // copy type from born
       first_out->type     = firstquark->type;
       (first_out+1)->type = secondquark->type;
     }
   }
 
   void PhaseSpacePoint::label_qqx(Event const & event){
     assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{}));
     assert(filter_partons(outgoing_).size() == outgoing_.size());
     if(qqxb_){
       label_extremal_qqx( event.outgoing().cbegin(), event.outgoing().cend(),
         outgoing_.begin()
       );
       return;
     }
     if(qqxf_){ // same as qqxb with reversed order
       label_extremal_qqx( event.outgoing().crbegin(), event.outgoing().crend(),
         outgoing_.rbegin()
       );
       return;
     }
     // central qqx
     const auto firstquark = get_first_anyquark_emission(event);
 
     // find jets at FO corresponding to the quarks
     // technically this isn't necessary for LO
     const auto firstjet_idx = get_back_quark_jet(event, firstquark);
 
     // find corresponding jets after resummation
     fastjet::ClusterSequence cs{to_PseudoJet(outgoing_), param_.jet_param.def};
     auto const jets = fastjet::sorted_by_rapidity(
                         cs.inclusive_jets( param_.jet_param.min_pt ));
     std::vector<int> const resum_indices{ cs.particle_jet_indices({jets}) };
 
     // assert that jets didn't move
     assert(nearby_ep( ( event.jets().cbegin()+firstjet_idx )->rapidity(),
                       jets[ firstjet_idx ].rapidity(),   1e-2) );
     assert(nearby_ep( ( event.jets().cbegin()+firstjet_idx+1 )->rapidity(),
                       jets[ firstjet_idx+1 ].rapidity(), 1e-2) );
 
     // find last partons in first (central) jet
     size_t idx_out = 0;
     for(size_t i=resum_indices.size()-2; i>0; --i)
       if(resum_indices[i] == firstjet_idx){
         idx_out = i;
         break;
       }
     assert(idx_out != 0);
 
     // check that there is sufficient pt in jets from the quarks
     const double minpartonjetpt = 1. - param_.max_ext_soft_pt_fraction;
     if (outgoing_[idx_out].p.pt()<minpartonjetpt*( event.jets().cbegin()+firstjet_idx )->pt()){
       weight_=0.;
       status_ = StatusCode::wrong_jets;
       return;
     }
 
     if (outgoing_[idx_out+1].p.pt()<minpartonjetpt*( event.jets().cbegin()+firstjet_idx+1 )->pt()){
       weight_=0.;
       status_ = StatusCode::wrong_jets;
       return;
     }
     // check that no additional emission between jets
     // such configurations are possible if we have an gluon gets generated
     // inside the rapidities of the qqx chain, but clusted to a
     // differnet/outside jet. Changing this is non trivial
     if(resum_indices[idx_out+1] != resum_indices[idx_out]+1){
       weight_=0.;
       status_ = StatusCode::gluon_in_qqx;
       return;
     }
     outgoing_[idx_out].type   = firstquark->type;
     outgoing_[idx_out+1].type = std::next(firstquark)->type;
   }
 
   void PhaseSpacePoint::label_quarks(Event const & ev){
     const auto WEmit = std::find_if(
       begin(ev.outgoing()), end(ev.outgoing()),
       [](Particle const & s){ return abs(s.type) == pid::Wp; }
     );
 
     if (WEmit != end(ev.outgoing())){
       if(!qqxb_) {
         const size_t backward_FKL_idx = unob_?1:0;
         const auto backward_FKL = std::next(ev.begin_partons(), backward_FKL_idx);
         outgoing_[backward_FKL_idx].type = backward_FKL->type;
       }
       if(!qqxf_) {
         const size_t forward_FKL_idx = unof_?1:0;
         const auto forward_FKL = std::prev(ev.end_partons(), 1+forward_FKL_idx);
         outgoing_.rbegin()[unof_].type = forward_FKL->type;
       }
     } else {
       most_backward_FKL(outgoing_).type = ev.incoming().front().type;
       most_forward_FKL(outgoing_).type = ev.incoming().back().type;
     }
 
     if(qqxmid_||qqxb_||qqxf_){
       label_qqx(ev);
     }
   }
 
   PhaseSpacePoint::PhaseSpacePoint(
       Event const & ev, PhaseSpacePointConfig conf, RNG & ran
   ):
     unob_{ev.type() == event_type::unob},
     unof_{ev.type() == event_type::unof},
     qqxb_{ev.type() == event_type::qqxexb},
     qqxf_{ev.type() == event_type::qqxexf},
     qqxmid_{ev.type() == event_type::qqxmid},
     param_{std::move(conf)},
     status_{unspecified}
   {
     weight_ = 1;
     const auto & Born_jets = ev.jets();
     const int ng = sample_ng(Born_jets, ran);
     weight_ /= std::tgamma(ng + 1);
     const int ng_jets = sample_ng_jets(ng, Born_jets, ran);
     std::vector<fastjet::PseudoJet> out_partons = gen_non_jet(
        ng - ng_jets, CMINPT, param_.jet_param.min_pt, ran
     );
 
     int qqxbackjet(-1);
     if(qqxmid_){
       qqxbackjet = getBackQuarkJet(ev);
     }
 
     const auto qperp = std::accumulate(
       begin(out_partons), end(out_partons),
       fastjet::PseudoJet{}
     );
     const auto jets = reshuffle(Born_jets, qperp);
     if(weight_ == 0.) {
       status_ = failed_reshuffle;
       return;
     }
     if(! pass_resummation_cuts(jets)){
       status_ = failed_resummation_cuts;
       weight_ = 0.;
       return;
     }
     std::vector<fastjet::PseudoJet> jet_partons = split(
       jets, ng_jets, qqxbackjet, ran
     );
     if(weight_ == 0.) {
       status_ = StatusCode::failed_split;
       return;
     }
     if(qqxmid_){
       rescale_qqx_rapidities(
         out_partons, jets,
         most_backward_FKL(jet_partons).rapidity(),
         most_forward_FKL(jet_partons).rapidity(),
         qqxbackjet
         );
     }
     else{
       rescale_rapidities(
         out_partons,
         most_backward_FKL(jet_partons).rapidity(),
         most_forward_FKL(jet_partons).rapidity()
       );
     }
     if(! cluster_jets(out_partons).empty()){
       weight_ = 0.;
       status_ = StatusCode::empty_jets;
       return;
     }
     std::sort(begin(out_partons), end(out_partons), rapidity_less{});
     assert(
       std::is_sorted(begin(jet_partons), end(jet_partons), rapidity_less{})
     );
     const auto first_jet_parton = out_partons.insert(
       end(out_partons), begin(jet_partons), end(jet_partons)
     );
     std::inplace_merge(
       begin(out_partons), first_jet_parton, end(out_partons), rapidity_less{}
     );
 
     if(! jets_ok(Born_jets, out_partons)){
       weight_ = 0.;
       status_ = StatusCode::wrong_jets;
       return;
     }
 
     weight_ *= phase_space_normalisation(Born_jets.size(), out_partons.size());
 
     outgoing_.reserve(out_partons.size() + 1); // one slot for possible A, W, Z, H
     for(auto & p: out_partons){
       outgoing_.emplace_back(Particle{pid::gluon, std::move(p), {}});
     }
     assert(!outgoing_.empty());
 
     label_quarks(ev);
     if(weight_ == 0.) {
       //! @TODO optimise s.t. this is not possible
       // status is handled internally
       return;
     }
 
     copy_AWZH_boson_from(ev);
 
     reconstruct_incoming(ev.incoming());
     status_ = StatusCode::good;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_non_jet(
       int count, double ptmin, double ptmax, RNG & ran
   ){
     // heuristic parameters for pt sampling
     const double ptpar = 1.3 + count/5.;
     const double temp1 = atan((ptmax - ptmin)/ptpar);
 
     std::vector<fastjet::PseudoJet> partons(count);
     for(size_t i = 0; i < static_cast<size_t>(count); ++i){
       const double r1 = ran.flat();
       const double pt = ptmin + ptpar*tan(r1*temp1);
       const double temp2 = cos(r1*temp1);
       const double phi = 2*M_PI*ran.flat();
       weight_ *= 2.0*M_PI*pt*ptpar*temp1/(temp2*temp2);
       // we don't know the allowed rapidity span yet,
       // set a random value to be rescaled later on
       const double y = ran.flat();
       partons[i].reset_PtYPhiM(pt, y, phi);
       // Set user index higher than any jet-parton index
       // in order to assert that these are not inside jets
       partons[i].set_user_index(i + 1 + ng_max);
 
       assert(ptmin-1e-5 <= partons[i].pt() && partons[i].pt() <= ptmax+1e-5);
     }
     assert(std::all_of(partons.cbegin(), partons.cend(), is_nonjet_parton));
     return sorted_by_rapidity(partons);
   }
 
   void PhaseSpacePoint::rescale_qqx_rapidities(
       std::vector<fastjet::PseudoJet> & out_partons,
       std::vector<fastjet::PseudoJet> const & jets,
       const double ymin1, const double ymax2,
       const int qqxbackjet
   ){
     const double ymax1 = jets[qqxbackjet].rapidity();
     const double ymin2 = jets[qqxbackjet+1].rapidity();
     constexpr double ep = 1e-7;
     const double tot_y = ymax1 - ymin1 + ymax2 - ymin2;
 
     std::vector<std::reference_wrapper<fastjet::PseudoJet>> refpart(
       out_partons.begin(), out_partons.end());
 
     double ratio = (ymax1 - ymin1)/tot_y;
 
     const auto gap{ std::find_if(refpart.begin(), refpart.end(),
                             [ratio](fastjet::PseudoJet p){
                               return (p.rapidity()>=ratio);} ) };
 
     double ymin = ymin1;
     double ymax = ymax1;
     double dy = ymax - ymin - 2*ep;
     double offset = 0.;
     for(auto it_part=refpart.begin(); it_part<refpart.end(); ++it_part){
       if(it_part == gap){
         ymin = ymin2;
         ymax = ymax2;
         dy = ymax - ymin - 2*ep;
         offset = ratio;
         ratio = 1-ratio;
       }
       fastjet::PseudoJet & part = *it_part;
       assert(offset <= part.rapidity() && part.rapidity() < ratio+offset);
       const double y = ymin + ep + dy*((part.rapidity()-offset)/ratio);
       part.reset_momentum_PtYPhiM(part.pt(), y, part.phi());
       weight_ *= tot_y-4.*ep;
       assert(ymin <= part.rapidity() && part.rapidity() <= ymax);
     }
     assert(is_sorted(begin(out_partons), end(out_partons), rapidity_less{}));
   }
 
   void PhaseSpacePoint::rescale_rapidities(
       std::vector<fastjet::PseudoJet> & partons,
       double ymin, double ymax
   ){
     constexpr double ep = 1e-7;
     for(auto & parton: partons){
       assert(0 <= parton.rapidity() && parton.rapidity() <= 1);
       const double dy = ymax - ymin - 2*ep;
       const double y = ymin + ep + dy*parton.rapidity();
       parton.reset_momentum_PtYPhiM(parton.pt(), y, parton.phi());
       weight_ *= dy;
       assert(ymin <= parton.rapidity() && parton.rapidity() <= ymax);
     }
   }
 
   namespace {
     template<typename T, typename... Rest>
     auto min(T const & a, T const & b, Rest&&... r) {
       using std::min;
       return min(a, min(b, std::forward<Rest>(r)...));
     }
   }
 
   double PhaseSpacePoint::probability_in_jet(
       std::vector<fastjet::PseudoJet> const & Born_jets
   ) const{
     assert(std::is_sorted(begin(Born_jets), end(Born_jets), rapidity_less{}));
     assert(Born_jets.size() >= 2);
 
     const double dy =
       Born_jets.back().rapidity() - Born_jets.front().rapidity();
     const double R = param_.jet_param.def.R();
     const int njets = Born_jets.size();
     const double p_J_y_large = (njets-1)*R*R/(2.*dy);
     const double p_J_y0 = njets*R/M_PI;
     return min(p_J_y_large, p_J_y0, 1.);
   }
 
   int PhaseSpacePoint::sample_ng_jets(
       int ng, std::vector<fastjet::PseudoJet> const & Born_jets, RNG & ran
   ){
     const double p_J = probability_in_jet(Born_jets);
     std::binomial_distribution<> bin_dist(ng, p_J);
     const int ng_J = bin_dist(ran);
     weight_ *= std::pow(p_J, -ng_J)*std::pow(1 - p_J, ng_J - ng);
     return ng_J;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::reshuffle(
       std::vector<fastjet::PseudoJet> const & Born_jets,
       fastjet::PseudoJet const & q
   ){
     if(q == fastjet::PseudoJet{0, 0, 0, 0}) return Born_jets;
     const auto jets = resummation_jet_momenta(Born_jets, q);
     if(jets.empty()){
       weight_ = 0;
       return {};
     }
 
     // additional Jacobian to ensure Born integration over delta gives 1
     weight_ *= resummation_jet_weight(Born_jets, q);
     return jets;
   }
 
   std::vector<int> PhaseSpacePoint::distribute_jet_partons(
       int ng_jets, std::vector<fastjet::PseudoJet> const & jets, RNG & ran
   ){
     size_t first_valid_jet = 0;
     size_t num_valid_jets = jets.size();
     const double R_eff = 5./3.*param_.jet_param.def.R();
     // if there is an unordered jet too far away from the FKL jets
     // then extra gluon constituents of the unordered jet would
     // violate the FKL rapidity ordering
     if((unob_||qqxb_) && jets[0].delta_R(jets[1]) > R_eff){
       ++first_valid_jet;
       --num_valid_jets;
     }
     else if((unof_||qqxf_) && jets[jets.size()-1].delta_R(jets[jets.size()-2]) > R_eff){
       --num_valid_jets;
     }
     std::vector<int> np(jets.size(), 1);
     for(int i = 0; i < ng_jets; ++i){
       ++np[first_valid_jet + ran.flat() * num_valid_jets];
     }
     weight_ *= std::pow(num_valid_jets, ng_jets);
     return np;
   }
 
 #ifndef NDEBUG
   namespace{
     bool tagged_FKL_backward(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       return std::find_if(
           begin(jet_partons), end(jet_partons),
           [](fastjet::PseudoJet const & p){
             return p.user_index() == backward_FKL_uid;
           }
       ) != end(jet_partons);
     }
 
     bool tagged_FKL_forward(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       // the most forward FKL parton is most likely near the end of jet_partons;
       // start search from there
       return std::find_if(
           jet_partons.rbegin(), jet_partons.rend(),
           [](fastjet::PseudoJet const & p){
             return p.user_index() == forward_FKL_uid;
           }
       ) != jet_partons.rend();
     }
 
     bool tagged_FKL_extremal(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       return tagged_FKL_backward(jet_partons) && tagged_FKL_forward(jet_partons);
     }
 
   } // namespace anonymous
 #endif
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::split(
       std::vector<fastjet::PseudoJet> const & jets,
       int ng_jets, size_t qqxbackjet, RNG & ran
   ){
     return split(
       jets, distribute_jet_partons(ng_jets, jets, ran), qqxbackjet, ran);
   }
 
   bool PhaseSpacePoint::pass_extremal_cuts(
       fastjet::PseudoJet const & ext_parton,
       fastjet::PseudoJet const & jet
   ) const{
     if(ext_parton.pt() < param_.min_extparton_pt) return false;
     return (ext_parton - jet).pt()/jet.pt() < param_.max_ext_soft_pt_fraction;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::split(
       std::vector<fastjet::PseudoJet> const & jets,
       std::vector<int> const & np,
       size_t qqxbackjet,
       RNG & ran
   ){
     assert(! jets.empty());
     assert(jets.size() == np.size());
     assert(pass_resummation_cuts(jets));
 
     const size_t most_backward_FKL_idx = 0 + unob_ + qqxb_;
     const size_t most_forward_FKL_idx = jets.size() - 1 - unof_ - qqxf_;
     const auto & jet = param_.jet_param;
     const JetSplitter jet_splitter{jet.def, jet.min_pt};
 
     std::vector<fastjet::PseudoJet> jet_partons;
     // randomly distribute jet gluons among jets
     for(size_t i = 0; i < jets.size(); ++i){
       auto split_res = jet_splitter.split(jets[i], np[i], ran);
       weight_ *= split_res.weight;
       if(weight_ == 0) return {};
       assert(
           std::all_of(
               begin(split_res.constituents), end(split_res.constituents),
               is_jet_parton
           )
       );
       const auto first_new_parton = jet_partons.insert(
           end(jet_partons),
           begin(split_res.constituents), end(split_res.constituents)
       );
       // mark uno and extremal FKL emissions here so we can check
       // their position once all emissions are generated
       // also mark qqxmid partons, and apply appropriate pt cut.
       auto extremal = end(jet_partons);
       if (i == most_backward_FKL_idx){ //FKL backward emission
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(backward_FKL_uid);
       }
       else if(((unob_ || qqxb_) && i == 0)){
         // unordered/qqxb
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index((unob_)?unob_uid:qqxb_uid);
       }
 
       else if (i == most_forward_FKL_idx){
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(forward_FKL_uid);
       }
       else if(((unof_ || qqxf_) && i == jets.size() - 1)){
         // unordered/qqxf
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index((unof_)?unof_uid:qqxf_uid);
       }
       else if((qqxmid_ && i == qqxbackjet)){
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(qqxmid1_uid);
       }
       else if((qqxmid_ && i == qqxbackjet+1)){
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(qqxmid2_uid);
       }
       if(
           extremal != end(jet_partons)
           && !pass_extremal_cuts(*extremal, jets[i])
       ){
         weight_ = 0;
         return {};
       }
     }
     assert(tagged_FKL_extremal(jet_partons));
     std::sort(begin(jet_partons), end(jet_partons), rapidity_less{});
     if(
         !extremal_ok(jet_partons)
         || !split_preserved_jets(jets, jet_partons)
     ){
       weight_ = 0.;
       return {};
     }
     return jet_partons;
   }
 
   bool PhaseSpacePoint::extremal_ok(
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     assert(std::is_sorted(begin(partons), end(partons), rapidity_less{}));
     if(unob_ && partons.front().user_index() !=  unob_uid) return false;
     if(unof_ && partons.back().user_index() !=  unof_uid) return false;
     if(qqxb_ && partons.front().user_index() !=  qqxb_uid) return false;
     if(qqxf_ && partons.back().user_index() !=  qqxf_uid) return false;
     return
       most_backward_FKL(partons).user_index() == backward_FKL_uid
       && most_forward_FKL(partons).user_index() == forward_FKL_uid;
   }
 
   bool PhaseSpacePoint::split_preserved_jets(
         std::vector<fastjet::PseudoJet> const & jets,
         std::vector<fastjet::PseudoJet> const & jet_partons
   ) const{
     assert(std::is_sorted(begin(jets), end(jets), rapidity_less{}));
 
     const auto split_jets = cluster_jets(jet_partons);
     // this can happen if two overlapping jets
     // are both split into more than one parton
     if(split_jets.size() != jets.size()) return false;
     for(size_t i = 0; i < split_jets.size(); ++i){
       // this can happen if there are two overlapping jets
       // and a parton is assigned to the "wrong" jet
       if(!nearby_ep(jets[i].rapidity(), split_jets[i].rapidity(), 1e-2)){
         return false;
       }
     }
     return true;
   }
 
   template<class Particle>
   Particle const & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> const & partons
   ) const{
     return partons[0 + unob_ + qqxb_];
   }
 
   template<class Particle>
   Particle const & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> const & partons
   ) const{
     const size_t idx = partons.size() - 1 - unof_ - qqxf_;
     assert(idx < partons.size());
     return partons[idx];
   }
 
   template<class Particle>
   Particle & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> & partons
   ) const{
     return partons[0 + unob_ + qqxb_];
   }
 
   template<class Particle>
   Particle & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> & partons
   ) const{
     const size_t idx = partons.size() - 1 - unof_ - qqxf_;
     assert(idx < partons.size());
     return partons[idx];
   }
 
   bool PhaseSpacePoint::contains_idx(
       fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton
   ) const {
     auto const & constituents = jet.constituents();
     const int idx = parton.user_index();
     const bool injet = std::find_if(
         begin(constituents), end(constituents),
         [idx](fastjet::PseudoJet const & con){return con.user_index() == idx;}
     ) != end(constituents);
     const double minpartonjetpt = 1. - param_.max_ext_soft_pt_fraction;
     return ((parton.pt()>minpartonjetpt*jet.pt())&&injet);
   }
 
   bool PhaseSpacePoint::jets_ok(
       std::vector<fastjet::PseudoJet> const & Born_jets,
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     fastjet::ClusterSequence cs(partons, param_.jet_param.def);
     const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt));
     if(jets.size() != Born_jets.size()) return false;
     int in_jet = 0;
     for(size_t i = 0; i < jets.size(); ++i){
       assert(jets[i].has_constituents());
       for(auto && parton: jets[i].constituents()){
         if(is_nonjet_parton(parton)) return false;
       }
       in_jet += jets[i].constituents().size();
     }
     const int expect_in_jet = std::count_if(
         partons.cbegin(), partons.cend(), is_jet_parton
     );
     if(in_jet != expect_in_jet) return false;
     // note that PseudoJet::contains does not work here
     if(! (
            contains_idx(most_backward_FKL(jets), most_backward_FKL(partons))
            &&  contains_idx(most_forward_FKL(jets), most_forward_FKL(partons))
        )) return false;
     if(unob_ && !contains_idx(jets.front(), partons.front())) return false;
     if(qqxb_ && !contains_idx(jets.front(), partons.front())) return false;
     if(unof_ && !contains_idx(jets.back(), partons.back())) return false;
     if(qqxf_ && !contains_idx(jets.back(), partons.back())) return false;
 #ifndef NDEBUG
     for(size_t i = 0; i < jets.size(); ++i){
       assert(nearby_ep(jets[i].rapidity(), Born_jets[i].rapidity(), 1e-2));
     }
 #endif
     return true;
   }
 
   void PhaseSpacePoint::reconstruct_incoming(
       std::array<Particle, 2> const & Born_incoming
   ){
     std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_);
     for(size_t i = 0; i < incoming_.size(); ++i){
       incoming_[i].type = Born_incoming[i].type;
     }
     assert(momentum_conserved());
   }
 
   double PhaseSpacePoint::phase_space_normalisation(
       int num_Born_jets, int num_out_partons
   ) const{
     return pow(16*pow(M_PI,3), num_Born_jets - num_out_partons);
   }
 
   bool PhaseSpacePoint::momentum_conserved() const{
     fastjet::PseudoJet diff;
     for(auto const & in: incoming()) diff += in.p;
     const double norm = diff.E();
     for(auto const & out: outgoing()) diff -= out.p;
     return nearby(diff, fastjet::PseudoJet{}, norm);
   }
 
 } //namespace HEJ
diff --git a/src/RivetAnalysis.cc b/src/RivetAnalysis.cc
index ccf3943..975a564 100644
--- a/src/RivetAnalysis.cc
+++ b/src/RivetAnalysis.cc
@@ -1,186 +1,186 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/RivetAnalysis.hh"
 
 #include "HEJ/ConfigFlags.hh"
 
 #ifdef HEJ_BUILD_WITH_RIVET
 
 #include <ostream>
 #include <stddef.h>
 
 #include "yaml-cpp/yaml.h"
 
 #include "Rivet/AnalysisHandler.hh"
 #include "Rivet/Config/RivetConfig.hh"
 
 #ifdef RIVET_ENABLE_HEPMC_3
 
 #include "HepMC3/GenEvent.h"
 #include "HEJ/HepMC3Interface.hh"
 
 #else // rivet with HepMC 2
 
 #include "HepMC/GenEvent.h"
 #include "HEJ/HepMC2Interface.hh"
 
 #endif
 
 #include "HEJ/Event.hh"
 #include "HEJ/exceptions.hh"
 
 #endif
 
 namespace HEJ{
   std::unique_ptr<Analysis> RivetAnalysis::create(
       YAML::Node const & config, LHEF::HEPRUP const & heprup
   ){
     return std::make_unique<RivetAnalysis>(config, heprup);
   }
 }
 
 #ifdef HEJ_BUILD_WITH_RIVET
 
 namespace HEJ {
 #ifdef RIVET_ENABLE_HEPMC_3
   using HepMCInterface=HepMC3Interface;
 #else
   using HepMCInterface=HepMC2Interface;
 #endif
 
   struct RivetAnalysis::rivet_info {
     rivet_info(std::unique_ptr<Rivet::AnalysisHandler> && h,
         std::string && s,  HEJ::HepMCInterface && m):
       handler{std::move(h)}, name{std::move(s)}, hepmc{std::move(m)}
     {}
     // AnalysisHandler doesn't allow a copy constructor -> use ptr
     std::unique_ptr<Rivet::AnalysisHandler> handler;
     std::string name;
     HEJ::HepMCInterface hepmc;
   };
 
   RivetAnalysis::RivetAnalysis(YAML::Node const & config,
                                LHEF::HEPRUP const & heprup
   ):
     heprup_{heprup},
     first_event_(true)
   {
 
     // assert that only supported options are provided
     if(!config.IsMap()) throw invalid_type{"Rivet config has to be a map!"};
     for(auto const & entry: config){
       const auto name = entry.first.as<std::string>();
       if(name != "output" && name != "rivet")
         throw unknown_option{"Unknown option \'"+name+"\' provided to rivet."};
     }
 
     // get output name
     if(!config["output"])
       throw std::invalid_argument{
         "No output was provided to rivet. "
         "Either give an output or deactivate rivet."
       };
     if(!config["output"].IsScalar())
       throw invalid_type{
         "Output for rivet must be a scalar."
       };
     output_name_ = config["output"].as<std::string>();
 
     // read in analyses
     const auto & name_node = config["rivet"];
     switch(name_node.Type()){
     case YAML::NodeType::Scalar:
       analyses_names_.push_back(name_node.as<std::string>());
       break;
     case YAML::NodeType::Sequence:
       for(YAML::const_iterator it = name_node.begin(); it != name_node.end(); ++it){
         analyses_names_.push_back(it->as<std::string>());
       }
       break;
     default:
       throw std::invalid_argument{
         "No analysis was provided to rivet. "
         "Either give an analysis or deactivate rivet."
       };
     }
   }
 
   // it is a bit ugly that we can't do this directly in `initialise`
   void RivetAnalysis::init(Event const & event){
 #ifdef HEJ_USE_RIVET2
     rivet_runs_.reserve(event.variations().size()+1);
 #else
     (void) event; // shut up compiler
 #endif
     rivet_runs_.emplace_back( std::make_unique<Rivet::AnalysisHandler>(),
       std::string(""), HepMCInterface(heprup_)
     );
     rivet_runs_.back().handler->addAnalyses(analyses_names_);
 
 #ifdef HEJ_USE_RIVET2
     //! scale variation in rivet 2 through multiple analyses
     if( !event.variations().empty() ){
       for(auto const & vari : event.variations()){
         rivet_runs_.emplace_back( std::make_unique<Rivet::AnalysisHandler>(),
           "."+to_simple_string(*vari.description), HepMCInterface(heprup_)
         );
         rivet_runs_.back().handler->addAnalyses(analyses_names_);
       }
     }
 #endif
   }
 
   void RivetAnalysis::fill(Event const & event, Event const &){
     if(first_event_){
       first_event_=false;
       init(event);
     }
     auto hepmc_event(rivet_runs_.front().hepmc(event));
     rivet_runs_.front().handler->analyze(hepmc_event);
 
 #ifdef HEJ_USE_RIVET2
     for(size_t i = 1; i < rivet_runs_.size(); ++i){
       auto & run = rivet_runs_[i];
       run.hepmc.set_central(hepmc_event, event, i-1);
       run.handler->analyze(hepmc_event);
     }
 #endif
   }
 
   void RivetAnalysis::finalise(){
     for(auto & run: rivet_runs_){
       run.handler->finalize();
       run.handler->writeData(output_name_+run.name+std::string(".yoda"));
     }
   }
 } // namespace HEJ
 
 #else // no rivet => create empty analysis
 
 namespace Rivet {
   class AnalysisHandler {};
 }
 
 namespace HEJ {
   struct RivetAnalysis::rivet_info{};
 
   RivetAnalysis::RivetAnalysis(YAML::Node const &, LHEF::HEPRUP const &){
     throw std::invalid_argument(
         "Failed to create RivetAnalysis: "
         "HEJ 2 was built without rivet support"
     );
   }
 
   void RivetAnalysis::init(Event const &){}
   void RivetAnalysis::fill(Event const &, Event const &){}
   void RivetAnalysis::finalise(){}
 } // namespace HEJ
 
 #endif
 
 namespace HEJ {
   RivetAnalysis::~RivetAnalysis() = default;
 }
diff --git a/src/YAMLreader.cc b/src/YAMLreader.cc
index 2a7cd19..f01914a 100644
--- a/src/YAMLreader.cc
+++ b/src/YAMLreader.cc
@@ -1,565 +1,565 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/YAMLreader.hh"
 
 #include <algorithm>
 #include <iostream>
 #include <limits>
 #include <map>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
 #include <dlfcn.h>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/ConfigFlags.hh"
 #include "HEJ/output_formats.hh"
 #include "HEJ/ScaleFunction.hh"
 
 namespace HEJ{
   class Event;
 
   namespace{
     //! Get YAML tree of supported options
     /**
      * The configuration file is checked against this tree of options
      * in assert_all_options_known.
      */
     YAML::Node const & get_supported_options(){
       const static YAML::Node supported = [](){
         YAML::Node supported;
         static const auto opts = {
           "trials", "min extparton pt", "max ext soft pt fraction",
           "scales", "scale factors", "max scale ratio", "import scales",
           "log correction", "event output", "analysis", "analyses", "vev",
           "regulator parameter", "max events"
         };
         // add subnodes to "supported" - the assigned value is irrelevant
         for(auto && opt: opts) supported[opt] = "";
         for(auto && jet_opt: {"min pt", "algorithm", "R"}){
           supported["resummation jets"][jet_opt] = "";
           supported["fixed order jets"][jet_opt] = "";
         }
         for(auto && opt: {"mt", "use impact factors", "include bottom", "mb"}){
           supported["Higgs coupling"][opt] = "";
         }
         for(auto && opt: {"name", "seed"}){
           supported["random generator"][opt] = "";
         }
         for(auto && opt: {"FKL", "unordered", "extremal qqx", "central qqx", "non-resummable"}){
           supported["event treatment"][opt] = "";
         }
         for(auto && particle_type: {"Higgs", "W", "Z"}){
           for(auto && particle_opt: {"mass", "width"}){
             supported["particle properties"][particle_type][particle_opt] = "";
           }
         }
         for(auto && opt: {"type", "trials", "max deviation"}){
           supported["unweight"][opt] = "";
         }
         return supported;
       }();
       return supported;
     }
 
     fastjet::JetAlgorithm to_JetAlgorithm(std::string const & algo){
       using namespace fastjet;
       static const std::map<std::string, fastjet::JetAlgorithm> known = {
         {"kt", kt_algorithm},
         {"cambridge", cambridge_algorithm},
         {"antikt", antikt_algorithm},
         {"cambridge for passive", cambridge_for_passive_algorithm},
         {"plugin", plugin_algorithm}
       };
       const auto res = known.find(algo);
       if(res == known.end()){
         throw std::invalid_argument("Unknown jet algorithm \"" + algo + "\"");
       }
       return res->second;
     }
 
     EventTreatment to_EventTreatment(std::string const & name){
       static const std::map<std::string, EventTreatment> known = {
         {"reweight", EventTreatment::reweight},
         {"keep", EventTreatment::keep},
         {"discard", EventTreatment::discard}
       };
       const auto res = known.find(name);
       if(res == known.end()){
         throw std::invalid_argument("Unknown event treatment \"" + name + "\"");
       }
       return res->second;
     }
 
     WeightType to_weight_type(std::string const & setting){
       if(setting == "weighted")
           return WeightType::weighted;
       if(setting =="resummation")
           return WeightType::unweighted_resum;
       if(setting =="partial")
           return WeightType::partially_unweighted;
       throw std::invalid_argument{"Unknown weight type \"" + setting + "\""};
     }
 
   } // namespace anonymous
 
   namespace detail{
     void set_from_yaml(fastjet::JetAlgorithm & setting, YAML::Node const & yaml){
       setting = to_JetAlgorithm(yaml.as<std::string>());
     }
 
     void set_from_yaml(EventTreatment & setting, YAML::Node const & yaml){
       setting = to_EventTreatment(yaml.as<std::string>());
     }
 
     void set_from_yaml(ParticleID & setting, YAML::Node const & yaml){
       setting = to_ParticleID(yaml.as<std::string>());
     }
 
     void set_from_yaml(WeightType & setting, YAML::Node const & yaml){
       setting = to_weight_type(yaml.as<std::string>());
     }
 
   } // namespace detail
 
   JetParameters get_jet_parameters(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     JetParameters result;
     fastjet::JetAlgorithm jet_algo = fastjet::antikt_algorithm;
     double R;
     set_from_yaml_if_defined(jet_algo, node, entry, "algorithm");
     set_from_yaml(R, node, entry, "R");
     result.def = fastjet::JetDefinition{jet_algo, R};
     set_from_yaml(result.min_pt, node, entry, "min pt");
     return result;
   }
 
   RNGConfig to_RNGConfig(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     RNGConfig result;
     set_from_yaml(result.name, node, entry, "name");
     set_from_yaml_if_defined(result.seed, node, entry, "seed");
     return result;
   }
 
   ParticleProperties get_particle_properties(
       YAML::Node const & node, std::string const & entry,
       std::string const & boson
   ){
     ParticleProperties result;
     set_from_yaml(result.mass, node, entry, boson, "mass");
     set_from_yaml(result.width, node, entry, boson, "width");
     return result;
   }
 
   EWConstants get_ew_parameters(YAML::Node const & node){
     EWConstants result;
     double vev;
     set_from_yaml(vev, node, "vev");
     result.set_vevWZH(vev,
       get_particle_properties(node, "particle properties", "W"),
       get_particle_properties(node, "particle properties", "Z"),
       get_particle_properties(node, "particle properties", "Higgs")
     );
     return result;
   }
 
   HiggsCouplingSettings get_Higgs_coupling(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     static constexpr double mt_max = 2e4;
 #ifndef HEJ_BUILD_WITH_QCDLOOP
     if(node[entry]){
       throw std::invalid_argument{
         "Higgs coupling settings require building HEJ 2 "
           "with QCDloop support"
           };
     }
 #endif
     HiggsCouplingSettings settings;
     set_from_yaml_if_defined(settings.mt, node, entry, "mt");
     set_from_yaml_if_defined(settings.mb, node, entry, "mb");
     set_from_yaml_if_defined(settings.include_bottom, node, entry, "include bottom");
     set_from_yaml_if_defined(settings.use_impact_factors, node, entry, "use impact factors");
     if(settings.use_impact_factors){
       if(settings.mt != std::numeric_limits<double>::infinity()){
         throw std::invalid_argument{
           "Conflicting settings: "
             "impact factors may only be used in the infinite top mass limit"
             };
       }
     }
     else{
       // huge values of the top mass are numerically unstable
       settings.mt = std::min(settings.mt, mt_max);
     }
     return settings;
   }
 
   FileFormat to_FileFormat(std::string const & name){
     static const std::map<std::string, FileFormat> known = {
       {"Les Houches", FileFormat::Les_Houches},
       {"HepMC", FileFormat::HepMC},
       {"HepMC2", FileFormat::HepMC2},
       {"HepMC3", FileFormat::HepMC3},
       {"HDF5", FileFormat::HDF5}
     };
     const auto res = known.find(name);
     if(res == known.end()){
       throw std::invalid_argument("Unknown file format \"" + name + "\"");
     }
     return res->second;
   }
 
   std::string extract_suffix(std::string const & filename){
     size_t separator = filename.rfind('.');
     if(separator == filename.npos) return {};
     return filename.substr(separator + 1);
   }
 
   FileFormat format_from_suffix(std::string const & filename){
     const std::string suffix = extract_suffix(filename);
     if(suffix == "lhe") return FileFormat::Les_Houches;
     if(suffix == "hepmc") return FileFormat::HepMC;
     if(suffix == "hepmc3") return FileFormat::HepMC3;
     if(suffix == "hepmc2") return FileFormat::HepMC2;
     if(suffix == "hdf5") return FileFormat::HDF5;
     throw std::invalid_argument{
       "Can't determine format for output file \"" + filename  + "\""
     };
   }
 
   void assert_all_options_known(
       YAML::Node const & conf, YAML::Node const & supported
   ){
     if(!conf.IsMap()) return;
     if(!supported.IsMap()) throw invalid_type{"must not have sub-entries"};
     for(auto const & entry: conf){
       const auto name = entry.first.as<std::string>();
       if(! supported[name]) throw unknown_option{name};
       /* check sub-options, e.g. 'resummation jets: min pt'
        * we don't check analyses sub-options
        * those depend on the analysis being used and should be checked there
        * similar for "import scales"
        */
       if(name != "analyses" && name != "analysis" && name != "import scales"){
         try{
           assert_all_options_known(conf[name], supported[name]);
         }
         catch(unknown_option const & ex){
           throw unknown_option{name + ": " + ex.what()};
         }
         catch(invalid_type const & ex){
           throw invalid_type{name + ": " + ex.what()};
         }
       }
     }
   }
 
 } // namespace HEJ
 
 namespace YAML {
 
   Node convert<HEJ::OutputFile>::encode(HEJ::OutputFile const & outfile) {
     Node node;
     node[to_string(outfile.format)] = outfile.name;
     return node;
   }
 
   bool convert<HEJ::OutputFile>::decode(Node const & node, HEJ::OutputFile & out) {
     switch(node.Type()){
     case NodeType::Map: {
       YAML::const_iterator it = node.begin();
       out.format = HEJ::to_FileFormat(it->first.as<std::string>());
       out.name = it->second.as<std::string>();
       return true;
     }
     case NodeType::Scalar:
       out.name = node.as<std::string>();
       out.format = HEJ::format_from_suffix(out.name);
       return true;
     default:
       return false;
     }
   }
 } // namespace YAML
 
 namespace HEJ{
 
   namespace detail{
     void set_from_yaml(OutputFile & setting, YAML::Node const & yaml){
       setting = yaml.as<OutputFile>();
     }
   }
 
   namespace{
     void update_fixed_order_jet_parameters(
         JetParameters & fixed_order_jets, YAML::Node const & yaml
     ){
       if(!yaml["fixed order jets"]) return;
       set_from_yaml_if_defined(
           fixed_order_jets.min_pt, yaml, "fixed order jets", "min pt"
       );
       fastjet::JetAlgorithm algo = fixed_order_jets.def.jet_algorithm();
       set_from_yaml_if_defined(algo, yaml, "fixed order jets", "algorithm");
       double R = fixed_order_jets.def.R();
       set_from_yaml_if_defined(R, yaml, "fixed order jets", "R");
       fixed_order_jets.def = fastjet::JetDefinition{algo, R};
     }
 
     // like std::stod, but throw if not the whole string can be converted
     double to_double(std::string const & str){
       std::size_t pos;
       const double result = std::stod(str, &pos);
       if(pos < str.size()){
         throw std::invalid_argument(str + " is not a valid double value");
       }
       return result;
     }
 
     using EventScale = double (*)(Event const &);
 
     void import_scale_functions(
         std::string const & file,
         std::vector<std::string> const & scale_names,
         std::unordered_map<std::string, EventScale> & known
     ) {
       auto handle = dlopen(file.c_str(), RTLD_NOW);
       char * error = dlerror();
       if(error != nullptr) throw std::runtime_error{error};
 
       for(auto const & scale: scale_names) {
         void * sym = dlsym(handle, scale.c_str());
         error = dlerror();
         if(error != nullptr) throw std::runtime_error{error};
         known.emplace(scale, reinterpret_cast<EventScale>(sym));
       }
     }
 
     auto get_scale_map(
         YAML::Node const & yaml
     ) {
       std::unordered_map<std::string, EventScale> scale_map;
       scale_map.emplace("H_T", H_T);
       scale_map.emplace("max jet pperp", max_jet_pt);
       scale_map.emplace("jet invariant mass", jet_invariant_mass);
       scale_map.emplace("m_j1j2", m_j1j2);
       if(yaml["import scales"]) {
         if(! yaml["import scales"].IsMap()) {
           throw invalid_type{"Entry 'import scales' is not a map"};
         }
         for(auto const & import: yaml["import scales"]) {
           const auto file = import.first.as<std::string>();
           const auto scale_names =
             import.second.IsSequence()
             ?import.second.as<std::vector<std::string>>()
             :std::vector<std::string>{import.second.as<std::string>()};
           import_scale_functions(file, scale_names, scale_map);
         }
       }
       return scale_map;
     }
 
     // simple (as in non-composite) scale functions
     /**
      * An example for a simple scale function would be H_T,
      * H_T/2 is then composite (take H_T and then divide by 2)
      */
     ScaleFunction parse_simple_ScaleFunction(
         std::string const & scale_fun,
         std::unordered_map<std::string, EventScale> const & known
     ) {
       assert(
           scale_fun.empty() ||
           (!std::isspace(scale_fun.front()) && !std::isspace(scale_fun.back()))
       );
       const auto it = known.find(scale_fun);
       if(it != end(known)) return {it->first, it->second};
       try{
         const double scale = to_double(scale_fun);
         return {scale_fun, FixedScale{scale}};
       } catch(std::invalid_argument const &){}
       throw std::invalid_argument{"Unknown scale choice: \"" + scale_fun + "\""};
     }
 
     std::string trim_front(std::string const & str){
       const auto new_begin = std::find_if(
           begin(str), end(str), [](char c){ return ! std::isspace(c); }
       );
       return std::string(new_begin, end(str));
     }
 
     std::string trim_back(std::string str){
       size_t pos = str.size() - 1;
       // use guaranteed wrap-around behaviour to check whether we have
       // traversed the whole string
       for(; pos < str.size() && std::isspace(str[pos]); --pos) {}
       str.resize(pos + 1); // note that pos + 1 can be 0
       return str;
     }
 
     ScaleFunction parse_ScaleFunction(
         std::string const & scale_fun,
         std::unordered_map<std::string, EventScale> const & known
     ){
       assert(
           scale_fun.empty() ||
           (!std::isspace(scale_fun.front()) && !std::isspace(scale_fun.back()))
       );
       // parse from right to left => a/b/c gives (a/b)/c
       const size_t delim = scale_fun.find_last_of("*/");
       if(delim == scale_fun.npos){
         return parse_simple_ScaleFunction(scale_fun, known);
       }
       const std::string first = trim_back(std::string{scale_fun, 0, delim});
       const std::string second = trim_front(std::string{scale_fun, delim+1});
       if(scale_fun[delim] == '/'){
         return parse_ScaleFunction(first, known)
           / parse_ScaleFunction(second, known);
       }
       else{
         assert(scale_fun[delim] == '*');
         return parse_ScaleFunction(first, known)
           * parse_ScaleFunction(second, known);
       }
     }
 
     EventTreatMap get_event_treatment(
       YAML::Node const & node, std::string const & entry
     ){
       using namespace event_type;
       EventTreatMap treat {
           {no_2_jets, EventTreatment::discard},
           {bad_final_state, EventTreatment::discard},
           {FKL, EventTreatment::discard},
           {unob, EventTreatment::discard},
           {unof, EventTreatment::discard},
           {qqxexb, EventTreatment::discard},
           {qqxexf, EventTreatment::discard},
           {qqxmid, EventTreatment::discard},
           {non_resummable, EventTreatment::discard}
         };
       set_from_yaml(treat.at(FKL), node, entry, "FKL");
       set_from_yaml(treat.at(unob), node, entry, "unordered");
       treat.at(unof) = treat.at(unob);
       set_from_yaml(treat.at(qqxexb), node, entry, "extremal qqx");
       treat.at(qqxexf) = treat.at(qqxexb);
       set_from_yaml(treat.at(qqxmid), node, entry, "central qqx");
       set_from_yaml(treat.at(non_resummable), node, entry, "non-resummable");
       if(treat[non_resummable] == EventTreatment::reweight){
         throw std::invalid_argument{"Cannot reweight non-resummable events"};
       }
       return treat;
     }
 
     Config to_Config(YAML::Node const & yaml){
       try{
         assert_all_options_known(yaml, get_supported_options());
       }
       catch(unknown_option const & ex){
         throw unknown_option{std::string{"Unknown option '"} + ex.what() + "'"};
       }
 
       Config config;
       config.resummation_jets = get_jet_parameters(yaml, "resummation jets");
       config.fixed_order_jets = config.resummation_jets;
       update_fixed_order_jet_parameters(config.fixed_order_jets, yaml);
       set_from_yaml_if_defined(config.min_extparton_pt, yaml, "min extparton pt");
       if(config.min_extparton_pt!=0)
         std::cerr << "WARNING: \"min extparton pt\" is deprecated."
           << " Please use \"max ext soft pt fraction\" instead.\n";
       set_from_yaml(
           config.max_ext_soft_pt_fraction, yaml, "max ext soft pt fraction"
       );
       // Sets the standard value, then changes this if defined
       config.regulator_lambda=CLAMBDA;
       set_from_yaml_if_defined(config.regulator_lambda, yaml, "regulator parameter");
       set_from_yaml_if_defined(config.max_events, yaml, "max events");
       set_from_yaml(config.trials, yaml, "trials");
       config.weight_type = WeightType::weighted;
       set_from_yaml_if_defined(config.weight_type, yaml, "unweight", "type");
       if(config.weight_type == WeightType::partially_unweighted) {
         config.unweight_config = PartialUnweightConfig{};
         set_from_yaml(
             config.unweight_config->trials, yaml,
             "unweight", "trials"
         );
         set_from_yaml(
             config.unweight_config->max_dev, yaml,
             "unweight", "max deviation"
         );
       }
       else if(yaml["unweight"]) {
         for(auto && opt: {"trials", "max deviation"}) {
           if(yaml["unweight"][opt]) {
             throw std::invalid_argument{
               "'unweight: " + std::string{opt} + "' "
               "is only supported if 'unweight: type' is set to 'partial'"
             };
           }
         }
       }
       set_from_yaml(config.log_correction, yaml, "log correction");
       config.treat = get_event_treatment(yaml, "event treatment");
       set_from_yaml_if_defined(config.output, yaml, "event output");
       config.rng = to_RNGConfig(yaml, "random generator");
       set_from_yaml_if_defined(config.analyses_parameters, yaml, "analyses");
       if(yaml["analysis"]){
         std::cerr <<
           "WARNING: Configuration entry 'analysis' is deprecated. "
           " Use 'analyses' instead.\n";
         set_from_yaml(config.analysis_parameters, yaml, "analysis");
         if(!config.analysis_parameters.IsNull()){
           config.analyses_parameters.push_back(config.analysis_parameters);
         }
       }
       config.scales = to_ScaleConfig(yaml);
       config.ew_parameters = get_ew_parameters(yaml);
       config.Higgs_coupling = get_Higgs_coupling(yaml, "Higgs coupling");
       return config;
     }
 
   } // namespace anonymous
 
   ScaleConfig to_ScaleConfig(YAML::Node const & yaml){
     ScaleConfig config;
     auto scale_funs = get_scale_map(yaml);
     std::vector<std::string> scales;
     set_from_yaml(scales, yaml, "scales");
     config.base.reserve(scales.size());
     std::transform(
         begin(scales), end(scales), std::back_inserter(config.base),
         [scale_funs](auto const & entry){
           return parse_ScaleFunction(entry, scale_funs);
         }
     );
     set_from_yaml_if_defined(config.factors, yaml, "scale factors");
     config.max_ratio = std::numeric_limits<double>::infinity();
     set_from_yaml_if_defined(config.max_ratio, yaml, "max scale ratio");
     return config;
   }
 
   Config load_config(std::string const & config_file){
     try{
       return to_Config(YAML::LoadFile(config_file));
     }
     catch(...){
       std::cerr << "Error reading " << config_file << ":\n  ";
       throw;
     }
   }
 
 } // namespace HEJ
diff --git a/src/bin/HEJ.cc b/src/bin/HEJ.cc
index 165c5eb..1ff402b 100644
--- a/src/bin/HEJ.cc
+++ b/src/bin/HEJ.cc
@@ -1,382 +1,382 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <array>
 #include <chrono>
 #include <iostream>
 #include <limits>
 #include <memory>
 #include <numeric>
 
 #include "yaml-cpp/yaml.h"
 
 #include "fastjet/ClusterSequence.hh"
 
 #include "HEJ/CombinedEventWriter.hh"
 #include "HEJ/Config.hh"
 #include "HEJ/CrossSectionAccumulator.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/EventReader.hh"
 #include "HEJ/BufferedEventReader.hh"
 #include "HEJ/EventReweighter.hh"
 #include "HEJ/get_analysis.hh"
 #include "HEJ/make_RNG.hh"
 #include "HEJ/optional.hh"
 #include "HEJ/ProgressBar.hh"
 #include "HEJ/stream.hh"
 #include "HEJ/Unweighter.hh"
 #include "HEJ/Version.hh"
 #include "HEJ/YAMLreader.hh"
 
 HEJ::Config load_config(char const * filename){
   try{
     return HEJ::load_config(filename);
   }
   catch(std::exception const & exc){
     std::cerr << "Error: " << exc.what() << '\n';
     std::exit(EXIT_FAILURE);
   }
 }
 
 std::vector<std::unique_ptr<HEJ::Analysis>> get_analyses(
     std::vector<YAML::Node> const & parameters, LHEF::HEPRUP const & heprup
 ){
   try{
     return HEJ::get_analyses(parameters, heprup);
   }
   catch(std::exception const & exc){
     std::cerr << "Failed to load analysis: " << exc.what() << '\n';
     std::exit(EXIT_FAILURE);
   }
 }
 
 // unique_ptr is a workaround:
 // HEJ::optional is a better fit, but gives spurious errors with g++ 7.3.0
 std::unique_ptr<HEJ::ProgressBar<double>> make_progress_bar(
     std::vector<double> const & xs
 ) {
   if(xs.empty()) return {};
   const double Born_xs = std::accumulate(begin(xs), end(xs), 0.);
   return std::make_unique<HEJ::ProgressBar<double>>(std::cout, Born_xs);
 }
 
 std::string time_to_string(const time_t time){
   char s[30];
   struct tm * p = localtime(&time);
   strftime(s, 30, "%a %b %d %Y %H:%M:%S", p);
   return s;
 }
 
 HEJ::Event to_event(
     LHEF::HEPEUP const & hepeup,
     HEJ::JetParameters const & fixed_order_jets
 ) {
     HEJ::Event::EventData event_data{hepeup};
     event_data.reconstruct_intermediate();
 
     return HEJ::Event{
       std::move(event_data).cluster(
           fixed_order_jets.def, fixed_order_jets.min_pt
       )
     };
 }
 
 void unweight(
   HEJ::Unweighter & unweighter,
   HEJ::WeightType weight_type,
   std::vector<HEJ::Event> & events,
   HEJ::RNG & ran
 ) {
     if(weight_type == HEJ::WeightType::unweighted_resum){
       unweighter.set_cut_to_maxwt(events);
     }
     events.erase(
       unweighter.unweight(begin(events), end(events), ran),
       end(events)
     );
 }
 
 // peek up to nevents events from reader
 std::vector<LHEF::HEPEUP> peek_events(
     HEJ::BufferedEventReader & reader,
     const int nevents
 ) {
   std::vector<LHEF::HEPEUP> events;
   while(
       static_cast<int>(events.size()) < nevents
       && reader.read_event()
   ) {
     events.emplace_back(reader.hepeup());
   }
   // put everything back into the reader
   for(auto it = rbegin(events); it != rend(events); ++it) {
     reader.emplace(*it);
   }
   return events;
 }
 
 void append_resummed_events(
     std::vector<HEJ::Event> & resummation_events,
     HEJ::EventReweighter & reweighter,
     LHEF::HEPEUP const & hepeup,
     const size_t trials,
     HEJ::JetParameters const & fixed_order_jets
 ) {
   const HEJ::Event FO_event = to_event(hepeup, fixed_order_jets);
   if(reweighter.treatment(FO_event.type()) != HEJ::EventTreatment::reweight) {
     return;
   }
   const auto resummed = reweighter.reweight(FO_event, trials);
   resummation_events.insert(
       end(resummation_events),
       begin(resummed), end(resummed)
   );
 }
 
 
 void train(
     HEJ::Unweighter & unweighter,
     HEJ::BufferedEventReader & reader,
     HEJ::EventReweighter & reweighter,
     const size_t total_trials,
     const double max_dev,
     double reweight_factor,
     HEJ::JetParameters const & fixed_order_jets
 ) {
   std::cout << "Reading up to " << total_trials << " training events...\n";
   auto FO_events = peek_events(reader, total_trials);
   if(FO_events.empty()) {
     throw std::runtime_error{
       "No events generated to calibrate the unweighting weight!"
       "Please increase the number \"trials\" or deactivate the unweighting."
     };
   }
   const size_t trials = total_trials/FO_events.size();
   // adjust reweight factor so that the overall normalisation
   // is the same as in the full run
   reweight_factor *= trials;
   for(auto & hepeup: FO_events) {
     hepeup.XWGTUP *= reweight_factor;
   }
   std::cout << "Training unweighter with "
             << trials << '*' << FO_events.size() << " events\n";
 
   auto progress = HEJ::ProgressBar<int>{
      std::cout, static_cast<int>(FO_events.size())
   };
   std::vector<HEJ::Event> resummation_events;
   for(auto const & hepeup: FO_events) {
     append_resummed_events(
         resummation_events,
         reweighter, hepeup, trials, fixed_order_jets
     );
     ++progress;
   }
 
   unweighter.set_cut_to_peakwt(resummation_events, max_dev);
   std::cout << "\nUnweighting events with weight up to "
             << unweighter.get_cut() << '\n';
 }
 
 int main(int argn, char** argv) {
   using clock = std::chrono::system_clock;
 
   if (argn != 3) {
     std::cerr << "\n# Usage:\n."<< argv[0] <<" config_file input_file\n\n";
     return EXIT_FAILURE;
   }
 
   const auto start_time = clock::now();
   {
     std::cout << "Starting " << HEJ::Version::package_name_full()
       << ", revision " << HEJ::Version::revision() << " ("
       << time_to_string(clock::to_time_t(start_time)) << ")" << std::endl;
   }
   fastjet::ClusterSequence::print_banner();
 
   // read configuration
   const HEJ::Config config = load_config(argv[1]);
   auto reader = HEJ::make_reader(argv[2]);
   assert(reader);
 
   auto heprup{ reader->heprup() };
   heprup.generators.emplace_back(LHEF::XMLTag{});
   heprup.generators.back().name = HEJ::Version::package_name();
   heprup.generators.back().version = HEJ::Version::String();
 
   auto analyses = get_analyses( config.analyses_parameters, heprup );
   assert(analyses.empty() || analyses.front() != nullptr);
 
   HEJ::CombinedEventWriter writer{config.output, std::move(heprup)};
 
   double global_reweight = 1.;
   const auto & max_events = config.max_events;
   // if we need the event number:
   if(std::abs(heprup.IDWTUP) == 4 || std::abs(heprup.IDWTUP) == 1 || max_events){
     // try to read from LHE head
     auto input_events{reader->number_events()};
     if(!input_events) {
       // else count manually
       auto t_reader = HEJ::make_reader(argv[2]);
       input_events = 0;
       while(t_reader->read_event()) ++(*input_events);
     }
     if(std::abs(heprup.IDWTUP) == 4 || std::abs(heprup.IDWTUP) == 1){
       // IDWTUP 4 or 1 assume average(weight)=xs, but we need sum(weights)=xs
       std::cout << "Found IDWTUP " << heprup.IDWTUP << ": "
         << "assuming \"cross section = average weight\".\n"
         << "converting to \"cross section = sum of weights\" ";
       global_reweight /= *input_events;
     }
     if(max_events && (*input_events > *max_events)){
       // maximal number of events given
       global_reweight *= *input_events/static_cast<double>(*max_events);
       std::cout << "Processing " << *max_events
                 << " out of " << *input_events << " events\n";
     }
   }
 
   HEJ::ScaleGenerator scale_gen{
     config.scales.base,
     config.scales.factors,
     config.scales.max_ratio
   };
   std::shared_ptr<HEJ::RNG> ran{
     HEJ::make_RNG(config.rng.name, config.rng.seed)};
   assert(ran != nullptr);
   HEJ::EventReweighter hej{
     reader->heprup(),
     std::move(scale_gen),
     to_EventReweighterConfig(config),
     ran
   };
 
   HEJ::optional<HEJ::Unweighter> unweighter{};
   if(config.weight_type != HEJ::WeightType::weighted) {
     unweighter = HEJ::Unweighter{};
   }
   if(config.weight_type == HEJ::WeightType::partially_unweighted) {
     HEJ::BufferedEventReader buffered_reader{std::move(reader)};
     assert(config.unweight_config);
     train(
         *unweighter,
         buffered_reader,
         hej,
         config.unweight_config->trials,
         config.unweight_config->max_dev,
         global_reweight/config.trials,
         config.fixed_order_jets
     );
     reader = std::make_unique<HEJ::BufferedEventReader>(
         std::move(buffered_reader)
     );
   }
 
   // status infos & eye candy
   size_t nevent = 0;
   std::array<int, HEJ::event_type::last_type + 1>
     nevent_type{0}, nfailed_type{0};
   auto progress = make_progress_bar(reader->heprup().XSECUP);
   HEJ::CrossSectionAccumulator xs;
   std::map<HEJ::StatusCode, int> status_counter;
   size_t total_trials = 0;
   size_t total_resum = 0;
 
   // Loop over the events in the input file
   while(reader->read_event() && (!max_events || nevent < *max_events) ){
     ++nevent;
 
     // reweight events so that the total cross section is conserved
     auto hepeup = reader->hepeup();
     hepeup.XWGTUP *= global_reweight;
 
     const auto FO_event = to_event(hepeup, config.fixed_order_jets);
     if(FO_event.central().weight == 0) {
       static const bool warned_once = [argv,nevent](){
         std::cerr
           << "WARNING: event number " << nevent
           << " in " << argv[2] << " has zero weight. "
           "Ignoring this and all further events with vanishing weight.\n";
         return true;
       }();
       (void) warned_once; // shut up compiler warnings
       continue;
     }
 
     auto resummed_events{ hej.reweight(FO_event, config.trials) };
 
     // some bookkeeping
     for(auto const & s: hej.status())
       ++status_counter[s];
     total_trials+=hej.status().size();
     ++nevent_type[FO_event.type()];
 
     if(resummed_events.empty()) ++nfailed_type[FO_event.type()];
 
     if(unweighter) {
       unweight(*unweighter, config.weight_type, resummed_events, *ran);
     }
 
     // analysis
     for(auto & ev: resummed_events){
       //TODO: move pass_cuts to after phase space point generation
       bool passed = analyses.empty();
       for(auto const & analysis: analyses){
         if(analysis->pass_cuts(ev, FO_event)){
           passed = true;
           analysis->fill(ev, FO_event);
         };
       }
       if(passed){
         writer.write(ev);
       } else {
         ev.parameters()*=0; // do not use discarded events afterwards
       }
     }
     xs.fill_correlated(resummed_events);
     total_resum += resummed_events.size();
     if(progress) progress->increment(FO_event.central().weight);
   } // main event loop
   std::cout << '\n';
   for(auto const & analysis: analyses){
     analysis->finalise();
   }
 
   using namespace HEJ::event_type;
   std::cout<< "Events processed: " << nevent << " (" << total_resum << " resummed)"<< '\n';
   std::cout << '\t' << name(EventType::first_type) << ": "
             << nevent_type[EventType::first_type]
             << ", failed to reconstruct " << nfailed_type[EventType::first_type]
             << '\n';
   for(auto i=EventType::first_type+1; i<=EventType::last_type; i*=2){
     std::cout << '\t' << name(static_cast<EventType>(i)) << ": "
               << nevent_type[i]
               << ", failed to reconstruct " << nfailed_type[i]
               << '\n';
   }
 
   std::cout << '\n' << xs << '\n';
 
   std::cout << "Generation statistic: "
     << status_counter[HEJ::StatusCode::good] << "/" << total_trials
     << " trials successful.\n";
   for(auto && entry: status_counter){
     const double fraction = static_cast<double>(entry.second)/total_trials;
     const int percent = std::round(100*fraction);
     std::cout << std::left << std::setw(17) << (to_string(entry.first) + ":")
               << " [";
     for(int i = 0; i < percent/2; ++i) std::cout << '#';
     for(int i = percent/2; i < 50; ++i) std::cout << ' ';
     std::cout << "] " <<std::setw(2)<<std::right<< percent << "%\n";
   }
 
   std::chrono::duration<double> run_time = (clock::now() - start_time);
   std::cout << "\nFinished " << HEJ::Version::package_name() << " at "
     << time_to_string(clock::to_time_t(clock::now()))
     << "\n=> Runtime: " << run_time.count() << " sec ("
     << nevent/run_time.count() << " Events/sec).\n";
 
   return EXIT_SUCCESS;
 }
diff --git a/src/get_analysis.cc b/src/get_analysis.cc
index 56f56c5..1262a28 100644
--- a/src/get_analysis.cc
+++ b/src/get_analysis.cc
@@ -1,51 +1,51 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/get_analysis.hh"
 
 #include <dlfcn.h>
 #include <string>
 
 #include "yaml-cpp/yaml.h"
 
 #include "HEJ/EmptyAnalysis.hh"
 #include "HEJ/RivetAnalysis.hh"
 
 namespace HEJ{
 
   std::unique_ptr<Analysis> get_analysis(
       YAML::Node const & parameters, LHEF::HEPRUP const & heprup
   ){
     if(!parameters["plugin"]){
       if(parameters["rivet"])
         return RivetAnalysis::create(parameters, heprup);
       return EmptyAnalysis::create(parameters, heprup);
     }
 
     using AnalysisMaker = std::unique_ptr<Analysis> (*)(
         YAML::Node const &, LHEF::HEPRUP const &);
     const auto plugin_name = parameters["plugin"].as<std::string>();
     auto handle = dlopen(plugin_name.c_str(), RTLD_NOW);
     char * error = dlerror();
     if(error != nullptr) throw std::runtime_error(error);
 
     void * sym = dlsym(handle, "make_analysis");
     error = dlerror();
     if(error != nullptr) throw std::runtime_error(error);
     auto make_analysis = reinterpret_cast<AnalysisMaker>(sym);
 
     return make_analysis(parameters, heprup);
   }
 
   std::vector<std::unique_ptr<Analysis>> get_analyses(
       std::vector<YAML::Node> const & parameters, LHEF::HEPRUP const & heprup
   ){
     std::vector<std::unique_ptr<Analysis>> anas;
     for(auto const & param: parameters){
       anas.emplace_back(get_analysis(param, heprup));
     }
     return anas;
   }
 }
diff --git a/src/jets.cc b/src/jets.cc
index 297e67f..fd71516 100644
--- a/src/jets.cc
+++ b/src/jets.cc
@@ -1,792 +1,792 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/jets.hh"
 
 #include <algorithm>
 
 #include "HEJ/Constants.hh"
 
 namespace {
   double metric(const size_t mu, const size_t nu) {
     if(mu != nu) return 0.;
     return (mu == 0)?1.:-1.;
   }
 }
 
 // Colour acceleration multiplier for gluons see eq. (7) in arXiv:0910.5113
 // @TODO: this is not a current and should be moved somewhere else
 double K_g(double p1minus, double paminus) {
     return 1./2.*(p1minus/paminus + paminus/p1minus)*(HEJ::C_A - 1./HEJ::C_A) + 1./HEJ::C_A;
 }
 double K_g(
   HLV const & pout,
   HLV const & pin
   ) {
   if(pin.z() > 0) return K_g(pout.plus(), pin.plus());
   return K_g(pout.minus(), pin.minus());
 }
 
 CCurrent CCurrent::operator+(const CCurrent& other)
 {
     COM result_c0=c0 + other.c0;
     COM result_c1=c1 + other.c1;
     COM result_c2=c2 + other.c2;
     COM result_c3=c3 + other.c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator-(const CCurrent& other)
 {
     COM result_c0=c0 - other.c0;
     COM result_c1=c1 - other.c1;
     COM result_c2=c2 - other.c2;
     COM result_c3=c3 - other.c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator*(const double x)
 {
     COM result_c0=x*CCurrent::c0;
     COM result_c1=x*CCurrent::c1;
     COM result_c2=x*CCurrent::c2;
     COM result_c3=x*CCurrent::c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator/(const double x)
 {
     COM result_c0=CCurrent::c0/x;
     COM result_c1=CCurrent::c1/x;
     COM result_c2=CCurrent::c2/x;
     COM result_c3=CCurrent::c3/x;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator*(const COM x)
 {
     COM result_c0=x*CCurrent::c0;
     COM result_c1=x*CCurrent::c1;
     COM result_c2=x*CCurrent::c2;
     COM result_c3=x*CCurrent::c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator/(const COM x)
 {
     COM result_c0=(CCurrent::c0)/x;
     COM result_c1=(CCurrent::c1)/x;
     COM result_c2=(CCurrent::c2)/x;
     COM result_c3=(CCurrent::c3)/x;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 std::ostream& operator <<(std::ostream& os, const CCurrent& cur)
 {
     os << "("<<cur.c0<< " ; "<<cur.c1<<" , "<<cur.c2<<" , "<<cur.c3<<")";
     return os;
 }
 
 CCurrent operator * ( double x, CCurrent& m)
 {
     return m*x;
 }
 
 CCurrent operator * ( COM x, CCurrent& m)
 {
     return m*x;
 }
 
 CCurrent operator / ( double x, CCurrent& m)
 {
     return m/x;
 }
 
 CCurrent operator / ( COM x, CCurrent& m)
 {
     return m/x;
 }
 
 COM CCurrent::dot(HLV p1)
 {
     //  Current goes (E,px,py,pz)
     //  Vector goes (px,py,pz,E)
     return p1[3]*c0-p1[0]*c1-p1[1]*c2-p1[2]*c3;
 }
 
 COM CCurrent::dot(CCurrent p1)
 {
     return p1.c0*c0-p1.c1*c1-p1.c2*c2-p1.c3*c3;
 }
 
 //Current Functions
 void joi(HLV pout, bool helout, HLV pin, bool helin, current &cur) {
   cur[0]=0.;
   cur[1]=0.;
   cur[2]=0.;
   cur[3]=0.;
 
   const double sqpop = sqrt(std::abs(pout.plus()));
   const double sqpom = sqrt(std::abs(pout.minus()));
 
   // Allow for "jii" format
   const COM poperp = (pout.x()==0 && pout.y() ==0) ? -1 : pout.x()+COM(0,1)*pout.y();
 
   if (helout != helin) {
     throw std::invalid_argument{"Non-matching helicities"};
   } else if (helout == false) { // negative helicity
     if (pin.plus() > pin.minus()) { // if forward
       const double sqpip = sqrt(std::abs(pin.plus()));
       cur[0] = sqpop * sqpip;
       cur[1] = sqpom * sqpip * poperp / std::abs(poperp);
       cur[2] = -COM(0,1) * cur[1];
       cur[3] = cur[0];
     } else { // if backward
       const double sqpim = sqrt(std::abs(pin.minus()));
       cur[0] = -sqpom * sqpim * poperp / std::abs(poperp);
       cur[1] = -sqpim * sqpop;
       cur[2] = COM(0,1) * cur[1];
       cur[3] = -cur[0];
     }
   } else { // positive helicity
     if (pin.plus() > pin.minus()) { // if forward
       const double sqpip = sqrt(std::abs(pin.plus()));
       cur[0] = sqpop * sqpip;
       cur[1] = sqpom * sqpip * conj(poperp) / std::abs(poperp);
       cur[2] = COM(0,1) * cur[1];
       cur[3] = cur[0];
     } else { // if backward
       double sqpim = sqrt(std::abs(pin.minus()));
       cur[0] = -sqpom * sqpim * conj(poperp) / std::abs(poperp);
       cur[1] = -sqpim * sqpop;
       cur[2] = -COM(0,1) * cur[1];
       cur[3] = -cur[0];
     }
   }
 }
 
 CCurrent joi (HLV pout, bool helout, HLV pin, bool helin)
 {
   current cur;
   joi(pout, helout, pin, helin, cur);
   return CCurrent(cur[0],cur[1],cur[2],cur[3]);
 }
 
 void jio(HLV pin, bool helin, HLV pout, bool helout, current &cur) {
   joi(pout, !helout, pin, !helin, cur);
 }
 
 CCurrent jio (HLV pin, bool helin, HLV pout, bool helout)
 {
   current cur;
   jio(pin, helin, pout, helout, cur);
   return CCurrent(cur[0],cur[1],cur[2],cur[3]);
 }
 
 void joo(HLV pi, bool heli, HLV pj, bool helj, current &cur) {
 
   // Zero our current
   cur[0] = 0.0;
   cur[1] = 0.0;
   cur[2] = 0.0;
   cur[3] = 0.0;
   if (heli!=helj) {
     throw std::invalid_argument{"Non-matching helicities"};
   } else if ( heli == true ) { // If positive helicity swap momenta
     std::swap(pi,pj);
   }
 
   const double sqpjp = sqrt(std::abs(pj.plus() ));
   const double sqpjm = sqrt(std::abs(pj.minus()));
   const double sqpip = sqrt(std::abs(pi.plus() ));
   const double sqpim = sqrt(std::abs(pi.minus()));
   // Allow for "jii" format
   const COM piperp = (pi.x()==0 && pi.y() ==0) ? -1 : pi.x()+COM(0,1)*pi.y();
   const COM pjperp = (pj.x()==0 && pj.y() ==0) ? -1 : pj.x()+COM(0,1)*pj.y();
 
   const COM phasei = piperp / std::abs(piperp);
   const COM phasej = pjperp / std::abs(pjperp);
 
   cur[0] = sqpim * sqpjm * phasei * conj(phasej) + sqpip * sqpjp;
   cur[1] = sqpim * sqpjp * phasei + sqpip * sqpjm * conj(phasej);
   cur[2] = -COM(0, 1) * (sqpim * sqpjp * phasei - sqpip * sqpjm * conj(phasej));
   cur[3] = -sqpim * sqpjm * phasei * conj(phasej) + sqpip * sqpjp;
 }
 
 CCurrent joo (HLV pi, bool heli, HLV pj, bool helj)
 {
   current cur;
   joo(pi, heli, pj, helj, cur);
   return CCurrent(cur[0],cur[1],cur[2],cur[3]);
 }
 namespace{
 //@{
   /**
    * @brief Pure Jet FKL Contributions, function to handle all incoming types.
    * @param p1out             Outgoing Particle 1.
    * @param p1in              Incoming Particle 1.
    * @param p2out             Outgoing Particle 2
    * @param p2in              Incoming Particle 2
    *
    * Calculates j_\mu    j^\mu.
    * Handles all possible incoming states. Helicity doesn't matter since we sum
    * over all of them.
    */
   double j_j(HLV const & p1out, HLV const & p1in,
              HLV const & p2out, HLV const & p2in
   ){
     HLV const q1=p1in-p1out;
     HLV const q2=-(p2in-p2out);
     current mj1m,mj1p,mj2m,mj2p;
 
     // Note need to flip helicities in anti-quark case.
     joi(p1out, false, p1in, false, mj1p);
     joi(p1out, true,  p1in, true,  mj1m);
     joi(p2out, false, p2in, false, mj2p);
     joi(p2out, true,  p2in, true,  mj2m);
 
     COM const Mmp=cdot(mj1m,mj2p);
     COM const Mmm=cdot(mj1m,mj2m);
     COM const Mpp=cdot(mj1p,mj2p);
     COM const Mpm=cdot(mj1p,mj2m);
 
     double const sst=abs2(Mmm)+abs2(Mmp)+abs2(Mpp)+abs2(Mpm);
 
     // Multiply by Cf^2
     return HEJ::C_F*HEJ::C_F*(sst)/(q1.m2()*q2.m2());
   }
 } //anonymous namespace
 double ME_qQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in);
 }
 
 double ME_qQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in);
 }
 
 double ME_qbarQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in);
 }
 
 double ME_qg(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in)*K_g(p2out, p2in)/HEJ::C_F;
 }
 
 double ME_qbarg(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in)*K_g(p2out, p2in)/(HEJ::C_F);
 }
 
 double ME_gg(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in)*K_g(p1out, p1in)*K_g(p2out, p2in)/(HEJ::C_F*HEJ::C_F);
 }
 //@}
 
 namespace{
   double juno_j(HLV const & pg, HLV const & p1out,
                 HLV const & p1in, HLV const & p2out, HLV const & p2in
   ){
     //  This construction is taking rapidity order: pg > p1out >> p2out
     HLV q1=p1in-p1out;  // Top End
     HLV q2=-(p2in-p2out);   // Bottom End
     HLV qg=p1in-p1out-pg;  // Extra bit post-gluon
 
     // Note <p1|eps|pa> current split into two by gauge choice.
     // See James C's Thesis (p72). <p1|eps|pa> -> <p1|pg><pg|pa>
     CCurrent mj1p=joi(p1out, false, p1in, false);
     CCurrent mj1m=joi(p1out,  true, p1in,  true);
     CCurrent jgap=joi(pg,    false, p1in, false);
     CCurrent jgam=joi(pg,     true, p1in,  true);
 
     // Note for function joo(): <p1+|pg+> = <pg-|p1->.
     CCurrent j2gp=joo(p1out, false, pg, false);
     CCurrent j2gm=joo(p1out,  true, pg,  true);
 
     CCurrent mj2p=joi(p2out, false, p2in, false);
     CCurrent mj2m=joi(p2out,  true, p2in,  true);
 
     // Dot products of these which occur again and again
     COM Mmp=mj1m.dot(mj2p);
     COM Mmm=mj1m.dot(mj2m);
     COM Mpp=mj1p.dot(mj2p);
     COM Mpm=mj1p.dot(mj2m);
 
     CCurrent p1o(p1out),p2o(p2out),p2i(p2in),qsum(q1+qg),p1i(p1in);
 
     CCurrent Lmm=(qsum*(Mmm)+(-2.*mj2m.dot(pg))*mj1m+2.*mj1m.dot(pg)*mj2m
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mmm/2.))/q1.m2();
     CCurrent Lmp=(qsum*(Mmp) + (-2.*mj2p.dot(pg))*mj1m+2.*mj1m.dot(pg)*mj2p
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mmp/2.))/q1.m2();
     CCurrent Lpm=(qsum*(Mpm) + (-2.*mj2m.dot(pg))*mj1p+2.*mj1p.dot(pg)*mj2m
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mpm/2.))/q1.m2();
     CCurrent Lpp=(qsum*(Mpp) + (-2.*mj2p.dot(pg))*mj1p+2.*mj1p.dot(pg)*mj2p
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mpp/2.))/q1.m2();
 
     CCurrent U1mm=(jgam.dot(mj2m)*j2gm+2.*p1o*Mmm)/(p1out+pg).m2();
     CCurrent U1mp=(jgam.dot(mj2p)*j2gm+2.*p1o*Mmp)/(p1out+pg).m2();
     CCurrent U1pm=(jgap.dot(mj2m)*j2gp+2.*p1o*Mpm)/(p1out+pg).m2();
     CCurrent U1pp=(jgap.dot(mj2p)*j2gp+2.*p1o*Mpp)/(p1out+pg).m2();
     CCurrent U2mm=((-1.)*j2gm.dot(mj2m)*jgam+2.*p1i*Mmm)/(p1in-pg).m2();
     CCurrent U2mp=((-1.)*j2gm.dot(mj2p)*jgam+2.*p1i*Mmp)/(p1in-pg).m2();
     CCurrent U2pm=((-1.)*j2gp.dot(mj2m)*jgap+2.*p1i*Mpm)/(p1in-pg).m2();
     CCurrent U2pp=((-1.)*j2gp.dot(mj2p)*jgap+2.*p1i*Mpp)/(p1in-pg).m2();
 
     constexpr double cf=HEJ::C_F;
 
     double amm=cf*(2.*vre(Lmm-U1mm,Lmm+U2mm))+2.*cf*cf/3.*vabs2(U1mm+U2mm);
     double amp=cf*(2.*vre(Lmp-U1mp,Lmp+U2mp))+2.*cf*cf/3.*vabs2(U1mp+U2mp);
     double apm=cf*(2.*vre(Lpm-U1pm,Lpm+U2pm))+2.*cf*cf/3.*vabs2(U1pm+U2pm);
     double app=cf*(2.*vre(Lpp-U1pp,Lpp+U2pp))+2.*cf*cf/3.*vabs2(U1pp+U2pp);
     double ampsq=-(amm+amp+apm+app);
 
     //Divide by t-channels
     ampsq/=q2.m2()*qg.m2();
     ampsq/=16.;
 
     // Factor of (Cf/Ca) for each quark to match j_j.
     ampsq*=(HEJ::C_F*HEJ::C_F)/(HEJ::C_A*HEJ::C_A);
 
     return ampsq;
 
   }
 }
 
 //Unordered bits for pure jet
 double ME_unob_qQ (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qbarQ (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qQbar (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qbarQbar (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qg (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 double ME_unob_qbarg (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 namespace {
   void eps(HLV refmom, HLV kb, bool hel, current &ep){
     //Positive helicity eps has negative helicity choices for spinors and vice versa
     joi(refmom,hel,kb,hel,ep);
     double norm;
     if(kb.z()<0.) norm = sqrt(2.*refmom.plus()*kb.minus());
     else          norm = sqrt(2.*refmom.minus()*kb.plus());
     // Normalise
     std::for_each(begin(ep), end(ep), [&,norm](auto & val){val/=norm;});
   }
 
   COM qggm1(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3, bool helchain,
             bool heltop, bool helb,HLV refmom){
     // Since everything is defined with currents, need to use compeleness relation
     // to expand p slash. i.e. pslash = |p><p|. Only one helicity 'survives' as
     // defined by the helicities of the spinors at the end of the chain.
     current cur33, cur23, curb3, cur2b, cur1a, ep;
     joo(p3, helchain, p3, helchain,cur33);
     joo(p2,helchain,p3,helchain,cur23);
     jio(pb,helchain,p3,helchain,curb3);
     joi(p2,helchain,pb,helchain,cur2b);
     joi(p1, heltop, pa, heltop,cur1a);
 
     const double t2 = (p3-pb)*(p3-pb);
     //Calculate Term 1 in Equation 3.23 in James Cockburn's Thesis.
     COM v1[4][4];
     for(int u=0; u<4;++u){
       for(int v=0; v<4;++v){
         v1[u][v]=(cur23[u]*cur33[v]-cur2b[u]*curb3[v])/t2*(-1.);
       }
     }
     //Dot in current and eps
     //eps
     eps(refmom,pb,helb, ep);
     COM M1=0.;
     // Perform Contraction: g^{ik} j_{1a}_k * v1_i^j eps^l g_lj
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         M1+= metric(i,i) *cur1a[i]*(v1[i][j])*ep[j]*metric(j,j);
       }
     }
     return M1;
   }
 
   COM qggm2(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3, bool helchain, bool heltop,
             bool helb,HLV refmom){
     // Since everything is defined with currents, need to use completeness relation
     // to expand p slash. i.e. pslash = |p><p|. Only one helicity 'survives' as
     // defined by the helicities of the spinors at the end of the chain.
     current cur22, cur23, curb3, cur2b, cur1a, ep;
     joo(p2, helchain, p2, helchain, cur22);
     joo(p2, helchain, p3, helchain, cur23);
     jio(pb, helchain, p3, helchain, curb3);
     joi(p2, helchain, pb, helchain, cur2b);
     joi(p1, heltop,   pa, heltop,   cur1a);
 
     const double t2t = (p2-pb)*(p2-pb);
     //Calculate Term 2 in Equation 3.23 in James Cockburn's Thesis.
     COM v2[4][4]={};
     for(int u=0; u<4;++u){
       for(int v=0; v<4; ++v){
         v2[u][v]=(cur22[v]*cur23[u]-cur2b[v]*curb3[u])/t2t;
       }
     }
     //Dot in current and eps
     //eps
     eps(refmom,pb,helb, ep);
     COM M2=0.;
     // Perform Contraction: g^{ik} j_{1a}_k * v2_i^j eps^l g_lj
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         M2+= metric(i,i)*cur1a[i]*(v2[i][j])*ep[j]*metric(j,j);
       }
     }
     return M2;
   }
 
   COM qggm3(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3, bool helchain, bool heltop,
             bool helb,HLV refmom){
     current qqcur,ep,cur1a;
     const double s23 = (p2+p3)*(p2+p3);
     joo(p2,helchain,p3,helchain,qqcur);
     joi(p1, heltop, pa, heltop,cur1a);
     //Redefine relevant momenta as currents - for ease of calling correct part of vector
     const current kb{pb.e(), pb.x(), pb.y(), pb.z()};
     const current k2{p2.e(), p2.x(), p2.y(), p2.z()};
     const current k3{p3.e(), p3.x(), p3.y(), p3.z()};
     //Calculate Term 3 in Equation 3.23 in James Cockburn's Thesis.
     COM V3g[4][4]={};
     const COM kbqq=kb[0]*qqcur[0] -kb[1]*qqcur[1] -kb[2]*qqcur[2] -kb[3]*qqcur[3];
     for(int u=0;u<4;++u){
       for(int v=0;v<4;++v){
         V3g[u][v] += 2.*COM(0.,1.)*(((k2[v]+k3[v])*qqcur[u] - (kb[u])*qqcur[v])+
                                     kbqq*metric(u,v))/s23;
       }
     }
     eps(refmom,pb,helb, ep);
     COM M3=0.;
     // Perform Contraction: g^{ik} j_{1a}_k * (v2_i^j) eps^l g_lj
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         M3+= metric(i,i)*cur1a[i]*(V3g[i][j])*ep[j]*metric(j,j);
       }
     }
     return M3;
   }
 
   //Now the function to give helicity/colour sum/average
   double MqgtqQQ(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3){
     // 4 indepedent helicity choices (complex conjugation related).
     //Need to evalute each independent hel configuration and store that result somewhere
     const COM Mmmm1 = qggm1(pa,pb,p1,p2,p3,false,false,false, pa);
     const COM Mmmm2 = qggm2(pa,pb,p1,p2,p3,false,false,false, pa);
     const COM Mmmm3 = qggm3(pa,pb,p1,p2,p3,false,false,false, pa);
     const COM Mmmp1 = qggm1(pa,pb,p1,p2,p3,false,true, false, pa);
     const COM Mmmp2 = qggm2(pa,pb,p1,p2,p3,false,true, false, pa);
     const COM Mmmp3 = qggm3(pa,pb,p1,p2,p3,false,true, false, pa);
     const COM Mpmm1 = qggm1(pa,pb,p1,p2,p3,false,false,true,  pa);
     const COM Mpmm2 = qggm2(pa,pb,p1,p2,p3,false,false,true,  pa);
     const COM Mpmm3 = qggm3(pa,pb,p1,p2,p3,false,false,true,  pa);
     const COM Mpmp1 = qggm1(pa,pb,p1,p2,p3,false,true, true,  pa);
     const COM Mpmp2 = qggm2(pa,pb,p1,p2,p3,false,true, true,  pa);
     const COM Mpmp3 = qggm3(pa,pb,p1,p2,p3,false,true, true,  pa);
 
     //Colour factors:
     const COM cm1m1 = 8./3.;
     const COM cm2m2 = 8./3.;
     const COM cm3m3 = 6.;
     const COM cm1m2 = -1./3.;
     const COM cm1m3 = -3.*COM(0.,1.);
     const COM cm2m3 = 3.*COM(0.,1.);
 
     //Sqaure and sum for each helicity config:
     const double Mmmm = real(cm1m1*pow(abs(Mmmm1),2)+cm2m2*pow(abs(Mmmm2),2)+
                              cm3m3*pow(abs(Mmmm3),2)+2.*real(cm1m2*Mmmm1*conj(Mmmm2))+
                              2.*real(cm1m3*Mmmm1*conj(Mmmm3))+2.*real(cm2m3*Mmmm2*conj(Mmmm3)));
     const double Mmmp = real(cm1m1*pow(abs(Mmmp1),2)+cm2m2*pow(abs(Mmmp2),2)+
                              cm3m3*pow(abs(Mmmp3),2)+2.*real(cm1m2*Mmmp1*conj(Mmmp2))+
                              2.*real(cm1m3*Mmmp1*conj(Mmmp3))+2.*real(cm2m3*Mmmp2*conj(Mmmp3)));
     const double Mpmm = real(cm1m1*pow(abs(Mpmm1),2)+cm2m2*pow(abs(Mpmm2),2)+
                              cm3m3*pow(abs(Mpmm3),2)+2.*real(cm1m2*Mpmm1*conj(Mpmm2))+
                              2.*real(cm1m3*Mpmm1*conj(Mpmm3))+2.*real(cm2m3*Mpmm2*conj(Mpmm3)));
     const double Mpmp = real(cm1m1*pow(abs(Mpmp1),2)+cm2m2*pow(abs(Mpmp2),2)+
                              cm3m3*pow(abs(Mpmp3),2)+2.*real(cm1m2*Mpmp1*conj(Mpmp2))+
                              2.*real(cm1m3*Mpmp1*conj(Mpmp3))+2.*real(cm2m3*Mpmp2*conj(Mpmp3)));
 
     // Factor of 2 for the helicity for conjugate configurations
     return (2.*(Mmmm+Mmmp+Mpmm+Mpmp)/3.)/(pa-p1).m2()/(p2+p3-pb).m2();
   }
 }
 
 // Extremal qqx
 double ME_Exqqx_qbarqQ(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqout, pqbarout);
 }
 
 double ME_Exqqx_qqbarQ(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqbarout, pqout);
 }
 
 double ME_Exqqx_qbarqg(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqout, pqbarout)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 double ME_Exqqx_qqbarg(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqbarout, pqout)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 namespace {
 
   void CurrentMatrix(current j1, current j2, COM array[4][4]){
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         array[i][j]=j1[i]*j2[j];
       }
     }
   }
 
 
 //qqbar produced in the middle
   COM m1(current jtop, current jbot, bool hel2, HLV ka, HLV kb, HLV k2, HLV k3,
          std::vector<HLV> partons, unsigned int nabove){
     const double s23 = 2.*(k2*k3);
     const double sa2 = 2.*(ka*k2);
     const double sa3 = 2.*(ka*k3);
     const double s12 = 2.*(partons.front()*k2);
     const double s13 = 2.*(partons.front()*k3);
     const double sb2 = 2.*(kb*k2);
     const double sb3 = 2.*(kb*k3);
     const double s42 = 2.*(partons.back()*k2);
     const double s43 = 2.*(partons.back()*k3);
     HLV q1=ka-partons.front();
     for(unsigned int i=1;i<nabove+1;++i)
       q1-=partons.at(i);
     const HLV q2=q1-partons.at(nabove+2)-partons.at(nabove+1);
     const double t1 = q1.m2();
     const double t3 = q2.m2();
     current cur23;
     joo(k2,hel2,k3,hel2,cur23);
     const current curka{ka.e(),ka.px(),ka.py(),ka.pz()};
     const current curkb{kb.e(),kb.px(),kb.py(),kb.pz()};
     const current curk1{partons.front().e(),partons.front().px(),
                         partons.front().py(),partons.front().pz()};
     const current curk2{k2.e(),k2.px(),k2.py(),k2.pz()};
     const current curk3{k3.e(),k3.px(),k3.py(),k3.pz()};
     const current curk4{partons.back().e(),partons.back().px(),
                         partons.back().py(),partons.back().pz()};
     const current qc1{q1.e(),q1.px(),q1.py(),q1.pz()};
     const current qc2{q2.e(),q2.px(),q2.py(),q2.pz()};
 
     //Create the two bits of this vertex
     COM veik[4][4],v3g[4][4];
     for(int i=0;i<4;++i) {
       for(int j=0;j<4;++j){
         veik[i][j] = (cdot(cur23,curka)*(t1/(sa2+sa3))+cdot(cur23,curk1)*
                       (t1/(s12+s13))-cdot(cur23,curkb)*(t3/(sb2+sb3))-
                       cdot(cur23,curk4)*(t3/(s42+s43)))*metric(i,j);
       }
     }
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         v3g[i][j] = qc1[j]*cur23[i]+curk2[j]*cur23[i]+curk3[j]*cur23[i]+
           qc2[i]*cur23[j]-curk2[i]*cur23[j]-curk3[i]*cur23[j]-
           (cdot(qc1,cur23)+cdot(qc2,cur23))*metric(i,j);
       }
     }
     //Now dot in the currents - potential problem here with Lorentz
     //indicies, so check this
     COM M1=0;
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         M1+= metric(i,i)*jtop[i]*(veik[i][j]+v3g[i][j])*jbot[j]*metric(j,j);
       }
     }
     M1/=s23;
     return M1;
   }
 
   COM m2 (current jtop, current jbot, bool hel2, HLV ka, HLV k2,
           HLV k3, std::vector<HLV> partons, unsigned int nabove){
     //In order to get correct momentum dependence in the vertex, forst
     //have to work with CCurrent objects and then convert to 'current'
     current cur22,cur23,cur2q,curq3;
     COM qarray[4][4]={};
     COM temp[4][4]={};
     joo(k2,hel2,k2,hel2,cur22);
     joo(k2,hel2,k3,hel2,cur23);
     joi(k2,hel2,ka,hel2,cur2q);
     jio(ka,hel2,k3,hel2,curq3);
     CurrentMatrix(cur2q, curq3, qarray);
     for(unsigned int i =0; i<nabove+1; ++i){
       joo(k2,hel2,partons.at(i),hel2,cur2q);
       joo(partons.at(i),hel2,k3,hel2,curq3);
       CurrentMatrix(cur2q, curq3, temp);
       for(int ii=0;ii<4;++ii){
         for(int jj=0;jj<4;++jj){
           qarray[ii][jj]=qarray[ii][jj]-temp[ii][jj];
         }
       }
     }
     HLV qt=ka-k2;
     for(unsigned int i=0; i<nabove+1;++i){
       qt-=partons.at(i);
     }
     const double t2=qt*qt;
     COM tempv[4][4];
     for(int i=0; i<4;++i){
       for(int j=0;j<4;++j){
         tempv[i][j] = COM(0.,1.)*(qarray[i][j]-cur22[i]*cur23[j]);
       }
     }
     COM M2=0.;
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         M2+= metric(i,i)*jtop[i]*(tempv[i][j])*jbot[j]*metric(j,j);
       }
     }
     M2/=t2;
     return M2;
   }
 
   COM m3 (current jtop, current jbot, bool hel2, HLV ka, HLV k2,
           HLV k3, std::vector<HLV> partons, unsigned int nabove){
     COM M3=0.;
 
     current cur23,cur33,cur2q,curq3;
     COM qarray[4][4]={};
     COM temp[4][4]={};
     joo(k3,hel2,k3,hel2,cur33);
     joo(k2,hel2,k3,hel2,cur23);
     joi(k2,hel2,ka,hel2,cur2q);
     jio(ka,hel2,k3,hel2,curq3);
     CurrentMatrix(cur2q, curq3, qarray);
     for(unsigned int i =0; i<nabove+1; ++i){
       joo(k2,hel2,partons.at(i),hel2,cur2q);
       joo(partons.at(i),hel2,k3,hel2,curq3);
       CurrentMatrix(cur2q, curq3, temp);
       for(int ii=0;ii<4;++ii){
         for(int jj=0;jj<4;++jj){
           qarray[ii][jj]=qarray[ii][jj]-temp[ii][jj];
         }
       }
     }
     HLV qt=ka-k3;
     for(unsigned int i=0; i<nabove+1;++i){
       qt-=partons.at(i);
     }
     const double t2t=qt*qt;
 
     COM tempv[4][4];
 
     for(int i=0; i<4;++i){
       for(int j=0;j<4;++j){
         tempv[i][j] = COM(0.,-1.)*(qarray[j][i]-cur23[j]*cur33[i]);
       }
     }
 
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         M3+= metric(i,i)*jtop[i]*(tempv[i][j])*jbot[j]*metric(j,j);
       }
     }
     M3/= t2t;
     return M3;
   }
 }
 
 double ME_Cenqqx_qq(HLV ka, HLV kb, std::vector<HLV> partons, bool aqlinepa,
                     bool aqlinepb, bool qqxmarker, int nabove){
   //Get all the possible outer currents
   current j1p,j1m,j4p,j4m;
   if(!(aqlinepa)){
     joi(partons.front(),true,ka,true,j1p);
     joi(partons.front(),false,ka,false,j1m);
   }
   if(aqlinepa){
     jio(ka,true,partons.front(),true,j1p);
     jio(ka,false,partons.front(),false,j1m);
   }
   if(!(aqlinepb)){
     joi(partons.back(),true,kb,true,j4p);
     joi(partons.back(),false,kb,false,j4m);
   }
   if(aqlinepb){
     jio(kb,true,partons.back(),true,j4p);
     jio(kb,false,partons.back(),false,j4m);
   }
 
   HLV k2,k3;
   if(!(qqxmarker)){
     k2=partons.at(nabove+1);
     k3=partons.at(nabove+2);
   }
   else{
     k2=partons.at(nabove+2);
     k3=partons.at(nabove+1);
   }
 
   //8 helicity choices we can make, but only 4 indepedent ones
   //(complex conjugation related).
 
   const COM Mmmm1 = m1(j1m,j4m,false,ka,kb,k2,k3,partons,nabove);
   const COM Mmmm2 = m2(j1m,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mmmm3 = m3(j1m,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mmmp1 = m1(j1m,j4m,true, ka,kb,k2,k3,partons,nabove);
   const COM Mmmp2 = m2(j1m,j4m,true, ka,   k2,k3,partons,nabove);
   const COM Mmmp3 = m3(j1m,j4m,true, ka,   k2,k3,partons,nabove);
   const COM Mpmm1 = m1(j1p,j4m,false,ka,kb,k2,k3,partons,nabove);
   const COM Mpmm2 = m2(j1p,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mpmm3 = m3(j1p,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mpmp1 = m1(j1p,j4m,true, ka,kb,k2,k3,partons,nabove);
   const COM Mpmp2 = m2(j1p,j4m,true, ka,   k2,k3,partons,nabove);
   const COM Mpmp3 = m3(j1p,j4m,true, ka,   k2,k3,partons,nabove);
 
   //Colour factors:
   const COM cm1m1=3.;
   const COM cm2m2=4./3.;
   const COM cm3m3=4./3.;
   const COM cm1m2 =3./2.*COM(0.,1.);
   const COM cm1m3 = -3./2.*COM(0.,1.);
   const COM cm2m3 = -1./6.;
 
   //Square and sum for each helicity config:
   const double Mmmm = real(cm1m1*pow(abs(Mmmm1),2)+cm2m2*pow(abs(Mmmm2),2)+
               cm3m3*pow(abs(Mmmm3),2)+2.*real(cm1m2*Mmmm1*conj(Mmmm2))+
               2.*real(cm1m3*Mmmm1*conj(Mmmm3))+2.*real(cm2m3*Mmmm2*conj(Mmmm3)));
   const double Mmmp = real(cm1m1*pow(abs(Mmmp1),2)+cm2m2*pow(abs(Mmmp2),2)+
               cm3m3*pow(abs(Mmmp3),2)+2.*real(cm1m2*Mmmp1*conj(Mmmp2))+
               2.*real(cm1m3*Mmmp1*conj(Mmmp3))+2.*real(cm2m3*Mmmp2*conj(Mmmp3)));
   const double Mpmm = real(cm1m1*pow(abs(Mpmm1),2)+cm2m2*pow(abs(Mpmm2),2)+
               cm3m3*pow(abs(Mpmm3),2)+2.*real(cm1m2*Mpmm1*conj(Mpmm2))+
               2.*real(cm1m3*Mpmm1*conj(Mpmm3))+2.*real(cm2m3*Mpmm2*conj(Mpmm3)));
   const double Mpmp = real(cm1m1*pow(abs(Mpmp1),2)+cm2m2*pow(abs(Mpmp2),2)+
               cm3m3*pow(abs(Mpmp3),2)+2.*real(cm1m2*Mpmp1*conj(Mpmp2))+
               2.*real(cm1m3*Mpmp1*conj(Mpmp3))+2.*real(cm2m3*Mpmp2*conj(Mpmp3)));
 
   //Result (averaged, without coupling or t-channel props). Factor of
   //2 for the 4 helicity configurations I didn't work out explicitly
   HLV prop1 = ka;
   for(int i=0; i<=nabove; ++i){
     prop1 -= partons[i];
   }
   const HLV prop2 = prop1 - k2 - k3;
 
   return (2.*(Mmmm+Mmmp+Mpmm+Mpmp)/9./4.) /
     ((ka-partons.front()).m2()*(kb-partons.back()).m2()*prop1.m2()*prop2.m2());
 }
diff --git a/src/resummation_jet.cc b/src/resummation_jet.cc
index 9f94c1d..af6f53a 100644
--- a/src/resummation_jet.cc
+++ b/src/resummation_jet.cc
@@ -1,113 +1,113 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/resummation_jet.hh"
 
 #include <assert.h>
 #include <math.h>
 #include <stdio.h>
 
 #include <boost/numeric/ublas/lu.hpp>
 #include <boost/numeric/ublas/matrix.hpp>
 
 #include "fastjet/PseudoJet.hh"
 
 #include "HEJ/utility.hh"
 
 namespace HEJ{
 
   std::vector<fastjet::PseudoJet> resummation_jet_momenta(
       std::vector<fastjet::PseudoJet> const & p_born,
       fastjet::PseudoJet const & qperp
   ) {
     // for "new" reshuffling p^B = p + qperp*|p^B|/P^B
     double Pperp_born = 0.;
     for(auto const & p: p_born) Pperp_born += p.perp();
 
     std::vector<fastjet::PseudoJet> p_res;
     p_res.reserve(p_born.size());
     for(auto & pB: p_born) {
       const double px = pB.px() - qperp.px()*pB.perp()/Pperp_born;
       const double py = pB.py() - qperp.py()*pB.perp()/Pperp_born;
       const double pperp = sqrt(px*px + py*py);
       // keep the rapidities fixed
       const double pz = pperp*sinh(pB.rapidity());
       const double E = pperp*cosh(pB.rapidity());
       p_res.emplace_back(px, py, pz, E);
       assert(
           HEJ::nearby_ep(
               p_res.back().rapidity(),
               pB.rapidity(),
               1e-5
           )
       );
     }
     return p_res;
   }
 
   namespace{
     enum coordinates : size_t {
       x1, x2
     };
 
     namespace ublas = boost::numeric::ublas;
 
     template<class Matrix>
     double det(ublas::matrix_expression<Matrix> const& m) {
 
       ublas::permutation_matrix<size_t> pivots{m().size1()};
       Matrix mLu{m()};
 
       const auto is_singular = lu_factorize(mLu, pivots);
 
       if(is_singular) return 0.;
 
       double det = 1.0;
       for (std::size_t i = 0; i < pivots.size(); ++i){
         if (pivots(i) != i) det = -det;
 
         det *= mLu(i,i);
       }
 
       return det;
     }
 
     using ublas::matrix;
   }
 
   double resummation_jet_weight(
       std::vector<fastjet::PseudoJet> const & p_born,
       fastjet::PseudoJet const & qperp
   ) {
 
     static constexpr int num_coordinates = 2;
     auto Jacobian = matrix<double>{
       num_coordinates*p_born.size(),
       num_coordinates*p_born.size()
     };
     double P_perp = 0.;
     for(auto const & J: p_born) P_perp += J.perp();
 
     for(size_t l = 0; l < p_born.size(); ++l){
       const double Jl_perp = p_born[l].perp();
       for(size_t lp = 0; lp < p_born.size(); ++lp){
         const int delta_l = (l == lp);
         const auto & Jlp = p_born[lp];
         const double Jlp_perp = Jlp.perp();
         for(size_t x = x1; x <= x2; ++x){
           const double qxy = (x==x1)?qperp.px():qperp.py();
           for(size_t xp = x1; xp <= x2; ++xp){
             const double Jxy = (xp==x1)?Jlp.px():Jlp.py();
             const int delta_x = x == xp;
             Jacobian(2*l + x, 2*lp + xp) =
               + delta_l*delta_x
               - qxy*Jxy/(P_perp*Jlp_perp) * (delta_l - Jl_perp/P_perp);
           }
         }
       }
     }
     return det(Jacobian);
   }
 }
diff --git a/t/check_lhe.cc b/t/check_lhe.cc
index ad9714d..4f5ab0c 100644
--- a/t/check_lhe.cc
+++ b/t/check_lhe.cc
@@ -1,68 +1,68 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <iostream>
 #include <unordered_map>
 
 #include "HEJ/event_types.hh"
 #include "HEJ/EventReader.hh"
 
 #include "hej_test.hh"
 
 namespace {
   static constexpr double ep = 1e-3;
   const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4};
   constexpr double min_jet_pt = 30;
 }
 
 
 int main(int argn, char** argv) {
   if(argn != 2){
     std::cerr << "Usage: " << argv[0] << " lhe_file\n";
     return EXIT_FAILURE;
   }
 
   auto reader{ HEJ::make_reader(argv[1]) };
 
   // version should be overwritten
   ASSERT(reader->heprup().version==LHEF::HEPRUP().version);
 
   std::unordered_map<int, double> xsec_ref;
   for(int i=0; i < reader->heprup().NPRUP; ++i)
       xsec_ref[reader->heprup().LPRUP[i]] = 0.;
 
   while(reader->read_event()){
     auto const & hepeup = reader->hepeup();
     ASSERT(hepeup.NUP > 2); // at least 3 particles (2 in + 1 out)
     // first incoming has positive pz
     ASSERT(hepeup.PUP[0][2] > hepeup.PUP[1][2]);
     // test that we can trasform IDPRUP to event type
     (void) name(static_cast<HEJ::event_type::EventType>(hepeup.IDPRUP));
     xsec_ref[hepeup.IDPRUP] += hepeup.weight();
     // test that a HEJ event can be transformed back to the original HEPEUP
     auto hej_event = HEJ::Event::EventData(hepeup).cluster(jet_def, min_jet_pt);
     // there are two different weight infos, which should be the same
     ASSERT(hej_event.central().weight == hepeup.weight());
     ASSERT(hej_event.central().weight == hepeup.XWGTUP);
     // reader->heprup() is const, we can't use it to create a hepeup
     auto cp_heprup = reader->heprup();
     auto new_event = HEJ::to_HEPEUP(hej_event, &cp_heprup);
     ASSERT_PROPERTY(new_event, hepeup, weight());
     ASSERT_PROPERTY(new_event, hepeup, XWGTUP);
     ASSERT_PROPERTY(new_event, hepeup, SCALUP);
     ASSERT_PROPERTY(new_event, hepeup, NUP);
   }
 
   for(size_t i = 0; i < xsec_ref.size(); ++i){
     double const ref = xsec_ref[reader->heprup().LPRUP[i]];
     double const calc = reader->heprup().XSECUP[i];
     std::cout << ref << '\t' << calc << '\n';
     if(std::abs(calc-ref) > ep*calc){
       std::cerr << "Cross sections deviate substantially";
       return EXIT_FAILURE;
     }
   }
   return EXIT_SUCCESS;
 }
diff --git a/t/check_lhe_sherpa.cc b/t/check_lhe_sherpa.cc
index 7f82d07..751ddff 100644
--- a/t/check_lhe_sherpa.cc
+++ b/t/check_lhe_sherpa.cc
@@ -1,52 +1,52 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <iostream>
 #include <string>
 
 #include "HEJ/LesHouchesReader.hh"
 
 #include "hej_test.hh"
 
 static constexpr double ep = 1e-5;
 
 int main(int argn, char** argv) {
   if(argn != 3){
     std::cerr << "Usage: " << argv[0] << " lhe_file xs\n";
     return EXIT_FAILURE;
   }
 
   auto reader{ HEJ::make_reader(argv[1])};
 
   const double ref_xs = std::stod(argv[2]);
 
   if(std::abs(reader->heprup().IDWTUP) != 3){
     std::cerr << "Sherpa Events should always be neutral/unweighted\n";
     return EXIT_FAILURE;
   }
   // version should be overwritten
   ASSERT(reader->heprup().version==LHEF::HEPRUP().version);
 
 
   double xs { 0. };
   size_t n_evts { 0 };
   ASSERT(std::abs(reader->heprup().XSECUP.front()-ref_xs) < ep*ref_xs);
   while(reader->read_event()){
     ++n_evts;
     xs += reader->hepeup().weight();
     ASSERT(reader->hepeup().weight() == reader->hepeup().XWGTUP);
   }
 
   if(std::abs(xs-ref_xs) > ep*xs){
     std::cerr << "Cross sections deviate substantially!\n"
       <<"Found "<< xs <<" but expected "<< ref_xs <<" -> "<< xs/ref_xs <<"\n";
     return EXIT_FAILURE;
   }
   if(!reader->number_events() || *(reader->number_events()) != n_evts){
     std::cerr << "Number of Event not correctly set for Sherpa LHE reader\n";
     return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;
 }
diff --git a/t/check_res.cc b/t/check_res.cc
index d4a7306..81b2c8e 100644
--- a/t/check_res.cc
+++ b/t/check_res.cc
@@ -1,150 +1,150 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <cmath>
 #include <iostream>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/CrossSectionAccumulator.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/EventReweighter.hh"
 #include "HEJ/Mixmax.hh"
 #include "HEJ/stream.hh"
 
 #include "hej_test.hh"
 
 namespace{
   const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4};
   const fastjet::JetDefinition Born_jet_def{jet_def};
   constexpr double Born_jetptmin = 30;
   constexpr double max_ext_soft_pt_fraction = 0.1;
   constexpr double jetptmin = 35;
   constexpr bool log_corr = false;
   const HEJ::ParticleProperties Wprop{80.385, 2.085};
   const HEJ::ParticleProperties Zprop{91.187, 2.495};
   const HEJ::ParticleProperties Hprop{125,    0.004165};
   constexpr double vev = 246.2196508;
   using EventTreatment = HEJ::EventTreatment;
   using namespace HEJ::event_type;
   HEJ::EventTreatMap treat{
     {no_2_jets, EventTreatment::discard},
     {bad_final_state, EventTreatment::discard},
     {non_resummable, EventTreatment::discard},
     {unof, EventTreatment::discard},
     {unob, EventTreatment::discard},
     {qqxexb, EventTreatment::discard},
     {qqxexf, EventTreatment::discard},
     {qqxmid, EventTreatment::discard},
     {FKL, EventTreatment::reweight}
   };
 
   bool correct_colour(HEJ::Event const & ev){
     if(!HEJ::event_type::is_resummable(ev.type()))
       return true;
     if(!ev.is_leading_colour())
       return false;
     return true;
   }
 }
 
 int main(int argn, char** argv) {
   if(argn == 5 && std::string(argv[4]) == "unof"){
     --argn;
     treat[unof] = EventTreatment::reweight;
     treat[unob] = EventTreatment::discard;
     treat[FKL] = EventTreatment::discard;
   }
   if(argn == 5 && std::string(argv[4]) == "unob"){
     --argn;
     treat[unof] = EventTreatment::discard;
     treat[unob] = EventTreatment::reweight;
     treat[FKL] = EventTreatment::discard;
   }
   else if(argn == 5 && std::string(argv[4]) == "splitf"){
     --argn;
     treat[qqxexb] = EventTreatment::discard;
     treat[qqxexf] = EventTreatment::reweight;
     treat[FKL] = EventTreatment::discard;
   }
   else if(argn == 5 && std::string(argv[4]) == "splitb"){
     --argn;
     treat[qqxexb] = EventTreatment::reweight;
     treat[qqxexf] = EventTreatment::discard;
     treat[FKL] = EventTreatment::discard;
   }
   else if(argn == 5 && std::string(argv[4]) == "qqxmid"){
     --argn;
     treat[qqxmid] = EventTreatment::reweight;
     treat[FKL] = EventTreatment::discard;
   }
   if(argn != 4){
     std::cerr << "Usage: check_res eventfile xsection tolerance [uno]";
     return EXIT_FAILURE;
   }
 
   const double xsec_ref = std::stod(argv[2]);
   const double tolerance = std::stod(argv[3]);
 
   HEJ::istream in{argv[1]};
   LHEF::Reader reader{in};
 
   HEJ::PhaseSpacePointConfig psp_conf;
   psp_conf.jet_param = HEJ::JetParameters{jet_def, jetptmin};
   psp_conf.max_ext_soft_pt_fraction = max_ext_soft_pt_fraction;
   HEJ::MatrixElementConfig ME_conf;
   ME_conf.log_correction = log_corr;
   ME_conf.Higgs_coupling = HEJ::HiggsCouplingSettings{};
   ME_conf.ew_parameters.set_vevWZH(vev, Wprop, Zprop, Hprop);
   HEJ::EventReweighterConfig conf;
   conf.psp_config = std::move(psp_conf);
   conf.ME_config = std::move(ME_conf);
   conf.treat = treat;
 
   reader.readEvent();
   const bool has_Higgs = std::find(
       begin(reader.hepeup.IDUP),
       end(reader.hepeup.IDUP),
       25
   ) != end(reader.hepeup.IDUP);
   const double mu = has_Higgs?125.:91.188;
   HEJ::ScaleGenerator scale_gen{
     {{std::to_string(mu), HEJ::FixedScale{mu}}}, {}, 1.
   };
   std::shared_ptr<HEJ::RNG> ran{std::make_shared<HEJ::Mixmax>()};
   HEJ::EventReweighter hej{reader.heprup, std::move(scale_gen), conf, ran};
 
   HEJ::CrossSectionAccumulator xs;
   do{
     auto ev_data = HEJ::Event::EventData{reader.hepeup};
     shuffle_particles(ev_data);
     ev_data.reconstruct_intermediate();
     HEJ::Event ev{
       ev_data.cluster(
         Born_jet_def, Born_jetptmin
       )
     };
     auto resummed_events = hej.reweight(ev, 100);
     for(auto const & res_ev: resummed_events) {
       ASSERT(correct_colour(res_ev));
       ASSERT(std::isfinite(res_ev.central().weight));
       // we fill the xs uncorrelated since we only want to test the uncertainty
       // of the resummation
       xs.fill(res_ev);
     }
   } while(reader.readEvent());
   const double xsec = xs.total().value;
   const double xsec_err = std::sqrt(xs.total().error);
   const double significance =
     std::abs(xsec - xsec_ref) / std::sqrt( xsec_err*xsec_err + tolerance*tolerance );
   std::cout << xsec_ref << " +/- " << tolerance << " ~ "
     << xsec << " +- " << xsec_err << " => " << significance << " sigma\n";
 
   if(significance > 3.){
     std::cerr << "Cross section is off by over 3 sigma!\n";
     return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;
 }
diff --git a/t/hej_test.hh b/t/hej_test.hh
index ecce1da..db07910 100644
--- a/t/hej_test.hh
+++ b/t/hej_test.hh
@@ -1,42 +1,42 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include "HEJ/Event.hh"
 #include "HEJ/exceptions.hh"
 
 //! throw error if condition not fulfilled
 #define ASSERT(x) if(!(x)) { \
     throw std::logic_error("Assertion '" #x "' failed."); \
   }
 
 //! throw error if prop is different between ev1 and ev2
 #define ASSERT_PROPERTY(ev1,ev2,prop) ASSERT(ev1.prop == ev2.prop)
 
 /** @brief get specific Phase Space Points for njets with boson at pos_boson
  *
  * if pos_boson = -1 (or not implemented) -> no boson
  *
  * njet==7 is special: has less jets, i.e. multiple parton in one jet,
  *                     all partons are massive (4 GeV) -> can be boson/decay
  *                     pos_boson < 0 to select process (see list for details)
  */
 HEJ::Event::EventData get_process(int njet, int pos_boson);
 
 //! select process from string input (see also get_process)
 //!
 //! overwrite_boson to force a specific boson position, indepentent from input
 //! (useful for njet == 7)
 HEJ::Event::EventData parse_configuration(
     std::array<std::string,2> const & in, std::vector<std::string> const & out,
     int overwrite_boson = 0
 );
 
 //! shuffle particles around
 void shuffle_particles(HEJ::Event::EventData & ev);
 
 //! Decay W boson to lepton & neutrino
 std::vector<HEJ::Particle> decay_W( HEJ::Particle const & parent );
diff --git a/t/test_ME_generic.cc b/t/test_ME_generic.cc
index 59db263..3c5b245 100644
--- a/t/test_ME_generic.cc
+++ b/t/test_ME_generic.cc
@@ -1,180 +1,180 @@
 /**
  *  \brief     Generic tester for the ME for a given set of PSP
  *
  *  \note      reference weights and PSP (as LHE file) have to be given as
  *             _individual_ files
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 
 #include <algorithm>
 #include <cmath>
 #include <fstream>
 
 #include "HEJ/Event.hh"
 #include "HEJ/EventReader.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/stream.hh"
 #include "HEJ/YAMLreader.hh"
 
 #include "hej_test.hh"
 
 constexpr double alpha_s = 0.118;
 constexpr double ep = 1e-9;
 constexpr double ep_mirror = 1e-3;
 
 enum MEComponent {tree, virt};
 
 MEComponent guess_component(std::string const & data_file) {
   if(data_file.find("virt") != data_file.npos) return MEComponent::virt;
   return MEComponent::tree;
 }
 
 HEJ::Event::EventData mirror_event(HEJ::Event::EventData ev){
   for(auto & part: ev.incoming){
     auto & p{ part.p };
     p.reset(p.px(),p.py(),-p.pz(),p.E());
   }
   for(auto & part: ev.outgoing){
     auto & p{ part.p };
     p.reset(p.px(),p.py(),-p.pz(),p.E());
   }
   for(auto & decay: ev.decays){
     for(auto & part: decay.second){
       auto & p{ part.p };
       p.reset(p.px(),p.py(),-p.pz(),p.E());
     }
   }
   return ev;
 }
 
 int main(int argn, char** argv){
   if(argn != 4 && argn != 5){
     std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml ME_weights input_file.lhe\n\n";
     return EXIT_FAILURE;
   }
   bool OUTPUT_MODE = false;
   if(argn == 5 && std::string("OUTPUT")==std::string(argv[4]))
       OUTPUT_MODE = true;
   const HEJ::Config config = HEJ::load_config(argv[1]);
 
   std::fstream wgt_file;
   if ( OUTPUT_MODE ) {
     std::cout << "_______________________USING OUTPUT MODE!_______________________" << std::endl;
     wgt_file.open(argv[2], std::fstream::out);
     wgt_file.precision(9);
     wgt_file << std::scientific;
   } else {
     wgt_file.open(argv[2], std::fstream::in);
   }
 
   auto reader{ HEJ::make_reader(argv[3])};
 
   const auto component = guess_component(argv[2]);
 
   HEJ::MatrixElement ME{
     [](double){ return alpha_s; },
     HEJ::to_MatrixElementConfig(config)
   };
   double max_ratio = 0.;
   size_t idx_max_ratio = 0;
 
   HEJ::Event ev_max_ratio(HEJ::Event::EventData{}.cluster(
       config.resummation_jets.def,0
     )
   );
   double av_ratio = 0;
 
   size_t i = 0;
   while(reader->read_event()){
     ++i;
 
     HEJ::Event::EventData data{reader->hepeup()};
     shuffle_particles(data);
 
     HEJ::Event::EventData data_mirror{mirror_event(data)};
     shuffle_particles(data_mirror);
 
     HEJ::Event event{
       data.cluster(
         config.resummation_jets.def,
         config.resummation_jets.min_pt
       )
     };
     HEJ::Event event_mirror{
       data_mirror.cluster(
         config.resummation_jets.def,
         config.resummation_jets.min_pt
       )
     };
     const double our_ME = (component == MEComponent::tree)?
       ME.tree(event).central:
       ME.virtual_corrections(event).central
       ;
     if(!std::isfinite(our_ME)){
       std::cerr << "Found non-finite ME ("<< our_ME <<")\n" << event << std::endl;
       return EXIT_FAILURE;
     }
     const double ME_mirror = (component == MEComponent::tree)?
       ME.tree(event_mirror).central:
       ME.virtual_corrections(event_mirror).central
       ;
     if(!std::isfinite(ME_mirror)){
       std::cerr << "Found non-finite ME ("<< ME_mirror <<")\n" << event_mirror << std::endl;
       return EXIT_FAILURE;
     }
 
     if(std::abs(our_ME/ME_mirror-1.)>ep_mirror){
       size_t precision(std::cout.precision());
       std::cerr.precision(16);
       std::cerr<< "z-Mirrored ME gives different result " << i << "\n"
         <<our_ME << " vs " << ME_mirror << " => difference: "
         << std::abs(our_ME/ME_mirror-1.) << "\n" << event
         << "\nmirrored:\n" << event_mirror << std::endl;
       std::cerr.precision(precision);
       return EXIT_FAILURE;
     }
 
     if ( OUTPUT_MODE ) {
       wgt_file << our_ME << std::endl;
     } else {
       std::string line;
       if(!std::getline(wgt_file,line)) break;
       const double ref_ME = std::stod(line);
       double diff;
       if(ref_ME == 0.){
         diff = (our_ME != 0.);
       } else {
         diff = std::abs(our_ME/ref_ME-1.);
       }
       av_ratio+=diff;
       if( diff > max_ratio ) {
         max_ratio = diff;
         idx_max_ratio = i;
         ev_max_ratio = event;
       }
       if( diff > ep ){
         size_t precision(std::cout.precision());
         std::cerr.precision(16);
         std::cerr<< "Large difference in PSP " << i << "\nis: "<<our_ME
           << " should: " << ref_ME << " => difference: " << diff << "\n"
           << event << std::endl;
         std::cerr.precision(precision);
         return EXIT_FAILURE;
       }
     }
   }
   wgt_file.close();
   if ( i<100 )
     throw std::invalid_argument{"Not enough PSP tested"};
   if ( !OUTPUT_MODE ) {
     size_t precision(std::cout.precision());
     std::cout.precision(16);
     std::cout << "Avg ratio after " << i << " PSP: " << av_ratio/i << std::endl;
     std::cout << "maximal ratio at " << idx_max_ratio << ": " << max_ratio << std::endl;
     std::cout.precision(precision);
   }
   return EXIT_SUCCESS;
 }
diff --git a/t/test_classify.cc b/t/test_classify.cc
index 3d342d5..80c4a24 100644
--- a/t/test_classify.cc
+++ b/t/test_classify.cc
@@ -1,491 +1,491 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <iostream>
 #include <random>
 
 #include "HEJ/Event.hh"
 #include "HEJ/exceptions.hh"
 
 #include "hej_test.hh"
 
 namespace {
   const fastjet::JetDefinition jet_def{fastjet::JetAlgorithm::antikt_algorithm, 0.4};
   const double min_jet_pt{30.};
   const std::vector<std::string> all_quarks{"-4","-1","1","2","3","4"};
   const std::vector<std::string> all_partons{"g","-2","-1","1","2","3","4"};
   const std::vector<std::string> all_bosons{"h", "Wp", "Wm"};
   const std::vector<std::string> all_gZ{"photon", "Z"};
   const std::vector<std::string> all_w{"W+", "W-"};
 
   static std::mt19937_64 ran{0};
 
   bool couple_quark(std::string const & boson, std::string & quark){
     if(abs(HEJ::to_ParticleID(boson)) == HEJ::ParticleID::Wp){
       auto qflav{ HEJ::to_ParticleID(quark) };
       if(!HEJ::is_anyquark(qflav)) return false;
       const int W_charge = HEJ::to_ParticleID(boson)>0?1:-1;
       if(W_charge*qflav < 0 && !(abs(qflav)%2)) return false; // not anti-down
       if(W_charge*qflav > 0 &&  (abs(qflav)%2)) return false; // not up
       quark=std::to_string(qflav-W_charge);
     }
     return true;
   }
 
   bool match_expectation( HEJ::event_type::EventType expected,
     std::array<std::string,2> const & in, std::vector<std::string> const & out,
       int const overwrite_boson = 0
   ){
     HEJ::Event ev{ parse_configuration(
                       in,out,overwrite_boson ).cluster(jet_def, min_jet_pt)};
     if(ev.type() != expected){
       std::cerr << "Expected type " << HEJ::event_type::name(expected)
                 << " but found " << HEJ::event_type::name(ev.type()) << "\n" << ev;
       auto jet_idx{ ev.particle_jet_indices() };
       std::cout << "Particle Jet indices: ";
       for(int const i: jet_idx)
         std::cout << i << " ";
       std::cout << std::endl;
       return false;
     }
     return true;
   }
 
   //! test FKL configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_fkl( bool const implemented=true ){
     using namespace HEJ;
     auto const type{ implemented?event_type::FKL:event_type::non_resummable };
     std::vector<std::string> bosons;
     if(implemented)
       bosons = all_bosons;
     else {
       bosons = all_gZ;
     }
     for(std::string const & first: all_partons)       // all quark flavours
       for(std::string const & last: all_partons){
         for(int njet=2; njet<=6; ++njet){             // all multiplicities
           if(njet==5) continue;
           std::array<std::string,2> base_in{first,last};
           std::vector<std::string> base_out(njet, "g");
           base_out.front() = first;
           base_out.back() = last;
           if(implemented && !match_expectation(type, base_in, base_out))
             return false;
           for(auto const & boson: bosons)         // any boson
             for(int pos=0; pos<=njet; ++pos){         // at any position
               auto in{base_in};
               auto out{base_out};
               // change quark flavours for W
               const bool couple_idx = std::uniform_int_distribution<int>{0,1}(ran);
               if(!couple_quark(boson, couple_idx?out.back():out.front()))
                 continue;
               out.insert(out.begin()+pos, boson);
               if(!match_expectation(type, in, out))
                 return false;
             }
         }
       }
     return true;
   }
 
   //! test unordered configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_uno( bool const implemented=true ){
     using namespace HEJ;
     auto const b{ implemented?event_type::unob:event_type::non_resummable };
     auto const f{ implemented?event_type::unof:event_type::non_resummable };
     std::vector<std::string> bosons;
     if(implemented)
       bosons = all_bosons;
     else {
       bosons = all_gZ;
     }
     for(std::string const & uno: all_quarks)          // all quark flavours
       for(std::string const & fkl: all_partons){
         for(int njet=3; njet<=6; ++njet){             // all multiplicities >2
           if(njet==5) continue;
           for(int i=0; i<2; ++i){                     // forward & backwards
             std::array<std::string,2> base_in;
             std::vector<std::string> base_out(njet, "g");
             const int uno_pos = i?1:(njet-2);
             const int fkl_pos = i?(njet-1):0;
             base_in[i?0:1] = uno;
             base_in[i?1:0] = fkl;
             base_out[uno_pos] = uno;
             base_out[fkl_pos] = fkl;
             auto expectation{ i?b:f };
             if( implemented
                 && !match_expectation(expectation, base_in, base_out) )
               return false;
             for(auto const & boson: bosons){      // any boson
               // at any position (higgs only inside FKL chain)
               int start = 0;
               int end = njet;
               if(to_ParticleID(boson) == pid::higgs){
                 start = i?(uno_pos+1):fkl_pos;
                 end = i?(fkl_pos+1):uno_pos;
               }
               for(int pos=start; pos<=end; ++pos){
                 auto in{base_in};
                 auto out{base_out};
                 // change quark flavours for W
                 const bool couple_idx = std::uniform_int_distribution<int>{0,1}(ran);
                 if(!couple_quark(boson, couple_idx?out[fkl_pos]:out[uno_pos]))
                   continue;
                 out.insert(out.begin()+pos, boson);
                 if(!match_expectation(expectation, in, out))
                   return false;
               }
             }
           }
         }
       }
     return true;
   }
 
   //! test extremal qqx configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_extremal_qqx( bool const implemented=true ){
     using namespace HEJ;
     auto const b{ implemented?event_type::qqxexb:event_type::non_resummable };
     auto const f{ implemented?event_type::qqxexf:event_type::non_resummable };
     std::vector<std::string> bosons;
     if(implemented)
       bosons = all_w;
     else {
       bosons = all_gZ;
       bosons.push_back("h");
     }
     for(std::string const & qqx: all_quarks)          // all quark flavours
       for(std::string const & fkl: all_partons){
         std::string const qqx2{ std::to_string(HEJ::to_ParticleID(qqx)*-1) };
         for(int njet=3; njet<=6; ++njet){             // all multiplicities >2
           if(njet==5) continue;
           for(int i=0; i<2; ++i){                     // forward & backwards
             std::array<std::string,2> base_in;
             std::vector<std::string> base_out(njet, "g");
             const int qqx_pos = i?0:(njet-2);
             const int fkl_pos = i?(njet-1):0;
             base_in[i?0:1] = "g";
             base_in[i?1:0] = fkl;
             base_out[fkl_pos]   = fkl;
             base_out[qqx_pos]   = qqx;
             base_out[qqx_pos+1] = qqx2;
             auto expectation{ i?b:f };
             if( implemented
                 && !match_expectation(expectation, base_in, base_out) )
               return false;
             for(auto const & boson: bosons){ // all bosons
               // at any position (higgs only inside FKL chain)
               int start = 0;
               int end = njet;
               if(to_ParticleID(boson) == pid::higgs){
                 start = i?(qqx_pos+2):fkl_pos;
                 end = i?(fkl_pos+1):qqx_pos;
               }
               for(int pos=start; pos<=end; ++pos){
                 auto in{base_in};
                 auto out{base_out};
                 // change quark flavours for W
                 const bool couple_idx = std::uniform_int_distribution<int>{0,1}(ran);
                 if(couple_idx || !couple_quark(boson, out[fkl_pos]) ){
                   // (randomly) try couple to FKL, else fall-back to qqx
                   if(!couple_quark(boson, out[qqx_pos]))
                     couple_quark(boson, out[qqx_pos+1]);
                 }
                 out.insert(out.begin()+pos, boson);
                 if(!match_expectation(expectation, in, out))
                   return false;
               }
             }
           }
         }
         // test allowed jet configurations
         if( implemented){
           if( !( match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -3)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -4)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -5)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -5)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -6)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -7)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -7)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -8)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -8)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -9)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -10)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -11)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -11)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqx,qqx2}, -12)
               && match_expectation(b,{"g",fkl},{qqx,qqx2,"g","g","g","g",fkl}, -12)
             ))
             return false;
          if (fkl == "2") {
           if( !( match_expectation(f,{"2","g"},{"1","Wp","g","g","g",qqx,qqx2}, -3)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"g","Wp","g","g","1"}, -4)
               && match_expectation(f,{"2","g"},{"1","Wp","g","g","g",qqx,qqx2}, -5)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"g","Wp","g","g","1"}, -5)
               && match_expectation(f,{"2","g"},{"1","g","Wp","g","g",qqx,qqx2}, -6)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqx,qqx2}, -7)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"g","g","g","Wp","1"}, -7)
               && match_expectation(f,{"2","g"},{"1","Wp","g","g","g",qqx,qqx2}, -8)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"Wp","g","g","g","1"}, -8)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"g","Wp","g","g","1"}, -9)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqx,qqx2}, -10)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqx,qqx2}, -11)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"g","g","g","Wp","1"}, -11)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqx,qqx2}, -12)
               && match_expectation(b,{"g","2"},{qqx,qqx2,"g","Wp","g","g","1"}, -12)
             ))
             return false;
          }
         }
       }
     return true;
   }
 
   //! test central qqx configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_central_qqx(bool const implemented=true){
     using namespace HEJ;
     auto const t{ implemented?event_type::qqxmid:event_type::non_resummable };
     std::vector<std::string> bosons;
     if(implemented)
       bosons = all_w;
     else {
       bosons = all_gZ;
       bosons.push_back("h");
     }
     for(std::string const & qqx: all_quarks)        // all quark flavours
       for(std::string const & fkl1: all_partons)
         for(std::string const & fkl2: all_partons){
           std::string const qqx2{ std::to_string(HEJ::to_ParticleID(qqx)*-1) };
           for(int njet=4; njet<=6; ++njet){                 // all multiplicities >3
             if(njet==5) continue;
             for(int qqx_pos=1; qqx_pos<njet-2; ++qqx_pos){ // any qqx position
               std::array<std::string,2> base_in;
               std::vector<std::string> base_out(njet, "g");
               base_in[0] = fkl1;
               base_in[1] = fkl2;
               base_out.front()    = fkl1;
               base_out.back()     = fkl2;
               base_out[qqx_pos]   = qqx;
               base_out[qqx_pos+1] = qqx2;
               if( implemented && !match_expectation(t, base_in, base_out) )
                 return false;
               for(auto const & boson: bosons)         // any boson
                 for(int pos=0; pos<=njet; ++pos){         // at any position
                   if( to_ParticleID(boson) == pid::higgs
                     && (pos==qqx_pos || pos==qqx_pos+1) )
                     continue;
                   auto in{base_in};
                   auto out{base_out};
                   // change quark flavours for W
                   const int couple_idx{ std::uniform_int_distribution<int>{0,2}(ran) };
                   // (randomly) try couple to FKL, else fall-back to qqx
                   if( couple_idx == 0 && couple_quark(boson, out.front()) ){}
                   else if( couple_idx == 1 && couple_quark(boson, out.back()) ){}
                   else {
                     if(!couple_quark(boson, out[qqx_pos]))
                       couple_quark(boson, out[qqx_pos+1]);
                   }
                   out.insert(out.begin()+pos, boson);
                   if(!match_expectation(t, in, out))
                     return false;
                 }
             }
           }
         }
     return true;
   }
 
   // this checks a (non excessive) list of non-resummable states
   bool check_non_resummable(){
     auto type{ HEJ::event_type::non_resummable};
     return
       // 2j - crossing lines
          match_expectation(type, {"g","2"},  {"2","g"})
       && match_expectation(type, {"-1","g"}, {"g","-1"})
       && match_expectation(type, {"1","-1"}, {"-1","1"})
       && match_expectation(type, {"g","2"},  {"2","g","h"})
       && match_expectation(type, {"1","2"},  {"2","h","1"})
       && match_expectation(type, {"1","-1"}, {"h","-1","1"})
       && match_expectation(type, {"g","2"},  {"Wp","1","g"})
       && match_expectation(type, {"1","-1"}, {"-2","Wp","1"})
       && match_expectation(type, {"4","g"},  {"g","3","Wp"})
       && match_expectation(type, {"1","-2"}, {"-1","Wm","1"})
       && match_expectation(type, {"g","3"},  {"4","g","Wm"})
       && match_expectation(type, {"1","3"},  {"Wm","4","1"})
       // 2j - qqx
       && match_expectation(type, {"g","g"},  {"1","-1"})
       && match_expectation(type, {"g","g"},  {"-2","2","h"})
       && match_expectation(type, {"g","g"},  {"-4","Wp","3"})
       && match_expectation(type, {"g","g"},  {"Wm","-1","2"})
       // 3j - crossing lines
       && match_expectation(type, {"g","4"},  {"4","g","g"})
       && match_expectation(type, {"-1","g"}, {"g","g","-1"})
       && match_expectation(type, {"1","3"},  {"3","g","1"})
       && match_expectation(type, {"-2","2"}, {"2","g","-2","h"})
       && match_expectation(type, {"-3","g"}, {"g","g","Wp","-4"})
       && match_expectation(type, {"1","-2"}, {"Wm","-1","g","1"})
       && match_expectation(type, {"-1","g"}, {"1","-1","-1"})
       // higgs inside uno
       && match_expectation(type, {"-1","g"}, {"g","h","-1","g"})
       && match_expectation(type, {"-1","1"}, {"g","h","-1","1"})
       && match_expectation(type, {"g","2"},  {"g","2","h","g"})
       && match_expectation(type, {"-1","1"}, {"-1","1","h","g"})
       // higgs outside uno
       && match_expectation(type, {"-1","g"}, {"h","g","-1","g"})
       && match_expectation(type, {"-1","1"}, {"-1","1","g","h"})
       // higgs inside qqx
       && match_expectation(type, {"g","g"}, {"-1","h","1","g","g"})
       && match_expectation(type, {"g","g"}, {"g","-1","h","1","g"})
       && match_expectation(type, {"g","g"}, {"g","g","2","h","-2"})
       // higgs outside qqx
       && match_expectation(type, {"g","g"}, {"h","-1","1","g","g"})
       && match_expectation(type, {"g","g"}, {"g","g","2","-2","h"})
       // 4j - two uno
       && match_expectation(type, {"-2","2"}, {"g","-2","2","g"})
       && match_expectation(type, {"1","3"},  {"g","1","h","3","g"})
       && match_expectation(type, {"1","2"},  {"g","1","3","Wp","g"})
       && match_expectation(type, {"1","-2"}, {"g","Wm","1","-1","g"})
       // 4j - two gluon outside
       && match_expectation(type, {"g","4"},  {"g","4","g","g"})
       && match_expectation(type, {"1","3"},  {"1","3","h","g","g"})
       && match_expectation(type, {"1","2"},  {"1","3","g","Wp","g"})
       && match_expectation(type, {"1","-2"}, {"1","Wm","-1","g","g"})
       && match_expectation(type, {"-1","g"}, {"g","g","-1","g"})
       && match_expectation(type, {"1","3"},  {"g","g","1","3","h"})
       && match_expectation(type, {"1","2"},  {"g","g","1","Wp","3"})
       && match_expectation(type, {"1","-2"}, {"Wm","g","g","1","-1"})
       // 4j - ggx+uno
       && match_expectation(type, {"g","4"},  {"1","-1","4","g"})
       && match_expectation(type, {"2","g"},  {"g","2","-3","3"})
       && match_expectation(type, {"g","4"},  {"1","-1","h","4","g"})
       && match_expectation(type, {"2","g"},  {"g","2","-3","3","h"})
       && match_expectation(type, {"g","4"},  {"Wp","1","-1","3","g"})
       && match_expectation(type, {"2","g"},  {"g","2","-4","Wp","3"})
       && match_expectation(type, {"g","4"},  {"2","Wm","-1","4","g"})
       && match_expectation(type, {"2","g"},  {"g","2","Wp","-3","4"})
       // 3j - crossing+uno
       && match_expectation(type, {"1","4"},  {"g","4","1"})
       && match_expectation(type, {"1","4"},  {"4","1","g"})
       && match_expectation(type, {"1","4"},  {"g","h","4","1"})
       && match_expectation(type, {"-1","-3"},{"Wm","g","-4","-1"})
       && match_expectation(type, {"1","4"},  {"3","1","Wp","g"})
       && match_expectation(type, {"1","4"},  {"3","1","g","h"})
       // 3j - crossing+qqx
       && match_expectation(type, {"1","g"},  {"-1","1","g","1"})
       && match_expectation(type, {"1","g"},  {"-1","1","1","g"})
       && match_expectation(type, {"g","1"},  {"1","g","1","-1"})
       && match_expectation(type, {"g","1"},  {"g","1","1","-1"})
       && match_expectation(type, {"1","g"},  {"2","-2","g","1"})
       && match_expectation(type, {"1","g"},  {"2","-2","1","g"})
       && match_expectation(type, {"g","1"},  {"1","g","-2","2"})
       && match_expectation(type, {"g","1"},  {"g","1","-2","2"})
       && match_expectation(type, {"1","g"},  {"-1","1","h","g","1"})
       && match_expectation(type, {"1","g"},  {"-1","h","1","1","g"})
       && match_expectation(type, {"g","1"},  {"1","g","1","h","-1"})
       && match_expectation(type, {"g","1"},  {"h","g","1","1","-1"})
       && match_expectation(type, {"1","g"},  {"2","-2","1","g","h"})
       && match_expectation(type, {"g","1"},  {"g","h","1","-2","2"})
       && match_expectation(type, {"1","g"},  {"Wp","3","-4","g","1"})
       && match_expectation(type, {"3","g"},  {"-2","Wm","1","3","g"})
       && match_expectation(type, {"g","1"},  {"1","g","Wm","-3","4"})
       && match_expectation(type, {"g","-3"},  {"g","-3","-1","Wp","2"})
       // 4j- gluon in qqx
       && match_expectation(type, {"g","1"},  {"1","g","-1","1"})
       && match_expectation(type, {"1","g"},  {"1","-1","g","1"})
       && match_expectation(type, {"g","1"},  {"1","g","Wm","-2","1"})
       && match_expectation(type, {"2","g"},  {"2","-2","g","Wp","1"})
       && match_expectation(type, {"g","g"},  {"Wp","3","g","-4","g"})
       && match_expectation(type, {"1","g"},  {"1","h","-1","g","1"})
       // 6j - two qqx
       && match_expectation(type, {"g","g"},  {"1","-1","g","g","1","-1"})
       && match_expectation(type, {"g","g"},  {"1","-1","g","1","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","-1","g","1","-1"})
       && match_expectation(type, {"g","g"},  {"g","1","-1","1","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","1","-1","-1","g"})
       && match_expectation(type, {"g","g"},  {"h","1","-1","g","g","1","-1"})
       && match_expectation(type, {"g","g"},  {"1","Wp","-2","g","1","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","Wp","-1","g","1","-2"})
       && match_expectation(type, {"g","g"},  {"g","1","-1","Wm","2","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","2","-1","Wm","-1","g"})
       // random stuff (can be non-physical)
       && match_expectation(type, {"g","g"},  {"1","-2","2","-1"}) // != 2 qqx
       && match_expectation(type, {"g","g"},  {"1","-2","2","g"})  // could be qqx
       && match_expectation(type, {"e+","e-"},{"1","-1"})          // bad initial state
       && match_expectation(type, {"1","e-"}, {"g","1","Wm"})      // bad initial state
       && match_expectation(type, {"h","g"},  {"g","g"})           // bad initial state
       && match_expectation(type, {"-1","g"}, {"-1","1","1"})      // bad qqx
       && match_expectation(type, {"-1","g"}, {"1","1","-1"})      // crossing in bad qqx
       && match_expectation(type, {"-1","g"}, {"-2","1","1","Wp"}) // bad qqx
       && match_expectation(type, {"1","2"},  {"1","-1","g","g","g","2"})  // bad qqx
       && match_expectation(type, {"1","2"},  {"1","-1","-2","g","g","2"}) // gluon in bad qqx
       && match_expectation(type, {"g","g"},  {"-1","2","g","g"}) // wrong back qqx
       && match_expectation(type, {"g","g"},  {"g","g","2","1"}) // wrong forward qqx
       && match_expectation(type, {"g","g"},  {"g","-2","1","g"}) // wrong central qqx
       && match_expectation(type, {"1","g"},  {"1","-2","g","g","Wp"}) // extra quark
       && match_expectation(type, {"g","1"},  {"g","g","-2","1","Wp"}) // extra quark
       && match_expectation(type, {"g","1"},  {"g","g","Wp","-2","1"}) // extra quark
       && match_expectation(type, {"g","1"},  {"g","-2","1","g","Wp"}) // extra quark
       && match_expectation(type, {"g","g"},  {"g","g","g","-2","1","-1","Wp"}) // extra quark
       && match_expectation(type, {"1","g"},  {"g","Wp","1","-2","g"}) // extra quark
       && match_expectation(type, {"g","g"},  {"1","-1","-2","g","g","g","Wp"}) // extra quark
       ;
   }
 
   // Two boson states, that are currently not implemented
   bool check_bad_FS(){
     auto type{ HEJ::event_type::bad_final_state};
     return
          match_expectation(type, {"g","g"},  {"g","h","h","g"})
       && match_expectation(type, {"g","g"},  {"h","g","h","g"})
       && match_expectation(type, {"g","-1"}, {"g","h","Wp","-2"})
       && match_expectation(type, {"-3","-1"},{"-4","g","Wp","Wp","-2"})
       && match_expectation(type, {"-4","-1"},{"-3","Wp","g","Wm","-2"})
       && match_expectation(type, {"-4","-1"},{"g","-3","Wp","Wm","-2"})
       && match_expectation(type, {"-4","-1"},{"-4","g","11","-11","-2"})
       && match_expectation(type, {"-4","-1"},{"-4","g","-13","g","-2"})
       && match_expectation(type, {"3","-2"}, {"Wp","3","Wm","g","g","g","-2"}, -13)
     ;
   }
 
   // not 2 jets
   bool check_not_2_jets(){
     auto type{ HEJ::event_type::no_2_jets};
     return
          match_expectation(type, {"g","g"},  {})
       && match_expectation(type, {"1","-1"}, {})
       && match_expectation(type, {"g","-1"}, {"-1"})
       && match_expectation(type, {"g","g"},  {"g"})
     ;
   }
 
   // not implemented processes
   bool check_not_implemented(){
     return check_fkl(false)
         && check_uno(false)
         && check_extremal_qqx(false)
         && check_central_qqx(false);
   }
 }
 
 int main() {
   // tests for "no false negatives"
   // i.e. all HEJ-configurations get classified correctly
   if(!check_fkl()) return EXIT_FAILURE;
   if(!check_uno()) return EXIT_FAILURE;
   if(!check_extremal_qqx()) return EXIT_FAILURE;
   if(!check_central_qqx()) return EXIT_FAILURE;
   // test for "no false positive"
   // i.e. non-resummable gives non-resummable
   if(!check_non_resummable()) return EXIT_FAILURE;
   if(!check_bad_FS()) return EXIT_FAILURE;
   if(!check_not_2_jets()) return EXIT_FAILURE;
   if(!check_not_implemented()) return EXIT_FAILURE;
 
   return EXIT_SUCCESS;
 }
diff --git a/t/test_colours.cc b/t/test_colours.cc
index 5435f44..28b9546 100644
--- a/t/test_colours.cc
+++ b/t/test_colours.cc
@@ -1,352 +1,352 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <stdexcept>
 #include <utility>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/RNG.hh"
 
 #include "hej_test.hh"
 
 /// biased RNG to connect always to colour
 class dum_rnd: public HEJ::DefaultRNG {
 public:
   dum_rnd() = default;
   double flat() override {
     return 0.;
   }
 };
 
 HEJ::Event::EventData decay_boson( HEJ::Event::EventData ev ){
   for( size_t i=0; i<ev.outgoing.size(); ++i ){
     if( std::abs(ev.outgoing[i].type) == HEJ::ParticleID::Wp){
       ev.decays[i] = decay_W(ev.outgoing[i]);
     }
   }
   return ev;
 }
 
 
 void dump_event(HEJ::Event const & ev){
   for(auto const & in: ev.incoming()){
     std::cerr << "in type=" << in.type
       << ", colour={" << (*in.colour).first
       << ", " << (*in.colour).second << "}\n";
   }
   for(auto const & out: ev.outgoing()){
     std::cerr << "out type=" << out.type << ", colour={";
     if(out.colour)
       std::cerr << (*out.colour).first << ", " << (*out.colour).second;
     else
       std::cerr << "non, non";
     std::cerr << "}\n";
   }
 }
 
 /// true if colour is allowed for particle
 bool correct_colour(HEJ::Particle const & part){
   if(!HEJ::is_parton(part) && !part.colour) return true;
   if(!part.colour) return false;
   int const colour = part.colour->first;
   int const anti_colour = part.colour->second;
   if(part.type == HEJ::ParticleID::gluon)
     return colour != anti_colour
       && colour >= HEJ::COLOUR_OFFSET
       && anti_colour >= HEJ::COLOUR_OFFSET;
   if(HEJ::is_quark(part))
     return anti_colour == 0 && colour >= HEJ::COLOUR_OFFSET;
   return colour == 0 && anti_colour >= HEJ::COLOUR_OFFSET;
 }
 
 bool correct_colour(HEJ::Event const & ev){
   if(!ev.is_leading_colour())
     return false;
   // some of these additional checks are also in ev.is_leading_colour()
   for(auto const & part: ev.incoming()){
     if(!correct_colour(part))
       return false;
   }
   for(auto const & part: ev.outgoing()){
     if(!correct_colour(part))
       return false;
   }
   return true;
 }
 
 bool match_expected(
   HEJ::Event const & ev,
   std::vector<HEJ::Colour> const & expected
 ){
   ASSERT(ev.outgoing().size()+2==expected.size());
   for(size_t i=0; i<ev.incoming().size(); ++i){
     ASSERT(ev.incoming()[i].colour);
     if( *ev.incoming()[i].colour != expected[i])
       return false;
   }
   for(size_t i=2; i<ev.outgoing().size()+2; ++i){
     if( ev.outgoing()[i-2].colour ){
       if( *ev.outgoing()[i-2].colour != expected[i] )
         return false;
     } else if( expected[i].first != 0 || expected[i].second != 0)
       return false;
   }
   return true;
 }
 
 void check_event(
   HEJ::Event::EventData unc_ev,
   std::vector<HEJ::Colour> const & expected_colours
 
 ){
   unc_ev = decay_boson(std::move(unc_ev));
   shuffle_particles(unc_ev); // make sure incoming order doesn't matter
   HEJ::Event ev{unc_ev.cluster(
     fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30.)
   };
   ASSERT(HEJ::event_type::is_resummable(ev.type()));
   dum_rnd rng;
   ASSERT(!ev.is_leading_colour());
   ASSERT(ev.generate_colours(rng));
   if(!correct_colour(ev)){
     std::cerr << "Found illegal colours for event\n";
     dump_event(ev);
     throw std::invalid_argument("Illegal colour set");
   }
   if(!match_expected(ev, expected_colours)){
     std::cerr << "Colours didn't match expectation. Found\n";
     dump_event(ev);
     std::cerr << "but expected\n";
     for(auto const & col: expected_colours){
       std::cerr << "colour={" << col.first << ", " << col.second << "}\n";
     }
     throw std::logic_error("Colours did not match expectation");
   }
 }
 
 HEJ::Event::EventData reset_colour(
   HEJ::Event::EventData ev, std::vector<HEJ::Colour> const & goal
 ){
   for(size_t i=0; i<2; ++i){
     ev.incoming[i].colour = goal[i];
   }
   for(size_t i=0; i<ev.outgoing.size(); ++i){
     auto const & col_goal{ goal[i+2] };
     if(col_goal.first == 0 && col_goal.second == 0)
       ev.outgoing[i].colour = HEJ::optional<HEJ::Colour>{};
     else
       ev.outgoing[i].colour = col_goal;
   }
   return ev;
 }
 
 int main() {
   HEJ::Event::EventData ev;
   std::vector<HEJ::Colour> expected_colours(7);
 
   /// pure gluon (they all have a mass of 4 GeV to allow decays)
   ev.incoming[0] =      { HEJ::ParticleID::gluon, {  0,   0, -205, 205}, {}};
   ev.incoming[1] =      { HEJ::ParticleID::gluon, {  0,   0,  279, 279}, {}};
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-15, -82,  -82, 117}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, { 68,  93,   20, 117}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::higgs, {-30, -65,   22,  75}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-12,  92,   76, 120}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-11, -38,   38,  55}, {}});
 
   expected_colours[0] = {502, 501};
   expected_colours[1] = {509, 502};
   expected_colours[2] = {503, 501};
   expected_colours[3] = {505, 503};
   expected_colours[4] = {  0,   0};
   expected_colours[5] = {507, 505};
   expected_colours[6] = {509, 507};
   // default colours is always forbidden!
   // default: swap last two (anti-)colour -> crossing
   ev=reset_colour(ev, expected_colours);
   std::swap(ev.outgoing[4].colour, ev.outgoing[3].colour);
   check_event(ev, expected_colours);
 
   /// last g to Qx (=> gQx -> g ... Qx)
   ev.incoming[1].type = HEJ::ParticleID::d_bar;
   ev.outgoing[4].type = HEJ::ParticleID::d_bar;
   // => only end changes
   expected_colours[1].first = 0;
   expected_colours[6].first = 0;
   // default: swap last two anti-colours -> last gluon colour singlet
   ev=reset_colour(ev, expected_colours);
   std::swap(ev.outgoing[4].colour->second, ev.outgoing[3].colour->second);
   check_event(ev, expected_colours);
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// uno forward (=> gQx -> g ... Qx g)
     std::swap(new_ev.outgoing[3].type, new_ev.outgoing[4].type);
     // => uno quarks eats colour and gluon connects to anti-colour
     new_expected[5] = {0, expected_colours[3].first};
     new_expected[6] = {expected_colours[0].first, expected_colours[0].first+2};
     new_expected[1].second += 2; // one more anti-colour in line
     // default: swap last two anti-colours -> crossing
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.outgoing[4].colour->second, new_ev.outgoing[3].colour->second);
     check_event(new_ev, new_expected);
   }
 
   /// swap Qx <-> Q (=> gQ -> g ... Q)
   ev.incoming[1].type = HEJ::ParticleID::d;
   ev.outgoing[4].type = HEJ::ParticleID::d;
   // => swap: colour<->anti && initial<->final
   std::swap(expected_colours[1], expected_colours[6]);
   std::swap(expected_colours[1].first, expected_colours[1].second);
   std::swap(expected_colours[6].first, expected_colours[6].second);
   // default: swap incoming <-> outgoing
   ev=reset_colour(ev, expected_colours);
   std::swap(ev.incoming[0].colour, ev.outgoing[0].colour);
   check_event(ev, expected_colours);
 
   /// first g to qx (=> qxQ -> qx ... Q)
   ev.incoming[0].type = HEJ::ParticleID::u_bar;
   ev.outgoing[0].type = HEJ::ParticleID::u_bar;
   expected_colours[0] = {  0, 501};
   // => shift anti-colour index one up
   expected_colours[1].first -= 2;
   expected_colours[5] = expected_colours[3];
   expected_colours[3] = expected_colours[2];
   expected_colours[2] = {  0, 502};
   // default: closed qx->qx g
   ev=reset_colour(ev, expected_colours);
   ev.outgoing[1].colour->first = ev.outgoing[0].colour->second;
   ev.outgoing[1].colour->second = ev.incoming[0].colour->second;
   ev.outgoing[4].colour->first = ev.outgoing[3].colour->second;
   check_event(ev, expected_colours);
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// uno backward (=> qxQ -> g qx ... Q)
     std::swap(new_ev.outgoing[0].type, new_ev.outgoing[1].type);
     // => uno gluon connects to quark colour
     new_expected[3] = expected_colours[2];
     new_expected[2] = {expected_colours[0].second+2, expected_colours[0].second};
     // default: Colourful Higgs
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.outgoing[2].colour = std::make_pair(1,1);
     check_event(new_ev, new_expected);
 
     /// swap qx <-> q (=> qQ -> g q ... Q)
     new_ev.incoming[0].type = HEJ::ParticleID::u;
     new_ev.outgoing[1].type = HEJ::ParticleID::u;
     // => swap: colour<->anti && inital<->final
     std::swap(new_expected[0], new_expected[3]);
     std::swap(new_expected[0].first, new_expected[0].second);
     std::swap(new_expected[3].first, new_expected[3].second);
     // => & connect first gluon with remaining anti-colour
     new_expected[2] = {new_expected[0].first, new_expected[0].first+2};
     // shift colour line one down
     new_expected[1].first-=2;
     new_expected[5].first-=2;
     new_expected[5].second-=2;
     // shift anti-colour line one up
     new_expected[6].first+=2;
     // default: swap 2 quarks -> disconnected
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.outgoing[1].colour, new_ev.outgoing[4].colour);
     check_event(new_ev, new_expected);
   }
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// uno forward (=> qxQ -> qx ... Q g)
     std::swap(new_ev.outgoing[3].type, new_ev.outgoing[4].type);
     // => uno gluon connects to remaining colour
     new_expected[5] = expected_colours[6];
     new_expected[6] = {expected_colours[3].first+2, expected_colours[3].first};
     // default: no colour on last gluon
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.incoming[1].colour->first = new_ev.outgoing[4].colour->second;
     new_ev.outgoing[4].colour = {};
     check_event(new_ev, new_expected);
   }
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// qqx backward (=> gQ -> qx q ... Q) with Wp
     // => swap: incoming q <-> outgoing gluon
     std::swap(new_ev.incoming[0].type, new_ev.outgoing[1].type);
     new_ev.outgoing[1].type=static_cast<HEJ::ParticleID>(
       -(new_ev.outgoing[1].type+1) );
     new_ev.outgoing[2].type = HEJ::ParticleID::Wp;
     // incoming q -> outgoing q (colour<->anti)
     std::swap(new_expected[0], new_expected[3]);
     std::swap(new_expected[3].first, new_expected[3].second);
     new_expected[3].first+=2;
     new_expected[0].first-=1; // skip one index
     // couple first in to first out
     new_expected[2].second=new_expected[0].second;
     // default: swap qqx <-> first g
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.outgoing[0].colour->second, new_ev.outgoing[3].colour->second);
     std::swap(new_ev.outgoing[1].colour->first, new_ev.outgoing[3].colour->first);
     check_event(new_ev, new_expected);
   }
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// qqx forward (=> qx g -> qx ... Qx Q) with Wp
     // => swap: incoming Q <-> outgoing gluon
     std::swap(new_ev.incoming[1].type, new_ev.outgoing[3].type);
     new_ev.outgoing[3].type=static_cast<HEJ::ParticleID>(
       -(new_ev.outgoing[3].type+1));
     new_ev.outgoing[2].type = HEJ::ParticleID::Wp;
     // incoming q -> outgoing q (colour<->anti)
     std::swap(new_expected[1], new_expected[5]);
     std::swap(new_expected[5].first, new_expected[5].second);
     new_expected[5].second-=2;
     new_expected[1].second-=1; // skip one index
     // couple last in to last out
     new_expected[6].first=new_expected[1].first;
     // default: uncoloured quark
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.outgoing[0].colour = {};
     check_event(new_ev, new_expected);
 
     // move Higgs to position 1 (=> qx g -> qx h g Qx Q)
     std::swap(new_ev.outgoing[1].type, new_ev.outgoing[2].type);
     std::swap(new_expected[3], new_expected[4]); // trivial
     // default: incoming qx wrong colour
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.incoming[0].colour->first  = 1;
     check_event(new_ev, new_expected);
 
     // central qqx (=> qx g -> qx h Q Qx g)
     // => swap: Q <-> g
     std::swap(new_ev.outgoing[2].type, new_ev.outgoing[4].type);
     std::swap(new_expected[4], new_expected[6]);
     // gluon was connected on left side, i.e. doesn't matter for QQx
     // => couple Q to out qx
     new_expected[4].first = new_expected[2].second;
     // Qx next in line
     new_expected[5].second = new_expected[4].first+2;
     // incoming g shifted by one position in line
     new_expected[1].first-=2;
     new_expected[1].second+=2;
     // default: wrong colour in last incoming
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.incoming[1].colour->first,
               new_ev.incoming[1].colour->second);
     check_event(new_ev, new_expected);
   }
 
   return EXIT_SUCCESS;
 }
diff --git a/t/test_hdf5_write.cc b/t/test_hdf5_write.cc
index 1fe7ed5..1f03a36 100644
--- a/t/test_hdf5_write.cc
+++ b/t/test_hdf5_write.cc
@@ -1,77 +1,77 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 
 #include <iostream>
 #include <unistd.h>
 
 #include "HEJ/Event.hh"
 #include "HEJ/HDF5Reader.hh"
 #include "HEJ/make_writer.hh"
 #include "HEJ/utility.hh"
 
 #include "hej_test.hh"
 
 namespace {
   const fastjet::JetDefinition jet_def{fastjet::JetAlgorithm::antikt_algorithm, 0.4};
   const double min_jet_pt{20.};
 }
 
 int main(int argc, char** argv) {
   if(argc != 3) {
     std::cerr << "Usage: " << argv[0] << " in_file.hdf5 out_file.hdf5\n";
     return EXIT_FAILURE;
   }
 
   std::vector<HEJ::Event> events;
   size_t max_nevent = 15299;
 
   // this scope is needed to trigger the HEJ::HDF5Writer destructor
   // don't remove it
   {
     auto reader = HEJ::make_reader(argv[1]);
 
     auto writer = HEJ::make_format_writer(
         HEJ::FileFormat::HDF5, argv[2], reader->heprup()
     );
 
     size_t nevent = 0;
     while(reader->read_event()) {
       ++nevent;
       const auto event = HEJ::Event::EventData{reader->hepeup()}.cluster(
         jet_def, min_jet_pt
       );
       events.emplace_back(event);
       writer->write(event);
       if(nevent>=max_nevent)
         break;
     }
     max_nevent = std::min(nevent,max_nevent);
   }
 
   HEJ::HDF5Reader reader{argv[2]};
   size_t nevent = 0;
   for(auto const & event: events) {
     ++nevent;
     reader.read_event();
     const auto ev = HEJ::Event::EventData{reader.hepeup()}.cluster(
         jet_def, min_jet_pt
     );
     ASSERT_PROPERTY(ev, event, incoming().size());
     for(size_t i = 0; i < ev.incoming().size(); ++i) {
       ASSERT(HEJ::nearby(ev.incoming()[i].p, event.incoming()[i].p));
     }
     ASSERT_PROPERTY(ev, event, outgoing().size());
     for(size_t i = 0; i < ev.outgoing().size(); ++i) {
       ASSERT(HEJ::nearby(ev.outgoing()[i].p, event.outgoing()[i].p));
     }
     ASSERT_PROPERTY(ev, event, decays().size());
     ASSERT_PROPERTY(ev, event, type());
     ASSERT_PROPERTY(ev, event, central().weight);
   }
   ASSERT(nevent == max_nevent);
 
   return EXIT_SUCCESS;
 }
diff --git a/t/test_jet_cuts.cc b/t/test_jet_cuts.cc
index 7abc0eb..7208eec 100644
--- a/t/test_jet_cuts.cc
+++ b/t/test_jet_cuts.cc
@@ -1,302 +1,302 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <iostream>
 
 #include "hej_test.hh"
 
 namespace {
   const fastjet::JetDefinition jet_def{fastjet::JetAlgorithm::antikt_algorithm, 0.4};
   const double min_jet_pt{30.};
   const double max_ext_soft_pt_fraction{0.4};
 
   bool correct( bool const expected,
     std::array<std::string,2> const & in, std::vector<std::string> const & out,
       int const overwrite_boson
   ){
     HEJ::Event ev{ parse_configuration(
                       in,out,overwrite_boson ).cluster(jet_def, min_jet_pt)};
     ASSERT(is_resummable(ev.type())); // only test jet configurations
     if(ev.valid_hej_state(max_ext_soft_pt_fraction) != expected){
       std::cerr << "Expected " << (expected?"valid":"invalid")
                 << " but found "
                 << (ev.valid_hej_state(max_ext_soft_pt_fraction)?"valid":"invalid")
                 << " jets (" << overwrite_boson << ")\n" << ev;
       auto jet_idx{ ev.particle_jet_indices() };
       std::cout << "Particle Jet indices: ";
       for(int const i: jet_idx)
         std::cout << i << " ";
       std::cout << std::endl;
       return false;
     }
     return true;
   }
 
   // valid jet configurations
   bool valid_jets(){
     // FKL
     // extremal parton inside jet
     for(int i=-3; i>-13;--i){
       std::array<std::string,2> base_in{"g","g"};
       std::vector<std::string> base_out(7, "g");
       if(!correct(true, base_in, base_out, i))
         return false;
     }
     std::cout << __LINE__ <<std::endl;
     // replace one of the extremal with a boson
     if( !( correct(true,{"g","d"},{"g","g","g","g","g","d","h"}, -13)
         && correct(true,{"d","d"},{"d","g","g","g","g","d","h"}, -13)
         && correct(true,{"2","2"},{"1","g","g","g","g","2","Wp"},-14)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     // uno
     if( !( correct(true,{"g","3"},{"h","g","g","g","g","3","g"},  -1)
         && correct(true,{"3","2"},{"Wp","g","3","g","g","g","1"}, -1)
         && correct(true,{"1","2"},{"Wm","g","2","g","g","g","2"}, -1)
         && correct(true,{"1","3"},{"1","g","g","g","g","3","g"},  -3)
         && correct(true,{"-1","3"},{"-1","g","g","g","g","3","g"},-5)
         && correct(true,{"3","-2"},{"g","3","g","g","g","g","-2"},-5)
         && correct(true,{"-3","3"},{"g","-3","g","g","g","g","3"},-6)
         && correct(true,{"-4","3"},{"-4","g","g","g","g","3","g"},-7)
         && correct(true,{"3","-5"},{"g","3","g","g","g","g","-5"},-7)
         && correct(true,{"g","3"},{"g","g","g","g","g","3","g"},  -8)
         && correct(true,{"3","g"},{"g","3","g","g","g","g","g"},  -8)
         && correct(true,{"2","3"},{"g","1","g","Wp","g","g","3"}, -9)
         && correct(true,{"2","3"},{"1","g","g","g","3","Wp","g"}, -9)
         && correct(true,{"2","3"},{"1","g","Wp","g","g","3","g"}, -10)
         && correct(true,{"3","g"},{"g","3","g","g","g","g","g"},  -11)
         && correct(true,{"1","3"},{"1","g","g","g","g","3","g"},  -12)
         && correct(true,{"3","2"},{"g","3","g","g","g","g","2"},  -12)
         && correct(true,{"3","g"},{"g","3","g","g","g","g","h"},  -13)
         && correct(true,{"2","3"},{"1","g","g","g","3","g","Wp"}, -13)
         && correct(true,{"2","3"},{"g","1","g","g","g","3","Wp"}, -14)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     // extremal qqx
     if( !( correct(true,{"2","g"},{"1","Wp","g","g","g","1","-1"}, -3)
         && correct(true,{"2","g"},{"1","Wp","g","g","g","1","-1"}, -5)
         && correct(true,{"g","2"},{"1","-1","g","Wp","g","g","1"}, -5)
         && correct(true,{"2","g"},{"1","g","g","g","Wp","1","-1"}, -7)
         && correct(true,{"g","2"},{"1","-1","g","g","g","Wp","1"}, -7)
         && correct(true,{"2","g"},{"1","Wp","g","g","g","1","-1"}, -8)
         && correct(true,{"g","2"},{"1","-1","Wp","g","g","g","1"}, -8)
         && correct(true,{"g","2"},{"1","-1","g","Wp","g","g","1"}, -9)
         && correct(true,{"g","3"},{"-2","1","g","Wp","g","g","3"}, -9)
         && correct(true,{"2","g"},{"1","g","g","g","3","Wp","-3"}, -9)
         && correct(true,{"2","g"},{"1","g","Wp","g","g","-3","3"}, -10)
         && correct(true,{"2","g"},{"1","g","g","g","Wp","1","-1"}, -12)
         && correct(true,{"g","2"},{"1","-1","g","Wp","g","g","1"}, -12)
         && correct(true,{"2","g"},{"1","g","g","g","-3","3","Wp"}, -13)
         && correct(true,{"g","g"},{"-2","1","g","g","g","g","Wp"}, -14)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     // central qqx
     if( !( correct(true,{"1","g"},{"2","g","-2","Wm","2","g","g"}, -3)
         && correct(true,{"1","g"},{"2","g","g","-2","2","Wm","g"}, -4)
         && correct(true,{"1","g"},{"2","g","g","Wm","-2","2","g"}, -5)
         && correct(true,{"1","g"},{"2","g","g","Wm","-2","2","g"}, -8)
         && correct(true,{"1","g"},{"2","Wm","g","-2","2","g","g"}, -9)
         && correct(true,{"1","g"},{"2","Wm","-2","2","g","g","g"}, -9)
         && correct(true,{"1","g"},{"2","g","g","-2","2","Wm","g"}, -10)
         && correct(true,{"1","g"},{"2","g","Wm","2","-2","g","g"}, -11)
         && correct(true,{"1","g"},{"2","g","g","-2","2","g","Wm"}, -12)
         && correct(true,{"1","g"},{"2","2","-2","g","g","g","Wm"}, -13)
         && correct(true,{"1","g"},{"2","2","-2","g","g","g","Wm"}, -14)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     return true;
   }
 
   // invalid jet configurations
   bool invalid_jets(){
     std::cout << __LINE__ <<std::endl;
     // not implemented extremal qqx for pure jets
     // if( !( true
     //     && correct(false,{"2","g"}, {"2","g","g","g","g","2","-2"},  -1)
     //     && correct(false,{"g","-2"},{"4","-4","g","g","g","g","-2"}, -2)
     //     // qqx backward not in jet
     //     && correct(false,{"g","g"}, {"g","g","g","g","g","-2","2"},  -2)
     //     // qqx backward in same jet
     //     && correct(false,{"g","2"}, {"-4","4","g","g","g","g","2"},  -3)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -3)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -4)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -5)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -5)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -6)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -7)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -7)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -8)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -8)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -9)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -10)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -11)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -11)
     //     && correct(false,{"-3","g"},{"-3","g","g","g","g","-3","3"}, -12)
     //     && correct(false,{"g","-3"},{"-3","3","g","g","g","g","-3"}, -12)
     // ))
     //   return false;
     std::cout << __LINE__ <<std::endl;
 
     // not implemented central qqx for pure jets
     // if( !( true
     //     && correct(false,{"-1","g"},{"-1","1","-1","g","g","g","g"}, -1)
     //     && correct(false,{"4","-3"},{"4","g","g","1","-1","g","-3"}, -2)
     //     && correct(false,{"1","-1"},{"1","g","-1","1","g","g","-1"}, -3)
     //     && correct(false,{"1","-1"},{"1","g","g","-1","1","g","-1"}, -3)
     //     && correct(false,{"1","-1"},{"1","g","g","-1","1","g","-1"}, -4)
     //     && correct(false,{"1","-1"},{"1","-1","1","g","g","g","-1"}, -4)
     //     && correct(false,{"1","-1"},{"1","g","g","g","-1","1","-1"}, -5)
     //     && correct(false,{"1","-1"},{"1","g","g","-1","1","g","-1"}, -6)
     //     && correct(false,{"1","-1"},{"1","g","-1","1","g","g","-1"}, -7)
     //     && correct(false,{"1","-1"},{"1","g","g","g","-1","1","-1"}, -7)
     //     && correct(false,{"1","-1"},{"1","-1","1","g","g","g","-1"}, -8)
     //     && correct(false,{"1","-1"},{"1","g","g","g","-1","1","-1"}, -8)
     //     && correct(false,{"1","-1"},{"1","-1","1","g","g","g","-1"}, -9)
     //     && correct(false,{"1","-1"},{"1","g","-1","1","g","g","-1"}, -9)
     //     && correct(false,{"1","-1"},{"1","g","g","-1","1","g","-1"}, -10)
     //     && correct(false,{"1","-1"},{"1","g","g","g","-1","1","-1"}, -10)
     //     && correct(false,{"1","-1"},{"1","g","g","g","-1","1","-1"}, -11)
     //     && correct(false,{"1","-1"},{"1","g","g","-1","1","g","-1"}, -12)
     //     && correct(false,{"1","-1"},{"1","g","g","g","-1","1","-1"}, -12)
     //   ))
     //   return false;
     std::cout << __LINE__ <<std::endl;
 
     // implemented processes failing jet cuts
     // FKL
     if( !( true
         && correct(false,{"1","g"}, {"g","1","g","g","g","g","g"},   -1)
         // FKL not in own jet
         && correct(false,{"1","1"}, {"1","g","g","g","g","g","1"},   -1)
         && correct(false,{"g","g"}, {"g","g","g","g","g","g","g"},   -2)
         && correct(false,{"1","-3"},{"1","g","g","g","g","-3","g"},  -1)
         && correct(false,{"2","g"}, {"g","2","g","g","g","g","g"},   -2)
         // FKL below pt
         && correct(false,{"1","1"}, {"1","g","g","g","g","g","1"},  -13)
         && correct(false,{"g","3"},{"h","g","g","g","g","3","g"},  -13)
         && correct(false,{"g","1"},{"Wm","g","g","g","g","2","g"}, -13)
         && correct(false,{"3","1"},{"Wm","g","3","g","g","g","2"}, -13)
         // uno in same jet as FKL
         && correct(false,{"-1","1"},{"-1","g","g","g","g","1","g"}, -9)
         && correct(false,{"3","3"}, {"g","3","g","g","g","g","3"},  -9)
         && correct(false,{"-1","1"},{"g","-1","g","g","g","g","1"}, -10)
         && correct(false,{"-1","1"},{"-1","g","g","g","g","1","g"}, -13)
         && correct(false,{"-1","1"},{"g","-1","g","g","g","g","1"}, -13)
         // FKL not in jet & uno other side
         && correct(false,{"2","3"},{"Wp","1","g","g","g","3","g"}, -15)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     // uno
     if( !( true
         // uno backward not in jet
         && correct(false,{"1","2"}, {"g","1","g","g","g","g","2"},  -1)
         // uno forward not in jet
         && correct(false,{"3","3"}, {"3","g","g","g","g","3","g"},   -2)
         // uno backward in same jet
         && correct(false,{"1","g"}, {"g","1","g","g","g","g","g"},   -3)
         // uno forward in same jet
         && correct(false,{"3","2"}, {"3","g","g","g","g","2","g"},   -4)
         // uno backward below pt
         && correct(false,{"3","g"}, {"g","3","g","g","g","g","g"},   -4)
         // uno forward below pt
         && correct(false,{"4","3"}, {"4","g","g","g","g","3","g"},  -10)
         && correct(false,{"2","3"}, {"1","g","g","g","3","g","Wp"}, -14)
         // uno forward not in jet
         && correct(false,{"5","3"}, {"5","g","g","g","g","3","g"},  -11)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     // extremal qqx
     if( !( true
         // qqxf in same jet
         && correct(false,{"g","g"}, {"Wm","g","g","g","g","-1","2"},  -4)
         // qqxb below pt
         && correct(false,{"g","2"},{"1","-1","g","Wp","g","g","1"},  -4)
         // central qqx not in jet
         && correct(false,{"1","2"}, {"1","g","-2","2","Wp","g","1"},  -5)
         // central qqx in same jet
         && correct(false,{"1","2"}, {"1","-2","2","g","Wp","g","1"},  -6)
         // central qqx in same jet
         && correct(false,{"1","2"}, {"1","Wm","g","g","2","-1","2"},  -6)
         // qqxf below pt
         && correct(false,{"2","g"},{"1","g","Wp","g","g","1","-1"}, -6)
         // central qqx in same jet
         && correct(false,{"1","2"}, {"1","-1","1","g","g","Wp","1"},  -7)
         // central qqx in same jet
         && correct(false,{"1","2"}, {"1","Wp","g","3","-3","g","1"},  -7)
         // central qqx in same jet
         && correct(false,{"g","3"}, {"g","Wp","-2","1","g","g","3"},  -8)
         // central qqx in same jet
         && correct(false,{"g","-2"},{"Wm","g","g","2","-1","g","-2"}, -8)
         // qqxf below pt
         && correct(false,{"2","g"},{"1","g","g","g","Wp","1","-1"}, -10)
         // qqxf outside jet
         && correct(false,{"2","g"},{"1","g","g","g","Wp","1","-1"}, -11)
         // extremal qqx in same jet as FKL
         && correct(false,{"-1","g"},{"-1","g","Wm","g","g","2","-1"},-9)
         && correct(false,{"g","1"}, {"2","-1","g","g","g","Wm","1"}, -10)
         // extraml qqx below pt
         && correct(false,{"-1","g"},{"-1","Wm","g","g","g","-1","2"},-13)
         && correct(false,{"g","g"}, {"g","g","g","g","-1","Wm","2"}, -14)
         && correct(false,{"g","g"}, {"-1","2","g","g","g","g","Wm"}, -15)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     // central qqx
     if( !( true
         && correct(false,{"1","g"}, {"2","Wm","-2","2","g","g","g"},  -3)
         && correct(false,{"1","g"}, {"2","g","Wm","-2","2","g","g"},  -3)
         && correct(false,{"1","g"}, {"2","-2","2","g","Wm","g","g"},  -4)
         && correct(false,{"-1","g"},{"-1","g","g","Wp","1","-2","g"}, -4)
         && correct(false,{"-1","g"},{"-1","1","-2","g","g","Wp","g"}, -5)
         && correct(false,{"-1","g"},{"-1","g","1","-2","g","Wp","g"}, -5)
         && correct(false,{"-1","g"},{"-1","g","g","1","-2","Wp","g"}, -5)
         && correct(false,{"1","g"}, {"2","g","g","Wm","2","-2","g"},  -6)
         && correct(false,{"1","g"}, {"2","g","Wm","-2","2","g","g"},  -6)
         && correct(false,{"g","4"}, {"g","g","-4","4","g","Wp","3"},  -6)
         && correct(false,{"1","g"}, {"2","g","Wm","g","-2","2","g"},  -7)
         && correct(false,{"g","4"}, {"g","g","-4","4","g","Wp","3"},  -7)
         && correct(false,{"g","4"}, {"g","Wp","g","-4","4","g","3"},  -7)
         && correct(false,{"1","g"}, {"2","-2","2","Wm","g","g","g"},  -8)
         && correct(false,{"g","4"}, {"g","-4","4","g","Wp","g","3"},  -8)
         && correct(false,{"g","g"}, {"g","g","g","g","-2","1","Wp"},  -9)
         && correct(false,{"1","g"}, {"2","-2","2","g","Wm","g","g"},  -9)
         && correct(false,{"1","g"}, {"2","g","-2","2","g","Wm","g"},  -9)
         && correct(false,{"1","g"}, {"2","g","g","Wm","-2","2","g"},  -10)
         && correct(false,{"g","g"}, {"g","1","-2","Wp","g","g","g"},  -10)
         && correct(false,{"g","1"}, {"g","g","Wp","g","-2","1","1"},  -11)
         && correct(false,{"1","g"}, {"2","g","Wm","g","-2","2","g"},  -11)
         && correct(false,{"1","g"}, {"2","Wm","g","-2","2","g","g"},  -12)
         && correct(false,{"3","2"}, {"3","g","-1","2","Wm","g","2"},  -12)
         && correct(false,{"3","-2"},{"3","-1","2","g","g","Wm","-2"}, -13)
         && correct(false,{"3","-2"},{"3","-1","2","g","g","Wm","-2"}, -14)
         && correct(false,{"3","-2"},{"3","g","g","-1","2","Wm","-2"}, -14)
         && correct(false,{"1","g"}, {"Wm","2","2","-2","g","g","g"},  -15)
         && correct(false,{"3","-2"},{"3","-1","2","g","g","-2","Wm"}, -15)
     ))
       return false;
     std::cout << __LINE__ <<std::endl;
 
     return true;
   }
 }
 
 int main() {
   if(!valid_jets()) return EXIT_FAILURE;
   if(!invalid_jets()) return EXIT_FAILURE;
   return EXIT_SUCCESS;
 }
diff --git a/t/test_scale_arithmetics.cc b/t/test_scale_arithmetics.cc
index 268fc9e..19bb514 100644
--- a/t/test_scale_arithmetics.cc
+++ b/t/test_scale_arithmetics.cc
@@ -1,95 +1,95 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include <fstream>
 #include <algorithm>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/EventReweighter.hh"
 #include "HEJ/make_RNG.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/YAMLreader.hh"
 #include "HEJ/stream.hh"
 
 #include "hej_test.hh"
 
 constexpr double ep = 1e-13;
 
 void dump(HEJ::Event const & ev){
   {
     LHEF::Writer writer{std::cout};
     std::cout << std::setprecision(6);
     writer.hepeup = to_HEPEUP(std::move(ev), nullptr);
     writer.writeEvent();
   }
   std::cout << "Rapidity ordering:\n";
   for(const auto & part: ev.outgoing()){
     std::cout << std::setw(2) << part.type << ": "<<  std::setw(7) << part.rapidity() << std::endl;
   }
 }
 
 int main(int argn, char** argv){
   if(argn != 3){
     std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml input_file.lhe\n\n";
     return EXIT_FAILURE;
   }
   HEJ::Config config = HEJ::load_config(argv[1]);
   config.scales = HEJ::to_ScaleConfig(
       YAML::Load("scales: [H_T, 1 * H_T, 2/2 * H_T, 2*H_T/2, H_T/2*2, H_T/2/2*4, H_T*H_T/H_T]")
   );
 
   HEJ::istream in{argv[2]};
   LHEF::Reader reader{in};
 
   std::shared_ptr<HEJ::RNG> ran{
     HEJ::make_RNG(config.rng.name, config.rng.seed)};
 
   HEJ::ScaleGenerator scale_gen{
     config.scales.base,
     config.scales.factors,
     config.scales.max_ratio
   };
 
   HEJ::EventReweighter resum{
     reader.heprup,
     std::move(scale_gen),
     to_EventReweighterConfig(config),
     ran
   };
 
   size_t i = 0;
   while(reader.readEvent()){
     ++i;
 
     HEJ::Event::EventData data{reader.hepeup};
     shuffle_particles(data);
 
     HEJ::Event event{
       data.cluster(
         config.resummation_jets.def,
         config.resummation_jets.min_pt
       )
     };
 
     auto resummed = resum.reweight(event, config.trials);
     for(auto && ev: resummed) {
       for(auto &&var: ev.variations()) {
         if(std::abs(var.muf - ev.central().muf) > ep) {
           std::cerr
             << std::setprecision(15)
             << "unequal scales: " << var.muf
             << " != " << ev.central().muf << '\n'
             << "in resummed event:\n";
           dump(ev);
           std::cerr << "\noriginal event:\n";
           dump(event);
           return EXIT_FAILURE;
         }
       }
     }
   }
   return EXIT_SUCCESS;
 }
diff --git a/t/test_unweighter.cc b/t/test_unweighter.cc
index 6c880e2..9f39a55 100644
--- a/t/test_unweighter.cc
+++ b/t/test_unweighter.cc
@@ -1,152 +1,152 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
+ *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 
 #include "HEJ/CrossSectionAccumulator.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/EventReader.hh"
 #include "HEJ/Mixmax.hh"
 #include "HEJ/Unweighter.hh"
 
 #include "hej_test.hh"
 
 namespace{
   const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4};
   const double min_pt{30.};
   const double sample_ratio{10.};
   const double max_dev{2.};
 
   bool compare_xs(
     HEJ::XSWithError<double> const & xs1, HEJ::XSWithError<double> const & xs2
   ){
     std::cout << xs1.value << "+/-" << xs1.error << " vs. "
       << xs2.value << "+/-" << xs2.error << std::endl;
     return std::abs(xs1.value/xs2.value-1.)<xs1.error;
   }
 }
 int main(int argc, char** argv) {
   if(argc != 2) {
     std::cerr << "Usage: " << argv[0] << " event_file\n";
     return EXIT_FAILURE;
   }
   std::string file{argv[1]};
   auto reader = HEJ::make_reader(file);
 
   // number of events
   auto nevents{reader->number_events()};
   if(!nevents) {
     auto t_reader = HEJ::make_reader(file);
     nevents = 0;
     while(t_reader->read_event()) ++(*nevents);
   }
   ASSERT(*nevents>sample_ratio);
   const size_t size_sample = floor(*nevents/sample_ratio);
   HEJ::Mixmax ran{};
 
   // no unweighting
   HEJ::CrossSectionAccumulator xs_base;
 
   std::vector<HEJ::Event> all_evts;
   // full unweighting
   HEJ::CrossSectionAccumulator xs_max;
   HEJ::Unweighter unw_max;
   size_t n_max{0};
   // midpoint on full sample
   HEJ::CrossSectionAccumulator xs_mid;
   HEJ::Unweighter unw_mid;
   size_t n_mid{0};
 
   // calc max from partial sample
   HEJ::CrossSectionAccumulator xs_pmax;
   HEJ::Unweighter unw_pmax;
   size_t n_pmax{0};
   // midpoint on partial sample
   HEJ::CrossSectionAccumulator xs_pmid;
   HEJ::Unweighter unw_pmid;
   size_t n_pmid{0};
 
   // initialise sample
   for(size_t n = 0; n < size_sample; ++n){
     if(!reader->read_event()){
       std::cerr << "Sample size bigger than event sample\n";
       return EXIT_FAILURE;
     }
     const HEJ::Event event{
       HEJ::Event::EventData{reader->hepeup()}.cluster(jet_def, min_pt)
     };
 
     xs_base.fill(event);
     all_evts.push_back(event);
   }
 
   // calculate partial settings
   unw_pmax.set_cut_to_maxwt(all_evts);
   unw_pmid.set_cut_to_peakwt(all_evts, max_dev);
 
   for(auto const & ev: unw_pmax.unweight(all_evts, ran)){
     xs_pmax.fill(ev);
     ++n_pmax;
   }
   for(auto const & ev: unw_pmid.unweight(all_evts, ran)){
     xs_pmid.fill(ev);
     ++n_pmid;
   }
 
   while(reader->read_event()){
     const HEJ::Event event{
       HEJ::Event::EventData{reader->hepeup()}.cluster(jet_def, min_pt)
     };
     xs_base.fill(event);
     auto ev{ unw_pmid.unweight(event, ran) };
     if(ev){
       xs_pmid.fill(*ev);
       ++n_pmid;
     }
     ev = unw_pmax.unweight(event, ran);
     if(ev){
       xs_pmax.fill(*ev);
       ++n_pmax;
     }
     all_evts.push_back(event);
   }
 
   unw_max.set_cut_to_maxwt(all_evts);
   unw_mid.set_cut_to_peakwt(all_evts, max_dev);
 
   for(auto const & ev: unw_max.unweight(all_evts, ran)){
     // make sure all the events have the same weight
     ASSERT( std::abs( std::abs(unw_max.get_cut()/ev.central().weight)-1. ) < 10e-16);
     xs_max.fill(ev);
     ++n_max;
   }
   for(auto const & ev: unw_mid.unweight(all_evts, ran)){
     xs_mid.fill(ev);
     ++n_mid;
   }
 
   // sanity check number of events
   ASSERT( all_evts.size() > 0);
   ASSERT( n_pmax > 0);
   ASSERT( n_max > 0);
   ASSERT( n_pmid > 0);
   ASSERT( n_mid > 0);
   ASSERT( n_pmax < all_evts.size() );
   ASSERT( n_max  < n_pmax );
   ASSERT( n_pmid < all_evts.size() );
   ASSERT( n_mid  < all_evts.size() );
   ASSERT( n_max  < n_mid );
 
   std::cout << "all_evts.size() " << all_evts.size() << " n_pmax " << n_pmax <<
   " n_max " << n_max << " n_pmid " << n_pmid << " n_mid " << n_mid << std::endl;
 
   // control xs (in circle)
   ASSERT(compare_xs( xs_base.total(), xs_pmax.total() ));
   ASSERT(compare_xs( xs_pmax.total(), xs_max.total()  ));
   ASSERT(compare_xs( xs_max.total() , xs_pmid.total() ));
   ASSERT(compare_xs( xs_pmid.total(), xs_mid.total()  ));
   ASSERT(compare_xs( xs_mid.total() , xs_base.total() ));
 
   return EXIT_SUCCESS;
 }