From bf061694c3d98053c97f7788d769a8fdfb4ea388 Mon Sep 17 00:00:00 2001 From: Matei Climescu Date: Fri, 17 Jul 2026 23:45:20 +0200 Subject: [PATCH] feat(data-model): add EventHeader for per-event weight and provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SHiP::EventHeader, a per-event metadata record carrying an event weight and the id of the originating event. The weight (default 1.0) supports weighted generation such as forced or oversampled muon DIS, where each replica of a source muon is written as its own event and down-weighted accordingly. parentEventId (default -1) records which source event a replica came from — event-level provenance, distinct from MCParticle::motherId, which links particles within an event. The struct is a single per-event record like SimResult, so it is registered in the ROOT dictionary without a std::vector counterpart, and added to both the installed header set and the dictionary inputs. RNTuple and TTree round-trip tests cover it, and the TTree dictionary-presence check now includes SHiP::EventHeader. Purely additive: existing classes and files are unchanged, so old files simply lack the new field. --- CMakeLists.txt | 2 ++ README.md | 1 + include/SHiP/EventHeader.hpp | 17 +++++++++++++++++ include/SHiP/LinkDef.h | 3 +++ tests/test_rntuple_io.cpp | 6 ++++++ tests/test_ttree_io.cpp | 17 +++++++++++++---- tests/test_utils.hpp | 13 +++++++++++++ 7 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 include/SHiP/EventHeader.hpp 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 &&