Minsky
equations.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2012
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 "equations.h"
22 #include "expr.h"
23 #include "str.h"
24 #include "flowCoef.h"
25 #include "userFunction.h"
26 #include "minskyTensorOps.h"
27 #include "minsky_epilogue.h"
28 using namespace minsky;
29 
30 namespace MathDAG
31 {
32 
33  namespace
34  {
35  // comparison function for sorting variables into their definition order
37  {
38  unsigned maxOrder;
39  VariableDefOrder(unsigned maxOrder): maxOrder(maxOrder) {}
40  bool operator()(const VariableDAG* x, const VariableDAG* y) const {
41  return x->order(maxOrder)<y->order(maxOrder);
42  }
43  };
44 
45  struct NoArgument: public runtime_error
46  {
47  std::string message;
48  NoArgument(const OperationPtr& s, unsigned a1, unsigned a2):
49  std::runtime_error
50  ("missing argument "+to_string(a1)+","+to_string(a2)+
51  (s?(" on operation "+OperationType::typeName(s->type())):string()))
53  };
54 
55  }
56 
57  VariableValuePtr ConstantDAG::addEvalOps
59  {
60  if (result->idx()<0)
61  {
63  if (r->type()!=VariableType::undefined && r->isFlowVar())
64  {
65  result=r;
66  // set the initial value of the actual variableValue (if it exists)
67  auto v=values.find(result->valueId());
68  if (v!=values.end())
69  v->second->init(value);
70  }
71  else
72  {
74  result->allocValue();
75  }
76  result->init(value);
77  values.resetValue(*result);
78  }
79  if (r->type()!=VariableType::undefined && r->isFlowVar() && r!=result)
80  ev.emplace_back(EvalOpPtr(new TensorEval(r,result)));
81  assert(result->idx()>=0);
82  doOneEvent(true);
83  return result;
84  }
85 
86  namespace
87  {
88  bool addTensorOp(const shared_ptr<VariableValue>& result, OperationDAGBase& nodeOp, EvalOpVector& ev)
89  {
90  if (auto state=nodeOp.state)
91  try
92  {
93  auto ec=make_shared<EvalCommon>();
94  const TensorPtr rhs=tensorOpFactory.create(state,TensorsFromPort(ec));
95  if (!rhs) return false;
96  result->index(rhs->index());
97  result->rhs=rhs;
98  ev.emplace_back(EvalOpPtr(new TensorEval(result, ec, rhs)));
99  return true;
100  }
101  catch(const FallBackToScalar&) {/* fall back to scalar processing */}
102  return false;
103  }
104  }
105 
106  bool VariableDAG::tensorEval(std::set<const Node*>&) const
107  {
108  return cminsky().variableValues[valueId]->rank()>0;
109  }
110 
112  {
113  if (rhs)
114  if (auto nodeOp=dynamic_cast<OperationDAGBase*>(rhs.payload))
115  return MathDAG::addTensorOp(result,*nodeOp,ev);
116  return false;
117  }
118 
119  VariableValuePtr VariableDAG::addEvalOps
121  {
122  if (result->idx()<0)
123  {
124  assert(isValueId(valueId));
125  auto ri=minsky::minsky().variableValues.find(valueId);
126  if (ri==minsky::minsky().variableValues.end())
128  result=ri->second;
129  if (rhs)
130  {
131  if ((!tensorEval() && !rhs->tensorEval()) || !addTensorOp(ev))
132  { // everything scalar, revert to scalar processing
133  if (result->idx()<0) result->allocValue();
134  rhs->addEvalOps(ev, result);
135  }
136  }
137  else
138  if (result->idx()<0) result->allocValue();
139  }
140  if (r->type()!=VariableType::undefined && r->isFlowVar() && (r!=result || result->isFlowVar()))
141  ev.emplace_back(EvalOpPtr(new TensorEval(r,result)));
142  assert(result->idx()>=0);
143  doOneEvent(true);
144  return result;
145  }
146 
147  VariableValuePtr IntegralInputVariableDAG::addEvalOps
149  {
150  assert(result);
151  if (result->type()==VariableType::undefined)
152  {
153  assert(r);
154  if (r->type()!=VariableType::undefined && r->isFlowVar())
155  result=r;
156  else
157  {
159  result->allocValue();
160  }
161  if (rhs)
162  rhs->addEvalOps(ev, result);
163  else
164  throw runtime_error("integral not defined for "+name);
165  }
166  assert(result->idx()>=0);
167  if (r && r->isFlowVar() && (r!=result || !result->isFlowVar()))
168  ev.emplace_back(EvalOpPtr(new TensorEval(r,result)));
169  doOneEvent(true);
170  return result;
171  }
172 
175 
176  OperationDAGBase* OperationDAGBase::create(Type type, const string& name)
177  {
178  auto r=operationDAGFactory.create(type);
179  r->name=name;
180  return r;
181  }
182 
183  // the definition order of an operation is simply the maximum of all
184  // of its arguments
185  int OperationDAGBase::order(unsigned maxOrder) const
186  {
187  if (type()==integrate)
188  return 0; //integrals have already been evaluated
189  if (cachedOrder>=0) return cachedOrder;
190 
191  if (maxOrder==0)
192  throw error("maximum order recursion reached");
193 
194 
195  // constants have order one, as they must be ordered after the
196  // "fake" variables have been initialised
197  int order=type()==constant? 1: 0;
198  for (size_t i=0; i<arguments.size(); ++i)
199  for (size_t j=0; j<arguments[i].size(); ++j)
200  {
201  checkArg(i,j);
202  order=std::max(order, arguments[i][j]->order(maxOrder-1));
203  }
204  return cachedOrder=order;
205  }
206 
207  bool OperationDAGBase::tensorEval(std::set<const Node*>& visited) const
208  {
209  if (!visited.insert(this).second)
210  return false; // cycle detected, break
211  switch (OperationType::classify(type()))
212  {
213  case reduction: case scan: case tensor: case statistics:
214  return true;
215  case general: case binop: case constop: case function:
216  for (auto& i: arguments)
217  for (auto j: i)
218  if (j && j->tensorEval(visited)) return true;
219  return false;
220  default:
221  assert(false);// above cases should exhaust
222  return false;
223  }
224  }
225 
226 
227  void OperationDAGBase::checkArg(unsigned i, unsigned j) const
228  {
229  if (arguments.size()<=i || arguments[i].size()<=j || !arguments[i][j])
230  throw NoArgument(state, i, j);
231  }
232 
233 
234  namespace
235  {
236  void cumulate(EvalOpVector& ev, const ItemPtr& state, VariableValuePtr& r,
237  const vector<vector<VariableValuePtr> >& argIdx,
238  OperationType::Type op, OperationType::Type accum, double groupIdentity)
239  {
240  assert(r);
241  // check if any arguments have x-vectors, and if so, initialise r.xVector
242  // For feature 47
243  for (auto& i: argIdx)
244  if (!i.empty())
245  {
246  // initialise r's xVector
247  r->hypercube(i[0]->hypercube());
248  break;
249  }
250  if (r->rank()>0)
251  throw runtime_error("Scalar processing not supported in tensor code, try adding an intermediate variable");
252  if (r->idx()==-1) r->allocValue();
253 
254  // short circuit multiplications if any of the terms are constant zero
255  if (accum==OperationType::multiply)
256  {
258  for (auto& i: argIdx[1])
259  if (i->isZero())
260  throw runtime_error("divide by constant zero");
261  for (auto& i: argIdx)
262  for (auto& j: i)
263  if (j->isZero())
264  {
265  r=j;
266  return;
267  }
268  }
269 
270  {
271  size_t i=0;
272  if (accum==OperationType::add)
273  while (!argIdx.empty() && i<argIdx[0].size() && argIdx[0][i]->isZero())
274  i++;
275  if (!argIdx.empty() && i<argIdx[0].size())
276  {
277  ev.push_back(EvalOpPtr(OperationType::copy, state, r, *argIdx[0][i]));
278  for (++i; i<argIdx[0].size(); ++i)
279  if (accum!=OperationType::add || !argIdx[0][i]->isZero())
280  ev.push_back(EvalOpPtr(accum, state, r, *r, *argIdx[0][i]));
281  }
282  else
283  {
284  //TODO: could be cleaned up if we don't need to support constant operators
285  ev.push_back(EvalOpPtr(OperationType::constant, state, r));
286  dynamic_cast<ConstantEvalOp&>(*ev.back()).value=groupIdentity;
287  }
288  }
289 
290  if (argIdx.size()>1)
291  {
292  if (argIdx[1].size()==1)
293  // eliminate redundant copy operation when only one wire
294  ev.push_back(EvalOpPtr(op, state, r, *r, *argIdx[1][0]));
295  else if (argIdx[1].size()>1)
296  {
297  // multiple wires to second input port
299  tmp->hypercube(r->hypercube());
300  size_t i=0;
301  if (accum==OperationType::add)
302  while (i<argIdx[1].size() && argIdx[1][i]->isZero()) i++;
303  if (i<argIdx[1].size())
304  {
305  ev.push_back(EvalOpPtr(OperationType::copy, state, tmp, *argIdx[1][i]));
306  for (++i; i<argIdx[1].size(); ++i)
307  if (accum!=OperationType::add || !argIdx[1][i]->isZero())
308  ev.push_back(EvalOpPtr(accum, state, tmp, *tmp, *argIdx[1][i]));
309  ev.push_back(EvalOpPtr(op, state, r, *r, *tmp));
310  }
311  }
312  }
313  }
314  }
315 
316  VariableValuePtr OperationDAGBase::addEvalOps
318  {
319  assert(result);
320  if (result->type()==VariableType::undefined)
321  {
322  assert(!dynamic_cast<IntOp*>(state.get()));
323  if (r->type()!=VariableType::undefined && r->isFlowVar())
324  result=r;
325  else
326  {
328  result->allocValue();
329  }
330  if (tensorEval() && addTensorOp(result, *this, ev))
331  return result;
332  assert(result->type()!=VariableType::undefined);
333  assert(result->idx()>=0);
334 
335  // prepare argument expressions
336  vector<vector<VariableValuePtr> > argIdx(arguments.size());
337  for (size_t i=0; type()!=integrate && i<arguments.size(); ++i)
338  for (size_t j=0; j<arguments[i].size(); ++j)
339  if (arguments[i][j])
340  argIdx[i].push_back(arguments[i][j]->addEvalOps(ev));
341  else
342  {
343  argIdx[i].emplace_back(VariableType::tempFlow);
344  argIdx[i].back()->allocValue();
345  }
346 
347  try
348  {
349  // basic arithmetic is handled in a cumulative fashion
350  switch (type())
351  {
352  case add:
353  cumulate(ev, state, result, argIdx, add, add, 0);
354  break;
355  case subtract:
356  cumulate(ev, state, result, argIdx, subtract, add, 0);
357  break;
358  case multiply:
359  cumulate(ev, state, result, argIdx, multiply, multiply, 1);
360  break;
361  case divide:
362  cumulate(ev, state, result, argIdx, divide, multiply, 1);
363  break;
364  case min:
365  cumulate(ev, state, result, argIdx, min, min, numeric_limits<double>::max());
366  break;
367  case max:
368  cumulate(ev, state, result, argIdx, max, max, -numeric_limits<double>::max());
369  break;
370  case and_:
371  cumulate(ev, state, result, argIdx, and_, and_, 1);
372  break;
373  case or_:
374  cumulate(ev, state, result, argIdx, or_, or_, 0);
375  break;
376  case constant:
377  if (state) minsky::minsky().displayErrorItem(*state);
378  throw error("Constant deprecated");
379  case lt: case le: case eq:
380  for (size_t i=0; i<arguments.size(); ++i)
381  if (arguments[i].empty())
382  {
383  argIdx[i].emplace_back(VariableType::tempFlow);
384  argIdx[i].back()->allocValue();
385  // ensure units are compatible (as we're doing comparisons with zero)
386  if (i>0)
387  argIdx[i][0]->units=argIdx[i-1][0]->units;
388  else if (arguments.size()>1 && !argIdx[1].empty())
389  argIdx[0][0]->units=argIdx[1][0]->units;
390  }
391  ev.push_back(EvalOpPtr(type(), state, result, *argIdx[0][0], *argIdx[1][0]));
392  break;
393  case userFunction:
394  for (size_t i=0; i<arguments.size(); ++i)
395  if (arguments[i].empty())
396  {
397  argIdx[i].emplace_back(VariableType::tempFlow);
398  argIdx[i].back()->allocValue();
399  }
400  ev.push_back(EvalOpPtr(type(), state, result, *argIdx[0][0], *argIdx[1][0]));
401  break;
402  case data:
403  if (!argIdx.empty() && argIdx[0].size()==1)
404  ev.push_back(EvalOpPtr(type(), state, result, *argIdx[0][0]));
405  else
406  throw error("inputs for highlighted operations incorrectly wired");
407  break;
408  default:
409  switch (classify(type()))
410  {
411  case reduction: case scan: case tensor:
412  if (result->idx()==-1) result->allocValue();
413  break; // TODO handle tensor properly later
414  default:
415  {
416  // sanity check that the correct number of arguments is provided
417  bool correctlyWired=true;
418  for (size_t i=0; i<arguments.size(); ++i)
419  correctlyWired &= arguments[i].size()==1;
420  if (!correctlyWired)
421  throw error("inputs for highlighted operations incorrectly wired");
422 
423  switch (arguments.size())
424  {
425  case 0:
426  ev.push_back(EvalOpPtr(type(), state, result));
427  break;
428  case 1:
429  ev.push_back(EvalOpPtr(type(), state, result, *argIdx[0][0]));
430  break;
431  case 2:
432  ev.push_back(EvalOpPtr(type(), state, result, *argIdx[0][0], *argIdx[1][0]));
433  break;
434  default:
435  throw error("Too many arguments");
436  }
437  }
438  }
439  }
440  }
441  catch (const std::exception&)
442  {
443  if (state) minsky::minsky().displayErrorItem(*state);
444  throw;
445  }
446  }
447  if (type()!=integrate && r->type()!=VariableType::undefined && r->isFlowVar() && result!=r)
448  ev.emplace_back(EvalOpPtr(new TensorEval(r,result)));
449  if (state && state->portsSize()>0)
450  if (auto statePort=state->ports(0).lock())
451  statePort->setVariableValue(result);
452  assert(result->idx()>=0);
453  doOneEvent(true);
454  return result;
455  }
456 
457  SystemOfEquations::SystemOfEquations(const Minsky& m): SystemOfEquations(m,*m.model) {}
458 
459 
461  {
464  zero->result=m.variableValues.find("constant:zero")->second;
465  one->result=m.variableValues.find("constant:one")->second;
466 
467  // store stock & integral variables for later reordering
468  map<string, VariableDAG*> integVarMap;
469 
470  vector<pair<VariableDAGPtr,Wire*>> integralInputs;
471  // list of stock vars whose input expression has not yet been calculated when the derivative operator is called.
472 
473  // search through operations looking for integrals
474  group.recursiveDo
475  (&Group::items,
476  [&](const Items&, Items::const_iterator it){
477  if (auto v=(*it)->variableCast())
478  {
479  // check variable is not multiply defined
480  if (v->inputWired() && v!=minsky.definingVar(v->valueId()).get())
481  {
482  minsky.displayErrorItem(*v);
483  throw runtime_error("Multiply defined");
484  }
485  // check that variable's type matches it's variableValue's type (see ticket #1087)
486  if (auto vv=v->vValue())
487  if (vv->type() != v->type())
488  {
489  minsky.displayErrorItem(*v);
490  throw error("type %s of variable %s doesn't match it's value's type %s",VariableType::typeName(v->type()).c_str(), v->name().c_str(), VariableType::typeName(vv->type()).c_str());
491  }
492  }
493  else if (IntOp* i=dynamic_cast<IntOp*>(it->get()))
494  {
495  if (const VariablePtr iv=i->intVar)
496  {
497  // .get() OK here because object lifetime controlled by
498  // expressionCache
499  VariableDAG* v=integVarMap[iv->valueId()]=
500  dynamic_cast<VariableDAG*>(makeDAG(*iv).get());
501  v->intOp=i;
502  if (!i->ports(1).lock()->wires().empty())
503  {
504  // with integrals, we need to create a distinct variable to
505  // prevent infinite recursion of order() in the case of graph cycles
507  input->name=iv->name();
508  variables.push_back(input.get());
509  // manage object's lifetime with expressionCache
510  expressionCache.insertIntegralInput(iv->valueId(), input);
511  try
512  {input->rhs=getNodeFromWire(*(i->ports(1).lock()->wires()[0]));}
513  catch (...)
514  {
515  // try again later
516  integralInputs.emplace_back(input,i->ports(1).lock()->wires()[0]);
517  // clear error indicator
519  }
520  }
521 
522  if (!i->ports(2).lock()->wires().empty())
523  {
524  // second port can be attached to a variable,
525  // which supplies an init string
526  NodePtr init;
527  try
528  {
529  init=getNodeFromWire(*(i->ports(2).lock()->wires()[0]));
530  }
531  catch (...) {}
532  if (auto v=dynamic_cast<VariableDAG*>(init.get()))
533  iv->init(uqName(v->name));
534  else if (auto c=dynamic_cast<ConstantDAG*>(init.get()))
535  {
536  // slightly convoluted to prevent sliderSet from overriding c->value
537  iv->init(c->value);
538  if (auto vv=iv->vValue()) vv->adjustSliderBounds();
539  }
540  else
541  throw error("only constants, parameters and variables can be connected to the initial value port");
542  }
543 
544  }
545  }
546  else if (auto fn=dynamic_cast<UserFunction*>(it->get()))
547  {
548  userDefinedFunctions.emplace(fn->description(), fn->expression);
549  }
550  return false;
551  });
552 
553  // add groups to the userDefinedFunctions table
554  group.recursiveDo
555  (&Group::groups,
556  [&](const Groups&, Groups::const_iterator it){
557  if (!(*it)->name().empty())
558  try
559  {
560  userDefinedFunctions.emplace((*it)->name()+(*it)->arguments(), (*it)->formula());
561  }
562  catch (const std::exception&)
563  {/* if we can't generate a function definition, too bad, too sad. */}
564  return false;
565  });
566 
567  // add input variables for all stock variables to the expression cache
568  group.recursiveDo
569  (&Group::items,
570  [&](const Items&, Items::const_iterator it){
571  if (auto i=dynamic_cast<Variable<VariableType::stock>*>(it->get()))
572  if (!expressionCache.getIntegralInput(i->valueId()))
573  {
575  input->name=i->name();
576  variables.push_back(input.get());
577  // manage object's lifetime with expressionCache
579  }
580  return false;
581  });
582 
583  // wire up integral inputs, now that all integrals are defined, so that derivative works. See #511
584  for (auto& i: integralInputs)
585  i.first->rhs=getNodeFromWire(*i.second);
586 
587  // process the Godley tables
588  derivInputs.clear();
589  map<string, GodleyColumnDAG> godleyVars;
590  group.recursiveDo
591  (&Group::items,
592  [&](const Items&, Items::const_iterator i)
593  {
594  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
595  processGodleyTable(godleyVars, *g);
596  return false;
597  });
598 
599  for (auto& g: godleyVars)
600  {
601  integVarMap[g.first]=dynamic_cast<VariableDAG*>
602  (makeDAG(g.first,
603  g.second.name, VariableValue::stock).get());
604  if (auto integralInput=expressionCache.getIntegralInput(g.first))
605  integralInput->rhs=expressionCache.insertAnonymous(make_shared<GodleyColumnDAG>(g.second));
606  }
607 
608  // fix up broken derivative computations, now that all stock vars
609  // are defined. See ticket #1087
610  for (auto& i: derivInputs)
611  if (auto ii=expressionCache.getIntegralInput(i.second))
612  {
613  if (ii->rhs)
614  {
615  i.first->rhs=ii->rhs;
616  continue;
617  }
618  }
619  else
620  throw runtime_error("Unable to differentiate "+i.second);
621 
622 // // check that all integral input variables now have a rhs defined,
623 // // so that derivatives can be processed correctly
624 // group.recursiveDo
625 // (&Group::items,
626 // [&](const Items&, Items::const_iterator i)
627 // {
628 // if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
629 // for (auto& v: g->flowVars())
630 // if (auto vv=minsky.definingVar(v->valueId()))
631 // {
632 // auto vd=makeDAG(*vv);
633 // static_cast<VariableDAG*>(vd.get())->rhs=getNodeFromWire(*vv->ports[1]->wires()[0]);
634 // }
635 // return false;
636 // });
637 
638 
639 
640  for (auto& v: integVarMap)
641  integrationVariables.push_back(v.second);
642 
643  if (&group==m.model.get())
644  {
645  for (auto& v: m.variableValues)
646  if (v.second->isFlowVar())
647  if (auto vv=dynamic_cast<VariableDAG*>
648  (makeDAG(v.first, v.second->name, v.second->type()).get()))
649  variables.push_back(vv);
650 
651  // sort variables into their order of definition
652  sort(variables.begin(), variables.end(),
653  VariableDefOrder(expressionCache.size()));
654  }
655  else
656  {
657  // now start with the variables, and work our way back to how they
658  // are defined
659  const VariableDefOrder variableDefOrder(expressionCache.size()+m.variableValues.size());
660  set<VariableDAG*,VariableDefOrder> variableSet(variableDefOrder);
661  group.recursiveDo
662  (&Group::items,
663  [&](const Items&, Items::const_iterator it){
664  if (auto v=(*it)->variableCast())
665  if (auto vv=v->vValue())
666  if (auto dag=dynamic_cast<VariableDAG*>
667  (makeDAG(v->valueId(), vv->name, vv->type()).get()))
668  variableSet.insert(dag);
669  return false;
670  });
671  // TODO - if we can pass VariableDefOrder to the definition of variableSet, we don't need to resort...
672  variables.insert(variables.end(), variableSet.begin(), variableSet.end());
673  }
674  }
675 
676  NodePtr SystemOfEquations::makeDAG(const string& valueId, const string& name, VariableType::Type type)
677  {
679  return expressionCache[valueId];
680 
681  if (!isValueId(valueId)||!minsky.variableValues.contains(valueId))
682  throw runtime_error("Invalid valueId: "+valueId);
683  auto vv=minsky.variableValues[valueId];
684 
685  if (type==VariableType::constant)
686  {
687  NodePtr r(new ConstantDAG(vv->init()));
688  r->result=vv;
690  return r;
691  }
692 
693  // ensure name is unique
694  string nm=name;
695  for (unsigned i=0; varNames.contains(nm); ++i)
696  nm=name+"_"+to_string(i);
697  varNames.insert(nm);
698 
699  shared_ptr<VariableDAG> r(new VariableDAG(valueId, nm, type));
701  r->init=vv->init();
702  if (auto v=minsky.definingVar(valueId))
703  if (v->type()!=VariableType::integral && v->numPorts()>1 && !v->ports(1).lock()->wires().empty())
704  r->rhs=getNodeFromWire(*v->ports(1).lock()->wires()[0]);
705  return r;
706  }
707 
709  {
711  return dynamic_pointer_cast<OperationDAGBase>(expressionCache[op]);
712 
713  if (op.type()==OperationType::differentiate)
714  {
715  assert(op.portsSize()==2);
716  NodePtr expr;
717  if (op.ports(1).lock()->wires().empty() || !(expr=getNodeFromWire(*op.ports(1).lock()->wires()[0])))
718  op.throw_error("derivative not wired");
719  try
720  {
721  return expressionCache.insert
722  (op, expr->derivative(*this));
723  }
724  catch (...)
725  {
727  throw;
728  }
729  }
730  else
731  {
732  shared_ptr<OperationDAGBase> r(OperationDAGBase::create(op.type()));
734  r->state=minsky.model->findItem(op);
735  assert(r->state);
736 
737  r->arguments.resize(op.numPorts()-1);
738  for (size_t i=1; i<op.portsSize(); ++i)
739  if (auto p=op.ports(i).lock())
740  for (auto w: p->wires())
741  r->arguments[i-1].push_back(getNodeFromWire(*w));
742  if (auto uf=dynamic_cast<const UserFunction*>(&op))
743  {
744  // add external variable references as additional "arguments" in order to determine the correct evaluation order
745  r->arguments.emplace_back();
746  for (auto& i: uf->symbolNames())
747  {
748  auto vv=minsky.variableValues.find(valueId(op.group.lock(), i));
749  if (vv!=minsky.variableValues.end())
750  {
751  r->arguments.back().emplace_back(makeDAG(vv->first,vv->second->name,vv->second->type()));
752  }
753  }
754  }
755  return r;
756  }
757  }
758 
760  {
761  // grab list of input wires
762  vector<Wire*> wires;
763  for (unsigned i=1; i<sw.portsSize(); ++i)
764  {
765  auto& w=sw.ports(i).lock()->wires();
766  if (w.empty())
767  {
768  minsky.displayErrorItem(sw);
769  throw error("input port not wired");
770  }
771  // shouldn't be more than 1 wire, ignore extraneous wires is present
772  assert(w.size()==1);
773  wires.push_back(w[0]);
774  }
775 
776  assert(wires.size()==sw.numCases()+1);
777  const Expr input(expressionCache, getNodeFromWire(*wires[0]));
778 
779  auto r=make_shared<OperationDAG<OperationType::add>>();
780  r->state=minsky.model->findItem(sw);
781  assert(r->state);
782  expressionCache.insert(sw, r);
783  r->arguments[0].resize(sw.numCases());
784 
785  // implemented as a sum of step functions
786  r->arguments[0][0]=getNodeFromWire(*wires[1])*(input<1);
787  for (unsigned i=1; i<sw.numCases()-1; ++i)
788  r->arguments[0][i]=getNodeFromWire
789  (*wires[i+1])*((input<i+1) - (input<i));
790  r->arguments[0][sw.numCases()-1]=getNodeFromWire
791  (*wires[sw.numCases()])*(1-(input<sw.numCases()-1));
792  return r;
793  };
794 
796  {
797  assert(result);
798  if (result->type()==VariableType::undefined)
799  {
800  if (r && r->isFlowVar())
801  result=r;
802  else
803  {
805  result->allocValue();
806  }
807  }
808 
809  if (!rhs) return result;
810  if (lock.locked())
811  try
812  {
813  auto chain=createRavelChain(lock.lockedState, rhs->addEvalOps(ev,{}));
814  if (chain.empty()) return {};
815  result->index(chain.back()->index());
816  result->hypercube(chain.back()->hypercube());
817  ev.emplace_back(EvalOpPtr(new TensorEval(result, make_shared<EvalCommon>(), chain.back())));
818  return result;
819  }
820  catch (const std::exception& ex)
821  {
822  lock.throw_error(ex.what());
823  }
824  return rhs->addEvalOps(ev,result);
825  }
826 
827 
829  {
830  auto r=make_shared<LockDAG>(l);
831  expressionCache.insert(l,r);
832  auto ravel=l.ravelInput();
833  if (ravel && l.locked())
834  {
835  if (auto p=ravel->ports(1).lock())
836  if (!p->wires().empty())
837  r->rhs=getNodeFromWire(*p->wires()[0]);
838  }
839  else
840  if (auto p=l.ports(1).lock())
841  if (!p->wires().empty())
842  r->rhs=getNodeFromWire(*p->wires()[0]);
843  return r;
844  }
845 
847  {
848  if (auto p=wire.from())
849  {
850  auto& item=p->item();
851  if (auto o=item.operationCast())
852  {
853  if (expressionCache.exists(*o))
854  return expressionCache[*o];
855  // we're wired to an operation
856  return makeDAG(*o);
857  }
858  if (auto v=item.variableCast())
859  {
860  if (expressionCache.exists(*v))
861  return expressionCache[*v];
862  if (v && v->type()!=VariableBase::undefined)
863  // we're wired to a variable
864  return makeDAG(*v);
865  }
866  else if (auto s=dynamic_cast<SwitchIcon*>(&item))
867  {
868  if (expressionCache.exists(*s))
869  return expressionCache[*s];
870  return makeDAG(*s);
871  }
872  else if (auto l=dynamic_cast<Lock*>(&item))
873  {
874  if (expressionCache.exists(*l))
875  return expressionCache[*l];
876  return makeDAG(*l);
877  }
878  }
879  return {};
880  }
881 
883  {
884  NodePtr r;
885  if (expressionCache.exists(v))
886  return dynamic_pointer_cast<VariableDAG>(expressionCache[v]);
887  if (v.type()!=VariableBase::undefined) r=makeDAG(const_cast<VariableBase&>(v));
888  return dynamic_pointer_cast<VariableDAG>(r);
889  }
890 
892  {
893  ostringstream o;
894 
896  if (input && input->rhs) input->rhs->latex(o);
897 
898  return o;
899  }
900 
901 
902  ostream& SystemOfEquations::latex(ostream& o) const
903  {
904  o << "\\begin{eqnarray*}\n";
905  // output user defined functions
906  for (auto& i: userDefinedFunctions)
907  o<<i.first<<"(x,y)&=&"<<i.second<<"\\\\\n";
908  for (const VariableDAG* i: variables)
909  {
910  if (dynamic_cast<const IntegralInputVariableDAG*>(i) ||
911  !i || i->type==VariableType::constant || i->type==VariableType::tempFlow) continue;
912  o << i->latex() << "&=&";
913  if (i->rhs)
914  i->rhs->latex(o);
915  else
916  o<<latexInit(i->init);
917  o << "\\\\\n";
918  }
919 
920  for (const VariableDAG* i: integrationVariables)
921  {
922  o << mathrm(i->name)<<"(0)&=&"<<latexInit(i->init)<<"\\\\\n";
923  o << "\\frac{ d " << mathrm(i->name) <<
924  "}{dt} &=&";
926  if (input && input->rhs)
927  input->rhs->latex(o);
928  o << "\\\\\n";
929  }
930  return o << "\\end{eqnarray*}\n";
931  }
932 
933  ostream& SystemOfEquations::latexWrapped(ostream& o) const
934  {
935  o << "\\begin{dgroup*}\n";
936  for (const VariableDAG* i: variables)
937  {
938  if (dynamic_cast<const IntegralInputVariableDAG*>(i)) continue;
939  if (!i || i->type==VariableType::constant || i->type==VariableType::tempFlow) continue;
940  o<<"\\begin{dmath*}\n";
941  o << i->latex() << "=";
942  if (i->rhs)
943  i->rhs->latex(o);
944  else
945  o<<latexInit(i->init);
946  o << "\n\\end{dmath*}\n";
947  }
948 
949  for (const VariableDAG* i: integrationVariables)
950  {
951  o<<"\\begin{dmath*}\n";
952  o << mathrm(i->name)<<"(0)="<<latexInit(i->init)<<"\n\\end{dmath*}\n";
953  o << "\\begin{dmath*}\n\\frac{ d " << mathrm(i->name) <<
954  "}{dt} =";
956  if (input && input->rhs)
957  input->rhs->latex(o);
958  o << "\\end{dmath*}\n";
959  }
960  return o << "\\end{dgroup*}\n";
961  }
962 
963  ostream& SystemOfEquations::matlab(ostream& o) const
964  {
965  // output user defined functions
966  for (auto& i: userDefinedFunctions)
967  {
968  o<<"function f="<<i.first<<"\n";
969  o<<"f="<<i.second<<"\nendfunction;\n\n";
970  }
971  o<<"function f=f(x,t)\n";
972  // define names for the components of x for reference
973  int j=1;
974  for (const VariableDAG* i: integrationVariables)
975  o<<i->matlab()<<"=x("<<j++<<");\n";
976 
977  for (const VariableDAG* i: variables)
978  {
979  if (dynamic_cast<const IntegralInputVariableDAG*>(i) ||
980  !i || i->type==VariableType::constant) continue;
981  o << i->matlab() << "=";
982  if (i->rhs)
983  o << i->rhs->matlab();
984  else
985  o << matlabInit(i->init);
986  o<<";\n";
987  }
988 
989  j=1;
990  for (const VariableDAG* i: integrationVariables)
991  {
992  o << "f("<<j++<<")=";
994  if (input && input->rhs)
995  input->rhs->matlab(o);
996  else
997  o<<0;
998  o<<";\n";
999  }
1000  o<<"endfunction;\n\n";
1001 
1002  // now write out the initial conditions
1003  j=1;
1004  for (const VariableDAG* i: integrationVariables)
1005  o << "x0("<<j++<<")="<<matlabInit(i->init)<<";\n";
1006 
1007  return o;
1008  }
1009 
1011  (EvalOpVector& equations, vector<Integral>& integrals)
1012  {
1013  equations.clear();
1014  integrals.clear();
1015 
1016  for (VariableDAG* i: variables)
1017  {
1018  i->addEvalOps(equations,i->result);
1019  assert(minsky.variableValues.validEntries());
1020  }
1021 
1022  for (const VariableDAG* i: integrationVariables)
1023  {
1024  const string vid=i->valueId;
1025  integrals.push_back(Integral());
1026  assert(isValueId(vid));
1027  assert(minsky.variableValues.count(vid));
1028  integrals.back().stock=minsky.variableValues[vid];
1029  integrals.back().operation=dynamic_cast<IntOp*>(i->intOp);
1030  const VariableDAGPtr iInput=expressionCache.getIntegralInput(vid);
1031  if (iInput && iInput->rhs)
1032  integrals.back().setInput(iInput->rhs->addEvalOps(equations));
1033  }
1034  assert(minsky.variableValues.validEntries());
1035  }
1036 
1038  {
1039  // ensure all variables have their output port's variable value up to date
1040  minsky.model->recursiveDo
1041  (&Group::items,
1042  [&](Items&, Items::iterator i)
1043  {
1044  if (auto v=(*i)->variableCast())
1045  {
1046  if (v->type()==VariableType::undefined)
1047  throw error("variable %s has undefined type",v->name().c_str());
1048  assert(minsky.variableValues.count(v->valueId()));
1049  if (v->portsSize()>0)
1050  v->ports(0).lock()->setVariableValue(minsky.variableValues[v->valueId()]);
1051  }
1052  else if (auto pw=(*i)->plotWidgetCast())
1053  for (size_t port=0; port<pw->portsSize(); ++port)
1054  for (auto w: pw->ports(port).lock()->wires())
1055  // ensure plot inputs are evaluated
1056  w->from()->setVariableValue(getNodeFromWire(*w)->addEvalOps(equations));
1057  else if (auto s=dynamic_cast<Sheet*>(i->get()))
1058  {
1059  for (auto w: s->ports(0).lock()->wires())
1060  // ensure sheet inputs are evaluated
1061  w->from()->setVariableValue(getNodeFromWire(*w)->addEvalOps(equations));
1062  s->computeValue();
1063  }
1064  else if (auto r=dynamic_cast<Ravel*>(i->get()))
1065  for (auto w: r->ports(1).lock()->wires())
1066  // ensure sheet inputs are evaluated
1067  w->from()->setVariableValue(getNodeFromWire(*w)->addEvalOps(equations));
1068 
1069  return false;
1070  });
1071  }
1072 
1074  (map<string, GodleyColumnDAG>& godleyVariables, const GodleyIcon& gi)
1075  {
1076  auto& godley=gi.table;
1077  for (size_t c=1; c<godley.cols(); ++c)
1078  {
1079  string colName=trimWS(godley.cell(0,c));
1080  if (uqName(colName).empty())
1081  continue; // ignore empty Godley columns
1082  // resolve scope
1083  colName=valueId(gi.group.lock(), colName);
1084  if (processedColumns.contains(colName)) continue; //skip shared columns
1085  processedColumns.insert(colName);
1086  GodleyColumnDAG& gd=godleyVariables[colName];
1087  gd.name=trimWS(godley.cell(0,c));
1088  gd.arguments.resize(2);
1089  const vector<WeakNodePtr>& arguments=gd.arguments[0];
1090  for (size_t r=1; r<godley.rows(); ++r)
1091  {
1092  if (godley.initialConditionRow(r)) continue;
1093  const FlowCoef fc(godley.cell(r,c));
1094  if (fc.name.empty()) continue;
1095 
1096  const VariablePtr v(VariableType::flow, fc.name);
1097  v->group=gi.group;
1098 
1099  if (abs(fc.coef)==1)
1100  gd.arguments[fc.coef<0? 1: 0].push_back(WeakNodePtr(makeDAG(*v)));
1101  else
1102  {
1105  gd.arguments[fc.coef<0? 1: 0].push_back
1106  (expressionCache.insertAnonymous(NodePtr(term)));
1107 
1108  term->arguments.resize(2);
1109  term->arguments[0].push_back
1110  (expressionCache.insertAnonymous(NodePtr(new ConstantDAG(abs(fc.coef)))));
1111  term->arguments[1].push_back(WeakNodePtr(makeDAG(*v)));
1112  }
1113  }
1114  }
1115  }
1116 }
1117 
1118 
virtual VariableValuePtr addEvalOps(EvalOpVector &, const VariableValuePtr &result={})=0
adds EvalOps to an EvalOpVector representing this node.
bool addTensorOp(const shared_ptr< VariableValue > &result, OperationDAGBase &nodeOp, EvalOpVector &ev)
Definition: equations.cc:88
bool exists(const T &x)
Definition: equations.h:315
void doOneEvent(bool idletasksOnly)
checks if any GUI events are waiting, and proces an event if so
Definition: tclmain.cc:161
Definition: input.py:1
Ravel * ravelInput() const
Ravel this is connected to. nullptr if not connected to a Ravel.
Definition: lock.cc:55
A helper to evaluate a variable value.
void displayErrorItem(const Item &op) const
indicate operation item has error, if visible, otherwise contining group
Definition: minsky.cc:1230
void resetValue(VariableValue &) const
reset a give variable value to it&#39;s initial condition, in this context
vector< TensorPtr > createRavelChain(const ravel::RavelState &state, const TensorPtr &arg)
creates a chain of tensor operations that represents a Ravel in state state, operating on arg ...
Definition: tensorOp.cc:497
NodePtr makeDAG(const string &valueId, const string &name, VariableType::Type type)
create a variable DAG. returns cached value if previously called
Definition: equations.cc:676
ostream & latexWrapped(ostream &) const
Definition: equations.cc:933
NoArgument(const OperationPtr &s, unsigned a1, unsigned a2)
Definition: equations.cc:48
VariableValues variableValues
Definition: minsky.h:200
An integral is an additional stock variable, that integrates its flow variable.
Definition: integral.h:28
shared_ptr class for polymorphic operation objects. Note, you may assume that this pointer is always ...
const NodePtr & insert(const T &x, const NodePtr &n)
Definition: equations.h:325
std::size_t size() const
Definition: equations.h:340
STL namespace.
void populateEvalOpVector(EvalOpVector &equations, std::vector< Integral > &integrals)
Definition: equations.cc:1011
SubexpressionCache expressionCache
Definition: equations.h:360
SystemOfEquations(const Minsky &, const Group &g)
construct the system of equations
Definition: equations.cc:460
bool operator()(const VariableDAG *x, const VariableDAG *y) const
Definition: equations.cc:40
std::shared_ptr< Item > ItemPtr
Definition: item.h:57
VariableValuePtr result
reference to where this node&#39;s value is stored
Definition: equations.h:128
std::string uqName(const std::string &name)
extract unqualified portion of name
Definition: valueId.cc:135
vector< VariableDAG * > integrationVariables
Definition: equations.h:363
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
Definition: valueId.cc:75
a shared_ptr that default constructs a default target, and is always valid
NodePtr zero
useful constants to share
Definition: equations.h:418
std::shared_ptr< ITensor > create(const ItemPtr &, const TensorsFromPort &tp={})
create a tensor representation of the expression rooted at op. If expression doesn&#39;t contain any refe...
string latexInit(const string &)
convert an initialisation string into a matlab expression
Definition: node_latex.cc:73
ravel::RavelState lockedState
Definition: lock.h:37
GodleyTable table
table data. Must be declared before editor
Definition: godleyIcon.h:80
struct TCLcmd::trap::init_t init
ItemPtr itemIndicator
for drawing error indicator on the canvas
Definition: canvas.h:120
bool locked() const
Definition: lock.h:39
OperationFactory< OperationDAGBase, OperationDAG, OperationType::numOps-1 > operationDAGFactory
Definition: equations.cc:174
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
std::vector< ItemPtr > Items
Definition: item.h:366
std::string trimWS(const std::string &s)
Definition: str.h:49
void processGodleyTable(map< string, GodleyColumnDAG > &godleyVariables, const GodleyIcon &)
Definition: equations.cc:1074
string matlabInit(const string &)
convert an initialisation string into a matlab expression
Definition: node_matlab.cc:45
virtual std::string valueId() const
string used to link to the VariableValue associated with this
Definition: variable.cc:186
VariableDAGPtr getIntegralInput(const string &name) const
Definition: equations.h:333
void updatePortVariableValue(EvalOpVector &equations)
Definition: equations.cc:1037
Canvas canvas
Definition: minsky.h:256
std::vector< GroupPtr > Groups
Definition: group.h:54
VariableValuePtr addEvalOps(EvalOpVector &, const VariableValuePtr &result={}) override
adds EvalOps to an EvalOpVector representing this node.
Definition: equations.cc:795
static std::string typeName(int t)
Definition: variableType.cc:30
void cumulate(EvalOpVector &ev, const ItemPtr &state, VariableValuePtr &r, const vector< vector< VariableValuePtr > > &argIdx, OperationType::Type op, OperationType::Type accum, double groupIdentity)
Definition: equations.cc:236
NodePtr getNodeFromWire(const Wire &wire)
returns cached subexpression node representing what feeds the wire, creating a new one if necessary ...
Definition: equations.cc:846
factory class template, for creating objects of type T<O>. N is the maximum value of O ...
Definition: operationType.h:76
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:549
ostream & matlab(ostream &) const
render as MatLab code create equations suitable for Runge-Kutta solver
Definition: equations.cc:963
void throw_error(const std::string &) const
mark item on canvas, then throw
Definition: item.cc:86
VariableDAGPtr getNodeFromVar(const VariableBase &v)
Definition: equations.cc:882
weak reference into subexpression cache
Definition: equations.h:133
represents a numerical coefficient times a variable (a "flow")
Definition: flowCoef.h:27
std::shared_ptr< ITensor > TensorPtr
std::shared_ptr< Node > NodePtr
Definition: equations.h:131
bool isValueId(const string &name)
check that name is a valid valueId (useful for assertions)
Definition: valueId.cc:33
Groups groups
Definition: group.h:79
represents the input of an integration operation - differs from Variable DAG in that it doesn&#39;t refer...
Definition: equations.h:212
int order(unsigned maxOrder) const override
returns evaluation order in sequence of variable defintions
Definition: equations.h:185
NodePtr insertAnonymous(NodePtr x)
Definition: equations.h:351
void insertIntegralInput(const string &name, const VariableDAGPtr &n)
Definition: equations.h:329
TensorOpFactory tensorOpFactory
vector< vector< WeakNodePtr > > arguments
Definition: equations.h:219
std::set< std::string > varNames
used to rename ambiguous variables in different scopes
Definition: equations.h:389
represents a Godley column
Definition: equations.h:270
vector< pair< VariableDAGPtr, string > > derivInputs
Definition: equations.h:365
std::map< std::string, std::string > userDefinedFunctions
table of user defined functions and their definitions
Definition: equations.h:393
GroupPtr model
Definition: minsky.h:255
vector< VariableDAG * > variables
Definition: equations.h:362
virtual std::weak_ptr< Port > ports(std::size_t i) const
callback to be run when item deleted from group
Definition: item.h:180
const Lock & lock
Definition: equations.h:278
Definition: group.tcl:84
string to_string(CONST84 char *x)
Definition: minskyTCLObj.h:33
shared_ptr< VariableDAG > VariableDAGPtr
Definition: equations.h:208
Minsky & minsky()
global minsky object
Definition: minskyTCL.cc:51
string mathrm(const string &nm)
wraps in if nm has more than one letter - and also takes into account LaTeX subscript/superscripts ...
Definition: node_latex.cc:41
ostringstream getDefFromIntVar(const VariableBase &v)
Definition: equations.cc:891
WeakNodePtr rhs
Definition: equations.h:279
static Group classify(Type t)
static OperationDAGBase * create(Type type, const string &name="")
factory method
Definition: equations.cc:176
WeakNodePtr rhs
Definition: equations.h:178
ostream & latex(ostream &) const
render as a LaTeX eqnarray Use LaTeX brqn environment to wrap long lines
Definition: equations.cc:902