Skip to content

Commit a2898e2

Browse files
author
Goetz Gaycken
committed
Added faster, in-place clusterization.
The in-place clusterization could be used instead of the default clusterization. It clusterizes by sorting a cell container in such a way that adjacent cells with the same label belong to the same cluster.
1 parent 871182f commit a2898e2

File tree

10 files changed

+1302
-17
lines changed

10 files changed

+1302
-17
lines changed

Core/include/Acts/Clusterization/InPlaceClusterization.hpp

Lines changed: 370 additions & 0 deletions
Large diffs are not rendered by default.

Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ class DigitizationAlgorithm final : public IAlgorithm {
8282
/// Minimum number of attempts to derive a valid dgitized measurement when
8383
/// random numbers are involved.
8484
std::size_t minMaxRetries = 10;
85+
/// Use InPlaceClusterization clusterization instead of the original
86+
/// clusterization if doMerge is true
87+
bool useInPlaceClusterization = false;
8588
};
8689

8790
/// Construct the smearing algorithm.

Examples/Algorithms/Digitization/include/ActsExamples/Digitization/ModuleClusters.hpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,13 @@ class ModuleClusters {
4040

4141
ModuleClusters(Acts::BinUtility segmentation,
4242
std::vector<Acts::BoundIndices> geoIndices, bool merge,
43-
double nsigma, bool commonCorner)
43+
double nsigma, bool commonCorner, bool alt = false)
4444
: m_segmentation(std::move(segmentation)),
4545
m_geoIndices(std::move(geoIndices)),
4646
m_merge(merge),
4747
m_nsigma(nsigma),
48-
m_commonCorner(commonCorner) {}
48+
m_commonCorner(commonCorner),
49+
m_useInPlaceClusterization(alt) {}
4950

5051
void add(DigitizedParameters params, simhit_t simhit);
5152
std::vector<std::pair<DigitizedParameters, std::set<simhit_t>>>
@@ -58,14 +59,21 @@ class ModuleClusters {
5859
bool m_merge;
5960
double m_nsigma;
6061
bool m_commonCorner;
62+
bool m_useInPlaceClusterization;
6163

6264
std::vector<ModuleValue> createCellCollection();
6365
void merge();
64-
ModuleValue squash(std::vector<ModuleValue>& values);
66+
void mergeWithInPlaceClusterization();
67+
template <typename index_t, unsigned int AXIS>
68+
void mergeWithInPlaceClusterizationImpl();
69+
70+
template <class cell_list_t>
71+
ModuleValue squash(cell_list_t& values) const;
6572
std::vector<std::size_t> nonGeoEntries(
66-
std::vector<Acts::BoundIndices>& indices);
73+
const std::vector<Acts::BoundIndices>& indices) const;
74+
template <class cell_list_t>
6775
std::vector<std::vector<ModuleValue>> mergeParameters(
68-
std::vector<ModuleValue> values);
76+
cell_list_t& values) const;
6977
};
7078

7179
} // namespace ActsExamples

Examples/Algorithms/Digitization/src/DigitizationAlgorithm.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,13 @@ ProcessCode DigitizationAlgorithm::execute(const AlgorithmContext& ctx) const {
196196

197197
// Run the digitizer. Iterate over the hits for this surface inside the
198198
// visitor so we do not need to lookup the variant object per-hit.
199+
199200
std::visit(
200201
[&](const auto& digitizer) {
201202
ModuleClusters moduleClusters(
202203
digitizer.geometric.segmentation, digitizer.geometric.indices,
203-
m_cfg.doMerge, m_cfg.mergeNsigma, m_cfg.mergeCommonCorner);
204+
m_cfg.doMerge, m_cfg.mergeNsigma, m_cfg.mergeCommonCorner,
205+
m_cfg.useInPlaceClusterization);
204206

205207
for (auto h = moduleSimHits.begin(); h != moduleSimHits.end(); ++h) {
206208
const auto& simHit = *h;

Examples/Algorithms/Digitization/src/ModuleClusters.cpp

Lines changed: 156 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "ActsExamples/Digitization/ModuleClusters.hpp"
1010

1111
#include "Acts/Clusterization/Clusterization.hpp"
12+
#include "Acts/Clusterization/InPlaceClusterization.hpp"
1213
#include "Acts/Utilities/Helpers.hpp"
1314
#include "ActsExamples/Digitization/MeasurementCreation.hpp"
1415
#include "ActsFatras/Digitization/Channelizer.hpp"
@@ -19,6 +20,7 @@
1920
#include <cstdlib>
2021
#include <limits>
2122
#include <memory>
23+
#include <ranges>
2224
#include <stdexcept>
2325
#include <type_traits>
2426

@@ -48,7 +50,12 @@ void ModuleClusters::add(DigitizedParameters params, simhit_t simhit) {
4850
std::vector<std::pair<DigitizedParameters, std::set<ModuleClusters::simhit_t>>>
4951
ModuleClusters::digitizedParameters() {
5052
if (m_merge) { // (re-)build the clusters
51-
merge();
53+
if (m_useInPlaceClusterization && !m_moduleValues.empty() &&
54+
std::holds_alternative<Cluster::Cell>(m_moduleValues.front().value)) {
55+
mergeWithInPlaceClusterization();
56+
} else {
57+
merge();
58+
}
5259
}
5360
std::vector<std::pair<DigitizedParameters, std::set<simhit_t>>> retv;
5461
for (ModuleValue& mval : m_moduleValues) {
@@ -96,6 +103,145 @@ std::vector<ModuleValue> ModuleClusters::createCellCollection() {
96103
}
97104
return cells;
98105
}
106+
void ModuleClusters::mergeWithInPlaceClusterization() {
107+
if (m_moduleValues.size() < std::numeric_limits<std::uint16_t>::max()) {
108+
mergeWithInPlaceClusterizationImpl<std::uint16_t, 0>();
109+
} else {
110+
mergeWithInPlaceClusterizationImpl<std::uint32_t, 0>();
111+
}
112+
}
113+
114+
// Helper to adapt an in-place clustered cell collection to imitate a
115+
// ModuleValue collection of a cluster cell_t must have an m_srcIndex member
116+
// which refers to a ModuleValue
117+
template <typename cell_t>
118+
struct ClusterModuleValues {
119+
// iterator to iterate over the ModuleValues of a cluster
120+
struct iterator {
121+
ModuleValue& operator*() {
122+
assert(m_moduleValues && m_iter->m_srcIndex < m_moduleValues->size());
123+
return (*m_moduleValues)[m_iter->m_srcIndex];
124+
}
125+
126+
// Test whether two iterators refer to the same ModuleValue
127+
// Assumes that all ModuleValues belong to the same container, and
128+
// that the underlying in-place clustered cell containers are the same.
129+
bool operator==(const iterator& other) const {
130+
assert(m_moduleValues == other.m_moduleValues);
131+
return other.m_iter == m_iter;
132+
}
133+
iterator& operator++() {
134+
++m_iter;
135+
return *this;
136+
}
137+
138+
std::vector<ModuleValue>* m_moduleValues;
139+
std::span<cell_t>::iterator m_iter;
140+
};
141+
142+
// Iterator to refer to the first ModuleValue of a range
143+
iterator begin() { return iterator{m_moduleValues, m_cells.begin()}; }
144+
// Iterator to refer to the ModuleValue after the last one of a range
145+
iterator end() { return iterator{m_moduleValues, m_cells.end()}; }
146+
147+
// Get the number of ModuleValues in the range
148+
std::size_t size() const { return m_cells.size(); }
149+
150+
// Return true if the ModuleValue range is empty
151+
bool empty() const { return m_cells.empty(); }
152+
ModuleValue& operator[](unsigned int idx) {
153+
assert(idx < m_cells.size() && m_moduleValues &&
154+
m_cells[idx].m_srcIndex < m_moduleValues->size());
155+
return (*m_moduleValues)[m_cells[idx].m_srcIndex];
156+
}
157+
158+
// return the ModuleValue at a certain position in the range (range checked)
159+
// @param idx the position in the range where 0 refers to the first element in the range
160+
// May throw a range_error if idx is not a valid index in the range.
161+
ModuleValue& at(unsigned int idx) {
162+
assert(idx < m_cells.size() && m_moduleValues &&
163+
m_cells[idx].m_srcIndex < m_moduleValues->size());
164+
return (*m_moduleValues).at(m_cells[idx].m_srcIndex);
165+
}
166+
167+
// return the ModuleValue at a certain position in the range (no range check)
168+
// @param idx the position in the range where 0 refers to the first element in the range
169+
const ModuleValue& operator[](unsigned int idx) const {
170+
assert(idx < m_cells.size() && m_moduleValues &&
171+
m_cells[idx].m_srcIndex < m_moduleValues->size());
172+
return (*m_moduleValues)[m_cells[idx]];
173+
}
174+
175+
std::vector<ModuleValue>* m_moduleValues;
176+
std::span<cell_t> m_cells; // A range of cells which refer to a ModuleValue
177+
// in the ModuleValue collection
178+
};
179+
180+
template <typename index_t, unsigned int AXIS>
181+
void ModuleClusters::mergeWithInPlaceClusterizationImpl() {
182+
// create a cell collection which contains the coordinates of the ModuleValues
183+
// and which refer back to the source ModuleValue.
184+
using Cell = Acts::InPlaceClusterization::Cell<std::int16_t, 2, index_t>;
185+
std::vector<Cell> cells;
186+
cells.reserve(m_moduleValues.size());
187+
{
188+
index_t idx = 0;
189+
for (const ModuleValue& mval : m_moduleValues) {
190+
if (std::holds_alternative<Cluster::Cell>(mval.value)) {
191+
cells.push_back(Cell(
192+
std::array<std::int16_t, 2>{
193+
static_cast<std::int16_t>(
194+
std::get<ActsExamples::Cluster::Cell>(mval.value).bin[0]),
195+
static_cast<std::int16_t>(
196+
std::get<ActsExamples::Cluster::Cell>(mval.value).bin[1])},
197+
idx));
198+
}
199+
++idx;
200+
}
201+
}
202+
// Clusterize the cell collection in-place assuming a 4 or 8 fold connectivity
203+
if (m_commonCorner) {
204+
Acts::InPlaceClusterization::clusterize<
205+
AXIS, std::vector<Cell>, index_t,
206+
Acts::InPlaceClusterization::EConnectionType::CommonEdgeOrCorner>(
207+
cells);
208+
} else {
209+
Acts::InPlaceClusterization::clusterize<
210+
AXIS, std::vector<Cell>, index_t,
211+
Acts::InPlaceClusterization::EConnectionType::CommonEdge>(cells);
212+
}
213+
214+
std::vector<ModuleValue> newVals;
215+
newVals.reserve(m_moduleValues.size());
216+
// Helper to create a cluster module value for the given cell range
217+
// the ModuleValues the cell range refers to will be squashed and then
218+
// added to a new Cluster.
219+
auto addClusters = [this, &newVals](std::vector<Cell>& all_cells,
220+
unsigned int idx_begin,
221+
unsigned int idx_end) {
222+
auto cluster_cells =
223+
std::span(all_cells.begin() + idx_begin, all_cells.begin() + idx_end);
224+
if (cluster_cells.size() == 1) {
225+
// no need to merge parameters
226+
ClusterModuleValues<Cell> temp{&m_moduleValues, cluster_cells};
227+
newVals.push_back(squash(temp));
228+
} else {
229+
ClusterModuleValues<Cell> temp{&m_moduleValues, cluster_cells};
230+
std::ranges::sort(cluster_cells, [](const Cell& a, const Cell& b) {
231+
return Acts::InPlaceClusterization::traits::getCellCoordinate(a, AXIS) <
232+
Acts::InPlaceClusterization::traits::getCellCoordinate(b, AXIS);
233+
});
234+
235+
for (std::vector<ModuleValue>& remerged : mergeParameters(temp)) {
236+
newVals.push_back(squash(remerged));
237+
}
238+
}
239+
};
240+
241+
// Create ModuleValues for each cell range which forms a cluster.
242+
for_each_cluster(cells, addClusters);
243+
m_moduleValues = std::move(newVals);
244+
}
99245

100246
void ModuleClusters::merge() {
101247
std::vector<ModuleValue> cells = createCellCollection();
@@ -117,7 +263,6 @@ void ModuleClusters::merge() {
117263
// only. Still have to check if they match based on the other
118264
// indices (a good example of this would a for a timing
119265
// detector).
120-
121266
for (std::vector<ModuleValue>& remerged : mergeParameters(cellv)) {
122267
newVals.push_back(squash(remerged));
123268
}
@@ -134,7 +279,7 @@ void ModuleClusters::merge() {
134279

135280
// ATTN: returns vector of index into `indices'
136281
std::vector<std::size_t> ModuleClusters::nonGeoEntries(
137-
std::vector<Acts::BoundIndices>& indices) {
282+
const std::vector<Acts::BoundIndices>& indices) const {
138283
std::vector<std::size_t> retv;
139284
for (std::size_t i = 0; i < indices.size(); i++) {
140285
auto idx = indices.at(i);
@@ -146,8 +291,9 @@ std::vector<std::size_t> ModuleClusters::nonGeoEntries(
146291
}
147292

148293
// Merging based on parameters
294+
template <class cell_list_t>
149295
std::vector<std::vector<ModuleValue>> ModuleClusters::mergeParameters(
150-
std::vector<ModuleValue> values) {
296+
cell_list_t& values) const {
151297
std::vector<std::vector<ModuleValue>> retv;
152298

153299
std::vector<bool> used(values.size(), false);
@@ -181,7 +327,7 @@ std::vector<std::vector<ModuleValue>> ModuleClusters::mergeParameters(
181327
// The cluster to be merged into can have more than one
182328
// associated value at this point, so we have to consider them
183329
// all
184-
for (ModuleValue& thisval : thisvec) {
330+
for (const ModuleValue& thisval : thisvec) {
185331
// Loop over non-geometric dimensions
186332
for (auto k : nonGeoEntries(thisval.paramIndices)) {
187333
double p_i = thisval.paramValues.at(k);
@@ -221,14 +367,15 @@ std::vector<std::vector<ModuleValue>> ModuleClusters::mergeParameters(
221367
return retv;
222368
}
223369

224-
ModuleValue ModuleClusters::squash(std::vector<ModuleValue>& values) {
370+
template <class cell_list_t>
371+
ModuleValue ModuleClusters::squash(cell_list_t& values) const {
225372
ModuleValue mval;
226373
double tot = 0;
227374
double tot2 = 0;
228375
std::vector<double> weights;
229376

230377
// First, start by computing cell weights
231-
for (ModuleValue& other : values) {
378+
for (const ModuleValue& other : values) {
232379
if (std::holds_alternative<Cluster::Cell>(other.value)) {
233380
weights.push_back(std::get<Cluster::Cell>(other.value).activation);
234381
} else {
@@ -240,7 +387,7 @@ ModuleValue ModuleClusters::squash(std::vector<ModuleValue>& values) {
240387

241388
// Now, go over the non-geometric indices
242389
for (std::size_t i = 0; i < values.size(); i++) {
243-
ModuleValue& other = values.at(i);
390+
const ModuleValue& other = values.at(i);
244391
for (std::size_t j = 0; j < other.paramIndices.size(); j++) {
245392
auto idx = other.paramIndices.at(j);
246393
if (!rangeContainsValue(m_geoIndices, idx)) {
@@ -272,7 +419,7 @@ ModuleValue ModuleClusters::squash(std::vector<ModuleValue>& values) {
272419
std::size_t b1max = 0;
273420

274421
for (std::size_t i = 0; i < values.size(); i++) {
275-
ModuleValue& other = values.at(i);
422+
const ModuleValue& other = values.at(i);
276423
if (!std::holds_alternative<Cluster::Cell>(other.value)) {
277424
continue;
278425
}

Examples/Python/python/acts/examples/simulation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ def addDigitization(
746746
rnd: Optional[acts.examples.RandomNumbers] = None,
747747
doMerge: Optional[bool] = None,
748748
mergeCommonCorner: Optional[bool] = None,
749+
useInPlaceClusterization: Optional[bool] = None,
749750
minEnergyDeposit: Optional[float] = None,
750751
logLevel: Optional[acts.logging.Level] = None,
751752
) -> acts.examples.Sequencer:
@@ -786,6 +787,7 @@ def addDigitization(
786787
**acts.examples.defaultKWArgs(
787788
doMerge=doMerge,
788789
mergeCommonCorner=mergeCommonCorner,
790+
useInPlaceClusterization=useInPlaceClusterization,
789791
),
790792
)
791793

Examples/Python/src/Digitization.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ void addDigitization(Context& ctx) {
5656
outputParticleMeasurementsMap, outputSimHitMeasurementsMap,
5757
surfaceByIdentifier, randomNumbers, doOutputCells, doClusterization,
5858
doMerge, mergeCommonCorner, minEnergyDeposit, digitizationConfigs,
59-
minMaxRetries);
59+
minMaxRetries, useInPlaceClusterization);
6060

6161
c.def_readonly("mergeNsigma", &Config::mergeNsigma);
6262

0 commit comments

Comments
 (0)