Minsky
minsky.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 #include "minsky.h"
20 #include "cairoItems.h"
21 #include "classdesc_access.h"
22 #include "flowCoef.h"
23 #include "userFunction.h"
24 #include "mdlReader.h"
25 #include "variableInstanceList.h"
26 
27 #include "TCL_obj_stl.h"
28 #include <cairo_base.h>
29 
30 #include "schema3.h"
31 
32 //#include <thread>
33 // std::thread apparently not supported on MXE for now...
34 #include <boost/thread.hpp>
35 
36 #include "minskyVersion.h"
37 
38 #include "fontDisplay.rcd"
39 #include "minsky.rcd"
40 #include "minsky.xcd"
41 #include "signature.h"
42 #include "dimension.rcd"
43 #include "callableFunction.rcd"
44 #include "tensorInterface.xcd"
45 #include "tensorVal.xcd"
46 #include "pannableTab.rcd"
47 #include "pannableTab.xcd"
48 #include "polyRESTProcessBase.h"
49 #include "minsky_epilogue.h"
50 
51 #include <algorithm>
52 #include <boost/filesystem.hpp>
53 
54 #ifdef _WIN32
55 #include <windows.h>
56 #else
57 #include <unistd.h>
58 #include <sys/wait.h>
59 #endif
60 
61 #if defined(__linux__)
62 #include <sys/sysinfo.h>
63 #endif
64 
65 #ifdef __APPLE__
66 #include <sys/types.h>
67 #include <sys/sysctl.h>
68 #endif
69 
70 #include <stdio.h>
71 
72 using namespace classdesc;
73 using namespace boost::posix_time;
74 
75 
76 namespace minsky
77 {
78  namespace
79  {
81  template <class E> vector<string> enumVals()
82  {
83  vector<string> r;
84  for (size_t i=0; i < sizeof(enum_keysData<E>::keysData) / sizeof(EnumKey); ++i)
85  r.push_back(enum_keysData<E>::keysData[i].name);
86  return r;
87  }
88 }
89 
90  bool Minsky::multipleEquities(const bool& m) {
91  m_multipleEquities=m;
92  canvas.requestRedraw();
93  redrawAllGodleyTables();
94  return m_multipleEquities;
95  }
96 
97  void Minsky::openLogFile(const string& name)
98  {
99  outputDataFile.reset(new ofstream(name));
100  *outputDataFile<< "#time";
101  for (auto& v: variableValues)
102  if (logVarList.contains(v.first))
103  *outputDataFile<<" "<<CSVQuote(v.second->name,' ');
104  *outputDataFile<<endl;
105  }
106 
108  void Minsky::logVariables() const
109  {
110  if (outputDataFile)
111  {
112  *outputDataFile<<t;
113  for (auto& v: variableValues)
114  if (logVarList.contains(v.first))
115  *outputDataFile<<" "<<v.second->value();
116  *outputDataFile<<endl;
117  }
118  }
119 
120  Minsky::~Minsky()
121  {
122  if (edited() && autoSaver)
123  // if we're at this point, then the user has already been asked to save, and chosen no.
124  boost::filesystem::remove(autoSaver->fileName);
125  }
126 
127  void Minsky::clearAllMaps(bool doClearHistory)
128  {
129  model->clear();
130  canvas.openGroupInCanvas(model);
131  equations.clear();
132  integrals.clear();
133  variableValues.clear();
134  variablePane.update();
135  maxValue.clear();
136  PhillipsFlow::maxFlow.clear();
137  PhillipsStock::maxStock.clear();
138  phillipsDiagram.clear();
139  publicationTabs.clear();
140  publicationTabs.emplace_back("Publication");
141  userFunctions.clear();
142  UserFunction::nextId=0;
143 
144  flowVars.clear();
145  stockVars.clear();
146 
147  dimensions.clear();
148  namedItems.clear();
149  flags=reset_needed|fullEqnDisplay_needed;
150  fileVersion=minskyVersion;
151  if (doClearHistory) clearHistory();
152  }
153 
154 
155  const std::string Minsky::minskyVersion=MINSKY_VERSION;
156 
157  void Minsky::cut()
158  {
159  copy();
160  for (auto& i: canvas.selection.items)
161  {
162  if (auto v=i->variableCast())
163  if (v->controller.lock())
164  continue; // do not delete a variable controlled by another item
165  model->deleteItem(*i);
166  }
167  for (auto& i: canvas.selection.groups)
168  model->removeGroup(*i);
169  for (auto& i: canvas.selection.wires)
170  model->removeWire(*i);
171  canvas.item.reset();
172  canvas.itemFocus.reset();
173 #ifndef NDEBUG
174  garbageCollect();
175  for (auto& i: canvas.selection.items)
176  {
177  if (auto v=i->variableCast())
178  if (v->controller.lock())
179  continue; // variable controlled by another item is not being destroyed
180  assert(i.use_count()==1);
181  }
182  for (auto& i: canvas.selection.groups)
183  assert(i.use_count()==1);
184  for (auto& i: canvas.selection.wires)
185  assert(i.use_count()==1);
186 #endif
187  canvas.selection.clear();
188  canvas.requestRedraw();
189  }
190 
191  void Minsky::copy() const
192  {
193  if (canvas.selection.empty())
194  clipboard.putClipboard(""); // clear clipboard
195  else
196  {
197  schema3::Minsky m(canvas.selection);
198  ostringstream os;
199  xml_pack_t packer(os, schemaURL);
200  xml_pack(packer, "Minsky", m);
201  clipboard.putClipboard(os.str());
202  }
203  }
204 
205  VariablePtr Minsky::definingVar(const string& valueId) const
206  {
207  return dynamic_pointer_cast<VariableBase>
208  (model->findAny(&Group::items, [&](const ItemPtr& x) {
209  auto v=x->variableCast();
210  return v && v->valueId()==valueId && v->defined();
211  }));
212  }
213 
214  void Minsky::saveGroupAsFile(const Group& g, const string& fileName)
215  {
216  const schema3::Minsky m(g);
217  Saver(fileName).save(m);
218  }
219 
220  void Minsky::paste()
221  try
222  {
223  // don't paste if previous created item hasn't been placed yet.
224  if (canvas.itemFocus) return;
225  map<string,string> existingParms;
226  // preserve initial conditions.
227  for (auto& [valueId,vv]: variableValues)
228  existingParms.emplace(valueId,vv->init());
229 
230  istringstream is(clipboard.getClipboard());
231  xml_unpack_t unpacker(is);
232  schema3::Minsky m(unpacker);
233  GroupPtr g(new Group);
234  g->self=g;
235  m.populateGroup(*g);
236 
237  // stash values of parameters in the copied group, for ticket 1258
238  for (auto& i: g->items)
239  if (auto v=i->variableCast(); v && v->type()==VariableType::parameter)
240  existingParms.emplace(v->valueId(),v->init());
241 
242  // Default pasting no longer occurs as grouped items or as a group within a group. Fix for tickets 1080/1098
243  canvas.selection.clear();
244  // The following is only necessary if one pastes into an existing model. For ticket 1258
245  if (!history.empty() || !canvas.model.get()->empty()) {
246  bool alreadyDefinedMessageDisplayed=false;
247 
248  // convert stock variables that aren't defined to flow variables, and other fix up multiply defined vars
249  g->recursiveDo(&GroupItems::items,
250  [&](Items&, Items::iterator i) {
251  if (auto v=(*i)->variableCast())
252  if (v->defined() || v->isStock())
253  {
254  // if defined, check no other defining variable exists
255  auto alreadyDefined = canvas.model->findAny
256  (&GroupItems::items,
257  [&v](const ItemPtr& j)
258  {return j.get()!=v && j->variableCast() && j->variableCast()->defined();});
259  if (v->isStock())
260  {
261  if (v->defined() && alreadyDefined && !alreadyDefinedMessageDisplayed)
262  {
263  message("Integral/Stock variable "+v->name()+" already defined.");
264  alreadyDefinedMessageDisplayed=true;
265  }
266  else if (!v->defined() && !alreadyDefined)
267  {
268  // need to do this var explicitly, as not currently part of model structure
269  if (auto vp=VariablePtr(*i))
270  {
271  vp.retype(VariableType::flow);
272  *i=vp;
273  convertVarType(vp->valueId(), VariableType::flow);
274  }
275  }
276  }
277  else if (alreadyDefined)
278  {
279  // delete defining wire from this
280  assert(v->portsSize()>1 && !v->ports(1).lock()->wires().empty());
281  g->removeWire(*v->ports(1).lock()->wires()[0]);
282  }
283  }
284  return false;
285  });
286  }
287 
288  canvas.model->addGroup(g); // needed to ensure wires are correctly handled
289  auto copyOfItems=g->items;
290  auto copyOfGroups=g->groups;
291 
292  // ungroup g, putting all its contents on the canvas
293  canvas.model->moveContents(*g);
294 
295  // leave newly ungrouped items in selection
296  for (auto& i: copyOfItems)
297  canvas.selection.ensureItemInserted(i);
298 
299  // ensure that initial values of pasted parameters are correct. for ticket 1258
300  for (auto& p: existingParms)
301  if (auto vv=variableValues.find(p.first); vv!=variableValues.end())
302  vv->second->init(p.second);
303  existingParms.clear();
304 
305  // Attach mouse focus only to first visible item in selection. For ticket 1098.
306  for (auto& i: canvas.selection.items)
307  if (i->visible())
308  {
309  canvas.setItemFocus(i);
310  break;
311  }
312 
313  if (!copyOfGroups.empty()) canvas.setItemFocus(copyOfGroups[0]);
314 
315  canvas.model->removeGroup(*g);
316  canvas.requestRedraw();
317  }
318  catch (...)
319  {
320  throw runtime_error("clipboard data invalid");
321  }
322 
323  void Minsky::insertGroupFromFile(const string& file)
324  {
325  ifstream inf(file);
326  if (!inf)
327  throw runtime_error(string("failed to open ")+file);
329  xml_unpack_t saveFile(inf);
330  const schema3::Minsky currentSchema(saveFile);
331 
332  const GroupPtr g(new Group);
333  currentSchema.populateGroup(*model->addGroup(g));
334  g->resizeOnContents();
335  canvas.itemFocus=g;
336  }
337 
338  void Minsky::makeVariablesConsistent()
339  {
340  // remove variableValues not in variables
341  set<string> existingNames;
342  existingNames.insert("constant:zero");
343  existingNames.insert("constant:one");
344  vector<GodleyIcon*> godleysToUpdate;
345  model->recursiveDo(&Group::items,
346  [&](Items&,Items::iterator i) {
347  if (auto v=(*i)->variableCast())
348  existingNames.insert(v->valueId());
349  // ensure Godley table variables are the correct types
350  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
351  godleysToUpdate.push_back(g);
352  return false;
353  }
354  );
355 
356  for (auto g: godleysToUpdate) g->update();
357  for (auto i=variableValues.begin(); i!=variableValues.end(); )
358  if (existingNames.contains(i->first))
359  ++i;
360  else
361  variableValues.erase(i++);
362 
363  }
364 
365  void Minsky::imposeDimensions()
366  {
367  for (auto& v: variableValues)
368  {
369  v.second->imposeDimensions(dimensions);
370  v.second->tensorInit.imposeDimensions(dimensions);
371  }
372  }
373 
374 
375  void Minsky::garbageCollect()
376  {
377  makeVariablesConsistent();
378  stockVars.clear();
379  flowVars.clear();
380  equations.clear();
381  integrals.clear();
382 
383  // remove all temporaries
384  for (auto v=variableValues.begin(); v!=variableValues.end();)
385  if (v->second->temp())
386  variableValues.erase(v++);
387  else
388  ++v;
389 
390  variableValues.reset();
391  }
392 
393  void Minsky::constructEquations()
394  {
395  if (cycleCheck()) throw error("cyclic network detected");
396 
397  const ProgressUpdater pu(progressState,"Construct equations",8);
398  garbageCollect();
399  equations.clear();
400  integrals.clear();
401  ++progressState;
402 
403  // add all user defined functions to the global symbol tables
404  userFunctions.clear();
405  model->recursiveDo
406  (&Group::items,
407  [this](const Items&, Items::const_iterator it){
408  if (auto f=dynamic_pointer_cast<CallableFunction>(*it))
409  userFunctions[valueIdFromScope((*it)->group.lock(), canonicalName(f->name()))]=f;
410  return false;
411  });
412  ++progressState;
413  model->recursiveDo
414  (&Group::groups,
415  [this](const Groups&, Groups::const_iterator it){
416  userFunctions[valueIdFromScope((*it)->group.lock(), canonicalName((*it)->name()))]=*it;
417  return false;
418  });
419  ++progressState;
420 
422 
423  MathDAG::SystemOfEquations system(*this);
424  ++progressState;
425  assert(variableValues.validEntries());
426  system.populateEvalOpVector(equations, integrals);
427  ++progressState;
428  assert(variableValues.validEntries());
429  system.updatePortVariableValue(equations);
430  }
431 
432  void Minsky::dimensionalAnalysis() const
433  {
434  const_cast<Minsky*>(this)->variableValues.resetUnitsCache();
435  // increment varsPassed by one to prevent resettting the cache on each check
436  const IncrDecrCounter vpIdc(VariableBase::varsPassed);
437  model->recursiveDo
438  (&Group::items,
439  [&](Items& m, Items::iterator i)
440  {
441  if (auto v=(*i)->variableCast())
442  {
443  // check only the defining variables
444  if (v->isStock() && (v->inputWired() || v->controller.lock().get()))
445  v->checkUnits();
446  }
447  else if ((*i)->portsSize()>0 && !(*i)->ports(0).lock()->input() &&
448  (*i)->ports(0).lock()->wires().empty())
449  (*i)->checkUnits(); // check anything with an unwired output port
450  else if (auto p=(*i)->plotWidgetCast())
451  for (size_t j=0; j<p->portsSize(); ++j)
452  p->ports(j).lock()->checkUnits();
453  else if (auto p=dynamic_cast<Sheet*>(i->get()))
454  for (size_t j=0; j<p->portsSize(); ++j)
455  p->ports(j).lock()->checkUnits();
456  return false;
457  });
458  }
459 
460  void Minsky::deleteAllUnits()
461  {
462  for (auto& i: variableValues)
463  i.second->units.clear();
464  timeUnit.clear();
465  }
466 
467  void Minsky::requestReset()
468  {
469  if (resetDuration<resetNowThreshold)
470  {
471  try
472  {
473  reset();
474  }
475  catch (...)
476  {}
477  return;
478  }
479  flags|=reset_needed;
480  // schedule reset for some time in the future
481  if (resetDuration<resetPostponedThreshold)
482  resetAt=std::chrono::system_clock::now()+resetPostponedThreshold;
483  else // postpone "indefinitely"
484  resetAt=std::chrono::time_point<std::chrono::system_clock>::max();
485  }
486 
487  void Minsky::requestRedraw()
488  {
489  // requestRedraw on all tabs - only the active one will actually do anything
490  canvas.requestRedraw();
491  equationDisplay.requestRedraw();
492  phillipsDiagram.requestRedraw();
493  for (auto& pub: publicationTabs)
494  pub.requestRedraw();
495  }
496 
497  void Minsky::populateMissingDimensions() {
498  // populate from variable value table first, then override by ravels
499  bool incompatibleMessageDisplayed=false;
500  for (auto& v: variableValues)
501  populateMissingDimensionsFromVariable(*v.second, incompatibleMessageDisplayed);
502  model->recursiveDo
503  (&Group::items,[&](Items& m, Items::iterator it)
504  {
505  if (auto ri=dynamic_cast<Ravel*>(it->get()))
506  {
507  auto state=ri->getState();
508  for (auto& j: state.handleStates)
509  dimensions.emplace(j.description,Dimension());
510  }
511  return false;
512  });
513  requestReset();
514  }
515 
516  void Minsky::populateMissingDimensionsFromVariable(const VariableValue& v, bool& incompatibleMessageDisplayed)
517  {
518  set<string> varDimensions;
519  for (auto& xv: v.hypercube().xvectors)
520  {
521  auto d=dimensions.find(xv.name);
522  if (d==dimensions.end())
523  {
524  dimensions.emplace(xv.name, xv.dimension);
525  varDimensions.insert(xv.name);
526  }
527  else if (d->second.type!=xv.dimension.type && !incompatibleMessageDisplayed)
528  {
529  message("Incompatible dimension type for dimension "+d->first+". Please adjust the global dimension in the dimensions dialog, which can be found under the Edit menu.");
530  incompatibleMessageDisplayed=true;
531  }
532  }
533  // set all newly populated dimensions on Ravels to forward sort order
534  model->recursiveDo
535  (&Group::items,[&](Items& m, Items::iterator it)
536  {
537  if (auto ri=dynamic_cast<Ravel*>(it->get()))
538  for (size_t i=0; i<ri->numHandles(); ++i)
539  if (varDimensions.contains(ri->handleDescription(i)))
540  ri->setHandleSortOrder(ravel::HandleSort::forward, i);
541  return false;
542  });
543  }
544 
545  void Minsky::renameDimension(const std::string& oldName, const std::string& newName)
546  {
547  auto i=dimensions.find(oldName);
548  if (i!=dimensions.end())
549  {
550  dimensions[newName]=i->second;
551  dimensions.erase(i);
552  }
553 
554  for (auto& v: variableValues)
555  {
556  auto hc=v.second->tensorInit.hypercube();
557  for (auto& x: hc.xvectors)
558  if (x.name==oldName)
559  {
560  x.name=newName;
561  }
562  v.second->tensorInit.hypercube(hc);
563  }
564  }
565 
566 
567  std::set<string> Minsky::matchingTableColumns(const GodleyIcon& godley, GodleyAssetClass::AssetClass ac)
568  {
569  std::set<string> r;
571  // matching liability with assets and vice-versa
572  switch (ac)
573  {
574  case GodleyAssetClass::liability:
575  case GodleyAssetClass::equity:
576  target_ac=GodleyAssetClass::asset;
577  break;
578  case GodleyAssetClass::asset:
579  target_ac=GodleyAssetClass::liability;
580  break;
581  default:
582  return r; // other types do not match anything
583  }
584 
585  std::set<string> duplicatedColumns;
586  model->recursiveDo
587  (&Group::items,
588  [&](Items& m, Items::iterator it)
589  {
590  if (auto gi=dynamic_cast<GodleyIcon*>(it->get()))
591  {
592  for (size_t i=1; i<gi->table.cols(); ++i)
593  if (!gi->table.cell(0,i).empty())
594  {
595  auto v=gi->table.cell(0,i);
596  auto scope=minsky::scope(gi->group.lock(),v);
597  if (scope->higher(*godley.group.lock()))
598  v=':'+v; //NOLINT
599  else if (scope!=godley.group.lock())
600  continue; // variable is inaccessible
601  if (r.contains(v) || gi->table._assetClass(i)!=target_ac)
602  {
603  r.erase(v); // column already duplicated, or in current, nothing to match
604  duplicatedColumns.insert(v);
605  }
606  else if (!duplicatedColumns.contains(v) && gi->table._assetClass(i)==target_ac &&
607  // insert unmatched asset columns from this table only for equity (feature #174)
608  // otherwise matches are between separate tables
609  ((ac!=GodleyAssetClass::equity && gi!=&godley) || (ac==GodleyAssetClass::equity && gi==&godley) ))
610  r.insert(v);
611  }
612  }
613  return false;
614  });
615  return r;
616  }
617 
618  void Minsky::importDuplicateColumn(GodleyTable& srcTable, int srcCol)
619  {
620  if (srcCol<0 || size_t(srcCol)>=srcTable.cols()) return;
621  // find any duplicate column, and use it to do balanceDuplicateColumns
622  const string& colName=trimWS(srcTable.cell(0,srcCol));
623  if (colName.empty()) return; //ignore blank columns
624 
625  try
626  {
627  model->recursiveDo
628  (&Group::items,
629  [&](Items& m, Items::iterator i)
630  {
631  if (auto gi=dynamic_cast<GodleyIcon*>(i->get()))
632  for (size_t col=1; col<gi->table.cols(); col++)
633  if ((&gi->table!=&srcTable || int(col)!=srcCol) && trimWS(gi->table.cell(0,col))==colName) // we have a match
634  balanceDuplicateColumns(*gi, col);
635  return false;
636  });
637  }
638  catch (...) // in the event of business rules being violated, delete column name
639  {
640  srcTable.cell(0,srcCol).clear();
641  throw;
642  }
643  }
644 
645  void Minsky::balanceColumns(const GodleyIcon& srcGodley, int srcCol, GodleyIcon& destGodley, int destCol) const
646  {
647  auto& srcTable=srcGodley.table;
648  auto& destTable=destGodley.table;
649  // reverse lookup tables for mapping flow variable to destination row numbers via row labels
650  map<string,string> srcRowLabels;
651  map<string, int> destRowLabels;
652  set<string> uniqueSrcRowLabels; // check for uniqueness of source row labels
653  for (size_t row=1; row!=srcTable.rows(); ++row)
654  if (!srcTable.initialConditionRow(row) && !srcTable.cell(row,0).empty() &&
655  !srcTable.cell(row,srcCol).empty())
656  {
657  if (!uniqueSrcRowLabels.insert(srcTable.cell(row,0)).second)
658  throw runtime_error("Duplicate source row label: "+srcTable.cell(row,0));
659  const FlowCoef fc(srcTable.cell(row,srcCol));
660  if (!fc.name.empty())
661  srcRowLabels[srcGodley.valueId(fc.name)]=
662  trimWS(srcTable.cell(row,0));
663  }
664  else if (srcTable.initialConditionRow(row))
665  // copy directly into destination initial condition,
666  for (size_t r=1; r<destTable.rows(); ++r)
667  if (destTable.initialConditionRow(r))
668  destTable.cell(r,destCol)=srcTable.cell(row,srcCol);
669  for (size_t row=1; row!=destTable.rows(); ++row)
670  if (!destTable.initialConditionRow(row) && !destTable.cell(row,0).empty())
671  destRowLabels[trimWS(destTable.cell(row,0))]=row;
672 
673 
674  // compute column signature for both src and destination columns
675  map<string,double> srcFlows=srcGodley.flowSignature(srcCol),
676  destFlows=destGodley.flowSignature(destCol);
677  // items to add
678  for (map<string,double>::iterator i=srcFlows.begin(); i!=srcFlows.end(); ++i)
679  if (i->second != destFlows[i->first])
680  {
681  const int scope=-1;
682  if (i->first.find(':')!=string::npos)
683  minsky::scope(i->first);
684  FlowCoef df;
685  if (scope==-1 || !variableValues.count(i->first))
686  df.name=uqName(i->first);
687  else
688  df.name=variableValues[i->first]->name;
689  df.coef=i->second-destFlows[i->first];
690  if (df.coef==0) continue;
691  const string flowEntry=df.str();
692  const string rowLabel=srcRowLabels[srcGodley.valueId(i->first)];
693  const map<string,int>::iterator dr=destRowLabels.find(rowLabel);
694  if (dr!=destRowLabels.end())
695  if (FlowCoef(destTable.cell(dr->second, destCol)).coef==0)
696  destTable.cell(dr->second, destCol) = flowEntry;
697  else
698  // add a new blank labelled flow line
699  {
700  destTable.resize(destTable.rows()+1,destTable.cols());
701  destTable.cell(destTable.rows()-1, destCol) = flowEntry;
702  }
703  else
704  // labels don't match, so add a new labelled line
705  {
706  destTable.resize(destTable.rows()+1,destTable.cols());
707  destTable.cell(destTable.rows()-1, 0) = rowLabel;
708  destRowLabels[rowLabel] = destTable.rows()-1;
709  destTable.cell(destTable.rows()-1, destCol) = flowEntry;
710  }
711  }
712  // items to delete
713  set<size_t> rowsToDelete;
714  for (map<string,double>::iterator i=destFlows.begin(); i!=destFlows.end(); ++i)
715  if (i->second!=0 && srcFlows[i->first]==0)
716  for (size_t row=1; row<destTable.rows(); ++row)
717  {
718  FlowCoef fc(destTable.cell(row, destCol));
719  if (!fc.name.empty())
720  fc.name=destGodley.valueId(fc.name);
721  if (fc.name==destGodley.valueId(i->first))
722  {
723  destTable.cell(row, destCol).clear();
724  // if this leaves an empty row, delete entire row
725  for (size_t c=0; c<destTable.cols(); ++c)
726  if (!destTable.cell(row, c).empty())
727  goto rowNotEmpty;
728  rowsToDelete.insert(row);
729  rowNotEmpty:;
730  }
731  }
732  // amalgamate unlabelled rows with singular value
733  map<string,double> unlabelledSigs;
734  for (size_t row=1; row<destTable.rows(); ++row)
735  {
736  if (!destTable.singularRow(row, destCol)) continue;
737  const FlowCoef fc(destTable.cell(row, destCol));
738  unlabelledSigs[fc.name]+=fc.coef;
739  rowsToDelete.insert(row);
740  }
741  // append amalgamated rows
742  for (auto& i: unlabelledSigs)
743  if (i.second!=0)
744  {
745  destTable.insertRow(destTable.rows());
746  destTable.cell(destTable.rows()-1,destCol)=FlowCoef(i.second,i.first).str();
747  }
748 
749  for (auto row=rowsToDelete.rbegin(); row!=rowsToDelete.rend(); ++row)
750  destTable.deleteRow(*row);
751  }
752 
753  void Minsky::balanceDuplicateColumns(const GodleyIcon& srcGodley, int srcCol)
754  {
755  const GodleyTable& srcTable=srcGodley.table;
756  if (srcCol<0 || size_t(srcCol)>=srcTable.cols()) return;
757  // find if there is a matching column
758  const string& colName=srcGodley.valueId(trimWS(srcTable.cell(0,srcCol)));
759  if (colName.empty() || colName==":_") return; //ignore blank columns
760 
761  bool matchFound=false;
762  model->recursiveDo
763  (&Group::items,
764  [&](Items& m, Items::iterator i)
765  {
766  if (auto gi=dynamic_cast<GodleyIcon*>(i->get()))
767  {
768  if (&gi->table==&srcTable) // if source table, then check for duplicated asset/equity columns
769  {
770  for (size_t col=1; col<gi->table.cols(); col++)
771  if (col==size_t(srcCol)) continue; // skip over source column
772  else if (srcGodley.valueId(trimWS(srcTable.cell(0,col)))==colName)
773  {
774  switch (srcGodley.table._assetClass(srcCol))
775  {
776  case GodleyAssetClass::asset:
777  if (srcTable._assetClass(col)!=GodleyAssetClass::equity)
778  throw error("asset column %s matches a non-liability column",colName.c_str());
779  break;
780  case GodleyAssetClass::equity:
781  if (srcTable._assetClass(col)!=GodleyAssetClass::asset)
782  throw error("equity column %s matches a non-asset column",colName.c_str());
783  break;
784  default:
785  throw error("invalid asset class for duplicate column %s",colName.c_str());
786  }
787  balanceColumns(srcGodley, srcCol, *gi, col);
788  }
789  }
790  else // match asset/liability columns on different Godley tables
791  for (size_t col=1; col<gi->table.cols(); col++)
792  if (gi->valueId(trimWS(gi->table.cell(0,col)))==colName) // we have a match
793  {
794  // checks asset class rules
795  switch (srcGodley.table._assetClass(srcCol))
796  {
797  case GodleyAssetClass::asset:
798  if (gi->table._assetClass(col)!=GodleyAssetClass::liability)
799  throw error("asset column %s matches a non-liability column",colName.c_str());
800  break;
801  case GodleyAssetClass::liability:
802  if (gi->table._assetClass(col)!=GodleyAssetClass::asset)
803  throw error("liability column %s matches a non-asset column",colName.c_str());
804  break;
805  default:
806  throw error("invalid asset class for duplicate column %s",colName.c_str());
807  }
808 
809  // checks that nom more than two duplicated columns exist
810  if (matchFound)
811  throw error("more than one duplicated column detected for %s",colName.c_str());
812  matchFound=true;
813  balanceColumns(srcGodley, srcCol, *gi, col);
814  }
815  }
816  return false;
817  }); // TODO - this lambda is FAR too long!
818  }
819 
820  vector<string> Minsky::allGodleyFlowVars() const
821  {
822  set<string> r;
823  model->recursiveDo(&GroupItems::items, [&](const Items&, Items::const_iterator i) {
824  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
825  {
826  auto flowVars=g->table.getVariables();
827  r.insert(flowVars.begin(),flowVars.end());
828  }
829  return false;
830  });
831  return {r.begin(),r.end()};
832  }
833 
834  namespace
835  {
836  struct GodleyIt: public vector<GodleyIcon*>::iterator
837  {
838  typedef vector<GodleyIcon*>::iterator Super;
839  GodleyIt(const Super& x): Super(x) {}
842  const std::vector<std::vector<std::string> >& data() const {
843  return Super::operator*()->table.getData();
844  }
846  {return Super::operator*()->table._assetClass(col);}
847  bool signConventionReversed(int col) const
848  {return Super::operator*()->table.signConventionReversed(col);}
849  bool initialConditionRow(int row) const
850  {return Super::operator*()->table.initialConditionRow(row);}
851  string valueId(const std::string& x) const {
852  return valueIdFromScope(Super::operator*()->group.lock(), canonicalName(x));
853  }
854  };
855  }
856 
857  void Minsky::initGodleys()
858  {
859  auto toGodleyIcon=[](const ItemPtr& i) {return dynamic_cast<GodleyIcon*>(i.get());};
860  auto godleyItems=model->findAll<GodleyIcon*>
861  (toGodleyIcon, &GroupItems::items, toGodleyIcon);
862  evalGodley.initialiseGodleys(GodleyIt(godleyItems.begin()),
863  GodleyIt(godleyItems.end()), variableValues);
864  }
865 
867  try
868  {
869  // do not reset while simulation is running
870  if (RKThreadRunning)
871  {
872  flags |= reset_needed;
873  if (RKThreadRunning) return;
874  }
875 
876  auto start=chrono::high_resolution_clock::now();
877  auto updateResetDuration=onStackExit([&]{resetDuration=chrono::duration_cast<chrono::milliseconds>(chrono::high_resolution_clock::now()-start);});
878  canvas.itemIndicator.reset();
879  const BusyCursor busy(*this);
880  EvalOpBase::t=t=t0;
881  lastT=t0;
882  const ProgressUpdater pu(progressState,"Resetting",5);
883  constructEquations();
884  constructEquations();
885  ++progressState;
886  // if no stock variables in system, add a dummy stock variable to
887  // make the simulation proceed
888  if (stockVars.empty()) stockVars.resize(1,0);
889 
890  initGodleys();
891  ++progressState;
892 
893  if (!stockVars.empty())
894  rkreset();
895 
896  // update flow variable
897  evalEquations();
898  ++progressState;
899 
900  // populate ravel hypercubes first, before reattaching plots.
901  model->recursiveDo
902  (&Group::items,
903  [&](Items& m, Items::iterator i)
904  {
905  if (auto r=dynamic_cast<Ravel*>(i->get()))
906  {
907  if (r->ports(1).lock()->numWires()>0)
908  if (auto vv=r->ports(1).lock()->getVariableValue())
909  r->populateHypercube(vv->hypercube());
910  }
911  else if (auto v=(*i)->variableCast())
912  v->resetMiniPlot();
913  return false;
914  });
915 
916  // attach the plots
917  model->recursiveDo
918  (&Group::items,
919  [&](Items& m, Items::iterator it)
920  {
921  if (auto p=(*it)->plotWidgetCast())
922  {
923  p->disconnectAllVars();// clear any old associations
924  p->clearPenAttributes();
925  p->autoScale();
926  for (size_t i=0; i<p->portsSize(); ++i)
927  {
928  auto pp=p->ports(i).lock();
929  for (auto wire: pp->wires())
930  if (auto fromPort=wire->from())
931  if (auto vv=wire->from()->getVariableValue())
932  if (vv->idx()>=0)
933  p->connectVar(vv, i);
934  }
935  p->clear();
936  if (running)
937  p->updateIcon(t);
938  else
939  p->addConstantCurves();
940  p->requestRedraw();
941  }
942  return false;
943  });
944  ++progressState;
945 
946  // if (running)
947  flags &= ~reset_needed; // clear reset flag
948  resetAt=std::chrono::time_point<std::chrono::system_clock>::max();
949  // else
950  // flags |= reset_needed; // enforce another reset at simulation start
951  running=false;
952 
953  requestRedraw();
954 
955  // update maxValues
956  PhillipsFlow::maxFlow.clear();
957  PhillipsStock::maxStock.clear();
958  for (auto& v: variableValues)
959  {
960  if (v.second->type()==VariableType::stock)
961  {
962  PhillipsStock::maxStock[v.second->units]+=v.second->value();
963  }
964  }
965  for (auto& i: PhillipsStock::maxStock) i.second=abs(i.second);
966  }
967  catch (...)
968  {
969  // in the event of an exception, clear reset flag
970  flags &= ~reset_needed; // clear reset flag
971  resetAt=std::chrono::time_point<std::chrono::system_clock>::max();
972  resetDuration=chrono::milliseconds::max();
973  throw;
974  }
975 
976  vector<double> Minsky::step()
977  {
978  lastT=t;
979  rkstep();
980 
981  logVariables();
982 
983  model->recursiveDo
984  (&Group::items,
985  [&](Items&, Items::iterator i)
986  {(*i)->updateIcon(t); return false;});
987 
988  // throttle redraws
989  const time_duration maxWait=milliseconds(maxWaitMS);
990  if ((microsec_clock::local_time()-(ptime&)lastRedraw) > maxWait)
991  {
992  requestRedraw();
993  lastRedraw=microsec_clock::local_time();
994  }
995 
996  return {t, deltaT()};
997  }
998 
999  string Minsky::diagnoseNonFinite() const
1000  {
1001  // firstly check if any variables are not finite
1002  for (VariableValues::const_iterator v=variableValues.begin();
1003  v!=variableValues.end(); ++v)
1004  if (!isfinite(v->second->value()))
1005  return v->first;
1006 
1007  // now check operator equations
1008  for (EvalOpVector::const_iterator e=equations.begin(); e!=equations.end(); ++e)
1009  if (!isfinite(flowVars[(*e)->out]))
1010  return OperationType::typeName((*e)->type());
1011  return "";
1012  }
1013 
1014  void Minsky::save(const std::string& filename)
1015  {
1016  // back up to temporary file name
1017  rename(filename.c_str(), (filename+"~").c_str());
1018 
1019  const schema3::Minsky m(*this);
1020  Saver saver(filename);
1021  saver.packer.prettyPrint=true;
1022  try
1023  {
1024  saver.save(m);
1025  }
1026  catch (...) {
1027  // rename backup in place
1028  rename((filename+"~").c_str(), filename.c_str());
1029  // if exception is due to file error, provide a more useful message
1030  if (!saver.os)
1031  throw runtime_error("cannot save to "+filename);
1032  throw;
1033  }
1034  pushHistory(); // ensure history is up to date to prevent trivial setting of dirty flag
1035  flags &= ~is_edited;
1036  fileVersion=minskyVersion;
1037  if (autoSaver)
1038  boost::filesystem::remove(autoSaver->fileName);
1039  // rotate saved versions
1040  for (int i=numBackups; i>1; --i)
1041  rename((filename+";"+to_string(i-1)).c_str(), (filename+";"+to_string(i)).c_str());
1042  if (numBackups>0)
1043  rename((filename+"~").c_str(), (filename+";1").c_str());
1044  else
1045  ::remove((filename+"~").c_str());
1046  }
1047 
1048  void Minsky::load(const std::string& filename)
1049  {
1050  running=false;
1051  clearAllMaps(true);
1052 
1053  ifstream inf(filename);
1054  if (!inf)
1055  throw runtime_error("failed to open "+filename);
1057 
1058  {
1059  const BusyCursor busy(*this);
1060  xml_unpack_t saveFile(inf);
1061  const schema3::Minsky currentSchema(saveFile);
1062  currentSchema.populateMinsky(*this);
1063  if (currentSchema.schemaVersion<currentSchema.version)
1064  message("You are converting the model from an older version of Minsky. "
1065  "Once you save this file, you may not be able to open this file"
1066  " in older versions of Minsky.");
1067  }
1068 
1069  const LocalMinsky lm(*this); // populateMinsky resets the local minsky pointer, so restore it here
1070  flags=fullEqnDisplay_needed;
1071 
1072  // try balancing all Godley tables
1073  try
1074  {
1075  model->recursiveDo(&Group::items,
1076  [this](Items&,Items::iterator i) {
1077  try
1078  {
1079  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
1080  {
1081  for (unsigned j=1; j<g->table.cols(); ++j)
1082  balanceDuplicateColumns(*g,j);
1083  }
1084  (*i)->adjustBookmark();
1085  }
1086  catch (...) {}
1087  return false;
1088  });
1089 
1090  // try resetting the system, but ignore any errors
1091  reset();
1092  populateMissingDimensions();
1093  }
1094  catch (...) {flags|=reset_needed;}
1095  canvas.requestRedraw();
1096  canvas.moveTo(0,0); // force placement of ports
1097  // sometimes we need to recalculate the bounding boxes
1098  model->recursiveDo(&Group::items,
1099  [](Items&,Items::iterator i) {
1100  (*i)->updateBoundingBox();
1101  return false;
1102  });
1103  pushHistory();
1104  }
1105 
1106  void Minsky::exportSchema(const string& filename, int schemaLevel)
1107  {
1108  xsd_generate_t x;
1109  switch (schemaLevel)
1110  {
1111  case 0:
1112  xsd_generate(x,"Minsky",schema0::Minsky());
1113  break;
1114  case 1:
1115  xsd_generate(x,"Minsky",schema1::Minsky());
1116  break;
1117  case 2:
1118  xsd_generate(x,"Minsky",schema2::Minsky());
1119  break;
1120  case 3:
1121  xsd_generate(x,"Minsky",schema3::Minsky());
1122  break;
1123  }
1124  ofstream f(filename);
1125  x.output(f,schemaURL);
1126  }
1127 
1128  namespace
1129  {
1130  struct Network: public multimap<const Port*,const Port*>
1131  {
1132  set<const Port*> portsVisited;
1133  vector<const Port*> stack;
1134  void emplace(Port* x, Port* y)
1135  {multimap<const Port*,const Port*>::emplace(x,y);}
1136  // depth-first network walk, return true if cycle detected
1137  bool followWire(const Port* p)
1138  {
1139  if (!portsVisited.insert(p).second)
1140  { //traverse finished, check for cycle along branch
1141  if (std::find(stack.begin(), stack.end(), p) != stack.end())
1142  {
1143  cminsky().displayErrorItem(p->item());
1144  return true;
1145  }
1146  return false;
1147  }
1148  stack.push_back(p);
1149  const pair<iterator,iterator> range=equal_range(p);
1150  for (iterator i=range.first; i!=range.second; ++i)
1151  if (followWire(i->second))
1152  return true;
1153  stack.pop_back();
1154  return false;
1155  }
1156  };
1157  }
1158 
1159  bool Minsky::cycleCheck() const
1160  {
1161  // construct the network schematic
1162  Network net;
1163  for (auto& w: model->findWires([](const WirePtr&){return true;}))
1164  net.emplace(w->from().get(), w->to().get());
1165  for (auto& i: model->findItems([](const ItemPtr&){return true;}))
1166  if (!dynamic_cast<IntOp*>(i.get()) && !dynamic_cast<GodleyIcon*>(i.get()))
1167  for (unsigned j=1; j<i->portsSize(); ++j)
1168  net.emplace(i->ports(j).lock().get(), i->ports(0).lock().get());
1169 
1170  for (auto& i: net)
1171  if (!i.first->input() && !net.portsVisited.contains(i.first))
1172  if (net.followWire(i.first))
1173  return true;
1174  return false;
1175  }
1176 
1177  bool Minsky::checkEquationOrder() const
1178  {
1179  ecolab::array<bool> fvInit(flowVars.size(), false);
1180  // firstly, find all flowVars that are constants
1181  for (auto& v: variableValues)
1182  if (!inputWired(v.first) && v.second->idx()>=0)
1183  fvInit[v.second->idx()]=true;
1184 
1185  for (auto& e: equations)
1186  if (auto eo=dynamic_cast<const ScalarEvalOp*>(e.get()))
1187  {
1188  if (eo->out < 0|| (eo->numArgs()>0 && eo->in1.empty()) ||
1189  (eo->numArgs() > 1 && eo->in2.empty()))
1190  {
1191  return false;
1192  }
1193  switch (eo->numArgs())
1194  {
1195  case 0:
1196  fvInit[eo->out]=true;
1197  break;
1198  case 1:
1199  fvInit[eo->out]=!eo->flow1 || fvInit[eo->in1[0]];
1200  break;
1201  case 2:
1202  // we need to check if an associated binary operator has
1203  // an unwired input, and if so, treat its input as
1204  // initialised, since it has already been initialised in
1205  // getInputFromVar()
1206  if (auto op=eo->state)
1207  switch (op->type())
1208  {
1209  case OperationType::add: case OperationType::subtract:
1210  case OperationType::multiply: case OperationType::divide:
1211  fvInit[eo->in1[0]] |= op->ports(1).lock()->wires().empty();
1212  fvInit[eo->in2[0][0].idx] |= op->ports(3).lock()->wires().empty();
1213  break;
1214  default: break;
1215  }
1216 
1217  fvInit[eo->out]=
1218  (!eo->flow1 || fvInit[eo->in1[0]]) && (!eo->flow2 || fvInit[eo->in2[0][0].idx]);
1219  break;
1220  default: break;
1221  }
1222  // if (!fvInit[eo.out])
1223  // cerr << "Operation "<<opIdOfEvalOp(eo)<<" out of order"<<endl;
1224  }
1225 
1226  return all(fvInit);
1227  }
1228 
1229 
1230  void Minsky::displayErrorItem(const Item& op) const
1231  {
1232  // this method is logically const, but because of the way
1233  // canvas rendering is done, canvas state needs updating
1234  auto& canvas=const_cast<Canvas&>(this->canvas);
1235  if (op.visible())
1236  canvas.itemIndicator=canvas.model->findItem(op);
1237  else if (auto v=op.variableCast())
1238  if (auto c=v->controller.lock())
1239  displayErrorItem(*c);
1240 
1241  if (!canvas.itemIndicator)
1242  if (auto g=op.group.lock())
1243  {
1244  while (g && !g->visible()) g=g->group.lock();
1245  if (g && g->visible())
1246  canvas.itemIndicator=g;
1247  }
1248 
1249  if (canvas.itemIndicator)
1250  {
1251  auto physX=canvas.itemIndicator->x();
1252  auto physY=canvas.itemIndicator->y();
1253  if (physX<100 || physX>canvas.frameArgs().childWidth-100 ||
1254  physY<100 || physY>canvas.frameArgs().childHeight-100)
1255  {
1256  canvas.model->moveTo(0.5*canvas.frameArgs().childWidth-physX+canvas.model->x(),
1257  0.5*canvas.frameArgs().childHeight-physY+canvas.model->y());
1258  minsky().resetScroll();
1259  }
1260  }
1261  //requestRedraw calls back into TCL, so don't call it from the simulation thread. See ticket #973
1262  if (!RKThreadRunning) canvas.requestRedraw();
1263  }
1264 
1265  bool Minsky::pushHistory()
1266  {
1267  // do not pushHistory after undo or redo
1268  if (undone)
1269  return undone=false;
1270 
1271  // go via a schema object, as serialising minsky::Minsky has
1272  // problems due to port management
1273  schema3::Minsky m(*this, false /* don't pack tensor data */);
1274  pack_t buf;
1275  buf<<m;
1276  if (history.empty())
1277  {
1278  history.emplace_back();
1279  buf.swap(history.back());
1280  historyPtr=history.size();
1281  return true;
1282  }
1283  while (history.size()>maxHistory)
1284  history.pop_front();
1285  if (history.empty() || history.back().size()!=buf.size() || memcmp(buf.data(), history.back().data(), buf.size())!=0)
1286  {
1287  // check XML versions differ (slower)
1288  ostringstream prev, curr;
1289  xml_pack_t prevXbuf(prev), currXbuf(curr);
1290  xml_pack(currXbuf,"Minsky",m);
1291  schema3::Minsky previousMinsky;
1292  history.back().reseto()>>previousMinsky;
1293  xml_pack(prevXbuf,"Minsky",previousMinsky);
1294 
1295  if (curr.str()!=prev.str())
1296  {
1297  // This bit of code outputs an XML representation that can be
1298  // used for debugging issues related to unnecessary
1299  // history pushes.
1300  //buf.reseto()>>m;
1301  //xml_pack_t tb(cout);
1302  //tb.prettyPrint=true;
1303  //xml_pack(tb,"Minsky",m);
1304  //cout<<"------"<<endl;
1305  history.emplace_back();
1306  buf.swap(history.back());
1307  historyPtr=history.size();
1308  if (autoSaver && doPushHistory)
1309  try
1310  {
1311  //autoSaver->packer.prettyPrint=true;
1312  autoSaver->save(m);
1313  }
1314  catch (...)
1315  {
1316  autoSaver.reset();
1317  throw;
1318  }
1319  return true;
1320  }
1321  }
1322  historyPtr=history.size();
1323  return false;
1324  }
1325 
1326  bool Minsky::commandHook(const std::string& command, unsigned nargs)
1327  {
1328  static const set<string> constableCommands={ // commands that should not trigger the edit flag
1329  "minsky.availableOperations",
1330  "minsky.canvas.displayDelayedTooltip",
1331  "minsky.canvas.findVariableDefinition",
1332  "minsky.canvas.focusFollowsMouse",
1333  "minsky.canvas.moveTo",
1334  "minsky.canvas.model.moveTo",
1335  "minsky.canvas.model.zoom",
1336  /* we record mouse movements, but filter from history */
1337  "minsky.canvas.mouseDown",
1338  "minsky.canvas.mouseMove",
1339  "minsky.canvas.position",
1340  "minsky.canvas.recentre",
1341  "minsky.canvas.select",
1342  "minsky.canvas.selectVar",
1343  "minsky.canvas.scaleFactor",
1344  "minsky.canvas.zoom",
1345  "minsky.canvas.zoomToFit",
1346  "minsky.histogramResource.setResource",
1347  "minsky.model.moveTo",
1348  "minsky.clearAllMaps",
1349  "minsky.doPushHistory",
1350  "minsky.fontScale",
1351  "minsky.load",
1352  "minsky.model.zoom",
1353  "minsky.multipleEquities",
1354  "minsky.openGroupInCanvas",
1355  "minsky.openModelInCanvas",
1356  "minsky.popFlags",
1357  "minsky.pushFlags",
1358  "minsky.pushHistory",
1359  "minsky.redrawAllGodleyTables",
1360  "minsky.reverse",
1361  "minsky.running",
1362  "minsky.save",
1363  "minsky.select",
1364  "minsky.selectVar",
1365  "minsky.setGodleyDisplayValue",
1366  "minsky.setGodleyIconResource",
1367  "minsky.setGroupIconResource",
1368  "minsky.setLockIconResource",
1369  "minsky.setRavelIconResource",
1370  "minsky.setAutoSaveFile",
1371  "minsky.step",
1372  "minsky.undo"
1373  };
1374  if (doPushHistory && !constableCommands.contains(command) &&
1375  command.find("minsky.phillipsDiagram")==string::npos &&
1376  command.find("minsky.equationDisplay")==string::npos &&
1377  command.find("minsky.publicationTabs")==string::npos &&
1378  command.find(".renderFrame")==string::npos &&
1379  command.find(".requestRedraw")==string::npos &&
1380  command.find(".backgroundColour")==string::npos &&
1381  command.find(".get")==string::npos &&
1382  command.find(".@elem")==string::npos &&
1383  command.find(".mouseFocus")==string::npos
1384  )
1385  {
1386  auto t=getCommandData(command);
1387  if (t==generic || (t==is_setterGetter && nargs>0))
1388  {
1389  const bool modelChanged=pushHistory();
1390  if (modelChanged && command.find(".keyPress")==string::npos)
1391  {
1392  markEdited();
1393  }
1394  return modelChanged;
1395  }
1396  }
1397  return command=="minsky.canvas.requestRedraw" || command=="minsky.canvas.mouseDown" || command=="minsky.canvas.mouseMove" || command.find(".get")!=string::npos;
1398  }
1399 
1400 
1401  long Minsky::undo(int changes)
1402  {
1403  // save current state for later restoration if needed
1404  if (historyPtr==history.size())
1405  pushHistory();
1406  historyPtr-=changes;
1407  if (historyPtr > 0 && historyPtr <= history.size())
1408  {
1409  schema3::Minsky m;
1410  history[historyPtr-1].reseto()>>m;
1411  // stash tensorInit data for later restoration
1412  auto stashedValues=std::move(variableValues);
1413  // preserve bookmarks. For now, we can only preserve model and canvas.model bookmarks
1414  // count the total number of bookmarks
1415  unsigned numBookmarks=0;
1416  model->recursiveDo(&GroupItems::groups, [&](const Groups&,const Groups::const_iterator i) {
1417  numBookmarks+=(*i)->bookmarks.size();
1418  return false;
1419  });
1420  auto stashedGlobalBookmarks=model->bookmarks;
1421  auto stashedCanvasBookmarks=canvas.model->bookmarks;
1422  clearAllMaps(false);
1423  model->clear();
1424  m.populateGroup(*model);
1425  model->setZoom(m.zoomFactor);
1426  m.phillipsDiagram.populatePhillipsDiagram(phillipsDiagram);
1427  m.populatePublicationTabs(publicationTabs);
1428  requestRedraw();
1429 
1430  // restore tensorInit data
1431  for (auto& v: variableValues)
1432  {
1433  auto stashedValue=stashedValues.find(v.first);
1434  if (stashedValue!=stashedValues.end())
1435  v.second->tensorInit=std::move(stashedValue->second->tensorInit);
1436  }
1437  // restore bookmarks
1438  model->bookmarks=std::move(stashedGlobalBookmarks);
1439  canvas.model->bookmarks=std::move(stashedCanvasBookmarks);
1440  unsigned numBookmarksAfterwards=0;
1441  model->recursiveDo(&GroupItems::groups, [&](const Groups&,const Groups::const_iterator i) {
1442  numBookmarksAfterwards+=(*i)->bookmarks.size();
1443  return false;
1444  });
1445  if (numBookmarksAfterwards!=numBookmarks)
1446  message("This undo/redo operation potentially deletes some bookmarks");
1447  try {requestReset();}
1448  catch (...) {}
1449 
1450  }
1451  else
1452  historyPtr+=changes; // revert
1453  undone=true; //ensure next pushHistory is ignored
1454  return historyPtr;
1455  }
1456 
1457  void Minsky::convertVarType(const string& name, VariableType::Type type)
1458  {
1459  assert(isValueId(name));
1460  const VariableValues::iterator i=variableValues.find(name);
1461  if (i==variableValues.end())
1462  throw error("variable %s doesn't exist",name.c_str());
1463  if (i->second->type()==type) return; // nothing to do!
1464 
1465  string newName=name; // useful for checking flows and stocks with same name and renaming them as the case may be. for ticket 1272
1466  model->recursiveDo
1467  (&GroupItems::items,
1468  [&](const Items&,Items::const_iterator i)
1469  {
1470  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
1471  {
1472  if (type!=VariableType::flow)
1473  for (auto& v: g->flowVars())
1474  if (v->valueId()==name)
1475  {
1476  newName=v->name()+"^{Flow}";
1477  const VariableValues::iterator iv=variableValues.find(newName);
1478  if (iv==variableValues.end()) {g->table.renameFlows(v->name(),newName); v->retype(VariableType::flow);}
1479  else throw error("flow variables in Godley tables cannot be converted to a different type");
1480  }
1481  if (type!=VariableType::stock)
1482  for (auto& v: g->stockVars())
1483  if (v->valueId()==name)
1484  {
1485  newName=v->name()+"^{Stock}";
1486  const VariableValues::iterator iv=variableValues.find(newName);
1487  if (iv==variableValues.end()) {g->table.renameStock(v->name(),newName); v->retype(VariableType::stock);}
1488  else throw error("stock variables in Godley tables cannot be converted to a different type");
1489  }
1490  }
1491  return false;
1492  });
1493 
1494  if (auto var=definingVar(name))
1495  // we want to be able to convert stock vars to flow vars when their input is wired. condition is only met when newName has not been changed above. for ticket 1272
1496  if (name==newName && var->type() != type && (!var->isStock() || var->controller.lock()))
1497  throw error("cannot convert a variable to a type other than its defined type");
1498 
1499  // filter out invalid targets
1500  switch (type)
1501  {
1502  case VariableType::undefined: case VariableType::numVarTypes:
1503  case VariableType::tempFlow:
1504  throw error("convertVarType not supported for type=%s",
1505  VariableType::typeName(type).c_str());
1506  default: break;
1507  }
1508 
1509  // convert all references
1510  model->recursiveDo(&Group::items,
1511  [&](Items&, Items::iterator i) {
1512  if (auto v=VariablePtr(*i))
1513  if (v->valueId()==name)
1514  {
1515  v.retype(type);
1516  if (*i==canvas.item)
1517  canvas.item=v;
1518  *i=v;
1519  }
1520  return false;
1521  });
1522  auto init=i->second->init();
1523  i->second=VariableValuePtr(type,i->second->name);
1524  i->second->init(init);
1525  }
1526 
1527  void Minsky::addIntegral()
1528  {
1529  if (auto v=canvas.item->variableCast())
1530  if (auto g=v->group.lock())
1531  {
1532  // nb throws if conversion cannot be performed
1533  convertVarType(v->valueId(),VariableType::integral);
1534  auto integ=new IntOp;
1535  g->addItem(integ);
1536  integ->moveTo(canvas.item->x(), canvas.item->y());
1537  // remove intVar from its group
1538  if (auto g=integ->intVar->group.lock())
1539  g->removeItem(*integ->intVar);
1540  integ->intVar=dynamic_pointer_cast<VariableBase>(canvas.item);
1541  integ->toggleCoupled();
1542 
1543  canvas.requestRedraw();
1544  }
1545  }
1546 
1547 
1548  void Minsky::renderAllPlotsAsSVG(const string& prefix) const
1549  {
1550  unsigned plotNum=0;
1551  model->recursiveDo(&Group::items,
1552  [&](Items&, Items::iterator i) {
1553  if (auto p=(*i)->plotWidgetCast())
1554  {
1555  if (!p->title.empty())
1556  p->renderToSVG(prefix+"-"+p->title+".svg");
1557  else
1558  p->renderToSVG(prefix+"-"+str(plotNum++)+".svg");
1559  }
1560  return false;
1561  });
1562  }
1563  void Minsky::exportAllPlotsAsCSV(const string& prefix) const
1564  {
1565  unsigned plotNum=0;
1566  model->recursiveDo(&Group::items,
1567  [&](Items&, Items::iterator i) {
1568  if (auto p=(*i)->plotWidgetCast())
1569  {
1570  if (!p->title.empty())
1571  p->exportAsCSV((prefix+"-"+p->title+".csv"));
1572  else
1573  p->exportAsCSV((prefix+"-"+str(plotNum++)+".csv"));
1574  }
1575  return false;
1576  });
1577  }
1578 
1579  void Minsky::setAllDEmode(bool mode) {
1580  model->recursiveDo(&GroupItems::items, [mode](Items&,Items::iterator i) {
1581  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
1582  g->table.setDEmode(mode);
1583  return false;
1584  });
1585  }
1586 
1587  void Minsky::setGodleyDisplayValue(bool displayValues, GodleyTable::DisplayStyle displayStyle)
1588  {
1589  this->displayValues=displayValues;
1590  this->displayStyle=displayStyle;
1591  canvas.requestRedraw();
1592  model->recursiveDo(&GroupItems::items, [](Items&,Items::iterator i) {
1593  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
1594  g->popup.requestRedraw();
1595  return false;
1596  });
1597  }
1598 
1599  void Minsky::importVensim(const string& filename)
1600  {
1601  ifstream f(filename);
1602  readMdl(*model,*this,f);
1603  canvas.requestRedraw();
1604  }
1605 
1606  vector<string> Minsky::availableOperations()
1607  {return enumVals<OperationType::Type>();}
1608  vector<string> Minsky::variableTypes()
1609  {return enumVals<VariableType::Type>();}
1610  vector<string> Minsky::assetClasses()
1611  {return enumVals<GodleyTable::AssetClass>();}
1612 
1613  Minsky::AvailableOperationsMapping Minsky::availableOperationsMapping() const
1614  {
1616  for (OperationType::Type op{}; op != OperationType::numOps; op=OperationType::Type(int(op)+1))
1617  {
1618  if (classifyOp(op)==OperationType::general) continue;
1619  if (op==OperationType::copy) continue;
1620  r[classdesc::to_string(classifyOp(op))].push_back(op);
1621  }
1622  return r;
1623  }
1624 
1625  void Minsky::autoLayout()
1626  {
1627  canvas.model->autoLayout();
1628  canvas.recentre();
1629  }
1630 
1631  void Minsky::randomLayout()
1632  {
1633  canvas.model->randomLayout();
1634  canvas.recentre();
1635  }
1636 
1637  void Minsky::listAllInstances()
1638  {
1639  if (canvas.item)
1640  if (auto v=canvas.item->variableCast())
1641  {
1642  variableInstanceList=std::make_shared<VariableInstanceList>(*canvas.model, v->valueId());
1643  return;
1644  }
1645  variableInstanceList.reset();
1646  }
1647 
1648  void Minsky::removeItems(Wire& wire)
1649  {
1650  if (wire.from()->wires().size()==1)
1651  { // only remove higher up the network if this item is the only item feeding from it
1652  auto& item=wire.from()->item();
1653  if (!item.variableCast())
1654  {
1655  for (size_t i=1; i<item.portsSize(); ++i)
1656  if (auto p=item.ports(i).lock())
1657  for (auto w: p->wires())
1658  removeItems(*w);
1659  model->removeItem(item);
1660  }
1661  else if (auto p=item.ports(1).lock())
1662  if (p->wires().empty())
1663  model->removeItem(item); // remove non-definition variables as well
1664  }
1665  model->removeWire(wire);
1666  }
1667 
1668  void Minsky::setDefinition(const std::string& valueId, const std::string& definition)
1669  {
1670  auto var=definingVar(valueId);
1671  if (!var) // find
1672  var=dynamic_pointer_cast<VariableBase>(model->findAny(&GroupItems::items, [&](const ItemPtr& it) {
1673  if (auto v=it->variableCast())
1674  return v->valueId()==valueId;
1675  return false;
1676  }));
1677  if (var)
1678  if (auto p=var->ports(1).lock())
1679  {
1680  auto group=var->group.lock();
1681  if (!group) group=model;
1682  UserFunction* udf=p->wires().empty()? nullptr: dynamic_cast<UserFunction*>(&p->wires().front()->from()->item());
1683  if (!udf)
1684  {
1685  // remove previous definition network
1686  for (auto w: p->wires())
1687  {
1688  assert(w);
1689  removeItems(*w);
1690  }
1691 
1692  udf=new UserFunction(var->name()+"()");
1693  group->addItem(udf); // ownership passed
1694  const bool notFlipped=!flipped(var->rotation());
1695  udf->moveTo(var->x()+(notFlipped? -1:1)*0.6*(var->width()+udf->width()), var->y());
1696  group->addWire(udf->ports(0), var->ports(1));
1697  }
1698  udf->expression=definition;
1699  }
1700  }
1701 
1702 
1703  void Minsky::redrawAllGodleyTables()
1704  {
1705  model->recursiveDo(&Group::items,
1706  [&](Items&,Items::iterator i) {
1707  if (auto g=dynamic_cast<GodleyIcon*>(i->get()))
1708  g->popup.requestRedraw();
1709  return false;
1710  });
1711  }
1712 
1713  size_t Minsky::physicalMem() const
1714  {
1715 #if defined(__linux__)
1716  struct sysinfo s;
1717  sysinfo(&s);
1718  return s.totalram;
1719 #elif defined(WIN32)
1720  MEMORYSTATUSEX s{sizeof(MEMORYSTATUSEX)};
1721  GlobalMemoryStatusEx(&s);
1722  return s.ullTotalPhys;
1723 #elif defined(__APPLE__)
1724  int mib[]={CTL_HW,HW_MEMSIZE};
1725  uint64_t physical_memory;
1726  size_t length = sizeof(uint64_t);
1727  if (sysctl(mib, sizeof(mib)/sizeof(int), &physical_memory, &length, NULL, 0))
1728  perror("physicalMem:");
1729  return physical_memory;
1730 #else
1731  // all else fails, return max value
1732  return ~0UL;
1733 #endif
1734  }
1735 
1736  static std::unique_ptr<char[]> _defaultFont;
1737 
1738  string Minsky::defaultFont()
1739  {return _defaultFont? _defaultFont.get(): "";}
1740 
1741  string Minsky::defaultFont(const std::string& x)
1742  {
1743  _defaultFont.reset(new char[x.length()+1]);
1744  strncpy(_defaultFont.get(),x.c_str(),x.length()+1);
1745  ecolab::Pango::defaultFamily=_defaultFont.get();
1746  return x;
1747  }
1748 
1749  double Minsky::fontScale()
1750  {return ecolab::Pango::scaleFactor;}
1751 
1752  double Minsky::fontScale(double s)
1753  {return ecolab::Pango::scaleFactor=s;}
1754 
1755  void Minsky::latex(const string& filename, bool wrapLaTeXLines)
1756  {
1757  if (cycleCheck()) throw error("cyclic network detected");
1758  ofstream f(filename);
1759 
1760  f<<"\\documentclass{article}\n";
1761  if (wrapLaTeXLines)
1762  {
1763  f<<"\\usepackage{breqn}\n\\begin{document}\n";
1765  }
1766  else
1767  {
1768  f<<"\\begin{document}\n";
1770  }
1771  f<<"\\end{document}\n";
1772  }
1773 
1774  int Minsky::numOpArgs(OperationType::Type o)
1775  {
1776  const OperationPtr op(o);
1777  return op->numPorts()-1;
1778  }
1779 
1780  void Minsky::setAutoSaveFile(const std::string& file) {
1781  if (file.empty())
1782  autoSaver.reset();
1783  else
1784  autoSaver.reset(new BackgroundSaver(file));
1785  }
1786 
1787  BusyCursor::BusyCursor(Minsky& m): minsky(m)
1788  {if (!minsky.busyCursorStack++) minsky.setBusyCursor();}
1789 
1791  {if (!--minsky.busyCursorStack) minsky.clearBusyCursor();}
1792 
1793 
1794  void Progress::displayProgress(bool inDestruct)
1795  {
1796  if (cancel && *cancel)
1797  {
1798  *cancel=false;
1799  if (!inDestruct) throw std::runtime_error("Cancelled");
1800  }
1801  minsky().progress(title, lround(progress));
1802  }
1803 
1804 }
1805 
1806 namespace classdesc
1807 {
1808  // specialise for VariableValues to give it an associative container flavour.
1809  // defining is_associative_container type attribute doesn't work
1810  template <> inline
1811  void RESTProcess(RESTProcess_t& t, const string& d, minsky::VariableValues& a)
1812  {t.add(d, new RESTProcessAssociativeContainer<minsky::VariableValues>(a));}
1813 }
1814 
1816 CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(classdesc::Signature);
1817 CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(classdesc::PolyRESTProcessBase);
std::size_t cols() const
Definition: godleyTable.h:115
function f
Definition: canvas.m:1
void populatePublicationTabs(std::vector< minsky::PubTab > &) const
Definition: schema3.cc:610
double zoomFactor
Definition: schema3.h:255
const Hypercube & hypercube() const override
void displayErrorItem(const Item &op) const
indicate operation item has error, if visible, otherwise contining group
Definition: minsky.cc:1230
variablePane
Definition: variablePane.tcl:4
std::ofstream os
Definition: saver.h:39
ostream & latexWrapped(ostream &) const
Definition: equations.cc:933
reset
Definition: minsky.tcl:1325
void populatePhillipsDiagram(minsky::PhillipsDiagram &) const
populate a Phillips Diagram from this
Definition: schema3.cc:850
void populateGroup(minsky::Group &g) const
populate a group object from this. This mutates the ids in a consistent way into the free id space of...
Definition: schema3.cc:636
double progress
Definition: progress.h:38
shared_ptr class for polymorphic operation objects. Note, you may assume that this pointer is always ...
minsky::Minsky minsky
Definition: pyminsky.cc:28
exception-safe increment/decrement of a counter in a block
Definition: variable.h:56
std::shared_ptr< std::atomic< bool > > cancel
set to true to cancel process in progreess displayProgress will throw if cancel is set...
Definition: progress.h:43
std::string name
Definition: flowCoef.h:30
PhillipsDiagram phillipsDiagram
Definition: schema3.h:259
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::Minsky)
size_t scope(const string &name)
extract scope from a qualified variable name
Definition: valueId.cc:83
void populateEvalOpVector(EvalOpVector &equations, std::vector< Integral > &integrals)
Definition: equations.cc:1011
void xsd_generate(xsd_generate_t &g, const string &d, const minsky::Optional< T > &a)
Definition: optional.h:77
std::shared_ptr< Item > ItemPtr
Definition: item.h:57
#define MINSKY_VERSION
Definition: minskyVersion.h:1
std::string uqName(const std::string &name)
extract unqualified portion of name
Definition: valueId.cc:135
std::shared_ptr< Wire > WirePtr
Definition: wire.h:102
string & cell(unsigned row, unsigned col)
Definition: godleyTable.h:144
void RESTProcess(RESTProcess_t &t, const string &d, minsky::VariableValues &a)
Definition: minsky.cc:1811
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
Definition: valueId.cc:75
undodelta
Definition: minsky.tcl:747
void populateMinsky(minsky::Minsky &) const
create a Minsky model from this
Definition: schema3.cc:448
a shared_ptr that default constructs a default target, and is always valid
std::string str() const
Definition: flowCoef.cc:81
bool flipped(double rotation)
returns if the angle (in degrees) is in the second or third quadrant
Definition: geometry.h:102
int schemaVersion
Definition: schema3.h:247
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
void displayProgress(bool inDestruct=false)
Definition: minsky.cc:1794
classdesc::StringKeyMap< std::vector< OperationType::Type > > AvailableOperationsMapping
Definition: minsky.h:510
std::string valueId(const std::string &x) const
returns valueid for variable reference in table
Definition: godleyIcon.h:142
void remove(std::vector< T > &x, const V &v)
remove an element from a vector. V must be comparable to a T
Definition: str.h:89
static std::unique_ptr< char[]> _defaultFont
Definition: minsky.cc:1736
void save(const schema3::Minsky &)
Definition: saver.cc:40
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
float width() const
Definition: item.h:242
string valueId(const std::string &x) const
Definition: minsky.cc:851
void readMdl(Group &group, Simulation &simParms, istream &mdlFile)
import a Vensim mdl file into group, also populating simParms from the control block ...
Definition: mdlReader.cc:250
std::vector< ItemPtr > Items
Definition: item.h:366
std::string trimWS(const std::string &s)
Definition: str.h:49
void updatePortVariableValue(EvalOpVector &equations)
Definition: equations.cc:1037
std::vector< GroupPtr > Groups
Definition: group.h:54
OnStackExit< F > onStackExit(F f)
generator function
Definition: str.h:85
std::string str(T x)
utility function to create a string representation of a numeric type
Definition: str.h:33
const Minsky & cminsky()
const version to help in const correctness
Definition: minsky.h:549
step
Definition: minsky.tcl:1289
std::map< string, double > flowSignature(unsigned col) const
flows, along with multipliers, appearing in col
Definition: godleyIcon.cc:258
string canonicalName(const string &name)
convert a raw name into a canonical name - this is not idempotent.
Definition: valueId.cc:63
const char * schemaURL
Definition: saver.cc:26
GodleyAssetClass::AssetClass assetClass(size_t col) const
Definition: minsky.cc:845
represents a numerical coefficient times a variable (a "flow")
Definition: flowCoef.h:27
bool isValueId(const string &name)
check that name is a valid valueId (useful for assertions)
Definition: valueId.cc:33
std::string title
Definition: progress.h:42
void xml_pack(xml_pack_t &t, const string &d, minsky::Optional< T > &a)
Definition: optional.h:84
Expr operator*(const NodePtr &x, const Expr &y)
Definition: expr.h:100
virtual void resetScroll()
reset main window scroll bars after model has been panned
Definition: minsky.h:453
vector< GodleyIcon * >::iterator Super
Definition: minsky.cc:838
const vector< AssetClass > & _assetClass() const
class of each column (used in DE compliant mode)
Definition: godleyTable.h:78
virtual void progress(const std::string &title, int)
set progress bar, out of 100, labelling the progress bar with title
Definition: minsky.h:447
string valueIdFromScope(const GroupPtr &scope, const std::string &name)
value Id from scope and canonical name name
Definition: valueId.cc:128
std::shared_ptr< Group > GroupPtr
Definition: port.h:32
cmd_data * getCommandData(const string &name)
Definition: minskyTCL.cc:62
cut
Definition: minsky.tcl:756
std::string CSVQuote(const std::string &x, char sep)
quotes a string if it contains a separator character, and double quotes quotes
Definition: str.h:162
void moveTo(float x, float y)
Definition: item.cc:256
RAII set the minsky object to a different one for the current scope.
Definition: minsky.h:551
std::string expression
Definition: userFunction.h:37
vector< string > enumVals()
list the possible string values of an enum (for TCL)
Definition: minsky.cc:81
UnitsExpressionWalker timeUnit
string latex(double)
convert double to a LaTeX string representing that value
Definition: node_latex.cc:28
void stripByteOrderingMarker(std::istream &s)
checks if the input stream has the UTF-8 byte ordering marker, and removes it if present ...
Definition: str.h:147
virtual std::weak_ptr< Port > ports(std::size_t i) const
callback to be run when item deleted from group
Definition: item.h:180
Definition: group.tcl:84
string to_string(CONST84 char *x)
Definition: minskyTCLObj.h:33
Minsky & minsky()
global minsky object
Definition: minskyTCL.cc:51
Item & item()
owner of this port
Definition: port.h:66
save
Definition: minsky.tcl:1469
const std::vector< std::vector< std::string > > & data() const
Definition: minsky.cc:842
classdesc::xml_pack_t packer
Definition: saver.h:40
ostream & latex(ostream &) const
render as a LaTeX eqnarray Use LaTeX brqn environment to wrap long lines
Definition: equations.cc:902
static const int version
Definition: schema3.h:246