OGS 6.1.0-1721-g6382411ad
NamedFunctionCaller.cpp
Go to the documentation of this file.
1 
10 #include "NamedFunctionCaller.h"
11 
12 #include <algorithm>
13 
14 #include "BaseLib/Algorithm.h"
15 
26 bool hasTopologicalOrdering(std::vector<std::vector<int>> const& graph)
27 {
28  // Number of incoming edges for each node of the graph
29  std::vector<unsigned> number_incoming_edges(graph.size());
30 
31  // Count all incoming edges (i,j) for each node (i).
32  for (auto const& node_i_adjacencies : graph)
33  {
34  for (int node_j : node_i_adjacencies)
35  {
36  if (node_j >= 0) // ignore negative indices.
37  ++number_incoming_edges[node_j];
38  }
39  }
40 
41  // Working queue: a set of nodes with no incoming edges.
42  std::vector<std::size_t> q;
43  for (std::size_t node_i = 0; node_i < number_incoming_edges.size();
44  ++node_i)
45  {
46  if (number_incoming_edges[node_i] == 0)
47  {
48  q.push_back(node_i);
49  }
50  }
51 
52 
53  auto num_dependent = number_incoming_edges.size() - q.size();
54 
55  while (!q.empty())
56  {
57  auto const node_i = q.back();
58  q.pop_back();
59 
60  // Decrement counts for all edges (i,j).
61  for (int node_j : graph[node_i])
62  {
63  if (node_j < 0)
64  continue; // ignore negative indices
65  if (--number_incoming_edges[node_j] == 0)
66  { // Add a node without incoming edges to the queue.
67  q.push_back(node_j);
68  --num_dependent;
69  }
70  }
71  }
72 
73  return num_dependent == 0;
74 }
75 
77 
84 template <typename Callback>
85 void traverse(std::vector<std::vector<int>> const& map_sink_source,
86  int sink_fct, Callback&& callback)
87 {
88  assert(sink_fct < static_cast<int>(map_sink_source.size()));
89  callback(sink_fct, TraversePosition::StartNode);
90 
91  if (sink_fct < 0)
92  return;
93 
94  auto const& si_so = map_sink_source[sink_fct];
95  std::size_t const num_args = si_so.size();
96  for (std::size_t sink_arg = 0; sink_arg != num_args; ++sink_arg) {
97  if (sink_arg != 0)
98  callback(sink_fct, TraversePosition::BetweenChildren);
99  traverse(map_sink_source, si_so[sink_arg], callback);
100  }
101 
102  callback(sink_fct, TraversePosition::EndNode);
103 }
104 
105 namespace NumLib
106 {
107 NamedFunctionCaller::NamedFunctionCaller(
108  std::initializer_list<std::string> unbound_argument_names)
109  : _uninitialized(-1 - unbound_argument_names.size())
110 {
111  int idx = -1;
112  for (auto arg : unbound_argument_names) {
114  _map_name_idx, arg, idx,
115  "The name of the unbound argument is not unique.");
116  --idx;
117  }
118 }
119 
121 {
122  DBUG("Adding named function `%s'", fct.getName().c_str());
123 
125  _map_name_idx, fct.getName(), _named_functions.size(),
126  "The name of the function is not unique.");
127 
128  _map_sink_source.emplace_back(fct.getArgumentNames().size(),
130  _named_functions.push_back(std::move(fct));
131 }
132 
133 void NamedFunctionCaller::plug(const std::string& sink_fct,
134  const std::string& sink_arg,
135  const std::string& source_fct)
136 {
137  _deferred_plugs.push_back({sink_fct, sink_arg, source_fct});
138 }
139 
141 {
142  while (!_deferred_plugs.empty())
143  {
144  auto const& plug = _deferred_plugs.back();
145  auto const& sink_fct = plug.sink_fct;
146  auto const& sink_arg = plug.sink_arg;
147  auto const& source = plug.source;
148 
149  auto const source_it = _map_name_idx.find(source);
150  if (source_it == _map_name_idx.end())
151  {
152  OGS_FATAL("A function with the name `%s' has not been found.",
153  source.c_str());
154  }
155  auto const source_idx = source_it->second;
156 
157  auto const sink_it = _map_name_idx.find(sink_fct);
158  if (sink_it == _map_name_idx.end())
159  {
160  OGS_FATAL("A function with the name `%s' has not been found.",
161  sink_fct.c_str());
162  }
163  auto const sink_fct_idx = sink_it->second;
164 
165  auto const& sink_args =
166  _named_functions[sink_it->second].getArgumentNames();
167  auto const sink_arg_it =
168  std::find(sink_args.begin(), sink_args.end(), sink_arg);
169  if (sink_arg_it == sink_args.end())
170  {
171  OGS_FATAL(
172  "An argument with the name `%s' has not been found for the "
173  "function `%s'.",
174  sink_arg.c_str(), sink_fct.c_str());
175  }
176  std::size_t const sink_arg_idx =
177  std::distance(sink_args.begin(), sink_arg_it);
178 
179  auto& sis_sos = _map_sink_source[sink_fct_idx];
180  if (sis_sos[sink_arg_idx] != _uninitialized)
181  {
182  OGS_FATAL("A dependency for `%s'.`%s' has already been introduced.",
183  sink_fct.c_str(), sink_arg.c_str());
184  }
185  sis_sos[sink_arg_idx] = source_idx;
187  {
188  OGS_FATAL(
189  "The call graph being plugged together must be an acyclic "
190  "graph. The added dependency for `%s'.`%s' introduces a cycle "
191  "into the graph.",
192  sink_fct.c_str(), sink_arg.c_str());
193  }
194 
195  _deferred_plugs.pop_back();
196  }
197 }
198 
200  std::size_t function_idx,
201  const std::vector<double>& unbound_arguments) const
202 {
203  assert(_deferred_plugs.empty() &&
204  "You must call applyPlugs() before this method!");
205 
206  auto const& sis_sos = _map_sink_source[function_idx];
207  assert(sis_sos.size() ==
208  _named_functions[function_idx].getArgumentNames().size());
209  std::vector<double> fct_args(sis_sos.size());
210 
211  for (std::size_t sink=0; sink<sis_sos.size(); ++sink)
212  {
213  auto const source = sis_sos[sink];
214 
215  if (source >= 0) {
216  fct_args[sink] = call(source, unbound_arguments);
217  } else {
218  assert(source != _uninitialized);
219  fct_args[sink] = unbound_arguments[-source-1];
220  }
221  }
222 
223  return _named_functions[function_idx].call(fct_args);
224 }
225 
227  std::string const& function_name) const
228 {
229  auto const fct_it = _map_name_idx.find(function_name);
230  if (fct_it == _map_name_idx.end()) {
231  OGS_FATAL("A function with the name `%s' has not been found.",
232  function_name.c_str());
233  }
234 
235  std::string expr;
236  auto callback = [&](int fct_idx, TraversePosition pos)
237  {
238  switch (pos) {
240  {
241  if (fct_idx < 0) {
242  auto it = std::find_if(
243  _map_name_idx.begin(), _map_name_idx.end(),
244  [fct_idx](std::pair<std::string, int> const& e) {
245  return e.second == fct_idx;
246  });
247  if (it == _map_name_idx.end()) {
248  OGS_FATAL("The function index %i has not been found.", fct_idx);
249  }
250  expr += it->first;
251  } else {
252  expr += _named_functions[fct_idx].getName() + "(";
253  }
254  break;
255  }
257  expr += ", ";
258  break;
260  expr += ")";
261  }
262  };
263 
264  traverse(_map_sink_source, fct_it->second, callback);
265  DBUG("expression: %s", expr.c_str());
266  return expr;
267 }
268 
270 NamedFunctionCaller::getSpecificFunctionCaller(const std::string &function_name)
271 {
272  auto const fct_it = _map_name_idx.find(function_name);
273  if (fct_it == _map_name_idx.end()) {
274  OGS_FATAL("A function with the name `%s' has not been found.",
275  function_name.c_str());
276  }
277  return SpecificFunctionCaller(fct_it->second, *this);
278 }
279 
281 {
282  return -_uninitialized - 1;
283 }
284 
285 SpecificFunctionCaller::SpecificFunctionCaller(const std::size_t function_idx,
286  const NamedFunctionCaller& caller)
287  : _function_idx(function_idx), _caller(caller)
288 {
289 }
290 
292  const std::vector<double>& unbound_arguments) const
293 {
294  return _caller.call(_function_idx, unbound_arguments);
295 }
296 
298 {
300 }
301 
302 } // namespace NumLib
double call(std::size_t function_idx, std::vector< double > const &unbound_arguments) const
NamedFunctionCaller const & _caller
The named function caller used for the evaluation.
void traverse(std::vector< std::vector< int >> const &map_sink_source, int sink_fct, Callback &&callback)
void plug(std::string const &sink_fct, std::string const &sink_arg, std::string const &source_fct)
double call(std::vector< double > const &unbound_arguments) const
Call the function set up with the given unbound arguments.
bool hasTopologicalOrdering(std::vector< std::vector< int >> const &graph)
std::map< std::string, int > _map_name_idx
TraversePosition
std::vector< SinkSource > _deferred_plugs
Saves plugs declared by plug().
Builds expression trees of named functions dynamically at runtime.
SpecificFunctionCaller(std::size_t const function_idx, NamedFunctionCaller const &caller)
Constructs a new instance.
std::size_t getNumberOfUnboundArguments() const
Returns the number of unbound arguments.
std::size_t const _function_idx
Index of the referenced function.
static const double q
void insertIfKeyUniqueElseError(Map &map, Key const &key, Value &&value, std::string const &error_message)
Definition: Algorithm.h:104
void addNamedFunction(NamedFunction &&fct)
Adds the given named function.
std::size_t getNumberOfUnboundArguments() const
Returns the number of unbound arguments.
std::vector< NamedFunction > _named_functions
Contains all named functions.
#define OGS_FATAL(fmt,...)
Definition: Error.h:71
std::vector< std::vector< int > > _map_sink_source
std::string getCallExpression(std::string const &function_name) const
SpecificFunctionCaller getSpecificFunctionCaller(std::string const &function_name)