diff --git a/Changes-API.md b/Changes-API.md
index b78cc9b..9f1ad56 100644
--- a/Changes-API.md
+++ b/Changes-API.md
@@ -1,132 +1,133 @@
 # Changelog for HEJ API
 
 This log lists only changes on the HEJ API. These are primarily code changes
 relevant for calling HEJ as an API. This file should only be read as an addition
 to [`Changes.md`](Changes.md), where the main features are documented.
 
 ## Version 2.2
 
 ### 2.2.0
 
-
 #### New and updated functions
 * New functions to decide whether particles are massive, charged,
   charged leptons, antiparticles.
+* Added `to_string` function for `Event`.
 * Updated function `Event::EventData.reconstruct_intermediate()`
   - Now requires an `EWConstants` argument
   - Added support for WpWp and WmWm events.
   - In the case of WW same-flavour the reconstruction will minimise the total
     off-shell momentum.
-* Renamed `no_2_jets` error flag to `not_enough_jets`.
+* Replaced `no_2_jets` and `bad_final_state` enumerators in `EventType` with
+  `invalid` and `unknown`.
 * Added helper functions `is_backward_g_to_h`, `is_forward_g_to_h` to
   detect incoming gluon to outgoing Higgs transitions.
 * Updated function `implemented_types()` to now be in HEJ namespace.
 
 
 ## Version 2.1
 
 ### 2.1.0
 
 #### Changes to Event class
 * Restructured `Event` class
   - `Event` can now only be build from a (new) `Event::EventData` class
   - Removed default constructor for `Event`
   - `Event::EventData` replaces the old `UnclusteredEvent` struct.
   - `UnclusteredEvent` is now **deprecated**, and will be removed in version
     2.2.0
   - Removed `Event.unclustered()` function
   - Added new member function `Events.parameters()`, to directly access
     (underlying) `Parameters<EventParameters>`
   - New member functions `begin_partons`, `end_partons` (`rbegin_partons`,
     `rend_partons`) with aliases `cbegin_partons`, `cend_partons`
     (`crbegin_partons`, `crend_partons`) for constant (reversed) iterators over
     outgoing partons.
 * New function `Event::EventData.reconstruct_intermediate()` to reconstruct
   bosons from decays, e.g. `positron + nu_e => Wp`
 * Added optional Colour charges to particles (`Particle.colour`)
   - Colour connection in the HEJ limit can be generated via
     `Event.generate_colours` (automatically done in the resummation)
   - Colour configuration of input events can be checked with
     `Event.is_leading_colour`
 * Added function `Event.valid_hej_state` to check if event _could have_ been
   produced by `HEJ` according to the `soft pt regulator` cut on the jets
 
 #### New and updated functions
 * Renamed `EventType::nonHEJ` to `EventType::non_resummable` and `is_HEJ()`
   to `is_resummable()` such that run card is consistent with internal workings
 * Made `MatrixElement.tree_kin(...)` and `MatrixElement.tree_param(...)` public
 * New `EventReweighter` member function `treatment` to query the
   treatment with respect to resummation for the various event types.
 * Added auxiliary functions to obtain a `std::string` from `EventDescription`
   (`to_string` for human readable, and `to_simple_string` for easy parsable
   string)
 * New `get_analyses` function to read in multiple `HEJ::Analysis` at once,
   similar to `get_analysis`
 * New `get_ew_parameters` function to extract electroweak parameters from
   YAML configuration.
 
 #### New classes
 * New class `Unweighter` to do unweighting
 * New class `CrossSectionAccumulator` to keep track of Cross Section of the
   different subprocess
 * New template struct `Parameters` similar to old `Weights`
   - `Weights` are now an alias for `Parameters<double>`. Calling `Weights` did
     not change
   - `Weights.hh` was replaced by `Parameters.hh`. The old `Weights.hh` header
     will be removed in version 2.2.0
 * Function to multiplication and division of `EventParameters.weight` by double
   - This can be combined with `Parameters`, e.g.
     `Parameters<EventParameters>*Weights`, see also `Events.parameters()`
   - Moved `EventParameters` to `Parameters.hh` header
 * new class `EWConstants` replaces previously hard coded `vev`
   - `EWConstants` have to be set in the general `Config` and the
     `MatrixElementConfig`
 
 #### Input/Output
 * New abstract `EventReader` class, as base for reading events from files
   - Moved LHE file reader to `HEJ::LesHouchesReader`
 * New (optional) function `finish()` in abstract class `EventWriter`. `finish()`
   is called _after_ all events are written.
 * Support reading (`HDF5Reader`) and writing (`HDF5Writer`) `hdf5` files
 * New `BufferedEventReader` class that allows to put events back into
   the reader.
 * New `SherpaLHEReader` to read Sherpa LHE files with correct weights
 * `get_analysis` now requires `YAML::Node` and `LHEF::HEPRUP` as arguments
 * Replaced `HepMCInterface` and `HepMCWriter` by `HepMCInterfaceX` and
   `HepMCWriterX` respectively, with `X` being the major version of HepMC (2 or
   3)
   - Renamed `HepMCInterfaceX::init_kinematics` to `HepMCInterfaceX::init_event`
     and protected it, use `HepMCInterfaceX::operator()` instead
   - Removed redundant `HepMCInterfaceX::add_variation` function
 
 #### Linking
 * Export cmake target `HEJ::HEJ` to link directly against `libHEJ`
 * Preprocessor flags (`HEJ_BUILD_WITH_XYZ`) for enabled/disabled dependencies
   are now written to `ConfigFlags.hh`
 * Provide links to version specific object files, e.g. `libHEJ.so ->
   libHEJ.so.2.1 (soname) -> libHEJ.so.2.1.0`
 * Removed `LHAPDF` from public interface
 
 #### Miscellaneous
 * Capitalisation of `Config.hh` now matches class `Config` (was `config.hh`)
 * Renamed `Config::max_ext_soft_pt_fraction` to `Config::soft_pt_regulator`.
   The new `Config::soft_pt_regulator` parameter is optional and the old
   `Config::max_ext_soft_pt_fraction` is **deprecated**.
 * Replaced redundant member `EventReweighterConfig::jet_param` with getter
   function `EventReweighter.jet_param()` (`JetParameters` are already in
   `PhaseSpacePointConfig`)
 * Avoid storing reference to the Random Number Generator inside classes
   - Constructors of `EventReweighter` now expect `std::shared_ptr<HEJ::RNG>`
     (was reference)
   - Moved reference to `HEJ::RNG` from constructor of `JetSplitter` to
     `JetSplitter.split`
 
 ## Version 2.0
 
 ### 2.0.4
 
 * Fixed wrong path of `HEJ_INCLUDE_DIR` in `hej-config.cmake`
 
 ### 2.0.0
 
 * First release
diff --git a/Changes.md b/Changes.md
index d32d0c6..954c587 100644
--- a/Changes.md
+++ b/Changes.md
@@ -1,198 +1,203 @@
 # Changelog
 
 This is the log for changes to the HEJ program. Further changes to the HEJ API
 are documented in [`Changes-API.md`](Changes-API.md). If you are using HEJ as a
 library, please also read the changes there.
 
 ## Version 2.2
 
 This release adds support for the new processes:
 
 * Wp+Wp with jets.
 * Wm+Wm with jets.
 
 In addition, there are new options when running HEJ:
 
 * Enable NLO truncation for use in HEJ@NLO predictions.
 * Require a lowpt jet, used for running lowpt separately.
 
 This release includes minor changes which affect users of HEJ as a library (see
 [`Changes-API.md`](Changes-API.md)). Compilation now requires a
 compiler supporting C++17.
 
 ### 2.2.0
 
 #### New Processes
 
 * Resummation for WpWp/WmWm with jets, including interference between
   configurations.
 * Significantly improved description of Higgs boson plus jet production:
   - Processes with extremal Higgs boson emission are now treated as
     leading-log.
   - Resummation is now enabled for Higgs boson production with a
     single jet.
 * `HEJFOG` can generate multiple jets together with a charged lepton-antilepton
   pair via a virtual Z boson or photon.
 
 #### Updates to configuration file
 
 * With the new option `off-shell tolerance` HEJ repairs the momenta
   of massless particles that are slightly off-shell and incoming
   particles with small but non-vanishing transverse momenta.
+* Introduced new event types `unknown` and `invalid` under the `event
+  treatment` option. `unknown` indicates processes that are not
+  recognised by HEJ. `invalid` is reserved for unphysical processes.
+* Introduced new event treatment `abort` for aborting the program when
+  an undesired event type is encountered.
 
 ## Version 2.1
 
 This release adds support for two new processes:
 
 * W boson with jets.
 * Jets with a charged lepton-antilepton pair via a virtual Z boson or photon.
 
 In addition, the complete set of first subleading processes (unordered
 gluon, central and extremal quark-antiquark pair) is implemented for
 pure jets and W + jets, see
 [arXiv:2012.10310](https://arxiv.org/abs/2012.10310). Unordered gluon emission
 is also supported for Higgs boson + jets and Z boson/photon + jets.
 
 This release include many changes to the code, which affect users of
 HEJ as a library (see [`Changes-API.md`](Changes-API.md)).
 
 ### 2.1.3
 
 * Updated documentation.
 
 ### 2.1.2
 
 * Updated `cxxopts.hpp` dependency.
 
 ### 2.1.1
 
 * Fixed invalid iterator accesses.
 * Reorganised automated tests.
 * Updated documentation.
 
 ### 2.1.0
 
 #### New Processes
 * Resummation for W bosons with jets
   - New subleading processes `extremal qqbar` & `central qqbar` for a quark and
     anti-quark in the final state, e.g. `g g => u d_bar Wm g` (the other
     subleading processes also work with W's)
   - `HEJFOG` can generate multiple jets together with a (off-shell) W bosons
     decaying into lepton & neutrino
 * Resummation for jets with a charged lepton-antilepton pair via a
   virtual Z boson or photon. Includes the `unordered` subleading process.
 * Resummation can now be performed on all subleading processes within pure
   jets also. This includes `unordered`, `extremal qqbar` and `central
   qqbar` processes.
 
 #### More Physics implementation
 * Partons now have a Colour charge
   - Colours are read from and written to LHE files
   - For reweighted events the colours are created according to leading colour in
     the FKL limit
 * Use relative fraction for soft transverse momentum in tagging jets (`soft pt
   regulator`) as new (optional) parameter.
   - This supersedes `min extparton pt`, which is marked **deprecated** and will
     be removed in version 2.2.0
   - This is a direct replacement for the old `max ext soft pt fraction`, which
     is also **deprecated**.
 * Changed the redistribution of the momenta of soft emissions. Now also bosons
   can take part of the recoil (previously only jets).
 
 #### Updates to Runcard
 * Allow multiplication and division of multiple scale functions e.g.
   `H_T/2*m_j1j2`
 * Grouped `event treatment` for subleading channels together in runcard
   - Rename `non-HEJ` processes to `non-resummable`
 * Read electro-weak constants from input
   - new mandatory setting `vev` to change vacuum expectation value
   - new mandatory settings `particle properties` to specify mass & width of
     bosons
   - `HEJFOG`: decays are now specified in `decays` setting (previously under
     `particle properties`)
 * Allow loading multiple analyses with `analyses`. The old `analysis` (with "i")
   is marked **deprecated**.
 * Optional setting to specify maximal number of Fixed Order events (`max
   events`, default is all)
 * Allow changing the regulator lambda in input (`regulator parameter`, only for
   advanced users)
 
 #### Changes to Input/Output
 * Added support to read & write `hdf5` event files suggested in
   [arXiv:1905.05120](https://arxiv.org/abs/1905.05120) (needs
   [HighFive](https://github.com/BlueBrain/HighFive))
 * Support input with average weight equal to the cross section (`IDWTUP=1 or 4`)
 * Support unmodified Les Houches Event Files written by Sherpa with
   `cross section = sum(weights)/sum(trials)`
 * Analyses now get general run information (`LHEF::HEPRUP`) in the
   constructor. **This might break previously written, external analyses!**
   - external analyses should now be created with
     `make_analysis(YAML::Node const & config, LHEF::HEPRUP const & heprup)`
 * Support `rivet` version 3 with both `HepMC` version 2 and 3
   - Multiple weights with `rivet 3` will only create one `.yoda` file (instead
     of one per weight/scale)
 * Added option to unweight only resummation events
   (`unweight: {type: resummation}`)
 * Added option for partially unweighting resummation events, similar to
   the fixed-order generator.
 * Improved unweighting algorithm.
 * Follow HepMC convention for particle Status codes: incoming = 11,
   decaying = 2, outgoing = 1 (unchanged)
 
 #### Miscellaneous
 * Print cross sections at end of run
 * Added example analysis & scale to `examples/`. Everything in `examples/` will
   be build when the flag `-DBUILD_EXAMPLES=TRUE` is set in `cmake`.
 * Dropped support for HepMC 3.0.0, either HepMC version 2 or >3.1 is required
   - It is now possible to write out both HepMC 2 and HepMC 3 events at the same
     time
 * Require LHADPF version 6. Dropped support for all other versions.
 * Use `git-lfs` for raw data in test (`make test` now requires `git-lfs`)
 * Currents are now generated with [`FORM`](https://github.com/vermaseren/form)
   - `FORM` is included as a `git submodule`, use `git submodule update --init`
     to download `FORM`
 * Create [Sphinx](http://sphinx-doc.org/) and [Doxygen](http://doxygen.org/)
   documentation by `make sphinx` or `make doxygen` in your `build/` folder
 
 ## Version 2.0
 
 First release of HEJ 2. Complete code rewrite compared to HEJ 1. Improved
 matching to Fixed Order ([arXiv:1805.04446](https://arxiv.org/abs/1805.04446)).
 Implemented processes: Higgs boson with jets (FKL and unordered gluon emission,
 with finite quark mass loop,
 [arXiv:1812.08072](https://arxiv.org/abs/1812.08072)), and pure jets (only FKL).
 See [arXiv:1902.08430](https://arxiv.org/abs/1902.08430)
 
 ## 2.0.7
 
 * Added missing `#include` directives.
 
 ### 2.0.6
 
 * Fixed compiling rivet when YODA headers are _outside_ of rivet directory.
 
 ### 2.0.5
 
 * Fixed event classification for input not ordered in rapidity.
 
 ### 2.0.4
 
 * Fixed wrong path of `HEJ_INCLUDE_DIR` in `hej-config.cmake`.
 
 ### 2.0.3
 
 * Fixed parsing of (numerical factor) * (base scale) in configuration.
 * Don't change scale names, but sanitise Rivet output file names instead.
 
 ### 2.0.2
 
 * Changed scale names to `"_over_"` and `"_times_"` for proper file names (was
   `"/"` and `"*"` before).
 
 ### 2.0.1
 
 * Fixed name of fixed-order generator in error message.
 
 ### 2.0.0
 
 * First release.
diff --git a/config.yml b/config.yml
index 0bfdcce..4859c3f 100644
--- a/config.yml
+++ b/config.yml
@@ -1,140 +1,140 @@
 ## Number of attempted resummation phase space points for each input event
 trials: 10
 
 resummation jets:       # resummation jet properties
   min pt: 30            # minimum jet transverse momentum
   algorithm: antikt     # jet clustering algorithm
   R: 0.4                # jet R parameter
 
 fixed order jets:       # properties of input jets
   min pt: 20
   # by default, algorithm and R are like for resummation jets
 
 ## Treatment of he various event classes
-## the supported settings are: reweight, keep, discard
-## non-resummable events cannot be reweighted
+## the supported settings are: reweight, keep, discard, abort
+## non-resummable, unknown, and invalid events cannot be reweighted
 event treatment:
   FKL: reweight
   unordered: keep
   extremal qqbar: keep
   central qqbar: keep
   non-resummable: keep
 
 ## Central scale choice or choices
 #
 ## multiple scales are allowed, e.g.
 # scales: [125, max jet pperp, H_T/2, 2*jet invariant mass, m_j1j2]
 scales: 91.188
 
 ## Factors by which the central scales should be multiplied
 ## renormalisation and factorisation scales are varied independently
 #
 # scale factors: [0.5, 0.7071, 1, 1.41421, 2]
 
 ## Maximum ratio between renormalisation and factorisation scale
 #
 # max scale ratio: 2.0001
 
 ## Import scale setting functions
 #
 # import scales:
 #   lib_my_scales.so: [scale0,scale1]
 
 ## Unweighting setting
 ## remove to obtain weighted events
 # unweight:
 #   # type of unweighting (one of 'weighted', 'resummation', 'partial')
 #   type: partial
 #   trials: 10000
 #   max deviation: 0
 
 ## Event output files
 #
 # the supported formats are
 #    - Les Houches (suffix .lhe)
 #    - HepMC2 (suffix .hepmc2)
 #    - HepMC3 (suffix .hepmc3 or .hepmc)
 #    - HDF5   (suffix .hdf5)
 #
 ## An output file's format is deduced either automatically from the suffix
 ## or from an explicit specification, e.g.
 ## - Les Houches: outfile
 #
 event output:
   - HEJ.lhe
 #  - HEJ_events.hepmc
 
 ## Analyses
 #
 # analyses:
 ## Rivet analysis
 #   - rivet: MC_XS # rivet analysis name
 #     output: HEJ # name of the yoda files, ".yoda" and scale suffix will be added
 ## Custom analysis
 #   - plugin: /path/to/libmyanalysis.so
 #     my analysis parameter: some value
 
 ## Selection of random number generator and seed
 ## The choices are
 ##   - mixmax (seed is an integer)
 ##   - ranlux64 (seed is a filename containing parameters)
 random generator:
   name: mixmax
   # seed: 1
 
 ## Whether or not to include higher order logs
 log correction: false
 
 ## Truncate higher-order corrections at NLO
 NLO truncation:
   enabled: false
 # nlo order: 2
 
 ## Only keep low pt contributions
 # require low pt jet: false
 
 ## Vacuum expectation value
 vev: 246.2196508
 
 ## Properties of the weak gauge bosons
 particle properties:
   Higgs:
     mass: 125
     width: 0.004165
   W:
     mass: 80.385
     width: 2.085
   Z:
     mass: 91.187
     width: 2.495
 
 ## Parameters for Higgs-gluon couplings
 ## This requires compilation with QCDloop
 #
 # Higgs coupling:
 #   use impact factors: false
 #   mt: 174
 #   include bottom: true
 #   mb: 4.7
 
 # # Tolerance towards numerical inaccuracies in input momenta
 # off-shell tolerance: 0.01
 
 ## ---------------------------------------------------------------------- ##
 ## The following settings are only intended for advanced users.           ##
 ## Please DO NOT SET them unless you know exactly what you are doing!     ##
 ## ---------------------------------------------------------------------- ##
 #
 ## Maximum soft transverse momentum fraction in any tagging jets, e.g.
 ## extremal or qqbar jet
 # soft pt regulator: 0.1
 #
 ## Minimum transverse momentum of extremal partons
 ## deprecated: use "soft pt regulator" instead
 # min extparton pt: 30
 #
 ## deprecated: this cot directly replaced by "soft pt regulator"
 # max ext soft pt fraction: 0.1
 #
 # max events: -1 # Maximal number of fixed order Events to process
 # regulator parameter: 0.2 # The regulator lambda for the subtraction terms
diff --git a/doc/sphinx/HEJ.rst b/doc/sphinx/HEJ.rst
index 72247e2..0c18246 100644
--- a/doc/sphinx/HEJ.rst
+++ b/doc/sphinx/HEJ.rst
@@ -1,488 +1,513 @@
 .. _`Running HEJ 2`:
 
 Running HEJ 2
 =============
 
 Quick start
 -----------
 
 In order to run HEJ 2, you need a configuration file and a file
 containing fixed-order events. A sample configuration is given by the
 :file:`config.yml` file distributed together with HEJ 2. Events in the
 Les Houches Event File format can be generated with standard Monte Carlo
 generators like `MadGraph5_aMC@NLO <https://launchpad.net/mg5amcnlo>`_
 or `Sherpa <https://sherpa.hepforge.org/trac/wiki>`_. If HEJ 2 was
 compiled with `HDF5 <https://www.hdfgroup.org/>`_ support, it can also
 read and write event files in the format suggested in
 `arXiv:1905.05120 <https://arxiv.org/abs/1905.05120>`_.
 HEJ 2 assumes that the cross section is given by the sum of the event
 weights. Depending on the fixed-order generator it may be necessary to
 adjust the weights in the Les Houches Event File accordingly.
 
 The processes supported by HEJ 2 are
 
 - Pure multijet production
 - Production of a Higgs boson with jets
 - Production of a W boson with jets
 - Production of jets with a charged lepton-antilepton pair, via a
   virtual Z boson and/or photon
 - Production of two same-sign W bosons with jets
 
 where at least two jets are required in each case. For the time being,
 only leading-order input events are supported.
 
 After generating an event file :file:`events.lhe` adjust the parameters
 under the `fixed order jets`_ setting in :file:`config.yml` to the
 settings in the fixed-order generation. Resummation can then be added by
 running::
 
   HEJ config.yml events.lhe
 
 Using the default settings, this will produce an output event file
 :file:`HEJ.lhe` with events including high-energy resummation.
 
 When using the `Docker image <https://hub.docker.com/r/hejdock/hej>`_,
 HEJ can be run with
 
 .. code-block:: bash
 
   docker run -v $PWD:$PWD -w $PWD hejdock/hej HEJ config.yml events.lhe
 
 .. _`HEJ 2 settings`:
 
 Settings
 --------
 
 HEJ 2 configuration files follow the `YAML <http://yaml.org/>`_
 format. The following configuration parameters are supported:
 
 .. _`trials`:
 
 **trials**
   High-energy resummation is performed by generating a number of
   resummation phase space configurations corresponding to an input
   fixed-order event. This parameter specifies how many such
   configurations HEJ 2 should try to generate for each input
   event. Typical values vary between 10 and 100.
 
 .. _`fixed order jets`:
 
 **fixed order jets**
   This tag collects a number of settings specifying the jet definition
   in the event input. The settings should correspond to the ones used in
   the fixed-order Monte Carlo that generated the input events.
 
    .. _`fixed order jets: min pt`:
 
    **min pt**
       Minimum transverse momentum in GeV of fixed-order jets.
 
    .. _`fixed order jets: algorithm`:
 
    **algorithm**
       The algorithm used to define jets. Allowed settings are
       :code:`kt`, :code:`cambridge`, :code:`antikt`,
       :code:`cambridge for passive`. See the `FastJet
       <http://fastjet.fr/>`_ documentation for a description of these
       algorithms.
 
    .. _`fixed order jets: R`:
 
    **R**
       The R parameter used in the jet algorithm, roughly corresponding
       to the jet radius in the plane spanned by the rapidity and the
       azimuthal angle.
 
 .. _`resummation jets`:
 
 **resummation jets**
   This tag collects a number of settings specifying the jet definition
   in the observed, i.e. resummed events. These settings are optional, by
   default the same values as for the `fixed order jets`_ are assumed.
 
    .. _`resummation jets: min pt`:
 
    **min pt**
       Minimum transverse momentum in GeV of resummation jets. This
       should be between 25% and 50% larger than the minimum transverse
       momentum of fixed order jets set by `fixed order jets: min pt`_. The
       contribution from the lower min pt fixed order jets can also be
       calculated separately using the `require low pt jet`_ option.
 
    .. _`resummation jets: algorithm`:
 
    **algorithm**
       The algorithm used to define jets. The HEJ 2 approach to
       resummation relies on properties of :code:`antikt` jets, so this
       value is strongly recommended. For a list of possible other
       values, see the `fixed order jets: algorithm`_ setting.
 
    .. _`resummation jets: R`:
 
    **R**
       The R parameter used in the jet algorithm.
 
 .. _`event treatment`:
 
 **event treatment**
   Specify how to treat different event types. The different event types
-  contribute to different orders in the high-energy limit. The possible values
-  are :code:`reweight` to enable resummation, :code:`keep` to keep the events as
-  they are up to a possible change of renormalisation and factorisation scale,
-  and :code:`discard` to discard these events. The following types are
-  implemented for the different bosons:
-
-  .. csv-table::
-    :header: , "FKL", "unordered", "extremal qqbar", "central qqbar"
-    :widths: auto
-    :align: center
-    :stub-columns: 1
-
-    "pure jets", "Yes", "Yes", "Yes", "Yes"
-    "Higgs + jets", "Yes", "Yes", "No", "No"
-    "W + jets", "Yes", "Yes", "Yes", "Yes"
-    "Z/γ + jets", "Yes", "Yes", "No", "No"
-    "same-sign W + jets", "Yes", "No", "No", "No"
-
-  Non-implemented process will always be classified as :code:`non-resummable`.
-  The different types are:
+  contribute to different orders in the high-energy limit. The types are:
 
   .. _`FKL`:
 
   **FKL**
      Specifies how to treat events respecting FKL rapidity ordering, where all
      but the two partons extremal in rapidity have to be gluons, e.g.
      :code:`u d => u g d`. These configurations are dominant in the high-energy
      limit.
 
   .. _`unordered`:
 
   **unordered**
      Specifies how to treat events with one gluon emission that does not respect
      FKL ordering, e.g. :code:`u d => g u d`. In the high-energy limit, such
      configurations are logarithmically suppressed compared to FKL
      configurations.
 
   .. _`extremal qqbar`:
 
   **extremal qqbar**
      Specifies how to treat events with a quark-antiquark pair as extremal
      partons in rapidity, e.g. :code:`g d => u u_bar d`. In the high-energy
      limit, such configurations are logarithmically suppressed compared to FKL
      configurations.
 
   .. _`central qqbar`:
 
   **central qqbar**
      Specifies how to treat events with a quark-antiquark pair central in
      rapidity, e.g. :code:`g g => g u u_bar g`. In the high-energy limit, such
      configurations are logarithmically suppressed compared to FKL
      configurations.
 
   .. _`non-resummable`:
 
   **non-resummable**
-     Specifies how to treat events that do not fall into any of the above
-     categories or that are not yet implemented. Only :code:`keep` or
-     :code:`discard` are valid options, *not* :code:`reweight` for obvious
-     reasons.
+     Specifies how to treat events where resummation is currently not
+     supported for the given rapidity ordering and/or assignment of
+     parton flavours.
+
+  .. _`unknown`:
+
+  **unknown**
+     Specifies how to treat events for processes that are not
+     implemented, e.g. di-photon production.
+
+  .. _`invalid`:
+
+  **invalid**
+     Specifies how to treat events that are considered
+     unphysical. Events in this category violate one or more of the
+     following requirements:
+
+     - Charge conservation.
+     - Four-momentum conservation.
+     - Momenta of massless particles must be lightlike.
+     - Incoming particles must not have transverse momentum.
+
+  The possible treatments are
+
+  - :code:`reweight` to enable resummation
+  - :code:`keep` to keep the events as they are up to a possible
+    change of renormalisation and factorisation scale
+  - :code:`discard` to discard these events
+  - :code:`abort` to abort the program if one of these events is encountered
+
+  The settings :code:`keep`, :code:`discard`, and :code:`abort` are
+  supported for all event types. The :code:`reweight` treatment is
+  implemented for different event types depending on the considered
+  process:
+
+  .. csv-table::
+    :header: , "FKL", "unordered", "extremal qqbar", "central qqbar"
+    :widths: auto
+    :align: center
+    :stub-columns: 1
+
+    "pure jets", "Yes", "Yes", "Yes", "Yes"
+    "Higgs + jets", "Yes", "Yes", "No", "No"
+    "W + jets", "Yes", "Yes", "Yes", "Yes"
+    "Z/γ + jets", "Yes", "Yes", "No", "No"
+    "same-sign W + jets", "Yes", "No", "No", "No"
+
+
 
 .. _`scales`:
 
 **scales**
    Specifies the renormalisation and factorisation scales for the output
    events. This can either be a single entry or a list :code:`[scale1,
    scale2, ...]`. For the case of a list the first entry defines the
    central scale. Possible values are fixed numbers to set the scale in
    GeV or the following:
 
    - :code:`H_T`: The sum of the scalar transverse momenta of all
      final-state particles
    - :code:`max jet pperp`: The maximum transverse momentum of all jets
    - :code:`jet invariant mass`: Sum of the invariant masses of all jets
    - :code:`m_j1j2`: Invariant mass between the two hardest jets.
 
    Scales can be multiplied or divided by overall factors, e.g. :code:`H_T/2`.
 
    It is also possible to import scales from an external library, see
    :ref:`Custom scales`
 
 .. _`scale factors`:
 
 **scale factors**
    A list of numeric factors by which each of the `scales`_ should be
    multiplied. Renormalisation and factorisation scales are varied
    independently. For example, a list with entries :code:`[0.5, 2]`
    would give the four scale choices (0.5μ\ :sub:`r`, 0.5μ\ :sub:`f`);
    (0.5μ\ :sub:`r`, 2μ\ :sub:`f`); (2μ\ :sub:`r`, 0.5μ\ :sub:`f`); (2μ\
    :sub:`r`, 2μ\ :sub:`f`) in this order. The ordering corresponds to
    the order of the final event weights.
 
 .. _`max scale ratio`:
 
 **max scale ratio**
    Specifies the maximum factor by which renormalisation and
    factorisation scales may difer. For a value of :code:`2` and the
    example given for the `scale factors`_ the scale choices
    (0.5μ\ :sub:`r`, 2μ\ :sub:`f`) and (2μ\ :sub:`r`, 0.5μ\ :sub:`f`)
    will be discarded.
 
 .. _`log correction`:
 
 **log correction**
    Whether to include corrections due to the evolution of the strong
    coupling constant in the virtual corrections. Allowed values are
    :code:`true` and :code:`false`.
 
 .. _`NLO truncation`:
 
 **NLO truncation**
    Options to truncate the HEJ resummation at next-to-leading order. Used for
    bin-by-bin NLO reweighting.
 
    .. _`NLO truncation: enabled`:
 
    **enabled**
       Enable truncation. Allowed values are :code:`true` and
       :code:`false` (default).
 
    .. _`NLO truncation: nlo order`:
 
    **nlo order**
       Set the (base) number of jets in the NLO sample.
       Allowed values are integers (default: 2).
 
 .. _`unweight`:
 
 **unweight**
    Settings for unweighting events. Unweighting can greatly reduce the
    number of resummation events, speeding up analyses and shrinking
    event file sizes.
 
    .. _`type`:
 
    **type**
       How to unweight events. The supported settings are
 
       - :code:`weighted`: Generate weighted events. Default, if nothing
         else specified.
       - :code:`resummation`: Unweight only resummation events. Each set
         of resummation events coming from *a single fixed order event*
         are unweighted separately according to the largest weight in the
         current chunk of events.
       - :code:`partial`: Unweight only resummation events with weights
         below a certain threshold. The weight threshold is determined
         automatically in a calibration run prior to the usual event
         generation.
 
    .. _`unweight: trials`:
 
    **trials**
       Maximum number of trial resummation events generated in the
       calibration run for partial unweighting. This option should only
       be set for partial unweighting.
 
       If possible, each trial is generated from a different input
       fixed-order event. If there are not sufficiently many input
       events, more than one trial event may be generated for each of
       them and the actual number of trial events may be smaller than
       requested.
 
       Increasing the number of trials generally leads to better
       unweighting calibration but increases the run time. Between 1000
       and 10000 trials are usually sufficient.
 
    .. _`unweight: max deviation`:
 
    **max deviation**
        Controls the range of events to which unweighting is
        applied. This option should only be set for partial unweighting.
 
        A larger value means that a larger fraction of events are
        unweighted.  Typical values are between -1 and 1.
 
 .. _`event output`:
 
 **event output**
    Specifies the name of a single event output file or a list of such
    files. The file format is either specified explicitly or derived from
    the suffix. For example, :code:`events.lhe` or, equivalently
    :code:`Les Houches: events.lhe` generates an output event file
    :code:`events.lhe` in the Les Houches format. The supported formats
    are
 
    - :code:`file.lhe` or :code:`Les Houches: file`: The Les Houches
      event file format.
    - :code:`file.hepmc2` or :code:`HepMC2: file`: HepMC format version 2.
    - :code:`file.hepmc3` or :code:`HepMC3: file`: HepMC format version 3.
    - :code:`file.hepmc` or :code:`HepMC: file`: The latest supported
      version of the HepMC format, currently version 3.
    - :code:`file.hdf5` or :code:`HDF5: file`: The HDF5-based format of
      `arXiv:1905.05120 <https://arxiv.org/abs/1905.05120>`_.
 
 .. _`random generator`:
 
 **random generator**
    Sets parameters for random number generation.
 
    .. _`random generator: name`:
 
    **name**
       Which random number generator to use. Currently, :code:`mixmax`
       and :code:`ranlux64` are supported. Mixmax is recommended. See
       the `CLHEP documentation
       <http://proj-clhep.web.cern.ch/proj-clhep/index.html#docu>`_ for
       details on the generators.
 
    .. _`random generator: seed`:
 
    **seed**
       The seed for random generation. This should be a single number for
       :code:`mixmax` and the name of a state file for :code:`ranlux64`.
 
 .. _`analyses`:
 
 **analyses**
    Names and settings for one or more custom and Rivet event
    analyses.
 
    Entries containing the :code:`rivet` key are interpreted as Rivet analyses;
    the values corresponding to this key should be the analyses names. In
    addition, there is a mandatory :code:`output` key which determines the prefix
    for the yoda output file.
 
    For a custom analysis the :code:`plugin` sub-entry
    should be set to the analysis file path. All further entries are passed on
    to the analysis. See :ref:`Writing custom analyses` for details.
 
 .. _`vev`:
 
 **vev**
    Higgs vacuum expectation value in GeV. All electro-weak constants are derived
    from this together with the `particle properties`_.
 
 .. _`particle properties`:
 
 **particle properties**
 
    Specifies various properties of the different particles (Higgs, W or Z). All
    electro-weak constants are derived from these together with the :ref:`vacuum
    expectation value<vev>`.
 
 .. _`particle properties: particle`:
 
    **Higgs, W or Z**
       The particle (Higgs, |W+| or |W-|, Z) for which the following properties
       are defined.
 
       .. |W+| replace:: W\ :sup:`+`
       .. |W-| replace:: W\ :sup:`-`
 
    .. _`particle properties: particle: mass`:
 
       **mass**
          The mass of the particle in GeV.
 
    .. _`particle properties: particle: width`:
 
       **width**
          The total decay width of the particle in GeV.
 
 .. _`Higgs coupling`:
 
 **Higgs coupling**
    This collects a number of settings concerning the effective coupling
    of the Higgs boson to gluons. This is only relevant for the
    production process of a Higgs boson with jets and only supported if
    HEJ 2 was compiled with `QCDLoop
    <https://github.com/scarrazza/qcdloop>`_ support.
 
 .. _`Higgs coupling: use impact factors`:
 
    **use impact factors**
       Whether to use impact factors for the coupling to the most forward
       and most backward partons. Impact factors imply the infinite
       top-quark mass limit.
 
 .. _`Higgs coupling: mt`:
 
    **mt**
       The value of the top-quark mass in GeV. If this is not specified,
       the limit of an infinite mass is taken.
 
 .. _`Higgs coupling: include bottom`:
 
    **include bottom**
       Whether to include the Higgs coupling to bottom quarks.
 
 .. _`Higgs coupling: mb`:
 
    **mb**
       The value of the bottom-quark mass in GeV. Only used for the Higgs
       coupling, external bottom-quarks are always assumed to be massless.
 
 .. _`require low pt jet`:
 
 **require low pt jet**
     Restrict output to only include events which have fixed order jets with pT
     less than the `resummation jets: min pt`_. Used to calculate the
     contribution from a lower min pt in the fixed order jets. This option allows
     this calculation to be done separately from the calculation where the fixed
     order min pt is equal to the resummation jets min pt. This is usually a small
     correction.
 
 .. _`off-shell tolerance`:
 
 **off-shell tolerance**
      Tolerance for numerical inaccuracies in input momenta. Momenta of
      massless particles with an invariant mass below the given value
      are rescaled to be on-shell. Transverse momentum components of
      incoming particles that are smaller than the given tolerance are
      set to zero. The default value is 0, leaving input momenta unchanged.
 
 
 Advanced Settings
 ~~~~~~~~~~~~~~~~~
 
 All of the following settings are optional. Please **do not set** any of the
 following options, unless you know exactly what you are doing. The default
 behaviour gives the most reliable results for a wide range of observables.
 
 .. _`soft pt regulator`:
 
 **soft pt regulator**
   Specifies the maximum fraction that soft radiation can contribute to the
   transverse momentum of each the tagging jets, i.e. any jet that affects the
   event classification, like the most forward and most backward jet or the jets
   of the central qqbar pair. This setting is needed to regulate an otherwise
   cancelled divergence. Default is 0.1.
 
 .. _`max ext soft pt fraction`:
 
 **max ext soft pt fraction**
   This is the same as `soft pt regulator`_ and will be removed in future
   versions.
 
 .. _`min extparton pt`:
 
 **min extparton pt**
   Specifies the minimum transverse momentum in GeV of the most forward and the
   most backward parton. Its value should be slightly below the minimum
   transverse momentum of jets specified by `resummation jets: min pt`_. This
   setting got superseded by `soft pt regulator`_ and will be removed in future
   versions.
 
 .. _`max events`:
 
 **max events**
     Maximal number of (input) Fixed Order events. HEJ will stop after processing
     `max events` many events. Default considers all events.
 
 .. _`regulator parameter`:
 
 **regulator parameter**
     Slicing parameter to regularise the subtraction term, called :math:`\lambda`
     in `arxiv:1706.01002 <https://arxiv.org/abs/1706.01002>`_. Default is 0.2.
 
 
 
diff --git a/include/HEJ/Config.hh b/include/HEJ/Config.hh
index 3b399de..e1f0b26 100644
--- a/include/HEJ/Config.hh
+++ b/include/HEJ/Config.hh
@@ -1,291 +1,292 @@
 /** \file
  *  \brief HEJ 2 configuration parameters
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2022
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <map>
 #include <string>
 #include <utility>
 #include <optional>
 #include <vector>
 
 #include "fastjet/JetDefinition.hh"
 #include "yaml-cpp/yaml.h"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/Fraction.hh"
 #include "HEJ/HiggsCouplingSettings.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/output_formats.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
     std::optional<std::string> seed;
   };
 
   //! Settings for partial unweighting
   struct PartialUnweightConfig {
     //! Number of trials for training
     size_t trials;
     //! Maximum distance in standard deviations from mean logarithmic weight
     double max_dev;
   };
 
   //! Settings for HEJ@NLO
   struct NLOConfig {
     //! Settings for HEJ@NLO Truncation
     bool enabled = false;
     //! NLO Born number of jets
     size_t nj = 2;
   };
 
   /**! 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 */
+    abort,                                     /**< Throw an exception */
   };
 
   //! Container to store the treatments for various event types
   using EventTreatMap = std::map<event_type::EventType, EventTreatment>;
 
   //! Possible setting for the event weight
   enum class WeightType{
     weighted,             //!< weighted events
     unweighted_resum,     //!< unweighted only resummation part
     partially_unweighted  //!< mixed weighted and unweighted
   };
 
   /**! Input parameters.
    *
    * This struct handles stores all configuration parameters
    * needed in a HEJ 2 run.
    *
    * \internal To add a new option:
    *           1. Add a member to the Config struct.
    *           2. Inside "src/YAMLreader.cc":
    *              - Add the option name to the "supported" Node in
    *                get_supported_options.
    *              - Initialise the new Config member in to_Config.
    *                The functions set_from_yaml (for mandatory options) and
    *                set_from_yaml_if_defined (non-mandatory) may be helpful.
    *           3. Add a new entry (with short description) to config.yaml
    *           4. Update the user documentation in "doc/Sphinx/"
    */
   struct Config {
     //! %Parameters for scale variation
     ScaleConfig scales;
     //! Resummation jet properties
     JetParameters resummation_jets;
     //! Fixed-order jet properties
     JetParameters fixed_order_jets;
     //! Minimum transverse momentum for extremal partons
     //! \deprecated This will be removed in future versions.
     //!             Use \ref soft_pt_regulator instead.
     double min_extparton_pt = 0.;
     //! \deprecated This is equivalent to\ref soft_pt_regulator
     //!             and will be removed in future versions.
     std::optional<Fraction<double>> max_ext_soft_pt_fraction{};
     //! @brief Maximum transverse momentum fraction from soft radiation in any
     //!        tagging jet (e.g. extremal or qqbar jet)
     Fraction<double> soft_pt_regulator{ DEFAULT_SOFT_PT_REGULATOR };
     //! The regulator lambda for the subtraction terms
     double regulator_lambda = CLAMBDA;
     //! Number of resummation configurations to generate per fixed-order event
     size_t trials{};
     //! Maximal number of events
     std::optional<size_t> max_events;
     //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running
     bool log_correction{};
     //! Event output files names and formats
     std::vector<OutputFile> output;
     //! Parameters for random number generation
     RNGConfig rng;
     //! Map to decide what to do for different event types
     EventTreatMap treat;
     //! %Parameters for custom analysis
     //! @deprecated use analyses_parameters instead
     YAML::Node analysis_parameters;
     //! %Parameters for custom analyses
     std::vector<YAML::Node> analyses_parameters;
     //! Settings for effective Higgs-gluon coupling
     HiggsCouplingSettings Higgs_coupling;
     //! elector weak parameters
     EWConstants ew_parameters;
     //! Type of event weight e.g. (un)weighted
     WeightType weight_type;
     //! Settings for partial unweighting
     std::optional<PartialUnweightConfig> unweight_config;
     //! HEJ@NLO settings
     NLOConfig nlo;
     //! LowPT settings
     bool lowpt = false;
     //! Tolerance towards numerical inaccuracies in input momenta
     double off_shell_tolerance = 0.;
   };
 
   //! Configuration options for the PhaseSpacePoint class
   struct PhaseSpacePointConfig {
     PhaseSpacePointConfig() = default;
     PhaseSpacePointConfig(
         JetParameters jet_param,
         NLOConfig nlo,
         double min_extparton_pt = 0.,
         Fraction<double> soft_pt_regulator =
           Fraction<double>{DEFAULT_SOFT_PT_REGULATOR}
     ):
     jet_param{std::move(jet_param)},
     nlo{std::move(nlo)},
     min_extparton_pt{min_extparton_pt},
     soft_pt_regulator{std::move(soft_pt_regulator)}
     {}
     //! Properties of resummation jets
     JetParameters jet_param;
     //! HEJ@NLO settings
     NLOConfig nlo;
     //! Minimum transverse momentum for extremal partons
     //! \deprecated This will be removed in future versions.
     //!             Use \ref soft_pt_regulator instead.
     double min_extparton_pt = 0.;
     //! \deprecated This is equivalent to\ref soft_pt_regulator
     //!             and will be removed in future versions.
     std::optional<Fraction<double>> max_ext_soft_pt_fraction{};
     //! @brief Maximum transverse momentum fraction from soft radiation in any
     //!        tagging jet (e.g. extremal or qqbar jet)
     Fraction<double> soft_pt_regulator{ DEFAULT_SOFT_PT_REGULATOR };
   };
 
   //! Configuration options for the MatrixElement class
   struct MatrixElementConfig {
     MatrixElementConfig() = default;
     MatrixElementConfig(
         bool log_correction,
         HiggsCouplingSettings Higgs_coupling,
         EWConstants ew_parameters,
         NLOConfig nlo,
         Fraction<double> soft_pt_regulator = Fraction<double>{DEFAULT_SOFT_PT_REGULATOR},
         double regulator_lambda = CLAMBDA
     ):
     log_correction{log_correction},
     Higgs_coupling{std::move(Higgs_coupling)},
     ew_parameters{std::move(ew_parameters)},
     nlo{std::move(nlo)},
     soft_pt_regulator{soft_pt_regulator},
     regulator_lambda{regulator_lambda}
     {}
 
     //! Whether to include the logarithmic correction from \f$\alpha_s\f$ running
     bool log_correction{};
     //! Settings for effective Higgs-gluon coupling
     HiggsCouplingSettings Higgs_coupling;
     //! elector weak parameters
     EWConstants ew_parameters;
     //! HEJ@NLO settings
     NLOConfig nlo;
     //! @brief Maximum transverse momentum fraction from soft radiation in any
     //!        tagging jet (e.g. extremal or qqbar jet)
     Fraction<double> soft_pt_regulator{ DEFAULT_SOFT_PT_REGULATOR };
     //! The regulator lambda for the subtraction terms
     double regulator_lambda = CLAMBDA;
   };
 
   //! Configuration options for the EventReweighter class
   struct EventReweighterConfig {
     //! Settings for phase space point generation
     PhaseSpacePointConfig psp_config;
     //! Settings for matrix element calculation
     MatrixElementConfig ME_config;
     //! Access properties of resummation jets
     JetParameters & jet_param() {
       return psp_config.jet_param;}
     //! Access properties of resummation jets (const version)
     JetParameters const & jet_param() const {
       return psp_config.jet_param;}
     //! Treatment of the various event types
     EventTreatMap treat;
     //! Option to only keep lowpt contribution
     bool lowpt = false;
   };
 
   /**! 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.nlo,
       conf.min_extparton_pt,
       conf.max_ext_soft_pt_fraction?*conf.max_ext_soft_pt_fraction
                                    :conf.soft_pt_regulator
     };
   }
 
   /**! Extract MatrixElementConfig from Config
    *
    * @see to_PhaseSpacePointConfig, to_EventReweighterConfig
    */
   inline
   MatrixElementConfig to_MatrixElementConfig(Config const & conf) {
     return {
       conf.log_correction,
       conf.Higgs_coupling,
       conf.ew_parameters,
       conf.nlo,
       conf.soft_pt_regulator,
       conf.regulator_lambda
     };
   }
 
   /**! Extract EventReweighterConfig from Config
    *
    * @see to_PhaseSpacePointConfig, to_MatrixElementConfig
    */
   inline
   EventReweighterConfig to_EventReweighterConfig(Config const & conf) {
     return {
       to_PhaseSpacePointConfig(conf),
       to_MatrixElementConfig(conf),
       conf.treat,
       conf.lowpt
     };
   }
 
 } // namespace HEJ
diff --git a/include/HEJ/Event.hh b/include/HEJ/Event.hh
index b7102e8..aa45c24 100644
--- a/include/HEJ/Event.hh
+++ b/include/HEJ/Event.hh
@@ -1,418 +1,421 @@
 /** \file
  *  \brief Declares the Event class and helpers
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <cstddef>
 #include <iosfwd>
 #include <iterator>
+#include <string>
 #include <unordered_map>
 #include <utility>
 #include <vector>
 
 #include "boost/iterator/filter_iterator.hpp"
 
 #include "fastjet/ClusterSequence.hh"
 #include "fastjet/PseudoJet.hh"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/event_types.hh"
 
 namespace LHEF {
   class HEPEUP;
   class HEPRUP;
 }
 
 namespace fastjet {
   class JetDefinition;
 }
 
 namespace HEJ {
   class EWConstants;
   struct RNG;
   struct UnclusteredEvent;
 
   //! Method for accessing implemented types
   size_t implemented_types(std::vector<Particle> const & bosons);
 
   /** @brief An event with clustered jets
     *
     * This is the main HEJ 2 event class.
     * It contains kinematic information including jet clustering,
     * parameter (e.g. scale) settings and the event weight.
     */
   class Event {
   public:
     class EventData;
 
     //! Iterator over partons
     using ConstPartonIterator = boost::filter_iterator<
       bool (*)(Particle const &),
       std::vector<Particle>::const_iterator
       >;
     //! Reverse Iterator over partons
     using ConstReversePartonIterator = std::reverse_iterator<
                                                 ConstPartonIterator>;
     //! No default Constructor
     Event() = delete;
     //! Event Constructor adding jet clustering to an unclustered event
     //! @deprecated UnclusteredEvent got superseded by EventData
     //!             and will be removed in HEJ 2.2.0
     [[deprecated("UnclusteredEvent got superseded by EventData")]]
     Event(
       UnclusteredEvent const & ev,
       fastjet::JetDefinition const & jet_def, double min_jet_pt
     );
 
     //! @name Particle Access
     //! @{
 
     //! Incoming particles
     std::array<Particle, 2> const &  incoming() const{
       return incoming_;
     }
     //! Outgoing particles
     std::vector<Particle> const &  outgoing() const{
       return outgoing_;
     }
     //! Iterator to the first outgoing parton
     ConstPartonIterator begin_partons() const;
     //! Iterator to the first outgoing parton
     ConstPartonIterator cbegin_partons() const;
 
     //! Iterator to the end of the outgoing partons
     ConstPartonIterator end_partons() const;
     //! Iterator to the end of the outgoing partons
     ConstPartonIterator cend_partons() const;
 
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator rbegin_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator crbegin_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator rend_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator crend_partons() const;
 
     //! Particle decays
     /**
      *  The key in the returned map corresponds to the index in the
      *  vector returned by outgoing()
      */
     std::unordered_map<std::size_t, std::vector<Particle>> const & decays()
     const {
       return decays_;
     }
     //! The jets formed by the outgoing partons, sorted in rapidity
     std::vector<fastjet::PseudoJet> const & jets() const{
       return jets_;
     }
     //! @}
 
     //! @name Weight variations
     //! @{
 
     //! All chosen parameter, i.e. scale choices (const version)
     Parameters<EventParameters> const & parameters() const{
       return parameters_;
     }
     //! All chosen parameter, i.e. scale choices
     Parameters<EventParameters> & parameters(){
       return parameters_;
     }
 
     //! Central parameter choice (const version)
     EventParameters const & central() const{
       return parameters_.central;
     }
     //! Central parameter choice
     EventParameters & central(){
       return parameters_.central;
     }
 
     //! Parameter (scale) variations (const version)
     std::vector<EventParameters> const & variations() const{
       return parameters_.variations;
     }
     //! Parameter (scale) variations
     std::vector<EventParameters> & variations(){
       return parameters_.variations;
     }
 
     //! Parameter (scale) variation (const version)
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters const & variations(std::size_t i) const{
       return parameters_.variations.at(i);
     }
     //! Parameter (scale) variation
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters & variations(std::size_t i){
       return parameters_.variations.at(i);
     }
     //! @}
 
     //! Indices of the jets the outgoing partons belong to
     /**
      *  @param jets   Jets to be tested
      *  @returns      A vector containing, for each outgoing parton,
      *                the index in the vector of jets the considered parton
      *                belongs to. If the parton is not inside any of the
      *                passed jets, the corresponding index is set to -1.
      */
     std::vector<int> particle_jet_indices(
         std::vector<fastjet::PseudoJet> const & jets
     ) const {
       return cs_.particle_jet_indices(jets);
     }
     //! particle_jet_indices() of the Event jets()
     std::vector<int> particle_jet_indices() const {
       return particle_jet_indices(jets());
     }
 
     //! Jet definition used for clustering
     fastjet::JetDefinition const & jet_def() const{
       return cs_.jet_def();
     }
 
     //! Minimum jet transverse momentum
     double min_jet_pt() const{
       return min_jet_pt_;
     }
 
     //! Event type
     event_type::EventType type() const{
       return type_;
     }
 
     //! Give colours to each particle
     /**
      * @returns true if new colours are generated, i.e. same as is_resummable()
      * @details Colour ordering is done according to leading colour in the MRK
      *          limit, see \cite Andersen:2011zd. This only affects \ref
      *          is_resummable() "HEJ" configurations, all other \ref event_type
      *          "EventTypes" will be ignored.
      * @note    This overwrites all previously set colours.
      */
     bool generate_colours(RNG & /*ran*/);
 
     //! Check that current colours are leading in the high energy limit
     /**
      * @details Checks that the colour configuration can be split up in
      *          multiple, rapidity ordered, non-overlapping ladders. Such
      *          configurations are leading in the MRK limit, see
      *          \cite Andersen:2011zd
      *
      * @note This is _not_ to be confused with \ref is_resummable(), however
      *       for all resummable states it is possible to create a leading colour
      *       configuration, see generate_colours()
      */
     bool is_leading_colour() const;
 
     /**
      * @brief Check if given event could have been produced by HEJ
      * @details A HEJ state has to fulfil:
      *          1. type() has to be \ref is_resummable() "resummable"
      *          2. Soft radiation in the tagging jets contributes at most to
      *             `soft_pt_regulator` of the total jet \f$ p_\perp \f$
      *          3. Partons related to subleading configurations (uno gluon, qqbar)
      *             must be in separate jets.
      *
      * @note This is true for any resummed stated produced by the
      *       EventReweighter or any \ref is_resummable() "resummable" Leading
      *       Order state.
      *
      * @param soft_pt_regulator  Maximum transverse momentum fraction from soft
      *                           radiation in tagging jets
      * @param min_pt             Absolute minimal \f$ p_\perp \f$,
      *                           \b deprecated use soft_pt_regulator instead
      * @return True if this state could have been produced by HEJ
      */
     bool valid_hej_state(
       double soft_pt_regulator = DEFAULT_SOFT_PT_REGULATOR,
       double min_pt = 0.
     ) const;
 
     //! Check that the incoming momenta are valid
     /**
      * @details Checks that the incoming parton momenta are on-shell and have
      *          vanishing transverse components.
      *
      */
     bool valid_incoming() const;
 
   private:
     //! \internal
     //! @brief Construct Event explicitly from input.
     /** This is only intended to be called from EventData.
      *
      * \warning The input is taken _as is_, sorting and classification has to be
      *          done externally, i.e. by EventData
      */
     Event(
       std::array<Particle, 2> && incoming,
       std::vector<Particle> && outgoing,
       std::unordered_map<std::size_t, std::vector<Particle>> && decays,
       Parameters<EventParameters> && parameters,
       fastjet::JetDefinition const & jet_def,
       double min_jet_pt
     );
 
     //! Iterator over partons (non-const)
     using PartonIterator = boost::filter_iterator<
       bool (*)(Particle const &),
       std::vector<Particle>::iterator
       >;
     //! Reverse Iterator over partons (non-const)
     using ReversePartonIterator = std::reverse_iterator<PartonIterator>;
 
     //! Iterator to the first outgoing parton (non-const)
     PartonIterator begin_partons();
     //! Iterator to the end of the outgoing partons (non-const)
     PartonIterator end_partons();
 
     //! Reverse Iterator to the first outgoing parton (non-const)
     ReversePartonIterator rbegin_partons();
     //! Reverse Iterator to the first outgoing parton (non-const)
     ReversePartonIterator rend_partons();
 
     std::array<Particle, 2> incoming_;
     std::vector<Particle> outgoing_;
     std::unordered_map<std::size_t, std::vector<Particle>> decays_;
     std::vector<fastjet::PseudoJet> jets_;
     Parameters<EventParameters> parameters_;
     fastjet::ClusterSequence cs_;
     double min_jet_pt_;
     event_type::EventType type_;
   }; // end class Event
 
 
   //! Detect if a backward incoming gluon turns into a backward outgoing Higgs boson
   inline
   bool is_backward_g_to_h(Event const & ev) {
     return ev.outgoing().front().type == pid::higgs
       && ev.incoming().front().type == pid::gluon;
   }
 
   //! Detect if a forward incoming gluon turns into a forward outgoing Higgs boson
   inline
   bool is_forward_g_to_h(Event const & ev) {
     return ev.outgoing().back().type == pid::higgs
       && ev.incoming().back().type == pid::gluon;
   }
 
   //! Class to store general Event setup, i.e. Phase space and weights
   class Event::EventData {
   public:
     //! Default Constructor
     EventData() = default;
     //! Constructor from LesHouches event information
     EventData(LHEF::HEPEUP const & hepeup);
     //! Constructor with all values given
     EventData(
       std::array<Particle, 2> incoming,
       std::vector<Particle> outgoing,
       std::unordered_map<std::size_t, std::vector<Particle>> decays,
       Parameters<EventParameters> parameters
     ):
       incoming(std::move(incoming)), outgoing(std::move(outgoing)),
       decays(std::move(decays)), parameters(std::move(parameters))
     {}
 
     //! Generate an Event from the stored EventData.
     /**
      * @details          Do jet clustering and classification.
      *                   Use this to generate an Event.
      *
      * @note             Calling this function destroys EventData
      *
      * @param jet_def    Jet definition
      * @param min_jet_pt minimal \f$p_T\f$ for each jet
      *
      * @returns          Full clustered and classified event.
      */
     Event cluster(
       fastjet::JetDefinition const & jet_def, double min_jet_pt);
 
     //! Alias for cluster()
     Event operator()(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt){
       return cluster(jet_def, min_jet_pt);
     }
 
     //! Sort particles in rapidity
     void sort();
 
     //! Reconstruct intermediate particles from final-state leptons
     /**
      *  Final-state leptons are created from virtual photons, W, or Z bosons.
      *  This function tries to reconstruct such intermediate bosons if they
      *  are not part of the event record.
      */
     void reconstruct_intermediate(EWConstants const & /*ew_parameters*/);
 
     //! Repair momenta of massless particles
     /**
      * This function changes the momenta of massless particles as follows:
      * - Close-to-zero incoming transverse momenta are set to zero.
      * - Nearly on-shell momenta are made lightlike by rescaling energy
      *   and spatial components. This rescaling is applied to both incoming
      *   and outgoing particles, including decay products.
      */
     void repair_momenta(double tolerance);
 
     //! Incoming particles
     std::array<Particle, 2> incoming;
     //! Outcoing particles
     std::vector<Particle> outgoing;
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<std::size_t, std::vector<Particle>> decays;
     //! Parameters, e.g. scale or inital weight
     Parameters<EventParameters> parameters;
   }; // end class EventData
 
   //! Print Event
   std::ostream& operator<<(std::ostream & os, Event const & ev);
 
+  std::string to_string(Event const & ev);
+
   //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$
   double shat(Event const & ev);
 
   //! Tolerance parameter for validity check on incoming momenta
   static constexpr double TOL = 1e-6;
 
   //! Convert an event to a LHEF::HEPEUP
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * /*heprup*/);
 
   // put deprecated warning at the end, so don't get the warning inside Event.hh,
   // additionally doxygen can not identify [[deprecated]] correctly
   struct [[deprecated("UnclusteredEvent will be replaced by EventData")]]
     UnclusteredEvent;
   //! An event before jet clustering
     //! @deprecated UnclusteredEvent got superseded by EventData
     //!             and will be removed in HEJ 2.2.0
   struct UnclusteredEvent{
     //! Default Constructor
     UnclusteredEvent() = default;
     //! Constructor from LesHouches event information
     UnclusteredEvent(LHEF::HEPEUP const & hepeup);
 
     std::array<Particle, 2> incoming;          /**< Incoming Particles */
     std::vector<Particle> outgoing;            /**< Outgoing Particles */
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<std::size_t, std::vector<Particle>> decays;
     //! Central parameter (e.g. scale) choice
     EventParameters central;
     std::vector<EventParameters> variations;    /**< For parameter variation */
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/EventReweighter.hh b/include/HEJ/EventReweighter.hh
index d65e545..2a4d213 100644
--- a/include/HEJ/EventReweighter.hh
+++ b/include/HEJ/EventReweighter.hh
@@ -1,196 +1,206 @@
 /** \file
  *  \brief Declares the EventReweighter class
  *
  *  EventReweighter is the main class used within HEJ 2. It reweights the
  *  resummation events.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <cstddef>
 #include <memory>
 #include <utility>
 #include <vector>
 
 #include "HEJ/Config.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/StatusCode.hh"
 #include "HEJ/event_types.hh"
 
 namespace LHEF {
   class HEPRUP;
 }
 
 namespace HEJ {
   class Event;
   struct RNG;
 
   //! Beam parameters
   /**
    *  Currently, only symmetric beams are supported,
    *  so there is a single beam energy.
    */
   struct Beam{
     double E;                                /**< Beam energy */
     std::array<ParticleID, 2> type;          /**< Beam particles */
   };
 
   //! Main class for reweighting events in HEJ.
   class EventReweighter{
     using EventType = event_type::EventType;
 
   public:
     EventReweighter(
         Beam const & beam,                    /**< Beam Energy */
         int pdf_id,                           /**< PDF ID */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         std::shared_ptr<RNG> ran              /**< Random number generator */
     );
 
     EventReweighter(
         LHEF::HEPRUP const & heprup,          /**< LHEF event header */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         std::shared_ptr<RNG> ran              /**< Random number generator */
     );
 
     //! Get the used pdf
     PDF const & pdf() const;
 
     //! Check the lowpt only restriction passes for lowpt runs
     bool pass_low_pt(
         HEJ::Event const & input_ev
     );
 
     //! Get event treatment
     EventTreatment treatment(EventType type) const;
 
     //! Generate resummation events for a given fixed-order event
     /**
      *  @param ev             Fixed-order event corresponding
      *                        to the resummation events
      *  @param num_events     Number of trial resummation configurations.
      *  @returns              A vector of resummation events.
      *
      *  The result vector depends on the type of the input event and the
      *  \ref EventTreatment of different types as specified in the constructor:
      *
      *  - EventTreatment::reweight: The result vector contains between 0 and
      *                              num_events resummation events.
      *  - EventTreatment::keep:     If the input event passes the resummation
      *                              jet cuts the result vector contains one
      *                              event. Otherwise it is empty.
      *  - EventTreatment::discard:  The result vector is empty
      */
     std::vector<Event> reweight(
         Event const & ev,
         std::size_t num_events
     );
 
     //! Gives all StatusCodes of the last reweight()
     /**
      * Each StatusCode corresponds to one tried generation. Only good
      * StatusCodes generated an event.
      */
     std::vector<StatusCode> const & status() const {
         return status_;
     }
 
   private:
 
     /** \internal
      * \brief main generation/reweighting function:
      * generate phase space points and divide out Born factors
      */
     std::vector<Event> gen_res_events(
         Event const & ev, std::size_t phase_space_points
     );
     std::vector<Event> rescale(
         Event const & Born_ev, std::vector<Event> events
     ) const;
 
     /** \internal
      * \brief Do the Jets pass the resummation Cuts?
      *
      * @param ev               Event in Question
      * @returns                0 or 1 depending on if ev passes Jet Cuts
      */
     bool jets_pass_resummation_cuts(Event const & ev) const;
 
     /** \internal
      * \brief pdf_factors Function
      *
      * @param ev         Event in Question
      * @returns          EventFactor due to PDFs
      *
      * Calculates the Central value and the variation due
      * to the PDF choice made.
      */
     Weights pdf_factors(Event const & ev) const;
 
     /** \internal
      * \brief matrix_elements Function
      *
      * @param ev         Event in question
      * @returns          EventFactor due to MatrixElements
      *
      * Calculates the Central value and the variation due
      * to the Matrix Element.
      */
     Weights matrix_elements(Event const & ev) const;
 
     /** \internal
      * \brief Scale-dependent part of fixed-order matrix element
      *
      * @param ev         Event in question
      * @returns          EventFactor scale variation due to FO-ME.
      *
      * This is only called to compute the scale variation for events where
      * we don't do resummation (e.g. non-FKL).
      * Since at tree level the scale dependence is just due to alpha_s,
      * it is enough to return the alpha_s(mur) factors in the matrix element.
      * The rest drops out in the ratio of (output event ME)/(input event ME),
      * so we never have to compute it.
      */
     Weights fixed_order_scale_ME(Event const & ev) const;
 
     /** \internal
      * \brief Computes the tree level matrix element
      *
      * @param ev                Event in Question
      * @returns                 HEJ approximation to Tree level Matrix Element
      *
      * This computes the HEJ approximation to the tree level FO
      * Matrix element which is used within the LO weighting process.
      */
     double tree_matrix_element(Event const & ev) const;
 
     //! \internal General parameters
     EventReweighterConfig param_;
 
     //! \internal Beam energy
     double E_beam_;
 
     //! \internal PDF
     PDF pdf_;
 
     //! \internal Object to calculate the square of the matrix element
     MatrixElement MEt2_;
     //! \internal Object to calculate event renormalisation and factorisation scales
     ScaleGenerator scale_gen_;
     //! \internal random number generator
     std::shared_ptr<RNG> ran_;
     //! \internal StatusCode of each attempt
     std::vector<StatusCode> status_;
   };
 
+  //! Exception indicating that an event is not accepted by the reweighter
+  /**
+   *  When constructing an EventReweighter one can specify the \ref abort treatment
+   *  for a certain EventType. If such an Event is passed to EventReweighter::reweight(),
+   *  this exception is thrown.
+   */
+  struct abort_event: std::invalid_argument {
+    explicit abort_event(Event const & ev);
+  };
+
 } // namespace HEJ
diff --git a/include/HEJ/event_types.hh b/include/HEJ/event_types.hh
index a9e3875..c62633a 100644
--- a/include/HEJ/event_types.hh
+++ b/include/HEJ/event_types.hh
@@ -1,113 +1,113 @@
 /** \file
  *  \brief Define different types of events.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "HEJ/exceptions.hh"
 
 namespace HEJ {
 
   //! Namespace for event types
   namespace event_type {
     //! Possible event types
     enum EventType: std::size_t {
       non_resummable = 0,     //!< event configuration not covered by All Order resummation
-      bad_final_state = 1,    //!< event with an unsupported final state
-      not_enough_jets = 2,    //!< event with less than two jets
+      invalid = 1,            //!< unphysical event, e.g. violating conservation laws
+      unknown = 2,            //!< configuration not considered by HEJ, e.g di-Higgs
       FKL = 4,                //!< FKL-type event
       unordered_backward = 8, //!< event with unordered backward emission
       unordered_forward = 16, //!< event with unordered forward emission
       extremal_qqbar_backward = 32,        //!< event with a backward extremal qqbar
       extremal_qqbar_forward = 64,         //!< event with a forward extremal qqbar
       central_qqbar = 128,                 //!< event with a central qqbar
       unob = unordered_backward,           //!< alias for unordered_backward
       unof = unordered_forward,            //!< alias for unordered_forward
       qqbar_exb = extremal_qqbar_backward, //!< alias for extremal_qqbar_backward
       qqbar_exf = extremal_qqbar_forward,  //!< alias for extremal_qqbar_forward
       qqbar_mid = central_qqbar,           //!< alias for central_qqbar
-      first_type = non_resummable,         //!< alias for non_resummable
-      last_type = central_qqbar            //!< alias for central_qqbar
+      first_type = non_resummable,         //!< alias for numerically smallest enumerator
+      last_type = central_qqbar            //!< alias for numerically largest enumerator
     };
 
     constexpr std::size_t UNO =  unordered_backward | unordered_forward;
 
     constexpr std::size_t EXTREMAL_QQBAR =
       extremal_qqbar_backward | extremal_qqbar_forward;
 
     constexpr std::size_t QQBAR =
       EXTREMAL_QQBAR | central_qqbar;
 
     constexpr auto NLL = UNO | QQBAR;
 
     constexpr auto RESUMMABLE = FKL | NLL;
 
     constexpr auto VALID = RESUMMABLE | non_resummable;
 
     //! Event type names
     /**
      * For example, name(FKL) is the string "FKL"
      */
     inline
     std::string name(EventType type) {
       switch(type) {
       case FKL:
         return "FKL";
       case unordered_backward:
         return "unordered backward";
       case unordered_forward:
         return "unordered forward";
       case extremal_qqbar_backward:
         return "extremal qqbar backward";
       case extremal_qqbar_forward:
         return "extremal qqbar forward";
       case central_qqbar:
         return "central qqbar";
       case non_resummable:
         return "non-resummable";
-      case not_enough_jets:
-        return "not enough jets";
-      case bad_final_state:
-        return "bad final state";
+      case invalid:
+        return "invalid";
+      case unknown:
+        return "unknown";
       default:
         throw std::logic_error{"Unreachable"};
       }
     }
 
     //! Returns True for a HEJ \ref event_type::EventType "EventType"
     inline
     constexpr bool is_resummable(EventType type) {
       return type & RESUMMABLE;
     }
 
     //! Returns True for an unordered \ref event_type::EventType "EventType"
     inline
     constexpr bool is_uno(EventType type) {
       return type & UNO;
     }
 
     //! Returns True for an extremal_qqbar \ref event_type::EventType "EventType"
     inline
     constexpr bool is_ex_qqbar(EventType type) {
       return type & EXTREMAL_QQBAR;
     }
 
     //! Returns True for an central_qqbar \ref event_type::EventType "EventType"
     inline
     constexpr bool is_mid_qqbar(EventType type) {
       return type == central_qqbar;
     }
 
     //! Returns True for any qqbar event \ref event_type::EventType "EventType"
     inline
     constexpr bool is_qqbar(EventType type) {
       return type & QQBAR;
     }
 
   } // namespace event_type
 } // namespace HEJ
diff --git a/include/HEJ/utility.hh b/include/HEJ/utility.hh
index 997dce8..6bbf2bf 100644
--- a/include/HEJ/utility.hh
+++ b/include/HEJ/utility.hh
@@ -1,104 +1,114 @@
 /**
  * \file
  * \brief Contains various utilities
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <memory>
 #include <string>
 
 #include "boost/core/demangle.hpp"
 
 #include "fastjet/PseudoJet.hh"
 
 namespace HEJ {
 
   inline
   std::string join(
       std::string const & /* delim */
   ){
     return "";
   }
 
   inline
   std::string join(
       std::string const & /* delim */, std::string const & str
   ){
     return str;
   }
 
   //! Join strings with a delimiter
   /**
    *   @param delim      Delimiter to be put between consecutive strings
    *   @param first      First string
    *   @param second     Second string
    *   @param rest       Remaining strings
    */
   template<typename... Strings>
   std::string join(
       std::string const & delim,
       std::string const & first, std::string const & second,
       Strings&&... rest
   ){
     return join(delim, first + delim + second, std::forward<Strings>(rest)...);
   }
 
   //! Return the name of the argument's type
   template<typename T>
   std::string type_string(T&& /*unused*/){
     return boost::core::demangle(typeid(T).name());
   }
 
   //! Eliminate compiler warnings for unused variables
   template<typename... T>
   constexpr void ignore(T&&... /*unused*/) {}
 
-  //! Check whether two doubles are closer than ep > 0 to each other
+  //! Check whether two doubles are closer than ep >= 0 to each other
   inline
   constexpr bool nearby_ep(double a, double b, double ep){
-    assert(ep > 0);
+    assert(ep >= 0);
     return std::abs(a-b) < ep;
   }
 
   //! Check whether all components of two PseudoJets are closer than ep to each other
   inline
   bool nearby_ep(
       fastjet::PseudoJet const & pa, fastjet::PseudoJet const & pb,
       double ep
   ){
-    assert(ep > 0);
+    assert(ep >= 0);
     for(size_t i = 0; i < 4; ++i){
       if(!nearby_ep(pa[i], pb[i], ep)) return false;
     }
     return true;
   }
 
   inline
   bool nearby(
       fastjet::PseudoJet const & pa, fastjet::PseudoJet const & pb,
       double const norm = 1.
   ){
     return nearby_ep(pa, pb, 1e-7*norm);
   }
 
   namespace detail {
     template<typename T, std::size_t N, std::size_t... Ns>
     struct ArrayTag{
       using type = typename ArrayTag<std::array<T, N>, Ns...>::type;
     };
     template<typename T, std::size_t N>
     struct ArrayTag<T, N> {
       using type = std::array<T, N>;
     };
   }
   // helper for multidimensional std::array, for example
   // MultiArray<T, N1, N2> = std::array<std::array<T, N1>, N2>
   template<typename T, std::size_t N, std::size_t... Ns>
   using MultiArray = typename detail::ArrayTag<T, N, Ns...>::type;
 
+  //! Check momentum conservation
+  template <class Event>
+  bool momentum_conserved(Event const &ev, const double tolerance = 1e-7) {
+    fastjet::PseudoJet diff;
+    for (auto const &in : ev.incoming()) diff += in.p;
+    const double norm = diff.E();
+    for (auto const &out : ev.outgoing()) diff -= out.p;
+    return nearby_ep(diff, fastjet::PseudoJet{}, tolerance*norm);
+  }
+
 } // namespace HEJ
diff --git a/src/Event.cc b/src/Event.cc
index 45e09b3..198e4aa 100644
--- a/src/Event.cc
+++ b/src/Event.cc
@@ -1,1408 +1,1509 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/Event.hh"
 
 #include <algorithm>
+#include <boost/rational.hpp>
 #include <cassert>
 #include <cstdlib>
 #include <iomanip>
 #include <iterator>
 #include <memory>
 #include <numeric>
 #include <optional>
 #include <ostream>
+#include <sstream>
 #include <string>
 #include <utility>
 
 #include "HEJ/event_types.hh"
 #include "fastjet/ClusterSequence.hh"
 #include "fastjet/JetDefinition.hh"
 #include "fastjet/PseudoJet.hh"
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/RNG.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/LorentzVector.hh"
 #include "HEJ/utility.hh"
 
 namespace HEJ {
 
     /**
      * returns all EventTypes implemented in HEJ
      */
     size_t implemented_types(std::vector<Particle> const & bosons){
       using namespace event_type;
       // no bosons
       if(bosons.empty()) return FKL | UNO | QQBAR;
       // 1 boson
       if(bosons.size()== 1) {
       switch (bosons[0].type) {
         case ParticleID::Wp:
         case ParticleID::Wm:
           return FKL | UNO | QQBAR;
         case ParticleID::Z_photon_mix:
           return FKL | UNO;
         case ParticleID::h:
           return FKL | UNO;
         default:
           return non_resummable;
         }
       }
       // 2 bosons
       if(bosons.size() == 2) {
         // Resum only samesign W events
         if(bosons[0].type == ParticleID::Wp && bosons[1].type == ParticleID::Wp) {
           return FKL;
         }
         else if(bosons[0].type == ParticleID::Wm && bosons[1].type == ParticleID::Wm) {
           return FKL;
         }
       }
 
       return non_resummable;
     }
 
   namespace {
     using std::size_t;
 
     //! LHE status codes
     namespace lhe_status {
       enum Status: int {
         in = -1,
         decay = 2,
         out = 1,
       };
     }
     using LHE_Status = lhe_status::Status;
 
     //! true if leptonic W decay
     bool valid_W_decay( int const w_charge,
                         std::vector<Particle> const & decays
     ){
       assert(std::abs(w_charge) == 1);
       if(decays.size() != 2) // no 1->2 decay
         return false;
       const int pidsum = decays[0].type + decays[1].type;
       if( std::abs(pidsum) != 1 || pidsum != w_charge ) // correct charge
         return false;
       // leptonic decay (only check first, second follows from pidsum)
       if( w_charge == 1 ) // W+
         return is_charged_antilepton(decays[0]) || is_neutrino(decays[0]);
       // W-
       return is_charged_lepton(decays[0]) || is_antineutrino(decays[0]);
     }
 
     //! true for Z decay to charged leptons
     bool valid_Z_decay(std::vector<Particle> const & decays){
       if(decays.size() != 2) // no 1->2 decay
         return false;
       if(decays[0].type != anti(decays[1].type)) {
         return false;
       }
       // leptonic decay (only check first, second follows from above)
       return is_charged_anylepton(decays[0]);
     }
 
     //! true if supported decay
     bool valid_decay(std::vector<Particle> const & decays){
       return valid_W_decay(+1, decays) ||       // Wp
              valid_W_decay(-1, decays) ||       // Wm
              valid_Z_decay(    decays)          // Z/gamma
       ;
     }
 
     /// @name helper functions to determine event type
     //@{
 
     /**
-     * \brief check if final state valid for HEJ
-     *
-     * check final state has the expected number of valid decays for bosons
-     * and all the rest are quarks or gluons
-     */
-    bool final_state_ok(Event const & ev){
-      size_t invalid_decays = ev.decays().size();
-
-      std::vector<Particle> const & outgoing = ev.outgoing();
-      for( size_t i=0; i<outgoing.size(); ++i ){
-        auto const & out{ outgoing[i] };
-        if(is_AWZH_boson(out.type)){
-          auto const decay = ev.decays().find(i);
-
-          // Momentum Conservating Decays
-          if(decay != ev.decays().cend()) {
-            auto const progeny = decay -> second;
-
-            fastjet::PseudoJet res = std::accumulate(
-              progeny.cbegin(), progeny.cend(), fastjet::PseudoJet(),
-              [](fastjet::PseudoJet & sum, Particle const & p) -> fastjet::PseudoJet {
-                return std::move(sum) + p.p;
-              }
-            );
-
-            if(!nearby(out.p, res, out.E())){ return false; }
-          }
-
-          // W decays (required)
-          if(std::abs(out.type) == ParticleID::Wp){
-            if( decay != ev.decays().cend() &&
-                valid_W_decay(out.type>0?+1:-1, decay->second)
-            ){
-              --invalid_decays;
-            }
-            else return false;
-          }
-
-          // Higgs decays (optional)
-          else if(out.type == ParticleID::h){
-            if(decay != ev.decays().cend()) --invalid_decays;
-          }
-
-          // Z decays (required)
-          else if(out.type == ParticleID::Z_photon_mix){
-            if( decay != ev.decays().cend() &&
-                valid_Z_decay(decay->second)
-            ){
-              --invalid_decays;
-            }
-            else return false;
-          }
-        }
-        else if(! is_parton(out.type)) return false;
-      }
-      // any invalid decays?
-      return invalid_decays == 0;
-    }
-
-
-
-    /**
      * \brief function which determines if type change is consistent with Wp emission.
      * @param in                      incoming Particle id
      * @param out                     outgoing Particle id
      * @param is_qqbar                Current both incoming/both outgoing?
      *
      * \see is_Wm_Change
      */
     bool is_Wp_Change(ParticleID in, ParticleID out, bool is_qqbar){
       using namespace pid;
       if(!is_qqbar && (in==d_bar || in==u || in==s_bar || in==c))
         return out ==  (in-1);
       if( is_qqbar && (in==d || in==u_bar || in==s || in==c_bar))
         return out == -(in+1);
       return false;
     }
 
     /**
      * \brief function which determines if type change is consistent with Wm emission.
      * @param in                      incoming Particle id
      * @param out                     outgoing Particle id
      * @param is_qqbar                Current both incoming/both outgoing?
      *
      * Ensures that change type of quark line is possible by a flavour changing
      * Wm emission. Allows checking of is_qqbar currents also.
      */
     bool is_Wm_Change(ParticleID in, ParticleID out, bool is_qqbar){
       using namespace pid;
       if(!is_qqbar && (in==d || in==u_bar || in==s || in==c_bar))
         return out ==  (in+1);
       if( is_qqbar && (in==d_bar || in==u || in==s_bar || in==c))
         return out == -(in-1);
       return false;
     }
 
     /**
      * \brief checks if particle type remains same from incoming to outgoing
      * @param in                      incoming Particle
      * @param out                     outgoing Particle
      * @param is_qqbar                Current both incoming/outgoing?
      */
     bool no_flavour_change(ParticleID in, ParticleID out, bool is_qqbar){
       const int qqbarCurrent = is_qqbar?-1:1;
       if(std::abs(in)<=pid::top || in==pid::gluon)
         return (in==out*qqbarCurrent);
       return false;
     }
 
-    bool has_enough_jets(Event const & event){
-      if(event.jets().size() >= 2) return true;
-      if(event.jets().empty()) return false;
-      // check for h+jet
-      const auto the_higgs = std::find_if(
-        begin(event.outgoing()), end(event.outgoing()),
-        [](const auto & particle) { return particle.type == pid::higgs; }
-      );
-      return the_higgs != end(event.outgoing());
-    }
-
     bool is_gluon_to_Higgs(const ParticleID in, const ParticleID out) {
       return in == pid::gluon && out == pid::Higgs;
     }
 
     /**
      * \brief check if we have a valid Impact factor
      * @param in                      incoming Particle
      * @param out                     outgoing Particle
      * @param is_qqbar                Current both incoming/outgoing?
      * @param W_change                returns +1 if Wp, -1 if Wm, else 0
      */
     bool is_valid_impact_factor(
       ParticleID in, ParticleID out, bool is_qqbar, int & W_change
     ){
       if( no_flavour_change(in, out, is_qqbar) || is_gluon_to_Higgs(in, out)) {
         return true;
       }
       if( is_Wp_Change(in, out, is_qqbar) ) {
         W_change+=1;
         return true;
       }
       if( is_Wm_Change(in, out, is_qqbar) ) {
         W_change-=1;
         return true;
       }
       return false;
     }
 
     bool is_extremal_higgs_off_quark(
       const ParticleID in,
       const ParticleID extremal_out,
       const ParticleID out
     ) {
       return in == out && extremal_out == pid::higgs && is_anyquark(in);
     }
 
     //! Returns all possible classifications from the impact factors
     // the beginning points are changed s.t. after the the classification they
     // point to the beginning of the (potential) FKL chain
     // sets W_change: + if Wp change
     //                0 if no change
     //                - if Wm change
     // This function can be used with forward & backwards iterators
     template<class OutIterator>
     size_t possible_impact_factors(
       ParticleID incoming_id,                                   // incoming
       OutIterator   & begin_out, OutIterator   const & end_out, // outgoing
       int & W_change, std::vector<Particle> const & boson,
       bool const backward                                       // backward?
     ){
       using namespace event_type;
 
 
       if(begin_out == end_out) return non_resummable;
       // keep track of all states that we don't test
       size_t not_tested = qqbar_mid;
       if(backward)
         not_tested |= unof | qqbar_exf;
       else
         not_tested |= unob | qqbar_exb;
 
       // Is this LL current?
       if( is_valid_impact_factor(incoming_id, begin_out->type, false, W_change) ){
         ++begin_out;
         return not_tested | FKL;
       }
       // q -> H q and qbar -> H qbar are technically not LL,
       // but we treat them as such anyway
       const auto next = std::next(begin_out);
       if(
         // first ensure that the next particle is not part of the *other* impact factor
         next != end_out
         && is_extremal_higgs_off_quark(incoming_id, begin_out->type, next->type)
       ) {
         std::advance(begin_out, 2);
         return not_tested | FKL;
       }
 
       // or NLL current?
       // -> needs two partons in two different jets
       if( std::distance(begin_out, end_out)>=2
       ){
         auto next = std::next(begin_out);
         // Is this unordered emisson?
         if( incoming_id!=pid::gluon && begin_out->type==pid::gluon ){
           if( is_valid_impact_factor(
                 incoming_id, next->type, false, W_change )
           ){
             // veto Higgs inside uno
             assert(next!=end_out);
             if( !boson.empty() && boson.front().type == ParticleID::h
             ){
               if(  (backward && boson.front().rapidity() < next->rapidity())
                 ||(!backward && boson.front().rapidity() > next->rapidity()))
               return non_resummable;
             }
             begin_out = std::next(next);
             return not_tested | (backward?unob:unof);
           }
         }
         // Is this QQbar?
         else if( incoming_id==pid::gluon ){
           if( is_valid_impact_factor(
                 begin_out->type, next->type, true, W_change )
           ){
             // veto Higgs inside qqbar
             assert(next!=end_out);
             if( !boson.empty() && boson.front().type == ParticleID::h
             ){
               if(  (backward && boson.front().rapidity() < next->rapidity())
                 ||(!backward && boson.front().rapidity() > next->rapidity()))
               return non_resummable;
             }
             begin_out = std::next(next);
             return not_tested | (backward?qqbar_exb:qqbar_exf);
           }
         }
       }
       return non_resummable;
     }
 
     //! Returns all possible classifications from central emissions
     // the beginning points are changed s.t. after the the classification they
     // point to the end of the emission chain
     // sets W_change: + if Wp change
     //               0 if no change
     //               - if Wm change
     template<class OutIterator>
     size_t possible_central(
       OutIterator & begin_out, OutIterator const & end_out,
       int & W_change, std::vector<Particle> const & boson
     ){
       using namespace event_type;
       // if we already passed the central chain,
       // then it is not a valid all-order state
       if(std::distance(begin_out, end_out) < 0) return non_resummable;
       // keep track of all states that we don't test
       size_t possible = UNO | EXTREMAL_QQBAR;
 
       // Find the first quark or antiquark emission
       begin_out = std::find_if(
         begin_out, end_out,
         [](Particle const & p) { return is_anyquark(p); }
       );
       // end of chain -> FKL
       if( begin_out==end_out ){
         return possible | FKL;
       }
 
       // is this a qqbar-pair?
       // needs two partons in two separate jets
       auto next = std::next(begin_out);
       if(
         next != end_out
         && is_valid_impact_factor(begin_out->type, next->type, true, W_change)
       ){
         // veto Higgs inside qqbar
         if( !boson.empty() && boson.front().type == ParticleID::h
             && boson.front().rapidity() > begin_out->rapidity()
             && boson.front().rapidity() < next->rapidity()
         ){
           return non_resummable;
         }
         begin_out = std::next(next);
         // remaining chain should be pure FKL (gluon or higgs)
         if(std::any_of(
              begin_out, end_out,
              [](Particle const & p) { return is_anyquark(p); }
            )) {
           return non_resummable;
         }
         return possible | qqbar_mid;
       }
       return non_resummable;
     }
 
     namespace {
       bool is_parton_or_higgs(Particle const & p) {
         return is_parton(p) || p.type == pid::higgs;
       }
+
+      bool decay_conserves_charge(
+        Particle const &parent,
+        std::vector<Particle> const &products
+      ) {
+        auto charge_diff = charge(parent);
+        for (auto const &p : products) {
+          charge_diff -= charge(p);
+        }
+        return charge_diff == 0;
+      }
+
+      bool charge_conserved(Event const &ev) {
+        boost::rational<int> charge_diff{0};
+        for (auto const &in : ev.incoming()) {
+          charge_diff += charge(in);
+        }
+        for (auto const &out : ev.outgoing()) {
+          charge_diff -= charge(out);
+        }
+        if (charge_diff != 0) return false;
+
+        return std::all_of(
+          ev.decays().begin(), ev.decays().end(),
+          [&ev](auto const &decay) {
+            auto const &[parent, products] = decay;
+            return decay_conserves_charge(ev.outgoing()[parent], products);
+          });
+      }
+
+      bool decay_conserves_momentum(
+        Particle const &parent,
+        std::vector<Particle> const &products,
+        const double tolerance
+      ) {
+        fastjet::PseudoJet total_p;
+        for (auto const &p : products) total_p += p.p;
+        return nearby_ep(parent.p, total_p, tolerance);
+      }
+
+      bool event_momentum_conserved(Event const &ev, const double tolerance) {
+        return momentum_conserved(ev, tolerance)
+          && std::all_of(
+            ev.decays().begin(), ev.decays().end(),
+            [&ev, tolerance](auto const &decay) {
+              auto const &[parent, products] = decay;
+              return decay_conserves_momentum(
+                ev.outgoing()[parent], products, tolerance);
+            });
+      }
+
+      template <class Container>
+      bool massless_particles_onshell(
+        Container const &c,
+        const double tolerance
+      ) {
+        return std::all_of(
+          c.begin(), c.end(),
+          [tolerance](Particle const & p) {
+            return is_massive(p) || p.m() < tolerance * std::max(p.E(), 1.0);
+          }
+        );
+      }
+
+      bool all_massless_particles_onshell(
+        Event const &ev,
+        const double tolerance
+      ) {
+        return massless_particles_onshell(ev.incoming(), tolerance)
+          && massless_particles_onshell(ev.outgoing(), tolerance)
+          && std::all_of(
+            ev.decays().begin(), ev.decays().end(),
+            [tolerance](auto const &decay) {
+              return massless_particles_onshell(decay.second, tolerance);
+            });
+      }
+
+      bool no_incoming_pt(Event const &ev, const double tolerance) {
+        return std::all_of(
+          ev.incoming().cbegin(), ev.incoming().end(),
+          [tolerance](Particle const &p) {
+            return std::abs(p.px()) < tolerance && std::abs(p.py()) < tolerance;
+          });
+      }
+
+      bool is_invalid(Event const &ev, const double tolerance) {
+        return !(
+          charge_conserved(ev)
+          && event_momentum_conserved(ev, tolerance)
+          && no_incoming_pt(ev, tolerance)
+          && all_massless_particles_onshell(ev, tolerance)
+        );
+      }
+
+      // TODO: choose reasonable value or make configurable
+      constexpr double TOLERANCE = 1e-3;
+
+      bool incoming_are_partons(Event const &ev) {
+        return std::all_of(
+          ev.incoming().begin(), ev.incoming().end(),
+          [](Particle const &p) { return is_parton(p); }
+        );
+      }
+
+      bool known_outgoing(Event const &ev) {
+        return std::all_of(
+          ev.outgoing().begin(), ev.outgoing().end(),
+          [](Particle const &p) {
+            return is_parton(p)
+              || p.type == pid::Higgs
+              || std::abs(p.type) == pid::Wp
+              || p.type == pid::Z_photon_mix;
+          });
+      }
+
+      bool is_same_sign_WW(std::vector<Particle> const &particles) {
+        return particles.size() == 2
+          && std::abs(particles.front().type) == pid::Wp
+          && particles.front().type == particles.back().type;
+      }
+
+      bool all_W_Zphoton_decay(Event const &ev) {
+        auto const &out = ev.outgoing();
+        for (std::size_t i = 0; i < out.size(); ++i) {
+          if (
+            (std::abs(out[i].type) == pid::Wp || out[i].type == pid::Z_photon_mix)
+            && ev.decays().count(i) == 0
+          ) {
+            return false;
+          }
+        }
+        return true;
+      }
+
+      bool decay_known(
+        Particle const &parent,
+        std::vector<Particle> const &products
+      ) {
+        if (parent.type == pid::Higgs) return true;
+        if (parent.type == pid::Z_photon_mix) return valid_Z_decay(products);
+        if (std::abs(parent.type) == pid::Wp) {
+          assert(charge(parent).denominator() == 1);
+          return valid_W_decay(charge(parent).numerator(), products);
+        }
+        return false;
+      }
+
+      bool all_decays_known(Event const &ev) {
+        return std::all_of(
+          ev.decays().begin(), ev.decays().end(),
+          [&ev](auto const &decay) {
+            auto const &[parent, products] = decay;
+            return decay_known(ev.outgoing()[parent], products);
+          });
+      }
+
+      bool is_known_process_type(Event const &ev) {
+        if (!incoming_are_partons(ev)) return false;
+        if (!known_outgoing(ev)) return false;
+        if (!all_W_Zphoton_decay(ev)) return false;
+        if (!all_decays_known(ev)) return false;
+        auto const bosons = filter_AWZH_bosons(ev.outgoing());
+        if (bosons.size() > 2) return false;
+        if (bosons.size() == 2 && !is_same_sign_WW(bosons)) {
+          return false;
+        }
+        if (bosons.size() == 1 && bosons.front().type == pid::Higgs) {
+          return !ev.jets().empty();
+        }
+        return ev.jets().size() >= 2;
+      }
     }
 
     /**
      * \brief Checks for all event types
      * @param ev          Event
      * @returns           Event Type
      *
      */
     event_type::EventType classify(Event const & ev){
       using namespace event_type;
-      if(! has_enough_jets(ev))
-        return not_enough_jets;
-      // currently we can't handle multiple boson states in the ME. So they are
-      // considered "bad_final_state" even though the "classify" could work with
-      // them.
-      if(! final_state_ok(ev))
-        return bad_final_state;
+      if(is_invalid(ev, TOLERANCE)) return invalid;
+      if(! is_known_process_type(ev)) return unknown;
 
       // initialise variables
       auto const & in = ev.incoming();
 
       // range for current checks
       auto begin_out = boost::make_filter_iterator(
         is_parton_or_higgs, cbegin(ev.outgoing()), cend(ev.outgoing())
       );
       auto rbegin_out = std::make_reverse_iterator(
         boost::make_filter_iterator(
           is_parton_or_higgs, cend(ev.outgoing()), cend(ev.outgoing())
         )
       );
 
       assert(std::distance(begin(in), end(in)) == 2);
       assert(std::distance(begin_out, rbegin_out.base()) >= 2);
       assert(std::is_sorted(begin_out, rbegin_out.base(), rapidity_less{}));
 
       auto const bosons{ filter_AWZH_bosons(ev.outgoing()) };
 
       // keep track of potential W couplings, at the end the sum should be 0
       int remaining_Wp = 0;
       int remaining_Wm = 0;
       for(auto const & boson : bosons){
           if(boson.type == ParticleID::Wp) ++remaining_Wp;
           else if(boson.type == ParticleID::Wm) ++remaining_Wm;
       }
 
       size_t final_type = VALID;
 
       // check forward impact factor
       int W_change = 0;
       final_type &= possible_impact_factors(
         in.front().type,
         begin_out, rbegin_out.base(),
         W_change, bosons, true );
       if( final_type == non_resummable )
         return non_resummable;
       if(W_change>0) remaining_Wp-=W_change;
       else if(W_change<0) remaining_Wm+=W_change;
 
       // check backward impact factor
       W_change = 0;
       final_type &= possible_impact_factors(
         in.back().type,
         rbegin_out, std::make_reverse_iterator(begin_out),
         W_change, bosons, false );
       if( final_type == non_resummable )
         return non_resummable;
       if(W_change>0) remaining_Wp-=W_change;
       else if(W_change<0) remaining_Wm+=W_change;
 
       // check central emissions
       W_change = 0;
       final_type &= possible_central(
         begin_out, rbegin_out.base(), W_change, bosons );
       if( final_type == non_resummable )
         return non_resummable;
       if(W_change>0) remaining_Wp-=W_change;
       else if(W_change<0) remaining_Wm+=W_change;
 
       // Check whether the right number of Ws are present
       if( remaining_Wp != 0 || remaining_Wm != 0 ) return non_resummable;
 
       // result has to be unique
       if( (final_type & (final_type-1)) != 0) return non_resummable;
 
       // check that each sub processes is implemented
       // (has to be done at the end)
       if( (final_type & ~implemented_types(bosons)) != 0 )
         return non_resummable;
 
       return static_cast<EventType>(final_type);
     }
     //@}
 
     Particle extract_particle(LHEF::HEPEUP const & hepeup, size_t i){
       auto id = static_cast<ParticleID>(hepeup.IDUP[i]);
       auto colour = is_parton(id)?hepeup.ICOLUP[i]:std::optional<Colour>();
       return { id,
                { hepeup.PUP[i][0], hepeup.PUP[i][1],
                  hepeup.PUP[i][2], hepeup.PUP[i][3] },
                colour
              };
     }
 
     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
 
   Event::EventData::EventData(LHEF::HEPEUP const & hepeup){
     parameters.central = EventParameters{
       hepeup.scales.mur, hepeup.scales.muf, hepeup.XWGTUP
     };
     size_t in_idx = 0;
     for (int i = 0; i < hepeup.NUP; ++i) {
       // skip decay products
       // we will add them later on, but we have to ensure that
       // the decayed particle is added before
       if(is_decay_product(hepeup.MOTHUP[i])) continue;
 
       auto particle = extract_particle(hepeup, i);
       // needed to identify mother particles for decay products
       particle.p.set_user_index(i+1);
 
       if(hepeup.ISTUP[i] == LHE_Status::in){
         if(in_idx > incoming.size()) {
           throw std::invalid_argument{
             "Event has too many incoming particles"
           };
         }
         incoming[in_idx++] = std::move(particle);
       }
       else outgoing.emplace_back(std::move(particle));
     }
 
     // add decay products
     for (int i = 0; i < hepeup.NUP; ++i) {
       if(!is_decay_product(hepeup.MOTHUP[i])) continue;
       const int mother_id = hepeup.MOTHUP[i].first;
       const auto mother = std::find_if(
           begin(outgoing), end(outgoing),
           [mother_id](Particle const & particle){
             return particle.p.user_index() == mother_id;
           }
       );
       if(mother == end(outgoing)){
         throw std::invalid_argument{"invalid decay product parent"};
       }
       const int mother_idx = std::distance(begin(outgoing), mother);
       assert(mother_idx >= 0);
       decays[mother_idx].emplace_back(extract_particle(hepeup, i));
     }
   }
 
   Event::Event(
     UnclusteredEvent const & ev,
     fastjet::JetDefinition const & jet_def, double const min_jet_pt
   ):
     Event( Event::EventData{
       ev.incoming, ev.outgoing, ev.decays,
       Parameters<EventParameters>{ev.central, ev.variations}
     }.cluster(jet_def, min_jet_pt) )
   {}
 
   //! @TODO remove in HEJ 2.2.0
   UnclusteredEvent::UnclusteredEvent(LHEF::HEPEUP const & hepeup){
       Event::EventData const evData{hepeup};
       incoming = evData.incoming;
       outgoing = evData.outgoing;
       decays = evData.decays;
       central = evData.parameters.central;
       variations = evData.parameters.variations;
   }
 
   void Event::EventData::sort(){
     // sort particles
     std::sort(
         begin(incoming), end(incoming),
         [](Particle const & o1, Particle const & o2){return o1.p.pz()<o2.p.pz();}
     );
 
     auto old_outgoing = std::move(outgoing);
     std::vector<size_t> idx(old_outgoing.size());
     std::iota(idx.begin(), idx.end(), 0);
     std::sort(idx.begin(), idx.end(), [&old_outgoing](size_t i, size_t j){
       return old_outgoing[i].rapidity() < old_outgoing[j].rapidity();
     });
     outgoing.clear();
     outgoing.reserve(old_outgoing.size());
     for(size_t i: idx) {
       outgoing.emplace_back(std::move(old_outgoing[i]));
     }
 
     // find decays again
     if(!decays.empty()){
       auto old_decays = std::move(decays);
       decays.clear();
       for(size_t i=0; i<idx.size(); ++i) {
         auto decay = old_decays.find(idx[i]);
         if(decay != old_decays.end())
           decays.emplace(i, std::move(decay->second));
       }
       assert(old_decays.size() == decays.size());
     }
   }
 
   namespace {
     // use valid_X_decay to determine boson type
     ParticleID reconstruct_type(std::vector<Particle> const & progeny) {
       if(valid_W_decay(+1, progeny)) { return ParticleID::Wp; }
       if(valid_W_decay(-1, progeny)) { return ParticleID::Wm; }
       if(valid_Z_decay(progeny)) { return ParticleID::Z_photon_mix; }
 
       throw not_implemented{
         "final state with decay X -> "
           + name(progeny[0].type)
           + " + "
           + name(progeny[1].type)
       };
     }
 
     // reconstruct particle with explicit ParticleID
     Particle reconstruct_boson(
       std::vector<Particle> const & progeny,
       ParticleID const & type
     ) {
       Particle progenitor;
       progenitor.p = progeny[0].p + progeny[1].p;
       progenitor.type = type;
       return progenitor;
     }
 
     // reconstruct via call to reconstruct_type
     Particle reconstruct_boson(std::vector<Particle> const & progeny) {
       Particle progenitor {reconstruct_boson(progeny, reconstruct_type(progeny))};
       assert(is_AWZH_boson(progenitor));
       return progenitor;
     }
 
     using GroupedParticles = std::vector<std::vector<Particle> >;
     using Decay = std::pair<Particle, std::vector<Particle> >;
     using Decays = std::vector<Decay>;
 
     // return groups of reconstructable progeny
     std::vector<GroupedParticles> group_progeny(std::vector<Particle> & leptons) {
       /**
       Warning: The partition in to charged/neutral leptons is valid ONLY for WW.
       **/
       assert(leptons.size() == 4);
       auto const begin_neutrino = std::partition(
           begin(leptons), end(leptons),
           [](Particle const & p) {return !is_anyneutrino(p);}
       );
 
       std::vector<Particle> neutrinos (begin_neutrino, end(leptons));
       leptons.erase(begin_neutrino, end(leptons));
 
       if(leptons.size() != 2 || neutrinos.size() != 2) { return {}; }
       assert(leptons.size() == 2 && neutrinos.size() == 2);
 
       std::vector<GroupedParticles> valid_groupings;
 
       GroupedParticles candidate_grouping{{leptons[0], neutrinos[0]}, {leptons[1], neutrinos[1]}};
       if(valid_decay(candidate_grouping.front()) && valid_decay(candidate_grouping.back())) {
         valid_groupings.emplace_back(std::move(candidate_grouping));
       }
 
       candidate_grouping = {{leptons[1], neutrinos[0]}, {leptons[0], neutrinos[1]}};
       if(valid_decay(candidate_grouping.front()) && valid_decay(candidate_grouping.back())) {
         valid_groupings.emplace_back(std::move(candidate_grouping));
       }
 
       return valid_groupings;
     }
 
     // 'best' decay ordering measure
     double decay_measure(const Particle& reconstructed, EWConstants const & params) {
         ParticleProperties ref = params.prop(reconstructed.type);
         return std::abs(reconstructed.p.m() - ref.mass);
     }
 
     // decay_measure accumulated over decays
     double decay_measure(Decays const & decays, EWConstants const & params) {
       return
       std::accumulate(
         cbegin(decays), cend(decays), 0.,
         [&params] (double dm, Decay const & decay) -> double {
           return dm + decay_measure(decay.first, params);
         }
       );
     }
 
     // select best combination of decays for the event
     Decays select_decays
     (
       std::vector<Particle> & leptons,
       EWConstants const & ew_parameters
     ) {
       std::vector<GroupedParticles> groupings = group_progeny(leptons);
 
       std::vector<Decays> valid_decays;
       valid_decays.reserve(groupings.size());
 
       // Reconstruct all groupings
       for(GroupedParticles const & group : groupings) {
         Decays decays;
         for(auto const & progeny : group) {
           decays.emplace_back(make_pair(reconstruct_boson(progeny), progeny));
         }
         valid_decays.emplace_back(decays);
       }
       if (valid_decays.empty()) {
         throw not_implemented{"No supported intermediate reconstruction available"};
       }
       if (valid_decays.size() == 1) {
         return valid_decays[0];
       }
       // select decay with smallest decay_measure
       auto selected = std::min_element(cbegin(valid_decays), cend(valid_decays),
         [&ew_parameters] (auto const & d1, auto const & d2) -> bool {
           return decay_measure(d1, ew_parameters) < decay_measure(d2, ew_parameters);
         }
       );
       return *selected;
     }
   } // namespace
 
   void Event::EventData::reconstruct_intermediate(EWConstants const & ew_parameters) {
     auto const begin_leptons = std::partition(
         begin(outgoing), end(outgoing),
         [](Particle const & p) {return !is_anylepton(p);}
     );
     std::vector<Particle> leptons(begin_leptons, end(outgoing));
     outgoing.erase(begin_leptons, end(outgoing));
     if(leptons.empty()) { return; }  // nothing to do
     if(leptons.size() == 2) {
       outgoing.emplace_back(reconstruct_boson(leptons));
       std::sort(begin(leptons), end(leptons), type_less{});
       decays.emplace(outgoing.size()-1, std::move(leptons));
     }
     else if(leptons.size() == 4) {
       Decays valid_decays = select_decays(leptons, ew_parameters);
       for(auto &decay : valid_decays) {
         outgoing.emplace_back(decay.first);
         std::sort(begin(decay.second), end(decay.second), type_less{});
         decays.emplace(outgoing.size()-1, std::move(decay.second));
       }
     }
     else {
       throw not_implemented {
         std::to_string(leptons.size())
         + " leptons in the final state"
       };
     }
   }
 
   namespace {
     void repair_momentum(fastjet::PseudoJet & p, const double tolerance) {
       if(p.e() > 0. && p.m2() != 0. && (p.m2() < tolerance * tolerance)) {
         const double rescale = std::sqrt(p.modp() / p.e());
         const double e = p.e() * rescale;
         const double px = p.px() / rescale;
         const double py = p.py() / rescale;
         const double pz = p.pz() / rescale;
         p.reset(px, py, pz, e);
       }
     }
   }
 
   void Event::EventData::repair_momenta(const double tolerance) {
     for(auto & in: incoming) {
       if(is_massless(in)) {
         const double px = (std::abs(in.px()) < tolerance)?0.:in.px();
         const double py = (std::abs(in.py()) < tolerance)?0.:in.py();
         in.p.reset(px, py, in.p.pz(), in.p.e());
         repair_momentum(in.p, tolerance);
       }
     }
     for(auto & out: outgoing) {
       if(is_massless(out)) repair_momentum(out.p, tolerance);
     }
     for(auto & decay: decays) {
       for(auto & out: decay.second) {
         if(is_massless(out)) repair_momentum(out.p, tolerance);
       }
     }
   }
 
   Event Event::EventData::cluster(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt
   ){
     sort();
     return Event{ std::move(incoming), std::move(outgoing), std::move(decays),
       std::move(parameters),
       jet_def, min_jet_pt
     };
   }
 
   Event::Event(
       std::array<Particle, 2> && incoming,
       std::vector<Particle> && outgoing,
       std::unordered_map<size_t, std::vector<Particle>> && decays,
       Parameters<EventParameters> && parameters,
       fastjet::JetDefinition const & jet_def,
       double const min_jet_pt
     ): incoming_{std::move(incoming)},
        outgoing_{std::move(outgoing)},
        decays_{std::move(decays)},
        parameters_{std::move(parameters)},
        cs_{ to_PseudoJet( filter_partons(outgoing_) ), jet_def },
        min_jet_pt_{min_jet_pt}
     {
       jets_ = sorted_by_rapidity(cs_.inclusive_jets(min_jet_pt_));
       assert(std::is_sorted(begin(outgoing_), end(outgoing_),
         rapidity_less{}));
       type_ = classify(*this);
     }
 
   namespace {
     //! check that Particles have a reasonable colour
     bool correct_colour(Particle const & part){
       ParticleID id{ part.type };
       if(!is_parton(id))
         return !part.colour;
 
       if(!part.colour)
         return false;
 
       Colour const & col{ *part.colour };
       if(is_quark(id))
         return col.first != 0 && col.second == 0;
       if(is_antiquark(id))
         return col.first == 0 && col.second != 0;
       assert(id==ParticleID::gluon);
       return col.first != 0 && col.second != 0 && col.first != col.second;
     }
 
     //! Connect parton to t-channel colour line & update the line
     //! returns false if connection not possible
     template<class OutIterator>
     bool try_connect_t(OutIterator const & it_part, Colour & line_colour){
       if( line_colour.first == it_part->colour->second ){
         line_colour.first = it_part->colour->first;
         return true;
       }
       if( line_colour.second == it_part->colour->first ){
         line_colour.second = it_part->colour->second;
         return true;
       }
       return false;
     }
 
     //! Connect parton to u-channel colour line & update the line
     //! returns false if connection not possible
     template<class OutIterator>
     bool try_connect_u(OutIterator & it_part, Colour & line_colour){
       auto it_next = std::next(it_part);
       if( try_connect_t(it_next, line_colour)
           && try_connect_t(it_part, line_colour)
       ){
         it_part=it_next;
         return true;
       }
       return false;
     }
   } // namespace
 
   bool Event::is_leading_colour() const {
     if( !correct_colour(incoming()[0]) || !correct_colour(incoming()[1]) )
       return false;
 
     Colour line_colour = *incoming()[0].colour;
     std::swap(line_colour.first, line_colour.second);
 
     // reasonable colour
     if(!std::all_of(outgoing().cbegin(), outgoing().cend(), correct_colour))
       return false;
 
     for(auto it_part = cbegin_partons(); it_part!=cend_partons(); ++it_part){
 
       switch (type()) {
       case event_type::FKL:
         if( !try_connect_t(it_part, line_colour) )
           return false;
         break;
       case event_type::unob:
       case event_type::qqbar_exb: {
         if( !try_connect_t(it_part, line_colour)
             // u-channel only allowed at impact factor
             && (std::distance(cbegin_partons(), it_part)!=0
                 || !try_connect_u(it_part, line_colour)))
           return false;
         break;
       }
       case event_type::unof:
       case event_type::qqbar_exf: {
         if( !try_connect_t(it_part, line_colour)
             // u-channel only allowed at impact factor
             && (std::distance(it_part, cend_partons())!=2
                 || !try_connect_u(it_part, line_colour)))
           return false;
         break;
       }
       case event_type::qqbar_mid:{
         auto it_next = std::next(it_part);
         if( !try_connect_t(it_part, line_colour)
             // u-channel only allowed at q-qbar/qbar-q pair
             && ( (   !(is_quark(*it_part) && is_antiquark(*it_next))
                   && !(is_antiquark(*it_part) && is_quark(*it_next)))
                 || !try_connect_u(it_part, line_colour))
           )
           return false;
         break;
       }
       default:
         throw std::logic_error{"unreachable"};
       }
 
       // no colour singlet exchange/disconnected diagram
       if(line_colour.first == line_colour.second)
         return false;
     }
 
     return (incoming()[1].colour->first == line_colour.first)
         && (incoming()[1].colour->second == line_colour.second);
   }
 
   namespace {
     //! connect incoming Particle to colour flow
     void connect_incoming(Particle & in, int & colour, int & anti_colour){
       in.colour = std::make_pair(anti_colour, colour);
       // gluon
       if(in.type == pid::gluon)
         return;
       if(in.type > 0){
         // quark
         assert(is_quark(in));
         in.colour->second = 0;
         colour*=-1;
         return;
       }
       // anti-quark
       assert(is_antiquark(in));
       in.colour->first = 0;
       anti_colour*=-1;
    }
 
     //! connect outgoing Particle to t-channel colour flow
     template<class OutIterator>
     void connect_tchannel(
         OutIterator & it_part, int & colour, int & anti_colour, RNG & ran
     ){
       assert(colour>0 || anti_colour>0);
       if(it_part->type == ParticleID::gluon){
         // gluon
         if(colour>0 && anti_colour>0){
           // on g line => connect to colour OR anti-colour (random)
           if(ran.flat() < 0.5){
             it_part->colour = std::make_pair(colour+2,colour);
             colour+=2;
           } else {
             it_part->colour = std::make_pair(anti_colour, anti_colour+2);
             anti_colour+=2;
           }
         } else if(colour > 0){
           // on q line => connect to available colour
             it_part->colour = std::make_pair(colour+2, colour);
             colour+=2;
         } else {
           assert(colour<0 && anti_colour>0);
           // on qbar line => connect to available anti-colour
           it_part->colour = std::make_pair(anti_colour, anti_colour+2);
           anti_colour+=2;
         }
       } else if(is_quark(*it_part)) {
         // quark
         assert(anti_colour>0);
         if(colour>0){
           // on g line => connect and remove anti-colour
           it_part->colour = std::make_pair(anti_colour, 0);
           anti_colour+=2;
           anti_colour*=-1;
         } else {
           // on qbar line => new colour
           colour*=-1;
           it_part->colour = std::make_pair(colour, 0);
         }
       } else if(is_antiquark(*it_part)) {
         // anti-quark
         assert(colour>0);
         if(anti_colour>0){
           // on g line => connect and remove colour
           it_part->colour = std::make_pair(0, colour);
           colour+=2;
           colour*=-1;
         } else {
           // on q line => new anti-colour
           anti_colour*=-1;
           it_part->colour = std::make_pair(0, anti_colour);
         }
       } else { // not a parton
         assert(!is_parton(*it_part));
         it_part->colour = {};
       }
     }
 
     //! connect to t- or u-channel colour flow
     template<class OutIterator>
     void connect_utchannel(
         OutIterator & it_part, int & colour, int & anti_colour, RNG & ran
     ){
       OutIterator it_first = it_part++;
       if(ran.flat()<.5) {// t-channel
         connect_tchannel(it_first, colour, anti_colour, ran);
         connect_tchannel(it_part, colour, anti_colour, ran);
       }
       else { // u-channel
         connect_tchannel(it_part, colour, anti_colour, ran);
         connect_tchannel(it_first, colour, anti_colour, ran);
       }
     }
   } // namespace
 
   bool Event::generate_colours(RNG & ran){
     // generate only for HEJ events
     if(!event_type::is_resummable(type()))
       return false;
     assert(std::is_sorted(
       begin(outgoing()), end(outgoing()), rapidity_less{}));
     assert(incoming()[0].pz() < incoming()[1].pz());
 
     // positive (anti-)colour -> can connect
     // negative (anti-)colour -> not available/used up by (anti-)quark
     int colour = COLOUR_OFFSET;
     int anti_colour = colour+1;
     // initialise first
     connect_incoming(incoming_[0], colour, anti_colour);
 
     // reset outgoing colours
     std::for_each(outgoing_.begin(), outgoing_.end(),
       [](Particle & part){ part.colour = {};});
 
     for(auto it_part = begin_partons(); it_part!=end_partons(); ++it_part){
         switch (type()) {
         // subleading can connect to t- or u-channel
         case event_type::unob:
         case event_type::qqbar_exb: {
           if( std::distance(begin_partons(), it_part)==0)
             connect_utchannel(it_part, colour, anti_colour, ran);
           else
             connect_tchannel(it_part, colour, anti_colour, ran);
           break;
         }
         case event_type::unof:
         case event_type::qqbar_exf: {
           if( std::distance(it_part, end_partons())==2)
             connect_utchannel(it_part, colour, anti_colour, ran);
           else
             connect_tchannel(it_part, colour, anti_colour, ran);
           break;
         }
         case event_type::qqbar_mid:{
           auto it_next = std::next(it_part);
           if( std::distance(begin_partons(), it_part)>0
               && std::distance(it_part, end_partons())>2
               && ( (is_quark(*it_part) && is_antiquark(*it_next))
                 || (is_antiquark(*it_part) && is_quark(*it_next)) )
           )
             connect_utchannel(it_part, colour, anti_colour, ran);
           else
             connect_tchannel(it_part, colour, anti_colour, ran);
           break;
         }
         default: // rest has to be t-channel
           connect_tchannel(it_part, colour, anti_colour, ran);
         }
     }
     // Connect last
     connect_incoming(incoming_[1], anti_colour, colour);
     assert(is_leading_colour());
     return true;
   } // generate_colours
 
   namespace {
     bool valid_parton(
       std::vector<fastjet::PseudoJet> const & jets,
       Particle const & parton, int const idx,
       double const soft_pt_regulator, double const min_extparton_pt
     ){
       // TODO code overlap with PhaseSpacePoint::pass_extremal_cuts
       if(min_extparton_pt > parton.pt()) return false;
       if(idx<0) return false;
       assert(static_cast<int>(jets.size())>=idx);
       auto const & jet{ jets[idx] };
       return (parton.p - jet).pt()/jet.pt() <= soft_pt_regulator;
     }
 
   } // namespace
 
   bool Event::valid_hej_state(double const soft_pt_regulator,
                               double const min_pt
   ) const {
     using namespace event_type;
     const auto is_valid_parton = [&](Particle const & parton, int const jet_idx) {
       return valid_parton(jets(), parton, jet_idx, soft_pt_regulator, min_pt);
     };
     if(!is_resummable(type()))
       return false;
 
     auto const & jet_indices{ particle_jet_indices() };
     auto jet_idx_begin{ jet_indices.cbegin() };
     auto jet_idx_end{  jet_indices.crbegin() };
 
     auto part_begin{ cbegin_partons() };
     auto part_end{  crbegin_partons() };
 
     if(!is_backward_g_to_h(*this)) {
       const int first_jet_idx = *jet_idx_begin;
       if(! is_valid_parton(*part_begin, first_jet_idx)) {
         return false;
       }
       ++part_begin;
       ++jet_idx_begin;
       // unob -> second parton in own jet
       if( type() & (unob | qqbar_exb) ){
         if(
           (*jet_idx_begin == first_jet_idx)
           || !is_valid_parton(*part_begin, *jet_idx_begin)
         ) {
           return false;
         }
         ++part_begin;
         ++jet_idx_begin;
       }
     }
     if(!is_forward_g_to_h(*this)) {
       const int last_jet_idx = *jet_idx_end;
       if(!is_valid_parton(*part_end, last_jet_idx)) {
         return false;
       }
       ++part_end;
       ++jet_idx_end;
       if( type() & (unof | qqbar_exf) ){
         if(
           (*jet_idx_end == last_jet_idx)
           || !is_valid_parton(*part_end, *jet_idx_end)
         ) {
           return false;
         }
         ++part_end;
         // ++jet_idx_end; // last check, we don't need idx_end afterwards
       }
     }
 
     if( type() & qqbar_mid ){
       // find qqbar pair
       auto begin_qqbar{ std::find_if( part_begin, part_end.base(),
         [](Particle const & part) -> bool {
           return part.type != ParticleID::gluon;
         }
       )};
       assert(begin_qqbar != part_end.base());
       long int qqbar_pos{ std::distance(part_begin, begin_qqbar) };
       assert(qqbar_pos >= 0);
       jet_idx_begin += qqbar_pos;
       const int next_jet_idx = *std::next(jet_idx_begin);
       if(
          (*jet_idx_begin == next_jet_idx)
         || ! is_valid_parton(*begin_qqbar, *jet_idx_begin)
         || ! is_valid_parton(*std::next(begin_qqbar), next_jet_idx)
       ) {
         return false;
       }
     }
     return true;
   }
 
   bool Event::valid_incoming() const{
     for(std::size_t i=0; i < incoming_.size(); ++i){
       if(!(HEJ::nearby_ep(std::abs(incoming_[i].pz()), incoming_[i].E(), TOL*incoming_[i].E())
            && (incoming_[i].pt()==0.)))
         return false;
     }
     return true;
   }
 
   Event::ConstPartonIterator Event::begin_partons() const {
     return cbegin_partons();
   }
   Event::ConstPartonIterator Event::cbegin_partons() const {
     return {HEJ::is_parton, cbegin(outgoing()), cend(outgoing())};
   }
 
   Event::ConstPartonIterator Event::end_partons() const {
     return cend_partons();
   }
   Event::ConstPartonIterator Event::cend_partons() const {
     return {HEJ::is_parton, cend(outgoing()), cend(outgoing())};
   }
 
   Event::ConstReversePartonIterator Event::rbegin_partons() const {
     return crbegin_partons();
   }
   Event::ConstReversePartonIterator Event::crbegin_partons() const {
     return std::reverse_iterator<ConstPartonIterator>( cend_partons() );
   }
 
   Event::ConstReversePartonIterator Event::rend_partons() const {
     return crend_partons();
   }
   Event::ConstReversePartonIterator Event::crend_partons() const {
     return std::reverse_iterator<ConstPartonIterator>( cbegin_partons() );
   }
 
   Event::PartonIterator Event::begin_partons() {
     return {HEJ::is_parton, begin(outgoing_), end(outgoing_)};
   }
 
   Event::PartonIterator Event::end_partons() {
     return {HEJ::is_parton, end(outgoing_), end(outgoing_)};
   }
 
   Event::ReversePartonIterator Event::rbegin_partons() {
     return std::reverse_iterator<PartonIterator>( end_partons() );
   }
 
   Event::ReversePartonIterator Event::rend_partons() {
     return std::reverse_iterator<PartonIterator>( begin_partons() );
   }
 
   namespace {
     void print_momentum(std::ostream & os, fastjet::PseudoJet const & part){
       constexpr int prec = 6;
       const std::streamsize orig_prec = os.precision();
       os <<std::scientific<<std::setprecision(prec) << "["
         <<std::setw(2*prec+1)<<std::right<< part.px() << ", "
         <<std::setw(2*prec+1)<<std::right<< part.py() << ", "
         <<std::setw(2*prec+1)<<std::right<< part.pz() << ", "
         <<std::setw(2*prec+1)<<std::right<< part.E() << "]"<< std::fixed;
       os.precision(orig_prec);
     }
 
     void print_colour(std::ostream & os, std::optional<Colour> const & col){
       constexpr int width = 3;
       if(!col)
         os << "(no color)"; // American spelling for better alignment
       else
         os << "("  <<std::setw(width)<<std::right<< col->first
            << ", " <<std::setw(width)<<std::right<< col->second << ")";
     }
   } // namespace
 
   std::ostream& operator<<(std::ostream & os, Event const & ev){
     constexpr int prec = 4;
     constexpr int wtype = 3; // width for types
     const std::streamsize orig_prec = os.precision();
     os <<std::setprecision(prec)<<std::fixed;
     os << "########## " << name(ev.type()) << " ##########" << std::endl;
     os << "Incoming particles:\n";
     for(auto const & in: ev.incoming()){
       os <<std::setw(wtype)<< in.type << ": ";
       print_colour(os, in.colour);
       os << " ";
       print_momentum(os, in.p);
       os << std::endl;
     }
     os << "\nOutgoing particles: " << ev.outgoing().size() << "\n";
     for(auto const & out: ev.outgoing()){
       os <<std::setw(wtype)<< out.type << ": ";
       print_colour(os, out.colour);
       os << " ";
       print_momentum(os, out.p);
       os << " => rapidity="
         <<std::setw(2*prec-1)<<std::right<< out.rapidity() << std::endl;
     }
     os << "\nForming Jets: " << ev.jets().size() << "\n";
     for(auto const & jet: ev.jets()){
       print_momentum(os, jet);
       os << " => rapidity="
         <<std::setw(2*prec-1)<<std::right<< jet.rapidity() << std::endl;
     }
     if(!ev.decays().empty() ){
       os << "\nDecays: " << ev.decays().size() << "\n";
       for(auto const & decay: ev.decays()){
         os <<std::setw(wtype)<< ev.outgoing()[decay.first].type
           << " (" << decay.first << ") to:\n";
         for(auto const & out: decay.second){
           os <<"  "<<std::setw(wtype)<< out.type << ": ";
           print_momentum(os, out.p);
           os << " => rapidity="
             <<std::setw(2*prec-1)<<std::right<< out.rapidity() << std::endl;
         }
       }
 
     }
     os << std::defaultfloat;
     os.precision(orig_prec);
     return os;
   }
 
+  std::string to_string(Event const & ev){
+    std::stringstream ss;
+    ss << ev;
+    return ss.str();
+  }
+
   double shat(Event const & ev){
     return (ev.incoming()[0].p + ev.incoming()[1].p).m2();
   }
 
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP * heprup){
     LHEF::HEPEUP result;
     result.heprup = heprup;
     result.weights = {{event.central().weight, nullptr}};
     for(auto const & var: event.variations()){
       result.weights.emplace_back(var.weight, nullptr);
     }
     size_t num_particles = event.incoming().size() + event.outgoing().size();
     for(auto const & decay: event.decays()) num_particles += decay.second.size();
     result.NUP = num_particles;
     // the following entries are pretty much meaningless
     result.IDPRUP = event.type();  // event type
     result.AQEDUP = 1./128.;  // alpha_EW
     //result.AQCDUP = 0.118 // alpha_QCD
     // end meaningless part
     result.XWGTUP = event.central().weight;
     result.SCALUP = event.central().muf;
     result.scales.muf = event.central().muf;
     result.scales.mur = event.central().mur;
     result.scales.SCALUP = event.central().muf;
     result.pdfinfo.p1 = event.incoming().front().type;
     result.pdfinfo.p2 = event.incoming().back().type;
     result.pdfinfo.scale = event.central().muf;
 
     result.IDUP.reserve(num_particles);   // PID
     result.ISTUP.reserve(num_particles);  // status (in, out, decay)
     result.PUP.reserve(num_particles);    // momentum
     result.MOTHUP.reserve(num_particles); // index mother particle
     result.ICOLUP.reserve(num_particles); // colour
     // incoming
     std::array<Particle, 2> incoming{ event.incoming() };
     // First incoming should be positive pz according to LHE standard
     // (or at least most (everyone?) do it this way, and Pythia assumes it)
     if(incoming[0].pz() < incoming[1].pz())
       std::swap(incoming[0], incoming[1]);
     for(Particle const & in: incoming){
       result.IDUP.emplace_back(in.type);
       result.ISTUP.emplace_back(LHE_Status::in);
       result.PUP.push_back({in.p[0], in.p[1], in.p[2], in.p[3], in.p.m()});
       result.MOTHUP.emplace_back(0, 0);
       assert(in.colour);
       result.ICOLUP.emplace_back(*in.colour);
     }
     // outgoing
     for(size_t i = 0; i < event.outgoing().size(); ++i){
       Particle const & out = event.outgoing()[i];
       result.IDUP.emplace_back(out.type);
       const int status = event.decays().count(i) != 0u
                           ?LHE_Status::decay
                           :LHE_Status::out;
       result.ISTUP.emplace_back(status);
       result.PUP.push_back({out.p[0], out.p[1], out.p[2], out.p[3], out.p.m()});
       result.MOTHUP.emplace_back(1, 2);
       if(out.colour)
         result.ICOLUP.emplace_back(*out.colour);
       else{
         result.ICOLUP.emplace_back(std::make_pair(0,0));
       }
     }
     // decays
     for(auto const & decay: event.decays()){
       for(auto const & out: decay.second){
         result.IDUP.emplace_back(out.type);
         result.ISTUP.emplace_back(LHE_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;
   }
 
 } // namespace HEJ
diff --git a/src/EventReweighter.cc b/src/EventReweighter.cc
index 14ecdb1..7ee97df 100644
--- a/src/EventReweighter.cc
+++ b/src/EventReweighter.cc
@@ -1,293 +1,301 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/EventReweighter.hh"
 
 #include <algorithm>
 #include <cassert>
 #include <cmath>
 #include <cstddef>
 #include <functional>
 #include <string>
 #include <unordered_map>
 #include <utility>
 
 #include "fastjet/ClusterSequence.hh"
 #include "fastjet/PseudoJet.hh"
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Event.hh"
 #include "HEJ/Fraction.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PhaseSpacePoint.hh"
 #include "HEJ/exceptions.hh"
 
 namespace HEJ {
 
   EventReweighter::EventReweighter(
       LHEF::HEPRUP const & heprup,
       ScaleGenerator scale_gen,
       EventReweighterConfig conf,
       std::shared_ptr<RNG> ran
   ):
     EventReweighter{
       Beam{
         heprup.EBMUP.first,
         {{
           static_cast<ParticleID>(heprup.IDBMUP.first),
           static_cast<ParticleID>(heprup.IDBMUP.second)
         }}
       },
       heprup.PDFSUP.first,
       std::move(scale_gen),
       std::move(conf),
       std::move(ran)
     }
   {
     if(heprup.EBMUP.second != E_beam_){
       throw std::invalid_argument(
           "asymmetric beam: " + std::to_string(E_beam_)
           + " ---> <--- " + std::to_string(heprup.EBMUP.second)
       );
     }
     if(heprup.PDFSUP.second != pdf_.id()){
       throw std::invalid_argument(
           "conflicting PDF ids: " + std::to_string(pdf_.id())
           + " vs. " + std::to_string(heprup.PDFSUP.second)
       );
     }
   }
 
   EventReweighter::EventReweighter(
       Beam const & beam,
       int pdf_id,
       ScaleGenerator scale_gen,
       EventReweighterConfig conf,
       std::shared_ptr<RNG> ran
   ):
     param_{std::move(conf)},
     E_beam_{beam.E},
     pdf_{pdf_id, beam.type.front(), beam.type.back()},
     MEt2_{
       [this](double mu){ return pdf_.Halphas(mu); },
       param_.ME_config
     },
     scale_gen_{std::move(scale_gen)},
     ran_{std::move(ran)}
   {
     // legacy code: override new variable with old
     if(param_.psp_config.max_ext_soft_pt_fraction){
       param_.psp_config.soft_pt_regulator = *param_.psp_config.max_ext_soft_pt_fraction;
       param_.psp_config.max_ext_soft_pt_fraction = {};
     }
     assert(ran_);
   }
 
   PDF const & EventReweighter::pdf() const{
     return pdf_;
   }
 
   bool EventReweighter::pass_low_pt(
       HEJ::Event const & input_ev
   ){
       // Keep only events where there is a fixed order event with at least 1
       // jet below the resummation jet pt but all resummation jets are above
       // the resummation jet pt
       if(param_.treat.at(EventType::non_resummable)
          != EventTreatment::discard){
           throw std::logic_error{
               "Non-resummable events should be discarded for lowpt runs"
           };
       }
       return std::any_of(begin(input_ev.jets()),
                       end(input_ev.jets()),
                       [&](fastjet::PseudoJet jet)
                       {return jet.pt() < param_.jet_param().min_pt;});
   }
 
   std::vector<Event> EventReweighter::reweight(
       Event const & input_ev, std::size_t num_events
   ){
       if(param_.lowpt && !EventReweighter::pass_low_pt(input_ev)){
           return {};
       }
       auto res_events{ gen_res_events(input_ev, num_events) };
       if(res_events.empty()) return {};
       for(auto & event: res_events) event = scale_gen_(std::move(event));
       return rescale(input_ev, std::move(res_events));
   }
 
   EventTreatment EventReweighter::treatment(EventType type) const {
     return param_.treat.at(type);
   }
 
   std::vector<Event> EventReweighter::gen_res_events(
       Event const & ev,
       std::size_t phase_space_points
   ){
     assert(ev.variations().empty());
     status_.clear();
 
 
 
     switch(treatment(ev.type())){
     case EventTreatment::discard: {
       status_.emplace_back(StatusCode::discard);
       return {};
     }
     case EventTreatment::keep:
       if(! jets_pass_resummation_cuts(ev)) {
         status_.emplace_back(StatusCode::failed_resummation_cuts);
         return {};
       }
       else {
         status_.emplace_back(StatusCode::good);
         return {ev};
       }
+    case EventTreatment::abort:
+      throw abort_event{ev};
     default:;
     }
     const double Born_shat = shat(ev);
 
     std::vector<Event> resummation_events;
     status_.reserve(phase_space_points);
     for(std::size_t psp_number = 0; psp_number < phase_space_points; ++psp_number){
       PhaseSpacePoint psp{ev, param_.psp_config, *ran_};
       status_.emplace_back(psp.status());
       assert(psp.status() != StatusCode::unspecified);
       if(psp.status() != StatusCode::good) continue;
       assert(psp.weight() != 0.);
       if(psp.incoming()[0].E() > E_beam_ || psp.incoming()[1].E() > E_beam_) {
         status_.back() = StatusCode::too_much_energy;
         continue;
       }
 
       resummation_events.emplace_back(
         to_EventData( std::move(psp) ).cluster(
           param_.jet_param().def, param_.jet_param().min_pt
         )
       );
       auto & new_event = resummation_events.back();
       assert( new_event.valid_hej_state(
         param_.psp_config.soft_pt_regulator,
         param_.psp_config.min_extparton_pt ) );
       if( new_event.type() != ev.type() ) {
         throw std::logic_error{
           "Resummation Event does not match Born event: "
           + name(new_event.type())
           + " != "
           + name(ev.type())
         };
       }
       new_event.generate_colours(*ran_);
       assert(new_event.variations().empty());
       new_event.central().mur = ev.central().mur;
       new_event.central().muf = ev.central().muf;
       const double resum_shat = shat(new_event);
       new_event.central().weight *= ev.central().weight*Born_shat*Born_shat/
         (phase_space_points*resum_shat*resum_shat);
 
 
     }
     return resummation_events;
   }
 
   std::vector<Event> EventReweighter::rescale(
       Event const & Born_ev,
       std::vector<Event> events
   ) const{
     const double Born_pdf = pdf_factors(Born_ev).central;
     const double Born_ME = tree_matrix_element(Born_ev);
 
     for(auto & cur_event: events){
       const auto pdf = pdf_factors(cur_event);
       assert(pdf.variations.size() == cur_event.variations().size());
       const auto ME = matrix_elements(cur_event);
       assert(ME.variations.size() == cur_event.variations().size());
       cur_event.parameters() *= pdf*ME/(Born_pdf*Born_ME);
     }
     return events;
   }
 
   bool EventReweighter::jets_pass_resummation_cuts(
       Event const & ev
   ) const{
     const auto out_as_PseudoJet = to_PseudoJet(filter_partons(ev.outgoing()));
     fastjet::ClusterSequence cs{out_as_PseudoJet, param_.jet_param().def};
     return cs.inclusive_jets(param_.jet_param().min_pt).size() == ev.jets().size();
   }
 
   Weights EventReweighter::pdf_factors(Event const & ev) const{
     auto const & a = ev.incoming().front();
     auto const & b = ev.incoming().back();
     const double xa = a.p.e()/E_beam_;
     const double xb = b.p.e()/E_beam_;
 
     Weights result;
     std::unordered_map<double, double> known_pdf;
     result.central =
       pdf_.pdfpt(0,xa,ev.central().muf,a.type)*
       pdf_.pdfpt(1,xb,ev.central().muf,b.type);
     known_pdf.emplace(ev.central().muf, result.central);
 
     result.variations.reserve(ev.variations().size());
     for(auto const & ev_param: ev.variations()){
       const double muf = ev_param.muf;
       auto cur_pdf = known_pdf.find(muf);
       if(cur_pdf == known_pdf.end()){
         cur_pdf = known_pdf.emplace(
             muf,
             pdf_.pdfpt(0,xa,muf,a.type)*pdf_.pdfpt(1,xb,muf,b.type)
         ).first;
       }
       result.variations.emplace_back(cur_pdf->second);
     }
     assert(result.variations.size() == ev.variations().size());
     return result;
   }
 
   Weights
   EventReweighter::matrix_elements(Event const & ev) const{
     assert(param_.treat.count(ev.type()) > 0);
     if(param_.treat.find(ev.type())->second == EventTreatment::keep){
       return fixed_order_scale_ME(ev);
     }
 
     return MEt2_(ev);
   }
 
   double EventReweighter::tree_matrix_element(Event const & ev) const{
     assert(ev.variations().empty());
     assert(param_.treat.count(ev.type()) > 0);
     if(param_.treat.find(ev.type())->second == EventTreatment::keep){
       return fixed_order_scale_ME(ev).central;
     }
     return MEt2_.tree(ev).central;
   }
 
   Weights
   EventReweighter::fixed_order_scale_ME(Event const & ev) const{
     int alpha_s_power = 0;
     for(auto const & part: ev.outgoing()){
       if(is_parton(part))
         ++alpha_s_power;
       else if(part.type == pid::Higgs) {
         alpha_s_power += 2;
       }
       // nothing to do for other uncoloured particles
     }
 
     Weights result;
     result.central = std::pow(pdf_.Halphas(ev.central().mur), alpha_s_power);
     for(auto const & var: ev.variations()){
       result.variations.emplace_back(
           std::pow(pdf_.Halphas(var.mur), alpha_s_power)
       );
     }
     return result;
   }
 
+  abort_event::abort_event(Event const & ev):
+    std::invalid_argument{
+      "Encountered `" + name(ev.type()) + "` event:\n"
+      + to_string(ev)
+    } {}
+
 } // namespace HEJ
diff --git a/src/PhaseSpacePoint.cc b/src/PhaseSpacePoint.cc
index 450be43..8e1b0a5 100644
--- a/src/PhaseSpacePoint.cc
+++ b/src/PhaseSpacePoint.cc
@@ -1,987 +1,983 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/PhaseSpacePoint.hh"
 
 #include <algorithm>
 #include <cassert>
 #include <cmath>
 #include <cstdlib>
 #include <functional>
 #include <iterator>
 #include <limits>
 #include <numeric>
 #include <random>
 #include <tuple>
 
 #include "fastjet/ClusterSequence.hh"
 #include "fastjet/JetDefinition.hh"
 
 #include "HEJ/Constants.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/JetSplitter.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/RNG.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/kinematics.hh"
 #include "HEJ/resummation_jet.hh"
 #include "HEJ/utility.hh"
 
 namespace HEJ {
   namespace {
     constexpr int MAX_JET_USER_IDX = 1000;
 
     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;
     }
 
     namespace user_idx {
       //! user indices for partons with extremal rapidity
       enum ID: int {
         qqbar_mid1 = -9,
         qqbar_mid2 = -8,
         qqbarb = -7,
         qqbarf = -6,
         unob = -5,
         unof = -4,
         backward_fkl = -3,
         forward_fkl = -2,
       };
     } // namespace user_idx
     using UID = user_idx::ID;
 
     double phase_space_normalisation(
         int num_Born_jets, int num_out_partons
     ){
       return std::pow(16.*std::pow(M_PI,3), num_Born_jets - num_out_partons);
     }
 
   } // namespace
 
   Event::EventData to_EventData(PhaseSpacePoint psp){
     Event::EventData result;
     result.incoming = std::move(psp).incoming_; // NOLINT(bugprone-use-after-move)
     result.outgoing = std::move(psp).outgoing_; // NOLINT(bugprone-use-after-move)
     // technically Event::EventData doesn't have to be sorted,
     // but PhaseSpacePoint should be anyway
     assert(
         std::is_sorted(
             begin(result.outgoing), end(result.outgoing),
             rapidity_less{}
         )
     );
     assert(result.outgoing.size() >= 2);
     static_assert(
         std::numeric_limits<double>::has_quiet_NaN,
         "no quiet NaN for double"
     );
     constexpr double nan = std::numeric_limits<double>::quiet_NaN();
     result.decays = std::move(psp).decays_; // NOLINT(bugprone-use-after-move)
     result.parameters.central = {nan, nan, psp.weight()}; // NOLINT(bugprone-use-after-move)
     return result;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::cluster_jets(
       std::vector<fastjet::PseudoJet> const & partons
   ) const{
     fastjet::ClusterSequence cs(partons, param_.jet_param.def);
     return sorted_by_rapidity(cs.inclusive_jets(param_.jet_param.min_pt));
   }
 
   bool PhaseSpacePoint::pass_resummation_cuts(
       std::vector<fastjet::PseudoJet> const & jets
   ) const{
     return cluster_jets(jets).size() == jets.size();
   }
 
   namespace {
     // find iterators to central qqbar emission
     auto get_central_qqbar(Event const & ev) {
       // find born quarks (ignore extremal partons)
       auto const firstquark = std::find_if(
           std::next(ev.begin_partons()), std::prev(ev.end_partons(), 2),
           [](Particle const & s){ return (is_anyquark(s)); }
       );
       // assert that it is a q-q_bar pair.
       assert(std::distance(firstquark, ev.end_partons()) != 2);
       assert(
           ( is_quark(*firstquark) && is_antiquark(*std::next(firstquark)) )
           || ( is_antiquark(*firstquark) && is_quark(*std::next(firstquark)) )
       );
       return std::make_pair(firstquark, std::next(firstquark));
     }
 
     //! returns index of most backward q-qbar jet
     template<class Iterator>
     int get_back_quark_jet(Event const & ev, Iterator firstquark){
       // find jets at FO corresponding to the quarks
       // technically this isn't necessary for LO
       std::vector<int> const born_indices{ ev.particle_jet_indices() };
       const auto firstquark_idx = std::distance(ev.begin_partons(), firstquark);
       int const firstjet_idx = born_indices[firstquark_idx];
       assert(firstjet_idx>0);
       assert( born_indices[firstquark_idx+1] == firstjet_idx+1 );
 
       return firstjet_idx;
     }
 
     //! returns index of most backward q-qbar jet
     int getBackQuarkJet(Event const & ev){
       const auto firstquark = get_central_qqbar(ev).first;
       return get_back_quark_jet(ev, firstquark);
     }
   } // namespace
 
   double PhaseSpacePoint::estimate_emission_rapidity_range(
     Event const & ev
   ) const {
     assert(std::is_sorted(begin(ev.jets()), end(ev.jets()), rapidity_less{}));
 
     const double ymin = is_backward_g_to_h(ev)?
       ev.outgoing().front().rapidity():
       most_backward_FKL(ev.jets()).rapidity();
     const double ymax = is_forward_g_to_h(ev)?
       ev.outgoing().back().rapidity():
       most_forward_FKL(ev.jets()).rapidity();
     double delta_y = ymax - ymin;
     // neglect tiny probability for emission between central qqbar pair
     if(ev.type() == event_type::central_qqbar) {
       const int qjet = getBackQuarkJet(ev);
       delta_y -= ev.jets()[qjet+1].rapidity() - ev.jets()[qjet].rapidity();
     }
     assert(delta_y >= 0);
     return delta_y;
   }
 
   double PhaseSpacePoint::estimate_ng_mean(Event const & ev) const {
     // Formula derived from fit in arXiv:1805.04446 (see Fig. 2)
     constexpr double GLUONS_PER_RAPIDITY = 0.975052;
     return GLUONS_PER_RAPIDITY*estimate_emission_rapidity_range(ev);
   }
 
   int PhaseSpacePoint::sample_ng(Event const & event, RNG & ran){
     if (param_.nlo.enabled == true){
         std::uniform_int_distribution<> dist(0, 1);
         const int ng = dist(ran);
         weight_ *= 2;
         assert(ng < 2);
         assert(ng >= 0);
         return ng;
     }
     else{
         const double ng_mean = estimate_ng_mean(event);
         std::poisson_distribution<int> dist(ng_mean);
         const int ng = dist(ran);
         assert(ng >= 0);
         assert(ng < MAX_JET_USER_IDX);
         weight_ *= std::tgamma(ng + 1)*std::exp(ng_mean)*std::pow(ng_mean, -ng);
         return ng;
     }
   }
 
   void PhaseSpacePoint::boost_AWZH_bosons_from(
       std::vector<fastjet::PseudoJet> const & boosted_bosons, Event const & event
   ){
     auto const & from = event.outgoing();
 
     auto find_AWZH = [](Particle const & p){ return is_AWZH_boson(p); };
     size_t boosted_idx = 0;
     for(
       auto original_boson = std::find_if(begin(from), end(from), find_AWZH);
       original_boson != end(from);
       original_boson = std::find_if(++original_boson, end(from), find_AWZH),
       ++boosted_idx
     ){
       auto insertion_point = std::lower_bound(
           begin(outgoing_), end(outgoing_), *original_boson, rapidity_less{}
       );
 
       // copy AWZH particle
       const int new_idx = std::distance(begin(outgoing_), insertion_point);
       assert(new_idx >= 0);  // insert invalidates distance
       outgoing_.insert(insertion_point,
           {original_boson->type, boosted_bosons[boosted_idx], original_boson->colour}
       );
       assert(outgoing_[new_idx].type == original_boson->type);
       assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{}));
 
       // copy & boost decay products
       const int idx = std::distance(begin(from), original_boson);
       assert(idx >= 0);
       const auto decay_it = event.decays().find(idx);
       if(decay_it != end(event.decays())){
         auto decayparticles = decay_it->second;
         // change the momenta of the decay products.
         fastjet::PseudoJet sum;
         for(auto & particle: decayparticles){
           auto & p = particle.p;
           // boost _to_ rest frame of input boson
           p.unboost(original_boson->p);
           // then boost _from_ rest frame of shuffled boson
           p.boost(boosted_bosons[boosted_idx]);
           if(p.E() < std::abs(p.pz())){
             throw std::underflow_error("Reshuffled decay with E<|pz|");
           }
           sum += p;
         }
         if(!nearby(boosted_bosons[boosted_idx], sum, boosted_bosons[boosted_idx].E())){
           throw std::underflow_error("Boson and decays momenta do not match after reshuffling");
         }
         decays_.emplace(new_idx, decayparticles);
       }
     }
   }
 
   namespace {
 
     template<class ConstIterator, class Iterator>
     void label_extremal_qqbar(
       ConstIterator born_begin, ConstIterator born_end,
       Iterator first_out
     ){
       // find born quarks
       const auto firstquark = std::find_if(
         born_begin, born_end-1,
           [](Particle const & s){ return (is_anyquark(s)); }
       );
       assert(firstquark != born_end-1);
       const auto secondquark = std::find_if(
         firstquark+1, born_end,
           [](Particle const & s){ return (is_anyquark(s)); }
       );
       assert(secondquark != born_end);
       assert( ( is_quark(*firstquark) && is_antiquark(*secondquark) )
             || ( is_antiquark(*firstquark) && is_quark(*secondquark) ));
       assert(first_out->type     == ParticleID::gluon);
       assert((first_out+1)->type == ParticleID::gluon);
 
       // copy type from born
       first_out->type     = firstquark->type;
       (first_out+1)->type = secondquark->type;
     }
   } // namespace
 
   void PhaseSpacePoint::label_qqbar(Event const & event){
     assert(std::is_sorted(begin(outgoing_), end(outgoing_), rapidity_less{}));
     assert(filter_partons(outgoing_).size() == outgoing_.size());
     if(qqbarb_){
       label_extremal_qqbar(event.outgoing().cbegin(), event.outgoing().cend(),
                            outgoing_.begin() );
       return;
     }
     if(qqbarf_){ // same as qqbarb with reversed order
       label_extremal_qqbar( event.outgoing().crbegin(), event.outgoing().crend(),
                             outgoing_.rbegin() );
       return;
     }
     // central qqbar
     const auto firstquark = get_central_qqbar(event).first;
 
     // find jets at FO corresponding to the quarks
     // technically this isn't necessary for LO
     const auto firstjet_idx = get_back_quark_jet(event, firstquark);
 
     // find corresponding jets after resummation
     fastjet::ClusterSequence cs{to_PseudoJet(outgoing_), param_.jet_param.def};
     auto const jets = fastjet::sorted_by_rapidity(
                         cs.inclusive_jets( param_.jet_param.min_pt ));
     std::vector<int> const resum_indices{ cs.particle_jet_indices({jets}) };
 
     // assert that jets didn't move
     assert(nearby_ep( ( event.jets().cbegin()+firstjet_idx )->rapidity(),
                       jets[ firstjet_idx ].rapidity(),   1e-2) );
     assert(nearby_ep( ( event.jets().cbegin()+firstjet_idx+1 )->rapidity(),
                       jets[ firstjet_idx+1 ].rapidity(), 1e-2) );
 
     // find last partons in first (central) jet
     size_t idx_out = 0;
     for(size_t i=resum_indices.size()-2; i>0; --i)
       if(resum_indices[i] == firstjet_idx){
         idx_out = i;
         break;
       }
     assert(idx_out != 0);
 
     // check that there is sufficient pt in jets from the quarks
     const double minpartonjetpt = 1. - param_.soft_pt_regulator;
     if (outgoing_[idx_out].p.pt()<minpartonjetpt*( event.jets().cbegin()+firstjet_idx )->pt()){
       weight_=0.;
       status_ = StatusCode::wrong_jets;
       return;
     }
 
     if (outgoing_[idx_out+1].p.pt()<minpartonjetpt*( event.jets().cbegin()+firstjet_idx+1 )->pt()){
       weight_=0.;
       status_ = StatusCode::wrong_jets;
       return;
     }
     // check that no additional emission between jets
     // such configurations are possible if we have an gluon gets generated
     // inside the rapidities of the qqbar chain, but clusted to a
     // differnet/outside jet. Changing this is non trivial
     if(resum_indices[idx_out+1] != resum_indices[idx_out]+1){
       weight_=0.;
       status_ = StatusCode::gluon_in_qqbar;
       return;
     }
     outgoing_[idx_out].type   = firstquark->type;
     outgoing_[idx_out+1].type = std::next(firstquark)->type;
   }
 
   void PhaseSpacePoint::label_quarks(Event const & ev){
     const auto WZEmit = std::find_if(
       begin(ev.outgoing()), end(ev.outgoing()),
       [](Particle const & s){ return (std::abs(s.type) == pid::Wp || s.type == pid::Z_photon_mix); }
     );
 
     if (WZEmit != end(ev.outgoing())){
       if(!qqbarb_) {
         const size_t backward_FKL_idx = unob_?1:0;
         const auto backward_FKL = std::next(ev.begin_partons(), backward_FKL_idx);
         outgoing_[backward_FKL_idx].type = backward_FKL->type;
       }
       if(!qqbarf_) {
         const size_t forward_FKL_idx = unof_?1:0;
         const auto forward_FKL = std::prev(ev.end_partons(), 1+forward_FKL_idx);
         outgoing_.rbegin()[unof_].type = forward_FKL->type; // NOLINT
       }
     } else {
       if(!is_backward_g_to_h(ev)) {
         most_backward_FKL(outgoing_).type = ev.incoming().front().type;
       }
       if(!is_forward_g_to_h(ev)) {
         most_forward_FKL(outgoing_).type = ev.incoming().back().type;
       }
     }
 
     if(qqbar_mid_||qqbarb_||qqbarf_){
       label_qqbar(ev);
     }
   }
 
   PhaseSpacePoint::PhaseSpacePoint(
       Event const & ev, PhaseSpacePointConfig conf, RNG & ran
   ):
     unob_{ev.type() == event_type::unob},
     unof_{ev.type() == event_type::unof},
     qqbarb_{ev.type() == event_type::qqbar_exb},
     qqbarf_{ev.type() == event_type::qqbar_exf},
     qqbar_mid_{ev.type() == event_type::qqbar_mid},
     param_{std::move(conf)},
     status_{unspecified}
   {
     // legacy code: override new variable with old
     if(param_.max_ext_soft_pt_fraction){
       param_.soft_pt_regulator = *param_.max_ext_soft_pt_fraction;
       param_.max_ext_soft_pt_fraction = {};
     }
 
     weight_ = 1;
     auto const & Born_jets = ev.jets();
     const int ng = sample_ng(ev, ran);
     weight_ /= std::tgamma(ng + 1);
     const int ng_jets = sample_ng_jets(ev, ng, ran);
     std::vector<fastjet::PseudoJet> out_partons = gen_non_jet(
        ng - ng_jets, CMINPT, param_.jet_param.min_pt, ran
     );
 
     const auto qperp = std::accumulate(
       begin(out_partons), end(out_partons),
       fastjet::PseudoJet{}
     );
 
     std::vector<fastjet::PseudoJet> jets;
     std::vector<fastjet::PseudoJet> bosons;
     std::tie(jets, bosons) = reshuffle(ev, qperp);
     if(weight_ == 0.) {
       status_ = failed_reshuffle;
       return;
     }
     if(! pass_resummation_cuts(jets)){
       status_ = failed_resummation_cuts;
       weight_ = 0.;
       return;
     }
 
     // split jets in multiple partons
     std::vector<fastjet::PseudoJet> jet_partons = split(
       ev, jets, ng_jets, ran
     );
     if(weight_ == 0.) {
       status_ = StatusCode::failed_split;
       return;
     }
 
     const double ymin = is_backward_g_to_h(ev)?
       ev.outgoing().front().rapidity():
       most_backward_FKL(jet_partons).rapidity()
       ;
     const double ymax = is_forward_g_to_h(ev)?
       ev.outgoing().back().rapidity():
       most_forward_FKL(jet_partons).rapidity()
       ;
     if(qqbar_mid_){
       const int qqbar_backjet = getBackQuarkJet(ev);
       rescale_qqbar_rapidities(
         out_partons, jets,
         ymin, ymax,
         qqbar_backjet
       );
     }
     else{
       rescale_rapidities(out_partons, ymin, ymax);
     }
     if(! cluster_jets(out_partons).empty()){
       weight_ = 0.;
       status_ = StatusCode::empty_jets;
       return;
     }
     std::sort(begin(out_partons), end(out_partons), rapidity_less{});
     assert(
       std::is_sorted(begin(jet_partons), end(jet_partons), rapidity_less{})
     );
     const auto first_jet_parton = out_partons.insert(
       end(out_partons), begin(jet_partons), end(jet_partons)
     );
     std::inplace_merge(
       begin(out_partons), first_jet_parton, end(out_partons), rapidity_less{}
     );
 
     if(! jets_ok(ev, out_partons)){
       weight_ = 0.;
       status_ = StatusCode::wrong_jets;
       return;
     }
 
     weight_ *= phase_space_normalisation(Born_jets.size(), out_partons.size());
 
     outgoing_.reserve(out_partons.size() + 2); // two slots for possible A, W, Z, H
     for( auto it = std::make_move_iterator(out_partons.begin());
               it != std::make_move_iterator(out_partons.end());
             ++it
     ){
       outgoing_.emplace_back( Particle{pid::gluon, *it, {}});
     }
     assert(!outgoing_.empty());
 
     label_quarks(ev);
     if(weight_ == 0.) {
       //! @TODO optimise s.t. this is not possible
       // status is handled internally
       return;
     }
 
     // reattach bosons & decays
     if(!bosons.empty()){
       try {
         boost_AWZH_bosons_from(bosons, ev);
       } catch (std::underflow_error const & e){
         weight_ = 0.;
         status_ = StatusCode::failed_reshuffle;
         return;
       }
     }
 
     reconstruct_incoming(ev.incoming());
     status_ = StatusCode::good;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::gen_non_jet(
       int const ng_non_jet, double const ptmin, double const ptmax, RNG & ran
   ){
     // heuristic parameters for pt sampling
     const double ptpar = 1.3 + ng_non_jet/5.;
     const double temp1 = std::atan((ptmax - ptmin)/ptpar);
 
     std::vector<fastjet::PseudoJet> partons(ng_non_jet);
     for(int i = 0; i < ng_non_jet; ++i){
       const double r1 = ran.flat();
       const double pt = ptmin + ptpar*std::tan(r1*temp1);
       const double temp2 = std::cos(r1*temp1);
       const double phi = 2*M_PI*ran.flat();
       weight_ *= 2.0*M_PI*pt*ptpar*temp1/(temp2*temp2);
       // we don't know the allowed rapidity span yet,
       // set a random value to be rescaled later on
       const double y = ran.flat();
       partons[i].reset_PtYPhiM(pt, y, phi);
       // Set user index higher than any jet-parton index
       // in order to assert that these are not inside jets
       partons[i].set_user_index(i + 1 + MAX_JET_USER_IDX);
 
       assert(ptmin-1e-5 <= partons[i].pt() && partons[i].pt() <= ptmax+1e-5);
     }
     assert(std::all_of(partons.cbegin(), partons.cend(), is_nonjet_parton));
     return sorted_by_rapidity(partons);
   }
 
   void PhaseSpacePoint::rescale_qqbar_rapidities(
       std::vector<fastjet::PseudoJet> & out_partons,
       std::vector<fastjet::PseudoJet> const & jets,
       const double ymin1, const double ymax2,
       const int qqbar_backjet
   ){
     const double ymax1 = jets[qqbar_backjet].rapidity();
     const double ymin2 = jets[qqbar_backjet+1].rapidity();
     constexpr double ep = 1e-7;
     const double tot_y = ymax1 - ymin1 + ymax2 - ymin2;
 
     std::vector<std::reference_wrapper<fastjet::PseudoJet>> refpart(
       out_partons.begin(), out_partons.end());
 
     double ratio = (ymax1 - ymin1)/tot_y;
 
     const auto gap{ std::find_if(refpart.begin(), refpart.end(),
                             [ratio](fastjet::PseudoJet const & p){
                               return (p.rapidity()>=ratio);} ) };
 
     double ymin = ymin1;
     double ymax = ymax1;
     double dy = ymax - ymin - 2*ep;
     double offset = 0.;
     for(auto it_part=refpart.begin(); it_part<refpart.end(); ++it_part){
       if(it_part == gap){
         ymin = ymin2;
         ymax = ymax2;
         dy = ymax - ymin - 2*ep;
         offset = ratio;
         ratio = 1-ratio;
       }
       fastjet::PseudoJet & part = *it_part;
       assert(offset <= part.rapidity() && part.rapidity() < ratio+offset);
       const double y = ymin + ep + dy*((part.rapidity()-offset)/ratio);
       part.reset_momentum_PtYPhiM(part.pt(), y, part.phi());
       weight_ *= tot_y-4.*ep;
       assert(ymin <= part.rapidity() && part.rapidity() <= ymax);
     }
     assert(is_sorted(begin(out_partons), end(out_partons), rapidity_less{}));
   }
 
   void PhaseSpacePoint::rescale_rapidities(
       std::vector<fastjet::PseudoJet> & partons,
       double ymin, double ymax
   ){
     constexpr double ep = 1e-7;
     for(auto & parton: partons){
       assert(0 <= parton.rapidity() && parton.rapidity() <= 1);
       const double dy = ymax - ymin - 2*ep;
       const double y = ymin + ep + dy*parton.rapidity();
       parton.reset_momentum_PtYPhiM(parton.pt(), y, parton.phi());
       weight_ *= dy;
       assert(ymin <= parton.rapidity() && parton.rapidity() <= ymax);
     }
   }
 
   namespace {
     template<typename T, typename... Rest>
     auto min(T const & a, T const & b, Rest&&... r) {
       using std::min;
       return min(a, min(b, std::forward<Rest>(r)...));
     }
   }
 
   double PhaseSpacePoint::probability_in_jet(Event const & ev) const{
     const double dy = estimate_emission_rapidity_range(ev);
     const double R = param_.jet_param.def.R();
     // jets into which we predominantly emit
     const int njets = ev.jets().size() - unof_ - unob_ - qqbarb_ - qqbarf_; //NOLINT
     assert(njets >= 1);
     const size_t nextremal_jets = std::min(njets, 2);
     const double p_J_y_large = (njets - nextremal_jets/2.)*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(Event const & event, int ng, RNG & ran){
     const double p_J = probability_in_jet(event);
     std::binomial_distribution<> bin_dist(ng, p_J);
     const int ng_J = bin_dist(ran);
     weight_ *= std::pow(p_J, -ng_J)*std::pow(1 - p_J, ng_J - ng);
     return ng_J;
   }
 
   std::pair< std::vector<fastjet::PseudoJet>,
              std::vector<fastjet::PseudoJet> >
   PhaseSpacePoint::reshuffle(
       Event const & ev,
       fastjet::PseudoJet const & q
   ){
     // Create a copy of the outgoing momenta not containing decay products
     std::vector<fastjet::PseudoJet const *> born_momenta;
     born_momenta.reserve(ev.jets().size());
     std::transform(ev.jets().cbegin(), ev.jets().cend(),
                    back_inserter(born_momenta),
                    [](fastjet::PseudoJet const & t) { return &t; });
 
     auto bosons = filter_AWZH_bosons(ev.outgoing());
 
     std::vector<fastjet::PseudoJet const *> p_boson_momenta;
     std::transform(bosons.cbegin(), bosons.cend(),
                    back_inserter(p_boson_momenta),
                    [](Particle const & t) { return &(t.p); });
 
     std::vector<fastjet::PseudoJet> boson_momenta;
     std::transform(bosons.cbegin(), bosons.cend(),
                    back_inserter(boson_momenta),
                    [](Particle const & t) { return t.p; });
 
     // reshuffle all momenta
     if(q == fastjet::PseudoJet{0, 0, 0, 0}) return {ev.jets(), boson_momenta};
     // add bosons to reshuffling
     if(!bosons.empty()) {
       born_momenta.insert( born_momenta.end(), p_boson_momenta.begin(), p_boson_momenta.end() );
     }
     auto shuffle_momenta = resummation_jet_momenta(born_momenta, q);
     if(shuffle_momenta.empty()){
       weight_ = 0;
       return {};
     }
     // additional Jacobian to ensure Born integration over delta gives 1
     weight_ *= resummation_jet_weight(born_momenta, q);
 
     // take out bosons again
     if(!bosons.empty()) {
       std::vector<fastjet::PseudoJet> shuffle_bosons;
       for(size_t i = 0; i < bosons.size(); ++i) {
         shuffle_bosons.push_back(shuffle_momenta.back());
         shuffle_momenta.pop_back();
       }
       std::reverse(shuffle_bosons.begin(), shuffle_bosons.end());
 
       return {shuffle_momenta, shuffle_bosons};
     }
 
     return {shuffle_momenta, {}};
   }
 
   std::vector<int> PhaseSpacePoint::distribute_jet_partons(
       int ng_jets, std::vector<fastjet::PseudoJet> const & jets, RNG & ran
   ){
     size_t first_valid_jet = 0;
     size_t num_valid_jets = jets.size();
     const double R_eff = 5./3.*param_.jet_param.def.R();
     // if there is an unordered jet too far away from the FKL jets
     // then extra gluon constituents of the unordered jet would
     // violate the FKL rapidity ordering
     if((unob_||qqbarb_) && jets[0].delta_R(jets[1]) > R_eff){
       ++first_valid_jet;
       --num_valid_jets;
     }
     else if((unof_||qqbarf_) && jets[jets.size()-1].delta_R(jets[jets.size()-2]) > R_eff){
       --num_valid_jets;
     }
     std::vector<int> np(jets.size(), 1);
     for(int i = 0; i < ng_jets; ++i){
       ++np[first_valid_jet + ran.flat() * num_valid_jets];
     }
     weight_ *= std::pow(num_valid_jets, ng_jets);
     return np;
   }
 
 #ifndef NDEBUG
   namespace {
     bool tagged_FKL_backward(
         std::vector<fastjet::PseudoJet> const & jet_partons
     ){
       return std::find_if(
           begin(jet_partons), end(jet_partons),
           [](fastjet::PseudoJet const & p){
             return p.user_index() == UID::backward_fkl;
           }
       ) != 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() == UID::forward_fkl;
           }
       ) != jet_partons.rend();
     }
 
   } // namespace
 #endif
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::split(
     Event const & Born_event,
     std::vector<fastjet::PseudoJet> const & jets,
     int ng_jets
     , RNG & ran
   ){
     return split(
       Born_event,
       jets,
       distribute_jet_partons(ng_jets, jets, ran),
       ran
     );
   }
 
   bool PhaseSpacePoint::pass_extremal_cuts(
       fastjet::PseudoJet const & ext_parton,
       fastjet::PseudoJet const & jet
   ) const{
     if(ext_parton.pt() < param_.min_extparton_pt) return false;
     return (ext_parton - jet).pt()/jet.pt() < param_.soft_pt_regulator;
   }
 
   std::vector<fastjet::PseudoJet> PhaseSpacePoint::split(
     Event const & Born_event,
     std::vector<fastjet::PseudoJet> const & jets,
     std::vector<int> const & np,
     RNG & ran
   ){
     assert(! jets.empty());
     assert(jets.size() == np.size());
     assert(pass_resummation_cuts(jets));
     constexpr auto no_such_jet_idx = std::numeric_limits<std::size_t>::max();
 
     const size_t most_backward_FKL_idx = is_backward_g_to_h(Born_event)?
       no_such_jet_idx: // we have backward Higgs instead of FKL jet
       (0 + unob_ + qqbarb_); // NOLINT
     const size_t most_forward_FKL_idx = is_forward_g_to_h(Born_event)?
       no_such_jet_idx: // we have forward Higgs instead of FKL jet
       (jets.size() - 1 - unof_ - qqbarf_); // NOLINT
     const size_t qqbar_jet_idx = qqbar_mid_?
       getBackQuarkJet(Born_event):
       no_such_jet_idx;
     auto const & jet = param_.jet_param;
     const JetSplitter jet_splitter{jet.def, jet.min_pt};
 
     std::vector<fastjet::PseudoJet> jet_partons;
     // randomly distribute jet gluons among jets
     for(size_t i = 0; i < jets.size(); ++i){
       auto split_res = jet_splitter.split(jets[i], np[i], ran);
       weight_ *= split_res.weight;
       if(weight_ == 0) return {};
       assert(
           std::all_of(
               begin(split_res.constituents), end(split_res.constituents),
               is_jet_parton
           )
       );
       const auto first_new_parton = jet_partons.insert(
           end(jet_partons),
           begin(split_res.constituents), end(split_res.constituents)
       );
       // mark uno and extremal FKL emissions here so we can check
       // their position once all emissions are generated
       // also mark qqbar_mid partons, and apply appropriate pt cut.
       auto extremal = end(jet_partons);
       if (i == most_backward_FKL_idx){ //FKL backward emission
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(UID::backward_fkl);
       }
       else if(((unob_ || qqbarb_) && i == 0)){
         // unordered/qqbarb
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index((unob_)?UID::unob:UID::qqbarb);
       }
 
       else if (i == most_forward_FKL_idx){
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(UID::forward_fkl);
       }
       else if(((unof_ || qqbarf_) && i == jets.size() - 1)){
         // unordered/qqbarf
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index((unof_)?UID::unof:UID::qqbarf);
       }
       else if((qqbar_mid_ && i == qqbar_jet_idx)){
         extremal = std::max_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(UID::qqbar_mid1);
       }
       else if((qqbar_mid_ && i == qqbar_jet_idx+1)){
         extremal = std::min_element(
             first_new_parton, end(jet_partons), rapidity_less{}
         );
         extremal->set_user_index(UID::qqbar_mid2);
       }
       if(
           extremal != end(jet_partons)
           && !pass_extremal_cuts(*extremal, jets[i])
       ){
         weight_ = 0;
         return {};
       }
     }
     assert(is_backward_g_to_h(Born_event) || tagged_FKL_backward(jet_partons));
     assert(is_forward_g_to_h(Born_event) || tagged_FKL_forward(jet_partons));
     std::sort(begin(jet_partons), end(jet_partons), rapidity_less{});
     if(
       !extremal_ok(Born_event, jet_partons)
         || !split_preserved_jets(jets, jet_partons)
     ){
       weight_ = 0.;
       return {};
     }
     return jet_partons;
   }
 
   bool PhaseSpacePoint::extremal_ok(
     Event const & Born_event,
     std::vector<fastjet::PseudoJet> const & partons
   ) const{
     assert(std::is_sorted(begin(partons), end(partons), rapidity_less{}));
     if(unob_ && partons.front().user_index() != UID::unob) return false;
     if(unof_ && partons.back().user_index()  != UID::unof) return false;
     if(qqbarb_ && partons.front().user_index() != UID::qqbarb) return false;
     if(qqbarf_ && partons.back().user_index()  != UID::qqbarf) return false;
     if(is_backward_g_to_h(Born_event)) {
       if(partons.front().rapidity() < Born_event.outgoing().front().rapidity()){
         return false;
       }
     } else if(most_backward_FKL(partons).user_index() != UID::backward_fkl) {
       return false;
     }
     if(is_forward_g_to_h(Born_event)) {
       return partons.back().rapidity() <= Born_event.outgoing().back().rapidity();
     }
     return most_forward_FKL(partons).user_index() == UID::forward_fkl;
   }
 
   bool PhaseSpacePoint::split_preserved_jets(
         std::vector<fastjet::PseudoJet> const & jets,
         std::vector<fastjet::PseudoJet> const & jet_partons
   ) const{
     assert(std::is_sorted(begin(jets), end(jets), rapidity_less{}));
 
     const auto split_jets = cluster_jets(jet_partons);
     // this can happen if two overlapping jets
     // are both split into more than one parton
     if(split_jets.size() != jets.size()) return false;
     for(size_t i = 0; i < split_jets.size(); ++i){
       // this can happen if there are two overlapping jets
       // and a parton is assigned to the "wrong" jet
       if(!nearby_ep(jets[i].rapidity(), split_jets[i].rapidity(), 1e-2)){
         return false;
       }
     }
     return true;
   }
 
   template<class Particle>
   Particle const & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> const & partons
   ) const{
     return partons[0 + unob_ + qqbarb_];
   }
 
   template<class Particle>
   Particle const & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> const & partons
   ) const{
     const size_t idx = partons.size() - 1 - unof_ - qqbarf_;
     assert(idx < partons.size());
     return partons[idx];
   }
 
   template<class Particle>
   Particle & PhaseSpacePoint::most_backward_FKL(
       std::vector<Particle> & partons
   ) const{
     return partons[0 + unob_ + qqbarb_];
   }
 
   template<class Particle>
   Particle & PhaseSpacePoint::most_forward_FKL(
       std::vector<Particle> & partons
   ) const{
     const size_t idx = partons.size() - 1 - unof_ - qqbarf_;
     assert(idx < partons.size());
     return partons[idx];
   }
 
   bool PhaseSpacePoint::contains_idx(
       fastjet::PseudoJet const & jet, fastjet::PseudoJet const & parton
   ) const {
     auto const & constituents = jet.constituents();
     const int idx = parton.user_index();
     const bool injet = std::find_if(
         begin(constituents), end(constituents),
         [idx](fastjet::PseudoJet const & con){return con.user_index() == idx;}
     ) != end(constituents);
     const double minpartonjetpt = 1. - param_.soft_pt_regulator;
     return ((parton.pt()>minpartonjetpt*jet.pt())&&injet);
   }
 
   bool PhaseSpacePoint::jets_ok(
     Event const & Born_event,
     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_event.jets().size()) return false;
     int in_jet = 0;
     for(auto const & jet : jets){
       assert(jet.has_constituents());
       for(auto && parton: jet.constituents()){
         if(is_nonjet_parton(parton)) return false;
       }
       in_jet += jet.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(
       !is_backward_g_to_h(Born_event) &&
       !contains_idx(most_backward_FKL(jets), most_backward_FKL(partons))
     ) return false;
     if(
       !is_forward_g_to_h(Born_event)
       &&  !contains_idx(most_forward_FKL(jets), most_forward_FKL(partons))
     ) return false;
     if(unob_ && !contains_idx(jets.front(), partons.front())) return false;
     if(qqbarb_ && !contains_idx(jets.front(), partons.front())) return false;
     if(unof_ && !contains_idx(jets.back(), partons.back())) return false;
     if(qqbarf_ && !contains_idx(jets.back(), partons.back())) return false;
 #ifndef NDEBUG
     for(size_t i = 0; i < jets.size(); ++i){
       assert(nearby_ep(jets[i].rapidity(), Born_event.jets()[i].rapidity(), 1e-2));
     }
 #endif
     return true;
   }
 
   void PhaseSpacePoint::reconstruct_incoming(
       std::array<Particle, 2> const & Born_incoming
   ){
     std::tie(incoming_[0].p, incoming_[1].p) = incoming_momenta(outgoing_);
     for(size_t i = 0; i < incoming_.size(); ++i){
       incoming_[i].type = Born_incoming[i].type;
     }
     assert(momentum_conserved());
   }
 
   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);
+    return HEJ::momentum_conserved(*this);
   }
 
 } //namespace HEJ
diff --git a/src/YAMLreader.cc b/src/YAMLreader.cc
index d2a5041..2c4c899 100644
--- a/src/YAMLreader.cc
+++ b/src/YAMLreader.cc
@@ -1,599 +1,608 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/YAMLreader.hh"
 
 #include <algorithm>
 #include <iostream>
 #include <limits>
 #include <map>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
 #include <dlfcn.h>
 
 #include "HEJ/ConfigFlags.hh"
 #include "HEJ/Constants.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/output_formats.hh"
 
 namespace HEJ {
   class Event;
 
   namespace {
     //! Get YAML tree of supported options
     /**
      * The configuration file is checked against this tree of options
      * in assert_all_options_known.
      */
     YAML::Node const & get_supported_options(){
       const static YAML::Node supported = [](){
         YAML::Node supported;
         static const auto opts = {
           "trials", "min extparton pt", "max ext soft pt fraction",
           "soft pt regulator",
           "scales", "scale factors", "max scale ratio", "import scales",
           "log correction", "event output", "analysis", "analyses", "vev",
           "regulator parameter", "max events", "off-shell tolerance",
           "require low pt jet"
         };
         // add subnodes to "supported" - the assigned value is irrelevant
         for(auto && opt: opts) supported[opt] = "";
         for(auto && jet_opt: {"min pt", "algorithm", "R"}){
           supported["resummation jets"][jet_opt] = "";
           supported["fixed order jets"][jet_opt] = "";
         }
         for(auto && opt: {"mt", "use impact factors", "include bottom", "mb"}){
           supported["Higgs coupling"][opt] = "";
         }
         for(auto && opt: {"name", "seed"}){
           supported["random generator"][opt] = "";
         }
         for(auto && opt: {"enabled", "nlo order"}){
           supported["NLO truncation"][opt] = "";
         }
-        for(auto && opt: {"FKL", "unordered", "extremal qqbar", "central qqbar", "non-resummable"}){
+        for(
+          auto && opt: {
+            "FKL", "unordered", "extremal qqbar", "central qqbar", "non-resummable",
+            "unknown", "invalid"
+          }){
           supported["event treatment"][opt] = "";
         }
         for(auto && particle_type: {"Higgs", "W", "Z"}){
           for(auto && particle_opt: {"mass", "width"}){
             supported["particle properties"][particle_type][particle_opt] = "";
           }
         }
         for(auto && opt: {"type", "trials", "max deviation"}){
           supported["unweight"][opt] = "";
         }
         return supported;
       }();
       return supported;
     }
 
     fastjet::JetAlgorithm to_JetAlgorithm(std::string const & algo){
       using namespace fastjet;
       static const std::map<std::string, fastjet::JetAlgorithm> known = {
         {"kt", kt_algorithm},
         {"cambridge", cambridge_algorithm},
         {"antikt", antikt_algorithm},
         {"cambridge for passive", cambridge_for_passive_algorithm},
         {"plugin", plugin_algorithm}
       };
       const auto res = known.find(algo);
       if(res == known.end()){
         throw std::invalid_argument("Unknown jet algorithm \"" + algo + "\"");
       }
       return res->second;
     }
 
     EventTreatment to_EventTreatment(std::string const & name){
       static const std::map<std::string, EventTreatment> known = {
         {"reweight", EventTreatment::reweight},
         {"keep", EventTreatment::keep},
-        {"discard", EventTreatment::discard}
+        {"discard", EventTreatment::discard},
+        {"abort", EventTreatment::abort}
       };
       const auto res = known.find(name);
       if(res == known.end()){
         throw std::invalid_argument("Unknown event treatment \"" + name + "\"");
       }
       return res->second;
     }
 
     WeightType to_weight_type(std::string const & setting){
       if(setting == "weighted")
           return WeightType::weighted;
       if(setting =="resummation")
           return WeightType::unweighted_resum;
       if(setting =="partial")
           return WeightType::partially_unweighted;
       throw std::invalid_argument{"Unknown weight type \"" + setting + "\""};
     }
 
   } // namespace
 
   namespace detail{
     void set_from_yaml(fastjet::JetAlgorithm & setting, YAML::Node const & yaml){
       setting = to_JetAlgorithm(yaml.as<std::string>());
     }
 
     void set_from_yaml(EventTreatment & setting, YAML::Node const & yaml){
       setting = to_EventTreatment(yaml.as<std::string>());
     }
 
     void set_from_yaml(ParticleID & setting, YAML::Node const & yaml){
       setting = to_ParticleID(yaml.as<std::string>());
     }
 
     void set_from_yaml(WeightType & setting, YAML::Node const & yaml){
       setting = to_weight_type(yaml.as<std::string>());
     }
 
   } // namespace detail
 
   JetParameters get_jet_parameters(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     JetParameters result;
     fastjet::JetAlgorithm jet_algo = fastjet::antikt_algorithm;
     double R = NAN;
     set_from_yaml_if_defined(jet_algo, node, entry, "algorithm");
     set_from_yaml(R, node, entry, "R");
     result.def = fastjet::JetDefinition{jet_algo, R};
     set_from_yaml(result.min_pt, node, entry, "min pt");
     return result;
   }
 
   RNGConfig to_RNGConfig(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     RNGConfig result;
     set_from_yaml(result.name, node, entry, "name");
     set_from_yaml_if_defined(result.seed, node, entry, "seed");
     return result;
   }
 
   NLOConfig to_NLOConfig(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     NLOConfig result;
     set_from_yaml_if_defined(result.enabled, node, entry, "enabled");
     set_from_yaml_if_defined(result.nj, node, entry, "nlo order");
     return result;
   }
 
   ParticleProperties get_particle_properties(
       YAML::Node const & node, std::string const & entry,
       std::string const & boson
   ){
     ParticleProperties result{};
     set_from_yaml(result.mass, node, entry, boson, "mass");
     set_from_yaml(result.width, node, entry, boson, "width");
     return result;
   }
 
   EWConstants get_ew_parameters(YAML::Node const & node){
     EWConstants result;
     double vev = NAN;
     set_from_yaml(vev, node, "vev");
     result.set_vevWZH(vev,
       get_particle_properties(node, "particle properties", "W"),
       get_particle_properties(node, "particle properties", "Z"),
       get_particle_properties(node, "particle properties", "Higgs")
     );
     return result;
   }
 
   HiggsCouplingSettings get_Higgs_coupling(
       YAML::Node const & node,
       std::string const & entry
   ){
     assert(node);
     static constexpr double mt_max = 2e4;
 #ifndef HEJ_BUILD_WITH_QCDLOOP
     if(node[entry].IsDefined()){
       throw std::invalid_argument{
         "Higgs coupling settings require building HEJ 2 "
           "with QCDloop support"
           };
     }
 #endif
     HiggsCouplingSettings settings;
     set_from_yaml_if_defined(settings.mt, node, entry, "mt");
     set_from_yaml_if_defined(settings.mb, node, entry, "mb");
     set_from_yaml_if_defined(settings.include_bottom, node, entry, "include bottom");
     set_from_yaml_if_defined(settings.use_impact_factors, node, entry, "use impact factors");
     if(settings.use_impact_factors){
       if(settings.mt != std::numeric_limits<double>::infinity()){
         throw std::invalid_argument{
           "Conflicting settings: "
             "impact factors may only be used in the infinite top mass limit"
             };
       }
     }
     else{
       // huge values of the top mass are numerically unstable
       settings.mt = std::min(settings.mt, mt_max);
     }
     return settings;
   }
 
   FileFormat to_FileFormat(std::string const & name){
     static const std::map<std::string, FileFormat> known = {
       {"Les Houches", FileFormat::Les_Houches},
       {"HepMC", FileFormat::HepMC},
       {"HepMC2", FileFormat::HepMC2},
       {"HepMC3", FileFormat::HepMC3},
       {"HDF5", FileFormat::HDF5}
     };
     const auto res = known.find(name);
     if(res == known.end()){
       throw std::invalid_argument("Unknown file format \"" + name + "\"");
     }
     return res->second;
   }
 
   std::string extract_suffix(std::string const & filename){
     size_t separator = filename.rfind('.');
     if(separator == std::string::npos) return {};
     return filename.substr(separator + 1);
   }
 
   FileFormat format_from_suffix(std::string const & filename){
     const std::string suffix = extract_suffix(filename);
     if(suffix == "lhe") return FileFormat::Les_Houches;
     if(suffix == "hepmc") return FileFormat::HepMC;
     if(suffix == "hepmc3") return FileFormat::HepMC3;
     if(suffix == "hepmc2") return FileFormat::HepMC2;
     if(suffix == "hdf5") return FileFormat::HDF5;
     throw std::invalid_argument{
       "Can't determine format for output file \"" + filename  + "\""
     };
   }
 
   void assert_all_options_known(
       YAML::Node const & conf, YAML::Node const & supported
   ){
     if(!conf.IsMap()) return;
     if(!supported.IsMap()) throw invalid_type{"must not have sub-entries"};
     for(auto const & entry: conf){
       const auto name = entry.first.as<std::string>();
       if(! supported[name]) throw unknown_option{name};
       /* check sub-options, e.g. 'resummation jets: min pt'
        * we don't check analyses sub-options
        * those depend on the analysis being used and should be checked there
        * similar for "import scales"
        */
       if(name != "analyses" && name != "analysis" && name != "import scales"){
         try{
           assert_all_options_known(conf[name], supported[name]);
         }
         catch(unknown_option const & ex){
           throw unknown_option{name + ": " + ex.what()};
         }
         catch(invalid_type const & ex){
           throw invalid_type{name + ": " + ex.what()};
         }
       }
     }
   }
 
 } // namespace HEJ
 
 namespace YAML {
 
   Node convert<HEJ::OutputFile>::encode(HEJ::OutputFile const & outfile) {
     Node node;
     node[to_string(outfile.format)] = outfile.name;
     return node;
   }
 
   bool convert<HEJ::OutputFile>::decode(Node const & node, HEJ::OutputFile & out) {
     switch(node.Type()){
     case NodeType::Map: {
       YAML::const_iterator it = node.begin();
       out.format = HEJ::to_FileFormat(it->first.as<std::string>());
       out.name = it->second.as<std::string>();
       return true;
     }
     case NodeType::Scalar:
       out.name = node.as<std::string>();
       out.format = HEJ::format_from_suffix(out.name);
       return true;
     default:
       return false;
     }
   }
 } // namespace YAML
 
 namespace HEJ {
 
   namespace detail{
     void set_from_yaml(OutputFile & setting, YAML::Node const & yaml){
       setting = yaml.as<OutputFile>();
     }
   }
 
   namespace {
     void update_fixed_order_jet_parameters(
         JetParameters & fixed_order_jets, YAML::Node const & yaml
     ){
       if(!yaml["fixed order jets"]) return;
       set_from_yaml_if_defined(
           fixed_order_jets.min_pt, yaml, "fixed order jets", "min pt"
       );
       fastjet::JetAlgorithm algo = fixed_order_jets.def.jet_algorithm();
       set_from_yaml_if_defined(algo, yaml, "fixed order jets", "algorithm");
       double R = fixed_order_jets.def.R();
       set_from_yaml_if_defined(R, yaml, "fixed order jets", "R");
       fixed_order_jets.def = fastjet::JetDefinition{algo, R};
     }
 
     // like std::stod, but throw if not the whole string can be converted
     double to_double(std::string const & str){
       std::size_t pos = 0;
       const double result = std::stod(str, &pos);
       if(pos < str.size()){
         throw std::invalid_argument(str + " is not a valid double value");
       }
       return result;
     }
 
     using EventScale = double (*)(Event const &);
 
     void import_scale_functions(
         std::string const & file,
         std::vector<std::string> const & scale_names,
         std::unordered_map<std::string, EventScale> & known
     ) {
       void * handle = dlopen(file.c_str(), RTLD_NOW);
       char * error = dlerror();
       if(error != nullptr) throw std::runtime_error{error};
 
       for(auto const & scale: scale_names) {
         void * sym = dlsym(handle, scale.c_str());
         error = dlerror();
         if(error != nullptr) throw std::runtime_error{error};
         known.emplace(scale, reinterpret_cast<EventScale>(sym)); // NOLINT
       }
     }
 
     auto get_scale_map(
         YAML::Node const & yaml
     ) {
       std::unordered_map<std::string, EventScale> scale_map;
       scale_map.emplace("H_T", H_T);
       scale_map.emplace("max jet pperp", max_jet_pt);
       scale_map.emplace("jet invariant mass", jet_invariant_mass);
       scale_map.emplace("m_j1j2", m_j1j2);
       if(yaml["import scales"].IsDefined()) {
         if(! yaml["import scales"].IsMap()) {
           throw invalid_type{"Entry 'import scales' is not a map"};
         }
         for(auto const & import: yaml["import scales"]) {
           const auto file = import.first.as<std::string>();
           const auto scale_names =
             import.second.IsSequence()
             ?import.second.as<std::vector<std::string>>()
             :std::vector<std::string>{import.second.as<std::string>()};
           import_scale_functions(file, scale_names, scale_map);
         }
       }
       return scale_map;
     }
 
     // simple (as in non-composite) scale functions
     /**
      * An example for a simple scale function would be H_T,
      * H_T/2 is then composite (take H_T and then divide by 2)
      */
     ScaleFunction parse_simple_ScaleFunction(
         std::string const & scale_fun,
         std::unordered_map<std::string, EventScale> const & known
     ) {
       assert(
           scale_fun.empty() ||
           (!std::isspace(scale_fun.front()) && !std::isspace(scale_fun.back()))
       );
       const auto it = known.find(scale_fun);
       if(it != end(known)) return {it->first, it->second};
       try{
         const double scale = to_double(scale_fun);
         return {scale_fun, FixedScale{scale}};
       } catch(std::invalid_argument const &){}
       throw std::invalid_argument{"Unknown scale choice: \"" + scale_fun + "\""};
     }
 
     std::string trim_front(std::string const & str){
       const auto new_begin = std::find_if(
           begin(str), end(str), [](char c){ return std::isspace(c) == 0; }
       );
       return std::string(new_begin, end(str));
     }
 
     std::string trim_back(std::string str){
       size_t pos = str.size() - 1;
       // use guaranteed wrap-around behaviour to check whether we have
       // traversed the whole string
       for(; pos < str.size() && std::isspace(str[pos]); --pos) {}
       str.resize(pos + 1); // note that pos + 1 can be 0
       return str;
     }
 
     ScaleFunction parse_ScaleFunction(
         std::string const & scale_fun,
         std::unordered_map<std::string, EventScale> const & known
     ){
       assert(
           scale_fun.empty() ||
           (!std::isspace(scale_fun.front()) && !std::isspace(scale_fun.back()))
       );
       // parse from right to left => a/b/c gives (a/b)/c
       const size_t delim = scale_fun.find_last_of("*/");
       if(delim == std::string::npos){
         return parse_simple_ScaleFunction(scale_fun, known);
       }
       const std::string first = trim_back(std::string{scale_fun, 0, delim});
       const std::string second = trim_front(std::string{scale_fun, delim+1});
       if(scale_fun[delim] == '/'){
         return parse_ScaleFunction(first, known)
           / parse_ScaleFunction(second, known);
       }
       assert(scale_fun[delim] == '*');
       return parse_ScaleFunction(first, known)
            * parse_ScaleFunction(second, known);
 
     }
 
     EventTreatMap get_event_treatment(
       YAML::Node const & node, std::string const & entry
     ){
       using namespace event_type;
       EventTreatMap treat {
-          {not_enough_jets, EventTreatment::discard},
-          {bad_final_state, EventTreatment::discard},
           {FKL, EventTreatment::discard},
           {unob, EventTreatment::discard},
           {unof, EventTreatment::discard},
           {qqbar_exb, EventTreatment::discard},
           {qqbar_exf, EventTreatment::discard},
           {qqbar_mid, EventTreatment::discard},
-          {non_resummable, EventTreatment::discard}
+          {non_resummable, EventTreatment::discard},
+          {unknown, EventTreatment::abort},
+          {invalid, EventTreatment::abort}
         };
       set_from_yaml(treat.at(FKL), node, entry, "FKL");
       set_from_yaml(treat.at(unob), node, entry, "unordered");
       treat.at(unof) = treat.at(unob);
       set_from_yaml(treat.at(qqbar_exb), node, entry, "extremal qqbar");
       treat.at(qqbar_exf) = treat.at(qqbar_exb);
       set_from_yaml(treat.at(qqbar_mid), node, entry, "central qqbar");
       set_from_yaml(treat.at(non_resummable), node, entry, "non-resummable");
-      if(treat[non_resummable] == EventTreatment::reweight){
-        throw std::invalid_argument{"Cannot reweight non-resummable events"};
+      set_from_yaml_if_defined(treat.at(unknown), node, entry, "unknown");
+      set_from_yaml_if_defined(treat.at(invalid), node, entry, "invalid");
+      for(auto type: {non_resummable, unknown, invalid}) {
+        if(treat[type] == EventTreatment::reweight){
+          throw std::invalid_argument{"Cannot reweight " + name(type) + " events"};
+        }
       }
       return treat;
     }
 
     Config to_Config(YAML::Node const & yaml){
       try{
         assert_all_options_known(yaml, get_supported_options());
       }
       catch(unknown_option const & ex){
         throw unknown_option{std::string{"Unknown option '"} + ex.what() + "'"};
       }
 
       Config config;
       config.resummation_jets = get_jet_parameters(yaml, "resummation jets");
       config.fixed_order_jets = config.resummation_jets;
       update_fixed_order_jet_parameters(config.fixed_order_jets, yaml);
       set_from_yaml_if_defined(config.min_extparton_pt, yaml, "min extparton pt");
       if(config.min_extparton_pt!=0)
         std::cerr << "WARNING: \"min extparton pt\" is deprecated."
           << " Please remove this entry or set \"soft pt regulator\" instead.\n";
       set_from_yaml_if_defined(
           config.max_ext_soft_pt_fraction, yaml, "max ext soft pt fraction"
       );
       if(config.max_ext_soft_pt_fraction){
         std::cerr << "WARNING: \"max ext soft pt fraction\" is deprecated."
           << " Please remove this entry or set \"soft pt regulator\" instead.\n";
         config.soft_pt_regulator = *config.max_ext_soft_pt_fraction;
       } else {
         set_from_yaml_if_defined(
             config.soft_pt_regulator, yaml, "soft pt regulator"
         );
       }
       // Sets the standard value, then changes this if defined
       config.regulator_lambda=CLAMBDA;
       set_from_yaml_if_defined(config.regulator_lambda, yaml, "regulator parameter");
       set_from_yaml_if_defined(config.max_events, yaml, "max events");
       set_from_yaml(config.trials, yaml, "trials");
       config.weight_type = WeightType::weighted;
       set_from_yaml_if_defined(config.weight_type, yaml, "unweight", "type");
       if(config.weight_type == WeightType::partially_unweighted) {
         config.unweight_config = PartialUnweightConfig{};
         set_from_yaml(
             config.unweight_config->trials, yaml,
             "unweight", "trials"
         );
         set_from_yaml(
             config.unweight_config->max_dev, yaml,
             "unweight", "max deviation"
         );
       }
       else if(yaml["unweight"].IsDefined()) {
         for(auto && opt: {"trials", "max deviation"}) {
           if(yaml["unweight"][opt].IsDefined()) {
             throw std::invalid_argument{
               "'unweight: " + std::string{opt} + "' "
               "is only supported if 'unweight: type' is set to 'partial'"
             };
           }
         }
       }
       set_from_yaml(config.log_correction, yaml, "log correction");
       config.treat = get_event_treatment(yaml, "event treatment");
       set_from_yaml_if_defined(config.output, yaml, "event output");
       config.rng = to_RNGConfig(yaml, "random generator");
       set_from_yaml_if_defined(config.lowpt, yaml, "require low pt jet");
 
       set_from_yaml_if_defined(config.analyses_parameters, yaml, "analyses");
       if(yaml["analysis"].IsDefined()){
         std::cerr <<
           "WARNING: Configuration entry 'analysis' is deprecated. "
           " Use 'analyses' instead.\n";
         set_from_yaml(config.analysis_parameters, yaml, "analysis");
         if(!config.analysis_parameters.IsNull()){
           config.analyses_parameters.push_back(config.analysis_parameters);
         }
       }
       config.scales = to_ScaleConfig(yaml);
       config.ew_parameters = get_ew_parameters(yaml);
       config.Higgs_coupling = get_Higgs_coupling(yaml, "Higgs coupling");
 
       //HEJ@NLO Truncation
       config.nlo = to_NLOConfig(yaml, "NLO truncation");
       set_from_yaml_if_defined(
         config.off_shell_tolerance,
         yaml,
         "off-shell tolerance"
       );
       return config;
     }
 
   } // namespace
 
   ScaleConfig to_ScaleConfig(YAML::Node const & yaml){
     ScaleConfig config;
     auto scale_funs = get_scale_map(yaml);
     std::vector<std::string> scales;
     set_from_yaml(scales, yaml, "scales");
     config.base.reserve(scales.size());
     std::transform(
         begin(scales), end(scales), std::back_inserter(config.base),
         [scale_funs](auto const & entry){
           return parse_ScaleFunction(entry, scale_funs);
         }
     );
     set_from_yaml_if_defined(config.factors, yaml, "scale factors");
     config.max_ratio = std::numeric_limits<double>::infinity();
     set_from_yaml_if_defined(config.max_ratio, yaml, "max scale ratio");
     return config;
   }
 
   Config load_config(std::string const & config_file){
     try{
       return to_Config(YAML::LoadFile(config_file));
     }
     catch(...){
       std::cerr << "Error reading " << config_file << ":\n  ";
       throw;
     }
   }
 
 } // namespace HEJ
diff --git a/t/check_res.cc b/t/check_res.cc
index b01e9db..6445d36 100644
--- a/t/check_res.cc
+++ b/t/check_res.cc
@@ -1,153 +1,151 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "hej_test.hh"
 
 #include <algorithm>
 #include <cmath>
 #include <cstdlib>
 #include <iostream>
 #include <iterator>
 #include <memory>
 #include <string>
 #include <utility>
 
 #include "HEJ/Config.hh"
 #include "HEJ/CrossSectionAccumulator.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/EventReweighter.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/Fraction.hh"
 #include "HEJ/HiggsCouplingSettings.hh"
 #include "HEJ/make_RNG.hh"
 #include "HEJ/Mixmax.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/EventReader.hh"
 #include "HEJ/YAMLreader.hh"
 
 #include "fastjet/JetDefinition.hh"
 
 #include "LHEF/LHEF.h"
 
 namespace HEJ { struct RNG; }
 
 namespace {
   using EventTreatment = HEJ::EventTreatment;
   using namespace HEJ::event_type;
   HEJ::EventTreatMap TREAT{
-    {not_enough_jets, EventTreatment::discard},
-    {bad_final_state, EventTreatment::discard},
     {non_resummable, EventTreatment::discard},
     {unof, EventTreatment::discard},
     {unob, EventTreatment::discard},
     {qqbar_exb, EventTreatment::discard},
     {qqbar_exf, EventTreatment::discard},
     {qqbar_mid, EventTreatment::discard},
     {FKL, EventTreatment::reweight}
   };
 
   bool correct_colour(HEJ::Event const & ev){
     if(!HEJ::event_type::is_resummable(ev.type()))
       return true;
     return ev.is_leading_colour();
   }
 } // namespace
 
 int main(int argn, char** argv) {
 
   if(argn == 6 && std::string(argv[5]) == "unof"){
     --argn;
     TREAT[unof] = EventTreatment::reweight;
     TREAT[unob] = EventTreatment::discard;
     TREAT[FKL] = EventTreatment::discard;
   }
   else if(argn == 6 && std::string(argv[5]) == "unob"){
     --argn;
     TREAT[unof] = EventTreatment::discard;
     TREAT[unob] = EventTreatment::reweight;
     TREAT[FKL] = EventTreatment::discard;
   }
   else if(argn == 6 && std::string(argv[5]) == "splitf"){
     --argn;
     TREAT[qqbar_exb] = EventTreatment::discard;
     TREAT[qqbar_exf] = EventTreatment::reweight;
     TREAT[FKL] = EventTreatment::discard;
   }
   else if(argn == 6 && std::string(argv[5]) == "splitb"){
     --argn;
     TREAT[qqbar_exb] = EventTreatment::reweight;
     TREAT[qqbar_exf] = EventTreatment::discard;
     TREAT[FKL] = EventTreatment::discard;
   }
   else if(argn == 6 && std::string(argv[5]) == "qqbar_mid"){
     --argn;
     TREAT[qqbar_mid] = EventTreatment::reweight;
     TREAT[FKL] = EventTreatment::discard;
   }
   if(argn != 5){
     std::cerr << "Usage: check_res yaml eventfile xsection tolerance [uno] \n";
     return EXIT_FAILURE;
   }
 
   HEJ::Config config = HEJ::load_config(argv[1]);
   auto reader = HEJ::make_reader(argv[2]);
   const double xsec_ref = std::stod(argv[3]);
   const double tolerance = std::stod(argv[4]);
 
   reader->read_event();
 
   HEJ::EventReweighterConfig conf = HEJ::to_EventReweighterConfig(config);
   conf.treat = TREAT;
   HEJ::MatrixElementConfig ME_conf = HEJ::to_MatrixElementConfig(config);
   HEJ::PhaseSpacePointConfig psp_conf = HEJ::to_PhaseSpacePointConfig(config);
   const fastjet::JetDefinition BORN_JET_DEF = config.fixed_order_jets.def;
   const double BORN_JETPTMIN = config.fixed_order_jets.min_pt;
   const int NUM_TRIES = config.trials;
   HEJ::ScaleGenerator scale_gen{
     config.scales.base,
     config.scales.factors,
     config.scales.max_ratio
   };
   std::shared_ptr<HEJ::RNG> ran{
     HEJ::make_RNG(config.rng.name,config.rng.seed)
   };
 
 
   HEJ::EventReweighter hej{reader->heprup(), std::move(scale_gen), conf, ran};
 
   HEJ::CrossSectionAccumulator xs;
   do{
     auto ev_data = HEJ::Event::EventData{reader->hepeup()};
     shuffle_particles(ev_data);
     ev_data.reconstruct_intermediate(ME_conf.ew_parameters);
     HEJ::Event ev{
       ev_data.cluster(
         BORN_JET_DEF, BORN_JETPTMIN
       )
     };
     auto resummed_events = hej.reweight(ev, NUM_TRIES);
     for(auto const & res_ev: resummed_events) {
       ASSERT(correct_colour(res_ev));
       ASSERT(std::isfinite(res_ev.central().weight));
       // we fill the xs uncorrelated since we only want to test the uncertainty
       // of the resummation
       xs.fill(res_ev);
     }
   } while(reader->read_event());
   const double xsec = xs.total().value;
   const double xsec_err = std::sqrt(xs.total().error);
   const double significance =
     std::abs(xsec - xsec_ref) / std::sqrt( xsec_err*xsec_err + tolerance*tolerance );
   std::cout << xsec_ref << " +/- " << tolerance << " ~ "
     << xsec << " +- " << xsec_err << " => " << significance << " sigma\n";
 
   if(significance > 3.){
     std::cerr << "Cross section is off by over 3 sigma!\n";
     return EXIT_FAILURE;
   }
   return EXIT_SUCCESS;
 }
diff --git a/t/hej_test.cc b/t/hej_test.cc
index 4808f29..4aa8b58 100644
--- a/t/hej_test.cc
+++ b/t/hej_test.cc
@@ -1,683 +1,642 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019-2020
+ *  \date      2019-2022
  *  \copyright GPLv2 or later
  */
 #include "hej_test.hh"
 
 #include <algorithm>
 #include <cmath>
 #include <cstddef>
 #include <cstdlib>
 #include <iterator>
 #include <memory>
 #include <numeric>
 #include <random>
 #include <utility>
 
 #include "HEJ/EWConstants.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDG_codes.hh"
+#include "HEJ/utility.hh"
 
 namespace {
   const HEJ::ParticleProperties Wprop{80.385, 2.085};
   const HEJ::ParticleProperties Zprop{91.187, 2.495};
   const HEJ::ParticleProperties Hprop{125,    0.004165};
   constexpr double vev = 246.2196508;
   const HEJ::EWConstants ew_parameters{vev, Wprop, Zprop, Hprop};
 }
 
 HEJ::Event::EventData get_process(int const njet, int const pos_boson){
   using namespace HEJ::pid;
   HEJ::Event::EventData ev;
   if(njet == 0){
     switch(pos_boson){
     case 0:
       ev.outgoing.push_back({higgs, {   0,    0,   44,  132}, {}});
-      ev.incoming[0] = {gluon, {   0,    0,  -44,   44}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,   88,   88}, {}};
       return ev;
     default: // jet idx: -1 -1
       ev.outgoing.push_back({gluon, { -24,   12,  -57,   63}, {}});
       ev.outgoing.push_back({gluon, {  24,  -12,   41,   49}, {}});
-      ev.incoming[0] = {gluon, {   0,    0,  -64,   64}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,   48,   48}, {}};
       return ev;
     }
   }
   if(njet == 1){
     switch(pos_boson){
     case 0:
       ev.outgoing.push_back({higgs, {  16,  -32,  -99,  163}, {}});
       ev.outgoing.push_back({gluon, { -16,   32,   76,   84}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -135,  135}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  112,  112}, {}};
       return ev;
     case 1:
       ev.outgoing.push_back({gluon, { -92,   84,  -57,  137}, {}});
       ev.outgoing.push_back({higgs, {  92,  -84,  -79,  193}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -233,  233}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,   97,   97}, {}};
       return ev;
     default: // jet idx: 0 -1 -1
       ev.outgoing.push_back({gluon, {  23,   28,  -44,   57}, {}});
       ev.outgoing.push_back({gluon, { -11,  -24,  -12,   29}, {}});
       ev.outgoing.push_back({gluon, { -12,   -4,   39,   41}, {}});
-      ev.incoming[0] = {gluon, {   0,    0,  -72,   72}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,   55,   55}, {}};
       return ev;
     }
   }
   if(njet == 2){
     switch(pos_boson){
     case 0:
       ev.outgoing.push_back({higgs, { 198,   33, -170,  291}, {}});
       ev.outgoing.push_back({gluon, {-154,   68,   44,  174}, {}});
       ev.outgoing.push_back({gluon, { -44, -101,   88,  141}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -322,  322}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  284,  284}, {}};
       return ev;
     case 1:
       ev.outgoing.push_back({gluon, {  -6,   82, -159,  179}, {}});
       ev.outgoing.push_back({higgs, { 195, -106,   74,  265}, {}});
       ev.outgoing.push_back({gluon, {-189,   24,  108,  219}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -320,  320}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  343,  343}, {}};
       return ev;
     case 2:
       ev.outgoing.push_back({gluon, { -80,  -80, -140,  180}, {}});
       ev.outgoing.push_back({gluon, { -60,  -32,    0,   68}, {}});
       ev.outgoing.push_back({higgs, { 140,  112,  177,  281}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -246,  246}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  283,  283}, {}};
       return ev;
     default:
       ev.outgoing.push_back({gluon, { -72,   24,   18,   78}, {}});
       ev.outgoing.push_back({gluon, {  72,  -24,   74,  106}, {}});
-      ev.incoming[0] = {gluon, {   0,    0,  -46,   46}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  138,  138}, {}};
       return ev;
     }
   }
   if(njet == 3){
     switch(pos_boson){
     case 0:
       ev.outgoing.push_back({higgs, { 152, -117,  -88,  245}, {}});
       ev.outgoing.push_back({gluon, {-146,   62,  -11,  159}, {}});
       ev.outgoing.push_back({gluon, { 126,  -72,   96,  174}, {}});
       ev.outgoing.push_back({gluon, {-132,  127,  144,  233}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -335,  335}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  476,  476}, {}};
       return ev;
     case 1:
       ev.outgoing.push_back({gluon, {-191,  188, -128,  297}, {}});
       ev.outgoing.push_back({higgs, { 199,   72,  -76,  257}, {}});
       ev.outgoing.push_back({gluon, { 184, -172,   -8,  252}, {}});
       ev.outgoing.push_back({gluon, {-192,  -88,   54,  218}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -591,  591}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  433,  433}, {}};
       return ev;
     case 2:
       ev.outgoing.push_back({gluon, { -42,   18,  -49,   67}, {}});
       ev.outgoing.push_back({gluon, { -12,  -54,  -28,   62}, {}});
       ev.outgoing.push_back({higgs, {  99,   32,  -16,  163}, {}});
       ev.outgoing.push_back({gluon, { -45,    4,   72,   85}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -199,  199}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  178,  178}, {}};
       return ev;
     case 3:
       ev.outgoing.push_back({gluon, { -65,  -32,  -76,  105}, {}});
       ev.outgoing.push_back({gluon, { -22,   31,  -34,   51}, {}});
       ev.outgoing.push_back({gluon, { -12,  -67,  -36,   77}, {}});
       ev.outgoing.push_back({higgs, {  99,   68,   -4,  173}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -278,  278}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  128,  128}, {}};
       return ev;
     default:
       ev.outgoing.push_back({gluon, { -90, -135,   30,  165}, {}});
       ev.outgoing.push_back({gluon, {-108,  198,   76,  238}, {}});
       ev.outgoing.push_back({gluon, { 198,  -63,  126,  243}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -207,  207}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  439,  439}, {}};
       return ev;
     }
   }
   if(njet == 4){
     switch(pos_boson){
     case 0:
       ev.outgoing.push_back({higgs, { 199,   72,  -76,  257}, {}});
       ev.outgoing.push_back({gluon, {-200, -155,  -64,  261}, {}});
       ev.outgoing.push_back({gluon, { 198,  194,   57,  283}, {}});
       ev.outgoing.push_back({gluon, {   1,   32,    8,   33}, {}});
       ev.outgoing.push_back({gluon, {-198, -143,  186,  307}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -515,  515}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  626,  626}, {}};
       return ev;
     case 1:
       ev.outgoing.push_back({gluon, { 198,   61, -162,  263}, {}});
       ev.outgoing.push_back({higgs, { 199,   72,  -76,  257}, {}});
       ev.outgoing.push_back({gluon, {-200,  135,  144,  281}, {}});
       ev.outgoing.push_back({gluon, {-198, -186,  171,  321}, {}});
       ev.outgoing.push_back({gluon, {   1,  -82,  122,  147}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -535,  535}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  734,  734}, {}};
       return ev;
     case 2:
       ev.outgoing.push_back({gluon, {-180,  -27, -164,  245}, {}});
       ev.outgoing.push_back({gluon, {-108,   78,  -36,  138}, {}});
       ev.outgoing.push_back({higgs, { 196, -189,   68,  307}, {}});
       ev.outgoing.push_back({gluon, {-107,  136,   76,  189}, {}});
       ev.outgoing.push_back({gluon, { 199,    2,  178,  267}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -512,  512}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  634,  634}, {}};
       return ev;
     case 3:
       ev.outgoing.push_back({gluon, { -12,  -30,  -84,   90}, {}});
       ev.outgoing.push_back({gluon, { -72,   22,  -96,  122}, {}});
       ev.outgoing.push_back({gluon, {  68,    0,  -51,   85}, {}});
       ev.outgoing.push_back({higgs, {  64,   72,  -81,  177}, {}});
       ev.outgoing.push_back({gluon, { -48,  -64,   84,  116}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -409,  409}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  181,  181}, {}};
       return ev;
     case 4:
       ev.outgoing.push_back({gluon, { -72,  -49,  -72,  113}, {}});
       ev.outgoing.push_back({gluon, { -48,    0,  -36,   60}, {}});
       ev.outgoing.push_back({gluon, { -12,   54,  -36,   66}, {}});
       ev.outgoing.push_back({gluon, {  68,  -77,  -56,  117}, {}});
       ev.outgoing.push_back({higgs, {  64,   72,  -81,  177}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -407,  407}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  126,  126}, {}};
       return ev;
     default:
       ev.outgoing.push_back({gluon, { 248,  -56, -122,  282}, {}});
       ev.outgoing.push_back({gluon, { 249,   30,  -10,  251}, {}});
       ev.outgoing.push_back({gluon, {-249,  -18,   26,  251}, {}});
       ev.outgoing.push_back({gluon, {-248,   44,  199,  321}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -506,  506}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  599,  599}, {}};
       return ev;
     }
   }
   if(njet == 6){
     switch(pos_boson){
     case 0:
       ev.outgoing.push_back({higgs, { 349,  330,  -94,  505}, {}});
       ev.outgoing.push_back({gluon, {-315, -300,    0,  435}, {}});
       ev.outgoing.push_back({gluon, { 347,  306,   18,  463}, {}});
       ev.outgoing.push_back({gluon, {-249, -342,  162,  453}, {}});
       ev.outgoing.push_back({gluon, { 345,  312,  284,  545}, {}});
       ev.outgoing.push_back({gluon, {-324, -126,  292,  454}, {}});
       ev.outgoing.push_back({gluon, {-153, -180,  304,  385}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -1137, 1137}, {}};
-      ev.incoming[1] = {gluon, {   0,    0, 2103, 2103}, {}};
       return ev;
     case 1:
       ev.outgoing.push_back({gluon, { 242,  241, -182,  387}, {}});
       ev.outgoing.push_back({higgs, { 243,  238, -190,  409}, {}});
       ev.outgoing.push_back({gluon, {-218, -215,  -74,  315}, {}});
       ev.outgoing.push_back({gluon, {-224, -224,  112,  336}, {}});
       ev.outgoing.push_back({gluon, { 241,  182,  154,  339}, {}});
       ev.outgoing.push_back({gluon, { -53, -234,  126,  271}, {}});
       ev.outgoing.push_back({gluon, {-231,   12,  156,  279}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -1117, 1117}, {}};
-      ev.incoming[1] = {gluon, {   0,    0, 1219, 1219}, {}};
       return ev;
     case 2:
       ev.outgoing.push_back({gluon, { 151,  102,  -42,  187}, {}});
       ev.outgoing.push_back({gluon, { -86,  -46,  -17,   99}, {}});
       ev.outgoing.push_back({higgs, { 152,  153,    0,  249}, {}});
       ev.outgoing.push_back({gluon, { -60, -135,   64,  161}, {}});
       ev.outgoing.push_back({gluon, { 150,  123,  110,  223}, {}});
       ev.outgoing.push_back({gluon, {-154,  -49,   98,  189}, {}});
       ev.outgoing.push_back({gluon, {-153, -148,  144,  257}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -504,  504}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  861,  861}, {}};
       return ev;
     case 3:
       ev.outgoing.push_back({gluon, { 198,  197,  -66,  287}, {}});
       ev.outgoing.push_back({gluon, {-198, -189,  -54,  279}, {}});
       ev.outgoing.push_back({gluon, {-200,  -64,    2,  210}, {}});
       ev.outgoing.push_back({higgs, { 199,  158,    6,  283}, {}});
       ev.outgoing.push_back({gluon, {-199, -184,  172,  321}, {}});
       ev.outgoing.push_back({gluon, { 196,  168,  177,  313}, {}});
       ev.outgoing.push_back({gluon, {   4,  -86,   92,  126}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -745,  745}, {}};
-      ev.incoming[1] = {gluon, {   0,    0, 1074, 1074}, {}};
       return ev;
     case 4:
       ev.outgoing.push_back({gluon, { 151,  102,  -42,  187}, {}});
       ev.outgoing.push_back({gluon, { -86, -133,  -14,  159}, {}});
       ev.outgoing.push_back({gluon, {-154, -104,   -8,  186}, {}});
       ev.outgoing.push_back({gluon, { -60,   11,    0,   61}, {}});
       ev.outgoing.push_back({higgs, { 152,  153,    0,  249}, {}});
       ev.outgoing.push_back({gluon, { 150,  125,   90,  215}, {}});
       ev.outgoing.push_back({gluon, {-153, -154,  126,  251}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -578,  578}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  730,  730}, {}};
       return ev;
     case 5:
       ev.outgoing.push_back({gluon, { -15,  -90,  -94,  131}, {}});
       ev.outgoing.push_back({gluon, { -11,   82,  -74,  111}, {}});
       ev.outgoing.push_back({gluon, {  23,  -80,  -64,  105}, {}});
       ev.outgoing.push_back({gluon, { -48,  -25,  -36,   65}, {}});
       ev.outgoing.push_back({gluon, { -12,   99,  -16,  101}, {}});
       ev.outgoing.push_back({higgs, {  68,   92,  -18,  170}, {}});
       ev.outgoing.push_back({gluon, {  -5,  -78,   54,   95}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -513,  513}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  265,  265}, {}};
       return ev;
     case 6:
       ev.outgoing.push_back({gluon, { 198,  197,  -66,  287}, {}});
       ev.outgoing.push_back({gluon, {   4,  -84,  -18,   86}, {}});
       ev.outgoing.push_back({gluon, {-198,  -60,  -36,  210}, {}});
       ev.outgoing.push_back({gluon, { 196,  -78,  -36,  214}, {}});
       ev.outgoing.push_back({gluon, {-200,   45,    0,  205}, {}});
       ev.outgoing.push_back({gluon, {-199, -178,    2,  267}, {}});
       ev.outgoing.push_back({higgs, { 199,  158,    6,  283}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -850,  850}, {}};
-      ev.incoming[1] = {gluon, {   0,    0,  702,  702}, {}};
       return ev;
     default:
       ev.outgoing.push_back({gluon, {-350, -112, -280,  462}, {}});
       ev.outgoing.push_back({gluon, { 347,  266, -322,  543}, {}});
       ev.outgoing.push_back({gluon, {-349, -314,  -38,  471}, {}});
       ev.outgoing.push_back({gluon, { 349,  348,   12,  493}, {}});
       ev.outgoing.push_back({gluon, {-342,  -54,   23,  347}, {}});
       ev.outgoing.push_back({gluon, { 345, -134,  138,  395}, {}});
-      ev.incoming[0] = {gluon, {   0,    0, -1589, 1589}, {}};
-      ev.incoming[1] = {gluon, {   0,    0, 1122, 1122}, {}};
       return ev;
     }
   }
   if(njet == 7){
     switch(pos_boson){
       case -1: // jet idx: -1 0 1 2 3 4 5
         ev.outgoing.push_back({gluon, { -15,  -18,  -54,   59}, {}});
         ev.outgoing.push_back({gluon, { -11,   98,  -70,  121}, {}});
         ev.outgoing.push_back({gluon, {  23, -100,  -64,  121}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,  -20,  117}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -92,  -12,   93}, {}});
         ev.outgoing.push_back({gluon, { -48,  -76,   -2,   90}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -439,  439}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  273,  273}, {}};
         return ev;
       case -2: // jet idx: 0 1 2 3 4 -1 -1
         ev.outgoing.push_back({gluon, {  -5,  -86,  -82,  119}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, { -48,  -14,   20,   54}, {}});
         ev.outgoing.push_back({gluon, {  23,  -50,   26,   61}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
         ev.outgoing.push_back({gluon, { -15,  -18,   54,   59}, {}});
         ev.outgoing.push_back({gluon, { -11,  -20,   88,   91}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -215,  215}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  397,  397}, {}};
         return ev;
       case -3: // jet idx: 0 0 1 2 2 3 4
         // jet pt fraction: 0.6 0.38 1 0.49 0.51 1 1
         ev.outgoing.push_back({gluon, {  23,  -94,  -62, 1.2e+02}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -62,  -34,   71}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20, 1.2e+02}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56, 1.1e+02}, {}});
         ev.outgoing.push_back({gluon, { -11,   98,   70, 1.2e+02}, {}});
         ev.outgoing.push_back({gluon, { -48, -1e+02,   82, 1.4e+02}, {}});
         ev.outgoing.push_back({gluon, { -15,  -30,   78,   85}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -2.7e+02, 2.7e+02}, {}};
-        ev.incoming[1] = {gluon, {   0,    0, 4.8e+02, 4.8e+02}, {}};
         return ev;
       case -4: // jet idx: 0 1 1 2 3 4 4
         // jet pt fraction: 1 0.51 0.49 1 1 0.25 0.75
         ev.outgoing.push_back({gluon, {  -5,  -88,  -64,  109}, {}});
         ev.outgoing.push_back({gluon, { -11,   98,  -70,  121}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,  -56,  111}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, {  23,  -70,   22,   77}, {}});
         ev.outgoing.push_back({gluon, { -15,  -32,   16,   39}, {}});
         ev.outgoing.push_back({gluon, { -48,  -96,   75,  131}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -381,  381}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  324,  324}, {}};
         return ev;
       case -5: // jet idx: 0 1 -1 -1 2 3 4
         ev.outgoing.push_back({gluon, { -15,  -26,  -62,   69}, {}});
         ev.outgoing.push_back({gluon, { -48,  -60,  -54,   94}, {}});
         ev.outgoing.push_back({gluon, {  23,   10,  -14,   29}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -20,    0,   21}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, { -11,  -92,   40,  101}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -278,  278}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  264,  264}, {}};
         return ev;
       case -6: // jet idx: 0 1 1 2 -1 2 3
         // jet pt fraction: 1 0.63 0.36 0.49 1 0.51 1
         ev.outgoing.push_back({gluon, {  68,   93,  -20,  117}, {}});
         ev.outgoing.push_back({gluon, { -48, -100,   26,  114}, {}});
         ev.outgoing.push_back({gluon, { -15,  -62,   26,   69}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -28,   20,   35}, {}});
         ev.outgoing.push_back({gluon, { -11,   98,   70,  121}, {}});
         ev.outgoing.push_back({gluon, {  23,  -96,   92,  135}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -216,  216}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  486,  486}, {}};
         return ev;
       case -7: // jet idx: 0 1 2 2 3 3 4
         // jet pt fraction: 1 1 0.51 0.49 0.18 0.82 1
         ev.outgoing.push_back({gluon, { -15,  -80, -100,  129}, {}});
         ev.outgoing.push_back({gluon, {  23,  -96,  -92,  135}, {}});
         ev.outgoing.push_back({gluon, { -11,   98,  -70,  121}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,  -56,  111}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -22,  -10,   25}, {}});
         ev.outgoing.push_back({gluon, { -48,  -88,  -31,  105}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -541,  541}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  202,  202}, {}};
         return ev;
       case -8: // jet idx: 0 1 2 2 2 3 4
         // jet pt fraction: 1 1 0.21 0.37 0.41 1 1
         ev.outgoing.push_back({gluon, { -48,  -44,  -62,   90}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,  -56,  111}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -50,  -22,   55}, {}});
         ev.outgoing.push_back({gluon, {  23,  -90,  -34,   99}, {}});
         ev.outgoing.push_back({gluon, { -15, -100,  -28,  105}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,  -20,  117}, {}});
         ev.outgoing.push_back({gluon, { -11,   96,   76,  123}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -423,  423}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  277,  277}, {}};
         return ev;
       case -9: // jet idx: 0 1 2 1 3 0 4
         // jet pt fraction: 0.72 0.51 1 0.49 1 0.28 1
         ev.outgoing.push_back({gluon, { -15,  -98,  -62,  117}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,  -56,  111}, {}});
         ev.outgoing.push_back({gluon, {  23,  -76,  -40,   89}, {}});
         ev.outgoing.push_back({gluon, { -11,   92,  -40,  101}, {}});
         ev.outgoing.push_back({gluon, { -48,  -68,  -34,   90}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -38,  -14,   41}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -446,  446}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  220,  220}, {}};
         return ev;
       case -10: // jet idx: 0 1 3 2 4 3 1
         // jet pt fraction: 1 0.33 0.51 1 1 0.49 0.67
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -48,   16,   51}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
         ev.outgoing.push_back({gluon, {  23,  -76,   52,   95}, {}});
         ev.outgoing.push_back({gluon, { -48,  -60,   54,   94}, {}});
         ev.outgoing.push_back({gluon, { -11,   92,   68,  115}, {}});
         ev.outgoing.push_back({gluon, { -15,  -96,   72,  121}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -183,  183}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  521,  521}, {}};
         return ev;
       case -11: // jet idx: 0 1 2 3 4 -1 5
         // jet pt fraction: 1 1 1 1 1 1 1
         ev.outgoing.push_back({gluon, { -11,   98,  -70,  121}, {}});
         ev.outgoing.push_back({gluon, { -15,  -98,  -62,  117}, {}});
         ev.outgoing.push_back({gluon, {  23,  -90,   -2,   93}, {}});
         ev.outgoing.push_back({gluon, { -48,  -76,    2,   90}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -22,   10,   25}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -360,  360}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  314,  314}, {}};
         return ev;
       case -12: // jet idx: 0 1 -1 2 3 4 3
         // jet pt fraction: 1 1 1 1 0.35 1 0.65
         ev.outgoing.push_back({gluon, {  23,  -94,  -62,  115}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,  -56,  111}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -28,    4,   29}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, { -15,  -58,   34,   69}, {}});
         ev.outgoing.push_back({gluon, { -11,   92,   68,  115}, {}});
         ev.outgoing.push_back({gluon, { -48, -100,   82,  138}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -302,  302}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  392,  392}, {}};
         return ev;
       case -13: // jet idx: 0 1 2 3 3 4 2
         // jet pt fraction: 1 1 0.5 0.35 0.65 1 0.5
         ev.outgoing.push_back({gluon, { -15,  -98,  -62,  117}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -28,   20,   35}, {}});
-        ev.outgoing.push_back({gluon, { -48,  -96,   75,  131}, {}});
+        ev.outgoing.push_back({gluon, { -48,  -96,   77,  131}, {}});
         ev.outgoing.push_back({gluon, {  23,  -62,   50,   83}, {}});
         ev.outgoing.push_back({gluon, { -11,   96,   76,  123}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -241,  241}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  476,  476}, {}};
         return ev;
       case -14: // jet idx: 0 1 2 3 3 4 2
         // jet pt fraction: 1 1 0.52 0.35 0.65 1 0.48
         ev.outgoing.push_back({gluon, {  23,  -94,  -62,  115}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,   56,  111}, {}});
         ev.outgoing.push_back({gluon, { -15,  -96,   72,  121}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -42,   38,   57}, {}});
         ev.outgoing.push_back({gluon, { -48,  -44,   62,   90}, {}});
         ev.outgoing.push_back({gluon, { -11,   88,   88,  125}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -231,  231}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  505,  505}, {}};
         return ev;
       case -15: // jet idx: 0 -1 1 0 2 3 4
         // jet pt fraction: 0.51 1 1 0.49 1 1 1
-        ev.outgoing.push_back({gluon, { -11,   98,  -70,  121}, {}});
+        ev.outgoing.push_back({gluon, { -11,   98,  -71,  121}, {}});
         ev.outgoing.push_back({gluon, {  -5,  -16,  -12,   21}, {}});
         ev.outgoing.push_back({gluon, {  23,  -94,  -62,  115}, {}});
         ev.outgoing.push_back({gluon, { -12,   95,  -56,  111}, {}});
         ev.outgoing.push_back({gluon, {  68,   93,   20,  117}, {}});
         ev.outgoing.push_back({gluon, { -48,  -76,   70,  114}, {}});
         ev.outgoing.push_back({gluon, { -15, -100,   80,  129}, {}});
-        ev.incoming[0] = {gluon, {   0,    0, -379,  379}, {}};
-        ev.incoming[1] = {gluon, {   0,    0,  349,  349}, {}};
         return ev;
     }
   }
   throw HEJ::unknown_option{"unknown process"};
 }
 
+double mass(HEJ::Particle const & p) {
+  static constexpr double M_PROTON = 0.938;
+  using namespace HEJ::pid;
+  switch(p.type){
+  case Higgs:
+    return Hprop.mass;
+  case Z:
+  case Z_photon_mix:
+    return Zprop.mass;
+  case Wm:
+  case Wp:
+    return Wprop.mass;
+  case proton:
+    return M_PROTON;
+  case bottom:
+  case antibottom:
+    return 0.;
+  default:
+    if(is_massless(p)) return 0.;
+    throw std::invalid_argument{
+      name(p.type) + " has unknown mass"
+    };
+  }
+}
+
+void repair_momenta(HEJ::Event::EventData & ev) {
+  double E_sum = 0., pz_sum = 0.;
+  for(auto & out: ev.outgoing) {
+    const double m = mass(out);
+    const double psq = out.px()*out.px() + out.py()*out.py() + out.pz()*out.pz();
+    const double E = sqrt(psq + m * m);
+    out.p.reset(out.px(), out.py(), out.pz(), E);
+    assert(HEJ::nearby_ep(out.p.m(), m, 1e-7*E));
+    E_sum += E;
+    pz_sum += out.pz();
+  }
+  const double p0 = (E_sum - pz_sum)/2.;
+  const double p1 = (E_sum + pz_sum)/2.;
+  ev.incoming[0].p.reset(0, 0, -p0, p0);
+  ev.incoming[1].p.reset(0, 0, +p1, p1);
+}
+
 HEJ::Event::EventData parse_configuration(
     std::array<std::string,2> const & in, std::vector<std::string> const & out,
     int const overwrite_boson
 ){
   auto boson = std::find_if(out.cbegin(), out.cend(),
     [](std::string const & id){
       return !HEJ::is_parton(HEJ::to_ParticleID(id)); });
   int const pos_boson = (overwrite_boson!=0)?overwrite_boson:
     ((boson == out.cend())?-1:std::distance(out.cbegin(), boson));
 
   std::size_t njets = out.size();
   if( (overwrite_boson == 0) && boson != out.cend()) --njets;
 
   HEJ::Event::EventData ev{get_process(njets, pos_boson)};
   ASSERT((pos_boson<0) || (ev.outgoing[pos_boson].type == HEJ::ParticleID::higgs));
   for(std::size_t i=0; i<out.size(); ++i){
     ev.outgoing[i].type = HEJ::to_ParticleID(out[i]);
+  }
+  for(std::size_t i=0; i<in.size(); ++i){
+    ev.incoming[i].type = HEJ::to_ParticleID(in[i]);
+  }
+  repair_momenta(ev);
+  for(std::size_t i = 0; i < out.size(); ++i) {
     // decay W
     if( std::abs(ev.outgoing[i].type) == HEJ::ParticleID::Wp )
       ev.decays[i]=decay_W(ev.outgoing[i]);
     // decay Z
     if( ev.outgoing[i].type == HEJ::ParticleID::Z_photon_mix )
       ev.decays[i]=decay_Z(ev.outgoing[i]);
   }
-  for(std::size_t i=0; i<in.size(); ++i){
-    ev.incoming[i].type = HEJ::to_ParticleID(in[i]);
-  }
   shuffle_particles(ev);
 
   return ev;
 }
 
-
 HEJ::Event::EventData rapidity_order_ps(
     std::array<std::string,2> const & in,
     std::vector<std::string> const & out,
     bool reconstruct /* = false */,
     std::unordered_map< size_t, std::vector<std::string> > decays /* = {} */,
     bool shuffle
   ) {
   using namespace HEJ::pid;
   HEJ::Event::EventData evd;
 
   const size_t n_out {out.size()};
 
   // Arbitrary non-zero mT2 shift in lieu of mass property of Z_photon_mix
   constexpr double arbitrary_mT2 = 80.*80.;
   // Arbitrary Parameters
   constexpr double pT {40.};
   constexpr double start_y {-4.5};
   constexpr double min_dy {0.5};  // chosen to be above typical jet-radius
   const double dy {std::max(-2.*start_y/(n_out-1), min_dy)};
 
   // Kinematic parameters
   std::array<double,2> cs_phi {1., 0.};
   std::array<double,2> cs_dphi {cos(2.*M_PI/n_out), sin(2.*M_PI/n_out)};
   double y {start_y};
 
   double sum_pE {0.};
   double sum_pz {0.};
   // Outgoing
   evd.outgoing.reserve(n_out);
   for(size_t i = 0; i < n_out; ++i) {
     auto pid = HEJ::to_ParticleID(out[i]);
     // Kinematics
     double mT2 = pT*pT;
     if(HEJ::is_AWZH_boson(pid)) {
       // Z_photon_mix has no properties
       if(pid == Z_photon_mix) { mT2 += arbitrary_mT2; }
       // regular bosons
       else {
         auto const ref = ew_parameters.prop(pid);
         mT2 += ref.mass*ref.mass;
       }
     }
     double pz {sqrt(mT2)*sinh(y)};
     double pE {sqrt(mT2)*cosh(y)};
     evd.outgoing.push_back({pid, {pT*cs_phi[0], pT*cs_phi[1], pz, pE}, {}});
     sum_pE+=pE;
     sum_pz+=pz;
     // Update cos,sin, y
     cs_phi = {cs_phi[0] * cs_dphi[0] - cs_phi[1] * cs_dphi[1],
               cs_phi[1] * cs_dphi[0] + cs_phi[0] * cs_dphi[1]};
     y += dy;
   }
 
   // Specified decays
   evd.decays.reserve(decays.size());
   for(auto const & decay : decays) {
     auto const parent = evd.outgoing.at(decay.first);
     std::vector<HEJ::Particle> progeny = decay_kinematics(parent);
     for(std::size_t i = 0; i < decay.second.size(); ++i) {
       progeny[i].type = HEJ::to_ParticleID(decay.second[i]);
     }
     evd.decays.emplace(decay.first, std::move(progeny));
   }
 
   // Reconstruct decays
   if(reconstruct) { evd.reconstruct_intermediate(ew_parameters); }
 
   // Incoming
-  double p0 {0.5*(sum_pE + sum_pz)};
+  double p0 {0.5*(sum_pE - sum_pz)};
   evd.incoming[0] = {HEJ::to_ParticleID(in[0]),{0,0,-p0,p0},{}};
-  double p1 {0.5*(sum_pE - sum_pz)};
+  double p1 {0.5*(sum_pE + sum_pz)};
   evd.incoming[1] = {HEJ::to_ParticleID(in[1]),{0,0,+p1,p1},{}};
 
   // Shuffle
   if(shuffle) { shuffle_particles(evd); }
   return evd;
 }
 
 namespace {
   std::mt19937_64 RAN{0};
 }
 
 void shuffle_particles(HEJ::Event::EventData & ev) {
   // incoming
   std::shuffle(ev.incoming.begin(), ev.incoming.end(), RAN);
   // outgoing (through index)
   auto old_outgoing = std::move(ev).outgoing;
   std::vector<std::size_t> idx(old_outgoing.size());
   std::iota(idx.begin(), idx.end(), 0);
   std::shuffle(idx.begin(), idx.end(), RAN);
   ev.outgoing.clear();
   ev.outgoing.reserve(old_outgoing.size());
   for(std::size_t i: idx) {
     ev.outgoing.emplace_back(std::move(old_outgoing[i]));
   }
   // find decays again
   if(!ev.decays.empty()){
     auto old_decays = std::move(ev).decays;
     ev.decays.clear();
     for(std::size_t i=0; i<idx.size(); ++i) {
       auto decay = old_decays.find(idx[i]);
       if(decay != old_decays.end())
         ev.decays.emplace(i, std::move(decay->second));
     }
     for(auto & decay: ev.decays){
       std::shuffle(decay.second.begin(), decay.second.end(), RAN);
     }
   }
 }
 
 bool couple_quark(std::string const & boson, std::string & quark){
   if(std::abs(HEJ::to_ParticleID(boson)) == HEJ::ParticleID::Wp){
     auto qflav{ HEJ::to_ParticleID(quark) };
     if(!HEJ::is_anyquark(qflav)) return false;
     const int W_charge = HEJ::to_ParticleID(boson)>0?1:-1;
     if(W_charge*qflav < 0 && !(std::abs(qflav)%2)) return false; // not anti-down
     if(W_charge*qflav > 0 &&  (std::abs(qflav)%2)) return false; // not up
     quark=std::to_string(qflav-W_charge);
   }
   if(HEJ::to_ParticleID(boson) == HEJ::ParticleID::Z_photon_mix){
     auto qflav{ HEJ::to_ParticleID(quark) };
     if(!HEJ::is_anyquark(qflav)) return false;
   }
   return true;
 }
 
 std::vector<HEJ::Particle> decay_kinematics( HEJ::Particle const & parent ) {
   std::vector<HEJ::Particle> decay_products(2);
   const double E = parent.m()/2;
   const double theta = 2.*M_PI*RAN()/static_cast<double>(RAN.max());
   const double cos_phi = 2.*RAN()/static_cast<double>(RAN.max())-1.;
   const double sin_phi = std::sqrt(1. - cos_phi*cos_phi);  // Know 0 < phi < pi
   const double px = E*std::cos(theta)*sin_phi;
   const double py = E*std::sin(theta)*sin_phi;
   const double pz = E*cos_phi;
   decay_products[0].p.reset(px, py, pz, E);
   decay_products[1].p.reset(-px, -py, -pz, E);
   for(auto & particle: decay_products) particle.p.boost(parent.p);
   return decay_products;
 }
 
 std::vector<HEJ::Particle> decay_W( HEJ::Particle const & parent ){
   if(parent.m() == 0.) // we can't decay massless partons
     return {};
   std::array<HEJ::ParticleID, 2> decays{};
   if(parent.type==HEJ::ParticleID::Wp){
     // order matters: first particle, second anti
     decays[0] = HEJ::ParticleID::nu_e;
     decays[1] = HEJ::ParticleID::e_bar;
   } else {
     // this function is for testing: we don't check that parent==W boson
     decays[0] = HEJ::ParticleID::e;
     decays[1] = HEJ::ParticleID::nu_e_bar;
   }
   std::vector<HEJ::Particle> decay_products = decay_kinematics(parent);
   for(std::size_t i = 0; i < decay_products.size(); ++i){
     decay_products[i].type = decays[i];
   }
   return decay_products;
 }
 
 std::vector<HEJ::Particle> decay_Z( HEJ::Particle const & parent ){
   if(parent.m() == 0.) // we can't decay massless partons
     return {};
   std::array<HEJ::ParticleID, 2> decays{};
   // order matters: first particle, second anti
   decays[0] = HEJ::ParticleID::electron;
   decays[1] = HEJ::ParticleID::positron;
   std::vector<HEJ::Particle> decay_products = decay_kinematics(parent);
   for(std::size_t i = 0; i < decay_products.size(); ++i){
     decay_products[i].type = decays[i];
   }
   return decay_products;
 }
diff --git a/t/hej_test.hh b/t/hej_test.hh
index 74f00b0..a5e9d5a 100644
--- a/t/hej_test.hh
+++ b/t/hej_test.hh
@@ -1,97 +1,105 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <string>
 #include <vector>
 
 #include "HEJ/Event.hh"
 #include "HEJ/exceptions.hh"
 
 namespace HEJ {
   struct Particle;
 }
 
 //! throw error if condition not fulfilled
 #define ASSERT(x) if(!(x)) { \
     throw std::logic_error("Assertion '" #x "' failed."); \
   }
 
 //! throw error if prop is different between ev1 and ev2
 #define ASSERT_PROPERTY(ev1,ev2,prop) ASSERT((ev1).prop == (ev2).prop)
 
 //! throw error if condition not fulfilled
 #define ASSERT_THROW(x, exception) try { \
       x; \
       std::cerr << "'" #x "' did not throw an exception.\n"; \
       throw; \
     } catch(exception const &){} \
     catch (...) { \
       std::cerr << "Unexpected exception thrown for '" #x "'.\n"; \
       throw; \
     }
 
 /** @brief get specific Phase Space Points for njets with boson at pos_boson
  *
  * if pos_boson = -1 (or not implemented) -> no boson
  *
  * njet==7 is special: has less jets, i.e. multiple parton in one jet,
  *                     all partons are massive (4 GeV) -> can be boson/decay
  *                     pos_boson < 0 to select process (see list for details)
  */
 HEJ::Event::EventData get_process(int njet, int pos_boson);
 
 //! select process from string input (see also get_process)
 //!
 //! overwrite_boson to force a specific boson position, indepentent from input
 //! (useful for njet == 7)
 HEJ::Event::EventData parse_configuration(
     std::array<std::string,2> const & in, std::vector<std::string> const & out,
     int overwrite_boson = 0
 );
 
+/** repair moment in event:
+ *  - adjust energies so that the ougoing particles are on-shell
+ *  - update incoming four-momenta to ensure momentum conservation
+ *
+ *  *does not adjust decays*
+ */
+void repair_momenta(HEJ::Event::EventData & ev);
+
 /**
  * @brief Generate a rapidity ordered event (with decays)
  * @param in             Incoming particle flavours
  * @param out            Outgoing particle flavours
  * @param reconstruct    Should call reconstruct_intermediate?
  * @param decays         Any decays associated with particles from out
  * @param shuffle        Perform shuffle on outgoing vector?
  * @returns              EventData for in -> out with rapidity ordered final state
  *
  * Generates EventData for the process in -> out. The outgoing particles
  * are generated in rapidity order. The incoming flavours are ordered:
  * {backward, forward}.
  *
  * Decays are provided by decays, if specificed, and via reconstruct_intermediate
  * if requested.
  *
  * Note: Specified decays are handled before reconstruct_intermediate is called.
  */
 
 HEJ::Event::EventData rapidity_order_ps(
     std::array<std::string,2> const & in,
     std::vector<std::string> const & out,
     bool reconstruct = false,
     std::unordered_map<size_t, std::vector<std::string> > decays = {},
     bool shuffle = true
   );
 
 //! shuffle particles around
 void shuffle_particles(HEJ::Event::EventData & ev);
 
 //! Helper function to couple quarksfor flavour-changing bosons
 bool couple_quark(std::string const & boson, std::string & quark);
 
 //! Decay kinematics for 1->2
 std::vector<HEJ::Particle> decay_kinematics( HEJ::Particle const & parent );
 
 //! Decay W boson to lepton & neutrino
 std::vector<HEJ::Particle> decay_W( HEJ::Particle const & parent );
 
 //! Decay Z to electron-positron
 std::vector<HEJ::Particle> decay_Z( HEJ::Particle const & parent );
diff --git a/t/test_classify.cc b/t/test_classify.cc
index b09c6a0..30e5c90 100644
--- a/t/test_classify.cc
+++ b/t/test_classify.cc
@@ -1,641 +1,693 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "hej_test.hh"
 
 #include <array>
 #include <cstdlib>
 #include <iostream>
 #include <random>
 #include <string>
+#include <unordered_set>
 #include <vector>
 
 #include "fastjet/JetDefinition.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/PDG_codes.hh"
 
 namespace {
   const fastjet::JetDefinition JET_DEF{fastjet::JetAlgorithm::antikt_algorithm, 0.4};
   const double MIN_JET_PT{30.};
 
   const std::vector<std::string> ALL_QUARKS{"-4","-1","1","2","3","4"};
   const std::vector<std::string> ALL_PARTONS{"g","-2","-1","1","2","3","4"};
   const std::vector<std::string> ALL_BOSONS{"h", "Wp", "Wm", "Z_photon_mix"};
   const std::vector<std::string> ALL_G_Z{"photon", "Z"};
   const std::vector<std::string> ALL_W{"W+", "W-"};
   const std::size_t MAX_MULTI = 6;
 
   std::mt19937_64 RAN{0};
 
+  bool is_compatible(
+    const HEJ::event_type::EventType found,
+    const HEJ::event_type::EventType expected
+  ) {
+    return found == expected;
+  }
+
+  bool is_compatible(
+    const HEJ::event_type::EventType found,
+    std::unordered_set<HEJ::event_type::EventType> const & expected
+  ) {
+    return expected.count(found) > 0;
+  }
+
+  std::string name(
+    std::unordered_set<HEJ::event_type::EventType> const & expected
+  ) {
+    if(expected.empty()) return "";
+    auto it = expected.begin();
+    std::string res = name(*it);
+    for(++it; it != expected.end(); ++it) {
+      res += " or " + name(*it);
+    }
+    return res;
+  }
+
   //! Upon clustering does EventData match the expected classification
-  bool match_expectation(HEJ::event_type::EventType expected,
-                         HEJ::Event::EventData evd
+  template<class T>
+  bool match_expectation(
+    T const & expected,
+    HEJ::Event::EventData evd
   ){
     HEJ::Event ev { evd.cluster(JET_DEF, MIN_JET_PT) };
-    if(ev.type() != expected){
+    if(!is_compatible(ev.type(), expected)){
       std::cerr << "Expected type " << name(expected)
                 << " but found " << name(ev.type()) << "\n" << ev;
       auto jet_idx{ ev.particle_jet_indices() };
       std::cout << "Particle Jet indices: ";
       for(int const i: jet_idx)
         std::cout << i << " ";
       std::cout << std::endl;
       return false;
     }
     return true;
   }
 
   //! Does match_expectation for EventData from parse_configuration
-  bool match_expectation( HEJ::event_type::EventType expected,
+  template<class T>
+  bool match_expectation(
+    T const & expected,
     std::array<std::string,2> const & in, std::vector<std::string> const & out,
       int const overwrite_boson = 0
   ){
     return match_expectation(expected, parse_configuration( in,out,overwrite_boson ));
   }
 
   //! test FKL configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_fkl( bool const implemented=true ){
     using namespace HEJ;
-    auto const type{ implemented?event_type::FKL:event_type::non_resummable };
+    auto const type{ implemented?event_type::FKL:event_type::unknown };
     std::vector<std::string> bosons;
     if(implemented)
       bosons = ALL_BOSONS;
     else {
       bosons = ALL_G_Z;
     }
     for(std::string const & first: ALL_PARTONS)       // all quark flavours
       for(std::string const & last: ALL_PARTONS){
         for(std::size_t njet=1; njet<=MAX_MULTI; ++njet){  // all multiplicities
           if(njet==5) continue;
           std::array<std::string,2> base_in{first,last};
           std::vector<std::string> base_out(njet, "g");
           if(njet>1){ // pure jets only for >=2j
             base_out.front() = first;
             base_out.back() = last;
             if(implemented && !match_expectation(type, base_in, base_out))
               return false;
           }
           for(auto const & boson: bosons){         // any boson
             for(std::size_t pos=0; pos<=njet; ++pos){    // at any position
               if(njet == 1){
                 // special case: one jet only for g->h
                 if(boson != "h" || ((pos==0?first:last) != "g")){
                   continue;
                 } else {
                   base_out.front() = pos==0?last:first;
                 }
               }
               auto const & in{base_in};
               auto out{base_out};
               // change quark flavours for W
               const bool couple_idx
                 = std::uniform_int_distribution<int>{0,1}(RAN) != 0;
               if(!couple_quark(boson, couple_idx?out.back():out.front()))
                 continue;
               out.insert(out.begin()+pos, boson);
               if(!match_expectation(type, in, out))
                 return false;
             }
           }
         }
       }
     return true;
   }
 
   //! test unordered configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_uno( bool const implemented=true ){
     using namespace HEJ;
-    auto const b{ implemented?event_type::unob:event_type::non_resummable };
-    auto const f{ implemented?event_type::unof:event_type::non_resummable };
+    auto const b{ implemented?event_type::unob:event_type::unknown };
+    auto const f{ implemented?event_type::unof:event_type::unknown };
     std::vector<std::string> bosons;
     if(implemented) {
       bosons = ALL_BOSONS;
     } else {
       bosons = ALL_G_Z;
     }
     for(std::string const & uno: ALL_QUARKS)          // all quark flavours
       for(std::string const & fkl: ALL_PARTONS){
         for(std::size_t njet=3; njet<=MAX_MULTI; ++njet){  // all multiplicities >2
           if(njet==5) continue;
           for(std::size_t i=0; i<2; ++i){                 // forward & backwards
             std::array<std::string,2> base_in;
             std::vector<std::string> base_out(njet, "g");
             const std::size_t uno_pos = i?1:(njet-2);
             const std::size_t fkl_pos = i?(njet-1):0;
             base_in[i?0:1] = uno;
             base_in[i?1:0] = fkl;
             base_out[uno_pos] = uno;
             base_out[fkl_pos] = fkl;
             auto expectation{ i?b:f };
             if( implemented
                 && !match_expectation(expectation, base_in, base_out) )
               return false;
             for(auto const & boson: bosons){      // any boson
               // at any position (higgs only inside FKL chain)
               std::size_t start = 0;
               std::size_t end = njet;
               if(to_ParticleID(boson) == pid::higgs){
                 start = i?(uno_pos+1):fkl_pos;
                 end = i?(fkl_pos+1):uno_pos;
               }
               for(std::size_t pos=start; pos<=end; ++pos){
                 auto const & in{base_in};
                 auto out{base_out};
                 // change quark flavours for W
                 const bool couple_idx
                   = std::uniform_int_distribution<int>{0,1}(RAN) != 0;
                 if(!couple_quark(boson, couple_idx?out[fkl_pos]:out[uno_pos]))
                   continue;
                 out.insert(out.begin()+pos, boson);
                 if(!match_expectation(expectation, in, out))
                   return false;
               }
             }
           }
         }
       }
     return true;
   }
 
   //! test extremal qqbar configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_extremal_qqbar( bool const implemented=true ){
     using namespace HEJ;
-    auto const b{ implemented?event_type::qqbar_exb:event_type::non_resummable };
-    auto const f{ implemented?event_type::qqbar_exf:event_type::non_resummable };
+    using namespace event_type;
+    std::unordered_set<EventType> b, f;
+    if(implemented) {
+      b.insert(qqbar_exb);
+      f.insert(qqbar_exf);
+    } else {
+      for(auto type: {non_resummable, unknown}) {
+        b.insert(type);
+        f.insert(type);
+      }
+    }
     std::vector<std::string> bosons;
     if(implemented)
       bosons = ALL_W;
     else {
       bosons = ALL_G_Z;
       bosons.emplace_back("h");
       bosons.emplace_back("Z_photon_mix");
     }
     for(std::string const & qqbar: ALL_QUARKS)          // all quark flavours
       for(std::string const & fkl: ALL_PARTONS){
         std::string const qqbar2{ std::to_string(HEJ::to_ParticleID(qqbar)*-1) };
         for(std::size_t njet=3; njet<=MAX_MULTI; ++njet){  // all multiplicities >2
           if(njet==5) continue;
           for(std::size_t i=0; i<2; ++i){             // forward & backwards
             std::array<std::string,2> base_in;
             std::vector<std::string> base_out(njet, "g");
             const std::size_t qqbar_pos = i?0:(njet-2);
             const std::size_t fkl_pos = i?(njet-1):0;
             base_in[i?0:1] = "g";
             base_in[i?1:0] = fkl;
             base_out[fkl_pos]   = fkl;
             base_out[qqbar_pos]   = qqbar;
             base_out[qqbar_pos+1] = qqbar2;
             auto expectation{ i?b:f };
             if( implemented
                 && !match_expectation(expectation, base_in, base_out) )
               return false;
             for(auto const & boson: bosons){ // all bosons
               // at any position (higgs only inside FKL chain)
               std::size_t start = 0;
               std::size_t end = njet;
               if(to_ParticleID(boson) == pid::higgs){
                 start = i?(qqbar_pos+2):fkl_pos;
                 end = i?(fkl_pos+1):qqbar_pos;
               }
               for(std::size_t pos=start; pos<=end; ++pos){
                 auto const & in{base_in};
                 auto out{base_out};
                 // change quark flavours for W
                 const bool couple_idx
                   = std::uniform_int_distribution<int>{0,1}(RAN) != 0;
                 if(couple_idx || !couple_quark(boson, out[fkl_pos]) ){
                   // (randomly) try couple to FKL, else fall-back to qqbar
                   if(!couple_quark(boson, out[qqbar_pos]))
                     couple_quark(boson, out[qqbar_pos+1]);
                 }
                 out.insert(out.begin()+pos, boson);
                 if(!match_expectation(expectation, in, out))
                   return false;
               }
             }
           }
         }
         // test allowed jet configurations
         if( implemented){
           if( !( match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -3)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -4)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -5)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -5)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -6)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -7)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -7)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -8)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -8)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -9)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -10)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -11)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -11)
               && match_expectation(f,{fkl,"g"},{fkl,"g","g","g","g",qqbar,qqbar2}, -12)
               && match_expectation(b,{"g",fkl},{qqbar,qqbar2,"g","g","g","g",fkl}, -12)
             ))
             return false;
          if (fkl == "2") {
           if( !( match_expectation(f,{"2","g"},{"1","Wp","g","g","g",qqbar,qqbar2}, -3)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"g","Wp","g","g","1"}, -4)
               && match_expectation(f,{"2","g"},{"1","Wp","g","g","g",qqbar,qqbar2}, -5)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"g","Wp","g","g","1"}, -5)
               && match_expectation(f,{"2","g"},{"1","g","Wp","g","g",qqbar,qqbar2}, -6)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqbar,qqbar2}, -7)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"g","g","g","Wp","1"}, -7)
               && match_expectation(f,{"2","g"},{"1","Wp","g","g","g",qqbar,qqbar2}, -8)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"Wp","g","g","g","1"}, -8)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"g","Wp","g","g","1"}, -9)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqbar,qqbar2}, -10)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqbar,qqbar2}, -11)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"g","g","g","Wp","1"}, -11)
               && match_expectation(f,{"2","g"},{"1","g","g","g","Wp",qqbar,qqbar2}, -12)
               && match_expectation(b,{"g","2"},{qqbar,qqbar2,"g","Wp","g","g","1"}, -12)
             ))
             return false;
          }
         }
       }
     return true;
   }
 
   //! test central qqbar configurations
   //! if implemented==false : check processes that are not in HEJ yet
   bool check_central_qqbar(bool const implemented=true){
     using namespace HEJ;
-    auto const t{ implemented?event_type::qqbar_mid:event_type::non_resummable };
+    using namespace event_type;
+    std::unordered_set<EventType> t;
+    if(implemented) {
+      t.insert(qqbar_mid);
+    } else {
+      for(auto type: {non_resummable, unknown}) {
+        t.insert(type);
+      }
+    }
     std::vector<std::string> bosons;
     if(implemented)
       bosons = ALL_W;
     else {
       bosons = ALL_G_Z;
       bosons.emplace_back("h");
       bosons.emplace_back("Z_photon_mix");
     }
     for(std::string const & qqbar: ALL_QUARKS)        // all quark flavours
       for(std::string const & fkl1: ALL_PARTONS)
         for(std::string const & fkl2: ALL_PARTONS){
           std::string const qqbar2{ std::to_string(HEJ::to_ParticleID(qqbar)*-1) };
           for(std::size_t njet=4; njet<=MAX_MULTI; ++njet){                 // all multiplicities >3
             if(njet==5) continue;
             for(std::size_t qqbar_pos=1; qqbar_pos<njet-2; ++qqbar_pos){ // any qqbar position
               std::array<std::string,2> base_in;
               std::vector<std::string> base_out(njet, "g");
               base_in[0] = fkl1;
               base_in[1] = fkl2;
               base_out.front()    = fkl1;
               base_out.back()     = fkl2;
               base_out[qqbar_pos]   = qqbar;
               base_out[qqbar_pos+1] = qqbar2;
               if( implemented && !match_expectation(t, base_in, base_out) )
                 return false;
               for(auto const & boson: bosons)         // any boson
                 for(std::size_t pos=0; pos<=njet; ++pos){         // at any position
                   if( to_ParticleID(boson) == pid::higgs
                     && (pos==qqbar_pos || pos==qqbar_pos+1) )
                     continue;
                   auto const & in{base_in};
                   auto out{base_out};
                   // change quark flavours for W
                   const int couple_idx{ std::uniform_int_distribution<int>{0,2}(RAN) };
                   // (randomly) try couple to FKL, else fall-back to qqbar
                   if( couple_idx == 0 && couple_quark(boson, out.front()) ){}
                   else if( couple_idx == 1 && couple_quark(boson, out.back()) ){}
                   else {
                     if(!couple_quark(boson, out[qqbar_pos]))
                       couple_quark(boson, out[qqbar_pos+1]);
                   }
                   out.insert(out.begin()+pos, boson);
                   if(!match_expectation(t, in, out))
                     return false;
                 }
             }
           }
         }
     return true;
   }
 
   // this checks a (non excessive) list of non-resummable states
   bool check_non_resummable(){
+    // some configurations violate charge conservation
+    using HEJ::event_type::invalid;
+    using HEJ::event_type::unknown;
     auto type{ HEJ::event_type::non_resummable};
     return
       // 1j - crossing lines
          match_expectation(type, {"g","2"},  {"2","h"})
       && match_expectation(type, {"-1","g"}, {"h","-1"})
       // 2j - crossing lines
       && match_expectation(type, {"g","2"},  {"2","g"})
       && match_expectation(type, {"-1","g"}, {"g","-1"})
       && match_expectation(type, {"1","-1"}, {"-1","1"})
       && match_expectation(type, {"g","2"},  {"2","g","h"})
       && match_expectation(type, {"1","2"},  {"2","h","1"})
       && match_expectation(type, {"1","-1"}, {"h","-1","1"})
       && match_expectation(type, {"g","2"},  {"Wp","1","g"})
       && match_expectation(type, {"1","-1"}, {"-2","Wp","1"})
       && match_expectation(type, {"4","g"},  {"g","3","Wp"})
       && match_expectation(type, {"1","-2"}, {"-1","Wm","1"})
       && match_expectation(type, {"g","3"},  {"4","g","Wm"})
       && match_expectation(type, {"1","3"},  {"Wm","4","1"})
       && match_expectation(type, {"g","2"},  {"Z_photon_mix","2","g"})
       && match_expectation(type, {"1","-1"}, {"-1","Z_photon_mix","1"})
       && match_expectation(type, {"4","g"},  {"g","4","Z_photon_mix"})
       // 2j - qqbar
       && match_expectation(type, {"g","g"},  {"1","-1"})
       && match_expectation(type, {"g","g"},  {"-2","2","h"})
       && match_expectation(type, {"g","g"},  {"-4","Wp","3"})
       && match_expectation(type, {"g","g"},  {"Wm","-1","2"})
       && match_expectation(type, {"g","g"},  {"-3","Z_photon_mix","3"})
       // 3j - crossing lines
       && match_expectation(type, {"g","4"},  {"4","g","g"})
       && match_expectation(type, {"-1","g"}, {"g","g","-1"})
       && match_expectation(type, {"1","3"},  {"3","g","1"})
       && match_expectation(type, {"-2","2"}, {"2","g","-2","h"})
       && match_expectation(type, {"-3","g"}, {"g","g","Wp","-4"})
       && match_expectation(type, {"1","-2"}, {"Wm","-1","g","1"})
       && match_expectation(type, {"-1","g"}, {"1","-1","-1"})
       && match_expectation(type, {"1","-4"}, {"Z_photon_mix","-4","g","1"})
       // higgs inside uno
       && match_expectation(type, {"-1","g"}, {"g","h","-1","g"})
       && match_expectation(type, {"-1","1"}, {"g","h","-1","1"})
       && match_expectation(type, {"g","2"},  {"g","2","h","g"})
       && match_expectation(type, {"-1","1"}, {"-1","1","h","g"})
       // higgs outside uno
       && match_expectation(type, {"-1","g"}, {"h","g","-1","g"})
       && match_expectation(type, {"-1","1"}, {"-1","1","g","h"})
       // higgs inside qqbar
       && match_expectation(type, {"g","g"}, {"-1","h","1","g","g"})
       && match_expectation(type, {"g","g"}, {"g","-1","h","1","g"})
       && match_expectation(type, {"g","g"}, {"g","g","2","h","-2"})
       // higgs outside qqbar
       && match_expectation(type, {"g","g"}, {"h","-1","1","g","g"})
       && match_expectation(type, {"g","g"}, {"g","g","2","-2","h"})
       // 4j - two uno
       && match_expectation(type, {"-2","2"}, {"g","-2","2","g"})
       && match_expectation(type, {"1","3"},  {"g","1","h","3","g"})
       && match_expectation(type, {"1","2"},  {"g","1","3","Wp","g"})
       && match_expectation(type, {"1","-2"}, {"g","Wm","1","-1","g"})
       && match_expectation(type, {"3","2"},  {"g","3","Z_photon_mix","2","g"})
       // 4j - two gluon outside
       && match_expectation(type, {"g","4"},  {"g","4","g","g"})
       && match_expectation(type, {"1","3"},  {"1","3","h","g","g"})
       && match_expectation(type, {"1","2"},  {"1","3","g","Wp","g"})
       && match_expectation(type, {"1","-2"}, {"1","Wm","-1","g","g"})
       && match_expectation(type, {"-1","g"}, {"g","g","-1","g"})
       && match_expectation(type, {"1","3"},  {"g","g","1","3","h"})
       && match_expectation(type, {"1","2"},  {"g","g","1","Wp","3"})
       && match_expectation(type, {"1","-2"}, {"Wm","g","g","1","-1"})
       && match_expectation(type, {"-1","2"}, {"g","g","-1","Z_photon_mix","2"})
       // 4j - ggx+uno
       && match_expectation(type, {"g","4"},  {"1","-1","4","g"})
       && match_expectation(type, {"2","g"},  {"g","2","-3","3"})
       && match_expectation(type, {"g","4"},  {"1","-1","h","4","g"})
       && match_expectation(type, {"2","g"},  {"g","2","-3","3","h"})
       && match_expectation(type, {"g","4"},  {"Wp","1","-1","3","g"})
       && match_expectation(type, {"2","g"},  {"g","2","-4","Wp","3"})
       && match_expectation(type, {"g","4"},  {"2","Wm","-1","4","g"})
-      && match_expectation(type, {"2","g"},  {"g","2","Wp","-3","4"})
+      && match_expectation(invalid, {"2","g"},  {"g","2","Wp","-3","4"})
       && match_expectation(type, {"-4","g"}, {"g","-4","-3","3","Z_photon_mix"})
       // 3j - crossing+uno
       && match_expectation(type, {"1","4"},  {"g","4","1"})
       && match_expectation(type, {"1","4"},  {"4","1","g"})
       && match_expectation(type, {"1","4"},  {"g","h","4","1"})
-      && match_expectation(type, {"-1","-3"},{"Wm","g","-4","-1"})
+      && match_expectation(invalid, {"-1","-3"},{"Wm","g","-4","-1"})
       && match_expectation(type, {"1","4"},  {"3","1","Wp","g"})
-      && match_expectation(type, {"1","4"},  {"3","1","g","h"})
+      && match_expectation(invalid, {"1","4"},  {"3","1","g","h"})
       && match_expectation(type, {"2","3"},  {"3","2","Z_photon_mix","g"})
       // 3j - crossing+qqbar
       && match_expectation(type, {"1","g"},  {"-1","1","g","1"})
       && match_expectation(type, {"1","g"},  {"-1","1","1","g"})
       && match_expectation(type, {"g","1"},  {"1","g","1","-1"})
       && match_expectation(type, {"g","1"},  {"g","1","1","-1"})
       && match_expectation(type, {"1","g"},  {"2","-2","g","1"})
       && match_expectation(type, {"1","g"},  {"2","-2","1","g"})
       && match_expectation(type, {"g","1"},  {"1","g","-2","2"})
       && match_expectation(type, {"g","1"},  {"g","1","-2","2"})
       && match_expectation(type, {"1","g"},  {"-1","1","h","g","1"})
       && match_expectation(type, {"1","g"},  {"-1","h","1","1","g"})
       && match_expectation(type, {"g","1"},  {"1","g","1","h","-1"})
       && match_expectation(type, {"g","1"},  {"h","g","1","1","-1"})
       && match_expectation(type, {"1","g"},  {"2","-2","1","g","h"})
       && match_expectation(type, {"g","1"},  {"g","h","1","-2","2"})
       && match_expectation(type, {"1","g"},  {"Wp","3","-4","g","1"})
-      && match_expectation(type, {"3","g"},  {"-2","Wm","1","3","g"})
+      && match_expectation(invalid, {"3","g"},  {"-2","Wm","1","3","g"})
       && match_expectation(type, {"g","1"},  {"1","g","Wm","-3","4"})
-      && match_expectation(type, {"g","-3"},  {"g","-3","-1","Wp","2"})
+      && match_expectation(invalid, {"g","-3"},  {"g","-3","-1","Wp","2"})
       && match_expectation(type, {"g","2"},  {"2","g","Z_photon_mix","4","-4"})
       // 4j- gluon in qqbar
       && match_expectation(type, {"g","1"},  {"1","g","-1","1"})
       && match_expectation(type, {"1","g"},  {"1","-1","g","1"})
-      && match_expectation(type, {"g","1"},  {"1","g","Wm","-2","1"})
+      && match_expectation(invalid, {"g","1"},  {"1","g","Wm","-2","1"})
       && match_expectation(type, {"2","g"},  {"2","-2","g","Wp","1"})
       && match_expectation(type, {"g","g"},  {"Wp","3","g","-4","g"})
       && match_expectation(type, {"1","g"},  {"1","h","-1","g","1"})
       && match_expectation(type, {"3","g"},  {"3","1","g","Z_photon_mix","-1"})
       // 6j - two qqbar
       && match_expectation(type, {"g","g"},  {"1","-1","g","g","1","-1"})
       && match_expectation(type, {"g","g"},  {"1","-1","g","1","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","-1","g","1","-1"})
       && match_expectation(type, {"g","g"},  {"g","1","-1","1","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","1","-1","-1","g"})
       && match_expectation(type, {"g","g"},  {"h","1","-1","g","g","1","-1"})
       && match_expectation(type, {"g","g"},  {"1","Wp","-2","g","1","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","Wp","-1","g","1","-2"})
       && match_expectation(type, {"g","g"},  {"g","1","-1","Wm","2","-1","g"})
       && match_expectation(type, {"g","g"},  {"g","1","2","-1","Wm","-1","g"})
       && match_expectation(type, {"g","g"},  {"2","-2","g","-1","1","Z_photon_mix","g"})
       // random stuff (can be non-physical)
       && match_expectation(type, {"g","g"},  {"1","-2","2","-1"}) // != 2 qqbar
-      && match_expectation(type, {"g","g"},  {"1","-2","2","g"})  // could be qqbar
-      && match_expectation(type, {"e+","e-"},{"1","-1"})          // bad initial state
-      && match_expectation(type, {"1","e-"}, {"g","1","Wm"})      // bad initial state
-      && match_expectation(type, {"h","g"},  {"g","g"})           // bad initial state
-      && match_expectation(type, {"-1","g"}, {"-1","1","1"})      // bad qqbar
-      && match_expectation(type, {"-1","g"}, {"1","1","-1"})      // crossing in bad qqbar
-      && match_expectation(type, {"-1","g"}, {"-2","1","1","Wp"}) // bad qqbar
-      && match_expectation(type, {"1","2"},  {"1","-1","g","g","g","2"})  // bad qqbar
-      && match_expectation(type, {"1","2"},  {"1","-1","-2","g","g","2"}) // gluon in bad qqbar
-      && match_expectation(type, {"g","g"},  {"-1","2","g","g"}) // wrong back qqbar
-      && match_expectation(type, {"g","g"},  {"g","g","2","1"}) // wrong forward qqbar
-      && match_expectation(type, {"g","g"},  {"g","-2","1","g"}) // wrong central qqbar
-      && match_expectation(type, {"1","g"},  {"1","-2","g","g","Wp"}) // extra quark
-      && match_expectation(type, {"g","1"},  {"g","g","-2","1","Wp"}) // extra quark
-      && match_expectation(type, {"g","1"},  {"g","g","Wp","-2","1"}) // extra quark
-      && match_expectation(type, {"g","1"},  {"g","-2","1","g","Wp"}) // extra quark
-      && match_expectation(type, {"g","g"},  {"g","g","g","-2","1","-1","Wp"}) // extra quark
-      && match_expectation(type, {"1","g"},  {"g","Wp","1","-2","g"}) // extra quark
-      && match_expectation(type, {"g","g"},  {"1","-1","-2","g","g","g","Wp"}) // extra quark
+      && match_expectation(invalid, {"g","g"},  {"1","-2","2","g"})  // could be qqbar
+      && match_expectation(unknown, {"e+","e-"},{"1","-1"})          // bad initial state
+      && match_expectation(unknown, {"1","e-"}, {"g","1","Wm"})      // bad initial state
+      && match_expectation(unknown, {"h","g"},  {"g","g"})           // bad initial state
+      && match_expectation(invalid, {"-1","g"}, {"-1","1","1"})      // bad qqbar
+      && match_expectation(invalid, {"-1","g"}, {"1","1","-1"})      // crossing in bad qqbar
+      && match_expectation(invalid, {"-1","g"}, {"-2","1","1","Wp"}) // bad qqbar
+      && match_expectation(invalid, {"1","2"},  {"1","-1","g","g","g","2"})  // bad qqbar
+      && match_expectation(invalid, {"1","2"},  {"1","-1","-2","g","g","2"}) // gluon in bad qqbar
+      && match_expectation(invalid, {"g","g"},  {"-1","2","g","g"}) // wrong back qqbar
+      && match_expectation(invalid, {"g","g"},  {"g","g","2","1"}) // wrong forward qqbar
+      && match_expectation(invalid, {"g","g"},  {"g","-2","1","g"}) // wrong central qqbar
+      && match_expectation(invalid, {"1","g"},  {"1","-2","g","g","Wp"}) // extra quark
+      && match_expectation(invalid, {"g","1"},  {"g","g","-2","1","Wp"}) // extra quark
+      && match_expectation(invalid, {"g","1"},  {"g","g","Wp","-2","1"}) // extra quark
+      && match_expectation(invalid, {"g","1"},  {"g","-2","1","g","Wp"}) // extra quark
+      && match_expectation(invalid, {"g","g"},  {"g","g","g","-2","1","-1","Wp"}) // extra quark
+      && match_expectation(invalid, {"1","g"},  {"g","Wp","1","-2","g"}) // extra quark
+      && match_expectation(invalid, {"g","g"},  {"1","-1","-2","g","g","g","Wp"}) // extra quark
       ;
   }
 
   // Two boson states, that are currently not implemented
   // Check for supported final states
   bool check_bad_FS(){
-    auto type{ HEJ::event_type::bad_final_state};
+    using HEJ::event_type::invalid;
+    using HEJ::event_type::unknown;
     return
-         match_expectation(type, {"g","g"},  {"g","p","p","g"}) // protons
-      && match_expectation(type, {"-4","-1"},{"-4","g","11","-11","-2"}) // leptons should be in decay
-      && match_expectation(type, {"-4","-1"},{"-4","g","-13","g","-2"}) // leptons should be in decay
+         match_expectation(invalid, {"g","g"},  {"g","p","p","g"}) // protons
+      && match_expectation(invalid, {"-4","-1"},{"-4","g","11","-11","-2"}) // charge not conserved
+      && match_expectation(unknown, {"-4","-1"},{"-4","g","-13","g","-2"}) // lepton in final state
     ;
   }
 
   // not enough jets
   bool check_not_enough_jets(){
-    auto type{ HEJ::event_type::not_enough_jets};
+    using HEJ::event_type::unknown;
     return
-         match_expectation(type, {"g","g"},  {})
-      && match_expectation(type, {"1","-1"}, {})
-      && match_expectation(type, {"g","-1"}, {"-1"})
-      && match_expectation(type, {"g","g"},  {"g"})
-      && match_expectation(type, {"g","g"},  {"h"})
-      && match_expectation(type, {"g","2"},  {"Wp","1"})
-      && match_expectation(type, {"g","2"},  {"Z","2"})
+         match_expectation(unknown, {"g","g"},  {})
+      && match_expectation(unknown, {"1","-1"}, {})
+      && match_expectation(unknown, {"g","-1"}, {"-1"})
+      && match_expectation(unknown, {"g","g"},  {"g"})
+      && match_expectation(unknown, {"g","g"},  {"h"})
+      && match_expectation(unknown, {"g","2"},  {"Wp","1"})
+      && match_expectation(unknown, {"g","2"},  {"Z","2"})
     ;
   }
 
   // 2 boson final-states
   bool check_2_boson(bool const implemented=true){
-    auto type = (implemented) ? HEJ::event_type::FKL : HEJ::event_type::non_resummable;
+    auto type = (implemented) ? HEJ::event_type::FKL : HEJ::event_type::unknown;
 
     // Implemented
     if ( implemented ) {
       //Checks WpWp and WmWm separatelty to use couple_quark method.
       for(std::string const & q1 : ALL_QUARKS){
         for(std::string const & q2 : ALL_QUARKS){
           for(size_t njet = 2; njet < 6; ++njet){
             // Event setup
             std::array<std::string,2> in {q1, q2};
             std::vector<std::string>  out(njet, "g");
             out.front() = q1;
             out.back()  = q2;
             // Adjust quark flavours for the boson, if possible
             // WpWp
             if( couple_quark("Wp", out.front()) &&
                 couple_quark("Wp", out.back() )
               ) {
 
                 std::unordered_map< size_t, std::vector<std::string> > decays;
                 for(size_t i = 0; i < 2; ++i){
                   out.emplace_back("Wp");
                   decays.emplace( out.size()-1,
                               std::initializer_list<std::string>{"e+", "nu_e"} );
                 }
                 // Check classification
                 if( !match_expectation(type, rapidity_order_ps(in, out, false, decays)) ){return false;}
             }
           }
         }
       }
 
       for(std::string const & q1 : ALL_QUARKS){
         for(std::string const & q2 : ALL_QUARKS){
           for(size_t njet = 2; njet < 6; ++njet){
             // Event setup
             std::array<std::string,2> in {q1, q2};
             std::vector<std::string>  out(njet, "g");
             out.front() = q1;
             out.back()  = q2;
             // Adjust quark flavours for the boson, if possible
             // WmWm
             if( couple_quark("Wm", out.front()) &&
                 couple_quark("Wm", out.back() )
               ) {
 
                 std::unordered_map< size_t, std::vector<std::string> > decays;
                 for(size_t i = 0; i < 2; ++i){
                   out.emplace_back("Wm");
                   decays.emplace( out.size()-1,
                               std::initializer_list<std::string>{"e-", "nu_e_bar"} );
                 }
                 // Check classification
                 if( !match_expectation(type, rapidity_order_ps(in, out, false, decays)) ){return false;}
             }
           }
         }
       }
       return true;
     }
 
     // Not Implemented (non_resummable)
     else {
       return
           // h h
           match_expectation(type, {"g","g"}, {"h","g","h","g"})
           // Wp Wm
           && match_expectation(type,
               rapidity_order_ps(
                 {"u","d"},
                 {"e+","nu_e","d","mu-","nu_mu_bar","u"},
                 true)
              )
           // Wm h
           && match_expectation(type,
               rapidity_order_ps(
                 {"u","d"},
                 {"e-","nu_e_bar","u","h","u"},
                 true)
              )
           // Wp h
           && match_expectation(type,
               rapidity_order_ps(
                 {"u","d"},
-                {"e+","nu_e","d","h","u"},
+                {"e+","nu_e","d","h","d"},
                 true)
              )
       ;
     }
   }
 
   // not implemented processes
   bool check_not_implemented(){
     return check_fkl(false)
         && check_uno(false)
 
         && check_extremal_qqbar(false)
         && check_central_qqbar(false)
         && check_2_boson(false);
   }
 
   // h + single jet
   bool check_hj(){
     using namespace HEJ::event_type;
     return
       match_expectation(FKL, {"g","g"},  {"h", "g"})
       && match_expectation(FKL, {"g","g"},  {"g", "h"})
       && match_expectation(FKL, {"u","g"},  {"u", "h"})
       && match_expectation(FKL, {"g","u"},  {"h", "u"})
       && match_expectation(non_resummable, {"g","u"},  {"u", "h"})
-      && match_expectation(non_resummable, {"u","u"},  {"u", "h"})
+      && match_expectation(invalid, {"u","u"},  {"u", "h"})
     ;
 
   }
 
 } // namespace
 
 
 int main() {
   // tests for "no false negatives"
   // i.e. all HEJ-configurations get classified correctly
   if(!check_fkl()) return EXIT_FAILURE;
   if(!check_uno()) return EXIT_FAILURE;
 
 
   if(!check_extremal_qqbar()) return EXIT_FAILURE;
   if(!check_central_qqbar()) return EXIT_FAILURE;
   if(!check_2_boson()) return EXIT_FAILURE;
 
   // test for "no false positive"
   // i.e. non-resummable gives non-resummable
   if(!check_non_resummable()) return EXIT_FAILURE;
   if(!check_bad_FS()) return EXIT_FAILURE;
   if(!check_not_enough_jets()) return EXIT_FAILURE;
   if(!check_not_implemented()) return EXIT_FAILURE;
   if(!check_hj()) return EXIT_FAILURE;
 
   return EXIT_SUCCESS;
 }
diff --git a/t/test_colours.cc b/t/test_colours.cc
index d7804ab..5582a2e 100644
--- a/t/test_colours.cc
+++ b/t/test_colours.cc
@@ -1,362 +1,363 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2022
  *  \copyright GPLv2 or later
  */
 #include "hej_test.hh"
 
 #include <cstdlib>
 #include <iostream>
 #include <utility>
 #include <vector>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/exceptions.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/RNG.hh"
 
 #include "fastjet/JetDefinition.hh"
 
 namespace {
   /// biased RNG to connect always to colour
   class dum_rnd: public HEJ::RNG {
   public:
     dum_rnd() = default;
     double flat() override {
       return 0.;
     }
   };
 
   HEJ::Event::EventData decay_boson( HEJ::Event::EventData ev ){
     for( std::size_t i=0; i<ev.outgoing.size(); ++i ){
       if( std::abs(ev.outgoing[i].type) == HEJ::ParticleID::Wp){
         ev.decays[i] = decay_W(ev.outgoing[i]);
       }
     }
     return ev;
   }
 
 
   void dump_event(HEJ::Event const & ev){
     for(auto const & in: ev.incoming()){
       std::cerr << "in type=" << in.type
         << ", colour={" << (*in.colour).first
         << ", " << (*in.colour).second << "}\n";
     }
     for(auto const & out: ev.outgoing()){
       std::cerr << "out type=" << out.type << ", colour={";
       if(out.colour)
         std::cerr << (*out.colour).first << ", " << (*out.colour).second;
       else
         std::cerr << "non, non";
       std::cerr << "}\n";
     }
   }
 
   /// true if colour is allowed for particle
   bool correct_colour(HEJ::Particle const & part){
     if(!HEJ::is_parton(part) && !part.colour) return true;
     if(!part.colour) return false;
     int const colour = part.colour->first;
     int const anti_colour = part.colour->second;
     if(part.type == HEJ::ParticleID::gluon)
       return colour != anti_colour
         && colour >= HEJ::COLOUR_OFFSET
         && anti_colour >= HEJ::COLOUR_OFFSET;
     if(HEJ::is_quark(part))
       return anti_colour == 0 && colour >= HEJ::COLOUR_OFFSET;
     return colour == 0 && anti_colour >= HEJ::COLOUR_OFFSET;
   }
 
   bool correct_colour(HEJ::Event const & ev){
     if(!ev.is_leading_colour())
       return false;
     // some of these additional checks are also in ev.is_leading_colour()
     for(auto const & part: ev.incoming()){
       if(!correct_colour(part))
         return false;
     }
     for(auto const & part: ev.outgoing()){
       if(!correct_colour(part))
         return false;
     }
     return true;
   }
 
   bool match_expected(
     HEJ::Event const & ev,
     std::vector<HEJ::Colour> const & expected
   ){
     ASSERT(ev.outgoing().size()+2==expected.size());
     for(std::size_t i=0; i<ev.incoming().size(); ++i){
       ASSERT(ev.incoming()[i].colour);
       if( *ev.incoming()[i].colour != expected[i])
         return false;
     }
     for(std::size_t i=2; i<ev.outgoing().size()+2; ++i){
       if( ev.outgoing()[i-2].colour ){
         if( *ev.outgoing()[i-2].colour != expected[i] )
           return false;
       } else if( expected[i].first != 0 || expected[i].second != 0)
         return false;
     }
     return true;
   }
 
   void check_event(
     HEJ::Event::EventData unc_ev,
     std::vector<HEJ::Colour> const & expected_colours
 
   ){
+    repair_momenta(unc_ev);
     unc_ev = decay_boson(std::move(unc_ev));
     shuffle_particles(unc_ev); // make sure incoming order doesn't matter
     HEJ::Event ev{unc_ev.cluster(
       fastjet::JetDefinition(fastjet::JetAlgorithm::antikt_algorithm, 0.4), 30.)
     };
     ASSERT(HEJ::event_type::is_resummable(ev.type()));
     dum_rnd rng;
     ASSERT(!ev.is_leading_colour());
     ASSERT(ev.generate_colours(rng));
     if(!correct_colour(ev)){
       std::cerr << "Found illegal colours for event\n";
       dump_event(ev);
       throw std::invalid_argument("Illegal colour set");
     }
     if(!match_expected(ev, expected_colours)){
       std::cerr << "Colours didn't match expectation. Found\n";
       dump_event(ev);
       std::cerr << "but expected\n";
       for(auto const & col: expected_colours){
         std::cerr << "colour={" << col.first << ", " << col.second << "}\n";
       }
       throw std::logic_error("Colours did not match expectation");
     }
   }
 
   HEJ::Event::EventData reset_colour(
     HEJ::Event::EventData ev, std::vector<HEJ::Colour> const & goal
   ){
     for(std::size_t i=0; i<2; ++i){
       ev.incoming[i].colour = goal[i];
     }
     for(std::size_t i=0; i<ev.outgoing.size(); ++i){
       auto const & col_goal{ goal[i+2] };
       if(col_goal.first == 0 && col_goal.second == 0)
         ev.outgoing[i].colour = std::optional<HEJ::Colour>{};
       else
         ev.outgoing[i].colour = col_goal;
     }
     return ev;
   }
 } // namespace
 
 int main() {
   HEJ::Event::EventData ev;
   std::vector<HEJ::Colour> expected_colours(7);
 
   /// pure gluon (they all have a mass of 4 GeV to allow decays)
   ev.incoming[0] =      { HEJ::ParticleID::gluon, {  0,   0, -205, 205}, {}};
   ev.incoming[1] =      { HEJ::ParticleID::gluon, {  0,   0,  279, 279}, {}};
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-15, -82,  -82, 117}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, { 68,  93,   20, 117}, {}});
-  ev.outgoing.push_back({ HEJ::ParticleID::higgs, {-30, -65,   22,  75}, {}});
+  ev.outgoing.push_back({ HEJ::ParticleID::higgs, {-30, -65,   42,  75}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-12,  92,   76, 120}, {}});
   ev.outgoing.push_back({ HEJ::ParticleID::gluon, {-11, -38,   38,  55}, {}});
 
   expected_colours[0] = {502, 501};
   expected_colours[1] = {509, 502};
   expected_colours[2] = {503, 501};
   expected_colours[3] = {505, 503};
   expected_colours[4] = {  0,   0};
   expected_colours[5] = {507, 505};
   expected_colours[6] = {509, 507};
   // default colours is always forbidden!
   // default: swap last two (anti-)colour -> crossing
   ev=reset_colour(ev, expected_colours);
   std::swap(ev.outgoing[4].colour, ev.outgoing[3].colour);
   check_event(ev, expected_colours);
 
   /// last g to Qbar (=> gQbar -> g ... Qbar)
   ev.incoming[1].type = HEJ::ParticleID::d_bar;
   ev.outgoing[4].type = HEJ::ParticleID::d_bar;
   // => only end changes
   expected_colours[1].first = 0;
   expected_colours[6].first = 0;
   // default: swap last two anti-colours -> last gluon colour singlet
   ev=reset_colour(ev, expected_colours);
   std::swap(ev.outgoing[4].colour->second, ev.outgoing[3].colour->second);
   check_event(ev, expected_colours);
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// uno forward (=> gQbar -> g ... Qbar g)
     std::swap(new_ev.outgoing[3].type, new_ev.outgoing[4].type);
     // => uno quarks eats colour and gluon connects to anti-colour
     new_expected[5] = {0, expected_colours[3].first};
     new_expected[6] = {expected_colours[0].first, expected_colours[0].first+2};
     new_expected[1].second += 2; // one more anti-colour in line
     // default: swap last two anti-colours -> crossing
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.outgoing[4].colour->second, new_ev.outgoing[3].colour->second);
     check_event(new_ev, new_expected);
   }
 
   /// swap Qbar <-> Q (=> gQ -> g ... Q)
   ev.incoming[1].type = HEJ::ParticleID::d;
   ev.outgoing[4].type = HEJ::ParticleID::d;
   // => swap: colour<->anti && initial<->final
   std::swap(expected_colours[1], expected_colours[6]);
   std::swap(expected_colours[1].first, expected_colours[1].second);
   std::swap(expected_colours[6].first, expected_colours[6].second);
   // default: swap incoming <-> outgoing
   ev=reset_colour(ev, expected_colours);
   std::swap(ev.incoming[0].colour, ev.outgoing[0].colour);
   check_event(ev, expected_colours);
 
   /// first g to qbar (=> qbarQ -> qbar ... Q)
   ev.incoming[0].type = HEJ::ParticleID::u_bar;
   ev.outgoing[0].type = HEJ::ParticleID::u_bar;
   expected_colours[0] = {  0, 501};
   // => shift anti-colour index one up
   expected_colours[1].first -= 2;
   expected_colours[5] = expected_colours[3];
   expected_colours[3] = expected_colours[2];
   expected_colours[2] = {  0, 502};
   // default: closed qbar->qbar g
   ev=reset_colour(ev, expected_colours);
   ev.outgoing[1].colour->first = ev.outgoing[0].colour->second;
   ev.outgoing[1].colour->second = ev.incoming[0].colour->second;
   ev.outgoing[4].colour->first = ev.outgoing[3].colour->second;
   check_event(ev, expected_colours);
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// uno backward (=> qbarQ -> g qbar ... Q)
     std::swap(new_ev.outgoing[0].type, new_ev.outgoing[1].type);
     // => uno gluon connects to quark colour
     new_expected[3] = expected_colours[2];
     new_expected[2] = {expected_colours[0].second+2, expected_colours[0].second};
     // default: Colourful Higgs
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.outgoing[2].colour = std::make_pair(1,1);
     check_event(new_ev, new_expected);
 
     /// swap qbar <-> q (=> qQ -> g q ... Q)
     new_ev.incoming[0].type = HEJ::ParticleID::u;
     new_ev.outgoing[1].type = HEJ::ParticleID::u;
     // => swap: colour<->anti && inital<->final
     std::swap(new_expected[0], new_expected[3]);
     std::swap(new_expected[0].first, new_expected[0].second);
     std::swap(new_expected[3].first, new_expected[3].second);
     // => & connect first gluon with remaining anti-colour
     new_expected[2] = {new_expected[0].first, new_expected[0].first+2};
     // shift colour line one down
     new_expected[1].first-=2;
     new_expected[5].first-=2;
     new_expected[5].second-=2;
     // shift anti-colour line one up
     new_expected[6].first+=2;
     // default: swap 2 quarks -> disconnected
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.outgoing[1].colour, new_ev.outgoing[4].colour);
     check_event(new_ev, new_expected);
   }
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// uno forward (=> qbarQ -> qbar ... Q g)
     std::swap(new_ev.outgoing[3].type, new_ev.outgoing[4].type);
     // => uno gluon connects to remaining colour
     new_expected[5] = expected_colours[6];
     new_expected[6] = {expected_colours[3].first+2, expected_colours[3].first};
     // default: no colour on last gluon
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.incoming[1].colour->first = new_ev.outgoing[4].colour->second;
     new_ev.outgoing[4].colour = {};
     check_event(new_ev, new_expected);
   }
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// qqbar backward (=> gQ -> qbar q ... Q) with Wp
     // => swap: incoming q <-> outgoing gluon
     std::swap(new_ev.incoming[0].type, new_ev.outgoing[1].type);
     new_ev.outgoing[1].type=static_cast<HEJ::ParticleID>(
       -(new_ev.outgoing[1].type+1) );
     new_ev.outgoing[2].type = HEJ::ParticleID::Wp;
     // incoming q -> outgoing q (colour<->anti)
     std::swap(new_expected[0], new_expected[3]);
     std::swap(new_expected[3].first, new_expected[3].second);
     new_expected[3].first+=2;
     new_expected[0].first-=1; // skip one index
     // couple first in to first out
     new_expected[2].second=new_expected[0].second;
     // default: swap qqbar <-> first g
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.outgoing[0].colour->second, new_ev.outgoing[3].colour->second);
     std::swap(new_ev.outgoing[1].colour->first, new_ev.outgoing[3].colour->first);
     check_event(new_ev, new_expected);
   }
 
   {
     // don't overwrite
     auto new_expected = expected_colours;
     auto new_ev = ev;
     /// qqbar forward (=> qbar g -> qbar ... Qbar Q) with Wp
     // => swap: incoming Q <-> outgoing gluon
     std::swap(new_ev.incoming[1].type, new_ev.outgoing[3].type);
     new_ev.outgoing[3].type=static_cast<HEJ::ParticleID>(
       -(new_ev.outgoing[3].type+1));
     new_ev.outgoing[2].type = HEJ::ParticleID::Wp;
     // incoming q -> outgoing q (colour<->anti)
     std::swap(new_expected[1], new_expected[5]);
     std::swap(new_expected[5].first, new_expected[5].second);
     new_expected[5].second-=2;
     new_expected[1].second-=1; // skip one index
     // couple last in to last out
     new_expected[6].first=new_expected[1].first;
     // default: uncoloured quark
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.outgoing[0].colour = {};
     check_event(new_ev, new_expected);
 
     // move Higgs to position 1 (=> qbar g -> qbar h g Qbar Q)
     std::swap(new_ev.outgoing[1].type, new_ev.outgoing[2].type);
     std::swap(new_expected[3], new_expected[4]); // trivial
     // default: incoming qbar wrong colour
     new_ev=reset_colour(new_ev, new_expected);
     new_ev.incoming[0].colour->first  = 1;
     check_event(new_ev, new_expected);
 
     // central qqbar (=> qbar g -> qbar h Q Qbar g)
     // => swap: Q <-> g
     std::swap(new_ev.outgoing[2].type, new_ev.outgoing[4].type);
     std::swap(new_expected[4], new_expected[6]);
     // gluon was connected on left side, i.e. doesn't matter for QQbar
     // => couple Q to out qbar
     new_expected[4].first = new_expected[2].second;
     // Qbar next in line
     new_expected[5].second = new_expected[4].first+2;
     // incoming g shifted by one position in line
     new_expected[1].first-=2;
     new_expected[1].second+=2;
     // default: wrong colour in last incoming
     new_ev=reset_colour(new_ev, new_expected);
     std::swap(new_ev.incoming[1].colour->first,
               new_ev.incoming[1].colour->second);
     check_event(new_ev, new_expected);
   }
 
   return EXIT_SUCCESS;
 }
diff --git a/t/test_decay.cc b/t/test_decay.cc
index eac5021..d245cc8 100644
--- a/t/test_decay.cc
+++ b/t/test_decay.cc
@@ -1,441 +1,447 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019-2020
+ *  \date      2019-2022
  *  \copyright GPLv2 or later
  *
- *  \brief Test classification for (invalid) W decays
+ *  \brief Test classification for (invalid) boson decays
  */
 #include "hej_test.hh"
 
 #include <array>
 #include <cstdlib>
 #include <initializer_list>
 #include <iostream>
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <utility>
 
 #include "fastjet/JetDefinition.hh"
 #include "fastjet/PseudoJet.hh"
 
 #include "HEJ/Event.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/PDG_codes.hh"
 
+using HEJ::event_type::FKL;
+using HEJ::event_type::unknown;
+using HEJ::event_type::invalid;
+
 namespace {
+
   const fastjet::JetDefinition JET_DEF{fastjet::JetAlgorithm::antikt_algorithm, 0.4};
   const double MIN_JET_PT{30.};
 
   HEJ::Event::EventData new_event() {
     HEJ::Event::EventData ev;
     ev.outgoing.push_back({HEJ::ParticleID::gluon, { -11,  -96,  -76,  123}, {}});
     ev.outgoing.push_back({HEJ::ParticleID::gluon, { -15,  -70,  -22,   75}, {}});
     ev.outgoing.push_back({HEJ::ParticleID::gluon, {  68,   93,  -20,  117}, {}});
     ev.outgoing.push_back({HEJ::ParticleID::gluon, { -12,   95,   56,  111}, {}});
     ev.outgoing.push_back({HEJ::ParticleID::gluon, { -30,  -22,   25,   45}, {}});
     ev.incoming[0] = {HEJ::ParticleID::gluon, {   0,    0, -254,  254}, {}};
     ev.incoming[1] = {HEJ::ParticleID::gluon, {   0,    0,  217,  217}, {}};
     return ev;
   }
 
-  bool test_event(HEJ::Event::EventData data, bool const valid
-  ){
-    using namespace HEJ::event_type;
-    EventType const expected{ valid?FKL:bad_final_state };
+  bool test_event(HEJ::Event::EventData data, const HEJ::event_type::EventType expected){
     shuffle_particles(data);
     auto const ev = std::move(data).cluster(JET_DEF, MIN_JET_PT);
     if(ev.type() != expected){
-      std::cerr << "Event does not match expectation, expected "
+      std::cerr << "Event does not match expectation. "
+        "Found " << name(ev.type()) << ", expected "
         << name(expected) << "\n" << ev << std::endl;
       return false;
     }
     return true;
   }
 
   // Check basic FKL event
   bool check_fkl() {
     auto ev = new_event();
-    return test_event(ev, true);
+    repair_momenta(ev);
+    return test_event(ev, FKL);
   }
 
   // Check W decays
   bool check_W_decay() {
     using namespace HEJ::pid;
 
     auto ev = new_event();
     // W position shouldn't matter
     for(auto const W_type: {Wp, Wm}){
       for(std::size_t w_pos = 1; w_pos<ev.outgoing.size()-1; ++w_pos){
         ev = new_event();
         ev.outgoing[w_pos].type = W_type;
         ev.outgoing.back().type = (W_type==Wp)?d:u;
         ev.incoming.back().type = (W_type==Wp)?u:d;
+        repair_momenta(ev);
 
         // no decay
-        if(!test_event(ev, false))
+        if(!test_event(ev, unknown))
           return false;
 
         // working decay (Wp -> nu_e,e+ OR Wp -> e-,nu_e_bar)
         ev.decays[w_pos] = decay_W(ev.outgoing[w_pos]);
-        if(!test_event(ev, true))
+        if(!test_event(ev, FKL))
           return false;
 
         // swapped W+ <-> W-
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         -ev.decays[w_pos].at(0).type );
         ev.decays[w_pos].at(1).type = static_cast<ParticleID>(
                                         -ev.decays[w_pos].at(1).type );
-        if(!test_event(ev, false))
+        if(!test_event(ev, invalid))
           return false;
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         -ev.decays[w_pos].at(0).type );
         ev.decays[w_pos].at(1).type = static_cast<ParticleID>(
                                         -ev.decays[w_pos].at(1).type );
 
         // replace e -> mu (normal)
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(0).type+2 );
-        if(!test_event(ev, false))
+        if(!test_event(ev, unknown))
           return false;
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(0).type-2 );
 
         // replace e -> mu (anti)
         ev.decays[w_pos].at(1).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(1).type-2 );
-        if(!test_event(ev, false))
+        if(!test_event(ev, unknown))
           return false;
 
         // all mu
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(0).type+2 );
-        if(!test_event(ev, true))
+        if(!test_event(ev, FKL))
           return false;
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(0).type-2 );
         ev.decays[w_pos].at(1).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(1).type+2 );
 
         // partonic
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(0).type-10 );
         ev.decays[w_pos].at(1).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(1).type+10 );
-        if(!test_event(ev, false))
+        if(!test_event(ev, unknown))
           return false;
         ev.decays[w_pos].at(0).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(0).type+10 );
         ev.decays[w_pos].at(1).type = static_cast<ParticleID>(
                                         ev.decays[w_pos].at(1).type-10 );
 
         // double check that we undid all changes
-        if(!test_event(ev, true))
+        if(!test_event(ev, FKL))
           return false;
 
         // 1->3 decay
         ev.decays[w_pos].emplace_back(
             HEJ::Particle{photon, fastjet::PseudoJet(0,0,0,0), {}}
         );
-        if(!test_event(ev, false))
+        if(!test_event(ev, unknown))
           return false;
         ev.decays[w_pos].pop_back();
 
         // invalid secondary decay
         ev.decays[0] = decay_W(ev.outgoing[0]);
         ev.decays[0].at(0).type = ev.outgoing[0].type;
         ev.decays[0].at(1).type = gluon;
-        if(!test_event(ev, false))
+        if(!test_event(ev, unknown))
           return false;
       }
     }
     return true;
   }
 
   // Check Z decays
   bool check_Z_decay() {
     using namespace HEJ::pid;
     auto ev = new_event();
     for(size_t z_pos = 1; z_pos<ev.outgoing.size()-1; ++z_pos){
       auto ev = new_event();
       ev.outgoing[z_pos].type = Z_photon_mix;
       ev.outgoing.back().type = u;
       ev.incoming.back().type = u;
+      repair_momenta(ev);
 
       // no decay
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
 
       // working decay (Z -> e-,e+)
       ev.decays[z_pos] = decay_Z(ev.outgoing[z_pos]);
-      if(!test_event(ev, true))
+      if(!test_event(ev, FKL))
         return false;
 
       // replace e- -> mu-
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type+2 );
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type-2 );
 
       // replace e+ -> mu+
       ev.decays[z_pos].at(1).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(1).type-2 );
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
 
       // all mu
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type+2 );
-      if(!test_event(ev, true))
+      if(!test_event(ev, FKL))
         return false;
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type-2 );
       ev.decays[z_pos].at(1).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(1).type+2 );
 
       // replace e- -> nu_e
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type+1 );
-      if(!test_event(ev, false))
+      if(!test_event(ev, invalid))
         return false;
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type-1 );
 
       // replace e+ -> nu_e_bar
       ev.decays[z_pos].at(1).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(1).type-1 );
-      if(!test_event(ev, false))
+      if(!test_event(ev, invalid))
         return false;
 
       // neutrino-antineutrino
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type+1 );
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type-1 );
       ev.decays[z_pos].at(1).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(1).type+1 );
 
       // partonic
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type-10 );
       ev.decays[z_pos].at(1).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(1).type+10 );
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
       ev.decays[z_pos].at(0).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(0).type+10 );
       ev.decays[z_pos].at(1).type = static_cast<ParticleID>(
                                       ev.decays[z_pos].at(1).type-10 );
 
       // double check that we undid all changes
-      if(!test_event(ev, true))
+      if(!test_event(ev, FKL))
         return false;
 
       // 1->3 decay
       ev.decays[z_pos].emplace_back(
           HEJ::Particle{photon, fastjet::PseudoJet(0,0,0,0), {}}
       );
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
       ev.decays[z_pos].pop_back();
 
       // invalid secondary decay
       ev.decays[0] = decay_Z(ev.outgoing[0]);
       ev.decays[0].at(0).type = ev.outgoing[0].type;
       ev.decays[0].at(1).type = gluon;
-      if(!test_event(ev, false))
+      if(!test_event(ev, unknown))
         return false;
     }
     return true;
   }
 
   // Check 2 bosons
   bool check_2_boson() {
     const std::vector<std::string> bosons {"Wp", "Wm", "Z_photon_mix", "h"};
 
     // Candidate decays
     std::unordered_map<std::string, std::vector<std::string> > test_decay;
     test_decay["Wp"] = {"nu_e","e+"};
     test_decay["Wm"] = {"e-", "nu_e_bar"};
     test_decay["Z_photon_mix"]  = {"e-", "e+"};
     test_decay["h"]  = {"gamma", "gamma"};
 
     size_t njet = 6;
     const std::vector<std::string> all_quarks{"-4","-1","1","2","3","4"};
     for(std::string const & q1 : all_quarks){
       for(std::string const & q2 : all_quarks){
         for(std::string const & b1 : bosons){
           for(std::string const & b2 : bosons){
             std::array<std::string,2> in {q1, q2};
             std::vector<std::string>  out(njet, "g");
             out.front() = q1;
             out.back()  = q2;
 
             if( !couple_quark(b1, out.front()) ||
                 !couple_quark(b2, out.back() )
               ) { continue; } // skip incompatible
 
             std::unordered_map< size_t, std::vector<std::string> > decays;
 
             // Boson 1
             out.emplace_back(b1);
             decays.emplace( out.size()-1, test_decay[b1] );
             // Boson 2
             out.emplace_back(b2);
             decays.emplace( out.size()-1, test_decay[b2] );
 
             auto ev = rapidity_order_ps(in, out, false, decays)
                       .cluster(JET_DEF, MIN_JET_PT);
 
-            if(ev.type() == HEJ::event_type::bad_final_state) {
-              std::cerr << "Event was expected to not be a bad final-state\n"
+            if(ev.type() == invalid) {
+              std::cerr << "Event was expected to be valid\n"
                         << ev << std::endl;
               return false;
             }
           }
         }
       }
     }
     return true;
   }
 
   // Check reconstruction
   bool check_reconstruction() {
 
     // reconstruct_intermediate doesn't support reconstruct Higgs!
     const std::vector<std::string> bosons {"Wp", "Wm", "Z_photon_mix"};
 
     // Candidate decays
     std::unordered_map<std::string, std::vector<std::string> > test_decay;
     test_decay["Wp"] = {"nu_e","e+"};
     test_decay["Wm"] = {"e-", "nu_e_bar"};
     test_decay["Z_photon_mix"]  = {"e-", "e+"};
 
     size_t njet = 2;
     const std::vector<std::string> all_quarks{"-4","-1","1","2","3","4"};
 
     // Single-boson Reconstruction
     for(std::string const & q1 : all_quarks){
       for(std::string const & q2 : all_quarks){
         for(std::string const & boson : bosons){
           std::array<std::string,2> in {q1, q2};
           std::vector<std::string>  out(njet, "g");
           out.front() = q1;
           out.back()  = q2;
 
           // skip incompatible
           if( !couple_quark(boson, out.front()) ) { continue; }
 
           // Add decay products to outgoing
           out.insert(out.end(), test_decay[boson].begin(), test_decay[boson].end());
 
           // Generate event, requesting reconstruction
           auto ev = rapidity_order_ps(in, out, true)
                     .cluster(JET_DEF, MIN_JET_PT);
 
           // Find and compare boson types
           HEJ::ParticleID boson_pid = HEJ::to_ParticleID(boson);
           size_t boson_idx = ev.decays().begin() -> first;
           auto res_boson = ev.outgoing().at(boson_idx);
 
           if(res_boson.type != boson_pid) {
             std::cerr << "Boson " <<  name(boson_pid)
                       << " reconstructed to incorrect type " << name(res_boson.type)
                       << "\n" << ev << std::endl;
             return false;
           }
         }
       }
     }
 
     // Two-boson Reconstruction
     /**
      * Note:
      * Event::reconstruct_intermediate() only supports W-boson reconstructions
      * at 2-boson.
      */
     const std::vector< std::vector<std::string> > two_bosons = {
       {"Wp", "Wm"}, {"Wp", "Wp"}, {"Wm", "Wm"}
     };
 
     for(auto const & bosons : two_bosons){
       for(std::string const & q1 : all_quarks){
         for(std::string const & q2 : all_quarks){
           std::string b1 = bosons[0];
           std::string b2 = bosons[1];
 
           std::array<std::string,2> in {q1, q2};
           std::vector<std::string>  out(njet, "g");
           out.front() = q1;
           out.back()  = q2;
 
           // skip incompatible
           if( !couple_quark(b1, out.front()) ||
               !couple_quark(b2, out.back() )
             ) { continue; } // skip incompatible
 
           // Add decay products to outgoing
           out.insert(out.end(), test_decay[b1].begin(), test_decay[b1].end());
           out.insert(out.end(), test_decay[b2].begin(), test_decay[b2].end());
 
           auto ev = rapidity_order_ps(in, out, true)
                     .cluster(JET_DEF, MIN_JET_PT);
 
           // Expecting 2 decays
           if(ev.decays().size() != bosons.size()) {
               std::cerr << "Expect 2 decays\n" << ev << std::endl;
               return false;
           }
 
           // Expected bosons
           std::vector<HEJ::ParticleID> expected_bosons = {
             HEJ::to_ParticleID(b1),
             HEJ::to_ParticleID(b2)
           };
           std::sort(begin(expected_bosons), end(expected_bosons));
 
           // Reconstructed bosons
           auto decays = ev.decays();
           std::vector<HEJ::ParticleID> res_bosons;
           std::transform(
             cbegin(decays), cend(decays), std::back_inserter(res_bosons),
             [&ev] (auto const & decay) -> HEJ::ParticleID {
               return ev.outgoing().at(decay.first).type;
             }
           );
           std::sort(begin(res_bosons), end(res_bosons));
 
           // Matching?
           if(expected_bosons != res_bosons) {
             std::cerr << "Reconstructed bosons did not match expectation ( ";
             std::copy(
               expected_bosons.begin(), expected_bosons.end(),
               std::ostream_iterator<int>(std::cerr, "  ")
             );
             std::cerr << ")\n" << ev << std::endl;
             return false;
           }
 
         }
       }
     }
 
     return true;
   }
 } // namespace anonymous
 
 
 int main() {
   if(!check_fkl()) return EXIT_FAILURE;
   if(!check_W_decay()) return EXIT_FAILURE;
   if(!check_Z_decay()) return EXIT_FAILURE;
   if(!check_2_boson()) return EXIT_FAILURE;
   if(!check_reconstruction()) return EXIT_FAILURE;
 
   return EXIT_SUCCESS;
 }
diff --git a/t/test_scale_arithmetics.cc b/t/test_scale_arithmetics.cc
index f94e4e4..aec8ab8 100644
--- a/t/test_scale_arithmetics.cc
+++ b/t/test_scale_arithmetics.cc
@@ -1,104 +1,105 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019-2020
  *  \copyright GPLv2 or later
  */
 #include "hej_test.hh"
 
 #include <algorithm>
 #include <iomanip>
 #include <iostream>
 #include <memory>
 #include <cstdlib>
 #include <utility>
 
 #include "HEJ/Config.hh"
 #include "HEJ/Event.hh"
 #include "HEJ/EventReweighter.hh"
 #include "HEJ/make_RNG.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/RNG.hh"
 #include "HEJ/stream.hh"
 #include "HEJ/YAMLreader.hh"
 
 #include "LHEF/LHEF.h"
 
 namespace {
   constexpr double EP = 1e-13;
 
   void dump(HEJ::Event const & ev){
     {
       LHEF::Writer writer{std::cout};
       std::cout << std::setprecision(6);
       writer.hepeup = to_HEPEUP(ev, nullptr);
       writer.writeEvent();
     }
     std::cout << "Rapidity ordering:\n";
     for(auto const & part: ev.outgoing()){
       std::cout << std::setw(2) << part.type << ": "<<  std::setw(7) << part.rapidity() << std::endl;
     }
   }
 }
 
 int main(int argn, char** argv){
   if(argn != 3){
     std::cerr << "\n# Usage:\n."<< argv[0] <<" config.yml input_file.lhe\n\n";
     return EXIT_FAILURE;
   }
   HEJ::Config config = HEJ::load_config(argv[1]);
   config.scales = HEJ::to_ScaleConfig(
       YAML::Load("scales: [H_T, 1 * H_T, 2/2 * H_T, 2*H_T/2, H_T/2*2, H_T/2/2*4, H_T*H_T/H_T]")
   );
+  config.treat[HEJ::event_type::unknown] = HEJ::EventTreatment::keep;
 
   HEJ::istream in{argv[2]};
   LHEF::Reader reader{in};
 
   std::shared_ptr<HEJ::RNG> ran{
     HEJ::make_RNG(config.rng.name, config.rng.seed)};
 
   HEJ::ScaleGenerator scale_gen{
     config.scales.base,
     config.scales.factors,
     config.scales.max_ratio
   };
 
   HEJ::EventReweighter resum{
     reader.heprup,
     std::move(scale_gen),
     to_EventReweighterConfig(config),
     ran
   };
 
   std::size_t i = 0;
   while(reader.readEvent()){
     ++i;
 
     HEJ::Event::EventData data{reader.hepeup};
     shuffle_particles(data);
 
     HEJ::Event event{
       data.cluster(
         config.resummation_jets.def,
         config.resummation_jets.min_pt
       )
     };
 
     auto resummed = resum.reweight(event, config.trials);
     for(auto && ev: resummed) {
       for(auto &&var: ev.variations()) {
         if(std::abs(var.muf - ev.central().muf) > EP) {
           std::cerr
             << std::setprecision(15)
             << "unequal scales: " << var.muf
             << " != " << ev.central().muf << '\n'
             << "in resummed event:\n";
           dump(ev);
           std::cerr << "\noriginal event:\n";
           dump(event);
           return EXIT_FAILURE;
         }
       }
     }
   }
   return EXIT_SUCCESS;
 }