Skip to content

Commit 89b39d7

Browse files
authored
[PWGLF] Added ML framework for the Lambdastar (#18138)
1 parent b4cc6ec commit 89b39d7

5 files changed

Lines changed: 661 additions & 6 deletions

File tree

‎PWGLF/Tasks/Resonances/CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ o2physics_add_dpl_workflow(k892hadronphoton
316316

317317
o2physics_add_dpl_workflow(k892hadronphotonbkg
318318
SOURCES k892hadronphotonBkg.cxx
319-
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB
319+
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB O2Physics::MLCore
320320
COMPONENT_NAME Analysis)
321321

322322
o2physics_add_dpl_workflow(chk892li

‎PWGLF/Tasks/Resonances/k892hadronphotonBkg.cxx‎

Lines changed: 183 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,23 @@
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>
@@ -50,6 +54,7 @@
5054
#include <vector>
5155

5256
using namespace o2;
57+
using namespace o2::ml;
5358
using namespace o2::framework;
5459
using namespace o2::framework::expressions;
5560
using std::array;
@@ -73,7 +78,10 @@ enum BkgResonance {
7378

7479
struct 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()};

‎PWGLF/Tasks/Strangeness/CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ o2physics_add_dpl_workflow(hstrangecorrelation
8686

8787
o2physics_add_dpl_workflow(sigmaanalysis
8888
SOURCES sigmaanalysis.cxx
89-
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB
89+
PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB O2Physics::MLCore
9090
COMPONENT_NAME Analysis)
9191

9292
o2physics_add_dpl_workflow(phik0shortanalysis

0 commit comments

Comments
 (0)