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