Back to home page

EIC code displayed by LXR

 
 

    


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

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_POINT_H
0007 #define YODA_POINT_H
0008 
0009 #include "YODA/AnalysisObject.h"
0010 #include "YODA/Exceptions.h"
0011 #include "YODA/Transformation.h"
0012 #include "YODA/Utils/MathUtils.h"
0013 #include "YODA/Utils/PointUtils.h"
0014 #include "YODA/Utils/Traits.h"
0015 #include "YODA/Utils/ndarray.h"
0016 #include <utility>
0017 #include <algorithm>
0018 #include <iostream>
0019 
0020 
0021 namespace YODA {
0022 
0023 
0024   /// Base class for all Point*Ds, providing generic access to their numerical properties
0025   class Point {
0026   public:
0027 
0028     using ValuePair = std::pair<double,double>;
0029 
0030 
0031     /// Virtual destructor for inheritance
0032     virtual ~Point() {};
0033 
0034 
0035     /// @name Core properties
0036     /// @{
0037 
0038     /// Space dimension of the point
0039     virtual size_t dim() const = 0;
0040 
0041     /// Get the point value for direction @a i
0042     virtual double val(size_t i) const = 0;
0043 
0044     /// Set the point value for direction @a i
0045     virtual void setVal(const size_t i, const double val) = 0;
0046 
0047     /// @}
0048 
0049 
0050     /// @name Errors
0051     /// @{
0052 
0053     /// Get error values for direction @a i
0054     //virtual const std::pair<double,double>& errs(size_t i) const = 0;
0055     /// Set symmetric error for direction @a i
0056     virtual void setErr(const size_t i, const double e) = 0;
0057 
0058     /// Set asymmetric error for direction @a i
0059     virtual void setErrs(const size_t i, const double eminus, const double eplus) = 0;
0060 
0061     /// Set error pair for direction @a i
0062     virtual void setErrs(const size_t i, const std::pair<double,double>& e) = 0;
0063 
0064     /// Get negative error value for direction @a i
0065     //virtual double errMinus(size_t i) const = 0;
0066     /// Set negative error for direction @a i
0067     virtual void setErrMinus(const size_t i, const double eminus) = 0;
0068 
0069     /// Get positive error value for direction @a i
0070     //virtual double errPlus(size_t i) const = 0;
0071     /// Set positive error for direction @a i
0072     virtual void setErrPlus(const size_t i, const double eplus) = 0;
0073 
0074     /// Get average error value for direction @a i
0075     //virtual double errAvg(size_t i) const = 0;
0076 
0077     // /// Get value minus negative error for direction @a i
0078     // double min(size_t i) const = 0;
0079     // /// Get value plus positive error for direction @a i
0080     // double max(size_t i) const = 0;*/
0081 
0082     /// @}
0083 
0084 
0085     /// @name Combined value and error setters
0086     /// @{
0087 
0088     /// Set value and symmetric error for direction @a i
0089     virtual void set(const size_t i, const double val, const double e) = 0;
0090 
0091     /// Set value and asymmetric error for direction @a i
0092     virtual void set(const size_t i, const double val, const double eminus, const double eplus) = 0;
0093 
0094     /// Set value and asymmetric error for direction @a i
0095     virtual void set(const size_t i, const double val, const std::pair<double,double>& e) = 0;
0096 
0097     /// @}
0098 
0099 
0100     /// @name Manipulations
0101     /// @{
0102 
0103     // /// Scaling of direction @a i
0104     virtual void scale(const size_t i, const double scale) = 0;
0105 
0106     //template<typename FN>
0107     //void scale(size_t i, FN f) = 0;
0108 
0109     /// @}
0110 
0111   };
0112 
0113 
0114 
0115 
0116 
0117   /// The base for an N-dimensional data point to be contained in a Scatter<N>
0118   template<size_t N>
0119   class PointBase : public Point {
0120   protected:
0121 
0122      // Convenient aliases
0123      using Pair = std::pair<double,double>;
0124      using ValList = std::initializer_list<double>;
0125      using PairList = std::initializer_list<Pair>;
0126 
0127      // extract the content type of an array Arr
0128      template<typename Arr>
0129      using containedType = std::decay_t<decltype(*std::declval<Arr>().begin())>;
0130 
0131      // check if content type of an array Arr is Pair
0132      template<typename Arr>
0133      using containsPair = typename std::is_same<containedType<Arr>, Pair>;
0134 
0135      // succeeds if T is an iterable container
0136      template<typename T>
0137      using isIterable = std::enable_if_t<Iterable<T>::value>;
0138 
0139      // succeeds if T is an iterable container and its content type is Pair
0140      template<typename T, typename U>
0141      using isIterableWithPair = std::enable_if_t<(Iterable<T>::value && Iterable<U>::value && containsPair<U>::value)>;
0142 
0143   public:
0144 
0145     // Typedefs
0146     using NdVal = typename Utils::ndarray<double, N>;
0147     using NdValPair = typename Utils::ndarray<std::pair<double,double>, N>;
0148     using DataSize = std::integral_constant<size_t, 3*N>;
0149 
0150 
0151     /// @name Constructors
0152     /// @{
0153 
0154     // @brief Default constructor
0155     PointBase() {
0156       clear();
0157     }
0158 
0159 
0160     /// @brief Constructor from position values without errors
0161     template <typename ValRange = ValList, typename = isIterable<ValRange>>
0162     PointBase(ValRange&& val) : _val(std::forward<ValRange>(val)) { }
0163 
0164 
0165     /// Constructor from values and a set of asymmetric errors
0166     template <typename ValRange = ValList,
0167               typename PairRange = PairList,
0168               typename = isIterableWithPair<ValRange,PairRange>>
0169     PointBase(ValRange&& val, PairRange&& errs)
0170       : _val(std::forward<ValRange>(val)), _errs(std::forward<PairRange>(errs)) { }
0171 
0172     /// Constructor from values and a set of symmetric errors
0173     template <typename ValRange = ValList, typename = isIterable<ValRange>>
0174     PointBase(ValRange&& val, ValRange&& errs)
0175        : _val(std::forward<ValRange>(val)) {
0176       if (val.size() != N || errs.size() != N)
0177         throw RangeError("Expected " + std::to_string(N) + " dimensions.");
0178       size_t i = 0;
0179       auto it = std::begin(errs);
0180       const auto& itEnd = std::end(errs);
0181       for (; it != itEnd; ++it) {
0182         _errs[i++] = std::make_pair(*it, *it);
0183       }
0184     }
0185 
0186 
0187     /// Constructor from values and a set of asymmetric errors
0188     template <typename ValRange = ValList, typename = isIterable<ValRange>>
0189     PointBase(ValRange&& val, ValRange&& errsdn, ValRange&& errsup)
0190        : _val(std::forward<ValRange>(val)) {
0191       if (val.size() != N || errsdn.size() != N || errsup.size() != N)
0192         throw RangeError("Expected " + std::to_string(N) + " dimensions.");
0193       size_t i = 0;
0194       auto itdn = std::begin(errsdn);
0195       auto itup = std::begin(errsup);
0196       const auto& itEnd = std::end(errsdn);
0197       for (; itdn != itEnd; ) {
0198         _errs[i++] = std::make_pair(*itdn++, *itup++);
0199       }
0200     }
0201 
0202     PointBase(const PointBase& p) : _val(p._val), _errs(p._errs) { }
0203 
0204     PointBase(PointBase&& p) : _val(std::move(p._val)), _errs(std::move(p._errs)) { }
0205 
0206     /// @}
0207 
0208     /// Space dimension of the point
0209     size_t dim() const { return N; }
0210 
0211     /// @name I/O
0212     /// @{
0213 
0214     void _renderYODA(std::ostream& os, const int width = 13) const noexcept {
0215 
0216       for (size_t i = 0; i < N; ++i) {
0217         os << std::setw(width) << std::left << _val[i] << "\t"
0218            << std::setw(width) << std::left << _errs[i].first  << "\t"
0219            << std::setw(width) << std::left << _errs[i].second << "\t";
0220       }
0221       os << "\n";
0222 
0223     }
0224 
0225     /// @}
0226 
0227     /// @name Modifiers
0228     /// @{
0229 
0230     /// Clear the point values and errors
0231     void clear() {
0232       for (size_t i = 0; i < N; ++i) {
0233         _val[i] = 0;
0234         _errs[i] = {0.,0.};
0235       }
0236     }
0237 
0238     /// Assignment operator
0239     PointBase& operator = (const PointBase& p) {
0240       if (this != &p) {
0241         _val = p._val;
0242         _errs = p._errs;
0243       }
0244       return *this;
0245     }
0246 
0247     /// Assignment operator
0248     PointBase& operator = (PointBase&& p) {
0249       if (this != &p) {
0250         _val = std::move(p._val);
0251         _errs = std::move(p._errs);
0252       }
0253       return *this;
0254     }
0255 
0256     /// @todo addError, addErrors, setErrors
0257 
0258     /// @}
0259 
0260 
0261   public:
0262 
0263     /// @name Coordinate accessors
0264     /// @{
0265 
0266     /// Get the coordinate vector
0267     NdVal& vals() { return _val; }
0268 
0269     /// Get the coordinate vector (const version)
0270     const NdVal& vals() const { return _val; }
0271 
0272     /// Get the value along direction @a i
0273     double val(size_t i) const {
0274       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0275       return _val[i];
0276     }
0277 
0278     /// Set the coordinate vector
0279     void setVal(const NdVal& val) {
0280       _val = val;
0281     }
0282 
0283     /// Set a specific coordinate
0284     void setVal(const size_t i, const double val) {
0285       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0286       _val[i] = val;
0287     }
0288 
0289     /// @}
0290 
0291 
0292     /// @name Error accessors
0293     /// @{
0294 
0295     /// Get error values
0296     NdValPair& errs() {
0297       return _errs;
0298     }
0299 
0300     /// Get error values (const version)
0301     const NdValPair& errs() const {
0302       return _errs;
0303     }
0304 
0305     /// Get error values along axis @a i
0306     Pair errs(const size_t i) const {
0307       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0308       return _errs[i];
0309     }
0310 
0311     /// Get the minus error along axis @a i
0312     double errMinus(const size_t i) const {
0313       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0314       return _errs[i].first;
0315     }
0316 
0317     /// Get the plus error along axis @a i
0318     double errPlus(const size_t i) const {
0319       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0320       return _errs[i].second;
0321     }
0322 
0323     // Get the average error along axis @a i
0324     double errAvg(const size_t i) const {
0325       return 0.5*(errMinus(i) + errPlus(i));
0326     }
0327 
0328     /// Get value minus negative error along axis @a i
0329     double min(const size_t i) const {
0330       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0331       return _val[i] - _errs[i].first;
0332     }
0333 
0334     /// Get value plus positive error along axis @a i
0335     double max(const size_t i) const {
0336       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0337       return _val[i] + _errs[i].second;
0338     }
0339 
0340     /// Set a symmetric error pair along axis @a i
0341     void setErr(const size_t i, const double e) {
0342       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0343       const double err = fabs(e);
0344       _errs[i] = { err, err};
0345     }
0346 
0347     /// Set an asymmetric error pair along axis @a i
0348     void setErrs(const size_t i, const double eminus, const double eplus) {
0349       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0350       _errs[i] = { eminus, eplus};
0351     }
0352 
0353     /// Set a specific error pair along axis @a i
0354     void setErrs(const size_t i, const std::pair<double,double>& e) {
0355       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0356       _errs[i] = e;
0357     }
0358 
0359     /// Set a specific minus error along axis @a i
0360     void setErrMinus(const size_t i, const double eminus) {
0361       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0362       _errs[i].first = eminus;
0363     }
0364 
0365     /// Set a specific plus error along axis @a i
0366     void setErrPlus(const size_t i, const double eplus) {
0367       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0368       _errs[i].second = eplus;
0369     }
0370 
0371     /// @}
0372 
0373     /// @name Combined value and error setters
0374     /// @{
0375 
0376     void set(const size_t i, const double val, const double e) {
0377       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0378       const double err = fabs(e);
0379       _val[i] = val;
0380       _errs[i] = {err,err};
0381     }
0382 
0383     void set(const size_t i, const double val, const double eminus, const double eplus) {
0384       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0385       _val[i] = val;
0386       _errs[i].first = eminus;
0387       _errs[i].second = eplus;
0388     }
0389 
0390     void set(const size_t i, const double val, const std::pair<double,double>& e) {
0391       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0392       _val[i] = val;
0393       _errs[i] = e;
0394     }
0395 
0396     /// @}
0397 
0398     /// @name Scaling and transformations
0399     /// @{
0400 
0401     /// Scaling value along direction @a i
0402     void scaleVal(const size_t i, const double scale) {
0403       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0404       _val[i] *= scale;
0405     }
0406 
0407     /// Scaling error along direction @a i
0408     void scaleErr(const size_t i, const double scale) {
0409       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0410       _errs[i].first  *= scale;
0411       _errs[i].second *= scale;
0412     }
0413 
0414     /// Scaling along direction @a i
0415     void scale(const size_t i, const double scale) {
0416       scaleVal(i, scale);
0417       scaleErr(i, scale);
0418     }
0419 
0420     /// Uniform scaling
0421     void scale(const NdVal& scales) {
0422       for (size_t i = 0; i < N; ++i) {
0423         scale(i, scales[i]);
0424       }
0425     }
0426 
0427     /// Generalised transformations with functors
0428     void scale(const Trf<N>& trf) {
0429       trf.transform(_val, _errs);
0430     }
0431 
0432     /// Generalised transformations with functors
0433     /// along axis @a i
0434     void scale(const size_t i, const Trf<N>& trf) {
0435       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0436       trf.transform(_val[i], _errs[i]);
0437     }
0438     //
0439     void transform(const size_t i, const Trf<N>& trf) {
0440       scale(i, trf);
0441     }
0442 
0443     /// @}
0444 
0445     /// @name MPI (de-)serialisation
0446     //@{
0447 
0448     std::vector<double> _serializeContent() const noexcept {
0449       std::vector<double> rtn;
0450       rtn.reserve(DataSize::value);
0451       rtn.insert(rtn.end(), _val.begin(),  _val.end());
0452       for (auto err : _errs) {
0453         rtn.push_back(std::move(err.first));
0454         rtn.push_back(std::move(err.second));
0455       }
0456       return rtn;
0457     }
0458 
0459     void _deserializeContent(const std::vector<double>& data) {
0460 
0461       if (data.size() != DataSize::value)
0462         throw UserError("Length of serialized data should be "+std::to_string(DataSize::value)+"!");
0463 
0464       for (size_t i = 0; i < N; ++i) {
0465         _val[i] = data[i];
0466         _errs[i] = { data[N+i], data[2*N+i] };
0467       }
0468 
0469     }
0470 
0471     // @}
0472 
0473   protected:
0474 
0475     /// @name Value and error variables
0476     /// @{
0477 
0478     NdVal _val;
0479     NdValPair _errs;
0480 
0481     /// @}
0482 
0483   };
0484 
0485 
0486 
0487   /// @name Comparison operators
0488   /// @{
0489 
0490   /// Equality test
0491   template <size_t N>
0492   inline bool operator==(const PointBase<N>& a, const PointBase<N>& b) {
0493     // Compare valitions
0494     for (size_t i = 0; i < N; ++i) {
0495       if ( !fuzzyEquals(a.vals()[i],        b.vals()[i]) )        return false;
0496       if ( !fuzzyEquals(a.errs()[i].first,  b.errs()[i].first))  return false;
0497       if ( !fuzzyEquals(a.errs()[i].second, b.errs()[i].second)) return false;
0498     }
0499     return true;
0500   }
0501 
0502   /// Inequality test
0503   template <size_t N>
0504   inline bool operator!=(const PointBase<N>& a, const PointBase<N>& b) {
0505     return !(a == b);
0506   }
0507 
0508 
0509   /// Less-than operator used to sort points
0510   template <size_t N>
0511   inline bool operator<(const PointBase<N>& a, const PointBase<N>& b) {
0512     #define LT_IF_NOT_EQ(a,b) { if (!fuzzyEquals(a, b)) return a < b; }
0513     for (size_t i = 0; i < N; ++i) {
0514       LT_IF_NOT_EQ(a.vals()[i],        b.vals()[i]);
0515       LT_IF_NOT_EQ(a.errs()[i].first,  b.errs()[i].first);
0516       LT_IF_NOT_EQ(a.errs()[i].second, b.errs()[i].second);
0517     }
0518     #undef LT_IF_NOT_EQ
0519     return false;
0520   }
0521 
0522   /// Less-than-or-equals operator used to sort points
0523   template <size_t N>
0524   inline bool operator<=(const PointBase<N>& a, const PointBase<N>& b) {
0525     if (a == b) return true;
0526     return a < b;
0527   }
0528 
0529   /// Greater-than operator used to sort points
0530   template <size_t N>
0531   inline bool operator>(const PointBase<N>& a, const PointBase<N>& b) {
0532     return !(a <= b);
0533   }
0534 
0535   /// Greater-than-or-equals operator used to sort points
0536   template <size_t N>
0537   inline bool operator>=(const PointBase<N>& a, const PointBase<N>& b) {
0538     return !(a < b);
0539   }
0540 
0541   /// @}
0542 
0543   template <size_t N>
0544   class PointND : public PointBase<N> {
0545     using BaseT = PointBase<N>;
0546     using BaseT::BaseT;
0547   };
0548 
0549 
0550 
0551   /// A 1D data point to be contained in a Scatter1D
0552   template <>
0553   class PointND<1> : public PointBase<1>,
0554                      public XDirectionMixin<PointND<1>> {
0555   public:
0556 
0557     using BaseT = PointBase<1>;
0558     using BaseT::BaseT;
0559 
0560     /// @name Constructors
0561     /// @{
0562 
0563     /// Constructor from values with optional symmetric errors
0564     PointND(double x, double ex=0.0)
0565       : BaseT({x}, {{ex, ex}}) {  }
0566 
0567 
0568     /// Constructor from values with explicit asymmetric errors
0569     PointND(double x, double exminus, double explus)
0570       : BaseT({x}, {{exminus, explus}}) {  }
0571 
0572 
0573     /// Constructor from values with asymmetric errors
0574     PointND(double x, const std::pair<double,double>& ex)
0575       : BaseT( {x}, {ex}) {  }
0576 
0577 
0578     /// Copy constructor
0579     PointND(const BaseT& other) : BaseT(other) {}
0580 
0581     /// Move constructor
0582     PointND(BaseT&& other) : BaseT(std::move(other)) {}
0583 
0584     /// @}
0585 
0586 
0587     /// @name Coordinate accessors
0588     /// @{
0589 
0590     /// Get the value along direction @a i
0591     double val(size_t i=0) const {
0592       if (i >= 1) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0593       return _val[i];
0594     }
0595 
0596     /// @}
0597 
0598   };
0599 
0600 
0601 
0602   /// A 2D data point to be contained in a Scatter2D
0603   template <>
0604   class PointND<2> : public PointBase<2>,
0605                      public XDirectionMixin<PointND<2>>,
0606                      public YDirectionMixin<PointND<2>> {
0607   public:
0608 
0609     using BaseT = PointBase<2>;
0610     using BaseT::BaseT;
0611 
0612     /// @name Constructors
0613     /// @{
0614 
0615     /// Constructor from values with optional symmetric errors
0616     PointND(double x, double y, double ex=0.0, double ey=0.0)
0617       : BaseT( {x,y}, {{ex,ex}, {ey,ey}}) {  }
0618 
0619 
0620     /// Constructor from values with explicit asymmetric errors
0621     PointND(double x, double y,
0622                  double exminus, double explus,
0623                  double eyminus, double eyplus)
0624       : BaseT({x,y}, {{exminus,explus}, {eyminus,eyplus}}) {  }
0625 
0626 
0627     /// Constructor from values with asymmetric errors on both x and y
0628     PointND(double x, double y, const std::pair<double,double>& ex, const std::pair<double,double>& ey)
0629       : BaseT({x,y}, {ex, ey}) {  }
0630 
0631 
0632     /// Copy constructor
0633     PointND(const BaseT& other) : BaseT(other) { }
0634 
0635     /// Move constructor
0636     PointND(BaseT&& other) : BaseT(std::move(other)) { }
0637 
0638     /// @}
0639 
0640 
0641     // @name Manipulations
0642     /// @{
0643 
0644     /// Scaling of both axes
0645     void scaleXY(double scalex, double scaley) {
0646       scaleX(scalex);
0647       scaleY(scaley);
0648     }
0649 
0650     /// @}
0651 
0652   };
0653 
0654 
0655 
0656   /// A 3D data point to be contained in a Scatter3D
0657   template <>
0658   class PointND<3> : public PointBase<3>,
0659                      public XDirectionMixin<PointND<3>>,
0660                      public YDirectionMixin<PointND<3>>,
0661                      public ZDirectionMixin<PointND<3>> {
0662   public:
0663 
0664     using BaseT = PointBase<3>;
0665     using BaseT::BaseT;
0666 
0667     /// @name Constructors
0668     /// @{
0669 
0670     /// Constructor from values with optional symmetric errors
0671     PointND(double x, double y, double z, double ex=0.0, double ey=0.0, double ez=0.0)
0672       : BaseT({x,y,z}, {{ex,ex}, {ey,ey}, {ez,ez}}) { }
0673 
0674 
0675     /// Constructor from values with explicit asymmetric errors
0676     PointND(double x, double y, double z,
0677                  double exminus, double explus,
0678                  double eyminus, double eyplus,
0679                  double ezminus, double ezplus)
0680       : BaseT({x,y,z}, {{exminus,explus}, {eyminus,eyplus}, {ezminus,ezplus}}) { }
0681 
0682     /// Constructor from asymmetric errors given as vectors
0683     PointND(double x, double y, double z,
0684                  const std::pair<double,double>& ex,
0685                  const std::pair<double,double>& ey,
0686                  const std::pair<double,double>& ez)
0687       : BaseT({x,y,z}, {ex,ey,ez}) {  }
0688 
0689 
0690     /// Copy constructor
0691     PointND(const BaseT& other) : BaseT(other) { }
0692 
0693     /// Move constructor
0694     PointND(BaseT&& other) : BaseT(std::move(other)) { }
0695 
0696     /// @}
0697 
0698     // @name Manipulations
0699     /// @{
0700 
0701     /// Scaling of both axes
0702     void scaleXYZ(double scalex, double scaley, double scalez) {
0703       scaleX(scalex);
0704       scaleY(scaley);
0705       scaleZ(scalez);
0706     }
0707 
0708     /// @}
0709 
0710   };
0711 
0712 
0713   /// @brief User-familiar alias
0714   using Point1D = PointND<1>;
0715   using Point2D = PointND<2>;
0716   using Point3D = PointND<3>;
0717   using Point4D = PointND<4>;
0718 
0719 }
0720 
0721 #endif