R3BROOT
R3B analysis software
Loading...
Searching...
No Matches
R3BNeulandCosmicTracker.cxx
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) 2019 GSI Helmholtzzentrum für Schwerionenforschung GmbH *
3 * Copyright (C) 2019-2025 Members of R3B Collaboration *
4 * *
5 * This software is distributed under the terms of the *
6 * GNU General Public Licence (GPL) version 3, *
7 * copied verbatim in the file "LICENSE". *
8 * *
9 * In applying this license GSI does not waive the privileges and immunities *
10 * granted to it by virtue of its status as an Intergovernmental Organization *
11 * or submit itself to any jurisdiction. *
12 ******************************************************************************/
13
15
16#include "FairLogger.h"
17
18#include "TCanvas.h"
19#include "TFitResult.h"
20#include "TFitResultPtr.h"
21#include "TGraphErrors.h"
22
23#include <algorithm>
24#include <cmath>
25#include <exception>
26#include <numeric>
27
28namespace R3B::Neuland // NOLINT
29{
30 namespace Calibration
31 {
32 // Data to calculate the range and energyloss of cosmic muons
33 // taken from "doi:10.1006/adnd.2001.0861"
34 // { Kinetic Energies / MeV, Stopping Powers / MeV cm²/g, Ranges / cm}
35 // const std::vector<Double_t> cosmicMuonData[3] = {
36 // { 10, 14, 20, 30, 40, 80, 100, 140, 200, 300, 400, 800, 1000, 1400,
37 // 2000, 3000, 4000, 8000, 10000, 14000, 20000, 30000, 40000, 80000, 100000, 140000, 200000, 300000 },
38 // { 7.917, 6.171, 4.816, 3.734, 3.187, 2.388, 2.237, 2.082, 1.992, 1.957, 1.962, 2.033, 2.066, 2.12,
39 // 2.179, 2.246, 2.293, 2.4, 2.433, 2.48, 2.528, 2.58, 2.615, 2.697, 2.722, 2.76, 2.8, 2.845 },
40 // { 0.7062, 1.285, 2.398, 4.789, 7.707, 22.66, 31.34, 49.97, 79.55, 130.3, 181.4, 381.7, 479.3, 670.22,
41 // 948.9, 1400, 1840, 3534, 4358, 4975, 8347, 12200, 15970, 30430, 37370, 50800, 69950, 99670 }
42 // };
43
44 using std::isfinite;
45 using std::isnan;
46 using DPair = std::array<Double_t, 2>;
47
48 constexpr auto MinPoints = 3;
49 constexpr auto TimeEps = 0.0001;
50 constexpr auto MaxDistance = 2 * BarSize_XY;
51 constexpr auto MaxSlope = 15.;
52
53 constexpr bool WithinBounds(const double val, const double lower_bound, const double upper_bound)
54 {
55 return (val >= lower_bound && val <= upper_bound);
56 }
57
58 constexpr bool WithinBounds(const double val, const DPair& bounds)
59 {
60 return WithinBounds(val, bounds[0], bounds[1]);
61 }
62
63 bool WithinBounds(const TVector3& point, const DPair& xBounds, const DPair& yBounds, const DPair& zBounds)
64 {
65 return WithinBounds(point[0], xBounds) && WithinBounds(point[1], yBounds) &&
66 WithinBounds(point[2], zBounds);
67 }
68
70 : fDistances{ 0. }
71 , fFit("CosmicTracker:fFit", "pol1")
72 {
73 fDistances.reserve(MaxNumberOfPlanes);
74 while (fDistances.size() < MaxNumberOfPlanes)
75 {
76 fDistances.push_back(fDistances.back() + BarSize_Z);
77 }
78 fTrack.Interactions.reserve(256);
79 fBarIDs.reserve(256);
80 }
81
82 void CosmicTracker::AddPoint(Int_t barID, const Double_t pos)
83 {
84 fBarIDs.push_back(barID);
85
86 const auto plane = ModuleID2PlaneID(barID);
87 const auto bar = barID % BarsPerPlane;
88
89 const auto zPosition = fDistances[plane] + 0.5 * BarSize_Z;
90
91 if (IsPlaneIDHorizontal(plane))
92 {
93 const auto barPoint = fYZ.GetN();
94 // ig fYZ.SetPoint(barPoint, zPosition, (bar - 0.5 * BarsPerPlane + 0.66) * BarSize_XY);
95 fYZ.SetPoint(barPoint, zPosition, (bar - 0.5 * BarsPerPlane + 0.5) * BarSize_XY);
96 fYZ.SetPointError(barPoint, BarUncertainty_Z, BarUncertainty_XY);
97
98 // ig if (!isnan(pos) && pos < 0.5 * BarLength + 10.) // + some margin
99 /*ig if (!isnan(pos) && fabs(pos) < 0.5 * BarLength + 10.) // + some margin
100 {
101 const auto posPoint = fXZ.GetN();
102 fXZ.SetPoint(posPoint, zPosition, pos);
103 //ig fXZ.SetPointError(posPoint, BarUncertainty_Z, 3 * BarUncertainty_XY);
104 fXZ.SetPointError(posPoint, BarUncertainty_Z, BarUncertainty_XY);
105 }*/
106 }
107 else
108 {
109 const auto barPoint = fXZ.GetN();
110 fXZ.SetPoint(barPoint, zPosition, (bar - 0.5 * BarsPerPlane + 0.5) * BarSize_XY);
111 fXZ.SetPointError(barPoint, BarUncertainty_Z, BarUncertainty_XY);
112
113 // ig if (!isnan(pos) && pos < 0.5 * BarLength + 10.) // + some margin
114 /*ig if (!isnan(pos) && fabs(pos) < 0.5 * BarLength + 10.) // + some margin
115 {
116 const auto posPoint = fYZ.GetN();
117 fYZ.SetPoint(posPoint, zPosition, pos);
118 //ig fYZ.SetPointError(posPoint, BarUncertainty_Z, 3 * BarUncertainty_XY);
119 fYZ.SetPointError(posPoint, BarUncertainty_Z, BarUncertainty_XY);
120 }*/
121 }
122 }
123
125 {
126 LOG(debug) << "CosmicTracker::Fit : Number of Points: X-Z: " << fXZ.GetN() << " Y-Z: " << fYZ.GetN();
127
128 if (fYZ.GetN() < MinPoints || fXZ.GetN() < MinPoints)
129 {
130 LOG(debug) << "CosmicTracker::Fit : Not enough Points to make reasonable fit.";
131 return fTrack;
132 }
133
134 filter(fYZ);
135 if (fYZ.GetN() < MinPoints)
136 {
137 LOG(debug) << "CosmicTracker::Fit : Not enough Points to make reasonable fit after "
138 "horizontal filtering.";
139 return fTrack;
140 }
141
142 filter(fXZ);
143 if (fXZ.GetN() < MinPoints)
144 {
145 LOG(debug) << "CosmicTracker::Fit : Not enough Points to make reasonable fit after vertical "
146 "filtering.";
147 return fTrack;
148 }
149
150 fXZ.Sort();
151 fYZ.Sort();
152
153 TVector3& direction = fTrack.Direction;
154 TVector3& entryPoint = fTrack.EntryPoint;
155 TVector3& invDirection = fTrack.InvDirection;
156
157 const auto yFit = fit(fYZ);
158 if (isnan(yFit[0]))
159 {
160 LOG(debug) << "CosmicTracker::Fit : Could not get a reasonable vertical fit.";
161 return fTrack;
162 }
163
164 if (fabs(yFit[0]) < 0.1)
165 {
166 LOG(debug) << "CosmicTracker::Fit : Y-Slope too small. Better reject this Track.";
167 return fTrack;
168 }
169
170 const auto xFit = fit(fXZ);
171 if (isnan(xFit[0]))
172 {
173 LOG(debug) << "CosmicTracker::Fit : Could not get a reasonable horizontal fit.";
174 return fTrack;
175 }
176
177 direction[0] = xFit[0];
178 entryPoint[0] = xFit[1];
179
180 direction[1] = yFit[0];
181 entryPoint[1] = yFit[1];
182
183 direction[2] = 1.0;
184 entryPoint[2] = 0.;
185
186 if (direction[1] > 0)
187 {
188 // Track is going up -> invert
189
190 entryPoint[2] = fDistances.back() + BarSize_Z; // i.e. back of NeuLAND
191 entryPoint[0] += direction[0] * entryPoint[2];
192 entryPoint[1] += direction[1] * entryPoint[2];
193
194 for (int i = 0; i < 3; ++i)
195 direction[i] *= -1.0;
196 }
197
198 // scale direction to c
199 const auto scale = CLight / direction.Mag();
200 for (int i = 0; i < 3; ++i)
201 {
202 direction[i] *= scale;
203 invDirection[i] = 1.0 / direction[i];
204 }
205
206 // time until cosmic enters NeuLAND
207 const auto flightTimeEntry = getCrossPointTime(entryPoint - direction * 2 * TimeEps,
208 direction,
209 invDirection,
210 { -0.5 * BarLength, 0.5 * BarLength },
211 { -0.5 * BarLength, 0.5 * BarLength },
212 { fDistances[0], fDistances.back() + BarSize_Z });
213
214 if (isnan(flightTimeEntry))
215 return fTrack;
216
217 entryPoint += direction * (flightTimeEntry - TimeEps);
218
219 LOG(debug) << "X: " << entryPoint[0] << " + t * " << direction[0];
220 LOG(debug) << "Y: " << entryPoint[1] << " + t * " << direction[1];
221 LOG(debug) << "Z: " << entryPoint[2] << " + t * " << direction[2];
222
223 fillInteractions(fTrack);
224 if (fTrack.Interactions.size() < 3) // ig || fTrack.Interactions.size() < fBarIDs.size() - 3)
225 {
226 fTrack.Interactions.clear();
227 fTrack.TotalTrackLength = 0.;
228 }
229
230 LOG(debug) << "Interactions: " << fTrack.Interactions.size();
231
232 return fTrack;
233 }
234
235 void CosmicTracker::filter(TGraphErrors& graph) const
236 {
237 std::array<Int_t, 256> remove;
238 auto nRemove = 0;
239
240 auto nPoints = graph.GetN();
241 auto x = graph.GetX();
242 auto y = graph.GetY();
243
244 // first remove points which are far away from all others
245
246 for (auto p = 0; p < nPoints; ++p)
247 {
248 Bool_t foundClose = false;
249 for (auto op = 0; op < nPoints; ++op)
250 {
251 if (p == op)
252 continue;
253 const auto dist2 = Sqr(x[p] - x[op]) + Sqr(y[p] - y[op]);
254 // ig if (dist2 < Sqr(MaxDistance))
255 if (dist2 < Sqr(2 * MaxDistance))
256 {
257 foundClose = true;
258 break;
259 }
260 }
261 if (!foundClose)
262 {
263 remove[nRemove] = p;
264 if (++nRemove == remove.size())
265 {
266 graph.Set(0);
267 return;
268 }
269 }
270 }
271
272 LOG(debug) << " Removed : " << nRemove;
273
274 if (nRemove > 0)
275 {
276 auto offset = 1;
277 const auto totalPoints = nPoints - nRemove;
278 for (auto p = remove[0]; p < totalPoints; ++p)
279 {
280 if (offset < nRemove && p + offset == remove[offset])
281 {
282 ++offset;
283 --p;
284 continue;
285 }
286
287 x[p] = x[p + offset];
288 y[p] = y[p + offset];
289 }
290
291 graph.Set(totalPoints);
292 }
293
294 x = graph.GetX();
295 y = graph.GetY();
296 nPoints = graph.GetN();
297 const auto linReg = linearRegression(x, y, nPoints);
298 const auto factor = 1. / (Sqr(linReg[1]) + 1.);
299
300 nRemove = 0;
301
302 for (auto p = nPoints - 1; p >= 0; --p)
303 {
304 const auto dist2 = Sqr(linReg[1] * x[p] - y[p] + linReg[0]) * factor;
305 // ig if (dist2 > Sqr(MaxDistance))
306 if (dist2 > Sqr(MaxDistance))
307 {
308 // seems like there is at least one cluster far away from the fit
309 // better reject this event
310 graph.Set(0);
311 return;
312 }
313
314 // ig if (dist2 > Sqr(1.5 * BarSize_XY))
315 if (dist2 > Sqr(1.5 * BarSize_XY))
316 {
317 // remove points which are a bit of
318 graph.RemovePoint(p);
319 if (++nRemove == 2)
320 {
321 // we removed to many points, better reject this event
322 graph.Set(0);
323 return;
324 }
325 }
326 }
327 }
328
330 {
331 fXZ.Set(0);
332 fYZ.Set(0);
333
334 fTrack.Interactions.clear();
335 fTrack.TotalTrackLength = 0.;
336 fTrack.Stopped = false;
337
338 fBarIDs.clear();
339 }
340
341 std::array<Double_t, 2> CosmicTracker::fit(TGraphErrors& graph)
342 {
343 const auto linReg = linearRegression(graph.GetX(), graph.GetY(), graph.GetN());
344 if (!isfinite(linReg[0]))
345 return { NaN, NaN };
346
347 fFit.SetParameter(0, linReg[0]);
348 fFit.SetParameter(1, linReg[1]);
349
350 TFitResultPtr resultptr = graph.Fit(&fFit, "NQS");
351
352 const auto slope = fFit.GetParameter(1);
353 const auto yIntercept = fFit.GetParameter(0);
354
355 auto redchi2 = resultptr->Chi2() / resultptr->Ndf();
356
357 // Throw away bad fits
358 // ig if (redchi2 < 0.5 || redchi2 > 2. || fabs(slope) > MaxSlope)
359 // ig return { NaN, NaN };
360
361 return { slope, yIntercept };
362 }
363
364 Double_t CosmicTracker::getCrossPointTime(const TVector3& point,
365 const TVector3& direction,
366 const TVector3& invDirection,
367 const DPair& xBounds,
368 const DPair& yBounds,
369 const DPair& zBounds) const
370 {
371 const auto invert = (WithinBounds(point, xBounds, yBounds, zBounds) ? -1. : 1.);
372
373 TVector3 crossPoint;
374
375 if (invert * invDirection[0] > 0)
376 {
377 crossPoint[0] = xBounds[0];
378 }
379 else
380 {
381 crossPoint[0] = xBounds[1];
382 }
383
384 if (invert * invDirection[1] > 0)
385 {
386 crossPoint[1] = yBounds[0];
387 }
388 else
389 {
390 crossPoint[1] = yBounds[1];
391 }
392
393 if (invert * invDirection[2] > 0)
394 {
395 crossPoint[2] = zBounds[0];
396 }
397 else
398 {
399 crossPoint[2] = zBounds[1];
400 }
401
402 auto wasHit = kFALSE;
403
404 for (int i = 0; i < 3; ++i)
405 {
406 const auto flightTime = (crossPoint[i] - point[i]) * invDirection[i];
407 if (flightTime > 0. &&
408 WithinBounds(point + direction * (flightTime + invert * TimeEps), xBounds, yBounds, zBounds))
409 return flightTime;
410 }
411
412 return NaN;
413 }
414
415 // Returns { YIntercept, Slope }
416 DPair CosmicTracker::linearRegression(const Double_t* x, const Double_t* y, const Int_t points) const
417 {
418 const auto invPoints = 1. / points;
419 const auto invRedPoints = 1. / (points - 1);
420 const auto xMean = invPoints * std::accumulate(x, x + points, 0.);
421 const auto yMean = std::accumulate(y, y + points, 0.) / points;
422 const auto xVar = invRedPoints * std::accumulate(x,
423 x + points,
424 0.,
425 [xMean](const Double_t acc, const Double_t val)
426 { return acc + Sqr(val - xMean); });
427
428 if (xVar == 0)
429 {
430 // we have a vertical line
431 return { Inf, xMean };
432 }
433 const auto yVar = invRedPoints * std::accumulate(y,
434 y + points,
435 0.,
436 [yMean](const Double_t acc, const Double_t val)
437 { return acc + Sqr(val - yMean); });
438
439 auto xyVar = 0.;
440 for (auto p = 0; p < points; ++p)
441 xyVar += (x[p] - xMean) * (y[p] - yMean);
442 xyVar *= invRedPoints;
443
444 const auto slope = (yVar - xVar + std::sqrt(Sqr(yVar - xVar) + 4 * Sqr(xyVar))) / (2 * xyVar);
445 const auto yIntercept = yMean - slope * xMean;
446
447 if (fabs(slope) > MaxSlope)
448 return { Inf, NaN };
449
450 return { yIntercept, slope };
451 }
452
453 void CosmicTracker::fillInteractions(R3BNeulandCosmicTrack& track) const
454 {
455 auto nMissedHits = 0;
456 auto lastValidSize = 0;
457 auto missedEnergy = 0.;
458
459 std::array<Int_t, 3> barDirections;
460 for (auto i = 0; i < 3; ++i)
461 {
462 barDirections[i] = (track.Direction[i] > 0. ? 1 : -1);
463 }
464
465 // first find the plane and bar where the cosmic entered NeuLAND
466 Int_t currentPlane = 0, currentBar;
467 track.EntryPoint += track.Direction * TimeEps;
468
469 while (currentPlane < fDistances.size() - 1 &&
470 !WithinBounds(track.EntryPoint.Z(), fDistances[currentPlane], fDistances[currentPlane + 1]))
471 {
472 ++currentPlane;
473 }
474
475 // now we now that the cosmic is between currentPlane and currentPlane+1
476 // there might be a gap so check if we are not in currentPlane
477 if (!WithinBounds(track.EntryPoint.Z(), fDistances[currentPlane], fDistances[currentPlane] + BarSize_Z))
478 {
479 // Seems like we are in a gap
480 if (track.Direction.Z() > 0.)
481 {
482 // we will hit the next plane
483 ++currentPlane;
484 }
485
486 const auto crossTime =
487 getCrossPointTime(track.EntryPoint,
488 track.Direction,
489 track.InvDirection,
490 { -0.5 * BarLength, 0.5 * BarLength },
491 { -0.5 * BarLength, 0.5 * BarLength },
492 { fDistances[currentPlane], fDistances[currentPlane] + BarSize_Z });
493
494 if (isnan(crossTime))
495 return;
496
497 track.EntryPoint += track.Direction * crossTime;
498 }
499
500 // we now know the plane
501
502 auto planeChanged = kTRUE;
503 DPair xBorder, yBorder;
504 auto currentPoint = track.EntryPoint;
505 auto tof = 0.;
506
507 LOG(debug) << "Entry Plane: " << currentPlane << " at " << currentPoint.X() << " " << currentPoint.Y()
508 << " " << currentPoint.Z();
509
510 while (kTRUE)
511 {
512 if (planeChanged)
513 {
514 if (IsPlaneIDHorizontal(currentPlane))
515 {
516 currentBar = (currentPoint.Y() + BarsPerPlane * 0.5 * BarSize_XY) / BarSize_XY;
517 xBorder = { -0.5 * BarLength, 0.5 * BarLength };
518 yBorder = { -0.5 * BarLength + currentBar * BarSize_XY,
519 -0.5 * BarLength + (currentBar + 1) * BarSize_XY };
520 }
521 else
522 {
523 currentBar = (currentPoint.X() + BarsPerPlane * 0.5 * BarSize_XY) / BarSize_XY;
524 xBorder = { -0.5 * BarLength + currentBar * BarSize_XY,
525 -0.5 * BarLength + (currentBar + 1) * BarSize_XY };
526 yBorder = { -0.5 * BarLength, 0.5 * BarLength };
527 }
528 }
529 else
530 {
531 if (IsPlaneIDHorizontal(currentPlane))
532 {
533 currentBar += barDirections[1];
534 yBorder[0] += barDirections[1] * BarSize_XY;
535 yBorder[1] += barDirections[1] * BarSize_XY;
536 }
537 else
538 {
539 currentBar += barDirections[0];
540 xBorder[0] += barDirections[0] * BarSize_XY;
541 xBorder[1] += barDirections[0] * BarSize_XY;
542 }
543 }
544 const auto crossTime =
545 getCrossPointTime(currentPoint,
546 track.Direction,
547 track.InvDirection,
548 xBorder,
549 yBorder,
550 { fDistances[currentPlane], fDistances[currentPlane] + BarSize_Z });
551
552 if (isnan(crossTime))
553 break;
554
555 const auto barID = currentBar + currentPlane * BarsPerPlane;
556 const auto trackLength = crossTime * CLight;
557 if (IsPlaneIDHorizontal(currentPlane))
558 {
559 track.Interactions.emplace_back(barID,
560 tof,
561 tof + crossTime,
562 currentPoint.X(),
563 currentPoint.X() + crossTime * track.Direction.X(),
564 trackLength * MIPStoppingPower,
565 trackLength);
566 }
567 else
568 {
569 track.Interactions.emplace_back(barID,
570 tof,
571 tof + crossTime,
572 currentPoint.Y(),
573 currentPoint.Y() + crossTime * track.Direction.Y(),
574 trackLength * MIPStoppingPower,
575 trackLength);
576 }
577
578 if (std::find(fBarIDs.begin(), fBarIDs.end(), barID) != fBarIDs.end())
579 {
580 missedEnergy = 0.;
581 nMissedHits = 0;
582 lastValidSize = track.Interactions.size();
583 }
584 else
585 {
586 ++nMissedHits;
587 missedEnergy += trackLength * MIPStoppingPower;
588 if (missedEnergy > 10. || (missedEnergy > 7. && nMissedHits > 1))
589 {
590 if (lastValidSize == 0)
591 {
592 return;
593 }
594 track.Interactions.resize(lastValidSize);
595 track.TotalTrackLength = track.Interactions.back().ExitTime * CLight;
596 track.Stopped = kTRUE;
597 return;
598 }
599 }
600
601 tof += crossTime + TimeEps;
602 currentPoint = track.EntryPoint + track.Direction * tof;
603
604 // check if we are still in NeuLAND
605 if (!WithinBounds(currentPoint,
606 { -0.5 * BarLength, 0.5 * BarLength },
607 { -0.5 * BarLength, 0.5 * BarLength },
608 { fDistances[0], fDistances.back() + BarSize_Z }))
609 {
610 LOG(debug) << "Left NeuLAND at Plane " << currentPlane << " with " << currentPoint.X() << " "
611 << currentPoint.Y() << " " << currentPoint.Z();
612 break;
613 }
614
615 // check if we left the plane
616 if (!WithinBounds(currentPoint.Z(), fDistances[currentPlane], fDistances[currentPlane] + BarSize_Z))
617 {
618 // we left the plane
619 planeChanged = true;
620 currentPlane += barDirections[2];
621
622 // check if we are already in the next plane
623 if (!WithinBounds(currentPoint.Z(), fDistances[currentPlane], fDistances[currentPlane] + BarSize_Z))
624 {
625 // propagate to next plane
626 const auto gapTime =
627 getCrossPointTime(currentPoint,
628 track.Direction,
629 track.InvDirection,
630 { -0.5 * BarLength, 0.5 * BarLength },
631 { -0.5 * BarLength, 0.5 * BarLength },
632 { fDistances[currentPlane], fDistances[currentPlane] + BarSize_Z });
633
634 if (isnan(gapTime))
635 break;
636
637 tof += gapTime + TimeEps;
638 currentPoint = track.EntryPoint + track.Direction * tof;
639 }
640 }
641 }
642 track.TotalTrackLength = tof * CLight;
643 return;
644 }
645 } // namespace Calibration
646} // namespace R3B::Neuland
std::array< Double_t, 2 > DPair
void AddPoint(const Int_t barID, const Double_t pos=NaN)
std::array< Double_t, 2 > DPair
constexpr bool WithinBounds(const double val, const double lower_bound, const double upper_bound)
Simulation of NeuLAND Bar/Paddle.
constexpr auto BarsPerPlane
constexpr auto ModuleID2PlaneID(int moduleID) -> int
constexpr auto BarUncertainty_Z
constexpr auto BarLength
constexpr auto BarSize_Z
constexpr auto MIPStoppingPower
constexpr auto CLight
constexpr auto BarSize_XY
constexpr auto NaN
constexpr auto BarUncertainty_XY
constexpr auto IsPlaneIDHorizontal(int plane_id) -> bool
constexpr auto MaxNumberOfPlanes
constexpr T Sqr(const T val)
constexpr auto Inf
std::vector< Interaction > Interactions