Creating a new xAOD output in EventLoop (optional/advanced)

Last update: 16 Aug 2024 [History] [Edit]

Here we will show you how to modify or create new xAOD containers/objects, and then how to write these to another xAOD using EventLoop.

warning Note that if you do it this way you will not be able to read the output xAOD back with POOL in Athena. We will make use of some methods in the xAODRootAccess package to create our new xAOD.

warning These instructions are only meant for EventLoop/AnalysisBase. With Athena/AthAnalysis you have to use the native Athena I/O system for writing mini-xAOD files.

IMPORTANT NOTE: If you are creating a new smaller xAOD file that is used by your analysis group, consider setting up a derivation in the Derivation Reduction Framework, which will automatically generate this smaller xAOD in the production system for you. See the Derivation Framework for more information.

tip Before you do this part of the tutorial, make sure that you worked through the section on modifying xAOD objects in its entirety. While it can be meaningful simply to copy input objects, you will almost always want to modify them in some way.

Creating a new xAOD output in EventLoop

First of all we need to set up a new “output stream” for writing the xAOD file. This happens in two separate steps.

  • Defining the output stream in the submission script;
  • Using the already existing stream in the algorithm to pass an output file to xAOD::TEvent.

Setting up the output stream

In your submission script you need to use EL::Job::outputAdd to define a new output stream. Assuming that your EL::Job object is called job (as it was shown in the EventLoop configuration section), you can declare the new output stream like the following in Python:

# Add an output stream called 'ANALYSIS'.
job.outputAdd( ROOT.EL.OutputStream( 'ANALYSIS' ) )

Writing the algorithm’s code

To make your algorithm the most robust, it’s a very good idea to set up a string property on it that allows setting the output stream name from the submission script. See the Using Properties section for more details on setting up such a property.

Assuming that you called the output stream’s name m_outputStreamName, you can connect the job’s xAOD::TEvent object to the output stream/file by putting the following into the initialize() function of your algorithm:

TFile* ofile = wk()->getOutputFile( m_outputStreamName );
if( ! ofile ) {
   // Handle the error...
}
ANA_CHECK( evtStore()->event()->writeTo( ofile ) );

tip If you have not done that yet, you may need to include "EventLoop/Worker.h" at this point to make this code compile successfully. That may also require adding EventLoop as a dependency in your CMakeLists.txt file.

Finally, in finalize() let’s tell the job to close up the output xAOD by adding:

// Finalize and close our output xAOD file.
TFile* ofile = wk()->getOutputFile( m_outputStreamName );
ANA_CHECK( evtStore()->event()->finishWritingTo( ofile ) );

With all of these set up, you are ready to start adding content to your output xAOD.

Copying full container(s) to a new xAOD

Here we will show you how to copy the contents of a full container, unmodified, for every event. We assume you have followed the instructions above to define a new output xAOD in EventLoop.

We will create this copy in the event loop, so in MyAnalysis/Root/MyxAODAnalysis.cxx in the execute() method add the following line to copy the full container for "AntiKt4EMTopoJets":

// copy full container(s) to new xAOD
// without modifying the contents of it: 
ANA_CHECK( evtStore()->event()->copy("AntiKt4EMTopoJets") );

At the end of execute() add this line to fill the xAOD with the content we have specified in the event loop:

// Save the event:
evtStore()->event()->fill();

Compile like usual and test your code as explained in the EventLoop configuration section.

If you have followed the instructions above you will find your output xAOD in submitDir/data-ANALYSIS/. (Assuming that you set "ANALYSIS" as the name of your output stream.)

Note that you can only copy xAOD objects and/or containers. You can determine if the container is of xAOD type by running checkxAOD.py on the xAOD and seeing which objects are of type xAOD::x (where x is the container of interest).

Shallow copy: record to output xAOD

There are two options here, dictated by how you set the flag setShallowIO:

  • Save a real shallow copy only writing to the xAOD container the variables you have overwritten while still pointing to the original for all other variables. In this case you must also write the original container (setShallowIO is true) as previously described.

  • Save an actually deep copy; in this case you do not need to also write the original container to the xAOD (setShallowIO is false).

For either option add these lines below our iterator over the shallow copied jets:

const xAOD::JetContainer *jets = nullptr;
ANA_CHECK (evtStore()->retrieve (jets, "AntiKt4EMTopoJets"));

std::pair< xAOD::JetContainer*, xAOD::ShallowAuxContainer* > jets_shallowCopy = xAOD::shallowCopyContainer( *jets );

xAOD::TEvent* event = wk()->xaodEvent();
  
// true = shallow copy, false = deep copy
// if true should have something like this line somewhere:
// event->copy("AntiKt4EMTopoJets");
jets_shallowCopy.second->setShallowIO( false );
ANA_CHECK (event->record (jets_shallowCopy.first, "ShallowCopiedJets"));
ANA_CHECK (event->record (jets_shallowCopy.second, "ShallowCopiedJetsAux."));

Shallow copy: record to TStore

You can record the shallow copy to TStore, in a very similar way we did above by storing it to TEvent. First in the source code in execute() you will need to define an instance of a TStore object (making use of the EventLoop worker object):

xAOD::TStore* store = wk()->xaodStore();

Then simply record your shallowed jet container (and aux container) to the store:

ANA_CHECK (store->record (jets_shallowCopy.first, "ShallowCopiedJets"));
ANA_CHECK (store->record (jets_shallowCopy.second, "ShallowCopiedJetsAux."));

EventLoop takes care of clearing the memory for you.

tip At any point you can see what is stored to your xAOD::TStore object by doing store->print().

Compile like usual and test your code. Depending on how you set setShallowIO you will have more or less variables in your new xAOD associated to the "ShallowCopiedJets" container. You can try changing the flag, recompiling and checking the alternative content of the xAOD.

Container slimming

As a final ingredient to writing out modified objects, you can select which of their properties should be written to the output file. The xAOD design was based around the idea that objects/containers may be slimmed during the analysis easily.

As you should know, all the data payload of xAOD objects/containers is in their auxiliary store objects. Because of this the way to specify which variables should be written out, is to set a property for the auxiliary store in question. This is done using the xAOD::TEvent::setAuxItemList function. By putting something like this into your algorithm’s initialize() function:

// Set which variables not to write out:
evtStore()->event()->setAuxItemList ("AntiKt4EMTopoJetsAux.",
                                     "-NumTrkPt1000.-NumTrkPt500");
// Set which variable to do write out:
evtStore()->event()->setAuxItemList ("GoodJetsAux.",
                                     "JetGhostArea.TrackCount");

Unfortunately at the time of writing this still has some issues when using multiple input files (code crashing on a few files into the job), but the formalism is going to be this once the code works as intended…