trout
Phlex-based reconstruction framework for the SHiP experiment.
Loading...
Searching...
No Matches
typed_rntuple_writer.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 CERN for the benefit of the SHiP Collaboration
2//
3// SPDX-License-Identifier: LGPL-3.0-or-later
4
5// typed_rntuple_writer.hpp
6//
7// Utility functions for writing types into an RNtuple
8
9#pragma once
10
11#include "TFile.h"
12
13#include <ROOT/RNTupleFillContext.hxx>
14#include <ROOT/RNTupleFillStatus.hxx>
15#include <ROOT/RNTupleModel.hxx>
16#include <ROOT/RNTupleParallelWriter.hxx>
17#include <tuple>
18
19#include <SHiP/SimHit.hpp>
20#include <SHiP/SimParticle.hpp>
21#include <SHiP/TrackFitResult.hpp>
22#include <SHiP/detectors/CaloHit.hpp>
23#include <SHiP/detectors/SBTHit.hpp>
24#include <SHiP/detectors/StrawTubesHit.hpp>
25#include <SHiP/detectors/TimeDetHit.hpp>
26#include <SHiP/detectors/UBTHit.hpp>
27#include <algorithm>
28#include <cstddef>
29#include <memory>
30#include <mutex>
31#include <oneapi/tbb/enumerable_thread_specific.h>
32#include <optional>
33#include <stdexcept>
34#include <string>
35#include <string_view>
36#include <vector>
37
38namespace SHiP::trout {
39
40using SpectrometerTracks = std::vector<SHiP::TrackFitResult>;
41using UpstreamTaggerObjects = std::vector<SHiP::UBTHit>;
42using SurroundTaggerObjects = std::vector<SHiP::SBTHit>;
43using CalorimeterObjects = std::vector<SHiP::CaloHit>;
44using TimingDetectorObjects = std::vector<SHiP::TimeDetHit>;
45using SimHits = std::vector<SHiP::SimHit>;
46using SimParticles = std::vector<SHiP::SimParticle>;
47
48// A compile-time string usable as a class-template non-type parameter
49// (structural type), so each Hit type can carry its RNTuple name as part
50// of one declaration (see NamedHit/TypedHitWriters below) instead of a
51// separately-maintained parallel list of names.
52template <std::size_t N>
54 constexpr FixedString(char const (&s)[N]) { std::copy_n(s, N, value); }
55 char value[N];
56 constexpr operator std::string_view() const { return {value, N - 1}; }
57};
58
59template <FixedString Name, typename Hit>
60struct NamedHit {
61 static constexpr auto name = Name;
62 using type = Hit;
63};
64
66 public:
67 explicit RNTupleFileService(std::string const& filename)
68 : file_{TFile::Open(filename.c_str(), "RECREATE")} {
69 if (!file_ || file_->IsZombie()) {
70 throw std::runtime_error{"Could not create ROOT output file: " + filename};
71 }
72 }
73
74 TFile& file() { return *file_; }
75 std::mutex& mutex() { return mutex_; }
76
77 private:
78 std::unique_ptr<TFile> file_;
79 std::mutex mutex_;
80};
81
82template <typename Hit>
84 public:
85 TypedHitWriter(RNTupleFileService& file_service, std::string_view ntuple_name)
86 : file_service_{file_service} {
87 auto model = ROOT::RNTupleModel::CreateBare();
88 model->MakeField<Hit>("hit");
89
90 std::scoped_lock lock{file_service_.mutex()};
91
92 writer_ = ROOT::RNTupleParallelWriter::Append(std::move(model), ntuple_name,
93 file_service_.file());
94 }
95
96 void write(Hit const& hit) {
97 auto& state = states_.local();
98
99 if (!state.context) {
100 state.context = writer_->CreateFillContext();
101 state.entry = state.context->CreateEntry();
102 }
103
104 *state.entry->template GetPtr<Hit>("hit") = hit;
105
106 ROOT::RNTupleFillStatus status;
107 state.context->FillNoFlush(*state.entry, status);
108
109 if (status.ShouldFlushCluster()) {
110 state.context->FlushColumns();
111
112 std::scoped_lock lock{file_service_.mutex()};
113 state.context->FlushCluster();
114 }
115 }
116
117 private:
118 struct FillState {
119 std::shared_ptr<ROOT::RNTupleFillContext> context;
120 std::unique_ptr<ROOT::REntry> entry;
121 };
122
123 RNTupleFileService& file_service_;
124 std::unique_ptr<ROOT::RNTupleParallelWriter> writer_;
125 tbb::enumerable_thread_specific<FillState> states_;
126};
127
128// Generic bundle of TypedHitWriter<Hit>, one per NamedHit<Name, Hit> in
129// NamedHits. Instantiate this once per group of hit types that should share
130// one RNTupleFileService/output file (see ReconstructedWriters and
131// SimTruthWriters below for two independent bundles) instead of writing
132// bespoke tuple/constructor boilerplate per group.
133template <typename... NamedHits>
135 public:
136 explicit TypedHitWriters(RNTupleFileService& file_service)
137 : writers_{TypedHitWriter<typename NamedHits::type>{file_service, NamedHits::name}...} {}
138
139 template <typename Hit>
141 return std::get<TypedHitWriter<Hit>>(writers_);
142 }
143
144 private:
145 std::tuple<TypedHitWriter<typename NamedHits::type>...> writers_;
146};
147
149 NamedHit<"spectrometer_tracks", SHiP::TrackFitResult>,
150 NamedHit<"upstream_tagger", SHiP::UBTHit>, NamedHit<"surround_tagger", SHiP::SBTHit>,
151 NamedHit<"calorimeter", SHiP::CaloHit>, NamedHit<"timing_detector", SHiP::TimeDetHit>>;
152
153using SimTruthWriters = TypedHitWriters<NamedHit<"sim_hits", SHiP::SimHit>,
154 NamedHit<"sim_particles", SHiP::SimParticle>>;
155
157 public:
158 explicit HitRNTupleWriter(std::string const& filename, bool isSim)
159 : file_service_{filename}, writers_{file_service_} {
160 if (isSim) {
161 sim_writers_.emplace(file_service_);
162 }
163 }
164
165 // Bind a specific Hit instantiation at each phlex registration site,
166 // e.g. &HitRNTupleWriter::write<SHiP::TrackFitResult> — get<Hit>() picks
167 // the matching TypedHitWriter<Hit> out of the bundle by type, so this
168 // covers every writer in ReconstructedWriters without a method per type.
169 template <typename Hit>
170 void write(std::vector<Hit> const& hits) {
171 write_all(hits, writers_.template get<Hit>());
172 }
173
174 // Kept separate: sim_writers_ is only conditionally constructed (when
175 // isSim), so it can't live in the always-present writers_ bundle. Only
176 // ever registered when isSim is true (see PHLEX_REGISTER_ALGORITHMS in
177 // rec_output_module.cpp), so sim_writers_ is guaranteed to hold a value
178 // whenever this is actually called.
179 template <typename Hit>
180 void write_sim(std::shared_ptr<std::vector<Hit>> const& hits) {
181 if (hits)
182 write_all(*hits, sim_writers_->template get<Hit>());
183 }
184
185 private:
186 template <typename Hit>
187 static void write_all(std::vector<Hit> const& hits, TypedHitWriter<Hit>& writer) {
188 for (auto const& hit : hits) {
189 writer.write(hit);
190 }
191 }
192
193 // Declared first so it is destroyed last.
194 RNTupleFileService file_service_;
195
196 ReconstructedWriters writers_;
197
198 // Left unconstructed (no RNTuples created at all) unless isSim
199 std::optional<SimTruthWriters> sim_writers_;
200};
201
202} // namespace SHiP::trout
void write_sim(std::shared_ptr< std::vector< Hit > > const &hits)
Definition typed_rntuple_writer.hpp:180
HitRNTupleWriter(std::string const &filename, bool isSim)
Definition typed_rntuple_writer.hpp:158
void write(std::vector< Hit > const &hits)
Definition typed_rntuple_writer.hpp:170
Definition typed_rntuple_writer.hpp:65
std::mutex & mutex()
Definition typed_rntuple_writer.hpp:75
TFile & file()
Definition typed_rntuple_writer.hpp:74
RNTupleFileService(std::string const &filename)
Definition typed_rntuple_writer.hpp:67
Definition typed_rntuple_writer.hpp:83
void write(Hit const &hit)
Definition typed_rntuple_writer.hpp:96
TypedHitWriter(RNTupleFileService &file_service, std::string_view ntuple_name)
Definition typed_rntuple_writer.hpp:85
Definition typed_rntuple_writer.hpp:134
TypedHitWriter< Hit > & get()
Definition typed_rntuple_writer.hpp:140
TypedHitWriters(RNTupleFileService &file_service)
Definition typed_rntuple_writer.hpp:136
Definition fit_seed.cpp:16
TypedHitWriters< NamedHit<"sim_hits", SHiP::SimHit >, NamedHit<"sim_particles", SHiP::SimParticle > > SimTruthWriters
Definition typed_rntuple_writer.hpp:153
std::vector< SHiP::SimParticle > SimParticles
Definition typed_rntuple_writer.hpp:46
std::vector< SHiP::UBTHit > UpstreamTaggerObjects
Definition typed_rntuple_writer.hpp:41
std::vector< SHiP::SimHit > SimHits
Definition typed_rntuple_writer.hpp:45
TypedHitWriters< NamedHit<"spectrometer_tracks", SHiP::TrackFitResult >, NamedHit<"upstream_tagger", SHiP::UBTHit >, NamedHit<"surround_tagger", SHiP::SBTHit >, NamedHit<"calorimeter", SHiP::CaloHit >, NamedHit<"timing_detector", SHiP::TimeDetHit > > ReconstructedWriters
Definition typed_rntuple_writer.hpp:148
std::vector< SHiP::TrackFitResult > SpectrometerTracks
Definition typed_rntuple_writer.hpp:40
std::vector< SHiP::CaloHit > CalorimeterObjects
Definition typed_rntuple_writer.hpp:43
std::vector< SHiP::SBTHit > SurroundTaggerObjects
Definition typed_rntuple_writer.hpp:42
std::vector< SHiP::TimeDetHit > TimingDetectorObjects
Definition typed_rntuple_writer.hpp:44
char value[N]
Definition typed_rntuple_writer.hpp:55
constexpr FixedString(char const (&s)[N])
Definition typed_rntuple_writer.hpp:54
Definition typed_rntuple_writer.hpp:60
Hit type
Definition typed_rntuple_writer.hpp:62
static constexpr auto name
Definition typed_rntuple_writer.hpp:61