4 Details
Gordon McCann edited this page 2022-10-01 23:03:13 -04:00

Details of the Code

The goal of this page is to outline some of the details of the SESPS-SABRE event builder so that modifications or improvements can be made to the code. As such, the contents of this page are subject to change and may be inaccurate depending on the specific branch that has been cloned. It is also not intended to be comprehensive. Rather it is meant to highlight the important details of the program.

CompassFile

CompassFile is a essentially a wrapper around a std::shared_ptr<std::ifstream> which directly interfaces with the actual binary file. The class contains the shared pointer (it must be a shared pointer to allow for movable and copyable data) as well a buffer for the file. It interfaces with a single CoMPASS binary file. When the file is opened, the file is checked for its length, to ensure that a) the file is not empty and b) that the file has at least enough data to form a single CoMPASS hit. A CoMPASS hit varies in size depending on the options chosen in CoMPASS. To handle this, CoMPASS writes a header to each file indicating the type of data written. After asserting the file has a header, the header is read, and then the file is checked to make sure it contains at least one hit. These checks need to be made as there are conditions where CoMPASS saves either a completely empty file (bad) or a file which does not contain a full datum (annoying). The class fills the data buffer from the file, and then places the next hit into a CompassHit structure, which can be retrieved from the class. It also has a boolean flag which can be switched on/off to indicate if the currently selected hit has been used and the next hit should be retrieved from the buffer.

Most important for the performance of the code is the size of the data buffer. In an ideal world, one would buffer the entirety of the data file into active memory, as this would provide the best performance. In some experiments, this is actually possible. SPS only experiments tend to have small file sizes when the DAQ is properly optimized, so there is no issue with buffering data. But most SPS-SABRE experiments have a lot of data (and a lot of data files) so the buffer has to be restricted. For the SPS_SABRE_EventBuilder repository, CoMPASS file has a locked buffer size of 200,000 hits per file (~4.8 Megabytes per file). This is to account for full experiments where there are 147 files, equating to about a Gigabyte of data. In principle one could expand this value and get increased performance, but there are some caveats. Obviously, if one expands the amount of total buffered data (across all files) beyond the amount of available memory, there will be large performance penalties or the program could crash. So be sure to keep the total buffered data (buffer size in hits times hit size [24 bytes] times number of files) less than the amount of available memory. Additionally, expanding the buffer beyond the size of the file incurs no benefit. Therefore, expanding the buffer beyond the size of the largest data file in the run is pointless. If the buffer size needs modified, it can be found in src/evb/CompassRun.h and is stored as the variable m_bufsize. Note that if you modify this value it is recommended to rebuild the entire program from scratch. Also, functions exist for implementing the buffer size as an input in something like the input file, however, they currently are not used. In general, the size of SPS data fluctuates so little, that it hasn't been deemed useful.

CompassRun

CompassRun is the main location of event building in the program. It serves as a hub for the separate event building operations and the collection of CompassFiles for the active run. CompassRun is given a path to a directory and it iterates over the elements of that directory, generating either a CompassFile or passing the name along to the scaler analysis if it matches a name in the scaler list. Once the set of files has been created, CompassRun begins the event building operation of choice (fast, slow, etc.) by selecting the first hit in time from the collection of files. Once it identifies the earliest hit, it passes the hit along to the next stage, typically slow sorting. The CompassFile from which the hit was taken has it's flag flipped so that the next hit in the buffer is set to the active hit. This process continues until all files have been exhausted of data.

EVBApp

This is the application level class. It is the direct interface with the user input data, and properly generates and runs various CompassRun operations. If you want to add some new form of user input, it should be done through this class.

SlowSort, FastSort, SFPAnalyzer

These classes are the implementation of the method described in the Home page. They typically receive a single hit from the CompassRun parent, perform some timing calculations, and then fill up a std::vector type structure. Once the window is exhausted, a flag is flipped, indicating to CompassRun that the built event is ready and should be passed to either the next analysis stage or written to disk.

ChannelMap and its interaction with SlowSort

As it is currently implemented, we map CoMPASS channel data to detector related data structures using the ChannelMap class in conjuction with SlowSort. ChannelMap reads in a map file, which essentially provides a conversion between a global channel number and a number specifically associated with a detector component (a detector id). The allowed values are specified by an enum list defined in ChannelMap. SlowSort then has a secondary map which converts this detector id into a pointer to the correct memory location in the data structure. The reason for this slightly complicated scheme is that it provides a performance boost over the alternative of a large if statement over hardcoded values, and also it is more stable against modifications. It also does a better job of encapsulating the idea of the mapping as well. However, it is slightly convoluted, and in a better implementation SlowSort would probably not have a map as well; the entire mapping process should be encapsulated within the ChannelMap class. Consider this an area of active development.

SFPPlotter and CutHandler

SFPPlotter is the class responsible for handling a plot request. It takes in a ROOT file with a tree of analyzed events and generates a new file with histograms. It can also perform one layer of cuts. Cuts are methods for selecting specific data out of the entire set. This is done with ROOT's TCutG class. A list of ROOT files, each of which contains a TCutG object named "CUTG" (the default name), can be given to the plotter which then passes the list along to a CutHandler. The handler retrieves the cut, gives it the new name specified in the list file, and associates the names of the variables to be cut upon. A map of variable names to memory location is created at construction by the CutHandler. If the user wants to expand the possible cut variables, they will need to add a name to the map and specify the memory location in the data structure.

The plotter has two overloaded methods named MyFill. These essentially handle the creation and filling of ROOT histograms. Using these type of patterns greatly reduce the code overhead of using the ROOT histograms. In general these methods should be used rather than individually constructing, filling, and writing histograms. There is some cost to using the MyFill style, but in general it is greatly outweighed by the reduction in code and increase in readability of the code.