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