LArSoft  v09_90_00
Liquid Argon Software toolkit - https://larsoft.org/
graph_algorithms.cc
Go to the documentation of this file.
3 #include "boost/graph/graph_traits.hpp"
4 #include "boost/graph/graph_utility.hpp"
6 #include "cetlib/HorizontalRule.h"
7 #include "cetlib/compiler_macros.h"
8 #include "range/v3/view.hpp"
9 
10 #include <limits>
11 
12 using art::detail::Edge;
21 
22 namespace {
23  std::string
24  comma_separated_list(name_set_t const& names)
25  {
26  if (names.empty())
27  return {};
28 
29  auto cb = cbegin(names);
30  std::string result{*cb};
31  for (auto i = std::next(cb), e = cend(names); i != e; ++i) {
32  result += ", ";
33  result += *i;
34  }
35  return result;
36  }
37 
38  inline std::string const&
39  module_label(art::WorkerInPath::ConfigInfo const& wci)
40  {
41  return wci.moduleConfigInfo->modDescription.moduleLabel();
42  }
43 }
44 
45 std::pair<ModuleGraph, std::string>
47  paths_to_modules_t const& trigger_paths,
48  configs_t const& end_path)
49 {
50  auto const nmodules = modInfos.size();
51  ModuleGraph module_graph{nmodules};
52 
53  make_trigger_path_subgraphs(modInfos, trigger_paths, module_graph);
54  make_product_dependency_edges(modInfos, module_graph);
55  auto err = verify_no_interpath_dependencies(modInfos, module_graph);
56  if (err.empty()) {
57  // Cannot currently check intrapath dependencies unless the above
58  // check is successful.
59  err += verify_in_order_dependencies(modInfos, trigger_paths);
60  }
61 
62  make_path_ordering_edges(modInfos, trigger_paths, module_graph);
63  make_synchronization_edges(modInfos, trigger_paths, end_path, module_graph);
64 
65  return std::make_pair(module_graph, err);
66 }
67 
68 void
70  ModuleGraphInfoMap const& modInfos,
71  paths_to_modules_t const& trigger_paths,
72  ModuleGraph& module_graph)
73 {
74  using namespace ::ranges;
75  std::map<path_name_t, ModuleGraph*> path_graphs;
76  auto const source_index = modInfos.vertex_index("input_source");
77  for (auto const& path_name : trigger_paths | views::keys |
78  views::transform([](auto const& path_spec) {
79  return path_spec.name;
80  })) {
81  auto& path_graph = module_graph.create_subgraph();
82  // Always include the source on the path.
83  add_vertex(source_index, path_graph);
84  path_graphs[path_name] = &path_graph;
85  }
86 
87  auto vertex_names = get(boost::vertex_name_t{}, module_graph);
88  for (auto const& [module_name, info] : modInfos) {
89  if (!is_modifier(info.module_type))
90  continue;
91  auto const index = modInfos.vertex_index(module_name);
92  for (auto const& path : info.paths) {
93  add_vertex(index, *path_graphs.at(path));
94  }
95  vertex_names[index] = module_name;
96  }
97 }
98 
99 void
101  ModuleGraph& graph)
102 {
103  auto edge_label = get(boost::edge_name, graph);
104  for (Vertex u{}; u < modInfos.size(); ++u) {
105  auto const& modu = modInfos.info(u);
106  for (auto const& dep : modu.consumed_products) {
107  auto const v = modInfos.vertex_index(dep.label);
108  auto const edge = add_edge(u, v, graph);
109  edge_label[edge.first] = "prod";
110  }
111  }
112 }
113 
114 void
116  paths_to_modules_t const& trigger_paths,
117  ModuleGraph& graph)
118 {
119  // Make edges corresponding to path ordering
120  auto path_label = get(boost::edge_name, graph);
121  for (auto const& [path_spec, modules] : trigger_paths) {
122  if (modules.empty())
123  continue;
124  auto prev = cbegin(modules);
125  auto curr = prev + 1;
126  auto const end = cend(modules);
127  while (curr != end) {
128  auto const pi = modInfos.vertex_index(module_label(*prev));
129  auto const ci = modInfos.vertex_index(module_label(*curr));
130  auto const edge = add_edge(ci, pi, graph);
131  path_label[edge.first] = "path:" + path_spec.name;
132  prev = curr;
133  ++curr;
134  }
135  }
136 }
137 
138 void
140  paths_to_modules_t const& trigger_paths,
141  configs_t const& end_path,
142  ModuleGraph& graph)
143 {
144  auto const source_index = modInfos.vertex_index("input_source");
145  auto sync_label = get(boost::edge_name, graph);
146  if (!trigger_paths.empty()) {
147  auto const tr_index = modInfos.vertex_index("TriggerResults");
148  for (auto const& [path_spec, modules] : trigger_paths) {
149  if (modules.empty()) {
150  continue;
151  }
152  auto const front_index =
153  modInfos.vertex_index(module_label(modules.front()));
154  auto const back_index =
155  modInfos.vertex_index(module_label(modules.back()));
156  auto const edge1 = add_edge(front_index, source_index, graph);
157  sync_label[edge1.first] = "source:" + path_spec.name;
158  auto const edge2 = add_edge(tr_index, back_index, graph);
159  sync_label[edge2.first] = "sync";
160  }
161  for (auto const& module : end_path) {
162  auto const index = modInfos.vertex_index(module_label(module));
163  auto const edge = add_edge(index, tr_index, graph);
164  sync_label[edge.first] = "sync";
165  }
166  } else if (!end_path.empty()) {
167  for (auto const& module : end_path) {
168  auto const index = modInfos.vertex_index(module_label(module));
169  auto const edge = add_edge(index, source_index, graph);
170  sync_label[edge.first] = "sync";
171  }
172  }
173 
174  auto constexpr invalid = std::numeric_limits<std::size_t>::max();
175 
176  // Now synchronize between previous filters
177  for (auto const& [path_spec, modules] : trigger_paths) {
178  auto preceding_filter_index = invalid;
179  for (auto const& module : modules) {
180  auto const index = modInfos.vertex_index(module_label(module));
181  auto const& info = modInfos.info(index);
182  if (preceding_filter_index != invalid) {
183  auto const edge = add_edge(index, preceding_filter_index, graph);
184  sync_label[edge.first] = "filter:" + path_spec.name;
185  }
186  if (info.module_type == ModuleType::filter) {
187  preceding_filter_index = index;
188  }
189  }
190  }
191 
192  if (trigger_paths.empty()) {
193  return;
194  }
195 
196  // Synchronize end-path modules if a 'SelectEvents' parameter has
197  // been specified. Treat it as a filter.
198  auto const tr_index = modInfos.vertex_index("TriggerResults");
199  for (auto const& module : end_path) {
200  auto const index = modInfos.vertex_index(module_label(module));
201  auto const& info = modInfos.info(index);
202  for (auto const& path : info.select_events) {
203  auto const edge = add_edge(index, tr_index, graph);
204  sync_label[edge.first] = "filter:" + path;
205  }
206  }
207 }
208 
209 std::string
211  ModuleGraphInfoMap const& modInfos,
212  ModuleGraph const& graph)
213 {
214  using namespace ::ranges;
215  auto make_range = [](auto const pr) { return subrange{pr.first, pr.second}; };
216 
217  std::map<Vertex, std::set<Vertex>> illegal_dependencies;
218  for (auto const& path_graph : make_range(graph.children())) {
219  for (auto const gv :
220  make_range(vertices(path_graph)) |
221  views::transform([&path_graph](auto const local_vertex) {
222  return path_graph.local_to_global(local_vertex);
223  })) {
224  // Verify that the target of each out edge is a member of the
225  // same subgraph (which is determined by calling find_vertex).
226  // If not, then it is an illegal path specification.
227  for (auto const out_edge : make_range(out_edges(gv, graph))) {
228  auto const tv = target(out_edge, graph);
229  if (path_graph.find_vertex(tv).second) {
230  continue;
231  }
232  illegal_dependencies[gv].insert(tv);
233  }
234  }
235  }
236 
237  if (illegal_dependencies.empty()) {
238  return {};
239  }
240 
241  std::ostringstream oss;
242  oss << "\nThe following represent cross-path data-dependency errors:\n"
243  << cet::HorizontalRule{60}('-') << '\n';
244  for (auto const& [mod_index, target_indices] : illegal_dependencies) {
245  auto const& module_name = modInfos.name(mod_index);
246  auto const& mod_paths = modInfos.info(mod_index).paths;
247  oss << " Module " << module_name << " on path"
248  << (mod_paths.size() == 1ull ? " " : "s ")
249  << comma_separated_list(mod_paths) << " depends on\n";
250  for (auto const& dep : target_indices) {
251  auto const& dep_name = modInfos.name(dep);
252  auto const& on_paths = modInfos.info(dep).paths;
253  oss << " Module " << dep_name << " on path"
254  << (on_paths.size() == 1ull ? " " : "s ")
255  << comma_separated_list(on_paths) << '\n';
256  }
257  }
258  oss << "\nSuch errors occur whenever a module on one path depends on the "
259  "data products\n"
260  << "from another. Such dependencies can be subtle--for example, a "
261  "module that\n"
262  << "uses an event.getManyByType call cannot be shared across paths if "
263  "the modules\n"
264  << "that precede it in the paths do not give consistent result. Please "
265  "check your\n"
266  << "configuration, or email artists@fnal.gov for assistance.\n";
267  return oss.str();
268 }
269 
270 namespace {
271  struct path_matches {
272  path_name_t path_name;
273  explicit path_matches(std::string const& name) : path_name{name} {}
274  bool
275  operator()(paths_to_modules_t::value_type const& pr) const
276  {
277  return pr.first.name == path_name;
278  }
279  };
280 
281  struct module_matches {
282  std::string module_name;
283  explicit module_matches(std::string const& name) : module_name{name} {}
284  bool
285  operator()(art::WorkerInPath::ConfigInfo const& info) const
286  {
287  return module_label(info) == module_name;
288  }
289  };
290 }
291 
292 std::string
294  ModuleGraphInfoMap const& modInfos,
295  paths_to_modules_t const& trigger_paths)
296 {
297  // Precondition: there are no inter-path dependencies
298  std::map<module_name_t, name_set_t> illegal_module_orderings;
299  for (auto const& module : modInfos) {
300  auto const& module_name = module.first;
301  auto const module_type = module.second.module_type;
302  auto const& module_paths = module.second.paths;
303  if (!is_modifier(module_type)) {
304  continue;
305  }
306  if (module_paths.empty()) {
307  continue;
308  }
309  // Only need to check one path since when we call this function,
310  // we have already guaranteed that there are no interpath
311  // dependencies.
312  auto const& first_path_for_module = *cbegin(module_paths);
313  auto const& path_it = std::find_if(trigger_paths.cbegin(),
314  trigger_paths.cend(),
315  path_matches{first_path_for_module});
316  assert(path_it != trigger_paths.cend());
317  auto const begin = cbegin(path_it->second);
318  auto const end = cend(path_it->second);
319  auto const module_position =
320  std::find_if(begin, end, module_matches{module_name});
321  assert(module_position != end);
322 
323  for (auto const& dep : module.second.consumed_products) {
324  if (dep.label == "input_source") {
325  continue;
326  }
327  auto const dep_position =
328  std::find_if(begin, end, module_matches{dep.label});
329  assert(dep_position != end);
330  if (dep_position < module_position) {
331  continue;
332  }
333  illegal_module_orderings[module_name].insert(dep.label);
334  }
335  }
336 
337  if (illegal_module_orderings.empty()) {
338  return {};
339  }
340 
341  std::ostringstream oss;
342  oss << "\nThe following are module-ordering errors due to declared "
343  "data-product dependencies:\n";
344  for (auto const& mod : illegal_module_orderings) {
345  auto const& module_name = mod.first;
346  auto const mod_index = modInfos.vertex_index(module_name);
347  auto const& module_paths = modInfos.info(mod_index).paths;
348  oss << " Module " << module_name << " on path"
349  << (module_paths.size() == 1ull ? " " : "s ")
350  << comma_separated_list(module_paths)
351  << " depends on either itself or modules that follow it:\n";
352  for (auto const& dep_name : mod.second) {
353  auto const dep_index = modInfos.vertex_index(dep_name);
354  auto const& on_paths = modInfos.info(dep_index).paths;
355  oss << " Module " << dep_name << " on path"
356  << (on_paths.size() == 1ull ? " " : "s ")
357  << comma_separated_list(on_paths)
358  << (module_name == dep_name ? " (self circularity)" : "") << '\n';
359  }
360  }
361  return oss.str();
362 }
363 
364 using EdgePair = std::pair<Vertex, Vertex>;
365 
366 namespace {
367  class graph_printer : public boost::dfs_visitor<> {
368  public:
369  explicit graph_printer(
370  ModuleGraphInfoMap const& modules,
371  std::set<Vertex>& vertices,
372  std::map<path_name_t, std::set<EdgePair>>& path_edges,
373  std::map<path_name_t, std::set<EdgePair>>& filter_edges,
374  std::set<EdgePair>& trigger_path_edges,
375  std::map<EdgePair, unsigned>& product_edges)
376  : modules_{modules}
377  , vertices_{vertices}
378  , path_edges_{path_edges}
379  , filter_edges_{filter_edges}
380  , trigger_path_edges_{trigger_path_edges}
381  , product_edges_{product_edges}
382  {}
383 
384  void
385  discover_vertex(Vertex const v, ModuleGraph const&)
386  {
387  vertices_.insert(v);
388  }
389 
390  void
391  forward_or_cross_edge(Edge const e, ModuleGraph const& g)
392  {
393  print_edge(e, g);
394  }
395 
396  void
397  tree_edge(Edge const e, ModuleGraph const& g)
398  {
399  print_edge(e, g);
400  }
401 
402  void
403  back_edge(Edge const e, ModuleGraph const& g)
404  {
405  print_edge(e, g);
406  }
407 
408  private:
409  enum class arrow_style { path, sync, source, filter, prod };
410 
411  static arrow_style
412  arrow_style_for(std::string const& edge_name)
413  {
414  auto pos = edge_name.find("path:");
415  if (pos == 0) {
416  return arrow_style::path;
417  }
418  pos = edge_name.find("sync");
419  if (pos == 0) {
420  return arrow_style::sync;
421  }
422  pos = edge_name.find("source:");
423  if (pos == 0) {
424  return arrow_style::source;
425  }
426  pos = edge_name.find("filter:");
427  if (pos == 0) {
428  return arrow_style::filter;
429  }
430  if (edge_name == "prod") {
431  return arrow_style::prod;
432  }
433  throw art::Exception{
435  "An occurred while printing the DOT file for this process.\n"}
436  << "The edge name '" << edge_name << "' is not recognized.";
437  }
438 
439  void
440  print_edge(Edge const e, ModuleGraph const& g)
441  {
442  auto const u = source(e, g);
443  auto const v = target(e, g);
444  auto const vertex_pair = std::make_pair(u, v);
445  auto const& edge_name = get(boost::edge_name, g, e);
446  switch (arrow_style_for(edge_name)) {
447  case arrow_style::path: {
448  auto path_name = edge_name.substr(5); // Removes 'path:' prefix
449  path_edges_[path_name].insert(vertex_pair);
450  break;
451  }
452  case arrow_style::sync: {
453  trigger_path_edges_.insert(vertex_pair);
454  break;
455  }
456  case arrow_style::source: {
457  auto path_name = edge_name.substr(7); // Remove 'source:' prefix
458  path_edges_[path_name].insert(vertex_pair);
459  break;
460  }
461  case arrow_style::filter: {
462  auto path_name = edge_name.substr(7); // Removes 'filter:' prefix
463  filter_edges_[path_name].insert(vertex_pair);
464  break;
465  }
466  case arrow_style::prod:
467  ++product_edges_[vertex_pair];
468  }
469  }
470 
471  ModuleGraphInfoMap const& modules_ UNUSED_PRIVATE_FIELD;
472  std::set<Vertex>& vertices_;
473  std::map<path_name_t, std::set<EdgePair>>& path_edges_;
474  std::map<path_name_t, std::set<EdgePair>>& filter_edges_;
475  std::set<EdgePair>& trigger_path_edges_;
476  std::map<EdgePair, unsigned>& product_edges_;
477  };
478 }
479 
480 void
482  ModuleGraphInfoMap const& info_map,
483  ModuleGraph const& graph)
484 {
485  std::set<Vertex> vertices;
486  std::map<path_name_t, std::set<EdgePair>> path_edges;
487  std::map<path_name_t, std::set<EdgePair>> filter_edges;
488  std::set<EdgePair> trigger_path_edges;
489  std::map<EdgePair, unsigned> product_edges;
490  graph_printer printer{info_map,
491  vertices,
492  path_edges,
493  filter_edges,
494  trigger_path_edges,
495  product_edges};
496  boost::depth_first_search(graph, visitor(printer));
497  os << "digraph {\n"
498  << " rankdir=BT\n";
499  // Vertices
500  for (auto const& v : vertices) {
501  auto const& name = info_map.name(v);
502  auto const& info = info_map.info(v);
503  if (name == "input_source") {
504  os << " \"input_source\"[shape=box label=source]";
505  } else if (name == "TriggerResults") {
506  os << " \"" << name << '\"';
507  os << "[shape=box style=filled fillcolor=black label=\"\" height=0.1 "
508  "width=2]";
509  } else {
510  os << " \"" << name << '\"';
511  if (info.module_type == art::ModuleType::filter) {
512  os << "[style=filled fillcolor=pink]";
513  }
514  }
515  os << ";\n";
516  }
517 
518  // Path edges
519  for (auto const& [path_name, edges] : path_edges) {
520  for (auto const& [source, target] : edges) {
521  os << " \"" << info_map.name(source) << "\" -> \""
522  << info_map.name(target) << '\"' << "[label=\"" << path_name
523  << "\" color=gray];\n";
524  }
525  }
526 
527  // Filter edges
528  for (auto const& [path_name, edges] : filter_edges) {
529  for (auto const& [source, target] : edges) {
530  os << " \"" << info_map.name(source) << "\" -> \""
531  << info_map.name(target) << '\"' << "[label=\"" << path_name
532  << "\" color=red];\n";
533  }
534  }
535 
536  // Trigger-path edges
537  for (auto const& [source, target] : trigger_path_edges) {
538  os << " \"" << info_map.name(source) << "\" -> \"" << info_map.name(target)
539  << '\"' << "[style=invisible arrowhead=none];\n";
540  }
541 
542  // Product edges
543  for (auto const& [edge_pair, multiplicity] : product_edges) {
544  auto const& [source, target] = edge_pair;
545  os << " \"" << info_map.name(source) << "\" -> \"" << info_map.name(target)
546  << '\"';
547  if (multiplicity > 1) {
548  os << "[label=\"" << multiplicity << "\" color=black]";
549  }
550  os << ";\n";
551  }
552  os << "}\n";
553 }
decltype(auto) constexpr cend(T &&obj)
ADL-aware version of std::cend.
Definition: StdUtils.h:93
boost::graph_traits< ModuleGraph >::edge_descriptor Edge
Definition: ModuleGraph.h:24
ModuleType module_type(std::string const &full_key)
std::string path_name_t
std::string verify_in_order_dependencies(ModuleGraphInfoMap const &modules, paths_to_modules_t const &trigger_paths)
Framework includes.
decltype(auto) constexpr end(T &&obj)
ADL-aware version of std::end.
Definition: StdUtils.h:77
std::set< std::string > name_set_t
std::vector< WorkerInPath::ConfigInfo > configs_t
cet::exempt_ptr< detail::ModuleConfigInfo const > moduleConfigInfo
Definition: WorkerInPath.h:39
void print_module_graph(std::ostream &os, ModuleGraphInfoMap const &modInfos, ModuleGraph const &graph)
std::vector< std::pair< PathSpec, configs_t >> paths_to_modules_t
std::string name
Definition: PathSpec.h:48
auto vertex_index(module_name_t const &name) const -> distance_t
bool is_modifier(ModuleType const mt)
Definition: ModuleType.h:22
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:66
auto const & info(std::size_t const i) const
void make_synchronization_edges(ModuleGraphInfoMap const &modInfos, paths_to_modules_t const &trigger_paths, configs_t const &end_path, ModuleGraph &graph)
void make_trigger_path_subgraphs(ModuleGraphInfoMap const &modInfos, paths_to_modules_t const &trigger_paths, ModuleGraph &graph)
std::pair< Vertex, Vertex > EdgePair
auto const & name(std::size_t const i) const
constexpr T pi()
Returns the constant pi (up to 35 decimal digits of precision)
cout<< "-> Edep in the target
Definition: analysis.C:53
decltype(auto) constexpr cbegin(T &&obj)
ADL-aware version of std::cbegin.
Definition: StdUtils.h:85
PathSpec path_spec(std::string const &path_spec)
Definition: PathSpec.cc:22
boost::subgraph< Graph > ModuleGraph
Definition: ModuleGraph.h:23
boost::graph_traits< ModuleGraph >::vertex_descriptor Vertex
Definition: ModuleGraph.h:25
decltype(auto) constexpr begin(T &&obj)
ADL-aware version of std::begin.
Definition: StdUtils.h:69
std::string verify_no_interpath_dependencies(ModuleGraphInfoMap const &modInfos, ModuleGraph const &graph)
Float_t e
Definition: plot.C:35
std::string module_name_t
void make_path_ordering_edges(ModuleGraphInfoMap const &modInfos, paths_to_modules_t const &paths, ModuleGraph &graph)
std::pair< ModuleGraph, std::string > make_module_graph(ModuleGraphInfoMap const &modInfos, paths_to_modules_t const &trigger_paths, configs_t const &end_path)
void make_product_dependency_edges(ModuleGraphInfoMap const &modInfos, ModuleGraph &graph)