Minsky
userFunction.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2020
3  @author Russell Standish
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "minsky.h"
21 #include "userFunction.h"
22 #include "evalOp.h"
23 #include "selection.h"
24 #include "itemT.rcd"
25 #include "userFunction.rcd"
26 #include "minsky_epilogue.h"
27 
28 #ifndef NO_EXPRTK
29 // preload these system headers here, to prevent them from being loaded into anonymous namespace
30 #include <algorithm>
31 #include <cctype>
32 #include <cmath>
33 #include <complex>
34 #include <cstdio>
35 #include <cstdlib>
36 #include <cstring>
37 #include <deque>
38 #include <exception>
39 #include <functional>
40 #include <iterator>
41 #include <limits>
42 #include <list>
43 #include <map>
44 #include <set>
45 #include <stack>
46 #include <stdexcept>
47 #include <string>
48 #include <utility>
49 #include <vector>
50 
51 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
52 # ifndef NOMINMAX
53 # define NOMINMAX
54 # endif
55 # ifndef WIN32_LEAN_AND_MEAN
56 # define WIN32_LEAN_AND_MEAN
57 # endif
58 # include <windows.h>
59 # include <ctime>
60 #else
61 # include <ctime>
62 # include <sys/time.h>
63 # include <sys/types.h>
64 #endif
65 #endif
66 
67 namespace minsky
68 {
70 
71  template <> void Operation<OperationType::userFunction>::iconDraw(cairo_t*) const
72  {assert(false);}
73 
74 #ifdef NO_EXPRTK
75  // dummy implementations to satisfy the linker
76  UserFunction::UserFunction(const string&, const string&) {}
77  vector<string> UserFunction::symbolNames() const {return {};}
78  void UserFunction::compile() {}
79  double UserFunction::evaluate(double, double) {return {};}
80  double UserFunction::operator()(const vector<double>& p) {}
81  Units UserFunction::units(bool check) const {return {};}
82  string UserFunction::description(const string&) {return {};}
83  string UserFunction::name() const {return {};}
84 #else
85 }
86 
87 #include <exprtk/exprtk.hpp>
88 
89 namespace minsky
90 {
91  namespace
92  {
93  // resolve overloads
94  inline double isfinite(double x) {return std::isfinite(x);}
95  inline double isinf(double x) {return std::isinf(x);}
96  inline double isnan(double x) {return std::isnan(x);}
97 
98  void addTimeVariables(exprtk::symbol_table<double>& table)
99  {
100  // Vensim names for these variables.
101  // TODO replace these by xmile names, and add user function to provide aliases for Vensim, and add the ability to resolve var names to argumentless functions
102  table.add_variable("time",minsky().t);
103  table.add_variable("timeStep",minsky().stepMax);
104  table.add_variable("initialTime",minsky().t0);
105  table.add_variable("finalTime",minsky().tmax);
106 
107  table.add_function("isfinite",isfinite);
108  table.add_function("isinf",isinf);
109  table.add_function("isnan",isnan);
110  }
111 
112  exprtk::parser<double> parser;
113 
114  struct ExprTkCallableFunction: public exprtk::ivararg_function<double>
115  {
116  std::weak_ptr<CallableFunction> f;
117  ExprTkCallableFunction(const std::weak_ptr<CallableFunction>& f): f(f) {}
118  double operator()(const std::vector<double>& x) {
119  if (auto sf=f.lock())
120  return (*sf)(x);
121  return nan("");
122  }
123  };
124  }
125 
127  {
128  exprtk::symbol_table<double> symbols;
129  exprtk::expression<double> compiledExpression;
130  std::vector<ExprTkCallableFunction> functions;
131  };
132 
133 
134  UserFunction::UserFunction(const string& name, const string& expression): impl(make_shared<Impl>()), argNames{"x","y"}, expression(expression) {
136  }
137 
138  vector<string> UserFunction::symbolNames() const
139  {
140  std::set<std::string> symbolNames;
141 
142  string word;
143  bool inWord=false, inString=false, quoted=false;
144  for (auto c: expression)
145  {
146  switch (c)
147  {
148  case '\'': if (!quoted) inString=!inString; break;
149  case '\\': quoted=true; break;
150  default: quoted=false; break; // I'm assuming that \" embeds a quote, but may not be true
151  }
152 
153  if (!inWord && !inString)
154  inWord=isalpha(c);
155 
156  if (inWord)
157  {
158  if (isalnum(c) || c=='_' || c=='.')
159  word+=c;
160  else
161  {
162  // trailing '.' not allowed
163  if (word.back()=='.') word.erase(word.end()-1);
164  symbolNames.insert(word);
165  word.clear();
166  inWord=false;
167  }
168  }
169  }
170  if (!word.empty()) // we ended on an identifier
171  symbolNames.insert(word);
172  return {symbolNames.begin(), symbolNames.end()};
173  }
174 
176  {
177  impl->compiledExpression=exprtk::expression<double>();
178 
179  // build symbol table
180  impl->symbols.clear();
181  impl->functions.clear();
182  addTimeVariables(impl->symbols);
183  for (auto& i: symbolNames())
184  {
185  auto scopedName=valueIdFromScope(group.lock(),canonicalName(i));
186  auto v=minsky().variableValues.find(scopedName);
187  if (v!=minsky().variableValues.end())
188  {
189  impl->symbols.add_variable(i, (*v->second)[0]);
190  continue;
191  }
192  auto f=minsky().userFunctions.find(scopedName);
193  if (f!=minsky().userFunctions.end())
194  {
195  impl->functions.emplace_back(f->second);
196  impl->symbols.add_function(i,impl->functions.back());
197  }
198  }
199 
200  // add arguments
201  argVals.resize(argNames.size());
202  for (size_t i=0; i<argNames.size(); ++i)
203  impl->symbols.add_variable(argNames[i], argVals[i]);
204  impl->compiledExpression.register_symbol_table(impl->symbols);
205 
206  if (!parser.compile(expression, impl->compiledExpression))
207  {
208  string errorInfo;
209  for (size_t i=0; i<parser.error_count(); ++i)
210  errorInfo+=parser.get_error(i).diagnostic+'\n';
211  throw_error("Invalid function expression:\n"+errorInfo);
212  }
213  }
214 
215  double UserFunction::evaluate(double in1, double in2)
216  {
217  if (!argVals.empty()) argVals[0]=in1;
218  if (argVals.size()>1) argVals[1]=in2;
219  for (size_t i=2; i<argVals.size(); ++i) argVals[i]=0;
220  return impl->compiledExpression.value();
221  }
222 
223  double UserFunction::operator()(const std::vector<double>& p)
224  {
225  size_t i=0;
226  for (; i<p.size() && i<argVals.size(); ++i) argVals[i]=p[i];
227  for (; i<argVals.size(); ++i) argVals[i]=0;
228  return impl->compiledExpression.value();
229  }
230 
231  string UserFunction::description(const string& nm)
232  {
234  static const regex extractArgList(R"([^(]*\(([^)]*)\))");
235  smatch match;
236  string argList;
237  if (regex_match(nm,match,extractArgList))
238  argList=match[1];
239 
240  argNames.clear();
241  auto end=argList.find(',');
242  decltype(end) begin=0;
243  for (; end!=string::npos; begin=end+1, end=argList.find(',',begin))
244  argNames.push_back(argList.substr(begin,end-begin));
245  argNames.push_back(argList.substr(begin));
246  return nm;
247  }
248 
249  string UserFunction::name() const
250  {
251  static const regex extractName(R"(([^(]*).*)");
252  smatch match;
253  auto d=description();
254  regex_match(d, match, extractName);
255  assert (match.size()>1);
256  return match[1];
257  }
258 }
259 
260 #endif
261 
function f
Definition: canvas.m:1
std::vector< double > argVals
Definition: userFunction.h:36
std::vector< std::string > argNames
Definition: userFunction.h:35
double operator()(const std::vector< double > &p) override
evaluate function on arbitrary number of arguments (exprtk support)
VariableValues variableValues
Definition: minsky.h:200
exprtk::symbol_table< double > symbols
exprtk::expression< double > compiledExpression
std::vector< ExprTkCallableFunction > functions
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::UserFunction)
std::vector< std::string > symbolNames() const
virtual std::string description() const
name of the associated data operation
Definition: operation.cc:573
void addTimeVariables(exprtk::symbol_table< double > &table)
Definition: userFunction.cc:98
void throw_error(const std::string &) const
mark item on canvas, then throw
Definition: item.cc:86
double evaluate(double x, double y)
string canonicalName(const string &name)
convert a raw name into a canonical name - this is not idempotent.
Definition: valueId.cc:63
std::shared_ptr< Impl > impl
Definition: userFunction.h:29
std::string name() const override
function name, shorn of argument decorators
Exclude< std::map< std::string, std::shared_ptr< CallableFunction > > > userFunctions
Definition: minsky.h:253
string valueIdFromScope(const GroupPtr &scope, const std::string &name)
value Id from scope and canonical name name
Definition: valueId.cc:128
ExprTkCallableFunction(const std::weak_ptr< CallableFunction > &f)
std::string expression
Definition: userFunction.h:37
Definition: group.tcl:84
Minsky & minsky()
global minsky object
Definition: minskyTCL.cc:51
void iconDraw(cairo_t *) const override
visual representation of operation on the canvas