NRP Core  1.4.1
computational_graph.h
Go to the documentation of this file.
1 /* * NRP Core - Backend infrastructure to synchronize simulations
2  *
3  * Copyright 2020-2023 NRP Team
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * This project has received funding from the European Union’s Horizon 2020
18  * Framework Programme for Research and Innovation under the Specific Grant
19  * Agreement No. 945539 (Human Brain Project SGA3).
20  */
21 
22 #ifndef COMPUTATION_GRAPH_H
23 #define COMPUTATION_GRAPH_H
24 
29 
54  private NGraph::tGraph<ComputationalNode *>
55 {
56 public:
57 
58  typedef std::vector<ComputationalGraph::vertex> comp_layer;
59 
61 
63 
67  void insert_edge(const vertex &a, const vertex& b)
68  {
69  if(this->_state > GraphState::EMPTY)
70  throw NRPException::logCreate("Inserting edges while the graph is being configured or is configured is not allowed");
71 
72  // Enforce edge rules
73  if(a == b)
74  throw NRPException::logCreate("Attempt to insert edge from node \"" + a->id() + "\" to itself. This is not allowed");
76  throw NRPException::logCreate("Attempt to insert edge with Output node \"" + a->id() + "\" as source. This is not allowed");
77  if(b->type() == ComputationalNode::Input)
78  throw NRPException::logCreate("Attempt to insert edge with Input node \"" + b->id() + "\" as target. This is not allowed");
79 
81  }
82 
86  void clear()
87  {
88  if(this->_state == GraphState::CONFIGURING)
89  throw NRPException::logCreate("Graph can't be cleared while is being configured");
90  else if( this->_state == GraphState::COMPUTING)
91  throw NRPException::logCreate("Graph can't be cleared while is computing");
92 
93  clearLayers();
95 
96  this->_state = GraphState::EMPTY;
97  }
98 
102  void configure()
103  {
104  if(this->_state > GraphState::EMPTY)
105  throw NRPException::logCreate("Graph is already configured. Please reset the graph first by calling 'clear()'");
106 
107  this->_state = GraphState::CONFIGURING;
108 
109  try {
110  // Configure nodes
111  for (const auto &e: *this)
112  e.first->configure();
113 
114  // Clear layers
115  clearLayers();
116 
117  // Reset isVisited
118  for (const auto &e: *this)
119  e.first->setVisited(false);
120 
121  // Creates input and output layers
122  setIOLayers();
123 
124  // Creates other layers
125  comp_layer layer;
126  setFirstLayer(layer);
127  while (!layer.empty()) {
128  _compLayers.push_back(layer);
129  layer = comp_layer();
130  setNextLayer(_compLayers.back(), layer);
131  }
132 
133  if (checkForCycles())
134  throw NRPException::logCreate("Cycle(s) found in the graph. Cycles are not supported");
135 
136 
137  this->_state = GraphState::READY;
138  }
139  catch(const std::exception& e) {
140  this->_state = GraphState::READY;
141  clear();
142  throw;
143  }
144  }
145 
149  void compute()
150  {
151  if (this->_state < GraphState::READY)
152  throw NRPException::logCreate("Graph is not configured. Please (re-)build the graph by calling 'configure()'");
153  else if (this->_state == GraphState::COMPUTING)
154  throw NRPException::logCreate("'compute' can't be called while graph is already computing");
155 
156  this->_state = GraphState::COMPUTING;
157 
158  // Inform OutputNodes that it is a new execution cycle, currently they are the only type of nodes using this
159  // information
160  sendCycleStartSignal();
161 
162  try {
163  // TODO: each of these loops could be possibly parallelized
164 
165  // Input nodes are always executed, regardless of the execution mode
166  for (auto &node: _inputLayer)
167  node->compute();
168 
169  // Functional nodes and output nodes are executed if they have been marked for execution or the CG is
170  // being run in input controlled execution mode
171  for (auto &layer: _compLayers)
172  for (auto &node: layer)
173  if (this->_execMode == ExecMode::ALL_NODES || node->doCompute()) {
174  node->compute();
175  node->setDoCompute(false);
176  }
177 
178  for (auto &node: _outputLayer)
179  if (this->_execMode == ExecMode::ALL_NODES || node->doCompute()) {
180  node->compute();
181  node->setDoCompute(false);
182  }
183 
184  this->_state = GraphState::READY;
185  }
186  catch(const std::exception& e) {
187  this->_state = GraphState::READY;
188  throw;
189  }
190  }
191 
196  { return this->_state; }
197 
199  {
200  if( this->_state == GraphState::COMPUTING)
201  throw NRPException::logCreate("Graph execution mode can't be changed while computing");
202 
203  _execMode = mode;
204  }
205 
207  { return _execMode; }
208 
209 private:
210 
211  void sendCycleStartSignal()
212  {
213  // Inform OutputNodes that it is a new execution cycle, currently they are the only type of nodes using this
214  // information
215  for (auto &node: _outputLayer) {
217  // In OUTPUT_DRIVEN mode, Output nodes that will execute this cycle propagates the "execution signal" back
218  // in the graph
219  if(this->_execMode == ExecMode::OUTPUT_DRIVEN && node->doCompute())
220  propagateExecSignalBack(node);
221  }
222  }
223 
224  void propagateExecSignalBack(const ComputationalGraph::vertex& v)
225  {
226  for(auto &node : this->in_neighbors(v))
227  // If the node is already marked for execution it means it has already been processed
228  if(!node->doCompute()) {
229  node->setDoCompute(true);
230  propagateExecSignalBack(node);
231  }
232  }
233 
237  void clearLayers()
238  {
239  _inputLayer.clear();
240  _outputLayer.clear();
241  _compLayers.clear();
242  }
243 
247  void setIOLayers()
248  {
249  for(const auto &e : *this) {
250  if(!e.first->isVisited() && e.first->type() == ComputationalNode::NodeType::Input) {
251  e.first->setVisited(true);
252  _inputLayer.push_back(e.first);
253  }
254 
255  if(!e.first->isVisited() && e.first->type() == ComputationalNode::NodeType::Output) {
256  e.first->setVisited(true);
257  _outputLayer.push_back(e.first);
258  }
259  }
260  }
261 
267  void setFirstLayer(comp_layer& layer)
268  {
269  for(const auto &e : *this) {
270  auto v = e.first;
271  if(v->isVisited() || v->type() != ComputationalNode::NodeType::Functional)
272  continue;
273 
274  bool isFirst = true;
275  for(const auto &i : this->in_neighbors(v))
276  if(i->type() != ComputationalNode::NodeType::Input) {
277  isFirst = false;
278  break;
279  }
280 
281  if(isFirst) {
282  v->setVisited(true);
283  layer.push_back(v);
284  }
285  }
286  }
287 
293  void setNextLayer(const comp_layer& prev_layer, comp_layer& layer)
294  {
295  // build output set
297  for(const auto &v : prev_layer)
298  output_set.insert(this->out_neighbors(v).begin(), this->out_neighbors(v).end());
299 
300  // build layer
301  for(const auto &v : output_set) {
302  if(v->type() == ComputationalNode::NodeType::Output)
303  continue;
304 
305  bool isNext = true;
306  for(const auto &i : this->in_neighbors(v))
307  if(i->type() != ComputationalNode::NodeType::Input && !i->isVisited()) {
308  isNext = false;
309  break;
310  }
311 
312  if(isNext) {
313  v->setVisited(true);
314  layer.push_back(v);
315  }
316  }
317  }
318 
319  bool checkForCycles()
320  {
321  for(const auto &e : *this)
322  if(!e.first->isVisited())
323  return true;
324 
325  return false;
326  }
327 
328  comp_layer _inputLayer;
329  comp_layer _outputLayer;
330  std::vector<comp_layer> _compLayers;
331 
332  GraphState _state = GraphState::EMPTY;
333 
334  ExecMode _execMode = ExecMode::ALL_NODES;
335 
336 
337 };
338 
339 
340 #endif //COMPUTATION_GRAPH_H
nrp_logger.h
ComputationalGraph::clear
void clear()
Clear graph.
Definition: computational_graph.h:86
ComputationalGraph::insert_edge
void insert_edge(const vertex &a, const vertex &b)
Insert edge.
Definition: computational_graph.h:67
ComputationalGraph::getExecMode
ExecMode getExecMode()
Definition: computational_graph.h:206
ComputationalGraph::compute
void compute()
Executes all nodes in the graph in order.
Definition: computational_graph.h:149
ComputationalGraph::setExecMode
void setExecMode(ExecMode mode)
Definition: computational_graph.h:198
ComputationalNode::setDoCompute
void setDoCompute(bool doCompute)
Sets a value for the node 'doCompute' property, used in some graph execution modes.
Definition: computational_node.h:87
ComputationalNode::type
NodeType type() const
Returns the node 'type'.
Definition: computational_node.h:63
ComputationalGraph::ALL_NODES
@ ALL_NODES
Definition: computational_graph.h:62
nrp_exceptions.h
ComputationalNode::setVisited
void setVisited(bool visited)
Sets a value for the node 'visited' property, used for graph traversing.
Definition: computational_node.h:75
ComputationalNode::Output
@ Output
Definition: computational_node.h:37
ComputationalNode::id
const std::string & id() const
Returns the node 'id'.
Definition: computational_node.h:57
ComputationalNode::isVisited
bool isVisited() const
Returns true if the node has been marked as visited, false otherwise.
Definition: computational_node.h:81
NGraph::tGraph< ComputationalNode * >::begin
iterator begin()
Definition: ngraph.hpp:139
ComputationalGraph::COMPUTING
@ COMPUTING
Definition: computational_graph.h:60
ComputationalGraph::EMPTY
@ EMPTY
Definition: computational_graph.h:60
NGraph::tGraph::clear
void clear()
Definition: ngraph.hpp:144
ComputationalNode::compute
virtual void compute()=0
Requests the node to execute its computation.
ComputationalGraph::OUTPUT_DRIVEN
@ OUTPUT_DRIVEN
Definition: computational_graph.h:62
NGraph::tGraph< ComputationalNode * >::vertex_set
std::set< vertex > vertex_set
Definition: ngraph.hpp:89
computational_node.h
ngraph.hpp
NGraph::tGraph< ComputationalNode * >::node
static const vertex & node(const_iterator p)
Definition: ngraph.hpp:700
ComputationalGraph::ExecMode
ExecMode
Definition: computational_graph.h:62
ComputationalNode::doCompute
virtual bool doCompute() const
Tells if this node should be executed in this graph execution cycle, used in some graph execution mod...
Definition: computational_node.h:96
NRPException::logCreate
static EXCEPTION logCreate(LOG_EXCEPTION_T &exception, const std::string &msg, NRPLogger::spdlog_out_fcn_t spdlogCall=NRPLogger::critical)
Definition: nrp_exceptions.h:73
ComputationalGraph::READY
@ READY
Definition: computational_graph.h:60
NGraph::tGraph::insert_edge
void insert_edge(iterator pa, iterator pb)
Definition: ngraph.hpp:282
NGraph::tGraph< ComputationalNode * >::in_neighbors
const vertex_set & in_neighbors(const vertex &a) const
Definition: ngraph.hpp:213
ComputationalGraph::configure
void configure()
Creates the graph execution structure and call 'configure' on each node.
Definition: computational_graph.h:102
ComputationalGraph::CONFIGURING
@ CONFIGURING
Definition: computational_graph.h:60
NGraph::tGraph
Definition: ngraph.hpp:80
ComputationalGraph::getState
GraphState getState() const
Returns true if the graph is configured, false otherwise.
Definition: computational_graph.h:195
ComputationalGraph
Definition: computational_graph.h:53
ComputationalNode::graphCycleStartCB
virtual void graphCycleStartCB()
Function called by the Computational Graph at the beginning of a new execution cycle.
Definition: computational_node.h:136
ComputationalNode::Input
@ Input
Definition: computational_node.h:36
NGraph::tGraph< ComputationalNode * >::out_neighbors
const vertex_set & out_neighbors(const vertex &a) const
Definition: ngraph.hpp:218
NGraph::tGraph< ComputationalNode * >::end
iterator end()
Definition: ngraph.hpp:141
ComputationalNode
Base class implementing a node in the computational graph.
Definition: computational_node.h:31
ComputationalGraph::GraphState
GraphState
Definition: computational_graph.h:60
ComputationalGraph::comp_layer
std::vector< ComputationalGraph::vertex > comp_layer
Definition: computational_graph.h:58