Column Accessors¶
The column accessors are the basic way to access per-object data in columnar code. Unlike xAOD objects which have a lot of member functions to access data the columnar objects don't provide such helpers and all accessors have to be declared in the tool. They also provide the needed data dependencies in columnar mode.
In xAOD mode the (simple) column accessors are essentially just wrappers
around SG::ConstAccessor or SG::Decorator. In columnar mode the
(simple) column accessors essentially access the associated column and
then access up the right element based on the index of the object.
For simple POD types (int, float, etc.) the accessors just directly
access and return a (const) reference to the underlying memory. This
allows the same syntax as xAOD accessors:
pt = ptAcc(electron);
ptOutDec(electron) = pt * 1.05;
pt = electron(ptAcc);
electron(ptOutDec) = pt * 1.05;
electron and pt. That may sound
trivial, but for tool migrations you have to do a lot of these. It also
makes it harder to make mistakes, and easier to spot them via git
diff.
There are also overloads for a lot of non-basic types (e.g.
vector<...> or ElementLink<...>), which will be described on
subsequent pages. While they are generally straightforward to use, they
require some extra explanation, as they need to do different things
under the hood for different modes.
There are also accessor-like classes that provide code that helps to
unpack xAOD data, e.g. to read etaBE2 from a calorimeter cluster.
These can range from a single line, to dozens of lines of code. Where it
is practical this code is shared with the xAOD accessor functions.
Like ObjectId the accessor is also specific to the container it works
on, so there is an ElectronAccessor, JetAccessor, etc. This makes
sure the accessor is only used for the the objects it's defined for, and
that the correct data dependencies are reported for the tool. There are
also separate accessors and decorators for each (like for xAOD accessors).
For various reasons the accessors need to be connected to the tool that uses them, making them in some regards more similar to StoreGate data handles than xAOD accessors. In practice that means an accessor declaration may look something like this:
ElectronAccessor<float> ptAcc {*this, "pt"};
ElectronDecorator<float> ptDec {*this, "ptOut", {.replacesColumn = "pt"}};
There is also a generic accessor template that can be used if you want
to explicitly pass in the ContainerId:
ColumnAccessor<ContainerId::electron,float> ptAcc {*this, "pt"};
ColumnDecorator<ContainerId::electron,float> ptDec {*this, "ptOut", {.replacesColumn = "pt"}};
columnar:: prefix), but in the end it is just a question
of style.
Since the column accessors are used to declare data dependencies it is imperative only to declare accessors that you will actually use. For every accessor declared, the corresponding data has to be read from disk and passed into the tool. It may even lead to some variables being kept in PHYSLITE and other data formats, even if they are not used.
Configurable Column Accessors¶
In many cases you may want to configure dynamically which columns your tool accesses. To that end, you can start out with an empty accessor:
ElectronAccessor<float> customAcc;
initialize assign it to a specific column. You can also reassign or
reset an already existing column accessor.
If you want to reset this accessor to a specific variable, you can do it like this:
resetAccessor (customAcc, *this, variableName);
customAcc = ElectronAccessor<float> (customAcc, *this, variableName);
resetAccessor variant is preferred, as it is both shorter and
doesn't include the container name in the syntax.
There is (so far) no way to automatically connect a configurable accessor to a tool property, like for decoration data handles. So you will have to manually declare the properties and then manually update the accessors.
Important: You have to do this in initialize or at the very least
before the user calls your tools. Once the user calls your tool the list
of columns passed is fixed, and if you add an extra column it will
simply not be there. There is no diagnostic for this. This is different
from xAOD accessors which can in the middle of processing the 42nd event
can ask for another decoration to be read from file.
Note that for xAOD accessors there are no "empty" accessors, so instead
xAOD code wraps conditionally existing accessors in std::unique_ptr or
std::optional. While you can in principle also do this for columnar
accessors it is neither necessary, nor recommended. By using an empty
accessor that is later reset you then get the same syntax for using the
accessor, as well as a declaration that looks very similar.
Optional Columns¶
It is quite a common situation for a tool to use an input variable, if it is available in the input file (or from an upstream algorithm), and have some fallback behavior if it isn't. There are some mixed opinions on whether this is a good or bad idea, but it is common and supported.
To indicate that a column is optional, you need to explicitly declare it as such:
ElectronAccessor<float> ptAcc {*this, "customPt", {.isOptional = true}};
You are then required to first check whether the column exists, before you are allowed to access it:
if (ptAcc.isAvailable (electron))
pt = ptAcc (electron);
What happens if the column is not available will depend on the mode. In xAOD mode you'll get an exception for a missing column (as for missing defined columns). In columnar code you'll generally just get a segfault and need to use a debugger to determine where it happens. If needed more protected optional column accessors could be defined in the future.
Updating Columns¶
A lot of CP tools need to update columns from the input, with the archetypal example being a momentum correction tool that needs to update the pt value of the object.
The way this is handled for xAOD code is that before the tool is called a shallow copy of the input is made, and when the pt for the copy is updated, the backend will first make a copy of the column with exactly the same content, and then you overwrite the value in that copy.
In columnar environments we will typically have a non-mutable input column and a mutable output column, and it is then up to the user (or an external framework) to use the output column for pt for subsequent use.
The way we map that in tool code is with a pair of accessors:
ElectronAccessor<float> ptAcc {*this, "pt"};
ElectronDecorator<float> ptDec {*this, "ptOut", {.replacesColumn = "pt"}};
replacesColumn argument
indicates that it is meant to replace the given column in the input. In
xAOD mode they will simply point to the same decoration.
It is not guaranteed that both columns are the same, or that they are
different, or what the second column is initialized to. As such it is
undefined what you read from ptDec before you set it, and what you
read from ptAcc after writing to ptDec. This works well in the
common case that you read a pt value, calculate the correction, then
write the output at the end of your function.
The above doesn't work so well, if you have e.g. a series of corrections you apply within the same tool and need to repeatedly read and write the pt value. For these cases there is an "update" accessor:
MutableElectronUpdater<float> ptAcc {*this, "pt"};
ColumnUpdater).
Mutable Containers¶
Just because you have a mutable container does not mean you have to declare the accessor as being on a mutable container, except for maybe update accessors. These accessors do exist, but we may just remove them at some point should they prove unnecessary.