[4]P. Gras 1]University of Zurich, Zürich, Switzerland 2]Erlangen Centre for
Astroparticle Physics, Friedrich-Alexander-Universität, Erlangen-Nürnberg,
Germany 3]University College London, London, United Kingdom [4]IRFU, CEA,
Université Paris-Saclay, Gif-sur-Yvette, France 5]CERN, European Organization
for Nuclear Research, Geneva, Switzerland 6]Max-Planck-Institut für Physik,
Munich, Germany 7]Center for Advanced Systems Understanding, Görlitz, Germany
8]Helmholtz-Zentrum Dresden-Rossendorf, Dresden, Germany 9]Laboratory for
Particle Physics and Cosmology, Harvard University, Cambridge, MA, USA
10]ORIGINS Excellence Cluster, Garching, Germany 11]Ludwig-Maximilians-
Universität, Munich, Germany 12]Universidad Antonio Nariño, Ibagué, Colombia
13]Princeton University, Princeton, NJ, USA 14]Pacific Northwest National
Laboratory, Richland, WA, USA 15]University of Oregon, Eugene, OR, USA

# Potential of the Julia programming language for high energy physics
computing

Submitted to Computing and Software for Big Science

J. Eschle  T. Gal  M. Giordano<EMAIL_ADDRESS>B. Hegner  L. Heinrich
U. Hernandez Acosta  S. Kluth  J. Ling  P. Mato  M. Mikhasenko  A. Moreno
Briceño  J. Pivarski  K. Samaras-Tsakiris  O. Schulz  G. .A. Stewart  J.
Strube  V. Vassilev  [  [  [  *  [  [  [  [  [  [  [  [  [  [  [

###### Abstract

Research in high energy physics (HEP) requires huge amounts of computing and
storage, putting strong constraints on the code speed and resource usage. To
meet these requirements, a compiled high-performance language is typically
used; while for physicists, who focus on the application when developing the
code, better research productivity pleads for a high-level programming
language. A popular approach consists of combining Python, used for the high-
level interface, and C++, used for the computing intensive part of the code. A
more convenient and efficient approach would be to use a language that
provides both high-level programming and high-performance. The Julia
programming language, developed at MIT especially to allow the use of a single
language in research activities, has followed this path. In this paper the
applicability of using the Julia language for HEP research is explored,
covering the different aspects that are important for HEP code development:
runtime performance, handling of large projects, interface with legacy code,
distributed computing, training, and ease of programming. The study shows that
the HEP community would benefit from a large scale adoption of this
programming language. The HEP-specific foundation libraries that would need to
be consolidated are identified.

##  1 Introduction

High throughput computing plays a major role in high energy physics (HEP)
research. The field requires the development of sophisticated computing codes,
which are continuously evolving in the course of the research work. Computing
grids, connecting computer centers all around the world, are required to
process the experiments’ data [1]. Computer algebra systems and high
performance computers are used to build new models and to calculate particle
production cross sections.

Fig. 1 shows the expected needs for the ATLAS and CMS experiments [2, 3] at
the Large Hadron Collider (LHC) [4] and its successor, the high-luminosity LHC
(HL-LHC) [5], together with the estimated planned resources. A data processing
improvement from R&D is required for HL-LHC to fit within the planned
resources, which total $20\cdot 10^{9}$ HS06111https://www.spec.org/cpu2006/
units of CPU resource.

Figure 1: Estimated CPU required by the CMS (top) and ATLAS (bottom)
experiments for LHC and HL-LHC [6, 7].

The need to reconcile high performance with fast development has led to the
development of a C++ interpreter [8] that provides the convenience of a read-
eval-print-loop (REPL) interactive experience, also known as programming
shell, that supports just-in-time compilation, and allows the use of the same
programming language for compiled and interpreted code. The same analysis
framework ROOT [9, 10] can then be used with compiled code and interactively.
In addition to the REPL, ROOT supports Jupyter notebooks, which are another
convenient method for interactive use. The shortcoming of this approach is
that the use of a complex programming language is not optimal for easy and
fast coding. For this reason, another approach that consists of using two
languages, one optimal for fast development, typically Python, and one optimal
for high performance, typically C++, is often adopted.

Using two languages is not ideal: it expands the required area of expertise;
it forces the reimplementation, in the high-performance language, of pieces of
code originally written with the fast-development language when they do not
meet the required performance; and it reduces the reusability of code.

In 2009, J. Bezanson, A. Edelman, S. Karpinski, and V. B. Shah imagined a new
programming language to address this “two language problem” by providing high
performance and ease of programming [11, 12, 13] simultaneously. It has been a
successful approach. The new language, Julia, which has evolved year upon
year, is now used by many users. Julia is a dynamic language, similar to
Python, yet with a performance similar to C/C++. As of October 11, 2022, 8387
packages were registered in the Julia general registry [14], which are
accessible to Julia’s integrated package manager. Fig 2 shows the rapid growth
in the number of packages.

Figure 2: Number of packages registered in the Julia general repository that
can be installed by the integrated package manager (top) and of Julia language
code GitHub stars (bottom) as function of time. The trend of the star counts
is compared with Numba and Jax.

As demonstrated by M. Stanitzki and J. Strube [15], the Julia language is a
good alternative to the combination of C++ and Python for HEP data analysis
and it fulfils its promise to be an easy, high performance language. This
report extends that study. It explores the possible benefits of the adoption
of Julia as the main programming language for HEP, in place of C++-Python, in
a similar way as happened with the switch from Fortran to C++ in the late
90’s.

###  1.1 The programming language community

More important than a list of technical features, however, are the culture and
interests of a programming language’s community, because the language and its
implementation will evolve to satisfy those interests. For instance, the
Haskell community is focused on language theory, and is unlikely to put much
effort into optimization for high performance computing, and the Go community
is so focused on language simplicity that they have resisted try-catch logic
[16]. The Julia community’s interests are well aligned with HEP, and many
Julia users are in the sciences. We see this in some design choices inherited
from existing technical languages (Fortran, R, MATLAB, Wolfram), like 1-based
indexing, column-major arrays, and built-in N-dimensional arrays, and also in
the effort placed on interoperability with other languages: ccall, PyCall.jl,
RCall.jl, MathLink.jl, and JavaCall.jl. Julia is supported by NumFOCUS (like
many Python data science projects), and many of its most prominent
applications are in numerical computing: NASA spacecraft modeling [17];
climate science [18]; and the Celeste project [19], which achieved 1.54
Petaflops on the Cori II supercomputer [20], a first for a dynamic language.

###  1.2 Key features of Julia

To locate Julia in the space of programming languages, its primary features
are characterized below.

  * •

A single implementation, rather than an abstract language specification with
multiple slightly incompatible implementations. The Julia computing platform
is primarily implemented in Julia (most parts), C, and C++ (LLVM), and it has
a built-in REPL.

  * •

Every function, including those entered interactively, is compiled just-in-
time (JIT) using LLVM as the back end. Julia has no virtual machine, and the
JIT is eager (always compiles before execution), unlike the tracing/hot spot
JIT seen in LuaJIT [21] or metatracing [22] like PyPy [23].

  * •

Partly thanks to dynamic typing, and also being a JIT language, Julia fully
supports: type reflection, source code as a built-in data type, which enables
Lisp-like (hygienic) macros222https://en.wikipedia.org/wiki/Hygienic_macro.

  * •

Fast N-dimensional arrays that store elements in-place.

  * •

Multiple dispatch (MD): a function-call invokes the most specific method that
matches the type of _all_ arguments. While many languages support (opt-in)
multiple dispatch (C#, Common Lisp), Julia is the first language that uses MD
as the central paradigm333Not only Julia lacks OOP by design, it also has
highest number of methods and degree of specialization among languages that
support MD [11]. while focusing on performance. MD allows for a surprising
amount of code reuse and composition among packages that do not know about
about each other (often a problem with OOP languages), this is further
discussed in Sec. 1.3.

  * •

Apart from the lack of classes, Julia has a fairly standard mix of imperative
and functional programming styles. Immutability is encouraged by default, but
mutable structs and arrays are allowed and are frequently used.

  * •

Built-in parallel processing support. Any piece of a program can be marked for
execution in parallel. Threads are scheduled globally—allowing a multithreaded
function to call other multithreaded function—on available resources without
oversubscribing, saving the developer from the burden of taking care of the
number of threads. Computing distributed on several computers is supported.
Julia code can run natively on GPUs.

  * •

Objects are not reference counted, but are garbage collected. The garbage
collector is standard mark-and-sweep (like Python), generational (like Java),
but non-compacting, so pointers to objects are valid as long as the objects
remain in scope.

The manner in which polymorphism is supported is the most noticeable
difference with CBOO programming languages, like C++ and Python, and it merits
a dedicated discussion.

###  1.3 Polymorphism in C++, Python, and Julia

Polymorphism is the “ability to provide a single interface to entities of
different types” [24, 25]: a polymorphic function will accept arguments of
different types. Here we compare Julia with C++ and Python due to their
prevalence in HEP.

We can distinguish two classes of polymorphism [26, 27]: ad-hoc polymorphism
where a different implementation is provided for each set of types, and
universal polymorphism, where a single generic implementation is provided for
all the sets.

Function overloading is an example of ad-hoc polymorphism, while C++ templates
are an example of universal polymorphism. Ad-hoc polymorphism can be combined
with universal polymorphism using template specialization: several
implementations are provided, while each implementation can be generic, either
partially or totally. A particular universal polymorphism is based on
subtypes: the function scope is extended to all subtypes of its argument.

Polymorphism can be static, i.e., resolved at compile time, or dynamic, i.e.,
resolved at runtime. It can apply to a single entity e.g., one of the
arguments of a function, or multiple entities e.g., all the arguments of a
function. In a function call, the mechanism that selects the implementation to
execute according the passed argument types is called dispatch.

In the following of this subsection, we will compare polymorphism provided by
Julia, C++ and Python. Code examples illustrating our statements can be found
in Appendix A.

Polymorphism is provided in C++ by two paradigms: one based on class
inheritance, function overloading, and function overriding; the other based on
templates. The first provides ad-hoc and subtype polymorphisms over functions,
while the second provides universal and ad-hoc polymorphisms over both
functions and types. By exploiting the concepts feature introduced by C++ 20,
subtyping polymorphism support can be added to the templates. The
functionalities of the two paradigms overlap.

In C++, class non-static member functions take a special argument, the class
instance reference (x) or pointer (ptr), through a dedicated syntax, x.f() and
ptr->f(). Both static and dynamic polymorphism are supported over this
argument, while only static polymorphism is provided for the other arguments.
Object copy with implicit type conversion can make difficult to follow the
polymorphism flow of an object.

Static ad-hoc polymorphism is supported over the arguments of global functions
and static member functions. C++ class templates provide static universal
polymorphism. One notable usage is the containers of the standard template
library.

The class inheritance is twofold, it provides inheritance of the interface
through the subtype polymorphism previously described and inheritance of the
data fields, a type is an aggregation of its fields and all the fields of its
supertypes. The bounding of the two inheritances can result in breaking
encapsulation [28] and it is often advocated to prefer composition to
inheritance for the fields, as dictated by the “second principle of object-
oriented design” of Ref. [29].

Python provides single dynamic dispatch for the class instance argument of
member functions. It does not provide polymorphism for other arguments.
Multiple dispatch emulation can be implemented in the function using
conditions on the argument types or using a decorator [30].

Julia provides ad-hoc and universal polymorphism, including subtype
polymorphism [31], within a consistent multiple-dispatch paradigm. Extending
the dynamic dispatch of C++ to all arguments of a functions makes it extremely
powerful, especially in terms of code re-usability. The Julia multiple
dispatch exploits JIT compilation and the classification into static and
dynamic dispatches is less relevant here: a specialized function is compiled
only before its use, although the behavior is always consistent with dynamic
dispatch; inlining and other compile-time optimizations can be performed
despite the dynamic behavior. Nevetheless, this optimisation is subject to the
ability of the compiler to infer the type of the passed arguments and requires
some attention from the developer. In particular, when code performance is
important, the developer must make sure that the return type of a function can
be inferred from the types of the its arguments. Julia ad-hoc and universal
polymorphism uses a simple syntax similar to function overloading, but with
argument types specified only when required, either to extend a function or to
enforce the types of arguments.

The implementation of a two-argument function to be called by default, in
absence of a more specialized implementation fitting better with the types of
the passed arguments, will be defined as `function(x, y)...end}. Its
specialization for a first argument of type A or of a subtype of A will be
defined by suffixing the first argument \mintinline`juliax with `::A}. It can
be further specialized for a first argument of type A or a subtype of A and
second argument of type B or of a subtype B, by annotating both arguments
respectively with \mintinline`julia::A and `::B}, which will read as,
\mintinline`juliafunction f(x::A, y::B). Universal polymorphism for type
definition is supported by parametric types. In the following example the type
Point has two fields of the same type, that must be a subtype of the Number
type.

⬇

struct Point{T <:Number}

x ::T

y ::T

end

Contrary to C++, in Julia, subtyping does not involve field inheritance. Data
aggregation must be done using composition, enforcing the “second principle of
object-oriented design”. Subtypes are used to define a type hierarchy for the
subtype polymorphism. The hierarchy tree is defined with abstract types that
do not contain data, only the leaves of the tree can be a concrete type.
Because variable assignment and function argument passing do not trigger an
object copy, Julia is not affected by the difficulty encountered in C++
mentioned before. This is demonstrated with the “King of Savannah” example
discussed in the Appendix A.

##  2 HEP computing requirements

Because the program codes used in HEP research are very large, with high
interdependence, a code typically uses many open source libraries developed by
other authors; thus the effort to change programming language is
consequential. The adoption of a new language can happen only if it brings a
substantial advantage over the already used paradigm. The key advantage of
Julia that can make the language switch worthwhile is the simplification that
will arise from using a single language in place of a combination of two, C++
and Python. HEP computing is wide and includes many use cases: automation for
the controls of the experiment, data acquisition, phenomenology and physics
event generation, simulation of the physical experiment, reconstruction of
physics events444In HEP experiments, we observe collisions of subatomic
particles or atoms. The result of a collision that produces new particles is
called an event. Detectors, that can be complex apparatus as large as
$46\,\text{m}\times 25\,\text{m}$ producing tens of millions of MBytes of data
per second, are used to capture the event. from recorded data, analysis of the
reconstructed events, and more. We will review in this section the properties
required for event analysis, event reconstruction, event simulation and event
generation. We will start with general features, common to all the use cases.

###  2.1 General features

####  2.1.1 An easy language

The easy language is one-side of the high-level and high-performance coin
property that would motivate the adoption of Julia as a programming language.
It is easy at least in two ways: easy, imperative syntax, and free of strong
typing when writing code. The surface syntax of Julia largely resembles
Python, MATLAB (control flow, literal array), while also getting inspiration,
such as the do-block, from Lua and Ruby. It has all the high-levelness one
would expect from a language: higher-order functions (functions can be
returned and passed as variables), native N-dimensional arrays, nested
irregular arrays (arrays of different-size arrays), and a syntax for
broadcasting over arrays. As a syntax comparison example, a for-loop will look
like the following in Python, Julia, and C++.

⬇

# Python

a = 0\.

for i in range(1,11):

a += i

a /= 10\.

⬇

# Julia

a = 0\.

for i in 1:10

a += i

end

a /= 10\.

⬇

// C++

auto a = 0.;

for(auto i = 0;

i < 11.; ++i){

a += i;

}

a /= 10.;

We will note in this example that Julia is free of type declaration, just like
Python. In this example, we use the C++

    auto} type declaration feature to achieve the same goal. It is worthy of mention that, as in \Cpp, Julia code interpretation is not sensitive to changes in indentation: appending two spaces at the beginning of the last line, will change the behavior of the Python code only.
    Julia supports list comprehension, like Python, as illustrated in this example that creates a vector with the series $1, 1/2,\dots,1/10$:
    % %For single-column layout
    % \begin

multicols2

⬇

# Julia

v = [ 1/x for x in 1:10 ]

⬇

# Python

v = [ 1.0/x for x in range(1, 11) ]

NumPy [32] function vectorization is provided natively in Julia for all
functions and operators, including those defined by the user, through the
broadcast operator: a dot prefix is used to specify that the function must be
applied to each element of a vector or of an array. The syntax is illustrated
below.

⬇

# Julia

v = [1, 1, 1] ./ [1, 2, 3]

⬇

# Python

import numpy as np

v = np.array([1, 1, 1]) \ / np.array([1, 2, 3])

The following example illustrates the native support of linear algebra and
multi-dimensional arrays and highlight the concise syntax it provides. It
solves the simple equation,

$\begin{pmatrix}1&1\\\ 1&-1\end{pmatrix}x=\begin{pmatrix}2\\\ 0\end{pmatrix}$

.

⬇

# Julia

m = [ 1 1; 1 -1 ] \ [ 2, 0 ]

⬇

# Python

import numpy as np

m = np.linalg.solve(

np.array([[1, 1], [1, -1]]),

np.array([2, 0]))

Because broadcasting has its own syntax, Julia is able to use mathematical
operators “correctly” when they are not broadcast, instead of relying special
names e.g., matrix multiplication (np.matmul) and exponentiation
(np.ling.expm). Two additional language design choices are worth noting for
their contribution to make the language easy to use without sacrificing
performance. The first is the evaluation of the function default parameter
values, done at each call, instead of once for all in Python. Thanks to this
choice, a function `f(x, v=[])} that appends the element x to vector v and
returns the latter will always return a one-element when called as `f(x) in
Julia, while it will return a vector growing in size at each new call in
Python. The second is the copy performed by the updating operators (+=, *=,..)
instead of the in-place operation done by Python. In the Julia code, `A = [0,
1]; B = A; B += [1, 1]}, the operator `+= will not modify the content of the
vector A, it is syntactically strictly equivalent to B = B + [1, 1]), while it
will in the Python code

    A = np.array([0, 1]); B = A; B += np.array([1, 1])}. We judge these two Julia behaviors more natural and more likely to match to what a non-expert would expect when writing or reading the code.
    As we illustrated with few examples, the Julia language is as easy as Python and sometimes easier thanks to a native support of features provided by external packages in Python that allows for a more concise and natural syntax.
    \subsubsection

Performance The other major advantage to Juila is high performance. Julia
provides performance similar to C++, and in some cases even surpassing C++, as
can be seen in Fig 3. The shown comparison is obtained by repeating the
microbenchmark555https://julialang.org/benchmarks/ for Julia version 1.9.0rc1
and Python 3.9.2. C and Julia implementations use Open BLAS for the matrix
operations, while Python uses NumPy (version 1.24.2) and Open BLAS. This
benchmark compares the run time of some short algorithms implemented in a
similar way in the different languages. The results are divided by the time
the C/C++ implementation takes. The GNU compiler gcc version 10.2.1 has been
used. The test is performed on a laptop equipped with a 11th Gen Intel(R)
Core(TM<EMAIL_ADDRESS>CPU and 16 GB of random access memory (RAM)
running the Linux operating system compile for x86 64-bit architecture. The
64-bit flavor Open BLAS library version 0.3.21 is used. This setup is used for
all performance tests described in this paper, if not specified otherwise. The
score goes from 0.73 to 1.67 (smaller is better) for Julia and 1.12 to 107 for
Python. C is doing the best with respect to the two other languages for the
recursive Fibonacci algorithm, implemented in Julia as below.

⬇

fib(n) = n < 2 ? n : fib(n-1) + fib(n-2)

This benchmark tests the performance for recursive calls. While expert
developers typically avoid it for performance reasons, a recursive expression
is the easiest and most natural way to implement a recursive algorithm. The
mathematical series $u_{n+1}=f(u_{n})$ maps directly to a recursive computing
function call. We compute the 20th Fibonacci series elements, which results in
21,891 nested calls, a good example of recursive calls. The C/C++
implementation is doing better because of a tail recursion optimization
performed by the compiler, that removes one of the two nested calls, disabling
this optimization leads to performance a little worse than with Julia. The
gain from this optimization is far from the one obtained by using a for-loop
implementation instead of the recursion. Such implementation runs
$\approx$1000 times faster. The tail recursion optimization does not work for
the recursive quicksort, leading to similar performance from C/C++ and Julia
(7% difference in favor of C/C++).

Figure 3: Comparison of C/C++, Python and Julia language performance for a set
of short algorithms. Open BLAS, together with NumPy in the Python case are
used for matrix operation. The score is defined as the time to run the
algorithm divided by the time to run the C version of the same algorithm.

We can use LHC open data to test performance on HEP-oriented code. We make
this test with a di-muon analysis on CMS data of LHC Run-1, from 2011 and
2012. The analysis consists of measuring the spectrum of the mass of the
system made of a muon and an antimuon, produced in proton-proton collisions at
the center-of-mass energy $\sqrt{s}=7\,$TeV. It uses data in which the muons
and antimuons are already reconstructed and identified. It does not correct
for instrumental efficiencies, contrary to the published physics results.
Different implementations have been compared: the for-loop based Julia
implementation from Ref. [33], the equivalent for-loop based implementations
in Python and C++, the ROOT data frame (RDataFrame) implementation from Ref.
[34], its equivalent in C++ in two flavors, and a data frame based
implementation done in Julia using the DataFrames.jl package [35]. In the data
frame implementation, the table rows are first filtered to obtain a data frame
with the di-muon events of interest, then a column with the dimuon mass is
added to the data frame, and finally a histogram is filled. RDataFrames use
lazy operations, and only the histogram is materialized, limiting the memory
footprint. In the first flavor of the C++ implementation, the formula to
compute the mass is provided as a character string and the code for this
computation is compiled JIT. In the second flavor a user-defined C++ function
is provided to compute this mass. The input data are read from a file stored
in the ROOT format with compression turned off. The UnROOT.jl package [36]
(version 0.9.2) is used to read the file with the Julia code. This package is
written in pure Julia. The native ROOT library (version 6.26/10) serves to
read the files from C++ and Python. The GNU gcc compiler (version
Debian-10.2.1-6) is used with a level-three optimization (option -O3). When
JIT compilation is involved (the cases of Julia and JIT RDataFrame) the event
analysis function is first run on a ten-event data file to trigger
compilations before performing the timing on 1 billion events. For the Julia
implementations, subsequent compilations occur during the timing loop; they
represent only 1.1% of the time. In the case of JIT RDataframe an overhead
(time independent of number of processed events) of $5.0\pm 0.2\,$ms (C++
version) or $11.2\pm 2\,$ms (Python version) is present in spite of the warm-
up. The overheard is subtracted from the measurement. The obtained numbers are
provided in Table 1. We observe that slight changes of source code can change
the runtime of the C++ for-loop and native RDataFrame implementations beyond
the statistical uncertainties. This effect is estimated by varying the code
outside if the timed loop (addition of a print-out statement, change of code
statement order) and included in the quoted uncertainties. For the other
implementations, no significant change is observed and the quoted uncertainty
include the statistical component only (at 68% confidence level). In this
example, the for-loop Julia implementation runs the fastest, the C++ for-loop
implementation is slightly behind (11% slower). The Julia implementation using
data frames takes 21% less time to run than with C++ RDataFrame. The Python
for-loop implementation is 1000 times slower than with Julia. Delegating the
loop to an underlying compiled library (in our case the ROOT library) is not
sufficient to achieve good performance with Python: the RDataFrame python
implementation is 2.2 (resp. 2.8) times slower than the Julia data frame
(resp. for-loop) implementation. The C++ RDataFrame implementations are slower
than the Julia and C++ for-loop implementations by a factor from 1.4 to 2.1
depending on the implementations we compare. The dimuon spectrum obtained with
the Julia code is shown in Fig. 4. The data frame benchmark includes the
insertion of a column in the data frame with the dimuon mass. In the Julia
case, the insertion is not needed for the analysis itself, but keeping it is
interesting for benchmark purpose. The data frame returned by UnROOT does not
allow direct insertion and the selected rows are copied to a DataFrames.jl
data frame supporting such an insertion. That leaves room for improvements; we
estimate that improved tools that would allow such insertion with no copy
would reduce the runtime by 16%. For Python, the pure python library Uproot
[37] can be used instead of the native ROOT library to read the data. This
library loads all the data of a file into the memory, similar to the Julia
data frame implementation. The data can be provided as a set of Awkward Arrays
[38], NumPy arrays, or as a Pandas data frame [39]. All these data structures
support vectorized operations permitting a delegation of the event loop to
underlying compiled libraries improving the running time. The results are
shown in Table 2. The measurement is done with Uproot version 4.3.4 (with
awkward package version 1.10.3). The implementation using Awkward Arrays
operating on a vector of all events runs faster than the Python RDataFrame
implementation and is only 1.6 times slower than with the Julia for-loop. We
note that the Python’s performance is highly dependent on the algorithm
implementation: the time ratio with respect to the Julia for-loop goes up to
63 for a vectorized implementation using Pandas data frames and to 1200 with
the event loop. Running on a 61.5 million event file shows that the for-loop
and RDataFrame implementations scale well with larger input files with no
penalty on the event throughput as we could have expected. The other
implementations would require modifications in the code in order to process
events in chunks and reduce the memory usage. The awkward array implementation
requires 14.5 GiB, at the limit of 15 GiB available on the machine used for
the measurement, while the panda and julia data frame versions exceed this
limit. We see in this example that Julia is performing similar or better than
C++ frameworks. For an event loop, Python is slower by three-orders-of-
magnitude than Julia. Vectorization of event processing serves as a mitigation
of Python’s slowness by delegating the event loop to underlying compiled
libraries and sacrifice flexibility, without achieving the performance of C++
and Julia666Recently, it became possible to use Numba + Awkward Array to
enable fast loops, sacrificing some Python features due to the more strict
compiling model.

Table 1: Comparison of the runtime of the di-muon spectrum analysis for implementations performed in different programming languages. The time corresponds to a run over 1 million events. Implementation |  Time to process $10^{6}$ events
---|---
Julia for-loop | 0.147 | $\pm$ |  0.0014 s
Julia Dataframe | 0.1839 | $\pm$ |  0.0019 s
Python for-loop | 153.7 | $\pm$ |  5.7 s
Python RDataFrame | 0.4083 | $\pm$ |  0.0083 s
C++ for-loop | 0.1627 | $\pm$ |  0.0019 s
C++ RDataFrame | 0.2338 | $\pm$ |  0.00031 s
C++ RDataFrame JIT | 0.3051 | $\pm$ |  0.0023 s
Table 2: Runtime of the dimuon spectrum analysis for three Python implementations using the Uproot library to read the data. Implementation |  Time to process $10^{6}$ events
---|---
Vectorized with Awkward Arrays | 0.2343 | $\pm$ |  $0.0027\,$s
Vectorized with Panda dataframes | 9.225 | $\pm$ |  $0.081\,$s
For loop | 177.2 | $\pm$ |  $1.8\,$s
Figure 4: Dimuon spectrum obtained from the CMS open data of Run 2012 with the
Julia implementation of the analysis.

####  2.1.2 Interoperability with legacy code

HEP computing is based on a heritage of program code written over decades.
Interfacing to libraries developed in C++ and Fortran is unavoidable, apart
from the last-step of analysis domain (and even here it would still be an
attractive feature). Julia can natively call C and Fortran functions with no
overhead compared to calling them from their native language. Examples of such
calls are given in Listings 5 and 6. For convenience, a wrapper function
written in Julia can be used to handle errors, as in the example in Listing 7.
Bindings to Python are supported thanks to the PyCall package. The interface
is very convenient and transparent in both directions, Python from Julia and
Julia from Python, as we can see in the examples provided in Listings 8 and 9.
In a Jupyter notebook, in addition to calling a Julia function from a notebook
running a Python kernel using these interfaces and vice-versa, it is possible
to write Julia code in cells of a notebook using a Python Kernel, and mix
cells written in Julia and in Python languages, as illustrated in Fig. 10.

⬇

function compute_dot(DX::Vector{Float64},

DY::Vector{Float64})

@assert length(DX) == length(DY)

n = length(DX)

incx = incy = 1

product = ccall((:ddot_, "libLAPACK"),

Float64,

(Ref{Int32}, Ptr{Float64},

Ref{Int32}, Ptr{Float64},

Ref{Int32}),

n, DX, incx, DY, incy)

return product

end

Figure 5: Example of call from Julia of a function implemented in Fortran [40]

⬇

path = ccall(:getenv, Cstring, (Cstring,),

"SHELL")

println(unsafe_string(path))

Figure 6: Example of call of a function of a C-library from Julia [40]

⬇

function getenv(var::AbstractString)

val = ccall(:getenv, Cstring,

(Cstring,), var)

if val == C_NULL

error("getenv: undefined variable: ",

var)

end

return unsafe_string(val)

end

Figure 7: Example of a wrapper in Julia to handle errors from a c-library
function [40]

⬇

# Enable Python call:

using PyCall

\par# Inport a python module:

math = pyimport("math")

\par# Use it as a Julia module:

math.sin(math.pi / 4)

Figure 8: Python function can be called transparently from Julia. Example of
call of a function from the Python math package. [40]

⬇

$ python3

>>> import julia

>>> julia.install()

>>> from julia import Base

>>> Base.sind(90)

1.0

Figure 9: The Python package named julia allows use of Julia packages within
Python codes. Figure 10: Example of a Jupyter notebook mixing cells with Julia
and Python code.

The CxxWrap package [41] can be used to add Julia bindings to C++ libraries.
Once bound, the library is accessed transparently from Julia as if it was a
native Julia package. The `object.method(args...)} and
\mintinline`c++object_ptr->method(args...) C++ like method calls translate
into the Julia-like call `method(object, args...)}. The package philosophy is
similar to \codename`Boost.Python [42] and Pybind11 [43]: the bindings are
produced with few lines of C++ code, one line per class and one line per
method, which must be compiled as a shared library. The package provides all
the flexibility to expose a different Julia interface to the C++ one, for
instance to adapt it to the Julia context and style of programming. CxxWrap
internally uses the built-in Julia-C interface, used for the interface between
shared libraries and Julia. The C++ standard template library `vectors} and
\mintinline`c++val_arrays are mapped to Julia

    Vectors} with zero copy.
    The \codename

WrapIt project [44] has demonstrated that binding code can be generated
automatically from a library’s header files, which would make the process of
adding Julia bindings to C++ libraries very easy. Automation of this Julia
binding has been tested on the ROOT libraries, and we have been able to
produce, draw, and fit histograms and graphs (`TGraph} class). The fit has
been tested with both functions defined in \codename`ROOT and functions
defined in Julia, demonstrating a perfect integration. Unlike direct calls
into C or Fortran libraries via the `ccall} function, calls between \Cpp and
Julia have to go through the intermediate layer created by the wrapper code.
We perform several measurements to estimate the overhead from the \Cpp-Julia
interface. The measurement is performed on a call to the \codename`ROOT
`TH1D::Fill} method, that adds a value to a histogram: we time a loop of 1
million call and average the result to get time per call. First, we create a
shared library, that exports C functions, we call from Julia with the
\mintinline`juliaccall method. The pointer to the histogram object is passed
to the C function as a `void*} type. When compared with a direct call to the
\mintinline`c++Fill method within the same C++ code, it shows an overhead of
0.23 ns from the call from Julia unexpectedly smaller than when calling the
wrapper from C++ (0.74 ns). In the end, the call from Julia takes only 4% more
time than a direct call from C++. It is 38 times faster than a call from
Python. Measurement is also done for a binding based on CxxWrap. All results
are shown in Table 2.1.2. We could imagine the Julia engine performing just-
in-time compilation of C++ using the LLVM infrastructure it uses for the Julia
code. The Cxx package [45] is providing this feature for Julia releases from
1.1.x to 1.3.x. With this package we can access to a C++ library without the
need of a C++ wrapper. Nevertheless, a Julia wrapper is needed to provide the
same transparency---calls to the C++ functions similar as calls to a Julia
functions. Using this package the call to the `Fill} function in our example
is found to be as fast as when using the C interface, as shown in
Table~\ref`tab:cxxcall. We used Julia version 1.3.1 to perform this
measurement. The Cxx approach is a good alternative to CxxWrap. In Ref. [15],
CxxWrap was used to interface to the LCIO C++ library [46] to read ILC [47]
simulated events and to Fastjet [48, 49] to cluster hadronic jets. The loss of
event throughput compared to a code uniformly written in C++ was 37%.

Table 3: Mean time to call the `m`ethod of a ROOT histogram from C++, Julia
and Python. The time corresponding to a single call is averaged on $10^{6}$
`c`alls. Three cases are considered for Julia: use of the plain Julia C
interface (‘‘C API’’), use of CxxWrap, and use of Cxx. For reference the time
to call the same function from C/C++, within the same code (‘‘C++’’) and
through the shared library developed the Julia C interface, is also measured.

| Mean time
---|---
| [ns]
C++ | 5.74 | $\pm$ | 0.01
C API from C | 6.48 | $\pm$ | 0.04
C API from Julia | 5.97 | $\pm$ | 0.03
Julia - CxxWrap | 8.21 | $\pm$ | 0.04
Julia - Cxx | 5.97 | $\pm$ | $(<0.01)$
Python | 226 | $\pm$ | 5

####  2.1.3 Support of standard HEP standard formats

Different file formats are used to store HEP data and supporting them is
crucial to a streamlined physics analysis experience. The file formats
currently used to store physics events are mainly HepMC, LHE [50], LCIO, and
ROOT. The pachages LHE.jl [51] and LCIO.jl [52] provide supports for LHE and
LCIO. Two packages are available to read ROOT files: UpROOT.jl [53] and
UnROOT.jl [36]. The UpROOT.jl package uses the Uproot pure-Python library to
provide read and write support. When using this package, a loop on events
typically suffers of the same performance penalty as with Python. This has
motivated the development of UnROOT.jl, a package written in pure Julia that
provides a fast processing of events, as demonstrated in the performance
measurements done in the previous section, which used this package. It leaves
the flexibility to use an explicit event loop, with a small memory footprint,
or works on vector of event quantities (‘‘columnar analysis’’). An event loop
will look like the following code snippet, where

    Muon_pt} is a vector (transverse momenta of the muons contained in the event) .
    \vspace

0.8em

⬇

for event in mytree

# Access to a single-event quantity

event.Muon_pt

end

A columnar analysis will look like the following.

⬇

# Access to a vector of event quantities

# (themselves vectors of numbers)

mytree.Muon_pt

UnROOT.jl uses thread-local storage to maximize performance and maintain
thread-safety. An event loop can be parallelized in several threads with
little effort using the standard Julia

    @threads} macro:
    \vspace

0.8em

⬇

julia> @threads for event in mytree

# ... Operate on event

end

The performance measurement presented in Sec. 2.1.1 are done in single-thread
mode. There are limitations. First, this package does not support data write.
Both UpRoot.jl and UnROOT.jl can access only to objects of a limited set of
types, either stored as such in the file or in a `TTree}. The supported types
covers already a large set of use cases, but not schemes where data is stored
as object of serialized \Cpp classes. Using the genuine \codename`ROOT library
via a Julia binding based on CxxWrap can be an alternative approach when
required. We have successfully read and write histograms (`TH1} objects) and
graphs (\mintinline`c++

TGraph objects) using this approach. We expect the implementation of the
support of `RNTuple} to be easier than \mintinline`c++TTree that it is
expected to replace, thanks to its design. Data are stored in column of
fundamental types (float, int,...) [54], similar to Apache Arrow[55], which
should ease support from programming languages other than C++ like Julia. In
the neutrino physics community, the industry-standard HDF5 and Parquet have
been used at scale, and these files can be readily read and write from Julia
via their respective packages.

####  2.1.4 Parallel computing

Apart from having memory shared multi-threading, Julia also ships with out-of-
core distributed computing capability as a standard library (Distributed). In
fact, it is as easy to command an array of heterogeneous nodes in real-time
via packages such as ClusterManagers.jl [56], which can mimic Dask’s
experience [57] with a fraction of the code. For more advanced features, such
as building out-of-core computation graphs, Dagger.jl [58] provides
facilities. While these libaries allow distribution of execution within the
Julia code, parallelization can be also done, as with C++ and Python, by
running parallel jobs of the same executable using commands of a batch
processing system, like HTCondor [59, 60], typically used in computer cluster
facilities.

####  2.1.5 Platform supports and license

Julia is supported on all major platforms, a list of which can be found on the
Julia website777https://julialang.org/downloads/#supported_platforms. Three
different support tiers are provided. The platforms with full-fledged support
(classified as tier 1) are, as of Oct, 2022:

  * •

macOS x86-64

  * •

Windows x86-64 and CUDA

  * •

Linux x86-64 and CUDA

  * •

Linux i686

Worth noting that many platforms well on their way into tier 1, such as macOS
with ARMv8 (M-series chips). Julia is distributed under the MIT license along
with vast majority of the ecosystem, which guarantees free use, modification,
and re-distribution for any use case.

####  2.1.6 Reproducibility

Julia includes a package manager and a general registry used by the whole
community in an organized manner. In particular, each package contains a
Project.toml file, that records the dependency and compatibility with other
packages in an uniform way. Furthermore, any binary dependencies are also
captured by the package system: they are distributed as
‘‘Artifact’’---packages with names ending _jll---but still behave as normal
packages when it comes to dependency and compatibility resolution. This
eliminates a few problems, including running out of pip space just because you
depend on a large library (e.g., CUDA). More details are giving in Sec. 3.1.
On the end-user side, one can easily capture the an environment by working
with the Manifest.toml file. While Project.toml records compatibility and
dependencies, Julia would try to use the latest compatible packages when
instantiating the environment. Manifest.toml, on the other hand, captures the
exact versions of every package used (recursively) such that exact
reproducibility can be guaranteed.

####  2.1.7 Numerical optimization

The statistical inference procedures relevant to HEP use numerical
optimization heavily, from Maximal Likelihood Estimation (MLE) to scans over
Parameters of Interest (POI) and obtaining the test statistics. Traditionally
this is done by minuit2 [61, 62] in ROOT, which uses the finite difference
method to provide gradient information for some of its optimization. Julia has
a solid ecosystem in numerical optimization (NLopt.jl [63], Optim.jl [64], and
meta algorithm package such as Optimization.jl [65] that brings local and
global optimization together). Julia’s performance has lead to most libraries
being written in pure Julia, which means that optimization tasks can often use
better algorithms such as Broyden–Fletcher–Goldfarb–Shanno (BFGS) [66, 67, 68,
69] that rely on gradient provided by automatic differentiation. Support for
automatic differentiation is further described in Sec. 3.3. Construction of a
complex probability distribution function is a common problem in HEP.
Description of continuous spectra often requires a multicomponent probability
density function (PDF) e.g., a sum of a signal component and a background
component. In addition, the convolution with the model PDF with the
experimental resolution is an essential for the HEP applications. The RooFit
framework is the standard tool for building complex high-dimensional
parametric functions out of lower dimensional building blocks. As great
convenience, the framework provides a homogeneous treatment of the PDF
variables and parameters that can be fixed, restricted to a range, or
constrained by a penalty to the likelihood functions. The framework is written
in C++ and available in Python. A pure-python package zfit [70, 71] give an
alternative solution to Python users that can better integrate with the
scientific Python ecosystem. Julia ecosystem offers a large variety of
standard density functions in the Distributions.jl package [72, 73]. The
package largely exploits the properties of the standard density functions,
such as moments and quantiles, which are computed using analytic expressions
for the unbound PDFs. Moreover, flexible construction functionality is greatly
missing. The mixture models of the Distributions.jl are the holder for the
multicomponent PDF, however, they cannot be used for fitting of the component
fractions, the prior probabilities. Extension of the convolution functionality
beyond a small set of low-level functions is required. The management of the
distribution parameters is a key missing functionality in Julia modelling
ecosystem.

###  2.2 Specific needs for analysis of reconstructed events

####  2.2.1 Tools to produce histograms and publication-quality plot

The statistics community in Julia has support for N-dimensional histograms
with arbitrary binning in StatsBase.jl [74], an extension to this basic
histogram is implemented in FHist.jl [75], which added support for bin error
and under/overflow and for filling the histograms in an event loop, as
typically done in HEP analyses. Many libraries of high quality are available
for plotting from Julia. In the interests of standardization, the Plots.jl
[76] package provides a front-end interface to many plotting packages,
allowing easy switching from one to another. It supports the concept of
recipes used by packages processing data to specify how to visualize them,
without depending on the Plots package: the dependency is limited to the
RecipeBase.jl [77] package which has less than 350 lines of code. The package
supports, currently, 7 backends. It supports themes, which are sets of default
attributes and provide a similar feature to the ROOT `TStyle} class. The back
end selected by default in Plots is GR~\cite`bib:gr, a rich visualization
package providing both 2D and 3D plotting and supporting LaTeX for text. The
GR package, or its GRUtils.jl [79] extension, can be used directly when a
shorter warm-up time is needed before obtaining the first plot of a running
session (see Sec. 5.2 for a discussion on the warm-up time). We should also
mention the Makie.jl ecosystem [80], a rich plotting package targeting
publication-quality plots, which is increasingly popular. This package
supports the recipe and theme features, but is not itself supported by
Plots.jl. For instance, the FHist.jl HEP-oriented histograming package
mentioned before provides a recipe to plot the histograms. Makie.jl suffers
from a longer time to obtain the first plot, even larger than with the
Plots.jl package with its default backend OpenGL. Use of LaTeX to generate
high-quality plots has been popularized in HEP community with the plotting
system of the Rivet Monte-Carlo event generator validation toolkit [81]. The
PFGPlots [82] and PFGPlotsX [83] packages offer LaTeX-based plotting. They are
both supported by the Plots.jl package. The Gaston.jl [84] package provides
plotting using the popular Gnuplot.jl utility [85]. People used to the Python
`matplot.pyplot} set of functions~\cite`bib:matplot can use the PyPlot.jl
package that provides a Julia API to this package. Those who prefer plotly to
matplot, can use the PlotlyJS.jl, a Julia interface to plotly. The high-level
grammar of interactive graphics Vega-Lite [87] is also supported, thanks to
the VegaLite.jl [88] package that supports exports to bitmap and vector image
files, including the PDF format, which is convenient to include in papers
written with LaTeX. Plotting can also be done on a text terminal, using the
UnicodePlots.jl [89] package, supported by the Plot front end. The
visualization tool ecosystem for Julia is rich, with the added benefit of
staying in the same environment as the analysis and enabling an interactive
workflow.

####  2.2.2 Notebook support

A computational notebook is an interface for literate programming that allows
embedding calculations within text. Notebooks have been made popular by
Mathematica [90], which has supported notebooks starting from its first
version, 1.0, released in 1988. In HEP, notebooks are widely used by
theoreticians for symbolic calculation e.g., with Mathematica, and by
experimentalists, for data analysis, and plotting using Python or C++ as
programming language. The notebook system used with Python, Jupyter, fully
supports Julia. The ‘‘ju’’ of Jupyter stands for Julia, while ‘‘py’’ stands
for Python and ‘‘er’’ for the R language. The ROOT analysis framework brings
C++ support to Jupyter. The notebook support for Julia is richer than for
Python and C++. In addition to Jupyter, Pluto.jl [91] provides a new-
generation notebook system for Julia. This system keeps track of the
dependency of all calculations spread in the document and updates
automatically any dependent results when a one of them is edited. Beyond being
convenient, this automatic update provides reproducibility. Pluto.jl is also a
very easy solution for interactive notebooks, where buttons, drop-down menus
and slides can be included. This is useful for students. It can also be used
to build a tool for experiments running shifters to analyze the data quality
in quasi-realtime. With Pluto.jl, notebooks are normal executable Julia files.
Notebook functionality is offered through special comments. This helps with
version control.

###  2.3 Specific needs for physics event reconstruction, simulation and data
acquisition trigger software

Physics event reconstruction, simulation and trigger software are typically
large codes developed by the experiment and project collaborations. The
software stack of the LHC experiments is particularly large and complicated,
due to the complexity of their detectors. The software is developed
collaboratively by many developers, with different levels of software skills.
Tools for both collaborative development and quality assurance are essential
for all experiment software. Software distribution and release management are
also important. The complexity of the C++ language, used in most of these
frameworks, can limit the integration of contributions developed by students.
This is more and more true given the growing use of high-level language (e.g.,
Python) as the teaching language for computing in universities, especially
among natural science departments. The Julia language and its ecosystem have
been built using an open-source and community approach. Tools have been put in
place and are widely adopted for efficient collaborative development. Julia
comes with a standard and convenient package management system providing
reproducibility, see Sec. 3.1. Julia has built-in unit testing, coverage
measurement, and officially maintained continuous integration recipes and
documentation generator. These are used in almost all of the Julia packages
registered publicly, thanks to the streamlined experience and low barrier to
entry. The simulation software of the experiments depends on external
libraries to simulate the underlying physics, such as Monte Carlo event
generators, and on some others, like Geant4 [92], to simulate the transport of
the produced particles and their interaction with the detector.
Interoperability with libraries written in C, C++, or Fortran, as discussed in
Sec. 2.1.2, it is essential not to have to re-write all the external libraries
in Julia. Simulation and reconstruction is compute intensive and therefore
good performance is essential: performance has a direct impact on the
computing infrastructure cost. We have seen in the previous section that Julia
meets the C/C++ performance and sometimes surpasses it. Code parallelization
and efficient use of single instruction multiple data (SIMD) vectorization
features of CPUs is essential at the LHC and for HL-LHC to efficiently use
current hardware resources, with a high density of computing cores, including
accelerators (e.g., GPU) that can count tens of thousands core [6]. The Julia
language provides a very good support for multi-threading: a loop can be
parallelized by a single macro (`@threads}), an operation can be made atomic
by prefixing it with \mintinline`julia@atomic, a more general lock mechanism
is provided, asynchronous tasks, with distribution of tasks to different
threads, is natively supported. Julia supports distributed computing, using
its own communication mechanism but also using MPI [93, 94]. It is possible to
use Julia’s compiler to vectorize loops by using the `@simd} macro or the more
advanced \mintinline`julia@turbo from the LoopVectorization.jl package [95].
Due to its effective metaprogramming capabilities, Julia has great support for
running code on heterogeneous architectures, Julia code can be compiled for
Nvidia (CUDA), AMD (ROC) and Intel (oneAPI) GPUs via compiler written in
Julia888https://github.com/JuliaGPU/GPUCompiler.jl, without dependency on, for
example, C++ CUDA or HIP library. Packages like GPUArrays.jl and
KernelAbstractions.jl allow the use of exactly the same core algorithm written
in Julia to be executed across different vendor platforms with minimal
boilerplate code, which is a currently a unique feature among languages. On
the more user-facing front, libraries such as Tullio.jl [96] combine
metaprogramming and kernel programming ability to allow users to express
tensor operation with Einstein notations regardless of whether the array lives
on RAM or GPU VRAM. This is very relevant for data preservation and for
unifying effort to write algorithms once and run them everywhere. The ability
to run native Julia code on both CPUs and GPUs, combined with the support for
automatic differentiation in Julia, makes Julia an excellent platform for
machine learning (ML) research. This is especially true for advanced
scientific machine learning that goes beyond combining conventional matrix-
crunching ML-primitives/layers and uses physical/semantic models or mixes them
with generic ML constructs.

###  2.4 Specific needs for event generation and for phenomenology

####  2.4.1 Symbolic Calculations in Julia

Julia is a fast, solid and reliable programming language with a well developed
Computer Algebra System (CAS) such as Symbolics.jl [97], a language for
symbolic calculations such as Symata.jl [98], and an interface to Mathematica
such as MathLink.jl [99], that could be widely used in HEP, considering the
advantages Julia has. Symbolics.jl [100] is a CAS written in pure Julia, which
is developed by the SciML community [101] who also maintain the state-of-the-
art differential equations ecosystem [102]. The package has scalable
performance and integrates with the rest of Julia ecosystem thanks to its non-
OOP design and multiple dispatch [97]. Some of the main features of
Symbolics.jl include pattern matching, simplification, substitution, logical
and boolean expressions, symbolic equation solving, support for non-standard
algebras with non-commutative symbols, automatic conversion of Julia code to
symbolic code and generation of high performance and parallel functions from
symbolic expressions [103], which make it even more interesting for possible
applications in HEP. At the heart of Symbolics.jl, we find ModelingToolkit.jl,
a symbolic equation-based modeling system [104], and SymbolicUtils.jl, a rule-
based rewrite system [105]. Symata.jl [98] is a language for symbolic
computations in which some features, such as evaluation, pattern matching and
flow control, are written in Julia, and symbolic calculations are developed by
wrapping Sympy, a python library for symbolic mathematics. MathLink.jl [99] is
a Julia language interface for the Wolfram Symbolic Transfer Protocol (WSTP)
(this requires the installation of Mathematica or the free Wolfram Engine to
run properly). The interface is a W"" string macro used to define Mathematica
symbols. MathLinkExtras.jl [106] adds extra functionalities such as W2Mstr,
which allows the conversion of Julia MathLink expressions into Mathematica
expressions, and `W2Tex} which converts Julia \juliapkg`MathLink.jl
expressions into LaTeX format. And, finally, one can evaluate the expression
in Mathematica using

    weval}.
    \subsubsection

Event Generators To be prepared for future needs for event generation [107],
it is conceivable to rewrite parts of the existing event generators in Julia
and making use of modern parallelisation technologies. One of the most
demanding tasks in event generation is the evaluation of matrix elements and
cross sections, where Julia provides several useful tools.
The package Dagger.jl is a framework for out-of-core and parallel computing
written in pure Julia. It is similar to the python library Dask and provides a
scheduler for parallelized execution of computing tasks represented as a
directed acyclic graphs (DAGs). Such DAGs could be used to represent the
evaluation of matrix elements in terms of elementary building blocks, similar
to HELAS-like functions in Madgraph4GPU (see e.g., [108]). Furthermore,
Dagger.jl supports the selection of different processors as well, making it
possible to be use for distributed computing on GPU as well (see e.g.,
DaggerGPU.jl [109]).

###  2.5 Feature summary

The Table 4 summarizes the programming language and ecosystem features we have
identified as required for HEP. It is surprising how Julia language manages to
fulfill almost all of these features. We should note that the interoperability
is less transparent for C++ than with the other languages as it requires to
write a code wrapper. Many HEP file format are already supported, including
for ROOT files, without covering the full versatility offered by this format.

Table 4: Summary of features needed for HEP applications and their availability in the Julia ecosystem. Requirement |  Fulfilled by Julia
---|---
Easy to learn for HEP physicists | ✓
Performance | ✓
Interoperability with legacy code | ✓
Support for HEP standard formats | partial
Support for architectures and open license | ✓
Cross-platform reproducibility | ✓
Tools to perform optimization/minimization | ✓
Histogramming | ✓
Plotting with publication quality | ✓
Notebook support | ✓
Tooling for large project (unit tests, continuous integration, software distribution) | ✓
SIMD and multi-threading | ✓
Distributed parallel computing | ✓
Native GPU support | ✓
Machine learning libraries | ✓
Computer Algebra System | ✓

##  3 The bonuses

In addition to solving the two-language problem and the mandatory features
listed in the previous section, the Julia ecosystem will provide other
advantages over the C++ and Python languages currently used.

###  3.1 Packaging

Julia comes with a built-in package manager, Pkg.jl. It builds on the
experience of package managers in other ecosystems, and it can be used to
install packages and manage ‘‘environments’’, similar to the concept of
virtual environments in Python. A Julia environment is defined by two files:

  * •

Project.toml: this file records version and UUID of the current project, it
also contains the list of direct dependencies of this project, as well as the
compatibility bounds with these packages and Julia itself. Moreover, all Julia
packages follow semantic versioning (semVer [110]): version numbers are
composed of three parts, major, minor and patch numbers, and breaking changes
can only be introduced in versions which change the left-most non-zero
component (e.g., going from 0.0.2 to 0.1.0, or from 2.7.3 to 3.0.0 are
considered breaking changes).

  * •

Manifest.toml: this file is automatically generated by the package manager
when instantiating an environment, if not already present, and it captures all
packages in the environment with their versions, including all indirect
dependencies. When used together with Project.toml, Manifest.toml file
describes an exact environment that can be recreated on any other platforms,
which is particularly important for reproducibility of applications (e.g.,
analysis pipelines).

Julia packages are organized in directories (usually also Git repositories) in
which there is a Project.toml file to define its environment. Packages can be
installed either via path (local path on a machine, or URL to a remote Git
repository), or by name if they are present in a package registry. Pkg.jl is
able to deal with multiple registries at the same time, which can be both
public and private, in case there is a need to provide packages relevant only
to a single group or collaboration. Because there is only one package manager
and only one way to record package dependency, the Julia package registry
simply records the dependency and compatibility metadata separately from
package’s source code. It allows a local resolver to correctly resolve
compatibility in a short amount of time. Users can interact with the package
manager either by using its programmatic API (useful for scripting) or an
interactive REPL mode, which can be entered by typing the closing square
bracket ] in Julia’s REPL. The package manager can also be used to run the
tests for packages with a single command (Pkg.test using the API, or the
command test in the REPL). Since Pkg.jl is a standard library and has many
capabilities, all users are familiar with it and do not need to resort to
third-party, mutually incompatible tools.

###  3.2 Multiple dispatch and Fast Development

We group multiple dispatch and fast development (i.e., hot reloading) into the
same section because of their direct impact on design of the packages and the
quality of life of developers. A side-by-side comparison between OOP and
multiple dispatch has been shown in Sec. 1.3. Here, we highlight that multiple
dispatch is also a known solution to the expression problem [111, 112].
Essentially, in class-based OOP, one of the following is much less natural
than the other:

  * •

add new methods to existing data type (class/struct).

  * •

add new data type (class/struct) for existing algorithm (method).

In OOP, the second one is easy, think inheritance; but the first requires
access to source code. In Julia, the first one is trivial since methods don’t
bind to data type (class/struct) to begin with. But the second one can also be
easily done by sub-typing the upstream abstract type. Making it easy for
developers to reuse existing packages is crucial in HEP also because libraries
are sometimes under-maintained. If we can cleanly extend and reuse these
libraries without making private forks, overall efficiency would be boosted.
As a dynamic language, hot reloading should not come as a surprise. It is, in
fact, crucial for Julia, due to the inevitable latency introduced by the JIT
compiler. The go-to package for this is Revise.jl [113] which automatically
detects file modifications and re-compiles the relevant functions on the fly.
It can also reload the source code of any Julia Base module, saving a lot of
time if (re)compiling Julia.

###  3.3 Automatic differentiation

The multiple dispatch system and the native speed of Julia eliminated the need
for many specialized libraries to implement the same interface (e.g NumPy-
interface in JAX [114], TensorFlow [115], PyTorch [116]). Instead, package
maintainers only have to focus on providing rules for the built-in functions
as they are fast already and downstream packages are mostly pure-Julia too,
see ChainRules.jl [117]. A dedicated organization, JuliaDiff [118] collects
all the packages and efforts regarding what each autodiff engine is good at.

##  4 Foundation HEP-specific libraries to be developed or consolidated

###  4.1 Integration in the ROOT framework

Because of the ubiquity of ROOT in HEP, a Julia interface to this framework,
similar to the existing Python one and that will allow people familiar to it
to find their way easily, is essential for the development of Julia in the HEP
community. In addition, this will provide access to a large set of software
used in HEP (storage support, statistics tools, unfolding, etc.) before their
counterpart are implemented in Julia.

###  4.2 HEP-specific data storage format

It will be important to consolidate the support for the ROOT data format. The
ROOT data format is very versatile and allows the storage of instances of
arbitrary C++ classes (this is true of the current TTree and the new RNTuple
format). Current Julia packages for ROOT I/O do not cover this whole
versatility and do not allow for reading and writing files with objects of
sophisticated types.

###  4.3 Physics object types and histogram

Packages to manipulate Lorentz vectors and to build histograms are already
available [119, 75]. Leveraging multiple dispatch, these packages are
relatively easy to implement, and compose well with rest of the Julia
ecosystem (e.g., collection of 4-vectors can be stored and sorted efficiently
without any special care). Defining a standard interface to Lorentz vectors
and histogram data structures, with a similar approach as the Table interface
[120] could be beneficial.

###  4.4 HEP specific statistical tools

Over years, HEP community has developed its statistical standard to assert a
level of confidence of the experimental results, for measurements, limits and
observation of new phenomena. The Julia ecosystem contains several high-
quality packages for Bayesian statistics and inference. Two examples are
BAT.jl (Bayesian Analysis Toolkit in Julia) BAT.jl [121] and Turing.jl [122],
which already have been used in several physics analyses. Both packages are
being actively developed with good communication of the authors across the
development teams. Common interfaces [123, 124, 125] have been established to
increase interoperability. More development is required for the frequentist
CLs approach used at LHC [126, 127, 128, 129] and based on profiled likelihood
fits. The method is derived from the hybrid method of the same name developed
at LEP [130, 131] and used later at Tevatron [132, 133]. The C++ tools
typically used by LHC experiments are the RooFit (originally developed for the
BaBar experiment [134, 135]) and RooStats libraries included in the ROOT
framework. For multinodal distributions these libraries are used through the
HistFactory [136] or HiggsCombine [137, 127, 138] tools. The pyhf package
[139] provides a pure-Python implementation of HistFactory that offers
different computational backends to perform the likelihood maximization and is
gaining popularity. The HistFactory, HiggsCombine, and pyhf are standalone
tools, for which inputs are provided in text files (XML or JSON). Thanks to
the transparent Julia-Python interface, pyhf can also be used in a Julia
session or code. For a perfect integration and to exploit the language
performance, a Julia implementation is desirable. An effort to implement pyhf
in Julia has already started [140] and would need to be consolidated.
Histogram unfolding [141] is another statistical tool widely used in HEP
experiments. It is used to correct from the effect of the finite resolution of
the particle detectors in differential cross section measurement. The TUnfold
[142] and RooUnfold [143] are the most commonly used packages. The
RooFitUnfold [144] package provides an extension of RooUnfold. New techniques
to perform unbinned high-dimensional data unfolding has been recently
developed [145]. Like for CLs, unfolding comes at the last step of a HEP data
analysis, and a Julia implementation would be useful.

##  5 Limits of the Julia programming language

###  5.1 Language popularity

Despite its smaller user base than C++ and Python, we have found that it is
extremely easy to find information on the web, either from Stack Overflow or
from dedicated channels, on Discourse, Slack, and Zulip. The community is very
collaborative. An annual conference JuliaCon 999https://juliacon.org/ is
boosting this collaboration. In particular, it encourages exchanges between
different fields, both from Academia and Industry. The popularity of Julia is
growing and it has been adopted by large academic projects, like the Climate
Modeling Alliance (CliMA); and companies like ASML, the largest supplier of
photolithography systems; Pharmacology actors like Pfizer, Moderna, and
AztraZeneca [146, 147, 148]; finance actors like Aviva, one of the largest
insurers, and the Federal Reserve Bank of New York [149, 150, 151].

###  5.2 Just-in-time compilation latency

While applications written in Julia run faster than with an interpreted
language, the first execution requires additional time to perform the just-in-
time (JIT) compilation. In order to limit this overhead, the intermediate
results of the compilation, called precompiled code, is cached on disk. The
precompilation of a package code is typically performed in parallel at
installation time, and the cached content includes, but is not limited to:
lowered code, type inference result, etc.; but at the time of writing, Julia
does not yet cache compiled machine code101010Progress is being made, see
https://github.com/JuliaLang/julia/pull/44527. The latency is often called
"time-to-first-plot". The JIT compilation latency has been improved from
version-to-version, in particular with versions 1.5, 1.6, 1.8, and 1.9 by
reducing the number of required recompilations. The various sources of latency
have been studied extensively [152, 153] and the reduction of the time-to-
first-plot is a high priority for the compiler team. Besides improvements
coming from the compiler, following the general guidelines of Julia code style
for performance [40], which ensure that the compiler can easily infer variable
types, should reduce such latency [152]. At the same time, tools have been
developed to both help ‘‘hunt’’ down unnecessary recompilation
(SnoopCompile.jl [154]), as well as help precompile known common user
routinges at installation time (PrecompileTools.jl [155]). The latency can
also be drastically reduced by preparing a custom system image: the system
image contains cached machine code for a set of precompiled packages and past
executions. It comes with the drawback that versions of the packages shipped
in the system image take precedence over the ones installed via the package
manager [156], which can be confusing and be a source of bugs. Updating these
packages requires rebuilding the custom system image. The time to produce a
first plot, consisting of a 2-D plot of 100 points, was measured to be
2.09$\pm 0.01\,$s with Julia 1.9.0-rc1 and the Plots.jl [76] package. The
Makie.jl package took 7.57$\pm 0.02\,$s using the Cairo backend. Time is
similar with the GL backend. Subsequent plots take less than a millisecond.
Building a custom system image brings down the latency to below 50 ms for both
packages. While for Plots.jl, the latency using the standard system image is
acceptable, building a custom system image would make use of Makie.jl for an
interactive session or for a short batch script much more convenient. To
measure the improvement brought by the efforts of Julia developers, the
measurement is repeated with the older long-term-support release 1.6.7. With
this older release the result is 29.0$\pm 0.1\,s$ for Makie.jl, showing an
improvement larger than a factor of 4. The start-up time could be a concern
for large HEP experiment simulation and reconstruction software. As an example
of software size, the CMS experiment software, CMSSW [157], totals more than 2
million of lines of C++ code. The assessment was done with release 12.3.5 and
the number of lines of code was defined as the number of semicolons contained
in the code. In lieu of a similar sized HEP software package written in Julia,
we have measured the start-up time on the relatively large package
OrdinaryDiffEq, using its version 6.49.4. The package consists of about
125,000 lines of Julia code, excluding comments, and 390,000 when including
the external packages. The lines of code have been counted with the Tokei
software [158] version 12.1.2. and the extra time to run the Example 1 of the
manual [159] the first time, compared to subsequent executions, was 5.91$\pm
0.01\,$s. It goes down to 826$\pm 2\,$ms when using a custom system image. We
should also note that the precompilation happening on package installation for
the package and its dependencies (120 packages) took 256 s only. For a large
experiment software framework, attention will need to be paid to limit code
invalidation by respecting the guidelines to ease type inference. This will
also help the compiler to optimize the code. While minimizing start-up time
may require some effort for large HEP project, we do not expect it to be a
show stopper. At worst, it will require to use custom images, with a
conciliation on the package management. In addition, development to improve
the start-up time is on-going and we should expect significant progress in the
near future [160].

###  5.3 Application programming interface specification

Julia lacks a single standard to define the application programming interface
(API) of a package. The one with the best support is the use of the export
directive to list the symbols exposed to the user. The directive is recognized
by the language’s introspection functions. The names function lists, by
default, the exported symbols, with an option to list all symbols. The
methodswith function, used to retrieve functions with an argument of a given
type will list only functions from the export list. Nevertheless, the export
directive has the side-effect that all the public symbols end up in the user’s
namespace, if the package is imported with the using statement. To quote the
Julia manual [40], ‘‘it is common to export names which form part of the API.
[...] Also, some modules don’t export names at all. This is usually done if
they use common words, such as derivative, in their API, which could easily
clash with the export lists of other modules.’’. The Julia language itself
uses the user manual to define the API, as explained in the ‘‘Frequently asked
questions’’ of this document [40]. With such an approach, we lose the benefit
of the introspection functions, themselves agnostic to the API information. A
built-in unexport directive, that would allow listing public symbols that the
using statement must keep in the module namespace and which would be
recognized by the introspection functions and also by the documentation
generator [161], would be very beneficial.

##  6 Training and language transition support

Julia has been successfully introduced into existing teams, gradually
replacing their C++ with Julia packages over time, for example in the LEGEND
and BAT groups at the Max-Planck-Institute for Physics. Julia is also the
official secondary language (after Python) of the whole LEGEND [162]
collaboration. Observed experience is that students with a basic programming
background (e.g., in Python or C++) do learn the language very quickly and
become productive after just a few days. After exposure to the language for a
few weeks, students are typically able to make contributions to larger
software packages as well. No problems have been found using Julia for short-
term thesis work (e.g., three-month bachelor theses) and even two-week
internship, with students and interns who were new to the language. The
reaction of these students has been uniformly positive. Master and PhD theses
that used Julia as the primary language have resulted in very positive
experience for both students and supervisors. Students who use Julia in
longer-term projects not only become very proficient in the language, but also
gain a lower-level understanding of computing, data structures and performance
implications of modern hardware in general, compared to students who work in
Python. This is because Julia makes it very easy the move between higher-level
and lower-level programming, in contrast to the Python-plus-C++ two-language
approach. More code reuse and transfer has been observed across student
generations in Julia, compared to C++. This is due to the combination of an
excellent package management with the use of multiple dispatch as a
foundation. The first simplifies the maintenance of systems consisting of
smaller and more modular packages, while the second solves the expression
problem.

##  7 Conclusions

The Julia programming language has been presented and compared with C++ and
Python. To study the potential of Julia for HEP, a list of requirements for
offline and software-based-trigger HEP applications, covering both the
language and its ecosystem, has been established. The compatibility of Julia
with these requirements has been studied. Julia and its ecosystem are
impressively fulfilling all these requirements. Moreover, Julia brings other
features---integrated packaging system with reproducibility support, multiple
dispatch and automatic differentiation---from which HEP applications would
benefit. The capacity to provide, at the same time, ease of programming and
performance makes Julia the ideal programming language for HEP data analysis
and is more generally an important asset for all the considered HEP
applications. The dynamic multiple dispatch paradigm of Julia has proven to
ease code reuse. This property will greatly benefit HEP community applications
that involve code developed by many people from many different groups. Using a
single and easy programming language will facilitate training. Experience has
shown students with either a C++ or Python background learn the language very
quickly, being productive after a few days. Using Julia as mainstream language
in a collaboration allows students on short-term projects to use the common
programming language, while in case of C++, using a simpler language as Python
is often needed. This eases the reuse of the code developed in such context.
We have measured the performance provided by the language in the context of
HEP data analysis. The measurements show excellent runtime performance,
competitive with C++: 11% faster for the simple LHC event analysis example
used as benchmark. When compared to Python, in addition to being faster, it is
much less sensitive to implementation choices. The Python implementation was
shown to be three orders of magnitude slower than Julia when the event loop is
performed in Python. Vectorization techniques can be used to move the event
loop by using underlying compiled libraries and this reduces the gap in
performance. One difference with C++ and Python is that Julia is younger and
has a smaller community. The Julia community is very collaborative and,
despite its lower popularity, information for developing with this language is
easy to find on the Internet. Julia’s rapid growth in academia and industry
gives us confidence on the long term continuity of the Julia language, which
is essential for HEP projects, because of their large time span. In view of
this study, the HEP community will definitively benefit from a large scale
adoption of the Julia programming language for its software development.
Consolidation of HEP-specific foundation libraries will be essential to ease
this adoption.

## Acknowledgements

The authors would like to thank Enrico Guiraud (Princeton University and CERN)
for the review of the C++ code used in the dimuon analysis benchmark and
fruitful discussions.

## Endorsement

Johannes Blaschke111NERSC, Lawrence Berkeley National Laboratory, Ankur
Dhar222SLAC National Accelerator Laboratory, Matthew Feickert333University of
Wisconsin–Madison, Madison, Sam Foreman, Cornelius Grunwald555Massachusetts
Institute of Technology,, Alexander Held333University of Wisconsin–Madison,
Madison, Philip Ilten666University of Cincinnati, Adam L. Lyon777Fermi
National Accelerator Laboratory, Mark Neubauer888University of Illinois
Urbana-Champaign, Ianna Osborne999Princeton University, Johannes
Schumann101010Erlangen Centre for Astroparticle Physics, Friedrich-Alexander-
Universität Erlangen-Nürnberg, Daniel Spitzbart111111Boston University, Boston
James Simone777Fermi National Accelerator Laboratory, Rongkun
Wang121212Harvard University, Cambridge, Michael Wilkinson666University of
Cincinnati, and Efe Yazgan131313National Taiwan University from the High
Energy Physics and Astrophysics communities endorse this work. The authors
thank them for their support. 44footnotetext: Fakultät Physik, Technische
Universität Dortmund

## References

  *   * [1] Bird, I. _et al._ Update of the computing models of the wlcg and the lhc experiments.  Tech. Rep. CERN-LHCC-2014-014, CERN (2014).  URL https://cds.cern.ch/record/1695401.
  * [2] Collaboration, A.  ATLAS Software and Computing HL-LHC Roadmap.  Tech. Rep. CERN-LHCC-2022-005, LHCC-G-182, CERN, Geneva (2022).  URL http://cds.cern.ch/record/2802918.
  * [3] Software, C. O. & Computing.  CMS phase-2 computing model: Update document.  Tech. Rep. CERN-CMS-NOTE-2022-008, CERN, Geneva (2022).  URL http://cds.cern.ch/record/2815292.
  * [4] Evans, L. & Bryant, P.  LHC Machine.  _JINST_ 3, S08001 (2008).  URL https://doi.org/10.1088/1748-0221/3/08/S08001.
  * [5] Apollinari, G. _et al._ High-Luminosity Large Hadron Collider (HL-LHC): Technical Design Report V. 0.1.  Tech. Rep. CERN-2017-007-M, CERN (2017).  URL https://doi.org/10.23731/CYRM-2017-004.
  * [6] Albrecht, J. _et al._ A Roadmap for HEP Software and Computing R&D for the 2020s.  _Comput. Softw. Big Sci._ 3, 7 (2019).  URL https://doi.org/10.1007/s41781-018-0018-8.
  * [7] Sexton-Kennedy, E.  HEP software development in the next decade; the views of the HSF community.  _Journal of Physics: Conference Series_ 1085, 022006 (2018).  URL https://doi.org/10.1088/1742-6596/1085/2/022006.
  * [8] Vassilev, V., Canal, P., Naumann, A., Moneta, L. & Russo, P.  Cling -- the new interactive interpreter for ROOT 6\.  _Journal of Physics: Conference Series_ 396, 052071 (2012).  URL https://doi.org/10.1088/1742-6596/396/5/052071.
  * [9] Brun, R. & Rademakers, F.  ROOT: An object oriented data analysis framework.  _Nucl. Instrum. Meth. A_ 389, 81--86 (1997).  URL https://doi.org/10.1016/S0168-9002(97)00048-X.  See also https://zenodo.org/record/3895860.
  * [10] Antcheva, I. _et al._ ROOT — A C++ framework for petabyte data storage, statistical analysis and visualization.  _Computer Physics Communications_ 180, 2499--2512 (2009).  URL https://doi.org/10.1016/j.cpc.2009.08.005.  40 YEARS OF CPC: A celebratory issue focused on quality software for high performance, grid and novel computing architectures.
  * [11] Bezanson, J., Edelman, A., Karpinski, S. & Shah, V. B.  Julia: A fresh approach to numerical computing.  _SIAM Review_ 59, 65--98 (2017).  URL https://doi.org/10.1137/141000671.
  * [12] Bezanson, J. _et al._ Julia: Dynamism and performance reconciled by design.  _Proc. ACM Program. Lang._ 2 (2018).  URL https://doi.org/10.1145/3276490.
  * [13] Bezanson, J., Karpinski, S., Shah, V. B. & Edelman, A. (2012).  URL https://julialang.org/blog/2012/02/why-we-created-julia/.
  * [14] computing, J.  Newsletter january 2022 - julia growth statistics (2022).  URL https://juliacomputing.com/blog/2022/01/newsletter-january/.
  * [15] Stanitzki, M. & Strube, J.  Performance of Julia for High Energy Physics Analyses.  _Comput. Softw. Big Sci._ 5, 10 (2021).  URL https://doi.org/10.1007/s41781-021-00053-3.
  * [16] Go 2 Error Handling Feedback.  URL https://github.com/golang/go/wiki/Go2ErrorHandlingFeedback.  (accessed October 12, 2021).
  * [17] Rackauckas, C.  ModelingToolkit, Modelica, and Modia: The Composable Modeling Future in Julia.  _The Winnower_ 8 (2021).  URL https://doi.org/10.15200/winn.162133.39054.
  * [18] Julia Computing, NVIDIA.  Julia Computing Brings Support for NVIDIA GPU Computing on Arm Powered Servers.  URL https://juliacomputing.com/blog/2019/12/nvidia-ngc-arm.  (accessed October 12, 2021).
  * [19] Regier, J. _et al._ Cataloging the visible universe through bayesian inference in julia at petascale.  _Journal of Parallel and Distributed Computing_ 127, 89--104 (2019).  URL https://doi.org/10.1016/j.jpdc.2018.12.008.
  * [20] Claster, A.  Julia Joins Petaflop Club.  URL https://juliacomputing.com/media/2017/09/julia-joins-petaflop-club.  (accessed October 12, 2021).
  * [21] The LuaJIT project website.  URL https://luajit.org.  (accessed April 7 2022).
  * [22] Bolz, C. F. & Tratt, L.  The impact of meta-tracing on vm design and implementation.  _Science of Computer Programming_ 98, 408--421 (2015).  URL https://doi.org/10.1016/j.scico.2013.02.001.  Special Issue on Advances in Dynamic Languages.
  * [23] Pypy project website.  URL https://www.pypy.org/.  (accessed April 7 2022).
  * [24] Stroustrup, B.  _A Tour of C++_ C++ In-Depth Series (Pearson Education, 2018).
  * [25] Stroustrup, B.  _The Design and Evolution of C++_ (Pearson Education, 1994).
  * [26] Cardelli, L. & Wegner, P.  On understanding types, data abstraction, and polymorphism.  _ACM Comput. Surv._ 17, 471–523 (1985).  URL https://doi.org/10.1145/6041.6042.
  * [27] Strachey, C.  Fundamental concepts in programming languages.  _Higher-Order and Symbolic Computation_ 13, 11--49 (2000).  URL https://doi.org/10.1023/A:1010000313106.
  * [28] Snyder, A.  Encapsulation and inheritance in object-oriented programming languages.  _SIGPLAN Not._ 21, 38–45 (1986).  URL https://doi.org/10.1145/960112.28702.
  * [29] Gamma, E., Helm, R., Johnson, R. E. & Vlissides, J.  _Design Patterns: Elements of Reusable Object-Oriented Software_ Addison-Wesley Professional Computing Series (Addison-Wesley, Reading, MA, 1995).  URL https://www.safaribooksonline.com/library/view/design-patterns-elements/0201633612/.
  * [30] Python multid-dispatch module (2022).  URL https://multiple-dispatch.readthedocs.io.  (accessed March 25, 2022).
  * [31] Zappa Nardelli, F. _et al._ Julia subtyping: A rational reconstruction.  _Proc. ACM Program. Lang._ 2 (2018).  URL https://doi.org/10.1145/3276483.
  * [32] Harris, C. R. _et al._ Array programming with numpy.  _Nature_ 585, 357--362 (2020).  URL https://doi.org/10.1038/s41586-020-2649-2.
  * [33] Gras, P.  Analysis of the di-muon spectrum using data from the CMS detector taken in 2012.  _10.7483/OPENDATA.KS4A.BD5W_ (2021).  URL https://doi.org/10.7483/OPENDATA.KS4A.BD5W.
  * [34] Wunsch, S.  Analysis of the di-muon spectrum using data from the CMS detector taken in 2012.  _10.7483/OPENDATA.CMS.AAR1.4NZQ_ (2019).  URL https://doi.org/10.7483/OPENDATA.CMS.AAR1.4NZQ.
  * [35] Dataframes.jl package documentation (2022).  URL https://dataframes.juliadata.org.  (accessed August 1, 2022).
  * [36] Gál, T., Ling, J. J. & Amin, N.  UnROOT: an I/O library for the CERN ROOT file format written in Julia.  _Journal of Open Source Software_ 7, 4452 (2022).  URL https://doi.org/10.21105/joss.04452.
  * [37] Pivarski, J. _et al._ Uproot.  _10.5281/zenodo.4340632_ (2017).  URL https://doi.org/10.5281/zenodo.4340632.
  * [38] Pivarski, J. _et al._ Awkward array.  _Zenodo_ (2018).  URL https://doi.org/10.5281/zenodo.7079705.
  * [39] Reback, J. _et al._ pandas-dev/pandas: Pandas 1.4.4.  _Zenodo_ (2022).  URL https://doi.org/10.5281/zenodo.7037953.
  * [40] The Julia language manual (2021).  URL https://docs.julialang.org/en/v1/.  (accessed September 29, 2021).
  * [41] Janssens, B.  CxxWrap code repository.  URL https://github.com/JuliaInterop/CxxWrap.jl.  (accessed March 17, 2022).
  * [42] Abrahams, D. & Grosse-Kunstleve, R. W.  Building hybrid systems with Boost.Python (2003).  URL https://www.boost.org/doc/libs/1_80_0/libs/python/doc/html/article.html.  (accessed August 1, 2022).
  * [43] Pybind11 code repository.  URL https://github.com/pybind/pybind11.  (accessed August 1, 2022).
  * [44] Gras, P.  Automatic generation of c++ -- julia bindings.  URL https://github.com/grasph/wrapit.  (accessed March 17, 2022).
  * [45] Fischer, K. _et al._ Cxx.jl code repository.  URL https://github.com/JuliaInterop/Cxx.jl.  (accessed March 17, 2022).
  * [46] The linear collider i/o framework code repository (2022).  URL https://github.com/iLCSoft/LCIO.  (accessed August 1, 2022).
  * [47] Behnke, T. _et al._ The International Linear Collider Technical Design Report - Volume 1: Executive Summary.  Tech. Rep., The International Linear Collider (2013).  1306.6327.
  * [48] Cacciari, M., Salam, G. P. & Soyez, G.  Fastjet user manual.  _Eur. Phys. J. C_ 72, 1896 (2012).  URL https://doi.org/10.1140/epjc/s10052-012-1896-2.
  * [49] Cacciari, M. & Salam, G. P.  Dispelling the $n^{3}$ myth for the $k_{t}$ jet-finder.  _Phys. Lett. B_ 641, 57--61 (2006).  URL https://doi.org/10.1016/j.physletb.2006.08.037.
  * [50] Alwall, J. _et al._ A standard format for les houches event files.  _Computer Physics Communications_ 176, 300--304 (2007).  URL https://doi.org/10.1016%2Fj.cpc.2006.11.010.
  * [51] LHEF.jl (2021).  URL https://github.com/JuliaHEP/LHEF.jl.  (accessed September 29, 2021).
  * [52] Strube, J., Saba, E. & TagBot, J.  jstrube/lcio.jl: v1.9.2.  _Zenodo_ (2021).  URL https://doi.org/10.5281/zenodo.4560484.
  * [53] UpROOT.jl library code repository (2021).  URL https://github.com/JuliaHEP/UpROOT.jl.  (accessed September 29, 2021).
  * [54] Blomer, J., Canal, P., Naumann, A. & Piparo, D.  Evolution of the ROOT Tree I/O.  _EPJ Web Conf._ 245, 02030 (2020).  URL https://doi.org/10.1051/epjconf/202024502030.
  * [55] Foundation, T. A. S.  Apache arrow.  URL https://arrow.apache.org/.  (accessed August 1, 2022).
  * [56] ClusterManagers package code repository.
  * [57] Dask library web site (2021).  URL https://docs.dask.org.  (accessed September 29, 2021).
  * [58] Dagger.jl package code repository.
  * [59] Litzkow, M., Livny, M. & Mutka, M.  _Condor---a hunter of idle workstations_ , 104--111 (IEEE, 1988).  URL https://doi.org/10.1109/DCS.1988.12507.
  * [60] Tokei computer program code repository (2021).  URL https://htcondor.org/.  (accessed September 29, 2021).
  * [61] James, F. & Roos, M.  Minuit: A System for Function Minimization and Analysis of the Parameter Errors and Correlations.  _Comput. Phys. Commun._ 10, 343--367 (1975).  URL https://doi.org/10.1016/0010-4655(75)90039-9.
  * [62] James, F. & Roos, M.  Minuit2 user guide.  URL https://root.cern/doc/master/md_math_minuit2_doc_Minuit2.html.  (accessed August 1, 2022).
  * [63] The NLopt module for julia, code repository (2021).  URL https://github.com/JuliaOpt/NLopt.jl.  (accessed September 29, 2021).
  * [64] Optim.jl package code repository (2021).  URL https://github.com/JuliaNLSolvers/Optim.jl.  (accessed September 29, 2021).
  * [65] Optimization.jl package code repository (2021).  URL https://github.com/SciML/Optimization.jl.  (accessed September 29, 2021).
  * [66] Broyden, C. G.  The convergence of a class of double-rank minimization algorithms 1. general considerations.  _IMA Journal of Applied Mathematics_ 6, 76--90 (1970).  URL https://doi.org/10.1093/imamat/6.1.76.
  * [67] Fletcher, R.  A new approach to variable metric algorithms.  _The Computer Journal_ 13, 317--322 (1970).  URL https://doi.org/10.1093/comjnl/13.3.317.
  * [68] Goldfarb, D.  A family of variable-metric methods derived by variational means.  _Mathematics of Computation_ 23--26 (1970).  URL https://doi.org/10.2307/2004873.
  * [69] Shanno, D. F.  Conditioning of quasi-newton methods for function minimization.  _Mathematics of Computation_ 647--656 (1970).  URL https://doi.org/10.2307/2004840.
  * [70] Eschle, J., Puig Navarro, A., Silva Coutinho, R. & Serra, N.  zfit: scalable pythonic fitting.  _SoftwareX_ (2019).  URL https://doi.org/10.1016/j.softx.2020.100508.
  * [71] Eschle, J., Puig, A. N., Silva Coutinho, R. & Serra, N.  zfit: scalable pythonic fitting.  _EPJ Web Conf._ 245, 06025 (2020).  URL https://doi.org/10.1051/epjconf/202024506025.
  * [72] Besançon, M. _et al._ Distributions.jl: Definition and modeling of probability distributions in the juliastats ecosystem.  _Journal of Statistical Software_ 98, 1--30 (2021).  URL https://www.jstatsoft.org/v098/i16.
  * [73] Lin, D. _et al._ JuliaStats/Distributions.jl: a Julia package for probability distributions and associated functions.  _Zenodo_ (2019).  URL https://doi.org/10.5281/zenodo.2647458.
  * [74] StatsBase.jl package code repository (2021).  URL https://github.com/JuliaStats/StatsBase.jl.  (accessed September 29, 2021).
  * [75] Ling, J., Amin, N., Jacobsen, R. & Gal, T.  A pure julia 1D/2D histogram package that focus on speed and thread-safe.  _10.5281/zenodo.7191111_ (2022).  URL https://doi.org/10.5281/zenodo.7191111.
  * [76] Breloff, T.  Plots -- Powerful convenience for vizualisation in Julia.  URL https://docs.juliaplots.org/v1.30/.  (accessed June 15 2022).
  * [77] RecipeBase.jl (2022).  URL https://github.com/JuliaPlots/RecipesBase.jl.  (accessed June 15, 2022).
  * [78] Heinen, J. _et al._ GR Framework (1985--2022).  URL https://gr-framework.org/.  (accessed June 15 2022).
  * [79] Rosario, H. D. & Heinen, J.  GRUtils (2019--2022).  URL https://heliosdrm.github.io/GRUtils.j.  (accessed June 15 2022).
  * [80] Danisch, S. & Krumbiegel, J.  Makie.jl: Flexible high-performance data visualization for julia.  _Journal of Open Source Software_ 6, 3349 (2021).  URL https://doi.org/10.21105/joss.03349.
  * [81] Bierlich, C. _et al._ Robust Independent Validation of Experiment and Theory: Rivet version 3.  _SciPost Phys._ 8, 026 (2020).  URL https://doi.org/10.21468/SciPostPhys.8.2.026.
  * [82] PFGPlots code repository (2022).  URL https://github.com/JuliaTeX/PGFPlots.jl.  (accessed June 15, 2022).
  * [83] PFGPlotsX code repository (2022).  URL https://github.com/KristofferC/PGFPlotsX.jl.  (accessed June 15, 2022).
  * [84] Gaston code repository (2022).  URL https://github.com/mbaz/Gaston.jl.  (accessed June 15, 2022).
  * [85] Gnuplot (2022).  URL http://www.gnuplot.info/.  (accessed June 15, 2022).
  * [86] Matplotlib (2022).  URL https://matplotlib.org/.  (accessed June 15, 2022).
  * [87] Vega-Lite (2022).  URL https://vega.github.io/vega-lite/.  (accessed June 15, 2022).
  * [88] VegaLite.jl code repository (2022).  URL https://github.com/queryverse/VegaLite.jl.  (accessed June 15, 2022).
  * [89] UnicodePlots code repository (2022).  URL https://github.com/JuliaPlots/UnicodePlots.jl.  (accessed June 15, 2022).
  * [90] Inc., W. R.  Mathematica, Version 13.1.  URL https://www.wolfram.com/mathematica.  Champaign, IL, 2022.
  * [91] van der Plas, F. _et al._ fonsp/pluto.jl: v0.19.11.  _Zenodo_ (2022).  URL https://doi.org/10.5281/zenodo.6916713.
  * [92] Agostinelli, S. _et al._ Geant4—a simulation toolkit.  _Nuclear Instruments and Methods in Physics Research Section A: Accelerators, Spectrometers, Detectors and Associated Equipment_ 506, 250--303 (2003).  URL https://www.sciencedirect.com/science/article/pii/S0168900203013688.
  * [93] Byrne, S., Wilcox, L. C. & Churavy, V.  Mpi.jl: Julia bindings for the message passing interface.  _Proceedings of the JuliaCon Conferences_ 1, 68 (2021).  URL https://doi.org/10.21105/jcon.00068.
  * [94] Poulson, J. _et al._ Elemental.jl code repository (2017-2022).  URL https://github.com/JuliaParallel/Elemental.jl.  (accessed March 17, 2022).
  * [95] Elrod, C. _et al._ Loopvectorization code repository.  URL https://github.com/JuliaSIMD/LoopVectorization.jl.  (accessed March 17, 2022).
  * [96] Abbott, M. _et al._ mcabbott/tullio.jl: v0.3.5.  _Zenodo_ (2022).  URL https://doi.org/10.5281/zenodo.7106192.
  * [97] Gowda, S. _et al._ High-performance symbolic-numerics via multiple dispatch.  _ACM Commun. Comput. Algebra_ 55, 92–96 (2022).  URL https://doi.org/10.1145/3511528.3511535.
  * [98] Symata.jl (2022).  URL https://github.com/jlapeyre/Symata.jl.  (accessed March 17, 2022).
  * [99] MathLink.jl (2022).  URL https://github.com/JuliaInterop/MathLink.jl.  (accessed March 15, 2022).
  * [100] Gowda, S. _et al._ High-performance symbolic-numerics via multiple dispatch.  _ACM Communications in Computer Algebra_ 55, 92--96 (2021).  URL https://doi.org/10.1145/3511528.3511535.
  * [101] (2022).  URL https://sciml.ai/.  (accessed August 1, 2022).
  * [102] Rackauckas, C. & Nie, Q.  Differentialequations.jl -- a performant and feature-rich ecosystem for solving differential equations in julia.  _Journal of Open Research Software_ 5, 15 (2017).  URL https://doi.org/10.5334/jors.151.
  * [103] JuliaSymbolics Roadmap: A Modern Computer Algebra System for a Modern Language (2022).  URL https://juliasymbolics.org/roadmap/.  (accessed March 16, 2022).
  * [104] Ma, Y. _et al._ Modelingtoolkit: A composable graph transformation system for equation-based modeling (2021).  URL https://arxiv.org/abs/2103.05244.  ArXiv:2103.05244.
  * [105] SymbolicUtils.jl (2022).  URL https://github.com/JuliaSymbolics/SymbolicUtils.jl.  (accessed March 15, 2022).
  * [106] MathLinkExtras.jl (2022).  URL https://github.com/fremling/MathLinkExtras.jl.  (accessed March 17, 2022].
  * [107] Amoroso, S. _et al._ Challenges in Monte Carlo Event Generator Software for High-Luminosity LHC.  _Comput. Softw. Big Sci._ 5, 12 (2021).  URL https://doi.org/10.1007/s41781-021-00055-1.
  * [108] Valassi, A., Roiser, S., Mattelaer, O. & Hageboeck, S.  Design and engineering of a simplified workflow execution for the MG5aMC event generator on GPUs and vector CPUs.  _EPJ Web Conf._ 251, 03045 (2021).  URL https://doi.org/10.1051/epjconf/202125103045.
  * [109] DaggerGPU package code repository (2021).  URL https://github.com/JuliaGPU/DaggerGPU.jl.  (accessed September 29, 2021).
  * [110] Preston-Werner, T.  Semantic versioning 2.0.0 (2013).  URL http://semver.org.
  * [111] Expression problem.  URL https://en.wikipedia.org/wiki/Expression_problem.  Champaign, IL, 2022.
  * [112] Reynolds, J. C.  _User-Defined Types and Procedural Data Structures as Complementary Approaches to Data Abstraction_ , 309--317 (Springer New York, New York, NY, 1978).  URL https://doi.org/10.1007/978-1-4612-6315-9_22.
  * [113] Revise package code repository (2021).  URL https://github.com/timholy/Revise.jl.  (accessed September 29, 2021).
  * [114] JAX library code repository (2021).  URL https://github.com/google/jax.  (accessed September 29, 2021).
  * [115] TensorFlow web site (2021).  URL https://www.tensorflow.org.  (accessed September 29, 2021).
  * [116] PyTorch web site (2021).  URL https://pytorch.org/.  (accessed September 29, 2021).
  * [117] White, F. C. _et al._ Juliadiff/chainrules.jl: v1.44.7.  _Zenodo_ (2022).  URL https://doi.org/10.5281/zenodo.7182461.
  * [118] Juliadiff organisation website.  URL https://juliadiff.org/.  (accessed April 7 2022).
  * [119] Lorentzvectorhep.  URL https://github.com/JuliaHEP/LorentzVectorHEP.jl.  (accessed March 17, 2022).
  * [120] Table.jl.  URL https://tables.juliadata.org/stable/.  (accessed March 17, 2022).
  * [121] Schulz, O. _et al._ Bat.jl: A julia-based tool for bayesian inference.  _SN Computer Science_ 2, 210 (2021).  URL https://doi.org/10.1007/s42979-021-00626-4.
  * [122] Ge, H., Xu, K. & Ghahramani, Z.  _Turing: a language for flexible probabilistic inference_ , 1682--1690 (MLR Press, 2018).  URL http://proceedings.mlr.press/v84/ge18b.html.  In proceedings of the Conference on Artificial Intelligence and Statistics, AISTATS 2018, 9-11 April 2018, Playa Blanca, Lanzarote, Canary Islands, Spain.
  * [123] Gabler, P., Schulz, O., Widmann, D. _et al._ Densityinterface.jl code repository (2021-2022).  URL https://github.com/JuliaMath/DensityInterface.jl.  (accessed November 23, 2022).
  * [124] Plavin, A., Scherrer, C., Schulz, O., Widmann, D. _et al._ Inversefunctions.jl code repository (2021-2022).  URL https://github.com/JuliaMath/InverseFunctions.jl.  (accessed November 23, 2022).
  * [125] Schulz, O., Widmann, D. _et al._ Changesofvariables.jl code repository (2021-2022).  URL https://github.com/JuliaMath/ChangesOfVariables.jl.  (accessed November 23, 2022).
  * [126] Collaboration, T. A., Collaboration, T. C. & Group, T. L. H. C.  Procedure for the LHC Higgs boson search combination in Summer 2011.  Tech. Rep. CMS-NOTE-2011-005, ATL-PHYS-PUB-2011-011, ATL-PHYS-PUB-2011-11, CERN, Geneva (2011).  URL https://cds.cern.ch/record/1379837.
  * [127] Chatrchyan, S. _et al._ Combined results of searches for the standard model Higgs boson in pp collisions at $\sqrt{s}=7\,$TeV.  _Phys. Lett. B_ 710, 26--48 (2012).  URL https://doi.org/10.1016/j.physletb.2012.02.064.
  * [128] Collaboration, T. A.  Observation of an Excess of Events in the Search for the Standard Model Higgs boson with the ATLAS detector at the LHC (2012).  URL https://cds.cern.ch/record/1460439.  ATLAS-CONF-2012-093.
  * [129] Cowan, G., Cranmer, K., Gross, E. & Vitells, O.  Asymptotic formulae for likelihood-based tests of new physics.  _Eur. Phys. J. C_ 71, 1554 (2011).  URL https://doi.org/10.1140/epjc/s10052-011-1554-0.  [Erratum: Eur.Phys.J.C 73, 2501 (2013)].
  * [130] Junk, T.  Confidence level computation for combining searches with small statistics.  _Nucl. Instrum. Meth. A_ 434, 435--443 (1999).  URL https://doi.org/10.1016/S0168-9002(99)00498-2.
  * [131] Read, A. L.  Presentation of search results: The CL(s) technique.  _J. Phys. G_ 28, 2693--2704 (2002).  URL https://doi.org/10.1088/0954-3899/28/10/313.
  * [132] Fisher, W.  Systematics and limit calculations.  _FERMILAB-TM-2386-E, D0-NOTE-5309_ (2006).  URL https://doi.org/10.2172/923070.
  * [133] Junk, T.  Sensitivity, exclusion and discovery with small signals, large backgrounds, and large systematic uncertainties (2006).  URL https://inspirehep.net/literature/1361506.  CDF-8128, CDF-Note-8128.
  * [134] Verkerke, W. & Kirkby, D.  _The RooFit toolkit for data modeling_ , 186--189 (World Scientific, 2006).  URL https://doi.org/10.1142/9781860948985_0039.
  * [135] Boutigny, D. _et al._ BaBar technical design report (1995).  URL http://www.slac.stanford.edu/cgi-wrap/getdoc/slac-r-457.pdf.  SLAC-R-457.
  * [136] Cranmer, K., Lewis, G., Moneta, L., Shibata, A. & Verkerke, W.  HistFactory: A tool for creating statistical models for use with RooFit and RooStats.  _CERN-OPEN-2012-016_ (2012).
  * [137] ATLAS, CMS, LHC Higgs Combination Group.  Procedure for the LHC Higgs boson search combination in Summer 2011.  _CMS-NOTE-2011-005, ATL-PHYS-PUB-2011-11_ (2011).
  * [138] HiggsCombine code repository.  URL https://github.com/cms-analysis/HiggsAnalysis-CombinedLimit.  (accessed August 1, 2022).
  * [139] Heinrich, L., Feickert, M., Stark, G. & Cranmer, K.  pyhf: pure-Python implementation of HistFactory statistical models.  _J. Open Source Softw._ 6, 2823 (2021).  URL https://doi.org/10.21105/joss.02823.
  * [140] Ling, J.  LiteHF.jl: Julia implementation of HistFactory-style likelihood ratio methods and test statistics.  _Zenodo_ (2022).  URL https://doi.org/10.5281/zenodo.7435541.
  * [141] Blobel, V.  _Unfolding_ , Ch. 6, 187--225 (John Wiley and Sons, Ltd, 2013).  URL https://doi.org/10.1002/9783527653416.ch6.
  * [142] Schmitt, S.  TUnfold: an algorithm for correcting migration effects in high energy physics.  _JINST_ 7, T10003 (2012).  URL https://doi.org/10.1088/1748-0221/7/10/T10003.
  * [143] Adye, T.  in Proceeedings of the PHYSTAT 2011 workshop on statistical issues related to discovery claims in search experiments and unfolding (eds Prosper, H. & Lyons, L.) _Unfolding algorithms and tests using RooUnfold_ 313--318 (CERN, Geneva, 2011).  URL https://doi.org/10.5170/CERN-2011-006.313.  1105.1160.
  * [144] Brenner, L. _et al._ Comparison of unfolding methods using RooFitUnfold.  _Int. J. Mod. Phys. A_ 35, 2050145 (2020).  URL https://doi.org/10.1142/S0217751X20501456.
  * [145] Andreassen, A., Komiske, P. T., Metodiev, E. M., Nachman, B. & Thaler, J.  OmniFold: A Method to Simultaneously Unfold All Observables.  _Phys. Rev. Lett._ 124, 182001 (2020).  URL https://doi.org/10.1103/PhysRevLett.124.182001.
  * [146] Pharmaceutical development, pfizer uses julia to accelerate simulations of new therapies for metabolic diseases up to 175x.  URL https://juliacomputing.com/case-studies/pfizer/.  (accessed August 1, 2022).
  * [147] (Moderna), H. A.  Modeling and simulation to guide dose selection for mrna therapeutics and vaccines.  URL https://live.juliacon.org/talk/9N9HZ3.  Presented at the JuliaCon 2022 conference.
  * [148] Predicting toxicity.  URL https://juliacomputing.com/case-studies/astra-zeneca/.  (accessed August 1, 2022).
  * [149] URL https://juliacomputing.com/industries/banking-and-finance/.  (accessed August 1, 2022).
  * [150] Solvency II compliance, one of europe’s largest insurers is using julia for solvency II compliance.  URL https://juliacomputing.com/case-studies/aviva/.  (accessed August 1, 2022).
  * [151] Macroeconomic modeling, the Federal reserve bank of new york publishes its trademark dynamic stochastic general equilibrium models in julia.  URL https://juliacomputing.com/case-studies/ny-fed/.  (accessed August 1, 2022).
  * [152] Holy, T., Bezanson, J. & Nash, J.  Analyzing sources of compiler latency in julia: method invalidations.  URL https://julialang.org/blog/2020/08/invalidations/.  (accessed April 7 2022).
  * [153] Holy, T.  Tutorial on precompilation.  URL https://julialang.org/blog/2021/01/precompile_tutorial/.  (accessed October 14 2022).
  * [154] SnoopCompile package code repository (2021).  URL https://github.com/timholy/SnoopCompile.jl.  (accessed September 29, 2021).
  * [155] PrecompileTools package code repository (2023).  URL https://github.com/JuliaLang/PrecompileTools.jl.  (accessed June 5, 2023).
  * [156] Packagecompiler manual: Sysimages.  URL https://julialang.github.io/PackageCompiler.jl/v2.0/sysimages.html.  (accessed June 15 2022).
  * [157] Collaboration, C.  Cms offline software repository.  URL https://github.com/cms-sw/cmssw.  (accessed August 1, 2022).
  * [158] Tokei computer program code repository (2021).  URL https://github.com/XAMPPRocky/tokei.  (accessed September 29, 2021).
  * [159] Differentialequations.jl: Scientific machine learning (SciML) enabled simulation and estimation.  URL https://diffeq.sciml.ai/v7.3.0/.  (accessed June 15 2022).
  * [160] Holy, T. & Churavy, V.  Improvements in package precompilation.  URL https://live.juliacon.org/talk/DUQQLN.  Talk given at JuliaCon 2022.
  * [161] Documenter.jl, a documentation generator for julia.  URL https://juliadocs.github.io/Documenter.jl/v0.27/.  (accessed June 15 2022).
  * [162] Abgrall, N. _et al._ The Large Enriched Germanium Experiment for Neutrinoless $\beta\beta$ Decay: LEGEND-1000 Preconceptual Design Report (2021).  URL https://arxiv.org/abs/2107.11462.  ArXiv:2107.11462.
  * [163] Evaluation strategy.  URL https://en.wikipedia.org/wiki/Evaluation_strateg.  (accessed August 1, 2022).

##  Appendix A Polymorphism in C++ and Julia illustrated in code

Differences of polymorphism support in C++, Julia, and Python are discussed in
Sec. 1.3. This appendix provides code example illustrating the discussion.
Static ad-hoc function polymorphism can be implemented in C++ using two
different paradigms, function overriding and templates. We will illustrate
this with an example. Lets consider two classes, `A} and a derived class
\mintinline`c++AChild, and a global function

    f()}. Using the function overriding paradigm, the ad-hoc polymorphism on the function argument can be implemented as,
    \vspace

0.8em

⬇

#include <iostream>

\parstruct A{};

struct AChild: public A{};

struct B{};

\parvoid f(A a){

std::cout << "A\n";

}

\parvoid f(B b){

std::cout << "B\n";

}

The same ad-hoc polymorphism can be implemented in the template paradigm as
follows.

⬇

#include <iostream>

#include <concepts>

\partemplate<typename C, typename P>

concept Derived =

std::is_base_of<P, C>::value;

\parstruct A{};

struct AChild: public A{};

struct B{};

\partemplate<typename T> void f(T x){}

\partemplate<Derived<A> T>

void f(T a){

std::cout << "A\n";

}

\partemplate<>

void f(B b){

std::cout << "B\n";

}

Both implementations can be tested with the following code, which will result
in the same output.  ⬇ A a; AChild aChild; B b; \parint main(){ f(a); //prints
A f(b); //prints B f(aChild); // prints A return 0; } Output:

    A
    B
    A

While in C++, an ad-hoc function polymorphism can be implemented using two
different paradigms, the multiple dispatch feature of the Julia language
provides a single and consistent way to implement polymorphism. The Julia
implementation looks like the following.

⬇

abstract type AbstractA end

abstract type AbstractAChild <: AbstractA end

\parstruct A <: AbstractA end

struct AChild <: AbstractAChild end

struct B end

\parfunction f(a ::AbstractA)

println("A")

end

\parfunction f(b:: B)

println("B")

end

\para = A()

aChild = AChild()

b = B()

\parf(a) #prints A

f(b) #prints B

f(aChild) #prints A

In C++, the dispatch on argument type can be static or dynamic for the class
instance argument (`this}) and is always static for the other arguments. This
situation can be intricate as in the example below, where the selection of a
static or dynamic dispatch over the \mintinline`c++this argument depends on
the type of the other argument.

⬇

#include <iostream>

\parstruct A{

virtual void f(int x) const {

std::cout << "A::f(int)\n";

}

\parvoid f(const char* x) const {

std::cout << "A::f(const char*)\n";

}

};

\parstruct B: public A{

void f(int x) const {

std::cout << "B::f(int)\n";

}

\parvoid f(const char* x) const {

std::cout << "B::f(const char*)\n";

}

};

\parint main(){

B b;

\parA& a = b;

\para.f(1); //prints B::f(int)

a.f(""); //prints A::f(const char*) }

The C++ language includes an implicit type conversion when copying an object.
This feature can lead to confusion, as illustrated in the code below.

⬇

#include <iostream>

\parstruct Animal{

virtual void f() const {

std::cout << "Animal\n";

}

};

\parstruct Lion: public Animal {

void f() const {

std::cout << "King of Savannah\n";

}

};

\par\parvoid g(const Animal& a){

a.f();

}

\parvoid h(Animal a){

a.f();

}

\par\parint main(){

Lion leo;

\parg(leo); //prints King of Savannah

h(leo); //prints Animal

\parreturn 0;

}

This pitfall does not exist in Julia. There is a single way to pass arguments,
‘‘call by sharing’’ [163], which does not copy the arguments.

⬇

\parabstract type Animal end

\parstruct Lion <: Animal

end

\parfunction f(x::Animal)

println("Animal")

end

\parfunction f(x::Lion)

println("King of Savannah")

end

\parfunction g(x)

f(x)

end

\parleo = Lion()

g(leo) # prints "King of Savannah"

The lion is grateful to Julia for honoring his title.