|
25 | 25 | #include "SimulationDataFormat/BaseHits.h" |
26 | 26 | #include "SimulationDataFormat/StackParam.h" |
27 | 27 | #include "CommonUtils/ConfigurationMacroHelper.h" |
| 28 | +#include "CCDB/CcdbApi.h" |
| 29 | +#include "ML/OrtInterface.h" |
28 | 30 |
|
29 | 31 | #include "TLorentzVector.h" // for TLorentzVector |
30 | 32 | #include "TParticle.h" // for TParticle |
31 | 33 | #include "TRefArray.h" // for TRefArray |
32 | 34 | #include "TVirtualMC.h" // for VMC |
33 | 35 | #include "TMCProcess.h" // for VMC Particle Production Process |
| 36 | +#include "TParticlePDG.h" |
34 | 37 |
|
35 | 38 | #include <algorithm> |
36 | 39 | #include <cassert> |
37 | 40 | #include <cstddef> // for NULL |
38 | 41 | #include <cmath> |
| 42 | +#include <map> |
| 43 | +#include <memory> |
| 44 | +#include <stdexcept> |
| 45 | +#include <unordered_map> |
39 | 46 |
|
40 | 47 | using std::cout; |
41 | 48 | using std::endl; |
42 | 49 | using std::pair; |
43 | 50 | using namespace o2::data; |
44 | 51 |
|
| 52 | +namespace |
| 53 | +{ |
| 54 | +// Feature contract used by the sim-pruning models, in order: |
| 55 | +// pdg, abs_pdg, charge_sign, mass, energy, ekin, px, py, pz, p, pt, eta, |
| 56 | +// phi, theta, rapidity, vx, vy, vz, t_ns, dx/dy/dz_from_event, r_xy, |
| 57 | +// r_from_event_xy, r3_from_event. Input normalisation can be embedded in the |
| 58 | +// ONNX graph, keeping this code independent of model topology. |
| 59 | +constexpr size_t OnnxFeatureCount = 25; |
| 60 | + |
| 61 | +class OnnxPrimaryTransport |
| 62 | +{ |
| 63 | + public: |
| 64 | + explicit OnnxPrimaryTransport(const o2::sim::StackParam& param) |
| 65 | + : mThreshold(param.transportPrimaryOnnxThreshold), |
| 66 | + mOutputIndex(param.transportPrimaryOnnxOutputIndex), |
| 67 | + mApplySigmoid(param.transportPrimaryOnnxApplySigmoid) |
| 68 | + { |
| 69 | + if (param.transportPrimaryOnnxCCDBPath.empty()) { |
| 70 | + throw std::runtime_error("Stack.transportPrimaryOnnxCCDBPath must be configured"); |
| 71 | + } |
| 72 | + |
| 73 | + o2::ccdb::CcdbApi ccdb; |
| 74 | + ccdb.init(param.transportPrimaryOnnxCCDBUrl); |
| 75 | + std::map<std::string, std::string> headers; |
| 76 | + ccdb.loadFileToMemory(mModelBytes, param.transportPrimaryOnnxCCDBPath, {}, |
| 77 | + param.transportPrimaryOnnxTimestamp, &headers, {}, {}, {}); |
| 78 | + if (mModelBytes.empty()) { |
| 79 | + throw std::runtime_error("failed to retrieve ONNX model from CCDB path " + param.transportPrimaryOnnxCCDBPath); |
| 80 | + } |
| 81 | + |
| 82 | + std::unordered_map<std::string, std::string> options{{"model-path", param.transportPrimaryOnnxCCDBPath}, |
| 83 | + {"device-type", "CPU"}, |
| 84 | + {"intra-op-num-threads", "1"}, |
| 85 | + {"inter-op-num-threads", "1"}, |
| 86 | + {"enable-optimizations", "99"}, |
| 87 | + {"logging-level", "2"}, |
| 88 | + {"onnx-environment-name", "primary-transport-pruning"}}; |
| 89 | + mModel.init(options); |
| 90 | + mModel.initSessionFromBuffer(mModelBytes.data(), mModelBytes.size()); |
| 91 | + |
| 92 | + const auto inputShapes = mModel.getNumInputNodes(); |
| 93 | + if (inputShapes.size() != 1 || inputShapes[0].empty() || |
| 94 | + (inputShapes[0].back() > 0 && inputShapes[0].back() != OnnxFeatureCount)) { |
| 95 | + throw std::runtime_error("primary transport ONNX model must have one float input with 25 features"); |
| 96 | + } |
| 97 | + if (mModel.getNumOutputNodes().size() != 1 || mOutputIndex < 0) { |
| 98 | + throw std::runtime_error("primary transport ONNX model must have one output and a non-negative output index"); |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + bool transport(const TParticle& particle, const std::vector<TParticle>& primaries) |
| 103 | + { |
| 104 | + std::vector<std::vector<float>> inputs{makeFeatures(particle, primaries)}; |
| 105 | + auto output = mModel.inference<float, float>(inputs); |
| 106 | + if (static_cast<size_t>(mOutputIndex) >= output.size()) { |
| 107 | + throw std::runtime_error("Stack.transportPrimaryOnnxOutputIndex is outside the model output"); |
| 108 | + } |
| 109 | + float score = output[mOutputIndex]; |
| 110 | + if (mApplySigmoid) { |
| 111 | + score = score >= 0.f ? 1.f / (1.f + std::exp(-score)) : std::exp(score) / (1.f + std::exp(score)); |
| 112 | + } |
| 113 | + // Class 1 means that GEANT transport can be avoided. |
| 114 | + return score < mThreshold; |
| 115 | + } |
| 116 | + |
| 117 | + private: |
| 118 | + static std::vector<float> makeFeatures(const TParticle& particle, const std::vector<TParticle>& primaries) |
| 119 | + { |
| 120 | + const double px = particle.Px(); |
| 121 | + const double py = particle.Py(); |
| 122 | + const double pz = particle.Pz(); |
| 123 | + const double momentum = std::sqrt(px * px + py * py + pz * pz); |
| 124 | + const double pt = std::hypot(px, py); |
| 125 | + const double mass = particle.GetMass(); |
| 126 | + const double energy = std::sqrt(std::max(0., mass * mass + momentum * momentum)); |
| 127 | + const double eta = momentum > std::abs(pz) ? 0.5 * std::log((momentum + pz) / (momentum - pz)) : 0.; |
| 128 | + const double theta = momentum > 0. ? std::acos(pz / momentum) : 0.; |
| 129 | + const double rapidity = energy > std::abs(pz) ? 0.5 * std::log((energy + pz) / (energy - pz)) : 0.; |
| 130 | + const auto* pdgInfo = particle.GetPDG(); |
| 131 | + const double chargeSign = pdgInfo == nullptr || pdgInfo->Charge() == 0. ? 0. : std::copysign(1., pdgInfo->Charge()); |
| 132 | + const TParticle& eventReference = primaries.empty() ? particle : primaries.front(); |
| 133 | + const double dx = particle.Vx() - eventReference.Vx(); |
| 134 | + const double dy = particle.Vy() - eventReference.Vy(); |
| 135 | + const double dz = particle.Vz() - eventReference.Vz(); |
| 136 | + const double pdg = particle.GetPdgCode(); |
| 137 | + |
| 138 | + return {static_cast<float>(pdg), static_cast<float>(std::abs(pdg)), static_cast<float>(chargeSign), |
| 139 | + static_cast<float>(mass), static_cast<float>(energy), static_cast<float>(energy - mass), |
| 140 | + static_cast<float>(px), static_cast<float>(py), static_cast<float>(pz), static_cast<float>(momentum), |
| 141 | + static_cast<float>(pt), static_cast<float>(eta), static_cast<float>(particle.Phi()), static_cast<float>(theta), |
| 142 | + static_cast<float>(rapidity), static_cast<float>(particle.Vx()), static_cast<float>(particle.Vy()), |
| 143 | + static_cast<float>(particle.Vz()), static_cast<float>(particle.T()), static_cast<float>(dx), |
| 144 | + static_cast<float>(dy), static_cast<float>(dz), static_cast<float>(std::hypot(particle.Vx(), particle.Vy())), |
| 145 | + static_cast<float>(std::hypot(dx, dy)), static_cast<float>(std::sqrt(dx * dx + dy * dy + dz * dz))}; |
| 146 | + } |
| 147 | + |
| 148 | + o2::ml::OrtModel mModel; |
| 149 | + std::vector<char> mModelBytes; // ORT may use model bytes directly; retain them for the session lifetime. |
| 150 | + float mThreshold; |
| 151 | + int mOutputIndex; |
| 152 | + bool mApplySigmoid; |
| 153 | +}; |
| 154 | +} // namespace |
| 155 | + |
45 | 156 | // small helper function to append to vector at arbitrary position |
46 | 157 | template <typename T, typename I> |
47 | 158 | void insertInVector(std::vector<T>& v, I index, T e) |
@@ -100,16 +211,27 @@ Stack::Stack(Int_t size) |
100 | 211 | transportPrimary = o2::conf::GetFromMacro<o2::data::Stack::TransportFcn>(param.transportPrimaryFileName, |
101 | 212 | param.transportPrimaryFuncName, |
102 | 213 | "o2::data::Stack::TransportFcn", "stack_transport_primary"); |
103 | | - if (!mTransportPrimary) { |
| 214 | + if (!transportPrimary) { |
104 | 215 | LOG(fatal) << "Failed to retrieve external \'transportPrimary\' function: problem with configuration "; |
105 | 216 | } |
106 | 217 | LOG(info) << "Successfully retrieve external \'transportPrimary\' frunction: " << param.transportPrimaryFileName; |
| 218 | + } else if (param.transportPrimary.compare("onnx") == 0) { |
| 219 | + try { |
| 220 | + auto classifier = std::make_shared<OnnxPrimaryTransport>(param); |
| 221 | + transportPrimary = [classifier](const TParticle& p, const std::vector<TParticle>& particles) { |
| 222 | + return classifier->transport(p, particles); |
| 223 | + }; |
| 224 | + LOG(info) << "Successfully configured ONNX primary transport pruning from CCDB path " |
| 225 | + << param.transportPrimaryOnnxCCDBPath; |
| 226 | + } catch (const std::exception& error) { |
| 227 | + LOG(fatal) << "Failed to configure ONNX primary transport pruning: " << error.what(); |
| 228 | + } |
107 | 229 | } else { |
108 | 230 | LOG(fatal) << "unsupported \'trasportPrimary\' mode: " << param.transportPrimary; |
109 | 231 | } |
110 | 232 |
|
111 | 233 | if (param.transportPrimaryInvert) { |
112 | | - mTransportPrimary = [transportPrimary](const TParticle& p, const std::vector<TParticle>& particles) { return !transportPrimary; }; |
| 234 | + mTransportPrimary = [transportPrimary](const TParticle& p, const std::vector<TParticle>& particles) { return !transportPrimary(p, particles); }; |
113 | 235 | } else { |
114 | 236 | mTransportPrimary = transportPrimary; |
115 | 237 | } |
|
0 commit comments