Belle II Software development
YScanner.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <top/reconstruction_cpp/YScanner.h>
10#include <top/reconstruction_cpp/func.h>
11#include <top/geometry/TOPGeometryPar.h>
12#include <framework/logging/Logger.h>
13#include <cmath>
14
15namespace Belle2 {
20 namespace TOP {
21
23 unsigned YScanner::s_maxTabulatedPower = 1024;
24
26 const InverseRaytracer::Solution& sol_dx,
27 const InverseRaytracer::Solution& sol_de,
28 const InverseRaytracer::Solution& sol_dL)
29 {
30 if (sol_dx.step == 0) B2ERROR("TOP::YScanner::Derivatives: step (dx) is zero");
31 if (sol_de.step == 0) B2ERROR("TOP::YScanner::Derivatives: step (de) is zero");
32 if (sol_dL.step == 0) B2ERROR("TOP::YScanner::Derivatives: step (dL) is zero");
33
34 dLen_dx = dLen_d(sol, sol_dx);
35 dLen_de = dLen_d(sol, sol_de);
36 dLen_dL = dLen_d(sol, sol_dL);
37
38 dyB_dx = dyB_d(sol, sol_dx);
39 dyB_de = dyB_d(sol, sol_de);
40 dyB_dL = dyB_d(sol, sol_dL);
41
42 dFic_dx = dFic_d(sol, sol_dx);
43 dFic_de = dFic_d(sol, sol_de);
44 dFic_dL = dFic_d(sol, sol_dL);
45 }
46
47
48 YScanner::YScanner(int moduleID, unsigned N): RaytracerBase(moduleID, c_Unified, c_SemiLinear),
50 m_pixelMasks(PixelMasks(moduleID)),
52 {
53 if (N < 2) {
54 B2FATAL("TOP::YScanner: N must be > 1");
55 return;
56 }
57
58 // set the pixel projection constants of the unfolded prism exit windows
59
60 m_prismZR = m_prism.zR;
61 m_halfBarThickness = m_bars.front().B / 2;
62 double dz = std::abs(m_prism.zD - m_prism.zFlat);
63 for (size_t k = 0; k < m_prism.unfoldedWindows.size(); k++) {
64 const auto& win = m_prism.unfoldedWindows[k];
65 WindowProjection projection;
66 projection.sy = win.sy;
67 projection.sz = win.sz;
68 projection.y0 = win.y0 + win.ny * dz;
69 projection.z0 = win.z0 + win.nz * dz;
70 projection.evenReflection = ((static_cast<int>(k) - m_prism.k0) % 2 == 0);
71 m_windowProjections.push_back(projection);
72 }
73
74 // set the table of nominal photon detection efficiencies (incl. wavelength filter)
75
76 const auto* topgp = TOPGeometryPar::Instance();
77 const auto* geo = topgp->getGeometry();
78 auto qe = geo->getNominalQE(); // get a copy
79 qe.applyFilterTransmission(geo->getWavelengthFilter());
80
81 double minE = TOPGeometryPar::c_hc / qe.getMaxLambda();
82 double maxE = TOPGeometryPar::c_hc / qe.getMinLambda();
83 if (minE >= maxE) {
84 B2FATAL("TOP::YScanner: quantum efficiency found zero for all wavelengths");
85 return;
86 }
87 m_efficiency.set(minE, (maxE - minE) / (N - 1));
88
89 const auto& tdc = geo->getNominalTDC();
90 for (unsigned i = 0; i < N; i++) {
91 double e = m_efficiency.getX(i);
92 double lambda = TOPGeometryPar::c_hc / e;
93 double effi = qe.getEfficiency(lambda) * tdc.getEfficiency();
94 m_efficiency.entries.push_back(TableEntry(effi, e, e * e));
95 }
96
97 // set cosine of total reflection angle using photon mean energy for beta = 1
98
99 double s = 0;
100 double se = 0;
101 double see = 0;
102 for (const auto& entry : m_efficiency.entries) {
103 double e = entry.x;
104 double p = entry.y * (1 - 1 / pow(topgp->getPhaseIndex(e), 2));
105 s += p;
106 se += p * e;
107 see += p * e * e;
108 }
109 if (s == 0) return;
110 m_meanE0 = se / s;
111 m_rmsE0 = sqrt(see / s - m_meanE0 * m_meanE0);
112 m_cosTotal = sqrt(1 - 1 / pow(topgp->getPhaseIndex(m_meanE0), 2));
113 }
114
115
117 {
118 double reflectivity = m_bars.front().reflectivity;
119
120 // an unreasonably large number of reflections must not blow up the table
121 if (n > s_maxTabulatedPower) return pow(reflectivity, n);
122
123 while (m_surfaceReflectivities.size() <= n) {
124 m_surfaceReflectivities.push_back(pow(reflectivity, m_surfaceReflectivities.size()));
125 }
126 return m_surfaceReflectivities[n];
127 }
128
129
130 void YScanner::clear() const
131 {
132 m_momentum = 0;
133 m_beta = 0;
134 m_length = 0;
135 m_numPhotons = 0;
136 m_meanE = 0;
137 m_rmsE = 0;
138 m_sigmaScat = 0;
139 m_sigmaAlpha = 0;
140 m_energyDistribution.clear();
143 m_aboveThreshold = false;
144 m_results.clear();
145 m_scanDone = false;
146 }
147
148
149 void YScanner::prepare(double momentum, double beta, double length) const
150 {
151 clear();
152
153 m_momentum = momentum;
154 m_beta = beta;
155 m_length = length;
156
157 // check for Cherenkov threshold, return if below
158
159 const auto* topgp = TOPGeometryPar::Instance();
160 if (beta * topgp->getPhaseIndex(m_meanE0) < 1) return;
161
162 // set photon energy distribution, and the mean and r.m.s of photon energy
163
164 auto area = setEnergyDistribution(beta);
165 if (area == 0) return;
166
167 // set number of Cerenkov photons per azimuthal angle per centimeter
168
169 m_numPhotons = 370 * area / (2 * M_PI);
170
171 // set multiple scattering and surface roughness sigmas in photon energy units
172
173 const double radLength = 12.3; // quartz radiation length [cm]
174 double thetaScat = 13.6e-3 / beta / momentum * sqrt(length / 2 / radLength); // r.m.s of multiple scattering angle
175
176 double n = topgp->getPhaseIndex(m_meanE);
177 if (beta * n < 1) {
178 B2ERROR("TOP::YScanner::prepare: beta * n < 1 ==> must be a bug!");
179 return;
180 }
181 double dndE = topgp->getPhaseIndexDerivative(m_meanE);
182 double dEdTheta = n * sqrt(pow(beta * n, 2) - 1) / dndE;
183 m_sigmaScat = std::abs(thetaScat * dEdTheta); // r.m.s of multiple scattering angle converted to photon energy
184 m_sigmaAlpha = std::abs(m_bars.back().sigmaAlpha * dEdTheta); // surface roughness converted to photon energy
185
186 // set photon energy distribution convoluted with multiple scattering
187
189
190 m_aboveThreshold = true;
191 }
192
193
194 double YScanner::setEnergyDistribution(double beta) const
195 {
196 const auto* topgp = TOPGeometryPar::Instance();
197
199
200 double s = 0;
201 double se = 0;
202 double see = 0;
203 for (const auto& entry : m_efficiency.entries) {
204 double e = entry.x;
205 double p = std::max(entry.y * (1 - 1 / pow(beta * topgp->getPhaseIndex(e), 2)), 0.0);
206 double ee = entry.xsq;
207 m_energyDistribution.entries.push_back(TableEntry(p, e, ee));
208 s += p;
209 se += p * e;
210 see += p * ee;
211 }
212 if (s == 0) return 0;
213
214 for (auto& entry : m_energyDistribution.entries) entry.y /= s;
215
216 m_meanE = se / s;
217 m_rmsE = sqrt(std::max(see / s - m_meanE * m_meanE, 0.0));
218
219 return s * m_energyDistribution.step;
220 }
221
222
224 {
225 if (m_quasyEnergyDistributions.size() > 1000) {
227 B2ERROR("TOP::YScanner:setQuasyEnergyDistribution: unexpectedly large size of the std::map found, map cleared");
228 }
229
230 double step = m_energyDistribution.step;
231 int ng = func::lround(3 * sigma / step);
232 auto& quasyEnergyDistribution = m_quasyEnergyDistributions[ng];
233
234 if (quasyEnergyDistribution.entries.empty()) {
235 std::vector<double> gaus;
236 for (int i = 0; i <= ng; i++) {
237 double x = step * i / sigma;
238 gaus.push_back(exp(-0.5 * x * x));
239 }
240
241 quasyEnergyDistribution.set(m_energyDistribution.getX(-ng), step);
242 int N = m_energyDistribution.entries.size();
243 const auto* entries = m_energyDistribution.entries.data();
244 const double* gausTable = gaus.data();
245 double sum = 0;
246 for (int k = -ng; k < N + ng; k++) {
247 // outside of this range getY(k - i) is zero, so those terms contribute nothing
248 const int iMin = std::max(-ng, k - N + 1);
249 const int iMax = std::min(ng, k);
250 double s = 0;
251 double se = 0;
252 double see = 0;
253 for (int i = iMin; i <= iMax; i++) {
254 double p = gausTable[std::abs(i)] * entries[k - i].y;
255 double e = entries[k - i].x; // equals m_energyDistribution.getX(k - i)
256 s += p;
257 se += p * e;
258 see += p * e * e;
259 }
260 if (s > 0) {
261 se /= s;
262 see /= s;
263 }
264 quasyEnergyDistribution.entries.push_back(TableEntry(s, se, see));
265 sum += s;
266 }
267 for (auto& entry : quasyEnergyDistribution.entries) entry.y /= sum;
268 }
269
270 m_quasyEnergyDistribution = &quasyEnergyDistribution;
271 }
272
273
274 void YScanner::expand(unsigned col, double yB, double dydz, const Derivatives& D, int Ny, bool doScan) const
275 {
276 m_results.clear();
277
278 if (D.dyB_de == 0) return;
279
280 double sigma = sqrt(pow(m_sigmaScat, 2) + pow(m_sigmaAlpha, 2) * std::abs(Ny));
282
283 double minE = m_quasyEnergyDistribution->getXmin();
284 double maxE = m_quasyEnergyDistribution->getXmax();
285 double pixDx = m_pixelPositions.get(col + 1).Dx;
286 double dely = (std::abs(D.dyB_dL) * m_length + std::abs(D.dyB_dx) * pixDx) / 2;
287 double y1 = yB - dely;
288 double y2 = yB + dely;
289 if (D.dyB_de > 0) {
290 y1 += D.dyB_de * (minE - m_meanE);
291 y2 += D.dyB_de * (maxE - m_meanE);
292 } else {
293 y1 += D.dyB_de * (maxE - m_meanE);
294 y2 += D.dyB_de * (minE - m_meanE);
295 }
296 double B = m_bars.front().B;
297 int j1 = func::lround(y1 / B);
298 int j2 = func::lround(y2 / B) + 1;
299
300 if (doScan and j2 - j1 <= s_maxReflections) {
301 scan(col, yB, dydz, D, j1, j2);
302 m_scanDone = true;
303 } else {
304 merge(col, dydz, j1, j2);
305 m_scanDone = false;
306 }
307 }
308
309
310 void YScanner::scan(unsigned col, double yB, double dydz, const Derivatives& D, int j1, int j2) const
311 {
312
313 const size_t numWindows = m_windowProjections.size();
314 const unsigned numRows = m_pixelPositions.getNumPixelRows();
315 const double barB = m_bars.front().B;
316
317 std::map<int, EnergyMask*> masks;
318 for (unsigned row = 0; row < numRows; row++) {
319
320 int pixelID = m_pixelPositions.pixelID(row, col);
321 if (not m_pixelMasks.isActive(pixelID)) continue;
322
323 const auto& pixel = m_pixelPositions.get(pixelID);
324 std::vector<PixelProjection> projections[2];
325 PixelProjection proj[2];
326 for (size_t k = 0; k < numWindows; k++) {
327 projectPixel(pixel.yc, pixel.Dy, k, dydz, proj);
328 if (proj[0].Dy > 0) projections[0].push_back(proj[0]);
329 proj[1].yc = -proj[1].yc;
330 if (proj[1].Dy > 0) projections[1].push_back(proj[1]);
331 }
332 if (projections[0].empty() and projections[1].empty()) continue;
333
334 for (unsigned k = 0; k < 2; k++) {
335 std::sort(projections[k].begin(), projections[k].end());
336 for (auto& projection : projections[k]) {
337 int iDy = func::lround(projection.Dy * 1000);
338 auto& mask = masks[iDy];
339 if (not mask) {
340 double Dy = projection.Dy;
341 double step = m_quasyEnergyDistribution->step;
342 mask = new EnergyMask(D.dyB_de, D.dyB_dL, D.dyB_dx, Dy, m_length, pixel.Dx, step);
343 }
344 projection.mask = mask;
345 }
346 }
347
348 double Ecp_old = 0;
349 double wid_old = 1000;
350 m_results.push_back(Result(pixelID));
351 for (int j = j1; j < j2; j++) {
352 double ybar = j * barB - yB;
353 for (const auto& projection : projections[std::abs(j) % 2]) {
354 double Ecp = (ybar + projection.yc) / D.dyB_de + m_meanE;
355 double wid = projection.mask->getFullWidth();
356 if (std::abs(Ecp - Ecp_old) > (wid + wid_old) / 2 and m_results.back().sum > 0) {
357 m_results.push_back(Result(pixelID));
358 }
359 integrate(projection.mask, Ecp, m_results.back());
360 Ecp_old = Ecp;
361 wid_old = wid;
362 }
363 }
364
365 if (m_results.back().sum == 0) m_results.pop_back();
366 }
367
368 for (auto& result : m_results) result.set();
369
370 for (const auto& mask : masks) {
371 if (mask.second) delete mask.second;
372 }
373
374 }
375
376
377 void YScanner::merge(unsigned col, double dydz, int j1, int j2) const
378 {
379 int Neven = func::getNumOfEven(j1, j2);
380 int Nodd = j2 - j1 - Neven;
381
382 const size_t numWindows = m_windowProjections.size();
383 const unsigned numRows = m_pixelPositions.getNumPixelRows();
384 const double barB = m_bars.front().B;
385
386 for (unsigned row = 0; row < numRows; row++) {
387
388 int pixelID = m_pixelPositions.pixelID(row, col);
389 if (not m_pixelMasks.isActive(pixelID)) continue;
390
391 const auto& pixel = m_pixelPositions.get(pixelID);
392 double Dy0 = 0;
393 double Dy1 = 0;
394 PixelProjection proj[2];
395 for (size_t k = 0; k < numWindows; k++) {
396 projectPixel(pixel.yc, pixel.Dy, k, dydz, proj);
397 if (proj[0].Dy > 0) Dy0 += proj[0].Dy;
398 if (proj[1].Dy > 0) Dy1 += proj[1].Dy;
399 }
400 if (Dy0 == 0 and Dy1 == 0) continue;
401
402 double Dy = (Dy0 * Neven + Dy1 * Nodd) / (Neven + Nodd);
403 Result result(pixelID);
404 result.sum = Dy / barB;
405 result.e0 = m_meanE;
406 result.sigsq = m_rmsE * m_rmsE;
407 m_results.push_back(result);
408 }
409 }
410
411
412 void YScanner::integrate(const EnergyMask* energyMask, double Ecp, Result& result) const
413 {
414 const auto& mask = energyMask->getMask();
415
416 if (mask.empty()) {
417 // direct mask calculation
418 for (size_t i = 0; i < m_quasyEnergyDistribution->entries.size(); i++) {
419 double E = m_quasyEnergyDistribution->getX(i);
420 double m = energyMask->getMask(E - Ecp);
421 if (m > 0) {
422 const auto& entry = m_quasyEnergyDistribution->entries[i];
423 double s = entry.y * m;
424 result.sum += s;
425 result.e0 += entry.x * s;
426 result.sigsq += entry.xsq * s;
427 }
428 }
429 } else {
430 // pre-calculated discrete mask w/ linear interpolation
431 int i0 = m_quasyEnergyDistribution->getIndex(Ecp);
432 double fract = -(Ecp - m_quasyEnergyDistribution->getX(i0)) / m_quasyEnergyDistribution->step;
433 if (fract < 0) {
434 i0++;
435 fract += 1;
436 }
437 int N = m_quasyEnergyDistribution->entries.size() - 1;
438 int M = mask.size() - 1;
439 int i1 = std::max(i0 - M, 0);
440 int i2 = std::min(i0 + M - 1, N);
441 for (int i = i1; i <= i2; i++) {
442 const auto& entry = m_quasyEnergyDistribution->entries[i];
443 double m = mask[std::abs(i - i0)] * (1 - fract) + mask[std::abs(i - i0 + 1)] * fract;
444 double s = entry.y * m;
445 result.sum += s;
446 result.e0 += entry.x * s;
447 result.sigsq += entry.xsq * s;
448 }
449 }
450 }
451
452
453 } //TOP
455} //Belle2
R E
internal precision of FFTW codelets
A mask for energy masking.
Definition EnergyMask.h:24
const std::vector< double > & getMask() const
Returns discrete mask (note: only half of the mask is stored)
Definition EnergyMask.h:68
Pixel relative efficiencies of a single module.
Pixel masks of a single module.
Definition PixelMasks.h:22
Pixel positions and dimensions in module local frame.
@ c_Unified
single bar with average width and thickness
@ c_SemiLinear
semi-linear approximation
RaytracerBase(int moduleID, EGeometry geometry=c_Unified, EOptics optics=c_SemiLinear)
Constructor.
Prism m_prism
prism geometry data
std::vector< BarSegment > m_bars
geometry data of bar segments
static TOPGeometryPar * Instance()
Static method to obtain the pointer to its instance.
static const double c_hc
Planck constant times speed of light in [eV*nm].
double m_cosTotal
cosine of total reflection angle
Definition YScanner.h:523
void prepare(double momentum, double beta, double length) const
Prepare for the PDF expansion in y for a given track mass hypothesis.
Definition YScanner.cc:149
Table m_energyDistribution
photon energy distribution
Definition YScanner.h:534
static unsigned s_maxTabulatedPower
maximal power of the surface reflectivity that is tabulated
Definition YScanner.h:545
PixelEfficiencies m_pixelEfficiencies
pixel relative efficiencies
Definition YScanner.h:514
double m_beta
particle beta
Definition YScanner.h:527
bool m_scanDone
true if scan performed, false if reflections just merged
Definition YScanner.h:542
Table m_efficiency
nominal photon detection efficiencies (PDE)
Definition YScanner.h:515
double m_meanE
mean photon energy
Definition YScanner.h:530
static int s_maxReflections
maximal number of reflections to perform scan
Definition YScanner.h:544
double m_rmsE
r.m.s of photon energy
Definition YScanner.h:531
std::vector< double > m_surfaceReflectivities
bar surface reflectivity to the power of the index; filled on demand, never cleared
Definition YScanner.h:518
bool m_aboveThreshold
true if beta is above the Cerenkov threshold
Definition YScanner.h:538
double m_sigmaScat
r.m.s.
Definition YScanner.h:532
void projectPixel(double yc, double size, int k, double dydz, PixelProjection proj[2]) const
Calculates projections of a pixel to prism entrance window (going down-stream the photon).
Definition YScanner.h:444
void merge(unsigned col, double dydz, int j1, int j2) const
Performs expansion by merging all reflections.
Definition YScanner.cc:377
void scan(unsigned col, double yB, double dydz, const Derivatives &D, int j1, int j2) const
Performs expansion w/ the scan over reflections.
Definition YScanner.cc:310
double tabulateSurfaceReflectivity(unsigned n) const
Extends the table of surface reflectivity powers up to n and returns the value.
Definition YScanner.cc:116
double m_meanE0
mean photon energy for beta = 1
Definition YScanner.h:521
double m_momentum
particle momentum magnitude
Definition YScanner.h:526
Table * m_quasyEnergyDistribution
a pointer to the element in m_quasyEnergyDistributions
Definition YScanner.h:537
PixelMasks m_pixelMasks
pixel masks
Definition YScanner.h:513
void setQuasyEnergyDistribution(double sigma) const
Sets photon energy distribution convoluted with a normalized Gaussian.
Definition YScanner.cc:223
void integrate(const EnergyMask *energyMask, double Ecp, Result &result) const
Integrates quasy energy distribution multiplied with energy mask.
Definition YScanner.cc:412
void expand(unsigned col, double yB, double dydz, const Derivatives &D, int Ny, bool doScan) const
Performs the PDF expansion in y for a given pixel column using scan or merge methods.
Definition YScanner.cc:274
YScanner(int moduleID, unsigned N=64)
Class constructor.
Definition YScanner.cc:48
std::map< int, Table > m_quasyEnergyDistributions
photon energy distributions convoluted with Gaussian of different widths
Definition YScanner.h:536
std::vector< WindowProjection > m_windowProjections
pixel projection constants of unfolded prism exit windows
Definition YScanner.h:516
double setEnergyDistribution(double beta) const
Sets photon energy distribution and mean photon energy according to nominal PDE and particle beta.
Definition YScanner.cc:194
double m_length
length of particle trajectory inside quartz
Definition YScanner.h:528
double m_rmsE0
r.m.s of photon energy for beta = 1
Definition YScanner.h:522
std::vector< Result > m_results
results of PDF expansion in y
Definition YScanner.h:541
double m_prismZR
z of the prism-bar joint (copy of m_prism.zR)
Definition YScanner.h:519
PixelPositions m_pixelPositions
positions and sizes of pixels
Definition YScanner.h:512
void clear() const
Clear mutable variables.
Definition YScanner.cc:130
double m_numPhotons
number of photons per Cerenkov azimuthal angle per track length
Definition YScanner.h:529
double m_halfBarThickness
half thickness of the bar at prism entrance
Definition YScanner.h:520
double m_sigmaAlpha
surface roughness parameter in photon energy units
Definition YScanner.h:533
double sqrt(double a)
sqrt for double
Definition beamHelpers.h:28
int getNumOfEven(int j1, int j2)
Returns number of even numbers in the range given by arguments.
Definition func.h:112
long lround(double x)
Rounds to the nearest integer, halfway cases away from zero.
Definition func.h:31
Abstract base class for different kinds of events.
Solution of inverse ray-tracing.
double step
step for numerical derivative calculation
static double dyB_d(const InverseRaytracer::Solution &sol0, const InverseRaytracer::Solution &sol1)
Calculates the derivative of unfolded y coordinate at prism entrance.
Definition YScanner.h:560
static double dLen_d(const InverseRaytracer::Solution &sol0, const InverseRaytracer::Solution &sol1)
Calculates the derivative of propagation length.
Definition YScanner.h:554
double dFic_de
Cerenkov azimuthal angle over photon energy.
Definition YScanner.h:48
double dLen_de
propagation length over photon energy
Definition YScanner.h:42
Derivatives()
Default constructor.
Definition YScanner.h:54
double dFic_dx
Cerenkov azimuthal angle over photon detection coordinate x.
Definition YScanner.h:47
double dFic_dL
Cerenkov azimuthal angle over running parameter of particle trajectory.
Definition YScanner.h:49
double dLen_dL
propagation length over running parameter of particle trajectory
Definition YScanner.h:43
double dyB_de
unfolded y coordinate at prism entrance over photon energy
Definition YScanner.h:45
double dLen_dx
propagation length over photon detection coordinate x
Definition YScanner.h:41
double dyB_dL
unfolded y coordinate at prism entrance over running parameter of particle trajectory
Definition YScanner.h:46
double dyB_dx
unfolded y coordinate at prism entrance over photon detection coordinate x
Definition YScanner.h:44
static double dFic_d(const InverseRaytracer::Solution &sol0, const InverseRaytracer::Solution &sol1)
Calculates the derivative of Cerenkov azimuthal angle.
Definition YScanner.h:566
Down-stream projection of a pixel to prism entrance window w/ a clip on bar exit thickness.
Definition YScanner.h:182
Single PDF peak data.
Definition YScanner.h:208
Geometry-only quantities needed to project a pixel to a given unfolded prism exit window.
Definition YScanner.h:196
double sz
window surface direction in z
Definition YScanner.h:198
double z0
z of the window origin, displaced to the prism entrance plane
Definition YScanner.h:200
bool evenReflection
true if the window has the same orientation as the true one
Definition YScanner.h:201
double sy
window surface direction in y
Definition YScanner.h:197
double y0
y of the window origin, displaced to the prism entrance plane
Definition YScanner.h:199