|
| 1 | +// Copyright (c) 2026 Arnaud Becheler |
| 2 | +// Distributed under the Boost Software License, Version 1.0. (See |
| 3 | +// accompanying file LICENSE_1_0.txt or copy at |
| 4 | +// http://www.boost.org/LICENSE_1_0.txt) |
| 5 | + |
| 6 | +#include <boost/core/lightweight_test.hpp> |
| 7 | +#include <boost/graph/adjacency_list.hpp> |
| 8 | +#include <boost/graph/subgraph.hpp> |
| 9 | + |
| 10 | +#include <utility> |
| 11 | + |
| 12 | +using namespace boost; |
| 13 | + |
| 14 | +template < typename Directedness > |
| 15 | +using subgraph_of = subgraph< adjacency_list< vecS, vecS, Directedness, |
| 16 | + property< vertex_color_t, int >, |
| 17 | + property< edge_index_t, std::size_t, property< edge_weight_t, int > > > >; |
| 18 | + |
| 19 | +template < typename Directedness > |
| 20 | +void test_descriptor_conversion() |
| 21 | +{ |
| 22 | + using graph_t = subgraph_of< Directedness >; |
| 23 | + using vertex_t = typename graph_traits< graph_t >::vertex_descriptor; |
| 24 | + using edge_t = typename graph_traits< graph_t >::edge_descriptor; |
| 25 | + using vertex_iterator = typename graph_traits< graph_t >::vertex_iterator; |
| 26 | + using edge_iterator = typename graph_traits< graph_t >::edge_iterator; |
| 27 | + |
| 28 | + // root: path 0-1-2-3, with edge indices assigned in insertion order. |
| 29 | + graph_t root(4); |
| 30 | + add_edge(0, 1, root); // edge index 0 |
| 31 | + add_edge(1, 2, root); // edge index 1 |
| 32 | + add_edge(2, 3, root); // edge index 2 |
| 33 | + |
| 34 | + // child g1 = {1, 2, 3}: induces edges (1,2) and (2,3), but not (0,1). |
| 35 | + graph_t& g1 = root.create_subgraph(); |
| 36 | + add_vertex(1, g1); |
| 37 | + add_vertex(2, g1); |
| 38 | + add_vertex(3, g1); |
| 39 | + |
| 40 | + // grandchild g1a = {2, 3}: induces only edge (2,3). |
| 41 | + graph_t& g1a = g1.create_subgraph(); |
| 42 | + add_vertex(2, g1a); |
| 43 | + add_vertex(3, g1a); |
| 44 | + |
| 45 | + // invariant: for an edge in the subgraph, global_to_local(local_to_global(e)) |
| 46 | + // must equal e, and its edge_index must be preserved. |
| 47 | + edge_iterator ei, ei_end; |
| 48 | + for (boost::tie(ei, ei_end) = edges(g1); ei != ei_end; ++ei) |
| 49 | + { |
| 50 | + edge_t e_local = *ei; |
| 51 | + edge_t e_round = g1.global_to_local(g1.local_to_global(e_local)); |
| 52 | + BOOST_TEST(e_round == e_local); |
| 53 | + BOOST_TEST(get(edge_index, g1, e_local) == get(edge_index, g1, e_round)); |
| 54 | + } |
| 55 | + |
| 56 | + // invariant: for a vertex in the subgraph, global_to_local(local_to_global(v)) |
| 57 | + // must equal v, and its vertex_index must be preserved. |
| 58 | + vertex_iterator vi, vi_end; |
| 59 | + for (boost::tie(vi, vi_end) = vertices(g1); vi != vi_end; ++vi) |
| 60 | + { |
| 61 | + vertex_t v_local = *vi; |
| 62 | + vertex_t v_round = g1.global_to_local(g1.local_to_global(v_local)); |
| 63 | + BOOST_TEST(v_round == v_local); |
| 64 | + BOOST_TEST(get(vertex_index, g1, v_local) == get(vertex_index, g1, v_round)); |
| 65 | + } |
| 66 | + |
| 67 | + // invariant: a property written through a child must be readable from the |
| 68 | + // root, because the whole tree shares one property store. |
| 69 | + edge_t some_local = *edges(g1).first; |
| 70 | + put(edge_weight, g1, some_local, 42); |
| 71 | + BOOST_TEST(get(edge_weight, root, g1.local_to_global(some_local)) == 42); |
| 72 | + |
| 73 | + // invariant: find_vertex must report a present vertex as true, and an absent |
| 74 | + // one as false with a null_vertex() descriptor. |
| 75 | + BOOST_TEST(g1.find_vertex(1).second); // vertex 1 is in g1 |
| 76 | + BOOST_TEST(!g1.find_vertex(0).second); // vertex 0 is not in g1 |
| 77 | + BOOST_TEST(g1.find_vertex(0).first == graph_traits< graph_t >::null_vertex()); |
| 78 | + |
| 79 | + // invariant: find_edge must report false with a default descriptor for a |
| 80 | + // root edge that is absent from the subgraph. |
| 81 | + edge_t e01 = edge(0, 1, root).first; |
| 82 | + BOOST_TEST(get(edge_index, root, e01) == 0u); |
| 83 | + BOOST_TEST(g1.find_edge(e01).first == edge_t()); // absent -> default |
| 84 | + BOOST_TEST(!g1.find_edge(e01).second); // absent -> false |
| 85 | + |
| 86 | + // invariant: edge round-trip identity must hold at any nesting depth, |
| 87 | + // not just for direct children. |
| 88 | + for (boost::tie(ei, ei_end) = edges(g1a); ei != ei_end; ++ei) |
| 89 | + { |
| 90 | + edge_t e_local = *ei; |
| 91 | + BOOST_TEST(g1a.global_to_local(g1a.local_to_global(e_local)) == e_local); |
| 92 | + } |
| 93 | + |
| 94 | + // invariant: a property written through a grandchild must reach the root. |
| 95 | + edge_t g1a_local = *edges(g1a).first; |
| 96 | + put(edge_weight, g1a, g1a_local, 7); |
| 97 | + BOOST_TEST(get(edge_weight, root, g1a.local_to_global(g1a_local)) == 7); |
| 98 | + |
| 99 | + // invariant: an edge present in the parent but not the grandchild must |
| 100 | + // report absent from the grandchild. |
| 101 | + edge_t e12 = edge(1, 2, root).first; |
| 102 | + BOOST_TEST(!g1a.find_edge(e12).second); |
| 103 | +} |
| 104 | + |
| 105 | +// idiom: find_edge is the membership query; global_to_local is the transform to |
| 106 | +// run once membership is confirmed. |
| 107 | +template < typename Directedness > |
| 108 | +void test_membership_query_then_convert() |
| 109 | +{ |
| 110 | + using graph_t = subgraph_of< Directedness >; |
| 111 | + using edge_t = typename graph_traits< graph_t >::edge_descriptor; |
| 112 | + |
| 113 | + graph_t root(3); |
| 114 | + add_edge(0, 1, root); // edge index 0 |
| 115 | + add_edge(1, 2, root); // edge index 1 |
| 116 | + |
| 117 | + // sg = {1, 2}: induces edge (1,2), but not edge (0,1). |
| 118 | + graph_t& sg = root.create_subgraph(); |
| 119 | + add_vertex(1, sg); |
| 120 | + add_vertex(2, sg); |
| 121 | + |
| 122 | + // Present edge: the query confirms membership, then the transform agrees |
| 123 | + // with the local descriptor the query returned. |
| 124 | + edge_t e12 = edge(1, 2, root).first; |
| 125 | + std::pair< edge_t, bool > found = sg.find_edge(e12); |
| 126 | + BOOST_TEST(found.second); |
| 127 | + BOOST_TEST(sg.global_to_local(e12) == found.first); |
| 128 | + |
| 129 | + // Absent edge: the query reports it, so the transform is not run. |
| 130 | + edge_t e01 = edge(0, 1, root).first; |
| 131 | + BOOST_TEST(!sg.find_edge(e01).second); |
| 132 | +} |
| 133 | + |
| 134 | +int main() |
| 135 | +{ |
| 136 | + test_descriptor_conversion< directedS >(); |
| 137 | + test_descriptor_conversion< bidirectionalS >(); |
| 138 | + test_membership_query_then_convert< directedS >(); |
| 139 | + test_membership_query_then_convert< bidirectionalS >(); |
| 140 | + return boost::report_errors(); |
| 141 | +} |
0 commit comments