Skip to content

ObjectId and ContainerId

The ObjectId class template identifies a single object (electron, muon, jet, etc.) within the context of a columnar tool. There is a separate type for each object/container used (ElectronId, MuonId, JetId, etc.). Each ObjectId is guaranteed to refer to a valid object in that container (see OptObjectId if you also need a null value).

If you are in xAOD mode under the hood this is a pointer to an xAOD object (e.g. xAOD::Electron) that is guaranteed to be not a nullptr. It is also generally const-qualified, unless the object has the Mutable prefix (e.g. MutableElectronId), as most tools operate on const objects.

If you are in columnar mode under the hood this is the index of the object within each associated column. It also contains a pointer to the data area, which will be explained in the accessor section, and can for the most part be ignored.

In general you should pass ObjectId by value, not by reference. Internally it is either a pointer, or a pointer and an integer, or potentially just an integer (no implementation currently does this). All of these are more efficient to pass by value, and doing so allows the compiler to make certain assumptions when optimizing.

If you have a section of the tool that only needs to work in xAOD mode, you can get a reference to the underlying xAOD object via the member function getXAODObject. This function also exists in columnar mode, but will throw an exception. This allows to mix xAOD-only code with columnar code seamlessly. It can also be used (temporarily) during migrations to update one part of the tool at a time and keep it in a state that compiles throughout the migration.

ObjectId, Containers And Conversions

It is important to note that the ObjectId is linked to a specific container, not just a generic object type. So if you need three different track containers, you would have to use Track0Id, Track1Id, and Track2Id (with TrackId being the same as Track0Id). The underlying reason is that in columnar mode the ObjectId is really just an index of the object in the container. And that index is only valid for that container. So if you have multiple track containers you need to identify the right container through the type.

While that may sound very restrictive, it is often not so critical in practice. For starters a lot of tools only use a single container of each type, so they are not affected. And even if they need multiple containers of each type, they often know which container each id came from. If neither applies to you, see the section on variant ids, but it is preferred to stay within these constraints if possible.

Also, there is no conversion to base containers, e.g. a JetId doesn't convert to a ParticleId. While it may be possible to set something like that up, there is very little use for that in columnar code. Both types have the same member functions and allow doing the same things, so in most cases you could just use ParticleId throughout.

If you have a mutable ObjectID (e.g. MutableJetId) you can convert that to the const version though (i.e. JetId). In general you will need to use mutable objects a lot less than for xAOD based code, and you should avoid using them, unless you need them. There are some types of accessors that need them, and otherwise if you need a mutable object in an xAOD-only code path.

An important thing to note is that the container ids only have a meaning within the specific instance of the tool (and its subtools or subobjects): So you could have multiple instances of the same tool operating on different containers, and don't have to worry whether another tool uses the same ContainerId for another container. However, for any subtool your tool holds you need to make sure you use the same ContainerId consistently, including for e.g. linked track containers.

ContainerId

Internally types are identified via a traits struct that gives their properties. Currently they are named ContainerId::jet etc., though there is the idea of renaming them to JetDef etc. to bring them more in line with how other types are named.

The different ObjectId types are then defined as type aliases (with CM being the columnar mode):

using JetId = ObjectId<ContainerId::jet,CM>
using ElectronId = ObjectId<ContainerId::electron,CM>
...
Similar type aliases exist for other important container-dependent types in the infrastructure. The main reason for these aliases is that they shorten a lot of code considerably, particularly once you have multiple of them in a line and also include the columnar:: namespace qualifier.

Later on you'll also encounter cases in which a ContainerId can be something other than a "regular" traits struct, which allows using the same syntax with some more advanced concepts.

OptObjectId

As noted above, an ObjectId is generally guaranteed to point to a valid object. This avoids having to check on every access that the object is indeed valid, and it is also a very common situation that an object just has to be valid. However, in case an object may or may not be valid, there is also the OptObjectId template (with aliases OptJetId, OptElectronId, etc.).

The syntax of the class is generally modelled after the std::optional template, so you have a value(), has_value(), and operator*(). There is no operator->() as that would not be practical in this context. Also getXAODObject() is changed to return a pointer, which can also be nullptr if no value is present.

Event IDs

Just like there is an EventContext in AthenaMT to identify the current event, there is also an EventContextId in columnar. Unlike AthenaMT it is mandatory to pass it in where it's needed, there is no currentContext function. The underlying reason is that (unlike AthenaMT) in columnar mode each thread is given a batch of events to process at once, so the specific event can no longer be inferred from the thread id.

In general it is preferred to retrieve the needed objects early and pass them in, instead of passing EventContextId all the way through to the inner functions and retrieving them there. This is actually less about columnar, but in xAOD mode it is more efficient if the user retrieves e.g. EventInfo once, instead of the tool retrieving it once per call (or in some cases multiple times per call).