In many situations just reading xAOD objects from a file is not good enough, but you may want to modify the objects as well. You could imagine you might want to do some of the following:
deep copy: create a new container with the same variables as an existing container, but with only a subset of objects/events passing some selection criteria (example: create a new jet collection that only contains jets with a pt greater than 50 GeV)
shallow copy: create a light-weight container that only “updates” some variables from an original container to new values (example: apply some energy correction and override existing reconstructed energy)
adding new variables: add variables/attributes to objects (example: adding a variable that identifies the jet as a b-jet)
Each of these are described in more detail below.
Many of the CP tools take advantage of one of these features to either apply corrections to existing containers or copied containers, or to decorate objects with new information.
Deep copying will create new objects/containers that have all the attributes (aka variables) of the original container. This is useful when you are only interested in objects that pass certain criteria.
Let’s create a new jet container of jets passing a pt threshold.
First, you need to create the new jet container in your execute()
function. For this, remember what you heard about the xAOD EDM in the
tutorial. An xAOD container is always described by two objects. An
“interface container” that you interact with, and an auxiliary store
object that holds all the data. Now that we want to create a new
container, you need to create both of these objects.
The naming convention for the auxiliary stores is that if an interface
container is called xAOD::BlaContainer
, then its auxiliary store
type is called xAOD::BlaAuxContainer
, where Bla
is the object of
interest. So for jets will look like:
#include <xAODJet/JetContainer.h>
#include <xAODJet/JetAuxContainer.h>
However instead of using the JetAuxContainer
we will use the generic
AuxContainerBase
this is because if we were using a derivation as
input some of the original (so-called “auxiliary”) variables may have
been slimmed away (removed to make the container smaller), so if we
were to do a deep-copy of the full JetAuxContainer
then we would
make our container larger than necessary (by creating a bunch of
variables that were not even in the original input DxAOD). Instead
let’s add these lines instead (to MyxAODAnalysis.cxx
):
#include <xAODJet/JetContainer.h>
#include <xAODCore/AuxContainerBase.h>
...
execute() {
...
// Retrieve input jet container
const xAOD::JetContainer *jets = nullptr;
ANA_CHECK (evtStore()->retrieve (jets, "AntiKt4EMPFlowJets"));
// Create the new container and its auxiliary store.
auto goodJets = std::make_unique<xAOD::JetContainer>();
auto goodJetsAux = std::make_unique<xAOD::AuxContainerBase>();
goodJets->setStore (goodJetsAux.get()); //< Connect the two
...
for (auto jet : *jets) {
if (jet->pt() < 50e03) continue; // skip jets below 50 GeV
// Copy this jet to the output container:
xAOD::Jet* goodJet = new xAOD::Jet();
goodJets->push_back (goodJet); // jet acquires the goodJets auxstore
*goodJet = *jet; // copies auxdata from one auxstore to the other
}
...
// Record the objects into the event store
ANA_CHECK (evtStore()->record (goodJets.release(), "GoodJets"));
ANA_CHECK (evtStore()->record (goodJetsAux.release(), "GoodJetsAux."));
...
}
Take note that you can call the interface container whatever you want
(within reason, it should be an alphanumeric name), but the
accompanying auxiliary store object must always have the same name,
postfixed by Aux.
. So, for any Bla
interface container you would
record a BlaAux.
auxiliary container.
Also notice that we create the output objects on the heap. We use
std::make_unique
for that, which ensures that the object gets
deleted once we are done with it. Once we pass the objects over to
evtStore()->record()
the event store takes ownership, meaning we
need to release it.
To make this compile make sure you have updated your package
dependencies in MyAnalysis/CMakeLists.txt
to include:
LINK_LIBRARIES [...] xAODJet xAODCore
where [...]
is any other package dependencies you may already have
included.
Typically the object will then either be used by a subsequent algorithm, or by writing them to a file. However for the sake of this tutorial, let’s just read it back in the same algorithm and print out the pt to make sure it got copied:
Another way of making copies of containers is called the shallow copy. If you want to apply some sort of modification/calibration to all objects in an input container, but you don’t want to perform an object selection at the same time, then the best idea is to make a “shallow copy” of the input container. This is done with the help of xAOD::shallowCopyContainer. Note that you can not add/remove objects to/from a shallow copy container (but you can decorate it with new variables associated to each object - see decorations in the next section). However it’s absolutely possible to make deep copies of some selected shallow copies later on in your code, and put these deep copies into a container of selected objects.
This creates a copy which only overrides default values with new ones that you set, while keeping all other unaffected quantities unchanged from the original. The shallowCopyContainer will return a pair of xAOD objects (one for the interface and one for the auxiliary store).
Let’s create a shallow copy of the "AntiKt4EMPFlowJets"
and shift the
pt of all jets up by 5%. In our source code (in MyxAODAnalysis.cxx
)
lets add:
#include <xAODCore/ShallowCopy.h>
...
execute() {
...
const xAOD::JetContainer* jets = 0;
ANA_CHECK (evtStore()->retrieve (jets, "AntiKt4EMPFlowJets" ));
//--------------
// shallow copy
//--------------
// "jets" jet container already defined above
auto shallowCopy = xAOD::shallowCopyContainer (*jets);
std::unique_ptr<xAOD::JetContainer> shallowJets (shallowCopy.first);
std::unique_ptr<xAOD::ShallowAuxContainer> shallowAux (shallowCopy.second);
// iterate over the shallow copy
for (auto jetSC : *shallowJets) {
// apply a shift in pt, up by 5%
double newPt = jetSC->pt() * (1 + 0.05);
// from: https://gitlab.cern.ch/atlas/athena/tree/21.2/Event/xAOD/xAODJet/xAODJet/versions/Jet_v1.h
xAOD::JetFourMom_t newp4 (newPt, jetSC->eta(), jetSC->phi(), jetSC->m());
jetSC->setJetP4 (newp4); // we've overwritten the 4-momentum
} // end iterator over jet shallow copy
...
}
...
By default when you create a shallowCopyContainer
you take ownership
of the pairs (meaning you need to take care to delete the both). You
can give ownership to evtStore()
, which will then handle deletion for
you.
You might want to modify an object by adding a new attribute (or variable) to it’s container. There are two ways to do this, depending on the “const” state of the container you want to modify:
for const
objects (for example adding variables to containers
from the input xAOD) you will “decorate” the container using the
auxdecor
function
for nonconst
objects (for example possibly like a shallow or
deep copied container) you will add auxiliary data to this
container using the auxdata
function
Now we will show you a simple example of each below.
Recall we looped over the “AntiKt4EMPFlowJets” jet container
and simply wrote to screen the jet pt (pretty boring). Now let’s go back to that code
and decorate this (const) jet container by adding a variable called
mySignal
that just checks if the jet pt is greater than 40
GeV. Modify the loop over the jets so it looks like:
...
// loop over the jets in the container
for (auto jet : *jets) {
ANA_MSG_INFO ("execute(): jet pt = " << (jet->pt() * 0.001) << " GeV"); // just to print out something
if (jet->pt() > 40000) {
jet->auxdecor<int>("mySignal") = 1; // 1 = yes, it's a signal jet!
} else {
jet->auxdecor<int>("mySignal") = 0; // 0 = nope, not a signal jet
}
} // end for loop over jets
...
Here we will add a variable to the (nonconst) jet container we did the shallow copy of above. In the loop over the shallow copied jets, after we have shifted the pt up by 5% you can add the following line:
...
// iterate over the shallow copy
for (auto jetSC : *shallowJets) {
// adding a (silly) variable: checking if shallow copied pt is greater than 40 GeV, after 5% shift up (classify as signal or not)
if (jetSC->pt() > 40000) {
jetSC->auxdata<int>("mySignal") = 1; // 1 = yes, it's a signal jet!
} else {
jetSC->auxdata<int>("mySignal") = 0; // 0 = nope, not a signal jet
}
} // end iterator over jet shallow copy
...
Here we have added an integer variable called mySignal
to the
shallow copied jets.
The final container type available in the ATLAS EDM is ConstDataVector. It is a transient only type (so you can’t write it to an output file) that allows one to create a selected list of objects, and make them appear as a “normal” container of that object type.
For instance to create a list of selected muons from a muon container, you can do the following:
#include <AthContainers/ConstDataVector.h>
...
const xAOD::MuonContainer *allMuons = nullptr;
ANA_CHECK (evtStore()->retrieve (allMuons, "Muons"));
auto selectedMuons = std::make_unique<ConstDataVector<xAOD::MuonContainer>> (SG::VIEW_ELEMENTS);
for (const xAOD::Muon *mu : *allMuons) {
if (mu->pt() > 30000) {
selectedMuons->push_back (mu);
}
}
ANA_CHECK (evtStore()->record( selectedMuons.release(), "SelectedMuons"));
Notice that:
xAOD::MuonContainer
for this, as that type doesn’t allow constant pointers to be added to it;SG::VIEW_ELEMENTS
to tell the object that it doesn’t own its elements. For analysis purposes you should only ever need to construct ConstDataVector
containers in this mode.ConstDataVector
.A container of type ConstDataVector
can be retrieved as a “normal” container in downstream code. Allowing another class/function to write:
const xAOD::MuonContainer *selectedMuons = nullptr;
ANA_CHECK (evtStore()->retrieve (selectionMuon, "SelectedMuons"));
auxdecor
.