ATLAS Offline Software
Loading...
Searching...
No Matches
HyPERModel.h
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5#ifndef HYPERANALYSISALGORITHMS_HYPERMODEL_H
6#define HYPERANALYSISALGORITHMS_HYPERMODEL_H
7
8#include <AsgTools/MessageCheckAsgTools.h> // To access ANA_MSG
9#include <math.h>
10
11#include <cstddef>
12#include <iostream>
13#include <map>
14#include <string>
15#include <type_traits>
16#include <variant>
17#include <vector>
18
21
22namespace EventReco {
23// @brief The enum class that holds the different topologies that the HyPER
24// model can make predictions on.
32
39
48 std::string name;
49 bool isFloat;
52};
53
54// @class HyPERModel
55// @brief Adapter around the Athena ONNX inference tools (AthOnnx) for the
56// HyPER models. Two tools are held, one per cross-validation fold, and the
57// caller picks between them per event. Inputs are bound by node name and
58// handed to ONNX Runtime in the order the graph declares them, which is what
59// the tensor-level AthOnnx interface requires.
61 public:
63 const AthOnnx::IOnnxRuntimeInferenceTool* trainedOnOdd,
64 HyPERTopology topology)
65 : m_topology(topology),
66 m_toolTrainedOnEven(trainedOnEven),
67 m_toolTrainedOnOdd(trainedOnOdd) {}
68
69 virtual ~HyPERModel() = default;
70
76 StatusCode initialize() {
77 using namespace asg::msgUserCode;
79 ANA_MSG_ERROR("HyPERModel: ONNX inference tools have not been set");
80 return StatusCode::FAILURE;
81 }
82
83 const std::vector<std::string> inputNames = getInputNames();
84 m_inputIndex.clear();
85 for (std::size_t i = 0; i < inputNames.size(); ++i) {
86 m_inputIndex[inputNames[i]] = i;
87 }
88 m_boundInputs.assign(inputNames.size(), BoundInput{});
89
91 m_outputIndex.clear();
92 for (std::size_t i = 0; i < m_outputNodes.size(); ++i) {
93 m_outputIndex[m_outputNodes[i].name] = i;
94 }
95 m_outputsFloat.assign(m_outputNodes.size(), std::vector<float>{});
96 m_outputsInt64.assign(m_outputNodes.size(), std::vector<int64_t>{});
97
98 // The dynamic output dimensions are read back off these two inputs.
99 if (m_inputIndex.find(s_edgeIndexName) == m_inputIndex.end() ||
101 ANA_MSG_ERROR("HyPERModel: the input names must contain '"
102 << s_edgeIndexName << "' and '" << s_hyperEdgeIndexName
103 << "'");
104 return StatusCode::FAILURE;
105 }
106
107 return StatusCode::SUCCESS;
108 }
109
110 void setTopology(HyPERTopology topology) =
111 delete; // Not able to change the topology after construction
113
118 virtual std::vector<std::string> getInputNames() const = 0;
119
123 virtual std::vector<HyPEROutputNode> getModelOutputs() const = 0;
124
131 virtual std::vector<std::string> getOutputNames() const = 0;
132
137 template <typename T>
138 void setInputs(const std::string& node, std::vector<T>& values,
139 const std::vector<int64_t>& shape) {
140 using namespace asg::msgUserCode;
141 static_assert(std::is_same_v<T, float> || std::is_same_v<T, int64_t>,
142 "HyPER ONNX inputs must be float or int64_t");
143 const auto it = m_inputIndex.find(node);
144 if (it == m_inputIndex.end()) {
145 ANA_MSG_ERROR("HyPERModel: unknown input node '" << node << "'");
146 return;
147 }
148 BoundInput& bound = m_boundInputs[it->second];
149 bound.data = &values;
150 bound.shape = shape;
151 bound.set = true;
152 }
153
158 StatusCode evaluate(unsigned sessionIndex) {
159 using namespace asg::msgUserCode;
160
162 (sessionIndex == 0) ? m_toolTrainedOnEven : m_toolTrainedOnOdd;
163
164 // Inputs, in the order the ONNX graph declares them.
165 std::vector<Ort::Value> inputTensors;
166 inputTensors.reserve(m_boundInputs.size());
167 for (std::size_t i = 0; i < m_boundInputs.size(); ++i) {
168 BoundInput& bound = m_boundInputs[i];
169 if (!bound.set) {
170 ANA_MSG_ERROR("HyPERModel: input node " << i << " was never set");
171 return StatusCode::FAILURE;
172 }
173 if (std::holds_alternative<std::vector<float>*>(bound.data)) {
174 inputTensors.push_back(AthOnnxUtils::createTensor(
175 *std::get<std::vector<float>*>(bound.data), bound.shape));
176 } else {
177 inputTensors.push_back(AthOnnxUtils::createTensor(
178 *std::get<std::vector<int64_t>*>(bound.data), bound.shape));
179 }
180 }
181
182 // The graph's dynamic dimensions, taken from the shapes of the index
183 // tensors: edge_index is {2, nEdges} and edge_index_h is {order, nHyper}.
184 const int64_t nEdges =
186 const int64_t nHyperEdges =
188
189 // Outputs, also in declaration order. ONNX Runtime writes straight into
190 // the buffers owned here, which getOutputs() then hands to the parser.
191 std::vector<Ort::Value> outputTensors;
192 outputTensors.reserve(m_outputNodes.size());
193 for (std::size_t i = 0; i < m_outputNodes.size(); ++i) {
195 int64_t leading = 1;
196 switch (node.dim) {
197 case HyPEROutputDim::HyperEdges: leading = nHyperEdges; break;
198 case HyPEROutputDim::Edges: leading = nEdges; break;
199 case HyPEROutputDim::Single: leading = 1; break;
200 }
201 std::vector<int64_t> shape{leading};
202 if (node.trailingOne) shape.push_back(1);
203
204 const int64_t size = AthOnnxUtils::getTensorSize(shape);
205 if (node.isFloat) {
206 m_outputsFloat[i].assign(static_cast<std::size_t>(size), 0.f);
207 outputTensors.push_back(
209 } else {
210 m_outputsInt64[i].assign(static_cast<std::size_t>(size), 0);
211 outputTensors.push_back(
213 }
214 }
215
216 return tool->inference(inputTensors, outputTensors);
217 }
218
223 template <typename T>
224 T* getOutputs(const std::string& node) {
225 using namespace asg::msgUserCode;
226 static_assert(std::is_same_v<T, float> || std::is_same_v<T, int64_t>,
227 "HyPER ONNX outputs must be float or int64_t");
228 const auto it = m_outputIndex.find(node);
229 if (it == m_outputIndex.end()) {
230 ANA_MSG_ERROR("HyPERModel: unknown output node '" << node << "'");
231 return nullptr;
232 }
233 if constexpr (std::is_same_v<T, float>) {
234 return m_outputsFloat[it->second].data();
235 } else {
236 return m_outputsInt64[it->second].data();
237 }
238 }
239
240 void clearInputs() {
241 for (BoundInput& bound : m_boundInputs) bound = BoundInput{};
242 }
243
245 for (std::vector<float>& out : m_outputsFloat) out.clear();
246 for (std::vector<int64_t>& out : m_outputsInt64) out.clear();
247 }
248
249 void printInputInfo(bool printContent = false) const {
250 using namespace asg::msgUserCode;
251 const std::vector<std::string> names = getInputNames();
252 for (std::size_t i = 0; i < m_boundInputs.size(); ++i) {
253 const BoundInput& bound = m_boundInputs[i];
254 ANA_MSG_INFO(" input " << i << " '" << names[i]
255 << "' shape=" << shapeToString(bound.shape));
256 if (!printContent || !bound.set) continue;
257 if (std::holds_alternative<std::vector<float>*>(bound.data)) {
259 *std::get<std::vector<float>*>(bound.data)));
260 } else {
262 *std::get<std::vector<int64_t>*>(bound.data)));
263 }
264 }
265 }
266
267 void printOutputInfo(bool printContent = false) const {
268 using namespace asg::msgUserCode;
269 for (std::size_t i = 0; i < m_outputNodes.size(); ++i) {
270 ANA_MSG_INFO(" output " << i << " '" << m_outputNodes[i].name << "'");
271 if (!printContent) continue;
272 if (m_outputNodes[i].isFloat) {
274 } else {
276 }
277 }
278 }
279
280 protected:
282
283 private:
286 static constexpr const char* s_edgeIndexName = "edge_index";
287 static constexpr const char* s_hyperEdgeIndexName = "edge_index_h";
288
289 using InputData = std::variant<std::vector<float>*, std::vector<int64_t>*>;
290 struct BoundInput {
291 InputData data{static_cast<std::vector<float>*>(nullptr)};
292 std::vector<int64_t> shape{};
293 bool set{false};
294 };
295
296 template <typename T>
297 static std::string contentToString(const std::vector<T>& values) {
298 std::string row = "[";
299 for (const T& value : values) row += std::to_string(value) + ", ";
300 return row + "]";
301 }
302
303 static std::string shapeToString(const std::vector<int64_t>& shape) {
304 std::string row = "(";
305 for (const int64_t dim : shape) row += std::to_string(dim) + ", ";
306 return row + ")";
307 }
308
311
312 std::map<std::string, std::size_t> m_inputIndex{};
313 std::vector<BoundInput> m_boundInputs{};
314
315 std::vector<HyPEROutputNode> m_outputNodes{};
316 std::map<std::string, std::size_t> m_outputIndex{};
317 std::vector<std::vector<float>> m_outputsFloat{};
318 std::vector<std::vector<int64_t>> m_outputsInt64{};
319};
320} // namespace EventReco
321
322#endif // HYPERANALYSISALGORITHMS_HYPERMODEL_H
#define ANA_MSG_ERROR(xmsg,...)
Macro printing error messages.
#define ANA_MSG_INFO(xmsg,...)
Macro printing info messages.
size_t size() const
Number of registered mappings.
Interface class for creating Onnx Runtime sessions.
std::vector< std::vector< float > > m_outputsFloat
Definition HyPERModel.h:317
T * getOutputs(const std::string &node)
Pointer to the buffer of the named output.
Definition HyPERModel.h:224
const AthOnnx::IOnnxRuntimeInferenceTool * m_toolTrainedOnEven
Definition HyPERModel.h:309
std::vector< BoundInput > m_boundInputs
Definition HyPERModel.h:313
std::variant< std::vector< float > *, std::vector< int64_t > * > InputData
Definition HyPERModel.h:289
static constexpr const char * s_edgeIndexName
Input nodes whose shapes carry the graph's dynamic dimensions.
Definition HyPERModel.h:286
void printOutputInfo(bool printContent=false) const
Definition HyPERModel.h:267
virtual std::vector< std::string > getOutputNames() const =0
Model-specific output names consumed by the parser.
HyPERTopology getTopology() const
Definition HyPERModel.h:112
static constexpr const char * s_hyperEdgeIndexName
Definition HyPERModel.h:287
std::vector< std::vector< int64_t > > m_outputsInt64
Definition HyPERModel.h:318
std::map< std::string, std::size_t > m_outputIndex
Definition HyPERModel.h:316
static std::string contentToString(const std::vector< T > &values)
Definition HyPERModel.h:297
void printInputInfo(bool printContent=false) const
Definition HyPERModel.h:249
HyPERTopology m_topology
Definition HyPERModel.h:281
std::vector< HyPEROutputNode > m_outputNodes
Definition HyPERModel.h:315
virtual std::vector< HyPEROutputNode > getModelOutputs() const =0
Every output node of the ONNX graph, in declaration order.
StatusCode initialize()
Resolve the node name -> node index maps.
Definition HyPERModel.h:76
virtual std::vector< std::string > getInputNames() const =0
Input node names in the order the ONNX graph declares them.
HyPERModel(const AthOnnx::IOnnxRuntimeInferenceTool *trainedOnEven, const AthOnnx::IOnnxRuntimeInferenceTool *trainedOnOdd, HyPERTopology topology)
Definition HyPERModel.h:62
std::map< std::string, std::size_t > m_inputIndex
Definition HyPERModel.h:312
void setInputs(const std::string &node, std::vector< T > &values, const std::vector< int64_t > &shape)
Bind an input by node name.
Definition HyPERModel.h:138
StatusCode evaluate(unsigned sessionIndex)
Run the model of the requested fold over the bound inputs.
Definition HyPERModel.h:158
virtual ~HyPERModel()=default
static std::string shapeToString(const std::vector< int64_t > &shape)
Definition HyPERModel.h:303
const AthOnnx::IOnnxRuntimeInferenceTool * m_toolTrainedOnOdd
Definition HyPERModel.h:310
void setTopology(HyPERTopology topology)=delete
Definition node.h:24
int64_t getTensorSize(const std::vector< int64_t > &dataShape)
Ort::Value createTensor(std::vector< T > &data, const std::vector< int64_t > &dataShape)
Definition OnnxUtils.h:92
HyPEROutputDim
Which of the graph's dynamic dimensions an output tensor scales with.
Definition HyPERModel.h:38
std::vector< int64_t > shape
Definition HyPERModel.h:292
Description of a single output node of the ONNX graph.
Definition HyPERModel.h:47
bool trailingOne
shape is {N, 1} rather than {N}
Definition HyPERModel.h:51
bool isFloat
false means int64
Definition HyPERModel.h:49
HyPEROutputDim dim
which dynamic dimension the tensor scales with
Definition HyPERModel.h:50