27 #include <cairo_base.h> 33 #include <boost/thread.hpp> 37 #include "fontDisplay.rcd" 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" 51 #include <boost/filesystem.hpp> 60 #if defined(__linux__) 61 #include <sys/sysinfo.h> 65 #include <sys/types.h> 66 #include <sys/sysctl.h> 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);
89 bool Minsky::multipleEquities(
const bool& m) {
91 canvas.requestRedraw();
92 redrawAllGodleyTables();
93 return m_multipleEquities;
96 void Minsky::openLogFile(
const string& name)
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;
107 void Minsky::logVariables()
const 112 for (
auto& v: variableValues)
113 if (logVarList.contains(v.first))
114 *outputDataFile<<
" "<<v.second->value();
115 *outputDataFile<<endl;
121 if (edited() && autoSaver)
126 void Minsky::clearAllMaps(
bool doClearHistory)
129 canvas.openGroupInCanvas(model);
132 variableValues.clear();
133 variablePane.update();
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;
148 flags=reset_needed|fullEqnDisplay_needed;
149 fileVersion=minskyVersion;
150 if (doClearHistory) clearHistory();
159 for (
auto& i: canvas.selection.items)
161 if (
auto v=i->variableCast())
162 if (v->controller.lock())
164 model->deleteItem(*i);
166 for (
auto& i: canvas.selection.groups)
167 model->removeGroup(*i);
168 for (
auto& i: canvas.selection.wires)
169 model->removeWire(*i);
171 canvas.itemFocus.reset();
174 for (
auto& i: canvas.selection.items)
176 if (
auto v=i->variableCast())
177 if (v->controller.lock())
179 assert(i.use_count()==1);
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);
186 canvas.selection.clear();
187 canvas.requestRedraw();
190 void Minsky::copy()
const 192 if (canvas.selection.empty())
193 clipboard.putClipboard(
"");
200 clipboard.putClipboard(os.str());
207 (model->findAny(&Group::items, [&](
const ItemPtr& x) {
208 auto v=x->variableCast();
209 return v && v->valueId()==
valueId && v->defined();
213 void Minsky::saveGroupAsFile(
const Group& g,
const string& fileName)
223 if (canvas.itemFocus)
return;
224 map<string,string> existingParms;
226 for (
auto& [
valueId,vv]: variableValues)
227 existingParms.emplace(
valueId,vv->init());
229 istringstream is(clipboard.getClipboard());
230 xml_unpack_t unpacker(is);
237 for (
auto& i: g->items)
238 if (
auto v=i->variableCast(); v && v->type()==VariableType::parameter)
239 existingParms.emplace(v->valueId(),v->init());
242 canvas.selection.clear();
244 if (!history.empty() || !canvas.model.get()->empty()) {
245 bool alreadyDefinedMessageDisplayed=
false;
248 g->recursiveDo(&GroupItems::items,
249 [&](
Items&, Items::iterator i) {
250 if (
auto v=(*i)->variableCast())
251 if (v->defined() || v->isStock())
254 auto alreadyDefined = canvas.model->findAny
257 {
return j.get()!=v && j->variableCast() && j->variableCast()->defined();});
260 if (v->defined() && alreadyDefined && !alreadyDefinedMessageDisplayed)
262 message(
"Integral/Stock variable "+v->name()+
" already defined.");
263 alreadyDefinedMessageDisplayed=
true;
265 else if (!v->defined() && !alreadyDefined)
270 vp.retype(VariableType::flow);
272 convertVarType(vp->valueId(), VariableType::flow);
276 else if (alreadyDefined)
279 assert(v->portsSize()>1 && !v->ports(1).lock()->wires().empty());
280 g->removeWire(*v->ports(1).lock()->wires()[0]);
287 canvas.model->addGroup(g);
288 auto copyOfItems=g->items;
289 auto copyOfGroups=g->groups;
292 canvas.model->moveContents(*g);
295 for (
auto& i: copyOfItems)
296 canvas.selection.ensureItemInserted(i);
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();
305 for (
auto& i: canvas.selection.items)
308 canvas.setItemFocus(i);
312 if (!copyOfGroups.empty()) canvas.setItemFocus(copyOfGroups[0]);
314 canvas.model->removeGroup(*g);
315 canvas.requestRedraw();
319 throw runtime_error(
"clipboard data invalid");
322 void Minsky::insertGroupFromFile(
const string& file)
326 throw runtime_error(
string(
"failed to open ")+file);
328 xml_unpack_t saveFile(inf);
333 g->resizeOnContents();
337 void Minsky::makeVariablesConsistent()
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());
349 if (
auto g=dynamic_cast<GodleyIcon*>(i->get()))
350 godleysToUpdate.push_back(g);
355 for (
auto g: godleysToUpdate) g->update();
356 for (
auto i=variableValues.begin(); i!=variableValues.end(); )
357 if (existingNames.contains(i->first))
360 variableValues.erase(i++);
364 void Minsky::imposeDimensions()
366 for (
auto& v: variableValues)
368 v.second->imposeDimensions(dimensions);
369 v.second->tensorInit.imposeDimensions(dimensions);
374 void Minsky::garbageCollect()
376 makeVariablesConsistent();
383 for (
auto v=variableValues.begin(); v!=variableValues.end();)
384 if (v->second->temp())
385 variableValues.erase(v++);
389 variableValues.reset();
392 void Minsky::constructEquations()
394 if (cycleCheck())
throw error(
"cyclic network detected");
403 userFunctions.clear();
406 [
this](
const Items&, Items::const_iterator it){
407 if (
auto f=dynamic_pointer_cast<CallableFunction>(*it))
414 [
this](
const Groups&, Groups::const_iterator it){
424 assert(variableValues.validEntries());
427 assert(variableValues.validEntries());
431 void Minsky::dimensionalAnalysis()
const 433 const_cast<Minsky*
>(
this)->variableValues.resetUnitsCache();
438 [&](
Items& m, Items::iterator i)
440 if (
auto v=(*i)->variableCast())
443 if (v->isStock() && (v->inputWired() || v->controller.lock().get()))
446 else if ((*i)->portsSize()>0 && !(*i)->ports(0).lock()->input() &&
447 (*i)->ports(0).lock()->wires().empty())
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();
459 void Minsky::deleteAllUnits()
461 for (
auto& i: variableValues)
462 i.second->units.clear();
466 void Minsky::requestReset()
468 if (resetDuration<resetNowThreshold)
480 if (resetDuration<resetPostponedThreshold)
481 resetAt=std::chrono::system_clock::now()+resetPostponedThreshold;
483 resetAt=std::chrono::time_point<std::chrono::system_clock>::max();
486 void Minsky::requestRedraw()
489 canvas.requestRedraw();
490 equationDisplay.requestRedraw();
491 phillipsDiagram.requestRedraw();
492 for (
auto& pub: publicationTabs)
496 void Minsky::populateMissingDimensions() {
498 bool incompatibleMessageDisplayed=
false;
499 for (
auto& v: variableValues)
500 populateMissingDimensionsFromVariable(*v.second, incompatibleMessageDisplayed);
502 (&Group::items,[&](
Items& m, Items::iterator it)
504 if (
auto ri=dynamic_cast<Ravel*>(it->get()))
506 auto state=ri->getState();
507 for (
auto& j: state.handleStates)
508 dimensions.emplace(j.description,Dimension());
515 void Minsky::populateMissingDimensionsFromVariable(
const VariableValue& v,
bool& incompatibleMessageDisplayed)
517 set<string> varDimensions;
520 auto d=dimensions.find(xv.name);
521 if (d==dimensions.end())
523 dimensions.emplace(xv.name, xv.dimension);
524 varDimensions.insert(xv.name);
526 else if (d->second.type!=xv.dimension.type && !incompatibleMessageDisplayed)
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;
534 (&Group::items,[&](
Items& m, Items::iterator it)
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);
544 void Minsky::renameDimension(
const std::string& oldName,
const std::string& newName)
546 auto i=dimensions.find(oldName);
547 if (i!=dimensions.end())
549 dimensions[newName]=i->second;
553 for (
auto& v: variableValues)
555 auto hc=v.second->tensorInit.hypercube();
556 for (
auto& x: hc.xvectors)
561 v.second->tensorInit.hypercube(hc);
573 case GodleyAssetClass::liability:
574 case GodleyAssetClass::equity:
575 target_ac=GodleyAssetClass::asset;
577 case GodleyAssetClass::asset:
578 target_ac=GodleyAssetClass::liability;
584 std::set<string> duplicatedColumns;
587 [&](
Items& m, Items::iterator it)
589 if (
auto gi=dynamic_cast<GodleyIcon*>(it->get()))
591 for (
size_t i=1; i<gi->table.cols(); ++i)
592 if (!gi->table.cell(0,i).empty())
594 auto v=gi->table.cell(0,i);
600 if (r.contains(v) || gi->table.assetClass(i)!=target_ac)
603 duplicatedColumns.insert(v);
605 else if (!duplicatedColumns.contains(v) && gi->table.assetClass(i)==target_ac &&
608 ((ac!=GodleyAssetClass::equity && gi!=&
godley) || (ac==GodleyAssetClass::equity && gi==&
godley) ))
617 void Minsky::importDuplicateColumn(
GodleyTable& srcTable,
int srcCol)
619 if (srcCol<0 ||
size_t(srcCol)>=srcTable.
cols())
return;
621 const string& colName=
trimWS(srcTable.
cell(0,srcCol));
622 if (colName.empty())
return;
628 [&](
Items& m, Items::iterator i)
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)
633 balanceDuplicateColumns(*gi, col);
639 srcTable.
cell(0,srcCol).clear();
644 void Minsky::balanceColumns(
const GodleyIcon& srcGodley,
int srcCol,
GodleyIcon& destGodley,
int destCol)
const 646 auto& srcTable=srcGodley.
table;
647 auto& destTable=destGodley.
table;
649 map<string,string> srcRowLabels;
650 map<string, int> destRowLabels;
651 set<string> uniqueSrcRowLabels;
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())
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));
663 else if (srcTable.initialConditionRow(row))
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;
677 for (map<string,double>::iterator i=srcFlows.begin(); i!=srcFlows.end(); ++i)
678 if (i->second != destFlows[i->first])
681 if (i->first.find(
':')!=string::npos)
684 if (
scope==-1 || !variableValues.count(i->first))
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;
699 destTable.resize(destTable.rows()+1,destTable.cols());
700 destTable.cell(destTable.rows()-1, destCol) = flowEntry;
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;
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)
717 FlowCoef fc(destTable.cell(row, destCol));
718 if (!fc.name.empty())
720 if (fc.name==destGodley.
valueId(i->first))
722 destTable.cell(row, destCol).clear();
724 for (
size_t c=0; c<destTable.cols(); ++c)
725 if (!destTable.cell(row, c).empty())
727 rowsToDelete.insert(row);
732 map<string,double> unlabelledSigs;
733 for (
size_t row=1; row<destTable.rows(); ++row)
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);
741 for (
auto& i: unlabelledSigs)
744 destTable.insertRow(destTable.rows());
745 destTable.cell(destTable.rows()-1,destCol)=
FlowCoef(i.second,i.first).
str();
748 for (
auto row=rowsToDelete.rbegin(); row!=rowsToDelete.rend(); ++row)
749 destTable.deleteRow(*row);
752 void Minsky::balanceDuplicateColumns(
const GodleyIcon& srcGodley,
int srcCol)
755 if (srcCol<0 ||
size_t(srcCol)>=srcTable.
cols())
return;
758 if (colName.empty() || colName==
":_")
return;
760 bool matchFound=
false;
763 [&](
Items& m, Items::iterator i)
765 if (
auto gi=dynamic_cast<GodleyIcon*>(i->get()))
767 if (&gi->table==&srcTable)
769 for (
size_t col=1; col<gi->table.cols(); col++)
770 if (col==
size_t(srcCol))
continue;
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());
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());
784 throw error(
"invalid asset class for duplicate column %s",colName.c_str());
786 balanceColumns(srcGodley, srcCol, *gi, col);
790 for (
size_t col=1; col<gi->table.cols(); col++)
791 if (gi->valueId(
trimWS(gi->table.cell(0,col)))==colName)
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());
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());
805 throw error(
"invalid asset class for duplicate column %s",colName.c_str());
810 throw error(
"more than one duplicated column detected for %s",colName.c_str());
812 balanceColumns(srcGodley, srcCol, *gi, col);
819 vector<string> Minsky::allGodleyFlowVars()
const 822 model->recursiveDo(&GroupItems::items, [&](
const Items&, Items::const_iterator i) {
823 if (
auto g=dynamic_cast<GodleyIcon*>(i->get()))
825 auto flowVars=g->table.getVariables();
826 r.insert(flowVars.begin(),flowVars.end());
830 return {r.begin(),r.end()};
835 struct GodleyIt:
public vector<GodleyIcon*>::iterator
837 typedef vector<GodleyIcon*>::iterator
Super;
841 const std::vector<std::vector<std::string> >&
data()
const {
856 void Minsky::initGodleys()
858 auto toGodleyIcon=[](
const ItemPtr& i) {
return dynamic_cast<GodleyIcon*
>(i.get());};
860 (toGodleyIcon, &GroupItems::items, toGodleyIcon);
861 evalGodley.initialiseGodleys(GodleyIt(godleyItems.begin()),
862 GodleyIt(godleyItems.end()), variableValues);
871 flags |= reset_needed;
872 if (RKThreadRunning)
return;
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();
882 constructEquations();
883 constructEquations();
887 if (stockVars.empty()) stockVars.resize(1,0);
892 if (!stockVars.empty())
902 [&](
Items& m, Items::iterator i)
904 if (
auto r=dynamic_cast<Ravel*>(i->get()))
906 if (r->ports(1).lock()->numWires()>0)
907 if (
auto vv=r->ports(1).lock()->getVariableValue())
908 r->populateHypercube(vv->hypercube());
910 else if (
auto v=(*i)->variableCast())
918 [&](
Items& m, Items::iterator it)
920 if (
auto p=(*it)->plotWidgetCast())
922 p->disconnectAllVars();
923 p->clearPenAttributes();
925 for (
size_t i=0; i<p->portsSize(); ++i)
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())
932 p->connectVar(vv, i);
938 p->addConstantCurves();
946 flags &= ~reset_needed;
947 resetAt=std::chrono::time_point<std::chrono::system_clock>::max();
955 PhillipsFlow::maxFlow.clear();
956 PhillipsStock::maxStock.clear();
957 for (
auto& v: variableValues)
959 if (v.second->type()==VariableType::stock)
961 PhillipsStock::maxStock[v.second->units]+=v.second->value();
964 for (
auto& i: PhillipsStock::maxStock) i.second=abs(i.second);
969 flags &= ~reset_needed;
970 resetAt=std::chrono::time_point<std::chrono::system_clock>::max();
971 resetDuration=chrono::milliseconds::max();
975 vector<double> Minsky::step()
984 [&](
Items&, Items::iterator i)
985 {(*i)->updateIcon(t);
return false;});
988 const time_duration maxWait=milliseconds(maxWaitMS);
989 if ((microsec_clock::local_time()-(ptime&)lastRedraw) > maxWait)
992 lastRedraw=microsec_clock::local_time();
995 return {t, deltaT()};
998 string Minsky::diagnoseNonFinite()
const 1001 for (VariableValues::const_iterator v=variableValues.begin();
1002 v!=variableValues.end(); ++v)
1007 for (EvalOpVector::const_iterator e=equations.begin(); e!=equations.end(); ++e)
1008 if (!
isfinite(flowVars[(*e)->out]))
1009 return OperationType::typeName((*e)->type());
1013 void Minsky::save(
const std::string& filename)
1016 rename(filename.c_str(), (filename+
"~").c_str());
1019 Saver saver(filename);
1020 saver.
packer.prettyPrint=
true;
1027 rename((filename+
"~").c_str(), filename.c_str());
1030 throw runtime_error(
"cannot save to "+filename);
1034 flags &= ~is_edited;
1035 fileVersion=minskyVersion;
1039 for (
int i=numBackups; i>1; --i)
1040 rename((filename+
";"+to_string(i-1)).c_str(), (filename+
";"+to_string(i)).c_str());
1042 rename((filename+
"~").c_str(), (filename+
";1").c_str());
1047 void Minsky::load(
const std::string& filename)
1052 ifstream inf(filename);
1054 throw runtime_error(
"failed to open "+filename);
1059 xml_unpack_t saveFile(inf);
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.");
1069 flags=fullEqnDisplay_needed;
1074 model->recursiveDo(&Group::items,
1075 [
this](
Items&,Items::iterator i) {
1078 if (
auto g=dynamic_cast<GodleyIcon*>(i->get()))
1080 for (
unsigned j=1; j<g->table.cols(); ++j)
1081 balanceDuplicateColumns(*g,j);
1083 (*i)->adjustBookmark();
1091 populateMissingDimensions();
1093 catch (...) {flags|=reset_needed;}
1094 canvas.requestRedraw();
1097 model->recursiveDo(&Group::items,
1098 [](
Items&,Items::iterator i) {
1099 (*i)->updateBoundingBox();
1105 void Minsky::exportSchema(
const string& filename,
int schemaLevel)
1108 switch (schemaLevel)
1123 ofstream
f(filename);
1129 struct Network:
public multimap<const Port*,const Port*>
1134 {multimap<const Port*,const Port*>::emplace(x,y);}
1138 if (!portsVisited.insert(p).second)
1140 if (std::find(stack.begin(), stack.end(), p) != stack.end())
1148 const pair<iterator,iterator> range=equal_range(p);
1149 for (iterator i=range.first; i!=range.second; ++i)
1150 if (followWire(i->second))
1158 bool Minsky::cycleCheck()
const 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());
1170 if (!i.first->input() && !net.portsVisited.contains(i.first))
1171 if (net.followWire(i.first))
1176 bool Minsky::checkEquationOrder()
const 1178 ecolab::array<bool> fvInit(flowVars.size(),
false);
1180 for (
auto& v: variableValues)
1181 if (!inputWired(v.first) && v.second->idx()>=0)
1182 fvInit[v.second->idx()]=
true;
1184 for (
auto& e: equations)
1185 if (
auto eo=dynamic_cast<const ScalarEvalOp*>(e.get()))
1187 if (eo->out < 0|| (eo->numArgs()>0 && eo->in1.empty()) ||
1188 (eo->numArgs() > 1 && eo->in2.empty()))
1192 switch (eo->numArgs())
1195 fvInit[eo->out]=
true;
1198 fvInit[eo->out]=!eo->flow1 || fvInit[eo->in1[0]];
1205 if (
auto op=eo->state)
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();
1217 (!eo->flow1 || fvInit[eo->in1[0]]) && (!eo->flow2 || fvInit[eo->in2[0][0].idx]);
1229 void Minsky::displayErrorItem(
const Item&
op)
const 1233 auto& canvas=
const_cast<Canvas&
>(this->canvas);
1236 else if (
auto v=
op.variableCast())
1237 if (
auto c=v->controller.lock())
1238 displayErrorItem(*c);
1240 if (!canvas.itemIndicator)
1241 if (
auto g=
op.group.lock())
1243 while (g && !g->visible()) g=g->group.lock();
1244 if (g && g->visible())
1245 canvas.itemIndicator=g;
1248 if (canvas.itemIndicator)
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)
1255 canvas.model->moveTo(0.5*canvas.frameArgs().childWidth-physX+canvas.model->x(),
1256 0.5*canvas.frameArgs().childHeight-physY+canvas.model->y());
1261 if (!RKThreadRunning) canvas.requestRedraw();
1264 bool Minsky::pushHistory()
1268 return undone=
false;
1275 if (history.empty())
1277 history.emplace_back();
1278 buf.swap(history.back());
1279 historyPtr=history.size();
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)
1287 ostringstream prev, curr;
1288 xml_pack_t prevXbuf(prev), currXbuf(curr);
1291 history.back().reseto()>>previousMinsky;
1292 xml_pack(prevXbuf,
"Minsky",previousMinsky);
1294 if (curr.str()!=prev.str())
1304 history.emplace_back();
1305 buf.swap(history.back());
1306 historyPtr=history.size();
1307 if (autoSaver && doPushHistory)
1321 historyPtr=history.size();
1325 bool Minsky::commandHook(
const std::string& command,
unsigned nargs)
1327 static const set<string> constableCommands={
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",
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",
1351 "minsky.model.zoom",
1352 "minsky.multipleEquities",
1353 "minsky.openGroupInCanvas",
1354 "minsky.openModelInCanvas",
1357 "minsky.pushHistory",
1358 "minsky.redrawAllGodleyTables",
1364 "minsky.setGodleyDisplayValue",
1365 "minsky.setGodleyIconResource",
1366 "minsky.setGroupIconResource",
1367 "minsky.setLockIconResource",
1368 "minsky.setRavelIconResource",
1369 "minsky.setAutoSaveFile",
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
1385 auto t=getCommandData(command);
1386 if (t==
generic || (t==is_setterGetter && nargs>0))
1388 const bool modelChanged=pushHistory();
1389 if (modelChanged && command.find(
".keyPress")==string::npos)
1393 return modelChanged;
1396 return command==
"minsky.canvas.requestRedraw" || command==
"minsky.canvas.mouseDown" || command==
"minsky.canvas.mouseMove" || command.find(
".get")!=string::npos;
1400 long Minsky::undo(
int changes)
1403 if (historyPtr==history.size())
1405 historyPtr-=changes;
1406 if (historyPtr > 0 && historyPtr <= history.size())
1409 history[historyPtr-1].reseto()>>m;
1411 auto stashedValues=std::move(variableValues);
1414 unsigned numBookmarks=0;
1415 model->recursiveDo(&GroupItems::groups, [&](
const Groups&,
const Groups::const_iterator i) {
1416 numBookmarks+=(*i)->bookmarks.size();
1419 auto stashedGlobalBookmarks=model->bookmarks;
1420 auto stashedCanvasBookmarks=canvas.model->bookmarks;
1421 clearAllMaps(
false);
1430 for (
auto& v: variableValues)
1432 auto stashedValue=stashedValues.find(v.first);
1433 if (stashedValue!=stashedValues.end())
1434 v.second->tensorInit=std::move(stashedValue->second->tensorInit);
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();
1444 if (numBookmarksAfterwards!=numBookmarks)
1445 message(
"This undo/redo operation potentially deletes some bookmarks");
1446 try {requestReset();}
1451 historyPtr+=changes;
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;
1464 string newName=name;
1466 (&GroupItems::items,
1467 [&](
const Items&,Items::const_iterator i)
1469 if (
auto g=dynamic_cast<GodleyIcon*>(i->get()))
1471 if (type!=VariableType::flow)
1472 for (
auto& v: g->flowVars())
1473 if (v->valueId()==name)
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");
1480 if (type!=VariableType::stock)
1481 for (
auto& v: g->stockVars())
1482 if (v->valueId()==name)
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");
1493 if (
auto var=definingVar(name))
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");
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());
1509 model->recursiveDo(&Group::items,
1510 [&](
Items&, Items::iterator i) {
1512 if (v->valueId()==name)
1515 if (*i==canvas.item)
1521 auto init=i->second->init();
1523 i->second->init(init);
1526 void Minsky::addIntegral()
1528 if (
auto v=canvas.item->variableCast())
1529 if (
auto g=v->group.lock())
1532 convertVarType(v->valueId(),VariableType::integral);
1533 auto integ=
new IntOp;
1535 integ->
moveTo(canvas.item->x(), canvas.item->y());
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();
1542 canvas.requestRedraw();
1547 void Minsky::renderAllPlotsAsSVG(
const string& prefix)
const 1550 model->recursiveDo(&Group::items,
1551 [&](
Items&, Items::iterator i) {
1552 if (
auto p=(*i)->plotWidgetCast())
1554 if (!p->title.empty())
1555 p->RenderNativeWindow::renderToSVG(prefix+
"-"+p->title+
".svg");
1557 p->RenderNativeWindow::renderToSVG(prefix+
"-"+
str(plotNum++)+
".svg");
1562 void Minsky::exportAllPlotsAsCSV(
const string& prefix)
const 1565 model->recursiveDo(&Group::items,
1566 [&](
Items&, Items::iterator i) {
1567 if (
auto p=(*i)->plotWidgetCast())
1569 if (!p->title.empty())
1570 p->exportAsCSV((prefix+
"-"+p->title+
".csv"));
1572 p->exportAsCSV((prefix+
"-"+
str(plotNum++)+
".csv"));
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);
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();
1598 void Minsky::importVensim(
const string& filename)
1600 ifstream
f(filename);
1602 canvas.requestRedraw();
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>();}
1617 if (classifyOp(
op)==OperationType::general)
continue;
1618 if (
op==OperationType::copy)
continue;
1619 r[classdesc::to_string(classifyOp(
op))].push_back(
op);
1624 void Minsky::autoLayout()
1626 canvas.model->autoLayout();
1630 void Minsky::randomLayout()
1632 canvas.model->randomLayout();
1636 void Minsky::listAllInstances()
1639 if (
auto v=canvas.item->variableCast())
1641 variableInstanceList=std::make_shared<VariableInstanceList>(*canvas.model, v->valueId());
1644 variableInstanceList.reset();
1649 if (
wire.from()->wires().size()==1)
1651 auto& item=
wire.from()->item();
1652 if (!item.variableCast())
1654 for (
size_t i=1; i<item.portsSize(); ++i)
1655 if (
auto p=item.ports(i).lock())
1656 for (
auto w: p->wires())
1658 model->removeItem(item);
1660 else if (
auto p=item.ports(1).lock())
1661 if (p->wires().empty())
1662 model->removeItem(item);
1664 model->removeWire(
wire);
1667 void Minsky::setDefinition(
const std::string&
valueId,
const std::string& definition)
1672 if (
auto v=it->variableCast())
1677 if (
auto p=
var->ports(1).lock())
1685 for (
auto w: p->wires())
1692 group->addItem(udf);
1693 const bool notFlipped=!
flipped(
var->rotation());
1701 void Minsky::reloadAllCSVParameters()
1703 model->recursiveDo(&GroupItems::items,
1704 [&](
Items&, Items::iterator i) {
1705 if (
auto v=(*i)->variableCast())
1715 void Minsky::redrawAllGodleyTables()
1717 model->recursiveDo(&Group::items,
1718 [&](
Items&,Items::iterator i) {
1719 if (
auto g=dynamic_cast<GodleyIcon*>(i->get()))
1720 g->popup.requestRedraw();
1725 size_t Minsky::physicalMem()
1727 #if defined(__linux__) 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;
1750 string Minsky::defaultFont()
1753 string Minsky::defaultFont(
const std::string& x)
1761 double Minsky::fontScale()
1762 {
return ecolab::Pango::scaleFactor;}
1764 double Minsky::fontScale(
double s)
1765 {
return ecolab::Pango::scaleFactor=s;}
1769 if (cycleCheck())
throw error(
"cyclic network detected");
1770 ofstream
f(filename);
1772 f<<
"\\documentclass{article}\n";
1775 f<<
"\\usepackage{breqn}\n\\begin{document}\n";
1780 f<<
"\\begin{document}\n";
1783 f<<
"\\end{document}\n";
1789 return op->numPorts()-1;
1792 void Minsky::setAutoSaveFile(
const std::string& file) {
1800 {
if (!
minsky.busyCursorStack++)
minsky.setBusyCursor();}
1803 {
if (!--
minsky.busyCursorStack)
minsky.clearBusyCursor();}
1811 if (!inDestruct)
throw std::runtime_error(
"Cancelled");
1824 {t.add(d,
new RESTProcessAssociativeContainer<minsky::VariableValues>(a));}
void emplace(Port *x, Port *y)
void populatePublicationTabs(std::vector< minsky::PubTab > &) const
const Hypercube & hypercube() const override
const vector< AssetClass > & assetClass() const
class of each column (used in DE compliant mode)
void displayErrorItem(const Item &op) const
indicate operation item has error, if visible, otherwise contining group
ostream & latexWrapped(ostream &) const
void populatePhillipsDiagram(minsky::PhillipsDiagram &) const
populate a Phillips Diagram from this
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...
shared_ptr class for polymorphic operation objects. Note, you may assume that this pointer is always ...
exception-safe increment/decrement of a counter in a block
std::shared_ptr< std::atomic< bool > > cancel
set to true to cancel process in progreess displayProgress will throw if cancel is set...
PhillipsDiagram phillipsDiagram
CLASSDESC_ACCESS_EXPLICIT_INSTANTIATION(minsky::Minsky)
size_t scope(const string &name)
extract scope from a qualified variable name
void populateEvalOpVector(EvalOpVector &equations, std::vector< Integral > &integrals)
void xsd_generate(xsd_generate_t &g, const string &d, const minsky::Optional< T > &a)
std::shared_ptr< Item > ItemPtr
std::string uqName(const std::string &name)
extract unqualified portion of name
std::shared_ptr< Wire > WirePtr
string & cell(unsigned row, unsigned col)
void RESTProcess(RESTProcess_t &t, const string &d, minsky::VariableValues &a)
string valueId(const string &name)
construct a valueId from fully qualified name @ name should not be canonicalised
void populateMinsky(minsky::Minsky &) const
create a Minsky model from this
a shared_ptr that default constructs a default target, and is always valid
bool flipped(double rotation)
returns if the angle (in degrees) is in the second or third quadrant
GodleyTable table
table data. Must be declared before editor
bool signConventionReversed(int col) const
ItemPtr itemIndicator
for drawing error indicator on the canvas
void displayProgress(bool inDestruct=false)
classdesc::StringKeyMap< std::vector< OperationType::Type > > AvailableOperationsMapping
std::string valueId(const std::string &x) const
returns valueid for variable reference in table
void remove(std::vector< T > &x, const V &v)
remove an element from a vector. V must be comparable to a T
static std::unique_ptr< char[]> _defaultFont
set< const Port * > portsVisited
void save(const schema3::Minsky &)
string valueId(const std::string &x) const
void readMdl(Group &group, Simulation &simParms, istream &mdlFile)
import a Vensim mdl file into group, also populating simParms from the control block ...
std::vector< ItemPtr > Items
std::string trimWS(const std::string &s)
double isfinite(double x)
void updatePortVariableValue(EvalOpVector &equations)
std::vector< GroupPtr > Groups
OnStackExit< F > onStackExit(F f)
generator function
std::string str(T x)
utility function to create a string representation of a numeric type
const Minsky & cminsky()
const version to help in const correctness
std::map< string, double > flowSignature(unsigned col) const
flows, along with multipliers, appearing in col
string canonicalName(const string &name)
convert a raw name into a canonical name - this is not idempotent.
GodleyAssetClass::AssetClass assetClass(size_t col) const
represents a numerical coefficient times a variable (a "flow")
bool initialConditionRow(int row) const
bool isValueId(const string &name)
check that name is a valid valueId (useful for assertions)
void xml_pack(xml_pack_t &t, const string &d, minsky::Optional< T > &a)
Expr operator*(const NodePtr &x, const Expr &y)
GodleyIcon * operator->()
virtual void resetScroll()
reset main window scroll bars after model has been panned
vector< GodleyIcon * >::iterator Super
virtual void progress(const std::string &title, int)
set progress bar, out of 100, labelling the progress bar with title
string valueIdFromScope(const GroupPtr &scope, const std::string &name)
value Id from scope and canonical name name
std::shared_ptr< Group > GroupPtr
std::string CSVQuote(const std::string &x, char sep)
quotes a string if it contains a separator character, and double quotes quotes
void moveTo(float x, float y)
RAII set the minsky object to a different one for the current scope.
vector< string > enumVals()
list the possible string values of an enum (for TCL)
UnitsExpressionWalker timeUnit
string latex(double)
convert double to a LaTeX string representing that value
void stripByteOrderingMarker(std::istream &s)
checks if the input stream has the UTF-8 byte ordering marker, and removes it if present ...
virtual std::weak_ptr< Port > ports(std::size_t i) const
callback to be run when item deleted from group
Minsky & minsky()
global minsky object
Item & item()
owner of this port
bool followWire(const Port *p)
vector< const Port * > stack
const std::vector< std::vector< std::string > > & data() const
classdesc::xml_pack_t packer
ostream & latex(ostream &) const
render as a LaTeX eqnarray Use LaTeX brqn environment to wrap long lines