diff --git a/include/boost/graph/adj_matrix_serialize.hpp b/include/boost/graph/adj_matrix_serialize.hpp new file mode 100644 index 000000000..74a70e02d --- /dev/null +++ b/include/boost/graph/adj_matrix_serialize.hpp @@ -0,0 +1,130 @@ +//======================================================================= +// Copyright 2026 Varun Sharma +// Authors: Varun Sharma +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= +#ifndef BOOST_GRAPH_ADJ_MATRIX_SERIALIZE_HPP +#define BOOST_GRAPH_ADJ_MATRIX_SERIALIZE_HPP + +#include + +#include +#include +#include +#include + +namespace boost +{ + +namespace serialization +{ + + // Disable tracking for adjacency_matrix, which is not polymorphic. This + // serializer writes graph structure and properties by value and rebuilds + // edges on load; it does not preserve graph-object identity. Use this only + // when pointer identity for graph objects need not be preserved. + template < class T > + struct tracking_level_impl; + + template < class Directed, class VP, class EP, class GP, class Allocator > + struct tracking_level_impl< const boost::adjacency_matrix< Directed, VP, EP, GP, Allocator > > + : boost::integral_constant< int, 0 > + { + }; + + template < class Archive, class Directed, class VP, class EP, class GP, class Allocator > + inline void save(Archive& ar, + const boost::adjacency_matrix< Directed, VP, EP, GP, Allocator >& graph, + const unsigned int /* version */) + { + const auto V = boost::num_vertices(graph); + const auto E = boost::num_edges(graph); + + ar << BOOST_NVP(V); + ar << BOOST_NVP(E); + + auto vertex_range = boost::vertices(graph); + for (auto it = vertex_range.first; it != vertex_range.second; ++it) + { + const auto& vertex_property + = boost::get(boost::vertex_all_t(), graph, *it); + ar << BOOST_NVP(vertex_property); + } + + auto edge_range = boost::edges(graph); + for (auto it = edge_range.first; it != edge_range.second; ++it) + { + const auto u = boost::source(*it, graph); + const auto v = boost::target(*it, graph); + const auto& edge_property + = boost::get(boost::edge_all_t(), graph, *it); + + ar << BOOST_NVP(u); + ar << BOOST_NVP(v); + ar << BOOST_NVP(edge_property); + } + + ar << boost::make_nvp( + "graph_property", boost::get_property(graph, boost::graph_all_t())); + } + + template < class Archive, class Directed, class VP, class EP, class GP, class Allocator > + inline void load(Archive& ar, + boost::adjacency_matrix< Directed, VP, EP, GP, Allocator >& graph, + const unsigned int /* version */) + { + using Graph = boost::adjacency_matrix< Directed, VP, EP, GP, Allocator >; + using VertexSize = typename Graph::vertices_size_type; + using EdgeSize = typename Graph::edges_size_type; + using Vertex = typename Graph::vertex_descriptor; + + VertexSize V = 0; + EdgeSize E = 0; + + ar >> BOOST_NVP(V); + ar >> BOOST_NVP(E); + + Graph loaded(V); + + auto vertex_range = boost::vertices(loaded); + for (auto it = vertex_range.first; it != vertex_range.second; ++it) + { + auto& vertex_property + = boost::get(boost::vertex_all_t(), loaded, *it); + ar >> BOOST_NVP(vertex_property); + } + + for (EdgeSize i = 0; i < E; ++i) + { + Vertex u = 0; + Vertex v = 0; + + ar >> BOOST_NVP(u); + ar >> BOOST_NVP(v); + + const auto e = boost::add_edge(u, v, loaded).first; + auto& edge_property = boost::get(boost::edge_all_t(), loaded, e); + ar >> BOOST_NVP(edge_property); + } + + ar >> boost::make_nvp("graph_property", + boost::get_property(loaded, boost::graph_all_t())); + + graph = std::move(loaded); + } + + template < class Archive, class Directed, class VP, class EP, class GP, class Allocator > + inline void serialize(Archive& ar, + boost::adjacency_matrix< Directed, VP, EP, GP, Allocator >& graph, + const unsigned int version) + { + boost::core::split_free(ar, graph, version); + } + +} // namespace serialization +} // namespace boost + +#endif // BOOST_GRAPH_ADJ_MATRIX_SERIALIZE_HPP diff --git a/include/boost/graph/csr_graph_serialize.hpp b/include/boost/graph/csr_graph_serialize.hpp new file mode 100644 index 000000000..44ae0a9eb --- /dev/null +++ b/include/boost/graph/csr_graph_serialize.hpp @@ -0,0 +1,153 @@ +//======================================================================= +// Copyright 2026 Varun Sharma +// Authors: Varun Sharma +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +//======================================================================= +#ifndef BOOST_GRAPH_CSR_GRAPH_SERIALIZE_HPP +#define BOOST_GRAPH_CSR_GRAPH_SERIALIZE_HPP + +#include +#include +#include + +#include +#include +#include +#include + +namespace boost +{ + +namespace serialization +{ + + // This serializer writes graph structure and properties by value and + // rebuilds edges on load; it does not preserve graph-object identity. + template < class T > + struct tracking_level_impl; + + template < class Directed, class VP, class EP, class GP, class Vertex, + class EdgeIndex > + struct tracking_level_impl< const boost::compressed_sparse_row_graph< + Directed, VP, EP, GP, Vertex, EdgeIndex > > + : boost::integral_constant< int, 0 > + { + }; + + template < class Archive, class Directed, class VP, class EP, class GP, + class Vertex, class EdgeIndex > + inline void save(Archive& ar, + const boost::compressed_sparse_row_graph< Directed, VP, EP, GP, Vertex, + EdgeIndex >& graph, + const unsigned int /* version */) + { + const auto V = boost::num_vertices(graph); + const auto E = boost::num_edges(graph); + + ar << BOOST_NVP(V); + ar << BOOST_NVP(E); + + auto vertex_range = boost::vertices(graph); + for (auto it = vertex_range.first; it != vertex_range.second; ++it) + { + const auto& vertex_property + = boost::get(boost::vertex_all_t(), graph, *it); + ar << BOOST_NVP(vertex_property); + } + + auto edge_range = boost::edges(graph); + for (auto it = edge_range.first; it != edge_range.second; ++it) + { + const auto u = boost::source(*it, graph); + const auto v = boost::target(*it, graph); + const auto& edge_property + = boost::get(boost::edge_all_t(), graph, *it); + + ar << BOOST_NVP(u); + ar << BOOST_NVP(v); + ar << BOOST_NVP(edge_property); + } + + ar << boost::make_nvp( + "graph_property", boost::get_property(graph, boost::graph_all_t())); + } + + template < class Archive, class Directed, class VP, class EP, class GP, + class Vertex, class EdgeIndex > + inline void load(Archive& ar, + boost::compressed_sparse_row_graph< Directed, VP, EP, GP, Vertex, + EdgeIndex >& graph, + const unsigned int /* version */) + { + using Graph = boost::compressed_sparse_row_graph< Directed, VP, EP, GP, + Vertex, EdgeIndex >; + using VertexSize = typename Graph::vertices_size_type; + using EdgeSize = typename Graph::edges_size_type; + using VertexDescriptor = typename Graph::vertex_descriptor; + using VertexProperty = typename Graph::vertex_bundled; + using EdgeProperty = typename Graph::edge_bundled; + + VertexSize V = 0; + EdgeSize E = 0; + + ar >> BOOST_NVP(V); + ar >> BOOST_NVP(E); + + std::vector< VertexProperty > vertex_properties( + static_cast< std::size_t >(V)); + for (auto& vertex_property : vertex_properties) + { + ar >> BOOST_NVP(vertex_property); + } + + std::vector< std::pair< VertexDescriptor, VertexDescriptor > > + endpoints; + std::vector< EdgeProperty > edge_properties; + endpoints.reserve(static_cast< std::size_t >(E)); + edge_properties.reserve(static_cast< std::size_t >(E)); + + for (EdgeSize i = 0; i < E; ++i) + { + VertexDescriptor u = 0; + VertexDescriptor v = 0; + EdgeProperty edge_property { }; + + ar >> BOOST_NVP(u); + ar >> BOOST_NVP(v); + ar >> BOOST_NVP(edge_property); + + endpoints.emplace_back(u, v); + edge_properties.push_back(std::move(edge_property)); + } + + Graph rebuilt(boost::edges_are_sorted, endpoints.begin(), + endpoints.end(), edge_properties.begin(), V, E); + for (VertexDescriptor vertex = 0; vertex < V; ++vertex) + { + boost::get(boost::vertex_all_t(), rebuilt, vertex) = std::move( + vertex_properties[static_cast< std::size_t >(vertex)]); + } + + ar >> boost::make_nvp("graph_property", + boost::get_property(rebuilt, boost::graph_all_t())); + + graph = std::move(rebuilt); + } + + template < class Archive, class Directed, class VP, class EP, class GP, + class Vertex, class EdgeIndex > + inline void serialize(Archive& ar, + boost::compressed_sparse_row_graph< Directed, VP, EP, GP, Vertex, + EdgeIndex >& graph, + const unsigned int version) + { + boost::core::split_free(ar, graph, version); + } + +} // namespace serialization +} // namespace boost + +#endif // BOOST_GRAPH_CSR_GRAPH_SERIALIZE_HPP diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 7287f460f..4fe9fd0a8 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -89,6 +89,14 @@ alias graph_test_regular : /boost/serialization//boost_serialization : : : ] + [ run adj_matrix_serialize.cpp + /boost/serialization//boost_serialization + : : : ] + + [ run csr_graph_serialize.cpp + /boost/serialization//boost_serialization + : : : ] + [ compile reverse_graph_cc.cpp ] [ run sequential_vertex_coloring.cpp ] diff --git a/test/adj_matrix_serialize.cpp b/test/adj_matrix_serialize.cpp new file mode 100644 index 000000000..b68acee0a --- /dev/null +++ b/test/adj_matrix_serialize.cpp @@ -0,0 +1,103 @@ +// Copyright 2026 Varun Sharma +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct vertex_properties +{ + std::string name; + + template < class Archive > + void serialize(Archive& ar, const unsigned int /* version */) + { + ar& boost::make_nvp("name", name); + } +}; + +struct edge_properties +{ + int weight; + + template < class Archive > + void serialize(Archive& ar, const unsigned int /* version */) + { + ar& boost::make_nvp("weight", weight); + } +}; + +using DirectedGraph = boost::adjacency_matrix< boost::directedS >; +using UndirectedGraph = boost::adjacency_matrix< boost::undirectedS, + vertex_properties, edge_properties >; + +static_assert(boost::serialization::tracking_level< DirectedGraph >::value + == boost::serialization::track_never, + "adjacency_matrix tracking_level must be track_never"); +static_assert(boost::serialization::tracking_level< UndirectedGraph >::value + == boost::serialization::track_never, + "adjacency_matrix tracking_level must be track_never"); + +int main() +{ + DirectedGraph directed(3); + boost::add_edge(0, 1, directed); + boost::add_edge(1, 2, directed); + + UndirectedGraph undirected(3); + undirected[0].name = "A"; + undirected[1].name = "B"; + undirected[2].name = "C"; + + edge_properties first_edge = { 7 }; + edge_properties second_edge = { 11 }; + boost::add_edge(0, 1, first_edge, undirected); + boost::add_edge(1, 2, second_edge, undirected); + + std::stringstream buffer; + { + boost::archive::xml_oarchive archive(buffer); + archive << boost::make_nvp("directed_graph", directed); + archive << boost::make_nvp("undirected_graph", undirected); + } + + DirectedGraph restored_directed(0); + UndirectedGraph restored_undirected(0); + { + boost::archive::xml_iarchive archive(buffer); + archive >> boost::make_nvp("directed_graph", restored_directed); + archive >> boost::make_nvp("undirected_graph", restored_undirected); + } + + BOOST_TEST(boost::num_vertices(restored_directed) == 3); + BOOST_TEST(boost::num_edges(restored_directed) == 2); + BOOST_TEST(boost::edge(0, 1, restored_directed).second); + BOOST_TEST(boost::edge(1, 2, restored_directed).second); + BOOST_TEST(!boost::edge(1, 0, restored_directed).second); + + BOOST_TEST(boost::num_vertices(restored_undirected) == 3); + BOOST_TEST(boost::num_edges(restored_undirected) == 2); + BOOST_TEST(restored_undirected[0].name == "A"); + BOOST_TEST(restored_undirected[1].name == "B"); + BOOST_TEST(restored_undirected[2].name == "C"); + + const auto first = boost::edge(0, 1, restored_undirected); + const auto second = boost::edge(1, 2, restored_undirected); + BOOST_TEST(first.second); + BOOST_TEST(second.second); + if (first.second) + BOOST_TEST(restored_undirected[first.first].weight == 7); + if (second.second) + BOOST_TEST(restored_undirected[second.first].weight == 11); + + return boost::report_errors(); +} diff --git a/test/csr_graph_serialize.cpp b/test/csr_graph_serialize.cpp new file mode 100644 index 000000000..ad4b925ee --- /dev/null +++ b/test/csr_graph_serialize.cpp @@ -0,0 +1,172 @@ +// Copyright 2026 Varun Sharma +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct vertex_properties +{ + std::string name; + + template < class Archive > + void serialize(Archive& ar, const unsigned int /* version */) + { + ar& boost::make_nvp("name", name); + } +}; + +struct edge_properties +{ + edge_properties() : weight(0) { } + explicit edge_properties(int value) : weight(value) { } + + int weight = 0; + + template < class Archive > + void serialize(Archive& ar, const unsigned int /* version */) + { + ar& boost::make_nvp("weight", weight); + } +}; + +struct graph_properties +{ + std::string name; + + template < class Archive > + void serialize(Archive& ar, const unsigned int /* version */) + { + ar& boost::make_nvp("name", name); + } +}; + +using CSRGraph = boost::compressed_sparse_row_graph< boost::directedS, + vertex_properties, edge_properties, graph_properties >; +using PlainCSRGraph = boost::compressed_sparse_row_graph< boost::directedS >; +using BidirCSRGraph = boost::compressed_sparse_row_graph< boost::bidirectionalS, + vertex_properties, edge_properties, graph_properties >; + +static_assert(boost::serialization::tracking_level< CSRGraph >::value + == boost::serialization::track_never, + "compressed_sparse_row_graph tracking_level must be track_never"); +static_assert(boost::serialization::tracking_level< PlainCSRGraph >::value + == boost::serialization::track_never, + "compressed_sparse_row_graph tracking_level must be track_never"); + +int main() +{ + using Vertex = CSRGraph::vertex_descriptor; + using Edge = std::pair< Vertex, Vertex >; + + std::vector< Edge > endpoints { Edge(0, 1), Edge(0, 2), Edge(2, 1) }; + std::vector< edge_properties > edge_data { edge_properties { 7 }, + edge_properties { 11 }, edge_properties { 13 } }; + std::vector< Edge > plain_endpoints { Edge(0, 1), Edge(1, 2) }; + + CSRGraph graph(boost::edges_are_sorted, endpoints.begin(), endpoints.end(), + edge_data.begin(), 3, endpoints.size()); + BidirCSRGraph bidirectional_graph(boost::edges_are_sorted, + endpoints.begin(), endpoints.end(), edge_data.begin(), 3, + endpoints.size()); + PlainCSRGraph plain_graph(boost::edges_are_sorted, plain_endpoints.begin(), + plain_endpoints.end(), 3); + graph[0].name = "A"; + graph[1].name = "B"; + graph[2].name = "C"; + boost::get_property(graph, boost::graph_all_t()).name = "sample"; + bidirectional_graph[0].name = "A"; + bidirectional_graph[1].name = "B"; + bidirectional_graph[2].name = "C"; + boost::get_property(bidirectional_graph, boost::graph_all_t()).name + = "sample"; + + std::stringstream buffer; + { + boost::archive::xml_oarchive archive(buffer); + archive << boost::make_nvp("graph", graph); + archive << boost::make_nvp("bidirectional_graph", bidirectional_graph); + archive << boost::make_nvp("plain_graph", plain_graph); + } + + CSRGraph restored; + BidirCSRGraph restored_bidirectional; + PlainCSRGraph restored_plain_graph; + { + boost::archive::xml_iarchive archive(buffer); + archive >> boost::make_nvp("graph", restored); + archive >> boost::make_nvp( + "bidirectional_graph", restored_bidirectional); + archive >> boost::make_nvp("plain_graph", restored_plain_graph); + } + + BOOST_TEST(boost::num_vertices(restored) == 3); + BOOST_TEST(boost::num_edges(restored) == 3); + BOOST_TEST(restored[0].name == "A"); + BOOST_TEST(restored[1].name == "B"); + BOOST_TEST(restored[2].name == "C"); + BOOST_TEST( + boost::get_property(restored, boost::graph_all_t()).name == "sample"); + + BOOST_TEST(boost::num_vertices(restored_bidirectional) == 3); + BOOST_TEST(boost::num_edges(restored_bidirectional) == 3); + BOOST_TEST(restored_bidirectional[0].name == "A"); + BOOST_TEST(restored_bidirectional[1].name == "B"); + BOOST_TEST(restored_bidirectional[2].name == "C"); + BOOST_TEST(boost::get_property( + restored_bidirectional, boost::graph_all_t()).name + == "sample"); + + const auto incoming = boost::in_edges(Vertex(1), restored_bidirectional); + BOOST_TEST(boost::in_degree(Vertex(1), restored_bidirectional) == 2); + BOOST_TEST(std::distance(incoming.first, incoming.second) == 2); + + bool found_0_to_1 = false; + bool found_2_to_1 = false; + for (auto it = incoming.first; it != incoming.second; ++it) + { + const Vertex source = boost::source(*it, restored_bidirectional); + const Vertex target = boost::target(*it, restored_bidirectional); + const int weight = restored_bidirectional[*it].weight; + if (source == Vertex(0) && target == Vertex(1) && weight == 7) + found_0_to_1 = true; + else if (source == Vertex(2) && target == Vertex(1) && weight == 13) + found_2_to_1 = true; + } + BOOST_TEST(found_0_to_1); + BOOST_TEST(found_2_to_1); + + const auto e01 = boost::edge(Vertex(0), Vertex(1), restored); + const auto e02 = boost::edge(Vertex(0), Vertex(2), restored); + const auto e21 = boost::edge(Vertex(2), Vertex(1), restored); + BOOST_TEST(e01.second); + BOOST_TEST(e02.second); + BOOST_TEST(e21.second); + if (e01.second) + BOOST_TEST(restored[e01.first].weight == 7); + if (e02.second) + BOOST_TEST(restored[e02.first].weight == 11); + if (e21.second) + BOOST_TEST(restored[e21.first].weight == 13); + + BOOST_TEST(boost::num_vertices(restored_plain_graph) == 3); + BOOST_TEST(boost::num_edges(restored_plain_graph) == 2); + BOOST_TEST( + boost::edge(Vertex(0), Vertex(1), restored_plain_graph).second); + BOOST_TEST( + boost::edge(Vertex(1), Vertex(2), restored_plain_graph).second); + + return boost::report_errors(); +}