Skip to content

Commit 324cc6a

Browse files
authored
Merge pull request #1027 from gertvanhoey/pileup-mt
Make pile-up actor suitable for multi-threaded simulations
2 parents 217b394 + 7434c83 commit 324cc6a

10 files changed

Lines changed: 383 additions & 234 deletions

File tree

core/opengate_core/opengate_lib/digitizer/GateCoincidenceSorterActor.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ void GateCoincidenceSorterActor::BeginOfRunActionMasterThread(int run_id) {
176176
// digis from all threads, so that the coincidence actor can identify
177177
// coincidences between singles, irrespective of the thread in which they were
178178
// simulated (prompt as well as random coincidences).
179-
fTimeSorter = std::make_unique<GateTimeSorter>();
179+
fTimeSorter = std::make_unique<GateTimeSorter>(fOutputDigiCollectionName);
180180
fTimeSorter->Init(fInputDigiCollection);
181181
fTimeSorter->SetSortingWindow(fSortingTime);
182182
fTimeSorter->SetMaxSize(fClearEveryNEvents);

core/opengate_core/opengate_lib/digitizer/GateDigitizerPileupActor.cpp

Lines changed: 116 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* --------------------------------------------------
22
Copyright (C): OpenGATE Collaboration
33
This software is distributed under the terms
4-
of the GNU Lesser General Public Licence (LGPL)
4+
of the GNU Lesser General Public Licence (LGPL)
55
See LICENSE.md for further details
66
-------------------------------------------------- */
77

@@ -12,7 +12,7 @@
1212
#include <memory>
1313

1414
GateDigitizerPileupActor::GateDigitizerPileupActor(py::dict &user_info)
15-
: GateVDigitizerWithOutputActor(user_info, false) {
15+
: GateVDigitizerWithOutputActor(user_info, true) {
1616
fActions.insert("EndOfEventAction");
1717
fActions.insert("EndOfRunAction");
1818
}
@@ -69,6 +69,22 @@ void GateDigitizerPileupActor::InitializeUserInfo(py::dict &user_info) {
6969
fInputDigiCollectionName = DictGetStr(user_info, "input_digi_collection");
7070
}
7171

72+
void GateDigitizerPileupActor::BeginOfRunActionMasterThread(int run_id) {
73+
74+
fTimeSorter = std::make_unique<GateTimeSorter>(fOutputDigiCollectionName);
75+
fTimeSorter->Init(fInputDigiCollection);
76+
fTimeSorter->SetSortingWindow(fSortingTime);
77+
fTimeSorter->SetMaxSize(fClearEveryNEvents);
78+
79+
auto &outputIter = fTimeSorter->OutputIterator();
80+
outputIter.TrackAttribute("GlobalTime", &fTimeSorterOutputTime);
81+
outputIter.TrackAttribute("TotalEnergyDeposit", &fTimeSorterOutputEdep);
82+
outputIter.TrackAttribute("PreStepUniqueVolumeID", &fTimeSorterOutputVolID);
83+
84+
fVolumePileupWindows.clear();
85+
fWindowExpiry = std::queue<volumeWindowExpiry>();
86+
}
87+
7288
void GateDigitizerPileupActor::SetGroupVolumeDepth(const int depth) {
7389
fGroupVolumeDepth = depth;
7490
}
@@ -82,41 +98,28 @@ void GateDigitizerPileupActor::DigitInitialize(
8298
a.push_back("PostPosition");
8399
GateVDigitizerWithOutputActor::DigitInitialize(a);
84100

85-
// Get output attribute pointer
86-
fOutputTimeAttribute = fOutputDigiCollection->GetDigiAttribute("GlobalTime");
87-
fOutputEdepAttribute =
88-
fOutputDigiCollection->GetDigiAttribute("TotalEnergyDeposit");
89-
fOutputPosAttribute = fOutputDigiCollection->GetDigiAttribute("PostPosition");
90-
91-
// Set up pointers to track specific attributes
92-
auto &lr = fThreadLocalVDigitizerData.Get();
93-
auto &l = fThreadLocalData.Get();
94-
95-
l.fTimeSorter.Init(fInputDigiCollection);
96-
l.fTimeSorter.OutputIterator().TrackAttribute("GlobalTime", &l.time);
97-
l.fTimeSorter.OutputIterator().TrackAttribute("TotalEnergyDeposit", &l.edep);
98-
l.fTimeSorter.OutputIterator().TrackAttribute("PreStepUniqueVolumeID",
99-
&l.volID);
100-
l.fTimeSorter.SetSortingWindow(fSortingTime);
101-
l.fTimeSorter.SetMaxSize(fClearEveryNEvents);
101+
fOutputDigiCollection->RootInitializeTupleForWorker();
102102
}
103103

104104
void GateDigitizerPileupActor::EndOfEventAction(const G4Event *) {
105-
auto &l = fThreadLocalData.Get();
106-
l.fTimeSorter.Ingest();
107-
l.fTimeSorter.Process();
108-
ProcessTimeSortedDigis();
105+
106+
fTimeSorter->OnEndOfEventAction([this]() { ProcessTimeSortedDigis(); });
109107
}
110108

111109
void GateDigitizerPileupActor::EndOfRunAction(const G4Run *) {
112-
auto &l = fThreadLocalData.Get();
113-
l.fTimeSorter.Flush();
114-
ProcessTimeSortedDigis();
115-
for (auto &[_vol_hash, window] : l.fVolumePileupWindows) {
116-
ProcessPileupWindow(window);
117-
}
118-
// Make sure everything is output into the root file.
119-
fOutputDigiCollection->FillToRootIfNeeded(true);
110+
111+
fTimeSorter->OnEndOfRunAction(
112+
[this]() { fOutputDigiCollection->FillToRootIfNeeded(true); },
113+
[this]() {
114+
ProcessTimeSortedDigis();
115+
// Process all pile-up windows which still have an expiry item.
116+
while (fWindowExpiry.size() > 0) {
117+
auto &window =
118+
fVolumePileupWindows.at(fWindowExpiry.front().volumeHash);
119+
ProcessPileupWindow(window);
120+
fWindowExpiry.pop();
121+
}
122+
});
120123
}
121124

122125
GateDigitizerPileupActor::PileupWindow &
@@ -128,31 +131,33 @@ GateDigitizerPileupActor::GetPileupWindowForCurrentVolume(
128131

129132
const auto vol_hash = volume->get()->GetIdUpToDepthAsHash(fGroupVolumeDepth);
130133

131-
// Look up the window based on volume hash
134+
// Look up the window based on volume hash.
132135
auto it = windows.find(vol_hash);
133136
if (it != windows.end()) {
134137
// Return a reference to the existing PileupWindow object for the volume.
135138
return it->second;
136139
} else {
137140
// A PileupWindow object does not yet exist for this volume: create one.
138141
PileupWindow window;
142+
window.hash = vol_hash;
139143
const auto vol_id = volume->get()->GetIdUpToDepth(fGroupVolumeDepth);
140-
auto &l = fThreadLocalData.Get();
141144
// Create a GateDigiCollection for this volume, as a temporary storage for
142145
// digis that belong to the same time window (the name must be unique).
143146
window.digis = GateDigiCollectionManager::GetInstance()->NewDigiCollection(
144147
GetName() + "_" + vol_id);
145148
window.digis->InitDigiAttributesFromCopy(fInputDigiCollection);
149+
window.digis->SetSharedStorage(
150+
true); // Same storage is accessible by all threads.
146151
// Create an iterator to be used when digis will be combined into one digi,
147152
// due to pile-up.
148153
window.digiIter = window.digis->NewIterator();
149-
window.digiIter.TrackAttribute("TotalEnergyDeposit", &l.edep);
150-
window.digiIter.TrackAttribute("PostPosition", &l.pos);
154+
window.digiIter.TrackAttribute("TotalEnergyDeposit", &fPileupWindowEdep);
155+
window.digiIter.TrackAttribute("PostPosition", &fPileupWindowPos);
151156
// Create a filler to copy all digi attributes from the sorted collection
152157
// into the collection of the window.
153158
window.fillerIn = std::make_unique<GateDigiAttributesFiller>(
154-
l.fTimeSorter.OutputCollection(), window.digis,
155-
l.fTimeSorter.OutputCollection()->GetDigiAttributeNames());
159+
fTimeSorter->OutputCollection(), window.digis,
160+
fTimeSorter->OutputCollection()->GetDigiAttributeNames());
156161
// Create a filler to copy digi attributes from the collection of the window
157162
// to the output collection (used for the digis that will result from
158163
// pile-up).
@@ -169,42 +174,51 @@ GateDigitizerPileupActor::GetPileupWindowForCurrentVolume(
169174
}
170175

171176
void GateDigitizerPileupActor::ProcessTimeSortedDigis() {
172-
auto &l = fThreadLocalData.Get();
173-
auto &iter = l.fTimeSorter.OutputIterator();
177+
178+
auto &iter = fTimeSorter->OutputIterator();
174179
iter.GoToBegin();
175180
while (!iter.IsAtEnd()) {
176181
// Look up or create the pile-up window object for the volume to which the
177182
// current digi belongs.
178-
auto &window =
179-
GetPileupWindowForCurrentVolume(l.volID, l.fVolumePileupWindows);
183+
auto &window = GetPileupWindowForCurrentVolume(fTimeSorterOutputVolID,
184+
fVolumePileupWindows);
185+
186+
const auto current_time = *fTimeSorterOutputTime;
187+
const auto current_edep = *fTimeSorterOutputEdep;
188+
189+
// The GlobalTime of digis provided by the time sorter are guaranteed to be
190+
// monotonically increasing. As a consequence, all pile-up windows that have
191+
// expired by the time of the most recently arrived digi can be handled.
192+
ProcessPileupWindows(current_time);
180193

181-
const auto current_time = *l.time;
182-
const auto current_edep = *l.edep;
183194
if (window.digis->GetSize() == 0) {
184-
// The window has no digis yet: make the window start at the time of the
185-
// current digi and remember the current edep as highest.
195+
// The window was empty: the newly arrived digi will open it.
186196
window.startTime = current_time;
197+
fWindowExpiry.push({window.hash, window.startTime + fTimeWindow});
187198
window.highestEdep = current_edep;
188-
} else if (current_time - window.startTime > fTimeWindow) {
189-
// The current digi is beyond the time window: process the digis that are
190-
// currently in the window, then make the window start at the time of the
191-
// current digi and remember the current edep as highest.
192-
ProcessPileupWindow(window);
193-
window.startTime = current_time;
194-
window.highestEdep = current_edep;
195-
}
196-
197-
if (fTimeWindowPolicy == TimeWindowPolicy::Paralyzable) {
198-
// In Paralyzable mode, extend the time window to start at the time of
199-
// the current digi.
200-
window.startTime = current_time;
201-
} else if (fTimeWindowPolicy == TimeWindowPolicy::EnergyWinnerParalyzable) {
202-
// In EnergyWinnerParalyzable mode, extend the time window only if
203-
// the current digi has higher energy than the highest-energy
204-
// digi so far.
205-
if (current_edep > window.highestEdep) {
199+
} else {
200+
// The window was already opened: update the window depending on the
201+
// policy.
202+
switch (fTimeWindowPolicy) {
203+
case TimeWindowPolicy::NonParalyzable:
204+
// window start time remains the same.
205+
break;
206+
case TimeWindowPolicy::Paralyzable:
207+
// The current digi moves the start time forward.
206208
window.startTime = current_time;
207-
window.highestEdep = current_edep;
209+
fWindowExpiry.push({window.hash, window.startTime + fTimeWindow});
210+
break;
211+
case TimeWindowPolicy::EnergyWinnerParalyzable:
212+
// The current digi moves the start time forward if its energy is higher
213+
// than previous energies.
214+
if (current_edep > window.highestEdep) {
215+
window.startTime = current_time;
216+
fWindowExpiry.push({window.hash, window.startTime + fTimeWindow});
217+
window.highestEdep = current_edep;
218+
}
219+
break;
220+
default:
221+
Fatal("Unknown time window policy");
208222
}
209223
}
210224

@@ -213,13 +227,11 @@ void GateDigitizerPileupActor::ProcessTimeSortedDigis() {
213227

214228
iter++;
215229
}
216-
l.fTimeSorter.MarkOutputAsProcessed();
230+
fTimeSorter->MarkOutputAsProcessed();
217231
}
218232

219233
void GateDigitizerPileupActor::ProcessPileupWindow(PileupWindow &window) {
220234
// This function simulates pile-up by combining the digis in the given window
221-
// into one digi.
222-
auto &l = fThreadLocalData.Get();
223235

224236
std::optional<double> highest_edep{};
225237
double total_edep = 0.0;
@@ -229,22 +241,26 @@ void GateDigitizerPileupActor::ProcessPileupWindow(PileupWindow &window) {
229241
G4ThreeVector weighted_position;
230242
G4ThreeVector highest_edep_position;
231243

232-
// Iterate over all digis in the window from the beginning.
244+
if (window.digis->GetSize() == 0) {
245+
return;
246+
}
233247

234-
window.digiIter.Reset();
235-
while (!window.digiIter.IsAtEnd()) {
236-
const auto current_edep = *l.edep;
237-
const auto current_pos = *l.pos;
248+
// Iterate over all digis in the window from the beginning.
249+
auto &iter = window.digiIter;
250+
iter.GoToBegin();
251+
while (!iter.IsAtEnd()) {
252+
const auto current_edep = *fPileupWindowEdep;
253+
const auto current_pos = *fPileupWindowPos;
238254
// Remember the index of the first digi.
239255
if (!first_index) {
240-
first_index = window.digiIter.fIndex;
256+
first_index = iter.fIndex;
241257
}
242258
// Remember the index of the last digi.
243-
last_index = window.digiIter.fIndex;
259+
last_index = iter.fIndex;
244260
// Remember the value and index of the highest deposited energy so far.
245261
if (!highest_edep.has_value() || current_edep > highest_edep.value()) {
246262
highest_edep = current_edep;
247-
highest_edep_index = window.digiIter.fIndex;
263+
highest_edep_index = iter.fIndex;
248264
highest_edep_position = current_pos;
249265
}
250266
// Accumulate all deposited energy values.
@@ -255,22 +271,28 @@ void GateDigitizerPileupActor::ProcessPileupWindow(PileupWindow &window) {
255271
// Accumulate the energy-weighted position.
256272
weighted_position += current_pos * current_edep;
257273
}
258-
window.digiIter++;
274+
iter++;
259275
}
260276
if (fPositionAttributePolicy ==
261277
PositionAttributePolicy::EnergyWeightedCentroid) {
262278
weighted_position /= total_edep;
263279
}
264280

281+
// Get output attribute pointer.
282+
auto outputEdepAttribute =
283+
fOutputDigiCollection->GetDigiAttribute("TotalEnergyDeposit");
284+
auto outputPosAttribute =
285+
fOutputDigiCollection->GetDigiAttribute("PostPosition");
286+
265287
// The resulting pile-up digi gets:
266288
// - the total edep value.
267-
fOutputEdepAttribute->FillDValue(total_edep);
289+
outputEdepAttribute->FillDValue(total_edep);
268290
// - the position according to the position attribute policy.
269291
if (fPositionAttributePolicy == PositionAttributePolicy::EnergyWinner) {
270-
fOutputPosAttribute->Fill3Value(highest_edep_position);
292+
outputPosAttribute->Fill3Value(highest_edep_position);
271293
} else if (fPositionAttributePolicy ==
272294
PositionAttributePolicy::EnergyWeightedCentroid) {
273-
fOutputPosAttribute->Fill3Value(weighted_position);
295+
outputPosAttribute->Fill3Value(weighted_position);
274296
}
275297
// All the other attribute values are according to the attribute policy.
276298
if (fAttributePolicy == AttributePolicy::First) {
@@ -283,3 +305,18 @@ void GateDigitizerPileupActor::ProcessPileupWindow(PileupWindow &window) {
283305
// Remove all processed digis from the window.
284306
window.digis->Clear();
285307
}
308+
309+
void GateDigitizerPileupActor::ProcessPileupWindows(double currentTime) {
310+
311+
// Process the expiry items for which the expiry time is before currentTime.
312+
while (fWindowExpiry.size() > 0 &&
313+
currentTime > fWindowExpiry.front().expiryTime) {
314+
auto &window = fVolumePileupWindows.at(fWindowExpiry.front().volumeHash);
315+
// Check again whether the window is actually expired, because its expiry
316+
// time may have been updated in a later expiry item.
317+
if (currentTime > window.startTime + fTimeWindow) {
318+
ProcessPileupWindow(window);
319+
}
320+
fWindowExpiry.pop();
321+
}
322+
}

0 commit comments

Comments
 (0)