Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-07-14 08:51:53

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