ATLAS Offline Software
Loading...
Searching...
No Matches
CaloClusterMomentsMaker.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5//-----------------------------------------------------------------------
6// File and Version Information:
7//
8// Description: see CaloClusterMomentsMaker.h
9//
10// Environment:
11// Software developed for the ATLAS Detector at CERN LHC
12//
13// Author List:
14// Sven Menke
15// Peter Loch
16//
17//-----------------------------------------------------------------------
18
20#include "CaloEvent/CaloCell.h"
21#include "CaloEvent/CaloClusterContainer.h"
22#include "CaloEvent/CaloCluster.h"
29
32
33#include "CLHEP/Units/SystemOfUnits.h"
34#include "CxxUtils/prefetch.h"
36#include <Eigen/Dense>
37#include <cmath>
38#include <cstdint>
39#include <iterator>
40#include <limits>
41#include <sstream>
42
43#include <map>
44#include <vector>
45#include <tuple>
46#include <string>
47#include <string_view>
48#include <algorithm>
49#include <cstdio>
50#include <cmath>
51
52using CLHEP::deg;
53using CLHEP::cm;
55
56// Known moments
57namespace {
59 using Pair = std::pair<std::string_view, MomentType>;
60 // name -> enum pairs, in alphabetical order
61 constexpr std::array momentMappings{
62 Pair{ "AVG_LAR_Q", AVG_LAR_Q },
63 Pair{ "AVG_TILE_Q", AVG_TILE_Q },
64 Pair{ "BADLARQ_FRAC", BADLARQ_FRAC },
65 Pair{ "BAD_CELLS_CORR_E", BAD_CELLS_CORR_E },
66 Pair{ "CELL_SIGNIFICANCE", CELL_SIGNIFICANCE },
67 Pair{ "CELL_SIG_SAMPLING", CELL_SIG_SAMPLING },
68 Pair{ "CENTER_LAMBDA", CENTER_LAMBDA },
69 Pair{ "CENTER_MAG", CENTER_MAG },
70 Pair{ "CENTER_X", CENTER_X },
71 Pair{ "CENTER_Y", CENTER_Y },
72 Pair{ "CENTER_Z", CENTER_Z },
73 Pair{ "DELTA_ALPHA", DELTA_ALPHA },
74 Pair{ "DELTA_PHI", DELTA_PHI },
75 Pair{ "DELTA_THETA", DELTA_THETA },
76 Pair{ "EM_PROBABILITY", EM_PROBABILITY },
77 Pair{ "ENG_BAD_CELLS", ENG_BAD_CELLS },
78 Pair{ "ENG_BAD_HV_CELLS", ENG_BAD_HV_CELLS },
79 Pair{ "ENG_FRAC_CORE", ENG_FRAC_CORE },
80 Pair{ "ENG_FRAC_EM", ENG_FRAC_EM },
81 Pair{ "ENG_FRAC_MAX", ENG_FRAC_MAX },
82 Pair{ "ENG_POS", ENG_POS },
83 Pair{ "FIRST_ENG_DENS", FIRST_ENG_DENS },
84 Pair{ "FIRST_ETA", FIRST_ETA },
85 Pair{ "FIRST_PHI", FIRST_PHI },
86 Pair{ "ISOLATION", ISOLATION },
87 Pair{ "LATERAL", LATERAL },
88 Pair{ "LONGITUDINAL", LONGITUDINAL },
89 Pair{ "MASS", MASS },
90 Pair{ "NCELL_SAMPLING", NCELL_SAMPLING },
91 Pair{ "N_BAD_CELLS", N_BAD_CELLS },
92 Pair{ "N_BAD_CELLS_CORR", N_BAD_CELLS_CORR },
93 Pair{ "N_BAD_HV_CELLS", N_BAD_HV_CELLS },
94 Pair{ "PTD", PTD },
95 Pair{ "SECOND_ENG_DENS", SECOND_ENG_DENS },
96 Pair{ "SECOND_LAMBDA", SECOND_LAMBDA },
97 Pair{ "SECOND_R", SECOND_R },
98 Pair{ "SECOND_TIME", SECOND_TIME },
99 Pair{ "SIGNIFICANCE", SIGNIFICANCE }
100 };
101 static_assert(std::ranges::is_sorted( momentMappings, std::less<>{}, &Pair::first));
102 std::optional<MomentType>
103 constexpr momentFromName(std::string_view name){
104 auto it = std::ranges::lower_bound(momentMappings, name, std::less<>{}, &Pair::first);
105 if (it == momentMappings.end()|| it->first != name) {
106 return std::nullopt;
107 }
108 return it->second;
109 }
110 std::optional<std::string_view>
111 constexpr nameFromMoment(MomentType m){
112 auto it = std::ranges::find(momentMappings, m, &Pair::second);
113 if (it == momentMappings.end()) {
114 return std::nullopt;
115 }
116 return it->first;
117 }
118}
119
120//###############################################################################
121
123 const std::string& name,
124 const IInterface* parent)
125 : AthAlgTool(type, name, parent),
126 m_calo_id(nullptr),
127 m_maxAxisAngle(20*deg),
128 m_minRLateral(4*cm),
130 m_minBadLArQuality(4000),
134 m_twoGaussianNoise(false),
135 m_caloDepthTool("CaloDepthTool",this),
136 m_larHVFraction("LArHVFraction",this),
137 m_absOpt(false)
138{
139 declareInterface<CaloClusterCollectionProcessor> (this);
140 // Name(s) of Moments to calculate
141 declareProperty("MomentsNames",m_momentsNames);
142
143 // Maximum allowed angle between shower axis and the vector pointing
144 // to the shower center from the IP in degrees. This property is needed
145 // to protect against cases where all significant cells are in one sampling
146 // and the shower axis can thus not be defined.
147 declareProperty("MaxAxisAngle",m_maxAxisAngle);
148 declareProperty("MinRLateral",m_minRLateral);
149 declareProperty("MinLLongitudinal",m_minLLongitudinal);
150 declareProperty("MinBadLArQuality",m_minBadLArQuality);
151 // Use 2-gaussian noise for Tile
152 declareProperty("TwoGaussianNoise",m_twoGaussianNoise);
153 declareProperty("LArHVFraction",m_larHVFraction,"Tool Handle for LArHVFraction");
154 // Not used anymore (with xAOD), but required when configured from COOL.
155 declareProperty("AODMomentsNames",m_momentsNamesAOD);
156 // Use weighting of neg. clusters option?
157 declareProperty("WeightingOfNegClusters", m_absOpt);
158 // Set eta boundary for transition from outer to inner wheel in EME2
159 declareProperty("EMECAbsEtaWheelTransition",m_etaInnerWheel);
160}
161
162//###############################################################################
163
165{
166 xAOD::CaloCluster dummyCluster;
167
168 // loop list of requested moments
169 std::string::size_type nstr(0); int nmom(0);
170 for (const auto& mom : m_momentsNames) {
171 ATH_MSG_DEBUG("Moment " << mom << " requested");
172 // check if moment is known (enumerator available)
173 auto fmap(momentFromName(mom));
174 if (fmap) {
175 // valid moment found
176 nstr = std::max(nstr, mom.length());
177 ++nmom;
178 if (fmap.value() == SECOND_TIME) {
179 // special flag for second moment of cell times - this moment is not
180 // calculated in this tool! Do not add to internal (!) valid moments
181 // list. Its value is available from xAOD::CaloCluster::secondTime()!
182 m_secondTime = true;
183
184 // Make sure the variable used for the moment is declared
185 // to the auxiliary variable registry. Otherwise, if we don't
186 // set the moment for the first event (perhaps because there
187 // are no clusters), then we can get warnings from AuxSelection.
188 (void)dummyCluster.getMomentValue (fmap.value());
189 } else if (fmap.value() == NCELL_SAMPLING) {
190 // flag indicates if number of cells in a sampling should be counted.
191 // This is a vector of integers counts that is filled in this tool but
192 // does not need any post-processing (e.g. normalization). It is not
193 // added to the valid moments list for this reason.
194 ATH_MSG_DEBUG("moment " << mom << " found");
195 m_nCellsPerSampling = true;
197
198 // Make sure the variable used for the moment is declared
199 // to the auxiliary variable registry.
200 (void)dummyCluster.retrieveMoment (fmap.value(), cellsdum);
201 } else if (fmap.value() == EM_PROBABILITY) {
203 << " not calculated in this tool - misconfiguration?");
204 } else {
205 // Make sure the variable used for the moment is declared
206 // to the auxiliary variable registry.
207 (void)dummyCluster.getMomentValue (fmap.value());
208
209 // all other valid moments
210 m_validMoments.push_back(fmap.value());
211 // flag some special requests
212 switch (fmap.value()) {
213 case SIGNIFICANCE:
214 case CELL_SIGNIFICANCE:
216 break;
217 case ISOLATION:
219 break;
220 case ENG_BAD_HV_CELLS:
222 break;
223 default:
224 break;
225 } // set special processing flags
226 } // moment calculated with this tool
227 } else {
228 ATH_MSG_ERROR("Moment name " << mom << " not known; known moments are:");
229 char buffer[128];
230 std::string::size_type lstr(nstr);
231 // determine field size
232 for (const auto& [name,moment] : momentMappings) {
233 lstr = std::max(lstr, name.length());
234 }
235 // print available moments
236 for (const auto& [name,moment] : momentMappings) {
237 snprintf(buffer, sizeof(buffer),"moment name: %-*.*s - enumerator: %i", (int)lstr,
238 (int)lstr, name.data(), (int)moment);
239 ATH_MSG_ERROR(buffer);
240 }
241 auto fmom(momentFromName("SECOND_TIME"));
242 snprintf(buffer, sizeof(buffer),"moment name: %-*.*s - enumerator: %i", (int)nstr,
243 (int)nstr, "SECOND_TIME", (int)fmom.value());
244 ATH_MSG_ERROR(buffer);
245 fmom = momentFromName("NCELL_SAMPLING");
246 snprintf(buffer, sizeof(buffer),"moment name: %-*.*s - enumerator: %i", (int)nstr,
247 (int)nstr, "NCELL_SAMPLING", (int)fmom.value());
248 ATH_MSG_ERROR(buffer);
249 return StatusCode::FAILURE;
250 } // found unknown moment name
251 } // loop configured moment names
252
253 // sort and remove duplicates
255 m_validMoments.erase(
257 m_validMoments.end());
258
259 // print configured moments
260 ATH_MSG_INFO("Construct and save " << nmom << " cluster moments: ");
261 char buffer[128];
262 for (auto menum : m_validMoments) {
263 snprintf(buffer, sizeof(buffer),"moment name: %-*.*s - enumerator: %i", (int)nstr,
264 (int)nstr, nameFromMoment(menum).value().data(), (int)menum);
265 ATH_MSG_INFO(buffer);
266 }
267 if (m_secondTime) {
268 auto fmom(momentFromName("SECOND_TIME"));
269 snprintf(buffer, sizeof(buffer),"moment name: %-*.*s - enumerator: %i (save only)",
270 (int)nstr, (int)nstr, "SECOND_TIME", (int)fmom.value());
271 ATH_MSG_INFO(buffer);
272 }
274 auto fmom(momentFromName("NCELL_SAMPLING"));
275 snprintf(buffer, sizeof(buffer),"moment name: %-*.*s - enumerator: %i", (int)nstr,
276 (int)nstr, "NCELL_SAMPLING", (int)fmom.value());
277 ATH_MSG_INFO(buffer);
278 }
279
280 // retrieve CaloCell ID server
281 CHECK(detStore()->retrieve(m_calo_id,"CaloCell_ID"));
282
283 // retrieve the calo depth tool
284 CHECK(m_caloDepthTool.retrieve());
285 ATH_CHECK(m_caloMgrKey.initialize());
286
287 // retrieve specific servers and tools for selected processes
289 if (m_calculateLArHVFraction) { ATH_CHECK(m_larHVFraction.retrieve()); } else { m_larHVFraction.disable(); }
290
291 return StatusCode::SUCCESS;
292}
293
295{
296 return StatusCode::SUCCESS;
297}
298
299//#############################################################################
300
302
303
304struct cellinfo {
305 double x{};
306 double y{};
307 double z{};
308 double energy{};
309 double eta{};
310 double phi{};
311 double r{};
312 double lambda{};
313 double volume{};
315 unsigned int identifier{};
316};
317
318} // namespace CaloClusterMomentsMaker_detail
319
320StatusCode
321CaloClusterMomentsMaker::execute(const EventContext& ctx,
322 xAOD::CaloClusterContainer *theClusColl)
323 const
324{
325 ATH_MSG_DEBUG("Executing " << name());
326
327 // Maps cell IdentifierHash to cluster index in cluster collection.
328 // Only used when cluster isolation moment is calculated.
329 using clusterIdx_t = std::uint16_t;
330 typedef std::pair<clusterIdx_t, clusterIdx_t> clusterPair_t;
331 std::vector<clusterPair_t> clusterIdx;
332 const clusterIdx_t noCluster = std::numeric_limits<clusterIdx_t>::max();
333
334 const CaloNoise* noise=nullptr;
337 noise=*noiseHdl;
338 }
339
341 const CaloDetDescrManager* caloDDMgr = *caloMgrHandle;
342 // Counters for number of empty and non-empty neighbor cells per sampling
343 // layer Only used when cluster isolation moment is calculated.
344 int nbEmpty[CaloCell_ID::Unknown];
345 int nbNonEmpty[CaloCell_ID::Unknown];
346
347 // prepare stuff from entire collection in case isolation moment
348 // should be calculated
349
350 if ( m_calculateIsolation ) {
351
352 if (theClusColl->size() >= noCluster) {
353 msg(MSG::ERROR) << "Too many clusters" << endmsg;
354 return StatusCode::FAILURE;
355 }
356
357 // initialize with "empty" values
358 clusterIdx.resize(m_calo_id->calo_cell_hash_max(),
359 clusterPair_t(noCluster, noCluster));
360
361 int iClus = 0;
362 for (xAOD::CaloCluster* theCluster : *theClusColl) {
363 // loop over all cell members and fill cell vector for used cells
364 xAOD::CaloCluster::cell_iterator cellIter = theCluster->cell_begin();
365 xAOD::CaloCluster::cell_iterator cellIterEnd = theCluster->cell_end();
366 for(; cellIter != cellIterEnd; cellIter++ ){
367 CxxUtils::prefetchNext(cellIter, cellIterEnd);
368 const CaloCell* pCell = *cellIter;
369
370 Identifier myId = pCell->ID();
371 IdentifierHash myHashId = m_calo_id->calo_cell_hash(myId);
372 if ( clusterIdx[(unsigned int)myHashId].first != noCluster) {
373 // check weight and assign to current cluster if weight is > 0.5
374 double weight = cellIter.weight();
375 if ( weight > 0.5 )
376 clusterIdx[(unsigned int)myHashId].first = iClus;
377 }
378 else {
379 clusterIdx[(unsigned int)myHashId].first = iClus;
380 }
381 }
382 ++iClus;
383 }
384 }
385
386 // Move allocation of temporary arrays outside the cluster loop.
387 // That way, we don't need to delete and reallocate them
388 // each time through the loop.
389
390 std::vector<CaloClusterMomentsMaker_detail::cellinfo> cellinfo;
391 std::vector<double> maxSampE (CaloCell_ID::Unknown);
392 std::vector<double> myMoments(m_validMoments.size(),0);
393 std::vector<double> myNorms(m_validMoments.size(),0);
394 std::vector<std::tuple<int,int> > nCellsSamp; nCellsSamp.reserve(CaloCell_ID::Unknown);
395 std::vector<IdentifierHash> theNeighbors;
396 // loop over individual clusters
397 xAOD::CaloClusterContainer::iterator clusIter = theClusColl->begin();
398 xAOD::CaloClusterContainer::iterator clusIterEnd = theClusColl->end();
399 int iClus = 0;
400 for( ;clusIter!=clusIterEnd;++clusIter,++iClus) {
401 xAOD::CaloCluster * theCluster = *clusIter;
402
403 double w(0),xc(0),yc(0),zc(0),mx(0),my(0),mz(0),mass(0);
404 double eBad(0),ebad_dac(0),ePos(0),eBadLArQ(0),sumSig2(0),maxAbsSig(0);
405 double eLAr2(0),eLAr2Q(0);
406 double eTile2(0),eTile2Q(0);
407 double eBadLArHV(0);
408 int nbad(0),nbad_dac(0),nBadLArHV(0);
409 unsigned int i,nSigSampl(0);
410 unsigned int theNumOfCells = theCluster->size();
411
412 // these two are needed for the LATERAL moment
413 int iCellMax(-1);
414 int iCellScndMax(-1);
415
416 cellinfo.clear();
417 if (cellinfo.capacity() == 0)
418 cellinfo.reserve (theNumOfCells*2);
419
420 for(i=0;i<(unsigned int)CaloCell_ID::Unknown;i++)
421 maxSampE[i] = 0;
422
423 if ( !m_momentsNames.empty() ) {
424 std::fill (myMoments.begin(), myMoments.end(), 0);
425 std::fill (myNorms.begin(), myNorms.end(), 0);
426 if ( m_calculateIsolation ) {
427 std::fill_n(nbNonEmpty, CaloCell_ID::Unknown, 0);
428 std::fill_n(nbEmpty, CaloCell_ID::Unknown, 0);
429 }
430
431 // loop over all cell members and calculate the center of mass
432 xAOD::CaloCluster::cell_iterator cellIter = theCluster->cell_begin();
433 xAOD::CaloCluster::cell_iterator cellIterEnd = theCluster->cell_end();
434 for(; cellIter != cellIterEnd; cellIter++ ){
435 CaloPrefetch::nextDDE(cellIter, cellIterEnd);
436
437 const CaloCell* pCell = (*cellIter);
438 Identifier myId = pCell->ID();
439 const CaloDetDescrElement* myCDDE = pCell->caloDDE();
440 double ene = pCell->e();
441 if(m_absOpt) ene = std::abs(ene);
442 double weight = cellIter.weight();//theCluster->getCellWeight(cellIter);
443 if ( pCell->badcell() ) {
444 eBad += ene*weight;
445 nbad++;
446 if(ene!=0){
447 ebad_dac+=ene*weight;
448 nbad_dac++;
449 }
450 }
451 else {
452 if ( myCDDE && ! (myCDDE->is_tile())
453 && ((pCell->provenance() & 0x2000) == 0x2000)
454 && !((pCell->provenance() & 0x0800) == 0x0800)) {
455 if ( pCell->quality() > m_minBadLArQuality ) {
456 eBadLArQ += ene*weight;
457 }
458 eLAr2 += ene*weight*ene*weight;
459 eLAr2Q += ene*weight*ene*weight*pCell->quality();
460 }
461 if ( myCDDE && myCDDE->is_tile() ) {
462 uint16_t tq = pCell->quality();
463 uint8_t tq1 = (0xFF00&tq)>>8; // quality in channel 1
464 uint8_t tq2 = (0xFF&tq); // quality in channel 2
465 // reject cells with either 0xFF00 or 0xFF
466 if ( ((tq1&0xFF) != 0xFF) && ((tq2&0xFF) != 0xFF) ) {
467 eTile2 += ene*weight*ene*weight;
468 // take the worse of both qualities (one might be 0 in
469 // 1-channel cases)
470 eTile2Q += ene*weight*ene*weight*(tq1>tq2?tq1:tq2);
471 }
472 }
473 }
474 if ( ene > 0 ) {
475 ePos += ene*weight;
476 }
477
479 // Avoid a spurious FPE from clang.
481
482 const float sigma = m_twoGaussianNoise ?\
483 noise->getEffectiveSigma(pCell->ID(),pCell->gain(),pCell->energy()) : \
484 noise->getNoise(pCell->ID(),pCell->gain());
485
486 sumSig2 += sigma*sigma;
487 // use geometry weighted energy of cell for leading cell significance
488 double Sig = (sigma>0?ene*weight/sigma:0);
489 if (m_useGPUCriteria) {
490 unsigned int thisSampl = myCDDE->getSampling();
491 if ( ( std::abs(Sig) > std::abs(maxAbsSig) ) ||
492 ( std::abs(Sig) == std::abs(maxAbsSig) && thisSampl > nSigSampl ) ||
493 ( std::abs(Sig) == std::abs(maxAbsSig) && thisSampl == nSigSampl && Sig > maxAbsSig ) ) {
494 maxAbsSig = Sig;
495 nSigSampl = thisSampl;
496 }
497
498 }
499 else {
500 if ( std::abs(Sig) > std::abs(maxAbsSig) ) {
501 maxAbsSig = Sig;
502 nSigSampl = myCDDE->getSampling();
503 }
504 }
505 }
506 if ( m_calculateIsolation ) {
507 // get all 2D Neighbours if the cell is not inside another cluster with
508 // larger weight
509
510 IdentifierHash myHashId = m_calo_id->calo_cell_hash(myId);
511 if ( clusterIdx[myHashId].first == iClus ) {
512 theNeighbors.clear();
513 m_calo_id->get_neighbours(myHashId, LArNeighbours::all2D, theNeighbors);
514 for (const auto& nhash: theNeighbors) {
515 clusterPair_t& idx = clusterIdx[nhash];
516
517 // only need to look at each cell once per cluster
518 if ( idx.second == iClus ) continue;
519 idx.second = iClus;
520
521 if ( idx.first == noCluster ) {
522 ++ nbEmpty[m_calo_id->calo_sample(nhash)];
523 } else if ( idx.first != iClus ) {
524 ++ nbNonEmpty[m_calo_id->calo_sample(nhash)];
525 }
526
527 }
528 }
529 }
530
531 if ( myCDDE != nullptr ) {
532 if ( m_nCellsPerSampling ) {
533 CaloCell_ID::CaloSample sam = myCDDE->getSampling();
534 size_t idx((size_t)sam);
535 if ( idx >= nCellsSamp.size() ) { nCellsSamp.resize(idx+1, { 0, 0 } ); }
536 std::get<0>(nCellsSamp[idx])++;
537 // special count for inner wheel cells in EME2
538 if ( sam == CaloCell_ID::EME2 && std::abs(myCDDE->eta()) > m_etaInnerWheel ) { std::get<1>(nCellsSamp[idx])++; }
539 }
540 if ( ene > 0. && weight > 0) {
541 // get all geometric information needed ...
542 cellinfo.push_back (CaloClusterMomentsMaker_detail::cellinfo {
543 .x = myCDDE->x(),
544 .y = myCDDE->y(),
545 .z = myCDDE->z(),
546 .energy = ene*weight,
547 .eta = myCDDE->eta(),
548 .phi = myCDDE->phi(),
549 .r = 0, // These two are filled in later.
550 .lambda = 0,
551 .volume = myCDDE->volume(),
552 .sample = myCDDE->getSampling(),
553 .identifier = cellIter.index()
554 //Using the index instead of the hash ID as disambiguation criterion
555 //is relevant for the GPU now that we no longer assume the two are the same,
556 //and this gives us better performance.
557 });
558
559 CaloClusterMomentsMaker_detail::cellinfo& ci = cellinfo.back();
560
561 if ( ci.energy > maxSampE[(unsigned int)ci.sample] )
562 maxSampE[(unsigned int)ci.sample] = ci.energy;
563
564 if (m_useGPUCriteria) {
565 if (iCellMax < 0 ||
566 ci.energy > cellinfo[iCellMax].energy ||
567 (ci.energy == cellinfo[iCellMax].energy && ci.identifier > cellinfo[iCellMax].identifier) ) {
568 iCellScndMax = iCellMax;
569 iCellMax = cellinfo.size()-1;
570 }
571 else if (iCellScndMax < 0 ||
572 ci.energy > cellinfo[iCellScndMax].energy ||
573 (ci.energy == cellinfo[iCellScndMax].energy && ci.identifier > cellinfo[iCellScndMax].identifier) )
574 {
575 iCellScndMax = cellinfo.size()-1;
576 }
577 }
578 else {
579 if (iCellMax < 0 || ci.energy > cellinfo[iCellMax].energy ) {
580 iCellScndMax = iCellMax;
581 iCellMax = cellinfo.size()-1;
582 }
583 else if (iCellScndMax < 0 ||
584 ci.energy > cellinfo[iCellScndMax].energy )
585 {
586 iCellScndMax = cellinfo.size()-1;
587 }
588 }
589
590 xc += ci.energy*ci.x;
591 yc += ci.energy*ci.y;
592 zc += ci.energy*ci.z;
593
594 double dir = ci.x*ci.x+ci.y*ci.y+ci.z*ci.z;
595
596 if ( dir > 0) {
597 dir = sqrt(dir);
598 dir = 1./dir;
599 }
600 mx += ci.energy*ci.x*dir;
601 my += ci.energy*ci.y*dir;
602 mz += ci.energy*ci.z*dir;
603
604 w += ci.energy;
605 } // cell has E>0 and weight != 0
606 } // cell has valid DDE
607 } //end of loop over all cells
609 const auto hvFrac=m_larHVFraction->getLArHVFrac(theCluster->getCellLinks(),ctx);
610 eBadLArHV= hvFrac.first;
611 nBadLArHV=hvFrac.second;
612 }
613
614 if ( w > 0 || (m_useGPUCriteria && w >= 0) ) {
615 mass = w*w - mx*mx - my*my - mz*mz;
616 if ( mass > 0) {
617 mass = sqrt(mass);
618 }
619 else {
620 // make mass negative if m^2 was negative
621 mass = -sqrt(-mass);
622 }
623
624 if (w == 0) {
625 w = 1.0;
626 }
627
628 xc/=w;
629 yc/=w;
630 zc/=w;
631 Amg::Vector3D showerCenter(xc,yc,zc);
632 w=0;
633
634
635 //log << MSG::WARNING << "Found bad cells " << xbad_dac << " " << ybad_dac << " " << zbad_dac << " " << ebad_dac << endmsg;
636 //log << MSG::WARNING << "Found Cluster " << xbad_dac << " " << ybad_dac << " " << zbad_dac << " " << endmsg;
637 // shower axis is just the vector pointing from the IP to the shower center
638 // in case there are less than 3 cells in the cluster
639
640 Amg::Vector3D showerAxis(xc,yc,zc);
641 showerAxis = showerAxis.normalized();
642
643 // otherwise the principal direction with the largest absolute
644 // eigenvalue will be used unless it's angle w.r.t. the vector pointing
645 // from the IP to the shower center is larger than allowed by the
646 // property m_maxAxisAngle
647
648 double angle(0),deltaPhi(0),deltaTheta(0);
649 if ( cellinfo.size() > 2 ) {
650 Eigen::Matrix3d C=Eigen::Matrix3d::Zero();
651 for(const CaloClusterMomentsMaker_detail::cellinfo& ci : cellinfo) {
652 const double e2 = ci.energy * ci.energy;
653
654 C(0,0) += e2*(ci.x-xc)*(ci.x-xc);
655 C(1,0) += e2*(ci.x-xc)*(ci.y-yc);
656 C(2,0) += e2*(ci.x-xc)*(ci.z-zc);
657
658 C(1,1) += e2*(ci.y-yc)*(ci.y-yc);
659 C(2,1) += e2*(ci.y-yc)*(ci.z-zc);
660
661 C(2,2) += e2*(ci.z-zc)*(ci.z-zc);
662 w += e2;
663 }
664 C/=(w != 0 ? w : 1.0);
665
666 Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(C);
667 if (eigensolver.info() != Eigen::Success) {
668 msg(MSG::WARNING) << "Failed to compute Eigenvalues -> Can't determine shower axis" << endmsg;
669 }
670 else {
671 // don't use the principal axes if at least one of the 3
672 // diagonal elements is 0
673
674 const Eigen::Vector3d& S=eigensolver.eigenvalues();
675 const Eigen::Matrix3d& U=eigensolver.eigenvectors();
676
677 const double epsilon = 1.E-6;
678
679 if ( std::abs(S[0]) >= epsilon && std::abs(S[1]) >= epsilon && std::abs(S[2]) >= epsilon ) {
680
681 Amg::Vector3D prAxis(showerAxis);
682 int iEigen = -1;
683
684 for (i=0;i<3;i++) {
685 Amg::Vector3D tmpAxis=U.col(i);
686
687 // calculate the angle
688 double tmpAngle=Amg::angle(tmpAxis,showerAxis);
689
690 if ( tmpAngle > 90*deg ) {
691 tmpAngle = 180*deg - tmpAngle;
692 tmpAxis = -tmpAxis;
693 }
694
695 if ( iEigen == -1 || tmpAngle < angle ) {
696 iEigen = i;
697 angle = tmpAngle;
698 prAxis = tmpAxis;
699 }
700 }//end for loop
701
702 // calculate theta and phi angle differences
703
704 deltaPhi = CaloPhiRange::diff(showerAxis.phi(),prAxis.phi());
705
706 deltaTheta = showerAxis.theta() - prAxis.theta();
707
708 // check the angle
709
710 if ( angle < m_maxAxisAngle ) {
711 showerAxis = prAxis;
712 }
713 else
714 ATH_MSG_DEBUG("principal Direction (" << prAxis[Amg::x] << ", "
715 << prAxis[Amg::y] << ", " << prAxis[Amg::z] << ") deviates more than "
716 << m_maxAxisAngle*(1./deg)
717 << " deg from IP-to-ClusterCenter-axis (" << showerAxis[Amg::x] << ", "
718 << showerAxis[Amg::y] << ", " << showerAxis[Amg::z] << ")");
719 }//end if std::abs(S)<epsilon
720 else {
721 ATH_MSG_DEBUG("Eigenvalues close to 0, do not use principal axis");
722 }
723 }//end got eigenvalues
724 } //end if cellinfo.size()>2
725
726 ATH_MSG_DEBUG("Shower Axis = (" << showerAxis[Amg::x] << ", "
727 << showerAxis[Amg::y] << ", " << showerAxis[Amg::z] << ")");
728
729
730 // calculate radial distance from and the longitudinal distance
731 // along the shower axis for each cell. The cluster center is
732 // at r=0 and lambda=0
733
734 for (auto& ci : cellinfo) {
735 const Amg::Vector3D currentCell(ci.x,ci.y,ci.z);
736 // calculate distance from shower axis r
737 ci.r = ((currentCell-showerCenter).cross(showerAxis)).mag();
738 // calculate distance from shower center along shower axis
739 ci.lambda = (currentCell-showerCenter).dot(showerAxis);
740 }
741
742 // loop over all positive energy cells and calculate all desired moments
743
744 // define common norm for all simple moments
745 double commonNorm = 0;
746 double phi0 = cellinfo.size() > 0 ? cellinfo[0].phi : 0;
747
748 for(unsigned i=0;i<cellinfo.size();i++) {
749 const CaloClusterMomentsMaker_detail::cellinfo& ci = cellinfo[i];
750 // loop over all valid moments
751 commonNorm += ci.energy;
752 for(size_t iMoment = 0, size = m_validMoments.size();
753 iMoment != size;
754 ++ iMoment)
755 {
756 // now calculate the actual moments
757 switch (m_validMoments[iMoment]) {
758 case FIRST_ETA:
759 myMoments[iMoment] += ci.energy*ci.eta;
760 break;
761 case FIRST_PHI:
762 // first cell decides the sign in order to avoid
763 // overlap problem at phi = -pi == +pi
764 // need to be normalized to the range [-pi,+pi] in the end
765 myMoments[iMoment] += ci.energy * proxim (ci.phi, phi0);
766 break;
767 case SECOND_R:
768 myMoments[iMoment] += ci.energy*ci.r*ci.r;
769 break;
770 case SECOND_LAMBDA:
771 myMoments[iMoment] += ci.energy*ci.lambda*ci.lambda;
772 break;
773 case LATERAL:
774 if ( (int)i != iCellMax && (int)i != iCellScndMax ) {
775 myMoments[iMoment] += ci.energy*ci.r*ci.r;
776 myNorms[iMoment] += ci.energy*ci.r*ci.r;
777 }
778 else {
779 double rm = ci.r;
780 if ( rm < m_minRLateral )
781 rm = m_minRLateral;
782 myNorms[iMoment] += rm*rm*ci.energy;
783 }
784 break;
785 case LONGITUDINAL:
786 if ( (int)i != iCellMax && (int)i != iCellScndMax ) {
787 myMoments[iMoment] += ci.energy*ci.lambda*ci.lambda;
788 myNorms[iMoment] += ci.energy*ci.lambda*ci.lambda;
789 }
790 else {
791 double lm = ci.lambda;
792 if ( lm < m_minLLongitudinal )
794 myNorms[iMoment] += lm*lm*ci.energy;
795 }
796 break;
797 case FIRST_ENG_DENS:
798 if ( ci.volume > 0 ) {
799 myMoments[iMoment] += ci.energy*ci.energy/ci.volume;
800 myNorms[iMoment] += ci.energy;
801 }
802 break;
803 case SECOND_ENG_DENS:
804 if ( ci.volume > 0 ) {
805 myMoments[iMoment] += ci.energy*std::pow(ci.energy/ci.volume,2);
806 myNorms[iMoment] += ci.energy;
807 }
808 break;
809 case ENG_FRAC_EM:
810 if ( ci.sample == CaloCell_ID::EMB1
811 || ci.sample == CaloCell_ID::EMB2
812 || ci.sample == CaloCell_ID::EMB3
813 || ci.sample == CaloCell_ID::EME1
814 || ci.sample == CaloCell_ID::EME2
815 || ci.sample == CaloCell_ID::EME3
816 || ci.sample == CaloCell_ID::FCAL0 )
817 myMoments[iMoment] += ci.energy;
818 break;
819 case ENG_FRAC_MAX:
820 if ( (int)i == iCellMax )
821 myMoments[iMoment] = ci.energy;
822 break;
823 case PTD:
824 // do not convert to pT since clusters are small and
825 // there is virtually no difference and cosh just costs
826 // time ...
827 myMoments[iMoment] += ci.energy*ci.energy;
828 myNorms[iMoment] += ci.energy;
829 break;
830 default:
831 // nothing to be done for other moments
832 break;
833 }
834 }
835 } //end of loop over cell
836
837
838 // assign moments which don't need the loop over the cells
839 for (size_t iMoment = 0, size = m_validMoments.size();
840 iMoment != size;
841 ++ iMoment)
842 {
843 // now calculate the actual moments
844 switch (m_validMoments[iMoment]) {
845 case FIRST_ETA:
846 case FIRST_PHI:
847 case SECOND_R:
848 case SECOND_LAMBDA:
849 case ENG_FRAC_EM:
850 case ENG_FRAC_MAX:
851 myNorms[iMoment] = commonNorm;
852 break;
853 case DELTA_PHI:
854 myMoments[iMoment] = deltaPhi;
855 break;
856 case DELTA_THETA:
857 myMoments[iMoment] = deltaTheta;
858 break;
859 case DELTA_ALPHA:
860 myMoments[iMoment] = angle;
861 break;
862 case CENTER_X:
863 myMoments[iMoment] = showerCenter.x();
864 break;
865 case CENTER_Y:
866 myMoments[iMoment] = showerCenter.y();
867 break;
868 case CENTER_Z:
869 myMoments[iMoment] = showerCenter.z();
870 break;
871 case CENTER_MAG:
872 myMoments[iMoment] = showerCenter.mag();
873 break;
874 case CENTER_LAMBDA:
875 // calculate the longitudinal distance along the shower axis
876 // of the shower center from the calorimeter start
877
878 // first need calo boundary at given eta phi try LAREM barrel
879 // first, then LAREM endcap OW, then LAREM endcap IW, then
880 // FCal
881 {
882 double r_calo(0),z_calo(0),lambda_c(0);
883 r_calo = m_caloDepthTool->get_entrance_radius(CaloCell_ID::EMB1,
884 showerCenter.eta(),
885 showerCenter.phi(),
886 caloDDMgr);
887 if ( r_calo == 0 ) {
888 z_calo = m_caloDepthTool->get_entrance_z(CaloCell_ID::EME1,
889 showerCenter.eta(),
890 showerCenter.phi(),
891 caloDDMgr);
892 if ( z_calo == 0 )
893 z_calo = m_caloDepthTool->get_entrance_z(CaloCell_ID::EME2,
894 showerCenter.eta(),
895 showerCenter.phi(),
896 caloDDMgr);
897 if ( z_calo == 0 )
898 z_calo = m_caloDepthTool->get_entrance_z(CaloCell_ID::FCAL0,
899 showerCenter.eta(),
900 showerCenter.phi(),
901 caloDDMgr);
902 if ( z_calo == 0 ) // for H6 TB without EMEC outer wheel
903 z_calo = m_caloDepthTool->get_entrance_z(CaloCell_ID::HEC0,
904 showerCenter.eta(),
905 showerCenter.phi(),
906 caloDDMgr);
907 if ( z_calo != 0 && showerAxis.z() != 0 ) {
908 lambda_c = std::abs((z_calo-showerCenter.z())/showerAxis.z());
909 }
910 }
911 else {
912 double r_s2 = showerAxis.x()*showerAxis.x()
913 +showerAxis.y()*showerAxis.y();
914 double r_cs = showerAxis.x()*showerCenter.x()
915 +showerAxis.y()*showerCenter.y();
916 double r_cr = showerCenter.x()*showerCenter.x()
917 +showerCenter.y()*showerCenter.y()-r_calo*r_calo;
918 if ( r_s2 > 0 ) {
919 double det = r_cs*r_cs/(r_s2*r_s2) - r_cr/r_s2;
920 if ( det > 0 ) {
921 det = sqrt(det);
922 double l1(-r_cs/r_s2);
923 double l2(l1);
924 l1 += det;
925 l2 -= det;
926 if ( std::abs(l1) < std::abs(l2) )
927 lambda_c = std::abs(l1);
928 else
929 lambda_c = std::abs(l2);
930 }
931 }
932 }
933 myMoments[iMoment] = lambda_c;
934 }
935 break;
936 case ENG_FRAC_CORE:
937 for(i=0;i<(int)CaloCell_ID::Unknown;i++)
938 myMoments[iMoment] += maxSampE[i];
939 myNorms[iMoment] = commonNorm;
940 break;
941 case ISOLATION:
942 {
943 // loop over empty and filled perimeter cells and
944 // get a weighted ratio by means of energy fraction per layer
945 for(unsigned int i=0; i != CaloSampling::Unknown; ++ i) {
947 if (theCluster->hasSampling(s)) {
948 const double eSample = theCluster->eSample(s);
949 if (eSample > 0) {
950 int nAll = nbEmpty[i]+nbNonEmpty[i];
951 if (nAll > 0) {
952 myMoments[iMoment] += (eSample*nbEmpty[i])/nAll;
953 myNorms[iMoment] += eSample;
954 }
955 }//end of eSample>0
956 }//end has sampling
957 }//end loop over samplings
958 }
959 break;
960 case ENG_BAD_CELLS:
961 myMoments[iMoment] = eBad;
962 break;
963 case N_BAD_CELLS:
964 myMoments[iMoment] = nbad;
965 break;
966 case N_BAD_CELLS_CORR:
967 myMoments[iMoment] = nbad_dac;
968 break;
969 case BAD_CELLS_CORR_E:
970 myMoments[iMoment] = ebad_dac;
971 break;
972 case BADLARQ_FRAC:
973 myMoments[iMoment] = eBadLArQ/(theCluster->e()!=0.?theCluster->e():1.);
974 break;
975 case ENG_POS:
976 myMoments[iMoment] = ePos;
977 break;
978 case SIGNIFICANCE:
979 myMoments[iMoment] = (sumSig2>0?theCluster->e()/sqrt(sumSig2):0.);
980 break;
981 case CELL_SIGNIFICANCE:
982 myMoments[iMoment] = maxAbsSig;
983 break;
984 case CELL_SIG_SAMPLING:
985 myMoments[iMoment] = nSigSampl;
986 break;
987 case AVG_LAR_Q:
988 myMoments[iMoment] = eLAr2Q/(eLAr2>0?eLAr2:1);
989 break;
990 case AVG_TILE_Q:
991 myMoments[iMoment] = eTile2Q/(eTile2>0?eTile2:1);
992 break;
993 case ENG_BAD_HV_CELLS:
994 myMoments[iMoment] = eBadLArHV;
995 break;
996 case N_BAD_HV_CELLS:
997 myMoments[iMoment] = nBadLArHV;
998 break;
999 case PTD:
1000 myMoments[iMoment] = sqrt(myMoments[iMoment]);
1001 break;
1002 case MASS:
1003 myMoments[iMoment] = mass;
1004 break;
1005 default:
1006 // nothing to be done for other moments
1007 break;
1008 }
1009 }
1010 }
1011
1012 // normalize moments and copy to Cluster Moment Store
1013 size_t size= m_validMoments.size();
1014 for (size_t iMoment = 0; iMoment != size; ++iMoment) {
1016 if ( myNorms[iMoment] != 0 )
1017 myMoments[iMoment] /= myNorms[iMoment];
1018 if ( moment == FIRST_PHI )
1019 myMoments[iMoment] = CaloPhiRange::fix(myMoments[iMoment]);
1020 theCluster->insertMoment(moment,myMoments[iMoment]);
1021 } // loop on moments for cluster
1022 } // check on requested moments
1023 // check on second moment of time if requested
1024 if ( m_secondTime ) { theCluster->insertMoment(SECOND_TIME,theCluster->secondTime()); }
1025 // check on number of cells per sampling moment if requested
1026 if ( m_nCellsPerSampling ) {
1027 for ( size_t isam(0); isam < nCellsSamp.size(); ++isam ) {
1028 theCluster->setNumberCellsInSampling((CaloCell_ID::CaloSample)isam,std::get<0>(nCellsSamp.at(isam)),false);
1029 if ( isam == (size_t)CaloCell_ID::EME2 && std::get<1>(nCellsSamp.at(isam)) > 0 ) {
1030 theCluster->setNumberCellsInSampling((CaloCell_ID::CaloSample)isam,std::get<1>(nCellsSamp.at(isam)),true);
1031 }
1032 } // loop on samplings
1033 nCellsSamp.clear();
1034 }
1035 } // loop on clusters
1036
1037 return StatusCode::SUCCESS;
1038}
Scalar deltaPhi(const MatrixBase< Derived > &vec) const
#define endmsg
STL-style allocator wrapper for ArenaPoolAllocator.
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(x,...)
#define ATH_MSG_INFO(x,...)
CaloPhiRange class declaration.
#define CHECK(...)
Evaluate an expression and check for errors.
#define deg
size_t size() const
Number of registered mappings.
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
AthAlgTool(const std::string &type, const std::string &name, const IInterface *parent)
Constructor with parameters:
Gaudi::Details::PropertyBase & declareProperty(Gaudi::Property< T, V, H > &t)
const ServiceHandle< StoreGateSvc > & detStore() const
MsgStream & msg() const
CaloSampling::CaloSample CaloSample
Definition CaloCell_ID.h:53
Data object for each calorimeter readout cell.
Definition CaloCell.h:57
virtual double e() const override final
get energy (data member) (synonym to method energy()
Definition CaloCell.h:333
double energy() const
get energy (data member)
Definition CaloCell.h:327
const CaloDetDescrElement * caloDDE() const
get pointer to CaloDetDescrElement (data member)
Definition CaloCell.h:321
uint16_t provenance() const
get provenance (data member)
Definition CaloCell.h:354
uint16_t quality() const
get quality (data member)
Definition CaloCell.h:348
virtual bool badcell() const
check is cell is dead
Definition CaloCell.cxx:144
CaloGain::CaloGain gain() const
get gain (data member )
Definition CaloCell.h:361
Identifier ID() const
get ID (from cached data member) non-virtual and inline for fast access
Definition CaloCell.h:295
SG::ReadCondHandleKey< CaloDetDescrManager > m_caloMgrKey
double m_maxAxisAngle
the maximal allowed deviation from the IP-to-ClusterCenter-axis.
bool m_calculateIsolation
Set to true if cluster isolation is to be calculated.
double m_minRLateral
the minimal in the definition of the Lateral moment
bool m_calculateSignificance
Set to true if significance moments are need.
bool m_calculateLArHVFraction
Set to true to calculate E and N of cells affected by LAr HV corrections.
bool m_secondTime
Retrieve second moment of cell times and store as moment.
ToolHandle< CaloDepthTool > m_caloDepthTool
std::vector< xAOD::CaloCluster::MomentType > m_validMoments
set of moments which will be calculated.
virtual StatusCode execute(const EventContext &ctx, xAOD::CaloClusterContainer *theClusColl) const override final
Execute on an entire collection of clusters.
CaloClusterMomentsMaker(const std::string &type, const std::string &name, const IInterface *parent)
std::string m_momentsNamesAOD
Not used anymore (with xAOD), but required when configured from COOL.
Gaudi::Property< bool > m_useGPUCriteria
bool m_absOpt
if set to true use abs E value of cells to calculate cluster moments
bool m_nCellsPerSampling
store number of cells per sampling layer as moment
double m_minLLongitudinal
the minimal in the definition of the Longitudinal moment
std::vector< std::string > m_momentsNames
vector holding the input list of names of moments to calculate.
double m_etaInnerWheel
Transition from outer to inner wheel in EME2.
double m_minBadLArQuality
the minimal cell quality in the LAr for declaring a cell bad
bool m_twoGaussianNoise
if set to true use 2-gaussian noise description for TileCal
virtual StatusCode finalize() override
ToolHandle< ILArHVFraction > m_larHVFraction
SG::ReadCondHandleKey< CaloNoise > m_noiseCDOKey
Key of the CaloNoise Conditions data object.
virtual StatusCode initialize() override
This class groups all DetDescr information related to a CaloCell.
CaloCell_ID::CaloSample getSampling() const
cell sampling
This class provides the client interface for accessing the detector description information common to...
static double fix(double phi)
static double diff(double phi1, double phi2)
simple phi1 - phi2 calculation, but result is fixed to respect range.
DataModel_detail::iterator< DataVector > iterator
Definition DataVector.h:865
const_iterator end() const noexcept
Return a const_iterator pointing past the end of the collection.
const_iterator begin() const noexcept
Return a const_iterator pointing at the beginning of the collection.
size_type size() const noexcept
Returns the number of elements in the collection.
This is a "hash" representation of an Identifier.
bool retrieveMoment(MomentType type, double &value) const
Retrieve individual moment.
double getMomentValue(MomentType type) const
Retrieve individual moment - no check for existance! Returns -999 on error.
const CaloClusterCellLink * getCellLinks() const
Get a pointer to the CaloClusterCellLink object (const version).
size_t size() const
size method (forwarded from CaloClusterCellLink obj)
CaloClusterCellLink::iterator cell_iterator
Iterator of the underlying CaloClusterCellLink (non-const version).
virtual double e() const
The total energy of the particle.
void insertMoment(MomentType type, double value)
const_cell_iterator cell_end() const
std::vector< uint16_t > ncells_store_t
Store type for number-of-cells-in-sampling counter.
float eSample(const CaloSample sampling) const
flt_t secondTime() const
Access second moment of cell timing distribution.
MomentType
Enums to identify different moments.
const_cell_iterator cell_begin() const
Iterator of the underlying CaloClusterCellLink (const version).
void setNumberCellsInSampling(CaloSampling::CaloSample samp, int ncells, bool isInnerWheel=false)
Set the number of cells in a sampling layer.
CaloSampling::CaloSample CaloSample
bool hasSampling(const CaloSample s) const
Checks if certain smapling contributes to cluster.
struct color C
double angle(const Amg::Vector3D &v1, const Amg::Vector3D &v2)
calculates the opening angle between two vectors
Eigen::Matrix< double, 3, 1 > Vector3D
void nextDDE(Iter iter, Iter endIter)
Prefetch next CaloDDE.
void prefetchNext(Iter iter, Iter endIter)
Prefetch next object in sequence.
Definition prefetch.h:130
bool FIRST_ENG_DENS(const xAOD::TauJet &, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
bool CENTER_MAG(const xAOD::TauJet &, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
bool CENTER_LAMBDA(const xAOD::TauJet &, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
bool EM_PROBABILITY(const xAOD::TauJet &, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
bool SECOND_R(const xAOD::TauJet &, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
bool SECOND_LAMBDA(const xAOD::TauJet &, const xAOD::CaloVertexedTopoCluster &cluster, float &out)
Definition dot.py:1
DataModel_detail::iterator< DVL > unique(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of unique for DataVector/List.
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
CaloCluster_v1 CaloCluster
Define the latest version of the calorimeter cluster class.
CaloClusterContainer_v1 CaloClusterContainer
Define the latest version of the calorimeter cluster container.
Functions to prefetch blocks of memory.
double proxim(double b, double a)
Definition proxim.h:16
Tell the compiler to optimize assuming that FP may trap.
#define CXXUTILS_TRAPPING_FP
Definition trapping_fp.h:24