File indexing completed on 2026-09-11 09:31:34
0001
0002
0003
0004
0005
0006 #ifndef YODA_BINNEDAXIS_H
0007 #define YODA_BINNEDAXIS_H
0008
0009 #include "YODA/Utils/BinEstimators.h"
0010 #include "YODA/Utils/BinningUtils.h"
0011 #include "YODA/Utils/MathUtils.h"
0012 #include "YODA/Utils/MetaUtils.h"
0013 #include <cmath>
0014 #include <cstdlib>
0015 #include <limits>
0016 #include <memory>
0017 #include <algorithm>
0018 #include <set>
0019 #include <string>
0020 #include <stdexcept>
0021 #include <type_traits>
0022 #include <vector>
0023 #include <iostream>
0024 #include <iomanip>
0025
0026 namespace YODA {
0027
0028
0029 const size_t SEARCH_SIZElc = 16;
0030 const size_t BISECT_LINEAR_THRESHOLDlc = 32;
0031
0032 namespace YODAConcepts {
0033
0034 using MetaUtils::conjunction;
0035
0036
0037 template <typename T>
0038 struct AxisImpl {
0039
0040
0041
0042
0043
0044
0045
0046
0047
0048
0049
0050 using index_sig = size_t (T::*)(const typename T::EdgeT&) const;
0051 using edge_sig = typename T::EdgeT (T::*)(const size_t) const;
0052 using edges_sig = const std::vector<std::reference_wrapper<const typename T::EdgeT>> (T::*)() const noexcept;
0053 using size_sig = size_t (T::*)() const noexcept;
0054 using same_edges_sig = bool (T::*)(const T&) const noexcept;
0055 using shared_edges_sig = std::vector<typename T::EdgeT> (T::*)(const T&) const noexcept;
0056
0057 using checkResult = conjunction<
0058 std::is_same<index_sig, decltype(&T::index)>,
0059 std::is_same<edge_sig, decltype(&T::edge)>,
0060
0061 std::is_same<size_sig, decltype(&T::size)>,
0062 std::is_same<same_edges_sig, decltype(&T::hasSameEdges)>,
0063 std::is_same<shared_edges_sig, decltype(&T::sharedEdges)>
0064 >;
0065 };
0066 }
0067
0068
0069
0070 namespace {
0071
0072 template <typename EdgeT>
0073 using isCAxis = std::enable_if_t<std::is_floating_point<EdgeT>::value>;
0074
0075
0076 template <typename T>
0077 struct hasWidth : std::false_type {};
0078
0079 template <>
0080 struct hasWidth<std::string> : std::true_type {};
0081 }
0082
0083
0084 template <typename T, typename>
0085 class Axis;
0086
0087
0088
0089
0090
0091 template <typename T, typename = void>
0092 class Axis {
0093 public:
0094 using EdgeT = T;
0095 using ContainerT = std::vector<T>;
0096 using const_iterator = typename ContainerT::const_iterator;
0097
0098
0099
0100
0101
0102 Axis() { }
0103
0104
0105
0106
0107 Axis(const std::vector<T>& edges);
0108
0109
0110
0111
0112 Axis(std::vector<T>&& edges);
0113
0114
0115
0116
0117 Axis(std::initializer_list<T>&& edges);
0118
0119
0120 Axis(Axis<T>&& other) : _edges(std::move(other._edges)) {}
0121
0122
0123 Axis(const Axis<T>& other) : _edges(other._edges) {}
0124
0125 Axis& operator=(const Axis& other) {
0126 if (this != &other) _edges = other._edges;
0127 return *this;
0128 }
0129
0130 Axis& operator=(Axis&& other) {
0131 if (this != &other) _edges = std::move(other._edges);
0132 return *this;
0133 }
0134
0135
0136
0137
0138
0139
0140 void _renderYODA(std::ostream& os) const noexcept {
0141 os << "[";
0142 for (size_t i = 0; i < _edges.size(); ++i) {
0143 if (i) os << ", ";
0144 if constexpr(std::is_same<T, std::string>::value) {
0145 os << std::quoted(_edges[i]);
0146 }
0147 else {
0148 os << _edges[i];
0149 }
0150 }
0151 os << "]";
0152 }
0153
0154
0155 std::string type() const noexcept { return TypeID<EdgeT>::name(); }
0156
0157 int maxEdgeWidth() const noexcept {
0158 int maxwidth = 0;
0159 if constexpr (hasWidth<EdgeT>::value) {
0160 auto it = std::max_element(_edges.begin(), _edges.end(),
0161 [](const auto& a, const auto& b) {
0162 return a.size() < b.size();
0163 });
0164 maxwidth = (*it).size();
0165 }
0166 return maxwidth;
0167 }
0168
0169
0170
0171
0172
0173
0174 size_t index(const T& x) const;
0175
0176
0177 EdgeT edge(const size_t i) const;
0178
0179
0180 std::vector<EdgeT> edges() const noexcept;
0181
0182
0183 const_iterator begin() const { return _edges.cbegin(); }
0184
0185
0186 const_iterator end() const { return _edges.cend(); }
0187
0188
0189 size_t size() const noexcept;
0190
0191
0192 size_t numBins(const bool includeOverflows=false) const noexcept;
0193
0194
0195 bool hasSameEdges(const Axis<T>& other) const noexcept;
0196
0197
0198
0199
0200 std::vector<T> sharedEdges(const Axis<T>& other) const noexcept;
0201
0202
0203
0204 bool isSubsetEdges(const Axis<T>& other) const noexcept;
0205
0206 protected:
0207
0208
0209
0210
0211
0212 void fillEdges(std::vector<EdgeT>&& edges) noexcept;
0213
0214
0215
0216
0217 std::vector<T> _edges;
0218
0219 };
0220
0221
0222
0223 template <typename T, typename U>
0224 Axis<T, U>::Axis(const std::vector<T>& edges) : Axis(std::vector<T>(edges)) {
0225
0226
0227 static_assert(MetaUtils::checkConcept<Axis<EdgeT>, YODAConcepts::AxisImpl>(),
0228 "Axis<T> should implement Axis concept.");
0229 }
0230
0231 template <typename T, typename U>
0232 Axis<T, U>::Axis(std::vector<T>&& edges) {
0233 fillEdges(std::move(edges));
0234 }
0235
0236 template <typename T, typename U>
0237 Axis<T, U>::Axis(std::initializer_list<T>&& edges) {
0238 fillEdges(std::vector<T>{edges});
0239 }
0240
0241 template <typename T, typename U>
0242 size_t Axis<T, U>::index(const T& x) const {
0243 auto it = std::find(this->_edges.begin(), this->_edges.end(), x);
0244 if (it == this->_edges.end()) return 0;
0245 return it - this->_edges.begin() + 1;
0246 }
0247
0248 template <typename T, typename U>
0249 typename Axis<T, U>::EdgeT Axis<T, U>::edge(const size_t i) const {
0250 if (this->_edges.empty()) {
0251 throw RangeError("Axis has no edges!");
0252 }
0253 if (!i || i > this->_edges.size()) {
0254 throw RangeError("Invalid index, must be in range 1.." + std::to_string(this->_edges.size()));
0255 }
0256 return this->_edges.at(i-1);
0257 }
0258
0259 template <typename T, typename U>
0260 std::vector<typename Axis<T, U>::EdgeT> Axis<T, U>::edges() const noexcept {
0261 return this->_edges;
0262 }
0263
0264
0265 template <typename T, typename U>
0266 size_t Axis<T, U>::size() const noexcept {
0267 return numBins(true);
0268 }
0269
0270
0271 template <typename T, typename U>
0272 size_t Axis<T, U>::numBins(const bool includeOverflows) const noexcept {
0273 return _edges.size() + (includeOverflows? 1 : 0);
0274 }
0275
0276 template <typename T, typename U>
0277 bool Axis<T, U>::hasSameEdges(const Axis<T>& other) const noexcept {
0278 return _edges.size() == other._edges.size() &&
0279 std::equal(_edges.begin(), _edges.end(), other._edges.begin());
0280 }
0281
0282 template <typename T, typename U>
0283 std::vector<T> Axis<T, U>::sharedEdges(const Axis<T>& other) const noexcept {
0284 std::vector<EdgeT> res;
0285 const auto& otherBegin = other._edges.begin();
0286 const auto& otherEnd = other._edges.end();
0287 for (const T& edge : this->_edges) {
0288 if (std::find(otherBegin, otherEnd, edge) != otherEnd)
0289 res.emplace_back(std::move(edge));
0290 }
0291 return res;
0292 }
0293
0294 template <typename T, typename U>
0295 bool Axis<T, U>::isSubsetEdges(const Axis<T>& other) const noexcept {
0296 if (_edges.size() == other._edges.size()) return hasSameEdges(other);
0297
0298 std::vector<T> edgesLhs(edges());
0299 std::vector<T> edgesRhs(other.edges());
0300
0301 std::sort(edgesLhs.begin(), edgesLhs.end());
0302 std::sort(edgesRhs.begin(), edgesRhs.end());
0303
0304
0305 return std::includes(edgesLhs.begin(), edgesLhs.end(),
0306 edgesRhs.begin(), edgesRhs.end());
0307 }
0308
0309 template <typename T, typename U>
0310 void Axis<T, U>::fillEdges(std::vector<EdgeT>&& edges) noexcept {
0311 for (auto& edge : edges) {
0312 if (std::find(this->_edges.begin(),
0313 this->_edges.end(), edge) == this->_edges.end())
0314 _edges.emplace_back(std::move(edge));
0315 }
0316 }
0317
0318
0319
0320
0321
0322
0323
0324
0325
0326 template <typename T>
0327 class Axis<T, isCAxis<T>> {
0328 public:
0329 using EdgeT = T;
0330 using ContainerT = std::vector<T>;
0331 using const_iterator = typename ContainerT::const_iterator;
0332 using CAxisT = isCAxis<T>;
0333
0334
0335 Axis() {
0336 _updateEdges({});
0337 _setEstimator();
0338 }
0339
0340 Axis(const Axis<T, CAxisT>& other);
0341
0342 Axis(Axis<T, CAxisT>&& other)
0343 : _est(other._est),
0344 _maskedBins(std::move(other._maskedBins)),
0345 _edges(std::move(other._edges)) {}
0346
0347
0348
0349
0350 Axis(std::vector<EdgeT> edges);
0351
0352
0353
0354
0355 Axis(std::initializer_list<T>&& edges);
0356
0357
0358
0359
0360 Axis(std::vector<std::pair<EdgeT, EdgeT>> binsEdges);
0361
0362 Axis(size_t nBins, EdgeT lower, EdgeT upper);
0363
0364 Axis& operator=(const Axis& other) {
0365 if (this != &other) {
0366 _est = other._est;
0367 _maskedBins = other._maskedBins;
0368 _edges = other._edges;
0369 }
0370 return *this;
0371 }
0372
0373 Axis& operator=(Axis&& other) {
0374 if (this != &other) {
0375 _est = std::move(other._est);
0376 _maskedBins = std::move(other._maskedBins);
0377 _edges = std::move(other._edges);
0378 }
0379 return *this;
0380 }
0381
0382
0383
0384
0385
0386
0387
0388
0389
0390
0391
0392
0393
0394
0395
0396
0397
0398
0399
0400
0401
0402
0403
0404
0405
0406
0407
0408
0409
0410
0411 size_t index(const EdgeT& x) const;
0412
0413
0414 size_t size() const noexcept;
0415
0416
0417 size_t numBins(const bool includeOverflows=false) const noexcept;
0418
0419
0420 EdgeT edge(const size_t i) const;
0421
0422
0423 std::vector<EdgeT> edges() const noexcept;
0424
0425
0426 const_iterator begin() const { return _edges.cbegin(); }
0427
0428
0429 const_iterator end() const { return _edges.cend(); }
0430
0431
0432 bool hasSameEdges(const Axis<EdgeT, CAxisT>& other) const noexcept;
0433
0434
0435
0436 std::vector<T> sharedEdges(const Axis<EdgeT, CAxisT>& other) const noexcept;
0437
0438
0439
0440 bool isSubsetEdges(const Axis<EdgeT, CAxisT>& other) const noexcept;
0441
0442
0443 std::vector<size_t> maskedBins() const noexcept { return _maskedBins; }
0444
0445
0446
0447
0448
0449
0450
0451
0452
0453
0454
0455 void _renderYODA(std::ostream& os) const noexcept {
0456 os << "[";
0457 size_t nEdges = _edges.size() - 2;
0458 for (size_t i = 0; i < nEdges; ++i) {
0459 if (i) {
0460 os << ", ";
0461 }
0462 os << _edges[i+1];
0463 }
0464 os << "]";
0465 }
0466
0467
0468 std::string type() const noexcept { return TypeID<EdgeT>::name(); }
0469
0470
0471
0472
0473
0474
0475
0476
0477
0478 void mergeBins(std::pair<size_t, size_t>);
0479
0480
0481
0482
0483
0484
0485
0486 std::vector<T> widths(const bool includeOverflows=false) const noexcept {
0487
0488
0489 size_t offset = includeOverflows? 1 : 3;
0490 std::vector<T> ret(_edges.size()-offset);
0491 size_t start = 1 + !includeOverflows;
0492 size_t end = _edges.size() - !includeOverflows;
0493 for (size_t i = start; i < end; ++i) {
0494 ret[i-start] = _edges[i] - _edges[i-1];
0495 }
0496 return ret;
0497 }
0498
0499
0500 std::vector<T> maxs(const bool includeOverflows=false) const noexcept {
0501
0502
0503 size_t offset = includeOverflows? 1 : 3;
0504 std::vector<T> ret(_edges.size()-offset);
0505 size_t start = 1 + !includeOverflows;
0506 size_t end = _edges.size() - !includeOverflows;
0507 for (size_t i = start; i < end; ++i) {
0508 ret[i-start] = _edges[i];
0509 }
0510 return ret;
0511 }
0512
0513
0514 std::vector<T> mins(const bool includeOverflows=false) const noexcept {
0515
0516
0517 size_t offset = includeOverflows? 1 : 3;
0518 std::vector<T> ret(_edges.size()-offset);
0519 size_t start = 1 + !includeOverflows;
0520 size_t end = _edges.size() - !includeOverflows;
0521 for (size_t i = start; i < end; ++i) {
0522 ret[i-start] = _edges[i-1];
0523 }
0524 return ret;
0525 }
0526
0527
0528 std::vector<T> mids(const bool includeOverflows=false) const noexcept {
0529
0530
0531 size_t offset = includeOverflows? 1 : 3;
0532 std::vector<T> ret(_edges.size()-offset);
0533 size_t start = 1 + !includeOverflows;
0534 size_t end = _edges.size() - !includeOverflows;
0535 for (size_t i = start; i < end; ++i) {
0536 ret[i-start] = 0.5*(_edges[i-1] + _edges[i]);
0537 }
0538 return ret;
0539 }
0540
0541
0542 EdgeT width(size_t binNum) const noexcept {
0543 return _edges[binNum+1] - _edges[binNum];
0544 }
0545
0546
0547 EdgeT max(size_t binNum) const noexcept {
0548 return _edges[binNum+1];
0549 }
0550
0551
0552
0553 EdgeT min(size_t binNum) const noexcept {
0554 return _edges[binNum];
0555 }
0556
0557
0558 EdgeT mid(size_t binNum) const noexcept {
0559
0560 if(binNum == 0)
0561 return std::numeric_limits<EdgeT>::min();
0562 if (binNum == (numBins(true) - 1))
0563 return std::numeric_limits<EdgeT>::max();
0564
0565 EdgeT minVal = min(binNum);
0566 return (max(binNum) - minVal)/2 + minVal;
0567 }
0568
0569
0570 protected:
0571
0572
0573 void _updateEdges(std::vector<EdgeT>&& edges) noexcept;
0574
0575
0576
0577 void _setEstimator() noexcept;
0578
0579
0580
0581
0582 ssize_t _linsearch_forward(size_t istart, double x, size_t nmax) const noexcept;
0583
0584
0585
0586
0587 ssize_t _linsearch_backward(size_t istart, double x, size_t nmax) const noexcept;
0588
0589
0590 size_t _bisect(double x, size_t imin, size_t imax) const noexcept;
0591
0592
0593 std::shared_ptr<BinEstimator> _est;
0594
0595
0596 std::vector<size_t> _maskedBins;
0597
0598
0599 std::vector<T> _edges;
0600
0601 };
0602
0603 template <typename T>
0604 Axis<T, isCAxis<T>>::Axis(const Axis<T, CAxisT>& other) {
0605
0606
0607
0608
0609 static_assert(MetaUtils::checkConcept<Axis<EdgeT>, YODAConcepts::AxisImpl>(),
0610 "Axis<T> should implement Axis concept.");
0611
0612 _est = other._est;
0613 _edges = other._edges;
0614 _maskedBins = other._maskedBins;
0615 }
0616
0617 template <typename T>
0618 Axis<T, isCAxis<T>>::Axis(const size_t nBins, const EdgeT lower, const EdgeT upper) {
0619 if(upper <= lower)
0620 throw(std::logic_error("Upper bound should be larger than lower."));
0621 _edges.resize(nBins+1+2);
0622 double step = (upper - lower) / nBins;
0623
0624 _edges[0] = -std::numeric_limits<double>::infinity();
0625
0626 _edges[1] = lower;
0627
0628 for(size_t i = 2; i < _edges.size()-1; i++) {
0629 _edges[i] = _edges[i-1] + step;
0630 }
0631
0632 _edges[_edges.size()-1] = std::numeric_limits<double>::infinity();
0633
0634 _setEstimator();
0635 }
0636
0637 template <typename T>
0638 Axis<T, isCAxis<T>>::Axis(std::vector<std::pair<EdgeT, EdgeT>> binsEdges) {
0639 if (binsEdges.empty()) throw BinningError("Expected at least one pair of edges");
0640
0641 std::sort(binsEdges.begin(), binsEdges.end(), [](auto &left, auto &right) {
0642 return left.first < right.first;
0643 });
0644
0645 _edges.resize(binsEdges.size()*2+2);
0646
0647 _edges[0] = -std::numeric_limits<double>::infinity();
0648
0649
0650
0651
0652
0653
0654
0655
0656
0657
0658
0659 size_t i = 1;
0660 for (const auto& pair : binsEdges) {
0661 if (!fuzzyGtrEquals(pair.first, _edges[i-1])) throw BinningError("Bin edges shouldn't intersect");
0662 if (i == 1 && std::isinf(pair.first) && pair.first < 0) {
0663 _edges[i++] = pair.second;
0664 continue;
0665 }
0666 if (fuzzyEquals(pair.first, _edges[i-1])) {
0667 _edges[i++] = pair.second;
0668 continue;
0669 }
0670 if (i != 1 && pair.first > _edges[i-1]) {
0671
0672 _maskedBins.emplace_back(i-1);
0673 }
0674 _edges[i++] = pair.first;
0675 _edges[i++] = pair.second;
0676 }
0677
0678 _edges[i] = std::numeric_limits<double>::infinity();
0679
0680 _edges.resize(i+1);
0681
0682 _setEstimator();
0683 }
0684
0685 template <typename T>
0686 Axis<T, isCAxis<T>>::Axis(std::vector<EdgeT> edges) {
0687 std::sort(edges.begin(), edges.end());
0688 edges.erase( std::unique(edges.begin(), edges.end()), edges.end() );
0689
0690 _updateEdges(std::move(edges));
0691
0692 _setEstimator();
0693 }
0694
0695 template <typename T>
0696 Axis<T, isCAxis<T>>::Axis(std::initializer_list<T>&& edgeList) {
0697 std::vector<T> edges{edgeList};
0698 std::sort(edges.begin(), edges.end());
0699 edges.erase( std::unique(edges.begin(), edges.end()), edges.end() );
0700
0701 _updateEdges(std::move(edges));
0702
0703 _setEstimator();
0704 }
0705
0706 template <typename T>
0707 size_t Axis<T, isCAxis<T>>::index(const EdgeT& x) const {
0708 if (_edges.size() <= 2) throw BinningError("Axis initialised without specifying edges");
0709
0710 if (_edges.size() == 3) return x >= _edges[1];
0711
0712 if (std::isinf(x)) { return x < 0? 0 : _edges.size() - 2; }
0713
0714 size_t index = std::min(_est->estindex(x), _edges.size()-2);
0715
0716 if (x >= this->_edges[index] && x < this->_edges[index+1]) return index;
0717
0718
0719 if (x > this->_edges[index]) {
0720 const ssize_t newindex = _linsearch_forward(index, x, SEARCH_SIZElc);
0721 index = (newindex > 0) ? newindex : _bisect(x, index, this->_edges.size()-1);
0722 } else if (x < this->_edges[index]) {
0723 const ssize_t newindex = _linsearch_backward(index, x, SEARCH_SIZElc);
0724 index = (newindex > 0) ? newindex : _bisect(x, 0, index+1);
0725 }
0726
0727 assert(x >= this->_edges[index] && (x < this->_edges[index+1] || std::isinf(x)));
0728 return index;
0729 }
0730
0731 template <typename T>
0732 size_t Axis<T, isCAxis<T>>::size() const noexcept {
0733 return numBins(true);
0734 }
0735
0736 template <typename T>
0737 size_t Axis<T, isCAxis<T>>::numBins(const bool includeOverflows) const noexcept {
0738 if (_edges.size() < 3) return includeOverflows? 1 : 0;
0739 return this->_edges.size() - (includeOverflows? 1 : 3);
0740 }
0741
0742 template <typename T>
0743 typename Axis<T, isCAxis<T>>::EdgeT Axis<T, isCAxis<T>>::edge(const size_t i) const {
0744 return this->_edges.at(i);
0745 }
0746
0747 template <typename T>
0748 std::vector<typename Axis<T, isCAxis<T>>::EdgeT> Axis<T, isCAxis<T>>::edges() const noexcept {
0749 return this->_edges;
0750 }
0751
0752 template <typename T>
0753 bool Axis<T, isCAxis<T>>::hasSameEdges(const Axis<T, CAxisT>& other) const noexcept{
0754 if (this->numBins(true) != other.numBins(true)) return false;
0755 for (size_t i = 1; i < this->numBins(true) - 1; i++) {
0756
0757 if (!fuzzyEquals(edge(i), other.edge(i))) return false;
0758 }
0759 return true;
0760 }
0761
0762 template <typename T>
0763 std::vector<T> Axis<T, isCAxis<T>>::sharedEdges(const Axis<T, CAxisT>& other) const noexcept {
0764 std::vector<T> intersection;
0765
0766
0767 if (_edges.size() > 2 && other._edges.size() > 2) {
0768 std::set_intersection(std::next(_edges.begin()), std::prev(_edges.end()),
0769 std::next(other._edges.begin()), std::prev(other._edges.end()),
0770 std::back_inserter(intersection));
0771 }
0772
0773 std::vector<T> rtn;
0774 rtn.reserve(intersection.size()+2);
0775
0776 rtn.emplace_back(-std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
0777 rtn.insert(std::next(rtn.begin()),
0778 std::make_move_iterator(intersection.begin()),
0779 std::make_move_iterator(intersection.end()));
0780 rtn.emplace_back(std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
0781
0782 return rtn;
0783 }
0784
0785 template <typename T>
0786 bool Axis<T, isCAxis<T>>::isSubsetEdges(const Axis<T, CAxisT>& other) const noexcept {
0787 if (_edges.size() == other._edges.size()) return hasSameEdges(other);
0788
0789
0790 if (_edges.size() > 2 && other._edges.size() > 2) {
0791
0792 return std::includes(std::next(_edges.begin()), std::prev(_edges.end()),
0793 std::next(other._edges.begin()), std::prev(other._edges.end()));
0794 }
0795
0796
0797
0798 return true;
0799 }
0800
0801 template <typename T>
0802 void Axis<T, isCAxis<T>>::mergeBins(std::pair<size_t, size_t> mergeRange) {
0803 if (_edges.size() <= 2) throw BinningError("Axis initialised without specifying edges");
0804 if (mergeRange.second < mergeRange.first) throw RangeError("Upper index comes before lower index");
0805 if (mergeRange.second >= numBins(true)) throw RangeError("Upper index exceeds last visible bin");
0806 _edges.erase(_edges.begin()+mergeRange.first+1, _edges.begin() + mergeRange.second + 1);
0807
0808
0809 std::vector<size_t> toRemove;
0810 size_t mrange = mergeRange.second - mergeRange.first;
0811 for (size_t i = 0; i < _maskedBins.size(); ++i) {
0812 if (_maskedBins[i] > mergeRange.second) _maskedBins[i] -= mrange;
0813 else if (_maskedBins[i] >= mergeRange.first) toRemove.push_back(_maskedBins[i]);
0814 }
0815 if (toRemove.size()) {
0816 _maskedBins.erase(
0817 std::remove_if(_maskedBins.begin(), _maskedBins.end(), [&](const auto& idx) {
0818 return std::find(toRemove.begin(), toRemove.end(), idx) != toRemove.end();
0819 }), _maskedBins.end());
0820 }
0821 }
0822
0823
0824 template <typename T>
0825 void Axis<T, isCAxis<T>>::_updateEdges(std::vector<EdgeT>&& edges) noexcept {
0826
0827 _edges.clear();
0828
0829
0830 _edges.emplace_back(-std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
0831 _edges.insert(std::next(_edges.begin()),
0832 std::make_move_iterator(edges.begin()),
0833 std::make_move_iterator(edges.end()));
0834 _edges.emplace_back(std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
0835 }
0836
0837 template <typename T>
0838 void Axis<T, isCAxis<T>>::_setEstimator() noexcept {
0839
0840
0841 if (_edges.size() <= 2) {
0842 _est = std::make_shared<LinBinEstimator>(0, 0, 1);
0843 return;
0844 }
0845
0846
0847 int front = 1, back = (int)_edges.size()-2;
0848 if (_edges[front] <= 0.0) {
0849 _est = std::make_shared<LinBinEstimator>(back - front, _edges[front], _edges[back]);
0850 }
0851 else {
0852 LinBinEstimator linEst(back - front, _edges[front], _edges[back]);
0853 LogBinEstimator logEst(back - front, _edges[front], _edges[back]);
0854
0855
0856 double logsum = 0, linsum = 0;
0857 for (int i = front; i <= back; ++i) {
0858 logsum += std::abs(logEst(_edges[i]) - double(i-1));
0859 linsum += std::abs(linEst(_edges[i]) - double(i-1));
0860 }
0861 const double log_avg = logsum / _edges.size();
0862 const double lin_avg = linsum / _edges.size();
0863
0864
0865
0866
0867 if (log_avg < lin_avg) {
0868 _est = std::make_shared<LogBinEstimator>(logEst);
0869 }
0870 else {
0871 _est = std::make_shared<LinBinEstimator>(linEst);
0872 }
0873 }
0874 }
0875
0876
0877 template <typename T>
0878 ssize_t Axis<T, isCAxis<T>>::_linsearch_forward(size_t istart, double x, size_t nmax) const noexcept {
0879 assert(x >= this->_edges[istart]);
0880 for (size_t i = 0; i < nmax; i++) {
0881 const size_t j = istart + i + 1;
0882 if (j > this->_edges.size()-1) return -1;
0883 if (x < this->_edges[j]) {
0884 assert(x >= this->_edges[j-1] && (x < this->_edges[j] || std::isinf(x)));
0885 return j-1;
0886 }
0887 }
0888 return -1;
0889 }
0890
0891 template <typename T>
0892 ssize_t Axis<T, isCAxis<T>>::_linsearch_backward(size_t istart, double x, size_t nmax) const noexcept {
0893 assert(x < this->_edges[istart]);
0894 for (size_t i = 0; i < nmax; i++) {
0895 const int j = istart - i - 1;
0896 if (j < 0) return -1;
0897 if (x >= this->_edges[j]) {
0898 assert(x >= this->_edges[j] && (x < this->_edges[j+1] || std::isinf(x)));
0899 return (ssize_t) j;
0900 }
0901 }
0902 return -1;
0903 }
0904
0905 template <typename T>
0906 size_t Axis<T, isCAxis<T>>::_bisect(double x, size_t imin, size_t imax) const noexcept {
0907 size_t len = imax - imin;
0908 while (len >= BISECT_LINEAR_THRESHOLDlc) {
0909 const size_t half = len >> 1;
0910 const size_t imid = imin + half;
0911 if (x >= this->_edges[imid]) {
0912 if (x < this->_edges[imid+1]) return imid;
0913 imin = imid;
0914 } else {
0915 imax = imid;
0916 }
0917 len = imax - imin;
0918 }
0919 assert(x >= this->_edges[imin] && (x < this->_edges[imax] || std::isinf(x)));
0920 return _linsearch_forward(imin, x, BISECT_LINEAR_THRESHOLDlc);
0921 }
0922
0923 }
0924
0925 #endif