yoda is hosted by Hepforge, IPPP Durham
YODA - Yet more Objects for Data Analysis 2.0.3
BinnedAxis.h
Go to the documentation of this file.
1// -*- C++ -*-
2//
3// This file is part of YODA -- Yet more Objects for Data Analysis
4// Copyright (C) 2008-2024 The YODA collaboration (see AUTHORS for details)
5//
6#ifndef YODA_BINNEDAXIS_H
7#define YODA_BINNEDAXIS_H
8
13#include <cmath>
14#include <cstdlib>
15#include <limits>
16#include <memory>
17#include <algorithm>
18#include <set>
19#include <string>
20#include <stdexcept>
21#include <type_traits>
22#include <vector>
23#include <iostream>
24#include <iomanip>
25
26namespace YODA {
27
28 const size_t SEARCH_SIZElc = 16;
29 const size_t BISECT_LINEAR_THRESHOLDlc = 32;
30
31 namespace YODAConcepts {
32
34
36 template <typename T>
37 struct AxisImpl {
38
47
49 using index_sig = size_t (T::*)(const typename T::EdgeT&) const;
50 using edge_sig = typename T::EdgeT (T::*)(const size_t) const;
51 using edges_sig = const std::vector<std::reference_wrapper<const typename T::EdgeT>> (T::*)() const noexcept;
52 using size_sig = size_t (T::*)() const noexcept;
53 using same_edges_sig = bool (T::*)(const T&) const noexcept;
54 using shared_edges_sig = std::vector<typename T::EdgeT> (T::*)(const T&) const noexcept;
55
57 std::is_same<index_sig, decltype(&T::index)>,
58 std::is_same<edge_sig, decltype(&T::edge)>,
59 //std::is_same<edges_sig, decltype(&T::edges)>,
60 std::is_same<size_sig, decltype(&T::size)>,
61 std::is_same<same_edges_sig, decltype(&T::hasSameEdges)>,
62 std::is_same<shared_edges_sig, decltype(&T::sharedEdges)>
63 >;
64 };
65 }
66
67
69 namespace {
71 template <typename EdgeT>
72 using isCAxis = std::enable_if_t<std::is_floating_point<EdgeT>::value>;
73
75 template <typename T>
76 struct hasWidth : std::false_type {};
77
78 template <>
79 struct hasWidth<std::string> : std::true_type {};
80 }
81
82
83 template <typename T, typename>
84 class Axis;
85
86
90 template <typename T, typename = void>
91 class Axis {
92 public:
93 using EdgeT = T;
94 using ContainerT = std::vector<T>;
95 using const_iterator = typename ContainerT::const_iterator;
96
98
99
101 Axis() { }
102
106 Axis(const std::vector<T>& edges);
107
111 Axis(std::vector<T>&& edges);
112
116 Axis(std::initializer_list<T>&& edges);
117
119 Axis(Axis<T>&& other) : _edges(std::move(other._edges)) {}
120
122 Axis(const Axis<T>& other) : _edges(other._edges) {}
123
124 Axis& operator=(const Axis& other) {
125 if (this != &other) _edges = other._edges;
126 return *this;
127 }
128
129 Axis& operator=(Axis&& other) {
130 if (this != &other) _edges = std::move(other._edges);
131 return *this;
132 }
133
135
137 // @{
138
139 void _renderYODA(std::ostream& os) const noexcept {
140 os << "[";
141 for (size_t i = 0; i < _edges.size(); ++i) {
142 if (i) os << ", ";
143 if constexpr(std::is_same<T, std::string>::value) {
144 os << std::quoted(_edges[i]);
145 }
146 else {
147 os << _edges[i];
148 }
149 }
150 os << "]";
151 }
152
154 std::string type() const noexcept { return TypeID<EdgeT>::name(); }
155
156 int maxEdgeWidth() const noexcept {
157 int maxwidth = 0;
158 if constexpr (hasWidth<EdgeT>::value) {
159 auto it = std::max_element(_edges.begin(), _edges.end(),
160 [](const auto& a, const auto& b) {
161 return a.size() < b.size();
162 });
163 maxwidth = (*it).size();
164 }
165 return maxwidth;
166 }
167
169
173 size_t index(const T& x) const;
174
176 EdgeT edge(const size_t i) const;
177
179 std::vector<EdgeT> edges() const noexcept;
180
182 const_iterator begin() const { return _edges.cbegin(); }
183
185 const_iterator end() const { return _edges.cend(); }
186
188 size_t size() const noexcept;
189
191 size_t numBins(const bool includeOverflows = false) const noexcept;
192
194 bool hasSameEdges(const Axis<T>& other) const noexcept;
195
199 std::vector<T> sharedEdges(const Axis<T>& other) const noexcept;
200
203 bool isSubsetEdges(const Axis<T>& other) const noexcept;
204
205 protected:
206
208 // @{
209
211 void fillEdges(std::vector<EdgeT>&& edges) noexcept;
212
213 // @}
214
216 std::vector<T> _edges;
217
218 };
219
220
221 // @todo Document!
222 template <typename T, typename U>
223 Axis<T, U>::Axis(const std::vector<T>& edges) : Axis(std::vector<T>(edges)) {
226 static_assert(MetaUtils::checkConcept<Axis<EdgeT>, YODAConcepts::AxisImpl>(),
227 "Axis<T> should implement Axis concept.");
228 }
229
230 template <typename T, typename U>
231 Axis<T, U>::Axis(std::vector<T>&& edges) {
232 fillEdges(std::move(edges));
233 }
234
235 template <typename T, typename U>
236 Axis<T, U>::Axis(std::initializer_list<T>&& edges) {
237 fillEdges(std::vector<T>{edges});
238 }
239
240 template <typename T, typename U>
241 size_t Axis<T, U>::index(const T& x) const {
242 auto it = std::find(this->_edges.begin(), this->_edges.end(), x);
243 if (it == this->_edges.end()) return 0; // otherflow
244 return it - this->_edges.begin() + 1;
245 }
246
247 template <typename T, typename U>
248 typename Axis<T, U>::EdgeT Axis<T, U>::edge(const size_t i) const {
249 if (this->_edges.empty()) {
250 throw RangeError("Axis has no edges!");
251 }
252 if (!i || i > this->_edges.size()) {
253 throw RangeError("Invalid index, must be in range 1.." + std::to_string(this->_edges.size()));
254 }
255 return this->_edges.at(i-1);
256 }
257
258 template <typename T, typename U>
259 std::vector<typename Axis<T, U>::EdgeT> Axis<T, U>::edges() const noexcept {
260 return this->_edges;
261 }
262
264 template <typename T, typename U>
265 size_t Axis<T, U>::size() const noexcept {
266 return numBins(true);
267 }
268
270 template <typename T, typename U>
271 size_t Axis<T, U>::numBins(const bool includeOverflows) const noexcept {
272 return _edges.size() + (includeOverflows? 1 : 0);
273 }
274
275 template <typename T, typename U>
276 bool Axis<T, U>::hasSameEdges(const Axis<T>& other) const noexcept {
277 return _edges.size() == other._edges.size() &&
278 std::equal(_edges.begin(), _edges.end(), other._edges.begin());
279 }
280
281 template <typename T, typename U>
282 std::vector<T> Axis<T, U>::sharedEdges(const Axis<T>& other) const noexcept {
283 std::vector<EdgeT> res;
284 const auto& otherBegin = other._edges.begin();
285 const auto& otherEnd = other._edges.end();
286 for (const T& edge : this->_edges) {
287 if (std::find(otherBegin, otherEnd, edge) != otherEnd)
288 res.emplace_back(std::move(edge));
289 }
290 return res;
291 }
292
293 template <typename T, typename U>
294 bool Axis<T, U>::isSubsetEdges(const Axis<T>& other) const noexcept {
295 if (_edges.size() == other._edges.size()) return hasSameEdges(other);
296
297 std::vector<T> edgesLhs(edges());
298 std::vector<T> edgesRhs(other.edges());
299
300 std::sort(edgesLhs.begin(), edgesLhs.end());
301 std::sort(edgesRhs.begin(), edgesRhs.end());
302
304 return std::includes(edgesLhs.begin(), edgesLhs.end(),
305 edgesRhs.begin(), edgesRhs.end());
306 }
307
308 template <typename T, typename U>
309 void Axis<T, U>::fillEdges(std::vector<EdgeT>&& edges) noexcept {
310 for (auto& edge : edges) {
311 if (std::find(this->_edges.begin(),
312 this->_edges.end(), edge) == this->_edges.end()) // no duplicate edges allowed
313 _edges.emplace_back(std::move(edge));
314 }
315 }
316
317
318
319
320
321
322
323
325 template <typename T>
326 class Axis<T, isCAxis<T>> {
327 public:
328 using EdgeT = T;
329 using ContainerT = std::vector<T>;
330 using const_iterator = typename ContainerT::const_iterator;
331 using CAxisT = isCAxis<T>;
332
335 _updateEdges({});
336 _setEstimator();
337 }
338
339 Axis(const Axis<T, CAxisT>& other);
340
342 : _est(other._est),
343 _maskedBins(std::move(other._maskedBins)),
344 _edges(std::move(other._edges)) {}
345
349 Axis(std::vector<EdgeT> edges);
350
354 Axis(std::initializer_list<T>&& edges);
355
359 Axis(std::vector<std::pair<EdgeT, EdgeT>> binsEdges);
360
361 Axis(size_t nBins, EdgeT lower, EdgeT upper);
362
363 Axis& operator=(const Axis& other) {
364 if (this != &other) {
365 _est = other._est;
366 _maskedBins = other._maskedBins;
367 _edges = other._edges;
368 }
369 return *this;
370 }
371
372 Axis& operator=(Axis&& other) {
373 if (this != &other) {
374 _est = std::move(other._est);
375 _maskedBins = std::move(other._maskedBins);
376 _edges = std::move(other._edges);
377 }
378 return *this;
379 }
380
381 // /// Explicit constructor, specifying the edges and estimation strategy
382 // Axis(const std::vector<double>& edges, bool log) {
383 // _updateEdges(edges);
384 // // Internally use a log or linear estimator as requested
385 // if (log) {
386 // _est.reset(new LogBinEstimator(edges.size()-1, edges.front(), edges.back()));
387 // } else {
388 // _est.reset(new LinBinEstimator(edges.size()-1, edges.front(), edges.back()));
389 // }
390 // }
391
392
410 size_t index(const EdgeT& x) const;
411
413 size_t size() const noexcept;
414
416 size_t numBins(const bool includeOverflows = false) const noexcept;
417
419 EdgeT edge(const size_t i) const;
420
422 std::vector<EdgeT> edges() const noexcept;
423
425 const_iterator begin() const { return _edges.cbegin(); }
426
428 const_iterator end() const { return _edges.cend(); }
429
431 bool hasSameEdges(const Axis<EdgeT, CAxisT>& other) const noexcept;
432
435 std::vector<T> sharedEdges(const Axis<EdgeT, CAxisT>& other) const noexcept;
436
439 bool isSubsetEdges(const Axis<EdgeT, CAxisT>& other) const noexcept;
440
442 std::vector<size_t> maskedBins() const noexcept { return _maskedBins; }
443
444 // ssize_t index_inrange(double x) const {
445 // const size_t i = index(x);
446 // /// Change to <= and >=
447 // if (i == 0 || i == _edges.size()-1) return -1;
448 // return i;
449 // }
450
452 // @{
453
454 void _renderYODA(std::ostream& os) const noexcept {
455 os << "[";
456 size_t nEdges = _edges.size() - 2; // exclude under-/overflow
457 for (size_t i = 0; i < nEdges; ++i) {
458 if (i) {
459 os << ", ";
460 }
461 os << _edges[i+1];
462 }
463 os << "]";
464 }
465
467 std::string type() const noexcept { return TypeID<EdgeT>::name(); }
468
469 // @}
470
472 // @{
473
477 void mergeBins(std::pair<size_t, size_t>);
478
479 // @}
480
482 // @{
483
484 std::vector<T> widths(const bool includeOverflows = false) const noexcept {
485 // number of widths = number of edges - 1
486 // unless you exclude under-/overflow
487 size_t offset = includeOverflows? 1 : 3;
488 std::vector<T> ret(_edges.size()-offset);
489 size_t start = 1 + !includeOverflows;
490 size_t end = _edges.size() - !includeOverflows;
491 for (size_t i = start; i < end; ++i) {
492 ret[i-start] = _edges[i] - _edges[i-1];
493 }
494 return ret;
495 }
496
498 EdgeT width(size_t binNum) const noexcept {
499 return _edges[binNum+1] - _edges[binNum];
500 }
501
503 EdgeT max(size_t binNum) const noexcept {
504 return _edges[binNum+1];
505 }
506
507
509 EdgeT min(size_t binNum) const noexcept {
510 return _edges[binNum];
511 }
512
514 EdgeT mid(size_t binNum) const noexcept {
516 if(binNum == 0)
517 return std::numeric_limits<EdgeT>::min();
518 if (binNum == (numBins(true) - 1))
519 return std::numeric_limits<EdgeT>::max();
520
521 EdgeT minVal = min(binNum);
522 return (max(binNum) - minVal)/2 + minVal;
523 }
524 // @}
525
526 protected:
527
529 void _updateEdges(std::vector<EdgeT>&& edges) noexcept;
530
533 void _setEstimator() noexcept;
534
538 ssize_t _linsearch_forward(size_t istart, double x, size_t nmax) const noexcept;
539
543 ssize_t _linsearch_backward(size_t istart, double x, size_t nmax) const noexcept;
544
546 size_t _bisect(double x, size_t imin, size_t imax) const noexcept;
547
549 std::shared_ptr<BinEstimator> _est;
550
552 std::vector<size_t> _maskedBins;
553
555 std::vector<T> _edges;
556
557 };
558
559 template <typename T>
560 Axis<T, isCAxis<T>>::Axis(const Axis<T, CAxisT>& other) {
565 static_assert(MetaUtils::checkConcept<Axis<EdgeT>, YODAConcepts::AxisImpl>(),
566 "Axis<T> should implement Axis concept.");
567
568 _est = other._est;
569 _edges = other._edges;
570 _maskedBins = other._maskedBins;
571 }
572
573 template <typename T>
574 Axis<T, isCAxis<T>>::Axis(const size_t nBins, const EdgeT lower, const EdgeT upper) {
575 if(upper <= lower)
576 throw(std::logic_error("Upper bound should be larger than lower."));
577 _edges.resize(nBins+1+2);
578 double step = (upper - lower) / nBins;
579
580 _edges[0] = -std::numeric_limits<double>::infinity();
581
582 _edges[1] = lower;
583
584 for(size_t i = 2; i < _edges.size()-1; i++) {
585 _edges[i] = _edges[i-1] + step;
586 }
587
588 _edges[_edges.size()-1] = std::numeric_limits<double>::infinity();
589
590 _setEstimator();
591 }
592
593 template <typename T>
594 Axis<T, isCAxis<T>>::Axis(std::vector<std::pair<EdgeT, EdgeT>> binsEdges) {
595 if (binsEdges.empty()) throw BinningError("Expected at least one pair of edges");
596
597 std::sort(binsEdges.begin(), binsEdges.end(), [](auto &left, auto &right) {
598 return left.first < right.first;
599 });
600
601 _edges.resize(binsEdges.size()*2+2);
602
603 _edges[0] = -std::numeric_limits<double>::infinity();
604
605
606 /* Edges pairs from binsEdges vector
613 */
614
615 size_t i = 1;
616 for (const auto& pair : binsEdges) {
617 if (!fuzzyGtrEquals(pair.first, _edges[i-1])) throw BinningError("Bin edges shouldn't intersect");
618 if (i == 1 && std::isinf(pair.first) && pair.first < 0) {
619 _edges[i++] = pair.second;
620 continue;
621 }
622 if (fuzzyEquals(pair.first, _edges[i-1])) {
623 _edges[i++] = pair.second;
624 continue;
625 }
626 if (i != 1 && pair.first > _edges[i-1]) {
628 _maskedBins.emplace_back(i-1);
629 }
630 _edges[i++] = pair.first;
631 _edges[i++] = pair.second;
632 }
633
634 _edges[i] = std::numeric_limits<double>::infinity();
635
636 _edges.resize(i+1);
637
638 _setEstimator();
639 }
640
641 template <typename T>
642 Axis<T, isCAxis<T>>::Axis(std::vector<EdgeT> edges) {
643 std::sort(edges.begin(), edges.end());
644 edges.erase( std::unique(edges.begin(), edges.end()), edges.end() );
645
646 _updateEdges(std::move(edges));
647
648 _setEstimator();
649 }
650
651 template <typename T>
652 Axis<T, isCAxis<T>>::Axis(std::initializer_list<T>&& edgeList) {
653 std::vector<T> edges{edgeList};
654 std::sort(edges.begin(), edges.end());
655 edges.erase( std::unique(edges.begin(), edges.end()), edges.end() );
656
657 _updateEdges(std::move(edges));
658
659 _setEstimator();
660 }
661
662 template <typename T>
663 size_t Axis<T, isCAxis<T>>::index(const EdgeT& x) const {
664 if (_edges.size() <= 2) throw BinningError("Axis initialised without specifying edges");
665 // Only one edge (i.e. axis has under- and overflow bin, but no visible bin)
666 if (_edges.size() == 3) return x >= _edges[1];
667 // Check overflows
668 if (std::isinf(x)) { return x < 0? 0 : _edges.size() - 2; }
669 // Get initial estimate
670 size_t index = std::min(_est->estindex(x), _edges.size()-2);
671 // Return now if this is the correct bin
672 if (x >= this->_edges[index] && x < this->_edges[index+1]) return index;
673
674 // Otherwise refine the estimate, if x is not exactly on a bin edge
675 if (x > this->_edges[index]) {
676 const ssize_t newindex = _linsearch_forward(index, x, SEARCH_SIZElc);
677 index = (newindex > 0) ? newindex : _bisect(x, index, this->_edges.size()-1);
678 } else if (x < this->_edges[index]) {
679 const ssize_t newindex = _linsearch_backward(index, x, SEARCH_SIZElc);
680 index = (newindex > 0) ? newindex : _bisect(x, 0, index+1);
681 }
682
683 assert(x >= this->_edges[index] && (x < this->_edges[index+1] || std::isinf(x)));
684 return index;
685 }
686
687 template <typename T>
688 size_t Axis<T, isCAxis<T>>::size() const noexcept {
689 return numBins(true); // number of visible + 2 for +-inf
690 }
691
692 template <typename T>
693 size_t Axis<T, isCAxis<T>>::numBins(const bool includeOverflows) const noexcept {
694 if (_edges.size() < 3) return includeOverflows? 1 : 0; // no visible bin edge
695 return this->_edges.size() - (includeOverflows? 1 : 3); // has 2 extra edges for +-inf
696 }
697
698 template <typename T>
699 typename Axis<T, isCAxis<T>>::EdgeT Axis<T, isCAxis<T>>::edge(const size_t i) const {
700 return this->_edges.at(i);
701 }
702
703 template <typename T>
704 std::vector<typename Axis<T, isCAxis<T>>::EdgeT> Axis<T, isCAxis<T>>::edges() const noexcept {
705 return this->_edges;
706 }
707
708 template <typename T>
709 bool Axis<T, isCAxis<T>>::hasSameEdges(const Axis<T, CAxisT>& other) const noexcept{
710 if (this->numBins(true) != other.numBins(true)) return false;
711 for (size_t i = 1; i < this->numBins(true) - 1; i++) {
713 if (!fuzzyEquals(edge(i), other.edge(i))) return false;
714 }
715 return true;
716 }
717
718 template <typename T>
719 std::vector<T> Axis<T, isCAxis<T>>::sharedEdges(const Axis<T, CAxisT>& other) const noexcept {
720 std::vector<T> intersection;
721
723 if (_edges.size() > 2 && other._edges.size() > 2) {
724 std::set_intersection(std::next(_edges.begin()), std::prev(_edges.end()),
725 std::next(other._edges.begin()), std::prev(other._edges.end()),
726 std::back_inserter(intersection));
727 }
728
729 std::vector<T> rtn;
730 rtn.reserve(intersection.size()+2);
731
732 rtn.emplace_back(-std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
733 rtn.insert(std::next(rtn.begin()),
734 std::make_move_iterator(intersection.begin()),
735 std::make_move_iterator(intersection.end()));
736 rtn.emplace_back(std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
737
738 return rtn;
739 }
740
741 template <typename T>
742 bool Axis<T, isCAxis<T>>::isSubsetEdges(const Axis<T, CAxisT>& other) const noexcept {
743 if (_edges.size() == other._edges.size()) return hasSameEdges(other);
744
746 if (_edges.size() > 2 && other._edges.size() > 2) {
748 return std::includes(std::next(_edges.begin()), std::prev(_edges.end()),
749 std::next(other._edges.begin()), std::prev(other._edges.end()));
750 }
751
754 return true;
755 }
756
757 template <typename T>
758 void Axis<T, isCAxis<T>>::mergeBins(std::pair<size_t, size_t> mergeRange) {
759 if (_edges.size() <= 2) throw BinningError("Axis initialised without specifying edges");
760 if (mergeRange.second < mergeRange.first) throw RangeError("Upper index comes before lower index");
761 if (mergeRange.second >= numBins(true)) throw RangeError("Upper index exceeds last visible bin");
762 _edges.erase(_edges.begin()+mergeRange.first+1, _edges.begin() + mergeRange.second + 1);
763 // masked bins above the merge range need to be re-indexed
764 // masked bins within the merge range need to be unmasked
765 std::vector<size_t> toRemove;
766 size_t mrange = mergeRange.second - mergeRange.first;
767 for (size_t i = 0; i < _maskedBins.size(); ++i) {
768 if (_maskedBins[i] > mergeRange.second) _maskedBins[i] -= mrange;
769 else if (_maskedBins[i] >= mergeRange.first) toRemove.push_back(_maskedBins[i]);
770 }
771 if (toRemove.size()) {
772 _maskedBins.erase(
773 std::remove_if(_maskedBins.begin(), _maskedBins.end(), [&](const auto& idx) {
774 return std::find(toRemove.begin(), toRemove.end(), idx) != toRemove.end();
775 }), _maskedBins.end());
776 }
777 }
778
779
780 template <typename T>
781 void Axis<T, isCAxis<T>>::_updateEdges(std::vector<EdgeT>&& edges) noexcept {
782 // Array of in-range edges, plus underflow and overflow sentinels
783 _edges.clear();
784
785 // Move vector and insert -+inf at ends
786 _edges.emplace_back(-std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
787 _edges.insert(std::next(_edges.begin()),
788 std::make_move_iterator(edges.begin()),
789 std::make_move_iterator(edges.end()));
790 _edges.emplace_back(std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
791 }
792
793 template <typename T>
794 void Axis<T, isCAxis<T>>::_setEstimator() noexcept {
795
796 // Empty set for nullary constructor
797 if (_edges.size() <= 2) {
798 _est = std::make_shared<LinBinEstimator>(0, 0, 1);
799 return;
800 }
801
802 // There is at least one visible edge
803 int front = 1, back = (int)_edges.size()-2;
804 if (_edges[front] <= 0.0) {
805 _est = std::make_shared<LinBinEstimator>(back - front, _edges[front], _edges[back]);
806 }
807 else {
808 LinBinEstimator linEst(back - front, _edges[front], _edges[back]);
809 LogBinEstimator logEst(back - front, _edges[front], _edges[back]);
810
811 // Calculate mean index estimate deviations from the correct answers (for bin edges)
812 double logsum = 0, linsum = 0;
813 for (int i = front; i <= back; ++i) {
814 logsum += abs(logEst(_edges[i]) - double(i-1));
815 linsum += abs(linEst(_edges[i]) - double(i-1));
816 }
817 const double log_avg = logsum / _edges.size();
818 const double lin_avg = linsum / _edges.size();
819
820 // This also implicitly works for NaN returned from the log There is a
821 // subtle bug here if the if statement is the other way around, as
822 // (nan < linsum) -> false always. But (nan > linsum) -> false also.
823 if (log_avg < lin_avg) { //< Use log estimator if its avg performance is better than lin
824 _est = std::make_shared<LogBinEstimator>(logEst);
825 }
826 else { // else use linear estimation
827 _est = std::make_shared<LinBinEstimator>(linEst);
828 }
829 }
830 }
831
832
833 template <typename T>
834 ssize_t Axis<T, isCAxis<T>>::_linsearch_forward(size_t istart, double x, size_t nmax) const noexcept {
835 assert(x >= this->_edges[istart]); // assumption that x >= start is wrong
836 for (size_t i = 0; i < nmax; i++) {
837 const size_t j = istart + i + 1; // index of _next_ edge
838 if (j > this->_edges.size()-1) return -1;
839 if (x < this->_edges[j]) {
840 assert(x >= this->_edges[j-1] && (x < this->_edges[j] || std::isinf(x)));
841 return j-1; // note one more iteration needed if x is on an edge
842 }
843 }
844 return -1;
845 }
846
847 template <typename T>
848 ssize_t Axis<T, isCAxis<T>>::_linsearch_backward(size_t istart, double x, size_t nmax) const noexcept {
849 assert(x < this->_edges[istart]); // assumption that x < start is wrong
850 for (size_t i = 0; i < nmax; i++) {
851 const int j = istart - i - 1; // index of _next_ edge (working backwards)
852 if (j < 0) return -1;
853 if (x >= this->_edges[j]) {
854 assert(x >= this->_edges[j] && (x < this->_edges[j+1] || std::isinf(x)));
855 return (ssize_t) j; // note one more iteration needed if x is on an edge
856 }
857 }
858 return -1;
859 }
860
861 template <typename T>
862 size_t Axis<T, isCAxis<T>>::_bisect(double x, size_t imin, size_t imax) const noexcept {
863 size_t len = imax - imin;
864 while (len >= BISECT_LINEAR_THRESHOLDlc) {
865 const size_t half = len >> 1;
866 const size_t imid = imin + half;
867 if (x >= this->_edges[imid]) {
868 if (x < this->_edges[imid+1]) return imid; // Might as well return directly if we get lucky!
869 imin = imid;
870 } else {
871 imax = imid;
872 }
873 len = imax - imin;
874 }
875 assert(x >= this->_edges[imin] && (x < this->_edges[imax] || std::isinf(x)));
876 return _linsearch_forward(imin, x, BISECT_LINEAR_THRESHOLDlc);
877 }
878
879}
880
881#endif
std::string type() const noexcept
Returns a string representation of EdgeT.
Definition BinnedAxis.h:467
std::vector< T > ContainerT
Definition BinnedAxis.h:329
Axis(Axis< T, CAxisT > &&other)
Definition BinnedAxis.h:341
Axis()
Nullary constructor for unique pointers etc.
Definition BinnedAxis.h:334
EdgeT mid(size_t binNum) const noexcept
Mid of bin side on this axis.
Definition BinnedAxis.h:514
EdgeT width(size_t binNum) const noexcept
Width of bin side on this axis.
Definition BinnedAxis.h:498
const_iterator end() const
Returns the const end iterator for the edges container.
Definition BinnedAxis.h:428
std::vector< T > widths(const bool includeOverflows=false) const noexcept
Definition BinnedAxis.h:484
typename ContainerT::const_iterator const_iterator
Definition BinnedAxis.h:330
Axis & operator=(const Axis &other)
Definition BinnedAxis.h:363
std::vector< size_t > maskedBins() const noexcept
Returns the masked indices.
Definition BinnedAxis.h:442
EdgeT min(size_t binNum) const noexcept
Min of bin side on this axis.
Definition BinnedAxis.h:509
Axis & operator=(Axis &&other)
Definition BinnedAxis.h:372
EdgeT max(size_t binNum) const noexcept
Max of bin side on this axis.
Definition BinnedAxis.h:503
Discrete axis with edges of non-floating-point-type.
Definition BinnedAxis.h:91
std::string type() const noexcept
Returns a string representation of EdgeT.
Definition BinnedAxis.h:154
Axis & operator=(const Axis &other)
Definition BinnedAxis.h:124
std::vector< T > ContainerT
Definition BinnedAxis.h:94
size_t size() const noexcept
Returns number of edges + 1 for this Axis.
Definition BinnedAxis.h:265
const_iterator begin() const
Returns the const begin iterator for the edges container.
Definition BinnedAxis.h:182
Axis()
Nullary constructor for unique pointers etc.
Definition BinnedAxis.h:101
const_iterator end() const
Returns the const end iterator for the edges container.
Definition BinnedAxis.h:185
Axis(const Axis< T > &other)
Copy constructs Axis.
Definition BinnedAxis.h:122
int maxEdgeWidth() const noexcept
Definition BinnedAxis.h:156
Axis & operator=(Axis &&other)
Definition BinnedAxis.h:129
Axis(Axis< T > &&other)
Move constructs Axis.
Definition BinnedAxis.h:119
typename ContainerT::const_iterator const_iterator
Definition BinnedAxis.h:95
Error for general binning problems.
Definition Exceptions.h:27
Error for e.g. use of invalid bin ranges.
Definition Exceptions.h:34
Anonymous namespace to limit visibility.
std::enable_if_t< std::is_arithmetic_v< N1 > &&std::is_arithmetic_v< N2 >, bool > fuzzyGtrEquals(N1 a, N2 b, double tolerance=1e-5)
Compare two numbers for >= with a degree of fuzziness.
Definition MathUtils.h:125
const size_t SEARCH_SIZElc
Definition BinnedAxis.h:28
std::enable_if_t< std::is_arithmetic_v< N1 > &&std::is_arithmetic_v< N2 > &&(std::is_floating_point_v< N1 >||std::is_floating_point_v< N2 >), bool > fuzzyEquals(N1 a, N2 b, double tolerance=1e-5)
Compare two numbers for equality with a degree of fuzziness.
Definition MathUtils.h:96
const size_t BISECT_LINEAR_THRESHOLDlc
Definition BinnedAxis.h:29
Logical conjuction implementation.
Definition MetaUtils.h:43
Bin estimator.
Returns the type ID as a character sequence.
const std::vector< std::reference_wrapper< const typename T::EdgeT > >(T::*)() const noexcept edges_sig
Definition BinnedAxis.h:51
typename T::EdgeT(T::*)(const size_t) const edge_sig
Definition BinnedAxis.h:50
size_t(T::*)(const typename T::EdgeT &) const index_sig
Function signatures.
Definition BinnedAxis.h:49
bool(T::*)(const T &) const noexcept same_edges_sig
Definition BinnedAxis.h:53
std::vector< typename T::EdgeT >(T::*)(const T &) const noexcept shared_edges_sig
Definition BinnedAxis.h:54
size_t(T::*)() const noexcept size_sig
Definition BinnedAxis.h:52