Chapter 12 User Code Plug-Ins
Note that the user-code plug-in mechanism has been currently (for version 2.2.0) disabled, as the huge refactoring of the code between versions 2.1.X and 2.2.X has completely changed many of the interfaces. We plan to bring the interface for user code for spectra, structure functions and event shapes, cuts and observables back online as soon as possible, at latest for version 2.4.0.
12.1 The plug-in mechanism
The capabilities of WHIZARD and its SINDARIN command language are not always sufficient to adapt to all users’ needs. To make the program more versatile, there are several spots in the workflow where the user may plug in his/her own code, to enhance or modify the default behavior.
User code can be injected, without touching WHIZARD’s source code, in the following places:
Additional plug-in locations may be added in the future.
User code is loaded dynamically by WHIZARD. There are two possibilities:
The library file should either be a dynamically loadable (shared) library with appropriate extension (.so on Linux), or a libtool archive (.la).
The user-provided procedures may have arbitrary names; the user just has to avoid clashes with procedures from the Fortran runtime library or from the operating-system environment.
12.2 Data Types Used for Communication
Since the user-code interface is designed to be interoperable with C, it communicates with WHIZARD only via C-interoperable data types. The basic data types (Fortran: integer and real kinds) c_int and c_double are usually identical with the default kinds on the Fortran side. If necessary, explicit conversion may be inserted.
For transferring particle data, we are using a specific derived type c_prt_t which has the following content:
type, bind(C) :: c_prt_t integer(c_int) :: type integer(c_int) :: pdg integer(c_int) :: polarized integer(c_int) :: h real(c_double) :: pe real(c_double) :: px real(c_double) :: py real(c_double) :: pz real(c_double) :: p2 end type c_prt_t
The meaning of the entries is as follows:
WHIZARD does not provide tools for manipulating c_prt_t objects directly. However, the four-momentum can be used in Lorentz-algebra calculations from the lorentz module. To this end, this module defines the transformational functions vector4_from_c_prt and vector4_to_c_prt.
12.3 User-defined Observables and Functions
12.3.1 Cut function
Instead of coding a cut expression in SINDARIN, it may be coded in Fortran, or in any other language with a C-compatible interface. A user-defined cut expression is referenced in SINDARIN as follows:
cuts = user_cut (name-string) [subevent]
The name-string is an expression that evaluates to string, the name of the function to call in the user code. The subevent is a subevent expression, analogous to the built-in cut definition syntax. The result of the user_cut function is a logical value in SINDARIN. It is true if the event passes the cut, false otherwise.
If coded in Fortran, the actual user-cut function in the user-provided source code has the following form:
function user_cut_fun (prt, n_prt) result (iflag) bind(C) use iso_c_binding use c_particles type(c_prt_t), dimension(*), intent(in) :: prt integer(c_int), intent(in) :: n_prt integer(c_int) :: iflag ! ... code that evaluates iflag end function user_cut_fun
Here, user_cut_fun can be replaced by an arbitrary name by which the function is referenced as name-string above. The bind(C) attribute in the function declaration is mandatory.
The argument prt is an array of objects of type c_prt_t, as described above. The integer n_prt is the number of entries in the array. It is passed separately in order to determine the actual size of the assumed-size prt array.
The result iflag is an integer. A nonzero value indicates true (i.e., the event passes the cut), zero value indicates false. (We do not use boolean values in the interface because their interoperability might be problematic on some systems.)
12.3.2 Event-shape function
An event-shape function is similar to a cut function. It takes a subevent as argument and returns a real (i.e., C double) variable. It can be used for defining subevent observables, event weights, or the event scale, as in
analysis = record hist-id (user_event_fun (name-string) [subevent])
scale = user_event_fun (name-string) [subevent]
The corresponding Fortran source code has the form
function user_event_fun (prt, n_prt) result (rval) bind(C) use iso_c_binding use c_particles type(c_prt_t), dimension(*), intent(in) :: prt integer(c_int), intent(in) :: n_prt real(c_double) :: rval ! ... code that evaluates rval end function user_event_fun
with user_event_fun replaced by name-string.
In SINDARIN, an observable-type function is a function of one or two particle objects that returns a real value. The particle objects result from scanning over subevents. In the SINDARIN code, the observable is used like a variable; the particle-object arguments are implictly assigned.
A user-defined observable is used analogously, e.g.,
cuts = all user_obs (name-string) > 0 [subevent]
The user observable is defined, as Fortran code, as either a unary or as a binary C-double-valued function of c_prt_t objects. The use in SINDARIN (unary or binary) must match the definition.
function user_obs_unary (prt1) result (rval) bind(C) use iso_c_binding use c_particles type(c_prt_t), intent(in) :: prt1 real(c_double) :: rval ! ... code that evaluates rval end function user_obs_unary
function user_obs_binary (prt1, prt2) result (rval) bind(C) use iso_c_binding use c_particles type(c_prt_t), intent(in) :: prt1, prt2 real(c_double) :: rval ! ... code that evaluates rval end function user_obs_binary
with user_obs_unary/binary replaced by name-string.
For an example, we implement three different ways of computing the transverse momentum of a particle. This observable is actually built into WHIZARD, so the examples are not particularly useful. However, implementing kinematical functions that are not supported (yet) by WHIZARD (and not easily computed via SINDARIN expressions) proceeds along the same lines.
The first function is a complete cut which can be used as
cuts = user_cut("ptcut") [subevt]
It is equivalent to
cuts = all Pt > 50 [subevt]
The implementation reads
function ptcut (prt, n_prt) result (iflag) bind(C) use iso_c_binding use c_particles use lorentz type(c_prt_t), dimension(*), intent(in) :: prt integer(c_int), intent(in) :: n_prt integer(c_int) :: iflag logical, save :: first = .true. if (all (transverse_part (vector4_from_c_prt (prt(1:n_prt))) > 50)) then iflag = 1 else iflag = 0 end if end function ptcut
The procedure makes use of the kinematical functions in the lorentz module, after transforming the particles into a vector4 array.
Similar functionality can be achieved by implementing an event-shape function. The function computes the minimum pT among all particles in the subevent. The SINDARIN expression reads
cuts = user_event_shape("pt_min") [subevt] > 50
and the function is coded as
function pt_min (prt, n_prt) result (rval) bind(C) use iso_c_binding use c_particles use lorentz type(c_prt_t), dimension(*), intent(in) :: prt integer(c_int), intent(in) :: n_prt real(c_double) :: rval rval = minval (transverse_part (vector4_from_c_prt (prt(1:n_prt)))) end function pt_min
The third (and probably simplest) user implementation of the pT cut computes a single-particle observable. Here, the usage is
cuts = all user_obs("ptval") > 50 [subevt]
and the subroutine reads
function ptval (prt1) result (rval) bind(C) use iso_c_binding use c_particles use lorentz type(c_prt_t), intent(in) :: prt1 real(c_double) :: rval rval = transverse_part (vector4_from_c_prt (prt1)) end function ptval
12.4 User Code and Static Executables
In Sec. 5.4.7 we describe how to build a static executable that can be submitted to batch jobs, e.g., on the grid, where a compiler may not be available.
If there is user plug-in code, it would require the same setup of libtool, compiler and linker on the target host, as physical process code. To avoid this, it is preferable to link the user code statically with the executable, which is then run as a monolithic program.
This is actually simple. Two conditions have to be met:
With these conditions met, the stand-alone executable will have the user code statically linked, and it will be able to use exactly those user-defined routines that have been listed in the various option strings. (It is possible nevertheless, to plug in additional user code into the stand-alone executable, using the same options as for the original WHIZARD program.)