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.
- {modelflowib-2.81 → modelflowib-2.82}/PKG-INFO +1 -1
- {modelflowib-2.81 → modelflowib-2.82}/modelclass.py +121 -25
- {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/PKG-INFO +1 -1
- {modelflowib-2.81 → modelflowib-2.82}/pyproject.toml +1 -1
- {modelflowib-2.81 → modelflowib-2.82}/README.md +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/license.md +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelBLfunk.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_Excel.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_cvx.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_dynare.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_financial_stability.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_latex.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_latex_class.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/model_parquet_mixin.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelconstruct.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelconstruct_estimation.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeldash.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeldashboot.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeldashsidebar.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeldekom.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeldiff.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeldisplay.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelestimation.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelestimator_new.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/SOURCES.txt +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/dependency_links.txt +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/requires.txt +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelflowib.egg-info/top_level.txt +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelgrab.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelgrabgdx.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelgrabwf2.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelhelp.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelhtml.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelinvert.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeljupyter.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeljupytermagic.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelmacrograb.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelmanipulation.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelmf.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelnet.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelnewton.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelnormalize.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelpattern.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelreport.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelsolver_ng.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modeluserfunk.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelvis.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelwidget.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/modelwidget_input.py +0 -0
- {modelflowib-2.81 → modelflowib-2.82}/setup.cfg +0 -0
|
@@ -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
|
-
|
|
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
|
|
4139
|
-
# order.
|
|
4140
|
-
#
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
4321
|
-
'''The point where the line from v towards
|
|
4322
|
-
|
|
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
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
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
|
-
|
|
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'<
|
|
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></
|
|
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="{
|
|
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
|
-
|
|
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
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modelflowib" # PyPI distribution name
|
|
7
|
-
version = "2.
|
|
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
|
|
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
|