Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-07-15 09:14:06

0001 // -*- C++ -*-
0002 //
0003 // This file is part of YODA -- Yet more Objects for Data Analysis
0004 // Copyright (C) 2008-2025 The YODA collaboration (see AUTHORS for details)
0005 //
0006 #ifndef YODA_AnalysisObject_h
0007 #define YODA_AnalysisObject_h
0008 
0009 #include "YODA/Exceptions.h"
0010 #include "YODA/Utils/StringUtils.h"
0011 #include "YODA/Config/BuildConfig.h"
0012 
0013 #ifdef HAVE_HDF5
0014 #include "YODA/Utils/H5Utils.h"
0015 #endif
0016 
0017 #include <iomanip>
0018 #include <string>
0019 #include <limits>
0020 #include <map>
0021 
0022 namespace YODA {
0023 
0024 
0025   /// AnalysisObject is the base class for histograms and scatters
0026   class AnalysisObject {
0027 
0028   public:
0029 
0030     /// Collection type for annotations, as a string-string map.
0031     typedef std::map<std::string, std::string> Annotations;
0032 
0033 
0034     /// @name Creation and destruction
0035     /// @{
0036 
0037     /// Default constructor
0038     AnalysisObject() { }
0039 
0040     /// Constructor giving a type, a path and an optional title
0041     AnalysisObject(const std::string& type, const std::string& path, const std::string& title="") {
0042       setAnnotation("Type", type);
0043       setPath(path);
0044       setTitle(title);
0045     }
0046 
0047     /// Constructor giving a type, a path, another AO to copy annotation from, and an optional title
0048     AnalysisObject(const std::string& type, const std::string& path,
0049                    const AnalysisObject& ao, const std::string& title="") {
0050       AnalysisObject::operator = (ao);
0051       setAnnotation("Type", type); // might override the copied ones
0052       setPath(path);
0053       setTitle(title);
0054     }
0055 
0056     // /// Default copy constructor
0057     // AnalysisObject(const AnalysisObject& ao) {
0058     //   if (ao.path().length() > 0) setPath(ao.path());
0059     //   if (ao.title().length() > 0) setTitle(ao.title());
0060     // }
0061 
0062     /// Default destructor
0063     virtual ~AnalysisObject() { }
0064 
0065     /// Default copy assignment operator
0066     virtual AnalysisObject& operator = (const AnalysisObject& ao) noexcept {
0067       for (const std::string& a : ao.annotations()) {
0068         if (a == "Type")  continue;
0069         if (a == "Path"  && !ao.path().length())  continue;
0070         if (a == "Title" && !ao.title().length())  continue;
0071         setAnnotation(a, ao.annotation(a));
0072       }
0073       return *this;
0074     }
0075 
0076     /// Make a copy on the heap, via 'new'
0077     virtual AnalysisObject* newclone() const = 0;
0078 
0079     /// @}
0080 
0081 
0082 
0083     /// @name Modifiers
0084     /// @{
0085 
0086     /// Reset this analysis object
0087     virtual void reset() = 0;
0088 
0089     /// @}
0090 
0091     /// @name I/O methods
0092     /// @{
0093 
0094     /// Render information about this analysis object
0095     virtual void _renderYODA(std::ostream& os, const int width = 13) const noexcept = 0;
0096 
0097     /// Render scatter-like information about this analysis object
0098     virtual void _renderFLAT(std::ostream& os, const int width = 13) const noexcept = 0;
0099 
0100     #ifdef HAVE_HDF5
0101 
0102     /// Helper method to extract error labels (where applicable) for the Writer
0103     virtual void _extractLabels(std::vector<std::string>&, std::vector<size_t>&) const noexcept { };
0104 
0105     /// Helper method to extract edges (where applicable) for the Writer
0106     virtual void _extractEdges(std::map<std::string, EdgeHandlerBasePtr>&,
0107                                const std::vector<std::string>&) const noexcept { };
0108 
0109     #endif
0110 
0111     /// Return axis configuration (where applicable)
0112     virtual std::string _config() const noexcept { return ""; }
0113 
0114     /// @}
0115 
0116 
0117 
0118     ///@name Annotations
0119     /// @{
0120 
0121     /// Get all the annotation names
0122     /// @todo Change this to return the str->str map, with a separate annotationKeys, etc.
0123     std::vector<std::string> annotations() const {
0124       std::vector<std::string> rtn;
0125       rtn.reserve(_annotations.size());
0126       for (const Annotations::value_type& kv : _annotations) rtn.push_back(kv.first);
0127       return rtn;
0128     }
0129 
0130 
0131     /// Check if an annotation is defined
0132     bool hasAnnotation(const std::string& name) const {
0133       return _annotations.find(name) != _annotations.end();
0134     }
0135 
0136 
0137     /// Get an annotation by name (as a string)
0138     const std::string& annotation(const std::string& name) const {
0139       Annotations::const_iterator v = _annotations.find(name);
0140       // If not found... written this way round on purpose
0141       if (v == _annotations.end()) {
0142         std::string missing = "YODA::AnalysisObject: No annotation named " + name;
0143         throw AnnotationError(missing);
0144       }
0145       return v->second;
0146     }
0147 
0148 
0149     /// Get an annotation by name (as a string) with a default in case the annotation is not found
0150     const std::string& annotation(const std::string& name, const std::string& defaultreturn) const {
0151       Annotations::const_iterator v = _annotations.find(name);
0152       if (v != _annotations.end()) return v->second;
0153       return defaultreturn;
0154     }
0155 
0156 
0157     /// @brief Get an annotation by name (copied to another type)
0158     ///
0159     /// @note Templated on return type
0160     template <typename T>
0161     const T annotation(const std::string& name) const {
0162       std::string s = annotation(name);
0163       return Utils::lexical_cast<T>(s);
0164     }
0165 
0166 
0167     /// @brief Get an annotation by name (copied to another type) with a default in case the annotation is not found
0168     ///
0169     /// @note Templated on return type
0170     template <typename T>
0171     const T annotation(const std::string& name, const T& defaultreturn) const {
0172       Annotations::const_iterator v = _annotations.find(name);
0173       if (v != _annotations.end()) return Utils::lexical_cast<T>(v->second);
0174       return defaultreturn;
0175     }
0176 
0177 
0178     /// @brief Add or set an annotation by name (templated for remaining types)
0179     ///
0180     /// @note Templated on arg type, but stored as a string.
0181     template <typename T>
0182     void setAnnotation(const std::string& name, const T& value) {
0183       if constexpr( std::is_floating_point<T>::value ) {
0184         // Recipe from Boost docs
0185         std::stringstream ss;
0186         ss << std::setprecision(std::numeric_limits<double>::max_digits10) << std::scientific << value;
0187         setAnnotation(name, ss.str());
0188       }
0189       else if constexpr( std::is_same<T, std::string>::value ) {
0190         _annotations[name] = value;
0191       }
0192       else {
0193         _annotations[name] = Utils::lexical_cast<std::string>(value);
0194       }
0195     }
0196 
0197 
0198     /// Set all annotations at once
0199     void setAnnotations(const Annotations& anns) {
0200       _annotations = anns;
0201     }
0202 
0203 
0204     /// @brief Add or set an annotation by name
0205     ///
0206     /// Note: Templated on arg type, but stored as a string. This is just a synonym for setAnnotation.
0207     template <typename T>
0208     void addAnnotation(const std::string& name, const T& value) {
0209       setAnnotation(name, value);
0210     }
0211 
0212 
0213     /// Delete an annotation by name
0214     void rmAnnotation(const std::string& name) {
0215       _annotations.erase(name);
0216     }
0217 
0218 
0219     /// Delete an annotation by name
0220     void clearAnnotations() {
0221       _annotations.clear();
0222     }
0223 
0224     /// @}
0225 
0226 
0227     /// @name Standard annotations
0228     /// @{
0229 
0230     /// @brief Get the AO title.
0231     ///
0232     /// Returns a null string if undefined, rather than throwing an exception cf. the annotation("Title").
0233     const std::string title() const {
0234       return annotation("Title", "");
0235     }
0236 
0237     /// Set the AO title
0238     void setTitle(const std::string& title) {
0239       setAnnotation("Title", title);
0240     }
0241 
0242     /// @brief Get the AO path.
0243     ///
0244     /// Returns a null string if undefined, rather than throwing an exception cf. annotation("Path").
0245     /// @note A leading / will be prepended if not already set.
0246     const std::string path() const {
0247       const std::string p = annotation("Path", "");
0248       // If not set at all, return an empty string
0249       if (p.empty()) return p;
0250       // If missing a leading slash, one will be prepended
0251       return p.find("/") == 0 ? p : ("/"+p);
0252     }
0253 
0254     /// Set the AO path
0255     ///
0256     /// @note A leading / will be prepended if not already given.
0257     void setPath(const std::string& path) {
0258       const std::string p = (path.find("/") == 0) ? path : "/"+path;
0259       // if (path.length() > 0 && path.find("/") != 0) {
0260       //   throw AnnotationError("Histo paths must start with a slash (/) character.");
0261       // }
0262       setAnnotation("Path", p);
0263     }
0264 
0265 
0266     /// Get the AO name -- the last part of the path.
0267     /// Returns a null string if path is undefined
0268     const std::string name() const {
0269       const std::string p = path();
0270       const size_t lastslash = p.rfind("/");
0271       if (lastslash == std::string::npos) return p;
0272       return p.substr(lastslash+1);
0273     }
0274 
0275     /// @}
0276 
0277 
0278   public:
0279 
0280     /// @name Persistency hooks / object type info
0281     /// @{
0282 
0283     /// Get name of the analysis object type
0284     virtual std::string type() const {
0285       return annotation("Type");
0286     }
0287 
0288     /// @brief Get the dimension of the analysis object type
0289     ///
0290     /// @note For fillable types this is the dimension of the fill space (e.g. Histo1D -> dim=1).
0291     ///    For scatter types, it is the total dimension of the points (e.g. Scatter3D -> dim=3).
0292     virtual size_t dim() const noexcept = 0;
0293 
0294     /// @brief Return an inert version of the analysis object (e.g. scatter, estimate)
0295     virtual AnalysisObject* mkInert(const std::string& path = "",
0296                                     const std::string& source = "") const noexcept {
0297       (void)source;
0298       AnalysisObject* rtn = this->newclone();
0299       rtn->setPath(path);
0300       return rtn;
0301     }
0302 
0303     /// @}
0304 
0305     /// @name MPI serialisation
0306     ///@{
0307 
0308     /// @brief Length of serialized content vector for MPI reduce operations
0309     virtual size_t lengthContent(bool fixed_length = false) const noexcept = 0;
0310 
0311     /// @brief Content serialisation for MPI reduce operations
0312     virtual std::vector<double> serializeContent(bool fixed_length = false) const noexcept = 0;
0313 
0314     /// @brief Content deserialisation for MPI reduce operations
0315     virtual void deserializeContent(const std::vector<double>& data) = 0;
0316 
0317     /// @brief Length of serialized meta-data vector for MPI reduce operations
0318     size_t lengthMeta(const bool skipPath = true,
0319                       const bool skipTitle = true) const noexcept {
0320       return 2*(_annotations.size() - skipPath - skipTitle);
0321     }
0322 
0323     /// @brief Meta-data serialisation for MPI reduce operations
0324     std::vector<std::string> serializeMeta(const bool skipPath = true,
0325                                            const bool skipTitle = true) const noexcept {
0326 
0327       // Assemble annotations
0328       std::vector<std::string> rtn;
0329       rtn.reserve(2*(_annotations.size() - skipPath - skipTitle));
0330       for (const auto& item : _annotations) {
0331         if (item.first == "Type")  continue;
0332         if (skipPath && item.first == "Path")  continue;
0333         if (skipTitle && item.first == "Title")  continue;
0334         rtn.push_back(item.first);
0335         rtn.push_back(item.second);
0336       }
0337       return rtn;
0338     }
0339 
0340     /// @brief Mate-data deserialisation for MPI reduce operations
0341     virtual void deserializeMeta(const std::vector<std::string>& data,
0342                                  const bool resetPath = false, const bool resetTitle = false) {
0343 
0344       if (data.empty())  return;
0345       if (data.size() % 2)
0346         throw  UserError("Expected even number of annotation elements (key-value pairs)!");
0347 
0348       const std::string path = annotation("Path");
0349       const std::string type = annotation("Type");
0350       const std::string title = annotation("Title");
0351       _annotations.clear();
0352       _annotations["Type"] = type;
0353       if (!resetPath)   _annotations["Path"] = path;
0354       if (!resetTitle)  _annotations["Title"] = title;
0355 
0356       auto itr = data.cbegin();
0357       const auto itrEnd = data.cend();
0358       while (itr != itrEnd) {
0359         const std::string key = *itr; ++itr;
0360         const std::string val = *itr; ++itr;
0361         _annotations[key] = val;
0362       }
0363     }
0364 
0365     /// @}
0366 
0367   private:
0368 
0369     /// The annotations indexed by name
0370     std::map<std::string,std::string> _annotations;
0371 
0372   };
0373 
0374 
0375   /// Convenience alias
0376   using AO = AnalysisObject;
0377 
0378 
0379 }
0380 
0381 #endif // YODA_AnalysisObject_h