Graph.cc
1 /*
2  * MoMEMta: a modular implementation of the Matrix Element Method
3  * Copyright (C) 2017 Universite catholique de Louvain (UCL), Belgium
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <Graph.h>
20 
21 #include <ModuleUtils.h>
22 #include <Path.h>
23 
24 #include <boost/graph/graphviz.hpp>
25 #include <boost/graph/topological_sort.hpp>
26 
27 #include <array>
28 
29 #ifdef DEBUG_TIMING
30 using namespace std::chrono;
31 #endif
32 
33 using namespace boost::uuids;
34 
35 namespace momemta {
36 
37 typedef boost::graph_traits<Graph>::out_edge_iterator out_edge_iterator_t;
38 typedef boost::graph_traits<Graph>::in_edge_iterator in_edge_iterator_t;
39 
40 class incomplete_looper_path: public std::runtime_error {
41  using std::runtime_error::runtime_error;
42 };
43 
44 class unresolved_input: public std::runtime_error {
45  using std::runtime_error::runtime_error;
46 };
47 
55 bool isConnectedToByOut(Graph& g, vertex_t vertex, vertex_t to) {
56  out_edge_iterator_t o, o_end;
57  std::tie(o, o_end) = boost::out_edges(vertex, g);
58 
59  for (; o != o_end; ++o) {
60  vertex_t target = boost::target(*o, g);
61  if (target == to)
62  return true;
63 
64  if (isConnectedToByOut(g, target, to))
65  return true;
66  }
67 
68  return false;
69 }
70 
78 bool isConnectedToByIn(Graph& g, vertex_t vertex, vertex_t to) {
79  in_edge_iterator_t i, i_end;
80  std::tie(i, i_end) = boost::in_edges(vertex, g);
81 
82  for (; i != i_end; ++i) {
83  vertex_t source = boost::source(*i, g);
84  if (source == to)
85  return true;
86 
87  if (isConnectedToByIn(g, source, to))
88  return true;
89  }
90 
91  return false;
92 }
93 
101 bool isConnectedTo(Graph& g, vertex_t vertex, vertex_t to) {
102  if (isConnectedToByOut(g, vertex, to))
103  return true;
104 
105  return isConnectedToByIn(g, vertex, to);
106 }
107 
115 bool isConnectedDirectlyTo(Graph& g, vertex_t from, vertex_t to) {
116  out_edge_iterator_t o, o_end;
117  std::tie(o, o_end) = boost::out_edges(from, g);
118 
119  for (; o != o_end; ++o) {
120  vertex_t target = boost::target(*o, g);
121  if (target == to)
122  return true;
123  }
124 
125  return false;
126 }
127 
137 bool checkInPath(const Configuration::ModuleDecl& looper_decl, const std::string& module_name) {
138 
139  const auto& looper_path = looper_decl.parameters->get<ExecutionPath>("path");
140 
141  auto it = std::find_if(looper_path.elements.begin(), looper_path.elements.end(),
142  [&module_name](const std::string& m) {
143  return m == module_name;
144  });
145 
146  return it != looper_path.elements.end();
147 }
148 
149 momemta::ModuleList::value_type get_module_def(const std::string& module_type,
150  const momemta::ModuleList& available_modules) {
151 
152  auto it = std::find_if(available_modules.begin(), available_modules.end(),
153  [&module_type](const momemta::ModuleList::value_type& m) {
154  return m.name == module_type;
155  });
156 
157  assert(it != available_modules.end());
158 
159  return *it;
160 }
161 
162 void ComputationGraph::addDecl(const uuid& path, const Configuration::ModuleDecl& decl) {
163 
164  auto& storage = module_decls;
165 
166  // Check if an entry already exists for the execution path.
167  auto map_it = storage.find(path);
168  if (map_it == storage.end()) {
169  // Add the path into the list of know path, keeping track of the order
170  sorted_execution_paths.emplace_back(path);
171 
172  // Add the module into its path
173  storage[path].emplace_back(decl);
174  } else {
175  map_it->second.emplace_back(decl);
176  }
177 }
178 
179 const std::vector<uuid>& ComputationGraph::getPaths() const {
180  return sorted_execution_paths;
181 }
182 
183 const std::vector<Configuration::ModuleDecl>& ComputationGraph::getDecls(const uuid& path) const {
184  return module_decls.at(path);
185 }
186 
187 void ComputationGraph::initialize(PoolPtr pool) {
188  const auto& execution_paths = sorted_execution_paths;
189 
190  // Keep track of the instantiated modules in their own execution path
191  std::map<uuid, std::vector<ModulePtr>> module_instances;
192 
193  // The list of execution path is sorted in the order we must execute the modules (modules from the first path first,
194  // then modules from the second path, etc.)
195  // However, some modules (ie Loopers) except as argument an execution path containing a list of module instances.
196  // Since modules and paths are sorted based on execution order, such dependencies are always in a other execution path.
197  // To solve this, we iterate the execution paths in reverse order, creating first the last execution path, free of
198  // any dependencies. This way, we are sure to find the final list of modules already available when we need it.
199  for (auto it = execution_paths.rbegin(); it != execution_paths.rend(); ++it) {
200 
201  const auto& modules = getDecls(*it);
202  for (auto module_decl_it = modules.begin(); module_decl_it != modules.end(); ++module_decl_it) {
203 
204  std::unique_ptr<ParameterSet> params(module_decl_it->parameters->clone());
205 
206  if (module_decl_it->type == "Looper") {
207  // Switch the "path" parameter to the list of module properly instantiated
208  auto config_path_id = params->get<ExecutionPath>("path").id;
209 
210  // Replace the `path` parameter with the list of modules
211  // Since paths are sorted and we iterate backwards, we are sure to find an existing path.
212  params->raw_set("path", Path(module_instances.at(config_path_id)));
213  }
214 
215  try {
216  module_instances[*it].push_back(ModuleFactory::get().create(module_decl_it->type, pool, *params));
217  } catch (...) {
218  LOG(fatal) << "Exception while trying to create module " << module_decl_it->type
219  << "::" << module_decl_it->name
220  << ". See message above for a (possible) more detailed description of the error.";
221  std::rethrow_exception(std::current_exception());
222  }
223  }
224  }
225 
226  modules = module_instances[DEFAULT_EXECUTION_PATH];
227 }
228 
229 void ComputationGraph::configure() {
230  for (auto& module: modules)
231  module->configure();
232 }
233 
234 void ComputationGraph::finish() {
235  for (auto& module: modules)
236  module->finish();
237 }
238 
239 void ComputationGraph::beginIntegration() {
240  for (auto& module: modules)
241  module->beginIntegration();
242 }
243 
244 void ComputationGraph::endIntegration() {
245  for (auto& module: modules)
246  module->endIntegration();
247 }
248 
249 Module::Status ComputationGraph::execute() {
250  for (auto& module: modules)
251  module->beginPoint();
252 
253  for (auto& module: modules) {
254 #ifdef DEBUG_TIMING
255  auto start = high_resolution_clock::now();
256 #endif
257  auto status = module->work();
258 #ifdef DEBUG_TIMING
259  module_timings[module.get()] += high_resolution_clock::now() - start;
260 #endif
261 
262  if (status == Module::Status::NEXT) {
263  // Stop execution for the current integration step
264  return Module::Status::NEXT;
265  } else if (status == Module::Status::ABORT) {
266  // Abort integration
267  return Module::Status::ABORT;
268  }
269  }
270 
271  for (auto& module: modules)
272  module->endPoint();
273 
274  return Module::Status::OK;
275 }
276 
277 #ifdef DEBUG_TIMING
278 void ComputationGraph::logTimings() const {
279  LOG(info) << "Time spent evaluating modules (more details for loopers below):";
280  for (auto it: module_timings) {
281  LOG(info) << " " << it.first->name() << ": " << duration_cast<duration<double>>(it.second).count() << "s";
282  }
283 }
284 #endif
285 
286 void ComputationGraph::setNDimensions(size_t n) {
287  n_dimensions = n;
288 }
289 
290 size_t ComputationGraph::getNDimensions() const {
291  return n_dimensions;
292 }
293 
294 ComputationGraphBuilder::ComputationGraphBuilder(const momemta::ModuleList& available_modules,
295  const Configuration& configuration):
296  available_modules(available_modules), configuration(configuration) { }
297 
298 std::shared_ptr<ComputationGraph> ComputationGraphBuilder::build() {
299 
300  uint32_t id = 0;
301  const auto& requested_modules = configuration.getModules();
302 
303  // Create graph vertices. Each vertex is a module requested in the configuration file
304  for (const auto& module: requested_modules) {
305  vertex_t v = boost::add_vertex(g);
306 
307  auto& vertex = g[v];
308  vertex.id = id++;
309  vertex.name = module.name;
310  vertex.type = module.type;
311 
312  // Attach module definition to the vertex
313  vertex.def = get_module_def(module.type, available_modules);
314 
315  // Attach module declaration to the vertex
316  vertex.decl = module;
317 
318  vertices.emplace(module.name, v);
319  }
320 
321  // Create edges, connecting modules together. An edge link module's outputs to module's inputs
322  typename boost::graph_traits<Graph>::vertex_iterator vtx_it, vtx_it_end;
323  for (std::tie(vtx_it, vtx_it_end) = boost::vertices(g); vtx_it != vtx_it_end; vtx_it++) {
324 
325  const auto& vertex = g[*vtx_it];
326 
327  // Connect each output of this module to any module needing it
328  for (const auto& output: vertex.def.outputs) {
329 
330  // Find any module using this output (iterate over the graph again)
331  typename boost::graph_traits<Graph>::vertex_iterator test_vtx_it, test_vtx_it_end;
332  for (std::tie(test_vtx_it, test_vtx_it_end) = boost::vertices(g); test_vtx_it != test_vtx_it_end;
333  test_vtx_it++) {
334 
335  const auto& test_module = g[*test_vtx_it];
336 
337  // Skip ourselves
338  if (test_module.name == vertex.name)
339  continue;
340 
341  // Get definition of this new module
342  const auto& test_module_def = test_module.def;
343  for (const auto& input: test_module_def.inputs) {
344  // Grab the InputTag for each input, and see if it points to the vertex
346  momemta::getInputTagsForInput(input, *test_module.decl.parameters);
347 
348  // If the input is optional, we may not have anything
349  if (! inputTags)
350  continue;
351 
352  for (const auto& inputTag: *inputTags) {
353  if (vertex.name == inputTag.module && output.name == inputTag.parameter) {
354  // We have a match, the InputTag points to the vertex output
355  // Create a new edge in the graph
356 
357  edge_t e;
358  bool inserted;
359  std::tie(e, inserted) = boost::add_edge(*vtx_it, vertices.at(test_module.name), g);
360 
361  auto& edge = g[e];
362  edge.virt = false;
363  edge.tag = inputTag;
364  edge.description = inputTag.parameter;
365  if (inputTag.isIndexed()) {
366  edge.description += "[" + std::to_string(inputTag.index) + "]";
367  }
368  }
369  }
370  }
371  }
372  }
373  }
374 
375  // We need to make sure that any dependencies of a module inside a looper
376  // is ran before the looper itself.
377  for (const auto& vertex: vertices) {
378  if (g[vertex.second].type != "Looper")
379  continue;
380 
381  const auto& looper_vtx = vertex.second;
382  const auto& looper_decl = g[looper_vtx].decl;
383 
384  // Retrieve the looper path
385  const auto& looper_path = looper_decl.parameters->get<ExecutionPath>("path");
386 
387  // Add virtual link between the looper and all module inside its execution path
388  for (const auto& m: looper_path.elements) {
389 
390  auto module_it = vertices.find(m);
391  if (module_it == vertices.end()) {
392  LOG(warning) << "Module '" << m << "' present in Looper '" << looper_decl.name
393  << "' execution path does not exists";
394  continue;
395  }
396 
397  auto module_vertex = module_it->second;
398  if (!isConnectedDirectlyTo(g, looper_vtx, module_vertex)) {
399  edge_t e;
400  bool inserted;
401  std::tie(e, inserted) = boost::add_edge(looper_vtx, module_vertex, g);
402  g[e].description = "virtual link (module in path)";
403  g[e].virt = true;
404  }
405  }
406 
407  out_edge_iterator_t e, e_end;
408  std::tie(e, e_end) = boost::out_edges(looper_vtx, g);
409 
410  // Iterator over all edges this Looper vertex is connected to
411  for (; e != e_end; ++e) {
412  auto target = boost::target(*e, g);
413 
414  // Iterate over all edges connected to the module
415  in_edge_iterator_t i, i_end;
416  std::tie(i, i_end) = boost::in_edges(target, g);
417 
418  for (; i != i_end; ++i) {
419  auto source = boost::source(*i, g);
420 
421  if (source == looper_vtx)
422  continue;
423 
424  // Check if the source vertex is connected to the looper in any way
425  if (!isConnectedTo(g, source, looper_vtx)) {
426  edge_t e;
427  bool inserted;
428  std::tie(e, inserted) = boost::add_edge(source, looper_vtx, g);
429  g[e].description = "virtual link";
430  g[e].virt = true;
431  }
432  }
433  }
434  }
435 
436  // Remove unused modules
437  prune_graph();
438 
439  // Sort the modules using their dependencies as constrains
440  sort_graph();
441 
442  // Validate the graph
443  validate();
444 
445  // Count the real number of dimension needed for the integration.
446  // For that, we use the virtual `cuba` module and count the number of out edges
447  // However, we need to be careful here because different modules can share the same dimension, so we need
448  // to explicitly check the index of the InputTag to count unique dimensions
449  out_edge_iterator_t o, o_end;
450  std::tie(o, o_end) = boost::out_edges(vertices.at("cuba"), g);
451 
452  std::set<size_t> unique_cuba_indices;
453  for (; o != o_end; ++o) {
454  const auto& inputTag = g[*o].tag;
455 
456  // We only care about phase-space points, not weights
457  if (inputTag.parameter != "ps_points")
458  continue;
459 
460  assert(inputTag.isIndexed());
461  unique_cuba_indices.emplace(inputTag.index);
462  }
463 
464  size_t n_dimensions = unique_cuba_indices.size();
465 
466  assert(n_dimensions <= configuration.getNDimensions());
467 
468  if (n_dimensions < configuration.getNDimensions()) {
469  // A module requesting a new dimension was removed from the computation graph
470  // Re-index cuba InputTag in order to ensure continuous indexing
471  std::unordered_map<size_t, size_t> new_indices_mapping;
472  size_t current_index = 0;
473  std::tie(o, o_end) = boost::out_edges(vertices.at("cuba"), g);
474  for (; o != o_end; ++o) {
475  const auto& module_vertex = g[boost::target(*o, g)];
476 
477  for (const auto& input: module_vertex.def.inputs) {
478 
479  // Get the inputs tags
481  momemta::getInputTagsForInput(input, *module_vertex.decl.parameters);
482 
483  // If the input is optional, we may not have anything
484  if (! inputTags)
485  continue;
486 
487  bool update_decl = false;
488  std::vector<InputTag> updatedInputTags;
489  for (const auto& inputTag: *inputTags) {
490 
491  auto updatedInputTag = inputTag;
492 
493  // TODO: find a better way than hardcoding the values
494  if (inputTag.module == "cuba" && inputTag.parameter == "ps_points") {
495  update_decl = true;
496  // It's a cuba InputTag, re-index it
497  auto it = new_indices_mapping.find(inputTag.index);
498  if (it == new_indices_mapping.end()) {
499  new_indices_mapping.emplace(inputTag.index, current_index);
500  updatedInputTag.index = current_index;
501  current_index++;
502  } else {
503  updatedInputTag.index = it->second;
504  }
505  updatedInputTag.update();
506  }
507 
508  updatedInputTags.push_back(updatedInputTag);
509  }
510 
511  if (update_decl) {
512  // At least one InputTag has been updated, we need to update the module parameters
513  momemta::setInputTagsForInput(input, *module_vertex.decl.parameters, updatedInputTags);
514  }
515  }
516  }
517  }
518 
519  // Finally, everything is setup. Create the final computation graph
520  const auto& execution_paths = configuration.getPaths();
521 
522  std::shared_ptr<ComputationGraph> computationGraph(new ComputationGraph());
523  computationGraph->setNDimensions(n_dimensions);
524  for (auto vertex: sorted_vertices) {
525 
526  // Find in which execution path this module is. If it's not found inside any execution path
527  // declared in the configuration, then the module is assigned to the default execution path.
528 
529  static auto find_module_in_path = [&vertex, this](std::shared_ptr<ExecutionPath> p) -> bool {
530  // Returns true if the module is inside the execution path `p`, False otherwise
531  auto it = std::find_if(p->elements.begin(), p->elements.end(),
532  [&vertex, this](const std::string& element) -> bool {
533  return g[vertex].name == element;
534  }
535  );
536 
537  return it != p->elements.end();
538  };
539 
540 
541  auto execution_path_it = std::find_if(execution_paths.begin(), execution_paths.end(), find_module_in_path);
542  auto execution_path = execution_path_it == execution_paths.end() ? DEFAULT_EXECUTION_PATH :
543  (*execution_path_it)->id;
544 
545  // The first declared path must always be the default one, otherwise we are in trouble
546  assert(!computationGraph->getPaths().empty() || execution_path == DEFAULT_EXECUTION_PATH);
547 
548  computationGraph->addDecl(execution_path, g[vertex].decl);
549  }
550 
551  graph_created = true;
552 
553  return computationGraph;
554 }
555 
556 void ComputationGraphBuilder::prune_graph() {
557 
558  // Find all vertices not connected to something and remove them
559  for (auto it = vertices.begin(), ite = vertices.end(); it != ite;) {
560  if (boost::out_degree(it->second, g) == 0) {
561 
562  auto& vertex = g[it->second];
563 
564  // Don't consider internal or sticky modules
565  if (vertex.def.internal || vertex.def.sticky) {
566  ++it;
567  continue;
568  }
569 
570  // Otherwise, remove it
571  LOG(info) << "Module '" << it->first << "' output is not used by any other module. Removing it from the configuration.";
572  boost::clear_vertex(it->second, g);
573  boost::remove_vertex(it->second, g);
574 
575  it = vertices.erase(it);
576  } else
577  ++it;
578  }
579 
580  // Ensure ids are continuous
581  uint32_t id = 0;
582  typename boost::graph_traits<Graph>::vertex_iterator vtx_it, vtx_it_end;
583  for (std::tie(vtx_it, vtx_it_end) = boost::vertices(g); vtx_it != vtx_it_end; vtx_it++) {
584  g[*vtx_it].id = id++;
585  }
586 }
587 
588 void ComputationGraphBuilder::sort_graph() {
589  const std::vector<std::shared_ptr<ExecutionPath>>& execution_paths = configuration.getPaths();
590 
591  try {
592  boost::topological_sort(g, std::front_inserter(sorted_vertices),
593  boost::vertex_index_map(boost::get(&Vertex::id, g)));
594  } catch (...) {
595  exportGraph("graph.debug");
596  LOG(fatal) << "Exception while sorting the graph. Graphviz representation saved as graph.debug";
597  throw;
598  }
599 
600  // Remove vertices corresponding to internal modules
601  sorted_vertices.erase(std::remove_if(sorted_vertices.begin(), sorted_vertices.end(),
602  [this](const vertex_t& vertex) -> bool {
603  return g[vertex].def.internal;
604  }), sorted_vertices.end());
605 }
606 
607 void ComputationGraphBuilder::validate() {
608 
609  auto log_and_throw_unresolved_input = [](const std::string& module_name, const InputTag& input) {
610  LOG(fatal) << "Module '" << module_name << "' requested a non-existing input (" << input.toString() << ")";
611  throw unresolved_input("Module '" + module_name + "' requested a non-existing input (" + input.toString() + ")");
612  };
613 
614  // Find any module whose input point to a non-existing module / parameter
615  for (auto it = vertices.begin(), ite = vertices.end(); it != ite; it++) {
616 
617  const auto& vertex = g[it->second];
618  // Ignore internal module (?)
619  if (vertex.def.internal)
620  continue;
621 
622  const auto& inputs = vertex.def.inputs;
623 
624  for (const auto& input_def: inputs) {
625 
626  // Get the InputTag for this input
628  momemta::getInputTagsForInput(input_def, *vertex.decl.parameters);
629 
630  if (! inputTags)
631  continue;
632 
633  // Ensure that every input tags point to an existing module
634  for (const auto& inputTag: *inputTags) {
635  auto target_it = vertices.find(inputTag.module);
636 
637  if (target_it == vertices.end()) {
638  // Non-existing module
639  log_and_throw_unresolved_input(it->first, inputTag);
640  }
641 
642 
643  // Look for input in module's output
644  const auto& target_module_outputs = g[target_it->second].def.outputs;
645  auto output_it = std::find_if(target_module_outputs.begin(),
646  target_module_outputs.end(),
647  [&inputTag](const ArgDef& output) {
648  return inputTag.parameter == output.name;
649  });
650 
651  if (output_it == target_module_outputs.end()) {
652  // Non-existing parameter
653  log_and_throw_unresolved_input(it->first, inputTag);
654  }
655  }
656  }
657  }
658 
659  // Ensure all the modules using a Looper output are present in the looper path
660  std::map<vertex_t, std::vector<vertex_t>> modules_not_in_path;
661  for (const auto& vertex: sorted_vertices) {
662  if (g[vertex].type == "Looper") {
663 
664  const auto& decl = g[vertex].decl;
665 
666  out_edge_iterator_t e, e_end;
667  std::tie(e, e_end) = boost::out_edges(vertex, g);
668 
669  // Iterator over all edges connected to this Looper vertex
670  for (; e != e_end; ++e) {
671  auto target = boost::target(*e, g);
672 
673  // Check if target is inside the looper path
674  if (! checkInPath(decl, g[target].name)) {
675  auto& loopers = modules_not_in_path[target];
676  auto it = std::find(loopers.begin(), loopers.end(), vertex);
677  if (it == loopers.end())
678  loopers.push_back(vertex);
679  } else {
680  modules_not_in_path.erase(target);
681  }
682  }
683  }
684  }
685 
686  if (modules_not_in_path.size() != 0) {
687  // Only print a message for the first module not in path
688  auto it = modules_not_in_path.begin();
689 
690  auto target = g[it->first];
691 
692  std::stringstream loopers;
693  for (size_t i = 0; i < it->second.size(); i++) {
694  loopers << "'" << g[it->second[i]].name << "'";
695  if (i != it->second.size() - 1)
696  loopers << ", ";
697  }
698 
699  std::string plural = it->second.size() > 1? "s" : "";
700  std::string one_of_the = it->second.size() > 1 ? "one of the" : "the";
701 
702  LOG(fatal) << "Module '" << target.name << "' is configured to use Looper " << loopers.str()
703  << " output" << plural << ", but is not actually part of the Looper" << plural << " execution path. This will lead to undefined "
704  << "behavior. You can fix the issue by adding the module '"
705  << target.name
706  << "' to " << one_of_the << " Looper" << plural << " execution path";
707 
708  throw incomplete_looper_path("A module is using the looper output but not actually part of its "
709  "execution path");
710  }
711 }
712 
713 /*
714  * Graphviz export
715  */
716 
718 public:
719  graph_writer(Graph g,
720  const std::vector<std::shared_ptr<ExecutionPath>>& paths):
721  graph(g), paths(paths) {}
722 
723 
724  // Vertex writer
725  void writeVertex(std::ostream& out, const vertex_t& v) const {
726  std::string shape = "ellipse";
727  std::string color = "black";
728  std::string style = "solid";
729  std::string extra = "";
730 
731  if (graph[v].def.internal) {
732  shape = "rectangle";
733  color = "black";
734  style = "dashed";
735  }
736 
737  if (graph[v].type == "Looper") {
738  style = "filled";
739  extra = "fillcolor=\"" + path_colors.at(graph[v].decl.parameters->get<ExecutionPath>("path").id) + "\"";
740  }
741 
742  out << "[shape=\"" << shape << "\",color=\"" << color << "\",style=\"" << style
743  << "\",label=\"" << graph[v].name << "\"";
744 
745  if (!extra.empty()) {
746  out << "," << extra;
747  }
748 
749  out << "]";
750  }
751 
752  // Edge writer
753  void writeEdge(std::ostream& out, const edge_t& e) const {
754  std::string color = "black";
755  std::string style = "solid";
756  std::string extra = "";
757 
758  if (graph[e].virt) {
759  style = "invis";
760  extra = "constraint=false";
761  }
762 
763  out << "[color=\"" << color << "\",style=\"" << style
764  << "\",label=\"" << graph[e].description << "\"";
765 
766  if (!extra.empty()) {
767  out << "," << extra;
768  }
769 
770  out << "]";
771  }
772 
773  // Graph writer
774  void writeGraph(std::ostream& out) const {
775 
776  size_t index = 0;
777  size_t color_index = 0;
778  for (const auto& path: paths) {
779 
780  out << "subgraph cluster_" << index << " {" << std::endl;
781 
782  out << R"(style=filled; fillcolor=")" << colors[color_index] << R"(";)" << std::endl;
783  path_colors.emplace(path->id, colors[color_index]);
784 
785  auto looper_vertex = find_looper(path->id);
786 
787  if (looper_vertex != boost::graph_traits<Graph>::null_vertex())
788  out << " label=\"" << graph[looper_vertex].name << " execution path\";" << std::endl;
789 
790  out << " ";
791  for (const auto& e: path->elements) {
792  auto vertex = find_vertex(e);
793  if (vertex != boost::graph_traits<Graph>::null_vertex())
794  out << graph[vertex].id << "; ";
795  }
796 
797  out << std::endl;
798  out << "}" << std::endl;
799 
800  index++;
801  color_index++;
802  if (color_index >= colors.size())
803  color_index = 0;
804  }
805  }
806 
807 private:
808  vertex_t find_vertex(const std::string& name) const {
809  typename boost::graph_traits<Graph>::vertex_iterator vtx_it, vtx_it_end;
810 
811  for(boost::tie(vtx_it, vtx_it_end) = boost::vertices(graph); vtx_it != vtx_it_end; ++vtx_it) {
812  if (graph[*vtx_it].name == name)
813  return *vtx_it;
814  }
815 
816  return boost::graph_traits<Graph>::null_vertex();
817  }
818 
819  vertex_t find_looper(const uuid& path) const {
820  typename boost::graph_traits<Graph>::vertex_iterator vtx_it, vtx_it_end;
821 
822  for(boost::tie(vtx_it, vtx_it_end) = boost::vertices(graph); vtx_it != vtx_it_end; ++vtx_it) {
823  if (graph[*vtx_it].type == "Looper") {
824  const auto& looper_path = graph[*vtx_it].decl.parameters->get<ExecutionPath>("path");
825  if (looper_path.id == path)
826  return *vtx_it;
827  }
828  }
829 
830  return boost::graph_traits<Graph>::null_vertex();
831  }
832 
833  Graph graph;
834  const std::vector<std::shared_ptr<ExecutionPath>> paths;
835 
836  mutable std::unordered_map<uuid, std::string,
837  boost::hash<uuid>> path_colors;
838 
839  const std::array<std::string, 5> colors = {{
840  "#BEEB9F",
841  "#ACF0F2",
842  "#F3FFE2",
843  "#79BD8F88",
844  "##EB7F0099"
845  }};
846 };
847 
849 public:
850  graph_writer_wrapper(std::shared_ptr<graph_writer> writer):
851  writer(writer) {}
852 
853  void operator()(std::ostream& out, const vertex_t& v) const {
854  writer->writeVertex(out, v);
855  }
856 
857  void operator()(std::ostream& out, const edge_t& e) const {
858  writer->writeEdge(out, e);
859  }
860 
861  void operator()(std::ostream& out) const {
862  writer->writeGraph(out);
863  }
864 
865 private:
866  std::shared_ptr<graph_writer> writer;
867 };
868 
869 void graphviz_export(const Graph& g,
870  const std::vector<std::shared_ptr<ExecutionPath>>& paths,
871  const std::string& filename) {
872 
873  std::ofstream f(filename.c_str());
874  auto writer = std::make_shared<graph_writer>(g, paths);
875  boost::write_graphviz(f, g, graph_writer_wrapper(writer), graph_writer_wrapper(writer),
876  graph_writer_wrapper(writer), boost::get(&Vertex::id, g));
877 }
878 
879 void ComputationGraphBuilder::exportGraph(const std::string& output) const {
880  if (! graph_created)
881  return;
882 
883  graphviz_export(g, configuration.getPaths(), output);
884 }
885 
886 }
size_t getNDimensions() const
std::string name
Name of the module (user-defined from the configuration file)
Definition: Configuration.h:44
std::shared_ptr< ComputationGraph > build()
Build the computation graph.
Definition: Graph.cc:298
std::shared_ptr< ParameterSet > parameters
Module&#39;s parameters, as parsed from the configuration file.
Definition: Configuration.h:47
Definition: Graph.h:21
std::vector< std::shared_ptr< ExecutionPath > > getPaths() const
An execution path.
Definition: Path.h:37
An identifier of a module&#39;s output.
Definition: InputTag_fwd.h:37
Defines an input / output.
Definition: ModuleDef.h:40
void exportGraph(const std::string &output) const
Export a GraphViz representation of the computation graph to a file.
Definition: Graph.cc:879
A frozen snapshot of the configuration file.
Definition: Configuration.h:36
A module declaration, defined from the configuration file.
Definition: Configuration.h:43
const std::vector< ModuleDecl > & getModules() const