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