Warning, /EICrecon/docs/design/svt_pixel_noise_injection.md is written in an unsupported language. File is not indexed.
0001 # RandomNoisePixel: silicon noise injection explained
0002
0003 ## 1. What problem does this algorithm solve?
0004
0005 A silicon tracker contains many electronic pixels. Even when no charged particle crosses a pixel,
0006 electronics noise can occasionally make that pixel appear to fire. `RandomNoisePixel` adds these
0007 noise-only hits to an EICrecon event.
0008
0009 The model starts from one probability:
0010
0011 ```text
0012 p = noiseRate
0013 ```
0014
0015 The default is `p = 2e-7` per pixel per event. In other words, a particular pixel has a probability
0016 of approximately two in ten million of producing a noise hit in one event. The same `p` is used
0017 throughout the SVT. Layers with more pixels have more expected noise hits.
0018
0019 The current implementation supports:
0020
0021 | Detector system | Segmentation coordinates | Sensor treatment |
0022 | --- | --- | --- |
0023 | BVTX | cylindrical phi-z | rectangular phi and z index ranges |
0024 | BTRK | Cartesian x-y | rectangular x and y index ranges |
0025 | ECTRK | Cartesian x-z | row-by-row trapezoid ranges |
0026
0027 The implementation is in
0028 [`RandomNoisePixel.cc`](../EICrecon/src/algorithms/digi/RandomNoisePixel.cc), with data structures in
0029 [`RandomNoisePixel.h`](../EICrecon/src/algorithms/digi/RandomNoisePixel.h) and user parameters in
0030 [`RandomNoisePixelConfig.h`](../EICrecon/src/algorithms/digi/RandomNoisePixelConfig.h).
0031
0032 ## 2. The statistical model
0033
0034 Let:
0035
0036 - `p` be the noise probability per pixel per event;
0037 - `N_m` be the number of addressable pixels on sensitive component `m`;
0038 - `N_l = sum_m N_m` be the total number of pixels in layer `l`.
0039
0040 The expected number of noise hits in a layer is
0041
0042 ```text
0043 lambda_l = p * N_l.
0044 ```
0045
0046 The event-by-event hit count is drawn from
0047
0048 ```text
0049 K_l ~ Poisson(lambda_l).
0050 ```
0051
0052 The exact independent-pixel model would be `Binomial(N_l, p)`. The Poisson model is an excellent
0053 approximation because `p` is very small and `N_l` is very large. It also avoids a random trial for
0054 every pixel.
0055
0056 ### Worked rate example
0057
0058 Suppose one layer has two sensitive components:
0059
0060 ```text
0061 component A: N_A = 1,000,000 pixels
0062 component B: N_B = 3,000,000 pixels
0063 ```
0064
0065 For `p = 2e-7`,
0066
0067 ```text
0068 N_l = 4,000,000 pixels
0069 lambda_l = 2e-7 * 4,000,000 = 0.8 noise hits/event.
0070 ```
0071
0072 The event may contain zero, one, two, or more noise hits; `0.8` is the long-run average. Component A
0073 is selected with probability `1/4`, and component B with probability `3/4`. Every individual pixel
0074 therefore has the same probability of being selected.
0075
0076 ## 3. The two phases
0077
0078 `RandomNoisePixel` separates geometry work from event work:
0079
0080 ```text
0081 DD4hep geometry
0082 |
0083 v
0084 init(): discover sensors and build compact pixel layouts
0085 |
0086 v
0087 cached IDs, pixel ranges, and layer totals
0088 |
0089 v
0090 process(): draw noise counts and create hits for each event
0091 ```
0092
0093 Geometry navigation is relatively expensive and involves shared TGeo state. It is done once in
0094 `init()`. Event processing uses cached integer information and does not navigate TGeo.
0095
0096 ## 4. Initialization: understand the detector once
0097
0098 ### Step 1: validate the configuration
0099
0100 If `addNoise` is false, initialization stops without building a cache. Otherwise, the configured
0101 rate must satisfy `0 <= p <= 1`, and the requested DD4hep readout must exist. Each factory instance
0102 uses one readout, such as `VertexBarrelHits`, `SiBarrelHits`, or `TrackerEndcapHits`.
0103
0104 ### Step 2: find sensitive silicon components
0105
0106 The code traverses the DD4hep detector hierarchy and keeps only sensitive placements whose readout
0107 matches the factory configuration. A module may itself be sensitive, or it may contain sensitive
0108 daughter volumes. The traversal handles both cases.
0109
0110 For every sensitive placement, initialization temporarily records:
0111
0112 - detector name and layer number;
0113 - its `TGeoVolume` and shape;
0114 - its local-to-world `TGeoHMatrix` transformation.
0115
0116 The transform identifies the placed sensor and validates the final cell position. It is discarded
0117 after initialization, so it consumes no persistent event-time cache memory.
0118
0119 The transform includes two distinct steps:
0120
0121 ```text
0122 sensitive-solid local point
0123 -> add/rotate by the component placement inside its module
0124 -> apply the module and all ancestor placements to reach world coordinates
0125 ```
0126
0127 This distinction matters when, for example, left and right silicon pieces are offset from the module
0128 center. The code composes the matrices once; it neither drops the component offset nor adds it a
0129 second time.
0130
0131 ### Step 3: determine the sensor's base volume ID
0132
0133 A cell ID contains two kinds of information:
0134
0135 ```text
0136 placement fields: detector, layer, module, sensor, ...
0137 pixel fields: x/y, x/z, or phi/z indices
0138 ```
0139
0140 The code chooses a point inside the sensor, transforms it from local to global coordinates, and asks
0141 `CellIDPositionConverter` for its complete cell ID. The segmentation's `volumeID()` operation
0142 removes the local pixel fields, leaving `baseVolumeID`.
0143
0144 During event processing, the algorithm starts from this base ID and writes new pixel indices into
0145 it. Packed numerical cell IDs are never assumed to form one continuous integer interval.
0146
0147 ### Step 4: read the actual segmentation
0148
0149 Pixel pitch and offset come directly from DD4hep. The algorithm has no separate 20 micrometre pitch
0150 parameter. If the segmentation changes, the cached pixel count changes automatically. The supplied
0151 per-pixel noise rate is not silently rescaled.
0152
0153 For a `MultiSegmentation`, the placement fields select the correct concrete sub-segmentation before
0154 the pixel layout is calculated.
0155
0156 ## 5. Compact representations of valid pixels
0157
0158 The algorithm never stores a cell ID for every physical pixel. Instead, it stores index ranges.
0159
0160 ### Rectangular Cartesian sensor
0161
0162 For a rectangle, all pixel centers inside these limits are valid:
0163
0164 ```text
0165 firstMin <= firstIndex <= firstMax
0166 secondMin <= secondIndex <= secondMax
0167 ```
0168
0169 The pixel count is
0170
0171 ```text
0172 N = (firstMax - firstMin + 1) * (secondMax - secondMin + 1).
0173 ```
0174
0175 For example, x indices `2...5` and y indices `10...12` describe `4 * 3 = 12` pixels. Only the four
0176 limits and total count are stored.
0177
0178 ### Trapezoidal sensor
0179
0180 An ECTRK trapezoid does not fill its rectangular bounding box. Its valid x range changes with z.
0181 The code examines each candidate z row and checks pixel centers with `TGeoShape::Contains()`.
0182
0183 For each non-empty row it stores:
0184
0185 ```text
0186 z index minimum x maximum x cumulative pixel total
0187 ------- --------- --------- ----------------------
0188 10 -1 1 3
0189 11 -2 2 8
0190 12 -3 3 15
0191 ```
0192
0193 The left and right boundaries are found with binary searches. This works for the current convex,
0194 centered trapezoids, where each row contains one continuous x interval.
0195
0196 To select a pixel, draw one flat index from `0...14`. The cumulative totals identify its row. Flat
0197 index 6 belongs to the second row because `3 <= 6 < 8`. Its offset is `6 - 3 = 3`, giving
0198
0199 ```text
0200 x = xMin + 3 = -2 + 3 = 1
0201 z = 11
0202 ```
0203
0204 Every valid pixel has equal probability even though rows have different widths. Empty rows and
0205 invalid bounding-box corners are never sampled.
0206
0207 ### Cylindrical phi-z sensor
0208
0209 For `CylindricalGridPhiZ`, phi and z are coordinates in the sensitive volume's local cylindrical
0210 frame. The code obtains local bounds directly from the sensor shape and converts them into discrete
0211 indices. DD4hep applies the component's placement transform later when converting the cell ID into a
0212 global hit position; applying that transform while building the indices would place the sensor
0213 twice.
0214
0215 Phi requires care near the `-pi`/`+pi` boundary. Angular differences are measured around the
0216 sensor's central phi so that a sensor crossing this boundary is not mistaken for one spanning almost
0217 the entire circle.
0218
0219 ### Pixel-center convention
0220
0221 A pixel is counted when its segmentation-defined center is inside the sensitive TGeo shape.
0222 Partial geometrical overlap at a sensor edge is not treated as a fractional pixel. This deterministic
0223 convention treats pixels as electronic channels.
0224
0225 Initialization checks representative pixels at the beginning, quarters, middle, and end of every
0226 placed component's address range. Each cell ID is converted by DD4hep to a global center and then
0227 transformed back into that exact sensitive solid. The point must be inside the TGeo shape. A point
0228 on a face is accepted only within ROOT's geometry tolerance, which avoids false failures from
0229 floating-point round trips while still detecting incorrect offsets or rotations.
0230
0231 ## 6. Sharing layouts between repeated sensors
0232
0233 Many placed sensors have the same logical TGeo volume and Cartesian segmentation. Their pixel
0234 ranges are identical even though their placement IDs differ. These components share one immutable
0235 `PixelLayout` through a `shared_ptr`.
0236
0237 Each placed component retains only:
0238
0239 - detector name and layer;
0240 - `baseVolumeID`;
0241 - a shared pixel-layout pointer;
0242 - its pixel count.
0243
0244 The full transform and TGeo volume pointer are initialization-only data. The cache therefore scales
0245 mainly with the number of components plus rows in unique trapezoid layouts, not with the total number
0246 of pixels.
0247
0248 ## 7. Building layer selection tables
0249
0250 Components are grouped by detector name and layer number. Each layer stores component indices and
0251 cumulative pixel totals. For component sizes
0252
0253 ```text
0254 N_0 = 100, N_1 = 300, N_2 = 200,
0255 ```
0256
0257 the cumulative table is `[100, 400, 600]`. A layer-wide random number in `0...599` selects:
0258
0259 - component 0 for `0...99`;
0260 - component 1 for `100...399`;
0261 - component 2 for `400...599`.
0262
0263 `std::upper_bound` finds the component in logarithmic time. The selection probability is
0264 automatically proportional to component pixel count.
0265
0266 ## 8. Processing one event
0267
0268 ### Step 1: create a reproducible random-number generator
0269
0270 The run and event identity come from the required `EventHeader`. `UniqueIDGenSvc` combines this
0271 identity with the algorithm name to make a deterministic seed. There is no silent fallback seed.
0272
0273 ### Step 2: draw one count per layer
0274
0275 For each layer:
0276
0277 ```text
0278 mean = p * layer.totalPixels
0279 requestedHits ~ Poisson(mean).
0280 ```
0281
0282 This is much cheaper than visiting every sensor or pixel in every event.
0283
0284 ### Step 3: select a component and pixel
0285
0286 For each requested hit:
0287
0288 1. Draw a uniform layer-wide pixel number.
0289 2. Use the cumulative table to select a component with probability `N_m/N_l`.
0290 3. Draw a uniform pixel index from that component's compact layout.
0291 4. Convert the flat pixel index into two segmentation indices.
0292 5. Copy `baseVolumeID` and set those fields with the DD4hep bit-field coder.
0293
0294 The first draw chooses a correctly weighted component. The second chooses a uniform pixel inside
0295 that component. Together they give uniform sampling over all layer pixels.
0296
0297 ### Step 4: reject duplicate pixels
0298
0299 Hits are stored in a map keyed by cell ID. If the same pixel is selected twice in one event, the
0300 second selection is retried. At the default occupancy, duplicates are extremely rare.
0301
0302 Retries are bounded so an unexpectedly high occupancy cannot create an infinite loop. A warning is
0303 emitted if the requested number of unique hits cannot be produced.
0304
0305 ### Step 5: create `RawTrackerHit` objects
0306
0307 Each accepted pixel becomes an `edm4eic::RawTrackerHit`. The current charge and timestamp are
0308
0309 ```text
0310 charge = 1.0e6
0311 timestamp = 0
0312 ```
0313
0314 The map is iterated in increasing cell-ID order, giving deterministic output ordering.
0315
0316 ## 9. Computational scaling
0317
0318 Let `M` be the number of sensitive components, `R` the number of cached rows across unique
0319 non-rectangular layouts, `L` the number of layer groups, and `K` the generated noise-hit count.
0320
0321 | Operation | Scaling |
0322 | --- | --- |
0323 | Geometry traversal during initialization | `O(M)` |
0324 | Rectangular layout construction | `O(1)` per unique layout |
0325 | Trapezoid layout construction | `O(R)` plus boundary searches |
0326 | Persistent cache | `O(M + R)` |
0327 | Event count draws | `O(L)` |
0328 | Event hit generation | `O(K log M_layer)` |
0329
0330 There is no array proportional to the total number of pixels and no per-event loop over every
0331 component. This is why the method remains practical for 100,000 or more sensitive components.
0332
0333 ## 10. Assumptions and limitations
0334
0335 1. Detector geometry and alignment remain static after `init()`.
0336 2. Unsupported segmentation types raise an error instead of using a silent area approximation.
0337 3. The row algorithm assumes a convex, centered shape with at most one valid interval per row. A
0338 non-convex sensor or one with holes would require multiple spans per row.
0339 4. Pixel centers define active channels at boundaries.
0340 5. The algorithm models occupancy, not pulse-height or timing distributions.
0341 6. The Poisson approximation is intended for sparse occupancies such as `2e-7`.
0342
0343 ## 11. Function map for reading the source
0344
0345 Read the implementation in this order:
0346
0347 | Function or structure | Purpose |
0348 | --- | --- |
0349 | `PixelLayout`, `PixelRow` | Represent valid segmentation indices compactly |
0350 | `init()` | Top-level cache construction |
0351 | `collectDetectorComponents()` | Enter detector layers |
0352 | `collectLayerComponents()` | Find modules and sensitive descendants |
0353 | `cachePixelLayouts()` | Determine base IDs and create layouts |
0354 | `makeCartesianLayout()` | Build rectangular or row-span Cartesian layouts |
0355 | `makeCylindricalLayout()` | Build local cylindrical phi-z layouts |
0356 | `buildLayers()` | Construct pixel-weighted component tables |
0357 | `pixelIndices()` | Map a flat pixel number to two segmentation indices |
0358 | `randomCellID()` | Encode a sampled pixel into a complete cell ID |
0359 | `addNoiseHitsForLayer()` | Draw the layer count and create unique hits |
0360 | `process()` | Event entry point and deterministic output |