Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
graph_utils.hh
Go to the documentation of this file.
1#ifndef UTOPIA_DATAIO_GRAPH_UTILS_HH
2#define UTOPIA_DATAIO_GRAPH_UTILS_HH
3
4#include <tuple>
5
6#include <boost/graph/adjacency_list.hpp>
7#include <boost/graph/adjacency_matrix.hpp>
8#include <boost/graph/graph_traits.hpp>
9#include <boost/graph/properties.hpp>
10#include <boost/hana/all_of.hpp>
11#include <boost/hana/ext/std/tuple.hpp>
12#include <boost/hana/for_each.hpp>
13
14#include <hdf5.h>
15
16#include "../core/logging.hh"
17#include "../core/type_traits.hh"
18#include "../core/graph/iterator.hh"
19#include "hdfdataset.hh"
20#include "hdfgroup.hh"
21
22using namespace std::literals::string_literals;
23
24namespace Utopia {
25namespace DataIO {
31namespace GraphUtilsHelper{
32
47template <typename Graph>
48std::pair<std::shared_ptr<HDFDataset>,
49 std::shared_ptr<HDFDataset>>
50setup_graph_containers(const Graph& g, const std::shared_ptr<HDFGroup>& grp)
51{
52 // Collect some information on the graph
53 const auto num_vertices = boost::num_vertices(g);
54 const auto num_edges = boost::num_edges(g);
55
56 // Get a logger to use here (Note: needs to have been setup beforehand)
57 spdlog::get("data_io")->info("Saving graph with {} vertices and {} edges "
58 "...", num_vertices, num_edges);
59
60 // Initialize datasets to store vertices and edges in
61 auto dset_vertices = grp->open_dataset("_vertices", { num_vertices });
62 auto dset_edges = grp->open_dataset("_edges", { 2, num_edges });
63 // NOTE Need shape to be {2, num_edges} because `write` writes line by line.
64
65 // Set attributes
66 dset_vertices->add_attribute("dim_name__0", "vertex_idx");
67 dset_vertices->add_attribute("coords_mode__vertex_idx", "trivial");
68
69 dset_edges->add_attribute("dim_name__0", "label");
70 dset_edges->add_attribute("coords_mode__label", "values");
71 dset_edges->add_attribute("coords__label",
72 std::vector<std::string>{"source", "target"});
73 dset_edges->add_attribute("dim_name__1", "edge_idx");
74 dset_edges->add_attribute("coords_mode__edge_idx", "trivial");
75
76 return std::make_pair(dset_vertices, dset_edges);
77}
78
80template <typename First, typename Second, typename... Tail>
81constexpr std::tuple<Tail...> tuple_tail(const std::tuple<First, Second,
82 Tail...>& t)
83{
84 return std::apply([](auto, auto, auto... tail) {
85 return std::make_tuple(tail...);}, t);
86}
87
90template <typename CoordT>
92{
93 if constexpr (Utils::is_string_v<CoordT>) {
94 return std::vector<std::string>{};
95 }
96 else {
97 return std::vector<CoordT>{};
98 }
99}
100
102enum class EntityKind {
103 vertex,
104 edge
105};
106
130template <EntityKind entity_kind,
131 typename Graph,
132 typename NWGroup,
133 typename ItPair>
134auto generate_write_function(const Graph& g,
135 NWGroup&& nw_grp,
136 std::string label,
137 ItPair&& it_pair,
138 std::size_t num_entities)
139{
140 static_assert(entity_kind == EntityKind::vertex or
142 "Error, 'entity_kind' has to be either 'vertex' or 'edge'");
143
144 return
145 [&g,
146 nw_grp{std::forward<NWGroup>(nw_grp)},
147 label{std::move(label)},
148 it_pair{std::forward<ItPair>(it_pair)},
149 num_entities{std::move(num_entities)}] (auto&& write_spec)
150 // By-reference captures are used where possible to avoid additional
151 // copies. Note that this is not possible for label and num_entities.
152 {
153
154 std::string prop_prefix;
155
156 if constexpr (entity_kind == EntityKind::vertex) {
157 prop_prefix = "vertex";
158 }
159 else {
160 prop_prefix = "edge";
161 }
162
163 const auto grp =
164 nw_grp->open_group(std::get<0>(write_spec));
165
166 // ... 1D case if second element of write_spec is a callable object
167 // such that the write specifications are of the form
168 // write_spec = (std::string name_adaptor, lambda adaptor)
169 if constexpr (Utils::is_callable_v<std::tuple_element_t<1,
170 std::decay_t<decltype(write_spec)>>>)
171 {
172 const auto dset = grp->open_dataset(label, { num_entities });
173
174 dset->write(it_pair.first, it_pair.second,
175 [&](auto&& desc) {
176 return std::get<1>(write_spec)(desc, g);
177 }
178 );
179
180 dset->add_attribute("dim_name__0", prop_prefix + "_idx");
181 dset->add_attribute("coords_mode__"s + prop_prefix + "_idx",
182 "trivial");
183 }
184
185 // ... 2D case otherwise
186 // write_spec = (adaptor_name, dim0_name,
187 // (coord1, adaptor1),
188 // (coord2, adaptor2), ...)
189 else {
190
191 static_assert(Utils::is_string_v<std::tuple_element_t<1,
192 std::decay_t<decltype(write_spec)>>>,
193 "Error, the name of dimension 0 has to be s string");
194
195 // Extract the adaptor pairs (coord, adaptor) from write_spec tuple
196 const auto adaptor_pairs = tuple_tail(write_spec);
197 // FIXME This cannot be calculated at compile time since
198 // hana::for_each uses non-constexpr access.
199
200 // Get number of adaptors
201 constexpr auto num_adaptors = std::tuple_size_v<std::decay_t<
202 decltype(adaptor_pairs)>>;
203
204 // Open a 2D dataset
205 const auto dset = grp->open_dataset(label,
207
208 // Deduce coordinate container type from adaptor_pairs
209 using WriteSpecT = std::decay_t<decltype(write_spec)>;
210 using FirstTupElementT = std::tuple_element_t<2, WriteSpecT>;
211 using CoordT = std::tuple_element_t<0, FirstTupElementT>;
212
213 // Create the (empty) coordinates container
215
216 // Create the lambda that saves the data to the captured
217 // fire-and-forget dataset and stores the coordinate value
218 auto apply_adaptor = [&](auto&& adaptor_pair){
219 // adaptor_pair = (coordinate, adaptor_function)
220 dset->write(it_pair.first, it_pair.second,
221 [&](auto&& desc) {
222 return std::get<1>(adaptor_pair)(desc, g);
223 }
224 );
225
226 static_assert(
227 // assert matching coordinate types
228 std::is_same_v<
229 std::tuple_element_t<0,
230 std::decay_t<decltype(adaptor_pair)>>,
231 CoordT>,
232 "Error, coordinate types do not match! Check that all "
233 "coordinates are of the same type");
234
235 coords.push_back(std::get<0>(adaptor_pair));
236 };
237
238 boost::hana::for_each(adaptor_pairs, apply_adaptor);
239
240 // Extract the 0th dimension name from the write spec
241 const std::string dim0_name = std::get<1>(write_spec);
242
243 // Set attributes
244 dset->add_attribute("dim_name__0", dim0_name);
245 dset->add_attribute("coords_mode__"s + dim0_name, "values");
246 dset->add_attribute("coords__"s + dim0_name, coords);
247
248 dset->add_attribute("dim_name__1", prop_prefix + "_idx");
249 dset->add_attribute("coords_mode__"s + prop_prefix + "_idx",
250 "trivial");
251 }
252 };
253}
254
255} // namespace GraphUtilsHelper
256
289template <typename Graph>
290std::shared_ptr<HDFGroup>
292 const std::shared_ptr<HDFGroup>& parent_grp,
293 const std::string& name)
294{
295 // Create the group for the graph and store metadata in its attributes
296 auto grp = parent_grp->open_group(name);
297
298 grp->add_attribute("content", "graph");
299 // Store additional metadata in the group attributes
300 grp->add_attribute("is_directed", boost::is_directed(g));
301 grp->add_attribute("allows_parallel", boost::allows_parallel_edges(g));
302
303 // Store the information on the edge container shape
304 // NOTE Here, it is assumed that the edge data is written in shape (2,N).
305 // If this is not the case, the attribute must be overwritten.
306 grp->add_attribute("edge_container_is_transposed", true);
307
308 // Prevent the vertex and edge dimension from being squeezed when selecting
309 // graph data via the GraphGroup.
310 grp->add_attribute(
311 "keep_dim", std::vector<std::string>{"vertex_idx", "edge_idx"}
312 );
313
314 spdlog::get("data_io")->info("Opened graph group '{}'.", name);
315
316 return grp;
317}
318
320
330template <typename Graph>
331void
332save_graph(const Graph& g, const std::shared_ptr<HDFGroup>& grp)
333{
334 // Create and prepare the datasets for vertices and edges
335 auto [dset_vertices,
337
338 // Save vertex list
339 auto [v, v_end] = boost::vertices(g);
340 dset_vertices->write(v, v_end, [&](auto vd) {
341 return boost::get(boost::vertex_index_t(), g, vd);
342 });
343
344 // Save edges
345 // NOTE Need to call write twice to achieve the desired data shape.
346 auto [e, e_end] = boost::edges(g);
347 dset_edges->write(e, e_end, [&](auto ed) {
348 return boost::get(boost::vertex_index_t(), g, boost::source(ed, g));
349 });
350
351 dset_edges->write(e, e_end, [&](auto ed) {
352 return boost::get(boost::vertex_index_t(), g, boost::target(ed, g));
353 });
354
355 spdlog::get("data_io")->debug("Graph saved.");
356}
357
359
372template <typename Graph, typename PropertyMap>
373void
374save_graph(const Graph& g,
375 const std::shared_ptr<HDFGroup>& grp,
377{
378 // Create and prepare the datasets for vertices and edges
379 auto [dset_vertices,
381
382 // Save vertex list
383 auto [v, v_end] = boost::vertices(g);
384 dset_vertices->write(
385 v, v_end, [&](auto vd) { return boost::get(vertex_ids, vd); });
386
387 // Save edges
388 // NOTE Need to call write twice to achieve the desired data shape.
389 auto [e, e_end] = boost::edges(g);
390 dset_edges->write(e, e_end, [&](auto ed) {
391 return boost::get(vertex_ids, boost::source(ed, g));
392 });
393
394 dset_edges->write(e, e_end, [&](auto ed) {
395 return boost::get(vertex_ids, boost::target(ed, g));
396 });
397
398 spdlog::get("data_io")->debug("Graph saved.");
399}
400
448template <IterateOver iterate_over, typename Graph, typename... Adaptors>
449void
451 const std::shared_ptr<HDFGroup>& nw_grp,
452 const std::string& label,
453 const std::tuple<Adaptors...>& adaptor_tuple)
454{
455
456 static_assert(
457 // assert stringlike-ness of first thing
458 std::conjunction_v<Utils::is_string<
459 std::tuple_element_t<0, std::decay_t<Adaptors>>>...>,
460 "Error, the first entry of each entry of 'adaptor_tuple' has to be "
461 "string like, to name the adaptor");
462
463 static_assert(
465 iterate_over == IterateOver::edges, "Error, unknown iterator type! "
466 "Has to be either 'IterateOver::vertices' or 'IterateOver::edges'"
467 );
468
469 // NOTE Consider a check for literal types, which is more capable than
470 // the (now removed) std::is_literal_type ...
471
472 // Get a logger
473 auto log = spdlog::get("data_io");
474
477
478 // ... vertex_iterator ...
479 if constexpr (iterate_over == IterateOver::vertices)
480 {
481 // Collect some information on the graph
482 const auto num_vertices = boost::num_vertices(g);
483
484 // Make vertex iterator pair
485 auto it_pair = GraphUtils::iterator_pair<IterateOver::vertices>(g);
486
487 // Get write function that saves data given by the adaptor(s) to new
488 // dataset(s), fire-and-forget
490 g, nw_grp,
491 label,
492 it_pair,
494
495 boost::hana::for_each(adaptor_tuple, write_f);
496
497 log->debug("Graph vertex properties saved with label '{}'.", label);
498 }
499
500 // ... and edge_iterator
501 else {
502 // Collect some information on the graph
503 const auto num_edges = boost::num_edges(g);
504
505 // Make edge iterator pair
506 auto it_pair = GraphUtils::iterator_pair<IterateOver::edges>(g);
507
508 // Get write function that saves data given by the adaptor(s) to new
509 // dataset(s), fire-and-forget
511 g, nw_grp,
512 label,
513 it_pair,
514 num_edges);
515
516 boost::hana::for_each(adaptor_tuple, write_f);
517
518 log->debug("Graph edge properties saved with label '{}'.", label);
519 }
520}
521
540template <typename Graph, typename... Adaptors>
541void
543 const std::shared_ptr<HDFGroup>& nw_grp,
544 const std::string& label,
545 const std::tuple<Adaptors...>& adaptor_tuple)
546{
548 nw_grp,
549 label,
551}
552
571template <typename Graph, typename... Adaptors>
572void
574 const std::shared_ptr<HDFGroup>& nw_grp,
575 const std::string& label,
576 const std::tuple<Adaptors...>& adaptor_tuple)
577{
579 nw_grp,
580 label,
582}
583
584// TODO add functions here to open datasets for edge or vertex attributes.
585
// end of group GraphUtilities // end of group DataIO
588
589} // namespace DataIO
590} // namespace Utopia
591
592#endif // UTOPIA_DATAIO_GRAPH_UTILS_HH
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
IterateOver
Over which graph entity to iterate.
Definition iterator.hh:19
@ vertices
Iterate over vertices.
@ edges
Iterate over edges.
void save_edge_properties(Graph &&g, const std::shared_ptr< HDFGroup > &nw_grp, const std::string &label, const std::tuple< Adaptors... > &adaptor_tuple)
Definition graph_utils.hh:573
std::shared_ptr< HDFGroup > create_graph_group(const Graph &g, const std::shared_ptr< HDFGroup > &parent_grp, const std::string &name)
Definition graph_utils.hh:291
void save_graph(const Graph &g, const std::shared_ptr< HDFGroup > &grp)
Write function for a boost::Graph.
Definition graph_utils.hh:332
void save_vertex_properties(Graph &&g, const std::shared_ptr< HDFGroup > &nw_grp, const std::string &label, const std::tuple< Adaptors... > &adaptor_tuple)
Definition graph_utils.hh:542
void save_graph_entity_properties(const Graph &g, const std::shared_ptr< HDFGroup > &nw_grp, const std::string &label, const std::tuple< Adaptors... > &adaptor_tuple)
Definition graph_utils.hh:450
This is the central file of the HDF5 dataIO module of Utopia and provides a class for writing to,...
This file provides a class for creating and managing groups in a HDF5 file, which then can create oth...
auto generate_write_function(const Graph &g, NWGroup &&nw_grp, std::string label, ItPair &&it_pair, std::size_t num_entities)
Definition graph_utils.hh:134
EntityKind
Kinds of Entities to get properties from.
Definition graph_utils.hh:102
constexpr std::tuple< Tail... > tuple_tail(const std::tuple< First, Second, Tail... > &t)
Builds new tuple containing all elements but the first two.
Definition graph_utils.hh:81
std::pair< std::shared_ptr< HDFDataset >, std::shared_ptr< HDFDataset > > setup_graph_containers(const Graph &g, const std::shared_ptr< HDFGroup > &grp)
Definition graph_utils.hh:50
auto coords_container()
Definition graph_utils.hh:91
constexpr bool is_callable_v
Shorthand for is_callable<T>::value.
Definition type_traits.hh:734
constexpr bool is_string_v
Shorthand for 'is_string<T>::value'.
Definition type_traits.hh:140
Definition agent.hh:11
Check if a type T is a string-like type, i.e. std::basic_string, const char*, char*,...
Definition type_traits.hh:105