Received January 24, 2014, accepted February 7, 2014, date of publication February 20, 2014, date of current version March 7, 2014. Digital Object Identifier 10.1109/ACCESS.2014.2306895

Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library ROLF E. ANDREASSEN1 , WEERADDANA MANJULA DE SILVA1 , BRIAN T. MEADOWS1 , MICHAEL D. SOKOLOFF1 , AND KAREN A. TOMKO2

1 Physics 2 Ohio

Department, University of Cincinnati, Cincinnati, OH 45221, USA Supercomputer Center, Columbus, OH 43212, USA

Corresponding author: M. D. Sokoloff ([email protected]) This work was supported by NSF under Grant PHY-1005530 for the Development of GooFit.

ABSTRACT GooFit is a thread-parallel, GPU-friendly function evaluation library, nominally designed for use with the maximum likelihood fitting program MINUIT. In this use case, it provides highly parallel calculations of normalization intergrals and log (likelihood) sums. A key feature of the design is its use of the Thrust library to manage all parallel kernel launches. This allows GooFit to execute on any architecture for which Thrust has a backend, currently, including CUDA for nVidia GPUs and OpenMP for single- and multicore CPUs. Running on an nVidia C2050, GooFit executes 300 times more quickly for a complex high energy physics problem than does the prior (algorithmically equivalent) code running on a single CPU core. The design and implementation choices, discussed in detail, can help to guide developers of other highly parallel, compute-intensive libraries. INDEX TERMS Parallel processing, parallel programming, parameter estimation, parameter extraction. I. INTRODUCTION

Parameter estimation, often called ‘fitting’, is an important step in many physics analyses. Modern high-energy physics experiments are delivering data at increasing rates, and data sets have already grown so large, and physics models so complicated, that the running time of fits is a serious bottleneck. For example, in Ref [1], the eventual data sample was in excess of half a million events, and fits parallelised using MPI took 8-12 hours using 20 cores. The most commonly used tool for parameter estimation in particle physics is MINUIT [2]; it is a library - originally written in FORTRAN, now available in C++ - containing several algorithms for searching a parameter space to find the minimum of a user-provided function. The most widely used part of MINUIT is known as MIGRAD, an implementation of Davidon’s variable metric algorithm [3]. MINUIT is commonly used with the wrapper library RooFit [4], which provides object-oriented variable and function definitions, methods for normalisation integrals, and plotting utilities. RooFit is the primary inspiration for GooFit. It is intended to offer a similar interface to MINUIT, but speed up its execution by, transparently to the user, parallelising function evaluation. In tests with real physics problems using an nVidia C2050 GPU, we have achieved speedups as high as a factor of 300 relative to the prior (algorithmically equivalent) code running in a single thread in

160

a single CPU core, a factor of 50 relative to precisely the same code executing in a single thread in a single CPU core under OpenMP, and a factor of 5 relative to executing in 16 threads in a dual quad-core system. GooFit originally targeted nVidia GPUs, using the proprietary language CUDA [5] and its nvcc compiler; consequently it distinguishes between the ‘host’, the CPU on which the organising thread runs, and the ‘device’, the accelerator which does the massively-parallel function evaluation. GooFit now uses the Thrust template library [6] to organise all kernel launches. As a side benefit, this also allows OpenMP execution on ordinary single- and multi-core CPUs, using the gcc compiler. This paper explains the internal workings of GooFit’s engine core, especially the index array mechanism that mimics the virtual-function lookups and member variables of classes available in vanilla C++, but not in CUDA. This mechanism is what allows GooFit to evaluate arbitrary functions, in combinations not known at GooFit’s compile-time, on the GPU. A companion paper [7] shows how to use the GooFit interface and how to customise the function library. There, we consider the engine core as a black box and show how to set up fits without concern for what is happening under the hood. This paper is intended for those who want to take the black box apart and look at the gears, whether to debug,

2169-3536 2014 IEEE. Translations and content mining are permitted for academic research only. Personal use is also permitted, but republication/redistribution requires IEEE permission. See http://www.ieee.org/publications_standards/publications/rights/index.html for more information.

VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

extend, or to apply the arbitrary-functions mechanism to other projects. Section II contains an overview of the program flow and overall design of GooFit. The following sections, III through VII, consider the detailed implementation of each step. In addition to the code snippets in these sections, some of which are slightly rearranged to make long lines fit on the page, the full GooFit codebase1 is distributed as an open-source project under terms of the GNU Lesser General Public License, version 3.0, at https://github.com/GooFit/ GooFit. Section VIII outlines the results of using GooFit in real physics analyses; this information has also been reported in [7]. Section IX shows how GooFit switches between the Thrust backends it supports, at the moment CUDA and OpenMP. Finally, section X considers the future development of GooFit.

and allow the user to construct functions, including composite functions, from C++ objects - no CUDA knowledge required. A secondary goal is to make it easy to add a new function class to the library when needed.

A. MAXIMUM LIKELIHOOD FITS

GooFit is a fitting package; its intended use is to estimate parameters by fitting models to data. Data consists of events; events are independent measurements of one or more observables. For example, we might measure the energy and polarisation of photons emitted from some physical process; each event thus consists of two observables E and θ. Then, our model may be that the energy is normally distributed with standard deviation σ around a mean µ, while the polarisation is distributed as, say, P(θ ) = a + b cos(θ ). Then σ , µ, a and b are the fit parameters, the values we want our fit to determine. Internally, GooFit represents both kinds of numbers using the Variable class, but they are conceptually distinct: Observables are measurements, which the fit must take as given, while parameters are what the fit varies to determine how the model can best match the data. Fits are usually made in one of two ways: unbinned, in which we calculate the probability of each event, and maximise the joint probability with respect to the parameters; and binned, in which we calculate the expected number of events in each bin and minimise the χ 2 statistic which describes the overall deviations from the observed numbers. II. DESIGN OF GOOFIT

In broad outline, the user of MINUIT must provide two things: first, a list of parameters for the fit to vary; second, a function of those parameters, which the fit will minimise. Typically the function is a probability density function (PDF) that predicts the distribution of a data set, and the quantity minimised is the difference between the PDF and the data. In principle, one could write a CUDA kernel and launch it manually on some set of data every time a new fit was needed; indeed this is a very useful exercise for anyone who is a newcomer to either MINUIT or CUDA. This approach is, however, slow and error-prone. The central design goal of GooFit is to abstract away the details of writing the kernels, 1 This paper refers to version 0.4. VOLUME 2, 2014

FIGURE 1. Control and data flow of a GooFit program (top) and overall architecture of GooFit (bottom). Previously published in [7].

A generic outline of the flow of a GooFit program is shown in Fig. 1. It begins in the yellow user code, which creates GooFit Variable and GooPdf objects, representing parameters and functions respectively. The user code usually also creates a DataSet object and passes it to the target device (labeled ‘GPU’ in the figure, though a multi-core CPU is also a possibility; this is discussed in section IX) using the setData method of GooPdf; this is discussed in detail in section IV. When all is ready, the user invokes the fit method, which translates the Variables for MINUIT by repeated calls to DefineParameter, sets the functionto-be-minimised to be FitFun, and by default invokes the MIGRAD search algorithm. Note that the user can access the underlying TMinuit object if he wants to run a different search algorithm, and in principle it is possible to replace MINUIT entirely and use a different search package - this is the purpose of the FitManager interface, to isolate the interaction with outside libraries (in purple) to a single point. 161

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 1. A class declaration for a hypothetical object-oriented implementation of GooFit.

SNIPPET 2. An evaluate method for a hypothetical object-oriented implementation of GooFit.

SNIPPET 3. Definition of the device_function_ptr function signature used to pass information to GooFit evaluation methods.

Next, the MIGRAD algorithm searches the parameter space, invoking FitFun with different sets of parameters. FitFun translates from MINUIT’s parameter representation to GooFit’s, and passes this information to calculateNll, which copies it to the target device. It then launches the normalisation and evaluation kernels using its MetricTaker object’s operator methods, as discussed in sections VII and VI respectively. Finally, the operator method dispatches event data to the evaluation functions, as discussed in section III. Note that the user may define custom evaluation functions, hence the reappearance of some yellow in the midst of GooFit blue. III. FUNCTION TABLE

In a fully object-oriented fitting implementation, we would create a PDF class with a virtual evaluate method, as indicated in Snippet 1, where the CUDA keyword __device__ indicates execution on the device, and fptype is a GooFit typedef that can point to either float or double, allowing rapid switching of precision. Unfortunately, when we started this project, CUDA did not support virtual functions on the GPU. GooFit therefore mimics what the C++ runtime would do by using a table of function pointers. This section explains how the function table is populated and used. A. SIGNATURE

In a hypothetical, fully object-oriented version of GooFit, evaluate would be a member function of a PdfBase subclass, and would have access to member variables describing which parameters to use and which observables to look at. For example, we can imagine a GaussianPdf class’s evaluate method, as indicated in Snippet 2, where m_xval, m_mean and m_sigm would all be member variables of GaussianPdf, and the Event and 162

Parameter classes would contain some mapping from (say) Variable* to fptype. In the non-hypothetical version of GooFit, the device-side implementation of PDFs is not at all object-oriented, and falls back on the older C-style idiom of explicitly passing around all the information used in the function call. In effect, the underlying structure hidden by the syntactic sugar of object-oriented code is all exposed in GooFit. Thus, in place of an Event object, a Parameter object, and member variables, GooFit evaluation methods accept raw fptype pointers to event and parameter arrays, and an index array containing the information about which entries in the event and parameter arrays to use. The code shown in Snippet 3 defines the shorthand device_function_ptr to mean ‘‘pointer to a function taking two fptype arrays and an array of unsigned int, and returning an fptype.’’ Populating the index array is a complex subject in its own right, explained in section IV. For purposes of understanding the function table, it is only necessary to know that all GooFit evaluation functions have the signature above.

B. POPULATING THE FUNCTION TABLE

The function table is dynamically populated at runtime more specifically, when GooPdf objects are created. Every GooPdf subclass constructor does at least two things: populate the index array and create a handle - an integer that serves as a nickname for the function pointer - for the associated evaluation function if one doesn’t already exist. The constructor of the GaussianPdf, which does nothing else, is a good example; see Snippet 4. The macro GET_FUNCTION_ADDR copies the address of the associated function, in this case device_Gaussian, from a statically declared device-side pointer, in this case ptr_to_Gaussian, VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 4. The GaussianPdf constructor is an example of a GooPdf subclass constructor.

SNIPPET 5. The code for findFunctionIdx which places an address into the function table if it is not already there.

into the global variable host_fcn_ptr. This rather circuitous approach is a workaround for features of nvcc. The initialise method then calls findFunctionIdx to place the address into the function table if it is not already there, as illustrated in Snippet 5. Note the return value:

VOLUME 2, 2014

it is the index of the function pointer in the function table. This index is used as the ‘‘function handle’’ everywhere else in GooFit. This allows PDFs to store other functions simply by stashing an unsigned integer in their index array. Note also that device_function_table is an array of 163

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 6. By default, callFunction is used to call standard evaluation functions.

SNIPPET 7. One of the two evaluation methods of the AddPdf class. ‘Ext’ is short for ‘extended’, indicating that there are as many weights as components; the non-extended method treats the weights as fractions, and takes the weight of the final component as one minus the weight of all the others.

void*; this allows storing of functions which do not have the usual (fptype*, fptype*, unsigned int*) argument signature. An example can be seen in TddpPdf, which stores resolution functions in this way. Some caution is suggested when using this feature, however; if a non-standard function is called through the usual callFunction method discussed below, GooFit will crash. C. NESTING

The callFunction method, shown in Snippet 6, is the default way to call a standard evaluation function, typically to get the PDF value for a single event in the data set being fit. Here, eventAddress points to the first observable for this event; the others are found by offsetting from eventAddress. Usually this will be somewhere in the global event array. The global variable cudaArray is an array containing the parameters of all the functions, and paramIndices is a global array containing the positions within cudaArray of the parameters of the PDFs. The offset parIdx indicates the starting position of the indices of the function being called. The purpose of the callFunction method’s signature is to allow nesting of function calls, since every function is uniquely identified by its handle and parameter index. 164

An example is the evaluation method of the AddPdf class, shownP in Snippet 7. This method evaluates a function of the form fi Fi (Ex ; αE i ), where the weights fi and the elements of αE i are parameters of the fit. For each function Fi in the sum, the method needs to know three things: the function index, its parameter index, and its weight. The index array, therefore, has this structure: the first entry is the number of parameters - this is a GooFit-wide convention, enforced by the initialise method of GooPdf. Then follow triplets of function information: function handle, parameter index, and index of the weight parameter. Notice that, although both the weights fi and component parameters αE are fit parameters, AddPdf treats them differently: It uses the weights in its own calculation, but passes the αE on to its components for their use. With this information, the device_AddPdfs method iterates over the triplets, passing the event, function handle, and parameter index to callFunction. Then callFunction, in its turn, may invoke device_AddPdfs again, albeit with a different set of parameters, or it may invoke other compound functions like ProdPdf, whose index array also contains pairs of function handles and parameter indices. Eventually this process bottoms out in a simple function that calls no others. VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

FIGURE 2. Global arrays: Function table, parameter indices, and parameter values for a sum of a Gaussian and a polynomial.

A specific example may be helpful at this point. Consider a measurement in which we have a signal component described by a Gaussian and a background or noise component described by a second-order polynomial. The probability density function is then P(x; µ, σ, a, b, c, ns , nb ) =

ns G(x; µ, σ ) + nb P(x; a, b, c) ns + nb

where G and P are Gaussian and polynomial functions normalised over the range of our observations, xmin to xmax : e−

G(x; µ, σ ) = R xmax xmin

(x−µ)2 2σ

e−

(x−µ)2 2σ

dx

ax 2

P(x; a, b, c) = R xmax xmin

+ bx + c . (ax 2 + bx + c)dx

Fig. 2 shows how AddPdf’s index array represents this sum. First, the global parameter array, the argument pars in Snippet 7, contains the current value of the fit parameters in the order they were registered, as discussed in section IV. The index arrays of the Gaussian and polynomial functions (in green and purple) consist of the indices for their variables within the parameter array, plus a header containing the number of parameters, plus information about the event variable, as discussed in section IV. Notice that all index arrays are part of the global paramIndices array; the division into ‘‘Gaussian index array, polynomial index array, AddPdf index array’’ is purely conceptual. The AddPdf part of the array, in amber, first has the header giving its size, in this case 6 - two triplets. Then follows the triplet for the Gaussian: 0, 0, 5. The first zero points into the function table, indicating the function to call; the second points at paramIndices (in which it is itself contained), indicating VOLUME 2, 2014

where the index array of that function starts; and the five points at the parameter array, indicating the position of the weight ns . Similarly, for the second triplet, the number 1 points at the function table; the 5 is the location within paramIndices where the index array for the polynomial begins; and the 6 is the location, within the parameter array, of nb . Although it may not be immediately obvious, there is some method to our choice of when to pass a raw pointer to an array, and when to pass an index and allow the called code to decide what it is an index into. In particular, we pass raw pointers to the global event array, as opposed to event indices, because in some contexts (most notably normalisation) it is useful to make a ‘‘fake’’ event, that is, one which is calculated on the spot rather than pre-loaded into memory. Similarly, although there is currently nothing in GooFit that makes a ‘‘fake’’ parameter array, we retain the option of introducing such a feature by explicitly passing the global parameter array instead of allowing the evaluation functions to assume that they are always to take parameter values from cudaArray. IV. THE INDEX ARRAY

Snippet 7 and Fig. 2 show an example of the index array in use, storing the function-and-parameter index pair that uniquely identifies functions, plus the index of a weight parameter, for use in the AddPdf class. Snippet 8 shows the same index array being constructed. Passed an array of components and an array of their weights, the AddPdf constructor iterates over both, recording triplets: •

Function handles, returned by the getFunctionIndex method and ultimately set by findFunctionIdx, as discussed in Section III. 165

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 8. Construction of the index array for AddPdf. Some code elided for clarity. The full code can be found at https://github.com/GooFit/GooFit.

SNIPPET 9. Device code for evaluating a Gaussian probability.

Parameter indices, returned by getParameterIndex and set by initialise, which will be discussed next. • Indices of weights, which are set and returned by registerParameter. The registerParameter method is similar to findFunctionIdx: it maintains a list of registered Variable objects with indices. When passed a new Variable, one not already in the list, the method assigns the Variable an index; otherwise it returns the existing index. This index is the position of the Variable within the global parameter array paramIndices. For example, suppose we are setting up a simple fit to a single Gaussian. The GaussianPdf constructor registers the mean µ and standard deviation σ as parameters, in that order; their indices are 0 and 1. If, instead, we have two Gaussians with a shared mean, the order of the registerParameter calls will be thus: • First Gaussian registers µ; index 0 is assigned and returned. • First Gaussian registers σ1 , index 1 is assigned and returned. • Second Gaussian registers µ, which is already in the list; 0 is returned. •

166

Second Gaussian registers σ2 ; index 2 is assigned and returned. In the evaluation method, each Gaussian will be passed its own index array, and use it to look up its own parameters, as shown in Snippet 9. For both Gaussians, indices[1] equals 0, the index of their shared mean; so they use the same entry in the parameter array par - which is precisely the intention. On the other hand, their values for indices[2] are 1 and 2 respectively, so they look up different entries for σ1,2 . Every GooPdf constructor, in addition to whatever other tasks it might have, must do at least these two things: it must populate its own index array - usually though not necessarily involving calls to registerParameter - and must send it to the initialise method. The initialise method will create the full index array of the PDF object, which consists of two parts: the parameter list, starting with the number of parameters; and the observable list. The parameter list consists of the integers that the subclass constructor pushes onto the pindices vector; as we’ve seen in the AddPdf case (see Fig. 2), they are not necessarily indices of parameters, but that is the default case. Note that it is up to the creator of new PDF classes and evaluation functions to interpret the indices correctly. •

VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 10. Code for recursively assigning indices to observables.

The second half of the index array consists of indices of observables within events, rather than parameters within the parameter array; otherwise it has the same structure, with the first entry being the number of observables, and subsequent entries being the indices within an event. Consider a fit to two Gaussians with different observables x and y, registered in that order and assigned indices 0 and 1. In the line fptype x = evt[indices[2 + indices[0]]]; the value of indices[2 + indices[0]] is either 0 or 1, depending on which Gaussian we are evaluating at the moment. This double-indirection lookup, using a value contained in indices to determine where in indices to look for the index of the value we actually want, is sometimes confusing, and worth considering in detail. First, the 2 used as an offset in the index of indices accounts for the spaces occupied by entries indicating the number of parameters or observables. Then, indices[0] is the number of parameters; to get to observables we want to skip ahead by this many. For example, in the case of a single Gaussian the index array looks like this: #p 2

p1 0

p2 1

#o 1

o1 0

The first entry, indices[0], is the number of parameters, which is 2. The next two entries are the indices of µ and σ within the parameter array. Then comes the number of observables, 1; and the index of that observable with the event array. We see that 2 + indices[0] is 4, the index of the index2 of the observable. Then indices[2 + indices[0]] is indices[4], which is 0; and evt[indices[2 + indices[0]]] is evt[0] which is (finally!) the value of the observable for this event. We now understand how the parameter part of the index array is used and populated, and how the observable part 2 Not a typo! VOLUME 2, 2014

is used. It remains to consider how the observable part is populated. The initialise method reserves space for the observable indices, but does not set the values. This is because the indices of observables are not known until the entire PDF has been constructed and all subcomponents have registered their observables. There is no global repository of observables;3 each component PDF maintains its own. A ‘‘boss’’ PDF such as AddPdf creates its list of observables by recursively fetching the observables of its components. If the observable indices were assigned at PDF creation time, the two Gaussians of our example above would both assign their observable, x and y respectively, the same index 0; and this would obviously be disastrous. Instead, a call to the setData method is taken as a signal that PDF construction is done and that the object on which we called setData is the top-level PDF, the one of which all others are components. (Care should be taken only to invoke setData on GooPdfs which genuinely are the top-level objects of their fit.) This boss PDF recursively gathers all the observables of itself and its components and iterates over them to assign indices. Next it copies these indices to host_indices, a hostside copy of the global parameter array paramIndices using the method shown in Snippet 10. Here, obsIter is a typedef for an iterator over the container class for observables, currently vector, and components is a member variable containing pointers to component PDFs. Each GooPdf object knows its own position within the global index array, stored in the member variable parameters; thus host_indices[parameters] is the equivalent of indices[0] in an evaluation function - it is the place where this GooPdf’s index array starts. Then we recognise 2 + numParams as just the offset to get to the index of the index of the first observable. The iteration over the observables sets these indices in each GooPdf’s part of the host-side parameter array. Finally, everything is copied across to the device side: 3 In hindsight, this design decision may not have been optimal.

167

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 11. setData code for moving observable values onto the target device.

MEMCPY_TO_SYMBOL(paramIndices, host_indices, totalParams*sizeof(unsigned int), 0, cudaMemcpyHostToDevice); This completes the population of the index array. Having gotten this far in studying what setData does, we may as well finish the job by considering its eponymous task, actually moving the observable values onto the target device, The code shown in Snippet 11 has two fairly straightforward parts. The double for loop copies from the internal storage of UnbinnedDataSet into a host-side data array. Event number i starts at i*dimensions, and the observable (*obs) is offset by its index from that position. The method getValue returns the value of the provided Variable in event number i. We thus create an array whose format is x1 y1 z1 x2 y2 z2 ... xN yN zN

168

where x, y and z are the observables. The second part allocates room for a copy of this data array on the target device, and copies the numbers across. Thus far we have considered unbinned data; for binned data the implementation is similar, but we create an array of the form x1 y1 z1 E1 V1 x2 y2 z2 E2 V2 ... xN yN zN EN VN where x, y, z describe the center of a three-dimensional bin, E is the number of entries in a bin, and V is either the volume or the weight of a bin - this allows fits with nonuniform binning, or fits with user-specified errors, but not both at the same time. Expanding GooFit to allow both is relatively straightforward. The evt pointer that is passed to evaluation methods is a pointer into the global event array; the thread that evaluates the nth event is passed the pointer dev_event_array + n*eventSize. This is considered in more detail in section VI.

VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 12. Core kernel-launching code. The iterator types and the transform_reduce method, marked in blue, are from the Thrust library.

SNIPPET 13. Conceptual transform_reduce call.

V. PARALLELISATION USING THRUST

The fundamental operation of GooFit, that which it is intended to parallelise, is to evaluate a particular function for each event in a data set, and return the sum of all the function values. This fits very neatly into a transform-reduce framework in which each event is first transformed by the evaluation of our function, and then the set is reduced by summing over it. It is not surprising, then, that the kernel launch, the point at which GooFit talks to the GPU through the intermediary of the Thrust library, uses the transform_reduce method. However, there is a certain amount of infrastructure surrounding this conceptual simplicity, and this is what we consider in this section. The core kernel launch occurs in the sumOfNll method in the GooPdf class. ‘Nll’ is short for ‘‘negative loglikelihood’’, that is, the negative sum of the logarithms of the probabilities of each event. Minimising this quantity - for historical reasons, MINUIT insists on minimising rather than maximising - is equivalent to maximising the joint probability of the data set. GooFit does not necessarily do a log-likelihood fit; the metric to be minimised can be anything the user pleases. A frequently used example, which GooFit already supports, is a sum of χ 2 in a binned fit. This is explored further in section VI. However, the log-likelihood metric was the first one implemented, so the name has stuck. The method itself is quite small, as shown in Snippet 12. Let us go through this from the inside out. The two calls to make_tuple within make_zip_iterator create the two triplets of iterators that define the range over VOLUME 2, 2014

which the transformation operator will work. Specifically, we pass the event number, the starting point of the data set (in this case dev_event_array), and the size of an event. A zip_iterator is a Thrust construct consisting of several individual iterators, each of which is incremented when the zip_iterator is. The difference between two zip_iterators is taken as the difference between their first members, in this case the counting_iterator over events - so numEntries threads are created. The two constant_iterators do not change on being incremented; thus, the nth thread gets the triplet • Event number n • Data array starts at dev_event_array • Event size is numVars. Thrust takes care of the division into blocks and the transformation from the underlying threadIdx and blockIdx (or in the case of OpenMP, thread number) variables to a unitary ‘‘event index’’ argument. We initially experimented with setting the number of blocks and threads manually, but found that the Thrust defaults were optimal to within a few percent for our test cases. Next, logger is a member variable of GooPdf, pointing to an instance of the auxiliary class MetricTaker. We return to this class in more detail in section VI; for now, it suffices to note that it keeps track of the top-level PDF and of the metric to apply, and that it does the transformation from event index to position in the event array. These three arguments specify the transformation part of the algorithm. Then, cudaPlus and dummy specify the reduction: simply 169

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 14. Operator method of the MetricTaker class, slightly rearranged for readability.

to add the results together, starting at zero. Conceptually we can translate the entire transform_reduce call into the for loop shown in Snippet 13. The call to logger’s operator method in the notional for loop raises a design question: it is clear why we are passing evtIndex on every call, but why are dev_event_array and eventSize, which do not change within the loop, arguments to this method? Why not make them member variables of MetricTaker, as the function handle and parameter index of the top-level PDF are? The answer is that although the data set and event size are constant here, we use different ones in the otherwise similar transform_reduce call in the normalise method discussed later; so it is more natural to have them as arguments. The function index, on the other hand, does not change after GooPdf initialisation (where the MetricTaker is constructed), so we specify it once and are done. VI. EVENT AND FUNCTION DISPATCH

The auxiliary MetricTaker class is named for its task of calculating the goodness-of-fit metric that we are trying to minimise, usually a negative log-likelihood or a χ 2 . To do so it stores, as member variables, the function handle and parameter index of the containing GooPdf object - usually the top-level PDF - and a function index for the eponymous goodness-of-fit metric. By writing new metric functions, one can extend the types of metric GooFit supports. As discussed in section V, MetricTaker’s operator method, one per thread, receives as input an event index (which Thrust calculates from the underlying thread and block information), a pointer to the beginning of the data array, and an event size. 170

From these it calculates the location of the event that this thread is to handle, and dispatches that pointer, along with the function handle and parameter index, to callFunction, which is discussed in section III. The value returned from callFunction is then passed to the metric function, as shown in Snippet 14. Notice that the pointer to the metric function, like pointers to evaluation functions, is stored in device_function_ table, but does not have the usual device_function_ ptr signature; hence the cast from void to function pointer is not done by callFunction. This is an example of the feature discussed in section III, that the device_function_table, being an array of void*, can store any kind of function. The tradeoff for this flexibility is that the developer must keep his tongue straight in his mouth and not try to use a pointer to fooFunctionType in a call to barFunctionType. Functions of type device_metric_ptr take three arguments, of which two are straightforward. The first is simply the function value for which to calculate the goodnessof-fit metric. The third is the parameter index for the containing GooPdf object; this is used to look up the normalisation factor, as explained below. The second argument is a pointer to the second-to-last entry in the current event. This is a hack which allows a single operator method to handle both unbinned and binned fits. For binned data, as explained in section IV, the last two entries of an event are the bin content and bin volume. These numbers are used in, for example, the χ 2 calculations shown in Snippet 15. Here, normalisationFactors[par] contains the inverse of the integral of the function across the allowed domain. Thus, the function value at the center of the VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 15. Code for calculating the χ 2 statistic.

SNIPPET 16. Code for calculating the negative logarithm of the PDF for a single event.

bin, rawPdf, multiplied by the normalisation factor and the bin volume, is (with the approximation that the function varies slowly across the bin) equal to the expected fraction of events in the bin. Multiplying by the total number of events, stored in functorConstants[0], gives the expected number of events. To get the χ 2 value, we subtract the observed number of events, divide by the square root of the observed number, and square the whole quantity. For unbinned fits, on the other hand, the second argument is ignored. This is illustrated in Snippet 16 where the code simply returns the negative logarithm of the normalised PDF, i.e. the likelihood, for the event. Notice that in this case, the event size might be 1 (for binned fits, the minimum event size is 3, in the case of a single dimension - bin center, bin content, bin size) and if so, the pointer to the ‘‘second-to-last’’ entry is actually pointing to the previous event! However, since it is not used, this does not matter. Notice that we take the absolute value of eventSize; this is a relic of an earlier method of distinguishing binned from unbinned fits. We have retained the flag of making eventSize negative for possible future use, at the expense of some clarity in the code plus the need for this explanatory paragraph. VII. FUNCTION NORMALISATION

Probability density functions, by definition, have the property that their integral over the range of interest is one; in other words, the sum of probabilities for all possible outcomes4 must be unity. GooFit evaluation functions generally return un-normalised values; that is, their integrals over the allowed range is not equal to one. The reason for this is threefold. First, not every fit involves a PDF; for example, one might fit 4 That is, all outcomes covered by our physics model. In general we do not allow for the equivalent of a coin landing on edge - such an event would be thrown out of the data set before any fitting was done. VOLUME 2, 2014

TABLE 1. Timing results for a time-dependent amplitude analysis of 100 000 D0 → π − π + π 0 candidates, an unbinned maximum likelihood fit (Mixing fit) and for a binned maximum likelihood fit (Zach’s fit). The platform specifications are provided in Table 2.

a polynomial to a set of points, and then the function does not obey the above constraint. Second, in many cases the analytic integral of a function is computationally expensive, or even unknown. Third, even when known and inexpensive, integrating within the evaluation function would require passing around the limits of the integration to be done, increasing the complexity of the code. For these reasons, GooFit performs numerical integration of PDFs, and does it separately from the main evaluation. This section shows the details of how this is done. Section VI shows how the metric-evaluation functions use the normalisationFactors array to calculate likelihoods (or expected fractions) from raw function values. This array is filled on every MINUIT iteration, just before the call to sumOfNll. In particular, the creatively-named normalise method first checks whether there is an analytic expression for the integral of its GooPdf class; if not, 171

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 17. Second operator method of the MetricTaker class.

it numerically calculates the integral. Either way, the inverse integral is stored in host_normalisation and, when all integrals have been calculated, copied to the deviceside normalisationFactors array. The parameter member variable of GooPdf has an extra duty here: it stores the index of the PDF’s inverse integral within normalisationFactors as well as of its parameters in cudaArray. GooPdf subclasses may advertise an analytic integral by overriding hasAnalyticIntegral to return true; if they do, they must also override the integrate method, unless their integral is zero. Most subclasses will use numerical integration. Here, a fitting package intended for massively parallel execution has an advantage: it can simply evaluate the integrand at several tens (or hundreds) of thousands of points and sum the values. This said, the number of points required for this brute-force approach is literally exponential in the number of dimensions. Normalisation is perhaps the weakest part of GooFit, requiring special care even for fits of only three or four dimensions. The brute-force approach 172

is ripe for disruptive evolution in future iterations of the package. To specify the grid of points for numerical integration, each GooPdf object has a normRanges member variable, which is an array of triplets, one for each observable of the function. Each triplet is the lower bound, upper bound, and number of integration bins to use for that observable. From this information, plus a global bin index, the second operator method of the MetricTaker class creates a ‘‘fake event’’ for each thread, storing the bin center for its global bin in the local binCenters array and passing that to callFunction, as shown in Snippet 17. The signature of this operator method is slightly different from the one discussed in section VI, which is how Thrust tells them apart: This one takes int, int, fptype* in that order, while the other takes int, fptype*, int. The invocation for this is shown in Snippet 18. The differences are that here the counting_iterator goes over the global bins of the normalisation range and arrayAddress points to the device-side copy of normRanges. VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

SNIPPET 18. Code to invoke the operator method of the MetricTaker class when used for function normalisation. TABLE 2. Some specifications of the testing platforms. Asterisks next to the number of cores indicate hyperthreading - two virtual processors per physical core.

GooPdf subclasses may override the normalise method; notably, AddPdf and ProdPdf do so to ensure that their components are normalised. AddPdf calls the normalise method of each of its components (which, if they in turn are AddPdfs or ProdPdfs, may recursively call their own components), and returns the weighted sum of their integrals. Although the inverse of the integral is stored in normalisationFactors, the normalise method returns the un-inverted integral, to avoid confusion when using normalise for purposes other than updating normalisationFactors. ProdPdf, similarly, recursively normalises its components. In functions that factorise, this avoids the exponential growth of the required number of points at the cost of some additional kernel launches. Thus, in a function of the form P(x, y) = A(x)B(y), the x and y integrals are evaluated in separate kernel launches, requiring only M + N threads (where M and N are the number of bins in x and y respectively), where the default GooPdf implementation would require MN threads. However, in cases where two or more components share an observable, so that the function does not factorise neatly, ProdPdf uses the GooPdf implementation. VIII. RESULTS

The results in this section are presented in greater detail in Ref. [7] which is targeted at the user community rather than the developer community. To ensure the usefulness of GooFit in real-world physics problems, we simultaneously developed GooFit and used it VOLUME 2, 2014

as the fitting tool for a time-dependent amplitude analysis of the decay D0 → π − π + π 0 , a study of particle-antiparticle mixing in the neutral charm meson system. Conceptually, the analysis parallels that for D0 → KS0 π − π + [1] which served as a model. The analysis was originally developed separately for CPU-only and GPU-enabled configurations. When GooFit evolved to use Thrust for all its parallel algorithms, it became possible to run precisely the same algorithm on a single CPU or multiple CPUs using the OpenMP back-end and on nVidia GPUs using the CUDA back-end. We compare intermediate results as well as final results from the analyses run on different architectures to assure the equivalence of the algorithms and their numerical stability, even though the GPU double precision arithmetic does not adhere to all aspects of the IEEE floating point standards. The primary benchmark code is the full time-dependent amplitude fit including 16 complex resonances in the signal model, a less complicated background model, a model for efficiency as a function of position in the Dalitz plot, and a distribution of the decay time uncertainty σt which varies across the Dalitz plot. There are about 40 free parameters and the data set is roughly 100K events. Characteristics of the platforms we use for timing the code are shown in Table 2. The results of the timing tests are summarized in Table 1. (Results for a Mac laptop with the same GPU as Starscream and a slightly more powerful CPU were consistent for the GPU configuration and somewhat faster for the OpenMP configuration.) Wall clock timing and internal processor timing results are consistent, and the timing results for any data 173

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

TABLE 3. GooFit keywords creating the abstraction layer for switching between CUDA and OpenMP.

set are robust. Using simulated data rather than real data, similarly sized data sets require different processing times, depending on the number of iterations of parameters needed for convergence, so nominally similar problems differ by a factor of 2.5 in execution time. However, the time per iteration is stable at the 5% level. In addition to the mixing fit described above, we have tested GooFit on ‘‘Zach’s fit’’, named for a (now graduated) student of our group. This is a binned fit, extracting the D∗+ line width from measurements of the D∗+ − D mass difference, and involving an underlying relativistic P-wave BreitWigner convolved with a resolution function comprising several Gaussians [8]. In its original RooFit implementation, this fit takes about 7 minutes on our workstation ‘Cerberus’. With GooFit this is reduced to just under 6 seconds using the C2050 GPU. This is a significant speedup, but not as impressive as with the mixing fit. We do not fully understand the details of these results. The mixing fit code running on a single core under OpenMP runs much more quickly than the equivalent code written for CPU execution. For Zach’s fit, using RooFit and GooFit interfaces to MINUIT with parallel calling sequences, we observe a similar ratio of execution times. We expect that this results from GooFit using fewer virtual function calls and more effective organization of memory. Executing GooFit on CPUs via OpenMP, the program speed increases almost linearly when running up to 8 processes on the dual quadcore system, then drops when hyperthreading is used to run 12 processes, and increases again at 16 processes. This very non-linear (not even monotonic) behavior may originate in memory access contention between the tasks. The overall speed increase moving from 8 to 16 processes is 30%. Running on a newer 4-core laptop, the overall speed increase moving from 4 to 8 processes is 50%. We interpret this to be a result of improved hyperthreading. Running on the C2050 nVidia board (an HPC product intended for computationally intensive scientific work) GooFit is 300 times faster on the GPU board than the equivalent code running in a single thread in a single CPU core, 50 times faster than itself running in a 174

single thread in a single CPU core, and 5 times faster than itself running in 16 threads in a dual quad-core system. On a laptop, GooFit runs almost twice as quickly on a gamer GPU chip as it does using 8 threads in a 4-core CPU using OpenMP. The speedup in Zach’s fit is not as good as in the mixing fit; we do not fully understand the difference, but believe it is partly because a binned fit, with only a few thousand PDF evaluations per MINUIT iteration, does not take as much advantage of the massive parallelisation of the GPU as does an unbinned fit with a hundred thousand events. IX. RUNNING ON BOTH GPU AND CPU

GooFit can use either the CUDA or the OpenMP backend of the Thrust library. The price of this flexibility is a layer of abstraction, replacing CUDA-specific keywords and methods such as __device__ and cudaMemcpy with GooFitspecific macros. For example, when compiling for a GPU, the evaluation functions must be marked __device__, denoting to nvcc that they are to be executed on the GPU. In the OpenMP case, the functions are to be executed on the host; thus in CUDA idiom they should be marked __host__, while in ordinary C++ idiom they should not be marked at all, since host execution is the assumed default. Similarly, for a CUDA target, GooFit relies on the CUDA built-in method cudaMemcpy, and its variant cudaMemcpyToSymbol for transfers to constant memory. In the OpenMP version, there is neither constant nor shared memory, and all memory transfers are done on the host using plain memcpy, familiar to C programmers since the days of Kernigan and Ritchie [9]. The #define preprocessor commands that comprise the switch between CUDA and OpenMP are in the GlobalCudaDefines.hh file in the main GooFit directory; Table 3 shows the keywords thus created. A. FLOATING-POINT ACCURACY

GlobalCudaDefines specifies that fptype is either float or (by default) double, and aliases the corresponding C math functions to all-caps macros. Thus for example EXP expands to either exp or expf, depending on VOLUME 2, 2014

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

whether fptype is double or float. This nominally allows switching GooFit’s precision with a one-line change; as noted below, the single-precision version does not work yet. X. FUTURE WORK

GooFit is ready for use in real-world physics problems, but several areas remain for future development and understanding. In no particular order: •







In principle, defining fptype as float should enable us to take better advantage of the subprocessors in nVidia GPUs, not all of which have double precision. However, doing so causes MIGRAD to take more iterations to converge, eliminating the gains from faster iterations. The difficulty appears to lie with the convergence criteria: MIGRAD reaches the vicinity of the eventual solution in roughly as many iterations as with double precision, but then hovers around the basin of attraction, apparently unable to ‘‘make up its mind’’ that it has found the answer. This is puzzling since the variablemetric algorithm is old enough to have been run when 32-bit architectures were the only game in town. A possible explanation is that the summation now runs over data sets orders of magnitude larger than what were available then. The brute-force normalisation method does not scale well, as discussed in section VII. Further, the MAX_NUM_OBSERVABLES limit in the normalising operator is not very elegant; worse, in tests on the 650M it causes crashes if set to the default 5. Presumably this is related to the size of __shared__ memory. Ideally, GooFit should support all the backends of Thrust; as more parallel frameworks are created, this will likely be a place for continuous development. For example, a backend for Intel’s Thread Building Blocks would allow GooFit to run effectively on their ManyIntegrated-Core accelerators, such as the Xeon Phi. Likewise, a backend for AMD’s GPUs would open up another class of accelerators for GooFit to run on. So far we have run GooFit on single accelerators. A longterm goal is to enable GooFit to talk to multiple GPUs, splitting the events between them and thus increasing still further the number of available processors.

XI. SUMMARY

GooFit is a thread-parallel, GPU-friendly function evaluation library. The default use case is providing MINUIT with highly parallel calculation of normalisation integrals and likelihood evaluations, but its modular structure allows it to provide similar functionality to any algorithm. A key feature of the design is its use of the Thrust library to manage all parallel kernel launches. This allows GooFit to execute on any architecture for which Thrust has a backend, currently including CUDA for nVidia GPUs and OpenMP for single- and multicore CPUs. In tests with binned and unbinned maximum likelihood fits, we observe speedups of order 5 on a single VOLUME 2, 2014

CPU with respect to other MINUIT wrappers. On an nVidia C2050 GPU, we observe another factor of 50 speedup for an unbinned likelihood fit, and less for a binned likelihood fit. With the OpenMP backend, we observe speedups linearly proportional to the number of physical cores available, and a further 30-50% speedup when taking advantage of hyperthreading. GooFit uses double indirection for both variable and function lookups. The resulting flat memory organisation allows fast data access; we believe some part of the performance advantage over RooFit comes from this non-object-oriented structure. Since earlier CUDA versions did not support virtual functions calls, GooFit implements run-time polymorphism using function pointers. This necessity has the virtue of avoiding a pitfall of object-oriented programming, where virtual functions, and their unavoidable overhead, may be nested within inner loops without the programmer noticing. GooFit allows the user to construct composite functions by combining C++ objects, and also to extend the function library without directly engaging with the engine core. This conceptual separation of the code into interface, customisation, and engine components may be a useful model for other thread-parallel libraries. ACKNOWLEDGMENT

We are grateful for valuable suggestions and help from Cristoph Deil and feedback and bug reports from Olli Lupton and Stefanie Reichert. Robert Lupton created the nvcc aliasing to allow easy OMP switching on systems without CUDA. Jan Veverka and Helge Voss contributed implementations of the bifurcated Gaussian and Landau distributions. The Ohio Supercomputer Center made their computer farm ‘‘Oakley’’ available for development, for testing, and for GooFit outreach workshops. nVidia’s Early Access Program made it possible to test our code on K10 and K20 boards. REFERENCES 0

[1] P. del Amo Sanchez et al., ‘‘Measurement of D0 − D mixing parameters using D0 → KS0 π + π − and D0 → KS0 K + K − decays,’’ Phys. Rev. Lett., vol. 105, pp. 081803-1–081803-7, Apr. 2010. [2] F. James, ‘‘Minuit—Function minimization and error analysis,’’ CERN Program Library Long Writeup, Meyrin, Switzerland, Tech. Rep. D506, 1972. [3] W. C. Davidon, ‘‘Variable metric method for minimization,’’ SIAM J. Optim., vol. 1, no. 1, pp. 1–17, 1991. [4] W. Verkerke and D. Kirkby, ‘‘The RooFit toolkit for data modeling,’’ in Proc. Conf., 2003, pp. 1–2. [5] nVidia Corp., Santa Clara, CA, USA. (2013). CUDA Runtime API [Online]. Available: http://docs.nvidia.com/cuda/pdf/CUDA_Runtime_ API.pdf [6] N. Bell and J. Hoberock, Thrust: A Productivity-Oriented Library for CUDA. Montgomery, AL, USA: GPU, 2012. [7] R. Andreassen, B. T. Meadows, M. de Silva, M. D. Sokoloff, and K. Tomko, ‘‘GooFit: A library for massively parallelising maximum-likelihood fits,’’ in Proc. CHEP, 2013, pp. 1–3. [8] J. Lees, V. Poireau, V. Tisserand, E. Grauges, A. Palano, G. Eigen, et al., ‘‘Measurement of the D∗ (2010)+ meson width and the D∗ (2010)+ − D0 mass difference,’’ Phys. Rev. Lett., vol. 111, no. 1, pp. 111801-1–111801-3, 2013. [9] B. Kernigan and D. Ritchie, The C Programming Language. Upper Saddle River, NJ, USA: Prentice-Hall, 1978. 175

ANDREASSEN et al.: Implementation of a Thread-Parallel, GPU-Friendly Function Evaluation Library

ROLF E. ANDREASSEN was born in Bergen, Norway, where he received his bachelor’s and master’s degrees. He moved to Cincinnati in 2004, and received his Ph.D. degree in particle physics in 2010; subsequently, he was a Post-Doctoral Fellow dividing his time between the GooFit project and analysis of BaBar data. He is currently working in the private sector.

WEERADDANA MANJULA DE SILVA received his B.Sc. degree in physics from the University of Ruhuna, Matara, Sri Lanka, in 2005. He was an Assistant Lecturer with the Department of Physics, University of Ruhuna, from 2006 to 2008. He was awarded a scholarship from the University of Cincinnati, USA, in 2009, to pursue his Ph.D. degree in physics. He is currently a Research Assistant with the Department of Physics, University of Cincinnati, and working on his Ph.D. degree using data taken from the LHCb experiment, CERN, under the supervision of Prof. Brian Meadows and Prof. Michael Sokoloff.

BRIAN T. MEADOWS is a Particle Experimentalist with current research interests in precision measurements in flavor physics. He received his D.Phil. degree from Oxford University in 1966 and, after Post-Doctoral Fellowships there and in Syracuse University, assumed a faculty position with the Physics Department, University of Cincinnati. Over a long research career, he has several hundred publications, mostly as a member of both large and small collaborations. He has authored phenomenological and review articles in particle physics. Several time-dependent amplitude analyses of huge and rapidly growing samples of recently acquired data have fed a long-term interest in physics models and in ways to test these as expeditiously as possible.

176

MICHAEL D. SOKOLOFF received his bachelor’s degree in physics from the University of Maryland, and his Ph.D. degree in experimental particle physics from the University of California, Berkeley, in 1974 and 1983, respectively. His use of computers in physics research dates back to 1972 when he began programming a PDP-8/L minicomputer (as they were called) to acquire and analyze real-time data from a balloon-borne experiment designed to measure cosmic rays at the top of the atmosphere. His current research interests include accelerator-based experimental high-energy physics where computationally intensive applications are critical elements of data acquisition as well as offline data reconstruction and analysis.

KAREN A. TOMKO is a Research Scientist with the Ohio Supercomputer Center, and has been collaborating with computational scientists for 20 years. Her experience ranges from optimization and parallelization of crashworthiness simulations to the development of a run-time adaptive parallelization scheme for wireless communications simulations. Current collaborations focus on Communication Runtimes, and Analysis Frameworks for High Energy Physics. She is interested in Computational Science Education and has taught distance courses on topics in high-performance computing. Dr. Tomko received her Ph.D. degree from the University of Michigan in 1995, and has previously held faculty positions with the University of Cincinnati and the Wright State University.

VOLUME 2, 2014

Implementation of a Thread-Parallel, GPU-Friendly Function ...

Mar 7, 2014 - using the method shown in Snippet 10. Here, obsIter is a ..... laptop, GooFit runs almost twice as quickly on a gamer GPU chip as it does using 8 ...

2MB Sizes 7 Downloads 271 Views

Recommend Documents

Implementation of Keccak hash function in Tree ...
opposition to host memory which resides on the host computer. The device global ... ing results of leaves in a serial way on the Host CPU. A leaf interleaving ...

A Hardware Implementation of POET -
and LT , dedicated registers have been con- sidered to store the corresponding values. The design supports only the message blocks with a size of a factor of 128 bits (16 bytes). The user can set the secret key SK, with which the other constants are

Study of the Transfer Function of a Thermocouple
in the concerned temperature range, while the electric resistivity is described by a linear function with respect to the temperature. We introduce the following ...

Resistance of copper wire as a function of temperature.pdf ...
Resistance of copper wire as a function of temperature.pdf. Resistance of copper wire as a function of temperature.pdf. Open. Extract. Open with. Sign In.

Correlation of Diffuse Emissions as a Function of ...
Feb 17, 2014 - This memo came about to investigate the non-zero phase shift seen in correlations from two co- linear dipoles spaced very close together, where a zero phase shift was expected. The formulation expands on that of LWA Memo 142 [1], and m

IMPLEMENTATION OF MIS Implementation of MIS ... -
space occupied by computers, terminals, printers, etc., as also by people and their movement. ... These classes are not necessarily exclusive, as they quite often.

Structure and function of mucosal immun function ...
beneath the epithelium directly in contact with M cells. Dendritic cells in ... genitourinary tract, and the breast during lactation (common mucosal immune system).

A distributed implementation using apache spark of a genetic ...
Oct 10, 2017 - This paper presents a distributed implementation for a genetic algorithm, using Apache Spark, a fast and popular data processing framework. Our approach is rather general, but in this paper the parallelized genetic algorithm is used fo

A Unifying Theory of Biological Function
Sep 6, 2016 - Johann Bernoulli Institute for Mathematics and Computer Science, University of Groningen, ... intuition on a previously published list with intuitions about .... The basic R loop produces evolution by differential reproduction (i.e., by

A Production Function of Economic Regulation: The ... - Dror Goldberg
hope that others will accept this paper from them. Second, this regulation criminalizes passive behavior. Third, it was often invented to support a ..... The all-important Tel Aviv metropolitan area lied in a ten-miles strip between the sea and. Jord

A Production Function of Economic Regulation: The ... - Dror Goldberg
that regulation) or supply (of that regulation). Formally, let D=1 if there ..... They threatened the business elite with collective deportations ... thrown out wholesale.

The differential Hilbert function of a differential rational ...
order indeterminates (its symbol) has full rank, the sys- tem (1) can be locally .... bra software packages, based on rewriting techniques. This is the reason why our ...... some Jacobian matrices by means of division-free slp. For this purpose, we .

A Generalization of the Rate-Distortion Function for ...
side information Y and independent Gaussian noise, while the source data X must be the ... However, the reduction of the general noisy WZ problem requires.

Google XRay: A Function Call Tracing System
Apr 5, 2016 - XRay enables efficient function call entry/exit .... functions to enable function call logging. 3. When tracing is explicitly turned off, we do either ...

Structure-function relationship underlying calculation-a combined ...
Structure-function relationship underlying calculation-a combined diffusion tensor imaging and fMRI study.pdf. Structure-function relationship underlying ...

Breaking Off Engagement: Readers' Disengagement as a Function of ...
The Institute for Intelligent Systems. Memphis Tennessee, USA. 1 901-678-2000 [email protected]. 2University of Waterloo. Department of Psychology.

Amphibian community structure as a function of forest ...
SIMPER, and we used PC-ORD version 5.0 (MjM Software,. Gleneden Beach) for the indicator species analysis. We used a Mantel test to evaluate the ...

2.4. Average Value of a Function (Mean Value Theorem) 2.4.1 ...
f(x)dx . 2.4.2. The Mean Value Theorem for Integrals. If f is con- tinuous on [a, b], then there exists a number c in [a, b] such that f(c) = fave = 1 b − a. ∫ b a f(x)dx ,.

Equilibrium Distribution Function of a Relativistic Dilute ...
Sep 14, 2007 - distribution and proposed an alternative equilibrium distribution for special .... where µ is the chemical potential and ε(pRe ) is the energy of the ...

C1-L6 - Rational Functions - Reciprocal of a Linear Function - Note ...
Page 2 of 2. C1-L6 - Rational Functions - Reciprocal of a Linear Function - Note filled in.pdf. C1-L6 - Rational Functions - Reciprocal of a Linear Function - Note filled in.pdf. Open. Extract. Open with. Sign In. Main menu. Displaying C1-L6 - Ration

Equilibrium Distribution Function of a Relativistic Dilute ...
Sep 14, 2007 - to work outside of a quantum field theoretical framework and retains a single universal time ..... Cambridge University Press, Cam- bridge, 1982.