Minsky
autoLayout.cc
Go to the documentation of this file.
1 /*
2  @copyright Steve Keen 2020
3  @author Russell Standish
4  This file is part of Minsky.
5 
6  Minsky is free software: you can redistribute it and/or modify it
7  under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  Minsky is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with Minsky. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "cairoItems.h"
21 #include "autoLayout.h"
22 #include "selection.h"
23 #include "lasso.h"
24 #include "userFunction.h"
25 #include "minsky.h"
26 #include "minsky_epilogue.h"
27 
28 #include <map>
29 #include <random>
30 #include <vector>
31 
32 #include <boost/graph/fruchterman_reingold.hpp>
33 #include <boost/graph/topology.hpp>
34 #include <boost/graph/directed_graph.hpp>
35 
36 using namespace std;
37 using namespace boost;
38 using namespace minsky;
39 
40 namespace minsky
41 {
42  namespace
43  {
44  using Graph=boost::directed_graph<Item*>;
45 
46  // computes minimum distance two items can approach each other
47  double minD(const Item& item1, const Item& item2)
48  {return std::max(item1.width()+item2.width(), item1.height()+item2.height());}
49 
50  // attractive force between connected items, repulsive when too close
51  struct WireForce
52  {
53  double operator()(const Graph::edge_descriptor& e, double k, double d, const Graph& g)
54  {
55  auto from=g[source(e,g)], to=g[target(e,g)];
56  auto m=minD(*from,*to);
57  if (d<m)
58  return 0;
59  //return exp(d-m)-1;//d*d*d;
60  return d*d;
61  }
62  };
63 
65  {
66  double operator()(const Graph::vertex_descriptor& v1, const Graph::vertex_descriptor& v2,
67  double k, double d, const Graph& g)
68  {
69  auto m=minD(*g[v1],*g[v2]);
70  if (d<m)
71  return k*k*m/(d*d);
72  return k*k/d;
73  }
74  };
75 
76  // compute total area occupied by items
77  double totalArea(const Group& g)
78  {
79  double area=0;
80  for (auto& i: g.items)
81  area+=double(i->width())*i->height();
82  for (auto& i: g.groups)
83  area+=double(i->width())*i->height();
84  return area;
85  }
86  }
87 
89  {
90  const double layoutSize=sqrt(3*totalArea(g));
91  default_random_engine gen;
92  uniform_real_distribution<double> rng(0,1);
93  for (auto& i: g.items)
94  i->moveTo(layoutSize*rng(gen), layoutSize*rng(gen));
95  for (auto& i: g.groups)
96  i->moveTo(layoutSize*rng(gen), layoutSize*rng(gen));
97  }
98 
99 
100 
102  {
103  if (g.items.size()+g.groups.size()<2) return;
104  const double layoutSize=sqrt(10*totalArea(g)); //half width of square to emplace the items
105 
106  Graph gg;
107  map<Item*, decltype(gg.add_vertex())> vertexMap;
108  for (auto& i: g.items)
109  vertexMap.emplace(i.get(), gg.add_vertex(i.get()));
110  for (auto& i: g.groups)
111  {
112  vertexMap.emplace(i.get(), gg.add_vertex(i.get()));
113  // add I/O variables, as these may be wired too.
114  for (auto& j: i->inVariables)
115  vertexMap.emplace(j.get(), gg.add_vertex(j.get()));
116  for (auto& j: i->outVariables)
117  vertexMap.emplace(j.get(), gg.add_vertex(j.get()));
118  }
119 
120  for (auto& w: g.wires)
121  gg.add_edge(vertexMap[&w->from()->item()], vertexMap[&w->to()->item()]);
122 
123  // add some additional vertices representing classes: functions, parameters, flowVars, stockVars etc
124  Item functions, parameters, flowVars, intVars;
125  functions.moveTo(-0.5*layoutSize,-0.5*layoutSize);
126  parameters.moveTo(-0.5*layoutSize,0.5*layoutSize);
127  flowVars.moveTo(-0.5*layoutSize,0.5*layoutSize);
128  intVars.moveTo(0.5*layoutSize,0.5*layoutSize);
129  vertexMap.emplace(&functions, gg.add_vertex(&functions));
130  vertexMap.emplace(&parameters, gg.add_vertex(&parameters));
131  vertexMap.emplace(&flowVars, gg.add_vertex(&flowVars));
132  vertexMap.emplace(&intVars, gg.add_vertex(&intVars));
133 
134  // now bind items without outputs to this fixtures
135  for (auto& i: g.items)
136  {
137  if (dynamic_cast<UserFunction*>(i.get()) && i->ports(0).lock()->wires().empty())
138  gg.add_edge(vertexMap[&functions], vertexMap[i.get()]);
139  else if (auto v=i->variableCast())
140  if ((i->portsSize()>0 && i->ports(0).lock()->wires().empty()) || (i->portsSize()>1 && !i->ports(1).lock()->wires().empty()))
141  switch (v->type())
142  {
143  case VariableType::parameter:
144  gg.add_edge(vertexMap[&parameters], vertexMap[i.get()]);
145  break;
146  case VariableType::flow:
147  gg.add_edge(vertexMap[&flowVars], vertexMap[i.get()]);
148  break;
149  case VariableType::integral:
150  gg.add_edge(vertexMap[&intVars], vertexMap[i.get()]);
151  break;
152  default:
153  break;
154  }
155  }
156 
157 
158  using Topology=square_topology<>;
159 
160  using PosMap=std::map<decltype(gg.add_vertex()), Topology::point_type>;
161  PosMap positions;
162  const boost::associative_property_map<PosMap> pm(positions);
163 
164  for (auto& i: vertexMap)
165  {
166  Topology::point_type p;
167  p[0]=i.first->x();
168  p[1]=i.first->y();
169  positions[i.second]=p;
170  }
171 
172  double temp=10;
173  // use the cooling schedule to drive the progress bar
174  ProgressUpdater pu(minsky().progressState, "Autolaying out...", 100);
175  fruchterman_reingold_force_directed_layout
176  (gg,pm, Topology(layoutSize), attractive_force(WireForce()).repulsive_force(RepulsiveForce()).
177  cooling([&temp,&pu](){pu.setProgress(0.1*(10-temp)); return std::max(0., temp-=0.1);}));
178  // maybe not needed
179  //.force_pairs(boost::all_force_pairs())
180 
181  // move items to result of algorithm
182  auto vertexRange=vertices(gg);
183  for (auto i=vertexRange.first; i!=vertexRange.second; ++i)
184  {
185  auto p=pm[*i];
186  gg[*i]->moveTo(p[0]+layoutSize,p[1]+layoutSize);
187  }
188 
189  // TODO should we recursively descend into groups or not? If so,
190  // then we need to be aware of group's size
191 // for (auto& i: g.groups)
192 // layoutGroup(*i);
193  }
194 }
195 
Expr sqrt(const Expr &x)
Definition: expr.h:154
minsky::Minsky minsky
Definition: pyminsky.cc:28
STL namespace.
void randomizeLayout(Group &g)
randomly place items on canvas
Definition: autoLayout.cc:88
Creation and access to the minskyTCL_obj object, which has code to record whenever Minsky&#39;s state cha...
Definition: constMap.h:22
float width() const
Definition: item.h:242
void setProgress(double fraction)
Sets the progress to a given fraction of this stack&#39;s allocation.
Definition: progress.h:79
double operator()(const Graph::vertex_descriptor &v1, const Graph::vertex_descriptor &v2, double k, double d, const Graph &g)
Definition: autoLayout.cc:66
double minD(const Item &item1, const Item &item2)
Definition: autoLayout.cc:47
boost::directed_graph< Item * > Graph
Definition: autoLayout.cc:44
Groups groups
Definition: group.h:79
float height() const
Definition: item.h:243
void layoutGroup(Group &g)
auto layout group items
Definition: autoLayout.cc:101
void moveTo(float x, float y)
Definition: item.cc:256
double operator()(const Graph::edge_descriptor &e, double k, double d, const Graph &g)
Definition: autoLayout.cc:53