Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-04-19 09:13:41

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