diff --git a/FixedOrderGen/src/EventGenerator.cc b/FixedOrderGen/src/EventGenerator.cc
index 4380eac..2fe6def 100644
--- a/FixedOrderGen/src/EventGenerator.cc
+++ b/FixedOrderGen/src/EventGenerator.cc
@@ -1,81 +1,78 @@
 #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,
       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},
     particles_properties_{std::move(particles_properties)},
     ran_{ran}
   {
   }
 
   HEJ::Event EventGenerator::gen_event(){
     HEJFOG::PhaseSpacePoint psp{
       process_,
       jets_,
       pdf_, beam_.energy,
       uno_chance_,
       particles_properties_,
       ran_
     };
     status_ = psp.status();
     if(status_ != good) return {};
 
     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);
+    const auto ME_weights = ME_.tree(ev, false);
+    ev.central().weight *= ME_weights.central/(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);
+    for(size_t i = 0; i < ev.variations().size(); ++i){
+      auto & var = ev.variations(i);
+      var.weight *= ME_weights.variations[i]/(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/include/HEJ/Event.hh b/include/HEJ/Event.hh
index 6ad19e5..51f9f50 100644
--- a/include/HEJ/Event.hh
+++ b/include/HEJ/Event.hh
@@ -1,186 +1,186 @@
 /** \file
  *  \brief Declares the Event class and helpers
  *
  */
 
 #pragma once
 
 #include <string>
 #include <memory>
 #include <unordered_map>
 
 #include "HEJ/utility.hh"
 #include "HEJ/event_types.hh"
 #include "LHEF/LHEF.h"
 #include "fastjet/JetDefinition.hh"
 #include "fastjet/ClusterSequence.hh"
 
 namespace HEJ{
 
 
   struct ParameterDescription;
 
   //! Event parameters
   struct EventParameters{
     double mur;              /**< Value of the Renormalisation Scale */
     double muf;              /**< Value of the Factorisation Scale */
     double weight;           /**< Event Weight */
     //! Optional description
     std::shared_ptr<ParameterDescription> description = nullptr;
   };
 
   //! Description of event parameters
   struct ParameterDescription {
     //! Name of central scale choice (e.g. "H_T/2")
     std::string scale_name;
     //! Actual renormalisation scale divided by central scale
     double mur_factor;
     //! Actual factorisation scale divided by central scale
     double muf_factor;
 
     ParameterDescription() = default;
     ParameterDescription(
         std::string scale_name, double mur_factor, double muf_factor
     ):
       scale_name{scale_name}, mur_factor{mur_factor}, muf_factor{muf_factor}
     {};
   };
 
   //! An event before jet clustering
   struct UnclusteredEvent{
     //! Default Constructor
     UnclusteredEvent() = default;
     //! Constructor from LesHouches event information
     UnclusteredEvent(LHEF::HEPEUP const & hepeup);
 
     std::array<Particle, 2> incoming;          /**< Incoming Particles */
     std::vector<Particle> outgoing;            /**< Outgoing Particles */
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays;
     //! Central parameter (e.g. scale) choice
     EventParameters central;
     std::vector<EventParameters> variations;    /**< For parameter variation */
   };
 
   /** An event with clustered jets
     *
     * This is the main HEJ 2 event class.
     * It contains kinematic information including jet clustering,
     * parameter (e.g. scale) settings and the event weight.
     */
   class Event{
   public:
     //! Default Event Constructor
     Event() = default;
     //! Event Constructor adding jet clustering to an unclustered event
     Event(
         UnclusteredEvent ev,
         fastjet::JetDefinition const & jet_def, double min_jet_pt
     );
 
     //! The jets formed by the outgoing partons
     std::vector<fastjet::PseudoJet> jets() const;
 
     //! The corresponding event before jet clustering
     UnclusteredEvent const & unclustered() const {
       return ev_;
     }
 
     //! Central parameter choice
     EventParameters const & central() const{
       return ev_.central;
     }
 
     //! Central parameter choice
     EventParameters & central(){
       return ev_.central;
     }
 
     //! Incoming particles
     std::array<Particle, 2> const &  incoming() const{
       return ev_.incoming;
     }
 
     //! Outgoing particles
     std::vector<Particle> const &  outgoing() const{
       return ev_.outgoing;
     }
 
     //! Particle decays
     /**
      *  The key in the returned map corresponds to the index in the
      *  vector returned by outgoing()
      */
     std::unordered_map<size_t, std::vector<Particle>> const &  decays() const{
       return ev_.decays;
     }
 
     //! Parameter (scale) variations
     std::vector<EventParameters> const & variations() const{
       return ev_.variations;
     }
 
     //! Parameter (scale) variations
     std::vector<EventParameters> & variations(){
       return ev_.variations;
     }
 
     //! Parameter (scale) variation
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters const & variations(size_t i) const{
       return ev_.variations[i];
     }
 
     //! Parameter (scale) variation
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters & variations(size_t i){
       return ev_.variations[i];
     }
 
     //! Indices of the jets the outgoing partons belong to
     /**
      *  @param jets   Jets to be tested
      *  @returns      A vector containing, for each outgoing parton,
      *                the index in the vector of jets the considered parton
      *                belongs to. If the parton is not inside any of the
      *                passed jets, the corresponding index is set to -1.
      */
     std::vector<int> particle_jet_indices(
         std::vector<fastjet::PseudoJet> const & jets
     ) const{
       return cs_.particle_jet_indices(jets);
     }
 
     //! Jet definition used for clustering
     fastjet::JetDefinition const & jet_def() const{
       return cs_.jet_def();
     }
 
     //! Minimum jet transverse momentum
     double min_jet_pt() const{
       return min_jet_pt_;
     }
 
     //! Event type
     event_type::EventType type() const{
       return type_;
     }
 
   private:
     UnclusteredEvent ev_;
     fastjet::ClusterSequence cs_;
     double min_jet_pt_;
     event_type::EventType type_;
   };
 
-  //! Square of the partonic centre-of-mass energy
+  //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$
   double shat(Event const & ev);
 
   //! Convert an event to a LHEF::HEPEUP
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP *);
 
 }
diff --git a/include/HEJ/EventReweighter.hh b/include/HEJ/EventReweighter.hh
index a3f3279..8e33d2d 100644
--- a/include/HEJ/EventReweighter.hh
+++ b/include/HEJ/EventReweighter.hh
@@ -1,136 +1,181 @@
 /** \file
  *  \brief Declares the EventReweighter class
  *
  *  EventReweighter is the main class used within HEJ 2. It reweights the
  *  resummation events.
  */
 #pragma once
 
 #include "fastjet/PseudoJet.hh"
 #include "fastjet/ClusterSequence.hh"
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/config.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/PhaseSpacePoint.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/RNG.hh"
 
 namespace HEJ{
 
   //! Beam parameters
   /**
    *  Currently, only symmetric beams are supported,
    *  so there is a single beam energy.
    */
   struct Beam{
     double E;                                /**< Beam energy */
     std::array<ParticleID, 2> type;          /**< Beam particles */
   };
 
   //! Main class for reweighting events in HEJ.
   class EventReweighter{
     using EventType = event_type::EventType;
   public:
 
     EventReweighter(
         Beam beam,                            /**< Beam Energy */
         int pdf_id,                           /**< PDF ID */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         HEJ::RNG & ran                       /**< Random number generator */
     );
 
     EventReweighter(
         LHEF::HEPRUP const & heprup,          /**< LHEF event header */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         HEJ::RNG & ran                       /**< Random number generator */
     );
 
     //! Get the used pdf
     PDF const & pdf() const;
 
 
     //! Generate resummation events for a given fixed-order event
     /**
      *  @param ev             Fixed-order event corresponding
      *                        to the resummation events
      *  @param num_events     Number of trial resummation configurations.
      *  @returns              A vector of resummation events.
      *
      *  The result vector depends on the type of the input event and the
      *  treatment of different types as specified in the constructor:
      *
      *  \ref reweight  The result vector contains between
      *                 0 and num_events resummation events.
      *
      *  \ref keep  If the input event passes the resummation jet cuts
      *             the result vector contains one event. Otherwise it is empty.
      *
      *  \ref discard   The result vector is empty
      */
     std::vector<Event> reweight(
         Event const & ev,
         int num_events
     );
 
   private:
 
-    struct EventFactors{
-      double central;
-      std::vector<double> variations;
-    };
-
     template<typename... T>
     PDF const & pdf(T&& ...);
 
+    /** \internal
+     * \brief main generation/reweighting function:
+     * generate phase space points and divide out Born factors
+     */
     std::vector<Event> gen_res_events(
         Event const & ev, int num_events
     );
     std::vector<Event> rescale(
         Event const & Born_ev, std::vector<Event> events
     ) const;
 
+    /** \internal
+     * \brief Do the Jets pass the resummation Cuts?
+     *
+     * @param ev               Event in Question
+     * @returns                0 or 1 depending on if ev passes Jet Cuts
+     */
     bool jets_pass_resummation_cuts(Event const & ev) const;
 
-    EventFactors pdf_factors(Event const & ev) const;
-
-    EventFactors matrix_elements(Event const & ev) const;
+    /** \internal
+    * \brief pdf_factors Function
+    *
+    * @param ev         Event in Question
+    * @returns          EventFactor due to PDFs
+    *
+    * Calculates the Central value and the variation due
+    * to the PDF choice made.
+    */
+    Weights pdf_factors(Event const & ev) const;
+
+    /** \internal
+     * \brief matrix_elements Function
+     *
+     * @param ev         Event in question
+     * @returns          EventFactor due to MatrixElements
+     *
+     * Calculates the Central value and the variation due
+     * to the Matrix Element.
+     */
+    Weights matrix_elements(Event const & ev) const;
 
-    EventFactors fixed_order_scale_ME(Event const & ev) const;
+    /** \internal
+     * \brief Scale-dependent part of fixed-order matrix element
+     *
+     * @param ev         Event in question
+     * @returns          EventFactor scale variation due to FO-ME.
+     *
+     * This is only called to compute the scale variation for events where
+     * we don't do resummation (e.g. non-FKL).
+     * Since at tree level the scale dependence is just due to alpha_s,
+     * it is enough to return the alpha_s(mur) factors in the matrix element.
+     * The rest drops out in the ratio of (output event ME)/(input event ME),
+     * so we never have to compute it.
+     */
+    Weights fixed_order_scale_ME(Event const & ev) const;
 
+    /** \internal
+     * \brief Computes the tree level matrix element
+     *
+     * @param ev                Event in Question
+     * @returns                 HEJ approximation to Tree level Matrix Element
+     *
+     * This computes the HEJ approximation to the tree level FO
+     * Matrix element which is used within the LO weighting process.
+     */
     double tree_matrix_element(Event const & ev) const;
 
 
     //! \internal General parameters
     EventReweighterConfig param_;
 
     //! \internal Beam energy
     double E_beam_;
 
     //! \internal PDF
     PDF pdf_;
 
     //! \internal Object to calculate the square of the matrix element
     MatrixElement MEt2_;
     //! \internal Object to calculate event renormalisation and factorisation scales
     ScaleGenerator scale_gen_;
-    //! \internal random number generator
-    /**
-     * \internal We use a reference_wrapper so that EventReweighter objects can
-     *           still be copied (which would be impossible with a reference).
+    /** \internal random number generator
+     *
+     *  \note We use a reference_wrapper so that EventReweighter objects can
+     *        still be copied (which would be impossible with a reference).
      */
     std::reference_wrapper<HEJ::RNG> ran_;
   };
 
   template<typename... T>
   PDF const & EventReweighter::pdf(T&&... t){
     return pdf_ = PDF{std::forward<T>(t)...};
   }
 
 }
diff --git a/include/HEJ/MatrixElement.hh b/include/HEJ/MatrixElement.hh
index 429b648..ebbc7f4 100644
--- a/include/HEJ/MatrixElement.hh
+++ b/include/HEJ/MatrixElement.hh
@@ -1,208 +1,163 @@
 /** \file
  *  \brief Contains the MatrixElement Class
  */
 #pragma once
 
 #include <functional>
 
 #include "HEJ/config.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/HiggsCouplingSettings.hh"
+#include "HEJ/Weights.hh"
 
 #include "CLHEP/Vector/LorentzVector.h"
 
 
 namespace HEJ{
+  class Event;
 
   //! Class to calculate the squares of matrix elements
   class MatrixElement{
   public:
     /** \brief MatrixElement Constructor
      * @param alpha_s        Function taking the renormalisation scale
      *                       and returning the strong coupling constant
      * @param conf           General matrix element settings
      */
     MatrixElement(
         std::function<double (double)> alpha_s,
         MatrixElementConfig conf
     );
 
   /**
-   * \brief regulated HEJ matrix element
-   * @param mur            Value of the renormalisation scale
-   * @param incoming       Incoming particles
-   * @param outgoing       Outgoing particles
+   * \brief squares of regulated HEJ matrix elements
+   * @param event          The event for which to calculate matrix elements
    * @param check_momenta  Special treatment for partons inside extremal jets
-   * @returns              The HEJ matrix element including virtual corrections
+   * @returns              The squares of HEJ matrix elements including virtual corrections
    *
    * cf. eq. (22) in \cite Andersen:2011hs
-   * Incoming particles should be ordered by ascending z momentum.
-   * Outgoing particles should be ordered by ascending rapidity.
    *
    * \internal Relation to standard HEJ Met2: MatrixElement = Met2*shat^2/(pdfta*pdftb)
    */
-    double operator()(
-        double mur,
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
+    Weights operator()(
+        Event const & event,
         bool check_momenta
     ) const;
 
-  //! HEJ tree-level matrix element
+  //! Squares of HEJ tree-level matrix elements
   /**
-   * @param mur            Value of the renormalisation scale
-   * @param incoming       Incoming particles
-   * @param outgoing       Outgoing particles
+   * @param event          The event for which to calculate matrix elements
    * @param check_momenta  Special treatment for partons inside extremal jets
-   * @returns              The HEJ matrix element without virtual corrections
+   * @returns              The squares of HEJ matrix elements without virtual corrections
    *
    * cf. eq. (22) in \cite Andersen:2011hs
-   * Incoming particles should be ordered by ascending z momentum.
-   * Outgoing particles should be ordered by ascending rapidity.
    */
-    double tree(
-        double mur,
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
-        bool check_momenta
-    ) const;
-
-  //! HEJ tree-level matrix element - parametric part
-  /**
-   * @param mur            Value of the renormalisation scale
-   * @param incoming       Incoming particles
-   * @param outgoing       Outgoing particles
-   * @returns              The parametric part of the tree matrix element
-   *
-   * cf. eq. (22) in \cite Andersen:2011hs
-   *
-   * The tree level matrix element factorises into a parametric part
-   * which depends on the theory parameters (alpha_s and scale)
-   * and a kinematic part comprising the dependence on the particle momenta
-   * and colour factors. This function returns the former.
-   */
-    double tree_param(
-        double mur,
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing
-    ) const;
-
-  //! HEJ tree-level matrix element - kinematic part
-  /**
-   * @param incoming       Incoming particles
-   * @param outgoing       Outgoing particles
-   * @param check_momenta  Special treatment for partons inside extremal jets
-   * @returns              The kinematic part of the tree matrix element
-   *
-   * cf. eq. (22) in \cite Andersen:2011hs
-   * Incoming particles should be ordered by ascending z momentum.
-   * Outgoing particles should be ordered by ascending rapidity.
-   *
-   * The tree level matrix element factorises into a parametric part
-   * which depends on the theory parameters (alpha_s and scale)
-   * and a kinematic part comprising the dependence on the particle momenta
-   * and colour factors. This function returns the latter.
-   */
-    double tree_kin(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
+    Weights tree(
+        Event const & event,
         bool check_momenta
     ) const;
 
    /**
-    * \brief Calculates the Virtual Corrections
-    * @param mur            Value of the renormalisation scale
-    * @param in             Incoming particles
-    * @param out            Outgoing particles
-    * @returns              The Virtual Corrections of the Matrix Element
-    *
-    * Incoming particles should be ordered by ascending z momentum.
-    * Outgoing particles should be ordered by ascending rapidity.
+    * \brief Virtual corrections to matrix element squares
+   * @param event          The event for which to calculate matrix elements
+    * @returns             The virtual corrections to the squares of the matrix elements
     *
     * The all order virtual corrections to LL in the MRK limit is
     * given by replacing 1/t in the scattering amplitude according to the
     * lipatov ansatz.
     *
     * cf. second-to-last line of eq. (22) in \cite Andersen:2011hs
     * note that indices are off by one, i.e. out[0].p corresponds to p_1
     */
-    double virtual_corrections(
-        double mur,
-        std::array<Particle, 2> const & in,
-        std::vector<Particle> const & out
+    Weights virtual_corrections(
+        Event const & event
     ) const;
 
   private:
+
+    Weights tree_param(
+        Event const & event
+    ) const;
+
+    double tree_kin(
+        Event const & event,
+        bool check_momenta
+    ) const;
+
+    double tree_param(
+        Event const & event,
+        double mur
+    ) const;
+
+    double virtual_corrections(
+        Event const & event,
+        double mur
+    ) const;
+
     //! \internal cf. last line of eq. (22) in \cite Andersen:2011hs
     double omega0(
         double alpha_s, double mur,
         fastjet::PseudoJet const & q_j, double lambda
     ) const;
 
     double tree_kin_jets(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> partons,
+        Event const & ev,
         bool check_momenta
     ) const;
     double tree_kin_Higgs(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
+        Event const & ev,
         bool check_momenta
     ) const;
     double tree_kin_Higgs_first(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
+        Event const & ev,
         bool check_momenta
     ) const;
     double tree_kin_Higgs_last(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
+        Event const & ev,
         bool check_momenta
     ) const;
 
     /**
      * \internal
      * \brief Higgs inbetween extremal partons.
      *
      * Note that in the case of unordered emission, the Higgs is *always*
      * treated as if in between the extremal (FKL) partons, even if its
      * rapidity is outside the extremal parton rapidities
      */
     double tree_kin_Higgs_between(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> const & outgoing,
+        Event const & ev,
         bool check_momenta
     ) const;
 
 
     double tree_param_partons(
         double alpha_s, double mur,
         std::vector<Particle> const & partons
     ) const;
 
 
     std::vector<int> in_extremal_jet_indices(
         std::vector<fastjet::PseudoJet> const & partons
     ) const;
 
 
     std::vector<Particle> tag_extremal_jet_partons(
-        std::array<Particle, 2> const & incoming,
-        std::vector<Particle> out_partons, bool check_momenta
+        Event const & ev, bool check_momenta
     ) const;
 
     double MH2_forwardH(
         CLHEP::HepLorentzVector p1out, CLHEP::HepLorentzVector p1in,
         pid::ParticleID type2,
         CLHEP::HepLorentzVector p2out, CLHEP::HepLorentzVector p2in,
         CLHEP::HepLorentzVector pH,
         double t1, double t2
     ) const;
 
     std::function<double (double)> alpha_s_;
 
     MatrixElementConfig param_;
   };
 
 
 }
diff --git a/include/HEJ/Weights.hh b/include/HEJ/Weights.hh
new file mode 100644
index 0000000..fc92237
--- /dev/null
+++ b/include/HEJ/Weights.hh
@@ -0,0 +1,45 @@
+/** \file
+ *  \brief Container for event weights
+ */
+#pragma once
+
+#include <vector>
+
+namespace HEJ{
+  //! Collection of weights assigned to a single event
+  /**
+   * A number of member functions of the MatrixElement class return Weights
+   * objects containing the squares of the matrix elements for the various
+   * scale choices.
+   */
+  struct Weights {
+    double central;
+    std::vector<double> variations;
+
+    Weights& operator*=(Weights const & other);
+    Weights& operator*=(double factor);
+    Weights& operator/=(Weights const & other);
+    Weights& operator/=(double factor);
+  };
+
+  inline
+  Weights operator*(Weights a, Weights const & b) {
+    return a*=b;
+  }
+  inline
+  Weights operator*(Weights a, double b) {
+    return a*=b;
+  }
+  inline
+  Weights operator*(double b, Weights a) {
+    return a*=b;
+  }
+  inline
+  Weights operator/(Weights a, Weights const & b) {
+    return a/=b;
+  }
+  inline
+  Weights operator/(Weights a, double b) {
+    return a/=b;
+  }
+}
diff --git a/include/HEJ/config.hh b/include/HEJ/config.hh
index a7fe397..7c51c05 100644
--- a/include/HEJ/config.hh
+++ b/include/HEJ/config.hh
@@ -1,175 +1,173 @@
 /** \file
  *  \brief HEJ 2 configuration parameters
  */
 
 #pragma once
 
 #include <string>
 #include <memory>
 
 #include "fastjet/JetDefinition.hh"
 #include "yaml-cpp/yaml.h"
 
 #include "HEJ/event_types.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/HiggsCouplingSettings.hh"
 #include "HEJ/optional.hh"
 #include "HEJ/output_formats.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/utility.hh"
 
 
 namespace HEJ{
 
   //! Jet parameters
   struct JetParameters{
     fastjet::JetDefinition def;          /**< Jet Definition */
     double min_pt;                       /**< Minimum Jet Transverse Momentum */
   };
 
   //! Settings for scale variation
   struct ScaleConfig{
     //! Base scale choices
     std::vector<ScaleFunction> base;
     //! Factors for multiplicative scale variation
     std::vector<double> factors;
     //! Maximum ratio between renormalisation and factorisation scale
     double max_ratio;
   };
 
   //! Settings for random number generator
   struct RNGConfig {
     //! Random number generator name
     std::string name;
     //! Optional initial seed
     optional<std::string> seed;
   };
 
   /**! Possible treatments for fixed-order input events.
    *
    *  The program will decide on how to treat an event based on
    *  the value of this enumeration.
    */
   enum class EventTreatment{
     reweight,                                  /**< Perform resummation */
     keep,                                      /**< Keep the event */
     discard,                                   /**< Discard the event */
   };
 
   //! Container to store the treatments for various event types
   using EventTreatMap = std::map<event_type::EventType, EventTreatment>;
 
   /**! Input parameters.
    *
    * This struct handles stores all configuration parameters
    * needed in a HEJ 2 run.
    *
    * \internal To add a new option:
    *           1. Add a member to the Config struct.
    *           2. Inside "src/YAMLreader.cc":
    *              - Add the option name to the "supported" Node in
    *                get_supported_options.
    *              - Initialise the new Config member in to_Config.
    *                The functions set_from_yaml (for mandatory options) and
    *                set_from_yaml_if_defined (non-mandatory) may be helpful.
    *           3. Add a new entry (with short description) to config.yaml
    *           4. Update the user documentation in "doc/Sphinx/"
    */
   struct Config {
     //! Parameters for scale variation
     ScaleConfig scales;
     //! Resummation jet properties
     JetParameters resummation_jets;
     //! Fixed-order jet properties
     JetParameters fixed_order_jets;
     //! Minimum transverse momentum for extremal partons
     double min_extparton_pt;
     //! Maximum transverse momentum fraction from soft radiation in extremal jets
     double max_ext_soft_pt_fraction;
     //! Number of resummation configurations to generate per fixed-order event
     int trials;
     //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running
     bool log_correction;
     //! Whether to perform unweighting
     bool unweight;
     //! Event output files names and formats
     std::vector<OutputFile> output;
     //! Parameters for random number generation
     RNGConfig rng;
     //! Map to decide what to do for different event types
     EventTreatMap treat;
     //! Parameters for custom analyses
     YAML::Node analysis_parameters;
     //! Settings for effective Higgs-gluon coupling
     HiggsCouplingSettings Higgs_coupling;
   };
 
   //! Configuration options for the PhaseSpacePoint class
   struct PhaseSpacePointConfig {
     //! Properties of resummation jets
     JetParameters jet_param;
     //! Minimum transverse momentum for extremal partons
     double min_extparton_pt;
     //! Maximum transverse momentum fraction from soft radiation in extremal jets
     double max_ext_soft_pt_fraction;
   };
 
   //! Configuration options for the MatrixElement class
   struct MatrixElementConfig {
-    //! Jet properties
-    JetParameters jet_param;
     //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running
     bool log_correction;
     //! Settings for effective Higgs-gluon coupling
     HiggsCouplingSettings Higgs_coupling;
   };
 
   //! Configuration options for the EventReweighter class
   struct EventReweighterConfig {
     //! Settings for phase space point generation
     PhaseSpacePointConfig psp_config;
     //! Settings for matrix element calculation
     MatrixElementConfig ME_config;
     //! Properties of resummation jets
     JetParameters jet_param;
     //! Treatment of the various event types
     EventTreatMap treat;
   };
 
   /**! Extract PhaseSpacePointConfig from Config
    *
    * \internal We do not provide a PhaseSpacePointConfig constructor from Config
    * so that PhaseSpacePointConfig remains an aggregate.
    * This faciliates writing client code (e.g. the HEJ fixed-order generator)
    * that creates a PhaseSpacePointConfig *without* a Config object.
    *
    * @see to_MatrixElementConfig, to_EventReweighterConfig
    */
   inline
   PhaseSpacePointConfig to_PhaseSpacePointConfig(Config const & conf) {
     return {conf.resummation_jets, conf.min_extparton_pt, conf.max_ext_soft_pt_fraction};
   }
 
   /**! Extract MatrixElementConfig from Config
    *
    * @see to_PhaseSpacePointConfig, to_EventReweighterConfig
    */
   inline
   MatrixElementConfig to_MatrixElementConfig(Config const & conf) {
-    return {conf.resummation_jets, conf.log_correction, conf.Higgs_coupling};
+    return {conf.log_correction, conf.Higgs_coupling};
   }
 
   /**! Extract EventReweighterConfig from Config
    *
    * @see to_PhaseSpacePointConfig, to_MatrixElementConfig
    */
   inline
   EventReweighterConfig to_EventReweighterConfig(Config const & conf) {
     return {
       to_PhaseSpacePointConfig(conf),
       to_MatrixElementConfig(conf),
       conf.resummation_jets, conf.treat
     };
   }
 
 } // namespace HEJ
diff --git a/include/HEJ/event_types.hh b/include/HEJ/event_types.hh
index 16f1416..6cf3d82 100644
--- a/include/HEJ/event_types.hh
+++ b/include/HEJ/event_types.hh
@@ -1,42 +1,60 @@
 /** \file
  *  \brief Define different types of events.
  *
  */
 
 #pragma once
 
 #include "HEJ/utility.hh"
 
 namespace HEJ{
 
   //! Namespace for event types
   namespace event_type{
     //! Possible event types
     enum EventType: size_t{
       FKL,                        /**< FKL-type event */
       unordered_backward,         /**< event with unordered backward emission */
       unordered_forward,          /**< event with unordered forward emission */
       nonHEJ,                     /**< event configuration not covered by HEJ */
       no_2_jets,                  /**< event with less than two jets */
       bad_final_state,            /**< event with an unsupported final state */
       unob = unordered_backward,
       unof = unordered_forward,
       first_type = FKL,
       last_type = bad_final_state
     };
 
     //! Event type names
     /**
      * For example, names[FKL] is the string "FKL"
      */
     static constexpr auto names = make_array(
       "FKL",
       "unordered backward",
       "unordered forward",
       "nonHEJ",
       "no 2 jets",
       "bad final state"
     );
+
+    inline
+    bool is_HEJ(EventType type) {
+      switch(type) {
+      case FKL:
+      case unordered_backward:
+      case unordered_forward:
+        return true;
+      default:
+        return false;
+      }
+    }
+
+    inline
+    bool is_uno(EventType type) {
+      return type == unordered_backward || type == unordered_forward;
+    }
+
   }
 
 }
diff --git a/include/HEJ/uno.hh b/include/HEJ/uno.hh
deleted file mode 100644
index 289a556..0000000
--- a/include/HEJ/uno.hh
+++ /dev/null
@@ -1,65 +0,0 @@
-/** \file uno.hh
- *  \brief Functions for determining if event is unordered.
- */
-
-#pragma once
-
-#include "HEJ/utility.hh"
-#include "HEJ/PDG_codes.hh"
-
-namespace HEJ{
-  //! Check if an event has an unordered backward gluon emission
-  /**
-   * @param incoming        Incoming particles in ascending pz
-   * @param outgoing        Outgoing particles in ascending rapidity
-   * @returns               true iff the most backward outgoing particle is a
-   *                        gluon and the first incoming particle is not a gluon.
-   *
-   * If the incoming and outgoing particles are ordered such that HEJ resummation
-   * is possible, this function can help to distinguish between FKL and unordered
-   * events.
-   */
-  inline
-  bool has_unob_gluon(
-      std::array<Particle, 2> const & incoming,
-      std::vector<Particle> const & outgoing
-  ){
-    return incoming.front().type != pid::gluon
-      && outgoing.front().type == pid::gluon;
-  }
-
-
-  //! Check if an event has an unordered backward gluon emission
-  /**
-   * @param incoming        Incoming particles in ascending pz
-   * @param outgoing        Outgoing particles in ascending rapidity
-   * @returns               true iff the most forward outgoing particle is a
-   *                        gluon and the last incoming particle is not a gluon.
-   *
-   * If the incoming and outgoing particles are ordered such that HEJ resummation
-   * is possible, this function can help to distinguish between FKL and unordered
-   * events.
-   */
-  inline
-  bool has_unof_gluon(
-      std::array<Particle, 2> const & incoming,
-      std::vector<Particle> const & outgoing
-  ){
-    return incoming.back().type != pid::gluon
-      && outgoing.back().type == pid::gluon;
-  }
-
-  //! Check if an event has an gluon emission
-  /**
-   * @see has_unob_gluon, has_unof_gluon
-   */
-  inline
-  bool has_uno_gluon(
-      std::array<Particle, 2> const & incoming,
-      std::vector<Particle> const & outgoing
-  ){
-    return
-      has_unob_gluon(incoming, outgoing) || has_unof_gluon(incoming, outgoing);
-  }
-
-}
diff --git a/src/Event.cc b/src/Event.cc
index f098930..6dc344f 100644
--- a/src/Event.cc
+++ b/src/Event.cc
@@ -1,343 +1,341 @@
 #include "HEJ/Event.hh"
 
 #include "HEJ/utility.hh"
 
 namespace HEJ{
 
   namespace{
     constexpr int status_in = -1;
     constexpr int status_decayed = 2;
     constexpr int status_out = 1;
 
-    // helper functions to determine event type
+    /// @name helper functions to determine event type
+    //@{
 
-    // check if there is at most one photon, W, H, Z in the final state
-    // and all the rest are quarks or gluons
+    /**
+     * \brief check if final state valid for HEJ
+     *
+     * check if there is at most one photon, W, H, Z in the final state
+     * and all the rest are quarks or gluons
+     */
     bool final_state_ok(std::vector<Particle> const & outgoing){
       bool has_AWZH_boson = false;
       for(auto const & out: outgoing){
         if(is_AWZH_boson(out.type)){
           if(has_AWZH_boson) return false;
           has_AWZH_boson = true;
         }
         else if(! is_parton(out.type)) return false;
       }
       return true;
     }
 
     template<class Iterator>
     Iterator remove_AWZH(Iterator begin, Iterator end){
       return std::remove_if(
           begin, end, [](Particle const & p){return is_AWZH_boson(p);}
       );
     }
 
     template<class Iterator>
     bool valid_outgoing(Iterator begin, Iterator end){
       return std::distance(begin, end) >= 2
         && std::is_sorted(begin, end, rapidity_less{})
         && std::count_if(
             begin, end, [](Particle const & s){return is_AWZH_boson(s);}
         ) < 2;
     }
 
-    // Note that this changes the outgoing range!
+    /// @note that this changes the outgoing range!
     template<class ConstIterator, class Iterator>
     bool is_FKL(
         ConstIterator begin_incoming, ConstIterator end_incoming,
         Iterator begin_outgoing, Iterator end_outgoing
     ){
       assert(std::distance(begin_incoming, end_incoming) == 2);
       assert(std::distance(begin_outgoing, end_outgoing) >= 2);
 
       // One photon, W, H, Z in the final state is allowed.
       // Remove it for remaining tests,
       end_outgoing = remove_AWZH(begin_outgoing, end_outgoing);
 
       // Test if this is a standard FKL configuration.
       return
         (begin_incoming->type == begin_outgoing->type)
         && ((end_incoming-1)->type == (end_outgoing-1)->type)
         && std::all_of(
             begin_outgoing + 1, end_outgoing - 1,
             [](Particle const & p){ return p.type == pid::gluon; }
         );
     }
 
     bool is_FKL(
         std::array<Particle, 2> const & incoming,
         std::vector<Particle> outgoing
     ){
       assert(std::is_sorted(begin(incoming), end(incoming), pz_less{}));
       assert(valid_outgoing(begin(outgoing), end(outgoing)));
 
       return is_FKL(
           begin(incoming), end(incoming),
           begin(outgoing), end(outgoing)
       );
     }
 
     bool has_2_jets(Event const & event){
       return event.jets().size() >= 2;
     }
 
     /**
      * \brief Checks whether event is unordered backwards
      * @param ev        Event
      * @returns         Is Event Unordered Backwards
      *
-     * Checks there is more than 3 constuents in the final state
-     * Checks there is more than 3 jets
-     * Checks the most backwards parton is a gluon
-     * Checks the most forwards jet is not a gluon
-     * Checks the rest of the event is FKL
-     * Checks the second most backwards is not a different boson
-     * Checks the unordered gluon actually forms a jet
+     * - Checks there is more than 3 constuents in the final state
+     * - Checks there is more than 3 jets
+     * - Checks the most backwards parton is a gluon
+     * - Checks the most forwards jet is not a gluon
+     * - Checks the rest of the event is FKL
+     * - Checks the second most backwards is not a different boson
+     * - Checks the unordered gluon actually forms a jet
      */
     bool is_unordered_backward(Event const & ev){
       auto const & in = ev.incoming();
       auto const & out = ev.outgoing();
       assert(std::is_sorted(begin(in), end(in), pz_less{}));
       assert(valid_outgoing(begin(out), end(out)));
 
       if(out.size() < 3) return false;
       if(ev.jets().size() < 3) return false;
       if(in.front().type == pid::gluon) return false;
       if(out.front().type != pid::gluon) return false;
       // When skipping the unordered emission
       // the remainder should be a regular FKL event,
       // except that the (new) first outgoing particle must not be a A,W,Z,H.
       const auto FKL_begin = next(begin(out));
       if(is_AWZH_boson(*FKL_begin)) return false;
       if(!is_FKL(in, {FKL_begin, end(out)})) return false;
       // check that the unordered gluon forms an extra jet
       const auto jets = sorted_by_rapidity(ev.jets());
       const auto indices = ev.particle_jet_indices({jets.front()});
       return indices[0] >= 0 && indices[1] == -1;
     }
 
     /**
      * \brief Checks for a forward unordered gluon emission
      * @param ev          Event
      * @returns           Is the event a forward unordered emission
      *
      * \see is_unordered_backward
      */
     bool is_unordered_forward(Event const & ev){
       auto const & in = ev.incoming();
       auto const & out = ev.outgoing();
       assert(std::is_sorted(begin(in), end(in), pz_less{}));
       assert(valid_outgoing(begin(out), end(out)));
 
       if(out.size() < 3) return false;
       if(ev.jets().size() < 3) return false;
       if(in.back().type == pid::gluon) return false;
       if(out.back().type != pid::gluon) return false;
       // When skipping the unordered emission
       // the remainder should be a regular FKL event,
       // except that the (new) last outgoing particle must not be a A,W,Z,H.
       const auto FKL_end = prev(end(out));
       if(is_AWZH_boson(*prev(FKL_end))) return false;
       if(!is_FKL(in, {begin(out), FKL_end})) return false;
       // check that the unordered gluon forms an extra jet
       const auto jets = sorted_by_rapidity(ev.jets());
       const auto indices = ev.particle_jet_indices({jets.back()});
       return indices.back() >= 0 && indices[indices.size()-2] == -1;
     }
 
     using event_type::EventType;
 
     EventType classify(Event const & ev){
       if(! final_state_ok(ev.outgoing())) return EventType::bad_final_state;
       if(! has_2_jets(ev)) return EventType::no_2_jets;
       if(is_FKL(ev.incoming(), ev.outgoing())) return EventType::FKL;
       if(is_unordered_backward(ev)){
         return EventType::unordered_backward;
       }
       if(is_unordered_forward(ev)){
         return EventType::unordered_forward;
       }
       return EventType::nonHEJ;
     }
+    //@}
 
     Particle extract_particle(LHEF::HEPEUP const & hepeup, int i){
       return Particle{
         static_cast<ParticleID>(hepeup.IDUP[i]),
         fastjet::PseudoJet{
           hepeup.PUP[i][0], hepeup.PUP[i][1],
           hepeup.PUP[i][2], hepeup.PUP[i][3]
         }
       };
     }
 
     bool is_decay_product(std::pair<int, int> const & mothers){
       if(mothers.first == 0) return false;
       return mothers.second == 0 || mothers.first == mothers.second;
     }
 
-  }
+  } // namespace anonymous
 
   UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup):
     central(EventParameters{
       hepeup.scales.mur, hepeup.scales.muf, hepeup.weight()
     })
   {
     size_t in_idx = 0;
     for (int i = 0; i < hepeup.NUP; ++i) {
       // skip decay products
       // we will add them later on, but we have to ensure that
       // the decayed particle is added before
       if(is_decay_product(hepeup.MOTHUP[i])) continue;
 
       auto particle = extract_particle(hepeup, i);
       // needed to identify mother particles for decay products
       particle.p.set_user_index(i+1);
 
       if(hepeup.ISTUP[i] == status_in){
          if(in_idx > incoming.size()) {
            throw std::invalid_argument{
              "Event has too many incoming particles"
            };
          }
          incoming[in_idx++] = std::move(particle);
       }
       else outgoing.emplace_back(std::move(particle));
     }
     std::sort(
         begin(incoming), end(incoming),
         [](Particle o1, Particle o2){return o1.p.pz()<o2.p.pz();}
     );
     std::sort(begin(outgoing), end(outgoing), rapidity_less{});
 
     // add decay products
     for (int i = 0; i < hepeup.NUP; ++i) {
       if(!is_decay_product(hepeup.MOTHUP[i])) continue;
       const int mother_id = hepeup.MOTHUP[i].first;
       const auto mother = std::find_if(
           begin(outgoing), end(outgoing),
           [mother_id](Particle const & particle){
             return particle.p.user_index() == mother_id;
           }
       );
       if(mother == end(outgoing)){
         throw std::invalid_argument{"invalid decay product parent"};
       }
       const int mother_idx = std::distance(begin(outgoing), mother);
       assert(mother_idx >= 0);
       decays[mother_idx].emplace_back(extract_particle(hepeup, i));
     }
   }
 
   Event::Event(
       UnclusteredEvent ev,
       fastjet::JetDefinition const & jet_def, double min_jet_pt
   ):
     ev_{std::move(ev)},
     cs_{to_PseudoJet(filter_partons(ev_.outgoing)), jet_def},
     min_jet_pt_{min_jet_pt}
   {
     type_ = classify(*this);
   }
 
   std::vector<fastjet::PseudoJet> Event::jets() const{
     return cs_.inclusive_jets(min_jet_pt_);
   }
 
-  /**
-   * \brief Returns the invarient mass of the event
-   * @param ev         Event
-   * @returns          s hat
-   *
-   * Makes use of the FastJet PseudoJet function m2().
-   * Applies this function to the sum of the incoming partons.
-   */
   double shat(Event const & ev){
     return (ev.incoming()[0].p + ev.incoming()[1].p).m2();
   }
 
   namespace{
     // colour flow according to Les Houches standard
     // TODO: stub
     std::vector<std::pair<int, int>> colour_flow(
         std::array<Particle, 2> const & incoming,
         std::vector<Particle> const & outgoing
     ){
       std::vector<std::pair<int, int>> result(
           incoming.size() + outgoing.size()
       );
       for(auto & col: result){
         col = std::make_pair(-1, -1);
       }
       return result;
     }
   }
 
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * heprup){
     LHEF::HEPEUP result;
     result.heprup = heprup;
     result.weights = {{event.central().weight, nullptr}};
     for(auto const & var: event.variations()){
       result.weights.emplace_back(var.weight, nullptr);
     }
     size_t num_particles = event.incoming().size() + event.outgoing().size();
     for(auto const & decay: event.decays()) num_particles += decay.second.size();
     result.NUP = num_particles;
     // the following entries are pretty much meaningless
     result.IDPRUP = event.type()+1;  // event ID
     result.AQEDUP = 1./128.;  // alpha_EW
     //result.AQCDUP = 0.118 // alpha_QCD
     // end meaningless part
     result.XWGTUP = event.central().weight;
     result.SCALUP = event.central().muf;
     result.scales.muf = event.central().muf;
     result.scales.mur = event.central().mur;
     result.scales.SCALUP = event.central().muf;
     result.pdfinfo.p1 = event.incoming().front().type;
     result.pdfinfo.p2 = event.incoming().back().type;
     result.pdfinfo.scale = event.central().muf;
     for(Particle const & in: event.incoming()){
       result.IDUP.emplace_back(in.type);
       result.ISTUP.emplace_back(status_in);
       result.PUP.push_back({in.p[0], in.p[1], in.p[2], in.p[3], in.p.m()});
       result.MOTHUP.emplace_back(0, 0);
     }
     for(size_t i = 0; i < event.outgoing().size(); ++i){
       Particle const & out = event.outgoing()[i];
       result.IDUP.emplace_back(out.type);
       const int status = event.decays().count(i)?status_decayed:status_out;
       result.ISTUP.emplace_back(status);
       result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()});
       result.MOTHUP.emplace_back(1, 2);
     }
     result.ICOLUP = colour_flow(
         event.incoming(), filter_partons(event.outgoing())
     );
     if(result.ICOLUP.size() < num_particles){
       const size_t AWZH_boson_idx = std::find_if(
           begin(event.outgoing()), end(event.outgoing()),
           [](Particle const & s){ return is_AWZH_boson(s); }
       ) - begin(event.outgoing()) + event.incoming().size();
       assert(AWZH_boson_idx <= result.ICOLUP.size());
       result.ICOLUP.insert(
           begin(result.ICOLUP) + AWZH_boson_idx,
           std::make_pair(0,0)
       );
     }
     for(auto const & decay: event.decays()){
       for(auto const out: decay.second){
         result.IDUP.emplace_back(out.type);
         result.ISTUP.emplace_back(status_out);
         result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()});
         const size_t mother_idx = 1 + event.incoming().size() + decay.first;
         result.MOTHUP.emplace_back(mother_idx, mother_idx);
         result.ICOLUP.emplace_back(0,0);
       }
     }
     assert(result.ICOLUP.size() == num_particles);
     static constexpr double unknown_spin = 9.;     //per Les Houches accord
     result.VTIMUP = std::vector<double>(num_particles, unknown_spin);
     result.SPINUP = result.VTIMUP;
     return result;
   }
 
 }
diff --git a/src/EventReweighter.cc b/src/EventReweighter.cc
index 7687fb2..72e1f6d 100644
--- a/src/EventReweighter.cc
+++ b/src/EventReweighter.cc
@@ -1,343 +1,259 @@
 #include "HEJ/EventReweighter.hh"
 
 #include <string>
 #include <unordered_map>
 
+#include "HEJ/exceptions.hh"
 #include "HEJ/PhaseSpacePoint.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/utility.hh"
 
 namespace HEJ{
   using EventType = event_type::EventType;
 
   namespace {
 
     static_assert(
         std::numeric_limits<double>::has_quiet_NaN,
         "no quiet NaN for double"
     );
     constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
 
     UnclusteredEvent to_UnclusteredEvent(PhaseSpacePoint const & psp){
       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),
               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 anonymous
 
   EventReweighter::EventReweighter(
       LHEF::HEPRUP const & heprup,
       ScaleGenerator scale_gen,
       EventReweighterConfig conf,
       HEJ::RNG & ran
   ):
     EventReweighter{
       HEJ::Beam{
         heprup.EBMUP.first,
         {{
           static_cast<HEJ::ParticleID>(heprup.IDBMUP.first),
           static_cast<HEJ::ParticleID>(heprup.IDBMUP.second)
         }}
       },
       heprup.PDFSUP.first,
       std::move(scale_gen),
       std::move(conf),
       ran
     }
   {
     if(heprup.EBMUP.second != E_beam_){
       throw std::invalid_argument(
           "asymmetric beam: " + std::to_string(E_beam_)
           + " ---> <--- " + std::to_string(heprup.EBMUP.second)
       );
     };
     if(heprup.PDFSUP.second != pdf_.id()){
       throw std::invalid_argument(
           "conflicting PDF ids: " + std::to_string(pdf_.id())
           + " vs. " + std::to_string(heprup.PDFSUP.second)
       );
     }
   }
 
   EventReweighter::EventReweighter(
       Beam beam,
       int pdf_id,
       ScaleGenerator scale_gen,
       EventReweighterConfig conf,
       HEJ::RNG & ran
   ):
     param_{std::move(conf)},
     E_beam_{beam.E},
     pdf_{pdf_id, beam.type.front(), beam.type.back()},
     MEt2_{
       [this](double mu){ return pdf_.Halphas(mu); },
       param_.ME_config
     },
     scale_gen_(std::move(scale_gen)),
     ran_{ran}
   {}
 
   PDF const & EventReweighter::pdf() const{
     return pdf_;
   }
 
   std::vector<Event> EventReweighter::reweight(
       Event const & input_ev, int num_events
   ){
     auto res_events = gen_res_events(input_ev, num_events);
     if(res_events.empty()) return {};
     for(auto & event: res_events) event = scale_gen_(event);
     return rescale(input_ev, std::move(res_events));
   }
 
-  /**
-   * \brief main generation/reweighting function:
-   * generate phase space points and divide out Born factors
-   */
   std::vector<Event> EventReweighter::gen_res_events(
       Event const & ev,
       int phase_space_points
   ){
     assert(ev.variations().empty());
 
     switch(param_.treat.at(ev.type())){
     case EventTreatment::discard: return {};
     case EventTreatment::keep:
       if(! jets_pass_resummation_cuts(ev)) return {};
       else return {ev};
     default:;
     }
     const double Born_shat = shat(ev);
 
     std::vector<Event> resummation_events;
     for(int psp_number = 0; psp_number < phase_space_points; ++psp_number){
       PhaseSpacePoint psp{ev, param_.psp_config, ran_};
       if(psp.weight() == 0.) continue;
       if(psp.incoming()[0].E() > E_beam_ || psp.incoming()[1].E() > E_beam_) continue;
 
       resummation_events.emplace_back(
           to_UnclusteredEvent(std::move(psp)),
           param_.jet_param.def, param_.jet_param.min_pt
       );
       auto & new_event = resummation_events.back();
       assert(new_event.variations().empty());
       new_event.central().mur = ev.central().mur;
       new_event.central().muf = ev.central().muf;
       const double resum_shat = shat(new_event);
       new_event.central().weight *= ev.central().weight*Born_shat*Born_shat/
         (phase_space_points*resum_shat*resum_shat);
     }
     return resummation_events;
   }
 
   std::vector<Event> EventReweighter::rescale(
       Event const & Born_ev,
       std::vector<Event> events
   ) const{
     const double Born_pdf = pdf_factors(Born_ev).central;
     const double Born_ME = tree_matrix_element(Born_ev);
 
     for(auto & cur_event: events){
       const auto pdf = pdf_factors(cur_event);
       assert(pdf.variations.size() == cur_event.variations().size());
       const auto ME = matrix_elements(cur_event);
       assert(ME.variations.size() == cur_event.variations().size());
       cur_event.central().weight *= pdf.central*ME.central/(Born_pdf*Born_ME);
       for(size_t i = 0; i < cur_event.variations().size(); ++i){
         cur_event.variations(i).weight *=
           pdf.variations[i]*ME.variations[i]/(Born_pdf*Born_ME);
       }
     }
     return events;
   };
 
-  /**
-   * \brief Do the Jets pass the resummation Cuts?
-   *
-   * @param ev               Event in Question
-   * @returns                0 or 1 depending on if ev passes Jet Cuts
-   */
   bool EventReweighter::jets_pass_resummation_cuts(
       Event const & ev
   ) const{
     const auto out_as_PseudoJet = to_PseudoJet(filter_partons(ev.outgoing()));
     fastjet::ClusterSequence cs{out_as_PseudoJet, param_.jet_param.def};
     return cs.inclusive_jets(param_.jet_param.min_pt).size() == ev.jets().size();
   }
 
-
-
-  /**
-   * \brief pdf_factors Function
-   *
-   * @param ev         Event in Question
-   * @returns          EventFactor due to PDFs
-   *
-   * Calculates the Central value and the variation due
-   * to the PDF choice made.
-   */
-  EventReweighter::EventFactors
+  Weights
   EventReweighter::pdf_factors(Event const & ev) const{
     auto const & a = ev.incoming().front();
     auto const & b = ev.incoming().back();
     const double xa = a.p.e()/E_beam_;
     const double xb = b.p.e()/E_beam_;
 
-    EventFactors result;
+    Weights result;
     std::unordered_map<double, double> known_pdf;
     result.central =
       pdf_.pdfpt(0,xa,ev.central().muf,a.type)*
       pdf_.pdfpt(1,xb,ev.central().muf,b.type);
     known_pdf.emplace(ev.central().muf, result.central);
 
     result.variations.reserve(ev.variations().size());
     for(auto const & ev_param: ev.variations()){
       const double muf = ev_param.muf;
       auto cur_pdf = known_pdf.find(muf);
       if(cur_pdf == known_pdf.end()){
         cur_pdf = known_pdf.emplace(
             muf,
             pdf_.pdfpt(0,xa,muf,a.type)*pdf_.pdfpt(1,xb,muf,b.type)
         ).first;
       }
       result.variations.emplace_back(cur_pdf->second);
     }
     assert(result.variations.size() == ev.variations().size());
     return result;
   }
 
-
-
-  /**
-   * \brief matrix_elements Function
-   *
-   * @param ev         Event in question
-   * @returns          EventFactor due to MatrixElements
-   *
-   * Calculates the Central value and the variation due
-   * to the Matrix Element.
-   */
-  EventReweighter::EventFactors
+  Weights
   EventReweighter::matrix_elements(Event const & ev) const{
     assert(param_.treat.count(ev.type()) > 0);
     if(param_.treat.find(ev.type())->second == EventTreatment::keep){
       return fixed_order_scale_ME(ev);
     }
 
-    // precompute overall kinematic factor
-    const double ME_kin = MEt2_.tree_kin(ev.incoming(), ev.outgoing(), true);
-
-    EventFactors result;
-    std::unordered_map<double, double> known_ME;
-    result.central = MEt2_(
-        ev.central().mur,
-        ev.incoming(), ev.outgoing(),
-        true
-    );
-    known_ME.emplace(ev.central().mur, result.central);
-
-    result.variations.reserve(ev.variations().size());
-    for(auto const & param: ev.variations()){
-      const double mur = param.mur;
-      auto cur_ME = known_ME.find(mur);
-      if(cur_ME == known_ME.end()){
-        const double ME = MEt2_.tree_param(
-            mur, ev.incoming(), ev.outgoing()
-        )*ME_kin*MEt2_.virtual_corrections(
-            mur, ev.incoming(), ev.outgoing()
-        );
-        cur_ME = known_ME.emplace(mur, ME).first;
-      }
-      result.variations.emplace_back(cur_ME->second);
-    }
-    assert(result.variations.size() == ev.variations().size());
-    return result;
+    return MEt2_(ev, true);
   }
 
-  /**
-   * \brief Computes the tree level matrix element
-   *
-   * @param ev                Event in Question
-   * @returns                 HEJ approximation to Tree level Matrix Element
-   *
-   * This computes the HEJ approximation to the tree level FO
-   * Matrix element which is used within the LO weighting process.
-   */
   double EventReweighter::tree_matrix_element(Event const & ev) const{
     assert(ev.variations().empty());
     assert(param_.treat.count(ev.type()) > 0);
     if(param_.treat.find(ev.type())->second == EventTreatment::keep){
       return fixed_order_scale_ME(ev).central;
     }
-    return MEt2_.tree(
-        ev.central().mur,
-        ev.incoming(), ev.outgoing(),
-        false
-    );
+    return MEt2_.tree(ev, false).central;
   }
 
-  /**
-   * \brief Scale-dependent part of fixed-order matrix element
-   *
-   * @param ev         Event in question
-   * @returns          EventFactor scale variation due to FO-ME.
-   *
-   * This is only called to compute the scale variation for events where
-   * we don't do resummation (e.g. non-FKL).
-   * Since at tree level the scale dependence is just due to alpha_s,
-   * it is enough to return the alpha_s(mur) factors in the matrix element.
-   * The rest drops out in the ratio of (output event ME)/(input event ME),
-   * so we never have to compute it.
-   */
-  EventReweighter::EventFactors
+  Weights
   EventReweighter::fixed_order_scale_ME(Event const & ev) const{
     int alpha_s_power = 0;
     for(auto const & part: ev.outgoing()){
       if(is_parton(part))
         ++alpha_s_power;
       else {
         switch(part.type){
         case pid::Higgs: {
           alpha_s_power += 2;
           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");
+          throw not_implemented("Emission of boson of unsupported type");
         }
       }
     }
 
-    EventFactors result;
+    Weights result;
     result.central = pow(pdf_.Halphas(ev.central().mur), alpha_s_power);
     for(auto const & var: ev.variations()){
       result.variations.emplace_back(
           pow(pdf_.Halphas(var.mur), alpha_s_power)
       );
     }
     return result;
   }
 
 } // namespace HEJ
diff --git a/src/MatrixElement.cc b/src/MatrixElement.cc
index e9c644d..5d31d30 100644
--- a/src/MatrixElement.cc
+++ b/src/MatrixElement.cc
@@ -1,826 +1,860 @@
 #include "HEJ/MatrixElement.hh"
 
+#include <unordered_map>
+
 #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/event_types.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;
   }
 
+  Weights MatrixElement::operator()(
+      Event const & event,
+      bool check_momenta
+  ) const {
+    return tree(event, check_momenta)*virtual_corrections(event);
+  }
+
+  Weights MatrixElement::tree(
+      Event const & event,
+      bool check_momenta
+  ) const {
+    if(! is_HEJ(event.type())) {
+      return Weights{0., std::vector<double>(event.variations().size(), 0.)};
+    }
+    return tree_param(event)*tree_kin(event, check_momenta);
+  }
+
+  Weights MatrixElement::tree_param(
+      Event const & event
+  ) const {
+    Weights result;
+    // only compute once for each renormalisation scale
+    std::unordered_map<double, double> known;
+    result.central = tree_param(event, event.central().mur);
+    known.emplace(event.central().mur, result.central);
+    for(auto const & var: event.variations()) {
+      const auto ME_it = known.find(var.mur);
+      if(ME_it == end(known)) {
+        const double wt = tree_param(event, var.mur);
+        result.variations.emplace_back(wt);
+        known.emplace(var.mur, wt);
+      }
+      else {
+        result.variations.emplace_back(ME_it->second);
+      }
+    }
+    return result;
+  }
+
+  Weights MatrixElement::virtual_corrections(
+      Event const & event
+  ) const {
+    if(! is_HEJ(event.type())) {
+      return Weights{0., std::vector<double>(event.variations().size(), 0.)};
+    }
+    Weights result;
+    // only compute once for each renormalisation scale
+    std::unordered_map<double, double> known;
+    result.central = virtual_corrections(event, event.central().mur);
+    known.emplace(event.central().mur, result.central);
+    for(auto const & var: event.variations()) {
+      const auto ME_it = known.find(var.mur);
+      if(ME_it == end(known)) {
+        const double wt = virtual_corrections(event, var.mur);
+        result.variations.emplace_back(wt);
+        known.emplace(var.mur, wt);
+      }
+      else {
+        result.variations.emplace_back(ME_it->second);
+      }
+    }
+    return result;
+  }
+
   double MatrixElement::virtual_corrections(
-      double mur,
-      std::array<Particle, 2> const & in,
-      std::vector<Particle> const & out
+      Event const & event,
+      double mur
   ) const{
+    auto const & in = event.incoming();
+    auto const & out = event.outgoing();
     fastjet::PseudoJet const & pa = in.front().p;
 #ifndef NDEBUG
     fastjet::PseudoJet const & pb = in.back().p;
     double const norm = (in.front().p + in.back().p).E();
 #endif
 
     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)){
+    if(out.front().type == pid::Higgs || event.type() == event_type::unob){
       q -= out[1].p;
       ++first_idx;
     }
-    if(out.back().type == pid::Higgs || has_unof_gluon(in, out)){
+    if(out.back().type == pid::Higgs || event.type() == event_type::unof){
       --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)
+        || event.type() == event_type::unof
     );
     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,
+      Event const & ev,
       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),
+        begin(ev.outgoing()), end(ev.outgoing()),
         [](Particle const & p){return is_AWZH_boson(p);}
     );
 
-    if(AWZH_boson == end(outgoing)){
-      return tree_kin_jets(incoming, outgoing, check_momenta);
+    if(AWZH_boson == end(ev.outgoing())){
+      return tree_kin_jets(ev, check_momenta);
     }
 
     switch(AWZH_boson->type){
     case pid::Higgs: {
-      return tree_kin_Higgs(incoming, outgoing, check_momenta);
+      return tree_kin_Higgs(ev, 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");
+      throw not_implemented("Emission of boson of unsupported type");
     }
   }
 
   namespace{
     constexpr int extremal_jet_idx = 1;
     constexpr int no_extremal_jet_idx = 0;
 
     bool treat_as_extremal(Particle const & parton){
       return parton.p.user_index() == extremal_jet_idx;
     }
 
     template<class InputIterator>
       double FKL_ladder_weight(
           InputIterator begin_gluon, InputIterator end_gluon,
           CLHEP::HepLorentzVector const & q0,
           CLHEP::HepLorentzVector const & pa, CLHEP::HepLorentzVector const & pb,
           CLHEP::HepLorentzVector const & p1, CLHEP::HepLorentzVector const & pn
       ){
       double 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
+      Event const & ev, bool check_momenta
   ) const{
+    auto out_partons = filter_partons(ev.outgoing());
     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));
+    // TODO: avoid reclustering
+    fastjet::ClusterSequence cs(to_PseudoJet(out_partons), ev.jet_def());
+    const auto jets = sorted_by_rapidity(cs.inclusive_jets(ev.min_jet_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)){
+    if(ev.type() == event_type::unob){
       assert(jets.size() >= 3);
       ++most_backward;
     }
-    else if(has_unof_gluon(incoming, out_partons)){
+    else if(ev.type() == event_type::unof){
       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,
+      Event const & ev,
       bool check_momenta
   ) const {
-    partons = tag_extremal_jet_partons(incoming, partons, check_momenta);
-    if(has_unob_gluon(incoming, partons) || has_unof_gluon(incoming, partons)){
+    auto const & incoming = ev.incoming();
+    const auto partons = tag_extremal_jet_partons(ev, check_momenta);
+    if(is_uno(ev.type())){
       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,
+      Event const & ev,
       bool check_momenta
   ) const {
-    if(has_uno_gluon(incoming, outgoing)){
-      return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
+    if(is_uno(ev.type())){
+      return tree_kin_Higgs_between(ev, check_momenta);
     }
-    if(outgoing.front().type == pid::Higgs){
-      return tree_kin_Higgs_first(incoming, outgoing, check_momenta);
+    if(ev.outgoing().front().type == pid::Higgs){
+      return tree_kin_Higgs_first(ev, check_momenta);
     }
-    if(outgoing.back().type == pid::Higgs){
-      return tree_kin_Higgs_last(incoming, outgoing, check_momenta);
+    if(ev.outgoing().back().type == pid::Higgs){
+      return tree_kin_Higgs_last(ev, check_momenta);
     }
-    return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
+    return tree_kin_Higgs_between(ev, 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,
+      Event const & ev,
       bool check_momenta
   ) const {
+    auto const & incoming = ev.incoming();
+    auto const & outgoing = ev.outgoing();
     assert(outgoing.front().type == pid::Higgs);
     if(outgoing[1].type != pid::gluon) {
       assert(incoming.front().type == outgoing[1].type);
-      return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
+      return tree_kin_Higgs_between(ev, 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
+        ev, 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,
+      Event const & ev,
       bool check_momenta
   ) const {
+    auto const & incoming = ev.incoming();
+    auto const & outgoing = ev.outgoing();
     assert(outgoing.back().type == pid::Higgs);
     if(outgoing[outgoing.size()-2].type != pid::gluon) {
       assert(incoming.back().type == outgoing[outgoing.size()-2].type);
-      return tree_kin_Higgs_between(incoming, outgoing, check_momenta);
+      return tree_kin_Higgs_between(ev, 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
+        ev, 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,
+      Event const & ev,
       bool check_momenta
   ) const {
+    using namespace event_type;
+    auto const & incoming = ev.incoming();
+    auto const & outgoing = ev.outgoing();
+
     const auto the_Higgs = std::find_if(
         begin(outgoing), end(outgoing),
         [](Particle const & s){ return s.type == pid::Higgs; }
     );
     assert(the_Higgs != end(outgoing));
     const auto pH = to_HepLorentzVector(*the_Higgs);
-    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 partons = tag_extremal_jet_partons(ev, 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]
+        partons[(ev.type() == unob)?1:0]
     );
     auto pn = to_HepLorentzVector(
-        partons[partons.size() - (has_unof_gluon(incoming, outgoing)?2:1)]
+        partons[partons.size() - ((ev.type() == unof)?2:1)]
     );
 
     auto first_after_Higgs = begin(partons) + (the_Higgs-begin(outgoing));
     assert(
         (first_after_Higgs == end(partons) && (
-            has_unob_gluon(incoming, outgoing)
+            (ev.type() == unob)
             || partons.back().type != pid::gluon
         ))
         || first_after_Higgs->rapidity() >= the_Higgs->rapidity()
     );
     assert(
         (first_after_Higgs == begin(partons) && (
-            has_unof_gluon(incoming, outgoing)
+            (ev.type() == unof)
             || partons.front().type != pid::gluon
         ))
         || (first_after_Higgs-1)->rapidity() <= the_Higgs->rapidity()
     );
     // always treat the Higgs as if it were in between the extremal FKL partons
     if(first_after_Higgs == begin(partons)) ++first_after_Higgs;
     else if(first_after_Higgs == end(partons)) --first_after_Higgs;
 
     // t-channel momentum before Higgs
     auto qH = pa;
     for(auto parton_it = begin(partons); parton_it != first_after_Higgs; ++parton_it){
       qH -= to_HepLorentzVector(*parton_it);
     }
 
     auto q0 = pa - p1;
     auto begin_ladder = begin(partons) + 1;
     auto end_ladder = end(partons) - 1;
 
     double current_factor;
-    if(has_unob_gluon(incoming, outgoing)){
+    if(ev.type() == unob){
       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)){
+    else if(ev.type() == unof){
       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
+      Event const & ev,
+      double mur
   ) const{
+    assert(is_HEJ(ev.type()));
+
+    const auto & out = ev.outgoing();
     const double alpha_s = alpha_s_(mur);
     auto AWZH_boson = std::find_if(
-        begin(outgoing), end(outgoing),
+        begin(out), end(out),
         [](auto const & p){return is_AWZH_boson(p);}
     );
     double AWZH_coupling = 1.;
-    if(AWZH_boson != end(outgoing)){
+    if(AWZH_boson != end(out)){
       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");
+        throw not_implemented("Emission of boson of unsupported type");
       }
     }
-    if(has_unob_gluon(incoming, outgoing)){
+    if(ev.type() == event_type::unob){
       return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons(
-          alpha_s, mur, filter_partons({begin(outgoing) + 1, end(outgoing)})
+          alpha_s, mur, filter_partons({begin(out) + 1, end(out)})
       );
     }
-    if(has_unof_gluon(incoming, outgoing)){
+    if(ev.type() == event_type::unof){
       return AWZH_coupling*4*M_PI*alpha_s*tree_param_partons(
-          alpha_s, mur, filter_partons({begin(outgoing), end(outgoing) - 1})
+          alpha_s, mur, filter_partons({begin(out), end(out) - 1})
       );
     }
-    return AWZH_coupling*tree_param_partons(alpha_s, mur, filter_partons(outgoing));
+    return AWZH_coupling*tree_param_partons(alpha_s, mur, filter_partons(out));
   }
 
-  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/PhaseSpacePoint.cc b/src/PhaseSpacePoint.cc
index d3f1850..ff32eb3 100644
--- a/src/PhaseSpacePoint.cc
+++ b/src/PhaseSpacePoint.cc
@@ -1,538 +1,537 @@
 #include "HEJ/PhaseSpacePoint.hh"
 
 #include <random>
 
 #include <CLHEP/Random/Randomize.h>
 #include <CLHEP/Random/RanluxEngine.h>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/resummation_jet.hh"
-#include "HEJ/uno.hh"
 #include "HEJ/utility.hh"
 #include "HEJ/kinematics.hh"
 
 namespace HEJ{
   namespace {
     constexpr int max_jet_user_idx = PhaseSpacePoint::ng_max;
 
     bool is_nonjet_parton(fastjet::PseudoJet const & parton){
       assert(parton.user_index() != -1);
       return parton.user_index() > max_jet_user_idx;
     }
 
     bool is_jet_parton(fastjet::PseudoJet const & parton){
       assert(parton.user_index() != -1);
       return parton.user_index() <= max_jet_user_idx;
     }
 
     // user indices for partons with extremal rapidity
     constexpr int unob_idx = -5;
     constexpr int unof_idx = -4;
     constexpr int backward_FKL_idx = -3;
     constexpr int forward_FKL_idx = -2;
   }
 
   namespace {
     double estimate_ng_mean(std::vector<fastjet::PseudoJet> const & Born_jets){
       const double delta_y =
         Born_jets.back().rapidity() - Born_jets.front().rapidity();
       assert(delta_y > 0);
       // Formula derived from fit in arXiv:1805.04446 (see Fig. 2)
       return 0.975052*delta_y;
     }
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::cluster_jets(
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     fastjet::ClusterSequence cs(partons, param_.jet_param.def);
     return cs.inclusive_jets(param_.jet_param.min_pt);
   }
 
   bool PhaseSpacePoint::pass_resummation_cuts(
       std::vector<fastjet::PseudoJet> const & jets
   ) const{
     return cluster_jets(jets).size() == jets.size();
   }
 
   int PhaseSpacePoint::sample_ng(std::vector<fastjet::PseudoJet> const & Born_jets){
     const double ng_mean = estimate_ng_mean(Born_jets);
     std::poisson_distribution<int> dist(ng_mean);
     const int ng = dist(ran_.get());
     assert(ng >= 0);
     assert(ng < ng_max);
     weight_ *= std::tgamma(ng + 1)*std::exp(ng_mean)*std::pow(ng_mean, -ng);
     return ng;
   }
 
   void PhaseSpacePoint::copy_AWZH_boson_from(Event const & event){
     auto const & from = event.outgoing();
 
     const auto AWZH_boson = std::find_if(
         begin(from), end(from),
         [](Particle const & p){ return is_AWZH_boson(p); }
     );
     if(AWZH_boson == end(from)) return;
     auto insertion_point = std::lower_bound(
         begin(outgoing_), end(outgoing_), *AWZH_boson, rapidity_less{}
     );
     outgoing_.insert(insertion_point, *AWZH_boson);
 
     // copy decay products
     const int idx = std::distance(begin(from), AWZH_boson);
     assert(idx >= 0);
     const auto decay_it = event.decays().find(idx);
     if(decay_it != end(event.decays())){
       const int new_idx = std::distance(begin(outgoing_), insertion_point);
       assert(new_idx >= 0);
       assert(outgoing_[new_idx].type == AWZH_boson->type);
       decays_.emplace(new_idx, decay_it->second);
     }
 
     assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{}));
   }
 
   PhaseSpacePoint::PhaseSpacePoint(
       Event const & ev, PhaseSpacePointConfig conf, HEJ::RNG & ran
   ):
-    unob_{has_unob_gluon(ev.incoming(), ev.outgoing())},
-    unof_{!unob_ && has_unof_gluon(ev.incoming(), ev.outgoing())},
+    unob_{ev.type() == event_type::unob},
+    unof_{ev.type() == event_type::unof},
     param_{std::move(conf)},
     ran_{ran}
   {
     weight_ = 1;
     const auto Born_jets = sorted_by_rapidity(ev.jets());
     const int ng = sample_ng(Born_jets);
     weight_ /= std::tgamma(ng + 1);
     const int ng_jets = sample_ng_jets(ng, Born_jets);
     std::vector<fastjet::PseudoJet> out_partons = gen_non_jet(
        ng - ng_jets, CMINPT, param_.jet_param.min_pt
     );
     {
       const auto qperp = std::accumulate(
         begin(out_partons), end(out_partons),
         fastjet::PseudoJet{}
       );
       const auto jets = reshuffle(Born_jets, qperp);
       if(weight_ == 0.) return;
       if(! pass_resummation_cuts(jets)){
        weight_ = 0.;
        return;
       }
       std::vector<fastjet::PseudoJet> jet_partons = split(jets, ng_jets);
       if(weight_ == 0.) return;
       rescale_rapidities(
         out_partons,
         most_backward_FKL(jet_partons).rapidity(),
         most_forward_FKL(jet_partons).rapidity()
       );
       if(! cluster_jets(out_partons).empty()){
         weight_ = 0.;
         return;
       }
       std::sort(begin(out_partons), end(out_partons), rapidity_less{});
       assert(
         std::is_sorted(begin(jet_partons), end(jet_partons), rapidity_less{})
       );
       const auto first_jet_parton = out_partons.insert(
         end(out_partons), begin(jet_partons), end(jet_partons)
       );
       std::inplace_merge(
         begin(out_partons), first_jet_parton, end(out_partons), rapidity_less{}
       );
     }
     if(! jets_ok(Born_jets, out_partons)){
       weight_ = 0.;
       return;
     }
     weight_ *= phase_space_normalisation(Born_jets.size(), out_partons.size());
 
     outgoing_.reserve(out_partons.size() + 1); // one slot for possible A, W, Z, H
     for(auto & p: out_partons){
       outgoing_.emplace_back(Particle{pid::gluon, std::move(p)});
     }
     most_backward_FKL(outgoing_).type = ev.incoming().front().type;
     most_forward_FKL(outgoing_).type = ev.incoming().back().type;
     copy_AWZH_boson_from(ev);
 
     assert(!outgoing_.empty());
     reconstruct_incoming(ev.incoming());
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_non_jet(
       int count, double ptmin, double ptmax
   ){
     // heuristic parameters for pt sampling
     const double ptpar = 1.3 + count/5.;
     const double temp1 = atan((ptmax - ptmin)/ptpar);
 
     std::vector<fastjet::PseudoJet> partons(count);
     for(size_t i = 0; i < (size_t) count; ++i){
       const double r1 = ran_.get().flat();
       const double pt = ptmin + ptpar*tan(r1*temp1);
       const double temp2 = cos(r1*temp1);
       const double phi = 2*M_PI*ran_.get().flat();
       weight_ *= 2.0*M_PI*pt*ptpar*temp1/(temp2*temp2);
       // we don't know the allowed rapidity span yet,
       // set a random value to be rescaled later on
       const double y = ran_.get().flat();
       partons[i].reset_PtYPhiM(pt, y, phi);
       // Set user index higher than any jet-parton index
       // in order to assert that these are not inside jets
       partons[i].set_user_index(i + 1 + ng_max);
 
       assert(ptmin-1e-5 <= partons[i].pt() && partons[i].pt() <= ptmax+1e-5);
     }
     assert(std::all_of(partons.cbegin(), partons.cend(), is_nonjet_parton));
     return partons;
   }
 
   void PhaseSpacePoint::rescale_rapidities(
       std::vector<fastjet::PseudoJet> & partons,
       double ymin, double ymax
   ){
     constexpr double ep = 1e-7;
     for(auto & parton: partons){
       assert(0 <= parton.rapidity() && parton.rapidity() <= 1);
       const double dy = ymax - ymin - 2*ep;
       const double y = ymin + ep + dy*parton.rapidity();
       parton.reset_momentum_PtYPhiM(parton.pt(), y, parton.phi());
       weight_ *= dy;
       assert(ymin <= parton.rapidity() && parton.rapidity() <= ymax);
     }
   }
 
   namespace {
     template<typename T, typename... Rest>
     auto min(T const & a, T const & b, Rest&&... r) {
       using std::min;
       return min(a, min(b, std::forward<Rest>(r)...));
     }
   }
 
   double PhaseSpacePoint::probability_in_jet(
       std::vector<fastjet::PseudoJet> const & Born_jets
   ) const{
     assert(std::is_sorted(begin(Born_jets), end(Born_jets), rapidity_less{}));
     assert(Born_jets.size() >= 2);
 
     const double dy =
       Born_jets.back().rapidity() - Born_jets.front().rapidity();
     const double R = param_.jet_param.def.R();
     const int njets = Born_jets.size();
     const double p_J_y_large = (njets-1)*R*R/(2.*dy);
     const double p_J_y0 = njets*R/M_PI;
     return min(p_J_y_large, p_J_y0, 1.);
   }
 
   int PhaseSpacePoint::sample_ng_jets(
       int ng, std::vector<fastjet::PseudoJet> const & Born_jets
   ){
     const double p_J = probability_in_jet(Born_jets);
     std::binomial_distribution<> bin_dist(ng, p_J);
     const int ng_J = bin_dist(ran_.get());
     weight_ *= std::pow(p_J, -ng_J)*std::pow(1 - p_J, ng_J - ng);
     return ng_J;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::reshuffle(
       std::vector<fastjet::PseudoJet> const & Born_jets,
       fastjet::PseudoJet const & q
   ){
     if(q == fastjet::PseudoJet{0, 0, 0, 0}) return Born_jets;
     const auto jets = resummation_jet_momenta(Born_jets, q);
     if(jets.empty()){
       weight_ = 0;
       return {};
     }
 
     // additional Jacobian to ensure Born integration over delta gives 1
     weight_ *= resummation_jet_weight(Born_jets, q);
     return jets;
   }
 
   std::vector<int> PhaseSpacePoint::distribute_jet_partons(
       int ng_jets, std::vector<fastjet::PseudoJet> const & jets
   ){
     size_t first_valid_jet = 0;
     size_t num_valid_jets = jets.size();
     const double R_eff = 5./3.*param_.jet_param.def.R();
     // if there is an unordered jet too far away from the FKL jets
     // then extra gluon constituents of the unordered jet would
     // violate the FKL rapidity ordering
     if(unob_ && jets[0].delta_R(jets[1]) > R_eff){
       ++first_valid_jet;
       --num_valid_jets;
     }
     else if(unof_ && jets[jets.size()-1].delta_R(jets[jets.size()-2]) > R_eff){
       --num_valid_jets;
     }
     std::vector<int> np(jets.size(), 1);
     for(int i = 0; i < ng_jets; ++i){
       ++np[first_valid_jet + ran_.get().flat() * num_valid_jets];
     }
     weight_ *= std::pow(num_valid_jets, ng_jets);
     return np;
   }
 
 #ifndef NDEBUG
   namespace{
     bool tagged_FKL_backward(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       return std::find_if(
           begin(jet_partons), end(jet_partons),
           [](fastjet::PseudoJet const & p){
             return p.user_index() == backward_FKL_idx;
           }
       ) != end(jet_partons);
     }
 
     bool tagged_FKL_forward(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       // the most forward FKL parton is most likely near the end of jet_partons;
       // start search from there
       return std::find_if(
           jet_partons.rbegin(), jet_partons.rend(),
           [](fastjet::PseudoJet const & p){
             return p.user_index() == forward_FKL_idx;
           }
       ) != jet_partons.rend();
     }
 
     bool tagged_FKL_extremal(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       return tagged_FKL_backward(jet_partons) && tagged_FKL_forward(jet_partons);
     }
 
   } // namespace anonymous
 #endif
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::split(
       std::vector<fastjet::PseudoJet> const & jets,
       int ng_jets
   ){
     return split(jets, distribute_jet_partons(ng_jets, jets));
   }
 
   bool PhaseSpacePoint::pass_extremal_cuts(
       fastjet::PseudoJet const & ext_parton,
       fastjet::PseudoJet const & jet
   ) const{
     if(ext_parton.pt() < param_.min_extparton_pt) return false;
     return (ext_parton - jet).pt()/jet.pt() < param_.max_ext_soft_pt_fraction;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::split(
       std::vector<fastjet::PseudoJet> const & jets,
       std::vector<int> const & np
   ){
     assert(! jets.empty());
     assert(jets.size() == np.size());
     assert(pass_resummation_cuts(jets));
 
     const size_t most_backward_FKL_idx = 0 + unob_;
     const size_t most_forward_FKL_idx = jets.size() - 1 - unof_;
     const auto & jet = param_.jet_param;
     const JetSplitter jet_splitter{jet.def, jet.min_pt, ran_};
 
     std::vector<fastjet::PseudoJet> jet_partons;
     // randomly distribute jet gluons among jets
     for(size_t i = 0; i < jets.size(); ++i){
       auto split_res = jet_splitter.split(jets[i], np[i]);
       weight_ *= split_res.weight;
       if(weight_ == 0) return {};
       assert(
           std::all_of(
               begin(split_res.constituents), end(split_res.constituents),
               is_jet_parton
           )
       );
       const auto first_new_parton = jet_partons.insert(
           end(jet_partons),
           begin(split_res.constituents), end(split_res.constituents)
       );
       // mark uno and extremal FKL emissions here so we can check
       // their position once all emissions are generated
       auto extremal = end(jet_partons);
       if((unob_ && i == 0) || i == most_backward_FKL_idx){
         // unordered or FKL backward emission
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(
             (i == most_backward_FKL_idx)?backward_FKL_idx:unob_idx
         );
       }
       else if((unof_ && i == jets.size() - 1) || i == most_forward_FKL_idx){
         // unordered or FKL forward emission
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(
             (i == most_forward_FKL_idx)?forward_FKL_idx:unof_idx
         );
       }
       if(
           extremal != end(jet_partons)
           && !pass_extremal_cuts(*extremal, jets[i])
       ){
         weight_ = 0;
         return {};
       }
     }
     assert(tagged_FKL_extremal(jet_partons));
     std::sort(begin(jet_partons), end(jet_partons), rapidity_less{});
     if(
         !extremal_ok(jet_partons)
         || !split_preserved_jets(jets, jet_partons)
     ){
       weight_ = 0.;
       return {};
     }
     return jet_partons;
   }
 
   bool PhaseSpacePoint::extremal_ok(
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     assert(std::is_sorted(begin(partons), end(partons), rapidity_less{}));
     if(unob_ && partons.front().user_index() !=  unob_idx) return false;
     if(unof_ && partons.back().user_index() !=  unof_idx) return false;
     return
       most_backward_FKL(partons).user_index() == backward_FKL_idx
       && most_forward_FKL(partons).user_index() == forward_FKL_idx;
   }
 
   bool PhaseSpacePoint::split_preserved_jets(
         std::vector<fastjet::PseudoJet> const & jets,
         std::vector<fastjet::PseudoJet> const & jet_partons
   ) const{
     assert(std::is_sorted(begin(jets), end(jets), rapidity_less{}));
 
     const auto split_jets = sorted_by_rapidity(cluster_jets(jet_partons));
     // this can happen if two overlapping jets
     // are both split into more than one parton
     if(split_jets.size() != jets.size()) return false;
     for(size_t i = 0; i < split_jets.size(); ++i){
       // this can happen if there are two overlapping jets
       // and a parton is assigned to the "wrong" jet
       if(!nearby_ep(jets[i].rapidity(), split_jets[i].rapidity(), 1e-2)){
         return false;
       }
     }
     return true;
   }
 
   template<class Particle>
   Particle const & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> const & partons
   ) const{
     return partons[0 + unob_];
   }
 
   template<class Particle>
   Particle const & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> const & partons
   ) const{
     const size_t idx = partons.size() - 1 - unof_;
     assert(idx < partons.size());
     return partons[idx];
   }
 
   template<class Particle>
   Particle & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> & partons
   ) const{
     return partons[0 + unob_];
   }
 
   template<class Particle>
   Particle & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> & partons
   ) const{
     const size_t idx = partons.size() - 1 - unof_;
     assert(idx < partons.size());
     return partons[idx];
   }
 
   namespace {
     bool contains_idx(
         fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton
     ){
       auto const & constituents = jet.constituents();
       const int idx = parton.user_index();
       return std::find_if(
           begin(constituents), end(constituents),
           [idx](fastjet::PseudoJet const & con){return con.user_index() == idx;}
       ) != end(constituents);
     }
   }
 
   /**
    * final jet test:
    *   - number of jets must match Born kinematics
    *   - no partons designated as nonjet may end up inside jets
    *   - all other outgoing partons *must* end up inside jets
    *   - the extremal (in rapidity) partons must be inside the extremal jets
    *   - rapidities must be the same (by construction)
    */
   bool PhaseSpacePoint::jets_ok(
       std::vector<fastjet::PseudoJet> const & Born_jets,
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     fastjet::ClusterSequence cs(partons, param_.jet_param.def);
     const auto jets = sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt));
     if(jets.size() != Born_jets.size()) return false;
     int in_jet = 0;
     for(size_t i = 0; i < jets.size(); ++i){
       assert(jets[i].has_constituents());
       for(auto && parton: jets[i].constituents()){
         if(is_nonjet_parton(parton)) return false;
       }
       in_jet += jets[i].constituents().size();
     }
     const int expect_in_jet = std::count_if(
         partons.cbegin(), partons.cend(), is_jet_parton
     );
     if(in_jet != expect_in_jet) return false;
     // note that PseudoJet::contains does not work here
     if(! (
            contains_idx(most_backward_FKL(jets), most_backward_FKL(partons))
            &&  contains_idx(most_forward_FKL(jets), most_forward_FKL(partons))
        )) return false;
     if(unob_ && !contains_idx(jets.front(), partons.front())) return false;
     if(unof_ && !contains_idx(jets.back(), partons.back())) return false;
     for(size_t i = 0; i < jets.size(); ++i){
       assert(nearby_ep(jets[i].rapidity(), Born_jets[i].rapidity(), 1e-2));
     }
     return true;
   }
 
   void PhaseSpacePoint::reconstruct_incoming(
       std::array<Particle, 2> const & Born_incoming
   ){
     std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_);
     for(size_t i = 0; i < incoming_.size(); ++i){
       incoming_[i].type = Born_incoming[i].type;
     }
     assert(momentum_conserved());
   }
 
   double PhaseSpacePoint::phase_space_normalisation(
       int num_Born_jets, int num_out_partons
   ) const{
     return pow(16*pow(M_PI,3), num_Born_jets - num_out_partons);
   }
 
   bool PhaseSpacePoint::momentum_conserved() const{
     fastjet::PseudoJet diff;
     for(auto const & in: incoming()) diff += in.p;
     const double norm = diff.E();
     for(auto const & out: outgoing()) diff -= out.p;
     return nearby(diff, fastjet::PseudoJet{}, norm);
   }
 
 } //namespace HEJ
diff --git a/src/Weights.cc b/src/Weights.cc
new file mode 100644
index 0000000..9e0ee9c
--- /dev/null
+++ b/src/Weights.cc
@@ -0,0 +1,41 @@
+#include "HEJ/Weights.hh"
+
+#include <stdexcept>
+
+namespace HEJ {
+
+  Weights& Weights::operator*=(Weights const & other) {
+    if(other.variations.size() != variations.size()) {
+      throw std::invalid_argument{"Wrong number of weights"};
+    }
+    central *= other.central;
+    for(std::size_t i = 0; i < variations.size(); ++i) {
+      variations[i] *= other.variations[i];
+    }
+    return *this;
+  }
+
+  Weights& Weights::operator*=(double factor) {
+    central *= factor;
+    for(auto & wt: variations) wt *= factor;
+    return *this;
+  }
+
+  Weights& Weights::operator/=(Weights const & other) {
+    if(other.variations.size() != variations.size()) {
+      throw std::invalid_argument{"Wrong number of weights"};
+    }
+    central /= other.central;
+    for(std::size_t i = 0; i < variations.size(); ++i) {
+      variations[i] /= other.variations[i];
+    }
+    return *this;
+  }
+
+  Weights& Weights::operator/=(double factor) {
+    central /= factor;
+    for(auto & wt: variations) wt /= factor;
+    return *this;
+  }
+
+}
diff --git a/t/check_res.cc b/t/check_res.cc
index 50b4325..9ef2ffd 100644
--- a/t/check_res.cc
+++ b/t/check_res.cc
@@ -1,97 +1,96 @@
 #include <iostream>
 
 #include "LHEF/LHEF.h"
 #include "HEJ/stream.hh"
 #include "HEJ/EventReweighter.hh"
 #include "HEJ/Mixmax.hh"
 
 namespace{
   const fastjet::JetDefinition jet_def{fastjet::kt_algorithm, 0.4};
   const fastjet::JetDefinition Born_jet_def{jet_def};
   constexpr double Born_jetptmin = 30;
   constexpr double extpartonptmin = 30;
   constexpr double max_ext_soft_pt_fraction =
     std::numeric_limits<double>::infinity();
   constexpr double jetptmin = 35;
   constexpr bool log_corr = false;
   using EventTreatment = HEJ::EventTreatment;
   using namespace HEJ::event_type;
   HEJ::EventTreatMap treat{
     {no_2_jets, EventTreatment::discard},
     {bad_final_state, EventTreatment::discard},
     {nonHEJ, EventTreatment::discard},
     {unof, EventTreatment::discard},
     {unob, EventTreatment::discard},
     {FKL, EventTreatment::reweight}
   };
 };
 
 int main(int argn, char** argv) {
   if(argn == 5 && std::string(argv[4]) == "uno"){
     --argn;
     treat[unof] = EventTreatment::reweight;
     treat[unob] = EventTreatment::reweight;
     treat[FKL] = EventTreatment::discard;
   }
   if(argn != 4){
     std::cerr << "Usage: check_res eventfile xsection tolerance [uno]";
     return EXIT_FAILURE;
   }
 
   const double xsec_ref = std::stod(argv[2]);
   const double tolerance = std::stod(argv[3]);
 
   HEJ::istream in{argv[1]};
   LHEF::Reader reader{in};
 
   HEJ::PhaseSpacePointConfig psp_conf;
   psp_conf.jet_param = HEJ::JetParameters{jet_def, jetptmin};
   psp_conf.min_extparton_pt = extpartonptmin;
   psp_conf.max_ext_soft_pt_fraction = max_ext_soft_pt_fraction;
   HEJ::MatrixElementConfig ME_conf;
-  ME_conf.jet_param = psp_conf.jet_param;
   ME_conf.log_correction = log_corr;
   ME_conf.Higgs_coupling = HEJ::HiggsCouplingSettings{};
   HEJ::EventReweighterConfig conf;
   conf.psp_config = std::move(psp_conf);
   conf.ME_config = std::move(ME_conf);
   conf.jet_param = psp_conf.jet_param;
   conf.treat = treat;
 
   reader.readEvent();
   const bool has_Higgs = std::find(
       begin(reader.hepeup.IDUP),
       end(reader.hepeup.IDUP),
       25
   ) != end(reader.hepeup.IDUP);
   const double mu = has_Higgs?125.:91.188;
   HEJ::ScaleGenerator scale_gen{
     {{std::to_string(mu), HEJ::FixedScale{mu}}}, {}, 1.
   };
   HEJ::Mixmax ran{};
   HEJ::EventReweighter hej{reader.heprup, std::move(scale_gen), conf, ran};
 
   double xsec = 0.;
   double xsec_err = 0.;
   do{
     HEJ::Event ev{
       HEJ::UnclusteredEvent{reader.hepeup},
       Born_jet_def, Born_jetptmin
     };
     auto resummed_events = hej.reweight(ev, 20);
     for(auto const & ev: resummed_events) {
       xsec += ev.central().weight;
       xsec_err +=  ev.central().weight*ev.central().weight;
     }
   } while(reader.readEvent());
   xsec_err = std::sqrt(xsec_err);
   const double significance =
     std::abs(xsec - xsec_ref) / std::sqrt( xsec_err*xsec_err + tolerance*tolerance );
   std::cout << xsec_ref << " +/- " << tolerance << " ~ "
     << xsec << " +- " << xsec_err << " => " << significance << " sigma\n";
 
   if(significance > 3.){
     std::cerr << "Cross section is off by over 3 sigma!\n";
     return EXIT_FAILURE;
   }
 }
diff --git a/t/test_ME_generic.cc b/t/test_ME_generic.cc
index 8563223..33a9077 100644
--- a/t/test_ME_generic.cc
+++ b/t/test_ME_generic.cc
@@ -1,104 +1,104 @@
 
 // Generic tester for the ME for a given set of PSP
 // reference weights and PSP (as LHE file) have to be given as _individual_ files
 
 #include <fstream>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/YAMLreader.hh"
 #include "HEJ/stream.hh"
 
 constexpr double alpha_s = 0.118;
-constexpr double mu = 1234.; // coupling fixed, mu doesn't matter
 constexpr double ep = 1e-6;
 
-void dump(HEJ::UnclusteredEvent const & ev, HEJ::Config config){
+void dump(HEJ::Event const & ev){
   {
     LHEF::Writer writer{std::cout};
     std::cout << std::setprecision(6);
-    HEJ::Event out_ev{ev, config.resummation_jets.def, config.resummation_jets.min_pt};
-    writer.hepeup = to_HEPEUP(std::move(out_ev), nullptr);
+    writer.hepeup = to_HEPEUP(std::move(ev), nullptr);
     writer.writeEvent();
   }
   std::cout << "Rapidity ordering:\n";
-  for(const auto & part: ev.outgoing){
+  for(const auto & part: ev.outgoing()){
     std::cout << std::setw(2) << part.type << ": "<<  std::setw(7) << part.rapidity() << std::endl;
   }
 }
 
 int main(int argn, char** argv){
   if(argn != 4 && argn != 5){
     std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml ME_weights input_file.lhe\n\n";
     return EXIT_FAILURE;
   }
   bool OUTPUT_MODE = false;
   if(argn == 5 && std::string("OUTPUT")==std::string(argv[4]))
       OUTPUT_MODE = true;
   const HEJ::Config config = HEJ::load_config(argv[1]);
 
   std::fstream wgt_file;
   if ( OUTPUT_MODE ) {
     std::cout << "_______________________USING OUTPUT MODE!_______________________" << std::endl;
     wgt_file.open(argv[2], std::fstream::out);
     wgt_file.precision(10);
   } else {
     wgt_file.open(argv[2], std::fstream::in);
   }
 
   HEJ::istream in{argv[3]};
   LHEF::Reader reader{in};
 
   HEJ::MatrixElement ME{
     [](double){ return alpha_s; },
     HEJ::to_MatrixElementConfig(config)
   };
   double max_ratio = 0.;
   size_t idx_max_ratio = 0;
-  HEJ::UnclusteredEvent ev_max_ratio;
+  HEJ::Event ev_max_ratio;
   double av_ratio = 0;
 
   size_t i = 0;
   while(reader.readEvent()){
     ++i;
 
-    HEJ::UnclusteredEvent event{reader.hepeup};
-    const double our_ME = ME.tree(
-        mu, event.incoming, event.outgoing, true
-    );
+    HEJ::Event event{
+      HEJ::UnclusteredEvent{reader.hepeup},
+      config.resummation_jets.def,
+      config.resummation_jets.min_pt
+    };
+    const double our_ME = ME.tree(event, true).central;
 
     if ( OUTPUT_MODE ) {
       wgt_file << our_ME << std::endl;
     } else {
       std::string line;
       if(!std::getline(wgt_file,line)) break;
       const double ref_ME = std::stod(line);
       const double diff = std::abs(our_ME/ref_ME-1.);
       av_ratio+=diff;
       if( diff > max_ratio ) {
         max_ratio = diff;
         idx_max_ratio = i;
         ev_max_ratio = event;
       }
       if( diff > ep ){
         size_t precision(std::cout.precision());
         std::cout.precision(16);
         std::cout<< "Large difference in PSP " << i << "\nis: "<<our_ME << " should: " << ref_ME << " => difference: " << diff << std::endl;
         std::cout.precision(precision);
-        dump(event, config);
+        dump(event);
         return EXIT_FAILURE;
       }
     }
   }
   wgt_file.close();
   if ( !OUTPUT_MODE ) {
     size_t precision(std::cout.precision());
     std::cout.precision(16);
     std::cout << "Avg ratio after " << i << " PSP: " << av_ratio/i << std::endl;
     std::cout << "maximal ratio at " << idx_max_ratio << ": " << max_ratio << std::endl;
     std::cout.precision(precision);
   }
   return EXIT_SUCCESS;
 }