modelflowib 2.81__tar.gz → 2.82__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. {modelflowib-2.81 → modelflowib-2.82}/PKG-INFO +1 -1
  2. {modelflowib-2.81 → modelflowib-2.82}/modelclass.py +121 -25
  3. {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/PKG-INFO +1 -1
  4. {modelflowib-2.81 → modelflowib-2.82}/pyproject.toml +1 -1
  5. {modelflowib-2.81 → modelflowib-2.82}/README.md +0 -0
  6. {modelflowib-2.81 → modelflowib-2.82}/license.md +0 -0
  7. {modelflowib-2.81 → modelflowib-2.82}/modelBLfunk.py +0 -0
  8. {modelflowib-2.81 → modelflowib-2.82}/model_Excel.py +0 -0
  9. {modelflowib-2.81 → modelflowib-2.82}/model_cvx.py +0 -0
  10. {modelflowib-2.81 → modelflowib-2.82}/model_dynare.py +0 -0
  11. {modelflowib-2.81 → modelflowib-2.82}/model_financial_stability.py +0 -0
  12. {modelflowib-2.81 → modelflowib-2.82}/model_latex.py +0 -0
  13. {modelflowib-2.81 → modelflowib-2.82}/model_latex_class.py +0 -0
  14. {modelflowib-2.81 → modelflowib-2.82}/model_parquet_mixin.py +0 -0
  15. {modelflowib-2.81 → modelflowib-2.82}/modelconstruct.py +0 -0
  16. {modelflowib-2.81 → modelflowib-2.82}/modelconstruct_estimation.py +0 -0
  17. {modelflowib-2.81 → modelflowib-2.82}/modeldash.py +0 -0
  18. {modelflowib-2.81 → modelflowib-2.82}/modeldashboot.py +0 -0
  19. {modelflowib-2.81 → modelflowib-2.82}/modeldashsidebar.py +0 -0
  20. {modelflowib-2.81 → modelflowib-2.82}/modeldekom.py +0 -0
  21. {modelflowib-2.81 → modelflowib-2.82}/modeldiff.py +0 -0
  22. {modelflowib-2.81 → modelflowib-2.82}/modeldisplay.py +0 -0
  23. {modelflowib-2.81 → modelflowib-2.82}/modelestimation.py +0 -0
  24. {modelflowib-2.81 → modelflowib-2.82}/modelestimator_new.py +0 -0
  25. {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/SOURCES.txt +0 -0
  26. {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/dependency_links.txt +0 -0
  27. {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/requires.txt +0 -0
  28. {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/top_level.txt +0 -0
  29. {modelflowib-2.81 → modelflowib-2.82}/modelgrab.py +0 -0
  30. {modelflowib-2.81 → modelflowib-2.82}/modelgrabgdx.py +0 -0
  31. {modelflowib-2.81 → modelflowib-2.82}/modelgrabwf2.py +0 -0
  32. {modelflowib-2.81 → modelflowib-2.82}/modelhelp.py +0 -0
  33. {modelflowib-2.81 → modelflowib-2.82}/modelhtml.py +0 -0
  34. {modelflowib-2.81 → modelflowib-2.82}/modelinvert.py +0 -0
  35. {modelflowib-2.81 → modelflowib-2.82}/modeljupyter.py +0 -0
  36. {modelflowib-2.81 → modelflowib-2.82}/modeljupytermagic.py +0 -0
  37. {modelflowib-2.81 → modelflowib-2.82}/modelmacrograb.py +0 -0
  38. {modelflowib-2.81 → modelflowib-2.82}/modelmanipulation.py +0 -0
  39. {modelflowib-2.81 → modelflowib-2.82}/modelmf.py +0 -0
  40. {modelflowib-2.81 → modelflowib-2.82}/modelnet.py +0 -0
  41. {modelflowib-2.81 → modelflowib-2.82}/modelnewton.py +0 -0
  42. {modelflowib-2.81 → modelflowib-2.82}/modelnormalize.py +0 -0
  43. {modelflowib-2.81 → modelflowib-2.82}/modelpattern.py +0 -0
  44. {modelflowib-2.81 → modelflowib-2.82}/modelreport.py +0 -0
  45. {modelflowib-2.81 → modelflowib-2.82}/modelsolver_ng.py +0 -0
  46. {modelflowib-2.81 → modelflowib-2.82}/modeluserfunk.py +0 -0
  47. {modelflowib-2.81 → modelflowib-2.82}/modelvis.py +0 -0
  48. {modelflowib-2.81 → modelflowib-2.82}/modelwidget.py +0 -0
  49. {modelflowib-2.81 → modelflowib-2.82}/modelwidget_input.py +0 -0
  50. {modelflowib-2.81 → modelflowib-2.82}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelflowib
3
- Version: 2.81
3
+ Version: 2.82
4
4
  Summary: A tool to solve and manage dynamic economic and financial models
5
5
  Author-email: Ib Hansen <Ib.Hansen.Iv@gmail.com>
6
6
  License-Expression: MIT
@@ -4098,8 +4098,10 @@ class Graph_Draw_Mixin():
4098
4098
  self.att_dic = {}
4099
4099
 
4100
4100
  G = nx.DiGraph()
4101
- # a set, to weed out multiple links
4102
- G.add_edges_from({(x.child, x.parent) for x in alllinks})
4101
+ # a set, to weed out multiple links, sorted so the nodes always go into
4102
+ # the graph in the same order. Then nx_back_edges cuts the same cycles
4103
+ # each time, and the same model gives the same drawing in every session
4104
+ G.add_edges_from(sorted({(x.child, x.parent) for x in alllinks}))
4103
4105
 
4104
4106
  for v in G.nodes:
4105
4107
  var_name = v.split('(')[0]
@@ -4135,14 +4137,18 @@ class Graph_Draw_Mixin():
4135
4137
  else:
4136
4138
  G.nodes[v]['layer'] = 0 if down_len is None else down_len
4137
4139
  else:
4138
- # no variable in the center, like drawmodel, then the layer is the logical
4139
- # order. The simultaneous blocks are condensed to one node, else there is
4140
- # no order, and a node of such a block gets the layer of its block
4141
- condensed = nx.condensation(G)
4142
- for layer, blocks in enumerate(nx.topological_generations(condensed)):
4143
- for block in blocks:
4144
- for v in condensed.nodes[block]['members']:
4145
- G.nodes[v]['layer'] = layer
4140
+ # no variable in the center, like drawmodel, then the layer is the
4141
+ # logical order. A simultaneous block is a cycle and has no order, so
4142
+ # the edges which close the cycles are taken out first, and then the
4143
+ # nodes of the block are spread over the columns like all the others.
4144
+ # This is what graphviz does before it ranks the nodes, and the edges
4145
+ # which were taken out are drawn as back edges by display_nx_svg
4146
+ dag = nx.DiGraph()
4147
+ dag.add_nodes_from(G)
4148
+ dag.add_edges_from(set(G.edges) - self.nx_back_edges(G))
4149
+ for layer, vs in enumerate(nx.topological_generations(dag)):
4150
+ for v in vs:
4151
+ G.nodes[v]['layer'] = layer
4146
4152
 
4147
4153
  for child, parent in G.edges:
4148
4154
  try:
@@ -4160,6 +4166,43 @@ class Graph_Draw_Mixin():
4160
4166
 
4161
4167
  return G
4162
4168
 
4169
+ @staticmethod
4170
+ def nx_back_edges(G):
4171
+ '''The edges which close a cycle, found by a depth first search
4172
+
4173
+ Taking them out leaves a graph without cycles, which can be sorted into
4174
+ columns. Graphviz does the same before it ranks the nodes of a drawing,
4175
+ and then draws the edges which were taken out as curved back edges.
4176
+
4177
+ For a model this means that a simultaneous block is spread over the
4178
+ columns like the rest of the model, instead of standing in one column
4179
+ where all its edges would be hidden behind the boxes.
4180
+
4181
+ Returns a set of (from, to) edges, self loops included.
4182
+ '''
4183
+ back = set()
4184
+ # the nodes on the current path have state 0, the finished ones state 1.
4185
+ # An edge to a node on the path closes a cycle, and is a back edge
4186
+ state = {}
4187
+ for root in G:
4188
+ if root in state:
4189
+ continue
4190
+ state[root] = 0
4191
+ stack = [(root, iter(G[root]))]
4192
+ while stack:
4193
+ v, children = stack[-1]
4194
+ for child in children:
4195
+ if state.get(child) == 0:
4196
+ back.add((v, child))
4197
+ elif child not in state:
4198
+ state[child] = 0
4199
+ stack.append((child, iter(G[child])))
4200
+ break
4201
+ else:
4202
+ state[v] = 1
4203
+ stack.pop()
4204
+ return back
4205
+
4163
4206
  @staticmethod
4164
4207
  def nx_layout(G, sort=True):
4165
4208
  '''Positions for a graph made by alllinks_to_nx
@@ -4251,7 +4294,10 @@ class Graph_Draw_Mixin():
4251
4294
  width=[G.edges[e]['width'] for e in edgelist],
4252
4295
  edge_color='grey', arrows=True, arrowstyle='-|>',
4253
4296
  arrowsize=12, node_size=1200,
4254
- min_source_margin=25, min_target_margin=25, ax=ax)
4297
+ min_source_margin=25, min_target_margin=25,
4298
+ # curved, so the two links between a variable and
4299
+ # its lagged value are not drawn on top of each other
4300
+ connectionstyle='arc3,rad=0.12', ax=ax)
4255
4301
  for v, (x, y) in pos.items():
4256
4302
  ax.text(x, y, G.nodes[v]['label'], ha='center', va='center',
4257
4303
  fontsize=fontsize, color='blue', zorder=3,
@@ -4284,7 +4330,7 @@ class Graph_Draw_Mixin():
4284
4330
  :browser: open the svg in a separate browser window, where it can be zoomed
4285
4331
  '''
4286
4332
  fontsize = kwargs.get('fontsize', 12)
4287
- pad, gap_x, gap_y = 6, 90, 22
4333
+ pad, gap_y = 6, 22
4288
4334
  char_width = 0.62 * fontsize # the labels are drawn in a monospace font
4289
4335
  pos = self.nx_layout(G, sort=kwargs.get('sort', True))
4290
4336
 
@@ -4296,14 +4342,20 @@ class Graph_Draw_Mixin():
4296
4342
  box = {v: (len(G.nodes[v]['label']) * char_width + 2 * pad, fontsize + 2 * pad)
4297
4343
  for v in G.nodes}
4298
4344
 
4345
+ # the space between two columns. A fixed distance looks cramped when the
4346
+ # labels carry the variable descriptions and the boxes get wide, so it
4347
+ # follows the widest box. des=False keeps the labels, and the boxes, short
4348
+ gap_x = max(90., 0.25 * max(w for w, h in box.values()))
4349
+
4299
4350
  # the nodes are placed in columns by layer, in the order found by nx_layout
4300
4351
  layers = defaultdict(list)
4301
4352
  for v in sorted(G.nodes, key=lambda v: pos[v][1]):
4302
4353
  layers[G.nodes[v]['layer']].append(v)
4303
4354
 
4304
- center, x = {}, gap_x
4355
+ center, colwidths, x = {}, {}, gap_x
4305
4356
  for lev in sorted(layers):
4306
4357
  colwidth = max(box[v][0] for v in layers[lev])
4358
+ colwidths[lev] = colwidth
4307
4359
  colheight = sum(box[v][1] + gap_y for v in layers[lev]) - gap_y
4308
4360
  y = -colheight / 2.
4309
4361
  for v in layers[lev]:
@@ -4317,9 +4369,9 @@ class Graph_Draw_Mixin():
4317
4369
  width = x
4318
4370
  height = max(cy + box[v][1] / 2. for v, (cx, cy) in center.items()) + gap_y
4319
4371
 
4320
- def border(v, other):
4321
- '''The point where the line from v towards other leaves the box of v'''
4322
- (cx, cy), (ox, oy) = center[v], center[other]
4372
+ def border(v, ox, oy):
4373
+ '''The point where the line from v towards (ox,oy) leaves the box of v'''
4374
+ cx, cy = center[v]
4323
4375
  w, h = box[v]
4324
4376
  dx, dy = ox - cx, oy - cy
4325
4377
  if not dx and not dy:
@@ -4327,21 +4379,51 @@ class Graph_Draw_Mixin():
4327
4379
  scale = min(w / 2. / abs(dx) if dx else 1e9, h / 2. / abs(dy) if dy else 1e9)
4328
4380
  return cx + dx * scale, cy + dy * scale
4329
4381
 
4382
+ def bow(child, parent):
4383
+ '''How far the link bows out from the straight line, 0 for a straight line
4384
+
4385
+ A straight line fails in three cases. A link which points back to an
4386
+ earlier column - the feedback over the periods from a variable to its
4387
+ lagged value is one - would run straight through the boxes between the
4388
+ two ends. The two links of a pair would fall on top of each other. And
4389
+ both ends in the same column gives a line behind the boxes of the
4390
+ column. Graphviz curves such back edges, and so does this.
4391
+ '''
4392
+ lchild, lparent = G.nodes[child]['layer'], G.nodes[parent]['layer']
4393
+ if lchild == lparent:
4394
+ # out of the column, so the curve is clear of the boxes
4395
+ return colwidths[lchild] + gap_x / 2.
4396
+ if lparent < lchild or G.has_edge(parent, child):
4397
+ # the longer the way back, the more boxes to arch over
4398
+ return gap_y * (1 + abs(lparent - lchild))
4399
+ return 0.
4400
+
4330
4401
  arrow = f'arrow{abs(hash(navn)) % 100000}' # an id of its own for each drawing
4331
4402
  lines = []
4332
4403
  for child, parent in G.edges:
4333
4404
  edge = G.edges[child, parent]
4334
4405
  if not edge.get('visible', True):
4335
4406
  continue
4336
- x1, y1 = border(child, parent)
4337
- x2, y2 = border(parent, child)
4338
- length = max(((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5, 0.1)
4407
+ (cx1, cy1), (cx2, cy2) = center[child], center[parent]
4408
+ dx, dy = cx2 - cx1, cy2 - cy1
4409
+ span = max((dx * dx + dy * dy) ** 0.5, 0.1)
4410
+ # the control point sits beside the middle of the straight line. It
4411
+ # follows the direction, so the two links of a pair bow to each their
4412
+ # side. With no bow it is the middle itself, and the curve is a line
4413
+ side = bow(child, parent)
4414
+ ctrlx = (cx1 + cx2) / 2. - dy / span * side
4415
+ ctrly = (cy1 + cy2) / 2. + dx / span * side
4416
+ x1, y1 = border(child, ctrlx, ctrly)
4417
+ x2, y2 = border(parent, ctrlx, ctrly)
4339
4418
  # stop short of the box, so the arrow head is not drawn on top of it
4340
- x2, y2 = x2 - (x2 - x1) * 8. / length, y2 - (y2 - y1) * 8. / length
4419
+ ex, ey = x2 - ctrlx, y2 - ctrly
4420
+ elen = max((ex * ex + ey * ey) ** 0.5, 0.1)
4421
+ x2, y2 = x2 - ex / elen * 8., y2 - ey / elen * 8.
4341
4422
  lines.append(
4342
- f'<line x1="{x1:.1f}" y1="{y1:.1f}" x2="{x2:.1f}" y2="{y2:.1f}" stroke="grey" '
4423
+ f'<path d="M {x1:.1f} {y1:.1f} Q {ctrlx:.1f} {ctrly:.1f} {x2:.1f} {y2:.1f}" '
4424
+ f'fill="none" stroke="grey" '
4343
4425
  f'stroke-width="{max(1., edge["width"]):.1f}" marker-end="url(#{arrow})">'
4344
- f'<title>{esc(edge["tooltip"])}</title></line>')
4426
+ f'<title>{esc(edge["tooltip"])}</title></path>')
4345
4427
 
4346
4428
  nodes = []
4347
4429
  for v, (cx, cy) in center.items():
@@ -4355,11 +4437,24 @@ class Graph_Draw_Mixin():
4355
4437
  f'{esc(G.nodes[v]["label"])}</text></g>')
4356
4438
 
4357
4439
  title = kwargs.get('title', navn)
4358
- titletext = (f'<text x="{gap_x}" y="{fontsize + 8}" font-family="sans-serif" '
4440
+ titletext = (f'<text x="{gap_x:.0f}" y="{fontsize + 8}" font-family="sans-serif" '
4359
4441
  f'font-size="{fontsize + 2}" fill="black">{esc(title)}</text>') if title else ''
4360
4442
 
4443
+ # the drawing fits the width of the notebook, as it always did, but it is
4444
+ # not scaled further down than minfontsize, where the labels stop being
4445
+ # readable and the boxes start to look like they run into each other.
4446
+ # A drawing wider than that keeps its size and the cell scrolls sideways.
4447
+ # min-width does this in the browser, which is the one who knows how wide
4448
+ # the cell is. width= overrules, '100%' always fits
4449
+ svgwidth = kwargs.get('width', '')
4450
+ if svgwidth:
4451
+ style = 'max-width:100%;height:auto'
4452
+ else:
4453
+ floor = min(1., kwargs.get('minfontsize', 9) / fontsize)
4454
+ svgwidth = f'{width:.0f}'
4455
+ style = f'max-width:100%;min-width:{width * floor:.0f}px;height:auto'
4361
4456
  svg = (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width:.0f} {height:.0f}" '
4362
- f'width="{kwargs.get("width", "100%")}" style="max-width:100%;height:auto">'
4457
+ f'width="{svgwidth}" style="{style}">'
4363
4458
  f'<defs><marker id="{arrow}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" '
4364
4459
  'markerHeight="6" orient="auto-start-reverse">'
4365
4460
  '<path d="M 0 0 L 10 5 L 0 10 z" fill="grey"/></marker></defs>'
@@ -4381,7 +4476,8 @@ class Graph_Draw_Mixin():
4381
4476
  print(f'No separate window here: {e}')
4382
4477
  print(f'The drawing is saved as {svgname} and is shown below')
4383
4478
 
4384
- display(HTML(svg))
4479
+ # the div scrolls when min-width keeps the drawing wider than the cell
4480
+ display(HTML(f'<div style="overflow-x:auto;max-width:100%">{svg}</div>'))
4385
4481
  self.last_svg = svg
4386
4482
  return
4387
4483
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modelflowib
3
- Version: 2.81
3
+ Version: 2.82
4
4
  Summary: A tool to solve and manage dynamic economic and financial models
5
5
  Author-email: Ib Hansen <Ib.Hansen.Iv@gmail.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modelflowib" # PyPI distribution name
7
- version = "2.81" # OVERWRITTEN at build time from MF_VERSION
7
+ version = "2.82" # OVERWRITTEN at build time from MF_VERSION
8
8
  # (set_pyproject_version.py / make_pip.py -v)
9
9
  description = "A tool to solve and manage dynamic economic and financial models"
10
10
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes