4545
4646#include < cstdint>
4747#include < fstream>
48+ #include < limits>
4849#include < memory>
4950#include < string>
5051#include < vector>
@@ -56,6 +57,7 @@ using namespace o2::framework;
5657namespace
5758{
5859constexpr int kNumClasses = 4 ; // pi, ka, pr, el - fixed order throughout, matches the paper's model
60+ constexpr float kNaN = std::numeric_limits<float >::quiet_NaN();
5961
6062// / itsClusterSizes packs 7 ITS layers into 4 bits each; a derived cluster
6163// / count is a far more sensible model input than the raw packed value.
@@ -81,18 +83,38 @@ int argmax4(std::vector<float> const& v)
8183 return best;
8284}
8385
86+ // / Per-group enable/disable, independent of what the input tree's hasXXX
87+ // / flags say. Default is everything enabled (true) - the normal case,
88+ // / using each track's real detector coverage as-is. Turning a group off
89+ // / forces its features to the same "absent" sentinel used when the
90+ // / detector genuinely didn't fire, and clears its mask bit - useful for
91+ // / testing how the model behaves with a detector deliberately excluded,
92+ // / independent of the data itself.
93+ struct GroupToggles {
94+ bool useTPC = true ;
95+ bool useTOF = true ;
96+ bool useTRD = true ;
97+ bool useITS = true ;
98+ bool useEMCal = true ;
99+ bool useHMPID = true ;
100+ bool useCentrality = true ;
101+ };
102+
84103// / All the real work: load the model, read the whole input tree, run
85104// / inference row by row, write predictions. Called once from init().
86105// /
87106// / Feature order fed to the model - THIS MUST MATCH YOUR TRAINING SCRIPT'S
88107// / COLUMN ORDER EXACTLY. Reasonable default (every reconstructed feature
89108// / except vz/centFT0C/sign/trackType and the Bayesian columns, which are a
90109// / comparison baseline, not a model input), followed by a 7-length group
91- // / mask (TPC/TOF/TRD/ITS/EMCal/HMPID/centrality; ITS and centrality are
92- // / assumed always-present). NOT verified against your actual training code.
110+ // / mask (TPC/TOF/TRD/ITS/EMCal/HMPID/centrality). Each group can be
111+ // / disabled via GroupToggles regardless of what the data says - see there
112+ // / for details. ITS and centrality have no hasXXX flag in the input tree
113+ // / (assumed always-present in the data itself), so their toggle is the
114+ // / only way to exclude them.
93115void runInference (std::string const & inputRootFile, std::string const & inputTreeName,
94116 std::string const & outputPath, bool exportCsv,
95- o2::analysis::MlResponse<float >& mlResponse)
117+ o2::analysis::MlResponse<float >& mlResponse, GroupToggles const & groups )
96118{
97119 std::unique_ptr<TFile> inFile (TFile::Open (inputRootFile.c_str (), " READ" ));
98120 if (!inFile || inFile->IsZombie ()) {
@@ -189,8 +211,17 @@ void runInference(std::string const& inputRootFile, std::string const& inputTree
189211 for (Long64_t i = 0 ; i < nEntries; i++) {
190212 tree->GetEntry (i);
191213
214+ // Effective presence = what the data says AND the group is enabled.
215+ // Disabling a group here forces the same "absent" state as a real
216+ // detector miss, regardless of what hasXXX says in the input tree.
217+ bool effTPC = hasTPC && groups.useTPC ;
218+ bool effTOF = hasTOF && groups.useTOF ;
219+ bool effTRD = hasTRD && groups.useTRD ;
220+ bool effEMCal = hasEMCal && groups.useEMCal ;
221+ bool effHMPID = hasHMPID && groups.useHMPID ;
222+
192223 x.clear ();
193- x.reserve (38 + 7 );
224+ x.reserve (39 + 7 );
194225 x.push_back (p);
195226 x.push_back (pt);
196227 x.push_back (px);
@@ -200,44 +231,44 @@ void runInference(std::string const& inputRootFile, std::string const& inputTree
200231 x.push_back (phi);
201232 x.push_back (dcaXY);
202233 x.push_back (dcaZ);
203- x.push_back (static_cast <float >(hasTPC ));
204- x.push_back (tpcSignal);
205- x.push_back (tpcNSigmaPi);
206- x.push_back (tpcNSigmaKa);
207- x.push_back (tpcNSigmaPr);
208- x.push_back (tpcNSigmaEl);
209- x.push_back (static_cast <float >(tpcNClsFound));
210- x.push_back (tpcChi2NCl);
211- x.push_back (static_cast <float >(hasTOF ));
212- x.push_back (tofMass);
213- x.push_back (beta);
214- x.push_back (tofNSigmaPi);
215- x.push_back (tofNSigmaKa);
216- x.push_back (tofNSigmaPr);
217- x.push_back (tofNSigmaEl);
218- x.push_back (static_cast <float >(hasTRD ));
219- x.push_back (trdSignal);
220- x.push_back (trdChi2);
221- x.push_back (static_cast <float >(trdPattern));
222- x.push_back (static_cast <float >(getItsNClusters (static_cast <uint32_t >(itsClusterSizes))));
223- x.push_back (itsChi2NCl);
224- x.push_back (static_cast <float >(hasEMCal ));
225- x.push_back (trackEtaEmcal);
226- x.push_back (trackPhiEmcal);
227- x.push_back (static_cast <float >(hasHMPID ));
228- x.push_back (hmpidSignal);
229- x.push_back (hmpidQMip);
230- x.push_back (static_cast <float >(hmpidNPhotons));
231- x.push_back (static_cast <float >(hmpidClusSize));
232- x.push_back (hmpidMom);
234+ x.push_back (static_cast <float >(effTPC ));
235+ x.push_back (effTPC ? tpcSignal : kNaN );
236+ x.push_back (effTPC ? tpcNSigmaPi : kNaN );
237+ x.push_back (effTPC ? tpcNSigmaKa : kNaN );
238+ x.push_back (effTPC ? tpcNSigmaPr : kNaN );
239+ x.push_back (effTPC ? tpcNSigmaEl : kNaN );
240+ x.push_back (effTPC ? static_cast <float >(tpcNClsFound) : 0 . f );
241+ x.push_back (effTPC ? tpcChi2NCl : kNaN );
242+ x.push_back (static_cast <float >(effTOF ));
243+ x.push_back (effTOF ? tofMass : kNaN );
244+ x.push_back (effTOF ? beta : kNaN );
245+ x.push_back (effTOF ? tofNSigmaPi : kNaN );
246+ x.push_back (effTOF ? tofNSigmaKa : kNaN );
247+ x.push_back (effTOF ? tofNSigmaPr : kNaN );
248+ x.push_back (effTOF ? tofNSigmaEl : kNaN );
249+ x.push_back (static_cast <float >(effTRD ));
250+ x.push_back (effTRD ? trdSignal : kNaN );
251+ x.push_back (effTRD ? trdChi2 : kNaN );
252+ x.push_back (effTRD ? static_cast <float >(trdPattern) : 0 . f );
253+ x.push_back (groups. useITS ? static_cast <float >(getItsNClusters (static_cast <uint32_t >(itsClusterSizes))) : 0 . f );
254+ x.push_back (groups. useITS ? itsChi2NCl : kNaN );
255+ x.push_back (static_cast <float >(effEMCal ));
256+ x.push_back (effEMCal ? trackEtaEmcal : kNaN );
257+ x.push_back (effEMCal ? trackPhiEmcal : kNaN );
258+ x.push_back (static_cast <float >(effHMPID ));
259+ x.push_back (effHMPID ? hmpidSignal : kNaN );
260+ x.push_back (effHMPID ? hmpidQMip : kNaN );
261+ x.push_back (effHMPID ? static_cast <float >(hmpidNPhotons) : 0 . f );
262+ x.push_back (effHMPID ? static_cast <float >(hmpidClusSize) : 0 . f );
263+ x.push_back (effHMPID ? hmpidMom : kNaN );
233264 // 7-length group mask
234- x.push_back (static_cast <float >(hasTPC ));
235- x.push_back (static_cast <float >(hasTOF ));
236- x.push_back (static_cast <float >(hasTRD ));
237- x.push_back (1 . f ); // ITS
238- x.push_back (static_cast <float >(hasEMCal ));
239- x.push_back (static_cast <float >(hasHMPID ));
240- x.push_back (1 . f ); // centrality
265+ x.push_back (static_cast <float >(effTPC ));
266+ x.push_back (static_cast <float >(effTOF ));
267+ x.push_back (static_cast <float >(effTRD ));
268+ x.push_back (static_cast < float >(groups. useITS ));
269+ x.push_back (static_cast <float >(effEMCal ));
270+ x.push_back (static_cast <float >(effHMPID ));
271+ x.push_back (static_cast < float >(groups. useCentrality ));
241272
242273 mlResponse.isSelectedMl (x, pt, mlOutput); // return value (selection) unused; mlOutput carries the 4 raw scores
243274 mlProbPi = mlOutput[0 ];
@@ -282,6 +313,15 @@ WorkflowSpec defineDataProcessing(ConfigContext const&)
282313 auto binsPtMl = ic.options ().get <std::vector<double >>(" binsPtMl" );
283314 auto nClassesMl = static_cast <int8_t >(ic.options ().get <int >(" nClassesMl" ));
284315
316+ GroupToggles groups;
317+ groups.useTPC = ic.options ().get <bool >(" useTPC" );
318+ groups.useTOF = ic.options ().get <bool >(" useTOF" );
319+ groups.useTRD = ic.options ().get <bool >(" useTRD" );
320+ groups.useITS = ic.options ().get <bool >(" useITS" );
321+ groups.useEMCal = ic.options ().get <bool >(" useEMCal" );
322+ groups.useHMPID = ic.options ().get <bool >(" useHMPID" );
323+ groups.useCentrality = ic.options ().get <bool >(" useCentrality" );
324+
285325 // Unused thresholds (CutNot everywhere) - this task always reports
286326 // all four probabilities rather than applying a selection cut, so
287327 // cutsMl/cutDirMl don't need to be user-configurable; hardcoded here
@@ -301,7 +341,7 @@ WorkflowSpec defineDataProcessing(ConfigContext const&)
301341 }
302342 mlResponse->init ();
303343
304- runInference (inputRootFile, inputTreeName, outputPath, exportCsv, *mlResponse);
344+ runInference (inputRootFile, inputTreeName, outputPath, exportCsv, *mlResponse, groups );
305345
306346 return [](ProcessingContext& pc) {
307347 // One-shot batch job: all work already happened in init(). Signal
@@ -323,6 +363,13 @@ WorkflowSpec defineDataProcessing(ConfigContext const&)
323363 {" onnxFileNames" , VariantType::ArrayString, std::vector<std::string>{" pid_feature_model.onnx" }, {" Local ONNX file path(s), used when loadModelFromCcdb is false" }},
324364 {" binsPtMl" , VariantType::ArrayDouble, std::vector<double >{-1 ., 9999 .}, {" pT bin edges for MlResponse (single bin = model isn't pT-binned)" }},
325365 {" nClassesMl" , VariantType::Int, kNumClasses , {" Number of model output classes" }},
366+ {" useTPC" , VariantType::Bool, true , {" Include TPC in inference. Default true (all detectors present); set false to force TPC excluded regardless of the data" }},
367+ {" useTOF" , VariantType::Bool, true , {" Include TOF in inference" }},
368+ {" useTRD" , VariantType::Bool, true , {" Include TRD in inference" }},
369+ {" useITS" , VariantType::Bool, true , {" Include ITS in inference" }},
370+ {" useEMCal" , VariantType::Bool, true , {" Include EMCal in inference" }},
371+ {" useHMPID" , VariantType::Bool, true , {" Include HMPID in inference" }},
372+ {" useCentrality" , VariantType::Bool, true , {" Include centrality in inference" }},
326373 }};
327374
328375 return WorkflowSpec{spec};
0 commit comments