diff --git a/CMakeLists.txt b/CMakeLists.txt index 56f17c5b..30adff52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1014,6 +1014,7 @@ SET (hs_compile_SRCS src/util/graph.h src/util/graph_range.h src/util/graph_small_color_map.h + src/util/graph_undirected.h src/util/hash.h src/util/hash_dynamic_bitset.h src/util/insertion_ordered.h diff --git a/src/nfagraph/ng_calc_components.cpp b/src/nfagraph/ng_calc_components.cpp index 65574b50..3e9454ee 100644 --- a/src/nfagraph/ng_calc_components.cpp +++ b/src/nfagraph/ng_calc_components.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2017, Intel Corporation + * Copyright (c) 2015-2018, Intel Corporation * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -53,11 +53,11 @@ #include "ng_depth.h" #include "ng_holder.h" #include "ng_prune.h" -#include "ng_undirected.h" #include "ng_util.h" #include "grey.h" #include "ue2common.h" #include "util/graph_range.h" +#include "util/graph_undirected.h" #include "util/make_unique.h" #include @@ -310,28 +310,19 @@ void splitIntoComponents(unique_ptr g, return; } - unordered_map old2new; - auto ug = createUnGraph(*g, true, true, old2new); + auto ug = make_undirected_graph(*g); - // Construct reverse mapping. - unordered_map new2old; - for (const auto &m : old2new) { - new2old.emplace(m.second, m.first); - } + // Filter specials and shell vertices from undirected graph. + unordered_set bad_vertices( + {g->start, g->startDs, g->accept, g->acceptEod}); + bad_vertices.insert(head_shell.begin(), head_shell.end()); + bad_vertices.insert(tail_shell.begin(), tail_shell.end()); - // Filter shell vertices from undirected graph. - unordered_set shell_undir_vertices; - for (auto v : head_shell) { - shell_undir_vertices.insert(old2new.at(v)); - } - for (auto v : tail_shell) { - shell_undir_vertices.insert(old2new.at(v)); - } auto filtered_ug = boost::make_filtered_graph( - ug, boost::keep_all(), make_bad_vertex_filter(&shell_undir_vertices)); + ug, boost::keep_all(), make_bad_vertex_filter(&bad_vertices)); // Actually run the connected components algorithm. - map split_components; + map split_components; const u32 num = connected_components( filtered_ug, boost::make_assoc_property_map(split_components)); @@ -348,10 +339,8 @@ void splitIntoComponents(unique_ptr g, // Collect vertex lists per component. for (const auto &m : split_components) { - NFAUndirectedVertex uv = m.first; + NFAVertex v = m.first; u32 c = m.second; - assert(contains(new2old, uv)); - NFAVertex v = new2old.at(uv); verts[c].push_back(v); DEBUG_PRINTF("vertex %zu is in comp %u\n", (*g)[v].index, c); } diff --git a/src/util/graph_undirected.h b/src/util/graph_undirected.h new file mode 100644 index 00000000..049964ab --- /dev/null +++ b/src/util/graph_undirected.h @@ -0,0 +1,501 @@ +/* + * Copyright (c) 2018, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * \brief Adaptor that presents an undirected view of a bidirectional BGL graph. + * + * Analogous to the reverse_graph adapter. You can construct one of these for + * bidirectional graph g with: + * + * auto ug = make_undirected_graph(g); + * + * The vertex descriptor type is the same as that of the underlying graph, but + * the edge descriptor is different. + */ + +#ifndef GRAPH_UNDIRECTED_H +#define GRAPH_UNDIRECTED_H + +#include "util/operators.h" + +#include +#include +#include +#include + +#include +#include + +namespace ue2 { + +struct undirected_graph_tag {}; + +template +class undirected_graph; + +namespace undirected_detail { + +template +class undirected_graph_edge_descriptor + : totally_ordered> { + using base_graph_type = BidirectionalGraph; + using base_graph_traits = typename boost::graph_traits; + using base_edge_type = typename base_graph_traits::edge_descriptor; + using base_vertex_type = typename base_graph_traits::vertex_descriptor; + + base_edge_type underlying_edge; + const base_graph_type *g; + bool reverse; // if true, reverse vertices in source() and target() + + inline std::pair + canonical_edge() const { + auto u = std::min(source(underlying_edge, *g), + target(underlying_edge, *g)); + auto v = std::max(source(underlying_edge, *g), + target(underlying_edge, *g)); + return std::make_pair(u, v); + } + + template + friend class ::ue2::undirected_graph; + +public: + undirected_graph_edge_descriptor() = default; + + undirected_graph_edge_descriptor(base_edge_type edge, + const base_graph_type &g_in, + bool reverse_in) + : underlying_edge(std::move(edge)), g(&g_in), reverse(reverse_in) {} + + bool operator==(const undirected_graph_edge_descriptor &other) const { + return canonical_edge() == other.canonical_edge(); + } + + bool operator<(const undirected_graph_edge_descriptor &other) const { + return canonical_edge() < other.canonical_edge(); + } + + base_vertex_type get_source() const { + return reverse ? target(underlying_edge, *g) + : source(underlying_edge, *g); + } + + base_vertex_type get_target() const { + return reverse ? source(underlying_edge, *g) + : target(underlying_edge, *g); + } +}; + +} // namespace undirected_detail + +template +class undirected_graph { +private: + using Self = undirected_graph; + using Traits = boost::graph_traits; + +public: + using base_type = BidirectionalGraph; + using base_ref_type = GraphRef; + + explicit undirected_graph(GraphRef g_in) : g(g_in) {} + + // Graph requirements + using vertex_descriptor = typename Traits::vertex_descriptor; + using edge_descriptor = + undirected_detail::undirected_graph_edge_descriptor; + using directed_category = boost::undirected_tag; + using edge_parallel_category = boost::disallow_parallel_edge_tag; + using traversal_category = typename Traits::traversal_category; + + // IncidenceGraph requirements + + /** + * \brief Templated iterator used for out_edge_iterator and + * in_edge_iterator, depending on the value of Reverse. + */ + template + class adj_edge_iterator + : public boost::iterator_facade< + adj_edge_iterator, edge_descriptor, + boost::forward_traversal_tag, edge_descriptor> { + vertex_descriptor u; + const base_type *g; + typename Traits::in_edge_iterator in_it; + typename Traits::out_edge_iterator out_it; + bool done_in = false; + public: + adj_edge_iterator() = default; + + adj_edge_iterator(vertex_descriptor u_in, const base_type &g_in, + bool end_iter) + : u(std::move(u_in)), g(&g_in) { + auto pi = in_edges(u, *g); + auto po = out_edges(u, *g); + if (end_iter) { + in_it = pi.second; + out_it = po.second; + done_in = true; + } else { + in_it = pi.first; + out_it = po.first; + if (in_it == pi.second) { + done_in = true; + find_first_valid_out(); + } + } + } + + private: + friend class boost::iterator_core_access; + + void find_first_valid_out() { + auto out_end = out_edges(u, *g).second; + for (; out_it != out_end; ++out_it) { + auto v = target(*out_it, *g); + if (!edge(v, u, *g).second) { + break; + } + } + } + + void increment() { + if (!done_in) { + auto in_end = in_edges(u, *g).second; + assert(in_it != in_end); + ++in_it; + if (in_it == in_end) { + done_in = true; + find_first_valid_out(); + } + } else { + ++out_it; + find_first_valid_out(); + } + } + bool equal(const adj_edge_iterator &other) const { + return in_it == other.in_it && out_it == other.out_it; + } + edge_descriptor dereference() const { + if (done_in) { + return edge_descriptor(*out_it, *g, Reverse); + } else { + return edge_descriptor(*in_it, *g, !Reverse); + } + } + }; + + using out_edge_iterator = adj_edge_iterator; + using in_edge_iterator = adj_edge_iterator; + + using degree_size_type = typename Traits::degree_size_type; + + // AdjacencyGraph requirements + using adjacency_iterator = + typename boost::adjacency_iterator_generator::type; + using inv_adjacency_iterator = + typename boost::inv_adjacency_iterator_generator< + Self, vertex_descriptor, in_edge_iterator>::type; + + // VertexListGraph requirements + using vertex_iterator = typename Traits::vertex_iterator; + + // EdgeListGraph requirements + enum { + is_edge_list = std::is_convertible::value + }; + + /** \brief Iterator used for edges(). */ + class edge_iterator + : public boost::iterator_facade { + const base_type *g; + typename Traits::edge_iterator it; + public: + edge_iterator() = default; + + edge_iterator(typename Traits::edge_iterator it_in, + const base_type &g_in) + : g(&g_in), it(std::move(it_in)) { + find_first_valid_edge(); + } + + private: + friend class boost::iterator_core_access; + + void find_first_valid_edge() { + const auto end = edges(*g).second; + for (; it != end; ++it) { + const auto &u = source(*it, *g); + const auto &v = target(*it, *g); + if (!edge(v, u, *g).second) { + break; // No reverse edge, we must visit this one + } + if (u <= v) { + // We have a reverse edge, but we'll return this one (and + // skip the other). Note that (u, u) shouldn't be skipped. + break; + } + } + } + + void increment() { + assert(it != edges(*g).second); + ++it; + find_first_valid_edge(); + } + bool equal(const edge_iterator &other) const { + return it == other.it; + } + edge_descriptor dereference() const { + return edge_descriptor(*it, *g, false); + } + }; + + using vertices_size_type = typename Traits::vertices_size_type; + using edges_size_type = typename Traits::edges_size_type; + + using graph_tag = undirected_graph_tag; + + using vertex_bundle_type = + typename boost::vertex_bundle_type::type; + using edge_bundle_type = typename boost::edge_bundle_type::type; + + vertex_bundle_type &operator[](const vertex_descriptor &d) { + return const_cast(g)[d]; + } + const vertex_bundle_type &operator[](const vertex_descriptor &d) const { + return g[d]; + } + + edge_bundle_type &operator[](const edge_descriptor &d) { + return const_cast(g)[d.underlying_edge]; + } + const edge_bundle_type &operator[](const edge_descriptor &d) const { + return g[d.underlying_edge]; + } + + static vertex_descriptor null_vertex() { return Traits::null_vertex(); } + + // Accessor free functions follow + + friend std::pair + vertices(const undirected_graph &ug) { + return vertices(ug.g); + } + + friend std::pair + edges(const undirected_graph &ug) { + auto e = edges(ug.g); + return std::make_pair(edge_iterator(e.first, ug.g), + edge_iterator(e.second, ug.g)); + } + + friend std::pair + out_edges(const vertex_descriptor &u, const undirected_graph &ug) { + return std::make_pair(out_edge_iterator(u, ug.g, false), + out_edge_iterator(u, ug.g, true)); + } + + friend vertices_size_type num_vertices(const undirected_graph &ug) { + return num_vertices(ug.g); + } + + friend edges_size_type num_edges(const undirected_graph &ug) { + auto p = edges(ug); + return std::distance(p.first, p.second); + } + + friend degree_size_type out_degree(const vertex_descriptor &u, + const undirected_graph &ug) { + return degree(u, ug); + } + + friend vertex_descriptor vertex(vertices_size_type n, + const undirected_graph &ug) { + return vertex(n, ug.g); + } + + friend std::pair edge(const vertex_descriptor &u, + const vertex_descriptor &v, + const undirected_graph &ug) { + auto e = edge(u, v, ug.g); + if (e.second) { + return std::make_pair(edge_descriptor(e.first, ug.g, false), true); + } + auto e_rev = edge(v, u, ug.g); + if (e_rev.second) { + return std::make_pair(edge_descriptor(e_rev.first, ug.g, true), + true); + } + return std::make_pair(edge_descriptor(), false); + } + + friend std::pair + in_edges(const vertex_descriptor &v, const undirected_graph &ug) { + return std::make_pair(in_edge_iterator(v, ug.g, false), + in_edge_iterator(v, ug.g, true)); + } + + friend std::pair + adjacent_vertices(const vertex_descriptor &u, const undirected_graph &ug) { + out_edge_iterator oi, oe; + std::tie(oi, oe) = out_edges(u, ug); + return std::make_pair(adjacency_iterator(oi, &ug), + adjacency_iterator(oe, &ug)); + } + + friend std::pair + inv_adjacent_vertices(const vertex_descriptor &v, + const undirected_graph &ug) { + in_edge_iterator ei, ee; + std::tie(ei, ee) = in_edges(v, ug); + return std::make_pair(inv_adjacency_iterator(ei, &ug), + inv_adjacency_iterator(ee, &ug)); + } + + friend degree_size_type in_degree(const vertex_descriptor &v, + const undirected_graph &ug) { + return degree(v, ug); + } + + friend vertex_descriptor source(const edge_descriptor &e, + const undirected_graph &) { + return e.get_source(); + } + + friend vertex_descriptor target(const edge_descriptor &e, + const undirected_graph &) { + return e.get_target(); + } + + friend degree_size_type degree(const vertex_descriptor &u, + const undirected_graph &ug) { + auto p = out_edges(u, ug); + return std::distance(p.first, p.second); + } + + // Property accessors. + + template + using prop_map = typename boost::property_map; + + template + friend typename prop_map::type + get(Property p, undirected_graph &ug) { + return get(p, ug.g); + } + + template + friend typename prop_map::const_type + get(Property p, const undirected_graph &ug) { + return get(p, ug.g); + } + + template + friend typename boost::property_traits< + typename prop_map::const_type>::value_type + get(Property p, const undirected_graph &ug, const Key &k) { + return get(p, ug.g, get_underlying_descriptor(k)); + } + + template + friend void put(Property p, const undirected_graph &ug, + const Key &k, const Value &val) { + put(p, const_cast(ug.g), + get_underlying_descriptor(k), val); + } + +private: + // Accessors are here because our free friend functions (above) cannot see + // edge_descriptor's private members. + static typename base_type::vertex_descriptor + get_underlying_descriptor(const vertex_descriptor &v) { + return v; + } + static typename base_type::edge_descriptor + get_underlying_descriptor(const edge_descriptor &e) { + return e.underlying_edge; + } + + // Reference to underlying bidirectional graph + GraphRef g; +}; + +template +undirected_graph +make_undirected_graph(const BidirectionalGraph &g) { + return undirected_graph(g); +} + +} // namespace ue2 + +namespace boost { + +/* Derive all the property map specializations from the underlying + * bidirectional graph. */ + +template +struct property_map, + Property> { + using base_map_type = property_map; + using type = typename base_map_type::type; + using const_type = typename base_map_type::const_type; +}; + +template +struct vertex_property_type> + : vertex_property_type {}; + +template +struct edge_property_type> + : edge_property_type {}; + +template +struct graph_property_type> + : graph_property_type {}; + +template +struct vertex_bundle_type> + : vertex_bundle_type {}; + +template +struct edge_bundle_type> + : edge_bundle_type {}; + +template +struct graph_bundle_type> + : graph_bundle_type {}; + +} // namespace boost + +#endif // GRAPH_UNDIRECTED_H diff --git a/unit/CMakeLists.txt b/unit/CMakeLists.txt index 06cddebd..7c39ae90 100644 --- a/unit/CMakeLists.txt +++ b/unit/CMakeLists.txt @@ -77,6 +77,7 @@ set(unit_internal_SOURCES internal/flat_set.cpp internal/flat_map.cpp internal/graph.cpp + internal/graph_undirected.cpp internal/insertion_ordered.cpp internal/lbr.cpp internal/limex_nfa.cpp diff --git a/unit/internal/graph_undirected.cpp b/unit/internal/graph_undirected.cpp new file mode 100644 index 00000000..babc01a6 --- /dev/null +++ b/unit/internal/graph_undirected.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2015-2018, Intel Corporation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Intel Corporation nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "gtest/gtest.h" +#include "util/container.h" +#include "util/graph.h" +#include "util/graph_range.h" +#include "util/graph_undirected.h" +#include "util/ue2_graph.h" + +#include + +using namespace std; +using namespace ue2; + +struct SimpleV { + size_t index; + string test_v = "SimpleV"; +}; + +struct SimpleE { + size_t index; + string test_e = "SimpleE"; +}; + +struct SimpleG : public ue2_graph {}; + +using SimpleVertex = SimpleG::vertex_descriptor; + +template +vector to_indices(const Range &range, const Graph &g) { + vector indices; + for (const auto &elem : range) { + indices.push_back(g[elem].index); + } + sort(indices.begin(), indices.end()); + return indices; +} + +template +vector to_indices(const std::initializer_list &range, + const Graph &g) { + vector indices; + for (const auto &elem : range) { + indices.push_back(g[elem].index); + } + sort(indices.begin(), indices.end()); + return indices; +} + +TEST(graph_undirected, simple_ue2_graph) { + SimpleG g; + auto a = add_vertex(g); + ASSERT_NE(SimpleG::null_vertex(), a); + auto b = add_vertex(g); + ASSERT_NE(SimpleG::null_vertex(), b); + auto c = add_vertex(g); + ASSERT_NE(SimpleG::null_vertex(), c); + + add_edge(a, b, g); + add_edge(b, a, g); + add_edge(a, c, g); + add_edge(c, b, g); + add_edge(c, c, g); + + auto ug = make_undirected_graph(g); + + ASSERT_EQ(3, num_vertices(ug)); + ASSERT_EQ(4, num_edges(ug)); + + // Check adjacencies + + ASSERT_EQ(2, out_degree(a, ug)); + ASSERT_EQ(to_indices({b, c}, ug), + to_indices(adjacent_vertices_range(a, ug), ug)); + + ASSERT_EQ(2, out_degree(b, ug)); + ASSERT_EQ(to_indices({a, c}, ug), + to_indices(adjacent_vertices_range(b, ug), ug)); + + ASSERT_EQ(3, out_degree(c, ug)); + ASSERT_EQ(to_indices({a, b, c}, ug), + to_indices(adjacent_vertices_range(c, ug), ug)); + + ASSERT_EQ(2, in_degree(b, ug)); + ASSERT_EQ(to_indices({a, c}, ug), + to_indices(inv_adjacent_vertices_range(b, ug), ug)); + + // Test reverse edge existence + + ASSERT_TRUE(edge(a, b, ug).second); + ASSERT_TRUE(edge(b, a, ug).second); + ASSERT_TRUE(edge(a, c, ug).second); + ASSERT_TRUE(edge(c, a, ug).second); // (a,c) actually exists + ASSERT_TRUE(edge(b, c, ug).second); // (c,b) actually exists + ASSERT_FALSE(edge(a, a, ug).second); + + // Vertex properties + + g[c].test_v = "vertex c"; + ASSERT_EQ("vertex c", ug[c].test_v); + ASSERT_EQ("vertex c", get(&SimpleV::test_v, ug, c)); + + ug[c].test_v = "vertex c again"; + ASSERT_EQ("vertex c again", g[c].test_v); + ASSERT_EQ("vertex c again", get(&SimpleV::test_v, g, c)); + + put(&SimpleV::test_v, ug, c, "vertex c once more"); + ASSERT_EQ("vertex c once more", g[c].test_v); + + const auto &vprops1 = ug[b]; + ASSERT_EQ(1, vprops1.index); + + const auto &vprops2 = get(boost::vertex_all, ug, b); + ASSERT_EQ(1, vprops2.index); + + // Edge Properties + + auto edge_undirected = edge(a, b, ug).first; + ug[edge_undirected].test_e = "edge (a,b)"; + ASSERT_EQ("edge (a,b)", ug[edge_undirected].test_e); + ASSERT_EQ("edge (a,b)", get(&SimpleE::test_e, ug, edge_undirected)); + + ug[edge_undirected].test_e = "edge (a,b) again"; + put(&SimpleE::test_e, ug, edge_undirected, "edge (a,b) once more"); +} + +TEST(graph_undirected, simple_adjacency_list) { + using AdjListG = + boost::adjacency_list; + + AdjListG g; + auto a = add_vertex(g); + ASSERT_NE(AdjListG::null_vertex(), a); + g[a].index = 0; + auto b = add_vertex(g); + ASSERT_NE(AdjListG::null_vertex(), b); + g[b].index = 1; + auto c = add_vertex(g); + ASSERT_NE(AdjListG::null_vertex(), c); + g[c].index = 2; + + add_edge(a, b, g); + add_edge(b, a, g); + add_edge(a, c, g); + add_edge(c, b, g); + add_edge(c, c, g); + + auto ug = make_undirected_graph(g); + + ASSERT_EQ(3, num_vertices(ug)); + ASSERT_EQ(4, num_edges(ug)); + + // Check adjacencies + + ASSERT_EQ(2, out_degree(a, ug)); + ASSERT_EQ(to_indices({b, c}, ug), + to_indices(adjacent_vertices_range(a, ug), ug)); + + ASSERT_EQ(2, out_degree(b, ug)); + ASSERT_EQ(to_indices({a, c}, ug), + to_indices(adjacent_vertices_range(b, ug), ug)); + + ASSERT_EQ(3, out_degree(c, ug)); + ASSERT_EQ(to_indices({a, b, c}, ug), + to_indices(adjacent_vertices_range(c, ug), ug)); + + ASSERT_EQ(2, in_degree(b, ug)); + ASSERT_EQ(to_indices({a, c}, ug), + to_indices(inv_adjacent_vertices_range(b, ug), ug)); + + // Test reverse edge existence + + ASSERT_TRUE(edge(a, b, ug).second); + ASSERT_TRUE(edge(b, a, ug).second); + ASSERT_TRUE(edge(a, c, ug).second); + ASSERT_TRUE(edge(c, a, ug).second); // (a,c) actually exists + ASSERT_TRUE(edge(b, c, ug).second); // (c,b) actually exists + ASSERT_FALSE(edge(a, a, ug).second); + + // Vertex properties + + g[c].test_v = "vertex c"; + ASSERT_EQ("vertex c", ug[c].test_v); + ASSERT_EQ("vertex c", get(&SimpleV::test_v, ug, c)); + + ug[c].test_v = "vertex c again"; + ASSERT_EQ("vertex c again", g[c].test_v); + ASSERT_EQ("vertex c again", get(&SimpleV::test_v, g, c)); + + put(&SimpleV::test_v, ug, c, "vertex c once more"); + ASSERT_EQ("vertex c once more", g[c].test_v); + + const auto &vprops1 = ug[b]; + ASSERT_EQ(1, vprops1.index); + + const auto &vprops2 = get(boost::vertex_all, ug, b); + ASSERT_EQ(1, vprops2.index); + + // Edge Properties + + auto edge_undirected = edge(a, b, ug).first; + ug[edge_undirected].test_e = "edge (a,b)"; + ASSERT_EQ("edge (a,b)", ug[edge_undirected].test_e); + ASSERT_EQ("edge (a,b)", get(&SimpleE::test_e, ug, edge_undirected)); + + ug[edge_undirected].test_e = "edge (a,b) again"; + put(&SimpleE::test_e, ug, edge_undirected, "edge (a,b) once more"); +}