2727#include " CommonUtils/ConfigurationMacroHelper.h"
2828#include " CCDB/BasicCCDBManager.h"
2929#include " ML/OrtInterface.h"
30+ #include " DetectorsBase/TrackTransportUtils.h"
3031
3132#include " TLorentzVector.h" // for TLorentzVector
3233#include " TParticle.h" // for TParticle
4142#include < cmath>
4243#include < map>
4344#include < memory>
45+ #include < limits>
46+ #include < mutex>
4447#include < stdexcept>
4548#include < unordered_map>
4649
@@ -58,14 +61,17 @@ namespace
5861// ONNX graph, keeping this code independent of model topology.
5962constexpr size_t OnnxFeatureCount = 25 ;
6063
61- class OnnxPrimaryTransport
64+ class OnnxTrackTransport
6265{
6366 public:
64- explicit OnnxPrimaryTransport (const o2::sim::StackParam& param)
67+ explicit OnnxTrackTransport (const o2::sim::StackParam& param)
6568 : mThreshold(param.transportPrimaryOnnxThreshold),
6669 mOutputIndex(param.transportPrimaryOnnxOutputIndex),
6770 mApplySigmoid(param.transportPrimaryOnnxApplySigmoid)
6871 {
72+ if (!std::isfinite (mThreshold ) || mThreshold < 0 .f || mThreshold > 1 .f ) {
73+ throw std::runtime_error (" ONNX pruning threshold must be finite and in [0,1]" );
74+ }
6975 if (param.transportPrimaryOnnxCCDBPath .empty ()) {
7076 throw std::runtime_error (" Stack.transportPrimaryOnnxCCDBPath must be configured" );
7177 }
@@ -94,63 +100,36 @@ class OnnxPrimaryTransport
94100 mModel .initSessionFromBuffer (mModelBytes .data (), mModelBytes .size ());
95101
96102 const auto inputShapes = mModel .getNumInputNodes ();
97- if (inputShapes.size () != 1 || inputShapes[0 ].empty () ||
98- (inputShapes[0 ].back () > 0 && inputShapes[0 ].back () != OnnxFeatureCount)) {
103+ if (inputShapes.size () != 1 || inputShapes[0 ].size () != 2 ||
104+ (inputShapes[0 ][0 ] != 1 && inputShapes[0 ][0 ] != -1 ) ||
105+ inputShapes[0 ][1 ] != OnnxFeatureCount) {
99106 throw std::runtime_error (" primary transport ONNX model must have one float input with 25 features" );
100107 }
101- if (mModel .getNumOutputNodes ().size () != 1 || mOutputIndex < 0 ) {
102- throw std::runtime_error (" primary transport ONNX model must have one output and a non-negative output index" );
108+ const auto outputShapes = mModel .getNumOutputNodes ();
109+ if (outputShapes.size () != 1 || outputShapes[0 ].size () != 2 ||
110+ (outputShapes[0 ][0 ] != 1 && outputShapes[0 ][0 ] != -1 ) ||
111+ outputShapes[0 ][1 ] <= 0 || mOutputIndex < 0 || mOutputIndex >= outputShapes[0 ][1 ]) {
112+ throw std::runtime_error (" track transport ONNX model must have one [batch, scores] output and a valid score index" );
103113 }
104114 }
105115
106- bool transport (const TParticle& particle, const std::vector<TParticle>& primaries )
116+ bool transport (const TParticle& particle, double eventX, double eventY, double eventZ )
107117 {
108- std::vector<std::vector<float >> inputs{makeFeatures (particle, primaries)};
118+ // OrtModel mutates its shape buffers during inference. Stack clones share
119+ // this classifier, so protect the session and those buffers together.
120+ std::lock_guard<std::mutex> lock (mMutex );
121+ std::vector<std::vector<float >> inputs{o2::data::detail::makeTrackTransportFeatures (particle, eventX, eventY, eventZ)};
109122 auto output = mModel .inference <float , float >(inputs);
110123 if (static_cast <size_t >(mOutputIndex ) >= output.size ()) {
111124 throw std::runtime_error (" Stack.transportPrimaryOnnxOutputIndex is outside the model output" );
112125 }
113- float score = output[mOutputIndex ];
114- if (mApplySigmoid ) {
115- score = score >= 0 .f ? 1 .f / (1 .f + std::exp (-score)) : std::exp (score) / (1 .f + std::exp (score));
116- }
117- // Class 1 means that GEANT transport can be avoided.
118- return score < mThreshold ;
126+ return o2::data::detail::transportFromOnnxScore (output[mOutputIndex ], mThreshold , mApplySigmoid );
119127 }
120128
121129 private:
122- static std::vector<float > makeFeatures (const TParticle& particle, const std::vector<TParticle>& primaries)
123- {
124- const double px = particle.Px ();
125- const double py = particle.Py ();
126- const double pz = particle.Pz ();
127- const double momentum = std::sqrt (px * px + py * py + pz * pz);
128- const double pt = std::hypot (px, py);
129- const double mass = particle.GetMass ();
130- const double energy = std::sqrt (std::max (0 ., mass * mass + momentum * momentum));
131- const double eta = momentum > std::abs (pz) ? 0.5 * std::log ((momentum + pz) / (momentum - pz)) : 0 .;
132- const double theta = momentum > 0 . ? std::acos (pz / momentum) : 0 .;
133- const double rapidity = energy > std::abs (pz) ? 0.5 * std::log ((energy + pz) / (energy - pz)) : 0 .;
134- const auto * pdgInfo = particle.GetPDG ();
135- const double chargeSign = pdgInfo == nullptr || pdgInfo->Charge () == 0 . ? 0 . : std::copysign (1 ., pdgInfo->Charge ());
136- const TParticle& eventReference = primaries.empty () ? particle : primaries.front ();
137- const double dx = particle.Vx () - eventReference.Vx ();
138- const double dy = particle.Vy () - eventReference.Vy ();
139- const double dz = particle.Vz () - eventReference.Vz ();
140- const double pdg = particle.GetPdgCode ();
141-
142- return {static_cast <float >(pdg), static_cast <float >(std::abs (pdg)), static_cast <float >(chargeSign),
143- static_cast <float >(mass), static_cast <float >(energy), static_cast <float >(energy - mass),
144- static_cast <float >(px), static_cast <float >(py), static_cast <float >(pz), static_cast <float >(momentum),
145- static_cast <float >(pt), static_cast <float >(eta), static_cast <float >(particle.Phi ()), static_cast <float >(theta),
146- static_cast <float >(rapidity), static_cast <float >(particle.Vx ()), static_cast <float >(particle.Vy ()),
147- static_cast <float >(particle.Vz ()), static_cast <float >(particle.T ()), static_cast <float >(dx),
148- static_cast <float >(dy), static_cast <float >(dz), static_cast <float >(std::hypot (particle.Vx (), particle.Vy ())),
149- static_cast <float >(std::hypot (dx, dy)), static_cast <float >(std::sqrt (dx * dx + dy * dy + dz * dz))};
150- }
151-
130+ std::vector<char > mModelBytes ; // Must outlive the session (members are destroyed in reverse order).
152131 o2::ml::OrtModel mModel ;
153- std::vector< char > mModelBytes ; // ORT may use model bytes directly; retain them for the session lifetime.
132+ std::mutex mMutex ;
154133 float mThreshold ;
155134 int mOutputIndex ;
156135 bool mApplySigmoid ;
@@ -221,20 +200,24 @@ Stack::Stack(Int_t size)
221200 LOG (info) << " Successfully retrieve external \' transportPrimary\' frunction: " << param.transportPrimaryFileName ;
222201 } else if (param.transportPrimary .compare (" onnx" ) == 0 ) {
223202 try {
224- auto classifier = std::make_shared<OnnxPrimaryTransport>(param);
225- transportPrimary = [classifier](const TParticle& p, const std::vector<TParticle>& particles) {
226- return classifier->transport (p, particles);
203+ auto classifier = std::make_shared<OnnxTrackTransport>(param);
204+ mTransportTrack = [classifier, invert = param.transportPrimaryInvert ](const TParticle& p, double x, double y, double z) {
205+ const bool transport = classifier->transport (p, x, y, z);
206+ return invert ? !transport : transport;
227207 };
228- LOG (info) << " Successfully configured ONNX primary transport pruning from CCDB path "
208+ // ONNX runs at PreTrack, where the true event vertex and both primary
209+ // and secondary birth states are available, also in parallel simulation.
210+ transportPrimary = [](const TParticle&, const std::vector<TParticle>&) { return true ; };
211+ LOG (info) << " Successfully configured ONNX track transport pruning from CCDB path "
229212 << param.transportPrimaryOnnxCCDBPath ;
230213 } catch (const std::exception& error) {
231- LOG (fatal) << " Failed to configure ONNX primary transport pruning: " << error.what ();
214+ LOG (fatal) << " Failed to configure ONNX track transport pruning: " << error.what ();
232215 }
233216 } else {
234217 LOG (fatal) << " unsupported \' trasportPrimary\' mode: " << param.transportPrimary ;
235218 }
236219
237- if (param.transportPrimaryInvert ) {
220+ if (param.transportPrimaryInvert && param. transportPrimary != " onnx " ) {
238221 mTransportPrimary = [transportPrimary](const TParticle& p, const std::vector<TParticle>& particles) { return !transportPrimary (p, particles); };
239222 } else {
240223 mTransportPrimary = transportPrimary;
@@ -259,7 +242,9 @@ Stack::Stack(const Stack& rhs)
259242 mMinHits(rhs.mMinHits ),
260243 mEnergyCut(rhs.mEnergyCut ),
261244 mTrackRefs(new std::vector<o2::TrackReference>),
262- mIsG4Like(rhs.mIsG4Like )
245+ mIsG4Like(rhs.mIsG4Like ),
246+ mTransportPrimary(rhs.mTransportPrimary ),
247+ mTransportTrack(rhs.mTransportTrack )
263248{
264249 LOG (debug) << " copy constructor called" ;
265250 mTracks = new std::vector<MCTrack>();
@@ -295,6 +280,8 @@ Stack& Stack::operator=(const Stack& rhs)
295280 mMinHits = rhs.mMinHits ;
296281 mEnergyCut = rhs.mEnergyCut ;
297282 mIsG4Like = rhs.mIsG4Like ;
283+ mTransportPrimary = rhs.mTransportPrimary ;
284+ mTransportTrack = rhs.mTransportTrack ;
298285
299286 return *this ;
300287}
@@ -395,6 +382,34 @@ void Stack::handleTransportPrimary(TParticle& p)
395382 }
396383}
397384
385+ bool Stack::transportTrack (const TParticle& particle, double eventX, double eventY, double eventZ)
386+ {
387+ if (!mTransportTrack || mTransportTrack (particle, eventX, eventY, eventZ)) {
388+ return true ;
389+ }
390+ // Keep bookkeeping and ancestry, but mark the birth track as inhibited.
391+ // Do not alter the primary completion count: its PreTrack/FinishPrimary
392+ // lifecycle has already started and will still be completed by the engine.
393+ auto inhibit = [](TParticle& p) {
394+ p.SetBit (ParticleStatus::kToBeDone , 0 );
395+ p.SetBit (ParticleStatus::kInhibited , 1 );
396+ };
397+ inhibit (mCurrentParticle );
398+ const int id = mIndexOfCurrentTrack ;
399+ if (id >= 0 && id < static_cast <int >(mPrimaryParticles .size ())) {
400+ inhibit (mPrimaryParticles [id]);
401+ (*mTracks )[id].setToBeDone (false );
402+ (*mTracks )[id].setInhibited (true );
403+ } else if (id >= 0 && id < static_cast <int >(mTrackIDtoParticlesEntry .size ())) {
404+ const int entry = mTrackIDtoParticlesEntry [id];
405+ if (entry >= 0 && entry < static_cast <int >(mParticles .size ())) {
406+ mParticles [entry].setToBeDone (false );
407+ mParticles [entry].setInhibited (true );
408+ }
409+ }
410+ return false ;
411+ }
412+
398413void Stack::PushTrack (int toBeDone, TParticle& p)
399414{
400415 // printf("stack -> Pushing Primary toBeDone %5d %5d parentId %5d pdgCode %5d is %5d entries %5d \n", toBeDone, p.TestBit(ParticleStatus::kToBeDone), p.GetFirstMother(), p.GetPdgCode(), p.GetStatusCode(), mNumberOfEntriesInParticles);
0 commit comments