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>
...
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).