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