Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-10-29 07:58:05

0001 // Copyright 2022, David Lawrence
0002 // Subject to the terms in the LICENSE file found in the top-level directory.
0003 //
0004 //
0005 // This is a JANA event source that uses PODIO to read from a ROOT
0006 // file created using the EDM4hep Data Model.
0007 
0008 #include "JEventSourcePODIO.h"
0009 
0010 #include <JANA/JApplication.h>
0011 #include <JANA/JEvent.h>
0012 #include <JANA/JException.h>
0013 #include <JANA/Utils/JTypeInfo.h>
0014 #include <TFile.h>
0015 #include <TObject.h>
0016 #include <fmt/core.h>
0017 #include <fmt/format.h>
0018 #include <fmt/ostream.h>
0019 #include <podio/CollectionBase.h>
0020 #include <podio/Frame.h>
0021 #include <podio/podioVersion.h>
0022 #include <algorithm>
0023 #include <exception>
0024 #include <iostream>
0025 #include <map>
0026 #include <memory>
0027 #include <sstream>
0028 #include <utility>
0029 #include <vector>
0030 
0031 // These files are generated automatically by make_datamodel_glue.py
0032 #include "services/io/podio/datamodel_glue.h"
0033 #include "services/io/podio/datamodel_includes.h" // IWYU pragma: keep
0034 #include "services/log/Log_service.h"
0035 
0036 // Formatter for podio::version::Version
0037 template <> struct fmt::formatter<podio::version::Version> : ostream_formatter {};
0038 
0039 //------------------------------------------------------------------------------
0040 // InsertingVisitor
0041 //
0042 /// This datamodel visitor will insert a PODIO collection into a JEvent.
0043 /// This allows us to access the PODIO data through JEvent::Get and JEvent::GetCollection.
0044 /// This makes it transparent to downstream factories whether the data was loaded from file, or calculated.
0045 /// InsertingVisitor is called in GetEvent()
0046 ///
0047 /// \param event             JANA JEvent to copy the data objects into
0048 /// \param collection_name   name of the collection which will be used as the factory tag for these objects
0049 //------------------------------------------------------------------------------
0050 struct InsertingVisitor {
0051   // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members): Lifetime of referenced objects is guaranteed beyond visitor lifetime in this pattern
0052   JEvent& m_event;
0053   const std::string& m_collection_name;
0054   // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members)
0055 
0056   InsertingVisitor(JEvent& event, const std::string& collection_name)
0057       : m_event(event), m_collection_name(collection_name) {};
0058 
0059   template <typename T> void operator()(const T& collection) {
0060 
0061     using ContentsT = decltype(collection[0]);
0062     m_event.InsertCollectionAlreadyInFrame<ContentsT>(&collection, m_collection_name);
0063   }
0064 };
0065 
0066 //------------------------------------------------------------------------------
0067 // Constructor
0068 //
0069 ///
0070 /// \param resource_name  Name of root file to open (n.b. file is not opened until Open() is called)
0071 /// \param app            JApplication
0072 //------------------------------------------------------------------------------
0073 JEventSourcePODIO::JEventSourcePODIO(std::string resource_name, JApplication* app)
0074     : JEventSource(resource_name, app) {
0075   SetTypeName(NAME_OF_THIS); // Provide JANA with class name
0076 #if JANA_NEW_CALLBACK_STYLE
0077   SetCallbackStyle(CallbackStyle::ExpertMode); // Use new, exception-free Emit() callback
0078 #endif
0079 
0080   // Get Logger
0081   m_log = GetApplication()->GetService<Log_service>()->logger("JEventSourcePODIO");
0082 
0083   // Tell JANA that we want it to call the FinishEvent() method.
0084   // EnableFinishEvent();
0085 
0086   // Allow user to specify to recycle events forever
0087   GetApplication()->SetDefaultParameter("podio:run_forever", m_run_forever,
0088                                         "set to true to recycle through events continuously");
0089 
0090   bool print_type_table = false;
0091   GetApplication()->SetDefaultParameter("podio:print_type_table", print_type_table,
0092                                         "Print list of collection names and their types");
0093 
0094   // Hopefully we won't need to reimplement background event merging. Using podio frames, it looks like we would
0095   // have to do a deep copy of all data in order to insert it into the same frame, which would probably be
0096   // quite inefficient.
0097   /*
0098     std::string background_filename;
0099     GetApplication()->SetDefaultParameter(
0100             "podio:background_filename",
0101             background_filename,
0102             "Name of file containing background events to merge in (default is not to merge any background)"
0103             );
0104 
0105     int num_background_events=1;
0106     GetApplication()->SetDefaultParameter(
0107             "podio:num_background_events",
0108             num_background_events,
0109             "Number of background events to add to every primary event."
0110     );
0111     */
0112 }
0113 
0114 //------------------------------------------------------------------------------
0115 // Destructor
0116 //------------------------------------------------------------------------------
0117 JEventSourcePODIO::~JEventSourcePODIO() {
0118   m_log->info("Closing Event Source for {}", GetResourceName());
0119 }
0120 
0121 //------------------------------------------------------------------------------
0122 // Open
0123 //
0124 /// Open the root file and read in metadata.
0125 //------------------------------------------------------------------------------
0126 void JEventSourcePODIO::Open() {
0127 
0128   bool print_type_table = GetApplication()->GetParameterValue<bool>("podio:print_type_table");
0129   // std::string background_filename = GetApplication()->GetParameterValue<std::string>("podio:background_filename");;
0130   // int num_background_events = GetApplication()->GetParameterValue<int>("podio:num_background_events");;
0131 
0132   // Open primary events file
0133   try {
0134 
0135     m_reader.openFile(GetResourceName());
0136 
0137     auto version          = m_reader.currentFileVersion();
0138     bool version_mismatch = version.major > podio::version::build_version.major;
0139     version_mismatch |= (version.major == podio::version::build_version.major) &&
0140                         (version.minor > podio::version::build_version.minor);
0141     if (version_mismatch) {
0142       std::stringstream ss;
0143       ss << "Mismatch in PODIO versions! " << version << " > " << podio::version::build_version;
0144       // FIXME: The podio ROOTReader is somehow failing to read in the correct version numbers from the file
0145       //            throw JException(ss.str());
0146     }
0147 
0148     m_log->info("PODIO version: file={} (executable={})", version, podio::version::build_version);
0149 
0150     Nevents_in_file = m_reader.getEntries("events");
0151     m_log->info("Opened PODIO Frame file \"{}\" with {} events", GetResourceName(),
0152                 Nevents_in_file);
0153 
0154     if (print_type_table) {
0155       PrintCollectionTypeTable();
0156     }
0157 
0158   } catch (std::exception& e) {
0159     m_log->error(e.what());
0160     throw JException(fmt::format("Problem opening file \"{}\"", GetResourceName()));
0161   }
0162 }
0163 
0164 //------------------------------------------------------------------------------
0165 // Close
0166 //
0167 /// Cleanly close the resource when JANA is terminated via Ctrl-C or jana:nevents
0168 ///
0169 /// \param event
0170 //------------------------------------------------------------------------------
0171 void JEventSourcePODIO::Close() {
0172   // m_reader.close();
0173   // TODO: ROOTReader does not appear to have a close() method.
0174 }
0175 
0176 //------------------------------------------------------------------------------
0177 // GetEvent
0178 //
0179 /// Read next event from file and copy its objects into the given JEvent.
0180 ///
0181 /// \param event
0182 //------------------------------------------------------------------------------
0183 #if JANA_NEW_CALLBACK_STYLE
0184 JEventSourcePODIO::Result JEventSourcePODIO::Emit(JEvent& event) {
0185 #else
0186 void JEventSourcePODIO::GetEvent(std::shared_ptr<JEvent> _event) {
0187   auto& event = *_event;
0188 #endif
0189 
0190   /// Calls to GetEvent are synchronized with each other, which means they can
0191   /// read and write state on the JEventSource without causing race conditions.
0192 
0193   // Check if we have exhausted events from file
0194   if (Nevents_read >= Nevents_in_file) {
0195     if (m_run_forever) {
0196       Nevents_read = 0;
0197     } else {
0198 #if JANA_NEW_CALLBACK_STYLE
0199       return Result::FailureFinished;
0200 #else
0201       throw RETURN_STATUS::kNO_MORE_EVENTS;
0202 #endif
0203     }
0204   }
0205 
0206   auto frame_data = m_reader.readEntry("events", Nevents_read);
0207   auto frame      = std::make_unique<podio::Frame>(std::move(frame_data));
0208 
0209   if (m_use_event_headers) {
0210     const auto& event_headers = frame->get<edm4hep::EventHeaderCollection>("EventHeader");
0211     if (event_headers.size() != 1) {
0212       m_log->warn("Missing or bad event headers: Entry {} contains {} items, but 1 expected. Will "
0213                   "not use event and run numbers from header",
0214                   Nevents_read, event_headers.size());
0215       m_use_event_headers = false;
0216     } else {
0217       event.SetEventNumber(event_headers[0].getEventNumber());
0218       event.SetRunNumber(event_headers[0].getRunNumber());
0219     }
0220   }
0221 
0222   // Insert contents odf frame into JFactories
0223   VisitPodioCollection<InsertingVisitor> visit;
0224   for (const std::string& coll_name : frame->getAvailableCollections()) {
0225     const podio::CollectionBase* collection = frame->get(coll_name);
0226     InsertingVisitor visitor(event, coll_name);
0227     visit(visitor, *collection);
0228   }
0229 
0230   event.Insert(frame.release()); // Transfer ownership from unique_ptr to JFactoryT<podio::Frame>
0231   Nevents_read += 1;
0232 #if JANA_NEW_CALLBACK_STYLE
0233   return Result::Success;
0234 #endif
0235 }
0236 
0237 //------------------------------------------------------------------------------
0238 // GetDescription
0239 //------------------------------------------------------------------------------
0240 std::string JEventSourcePODIO::GetDescription() {
0241 
0242   /// GetDescription() helps JANA explain to the user what is going on
0243   return "PODIO root file (Frames, podio >= v0.16.3)";
0244 }
0245 
0246 //------------------------------------------------------------------------------
0247 // CheckOpenable
0248 //
0249 /// Return a value from 0-1 indicating probability that this source will be
0250 /// able to read this root file. Currently, it simply checks that the file
0251 /// name contains the string ".root" and if does, returns a small number (0.02).
0252 /// This will need to be made more sophisticated if the alternative root file
0253 /// formats need to be supported by other event sources.
0254 ///
0255 /// \param resource_name name of root file to evaluate.
0256 /// \return              value from 0-1 indicating confidence that this source can open the given file
0257 //------------------------------------------------------------------------------
0258 template <>
0259 double JEventSourceGeneratorT<JEventSourcePODIO>::CheckOpenable(std::string resource_name) {
0260 
0261   // PODIO Frame reader gets slightly higher precedence than PODIO Legacy reader, but only if the file
0262   // contains a 'podio_metadata' TTree. If the file doesn't exist, this will return 0. The "file not found"
0263   // error will hopefully be generated by the PODIO legacy reader instead.
0264   if (resource_name.find(".root") == std::string::npos) {
0265     return 0.0;
0266   }
0267 
0268   // PODIO FrameReader segfaults on legacy input files, so we use ROOT to validate beforehand. Of course,
0269   // we can't validate if ROOT can't read the file.
0270   std::unique_ptr<TFile> file = std::unique_ptr<TFile>{TFile::Open(resource_name.c_str())};
0271   if (!file || file->IsZombie()) {
0272     return 0.0;
0273   }
0274 
0275   // We test the format the same way that PODIO's python API does. See python/podio/reading.py
0276   TObject* tree = file->Get("podio_metadata");
0277   if (tree == nullptr) {
0278     return 0.0;
0279   }
0280   return 0.03;
0281 }
0282 
0283 //------------------------------------------------------------------------------
0284 // PrintCollectionTypeTable
0285 //
0286 /// Print the list of collection names from the currently open file along
0287 /// with their types. This will be called automatically when the file is
0288 /// open if the PODIO:PRINT_TYPE_TABLE variable is set to a non-zero value
0289 //------------------------------------------------------------------------------
0290 void JEventSourcePODIO::PrintCollectionTypeTable() {
0291 
0292   // Read the zeroth entry. This assumes that m_reader has already been initialized with a valid filename
0293   auto frame_data = m_reader.readEntry("events", 0);
0294   auto frame      = std::make_unique<podio::Frame>(std::move(frame_data));
0295 
0296   std::map<std::string, std::string> collectionNames;
0297   std::size_t max_name_len = 0;
0298   std::size_t max_type_len = 0;
0299 
0300   // Record all (collection name, value type name) pairs
0301   // Record the maximum length of both strings so that we can print nicely aligned columns.
0302   for (const std::string& name : frame->getAvailableCollections()) {
0303     const podio::CollectionBase* coll = frame->get(name);
0304     const auto type                   = coll->getTypeName();
0305     max_name_len                      = std::max(max_name_len, name.length());
0306     max_type_len                      = std::max(max_type_len, type.length());
0307     collectionNames[name]             = std::string(type);
0308   }
0309 
0310   // Print table
0311   std::cout << std::endl;
0312   std::cout << "Available Collections" << std::endl;
0313   std::cout << std::endl;
0314   std::cout << "Collection Name"
0315             << std::string(max_name_len + 2 - std::string("Collection Name").length(), ' ')
0316             << "Data Type" << std::endl;
0317   std::cout << std::string(max_name_len, '-') << "  " << std::string(max_name_len, '-')
0318             << std::endl;
0319   for (auto& [name, type] : collectionNames) {
0320     std::cout << name + std::string(max_name_len + 2 - name.length(), ' ');
0321     std::cout << type << std::endl;
0322   }
0323   std::cout << std::endl;
0324 }