Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-16 08:34:51

0001 // -*- C++ -*-
0002 #ifndef RIVET_RivetONNXrt_HH
0003 #define RIVET_RivetONNXrt_HH
0004 
0005 #include <iostream>
0006 #include <functional>
0007 #include <numeric>
0008 
0009 #include "Rivet/Tools/RivetPaths.hh"
0010 #include "Rivet/Tools/Utils.hh"
0011 #include "onnxruntime/onnxruntime_cxx_api.h"
0012 
0013 namespace Rivet {
0014 
0015 
0016   /// @brief Simple interface class to take care of basic ONNX networks
0017   ///
0018   /// See analyses/examples/EXAMPLE_ONNX.cc for how to use this.
0019   ///
0020   /// @note A node is not a neuron but a single tensor of arbitrary dimension size
0021   class RivetONNXrt {
0022   public:
0023 
0024     // Suppress default constructor
0025     RivetONNXrt() = delete;
0026 
0027     /// Constructor
0028     RivetONNXrt(const string& filename, const string& runname="RivetONNXrt") {
0029 
0030       // Set some ORT variables that need to be kept in memory
0031       _env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, runname.c_str());
0032 
0033       // Load the model
0034       Ort::SessionOptions sessionopts;
0035       try {
0036         _session = std::make_unique<Ort::Session> (*_env, filename.c_str(), sessionopts);
0037       } catch (const std::exception & e) {
0038         MSG_ERROR("Failure loading onnx file: " << e.what());
0039       }
0040 
0041       // Store network hyperparameters (input/output shape, etc.)
0042       getNetworkInfo();
0043 
0044       MSG_DEBUG(*this);
0045     }
0046 
0047 
0048     /// Given a multi-node input vector, populate and return the multi-node output vector
0049     ///
0050     /// @todo Expose template param for optional double-valued networks?
0051     template <typename T=float>
0052     vector<vector<T>> compute(const vector<vector<T>>& inputs) const {
0053 
0054       // Check that number of input nodes matches what the model expects
0055       if (inputs.size() != _inDims.size()) {
0056         throw DataError("Expected " + to_string(_inDims.size()) + " input nodes, " +
0057                         "received " + to_string(inputs.size()));
0058       }
0059 
0060       // Create input tensor objects from input data
0061       vector<Ort::Value> ort_input;
0062       ort_input.reserve(_inDims.size());
0063       auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
0064       for (size_t i = 0; i < _inDims.size(); ++i) {
0065 
0066         // Check that input data matches expected input node dimension
0067         if (inputs[i].size() != (size_t)_inDimsFlat[i]) {
0068           throw DataError("Expected flattened dimension " + to_string(_inDimsFlat[i]) +
0069                           " for input node " + to_string(i) +
0070                           ", received " + to_string(inputs[i].size()));
0071         }
0072 
0073         // Check that input data matches expected input node type
0074         _checkTypes(inputs[i].data(), i); //< bit hacky, but minimises duplication
0075 
0076         ort_input.emplace_back(Ort::Value::CreateTensor<T>(memory_info,
0077                                                            const_cast<T*>(inputs[i].data()), inputs[i].size(),
0078                                                            _inDims[i].data(), _inDims[i].size()));
0079       }
0080 
0081       // Retrieve output tensors
0082       auto ort_output = _session->Run(Ort::RunOptions{nullptr}, _inNames.data(),
0083                                       ort_input.data(), ort_input.size(),
0084                                       _outNames.data(), _outNames.size());
0085 
0086       // Construct flattened values and return
0087       vector<vector<T>> outputs; outputs.resize(_outDims.size());
0088       for (size_t i = 0; i < _outDims.size(); ++i) {
0089         T* floatarr = ort_output[i].GetTensorMutableData<T>();
0090         outputs[i].assign(floatarr, floatarr + _outDimsFlat[i]);
0091       }
0092       return outputs;
0093     }
0094 
0095 
0096     /// Given a single-node input vector, populate and return the single-node output vector
0097     template <typename T=float>
0098     vector<T> compute(const vector<T>& inputs) const {
0099       if (_inDims.size() != 1 || _outDims.size() != 1) {
0100         throw("This method assumes a single input/output node!");
0101       }
0102       vector<vector<T>> wrapped_inputs = { inputs };
0103       vector<vector<T>> outputs = compute(wrapped_inputs);
0104       return outputs[0];
0105     }
0106 
0107 
0108     /// Method to check if @a key exists in network metatdata
0109     bool hasKey(const std::string& key) const {
0110       Ort::AllocatorWithDefaultOptions allocator;
0111       return (bool)_metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
0112     }
0113 
0114 
0115     /// Method to retrieve value associated with @a key
0116     /// from network metadata and return value as type T
0117     template <typename T, typename std::enable_if_t<!is_iterable_v<T> | is_cstring_v<T> >>
0118     T retrieve(const std::string& key) const {
0119       Ort::AllocatorWithDefaultOptions allocator;
0120       Ort::AllocatedStringPtr res = _metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
0121       if (!res) {
0122         throw("Key '"+key+"' not found in network metadata!");
0123       }
0124       /*if constexpr (std::is_same<T, std::string>::value) {
0125         return res.get();
0126       }*/
0127       return lexical_cast<T>(res.get());
0128     }
0129 
0130     /// Template specialisation of retrieve for std::string
0131     std::string retrieve(const std::string& key) const {
0132       Ort::AllocatorWithDefaultOptions allocator;
0133       Ort::AllocatedStringPtr res = _metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
0134       if (!res) {
0135         throw("Key '"+key+"' not found in network metadata!");
0136       }
0137       return res.get();
0138     }
0139 
0140     /// Overload of retrieve for vector<T>
0141     template <typename T>
0142     vector<T> retrieve(const std::string & key) const {
0143       const vector<string> stringvec = split(retrieve(key), ",");
0144       vector<T> returnvec = {};
0145       for (const string & s : stringvec){
0146         returnvec.push_back(lexical_cast<T>(s));
0147       }
0148       return returnvec;
0149     }
0150 
0151     /// Overload of retrieve for vector<T>, with a default return
0152     template <typename T>
0153     vector<T> retrieve(const std::string & key, const vector<T> & defaultreturn) const {
0154       try {
0155         return retrieve<T>(key);
0156       } catch (...) {
0157         return defaultreturn;
0158       }
0159     }
0160 
0161     std::string retrieve(const std::string& key, const std::string& defaultreturn) const {
0162       try {
0163         return retrieve(key);
0164       } catch (...) {
0165         return defaultreturn;
0166       }
0167     }
0168 
0169     /// Variation of retrieve method that falls back
0170     /// to @a defaultreturn if @a key cannot be found
0171     template <typename T, typename std::enable_if_t<!is_iterable_v<T> | is_cstring_v<T> >>
0172     T retrieve(const std::string& key, const T& defaultreturn) const {
0173       try {
0174         return retrieve<T>(key);
0175       } catch (...) {
0176         return defaultreturn;
0177       }
0178     }
0179 
0180     /// Printing function for debugging.
0181     friend std::ostream& operator << (std::ostream& os, const RivetONNXrt& rort) {
0182       os << "RivetONNXrt Network Summary: \n";
0183       for (size_t i=0; i < rort._inNames.size(); ++i) {
0184         os << "- Input node " << i << " name: " << rort._inNames[i];
0185         os << ", dimensions: (";
0186         for (size_t j=0; j < rort._inDims[i].size(); ++j){
0187           if (j)  os << ", ";
0188           os << rort._inDims[i][j];
0189         }
0190         os << "), type (as ONNX enums): " << rort._inTypes[i] << "\n";
0191       }
0192       for (size_t i=0; i < rort._outNames.size(); ++i) {
0193         os << "- Output node " << i << " name: " << rort._outNames[i];
0194         os << ", dimensions: (";
0195         for (size_t j=0; j < rort._outDims[i].size(); ++j){
0196           if (j)  os << ", ";
0197           os << rort._outDims[i][j];
0198         }
0199         os << "), type (as ONNX enums): (" << rort._outTypes[i] << "\n";
0200       }
0201       return os;
0202     }
0203 
0204     /// Logger
0205     Log& getLog() const {
0206       string logname = "Rivet.RivetONNXrt";
0207       return Log::getLog(logname);
0208     }
0209 
0210 
0211   private:
0212 
0213     /// @brief Extract and store key info about this network, for checking against inputs
0214     void getNetworkInfo() {
0215 
0216       Ort::AllocatorWithDefaultOptions allocator;
0217 
0218       // Retrieve network metadata
0219       _metadata = std::make_unique<Ort::ModelMetadata>(_session->GetModelMetadata());
0220 
0221       // Find out how many input nodes the model expects
0222       const size_t num_input_nodes = _session->GetInputCount();
0223       _inDimsFlat.reserve(num_input_nodes);
0224       _inTypes.reserve(num_input_nodes);
0225       _inDims.reserve(num_input_nodes);
0226       _inNames.reserve(num_input_nodes);
0227       _inNamesPtr.reserve(num_input_nodes);
0228       for (size_t i = 0; i < num_input_nodes; ++i) {
0229         // Retrieve input node name
0230         auto input_name = _session->GetInputNameAllocated(i, allocator);
0231         _inNames.push_back(input_name.get());
0232         _inNamesPtr.push_back(std::move(input_name));
0233 
0234         // Retrieve input node type
0235         auto in_type_info = _session->GetInputTypeInfo(i);
0236         auto in_tensor_info = in_type_info.GetTensorTypeAndShapeInfo();
0237         _inTypes.push_back(in_tensor_info.GetElementType());
0238         _inDims.push_back(in_tensor_info.GetShape());
0239       }
0240 
0241       // Fix negative shape values - appears to be an artefact of batch size issues.
0242       for (auto& dims : _inDims) {
0243         int64_t n = 1;
0244         for (auto& dim : dims) {
0245           if (dim < 0)  dim = abs(dim);
0246           n *= dim;
0247         }
0248         _inDimsFlat.push_back(n);
0249       }
0250 
0251       // Find out how many output nodes the model expects
0252       const size_t num_output_nodes = _session->GetOutputCount();
0253       _outDimsFlat.reserve(num_output_nodes);
0254       _outTypes.reserve(num_output_nodes);
0255       _outDims.reserve(num_output_nodes);
0256       _outNames.reserve(num_output_nodes);
0257       _outNamesPtr.reserve(num_output_nodes);
0258       for (size_t i = 0; i < num_output_nodes; ++i) {
0259         // Retrieve output node name
0260         auto output_name = _session->GetOutputNameAllocated(i, allocator);
0261         _outNames.push_back(output_name.get());
0262         _outNamesPtr.push_back(std::move(output_name));
0263 
0264         // Retrieve output node type
0265         auto out_type_info = _session->GetOutputTypeInfo(i);
0266         auto out_tensor_info = out_type_info.GetTensorTypeAndShapeInfo();
0267         _outTypes.push_back(out_tensor_info.GetElementType());
0268         _outDims.push_back(out_tensor_info.GetShape());
0269       }
0270 
0271       // Fix negative shape values - appears to be an artefact of batch size issues.
0272       for (auto& dims : _outDims) {
0273         int64_t n = 1;
0274         for (auto& dim : dims) {
0275           if (dim < 0)  dim = abs(dim);
0276           n *= dim;
0277         }
0278         _outDimsFlat.push_back(n);
0279       }
0280     }
0281 
0282 
0283     /// Check for type mismatches (float overload)
0284     void _checkTypes(const float*, size_t inode) const {
0285       if (_inTypes[inode] != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT)
0286         throw DataError("ONNX network provided wrong input type (float)");
0287     }
0288     /// Check for type mismatches (double overload)
0289     void _checkTypes(const double*, size_t inode) const {
0290       if (_inTypes[inode] != ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE)
0291         throw DataError("ONNX network provided wrong input type (double)");
0292     }
0293 
0294   private:
0295 
0296     /// ONNXrt environment for this session
0297     std::unique_ptr<Ort::Env> _env;
0298 
0299     /// ONNXrt session holiding the network
0300     std::unique_ptr<Ort::Session> _session;
0301 
0302     /// Network metadata
0303     std::unique_ptr<Ort::ModelMetadata> _metadata;
0304 
0305     /// Input/output node dimensions
0306     ///
0307     /// @note Each could be a multidimensional tensor
0308     vector<vector<int64_t>> _inDims, _outDims;
0309 
0310     /// Equivalent length for flattened input/output node structure
0311     vector<int64_t> _inDimsFlat, _outDimsFlat;
0312 
0313     /// Types of input/output nodes (as ONNX enums)
0314     vector<ONNXTensorElementDataType> _inTypes, _outTypes;
0315 
0316     /// Pointers to the ONNXrt inout/output node names
0317     vector<Ort::AllocatedStringPtr> _inNamesPtr, _outNamesPtr;
0318 
0319     /// C-style arrays of the input/output node names
0320     vector<const char*> _inNames, _outNames;
0321   };
0322 
0323 
0324   /// Typedef for a handle to an OONXrt object
0325   using RivetONNXrtPtr = unique_ptr<RivetONNXrt>;
0326 
0327 
0328   /// @brief Useful function for getting ONNX file paths
0329   ///
0330   /// Based on getDatafilePath from RivetYODA.cc
0331   inline string getONNXFilePath(const string& filename) {
0332     /// Try to find an ONNX file matching this analysis name
0333     const string path1 = findAnalysisDataFile(filename);
0334     if (!path1.empty()) return path1;
0335     throw Rivet::Error("Couldn't find an ONNX data file for '" + filename + "' " +
0336                        "in the path " + toString(getRivetDataPath()));
0337   }
0338 
0339 
0340   /// Function to get a RivetONNXrt object from an analysis name
0341   ///
0342   /// Use suffix to help disambiguate if an analysis requires 
0343   /// multiple networks. The filename will be structured like
0344   /// ANALYSISNAME[-SUFFIX].EXTN .
0345   ///
0346   /// @todo When/if ONNXrt is fully integrated into Rivet, move to
0347   /// analysis class and remove the analysisname arg.
0348   inline RivetONNXrtPtr getONNX(const string& analysisname, const string& suffix="", const string& extn="onnx") {
0349     const string fname = analysisname + (suffix.empty() ? "" : "-") + suffix + "." + extn;
0350     return make_unique<RivetONNXrt>(getONNXFilePath(fname));
0351   }
0352 
0353 
0354 
0355   /// Forward-compatibility typedefs, for when we eliminate the "Rivet" prefix
0356   /// @{
0357   using ONNXrt = RivetONNXrt;
0358   using ONNXrtPtr = RivetONNXrtPtr;
0359   ///@}
0360 
0361 
0362 }
0363 
0364 #endif