
%======================================================================
%======================================================================
\section{QCD evolution at N$^3$LO}
\label{sec:n3lo-evolution}
In recent years significant progress in determining the perturbative
components needed for unpolarised QCD evolution at N$^3$LO has been
made (cf.\ \ref{sec:pqcd} for technical details on the evolution at
N$^3$LO).
%
This has now reached the stage that two PDF groups have released fits
at approximate N$^3$LO (aN$^3$LO)
accuracy~\cite{McGowan:2022nag,NNPDF:2024nan} along with their
combination in Ref.~\cite{Cridge:2024icl}.
%
Of the three contributions that are needed at full N$^3$LO accuracy
only two are fully known.
%
In particular the four-loop
$\beta$-function~\cite{vanRitbergen:1997va,Czakon:2004bu} and
associated mass thresholds~\cite{Chetyrkin:1997sg} have been known for
a very long time.
%
On the other hand the intricate calculations of the three-loop
matching relations needed for both the single and two mass VFNS were
only very recently
completed~\cite{Bierenbaum:2009mv,Ablinger:2010ty,Kawamura:2012cr,Blumlein:2012vq,ABLINGER2014263,Ablinger:2014nga,Ablinger:2014vwa,Behring:2014eya,Ablinger:2019etw,Behring:2021asx,Ablinger:2023ahe,Ablinger:2024xtt,Ablinger:2025awb}.
%
Finally the four-loop splitting functions entering the DGLAP equation
are currently not known exactly, except for certain $n_f$-dependent
terms~\cite{Gracey:1994nn,Davies:2016jie,Moch:2017uml,Gehrmann:2023cqm,Falcioni:2023tzp,Gehrmann:2023iah,Kniehl:2025ttz}.
%
However, enough Mellin-moments have been computed that together with
the exact pieces just mentioned and known small- and large-$x$
behaviours, approximate splitting functions suitable for phenomenology
can be reliably
determined~\cite{McGowan:2022nag,NNPDF:2024nan,Moch:2021qrk,Falcioni:2023luc,Falcioni:2023vqq,Moch:2023tdj,Falcioni:2024xyt,Falcioni:2024qpd}.
%
We have therefore incorporated the aforementioned pieces, using code
that is publicly available with those references, with the intention of
updating the splitting functions as they become more precisely known.

\subsection{Interface}

For the specific implementation in \hoppet version 2.0.0 we rely on
the approximations computed in
Refs.~\cite{Davies:2016jie,Moch:2017uml,Falcioni:2023luc,Falcioni:2023vqq,Moch:2023tdj,Falcioni:2024xyt,Falcioni:2024qpd} (FHMPRUVV),
the implementation of the three-loop (single-mass) VFNS coefficients as found in
Ref.~\cite{BlumleinCode}, which contains code associated with
Refs.~\cite{Ablinger:2024xtt,Fael:2022miw} and our own implementation
of the four-loop running coupling.\footnote{The implementation does
not allow for non-standard values of the QCD Casimir invariants beyond
three loops.}
%
Since the implementation here extends core features of \hoppet that
were already available in version 1.1.0, very little is needed on the
side of the user to invoke the evolution.
%
Most importantly all routines that take an \ttt{nloop} argument,
e.g.\ \ttt{InitDglapHolder}, \ttt{InitRunningCoupling},
\ttt{hoppetStart}, and \ttt{hoppetEval} now support \ttt{nloop = 4}.
%
The user also has a few choices they can make in terms of the
splitting functions used.
%
The approximate splitting functions can return
three different
choices corresponding to upper and lower edges of an uncertainty band and their
average. 
%
The user can control which one to use by setting the
\ttt{n3lo\_splitting\_variant} variable in the
\repolink{src/dglap_choices.f90}{dglap\_choices} module.
%
Allowed values are
\ttt{n3lo\_splitting\_Nfitav} (default),
\ttt{n3lo\_splitting\_Nfiterr1}, or \ttt{n3lo\_splitting\_Nfiterr2}.
%
When the exact splitting functions become available they will
correspond to \ttt{n3lo\_splitting\_exact}.
%
Similarly if a set of parametrisations of the exact splitting
functions become available they will correspond to
\ttt{n3lo\_splitting\_param}.

It is expected that more precise approximations will become
available in the future.
%
The user can control which series of approximation to use, again by setting
a variable in \repolink{src/dglap_choices.f90}{dglap\_choices}, called
\ttt{n3lo\_splitting\_approximation}.\footnote{In the streamlined
  interface, instead call \ttt{hoppetSetApproximateDGLAPN3LO(splitting\_approx)}.}
%
By default it is set to the most recent set of approximations by
the FHMPRUVV group. At the time of writing it can take one of three values:
%
\begin{itemize}
\item \ttt{n3lo\_splitting\_approximation\_up\_to\_2310\_05744}, with the
  approximations in papers up to and including Ref.~\cite{Moch:2023tdj};
\item \ttt{n3lo\_splitting\_approximation\_up\_to\_2404\_09701}, with the
  approximations in papers up to and including
  Ref.~\cite{Falcioni:2024xyt};
\item \ttt{n3lo\_splitting\_approximation\_up\_to\_2410\_08089}, the default value at the time of
  writing, with the approximations in papers up to and including
  Ref.~\cite{Falcioni:2024qpd}.
\end{itemize}
Besides the splitting functions, we have also incorporated the
single-mass thresholds up to N$^3$LO, as calculated in
Refs.~\cite{Bierenbaum:2009mv,Ablinger:2010ty,Kawamura:2012cr,Blumlein:2012vq,ABLINGER2014263,Ablinger:2014nga,Ablinger:2014vwa,Behring:2014eya,Ablinger:2019etw,Behring:2021asx,Ablinger:2023ahe,Ablinger:2024xtt}.
%
As of version 2.1.0, the \ntlo thresholds are available in two forms,
using code from Refs.~\cite{BlumleinCode,Fael:2022miw}.
%
One can choose which form to use by setting the
\ttt{n3lo\_nfthreshold} variable\footnote{%
  In the streamlined interface, call
  \ttt{hoppetSetN3LOnfthresholds(n3lo\_threshold\_choice)}. In \CPP,
  the constants are in the \ttt{hoppet} namespace.} to one of
\begin{itemize}
\item \ttt{n3lo\_nfthreshold\_libOME}:
  %
  this uses piecewise high-order Laurent-series expansions in different
  regions of $x$, with a stated accuracy of at least $2048\epsilon$
  where $\epsilon$ is the precision of a 64-bit real variable.
  % 
  It relies on the
  \ttt{libome} \CPP library from \url{https://gitlab.com/libome/libome}.
  %
  Initialisation time is below $1\,\text{s}$.
  %
  This is the default choice.
  
\item \ttt{n3lo\_nfthreshold\_exact\_fortran}: all contributions are
  exact, except for the $A_\text{hg}^\text{S}$ term in
  Eq.~(\ref{eq:hp-nf1}), which is based on piecewise expansions.
  %
  The exact contributions make extensive use of
  \texttt{hplog5}~\cite{FortranPolyLog} calls and lead to an
  initialisation time of $10{-}30\,\text{s}$.
  %
  The piecewise expansions for $A_\text{hg}^\text{S}$ are less
  accurate than with the \ttt{n3lo\_nfthreshold\_libOME} choice.
\end{itemize}

%   
% 
% and kindly supplied to us in the form of Fortran
% code~\cite{BlumleinCode,Fael:2022miw}.
% %
% Note that because many of these contributions are available only in
% exact form (i.e.\ using extensive
% \texttt{hplog5}~\cite{FortranPolyLog} evaluations), they involve a
% significant initialisation time, of the order of
% $10{-}30\,\text{s}$.\footnote{As \hoppet v2.0.0 was being finalised,
%   Ref.~\cite{BlumleinCode} was publicly released, notably
%   including faster, parametrised versions. We expect to update \hoppet
%   to include these in the near future.}

\subsection{Reference results}

In Table~\ref{tab:n3lo-evolve} we show the results of the full N$^3$LO
evolution in the VFNS, using the most up-to-date perturbative input at
the time of writing this release note. To assess the evolution we take
the initial condition of Ref.~\cite{Dittmar:2005ed} at an initial
scale $\sqrt{2}~\text{GeV}$ and $n_f=3$. We evolve to
$Q=100~\text{GeV}$. The numbers are obtained using parametrised NNLO
splitting functions, but exact mass thresholds at this order.
%
The table has been generated with $\texttt{dy}=0.05$,
$\texttt{dlnlnQ}=\text{dy/4}$.
%
Increasing $\texttt{dy}=0.10$ leaves the results unchanged at the
precision shown, while going to $\texttt{dy}=0.20$ for higher speed
would change the results by a relative amount below $10^{-4}$.

Table~\ref{tab:n3lo-evolve} cannot be directly compared to the
benchmarking tables of Ref.~\cite{Cooper-Sarkar:2024crx} because they
do not include the mass thresholds in the \ntlo evolution.
%
To facilitate comparisons we therefore additionally provide
Table~\ref{tab:n3lo-evolve-nf4}, corresponding to fixed-flavour ($n_f=4$)
evolution, with the choice of
\ttt{n3lo\_splitting\_approximation\_up\_to\_2310\_05744}.
%
Of the MSHT and NNPDF results, the NNPDF results (Table~2 of
Ref.~\cite{Cooper-Sarkar:2024crx}) are closer to ours.

The results in both tables can be regenerated with the help
of the following script, which uses the Python interface of
Section~\ref{sec:pyinterface}:
\masterlink{benchmarking/tabulation_crosscheck_2406_16188.py}.

\begin{table}[p]
  %  \footnotesize \centering
  \small \centering
  \begin{tabular}{c|cccccccc}
    $x$ &  $u-\bar u$ &$d-\bar d$ &$\bar d-\bar u$ &  $ 2(\bar u+\bar d)$  &$s+\bar s$&$c+\bar c$ &$b+\bar b$  &   $g$\\
    \toprule
$10^{-7}$ &  $1.0589^{-4}$ &  $4.8664^{-5}$ &  $8.1967^{-6}$ &  $1.6234^{+2}$ &  $8.0100^{+1}$ &  $7.7207^{+1}$ &  $6.5254^{+1}$ &  $1.1238^{+3}$ \\
$10^{-6}$ &  $5.9691^{-4}$ &  $3.2634^{-4}$ &  $3.3146^{-5}$ &  $7.6786^{+1}$ &  $3.7544^{+1}$ &  $3.5836^{+1}$ &  $2.9889^{+1}$ &  $5.1159^{+2}$ \\
$10^{-5}$ &  $3.0235^{-3}$ &  $1.7532^{-3}$ &  $1.3081^{-4}$ &  $3.5436^{+1}$ &  $1.7044^{+1}$ &  $1.6106^{+1}$ &  $1.3142^{+1}$ &  $2.2224^{+2}$ \\
$10^{-4}$ &  $1.4079^{-2}$ &  $8.2354^{-3}$ &  $4.9511^{-4}$ &  $1.5611^{+1}$ &  $7.2731^{+0}$ &  $6.7862^{+0}$ &  $5.3294^{+0}$ &  $8.8594^{+1}$ \\
$10^{-3}$ &  $6.0849^{-2}$ &  $3.5086^{-2}$ &  $1.7751^{-3}$ &  $6.3823^{+0}$ &  $2.7798^{+0}$ &  $2.5204^{+0}$ &  $1.8516^{+0}$ &  $3.0349^{+1}$ \\
$10^{-2}$ &  $2.3361^{-1}$ &  $1.3074^{-1}$ &  $5.8324^{-3}$ &  $2.2673^{+0}$ &  $8.5415^{-1}$ &  $7.0444^{-1}$ &  $4.6228^{-1}$ &  $7.7859^{+0}$ \\
$0.1$    &  $5.4846^{-1}$ &  $2.6950^{-1}$ &  $9.9965^{-3}$ &  $3.8453^{-1}$ &  $1.1248^{-1}$ &  $6.8296^{-2}$ &  $3.7899^{-2}$ &  $8.4964^{-1}$ \\
$0.3$    &  $3.4441^{-1}$ &  $1.2761^{-1}$ &  $2.9457^{-3}$ &  $3.4575^{-2}$ &  $8.8873^{-3}$ &  $3.9659^{-3}$ &  $2.0846^{-3}$ &  $7.8697^{-2}$ \\
$0.5$    &  $1.1790^{-1}$ &  $3.0597^{-2}$ &  $3.6526^{-4}$ &  $2.3206^{-3}$ &  $5.6808^{-4}$ &  $2.0185^{-4}$ &  $1.1382^{-4}$ &  $7.6337^{-3}$ \\
$0.7$    &  $1.9329^{-2}$ &  $2.9648^{-3}$ &  $1.2848^{-5}$ &  $5.2429^{-5}$ &  $1.2662^{-5}$ &  $3.4020^{-6}$ &  $2.4956^{-6}$ &  $3.7094^{-4}$ \\
$0.9$    &  $3.3153^{-4}$ &  $1.6737^{-5}$ &  $8.0961^{-9}$ &  $2.5214^{-8}$ &  $6.6432^{-9}$ &  $7.6153^{-10}$ &  $1.4323^{-9}$ &  $1.1716^{-6}$ 
  \end{tabular}
  \caption{N$^3$LO evolution of the initial condition given in Section
    4.4 of Ref.~\cite{Dittmar:2005ed}, using the same notation where
    $a\cdot10^{b} = a^b$. The evolution is performed taking the
    initial condition at $\sqrt{2}~\text{GeV}$ (just below the charm
    mass) and evolving in the VFNS up to $Q=100~\text{GeV}$, with a
    charm-quark pole mass of $1.414213563\GeV$ and a bottom-quark
    pole mass of $4.5\GeV$.
    %
    The NNLO splitting functions are the parametrised form
    (\texttt{nnlo\_splitting\_variant = nnlo\_splitting\_param}), to 
    facilitate comparisons by other groups, and the \ntlo splitting
    functions use the
    \ttt{n3lo\_splitting\_approximation = n3lo\_splitting\_approximation\_up\_to\_2410\_08089} choice.}
  \label{tab:n3lo-evolve}
\end{table}

\begin{table}[p]
  \small \centering
  \begin{tabular}{c|cccccccc}
    $x$ &  $u-\bar u$ &$d-\bar d$ &$\bar d-\bar u$ &  $ 2(\bar u+\bar d)$  &$s-\bar s$ &$s+\bar s$&$c+\bar c$ &   $g$\\
    \toprule
$10^{-7}$ &  $9.8370^{-5}$ &  $4.5171^{-5}$ &  $7.5013^{-6}$ &  $1.4888^{+2}$ &  $-2.9105^{-5}$ &             $7.3368^{+1}$ &  $7.2653^{+1}$ &  $1.0851^{+3}$ \\
$10^{-6}$ &  $5.6405^{-4}$ &  $3.0895^{-4}$ &  $3.0730^{-5}$ &  $7.1925^{+1}$ &  $-4.6739^{-5}$ &             $3.5111^{+1}$ &  $3.4544^{+1}$ &  $5.0392^{+2}$ \\
$10^{-5}$ &  $2.8946^{-3}$ &  $1.6810^{-3}$ &  $1.2302^{-4}$ &  $3.3868^{+1}$ &  $-3.5766^{-6}$ &             $1.6258^{+1}$ &  $1.5808^{+1}$ &  $2.2292^{+2}$ \\
$10^{-4}$ &  $1.3633^{-2}$ &  $7.9832^{-3}$ &  $4.7274^{-4}$ &  $1.5188^{+1}$ &  $\phantom{-}2.1123^{-4}$ &   $7.0599^{+0}$ &  $6.7033^{+0}$ &  $9.0268^{+1}$ \\
$10^{-3}$ &  $5.9567^{-2}$ &  $3.4382^{-2}$ &  $1.7232^{-3}$ &  $6.3028^{+0}$ &  $\phantom{-}3.9314^{-4}$ &   $2.7387^{+0}$ &  $2.4621^{+0}$ &  $3.1350^{+1}$ \\
$10^{-2}$ &  $2.3130^{-1}$ &  $1.2962^{-1}$ &  $5.7645^{-3}$ &  $2.2675^{+0}$ &  $-1.9644^{-4}$ &             $8.5255^{-1}$ &  $6.6402^{-1}$ &  $8.1568^{+0}$ \\
$0.1$    &  $5.5131^{-1}$ &  $2.7140^{-1}$ &  $1.0085^{-2}$ &  $3.8980^{-1}$ &  $-3.1812^{-4}$ &             $1.1388^{-1}$ &  $5.9843^{-2}$ &  $9.0615^{-1}$ \\
$0.3$    &  $3.5044^{-1}$ &  $1.3015^{-1}$ &  $3.0145^{-3}$ &  $3.5426^{-2}$ &  $-3.8409^{-5}$ &             $9.0900^{-3}$ &  $3.3507^{-3}$ &  $8.4431^{-2}$ \\
$0.5$    &  $1.2112^{-1}$ &  $3.1518^{-2}$ &  $3.7779^{-4}$ &  $2.3973^{-3}$ &  $-3.3053^{-6}$ &             $5.8501^{-4}$ &  $1.7709^{-4}$ &  $8.1568^{-3}$ \\
$0.7$    &  $2.0078^{-2}$ &  $3.0889^{-3}$ &  $1.3448^{-5}$ &  $5.4598^{-5}$ &  $-1.1810^{-8}$ &             $1.3105^{-5}$ &  $3.6963^{-6}$ &  $3.9248^{-4}$ \\
$0.9$    &  $3.5128^{-4}$ &  $1.7793^{-5}$ &  $8.6472^{-9}$ &  $2.6378^{-8}$ &  $-1.5848^{-10}$ &            $6.8222^{-9}$ &  $2.6776^{-9}$ &  $1.2262^{-6}$ 
  \end{tabular}
  \caption{N$^3$LO evolution of the initial condition given in Section
    4.4 of Ref.~\cite{Dittmar:2005ed}, using the same notation where
    $a\cdot10^{b} = a^b$. The evolution is performed taking the
    initial condition at $\sqrt{2}~\text{GeV}$ (just below the charm
    mass) and evolving in the FFN scheme ($n_f = 4$) up to
    $Q=100~\text{GeV}$.
    %
    The NNLO splitting functions are the parametrised form
    (\texttt{nnlo\_splitting\_variant = nnlo\_splitting\_param}), to 
    facilitate comparisons by other groups, and the \ntlo splitting
    functions use the
    \ttt{n3lo\_splitting\_approximation =
      n3lo\_splitting\_approximation\_up\_to\_2310\_05744} choice.
    %
    This table can be directly compared to tables 1 and 2 in Ref.~\cite{Cooper-Sarkar:2024crx}}
  \label{tab:n3lo-evolve-nf4}
\end{table}

%\ak{Started collecting some references here as there are many and they are not that easy to find...}
%\begin{itemize}
%\item As of 2024-04-26 this is the list of all papers on 4-loop splitting functions~\cite{Davies:2016jie,Moch:2017uml,Moch:2021qrk,Falcioni:2023luc,Falcioni:2023vqq,Gehrmann:2023cqm,Falcioni:2023tzp,Moch:2023tdj,Gehrmann:2023iah,Falcioni:2024xyt} and this is the list of what is actually implemented~\cite{Davies:2016jie,Moch:2017uml,Falcioni:2023luc,Falcioni:2023vqq,Moch:2023tdj,Falcioni:2024xyt}
%\item And this is the list of all papers relating to the 3-loop mass thresholds~\cite{Bierenbaum:2009mv,Ablinger:2010ty,Kawamura:2012cr,Blumlein:2012vq,ABLINGER2014263,Ablinger:2014nga,Ablinger:2014vwa,Behring:2014eya,Ablinger:2019etw,Behring:2021asx,Ablinger:2023ahe,Ablinger:2024xtt}. The code that we use relies on this set~\cite{Ablinger:2010ty,Blumlein:2012vq,ABLINGER2014263,Ablinger:2014nga,Ablinger:2014vwa,Behring:2014eya,Ablinger:2019etw,Behring:2021asx,Ablinger:2023ahe,Ablinger:2024xtt} plus the code from Bluemlein for which we do not yet have a reference.
%\end{itemize}

\begin{figure}[p]
  \centering
  \begin{subfigure}{0.49\textwidth}
    \centering
    \includegraphics[width=\textwidth,page=1]{figs-v2/n3lo_k_factors.pdf}\\[-1.5ex]
    \caption{}
    \label{fig:n3lok-benchmark-mtm}
  \end{subfigure}
  \begin{subfigure}{0.49\textwidth}
    \centering
    \includegraphics[width=\textwidth,page=3]{figs-v2/n3lo_k_factors.pdf}\\[-1.5ex]
    \caption{}
    \label{fig:n3lok-benchmark-nomtm}
  \end{subfigure}\\[1ex]
  \begin{subfigure}{0.49\textwidth}
    \centering
    \includegraphics[width=\textwidth,page=4]{figs-v2/n3lo_k_factors.pdf}\\[-1.5ex]
    \caption{}
    \label{fig:n3lok-nnpdf40-mtm}
  \end{subfigure}
  \begin{subfigure}{0.49\textwidth}
    \centering
    \includegraphics[width=\textwidth,page=6]{figs-v2/n3lo_k_factors.pdf}\\[-1.5ex]
    \caption{}
    \label{fig:n3lok-nnpdf40-nomtm}
  \end{subfigure}
  %
  \caption{Ratio of the PDFs at $100\GeV$ with \ntlo evolution versus
    NNLO evolution.
    %
    The evolution starts from the same initial condition
    at NNLO and \ntlo, at an initial scale
    $Q_0\simeq 1.41\GeV$.
    %
    In the upper plots, we use the standard benchmark initial
    condition.
    %
    In the lower plots, the initial condition is the
    \ttt{NNPDF40\_pch\_nnlo\_as\_01180} PDF~\cite{NNPDF:2021njg} at $Q_0$.
    %
    The left-hand plots show the ratio with \ntlo evolution
    including \ntlo mass thresholds, while the right-hand plots show
    the evolution without the \ntlo mass thresholds.
    %
    In all cases, the \ntlo evolution uses
    \ttt{n3lo\_splitting\_approximation\_up\_to\_2410\_08089}.  }
  \label{fig:n3lo-v-nnlo-ev}
\end{figure}

We close this section by illustrating the impact of \ntlo versus NNLO
evolution, Fig.~\ref{fig:n3lo-v-nnlo-ev}.
%
Let us first focus on the top-left plot,
Fig.~\ref{fig:n3lok-benchmark-mtm}.
%
We start with the benchmark initial condition at the standard low
scale of $Q_0 = \sqrt{2}\GeV$, just below a charm mass of $m_c =
1.414213563 \GeV$, so as to ensure that the evolution starts with $n_f
= 3$.
%
We then evolve the PDF separately with \ntlo and NNLO evolution, and
show the \ntlo/NNLO ratio at $Q = 100\GeV$.
%
Each line corresponds to a different flavour.
%
For light flavours, in the range that is relevant to the LHC at
central rapidities, $10^{-4} \lesssim x \lesssim 0.5$, the effect of
\ntlo corrections on the evolution of the light-flavour PDFs is
generally below a percent, and typically less than or around half a
percent.
%
For heavy flavour, the effect is much more significant, with a
$\sim 6\%$ effect on the charm distribution for $0.01 \lesssim x
\lesssim 0.1$ and about $-15\%$ at $x=0.5$.

Fig.~\ref{fig:n3lok-benchmark-nomtm} is the analogous plot with the
\ntlo mass-threshold contributions turned off.
%
It illustrates that the large effects on the charm and bottom PDFs are
a consequence mainly of \ntlo mass-thresholds, not the \ntlo splitting functions.
%
Comparing Figs.~\ref{fig:n3lok-benchmark-mtm} and
\ref{fig:n3lok-benchmark-nomtm} for light flavours, one sees that the $0.5\%$
effects are coming both from the \ntlo splitting functions and the
\ntlo mass thresholds.

Finally, Figs.~\ref{fig:n3lok-nnpdf40-mtm} and
\ref{fig:n3lok-nnpdf40-nomtm} show analogous plots with an initial
condition taken from the \ttt{NNPDF40\_pch\_nnlo\_as\_01180} PDF
set~\cite{NNPDF:2021njg} at a similar $Q_0 = 1.41 \GeV$ (again below
the charm threshold, $m_c = 1.51\GeV$).
%
The results are broadly similar, showing that our conclusions about the
size of \ntlo effects are robust with respect to the choice of PDF.



%======================================================================
%======================================================================
\section{Hadronic Structure Functions}
\label{sec:structure-funcs}
As of \hoppet version 2.0.0, the code provides access to the massless
hadronic structure functions. The structure functions are expressed as
convolutions of a set of massless hard coefficient functions and PDFs,
and make use of the tabulated PDFs and streamlined interface.  They
are provided such that they can be used directly for cross section
computations in DIS or VBF, as implemented for example in {\tt
  disorder}~\cite{Karlberg:2024hnl} and the {\tt proVBFH}
package~\cite{Cacciari:2015jma,Dreyer:2016oyx,Dreyer:2018qbw,Dreyer:2018rfu,Dreyer:2020urf,Dreyer:2020xaj}.

The massless structure functions have been found to be in good
agreement with those that can be obtained with
\APFELPP{}~\cite{Bertone:2013vaa,Bertone:2017gds} (at the level of
$10^{-5}$ relative precision).
%
The benchmarks with \APFELPP{} and the code used to carry them out are
described in detail in Ref.~\cite{Bertone:2024dpm} and at
\url{https://github.com/alexanderkarlberg/n3lo-structure-function-benchmarks}. A
simplified version of that benchmark is also included in the \hoppet
repository and can be found in
\masterlink{benchmarking/structure\_functions\_benchmark\_checks.f90}.
%
Technical details on the implementation of the structure functions in
\hoppet can be found in
Refs.~\cite{Dreyer:2016vbc,Karlberg:2016zik,Bertone:2024dpm}, and here
we mainly focus on the code interface.
%A small sample program
%that prints out the structure functions can be found in
%\masterlink{example\_f90/structure\_functions\_example.f90}.

The structure functions have been implemented including only QCD
corrections up to N$^3$LO using both the exact and parametrised
coefficient functions found in
Refs.~\cite{vanNeerven:1999ca,vanNeerven:2000uj,Moch:2004xu,Vermaseren:2005qc,Moch:2008fj,Davies:2016ruz,Blumlein:2022gpp},\footnote{Note
that the piece presented in Ref.~\cite{Davies:2016ruz} has not been
given in exact form. Only a parametrised version is available and
is what is being used in \hoppet.}  and can make use of PDFs evolved
at N$^3$LO as described in Sec.~\ref{sec:n3lo-evolution}. The
structure functions can also be computed using PDFs interfaced through
LHAPDF~\cite{LHAPDF}.

\subsection{Initialisation}
\label{sec:structure-funcs-init}

The structure functions can be accessed by using the
\repolink{src/structure_functions.f90}{structure\_functions} module.
%
They can also be accessed through the
streamlined interface by prefixing \ttt{hoppet}, as described
later in section~\ref{sec:structure-functions-streamlined}.
%
The description here corresponds to an intermediate-level interface, which
relies on elements such as the \ttt{grid} and splitting functions
having been initialised in the streamlined interface, through a call
to \ttt{hoppetStart} or \ttt{hoppetStartExtended}, cf.\
%
\ifreleasenote
Section~8 of Ref.~\cite{Salam:2008qg}.\footnote{Users needing a lower level
  interface should inspect the code in \masterlink{src/structure\_functions.f90}.}
\else
Section~\ref{sec:vanilla}.\footnote{Users needing a lower level
  interface should inspect the code in \masterlink{src/structure\_functions.f90}.}
\fi
%
After this initialisation has been carried out, one calls
\begin{lstlisting}
  call StartStrFct(order_max [, nflav] [, scale_choice] &
                  & [, constant_mu] [ ,param_coefs] [ ,wmass] [ ,zmass])
\end{lstlisting}
specifying as a minimum the perturbative order --- currently
\ttt{order\_max} $ \le 4$ (\ttt{order\_max} $ =1$ corresponds to LO).

If \ttt{nflav} is not passed as an argument, the structure functions
are initialised to support a variable flavour-number scheme (the
masses that are used at any given stage will be those set in the
streamlined interface).
%
Otherwise a fixed number of light flavours is used, as indicated by
\ttt{nflav}, which speeds up initialisation.
%
Note that specifying a variable flavour-number scheme only has an
impact on the evolution and on $n_f$ terms in the coefficient
functions.
%
The latter, however always assume massless quarks.
%
Hence in both the fixed and variable flavour-number scheme the
structure functions should not be considered phenomenologically
reliable if $Q$ is comparable to the quark mass.
%
%structure functions should only be considered reliable for $Q \gg m$.
%\commentpn{Actually this is also true in a fixed flavour number
%  scheme, as long as mass effects are not being included in the
%  coefficient functions, i.e. for $Q\gg m$ variable flavour is the
%  same as fixed flavours (including the flavour of mass $m$ as a light
%  flavour.)}\ak{I added a mentioning of the fixed flavour
%  limitations as well}
%\ak{Need to adapt this to what the code acutally does}
Together \ttt{xR}, \ttt{xF}, \ttt{scale\_choice}, and \ttt{constant\_mu} control
the renormalisation and factorisation scales and the degree of
flexibility that will be available in choosing them at later stages.
%
Specifically the (integer) \ttt{scale\_choice}
argument should be one of the following values (defined in the
\ttt{structure\_functions} module):
\begin{itemize}
\item \ttt{scale\_choice\_Q} (default) means that the code will always
  use $Q$ multiplied by \ttt{xR} or \ttt{xF} as the renormalisation
  and factorisation scale respectively (with \ttt{xR} or \ttt{xF} as
  set at initialisation).
\item \ttt{scale\_choice\_fixed} corresponds to a fixed scale
  \ttt{constant\_mu}, multiplied by \ttt{xR} or \ttt{xF} as set at
  initialisation.
\item \ttt{scale\_choice\_arbitrary} allows the user to choose
  arbitrary scales at the moment of evaluating the structure
  functions.
  %
  In this last case, the structure functions are saved as separate arrays, 
  one for each perturbative order, and with dedicated additional arrays
  for terms proportional to logarithms of $Q/\mu_F$. 
  % 
  This makes for a slower evaluation compared to the two other
  scale choices.
\end{itemize}
%
% The default value for \ttt{scale\_choice} is \ttt{scale\_choice\_Q} which means that $Q$
% multiplied by \ttt{xR} or \ttt{xF} is used as the renormalisation or
% factorisation scale respectively.
% %
% The choice \ttt{scale\_choice\_fixed} corresponds to a fixed scale
% \ttt{constant\_mu} (multiplied by \ttt{xR} or \ttt{xF}).
% %
% Should a user wish to use some arbitrary scale choice,
% \ttt{scale\_choice} should be set to \ttt{scale\_choice\_arbitrary}.
% %
% \gps{Do we want to introduce some constants such as
%   \ttt{scale\_choice\_Q=1}, \ttt{scale\_choice\_fixed=0}, \ttt{scale\_choice\_arbitrary=2}?} \ak{Yes, and done!}
% %
% In this last case, the structure functions are saved as arrays not only in
% $Q$ but also $\mu_R$ and $\mu_F$.
% %
% This makes for a slightly slower evaluation compared to the two other
% scale choices.

If \ttt{param\_coefs} is set to \ttt{.true.}\ (its default) then the
structure functions are computed using the NNLO and N$^3$LO
parametrisations found in
Refs.~\cite{vanNeerven:1999ca,vanNeerven:2000uj,Moch:2004xu,Vermaseren:2005qc,Moch:2008fj,Davies:2016ruz},
which are stated to have a relative precision of a few permille (order
by order) except at particularly small or large values of $x$.
%
Otherwise the exact versions are used.\footnote{The LO and NLO
coefficient functions are always exact as their expressions are very
compact. Note that for very large values of $x$ we switch to a
large-$x$ expansion for the regular part of non-singlet coefficient
functions at N$^3$LO, to avoid numerical instabilities in the exact
expressions as discussed in Appendix A of
Ref.~\cite{Bertone:2024dpm}.}
%
This however means that the initialisation becomes slow (about two
minutes rather than a few seconds).
%
Given the good accuracy of the parametrised coefficient functions,
they are to be preferred for most applications.
% 
% and since the parametrised
% expressions are good to a relative accuracy of $10^{-4}$ for most
% values of $x$, it is recommended to use the parametrised option for
% most applications.
% 
Note that the exact expressions also add to compilation time and need
to be explicitly enabled with the \texttt{-DHOPPET\_USE\_EXACT\_COEF=ON}
CMake flag.\footnote{At N$^3$LO they rely on an extended version of
  \ttt{hplog}~\cite{FortranPolyLog}, \texttt{hplog5} version 1.0, that
  is able to handle harmonic polylogarithms up to weight 5.}

The masses of the electroweak vector bosons are used only to calculate
the weak mixing angle, $\sin^2 \theta_W = 1 - (m_W/m_Z)^2$, which
enters in the neutral-current structure functions.

At this point all the tables that are needed for the structure
functions have been allocated.
%
In order to fill the tables, one first needs to set up the running
coupling and evolve the initial PDF with \ttt{hoppetEvolve}, as
described in
\ifreleasenote
Section~8.2 of Ref.~\cite{Salam:2008qg}.
\else
Section~\ref{sec:vanilla_usage}.
\fi

%
% Care should be taken here such that both the coupling and PDF
% evolution are carried out at the correct perturbative order and with
% mass thresholds as appropriate.
%
With the PDF table filled in the streamlined interface one calls
\begin{lstlisting}
  call InitStrFct(order[, separate_orders] [, xR] [, xF] [, flavour_decomposition])
\end{lstlisting}
specifying the order at which one would like to compute the structure
functions.
%
The logical flag \ttt{separate\_orders} should be set to \ttt{.true.}\ if one
wants access to the individual coefficients of the perturbative
expansion as well as the sum up to some maximum order, \ttt{order}.
%
With \ttt{scale\_choice\_Q} and \ttt{scale\_choice\_fixed}, the
default of \ttt{.false.}\ causes only the sum over perturbative orders
to be stored.
%
This gives faster evaluations of structure functions because it is
only necessary to interpolate the sum over orders, rather than
interpolate one table for each order.
%
With \texttt{scale\_choice\_arbitrary}, the default is \ttt{.true.},
which is the only allowed option, because separate tables for each
order are required for the underlying calculations.

Finally, the optional flag \ttt{flavour\_decomposition} controls an
experimental feature of giving access to the structure functions
decomposed into their underlying quark flavours without the associated
vector boson couplings. It is currently only possible to access the
structure functions in this way up to NLO, and since the feature is
not fully mature we invite interested readers to inspect the source
code directly for more information.

\subsection{Accessing the Structure Functions}
\label{sec:structure-funcs-access}
At this point the structure functions can be accessed as in the following example
\begin{lstlisting}
  real(dp) :: ff(-6:7), x, Q, muR, muF
  
  call StartStrFct(order_max = 4, scale_choice = scale_choice_Q)
  [...]
  call InitStrFct(order_max = 4)
  ff = StrFct(x, Q[, muR] [, muF])
\end{lstlisting}
at the value $x$ and $Q$.
%\gps{Added explicit scale choice arg in init}\ak{Not sure I understand, that's not the signature of the routine}
%
With \ttt{scale\_choice\_arbitrary}, the \ttt{muR} and \ttt{muF}
arguments must be provided.
%
With other scale choices, they do not need to be provided, but if they
are then they should be consistent with the original scale choice.
%
The structure functions in this example are stored in the
array \ttt{ff}. The components of this array can be accessed through
the indices 
\begin{lstlisting}
  integer, parameter :: iF1Wp = 1 !< F1 W+
  integer, parameter :: iF2Wp = 2 !< F2 W+
  integer, parameter :: iF3Wp = 3 !< F3 W+
  integer, parameter :: iF1Wm =-1 !< F1 W-
  integer, parameter :: iF2Wm =-2 !< F2 W-
  integer, parameter :: iF3Wm =-3 !< F3 W-
  integer, parameter :: iF1Z  = 4 !< F1 Z 
  integer, parameter :: iF2Z  = 5 !< F2 Z
  integer, parameter :: iF3Z  = 6 !< F3 Z
  integer, parameter :: iF1EM =-4 !< F1 $\gamma$
  integer, parameter :: iF2EM =-5 !< F2 $\gamma$
  integer, parameter :: iF1gZ = 0 !< F1 $\gamma$Z interference
  integer, parameter :: iF2gZ =-6 !< F2 $\gamma$Z interference
  integer, parameter :: iF3gZ = 7 !< F3 $\gamma$Z interference
\end{lstlisting}
For instance one would access the electromagnetic $F_1$ structure
function through \ttt{ff(iF1EM)}. It is returned at the \ttt{order\_max}
that was specified in \ttt{InitStrFct}.
%
The structure functions can also be accessed order by order if the
\ttt{separate\_orders} flag was set to \ttt{.true.} when initialising.
%
They are then obtained as follows
\begin{lstlisting}
  real(dp) :: flo(-6:7), fnlo(-6:7), fnnlo(-6:7), fn3lo(-6:7), x, Q, muR, muF
  [...]
  call InitStrFct(4, .true.)
  flo   = F_LO(x, Q, muR, muF)
  fnlo  = F_NLO(x, Q, muR, muF)
  fnnlo = F_NNLO(x, Q, muR, muF)
  fn3lo = F_N3LO(x, Q, muR, muF)
\end{lstlisting}
The functions return the individual contributions at each order in
$\as$, including the relevant factor of $\as^n$.
%
Hence the sum of \ttt{flo}, \ttt{fnlo}, \ttt{fnnlo}, and
\ttt{fn3lo} would be equal to the full structure function at N$^3$LO as
contained in \ttt{ff} in the example above.
%
Note that in the \ttt{F\_LO} etc.\ calls, the \ttt{muR} and \ttt{muF}
arguments are not optional and that when a prior scale choice has been
made (e.g. \ttt{scale\_choice\_Q}) they are required to be consistent
with that prior scale choice.

An example of structure function evaluations using the Fortran~90
interface is to be found in
\repolink{example_f90/structure_functions_example.f90}{example\_f90/structure\_functions\_example.f90}. 
%%
%\gps{added this link}\ak{It is also at the beginning of the section}

\subsection{Streamlined interface}
\label{sec:structure-functions-streamlined}
The structure functions can also be accessed through the streamlined
interface, so that they may be called for instance from C/\CPP{}. The
functions to be called are very similar to those described above.
%
For simple usage one can call 
\begin{lstlisting}
  call hoppetStartStrFct(order_max)
\end{lstlisting}
where \ttt{order\_max-1}
%
is the maximal
power of $\as$.
%
Alternatively, the extended version of the interface,
\ttt{hoppetStartStrFctExtended},  takes all the same arguments as
\ttt{StartStrFct} described above. One difference is that in order to
use a variable flavour scheme the user should set \ttt{nflav} to a
negative value. After evolving or reading in a PDF, the user then calls
\begin{lstlisting}
  call hoppetInitStrFct(order, separate_orders)
\end{lstlisting}
to initialise the actual structure functions. The structure functions
can then be accessed through the subroutines
\begin{lstlisting}
  real(dp) :: ff(-6:7), flo(-6:7), fnlo(-6:7), fnnlo(-6:7), fn3lo(-6:7), x, Q, muR, muF
  [...]
  call hoppetStrFct(x, Q, muR, muF, ff)        ! Full structure function
  call hoppetStrFctNoMu(x, Q, ff)              ! Full structure function, muR=muF=Q
  call hoppetStrFctLO(x, Q, muR, muF, flo)     ! LO term
  call hoppetStrFctNLO(x, Q, muR, muF, fnlo)   ! NLO term
  call hoppetStrFctNNLO(x, Q, muR, muF, fnnlo) ! NNLO term
  call hoppetStrFctN3LO(x, Q, muR, muF, fn3lo) ! N3LO term
\end{lstlisting}
The \CPP{} header contains indices for the structure functions and scale
choices, which are all in the \ttt{hoppet} namespace.
%
\begin{lstlisting}
  const int iF1Wp = 1+6;
  const int iF2Wp = 2+6;
  const int iF3Wp = 3+6;
  const int iF1Wm =-1+6;
  const int iF2Wm =-2+6;
  const int iF3Wm =-3+6;
  const int iF1Z  = 4+6;
  const int iF2Z  = 5+6;
  const int iF3Z  = 6+6;
  const int iF1EM =-4+6;
  const int iF2EM =-5+6;
  const int iF1gZ = 0+6;
  const int iF2gZ =-6+6;
  const int iF3gZ = 7+6;

  const int scale_choice_fixed     = 0;
  const int scale_choice_Q         = 1;
  const int scale_choice_arbitrary = 2;
\end{lstlisting}
Note that in \CPP{} the structure function indices start from 0 and that the \CPP{}
array that is to be passed to functions such as \ttt{hoppetStrFct}
would be defined as \ttt{double ff[14]}.

An example of structure function evaluations using the \CPP{} version
of the streamlined interface is to be found in
\repolink{example_cpp/structure_functions_example.cc}{example\_cpp/structure\_functions\_example.cc}
and a Python example is similarly to be found at
\masterlink{example_py/structure_function_example.py}.

Finally we present a small update on the results presented in
Ref.~\cite{Bertone:2024dpm}. That reference was published before the
full \ntlo evolution had been implemented in \hoppet, and the \ntlo
structure functions were therefore obtained with NNLO evolution. A
full update of the tables and plots is beyond the scope of the current
work, but for illustrative purposes we present here an updated version
of Fig.~3 of Ref.~\cite{Bertone:2024dpm}.
%
The update uses the full \ntlo evolution and is shown as
Fig.~\ref{fig:perturbativeconvergence}.
%
\begin{figure}[tb!]
  \centering\includegraphics[width=0.49\textwidth]{figs-v2/PerturbativeConvergence_Q_2_GeV.pdf}
  \centering\includegraphics[width=0.49\textwidth]{figs-v2/PerturbativeConvergence_Q_10_GeV.pdf}
  \caption{The structure function $F_2^{\rm NC}$ plotted as a function
    of $x_{\rm B}$ in the range $[10^{-5}:0.9]$ at $Q=2$~GeV (left)
    and $Q=10$~GeV (right). Each plot displays the curves at LO, NLO,
    NNLO, and N$^3$LO with the middle panel showing the ratio to
    N$^3$LO. The lowest panel shows the ratio of the \ntlo with NNLO
    evolution to the full \ntlo result. Adapted from
    Ref.~\cite{Bertone:2024dpm} using the full \ntlo evolution.}
  \label{fig:perturbativeconvergence}
\end{figure}
%
It shows the full charged-lepton neutral current $F_2$ structure
function ($F_2^{\rm NC}$) at various perturbative orders, for two
values of $Q$ ($2$ GeV and $10$ GeV). It also shows the relative
difference of the various orders with respect to \ntlo, and in the
lower panel the ratio of the \ntlo result of
Ref.~\cite{Bertone:2024dpm} to that obtained here with the full
evolution.
%
As can be seen by comparing to the original figures and from the lowest
panel, the \ntlo evolution has the effect at low $Q$ of increasing the
\ntlo $F_2^{\rm NC}$ by a few percent, at least for $x$-values in the range
$10^{-4}$ to $10^{-1}$.
%
%This is most easily seen from the shifts in the LO, NLO and NNLO
%curves in the lower panel, which are all ratios to the \ntlo (with
%\ntlo evolution here, without in Ref.~\cite{Bertone:2024dpm}.)


% ======================================================================
\section{Evolution including QED contributions}
\label{sec:qed-evolution}

The combined QED+QCD evolution, as implemented in \hoppet since
version 2.0.0 (and earlier in a dedicated \ttt{qed} branch), was first
described in
Refs.~\cite{Manohar:2016nzj,Manohar:2017eqh,Buonocore:2020nai,Buonocore:2021bsf}.
%
The determination of which contributions to include follows a
consistent approach based on the so-called ``phenomenological'' counting
scheme.
%
Within this scheme, one considers the QED coupling $\alpha$ to be of order $\as^2$, and takes the
photon (lepton) PDF to be of order $\alpha L$ ($\alpha^2 L^2$), where
$L$ is the logarithm of the
ratio of the factorisation scale to a typical hadronic scale and is considered to be of order $L \sim 1/\as$.
In contrast, quark and gluon
PDFs are considered to be of order $(\as L)^n={\cal O}(1)$.\footnote{The
  above counting is to be contrasted with a ``democratic'' scheme, in
  which one considers $\alpha \sim \as \sim 1/L$ and where the aim would be to maintain
  the same loop order across all couplings and splitting functions,
  regardless of the relative numbers of QCD and QED couplings that
  they involved.}
%
From this point of view, NNLO (3-loop) QCD evolution provides control of
terms of order up to $\as^{n+2} L^n \sim \as^2$.
%
To achieve a corresponding accuracy when including QED contributions,
\hoppet has been extended to account for
\begin{enumerate}
\item \label{item:qed1} 1-loop QED splitting functions~\cite{Roth:2004ti}, which first
  contribute at order $\alpha L \sim \as$, i.e.\ count as NLO QCD
  corrections;
  
\item \label{item:qed2} 1-loop QED running coupling, including lepton and quark
  thresholds, which first contributes at order $\alpha^2 L^2 \sim
  \as^2$, i.e. like NNLO QCD; 
  
\item \label{item:qed3} 2-loop mixed QCD-QED splitting
  functions~\cite{deFlorian:2015ujt},
  %\footnote{One-loop triple collinear splitting functions with photons have been computed in refs~\cite{Sborlini:2014mpa,Sborlini:2014kla}};,
  which first contribute at order
  $\alpha \as L \sim \as^2$, i.e.\ count as NNLO QCD corrections;

\item \label{item:qed4} optionally, the 2-loop pure QED $P_{\ell q}$ splitting
  function~\cite{deFlorian:2016gvk}, which brings absolute accuracy
  $\alpha^2 L\sim \as^3$ to the lepton distribution (which starts at
  $\alpha^2 L^2 \sim \as^2$).\footnote{The implementation of the 2-loop $P_{\ell q}$ splitting
  function in the hoppet code (\ttt{Plq\_02} in the code) was carried out by Luca Buonocore.}
  %
  In an absolute counting of accuracy, this is not needed.
  % 
  However, if one wants lepton distributions to have the same relative
  NLO accuracy as the photon distribution, it should be
  included.
\end{enumerate}
%
The code could be extended systematically to aim at a higher
accuracy. For instance, if one wished to reach N$^3$LO accuracy in the
phenomenological counting, one would need to include 3-loop mixed
QCD-QED splitting functions at order $\alpha \as^2$, which contribute
at order $\alpha \as^2 L \sim \as^3$ but are currently not
available, the full 2-loop pure QED splitting functions~\cite{deFlorian:2016gvk}, 
%
and the 2-loop mixed QED-QCD contributions to the running
of the QED coupling, which contribute at order $\alpha^2 \as L^2 \sim \as^3$
(see e.g.\ \cite{Cieri:2018sfk}). 
%\commentpn{I would put the comma differently (as before, Gavin decides):
%   at order $\alpha \as^2$, which contribute
%at order $\alpha \as^2 L \sim \as^3$ but are currently not
%available, and the well-known 2-loop QED running coupling at order $\alpha \alpha_s$, which contributes at order $\alpha^2 \as L^2 \sim \as^3$.}
% 
%\comment{Discuss what is needed to go effectively to \ntlo? $\alpha^2$
%  splitting exists, $\alpha\as^2$ does not?}
%

The rest of this section is structured as follows:
Section~\ref{sec:qed-implementation} gives a technical discussion of
the implementation of the QED evolution.
%
Some readers may prefer to skip or skim this on a first reading.
%
Section~\ref{sec:qed-streamlined} shows how to get QED evolution in
the streamlined interface, which is relatively straightforward.

\subsection{Implementation of the QED extension}
\label{sec:qed-implementation}

\myparagraph{QED coupling}

A first ingredient is the setup of the QED coupling object, defined in
\ttt{module qed\_coupling}:
\begin{lstlisting}
  type qed_coupling 
     real(dp) :: m_light_quarks
     real(dp) :: mc, mb, mt
     integer  :: n_thresholds
     integer  :: nflav(3, 0:n_thresholds) ! first index: 1 = nleptons, 2=ndown, 3=nup
     real(dp) :: thresholds(0:n_thresholds+1)
     real(dp) :: b0_values(n_thresholds)
     real(dp) :: alpha_values(n_thresholds)
  end type qed_coupling
\end{lstlisting}
%
This is initialized through a call to 
\begin{lstlisting}
  subroutine InitQEDCoupling(coupling, m_light_quarks, &
   &                           m_heavy_quarks(4:6) [,value_at_scale_0])
    type(qed_coupling), intent(out) :: coupling
    real(dp),           intent(in)  :: m_light_quarks, m_heavy_quarks(4:6)
    real(dp), optional, intent(in)  :: value_at_scale_0 ! defaults to alpha_qed_scale_0
    [...]
  end subroutine InitQEDCoupling
\end{lstlisting}
%
It initialises the parameters relevant to the QED coupling and its
running.  The electromagnetic coupling at scale zero is set by default
to its PDG Thomson value~\cite{ParticleDataGroup:2022pth} value, unless the optional
argument \ttt{value\_at\_scale\_0} is provided, in which case the
latter is taken.
% as the value of the QED coupling at zero momentum.
%\commentpn{prefer:  is set by default
%to its PDG~\cite{ParticleDataGroup:2022pth} Thomson value, unless the optional
%argument \ttt{value\_at\_scale\_0} is provided, in which case the
%latter is used.}

The running is performed at leading order level, using seven
thresholds: a common effective mass for the three light quarks
(\ttt{m\_light\_quarks}), the three lepton masses (hard-coded to their
2025 PDG values~\cite{ParticleDataGroup:2024cfk} in the
\ttt{src/qed\_coupling.f90} file), and the three masses of the heavy
quarks (\ttt{m\_heavy\_quarks(4:6)}).
%
The common value of the light quark masses is used to mimic the
physical evolution in the region $0.1\GeV \lesssim \mu \lesssim
1\GeV$, which involves hadronic states.
%
Using a value of $0.1055\GeV$ generates QED coupling values at the
masses of the $\tau$ lepton ($1/133.444$) and $Z$-boson ($1/127.938$)
that agree to within relative $\sim 1\times 10^{-4}$ (and $1\sigma$) accuracy with the
$\MSbar$ values ($1/(133.450 \pm 0.008)$ and $1/(127.930 \pm 0.006)$)
from the ``Electroweak and constraints on New Physics'' section of the
2024 Particle Data Group review~\cite{ParticleDataGroup:2024cfk}.

%\gps{We don't really have a
%  QED renormalisation scheme defined and $1/137$ that we use for $\alpha(0)$
%  is not, I think, an $\MSbar$ value; hence the rephrasing putting
%  MSbar in front of PDG rather than in front of our coupling.}
%\commentpn{Actually the Thomson value IS equal the MSbar value below all thresholds.}
% 
%Lepton masses are hard-coded to their PDG
%values~\cite{ParticleDataGroup:2022pth} in the
%\ttt{src/qed\_coupling.f90} file.
%
%The heavy-quark masses \ttt{quark\_masses(4:6)} are instead required as
%non-optional arguments.

The quark and lepton masses are used to set all thresholds where the
fermion content changes.
%
The values of the thresholds are contained in an array
\ttt{threshold(0:8)}.
%
The \ttt{threshold(1:7)} entries are active thresholds, while
\ttt{threshold(0)} is set to zero and \ttt{threshold(8)} to an
arbitrary large number (currently $10^{200}$).
%
For a given $Q$, the code identifies the index \ttt{i} such that
\ttt{threshold(i-1)}$<Q<$ \ttt{threshold(i)}.
%
The flavour content at a given $Q$ is then accessible through the
integer array \ttt{nflav(3,0:n\_thresholds)}, where the
\ttt{nflav(1:3,i)} entries indicate respectively the number of
leptons, down-type, and up-type quarks at a given $Q$.
%
%
The \ttt{nflav(:,:)} array is then used to compute the
$\beta_{0,\rm QED}$ function \ttt{b0\_values(1:n\_thresholds)} at the
seven threshold values and this is finally used to compute the value
of the QED coupling \ttt{alpha\_values(1:n\_thresholds)} at the
threshold values.
%
The function \ttt{Delete(qed\_coupling)} is also provided for
consistency with general \hoppet conventions, although in this case it does nothing.
%
After this initialisation, the function \ttt{Value(qed\_coupling,mu)} returns the QED coupling at
scale $\mu$.

\myparagraph{QED splitting matrices}

The QED splitting matrices are stored in the object

\begin{lstlisting}
  type qed_split_mat
     type(qed_split_mat_lo)   :: lo
     type(qed_split_mat_nlo)  :: nlo
     type(qed_split_mat_nnlo) :: nnlo
  end type qed_split_mat
\end{lstlisting}
defined in \ttt{qed\_objects.f90}. 
This contains the LO, NLO and NNLO splitting matrices 
\begin{lstlisting}
  ! a leading-order splitting matrix (multiplies alpha/2pi)
  type qed_split_mat_lo
     type(grid_conv) :: Pqq_01, Pqy_01, Pyq_01, Pyy_01
     integer         :: nu, nd, nl, nf
  end type qed_split_mat_lo

  ! a NLO splitting matrix (multiplies (alpha alpha_s)/(2pi)^2)
  type qed_split_mat_nlo
     type(grid_conv) :: Pqy_11, Pyy_11, Pgy_11
     type(grid_conv) :: Pqg_11, Pyg_11, Pgg_11
     type(grid_conv) :: PqqV_11, PqqbarV_11, Pgq_11, Pyq_11
     integer         :: nu, nd, nl, nf
  end type qed_split_mat_nlo

  ! a NNLO splitting matrix (multiplies (alpha/(2pi) )^2) 
  ! contains only Plq splitting!
  type qed_split_mat_nnlo 
     type(grid_conv) :: Plq_02
     integer         :: nu, nd, nl, nf
  end type qed_split_mat_nnlo
\end{lstlisting}     
%
%\commentpn{No paragraph here}
Above, \ttt{y} denotes a photon and the pairs of integers \ttt{01},
\ttt{11} and \ttt{02} denote the orders in the QCD and QED couplings,
respectively. Besides the number of quarks \ttt{nf}, these splitting
matrices also need the number of up-type (\ttt{nu}) and down-type
quarks (\ttt{nd}) separately, and the number of leptons (\ttt{nl}).
%
Note that the splitting functions of order $\alpha$ (i.e.\ \ttt{01})
for the leptons are simply obtained from the ones
involving quarks by adjusting colour factors and couplings.

A call to the subroutine 
\begin{lstlisting}     
  subroutine InitQEDSplitMat(grid, qed_split)
    use qed_splitting_functions
    type(grid_def),      intent(in)    :: grid
    type(qed_split_mat), intent(inout) :: qed_split
    [...]
  end subroutine InitQEDSplitMat
\end{lstlisting}     
initializes the \ttt{qed\_split\_mat} object \ttt{qed\_split} and sets all QED
splitting functions on the given \ttt{grid}.
%
The above QED objects can be used for any sensible value of the
numbers of flavours, on the condition that one first registers
the current number of flavours with a call to
\begin{lstlisting}
  QEDSplitMatSetNf(qed_split, nl, nd, nu)
\end{lstlisting}
where \ttt{nl}, \ttt{nd} and \ttt{nu} are respectively the current
numbers of light leptons, down-type and up-type quarks.
%
In practice, this is always handled internally by the QED-QCD
evolution routines, based on the thresholds encoded in the QED
coupling.\footnote{While the QCD splitting functions are initialised
  and stored separately for each relevant value of $n_f$, in the QED
  case the parts that depend on the numbers of flavours are separated
  out.
  %
  Only when the convolutions with PDFs are performed are the relevant
  $n_f$ and electric charge factors included.}
%
The one situation where a user would need to call this routine
directly is if they wish to manually carry out convolutions of the
QED splitting functions with a PDF.

Subroutines \ttt{Copy} and \ttt{Delete} are also provided for the
\ttt{qed\_split\_mat} type. As in the pure QCD case, convolutions with
QED splitting functions can be represented by the \ttt{.conv.} operator or
using the product sign \ttt{*}.


\myparagraph{PDF arrays with photons and leptons}

A call to the subroutine
\begin{lstlisting}  
  subroutine AllocPDFWithPhoton(grid, pdf)
    type(grid_def), intent(in) :: grid
    real(dp),       pointer    :: pdf(:,:)
    [...]
  end subroutine AllocPDFWithPhoton
\end{lstlisting}
allocates PDFs (\ttt{pdf}) including photons, while a call to
\begin{lstlisting}  
  subroutine AllocPDFWithLeptons(grid, pdf)
    type(grid_def), intent(in) :: grid
    real(dp),       pointer    :: pdf(:,:)
    [...]
    end subroutine AllocPDFWithLeptons
\end{lstlisting}
allocates PDFs including both photons and leptons.
%
The two dimensions of the \ttt{pdf} refer respectively to
the index of the $x$ value in the \ttt{grid}, and to the flavour index.
The flavour indices for photons and leptons %in the arrays \ttt{pdf}
are
given by
\begin{lstlisting}  
  integer, parameter, public :: iflv_photon   =  8
  integer, parameter, public :: iflv_electron =  9
  integer, parameter, public :: iflv_muon     = 10
  integer, parameter, public :: iflv_tau      = 11
\end{lstlisting}
where each \ttt{pdf(:,9:11)} contains the sum of a lepton and
anti-lepton flavour (which are identical). Note that if one were to
extend the calculation of lepton PDFs to higher order in $\alpha$,
then an asymmetry in the lepton and anti-lepton distribution would
arise, due to the \ttt{Plq\_03} splitting function.
%\ak{I think this
%  needs more explanation}.
%
%\gps{Giulia had found a ref that gave a nice explanation, maybe we can
%  add that ref here?}
%\commentpn{What about:
In fact, at that order, there are also graphs with three
electromagnetic vertices on the quark line and three on the lepton line, that change sign if the lepton line is charge-conjugated.\footnote{Analogous terms appear in the non-singlet three-loop splitting functions~\cite{NNLO-NS}.}
%
%
In that case it would become useful
to have separate indices for leptons and anti-leptons.

% \footnote{
%   Were one to use \hoppet to obtain the distribution of
%   partons inside a lepton, then it would become useful to separate
%   lepton and anti-lepton PDFs.
%   % 
%   However further considerations arise, notably for the treatment of
%   the $1-x \ll 1$ region.\commentgz{explain better?} 
%   % 
%   See for example discussion in Ref.~\cite{Frixione:2023gmf}\commentgz{update refs}.}

The subroutine \ttt{AllocPDFWithPhotons} allocates the \ttt{pdf}
array with the flavour index from -6 to 8,
while in the subroutine \ttt{AllocPDFWithLeptons},
the flavour index extends from -6 to 11.


\myparagraph{PDF tables with photons and leptons}

Next one needs to prepare a \ttt{pdf\_table} object forming the
interpolating grid for the evolved PDF's. We recall that the
\ttt{pdf\_table} object contains an underlying array \ttt{pdf\_table\%tab(:,:,:)},
where the first index loops over $x$ values, the second loops over
flavours and the last loops over $Q^2$ values.


This is initialized by a call to 
\begin{lstlisting}
  subroutine AllocPdfTableWithLeptons(grid, pdftable, Qmin, Qmax & 
  & [, dlnlnQ] [, lnlnQ_order] [, freeze_at_Qmin])
   use qed_objects
   type(grid_def),    intent(in)    :: grid
   type(pdf_table),   intent(inout) :: pdftable
   real(dp), intent(in)             :: Qmin, Qmax
   real(dp), intent(in), optional   :: dlnlnQ
   integer,  intent(in), optional   :: lnlnQ_order
   logical,  intent(in), optional   :: freeze_at_Qmin
   [...]
   end subroutine AllocPdfTableWithLeptons
\end{lstlisting}
that is identical to the one without photon or leptons, the only difference is that the
maximum pdf flavour index in \ttt{pdf\_table\%tab} now includes the photon and leptons.
%
An analogous subroutine \ttt{AllocPdfTableWithPhoton} includes the
photon but no leptons.

\myparagraph{Evolution with photons and leptons}


To fill a table via an evolution from an initial scale, one calls the subroutine 
\begin{lstlisting}
    subroutine EvolvePdfTableQED(table, Q0, pdf0, dh, qed_split, &
    &  coupling, coupling_qed, nloop_qcd, nloopqcd_qed, with_Plq_nnloqed)
       type(pdf_table),        intent(inout) :: table
       real(dp),               intent(in)    :: Q0
       real(dp),               intent(in)    :: pdf0(:,:)
       type(dglap_holder),     intent(in)    :: dh
       type(qed_split_mat),    intent(in)    :: qed_split
       type(running_coupling), intent(in)    :: coupling
       type(qed_coupling),     intent(in)    :: coupling_qed
       integer,                intent(in)    :: nloop_qcd, nloopqcd_qed
       logical,  optional,     intent(in)    :: with_Plq_nnloqed
       [...]
    end subroutine EvolvePdfTableQED   
\end{lstlisting}
where \ttt{table} is the output, \ttt{Q0} the initial scale and
\ttt{pdf0} is the PDF at the initial scale.
%
We recall that the lower and upper limits on 
scales in the table are as set at initialisation time for the table.
%
When \ttt{nloopqcd\_qed} is set to 1 (0) mixed QCD-QED effects are
(are not) included in the evolution.
%
Setting the variable \ttt{with\_Plq\_nnloqed=.true.}\ includes also the NNLO $P_{lq}$
splittings in the evolution.

To perform the evolution \ttt{EvolvePdfTableQED} calls the routine 
\begin{lstlisting}
    subroutine QEDQCDEvolvePDF(dh, qed_sm, pdf, coupling_qcd, coupling_qed,&
    &                     Q_init, Q_end, nloop_qcd, nqcdloop_qed, with_Plq_nnloqed)
       type(dglap_holder),     intent(in), target :: dh
       type(qed_split_mat),    intent(in), target :: qed_sm
       type(running_coupling), intent(in), target :: coupling_qcd
       type(qed_coupling),     intent(in), target :: coupling_qed
       real(dp),               intent(inout)      :: pdf(0:,ncompmin:)
       real(dp),               intent(in)         :: Q_init, Q_end
       integer,                intent(in)         :: nloop_qcd
       integer,                intent(in)         :: nqcdloop_qed
       logical,  optional,     intent(in)         :: with_Plq_nnloqed
       [...]
       end subroutine QEDQCDEvolvePDF
\end{lstlisting}
Given the \ttt{pdf} at an initial scale \ttt{Q\_init}, it
evolves it to scale \ttt{Q\_end}, overwriting the \ttt{pdf} array. 
%
In order to get interpolated PDF values from the table we use the
\ttt{EvalPdfTable\_*} calls, described in
\ifreleasenote
Section~7.2 of Ref.~\cite{Salam:2008qg}.
\else
Section~\ref{sec:acc_table}.
\fi
%
However the \texttt{pdf} array that is passed as an argument and that
is set by those subroutines should range not from \texttt{(-6:6)} but
instead from \texttt{(-6:8)} if the PDF just has photons and
\texttt{(-6:11)} if the PDF also includes leptons.\footnote{Index $7$
is a historical artefact associated with internal \hoppet bookkeeping
and should be ignored in the resulting \ttt{pdf} array.}

Note that at the moment, when QED effects are included, cached
evolution is not supported.


\subsection{Streamlined interface with QED effects}
\label{sec:qed-streamlined}
The streamlined interface including QED effects works as in the case of pure QCD evolution.
One has to add the following call
\begin{lstlisting}
  logical use_qed, use_qcd_qed, use_Plq_nnlo
  ...
  call hoppetSetQED(use_qed, use_qcd_qed, use_Plq_nnlo)
\end{lstlisting}
before using the streamlined interface routines.
%
The \ttt{use\_qed} argument turns QED evolution on/off at order
${\cal O}(\alpha)$ (i.e.\ items \ref{item:qed1} and \ref{item:qed2} in
the enumerated list at the beginning of
Sec.~\ref{sec:qed-evolution}).
%
The \ttt{use\_qcd\_qed} one turns
mixed QCD$\times$QED effects on/off in the evolution (i.e.\ item \ref{item:qed3}) and \ttt{use\_Plq\_nnlo} turns 
the order $\alpha^2 P_{\ell q}$ splitting function on/off (i.e.\
item \ref{item:qed4}).
%
Without this call, all QED corrections
are off.

With the above, the streamlined interface can then be used as normal.
%
E.g.\ by calling the \ttt{hoppetEvolve(...)} function to fill the PDF
table and \ttt{hoppetEval(x,Q,f)} to evaluate the PDF at a given $x$
and $Q$.
%
Note that the \ttt{f} array in the latter call must be
suitably large, e.g.\ \ttt{f(-6:11)} for a PDF with leptons.
%
Examples of the streamlined interface being used with QED evolution
can be found in
\begin{quote}
  \masterlink{example_f90/tabulation_example_qed_streamlined.f90}\\
  \masterlink{example_cpp/tabulation_example_qed.cc}
\end{quote}
%\gps{added this last paragraph}
%\gps{NB: to make links work, I've hard-coded them to the current
%  branch, but we should switch over to the master/main branch}

\section{Python interface}
\label{sec:pyinterface}
From version~2.0.0, \hoppet also includes a Python interface to the
most common evolution routines. It currently includes exactly the same
functionality as the streamlined interface, and can be called in much
the same way. The names of functions and routines are the same as in
the streamlined interface, but stripped of the \ttt{hoppet}
prefix. The interface can be obtained from \ttt{PyPi} by invoking
\begin{lstlisting}
  pip install hoppet
\end{lstlisting}
Alternatively the interface can be built by CMake with
\ttt{-DHOPPET\_BUILD\_PYINTERFACE=ON} (cf.\ Section~\Ref{sec:cmake}
for details on building with CMake). In both cases the interface
can be imported into a \ttt{Python} instance through \ttt{import
  hoppet}.
%
For a simple tabulation example, the user should take a look
at
\repolink{example_py/tabulation_example.py}{example\_py/tabulation\_example.py}
(and in the same directory for a number of other illustrative examples
of how to use the interface, including in conjunction with
LHAPDF~\cite{LHAPDF}). The interface uses SWIG (Simplified Wrapper and
Interface Generator) which therefore needs to be available on the
system if building with CMake. It can be installed through most
package managers (e.g.\ \texttt{apt install swig}, \texttt{brew
  install swig}) but can also be obtained from the SWIG GitHub
\href{https://github.com/swig}{repository}.

One significant difference between the Python interface and the
streamlined interface is that Python does not provide native support
for pointers.
%
A number of \CPP routines
fill an array of PDF flavours that is passed as a pointer
argument.
%
Instead in Python, those routines return a
Python list directly.
%
For instance, where in \CPP{}
one might have the call
%
\begin{lstlisting}
  #include "hoppet.h"
  [...]
  double x = ...;
  double Q = ...;
  double pdf[13];
  [...]
  hoppetEval(x, Q, pdf);
\end{lstlisting}
%
instead in Python one would have
%
\begin{lstlisting}
  import hoppet as hp
  [...]
  x = ...
  Q = ...
  pdf = hp.Eval(x,Q)
\end{lstlisting}
This is relevant not just for evaluation of PDFs, but also in setting
initial conditions. 
%
For example the \ttt{Assign}, \ttt{CachedEvolve}, and
\ttt{Evolve} routines should be passed a function
of 
% ets an
% argument that is a flavour array PDF interface as an input in
% the streamlined interface, now instead take a function of
\ttt{x} and \ttt{Q} that returns an object that corresponds to array
of flavours (it can be a \ttt{numpy}~\cite{Harris:2020xlr} array of a
Python list; see the \ttt{hera\_lhc(x,Q)} function in
\repolink{example\_py/tabulation_example.py}{tabulation\_example.py}
for an explicit example).

In addition to the examples provided in
\repolink{example\_py/}{example\_py/} we have also developed a small
tool that loads a grid from LHAPDF at an initial scale and evolves it
with \hoppet{} over a large range of $Q$. The resulting grids are then
compared between \hoppet{} and LHAPDF to determine the relative
accuracy of the LHAPDF grids. The tool, along with some documentation,
can be found at
\url{https://github.com/hoppet-code/hoppet-lhapdf-grid-checker}.

\section{CMake build system}
\label{sec:cmake}

In v1.x, \hoppet used a hand-crafted \ttt{./configure} script followed
by \ttt{make [install]}.
%
%
As of v2
% 
\hoppet uses CMake.\footnote{We thank Andrii Verbitskyi
  for providing much of this system.
  %
  Support for the old build system was retained in the briefly lived
  2.0.x series, but was retired in version 2.1.0 owing to the need to
  support compilation with both Fortran and \CPP, the latter for the
  \ttt{libome} library.}

For a typical user it will be enough to invoke the following lines
from the main directory 
\begin{lstlisting}
  cmake -S . -B build
  cmake --build build -j
  ctest --test-dir build [-j8]
  cmake --install build
\end{lstlisting}
% 
%   mkdir build; cd build
%   cmake ..
%   make [-j]
%   ctest [-j8]
%   make install [-j]
% 
This will compile and install \hoppet, along with the streamlined interface.
%
Note that \ttt{make install} will typically
install in a location that requires root privileges, unless a user has
specified a custom prefix (through
\ttt{-DCMAKE\_INSTALL\_PREFIX=/install/path}). A number of options can
be passed to CMake. They are documented in
\repolink{CMakeLists.txt}{CMakeLists.txt} and can be printed on screen
by a call to \ttt{cmake -LH ..} from the \ttt{build} directory.

Of particular note to most users are 
\begin{lstlisting}
  option(HOPPET_USE_EXACT_COEF    "Use exact coefficient functions"  OFF)
  option(HOPPET_BUILD_EXAMPLES    "Build examples" ON)
  option(HOPPET_ENABLE_TESTING    "Enable testing. Requires building the examples." ON)
  option(HOPPET_BUILD_BENCHMARK   "Build benchmark." ON)
  option(HOPPET_BUILD_PYINTERFACE "Build python interface." OFF)
  option(HOPPET_ENABLE_FPES       "Enable trapping for the floating point exceptions." OFF)
  option(HOPPET_BUILD_TESTDIR     "Enable build of the code in the test directory." OFF)
\end{lstlisting}
They can be set in the usual \ttt{cmake} way, e.g.\
\begin{lstlisting}
  cmake -S . -B build -DHOPPET_USE_EXACT_COEF=ON
\end{lstlisting}
to compile \hoppet with the exact coefficient functions.
%
Note that the Python interface is not compiled by default, because we
anticipate that users of Python will prefer to obtain \hoppet through
\ttt{pip install hoppet}.

\section{Saving LHAPDF grids}
\label{sec:lhapdf}
A minor new feature of this release is the possibility to save a
\hoppet{} table in the form of an LHAPDF6~\cite{LHAPDF} grid.
%Access to this functionality is also provided in the streamlined interface.
% GPS -> removed this because it's said below
The main routine has the following structure
\begin{lstlisting}
  subroutine WriteLHAPDFFromPdfTable(table, coupling, basename, pdf_index, &
                                 & iy_increment, flav_indices, flav_pdg_ids, flav_rescale)                                
    use qed_objects
    type(pdf_table),        intent(in) :: table
    type(running_coupling), intent(in) :: coupling
    character(len=*),       intent(in) :: basename
    integer,                intent(in) :: pdf_index
    integer,  optional,     intent(in) :: iy_increment
    integer,  optional,     intent(in) :: flav_indices(:), flav_pdg_ids(:)
    real(dp), optional,     intent(in) :: flav_rescale(:)
\end{lstlisting}
A user has to provide a \ttt{table} and associated \ttt{coupling}
object along with a string \ttt{basename} and the \ttt{pdf\_index} as
needed by LHAPDF.
%
If \ttt{pdf\_index} is equal to 0 then the routine
outputs the contents of the table in \ttt{basename\_0000.dat} and
writes a template \ttt{basename.info} again in LHAPDF
format.
%
\hoppet{} fills most of the entries in the \ttt{.info}
file, but a few need to be edited manually by the user.
%
For any other value of \ttt{pdf\_index}, only the corresponding
\ttt{.dat} file gets written.

By default, the code uses the same grid spacing as in the internal
\hoppet{} table (\ttt{iy\_increment = 1}) and prints all the possible
flavours of the PDF, even if they are zero.
%
The user can overwrite
this default behaviour by providing an array of flavours and their pdg
values.
%
If \ttt{iy\_increment > 1} a coarser grid is provided by
skipping over \ttt{iy\_increment - 1} points in the grid. The
\ttt{flav\_rescale} is currently needed for the lepton PDF which is
provided as the sum over flavour and anti-flavour and therefore needs
an extra factor half.
%
The \ttt{flav\_rescale} array only needs to be
provided if the user is also providing the array of flavours and pdf
values.

Finally the routine can also be accessed from the streamlined
interface for \CPP{} or Python usage. In this case the routine is
significantly simplified and only takes \ttt{basename} and the
\ttt{pdf\_index} arguments, for instance in \CPP{} like this
\begin{lstlisting}
  #include "hoppet.h"
  #include <string>
  ...
  int main () {
    ...
    const std::string basename = ...;
    const int pdf_index = ...;
    hoppetWriteLHAPDFGrid(basename, pdf_index);
    ...
  }
\end{lstlisting}
The routine writes the contents of the streamlined interface
\ttt{tables(0)} and hence requires that this object has been filled
either through a call to \ttt{hoppetAssign} or \ttt{hoppetEvolve}.

\section{Updated performance studies}
\label{sec:v2-performance}

In this section we present some updated performance studies relative
to
%
\ifreleasenote
Section~9 of Ref.~\cite{Salam:2008qg},
\else
Section~\ref{sec:benchmarks},
\fi
mainly reflecting updated hardware and compilers of 2025, but also the
standard nested grid choice that is obtained with the streamlined
interface or, from the modern Fortran interface by calling
\ttt{InitGridDefDefault(grid, dy, ymax[, order])}, with the default
choice of interpolation \ttt{order=-6}.
%
\ifreleasenote
The \ttt{InitGridDefDefault(...)} routine is new relative to v1 and sets up
the following grid
\begin{lstlisting}
  call InitGridDef(gdarray(4),dy/27.0_dp, 0.2_dp, order)
  call InitGridDef(gdarray(3),dy/9.0_dp,  0.5_dp, order)
  call InitGridDef(gdarray(2),dy/3.0_dp,  2.0_dp, order)
  call InitGridDef(gdarray(1),dy,         ymax  , order)
  call InitGridDef(grid,gdarray(1:4),locked=.true.)
\end{lstlisting}
Users will usually only need a different choice if they plan studies
at $x$ very close to $1$ or if they wish to explore fine optimisation of
grid choices.
\else
  The \ttt{InitGridDefDefault(...)} routine is the recommended default
  for setting up a grid, as described in Section~\ref{sec:grid}.
\fi

We split our study here into two parts: the accuracy of PDF evolution
and tabulation (section~\ref{sec:acc-pdf-evol}) and PDF evaluation
(section~\ref{sec:fastpdf}).
%
The latter also outlines new functionality for choosing interpolation
orders differently in the PDF evaluation versus the PDF evolution and
it includes comparisons to LHAPDF.

% %
% Most importantly we benchmark the accuracy and timing of the new
% N$^3$LO evolution. We also present updated plots at NNLO, to give a
% better idea of the performance of \hoppet{} on typical compilers and
% hardware in 2025.

\subsection{PDF evolution and tabulation}
\label{sec:acc-pdf-evol}

\begin{figure}[htbp]
    \centering
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth,page=2]{figs-v2/hoppet-v2.1-accuracy-speed-v-dy.pdf}
        \caption{}
        \label{fig:main-sub1}
    \end{subfigure}
    \hfill
    \begin{subfigure}{0.49\textwidth}
        \centering
        \includegraphics[width=\textwidth,page=5]{figs-v2/hoppet-v2.1-accuracy-speed-v-dy.pdf}
        \caption{}
        \label{fig:main-sub2}
    \end{subfigure}
    \caption{Accuracy (top) and timing (bottom) versus \texttt{dy} at
      (a) NNLO and (b) N$^3$LO in \hoppet{} v2.1.0.
      %
      The accuracy corresponds to the worst fractional accuracy for any flavour,
      at any $x$ value below the corresponding limit, as described in
      the text.
      %
      The timings were
      obtained on an \texttt{M2Pro} with \texttt{gfortran v15.1} and
      \texttt{-O3} optimisation. We have further used \texttt{ymax = 12} and
      \texttt{dlnlnQ = dy/4}.
      %
    }
    \label{fig:main}
\end{figure}

The studies are performed using the initial condition of
Ref.~\cite{Dittmar:2005ed} as detailed in
%
\ifreleasenote
Section~9.1 of Ref.~\cite{Salam:2008qg},
\else
Section~\ref{sec:Accuracy},
\fi
%
evolved in the VFNS scheme.
%
At NNLO we use the parametrised splitting
functions and mass thresholds, as per \hoppet defaults.
%
To
assess the accuracy we first create a reference run with a very high
density grid. We then run \hoppet{} with different values of grid
spacing \texttt{dy}, keeping \texttt{dlnlnQ = dy/4}.
%
The accuracy is then computed by looking at the largest relative
deviation from the reference run across either all flavours (all-flav)
or the light flavours (guds).
%
The full tabulation covers the range $10^{-5} < x < 1$,
$2 < Q < 10^4\, \text{GeV}$.\footnote{For tabulations extending to
  significantly smaller $x$ values, it can be advantageous to take a
  smaller \texttt{dlnlnQ} choice, e.g.\ \texttt{dlnlnQ = dy/8},
  because of the steep $\ln Q$ derivative of the parton
  distributions at the smallest $x$ values.}
%
At $x$ values close to $1$, the numerical precision degrades because the
parton distribution functions become a very steep function of $\ln
x$.
%
However, the parton distributions have small values there and so we
carry out our precision study with two potential upper limits on the
$x$ value being probed, $x < 0.9$ and $x < 0.7$.
%
We also exclude PDF flavours in the $x$ and $Q$ vicinity of any sign
change, as per Ref.~\cite{Salam:2008qg}.
% since the grids quickly
% deteriorate as $x \to 1$ due to divergent logarithmic derivatives we
% limit ourselves to $x < 0.9$ and $x < 0.7$ which cover any potential
% phenomenological application \ak{is this true?}.

The results for the accuracy study can be seen in the top panels of
Figures~\ref{fig:main-sub1}--\ref{fig:main-sub2} at NNLO and N$^3$LO
respectively, as a function of \texttt{dy}.
%
At NNLO, the accuracy comes out similar to previous
versions of \hoppet.
%
With \texttt{dy =
  0.2} one obtains a relative accuracy of $10^{-4}$ across all
flavours in the range $x < 0.9$.
%
At the finest grid spacing \texttt{dy = 0.05}, a relative accuracy of
few times $10^{-7}$ can be achieved, good enough for precise benchmark
comparisons as were for instance carried out in
Refs.~\cite{Dittmar:2005ed,Bertone:2024dpm}.
%
For comparison,
the recent benchmark of aN$^3$LO codes in
Ref.~\cite{Cooper-Sarkar:2024crx} reaches a relative precision of a
few times $10^{-4}$ at best (cf.\ the gluon PDF at $x = 10^{-2}$ in
Table~\ref{tab:n3lo-evolve-nf4} and Table 1 of
Ref.~\cite{Cooper-Sarkar:2024crx} which differ by $\sim 8\cdot 10^{-4}$.)

The scaling of the precision is roughly consistent with a power law in
\texttt{dy}.
%
In particular the Runge-Kutta algorithm for the $Q^2$ evolution is
expected to yield an error proportional to $\texttt{dlnlnQ}^4$, which,
given our choice of $\ttt{dlnlnQ} = \ttt{dy}/4$
translates into a behaviour $\sim \texttt{dy}^4$ in
Fig.~\ref{fig:main}.
%
The observed scaling is, if anything, slightly better than this, given
the factor of $1000$ improvement in accuracy when reducing \texttt{dy}
by a factor of $5$ from $0.2$ to $0.04$.
%
The precise scaling depends on whether it is Runge-Kutta or the
splitting function grid representation that dominates the error.
%
At N$^3$LO we observe a similar level of accuracy as at NNLO, except
for a slight worsening associated with the heavy-flavour components.

%\footnote{Note that at N$^3$LO we have omitted the 
%  all-flav, $x<0.9$ line.
%  %
%  For that case, we have observed issues affecting the accuracy at the
%  level of up to relative $10^{-5}$ accuracy.
%  %
%  The deterioration is there only when the N$^3$LO mass thresholds are
%  used and we suspect that it may be connected with difficulties in the
%  integration over the $x \to 1$ region of the mass-threshold
%  functions.
%  %
%  Given that the corresponding preliminary code has been kindly
%  provided to us ahead of publication~\cite{BlumleinCode}, we defer
%  more detailed study of this matter until the final code is ready.
%  %
%  We anticipate that it may be possible to address the problem by
%  switching to an expansion in powers (and logarithms) of $(1-x)$ for
%  $x \to 1$.  }


% for the all-flav, $x<0.9$ case with \texttt{dy} below \texttt{0.1}. We
% have confirmed that this deterioration is due to small numerical
% instabilities in the exact mass threshold expressions at N$^3$LO,
% presumably when $x \to 1$.
% %
% We
% therefore suspect that this could be improved by switching to as
% expansion in powers (and logarithms) of $(1-x)$ when $x \to
% 1$.\footnote{Once the final results from \cite{BlumleinCode} are
%   available we will revisit this question.}
% %
% Since this deterioration is only present at the relative $10^{-5}$
% level, and at very large values of $x$, it is unlikely to be visible
% in any realistic application.
% %
% In particular we note that for
% $x<0.7$ the accuracy is comparable to that at NNLO.

For the timing studies we again run \hoppet{} for different values of
\texttt{dy}, on an \texttt{M2Pro} (MacOS 15.6.1) with \texttt{gfortran v15.1} and
\texttt{-O3} optimisation.
%In order to avoid fluctuations we take the
%average over \ak{N} runs.
%
As discussed in \cite{Salam:2008qg}, the time spent in \hoppet for a
given analysis can be expressed as follows, depending on whether or
not one carries out cached evolution (pre-evolution):
\begin{subequations}
  \label{eq:timing-v2}
  \begin{align}
    t_\text{no pre-ev}   &= t_s + n_\alpha t_\alpha + n_i (t_i  + n_{xQ}\, t_{xQ})\,,\\
    t_\text{with pre-ev} &= t_s + n_\alpha (t_\alpha + t_p) + n_i (t_c + n_{xQ}\,
    t_{xQ})\,,
  \end{align}
\end{subequations}
where $t_s$ is the time for setting up the splitting functions and
mass threshold functions, $n_\alpha$ is the number of different
running couplings that one has, $t_\alpha$ is the time for
initialising the coupling,
%
$n_i$ is the number of PDF initial conditions that one wishes to
consider, $t_i$ is the time to carry out the tabulation and evolution
for a single initial condition, $n_{xQ}$ is the number of points in
$x,Q$ at which one evaluates the full set of flavours once per PDF
initial condition; in the case with cached evolution, $t_p$ is the
time for preparing a cached evolution and $t_c$ is the time for
performing the cached evolution. Finally, $t_{xQ}$ is the time it takes
to evaluate the PDFs at a given value of $(x,Q)$ once the tabulation
has been performed.

Here we focus on $t_s$, $t_i$, and $t_c$. The results can be seen in the
bottom panels of Figures~\ref{fig:main-sub1}--\ref{fig:main-sub2} at NNLO and
N$^3$LO respectively.
%
The expected scaling is
$t_i, t_c \sim (\text{\texttt{dy}}^2 \text{\texttt{dlnlnQ}})^{-1}$, which for our
choice of $\texttt{dlnlnQ} \propto\texttt{dy}$ reduces to
$1/\text{\texttt{dy}}^3$.
%
That is consistent with what is seen in the plot.
%
Turning to the setup time, at NNLO 
$t_s \sim 60-300\,\mathrm{ms}$ dominates over the evolution time across
almost all \texttt{dy} values that we study.
%
It scales slightly more slowly than $t_s \sim 1/\texttt{dy}$.

We note that when using cached evolution, the evolution time $t_c$ reaches as
little as $1\,\mathrm{ms}$ for \texttt{dy = 0.2}.
%
Comparing these numbers to those of  
%
\ifreleasenote
Table~2 of Ref.~\cite{Salam:2008qg},
\else
Table~\ref{tab:timings},
\fi
%
which were obtained with 2008 hardware and compilers, we see a
speed-up of roughly a factor 10, which we attribute mainly to
improvements in the hardware.

At \ntlo  evolution times ($t_i$ and $t_c$) are essentially identical to the
NNLO case: for $t_i$ the only extra operation that is needed is the
addition of the \ntlo splitting function to the lower-order ones and
at each $Q$ value, this involves $\order{N}$ operations for a $y$-grid
of size $N$, while the convolution itself involves $\order{N^2}$
operations.
%
For the cached evolution, there is no additional penalty, because the
\ntlo contributions are already included in the cached evolution
operators.
%
The initialisation times are somewhat larger, in a range from 250\,ms
to 1\,s.
%
The longer time is associated both with the approximate \ntlo
splitting functions and the mass threshold functions of
Ref.~\cite{BlumleinCode}, using the \ttt{n3lo\_nfthreshold\_libOME} option.
%
In practice the initialisation time remains adequate for most
interactive work.
%
In any long-running application where \hoppet either has to evolve or
access the evolved tables many times, the initialisation time is
insignificant.
%
% However the penalty can be noticeable in interactive work.
% %
% We understand that the final version of the code of
% Ref.~\cite{BlumleinCode} is significantly faster, and we hope to
% incorporate it into \hoppet in the near future.

% Were parametrised expressions of the mass threshold functions to
% become available, we expect that the initialisation time would be
% substantially reduced and we believe this would be useful for many
% users.

% We conclude that \hoppet{} is competitive compared to other evolution
% codes on the market both in terms of accuracy and speed.
% %
% \gps{We should cite them, and give comparisons where possible}

\subsection{Fast PDF access}
\label{sec:fastpdf}

Here we detail updates for faster PDF access within the
modern Fortran and streamlined interfaces (including the Python interface).
% 
% Outside the streamlined interface this was already
% possible, but only through manually modifying the source code.
%
In earlier versions of \hoppet, the interpolation was carried out by a
single routine that could flexibly handle any choice of $y$ and $Q$
interpolation orders up to some hard-coded maximum.
%
As of v2.0.0, a number of interpolation-order choices now have
dedicated code, which makes it easier for the compiler to optimise the
underlying assembly, e.g.\ with loop unrolling, giving speed gains of
almost a factor of three.
%
% 
Additionally new functionality allows the user to modify
the interpolation order of the \hoppet{} grids, trading accuracy
versus speed.

% Part of the speedup, which was not
% available in earlier releases, comes from manually unwinding a set of
% loops when the user specifies either quadratic or cubic interpolation,
% which are therefore faster than any of the other interpolation orders.



Specificaly, the user can now globally override any table-specific interpolation order
settings by calling one of
\begin{lstlisting}
  call hoppetSetYLnlnQInterpOrders(yorder, lnlnQorder)  ! streamlined interface
\end{lstlisting}
or
\begin{lstlisting}
  call PdfTableOverrideInterpOrders(yorder, lnlnQorder) ! F90 interface
\end{lstlisting}
A value of $\ttt{order}=2$ corresponds to quadratic interpolation, $3$ to
cubic interpolation, etc.\footnote{The \ttt{(yorder, lnlnQorder)}
  choices with dedicated code are $(2,2)$, $(3,3)$, $(4,4)$, $(5,4)$
  and $(6,4)$.}
%
The default interpolation order is quartic in the \ttt{lnlnQ}
direction, and \ttt{|grid\%order|-1} in the \ttt{y} direction (bounded
to be between 3 and 9 if outside that range).
%
These rather high interpolation orders help ensure good accuracy in a normal
\hoppet{} run even with $\ttt{dy=0.2}$, but come with a speed penalty
because of the larger number of operations.
%
However, if PDF evaluation represents a significant fraction of the
time for a user's code, the user can choose to lower the interpolation
order and still retain good accuracy by reducing \ttt{dy} and
\ttt{dlnlnQ}.

\begin{figure}[tbp]
    \centering
    %\begin{subfigure}{0.49\textwidth}
    %    \centering
        \includegraphics[width=0.49\textwidth,page=3]{figs-v2/hoppet-v2.1-accuracy-speed-v-dy.pdf}
    %    \caption{}
    %\end{subfigure}
    \caption{
      Study of the impact of the choice of the $y$ and $Q$ interpolation
      orders (\ttt{oY}, \ttt{oQ}) on accuracy (upper panel) and speed
      (lower panel) for evaluating
      the PDF table at a given $y$ and $Q$ point.
      %
      Note that the interpolation order that enters the splitting
      functions remains $6$ as in Fig.~\ref{fig:main}.
      %
      The timing corresponds to the \ttt{EvalPdfTable\_yQ(table,y,Q,vals)} modern
      Fortran call.
      %
      The timings have been
      obtained on an \texttt{M2Pro} with \texttt{gfortran v15.1} and
      \texttt{-O3} optimisation.
      %
      Calling \ttt{EvalPdfTable\_xQ(table,x,Q,vals)}, or the corresponding
      functions in the streamlined interfaces, adds about $5\ns$.
      %
      We have further used \texttt{ymax = 12} and
      \texttt{dlnlnQ = dy/4}.
      %
      Note that our accuracy definition is to show the \emph{worst}
      accuracy across any $[guds]$ flavour and $x$ value in range.
      %
      In practice, most points have a significantly higher
      accuracy. We also show results obtained by interpolating our
      grids with LHAPDF 6.5.5, calling it from its native \CPP
      interface to maximise its speed.  }
        \label{fig:interp-accuracy}
    %\label{fig:interp-accuracy}
\end{figure}


Fig.~\ref{fig:interp-accuracy} shows the accuracy and timing as a
function of \ttt{dy} for different interpolation order choices.
%
It compares evolution as in Fig.~\ref{fig:main}, followed by
interpolation with a given order.
%
It illustrates the significant loss in accuracy when decreasing
the interpolation order below $4$.
%
For orders of $4$ or higher, the limitation on accuracy is, however,
no longer just the interpolation order during PDF evaluation, but also
the orders that appear in the grid representation of the splitting
functions and of the $Q$ evolution when producing the original
table.\footnote{Recall that the former can be controlled with the
  \ttt{order} argument when setting up the grid.
  % 
  Currently, the latter is hard-coded as part of the Runge-Kutta
  evolution routines (cf.\ above).
}
%
The time that is shown in the lower panel corresponds to the
evaluation of all flavours in one go, via the
\ttt{EvalPdfTable\_yQ(...)} routine.
%
It ranges from $60\ns$ to $100\ns$ in going from the $(2,2)$ order
combination to $(5,4)$.
%
It is largely independent of \ttt{dy}.
%
Calls to evaluate a single flavour are only $2.5{-}3$ times faster,
because of overheads associated with identifying the grid location and
calculation interpolation coefficients, which are independent of the
number of flavours one evaluates.


Fig.~\ref{fig:interp-accuracy} also shows results with LHAPDF, calling
it from its native \CPP to maximise its speed.
%
We generate an LHAPDF grid for our standard benchmark initial
conditions, with a given \ttt{dy} spacing, cf.\
Section~\ref{sec:lhapdf}, and then examine the difference between the
LHAPDF evaluation and our high-accuracy reference.
%
The upper panel of Fig.~\ref{fig:interp-accuracy} shows the accuracy
with the same definition as used in our other performance results,
i.e.\ the worst relative accuracy observed anywhere for $x < 0.7$,
across any of the g,u,d,s flavours, as in the solid lines of
Fig.~\ref{fig:main}.
%
For most flavours and most of the $x$ region, the accuracy is better
than shown.
%
Interestingly, the LHAPDF accuracy is comparable to \hoppet's
quadratic interpolation.
%
At first sight this might be surprising given that LHAPDF uses cubic
interpolation: however it is our understanding that in the $x$
direction it uses a cubic \emph{spline}.
%
The spline effectively sacrifices one of the orders of accuracy in
function evaluation and instead uses it to ensure exact continuity of
the first derivative.
%
In contrast, \hoppet does not enforce continuity of the derivatives
but relies on the fact that for high interpolation order any
discontinuity of the derivatives will scale as a high power of the
grid spacing. 
%
Which choice is better may depend on the application. 
%
Concerning speed, we find that LHAPDF is somewhere in between our
$(4,4)$ and $(5,4)$ choices.

For use-cases where PDF evaluation speed is critical, one option is
to read in a PDF grid with LHAPDF, evaluate it at all \hoppet grid points and
then use \hoppet for the interpolation, using a fine grid spacing and
a $(2,2)$ interpolation choice.
%
We provide examples for how to do this in Fortran, \CPP, and Python:
\begin{itemize}
 \item \masterlink{example_f90/with-lhapdf/lhapdf_to_hoppet.f90}: it
   uses the module \ttt{hoppet\_lhapdf} and the associated
   \ttt{subroutine LoadLHAPDF(name, imem)}. The module is in the same
   directory and can be copied over to a user's application;
 \item
   \masterlink{example_f90/with-lhapdf/lhapdf_to_hoppet_allmembers.f90},
   same as above but the example shows how to read in and efficiently
   evaluate all
   LHAPDF members, with an illustration for computing the PDF uncertainty;
 \item \masterlink{example_cpp/with-lhapdf/lhapdf_to_hoppet.cc}, which
   includes a routine called \ttt{void load\_lhapdf\_assign\_hoppet(const
     string \& pdfname, int imem=0)} which can be included directly in a
   user's application;
 \item \masterlink{example_py/lhapdf_to_hoppet.py}, makes use
   of \ttt{hoppet.lhapdf.load()}, accessible using ``\ttt{from hoppet import lhapdf}''.
\end{itemize}
%
%The Fortran and \CPP examples are virtually identical.
%
%In each of them, there is a routine called
%\ttt{load\_lhapdf\_assign\_hoppet} that can be directly copied over to
%a user's application. In Python we instead provide an internal
%function \ttt{hoppet.lhapdf.load()} which can be made accessible
%through \ttt{from hoppet import lhapdf}.
%
The PDF can then be evaluated in the usual
way through a call to \ttt{hoppetEval} and likewise for the coupling
through \ttt{hoppetAlphaS}.\footnote{%
  For the PDF, \hoppet just
  interpolates its table, which is filled directly from LHAPDF.
  %
  For $\as$, \hoppet carries out its own evolution, which may have
  small differences from the LHAPDF $\as$, notably if the LHAPDF grid
  provides a coupling that is not an exact solution of the renormalisation
  group equation.
  %
  Another subtlety concerns the top threshold.
  %
  The LHAPDF grid files usually quote a physical top mass, and in the
  examples above, \hoppet reads the top mass and provides $\as$
  evolution at higher scales with $6$ light flavours.
  %
  However in some LHAPDF sets, the top-mass is only intended to
  indicate the value of the top mass used in the PDF fit, not an $n_f = 5
  \to 6$ threshold in the evolution.
  %
  The examples above all use a call similar to
  \ttt{if(!pdf->hasFlavor(6)) mt = 2.0*Qmax;} which manually overrides
  the top mass to a value outside the grid ranges if there is no top
  quark present in the set.
  %
  %Users wishing to use the \hoppet $\as$ evaluation may therefore wish
  %to manually communicate a large top mass to \hoppet to avoid a
  %switch to $6$-flavour running.
}
%
All three examples also print the timings to the screen so that users
can check speed on their hardware. Note that these examples are not
fully general, and caution should be exercised when using them. Users
with more advanced requirements are invited to contact the \hoppet
authors for assistance.


%
\begin{table}[htbp]
  \centering
  %\newcolumntype{C}[1]{>{\centering\arraybackslash}p{#1}}
  \newcolumntype{C}{>{\centering\arraybackslash}p{3cm}}
  \begin{tabular}{|c|c|CCC|}
    \hline
    \ttt{yorder} & \ttt{lnlnQorder} & \multicolumn{3}{c|}{Time per \ttt{hoppetEval} or
                                        LHAPDF \ttt{xfxQ}/\ttt{EvolvePDF} call (ns)} \\
     \hline
      &                          & Fortran & \CPP   & Python \\
    5 & 4                        & 108     & 108    & 295 \\
    4 & 4                        &  92     &  93    & 278 \\
    3 & 3                        &  76     &  77    & 258 \\
    2 & 2                        &  63     &  63    & 244 \\ \hline\hline
  %%hoppet-alphas                &  11     &  13    &  86 \\ \hline
    \multicolumn{2}{|l|}{\rule{0pt}{2.5ex}HOPPET 1.2.0}
                                 & 313     & 313    &  -- \\ %\hline
    %\hline
    \multicolumn{2}{|l|}{\rule{0pt}{2.5ex}LHAPDF 6.5.5}
                                 &520 &  87    & 1645 \\
    \multicolumn{2}{|l|}{\rule{0pt}{2.5ex}LHAPDF 6.5.5 + patch}
                               &114 &  87    & 1028 \\
  %%LHAPDF-alphas                &  28     &  18    &  76
    \hline
    %% Timings from Gavin on 2025-09-25
    %%
    %% Fortran: evaluated by calling build/example_f90/fast_pdf_evaluation
    %%      manually editing yorder and lnlnQorder
    %%
    %% C++: evaluated by calling build/example_cpp/fast_pdf_evaluation,
    %%      manually editing yorder and lnlnQorder
    %%
    %% Python: make install, make sure Python picks up that version
    %%      then run example_py/tabulation_example_lhapdf.py -pdf PDF4LHC21_40 -yorder 2 -lnlnQorder 2
    %%
    %% Zoom was on, and I picked the smallest consistent speed
    %% (i.e. I needed to see it a couple of times before putting it in) 
  \end{tabular}
  \caption{Time for a call to
  \ttt{hoppetEval}, which evaluates all flavours at a given $x,Q$
  point.
  %
  The rows show the timings with different
  interpolation orders.
  %
  The results have been obtained with the \ttt{PDF4LHC21\_40} set, on an M2Pro with
  gfortran-15.1, Apple clang version 17.0.0, O3 optimisation and
  Python 3.12.7.
  %
  The hoppet grid spacings are $\ttt{dy}=0.05$ and
  $\ttt{dlnlnQ}=\ttt{dy}/4$, with $\ttt{ymax} = 14$.
  %
  % For comparison, on the same system, the \hoppet v1.2.0 timing in
  % Fortran is $313\ns$, using
  % $\ttt{yorder}=5$ and $\ttt{lnlnQorder}=4$.
  % \\
  The table also shows the timings for the equivalent calls in
  LHAPDF~6.5.5.
  %
  Version 6.5.5 of LHAPDF, in its Fortran (\ttt{EvolvePDF(...)},
  \ttt{lhapdf\_xfxq(...)}) and Python (\ttt{pdf.xfxQ(...)}) 
  interfaces, 
   loops over an underlying call to single-flavour evaluation
  (\ttt{double PDF::xfxQ(int id, double x, double q))}), 
  which is why it is significantly slower than the \CPP interface,
  which evaluates all flavours in one go.
  %
  A proposed patch for the Fortran and Python interfaces has been submitted
  to LHAPDF with corresponding timings also indicated.
}
  \label{tab:interp_performance}
\end{table}

Table~\ref{tab:interp_performance} illustrates those timings for a range of
interpolation orders and across interfaces in different languages.
%
These tests have been carried out with the \ttt{PDF4LHC21\_40} set.
%
Again, they confirm that \hoppet with lower interpolation orders
can offer a speed gain relative to LHAPDF.
%
That speed gain is moderate in \CPP, more significant in Fortran and
Python.
%
As concerns Fortran, there are straightforward modifications to LHAPDF
that would improve its speed and these have been proposed to the
LHAPDF authors.

The PDF and $\alpha_s$ evaluation routines in \hoppet are
thread safe.
%
Other parts of the code, notably initialisation and evolution, are not.

% \comment{continue here}is only
% interested in fast PDF access, with similar accuracy as can typically
% be achieved by LHAPDF, then quadratic and cubic interpolation is
% sufficient, as long as the user sets up a dense enough
% grid\footnote{In our tests we have been using \ttt{dy = 0.05} with
% quadratic interpolation, finding agreement with LHAPDF, typically at
% the level of $\mathcal{O}(0.01\%)$.} 

% In table~\ref{tab:interp_performance} we show the performance of
% different interpolation orders. The timings were obtained on an
% \texttt{M2Pro} with \texttt{gfortran v15.1} and \texttt{-O3}
% optimisation. We also report the timings found by calling LHAPDF
% directly.\footnote{With the Fortran interface that comes with LHAPDF
% 6.5.5, \ttt{LHAGlue.cc}, we actually find that a call to
% \ttt{EvolvePDF} takes about 4 times longer than the quoted number.
% This is due to that function calling the \ttt{LHAPDF::xfxQ} method for
% each flavour. If instead one modifies the routine to access the PDF
% through a \ttt{std::vector} timings are as reported here.} As can be
% seen, with quadratic interpolation, \hoppet{} is as fast, if not
% faster, than LHAPDF. The difference between Fortran and \CPP{} in
% LHAPDF in all likelihood comes from the fact that the \CPP{} interface
% has to allocate a vector upon each call. It is likely that this could
% be optimised in LHAPDF. 



%%% Local Variables:
%%% TeX-master: "HOPPET-v2.0-release.tex"
%%% End:
