yoda is hosted by Hepforge, IPPP Durham
YODA - Yet more Objects for Data Analysis 2.0.2
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 discrete edge");
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 // Check overflows
666 if (std::isinf(x)) { return x < 0? 0 : _edges.size() - 2; }
667 // Get initial estimate
668 size_t index = std::min(_est->estindex(x), _edges.size()-2);
669 // Return now if this is the correct bin
670 if (x >= this->_edges[index] && x < this->_edges[index+1]) return index;
671
672 // Otherwise refine the estimate, if x is not exactly on a bin edge
673 if (x > this->_edges[index]) {
674 const ssize_t newindex = _linsearch_forward(index, x, SEARCH_SIZElc);
675 index = (newindex > 0) ? newindex : _bisect(x, index, this->_edges.size()-1);
676 } else if (x < this->_edges[index]) {
677 const ssize_t newindex = _linsearch_backward(index, x, SEARCH_SIZElc);
678 index = (newindex > 0) ? newindex : _bisect(x, 0, index+1);
679 }
680
681 assert(x >= this->_edges[index] && (x < this->_edges[index+1] || std::isinf(x)));
682 return index;
683 }
684
685 template <typename T>
686 size_t Axis<T, isCAxis<T>>::size() const noexcept {
687 return numBins(true); // number of visible + 2 for +-inf
688 }
689
690 template <typename T>
691 size_t Axis<T, isCAxis<T>>::numBins(const bool includeOverflows) const noexcept {
692 if (_edges.size() < 3) return includeOverflows? 1 : 0; // no visible bin edge
693 return this->_edges.size() - (includeOverflows? 1 : 3); // has 2 extra edges for +-inf
694 }
695
696 template <typename T>
697 typename Axis<T, isCAxis<T>>::EdgeT Axis<T, isCAxis<T>>::edge(const size_t i) const {
698 return this->_edges.at(i);
699 }
700
701 template <typename T>
702 std::vector<typename Axis<T, isCAxis<T>>::EdgeT> Axis<T, isCAxis<T>>::edges() const noexcept {
703 return this->_edges;
704 }
705
706 template <typename T>
707 bool Axis<T, isCAxis<T>>::hasSameEdges(const Axis<T, CAxisT>& other) const noexcept{
708 if (this->numBins(true) != other.numBins(true)) return false;
709 for (size_t i = 1; i < this->numBins(true) - 1; i++) {
711 if (!fuzzyEquals(edge(i), other.edge(i))) return false;
712 }
713 return true;
714 }
715
716 template <typename T>
717 std::vector<T> Axis<T, isCAxis<T>>::sharedEdges(const Axis<T, CAxisT>& other) const noexcept {
718 std::vector<T> intersection;
719
721 if (_edges.size() > 2 && other._edges.size() > 2) {
722 std::set_intersection(std::next(_edges.begin()), std::prev(_edges.end()),
723 std::next(other._edges.begin()), std::prev(other._edges.end()),
724 std::back_inserter(intersection));
725 }
726
727 std::vector<T> rtn;
728 rtn.reserve(intersection.size()+2);
729
730 rtn.emplace_back(-std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
731 rtn.insert(std::next(rtn.begin()),
732 std::make_move_iterator(intersection.begin()),
733 std::make_move_iterator(intersection.end()));
734 rtn.emplace_back(std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
735
736 return rtn;
737 }
738
739 template <typename T>
740 bool Axis<T, isCAxis<T>>::isSubsetEdges(const Axis<T, CAxisT>& other) const noexcept {
741 if (_edges.size() == other._edges.size()) return hasSameEdges(other);
742
744 if (_edges.size() > 2 && other._edges.size() > 2) {
746 return std::includes(std::next(_edges.begin()), std::prev(_edges.end()),
747 std::next(other._edges.begin()), std::prev(other._edges.end()));
748 }
749
752 return true;
753 }
754
755 template <typename T>
756 void Axis<T, isCAxis<T>>::mergeBins(std::pair<size_t, size_t> mergeRange) {
757 if (_edges.size() <= 2) throw BinningError("Axis initialised without specifying edges");
758 if (mergeRange.second < mergeRange.first) throw RangeError("Upper index comes before lower index");
759 if (mergeRange.second >= numBins(true)) throw RangeError("Upper index exceeds last visible bin");
760 _edges.erase(_edges.begin()+mergeRange.first+1, _edges.begin() + mergeRange.second + 1);
761 // masked bins above the merge range need to be re-indexed
762 // masked bins within the merge range need to be unmasked
763 std::vector<size_t> toRemove;
764 size_t mrange = mergeRange.second - mergeRange.first;
765 for (size_t i = 0; i < _maskedBins.size(); ++i) {
766 if (_maskedBins[i] > mergeRange.second) _maskedBins[i] -= mrange;
767 else if (_maskedBins[i] >= mergeRange.first) toRemove.push_back(_maskedBins[i]);
768 }
769 if (toRemove.size()) {
770 _maskedBins.erase(
771 std::remove_if(_maskedBins.begin(), _maskedBins.end(), [&](const auto& idx) {
772 return std::find(toRemove.begin(), toRemove.end(), idx) != toRemove.end();
773 }), _maskedBins.end());
774 }
775 }
776
777
778 template <typename T>
779 void Axis<T, isCAxis<T>>::_updateEdges(std::vector<EdgeT>&& edges) noexcept {
780 // Array of in-range edges, plus underflow and overflow sentinels
781 _edges.clear();
782
783 // Move vector and insert -+inf at ends
784 _edges.emplace_back(-std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
785 _edges.insert(std::next(_edges.begin()),
786 std::make_move_iterator(edges.begin()),
787 std::make_move_iterator(edges.end()));
788 _edges.emplace_back(std::numeric_limits<Axis<T, isCAxis<T>>::EdgeT>::infinity());
789 }
790
791 template <typename T>
792 void Axis<T, isCAxis<T>>::_setEstimator() noexcept {
793 if (_edges.empty()) {
794 _est = std::make_shared<LinBinEstimator>(0, 0, 1);
795 } else if (_edges.front() <= 0.0) {
796 _est = std::make_shared<LinBinEstimator>(_edges.size()-1, _edges.front(), _edges.back());
797 } else {
798 LinBinEstimator linEst(_edges.size()-1, _edges.front(), _edges.back());
799 LogBinEstimator logEst(_edges.size()-1, _edges.front(), _edges.back());
800
801 // Calculate mean index estimate deviations from the correct answers (for bin edges)
802 double logsum = 0, linsum = 0;
803 for (size_t i = 0; i < _edges.size(); i++) {
804 logsum += logEst(_edges[i]) - i;
805 linsum += linEst(_edges[i]) - i;
806 }
807 const double log_avg = logsum / _edges.size();
808 const double lin_avg = linsum / _edges.size();
809
810 // This also implicitly works for NaN returned from the log There is a
811 // subtle bug here if the if statement is the other way around, as
812 // (nan < linsum) -> false always. But (nan > linsum) -> false also.
813 if (log_avg < lin_avg) { //< Use log estimator if its avg performance is better than lin
814 _est = std::make_shared<LogBinEstimator>(logEst);
815 } else { // Else use linear estimation
816 _est = std::make_shared<LinBinEstimator>(linEst);
817 }
818 }
819 }
820
821
822 template <typename T>
823 ssize_t Axis<T, isCAxis<T>>::_linsearch_forward(size_t istart, double x, size_t nmax) const noexcept {
824 assert(x >= this->_edges[istart]); // assumption that x >= start is wrong
825 for (size_t i = 0; i < nmax; i++) {
826 const size_t j = istart + i + 1; // index of _next_ edge
827 if (j > this->_edges.size()-1) return -1;
828 if (x < this->_edges[j]) {
829 assert(x >= this->_edges[j-1] && (x < this->_edges[j] || std::isinf(x)));
830 return j-1; // note one more iteration needed if x is on an edge
831 }
832 }
833 return -1;
834 }
835
836 template <typename T>
837 ssize_t Axis<T, isCAxis<T>>::_linsearch_backward(size_t istart, double x, size_t nmax) const noexcept {
838 assert(x < this->_edges[istart]); // assumption that x < start is wrong
839 for (size_t i = 0; i < nmax; i++) {
840 const int j = istart - i - 1; // index of _next_ edge (working backwards)
841 if (j < 0) return -1;
842 if (x >= this->_edges[j]) {
843 assert(x >= this->_edges[j] && (x < this->_edges[j+1] || std::isinf(x)));
844 return (ssize_t) j; // note one more iteration needed if x is on an edge
845 }
846 }
847 return -1;
848 }
849
850 template <typename T>
851 size_t Axis<T, isCAxis<T>>::_bisect(double x, size_t imin, size_t imax) const noexcept {
852 size_t len = imax - imin;
853 while (len >= BISECT_LINEAR_THRESHOLDlc) {
854 const size_t half = len >> 1;
855 const size_t imid = imin + half;
856 if (x >= this->_edges[imid]) {
857 if (x < this->_edges[imid+1]) return imid; // Might as well return directly if we get lucky!
858 imin = imid;
859 } else {
860 imax = imid;
861 }
862 len = imax - imin;
863 }
864 assert(x >= this->_edges[imin] && (x < this->_edges[imax] || std::isinf(x)));
865 return _linsearch_forward(imin, x, BISECT_LINEAR_THRESHOLDlc);
866 }
867
868}
869
870#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:42
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