diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d12bcb..01de56c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ target_sources( TYPE HEADERS BASE_DIRS include FILES + include/SHiP/EventHeader.hpp include/SHiP/MCParticle.hpp include/SHiP/SimHit.hpp include/SHiP/SimParticle.hpp @@ -43,6 +44,7 @@ target_link_libraries(SHiPUnits INTERFACE mp-units::mp-units) root_generate_dictionary( G__SHiPDataModel + SHiP/EventHeader.hpp SHiP/MCParticle.hpp SHiP/SimHit.hpp SHiP/SimParticle.hpp diff --git a/README.md b/README.md index 4d2d6cd..36bb94b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ An [automatic class reference](https://shipsoft.github.io/data-model/) is built | Header | Class | Category | |--------|-------|----------| +| `SHiP/EventHeader.hpp` | `SHiP::EventHeader` | Event metadata | | `SHiP/MCParticle.hpp` | `SHiP::MCParticle` | MC / generation | | `SHiP/SimHit.hpp` | `SHiP::SimHit` | Simulation | | `SHiP/SimParticle.hpp` | `SHiP::SimParticle` | Simulation | diff --git a/include/SHiP/EventHeader.hpp b/include/SHiP/EventHeader.hpp new file mode 100644 index 0000000..404f110 --- /dev/null +++ b/include/SHiP/EventHeader.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace SHiP { + +/// Per-event metadata — one record per event +struct EventHeader { + double weight{1.0}; ///< Event weight (e.g. P_DIS / nReplicas) + std::int64_t parentEventId{-1}; ///< Originating event id, e.g. the muon + ///< event that seeded this replica (-1 = + ///< none). Event-level provenance, distinct + ///< from MCParticle::motherId which links + ///< particles within an event. +}; + +} // namespace SHiP diff --git a/include/SHiP/LinkDef.h b/include/SHiP/LinkDef.h index 33ad6b5..1efec48 100644 --- a/include/SHiP/LinkDef.h +++ b/include/SHiP/LinkDef.h @@ -3,6 +3,9 @@ #pragma link off all classes; #pragma link off all functions; +// Event metadata +#pragma link C++ class SHiP::EventHeader+; + // MC / generation #pragma link C++ class SHiP::MCParticle+; #pragma link C++ class std::vector+; diff --git a/tests/test_rntuple_io.cpp b/tests/test_rntuple_io.cpp index 5baf34d..1843330 100644 --- a/tests/test_rntuple_io.cpp +++ b/tests/test_rntuple_io.cpp @@ -11,6 +11,7 @@ #include "ROOT/RNTupleModel.hxx" #include "ROOT/RNTupleReader.hxx" #include "ROOT/RNTupleWriter.hxx" +#include "SHiP/EventHeader.hpp" #include "SHiP/MCParticle.hpp" #include "SHiP/RecParticle.hpp" #include "SHiP/SimHit.hpp" @@ -25,6 +26,7 @@ constexpr char const* kFileName = "test_rntuple_io_tmp.root"; bool writeFile() { auto model = ROOT::RNTupleModel::Create(); + auto eventHeader = model->MakeField("eventHeader"); auto mcParticles = model->MakeField>("mcParticles"); auto simHits = model->MakeField>("simHits"); @@ -41,6 +43,7 @@ bool writeFile() { return false; } for (int entry = 0; entry < kEntries; ++entry) { + *eventHeader = SHiP::test::makeEventHeader(entry); *mcParticles = SHiP::test::makeMCParticles(entry); *simHits = SHiP::test::makeSimHits(entry); *simParticles = SHiP::test::makeSimParticles(entry); @@ -64,6 +67,7 @@ bool readAndCompare() { } auto const& entry = reader->GetModel().GetDefaultEntry(); + auto eventHeader = entry.GetPtr("eventHeader"); auto mcParticles = entry.GetPtr>("mcParticles"); auto simHits = entry.GetPtr>("simHits"); auto simParticles = @@ -76,6 +80,8 @@ bool readAndCompare() { for (int i = 0; i < kEntries; ++i) { reader->LoadEntry(i); std::string const suffix = " (entry " + std::to_string(i) + ")"; + ok &= SHiP::test::check("EventHeader round-trip" + suffix, + SHiP::test::makeEventHeader(i), *eventHeader); ok &= SHiP::test::check("MCParticle round-trip" + suffix, SHiP::test::makeMCParticles(i), *mcParticles); ok &= SHiP::test::check("SimHit round-trip" + suffix, diff --git a/tests/test_ttree_io.cpp b/tests/test_ttree_io.cpp index 944bf86..6628656 100644 --- a/tests/test_ttree_io.cpp +++ b/tests/test_ttree_io.cpp @@ -11,6 +11,7 @@ #include #include +#include "SHiP/EventHeader.hpp" #include "SHiP/MCParticle.hpp" #include "SHiP/RecParticle.hpp" #include "SHiP/SimHit.hpp" @@ -26,10 +27,10 @@ constexpr char const* kFileName = "test_ttree_io_tmp.root"; bool checkDictionaries() { bool ok = true; for (auto const* name : - {"SHiP::MCParticle", "SHiP::SimHit", "SHiP::SimParticle", - "SHiP::SimResult", "SHiP::RecParticle", "std::vector", - "std::vector", "std::vector", - "std::vector"}) { + {"SHiP::EventHeader", "SHiP::MCParticle", "SHiP::SimHit", + "SHiP::SimParticle", "SHiP::SimResult", "SHiP::RecParticle", + "std::vector", "std::vector", + "std::vector", "std::vector"}) { if (TClass::GetClass(name) == nullptr) { std::cout << "dictionary for " << name << ": FAIL (TClass::GetClass is null)\n"; @@ -49,12 +50,14 @@ bool writeFile() { } TTree tree("events", "SHiP data model I/O test"); + SHiP::EventHeader eventHeader; std::vector mcParticles; std::vector simHits; std::vector simParticles; std::vector recParticles; SHiP::SimResult simResult; + tree.Branch("eventHeader", &eventHeader); tree.Branch("mcParticles", &mcParticles); tree.Branch("simHits", &simHits); tree.Branch("simParticles", &simParticles); @@ -62,6 +65,7 @@ bool writeFile() { tree.Branch("simResult", &simResult); for (int entry = 0; entry < kEntries; ++entry) { + eventHeader = SHiP::test::makeEventHeader(entry); mcParticles = SHiP::test::makeMCParticles(entry); simHits = SHiP::test::makeSimHits(entry); simParticles = SHiP::test::makeSimParticles(entry); @@ -90,12 +94,14 @@ bool readAndCompare() { return false; } + auto* eventHeader = new SHiP::EventHeader(); auto* mcParticles = new std::vector(); auto* simHits = new std::vector(); auto* simParticles = new std::vector(); auto* recParticles = new std::vector(); auto* simResult = new SHiP::SimResult(); + tree->SetBranchAddress("eventHeader", &eventHeader); tree->SetBranchAddress("mcParticles", &mcParticles); tree->SetBranchAddress("simHits", &simHits); tree->SetBranchAddress("simParticles", &simParticles); @@ -106,6 +112,8 @@ bool readAndCompare() { for (int entry = 0; entry < kEntries; ++entry) { tree->GetEntry(entry); std::string const suffix = " (entry " + std::to_string(entry) + ")"; + ok &= SHiP::test::check("EventHeader round-trip" + suffix, + SHiP::test::makeEventHeader(entry), *eventHeader); ok &= SHiP::test::check("MCParticle round-trip" + suffix, SHiP::test::makeMCParticles(entry), *mcParticles); ok &= SHiP::test::check("SimHit round-trip" + suffix, @@ -119,6 +127,7 @@ bool readAndCompare() { } tree->ResetBranchAddresses(); + delete eventHeader; delete mcParticles; delete simHits; delete simParticles; diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp index 4156151..2a96c01 100644 --- a/tests/test_utils.hpp +++ b/tests/test_utils.hpp @@ -5,6 +5,7 @@ #include #include +#include "SHiP/EventHeader.hpp" #include "SHiP/MCParticle.hpp" #include "SHiP/RecParticle.hpp" #include "SHiP/SimHit.hpp" @@ -13,6 +14,14 @@ namespace SHiP::test { +/// Build an EventHeader with distinctive non-default values in every member. +inline EventHeader makeEventHeader(int offset) { + EventHeader h; + h.weight = 0.125 + offset; + h.parentEventId = 7000 + offset; + return h; +} + /// Build MCParticles with distinctive non-default values in every member. inline std::vector makeMCParticles(int offset) { std::vector v; @@ -84,6 +93,10 @@ inline SimResult makeSimResult(int offset) { .particles = makeSimParticles(offset + 5)}; } +inline bool equal(EventHeader const& a, EventHeader const& b) { + return a.weight == b.weight && a.parentEventId == b.parentEventId; +} + inline bool equal(MCParticle const& a, MCParticle const& b) { return a.pdgCode == b.pdgCode && a.vertex == b.vertex && a.momentum == b.momentum && a.energy == b.energy && a.time == b.time &&