Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-11-01 08:59:35

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_Scatter_h
0007 #define YODA_Scatter_h
0008 
0009 #include "YODA/AnalysisObject.h"
0010 #include "YODA/Point.h"
0011 #include "YODA/Transformation.h"
0012 #include "YODA/Utils/Traits.h"
0013 #include "YODA/Utils/ndarray.h"
0014 #include <vector>
0015 #include <set>
0016 #include <string>
0017 #include <utility>
0018 #include <memory>
0019 
0020 namespace YODA {
0021 
0022   /// Limit visibility
0023   namespace {
0024 
0025     // Checks that the first half of tuple arguments
0026     // can be cast to double and the second half
0027     // to pair<double,double>
0028     template<class T, class U, typename = void>
0029     struct HalfValsHalfPairs : std::false_type { };
0030     //
0031     template<class T, size_t... Is>
0032     struct HalfValsHalfPairs<T, std::index_sequence<Is...>,
0033                    std::enable_if_t<( std::is_same<double,
0034                                            std::common_type_t<std::tuple_element_t<Is,T>...,
0035                                            double>>::value && // first half double
0036                                       std::is_same<std::pair<double,double>,
0037                                            std::common_type_t<std::tuple_element_t<sizeof...(Is)+Is,T>...,
0038                                            std::pair<double,double>>>::value // second half pair<double,double>
0039                                     )>> : std::true_type { };
0040   }
0041 
0042   /// A base class for common operations on scatter types (Scatter1D, etc.)
0043   class Scatter {
0044   public:
0045 
0046     /// @todo Add a generic Scatter base class, providing reset(), rmPoint(), etc.
0047 
0048     /// Virtual destructor for inheritance
0049     virtual ~Scatter() {}
0050 
0051     //@}
0052 
0053 
0054     /// Dimension of this data object
0055     virtual size_t dim() const noexcept = 0;
0056 
0057 
0058     /// @name Modifiers
0059     //@{
0060 
0061     /// Clear all points
0062     virtual void reset() = 0;
0063 
0064     /// Scaling along direction @a i
0065     //virtual void scale(size_t i, double scale) = 0;
0066 
0067     //@}
0068 
0069 
0070     ///////////////////////////////////////////////////
0071 
0072 
0073     /// Number of points in the scatter
0074     virtual size_t numPoints() const = 0;
0075 
0076 
0077     /// @name Point removers
0078     /// @{
0079 
0080     /// Remove the point with index @a index
0081     virtual void rmPoint(size_t index) = 0;
0082 
0083     /// Safely remove the points with indices @a indices
0084     virtual void rmPoints(std::vector<size_t> indices) {
0085       // remove points in decreasing order, so the numbering isn't invalidated mid-loop:
0086       std::sort(indices.begin(), indices.end(), std::greater<size_t>()); //< reverse-sort
0087       for (size_t i : indices) rmPoint(i);
0088     }
0089 
0090     /// @}
0091 
0092 
0093     /// @name Point inserters
0094     ///
0095     /// These don't currently make sense for a generic base class, but passing
0096     /// generic tuples of vals/errs would be quite nice.
0097     ///
0098     /// @{
0099 
0100     // /// Insert a new point, defined as the x value and no errors
0101     // void addPoint(double x) {
0102     //   Point1D thisPoint=Point1D(x);
0103     //   thisPoint.setParentAO(this);
0104     //   _points.insert(thisPoint);
0105     // }
0106 
0107     // /// Insert a new point, defined as the x value and symmetric errors
0108     // void addPoint(double x, double ex) {
0109     //   Point1D thisPoint=Point1D(x, ex);
0110     //   thisPoint.setParentAO(this);
0111     //   _points.insert(thisPoint);
0112     // }
0113 
0114     // /// Insert a new point, defined as the x value and an asymmetric error pair
0115     // void addPoint(double x, const std::pair<double,double>& ex) {
0116     //   Point1D thisPoint=Point1D(x, ex);
0117     //   thisPoint.setParentAO(this);
0118     //   _points.insert(thisPoint);
0119     // }
0120 
0121     // /// Insert a new point, defined as the x value and explicit asymmetric errors
0122     // void addPoint(double x, double exminus, double explus) {
0123     //   Point1D thisPoint=Point1D(x, exminus, explus);
0124     //   thisPoint.setParentAO(this);
0125     //   _points.insert(thisPoint);
0126     // }
0127 
0128     // /// Insert a collection of new points
0129     // void addPoints(const Points& pts) {
0130     //   for (const Point1D& pt : pts) addPoint(pt);
0131     // }
0132 
0133     //@}
0134 
0135 
0136     // /// Equality operator
0137     // bool operator == (const Scatter1D& other) {
0138     //   return _points == other._points;
0139     // }
0140 
0141     // /// Non-equality operator
0142     // bool operator != (const Scatter1D& other) {
0143     //   return ! operator == (other);
0144     // }
0145 
0146   };
0147 
0148 
0149 
0150 
0151   /// A generic data type which is just a collection of n-dim data points with errors
0152   template <size_t N>
0153   class ScatterND : public AnalysisObject, public Scatter {
0154   protected:
0155 
0156      // Convenient aliases
0157      using Pair = std::pair<double,double>;
0158      using ValVec = std::vector<double>;
0159      using PairVec = std::vector<Pair>;
0160      using ValList = std::initializer_list<double>;
0161      using PairList = std::initializer_list<Pair>;
0162 
0163      // extract the content type of an array Arr
0164      template<typename Arr>
0165      using containedType = std::decay_t<decltype(*std::declval<Arr>().begin())>;
0166 
0167      // check if content type of an array Arr is Pair
0168      template<typename Arr>
0169      using containsPair = typename std::is_same<containedType<Arr>, Pair>;
0170 
0171      // succeeds if the first argument is vector<vector<double> (or similar iterable)
0172      // and the second argument is vector<vector<Pair>> (or similar iterable)
0173      template<typename T, typename U>
0174      using enableIfNestedArrayWithPair = std::enable_if_t<(isIterable<T,U,containedType<T>,containedType<U>> &&
0175                                                            containsPair<containedType<U>>::value)>;
0176 
0177      // check if every element in parameter pack can be cast to double
0178      template<typename... Args>
0179      using isAllVals = std::is_same<double, std::common_type_t<Args..., double>>;
0180 
0181      // check if every element in first half of parameter pack can be
0182      // cast to double and every element in the second half to Pair
0183      // (based on helper struct defined at the top of the file)
0184      template<typename... Args>
0185      using isHalfValsHalfPairs = HalfValsHalfPairs<std::tuple<Args...>, std::make_index_sequence<N>>;
0186 
0187   public:
0188 
0189     // Typedefs
0190     using NdVal = Utils::ndarray<double, N>;
0191     using NdValPair = Utils::ndarray<std::pair<double,double>, N>;
0192     using Point = PointND<N>;
0193     using Points = std::vector<Point>;
0194     using Ptr = std::shared_ptr<ScatterND>;
0195     using AnalysisObject::operator =;
0196 
0197 
0198     /// @name Constructors
0199     /// @{
0200 
0201     /// Empty constructor
0202     ScatterND(const std::string& path = "", const std::string& title="")
0203       : AnalysisObject("Scatter"+std::to_string(N)+"D", path, title) {  }
0204 
0205     /// Constructor from a set of points
0206     ScatterND(const Points& points,
0207               const std::string& path="", const std::string& title="")
0208       : AnalysisObject("Scatter"+std::to_string(N)+"D", path, title),
0209         _points(points) {  }
0210 
0211     /// Constructor from a set of rvalue points
0212     ScatterND(Points&& points,
0213               const std::string& path="", const std::string& title="")
0214       : AnalysisObject("Scatter"+std::to_string(N)+"D", path, title),
0215         _points(std::move(points)) {  }
0216 
0217     /// Constructor from a vector of position values with no errors
0218     template <typename ValRange = std::initializer_list<ValList>,
0219               typename = std::enable_if_t<isIterable<ValRange, containedType<ValRange>>>> // enable if is nested array
0220     ScatterND(ValRange&& positions, const std::string& path="", const std::string& title="")
0221       : AnalysisObject("Scatter"+std::to_string(N)+"D", path, title) {
0222       for (size_t i = 0; i < positions.size(); ++i) {
0223         addPoint(PointND<N>(std::forward<containedType<ValRange>>(positions[i])));
0224       }
0225     }
0226 
0227     /// Constructor from vectors of values for positions and a single set of symmetric errors
0228     template <typename ValRange = std::initializer_list<ValList>,
0229               typename = std::enable_if_t<isIterable<ValRange, containedType<ValRange>>>> // enable if is nested array
0230     ScatterND(ValRange&& positions, ValRange&& errors, const std::string& path="", const std::string& title="")
0231       : AnalysisObject("Scatter"+std::to_string(N)+"D", path, title) {
0232       if (positions.size() != errors.size()) throw RangeError("Number of errors doesn't match number of positions");
0233       for (size_t i = 0; i < positions.size(); ++i) {
0234         addPoint(PointND<N>(std::forward<containedType<ValRange>>(positions[i]),
0235                             std::forward<containedType<ValRange>>(errors[i])));
0236       }
0237     }
0238 
0239     /// Constructor from vectors of values for positions and a single set of symmetric errors
0240     template <typename ValRange = std::initializer_list<ValList>,
0241               typename PairRange = std::initializer_list<PairList>,
0242               typename = enableIfNestedArrayWithPair<ValRange,PairRange>>
0243     ScatterND(ValRange&& positions, PairRange&& errors, const std::string& path="", const std::string& title="")
0244       : AnalysisObject("Scatter"+std::to_string(N)+"D", path, title) {
0245       if (positions.size() != errors.size()) throw RangeError("Number of error pairs doesn't match number of positions");
0246       for (size_t i = 0; i < positions.size(); ++i) {
0247         addPoint(PointND<N>(std::forward<containedType<ValRange>>(positions[i]),
0248                             std::forward<containedType<PairRange>>(errors[i])));
0249       }
0250     }
0251 
0252     /// Copy constructor with optional new path
0253     ScatterND(const ScatterND<N>& s, const std::string& path = "")
0254       : AnalysisObject("Scatter"+std::to_string(N)+"D", (path != "")? path : s.path(), s, s.title()),
0255         _points(s._points)  { }
0256 
0257     /// Move constructor with optional new path
0258     ScatterND(ScatterND<N>&& s, const std::string& path = "")
0259       : AnalysisObject("Scatter"+std::to_string(N)+"D", (path != "")? path : s.path(), s, s.title()),
0260         _points(std::move(s._points))  {
0261     }
0262 
0263 
0264     /// Assignment operator
0265     ScatterND<N>& operator = (const ScatterND<N>& s) {
0266       if (this != &s) {
0267         AnalysisObject::operator = (s);
0268         _points = s._points;
0269       }
0270       return *this;
0271     }
0272 
0273     /// Move operator
0274     ScatterND<N>& operator = (ScatterND<N>&& s) {
0275       if (this != &s) {
0276         AnalysisObject::operator = (s);
0277         _points = std::move(s._points);
0278       }
0279       return *this;
0280     }
0281 
0282     /// Make a copy on the stack
0283     ScatterND<N> clone() const {
0284       return ScatterND<N>(*this);
0285     }
0286 
0287     /// Make a copy on the heap, via 'new'
0288     ScatterND<N>* newclone() const {
0289       return new ScatterND<N>(*this);
0290     }
0291 
0292     /// @}
0293 
0294     /// Dimension of this data object
0295     size_t dim() const noexcept { return N; }
0296 
0297     /// @name Modifiers
0298     /// @{
0299 
0300     /// Clear all points
0301     void reset() {
0302       _points.clear();
0303     }
0304 
0305     /// Scaling
0306     void scale(const NdVal& scales) {
0307       for (PointND<N>& p : _points) p.scale(scales);
0308     }
0309 
0310     void scale(const std::vector<double>& scales) {
0311       if (scales.size() != N) throw RangeError("Expected " + std::to_string(N) + " scale factors");
0312       for (PointND<N>& p : _points) p.scale(scales);
0313     }
0314 
0315     /// Scale value and error along direction @a i
0316     void scale(const size_t i, double factor) {
0317       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0318       for (PointND<N>& p : _points) p.scale(i, factor);
0319     }
0320 
0321     /// Scale value along direction @a i
0322     void scaleVal(const size_t i, double factor) {
0323       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0324       for (PointND<N>& p : _points) p.scaleVal(i, factor);
0325     }
0326 
0327     /// Scale error along direction @a i
0328     void scaleErr(const size_t i, double factor) {
0329       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0330       for (PointND<N>& p : _points) p.scaleErr(i, factor);
0331     }
0332 
0333     /// @}
0334 
0335 
0336     ///////////////////////////////////////////////////
0337 
0338 
0339     /// @name Point accessors
0340     /// @{
0341 
0342     /// Number of points in the scatter
0343     size_t numPoints() const {
0344       return _points.size();
0345     }
0346 
0347 
0348     /// Get the collection of points
0349     Points& points() {
0350       return _points;
0351     }
0352 
0353 
0354     /// Get the collection of points (const version)
0355     const Points& points() const {
0356       return _points;
0357     }
0358 
0359 
0360     /// Get a reference to the point with index @a index
0361     PointND<N>& point(size_t index) {
0362       return _points.at(index);
0363     }
0364 
0365 
0366     /// Get the point with index @a index (const version)
0367     const PointND<N>& point(size_t index) const {
0368       return _points.at(index);
0369     }
0370 
0371     /// @}
0372 
0373 
0374     /// @name Point inserters/removers
0375     /// @{
0376 
0377     /// Insert a new point
0378     ScatterND<N>& addPoint(const PointND<N>& pt) {
0379       _points.push_back(pt);
0380       return *this;
0381     }
0382 
0383     /// Insert a new rvalue point
0384     ScatterND<N>& addPoint(PointND<N>&& pt) {
0385       _points.push_back(std::move(pt));
0386       return *this;
0387     }
0388 
0389     /// Insert a new point from a position array (of N elements)
0390     template <typename ValRange = ValList,
0391               typename = std::enable_if_t<isIterable<ValRange>>>
0392     ScatterND<N>& addPoint(ValRange&& pos) {
0393       _points.push_back(PointND<N>(std::forward<ValRange>(pos)));
0394       return *this;
0395     }
0396 
0397     /// Insert a new point from position and symmetric error arrays (of N elements)
0398     template <typename ValRange = ValList,
0399               typename = std::enable_if_t<isIterable<ValRange>>>
0400     ScatterND<N>& addPoint(ValRange&& pos, ValRange&& err) {
0401       _points.push_back(PointND<N>(std::forward<ValRange>(pos), std::forward<ValRange>(err)));
0402       return *this;
0403     }
0404 
0405     /// Insert a new point from position and asymmetric error arrays (of N elements)
0406     template <typename ValRange = ValList,
0407               typename = std::enable_if_t<isIterable<ValRange>>>
0408     ScatterND<N>& addPoint(ValRange&& pos, ValRange&& errdn, ValRange&& errup) {
0409       _points.push_back(PointND<N>(std::forward<ValRange>(pos),
0410                                    std::forward<ValRange>(errdn),
0411                                    std::forward<ValRange>(errup)));
0412       return *this;
0413     }
0414 
0415     /// Insert a new point from a position and error-pair array
0416     template <typename ValRange = ValList, typename PairRange = PairList>
0417     auto addPoint(ValRange&& pos, PairRange&& err)
0418     -> std::enable_if_t<(isIterable<ValRange,PairRange> && containsPair<PairRange>::value), ScatterND<N>>& {
0419       _points.push_back(PointND<N>(pos, err));
0420       return *this;
0421     }
0422 
0423     /// Insert a new point from a parameter pack
0424     ///
0425     /// The deduction guide on the parameter pack asks
0426     /// that its length be 2N or 3N for dimension N.
0427     ///
0428     /// If accepted, the parameter pack is then forwarded as
0429     /// a tuple to the helper method addPoint_aux, which is
0430     /// overloaded to deal with the following three cases:
0431     ///
0432     /// Case 1: position and symmetric errors:
0433     ///         Scatter1D::addPoint(x, ex);
0434     ///         Scatter2D::addPoint(x,y, ex,ey);
0435     ///         ...
0436     /// Case 2: position and asymmetric errors:
0437     ///         Scatter1D::addPoint(x, exdn,exup);
0438     ///         Scatter2D::addPoint(x,y, exdn,eydn, exup,eyup);
0439     ///         ...
0440     /// Case 3: position and error pairs
0441     ///         Scatter1D::addPoint(x, make_pair(exdn,exup));
0442     ///         Scatter2D::addPoint(x,y, make_pair(exdn,eydn), make_pair(exup,eyup));
0443     ///         ...
0444     template<typename... Args>
0445     auto addPoint(Args&&... args)
0446     -> std::enable_if_t<(sizeof...(Args) == 2*N || sizeof...(Args) == 3*N), ScatterND<N>>& {
0447       return addPoint_aux(std::make_tuple(std::forward<Args>(args)...), std::make_index_sequence<N>{});
0448     }
0449 
0450   protected:
0451 
0452     /// Helper method to deal with parameter pack that
0453     /// has been forwarded as a tuple from addPoints.
0454     ///
0455     /// The deduction guide on the parameter pack asks
0456     /// that all arguments can be cast to double,
0457     /// thereby covering the following two cases:
0458     ///
0459     /// Case 1: position and symmetric errors:
0460     ///         Scatter1D::addPoint(x, ex);
0461     ///         Scatter2D::addPoint(x,y, ex,ey);
0462     ///         ...
0463     /// Case 2: position and asymmetric errors:
0464     ///         Scatter1D::addPoint(x, exdn,exup);
0465     ///         Scatter2D::addPoint(x,y, exdn,eydn, exup,eyup);
0466     ///         ...
0467     template<typename... Args, size_t... Is>
0468     auto addPoint_aux(std::tuple<Args...>&& t, std::index_sequence<Is...>)
0469     -> std::enable_if_t<(isAllVals<Args...>::value), ScatterND<N>>& {
0470       if constexpr(sizeof...(Args) == 2*N) { // Case 1: symmetric errors
0471         _points.push_back(
0472           PointND<N>( ValVec{  static_cast<double>(std::get<Is>(t))...},
0473                       PairVec{{static_cast<double>(std::get<N+Is>(t)),
0474                                static_cast<double>(std::get<N+Is>(t))}...} ));
0475       }
0476       else { // Case 2: asymmetric errors
0477         _points.push_back(PointND<N>( ValVec{  static_cast<double>(std::get<Is>(t))...},
0478                                       PairVec{{static_cast<double>(std::get<N+2*Is>(t)),
0479                                                static_cast<double>(std::get<N+2*Is+1>(t))}...} ));
0480       }
0481       return *this;
0482     }
0483 
0484 
0485     /// Helper method to deal with parameter pack that
0486     /// has been forwarded as a tuple from addPoints.
0487     ///
0488     /// The deduction guide on the parameter pack asks that
0489     /// its length be 2N for dimension N, that the first
0490     /// half of the arguments can be cast to double, and
0491     /// that the second half can be cast to Pair,
0492     /// thereby covering the following case:
0493     ///
0494     /// Case 3: position and error pairs
0495     ///         Scatter1D::addPoint(x, make_pair(exdn,exup));
0496     ///         Scatter2D::addPoint(x,y, make_pair(exdn,eydn), make_pair(exup,eyup));
0497     ///         ...
0498     template<typename... Args, size_t... Is>
0499     auto addPoint_aux(std::tuple<Args...>&& t, std::index_sequence<Is...>) // Case 3: error pairs
0500     -> std::enable_if_t<(sizeof...(Args) == 2*N && isHalfValsHalfPairs<Args...>::value), ScatterND<N>>& {
0501       _points.push_back(PointND<N>( ValVec{  static_cast<double>(std::get<Is>(t))...},
0502                                     PairVec{ static_cast<Pair>(std::get<N+Is>(t))...} ));
0503       return *this;
0504     }
0505 
0506 
0507   public:
0508 
0509     /// Insert a collection of new points
0510     ScatterND<N>& addPoints(Points pts) {
0511       for (const PointND<N>& pt : pts) addPoint(pt);
0512       return *this;
0513     }
0514 
0515     void rmPoint(size_t index) {
0516       _points.erase(_points.begin()+index);
0517     }
0518 
0519     /// @}
0520 
0521     /// @name MPI (de-)serialisation
0522     //@{
0523 
0524     size_t lengthContent(bool fixed_length = false) const noexcept {
0525       if (fixed_length) return 0;
0526       return numPoints() * Point::DataSize::value;
0527     }
0528 
0529     std::vector<double> serializeContent(bool fixed_length = false) const noexcept {
0530 
0531       if (fixed_length)  return { }; // cannot guarantee fixed length
0532 
0533       std::vector<double> rtn;
0534       rtn.reserve(lengthContent());
0535       for (size_t i = 0; i < numPoints(); ++i) {
0536         std::vector<double> pdata = point(i)._serializeContent();
0537         rtn.insert(std::end(rtn),
0538                    std::make_move_iterator(std::begin(pdata)),
0539                    std::make_move_iterator(std::end(pdata)));
0540       }
0541       return rtn;
0542     }
0543 
0544     void deserializeContent(const std::vector<double>& data) {
0545 
0546       if (data.size() % Point::DataSize::value)
0547         throw UserError("Length of serialized data should be a multiple of "+std::to_string(Point::DataSize::value)+"!");
0548 
0549       const size_t nPoints = data.size()/Point::DataSize::value;
0550       const auto itr = data.cbegin();
0551       reset();
0552       for (size_t i = 0; i < nPoints; ++i) {
0553         addPoint(Point());
0554         auto first = itr + i*Point::DataSize::value;
0555         auto last = first + Point::DataSize::value;
0556         point(i)._deserializeContent(std::vector<double>{first, last});
0557       }
0558 
0559     }
0560 
0561     // @}
0562 
0563     /// @name Combining sets of scatter points
0564     /// @{
0565 
0566     /// @todo Better name?
0567     ScatterND<N>& combineWith(const ScatterND<N>& other) {
0568       addPoints(other.points());
0569       return *this;
0570     }
0571 
0572     /// @todo Better name?
0573     ScatterND<N>& combineWith(ScatterND<N>&& other) {
0574       addPoints(std::move(other._points));
0575       return *this;
0576     }
0577 
0578     /// @todo Better name?
0579     ScatterND<N>& combineWith(const std::vector< ScatterND<N> >& others) {
0580       for (const ScatterND<N>& s : others) combineWith(s);
0581       return *this;
0582     }
0583 
0584     /// @todo Better name?
0585     ScatterND<N>& combineWith(std::vector< ScatterND<N> >&& others) {
0586       for (ScatterND<N>&& s : others) combineWith(std::move(s));
0587       return *this;
0588     }
0589 
0590     /// @}
0591 
0592     /// @name Coordinate accessors
0593     /// @{
0594 
0595     /// Get the coordinate vector along axis @a i
0596     ValVec vals(const size_t i) const {
0597       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0598       ValVec rtn;  rtn.reserve(_points.size());
0599       for (const auto& pt : _points) {
0600         rtn.push_back( pt.val(i) );
0601       }
0602       return rtn;
0603     }
0604 
0605     /// Get the lowest value vector along axis @a i
0606     ValVec mins(const size_t i) const {
0607       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0608       ValVec rtn;  rtn.reserve(_points.size());
0609       for (const auto& pt : _points) {
0610         rtn.push_back( pt.min(i) );
0611       }
0612       return rtn;
0613     }
0614 
0615     /// Get the positive error vector along axis @a i
0616     ValVec maxs(const size_t i) const {
0617       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0618       ValVec rtn;  rtn.reserve(_points.size());
0619       for (const auto& pt : _points) {
0620         rtn.push_back( pt.max(i) );
0621       }
0622       return rtn;
0623     }
0624 
0625     /// Get the smallest central value along axis @a i
0626     double min(const size_t i) const {
0627       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0628       const ValVec cvals = vals(i);
0629       return *std::min_element(cvals.begin(), cvals.end());
0630     }
0631 
0632     /// Get the largest central value along axis @a i
0633     double max(const size_t i) const {
0634       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0635       const ValVec cvals = vals(i);
0636       return *std::max_element(cvals.begin(), cvals.end());
0637     }
0638 
0639     /// Get the error pairs along axis @a i
0640     PairVec errs(const size_t i) const {
0641       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0642       PairVec rtn;  rtn.reserve(_points.size());
0643       for (const auto& pt : _points) {
0644         rtn.push_back( pt.errs(i) );
0645       }
0646       return rtn;
0647     }
0648 
0649     /// Get the average error along axis @a i
0650     ValVec errAvgs(const size_t i) const {
0651       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0652       ValVec rtn;  rtn.reserve(_points.size());
0653       for (const auto& pt : _points) {
0654         rtn.push_back( pt.errAvg(i) );
0655       }
0656       return rtn;
0657     }
0658 
0659     /// Axis-specific alias
0660     ValVec xVals() const { return vals(0); }
0661 
0662     /// Axis-specific alias
0663     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0664     ValVec yVals() const { return vals(1); }
0665 
0666     /// Axis-specific alias
0667     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0668     ValVec zVals() const { return vals(2); }
0669 
0670     /// Axis-specific alias
0671     ValVec xMins() const { return mins(0); }
0672 
0673     /// Axis-specific alias
0674     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0675     ValVec yMins() const { return mins(1); }
0676 
0677     /// Axis-specific alias
0678     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0679     ValVec zMins() const { return mins(2); }
0680 
0681     /// Axis-specific alias
0682     ValVec xMaxs() const { return maxs(0); }
0683 
0684     /// Axis-specific alias
0685     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0686     ValVec yMaxs() const { return maxs(1); }
0687 
0688     /// Axis-specific alias
0689     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0690     ValVec zMaxs() const { return maxs(2); }
0691 
0692     /// Axis-specific alias
0693     double xMin() const {  return min(0); }
0694 
0695     /// Axis-specific alias
0696     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0697     double yMin() const {  return min(1); }
0698 
0699     /// Axis-specific alias
0700     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0701     double zMin() const {  return min(2); }
0702 
0703     /// Axis-specific alias
0704     double xMax() const {  return max(0); }
0705 
0706     /// Axis-specific alias
0707     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0708     double yMax() const {  return max(1); }
0709 
0710     /// Axis-specific alias
0711     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0712     double zMax() const {  return max(2); }
0713 
0714     /// Axis-specific alias
0715     PairVec xErrs() const {  return errs(0); }
0716 
0717     /// Axis-specific alias
0718     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0719     PairVec yErrs() const {  return errs(1); }
0720 
0721     /// Axis-specific alias
0722     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0723     PairVec zErrs() const {  return errs(2); }
0724 
0725     /// Axis-specific alias
0726     ValVec xErrAvgs() const {  return errAvgs(0); }
0727 
0728     /// Axis-specific alias
0729     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 2)>>
0730     ValVec yErrAvgs() const {  return errAvgs(1); }
0731 
0732     /// Axis-specific alias
0733     template<size_t axisN = N, typename = std::enable_if_t<(axisN >= 3)>>
0734     ValVec zErrAvgs() const {  return errAvgs(2); }
0735 
0736     /// @}
0737 
0738     /// @name I/O
0739     /// @{
0740 
0741     // @brief Render information about this AO
0742     void _renderYODA(std::ostream& os, const int width = 13) const noexcept {
0743 
0744       os << "# ";
0745       for (size_t i = 0; i < N; ++i) {
0746         os << std::setw(width - int(i? 0 : 2)) << std::left << ("val" + std::to_string(i+1)) << "\t"
0747            << std::setw(width) << std::left << ("err" + std::to_string(i+1) + "-") << "\t"
0748            << std::setw(width) << std::left << ("err" + std::to_string(i+1) + "+") << "\t";
0749       }
0750       os << "\n";
0751 
0752       for (const auto& pt : _points) {
0753         pt._renderYODA(os, width);
0754       }
0755     }
0756 
0757     // @brief Render information about this AO
0758     void _renderFLAT(std::ostream& os, const int width = 13) const noexcept { _renderYODA(os, width); }
0759 
0760     /// @}
0761 
0762     /// @name Utilities
0763     /// @{
0764 
0765     std::vector<Pair> edges(const size_t i) const {
0766       if (i >= N) throw RangeError("Invalid axis int, must be in range 0..dim-1");
0767       std::vector<Pair> rtn;
0768       rtn.resize(numPoints());
0769       size_t j = 0;
0770       for (const Point& p : points()) {
0771         rtn[j++] = std::make_pair(p.min(i), p.max(i));
0772       }
0773       std::sort(rtn.begin(), rtn.end());
0774       rtn.erase(std::unique(rtn.begin(), rtn.end()), rtn.end());
0775       return rtn;
0776     }
0777 
0778     /// @}
0779 
0780   private:
0781 
0782     Points _points;
0783 
0784   };
0785 
0786 
0787   /// @name Combining scatters by merging sets of points
0788   /// @{
0789 
0790   template <int N>
0791   inline ScatterND<N> combine(ScatterND<N> a, const ScatterND<N>& b) {
0792     a.combineWith(b);
0793     return a;
0794   }
0795 
0796   template <int N>
0797   inline ScatterND<N> combine(ScatterND<N> a, ScatterND<N>&& b) {
0798     a.combineWith(std::move(b));
0799     return a;
0800   }
0801 
0802   template <int N>
0803   inline ScatterND<N> combine(const std::vector< ScatterND<N> >& scatters) {
0804     ScatterND<N> rtn;
0805     rtn.combineWith(scatters);
0806     return rtn;
0807   }
0808 
0809   template <int N>
0810   inline ScatterND<N> combine(std::vector< ScatterND<N> >&& scatters) {
0811     ScatterND<N> rtn;
0812     rtn.combineWith(std::move(scatters));
0813     return rtn;
0814   }
0815 
0816   /// @}
0817 
0818 
0819   //////////////////////////////////
0820 
0821 
0822   // /// @name Combining scatters: global operators, assuming aligned points
0823   // /// @{
0824 
0825   // /// Add two scatters
0826   // template <size_t N>
0827   // Scatter add(const Scatter& first, const Scatter& second);
0828 
0829 
0830   // /// Add two scatters
0831   // template <size_t N>
0832   // inline Scatter operator + (const Scatter& first, const Scatter& second) {
0833   //   return add(first, second);
0834   // }
0835 
0836 
0837   // /// Subtract two scatters
0838   // template <size_t N>
0839   // Scatter subtract(const Scatter& first, const Scatter& second);
0840 
0841 
0842   // /// Subtract two scatters
0843   // template <size_t N>
0844   // inline Scatter operator - (const Scatter& first, const Scatter& second) {
0845   //   return subtract(first, second);
0846   // }
0847 
0848 
0849   // /// Divide two scatters
0850   // template <size_t N>
0851   // Scatter divide(const Scatter& numer, const Scatter& denom);
0852 
0853 
0854   // /// Divide two scatters
0855   // template <size_t N>
0856   // inline Scatter operator / (const Scatter& numer, const Scatter& denom) {
0857   //   return divide(numer, denom);
0858   // }
0859 
0860   // /// @}
0861 
0862 
0863   //////////////////////////////////
0864 
0865 
0866   /// @name Generalised transformations
0867   /// @{
0868 
0869   template<size_t N>
0870   inline void transform(ScatterND<N>& s, const Trf<N>& fn, const size_t i) {
0871     for (auto& p : s.points()) {
0872       p.transform(i, fn);
0873     }
0874   }
0875 
0876   template<size_t N, typename FN>
0877   inline void transform(ScatterND<N>& s, const FN& fn, const size_t i) {
0878     transform(s, Trf<N>(fn), i);
0879   }
0880 
0881   template<size_t N, typename FN>
0882   inline void transformX(ScatterND<N>& s, const FN& fn) {
0883     transform(s, fn, 0);
0884   }
0885 
0886   template<size_t N, typename FN>
0887   inline void transformY(ScatterND<N>& s, const FN& fn) {
0888     transform(s, fn, 1);
0889   }
0890 
0891   template<size_t N, typename FN>
0892   inline void transformZ(ScatterND<N>& s, const FN& fn) {
0893     transform(s, fn, 2);
0894   }
0895 
0896   /// @}
0897 
0898   /// @name User friendly aliases
0899   /// @{
0900 
0901   using Scatter1D = ScatterND<1>;
0902   using Scatter2D = ScatterND<2>;
0903   using Scatter3D = ScatterND<3>;
0904   using Scatter4D = ScatterND<4>;
0905   using S1D = Scatter1D;
0906   using S2D = Scatter2D;
0907   using S3D = Scatter3D;
0908   using S4D = Scatter4D;
0909 
0910   /// @}
0911 
0912 
0913 }
0914 
0915 #endif