LDMX Software
TrigClusterNNProducer.cxx
2
3#include <algorithm>
4#include <chrono>
5#include <cmath>
6#include <cstdint>
7#include <iomanip>
8#include <ios>
9#include <numeric>
10#include <ratio>
11
12#include "Framework/Event.h"
13#include "Framework/Exception/Exception.h"
14#include "Framework/Logger.h"
15#include "Trigger/Event/TrigClusterNNScore.h"
16
17namespace trigger {
18
19namespace {
20
22std::vector<double> logSoftmax(const std::vector<float>& logits) {
23 const double max_val = *std::max_element(logits.begin(), logits.end());
24 double sum_exp = 0.;
25 for (const float logit : logits) sum_exp += std::exp(logit - max_val);
26 const double log_sum_exp = max_val + std::log(sum_exp);
27
28 std::vector<double> result;
29 result.reserve(logits.size());
30 for (const float logit : logits) result.push_back(logit - log_sum_exp);
31 return result;
32}
33
34} // namespace
35
36TrigClusterNNProducer::TrigClusterNNProducer(const std::string& name,
37 framework::Process& process)
38 : framework::Producer(name, process),
39 data_{std::vector<float>(MAX_CLUSTERS * N_FEATURES, 0.f)} {}
40
41void TrigClusterNNProducer::configure(framework::config::Parameters& ps) {
42 cluster_coll_name_ = ps.get<std::string>("cluster_coll_name");
43 ecal_sum_coll_name_ = ps.get<std::string>("ecal_sum_coll_name");
44 hcal_sum_coll_name_ = ps.get<std::string>("hcal_sum_coll_name");
45 input_pass_ = ps.get<std::string>("input_pass");
46 score_coll_name_ = ps.get<std::string>("score_coll_name");
47
48 const auto model_path{ps.get<std::string>("model_path")};
49 if (model_path.empty()) {
50 EXCEPTION_RAISE("NoModel",
51 "model_path is empty. Pass the path to "
52 "ecal_nn_trigger.onnx.");
53 }
54 rt_ = std::make_unique<ldmx::ort::ONNXRuntime>(model_path);
55
56 // the batch dimension is reported as -1 by the wrapper
57 const std::vector<int64_t> expected_shape{-1,
58 static_cast<int64_t>(N_CLASSES)};
59 const auto& outputs = rt_->getOutputNames();
60 if (std::find(outputs.begin(), outputs.end(), "logits") == outputs.end() or
61 rt_->getOutputShape("logits") != expected_shape) {
62 EXCEPTION_RAISE("BadModel", "Model '" + model_path +
63 "' does not have a 'logits' output of "
64 "shape (batch, " +
65 std::to_string(N_CLASSES) + ").");
66 }
67}
68
69void TrigClusterNNProducer::produce(framework::Event& event) {
70 const auto start = std::chrono::high_resolution_clock::now();
71 nevents_++;
72
73 // throws an exception if an input is missing or found in more than
74 // one pass; an empty input is valid and the event is still scored
75 const auto& clusters{
76 event.getCollection<TrigCaloCluster>(cluster_coll_name_, input_pass_)};
77 const auto& ecal_sums{
78 event.getCollection<TrigEnergySum>(ecal_sum_coll_name_, input_pass_)};
79 const auto& hcal_sums{
80 event.getCollection<TrigEnergySum>(hcal_sum_coll_name_, input_pass_)};
81
82 fillInputs(clusters, ecal_sums, hcal_sums, data_[0]);
83 const auto inference_start = std::chrono::high_resolution_clock::now();
84 const auto logits{rt_->run({"x"}, data_, {"logits"})[0]};
85 inference_time_ +=
86 std::chrono::duration<double, std::milli>(
87 std::chrono::high_resolution_clock::now() - inference_start)
88 .count();
89
90 // P(bkg) in double: 1 - P(bkg) saturates to 1 in float
91 const double p_bkg = std::exp(logSoftmax(logits)[BKG_CLASS]);
92
93 ldmx_log(debug) << "n_clusters = " << clusters.size()
94 << ", P(bkg) = " << p_bkg;
95
97 score.setLogits(logits);
98 score.setPBkg(p_bkg);
99 event.add(score_coll_name_, score);
100
101 processing_time_ += std::chrono::duration<double, std::milli>(
102 std::chrono::high_resolution_clock::now() - start)
103 .count();
104}
105
106void TrigClusterNNProducer::onProcessEnd() {
107 if (nevents_ == 0) return;
108 ldmx_log(info) << "AVG Time/Event: total " << std::fixed
109 << std::setprecision(3) << processing_time_ / nevents_
110 << " ms, inference " << inference_time_ / nevents_ << " ms";
111}
112
113void TrigClusterNNProducer::fillInputs(
114 const TrigCaloClusterCollection& clusters,
115 const TrigEnergySumCollection& ecal_sums,
116 const TrigEnergySumCollection& hcal_sums, std::vector<float>& inputs) {
117 inputs.assign(MAX_CLUSTERS * N_FEATURES, 0.f);
118
119 // sort cluster indices by descending energy
120 std::vector<std::size_t> order(clusters.size());
121 std::iota(order.begin(), order.end(), 0);
122 std::stable_sort(order.begin(), order.end(),
123 [&clusters](const std::size_t lhs, const std::size_t rhs) {
124 return clusters[lhs].e() > clusters[rhs].e();
125 });
126
127 double ecal_sum_e = 0.;
128 for (const auto& sum : ecal_sums) ecal_sum_e += sum.energy();
129 // the HCal sums carry hardware (ADC) energy
130 int hcal_sum_adc = 0;
131 for (const auto& sum : hcal_sums) hcal_sum_adc += sum.hwEnergy();
132
133 for (std::size_t slot = 0; slot < MAX_CLUSTERS; ++slot) {
134 float* feats = &inputs[slot * N_FEATURES];
135 if (slot < order.size()) {
136 const auto& cluster = clusters[order[slot]];
137 feats[FEAT_X] = cluster.x();
138 feats[FEAT_Y] = cluster.y();
139 feats[FEAT_Z] = cluster.z();
140 feats[FEAT_E] = cluster.e();
141 feats[FEAT_DEPTH] = cluster.depth();
142 feats[FEAT_N_TP] = cluster.nTP();
143 }
144 // event-level features are filled in every slot
145 feats[FEAT_N_CLUSTERS] = clusters.size();
146 feats[FEAT_ECAL_SUM_E] = ecal_sum_e;
147 feats[FEAT_HCAL_SUM_ADC] = hcal_sum_adc;
148 }
149}
150
151} // namespace trigger
152
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Class implementing an event buffer system for storing event data.
ECal NN classifier score from an MLP over trigger clusters and calorimeter sums.
Implements an event buffer system for storing event data.
Definition Event.h:40
Class which represents the process under execution.
Definition Process.h:34
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
Class for clusters built from trigger calo hits.
Scores each event with the ECal trigger MLP and stores the output.
Output of the ECal trigger-cluster NN classifier for one event.
Contains the trigger output for generic calo objects.
All classes in the ldmx-sw project use this namespace.