diff --git a/FixedOrderGen/configFO.yml b/FixedOrderGen/configFO.yml
index b820534..4f228fd 100644
--- a/FixedOrderGen/configFO.yml
+++ b/FixedOrderGen/configFO.yml
@@ -1,63 +1,64 @@
 # number of generated events
 events: 200
 
 jets:
   min pt: 20
   peak pt: 30
   algorithm: antikt
   R: 0.4
   max rapidity: 5
 
 beam:
   energy: 6500
   particles: [p, p]
 
 pdf: 230000
 
 process: p p => h 4j
 
 # fraction of events with two extremal emissions in one direction
 # that contain an unordered emission
 unordered fraction: 0.05
 
 scales: max jet pperp
 
 event output:
   - HEJFO.lhe
 #  - HEJ.lhe
 #  - HEJ_events.hepmc
 
-Higgs properties:
-  mass: 125
-  width: 0.004165
-  decays: {into: [photon, photon], branching ratio: 0.0023568762400521404}
+Particle properties:
+  Higgs:
+    mass: 125
+    width: 0.004165
+    decays: {into: [photon, photon], branching ratio: 0.0023568762400521404}
 
 random generator:
   name: mixmax
 
 # unweighting parameters
 # remove to obtain weighted events
 unweight:
   sample size: 200  # should be similar to "events:", but not more than ~10000
   max deviation: 0
 
 # to use a rivet analysis
 #
 # analysis:
 #   rivet: MC_XS # rivet analysis name
 #   output: HEJ # name of the yoda files, ".yoda" and scale suffix will be added
 #
 # to use a custom analysis
 #
 # analysis:
 #   plugin: /path/to/libmyanalysis.so
 #   my analysis parameter: some value
 
 # parameters for Higgs-gluon couplings
 # this requires compilation with qcdloop
 #
 # Higgs coupling:
 #   use impact factors: false
 #   mt: 174
 #   include bottom: true
 #   mb: 4.7
diff --git a/FixedOrderGen/include/EventGenerator.hh b/FixedOrderGen/include/EventGenerator.hh
index 986b404..7866fbc 100644
--- a/FixedOrderGen/include/EventGenerator.hh
+++ b/FixedOrderGen/include/EventGenerator.hh
@@ -1,53 +1,53 @@
 #pragma once
 
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/RNG.hh"
 
 #include "JetParameters.hh"
 #include "Process.hh"
 #include "Beam.hh"
 #include "Status.hh"
-#include "HiggsProperties.hh"
+#include "ParticleProperties.hh"
 
 namespace HEJ{
   class Event;
   class HiggsCouplingSettings;
   class ScaleGenerator;
 }
 
 namespace HEJFOG{
   class EventGenerator{
   public:
     EventGenerator(
         Process process,
         Beam beam,
         HEJ::ScaleGenerator scale_gen,
         JetParameters jets,
         int pdf_id,
         double uno_chance,
-        HiggsProperties higgs_properties,
+        ParticlesPropMap particles_properties,
         HEJ::HiggsCouplingSettings Higgs_coupling,
         HEJ::RNG & ran
     );
 
     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 uno_chance_;
-    HiggsProperties higgs_properties_;
+    ParticlesPropMap particles_properties_;
     std::reference_wrapper<HEJ::RNG> ran_;
   };
 
 }
diff --git a/FixedOrderGen/include/HiggsProperties.hh b/FixedOrderGen/include/ParticleProperties.hh
similarity index 50%
rename from FixedOrderGen/include/HiggsProperties.hh
rename to FixedOrderGen/include/ParticleProperties.hh
index 4d86d56..5e72895 100644
--- a/FixedOrderGen/include/HiggsProperties.hh
+++ b/FixedOrderGen/include/ParticleProperties.hh
@@ -1,13 +1,16 @@
 #pragma once
 
 #include <vector>
+#include <unordered_map>
 
 #include "Decay.hh"
 
 namespace HEJFOG{
-  struct HiggsProperties{
+  struct ParticleProperties{
     double mass;
     double width;
     std::vector<Decay> decays;
   };
+  using ParticlesPropMap
+    = std::unordered_map<HEJ::ParticleID, ParticleProperties>;
 }
diff --git a/FixedOrderGen/include/PhaseSpacePoint.hh b/FixedOrderGen/include/PhaseSpacePoint.hh
index 791c665..957902d 100644
--- a/FixedOrderGen/include/PhaseSpacePoint.hh
+++ b/FixedOrderGen/include/PhaseSpacePoint.hh
@@ -1,164 +1,164 @@
 /** \file PhaseSpacePoint.hh
  *  \brief Contains the PhaseSpacePoint Class
  */
 
 #pragma once
 
 #include <vector>
 
 #include "HEJ/utility.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/RNG.hh"
 
 #include "Status.hh"
 #include "JetParameters.hh"
-#include "HiggsProperties.hh"
+#include "ParticleProperties.hh"
 
 namespace HEJFOG{
   class Process;
 
   using HEJ::Particle;
   //! A point in resummation phase space
   class PhaseSpacePoint{
   public:
     //! Default PhaseSpacePoint Constructor
     PhaseSpacePoint() = default;
 
     //! PhaseSpacePoint Constructor
     /**
      * @param proc             The process to generate
      * @param jet_def          The Jet Definition Used
      * @param jetptmin         Minimum Jet Transverse Momentum
      * @param rapmax           Maximum parton rapidity
      * @param pdf              The pdf set (used for sampling)
      * @param uno_chance       Chance to turn a potentially unordered
      *                         emission into an actual one
      *
      * Initially, only FKL phase space points are generated. If the most
      * extremal emission in any direction is a quark or anti-quark and the
      * next emission is a gluon, uno_chance is the chance to turn this into
      * an unordered emission event by swapping the two flavours. At most one
      * unordered emission will be generated in this way.
      */
     PhaseSpacePoint(
       Process const & proc,
       JetParameters const & jet_properties,
       HEJ::PDF & pdf, double E_beam,
       double uno_chance,
-      HiggsProperties const & higgs_properties,
+      ParticlesPropMap const & particles_properties,
       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<int, std::vector<Particle>> const & decays() const{
       return decays_;
     }
 
     private:
 
     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;
     void reconstruct_incoming(
         std::array<HEJ::ParticleID, 2> const & ids,
         HEJ::PDF & pdf, double E_beam,
         double uf,
         HEJ::RNG & ran
     );
     HEJ::ParticleID generate_incoming_id(
         size_t beam_idx, double x, double uf, HEJ::PDF & pdf,
         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);
     void maybe_turn_to_uno(double chance, HEJ::RNG & ran);
     std::vector<Particle> decay_boson(
         HEJ::Particle const & parent,
         std::vector<Decay> const & decays,
         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<int, std::vector<Particle>> decays_;
   };
   HEJ::UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp);
 }
diff --git a/FixedOrderGen/include/config.hh b/FixedOrderGen/include/config.hh
index b8220a9..ce2371c 100644
--- a/FixedOrderGen/include/config.hh
+++ b/FixedOrderGen/include/config.hh
@@ -1,37 +1,37 @@
 #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 "Process.hh"
 #include "JetParameters.hh"
 #include "Beam.hh"
-#include "HiggsProperties.hh"
+#include "ParticleProperties.hh"
 #include "UnweightSettings.hh"
 
 namespace HEJFOG{
 
   struct Config{
     Process process;
     int events;
     JetParameters jets;
     Beam beam;
     int pdf_id;
     double unordered_fraction;
-    HiggsProperties Higgs_properties;
+    ParticlesPropMap particles_properties;
     YAML::Node analysis_parameters;
     HEJ::ScaleConfig scales;
     std::vector<HEJ::OutputFile> output;
     HEJ::RNGConfig rng;
     HEJ::HiggsCouplingSettings Higgs_coupling;
     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 c457239..4380eac 100644
--- a/FixedOrderGen/src/EventGenerator.cc
+++ b/FixedOrderGen/src/EventGenerator.cc
@@ -1,81 +1,81 @@
 #include "EventGenerator.hh"
 
 #include "Process.hh"
 #include "Beam.hh"
 #include "JetParameters.hh"
 #include "PhaseSpacePoint.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/config.hh"
 
 namespace HEJFOG{
   EventGenerator::EventGenerator(
       Process process,
       Beam beam,
       HEJ::ScaleGenerator scale_gen,
       JetParameters jets,
       int pdf_id,
       double uno_chance,
-      HiggsProperties higgs_properties,
+      ParticlesPropMap particles_properties,
       HEJ::HiggsCouplingSettings Higgs_coupling,
       HEJ::RNG & ran
   ):
     pdf_{pdf_id, beam.particles[0], beam.particles[1]},
     ME_{
       [this](double mu){ return pdf_.Halphas(mu); },
       HEJ::MatrixElementConfig{
         HEJ::JetParameters{jets.def, jets.min_pt},
         false,
         std::move(Higgs_coupling)
       }
     },
     scale_gen_{std::move(scale_gen)},
     process_{std::move(process)},
     jets_{std::move(jets)},
     beam_{std::move(beam)},
     uno_chance_{uno_chance},
-    higgs_properties_{std::move(higgs_properties)},
+    particles_properties_{std::move(particles_properties)},
     ran_{ran}
   {
   }
 
   HEJ::Event EventGenerator::gen_event(){
     HEJFOG::PhaseSpacePoint psp{
       process_,
       jets_,
       pdf_, beam_.energy,
       uno_chance_,
-      higgs_properties_,
+      particles_properties_,
       ran_
     };
     status_ = psp.status();
     if(status_ != good) return {};
 
-    HEJ::Event	ev = scale_gen_(
+    HEJ::Event ev = scale_gen_(
         HEJ::Event{
           to_UnclusteredEvent(std::move(psp)),
           jets_.def, jets_.min_pt
         }
     );
 
     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 on this point
     ev.central().weight *= ME_.tree(
         ev.central().mur, ev.incoming(), ev.outgoing(), false
     )/(shat*shat);
     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(auto & var: ev.variations()){
       var.weight *= ME_.tree(
         var.mur, ev.incoming(), ev.outgoing(), false
       )/(shat*shat);
       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 7aad62f..31b00b3 100644
--- a/FixedOrderGen/src/PhaseSpacePoint.cc
+++ b/FixedOrderGen/src/PhaseSpacePoint.cc
@@ -1,448 +1,457 @@
 #include "PhaseSpacePoint.hh"
 
 #include <random>
 
 #include "HEJ/kinematics.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/exceptions.hh"
 
 #include "Process.hh"
 
 #include <CLHEP/Random/Randomize.h>
 #include <CLHEP/Random/RanluxEngine.h>
 
 using namespace HEJ;
 
 namespace HEJFOG{
 
   static_assert(
       std::numeric_limits<double>::has_quiet_NaN,
       "no quiet NaN for double"
   );
   constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
   HEJ::UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp){
     HEJ::UnclusteredEvent result;
     result.incoming = psp.incoming();
     std::sort(
         begin(result.incoming), end(result.incoming),
         [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();}
     );
     assert(result.incoming.size() == 2);
     result.outgoing = psp.outgoing();
     assert(
         std::is_sorted(
             begin(result.outgoing), end(result.outgoing),
             HEJ::rapidity_less{}
         )
     );
     assert(result.outgoing.size() >= 2);
     result.decays = psp.decays();
     result.central.mur = NaN;
     result.central.muf = NaN;
     result.central.weight = psp.weight();
     return result;
   }
 
   namespace{
     bool can_swap_to_uno(
         HEJ::Particle const & p1, HEJ::Particle const & p2
     ){
       return is_parton(p1)
         && p1.type != HEJ::pid::gluon
         && p2.type == HEJ::pid::gluon;
     }
   }
 
   void PhaseSpacePoint::maybe_turn_to_uno(
       double chance,
       HEJ::RNG & ran
   ){
     assert(outgoing_.size() >= 2);
     const size_t nout = outgoing_.size();
     const bool can_be_uno_backward = can_swap_to_uno(
         outgoing_[0], outgoing_[1]
     );
     const bool can_be_uno_forward = can_swap_to_uno(
         outgoing_[nout-1], outgoing_[nout-2]
     );
     if(!can_be_uno_backward && !can_be_uno_forward) return;
     if(ran.flat() < chance){
       weight_ /= chance;
       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);
       }
     }
     else weight_ /= 1 - chance;
   }
 
   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};
   }
 
   PhaseSpacePoint::PhaseSpacePoint(
     Process const & proc,
     JetParameters const & jet_param,
     HEJ::PDF & pdf, double E_beam,
     double uno_chance,
-    HiggsProperties const & h,
+    ParticlesPropMap const & particles_properties,
     HEJ::RNG & ran
   )
   {
     assert(proc.njets >= 2);
+    if(proc.boson
+      && particles_properties.find(*(proc.boson))
+        == particles_properties.end())
+      throw HEJ::missing_option("Boson "
+        +std::to_string(*(proc.boson))+" can't be generated: missing properties");
     status_ = good;
     weight_ = 1;
 
     const int nout = proc.njets + (proc.boson?1:0);
     outgoing_.reserve(nout);
 
     const bool is_pure_jets = !proc.boson;
     auto partons = gen_LO_partons(
         proc.njets, is_pure_jets, jet_param, E_beam, ran
     );
     for(auto&& p_out: partons) {
       outgoing_.emplace_back(Particle{pid::gluon, std::move(p_out)});
     }
     if(status_ != good) return;
 
-    if(proc.boson && *proc.boson == pid::Higgs){
-      // The Higgs
-      auto Hparticle=gen_boson(pid::higgs, h.mass, h.width, ran);
-      auto pos=std::upper_bound(
-          begin(outgoing_),end(outgoing_),Hparticle,rapidity_less{}
+    // create boson
+    if(proc.boson){
+      const auto & boson_prop = particles_properties.at(*proc.boson);
+      auto boson(gen_boson(*proc.boson, boson_prop.mass, boson_prop.width, ran));
+      const auto pos = std::upper_bound(
+          begin(outgoing_),end(outgoing_),boson,rapidity_less{}
       );
-      outgoing_.insert(pos, Hparticle);
-      if(! h.decays.empty()){
+      outgoing_.insert(pos, std::move(boson));
+      if(! boson_prop.decays.empty()){
         const int boson_idx = std::distance(begin(outgoing_), pos);
         decays_.emplace(
             boson_idx,
-            decay_boson(outgoing_[boson_idx], h.decays, ran)
+            decay_boson(outgoing_[boson_idx], boson_prop.decays, ran)
         );
       }
     }
     // normalisation of momentum-conserving delta function
     weight_ *= pow(2*M_PI, 4);
 
     reconstruct_incoming(proc.incoming, 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;
 
     if(proc.njets > 2) maybe_turn_to_uno(uno_chance, 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
   ){
+    if(bosonid != HEJ::pid::Higgs)
+      throw HEJ::not_implemented("Production of boson "+std::to_string(bosonid)
+        +" not implemented yet.");
     // Usual phase space measure
     weight_ /= 16.*pow(M_PI, 3);
 
     // Generate a y Gaussian distributed around 0
     // TODO: magic number only for Higgs
     const double y = random_normal(1.6, ran);
     const double r1 = ran.flat();
     const double sH = mass*(
         mass + width*tan(M_PI/2.*r1 + (r1-1.)*atan(mass/width))
     );
 
     auto p = gen_last_momentum(outgoing_, sH, 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];
   }
 
   void PhaseSpacePoint::reconstruct_incoming(
       std::array<HEJ::ParticleID, 2> const & ids,
       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].p.e()-incoming_[0].p.pz())/sqrts;
     const double xb=(incoming_[1].p.e()+incoming_[1].p.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;
     }
     // pick pdfs
     /** TODO:
      *  ufa, ufb don'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
      */
     for(size_t i = 0; i < 2; ++i){
       if(ids[i] == pid::proton || ids[i] == pid::p_bar){
         incoming_[i].type = generate_incoming_id(i, i?xb:xa, uf, pdf, ran);
       }
       else {
         incoming_[i].type = ids[i];
       }
     }
     assert(momentum_conserved(1e-7));
   }
 
   namespace {
     /// particles are ordered, odd = anti
     ParticleID index_to_pid(size_t i){
       if(!i) return pid::gluon;
       return static_cast<ParticleID>(i%2?(i+1)/2:-i/2);
     }
   }
 
   HEJ::ParticleID PhaseSpacePoint::generate_incoming_id(
       size_t const beam_idx, double const x, double const uf,
       HEJ::PDF & pdf, HEJ::RNG & ran
   ){
     std::array<double,11> pdf_wt;
     pdf_wt[0] = fabs(pdf.pdfpt(beam_idx,x,uf,pid::gluon));
     double pdftot = pdf_wt[0];
       for(size_t i = 1; i < pdf_wt.size(); ++i){
       pdf_wt[i] = 4./9.*fabs(pdf.pdfpt(beam_idx,x,uf,index_to_pid(i)));
       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"};
   }
 
   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;
     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);
     if(channel.products.size() != 2){
       throw HEJ::not_implemented{
         "only decays into two particles are implemented"
       };
     }
     std::vector<Particle> decay_products(channel.products.size());
     for(size_t i = 0; i < channel.products.size(); ++i){
       decay_products[i].type = channel.products[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 e709864..8908762 100644
--- a/FixedOrderGen/src/config.cc
+++ b/FixedOrderGen/src/config.cc
@@ -1,287 +1,309 @@
 #include "config.hh"
 
 #include <cctype>
 
 #include "HEJ/config.hh"
 #include "HEJ/YAMLreader.hh"
 
 namespace HEJFOG{
   using HEJ::set_from_yaml;
   using HEJ::set_from_yaml_if_defined;
 
   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", "unordered fraction", "scales", "scale factors",
           "max scale ratio", "pdf", "event output", "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 && Higgs_opt: {"mass", "width"}){
-          supported["Higgs properties"][Higgs_opt] = "";
+        for(auto && particle_type: {"Higgs", "Wp", "Wm", "Z"}){
+          for(auto && particle_opt: {"mass", "width"}){
+            supported["Particle properties"][particle_type][particle_opt] = "";
+          }
+          supported["Particle properties"][particle_type]["decays"]["into"] = "";
+          supported["Particle properties"][particle_type]["decays"]["branching ratio"] = "";
         }
-        supported["Higgs properties"]["decays"]["into"] = "";
-        supported["Higgs properties"]["decays"]["branching ratio"] = "";
         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");
       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', 'W+', 'W-', 'Z', 'H'"
       };
     }
 
     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())){
-          if(particles[i].back() != 'j'){
-            throw invalid_outgoing(particles[i]);
-          }
+        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(!HEJ::is_AWZH_boson(pid)){
             throw invalid_outgoing(particles[i]);
           }
           if(result.boson){
             throw std::invalid_argument{
               "More than one outgoing boson is not supported"
             };
           }
           result.boson = pid;
         }
       }
       if(result.njets < 2){
         throw std::invalid_argument{
           "Process has to include at least two jets ('j')"
         };
       }
       return result;
     }
 
     Decay get_decay(YAML::Node const & node){
       Decay decay;
       set_from_yaml(decay.products, node, "into");
       set_from_yaml(decay.branching_ratio, node, "branching ratio");
       return decay;
     }
 
     std::vector<Decay> get_decays(YAML::Node const & node){
       using YAML::NodeType;
       if(!node) return {};
       switch(node.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)};
       case NodeType::Sequence:
         std::vector<Decay> result;
         for(auto && decay_str: node){
           result.emplace_back();
           set_from_yaml(result.back().products, decay_str, "into");
           set_from_yaml(result.back().branching_ratio, decay_str, "branching ratio");
         }
         return result;
       }
       throw std::logic_error{"unreachable"};
     }
 
-    HiggsProperties get_higgs_properties(
+    ParticleProperties get_particle_properties(
         YAML::Node const & node, std::string const & entry
     ){
-      HiggsProperties result;
+      ParticleProperties result;
       set_from_yaml(result.mass, node, entry, "mass");
       set_from_yaml(result.width, node, entry, "width");
       try{
         result.decays = get_decays(node[entry]["decays"]);
       }
       catch(HEJ::missing_option const & ex){
         throw HEJ::missing_option{entry + ": decays: " + ex.what()};
       }
       catch(HEJ::invalid_type const & ex){
         throw HEJ::invalid_type{entry + ": decays: " + ex.what()};
       }
       return result;
     }
 
+    ParticlesPropMap get_all_particles_properties(YAML::Node const & node){
+      ParticlesPropMap result;
+      using namespace HEJ;
+      // @TODO allow more synonyms
+      if(node["Higgs"])
+        result[pid::Higgs] = get_particle_properties(node,"Higgs");
+      if(node["Wp"])
+        result[pid::Wp] = get_particle_properties(node,"Wp");
+      if(node["Wm"])
+        result[pid::Wm] = get_particle_properties(node,"Wm");
+      if(node["Z"])
+        result[pid::Z] = get_particle_properties(node,"Z");
+      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.unordered_fraction, yaml, "unordered fraction");
       if(config.unordered_fraction < 0 || config.unordered_fraction > 1){
         throw std::invalid_argument{
           "unordered fraction has to be between 0 and 1"
         };
       }
-      config.Higgs_properties = get_higgs_properties(yaml, "Higgs properties");
+      if(yaml["Particle properties"]){
+        config.particles_properties = get_all_particles_properties(
+          yaml["Particle properties"]);
+      }
+      if(config.process.boson
+        && config.particles_properties.find(*(config.process.boson))
+          == config.particles_properties.end())
+        throw HEJ::missing_option("Process wants to generate boson "
+          +std::to_string(*(config.process.boson))+", but boson properties are missing");
       set_from_yaml_if_defined(config.analysis_parameters, yaml, "analysis");
       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 9f77655..0a8d9cf 100644
--- a/FixedOrderGen/src/main.cc
+++ b/FixedOrderGen/src/main.cc
@@ -1,247 +1,247 @@
 /**
  * Name: main.cc
  * Authors: Jeppe R. Andersen
  */
 
 #include <fstream>
 #include <algorithm>
 #include <memory>
 #include <chrono>
 #include <iostream>
 #include <map>
 
 #include "yaml-cpp/yaml.h"
 
 #include "config.hh"
 
 #include "LHEF/LHEF.h"
 #include "HEJ/CombinedEventWriter.hh"
 #include "HEJ/get_analysis.hh"
 #include "HEJ/utility.hh"
 //#include "HEJ/EventReweighter.hh"
 #include "HEJ/stream.hh"
 #include "HEJ/LesHouchesWriter.hh"
 #include "HEJ/ProgressBar.hh"
 #include "HEJ/make_RNG.hh"
 
 #include "EventGenerator.hh"
 #include "PhaseSpacePoint.hh"
 #include "Unweighter.hh"
 #include "CrossSectionAccumulator.hh"
 #include "Version.hh"
 
 namespace{
   constexpr auto banner =
     "     __  ___       __       ______                                   __     __                               \n"
     "    / / / (_)___ _/ /_     / ____/___  ___  _________ ___  __       / /__  / /______                         \n"
     "   / /_/ / / __ `/ __ \\   / __/ / __ \\/ _ \\/ ___/ __ `/ / / /  __  / / _ \\/ __/ ___/                         \n"
     "  / __  / / /_/ / / / /  / /___/ / / /  __/ /  / /_/ / /_/ /  / /_/ /  __/ /_(__  )                          \n"
     " /_/ /_/_/\\__, /_/ /_/  /_____/_/ /_/\\___/_/   \\__, /\\__, /   \\____/\\___/\\__/____/                           \n"
     "     ____///__/            __   ____          ///__//____/    ______                           __            \n"
     "    / ____(_)  _____  ____/ /  / __ \\_________/ /__  _____   / ____/__  ____  ___  _________ _/ /_____  _____\n"
     "   / /_  / / |/_/ _ \\/ __  /  / / / / ___/ __  / _ \\/ ___/  / / __/ _ \\/ __ \\/ _ \\/ ___/ __ `/ __/ __ \\/ ___/\n"
     "  / __/ / />  </  __/ /_/ /  / /_/ / /  / /_/ /  __/ /     / /_/ /  __/ / / /  __/ /  / /_/ / /_/ /_/ / /    \n"
     " /_/   /_/_/|_|\\___/\\__,_/   \\____/_/   \\__,_/\\___/_/      \\____/\\___/_/ /_/\\___/_/   \\__,_/\\__/\\____/_/     \n"
     ;
 
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr long long max_warmup_events = 10000;
 }
 
 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::unique_ptr<HEJ::Analysis> get_analysis(
     YAML::Node const & parameters
 ){
   try{
     return HEJ::get_analysis(parameters);
   }
   catch(std::exception const & exc){
     std::cerr << "Failed to load analysis: " << exc.what() << '\n';
     std::exit(EXIT_FAILURE);
   }
 }
 
 void rescale_weights(HEJ::Event & ev, double factor) {
     ev.central().weight *= factor;
     for(auto & var: ev.variations()){
       var.weight *= factor;
     }
 }
 
 int main(int argn, char** argv) {
   using namespace std::string_literals;
   if (argn < 2) {
     std::cerr << "\n# Usage:\n.FOgen 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::unique_ptr<HEJ::Analysis> analysis = get_analysis(
       config.analysis_parameters
   );
   assert(analysis != nullptr);
 
   auto 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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     *ran
   };
 
   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};
 
   HEJ::optional<HEJFOG::Unweighter> unweighter{};
   std::map<HEJFOG::Status, int> status_counter;
 
   std::vector<HEJ::Event> events;
   int trials = 0;
   // warm-up phase to train unweighter
   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()];
       if(generator.status() == HEJFOG::good && analysis->pass_cuts(ev, ev)) {
         events.emplace_back(std::move(ev));
         ++warmup_progress;
       }
     }
     std::cout << std::endl;
     unweighter = HEJFOG::Unweighter{
       begin(events), end(events), config.unweight->max_dev, *ran,
       config.jets.peak_pt?(*config.jets.peak_pt):0.
     };
     std::vector<HEJ::Event> unweighted_events;
     for(auto && ev: events) {
       auto unweighted = unweighter->unweight(std::move(ev));
       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";
   }
 
   HEJ::ProgressBar<long long> progress{std::cout, config.events};
   progress.increment(events.size());
   events.reserve(config.events);
   for(; events.size() < static_cast<size_t>(config.events); ++trials){
     auto ev = generator.gen_event();
     ++status_counter[generator.status()];
     if(generator.status() == HEJFOG::good && analysis->pass_cuts(ev, ev)) {
       if(unweighter) {
         auto unweighted = unweighter->unweight(std::move(ev));
         if(! unweighted) continue;
         ev = std::move(*unweighted);
       }
       events.emplace_back(std::move(ev));
       ++progress;
     }
   }
   std::cout << std::endl;
 
   HEJFOG::CrossSectionAccumulator xs;
   for(auto & ev: events){
     rescale_weights(ev, invGeV2_to_pb/trials);
     analysis->fill(ev, ev);
     writer.write(ev);
     xs.fill(ev);
   }
   analysis->finalise();
 
   const std::chrono::duration<double> run_time = (clock::now() - start_time);
   std::cout << "\nTask Runtime: " << run_time.count() << " seconds.\n\n";
 
   std::cout.precision(10);
   std::cout.setf(std::ios::fixed);
   std::cout
     << "   " << std::left << std::setw(20)
     << "Cross section: " << xs.total().value
     << " +- " << std::sqrt(xs.total().error) << " (pb)\n";
   for(auto const & xs_type: xs) {
     std::cout
        << "   " << std::left << std::setw(20)
       << (HEJ::event_type::names[xs_type.first] + ": "s)
       << xs_type.second.value << " +- "
       << std::sqrt(xs_type.second.error) << " (pb)\n";
   }
 
   std::cout << '\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 << "%\n";
   }
 
 }
diff --git a/FixedOrderGen/t/2j.cc b/FixedOrderGen/t/2j.cc
index 4731eb6..7e0aaef 100644
--- a/FixedOrderGen/t/2j.cc
+++ b/FixedOrderGen/t/2j.cc
@@ -1,57 +1,57 @@
 #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/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");
 
   HEJ::Mixmax ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) 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';
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.01*xs);
 }
diff --git a/FixedOrderGen/t/4j.cc b/FixedOrderGen/t/4j.cc
index 5e6fd4b..dc543d3 100644
--- a/FixedOrderGen/t/4j.cc
+++ b/FixedOrderGen/t/4j.cc
@@ -1,58 +1,58 @@
 #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/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.81063619*1e6; //calculated with "combined" HEJ svn r3480
 
   auto config = load_config("config_2j.yml");
   config.process.njets = 4;
 
   HEJ::Mixmax ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) 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';
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.03*xs);
 }
diff --git a/FixedOrderGen/t/config_2j.yml b/FixedOrderGen/t/config_2j.yml
index 67625dc..30d737d 100644
--- a/FixedOrderGen/t/config_2j.yml
+++ b/FixedOrderGen/t/config_2j.yml
@@ -1,26 +1,27 @@
 events: 100000
 
 jets:
   min pt: 30
   R: 0.4
   algorithm: antikt
   max rapidity: 5
 
 beam:
   energy: 6500
   particles: [p, p]
 
 pdf: 11000
 
 process: p p => 2j
 
 unordered fraction: 0
 
 scales: 125
 
 random generator:
   name: mixmax
 
-Higgs properties:
-  mass: 125
-  width: 0
+Particle properties:
+  Higgs:
+    mass: 125
+    width: 0
diff --git a/FixedOrderGen/t/config_h_2j.yml b/FixedOrderGen/t/config_h_2j.yml
index fb6e0d7..e75483e 100644
--- a/FixedOrderGen/t/config_h_2j.yml
+++ b/FixedOrderGen/t/config_h_2j.yml
@@ -1,26 +1,27 @@
 events: 200000
 
 jets:
   min pt: 30
   R: 0.4
   algorithm: antikt
   max rapidity: 5
 
 beam:
   energy: 6500
   particles: [p, p]
 
 pdf: 11000
 
 process: p p => h 2j
 
 unordered fraction: 0
 
 scales: 125
 
-Higgs properties:
-  mass: 125
-  width: 0
+Particle properties:
+  Higgs:
+    mass: 125
+    width: 0
 
 random generator:
   name: mixmax
diff --git a/FixedOrderGen/t/config_h_2j_decay.yml b/FixedOrderGen/t/config_h_2j_decay.yml
index f0719ec..125b430 100644
--- a/FixedOrderGen/t/config_h_2j_decay.yml
+++ b/FixedOrderGen/t/config_h_2j_decay.yml
@@ -1,27 +1,28 @@
 events: 200000
 
 jets:
   min pt: 30
   R: 0.4
   algorithm: antikt
   max rapidity: 5
 
 beam:
   energy: 6500
   particles: [p, p]
 
 pdf: 11000
 
 process: p p => h 2j
 
 unordered fraction: 0
 
 scales: 125
 
-Higgs properties:
-  mass: 125
-  width: 0.00407
-  decays: {into: [photon, photon], branching ratio: 0.00228}
+Particle properties:
+  Higgs:
+    mass: 125
+    width: 0.00407
+    decays: {into: [photon, photon], branching ratio: 0.00228}
 
 random generator:
   name: mixmax
diff --git a/FixedOrderGen/t/h_2j.cc b/FixedOrderGen/t/h_2j.cc
index de76905..241348c 100644
--- a/FixedOrderGen/t/h_2j.cc
+++ b/FixedOrderGen/t/h_2j.cc
@@ -1,65 +1,65 @@
 #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/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 rHEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
 
   HEJ::Ranlux64 ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     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 << '\n';
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.01*xs);
 }
diff --git a/FixedOrderGen/t/h_2j_decay.cc b/FixedOrderGen/t/h_2j_decay.cc
index e7f5f0d..3896fb7 100644
--- a/FixedOrderGen/t/h_2j_decay.cc
+++ b/FixedOrderGen/t/h_2j_decay.cc
@@ -1,83 +1,83 @@
 #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/PDF.hh"
 #include "HEJ/MatrixElement.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 rHEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j_decay.yml");
 
   HEJ::Ranlux64 ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     assert(ev.decays().size() == 1);
     const auto decay = begin(ev.decays());
     assert(ev.outgoing().size() > static_cast<size_t>(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 << '\n';
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.012*xs);
 }
diff --git a/FixedOrderGen/t/h_3j.cc b/FixedOrderGen/t/h_3j.cc
index e46b8d0..f06bc0a 100644
--- a/FixedOrderGen/t/h_3j.cc
+++ b/FixedOrderGen/t/h_3j.cc
@@ -1,66 +1,66 @@
 #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/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 1.08083; // +- 0.00615934
   //calculated with rHEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 3;
 
   HEJ::Ranlux64 ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     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 << '\n';
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.02*xs);
 }
diff --git a/FixedOrderGen/t/h_3j_uno1.cc b/FixedOrderGen/t/h_3j_uno1.cc
index cc553c1..2eaee1a 100644
--- a/FixedOrderGen/t/h_3j_uno1.cc
+++ b/FixedOrderGen/t/h_3j_uno1.cc
@@ -1,69 +1,69 @@
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 // check that adding uno emissions doesn't change the FKL cross section
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.0243548; // +- 0.000119862
   //calculated with rHEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 3;
   config.process.incoming = {HEJ::pid::u, HEJ::pid::u};
   config.unordered_fraction = 0.37;
 
   HEJ::Ranlux64 ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   int uno_found = 0;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) continue;
     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\n";
   assert(uno_found > 0);
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.05*xs);
 }
diff --git a/FixedOrderGen/t/h_3j_uno2.cc b/FixedOrderGen/t/h_3j_uno2.cc
index bae1a06..0302745 100644
--- a/FixedOrderGen/t/h_3j_uno2.cc
+++ b/FixedOrderGen/t/h_3j_uno2.cc
@@ -1,63 +1,63 @@
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 
 // check uno cross section
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.00347538; // +- 3.85875e-05
   //calculated with rHEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 3;
   config.process.incoming = {HEJ::pid::u, HEJ::pid::u};
   config.unordered_fraction = 1.;
 
   HEJ::Ranlux64 ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(ev.type() == HEJ::event_type::FKL) continue;
     if(generator.status() != good) 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';
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.05*xs);
 }
diff --git a/FixedOrderGen/t/h_5j.cc b/FixedOrderGen/t/h_5j.cc
index ad8cbf1..8841db7 100644
--- a/FixedOrderGen/t/h_5j.cc
+++ b/FixedOrderGen/t/h_5j.cc
@@ -1,61 +1,61 @@
 #ifdef NDEBUG
 #undef NDEBUG
 #endif
 // This is a regression test
 // the reference cross section has not been checked against any other program
 
 #include <algorithm>
 #include <cmath>
 #include <cassert>
 #include <iostream>
 
 #include "config.hh"
 #include "EventGenerator.hh"
 #include "HEJ/Ranlux64.hh"
 
 #include "HEJ/PDF.hh"
 #include "HEJ/MatrixElement.hh"
 
 using namespace HEJFOG;
 
 int main(){
   constexpr double invGeV2_to_pb = 389379292.;
   constexpr double xs_ref = 0.252273; // +- 0.00657742
   //calculated with rHEJ revision 9570e3809613272ac4b8bf3236279ba23cf64d20
 
   auto config = load_config("config_h_2j.yml");
   config.process.njets = 5;
 
   HEJ::Ranlux64 ran{};
   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.unordered_fraction,
-    config.Higgs_properties,
+    config.particles_properties,
     config.Higgs_coupling,
     ran
   };
 
   double xs = 0., xs_err = 0.;
   for (int trials = 0; trials < config.events; ++trials){
     auto ev = generator.gen_event();
     if(generator.status() != good) 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';
 
   assert(std::abs(xs - xs_ref) < 3*xs_err);
   assert(xs_err < 0.06*xs);
 }
diff --git a/doc/sphinx/HEJFOG.rst b/doc/sphinx/HEJFOG.rst
index ac8be40..185c30b 100644
--- a/doc/sphinx/HEJFOG.rst
+++ b/doc/sphinx/HEJFOG.rst
@@ -1,252 +1,258 @@
 The HEJ Fixed Order Generator
 =============================
 
 For high jet multiplicities event generation with standard fixed-order
 generators becomes increasingly cumbersome. For example, the
 leading-order production of a Higgs Boson with five or more jets is
 computationally prohibitively expensive.
 
 To this end, HEJ 2 provides the ``HEJFOG`` fixed-order generator
 that allows to generate events with high jet multiplicities. To
 facilitate the computation the limit of Multi-Regge Kinematics with
 large invariant masses between all outgoing particles is assumed in the
 matrix elements. The typical use of the ``HEJFOG`` is to supplement
 low-multiplicity events from standard generators with high-multiplicity
 events before using the HEJ 2 program to add high-energy
 resummation.
 
 Installation
 ------------
 
 The ``HEJFOG`` comes bundled together with HEJ 2 and the
 installation is very similar. After downloading HEJ 2 and
 installing the prerequisites as described in :ref:`Installation` the
 ``HEJFOG`` can be installed with::
 
   cmake /path/to/FixedOrderGen -DCMAKE_INSTALL_PREFIX=target/directory -DCMAKE_BUILD_TYPE=Release
   make install
 
 where :file:`/path/to/FixedOrderGen` refers to the :file:`FixedOrderGen`
 subdirectory in the HEJ 2 directory. If HEJ 2 was
 installed to a non-standard location, it may be necessary to specify the
 directory containing :file:`HEJ-config.cmake`. If the base installation
 directory is :file:`/path/to/HEJ`, :file:`HEJ-config.cmake` should be
 found in :file:`/path/to/HEJ/lib/cmake/HEJ` and the commands for
 installing the ``HEJFOG`` would read::
 
   cmake /path/to/FixedOrderGen -DHEJ_DIR=/path/to/HEJ/lib/cmake/HEJ -DCMAKE_INSTALL_PREFIX=target/directory -DCMAKE_BUILD_TYPE=Release
   make install
 
 The installation can be tested with::
 
   make test
 
 provided that the NNPDF 3.0 PDF set is installed.
 
 Running the fixed-order generator
 ---------------------------------
 
 After installing the ``HEJFOG`` you can modify the provided
 configuration file :file:`configFO.yml` and run the generator with::
 
   HEJFOG configFO.yml
 
 The resulting event file, by default named :file:`HEJFO.lhe`, can then be
 fed into HEJ 2 like any event file generated from a standard
 fixed-order generator, see :ref:`Running HEJ 2`.
 
 Settings
 --------
 
 Similar to HEJ 2, the ``HEJFOG`` uses a `YAML
 <http://yaml.org/>`_ configuration file. The settings are
 
 .. _`process`:
 
 **process**
    The scattering process for which events are being generated. The
    format is
 
    :code:`in1 in2 => out1 out2 ...`
 
    The incoming particles, :code:`in1`, :code:`in2` can be
 
    - quarks: :code:`u`, :code:`d`, :code:`u_bar`, and so on
    - gluons: :code:`g`
    - protons :code:`p` or antiprotons :code:`p_bar`
 
    At most one of the outgoing particles can be a boson. At the moment
    only the Higgs boson :code:`h` is supported. All other outgoing
    particles are jets. Multiple jets can be grouped together, so
    :code:`p p => h j j` is the same as :code:`p p => h 2j`. There have
    to be at least two jets.
 
 .. _`events`:
 
 **events**
    Specifies the number of events to generate.
 
 .. _`jets`:
 
 **jets**
    Defines the properties of the generated jets.
 
    .. _`jets: min pt`:
 
    **min pt**
       Minimum jet transverse momentum in GeV.
 
    .. _`jets: peak pt`:
 
    **peak pt**
       Optional setting to specify the dominant jet transverse momentum
       in GeV. If the generated events are used as input for HEJ
       resummation, this should be set to the minimum transverse momentum
       of the resummation jets. The effect is that only a small fraction
       of jets will be generated with a transverse momentum below the
       value of this setting.
 
    .. _`jets: algorithm`:
 
    **algorithm**
       The algorithm used to define jets. Allowed settings are
       :code:`kt`, :code:`cambridge`, :code:`antikt`, :code:`genkt`,
       :code:`cambridge for passive`, :code:`genkt for passive`,
       :code:`ee kt`, :code:`ee genkt`. See the `FastJet
       <http://fastjet.fr/>`_ documentation for a description of these
       algorithms.
 
    .. _`jets: R`:
 
    **R**
       The R parameter used in the jet algorithm.
 
    .. _`jets: max rapidity`:
 
    **max rapidity**
       Maximum absolute value of the jet rapidity.
 
 .. _`beam`:
 
 **beam**
    Defines various properties of the collider beam.
 
    .. _`beam: energy`:
 
    **energy**
       The beam energy in GeV. For example, the 13
       TeV LHC corresponds to a value of 6500.
 
    .. _`beam: particles`:
 
    **particles**
       A list :code:`[p1, p2]` of two beam particles. The only supported
       entries are protons :code:`p` and antiprotons :code:`p_bar`.
 
 .. _`pdf`:
 
 **pdf**
    The LHAPDF number of the PDF set. For example, 230000 corresponds
    to an NNPDF 2.3 NLO PDF set.
 
 .. _`unordered fraction`:
 
 **unordered fraction**
    This setting is related to the fraction of events where a gluon is
    emitted outside the rapidity ordering required in FKL events. More
    precisely, if at least one of the incoming particles is a quark or
    antiquark and there are more than two jets in the final state, this
    states the probability that the flavours of the outgoing particles
    are assigned in such a way that an unordered configuration
    arises. Typically, this value should be between 0.01 and 0.1.
 
 .. _`unweight`:
 
 **unweight**
    This setting defines the parameters for the partial unweighting of
    events. You can disable unweighting by removing this entry from the
    configuration file.
 
 .. _`unweight: sample size`:
 
    **sample size**
       The number of weighted events used to calibrate the unweighting.
       A good default is to set this to the number of target
       `events`_. If the number of `events`_ is large this can
       lead to significant memory consumption and a lower value should be
       chosen. Contrarily, for large multiplicities the unweighting
       efficiency becomes worse and the sample size should be increased.
 
 .. _`unweight: max deviation`:
 
    **max deviation**
      Controls the range of events to which unweighting is applied. A
      larger value means that a larger fraction of events are unweighted.
      Typical values are between -1 and 1.
 
-.. _`Higgs properties`:
+.. _`Particle properties`:
 
-**Higgs properties**
-   Specifies various properties of the Higgs boson. This is only
-   relevant if the chosen `process`_ is the production of a Higgs boson
-   with jets.
+**Particle properties**
+   Specifies various properties of the different particles (Higgs, W or Z).
+   This is only relevant if the chosen `process`_ is the production of the
+   corresponding particles with jets. E.g. for the `process`_ :code:`p p => h 2j` the :code:`mass`, :code:`width` and the :code:`decays` of the :code:`Higgs` boson are required, while all other particle properties will be ignored.
 
-.. _`Higgs properties: mass`:
+.. _`Higgs properties: particle`:
 
-   **mass**
-      The mass of the Higgs boson in GeV.
+   **Higgs, Wp, Wm or Z**
 
-   **width**
-      The total decay width of the Higgs boson in GeV.
+   Name of the boson. Can be any of the once above.
 
-   **decays**
-      Optional setting specifying the decays of the Higgs boson. This is
-      treated as the production and subsequent decay of an on-shell
-      Higgs boson, so decays into e.g. Z bosons are not supported. Only
-      the decay into two particles is implemented. Each decay has the
-      form
+   .. _`Higgs properties: particle: mass`:
 
-      :code:`{into: [p1,p2], branching ratio: r}`
+      **mass**
+         The mass of the Higgs boson in GeV.
 
-      where :code:`p1` and :code:`p2` are the particle names of the
-      decay product (e.g. :code:`photon`) and :code:`r` is the branching
-      ratio.
+      **width**
+         The total decay width of the Higgs boson in GeV.
+
+      **decays**
+         Optional setting specifying the decays of the Higgs boson. This is
+         treated as the production and subsequent decay of an on-shell
+         Higgs boson, so decays into e.g. Z bosons are not supported. Only
+         the decay into two particles is implemented. Each decay has the
+         form
+
+         :code:`{into: [p1,p2], branching ratio: r}`
+
+         where :code:`p1` and :code:`p2` are the particle names of the
+         decay product (e.g. :code:`photon`) and :code:`r` is the branching
+         ratio.
 
 .. _`scales`:
 
 **scales**
    Specifies the renormalisation and factorisation scales for the output
    events. For details, see the corresponding entry in the HEJ 2
    :ref:`HEJ 2 settings`. Note that this should usually be a
    single value, as the weights resulting from additional scale choices
    will simply be ignored by HEJ 2.
 
 .. _`event output`:
 
 **event output**
    Specifies the name of a single event output file or a list of such
    files. See the corresponding entry in the HEJ 2
    :ref:`HEJ 2 settings` for details.
 
 .. _`RanLux init`:
 
 .. _`random generator`:
 
 **random generator**
    Sets parameters for random number generation. See the
    :ref:`HEJ 2 settings` for details.
 
 .. _`analysis`:
 
 **analysis**
    Specifies the name and settings for a custom analysis library. This
    can be useful to specify cuts at the fixed-order level. See the
    corresponding entry in the HEJ 2 :ref:`HEJ 2 settings`
    for details.
 
 .. _`Higgs coupling`:
 
 **Higgs coupling**
    This collects a number of settings concerning the effective coupling
    of the Higgs boson to gluons. See the corresponding entry in the
    HEJ 2 :ref:`HEJ 2 settings` for details.
diff --git a/include/HEJ/PDG_codes.hh b/include/HEJ/PDG_codes.hh
index 8b93ca3..be9a45f 100644
--- a/include/HEJ/PDG_codes.hh
+++ b/include/HEJ/PDG_codes.hh
@@ -1,92 +1,98 @@
 /** \file PDG_codes.hh
  *  \brief Contains the Particle IDs of all relevant SM particles.
  *
  *  Large enumeration included which has multiple entries for potential
  *  alternative names of different particles. There are also functions
  *  which can be used to determine if a particle is a parton or if
  *  it is a non-gluon boson.
  */
 
 #pragma once
 
+#include <string>
+
 namespace HEJ {
 
   //! particle ids according to PDG
   namespace pid {
     //! The possible particle identities. We use PDG IDs as standard.
     enum ParticleID{
       d = 1,                          /*!< Down Quark */
       down = d,                       /*!< Down Quark */
       u = 2,                          /*!< Up Quark */
       up = u,                         /*!< Up Quark */
       s = 3,                          /*!< Strange Quark */
       strange = s,                    /*!< Strange Quark */
       c = 4,                          /*!< Charm Quark */
       charm = c,                      /*!< Charm Quark */
       b = 5,                          /*!< Bottom Quark */
       bottom = b,                     /*!< Bottom Quark */
       t = 6,                          /*!< Top Quark */
       top = t,                        /*!< Top Quark */
       e = 11,                         /*!< Electron */
       electron = e,                   /*!< Electron */
       nu_e = 12,                      /*!< Electron Neutrino */
       electron_neutrino = nu_e,       /*!< Electron neutrino */
       mu = 13,                        /*!< Muon */
       muon = mu,                      /*!< Muon */
       nu_mu = 14,                     /*!< Muon Neutrino */
       muon_neutrino = nu_mu,          /*!< Muon Neutrino */
       tau = 15,                       /*!< Tau */
       nu_tau = 16,                    /*!< Tau Neutrino */
       tau_neutrino = nu_tau,          /*!< Tau Neutrino */
       d_bar = -d,                     /*!< Anti-Down Quark */
       u_bar = -u,                     /*!< Anti-Up quark */
       s_bar = -s,                     /*!< Anti-Strange Quark */
       c_bar = -c,                     /*!< Anti-Charm Quark */
       b_bar = -b,                     /*!< Anti-Bottom Quark */
       t_bar = -t,                     /*!< Anti-Top Quark */
       e_bar = -e,                     /*!< Positron */
+      positron = e_bar,               /*!< Positron */
       nu_e_bar = -nu_e,               /*!< Anti-Electron Neutrino */
       mu_bar = -mu,                   /*!< Anti-Muon */
       nu_mu_bar = -nu_mu,             /*!< Anti-Muon Neutrino */
       tau_bar = -tau,                 /*!< Anti-Tau */
       nu_tau_bar = -nu_tau,           /*!< Anti-Tau Neutrino */
       gluon = 21,                     /*!< Gluon */
       g = gluon,                      /*!< Gluon */
       photon = 22,                    /*!< Photon */
       gamma = photon,                 /*!< Photon */
       Z = 23,                         /*!< Z Boson */
       Wp = 24,                        /*!< W- Boson */
       Wm = -Wp,                        /*!< W+ Boson */
       h = 25,                         /*!< Higgs Boson */
       Higgs = h,                      /*!< Higgs Boson */
       higgs = h,                      /*!< Higgs Boson */
       p = 2212,                       /*!< Proton */
       proton = p,                     /*!< Proton */
       p_bar = -p,                     /*!< Anti-Proton */
     };
 
   }
 
   using ParticleID = pid::ParticleID;
 
+  //! Convert a particle name to the corresponding PDG particle ID
+  ParticleID to_ParticleID(std::string const & name);
+
   /**
    * \brief Function to determine if particle is a parton
    * @param p              PDG ID of particle
    * @returns              true if the particle is a parton, false otherwise
    */
   inline
   constexpr bool is_parton(ParticleID p){
     return p == pid::gluon || std::abs(p) <= pid::top;
   }
 
   /**
    * \brief function to determine if the particle is a photon, W, Z, or Higgs boson
    * @param id             PDG ID of particle
    * @returns              true if the partice is a A,W,Z, or H, false otherwise
    */
   inline
   constexpr bool is_AWZH_boson(ParticleID id){
     return id == pid::Wm || (id >= pid::photon && id <= pid::Higgs);
   }
 
 }
diff --git a/include/HEJ/YAMLreader.hh b/include/HEJ/YAMLreader.hh
index d2ed33e..8740f14 100644
--- a/include/HEJ/YAMLreader.hh
+++ b/include/HEJ/YAMLreader.hh
@@ -1,244 +1,241 @@
 /** \file
  *  \brief The file which handles the configuration file parameters
  *
  *  The configuration files parameters are read and then stored
  *  within this objects.
  */
 
 #pragma once
 
 #include "yaml-cpp/yaml.h"
 
 #include "HEJ/config.hh"
 #include "HEJ/exceptions.hh"
 
 
 namespace HEJ{
   //! 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);
 
-  //! Convert a particle name to the corresponding PDG particle ID
-  ParticleID to_ParticleID(std::string const & particle_name);
-
   //! 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);
 
     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)...
       );
     }
   }
 
   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 YAML {
 
   template<>
   struct convert<HEJ::OutputFile> {
     static Node encode(HEJ::OutputFile const & outfile);
     static bool decode(Node const & node, HEJ::OutputFile & out);
   };
 }
diff --git a/src/MatrixElement.cc b/src/MatrixElement.cc
index 6657919..e9c644d 100644
--- a/src/MatrixElement.cc
+++ b/src/MatrixElement.cc
@@ -1,825 +1,826 @@
 #include "HEJ/MatrixElement.hh"
 
 #include <CLHEP/Random/Randomize.h>
 #include <CLHEP/Random/RanluxEngine.h>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/currents.hh"
+#include "HEJ/exceptions.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/uno.hh"
 #include "HEJ/utility.hh"
 
 namespace HEJ{
   //cf. last line of eq. (22) in \ref Andersen:2011hs
   double MatrixElement::omega0(
       double alpha_s, double mur,
       fastjet::PseudoJet const & q_j, double lambda
   ) const {
     const double result = - alpha_s*N_C/M_PI*log(q_j.perp2()/(lambda*lambda));
     if(! param_.log_correction) return result;
     // use alpha_s(sqrt(q_j*lambda)), evolved to mur
     return (
         1. + alpha_s/(4.*M_PI)*beta0*log(mur*mur/(q_j.perp()*lambda))
     )*result;
   }
 
   double MatrixElement::virtual_corrections(
       double mur,
       std::array<Particle, 2> const & in,
       std::vector<Particle> const & out
   ) const{
     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(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 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 || has_unob_gluon(in, out)){
       q -= out[1].p;
       ++first_idx;
     }
     if(out.back().type == pid::Higgs || has_unof_gluon(in, out)){
       --last_idx;
     }
 
     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, CLAMBDA)*(
           out[j+1].rapidity() - out[j].rapidity()
       );
       q -= out[j+1].p;
     }
     assert(
         nearby(q, -1*pb, norm)
         || out.back().type == pid::Higgs
         || has_unof_gluon(in, out)
     );
     return exp(exponent);
   }
 } // namespace HEJ
 
 namespace {
   //! Lipatov vertex for partons emitted into extremal jets
   double C2Lipatov(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv,
     CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2)
   {
     CLHEP::HepLorentzVector temptrans=-(qav+qbv);
     CLHEP::HepLorentzVector p5=qav-qbv;
     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));
 
     // cout << "#Fadin qa : "<<qav<<endl;
     // cout << "#Fadin qb : "<<qbv<<endl;
     // cout << "#Fadin p1 : "<<p1<<endl;
     // cout << "#Fadin p2 : "<<p2<<endl;
     // cout << "#Fadin p5 : "<<p5<<endl;
     // cout << "#Fadin Gauge Check : "<< CL.dot(p5)<<endl;
     // cout << "#Fadin C2L : "<< -CL.dot(CL)<<" "<<-CL.dot(CL)/(qav.m2()*qbv.m2())/(4./p5.perp2())<<endl;
 
     // TODO can this dead test go?
     // if (-CL.dot(CL)<0.)
       //   if (fabs(CL.dot(p5))>fabs(CL.dot(CL))) // not sufficient!
     //   return 0.;
     // else
     return -CL.dot(CL);
   }
 
   //! Lipatov vertex with soft subtraction for partons emitted into extremal jets
   double C2Lipatovots(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv,
     CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2)
   {
     double kperp=(qav-qbv).perp();
     if (kperp>HEJ::CLAMBDA)
       return C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2());
     else {
       double Cls=(C2Lipatov(qav, qbv, p1, p2)/(qav.m2()*qbv.m2()));
       return Cls-4./(kperp*kperp);
     }
   }
 
   //! Lipatov vertex
   double C2Lipatov(CLHEP::HepLorentzVector qav, CLHEP::HepLorentzVector qbv,
     CLHEP::HepLorentzVector pim, CLHEP::HepLorentzVector pip,
     CLHEP::HepLorentzVector pom, CLHEP::HepLorentzVector pop) // B
   {
     CLHEP::HepLorentzVector temptrans=-(qav+qbv);
     CLHEP::HepLorentzVector p5=qav-qbv;
     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 qav, CLHEP::HepLorentzVector qbv,
     CLHEP::HepLorentzVector pa, CLHEP::HepLorentzVector pb,
     CLHEP::HepLorentzVector p1, CLHEP::HepLorentzVector p2)
   {
     double kperp=(qav-qbv).perp();
     if (kperp>HEJ::CLAMBDA)
       return C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2());
     else {
       double Cls=(C2Lipatov(qav, qbv, pa, pb, p1, p2)/(qav.m2()*qbv.m2()));
       double temp=Cls-4./(kperp*kperp);
       return temp;
     }
   }
 
   /** 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(
       int aptype, int bptype,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & p1,
       CLHEP::HepLorentzVector const & pa
   ){
     if (aptype==21&&bptype==21) {
       return jM2gg(pn,pb,p1,pa);
     } else if (aptype==21&&bptype!=21) {
       if (bptype > 0)
         return jM2qg(pn,pb,p1,pa);
       else
         return jM2qbarg(pn,pb,p1,pa);
     }
     else if (bptype==21&&aptype!=21) { // ----- || -----
       if (aptype > 0)
         return jM2qg(p1,pa,pn,pb);
       else
         return jM2qbarg(p1,pa,pn,pb);
     }
     else { // they are both quark
       if (bptype>0) {
         if (aptype>0)
           return jM2qQ(pn,pb,p1,pa);
         else
           return jM2qQbar(pn,pb,p1,pa);
       }
       else {
         if (aptype>0)
           return jM2qQbar(p1,pa,pn,pb);
         else
           return jM2qbarQbar(pn,pb,p1,pa);
       }
     }
     throw std::logic_error("unknown particle types");
   }
 
   /** \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(
       int aptype, int 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
   ){
     if (aptype==21&&bptype==21) // gg initial state
       return MH2gg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
     else if (aptype==21&&bptype!=21) {
       if (bptype > 0)
         return MH2qg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4./9.;
       else
         return MH2qbarg(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4./9.;
     }
     else if (bptype==21&&aptype!=21) {
       if (aptype > 0)
         return MH2qg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4./9.;
       else
         return MH2qbarg(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4./9.;
     }
     else { // they are both quark
       if (bptype>0) {
         if (aptype>0)
           return MH2qQ(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.);
         else
           return MH2qQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.);
       }
       else {
         if (aptype>0)
           return MH2qQbar(p1,pa,pn,pb,-qH,-qHp1,mt,include_bottom,mb)*4.*4./(9.*9.);
         else
           return MH2qbarQbar(pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb)*4.*4./(9.*9.);
       }
     }
     throw std::logic_error("unknown particle types");
   }
 
   /** \brief  Current matrix element squared with Higgs and unordered forward emission
    *  @param aptype          Particle A PDG ID
    *  @param bptype          Particle B PDG ID
    *  @param punof           Unordered Particle Momentum
    *  @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 with Higgs and unordered forward emission
    */
   double ME_Higgs_current_unof(
       int aptype, int bptype,
       CLHEP::HepLorentzVector const & punof,
       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
   ){
     if (aptype==21&&bptype!=21) {
       if (bptype > 0)
         return jM2unogqHg(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
       else
         return jM2unogqbarHg(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
     }
     else { // they are both quark
       if (bptype>0) {
         if (aptype>0)
           return jM2unogqHQ(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
         else
           return jM2unogqHQbar(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
       }
       else {
         if (aptype>0)
           return jM2unogqbarHQ(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
         else
           return jM2unogqbarHQbar(punof,pn,pb,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
       }
     }
     throw std::logic_error("unknown particle types");
   }
 
   /** \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 punob           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
    */
   double ME_Higgs_current_unob(
       int aptype, int bptype,
       CLHEP::HepLorentzVector const & pn,
       CLHEP::HepLorentzVector const & pb,
       CLHEP::HepLorentzVector const & punob,
       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
   ){
     if (bptype==21&&aptype!=21) {
       if (aptype > 0)
         return jM2unobgHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
       else
         return jM2unobgHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
     }
     else { // they are both quark
       if (aptype>0) {
         if (bptype>0)
           return jM2unobqHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
         else
           return jM2unobqbarHQg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
       }
       else {
         if (bptype>0)
           return jM2unobqHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
         else
           return jM2unobqbarHQbarg(pn,pb,punob,p1,pa,-qHp1,-qH,mt,include_bottom,mb);
       }
     }
     throw std::logic_error("unknown particle types");
   }
 
   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
 
 namespace HEJ{
   MatrixElement::MatrixElement(
       std::function<double (double)> alpha_s,
       MatrixElementConfig conf
   ):
     alpha_s_{std::move(alpha_s)},
     param_{std::move(conf)}
   {
     validate(param_);
   }
 
   double MatrixElement::operator()(
       double mur,
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     return tree(
         mur,
         incoming, outgoing,
         check_momenta
     )*virtual_corrections(
         mur,
         incoming, outgoing
     );
   }
 
   double MatrixElement::tree_kin(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     assert(
         std::is_sorted(
             incoming.begin(), incoming.end(),
             [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();}
         )
     );
     assert(std::is_sorted(outgoing.begin(), outgoing.end(), rapidity_less{}));
 
     auto AWZH_boson = std::find_if(
         begin(outgoing), end(outgoing),
         [](Particle const & p){return is_AWZH_boson(p);}
     );
 
     if(AWZH_boson == end(outgoing)){
       return tree_kin_jets(incoming, outgoing, check_momenta);
     }
 
     switch(AWZH_boson->type){
     case pid::Higgs: {
       return tree_kin_Higgs(incoming, outgoing, check_momenta);
     }
     // TODO
     case pid::Wp:
     case pid::Wm:
     case pid::photon:
     case pid::Z:
     default:
       throw std::logic_error("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 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)*C_A;
         } else{
           wt *= C2Lipatovots(qip1, qi, pa, pb, p1, pn)*C_A;
         }
 
         qi = qip1;
       }
       return wt;
     }
 
   }  // namespace anonymous
 
   std::vector<Particle> MatrixElement::tag_extremal_jet_partons(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> out_partons, bool check_momenta
   ) const{
     if(!check_momenta){
       for(auto & parton: out_partons){
         parton.p.set_user_index(no_extremal_jet_idx);
       }
       return out_partons;
     }
     fastjet::ClusterSequence cs(to_PseudoJet(out_partons), param_.jet_param.def);
     const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt));
     assert(jets.size() >= 2);
     auto most_backward = begin(jets);
     auto most_forward = end(jets) - 1;
     // skip jets caused by unordered emission
     if(has_unob_gluon(incoming, out_partons)){
       assert(jets.size() >= 3);
       ++most_backward;
     }
     else if(has_unof_gluon(incoming, out_partons)){
       assert(jets.size() >= 3);
       --most_forward;
     }
     const auto extremal_jet_indices = cs.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;
   }
 
   double MatrixElement::tree_kin_jets(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> partons,
       bool check_momenta
   ) const {
     partons = tag_extremal_jet_partons(incoming, partons, check_momenta);
     if(has_unob_gluon(incoming, partons) || has_unof_gluon(incoming, partons)){
-      throw std::logic_error("unordered emission not implemented for pure jets");
+      throw not_implemented("unordered emission not implemented for pure jets");
     }
 
     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
     );
   }
 
   double MatrixElement::tree_kin_Higgs(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     if(has_uno_gluon(incoming, outgoing)){
       return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
     }
     if(outgoing.front().type == pid::Higgs){
       return tree_kin_Higgs_first(incoming, outgoing, check_momenta);
     }
     if(outgoing.back().type == pid::Higgs){
       return tree_kin_Higgs_last(incoming, outgoing, check_momenta);
     }
     return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
   }
 
   namespace {
     // Colour acceleration multipliers, for gluons see eq. (7) in arXiv:0910.5113
     // TODO: code duplication with currents.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 == ParticleID::gluon) return K_g(pout, pin);
       return C_F;
     }
     // Colour factor in strict MRK limit
     double K_MRK(ParticleID type) {
       return (type == ParticleID::gluon)?C_A:C_F;
     }
   }
 
   double MatrixElement::MH2_forwardH(
       CLHEP::HepLorentzVector p1out, CLHEP::HepLorentzVector p1in,
       ParticleID type2,
       CLHEP::HepLorentzVector p2out, CLHEP::HepLorentzVector p2in,
       CLHEP::HepLorentzVector pH,
       double t1, double t2
   ) const{
     ignore(p2out, p2in);
     const double shat = p1in.invariantMass2(p2in);
     // 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*MH2gq_outsideH(
           p1out, p1in, p2out, p2in, pH,
           param_.Higgs_coupling.mt, param_.Higgs_coupling.include_bottom,
           param_.Higgs_coupling.mb
       )/(4*(N_C*N_C - 1));
     }
 #endif
     return K_MRK(type2)/C_A*9./2.*shat*shat*(
         C2gHgp(p1in,p1out,pH) + C2gHgm(p1in,p1out,pH)
     )/(t1*t2);
   }
 
   double MatrixElement::tree_kin_Higgs_first(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     assert(outgoing.front().type == pid::Higgs);
     if(outgoing[1].type != pid::gluon) {
       assert(incoming.front().type == outgoing[1].type);
       return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
     }
     const auto pH = to_HepLorentzVector(outgoing.front());
     const auto partons = tag_extremal_jet_partons(
         incoming,
         std::vector<Particle>(begin(outgoing) + 1, end(outgoing)),
         check_momenta
     );
 
     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
     );
   }
 
   double MatrixElement::tree_kin_Higgs_last(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     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(incoming, outgoing, check_momenta);
     }
     const auto pH = to_HepLorentzVector(outgoing.back());
     const auto partons = tag_extremal_jet_partons(
         incoming,
         std::vector<Particle>(begin(outgoing), end(outgoing) - 1),
         check_momenta
     );
 
     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
     );
   }
 
 
   double MatrixElement::tree_kin_Higgs_between(
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     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);
     std::vector<Particle> partons(begin(outgoing), the_Higgs);
     partons.insert(end(partons), the_Higgs + 1, end(outgoing));
     partons = tag_extremal_jet_partons(incoming, partons, check_momenta);
 
     const auto pa = to_HepLorentzVector(incoming[0]);
     const auto pb = to_HepLorentzVector(incoming[1]);
 
     auto p1 = to_HepLorentzVector(
         partons[has_unob_gluon(incoming, outgoing)?1:0]
     );
     auto pn = to_HepLorentzVector(
         partons[partons.size() - (has_unof_gluon(incoming, outgoing)?2:1)]
     );
 
     auto first_after_Higgs = begin(partons) + (the_Higgs-begin(outgoing));
     assert(
         (first_after_Higgs == end(partons) && (
             has_unob_gluon(incoming, outgoing)
             || partons.back().type != pid::gluon
         ))
         || first_after_Higgs->rapidity() >= the_Higgs->rapidity()
     );
     assert(
         (first_after_Higgs == begin(partons) && (
             has_unof_gluon(incoming, outgoing)
             || 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(has_unob_gluon(incoming, outgoing)){
       current_factor = C_A*C_A/2.*ME_Higgs_current_unob( // 1/2 = "K_uno"
           incoming[0].type, incoming[1].type,
           pn, pb, to_HepLorentzVector(partons.front()), p1, pa, qH, qH - pH,
           param_.Higgs_coupling.mt,
           param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb
       );
       const auto p_unob = to_HepLorentzVector(partons.front());
       q0 -= p_unob;
       p1 += p_unob;
       ++begin_ladder;
     }
     else if(has_unof_gluon(incoming, outgoing)){
       current_factor = C_A*C_A/2.*ME_Higgs_current_unof( // 1/2 = "K_uno"
           incoming[0].type, incoming[1].type,
            to_HepLorentzVector(partons.back()), pn, pb, p1, pa, qH, qH - pH,
           param_.Higgs_coupling.mt,
           param_.Higgs_coupling.include_bottom, param_.Higgs_coupling.mb
       );
       pn += to_HepLorentzVector(partons.back());
       --end_ladder;
     }
     else{
       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
       );
     }
 
     const double ladder_factor = FKL_ladder_weight(
         begin_ladder, first_after_Higgs,
         q0, pa, pb, p1, pn
     )*FKL_ladder_weight(
         first_after_Higgs, end_ladder,
         qH - pH, pa, pb, p1, pn
     );
     return current_factor*C_A*C_A/(N_C*N_C-1.)*ladder_factor;
   }
 
   double MatrixElement::tree_param_partons(
       double alpha_s, double mur,
       std::vector<Particle> const & partons
   ) const{
     const double gs2 = 4.*M_PI*alpha_s;
     double wt = std::pow(gs2, partons.size());
     if(param_.log_correction){
       // use alpha_s(q_perp), evolved to mur
       assert(partons.size() >= 2);
       for(size_t i = 1; i < partons.size()-1; ++i){
         wt *= 1 + alpha_s/(2*M_PI)*beta0*log(mur/partons[i].p.perp());
       }
     }
     return wt;
   }
 
   double MatrixElement::tree_param(
       double mur,
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing
   ) const{
     const double alpha_s = alpha_s_(mur);
     auto AWZH_boson = std::find_if(
         begin(outgoing), end(outgoing),
         [](auto const & p){return is_AWZH_boson(p);}
     );
     double AWZH_coupling = 1.;
     if(AWZH_boson != end(outgoing)){
       switch(AWZH_boson->type){
       case pid::Higgs: {
         AWZH_coupling = alpha_s*alpha_s;
         break;
       }
       // TODO
       case pid::Wp:
       case pid::Wm:
       case pid::photon:
       case pid::Z:
       default:
         throw std::logic_error("Emission of boson of unsupported type");
       }
     }
     if(has_unob_gluon(incoming, outgoing)){
       return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons(
           alpha_s, mur, filter_partons({begin(outgoing) + 1, end(outgoing)})
       );
     }
     if(has_unof_gluon(incoming, outgoing)){
       return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons(
           alpha_s, mur, filter_partons({begin(outgoing), end(outgoing) - 1})
       );
     }
     return AWZH_coupling*tree_param_partons(alpha_s, mur, filter_partons(outgoing));
   }
 
   double MatrixElement::tree(
       double mur,
       std::array<Particle, 2> const & incoming,
       std::vector<Particle> const & outgoing,
       bool check_momenta
   ) const {
     return tree_param(mur, incoming, outgoing)*tree_kin(
         incoming, outgoing, check_momenta
     );
   }
 } // namespace HEJ
diff --git a/src/PDG_codes.cc b/src/PDG_codes.cc
new file mode 100644
index 0000000..6dc9379
--- /dev/null
+++ b/src/PDG_codes.cc
@@ -0,0 +1,48 @@
+#include "HEJ/PDG_codes.hh"
+
+#include <map>
+#include <iostream>
+
+namespace HEJ{
+  ParticleID to_ParticleID(std::string const & name){
+    using namespace HEJ::pid;
+    static const std::map<std::string, ParticleID> known = {
+      {"d", d}, {"down", down}, {"1",static_cast<ParticleID>(1)},
+      {"u", u}, {"up", up}, {"2",static_cast<ParticleID>(2)},
+      {"s", s}, {"strange", strange}, {"3",static_cast<ParticleID>(3)},
+      {"c", c}, {"charm", charm}, {"4",static_cast<ParticleID>(4)},
+      {"b", b}, {"bottom", bottom}, {"5",static_cast<ParticleID>(5)},
+      {"t", t}, {"top", top}, {"6",static_cast<ParticleID>(6)},
+      {"e", e}, {"electron", electron}, {"e-", e}, {"11",static_cast<ParticleID>(11)},
+      {"nu_e", nu_e}, {"electron_neutrino", electron_neutrino}, {"12",static_cast<ParticleID>(12)},
+      {"mu", mu}, {"muon", muon}, {"mu-", mu}, {"13",static_cast<ParticleID>(13)},
+      {"nu_mu", nu_mu}, {"muon_neutrino", muon_neutrino}, {"14",static_cast<ParticleID>(14)},
+      {"tau", tau}, {"tau-", tau}, {"15",static_cast<ParticleID>(15)},
+      {"nu_tau", nu_tau}, {"tau_neutrino", tau_neutrino}, {"16",static_cast<ParticleID>(16)},
+      {"d_bar", d_bar}, {"-1",static_cast<ParticleID>(-1)},
+      {"u_bar", u_bar}, {"-2",static_cast<ParticleID>(-2)},
+      {"s_bar", s_bar}, {"-3",static_cast<ParticleID>(-3)},
+      {"c_bar", c_bar}, {"-4",static_cast<ParticleID>(-4)},
+      {"b_bar", b_bar}, {"-5",static_cast<ParticleID>(-5)},
+      {"t_bar", t_bar}, {"-6",static_cast<ParticleID>(-6)},
+      {"e_bar", e_bar}, {"positron", positron}, {"e+", e_bar}, {"-11",static_cast<ParticleID>(-11)},
+      {"nu_e_bar", nu_e_bar}, {"-12",static_cast<ParticleID>(-12)},
+      {"mu_bar", mu_bar}, {"mu+", mu_bar}, {"-13",static_cast<ParticleID>(-13)},
+      {"nu_mu_bar", nu_mu_bar}, {"-14",static_cast<ParticleID>(-14)},
+      {"tau_bar", tau_bar}, {"tau+", tau_bar}, {"-15",static_cast<ParticleID>(-15)},
+      {"nu_tau_bar", nu_tau_bar}, {"-16",static_cast<ParticleID>(-16)},
+      {"gluon", gluon}, {"g", g}, {"21",static_cast<ParticleID>(21)},
+      {"photon", photon}, {"gamma", gamma}, {"22",static_cast<ParticleID>(22)},
+      {"Z", Z}, {"23",static_cast<ParticleID>(23)},
+      {"Wp", Wp}, {"W+", Wp}, {"24",static_cast<ParticleID>(24)},
+      {"Wm", Wm}, {"W-", Wm}, {"-24",static_cast<ParticleID>(-24)},
+      {"h", h}, {"H", h}, {"Higgs", Higgs}, {"higgs", higgs}, {"25",static_cast<ParticleID>(25)},
+      {"p", p}, {"proton", proton}, {"p_bar", p_bar}, {"2212",static_cast<ParticleID>(2212)}
+    };
+    const auto res = known.find(name);
+    if(res == known.end()){
+      throw std::invalid_argument("Unknown particle " + name);
+    }
+    return res->second;
+  }
+}
diff --git a/src/YAMLreader.cc b/src/YAMLreader.cc
index ef503de..13f3888 100644
--- a/src/YAMLreader.cc
+++ b/src/YAMLreader.cc
@@ -1,486 +1,462 @@
 #include "HEJ/YAMLreader.hh"
 
 #include <set>
 #include <string>
 #include <vector>
 #include <iostream>
 #include <stdexcept>
 
 #include <dlfcn.h>
 
 namespace HEJ{
 
   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",
           "FKL", "unordered", "non-HEJ",
           "scales", "scale factors", "max scale ratio", "import scales",
           "log correction", "unweight", "event output", "analysis"
         };
         // 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] = "";
         }
         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},
         {"genkt", genkt_algorithm},
         {"cambridge for passive", cambridge_for_passive_algorithm},
         {"genkt for passive", genkt_for_passive_algorithm},
         {"ee kt", ee_kt_algorithm},
         {"ee genkt", ee_genkt_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;
     }
 
   } // namespace anonymous
 
-  ParticleID to_ParticleID(std::string const & name){
-    using namespace HEJ::pid;
-    static const std::map<std::string, ParticleID> known = {
-      {"d", d}, {"down", down}, {"u", u}, {"up", up}, {"s", s}, {"strange", strange},
-      {"c", c}, {"charm", charm}, {"b", b}, {"bottom", bottom}, {"t", t}, {"top", top},
-      {"e", e}, {"electron", electron}, {"nu_e", nu_e}, {"electron_neutrino", electron_neutrino},
-      {"mu", mu}, {"muon", muon}, {"nu_mu", nu_mu}, {"muon_neutrino", muon_neutrino},
-      {"tau", tau}, {"nu_tau", nu_tau}, {"tau_neutrino", tau_neutrino},
-      {"d_bar", d_bar}, {"u_bar", u_bar}, {"s_bar", s_bar}, {"c_bar", c_bar},
-      {"b_bar", b_bar}, {"t_bar", t_bar}, {"e_bar", e_bar},
-      {"nu_e_bar", nu_e_bar}, {"mu_bar", mu_bar}, {"nu_mu_bar", nu_mu_bar},
-      {"tau_bar", tau_bar}, {"nu_tau_bar", nu_tau_bar},
-      {"gluon", gluon}, {"g", g}, {"photon", photon}, {"gamma", gamma},
-      {"Z", Z}, {"Wp", Wp}, {"Wm", Wm}, {"W+", Wp}, {"W-", Wm},
-      {"h", h}, {"H", h}, {"Higgs", Higgs}, {"higgs", higgs},
-      {"p", p}, {"proton", proton}, {"p_bar", p_bar}
-    };
-    const auto res = known.find(name);
-    if(res == known.end()){
-      throw std::invalid_argument("Unknown particle " + name);
-    }
-    return res->second;
-  }
-
   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>());
     }
   } // 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;
   }
 
   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}
     };
     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;
     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 analysis sub-options
        * those depend on the analysis being used and should be checked there
        * similar for "import scales"
        */
       if(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()))
       );
       const size_t delim = scale_fun.find_first_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_simple_ScaleFunction(first, known)/to_double(second);
       }
       else{
         assert(scale_fun[delim] == '*');
         try{
           const double factor = to_double(second);
           return factor*parse_simple_ScaleFunction(first, known);
         }
         catch(std::invalid_argument const &){
           const double factor = to_double(first);
           return factor*parse_simple_ScaleFunction(first, known);
         }
       }
     }
 
     EventTreatMap get_event_treatment(
         YAML::Node const & yaml
     ){
       using namespace event_type;
       EventTreatMap treat {
         {no_2_jets, EventTreatment::discard},
         {bad_final_state, EventTreatment::discard},
         {FKL, EventTreatment::reweight},
         {unob, EventTreatment::keep},
         {unof, EventTreatment::keep},
         {nonHEJ, EventTreatment::keep}
       };
       set_from_yaml(treat.at(FKL), yaml, "FKL");
       set_from_yaml(treat.at(unob), yaml, "unordered");
       treat.at(unof) = treat.at(unob);
       set_from_yaml(treat.at(nonHEJ), yaml, "non-HEJ");
       if(treat[nonHEJ] == EventTreatment::reweight){
         throw std::invalid_argument{"Cannot reweight non-HEJ 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(config.min_extparton_pt, yaml, "min extparton pt");
       config.max_ext_soft_pt_fraction = std::numeric_limits<double>::infinity();
       set_from_yaml_if_defined(
           config.max_ext_soft_pt_fraction, yaml, "max ext soft pt fraction"
       );
       set_from_yaml(config.trials, yaml, "trials");
       set_from_yaml(config.log_correction, yaml, "log correction");
       set_from_yaml(config.unweight, yaml, "unweight");
       config.treat = get_event_treatment(yaml);
       set_from_yaml_if_defined(config.output, yaml, "event output");
       config.rng = to_RNGConfig(yaml, "random generator");
       set_from_yaml_if_defined(config.analysis_parameters, yaml, "analysis");
       config.scales = to_ScaleConfig(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