ATLAS Offline Software
Loading...
Searching...
No Matches
AnomalyDetectionBDT.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
5/***************************
6
7 * AnomalyDetectionBDT.cxx
8 * Created by Santiago Cane 4/15/2025
9 *
10 * @brief Algorithm is a Boosted Decision Tree for regression.
11 * It uses the Pt, Eta and Phi of 3 muons to regress an anomaly score of a
12 * previously trained Variational Autoencoder and compares it to a threshold value.
13 *
14 *
15 *@param ScoreThreshold
16 *
17 ***************************/
18
23#ifndef TRIGCONF_STANDALONE
25#endif
26#include <fstream>
27
28REGISTER_ALG_TCS(AnomalyDetectionBDT)
29
30template <typename T>
31std::string
32vectorToString(const std::vector<T>& vec) {
33 std::ostringstream oss;
34 oss << "[";
35 bool first = true;
36 for (const auto& val : vec) {
37 if (!first) {
38 oss << ", ";
39 }
40 oss << val;
41 first = false;
42 }
43 oss << "]";
44 return oss.str();
45}
46
47namespace TCS {
48 std::ostream&
49 operator<<(std::ostream& os, const TCS::Bin& bin) {
50 os << "Bin(score=" << bin.score << ", nVar=" << bin.nVar
51 << ", minVals=" << vectorToString(bin.minVals)
52 << ", maxVals=" << vectorToString(bin.maxVals) << ")";
53 return os;
54 }
55}
56
57namespace {
58
59 int scale(float val,float min, float max, int bits=7) {
60 float arrRange = max - min;
61 int nPoints = (1 << bits) - 1;
62 float resolution = arrRange / nPoints;
63 int out = static_cast<int>((val - min) / resolution);
64
65 // Staying within a valid range
66 if (out < 0) out = 0;
67 if (out > nPoints) out = nPoints;
68
69 return out;
70 }
71
72}
73
74namespace {
75 int mapval(int value, const std::string & vartype){
76
77 static const std::map<int,int> eta_LUT = {
79 //Add eta LUT here
80 {-100,4},
81 {-99,5},
82 {-98,5},
83 {-97,6},
84 {-96,7},
85 {-95,7},
86 {-94,8},
87 {-93,8},
88 {-92,9},
89 {-91,9},
90 {-90,10},
91 {-89,11},
92 {-88,11},
93 {-87,12},
94 {-86,12},
95 {-85,13},
96 {-84,14},
97 {-83,14},
98 {-82,15},
99 {-81,15},
100 {-80,16},
101 {-79,17},
102 {-78,17},
103 {-77,18},
104 {-76,18},
105 {-75,19},
106 {-74,19},
107 {-73,20},
108 {-72,21},
109 {-71,21},
110 {-70,22},
111 {-69,22},
112 {-68,23},
113 {-67,24},
114 {-66,24},
115 {-65,25},
116 {-64,25},
117 {-63,26},
118 {-62,27},
119 {-61,27},
120 {-60,28},
121 {-59,28},
122 {-58,29},
123 {-57,29},
124 {-56,30},
125 {-55,31},
126 {-54,31},
127 {-53,32},
128 {-52,32},
129 {-51,33},
130 {-50,34},
131 {-49,34},
132 {-48,35},
133 {-47,35},
134 {-46,36},
135 {-45,37},
136 {-44,37},
137 {-43,38},
138 {-42,38},
139 {-41,39},
140 {-40,39},
141 {-39,40},
142 {-38,41},
143 {-37,41},
144 {-36,42},
145 {-35,42},
146 {-34,43},
147 {-33,44},
148 {-32,44},
149 {-31,45},
150 {-30,45},
151 {-29,46},
152 {-28,47},
153 {-27,47},
154 {-26,48},
155 {-25,48},
156 {-24,49},
157 {-23,49},
158 {-22,50},
159 {-21,51},
160 {-20,51},
161 {-19,52},
162 {-18,52},
163 {-17,53},
164 {-16,54},
165 {-15,54},
166 {-14,55},
167 {-13,55},
168 {-12,56},
169 {-11,57},
170 {-10,57},
171 {-9,58},
172 {-8,58},
173 {-7,59},
174 {-6,59},
175 {-5,60},
176 {-4,61},
177 {-3,61},
178 {-2,62},
179 {-1,62},
180 {0,63},
181 {1,64},
182 {2,64},
183 {3,65},
184 {4,65},
185 {5,66},
186 {6,67},
187 {7,67},
188 {8,68},
189 {9,68},
190 {10,69},
191 {11,69},
192 {12,70},
193 {13,71},
194 {14,71},
195 {15,72},
196 {16,72},
197 {17,73},
198 {18,74},
199 {19,74},
200 {20,75},
201 {21,75},
202 {22,76},
203 {23,77},
204 {24,77},
205 {25,78},
206 {26,78},
207 {27,79},
208 {28,79},
209 {29,80},
210 {30,81},
211 {31,81},
212 {32,82},
213 {33,82},
214 {34,83},
215 {35,84},
216 {36,84},
217 {37,85},
218 {38,85},
219 {39,86},
220 {40,87},
221 {41,87},
222 {42,88},
223 {43,88},
224 {44,89},
225 {45,89},
226 {46,90},
227 {47,91},
228 {48,91},
229 {49,92},
230 {50,92},
231 {51,93},
232 {52,94},
233 {53,94},
234 {54,95},
235 {55,95},
236 {56,96},
237 {57,97},
238 {58,97},
239 {59,98},
240 {60,98},
241 {61,99},
242 {62,99},
243 {63,100},
244 {64,101},
245 {65,101},
246 {66,102},
247 {67,102},
248 {68,103},
249 {69,104},
250 {70,104},
251 {71,105},
252 {72,105},
253 {73,106},
254 {74,107},
255 {75,107},
256 {76,108},
257 {77,108},
258 {78,109},
259 {79,109},
260 {80,110},
261 {81,111},
262 {82,111},
263 {83,112},
264 {84,112},
265 {85,113},
266 {86,114},
267 {87,114},
268 {88,115},
269 {89,115},
270 {90,116},
271 {91,117},
272 {92,117},
273 {93,118},
274 {94,118},
275 {95,119},
276 {96,119},
277 {97,120},
278 {98,121},
279 {99,121},
280 {100,122}
282 };
283
284 static const std::map<int,int> phi_LUT = []{
285 std::map<int,int> m;
286 for (int i = 0; i <= 125; ++i) m.emplace(i,i);
287 m.emplace(126,127);
288 m.emplace(127,127);
289 return m;
290 }();
291
292 static const std::map<int,int> pt_LUT = {
294 //Add pt LUT here
295 {30,0},
296 {40,8},
297 {50,15},
298 {60,22},
299 {70,30},
300 {80,37},
301 {90,45},
302 {100,52},
303 {110,59},
304 {120,67},
305 {130,74},
306 {140,81},
307 {150,89},
308 {160,96},
309 {170,104},
310 {180,111},
311 {190,118},
312 {200,126}
314 };
315
316 //If a transformation is not found in the provided LUT, the function will return the original input
317
318 if(vartype=="eta"){
319 auto it_eta = eta_LUT.find(value);
320 return it_eta != eta_LUT.end() ? it_eta->second : value;
321 }
322
323 else if (vartype=="phi"){
324 auto it_phi = phi_LUT.find(value);
325 return it_phi != phi_LUT.end() ? it_phi->second : value;
326 }
327
328 else if(vartype =="pt"){
329 auto it_pt = pt_LUT.find(value);
330 return it_pt != pt_LUT.end() ? it_pt->second : value;
331 }
332
333 else{
334 return value;
335 }
336 }
337}
338
339TCS::Bin::Bin(const nlohmann::json& obj, int nVars)
340: score(obj["score"]), nVar(nVars) {
341 for (auto &[name, val] : obj["ranges"]["min"].items()) {
342 // this works for vectors [a,b,c] and dictionary values {"0": a, "1": b, "2": c}
343 minVals.push_back(val);
344 }
345 for (auto &[name, val] : obj["ranges"]["max"].items()) {
346 maxVals.push_back(val);
347 }
348}
349
350bool
351TCS::Bin::isInside(const std::vector<int64_t>& inputEvent) const {
352 for (size_t i = 0; i < inputEvent.size(); ++i) {
353 if (inputEvent[i] <= minVals[i]) {
354 return false;
355 }
356 else if (inputEvent[i] > maxVals[i]) {
357 return false;
358 }
359 }
360 return true;
361}
362
363
364TCS::Tree::Tree(const nlohmann::json& obj, int nVars) {
365 for (auto &[binName, bin] : obj["bins"].items()) {
366 bins.push_back(Bin(bin, nVars));
367 }
368}
369
370int64_t
371TCS::Tree::getTreeScore(const std::vector<int64_t>& inputEvent) const {
372 for(const Bin& bin : bins) {
373 if(bin.isInside(inputEvent)) {
374 return bin.score;
375 }
376 }
377 return 0;
378 //throw std::runtime_error("AnomalyDetection: the code has reached an unreachable state");
379}
380
381
382// constructor
384: DecisionAlg(name) {
385 defineParameter("MinET1",0);
386 defineParameter("MinET2",0);
387 defineParameter("ScoreThreshold", 1, 0);
388 defineParameter("ScoreThreshold", 1, 1);
389 defineParameter("MaxTob",3);
390 defineParameter("NumResultBits",2);
391 setNumberOutputBits(2); // 2 decision bits for 2 different score thresholds
392 // (BDT Regression score is calculated internally)
393}
394
395// destructor
398
401
402 p_MaxTob = parameter("MaxTob").value();
403 p_minEt1 = parameter("MinET1").value();
404 p_minEt2 = parameter("MinET2").value();
405 for (size_t i=0; i <numberOutputBits(); ++i) {
406 p_ScoreThreshold[i] = parameter("ScoreThreshold", i).value();
407 }
408 TRG_MSG_INFO("ADBDT: Threshold set to " << p_ScoreThreshold);
409 const std::string bdtfn = "TrigAnomalyDetectionBDT/2025-06-18/fwX-config_nomAD_Jun3_2pi200t20d.json";
410
411 std::string fileLocation;
412
413 #ifndef TRIGCONF_STANDALONE
414 fileLocation = PathResolver::find_calib_file(bdtfn);
415 #else
416 return StatusCode::SUCCESS;
417 #endif
418 // Reading in the config
419 std::ifstream configFile(fileLocation);
420
421 if (!configFile.is_open()) {
422 TRG_MSG_ERROR("Unable to open BDT configuration file." << fileLocation);
423 return StatusCode::FAILURE;
424 }
425
426 nlohmann::json bdt_config; // BDT configuration
427 configFile >> bdt_config;
428 m_nVar = bdt_config["nDim"];
429 m_mu1_ptmin = bdt_config["variable_float_ranges"]["flat_dimu_mu1_pt_100MeV"]["min"];
430 m_mu1_ptmax = bdt_config["variable_float_ranges"]["flat_dimu_mu1_pt_100MeV"]["max"];
431 m_mu1_etamin = bdt_config["variable_float_ranges"]["flat_dimu_mu1_eta"]["min"];
432 m_mu1_etamax = bdt_config["variable_float_ranges"]["flat_dimu_mu1_eta"]["max"];
433 m_mu1_phimin = bdt_config["variable_float_ranges"]["flat_dimu_mu1_phi_2pi"]["min"];
434 m_mu1_phimax = bdt_config["variable_float_ranges"]["flat_dimu_mu1_phi_2pi"]["max"];
435 m_mu2_ptmin = bdt_config["variable_float_ranges"]["flat_dimu_mu2_pt_100MeV"]["min"];
436 m_mu2_ptmax = bdt_config["variable_float_ranges"]["flat_dimu_mu2_pt_100MeV"]["max"];
437 m_mu2_etamin = bdt_config["variable_float_ranges"]["flat_dimu_mu2_eta"]["min"];
438 m_mu2_etamax = bdt_config["variable_float_ranges"]["flat_dimu_mu2_eta"]["max"];
439 m_mu2_phimin = bdt_config["variable_float_ranges"]["flat_dimu_mu2_phi_2pi"]["min"];
440 m_mu2_phimax = bdt_config["variable_float_ranges"]["flat_dimu_mu2_phi_2pi"]["max"];
441
442 for (auto& [treeName, tree] : bdt_config["trees"].items()) {
443 m_trees.emplace_back(tree, m_nVar);
444 }
445
446 TRG_MSG_DEBUG("In initialize. There are " << m_trees.size() << " trees for AnomalyDetectionBDT.");
447
448 //Setting simulation mode.
449 //This can be set to "lut" or to "math"
450 //"lut" mimics exactly what is done in firmware
451 m_sim_mode = "lut";
452 TRG_MSG_INFO("The sim_mode is " << m_sim_mode);
453
454 //Booking histograms
455 for (size_t i=0; i<numberOutputBits(); ++i) {
456 std::string hname_accept = "hAnomalyDetecionBDT_accept_bit"+std::to_string((int)i);
457 std::string hname_reject = "hAnomalyDetecionBDT_reject_bit"+std::to_string((int)i);
458
459 bookHist(m_histAccept, hname_accept, "AnomalyScore", 100, 0, 128);
460 bookHist(m_histReject, hname_reject, "AnomalyScore", 100, 0, 128);
461 }
462 return StatusCode::SUCCESS;
463}
464
466TCS::AnomalyDetectionBDT::processBitCorrect(const std::vector<TCS::TOBArray const *> & input,
467 const std::vector<TCS::TOBArray *> & output,
468 Decision & decision) {
469
470 if (input.size() != 1){
471 TCS_EXCEPTION("ADBDT algorithm expects only one TOBArray input (muons), but got " << input.size());
472 }
473
474 const TCS::TOBArray* muons = input[0];
475
476 //Reading in number of muons:
477 TRG_MSG_DEBUG("There are " << muons->size() << " muons.");
478
479 if (muons->size() < 2) {
480 TRG_MSG_DEBUG("There are less than 2 muons. Skipping event.");
481 m_totalScore = 0;
482 return StatusCode::SUCCESS;
483 }
484
485 bool hasAmbiguousInputs = TSU::isAmbiguousAnywhere(muons, p_MaxTob, std::min(p_minEt1, p_minEt2));
486
487 int64_t maxScore = 0;
488
489 size_t nMuons = p_MaxTob > 0 ? std::min(muons->size(), p_MaxTob) : muons->size();
490 //We define muon pairs based on the 2-3 leading muons
491 std::vector<std::pair<int, int>> muonPairs;
492 for (size_t i = 0; i < nMuons; ++i) {
493 for (size_t j = i+1; j < nMuons; ++j) {
494 muonPairs.emplace_back(i, j);
495 }
496 }
497
498 for (const auto& [i,j] : muonPairs) {
499
500 std::vector<int64_t> eventValues;
501
502 const auto& mu1 = (*muons)[i];
503 const auto& mu2 = (*muons)[j];
504
505 //ignore combinations failing minET cuts
506 if ( parType_t( mu1.Et() ) <= p_minEt1 ) continue;
507 if ( parType_t( mu2.Et() ) <= p_minEt2 ) continue;
508
509 if (scale(mu1.Et(),m_mu1_ptmin, m_mu1_ptmax) != mapval(mu1.Et(), "pt")
510 || scale(mu1.eta()/40.0, m_mu1_etamin, m_mu1_etamax) != mapval(mu1.eta(), "eta")
511 || scale(mu1.phi()/20.0, m_mu1_phimin, m_mu1_phimax) != mapval(mu1.phi(), "phi")
512 || scale(mu2.Et(),m_mu2_ptmin, m_mu2_ptmax) != mapval(mu2.Et(), "pt")
513 || scale(mu2.eta()/40.0, m_mu2_etamin, m_mu2_etamax) != mapval(mu2.eta(), "eta")
514 || scale(mu2.phi()/20.0, m_mu2_phimin, m_mu2_phimax) != mapval(mu2.phi(), "phi")){
515
516 TRG_MSG_WARNING("The math function for the mapping of Muon pt,eta,or phi does not match the LUT mapping. Check LUT, 'scale' function, or if illegal vartype was given to 'mapval' function");
517
518 }
519
520 if (mapval(mu1.Et(), "pt") == int(mu1.Et()) || mapval(mu2.Et(), "pt") == int(mu2.Et())) {
521
522 TRG_MSG_WARNING("No input transformation done by LUT for muon ET");
523
524 }
525
526 if (mapval(mu1.eta(), "eta") == mu1.eta() || mapval(mu2.eta(), "eta") == mu2.eta()) {
527
528 TRG_MSG_WARNING("No input transformation done by LUT for muon Eta");
529
530 }
531
532 if (mapval(mu1.phi(), "phi") == mu1.phi() || mapval(mu2.phi(), "phi") == mu2.phi()) {
533
534
535 TRG_MSG_DEBUG("No input transformation done by LUT for muon Phi"); //Not a warning, since current transformation for phi
536 //does not change the value except for one input number
537 }
538
539 if (m_sim_mode == "math"){
540 eventValues.push_back(scale(mu1.Et(), m_mu1_ptmin, m_mu1_ptmax)); // muon pT (100MeV)
541 eventValues.push_back(scale(mu1.eta()/40.0, m_mu1_etamin, m_mu1_etamax)); // muon eta (25mrad)
542 eventValues.push_back(scale(mu1.phi()/20.0, m_mu1_phimin, m_mu1_phimax)); // muon phi (50mrad)
543
544 eventValues.push_back(scale(mu2.Et(), m_mu2_ptmin, m_mu2_ptmax)); // muon pT (100MeV)
545 eventValues.push_back(scale(mu2.eta()/40.0, m_mu2_etamin, m_mu2_etamax)); // muon eta (25mrad)
546 eventValues.push_back(scale(mu2.phi()/20.0, m_mu2_phimin, m_mu2_phimax)); // muon phi (50mrad)
547 }
548
549 else if(m_sim_mode == "lut"){
550 eventValues.push_back(mapval(mu1.Et(), "pt"));
551 eventValues.push_back(mapval(mu1.eta(), "eta"));
552 eventValues.push_back(mapval(mu1.phi(), "phi"));
553
554 eventValues.push_back(mapval(mu2.Et(), "pt"));
555 eventValues.push_back(mapval(mu2.eta(), "eta"));
556 eventValues.push_back(mapval(mu2.phi(), "phi"));
557 }
558
559 else{
560 eventValues.push_back(scale(mu1.Et(), m_mu1_ptmin, m_mu1_ptmax)); // muon pT (100MeV)
561 eventValues.push_back(scale(mu1.eta()/40.0, m_mu1_etamin, m_mu1_etamax)); // muon eta (25mrad)
562 eventValues.push_back(scale(mu1.phi()/20.0, m_mu1_phimin, m_mu1_phimax)); // muon phi (50mrad)
563
564 eventValues.push_back(scale(mu2.Et(), m_mu2_ptmin, m_mu2_ptmax)); // muon pT (100MeV)
565 eventValues.push_back(scale(mu2.eta()/40.0, m_mu2_etamin, m_mu2_etamax)); // muon eta (25mrad)
566 eventValues.push_back(scale(mu2.phi()/20.0, m_mu2_phimin, m_mu2_phimax)); // muon phi (50mrad)
567 }
568
569 // calculate the score as sum of score from all trees
570 int64_t score = 0;
571 for (Tree tree : m_trees) {
572 score += tree.getTreeScore(eventValues);
573 }
574
575 // from all muon combinations keep the highest score
576 if (score > maxScore) {
577 maxScore = score;
578 }
579 }
580
581 m_totalScore = maxScore;
582 TRG_MSG_DEBUG("Anomaly Score = " << m_totalScore);
583
584 //Now, we compare and set the decision bit:
585 for (size_t i=0; i<numberOutputBits(); ++i){
586 bool accept = false;
587 // const bool fillAccept = fillHistos() && (fillHistosBasedOnHardware() ? getDecisionHardwareBit(i) : accept);
588 // const bool fillReject = fillHistos() && !fillAccept;
589 // const bool alreadyFilled = decision.bit(i);
590
592 accept = true;
593 decision.setBit(i,true);
594 for (size_t k = 0; k < 3 && k < muons->size(); ++k){
595 output[i]->push_back((*muons)[k]);
596 }
597 }
598
599 output[i]->setAmbiguityFlag(hasAmbiguousInputs);
600
601 if(fillHistos()) {
602 const bool fillAccept = fillHistosBasedOnHardware() ? getDecisionHardwareBit(i) : accept;
603 if (fillAccept) {
605 } else {
607 }
608 }
609
610 TRG_MSG_DEBUG("Decision for bit " << i << ": " <<(accept?"pass":"fail") << "with an anomaly score = " << m_totalScore << std::endl);
611 }
612 return StatusCode::SUCCESS;
613}
614
615TCS::StatusCode TCS::AnomalyDetectionBDT::process(const std::vector<TCS::TOBArray const *> & input,
616 const std::vector<TCS::TOBArray *> & output,
617 Decision & decision) {
618#ifdef TRIGCONF_STANDALONE
619 return StatusCode::SUCCESS;
620#endif
621 TCS::StatusCode status = processBitCorrect(input,output,decision);
622 if (!status.isSuccess()){
623 return status;
624 }
625
626 // Check if totalScore exceeds the bits that we expect
627 if (m_totalScore >= (1L << 8)) {
628 TRG_MSG_WARNING("ADBDT:The calculated anomaly score exceeds the allowed number of bits.");
629 }
630
632}
#define REGISTER_ALG_TCS(CLASS)
Definition AlgFactory.h:62
std::string vectorToString(const std::vector< T > &vec)
std::vector< size_t > vec
#define min(a, b)
Definition cfImp.cxx:40
#define max(a, b)
Definition cfImp.cxx:41
static std::string find_calib_file(const std::string &logical_file_name)
virtual StatusCode process(const std::vector< TCS::TOBArray const * > &input, const std::vector< TCS::TOBArray * > &output, Decision &decision)
virtual StatusCode processBitCorrect(const std::vector< TCS::TOBArray const * > &input, const std::vector< TCS::TOBArray * > &output, Decision &decision)
AnomalyDetectionBDT(const std::string &name)
std::vector< Tree > m_trees
virtual StatusCode initialize()
std::vector< int64_t > minVals
std::vector< int64_t > maxVals
bool isInside(const std::vector< int64_t > &inputEvent) const
Bin(const nlohmann::json &obj, int nVars)
const Parameter & parameter(const std::string &parameterName) const
const std::string & name() const
void bookHist(std::vector< std::string > &regName, const std::string &name, const std::string &title, const int binx, const int xmin, const int xmax)
void fillHist1D(const std::string &histName, double x)
void defineParameter(const std::string &name, TCS::parType_t value)
size_t size() const
void setNumberOutputBits(unsigned int numberOutputBits)
Definition DecisionAlg.h:40
DecisionAlg(const std::string &name)
Definition DecisionAlg.h:25
bool fillHistosBasedOnHardware() const
! getter
bool fillHistos() const
whether the monitoring histograms should be filled
std::vector< std::string > m_histAccept
Definition DecisionAlg.h:73
std::vector< std::string > m_histReject
Definition DecisionAlg.h:74
unsigned int numberOutputBits() const
Definition DecisionAlg.h:39
bool getDecisionHardwareBit(const unsigned int &bitNumber) const
! get one hardware decision bit from this algo
void setBit(unsigned int index, bool value)
Definition Decision.cxx:12
Tree(const nlohmann::json &obj, int nVars)
std::vector< Bin > bins
int64_t getTreeScore(const std::vector< int64_t > &inputEvent) const
std::ostream & operator<<(std::ostream &os, const TCS::Bin &bin)
uint32_t parType_t
Definition Parameter.h:22
bool isAmbiguousAnywhere(TCS::TOBArray const *tobs, size_t pos, unsigned minEt=0)
TChain * tree