1616#include " PWGLF/DataModel/LFStrangenessMLTables.h"
1717#include " PWGLF/DataModel/LFStrangenessPIDTables.h"
1818#include " PWGLF/DataModel/LFStrangenessTables.h"
19+ #include " PWGLF/Utils/ResonanceMlResponse.h"
1920
2021#include " Common/CCDB/EventSelectionParams.h"
2122#include " Common/CCDB/ctpRateFetcher.h"
2223#include " Common/Core/RecoDecay.h"
2324#include " Common/DataModel/Centrality.h"
25+ #include " Tools/ML/MlResponse.h"
2426
2527#include < CCDB/BasicCCDBManager.h>
28+ #include < CCDB/CcdbApi.h>
2629#include < CommonConstants/MathConstants.h>
2730#include < CommonConstants/PhysicsConstants.h>
2831#include < Framework/ASoA.h>
2932#include < Framework/AnalysisDataModel.h>
3033#include < Framework/AnalysisHelpers.h>
3134#include < Framework/AnalysisTask.h>
35+ #include < Framework/Array2D.h>
3236#include < Framework/BinningPolicy.h>
3337#include < Framework/Configurable.h>
3438#include < Framework/HistogramRegistry.h>
5054#include < vector>
5155
5256using namespace o2 ;
57+ using namespace o2 ::ml;
5358using namespace o2 ::framework;
5459using namespace o2 ::framework::expressions;
5560using std::array;
@@ -73,7 +78,10 @@ enum BkgResonance {
7378
7479struct k892hadronphotonBkg {
7580 Service<o2::ccdb::BasicCCDBManager> ccdb{};
81+ o2::ccdb::CcdbApi ccdbApi;
7682 ctpRateFetcher rateFetcher;
83+ o2::analysis::ResonanceMlResponse<float > mlResponse;
84+
7785 TRandom3 rotRng{12345 }; // struct member; fixed seed for reproducibility across grid jobs
7886
7987 // Histogram registry
@@ -119,6 +127,22 @@ struct k892hadronphotonBkg {
119127 Configurable<bool > rotGamma{" rotGamma" , false , " Flag to rotate the photon direction" };
120128 } lstarBkgConfig;
121129
130+ struct : ConfigurableGroup {
131+ std::string prefix = " bdt" ; // JSON group name
132+ Configurable<std::string> ccdbUrl{" ccdbUrl" , " http://alice-ccdb.cern.ch" , " url of the ccdb repository" };
133+ Configurable<std::vector<std::string>> onnxFileNames{" onnxFileNames" , std::vector<std::string>{" BDTModel.onnx" }, " Local .onnx file names, one per pT bin" };
134+ Configurable<std::vector<std::string>> modelPathsCCDB{" modelPathsCCDB" , std::vector<std::string>{" Users/o/obenchik/MLModels/BDT" }, " Model paths on CCDB, one per pT bin (each model needs its own folder)" };
135+ Configurable<int64_t > timestampCCDB{" timestampCCDB" , 1695750420200 , " timestamp of the ONNX file for ML model used to query in CCDB. Please use 1695750420200" };
136+ Configurable<bool > loadModelsFromCCDB{" loadModelsFromCCDB" , false , " Flag to enable or disable the loading of models from CCDB" };
137+ Configurable<bool > enableOptimizations{" enableOptimizations" , false , " Enables the ONNX extended model-optimization: sessionOptions.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_EXTENDED)" };
138+ Configurable<int > numThreads{" numThreads" , 1 , " ONNX intra-op threads. 0 lets ONNX Runtime default to one thread per physical core" };
139+ Configurable<bool > enableML{" enableML" , false , " Enables bdt model" };
140+ Configurable<std::vector<double >> ptBinEdges{" ptBinEdges" , {0 ., 30 .}, " Pair-pT bin edges of the BDT models, one model per bin (pairs outside are rejected)" };
141+ Configurable<LabeledArray<double >> scoreCuts{" scoreCuts" , {std::array<double , 2 >{0 ., 0 .}.data (), 1 , 2 , {" pT bin 0" }, {" Background score" , " Signal score" }}, " BDT score cuts, one row per pT bin" };
142+ Configurable<std::vector<int >> cutDir{" cutDir" , std::vector<int >{o2::cuts_ml::CutNot, o2::cuts_ml::CutNot}, " Cut direction per class: 0 = keep score < cut, 1 = keep score >= cut, 2 = no cut" };
143+ Configurable<std::vector<std::string>> namesInputFeatures{" namesInputFeatures" , std::vector<std::string>{" lambdaDCADau" , " lambdaAlpha" , " lambdaDCANegPV" , " lambdaDCAPosPV" , " lambdaQt" , " photonAlpha" , " photonCosPA" , " photonDCADau" , " photonDCANegPV" , " photonDCAPosPV" , " photonQt" , " photonRadius" , " opAngle" }, " Names and order of the BDT input features (see ResonanceMlResponse.h): must match FeaturesToTrain" };
144+ } bdt;
145+
122146 ConfigurableAxis axisVertexMixBkg{" axisVertexMixBkg" , {VARIABLE_WIDTH , -10 .f , -8 .f , -6 .f , -4 .f , -2 .f , 0 .f , 2 .f , 4 .f , 6 .f , 8 .f , 10 .f }, " z-vertex bins for mixing" };
123147 ConfigurableAxis axisCentralityMixBkg{" axisCentralityMixBkg" , {VARIABLE_WIDTH , 0 .0f , 1 .0f , 5 .0f , 10 .0f , 20 .0f , 30 .0f , 40 .0f , 50 .0f , 60 .0f , 70 .0f , 80 .0f , 90 .0f , 100 .0f , 110 .0f }, " centrality bins for mixing" };
124148
@@ -250,6 +274,14 @@ struct k892hadronphotonBkg {
250274 ConfigurableAxis axisAPQt{" axisAPQt" , {220 , 0 .0f , 1 .1f }, " Resonance AP q_{T} (GeV/c)" };
251275 ConfigurableAxis axisCandSel{" axisCandSel" , {15 , 0 .5f , +15 .5f }, " Candidate Selection" };
252276 ConfigurableAxis axisOPAngle{" axisOPAngle" , {140 , 0 .0f , 7 .0f }, " Opening angle (rad)" };
277+ // BDT QA axes
278+ ConfigurableAxis mlProb{" mlProb" , {100 , 0 .0f , 1 .0f }, " BDT signal score" };
279+ ConfigurableAxis axisCosPA{" axisCosPA" , {200 , 0 .5f , 1 .0f }, " Cosine of pointing angle" };
280+ ConfigurableAxis axisDCAdau{" axisDCAdau" , {50 , 0 .0f , 5 .0f }, " DCA (cm)" };
281+ ConfigurableAxis axisSignedDCAtoPV{" axisSignedDCAtoPV" , {1000 , -50 .0f , 50 .0f }, " signed DCA (cm)" };
282+ ConfigurableAxis axisSignedDCAtoPVLambda{" axisSignedDCAtoPVLambda" , {500 , -10 .0f , 10 .0f }, " signed DCA (cm)" };
283+ ConfigurableAxis axisV0APQt{" axisV0APQt" , {220 , 0 .0f , 0 .5f }, " V0 AP q_{T} (GeV/c)" };
284+ ConfigurableAxis axisV0Radius{" axisV0Radius" , {240 , 0 .0f , 120 .0f }, " V0 radius (cm)" };
253285 } axisConfig;
254286
255287 void init (InitContext const &)
@@ -259,6 +291,58 @@ struct k892hadronphotonBkg {
259291 ccdb->setCaching (true );
260292 ccdb->setFatalWhenNull (false );
261293
294+ if (bdt.enableML ) {
295+ ccdb->setURL (bdt.ccdbUrl .value );
296+
297+ // One model per pair-pT bin. MlResponse checks the model files and cutDir, not the rows of scoreCuts
298+ constexpr uint8_t NClassesML = 2 ; // background, signal
299+ if (bdt.scoreCuts .value .rows () != bdt.ptBinEdges .value .size () - 1 || bdt.scoreCuts .value .cols () != NClassesML) {
300+ LOG (fatal) << " bdt.scoreCuts needs one row per pT bin and " << static_cast <int >(NClassesML) << " columns" ;
301+ }
302+ mlResponse.configure (bdt.ptBinEdges .value , bdt.scoreCuts .value , bdt.cutDir .value , NClassesML);
303+ mlResponse.cacheInputFeaturesIndices (bdt.namesInputFeatures );
304+
305+ if (bdt.loadModelsFromCCDB ) {
306+ ccdbApi.init (bdt.ccdbUrl );
307+ LOG (info) << " Fetching models for timestamp: " << bdt.timestampCCDB .value ;
308+ mlResponse.setModelPathsCCDB (bdt.onnxFileNames .value , ccdbApi, bdt.modelPathsCCDB .value , bdt.timestampCCDB .value );
309+ } else {
310+ mlResponse.setModelPathsLocal (bdt.onnxFileNames .value );
311+ }
312+ mlResponse.init (bdt.enableOptimizations .value , bdt.numThreads .value );
313+
314+ // It is applied to the Lambda(1520) MIXED background only (for now!!)
315+ if (!lstarBkgConfig.doSameEvtRotation && !lstarBkgConfig.doEvtMixing ) {
316+ LOG (warning) << " bdt.enableML is set but no Lambda(1520) background is requested: the BDT will not be applied." ;
317+ }
318+ if (kstarBkgConfig.doSameEvtRotation || kstarBkgConfig.doEvtMixing ) {
319+ LOG (info) << " The BDT (gamma + Lambda features) is not applied to the K*(892) background." ;
320+ }
321+
322+ histos.add (" BDT/hScoreSignal" , " hScoreSignal" , kTH1D , {axisConfig.mlProb });
323+ histos.add (" BDT/hScoreBackground" , " hScoreBackground" , kTH1D , {axisConfig.mlProb });
324+ histos.add (" BDT/h2dScoreVsMassSignal" , " h2dScoreVsMassSignal" , kTH2D , {axisConfig.axisLambdaStarMass , axisConfig.mlProb });
325+ histos.add (" BDT/h2dScoreVsPtSignal" , " h2dScoreVsPtSignal" , kTH2D , {axisConfig.axisPt , axisConfig.mlProb });
326+ histos.add (" BDT/h3dScoreSignal" , " h3dScoreSignal" , kTH3D , {axisConfig.axisPt , axisConfig.axisLambdaStarMass , axisConfig.mlProb });
327+ histos.add (" BDT/h2dScoreVsMassBackground" , " h2dScoreVsMassBackground" , kTH2D , {axisConfig.axisLambdaStarMass , axisConfig.mlProb });
328+ histos.add (" BDT/h2dScoreVsPtBackground" , " h2dScoreVsPtBackground" , kTH2D , {axisConfig.axisPt , axisConfig.mlProb });
329+ histos.add (" BDT/h3dScoreBackground" , " h3dScoreBackground" , kTH3D , {axisConfig.axisPt , axisConfig.axisLambdaStarMass , axisConfig.mlProb });
330+ histos.add (" BDT/h2dLambdaDCADaughters" , " h2dLambdaDCADaughters" , kTH2D , {axisConfig.mlProb , axisConfig.axisDCAdau });
331+
332+ histos.add (" BDT/h2dLambdaAlpha" , " h2dLambdaAlpha" , kTH2D , {axisConfig.mlProb , axisConfig.axisAPAlpha });
333+ histos.add (" BDT/h2dLambdaDCANegPV" , " h2dLambdaDCANegPV" , kTH2D , {axisConfig.mlProb , axisConfig.axisSignedDCAtoPVLambda });
334+ histos.add (" BDT/h2dLambdaDCAPosPV" , " h2dLambdaDCAPosPV" , kTH2D , {axisConfig.mlProb , axisConfig.axisSignedDCAtoPVLambda });
335+ histos.add (" BDT/h2dLambdaQt" , " h2dLambdaQt" , kTH2D , {axisConfig.mlProb , axisConfig.axisV0APQt });
336+ histos.add (" BDT/h2dPhotonAlpha" , " h2dPhotonAlpha" , kTH2D , {axisConfig.mlProb , axisConfig.axisAPAlpha });
337+ histos.add (" BDT/h2dPhotonCosPA" , " h2dPhotonCosPA" , kTH2D , {axisConfig.mlProb , axisConfig.axisCosPA });
338+ histos.add (" BDT/h2dPhotonDCADau" , " h2dPhotonDCADau" , kTH2D , {axisConfig.mlProb , axisConfig.axisDCAdau });
339+ histos.add (" BDT/h2dPhotonDCANegPV" , " h2dPhotonDCANegPV" , kTH2D , {axisConfig.mlProb , axisConfig.axisSignedDCAtoPV });
340+ histos.add (" BDT/h2dPhotonDCAPosPV" , " h2dPhotonDCAPosPV" , kTH2D , {axisConfig.mlProb , axisConfig.axisSignedDCAtoPV });
341+ histos.add (" BDT/h2dPhotonQt" , " h2dPhotonQt" , kTH2D , {axisConfig.mlProb , axisConfig.axisV0APQt });
342+ histos.add (" BDT/h2dPhotonRadius" , " h2dPhotonRadius" , kTH2D , {axisConfig.mlProb , axisConfig.axisV0Radius });
343+ histos.add (" BDT/h2dOPAngle" , " h2dOPAngle" , kTH2D , {axisConfig.mlProb , axisConfig.axisOPAngle });
344+ }
345+
262346 histos.add (" hEventCentrality" , " hEventCentrality" , kTH1D , {axisConfig.axisCentrality });
263347
264348 if (eventSelections.fUseEventSelection ) {
@@ -351,6 +435,8 @@ struct k892hadronphotonBkg {
351435 histos.add (" LambdaStarBkg/h4dMixedLambdaStarPtVsAPAlphaVsAPQt" , " h4dMixedLambdaStarPtVsAPAlphaVsAPQt" , kTHnD , {axisConfig.axisAPAlpha , axisConfig.axisAPQt , axisConfig.axisPt , axisConfig.axisLambdaStarMass });
352436 }
353437 }
438+
439+ histos.print ();
354440 }
355441
356442 // _______________________________________________
@@ -793,6 +879,73 @@ struct k892hadronphotonBkg {
793879 return std::sqrt (RecoDecay::p2 (hadronP) - dp * dp / momTot2);
794880 }
795881
882+ // _______________________________________________
883+ // The two V0s share a daughter track (or are the same V0): rejected by the builder
884+ template <typename TV0Object>
885+ static bool shareDaughters (TV0Object const & photon, TV0Object const & hadron)
886+ {
887+ return photon.globalIndex () == hadron.globalIndex () ||
888+ photon.posTrackExtraId () == hadron.posTrackExtraId () ||
889+ photon.negTrackExtraId () == hadron.negTrackExtraId () ||
890+ photon.posTrackExtraId () == hadron.negTrackExtraId () ||
891+ photon.negTrackExtraId () == hadron.posTrackExtraId ();
892+ }
893+
894+ // _______________________________________________
895+ // Fill BDT performance QA
896+ template <typename TV0Object>
897+ void fillBDTPerformance (TV0Object const & lambda, TV0Object const & photon, float openAngle, float score, float pt, float mass)
898+ {
899+ float bkgScore = 1 .0f - score;
900+
901+ // Signal-probability output
902+ histos.fill (HIST (" BDT/hScoreSignal" ), score);
903+ histos.fill (HIST (" BDT/h2dScoreVsMassSignal" ), mass, score);
904+ histos.fill (HIST (" BDT/h2dScoreVsPtSignal" ), pt, score);
905+ histos.fill (HIST (" BDT/h3dScoreSignal" ), pt, mass, score);
906+
907+ // Background-probability output
908+ histos.fill (HIST (" BDT/hScoreBackground" ), bkgScore);
909+ histos.fill (HIST (" BDT/h2dScoreVsMassBackground" ), mass, bkgScore);
910+ histos.fill (HIST (" BDT/h2dScoreVsPtBackground" ), pt, bkgScore);
911+ histos.fill (HIST (" BDT/h3dScoreBackground" ), pt, mass, bkgScore);
912+
913+ // Signal score vs the main topological variables
914+ histos.fill (HIST (" BDT/h2dLambdaDCADaughters" ), score, lambda.dcaV0daughters ());
915+ histos.fill (HIST (" BDT/h2dLambdaAlpha" ), score, lambda.alpha ());
916+ histos.fill (HIST (" BDT/h2dLambdaDCANegPV" ), score, lambda.dcanegtopv ());
917+ histos.fill (HIST (" BDT/h2dLambdaDCAPosPV" ), score, lambda.dcapostopv ());
918+ histos.fill (HIST (" BDT/h2dLambdaQt" ), score, lambda.qtarm ());
919+ histos.fill (HIST (" BDT/h2dPhotonAlpha" ), score, photon.alpha ());
920+ histos.fill (HIST (" BDT/h2dPhotonCosPA" ), score, photon.v0cosPA ());
921+ histos.fill (HIST (" BDT/h2dPhotonDCADau" ), score, photon.dcaV0daughters ());
922+ histos.fill (HIST (" BDT/h2dPhotonDCANegPV" ), score, photon.dcanegtopv ());
923+ histos.fill (HIST (" BDT/h2dPhotonDCAPosPV" ), score, photon.dcapostopv ());
924+ histos.fill (HIST (" BDT/h2dPhotonQt" ), score, photon.qtarm ());
925+ histos.fill (HIST (" BDT/h2dPhotonRadius" ), score, photon.v0radius ());
926+ histos.fill (HIST (" BDT/h2dOPAngle" ), score, openAngle);
927+ }
928+
929+ // _______________________________________________
930+ // BDT selection of a Lambda + photon pair
931+ template <typename TV0Object>
932+ bool selectML (TV0Object const & lambda, TV0Object const & photon,
933+ float openAngle, float pt, float mass)
934+ {
935+ // No model outside the bdt.ptBinEdges range
936+ if (pt < bdt.ptBinEdges .value .front () || pt >= bdt.ptBinEdges .value .back ())
937+ return false ;
938+
939+ // Features in the order of bdt.namesInputFeatures
940+ auto inputFeatures = mlResponse.getInputFeatures (lambda, photon, openAngle);
941+ std::vector<float > outputMl;
942+ const bool isSelected = mlResponse.isSelectedMl (inputFeatures, pt, outputMl); // model and cut of the pT bin
943+
944+ fillBDTPerformance (lambda, photon, openAngle, outputMl[1 ], pt, mass);
945+
946+ return isSelected;
947+ }
948+
796949 // _______________________________________________
797950 // Compute same-event rotational background within a single collision.
798951 template <int resonance, typename TCollision, typename TV0s>
@@ -820,6 +973,10 @@ struct k892hadronphotonBkg {
820973 for (const int & pIdx : photonIndices) {
821974 const auto & photon = fullV0s.rawIteratorAt (pIdx);
822975
976+ // Same pair rejection
977+ if (shareDaughters (photon, hadron))
978+ continue ;
979+
823980 // photon as a massless 4-vector
824981 ROOT ::Math::PtEtaPhiMVector pGamma (photon.pt (),
825982 photon.eta (),
@@ -853,6 +1010,16 @@ struct k892hadronphotonBkg {
8531010 // Opening angle between photon and hadron
8541011 double cosOA = gammaLeg.Vect ().Dot (hadronLeg.Vect ()) / (gammaLeg.P () * hadronLeg.P ());
8551012 double openAngle = std::acos (cosOA);
1013+ // double pt = reso.Pt();
1014+ // double mass = reso.M();
1015+
1016+ // // To:Do BDT selection (Lambda(1520))
1017+ // if constexpr (resonance == kResoLambdaStar) {
1018+ // if (bdt.enableML) {
1019+ // if (!selectML(hadron, photon, openAngle, pt, mass))
1020+ // continue;
1021+ // }
1022+ // }
8561023
8571024 // Armenteros-Podolanski of the rotated pair
8581025 const std::array<float , 3 > gammaMom{static_cast <float >(gammaLeg.Px ()), static_cast <float >(gammaLeg.Py ()), static_cast <float >(gammaLeg.Pz ())};
@@ -882,9 +1049,9 @@ struct k892hadronphotonBkg {
8821049
8831050 // _______________________________________________
8841051 // Mixed-event pairing: hadrons and photons come from two different collisions.
885- // Centrality is taken from the reference collision (the first of the pair)
886- template <int resonance, typename TCollision , typename TV0s>
887- void calculateMixedBackground (TCollision const & refColl,
1052+ // Centrality is taken from the reference collision (the first of the pair).
1053+ template <int resonance, typename TRefColl , typename TV0s>
1054+ void calculateMixedBackground (TRefColl const & refColl,
8881055 std::vector<int > const & hadronIndices,
8891056 std::vector<int > const & photonIndices,
8901057 TV0s const & fullV0s)
@@ -909,6 +1076,11 @@ struct k892hadronphotonBkg {
9091076
9101077 for (const int & pIdx : photonIndices) {
9111078 const auto & photon = fullV0s.rawIteratorAt (pIdx);
1079+
1080+ // Same pair rejection as the builder
1081+ if (shareDaughters (photon, hadron))
1082+ continue ;
1083+
9121084 float pP = std::hypot (photon.px (), photon.py (), photon.pz ());
9131085 ROOT ::Math::PxPyPzEVector fourMomPhoton (
9141086 photon.px (), photon.py (), photon.pz (), pP);
@@ -931,6 +1103,14 @@ struct k892hadronphotonBkg {
9311103 if (std::abs (rapidity) > maxRap)
9321104 continue ;
9331105
1106+ // BDT selection (Lambda(1520) only)
1107+ if constexpr (resonance == kResoLambdaStar ) {
1108+ if (bdt.enableML ) {
1109+ if (!selectML (hadron, photon, openAngle, pt, mass))
1110+ continue ;
1111+ }
1112+ }
1113+
9341114 // Armenteros-Podolanski of the mixed pair
9351115 const std::array<float , 3 > gammaMom{photon.px (), photon.py (), photon.pz ()};
9361116 const std::array<float , 3 > hadronMom{hadron.px (), hadron.py (), hadron.pz ()};
0 commit comments