Skip to content

Commit 04e2b4c

Browse files
authored
Merge pull request #48857 from Electricks94/MultiVectorManager
Move MultiVectorManager to DataFormats/Common and address discussion from #48826 and rename it to MultiSpan
2 parents daf940c + 8ad3881 commit 04e2b4c

29 files changed

+457
-211
lines changed
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Author: Felice Pantaleo (CERN), 2023, [email protected]
2+
#ifndef DataFormats_Common_MultiSpan_h
3+
#define DataFormats_Common_MultiSpan_h
4+
5+
#include <algorithm>
6+
#include <cassert>
7+
#include <span>
8+
#include <stdexcept>
9+
#include <vector>
10+
11+
namespace edm {
12+
13+
/**
14+
* @brief A view-like container that provides a contiguous indexing interface over multiple disjoint spans.
15+
*
16+
* MultiSpan allows to append multiple `std::vector<T>` as std::span<const T> instances and access them through a
17+
* single global index as if they formed one continuous sequence.
18+
*
19+
* This class is read-only and does not take ownership of the underlying data.
20+
* It is intended for iteration over heterogeneous but logically connected data ranges without copying
21+
* or merging them into a single container.
22+
*
23+
* To find a span that corresponds to a global index, a binary search is used, making the access time logarithmic in the number of spans.
24+
* This means when iterating over the elements the binary search over spans is repeated for every element.
25+
*
26+
*/
27+
template <typename T>
28+
class MultiSpan {
29+
public:
30+
MultiSpan() = default;
31+
32+
void add(std::span<const T> sp) {
33+
// Empty spans are not added to reduce the number of spans and speed up the binary search
34+
if (sp.empty()) {
35+
return;
36+
}
37+
38+
spans_.emplace_back(sp);
39+
offsets_.push_back(totalSize_);
40+
totalSize_ += sp.size();
41+
}
42+
43+
const T& operator[](const std::size_t globalIndex) const {
44+
#ifndef NDEBUG
45+
if (globalIndex >= totalSize_) {
46+
throw std::out_of_range("Global index out of range");
47+
}
48+
#endif
49+
const auto [spanIndex, indexWithinSpan] = spanAndLocalIndex(globalIndex);
50+
return spans_[spanIndex][indexWithinSpan];
51+
}
52+
53+
std::size_t globalIndex(const std::size_t spanIndex, const std::size_t indexWithinSpan) const {
54+
#ifndef NDEBUG
55+
if (spanIndex >= spans_.size()) {
56+
throw std::out_of_range("spanIndex index out of range");
57+
}
58+
if (indexWithinSpan >= spans_[spanIndex].size()) {
59+
throw std::out_of_range("indexWithinSpan index out of range");
60+
}
61+
#endif
62+
63+
return offsets_[spanIndex] + indexWithinSpan;
64+
}
65+
66+
std::pair<std::size_t, std::size_t> spanAndLocalIndex(const std::size_t globalIndex) const {
67+
#ifndef NDEBUG
68+
if (globalIndex >= totalSize_) {
69+
throw std::out_of_range("Global index out of range");
70+
}
71+
#endif
72+
auto it = std::upper_bound(offsets_.begin(), offsets_.end(), globalIndex);
73+
std::size_t spanIndex = std::distance(offsets_.begin(), it) - 1;
74+
std::size_t indexWithinSpan = globalIndex - offsets_[spanIndex];
75+
76+
return {spanIndex, indexWithinSpan};
77+
}
78+
79+
std::size_t size() const { return totalSize_; }
80+
81+
class ConstRandomAccessIterator {
82+
public:
83+
using iterator_category = std::random_access_iterator_tag;
84+
using difference_type = std::ptrdiff_t;
85+
using value_type = T;
86+
using pointer = const T*;
87+
using reference = const T&;
88+
89+
ConstRandomAccessIterator(const MultiSpan& ms, const std::size_t index) : ms_(&ms), currentIndex_(index) {}
90+
91+
reference operator*() const { return (*ms_)[currentIndex_]; }
92+
pointer operator->() const { return &(*ms_)[currentIndex_]; }
93+
94+
reference operator[](difference_type n) const { return (*ms_)[currentIndex_ + n]; }
95+
96+
ConstRandomAccessIterator& operator++() {
97+
++currentIndex_;
98+
return *this;
99+
}
100+
ConstRandomAccessIterator operator++(int) {
101+
auto tmp = *this;
102+
++(*this);
103+
return tmp;
104+
}
105+
ConstRandomAccessIterator& operator--() {
106+
--currentIndex_;
107+
return *this;
108+
}
109+
ConstRandomAccessIterator operator--(int) {
110+
auto tmp = *this;
111+
--(*this);
112+
return tmp;
113+
}
114+
115+
ConstRandomAccessIterator& operator+=(difference_type n) {
116+
currentIndex_ += n;
117+
return *this;
118+
}
119+
ConstRandomAccessIterator& operator-=(difference_type n) {
120+
currentIndex_ -= n;
121+
return *this;
122+
}
123+
124+
ConstRandomAccessIterator operator+(difference_type n) const {
125+
return ConstRandomAccessIterator(*ms_, currentIndex_ + n);
126+
}
127+
128+
ConstRandomAccessIterator operator-(difference_type n) const {
129+
return ConstRandomAccessIterator(*ms_, currentIndex_ - n);
130+
}
131+
132+
difference_type operator-(const ConstRandomAccessIterator& other) const {
133+
return currentIndex_ - other.currentIndex_;
134+
}
135+
136+
bool operator==(const ConstRandomAccessIterator& other) const { return currentIndex_ == other.currentIndex_; }
137+
bool operator!=(const ConstRandomAccessIterator& other) const { return currentIndex_ != other.currentIndex_; }
138+
auto operator<=>(const ConstRandomAccessIterator& other) const { return currentIndex_ <=> other.currentIndex_; }
139+
140+
private:
141+
const MultiSpan* ms_;
142+
std::size_t currentIndex_;
143+
};
144+
145+
using const_iterator = ConstRandomAccessIterator;
146+
147+
const_iterator begin() const { return const_iterator(*this, 0); }
148+
const_iterator end() const { return const_iterator(*this, totalSize_); }
149+
150+
private:
151+
std::vector<std::span<const T>> spans_;
152+
std::vector<std::size_t> offsets_;
153+
std::size_t totalSize_{0};
154+
};
155+
156+
} // namespace edm
157+
158+
#endif // DataFormats_Common_MultiSpan_h
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#include <algorithm>
2+
#include <cassert>
3+
#include <cmath>
4+
#include <numeric>
5+
#include <vector>
6+
#include <catch.hpp>
7+
8+
#include "DataFormats/Common/interface/MultiSpan.h"
9+
10+
TEST_CASE("MultiSpan basic indexing", "[MultiSpan]") {
11+
edm::MultiSpan<int> emptyMultiSpan;
12+
edm::MultiSpan<int> ms;
13+
14+
edm::MultiSpan<int> ms1; // MultiSpan with empty span as first span
15+
edm::MultiSpan<int> ms2; // MultiSpan with several empty spans
16+
edm::MultiSpan<int> ms3; // MultiSpan with empty span as last span
17+
18+
std::vector<int> a = {1, 2, 3};
19+
std::vector<int> b = {4, 5};
20+
std::vector<int> c;
21+
22+
ms.add(a);
23+
ms.add(b);
24+
25+
ms1.add(c);
26+
ms1.add(b);
27+
ms1.add(a);
28+
29+
ms2.add(b);
30+
ms2.add(c);
31+
ms2.add(c);
32+
ms2.add(c);
33+
ms2.add(b);
34+
ms2.add(a);
35+
ms2.add(c);
36+
37+
ms3.add(a);
38+
ms3.add(c);
39+
40+
using ElementType = decltype(ms[0]);
41+
// Check that the const-correctness of the MultiSpan
42+
static_assert(!std::is_assignable<ElementType, int>::value,
43+
"It should not be possible to assign to an element of MultiSpan; See PR #48826");
44+
45+
SECTION("Empty MultiSpan") {
46+
REQUIRE(emptyMultiSpan.size() == 0);
47+
REQUIRE(emptyMultiSpan.begin() == emptyMultiSpan.end());
48+
REQUIRE_THROWS_AS(emptyMultiSpan[0], std::out_of_range);
49+
REQUIRE_THROWS_AS(emptyMultiSpan.globalIndex(0, 0), std::out_of_range);
50+
}
51+
52+
SECTION("Size is correct") { REQUIRE(ms.size() == 5); }
53+
54+
SECTION("Range check") {
55+
REQUIRE_THROWS_AS(ms[5], std::out_of_range);
56+
REQUIRE_THROWS_AS(ms.globalIndex(2, 0), std::out_of_range);
57+
REQUIRE_THROWS_AS(ms.globalIndex(1, 2), std::out_of_range);
58+
REQUIRE_THROWS_AS(ms.spanAndLocalIndex(5), std::out_of_range);
59+
}
60+
61+
SECTION("Indexing returns correct values") {
62+
REQUIRE(ms[0] == 1);
63+
REQUIRE(ms[1] == 2);
64+
REQUIRE(ms[2] == 3);
65+
REQUIRE(ms[3] == 4);
66+
REQUIRE(ms[4] == 5);
67+
}
68+
69+
SECTION("Global index from span index and local index") {
70+
REQUIRE(ms.globalIndex(0, 0) == 0);
71+
REQUIRE(ms.globalIndex(0, 2) == 2);
72+
REQUIRE(ms.globalIndex(1, 0) == 3);
73+
REQUIRE(ms.globalIndex(1, 1) == 4);
74+
}
75+
76+
SECTION("Span and local index from global index") {
77+
auto [span0, local0] = ms.spanAndLocalIndex(0);
78+
REQUIRE(span0 == 0);
79+
REQUIRE(local0 == 0);
80+
81+
auto [span1, local1] = ms.spanAndLocalIndex(4);
82+
REQUIRE(span1 == 1);
83+
REQUIRE(local1 == 1);
84+
}
85+
86+
SECTION("Iterators work with range-based for") {
87+
std::vector<int> collected;
88+
for (auto val : ms) {
89+
collected.push_back(val);
90+
}
91+
REQUIRE(collected == std::vector<int>{1, 2, 3, 4, 5});
92+
}
93+
94+
SECTION("Random access iterator supports arithmetic") {
95+
auto it = ms.begin();
96+
REQUIRE(*(it + 2) == 3);
97+
REQUIRE(*(ms.end() - 2) == 4);
98+
REQUIRE((ms.end() - ms.begin()) == 5);
99+
}
100+
101+
SECTION("std::find works") {
102+
auto it = std::find(ms.begin(), ms.end(), 4);
103+
REQUIRE(it != ms.end());
104+
REQUIRE(*it == 4);
105+
REQUIRE((it - ms.begin()) == 3);
106+
}
107+
108+
SECTION("std::distance returns correct result") {
109+
auto dist = std::distance(ms.begin(), ms.end());
110+
REQUIRE(dist == 5);
111+
}
112+
113+
SECTION("std::copy copies all values") {
114+
std::vector<int> out(5);
115+
std::copy(ms.begin(), ms.end(), out.begin());
116+
117+
REQUIRE(ms[0] == out[0]);
118+
REQUIRE(ms[1] == out[1]);
119+
REQUIRE(ms[2] == out[2]);
120+
REQUIRE(ms[3] == out[3]);
121+
REQUIRE(ms[4] == out[4]);
122+
}
123+
124+
SECTION("Check MultiSpan with empty span as first span") {
125+
REQUIRE(ms1.size() == 5);
126+
127+
REQUIRE(ms1[0] == b[0]);
128+
REQUIRE(ms1[1] == b[1]);
129+
REQUIRE(ms1[2] == a[0]);
130+
REQUIRE(ms1[3] == a[1]);
131+
REQUIRE(ms1[4] == a[2]);
132+
133+
REQUIRE(ms1.globalIndex(0, 0) == 0);
134+
REQUIRE(ms1.globalIndex(1, 1) == 3);
135+
136+
std::vector<int> collected;
137+
for (auto val : ms1) {
138+
collected.push_back(val);
139+
}
140+
REQUIRE(collected == std::vector<int>{b[0], b[1], a[0], a[1], a[2]});
141+
}
142+
143+
SECTION("Check MultiSpan with serveral empty spans") {
144+
REQUIRE(ms2.size() == 7);
145+
146+
REQUIRE(ms2[0] == b[0]);
147+
REQUIRE(ms2[1] == b[1]);
148+
REQUIRE(ms2[2] == b[0]);
149+
REQUIRE(ms2[3] == b[1]);
150+
REQUIRE(ms2[4] == a[0]);
151+
REQUIRE(ms2[5] == a[1]);
152+
REQUIRE(ms2[6] == a[2]);
153+
154+
REQUIRE(ms2.globalIndex(0, 0) == 0);
155+
REQUIRE(ms2.globalIndex(1, 1) == 3);
156+
REQUIRE(ms2.globalIndex(2, 1) == 5);
157+
158+
std::vector<int> collected;
159+
for (auto val : ms2) {
160+
collected.push_back(val);
161+
}
162+
REQUIRE(collected == std::vector<int>{b[0], b[1], b[0], b[1], a[0], a[1], a[2]});
163+
}
164+
165+
SECTION("Check MultiSpan with serveral empty spans") {
166+
REQUIRE(ms3.size() == 3);
167+
168+
REQUIRE(ms3[0] == a[0]);
169+
REQUIRE(ms3[1] == a[1]);
170+
REQUIRE(ms3[2] == a[2]);
171+
172+
std::vector<int> collected;
173+
for (auto val : ms3) {
174+
collected.push_back(val);
175+
}
176+
REQUIRE(collected == std::vector<int>{a[0], a[1], a[2]});
177+
}
178+
}

0 commit comments

Comments
 (0)