|
ACTS
Experiment-independent tracking
|
High-level Track Event Data Model.
Track information in ACTS can be divided into two parts: track-level information and track state-level information.
Track-level information are properties that relate to the full track. This includes the fitted track parameters with respect to some reference point, often the origin of the detector or the beamspot. It can also include summary information from the track finding stage, like the overall number of clusters that were used in the creation of the track, or the fit quality from the track fit.
Tracks are built-up from track states, where each track state corresponds to a discrete state determining the track properties. This mainly includes measurements states, expected intersections with sensors where no measurement was found (holes), and intersections with known passive material. The EDM allows building up a track from these track states iteratively. For example, the Kalman Filter will append track states to the sequence whenever it encounters a sensitive detector layer. The content of the track states is defined such that the fitter can store all relevant information, with as little need for extra information as possible. It is also designed to be flexible enough to support different fitters, which might require different information to be stored, as well as the Combinatorial Kalman Filter, which produces a tree of track states, instead of a fully linear sequence.
Ultimately, each output track is associated with a well-defined sequence of track states, allowing downstream consumers of the EDM to access the fully detailed information produced during track reconstruction.
The Track EDM is structured such that the memory-layout can be SoA, while presenting an object-oriented interface for convenient usage.
The image below shows this object-oriented access model for the example of the track container and track proxy object. The track container holds vectors of the various pieces of information, and has methods to add a track, and to allow iteration over all tracks. This iteration, or index based access, yields a track proxy object, which exposes the properties as methods returning references, while internally only holding a pointer to and an index into the track container. The types are built in a way that preserves const-correctness, i.e. even though a track proxy is a value type which can be copied, it will not allow modification of the underlying track container if it is immutable:

The track EDM is fully agnostic to the concrete persistency framework of an experiment. This avoids having to convert the data between different representations, if implemented correctly.
To make the EDM implementation independent of an experiment persistency framework, it is separated into a frontend layer and a backend layer. The frontend layer contains user-facing getters and setters, as well as convenience methods that can be helpful. These methods are located either in the proxy objects or in the containers, depending on whether they operate on a single element or the entire container.
Overall, there are four main classes that make up the frontend layer: Acts::TrackProxy, Acts::TrackContainer, Acts::TrackStateProxy and Acts::MultiTrajectory. The latter serves as the track state container, where the name indicates that it is able to handle a branching tree structure of track states. TrackProxy and TrackStateProxy expose methods to get the local track parameters and covariance, corresponding reference surface, and also includes global statistics like the total number of measurements, outliers or holes in case of TrackProxy. TrackProxy also has a method to conveniently iterate over the associated track states from the last track state to the first one yielding TrackStateProxy objects from the track state container. In the common case of a track from the center of a cylindrical detector going outward, the default iteration order is from the outside inwards.
In case of TrackStateProxy, functionality is exposed in the frontend layer to allocate optional components, with the goal of reduced memory footprint. There are two main use cases of this: track parameters and measurements. The track-state EDM supports storing up to three sets of local track parameters and covariance matrices, modeled after the information the Kalman Filter formalism needs to store:
In case of combinatorial track finding (see Track Finding), specifically Acts::CombinatorialKalmanFilter, track hypothesis can start out with a common sequence of track states, and then branch out when multiple compatible measurements are encountered, as seen in the track state picture.
The track state EDM allows allocating only the track parameters that are needed, and also allows sharing the same track parameters between multiple track states, so that branching track states can share for example the same predicted parameters. How this is achieved exactly is left to the backend layer. Measurements are handled in a similar way, where the track finding decides how much storage is needed based on the number of dimensions of an incoming measurement. It then instructs the EDM through the frontend layer to ensure enough memory is available, where the specifics are again left up to the backend layer.
The backend layer exposes an interface that is used by the frontend layer to store and retrieve information. It uses dedicated methods where needed, such as for storing reference surfaces or source-link objects, which are lightweight container objects for experiment-specific measurements. For the majority of components, the frontend communicates with the backend through a single method to obtain references to the underlying data. Components are accessed via hashes of the component name, where the hashes are calculated at compile-time wherever possible. The backend can then use the hashed component name to retrieve the relevant memory. To allow directly manipulating the backing memory, the frontend expects the backend to return references into the backing storage.
TrackProxy provides a method to copy a track between different track containers, and only uses the frontend layer to accomplish this. This means that copying tracks between different backend implementations is trivial.
The picture above shows a diagram of the EDM architecture. At the center are the TrackProxy and TrackContainer. These classes are produced by the track finding and track fitting components, and are the main interface point with the clients of tracking. In ACTS itself, all of the performance monitoring and downstream reconstruction is either directly built on top of these objects, or converts them into an internal EDM on the use case. Behind the backend interface, the track container coordinates with both a track state and a track backend, where a few examples are shown, and will be discussed below.
By default, track states are connected as a one-directional linked list, where each track state knows its previous track state. The picture below shows an example of a track state tree, like it is constructed by the combinatorial track finding.
In the picture states \(S_7\) and \(S_6\) are the two tip states of the track state tree, while \(S_1\) is the single stem state. In the case of combinatorial track finding starting from e.g. a seed, it could be the location of the innermost space point.
Each track object points at a single tip state to define its track state sequence. The Acts::TrackProxy class has various methods to access the track state sequence:
Note that Acts::TrackProxy::trackStatesReversed iterates from the tip state to the stem state, i.e. from the outside in.
The reason for this is: As the trajectory branches at the second sensor into \(S_2\)/ \(S_3\), it is not possible to connect the states forward, i.e. store in \(S_1\) what the next state is going to be: it is ambiguous!
However, when track finding has concluded, and the trajectories identified by tip states \(S_7\) and \(S_8\) have been discarded or are being copied into an output container, it is possible to forward link the track state sequences. This is possible if the trajectory does not branch anymore! Acts::TrackProxy::copyFrom will implicitly forward link the track states, as it is guaranteed to not branch after copying.
You can manually add forward-linking to a track by calling Acts::TrackProxy::linkForward or Acts::TrackProxy::reverseTrackStates.
In this example, before any forward linking, the sequence looks like this:
After a copy operation of \(S_6\) and \(S_7\) the resulting track state sequences will look like this:
This now includes both forward and backward links, which allows iteration from \(S_1\)/ \(S_2\) to \(S_6\)/ \(S_7\) and the other way around.
Forward iteration can then be achieved like this:
and the innermost track state becomes directly accessible via Acts::TrackProxy::innermostTrackState.
Acts::MultiTrajectory is designed so that components can be shared between track states. This can be achieved using the Acts::TrackStateProxy::shareFrom can be used to set this up.
Shareable components are
To illustrate why this can be useful, consider again the track state picture, where \(S_2\) and \(S_3\) branch out from a shared \(S_1\). In this case, the predicted parameter vector and covariance, as well as the jacobian from \(S_1\to S_2\) and \(S_1 \to S_3\) will be identical. In this case, the combinatorial track finding will use the sharing functionality to share these components.
Aside from the static properties that both the track states and the track have, the EDM supports adding almost arbitrary additional information as dynamic columns. The implementation of the dynamic column mechanism is given by the backend, where the interface layer classes Acts::MultiTrajectory and Acts::TrackContainer and associated proxies only coordinate the creation, access and copying of dynamic columns.
The following illustrates the usage of dynamic columns for Acts::TrackContainer, but usage on Acts::MultiTrajectory is identical.
Assume you create a track container using some combination of backends (see Track EDM backends for information on the backends shipped with ACTS).
Adding columns is only supported on mutable track containers, const track containers should contain the original dynamic columns from when they were created. It is up to the backend to implement recovering dynamic columns from e.g. input files.
With these dynamic columns registered, it is now possible to set and get values for these columns on tracks.
The components are accessed by a hash of the name of the component. This hash can be calculated from a string at compile-time, if the string is known at compile time. The difference between the two component access signatures is that in the first case, the hash of the component is guaranteed to be evaluated at compile-time, since it is given to the component function as a template argument. A third option is available to access components: see Accessors.
It can be inconvenient to have to write the full component access signature, especially if you want to access the same components repeatedly. An alternative are accessors. They encapsulate the type of the component, and the component name hash into an object:
The above accessor is a mutable accessor, meaning it can only be used with mutable proxy objects!
To access properties on const proxy objects, you need to use a dedicated accessor type:
For both const and mutable proxy accessors you do not actually need a mutable reference, as the internal accessor state is not mutated after construction. You can safely use a static instance of these accessors to avoid constructing them over and over again:
The Acts::TrackContainer implements a mechanism to optionally own the backends that it is constructed with. This is implemented using a holder type, which is passed either as a template parameter, or deduced automatically.
Available default holders are:
Other user-specified holders can also be used, for example, it is possible to use std::shared_ptr as a holder directly, like:
Tracks can be created directly from the EDM interface. You start by creating or obtaining a mutable track container:
A single track can be added like this:
The track is still lacking track states. You can append track states to the track, which means that a track state is attached behind the outermost track state currently assigned.
Note that this means that you have to create track state from the inside out! If you have to add track states from the outside in, you can still append them and reverse the track at the very end.
The transient vector backend implements the reference backend for the track EDM. It does not implement any persistency directly. The implementation of this backend for both track and track state containers uses separate classes for the mutable and const versions, in order to fully comply with const correctness. It also uses a common base class internally, which is however an implementation detail.
To build a track container with this backend, you can write
or
The PODIO track EDM backend shipped with the library uses a custom PODIO-EDM defined in edm.yml in the ACTS core repository.
The working model is this:
Mutable PODIO track and track state backends are created with a helper
A track container is built using these PODIO backend instances
The track container is used by some algorithm
The mutable backends released into a podio::Frame for writing.
When reading, const track state and track PODIO backends are created from a podio::Frame
PODIO cannot directly store Acts::Surface and Acts::SourceLink instances. The PODIO backends rely on a helper class that implements the following ActsPlugins::PodioUtil::ConversionHelper interface.
Specifically, the PODIO backends will, before persisting and after reading, consult the helper object to convert between an in-memory Acts::Surface and an optional identifier. The identifier a 64-bit integer whose interpretation can depend on the experiment, it could be a sensor index (hash in ATLAS) for example.
If no identifier is returned, that means the surface is not expressible as an identifier, and it needs to be persisted directly, which is implemented centrally. In that case, the defining parameters of the surface are saved, and the object is rebuilt when reading. An example of this is in the case of a reference surface like a perigee surface that represents the beam axis, which is not commonly identified in the experiment geometry.
A similar mechanism is used for source links. Remember that Acts::SourceLink is type-erased proxy object that stands for an experiment-specific uncalibrated measurement. As such, the PODIO backend cannot directly store these. For use with the PODIO backends, source links need to be convertible to and from 64-bit integers, with the help of the helper object, that can communicate with the experiment infrastructure.
Both track and track state backends need to conform to respective concepts. Effectively, the backend has to allow for collection functionality, like getting the current size etc.
Further, the backend needs to respond to queries for properties of the elements, where the element is identified by an index.
The backend needs to flag itself as mutable or const, by specializing
This informs the interface layer to permit or prevent mutable access.
Common between both track and track state backends is the component access. Here, the component is identified by a compile-time string hash of the component name, which the backend responds to by a type-erased mutable or const pointer. There is a hard requirement for the backend to return stable pointers here, as the interface layer expects this and exposes this in the API.
This can be accomplished like in the transient backend with a switch-statements like:
The default statement deals with Dynamic columns. For support of dynamic columns, the backend needs to implement hasColumn_impl to return if a column exists, mutable backends need to implement addColumn_impl which adds a column with a type and a string name. copyDynamicFrom_impl and ensureDynamicColumn_impl on mutable backends allows copying dynamic content between container backends. dynamicKeys_impl returns an iterator pair that informs the caller of which dynamic keys are registered. This is also used in dynamic column copies.
Both backend types return parameter vectors and covariance matrices. This is always done as Eigen maps, which have reference semantics into the backing storage.
The track state container can share components. This is implemented for the shareable components by the interface layer looking up a component that stores an index into a separate shareable-component-container. Then, functions exists which return Eigen maps into the relevant backend storage based on the index looked up as a component before.
The track state container also handles calibrated measurement in a packed way. This is to say, that for $N$ dimensional measurement, the backend can store only the dimension $N$ and then $N$ numbers representing the measurement vector and \(N\times N\) numbers for the associated covariance matrix. To enable this, a mutable backend exposes allocateCalibrated_impl which accepts a track state index and the measurement size, and ensures storage is available to hand out a reference into associated backing storage. An example is the transient vector backend, which stores the offset into measurement and covariance matrix vector, and ensures its size is consistent.
In both cases above, the absence of the value can be indicated by setting the indices to the sentinel value kInvalid. The backend also has query methods that test if the index is invalid and return a boolean.
The track state container also stores SourceLinks, which are assigned by value and returned by value, which allows the backend to unpack assigned source links and repackage uncalibrated measurement references back into a source link when returning.
Classes | |
| class | Acts::ConstVectorMultiTrajectory |
| Const version of VectorMultiTrajectory. More... | |
| class | Acts::VectorMultiTrajectory |
In-memory transient multi-trajectory implementation using std::vector as backend. More... | |