iMSTK
Interactive Medical Simulation Toolkit
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
imstkTaskGraph.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkTaskGraph.h"
8 #include "imstkLogger.h"
9 
10 #include <algorithm>
11 #include <queue>
12 #include <stack>
13 
14 namespace imstk
15 {
19 static void
20 computeDepths(std::shared_ptr<TaskGraph> graph,
21  std::unordered_map<std::shared_ptr<TaskNode>, int>& depths)
22 {
23  TaskNodeSet visitedNodes;
24 
25  const TaskNodeAdjList& adjList = graph->getAdjList();
26 
27  // DFS for the dependencies
28  std::stack<std::shared_ptr<TaskNode>> nodeStack;
29  depths[graph->getSource()] = 0;
30  nodeStack.push(graph->getSource());
31  while (!nodeStack.empty())
32  {
33  std::shared_ptr<TaskNode> currNode = nodeStack.top();
34  int currLevel = depths[currNode];
35  nodeStack.pop();
36 
37  // Add children to stack if not yet visited
38  if (adjList.count(currNode) != 0)
39  {
40  const TaskNodeSet& outputNodes = adjList.at(currNode);
41  for (TaskNodeSet::const_iterator i = outputNodes.begin(); i != outputNodes.end(); i++)
42  {
43  std::shared_ptr<TaskNode> childNode = *i;
44  if (visitedNodes.count(childNode) == 0)
45  {
46  visitedNodes.insert(childNode);
47  depths[childNode] = currLevel + 1;
48  nodeStack.push(childNode);
49  }
50  }
51  }
52  }
53 }
54 
55 TaskGraph::TaskGraph(std::string sourceName, std::string sinkName) :
56  m_source(std::make_shared<TaskNode>()),
57  m_sink(std::make_shared<TaskNode>())
58 {
59  m_source->m_name = sourceName;
60  m_sink->m_name = sinkName;
61  addNode(m_source);
62  addNode(m_sink);
63 }
64 
65 TaskNodeVector::iterator
66 TaskGraph::findNode(std::string name)
67 {
68  return std::find_if(m_nodes.begin(), m_nodes.end(),
69  [&name](const std::shared_ptr<TaskNode>& x) { return x->m_name == name; });
70 }
71 
72 TaskNodeVector::const_iterator
73 TaskGraph::findNode(std::string name) const
74 {
75  return std::find_if(m_nodes.begin(), m_nodes.end(),
76  [&name](const std::shared_ptr<TaskNode>& x) { return x->m_name == name; });
77 }
78 
79 TaskNodeVector::iterator
80 TaskGraph::findNode(std::shared_ptr<TaskNode> node)
81 {
82  return std::find(m_nodes.begin(), m_nodes.end(), node);
83 }
84 
85 TaskNodeVector::const_iterator
86 TaskGraph::findNode(std::shared_ptr<TaskNode> node) const
87 {
88  return std::find(m_nodes.begin(), m_nodes.end(), node);
89 }
90 
91 bool
92 TaskGraph::containsNode(std::shared_ptr<TaskNode> node) const
93 {
94  return findNode(node) != endNode();
95 }
96 
97 bool
98 TaskGraph::containsEdge(std::shared_ptr<TaskNode> srcNode, std::shared_ptr<TaskNode> destNode) const
99 {
100  return (m_adjList.count(srcNode) != 0 && m_adjList.at(srcNode).count(destNode) != 0);
101 }
102 
103 void
104 TaskGraph::addEdge(std::shared_ptr<TaskNode> srcNode, std::shared_ptr<TaskNode> destNode)
105 {
106  CHECK(containsNode(srcNode)) << "source node \"" << srcNode->m_name << "\" does not exist in graph";
107  CHECK(containsNode(destNode)) << "destination node \"" << destNode->m_name << "\" does not exist in graph";
108 
109  m_adjList[srcNode].insert(destNode);
110  m_invAdjList[destNode].insert(srcNode);
111 }
112 
113 void
114 TaskGraph::addEdges(const std::vector<std::pair<std::shared_ptr<TaskNode>, std::shared_ptr<TaskNode>>>& edges)
115 {
116  using EdgePair = std::pair<std::shared_ptr<TaskNode>, std::shared_ptr<TaskNode>>;
117  std::for_each(edges.begin(), edges.end(), [this](const EdgePair& edge) { addEdge(edge.first, edge.second); });
118 }
119 
120 void
121 TaskGraph::nestGraph(std::shared_ptr<TaskGraph> subgraph, std::shared_ptr<TaskNode> source, std::shared_ptr<TaskNode> sink)
122 {
123  CHECK(containsNode(source)) << "Tried to nest a graph using source, but source does not exist in this";
124  CHECK(containsNode(sink)) << "Tried to nest a graph using sink, but sink does not exist in this";
125 
126  // Copy all of the nodes into this graph (check duplicates)
127  for (const auto& node : subgraph->getNodes())
128  {
129  addNode(node);
130  }
131 
132  // Copy all the edges into the graph (no need to check for duplicates)
133  const TaskNodeAdjList& adjList = subgraph->getAdjList();
134  for (TaskNodeAdjList::const_iterator it = adjList.begin(); it != adjList.end(); it++)
135  {
136  const TaskNodeSet& outputNodes = it->second;
137  for (TaskNodeSet::const_iterator jt = outputNodes.begin(); jt != outputNodes.end(); jt++)
138  {
139  addEdge(it->first, *jt);
140  }
141  }
142  // Add source and sink edges
143  addEdge(source, subgraph->getSource());
144  addEdge(subgraph->getSink(), sink);
145 }
146 
147 void
148 TaskGraph::removeEdge(std::shared_ptr<TaskNode> srcNode, std::shared_ptr<TaskNode> destNode)
149 {
150  if (m_adjList.count(srcNode) == 0)
151  {
152  return;
153  }
154  if (m_adjList[srcNode].count(destNode) == 0)
155  {
156  return;
157  }
158  m_adjList[srcNode].erase(destNode);
159  m_invAdjList[destNode].erase(srcNode);
160  if (m_adjList[srcNode].size() == 0)
161  {
162  m_adjList.erase(srcNode);
163  }
164  if (m_invAdjList[destNode].size() == 0)
165  {
166  m_invAdjList.erase(destNode);
167  }
168 }
169 
170 bool
171 TaskGraph::addNode(std::shared_ptr<TaskNode> node)
172 {
173  // If the node already exists return false
174  if (!containsNode(node))
175  {
176  // Put it in this graph
177  m_nodes.push_back(node);
178  return true;
179  }
180  else
181  {
182  return false;
183  }
184 }
185 
186 void
187 TaskGraph::addNodes(const std::vector<std::shared_ptr<TaskNode>>& nodes)
188 {
189  std::for_each(nodes.begin(), nodes.end(), [this](const std::shared_ptr<TaskNode>& node) { addNode(node); });
190 }
191 
192 std::shared_ptr<TaskNode>
193 TaskGraph::addFunction(std::string name, std::function<void()> func)
194 {
195  std::shared_ptr<TaskNode> node = std::make_shared<TaskNode>(func, name);
196  m_nodes.push_back(node);
197  return node;
198 }
199 
200 bool
201 TaskGraph::removeNode(std::shared_ptr<TaskNode> node)
202 {
203  if (!containsNode(node))
204  {
205  LOG(INFO) << "Tried to remove node " + node->m_name + " from graph but it doesn't contain the node.";
206  return false;
207  }
208 
209  // Erase edges
210  TaskNodeSet inputs = m_invAdjList[node];
211  TaskNodeSet outputs = m_adjList[node];
212  for (TaskNodeSet::iterator i = inputs.begin(); i != inputs.end(); i++)
213  {
214  removeEdge(*i, node);
215  }
216  for (TaskNodeSet::iterator i = outputs.begin(); i != outputs.end(); i++)
217  {
218  removeEdge(node, *i);
219  }
220 
221  // Finally remove the node from the graph
222  TaskNodeVector::iterator it = findNode(node);
223  if (it != endNode())
224  {
225  m_nodes.erase(it);
226  }
227  return true;
228 }
229 
230 bool
231 TaskGraph::removeNodeAndRedirect(std::shared_ptr<TaskNode> node)
232 {
233  if (!containsNode(node))
234  {
235  LOG(INFO) << "Tried to remove node " + node->m_name + " from graph but it doesn't contain the node.";
236  return false;
237  }
238 
239  // Erase edges
240  TaskNodeSet inputs = m_invAdjList[node];
241  TaskNodeSet outputs = m_adjList[node];
242  for (TaskNodeSet::iterator i = inputs.begin(); i != inputs.end(); i++)
243  {
244  removeEdge(*i, node);
245  }
246  for (TaskNodeSet::iterator i = outputs.begin(); i != outputs.end(); i++)
247  {
248  removeEdge(node, *i);
249  }
250 
251  // Fix the edges
252  for (TaskNodeSet::iterator i = inputs.begin(); i != inputs.end(); i++)
253  {
254  for (TaskNodeSet::iterator j = outputs.begin(); j != outputs.end(); j++)
255  {
256  addEdge(*i, *j);
257  }
258  }
259 
260  // Finally remove the node from the graph
261  TaskNodeVector::iterator it = findNode(node);
262  if (it != endNode())
263  {
264  m_nodes.erase(it);
265  }
266 
267  return true;
268 }
269 
270 void
271 TaskGraph::insertAfter(std::shared_ptr<TaskNode> refNode, std::shared_ptr<TaskNode> newNode)
272 {
273  CHECK(containsNode(refNode)) << "Reference Node has to exist in graph for insertAfter.";
274  CHECK(!containsNode(newNode)) << "New Node " << newNode->m_name << " already exists in this graph.";
275 
276  addNode(newNode);
277 
278  // Remove output edges
279  TaskNodeSet outputs = m_adjList[refNode]; // Copy (since we are modifying)
280  for (TaskNodeSet::iterator i = outputs.begin(); i != outputs.end(); i++)
281  {
282  removeEdge(refNode, *i);
283  }
284 
285  // Add refNode->newNode
286  addEdge(refNode, newNode);
287 
288  // Add newNode->outputs[i]
289  for (TaskNodeSet::iterator i = outputs.begin(); i != outputs.end(); i++)
290  {
291  addEdge(newNode, *i);
292  }
293 }
294 
295 void
296 TaskGraph::insertBefore(std::shared_ptr<TaskNode> refNode, std::shared_ptr<TaskNode> newNode)
297 {
298  // Try to add to graph, if already exists, exit
299  if (!addNode(newNode))
300  {
301  return;
302  }
303 
304  // Remove input edges
305  TaskNodeSet inputs = m_invAdjList[refNode]; // Copy (since we are modifying)
306  for (TaskNodeSet::iterator i = inputs.begin(); i != inputs.end(); i++)
307  {
308  removeEdge(*i, refNode);
309  }
310 
311  // inputs[i]->newNode
312  for (TaskNodeSet::iterator i = inputs.begin(); i != inputs.end(); i++)
313  {
314  addEdge(*i, newNode);
315  }
316 
317  // refNode->newNode
318  addEdge(newNode, refNode);
319 }
320 
321 bool
322 TaskGraph::isReachable(std::shared_ptr<TaskNode> srcNode, std::shared_ptr<TaskNode> destNode)
323 {
324  const TaskNodeAdjList& adjList = getAdjList();
325 
326  // Simple BFS
327  TaskNodeSet visitedNodes;
328 
329  // It inserts itself as well
330  std::queue<std::shared_ptr<TaskNode>> nodeStack;
331  nodeStack.push(srcNode);
332  while (!nodeStack.empty())
333  {
334  std::shared_ptr<TaskNode> currNode = nodeStack.front();
335  nodeStack.pop();
336 
337  // If we've arrived at the desired node
338  if (destNode == currNode)
339  {
340  return true;
341  }
342 
343  // Add children to stack if not yet visited
344  if (adjList.count(currNode) != 0)
345  {
346  const TaskNodeSet& outputNodes = adjList.at(currNode);
347  for (TaskNodeSet::const_iterator j = outputNodes.begin(); j != outputNodes.end(); j++)
348  {
349  std::shared_ptr<TaskNode> childNode = *j;
350  if (visitedNodes.count(childNode) == 0)
351  {
352  visitedNodes.insert(childNode);
353  nodeStack.push(childNode);
354  }
355  }
356  }
357  }
358  return false;
359 }
360 
361 void
363 {
364  m_nodes.clear();
365  clearEdges();
366  addNode(m_source);
367  addNode(m_sink);
368 }
369 
370 //std::shared_ptr<TaskGraph>
371 //TaskGraph::sum(std::shared_ptr<TaskGraph> graphA, std::shared_ptr<TaskGraph> graphB)
372 //{
373 // std::shared_ptr<TaskGraph> results = std::make_shared<TaskGraph>();
374 // // Get rid of the results source/sink
375 // results->m_source = nullptr;
376 // results->m_sink = nullptr;
377 // results->m_nodes.clear();
378 //
379 // // Sum the nodes
380 // TaskNodeVector& nodesA = graphA->getNodes();
381 // for (size_t i = 0; i < nodesA.size(); i++)
382 // {
383 // results->addNode(nodesA[i]);
384 // }
385 // TaskNodeVector& nodesB = graphB->getNodes();
386 // for (size_t i = 0; i < nodesB.size(); i++)
387 // {
388 // results->addNode(nodesB[i]);
389 // }
390 //
391 // // Now sum the edges
392 // const TaskNodeAdjList& adjListA = graphA->getAdjList();
393 // for (TaskNodeAdjList::const_iterator it = adjListA.begin(); it != adjListA.end(); it++)
394 // {
395 // const TaskNodeSet& outputNodes = it->second;
396 // for (TaskNodeSet::const_iterator jt = outputNodes.begin(); jt != outputNodes.end(); jt++)
397 // {
398 // results->addEdge(it->first, *jt);
399 // }
400 // }
401 // const TaskNodeAdjList& adjListB = graphB->getAdjList();
402 // for (TaskNodeAdjList::const_iterator it = adjListB.begin(); it != adjListB.end(); it++)
403 // {
404 // const TaskNodeSet& outputNodes = it->second;
405 // for (TaskNodeSet::const_iterator jt = outputNodes.begin(); jt != outputNodes.end(); jt++)
406 // {
407 // results->addEdge(it->first, *jt);
408 // }
409 // }
410 //
411 // return results;
412 //}
413 
414 std::shared_ptr<TaskNodeList>
415 TaskGraph::topologicalSort(std::shared_ptr<TaskGraph> graph)
416 {
417  CHECK(graph != nullptr) << "Graph is nullptr";
418  // Compute the number of inputs to each node (we will remove these as we go)
419  std::unordered_map<std::shared_ptr<TaskNode>, size_t> numInputs;
420 
421  const TaskNodeAdjList& adjList = graph->getAdjList();
422  const TaskNodeAdjList& invAdjList = graph->getInvAdjList();
423 
424  for (TaskNodeAdjList::const_iterator i = invAdjList.begin(); i != invAdjList.end(); i++)
425  {
426  if (invAdjList.count(i->first) != 0)
427  {
428  numInputs[i->first] = invAdjList.at(i->first).size();
429  }
430  }
431 
432  // Create an edge blacklist for edge removal during algorithm
433  std::unordered_map<std::shared_ptr<TaskNode>, std::shared_ptr<TaskNode>> removedEdges;
434 
435  auto edgeHasBeenRemoved = [&removedEdges](const std::shared_ptr<TaskNode>& node1, const std::shared_ptr<TaskNode>& node2)
436  {
437  return (removedEdges.count(node1) != 0) && (removedEdges[node1] == node2);
438  };
439 
440  // Kahns algorithm (BFS/queue)
441  // iterate through all nodes (BFS or DFS) removing edges
442  // nodes are accepted when all input edges have been removed
443  std::queue<std::shared_ptr<TaskNode>> sources;
444  sources.push(graph->m_source);
445  std::shared_ptr<TaskNodeList> results = std::make_shared<TaskNodeList>();
446 
447  while (!sources.empty())
448  {
449  // Remove a node
450  std::shared_ptr<TaskNode> node = sources.front();
451  sources.pop();
452 
453  results->push_back(node);
454 
455  // As long as there are children
456  if (adjList.count(node) != 0)
457  {
458  const TaskNodeSet& outputNodes = adjList.at(node);
459  for (TaskNodeSet::const_iterator it = outputNodes.begin(); it != outputNodes.end(); it++)
460  {
461  std::shared_ptr<TaskNode> childNode = *it;
462  // If edge is present
463  if (!edgeHasBeenRemoved(node, childNode))
464  {
465  // Mark edge as removed
466  removedEdges[node] = childNode;
467 
468  // Decrement amount of inputs
469  numInputs[childNode]--;
470  if (numInputs[childNode] <= 0)
471  {
472  sources.push(childNode);
473  }
474  }
475  }
476  }
477  }
478  return results;
479 }
480 
481 std::shared_ptr<TaskGraph>
482 TaskGraph::transitiveReduce(std::shared_ptr<TaskGraph> graph)
483 {
484  CHECK(graph != nullptr) << "Graph is nullptr";
485  // It's a bad idea to do this method if the graph is cyclic
486  if (isCyclic(graph))
487  {
488  return nullptr;
489  }
490 
491  std::shared_ptr<TaskGraph> results = std::make_shared<TaskGraph>(*graph);
492 
493  // Copy the adjList
494  const TaskNodeAdjList adjList = results->getAdjList();
495 
496  // For every edge in the graph
497  for (TaskNodeAdjList::const_iterator i = adjList.begin(); i != adjList.end(); i++)
498  {
499  std::shared_ptr<TaskNode> inputNode = i->first;
500  const TaskNodeSet& outputNodes = i->second;
501  for (TaskNodeSet::const_iterator j = outputNodes.begin(); j != outputNodes.end(); j++)
502  {
503  std::shared_ptr<TaskNode> outputNode = *j;
504  // Edge inputNode->outputNode
505 
506  // Remove the edge and see if it still reaches
507  results->removeEdge(inputNode, outputNode);
508 
509  // If there still exists a path inputNode->outputNode, leave it removed
510  if (!results->isReachable(inputNode, outputNode))
511  {
512  results->addEdge(inputNode, outputNode);
513  }
514  }
515  }
516 
517  return results;
518 }
519 
520 std::shared_ptr<TaskGraph>
521 TaskGraph::removeRedundantNodes(std::shared_ptr<TaskGraph> graph)
522 {
523  CHECK(graph != nullptr) << "Graph is nullptr";
524  std::shared_ptr<TaskGraph> results = std::make_shared<TaskGraph>(*graph);
525 
526  const TaskNodeAdjList& adjList = results->getAdjList();
527  const TaskNodeAdjList& invAdjList = results->getInvAdjList();
528  TaskNodeVector& nodes = results->getNodes();
529 
530  for (size_t i = 0; i < nodes.size(); i++)
531  {
532  std::shared_ptr<TaskNode> node = nodes[i];
533 
534  // These can't be removed (following code would break as they have no inputs/outputs)
535  if (node == graph->getSource() || node == graph->getSink())
536  {
537  continue;
538  }
539 
540  // If node not functional and has single input/output it is removeable
541  if (!node->isFunctional())
542  {
543  // Get copies of the inputs and outputs of the node
544  TaskNodeSet inputs = invAdjList.at(node);
545  TaskNodeSet outputs = adjList.at(node);
546  if (inputs.size() == 1 && outputs.size() == 1)
547  {
548  // Remove inputs/outputs of node
549  for (TaskNodeSet::iterator j = inputs.begin(); j != inputs.end(); j++)
550  {
551  results->removeEdge(*j, node);
552  }
553  for (TaskNodeSet::iterator j = outputs.begin(); j != outputs.end(); j++)
554  {
555  results->removeEdge(node, *j);
556  }
557 
558  // Fix the edges
559  for (TaskNodeSet::iterator j = inputs.begin(); j != inputs.end(); j++)
560  {
561  for (TaskNodeSet::iterator k = outputs.begin(); k != outputs.end(); k++)
562  {
563  results->addEdge(*j, *k);
564  }
565  }
566 
567  // Finally remove the node from the graph
568  nodes.erase(nodes.begin() + i);
569 
570  // If node was removed, don't advance
571  i--;
572  }
573  }
574  }
575  return results;
576 }
577 
578 std::shared_ptr<TaskGraph>
579 TaskGraph::removeUnusedNodes(std::shared_ptr<TaskGraph> graph)
580 {
581  CHECK(graph != nullptr) << "Graph is nullptr";
582  auto results = std::make_shared<TaskGraph>(*graph);
583 
584  // Find the set of nodes not used by any edge
585  TaskNodeSet nodes;
586  nodes.reserve(results->m_nodes.size());
587  for (auto& i : results->m_adjList)
588  {
589  nodes.insert(i.first);
590  for (auto& j : i.second)
591  {
592  nodes.insert(j);
593  }
594  }
595  results->m_nodes.resize(nodes.size());
596  int iter = 0;
597  for (auto& i : nodes)
598  {
599  results->m_nodes[iter++] = i;
600  }
601  return results;
602 }
603 
604 bool
605 TaskGraph::isCyclic(std::shared_ptr<TaskGraph> graph)
606 {
607  CHECK(graph != nullptr) << "Graph is nullptr";
608  // Brute force, DFS every node to find it again
609  const TaskNodeAdjList& adjList = graph->getAdjList();
610  const TaskNodeVector& nodes = graph->getNodes();
611  for (size_t i = 0; i < nodes.size(); i++)
612  {
613  TaskNodeSet visitedNodes;
614 
615  // DFS for the dependencies (start at children instead of itself)
616  std::stack<std::shared_ptr<TaskNode>> nodeStack;
617  // Add children to stack if not yet visited
618  if (adjList.count(nodes[i]) != 0)
619  {
620  const TaskNodeSet& outputNodes = adjList.at(nodes[i]);
621  for (TaskNodeSet::const_iterator j = outputNodes.begin(); j != outputNodes.end(); j++)
622  {
623  std::shared_ptr<TaskNode> childNode = *j;
624  if (visitedNodes.count(childNode) == 0)
625  {
626  visitedNodes.insert(childNode);
627  nodeStack.push(childNode);
628  }
629  }
630  }
631  while (!nodeStack.empty())
632  {
633  std::shared_ptr<TaskNode> currNode = nodeStack.top();
634  nodeStack.pop();
635 
636  // If we revisit a node then it must be cyclic
637  if (nodes[i] == currNode)
638  {
639  return true;
640  }
641 
642  // Add children to stack if not yet visited
643  if (adjList.count(currNode) != 0)
644  {
645  const TaskNodeSet& outputNodes = adjList.at(currNode);
646  for (TaskNodeSet::const_iterator j = outputNodes.begin(); j != outputNodes.end(); j++)
647  {
648  std::shared_ptr<TaskNode> childNode = *j;
649  if (visitedNodes.count(childNode) == 0)
650  {
651  visitedNodes.insert(childNode);
652  nodeStack.push(childNode);
653  }
654  }
655  }
656  }
657  }
658 
659  return false;
660 }
661 
662 TaskNodeNameMap
663 TaskGraph::getUniqueNodeNames(std::shared_ptr<TaskGraph> graph, bool apply)
664 {
665  CHECK(graph != nullptr) << "Graph is nullptr";
666  // Produce non colliding names
667 
668  TaskNodeNameMap nodeNames;
669  std::unordered_map<std::string, int> names;
670  const TaskNodeVector& nodes = graph->getNodes();
671  for (size_t i = 0; i < nodes.size(); i++)
672  {
673  nodeNames[nodes[i]] = nodes[i]->m_name;
674  names[nodes[i]->m_name]++;
675  }
676  // Adjust names
677  for (TaskNodeNameMap::iterator it = nodeNames.begin();
678  it != nodeNames.end(); it++)
679  {
680  int nameIter = 0;
681  std::string currName = it->second;
682  // If we can find a node with this name, increment name counter and try again
683  while (names[currName] > 1)
684  {
685  names[currName]--;
686  currName = it->second + std::to_string(nameIter);
687  names[currName]++;
688  nameIter++;
689  }
690  nodeNames[it->first] = currName;
691  }
692  if (apply)
693  {
694  for (std::shared_ptr<TaskNode> node : nodes)
695  {
696  node->m_name = nodeNames[node];
697  }
698  }
699  return nodeNames;
700 }
701 
702 std::unordered_map<std::shared_ptr<TaskNode>, double>
703 TaskGraph::getNodeStartTimes(std::shared_ptr<TaskGraph> graph)
704 {
705  CHECK(graph != nullptr) << "Graph is nullptr";
706  const TaskNodeAdjList& adjList = graph->getAdjList();
707 
708  // Setup a map for total elapsed times at each node
709  using NodeTimeMap = std::unordered_map<std::shared_ptr<TaskNode>, double>;
710  NodeTimeMap startTimes;
711 
712  {
713  TaskNodeSet visitedNodes;
714 
715  // BFS down the tree computing total times
716  std::stack<std::shared_ptr<TaskNode>> nodeStack;
717  startTimes[graph->getSource()] = 0.0;
718  nodeStack.push(graph->getSource());
719  while (!nodeStack.empty())
720  {
721  std::shared_ptr<TaskNode> currNode = nodeStack.top();
722  nodeStack.pop();
723 
724  // Add children to stack if not yet visited
725  if (adjList.count(currNode) != 0)
726  {
727  const TaskNodeSet& outputNodes = adjList.at(currNode);
728  for (TaskNodeSet::const_iterator i = outputNodes.begin(); i != outputNodes.end(); i++)
729  {
730  std::shared_ptr<TaskNode> childNode = *i;
731 
732  // Determine the time it would take to start this child
733  const double childStartTime = startTimes[currNode] + currNode->m_computeTime;
734  if (childStartTime > startTimes[childNode])
735  {
736  // Accept the longest time as nodes can't continue until all child inputs complete
737  startTimes[childNode] = childStartTime;
738  }
739  // If not visited yet add child to stack
740  if (visitedNodes.count(childNode) == 0)
741  {
742  visitedNodes.insert(childNode);
743  //times[childNode] = times[currNode] + childNode->m_computeTime;
744  nodeStack.push(childNode);
745  }
746  }
747  }
748  }
749  }
750  return startTimes;
751 }
752 
753 TaskNodeList
754 TaskGraph::getCriticalPath(std::shared_ptr<TaskGraph> graph)
755 {
756  CHECK(graph != nullptr) << "Graph is nullptr";
757  std::unordered_map<std::shared_ptr<TaskNode>, double> nodeStartTimes =
758  getNodeStartTimes(graph);
759 
760  // Now backtrack to acquire the path of longest duration
761  const TaskNodeAdjList& invAdjList = graph->getInvAdjList();
762  std::list<std::shared_ptr<TaskNode>> results;
763  {
764  std::shared_ptr<TaskNode> currNode = graph->getSink();
765  // Starting from the sink, always choose the input with the longer start time
766  while (currNode != graph->getSource())
767  {
768  results.push_front(currNode);
769  std::shared_ptr<TaskNode> longestNode = nullptr;
770  double maxTime = 0.0;
771 
772  // For every parent
773  if (invAdjList.count(currNode) != 0)
774  {
775  const TaskNodeSet& inputNodes = invAdjList.at(currNode);
776  for (TaskNodeSet::const_iterator i = inputNodes.begin(); i != inputNodes.end(); i++)
777  {
778  std::shared_ptr<TaskNode> parentNode = *i;
779  if (nodeStartTimes[parentNode] >= maxTime)
780  {
781  maxTime = nodeStartTimes[parentNode];
782  longestNode = parentNode;
783  }
784  }
785  }
786 
787  // Move to the node with the longest time
788  currNode = longestNode;
789  }
790  }
791  results.push_front(graph->getSource());
792  return results;
793 }
794 } // namespace imstk
void insertBefore(std::shared_ptr< TaskNode > refNode, std::shared_ptr< TaskNode > newNode)
newNode gets placed before refNode & added to the graph. newNode takes on refNode&#39;s inputs ...
static std::shared_ptr< TaskGraph > transitiveReduce(std::shared_ptr< TaskGraph > graph)
Remove redundant edges. Removal is such that all vertices are still reachable and graph goes from sou...
static bool isCyclic(std::shared_ptr< TaskGraph > graph)
Returns if Graph is cyclic or not.
TaskNodeVector::iterator findNode(std::string name)
Linear search for node by name within this graph.
Hash together a pair of edges.
void clear()
Removes all nodes & edges from the graph. Source and sink nodes maintained.
bool isReachable(std::shared_ptr< TaskNode > srcNode, std::shared_ptr< TaskNode > destNode)
Returns true if srcNode reaches destNode.
Compound Geometry.
static std::shared_ptr< TaskGraph > removeRedundantNodes(std::shared_ptr< TaskGraph > graph)
Removes nullptr/nonfunctional TaskNode&#39;s that don&#39;t split/join.
void removeEdge(std::shared_ptr< TaskNode > srcNode, std::shared_ptr< TaskNode > destNode)
Removes an edge from the graph (removes from adjList and invAdjList, cleans)
void addEdge(std::shared_ptr< TaskNode > srcNode, std::shared_ptr< TaskNode > destNode)
Adds a directed edge to the graph both the source and target have to exist in the graph...
bool removeNode(std::shared_ptr< TaskNode > node)
Removes node from the graph and all relevant edges Returns false and fails if node is not present in ...
static TaskNodeList getCriticalPath(std::shared_ptr< TaskGraph > graph)
Computes the critical path.
void insertAfter(std::shared_ptr< TaskNode > refNode, std::shared_ptr< TaskNode > newNode)
newNode gets placed after refNode & added to the graph. newNode takes on refNode&#39;s outputs ...
bool addNode(std::shared_ptr< TaskNode > node)
Adds a node to the graph, returns if successful. Returns false and fails if node already exists in gr...
static TaskNodeNameMap getUniqueNodeNames(std::shared_ptr< TaskGraph > graph, bool apply=false)
Nodes may not have unique names. Iterates the names with numeric postfix to generate uniques...
static std::shared_ptr< TaskNodeList > topologicalSort(std::shared_ptr< TaskGraph > graph)
Graph sum, shared references are considered identical nodes, source/sink of results invalidated/nullp...
void nestGraph(std::shared_ptr< TaskGraph > subgraph, std::shared_ptr< TaskNode > source, std::shared_ptr< TaskNode > sink)
Attaches another TaskGraph as a subgraph (copies nodes and edges, then connects source->subgraph::sou...
void addEdges(const std::vector< std::pair< std::shared_ptr< TaskNode >, std::shared_ptr< TaskNode >>> &edges)
Adds a series of directed edges {source, target} to the graph both the source and target of the edge ...
bool containsEdge(std::shared_ptr< TaskNode > srcNode, std::shared_ptr< TaskNode > destNode) const
Returns whether or not this graph contains the given directed edge.
bool removeNodeAndRedirect(std::shared_ptr< TaskNode > node)
Removes node from the graph and all relevant edges. Any incoming edges to the node are redirected to ...
std::shared_ptr< TaskNode > addFunction(std::string name, std::function< void()> func)
Creates a node for the function, adds it to the graph.
bool containsNode(std::shared_ptr< TaskNode > node) const
Check if node exists in this graph.
void addNodes(const std::vector< std::shared_ptr< TaskNode >> &nodes)
Adds a series of nodes at the same time Use with {} initializer for easier graph construction.
static std::unordered_map< std::shared_ptr< TaskNode >, double > getNodeStartTimes(std::shared_ptr< TaskGraph > graph)
Gets the completion times of each node (source = ~0s)