File indexing completed on 2026-08-18 08:30:23
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010 #include "RandomNoisePixel.h"
0011
0012 #include <DD4hep/Alignments.h>
0013 #include <DD4hep/IDDescriptor.h>
0014 #include <DD4hep/Objects.h>
0015 #include <DD4hep/Segmentations.h>
0016 #include <DD4hep/Volumes.h>
0017 #include <DDSegmentation/BitFieldCoder.h>
0018 #include <DDSegmentation/CartesianGridXY.h>
0019 #include <DDSegmentation/CartesianGridXZ.h>
0020 #include <DDSegmentation/CylindricalGridPhiZ.h>
0021 #include <DDSegmentation/MultiSegmentation.h>
0022 #include <DDSegmentation/Segmentation.h>
0023 #include <Math/GenVector/Cartesian3D.h>
0024 #include <Math/GenVector/DisplacementVector3D.h>
0025 #include <RtypesCore.h>
0026 #include <TGeoBBox.h>
0027 #include <TGeoNode.h>
0028 #include <TGeoShape.h>
0029 #include <TGeoTube.h>
0030 #include <algorithms/geo.h>
0031 #include <algorithm>
0032 #include <array>
0033 #include <cctype>
0034 #include <cmath>
0035 #include <gsl/pointers>
0036 #include <initializer_list>
0037 #include <iterator>
0038 #include <limits>
0039 #include <mutex>
0040 #include <numbers>
0041 #include <optional>
0042 #include <stdexcept>
0043 #include <tuple>
0044 #include <utility>
0045
0046 #include "algorithms/digi/RandomNoisePixelConfig.h"
0047
0048 namespace eicrecon {
0049 namespace {
0050
0051 using RawSegmentation = dd4hep::DDSegmentation::Segmentation;
0052
0053
0054
0055 std::mutex& geometryInitializationMutex() {
0056 static std::mutex mutex;
0057 return mutex;
0058 }
0059
0060
0061 std::string lower(std::string value) {
0062 std::transform(value.begin(), value.end(), value.begin(),
0063 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
0064 return value;
0065 }
0066
0067
0068 std::optional<int> layerFromPlacement(const dd4hep::PlacedVolume& placement) {
0069 if (!placement.isValid()) {
0070 return std::nullopt;
0071 }
0072 for (const auto& [field, value] : placement.volIDs()) {
0073 const auto name = lower(field);
0074 if (name == "layer" || name.find("lay") != std::string::npos) {
0075 return value;
0076 }
0077 }
0078 return std::nullopt;
0079 }
0080
0081
0082
0083 std::optional<int> layerFromDetElement(const dd4hep::DetElement& layer,
0084 const dd4hep::DetElement& module) {
0085 if (auto id = layerFromPlacement(layer.placement())) {
0086 return id;
0087 }
0088 return layerFromPlacement(module.placement());
0089 }
0090
0091
0092 TGeoHMatrix placementTransform(const dd4hep::PlacedVolume& placement) {
0093 if (auto* node = placement.ptr()) {
0094 if (auto* matrix = node->GetMatrix()) {
0095 return TGeoHMatrix{*matrix};
0096 }
0097 }
0098 return TGeoHMatrix{};
0099 }
0100
0101
0102 TGeoHMatrix composeTransforms(const TGeoHMatrix& parentToWorld,
0103 const TGeoHMatrix& childToParent) {
0104 TGeoHMatrix childToWorld{parentToWorld};
0105 childToWorld.Multiply(childToParent);
0106 return childToWorld;
0107 }
0108
0109
0110
0111 bool hasRequestedReadout(const dd4hep::PlacedVolume& placement, std::string_view readoutName) {
0112 if (!placement.isValid()) {
0113 return false;
0114 }
0115 dd4hep::SensitiveDetector sensitive{placement.volume().sensitiveDetector()};
0116 if (!sensitive.isValid()) {
0117 return false;
0118 }
0119 const auto readout = sensitive.readout();
0120 return readout.isValid() && readout.name() == readoutName;
0121 }
0122
0123
0124
0125 RandomNoisePixel::SensitiveComponentGeometry
0126 makeComponentGeometry(const std::string& detectorName, int layer,
0127 const dd4hep::PlacedVolume& placement, const TGeoHMatrix& localToWorld) {
0128 auto volume = placement.volume();
0129 if (!volume.isValid() || !volume.ptr() || !volume->GetShape()) {
0130 throw std::runtime_error("RandomNoisePixel sensitive volume has no TGeo shape");
0131 }
0132 return {.detectorName = detectorName,
0133 .layer = layer,
0134 .volume = volume.ptr(),
0135 .localToWorldTransform = localToWorld};
0136 }
0137
0138
0139
0140 void
0141 collectSensitiveDescendants(const std::string& detectorName, int layer,
0142 const dd4hep::PlacedVolume& parent, const TGeoHMatrix& parentToWorld,
0143 std::string_view readoutName,
0144 std::vector<RandomNoisePixel::SensitiveComponentGeometry>& output) {
0145 auto* node = parent.ptr();
0146 if (!node) {
0147 return;
0148 }
0149
0150 for (int i = 0; i < node->GetNdaughters(); ++i) {
0151 auto* daughterNode = node->GetDaughter(i);
0152 if (!daughterNode) {
0153 continue;
0154 }
0155 dd4hep::PlacedVolume daughter{daughterNode};
0156 const auto daughterToWorld = composeTransforms(parentToWorld, placementTransform(daughter));
0157 if (hasRequestedReadout(daughter, readoutName)) {
0158 output.push_back(makeComponentGeometry(detectorName, layer, daughter, daughterToWorld));
0159 } else {
0160 collectSensitiveDescendants(detectorName, layer, daughter, daughterToWorld, readoutName,
0161 output);
0162 }
0163 }
0164 }
0165
0166
0167
0168 std::vector<RandomNoisePixel::SensitiveComponentGeometry>
0169 findSensitiveComponents(const std::string& detectorName, int layer,
0170 const dd4hep::DetElement& module, std::string_view readoutName) {
0171 const auto modulePlacement = module.placement();
0172 if (!modulePlacement.isValid()) {
0173 return {};
0174 }
0175
0176 std::vector<RandomNoisePixel::SensitiveComponentGeometry> components;
0177 const auto moduleToWorld = module.nominal().worldTransformation();
0178 if (hasRequestedReadout(modulePlacement, readoutName)) {
0179 components.push_back(
0180 makeComponentGeometry(detectorName, layer, modulePlacement, moduleToWorld));
0181 } else {
0182 collectSensitiveDescendants(detectorName, layer, modulePlacement, moduleToWorld, readoutName,
0183 components);
0184 }
0185 return components;
0186 }
0187
0188
0189 const TGeoBBox& boundingBox(const RandomNoisePixel::SensitiveComponentGeometry& component) {
0190 const auto* box = dynamic_cast<const TGeoBBox*>(component.volume->GetShape());
0191 if (!box) {
0192 throw std::runtime_error("RandomNoisePixel requires a TGeo shape with bounding-box data");
0193 }
0194 return *box;
0195 }
0196
0197
0198
0199 std::array<double, 3>
0200 referenceLocalPoint(const RandomNoisePixel::SensitiveComponentGeometry& component) {
0201 if (const auto* tube = dynamic_cast<const TGeoTubeSeg*>(component.volume->GetShape())) {
0202 const double radius = 0.5 * (tube->GetRmin() + tube->GetRmax());
0203 const double phi = 0.5 * (tube->GetPhi1() + tube->GetPhi2()) * std::numbers::pi / 180.0;
0204 return {radius * std::cos(phi), radius * std::sin(phi), 0.0};
0205 }
0206
0207 const auto& box = boundingBox(component);
0208 const auto* origin = box.GetOrigin();
0209 return {origin[0], origin[1], origin[2]};
0210 }
0211
0212
0213
0214 const RawSegmentation& selectedSegmentation(const dd4hep::Segmentation& segmentation,
0215 std::uint64_t baseVolumeID) {
0216 const RawSegmentation* selected = segmentation.segmentation();
0217 while (selected && selected->type() == "MultiSegmentation") {
0218 const auto* multi = dynamic_cast<const dd4hep::DDSegmentation::MultiSegmentation*>(selected);
0219 if (!multi) {
0220 throw std::runtime_error("RandomNoisePixel cannot access MultiSegmentation");
0221 }
0222 selected = &multi->subsegmentation(baseVolumeID);
0223 }
0224 if (!selected) {
0225 throw std::runtime_error("RandomNoisePixel readout has no segmentation");
0226 }
0227 return *selected;
0228 }
0229
0230
0231
0232 std::pair<std::int64_t, std::int64_t>
0233 indexRange(double minimum, double maximum, double pitch, double offset,
0234 const dd4hep::DDSegmentation::BitFieldElement& field) {
0235 if (!(pitch > 0.0) || !std::isfinite(pitch)) {
0236 throw std::runtime_error("RandomNoisePixel found an invalid segmentation pitch");
0237 }
0238 constexpr double indexTolerance = 1.0e-9;
0239 auto first = static_cast<std::int64_t>(std::ceil((minimum - offset) / pitch - indexTolerance));
0240 auto last = static_cast<std::int64_t>(std::floor((maximum - offset) / pitch + indexTolerance));
0241 first = std::max(first, static_cast<std::int64_t>(field.minValue()));
0242 last = std::min(last, static_cast<std::int64_t>(field.maxValue()));
0243 return {first, last};
0244 }
0245
0246
0247 bool isPureBox(const TGeoShape& shape) {
0248 return std::string_view{shape.ClassName()} == "TGeoBBox";
0249 }
0250
0251
0252
0253
0254 bool containsOrTouches(const TGeoShape& shape, const double point[3]) {
0255 return shape.Contains(point) || shape.Safety(point, false) <= 10.0 * TGeoShape::Tolerance();
0256 }
0257
0258
0259
0260 std::shared_ptr<RandomNoisePixel::PixelLayout>
0261 makeCartesianLayout(const RandomNoisePixel::SensitiveComponentGeometry& component,
0262 RandomNoisePixel::GridKind kind, std::string firstField,
0263 std::string secondField, double firstPitch, double secondPitch,
0264 double firstOffset, double secondOffset,
0265 const dd4hep::DDSegmentation::BitFieldCoder& decoder) {
0266 const auto& box = boundingBox(component);
0267 const auto* origin = box.GetOrigin();
0268 const bool xy = kind == RandomNoisePixel::GridKind::CartesianXY;
0269 const double firstCenter = origin[0];
0270 const double firstHalf = box.GetDX();
0271 const double secondCenter = xy ? origin[1] : origin[2];
0272 const double secondHalf = xy ? box.GetDY() : box.GetDZ();
0273
0274 const auto [firstMin, firstMax] = indexRange(firstCenter - firstHalf, firstCenter + firstHalf,
0275 firstPitch, firstOffset, decoder[firstField]);
0276 const auto [secondMin, secondMax] =
0277 indexRange(secondCenter - secondHalf, secondCenter + secondHalf, secondPitch, secondOffset,
0278 decoder[secondField]);
0279
0280 if (firstMax < firstMin || secondMax < secondMin) {
0281 throw std::runtime_error("RandomNoisePixel sensitive component contains no pixel centers");
0282 }
0283
0284 auto layout = std::make_shared<RandomNoisePixel::PixelLayout>();
0285 layout->kind = kind;
0286 layout->firstField = std::move(firstField);
0287 layout->secondField = std::move(secondField);
0288
0289
0290 if (isPureBox(*component.volume->GetShape())) {
0291 layout->rectangular = true;
0292 layout->firstMin = firstMin;
0293 layout->firstMax = firstMax;
0294 layout->secondMin = secondMin;
0295 layout->secondMax = secondMax;
0296 const auto firstCount = static_cast<std::uint64_t>(firstMax - firstMin + 1);
0297 const auto secondCount = static_cast<std::uint64_t>(secondMax - secondMin + 1);
0298 if (firstCount > std::numeric_limits<std::uint64_t>::max() / secondCount) {
0299 throw std::overflow_error("RandomNoisePixel pixel count overflow");
0300 }
0301 layout->totalPixels = firstCount * secondCount;
0302 return layout;
0303 }
0304
0305
0306 const auto* shape = component.volume->GetShape();
0307 auto contains = [&](std::int64_t firstIndex, std::int64_t secondIndex) {
0308 const double first = firstOffset + firstPitch * static_cast<double>(firstIndex);
0309 const double second = secondOffset + secondPitch * static_cast<double>(secondIndex);
0310 double local[3] = {origin[0], origin[1], origin[2]};
0311 local[0] = first;
0312 if (xy) {
0313 local[1] = second;
0314 } else {
0315 local[2] = second;
0316 }
0317 return containsOrTouches(*shape, local);
0318 };
0319
0320
0321
0322 const auto middleGuess = std::clamp(
0323 static_cast<std::int64_t>(std::llround((firstCenter - firstOffset) / firstPitch)), firstMin,
0324 firstMax);
0325 std::uint64_t cumulative = 0;
0326 for (std::int64_t second = secondMin; second <= secondMax; ++second) {
0327 if (!contains(middleGuess, second)) {
0328 continue;
0329 }
0330
0331 std::int64_t low = firstMin;
0332 std::int64_t high = middleGuess;
0333 while (low < high) {
0334 const auto middle = low + (high - low) / 2;
0335 if (contains(middle, second)) {
0336 high = middle;
0337 } else {
0338 low = middle + 1;
0339 }
0340 }
0341 const auto rowFirst = low;
0342
0343 low = middleGuess;
0344 high = firstMax;
0345 while (low < high) {
0346 const auto middle = low + (high - low + 1) / 2;
0347 if (contains(middle, second)) {
0348 low = middle;
0349 } else {
0350 high = middle - 1;
0351 }
0352 }
0353 const auto rowLast = low;
0354 const auto rowCount = static_cast<std::uint64_t>(rowLast - rowFirst + 1);
0355 if (cumulative > std::numeric_limits<std::uint64_t>::max() - rowCount) {
0356 throw std::overflow_error("RandomNoisePixel pixel row count overflow");
0357 }
0358
0359 cumulative += rowCount;
0360 layout->rows.push_back({second, rowFirst, rowLast, cumulative});
0361 }
0362
0363 if (layout->rows.empty()) {
0364 throw std::runtime_error("RandomNoisePixel found no segmentation centers inside TGeo shape");
0365 }
0366 layout->totalPixels = cumulative;
0367 return layout;
0368 }
0369
0370
0371
0372
0373 std::shared_ptr<RandomNoisePixel::PixelLayout>
0374 makeCylindricalLayout(const RandomNoisePixel::SensitiveComponentGeometry& component,
0375 const dd4hep::DDSegmentation::CylindricalGridPhiZ& grid,
0376 const dd4hep::DDSegmentation::BitFieldCoder& decoder) {
0377
0378 const auto center = referenceLocalPoint(component);
0379 const double centerPhi = std::atan2(center[1], center[0]);
0380
0381 double phiMin = std::numeric_limits<double>::max();
0382 double phiMax = std::numeric_limits<double>::lowest();
0383 double zMin = std::numeric_limits<double>::max();
0384 double zMax = std::numeric_limits<double>::lowest();
0385 auto includeLocalPoint = [&](double x, double y, double z) {
0386 const double phi =
0387 centerPhi + std::remainder(std::atan2(y, x) - centerPhi, 2.0 * std::numbers::pi);
0388 phiMin = std::min(phiMin, phi);
0389 phiMax = std::max(phiMax, phi);
0390 zMin = std::min(zMin, z);
0391 zMax = std::max(zMax, z);
0392 };
0393
0394
0395
0396
0397 if (const auto* tube = dynamic_cast<const TGeoTubeSeg*>(component.volume->GetShape())) {
0398 for (double radius : {tube->GetRmin(), tube->GetRmax()}) {
0399 for (double phiDegrees : {tube->GetPhi1(), tube->GetPhi2()}) {
0400 const double phi = phiDegrees * std::numbers::pi / 180.0;
0401 for (double z : {-tube->GetDz(), tube->GetDz()}) {
0402 includeLocalPoint(radius * std::cos(phi), radius * std::sin(phi), z);
0403 }
0404 }
0405 }
0406 } else {
0407 const auto& box = boundingBox(component);
0408 const auto* origin = box.GetOrigin();
0409 for (double sx : {-1.0, 1.0}) {
0410 for (double sy : {-1.0, 1.0}) {
0411 for (double sz : {-1.0, 1.0}) {
0412 includeLocalPoint(origin[0] + sx * box.GetDX(), origin[1] + sy * box.GetDY(),
0413 origin[2] + sz * box.GetDZ());
0414 }
0415 }
0416 }
0417 }
0418
0419
0420 const auto [phiIndexMin, phiIndexMax] = indexRange(
0421 phiMin, phiMax, grid.gridSizePhi(), grid.offsetPhi(), decoder[grid.fieldNamePhi()]);
0422 const auto [zIndexMin, zIndexMax] =
0423 indexRange(zMin, zMax, grid.gridSizeZ(), grid.offsetZ(), decoder[grid.fieldNameZ()]);
0424 if (phiIndexMax < phiIndexMin || zIndexMax < zIndexMin) {
0425 throw std::runtime_error("RandomNoisePixel cylindrical component contains no pixels");
0426 }
0427
0428 auto layout = std::make_shared<RandomNoisePixel::PixelLayout>();
0429 layout->kind = RandomNoisePixel::GridKind::CylindricalPhiZ;
0430 layout->firstField = grid.fieldNamePhi();
0431 layout->secondField = grid.fieldNameZ();
0432 layout->rectangular = true;
0433 layout->firstMin = phiIndexMin;
0434 layout->firstMax = phiIndexMax;
0435 layout->secondMin = zIndexMin;
0436 layout->secondMax = zIndexMax;
0437 const auto phiCount = static_cast<std::uint64_t>(phiIndexMax - phiIndexMin + 1);
0438 const auto zCount = static_cast<std::uint64_t>(zIndexMax - zIndexMin + 1);
0439 if (phiCount > std::numeric_limits<std::uint64_t>::max() / zCount) {
0440 throw std::overflow_error("RandomNoisePixel cylindrical pixel count overflow");
0441 }
0442 layout->totalPixels = phiCount * zCount;
0443 return layout;
0444 }
0445
0446
0447
0448 std::pair<std::int64_t, std::int64_t> pixelIndices(const RandomNoisePixel::PixelLayout& layout,
0449 std::uint64_t linearIndex) {
0450 if (linearIndex >= layout.totalPixels) {
0451 throw std::out_of_range("RandomNoisePixel linear pixel index is out of range");
0452 }
0453 if (layout.rectangular) {
0454 const auto firstCount = static_cast<std::uint64_t>(layout.firstMax - layout.firstMin + 1);
0455 return {layout.firstMin + static_cast<std::int64_t>(linearIndex % firstCount),
0456 layout.secondMin + static_cast<std::int64_t>(linearIndex / firstCount)};
0457 }
0458
0459 const auto row =
0460 std::upper_bound(layout.rows.begin(), layout.rows.end(), linearIndex,
0461 [](std::uint64_t value, const RandomNoisePixel::PixelRow& candidate) {
0462 return value < candidate.cumulativeEnd;
0463 });
0464 if (row == layout.rows.end()) {
0465 throw std::out_of_range("RandomNoisePixel could not resolve compact pixel row");
0466 }
0467 const auto previousEnd = row == layout.rows.begin() ? 0 : (row - 1)->cumulativeEnd;
0468 return {row->firstMin + static_cast<std::int64_t>(linearIndex - previousEnd), row->secondIndex};
0469 }
0470
0471 }
0472
0473
0474 void RandomNoisePixel::init() {
0475
0476 m_components.clear();
0477 m_layers.clear();
0478
0479 if (!m_cfg.addNoise) {
0480 debug("RandomNoisePixel '{}': disabled by configuration; skipping geometry cache", name());
0481 return;
0482 }
0483 if (!(m_cfg.noise_rate_per_pixel_per_event >= 0.0 &&
0484 m_cfg.noise_rate_per_pixel_per_event <= 1.0)) {
0485 throw std::invalid_argument(
0486 "RandomNoisePixel noise_rate_per_pixel_per_event must be within [0, 1]");
0487 }
0488
0489
0490
0491 const std::scoped_lock geometryLock{geometryInitializationMutex()};
0492
0493
0494 const auto& geo = algorithms::GeoSvc::instance();
0495 m_dd4hepGeo = geo.detector();
0496 m_converter = geo.cellIDPositionConverter();
0497 if (!m_dd4hepGeo || !m_converter) {
0498 throw std::runtime_error("RandomNoisePixel requires DD4hep geometry and cell-ID services");
0499 }
0500
0501 m_readout = m_dd4hepGeo->readout(m_cfg.readout_name);
0502 if (!m_readout.isValid()) {
0503 throw std::invalid_argument("RandomNoisePixel invalid readout: " + m_cfg.readout_name);
0504 }
0505
0506
0507
0508 {
0509 std::vector<SensitiveComponentGeometry> componentGeometry;
0510 for (const auto& [name, detector] : m_dd4hepGeo->detectors()) {
0511 const auto sensitive = m_dd4hepGeo->sensitiveDetector(name);
0512 if (sensitive.isValid() && sensitive.readout().isValid() &&
0513 sensitive.readout().name() == m_cfg.readout_name) {
0514 collectDetectorComponents(detector, componentGeometry);
0515 }
0516 }
0517
0518
0519 cachePixelLayouts(componentGeometry);
0520 }
0521
0522
0523 buildLayers();
0524 info("RandomNoisePixel '{}': cached {} sensitive components and {} layer groups for readout '{}'",
0525 name(), m_components.size(), m_layers.size(), m_cfg.readout_name);
0526 }
0527
0528
0529 void RandomNoisePixel::collectDetectorComponents(
0530 const dd4hep::DetElement& detector,
0531 std::vector<SensitiveComponentGeometry>& componentGeometry) {
0532 for (const auto& [_, layer] : detector.children()) {
0533 collectLayerComponents(detector.name(), layer, componentGeometry);
0534 }
0535 }
0536
0537
0538 void RandomNoisePixel::collectLayerComponents(
0539 const std::string& detectorName, const dd4hep::DetElement& layer,
0540 std::vector<SensitiveComponentGeometry>& componentGeometry) {
0541 for (const auto& [_, module] : layer.children()) {
0542
0543 const auto layerID = layerFromDetElement(layer, module);
0544 if (layerID) {
0545 auto components = findSensitiveComponents(detectorName, *layerID, module, m_cfg.readout_name);
0546 if (!components.empty()) {
0547 componentGeometry.insert(componentGeometry.end(),
0548 std::make_move_iterator(components.begin()),
0549 std::make_move_iterator(components.end()));
0550 continue;
0551 }
0552 }
0553
0554
0555 for (const auto& [__, child] : module.children()) {
0556 const auto childLayerID = layerID ? layerID : layerFromDetElement(layer, child);
0557 if (!childLayerID) {
0558 continue;
0559 }
0560 auto childComponents =
0561 findSensitiveComponents(detectorName, *childLayerID, child, m_cfg.readout_name);
0562 componentGeometry.insert(componentGeometry.end(),
0563 std::make_move_iterator(childComponents.begin()),
0564 std::make_move_iterator(childComponents.end()));
0565 }
0566 }
0567 }
0568
0569
0570 void RandomNoisePixel::cachePixelLayouts(
0571 const std::vector<SensitiveComponentGeometry>& componentGeometry) {
0572
0573 const auto segmentation = m_readout.segmentation();
0574 const auto* decoder = m_readout.idSpec().decoder();
0575 if (!segmentation.isValid() || !decoder) {
0576 throw std::runtime_error("RandomNoisePixel readout has no segmentation or ID decoder");
0577 }
0578
0579
0580
0581 struct SharedLayout {
0582 const TGeoVolume* volume = nullptr;
0583 const RawSegmentation* segmentation = nullptr;
0584 std::shared_ptr<const PixelLayout> layout;
0585 };
0586 std::vector<SharedLayout> sharedCartesianLayouts;
0587
0588 m_components.reserve(componentGeometry.size());
0589 for (const auto& geometry : componentGeometry) {
0590
0591
0592 const auto reference = referenceLocalPoint(geometry);
0593 double local[3] = {reference[0], reference[1], reference[2]};
0594 if (!geometry.volume->GetShape()->Contains(local)) {
0595 throw std::runtime_error("RandomNoisePixel reference point is outside sensitive volume '" +
0596 std::string{geometry.volume->GetName()} + "' with shape '" +
0597 geometry.volume->GetShape()->ClassName() + "'");
0598 }
0599 double globalCoordinates[3];
0600 geometry.localToWorldTransform.LocalToMaster(local, globalCoordinates);
0601 const dd4hep::Position global{globalCoordinates[0], globalCoordinates[1], globalCoordinates[2]};
0602 const auto referenceCell = m_converter->cellID(global);
0603 if (!m_converter->findContext(referenceCell)) {
0604 throw std::runtime_error("RandomNoisePixel could not resolve a component reference cell ID");
0605 }
0606 const auto baseVolumeID = segmentation.volumeID(referenceCell);
0607 SensitiveComponent component;
0608 component.detectorName = geometry.detectorName;
0609 component.layer = geometry.layer;
0610 component.baseVolumeID = baseVolumeID;
0611
0612
0613 const auto& selected = selectedSegmentation(segmentation, baseVolumeID);
0614 const auto findShared = [&]() -> std::shared_ptr<const PixelLayout> {
0615 for (const auto& cached : sharedCartesianLayouts) {
0616 if (cached.volume == geometry.volume && cached.segmentation == &selected) {
0617 return cached.layout;
0618 }
0619 }
0620 return {};
0621 };
0622
0623
0624 if (const auto* grid =
0625 dynamic_cast<const dd4hep::DDSegmentation::CartesianGridXY*>(&selected)) {
0626 component.layout = findShared();
0627 if (!component.layout) {
0628 component.layout = makeCartesianLayout(
0629 geometry, GridKind::CartesianXY, grid->fieldNameX(), grid->fieldNameY(),
0630 grid->gridSizeX(), grid->gridSizeY(), grid->offsetX(), grid->offsetY(), *decoder);
0631 sharedCartesianLayouts.push_back({geometry.volume, &selected, component.layout});
0632 }
0633 } else if (const auto* grid =
0634 dynamic_cast<const dd4hep::DDSegmentation::CartesianGridXZ*>(&selected)) {
0635 component.layout = findShared();
0636 if (!component.layout) {
0637 component.layout = makeCartesianLayout(
0638 geometry, GridKind::CartesianXZ, grid->fieldNameX(), grid->fieldNameZ(),
0639 grid->gridSizeX(), grid->gridSizeZ(), grid->offsetX(), grid->offsetZ(), *decoder);
0640 sharedCartesianLayouts.push_back({geometry.volume, &selected, component.layout});
0641 }
0642 } else if (const auto* grid =
0643 dynamic_cast<const dd4hep::DDSegmentation::CylindricalGridPhiZ*>(&selected)) {
0644 component.layout = makeCylindricalLayout(geometry, *grid, *decoder);
0645 } else {
0646 throw std::runtime_error("RandomNoisePixel unsupported segmentation type: " +
0647 selected.type());
0648 }
0649
0650
0651
0652
0653 component.pixelCount = component.layout->totalPixels;
0654 std::array<std::uint64_t, 5> sampleLinearIndices = {
0655 0, component.pixelCount / 4, component.pixelCount / 2,
0656 component.pixelCount / 2 + component.pixelCount / 4, component.pixelCount - 1};
0657 std::sort(sampleLinearIndices.begin(), sampleLinearIndices.end());
0658 const auto uniqueEnd = std::unique(sampleLinearIndices.begin(), sampleLinearIndices.end());
0659
0660
0661
0662
0663
0664 for (auto sample = sampleLinearIndices.begin(); sample != uniqueEnd; ++sample) {
0665 const auto [sampleFirst, sampleSecond] = pixelIndices(*component.layout, *sample);
0666 auto sampleID = component.baseVolumeID;
0667 decoder->set(sampleID, component.layout->firstField, sampleFirst);
0668 decoder->set(sampleID, component.layout->secondField, sampleSecond);
0669 if (!m_converter->findContext(sampleID)) {
0670 throw std::runtime_error("RandomNoisePixel generated an invalid sample cell ID");
0671 }
0672
0673 const auto sampleGlobal = m_converter->position(sampleID);
0674 double sampleGlobalCoordinates[3] = {sampleGlobal.x(), sampleGlobal.y(), sampleGlobal.z()};
0675 double sampleLocalCoordinates[3];
0676 geometry.localToWorldTransform.MasterToLocal(sampleGlobalCoordinates, sampleLocalCoordinates);
0677 if (!containsOrTouches(*geometry.volume->GetShape(), sampleLocalCoordinates)) {
0678 throw std::runtime_error(
0679 "RandomNoisePixel sample cell center does not return inside sensitive volume '" +
0680 std::string{geometry.volume->GetName()} + "' in detector '" + component.detectorName +
0681 "' layer " + std::to_string(component.layer) + "; pixel indices=(" +
0682 std::to_string(sampleFirst) + ", " + std::to_string(sampleSecond) +
0683 "), returned local position=(" + std::to_string(sampleLocalCoordinates[0]) + ", " +
0684 std::to_string(sampleLocalCoordinates[1]) + ", " +
0685 std::to_string(sampleLocalCoordinates[2]) +
0686 "); check segmentation coordinates and placement transforms");
0687 }
0688 }
0689 m_components.push_back(std::move(component));
0690 }
0691 }
0692
0693
0694 void RandomNoisePixel::buildLayers() {
0695 for (std::size_t componentIndex = 0; componentIndex < m_components.size(); ++componentIndex) {
0696 const auto& component = m_components[componentIndex];
0697 auto layer =
0698 std::find_if(m_layers.begin(), m_layers.end(), [&](const LayerGeometry& candidate) {
0699 return candidate.detectorName == component.detectorName &&
0700 candidate.layer == component.layer;
0701 });
0702 if (layer == m_layers.end()) {
0703 m_layers.push_back({component.detectorName, component.layer, {}, {}, 0});
0704 layer = std::prev(m_layers.end());
0705 }
0706 if (layer->totalPixels > std::numeric_limits<std::uint64_t>::max() - component.pixelCount) {
0707 throw std::overflow_error("RandomNoisePixel layer pixel count overflow");
0708 }
0709
0710
0711 layer->totalPixels += component.pixelCount;
0712 layer->componentIndices.push_back(componentIndex);
0713 layer->cumulativePixels.push_back(layer->totalPixels);
0714 }
0715
0716 for (const auto& layer : m_layers) {
0717 info("RandomNoisePixel geometry: detector='{}' layer={} components={} pixels={} "
0718 "expected_hits={}",
0719 layer.detectorName, layer.layer, layer.componentIndices.size(), layer.totalPixels,
0720 m_cfg.noise_rate_per_pixel_per_event * static_cast<double>(layer.totalPixels));
0721 }
0722 }
0723
0724
0725 std::uint64_t
0726 RandomNoisePixel::seedFromEventHeader(const edm4hep::EventHeaderCollection& headers) const {
0727 if (headers.empty()) {
0728 throw std::runtime_error("RandomNoisePixel requires a non-empty EventHeader collection");
0729 }
0730 return m_uid.getUniqueID(headers, name());
0731 }
0732
0733
0734 std::uint64_t RandomNoisePixel::randomCellID(const SensitiveComponent& component,
0735 std::mt19937_64& rng) const {
0736 std::uniform_int_distribution<std::uint64_t> pickPixel(0, component.pixelCount - 1);
0737 const auto [firstIndex, secondIndex] = pixelIndices(*component.layout, pickPixel(rng));
0738 auto cellID = component.baseVolumeID;
0739 const auto* decoder = m_readout.idSpec().decoder();
0740 decoder->set(cellID, component.layout->firstField, firstIndex);
0741 decoder->set(cellID, component.layout->secondField, secondIndex);
0742 return cellID;
0743 }
0744
0745
0746 void RandomNoisePixel::addNoiseHitsForLayer(
0747 const LayerGeometry& layer, std::map<std::uint64_t, edm4eic::MutableRawTrackerHit>& hitMap,
0748 std::mt19937_64& rng) const {
0749 if (layer.totalPixels == 0 || m_cfg.noise_rate_per_pixel_per_event == 0.0) {
0750 return;
0751 }
0752
0753
0754 const double mean = m_cfg.noise_rate_per_pixel_per_event * static_cast<double>(layer.totalPixels);
0755 std::poisson_distribution<std::uint64_t> poisson(mean);
0756 const auto requested = poisson(rng);
0757 const auto maxAttempts = std::max<std::uint64_t>(100, requested * 20);
0758 std::uniform_int_distribution<std::uint64_t> pickLayerPixel(0, layer.totalPixels - 1);
0759
0760 std::uint64_t created = 0;
0761 std::uint64_t attempts = 0;
0762
0763
0764 while (created < requested && attempts < maxAttempts) {
0765 ++attempts;
0766 const auto selectedPixel = pickLayerPixel(rng);
0767 const auto componentPosition = std::upper_bound(layer.cumulativePixels.begin(),
0768 layer.cumulativePixels.end(), selectedPixel);
0769 const auto componentOffset =
0770 static_cast<std::size_t>(componentPosition - layer.cumulativePixels.begin());
0771 const auto& component = m_components[layer.componentIndices[componentOffset]];
0772 const auto cellID = randomCellID(component, rng);
0773
0774
0775 if (hitMap.contains(cellID)) {
0776 continue;
0777 }
0778
0779
0780 edm4eic::MutableRawTrackerHit hit;
0781 hit.setCellID(cellID);
0782 hit.setCharge(1.0e6);
0783 hit.setTimeStamp(0);
0784 hitMap.emplace(cellID, hit);
0785 ++created;
0786 }
0787
0788 if (created < requested) {
0789 warning("RandomNoisePixel '{}': created {}/{} requested hits for detector '{}' layer {}",
0790 name(), created, requested, layer.detectorName, layer.layer);
0791 }
0792 }
0793
0794
0795 void RandomNoisePixel::process(const Input& in, const Output& out) const {
0796 auto [outHits] = out;
0797 const auto [headers] = in;
0798
0799 if (!m_cfg.addNoise) {
0800 return;
0801 }
0802 if (!headers) {
0803 throw std::runtime_error("RandomNoisePixel requires an EventHeader collection");
0804 }
0805 if (m_layers.empty()) {
0806 throw std::runtime_error("RandomNoisePixel has no cached sensitive geometry");
0807 }
0808
0809
0810 std::mt19937_64 rng(seedFromEventHeader(*headers));
0811
0812
0813 std::map<std::uint64_t, edm4eic::MutableRawTrackerHit> noiseHits;
0814 for (const auto& layer : m_layers) {
0815 addNoiseHitsForLayer(layer, noiseHits, rng);
0816 }
0817
0818 for (const auto& [_, hit] : noiseHits) {
0819 outHits->push_back(hit);
0820 }
0821 }
0822
0823 }