Skip to content

Commit a2df8fa

Browse files
committed
hyperedge list alignment
1 parent afa3fc9 commit a2df8fa

6 files changed

Lines changed: 173 additions & 64 deletions

File tree

include/hgl/hypergraph.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ class hypergraph final {
368368
this->_n_vertices--;
369369
if constexpr (type_traits::c_non_empty_properties<vertex_properties_type>)
370370
this->_vertex_properties.erase(this->_vertex_properties.begin() + vertex_id);
371+
// TODO: impl::remove_vertex(vertex_id)
371372
}
372373

373374
// --- hyperedge methods ---

include/hgl/hypergraph_traits.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ template <
3737
type_traits::c_hypergraph_directional_tag DirectionalTag = undirected_t,
3838
type_traits::c_properties VertexProperties = types::empty_properties,
3939
type_traits::c_properties HyperedgeProperties = types::empty_properties>
40-
using adjacency_list_hg_traits =
41-
hypergraph_traits<DirectionalTag, VertexProperties, HyperedgeProperties, impl::adjacency_list_t>;
40+
using vertex_list_hg_traits =
41+
hypergraph_traits<DirectionalTag, VertexProperties, HyperedgeProperties, impl::vertex_list_t>;
4242

4343
template <
4444
type_traits::c_hypergraph_directional_tag DirectionalTag = undirected_t,
@@ -69,9 +69,9 @@ concept c_hyperedge_list_hg_traits =
6969
and std::same_as<typename TraitsType::implementation_tag, impl::hyperedge_list_t>;
7070

7171
template <typename TraitsType>
72-
concept c_adjacency_list_hg_traits =
72+
concept c_vertex_list_hg_traits =
7373
c_instantiation_of<TraitsType, hypergraph_traits>
74-
and std::same_as<typename TraitsType::implementation_tag, impl::adjacency_list_t>;
74+
and std::same_as<typename TraitsType::implementation_tag, impl::vertex_list_t>;
7575

7676
template <typename TraitsType>
7777
concept c_incidence_matrix_hg_traits =

include/hgl/impl/hyperedge_list.hpp

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "hgl/types/types.hpp"
88

99
#include <algorithm>
10+
#include <ranges>
1011
#include <vector>
1112

1213
#ifdef HGL_TESTING
@@ -45,11 +46,36 @@ class undirected_hyperedge_list final {
4546

4647
// --- vertex methods ---
4748

48-
gl_attr_force_inline void add_vertices(const types::size_type) const noexcept {}
49+
// clang-format off
50+
51+
gl_attr_force_inline void add_vertices([[maybe_unused]] const types::size_type) const noexcept {}
52+
53+
// clang-format on
4954

5055
void remove_vertex(const types::id_type vertex_id) noexcept {
51-
for (auto& hyperedge_vertices : this->_storage)
52-
this->_unbind_impl(hyperedge_vertices, vertex_id);
56+
for (auto& hyperedge : this->_storage) {
57+
const auto vertex_it = std::ranges::lower_bound(hyperedge, vertex_id);
58+
if (vertex_it != hyperedge.end() and *vertex_it == vertex_id)
59+
return hyperedge.erase(vertex_it);
60+
while (vertex_it != hyperedge.end())
61+
--(*vertex_it++); // decrement ids > vertex_id
62+
}
63+
}
64+
65+
[[nodiscard]] gl_attr_force_inline auto incident_hyperedges(const types::id_type vertex_id
66+
) const noexcept {
67+
return std::views::iota(0uz, this->_storage.size())
68+
| std::views::filter([this, vertex_id](types::id_type hid) {
69+
return this->_are_bound_impl(this->_storage[hid], vertex_id);
70+
});
71+
}
72+
73+
[[nodiscard]] types::size_type degree(const types::id_type vertex_id) const noexcept {
74+
types::size_type deg = 0uz;
75+
for (const auto& hyperedge : this->_storage)
76+
if (this->_are_bound_impl(hyperedge, vertex_id))
77+
++deg;
78+
return deg;
5379
}
5480

5581
// --- hyperedge methods ---
@@ -62,52 +88,52 @@ class undirected_hyperedge_list final {
6288
this->_storage.erase(this->_storage.begin() + hyperedge_id);
6389
}
6490

65-
[[nodiscard]] gl_attr_force_inline types::size_type hyperedge_size(
66-
const types::id_type hyperedge_id
91+
[[nodiscard]] gl_attr_force_inline auto incident_vertices(const types::id_type hyperedge_id
6792
) const noexcept {
68-
return this->_storage[hyperedge_id].size();
93+
return std::views::all(this->_storage[hyperedge_id]);
6994
}
7095

71-
[[nodiscard]] gl_attr_force_inline auto hyperedge_vertices(const types::id_type hyperedge_id
96+
[[nodiscard]] gl_attr_force_inline types::size_type hyperedge_size(
97+
const types::id_type hyperedge_id
7298
) const noexcept {
73-
return std::views::all(this->_storage[hyperedge_id]);
99+
return this->_storage[hyperedge_id].size();
74100
}
75101

76102
// --- binding methods ---
77103

78-
void bind(const types::id_type hyperedge_id, const types::id_type vertex_id) noexcept {
79-
auto& hyperedge_vertices = this->_storage[hyperedge_id];
104+
void bind(const types::id_type vertex_id, const types::id_type hyperedge_id) noexcept {
105+
auto& hyperedge = this->_storage[hyperedge_id];
80106

81107
// insert the id at the correct position to keep the vertex-id collection sorted
82-
const auto it = std::ranges::lower_bound(hyperedge_vertices, vertex_id);
83-
if (it == hyperedge_vertices.end() or *it != vertex_id)
84-
hyperedge_vertices.insert(it, vertex_id);
108+
const auto it = std::ranges::lower_bound(hyperedge, vertex_id);
109+
if (it == hyperedge.end() or *it != vertex_id)
110+
hyperedge.insert(it, vertex_id);
85111
}
86112

87113
gl_attr_force_inline void unbind(
88-
const types::id_type hyperedge_id, const types::id_type vertex_id
114+
const types::id_type vertex_id, const types::id_type hyperedge_id
89115
) noexcept {
90-
this->_unbind_impl(this->_storage[hyperedge_id], vertex_id);
116+
const auto vertex_it = std::ranges::lower_bound(hyperedge, vertex_id);
117+
if (vertex_it != hyperedge.end() and *vertex_it == vertex_id)
118+
hyperedge.erase(vertex_it);
91119
}
92120

93-
[[nodiscard]] bool are_bound(const types::id_type hyperedge_id, const types::id_type vertex_id)
94-
const noexcept {
95-
auto& hyperedge_vertices = this->_storage[hyperedge_id];
96-
const auto vertex_it = std::ranges::lower_bound(hyperedge_vertices, vertex_id);
97-
return vertex_it != hyperedge_vertices.end() and *vertex_it == vertex_id;
121+
[[nodiscard]] gl_attr_force_inline bool are_bound(
122+
const types::id_type vertex_id, const types::id_type hyperedge_id
123+
) const noexcept {
124+
return this->_are_bound_impl(this->_storage[hyperedge_id], vertex_id);
98125
}
99126

100127
#ifdef HGL_TESTING
101128
friend struct hgl_testing::test_hyperedge_list;
102129
#endif
103130

104131
private:
105-
void _unbind_impl(
106-
hyperedge_storage_type& hyperedge_vertices, const types::id_type vertex_id
107-
) noexcept {
108-
const auto vertex_it = std::ranges::lower_bound(hyperedge_vertices, vertex_id);
109-
if (vertex_it != hyperedge_vertices.end() and *vertex_it == vertex_id)
110-
hyperedge_vertices.erase(vertex_it);
132+
[[nodiscard]] bool _are_bound_impl(
133+
const hyperedge_storage_type& hyperedge, const types::id_type vertex_id
134+
) const noexcept {
135+
const auto vertex_it = std::ranges::lower_bound(hyperedge, vertex_id);
136+
return vertex_it != hyperedge.end() and *vertex_it == vertex_id;
111137
}
112138

113139
hypergraph_storage_type _storage;

include/hgl/impl/impl_tags.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ namespace impl {
1212

1313
struct hyperedge_list_t {};
1414

15-
struct adjacency_list_t {};
15+
struct vertex_list_t {};
1616

1717
struct incidence_matrix_t {};
1818

@@ -22,7 +22,7 @@ namespace type_traits {
2222

2323
template <typename T>
2424
concept c_hypergraph_impl_tag =
25-
c_one_of<T, impl::hyperedge_list_t, impl::adjacency_list_t, impl::incidence_matrix_t>;
25+
c_one_of<T, impl::hyperedge_list_t, impl::vertex_list_t, impl::incidence_matrix_t>;
2626

2727
} // namespace type_traits
2828

tests/source/hgl/test_hyperedge_list.cpp

Lines changed: 110 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -58,100 +58,182 @@ TEST_CASE_FIXTURE(
5858
CHECK_EQ(storage(sut).size(), constants::n_hyperedges - 1uz);
5959
}
6060

61-
TEST_CASE_FIXTURE(test_undirected_hyperedge_list, "hyperedge_size should return 0 by default") {
61+
TEST_CASE_FIXTURE(
62+
test_undirected_hyperedge_list, "incident_vertices should remove an empty view by default"
63+
) {
6264
sut_type sut{constants::n_vertices, constants::n_hyperedges};
6365
CHECK(std::ranges::all_of(constants::hyperedge_ids_view, [&sut](const auto hyperedge_id) {
64-
return sut.hyperedge_size(hyperedge_id) == 0uz;
66+
return std::ranges::empty(sut.incident_vertices(hyperedge_id));
6567
}));
6668
}
6769

68-
TEST_CASE_FIXTURE(
69-
test_undirected_hyperedge_list, "hyperedge_vertices should remove an empty view by default"
70-
) {
70+
TEST_CASE_FIXTURE(test_undirected_hyperedge_list, "hyperedge_size should return 0 by default") {
7171
sut_type sut{constants::n_vertices, constants::n_hyperedges};
7272
CHECK(std::ranges::all_of(constants::hyperedge_ids_view, [&sut](const auto hyperedge_id) {
73-
return std::ranges::empty(sut.hyperedge_vertices(hyperedge_id));
73+
return sut.hyperedge_size(hyperedge_id) == 0uz;
7474
}));
7575
}
7676

7777
TEST_CASE_FIXTURE(
7878
test_undirected_hyperedge_list,
79-
"bind(hyperedge, vertex) should add the vertex to the given hyperedge's storage only if the "
80-
"they are not bound"
79+
"bind should add the vertex to the given hyperedge's storage only if the they are not bound"
8180
) {
8281
sut_type sut{constants::n_vertices, constants::n_hyperedges};
83-
REQUIRE(std::ranges::empty(sut.hyperedge_vertices(constants::id1)));
82+
REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1)));
8483

8584
sut.bind(constants::id1, constants::id1);
86-
const auto vertices1 = sut.hyperedge_vertices(constants::id1) | std::ranges::to<std::vector>();
85+
const auto vertices1 = sut.incident_vertices(constants::id1) | std::ranges::to<std::vector>();
8786
CHECK_EQ(sut.hyperedge_size(constants::id1), 1uz);
8887
CHECK_EQ(std::ranges::size(vertices1), 1uz);
8988
CHECK(std::ranges::contains(vertices1, constants::id1));
9089
CHECK(std::ranges::equal(vertices1, storage(sut)[constants::id1]));
9190

9291
sut.bind(constants::id1, constants::id1);
93-
const auto vertices2 = sut.hyperedge_vertices(constants::id1) | std::ranges::to<std::vector>();
92+
const auto vertices2 = sut.incident_vertices(constants::id1) | std::ranges::to<std::vector>();
9493
CHECK_EQ(std::ranges::size(vertices2), 1uz);
9594
CHECK(std::ranges::equal(vertices2, vertices1));
9695
}
9796

9897
TEST_CASE_FIXTURE(
9998
test_undirected_hyperedge_list,
100-
"unbind(hyperedge, vertex) should remove the vertex from the given hyperedge's storage only if "
101-
"the they are bound"
99+
"unbind should remove the vertex from the given hyperedge's storage only if the they are bound"
102100
) {
103101
sut_type sut{constants::n_vertices, constants::n_hyperedges};
104-
REQUIRE(std::ranges::empty(sut.hyperedge_vertices(constants::id1)));
102+
REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1)));
105103

106104
sut.bind(constants::id1, constants::id1);
107-
const auto vertices1 = sut.hyperedge_vertices(constants::id1) | std::ranges::to<std::vector>();
105+
const auto vertices1 = sut.incident_vertices(constants::id1) | std::ranges::to<std::vector>();
108106
REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz);
109107
REQUIRE(std::ranges::contains(vertices1, constants::id1));
110108

111109
sut.unbind(constants::id1, constants::id2);
112-
const auto vertices2 = sut.hyperedge_vertices(constants::id1) | std::ranges::to<std::vector>();
110+
const auto vertices2 = sut.incident_vertices(constants::id1) | std::ranges::to<std::vector>();
113111
REQUIRE_EQ(std::ranges::size(vertices2), 1uz);
114112
REQUIRE(std::ranges::equal(vertices2, vertices1));
115113

116114
sut.unbind(constants::id1, constants::id1);
117-
CHECK(std::ranges::empty(sut.hyperedge_vertices(constants::id1)));
115+
CHECK(std::ranges::empty(sut.incident_vertices(constants::id1)));
118116
}
119117

120118
TEST_CASE_FIXTURE(
121119
test_undirected_hyperedge_list,
122-
"are_bound(hyperedge, vertex) should return true only when the given vertex is present in the "
123-
"hyperedge's storage"
120+
"are_bound should return true only when the given vertex is present in the hyperedge's storage"
124121
) {
125122
sut_type sut{constants::n_vertices, constants::n_hyperedges};
126-
REQUIRE(std::ranges::empty(sut.hyperedge_vertices(constants::id1)));
123+
REQUIRE(std::ranges::empty(sut.incident_vertices(constants::id1)));
127124

128125
sut.bind(constants::id1, constants::id1);
129-
const auto vertices1 = sut.hyperedge_vertices(constants::id1) | std::ranges::to<std::vector>();
130126
REQUIRE_EQ(sut.hyperedge_size(constants::id1), 1uz);
131-
REQUIRE(std::ranges::contains(vertices1, constants::id1));
127+
REQUIRE(std::ranges::contains(sut.incident_vertices(constants::id1), constants::id1));
132128

133129
CHECK(sut.are_bound(constants::id1, constants::id1));
134130
CHECK_FALSE(sut.are_bound(constants::id1, constants::id2));
135131
CHECK_FALSE(sut.are_bound(constants::id2, constants::id1));
136132
}
137133

134+
TEST_CASE_FIXTURE(test_undirected_hyperedge_list, "add_vertices should do nothing") {
135+
sut_type sut{};
136+
sut.add_vertices(constants::n_vertices);
137+
CHECK(storage(sut).empty());
138+
}
139+
140+
TEST_CASE_FIXTURE(
141+
test_undirected_hyperedge_list,
142+
"remove_vertex should properly unbind (if necessary) the given vertex and align ids > vertex_id"
143+
) {
144+
constexpr hgl::types::size_type n_vertices = 4uz, n_hyperedges = 1uz;
145+
constexpr hgl::types::id_type hyperedge_id = constants::id1;
146+
147+
sut_type sut{n_vertices, n_hyperedges};
148+
sut.bind(constants::id2, hyperedge_id);
149+
sut.bind(constants::id4, hyperedge_id);
150+
151+
hgl::types::id_type rem_vid;
152+
std::vector<hgl::types::id_type> expected_vertices;
153+
154+
SUBCASE("not present vertex < first incident vertex") {
155+
rem_vid = constants::id1;
156+
expected_vertices = {constants::id2 - 1uz, constants::id4 - 1uz};
157+
}
158+
159+
SUBCASE("present vertex = first incident vertex") {
160+
rem_vid = constants::id2;
161+
expected_vertices = {constants::id4 - 1uz};
162+
}
163+
164+
SUBCASE("not present vertex > first incident vertex") {
165+
rem_vid = constants::id3;
166+
expected_vertices = {constants::id2, constants::id4 - 1uz};
167+
}
168+
169+
SUBCASE("present vertex = last incident vertex") {
170+
rem_vid = constants::id4;
171+
expected_vertices = {constants::id2};
172+
}
173+
174+
SUBCASE("not present vertex > last incident vertex") {
175+
rem_vid = constants::id4 + 1uz;
176+
expected_vertices = {constants::id2, constants::id4};
177+
}
178+
179+
CAPTURE(rem_vid);
180+
CAPTURE(expected_vertices);
181+
182+
sut.remove_vertex(rem_vid);
183+
CHECK(std::ranges::equal(sut.incident_vertices(hyperedge_id), expected_vertices));
184+
}
185+
138186
TEST_CASE_FIXTURE(
139187
test_undirected_hyperedge_list,
140-
"remove_vertex should unbind the given vertex from all hyperedges"
188+
"remove_vertex should unbind the given vertex from all hyperedges and align ids > vertex_id"
141189
) {
142190
sut_type sut{constants::n_vertices, constants::n_hyperedges};
143-
for (const auto hyperedge_id : constants::hyperedge_ids_view)
144-
sut.bind(hyperedge_id, constants::id1);
191+
for (const auto vid : constants::vertex_ids_view)
192+
for (const auto heid : constants::hyperedge_ids_view)
193+
sut.bind(vid, heid);
194+
195+
constexpr auto vertex_id = constants::id1;
145196
REQUIRE(std::ranges::all_of(constants::hyperedge_ids_view, [&sut](const auto hyperedge_id) {
146-
return sut.are_bound(hyperedge_id, constants::id1);
197+
return sut.are_bound(vertex_id, hyperedge_id);
147198
}));
148199

149-
sut.remove_vertex(constants::id1);
200+
sut.remove_vertex(vertex_id);
150201
CHECK(std::ranges::all_of(constants::hyperedge_ids_view, [&sut](const auto hyperedge_id) {
151-
return std::ranges::empty(sut.hyperedge_vertices(hyperedge_id));
202+
return std::ranges::equal(
203+
sut.incident_vertices(hyperedge_id),
204+
std::views::iota(constants::id1, constants::n_vertices - 1uz)
205+
);
152206
}));
153207
}
154208

209+
TEST_CASE_FIXTURE(
210+
test_undirected_hyperedge_list,
211+
"incident_hyperedges should return a view of the vertex's incident hyperedge ids"
212+
) {
213+
sut_type sut{constants::n_vertices, constants::n_hyperedges};
214+
215+
constexpr auto vertex_id = constants::id1;
216+
REQUIRE(std::ranges::empty(sut.incident_hyperedges(vertex_id)));
217+
218+
for (const auto hyperedge_id : constants::hyperedge_ids_view)
219+
sut.bind(vertex_id, hyperedge_id);
220+
CHECK(std::ranges::equal(sut.incident_hyperedges(vertex_id), constants::hyperedge_ids_view));
221+
}
222+
223+
TEST_CASE_FIXTURE(
224+
test_undirected_hyperedge_list,
225+
"degree should return the number of the vertex's incident hyperedges"
226+
) {
227+
sut_type sut{constants::n_vertices, constants::n_hyperedges};
228+
229+
constexpr auto vertex_id = constants::id1;
230+
REQUIRE_EQ(sut.degree(vertex_id), 0uz);
231+
232+
for (const auto hyperedge_id : constants::hyperedge_ids_view)
233+
sut.bind(vertex_id, hyperedge_id);
234+
CHECK_EQ(sut.degree(vertex_id), constants::n_hyperedges);
235+
}
236+
155237
TEST_SUITE_END(); // test_hyperedge_list
156238

157239
} // namespace hgl_testing

0 commit comments

Comments
 (0)