From c542c38cd626adf4e992c439744e27eb595c4527 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 11 Apr 2026 18:14:45 +0200 Subject: [PATCH 01/10] moved graph io files to the io directory --- include/gl/io.hpp | 7 +++++++ include/gl/{graph_file_io.hpp => io/graph_fio.hpp} | 0 include/gl/{graph_io.hpp => io/graph_options.hpp} | 0 include/gl/vertex_descriptor.hpp | 2 +- tests/include/testing/gl/alg_utils.hpp | 2 +- tests/source/gl/test_alg_coloring.cpp | 2 +- tests/source/gl/test_alg_dijkstra.cpp | 2 +- tests/source/gl/test_alg_prim_mst.cpp | 2 +- tests/source/gl/test_alg_topological_sort.cpp | 2 +- tests/source/gl/test_graph_file_io.cpp | 2 +- 10 files changed, 14 insertions(+), 7 deletions(-) rename include/gl/{graph_file_io.hpp => io/graph_fio.hpp} (100%) rename include/gl/{graph_io.hpp => io/graph_options.hpp} (100%) diff --git a/include/gl/io.hpp b/include/gl/io.hpp index fba6d979..d6ad16eb 100644 --- a/include/gl/io.hpp +++ b/include/gl/io.hpp @@ -4,5 +4,12 @@ #pragma once +// clang-format off + #include "gl/io/format.hpp" #include "gl/io/stream_options_manipulator.hpp" + +#include "gl/io/graph_options.hpp" +#include "gl/io/graph_fio.hpp" + +// clang-format on diff --git a/include/gl/graph_file_io.hpp b/include/gl/io/graph_fio.hpp similarity index 100% rename from include/gl/graph_file_io.hpp rename to include/gl/io/graph_fio.hpp diff --git a/include/gl/graph_io.hpp b/include/gl/io/graph_options.hpp similarity index 100% rename from include/gl/graph_io.hpp rename to include/gl/io/graph_options.hpp diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 8363cf17..189aecc4 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -6,7 +6,7 @@ #include "gl/constants.hpp" #include "gl/decl/graph_traits.hpp" -#include "gl/graph_io.hpp" +#include "gl/io/graph_options.hpp" #include "gl/traits.hpp" #include "gl/types/core.hpp" #include "gl/types/properties.hpp" diff --git a/tests/include/testing/gl/alg_utils.hpp b/tests/include/testing/gl/alg_utils.hpp index 59e8217b..1aabf71b 100644 --- a/tests/include/testing/gl/alg_utils.hpp +++ b/tests/include/testing/gl/alg_utils.hpp @@ -6,7 +6,7 @@ #include #include -#include +#include namespace fs = std::filesystem; diff --git a/tests/source/gl/test_alg_coloring.cpp b/tests/source/gl/test_alg_coloring.cpp index 68112d76..0dd88b2c 100644 --- a/tests/source/gl/test_alg_coloring.cpp +++ b/tests/source/gl/test_alg_coloring.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include -#include +#include #include #include diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index 1d73517e..bf249580 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -5,7 +5,7 @@ #include "testing/gl/functional.hpp" #include -#include +#include #include #include diff --git a/tests/source/gl/test_alg_prim_mst.cpp b/tests/source/gl/test_alg_prim_mst.cpp index a3b8b6cf..4d449917 100644 --- a/tests/source/gl/test_alg_prim_mst.cpp +++ b/tests/source/gl/test_alg_prim_mst.cpp @@ -4,7 +4,7 @@ #include "testing/gl/functional.hpp" #include -#include +#include #include #include diff --git a/tests/source/gl/test_alg_topological_sort.cpp b/tests/source/gl/test_alg_topological_sort.cpp index 380112a3..17c013e5 100644 --- a/tests/source/gl/test_alg_topological_sort.cpp +++ b/tests/source/gl/test_alg_topological_sort.cpp @@ -2,7 +2,7 @@ #include "testing/gl/constants.hpp" #include -#include +#include #include #include diff --git a/tests/source/gl/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp index 36a28766..ea3ca2d5 100644 --- a/tests/source/gl/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -3,7 +3,7 @@ #include "testing/gl/io_common.hpp" #include -#include +#include #include #include From 7a00d6b31c01bd932e321526417a95637e533dad Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 11 Apr 2026 19:27:32 +0200 Subject: [PATCH 02/10] wip: gl::io refactor --- include/gl/edge_descriptor.hpp | 17 +++++++---- include/gl/graph.hpp | 13 ++++++--- include/gl/io.hpp | 2 +- include/gl/io/graph_options.hpp | 39 ------------------------- include/gl/io/options.hpp | 50 ++++++++++++++++++++++++++++++++ include/gl/vertex_descriptor.hpp | 20 ++++++------- 6 files changed, 81 insertions(+), 60 deletions(-) delete mode 100644 include/gl/io/graph_options.hpp create mode 100644 include/gl/io/options.hpp diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 54e1be61..d49e1f48 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -7,6 +7,7 @@ #include "gl/constants.hpp" #include "gl/directional_tags.hpp" #include "gl/io/format.hpp" +#include "gl/io/options.hpp" #include "gl/types/core.hpp" #include "gl/vertex_descriptor.hpp" @@ -166,29 +167,33 @@ class edge_descriptor final { private: void _write(std::ostream& os) const { + using io::detail::option_bit; + if constexpr (not traits::c_writable) { this->_write_no_properties(os); return; } else { - if (not io::is_option_set(os, io::graph_option::with_edge_properties)) { + if (not io::is_option_set(os, option_bit::with_connection_properties)) { this->_write_no_properties(os); return; } - if (io::is_option_set(os, io::graph_option::verbose)) { + // TODO: print ID + if (io::is_option_set(os, option_bit::verbose)) os << "[source: " << this->_vertices.first << ", target: " << this->_vertices.second << " | properties: " << this->_properties.get() << "]"; - } - else { + else os << "[" << this->_vertices.first << ", " << this->_vertices.second << " | " << this->_properties.get() << "]"; - } } } void _write_no_properties(std::ostream& os) const { - if (io::is_option_set(os, io::graph_option::verbose)) + using io::detail::option_bit; + + // TODO: print ID + if (io::is_option_set(os, option_bit::verbose)) os << "[source: " << this->_vertices.first << ", target: " << this->_vertices.first << "]"; else diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index ad76c0e4..08feca09 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -7,6 +7,7 @@ #include "gl/constants.hpp" #include "gl/graph_traits.hpp" #include "gl/impl/impl_tags.hpp" +#include "gl/io/options.hpp" #include "gl/io/stream_options_manipulator.hpp" #include "gl/util/ranges.hpp" @@ -630,12 +631,14 @@ class graph final { // --- stream operators --- friend std::ostream& operator<<(std::ostream& os, const graph& g) { - if (io::is_option_set(os, io::graph_option::gsf)) { + using io::detail::option_bit; + + if (io::is_option_set(os, option_bit::specification_fmt)) { g._gsf_write(os); return os; } - if (io::is_option_set(os, io::graph_option::verbose)) + if (io::is_option_set(os, option_bit::verbose)) g._verbose_write(os); else g._concise_write(os); @@ -747,10 +750,12 @@ class graph final { } void _gsf_write(std::ostream& os) const { + using io::detail::option_bit; + const bool with_vertex_properties = - io::is_option_set(os, io::graph_option::with_vertex_properties); + io::is_option_set(os, option_bit::with_vertex_properties); const bool with_edge_properties = - io::is_option_set(os, io::graph_option::with_edge_properties); + io::is_option_set(os, option_bit::with_connection_properties); // print graph size os << std::format( diff --git a/include/gl/io.hpp b/include/gl/io.hpp index d6ad16eb..f895e050 100644 --- a/include/gl/io.hpp +++ b/include/gl/io.hpp @@ -9,7 +9,7 @@ #include "gl/io/format.hpp" #include "gl/io/stream_options_manipulator.hpp" -#include "gl/io/graph_options.hpp" +#include "gl/io/options.hpp" #include "gl/io/graph_fio.hpp" // clang-format on diff --git a/include/gl/io/graph_options.hpp b/include/gl/io/graph_options.hpp deleted file mode 100644 index a6e5e33e..00000000 --- a/include/gl/io/graph_options.hpp +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/io/stream_options_manipulator.hpp" - -namespace gl::io { - -enum class graph_option : bit_position_type { - verbose = 0ul, - with_vertex_properties = 1ul, - with_edge_properties = 2ul, - gsf = 3ul // graph specification format -}; - -inline const stream_options_manipulator verbose = set_option(graph_option::verbose); -inline const stream_options_manipulator concise = unset_option(graph_option::verbose); - -inline const stream_options_manipulator with_vertex_properties = - set_option(graph_option::with_vertex_properties); -inline const stream_options_manipulator without_vertex_properties = - unset_option(graph_option::with_vertex_properties); - -inline const stream_options_manipulator with_edge_properties = - set_option(graph_option::with_edge_properties); -inline const stream_options_manipulator without_edge_properties = - unset_option(graph_option::with_edge_properties); - -inline const stream_options_manipulator with_properties = - set_options({graph_option::with_vertex_properties, graph_option::with_edge_properties}); -inline const stream_options_manipulator without_properties = - unset_options({graph_option::with_vertex_properties, graph_option::with_edge_properties}); - -inline const stream_options_manipulator enable_gsf = set_option(graph_option::gsf); -inline const stream_options_manipulator disable_gsf = unset_option(graph_option::gsf); - -} // namespace gl::io diff --git a/include/gl/io/options.hpp b/include/gl/io/options.hpp new file mode 100644 index 00000000..bc3eaacb --- /dev/null +++ b/include/gl/io/options.hpp @@ -0,0 +1,50 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "gl/io/stream_options_manipulator.hpp" + +namespace gl::io { + +namespace detail { + +enum class option_bit : bit_position_type { + verbose = 0ul, + with_vertex_properties = 1ul, + with_connection_properties = 2ul, + specification_fmt = 3ul +}; + +} // namespace detail + +inline const stream_options_manipulator verbose = set_option(detail::option_bit::verbose); +inline const stream_options_manipulator concise = unset_option(detail::option_bit::verbose); + +inline const stream_options_manipulator with_vertex_properties = + set_option(detail::option_bit::with_vertex_properties); +inline const stream_options_manipulator without_vertex_properties = + unset_option(detail::option_bit::with_vertex_properties); + +inline const stream_options_manipulator with_edge_properties = + set_option(detail::option_bit::with_connection_properties); +inline const stream_options_manipulator without_edge_properties = + unset_option(detail::option_bit::with_connection_properties); + +inline const stream_options_manipulator with_properties = set_options( + {detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties} +); +inline const stream_options_manipulator without_properties = unset_options( + {detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties} +); + +inline const stream_options_manipulator enable_gsf = + set_option(detail::option_bit::specification_fmt); +inline const stream_options_manipulator disable_gsf = + unset_option(detail::option_bit::specification_fmt); + +inline const stream_options_manipulator default_options = + unset_options(~static_cast(0)); + +} // namespace gl::io diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 189aecc4..6c96b6a7 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -6,7 +6,7 @@ #include "gl/constants.hpp" #include "gl/decl/graph_traits.hpp" -#include "gl/io/graph_options.hpp" +#include "gl/io/options.hpp" #include "gl/traits.hpp" #include "gl/types/core.hpp" #include "gl/types/properties.hpp" @@ -99,32 +99,32 @@ class vertex_descriptor final { private: void _write(std::ostream& os) const { + using io::detail::option_bit; + if constexpr (not traits::c_writable) { this->_write_no_properties(os); return; } else { - if (not io::is_option_set(os, io::graph_option::with_vertex_properties)) { + if (not io::is_option_set(os, option_bit::with_vertex_properties)) { this->_write_no_properties(os); return; } - if (io::is_option_set(os, io::graph_option::verbose)) { + if (io::is_option_set(os, option_bit::verbose)) os << "[id: " << this->_id << " | properties: " << this->_properties.get() << "]"; - } - else { + else os << "[" << this->_id << " | " << this->_properties.get() << "]"; - } } } void _write_no_properties(std::ostream& os) const { - if (io::is_option_set(os, io::graph_option::verbose)) { + using io::detail::option_bit; + + if (io::is_option_set(os, option_bit::verbose)) os << std::format("[id: {}]", this->_id); - } - else { + else os << this->_id; - } } id_type _id; From 65da6c71ce59c2989fd86d606e8eaf3baaa15c19 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 11 Apr 2026 21:05:49 +0200 Subject: [PATCH 03/10] vertex and edge ostream operator refinement --- include/gl/edge_descriptor.hpp | 77 +++++++++++++++++++++++--------- include/gl/io/options.hpp | 8 ++-- include/gl/vertex_descriptor.hpp | 29 ++++++------ 3 files changed, 75 insertions(+), 39 deletions(-) diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index d49e1f48..074ca030 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -4,6 +4,7 @@ #pragma once +#include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/directional_tags.hpp" #include "gl/io/format.hpp" @@ -160,44 +161,80 @@ class edge_descriptor final { return this->_properties.get(); } - friend inline std::ostream& operator<<(std::ostream& os, const edge_descriptor& edge) { - edge._write(os); - return os; + friend gl_attr_force_inline std::ostream& operator<<( + std::ostream& os, const edge_descriptor& edge + ) { + return edge._write(os); } private: - void _write(std::ostream& os) const { + std::ostream& _write(std::ostream& os) const + requires std::same_as + { using io::detail::option_bit; if constexpr (not traits::c_writable) { - this->_write_no_properties(os); - return; + return this->_write_no_properties(os); } else { - if (not io::is_option_set(os, option_bit::with_connection_properties)) { - this->_write_no_properties(os); - return; - } + if (not io::is_option_set(os, option_bit::with_connection_properties)) + return this->_write_no_properties(os); - // TODO: print ID if (io::is_option_set(os, option_bit::verbose)) - os << "[source: " << this->_vertices.first << ", target: " << this->_vertices.second - << " | properties: " << this->_properties.get() << "]"; + return os + << "[id: " << this->_id << " | endpoints: {" << this->_vertices.first << ", " + << this->_vertices.second << "} | " << this->_properties.get() << ']'; else - os << "[" << this->_vertices.first << ", " << this->_vertices.second << " | " - << this->_properties.get() << "]"; + return os << '{' << this->_vertices.first << ", " << this->_vertices.second << "}[" + << this->_properties.get() << ']'; } } - void _write_no_properties(std::ostream& os) const { + std::ostream& _write_no_properties(std::ostream& os) const + requires std::same_as + { + using io::detail::option_bit; + + if (io::is_option_set(os, option_bit::verbose)) + return os << "[id: " << this->_id << " | endpoints: {" << this->_vertices.first << ", " + << this->_vertices.second << "}]"; + else + return os << '{' << this->_vertices.first << ", " << this->_vertices.second << '}'; + } + + std::ostream& _write(std::ostream& os) const + requires std::same_as + { + using io::detail::option_bit; + + if constexpr (not traits::c_writable) { + return this->_write_no_properties(os); + } + else { + if (not io::is_option_set(os, option_bit::with_connection_properties)) + return this->_write_no_properties(os); + + if (io::is_option_set(os, option_bit::verbose)) + return os + << "[id: " << this->_id << " | source: " << this->_vertices.first + << ", target: " << this->_vertices.second << " | " << this->_properties.get() + << ']'; + else + return os << '(' << this->_vertices.first << ", " << this->_vertices.second << ")[" + << this->_properties.get() << ']'; + } + } + + std::ostream& _write_no_properties(std::ostream& os) const + requires std::same_as + { using io::detail::option_bit; - // TODO: print ID if (io::is_option_set(os, option_bit::verbose)) - os << "[source: " << this->_vertices.first << ", target: " << this->_vertices.first - << "]"; + return os << "[id: " << this->_id << " | source: " << this->_vertices.first + << ", target: " << this->_vertices.second << ']'; else - os << "[" << this->_vertices.first << ", " << this->_vertices.second << "]"; + return os << '(' << this->_vertices.first << ", " << this->_vertices.second << ')'; } id_type _id; diff --git a/include/gl/io/options.hpp b/include/gl/io/options.hpp index bc3eaacb..1a08a745 100644 --- a/include/gl/io/options.hpp +++ b/include/gl/io/options.hpp @@ -11,10 +11,10 @@ namespace gl::io { namespace detail { enum class option_bit : bit_position_type { - verbose = 0ul, - with_vertex_properties = 1ul, - with_connection_properties = 2ul, - specification_fmt = 3ul + verbose = 0u, + with_vertex_properties = 1u, + with_connection_properties = 2u, + specification_fmt = 3u }; } // namespace detail diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 6c96b6a7..fb8a933a 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -4,6 +4,7 @@ #pragma once +#include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/decl/graph_traits.hpp" #include "gl/io/options.hpp" @@ -92,39 +93,37 @@ class vertex_descriptor final { return this->_properties.get(); } - friend inline std::ostream& operator<<(std::ostream& os, const vertex_descriptor& vertex) { - vertex._write(os); - return os; + friend gl_attr_force_inline std::ostream& operator<<( + std::ostream& os, const vertex_descriptor& vertex + ) { + return vertex._write(os); } private: - void _write(std::ostream& os) const { + std::ostream& _write(std::ostream& os) const { using io::detail::option_bit; if constexpr (not traits::c_writable) { - this->_write_no_properties(os); - return; + return this->_write_no_properties(os); } else { - if (not io::is_option_set(os, option_bit::with_vertex_properties)) { - this->_write_no_properties(os); - return; - } + if (not io::is_option_set(os, option_bit::with_vertex_properties)) + return this->_write_no_properties(os); if (io::is_option_set(os, option_bit::verbose)) - os << "[id: " << this->_id << " | properties: " << this->_properties.get() << "]"; + return os << "[id: " << this->_id << " | " << this->_properties.get() << ']'; else - os << "[" << this->_id << " | " << this->_properties.get() << "]"; + return os << this->_id << '[' << this->_properties.get() << ']'; } } - void _write_no_properties(std::ostream& os) const { + std::ostream& _write_no_properties(std::ostream& os) const { using io::detail::option_bit; if (io::is_option_set(os, option_bit::verbose)) - os << std::format("[id: {}]", this->_id); + return os << std::format("[id: {}]", this->_id); else - os << this->_id; + return os << this->_id; } id_type _id; From 9b48ebbbbb7aa4c93167fb949f5f6090ccad9192 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Sat, 11 Apr 2026 21:59:42 +0200 Subject: [PATCH 04/10] gl io refactor done --- include/gl/edge_descriptor.hpp | 2 + include/gl/graph.hpp | 79 ++++++++++--------- include/gl/io.hpp | 9 +-- include/gl/io/graph_fmt_traits.hpp | 28 +++++++ include/gl/vertex_descriptor.hpp | 3 +- .../gl/test_stream_options_manipulator.cpp | 3 +- 6 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 include/gl/io/graph_fmt_traits.hpp diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 074ca030..8af7d22a 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -12,6 +12,8 @@ #include "gl/types/core.hpp" #include "gl/vertex_descriptor.hpp" +#include + namespace gl { template < diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 08feca09..8de80e11 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -4,9 +4,11 @@ #pragma once +#include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/graph_traits.hpp" #include "gl/impl/impl_tags.hpp" +#include "gl/io/graph_fmt_traits.hpp" #include "gl/io/options.hpp" #include "gl/io/stream_options_manipulator.hpp" #include "gl/util/ranges.hpp" @@ -633,22 +635,17 @@ class graph final { friend std::ostream& operator<<(std::ostream& os, const graph& g) { using io::detail::option_bit; - if (io::is_option_set(os, option_bit::specification_fmt)) { - g._gsf_write(os); - return os; - } + if (io::is_option_set(os, option_bit::specification_fmt)) + return g._gsf_write(os); if (io::is_option_set(os, option_bit::verbose)) - g._verbose_write(os); + return g._verbose_write(os); else - g._concise_write(os); - - return os; + return g._concise_write(os); } - friend inline std::istream& operator>>(std::istream& is, graph& g) { - g._gsf_read(is); - return is; + friend gl_attr_force_inline std::istream& operator>>(std::istream& is, graph& g) { + return g._gsf_read(is); } // --- friend declarations --- @@ -663,6 +660,8 @@ class graph final { friend struct detail::to_impl; private: + using fmt_traits = io::detail::graph_fmt_traits; + graph(const graph& other) : _n_vertices{other._n_vertices}, _n_edges{other._n_edges}, _impl{other._impl} { // Deep copy vertex properties @@ -682,11 +681,7 @@ class graph final { } } - [[nodiscard]] static constexpr std::string _directed_type_str() { - return traits::c_directed_edge ? "directed" : "undirected"; - } - - // --- graph element verification methods --- + // --- element validation --- gl_attr_force_inline void _verify_vertex_id(const id_type vertex_id) const { if (not this->has_vertex(vertex_id)) @@ -704,7 +699,7 @@ class graph final { )); } - // --- vertex methods --- + // --- vertex modifiers --- void _remove_vertex_impl(const id_type vertex_id) { const auto removed_edge_ids = this->_impl.remove_vertex(vertex_id); @@ -721,35 +716,37 @@ class graph final { } } - // --- io methods --- - - void _verbose_write(std::ostream& os) const { - os << std::format( - "type: {}\nnumber of vertices: {}\nnumber of edges: {}\nvertices:\n", - _directed_type_str(), - this->order(), - this->size() - ); + // --- I/O utility --- + std::ostream& _verbose_write(std::ostream& os) const { + os << "type: " << fmt_traits::type << ", |V| = " << this->order() + << ", |E| = " << this->size() << '\n'; for (const auto& vertex : this->vertices()) { - os << "- " << vertex << "\n incident edges:\n"; + os << "- " << vertex << "\n " << fmt_traits::out_edges << ":\n"; for (const auto& edge : this->out_edges(vertex.id())) os << "\t- " << edge << '\n'; } + return os; } - void _concise_write(std::ostream& os) const { - os << std::format("{} {} {}\n", _directed_type_str(), this->order(), this->size()); + std::ostream& _concise_write(std::ostream& os) const { + using io::detail::option_bit; - for (const auto& vertex : this->vertices()) { - os << "- " << vertex << " :"; - for (const auto& edge : this->out_edges(vertex.id())) - os << ' ' << edge; + for (const auto& src : this->vertices()) { + os << src << " :"; + for (const auto& edge : this->out_edges(src.id())) { + os << ' ' << edge.other(src.id()); + if constexpr (traits::c_writable) + if (io::is_option_set(os, option_bit::with_connection_properties)) + os << '[' << edge.properties() << ']'; + } os << '\n'; } + + return os; } - void _gsf_write(std::ostream& os) const { + std::ostream& _gsf_write(std::ostream& os) const { using io::detail::option_bit; const bool with_vertex_properties = @@ -767,12 +764,12 @@ class graph final { static_cast(with_edge_properties) ); - if constexpr (traits::c_writable) + if constexpr (traits::c_writable) if (with_vertex_properties) for (const auto& vertex : this->vertices()) os << vertex.properties() << '\n'; - if constexpr (traits::c_writable) { + if constexpr (traits::c_writable) { if (with_edge_properties) { const auto print_out_edges = [this, &os](const id_type vertex_id) { for (const auto& edge : this->out_edges(vertex_id)) { @@ -786,7 +783,7 @@ class graph final { for (const auto vertex_id : this->vertex_ids()) print_out_edges(vertex_id); - return; + return os; } } @@ -800,16 +797,18 @@ class graph final { for (const auto vertex_id : this->vertex_ids()) print_out_edges(vertex_id); + + return os; } - void _gsf_read(std::istream& is) { + std::istream& _gsf_read(std::istream& is) { bool directed; is >> directed; if (directed != traits::c_directed_edge) throw std::ios_base::failure(std::format( "Invalid graph specification: directional tag does not match - should be {}", - _directed_type_str() + fmt_traits::type )); // read initial graph parameters @@ -866,6 +865,8 @@ class graph final { this->add_edge(source_id, target_id); } } + + return is; } size_type _n_vertices = 0uz; diff --git a/include/gl/io.hpp b/include/gl/io.hpp index f895e050..9ca77d7a 100644 --- a/include/gl/io.hpp +++ b/include/gl/io.hpp @@ -4,12 +4,5 @@ #pragma once -// clang-format off - -#include "gl/io/format.hpp" -#include "gl/io/stream_options_manipulator.hpp" - -#include "gl/io/options.hpp" #include "gl/io/graph_fio.hpp" - -// clang-format on +#include "gl/io/options.hpp" diff --git a/include/gl/io/graph_fmt_traits.hpp b/include/gl/io/graph_fmt_traits.hpp new file mode 100644 index 00000000..7b555ba6 --- /dev/null +++ b/include/gl/io/graph_fmt_traits.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "gl/directional_tags.hpp" + +#include + +namespace gl::io::detail { + +template +struct graph_fmt_traits; + +template <> +struct graph_fmt_traits { + static constexpr std::string_view type = "directed"; + static constexpr std::string_view out_edges = "outgoing edges"; +}; + +template <> +struct graph_fmt_traits { + static constexpr std::string_view type = "undirected"; + static constexpr std::string_view out_edges = "incident edges"; +}; + +} // namespace gl::io::detail diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index fb8a933a..93e792db 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -13,7 +13,6 @@ #include "gl/types/properties.hpp" #include -#include namespace gl { @@ -121,7 +120,7 @@ class vertex_descriptor final { using io::detail::option_bit; if (io::is_option_set(os, option_bit::verbose)) - return os << std::format("[id: {}]", this->_id); + return os << "[id: " << this->_id << ']'; else return os << this->_id; } diff --git a/tests/source/gl/test_stream_options_manipulator.cpp b/tests/source/gl/test_stream_options_manipulator.cpp index 34058819..b31df582 100644 --- a/tests/source/gl/test_stream_options_manipulator.cpp +++ b/tests/source/gl/test_stream_options_manipulator.cpp @@ -1,9 +1,8 @@ +#include "doctest.h" #include "testing/gl/constants.hpp" #include -#include - #include namespace gl_testing { From 15bcfad8897f4cbf363c24a457fa31e683858e2f Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 10:51:22 +0200 Subject: [PATCH 05/10] initial hgl io --- include/gl/edge_descriptor.hpp | 2 + include/gl/graph.hpp | 7 +- include/gl/io/format.hpp | 35 ++++ include/gl/vertex_descriptor.hpp | 1 + include/hgl/hypergraph.hpp | 156 ++++++++++++++++-- include/hgl/hypergraph_elements.hpp | 34 ++++ include/hgl/io.hpp | 67 ++++++++ .../gl/test_stream_options_manipulator.cpp | 12 +- 8 files changed, 290 insertions(+), 24 deletions(-) create mode 100644 include/hgl/io.hpp diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 8af7d22a..aa0ce3c8 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -192,6 +192,7 @@ class edge_descriptor final { } } + // TODO: rm std::ostream& _write_no_properties(std::ostream& os) const requires std::same_as { @@ -227,6 +228,7 @@ class edge_descriptor final { } } + // TODO: rm std::ostream& _write_no_properties(std::ostream& os) const requires std::same_as { diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 8de80e11..58a3f4b4 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -210,14 +210,14 @@ class graph final { // --- vertex getters --- - [[nodiscard]] gl_attr_force_inline auto vertices() const + [[nodiscard]] gl_attr_force_inline auto vertices() const noexcept requires(traits::c_empty_properties) { return this->vertex_ids() | std::views::transform([](const id_type id) { return vertex_descriptor{id}; }); } - [[nodiscard]] gl_attr_force_inline auto vertices() const + [[nodiscard]] gl_attr_force_inline auto vertices() const noexcept requires(traits::c_non_empty_properties) { return this->_vertex_properties | std::views::enumerate @@ -749,12 +749,13 @@ class graph final { std::ostream& _gsf_write(std::ostream& os) const { using io::detail::option_bit; + // TODO: include c_writable + rename to _props const bool with_vertex_properties = io::is_option_set(os, option_bit::with_vertex_properties); const bool with_edge_properties = io::is_option_set(os, option_bit::with_connection_properties); - // print graph size + // print graph metadata os << std::format( "{} {} {} {} {}\n", static_cast(traits::c_directed_edge), diff --git a/include/gl/io/format.hpp b/include/gl/io/format.hpp index c8c39803..1487e22b 100644 --- a/include/gl/io/format.hpp +++ b/include/gl/io/format.hpp @@ -7,14 +7,49 @@ #include "gl/attributes/force_inline.hpp" #include "gl/traits.hpp" +#include +#include + namespace gl::io { +// TODO: add tests +template +struct range_formatter { + const Range& range; + std::string_view sep = ", "; + std::string_view open = "["; + std::string_view close = "]"; + + friend std::ostream& operator<<(std::ostream& os, const range_formatter& formatter) { + os << formatter.open; + bool first = true; + for (const auto& item : formatter.range) { + if (! first) + os << formatter.sep; + os << item; + first = false; + } + os << formatter.close; + return os; + } +}; + +template +range_formatter(const Range&) -> range_formatter; + +template +range_formatter set_formatter(const Range& range, std::string_view sep = ", ") { + return range_formatter{range, sep, "{", "}"}; +} + /* +Is it necessary Custom format functions (casts to types compatible with std::formatter) Not std::formatter overloads to avoid collision with user defined std::formatter overloads */ + template [[nodiscard]] gl_attr_force_inline void* format(T* ptr) { // std::format is not compatible with all types of ptrs diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 93e792db..e84c8243 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -116,6 +116,7 @@ class vertex_descriptor final { } } + // TODO: rm std::ostream& _write_no_properties(std::ostream& os) const { using io::detail::option_bit; diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index e678e5b4..c7a026a3 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -5,11 +5,13 @@ #pragma once #include "gl/attributes/force_inline.hpp" +#include "gl/traits.hpp" #include "gl/types/core.hpp" #include "hgl/constants.hpp" #include "hgl/directional_tags.hpp" #include "hgl/hypergraph_traits.hpp" #include "hgl/impl/impl_tags.hpp" +#include "hgl/io.hpp" #include "hgl/util.hpp" #include @@ -131,7 +133,7 @@ class hypergraph final { // --- vertex methods --- - [[nodiscard]] gl_attr_force_inline auto vertices() noexcept { + [[nodiscard]] gl_attr_force_inline auto vertices() const noexcept { return this->vertex_ids() | std::views::transform(this->_create_vertex_descriptor()); } @@ -258,7 +260,7 @@ class hypergraph final { // --- hyperedge methods --- - [[nodiscard]] gl_attr_force_inline auto hyperedges() noexcept { + [[nodiscard]] gl_attr_force_inline auto hyperedges() const noexcept { return this->hyperedge_ids() | std::views::transform(this->_create_hyperedge_descriptor()); } @@ -517,14 +519,14 @@ class hypergraph final { return this->_impl.degree_map(this->_n_vertices); } - [[nodiscard]] gl_attr_force_inline auto out_hyperedges(const id_type vertex_id) + [[nodiscard]] gl_attr_force_inline auto out_hyperedges(const id_type vertex_id) const requires std::same_as { return this->out_hyperedge_ids(vertex_id) | std::views::transform(this->_create_hyperedge_descriptor()); } - [[nodiscard]] gl_attr_force_inline auto out_hyperedges(const vertex_type& vertex) + [[nodiscard]] gl_attr_force_inline auto out_hyperedges(const vertex_type& vertex) const requires std::same_as { return this->out_hyperedges(vertex.id()); @@ -562,14 +564,14 @@ class hypergraph final { return this->_impl.out_degree_map(this->_n_vertices); } - [[nodiscard]] gl_attr_force_inline auto in_hyperedges(const id_type vertex_id) + [[nodiscard]] gl_attr_force_inline auto in_hyperedges(const id_type vertex_id) const requires std::same_as { return this->in_hyperedge_ids(vertex_id) | std::views::transform(this->_create_hyperedge_descriptor()); } - [[nodiscard]] gl_attr_force_inline auto in_hyperedges(const vertex_type& vertex) + [[nodiscard]] gl_attr_force_inline auto in_hyperedges(const vertex_type& vertex) const requires std::same_as { return this->in_hyperedges(vertex.id()); @@ -607,12 +609,13 @@ class hypergraph final { return this->_impl.in_degree_map(this->_n_vertices); } - [[nodiscard]] auto incident_vertices(const id_type hyperedge_id) { + [[nodiscard]] auto incident_vertices(const id_type hyperedge_id) const { return this->incident_vertex_ids(hyperedge_id) | std::views::transform(this->_create_vertex_descriptor()); } - [[nodiscard]] gl_attr_force_inline auto incident_vertices(const hyperedge_type& hyperedge) { + [[nodiscard]] gl_attr_force_inline auto incident_vertices(const hyperedge_type& hyperedge + ) const { return this->incident_vertices(hyperedge.id()); } @@ -640,14 +643,14 @@ class hypergraph final { return this->_impl.hyperedge_size_map(this->_n_hyperedges); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices(const id_type hyperedge_id) + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const id_type hyperedge_id) const requires std::same_as { return this->tail_vertex_ids(hyperedge_id) | std::views::transform(this->_create_vertex_descriptor()); } - [[nodiscard]] gl_attr_force_inline auto tail_vertices(const hyperedge_type& hyperedge) + [[nodiscard]] gl_attr_force_inline auto tail_vertices(const hyperedge_type& hyperedge) const requires std::same_as { return this->tail_vertices(hyperedge.id()); @@ -685,14 +688,14 @@ class hypergraph final { return this->_impl.tail_size_map(this->_n_hyperedges); } - [[nodiscard]] gl_attr_force_inline auto head_vertices(const id_type hyperedge_id) + [[nodiscard]] gl_attr_force_inline auto head_vertices(const id_type hyperedge_id) const requires std::same_as { return this->head_vertex_ids(hyperedge_id) | std::views::transform(this->_create_vertex_descriptor()); } - [[nodiscard]] gl_attr_force_inline auto head_vertices(const hyperedge_type& hyperedge) + [[nodiscard]] gl_attr_force_inline auto head_vertices(const hyperedge_type& hyperedge) const requires std::same_as { return this->head_vertices(hyperedge.id()); @@ -753,6 +756,80 @@ class hypergraph final { return lhs._impl == rhs._impl; } + // --- I/O utility --- + + struct hyperedge_formatter { + public: + const hypergraph& hg; + const hyperedge_type& hyperedge; + + friend std::ostream& operator<<(std::ostream& os, const hyperedge_formatter& proxy) + requires std::same_as + { + using io::detail::option_bit; + const bool with_props = + io::is_option_set(os, option_bit::with_connection_properties) + and traits::c_writable; + + if (io::is_option_set(os, option_bit::verbose)) { + os << "[id: " << proxy.hyperedge.id() << " | vertices: " + << io::set_formatter(proxy.hg.incident_vertex_ids(proxy.hyperedge.id())); + if (with_props) + os << " | " << proxy.hyperedge.properties(); + os << ']'; + } + else { // concise + os << io::set_formatter(proxy.hg.incident_vertex_ids(proxy.hyperedge.id())); + if (with_props) + os << '[' << proxy.hyperedge.properties() << ']'; + } + + return os; + } + + friend std::ostream& operator<<(std::ostream& os, const hyperedge_formatter& proxy) + requires std::same_as + { + using io::detail::option_bit; + const bool with_props = + io::is_option_set(os, option_bit::with_connection_properties) + and traits::c_writable; + + if (io::is_option_set(os, option_bit::verbose)) { + os << "[id: " << proxy.hyperedge.id() << " | tail: " + << io::set_formatter(proxy.hg.tail_vertex_ids(proxy.hyperedge.id())) + << ", head: " + << io::set_formatter(proxy.hg.head_vertex_ids(proxy.hyperedge.id())); + if (with_props) + os << " | " << proxy.hyperedge.properties(); + os << "]"; + } + else { // concise + os << '(' << io::set_formatter(proxy.hg.tail_vertex_ids(proxy.hyperedge.id())) + << " -> " << io::set_formatter(proxy.hg.head_vertex_ids(proxy.hyperedge.id())) + << ')'; + if (with_props) + os << '[' << proxy.hyperedge.properties() << ']'; + } + + return os; + } + }; + + [[nodiscard]] hyperedge_formatter display(const hyperedge_type& hyperedge) const { + return hyperedge_formatter{*this, hyperedge}; + } + + friend std::ostream& operator<<(std::ostream& os, const hypergraph& hg) { + // if (gl::io::is_option_set(os, gl::io::detail::option_bit::gsf)) + // return hg._gsf_write(os); + + if (gl::io::is_option_set(os, gl::io::detail::option_bit::verbose)) + return hg._verbose_write(os); + + return hg._concise_write(os); + } + // --- friend declarations --- template @@ -824,13 +901,13 @@ class hypergraph final { // --- transformations --- - gl_attr_force_inline auto _create_vertex_descriptor() noexcept + gl_attr_force_inline auto _create_vertex_descriptor() const noexcept requires(traits::c_empty_properties) { return [](const id_type id) { return vertex_type{id}; }; } - gl_attr_force_inline auto _create_vertex_descriptor() noexcept + gl_attr_force_inline auto _create_vertex_descriptor() const noexcept requires(traits::c_non_empty_properties) { return [&pmap = this->_vertex_properties](const id_type id) { @@ -838,13 +915,13 @@ class hypergraph final { }; } - gl_attr_force_inline auto _create_hyperedge_descriptor() noexcept + gl_attr_force_inline auto _create_hyperedge_descriptor() const noexcept requires(traits::c_empty_properties) { return [](const id_type id) { return hyperedge_type{id}; }; } - gl_attr_force_inline auto _create_hyperedge_descriptor() noexcept + gl_attr_force_inline auto _create_hyperedge_descriptor() const noexcept requires(traits::c_non_empty_properties) { return [&pmap = this->_hyperedge_properties](const id_type id) { @@ -852,6 +929,53 @@ class hypergraph final { }; } + // --- I/O utility --- + + std::ostream& _verbose_write(std::ostream& os) const { + using enum io::detail::option_bit; + using fmt_traits = io::detail::hypergraph_fmt_traits; + + os << "type: " << fmt_traits::type << ", |V| = " << this->order() + << ", |E| = " << this->size() << '\n'; + + const bool with_v_props = + io::is_option_set(os, with_vertex_properties) + and traits::c_writable; + if (with_v_props) { + os << "vertices:\n"; + for (const auto& vertex : this->vertices()) + os << " - " << vertex << '\n'; + } + + os << "hyperedges:\n"; + for (const auto& edge : this->hyperedges()) + os << " - " << this->display(edge) << '\n'; + + return os; + } + + std::ostream& _concise_write(std::ostream& os) const { + using enum io::detail::option_bit; + + const bool with_v_props = + io::is_option_set(os, with_vertex_properties) + and traits::c_writable; + if (with_v_props) { + os << "V :"; + for (const auto& vertex : this->vertices()) + os << " " << vertex; + } + else { + os << "|V| = " << this->order(); + } + + os << "\nE:\n"; + for (const auto& edge : this->hyperedges()) + os << this->display(edge) << '\n'; + + return os; + } + // --- data members --- size_type _n_vertices = 0uz; diff --git a/include/hgl/hypergraph_elements.hpp b/include/hgl/hypergraph_elements.hpp index 9e599c03..1aa5ffec 100644 --- a/include/hgl/hypergraph_elements.hpp +++ b/include/hgl/hypergraph_elements.hpp @@ -7,6 +7,7 @@ #include "gl/types/core.hpp" #include "gl/vertex_descriptor.hpp" #include "hgl/constants.hpp" +#include "hgl/io.hpp" #include "hgl/traits.hpp" #include "hgl/types.hpp" @@ -93,7 +94,40 @@ class hyperedge_descriptor final { return this->_properties.get(); } + friend gl_attr_force_inline std::ostream& operator<<( + std::ostream& os, const hyperedge_descriptor& hyperedge + ) { + return hyperedge._write(os); + } + private: + std::ostream& _write(std::ostream& os) const { + using io::detail::option_bit; + + if constexpr (not traits::c_writable) { + return this->_write_no_properties(os); + } + else { + if (not io::is_option_set(os, option_bit::with_connection_properties)) + return this->_write_no_properties(os); + + if (io::is_option_set(os, option_bit::verbose)) + return os << "[id: " << this->_id << " | " << this->_properties.get() << "]"; + else + return os << this->_id << "[" << this->_properties.get() << "]"; + } + } + + // TODO: rm + std::ostream& _write_no_properties(std::ostream& os) const { + using io::detail::option_bit; + + if (io::is_option_set(os, option_bit::verbose)) + return os << "[id: " << this->_id << "]"; + else + return os << this->_id; + } + id_type _id; [[no_unique_address]] std::conditional_t< traits::c_empty_properties, diff --git a/include/hgl/io.hpp b/include/hgl/io.hpp new file mode 100644 index 00000000..303e33fd --- /dev/null +++ b/include/hgl/io.hpp @@ -0,0 +1,67 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "gl/io/format.hpp" +#include "gl/io/options.hpp" +#include "gl/io/stream_options_manipulator.hpp" +#include "hgl/directional_tags.hpp" + +#include + +namespace hgl::io { + +using gl::io::range_formatter; +using gl::io::set_formatter; + +using gl::io::are_options_set; +using gl::io::is_option_set; +using gl::io::set_option; +using gl::io::set_options; +using gl::io::stream_options_manipulator; +using gl::io::unset_option; +using gl::io::unset_options; + +namespace detail { + +using gl::io::detail::get_options_bitmask; +using gl::io::detail::option_bit; + +} // namespace detail + +using gl::io::concise; +using gl::io::verbose; + +using gl::io::with_vertex_properties; +using gl::io::without_vertex_properties; + +inline const stream_options_manipulator with_hyperedge_properties = + set_option(detail::option_bit::with_connection_properties); +inline const stream_options_manipulator without_hyperedge_properties = + unset_option(detail::option_bit::with_connection_properties); + +using gl::io::with_properties; +using gl::io::without_properties; + +using gl::io::default_options; + +namespace detail { + +template +struct hypergraph_fmt_traits; + +template <> +struct hypergraph_fmt_traits { + static constexpr std::string_view type = "BF-directed"; +}; + +template <> +struct hypergraph_fmt_traits { + static constexpr std::string_view type = "undirected"; +}; + +} // namespace detail + +} // namespace hgl::io diff --git a/tests/source/gl/test_stream_options_manipulator.cpp b/tests/source/gl/test_stream_options_manipulator.cpp index b31df582..7f7216db 100644 --- a/tests/source/gl/test_stream_options_manipulator.cpp +++ b/tests/source/gl/test_stream_options_manipulator.cpp @@ -64,11 +64,6 @@ TEST_CASE_FIXTURE( CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); } -enum class BitPositionEnum : gl::io::bit_position_type { - bit_position_1 = test_stream_options_manipulator::bit_position_1, - bit_position_2 = test_stream_options_manipulator::bit_position_2 -}; - TEST_CASE_FIXTURE( test_stream_options_manipulator, "should properly handle istream option operations for bit position lists" @@ -115,6 +110,11 @@ TEST_CASE_FIXTURE( CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); } +enum class BitPositionEnum : gl::io::bit_position_type { + bit_position_1 = test_stream_options_manipulator::bit_position_1, + bit_position_2 = test_stream_options_manipulator::bit_position_2 +}; + TEST_CASE_FIXTURE( test_stream_options_manipulator, "should properly handle istream option operations for enum bit position lists" @@ -161,6 +161,8 @@ TEST_CASE_FIXTURE( CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); } +// TODO: default test + TEST_SUITE_END(); // test_stream_options_manipulator } // namespace gl_testing From 382b44fd74c1739d7e6667de4c96ba06bef44b0c Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 11:09:24 +0200 Subject: [PATCH 06/10] gl io refinment (again, but small this time) --- include/gl/edge_descriptor.hpp | 102 ++++++++++++---------------- include/gl/graph.hpp | 35 +++++++--- include/gl/io/format.hpp | 46 +++---------- include/gl/vertex_descriptor.hpp | 52 +++++++------- include/hgl/hypergraph_elements.hpp | 51 +++++++------- 5 files changed, 129 insertions(+), 157 deletions(-) diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index aa0ce3c8..2debe972 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -163,82 +163,70 @@ class edge_descriptor final { return this->_properties.get(); } - friend gl_attr_force_inline std::ostream& operator<<( - std::ostream& os, const edge_descriptor& edge - ) { - return edge._write(os); + friend std::ostream& operator<<(std::ostream& os, const edge_descriptor& edge) { + using enum io::detail::option_bit; + + if (io::is_option_set(os, verbose)) + return edge._verbose_write(os); + else + return edge._concise_write(os); } private: - std::ostream& _write(std::ostream& os) const + std::ostream& _verbose_write(std::ostream& os) const requires std::same_as { - using io::detail::option_bit; - - if constexpr (not traits::c_writable) { - return this->_write_no_properties(os); - } - else { - if (not io::is_option_set(os, option_bit::with_connection_properties)) - return this->_write_no_properties(os); - - if (io::is_option_set(os, option_bit::verbose)) - return os - << "[id: " << this->_id << " | endpoints: {" << this->_vertices.first << ", " - << this->_vertices.second << "} | " << this->_properties.get() << ']'; - else - return os << '{' << this->_vertices.first << ", " << this->_vertices.second << "}[" - << this->_properties.get() << ']'; - } + using enum io::detail::option_bit; + + os << "[id: " << this->_id << " | endpoints: {" << this->_vertices.first << ", " + << this->_vertices.second << '}'; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_connection_properties)) + os << " | " << this->_properties.get(); + os << ']'; + + return os; } - // TODO: rm - std::ostream& _write_no_properties(std::ostream& os) const + std::ostream& _concise_write(std::ostream& os) const requires std::same_as { - using io::detail::option_bit; + using enum io::detail::option_bit; - if (io::is_option_set(os, option_bit::verbose)) - return os << "[id: " << this->_id << " | endpoints: {" << this->_vertices.first << ", " - << this->_vertices.second << "}]"; - else - return os << '{' << this->_vertices.first << ", " << this->_vertices.second << '}'; + os << '{' << this->_vertices.first << ", " << this->_vertices.second << '}'; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_vertex_properties)) + os << '[' << this->_properties.get() << ']'; + + return os; } - std::ostream& _write(std::ostream& os) const + std::ostream& _verbose_write(std::ostream& os) const requires std::same_as { - using io::detail::option_bit; - - if constexpr (not traits::c_writable) { - return this->_write_no_properties(os); - } - else { - if (not io::is_option_set(os, option_bit::with_connection_properties)) - return this->_write_no_properties(os); - - if (io::is_option_set(os, option_bit::verbose)) - return os - << "[id: " << this->_id << " | source: " << this->_vertices.first - << ", target: " << this->_vertices.second << " | " << this->_properties.get() - << ']'; - else - return os << '(' << this->_vertices.first << ", " << this->_vertices.second << ")[" - << this->_properties.get() << ']'; - } + using enum io::detail::option_bit; + + os << "[id: " << this->_id << " | source: " << this->_vertices.first + << ", target: " << this->_vertices.second; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_connection_properties)) + os << " | " << this->_properties.get(); + os << ']'; + + return os; } - // TODO: rm - std::ostream& _write_no_properties(std::ostream& os) const + std::ostream& _concise_write(std::ostream& os) const requires std::same_as { - using io::detail::option_bit; + using enum io::detail::option_bit; - if (io::is_option_set(os, option_bit::verbose)) - return os << "[id: " << this->_id << " | source: " << this->_vertices.first - << ", target: " << this->_vertices.second << ']'; - else - return os << '(' << this->_vertices.first << ", " << this->_vertices.second << ')'; + os << '(' << this->_vertices.first << ", " << this->_vertices.second << ')'; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_vertex_properties)) + os << '[' << this->_properties.get() << ']'; + + return os; } id_type _id; diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 58a3f4b4..c44fc1f9 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -11,6 +11,7 @@ #include "gl/io/graph_fmt_traits.hpp" #include "gl/io/options.hpp" #include "gl/io/stream_options_manipulator.hpp" +#include "gl/traits.hpp" #include "gl/util/ranges.hpp" #include @@ -718,6 +719,21 @@ class graph final { // --- I/O utility --- + struct concise_target_formatter { + edge_type edge; + id_type src_id; + bool with_props; + + friend std::ostream& operator<<(std::ostream& os, const concise_target_formatter& proxy) { + os << proxy.edge.other(proxy.src_id); + if constexpr (traits::c_writable) + if (proxy.with_props) + os << '[' << proxy.edge.properties() << ']'; + + return os; + } + }; + std::ostream& _verbose_write(std::ostream& os) const { os << "type: " << fmt_traits::type << ", |V| = " << this->order() << ", |E| = " << this->size() << '\n'; @@ -730,17 +746,17 @@ class graph final { } std::ostream& _concise_write(std::ostream& os) const { - using io::detail::option_bit; + using enum io::detail::option_bit; for (const auto& src : this->vertices()) { - os << src << " :"; - for (const auto& edge : this->out_edges(src.id())) { - os << ' ' << edge.other(src.id()); - if constexpr (traits::c_writable) - if (io::is_option_set(os, option_bit::with_connection_properties)) - os << '[' << edge.properties() << ']'; - } - os << '\n'; + auto tgts = std::views::transform( + this->out_edges(src.id()), + [src_id = src.id(), + with_props = io::is_option_set(os, with_connection_properties)](const auto& edge) { + return concise_target_formatter{edge, src_id, with_props}; + } + ); + os << src << " : " << io::range_formatter(tgts) << '\n'; } return os; @@ -749,7 +765,6 @@ class graph final { std::ostream& _gsf_write(std::ostream& os) const { using io::detail::option_bit; - // TODO: include c_writable + rename to _props const bool with_vertex_properties = io::is_option_set(os, option_bit::with_vertex_properties); const bool with_edge_properties = diff --git a/include/gl/io/format.hpp b/include/gl/io/format.hpp index 1487e22b..4e48114f 100644 --- a/include/gl/io/format.hpp +++ b/include/gl/io/format.hpp @@ -15,16 +15,16 @@ namespace gl::io { // TODO: add tests template struct range_formatter { - const Range& range; + Range range; std::string_view sep = ", "; std::string_view open = "["; std::string_view close = "]"; - friend std::ostream& operator<<(std::ostream& os, const range_formatter& formatter) { + friend std::ostream& operator<<(std::ostream& os, range_formatter formatter) { os << formatter.open; bool first = true; for (const auto& item : formatter.range) { - if (! first) + if (not first) os << formatter.sep; os << item; first = false; @@ -34,42 +34,12 @@ struct range_formatter { } }; -template -range_formatter(const Range&) -> range_formatter; - -template -range_formatter set_formatter(const Range& range, std::string_view sep = ", ") { - return range_formatter{range, sep, "{", "}"}; -} - -/* -Is it necessary -Custom format functions (casts to types compatible with std::formatter) -Not std::formatter overloads to avoid collision with - user defined std::formatter overloads -*/ - - -template -[[nodiscard]] gl_attr_force_inline void* format(T* ptr) { - // std::format is not compatible with all types of ptrs - return static_cast(ptr); -} - -// clang-format off -// gl_attr_force_inline misplacement - -template -[[nodiscard]] gl_attr_force_inline const void* format(const T* ptr) { - // std::format is not compatible with all types format ptrs - return static_cast(ptr); -} - -// clang-format on +template +range_formatter(R&& r) -> range_formatter>; -template -[[nodiscard]] gl_attr_force_inline void* format(const PtrType& ptr) { - return formatter(ptr.get()); +template +auto set_formatter(R&& range, std::string_view sep = ", ") { + return range_formatter{std::forward(range), sep, "{", "}"}; } } // namespace gl::io diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index e84c8243..7f8ea26f 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -8,6 +8,7 @@ #include "gl/constants.hpp" #include "gl/decl/graph_traits.hpp" #include "gl/io/options.hpp" +#include "gl/io/stream_options_manipulator.hpp" #include "gl/traits.hpp" #include "gl/types/core.hpp" #include "gl/types/properties.hpp" @@ -92,38 +93,37 @@ class vertex_descriptor final { return this->_properties.get(); } - friend gl_attr_force_inline std::ostream& operator<<( - std::ostream& os, const vertex_descriptor& vertex - ) { - return vertex._write(os); + friend std::ostream& operator<<(std::ostream& os, const vertex_descriptor& vertex) { + using enum io::detail::option_bit; + + if (io::is_option_set(os, verbose)) + return vertex._verbose_write(os); + else + return vertex._concise_write(os); } private: - std::ostream& _write(std::ostream& os) const { - using io::detail::option_bit; - - if constexpr (not traits::c_writable) { - return this->_write_no_properties(os); - } - else { - if (not io::is_option_set(os, option_bit::with_vertex_properties)) - return this->_write_no_properties(os); - - if (io::is_option_set(os, option_bit::verbose)) - return os << "[id: " << this->_id << " | " << this->_properties.get() << ']'; - else - return os << this->_id << '[' << this->_properties.get() << ']'; - } + std::ostream& _verbose_write(std::ostream& os) const { + using enum io::detail::option_bit; + + os << "[id: " << this->_id; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_vertex_properties)) + os << " | " << this->_properties.get(); + os << ']'; + + return os; } - // TODO: rm - std::ostream& _write_no_properties(std::ostream& os) const { - using io::detail::option_bit; + std::ostream& _concise_write(std::ostream& os) const { + using enum io::detail::option_bit; - if (io::is_option_set(os, option_bit::verbose)) - return os << "[id: " << this->_id << ']'; - else - return os << this->_id; + os << this->_id; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_vertex_properties)) + os << '[' << this->_properties.get() << ']'; + + return os; } id_type _id; diff --git a/include/hgl/hypergraph_elements.hpp b/include/hgl/hypergraph_elements.hpp index 1aa5ffec..a6c783ac 100644 --- a/include/hgl/hypergraph_elements.hpp +++ b/include/hgl/hypergraph_elements.hpp @@ -94,38 +94,37 @@ class hyperedge_descriptor final { return this->_properties.get(); } - friend gl_attr_force_inline std::ostream& operator<<( - std::ostream& os, const hyperedge_descriptor& hyperedge - ) { - return hyperedge._write(os); + friend std::ostream& operator<<(std::ostream& os, const hyperedge_descriptor& hyperedge) { + using enum io::detail::option_bit; + + if (io::is_option_set(os, verbose)) + return hyperedge._verbose_write(os); + else + return hyperedge._concise_write(os); } private: - std::ostream& _write(std::ostream& os) const { - using io::detail::option_bit; - - if constexpr (not traits::c_writable) { - return this->_write_no_properties(os); - } - else { - if (not io::is_option_set(os, option_bit::with_connection_properties)) - return this->_write_no_properties(os); - - if (io::is_option_set(os, option_bit::verbose)) - return os << "[id: " << this->_id << " | " << this->_properties.get() << "]"; - else - return os << this->_id << "[" << this->_properties.get() << "]"; - } + std::ostream& _verbose_write(std::ostream& os) const { + using enum io::detail::option_bit; + + os << "[id: " << this->_id; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_vertex_properties)) + os << " | " << this->_properties.get(); + os << ']'; + + return os; } - // TODO: rm - std::ostream& _write_no_properties(std::ostream& os) const { - using io::detail::option_bit; + std::ostream& _concise_write(std::ostream& os) const { + using enum io::detail::option_bit; - if (io::is_option_set(os, option_bit::verbose)) - return os << "[id: " << this->_id << "]"; - else - return os << this->_id; + os << this->_id; + if constexpr (traits::c_writable) + if (io::is_option_set(os, with_vertex_properties)) + os << '[' << this->_properties.get() << ']'; + + return os; } id_type _id; From b63e0b5af166dbe99dfe2c92d66cffb83a45af39 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 12:17:21 +0200 Subject: [PATCH 07/10] hypergraph i/o refined --- include/gl/io/format.hpp | 12 ++++++++- include/gl/io/options.hpp | 1 + include/hgl/hypergraph.hpp | 50 ++++++++++++++++++++++++++++---------- include/hgl/io.hpp | 3 +++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/include/gl/io/format.hpp b/include/gl/io/format.hpp index 4e48114f..77ff8e30 100644 --- a/include/gl/io/format.hpp +++ b/include/gl/io/format.hpp @@ -39,7 +39,17 @@ range_formatter(R&& r) -> range_formatter>; template auto set_formatter(R&& range, std::string_view sep = ", ") { - return range_formatter{std::forward(range), sep, "{", "}"}; + using view_type = std::views::all_t; + return range_formatter{std::views::all(std::forward(range)), sep, "{", "}"}; +} + +/// @todo Add an indent_width parameter +template +auto multiline_set_formatter(R&& range) { + using view_type = std::views::all_t; + return range_formatter{ + std::views::all(std::forward(range)), ",\n ", "{\n ", "\n}" + }; } } // namespace gl::io diff --git a/include/gl/io/options.hpp b/include/gl/io/options.hpp index 1a08a745..d6f6d2da 100644 --- a/include/gl/io/options.hpp +++ b/include/gl/io/options.hpp @@ -39,6 +39,7 @@ inline const stream_options_manipulator without_properties = unset_options( {detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties} ); +// TODO: rename to _spec_fmt inline const stream_options_manipulator enable_gsf = set_option(detail::option_bit::specification_fmt); inline const stream_options_manipulator disable_gsf = diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index c7a026a3..9938d4d0 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -761,7 +761,7 @@ class hypergraph final { struct hyperedge_formatter { public: const hypergraph& hg; - const hyperedge_type& hyperedge; + const hyperedge_type hyperedge; friend std::ostream& operator<<(std::ostream& os, const hyperedge_formatter& proxy) requires std::same_as @@ -946,10 +946,18 @@ class hypergraph final { for (const auto& vertex : this->vertices()) os << " - " << vertex << '\n'; } + else { + this->_write_implicit_vset(os) << '\n'; + } - os << "hyperedges:\n"; - for (const auto& edge : this->hyperedges()) - os << " - " << this->display(edge) << '\n'; + if (this->size() == 0uz) { + os << "hyperedges: {}"; + } + else { + os << "hyperedges:\n"; + for (const auto& edge : this->hyperedges()) + os << " - " << this->display(edge) << '\n'; + } return os; } @@ -960,22 +968,38 @@ class hypergraph final { const bool with_v_props = io::is_option_set(os, with_vertex_properties) and traits::c_writable; - if (with_v_props) { - os << "V :"; - for (const auto& vertex : this->vertices()) - os << " " << vertex; + if (with_v_props) + os << "V = " << io::multiline_set_formatter(this->vertices()) << '\n'; + else + this->_write_implicit_vset(os) << '\n'; + + if (this->size() == 0uz) { + os << "E = {}\n"; } else { - os << "|V| = " << this->order(); - } + auto hyperedges = std::views::transform(this->hyperedges(), [this](auto hyperedge) { + return this->display(hyperedge); + }); - os << "\nE:\n"; - for (const auto& edge : this->hyperedges()) - os << this->display(edge) << '\n'; + // TODO: set/multiline_set + os << "E = " << io::multiline_set_formatter(hyperedges) << '\n'; + } return os; } + std::ostream& _write_implicit_vset(std::ostream& os) const { + const auto n = this->order(); + if (n == 0uz) + return os << "V = {}"; + if (n == 1uz) + return os << "V = {0}"; + if (n == 2uz) + return os << "V = {0, 1}"; + + return os << "V = {0, ..., " << n - 1 << '}'; + } + // --- data members --- size_type _n_vertices = 0uz; diff --git a/include/hgl/io.hpp b/include/hgl/io.hpp index 303e33fd..8a9ee816 100644 --- a/include/hgl/io.hpp +++ b/include/hgl/io.hpp @@ -13,6 +13,7 @@ namespace hgl::io { +using gl::io::multiline_set_formatter; using gl::io::range_formatter; using gl::io::set_formatter; @@ -42,6 +43,8 @@ inline const stream_options_manipulator with_hyperedge_properties = inline const stream_options_manipulator without_hyperedge_properties = unset_option(detail::option_bit::with_connection_properties); +// TODO: spec fmt + using gl::io::with_properties; using gl::io::without_properties; From 2d44c1012f6e19f9ca8b567f73b8803a656f64e5 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 12:56:10 +0200 Subject: [PATCH 08/10] stream_options_manipulator refactor --- include/gl/edge_descriptor.hpp | 2 +- include/gl/graph.hpp | 4 +- include/gl/io/graph_fio.hpp | 6 +- include/gl/io/options.hpp | 55 ++--- include/gl/io/options_manip.hpp | 137 +++++++++++ include/gl/io/{format.hpp => ranges.hpp} | 45 +++- include/gl/io/stream_options_manipulator.hpp | 216 ------------------ include/gl/traits.hpp | 10 +- include/gl/vertex_descriptor.hpp | 2 +- include/hgl/hypergraph.hpp | 75 +++--- include/hgl/io.hpp | 25 +- tests/source/gl/test_graph_io.cpp | 5 +- tests/source/gl/test_io_options_manip.cpp | 123 ++++++++++ tests/source/gl/test_io_ranges.cpp | 105 +++++++++ .../gl/test_stream_options_manipulator.cpp | 168 -------------- 15 files changed, 493 insertions(+), 485 deletions(-) create mode 100644 include/gl/io/options_manip.hpp rename include/gl/io/{format.hpp => ranges.hpp} (52%) delete mode 100644 include/gl/io/stream_options_manipulator.hpp create mode 100644 tests/source/gl/test_io_options_manip.cpp create mode 100644 tests/source/gl/test_io_ranges.cpp delete mode 100644 tests/source/gl/test_stream_options_manipulator.cpp diff --git a/include/gl/edge_descriptor.hpp b/include/gl/edge_descriptor.hpp index 2debe972..f0a0133e 100644 --- a/include/gl/edge_descriptor.hpp +++ b/include/gl/edge_descriptor.hpp @@ -7,8 +7,8 @@ #include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" #include "gl/directional_tags.hpp" -#include "gl/io/format.hpp" #include "gl/io/options.hpp" +#include "gl/io/ranges.hpp" #include "gl/types/core.hpp" #include "gl/vertex_descriptor.hpp" diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index c44fc1f9..ab6bc4f7 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -10,7 +10,7 @@ #include "gl/impl/impl_tags.hpp" #include "gl/io/graph_fmt_traits.hpp" #include "gl/io/options.hpp" -#include "gl/io/stream_options_manipulator.hpp" +#include "gl/io/options_manip.hpp" #include "gl/traits.hpp" #include "gl/util/ranges.hpp" @@ -636,7 +636,7 @@ class graph final { friend std::ostream& operator<<(std::ostream& os, const graph& g) { using io::detail::option_bit; - if (io::is_option_set(os, option_bit::specification_fmt)) + if (io::is_option_set(os, option_bit::spec_fmt)) return g._gsf_write(os); if (io::is_option_set(os, option_bit::verbose)) diff --git a/include/gl/io/graph_fio.hpp b/include/gl/io/graph_fio.hpp index 2b27f46f..4d7c8aa9 100644 --- a/include/gl/io/graph_fio.hpp +++ b/include/gl/io/graph_fio.hpp @@ -91,11 +91,11 @@ template void save( const GraphType& graph, const std::filesystem::path& path = "graph.gsf", - const std::initializer_list& options = {} + const std::initializer_list& options = {} ) { std::ofstream file = detail::open_outfile(path); - file << enable_gsf; + file << spec_fmt; for (const auto& option : options) file << option; @@ -106,7 +106,7 @@ template [[nodiscard]] GraphType load(const std::filesystem::path& path = "graph.gsf") { std::ifstream file = detail::open_infile(path); - file >> enable_gsf; + file >> spec_fmt; GraphType graph; file >> graph; diff --git a/include/gl/io/options.hpp b/include/gl/io/options.hpp index d6f6d2da..c48da3a3 100644 --- a/include/gl/io/options.hpp +++ b/include/gl/io/options.hpp @@ -4,7 +4,7 @@ #pragma once -#include "gl/io/stream_options_manipulator.hpp" +#include "gl/io/options_manip.hpp" namespace gl::io { @@ -12,40 +12,43 @@ namespace detail { enum class option_bit : bit_position_type { verbose = 0u, - with_vertex_properties = 1u, - with_connection_properties = 2u, - specification_fmt = 3u + spec_fmt = 1u, + with_vertex_properties = 2u, + with_connection_properties = 3u }; +inline constexpr iword_type layout_options_mask = + detail::build_mask(detail::option_bit::verbose, detail::option_bit::spec_fmt); + } // namespace detail -inline const stream_options_manipulator verbose = set_option(detail::option_bit::verbose); -inline const stream_options_manipulator concise = unset_option(detail::option_bit::verbose); +// TODO: add tests -inline const stream_options_manipulator with_vertex_properties = - set_option(detail::option_bit::with_vertex_properties); -inline const stream_options_manipulator without_vertex_properties = - unset_option(detail::option_bit::with_vertex_properties); +inline constexpr options_manip concise{0ul, detail::layout_options_mask}; +inline constexpr options_manip verbose{ + detail::build_mask(detail::option_bit::verbose), detail::layout_options_mask +}; +inline constexpr options_manip spec_fmt{ + detail::build_mask(detail::option_bit::spec_fmt), detail::layout_options_mask +}; -inline const stream_options_manipulator with_edge_properties = - set_option(detail::option_bit::with_connection_properties); -inline const stream_options_manipulator without_edge_properties = - unset_option(detail::option_bit::with_connection_properties); +inline constexpr options_manip with_vertex_properties = + set_options(detail::option_bit::with_vertex_properties); +inline constexpr options_manip without_vertex_properties = + clear_options(detail::option_bit::with_vertex_properties); -inline const stream_options_manipulator with_properties = set_options( - {detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties} +inline constexpr options_manip with_edge_properties = + set_options(detail::option_bit::with_connection_properties); +inline constexpr options_manip without_edge_properties = + clear_options(detail::option_bit::with_connection_properties); + +inline constexpr options_manip with_properties = set_options( + detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties ); -inline const stream_options_manipulator without_properties = unset_options( - {detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties} +inline constexpr options_manip without_properties = clear_options( + detail::option_bit::with_vertex_properties, detail::option_bit::with_connection_properties ); -// TODO: rename to _spec_fmt -inline const stream_options_manipulator enable_gsf = - set_option(detail::option_bit::specification_fmt); -inline const stream_options_manipulator disable_gsf = - unset_option(detail::option_bit::specification_fmt); - -inline const stream_options_manipulator default_options = - unset_options(~static_cast(0)); +inline constexpr options_manip default_options{0ul, ~static_cast(0)}; } // namespace gl::io diff --git a/include/gl/io/options_manip.hpp b/include/gl/io/options_manip.hpp new file mode 100644 index 00000000..fe6fe7a1 --- /dev/null +++ b/include/gl/io/options_manip.hpp @@ -0,0 +1,137 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "gl/attributes/force_inline.hpp" +#include "gl/traits.hpp" + +#include +#include + +namespace gl::io { + +using index_type = int; +using iword_type = long; +using bit_position_type = unsigned; + +inline constexpr iword_type iword_bit = 1ul; + +class options_manip { +public: + options_manip() = delete; + + constexpr explicit options_manip(iword_type set_mask, iword_type clear_mask = 0ul) + : _set_mask(set_mask), _clear_mask(clear_mask) {} + + friend std::ostream& operator<<(std::ostream& os, const options_manip& manip) { + os.iword(_iostream_property_map_index()) &= ~manip._clear_mask; + os.iword(_iostream_property_map_index()) |= manip._set_mask; + return os; + } + + friend std::istream& operator>>(std::istream& is, const options_manip& manip) { + is.iword(_iostream_property_map_index()) &= ~manip._clear_mask; + is.iword(_iostream_property_map_index()) |= manip._set_mask; + return is; + } + + [[nodiscard]] gl_attr_force_inline static bool is_option_set( + std::ios_base& stream, bit_position_type bit_position + ) { + return (stream.iword(_iostream_property_map_index()) & (iword_bit << bit_position)) != 0; + } + + [[nodiscard]] gl_attr_force_inline static bool is_option_set( + std::ios_base& stream, traits::c_enum auto bit + ) { + return is_option_set(stream, static_cast(bit)); + } + + [[nodiscard]] gl_attr_force_inline static bool are_options_set( + std::ios_base& stream, iword_type bitmask + ) { + return (stream.iword(_iostream_property_map_index()) & bitmask) == bitmask; + } + +private: + [[nodiscard]] gl_attr_force_inline static index_type _iostream_property_map_index() { + static index_type index = std::ios_base::xalloc(); + return index; + } + + iword_type _set_mask; + iword_type _clear_mask; +}; + +namespace detail { + +// clang-format off + +template +[[nodiscard]] constexpr iword_type build_mask(Args... bits) { + return ((iword_bit << static_cast(bits)) | ...); +} + +// clang-format on + +template +[[nodiscard]] constexpr iword_type build_mask_from(std::initializer_list bits) { + iword_type mask = 0ul; + for (auto b : bits) + mask |= iword_bit << static_cast(b); + return mask; +} + +} // namespace detail + +template +[[nodiscard]] constexpr options_manip set_options(Args... bits) { + return options_manip{detail::build_mask(bits...), 0ul}; +} + +template +[[nodiscard]] constexpr options_manip set_options(std::initializer_list bits) { + return options_manip{detail::build_mask_from(bits), 0ul}; +} + +template +[[nodiscard]] constexpr options_manip clear_options(Args... bits) { + return options_manip{0ul, detail::build_mask(bits...)}; +} + +template +[[nodiscard]] constexpr options_manip clear_options(std::initializer_list bits) { + return options_manip{0ul, detail::build_mask_from(bits)}; +} + +[[nodiscard]] gl_attr_force_inline bool is_option_set( + std::ios_base& stream, bit_position_type bit_position +) { + return options_manip::is_option_set(stream, bit_position); +} + +[[nodiscard]] gl_attr_force_inline bool is_option_set( + std::ios_base& stream, traits::c_enum auto bit +) { + return options_manip::is_option_set(stream, bit); +} + +[[nodiscard]] gl_attr_force_inline bool are_options_set(std::ios_base& stream, iword_type bitmask) { + return options_manip::are_options_set(stream, bitmask); +} + +template +[[nodiscard]] gl_attr_force_inline bool are_options_set(std::ios_base& stream, Args... bits) { + return options_manip::are_options_set(stream, detail::build_mask(bits...)); +} + +template +[[nodiscard]] gl_attr_force_inline bool are_options_set( + std::ios_base& stream, std::initializer_list bits +) { + return options_manip::are_options_set(stream, detail::build_mask_from(bits)); +} + +} // namespace gl::io diff --git a/include/gl/io/format.hpp b/include/gl/io/ranges.hpp similarity index 52% rename from include/gl/io/format.hpp rename to include/gl/io/ranges.hpp index 77ff8e30..b7065b8d 100644 --- a/include/gl/io/format.hpp +++ b/include/gl/io/ranges.hpp @@ -4,15 +4,11 @@ #pragma once -#include "gl/attributes/force_inline.hpp" -#include "gl/traits.hpp" - #include #include namespace gl::io { -// TODO: add tests template struct range_formatter { Range range; @@ -34,9 +30,21 @@ struct range_formatter { } }; +// CTAD helpers + template range_formatter(R&& r) -> range_formatter>; +template +range_formatter(R&& r, std::string_view) -> range_formatter>; + +template +range_formatter(R&& r, std::string_view, std::string_view) -> range_formatter>; + +template +range_formatter(R&& r, std::string_view, std::string_view, std::string_view) + -> range_formatter>; + template auto set_formatter(R&& range, std::string_view sep = ", ") { using view_type = std::views::all_t; @@ -52,4 +60,33 @@ auto multiline_set_formatter(R&& range) { }; } +template +struct implicit_range_formatter { + T first; + T last; // exclusive + + friend std::ostream& operator<<(std::ostream& os, implicit_range_formatter proxy) { + if (proxy.first >= proxy.last) + return os << "{}"; + + const auto dist = proxy.last - proxy.first; + if (dist == 1) + return os << '{' << proxy.first << '}'; + if (dist == 2) + return os << '{' << proxy.first << ", " << proxy.first + 1 << '}'; + + return os << '{' << proxy.first << ", ..., " << proxy.last - 1 << '}'; + } +}; + +template +[[nodiscard]] constexpr auto implicit_range(T first, T last, bool inclusive = false) { + return implicit_range_formatter{first, last + static_cast(inclusive)}; +} + +template +[[nodiscard]] constexpr auto implicit_range(T last, bool inclusive = false) { + return implicit_range(static_cast(0), last, inclusive); +} + } // namespace gl::io diff --git a/include/gl/io/stream_options_manipulator.hpp b/include/gl/io/stream_options_manipulator.hpp deleted file mode 100644 index bf3ac32c..00000000 --- a/include/gl/io/stream_options_manipulator.hpp +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) 2024-2026 Jakub Musiał -// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). -// Licensed under the MIT License. See the LICENSE file in the project root for full license information. - -#pragma once - -#include "gl/attributes/force_inline.hpp" - -#include -#include - -namespace gl::io { - -using index_type = int; -using iword_type = long; -using bit_position_type = unsigned; - -inline constexpr iword_type iword_bit = 1ul; - -namespace detail { - -template -concept c_bit_position_enum = - std::is_enum_v and std::convertible_to, bit_position_type>; - -} // namespace detail - -class stream_options_manipulator { -public: - stream_options_manipulator() = delete; - - explicit stream_options_manipulator(iword_type bitmask, bool operation = set) - : _bitmask(bitmask), _operation(operation) {} - - [[nodiscard]] gl_attr_force_inline static stream_options_manipulator from_bit_position( - bit_position_type bit_position, bool operation = set - ) { - return stream_options_manipulator{iword_bit << bit_position, operation}; - } - - friend std::ostream& operator<<( - std::ostream& os, const stream_options_manipulator& property_manipulator - ) { - if (property_manipulator._operation == set) - os.iword(_iostream_property_map_index()) |= property_manipulator._bitmask; - else - os.iword(_iostream_property_map_index()) &= ~property_manipulator._bitmask; - - return os; - } - - friend std::istream& operator>>( - std::istream& is, const stream_options_manipulator& property_manipulator - ) { - if (property_manipulator._operation == set) - is.iword(_iostream_property_map_index()) |= property_manipulator._bitmask; - else - is.iword(_iostream_property_map_index()) &= ~property_manipulator._bitmask; - - return is; - } - - [[nodiscard]] gl_attr_force_inline static bool is_option_set( - std::ios_base& stream, bit_position_type bit_position - ) { - return (stream.iword(_iostream_property_map_index()) & (iword_bit << bit_position)) != 0; - } - - [[nodiscard]] gl_attr_force_inline static bool are_options_set( - std::ios_base& stream, iword_type bitmask - ) { - return (stream.iword(_iostream_property_map_index()) & bitmask) == bitmask; - } - - static constexpr bool set = true; - static constexpr bool unset = false; - -private: - [[nodiscard]] gl_attr_force_inline static index_type _iostream_property_map_index() { - static index_type index = std::ios_base::xalloc(); - return index; - } - - iword_type _bitmask; - bool _operation; -}; - -namespace detail { - -[[nodiscard]] inline iword_type get_options_bitmask( - const std::initializer_list& bit_positions -) { - iword_type options_bitmask = 0ul; - for (const auto bit_position : bit_positions) - options_bitmask |= iword_bit << bit_position; - return options_bitmask; -} - -template -[[nodiscard]] iword_type get_options_bitmask(const std::initializer_list& bit_positions -) { - iword_type options_bitmask = 0ul; - for (const auto bit_position : bit_positions) - options_bitmask |= - iword_bit << static_cast(std::to_underlying(bit_position)); - return options_bitmask; -} - -} // namespace detail - -[[nodiscard]] gl_attr_force_inline stream_options_manipulator set_options(iword_type bitmask) { - return stream_options_manipulator{bitmask, stream_options_manipulator::set}; -} - -[[nodiscard]] gl_attr_force_inline stream_options_manipulator -set_options(const std::initializer_list& bit_positions) { - return stream_options_manipulator{ - detail::get_options_bitmask(bit_positions), stream_options_manipulator::set - }; -} - -template -[[nodiscard]] gl_attr_force_inline stream_options_manipulator -set_options(const std::initializer_list& bit_positions) { - return stream_options_manipulator{ - detail::get_options_bitmask(bit_positions), stream_options_manipulator::set - }; -} - -[[nodiscard]] gl_attr_force_inline stream_options_manipulator -set_option(bit_position_type bit_position) { - return stream_options_manipulator::from_bit_position( - bit_position, stream_options_manipulator::set - ); -} - -template -[[nodiscard]] gl_attr_force_inline stream_options_manipulator set_option(BitPosition bit_position) { - return stream_options_manipulator::from_bit_position( - static_cast(std::to_underlying(bit_position)), - stream_options_manipulator::set - ); -} - -[[nodiscard]] gl_attr_force_inline stream_options_manipulator unset_options(iword_type bitmask) { - return stream_options_manipulator{bitmask, stream_options_manipulator::unset}; -} - -[[nodiscard]] gl_attr_force_inline stream_options_manipulator -unset_options(const std::initializer_list& bit_positions) { - return stream_options_manipulator{ - detail::get_options_bitmask(bit_positions), stream_options_manipulator::unset - }; -} - -template -[[nodiscard]] gl_attr_force_inline stream_options_manipulator -unset_options(const std::initializer_list& bit_positions) { - return stream_options_manipulator{ - detail::get_options_bitmask(bit_positions), stream_options_manipulator::unset - }; -} - -[[nodiscard]] gl_attr_force_inline stream_options_manipulator -unset_option(bit_position_type bit_position) { - return stream_options_manipulator::from_bit_position( - bit_position, stream_options_manipulator::unset - ); -} - -template -[[nodiscard]] gl_attr_force_inline stream_options_manipulator unset_option(BitPosition bit_position -) { - return stream_options_manipulator::from_bit_position( - static_cast(std::to_underlying(bit_position)), - stream_options_manipulator::unset - ); -} - -[[nodiscard]] gl_attr_force_inline bool are_options_set(std::ios_base& stream, iword_type bitmask) { - return stream_options_manipulator::are_options_set(stream, bitmask); -} - -[[nodiscard]] gl_attr_force_inline bool are_options_set( - std::ios_base& stream, const std::initializer_list& bit_positions -) { - return stream_options_manipulator::are_options_set( - stream, detail::get_options_bitmask(bit_positions) - ); -} - -template -[[nodiscard]] gl_attr_force_inline bool are_options_set( - std::ios_base& stream, const std::initializer_list& bit_positions -) { - return stream_options_manipulator::are_options_set( - stream, detail::get_options_bitmask(bit_positions) - ); -} - -[[nodiscard]] gl_attr_force_inline bool is_option_set( - std::ios_base& stream, bit_position_type bit_position -) { - return stream_options_manipulator::is_option_set(stream, bit_position); -} - -template -[[nodiscard]] gl_attr_force_inline bool is_option_set( - std::ios_base& stream, BitPosition bit_position -) { - return stream_options_manipulator::is_option_set( - stream, static_cast(std::to_underlying(bit_position)) - ); -} - -} // namespace gl::io diff --git a/include/gl/traits.hpp b/include/gl/traits.hpp index 77f00880..f2e8a7af 100644 --- a/include/gl/traits.hpp +++ b/include/gl/traits.hpp @@ -79,13 +79,6 @@ concept c_const_range = requires(R& r) { std::ranges::cend(r); }; -template -concept c_strong_smart_ptr = - c_instantiation_of or c_instantiation_of; - -template -concept c_strong_ptr = c_strong_smart_ptr or std::is_pointer_v; - template concept c_const_iterator = requires(T iter) { { *iter } -> std::same_as&>; @@ -111,4 +104,7 @@ concept c_readable = requires(T value, std::istream& is) { is >> value; }; template concept c_writable = requires(T value, std::ostream& os) { os << value; }; +template +concept c_enum = std::is_enum_v; + } // namespace gl::traits diff --git a/include/gl/vertex_descriptor.hpp b/include/gl/vertex_descriptor.hpp index 7f8ea26f..e74f3eb5 100644 --- a/include/gl/vertex_descriptor.hpp +++ b/include/gl/vertex_descriptor.hpp @@ -8,7 +8,7 @@ #include "gl/constants.hpp" #include "gl/decl/graph_traits.hpp" #include "gl/io/options.hpp" -#include "gl/io/stream_options_manipulator.hpp" +#include "gl/io/options_manip.hpp" #include "gl/traits.hpp" #include "gl/types/core.hpp" #include "gl/types/properties.hpp" diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 9938d4d0..6b19dc60 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -767,21 +767,20 @@ class hypergraph final { requires std::same_as { using io::detail::option_bit; - const bool with_props = - io::is_option_set(os, option_bit::with_connection_properties) - and traits::c_writable; if (io::is_option_set(os, option_bit::verbose)) { os << "[id: " << proxy.hyperedge.id() << " | vertices: " << io::set_formatter(proxy.hg.incident_vertex_ids(proxy.hyperedge.id())); - if (with_props) - os << " | " << proxy.hyperedge.properties(); + if constexpr (traits::c_writable) + if (io::is_option_set(os, option_bit::with_connection_properties)) + os << " | " << proxy.hyperedge.properties(); os << ']'; } else { // concise os << io::set_formatter(proxy.hg.incident_vertex_ids(proxy.hyperedge.id())); - if (with_props) - os << '[' << proxy.hyperedge.properties() << ']'; + if constexpr (traits::c_writable) + if (io::is_option_set(os, option_bit::with_connection_properties)) + os << '[' << proxy.hyperedge.properties() << ']'; } return os; @@ -791,25 +790,24 @@ class hypergraph final { requires std::same_as { using io::detail::option_bit; - const bool with_props = - io::is_option_set(os, option_bit::with_connection_properties) - and traits::c_writable; if (io::is_option_set(os, option_bit::verbose)) { os << "[id: " << proxy.hyperedge.id() << " | tail: " << io::set_formatter(proxy.hg.tail_vertex_ids(proxy.hyperedge.id())) << ", head: " << io::set_formatter(proxy.hg.head_vertex_ids(proxy.hyperedge.id())); - if (with_props) - os << " | " << proxy.hyperedge.properties(); + if constexpr (traits::c_writable) + if (io::is_option_set(os, option_bit::with_connection_properties)) + os << " | " << proxy.hyperedge.properties(); os << "]"; } else { // concise os << '(' << io::set_formatter(proxy.hg.tail_vertex_ids(proxy.hyperedge.id())) << " -> " << io::set_formatter(proxy.hg.head_vertex_ids(proxy.hyperedge.id())) << ')'; - if (with_props) - os << '[' << proxy.hyperedge.properties() << ']'; + if constexpr (traits::c_writable) + if (io::is_option_set(os, option_bit::with_connection_properties)) + os << '[' << proxy.hyperedge.properties() << ']'; } return os; @@ -938,16 +936,19 @@ class hypergraph final { os << "type: " << fmt_traits::type << ", |V| = " << this->order() << ", |E| = " << this->size() << '\n'; - const bool with_v_props = - io::is_option_set(os, with_vertex_properties) - and traits::c_writable; - if (with_v_props) { - os << "vertices:\n"; - for (const auto& vertex : this->vertices()) - os << " - " << vertex << '\n'; + os << "vertices: "; + if constexpr (traits::c_writable) { + if (io::is_option_set(os, with_vertex_properties)) { + os << '\n'; + for (const auto& vertex : this->vertices()) + os << " - " << vertex << '\n'; + } + else { + os << io::implicit_range(this->order()) << '\n'; + } } else { - this->_write_implicit_vset(os) << '\n'; + os << io::implicit_range(this->order()) << '\n'; } if (this->size() == 0uz) { @@ -965,13 +966,16 @@ class hypergraph final { std::ostream& _concise_write(std::ostream& os) const { using enum io::detail::option_bit; - const bool with_v_props = - io::is_option_set(os, with_vertex_properties) - and traits::c_writable; - if (with_v_props) - os << "V = " << io::multiline_set_formatter(this->vertices()) << '\n'; - else - this->_write_implicit_vset(os) << '\n'; + os << "V = "; + if constexpr (traits::c_writable) { + if (io::is_option_set(os, with_vertex_properties)) + os << io::multiline_set_formatter(this->vertices()) << '\n'; + else + os << io::implicit_range(this->order()) << '\n'; + } + else { + os << io::implicit_range(this->order()) << '\n'; + } if (this->size() == 0uz) { os << "E = {}\n"; @@ -981,25 +985,12 @@ class hypergraph final { return this->display(hyperedge); }); - // TODO: set/multiline_set os << "E = " << io::multiline_set_formatter(hyperedges) << '\n'; } return os; } - std::ostream& _write_implicit_vset(std::ostream& os) const { - const auto n = this->order(); - if (n == 0uz) - return os << "V = {}"; - if (n == 1uz) - return os << "V = {0}"; - if (n == 2uz) - return os << "V = {0, 1}"; - - return os << "V = {0, ..., " << n - 1 << '}'; - } - // --- data members --- size_type _n_vertices = 0uz; diff --git a/include/hgl/io.hpp b/include/hgl/io.hpp index 8a9ee816..e772d5ad 100644 --- a/include/hgl/io.hpp +++ b/include/hgl/io.hpp @@ -4,46 +4,45 @@ #pragma once -#include "gl/io/format.hpp" #include "gl/io/options.hpp" -#include "gl/io/stream_options_manipulator.hpp" +#include "gl/io/options_manip.hpp" +#include "gl/io/ranges.hpp" #include "hgl/directional_tags.hpp" #include namespace hgl::io { +using gl::io::implicit_range; +using gl::io::implicit_range_formatter; using gl::io::multiline_set_formatter; using gl::io::range_formatter; using gl::io::set_formatter; using gl::io::are_options_set; +using gl::io::clear_options; using gl::io::is_option_set; -using gl::io::set_option; +using gl::io::options_manip; using gl::io::set_options; -using gl::io::stream_options_manipulator; -using gl::io::unset_option; -using gl::io::unset_options; namespace detail { -using gl::io::detail::get_options_bitmask; +using gl::io::detail::build_mask; using gl::io::detail::option_bit; } // namespace detail using gl::io::concise; +using gl::io::spec_fmt; using gl::io::verbose; using gl::io::with_vertex_properties; using gl::io::without_vertex_properties; -inline const stream_options_manipulator with_hyperedge_properties = - set_option(detail::option_bit::with_connection_properties); -inline const stream_options_manipulator without_hyperedge_properties = - unset_option(detail::option_bit::with_connection_properties); - -// TODO: spec fmt +inline const options_manip with_hyperedge_properties = + set_options(detail::option_bit::with_connection_properties); +inline const options_manip without_hyperedge_properties = + clear_options(detail::option_bit::with_connection_properties); using gl::io::with_properties; using gl::io::without_properties; diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index 78ca4265..d1290787 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -1,3 +1,4 @@ +#include "gl/io/options.hpp" #include "testing/gl/constants.hpp" #include "testing/gl/io_common.hpp" @@ -20,7 +21,7 @@ struct test_directed_graph_io { using sut_type = gl::graph; test_directed_graph_io() { - ss << gl::io::enable_gsf; + ss << gl::io::spec_fmt; sut_out = gl::topology::clique(n_vertices); @@ -129,7 +130,7 @@ struct test_undirected_graph_io { using sut_type = gl::graph; test_undirected_graph_io() { - ss << gl::io::enable_gsf; + ss << gl::io::spec_fmt; sut_out = gl::topology::clique(n_vertices); diff --git a/tests/source/gl/test_io_options_manip.cpp b/tests/source/gl/test_io_options_manip.cpp new file mode 100644 index 00000000..a5fd46a4 --- /dev/null +++ b/tests/source/gl/test_io_options_manip.cpp @@ -0,0 +1,123 @@ +#include "doctest.h" + +#include + +#include + +namespace gl_testing { + +TEST_SUITE_BEGIN("test_io_options_manip"); + +struct test_io_options_manip { + using sut_type = gl::io::options_manip; + + test_io_options_manip() { + REQUIRE_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + REQUIRE_FALSE(gl::io::is_option_set(ss2, bit_position_1)); + } + + std::stringstream ss1; + std::stringstream ss2; + + static constexpr gl::io::bit_position_type bit_position_1 = 0uz; + static constexpr gl::io::bit_position_type bit_position_2 = 1uz; + static constexpr gl::io::bit_position_type bit_position_3 = 2uz; + + static constexpr gl::io::iword_type options_bitmask = + (gl::io::iword_bit << bit_position_1) | (gl::io::iword_bit << bit_position_2); +}; + +enum class bit_pos_enum : gl::io::bit_position_type { + bit_position_1 = test_io_options_manip::bit_position_1, + bit_position_2 = test_io_options_manip::bit_position_2 +}; + +TEST_CASE_FIXTURE( + test_io_options_manip, "should properly handle single option operations (istream/ostream)" +) { + ss1 << gl::io::set_options(bit_position_1); + CHECK(gl::io::is_option_set(ss1, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); + + ss1 << gl::io::clear_options(bit_position_1); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + + ss2 >> gl::io::set_options(bit_position_1); + CHECK(gl::io::is_option_set(ss2, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + + ss2 >> gl::io::clear_options(bit_position_1); + CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); +} + +TEST_CASE_FIXTURE(test_io_options_manip, "should properly handle variadic option operations") { + ss1 << gl::io::set_options(bit_position_1, bit_position_2); + CHECK(gl::io::are_options_set(ss1, bit_position_1, bit_position_2)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::clear_options(bit_position_1, bit_position_2); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_position_1, bit_position_2)); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); +} + +TEST_CASE_FIXTURE( + test_io_options_manip, "should properly handle initializer list option operations" +) { + const auto bit_positions = {bit_position_1, bit_position_2}; + + ss1 << gl::io::set_options(bit_positions); + CHECK(gl::io::are_options_set(ss1, bit_positions)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::clear_options(bit_positions); + CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); +} + +TEST_CASE_FIXTURE( + test_io_options_manip, "should properly handle enum option operations (variadic & list)" +) { + ss1 << gl::io::set_options(bit_pos_enum::bit_position_1, bit_pos_enum::bit_position_2); + CHECK(gl::io::are_options_set(ss1, bit_pos_enum::bit_position_1, bit_pos_enum::bit_position_2)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); + + ss1 << gl::io::clear_options(bit_pos_enum::bit_position_1, bit_pos_enum::bit_position_2); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_pos_enum::bit_position_1)); + + const auto enum_list = {bit_pos_enum::bit_position_1, bit_pos_enum::bit_position_2}; + ss1 >> gl::io::set_options(enum_list); + CHECK(gl::io::are_options_set(ss1, enum_list)); + CHECK(gl::io::are_options_set(ss1, options_bitmask)); +} + +TEST_CASE_FIXTURE( + test_io_options_manip, "should handle simultaneous set and clear operations (layout triads)" +) { + // init state + ss1 << gl::io::set_options(bit_position_1, bit_position_2); + constexpr auto complex_manip = sut_type( + gl::io::iword_bit << bit_position_3, // set mask + gl::io::iword_bit << bit_position_1 // clear mask + ); + + ss1 << complex_manip; + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + CHECK(gl::io::is_option_set(ss1, bit_position_2)); + CHECK(gl::io::is_option_set(ss1, bit_position_3)); +} + +TEST_CASE_FIXTURE(test_io_options_manip, "should properly reset/clear all options (default state)") { + ss1 << gl::io::set_options(bit_position_1, bit_position_2, bit_position_3); + + constexpr auto reset_manip = sut_type(0ul, ~static_cast(0)); + ss1 << reset_manip; + + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_2)); + CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_3)); + CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); +} + +TEST_SUITE_END(); // test_io_options_manip + +} // namespace gl_testing diff --git a/tests/source/gl/test_io_ranges.cpp b/tests/source/gl/test_io_ranges.cpp new file mode 100644 index 00000000..e57a9c09 --- /dev/null +++ b/tests/source/gl/test_io_ranges.cpp @@ -0,0 +1,105 @@ +#include "doctest.h" + +#include + +#include +#include +#include + +namespace gl_testing { + +TEST_SUITE_BEGIN("test_io_range_formatter"); + +struct test_io_ranges { + std::stringstream ss; + + template + std::string format(Formatter&& fmt) { + ss.str(""); + ss.clear(); + ss << std::forward(fmt); + return ss.str(); + } +}; + +struct test_io_range_formatter : public test_io_ranges { + std::vector empty_vec{}; + std::vector single_vec{42}; + std::vector multi_vec{1, 2, 3}; +}; + +TEST_CASE_FIXTURE(test_io_range_formatter, "should properly format generic ranges") { + // Default formatting + CHECK(format(gl::io::range_formatter{empty_vec}) == "[]"); + CHECK(format(gl::io::range_formatter{single_vec}) == "[42]"); + CHECK(format(gl::io::range_formatter{multi_vec}) == "[1, 2, 3]"); + + // Custom separators and bounds + CHECK(format(gl::io::range_formatter{multi_vec, " | ", "<", ">"}) == "<1 | 2 | 3>"); +} + +TEST_CASE_FIXTURE(test_io_range_formatter, "should properly format sets using set_formatter") { + CHECK(format(gl::io::set_formatter(empty_vec)) == "{}"); + CHECK(format(gl::io::set_formatter(single_vec)) == "{42}"); + CHECK(format(gl::io::set_formatter(multi_vec)) == "{1, 2, 3}"); + + // Custom separator + CHECK(format(gl::io::set_formatter(multi_vec, "; ")) == "{1; 2; 3}"); +} + +TEST_CASE_FIXTURE( + test_io_range_formatter, "set_formatter should safely handle rvalues and views in set_formatter" +) { + // Rvalue temporary containers + CHECK(format(gl::io::set_formatter(std::vector{7, 8, 9})) == "{7, 8, 9}"); + + // Lazy std::ranges::views + auto transformed_view = multi_vec | std::views::transform([](int x) { return x * 10; }); + CHECK(format(gl::io::set_formatter(transformed_view)) == "{10, 20, 30}"); +} + +TEST_CASE_FIXTURE(test_io_range_formatter, "should properly format multiline sets") { + CHECK(format(gl::io::multiline_set_formatter(empty_vec)) == "{\n \n}"); + CHECK(format(gl::io::multiline_set_formatter(single_vec)) == "{\n 42\n}"); + CHECK(format(gl::io::multiline_set_formatter(multi_vec)) == "{\n 1,\n 2,\n 3\n}"); +} + +struct test_io_implicit_range_formatter : public test_io_ranges {}; + +TEST_CASE_FIXTURE( + test_io_implicit_range_formatter, "should properly format zero-based implicit ranges" +) { + // Exclusive (default) + CHECK(format(gl::io::implicit_range(0uz)) == "{}"); + CHECK(format(gl::io::implicit_range(1uz)) == "{0}"); + CHECK(format(gl::io::implicit_range(2uz)) == "{0, 1}"); + CHECK(format(gl::io::implicit_range(5uz)) == "{0, ..., 4}"); + + // Inclusive + CHECK(format(gl::io::implicit_range(0uz, true)) == "{0}"); + CHECK(format(gl::io::implicit_range(1uz, true)) == "{0, 1}"); + CHECK(format(gl::io::implicit_range(4uz, true)) == "{0, ..., 4}"); +} + +TEST_CASE_FIXTURE( + test_io_implicit_range_formatter, "should properly format arbitrary bound implicit ranges" +) { + // Signed integers (exclusive) + CHECK(format(gl::io::implicit_range(10, 10)) == "{}"); + CHECK(format(gl::io::implicit_range(10, 11)) == "{10}"); + CHECK(format(gl::io::implicit_range(10, 12)) == "{10, 11}"); + CHECK(format(gl::io::implicit_range(10, 15)) == "{10, ..., 14}"); + + // Signed integers (inclusive) + CHECK(format(gl::io::implicit_range(10, 10, true)) == "{10}"); + CHECK(format(gl::io::implicit_range(10, 11, true)) == "{10, 11}"); + CHECK(format(gl::io::implicit_range(10, 14, true)) == "{10, ..., 14}"); + + // Negative ranges + CHECK(format(gl::io::implicit_range(-5, -2)) == "{-5, ..., -3}"); + CHECK(format(gl::io::implicit_range(-2, 0)) == "{-2, -1}"); +} + +TEST_SUITE_END(); // test_io_range_formatter + +} // namespace gl_testing diff --git a/tests/source/gl/test_stream_options_manipulator.cpp b/tests/source/gl/test_stream_options_manipulator.cpp deleted file mode 100644 index 7f7216db..00000000 --- a/tests/source/gl/test_stream_options_manipulator.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include "doctest.h" -#include "testing/gl/constants.hpp" - -#include - -#include - -namespace gl_testing { - -TEST_SUITE_BEGIN("test_stream_options_manipulator"); - -struct test_stream_options_manipulator { - test_stream_options_manipulator() { - REQUIRE_FALSE(gl::io::is_option_set(ss1, bit_position_1)); - REQUIRE_FALSE(gl::io::is_option_set(ss2, bit_position_1)); - } - - std::stringstream ss1; - std::stringstream ss2; - - static constexpr gl::io::bit_position_type bit_position_1 = 0uz; - static constexpr gl::io::bit_position_type bit_position_2 = 1uz; - static constexpr gl::io::iword_type options_bitmask = - (gl::io::iword_bit << bit_position_1) | (gl::io::iword_bit << bit_position_2); -}; - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, "should properly handle istream option operations per stream" -) { - ss1 >> gl::io::set_option(bit_position_1); - CHECK(gl::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); - - ss2 >> gl::io::set_option(bit_position_1); - CHECK(gl::io::is_option_set(ss1, bit_position_1)); - CHECK(gl::io::is_option_set(ss2, bit_position_1)); - - ss1 >> gl::io::unset_option(bit_position_1); - CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); - CHECK(gl::io::is_option_set(ss2, bit_position_1)); - - ss2 >> gl::io::unset_option(bit_position_1); - CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, "should properly handle ostream option operations per stream" -) { - ss1 << gl::io::set_option(bit_position_1); - CHECK(gl::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); - - ss2 << gl::io::set_option(bit_position_1); - CHECK(gl::io::is_option_set(ss1, bit_position_1)); - CHECK(gl::io::is_option_set(ss2, bit_position_1)); - - ss1 << gl::io::unset_option(bit_position_1); - CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); - CHECK(gl::io::is_option_set(ss2, bit_position_1)); - - ss2 << gl::io::unset_option(bit_position_1); - CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_1)); - CHECK_FALSE(gl::io::is_option_set(ss2, bit_position_1)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle istream option operations for bit position lists" -) { - const auto bit_positions = {bit_position_1, bit_position_2}; - - ss1 >> gl::io::set_options(bit_positions); - CHECK(gl::io::are_options_set(ss1, bit_positions)); - CHECK(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 >> gl::io::unset_options(bit_positions); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 >> gl::io::set_options({bit_position_1}); - CHECK(gl::io::is_option_set(ss1, bit_position_1)); - CHECK(gl::io::are_options_set(ss1, {bit_position_1})); - CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_2)); - CHECK_FALSE(gl::io::are_options_set(ss1, {bit_position_2})); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle osream option operations for bit position lists" -) { - const auto bit_positions = {bit_position_1, bit_position_2}; - - ss1 << gl::io::set_options(bit_positions); - CHECK(gl::io::are_options_set(ss1, bit_positions)); - CHECK(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 << gl::io::unset_options(bit_positions); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 << gl::io::set_options({bit_position_1}); - CHECK(gl::io::is_option_set(ss1, bit_position_1)); - CHECK(gl::io::are_options_set(ss1, {bit_position_1})); - CHECK_FALSE(gl::io::is_option_set(ss1, bit_position_2)); - CHECK_FALSE(gl::io::are_options_set(ss1, {bit_position_2})); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); -} - -enum class BitPositionEnum : gl::io::bit_position_type { - bit_position_1 = test_stream_options_manipulator::bit_position_1, - bit_position_2 = test_stream_options_manipulator::bit_position_2 -}; - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle istream option operations for enum bit position lists" -) { - const auto bit_positions = {BitPositionEnum::bit_position_1, BitPositionEnum::bit_position_2}; - - ss1 >> gl::io::set_options(bit_positions); - CHECK(gl::io::are_options_set(ss1, bit_positions)); - CHECK(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 >> gl::io::unset_options(bit_positions); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 >> gl::io::set_options({BitPositionEnum::bit_position_1}); - CHECK(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_1)); - CHECK(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_1})); - CHECK_FALSE(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_2)); - CHECK_FALSE(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_2})); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); -} - -TEST_CASE_FIXTURE( - test_stream_options_manipulator, - "should properly handle ostream option operations for enum bit position lists" -) { - const auto bit_positions = {BitPositionEnum::bit_position_1, BitPositionEnum::bit_position_2}; - - ss1 << gl::io::set_options(bit_positions); - CHECK(gl::io::are_options_set(ss1, bit_positions)); - CHECK(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 << gl::io::unset_options(bit_positions); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); - - ss1 << gl::io::set_options({BitPositionEnum::bit_position_1}); - CHECK(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_1)); - CHECK(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_1})); - CHECK_FALSE(gl::io::is_option_set(ss1, BitPositionEnum::bit_position_2)); - CHECK_FALSE(gl::io::are_options_set(ss1, {BitPositionEnum::bit_position_2})); - CHECK_FALSE(gl::io::are_options_set(ss1, bit_positions)); - CHECK_FALSE(gl::io::are_options_set(ss1, options_bitmask)); -} - -// TODO: default test - -TEST_SUITE_END(); // test_stream_options_manipulator - -} // namespace gl_testing From ee60c42a5396fdc2d8679a0f2a34b6214b86de61 Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 14:56:28 +0200 Subject: [PATCH 09/10] hypergraph spec fmt support --- include/gl/graph.hpp | 39 +++-- include/gl/io/graph_fmt_traits.hpp | 14 +- include/hgl/hypergraph.hpp | 179 ++++++++++++++++++++++- include/hgl/hypergraph_elements.hpp | 2 +- include/hgl/io.hpp | 64 +------- include/hgl/io/core.hpp | 52 +++++++ include/hgl/io/hypergraph_fmt_traits.hpp | 28 ++++ 7 files changed, 283 insertions(+), 95 deletions(-) create mode 100644 include/hgl/io/core.hpp create mode 100644 include/hgl/io/hypergraph_fmt_traits.hpp diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index ab6bc4f7..72ea49fe 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -763,30 +763,23 @@ class graph final { } std::ostream& _gsf_write(std::ostream& os) const { - using io::detail::option_bit; + using enum io::detail::option_bit; - const bool with_vertex_properties = - io::is_option_set(os, option_bit::with_vertex_properties); - const bool with_edge_properties = - io::is_option_set(os, option_bit::with_connection_properties); + const bool with_v_props = io::is_option_set(os, with_vertex_properties); + const bool with_e_props = io::is_option_set(os, with_connection_properties); // print graph metadata - os << std::format( - "{} {} {} {} {}\n", - static_cast(traits::c_directed_edge), - this->order(), - this->size(), - static_cast(with_vertex_properties), - static_cast(with_edge_properties) - ); + os << traits::c_directed_edge << ' ' << this->order() << ' ' << this->size() + << ' ' << static_cast(with_v_props) << ' ' << static_cast(with_e_props) + << '\n'; if constexpr (traits::c_writable) - if (with_vertex_properties) + if (with_v_props) for (const auto& vertex : this->vertices()) os << vertex.properties() << '\n'; if constexpr (traits::c_writable) { - if (with_edge_properties) { + if (with_e_props) { const auto print_out_edges = [this, &os](const id_type vertex_id) { for (const auto& edge : this->out_edges(vertex_id)) { if (edge.source() != vertex_id) @@ -818,16 +811,20 @@ class graph final { } std::istream& _gsf_read(std::istream& is) { - bool directed; - is >> directed; + using fmt_traits = io::detail::graph_fmt_traits; + + int dir_discr; + is >> dir_discr; - if (directed != traits::c_directed_edge) + if (dir_discr != fmt_traits::discriminator) throw std::ios_base::failure(std::format( - "Invalid graph specification: directional tag does not match - should be {}", - fmt_traits::type + "Invalid hypergraph specification: directional specifier {} does not match " + "expected {}", + dir_discr, + fmt_traits::discriminator )); - // read initial graph parameters + // read graph metadata id_type n_vertices, n_edges; is >> n_vertices >> n_edges; diff --git a/include/gl/io/graph_fmt_traits.hpp b/include/gl/io/graph_fmt_traits.hpp index 7b555ba6..0ecc4292 100644 --- a/include/gl/io/graph_fmt_traits.hpp +++ b/include/gl/io/graph_fmt_traits.hpp @@ -13,16 +13,18 @@ namespace gl::io::detail { template struct graph_fmt_traits; -template <> -struct graph_fmt_traits { - static constexpr std::string_view type = "directed"; - static constexpr std::string_view out_edges = "outgoing edges"; -}; - template <> struct graph_fmt_traits { + static constexpr int discriminator = 0; static constexpr std::string_view type = "undirected"; static constexpr std::string_view out_edges = "incident edges"; }; +template <> +struct graph_fmt_traits { + static constexpr int discriminator = 1; + static constexpr std::string_view type = "directed"; + static constexpr std::string_view out_edges = "outgoing edges"; +}; + } // namespace gl::io::detail diff --git a/include/hgl/hypergraph.hpp b/include/hgl/hypergraph.hpp index 6b19dc60..68d082a6 100644 --- a/include/hgl/hypergraph.hpp +++ b/include/hgl/hypergraph.hpp @@ -11,7 +11,8 @@ #include "hgl/directional_tags.hpp" #include "hgl/hypergraph_traits.hpp" #include "hgl/impl/impl_tags.hpp" -#include "hgl/io.hpp" +#include "hgl/io/core.hpp" +#include "hgl/io/hypergraph_fmt_traits.hpp" #include "hgl/util.hpp" #include @@ -819,15 +820,21 @@ class hypergraph final { } friend std::ostream& operator<<(std::ostream& os, const hypergraph& hg) { - // if (gl::io::is_option_set(os, gl::io::detail::option_bit::gsf)) - // return hg._gsf_write(os); + using enum io::detail::option_bit; + + if (gl::io::is_option_set(os, spec_fmt)) + return hg._hgsf_write(os); - if (gl::io::is_option_set(os, gl::io::detail::option_bit::verbose)) + if (gl::io::is_option_set(os, verbose)) return hg._verbose_write(os); return hg._concise_write(os); } + friend gl_attr_force_inline std::istream& operator>>(std::istream& is, hypergraph& hg) { + return hg._hgsf_read(is); + } + // --- friend declarations --- template @@ -991,6 +998,170 @@ class hypergraph final { return os; } + std::ostream& _hgsf_write(std::ostream& os) const { + using enum io::detail::option_bit; + using fmt_traits = io::detail::hypergraph_fmt_traits; + + const bool with_v_props = io::is_option_set(os, with_vertex_properties); + const bool with_he_props = io::is_option_set(os, with_connection_properties); + + // print hypergraph metadata + os << fmt_traits::discriminator << ' ' << this->order() << ' ' << this->size() << ' ' + << static_cast(with_v_props) << ' ' << static_cast(with_he_props) << '\n'; + + if constexpr (traits::c_writable) + if (with_v_props) + for (const auto& vertex : this->vertices()) + os << vertex.properties() << '\n'; + + for (const auto& hyperedge : this->hyperedges()) { + const auto he_id = hyperedge.id(); + + if constexpr (std::same_as) { + os << this->hyperedge_size(he_id); + for (const auto v : this->incident_vertex_ids(he_id)) + os << ' ' << v; + } + else if constexpr (std::same_as) { + os << this->tail_size(he_id) << ' ' << this->head_size(he_id); + for (const auto v : this->tail_vertex_ids(he_id)) + os << ' ' << v; + for (const auto v : this->head_vertex_ids(he_id)) + os << ' ' << v; + } + + if constexpr (traits::c_writable) { + if (with_he_props) + os << ' ' << hyperedge.properties(); + } + + os << '\n'; + } + + return os; + } + + std::istream& _hgsf_read(std::istream& is) { + using fmt_traits = io::detail::hypergraph_fmt_traits; + + int dir_discr; + is >> dir_discr; + + if (dir_discr != fmt_traits::discriminator) + throw std::ios_base::failure(std::format( + "Invalid hypergraph specification: directional specifier {} does not match " + "expected {}", + dir_discr, + fmt_traits::discriminator + )); + + // read hypergraph metadata + id_type n_vertices, n_hyperedges; + is >> n_vertices >> n_hyperedges; + + bool with_v_props, with_he_props; + is >> with_v_props >> with_he_props; + + if (with_v_props) { + if constexpr (not traits::c_readable) { + throw std::ios_base::failure( + "Invalid hypergraph specification: vertex_properties=true " + "when vertex_properties_type is not readable" + ); + } + else { + std::vector vertex_properties(n_vertices); + for (auto i = 0uz; i < n_vertices; ++i) + is >> vertex_properties[i]; + this->add_vertices_with(vertex_properties); + } + } + else { + this->add_vertices(n_vertices); + } + + if (with_he_props) { + if constexpr (not traits::c_readable) { + throw std::ios_base::failure( + "Invalid hypergraph specification: hyperedge_properties=true " + "when hyperedge_properties_type is not readable" + ); + } + } + + this->_read_hyperedges(is, n_hyperedges, with_he_props); + + return is; + } + + void _read_hyperedges(std::istream& is, const size_type n_hyperedges, const bool with_he_props) + requires std::same_as + { + for (auto _ = 0uz; _ < n_hyperedges; ++_) { + size_type size; + is >> size; + + std::vector v_ids(size); + for (auto i = 0uz; i < size; ++i) + is >> v_ids[i]; + + id_type new_he_id; + if constexpr (traits::c_readable) { + if (with_he_props) { + hyperedge_properties_type props; + is >> props; + new_he_id = this->add_hyperedge_with(std::move(props)).id(); + } + else { + new_he_id = this->add_hyperedge().id(); + } + } + else { + new_he_id = this->add_hyperedge().id(); + } + + for (const auto v_id : v_ids) + this->bind(v_id, new_he_id); + } + } + + void _read_hyperedges(std::istream& is, const size_type n_hyperedges, const bool with_he_props) + requires std::same_as + { + for (auto _ = 0uz; _ < n_hyperedges; ++_) { + size_type tail_size, head_size; + is >> tail_size >> head_size; + + std::vector tail_ids(tail_size); + for (auto i = 0uz; i < tail_size; ++i) + is >> tail_ids[i]; + + std::vector head_ids(head_size); + for (auto i = 0uz; i < head_size; ++i) + is >> head_ids[i]; + + id_type new_he_id; + if constexpr (traits::c_readable) { + if (with_he_props) { + hyperedge_properties_type props; + is >> props; + new_he_id = this->add_hyperedge_with(std::move(props)).id(); + } + else { + new_he_id = this->add_hyperedge().id(); + } + } + else { + new_he_id = this->add_hyperedge().id(); + } + + for (const auto v_id : tail_ids) + this->bind_tail(v_id, new_he_id); + for (const auto v_id : head_ids) + this->bind_head(v_id, new_he_id); + } + } + // --- data members --- size_type _n_vertices = 0uz; diff --git a/include/hgl/hypergraph_elements.hpp b/include/hgl/hypergraph_elements.hpp index a6c783ac..49ee37a1 100644 --- a/include/hgl/hypergraph_elements.hpp +++ b/include/hgl/hypergraph_elements.hpp @@ -7,7 +7,7 @@ #include "gl/types/core.hpp" #include "gl/vertex_descriptor.hpp" #include "hgl/constants.hpp" -#include "hgl/io.hpp" +#include "hgl/io/core.hpp" #include "hgl/traits.hpp" #include "hgl/types.hpp" diff --git a/include/hgl/io.hpp b/include/hgl/io.hpp index e772d5ad..d8b9932d 100644 --- a/include/hgl/io.hpp +++ b/include/hgl/io.hpp @@ -4,66 +4,4 @@ #pragma once -#include "gl/io/options.hpp" -#include "gl/io/options_manip.hpp" -#include "gl/io/ranges.hpp" -#include "hgl/directional_tags.hpp" - -#include - -namespace hgl::io { - -using gl::io::implicit_range; -using gl::io::implicit_range_formatter; -using gl::io::multiline_set_formatter; -using gl::io::range_formatter; -using gl::io::set_formatter; - -using gl::io::are_options_set; -using gl::io::clear_options; -using gl::io::is_option_set; -using gl::io::options_manip; -using gl::io::set_options; - -namespace detail { - -using gl::io::detail::build_mask; -using gl::io::detail::option_bit; - -} // namespace detail - -using gl::io::concise; -using gl::io::spec_fmt; -using gl::io::verbose; - -using gl::io::with_vertex_properties; -using gl::io::without_vertex_properties; - -inline const options_manip with_hyperedge_properties = - set_options(detail::option_bit::with_connection_properties); -inline const options_manip without_hyperedge_properties = - clear_options(detail::option_bit::with_connection_properties); - -using gl::io::with_properties; -using gl::io::without_properties; - -using gl::io::default_options; - -namespace detail { - -template -struct hypergraph_fmt_traits; - -template <> -struct hypergraph_fmt_traits { - static constexpr std::string_view type = "BF-directed"; -}; - -template <> -struct hypergraph_fmt_traits { - static constexpr std::string_view type = "undirected"; -}; - -} // namespace detail - -} // namespace hgl::io +#include "hgl/io/core.hpp" diff --git a/include/hgl/io/core.hpp b/include/hgl/io/core.hpp new file mode 100644 index 00000000..20a4aa39 --- /dev/null +++ b/include/hgl/io/core.hpp @@ -0,0 +1,52 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "gl/io/options.hpp" +#include "gl/io/options_manip.hpp" +#include "gl/io/ranges.hpp" +#include "hgl/directional_tags.hpp" + +#include + +namespace hgl::io { + +using gl::io::implicit_range; +using gl::io::implicit_range_formatter; +using gl::io::multiline_set_formatter; +using gl::io::range_formatter; +using gl::io::set_formatter; + +using gl::io::are_options_set; +using gl::io::clear_options; +using gl::io::is_option_set; +using gl::io::options_manip; +using gl::io::set_options; + +namespace detail { + +using gl::io::detail::build_mask; +using gl::io::detail::option_bit; + +} // namespace detail + +using gl::io::concise; +using gl::io::spec_fmt; +using gl::io::verbose; + +using gl::io::with_vertex_properties; +using gl::io::without_vertex_properties; + +inline const options_manip with_hyperedge_properties = + set_options(detail::option_bit::with_connection_properties); +inline const options_manip without_hyperedge_properties = + clear_options(detail::option_bit::with_connection_properties); + +using gl::io::with_properties; +using gl::io::without_properties; + +using gl::io::default_options; + +} // namespace hgl::io diff --git a/include/hgl/io/hypergraph_fmt_traits.hpp b/include/hgl/io/hypergraph_fmt_traits.hpp new file mode 100644 index 00000000..31c16f6f --- /dev/null +++ b/include/hgl/io/hypergraph_fmt_traits.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "hgl/directional_tags.hpp" + +#include + +namespace hgl::io::detail { + +template +struct hypergraph_fmt_traits; + +template <> +struct hypergraph_fmt_traits { + static constexpr int discriminator = 0; + static constexpr std::string_view type = "undirected"; +}; + +template <> +struct hypergraph_fmt_traits { + static constexpr int discriminator = 1; + static constexpr std::string_view type = "BF-directed"; +}; + +} // namespace hgl::io::detail From 209da0196f6b99d989488621953d8055d6e4792d Mon Sep 17 00:00:00 2001 From: SpectraL519 Date: Mon, 13 Apr 2026 15:36:27 +0200 Subject: [PATCH 10/10] hypergraph io tests --- include/gl/graph.hpp | 11 +- include/gl/io/graph_fio.hpp | 23 +- include/gl/io/options.hpp | 2 - include/hgl/io.hpp | 1 + include/hgl/io/hypergraph_fio.hpp | 55 +++ .../testing/{gl => common}/functional.hpp | 4 - tests/include/testing/common/io.hpp | 20 + .../testing/gl/{io_common.hpp => io.hpp} | 0 tests/include/testing/hgl/io.hpp | 65 +++ tests/source/gl/test_adjacency_list.cpp | 2 +- tests/source/gl/test_adjacency_matrix.cpp | 2 +- tests/source/gl/test_alg_dijkstra.cpp | 2 +- tests/source/gl/test_alg_prim_mst.cpp | 2 +- tests/source/gl/test_edge_descriptor.cpp | 2 +- tests/source/gl/test_graph.cpp | 2 +- tests/source/gl/test_graph_file_io.cpp | 86 ++-- tests/source/gl/test_graph_io.cpp | 380 +++++++++++------- tests/source/gl/test_properties.cpp | 2 +- tests/source/gl/test_vertex_descriptor.cpp | 2 +- tests/source/hgl/test_hypergraph_file_io.cpp | 155 +++++++ tests/source/hgl/test_hypergraph_io.cpp | 193 +++++++++ 21 files changed, 788 insertions(+), 223 deletions(-) create mode 100644 include/hgl/io/hypergraph_fio.hpp rename tests/include/testing/{gl => common}/functional.hpp (61%) rename tests/include/testing/gl/{io_common.hpp => io.hpp} (100%) create mode 100644 tests/include/testing/hgl/io.hpp create mode 100644 tests/source/hgl/test_hypergraph_file_io.cpp create mode 100644 tests/source/hgl/test_hypergraph_io.cpp diff --git a/include/gl/graph.hpp b/include/gl/graph.hpp index 72ea49fe..9bd41103 100644 --- a/include/gl/graph.hpp +++ b/include/gl/graph.hpp @@ -6,6 +6,7 @@ #include "gl/attributes/force_inline.hpp" #include "gl/constants.hpp" +#include "gl/directional_tags.hpp" #include "gl/graph_traits.hpp" #include "gl/impl/impl_tags.hpp" #include "gl/io/graph_fmt_traits.hpp" @@ -782,8 +783,9 @@ class graph final { if (with_e_props) { const auto print_out_edges = [this, &os](const id_type vertex_id) { for (const auto& edge : this->out_edges(vertex_id)) { - if (edge.source() != vertex_id) - continue; // vertex is not the source + if constexpr (std::same_as) + if (edge.other(vertex_id) > vertex_id) + continue; // deduplicate edges os << edge.source() << ' ' << edge.target() << ' ' << edge.properties() << '\n'; } @@ -798,8 +800,9 @@ class graph final { const auto print_out_edges = [this, &os](const id_type vertex_id) { for (const auto& edge : this->out_edges(vertex_id)) { - if (edge.source() != vertex_id) - continue; // vertex is not the source + if constexpr (std::same_as) + if (edge.other(vertex_id) > vertex_id) + continue; // deduplicate edges os << edge.source() << ' ' << edge.target() << '\n'; } }; diff --git a/include/gl/io/graph_fio.hpp b/include/gl/io/graph_fio.hpp index 4d7c8aa9..973c5ee3 100644 --- a/include/gl/io/graph_fio.hpp +++ b/include/gl/io/graph_fio.hpp @@ -9,18 +9,26 @@ #include #include -namespace gl::io { +namespace gl { +namespace io { struct write {}; struct append {}; -namespace detail { +} // namespace io + +namespace traits { template -concept c_io_save_mode = traits::c_one_of; +concept c_io_save_mode = traits::c_one_of; + +} // namespace traits + +namespace io { +namespace detail { -template +template requires(std::same_as) [[nodiscard]] std::ofstream open_outfile(const std::filesystem::path& path) { if (std::filesystem::exists(path)) @@ -37,7 +45,7 @@ requires(std::same_as) return file; } -template +template requires(std::same_as) [[nodiscard]] std::ofstream open_outfile(const std::filesystem::path& path) { if (not std::filesystem::exists(path)) @@ -87,7 +95,7 @@ requires(std::same_as) } // namespace detail -template +template void save( const GraphType& graph, const std::filesystem::path& path = "graph.gsf", @@ -114,4 +122,5 @@ template return graph; } -} // namespace gl::io +} // namespace io +} // namespace gl diff --git a/include/gl/io/options.hpp b/include/gl/io/options.hpp index c48da3a3..c49d9a8a 100644 --- a/include/gl/io/options.hpp +++ b/include/gl/io/options.hpp @@ -22,8 +22,6 @@ inline constexpr iword_type layout_options_mask = } // namespace detail -// TODO: add tests - inline constexpr options_manip concise{0ul, detail::layout_options_mask}; inline constexpr options_manip verbose{ detail::build_mask(detail::option_bit::verbose), detail::layout_options_mask diff --git a/include/hgl/io.hpp b/include/hgl/io.hpp index d8b9932d..5dd9b753 100644 --- a/include/hgl/io.hpp +++ b/include/hgl/io.hpp @@ -5,3 +5,4 @@ #pragma once #include "hgl/io/core.hpp" +#include "hgl/io/hypergraph_fio.hpp" diff --git a/include/hgl/io/hypergraph_fio.hpp b/include/hgl/io/hypergraph_fio.hpp new file mode 100644 index 00000000..44558c4e --- /dev/null +++ b/include/hgl/io/hypergraph_fio.hpp @@ -0,0 +1,55 @@ +// Copyright (c) 2024-2026 Jakub Musiał +// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl). +// Licensed under the MIT License. See the LICENSE file in the project root for full license information. + +#pragma once + +#include "gl/io/graph_fio.hpp" +#include "gl/io/options.hpp" +#include "gl/io/options_manip.hpp" +#include "hgl/hypergraph.hpp" + +#include +#include +#include + +namespace hgl::io { + +using gl::io::append; +using gl::io::write; + +namespace detail { + +using gl::io::detail::open_infile; +using gl::io::detail::open_outfile; + +} // namespace detail + +template +void save( + const HypergraphType& hypergraph, + const std::filesystem::path& path = "hypergraph.hgsf", + const std::initializer_list& options = {} +) { + std::ofstream file = detail::open_outfile(path); + + file << gl::io::spec_fmt; + for (auto option : options) + file << option; + + file << hypergraph; +} + +template +[[nodiscard]] HypergraphType load(const std::filesystem::path& path = "hypergraph.hgsf") { + std::ifstream file = detail::open_infile(path); + + file >> gl::io::spec_fmt; + + HypergraphType hypergraph; + file >> hypergraph; + + return hypergraph; +} + +} // namespace hgl::io diff --git a/tests/include/testing/gl/functional.hpp b/tests/include/testing/common/functional.hpp similarity index 61% rename from tests/include/testing/gl/functional.hpp rename to tests/include/testing/common/functional.hpp index aec8d3a9..11ceb417 100644 --- a/tests/include/testing/gl/functional.hpp +++ b/tests/include/testing/common/functional.hpp @@ -1,10 +1,6 @@ #pragma once -namespace gl_testing { - template void discard_result(T&&) { // do nothing } - -} // namespace gl_testing diff --git a/tests/include/testing/common/io.hpp b/tests/include/testing/common/io.hpp index f495ae7d..8f3015d5 100644 --- a/tests/include/testing/common/io.hpp +++ b/tests/include/testing/common/io.hpp @@ -3,6 +3,7 @@ #include "doctest.h" #include +#include #include #include #include @@ -23,3 +24,22 @@ struct StringMaker { }; } // namespace doctest + +namespace fs = std::filesystem; + +#define GL_REQUIRE_THROWS_FS_ERROR(expr, errc) \ + try { \ + expr; \ + FAIL("Expected `std::filesystem::filesystem_error` but no exception was thrown"); \ + } \ + catch (const fs::filesystem_error& e) { \ + const auto expected_code = std::make_error_code(errc); \ + if (e.code() != expected_code) { \ + FAIL(std::format( \ + "Expected error code {}, but got {}.", expected_code.value(), e.code().value() \ + )); \ + } \ + } \ + catch (...) { \ + FAIL("Expected `std::filesystem::filesystem_error` but caught a different exception"); \ + } diff --git a/tests/include/testing/gl/io_common.hpp b/tests/include/testing/gl/io.hpp similarity index 100% rename from tests/include/testing/gl/io_common.hpp rename to tests/include/testing/gl/io.hpp diff --git a/tests/include/testing/hgl/io.hpp b/tests/include/testing/hgl/io.hpp new file mode 100644 index 00000000..75f60b27 --- /dev/null +++ b/tests/include/testing/hgl/io.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "doctest.h" + +#include + +#include + +namespace hgl_testing { + +template +void verify_hypergraph_structure(const HypergraphType& actual, const HypergraphType& expected) { + REQUIRE_EQ(actual.order(), expected.order()); + REQUIRE_EQ(actual.size(), expected.size()); + + for (const auto& he_actual : actual.hyperedges()) { + const auto id = he_actual.id(); + REQUIRE(expected.has_hyperedge(id)); + + if constexpr (std::same_as) { + auto actual_vs = actual.incident_vertex_ids(id) | std::ranges::to(); + auto expected_vs = expected.incident_vertex_ids(id) | std::ranges::to(); + CHECK(std::ranges::is_permutation(actual_vs, expected_vs)); + } + else if constexpr (std::same_as< + typename HypergraphType::directional_tag, + hgl::bf_directed_t>) { + auto actual_tail = actual.tail_vertex_ids(id) | std::ranges::to(); + auto expected_tail = expected.tail_vertex_ids(id) | std::ranges::to(); + CHECK(std::ranges::is_permutation(actual_tail, expected_tail)); + + auto actual_head = actual.head_vertex_ids(id) | std::ranges::to(); + auto expected_head = expected.head_vertex_ids(id) | std::ranges::to(); + CHECK(std::ranges::is_permutation(actual_head, expected_head)); + } + } +} + +template +void verify_vertex_properties(const HypergraphType& actual, const HypergraphType& expected) { + const auto properties_proj = [](const auto& item) { return item.properties(); }; + + CHECK(std::ranges::equal( + actual.vertices(), + expected.vertices(), + std::ranges::equal_to{}, + properties_proj, + properties_proj + )); +} + +template +void verify_hyperedge_properties(const HypergraphType& actual, const HypergraphType& expected) { + const auto properties_proj = [](const auto& item) { return item.properties(); }; + + CHECK(std::ranges::equal( + actual.hyperedges(), + expected.hyperedges(), + std::ranges::equal_to{}, + properties_proj, + properties_proj + )); +} + +} // namespace hgl_testing diff --git a/tests/source/gl/test_adjacency_list.cpp b/tests/source/gl/test_adjacency_list.cpp index aa8a1ca7..b94ac9d9 100644 --- a/tests/source/gl/test_adjacency_list.cpp +++ b/tests/source/gl/test_adjacency_list.cpp @@ -1,5 +1,5 @@ +#include "testing/common/functional.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include #include diff --git a/tests/source/gl/test_adjacency_matrix.cpp b/tests/source/gl/test_adjacency_matrix.cpp index b3da0dbb..c11e968e 100644 --- a/tests/source/gl/test_adjacency_matrix.cpp +++ b/tests/source/gl/test_adjacency_matrix.cpp @@ -1,6 +1,6 @@ #include "gl/types/core.hpp" +#include "testing/common/functional.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include #include diff --git a/tests/source/gl/test_alg_dijkstra.cpp b/tests/source/gl/test_alg_dijkstra.cpp index bf249580..696e9ce5 100644 --- a/tests/source/gl/test_alg_dijkstra.cpp +++ b/tests/source/gl/test_alg_dijkstra.cpp @@ -1,8 +1,8 @@ #include "gl/constants.hpp" #include "gl/types/core.hpp" +#include "testing/common/functional.hpp" #include "testing/gl/alg_utils.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include #include diff --git a/tests/source/gl/test_alg_prim_mst.cpp b/tests/source/gl/test_alg_prim_mst.cpp index 4d449917..9ed4b5f7 100644 --- a/tests/source/gl/test_alg_prim_mst.cpp +++ b/tests/source/gl/test_alg_prim_mst.cpp @@ -1,7 +1,7 @@ #include "gl/impl/impl_tags.hpp" +#include "testing/common/functional.hpp" #include "testing/gl/alg_utils.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include #include diff --git a/tests/source/gl/test_edge_descriptor.cpp b/tests/source/gl/test_edge_descriptor.cpp index baf92139..6d48a6c9 100644 --- a/tests/source/gl/test_edge_descriptor.cpp +++ b/tests/source/gl/test_edge_descriptor.cpp @@ -1,5 +1,5 @@ +#include "testing/common/functional.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include "testing/gl/types.hpp" #include diff --git a/tests/source/gl/test_graph.cpp b/tests/source/gl/test_graph.cpp index aa959a58..62301928 100644 --- a/tests/source/gl/test_graph.cpp +++ b/tests/source/gl/test_graph.cpp @@ -1,6 +1,6 @@ #include "doctest.h" +#include "testing/common/functional.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include "testing/gl/types.hpp" #include diff --git a/tests/source/gl/test_graph_file_io.cpp b/tests/source/gl/test_graph_file_io.cpp index ea3ca2d5..4c65d1b7 100644 --- a/tests/source/gl/test_graph_file_io.cpp +++ b/tests/source/gl/test_graph_file_io.cpp @@ -1,68 +1,58 @@ +#include "doctest.h" +#include "testing/common/functional.hpp" +#include "testing/common/io.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" -#include "testing/gl/io_common.hpp" +#include "testing/gl/io.hpp" #include #include #include -#include - #include -namespace fs = std::filesystem; - namespace gl_testing { -#define GL_REQUIRE_THROWS_FS_ERROR(expr, errc) \ - try { \ - expr; \ - FAIL("Expected `std::filesystem::filesystem_error` but no exception was thrown"); \ - } \ - catch (const fs::filesystem_error& e) { \ - const auto expected_code = std::make_error_code(errc); \ - if (e.code() != expected_code) { \ - FAIL(std::format( \ - "Expected error code {}, but got {}.", expected_code.value(), e.code().value() \ - )); \ - } \ - } \ - catch (...) { \ - FAIL("Expected `std::filesystem::filesystem_error` but caught a different exception"); \ - } - TEST_SUITE_BEGIN("test_graph_file_io"); -// Tests covering only the io functionality with the graph specification format enabled - -struct test_graph_file_io { - using traits_type = gl::graph_traits; - using sut_type = gl::graph; +template +struct test_graph_file_io_fixture { + using sut_type = SutType; + using directional_tag = typename sut_type::directional_tag; - test_graph_file_io() { + test_graph_file_io_fixture() { sut_out = gl::topology::clique(n_vertices); // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; - for (const auto& vertex : sut_out.vertices()) { + + for (const auto& vertex : sut_out.vertices()) vertex.properties() = std::format("vertex_{}", v_idx++); - for (const auto& edge : sut_out.out_edges(vertex)) - edge.properties() = std::format("edge_{}", e_idx++); + + for (const auto& vertex : sut_out.vertices()) { + for (const auto& edge : sut_out.out_edges(vertex)) { + if constexpr (std::same_as) { + if (edge.source() == vertex.id()) + edge.properties() = std::format("edge_{}", e_idx++); + } + else { + edge.properties() = std::format("edge_{}", e_idx++); + } + } } } - ~test_graph_file_io() { + ~test_graph_file_io_fixture() { fs::remove(path); } const gl::size_type n_vertices = 5ull; sut_type sut_out; - fs::path path{"test_directed_graph_file_io.gsf"}; + fs::path path{"test_graph_file_io.gsf"}; }; -TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_template) { - using fixture_type = test_graph_file_io; +TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, graph_file_io_template) { + using fixture_type = test_graph_file_io_fixture; using sut_type = typename fixture_type::sut_type; fixture_type fixture; @@ -77,7 +67,7 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te ); } - SUBCASE("load shoul throw if a file does not exist") { + SUBCASE("load should throw if a file does not exist") { GL_REQUIRE_THROWS_FS_ERROR( discard_result(gl::io::load(fixture.path)), std::errc::no_such_file_or_directory @@ -119,18 +109,22 @@ TEST_CASE_TEMPLATE_DEFINE("graph file io tests", SutType, directional_tag_sut_te } } +// clang-format off + TEST_CASE_TEMPLATE_INSTANTIATE( - directional_tag_sut_template, - gl::graph>, // directed adj list - gl::graph>, // undirected adj list - gl::graph>, // directed flat adj list - gl::graph>, // undirected flat adj list - gl::graph>, // directed adj matrix - gl::graph>, // undirected adj matrix - gl::graph>, // directed flat adj matrix - gl::graph> // undirected flat adj matrix + graph_file_io_template, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph> ); +// clang-format on + TEST_SUITE_END(); // test_graph_file_io } // namespace gl_testing diff --git a/tests/source/gl/test_graph_io.cpp b/tests/source/gl/test_graph_io.cpp index d1290787..36c4ab6a 100644 --- a/tests/source/gl/test_graph_io.cpp +++ b/tests/source/gl/test_graph_io.cpp @@ -1,36 +1,43 @@ +#include "doctest.h" #include "gl/io/options.hpp" -#include "testing/gl/constants.hpp" -#include "testing/gl/io_common.hpp" +#include "gl/io/options_manip.hpp" +#include "testing/gl/io.hpp" #include #include #include -#include - #include namespace gl_testing { TEST_SUITE_BEGIN("test_graph_io"); -// Tests covering only the io functionality with the graph specification format enabled - -struct test_directed_graph_io { - using traits_type = gl::graph_traits; - using sut_type = gl::graph; +template +struct test_graph_io_fixture { + using sut_type = SutType; + using directional_tag = typename sut_type::directional_tag; - test_directed_graph_io() { + test_graph_io_fixture() { ss << gl::io::spec_fmt; sut_out = gl::topology::clique(n_vertices); // prepare vertex and edge properties std::size_t v_idx = 0, e_idx = 0; - for (const auto& vertex : sut_out.vertices()) { + for (const auto& vertex : sut_out.vertices()) vertex.properties() = std::format("vertex_{}", v_idx++); - for (const auto& edge : sut_out.out_edges(vertex)) - edge.properties() = std::format("edge_{}", e_idx++); + + for (const auto& vertex : sut_out.vertices()) { + for (const auto& edge : sut_out.out_edges(vertex)) { + if constexpr (std::same_as) { + if (edge.source() == vertex.id()) + edge.properties() = std::format("edge_{}", e_idx++); + } + else { + edge.properties() = std::format("edge_{}", e_idx++); + } + } } } @@ -41,198 +48,267 @@ struct test_directed_graph_io { std::stringstream ss; }; -TEST_CASE_FIXTURE( - test_directed_graph_io, "io read should throw if the directional tag doesn't match" -) { - using undir_traits = gl::graph_traits; +template +struct wrong_dir_tag {}; - gl::graph invalid_sut_out{n_vertices}; - ss << invalid_sut_out; +template <> +struct wrong_dir_tag { + using type = gl::directed_t; +}; - CHECK_THROWS_AS(ss >> sut_in, std::ios_base::failure); -} +template <> +struct wrong_dir_tag { + using type = gl::undirected_t; +}; -TEST_CASE_FIXTURE( - test_directed_graph_io, - "io read should throw for a stream with vertex properties if vertex_properties_type is not " - "readable" -) { - ss << gl::io::with_vertex_properties << sut_out; +TEST_CASE_TEMPLATE_DEFINE("graph io tests", SutType, graph_io_template) { + using fixture_type = test_graph_io_fixture; + using sut_type = typename fixture_type::sut_type; + using dir_tag = typename sut_type::directional_tag; - using not_readable_vp_traits = - gl::graph_traits; - gl::graph invalid_sut_in; + fixture_type fixture; - CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); -} + SUBCASE("io read should throw if the directional tag doesn't match") { + using wrong_tag = typename wrong_dir_tag::type; + using wrong_traits = gl::graph_traits; + gl::graph invalid_sut_out{fixture.n_vertices}; -TEST_CASE_FIXTURE( - test_directed_graph_io, - "io read should throw for a stream with edge properties if edge_properties_type is not readable" -) { - ss << gl::io::with_edge_properties << sut_out; + std::stringstream err_ss; + err_ss << gl::io::spec_fmt << invalid_sut_out; - using not_readable_vp_traits = - gl::graph_traits; - gl::graph invalid_sut_in; + CHECK_THROWS_AS(err_ss >> fixture.sut_in, std::ios_base::failure); + } - CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); -} + SUBCASE("io read should throw for a stream with vertex properties if vertex_properties_type is " + "not readable") { + fixture.ss << gl::io::with_vertex_properties << fixture.sut_out; -TEST_CASE_FIXTURE( - test_directed_graph_io, - "io operators should properly write and read the graph from a stream with no additional " - "options enabled" -) { - ss << sut_out; - ss >> sut_in; + using not_readable_vp_traits = gl::graph_traits; + gl::graph invalid_sut_in; - verify_graph_structure(sut_in, sut_out); -} + CHECK_THROWS_AS(fixture.ss >> invalid_sut_in, std::ios_base::failure); + } -TEST_CASE_FIXTURE( - test_directed_graph_io, - "io operators should properly write and read the graph from a stream with vertex properties" -) { - ss << gl::io::with_vertex_properties << sut_out; - ss >> sut_in; + SUBCASE("io read should throw for a stream with edge properties if edge_properties_type is not " + "readable") { + fixture.ss << gl::io::with_edge_properties << fixture.sut_out; - verify_graph_structure(sut_in, sut_out); - verify_vertex_properties(sut_in, sut_out); -} + using not_readable_hp_traits = gl::graph_traits; + gl::graph invalid_sut_in; -TEST_CASE_FIXTURE( - test_directed_graph_io, - "io operators should properly write and read the graph from a stream with edge properties" -) { - ss << gl::io::with_edge_properties << sut_out; - ss >> sut_in; + CHECK_THROWS_AS(fixture.ss >> invalid_sut_in, std::ios_base::failure); + } - verify_graph_structure(sut_in, sut_out); - verify_edge_properties(sut_in, sut_out); -} + SUBCASE("io operators should properly write and read the graph from a stream with no " + "additional options enabled") { + fixture.ss << fixture.sut_out; + fixture.ss >> fixture.sut_in; -TEST_CASE_FIXTURE( - test_directed_graph_io, - "io operators should properly write and read the graph from a stream with both vertex and edge " - "properties" -) { - ss << gl::io::with_vertex_properties << gl::io::with_edge_properties << sut_out; - ss >> sut_in; - - verify_graph_structure(sut_in, sut_out); - verify_vertex_properties(sut_in, sut_out); - verify_edge_properties(sut_in, sut_out); -} + verify_graph_structure(fixture.sut_in, fixture.sut_out); + } -struct test_undirected_graph_io { - using traits_type = gl::graph_traits; - using sut_type = gl::graph; + SUBCASE("io operators should properly write and read the graph from a stream with vertex " + "properties") { + fixture.ss << gl::io::with_vertex_properties << fixture.sut_out; + fixture.ss >> fixture.sut_in; - test_undirected_graph_io() { - ss << gl::io::spec_fmt; + verify_graph_structure(fixture.sut_in, fixture.sut_out); + verify_vertex_properties(fixture.sut_in, fixture.sut_out); + } - sut_out = gl::topology::clique(n_vertices); + SUBCASE("io operators should properly write and read the graph from a stream with edge " + "properties") { + fixture.ss << gl::io::with_edge_properties << fixture.sut_out; + fixture.ss >> fixture.sut_in; - // prepare vertex and edge properties - std::size_t v_idx = 0, e_idx = 0; - for (const auto& vertex : sut_out.vertices()) { - vertex.properties() = std::format("vertex_{}", v_idx++); - for (const auto& edge : sut_out.out_edges(vertex)) - if (edge.source() == vertex.id()) - edge.properties() = std::format("edge_{}", e_idx++); - } + verify_graph_structure(fixture.sut_in, fixture.sut_out); + verify_edge_properties(fixture.sut_in, fixture.sut_out); } - const gl::size_type n_vertices = 5ull; - sut_type sut_out; - sut_type sut_in; + SUBCASE("io operators should properly write and read the graph from a stream with both vertex " + "and edge properties") { + fixture.ss << gl::io::with_properties << fixture.sut_out; + fixture.ss >> fixture.sut_in; - std::stringstream ss; -}; + verify_graph_structure(fixture.sut_in, fixture.sut_out); + verify_vertex_properties(fixture.sut_in, fixture.sut_out); + verify_edge_properties(fixture.sut_in, fixture.sut_out); + } +} -TEST_CASE_FIXTURE( - test_undirected_graph_io, "io read should throw if the directional tag doesn't match" -) { - using dir_traits = gl::graph_traits; +// clang-format off - gl::graph invalid_sut_out{n_vertices}; - ss << invalid_sut_out; +TEST_CASE_TEMPLATE_INSTANTIATE( + graph_io_template, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph>, + gl::graph> +); - CHECK_THROWS_AS(ss >> sut_in, std::ios_base::failure); -} +// clang-format on -TEST_CASE_FIXTURE( - test_undirected_graph_io, - "io read should throw for a stream with vertex properties if vertex_properties_type is not " - "readable" -) { - ss << gl::io::with_vertex_properties << sut_out; +struct test_graph_io_options { + using opt = gl::io::detail::option_bit; - using not_readable_vp_traits = - gl::graph_traits; - gl::graph invalid_sut_in; + [[nodiscard]] bool is_set(opt bit) { + return gl::io::is_option_set(ss, bit); + } - CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); + std::stringstream ss; +}; + +TEST_CASE_FIXTURE(test_graph_io_options, "layout options should be mutually exclusive") { + REQUIRE_FALSE(is_set(opt::verbose)); + REQUIRE_FALSE(is_set(opt::spec_fmt)); + + // verbose - spec_fmt toggling + ss << gl::io::verbose; + CHECK(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); + + ss << gl::io::spec_fmt; + CHECK_FALSE(is_set(opt::verbose)); + CHECK(is_set(opt::spec_fmt)); + + ss << gl::io::verbose; + CHECK(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); + + // concise toggling + ss << gl::io::concise; + CHECK_FALSE(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); + + ss << gl::io::spec_fmt; + CHECK(is_set(opt::spec_fmt)); + ss << gl::io::concise; + CHECK_FALSE(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); } TEST_CASE_FIXTURE( - test_undirected_graph_io, - "io read should throw for a stream with edge properties if edge_properties_type is not readable" + test_graph_io_options, "with_vertex_properties should toggle only the vertex properties option" ) { - ss << gl::io::with_edge_properties << sut_out; - - using not_readable_vp_traits = - gl::graph_traits; - gl::graph invalid_sut_in; + REQUIRE_FALSE(is_set(opt::with_vertex_properties)); + REQUIRE_FALSE(is_set(opt::with_connection_properties)); - CHECK_THROWS_AS(ss >> invalid_sut_in, std::ios_base::failure); + ss << gl::io::with_vertex_properties; + CHECK(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); } TEST_CASE_FIXTURE( - test_undirected_graph_io, - "io operators should properly write and read the graph from a stream with no additional " - "options enabled" + test_graph_io_options, + "without_vertex_properties should disable only the vertex properties option" ) { - ss << sut_out; - ss >> sut_in; + ss << gl::io::with_properties; + REQUIRE(is_set(opt::with_vertex_properties)); + REQUIRE(is_set(opt::with_connection_properties)); - verify_graph_structure(sut_in, sut_out); + ss << gl::io::without_vertex_properties; + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK(is_set(opt::with_connection_properties)); } TEST_CASE_FIXTURE( - test_undirected_graph_io, - "io operators should properly write and read the graph from a stream with vertex properties" + test_graph_io_options, + "with_edge_properties should toggle only the connection properties option" ) { - ss << gl::io::with_vertex_properties << sut_out; - ss >> sut_in; + REQUIRE_FALSE(is_set(opt::with_vertex_properties)); + REQUIRE_FALSE(is_set(opt::with_connection_properties)); - verify_graph_structure(sut_in, sut_out); - verify_vertex_properties(sut_in, sut_out); + ss << gl::io::with_edge_properties; + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK(is_set(opt::with_connection_properties)); } TEST_CASE_FIXTURE( - test_undirected_graph_io, - "io operators should properly write and read the graph from a stream with edge properties" + test_graph_io_options, + "without_edge_properties should disable only the connection properties option" ) { - ss << gl::io::with_edge_properties << sut_out; - ss >> sut_in; + ss << gl::io::with_properties; + REQUIRE(is_set(opt::with_vertex_properties)); + REQUIRE(is_set(opt::with_connection_properties)); - verify_graph_structure(sut_in, sut_out); - verify_edge_properties(sut_in, sut_out); + ss << gl::io::without_edge_properties; + CHECK(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); } TEST_CASE_FIXTURE( - test_undirected_graph_io, - "io operators should properly write and read the graph from a stream with both vertex and edge " - "properties" + test_graph_io_options, + "with(out)_properties should toggle both vertex and connection properties options" ) { - ss << gl::io::with_vertex_properties << gl::io::with_edge_properties << sut_out; - ss >> sut_in; + REQUIRE_FALSE(is_set(opt::with_vertex_properties)); + REQUIRE_FALSE(is_set(opt::with_connection_properties)); + + ss << gl::io::with_properties; + CHECK(is_set(opt::with_vertex_properties)); + CHECK(is_set(opt::with_connection_properties)); + + // both enabled + ss << gl::io::without_properties; + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); + + // one enabled + ss << gl::io::with_vertex_properties; + CHECK(is_set(opt::with_vertex_properties)); + ss << gl::io::without_properties; + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); + + ss << gl::io::with_edge_properties; + CHECK(is_set(opt::with_connection_properties)); + ss << gl::io::without_properties; + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); +} - verify_graph_structure(sut_in, sut_out); - verify_vertex_properties(sut_in, sut_out); - verify_edge_properties(sut_in, sut_out); +TEST_CASE_FIXTURE(test_graph_io_options, "default_options should disable all option flags") { + // concise layout + ss << gl::io::with_properties; + REQUIRE_FALSE(is_set(opt::verbose)); + REQUIRE_FALSE(is_set(opt::spec_fmt)); + REQUIRE(is_set(opt::with_vertex_properties)); + REQUIRE(is_set(opt::with_connection_properties)); + + ss << gl::io::default_options; + CHECK_FALSE(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); + + // verbose layout + ss << gl::io::verbose << gl::io::with_properties; + REQUIRE(is_set(opt::verbose)); + REQUIRE_FALSE(is_set(opt::spec_fmt)); + REQUIRE(is_set(opt::with_vertex_properties)); + REQUIRE(is_set(opt::with_connection_properties)); + + ss << gl::io::default_options; + CHECK_FALSE(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); + + // specification layout + ss << gl::io::spec_fmt << gl::io::with_properties; + REQUIRE_FALSE(is_set(opt::verbose)); + REQUIRE(is_set(opt::spec_fmt)); + REQUIRE(is_set(opt::with_vertex_properties)); + REQUIRE(is_set(opt::with_connection_properties)); + + ss << gl::io::default_options; + CHECK_FALSE(is_set(opt::verbose)); + CHECK_FALSE(is_set(opt::spec_fmt)); + CHECK_FALSE(is_set(opt::with_vertex_properties)); + CHECK_FALSE(is_set(opt::with_connection_properties)); } TEST_SUITE_END(); // test_graph_io diff --git a/tests/source/gl/test_properties.cpp b/tests/source/gl/test_properties.cpp index 0c58c13f..693e6340 100644 --- a/tests/source/gl/test_properties.cpp +++ b/tests/source/gl/test_properties.cpp @@ -1,4 +1,4 @@ -#include "testing/gl/functional.hpp" +#include "testing/common/functional.hpp" #include diff --git a/tests/source/gl/test_vertex_descriptor.cpp b/tests/source/gl/test_vertex_descriptor.cpp index 4981c211..d5d8c480 100644 --- a/tests/source/gl/test_vertex_descriptor.cpp +++ b/tests/source/gl/test_vertex_descriptor.cpp @@ -1,5 +1,5 @@ +#include "testing/common/functional.hpp" #include "testing/gl/constants.hpp" -#include "testing/gl/functional.hpp" #include "testing/gl/types.hpp" #include diff --git a/tests/source/hgl/test_hypergraph_file_io.cpp b/tests/source/hgl/test_hypergraph_file_io.cpp new file mode 100644 index 00000000..7ea596a4 --- /dev/null +++ b/tests/source/hgl/test_hypergraph_file_io.cpp @@ -0,0 +1,155 @@ +#include "doctest.h" +#include "testing/common/functional.hpp" +#include "testing/common/io.hpp" +#include "testing/hgl/io.hpp" + +#include +#include + +#include + +namespace hgl_testing { + +TEST_SUITE_BEGIN("test_hypergraph_file_io"); + +template +struct test_hypergraph_file_io_fixture { + using sut_type = SutType; + using directional_tag = typename sut_type::directional_tag; + + test_hypergraph_file_io_fixture() { + sut_out.add_vertices(n_vertices); + + std::size_t v_idx = 0; + for (const auto& vertex : sut_out.vertices()) + vertex.properties() = std::format("vertex_{}", v_idx++); + + auto add_edge = [&](const std::string& name) { + return sut_out.add_hyperedge_with(hgl::name_property{name}).id(); + }; + + // Construct graph based on directional tag + if constexpr (std::same_as) { + auto he1 = add_edge("edge_0"); + sut_out.bind(0, he1); + sut_out.bind(1, he1); + sut_out.bind(2, he1); + + auto he2 = add_edge("edge_1"); + sut_out.bind(1, he2); + sut_out.bind(2, he2); + sut_out.bind(3, he2); + } + else if constexpr (std::same_as) { + auto he1 = add_edge("edge_0"); + sut_out.bind_tail(0, he1); + sut_out.bind_tail(1, he1); + sut_out.bind_head(2, he1); + + auto he2 = add_edge("edge_1"); + sut_out.bind_tail(2, he2); + sut_out.bind_head(3, he2); + sut_out.bind_head(4, he2); + } + } + + ~test_hypergraph_file_io_fixture() { + fs::remove(path); + } + + const hgl::size_type n_vertices = 5uz; + sut_type sut_out; + + fs::path path{"test_hypergraph_file_io.hgsf"}; +}; + +TEST_CASE_TEMPLATE_DEFINE("hypergraph file io tests", SutType, hypergraph_file_io_template) { + using fixture_type = test_hypergraph_file_io_fixture; + using sut_type = typename fixture_type::sut_type; + + fixture_type fixture; + + SUBCASE("save should throw in the write mode if a file already exists") { + std::ofstream file(fixture.path); + if (not file.is_open()) + FAIL("Could not initialize an empty file"); + + GL_REQUIRE_THROWS_FS_ERROR( + hgl::io::save(fixture.sut_out, fixture.path), std::errc::file_exists + ); + } + + SUBCASE("load should throw if a file does not exist") { + GL_REQUIRE_THROWS_FS_ERROR( + discard_result(hgl::io::load(fixture.path)), + std::errc::no_such_file_or_directory + ); + } + + SUBCASE("file io should properly save and load a hypergraph in a hgsf format") { + hgl::io::save(fixture.sut_out, fixture.path); + const auto sut_in = hgl::io::load(fixture.path); + + verify_hypergraph_structure(sut_in, fixture.sut_out); + } + + SUBCASE("file io should properly save and load a hypergraph in a hgsf format with vertex " + "properties") { + hgl::io::save(fixture.sut_out, fixture.path, {hgl::io::with_vertex_properties}); + const auto sut_in = hgl::io::load(fixture.path); + + verify_hypergraph_structure(sut_in, fixture.sut_out); + verify_vertex_properties(sut_in, fixture.sut_out); + } + + SUBCASE("file io should properly save and load a hypergraph in a hgsf format with hyperedge " + "properties") { + hgl::io::save(fixture.sut_out, fixture.path, {hgl::io::with_hyperedge_properties}); + const auto sut_in = hgl::io::load(fixture.path); + + verify_hypergraph_structure(sut_in, fixture.sut_out); + verify_hyperedge_properties(sut_in, fixture.sut_out); + } + + SUBCASE("file io should properly save and load a hypergraph in a hgsf format with vertex and " + "hyperedge properties") { + hgl::io::save(fixture.sut_out, fixture.path, {hgl::io::with_properties}); + const auto sut_in = hgl::io::load(fixture.path); + + verify_hypergraph_structure(sut_in, fixture.sut_out); + verify_vertex_properties(sut_in, fixture.sut_out); + verify_hyperedge_properties(sut_in, fixture.sut_out); + } +} + +// clang-format off + +TEST_CASE_TEMPLATE_INSTANTIATE( + hypergraph_file_io_template, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph> +); + +// clang-format on + +TEST_SUITE_END(); // test_hypergraph_file_io + +} // namespace hgl_testing diff --git a/tests/source/hgl/test_hypergraph_io.cpp b/tests/source/hgl/test_hypergraph_io.cpp new file mode 100644 index 00000000..655d3c5e --- /dev/null +++ b/tests/source/hgl/test_hypergraph_io.cpp @@ -0,0 +1,193 @@ +#include "doctest.h" +#include "testing/hgl/io.hpp" + +#include +#include + +#include +#include + +namespace hgl_testing { + +TEST_SUITE_BEGIN("test_hypergraph_io"); + +template +struct test_hypergraph_io_fixture { + using sut_type = SutType; + using directional_tag = typename sut_type::directional_tag; + + test_hypergraph_io_fixture() { + ss << hgl::io::spec_fmt; + + sut_out.add_vertices(n_vertices); + + std::size_t v_idx = 0; + for (const auto& vertex : sut_out.vertices()) + vertex.properties() = std::format("vertex_{}", v_idx++); + + auto add_edge = [&](const std::string& name) { + return sut_out.add_hyperedge_with(hgl::name_property{name}).id(); + }; + + // Construct graph based on directional tag + if constexpr (std::same_as) { + auto he1 = add_edge("edge_0"); + sut_out.bind(0, he1); + sut_out.bind(1, he1); + sut_out.bind(2, he1); + + auto he2 = add_edge("edge_1"); + sut_out.bind(1, he2); + sut_out.bind(2, he2); + sut_out.bind(3, he2); + + auto he3 = add_edge("edge_2"); + sut_out.bind(0, he3); + sut_out.bind(3, he3); + } + else if constexpr (std::same_as) { + auto he1 = add_edge("edge_0"); + sut_out.bind_tail(0, he1); + sut_out.bind_tail(1, he1); + sut_out.bind_head(2, he1); + + auto he2 = add_edge("edge_1"); + sut_out.bind_tail(2, he2); + sut_out.bind_head(3, he2); + sut_out.bind_head(4, he2); + + auto he3 = add_edge("edge_2"); + sut_out.bind_tail(1, he3); + sut_out.bind_head(4, he3); + } + } + + const hgl::size_type n_vertices = 5uz; + sut_type sut_out; + sut_type sut_in; + + std::stringstream ss; +}; + +template +struct wrong_dir_tag {}; + +template <> +struct wrong_dir_tag { + using type = hgl::bf_directed_t; +}; + +template <> +struct wrong_dir_tag { + using type = hgl::undirected_t; +}; + +TEST_CASE_TEMPLATE_DEFINE("hypergraph io tests", SutType, hypergraph_io_template) { + using fixture_type = test_hypergraph_io_fixture; + using sut_type = typename fixture_type::sut_type; + using dir_tag = typename sut_type::directional_tag; + + fixture_type fixture; + + SUBCASE("io read should throw if the directional tag doesn't match") { + // Resolve the opposite directional tag + using wrong_tag = typename wrong_dir_tag::type; + using wrong_traits = hgl::hypergraph_traits; + hgl::hypergraph invalid_sut_out{fixture.n_vertices}; + + std::stringstream err_ss; + err_ss << hgl::io::spec_fmt << invalid_sut_out; + + CHECK_THROWS_AS(err_ss >> fixture.sut_in, std::ios_base::failure); + } + + SUBCASE("io read should throw for a stream with vertex properties if vertex_properties_type is " + "not readable") { + fixture.ss << hgl::io::with_vertex_properties << fixture.sut_out; + + using not_readable_vp_traits = + hgl::hypergraph_traits; + hgl::hypergraph invalid_sut_in; + + CHECK_THROWS_AS(fixture.ss >> invalid_sut_in, std::ios_base::failure); + } + + SUBCASE("io read should throw for a stream with hyperedge properties if " + "hyperedge_properties_type is not readable") { + fixture.ss << hgl::io::with_hyperedge_properties << fixture.sut_out; + + using not_readable_hp_traits = + hgl::hypergraph_traits; + hgl::hypergraph invalid_sut_in; + + CHECK_THROWS_AS(fixture.ss >> invalid_sut_in, std::ios_base::failure); + } + + SUBCASE("io operators should properly write and read the hypergraph from a stream with no " + "additional options enabled") { + fixture.ss << fixture.sut_out; + fixture.ss >> fixture.sut_in; + + verify_hypergraph_structure(fixture.sut_in, fixture.sut_out); + } + + SUBCASE("io operators should properly write and read the hypergraph from a stream with vertex " + "properties") { + fixture.ss << hgl::io::with_vertex_properties << fixture.sut_out; + fixture.ss >> fixture.sut_in; + + verify_hypergraph_structure(fixture.sut_in, fixture.sut_out); + verify_vertex_properties(fixture.sut_in, fixture.sut_out); + } + + SUBCASE("io operators should properly write and read the hypergraph from a stream with " + "hyperedge properties") { + fixture.ss << hgl::io::with_hyperedge_properties << fixture.sut_out; + fixture.ss >> fixture.sut_in; + + verify_hypergraph_structure(fixture.sut_in, fixture.sut_out); + verify_hyperedge_properties(fixture.sut_in, fixture.sut_out); + } + + SUBCASE("io operators should properly write and read the hypergraph from a stream with both " + "vertex and hyperedge properties") { + fixture.ss << hgl::io::with_properties << fixture.sut_out; + fixture.ss >> fixture.sut_in; + + verify_hypergraph_structure(fixture.sut_in, fixture.sut_out); + verify_vertex_properties(fixture.sut_in, fixture.sut_out); + verify_hyperedge_properties(fixture.sut_in, fixture.sut_out); + } +} + +// clang-format off + +TEST_CASE_TEMPLATE_INSTANTIATE( + hypergraph_io_template, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph>, + hgl::hypergraph> +); + +// clang-format on + +TEST_SUITE_END(); // test_hypergraph_io + +} // namespace hgl_testing