Skip to content

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;
However, the columnar code also allows the inverted syntax:
  pt = electron(ptAcc);
  electron(ptOutDec) = pt * 1.05;
The original reason for supporting the second syntax is that if you switch from member functions to accessors this is a lot more convenient, as it avoids switching the order for 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"}};
The second also demonstrates how you pass optional parameters into an accessor. See more on options below.

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"}};
In some cases you will have to use this form because there is no alias. Otherwise it is mostly a stylistic choice. Personally I prefer the aliased version, as it is substantially shorter (particularly if you include the 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;
This gives you an accessor that if used will fail. You can then in 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);
which is a short-hand for this:
customAcc = ElectronAccessor<float> (customAcc, *this, variableName);
The 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}};
This signals to the columnar framework that if the column doesn't exist the tool can still be called. For regular columns, if a column can't be found the code will fail before the tool is even called.

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"}};
The first accessor accesses the incoming column, while the second allows you to write into the new column. The 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"};
This will then leave it to the user to first copy the pt column over to a new (mutable) column and then pass that in. It is not yet fully understood what the drawbacks of this mechanism are, so the above is preferred. However, if this is the functionality you need, go for it. Note that you may have to declare the relevant type alias yourself (or use the generic 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.