diff --git a/current_generator/include/write.frm b/current_generator/include/write.frm
index fbfe623..aadb6a8 100644
--- a/current_generator/include/write.frm
+++ b/current_generator/include/write.frm
@@ -1,166 +1,165 @@
 */**
 *  \brief Procedures for writing current contraction headers
 *
 *  \authors   The HEJ collaboration (see AUTHORS for details)
 *  \date      2020
 *  \copyright GPLv2 or later
 */
 #ifndef `$HEJWriteIncluded'
 #$HEJWriteIncluded = 1;
 
 * Write start of C++ header to `OUTPUT'
 #procedure WriteHeader(OUTPUT)
 
    #write <`OUTPUT'> "#pragma once"
    #write <`OUTPUT'> ""
    #write <`OUTPUT'> "#include <complex>"
    #write <`OUTPUT'> "#include \"HEJ/helicity.hh\""
    #write <`OUTPUT'> "#include \"HEJ/LorentzVector.hh\""
-   #write <`OUTPUT'> "#include \"HEJ/Tensor.hh\""
    #write <`OUTPUT'> ""
    #write <`OUTPUT'> "namespace HEJ {"
 
 #endprocedure
 
 * Write optimised expression to C++ header `OUTPUT'
 #procedure WriteOptimised(OUTPUT,EXPRESSION,NUMHELICITIES,?MOMENTA)
 
    #write <`OUTPUT'> "   template<%"
    #define FIRST "1"
    #do i=1,`NUMHELICITIES'
       #ifdef `FIRST'
          #undefine FIRST
          #write <`OUTPUT'> "Helicity%"
       #else
          #write <`OUTPUT'> ", Helicity%"
       #endif
    #enddo
    #write <`OUTPUT'> ">"
    #write <`OUTPUT'> "   std::complex<double> `EXPRESSION'("
       #call WriteMomenta(`?MOMENTA')
    #write <`OUTPUT'> "\n   );\n"
 
    #call WriteOptimisedHelper(`OUTPUT',`EXPRESSION',`NUMHELICITIES',`?MOMENTA')
 
 #endprocedure
 
 *INTERNAL PROCEDURE
 #procedure WriteOptimisedHelper(OUTPUT,EXPRESSION,NUMHELICITIES,?REST)
 
    #if `NUMHELICITIES' > 0
       #do H={+,-}
          #call WriteOptimisedHelper(`OUTPUT',`EXPRESSION',{`NUMHELICITIES'-1},`?REST',`H')
       #enddo
 
    #else
 
       #define HELSTRING ""
       #define TEMPLATEARGS ""
       #define MOMENTA ""
       #do ARG={`?REST'}
          #if ("`ARG'" == "+") || ("`ARG'" == "-")
 *           arguments that define helicities
             #redefine HELSTRING "`HELSTRING'`ARG'"
             #if "`TEMPLATEARGS'" != ""
                #redefine TEMPLATEARGS "`TEMPLATEARGS',"
             #endif
             #if "`ARG'" == "+"
                #redefine TEMPLATEARGS "`TEMPLATEARGS'Helicity::plus"
             #else
                #redefine TEMPLATEARGS "`TEMPLATEARGS'Helicity::minus"
             #endif
 
          #else
 *           arguments that define momenta
             #if "`MOMENTA'" != ""
                #redefine MOMENTA "`MOMENTA',"
             #endif
             #redefine MOMENTA "`MOMENTA'`ARG'"
          #endif
       #enddo
       #optimize [`EXPRESSION'`HELSTRING']
       #write "operations in [`EXPRESSION'`HELSTRING']: `optimvalue_'"
       #write <`OUTPUT'> "   template<>"
       #write <`OUTPUT'> "   inline std::complex<double> `EXPRESSION'<%"
 *     we use a loop here because otherwise FORM will insert line breaks
 *     if the string is too large
       #define FIRST "1"
       #do TEMPLATEARG={`TEMPLATEARGS'}
          #ifdef `FIRST'
             #undefine FIRST
          #else
             #write <`OUTPUT'> ", %"
          #endif
          #write <`OUTPUT'> "`TEMPLATEARG'%"
       #enddo
       #write <`OUTPUT'> ">("
       #if termsin([`EXPRESSION'`HELSTRING']) > 0
          #call WriteMomenta(`MOMENTA')
       #else
          #call WriteMomentaCommentedOut(`MOMENTA')
       #endif
       #write <`OUTPUT'> "\n   ) {"
       #write <`OUTPUT'> "        static constexpr std::complex<double> i_{0., 1.};"
       #write <`OUTPUT'> "        (void) i_; //potentially unused"
       #if `optimmaxvar_' > 0
          #write <`OUTPUT'> "     std::complex<double> %"
          #define FIRST "1"
          #do i=1,`optimmaxvar_'
             #ifdef `FIRST'
                #undefine FIRST
                #write <`OUTPUT'> "Z`i'_%"
             #else
                #write <`OUTPUT'> ", Z`i'_%"
             #endif
          #enddo
          #write <`OUTPUT'> ";"
       #endif
       #write <`OUTPUT'> "   %O"
       #write <`OUTPUT'> "      return %E;" [`EXPRESSION'`HELSTRING']
       #write <`OUTPUT'> "   }\n" [`EXPRESSION'`HELSTRING']
       #clearoptimize
    #endif
 
 #endprocedure
 
 *INTERNAL PROCEDURE
 * Write momenta as C++ function arguments to `OUTPUT'
 #procedure WriteMomenta(?MOMENTA)
 
    #define FIRST "1"
    #do P={`?MOMENTA'}
       #ifdef `FIRST'
          #undefine FIRST
          #write <`OUTPUT'> "      CLHEP::HepLorentzVector const & `P'%"
       #else
          #write <`OUTPUT'> ",\n      CLHEP::HepLorentzVector const & `P'%"
       #endif
    #enddo
 
 #endprocedure
 
 *INTERNAL PROCEDURE
 * Write momenta as C++ function arguments to `OUTPUT'
 * with momentum names commented out
 #procedure WriteMomentaCommentedOut(?MOMENTA)
 
    #define FIRST "1"
    #do P={`?MOMENTA'}
       #ifdef `FIRST'
          #undefine FIRST
          #write <`OUTPUT'> "      CLHEP::HepLorentzVector const & /* `P' */%"
       #else
          #write <`OUTPUT'> ",\n      CLHEP::HepLorentzVector const & /* `P' */%"
       #endif
    #enddo
 
 #endprocedure
 
 * Write end of C++ header to `OUTPUT'
 #procedure WriteFooter(OUTPUT)
 
    #write <`OUTPUT'> "}"
 
 #endprocedure
 
 #endif
diff --git a/doc/developer_manual/currents.tex b/doc/developer_manual/currents.tex
index 9dc2a7a..8f0dc12 100644
--- a/doc/developer_manual/currents.tex
+++ b/doc/developer_manual/currents.tex
@@ -1,694 +1,693 @@
 \section{Currents}
 \label{sec:currents_impl}
 The following section contains a list of all the currents implemented
 in \HEJ. Clean up of the code structure is ongoing. All $W$+Jet currents
 are located in \texttt{src/Wjets.cc}, all Higgs+Jets currents are
 defined in \texttt{src/Hjets.cc}, and pure jet currents are defined in
 in \texttt{src/jets.cc}. All of these have their own separate header
 files: \texttt{include/HEJ/Wjets.hh}, \texttt{include/HEJ/Hjets.hh} and
 \texttt{include/HEJ/jets.hh} respectively.
 
 The naming convention for the current contraction $\left\|S_{f_1 f_2\to f_1
 f_2}\right\|^2$ is \lstinline!ME_[Boson]_[subleading-type]_[incoming]!. For
 example \lstinline!ME_W_unob_qq! corresponds to the contraction $j_W^\mu
 j_{\text{uno}, \mu}$ ($qQ\to \bar{q}WQg$). For bosons on the same side as the
 subleading we drop the connecting underscore, e.g. \lstinline!ME_Wuno_qq!
 gives $j_{W,\text{uno}}^\mu j_\mu$ ($qQ\to g\bar{q}WQ$).
 
 \subsection{Pure Jets}
 \subsubsection{Quark}
 \label{sec:current_quark}
 
 \begin{align}
 j_\mu(p_i,p_j)=\bar{u}(p_i)\gamma_\mu u(p_j)
 \end{align}
 The exact for depends on the helicity and direction (forward/backwards) for the quarks. Currently all different contractions of incoming and outgoing states are defined in \lstinline!joi!, \lstinline!jio! and \lstinline!joo!.
 
 \subsubsection{Gluon}
 In \HEJ the currents for gluons and quarks are the same, up to a colour factor $K_g/C_F$, where
 \begin{align}
  K_g(p_1^-, p_a^-) = \frac{1}{2}\left(\frac{p_1^-}{p_a^-} + \frac{p_a^-}{p_1^-}\right)\left(C_A -
           \frac{1}{C_A}\right)+\frac{1}{C_A}.
 \end{align}
 Thus we can just reuse the results from sec.~\ref{sec:current_quark}.
 
 \subsubsection{Single unordered gluon}
 Configuration $q(p_a) \to g(p_1) q(p_2) g^*(\tilde{q}_2)$~\cite{Andersen:2017kfc}
 \begin{align}
 \label{eq:juno}
 \begin{split}
 &j^{{\rm uno}\; \mu\ cd}(p_2,p_1,p_a) = i \varepsilon_{1\nu} \left(  T_{2i}^{c}T_{ia}^d\
 \left(U_1^{\mu\nu}-L^{\mu\nu} \right) + T_{2i}^{d}T_{ia}^c\ \left(U_2^{\mu\nu} +
 L^{\mu\nu} \right) \right). \\
 U_1^{\mu\nu} &= \frac1{s_{21}} \left( j_{21}^\nu j_{1a}^\mu + 2 p_2^\nu
 j_{2a}^\mu \right) \qquad  \qquad U_2^{\mu\nu} = \frac1{t_{a1}} \left( 2
 j_{2a}^\mu p_a^\nu - j_{21}^\mu  j_{1a}^\nu \right) \\
 L^{\mu\nu} &= \frac1{t_{a2}} \left(-2p_1^\mu j_{2a}^\nu+2p_1.j_{2a}
 g^{\mu\nu} + (\tilde{q}_1+\tilde{q}_2)^\nu j_{2a}^\mu + \frac{t_{b2}}{2} j_{2a}^\mu \left(
 \frac{p_2^\nu}{p_1.p_2} + \frac{p_b^\nu}{p_1.p_b} \right) \right) ,
 \end{split}
 \end{align}
 $j^{{\rm uno}\; \mu}$ is currently not calculated as a separate current, but always as needed for the ME (i.e. in \lstinline!ME_unob_XX!).
 
 \subsubsection{Extremal $q\bar{q}$}
 In Pure jets we also include the subleading process which arises when
 an incoming gluon splits into a $q\bar{q}$ pair. This splitting impact
 factor is related to the unordered current by simple means of a
 crossing symmetry.
 
 \subsubsection{Central $q\bar{q}$}
 The final subleading process type in the Pure Jets case is Central
 $q\bar{q}$. In this process type, we have two currents scattering off
 of each other, but this time, via an effective vertex, which connects
 together two FKL chains. Each FKL chain t-channel gluon splits into a
 $q\bar{q}$ and this results in a quark and anti-quark in between the
 most forward and backward jets. One can see an example of such a
 process in Figure \ref{fig:qqbarcen_example}.
 
 \begin{figure}[ht]
 \centering
 \includegraphics[]{Cenqqbar_jx}
 \caption{Momentum labeling for a central $q\bar{q}$ process.}
 \label{fig:qqbarcen_example}
 \end{figure}
 
 As the new central $q\bar{q}$ piece contains the quark propagator, we will treat
 this as part of the skeleton process.  This means that we do not impose strong ordering
 between the $q\bar{q}$-pair taking
 \begin{align}
 \label{eq:cenqqbarraporder}
   y_1 \ll y_q,y_{\bar{q}} \ll y_n.
 \end{align}
 The HEJ Matrix element for this process can be calculated as:
   \begin{align}
   \label{eq:Mcentral}
     i\mathcal{M} &= g_s^4 T^d_{1a} T^e_{nb}\ \frac{j_{\mu}(p_a,p_1)\ X^{ab\, \mu
     \nu}_{{\rm cen}}(p_q,p_{\bar{q}},q_1,q_3)\
     j_{\nu}(p_b,p_n)}{t_{a1}t_{bn}}.
   \end{align}
 where $X^{\mu \nu}_{\rm cen}$ is given by:
 \begin{equation}
   \label{eq:Xcen}
   \begin{split}
   X^{\mu \nu}_{\rm cen} ={}&\frac{f^{ced}T^c_{q\bar{q}}}{s_{q\bar{q}}}
      \left(\eta^{\mu \nu} X_{sym}^\sigma + V^{\mu \nu \sigma}_{\bar{q}g} \right)
      \bar{u}(p_q) \gamma^\sigma u(p_{\bar{q}}) \\ & \qquad + \frac{i
      T^d_{qj}T^e_{j\bar{q}}}{(q_1-p_q)^2} X^{\mu\nu}_{\text{qprop}} - \frac{i
      T^e_{qj}T^d_{j\bar{q}}}{(q_1-p_{\bar{q}})^2} X^{\mu\nu}_{\text{crossed}}\,,
  \end{split}
 \end{equation}
 with
 \begin{align}
   \label{eq:Xsym}
   X_{sym}^\sigma ={}& q_1^2 \left(
   \frac{p_a^\sigma}{s_{aq} + s_{a\bar{q}}} +  \frac{p_1^\sigma}{s_{1q} + s_{1\bar{q}}}
   \right) - q_3^2 \left(
   \frac{p_b^\sigma}{s_{bq} + s_{b\bar{q}}} +  \frac{p_n^\sigma}{s_{nq} + s_{n\bar{q}}}
   \right)\,,\\
   \label{eq:V3g}
   V_{3g}^{\mu\nu\sigma} ={}& (q_1 + p_q + p_{\bar{q}})^\nu \eta^{\mu\sigma}
   + (q_3 - p_q - p_{\bar{q}})^\mu \eta^{\nu\sigma}
   - (q_1 + q_3)^\sigma \eta^{\mu\nu}\,,\\
   \label{eq:Xqprop}
   X^{\mu\nu}_{\text{qprop}} ={}& \frac{\langle p_q | \mu (q_1-p_q) \nu | p_{\bar{q}}\rangle}{(q_1-p_q)^2}\,,\\
   \label{eq:Xcrossed}
   X^{\mu\nu}_{\text{crossed}} ={}& \frac{\langle p_q | \nu (q_1-p_{\bar{q}}) \mu | p_{\bar{q}}\rangle}{(q_1-p_{\bar{q}})^2}\,,
 \end{align}
 and $q_3 = q_1 - p_q - p_{\bar{q}}$.
 
 \subsection{Higgs}
 Different rapidity orderings \todo{give name of functions}
 \begin{enumerate}
 \item $qQ\to HqQ/qHQ/qQH$ (any rapidity order, full LO ME) $\Rightarrow$ see~\ref{sec:V_H}
 \item $qg\to Hqg$ (Higgs outside quark) $\Rightarrow$ see~\ref{sec:V_H}
 \item $qg\to qHg$ (central Higgs) $\Rightarrow$ see~\ref{sec:V_H}
 \item $qg\to qgH$ (Higgs outside gluon) $\Rightarrow$ see~\ref{sec:jH_mt}
 \item $gg\to gHg$ (central Higgs) $\Rightarrow$ see~\ref{sec:V_H}
 \item $gg\to ggH$ (Higgs outside gluon) $\Rightarrow$ see~\ref{sec:jH_mt}
 \end{enumerate}
 
 \subsubsection{Higgs gluon vertex}
 \label{sec:V_H}
 The coupling of the Higgs boson to gluons via a virtual quark loop can be written as
 \begin{align}
 \label{eq:VH}
 V^{\mu\nu}_H(q_1, q_2) = \mathgraphics{V_H.pdf} &=
 \frac{\alpha_s m^2}{\pi v}\big[
 g^{\mu\nu} T_1(q_1, q_2) - q_2^\mu q_1^\nu T_2(q_1, q_2)
 \big]\, \\
 &\xrightarrow{m \to \infty} \frac{\alpha_s}{3\pi
     v} \left(g^{\mu\nu} q_1\cdot q_2 - q_2^\mu q_1^\nu\right).
 \end{align}
 The outgoing momentum of the Higgs boson is $p_H = q_1 - q_2$.
 As a contraction with two currents this by implemented in \lstinline!cHdot! inside \texttt{src/Hjets.cc}.
 The form factors $T_1$ and $T_2$ are then given by~\cite{DelDuca:2003ba}
 \begin{align}
 \label{eq:T_1}
 T_1(q_1, q_2) ={}& -C_0(q_1, q_2)\*\left[2\*m^2+\frac{1}{2}\*\left(q_1^2+q_2^2-p_H^2\right)+\frac{2\*q_1^2\*q_2^2\*p_H^2}{\lambda}\right]\notag\\
 &-\left[B_0(q_2)-B_0(p_H)\right]\*\frac{q_2^2}{\lambda}\*\left(q_2^2-q_1^2-p_H^2\right)\notag\\
 &-\left[B_0(q_1)-B_0(p_H)\right]\*\frac{q_1^2}{\lambda}\*\left(q_1^2-q_2^2-p_H^2\right)-1\,,\displaybreak[0]\\
 \label{eq:T_2}
 T_2(q_1, q_2) ={}& C_0(q_1,
 q_2)\*\left[\frac{4\*m^2}{\lambda}\*\left(p_H^2-q_1^2-q_2^2\right)-1-\frac{4\*q_1^2\*q_2^2}{\lambda}
 -
 \frac{12\*q_1^2\*q_2^2\*p_H^2}{\lambda^2}\*\left(q_1^2+q_2^2-p_H^2\right)\right]\notag\\
 &-\left[B_0(q_2)-B_0(p_H)\right]\*\left[\frac{2\*q_2^2}{\lambda}+\frac{12\*q_1^2\*q_2^2}{\lambda^2}\*\left(q_2^2-q_1^2+p_H^2\right)\right]\notag\\
 &-\left[B_0(q_1)-B_0(p_H)\right]\*\left[\frac{2\*q_1^2}{\lambda}+\frac{12\*q_1^2\*q_2^2}{\lambda^2}\*\left(q_1^2-q_2^2+p_H^2\right)\right]\notag\\
 &-\frac{2}{\lambda}\*\left(q_1^2+q_2^2-p_H^2\right)\,,
 \end{align}
 where we have used the scalar bubble and triangle integrals
 \begin{align}
 \label{eq:B0}
 B_0\left(p\right) ={}& \int \frac{d^dl}{i\pi^{\frac{d}{2}}}
 \frac{1}{\left(l^2-m^2\right)\left((l+p)^2-m^2\right)}\,,\\
 \label{eq:C0}
 C_0\left(p,q\right) ={}& \int \frac{d^dl}{i\pi^{\frac{d}{2}}} \frac{1}{\left(l^2-m^2\right)\left((l+p)^2-m^2\right)\left((l+p-q)^2-m^2\right)}\,,
 \end{align}
 and the K\"{a}ll\'{e}n function
 \begin{equation}
 \label{eq:lambda}
 \lambda = q_1^4 + q_2^4 + p_H^4 - 2\*q_1^2\*q_2^2 - 2\*q_1^2\*p_H^2- 2\*q_2^2\*p_H^2\,.
 \end{equation}
 The Integrals as such are provided by \QCDloop{} (see wrapper functions \lstinline!B0DD! and \lstinline!C0DD! in \texttt{src/Hjets.cc}).
 In the code we are sticking to the convention of~\cite{DelDuca:2003ba}, thus instead of the $T_{1/2}$ we implement (in the functions \lstinline!A1! and \lstinline!A2!)
 \begin{align}
 \label{eq:A_1}
 A_1(q_1, q_2) ={}& \frac{i}{16\pi^2}\*T_2(-q_1, q_2)\,,\\
 \label{eq:A_2}
 A_2(q_1, q_2) ={}& -\frac{i}{16\pi^2}\*T_1(-q_1, q_2)\,.
 \end{align}
 
 \subsubsection{Peripheral Higgs emission - Finite quark mass}
 \label{sec:jH_mt}
 
 We describe the emission of a peripheral Higgs boson close to a
 scattering gluon with an effective current. In the following we consider
 a lightcone decomposition of the gluon momenta, i.e. $p^\pm = E \pm p_z$
 and $p_\perp = p_x + i p_y$. The incoming gluon momentum $p_a$ defines
 the $-$ direction, so that $p_a^+ = p_{a\perp} = 0$. The outgoing
 momenta are $p_1$ for the gluon and $p_H$ for the Higgs boson. We choose
 the following polarisation vectors:
 \begin{equation}
   \label{eq:pol_vectors}
   \epsilon_\mu^\pm(p_a) = \frac{j_\mu^\pm(p_1, p_a)}{\sqrt{2}
     \bar{u}^\pm(p_a)u^\mp(p_1)}\,, \quad \epsilon_\mu^{\pm,*}(p_1) = -\frac{j_\mu^\pm(p_1, p_a)}{\sqrt{2}
     \bar{u}^\mp(p_1)u^\pm(p_a)}\,.
 \end{equation}
 Following~\cite{DelDuca:2001fn}, we introduce effective polarisation
 vectors to describe the contraction with the Higgs-boson production
 vertex eq.~\eqref{eq:VH}:
 \begin{align}
   \label{eq:eps_H}
   \epsilon_{H,\mu}(p_a) = \frac{T_2(p_a, p_a-p_H)}{(p_a-p_H)^2}\big[p_a\cdot
   p_H\epsilon_\mu(p_a) - p_H\cdot\epsilon(p_a) p_{a,\mu}\big]\,,\\
   \epsilon_{H,\mu}^*(p_1) = -\frac{T_2(p_1+p_H, p_1)}{(p_1+p_H)^2}\big[p_1\cdot
   p_H\epsilon_\mu^*(p_1) - p_H\cdot\epsilon^*(p_1) p_{1,\mu}\big]\,,
 \end{align}
 We also employ the usual short-hand notation
 \begin{equation}
   \label{eq:spinor_helicity}
   \spa i.j = \bar{u}^-(p_i)u^+(p_j)\,,\qquad \spb i.j =
   \bar{u}^+(p_i)u^-(p_j)\,, \qquad[ i | H | j\rangle = j_\mu^+(p_i, p_j)p_H^\mu\,.
 \end{equation}
 Without loss of generality, we consider only the case where the incoming
 gluon has positive helicity. The remaining helicity configurations can
 be obtained through parity transformation.
 
 Labelling the effective current by the helicities of the gluons we obtain
 for the same-helicity case
 \begin{equation}
   \label{eq:jH_same_helicity}
   \begin{split}
     j_{H,\mu}^{++}{}&(p_1,p_a,p_H) =
     \frac{m^2}{\pi v}\bigg[\\
       &-\sqrt{\frac{2p_1^-}{p_a^-}}\frac{p_{1\perp}^*}{|p_{1\perp}|}\frac{t_2}{\spb a.1}\epsilon^{+,*}_{H,\mu}(p_1)
       +\sqrt{\frac{2p_a^-}{p_1^-}}\frac{p_{1\perp}^*}{|p_{1\perp}|}\frac{t_2}{\spa 1.a}\epsilon^{+}_{H,\mu}(p_a)\\
       &+ [1|H|a\rangle \bigg(
         \frac{\sqrt{2}}{\spa 1.a}\epsilon^{+}_{H,\mu}(p_a)
         +         \frac{\sqrt{2}}{\spb a.1}\epsilon^{+,*}_{H,\mu}(p_1)-\frac{\spa 1.a T_2(p_a, p_a-p_H)}{\sqrt{2}(p_a-p_H)^2}\epsilon^{+,*}_{\mu}(p_1)\\
         &
         \qquad
         -\frac{\spb a.1 T_2(p_1+p_H,
           p_1)}{\sqrt{2}(p_1+p_H)^2}\epsilon^{+}_{\mu}(p_a)-\frac{RH_4}{\sqrt{2}\spb a.1}\epsilon^{+,*}_{\mu}(p_1)+\frac{RH_5}{\sqrt{2}\spa 1.a}\epsilon^{+}_{\mu}(p_a)
         \bigg)\\
         &
         - \frac{[1|H|a\rangle^2}{2 t_1}(p_{a,\mu} RH_{10} - p_{1,\mu} RH_{12})\bigg]
   \end{split}
 \end{equation}
 with $t_1 = (p_a-p_1)^2$, $t_2 = (p_a-p_1-p_H)^2$ and $R = 8 \pi^2$. Eq.~\eqref{eq:jH_same_helicity}
 is implemented by \lstinline!g_gH_HC! in \texttt{src/Hjets.cc}
 \footnote{\lstinline!g_gH_HC! and \lstinline!g_gH_HNC! includes an additional
 $1/t_2$ factor, which should be in the Matrix element instead.}.
 
 The currents with a helicity flip is given through
 \begin{equation}
   \label{eq:jH_helicity_flip}
   \begin{split}
     j_{H,\mu}^{+-}{}&(p_1,p_a,p_H) =
     \frac{m^2}{\pi v}\bigg[\\
     &-\sqrt{\frac{2p_1^-}{p_a^-}}\frac{p_{1\perp}^*}{|p_{1\perp}|}\frac{t_2}{\spb
       a.1}\epsilon^{-,*}_{H,\mu}(p_1)
     +\sqrt{\frac{2p_a^-}{p_1^-}}\frac{p_{1\perp}}{|p_{1\perp}|}\frac{t_2}{\spb a.1}\epsilon^{+}_{H,\mu}(p_a)\\
     &+ [1|H|a\rangle \left(
     \frac{\sqrt{2}}{\spb a.1} \epsilon^{-,*}_{H,\mu}(p_1)
     -\frac{\spa 1.a T_2(p_a, p_a-p_H)}{\sqrt{2}(p_a-p_H)^2}\epsilon^{-,*}_{\mu}(p_1)
     - \frac{RH_4}{\sqrt{2}\spb a.1}\epsilon^{-,*}_{\mu}(p_1)\right)
     \\
     &+ [a|H|1\rangle \left(
       \frac{\sqrt{2}}{\spb a.1}\epsilon^{+}_{H,\mu}(p_a)
       -\frac{\spa 1.a
         T_2(p_1+p_H,p_1)}{\sqrt{2}(p_1+p_H)^2}\epsilon^{+}_{\mu}(p_a)
       +\frac{RH_5}{\sqrt{2}\spb a.1}\epsilon^{+}_{\mu}(p_a)
     \right)\\
     & - \frac{[1|H|a\rangle [a|H|1\rangle}{2 \spb a.1 ^2}(p_{a,\mu} RH_{10} - p_{1,\mu}
     RH_{12})\\
     &+ \frac{\spa 1.a}{\spb a.1}\bigg(RH_1p_{1,\mu}-RH_2p_{a,\mu}+2
     p_1\cdot p_H \frac{T_2(p_1+p_H, p_1)}{(p_1+p_H)^2} p_{a,\mu}
     \\
     &
     \qquad- 2p_a \cdot p_H \frac{T_2(p_a, p_a-p_H)}{(p_a-p_H)^2} p_{1,\mu}+ T_1(p_a-p_1, p_a-p_1-p_H)\frac{(p_1 + p_a)_\mu}{t_1}\\
     &\qquad-\frac{(p_1+p_a)\cdot p_H}{t_1} T_2(p_a-p_1, p_a-p_1-p_H)(p_1 - p_a)_\mu
     \bigg)
     \bigg]\,,
   \end{split}
 \end{equation}
 and implemented by \lstinline!g_gH_HNC! again in \texttt{src/Hjets.cc}.
 If we instead choose the gluon momentum in the $+$ direction, so that
 $p_a^- = p_{a\perp} = 0$, the corresponding currents are obtained by
 replacing $p_1^- \to p_1^+, p_a^- \to p_a^+,
 \frac{p_{1\perp}}{|p_{1\perp}|} \to -1$ in the second line of
 eq.~\eqref{eq:jH_same_helicity} and eq.~\eqref{eq:jH_helicity_flip} (see variables \lstinline!ang1a! and \lstinline!sqa1! in the implementation).
 
  The form factors $H_1,H_2,H_4,H_5, H_{10}, H_{12}$ are given in~\cite{DelDuca:2003ba}, and are implemented under their name in \texttt{src/Hjets.cc}. They reduce down to fundamental QCD integrals, which are again provided by \QCDloop.
 
 \subsubsection{Peripheral Higgs emission - Infinite top mass}
 \label{sec:jH_eff}
 
 To get the result with infinite top mass we could either take the limit $m_t\to \infty$ in~\eqref{eq:jH_helicity_flip} and~\eqref{eq:jH_same_helicity}, or use the \textit{impact factors} as given in~\cite{DelDuca:2003ba}. Both methods are equivalent, and lead to the same result. For the first one would find
 \begin{align}
 \lim_{m_t\to\infty} m_t^2 H_1    &= i \frac{1}{24 \pi^2}\\
 \lim_{m_t\to\infty} m_t^2 H_2    &=-i \frac{1}{24 \pi^2}\\
 \lim_{m_t\to\infty} m_t^2 H_4    &= i \frac{1}{24 \pi^2}\\
 \lim_{m_t\to\infty} m_t^2 H_5    &=-i \frac{1}{24 \pi^2}\\
 \lim_{m_t\to\infty} m_t^2 H_{10} &= 0 \\
 \lim_{m_t\to\infty} m_t^2 H_{12} &= 0.
 \end{align}
 \todo{double check this, see James thesis eq. 4.33}
 However only the second method is implemented in the code through \lstinline!C2gHgp!
 and \lstinline!C2gHgm! inside \texttt{src/Hjets.cc}, each function
 calculates the square of eq. (4.23) and (4.22) from~\cite{DelDuca:2003ba} respectively.
 
 \subsection{$W$+Jets}
 \label{sec:currents_W}
 \subsubsection{Quark+$W$}
 
 \begin{figure}
   \centering
   \begin{minipage}[b]{0.2\textwidth}
     \includegraphics[width=\textwidth]{Wbits.pdf}
   \end{minipage}
   \begin{minipage}[b]{0.1\textwidth}
     \centering{=}
     \vspace{0.7cm}
   \end{minipage}
   \begin{minipage}[b]{0.2\textwidth}
     \includegraphics[width=\textwidth]{Wbits2.pdf}
   \end{minipage}
   \begin{minipage}[b]{0.1\textwidth}
     \centering{+}
     \vspace{0.7cm}
   \end{minipage}
   \begin{minipage}[b]{0.2\textwidth}
     \includegraphics[width=\textwidth]{Wbits3.pdf}
   \end{minipage}
   \caption{The $j_W$ current is constructed from the two diagrams which
     contribute to the emission of a $W$-boson from a given quark line.}
   \label{fig:jW}
 \end{figure}
 For a $W$ emission we require a fermion. The $j_W$ current is actually a sum of
 two separate contributions, see figure~\ref{fig:jW}, one with a $W$-emission
 from the initial state, and one with the $W$-emission from the final state.
 Mathematically this can be seen as the following two
 terms~\cite{Andersen:2012gk}\todo{cite W subleading paper}:
 \begin{align}
   \label{eq:Weffcur1}
   j_W^\mu(p_a,p_{\ell},p_{\bar{\ell}}, p_1) =&\ \frac{g_W^2}{2}\
      \frac1{p_W^2-M_W^2+i\ \Gamma_W M_W}\ \bar{u}^-(p_\ell) \gamma_\alpha
                                                v^-(p_{\bar\ell})\nonumber \\
 & \cdot \left( \frac{ \bar{u}^-(p_1) \gamma^\alpha (\slashed{p}_W +
   \slashed{p}_1)\gamma^\mu u^-(p_a)}{(p_W+p_1)^2} +
 \frac{ \bar{u}^-(p_1)\gamma^\mu (\slashed{p}_a - \slashed{p}_W)\gamma^\alpha u^-(p_a)}{(p_a-p_W)^2} \right).
 \end{align}
 
 There are a couple of subtleties here. There is a minus sign
 distinction between the quark-anti-quark cases due to the fermion flow
 of the propagator in the current. Note that the type of $W$ emission
 (+ or -) will depend on the quark flavour, and that the handedness of
 the quark-line is given by whether its a quark or anti-quark.
 
 The FKL $W$ current is
-\todo{Text. The following is what's currently implemented in \lstinline!jW!}
 \begin{align}
   \label{eq:jW-}
     j^-_{W}(p_a, p_1, p_{\bar{l}}, p_{l}) ={}&
     \frac{2 \spa 1.l}{(p_1+p_W)^2}\*\Big(
     \spb 1.{\bar{l}} j^-(p_1, p_a) + \spb l.{\bar{l}}
     j^-(p_{l}, p_a)\Big)\notag\\
     & + \frac{2\spb a.{\bar{l}}}{(p_a-p_W)^2}\Big(
      \spa l.{\bar{l}} j^-(p_1, p_{\bar{l}}) + \spa a.l
     j^-(p_1, p_a)
       \Big)\,,\\
   \label{eq:jW+}
       j^+_{W}(p_a, p_1, p_{\bar{l}}, p_{l}) =& \big[j^-_{W}(p_a, p_1, p_l, p_{\bar{l}})\big]^*\,,
 \end{align}
 where the negative-helicity current is used for emission off a quark
-line and the positive-helicity current for emissions off antiquark.
+line and the positive-helicity current for emissions off antiquark. The
+implementation is in \texttt{include/currents.frm} inside the
+\texttt{current\_generator} (see section~\ref{sec:cur_gen}). To use it inside
+\FORM use the place-holder \lstinline!JW(h, mu, pa, p1, plbar, pl)!.
 
 \subsubsection{$W$+uno}
 \begin{figure}
   \centering
   \begin{subfigure}{0.45\textwidth}
   \centering
   \includegraphics{wuno1}
   \caption{}
   \label{fig:U1diags}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
   \centering
 \includegraphics{wuno2}
 \caption{}
 \label{fig:U2diags}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
   \centering
   \includegraphics{wuno3}
 \caption{}
 \label{fig:Cdiags}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{wuno4}
 \caption{}
  \label{fig:Ddiags}
  \end{subfigure}
   \vspace{0.4cm}
   \caption{Examples of each of the four categories of Feynman diagram which
     contribute to at leading-order; there are twelve in total.  (a) is an example where the gluon and $W$
     boson are emitted from the same quark line and the gluon comes after the
     $t$-channel propagator.  In (b), the gluon and $W$ boson are emitted from
     the same quark line and the gluon comes before the $t$-channel proagator.
     In (c) the gluon is emitted from the $t$-channel gluon and in (d) the gluon
     is emitted from the $b$--$3$ quark line.}
   \label{fig:Wunodiags}
 \end{figure}
-It is necessary to include subleading processes in $W$+Jets also. All of
-these currents have been built for the \lstinline!Tensor! Class detailed in
-section~\ref{sec:tensor}. Similarly to the pure jet case, the uno currents are
-not calculated separately, and only in the ME functions when required
-in the \texttt{src/Wjets.cc} file. For unordered emissions a new
-current is required, $j_{W,{\rm uno}}$, it is only non-zero for
-$h_a=h_1=-$ and hence we have suppressed its helicity indices.  It is
-derived from the 12 leading-order Feynman diagrams in the QMRK
-limit (see figure~\ref{fig:Wunodiags}). Using $T^m_{ij}$ represent fundamental
-colour matrices between quark state $i$ and $j$ with adjoint index $m$ we find
+It is necessary to include subleading processes in $W$+Jets also.Similarly to
+the pure jet case, the uno currents are not calculated separately, and only in
+the ME functions when required in the \texttt{src/Wjets.cc} file. For unordered
+emissions a new current is required, $j_{W,{\rm uno}}$, it is only non-zero for
+$h_a=h_1=-$ and hence we have suppressed its helicity indices.  It is derived
+from the 12 leading-order Feynman diagrams in the QMRK limit (see
+figure~\ref{fig:Wunodiags}). Using $T^m_{ij}$ represent fundamental colour
+matrices between quark state $i$ and $j$ with adjoint index $m$ we find
 \begin{align}\label{eq:wunocurrent}
 \begin{split}
   j^{d\,\mu}_{\rm W,uno}(p_a,p_1,p_2,p_\ell,p_{\bar{\ell}}) =& \ i \varepsilon_{\nu}(p_1)\
   \bar{u}^-(p_\ell) \gamma_\rho v^-(p_{\bar \ell}) \\ & \quad \times\
   \left(T^1_{2i} T^d_{ia} (\tilde U_1^{\nu\mu\rho}-\tilde L^{\nu\mu\rho}) +
     T^d_{2i} T^1_{ia} (\tilde U_2^{\nu\mu\rho}+\tilde L^{\nu\mu\rho}) \right),
   \end{split}
 \end{align}
 where expressions for $\tilde U_{1,2}^{\nu\mu\rho}$ and $\tilde L^{\nu\mu\rho}$
 are given as:
 \begin{align}
   \label{eq:U1tensor}
   \begin{split}
     \tilde U_1^{\nu\mu\rho} ={}&\frac{\langle 2|\nu (\slashed{p}_2+ \slashed{p}_1)\mu (\slashed{p}_a - \slashed{p}_W)\rho P_L |a\rangle }{s_{12}t_{aW}}  + \frac{\langle 2|\nu (\slashed{p}_2+ \slashed{p}_1)\rho P_L (\slashed{p}_2+\slashed{p}_1 + \slashed{p}_W)\mu |a\rangle }{s_{12}s_{12W}} \\
     &+ \frac{\langle 2|\rho P_L (\slashed{p}_2+ \slashed{p}_W) \nu
       (\slashed{p}_1 + \slashed{p}_2+\slashed{p}_W)\mu |a\rangle}{s_{2W}s_{12W}}\,,
   \end{split}\\
   \label{eq:U2tensor}
   \begin{split}
     \tilde U_2^{\nu\mu\rho} ={}&\frac{\langle 2|\mu (\slashed{p}_a-\slashed{p}_W-\slashed{p}_1)\nu (\slashed{p}_a - \slashed{p}_W)\rho P_L |a\rangle }{t_{aW1}t_{aW}}  + \frac{\langle 2|\mu (\slashed{p}_a-\slashed{p}_W- \slashed{p}_1)\rho P_L (\slashed{p}_a-\slashed{p}_1) \nu |a\rangle }{t_{a1W}t_{a1}}  \\
     &+ \frac{\langle 2|\rho P_L (\slashed{p}_2+ \slashed{p}_W) \mu
       (\slashed{p}_a-\slashed{p}_1)\nu |a\rangle}{s_{2W}t_{a1}}\,,
   \end{split}\\
 \label{eq:Ltensor}
 \begin{split}
   \tilde L^{\nu\mu\rho} ={}& \frac{1}{t_{aW2}}\left[
    \frac{\langle 2 | \sigma (\slashed{p}_a-\slashed{p}_W)\rho|a\rangle}{t_{aW}}
    +\frac{\langle 2 | \rho (\slashed{p}_2+\slashed{p}_W)\sigma|a\rangle}{s_{2W}}
 \right]\\
 &\times \left\{\left(\frac{p_b^\nu}{s_{1b}} + \frac{p_3^\nu}{s_{13}}\right)(q_1-p_1)^2g^{\mu\sigma}+(2q_1-p_1)^\nu g^{\mu\sigma} - 2p_1^\mu g^{\nu\sigma} + (2p_1-q_1)^\sigma g^{\mu\nu} \right\}\,,
 \end{split}
 \end{align}
 where $s_{ij\dots} = (p_i + p_j + \dots)^2, t_{ij\dots} = (p_i - p_j - \dots)^2$ and $q_1 = p_a-p_2-p_W$.
 
 \subsubsection{$W$+Extremal $\mathbf{q\bar{q}}$}
 The $W$+Jet sub-leading processes containing an extremal $q\bar{q}$ are
 related by crossing symmetry to the $W$+Jet unordered processes. This
 means that one can simply perform a crossing symmetry argument on
 eq.~\ref{eq:wunocurrent} to arrive at the extremal $q\bar{q}$ current
 required.We show the basic structure of the extremal $q\bar{q}$
 current in figure~\ref{fig:qgimp}, neglecting the $W$-emission for
 simplicity.
 
 \begin{figure}
 \centering
 \includegraphics[width=0.3\textwidth]{{qqbarex_schem}}
 \caption{Schematic structure of the $gq \to \bar{Q}Qq$ amplitude in the limit
   $y_1 \sim y_2 \ll y_3$}
 \label{fig:qgimp}
 \end{figure}
 
 \begin{figure}
 \centering
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarex1}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarex2}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarex4}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarex5}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarex3}
 \end{subfigure}
 \caption{The five tree-level graphs which contribute to the process $gq \to \bar{Q}Qq$.}
 \label{fig:qg_qQQ_graphs}
 \end{figure}
 We can obtain the current for $g\rightarrow W q \bar{q}$ by evaluating
 the current for $W$ plus unordered emissions with the normal arguments
 $p_a \leftrightarrow -p_1 $ interchanged.  This is a non-trivial
 statement: due to the minimality of the approximations made, the
 crossing symmetry normally present in the full amplitude may be
 extended to the factorised current.
 
 We must again note that swapping $p_a \leftrightarrow -p_1$ will lead
 to $u$-spinors with momenta with negative energy.  These are identical
 to $v$-spinors with momenta with positive energy, up to an overall
 phase which is common to all terms, and can therefore be neglected.
 
 Mathematically, this is given by:
 
 \begin{align}\label{eq:crossedJ}
   j^\mu_{\rm W,g\to Q\bar{Q}}(p_a,p_1,p_2,p_\ell,p_{\bar{\ell}}) =i \varepsilon_{g\nu}
   \langle \ell | \rho | \bar \ell \rangle_L
 \left(T^1_{2i} T^d_{ia} (\tilde U_{1,X}^{\nu\mu\rho}-\tilde L^{\nu\mu\rho}_X) + T^d_{2i} T^1_{ia} (\tilde U_{2,X}^{\nu\mu\rho}+\tilde L_X^{\nu\mu\rho}) \right),
 \end{align}
 where the components are now given by
 \begin{align}
   \label{eq:U1tensorX}
   \begin{split}
     \tilde U_{1,X}^{\nu\mu\rho} =&\frac{\langle 2|\nu (\slashed{p}_a- \slashed{p}_2)\mu (\slashed{p}_1 + \slashed{p}_W)\rho P_L |1\rangle }{t_{a2}s_{1W}}  + \frac{\langle 2|\nu (\slashed{p}_a- \slashed{p}_2)\rho P_L (\slashed{p}_a-\slashed{p}_2 - \slashed{p}_W)\mu |1\rangle }{t_{a2}t_{a2W}} \\
     &- \frac{\langle 2|\rho P_L (\slashed{p}_2+ \slashed{p}_W) \nu
       (\slashed{p}_a - \slashed{p}_2-\slashed{p}_W)\mu
       |1\rangle}{s_{2W}t_{a2W}}\,,
   \end{split}\\
   \label{eq:U2tensorX}
   \begin{split}
     \tilde U_{2,X}^{\nu\mu\rho} =&-\frac{\langle 2|\mu (\slashed{p}_a-\slashed{p}_W-\slashed{p}_1)\nu (\slashed{p}_1 + \slashed{p}_W)\rho P_L |1\rangle }{t_{aW1}s_{1W}}  + \frac{\langle 2|\mu (\slashed{p}_a-\slashed{p}_W- \slashed{p}_1)\rho P_L (\slashed{p}_a-\slashed{p}_1) \nu |1\rangle }{t_{a1W}t_{a1}}  \\
     &+ \frac{\langle 2|\rho P_L (\slashed{p}_2+ \slashed{p}_W) \mu
       (\slashed{p}_a-\slashed{p}_1)\nu |1\rangle}{s_{2W}t_{a1}}\,,
   \end{split}\\
   \label{eq:LtensorX}
   \begin{split}
     \tilde L^{\nu\mu\rho}_X &= \frac{1}{s_{W12}}\left[-\frac{\langle 2 |\sigma (\slashed{p}_1 + \slashed{p}_W) \rho P_L | 1\rangle}{s_{1W}} + \frac{\langle 2 |\rho P_L (\slashed{p}_2 + \slashed{p}_W) \sigma | 1\rangle }{s_{2W}} \right] \\
     &\vphantom{+\frac{1}{t_{aW2}}}\quad\cdot \left( -\left(
         \frac{p_b^\nu}{s_{ab}} + \frac{p_n^\nu}{s_{an}} \right) (q_1+p_a)^2 g^{\sigma\mu}+ g^{\sigma \mu} (2q_1 +p_a)^\nu - g^{\mu \nu}(2p_a+q_1)^\sigma+ 2g^{\nu \sigma}p_a^\mu \right)\,,
   \end{split}
 \end{align}
 where $q_1=-(p_1+p_2+p_W)$.  Notice in particular the similarity to the $W$+uno scenario (from which
 this has been derived).
 \subsubsection{Central $\mathbf{q\bar{q}}$ Vertex}
 The final subleading process in the $W$+Jet case is the Central
 $q\bar{q}$ vertex. This subleading process does not require an altered
 current, but an effective vertex which is contracted with two regular
-\HEJ currents. This complexity is dealt with nicely by the \lstinline!Tensor!
-class, which is detailed in section~\ref{sec:tensor}.
+\HEJ currents. This complexity is dealt with nicely by the \FORM inside the
+\texttt{current\_generator/j\_Wqqbar\_j.frm}, which is detailed in
+section~\ref{sec:contr_calc}.
 
 The $W$-emission can be from the central effective vertex (scenario
 dealt with by the function \lstinline!ME_WCenqqx_qq! in the file
 \texttt{src/Wjets.cc}); or from either of the external quark legs
 (scenario dealt with by \lstinline!ME_W_Cenqqx_qq! in same file).  In
 the pure jets case, there are 7 separate diagrams which contribute to
 this, which can be seen in figure~\ref{fig:qq_qQQq_graphs}. In the $W$+Jets
 case, there are then 45 separate contributions.
 
 \begin{figure}
 \centering
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen1}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen2}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen3}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen4}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen5}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen6}
 \end{subfigure}
 \begin{subfigure}{0.45\textwidth}
 \centering
 \includegraphics{qqbarcen7}
 \end{subfigure}
 \caption{All Feynman diagrams which contribute to $qq' \to qQ\bar{Q}q'$ at
   leading order.}
 \label{fig:qq_qQQq_graphs}
 \end{figure}
 
 The end result is of the effective vertex, after derivation, is:
 
 \begin{align}
   \label{eq:EffectiveVertexqqbar}
   \begin{split}
   V^{\mu\nu}_{\text{Eff}}=&
   \frac{C_1}{s_{23AB}}\left(X^{\mu\nu\sigma}_{1a}\hat{t_1} + X^{\mu\nu\sigma}_{4b}\hat{t_3} +V^{\mu\nu\sigma}_{3g}\right)J_{\text{V} \sigma}(p_2,p_A,p_B,p_3)
   \\
   &\quad +iC_2X^{\mu\nu}_{Unc}+iC_3X^{\mu\nu}_{Cro},
   \end{split}
 \end{align}
 
 where:
 
 \begin{eqnarray}
   \begin{split}
   C_1=&T^e_{1q}T^g_{qa}T^e_{23}T^g_{4b} -
   T^g_{1q}T^e_{qa}T^e_{23}T^g_{4b} = f^{egc}T^c_{1a}T^e_{23}T^g_{4b},
   \\
   C_2=&T^g_{1a}T^g_{2q}T^{g'}_{q3}T^{g'}_{4b}
   \\
   C_3=&T^g_{1a}T^{g'}_{2q}T^g_{q3}T^{g'}_{4b}
   \end{split}
 \end{eqnarray}
 are the colour factors of different contributions and
 \todo{Different convention for $J_V$}
 \begin{equation}
   \label{eq:J_V}
     J_V^\mu(p_2,p_l,p_{\bar{l}},p_3)=-i\left( \frac{ \bar{u}_2 \gamma^\nu (\slashed{p}_2 +
         \slashed{p}_l +
         \slashed{p}_{\bar{l}}) \gamma^\mu u_3}{s_{2l\bar{l}}} -  \frac{\bar u_2
         \gamma^\mu(\slashed{p}_3 + \slashed{p}_l + \slashed{p}_{\bar{l}}) \gamma^\nu
         u_3}{s_{3l\bar{l}}} \right) [\bar{u}_l \gamma_\nu u_{\bar{l}}].
 \end{equation}
 The following tensor structures correspond to groupings of diagrams in
 figure~\ref{fig:qq_qQQq_graphs}.
 \begin{eqnarray}
   \label{eq:X_1a}
   X_{1a}^{\mu\nu\sigma} &=
 \frac{-g^{\mu\nu}}{s_{23AB}\hat{t_3}}\left(\frac{p^\sigma_a}{(s_{a23AB})} +
                     \frac{p^\sigma_1}{(s_{123AB})}\right)
   \\
   \label{eq:X_4b}
   X_{4b}^{\mu\nu\sigma}
 &=\frac{g^{\mu\nu}}{s_{23AB}\hat{t_1}}\left(\frac{p^\sigma_b}{(s_{23bAB})}+\frac{p^\sigma_4}{(s_{234AB}}\right)
 \end{eqnarray}
 correspond to the first and second row of diagrams in figure~\ref{fig:qq_qQQq_graphs}.
 \begin{align}
   \label{eq:3GluonWEmit}
   \begin{split}
   V^{\mu\nu\sigma}_{3g}=\frac{1}{
     \hat{t}_1s_{23AB}\,\hat{t}_3}
   \bigg[&\left(q_1+p_2+p_3+p_A+p_B\right)^\nu
   g^{\mu\sigma}+
   \\
   &\quad\left(q_3-p_2-p_3-p_A-p_B\right)^\mu g^{\sigma\nu}-
   \\
   & \qquad\left(q_1+q_3\right)^\sigma g^{\mu\nu}\bigg]J_{\text{V} \sigma}(p_2,p_A,p_B,p_3)
   \end{split}
 \end{align}
 corresponds to the left diagram on the third row in
 figure~\ref{fig:qq_qQQq_graphs}. One notes that all of these
 contributions have the same colour factor, and as such we can group
-them together nicely before summing over helicities etc. As such, the
-function \lstinline!M_sym_W! that is part of the header
-\texttt{W\_central\_qqbar.hh} generated by the current generator
-(section~\ref{sec:cur_gen}) returns a contraction of the above tensor
-containing the information from these 5 groupings of contributions (30
-diagrams in total).
+them together nicely before summing over helicities etc. As such, the function
+\lstinline!M_sym_W! returns a contraction of the above tensor containing the
+information from these 5 groupings of contributions (30 diagrams in total). It
+is available through the generated header \texttt{j\_Wqqbar\_j.hh} (see
+section~\ref{sec:cur_gen}).
 \begin{align}
   \label{eq:X_Unc}
   \begin{split}
       X^{\mu\nu}_{Unc}=\frac{\langle A|\sigma P_L|B\rangle}{\hat{t_1}\hat{t_3}} \bar{u}_2&\left[-\frac{
   \gamma^\sigma P_L(\slashed{p}_2+\slashed{p}_A+\slashed{p}_B)\gamma^\mu
   (\slashed{q}_3+ \slashed{p}_3)\gamma^\nu}{(s_{2AB})(t_{unc_{2}})}\right.+
   \\
     &\qquad\left. \frac{\gamma^\mu(\slashed{q}_1-\slashed{p}_2)\gamma^\sigma
         P_L(\slashed{q}_3+\slashed{p}_3)\gamma^\nu}{(t_{unc_{1}})(t_{unc_{2}})}\right. +
   \\
     &\qquad\qquad\left. \frac{\gamma^\mu(\slashed{q}_1-\slashed{p}_2)\gamma^\nu(\slashed{p}_3+\slashed{p}_A+\slashed{p}_B)\gamma^\sigma P_L
   }{(t_{unc_1})(s_{3AB})}\right]v_3
   \end{split}
 \end{align}
 corresponds to the diagram on the right of row three in
 figure~\ref{fig:qq_qQQq_graphs}. This contribution to the current
 contraction can be obtained in the code with the function
-\lstinline!M_uncross_W! in the autogenerated header \texttt{W\_central\_qqbar.hh}.
+\lstinline!M_uncross_W!.
 \begin{align}
   \begin{split}
   \label{eq:X_Cro}
     X^{\mu\nu}_{Cro}=\frac{\langle
 A|\sigma P_L|B\rangle}{\hat{t_1}\hat{t_3}} \bar{u}_2&\left[-\frac{
 \gamma^\nu(\slashed{q}_3+\slashed{p}_2)\gamma^\mu
 (\slashed{p}_3+\slashed{p}_A+\slashed{p}_B)\gamma^\sigma P_L}{(t_{cro_1})(s_{3AB})}\right.+
 \\
 &\qquad\left. \frac{\gamma^\nu(\slashed{q}_3+\slashed{p}_2)\gamma^\sigma
     P_L(\slashed{q}_1-\slashed{p}_3)\gamma^\mu}{(t_{cro_{1}})(t_{cro_{2}})}\right.+
 \\ &\qquad\qquad\left
 . \frac{\gamma^\sigma P_L(\slashed{p}_2+\slashed{p}_A+\slashed{p}_B)\gamma^\nu(\slashed{q}_1-\slashed{p}_3)\gamma^\mu
 }{(s_{2AB})(t_{cro_2})}\right]v_3
   \end{split}
 \end{align}
 corresponds to the last diagram in
 figure~\ref{fig:qq_qQQq_graphs}. This contribution to the current
 contraction can be obtained in the code with the function
-\lstinline!M_cross_W! in the generated header
-\texttt{W\_central\_qqbar.hh}.
+\lstinline!M_cross_W!.
 
 %%% Local Variables:
 %%% mode: latex
 %%% TeX-master: "developer_manual"
 %%% End:
diff --git a/doc/developer_manual/developer_manual.tex b/doc/developer_manual/developer_manual.tex
index 588e506..51a7b4e 100644
--- a/doc/developer_manual/developer_manual.tex
+++ b/doc/developer_manual/developer_manual.tex
@@ -1,1973 +1,1975 @@
 \documentclass[a4paper,11pt]{article}
 
 \usepackage{fourier}
 \usepackage[T1]{fontenc}
 \usepackage{microtype}
 \usepackage{geometry}
 \usepackage{enumitem}
 \setlist[description]{leftmargin=\parindent,labelindent=\parindent}
 \usepackage{amsmath}
 \usepackage{amssymb}
 \usepackage[utf8x]{inputenc}
 \usepackage{graphicx}
 \usepackage{xcolor}
 \usepackage{todonotes}
 \usepackage{listings}
 \usepackage{xspace}
 \usepackage{tikz}
 \usepackage{slashed}
 \usepackage{subcaption}
 \usetikzlibrary{arrows.meta}
 \usetikzlibrary{shapes}
 \usetikzlibrary{calc}
 \usepackage[colorlinks,linkcolor={blue!50!black}]{hyperref}
 \graphicspath{{build/figures/}{figures/}}
 \usepackage[left]{showlabels}
 \renewcommand{\showlabelfont}{\footnotesize\color{darkgreen}}
 \emergencystretch \hsize
 
 \newcommand{\HEJ}{{\tt HEJ}\xspace}
 \newcommand{\HIGHEJ}{\emph{High Energy Jets}\xspace}
 \newcommand{\cmake}{\href{https://cmake.org/}{cmake}\xspace}
 \newcommand{\html}{\href{https://www.w3.org/html/}{html}\xspace}
 \newcommand{\YAML}{\href{http://yaml.org/}{YAML}\xspace}
 \newcommand{\QCDloop}{\href{https://github.com/scarrazza/qcdloop}{QCDloop}\xspace}
+\newcommand{\FORM}{{\tt FORM}\xspace}
 \newcommand\matel[4][]{\mathinner{\langle#2\vert#3\vert#4\rangle}_{#1}}
 
 \newcommand{\as}{\alpha_s}
 \DeclareRobustCommand{\mathgraphics}[1]{\vcenter{\hbox{\includegraphics{#1}}}}
 \def\spa#1.#2{\left\langle#1\,#2\right\rangle}
 \def\spb#1.#2{\left[#1\,#2\right]} \def\spaa#1.#2.#3{\langle\mskip-1mu{#1} |
   #2 | {#3}\mskip-1mu\rangle} \def\spbb#1.#2.#3{[\mskip-1mu{#1} | #2 |
   {#3}\mskip-1mu]} \def\spab#1.#2.#3{\langle\mskip-1mu{#1} | #2 |
   {#3}\mskip-1mu\rangle} \def\spba#1.#2.#3{\langle\mskip-1mu{#1}^+ | #2 |
   {#3}^+\mskip-1mu\rangle} \def\spav#1.#2.#3{\|\mskip-1mu{#1} | #2 |
   {#3}\mskip-1mu\|^2} \def\jc#1.#2.#3{j^{#1}_{#2#3}}
 
 % expectation value
 \newcommand{\ev}[1]{\text{E}\left[#1\right]}
 
 \definecolor{darkgreen}{rgb}{0,0.4,0}
 \lstset{ %
  backgroundcolor=\color{lightgray},   % choose the background color; you must add \usepackage{color} or \usepackage{xcolor}
  basicstyle=\footnotesize\usefont{T1}{DejaVuSansMono-TLF}{m}{n},        % the size of the fonts that are used for the code
  breakatwhitespace=false,         % sets if automatic breaks should only happen at whitespace
  breaklines=false,                % sets automatic line breaking
  captionpos=t,                    % sets the caption-position to bottom
  commentstyle=\color{red},        % comment style
  deletekeywords={...},            % if you want to delete keywords from the given language
  escapeinside={\%*}{*)},          % if you want to add LaTeX within your code
  extendedchars=true,              % lets you use non-ASCII characters; for 8-bits encodings only, does not work with UTF-8
  frame=false,                     % adds a frame around the code
  keepspaces=true,                 % keeps spaces in text, useful for keeping indentation of code (possibly needs columns=flexible)
  keywordstyle=\color{blue},       % keyword style
  otherkeywords={},                 % if you want to add more keywords to the set
  numbers=none,                    % where to put the line-numbers; possible values are (none, left, right)
  numbersep=5pt,                   % how far the line-numbers are from the code
  rulecolor=\color{black},         % if not set, the frame-color may be changed on line-breaks within not-black text (e.g. comments (green here))
  showspaces=false,                % show spaces everywhere adding particular underscores; it overrides 'showstringspaces'
  showstringspaces=false,          % underline spaces within strings only
  showtabs=false,                  % show tabs within strings adding particular underscores
  stepnumber=2,                    % the step between two line-numbers. If it's 1, each line will be numbered
  stringstyle=\color{gray},         % string literal style
  tabsize=2,                    % sets default tabsize to 2 spaces
  title=\lstname,
  emph={},
  emphstyle=\color{darkgreen}
 }
 
 \begin{document}
 
 \tikzstyle{mynode}=[rectangle split,rectangle split parts=2, draw,rectangle split part fill={lightgray, none}]
 
 \title{\HEJ 2 developer manual}
 \author{}
 \maketitle
 \tableofcontents
 
 \newpage
 
 \section{Overview}
 \label{sec:overview}
 
 \HEJ 2 is a C++ program and library implementing an algorithm to
 apply \HIGHEJ resummation~\cite{Andersen:2008ue,Andersen:2008gc} to
 pre-generated fixed-order events. This document is intended to give an
 overview over the concepts and structure of this implementation.
 
 \subsection{Project structure}
 \label{sec:project}
 
 \HEJ 2 is developed under the \href{https://git-scm.com/}{git}
 version control system. The main repository is on the IPPP
 \href{https://gitlab.com/}{gitlab} server under
 \url{https://gitlab.dur.scotgrid.ac.uk/hej/hej}. To get a local
 copy, get an account on the gitlab server and use
 \begin{lstlisting}[language=sh,caption={}]
   git clone git@gitlab.dur.scotgrid.ac.uk:hej/hej.git
 \end{lstlisting}
 This should create a directory \texttt{hej} with the following
 contents:
 \begin{description}
 \item[README.md:] Basic information concerning \HEJ.
 \item[doc:] Contains additional documentation, see section~\ref{sec:doc}.
 \item[include:] Contains the C++ header files.
 \item[src:] Contains the C++ source files.
 \item[examples:] Example analyses and scale setting code.
 \item[t:] Contains the source code for the automated tests.
 \item[CMakeLists.txt:] Configuration file for the \cmake build
   system. See section~\ref{sec:cmake}.
 \item[cmake:] Auxiliary files for \cmake. This includes modules for
   finding installed software in \texttt{cmake/Modules} and templates for
   code generation during the build process in \texttt{cmake/Templates}.
 \item[config.yml:] Sample configuration file for running \HEJ 2.
 \item[current\_generator:] Contains the code for the current generator,
   see section~\ref{sec:cur_gen}.
 \item[FixedOrderGen:] Contains the code for the fixed-order generator,
   see section~\ref{sec:HEJFOG}.
 \item[COPYING:] License file.
 \item[AUTHORS:] List of \HEJ authors.
 \item[Changes-API.md:] Change history for the \HEJ API (application programming interface).
 \item[Changes.md:] History of changes relevant for \HEJ users.
 \end{description}
 In the following all paths are given relative to the
 \texttt{hej} directory.
 
 \subsection{Documentation}
 \label{sec:doc}
 
 The \texttt{doc} directory contains user documentation in
 \texttt{doc/sphinx} and the configuration to generate source code
 documentation in \texttt{doc/doxygen}.
 
 The user documentation explains how to install and run \HEJ 2. The
 format is
 \href{http://docutils.sourceforge.net/rst.html}{reStructuredText}, which
 is mostly human-readable. Other formats, like \html, can be generated with the
 \href{http://www.sphinx-doc.org/en/master/}{sphinx} generator with
 \begin{lstlisting}[language=sh,caption={}]
   make sphinx
 \end{lstlisting}
 
 To document the source code we use
 \href{https://www.stack.nl/~dimitri/doxygen/}{doxygen}. To generate
 \html documentation, use the command
 \begin{lstlisting}[language=sh,caption={}]
   make doxygen
 \end{lstlisting}
 in your \texttt{build/} directory.
 
 \subsection{Build system}
 \label{sec:cmake}
 
 For the most part, \HEJ 2 is a library providing classes and
 functions that can be used to add resummation to fixed-order events. In
 addition, there is a relatively small executable program leveraging this
 library to read in events from an input file and produce resummation
 events. Both the library and the program are built and installed with
 the help of \cmake.
 Debug information can be turned on by using
 \begin{lstlisting}[language=sh,caption={}]
   cmake base/directory -DCMAKE_BUILD_TYPE=Debug
   make install
 \end{lstlisting}
 This facilitates the use of debuggers like \href{https://www.gnu.org/software/gdb/}{gdb}.
 
 The main \cmake configuration file is \texttt{CMakeLists.txt}. It defines the
 compiler flags, software prerequisites, header and source files used to
 build \HEJ 2, and the automated tests.
 \texttt{cmake/Modules} contains module files that help with the
 detection of the software prerequisites and \texttt{cmake/Templates}
 template files for the automatic generation of header and
 source files. For example, this allows to only keep the version
 information in one central location (\texttt{CMakeLists.txt}) and
 automatically generate a header file from the template \texttt{Version.hh.in} to propagate this to the C++ code.
 
 \subsection{General coding guidelines}
 \label{sec:notes}
 
 The goal is to make the \HEJ 2 code well-structured and
 readable. Here are a number of guidelines to this end.
 
 \begin{description}
 \item[Observe the boy scout rule.] Always leave the code cleaner
   than how you found it. Ugly hacks can be useful for testing, but
   shouldn't make their way into the main branch.
 \item[Ask if something is unclear.] Often there is a good reason why
   code is written the way it is. Sometimes that reason is only obvious to
   the original author (use \lstinline!git blame! to find them), in which
   case they should be poked to add a comment. Sometimes there is no good
   reason, but nobody has had the time to come up with something better,
   yet. In some places the code might just be bad.
 \item[Don't break tests.] There are a number of tests in the \texttt{t}
   directory, which can be run with \lstinline!make test!. Ideally, all
   tests should run successfully in each git revision. If your latest
   commit broke a test and you haven't pushed to the central repository
   yet, you can fix it with \lstinline!git commit --amend!. If an earlier
   local commit broke a test, you can use \lstinline!git rebase -i! if
   you feel confident. Additionally each \lstinline!git push! is also
   automatically tested via the GitLab CI (see appendix~\ref{sec:CI}).
 \item[Test your new code.] When you add some new functionality, also add an
   automated test. This can be useful even if you don't know the
   ``correct'' result because it prevents the code from changing its behaviour
   silently in the future. \href{http://www.valgrind.org/}{valgrind} is a
   very useful tool to detect potential memory leaks. The code coverage of all
   tests can be generated with \href{https://gcovr.com/en/stable/}{gcovr}.
   Therefore add the flag \lstinline!-DTEST_COVERAGE=True! to cmake and run
   \lstinline!make ctest_coverage!.
 \item[Stick to the coding style.] It is somewhat easier to read code
   that has a uniform coding and indentation style. We don't have a
   strict style, but it helps if your code looks similar to what is
   already there.
 \end{description}
 
 \section{Program flow}
 \label{sec:flow}
 
 A run of the \HEJ 2 program has three stages: initialisation,
 event processing, and cleanup. The following sections outline these
 stages and their relations to the various classes and functions in the
 code. Unless denoted otherwise, all classes and functions are part of
 the \lstinline!HEJ! namespace. The code for the \HEJ 2 program is
 in \texttt{src/bin/HEJ.cc}, all other code comprises the \HEJ 2
 library. Classes and free functions are usually implemented in header
 and source files with a corresponding name, i.e. the code for
 \lstinline!MyClass! can usually be found in
 \texttt{include/HEJ/MyClass.hh} and \texttt{src/MyClass.cc}.
 
 \subsection{Initialisation}
 \label{sec:init}
 
 The first step is to load and parse the \YAML configuration file. The
 entry point for this is the \lstinline!load_config! function and the
 related code can be found in \texttt{include/HEJ/YAMLreader.hh},
 \texttt{include/HEJ/config.hh} and the corresponding \texttt{.cc} files
 in the \texttt{src} directory. The implementation is based on the
 \href{https://github.com/jbeder/yaml-cpp}{yaml-cpp} library.
 The \lstinline!load_config! function returns a \lstinline!Config! object
 containing all settings. To detect potential mistakes as early as
 possible, we throw an exception whenever one of the following errors
 occurs:
 \begin{itemize}
 \item There is an unknown option in the \YAML file.
 \item A setting is invalid, for example a string is given where a number
   would be expected.
 \item An option value is not set.
 \end{itemize}
 The third rule is sometimes relaxed for ``advanced'' settings with an
 obvious default, like for importing custom scales or analyses.
 
 The information stored in the \lstinline!Config! object is then used to
 initialise various objects required for the event processing stage described in
 section~\ref{sec:processing}. First, the \lstinline!get_analyses! function
 creates a number objects that inherits from the \lstinline!Analysis!
 interface.\footnote{In the context of C++ the proper technical expression is
 ``pure abstract class''.} Using an interface allows us to decide the concrete
 type of the analysis at run time instead of having to make a compile-time
 decision. Depending on the settings, \lstinline!get_analyses! creates a number
 of user-defined analyses loaded from one or more external libraries and
 \textsc{rivet} analyses (see the user documentation
 \url{https://hej.web.cern.ch/HEJ/doc/current/user/})
 
 Together with a number of further objects, whose roles are described in
 section~\ref{sec:processing}, we also initialise the global random
 number generator. We again use an interface to defer deciding the
 concrete type until the program is actually run. Currently, we support the
 \href{https://mixmax.hepforge.org/}{MIXMAX}
 (\texttt{include/HEJ/Mixmax.hh}) and Ranlux64
 (\texttt{include/HEJ/Ranlux64.hh}) random number generators, both are provided
 by \href{http://proj-clhep.web.cern.ch/}{CLHEP}.
 
 We also set up a \lstinline!HEJ::EventReader! object for reading events
 either in the the Les Houches event file format~\cite{Alwall:2006yp} or
 an \href{https://www.hdfgroup.org/}{HDF5}-based
 format~\cite{Hoeche:2019rti}. To allow making the decision at run time,
 \lstinline!HEJ::EventReader! is an abstract base class defined in
 \texttt{include/HEJ/EventReader.hh} and the implementations of the
 derived classes are in \texttt{include/HEJ/LesHouchesReader.hh},
 \texttt{include/HEJ/HDF5Reader.hh} and the corresponding \texttt{.cc}
 source files in the \texttt{src} directory. The
 \lstinline!LesHouchesReader! leverages
 \href{http://home.thep.lu.se/~leif/LHEF/}{\texttt{include/LHEF/LHEF.h}}. A
 small wrapper around the
 \href{https://www.boost.org/doc/libs/1_67_0/libs/iostreams/doc/index.html}{boost
 iostreams} library allows us to also read event files compressed with
 \href{https://www.gnu.org/software/gzip/}{gzip}. The wrapper code is in
 \texttt{include/HEJ/stream.hh} and the \texttt{src/stream.cc}.
 
 If unweighting is enabled, we also initialise an unweighter as defined
 in \texttt{include/HEJ/Unweighter.hh}. The unweighting strategies are
 explained in section~\ref{sec:unweight}.
 
 \subsection{Event processing}
 \label{sec:processing}
 
 In the second stage events are continuously read from the event
 file. After jet clustering, a number of corresponding resummation events
 are generated for each input event and fed into the analyses and a
 number of output files. The roles of various classes and functions are
 illustrated in the following flow chart:
 \begin{center}
   \begin{tikzpicture}[node distance=2cm and 5mm]
     \node (reader) [mynode]
     {\lstinline!EventReader::read_event!\nodepart{second}{read event}};
     \node
     (data) [mynode,below=of reader]
     {\lstinline!Event::EventData! constructor\nodepart{second}{convert to \HEJ object}};
     \node
     (cluster) [mynode,below=of data]
     {\lstinline!Event::EventData::cluster!\nodepart{second}{cluster jets \&
     classify \lstinline!EventType!}};
     \node
     (resum) [mynode,below=of cluster]
     {\lstinline!EventReweighter::reweight!\nodepart{second}{perform resummation}};
     \node
     (cut) [mynode,below=of resum]
     {\lstinline!Analysis::pass_cuts!\nodepart{second}{apply cuts}};
     \node
     (cut) [mynode,below=of resum]
     {\lstinline!Analysis::pass_cuts!\nodepart{second}{apply cuts}};
     \node
     (unweight) [mynode,below=of cut]
     {\lstinline!Unweighter::unweight!\nodepart{second}{unweight (optional)}};
     \node
     (fill) [mynode,below left=of unweight]
     {\lstinline!Analysis::fill!\nodepart{second}{analyse event}};
     \node
     (write) [mynode,below right=of unweight]
     {\lstinline!CombinedEventWriter::write!\nodepart{second}{write out event}};
     \node
     (control) [below=of unweight] {};
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (reader.south) -- node[left] {\lstinline!LHEF::HEPEUP!} (data.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (data.south) -- node[left] {\lstinline!Event::EventData!} (cluster.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (cluster.south) -- node[left] {\lstinline!Event!} (resum.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (resum.south) --  (cut.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(resum.south)+(10mm, 0cm)$) -- ($(cut.north)+(10mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(resum.south)+(5mm, 0cm)$) -- ($(cut.north)+(5mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(resum.south)-(5mm, 0cm)$) -- ($(cut.north)-(5mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(resum.south)-(10mm, 0cm)$) -- node[left] {\lstinline!Event!} ($(cut.north)-(10mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (cut.south) --  (unweight.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(cut.south)+(7mm, 0cm)$) -- ($(unweight.north)+(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(cut.south)-(7mm, 0cm)$) -- node[left] {\lstinline!Event!} ($(unweight.north)-(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(unweight.south)-(3mm,0mm)$) .. controls ($(control)-(3mm,0mm)$) ..node[left] {\lstinline!Event!} (fill.east);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(unweight.south)-(3mm,0mm)$)  .. controls ($(control)-(3mm,0mm)$) .. (write.west);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(unweight.south)+(3mm,0mm)$) .. controls ($(control)+(3mm,0mm)$) .. (fill.east);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(unweight.south)+(3mm,0mm)$)  .. controls ($(control)+(3mm,0mm)$) ..node[right] {\lstinline!Event!} (write.west);
   \end{tikzpicture}
 \end{center}
 
 \lstinline!EventData! is an intermediate container, its members are completely
 accessible. In contrast after jet clustering and classification the phase space
 inside \lstinline!Event! can not be changed any more
 (\href{https://wikipedia.org/wiki/Builder_pattern}{Builder design pattern}). The
 resummation is performed by the \lstinline!EventReweighter! class, which is
 described in more detail in section~\ref{sec:resum}. The
 \lstinline!CombinedEventWriter! writes events to zero or more output files. To
 this end, it contains a number of objects implementing the
 \lstinline!EventWriter! interface. These event writers typically write the
 events to a file in a given format. We currently have the
 \lstinline!LesHouchesWriter! for event files in the Les Houches Event File
 format, the \lstinline!HDF5Writer! for
 \href{https://www.hdfgroup.org/}{HDF5}~\cite{Hoeche:2019rti} and the
 \lstinline!HepMC2Writer! or \lstinline!HepMC3Writer! for the
 \href{https://hepmc.web.cern.ch/hepmc/}{HepMC} format (Version 2 and
 3).
 
 \subsection{Resummation}
 \label{sec:resum}
 
 In the \lstinline!EventReweighter::reweight! member function, we first
 classify the input fixed-order event (FKL, unordered, non-resummable, \dots)
 and decide according to the user settings whether to discard, keep, or
 resum the event. If we perform resummation for the given event, we
 generate a number of trial \lstinline!PhaseSpacePoint! objects. Phase
 space generation is discussed in more detail in
 section~\ref{sec:pspgen}. We then perform jet clustering according to
 the settings for the resummation jets on each
 \lstinline!PhaseSpacePoint!, update the factorisation and
 renormalisation scale in the resulting \lstinline!Event! and reweight it
 according to the ratio of pdf factors and \HEJ matrix elements between
 resummation and original fixed-order event:
 \begin{center}
   \begin{tikzpicture}[node distance=1.5cm and 5mm]
     \node (in) {};
     \node (treat) [diamond,draw,below=of in,minimum size=3.5cm,
     label={[anchor=west, inner sep=8pt]west:discard},
     label={[anchor=east, inner sep=14pt]east:keep},
     label={[anchor=south, inner sep=20pt]south:reweight}
     ] {};
     \draw (treat.north west) -- (treat.south east);
     \draw (treat.north east) -- (treat.south west);
     \node
     (psp) [mynode,below=of treat]
     {\lstinline!PhaseSpacePoint! constructor};
     \node
     (cluster) [mynode,below=of psp]
     {\lstinline!Event::EventData::cluster!\nodepart{second}{cluster jets}};
     \node
     (colour) [mynode,below=of cluster]
     {\lstinline!Event::generate_colours()!\nodepart{second}{generate particle colour}};
     \node
     (gen_scales) [mynode,below=of colour]
     {\lstinline!ScaleGenerator::operator()!\nodepart{second}{update scales}};
     \node
     (rescale) [mynode,below=of gen_scales]
     {\lstinline!PDF::pdfpt!,
       \lstinline!MatrixElement!\nodepart{second}{reweight}};
     \node (out) [below of=rescale] {};
 
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (in.south) -- node[left] {\lstinline!Event!} (treat.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (treat.south) -- node[left] {\lstinline!Event!} (psp.north);
 
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (psp.south) --  (cluster.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(psp.south)+(7mm, 0cm)$) -- ($(cluster.north)+(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(psp.south)-(7mm, 0cm)$) -- node[left]
     {\lstinline!PhaseSpacePoint!} ($(cluster.north)-(7mm, 0cm)$);
 
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (cluster.south) --  (colour.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(cluster.south)+(7mm, 0cm)$) -- ($(colour.north)+(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(cluster.south)-(7mm, 0cm)$) -- node[left]
     {\lstinline!Event!} ($(colour.north)-(7mm, 0cm)$);
 
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (colour.south) --  (gen_scales.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(colour.south)+(7mm, 0cm)$) -- ($(gen_scales.north)+(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(colour.south)-(7mm, 0cm)$) -- node[left]
     {\lstinline!Event!} ($(gen_scales.north)-(7mm, 0cm)$);
 
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (gen_scales.south) --  (rescale.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(gen_scales.south)+(7mm, 0cm)$) -- ($(rescale.north)+(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(gen_scales.south)-(7mm, 0cm)$) -- node[left]
     {\lstinline!Event!} ($(rescale.north)-(7mm, 0cm)$);
 
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (rescale.south) --  (out.north);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(rescale.south)+(7mm, 0cm)$) -- ($(out.north)+(7mm, 0cm)$);
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     ($(rescale.south)-(7mm, 0cm)$) -- node[left]
     {\lstinline!Event!} ($(out.north)-(7mm, 0cm)$);
 
     \node (helper) at ($(treat.east) + (15mm,0cm)$) {};
     \draw[-{Latex[length=3mm, width=1.5mm]}]
     (treat.east) -- ($(treat.east) + (15mm,0cm)$)
     -- node[left] {\lstinline!Event!} (helper |- gen_scales.east) -- (gen_scales.east)
     ;
   \end{tikzpicture}
 \end{center}
 
 \subsection{Phase space point generation}
 \label{sec:pspgen}
 
 The resummed and matched \HEJ cross section for pure jet production of
 FKL configurations is given by (cf. eq. (3) of~\cite{Andersen:2018tnm})
 \begin{align}
     \label{eq:resumdijetFKLmatched2}
 %    \begin{split}
       \sigma&_{2j}^\mathrm{resum, match}=\sum_{f_1, f_2}\ \sum_m
       \prod_{j=1}^m\left(
         \int_{p_{j\perp}^B=0}^{p_{j\perp}^B=\infty}
         \frac{\mathrm{d}^2\mathbf{p}_{j\perp}^B}{(2\pi)^3}\ \int
         \frac{\mathrm{d} y_j^B}{2} \right) \
       (2\pi)^4\ \delta^{(2)}\!\!\left(\sum_{k=1}^{m}
         \mathbf{p}_{k\perp}^B\right)\nonumber\\
       &\times\ x_a^B\ f_{a, f_1}(x_a^B, Q_a^B)\ x_b^B\ f_{b, f_2}(x_b^B, Q_b^B)\
       \frac{\overline{\left|\mathcal{M}_\text{LO}^{f_1f_2\to f_1g\cdots
           gf_2}\big(\big\{p^B_j\big\}\big)\right|}^2}{(\hat {s}^B)^2}\nonumber\\
 & \times (2\pi)^{-4+3m}\ 2^m \nonumber\\
 &\times\ \sum_{n=2}^\infty\
 \int_{p_{1\perp}=p_{\perp,\mathrm{min}} }^{p_{1\perp}=\infty}
       \frac{\mathrm{d}^2\mathbf{p}_{1\perp}}{(2\pi)^3}\
  \int_{p_{n\perp}=p_{\perp,\mathrm{min}}}^{p_{n\perp}=\infty}
        \frac{\mathrm{d}^2\mathbf{p}_{n\perp}}{(2\pi)^3}\
      \prod_{i=2}^{n-1}\int_{p_{i\perp}=\lambda}^{p_{i\perp}=\infty}
        \frac{\mathrm{d}^2\mathbf{p}_{i\perp}}{(2\pi)^3}\ (2\pi)^4\ \delta^{(2)}\!\!\left(\sum_{k=1}^n
         \mathbf{p}_{k\perp}\right )\\
      &\times \ \mathbf{T}_y \prod_{i=1}^n
        \left(\int \frac{\mathrm{d} y_i}{2}\right)\
       \mathcal{O}_{mj}^e\
       \left(\prod_{l=1}^{m-1}\delta^{(2)}(\mathbf{p}_{\mathcal{J}_{l}\perp}^B -
        \mathbf{j}_{l\perp})\right)\
        \left(\prod_{l=1}^m\delta(y^B_{\mathcal{J}_l}-y_{\mathcal{J}_l})\right)
   \ \mathcal{O}_{2j}(\{p_i\})\nonumber\\
      &\times \frac{(\hat{s}^B)^2}{\hat{s}^2}\ \frac{x_a f_{a,f_1}(x_a, Q_a)\ x_b f_{b,f_2}(x_b, Q_b)}{x_a^B\ f_{a,f_1}(x_a^B, Q_a^B)\ x_b^B\ f_{b,f_2}(x_b^B, Q_b^B)}\ \frac{\overline{\left|\mathcal{M}_{\mathrm{HEJ}}^{f_1 f_2\to f_1 g\cdots
             gf_2}(\{ p_i\})\right|}^2}{\overline{\left|\mathcal{M}_\text{LO, HEJ}^{f_1f_2\to f_1g\cdots
           gf_2}\big(\big\{p^B_j\big\}\big)\right|}^{2}} \,.\nonumber
 %    \end{split}
 \end{align}
 The first two lines correspond to the generation of the fixed-order
 input events with incoming partons $f_1, f_2$ and outgoing momenta
 $p_j^B$, where $\mathbf{p}_{j\perp}^B$ and $y_j^B$ denote the respective
 transverse momentum and rapidity. Note that, at leading order, these
 coincide with the fixed-order jet momenta $p_{\mathcal{J}_j}^B$.
 $f_{a,f_1}(x_a, Q_a),f_{b,f_2}(x_b, Q_b)$ are the pdf factors for the incoming partons with
 momentum fractions $x_a$ and $x_b$. The square of the partonic
 centre-of-mass energy is denoted by $\hat{s}^B$ and
 $\mathcal{M}_\text{LO}^{f_1f_2\to f_1g\cdots gf_2}$ is the
 leading-order matrix element.
 
 The third line is a factor accounting for the different multiplicities
 between fixed-order and resummation events. Lines four and five are
 the integration over the resummation phase space described in this
 section. $p_i$ are the momenta of the outgoing partons in resummation
 phase space. $\mathbf{T}_y$ denotes rapidity
 ordering and $\mathcal{O}_{mj}^e$ projects out the exclusive $m$-jet
 component. The relation between resummation and fixed-order momenta is
 fixed by the $\delta$ functions. The first sets each transverse fixed-order jet
 momentum to some function $\mathbf{j_{l\perp}}$ of the resummation
 momenta. The exact form is described in section~\ref{sec:ptj_res}. The second
 $\delta$ forces the rapidities of resummation and fixed-order jets to be
 the same. Finally, the last line is the reweighting of pdf and matrix
 element factors already shown in section~\ref{sec:resum}.
 
 There are two kinds of cut-off in the integration over the resummation
 partons. $\lambda$ is a technical cut-off connected to the cancellation
 of infrared divergencies between real and virtual corrections. Its
 numerical value is set in
 \texttt{include/HEJ/Constants.h}. $p_{\perp,\mathrm{min}}$ regulates
 and \emph{uncancelled} divergence in the extremal parton momenta. Its
 size is set by the user configuration \url{https://hej.web.cern.ch/HEJ/doc/current/user/HEJ.html#settings}.
 
 It is straightforward to generalise eq.~(\ref{eq:resumdijetFKLmatched2})
 to unordered configurations and processes with additional colourless
 emissions, for example a Higgs or electroweak boson. In the latter case only
 the fixed-order integration and the matrix elements change.
 
 \subsubsection{Gluon Multiplicity}
 \label{sec:psp_ng}
 
 The first step in evaluating the resummation phase space in
 eq.~(\ref{eq:resumdijetFKLmatched2}) is to randomly pick terms in the
 sum over the number of emissions. This sampling of the gluon
 multiplicity is done in the \lstinline!PhaseSpacePoint::sample_ng!
 function in \texttt{src/PhaseSpacePoint.cc}.
 
 The typical number of extra emissions depends strongly on the rapidity
 span of the underlying fixed-order event.  Let us, for example, consider
 a fixed-order FKL-type multi-jet configuration with rapidities
 $y_{j_f},\,y_{j_b}$ of the most forward and backward jets,
 respectively. By eq.~(\ref{eq:resumdijetFKLmatched2}), the jet
 multiplicity and the rapidity of each jet are conserved when adding
 resummation. This implies that additional hard radiation is restricted
 to rapidities $y$ within a region $y_{j_b} \lesssim y \lesssim
 y_{j_f}$. Within \HEJ, we require the most forward and most backward
 emissions to be hard \todo{specify how hard} in order to avoid divergences, so this constraint
 in fact applies to \emph{all} additional radiation.
 
 To simplify the remaining discussion, let us remove the FKL rapidity
 ordering
 \begin{equation}
   \label{eq:remove_y_order}
   \mathbf{T}_y \prod_{i=1}^n\int \frac{\mathrm{d}y_i}{2} =
   \frac{1}{n!}\prod_{i=1}^n\int
   \frac{\mathrm{d}y_i}{2}\,,
 \end{equation}
 where all rapidity integrals now cover a region which is approximately
 bounded by $y_{j_b}$ and $y_{j_f}$. Each of the $m$ jets has to contain at least
 one parton; selecting random emissions we can rewrite the phase space
 integrals as
 \begin{equation}
   \label{eq:select_jets}
   \frac{1}{n!}\prod_{i=1}^n\int [\mathrm{d}p_i] =
   \left(\prod_{i=1}^{m}\int [\mathrm{d}p_i]\ {\cal J}_i(p_i)\right)
   \frac{1}{n_g!}\prod_{i=m+1}^{m+n_g}\int [\mathrm{d}p_i]
 \end{equation}
 with jet selection functions
 \begin{equation}
   \label{eq:def_jet_selection}
   {\cal J}_i(p) =
   \begin{cases}
     1 &p\text{ clustered into jet }i\\
     0 & \text{otherwise}
   \end{cases}
 \end{equation}
 and $n_g \equiv n - m$. Here and in the following we use the short-hand
 notation $[\mathrm{d}p_i]$ to denote the phase-space measure for parton
 $i$. As is evident from eq.~\eqref{eq:select_jets}, adding an extra emission
 $n_g+1$ introduces a suppression factor $\tfrac{1}{n_g+1}$. However, the
 additional phase space integral also results in an enhancement proportional
 to $\Delta y_{j_f j_b} = y_{j_f} - y_{j_b}$. This is a result of the
 rapidity-independence of the MRK limit of the integrand, consisting of the
 matrix elements divided by the flux factor. Indeed, we observe that the
 typical number of gluon emissions is to a good approximation proportional to
 the rapidity separation and the phase space integral is dominated by events
 with $n_g \approx \Delta y_{j_f j_b}$.
 
 For the actual phase space sampling, we assume a Poisson distribution
 and extract the mean number of gluon emissions in different rapidity
 bins and fit the results to a linear function in $\Delta y_{j_f j_b}$,
 finding a coefficient of $0.975$ for the inclusive production of a Higgs
 boson with two jets. Here are the observed and fitted average gluon
 multiplicities as a function of $\Delta y_{j_f j_b}$:
 \begin{center}
   \includegraphics[width=.75\textwidth]{ng_mean}
 \end{center}
 As shown for two rapidity slices the assumption of a Poisson
 distribution is also a good approximation:
 \begin{center}
   \includegraphics[width=.49\textwidth]{{ng_1.5}.pdf}\hfill
   \includegraphics[width=.49\textwidth]{{ng_5.5}.pdf}
 \end{center}
 
 \subsubsection{Number of Gluons inside Jets}
 \label{sec:psp_ng_jet}
 
 For each of the $n_g$ gluon emissions we can split the phase-space
 integral into a (disconnected) region inside the jets and a remainder:
 \begin{equation}
   \label{eq:psp_split}
   \int [\mathrm{d}p_i] = \int [\mathrm{d}p_i]\,
   \theta\bigg(\sum_{j=1}^{m}{\cal J}_j(p_i)\bigg) + \int [\mathrm{d}p_i]\,
   \bigg[1-\theta\bigg(\sum_{j=1}^{m}{\cal J}_j(p_i)\bigg)\bigg]\,.
 \end{equation}
 The next step is to decide how many of the gluons will form part of a
 jet. This is done in the \lstinline!PhaseSpacePoint::sample_ng_jets!
 function.
 
 We choose an importance sampling which is flat in the plane
 spanned by the azimuthal angle $\phi$ and the rapidity $y$. This is
 observed in BFKL and valid in the limit of Multi-Regge-Kinematics
 (MRK). Furthermore, we assume anti-$k_t$ jets, which cover an area of
 $\pi R^2$.
 
 In principle, the total accessible area in the $y$-$\phi$ plane is given
 by $2\pi \Delta y_{fb}$, where $\Delta y_{fb}\geq \Delta y_{j_f j_b}$ is
 the a priori unknown rapidity separation between the most forward and
 backward partons. In most cases the extremal jets consist of single
 partons, so that $\Delta y_{fb} = \Delta y_{j_f j_b}$. For the less common
 case of two partons forming a jet we observe a maximum distance of $R$
 between the constituents and the jet centre. In rare cases jets have
 more than two constituents. Empirically, they are always within a
 distance of $\tfrac{5}{3}R$ to the centre of the jet, so
 $\Delta y_{fb} \leq \Delta y_{j_f j_b} + \tfrac{10}{3} R$. In practice, the
 extremal partons are required to carry a large fraction of the jet
 transverse momentum and will therefore be much closer to the jet axis.
 
 In summary, for sufficiently large rapidity separations we can use the
 approximation $\Delta y_{fb} \approx \Delta y_{j_f j_b}$. This scenario
 is depicted here:
 \begin{center}
   \includegraphics[width=0.5\linewidth]{ps_large_y}
 \end{center}
 If there is no overlap between jets, the probability $p_{\cal J, >}$ for
 an extra gluon to end up inside a jet is then given by
 \begin{equation}
   \label{eq:p_J_large}
   p_{\cal J, >} = \frac{(m - 1)\*R^2}{2\Delta y_{j_f j_b}}\,.
 \end{equation}
 For a very small rapidity separation, eq.~\eqref{eq:p_J_large}
 obviously overestimates the true probability. The maximum phase space
 covered by jets in the limit of a vanishing rapidity distance between
 all partons is $2mR \Delta y_{fb}$:
 \begin{center}
   \includegraphics[width=0.5\linewidth]{ps_small_y}
 \end{center}
 We therefore estimate the probability for a parton to end up inside a jet as
 \begin{equation}
   \label{eq:p_J}
   p_{\cal J} = \min\bigg(\frac{(m - 1)\*R^2}{2\Delta y_{j_f j_b}}, \frac{mR}{\pi}\bigg)\,.
 \end{equation}
 Here we compare this estimate with the actually observed
 fraction of additional emissions into jets as a function of the rapidity
 separation:
 \begin{center}
   \includegraphics[width=0.75\linewidth]{pJ}
 \end{center}
 
 \subsubsection{Gluons outside Jets}
 \label{sec:gluons_nonjet}
 
 Using our estimate for the probability of a gluon to be a jet
 constituent, we choose a number $n_{g,{\cal J}}$ of gluons inside
 jets, which also fixes the number $n_g - n_{g,{\cal J}}$ of gluons
 outside jets. As explained later on, we need to generate the momenta of
 the gluons outside jets first. This is done in
 \lstinline!PhaseSpacePoint::gen_non_jet!.
 
 The azimuthal angle $\phi$ is generated flat within $0\leq \phi \leq 2
 \pi$. The allowed rapidity interval is set by the most forward and
 backward partons, which are necessarily inside jets. Since these parton
 rapidities are not known at this point, we also have to postpone the
 rapidity generation for the gluons outside jets. For the scalar
 transverse momentum $p_\perp = |\mathbf{p}_\perp|$ of a gluon outside
 jets we use the parametrisation
 \begin{equation}
   \label{eq:p_nonjet}
   p_\perp = \lambda + \tilde{p}_\perp\*\tan(\tau\*r)\,, \qquad
   \tau = \arctan\bigg(\frac{p_{\perp{\cal J}_\text{min}} - \lambda}{\tilde{p}_\perp}\bigg)\,.
 \end{equation}
 For $r \in [0,1)$, $p_\perp$ is always less than the minimum momentum
 $p_{\perp{\cal J}_\text{min}}$ required for a jet. $\tilde{p}_\perp$ is
 a free parameter, a good empirical value is $\tilde{p}_\perp = [1.3 +
 0.2\*(n_g - n_{g,\cal J})]\,$GeV
 
 \subsubsection{Resummation jet momenta}
 \label{sec:ptj_res}
 
 On the one hand, each jet momentum is given by the sum of its
 constituent momenta. On the other hand, the resummation jet momenta are
 fixed by the constraints in line five of the master
 equation~\eqref{eq:resumdijetFKLmatched2}. We therefore have to
 calculate the resummation jet momenta from these constraints before
 generating the momenta of the gluons inside jets. This is done in
 \lstinline!PhaseSpacePoint::reshuffle! and in the free
 \lstinline!resummation_jet_momenta! function (declared in \texttt{resummation\_jet.hh}).
 
 The resummation jet momenta are determined by the $\delta$ functions in
 line five of eq.~(\ref{eq:resumdijetFKLmatched2}). The rapidities are
 fixed to the rapidities of the jets in the input fixed-order events, so
 that the FKL ordering is guaranteed to be preserved.
 
 In traditional \HEJ reshuffling the transverse momentum are given through
 \begin{equation}
 \label{eq:ptreassign_old}
 \mathbf{p}^B_{\mathcal{J}_{l\perp}} = \mathbf{j}_{l\perp} \equiv \mathbf{p}_{\mathcal{J}_{l}\perp}
 + \mathbf{q}_\perp \,\frac{|\mathbf{p}_{\mathcal{J}_{l}\perp}|}{P_\perp},
 \end{equation}
 where $\mathbf{q}_\perp = \sum_{j=1}^n \mathbf{p}_{i\perp}
 \bigg[1-\theta\bigg(\sum_{j=1}^{m}{\cal J}_j(p_i)\bigg)\bigg] $ is the
 total transverse momentum of all partons \emph{outside} jets and
 $P_\perp = \sum_{j=1}^m |\mathbf{p}_{\mathcal{J}_{j}\perp}|$. Since the
 total transverse momentum of an event vanishes, we can also use
 $\mathbf{q}_\perp = - \sum_{j=1}^m
 \mathbf{p}_{\mathcal{J}_{j}\perp}$. Eq.~(\ref{eq:ptreassign}) is a
 non-linear system of equations in the resummation jet momenta
 $\mathbf{p}_{\mathcal{J}_{l}\perp}$. Hence we would have to solve
 \begin{equation}
 \label{eq:ptreassign_eq}
 \mathbf{p}_{\mathcal{J}_{l}\perp}=\mathbf{j}^B_{l\perp} \equiv\mathbf{j}_{l\perp}^{-1}
 \left(\mathbf{p}^B_{\mathcal{J}_{l\perp}}\right)
 \end{equation}
 numerically.
 
 Since solving such a system is computationally expensive, we instead
 change the reshuffling around to be linear in the resummation jet
 momenta. Hence~\eqref{eq:ptreassign_eq} gets replaces by
 \begin{equation}
 \label{eq:ptreassign}
 \mathbf{p}_{\mathcal{J}_{l\perp}} = \mathbf{j}^B_{l\perp} \equiv \mathbf{p}^B_{\mathcal{J}_{l}\perp}
 - \mathbf{q}_\perp \,\frac{|\mathbf{p}^B_{\mathcal{J}_{l}\perp}|}{P^B_\perp},
 \end{equation}
 which is linear in the resummation momentum. Consequently the equivalent
 of~\eqref{eq:ptreassign_old} is non-linear in the Born momentum. However
 the exact form of~\eqref{eq:ptreassign_old} is not relevant for the resummation.
 Both methods have been tested for two and three jets with the \textsc{rivet}
 standard analysis \texttt{MC\_JETS}. They didn't show any differences even
 after $10^9$ events.
 
 The reshuffling relation~\eqref{eq:ptreassign} allows the transverse
 momenta $p^B_{\mathcal{J}_{l\perp}}$ of the fixed-order jets to be
 somewhat below the minimum transverse momentum of resummation jets. It
 is crucial that this difference does not become too large, as the
 fixed-order cross section diverges for vanishing transverse momenta. In
 the production of a Higgs boson with resummation jets above $30\,$GeV we observe
 that the contribution from fixed-order events with jets softer than
 about $20\,$GeV can be safely neglected. This is shown in the following
 plot of the differential cross section over the transverse momentum of
 the softest fixed-order jet:
 \begin{center}
   \includegraphics[width=.75\textwidth]{ptBMin}
 \end{center}
 
 Finally, we have to account for the fact that the reshuffling
 relation~\eqref{eq:ptreassign} is non-linear in the Born momenta. To
 arrive at the master formula~\eqref{eq:resumdijetFKLmatched2} for the
 cross section, we have introduced unity in the form of an integral over
 the Born momenta with $\delta$ functions in the integrand, that is
 \begin{equation}
   \label{eq:delta_intro}
   1 = \int_{p_{j\perp}^B=0}^{p_{j\perp}^B=\infty}
   \mathrm{d}^2\mathbf{p}_{j\perp}^B\delta^{(2)}(\mathbf{p}_{\mathcal{J}_{j\perp}}^B -
   \mathbf{j}_{j\perp})\,.
 \end{equation}
 If the arguments of the $\delta$ functions are not linear in the Born
 momenta, we have to compensate with additional Jacobians as
 factors. Explicitly, for the reshuffling relation~\eqref{eq:ptreassign}
 we have
 \begin{equation}
   \label{eq:delta_rewrite}
   \prod_{l=1}^m \delta^{(2)}(\mathbf{p}_{\mathcal{J}_{l\perp}}^B -
   \mathbf{j}_{l\perp}) = \Delta \prod_{l=1}^m \delta^{(2)}(\mathbf{p}_{\mathcal{J}_{l\perp}} -
   \mathbf{j}_{l\perp}^B)\,,
 \end{equation}
 where $\mathbf{j}_{l\perp}^B$ is given by~\eqref{eq:ptreassign_eq} and only
 depends on the Born momenta. We have extended the product to run to $m$
 instead of $m-1$ by eliminating the last $\delta$ function
 $\delta^{(2)}\!\!\left(\sum_{k=1}^n \mathbf{p}_{k\perp}\right )$.
 The Jacobian $\Delta$ is the determinant of a $2m \times 2m$ matrix with $l, l' = 1,\dots,m$
 and $X, X' = x,y$.
 \begin{equation}
   \label{eq:jacobian}
   \Delta = \left|\frac{\partial\,\mathbf{j}^B_{l'\perp}}{\partial\, \mathbf{p}^B_{{\cal J}_l \perp}} \right|
     = \left| \delta_{l l'} \delta_{X X'} - \frac{q_X\, p^B_{{\cal
         J}_{l'}X'}}{\left|\mathbf{p}^B_{{\cal J}_{l'} \perp}\right| P^B_\perp}\left(\delta_{l l'}
        - \frac{\left|\mathbf{p}^B_{{\cal J}_l \perp}\right|}{P^B_\perp}\right)\right|\,.
 \end{equation}
 The determinant is calculated in \lstinline!resummation_jet_weight!,
 again coming from the \texttt{resummation\_jet.hh} header.
 Having to introduce this Jacobian is not a disadvantage specific to the new
 reshuffling. If we instead use the old reshuffling
 relation~\eqref{eq:ptreassign_old} we \emph{also} have to introduce a
 similar Jacobian since we actually want to integrate over the
 resummation phase space and need to transform the argument of the
 $\delta$ function to be linear in the resummation momenta for this.
 
 \subsubsection{Gluons inside Jets}
 \label{sec:gluons_jet}
 
 After the steps outlined in section~\ref{sec:psp_ng_jet}, we have a
 total number of $m + n_{g,{\cal J}}$ constituents. In
 \lstinline!PhaseSpacePoint::distribute_jet_partons! we distribute them
 randomly among the jets such that each jet has at least one
 constituent. We then generate their momenta in
 \lstinline!PhaseSpacePoint::split! using the \lstinline!Splitter! class.
 
 The phase space integral for a jet ${\cal J}$ is given by
 \begin{equation}
   \label{eq:ps_jetparton} \prod_{i\text{ in }{\cal J}} \bigg(\int
 \mathrm{d}\mathbf{p}_{i\perp}\ \int \mathrm{d} y_i
 \bigg)\delta^{(2)}\Big(\sum_{i\text{ in }{\cal J}} \mathbf{p}_{i\perp} -
 \mathbf{j}_{\perp}^B\Big)\delta(y_{\mathcal{J}}-y^B_{\mathcal{J}})\,.
 \end{equation}
 For jets with a single constituent, the parton momentum is obiously equal to the
 jet momentum. In the case of two constituents, we observe that the
 partons are always inside the jet cone with radius $R$ and often very
 close to the jet centre. The following plots show the typical relative
 distance $\Delta R/R$ for this scenario:
 \begin{center}
   \includegraphics[width=0.45\linewidth]{dR_2}
   \includegraphics[width=0.45\linewidth]{dR_2_small}
 \end{center}
 According to this preference for small values of $\Delta R$, we
 parametrise the $\Delta R$ integrals as
 \begin{equation}
   \label{eq:dR_sampling}
   \frac{\Delta R}{R} =
   \begin{cases}
     0.25\,x_R & x_R < 0.4 \\
     1.5\,x_R - 0.5 & x_R \geq 0.4
   \end{cases}\,.
 \end{equation}
 Next, we generate $\Theta_1 \equiv \Theta$ and use the constraint $\Theta_2 = \Theta
 \pm \pi$. The transverse momentum of the first parton is then given by
 \begin{equation}
   \label{eq:delta_constraints}
   p_{1\perp} =
   \frac{p_{\mathcal{J} y} - \tan(\phi_2) p_{\mathcal{J} x}}{\sin(\phi_1)
     - \tan(\phi_2)\cos(\phi_1)}\,.
 \end{equation}
 We get $p_{2\perp}$ by exchanging $1 \leftrightarrow 2$ in the
 indices. To obtain the Jacobian of the transformation, we start from the
 single jet phase space eq.~(\ref{eq:ps_jetparton}) with the rapidity
 delta function already rewritten to be linear in the rapidity of the
 last parton, i.e.
 \begin{equation}
   \label{eq:jet_2p}
 \prod_{i=1,2} \bigg(\int
 \mathrm{d}\mathbf{p}_{i\perp}\ \int \mathrm{d} y_i
 \bigg)\delta^{(2)}\Big(\mathbf{p}_{1\perp} + \mathbf{p}_{2\perp} -
 \mathbf{j}_{\perp}^B\Big)\delta(y_2- \dots)\,.
 \end{equation}
 The integral over the second parton momentum is now trivial; we can just replace
 the integral over $y_2$ with the equivalent constraint
 \begin{equation}
   \label{eq:R2}
 \int \mathrm{d}R_2 \ \delta\bigg(R_2 - \bigg[\phi_{\cal J} - \arctan
 \bigg(\frac{p_{{\cal J}y} - p_{1y}}{p_{{\cal J}x} -
   p_{1x}}\bigg)\bigg]/\cos \Theta\bigg) \,.
 \end{equation}
 In order to fix the integral over $p_{1\perp}$ instead, we rewrite this
 $\delta$ function. This introduces the Jacobian
 \begin{equation}
   \label{eq:jac_pt1}
 \bigg|\frac{\partial p_{1\perp}}{\partial R_2} \bigg| =
 \frac{\cos(\Theta)\mathbf{p}_{2\perp}^2}{p_{{\cal J}\perp}\sin(\phi_{\cal J}-\phi_1)}\,.
 \end{equation}
 The final form of the integral over the two parton momenta is then
 \begin{equation}
   \label{eq:ps_jet_2p}
 \int \mathrm{d}R_1\ R_1 \int \mathrm{d}R_2 \int \mathrm{d}x_\Theta\ 2\pi \int
 \mathrm{d}p_{1\perp}\ p_{1\perp} \int \mathrm{d}p_{2\perp}
 \ \bigg|\frac{\partial p_{1\perp}}{\partial R_2} \bigg|\delta(p_{1\perp}
 -\dots) \delta(p_{2\perp} - \dots)\,.
 \end{equation}
 
 As is evident from section~\ref{sec:psp_ng_jet}, jets with three or more
 constituents are rare and an efficient phase-space sampling is less
 important. For such jets, we exploit the observation that partons with a
 distance larger than $R_{\text{max}} = \tfrac{5}{3} R$ to
 the jet centre are never clustered into the jet. Assuming $N$
 constituents, we generate all components
 for the first $N-1$ partons and fix the remaining parton with the
 $\delta$-functional. In order to end up inside the jet, we use the
 parametrisation
 \begin{align}
   \label{eq:ps_jet_param}
   \phi_i ={}& \phi_{\cal J} + \Delta \phi_i\,, & \Delta \phi_i ={}&  \Delta
                                                                  R_i
                                                                  \cos(\Theta_i)\,, \\
   y_i ={}& y_{\cal J} + \Delta y_i\,, & \Delta y_i ={}&  \Delta
                                                                  R_i
                                                                  \sin(\Theta_i)\,,
 \end{align}
 and generate $\Theta_i$ and $\Delta R_i$ randomly with $\Delta R_i \leq
 R_{\text{max}}$ and the empiric value $R_{\text{max}} = 5\*R/3$. We can
 then write the phase space integral for a single parton as $(p_\perp = |\mathbf{p}_\perp|)$
 \begin{equation}
   \label{eq:ps_jetparton_x}
   \int \mathrm{d}\mathbf{p}_{\perp}\ \int
         \mathrm{d} y \approx \int_{\Box} \mathrm{d}x_{\perp}
         \mathrm{d}x_{ R}
         \mathrm{d}x_{\theta}\
         2\*\pi\,\*R_{\text{max}}^2\,\*x_{R}\,\*p_{\perp}\,\*(p_{\perp,\text{max}}
         - p_{\perp,\text{min}})
 \end{equation}
 with
 \begin{align}
   \label{eq:ps_jetparton_parameters}
   \Delta \phi ={}&  R_{\text{max}}\*x_{R}\*\cos(2\*\pi\*x_\theta)\,,&
   \Delta y ={}& R_{\text{max}}\*x_{R}\*\sin(2\*\pi\*x_\theta)\,, \\
   p_{\perp} ={}& (p_{\perp,\text{max}} - p_{\perp,\text{min}})\*x_\perp +
   p_{\perp,\text{min}}\,.
 \end{align}
 $p_{\perp,\text{max}}$ is determined from the requirement that the total
 contribution from the first $n-1$ partons --- i.e. the projection onto the
 jet $p_{\perp}$ axis --- must never exceed the jet $p_\perp$. This gives
 \todo{This bound is too high}
 \begin{equation}
   \label{eq:pt_max}
   p_{i\perp,\text{max}} = \frac{p_{{\cal J}\perp} - \sum_{j<i} p_{j\perp}
     \cos \Delta
 \phi_j}{\cos \Delta
 \phi_i}\,.
 \end{equation}
 The $x$ and $y$ components of the last parton follow immediately from
 the first $\delta$ function. The last rapidity is fixed by the condition that
 the jet rapidity is kept fixed by the reshuffling, i.e.
 \begin{equation}
   \label{eq:yJ_delta}
   y^B_{\cal J} = y_{\cal J} = \frac 1 2 \ln \frac{\sum_{i=1}^n  E_i+ p_{iz}}{\sum_{i=1}^n E_i - p_{iz}}\,.
 \end{equation}
 With $E_n \pm p_{nz} = p_{n\perp}\exp(\pm y_n)$ this can be rewritten to
 \begin{equation}
   \label{eq:yn_quad_eq}
   \exp(2y_{\cal J}) = \frac{\sum_{i=1}^{n-1}  E_i+ p_{iz}+p_{n\perp} \exp(y_n)}{\sum_{i=1}^{n-1} E_i - p_{iz}+p_{n\perp} \exp(-y_n)}\,,
 \end{equation}
 which is a quadratic equation in $\exp(y_n)$. The physical solution is
 \begin{align}
   \label{eq:yn}
   y_n ={}& \log\Big(-b + \sqrt{b^2 + \exp(2y_{\cal J})}\,\Big)\,,\\
 b ={}& \bigg(\sum_{i=1}^{n-1}  E_i + p_{iz} - \exp(2y_{\cal J})
   \sum_{i=1}^{n-1}  E_i - p_{iz}\bigg)/(2 p_{n\perp})\,.
 \end{align}
 \todo{what's wrong with the following?} To eliminate the remaining rapidity
 integral, we transform the $\delta$ function to be linear in the
 rapidity $y$ of the last parton. The corresponding Jacobian is
 \begin{equation}
   \label{eq:jacobian_y}
   \bigg|\frac{\partial y_{\cal J}}{\partial y_n}\bigg|^{-1} = 2 \bigg( \frac{E_n +
     p_{nz}}{E_{\cal J} + p_{{\cal J}z}} + \frac{E_n - p_{nz}}{E_{\cal J} -
     p_{{\cal J}z}}\bigg)^{-1}\,.
 \end{equation}
 Finally, we check that all designated constituents are actually
 clustered into the considered jet.
 
 \subsubsection{Final steps}
 \label{sec:final}
 
 Knowing the rapidity span covered by the extremal partons, we can now
 generate the rapdities for the partons outside jets. We perform jet
 clustering on all partons and check in
 \lstinline!PhaseSpacePoint::jets_ok! that all the following criteria are
 fulfilled:
 \begin{itemize}
 \item The number of resummation jets must match the number of
   fixed-order jets.
 \item No partons designated to be outside jets may end up inside jets.
 \item All other outgoing partons \emph{must} end up inside jets.
 \item The extremal (in rapidity) partons must be inside the extremal
   jets. If there is, for example, an unordered forward emission, the
   most forward parton must end up inside the most forward jet and the
   next parton must end up inside second jet.
 \item The rapidities of fixed-order and resummation jets must match.
 \end{itemize}
 After this, we adjust the phase-space normalisation according to the
 third line of eq.~(\ref{eq:resumdijetFKLmatched2}), determine the
 flavours of the outgoing partons, and adopt any additional colourless
 bosons from the fixed-order input event. Finally, we use momentum
 conservation to reconstruct the momenta of the incoming partons.
 
 \subsection{Colour connection}
 \label{sec:Colour}
 
 \begin{figure}
   \input{src/ColourConnect.tex}
   \caption{Left: Non-crossing colour flow dominating in the MRK limit. The
     crossing of the colour line connecting to particle 2 can be resolved by
     writing particle 2 on the left. Right: A colour flow with a (manifest)
     colour-crossing. The crossing can only be resolved if one breaks the
     rapidities order, e.g. switching particles 2 and 3. From~\cite{Andersen:2017sht}.}
   \label{fig:Colour_crossing}
 \end{figure}
 
 After the phase space for the resummation event is generated, we can construct
 the colour for each particle. To generate the colour flow one has to call
 \lstinline!Event::generate_colours! on any \HEJ configuration. For non-\HEJ
 event we do not change the colour, and assume it is provided by the user (e.g.
 through the LHE file input). The colour connection is done in the large $N_c$
 (infinite number of colour) limit with leading colour in
 MRK~\cite{Andersen:2008ue, Andersen:2017sht}. The idea is to allow only
 $t$-channel colour exchange, without any crossing colour lines. For example the
 colour crossing in the colour connection on the left of
 figure~\ref{fig:Colour_crossing} can be resolved by switching \textit{particle
 2} to the left.
 
 We can write down the colour connections by following the colour flow from
 \textit{gluon a} to \textit{gluon b} and back to \textit{gluon a}, e.g.
 figure~\ref{fig:Colour_gleft} corresponds to $a123ba$. In such an expression any
 valid, non-crossing colour flow will connect all external legs while respecting
 the rapidity ordering. Thus configurations like the left of
 figure~\ref{fig:Colour_crossing} are allowed ($a134b2a$), but the right of the
 same figures breaks the rapidity ordering between 2 and 3 ($a1324ba$). Note that
 connections between $b$ and $a$ are in inverse order, e.g. $ab321a$ corresponds to~\ref{fig:Colour_gright} ($a123ba$) just with colour and anti-colour swapped.
 
 \begin{figure}
 \centering
 \subcaptionbox{$a123ba$\label{fig:Colour_gright}}{
   \includegraphics[height=0.25\textwidth]{colour_gright.jpg}}
 \subcaptionbox{$a13b2a$\label{fig:Colour_gleft}}{
   \includegraphics[height=0.25\textwidth]{colour_gleft.jpg}}
 \subcaptionbox{$a\_123ba$\label{fig:Colour_qx}}{
   \includegraphics[height=0.25\textwidth]{colour_qx.jpg}}
 \subcaptionbox{$a\_23b1a$\label{fig:Colour_uno}}{
   \includegraphics[height=0.25\textwidth]{colour_uno.jpg}}
 \subcaptionbox{$a14b3\_2a$\label{fig:Colour_qqx}}{
   \includegraphics[height=0.25\textwidth]{colour_centralqqx.jpg}}
 \caption{Different colour non-crossing colour connections. Both incoming
   particles are drawn at the top or bottom and the outgoing left or right.
   The Feynman diagram is shown in black and the colour flow in blue.}
   %TODO Maybe make these plots nicer (in Latex/asy)
 \end{figure}
 
 If we replace two gluons with a quark, (anti-)quark pair we break one of the
 colour connections. Still the basic concept from before holds, we just have to
 treat the connection between two (anti-)quarks like an unmovable (anti-)colour.
 We denote such a connection by a underscore (e.g. $1\_a$). For example the
 equivalent of~\ref{fig:Colour_gright} ($a123ba$) with an incoming antiquark
 is~\ref{fig:Colour_qx} ($a\_123ba$). As said this also holds for other
 subleading configurations like unordered emission~\ref{fig:Colour_uno} or
 central quark-antiquark pairs~\ref{fig:Colour_qqx} \footnote{Obviously this can
 not be guaranteed for non-\HEJ configurations, e.g. $qQ\to Qq$ requires a
 $u$-channel exchange.}.
 
 Some rapidity ordering can have multiple possible colour connections,
 e.g.~\ref{fig:Colour_gright} and~\ref{fig:Colour_gleft}. This is always the case
 if a gluon radiates off a gluon line. In that case we randomly connect the gluon
 to either the colour or anti-colour. Thus in the generation we keep track
 whether we are on a quark or gluon line, and act accordingly.
 
 \subsection{The matrix element }
 \label{sec:ME}
 
 The derivation of the \HEJ matrix element is explained in some detail
 in~\cite{Andersen:2017kfc}, where also results for leading and
 subleading matrix elements for pure multijet production and production
 of a Higgs boson with at least two associated jets are listed. Matrix
 elements for $Z/\gamma^*$ production together with jets are
 given in~\cite{Andersen:2016vkp}, but not yet included.
 A full list of all implemented currents is given in
 section~\ref{sec:currents_impl}.
 
 The matrix elements are implemented in the \lstinline!MatrixElement!
 class. To discuss the structure, let us consider the squared matrix
 element for FKL multijet production with $n$ final-state partons:
 \begin{align}
   \label{eq:ME}
   \begin{split}
         \overline{\left|\mathcal{M}_\HEJ^{f_1 f_2 \to f_1
           g\cdots g f_2}\right|}^2 = \ &\frac {(4\pi\alpha_s)^n} {4\ (N_c^2-1)}
     \cdot\ \textcolor{blue}{\frac {K_{f_1}(p_1^-, p_a^-)} {t_1}\ \cdot\ \frac{K_{f_2}(p_n^+, p_b^+)}{t_{n-1}}\  \cdot\ \left\|S_{f_1 f_2\to f_1 f_2}\right\|^2}\\
      & \cdot \prod_{i=1}^{n-2} \textcolor{gray}{\left( \frac{-C_A}{t_it_{i+1}}\
        V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)}\\
      & \cdot \prod_{j=1}^{n-1} \textcolor{red}{\exp\left[\omega^0(q_{j\perp})(y_{j+1}-y_j)\right]}.
   \end{split}
 \end{align}
 The structure and momentum assignment of the unsquared matrix element is
 as illustrated here:
 \begin{center}
   \includegraphics{HEJ_amplitude}
 \end{center}
 The square
 of the complete matrix element as given in eq.~\eqref{eq:ME} is
 calculated by \lstinline!MatrixElement::operator()!.  The \textcolor{red}{last line} of
 eq.~\eqref{eq:ME} constitutes the all-order virtual correction,
 implemented in
 \lstinline!MatrixElement::virtual_corrections!.
 $\omega^0$ is the
 \textit{regularised Regge trajectory}
 \begin{equation}
   \label{eq:omega_0}
   \omega^0(q_\perp) = - C_A \frac{\alpha_s}{\pi} \log \left(\frac{q_\perp^2}{\lambda^2}\right)\,,
 \end{equation}
 where $\lambda$ is the slicing parameter limiting the softness of real
 gluon emissions, cf. eq.~\eqref{eq:resumdijetFKLmatched2}. $\lambda$ can be
 changed at runtime by setting \lstinline!regulator parameter! in
 \lstinline!conifg.yml!.
 
 The remaining parts, which correspond to the square of the leading-order
 \HEJ matrix element $\overline{\left|\mathcal{M}_\text{LO,
 \HEJ}^{f_1f_2\to f_1g\cdots
 gf_2}\big(\big\{p^B_j\big\}\big)\right|}^{2}$, are computed in
 \lstinline!MatrixElement::tree!. We can further factor off the
 scale-dependent ``parametric'' part
 \lstinline!MatrixElement::tree_param! containing all factors of the
 strong coupling $4\pi\alpha_s$. Using this function saves some CPU time
 when adjusting the renormalisation scale, see
 section~\ref{sec:resum}. The remaining ``kinematic'' factors are
 calculated in \lstinline!MatrixElement::kin!.
 
 \subsubsection{Matrix elements for Higgs plus dijet}
 \label{sec:ME_h_jets}
 
 In the production of a Higgs boson together with jets the parametric
 parts and the virtual corrections only require minor changes in the
 respective functions. However, in the ``kinematic'' parts we have to
 distinguish between several cases, which is done in
 \lstinline!MatrixElement::tree_kin_Higgs!. The Higgs boson can be
 \emph{central}, i.e. inside the rapidity range spanned by the extremal
 partons (\lstinline!MatrixElement::tree_kin_Higgs_central!) or
 \emph{peripheral} and outside this range
 (\lstinline!MatrixElement::tree_kin_Higgs_first!  or
 \lstinline!MatrixElement::tree_kin_Higgs_last!). Currently the current for an
 unordered emission with an Higgs on the same side it not implemented
 \footnote{In principle emitting a Higgs boson \textit{on the other
 side} of the unordered gluon is possible by contracting an unordered and
 external Higgs current. Obviously this would not cover all possible
 configurations, e.g. $qQ\to HgqQ$ requires contraction of the standard $Q\to Q$
 current with an (unknown) $q\to Hgq$ one.}.
 
 If a Higgs boson with momentum $p_H$ is emitted centrally, after parton
 $j$ in rapidity, the matrix element reads
 \begin{equation}
   \label{eq:ME_h_jets_central}
   \begin{split}
         \overline{\left|\mathcal{M}_\HEJ^{f_1 f_2 \to f_1 g\cdot H
               \cdot g f_2}\right|}^2 = \ &\frac {\alpha_s^2 (4\pi\alpha_s)^n} {4\ (N_c^2-1)}
     \cdot\ \textcolor{blue}{\frac {K_{f_1}(p_1^-, p_a^-)} {t_1}\
 \cdot\ \frac{1}{t_j t_{j+1}} \cdot\ \frac{K_{f_2}(p_n^+, p_b^+)}{t_{n}}\  \cdot\ \left\|S_{f_1
           f_2\to f_1 H f_2}\right\|^2}\\
      & \cdot \prod_{\substack{i=1\\i \neq j}}^{n-1} \textcolor{gray}{\left( \frac{-C_A}{t_it_{i+1}}\
        V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)}\\
      & \cdot \textcolor{red}{\prod_{i=1}^{n-1}
      \exp\left[\omega^0(q_{i\perp})\Delta y_i\right]}
   \end{split}
 \end{equation}
 with the momentum definitions
 \begin{center}
   \includegraphics{HEJ_central_Higgs_amplitude}
 \end{center}
 $q_i$ is the $i$th $t$-channel momentum and $\Delta y_i$ the rapidity
 gap between outgoing \emph{particles} (not partons) $i$ and $i+1$ in
 rapidity ordering.
 
 For \emph{peripheral} emission in the backward direction
 (\lstinline!MatrixElement::tree_kin_Higgs_first!) we first check whether
 the most backward parton is a gluon or an (anti-)quark. In the latter
 case the leading contribution to the matrix element arises through
 emission off the $t$-channel gluons and we can use the same formula
 eq.~(\ref{eq:ME_h_jets_central}) as for central emission. If the most
 backward parton is a gluon, the square of the matrix element can be
 written as
 \begin{equation}
   \label{eq:ME_h_jets_peripheral}
   \begin{split}
         \overline{\left|\mathcal{M}_\HEJ^{g f_2 \to H g\cdot g f_2}\right|}^2 = \ &\frac {\alpha_s^2 (4\pi\alpha_s)^n} {\textcolor{blue}{4\ (N_c^2-1)}}
     \textcolor{blue}{\cdot\ K_{H}\
       \frac{K_{f_2}(p_n^+, p_b^+)}{t_{n-1}}\  \cdot\ \left\|S_{g
           f_2\to H g f_2}\right\|^2}\\
      & \cdot \prod_{\substack{i=1}}^{n-2} \textcolor{gray}{\left( \frac{-C_A}{t_it_{i+1}}\
        V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)}\\
      & \cdot \textcolor{red}{\prod_{i=1}^{n-1}
      \exp\left[\omega^0(q_{i\perp}) (y_{i+1} - y_i)\right]}
   \end{split}
 \end{equation}
 with the momenta as follows:
 \begin{center}
   \includegraphics{HEJ_peripheral_Higgs_amplitude}
 \end{center}
 The \textcolor{blue}{blue part} is implemented in
 \lstinline!MatrixElement::MH2_forwardH!. All other building blocks are
 already available.\todo{Impact factors} The actual current contraction
 is calculated in \lstinline!MH2gq_outsideH! inside
 \lstinline!src/Hjets.cc!, which corresponds to $\tfrac{16 \pi^2}{t_1} \left\|S_{g
           f_2\to H g f_2}\right\|^2$.\todo{Fix this insane normalisation}
 
 The forward emission of a Higgs boson is completely analogous. We can
 use the same function \lstinline!MatrixElement::MH2_forwardH!, swapping
 $p_1 \leftrightarrow p_n,\,p_a \leftrightarrow p_b$.
 
 \subsubsection{FKL ladder and Lipatov vertices}
 \label{sec:FKL_ladder}
 
 The ``FKL ladder'' is the product
 \begin{equation}
   \label{eq:FKL_ladder}
   \prod_{i=1}^{n-2} \left( \frac{-C_A}{t_it_{i+1}}\
     V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1}) \right)
 \end{equation}
 appearing in the square of the matrix element for $n$ parton production,
 cf. eq.~(\ref{eq:ME}), and implemented in
 \lstinline!MatrixElement::FKL_ladder_weight!. The Lipatov vertex contraction
 $V^\mu(q_i,q_{i+1})V_\mu(q_i,q_{i+1})$ is implemented \lstinline!C2Lipatovots!.
 It is given by \todo{equation} \todo{mention difference between the two versions
 of \lstinline!C2Lipatovots!, maybe even get rid of one}.
 
 \subsubsection{Currents}
 \label{sec:currents}
 
 The current factors $\frac{K_{f_1}K_{f_2}}{t_1 t_{n-1}}\left\|S_{f_1
 f_2\to f_1 f_2}\right\|^2$ and their extensions for unordered and Higgs
 boson emissions are implemented in the \lstinline!jM2!$\dots$ functions
 of \texttt{src/Hjets.cc}. \todo{Only $\|S\|^2$ should be in currents}
 \footnote{The current implementation for
 Higgs production in \texttt{src/Hjets.cc} includes the $1/4$ factor
 inside $S$, opposing to~\eqref{eq:ME}. Thus the overall normalisation is
 unaffected.} The ``colour acceleration multiplier'' (CAM) $K_{f}$
 for a parton $f\in\{g,q,\bar{q}\}$ is defined as
 \begin{align}
   \label{eq:K_g}
  K_g(p_1^-, p_a^-) ={}& \frac{1}{2}\left(\frac{p_1^-}{p_a^-} + \frac{p_a^-}{p_1^-}\right)\left(C_A -
                         \frac{1}{C_A}\right)+\frac{1}{C_A}\\
   \label{eq:K_q}
   K_q(p_1^-, p_a^-) ={}&K_{\bar{q}}(p_1^-, p_a^-) = C_F\,.
 \end{align}
 The Higgs current CAM used in eq.~(\ref{eq:ME_h_jets_peripheral}) is
 \begin{equation}
   \label{eq:K_H}
   K_H = C_A\,.
 \end{equation}
 The current contractions are given by\todo{check all this
   carefully!}
 \begin{align}
   \label{eq:S}
   \left\|S_{f_1 f_2\to f_1 f_2}\right\|^2 ={}& \sum_{\substack{\lambda_a =
   +,-\\\lambda_b = +,-}} \left|j^{\lambda_a}_\mu(p_1, p_a)\
 j^{\lambda_b\,\mu}(p_n, p_b)\right|^2 = 2\sum_{\lambda =
   +,-} \left|j^{-}_\mu(p_1, p_a)\ j^{\lambda\,\mu}(p_n, p_b)\right|^2\,,\\
   \left\|S_{f_1 f_2\to f_1 H f_2}\right\|^2 ={}& \sum_{\substack{\lambda_a =
   +,-\\\lambda_b = +,-}} \left|j^{\lambda_a}_\mu(p_1, p_a)V_H^{\mu\nu}(q_j, q_{j+1})\
   j^{\lambda_b}_\nu(p_n, p_b)\right|^2\,,\\
   \left\|S_{g f_2 \to  H g f_2}\right\|^2 ={}& \sum_{
   \substack{
   \lambda_{a} = +,-\\
   \lambda_{1} =+,-\\
   \lambda_{b} = +,-
   }}
  \left|j^{\lambda_a\lambda_1}_{H\,\mu}(p_1, p_a, p_H)\ j^{\lambda_b\,\mu}(p_n, p_b)\right|^2\,.
 \end{align}
 Currently, most of the expressions for the current contractions are hard-coded. Work is ongoing to use the current generator described in section~\ref{sec:cur_gen} instead.
 
 \subsection{Unweighting}
 \label{sec:unweight}
 
 Straightforward event generation tends to produce many events with small
 weights. Those events have a negligible contribution to the final
 observables, but can take up considerable storage space and CPU time in
 later processing stages. This problem can be addressed by unweighting.
 
 For naive unweighting, one would determine the maximum weight
 $w_\text{max}$ of all events, discard each event with weight $w$ with a
 probability $p=w/w_\text{max}$, and set the weights of all remaining
 events to $w_\text{max}$. The downside to this procedure is that it also
 eliminates a sizeable fraction of events with moderate weight, so that
 the statistical convergence deteriorates. Naive unweighting can be
 performed by using the \lstinline!set_cut_to_maxwt! member function of the
 \lstinline!Unweighter! on the events and then call the
 \lstinline!unweight! member function. It can be enabled for the
 resummation events as explained in the user documentation.
 
 To ameliorate the problem of naive unweighting, we also implement
 partial unweighting. That is, we perform unweighting only for events
 with sufficiently small weights. When using the \lstinline!Unweighter!
 member function \lstinline!set_cut_to_peakwt! we estimate the mean and
 width of the weight-weight distribution from a sample of events. We
 use these estimates to determine the maximum weight below which
 unweighting is performed; events with a larger weight are not
 touched. The actual unweighting is again done in the
 \lstinline!Unweighter::unweight! function.
 
 To estimate the peak weight we employ the following heuristic
 algorithm. For a calibration sample of $n$ events, create a histogram
 with $b=\sqrt{n}$ equal-sized bins. The histogram ranges from $
 \log(\min |w_i|)$ to $\log(|\max w_i|)$, where $w_i$ are the event weights. For
 each event, add $|w_i|$ to the corresponding bin. We then prune the
 histogram by setting all bins containing less than $c=n/b$
 events to zero. This effectively removes statistical outliers. The
 logarithm of the peak weight is then the centre of the highest bin in
 the histogram. In principle, the number of bins $b$ and the pruning
 parameter $c$ could be tuned further.
 
 To illustrate the principle, here is a weight-weight histogram
 filled with a sample of 100000 event weights before the pruning:
 \begin{center}
   \includegraphics[width=0.7\linewidth]{wtwt}
 \end{center}
 The peaks to the right are clearly outliers caused by single
 events. After pruning we get the following histogram:
 \begin{center}
   \includegraphics[width=0.7\linewidth]{wtwt_cut}
 \end{center}
 The actual peak weight probably lies above the cut, and the algorithm
 can certainly be improved. Still, the estimate we get from the pruned
 histogram is already good enough to eliminate about $99\%$ of the
 low-weight events.
 
 \section{The current generator}
 \label{sec:cur_gen}
 
 The current generator in the \texttt{current\_generator} directory is
 automatically invoked when building \HEJ 2. Its task is to compute and
 generate C++ code for the current contractions listed in
 section~\ref{sec:currents}. For each source file \texttt{<j\_j>.frm}
 inside \texttt{current\_generator} it generates a corresponding header
 \texttt{include/HEJ/currents/<j\_j>.hh} inside the build directory. The
 header can be included with
 \begin{lstlisting}[language=C++,caption={}]
 #include "HEJ/currents/<j>.hh"
 \end{lstlisting}
 The naming scheme is
 \begin{itemize}
 \item \texttt{j1\_j2.frm} for the contraction of current \texttt{j1} with current \texttt{j2}.
 \item \texttt{j1\_vx\_j2.frm} for the contraction of current \texttt{j1}
 with the vertex \texttt{vx} and the current \texttt{j2}.
 \end{itemize}
 For instance, \texttt{juno\_qqbarW\_j.frm} would indicate the
 contraction of an unordered current with a (central) quark-antiquark-W
 emission vertex and a standard FKL current.
 
 \subsection{Implementing new current contractions}
 \label{sec:cur_gen_new}
 
 To implement a new current contraction \lstinline!jcontr! create a new
 file \texttt{jcontr.frm} inside the \texttt{current\_generator}
-directory. \texttt{jcontr.frm} should contain FORM code, see
-\url{https://www.nikhef.nl/~form/} for more information on FORM. Here
+directory. \texttt{jcontr.frm} should contain \FORM code, see
+\url{https://www.nikhef.nl/~form/} for more information on \FORM. Here
 is a small example:
 \begin{lstlisting}[caption={}]
 * FORM comments are started by an asterisk * at the beginning of a line
 
 * First include the relevant headers
 #include- include/helspin.frm
 #include- include/write.frm
 
 * Define the symbols that appear.
 * UPPERCASE symbols are reserved for internal use
 vectors p1,...,p10;
 indices mu1,...,mu10;
 
 * Define local expressions of the form [NAME HELICITIES]
 * for the current contractions for all relevant helicity configurations
 #do HELICITY1={+,-}
    #do HELICITY2={+,-}
 *     We use the Current function
 *     Current(h, p1, mu1, ..., muX, p2) =
 *     u(p1) \gamma_{mu1} ... \gamma_{muX} u(p2)
 *     where h=+1 or h=-1 is the spinor helicity.
 *     All momenta appearing as arguments have to be *lightlike*
       local [jcontr `HELICITY1'`HELICITY2'] =
          Current(`HELICITY1'1, p1, mu1, p2, mu2, p3)
          *Current(`HELICITY2'1, p4, mu2, p2, mu1, p1);
    #enddo
 #enddo
 .sort
 
 * Main procedure that calculates the contraction
 #call ContractCurrents
 .sort
 
 * Optimise expression
 format O4;
 * Format in a (mostly) c compatible way
 format c;
 * Write start of C++ header file
 #call WriteHeader(`OUTPUT')
 * Write a template function jcontr
 * taking as template arguments two helicities
 * and as arguments the momenta p1,...,p4
 * returning the contractions [jcontr HELICITIES] defined above
 #call WriteOptimised(`OUTPUT',jcontr,2,p1,p2,p3,p4)
 * Wrap up
 #call WriteFooter(`OUTPUT')
 .end
 \end{lstlisting}
 
 \subsection{Calculation of contractions}
 \label{sec:contr_calc}
 
 In order to describe the algorithm for the calculation of current
 contractions we first have to define the currents and establish some
 useful relations.
 
 \subsubsection{Massive momenta}
 \label{sec:p_massive}
 
 We want to use relations for lightlike momenta.  Momenta $P$ that are
 \emph{not} lightlike can be written as the sum of two lightlike
 momenta:
 \begin{equation}
   \label{eq:P_massive}
   P^\mu =  p^\mu + q^\mu\,, \qquad p^2 = q^2 = 0 \,.
 \end{equation}
 This decomposition is not unique. If we impose the arbitrary
 constraint $q_\perp = 0$ and require real-valued momentum
 components we can use the ansatz
 \begin{align}
   \label{eq:P_massive_p}
   p_\mu ={}&  P_\perp\*(\cosh y, \cos \phi, \sin \phi, \sinh y)\,,\\
   \label{eq:P_massive_q}
   q_\mu ={}& (E, 0, 0, s\,E)\,,
 \end{align}
 where $P_\perp$ is the transverse momentum of $P$ and $\phi$ the corresponding azimuthal angle. For the remaining parameters we obtain
 \begin{align}
   \label{eq:P_massive_plus}
 P^+ > 0:& & y ={}& \log \frac{P^+}{P_\perp}\,,\qquad E = \frac{P^2}{2P^+}\,,\qquad s = -1\,,\\
   \label{eq:P_massive_minus}
 P^- > 0:& & y ={}& \log \frac{P_\perp}{P^-}\,,\qquad E = \frac{P^2}{2P^-}\,,\qquad s = +1\,.
 \end{align}
 
 \subsubsection{Currents and current relations}
 \label{sec:current_relations}
 
 Our starting point are generalised currents
 \begin{equation}
   \label{eq:j_gen}
   j^{\pm}(p, \mu_1,\dots,\mu_{2N-1},q) = \bar{u}^{\pm}(p)\gamma_{\mu_1} \dots \gamma_{\mu_{2N-1}} u^\pm(q)\,.
 \end{equation}
 Since there are no masses, we can consider two-component chiral spinors
 \begin{align}
   \label{eq:u_plus}
   u^+(p)={}& \left(\sqrt{p^+}, \sqrt{p^-} \hat{p}_\perp \right) \,,\\
   \label{eq:u_minus}
   u^-(p)={}& \left(\sqrt{p^-} \hat{p}^*_\perp, -\sqrt{p^+}\right)\,,
 \end{align}
 with $p^\pm = E\pm p_z,\, \hat{p}_\perp = \tfrac{p_\perp}{|p_\perp|},\,
 p_\perp = p_x + i p_y$. The spinors for vanishing transverse momentum
 are obtained by replacing $\hat{p}_\perp \to -1$.
 
 This gives
 \begin{equation}
   \label{eq:jminus_gen}
   j^-(p,\mu_1,\dots,\mu_{2N-1},q) = u^{-,\dagger}(p)\ \sigma^-_{\mu_1}\ \sigma^+_{\mu_2}\dots\sigma^-_{\mu_{2N-1}}\ u^{-}(q)\,.
 \end{equation}
 where $\sigma_\mu^\pm = (1, \pm \sigma_i)$ and $\sigma_i$ are the Pauli
 matrices
 \begin{equation}
   \label{eq:Pauli_matrices}
   \sigma_1 =
   \begin{pmatrix}
     0 & 1\\ 1 & 0
   \end{pmatrix}
 \,,
   \qquad \sigma_2 =
   \begin{pmatrix}
     0 & -i\\ i & 0
   \end{pmatrix}
 \,,
   \qquad \sigma_3 =
   \begin{pmatrix}
     1 & 0\\ 0 & -1
   \end{pmatrix}
 \,.
 \end{equation}
 For positive-helicity currents we can either flip all helicities in
 eq.~(\ref{eq:jminus_gen}) or reverse the order of the arguments, i.e.
 \begin{equation}
   \label{eq:jplus_gen}
   j^+(p,\mu_1,\dots,\mu_{2N-1},q) = \big(j^-(p,\mu_1,\dots,\mu_{2N-1},q)\big)^* = j^-(q,\mu_{2N-1},\dots,\mu_1,p) \,.
 \end{equation}
 Using the standard spinor-helicity notation we have
 \begin{gather}
   \label{eq:spinors_spinhel}
   u^+(p) = | p \rangle\,, \qquad u^-(p) = | p ]\,, \qquad u^{+,\dagger}(p) = [ p |\,, \qquad u^{-,\dagger}(p) = \langle p |\,,\\
   \label{eq:current_spinhel}
   j^-(p,\mu_1,\dots,\mu_{2N-1},q) = \langle p |\ \mu_1\ \dots\ \mu_{2N-1}\ | q ] \,.\\
   \label{eq:contraction_spinhel}
   P_{\mu_i} j^-(p,\mu_1,\dots,\mu_{2N-1},q) = \langle p |\ \mu_1\ \dots\ \mu_{i-1}\ P\ \mu_{i+1}\ \dots\ \mu_{2N-1}\ | q ] \,.
 \end{gather}
 Lightlike momenta $p$ can be decomposed into spinor products:
 \begin{equation}
   \label{eq:p_decomp}
   \slashed{p} = |p\rangle [p| + |p] \langle p |\,.
 \end{equation}
 Taking into account helicity conservation this gives the following relations:
 \begingroup
 \addtolength{\jot}{1em}
 \begin{align}
   \label{eq:p_in_current}
   \langle p |\ \mu_1\ \dots\ \mu_i\ P\ \mu_{i+1}\ \dots\ \mu_{2N-1}\ | q ] ={}&
   \begin{cases}
     \langle p |\ \mu_1\ \dots\ \mu_i\ |P]\ \langle P|\ \mu_{i+1}\ \dots\ \mu_{2N-1}\ | q ]& i \text{ even}\\
     \langle p |\ \mu_1\ \dots\ \mu_i\ |P\rangle\ [ P|\ \mu_{i+1}\ \dots\ \mu_{2N-1}\ | q ]& i \text{ odd}
   \end{cases}\,,\\
   \label{eq:p_in_angle}
   \langle p |\ \mu_1\ \dots\ \mu_i\ P\ \mu_{i+1}\ \dots\ \mu_{2N}\ | q \rangle ={}&
   \begin{cases}
    \langle p |\ \mu_1\ \dots\ \mu_i\ |P]\ \langle P| \mu_{i+1}\ \dots\ \mu_{2N}\ | q \rangle & i \text{ even}\\
    \langle p |\ \mu_1\ \dots\ \mu_i\ |P\rangle\ \big(\langle P| \mu_{i+1}\ \dots\ \mu_{2N}\ | q ]\big)^* & i \text{ odd}
   \end{cases}\,,\\
   \label{eq:p_in_square}
   [ p |\ \mu_1\ \dots\ \mu_i\ P\ \mu_{i+1}\ \dots\ \mu_{2N}\ | q ] ={}&
   \begin{cases}
    \big(\langle p |\ \mu_1\ \dots\ \mu_i\ |P]\big)^* \ [ P| \mu_{i+1}\ \dots\ \mu_{2N}\ | q ] & i \text{ even}\\
    [ p |\ \mu_1\ \dots\ \mu_i\ |P]\ \langle P| \mu_{i+1}\ \dots\ \mu_{2N}\ | q ] & i \text{ odd}
  \end{cases}\,.
 \end{align}
 \endgroup
 For contractions of vector currents we can use the Fierz identity
 \begin{equation}
   \label{eq:Fierz}
   \langle p|\ \mu\ |q]\ \langle k|\ \mu\ |l] = 2 \spa p.k \spb l.q\,.
 \end{equation}
 The scalar angle and square products are given by
 \begin{align}
   \label{eq:angle_product}
   \spa p.q ={}& {\big(u^-(p)\big)}^\dagger u^+(q) =
   \sqrt{p^-q^+}\hat{p}_{i,\perp} - \sqrt{p^+q^-}\hat{q}_{j,\perp} = - \spa q.p\,,\\
   \label{eq:square_product}
   \spb p.q ={}& {\big(u^+(p)\big)}^\dagger u^-(q) = -\spa p.q ^* = - \spb q.p\,.
 \end{align}
 
 \subsubsection{Contraction algorithm}
 \label{sec:contr_calc_algo}
 
 The contractions are now calculated as follows:
 \begin{enumerate}
 \item Use equations \eqref{eq:jplus_gen}, \eqref{eq:current_spinhel} to write all currents in a canonical form.
 \item Assume that all momenta are lightlike and use the relations
 \eqref{eq:p_in_current}, \eqref{eq:p_in_angle}, \eqref{eq:p_in_square}
 to split up currents that are contracted with momenta.
 \item Apply the Fierz transformation~\eqref{eq:Fierz} to eliminate
 contractions between vector currents.
 \item Write the arguments of the antisymmetric angle and scalar products in canonical order, see equations~\eqref{eq:angle_product} ,\eqref{eq:square_product}.
 \end{enumerate}
+The corresponding \lstinline!ContractCurrents! procedure is implemented in
+\texttt{include/helspin.fm}.
 
 
 \section{The fixed-order generator}
 \label{sec:HEJFOG}
 
 Even at leading order, standard fixed-order generators can only generate
 events with a limited number of final-state particles within reasonable
 CPU time. The purpose of the fixed-order generator is to supplement this
 with high-multiplicity input events according to the first two lines of
 eq.~\eqref{eq:resumdijetFKLmatched2} with the \HEJ approximation
 $\mathcal{M}_\text{LO, \HEJ}^{f_1f_2\to f_1g\cdots gf_2}$ instead of the
 full fixed-order matrix element $\mathcal{M}_\text{LO}^{f_1f_2\to
 f_1g\cdots gf_2}$. Its usage is described in the user
 documentation \url{https://hej.web.cern.ch/HEJ/doc/current/user/HEJFOG.html}.
 
 \subsection{File structure}
 \label{sec:HEJFOG_structure}
 
 The code for the fixed-order generator is in the \texttt{FixedOrderGen}
 directory, which contains the following:
 \begin{description}
 \item[include:] Contains the C++ header files.
 \item[src:] Contains the C++ source files.
 \item[t:] Contains the source code for the automated tests.
 \item[CMakeLists.txt:] Configuration file for the \cmake build system.
 \item[configFO.yml:] Sample configuration file for the fixed-order generator.
 \end{description}
 The code is generally in the \lstinline!HEJFOG! namespace. Functions and
 classes \lstinline!MyClass! are usually declared in
 \texttt{include/MyClass.hh} and implemented in \texttt{src/MyClass.cc}.
 
 \subsection{Program flow}
 \label{sec:prog_flow}
 
 A single run of the fixed-order generator consists of three or four
 stages.
 
 First, we perform initialisation similar to \HEJ 2, see
 section~\ref{sec:init}. Since there is a lot of overlap we frequently
 reuse classes and functions from \HEJ 2, i.e. from the
 \lstinline!HEJ! namespace. The code for parsing the configuration file
 is in \texttt{include/config.hh} and implemented in
 \texttt{src/config.cc}.
 
 If partial unweighting is requested in the user settings \url{https://hej.web.cern.ch/HEJ/doc/current/user/HEJFOG.html#settings},
 the initialisation is followed by a calibration phase. We use a
 \lstinline!EventGenerator! to produce a number of trial
 events. We use these to calibrate the \lstinline!Unweighter! in
 its constructor and produce a first batch of partially unweighted
 events. This also allows us to estimate our unweighting efficiency.
 
 In the next step, we continue to generate events and potentially
 unweight them. Once the user-defined target number of events is reached,
 we adjust their weights according to the number of required trials. As
 in \HEJ 2 (see section~\ref{sec:processing}), we pass the final
 events to a number of \lstinline!HEJ::Analysis! objects and a
 \lstinline!HEJ::CombinedEventWriter!.
 
 \subsection{Event generation}
 \label{sec:evgen}
 
 Event generation is performed by the
 \lstinline!EventGenerator::gen_event! member function. We begin by generating a
 \lstinline!PhaseSpacePoint!. This is not to be confused with
 the resummation phase space points represented by
 \lstinline!HEJ::PhaseSpacePoint!! After jet clustering, we compute the
 leading-order matrix element (see section~\ref{sec:ME}) and pdf factors.
 
 The phase space point generation is performed in the
 \lstinline!PhaseSpacePoint! constructor. We first construct the
 user-defined number of $n_p$ partons (by default gluons) in
 \lstinline!PhaseSpacePoint::gen_LO_partons!. We use flat sampling in
 rapidity and azimuthal angle. For the scalar transverse momenta, we
 distinguish between two cases. By default, they are generated based on a
 random variable $x_{p_\perp}$ according to
 \begin{equation}
   \label{eq:pt_sampling}
 p_\perp = p_{\perp,\text{min}} +
 \begin{cases}
    p_{\perp,\text{par}}
    \tan\left(
    x_{p_\perp}
    \arctan\left(
      \frac{p_{\perp,\text{max}} - p_{\perp,\text{min}}}{p_{\perp,\text{par}}}
      \right)
  \right)
 & y < y_\text{cut}
   \\
  - \tilde{p}_{\perp,\text{par}}\log\left(1 - x_{p_\perp}\left[1 -
      \exp\left(\frac{p_{\perp,\text{min}} -
          p_{\perp,\text{max}}}{\tilde{p}_{\perp,\text{par}}}\right)\right]\right)
 & y \geq y_\text{cut}
 \end{cases}\,,
 \end{equation}
 where $p_{\perp,\text{min}}$ is the minimum jet transverse momentum,
 $p_{\perp,\text{max}}$ is the maximum transverse parton momentum,
 tentatively set to the beam energy, and $y_\text{cut}$, $p_{\perp,\text{par}}$
 and $\tilde{p}_{\perp,\text{par}}$ are generation parameters set to
 heuristically determined values of
 \begin{align}
   y_\text{cut}&=3,\\
   p_{\perp,\text{par}}&=p_{\perp,\min}+\frac{n_p}{5}, \\
   \tilde{p}_{\perp,\text{par}}&=\frac{p_{\perp,\text{par}}}{1 +
     5(y-y_\text{cut})}.
 \end{align}
 The problem with this generation is that the transverse momenta peak at
 the minimum transverse momentum required for fixed-order jets. However,
 if we use the generated events as input for \HEJ resummation, events
 with such soft transverse momenta hardly contribute, see
 section~\ref{sec:ptj_res}. To generate efficient input for resummation,
 there is the user option \texttt{peak pt}, which specifies the
 dominant transverse momentum for resummation jets. If this option is
 set, most jets will be generated as above, but with
 $p_{\perp,\text{min}}$ set to the peak transverse momentum $p_{\perp,
 \text{peak}}$. In addition, there is a small chance of around $2\%$ to
 generate softer jets. The heuristic ansatz for the transverse momentum
 distribution in the ``soft'' region is
 \begin{equation}
   \label{FO_pt_soft}
   \frac{\partial \sigma}{\partial p_\perp} \propto e^{n_p\frac{p_\perp- p_{\perp,
 \text{peak}}}{\bar{p}_\perp}}\,,
 \end{equation}
 where $n_p$ is the number of partons and $\bar{p}_\perp \approx
 4\,$GeV. To achieve this distribution, we use
 \begin{equation}
   \label{eq:FO_pt_soft_sampling}
   p_\perp = p_{\perp, \text{peak}} + \bar{p}_\perp \frac{\log x_{p_\perp}}{n_p}
 \end{equation}
 and discard the phase space point if the parton is too soft, i.e. below the threshold for
 fixed-order jets.
 
 After ensuring that all partons form separate jets, we generate any
 potential colourless emissions. We then determine the incoming momenta
 and flavours in \lstinline!PhaseSpacePoint::reconstruct_incoming! and
 adjust the outgoing flavours to ensure an FKL configuration. Finally, we
 may reassign outgoing flavours to generate suppressed (for example
 unordered) configurations.
 
 \input{currents}
-\input{tensor}
 
 \appendix
 \section{Continuous Integration}
 \label{sec:CI}
 
 Whenever you are implementing something new or fixed a bug, please also add a
 test for the new behaviour to \texttt{t/CMakeLists.txt} via
 \lstinline!add_test!. These test can be triggered by running
 \lstinline!make test! or \lstinline!ctest! after compiling. A typical test
 should be at most a few seconds, so it can be potentially run on each commit
 change by each developer. If you require a longer, more careful test, preferably
 on top of a small one, surround it with
 \begin{lstlisting}[caption={}]
 if(${TEST_ALL})
   add_test(
     NAME t_feature
     COMMAND really_long_test
     )
 endif()
 \end{lstlisting}
 Afterwards you can execute the longer tests with\footnote{No recompiling is
 needed, as long as only the \lstinline!add_test! command is guarded, not the
 compiling commands itself.}
 \begin{lstlisting}[language=sh,caption={}]
   cmake base/directory -DTEST_ALL=TRUE
   make test
 \end{lstlisting}
 
 On top of that you should add
 \href{https://en.cppreference.com/w/cpp/error/assert}{\lstinline!assert!s} in
 the code itself. They are only executed when compiled with
 \lstinline!CMAKE_BUILD_TYPE=Debug!, without slowing down release code. So you
 can use them everywhere to test \textit{expected} or \textit{assumed} behaviour,
 e.g. requiring a Higgs boson or relying on rapidity ordering.
 
 GitLab provides ways to directly test code via \textit{Continuous integrations}.
 The CI is controlled by \texttt{.gitlab-ci.yml}. For all options for the YAML
 file see \href{https://docs.gitlab.com/ee/ci/yaml/}{docs.gitlab.com/ee/ci/yaml/}.https://gitlab.dur.scotgrid.ac.uk/hej/docold/tree/master/Theses
 GitLab also provides a small tool to check that YAML syntax is correct under
 \lstinline!CI/CD > Pipelines > CI Lint! or
 \href{https://gitlab.dur.scotgrid.ac.uk/hej/HEJ/-/ci/lint}{gitlab.dur.scotgrid.ac.uk/hej/HEJ/-/ci/lint}.
 
 Currently the CI is configured to trigger a \textit{Pipeline} on each
 \lstinline!git push!. The corresponding \textit{GitLab runners} are configured
 under \lstinline!CI/CD Settings>Runners! in the GitLab UI. All runners use a
 \href{https://www.docker.com/}{docker} image as virtual environments\footnote{To
 use only Docker runners set the \lstinline!docker! tag in
 \texttt{.gitlab-ci.yml}.}. The specific docker images maintained separately. If
 you add a new dependencies, please also provide a docker image for the CI. The
 goal to be able to test \HEJ with all possible configurations.
 
 Each pipeline contains multiple stages (see \lstinline!stages! in
 \texttt{.gitlab-ci.yml}) which are executed in order from top to bottom.
 Additionally each stage contains multiple jobs. For example the stage
 \lstinline!build! contains the jobs \lstinline!build:basic!,
 \lstinline!build:qcdloop!, \lstinline!build:rivet!, etc., which compile \HEJ for
 different environments and dependencies, by using different Docker images. Jobs
 starting with an dot are ignored by the Runner, e.g. \lstinline!.HEJ_build! is
 only used as a template, but never executed directly. Only after all jobs of the
 previous stage was executed without any error the next stage will start.
 
 To pass information between multiple stages we use \lstinline!artifacts!. The
 runner will automatically load all artifacts form all \lstinline!dependencies!
 for each job\footnote{If no dependencies are defined \textit{all} artifacts from
 all previous jobs are downloaded. Thus please specify an empty dependence if you
 do not want to load any artifacts.}. For example the compiled \HEJ code from
 \lstinline!build:basic! gets loaded in \lstinline!test:basic! and
 \lstinline!FOG:build:basic!, without recompiling \HEJ again. Additionally
 artifacts can be downloaded from the GitLab web page, which could be handy for
 debugging.
 
 We also trigger some jobs \lstinline!only! on specific events. For example we
 only push the code to
 \href{https://phab.hepforge.org/source/hej/repository/v2.0/}{HepForge} on
 release branches (e.g. v2.0). Also we only execute the \textit{long} tests for
 merge requests, on pushes for any release or the \lstinline!master! branch, or
 when triggered manually from the GitLab web page.
 
 The actual commands are given in the \lstinline!before_script!,
 \lstinline!script! and \lstinline!after_script!
 \footnote{\lstinline!after_script! is always executed} sections, and are
 standard Linux shell commands (dependent on the docker image). Any failed
 command, i.e. returning not zero, stops the job and making the pipeline fail
 entirely. Most tests are just running \lstinline!make test! or are based on it.
 Thus, to emphasise it again, write tests for your code in \lstinline!cmake!. The
 CI is only intended to make automated testing in different environments easier.
 
 \section{Monte Carlo uncertainty}
 \label{sec:MC_err}
 
 Since \HEJ is reweighting each Fixed Order point with multiple resummation
 events, the Monte Carlo uncertainty of \HEJ is a little bit more complicated
 then usual. We start by defining the \HEJ cross section after $N$ FO points
 \begin{align}
 \sigma_N:=\sum_{i}^N x_i \sum_{j}^{M_i} y_{i,j}=:\sum_i^N\sum_j^{M_i} w_{i,j},
 \end{align}
 where $x_i$ are the FO weights\footnote{In this definition $x_i$ can be zero,
 see the discussion in the next section.}, $y_{i,j}$ are the reweighting weights
 , and $M_i$ the number of resummation points. We can set $M=M_i \forall i$ by
 potentially adding some points with $y_{i,j}=0$, i.e. $M$ correspond to the
 \lstinline!trials! in \lstinline!EventReweighter!.  $w_{i,j}$ are the weights as
 written out by \HEJ. The expectation value of $\sigma$ is then
 \begin{align}
 \ev{\sigma_N}= \sum_i \ev{x_i}\sum_j\ev{y_{i,j}}=M \mu_x\sum_i\mu_{y_i},\label{eq:true_sigma}
 \end{align}
 with $\mu_{x/y}$ being the (true) mean value of $x$ or $y$, i.e.
 \begin{align}
 \mu_{x}:=\ev{\bar{x}}=\ev{\frac{\sum_i x_i}{N}}=\ev{x}.
 \end{align}
 
 The true underlying standard derivation on $\sigma_N$, assuming $\delta_{x}$
 and $\delta_{y_i}$ are the standard derivations of $x$ and $y_i$ is
 \begin{align}
 \delta_{\sigma_N}^2&=M^2 \delta_{x}^2 \sum_i \mu_{y_i}^2
                   +M \mu_x^2 \sum_i \delta_{y_i}^2. \label{eq:true_err}
 \end{align}
 Notice that each point $i$ can have an different expectation for $y_i$.
 Since we do not know the true distribution of $x$ and $y$ we need to estimate
 it. We use the standard derivation
 \begin{align}
 \tilde{\delta}_{x_i}^2&:=\left(x_i-\bar x\right)^2
               =\left(\frac{N-1}{N} x_i - \frac{\sum_{j\neq i} x_j}{N}\right)^2
               \label{eq:err_x}\\
 \tilde{\delta}_{y_{i,j}}^2&:=\left(y_{i,j}-\bar y_i\right)^2 \label{eq:err_y},
 \end{align}
 and the mean values $\bar x$ and $\bar y$, to get an estimator for
 $\delta_{\sigma_N}$
 \begin{align}
 \tilde\delta_{\sigma_N}^2&=M^2 \sum_i \tilde\delta_{x_i}^2 \bar{y_i}^2
                   +\sum_{i,j} x_i^2\tilde\delta_{y_{i,j}}^2. \label{eq:esti_err}
 \end{align}
 
 Trough error propagation we can connect the estimated uncertainties back to the
 fundamental ones
 \begin{align}
 \delta_{\tilde{\delta}_{x_i}}^2=\frac{N-1}{N} \delta_x^2.
 \end{align}
 Together with $\delta_x^2=\ev{x^2}-\ev{x}^2$ and $\ev{\tilde\delta}=0$ this
 leads to
 \begin{align}
 \ev{\tilde{\delta}_{x_i}^2 \bar y_i^2}&=\ev{\tilde{\delta}_{x_i} \bar y_i}^2
                                 +\delta_{\tilde{\delta}_{x_i}}^2 \mu_{y_i}^2
                                 +\delta_{y_i}^2 \mu_{\tilde\delta}^2 \\
                                   &=\frac{N-1}{N} \delta_x^2\mu_{y_i}^2,
 \end{align}
 and a similar results for $y$. Therefore
 \begin{align}
 \ev{\delta_{\sigma_N}}=\frac{N-1}{N} M^2 \delta_{x}^2 \sum_i \mu_{y_i}^2
                   +\frac{M-1}{M} M \mu_x^2 \sum_i \delta_{y_i}^2,
 \end{align}
 where we can compensate for the additional factors compared to~\eqref{eq:true_err}, by replacing
 \begin{align}
 \tilde\delta_x&\to\frac{N}{N-1}\tilde\delta_x \label{eq:xcom_bias}\\
 \tilde\delta_{y_i}&\to\frac{M}{M-1}\tilde\delta_{y_i}. \label{eq:ycom_bias}
 \end{align}
 Thus~\eqref{eq:esti_err} is an unbiased estimator of $\delta_{\sigma_N}$.
 
 \subsection{Number of events vs. number of trials}
 
 Even though the above calculation is completely valid, it is unpractical. Both
 $x_i$ and $y_{ij}$ could be zero, but zero weight events are typically not
 written out. In that sense $N$ and $M$ are the \textit{number of trials} it took
 to generate $N'$ and $M'$ (non-zero) events. We can not naively replace all $N$
 and $M$ with $N'$ and $M'$ in the above equations, since this would also change
 the definition of the average $\bar x$ and $\bar y$.
 
 For illustration let us consider unweighted events, with all weights equal to
 $x'$, without changing the cross section $\sum_i^N x_i=\sum_i^{N'} x'_i=N' x'$.
 Then the average trial weight is unequal to the average event weight
 \begin{align}
 \bar x = \frac{\sum_i^{N} x_i}{N} = \frac{\sum_i^{N'} x'}{N}=x'\frac{N'}{N}
   \neq x'=\frac{\sum_i^{N'} x'}{N'}.
 \end{align}
 $N=N'$ would correspond to an $100\%$ efficient unweighting, i.e. a perfect
 sampling, where we know the analytical results. In particular using $N'$ instead
 of $N$ in the standard derivation gives
 \begin{align}
 \sum_i \left(x_i-\frac{\sum_i^{N} x_i}{N'}\right)^2=\sum_i \left(x'-x' \frac{\sum_i^{N'}}{N'}\right)^2=0,
 \end{align}
 which is obviously not true in general for $\tilde\delta^2_x$.
 
 Hence we would have to use the number of trials $N$ everywhere. This would
 require an additional parameter to be passed with each events, which is not
 always available in practice\footnote{ \texttt{Sherpa} gives the number of
 trials, as an \lstinline!attribute::trials! of \lstinline!HEPEUP! in the
 \texttt{LHE} file, or similarly as a data member in the HDF5 format
 \cite{Hoeche:2019rti}. The \texttt{LHE} standard itself provides the
 variable \lstinline!ntries! per event (see
 \href{https://phystev.cnrs.fr/wiki/2017:groups:tools:lhe}{this proposal}),
 though I have not seen this used anywhere.}. Instead we use
 \begin{align}
 \tilde\delta_{x}'^2:=\sum_i^{N} x_i^2\geq\tilde\delta_x^2, \label{eq:err_prac}
 \end{align}
 where the bias of $\delta_x'^2$ vanishes for large $N$. Thus we can use the sum
 of weight squares~\eqref{eq:err_prac} instead of~\eqref{eq:err_x}
 and~\eqref{eq:err_y}, without worrying about the difference between trials and
 generated events. The total error~\eqref{eq:esti_err} becomes
 \begin{align}
 \tilde\delta_{\sigma_N}^2=\sum_i \left(\sum_j w_{i,j}\right)^2+\sum_{i,j} \left(w_{i,j}\right)^2,
 \end{align}
 which (conveniently) only dependent on the \HEJ weights $w_{i,j}$.
 
 \section{Explicit formulas for vector currents}
 \label{sec:j_vec}
 
 
 Using eqs.~\eqref{eq:u_plus}\eqref{eq:u_minus}\eqref{eq:jminus_gen}\eqref{eq:Pauli_matrices}\eqref{eq:jplus_gen}, the vector currents read
 \begin{align}
   \label{eq:j-_explicit}
   j^-_\mu(p, q) ={}&
      \begin{pmatrix}
        \sqrt{p^+\,q^+} + \sqrt{p^-\,q^-} \hat{p}_{\perp} \hat{q}_{\perp}^*\\
        \sqrt{p^-\,q^+}\, \hat{p}_{\perp} + \sqrt{p^+\,q^-}\,\hat{q}_{\perp}^*\\
        -i \sqrt{p^-\,q^+}\, \hat{p}_{\perp} + i \sqrt{p^+\,q^-}\, \hat{q}_{\perp}^*\\
        \sqrt{p^+\,q^+} - \sqrt{p^-\,q^-}\, \hat{p}_{\perp}\, \hat{q}_{\perp}^*
      \end{pmatrix}\,,\\
   j^+_\mu(p, q) ={}&\big(j^-_\mu(p, q)\big)^*\,,\\
   j^\pm_\mu(q, p) ={}&\big(j^\pm_\mu(p, q)\big)^*\,.
 \end{align}
 If $q= p_{\text{in}}$ is the momentum of an incoming parton, we have
 $\hat{p}_{\text{in} \perp} = -1$ and either $p_{\text{in}}^+ = 0$ or
 $p_{\text{in}}^- = 0$. The current simplifies further:\todo{Helicities flipped w.r.t code}
 \begin{align}
   \label{eq:j_explicit}
   j^-_\mu(p_{\text{out}}, p_{\text{in}}) ={}&
     \begin{pmatrix}
       \sqrt{p_{\text{in}}^+\,p_{\text{out}}^+}\\
       \sqrt{p_{\text{in}}^+\,p_{\text{out}}^-} \ \hat{p}_{\text{out}\,\perp}\\
       -i\,j^-_1\\
       j^-_0
     \end{pmatrix}
 & p_{\text{in}\,z} > 0\,,\\
   j^-_\mu(p_{\text{out}}, p_{\text{in}}) ={}&
     \begin{pmatrix}
       -\sqrt{p_{\text{in}}^-\,p_{\text{out}}^{-\phantom{+}}} \ \hat{p}_{\text{out}\,\perp}\\
       - \sqrt{p_{\text{in}}^-\,p_{\text{out}}^+}\\
       i\,j^-_1\\
       -j^-_0
     \end{pmatrix} & p_{\text{in}\,z} < 0\,.
 \end{align}
 We also employ the usual short-hand notation
 For the gluon polarisation vectors with gluon momentum $p_g$ and auxiliary
 reference vector $p_r$ we use
 \begin{equation}
   \label{eq:pol_vector}
   \epsilon_\mu^+(p_g, p_r) = \frac{j_\mu^+(p_r, p_g)}{\sqrt{2}\spb g.r}\,,\qquad\epsilon_\mu^-(p_g, p_r) = \frac{j_\mu^-(p_r, p_g)}{\sqrt{2}\spa g.r}\,.
 \end{equation}
 
 
 \bibliographystyle{JHEP}
 \bibliography{biblio}
 
 \end{document}
diff --git a/doc/developer_manual/tensor.tex b/doc/developer_manual/tensor.tex
deleted file mode 100644
index 31404d3..0000000
--- a/doc/developer_manual/tensor.tex
+++ /dev/null
@@ -1,98 +0,0 @@
-\section{Tensor Class}
-\label{sec:tensor}
-In the following section we detail the implementation and user
-interface to the tensor class used in the W+Jet subleading
-currents. The aim is to convert all currents to this class.
-
-\subsection{Template Class}
-\label{sec:template}
-The Tensor class is a template class. This means that it is declared as
-\lstinline!Tensor <N>!, with $N$, the rank of the tensor, specified when called.
-This means that this class breaks the mould and to keep the header file clean,
-an extra header, \texttt{include/HEJ/detail/Tensor\_impl.hh}, has been created
-which includes the implementation necessary for a template class.
-
-This grants us extra flexibility and ensures we do not have to create
-(and then maintain) multiple functions for different rank tensors,
-except in a few distinct cases.
-
-\subsection{Creating a Tensor}
-\label{sec:createTensor}
-The tensor class has several constructors and there are a few options
-open to prospective users. Firstly, if one has an old HEJ \lstinline!CCurrent! they
-simply wish to re-represent as a \lstinline!Tensor<1>!, it is
-possible to simply call \lstinline!Construct1Tensor(CCurrent j)!. One
-can also construct a Tensor class object with the use of a
-\lstinline!CLHEP::HepLorentzVector! with a similar
-syntax.
-
-One can also create a Tensor from scratch. One can calculate a
-standard \HEJ extremal quark current, $\langle p_1 | \mu | p_a \rangle$,
-where $p_a$ is the incoming momenta and $p_1$ the outgoing by the
-command: \lstinline!TCurrent(p1, h1, pa, ha)!, where \lstinline!h1! and \lstinline!ha! are the
-helicities of the particles \lstinline!p1! and \lstinline!pa!
-respectively. The order in which these particles must be supplied to
-this function is opposite to fermion flow (as with Feynman rules). So,
-if you wish to calculate the same current but for an anti-quark you simply
-need to swap the order of arguments.
-
-The light-cone momenta of those supplied in the arguments is checked
-to ensure that the correct incoming/outgoing labels are used for the
-particles, so there is no need for one to worry over the use of
-\lstinline!joi()! vs \lstinline!jio()! as in previous implementations of
-currents in \HEJ.
-
-Where this class sets itself apart from previous implementations comes
-from it being a template class, as explained in section~\ref{sec:template}.
-We can deal with much higher rank tensors with
-relative ease. One can construct $\langle p_1 | \mu \nu \rho| p_a \rangle$
-with no more effort than before, simply calling
-\lstinline!T3Current(p1, h1, pa, ha)!. A similar result can be
-obtained with \lstinline!T5Current(p1, h1, pa, ha)!, which gives the
-rank 5 version of this current.
-
-\subsection{Using the Tensor Class}
-\label{sec:UseTensor}
-There are several pre-existing Tensor class objects within HEJ, for
-example, \lstinline!eps(HLV k, HLV ref, bool pol)! will give you the
-polarisation tensor for a gluon with momenta \lstinline!k!, reference
-momentum \lstinline!ref! and polarisation \lstinline!pol!. One can
-contract Tensors together with the use of the member function
-Contract. The simplest \HEJ current can be calculated with the
-following snippet:
-
-\begin{lstlisting}[language=c++,caption={}]
-  Tensor<1> j1a = TCurrent(p1,h1,pa,ha);
-  Tensor<1> j4b = TCurrent(p4,h4,pb,hb);
-  COM 2jFKL = j1a.contract(j4b,1).at(0);
-\end{lstlisting}
-
-Where we have used \lstinline!Tensor::contract(Tensor<1> T, int i)!, which
-contracts Tensor \lstinline!T! with index \lstinline!i! of the Tensor this
-member function was called with. Notice that the contracted indices begin at
-1\todo{why? I would prefer starting at 0 - MH}. Also, that to access the value
-after contraction to a \lstinline!Tensor<0>! one has to use
-\lstinline!Tensor::at(i)!.
-
-One can contract a tensor of rank N with any tensor of rank
-1. Contractions of tensors of higher ranks have not been implemented
-at this stage.
-
-Notice this gives a lot of flexibility to calculate some very complex currents
-and easily contract them with others in the \HEJ formalism. For example, one can
-compute a Central-q$\bar{\text{q}}$ matrix element with a W boson emission from
-the Central-q$\bar{\text{q}}$ vertex with a single object that can then be
-contracted with external currents with ease.
-
-The Tensor class was implemented during the development of the W+Jets subleading
-processes (due to a need to easily construct more complex currents). This means
-that currently it is only used in the W+Jets subleading processes. It was also
-used to reimplement the pure jet central q$\bar{\text{q}}$ vertex, so once the
-central q$\bar{\text{q}}$ subleading process is implemented in the pure jets
-case fully, it will also be used there. The aim is for all processes to be moved
-over to using this Tensor class.
-
-%%% Local Variables:
-%%% mode: latex
-%%% TeX-master: "developer_manual"
-%%% End:
diff --git a/include/HEJ/CrossSectionAccumulator.hh b/include/HEJ/CrossSectionAccumulator.hh
index ab31431..c6b2fb0 100644
--- a/include/HEJ/CrossSectionAccumulator.hh
+++ b/include/HEJ/CrossSectionAccumulator.hh
@@ -1,97 +1,99 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <iterator>
 #include <map>
 #include <ostream>
 #include <vector>
 
 #include "HEJ/event_types.hh"
 
 namespace HEJ {
   class Event;
 
+  //! Collection of Cross Section with its uncertainty
   template<typename T>
   struct XSWithError {
-    T value = T{};
-    T error = T{};
+    T value = T{}; //!< Cross Section
+    T error = T{}; //!< Error
   };
 
   /**
    * @brief Sum of Cross Section for different subproccess
    */
   class CrossSectionAccumulator {
   public:
     //! Fill with single event
     //! @note for multiple resummation events use fill_correlated() instead
     void fill(Event const & ev);
     //! Fill by weight and type
     //! @note for multiple resummation events use fill_correlated() instead
     void fill(double weight, event_type::EventType type);
     //! Fill by weight, error and type
     //! @note The error will be _added_ to the current error
     void fill(double weight, double delta_error, event_type::EventType type);
 
     /**
      * @brief   Fill with multiple correlated weights
      * @details This should be used to fill multiple reweighted events,
      *          coming from the same fixed order point.
      *          Total error for \f$N\f$ fixed order points each giving \f$M_i\f$
      *          resummation events is:
      *          \f[
      *          \delta^2=\sum_i \left(\sum_j w_{i,j}\right)^2
      *                   +\sum_{i,j} \left(w_{i,j}\right)^2,
      *          \f]
      * @note    This is equivalent to fill() for only one reweighted event
      *          coming from each fixed order point (\f$M_i=1\f$)
      */
     void fill_correlated(std::vector<Event> const & evts);
     //! iterator implementation of fill_correlated()
     template<class ConstIt>
     void fill_correlated(ConstIt begin, ConstIt end);
     //! explicit version of fill_correlated() by giving sum(wt) and sum(wt^2)
     void fill_correlated(double sum, double sum2, event_type::EventType type);
 
     //! begin of Cross Section and error for subprocesses
     auto begin() const {
       return std::begin(xs_);
     }
     //! end of Cross Section and error for subprocesses
     auto end() const {
       return std::end(xs_);
     }
     //! total Cross Section and error
     XSWithError<double> const & total() const {
       return total_;
     }
 
   private:
     std::map<HEJ::event_type::EventType, XSWithError<double>> xs_;
     XSWithError<double> total_;
   };
 
+  //! Print CrossSectionAccumulator to stream
   std::ostream& operator<<(std::ostream& os, const CrossSectionAccumulator& xs);
 
 // ------------ Implementation ------------
 
   template<class ConstIt>
   void CrossSectionAccumulator::fill_correlated(ConstIt begin, ConstIt end){
     if(std::distance(begin, end) < 2){ // only one event
       fill(*begin);
       return;
     }
     double sum = 0.;
     double sum2 = 0.;
     const auto type = begin->type();
     for(; begin != end; ++begin){
       double const wt = begin->central().weight;
       sum += wt;
       sum2 += wt*wt;
     }
     fill_correlated(sum, sum2, type);
   }
 } // namespace HEJ
diff --git a/include/HEJ/EWConstants.hh b/include/HEJ/EWConstants.hh
index e0ec7b0..709bac5 100644
--- a/include/HEJ/EWConstants.hh
+++ b/include/HEJ/EWConstants.hh
@@ -1,90 +1,90 @@
 /** \file
  *  \brief Defines the electro weak parameters
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <cmath>
 
 #include "HEJ/exceptions.hh"
 #include "HEJ/PDG_codes.hh"
 
 namespace HEJ {
   //! collection of basic particle properties
   struct ParticleProperties {
-    double mass;
-    double width;
+    double mass;  //!< Mass
+    double width; //!< Decay width
   };
 
   //! Collection of electro-weak constants
   class EWConstants {
   public:
     EWConstants() = default;
     //! initialise by Vacuum expectation value & boson properties
     EWConstants(
       double vev, //!< vacuum expectation value
       ParticleProperties const & Wprop, //!< W boson mass & width
       ParticleProperties const & Zprop, //!< Z boson mass & width
       ParticleProperties const & Hprop  //!< Higgs boson mass & width
     ): set{true}, vev_{vev}, Wprop_{Wprop}, Zprop_{Zprop}, Hprop_{Hprop} {}
     //! set constants by Vacuum expectation value & boson properties
     void set_vevWZH(
       double vev, //!< vacuum expectation value
       ParticleProperties const & Wprop, //!< W boson mass & width
       ParticleProperties const & Zprop, //!< Z boson mass & width
       ParticleProperties const & Hprop  //!< Higgs boson mass & width
     ){
       set = true; vev_= vev; Wprop_= Wprop; Zprop_= Zprop; Hprop_= Hprop;
     }
     //! vacuum expectation value
     double vev()     const {check_set(); return vev_;}
     //! Properties of the W boson
     ParticleProperties const & Wprop() const {check_set(); return Wprop_;}
     //! Properties of the Z boson
     ParticleProperties const & Zprop() const {check_set(); return Zprop_;}
     //! Properties of the Higgs boson
     ParticleProperties const & Hprop() const {check_set(); return Hprop_;}
     //! access Properties by boson id
     ParticleProperties const & prop(ParticleID const id) const {
       using namespace pid;
       switch(id){
       case Wp:
       case Wm:
         return Wprop();
       case Z:
         return Zprop();
       case h:
         return Hprop();
       default:
         throw std::invalid_argument("No properties available for particle "+name(id));
       }
     }
     //! cosine of Weinberg angle
     double cos_tw()   const {return Wprop().mass/Zprop().mass;}
     //! cosine square of Weinberg angle
     double cos2_tw()  const {return cos_tw()*cos_tw();}
     //! sinus Weinberg angle
     double sin_tw()   const {return sqrt(sin2_tw());}
     //! sinus square of Weinberg angle
     double sin2_tw()  const {return 1. - cos2_tw();}
     //! elector magnetic coupling
     double alpha_em() const {return e2()/4./M_PI;}
     //! weak coupling
     double alpha_w()  const {return gw2()/2.;}
 
   private:
     double gw2() const {return 4*Wprop().mass/vev()*Wprop().mass/vev();}
     double e2()  const {return gw2()*sin2_tw();}
     void check_set() const {
       if(!set) throw std::invalid_argument("EW constants not specified");
     }
     bool set{false};
     double vev_;
     ParticleProperties Wprop_;
     ParticleProperties Zprop_;
     ParticleProperties Hprop_;
   };
 } // namespace HEJ
diff --git a/include/HEJ/EmptyAnalysis.hh b/include/HEJ/EmptyAnalysis.hh
index 082c216..cbecbdf 100644
--- a/include/HEJ/EmptyAnalysis.hh
+++ b/include/HEJ/EmptyAnalysis.hh
@@ -1,48 +1,49 @@
 /** \file
  *  \brief Declaration of the trivial (empty) analysis
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 
 #include "HEJ/Analysis.hh"
 
 //! YAML Namespace
 namespace YAML {
   class Node;
 }
 
 namespace HEJ {
   /** An analysis that does nothing
    *
    *  This analysis is used by default if no user analysis is specified.
    *  The member functions don't do anything and events passed to the
    *  analysis are simply ignored.
    */
   struct EmptyAnalysis: Analysis{
+    //! Create EmptyAnalysis
     static std::unique_ptr<Analysis> create(
         YAML::Node const & parameters, LHEF::HEPRUP const &);
 
     //! Fill event into analysis (e.g. to histograms)
     /**
      *  This function does nothing
      */
     virtual void fill(Event const &, Event const &) override;
     //! Whether a resummation event passes all cuts
     /**
      *  There are no cuts, so all events pass
      */
     virtual bool pass_cuts(Event const &, Event const &) override;
     //! Finalise analysis
     /**
      * This function does nothing
      */
     virtual void finalise() override;
 
     virtual ~EmptyAnalysis() override = default;
   };
 } // namespace HEJ
diff --git a/include/HEJ/Event.hh b/include/HEJ/Event.hh
index c752f34..7d828d8 100644
--- a/include/HEJ/Event.hh
+++ b/include/HEJ/Event.hh
@@ -1,346 +1,351 @@
 /** \file
  *  \brief Declares the Event class and helpers
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
 #include "boost/iterator/filter_iterator.hpp"
 
 #include "fastjet/ClusterSequence.hh"
 
 #include "HEJ/event_types.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/Particle.hh"
 #include "HEJ/RNG.hh"
 
 namespace LHEF {
   class HEPEUP;
   class HEPRUP;
 }
 
 namespace fastjet {
   class JetDefinition;
 }
 
 namespace HEJ {
 
   struct UnclusteredEvent;
 
   /** @brief An event with clustered jets
     *
     * This is the main HEJ 2 event class.
     * It contains kinematic information including jet clustering,
     * parameter (e.g. scale) settings and the event weight.
     */
   class Event {
   public:
     class EventData;
 
+    //! Iterator over partons
     using ConstPartonIterator = boost::filter_iterator<
       bool (*)(Particle const &),
       std::vector<Particle>::const_iterator
       >;
+    //! Reverse Iterator over partons
     using ConstReversePartonIterator = std::reverse_iterator<
                                                 ConstPartonIterator>;
     //! No default Constructor
     Event() = delete;
     //! Event Constructor adding jet clustering to an unclustered event
     //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.2.0
     [[deprecated("UnclusteredEvent will be replaced by EventData")]]
     Event(
       UnclusteredEvent const & ev,
       fastjet::JetDefinition const & jet_def, double min_jet_pt
     );
 
     //! @name Particle Access
     //! @{
 
     //! Incoming particles
     std::array<Particle, 2> const &  incoming() const{
       return incoming_;
     }
     //! Outgoing particles
     std::vector<Particle> const &  outgoing() const{
       return outgoing_;
     }
     //! Iterator to the first outgoing parton
     ConstPartonIterator begin_partons() const;
     //! Iterator to the first outgoing parton
     ConstPartonIterator cbegin_partons() const;
 
     //! Iterator to the end of the outgoing partons
     ConstPartonIterator end_partons() const;
     //! Iterator to the end of the outgoing partons
     ConstPartonIterator cend_partons() const;
 
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator rbegin_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator crbegin_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator rend_partons() const;
     //! Reverse Iterator to the first outgoing parton
     ConstReversePartonIterator crend_partons() const;
 
     //! Particle decays
     /**
      *  The key in the returned map corresponds to the index in the
      *  vector returned by outgoing()
      */
     std::unordered_map<size_t, std::vector<Particle>> const &  decays() const{
       return decays_;
     }
     //! The jets formed by the outgoing partons, sorted in rapidity
     std::vector<fastjet::PseudoJet> const & jets() const{
       return jets_;
     }
     //! @}
 
     //! @name Weight variations
     //! @{
 
     //! All chosen parameter, i.e. scale choices (const version)
     Parameters<EventParameters> const & parameters() const{
       return parameters_;
     }
     //! All chosen parameter, i.e. scale choices
     Parameters<EventParameters> & parameters(){
       return parameters_;
     }
 
     //! Central parameter choice (const version)
     EventParameters const & central() const{
       return parameters_.central;
     }
     //! Central parameter choice
     EventParameters & central(){
       return parameters_.central;
     }
 
     //! Parameter (scale) variations (const version)
     std::vector<EventParameters> const & variations() const{
       return parameters_.variations;
     }
     //! Parameter (scale) variations
     std::vector<EventParameters> & variations(){
       return parameters_.variations;
     }
 
     //! Parameter (scale) variation (const version)
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters const & variations(size_t i) const{
       return parameters_.variations.at(i);
     }
     //! Parameter (scale) variation
     /**
      *  @param i   Index of the requested variation
      */
     EventParameters & variations(size_t i){
       return parameters_.variations.at(i);
     }
     //! @}
 
     //! Indices of the jets the outgoing partons belong to
     /**
      *  @param jets   Jets to be tested
      *  @returns      A vector containing, for each outgoing parton,
      *                the index in the vector of jets the considered parton
      *                belongs to. If the parton is not inside any of the
      *                passed jets, the corresponding index is set to -1.
      */
     std::vector<int> particle_jet_indices(
         std::vector<fastjet::PseudoJet> const & jets
     ) const {
       return cs_.particle_jet_indices(jets);
     }
     //! particle_jet_indices() of the Event jets()
     std::vector<int> particle_jet_indices() const {
       return particle_jet_indices(jets());
     }
 
     //! Jet definition used for clustering
     fastjet::JetDefinition const & jet_def() const{
       return cs_.jet_def();
     }
 
     //! Minimum jet transverse momentum
     double min_jet_pt() const{
       return min_jet_pt_;
     }
 
     //! Event type
     event_type::EventType type() const{
       return type_;
     }
 
     //! Give colours to each particle
     /**
      * @returns true if new colours are generated, i.e. same as is_resummable()
      * @details Colour ordering is done according to leading colour in the MRK
      *          limit, see \cite Andersen:2011zd. This only affects \ref
      *          is_resummable() "HEJ" configurations, all other \ref event_type
      *          "EventTypes" will be ignored.
      * @note    This overwrites all previously set colours.
      */
     bool generate_colours(HEJ::RNG &);
 
     //! Check that current colours are leading in the high energy limit
     /**
      * @details Checks that the colour configuration can be split up in
      *          multiple, rapidity ordered, non-overlapping ladders. Such
      *          configurations are leading in the MRK limit, see
      *          \cite Andersen:2011zd
      *
      * @note This is _not_ to be confused with \ref is_resummable(), however
      *       for all resummable states it is possible to create a leading colour
      *       configuration, see generate_colours()
      */
     bool is_leading_colour() const;
 
     /**
      * @brief Check if given event could have been produced by HEJ
      * @details A HEJ state has to fulfil:
      *          1. type() has to be \ref is_resummable() "resummable"
      *          2. Soft radiation in the tagging jets contributes at most to
      *             `max_ext_soft_pt_fraction` of the total jet \f$ p_\perp \f$
      *
      * @note This is true for any resummed stated produced by the
      *       EventReweighter or any \ref is_resummable() "resummable" Leading
      *       Order state.
      *
      * @param max_ext_soft_pt_fraction Maximum transverse momentum fraction from
      *                                 soft radiation in extremal jets
      * @param min_extparton_pt         Absolute minimal \f$ p_\perp \f$,
      *                                 \b deprecated use max_ext_soft_pt_fraction
      *                                 instead
      * @return True if this state could have been produced by HEJ
      */
     bool valid_hej_state(
       double max_ext_soft_pt_fraction, double min_extparton_pt = 0.) const;
 
   private:
     //! \internal
     //! @brief Construct Event explicitly from input.
     /** This is only intended to be called from EventData.
      *
      * \warning The input is taken _as is_, sorting and classification has to be
      *          done externally, i.e. by EventData
      */
     Event(
       std::array<Particle, 2> && incoming,
       std::vector<Particle> && outgoing,
       std::unordered_map<size_t, std::vector<Particle>> && decays,
       Parameters<EventParameters> && parameters,
       fastjet::JetDefinition const & jet_def,
       double const min_jet_pt
     );
 
     std::array<Particle, 2> incoming_;
     std::vector<Particle> outgoing_;
     std::unordered_map<size_t, std::vector<Particle>> decays_;
     std::vector<fastjet::PseudoJet> jets_;
     Parameters<EventParameters> parameters_;
     fastjet::ClusterSequence cs_;
     double min_jet_pt_;
     event_type::EventType type_;
   }; // end class Event
 
   //! Class to store general Event setup, i.e. Phase space and weights
   class Event::EventData {
   public:
     //! Default Constructor
     EventData() = default;
     //! Constructor from LesHouches event information
     EventData(LHEF::HEPEUP const & hepeup);
     //! Constructor with all values given
     EventData(
       std::array<Particle, 2> incoming,
       std::vector<Particle> outgoing,
       std::unordered_map<size_t, std::vector<Particle>> decays,
       Parameters<EventParameters> parameters
     ):
       incoming(std::move(incoming)), outgoing(std::move(outgoing)),
       decays(std::move(decays)), parameters(std::move(parameters))
     {}
 
     //! Generate an Event from the stored EventData.
     /**
      * @details          Do jet clustering and classification.
      *                   Use this to generate an Event.
      *
      * @note             Calling this function destroys EventData
      *
      * @param jet_def    Jet definition
      * @param min_jet_pt minimal \f$p_T\f$ for each jet
      *
      * @returns          Full clustered and classified event.
      */
     Event cluster(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt);
 
     //! Alias for cluster()
     Event operator()(
       fastjet::JetDefinition const & jet_def, double const min_jet_pt){
       return cluster(jet_def, min_jet_pt);
     }
 
     //! Sort particles in rapidity
     void sort();
 
     //! Reconstruct intermediate particles from final-state leptons
     /**
      *  Final-state leptons are created from virtual photons, W, or Z bosons.
      *  This function tries to reconstruct such intermediate bosons if they
      *  are not part of the event record.
      */
     void reconstruct_intermediate();
 
+    //! Incoming particles
     std::array<Particle, 2> incoming;
+    //! Outcoing particles
     std::vector<Particle> outgoing;
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays;
+    //! Parameters, e.g. scale or inital weight
     Parameters<EventParameters> parameters;
   }; // end class EventData
 
   //! Print Event
   std::ostream& operator<<(std::ostream & os, Event const & ev);
 
   //! Square of the partonic centre-of-mass energy \f$\hat{s}\f$
   double shat(Event const & ev);
 
   //! Convert an event to a LHEF::HEPEUP
   LHEF::HEPEUP to_HEPEUP(Event const & event, LHEF::HEPRUP *);
 
   // put deprecated warning at the end, so don't get the warning inside Event.hh,
   // additionally doxygen can not identify [[deprecated]] correctly
   struct [[deprecated("UnclusteredEvent will be replaced by EventData")]]
     UnclusteredEvent;
   //! An event before jet clustering
   //! @deprecated UnclusteredEvent will be replaced by EventData in HEJ 2.2.0
   struct UnclusteredEvent{
     //! Default Constructor
     UnclusteredEvent() = default;
     //! Constructor from LesHouches event information
     UnclusteredEvent(LHEF::HEPEUP const & hepeup);
 
     std::array<Particle, 2> incoming;          /**< Incoming Particles */
     std::vector<Particle> outgoing;            /**< Outgoing Particles */
     //! Particle decays in the format {outgoing index, decay products}
     std::unordered_map<size_t, std::vector<Particle>> decays;
     //! Central parameter (e.g. scale) choice
     EventParameters central;
     std::vector<EventParameters> variations;    /**< For parameter variation */
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/EventReader.hh b/include/HEJ/EventReader.hh
index a1d838e..e816a02 100644
--- a/include/HEJ/EventReader.hh
+++ b/include/HEJ/EventReader.hh
@@ -1,57 +1,57 @@
 /** \file
  *  \brief Header file for event reader interface
  *
  *  This header defines an abstract base class for reading events from files.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/optional.hh"
 
 namespace HEJ {
   class EventData;
 
-  // Abstract base class for reading events from files
+  //! Abstract base class for reading events from files
   struct EventReader {
     //! Read an event
     virtual bool read_event() = 0;
 
     //! Access header text
     virtual std::string const & header() const = 0;
 
     //! Access run information
     virtual LHEF::HEPRUP const & heprup() const = 0;
 
     //! Access last read event
     virtual LHEF::HEPEUP const & hepeup() const = 0;
 
     //! Guess number of events from header
     virtual HEJ::optional<size_t> number_events() const {
       size_t start = header().rfind("Number of Events");
       start = header().find_first_of("123456789", start);
       if(start == std::string::npos) {
         return {};
       }
       const size_t end = header().find_first_not_of("0123456789", start);
       return std::stoi(header().substr(start, end - start));
     }
 
     virtual ~EventReader() = default;
   };
 
   //! Factory function for event readers
   /**
    *  @param filename   The name of the input file
    *  @returns          A pointer to an instance of an EventReader
    *                    for the input file
    */
   std::unique_ptr<EventReader> make_reader(std::string const & filename);
 } // namespace HEJ
diff --git a/include/HEJ/EventReweighter.hh b/include/HEJ/EventReweighter.hh
index 3796a3d..969311d 100644
--- a/include/HEJ/EventReweighter.hh
+++ b/include/HEJ/EventReweighter.hh
@@ -1,197 +1,196 @@
 /** \file
  *  \brief Declares the EventReweighter class
  *
  *  EventReweighter is the main class used within HEJ 2. It reweights the
  *  resummation events.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <array>
 #include <memory>
 #include <vector>
 
 #include "HEJ/Config.hh"
 #include "HEJ/event_types.hh"
 #include "HEJ/MatrixElement.hh"
 #include "HEJ/Parameters.hh"
 #include "HEJ/PDF.hh"
 #include "HEJ/PDG_codes.hh"
 #include "HEJ/ScaleFunction.hh"
 #include "HEJ/StatusCode.hh"
 
 namespace LHEF {
   class HEPRUP;
 }
 
 namespace HEJ {
   class Event;
   class RNG;
 
   //! Beam parameters
   /**
    *  Currently, only symmetric beams are supported,
    *  so there is a single beam energy.
    */
   struct Beam{
     double E;                                /**< Beam energy */
     std::array<ParticleID, 2> type;          /**< Beam particles */
   };
 
   //! Main class for reweighting events in HEJ.
   class EventReweighter{
     using EventType = event_type::EventType;
 
   public:
     EventReweighter(
         Beam beam,                            /**< Beam Energy */
         int pdf_id,                           /**< PDF ID */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         std::shared_ptr<RNG> ran              /**< Random number generator */
     );
 
     EventReweighter(
         LHEF::HEPRUP const & heprup,          /**< LHEF event header */
         ScaleGenerator scale_gen,             /**< Scale settings */
         EventReweighterConfig conf,           /**< Configuration parameters */
         std::shared_ptr<RNG> ran              /**< Random number generator */
     );
 
     //! Get the used pdf
     PDF const & pdf() const;
 
     //! Get event treatment
     EventTreatment treatment(EventType type) const;
 
     //! Generate resummation events for a given fixed-order event
     /**
      *  @param ev             Fixed-order event corresponding
      *                        to the resummation events
      *  @param num_events     Number of trial resummation configurations.
      *  @returns              A vector of resummation events.
      *
      *  The result vector depends on the type of the input event and the
-     *  treatment of different types as specified in the constructor:
+     *  \ref EventTreatment of different types as specified in the constructor:
      *
-     *  \ref reweight  The result vector contains between
-     *                 0 and num_events resummation events.
-     *
-     *  \ref keep  If the input event passes the resummation jet cuts
-     *             the result vector contains one event. Otherwise it is empty.
-     *
-     *  \ref discard   The result vector is empty
+     *  - EventTreatment::reweight: The result vector contains between 0 and
+     *                              num_events resummation events.
+     *  - EventTreatment::keep:     If the input event passes the resummation
+     *                              jet cuts the result vector contains one
+     *                              event. Otherwise it is empty.
+     *  - EventTreatment::discard:  The result vector is empty
      */
     std::vector<Event> reweight(
         Event const & ev,
         size_t num_events
     );
 
     //! Gives all StatusCodes of the last reweight()
     /**
      * Each StatusCode corresponds to one tried generation. Only good
      * StatusCodes generated an event.
      */
     std::vector<StatusCode> const & status() const {
         return status_;
     }
 
   private:
     template<typename... T>
     PDF const & pdf(T&& ...);
 
     /** \internal
      * \brief main generation/reweighting function:
      * generate phase space points and divide out Born factors
      */
     std::vector<Event> gen_res_events(
         Event const & ev, size_t num_events
     );
     std::vector<Event> rescale(
         Event const & Born_ev, std::vector<Event> events
     ) const;
 
     /** \internal
      * \brief Do the Jets pass the resummation Cuts?
      *
      * @param ev               Event in Question
      * @returns                0 or 1 depending on if ev passes Jet Cuts
      */
     bool jets_pass_resummation_cuts(Event const & ev) const;
 
     /** \internal
      * \brief pdf_factors Function
      *
      * @param ev         Event in Question
      * @returns          EventFactor due to PDFs
      *
      * Calculates the Central value and the variation due
      * to the PDF choice made.
      */
     Weights pdf_factors(Event const & ev) const;
 
     /** \internal
      * \brief matrix_elements Function
      *
      * @param ev         Event in question
      * @returns          EventFactor due to MatrixElements
      *
      * Calculates the Central value and the variation due
      * to the Matrix Element.
      */
     Weights matrix_elements(Event const & ev) const;
 
     /** \internal
      * \brief Scale-dependent part of fixed-order matrix element
      *
      * @param ev         Event in question
      * @returns          EventFactor scale variation due to FO-ME.
      *
      * This is only called to compute the scale variation for events where
      * we don't do resummation (e.g. non-FKL).
      * Since at tree level the scale dependence is just due to alpha_s,
      * it is enough to return the alpha_s(mur) factors in the matrix element.
      * The rest drops out in the ratio of (output event ME)/(input event ME),
      * so we never have to compute it.
      */
     Weights fixed_order_scale_ME(Event const & ev) const;
 
     /** \internal
      * \brief Computes the tree level matrix element
      *
      * @param ev                Event in Question
      * @returns                 HEJ approximation to Tree level Matrix Element
      *
      * This computes the HEJ approximation to the tree level FO
      * Matrix element which is used within the LO weighting process.
      */
     double tree_matrix_element(Event const & ev) const;
 
     //! \internal General parameters
     EventReweighterConfig param_;
 
     //! \internal Beam energy
     double E_beam_;
 
     //! \internal PDF
     PDF pdf_;
 
     //! \internal Object to calculate the square of the matrix element
     MatrixElement MEt2_;
     //! \internal Object to calculate event renormalisation and factorisation scales
     ScaleGenerator scale_gen_;
     //! \internal random number generator
     std::shared_ptr<RNG> ran_;
     //! \internal StatusCode of each attempt
     std::vector<StatusCode> status_;
   };
 
   template<typename... T>
   PDF const & EventReweighter::pdf(T&&... t){
     return pdf_ = PDF{std::forward<T>(t)...};
   }
 
 } // namespace HEJ
diff --git a/include/HEJ/JetSplitter.hh b/include/HEJ/JetSplitter.hh
index bf0fabd..2587de5 100644
--- a/include/HEJ/JetSplitter.hh
+++ b/include/HEJ/JetSplitter.hh
@@ -1,70 +1,71 @@
 /**
  * \file
  * \brief Declaration of the JetSplitter class
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <vector>
 
 #include "fastjet/JetDefinition.hh"
 
 namespace fastjet {
   class PseudoJet;
 }
 
 namespace HEJ {
   class RNG;
 
   //! Class to split jets into their constituents
   class JetSplitter {
   public:
+    //! Wrapper for return values
     struct SplitResult {
       std::vector<fastjet::PseudoJet> constituents;
       double weight;
     };
 
     //! Constructor
     /**
      * @param jet_def   Jet definition
      * @param min_pt    Minimum jet transverse momentum
      */
     JetSplitter(fastjet::JetDefinition jet_def, double min_pt);
 
     //! Split a get into constituents
     /**
      * @param j2split   Jet to be split
      * @param ncons     Number of constituents
      * @param ran       Random number generator
      * @returns         The constituent momenta together with the associated
      *                  weight
      */
     SplitResult split(
         fastjet::PseudoJet const & j2split, int ncons, RNG & ran
     ) const;
 
     //! Maximum distance of constituents to jet axis
     static constexpr double R_factor = 5./3.;
 
   private:
     //! \internal split jet into two partons
     SplitResult Split2(fastjet::PseudoJet const & j2split, RNG & ran) const;
 
     /** \internal
      * @brief sample y-phi distance to jet pt axis for a jet splitting into two
      *        partons
      *
      * @param wt    Multiplied by the weight of the sampling point
      * @param ran   Random number generator
      * @returns     The distance in units of the jet radius
      */
     double sample_distance_2p(double & wt, RNG & ran) const;
 
     double min_jet_pt_;
     fastjet::JetDefinition jet_def_;
   };
 } // namespace HEJ
diff --git a/include/HEJ/LesHouchesReader.hh b/include/HEJ/LesHouchesReader.hh
index 4892ba6..549cfbc 100644
--- a/include/HEJ/LesHouchesReader.hh
+++ b/include/HEJ/LesHouchesReader.hh
@@ -1,78 +1,80 @@
 /** \file
  *  \brief Header file for reading events in the Les Houches Event File format.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Event.hh"
 #include "HEJ/EventReader.hh"
 #include "HEJ/stream.hh"
 
 namespace HEJ{
 
   //! Class for reading events from a file in the Les Houches Event File format
   class LesHouchesReader : public EventReader{
   public:
     //! Contruct object reading from the given file
     explicit LesHouchesReader(std::string const & filename):
       stream_{filename},
       reader_{stream_}
     {}
 
     //! Read an event
     bool read_event() override {
       return reader_.readEvent();
     }
 
     //! Access header text
     std::string const & header() const override {
       return reader_.headerBlock;
     }
 
     //! Access run information
     LHEF::HEPRUP const & heprup() const override {
       return reader_.heprup;
     }
 
     //! Access last read event
     LHEF::HEPEUP const & hepeup() const override {
       return reader_.hepeup;
     }
 
   private:
     HEJ::istream stream_;
 
   protected:
+    //! Underlying reader
     LHEF::Reader reader_;
   };
 
   /**
    * @brief   Les Houches Event file reader for LHE files created by Sherpa
    * @details In Sherpa the cross section is given by
    *          sum(weights)/(number of trials). This EventReader converts the
    *          weights such that cross section=sum(weights)
    *  @note   Reading from a pipe is not possible!
    */
   class SherpaLHEReader : public LesHouchesReader{
   public:
+    //! Inialise Reader for a Sherpa LHE file
     explicit SherpaLHEReader(std::string const & filename);
 
     bool read_event() override;
 
     HEJ::optional<size_t> number_events() const override {
       return num_events;
     }
 
   private:
     double num_trials;
     size_t num_events;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/LorentzVector.hh b/include/HEJ/LorentzVector.hh
index df42d12..c3b1bdb 100644
--- a/include/HEJ/LorentzVector.hh
+++ b/include/HEJ/LorentzVector.hh
@@ -1,39 +1,55 @@
 /** \file
  *  \brief Auxiliary functions for Lorentz vectors
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
+#include <complex>
+
 #include "CLHEP/Vector/LorentzVector.h"
 
 namespace HEJ {
+  //! "dot" product
   inline
   auto dot(
-    CLHEP::HepLorentzVector const & h1,
-    CLHEP::HepLorentzVector const & h2
+    CLHEP::HepLorentzVector const & pi,
+    CLHEP::HepLorentzVector const & pj
   ) {
-    return h1.dot(h2);
+    return pi.dot(pj);
   }
 
+  //! "angle" product angle(pi, pj) = \<i j\>
+  std::complex<double> angle(
+    CLHEP::HepLorentzVector const & pi,
+    CLHEP::HepLorentzVector const & pj
+  );
+
+  //! "square" product square(pi, pj) = [i j]
+  std::complex<double> square(
+    CLHEP::HepLorentzVector const & pi,
+    CLHEP::HepLorentzVector const & pj
+  );
+
+  //! Invariant mass
   inline
   auto m2(CLHEP::HepLorentzVector const & h1) {
     return h1.m2();
   }
 
   //! Split a single Lorentz vector into two lightlike Lorentz vectors
   /**
    *  @param P   Lorentz vector to be split
    *  @returns   A pair (p, q) of Lorentz vectors with P = p + q and p^2 = q^2 = 0
    *
    *  P.perp() has to be positive.
    *
    *  p.e() is guaranteed to be positive.
    *  In addition, if either of P.plus() or P.minus() is positive,
    *  q.e() has the same sign as P.m2()
    */
   std::pair<CLHEP::HepLorentzVector, CLHEP::HepLorentzVector>
   split_into_lightlike(CLHEP::HepLorentzVector const & P);
 }
diff --git a/include/HEJ/Mixmax.hh b/include/HEJ/Mixmax.hh
index 61f2c52..03d229f 100644
--- a/include/HEJ/Mixmax.hh
+++ b/include/HEJ/Mixmax.hh
@@ -1,35 +1,36 @@
 /** \file
  * \brief The Mixmax random number generator
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include "CLHEP/Random/MixMaxRng.h"
 #include "CLHEP/Random/Randomize.h"
 
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
 
   //! MIXMAX random number generator
   /**
    *  For details on MIXMAX, see \cite Savvidy:2014ana
    */
   class Mixmax : public DefaultRNG {
   public:
     Mixmax() = default;
+    //! Constructor with explicit seed
     Mixmax(long seed): ran_{seed} {}
 
     //! Generate pseudorandom number between 0 and 1
     double flat() override {
       return ran_.flat();
     }
 
   private:
     CLHEP::MixMaxRng ran_;
   };
 
 }
diff --git a/include/HEJ/RNG.hh b/include/HEJ/RNG.hh
index 2f6af42..bd19749 100644
--- a/include/HEJ/RNG.hh
+++ b/include/HEJ/RNG.hh
@@ -1,47 +1,48 @@
 /** \file
  *  \brief Interface for pseudorandom number generators
  *
  *  We select our random number generator at runtime according to the
  *  configuration file. This interface guarantees that we can use all
  *  generators in the same way.
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <limits>
 
 namespace HEJ {
 
   //! Interface for random number generator
   struct RNG {
+    //! Random number type, see std::RandomNumberDistribution
     using result_type = unsigned;
 
     //! Generate random number in [0,1)
     virtual double flat() = 0;
 
     //! Minimum number that can be generated
     virtual result_type min() const = 0;
     //! Maximum number that can be generated
     virtual result_type max() const = 0;
     //! Generate random number in [min(), max()]
     virtual result_type operator()() = 0;
 
     virtual ~RNG() = default;
   };
 
   //! Helper struct with default implementations
   struct DefaultRNG : virtual RNG {
     result_type min() const override {
       return 0u;
     }
     result_type max() const override {
       return std::numeric_limits<result_type>::max() - 1;
     }
     result_type operator()() override {
       return flat()*std::numeric_limits<result_type>::max();
     }
   };
 } // namespace HEJ
diff --git a/include/HEJ/Ranlux64.hh b/include/HEJ/Ranlux64.hh
index 22e898a..50323d9 100644
--- a/include/HEJ/Ranlux64.hh
+++ b/include/HEJ/Ranlux64.hh
@@ -1,34 +1,35 @@
 /** \file
  * \brief Contains a class for the ranlux64 random number generator
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "CLHEP/Random/Ranlux64Engine.h"
 
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
 
   //! Ranlux64 random number generator
   /**
    *  For details on ranlux64, see \cite Luscher:1993dy, \cite James:1993np
    */
   class Ranlux64 : public DefaultRNG {
   public:
     Ranlux64();
+    //! Constructor with a file as seed
     Ranlux64(std::string const & seed_file);
 
     //! Generate pseudorandom number between 0 and 1
     double flat() override;
 
   private:
     CLHEP::Ranlux64Engine ran_;
   };
 
 } // namespace HEJ
diff --git a/include/HEJ/RivetAnalysis.hh b/include/HEJ/RivetAnalysis.hh
index 79a7598..9570f92 100644
--- a/include/HEJ/RivetAnalysis.hh
+++ b/include/HEJ/RivetAnalysis.hh
@@ -1,72 +1,70 @@
 /** \file
  *  \brief HEJ 2 interface to rivet analyses
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <memory>
 #include <string>
 #include <vector>
 
 #include "LHEF/LHEF.h"
 
 #include "HEJ/Analysis.hh"
 #include "HEJ/optional.hh"
 
-namespace Rivet {
-  class AnalysisHandler;
-}
 namespace YAML {
   class Node;
 }
 
 namespace HEJ {
   /**
     * @brief Class representing a Rivet analysis
     *
     * This class inherits from Analysis and can therefore be used
     * like any other HEJ 2 analysis.
     */
   class RivetAnalysis: public HEJ::Analysis {
   public:
+    //! Create RivetAnalysis
     static std::unique_ptr<Analysis> create(
         YAML::Node const & config, LHEF::HEPRUP const & heprup);
 
     //! Constructor
     /**
      *  @param config    Configuration parameters
      *  @param heprup    General run informations
      *
      * config["rivet"] should be the name of a single Rivet analysis or
      * a list of Rivet analyses. config["output"] is the prefix for
      * the .yoda output files.
      */
     RivetAnalysis(YAML::Node const & config, LHEF::HEPRUP const & heprup);
     ~RivetAnalysis() override;
     //! Pass an event to the underlying Rivet analysis
     void fill(HEJ::Event const & event, HEJ::Event const &) override;
     bool pass_cuts(HEJ::Event const &, HEJ::Event const &) override
        {return true;} //!< no additional cuts are applied
     void finalise() override;
 
   private:
     std::vector<std::string> analyses_names_;
     std::string output_name_;
     LHEF::HEPRUP heprup_;
 
     /// struct to organise the infos per rivet run/scale setting
     struct rivet_info;
     std::vector<rivet_info> rivet_runs_;
 
     /**
      *  \internal
      * @brief Calculates the scale variation from the first event for the output
      *        file
      */
     void init(HEJ::Event const & event);
     bool first_event_;
   };
 } // namespace HEJ
diff --git a/include/HEJ/StatusCode.hh b/include/HEJ/StatusCode.hh
index d76b934..03dd94d 100644
--- a/include/HEJ/StatusCode.hh
+++ b/include/HEJ/StatusCode.hh
@@ -1,46 +1,47 @@
 /** \file
  *  \brief Header file for status codes of event generation
  *
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <string>
 
 #include "HEJ/exceptions.hh"
 
 namespace HEJ {
   //! Possible status codes from the event generation
   enum StatusCode{
     good,
     discard,
     empty_jets,
     failed_reshuffle,
     failed_resummation_cuts,
     failed_split,
     too_much_energy,
     gluon_in_qqx,
     wrong_jets,
     unspecified // should never appear
   };
 
-  // @TODO better names
+  //! Get name of StatusCode
+  //! @TODO better names
   inline std::string to_string(StatusCode s){
     switch(s){
       case good:                    return "good";
       case discard:                 return "discard";
       case empty_jets:              return "empty jets";
       case failed_reshuffle:        return "failed reshuffle";
       case failed_resummation_cuts: return "below cuts";
       case failed_split:            return "failed split";
       case too_much_energy:         return "too much energy";
       case gluon_in_qqx:            return "gluon inside qqx";
       case wrong_jets:              return "wrong jets";
       case unspecified:             return "unspecified";
       default:{}
     }
     throw std::logic_error{"unreachable"};
   }
 } // namespace HEJ
diff --git a/include/HEJ/Tensor.hh b/include/HEJ/Tensor.hh
deleted file mode 100644
index fd993e8..0000000
--- a/include/HEJ/Tensor.hh
+++ /dev/null
@@ -1,252 +0,0 @@
-/** \file
- *  \brief Tensor Template Class declaration.
- *
- *  This file contains the declaration of the Tensor Template class. This
- *  is used to calculate some of the more complex currents within the
- *  W+Jets implementation particularly.
- *
- *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
- *  \copyright GPLv2 or later
- */
-#pragma once
-
-#include <array>
-#include <complex>
-#include <valarray>
-
-#include "HEJ/helicity.hh"
-
-namespace CLHEP {
-  class HepLorentzVector;
-}
-
-namespace HEJ {
-  typedef std::complex<double> COM;
-
-  namespace detail {
-    static constexpr std::size_t d = 4;
-
-    //! \internal Compute integer powers at compile time
-    inline
-    constexpr std::size_t power(unsigned base, unsigned exp)  {
-      if(exp == 0) return 1;
-      // use exponentiation by squaring
-      // there are potentially faster implementations using bit shifts
-      // but we don't really care because everything's done at compile time
-      // and this is easier to understand
-      const std::size_t sqrt = power(base, exp/2);
-      return (exp % 2)?base*sqrt*sqrt:sqrt*sqrt;
-    }
-  }
-
-  template <unsigned int N>
-  class Tensor{
-  public:
-    static constexpr std::size_t d = detail::d;
-
-    //! Constructor
-    Tensor() = default;
-    explicit Tensor(COM x);
-
-    //! Rank of Tensor
-    constexpr unsigned rank() const{
-      return N;
-    }
-    //! total number of entries
-    std::size_t size() const {
-      return components.size();
-    }
-
-    //! Tensor element with given indices
-    template<typename... Indices>
-    COM const & operator()(Indices... i) const;
-    //! Tensor element with given indices
-    template<typename... Indices>
-    COM & operator()(Indices... rest);
-
-    //! implicit conversion to complex number for rank 0 tensors (scalars)
-    operator COM() const;
-
-    Tensor<N> & operator*=(COM const & x);
-    Tensor<N> & operator/=(COM const & x);
-
-    Tensor<N> & operator+=(Tensor<N> const & T2);
-    Tensor<N> & operator-=(Tensor<N> const & T2);
-
-    template<unsigned M>
-    friend bool operator==(Tensor<M> const & T1, Tensor<M> const & T2);
-
-    //! Outer product of two tensors
-    template<unsigned M>
-    Tensor<N+M> outer(Tensor<M> const & T2) const;
-
-    //! Outer product of two tensors
-    template<unsigned K, unsigned M>
-    friend Tensor<K+M> outer(Tensor<K> const & T1, Tensor<M> const & T2);
-
-    //! Outer product of tensor with HLV
-    template<unsigned K>
-    friend Tensor<K+1> outer(Tensor<K> const & T1, CLHEP::HepLorentzVector const & p2);
-
-    //! Outer product of HLV with tensor
-    template<unsigned K>
-    friend Tensor<K+1> outer(CLHEP::HepLorentzVector const & p1, Tensor<K> const & T2);
-
-    /**
-     * \brief T^(mu1...mk..mN)T2_(muk) contract kth index, where k member of [1,N]
-     * @param T2           Tensor of Rank 1 to contract with.
-     * @param k            int to contract Tensor T2 with from original Tensor.
-     * @returns            T1.contract(T2,1) -> T1^(mu,nu,rho...)T2_mu
-     */
-    Tensor<N-1> contract(Tensor<1> const & T2, int k) const;
-
-    /**
-     * \brief T^(mu1...mk..mN)p2_(muk) contract kth index, where k member of [1,N]
-     * @param p2           HepLorentzVector to contract with.
-     * @param k            int to contract HLV p2 with from original Tensor.
-     * @returns            T1.contract(p2,1) -> T1^(mu,nu,rho...)p2_mu
-     */
-    Tensor<N-1> contract(CLHEP::HepLorentzVector const & p2, int k) const;
-
-    /**
-     * \brief "Dot" product, contracting the last open index
-     * @param T2           Vector (e.g. Rank 1 Tensor) to contract with
-     * @returns            Same as T1.contract(T2, N)
-     */
-    template<typename T>
-    Tensor<N-1> dot(T const & T2) const {
-      return contract(T2, N);
-    }
-
-    //! Complex conjugate tensor
-    Tensor<N> & complex_conj();
-
-    std::array<COM, detail::power(d, N)> components;
-  };
-
-  template<unsigned N>
-  Tensor<N> operator*(COM const & x, Tensor<N> t);
-  template<unsigned N>
-  Tensor<N> operator*(Tensor<N> t, COM const & x);
-  template<unsigned N>
-  Tensor<N> operator/(Tensor<N> t, COM const & x);
-
-  template<unsigned N>
-  Tensor<N> operator+(Tensor<N> T1, Tensor<N> const & T2);
-  template<unsigned N>
-  Tensor<N> operator-(Tensor<N> T1, Tensor<N> const & T2);
-
-  template<unsigned N>
-  bool operator==(Tensor<N> const & T1, Tensor<N> const & T2);
-  template<unsigned N>
-  bool operator!=(Tensor<N> const & T1, Tensor<N> const & T2);
-
-  //! Absolute square of a vector
-  inline
-  double abs2(Tensor<1> const & v) {
-    double res = std::norm(v(0));
-    for(size_t i = 1; i < detail::d; ++i) {
-      res -= std::norm(v(i));
-    }
-    return res;
-  }
-
-  //! Real part of a.dot(b.complex_conj())
-  // @TODO needs better name
-  inline
-  double vre(const Tensor<1> & a, const Tensor<1> & b) {
-    COM res = a(0)*conj(b(0));
-    for(size_t i = 1; i < detail::d; ++i) {
-      res -= a(i)*conj(b(i));
-    }
-    return real(res);
-  }
-
-  /**
-   * \brief Returns diag(+---) metric
-   * @returns      metric {(1,0,0,0),(0,-1,0,0),(0,0,-1,0),(0,0,0,-1)}
-   */
-  Tensor<2> metric();
-
-  /**
-   * \brief Calculates current <p1|mu|p2>
-   * @param p1       Momentum of Particle 1
-   * @param p2       Momentum of Particle 2
-   * @param h        Helicity of the Spinors
-   * @returns        Tensor T^mu = <p1|mu|p2>
-   *
-   * The vector current is helicity conserving, so we only need to state
-   * one helicity.
-   *
-   * @note in/out configuration considered in calculation
-   */
-  Tensor<1> current(
-      CLHEP::HepLorentzVector const & p1,
-      CLHEP::HepLorentzVector const & p2,
-      Helicity h
-  );
-
-  //! "angle" product angle(pi, pj) = \<i j\>
-  COM angle(CLHEP::HepLorentzVector const & pi,
-            CLHEP::HepLorentzVector const & pj);
-
-  //! "square" product square(pi, pj) = [i j]
-  COM square(CLHEP::HepLorentzVector const & pi,
-             CLHEP::HepLorentzVector const & pj);
-
-  /**
-   * \brief Calculates current <p1|mu nu rho|p2>
-   * @param p1       Momentum of Particle 1
-   * @param p2       Momentum of Particle 2
-   * @param h        Helicity of the Spinors
-   * @returns        Tensor T^mu^nu^rho = <p1|mu nu rho|p2>
-   *
-   * @note in/out configuration considered in calculation
-   */
-  Tensor<3> rank3_current(
-      CLHEP::HepLorentzVector const & p1,
-      CLHEP::HepLorentzVector const & p2,
-      Helicity h
-  );
-  /**
-   * \brief Calculates current <p1|mu nu rho tau sigma|p2>
-   * @param p1       Momentum of Particle 1
-   * @param p2       Momentum of Particle 2
-   * @param h        Helicity of the Spinors
-   * @returns        Tensor T^mu^nu^rho = <p1|mu nu rho tau sigma|p2>
-   *
-   * @note in/out configuration considered in calculation
-   */
-  Tensor<5> rank5_current(
-      CLHEP::HepLorentzVector const & p1,
-      CLHEP::HepLorentzVector const & p2,
-      Helicity h
-  );
-
-  /**
-   * \brief Convert from HLV class
-   * @param p          Current in HLV format
-   * @returns          Current in Tensor Format
-   */
-  Tensor<1> to_tensor(CLHEP::HepLorentzVector const & p);
-
-  /**
-   * \brief Construct Epsilon (Polarisation) Tensor
-   * @param k          Momentum of incoming/outgoing boson
-   * @param ref        Reference momentum for calculation
-   * @param pol        Polarisation of boson
-   * @returns          Polarisation Tensor E^mu
-   */
-  Tensor<1> eps(
-      CLHEP::HepLorentzVector const & k,
-      CLHEP::HepLorentzVector const & ref,
-      Helicity pol
-  );
-
-  //! Initialises Tensor values by iterating over permutations of gamma matrices.
-  void init_sigma_index();
-} // namespace HEJ
-
-// implementation of template functions
-#include "HEJ/detail/Tensor_impl.hh"
diff --git a/include/HEJ/Unweighter.hh b/include/HEJ/Unweighter.hh
index 383654b..1fea8f6 100644
--- a/include/HEJ/Unweighter.hh
+++ b/include/HEJ/Unweighter.hh
@@ -1,77 +1,84 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include <functional>
 #include <vector>
 
 #include "HEJ/optional.hh"
 
 namespace HEJ {
   class Event;
   class RNG;
 
+  /**
+   * @brief Unweight events
+   * @details Throws away events below with abs(weight)<cut with probability
+   *          wt/cut
+   */
   class Unweighter {
   public:
+    //! Constructor
+    //! @param cut Initial value of cut, negative values for no cut
     Unweighter(double cut = -1.):
       cut_{cut}{}
 
     //! Explicitly set cut
     void set_cut(double max_weight){
       cut_ = max_weight;
     }
     //! Set cut as max(weight) of events
     void set_cut_to_maxwt(std::vector<Event> const & events){
       set_cut_to_maxwt(events.cbegin(), events.cend());
     }
     //! Iterator version of set_max()
     template<class ConstIt>
     void set_cut_to_maxwt(ConstIt begin, ConstIt end);
 
     //! Estimate some reasonable cut for partial unweighting
     /**
      * @param events            Events used for the estimation
      * @param max_dev           Standard derivation to include above mean weight
      */
     void set_cut_to_peakwt(std::vector<Event> const & events, double max_dev){
       set_cut_to_peakwt(events.cbegin(), events.cend(), max_dev);
     }
     //! Iterator version of set_cut_to_peakwt()
     template<class ConstIt>
     void set_cut_to_peakwt(ConstIt begin, ConstIt end, double max_dev);
 
     //! Returns current value of the cut
     double get_cut() const {
       return cut_;
     }
 
     //! Unweight one event, returns original event if weight > get_cut()
     optional<Event> unweight(Event ev, RNG & ran) const;
     //! Unweight for multiple events at once
     std::vector<Event> unweight(
       std::vector<Event> events, RNG & ran
     ) const;
     //! @brief Iterator implementation of unweight(),
     /**
      * Usage similar to std::remove(), i.e. use with erase()
      *
      * @return Beginning of "discarded" range
      */
     template<class Iterator>
     Iterator unweight(
       Iterator begin, Iterator end, RNG & ran
     ) const;
 
   private:
     double cut_;
     //! Returns true if element can be removed/gets discarded
     //! directly corrects weight if is accepted (not removed)
     bool discard(RNG & ran, Event & ev) const;
   };
 } // namespace HEJ
 
 // implementation of template functions
 #include "HEJ/detail/Unweighter_impl.hh"
diff --git a/include/HEJ/detail/HepMCInterface_common.hh b/include/HEJ/detail/HepMCInterface_common.hh
index 3de7956..9db9533 100644
--- a/include/HEJ/detail/HepMCInterface_common.hh
+++ b/include/HEJ/detail/HepMCInterface_common.hh
@@ -1,86 +1,95 @@
 /** \file
  *  \brief Template functions shared between HepMC2 and HepMC3
  *
  *  \authors The HEJ collaboration (see AUTHORS for details)
  *  \date 2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include "HEJ/Event.hh"
 #include "HEJ/Particle.hh"
 
 namespace HEJ{
   namespace detail_HepMC{
 
+    //! Convert Particle to HepMC 4-vector (x,y,z,E)
     template<class FourVector>
     FourVector to_FourVector(Particle const & sp){
       return {sp.px(), sp.py(), sp.pz(), sp.E()};
     }
+    //! @name Conventional status codes in HepMC
+    //!@{
+    constexpr int status_beam = 4;    //!< Beam
+    constexpr int status_in = 11;     //!< Incoming
+    constexpr int status_decayed = 2; //!< Decaying
+    constexpr int status_out = 1;     //!< Final outgoing
 
-    constexpr int status_beam = 4;
-    constexpr int status_in = 11;
-    constexpr int status_decayed = 2;
-    constexpr int status_out = 1;
+    //!@}
 
-    // Helper classes depending on HepMC version V
+    //! @name Helper classes/functions
+    //! @brief Have to be implemented for each HepMC version V
+    //!@{
     template<int V> struct HepMCVersion;
     template<int V>
     using GenEvent = typename HepMCVersion<V>::GenEvent;
     template<int V>
     using Beam = typename HepMCVersion<V>::Beam;
 
+    //! Make HepMC particle
     template<int V>
     auto make_particle_ptr(
         Particle const & sp, int status
     );
 
+    //! Make HepMC vetex
     template<int V>
     auto make_vx_ptr();
+    //!@}
 
     /**
      * @brief Template class to initialise the kinematics for HepMC
      *
      * @tparam V        HepMC major version
      * @param event     HEJ event
      * @param beam      beam particles
      * @param out_ev    new HepMC event
      */
     template<int V>
     GenEvent<V> HepMC_init_kinematics(
         Event const & event, Beam<V> const & beam, GenEvent<V> && out_ev
     ){
       out_ev.set_beam_particles(beam[0], beam[1]);
 
       auto vx = make_vx_ptr<V>();
       for(size_t i=0; i<event.incoming().size(); ++i){
         auto particle = make_particle_ptr<V>(event.incoming()[i], status_in);
         auto vx_beam = make_vx_ptr<V>();
         vx_beam->add_particle_in(beam[i]);
         vx_beam->add_particle_out(particle);
         out_ev.add_vertex(vx_beam);
         vx->add_particle_in(particle);
       }
       for(size_t i=0; i < event.outgoing().size(); ++i){
         auto const & out = event.outgoing()[i];
         auto particle = make_particle_ptr<V>(out, status_out);
         const int status = event.decays().count(i)?status_decayed:status_out;
         particle->set_status(status);
         if( status == status_decayed ){
           auto vx_decay = make_vx_ptr<V>();
           vx_decay->add_particle_in(particle);
           for( auto const & decay: event.decays().at(i) ){
             vx_decay->add_particle_out(
                 make_particle_ptr<V>(decay, status_out)
             );
           }
           out_ev.add_vertex(vx_decay);
         }
         vx->add_particle_out(particle);
       }
       out_ev.add_vertex(vx);
 
       return out_ev;
     }
   } // namespace detail_HepMC
 } // namespace HEJ
diff --git a/include/HEJ/detail/Tensor_impl.hh b/include/HEJ/detail/Tensor_impl.hh
deleted file mode 100644
index 7d7a7f3..0000000
--- a/include/HEJ/detail/Tensor_impl.hh
+++ /dev/null
@@ -1,208 +0,0 @@
-/** \file
- *  \brief Tensor Template Class Implementation.
- *
- *  This file contains the implementation of the Tensor Template functions
- *
- *  \authors The HEJ collaboration (see AUTHORS for details)
- *  \date 2019
- *  \copyright GPLv2 or later
- */
-#pragma once
-#include "HEJ/Tensor.hh"
-
-#include <algorithm>
-#include <cassert>
-
-#include "CLHEP/Vector/LorentzVector.h"
-
-namespace HEJ {
-  namespace detail {
-    inline
-    constexpr std::size_t accumulate_idx(const std::size_t acc) {
-      return acc;
-    }
-
-    template<typename... Indices>
-    constexpr std::size_t accumulate_idx(
-        const std::size_t acc,
-        const std::size_t idx, Indices... indices
-    ) {
-      return accumulate_idx(d*acc + idx, indices...);
-    }
-  }
-
-  template <unsigned int N>
-  template<typename... Indices>
-  COM & Tensor<N>::operator()(Indices... i) {
-    static_assert(
-        sizeof...(Indices) == N,
-        "number of indices must match tensor rank"
-    );
-    return components[detail::accumulate_idx(0, i...)];
-  }
-
-  template <unsigned int N>
-  template<typename... Indices>
-  COM const & Tensor<N>::operator()(Indices... i) const {
-    static_assert(
-        sizeof...(Indices) == N,
-        "number of indices must match tensor rank"
-    );
-    return components[detail::accumulate_idx(0, i...)];
-  }
-
-  template <unsigned int N>
-  Tensor<N>::operator COM() const{
-    static_assert(N==0, "Can only convert a scalar (rank 0 tensor) to a number");
-    assert(components.size() == 1);
-    return components[0];
-  }
-
-  template <unsigned int N>
-  Tensor<N>::Tensor(COM x) {
-    components.fill(x);
-  }
-
-  template<unsigned N>
-  Tensor<N> & Tensor<N>::operator*=(COM const & x) {
-    for(auto & c: components) c *= x;
-    return *this;
-  }
-  template<unsigned N>
-  Tensor<N> & Tensor<N>::operator/=(COM const & x) {
-    for(auto & c: components) c /= x;
-    return *this;
-  }
-
-  template<unsigned N>
-  Tensor<N> operator*(Tensor<N> t, COM const & x) {
-    return t *= x;
-  }
-  template<unsigned N>
-  Tensor<N> operator*(COM const & x, Tensor<N> t) {
-    return t *= x;
-  }
-  template<unsigned N>
-  Tensor<N> operator/(Tensor<N> t, COM const & x) {
-    return t /= x;
-  }
-
-  template <unsigned int N>
-  Tensor<N> & Tensor<N>::operator+=(Tensor<N> const & T2){
-    for(std::size_t i = 0; i < size(); ++i) {
-      components[i] += T2.components[i];
-    }
-    return *this;
-  }
-
-  template <unsigned int N>
-  Tensor<N> & Tensor<N>::operator-=(Tensor<N> const & T2){
-    for(std::size_t i = 0; i < size(); ++i) {
-      components[i] -= T2.components[i];
-    }
-    return *this;
-  }
-
-  template<unsigned N>
-  Tensor<N> operator+(Tensor<N> T1, Tensor<N> const & T2) {
-    return T1 += T2;
-  }
-  template<unsigned N>
-  Tensor<N> operator-(Tensor<N> T1, Tensor<N> const & T2) {
-    return T1 -= T2;
-  }
-
-  template<unsigned N>
-  bool operator==(Tensor<N> const & T1, Tensor<N> const & T2) {
-    return T1.components == T2.components;
-  }
-  template<unsigned N>
-  bool operator!=(Tensor<N> const & T1, Tensor<N> const & T2) {
-    return !(T1 == T2);
-  }
-
-  template<unsigned K, unsigned M>
-  Tensor<K+M> outer(Tensor<K> const & T1, Tensor<M> const & T2) {
-    Tensor<K+M> product;
-    auto target = begin(product.components);
-    for(auto && t1: T1.components) {
-      std::transform(
-          begin(T2.components), end(T2.components),
-          target,
-          [t1](auto && t2) { return t1*t2; }
-      );
-      std::advance(target, T2.components.size());
-    }
-    return product;
-  }
-
-  template<unsigned K>
-  Tensor<K+1> outer(CLHEP::HepLorentzVector const & p1, Tensor<K> const & T2) {
-    return outer(to_tensor(p1), T2);
-  }
-
-  template<unsigned K>
-  Tensor<K+1> outer(Tensor<K> const & T1, CLHEP::HepLorentzVector const & p2) {
-    return outer(T1, to_tensor(p2));
-  }
-
-  template<unsigned N>
-  template<unsigned M>
-  Tensor<N+M> Tensor<N>::outer(Tensor<M> const & T2) const {
-    return outer(*this, T2);
-  }
-
-  namespace detail {
-    inline
-    COM metric_sign(unsigned int i){
-      if(i==0)
-        return 1.;
-      else
-        return -1.;
-    }
-  }
-
-  template <unsigned int N>
-  Tensor<N-1> Tensor<N>::contract(Tensor<1> const & T2, const int k) const {
-    Tensor<N-1> result;
-    // distance between consecutive contracted elements in this Tensor
-    const std::size_t step = detail::power(d, N-k);
-    for(std::size_t res_idx = 0; res_idx < result.size(); ++res_idx){
-      COM & res = result.components[res_idx];
-      // Assume we compute T_{i_1,...i_N} * T2_{i_k}.
-      // The index into our components (T) is
-      // index = d^N*i_1 + d^{N-1}*i_2 + ... + i_N
-      //
-      // The corresponding index in the resulting contracted vector is
-      // res_idx = d^{N-1}*i_1 + ... + d^{N-k+1}*i_{k-1}
-      //             + d^{N-k}*i_{k+1} + ... + i_N
-      // The second line is just (res_idx mod d^{N-k}):
-      const std::size_t remainder = res_idx % step;
-      // To get to the starting index (i_k = 0) into our components from res_idx,
-      // we have to multiply the first line by d and add the second line:
-      std::size_t our_idx = (res_idx - remainder)*d + remainder;
-      for (unsigned int i = 0; i < d; ++i){
-        res += components[our_idx]*T2.components[i]*detail::metric_sign(i);
-        our_idx += step;
-      }
-    }
-    return result;
-  }
-
-  template <unsigned int N>
-  Tensor<N-1> Tensor<N>::contract(
-      CLHEP::HepLorentzVector const & p2, const int k
-  ) const {
-    return contract(to_tensor(p2), k);
-  }
-
-
-  template <unsigned int N>
-  Tensor<N> & Tensor<N>::complex_conj() {
-    for(auto & c: components) {
-      c = std::conj(c);
-    }
-    return *this;
-  }
-
-} // namespace HEJ
diff --git a/include/HEJ/detail/Unweighter_impl.hh b/include/HEJ/detail/Unweighter_impl.hh
index a8a44eb..619dd8e 100644
--- a/include/HEJ/detail/Unweighter_impl.hh
+++ b/include/HEJ/detail/Unweighter_impl.hh
@@ -1,147 +1,148 @@
 /** \file
  *  \brief Unweighter Class Implementation for template functions
  *
  *  \authors The HEJ collaboration (see AUTHORS for details)
  *  \date 2019
  *  \copyright GPLv2 or later
  */
 #pragma once
 
 #include "HEJ/Unweighter.hh"
 
 #include <limits>
 #include <utility>
 #include <cstdint>
 
 #include "HEJ/Event.hh"
 #include "HEJ/RNG.hh"
 
 namespace HEJ {
   namespace detail {
-    // primitive histogram class
-    // we don't want to do much with this,
-    // so try to avoid an external dependence like YODA or ROOT
+    //! primitive histogram class
+    //! @internal we don't want to do much with this,
+    //!           so try to avoid an external dependence like YODA or ROOT
     class Histogram {
     public:
       Histogram(size_t nbins, double min, double max):
         bins_(nbins),
         min_{min},
         width_{(max-min)/nbins}
       {}
 
       size_t bin_idx(double pos) {
         int const idx = std::abs((pos - min_)/width_);
         if(idx < 0) return 0;
         return std::min(static_cast<size_t>(idx), bins_.size()-1);
       }
 
       void fill(double pos, double val) {
         size_t const bin = bin_idx(pos);
         if(bin >= bins_.size()) return;
         bins_[bin] += val;
       }
 
       double operator[](size_t i) const {
         return bins_[i];
       }
       double & operator[](size_t i) {
         return bins_[i];
       }
 
       std::pair<double, double> peak_bin() const {
         const auto max = std::max_element(begin(bins_), end(bins_));
         if(max == end(bins_)) {
           static constexpr double nan = std::numeric_limits<double>::quiet_NaN();
           return std::make_pair(nan, nan);
         }
         const size_t nbin = std::distance(begin(bins_), max);
         return std::make_pair(min_ + (nbin + 0.5)*width_, *max);
       }
 
     private:
       std::vector<double> bins_;
       double min_, width_;
     };
 
+    //! Get minimal and maximal absolute weight
     template<class ConstIt>
     std::pair<double, double> awt_range(ConstIt begin, ConstIt end) {
       // can't use std::minmax_element
       // because boost filter iterators are not assignable
       assert(begin != end);
       double min = std::numeric_limits<double>::infinity();
       double max = 0.;
       for(auto it = begin; it != end; ++it) {
         const double awt = std::abs(it->central().weight);
         if(awt == 0) continue;
         if(awt < min) {
           min = awt;
         } else if (awt > max) {
           max = awt;
         }
       }
       return std::make_pair(min, max);
     }
   }
 
   template<class ConstIt>
   void Unweighter::set_cut_to_peakwt(ConstIt begin, ConstIt end, double max_dev){
     if(begin == end) {
       throw std::invalid_argument{"Empty range of events"};
     }
     double err = 0.;
     double awt_sum = 0.;
     const auto extremal_awts = detail::awt_range(begin, end);
     const double min_lwt = std::log(extremal_awts.first);
     const double max_lwt = std::log(extremal_awts.second);
     const size_t nevents = std::distance(begin, end);
     // heuristic value for number of bins
     const size_t nbins = std::sqrt(nevents);
     detail::Histogram wtwt{nbins, min_lwt, max_lwt};
     detail::Histogram nwt{nbins, min_lwt, max_lwt};
 
     for(auto it=begin; it != end; ++it){
       const double awt = std::abs(it->central().weight);
       nwt.fill(std::log(awt), 1.);
     }
 
     const auto cut = nevents/nbins;
     for(; begin != end; ++begin){
       const double awt = std::abs(begin->central().weight);
       const double log_wt = std::log(awt);
       const double bin_idx = nwt.bin_idx(log_wt);
       assert(bin_idx<nbins);
       assert(bin_idx>=0);
       // prune low statistics bins with a heuristic cut
       if(nwt[bin_idx] >= cut){
         wtwt.fill(log_wt, awt);
         const double tmp = awt*log_wt;
         err += tmp*tmp;
         awt_sum += awt;
       }
     }
 
     const auto peak = wtwt.peak_bin();
     err = std::sqrt(err)/awt_sum;
     set_cut(std::exp(peak.first + max_dev*err));
   }
 
   template<class ConstIt>
   void Unweighter::set_cut_to_maxwt(ConstIt begin, ConstIt end){
     set_cut(-1);
     for(; begin != end; ++begin){
       const double awt = std::abs(begin->central().weight);
       if( awt > get_cut())
         set_cut(awt);
     }
   }
   template<class Iterator>
   Iterator Unweighter::unweight(
     Iterator begin, Iterator end, RNG & ran
   ) const {
     if(get_cut() < 0) return end;
     return std::remove_if(begin, end,
                 [&](auto & ev) -> bool {
                     return this->discard(ran, ev);
                 });
   }
 } // namespace HEJ
diff --git a/src/LorentzVector.cc b/src/LorentzVector.cc
index 926381c..f1b1909 100644
--- a/src/LorentzVector.cc
+++ b/src/LorentzVector.cc
@@ -1,33 +1,68 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/LorentzVector.hh"
 
 #include <cmath>
 #include <utility>
 
 namespace HEJ {
+
+  namespace {
+    using COM = std::complex<double>;
+
+    COM perp(CLHEP::HepLorentzVector const & p) {
+      return COM{p.x(), p.y()};
+    }
+
+    COM perp_hat(CLHEP::HepLorentzVector const & p) {
+      const COM p_perp = perp(p);
+      if(p_perp == COM{0., 0.}) return -1.;
+      return p_perp/std::abs(p_perp);
+    }
+  } // anonymous namespace
+
+  // "angle" product angle(pi, pj) = <i j>
+  // see eq. (53) (\eqref{eq:angle_product}) in developer manual
+  COM angle(
+    CLHEP::HepLorentzVector const & pi,
+    CLHEP::HepLorentzVector const & pj
+  ) {
+    return
+      + std::sqrt(pi.minus()*pj.plus())*perp_hat(pi)
+      - std::sqrt(pi.plus()*pj.minus())*perp_hat(pj);
+  }
+
+  // "square" product square(pi, pj) = [i j]
+  // see eq. (54) (\eqref{eq:square_product}) in developer manual
+  COM square(
+    CLHEP::HepLorentzVector const & pi,
+    CLHEP::HepLorentzVector const & pj
+  ) {
+    return -std::conj(angle(pi, pj));
+  }
+
   // see eq:P_massive, eq:P_massive_p, eq:P_massive_q, , eq:P_massive_plus,
   // eq:P_massive_minus in the developer manual
   std::pair<CLHEP::HepLorentzVector, CLHEP::HepLorentzVector>
   split_into_lightlike(CLHEP::HepLorentzVector const & P) {
     const double pt = P.perp();
     if(P.plus() > 0){
       const double y = std::log(P.plus()/pt);
       const double E = P.m2()/(2.*P.plus());
       return std::make_pair(
         CLHEP::HepLorentzVector{P.x(), P.y(), pt*std::sinh(y), pt*std::cosh(y)},
         CLHEP::HepLorentzVector{0., 0., -E, E}
       );
     } else {
       const double y = std::log(pt/P.minus());
       const double E = P.m2()/(2.*P.minus());
       return std::make_pair(
         CLHEP::HepLorentzVector{P.x(), P.y(), pt*std::sinh(y), pt*std::cosh(y)},
         CLHEP::HepLorentzVector{0., 0., +E, E}
       );
     }
   }
 }
diff --git a/src/Tensor.cc b/src/Tensor.cc
deleted file mode 100644
index 69d8d75..0000000
--- a/src/Tensor.cc
+++ /dev/null
@@ -1,691 +0,0 @@
-/**
- *  \authors   The HEJ collaboration (see AUTHORS for details)
- *  \date      2019
- *  \copyright GPLv2 or later
- */
-#include "HEJ/Tensor.hh"
-
-#include <array>
-#include <iostream>
-#include <valarray>
-#include <vector>
-
-#include <CLHEP/Vector/LorentzVector.h>
-
-namespace HEJ {
-  namespace{
-    // Tensor Template definitions
-    short int sigma_index5[1024];
-    short int sigma_index3[64];
-    std::valarray<COM> permfactor5;
-    std::valarray<COM> permfactor3;
-    short int helfactor5[2][1024];
-    short int helfactor3[2][64];
-
-    // map from a list of tensor lorentz indices onto a single index
-    // in d dimensional spacetime
-    // TODO: basically the same as detail::accumulate_idx
-    template<std::size_t N>
-      std::size_t tensor_to_list_idx(std::array<int, N> const & indices) {
-      std::size_t list_idx = 0;
-      for(std::size_t i = 1, factor = 1; i <= N; ++i) {
-        list_idx += factor*indices[N-i];
-        factor *= detail::d;
-      }
-#ifndef NDEBUG
-      constexpr std::size_t max_possible_idx = detail::power(detail::d, N);
-      assert(list_idx < max_possible_idx);
-#endif
-      return list_idx;
-    }
-
-    // generate all unique perms of vectors {a,a,a,a,b}, return in perms
-    // set_permfactor is a bool which encodes the anticommutation relations of the
-    // sigma matrices namely if we have sigma0, set_permfactor= false because it
-    // commutes with all others otherwise we need to assign a minus sign to odd
-    // permutations, set in permfactor
-    // note, inital perm is always even
-    void perms41(int same4, int diff, std::vector<std::array<int,5>> & perms){
-      bool set_permfactor(true);
-      if(same4==0||diff==0)
-        set_permfactor=false;
-
-      for(int diffpos=0;diffpos<5;++diffpos){
-        std::array<int,5> tempvec={same4,same4,same4,same4,same4};
-        tempvec[diffpos]=diff;
-        perms.push_back(tempvec);
-        if(set_permfactor){
-          if(diffpos%2==1)
-            permfactor5[tensor_to_list_idx(tempvec)]=-1.;
-        }
-      }
-    }
-
-    // generate all unique perms of vectors {a,a,a,b,b}, return in perms
-    // note, inital perm is always even
-    void perms32(int same3, int diff, std::vector<std::array<int,5>> & perms){
-      bool set_permfactor(true);
-      if(same3==0||diff==0)
-        set_permfactor=false;
-
-      for(int diffpos1=0;diffpos1<5;++diffpos1){
-        for(int diffpos2=diffpos1+1;diffpos2<5;++diffpos2){
-          std::array<int,5> tempvec={same3,same3,same3,same3,same3};
-          tempvec[diffpos1]=diff;
-          tempvec[diffpos2]=diff;
-          perms.push_back(tempvec);
-          if(set_permfactor){
-            if((diffpos2-diffpos1)%2==0)
-              permfactor5[tensor_to_list_idx(tempvec)]=-1.;
-          }
-        }
-      }
-    }
-
-    // generate all unique perms of vectors {a,a,a,b,c}, return in perms
-    // we have two bools since there are three distinct type of sigma matrices to
-    // permute, so need to test if the 3xrepeated = sigma0 or if one of the
-    // singles is sigma0
-    // if diff1/diff2 are distinct, flipping them results in a change of perm,
-    // otherwise it'll be symmetric under flip -> encode this in set_permfactor2
-    // as long as diff2!=0 can ensure inital perm is even
-    // if diff2=0 then it is not possible to ensure inital perm even -> encode in
-    // bool signflip
-    void perms311(int same3, int diff1, int diff2,
-                  std::vector<std::array<int,5>> & perms
-    ){
-      bool set_permfactor2(true);
-      bool same0(false);
-      bool diff0(false);
-      bool signflip(false); // if true, inital perm is odd
-
-      if(same3==0) // is the repeated matrix sigma0?
-        same0 = true;
-      else if(diff2==0){ // is one of the single matrices sigma0
-        diff0=true;
-        if((diff1%3-same3)!=-1)
-          signflip=true;
-      } else if(diff1==0){
-        std::cerr<<"Note, only first and last argument may be zero"<<std::endl;
-        return;
-      }
-
-      // possible outcomes: tt, ft, tf
-
-      for(int diffpos1=0;diffpos1<5;++diffpos1){
-        for(int diffpos2=diffpos1+1;diffpos2<5;++diffpos2){
-          std::array<int,5> tempvec={same3,same3,same3,same3,same3};
-          tempvec[diffpos1]=diff1;
-          tempvec[diffpos2]=diff2;
-          perms.push_back(tempvec);
-
-          if(!same0 && !diff0){
-            // full antisymmetric under exchange of same3,diff1,diff2
-            if((diffpos2-diffpos1)%2==0){
-              permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1); // odd perm
-              // if this perm is odd, swapping diff1<->diff2 automatically even
-              set_permfactor2=false;
-            } else {
-              permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1); // even perm
-              // if this perm is even, swapping diff1<->diff2 automatically odd
-              set_permfactor2=true;
-            }
-          } else if(diff0){// one of the single matrices is sigma0
-            if(signflip){ // initial config is odd!
-              if(diffpos1%2==1){
-                permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1); // even perm
-                // initally symmetric under diff1<->diff2 =>
-                // if this perm is even, automatically even for first diffpos2
-                set_permfactor2=false;
-              } else {
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1); // odd perm
-                // initally symmetric under diff1<->diff2 =>
-                // if this perm is odd, automatically odd for first diffpos2
-                set_permfactor2=true;
-              }
-            } else {// diff0=true, initial config is even
-              if(diffpos1%2==1){
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1); // odd perm
-                // initally symmetric under diff1<->diff2 =>
-                // if this perm is odd, automatically odd for first diffpos2
-                set_permfactor2=true;
-              } else {
-                permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1); // even perm
-                // initally symmetric under diff1<->diff2 =>
-                // if this perm is even, automatically even for first diffpos2
-                set_permfactor2=false;
-              }
-            }
-            if((diffpos2-diffpos1-1)%2==1)
-              set_permfactor2=!set_permfactor2; // change to account for diffpos2
-          } else if(same0){
-            // the repeated matrix is sigma0
-            // => only relative positions of diff1, diff2 matter.
-            // always even before flip  because diff1pos<diff2pos
-            permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1);
-            // if this perm is odd, swapping diff1<->diff2 automatically odd
-            set_permfactor2=true;
-          }
-
-          tempvec[diffpos1]=diff2;
-          tempvec[diffpos2]=diff1;
-          perms.push_back(tempvec);
-
-          if(set_permfactor2)
-            permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1);
-          else
-            permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1);
-
-        }
-      }
-    } // end perms311
-
-    // generate all unique perms of vectors {a,a,b,b,c}, return in perms
-    void perms221(int same2a, int same2b, int diff,
-                  std::vector<std::array<int,5>> & perms
-    ){
-      bool set_permfactor1(true);
-      bool set_permfactor2(true);
-      if(same2b==0){
-        std::cerr<<"second entry in perms221() shouldn't be zero" <<std::endl;
-        return;
-      } else if(same2a==0)
-        set_permfactor1=false;
-      else if(diff==0)
-        set_permfactor2=false;
-
-      for(int samepos=0;samepos<5;++samepos){
-        int permcount = 0;
-        for(int samepos2=samepos+1;samepos2<5;++samepos2){
-          for(int diffpos=0;diffpos<5;++diffpos){
-            if(diffpos==samepos||diffpos==samepos2) continue;
-
-            std::array<int,5> tempvec={same2a,same2a,same2a,same2a,same2a};
-            tempvec[samepos]=same2b;
-            tempvec[samepos2]=same2b;
-            tempvec[diffpos]=diff;
-            perms.push_back(tempvec);
-
-            if(set_permfactor1){
-              if(set_permfactor2){// full anti-symmetry
-                if(permcount%2==1)
-                  permfactor5[tensor_to_list_idx(tempvec)]=-1.;
-              } else { // diff is sigma0
-                if( ((samepos2-samepos-1)%3>0)
-                    && (abs(abs(samepos2-diffpos)-abs(samepos-diffpos))%3>0) )
-                  permfactor5[tensor_to_list_idx(tempvec)]=-1.;
-              }
-            } else { // same2a is sigma0
-              if((diffpos>samepos) && (diffpos<samepos2))
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.;
-            }
-            ++permcount;
-          }
-        }
-      }
-    }
-
-    // generate all unique perms of vectors {a,a,b,b,c}, return in perms
-    // there must be a sigma zero if we have 4 different matrices in string
-    // bool is true if sigma0 is the repeated matrix
-    void perms2111(int same2, int diff1,int diff2,int diff3,
-                   std::vector<std::array<int,5>> & perms
-    ){
-      bool twozero(false);
-      if(same2==0)
-        twozero=true;
-      else if (diff1!=0){
-        std::cerr<<"One of first or second argurments must be a zero"<<std::endl;
-        return;
-      } else if(diff2==0|| diff3==0){
-        std::cerr<<"Only first and second arguments may be a zero."<<std::endl;
-        return;
-      }
-
-      int permcount = 0;
-
-      for(int diffpos1=0;diffpos1<5;++diffpos1){
-        for(int diffpos2=0;diffpos2<5;++diffpos2){
-          if(diffpos2==diffpos1) continue;
-          for(int diffpos3=0;diffpos3<5;++diffpos3){
-            if(diffpos3==diffpos2||diffpos3==diffpos1) continue;
-
-            std::array<int,5> tempvec={same2,same2,same2,same2,same2};
-            tempvec[diffpos1]=diff1;
-            tempvec[diffpos2]=diff2;
-            tempvec[diffpos3]=diff3;
-            perms.push_back(tempvec);
-
-            if(twozero){// don't care about exact positions of singles, just order
-              if(diffpos2>diffpos3 && diffpos3>diffpos1)
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1);// odd
-              else if(diffpos1>diffpos2 && diffpos2>diffpos3)
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1);// odd
-              else if(diffpos3>diffpos1 && diffpos1>diffpos2)
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1);// odd
-              else
-                permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1);// evwn
-            } else {
-              if(permcount%2==1)
-                permfactor5[tensor_to_list_idx(tempvec)]=-1.*COM(0,1);
-              else
-                permfactor5[tensor_to_list_idx(tempvec)]=COM(0,1);
-            }
-            ++permcount;
-          }
-        }
-      }
-    }
-
-    void perms21(int same, int diff, std::vector<std::array<int,3>> & perms){
-
-      bool set_permfactor(true);
-      if(same==0||diff==0)
-        set_permfactor=false;
-
-      for(int diffpos=0; diffpos<3;++diffpos){
-        std::array<int,3> tempvec={same,same,same};
-        tempvec[diffpos]=diff;
-        perms.push_back(tempvec);
-        if(set_permfactor && diffpos==1)
-          permfactor3[tensor_to_list_idx(tempvec)]=-1.;
-      }
-    }
-
-    void perms111(int diff1, int diff2, int diff3,
-                  std::vector<std::array<int,3>> & perms
-    ){
-      bool sig_zero(false);
-      if(diff1==0)
-        sig_zero=true;
-      else if(diff2==0||diff3==0){
-        std::cerr<<"Only first argument may be a zero."<<std::endl;
-        return;
-      }
-      int permcount=0;
-      for(int pos1=0;pos1<3;++pos1){
-        for(int pos2=pos1+1;pos2<3;++pos2){
-          std::array<int,3> tempvec={diff1,diff1,diff1};
-          tempvec[pos1]=diff2;
-          tempvec[pos2]=diff3;
-          perms.push_back(tempvec);
-          if(sig_zero){
-            permfactor3[tensor_to_list_idx(tempvec)]=1.*COM(0,1); // even
-          } else if(permcount%2==1){
-            permfactor3[tensor_to_list_idx(tempvec)]=-1.*COM(0,1); // odd
-          } else {
-            permfactor3[tensor_to_list_idx(tempvec)]=1.*COM(0,1); // even
-          }
-
-          tempvec[pos1]=diff3;
-          tempvec[pos2]=diff2;
-          perms.push_back(tempvec);
-
-          if(sig_zero){
-            permfactor3[tensor_to_list_idx(tempvec)]=-1.*COM(0,1); // odd
-          } else if(permcount%2==1){
-            permfactor3[tensor_to_list_idx(tempvec)]=1.*COM(0,1); // even
-          } else {
-            permfactor3[tensor_to_list_idx(tempvec)]=-1.*COM(0,1); // odd
-          }
-          ++permcount;
-        }
-      }
-    }
-
-    COM perp(CLHEP::HepLorentzVector const & p) {
-      return COM{p.x(), p.y()};
-    }
-
-    COM perp_hat(CLHEP::HepLorentzVector const & p) {
-      const COM p_perp = perp(p);
-      if(p_perp == COM{0., 0.}) return -1.;
-      return p_perp/std::abs(p_perp);
-    }
-
-  } // anonymous namespace
-
-    // "angle" product angle(pi, pj) = <i j>
-    // see eq. (53) (\eqref{eq:angle_product}) in developer manual
-  COM angle(CLHEP::HepLorentzVector const & pi, CLHEP::HepLorentzVector const & pj) {
-    return
-      + std::sqrt(COM{pi.minus()*pj.plus()})*perp_hat(pi)
-      - std::sqrt(COM{pi.plus()*pj.minus()})*perp_hat(pj);
-  }
-
-  // "square" product square(pi, pj) = [i j]
-  // see eq. (54) (\eqref{eq:square_product}) in developer manual
-  COM square(CLHEP::HepLorentzVector const & pi, CLHEP::HepLorentzVector const & pj) {
-    return -std::conj(angle(pi, pj));
-  }
-
-  Tensor<2> metric(){
-    static const Tensor<2> g = [](){
-      Tensor<2> g(0.);
-      g(0,0) = 1.;
-      g(1,1) = -1.;
-      g(2,2) = -1.;
-      g(3,3) = -1.;
-      return g;
-    }();
-    return g;
-  }
-
-  // <1|mu|2>
-  // see eqs. (48), (49) in developer manual
-  Tensor<1> current(
-      CLHEP::HepLorentzVector const & p,
-      CLHEP::HepLorentzVector const & q,
-      Helicity h
-  ){
-    using namespace helicity;
-    const COM p_perp_hat = perp_hat(p);
-    const COM q_perp_hat = perp_hat(q);
-    std::array<std::array<COM, 2>, 2> sqrt_pq;
-    sqrt_pq[minus][minus] = std::sqrt(COM{p.minus()*q.minus()})*p_perp_hat*conj(q_perp_hat);
-    sqrt_pq[minus][plus] = std::sqrt(COM{p.minus()*q.plus()})*p_perp_hat;
-    sqrt_pq[plus][minus] = std::sqrt(COM{p.plus()*q.minus()})*conj(q_perp_hat);
-    sqrt_pq[plus][plus] = std::sqrt(COM{p.plus()*q.plus()});
-    Tensor<1> j;
-    j(0) = sqrt_pq[plus][plus] + sqrt_pq[minus][minus];
-    j(1) = sqrt_pq[plus][minus] + sqrt_pq[minus][plus];
-    j(2) = COM{0., 1.}*(sqrt_pq[plus][minus] - sqrt_pq[minus][plus]);
-    j(3) = sqrt_pq[plus][plus] - sqrt_pq[minus][minus];
-
-    return (h == minus)?j:j.complex_conj();
-  }
-
-  // <1|mu nu sigma|2>
-  // TODO: how does this work?
-  Tensor<3> rank3_current(
-      CLHEP::HepLorentzVector const & p1,
-      CLHEP::HepLorentzVector const & p2,
-      Helicity h
-  ){
-    const CLHEP::HepLorentzVector p1new{ p1.e()<0?-p1:p1 };
-    const CLHEP::HepLorentzVector p2new{ p2.e()<0?-p2:p2 };
-
-    auto j = HEJ::current(p1new, p2new, h);
-    if(h == helicity::minus) {
-      j *= -1;
-      j(0) *= -1;
-    }
-
-    Tensor<3> j3;
-    for(std::size_t tensor_idx=0; tensor_idx < j3.size(); ++tensor_idx){
-      const double hfact = helfactor3[h][tensor_idx];
-      j3.components[tensor_idx] = j(sigma_index3[tensor_idx]) * hfact
-        * permfactor3[tensor_idx];
-    }
-
-    return j3;
-  }
-
-  // <1|mu nu sigma tau rho|2>
-  Tensor<5> rank5_current(
-      CLHEP::HepLorentzVector const & p1,
-      CLHEP::HepLorentzVector const & p2,
-      Helicity h
-  ){
-    const CLHEP::HepLorentzVector p1new{ p1.e()<0?-p1:p1 };
-    const CLHEP::HepLorentzVector p2new{ p2.e()<0?-p2:p2 };
-
-    auto j = HEJ::current(p1new, p2new, h);
-    if(h == helicity::minus) {
-      j *= -1;
-      j(0) *= -1;
-    }
-
-    Tensor<5> j5;
-    for(std::size_t tensor_idx=0; tensor_idx < j5.size(); ++tensor_idx){
-      const double hfact = helfactor5[h][tensor_idx];
-      j5.components[tensor_idx] = j(sigma_index5[tensor_idx]) * hfact
-        * permfactor5[tensor_idx];
-    }
-
-    return j5;
-  }
-
-  Tensor<1> to_tensor(CLHEP::HepLorentzVector const & p){
-    Tensor<1> newT;
-    newT.components={p.e(),p.x(),p.y(),p.z()};
-    return newT;
-  }
-
-  // polarisation vector according to eq. (55) in developer manual
-  Tensor<1> eps(
-      CLHEP::HepLorentzVector const & pg,
-      CLHEP::HepLorentzVector const & pr,
-      Helicity pol
-  ){
-    if(pol == helicity::plus) {
-      return current(pr, pg, pol)/(std::sqrt(2)*square(pg, pr));
-    }
-    return current(pr, pg, pol)/(std::sqrt(2)*angle(pg, pr));
-  }
-
-  namespace {
-    // slow function! - but only need to evaluate once.
-    bool init_sigma_index_helper(){
-      // initialize permfactor(3) to list of ones (change to minus one for each odd
-      // permutation and multiply by i for all permutations in perms2111, perms311,
-      // perms111)
-      permfactor5.resize(1024,1.);
-      permfactor3.resize(64,1.);
-
-      // first set sigma_index (5)
-      std::vector<std::array<int,5>> sigma0indices;
-      std::vector<std::array<int,5>> sigma1indices;
-      std::vector<std::array<int,5>> sigma2indices;
-      std::vector<std::array<int,5>> sigma3indices;
-
-      // need to generate all possible permuations of {i,j,k,l,m}
-      // where each index can be {0,1,2,3,4}
-      // 1024 possibilities
-
-      // perms with 5 same
-      sigma0indices.push_back({0,0,0,0,0});
-      sigma1indices.push_back({1,1,1,1,1});
-      sigma2indices.push_back({2,2,2,2,2});
-      sigma3indices.push_back({3,3,3,3,3});
-
-      // perms with 4 same
-      perms41(1,0,sigma0indices);
-      perms41(2,0,sigma0indices);
-      perms41(3,0,sigma0indices);
-      perms41(0,1,sigma1indices);
-      perms41(2,1,sigma1indices);
-      perms41(3,1,sigma1indices);
-      perms41(0,2,sigma2indices);
-      perms41(1,2,sigma2indices);
-      perms41(3,2,sigma2indices);
-      perms41(0,3,sigma3indices);
-      perms41(1,3,sigma3indices);
-      perms41(2,3,sigma3indices);
-
-      // perms with 3 same, 2 same
-      perms32(0,1,sigma0indices);
-      perms32(0,2,sigma0indices);
-      perms32(0,3,sigma0indices);
-      perms32(1,0,sigma1indices);
-      perms32(1,2,sigma1indices);
-      perms32(1,3,sigma1indices);
-      perms32(2,0,sigma2indices);
-      perms32(2,1,sigma2indices);
-      perms32(2,3,sigma2indices);
-      perms32(3,0,sigma3indices);
-      perms32(3,1,sigma3indices);
-      perms32(3,2,sigma3indices);
-
-      // perms with 3 same, 2 different
-      perms311(1,2,3,sigma0indices);
-      perms311(2,3,1,sigma0indices);
-      perms311(3,1,2,sigma0indices);
-      perms311(0,2,3,sigma1indices);
-      perms311(2,3,0,sigma1indices);
-      perms311(3,2,0,sigma1indices);
-      perms311(0,3,1,sigma2indices);
-      perms311(1,3,0,sigma2indices);
-      perms311(3,1,0,sigma2indices);
-      perms311(0,1,2,sigma3indices);
-      perms311(1,2,0,sigma3indices);
-      perms311(2,1,0,sigma3indices);
-
-      perms221(1,2,0,sigma0indices);
-      perms221(1,3,0,sigma0indices);
-      perms221(2,3,0,sigma0indices);
-      perms221(0,2,1,sigma1indices);
-      perms221(0,3,1,sigma1indices);
-      perms221(2,3,1,sigma1indices);
-      perms221(0,1,2,sigma2indices);
-      perms221(0,3,2,sigma2indices);
-      perms221(1,3,2,sigma2indices);
-      perms221(0,1,3,sigma3indices);
-      perms221(0,2,3,sigma3indices);
-      perms221(1,2,3,sigma3indices);
-
-      perms2111(0,1,2,3,sigma0indices);
-      perms2111(1,0,2,3,sigma1indices);
-      perms2111(2,0,3,1,sigma2indices);
-      perms2111(3,0,1,2,sigma3indices);
-
-      if(sigma0indices.size()!=256){
-        std::cerr<<"sigma_index not set: ";
-        std::cerr<<"sigma0indices has "<< sigma0indices.size() << " components" << std::endl;
-        return false;
-      } else if(sigma1indices.size()!=256){
-        std::cerr<<"sigma_index not set: ";
-        std::cerr<<"sigma1indices has "<< sigma0indices.size() << " components" << std::endl;
-        return false;
-      } else if(sigma2indices.size()!=256){
-        std::cerr<<"sigma_index not set: ";
-        std::cerr<<"sigma2indices has "<< sigma0indices.size() << " components" << std::endl;
-        return false;
-      } else if(sigma3indices.size()!=256){
-        std::cerr<<"sigma_index not set: ";
-        std::cerr<<"sigma3indices has "<< sigma0indices.size() << " components" << std::endl;
-        return false;
-      }
-
-      for(int i=0;i<256;++i){
-        // map each unique set of tensor indices to its position in a list
-        int index0 = tensor_to_list_idx(sigma0indices.at(i));
-        int index1 = tensor_to_list_idx(sigma1indices.at(i));
-        int index2 = tensor_to_list_idx(sigma2indices.at(i));
-        int index3 = tensor_to_list_idx(sigma3indices.at(i));
-        sigma_index5[index0]=0;
-        sigma_index5[index1]=1;
-        sigma_index5[index2]=2;
-        sigma_index5[index3]=3;
-
-        short int sign[4]={1,-1,-1,-1};
-        // plus->true->1
-        helfactor5[1][index0] = sign[sigma0indices.at(i)[1]]
-          * sign[sigma0indices.at(i)[3]];
-        helfactor5[1][index1] = sign[sigma1indices.at(i)[1]]
-          * sign[sigma1indices.at(i)[3]];
-        helfactor5[1][index2] = sign[sigma2indices.at(i)[1]]
-          * sign[sigma2indices.at(i)[3]];
-        helfactor5[1][index3] = sign[sigma3indices.at(i)[1]]
-          * sign[sigma3indices.at(i)[3]];
-        // minus->false->0
-        helfactor5[0][index0] = sign[sigma0indices.at(i)[0]]
-          * sign[sigma0indices.at(i)[2]]
-          * sign[sigma0indices.at(i)[4]];
-        helfactor5[0][index1] = sign[sigma1indices.at(i)[0]]
-          * sign[sigma1indices.at(i)[2]]
-          * sign[sigma1indices.at(i)[4]];
-        helfactor5[0][index2] = sign[sigma2indices.at(i)[0]]
-          * sign[sigma2indices.at(i)[2]]
-          * sign[sigma2indices.at(i)[4]];
-        helfactor5[0][index3] = sign[sigma3indices.at(i)[0]]
-          * sign[sigma3indices.at(i)[2]]
-          * sign[sigma3indices.at(i)[4]];
-      }
-
-      // now set sigma_index3
-      std::vector<std::array<int,3>> sigma0indices3;
-      std::vector<std::array<int,3>> sigma1indices3;
-      std::vector<std::array<int,3>> sigma2indices3;
-      std::vector<std::array<int,3>> sigma3indices3;
-
-      // perms with 3 same
-      sigma0indices3.push_back({0,0,0});
-      sigma1indices3.push_back({1,1,1});
-      sigma2indices3.push_back({2,2,2});
-      sigma3indices3.push_back({3,3,3});
-
-      // 2 same
-      perms21(1,0,sigma0indices3);
-      perms21(2,0,sigma0indices3);
-      perms21(3,0,sigma0indices3);
-      perms21(0,1,sigma1indices3);
-      perms21(2,1,sigma1indices3);
-      perms21(3,1,sigma1indices3);
-      perms21(0,2,sigma2indices3);
-      perms21(1,2,sigma2indices3);
-      perms21(3,2,sigma2indices3);
-      perms21(0,3,sigma3indices3);
-      perms21(1,3,sigma3indices3);
-      perms21(2,3,sigma3indices3);
-
-      // none same
-      perms111(1,2,3,sigma0indices3);
-      perms111(0,2,3,sigma1indices3);
-      perms111(0,3,1,sigma2indices3);
-      perms111(0,1,2,sigma3indices3);
-
-      if(sigma0indices3.size()!=16){
-        std::cerr<<"sigma_index3 not set: ";
-        std::cerr<<"sigma0indices3 has "<< sigma0indices3.size() << " components" << std::endl;
-        return false;
-      } else if(sigma1indices3.size()!=16){
-        std::cerr<<"sigma_index3 not set: ";
-        std::cerr<<"sigma1indices3 has "<< sigma0indices3.size() << " components" << std::endl;
-        return false;
-      } else if(sigma2indices3.size()!=16){
-        std::cerr<<"sigma_index3 not set: ";
-        std::cerr<<"sigma2indices3 has "<< sigma0indices3.size() << " components" << std::endl;
-        return false;
-      } else if(sigma3indices3.size()!=16){
-        std::cerr<<"sigma_index3 not set: ";
-        std::cerr<<"sigma3indices3 has "<< sigma0indices3.size() << " components" << std::endl;
-        return false;
-      }
-
-      for(int i=0;i<16;++i){
-        int index0 = tensor_to_list_idx(sigma0indices3.at(i));
-        int index1 = tensor_to_list_idx(sigma1indices3.at(i));
-        int index2 = tensor_to_list_idx(sigma2indices3.at(i));
-        int index3 = tensor_to_list_idx(sigma3indices3.at(i));
-        sigma_index3[index0]=0;
-        sigma_index3[index1]=1;
-        sigma_index3[index2]=2;
-        sigma_index3[index3]=3;
-
-        short int sign[4]={1,-1,-1,-1};
-        // plus->true->1
-        helfactor3[1][index0] = sign[sigma0indices3.at(i)[1]];
-        helfactor3[1][index1] = sign[sigma1indices3.at(i)[1]];
-        helfactor3[1][index2] = sign[sigma2indices3.at(i)[1]];
-        helfactor3[1][index3] = sign[sigma3indices3.at(i)[1]];
-        // minus->false->0
-        helfactor3[0][index0] = sign[sigma0indices3.at(i)[0]]
-          * sign[sigma0indices3.at(i)[2]];
-        helfactor3[0][index1] = sign[sigma1indices3.at(i)[0]]
-          * sign[sigma1indices3.at(i)[2]];
-        helfactor3[0][index2] = sign[sigma2indices3.at(i)[0]]
-          * sign[sigma2indices3.at(i)[2]];
-        helfactor3[0][index3] = sign[sigma3indices3.at(i)[0]]
-          * sign[sigma3indices3.at(i)[2]];
-      }
-      return true;
-    } // end init_sigma_index
-  }
-
-  void init_sigma_index(){
-    static const bool initialised = init_sigma_index_helper();
-    (void) initialised; // shut up compiler warnings
-  }
-
-}
diff --git a/src/Wjets.cc b/src/Wjets.cc
index dd8f7ba..214571f 100644
--- a/src/Wjets.cc
+++ b/src/Wjets.cc
@@ -1,735 +1,736 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2020
  *  \copyright GPLv2 or later
  */
 #include "HEJ/Wjets.hh"
 
+#include <algorithm>
 #include <array>
+#include <cassert>
 #include <iostream>
 
 #include "HEJ/Constants.hh"
 #include "HEJ/EWConstants.hh"
 #include "HEJ/jets.hh"
-#include "HEJ/Tensor.hh"
 #include "HEJ/LorentzVector.hh"
 #include "HEJ/exceptions.hh"
 
 // generated headers
 #include "HEJ/currents/jW_j.hh"
 #include "HEJ/currents/jWuno_j.hh"
 #include "HEJ/currents/jWqqbar_j.hh"
 #include "HEJ/currents/jW_qqbar_j.hh"
 #include "HEJ/currents/j_Wqqbar_j.hh"
 #include "HEJ/currents/jW_jqqbar.hh"
 #include "HEJ/currents/jW_juno.hh"
 
 using HEJ::Helicity;
 using HEJ::ParticleProperties;
 
 namespace helicity = HEJ::helicity;
 
 namespace { // Helper Functions
   // FKL W Helper Functions
   double WProp (const HLV & plbar, const HLV & pl, ParticleProperties const & wprop){
     COM propW = COM(0.,-1.)/( (pl+plbar).m2() - wprop.mass*wprop.mass
                             + COM(0.,1.)*wprop.mass*wprop.width);
     double PropFactor=(propW*conj(propW)).real();
     return PropFactor;
   }
 
   /**
    * @brief  Contraction of W + unordered jet current with FKL current
    *
    * See eq:wunocurrent in the developer manual for the definition
    * of the W + unordered jet current
    */
   template<Helicity h1, Helicity h2, Helicity pol>
   double jM2Wuno(
       HLV const & pg, HLV const & p1, HLV const & plbar, HLV const & pl,
       HLV const & pa, HLV const & p2, HLV const & pb,
       ParticleProperties const & wprop
   ){
     using HEJ::C_A;
     using HEJ::C_F;
 
     const COM U1 = HEJ::U1<h1, h2, pol>(p1, p2, pa, pb, pg, pl, plbar);
     const COM U2 = HEJ::U2<h1, h2, pol>(p1, p2, pa, pb, pg, pl, plbar);
     const COM L = HEJ::L<h1, h2, pol>(p1, p2, pa, pb, pg, pl, plbar);
 
     const COM X = U1 - L;
     const COM Y = U2 + L;
 
     double amp = C_A*C_F*C_F/2.*(norm(X)+norm(Y)) - HEJ::C_F/2.*(X*conj(Y)).real();
     amp *= WProp(plbar, pl, wprop);
 
     const HLV q1g = pa-pl-plbar-p1-pg;
     const HLV q2 = p2-pb;
 
     const double t1 = q1g.m2();
     const double t2 = q2.m2();
 
     amp /= (t1*t2);
     return amp;
   }
 
   /**
    * @brief Contraction of W + extremal qqbar current with FKL current
    *
    * See eq:crossed in the developer manual for the definition
    * of the W + extremal qqbar current.
    *
    */
   template<Helicity h1, Helicity h2, Helicity hg>
   double jM2_W_extremal_qqbar(
     HLV const & pg, HLV const & pq, HLV const & plbar, HLV const & pl,
     HLV const & pqbar, HLV const & p3, HLV const & pb,
     ParticleProperties const & wprop
   ){
     using HEJ::C_A;
     using HEJ::C_F;
 
     const COM U1Xcontr = HEJ::U1X<h1, h2, hg>(pg, pq, plbar, pl, pqbar, p3, pb);
     const COM U2Xcontr = HEJ::U2X<h1, h2, hg>(pg, pq, plbar, pl, pqbar, p3, pb);
     const COM LXcontr = HEJ::LX<h1, h2, hg>(pg, pq, plbar, pl, pqbar, p3, pb);
 
     const COM X = U1Xcontr - LXcontr;
     const COM Y = U2Xcontr + LXcontr;
 
     double amp = C_A*C_F*C_F/2.*(norm(X)+norm(Y)) - HEJ::C_F/2.*(X*conj(Y)).real();
     amp *= WProp(plbar, pl, wprop);
 
     const HLV q1 = pg-pl-plbar-pq-pqbar;
     const HLV q2 = p3-pb;
 
     const double t1 = q1.m2();
     const double t2 = q2.m2();
 
     amp /= (t1*t2);
     return amp;
   }
 
 //! W+Jets FKL Contributions
 /**
  * @brief W+Jets FKL Contributions, function to handle all incoming types.
  * @param p1out             Outgoing Particle 1. (W emission)
  * @param plbar             Outgoing election momenta
  * @param pl                Outgoing neutrino momenta
  * @param p1in              Incoming Particle 1. (W emission)
  * @param p2out             Outgoing Particle 2
  * @param p2in              Incoming Particle 2
  * @param aqlineb           Bool. Is Backwards quark line an anti-quark line?
  * @param aqlinef           Bool. Is Forwards quark line an anti-quark line?
  *
  * Calculates j_W ^\mu    j_\mu.
  * Handles all possible incoming states.
  */
   double jW_j( HLV p1out, HLV plbar, HLV pl, HLV p1in, HLV p2out, HLV p2in,
                bool aqlineb, bool /* aqlinef */,
                ParticleProperties const & wprop
     ){
     using helicity::minus;
     using helicity::plus;
     const HLV q1=p1in-p1out-plbar-pl;
     const HLV q2=-(p2in-p2out);
 
     const double WPropfact = WProp(plbar, pl, wprop);
 
     // we assume that the W is emitted off a quark line
     // if this is not the case, we have to apply CP conjugation,
     // which is equivalent to swapping lepton and antilepton momenta
     if(aqlineb) std::swap(pl, plbar);
 
     const COM ampm = HEJ::jW_j<minus>(p1in,p1out,p2in,p2out,pl,plbar);
     const COM ampp = HEJ::jW_j<plus>(p1in,p1out,p2in,p2out,pl,plbar);
 
     const double Msqr = std::norm(ampm) + std::norm(ampp);
 
     // Division by colour and Helicity average (Nc2-1)(4)
     // Multiply by Cf^2
     return HEJ::C_F*HEJ::C_F*WPropfact*Msqr/(q1.m2()*q2.m2()*(HEJ::N_C*HEJ::N_C - 1)*4);
   }
 } // Anonymous Namespace
 
 double ME_W_qQ (HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out, HLV p2in,
    ParticleProperties const & wprop
 ){
   return jW_j(p1out, plbar, pl, p1in, p2out, p2in, false, false, wprop);
 }
 
 double ME_W_qQbar (HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out, HLV p2in,
    ParticleProperties const & wprop
 ){
   return jW_j(p1out, plbar, pl, p1in, p2out, p2in, false, true, wprop);
 }
 
 double ME_W_qbarQ (HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out, HLV p2in,
    ParticleProperties const & wprop
 ){
   return jW_j(p1out, plbar, pl, p1in, p2out, p2in, true, false, wprop);
 }
 
 double ME_W_qbarQbar (HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out, HLV p2in,
    ParticleProperties const & wprop
 ){
   return jW_j(p1out, plbar, pl, p1in, p2out, p2in, true, true, wprop);
 }
 
 double ME_W_qg (HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out, HLV p2in,
    ParticleProperties const & wprop
 ){
   return jW_j(p1out, plbar, pl, p1in, p2out, p2in, false, false, wprop)
           *K_g(p2out, p2in)/HEJ::C_F;
 }
 
 double ME_W_qbarg (HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out, HLV p2in,
    ParticleProperties const & wprop
 ){
   return jW_j(p1out, plbar, pl, p1in, p2out, p2in, true, false, wprop)
           *K_g(p2out, p2in)/HEJ::C_F;
 }
 
 namespace{
   // helicity amplitude squares for contraction of W current with unordered current
   template<Helicity h2, Helicity hg>
   double ampsq_jW_juno(
     HLV const & pa, HLV const & p1,
     HLV const & pb, HLV const & p2,
     HLV const & pg,
     HLV const & pl, HLV const & plbar
   ){
     const COM U1 = HEJ::U1_jW<h2,hg>(pa,p1,pb,p2,pg,pl,plbar);
     const COM U2 = HEJ::U2_jW<h2,hg>(pa,p1,pb,p2,pg,pl,plbar);
     const COM L = HEJ::L_jW<h2,hg>(pa,p1,pb,p2,pg,pl,plbar);
 
     return 2.*HEJ::C_F*std::real((L-U1)*std::conj(L+U2))
       + 2.*HEJ::C_F*HEJ::C_F/3.*std::norm(U1+U2)
       ;
   }
 
   /**
    * @brief W+Jets Unordered Contributions, function to handle all incoming types.
    * @param p1out             Outgoing Particle 1. (W emission)
    * @param plbar             Outgoing election momenta
    * @param pl                Outgoing neutrino momenta
    * @param p1in              Incoming Particle 1. (W emission)
    * @param p2out             Outgoing Particle 2 (Quark, unordered emission this side.)
    * @param p2in              Incoming Particle 2 (Quark, unordered emission this side.)
    * @param pg                Unordered Gluon momenta
    * @param aqlineb           Bool. Is Backwards quark line an anti-quark line?
    * @param aqlinef           Bool. Is Forwards quark line an anti-quark line?
    *
    * Calculates j_W ^\mu    j_{uno}_\mu. Ie, unordered with W emission opposite side.
    * Handles all possible incoming states.
    */
   double jW_juno(
     HLV p1out, HLV plbar, HLV pl,HLV p1in, HLV p2out,
     HLV p2in, HLV pg, bool aqlineb, bool /* aqlinef */,
     ParticleProperties const & wprop
   ){
     using helicity::minus;
     using helicity::plus;
 
     // we assume that the W is emitted off a quark line
     // if this is not the case, we have to apply CP conjugation,
     // which is equivalent to swapping lepton and antilepton momenta
     if(aqlineb) std::swap(pl, plbar);
 
     // helicity assignments assume aqlinef == false
     // in the end, this is irrelevant since we sum over all helicities
     const double ampsq =
       + ampsq_jW_juno<minus, minus>(p1in,p1out,p2in,p2out,pg,pl,plbar)
       + ampsq_jW_juno<minus, plus>(p1in,p1out,p2in,p2out,pg,pl,plbar)
       + ampsq_jW_juno<plus, minus>(p1in,p1out,p2in,p2out,pg,pl,plbar)
       + ampsq_jW_juno<plus, plus>(p1in,p1out,p2in,p2out,pg,pl,plbar)
       ;
 
     const HLV q1=p1in-p1out-plbar-pl;
     const HLV q2=-(p2in-p2out-pg);
 
     return WProp(plbar, pl, wprop)*ampsq/((16)*(q2.m2()*q1.m2()));
   }
 }
 double ME_W_unob_qQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                     HLV pg, HLV plbar, HLV pl,
                     ParticleProperties const & wprop
 ){
   return jW_juno(p2out, plbar, pl, p2in, p1out, p1in, pg, false, false, wprop);
 }
 
 double ME_W_unob_qQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                        HLV pg, HLV plbar, HLV pl,
                        ParticleProperties const & wprop
 ){
   return jW_juno(p2out, plbar, pl, p2in, p1out, p1in, pg, false, true, wprop);
 }
 
 double ME_W_unob_qbarQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                        HLV pg, HLV plbar, HLV pl,
                        ParticleProperties const & wprop
 ){
   return jW_juno(p2out, plbar, pl, p2in, p1out, p1in, pg, true, false, wprop);
 }
 
 double ME_W_unob_qbarQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                           HLV pg, HLV plbar, HLV pl,
                           ParticleProperties const & wprop
 ){
   return jW_juno(p2out, plbar, pl, p2in, p1out, p1in, pg, true, true, wprop);
 }
 
 namespace{
 
   // helicity sum helper for jWuno_j(...)
   template<Helicity h1>
     double jWuno_j_helsum(
       HLV const & pg, HLV const & p1, HLV const & plbar, HLV const & pl,
       HLV const & pa, HLV const & p2, HLV const & pb,
       ParticleProperties const & wprop
     ){
     using helicity::minus;
     using helicity::plus;
 
     const double ME2h1pp = jM2Wuno<h1, plus, plus>(
       pg, p1, plbar, pl, pa, p2, pb, wprop
     );
 
     const double ME2h1pm = jM2Wuno<h1, plus, minus>(
       pg, p1, plbar, pl, pa, p2, pb, wprop
     );
 
     const double ME2h1mp = jM2Wuno<h1, minus, plus>(
       pg, p1, plbar, pl, pa, p2, pb, wprop
     );
 
     const double ME2h1mm = jM2Wuno<h1, minus, minus>(
       pg, p1, plbar, pl, pa, p2, pb, wprop
     );
 
     return ME2h1pp + ME2h1pm + ME2h1mp + ME2h1mm;
   }
 
 /**
  * @brief W+Jets Unordered Contributions, function to handle all incoming types.
  * @param pg                Unordered Gluon momenta
  * @param p1out             Outgoing Particle 1. (Quark - W and Uno emission)
  * @param plbar             Outgoing election momenta
  * @param pl                Outgoing neutrino momenta
  * @param p1in              Incoming Particle 1. (Quark - W and Uno emission)
  * @param p2out             Outgoing Particle 2
  * @param p2in              Incoming Particle 2
  * @param aqlineb           Bool. Is Backwards quark line an anti-quark line?
  *
  * Calculates j_W_{uno} ^\mu    j_\mu. Ie, unordered with W emission same side.
  * Handles all possible incoming states. Note this handles both forward and back-
  * -ward Wuno emission. For forward, ensure p1out is the uno and W emission parton.
  * @TODO: Include separate wrapper functions for forward and backward to clean up
  *        ME_W_unof_current in `MatrixElement.cc`.
  */
   double jWuno_j(HLV pg, HLV p1out, HLV plbar, HLV pl, HLV p1in,
                  HLV p2out, HLV p2in, bool aqlineb,
                  ParticleProperties const & wprop
     ){
     const auto helsum = aqlineb?
       jWuno_j_helsum<helicity::plus>:
       jWuno_j_helsum<helicity::minus>;
 
     //Helicity sum and average over initial states
     return helsum(pg, p1out,plbar,pl,p1in,p2out,p2in, wprop)/(4.*HEJ::C_A*HEJ::C_A);
   }
 }
 double ME_Wuno_qQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                   HLV pg, HLV plbar, HLV pl, ParticleProperties const & wprop
 ){
   return jWuno_j(pg, p1out, plbar, pl, p1in, p2out, p2in, false, wprop);
 }
 
 double ME_Wuno_qQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                      HLV pg, HLV plbar, HLV pl,
                      ParticleProperties const & wprop
 ){
   return jWuno_j(pg, p1out, plbar, pl, p1in, p2out, p2in, false, wprop);
 }
 
 double ME_Wuno_qbarQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                      HLV pg, HLV plbar, HLV pl,
                      ParticleProperties const & wprop
 ){
   return jWuno_j(pg, p1out, plbar, pl, p1in, p2out, p2in, true, wprop);
 }
 
 double ME_Wuno_qbarQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                         HLV pg, HLV plbar, HLV pl,
                         ParticleProperties const & wprop
 ){
   return jWuno_j(pg, p1out, plbar, pl, p1in, p2out, p2in, true, wprop);
 }
 
 double ME_Wuno_qg(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                   HLV pg, HLV plbar, HLV pl, ParticleProperties const & wprop
 ){
   return jWuno_j(pg, p1out, plbar, pl, p1in, p2out, p2in, false, wprop)
           *K_g(p2out, p2in)/HEJ::C_F;
 }
 
 double ME_Wuno_qbarg(HLV p1out, HLV p1in, HLV p2out, HLV p2in,
                      HLV pg, HLV plbar, HLV pl,
                      ParticleProperties const & wprop
 ){
   return jWuno_j(pg, p1out, plbar, pl, p1in, p2out, p2in, true, wprop)
           *K_g(p2out, p2in)/HEJ::C_F;
 }
 
 // helicity sum helper for jWqqx_j(...)
 template<Helicity h1>
 double jWqqx_j_helsum(
   HLV const & pg, HLV const & pq, HLV const & plbar, HLV const & pl,
   HLV const & pqbar, HLV const & p3, HLV const & pb,
   ParticleProperties const & wprop
 ){
   using helicity::minus;
   using helicity::plus;
 
   const double ME2h1pp = jM2_W_extremal_qqbar<h1, plus, plus>(
     pg, pq, plbar, pl, pqbar, p3, pb, wprop
   );
   const double ME2h1pm = jM2_W_extremal_qqbar<h1, plus, minus>(
     pg, pq, plbar, pl, pqbar, p3, pb, wprop
   );
   const double ME2h1mp = jM2_W_extremal_qqbar<h1, minus, plus>(
     pg, pq, plbar, pl, pqbar, p3, pb, wprop
   );
   const double ME2h1mm = jM2_W_extremal_qqbar<h1, minus, minus>(
     pg, pq, plbar, pl, pqbar, p3, pb, wprop
   );
 
   return ME2h1pp + ME2h1pm + ME2h1mp + ME2h1mm;
 }
 
 /**
  * @brief W+Jets Extremal qqx Contributions, function to handle all incoming types.
  * @param pgin              Incoming gluon which will split into qqx.
  * @param pqout             Quark of extremal qqx outgoing (W-Emission).
  * @param plbar             Outgoing anti-lepton momenta
  * @param pl                Outgoing lepton momenta
  * @param pqbarout          Anti-quark of extremal qqx pair. (W-Emission)
  * @param pout              Outgoing Particle 2 (end of FKL chain)
  * @param p2in              Incoming Particle 2
  * @param aqlinef           Bool. Is Forwards quark line an anti-quark line?
  *
  * Calculates j_W_{qqx} ^\mu    j_\mu. Ie, Ex-QQX with W emission same side.
  * Handles all possible incoming states. Calculated via crossing symmetry from jWuno_j.
  */
 double jWqqx_j(HLV pgin, HLV pqout, HLV plbar, HLV pl,
                HLV pqbarout, HLV p2out, HLV p2in, bool aqlinef,
                ParticleProperties const & wprop
 ){
   const auto helsum = aqlinef?
     jWqqx_j_helsum<helicity::plus>:
     jWqqx_j_helsum<helicity::minus>;
 
   //Helicity sum and average over initial states.
   double ME2 = helsum(pgin, pqout, plbar, pl, pqbarout, p2out, p2in, wprop)/
     (4.*HEJ::C_A*HEJ::C_A);
 
   //Correct colour averaging after crossing:
   ME2*=(3.0/8.0);
 
   return ME2;
 }
 
 double ME_WExqqx_qbarqQ(HLV pgin, HLV pqout, HLV plbar, HLV pl,
                       HLV pqbarout, HLV p2out, HLV p2in,
                ParticleProperties const & wprop
 ){
   return jWqqx_j(pgin, pqout, plbar, pl, pqbarout, p2out, p2in, false, wprop);
 }
 
 double ME_WExqqx_qqbarQ(HLV pgin, HLV pqbarout, HLV plbar, HLV pl,
                       HLV pqout, HLV p2out, HLV p2in,
                       ParticleProperties const & wprop
 ){
   return jWqqx_j(pgin, pqbarout, plbar, pl, pqout, p2out, p2in, true, wprop);
 }
 
 double ME_WExqqx_qbarqg(HLV pgin, HLV pqout, HLV plbar, HLV pl,
                       HLV pqbarout, HLV p2out, HLV p2in,
                       ParticleProperties const & wprop
 ){
   return jWqqx_j(pgin, pqout, plbar, pl, pqbarout, p2out, p2in, false, wprop)
           *K_g(p2out,p2in)/HEJ::C_F;
 }
 
 double ME_WExqqx_qqbarg(HLV pgin, HLV pqbarout, HLV plbar, HLV pl,
                       HLV pqout, HLV p2out, HLV p2in,
                       ParticleProperties const & wprop
 ){
   return jWqqx_j(pgin, pqbarout, plbar, pl, pqout, p2out, p2in, true, wprop)
           *K_g(p2out,p2in)/HEJ::C_F;
 }
 
 namespace {
   template<Helicity h1, Helicity hg>
   double jW_jqqbar(
     HLV const & pb,
     HLV const & p2,
     HLV const & p3,
     HLV const & pa,
     HLV const & p1,
     HLV const & pl,
     HLV const & plbar
   ) {
     using helicity::minus;
     using helicity::plus;
 
     static constexpr COM cm1m1 = 8./3.;
     static constexpr COM cm2m2 = 8./3.;
     static constexpr COM cm3m3 = 6.;
     static constexpr COM cm1m2 =-1./3.;
     static constexpr COM cm1m3 = COM{0.,-3.};
     static constexpr COM cm2m3 = COM{0.,3.};
 
     const COM m1 = HEJ::jW_qggm1<h1,hg>(pb,p2,p3,pa,p1,pl,plbar);
     const COM m2 = HEJ::jW_qggm2<h1,hg>(pb,p2,p3,pa,p1,pl,plbar);
     const COM m3 = HEJ::jW_qggm3<h1,hg>(pb,p2,p3,pa,p1,pl,plbar);
 
     return real(
       cm1m1*pow(abs(m1),2) + cm2m2*pow(abs(m2),2)
       + cm3m3*pow(abs(m3),2) + 2.*real(cm1m2*m1*conj(m2))
     )
       + 2.*real(cm1m3*m1*conj(m3))
       + 2.*real(cm2m3*m2*conj(m3)) ;
   }
 }
 
 // contraction of W current with extremal qqbar on the other side
 double ME_W_Exqqx_QQq(
   HLV pa, HLV pb,
   HLV p1,  HLV p2,
   HLV p3, HLV plbar, HLV pl, bool aqlinepa,
   ParticleProperties const & wprop
 ){
   using helicity::minus;
   using helicity::plus;
 
   const double WPropfact = WProp(plbar, pl, wprop);
   const double prefactor = 2.*WPropfact/24./4./(
     (pa-p1-pl-plbar).m2()*(p2+p3-pb).m2()
   );
   if(aqlinepa) {
     return prefactor*(
       jW_jqqbar<plus, minus>(pb,p2,p3,pa,p1,pl,plbar)
       + jW_jqqbar<plus, plus>(pb,p2,p3,pa,p1,pl,plbar)
     );
   }
   return prefactor*(
     jW_jqqbar<minus, minus>(pb,p2,p3,pa,p1,pl,plbar)
     + jW_jqqbar<minus, plus>(pb,p2,p3,pa,p1,pl,plbar)
   );
 }
 
 namespace {
   // helper function for matrix element for W + Jets with central qqbar
   template<HEJ::Helicity h1a, HEJ::Helicity h4b>
   double amp_WCenqqx_qq(
     HLV const & pa, HLV const &  p1,
     HLV const & pb, HLV const &  p4,
     HLV const &  pq, HLV const &  pqbar,
     HLV const &  pl, HLV const &  plbar,
     HLV const &  q11, HLV const &  q12
   ) {
     const COM sym = HEJ::M_sym_W<h1a, h4b>(
       pa, p1, pb, p4, pq, pqbar, pl, plbar, q11, q12
     );
     const COM cross = HEJ::M_cross_W<h1a, h4b>(
       pa, p1, pb, p4, pq, pqbar, pl, plbar, q11, q12
     );
     const COM uncross = HEJ::M_uncross_W<h1a, h4b>(
       pa, p1, pb, p4, pq, pqbar, pl, plbar, q11, q12
     );
 
     // Colour factors
     static constexpr COM cmsms = 3.;
     static constexpr COM cmumu = 4./3.;
     static constexpr COM cmcmc = 4./3.;
     static constexpr COM cmsmu = COM{0., 3./2.};
     static constexpr COM cmsmc = COM{0.,-3./2.};
     static constexpr COM cmumc = -1./6.;
 
     return real(
       cmsms*pow(abs(sym),2)
       +cmumu*pow(abs(uncross),2)
       +cmcmc*pow(abs(cross),2)
     )
       +2.*real(cmsmu*sym*conj(uncross))
       +2.*real(cmsmc*sym*conj(cross))
       +2.*real(cmumc*uncross*conj(cross))
       ;
   }
 }
 
 // matrix element for W + Jets with W emission off central qqbar
 double ME_WCenqqx_qq(
   HLV pa, HLV pb, HLV pl, HLV plbar, std::vector<HLV> partons,
   bool /* aqlinepa */, bool /* aqlinepb */, bool qqxmarker, int nabove,
   ParticleProperties const & wprop
 ) {
   using helicity::plus;
   using helicity::minus;
 
   CLHEP::HepLorentzVector q1 = pa;
   for(int i = 0; i <= nabove; ++i){
     q1 -= partons[i];
   }
   const auto qq = HEJ::split_into_lightlike(q1);
   // since q1.m2() < 0 the following assertion is always true
   // see documentation for split_into_lightlike
   assert(qq.second.e() < 0);
 
   HLV pq, pqbar;
   if (qqxmarker){
     pqbar = partons[nabove+1];
     pq = partons[nabove+2];}
   else{
     pq = partons[nabove+1];
     pqbar = partons[nabove+2];
   }
   const HLV p1 = partons.front();
   const HLV p4 = partons.back();
 
   // 4 Different Helicity Choices (Differs from Pure Jet Case, where there is
   // also the choice in qqbar helicity.
   // the first helicity label is for aqlinepa == true,
   // the second one for aqlinepb == true
   // In principle the corresponding helicity should be flipped
   // if either of them is false, but we sum up all helicities,
   // so this has no effect in the end.
   const double amp_mm = amp_WCenqqx_qq<minus, minus>(
     pa, p1, pb, p4, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
   const double amp_mp = amp_WCenqqx_qq<minus, plus>(
     pa, p1, pb, p4, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
   const double amp_pm = amp_WCenqqx_qq<plus, minus>(
     pa, p1, pb, p4, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
   const double amp_pp = amp_WCenqqx_qq<plus, plus>(
     pa, p1, pb, p4, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
 
   const double t1 = q1.m2();
   const double t3 = (q1-pl-plbar-pq-pqbar).m2();
 
   const double amp = WProp(plbar, pl, wprop)*(
     amp_mm+amp_mp+amp_pm+amp_pp
   )/(9.*4.*t1*t1*t3*t3);
 
   return amp;
 }
 
 // helper function for matrix element for W + Jets with central qqbar
 // W emitted off extremal parton
 // @TODO: code duplication with amp_WCenqqx_qq
 template<HEJ::Helicity h1a, HEJ::Helicity hqqbar>
 double amp_W_Cenqqx_qq(
     HLV const & pa, HLV const &  p1,
     HLV const & pb, HLV const &  pn,
     HLV const &  pq, HLV const &  pqbar,
     HLV const &  pl, HLV const &  plbar,
     HLV const &  q11, HLV const &  q12
   ) {
 
   const COM crossed = HEJ::M_cross<h1a, hqqbar>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, q11, q12
   );
   const COM uncrossed = HEJ::M_qbar<h1a, hqqbar>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, q11, q12
   );
   const COM sym = HEJ::M_sym<h1a, hqqbar>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, q11, q12
   );
 
   //Colour factors:
   static constexpr COM cmsms = 3.;
   static constexpr COM cmumu = 4./3.;
   static constexpr COM cmcmc = 4./3.;
   static constexpr COM cmsmu = COM{0.,3./2.};
   static constexpr COM cmsmc = COM{0.,-3./2.};
   static constexpr COM cmumc = -1./6.;
 
   return real(
     cmsms*pow(abs(sym),2)
     +cmumu*pow(abs(uncrossed),2)
     +cmcmc*pow(abs(crossed),2)
   )
     +2.*real(cmsmu*sym*conj(uncrossed))
     +2.*real(cmsmc*sym*conj(crossed))
     +2.*real(cmumc*uncrossed*conj(crossed))
     ;
 }
 
 // matrix element for W + Jets with W emission *not* off central qqbar
 double ME_W_Cenqqx_qq(
   HLV pa, HLV pb,HLV pl,HLV plbar, std::vector<HLV> partons,
   bool aqlinepa, bool aqlinepb, bool qqxmarker, int nabove,
   int nbelow, bool forwards, ParticleProperties const & wprop
 ){
   using helicity::minus;
   using helicity::plus;
 
   if (!forwards){ //If Emission from Leg a instead, flip process.
     std::swap(pa, pb);
     std::reverse(partons.begin(),partons.end());
     std::swap(aqlinepa, aqlinepb);
     qqxmarker = !qqxmarker;
     std::swap(nabove,nbelow);
   }
 
   HLV q1=pa;
   for(int i=0;i<nabove+1;++i){
     q1-=partons.at(i);
   }
   const auto qq = HEJ::split_into_lightlike(q1);
 
   HLV pq, pqbar;
   if (qqxmarker){
     pqbar = partons[nabove+1];
     pq = partons[nabove+2];}
   else{
     pq = partons[nabove+1];
     pqbar = partons[nabove+2];}
 
   // we assume that the W is emitted off a quark line
   // if this is not the case, we have to apply CP conjugation,
   // which is equivalent to swapping lepton and antilepton momenta
   if(aqlinepb) std::swap(pl, plbar);
 
   const HLV p1 = partons.front();
   const HLV pn = partons.back();
 
   // helicity labels are for aqlinepa == aqlineb == false,
   // In principle the helicities should be flipped
   // if either of them is true, but we sum up all helicities,
   // so this has no effect in the end.
   const double amp_mm = amp_W_Cenqqx_qq<minus, minus>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
   const double amp_mp = amp_W_Cenqqx_qq<minus, plus>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
   const double amp_pm = amp_W_Cenqqx_qq<plus, minus>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
   const double amp_pp = amp_W_Cenqqx_qq<plus, plus>(
     pa, p1, pb, pn, pq, pqbar, pl, plbar, qq.first, -qq.second
   );
 
   const double t1 = q1.m2();
   const double t3 = (q1 - pq - pqbar).m2();
 
   const double amp= WProp(plbar, pl, wprop)*(
     amp_mm+amp_mp+amp_pm+amp_pp
   )/(9.*4.*t1*t1*t3*t3);
 
   return amp;
 }
diff --git a/src/jets.cc b/src/jets.cc
index ef893e2..297e67f 100644
--- a/src/jets.cc
+++ b/src/jets.cc
@@ -1,794 +1,792 @@
 /**
  *  \authors   The HEJ collaboration (see AUTHORS for details)
  *  \date      2019
  *  \copyright GPLv2 or later
  */
 #include "HEJ/jets.hh"
 
-#include "HEJ/Tensor.hh"
+#include <algorithm>
+
 #include "HEJ/Constants.hh"
 
+namespace {
+  double metric(const size_t mu, const size_t nu) {
+    if(mu != nu) return 0.;
+    return (mu == 0)?1.:-1.;
+  }
+}
+
 // Colour acceleration multiplier for gluons see eq. (7) in arXiv:0910.5113
 // @TODO: this is not a current and should be moved somewhere else
 double K_g(double p1minus, double paminus) {
     return 1./2.*(p1minus/paminus + paminus/p1minus)*(HEJ::C_A - 1./HEJ::C_A) + 1./HEJ::C_A;
 }
 double K_g(
   HLV const & pout,
   HLV const & pin
   ) {
   if(pin.z() > 0) return K_g(pout.plus(), pin.plus());
   return K_g(pout.minus(), pin.minus());
 }
 
 CCurrent CCurrent::operator+(const CCurrent& other)
 {
     COM result_c0=c0 + other.c0;
     COM result_c1=c1 + other.c1;
     COM result_c2=c2 + other.c2;
     COM result_c3=c3 + other.c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator-(const CCurrent& other)
 {
     COM result_c0=c0 - other.c0;
     COM result_c1=c1 - other.c1;
     COM result_c2=c2 - other.c2;
     COM result_c3=c3 - other.c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator*(const double x)
 {
     COM result_c0=x*CCurrent::c0;
     COM result_c1=x*CCurrent::c1;
     COM result_c2=x*CCurrent::c2;
     COM result_c3=x*CCurrent::c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator/(const double x)
 {
     COM result_c0=CCurrent::c0/x;
     COM result_c1=CCurrent::c1/x;
     COM result_c2=CCurrent::c2/x;
     COM result_c3=CCurrent::c3/x;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator*(const COM x)
 {
     COM result_c0=x*CCurrent::c0;
     COM result_c1=x*CCurrent::c1;
     COM result_c2=x*CCurrent::c2;
     COM result_c3=x*CCurrent::c3;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 CCurrent CCurrent::operator/(const COM x)
 {
     COM result_c0=(CCurrent::c0)/x;
     COM result_c1=(CCurrent::c1)/x;
     COM result_c2=(CCurrent::c2)/x;
     COM result_c3=(CCurrent::c3)/x;
 
     return CCurrent(result_c0,result_c1,result_c2,result_c3);
 }
 
 std::ostream& operator <<(std::ostream& os, const CCurrent& cur)
 {
     os << "("<<cur.c0<< " ; "<<cur.c1<<" , "<<cur.c2<<" , "<<cur.c3<<")";
     return os;
 }
 
 CCurrent operator * ( double x, CCurrent& m)
 {
     return m*x;
 }
 
 CCurrent operator * ( COM x, CCurrent& m)
 {
     return m*x;
 }
 
 CCurrent operator / ( double x, CCurrent& m)
 {
     return m/x;
 }
 
 CCurrent operator / ( COM x, CCurrent& m)
 {
     return m/x;
 }
 
 COM CCurrent::dot(HLV p1)
 {
     //  Current goes (E,px,py,pz)
     //  Vector goes (px,py,pz,E)
     return p1[3]*c0-p1[0]*c1-p1[1]*c2-p1[2]*c3;
 }
 
 COM CCurrent::dot(CCurrent p1)
 {
     return p1.c0*c0-p1.c1*c1-p1.c2*c2-p1.c3*c3;
 }
 
 //Current Functions
 void joi(HLV pout, bool helout, HLV pin, bool helin, current &cur) {
   cur[0]=0.;
   cur[1]=0.;
   cur[2]=0.;
   cur[3]=0.;
 
   const double sqpop = sqrt(std::abs(pout.plus()));
   const double sqpom = sqrt(std::abs(pout.minus()));
 
   // Allow for "jii" format
   const COM poperp = (pout.x()==0 && pout.y() ==0) ? -1 : pout.x()+COM(0,1)*pout.y();
 
   if (helout != helin) {
     throw std::invalid_argument{"Non-matching helicities"};
   } else if (helout == false) { // negative helicity
     if (pin.plus() > pin.minus()) { // if forward
       const double sqpip = sqrt(std::abs(pin.plus()));
       cur[0] = sqpop * sqpip;
       cur[1] = sqpom * sqpip * poperp / std::abs(poperp);
       cur[2] = -COM(0,1) * cur[1];
       cur[3] = cur[0];
     } else { // if backward
       const double sqpim = sqrt(std::abs(pin.minus()));
       cur[0] = -sqpom * sqpim * poperp / std::abs(poperp);
       cur[1] = -sqpim * sqpop;
       cur[2] = COM(0,1) * cur[1];
       cur[3] = -cur[0];
     }
   } else { // positive helicity
     if (pin.plus() > pin.minus()) { // if forward
       const double sqpip = sqrt(std::abs(pin.plus()));
       cur[0] = sqpop * sqpip;
       cur[1] = sqpom * sqpip * conj(poperp) / std::abs(poperp);
       cur[2] = COM(0,1) * cur[1];
       cur[3] = cur[0];
     } else { // if backward
       double sqpim = sqrt(std::abs(pin.minus()));
       cur[0] = -sqpom * sqpim * conj(poperp) / std::abs(poperp);
       cur[1] = -sqpim * sqpop;
       cur[2] = -COM(0,1) * cur[1];
       cur[3] = -cur[0];
     }
   }
 }
 
 CCurrent joi (HLV pout, bool helout, HLV pin, bool helin)
 {
   current cur;
   joi(pout, helout, pin, helin, cur);
   return CCurrent(cur[0],cur[1],cur[2],cur[3]);
 }
 
 void jio(HLV pin, bool helin, HLV pout, bool helout, current &cur) {
   joi(pout, !helout, pin, !helin, cur);
 }
 
 CCurrent jio (HLV pin, bool helin, HLV pout, bool helout)
 {
   current cur;
   jio(pin, helin, pout, helout, cur);
   return CCurrent(cur[0],cur[1],cur[2],cur[3]);
 }
 
 void joo(HLV pi, bool heli, HLV pj, bool helj, current &cur) {
 
   // Zero our current
   cur[0] = 0.0;
   cur[1] = 0.0;
   cur[2] = 0.0;
   cur[3] = 0.0;
   if (heli!=helj) {
     throw std::invalid_argument{"Non-matching helicities"};
   } else if ( heli == true ) { // If positive helicity swap momenta
     std::swap(pi,pj);
   }
 
   const double sqpjp = sqrt(std::abs(pj.plus() ));
   const double sqpjm = sqrt(std::abs(pj.minus()));
   const double sqpip = sqrt(std::abs(pi.plus() ));
   const double sqpim = sqrt(std::abs(pi.minus()));
   // Allow for "jii" format
   const COM piperp = (pi.x()==0 && pi.y() ==0) ? -1 : pi.x()+COM(0,1)*pi.y();
   const COM pjperp = (pj.x()==0 && pj.y() ==0) ? -1 : pj.x()+COM(0,1)*pj.y();
 
   const COM phasei = piperp / std::abs(piperp);
   const COM phasej = pjperp / std::abs(pjperp);
 
   cur[0] = sqpim * sqpjm * phasei * conj(phasej) + sqpip * sqpjp;
   cur[1] = sqpim * sqpjp * phasei + sqpip * sqpjm * conj(phasej);
   cur[2] = -COM(0, 1) * (sqpim * sqpjp * phasei - sqpip * sqpjm * conj(phasej));
   cur[3] = -sqpim * sqpjm * phasei * conj(phasej) + sqpip * sqpjp;
 }
 
 CCurrent joo (HLV pi, bool heli, HLV pj, bool helj)
 {
   current cur;
   joo(pi, heli, pj, helj, cur);
   return CCurrent(cur[0],cur[1],cur[2],cur[3]);
 }
 namespace{
 //@{
   /**
    * @brief Pure Jet FKL Contributions, function to handle all incoming types.
    * @param p1out             Outgoing Particle 1.
    * @param p1in              Incoming Particle 1.
    * @param p2out             Outgoing Particle 2
    * @param p2in              Incoming Particle 2
    *
    * Calculates j_\mu    j^\mu.
    * Handles all possible incoming states. Helicity doesn't matter since we sum
    * over all of them.
    */
   double j_j(HLV const & p1out, HLV const & p1in,
              HLV const & p2out, HLV const & p2in
   ){
     HLV const q1=p1in-p1out;
     HLV const q2=-(p2in-p2out);
     current mj1m,mj1p,mj2m,mj2p;
 
     // Note need to flip helicities in anti-quark case.
     joi(p1out, false, p1in, false, mj1p);
     joi(p1out, true,  p1in, true,  mj1m);
     joi(p2out, false, p2in, false, mj2p);
     joi(p2out, true,  p2in, true,  mj2m);
 
     COM const Mmp=cdot(mj1m,mj2p);
     COM const Mmm=cdot(mj1m,mj2m);
     COM const Mpp=cdot(mj1p,mj2p);
     COM const Mpm=cdot(mj1p,mj2m);
 
     double const sst=abs2(Mmm)+abs2(Mmp)+abs2(Mpp)+abs2(Mpm);
 
     // Multiply by Cf^2
     return HEJ::C_F*HEJ::C_F*(sst)/(q1.m2()*q2.m2());
   }
 } //anonymous namespace
 double ME_qQ(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in);
 }
 
 double ME_qQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in);
 }
 
 double ME_qbarQbar(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in);
 }
 
 double ME_qg(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in)*K_g(p2out, p2in)/HEJ::C_F;
 }
 
 double ME_qbarg(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in)*K_g(p2out, p2in)/(HEJ::C_F);
 }
 
 double ME_gg(HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return j_j(p1out, p1in, p2out, p2in)*K_g(p1out, p1in)*K_g(p2out, p2in)/(HEJ::C_F*HEJ::C_F);
 }
 //@}
 
 namespace{
   double juno_j(HLV const & pg, HLV const & p1out,
                 HLV const & p1in, HLV const & p2out, HLV const & p2in
   ){
     //  This construction is taking rapidity order: pg > p1out >> p2out
     HLV q1=p1in-p1out;  // Top End
     HLV q2=-(p2in-p2out);   // Bottom End
     HLV qg=p1in-p1out-pg;  // Extra bit post-gluon
 
     // Note <p1|eps|pa> current split into two by gauge choice.
     // See James C's Thesis (p72). <p1|eps|pa> -> <p1|pg><pg|pa>
     CCurrent mj1p=joi(p1out, false, p1in, false);
     CCurrent mj1m=joi(p1out,  true, p1in,  true);
     CCurrent jgap=joi(pg,    false, p1in, false);
     CCurrent jgam=joi(pg,     true, p1in,  true);
 
     // Note for function joo(): <p1+|pg+> = <pg-|p1->.
     CCurrent j2gp=joo(p1out, false, pg, false);
     CCurrent j2gm=joo(p1out,  true, pg,  true);
 
     CCurrent mj2p=joi(p2out, false, p2in, false);
     CCurrent mj2m=joi(p2out,  true, p2in,  true);
 
     // Dot products of these which occur again and again
     COM Mmp=mj1m.dot(mj2p);
     COM Mmm=mj1m.dot(mj2m);
     COM Mpp=mj1p.dot(mj2p);
     COM Mpm=mj1p.dot(mj2m);
 
     CCurrent p1o(p1out),p2o(p2out),p2i(p2in),qsum(q1+qg),p1i(p1in);
 
     CCurrent Lmm=(qsum*(Mmm)+(-2.*mj2m.dot(pg))*mj1m+2.*mj1m.dot(pg)*mj2m
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mmm/2.))/q1.m2();
     CCurrent Lmp=(qsum*(Mmp) + (-2.*mj2p.dot(pg))*mj1m+2.*mj1m.dot(pg)*mj2p
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mmp/2.))/q1.m2();
     CCurrent Lpm=(qsum*(Mpm) + (-2.*mj2m.dot(pg))*mj1p+2.*mj1p.dot(pg)*mj2m
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mpm/2.))/q1.m2();
     CCurrent Lpp=(qsum*(Mpp) + (-2.*mj2p.dot(pg))*mj1p+2.*mj1p.dot(pg)*mj2p
                   +(p2o/pg.dot(p2out) + p2i/pg.dot(p2in))*(qg.m2()*Mpp/2.))/q1.m2();
 
     CCurrent U1mm=(jgam.dot(mj2m)*j2gm+2.*p1o*Mmm)/(p1out+pg).m2();
     CCurrent U1mp=(jgam.dot(mj2p)*j2gm+2.*p1o*Mmp)/(p1out+pg).m2();
     CCurrent U1pm=(jgap.dot(mj2m)*j2gp+2.*p1o*Mpm)/(p1out+pg).m2();
     CCurrent U1pp=(jgap.dot(mj2p)*j2gp+2.*p1o*Mpp)/(p1out+pg).m2();
     CCurrent U2mm=((-1.)*j2gm.dot(mj2m)*jgam+2.*p1i*Mmm)/(p1in-pg).m2();
     CCurrent U2mp=((-1.)*j2gm.dot(mj2p)*jgam+2.*p1i*Mmp)/(p1in-pg).m2();
     CCurrent U2pm=((-1.)*j2gp.dot(mj2m)*jgap+2.*p1i*Mpm)/(p1in-pg).m2();
     CCurrent U2pp=((-1.)*j2gp.dot(mj2p)*jgap+2.*p1i*Mpp)/(p1in-pg).m2();
 
     constexpr double cf=HEJ::C_F;
 
     double amm=cf*(2.*vre(Lmm-U1mm,Lmm+U2mm))+2.*cf*cf/3.*vabs2(U1mm+U2mm);
     double amp=cf*(2.*vre(Lmp-U1mp,Lmp+U2mp))+2.*cf*cf/3.*vabs2(U1mp+U2mp);
     double apm=cf*(2.*vre(Lpm-U1pm,Lpm+U2pm))+2.*cf*cf/3.*vabs2(U1pm+U2pm);
     double app=cf*(2.*vre(Lpp-U1pp,Lpp+U2pp))+2.*cf*cf/3.*vabs2(U1pp+U2pp);
     double ampsq=-(amm+amp+apm+app);
 
     //Divide by t-channels
     ampsq/=q2.m2()*qg.m2();
     ampsq/=16.;
 
     // Factor of (Cf/Ca) for each quark to match j_j.
     ampsq*=(HEJ::C_F*HEJ::C_F)/(HEJ::C_A*HEJ::C_A);
 
     return ampsq;
 
   }
 }
 
 //Unordered bits for pure jet
 double ME_unob_qQ (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qbarQ (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qQbar (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qbarQbar (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in);
 }
 
 double ME_unob_qg (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 double ME_unob_qbarg (HLV pg, HLV p1out, HLV p1in, HLV p2out, HLV p2in){
   return juno_j(pg, p1out, p1in, p2out, p2in)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 namespace {
   void eps(HLV refmom, HLV kb, bool hel, current &ep){
     //Positive helicity eps has negative helicity choices for spinors and vice versa
     joi(refmom,hel,kb,hel,ep);
     double norm;
     if(kb.z()<0.) norm = sqrt(2.*refmom.plus()*kb.minus());
     else          norm = sqrt(2.*refmom.minus()*kb.plus());
     // Normalise
     std::for_each(begin(ep), end(ep), [&,norm](auto & val){val/=norm;});
   }
 
   COM qggm1(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3, bool helchain,
             bool heltop, bool helb,HLV refmom){
     // Since everything is defined with currents, need to use compeleness relation
     // to expand p slash. i.e. pslash = |p><p|. Only one helicity 'survives' as
     // defined by the helicities of the spinors at the end of the chain.
     current cur33, cur23, curb3, cur2b, cur1a, ep;
     joo(p3, helchain, p3, helchain,cur33);
     joo(p2,helchain,p3,helchain,cur23);
     jio(pb,helchain,p3,helchain,curb3);
     joi(p2,helchain,pb,helchain,cur2b);
     joi(p1, heltop, pa, heltop,cur1a);
 
     const double t2 = (p3-pb)*(p3-pb);
     //Calculate Term 1 in Equation 3.23 in James Cockburn's Thesis.
     COM v1[4][4];
     for(int u=0; u<4;++u){
       for(int v=0; v<4;++v){
         v1[u][v]=(cur23[u]*cur33[v]-cur2b[u]*curb3[v])/t2*(-1.);
       }
     }
     //Dot in current and eps
-    //Metric tensor
-    auto eta = HEJ::metric();
     //eps
     eps(refmom,pb,helb, ep);
     COM M1=0.;
     // Perform Contraction: g^{ik} j_{1a}_k * v1_i^j eps^l g_lj
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
-        M1+= eta(i,i) *cur1a[i]*(v1[i][j])*ep[j]*eta(j,j);
+        M1+= metric(i,i) *cur1a[i]*(v1[i][j])*ep[j]*metric(j,j);
       }
     }
     return M1;
   }
 
   COM qggm2(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3, bool helchain, bool heltop,
             bool helb,HLV refmom){
     // Since everything is defined with currents, need to use completeness relation
     // to expand p slash. i.e. pslash = |p><p|. Only one helicity 'survives' as
     // defined by the helicities of the spinors at the end of the chain.
     current cur22, cur23, curb3, cur2b, cur1a, ep;
     joo(p2, helchain, p2, helchain, cur22);
     joo(p2, helchain, p3, helchain, cur23);
     jio(pb, helchain, p3, helchain, curb3);
     joi(p2, helchain, pb, helchain, cur2b);
     joi(p1, heltop,   pa, heltop,   cur1a);
 
     const double t2t = (p2-pb)*(p2-pb);
     //Calculate Term 2 in Equation 3.23 in James Cockburn's Thesis.
     COM v2[4][4]={};
     for(int u=0; u<4;++u){
       for(int v=0; v<4; ++v){
         v2[u][v]=(cur22[v]*cur23[u]-cur2b[v]*curb3[u])/t2t;
       }
     }
     //Dot in current and eps
-    auto eta=HEJ::metric();
     //eps
     eps(refmom,pb,helb, ep);
     COM M2=0.;
     // Perform Contraction: g^{ik} j_{1a}_k * v2_i^j eps^l g_lj
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
-        M2+= eta(i,i)*cur1a[i]*(v2[i][j])*ep[j]*eta(j,j);
+        M2+= metric(i,i)*cur1a[i]*(v2[i][j])*ep[j]*metric(j,j);
       }
     }
     return M2;
   }
 
   COM qggm3(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3, bool helchain, bool heltop,
             bool helb,HLV refmom){
     current qqcur,ep,cur1a;
     const double s23 = (p2+p3)*(p2+p3);
     joo(p2,helchain,p3,helchain,qqcur);
     joi(p1, heltop, pa, heltop,cur1a);
     //Redefine relevant momenta as currents - for ease of calling correct part of vector
     const current kb{pb.e(), pb.x(), pb.y(), pb.z()};
     const current k2{p2.e(), p2.x(), p2.y(), p2.z()};
     const current k3{p3.e(), p3.x(), p3.y(), p3.z()};
-    auto eta=HEJ::metric();
     //Calculate Term 3 in Equation 3.23 in James Cockburn's Thesis.
     COM V3g[4][4]={};
     const COM kbqq=kb[0]*qqcur[0] -kb[1]*qqcur[1] -kb[2]*qqcur[2] -kb[3]*qqcur[3];
     for(int u=0;u<4;++u){
       for(int v=0;v<4;++v){
         V3g[u][v] += 2.*COM(0.,1.)*(((k2[v]+k3[v])*qqcur[u] - (kb[u])*qqcur[v])+
-                                    kbqq*eta(u,v))/s23;
+                                    kbqq*metric(u,v))/s23;
       }
     }
     eps(refmom,pb,helb, ep);
     COM M3=0.;
     // Perform Contraction: g^{ik} j_{1a}_k * (v2_i^j) eps^l g_lj
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
-        M3+= eta(i,i)*cur1a[i]*(V3g[i][j])*ep[j]*eta(j,j);
+        M3+= metric(i,i)*cur1a[i]*(V3g[i][j])*ep[j]*metric(j,j);
       }
     }
     return M3;
   }
 
   //Now the function to give helicity/colour sum/average
   double MqgtqQQ(HLV pa, HLV pb, HLV p1, HLV p2, HLV p3){
     // 4 indepedent helicity choices (complex conjugation related).
     //Need to evalute each independent hel configuration and store that result somewhere
     const COM Mmmm1 = qggm1(pa,pb,p1,p2,p3,false,false,false, pa);
     const COM Mmmm2 = qggm2(pa,pb,p1,p2,p3,false,false,false, pa);
     const COM Mmmm3 = qggm3(pa,pb,p1,p2,p3,false,false,false, pa);
     const COM Mmmp1 = qggm1(pa,pb,p1,p2,p3,false,true, false, pa);
     const COM Mmmp2 = qggm2(pa,pb,p1,p2,p3,false,true, false, pa);
     const COM Mmmp3 = qggm3(pa,pb,p1,p2,p3,false,true, false, pa);
     const COM Mpmm1 = qggm1(pa,pb,p1,p2,p3,false,false,true,  pa);
     const COM Mpmm2 = qggm2(pa,pb,p1,p2,p3,false,false,true,  pa);
     const COM Mpmm3 = qggm3(pa,pb,p1,p2,p3,false,false,true,  pa);
     const COM Mpmp1 = qggm1(pa,pb,p1,p2,p3,false,true, true,  pa);
     const COM Mpmp2 = qggm2(pa,pb,p1,p2,p3,false,true, true,  pa);
     const COM Mpmp3 = qggm3(pa,pb,p1,p2,p3,false,true, true,  pa);
 
     //Colour factors:
     const COM cm1m1 = 8./3.;
     const COM cm2m2 = 8./3.;
     const COM cm3m3 = 6.;
     const COM cm1m2 = -1./3.;
     const COM cm1m3 = -3.*COM(0.,1.);
     const COM cm2m3 = 3.*COM(0.,1.);
 
     //Sqaure and sum for each helicity config:
     const double Mmmm = real(cm1m1*pow(abs(Mmmm1),2)+cm2m2*pow(abs(Mmmm2),2)+
                              cm3m3*pow(abs(Mmmm3),2)+2.*real(cm1m2*Mmmm1*conj(Mmmm2))+
                              2.*real(cm1m3*Mmmm1*conj(Mmmm3))+2.*real(cm2m3*Mmmm2*conj(Mmmm3)));
     const double Mmmp = real(cm1m1*pow(abs(Mmmp1),2)+cm2m2*pow(abs(Mmmp2),2)+
                              cm3m3*pow(abs(Mmmp3),2)+2.*real(cm1m2*Mmmp1*conj(Mmmp2))+
                              2.*real(cm1m3*Mmmp1*conj(Mmmp3))+2.*real(cm2m3*Mmmp2*conj(Mmmp3)));
     const double Mpmm = real(cm1m1*pow(abs(Mpmm1),2)+cm2m2*pow(abs(Mpmm2),2)+
                              cm3m3*pow(abs(Mpmm3),2)+2.*real(cm1m2*Mpmm1*conj(Mpmm2))+
                              2.*real(cm1m3*Mpmm1*conj(Mpmm3))+2.*real(cm2m3*Mpmm2*conj(Mpmm3)));
     const double Mpmp = real(cm1m1*pow(abs(Mpmp1),2)+cm2m2*pow(abs(Mpmp2),2)+
                              cm3m3*pow(abs(Mpmp3),2)+2.*real(cm1m2*Mpmp1*conj(Mpmp2))+
                              2.*real(cm1m3*Mpmp1*conj(Mpmp3))+2.*real(cm2m3*Mpmp2*conj(Mpmp3)));
 
     // Factor of 2 for the helicity for conjugate configurations
     return (2.*(Mmmm+Mmmp+Mpmm+Mpmp)/3.)/(pa-p1).m2()/(p2+p3-pb).m2();
   }
 }
 
 // Extremal qqx
 double ME_Exqqx_qbarqQ(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqout, pqbarout);
 }
 
 double ME_Exqqx_qqbarQ(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqbarout, pqout);
 }
 
 double ME_Exqqx_qbarqg(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqout, pqbarout)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 double ME_Exqqx_qqbarg(HLV pgin, HLV pqout, HLV pqbarout, HLV p2out, HLV p2in){
   return MqgtqQQ(p2in, pgin, p2out, pqbarout, pqout)*K_g(p2out,p2in)/HEJ::C_F;
 }
 
 namespace {
 
   void CurrentMatrix(current j1, current j2, COM array[4][4]){
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         array[i][j]=j1[i]*j2[j];
       }
     }
   }
 
 
 //qqbar produced in the middle
   COM m1(current jtop, current jbot, bool hel2, HLV ka, HLV kb, HLV k2, HLV k3,
          std::vector<HLV> partons, unsigned int nabove){
     const double s23 = 2.*(k2*k3);
     const double sa2 = 2.*(ka*k2);
     const double sa3 = 2.*(ka*k3);
     const double s12 = 2.*(partons.front()*k2);
     const double s13 = 2.*(partons.front()*k3);
     const double sb2 = 2.*(kb*k2);
     const double sb3 = 2.*(kb*k3);
     const double s42 = 2.*(partons.back()*k2);
     const double s43 = 2.*(partons.back()*k3);
     HLV q1=ka-partons.front();
     for(unsigned int i=1;i<nabove+1;++i)
       q1-=partons.at(i);
     const HLV q2=q1-partons.at(nabove+2)-partons.at(nabove+1);
     const double t1 = q1.m2();
     const double t3 = q2.m2();
     current cur23;
     joo(k2,hel2,k3,hel2,cur23);
     const current curka{ka.e(),ka.px(),ka.py(),ka.pz()};
     const current curkb{kb.e(),kb.px(),kb.py(),kb.pz()};
     const current curk1{partons.front().e(),partons.front().px(),
                         partons.front().py(),partons.front().pz()};
     const current curk2{k2.e(),k2.px(),k2.py(),k2.pz()};
     const current curk3{k3.e(),k3.px(),k3.py(),k3.pz()};
     const current curk4{partons.back().e(),partons.back().px(),
                         partons.back().py(),partons.back().pz()};
     const current qc1{q1.e(),q1.px(),q1.py(),q1.pz()};
     const current qc2{q2.e(),q2.px(),q2.py(),q2.pz()};
 
-    //Metric tensor
-    auto eta=HEJ::metric();
     //Create the two bits of this vertex
     COM veik[4][4],v3g[4][4];
     for(int i=0;i<4;++i) {
       for(int j=0;j<4;++j){
         veik[i][j] = (cdot(cur23,curka)*(t1/(sa2+sa3))+cdot(cur23,curk1)*
                       (t1/(s12+s13))-cdot(cur23,curkb)*(t3/(sb2+sb3))-
-                      cdot(cur23,curk4)*(t3/(s42+s43)))*eta(i,j);
+                      cdot(cur23,curk4)*(t3/(s42+s43)))*metric(i,j);
       }
     }
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
         v3g[i][j] = qc1[j]*cur23[i]+curk2[j]*cur23[i]+curk3[j]*cur23[i]+
           qc2[i]*cur23[j]-curk2[i]*cur23[j]-curk3[i]*cur23[j]-
-          (cdot(qc1,cur23)+cdot(qc2,cur23))*eta(i,j);
+          (cdot(qc1,cur23)+cdot(qc2,cur23))*metric(i,j);
       }
     }
     //Now dot in the currents - potential problem here with Lorentz
     //indicies, so check this
     COM M1=0;
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
-        M1+= eta(i,i)*jtop[i]*(veik[i][j]+v3g[i][j])*jbot[j]*eta(j,j);
+        M1+= metric(i,i)*jtop[i]*(veik[i][j]+v3g[i][j])*jbot[j]*metric(j,j);
       }
     }
     M1/=s23;
     return M1;
   }
 
   COM m2 (current jtop, current jbot, bool hel2, HLV ka, HLV k2,
           HLV k3, std::vector<HLV> partons, unsigned int nabove){
     //In order to get correct momentum dependence in the vertex, forst
     //have to work with CCurrent objects and then convert to 'current'
     current cur22,cur23,cur2q,curq3;
     COM qarray[4][4]={};
     COM temp[4][4]={};
     joo(k2,hel2,k2,hel2,cur22);
     joo(k2,hel2,k3,hel2,cur23);
     joi(k2,hel2,ka,hel2,cur2q);
     jio(ka,hel2,k3,hel2,curq3);
     CurrentMatrix(cur2q, curq3, qarray);
     for(unsigned int i =0; i<nabove+1; ++i){
       joo(k2,hel2,partons.at(i),hel2,cur2q);
       joo(partons.at(i),hel2,k3,hel2,curq3);
       CurrentMatrix(cur2q, curq3, temp);
       for(int ii=0;ii<4;++ii){
         for(int jj=0;jj<4;++jj){
           qarray[ii][jj]=qarray[ii][jj]-temp[ii][jj];
         }
       }
     }
     HLV qt=ka-k2;
     for(unsigned int i=0; i<nabove+1;++i){
       qt-=partons.at(i);
     }
     const double t2=qt*qt;
-    //Metric tensor
-    auto eta=HEJ::metric();
     COM tempv[4][4];
     for(int i=0; i<4;++i){
       for(int j=0;j<4;++j){
         tempv[i][j] = COM(0.,1.)*(qarray[i][j]-cur22[i]*cur23[j]);
       }
     }
     COM M2=0.;
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
-        M2+= eta(i,i)*jtop[i]*(tempv[i][j])*jbot[j]*eta(j,j);
+        M2+= metric(i,i)*jtop[i]*(tempv[i][j])*jbot[j]*metric(j,j);
       }
     }
     M2/=t2;
     return M2;
   }
 
   COM m3 (current jtop, current jbot, bool hel2, HLV ka, HLV k2,
           HLV k3, std::vector<HLV> partons, unsigned int nabove){
     COM M3=0.;
 
     current cur23,cur33,cur2q,curq3;
     COM qarray[4][4]={};
     COM temp[4][4]={};
     joo(k3,hel2,k3,hel2,cur33);
     joo(k2,hel2,k3,hel2,cur23);
     joi(k2,hel2,ka,hel2,cur2q);
     jio(ka,hel2,k3,hel2,curq3);
     CurrentMatrix(cur2q, curq3, qarray);
     for(unsigned int i =0; i<nabove+1; ++i){
       joo(k2,hel2,partons.at(i),hel2,cur2q);
       joo(partons.at(i),hel2,k3,hel2,curq3);
       CurrentMatrix(cur2q, curq3, temp);
       for(int ii=0;ii<4;++ii){
         for(int jj=0;jj<4;++jj){
           qarray[ii][jj]=qarray[ii][jj]-temp[ii][jj];
         }
       }
     }
     HLV qt=ka-k3;
     for(unsigned int i=0; i<nabove+1;++i){
       qt-=partons.at(i);
     }
     const double t2t=qt*qt;
 
-    //Metric tensor
-    auto eta=HEJ::metric();
     COM tempv[4][4];
 
     for(int i=0; i<4;++i){
       for(int j=0;j<4;++j){
         tempv[i][j] = COM(0.,-1.)*(qarray[j][i]-cur23[j]*cur33[i]);
       }
     }
 
     for(int i=0;i<4;++i){
       for(int j=0;j<4;++j){
-        M3+= eta(i,i)*jtop[i]*(tempv[i][j])*jbot[j]*eta(j,j);
+        M3+= metric(i,i)*jtop[i]*(tempv[i][j])*jbot[j]*metric(j,j);
       }
     }
     M3/= t2t;
     return M3;
   }
 }
 
 double ME_Cenqqx_qq(HLV ka, HLV kb, std::vector<HLV> partons, bool aqlinepa,
                     bool aqlinepb, bool qqxmarker, int nabove){
   //Get all the possible outer currents
   current j1p,j1m,j4p,j4m;
   if(!(aqlinepa)){
     joi(partons.front(),true,ka,true,j1p);
     joi(partons.front(),false,ka,false,j1m);
   }
   if(aqlinepa){
     jio(ka,true,partons.front(),true,j1p);
     jio(ka,false,partons.front(),false,j1m);
   }
   if(!(aqlinepb)){
     joi(partons.back(),true,kb,true,j4p);
     joi(partons.back(),false,kb,false,j4m);
   }
   if(aqlinepb){
     jio(kb,true,partons.back(),true,j4p);
     jio(kb,false,partons.back(),false,j4m);
   }
 
   HLV k2,k3;
   if(!(qqxmarker)){
     k2=partons.at(nabove+1);
     k3=partons.at(nabove+2);
   }
   else{
     k2=partons.at(nabove+2);
     k3=partons.at(nabove+1);
   }
 
   //8 helicity choices we can make, but only 4 indepedent ones
   //(complex conjugation related).
 
   const COM Mmmm1 = m1(j1m,j4m,false,ka,kb,k2,k3,partons,nabove);
   const COM Mmmm2 = m2(j1m,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mmmm3 = m3(j1m,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mmmp1 = m1(j1m,j4m,true, ka,kb,k2,k3,partons,nabove);
   const COM Mmmp2 = m2(j1m,j4m,true, ka,   k2,k3,partons,nabove);
   const COM Mmmp3 = m3(j1m,j4m,true, ka,   k2,k3,partons,nabove);
   const COM Mpmm1 = m1(j1p,j4m,false,ka,kb,k2,k3,partons,nabove);
   const COM Mpmm2 = m2(j1p,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mpmm3 = m3(j1p,j4m,false,ka,   k2,k3,partons,nabove);
   const COM Mpmp1 = m1(j1p,j4m,true, ka,kb,k2,k3,partons,nabove);
   const COM Mpmp2 = m2(j1p,j4m,true, ka,   k2,k3,partons,nabove);
   const COM Mpmp3 = m3(j1p,j4m,true, ka,   k2,k3,partons,nabove);
 
   //Colour factors:
   const COM cm1m1=3.;
   const COM cm2m2=4./3.;
   const COM cm3m3=4./3.;
   const COM cm1m2 =3./2.*COM(0.,1.);
   const COM cm1m3 = -3./2.*COM(0.,1.);
   const COM cm2m3 = -1./6.;
 
   //Square and sum for each helicity config:
   const double Mmmm = real(cm1m1*pow(abs(Mmmm1),2)+cm2m2*pow(abs(Mmmm2),2)+
               cm3m3*pow(abs(Mmmm3),2)+2.*real(cm1m2*Mmmm1*conj(Mmmm2))+
               2.*real(cm1m3*Mmmm1*conj(Mmmm3))+2.*real(cm2m3*Mmmm2*conj(Mmmm3)));
   const double Mmmp = real(cm1m1*pow(abs(Mmmp1),2)+cm2m2*pow(abs(Mmmp2),2)+
               cm3m3*pow(abs(Mmmp3),2)+2.*real(cm1m2*Mmmp1*conj(Mmmp2))+
               2.*real(cm1m3*Mmmp1*conj(Mmmp3))+2.*real(cm2m3*Mmmp2*conj(Mmmp3)));
   const double Mpmm = real(cm1m1*pow(abs(Mpmm1),2)+cm2m2*pow(abs(Mpmm2),2)+
               cm3m3*pow(abs(Mpmm3),2)+2.*real(cm1m2*Mpmm1*conj(Mpmm2))+
               2.*real(cm1m3*Mpmm1*conj(Mpmm3))+2.*real(cm2m3*Mpmm2*conj(Mpmm3)));
   const double Mpmp = real(cm1m1*pow(abs(Mpmp1),2)+cm2m2*pow(abs(Mpmp2),2)+
               cm3m3*pow(abs(Mpmp3),2)+2.*real(cm1m2*Mpmp1*conj(Mpmp2))+
               2.*real(cm1m3*Mpmp1*conj(Mpmp3))+2.*real(cm2m3*Mpmp2*conj(Mpmp3)));
 
   //Result (averaged, without coupling or t-channel props). Factor of
   //2 for the 4 helicity configurations I didn't work out explicitly
   HLV prop1 = ka;
   for(int i=0; i<=nabove; ++i){
     prop1 -= partons[i];
   }
   const HLV prop2 = prop1 - k2 - k3;
 
   return (2.*(Mmmm+Mmmp+Mpmm+Mpmp)/9./4.) /
     ((ka-partons.front()).m2()*(kb-partons.back()).m2()*prop1.m2()*prop2.m2());
 }