modelflowib 2.80__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.80 → modelflowib-2.82}/PKG-INFO +14 -10
- {modelflowib-2.80 → modelflowib-2.82}/modelclass.py +213 -55
- {modelflowib-2.80 → modelflowib-2.82}/modelflowib.egg-info/PKG-INFO +14 -10
- {modelflowib-2.80 → modelflowib-2.82}/modelflowib.egg-info/requires.txt +12 -6
- {modelflowib-2.80 → modelflowib-2.82}/pyproject.toml +23 -11
- {modelflowib-2.80 → modelflowib-2.82}/README.md +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/license.md +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelBLfunk.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_Excel.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_cvx.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_dynare.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_financial_stability.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_latex.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_latex_class.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/model_parquet_mixin.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelconstruct.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelconstruct_estimation.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeldash.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeldashboot.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeldashsidebar.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeldekom.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeldiff.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeldisplay.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelestimation.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelestimator_new.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelflowib.egg-info/SOURCES.txt +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelflowib.egg-info/dependency_links.txt +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelflowib.egg-info/top_level.txt +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelgrab.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelgrabgdx.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelgrabwf2.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelhelp.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelhtml.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelinvert.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeljupyter.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeljupytermagic.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelmacrograb.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelmanipulation.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelmf.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelnet.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelnewton.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelnormalize.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelpattern.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelreport.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelsolver_ng.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modeluserfunk.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelvis.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelwidget.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/modelwidget_input.py +0 -0
- {modelflowib-2.80 → modelflowib-2.82}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelflowib
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -20,22 +20,26 @@ Requires-Dist: numpy
|
|
|
20
20
|
Requires-Dist: networkx
|
|
21
21
|
Requires-Dist: tqdm
|
|
22
22
|
Requires-Dist: scipy
|
|
23
|
-
Requires-Dist: numexpr
|
|
24
|
-
Requires-Dist: bottleneck
|
|
25
23
|
Requires-Dist: XlsxWriter
|
|
26
24
|
Requires-Dist: pyarrow
|
|
27
25
|
Requires-Dist: tabulate
|
|
28
26
|
Requires-Dist: openpyxl
|
|
29
27
|
Requires-Dist: xarray
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist: dash
|
|
33
|
-
Requires-Dist: dash-bootstrap-components
|
|
34
|
-
Requires-Dist: dash-interactive-graphviz
|
|
35
|
-
Requires-Dist: lmfit
|
|
36
|
-
Requires-Dist: jupyter
|
|
28
|
+
Requires-Dist: jinja2
|
|
29
|
+
Requires-Dist: ipython
|
|
37
30
|
Requires-Dist: ipywidgets
|
|
38
31
|
Requires-Dist: ipydatagrid
|
|
32
|
+
Requires-Dist: lmfit
|
|
33
|
+
Requires-Dist: statsmodels
|
|
34
|
+
Requires-Dist: numba; sys_platform != "emscripten"
|
|
35
|
+
Requires-Dist: cvxopt; sys_platform != "emscripten"
|
|
36
|
+
Requires-Dist: numexpr; sys_platform != "emscripten"
|
|
37
|
+
Requires-Dist: bottleneck; sys_platform != "emscripten"
|
|
38
|
+
Requires-Dist: dash; sys_platform != "emscripten"
|
|
39
|
+
Requires-Dist: dash-bootstrap-components; sys_platform != "emscripten"
|
|
40
|
+
Requires-Dist: dash-interactive-graphviz; sys_platform != "emscripten"
|
|
41
|
+
Requires-Dist: plotly; sys_platform != "emscripten"
|
|
42
|
+
Requires-Dist: jupyter; sys_platform != "emscripten"
|
|
39
43
|
Provides-Extra: shiny
|
|
40
44
|
Requires-Dist: shiny; extra == "shiny"
|
|
41
45
|
Requires-Dist: shinywidgets; extra == "shiny"
|
|
@@ -73,6 +73,11 @@ from typing import Iterable
|
|
|
73
73
|
import zipfile
|
|
74
74
|
from functools import partial,cached_property,lru_cache
|
|
75
75
|
from tqdm.auto import tqdm
|
|
76
|
+
if sys.platform == 'emscripten':
|
|
77
|
+
# a browser has no threads, so tqdm can not start its monitor thread. Set on the
|
|
78
|
+
# base class, which tqdm.auto and tqdm.notebook inherit it from
|
|
79
|
+
import tqdm as tqdm_module
|
|
80
|
+
tqdm_module.tqdm.monitor_interval = 0
|
|
76
81
|
import operator
|
|
77
82
|
from urllib.request import urlopen
|
|
78
83
|
from io import BytesIO,StringIO
|
|
@@ -4093,8 +4098,10 @@ class Graph_Draw_Mixin():
|
|
|
4093
4098
|
self.att_dic = {}
|
|
4094
4099
|
|
|
4095
4100
|
G = nx.DiGraph()
|
|
4096
|
-
# a set, to weed out multiple links
|
|
4097
|
-
|
|
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}))
|
|
4098
4105
|
|
|
4099
4106
|
for v in G.nodes:
|
|
4100
4107
|
var_name = v.split('(')[0]
|
|
@@ -4130,14 +4137,18 @@ class Graph_Draw_Mixin():
|
|
|
4130
4137
|
else:
|
|
4131
4138
|
G.nodes[v]['layer'] = 0 if down_len is None else down_len
|
|
4132
4139
|
else:
|
|
4133
|
-
# no variable in the center, like drawmodel, then the layer is the
|
|
4134
|
-
# order.
|
|
4135
|
-
#
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
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
|
|
4141
4152
|
|
|
4142
4153
|
for child, parent in G.edges:
|
|
4143
4154
|
try:
|
|
@@ -4155,6 +4166,43 @@ class Graph_Draw_Mixin():
|
|
|
4155
4166
|
|
|
4156
4167
|
return G
|
|
4157
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
|
+
|
|
4158
4206
|
@staticmethod
|
|
4159
4207
|
def nx_layout(G, sort=True):
|
|
4160
4208
|
'''Positions for a graph made by alllinks_to_nx
|
|
@@ -4246,7 +4294,10 @@ class Graph_Draw_Mixin():
|
|
|
4246
4294
|
width=[G.edges[e]['width'] for e in edgelist],
|
|
4247
4295
|
edge_color='grey', arrows=True, arrowstyle='-|>',
|
|
4248
4296
|
arrowsize=12, node_size=1200,
|
|
4249
|
-
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)
|
|
4250
4301
|
for v, (x, y) in pos.items():
|
|
4251
4302
|
ax.text(x, y, G.nodes[v]['label'], ha='center', va='center',
|
|
4252
4303
|
fontsize=fontsize, color='blue', zorder=3,
|
|
@@ -4279,7 +4330,7 @@ class Graph_Draw_Mixin():
|
|
|
4279
4330
|
:browser: open the svg in a separate browser window, where it can be zoomed
|
|
4280
4331
|
'''
|
|
4281
4332
|
fontsize = kwargs.get('fontsize', 12)
|
|
4282
|
-
pad,
|
|
4333
|
+
pad, gap_y = 6, 22
|
|
4283
4334
|
char_width = 0.62 * fontsize # the labels are drawn in a monospace font
|
|
4284
4335
|
pos = self.nx_layout(G, sort=kwargs.get('sort', True))
|
|
4285
4336
|
|
|
@@ -4291,14 +4342,20 @@ class Graph_Draw_Mixin():
|
|
|
4291
4342
|
box = {v: (len(G.nodes[v]['label']) * char_width + 2 * pad, fontsize + 2 * pad)
|
|
4292
4343
|
for v in G.nodes}
|
|
4293
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
|
+
|
|
4294
4350
|
# the nodes are placed in columns by layer, in the order found by nx_layout
|
|
4295
4351
|
layers = defaultdict(list)
|
|
4296
4352
|
for v in sorted(G.nodes, key=lambda v: pos[v][1]):
|
|
4297
4353
|
layers[G.nodes[v]['layer']].append(v)
|
|
4298
4354
|
|
|
4299
|
-
center, x = {}, gap_x
|
|
4355
|
+
center, colwidths, x = {}, {}, gap_x
|
|
4300
4356
|
for lev in sorted(layers):
|
|
4301
4357
|
colwidth = max(box[v][0] for v in layers[lev])
|
|
4358
|
+
colwidths[lev] = colwidth
|
|
4302
4359
|
colheight = sum(box[v][1] + gap_y for v in layers[lev]) - gap_y
|
|
4303
4360
|
y = -colheight / 2.
|
|
4304
4361
|
for v in layers[lev]:
|
|
@@ -4312,9 +4369,9 @@ class Graph_Draw_Mixin():
|
|
|
4312
4369
|
width = x
|
|
4313
4370
|
height = max(cy + box[v][1] / 2. for v, (cx, cy) in center.items()) + gap_y
|
|
4314
4371
|
|
|
4315
|
-
def border(v,
|
|
4316
|
-
'''The point where the line from v towards
|
|
4317
|
-
|
|
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]
|
|
4318
4375
|
w, h = box[v]
|
|
4319
4376
|
dx, dy = ox - cx, oy - cy
|
|
4320
4377
|
if not dx and not dy:
|
|
@@ -4322,21 +4379,51 @@ class Graph_Draw_Mixin():
|
|
|
4322
4379
|
scale = min(w / 2. / abs(dx) if dx else 1e9, h / 2. / abs(dy) if dy else 1e9)
|
|
4323
4380
|
return cx + dx * scale, cy + dy * scale
|
|
4324
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
|
+
|
|
4325
4401
|
arrow = f'arrow{abs(hash(navn)) % 100000}' # an id of its own for each drawing
|
|
4326
4402
|
lines = []
|
|
4327
4403
|
for child, parent in G.edges:
|
|
4328
4404
|
edge = G.edges[child, parent]
|
|
4329
4405
|
if not edge.get('visible', True):
|
|
4330
4406
|
continue
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
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)
|
|
4334
4418
|
# stop short of the box, so the arrow head is not drawn on top of it
|
|
4335
|
-
|
|
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.
|
|
4336
4422
|
lines.append(
|
|
4337
|
-
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" '
|
|
4338
4425
|
f'stroke-width="{max(1., edge["width"]):.1f}" marker-end="url(#{arrow})">'
|
|
4339
|
-
f'<title>{esc(edge["tooltip"])}</title></
|
|
4426
|
+
f'<title>{esc(edge["tooltip"])}</title></path>')
|
|
4340
4427
|
|
|
4341
4428
|
nodes = []
|
|
4342
4429
|
for v, (cx, cy) in center.items():
|
|
@@ -4350,11 +4437,24 @@ class Graph_Draw_Mixin():
|
|
|
4350
4437
|
f'{esc(G.nodes[v]["label"])}</text></g>')
|
|
4351
4438
|
|
|
4352
4439
|
title = kwargs.get('title', navn)
|
|
4353
|
-
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" '
|
|
4354
4441
|
f'font-size="{fontsize + 2}" fill="black">{esc(title)}</text>') if title else ''
|
|
4355
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'
|
|
4356
4456
|
svg = (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width:.0f} {height:.0f}" '
|
|
4357
|
-
f'width="{
|
|
4457
|
+
f'width="{svgwidth}" style="{style}">'
|
|
4358
4458
|
f'<defs><marker id="{arrow}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" '
|
|
4359
4459
|
'markerHeight="6" orient="auto-start-reverse">'
|
|
4360
4460
|
'<path d="M 0 0 L 10 5 L 0 10 z" fill="grey"/></marker></defs>'
|
|
@@ -4376,7 +4476,8 @@ class Graph_Draw_Mixin():
|
|
|
4376
4476
|
print(f'No separate window here: {e}')
|
|
4377
4477
|
print(f'The drawing is saved as {svgname} and is shown below')
|
|
4378
4478
|
|
|
4379
|
-
|
|
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>'))
|
|
4380
4481
|
self.last_svg = svg
|
|
4381
4482
|
return
|
|
4382
4483
|
|
|
@@ -6317,36 +6418,41 @@ class Display_Mixin():
|
|
|
6317
6418
|
from IPython.display import display, Markdown, HTML
|
|
6318
6419
|
from pathlib import Path
|
|
6319
6420
|
display(Markdown(text))
|
|
6421
|
+
rows = []
|
|
6320
6422
|
for dir in sorted(Path(folder).glob('**')):
|
|
6321
6423
|
# print(f'{dir=} {nocwd=}')
|
|
6322
6424
|
if len(dir.parts) and str(dir.parts[-1]).startswith('.'):
|
|
6323
6425
|
continue
|
|
6324
|
-
|
|
6426
|
+
|
|
6325
6427
|
if dir == Path('.') and nocwd :
|
|
6326
6428
|
continue
|
|
6327
|
-
|
|
6328
|
-
filelist = (list(dir.glob('*readme.ipynb'))
|
|
6329
|
-
+ [f for f in sorted(dir.glob('*.ipynb'))
|
|
6429
|
+
|
|
6430
|
+
filelist = (list(dir.glob('*readme.ipynb'))
|
|
6431
|
+
+ [f for f in sorted(dir.glob('*.ipynb'))
|
|
6330
6432
|
if not f.stem.endswith('readme')])
|
|
6331
|
-
|
|
6332
|
-
for i, notebook in enumerate(filelist):
|
|
6333
|
-
# print(notebook)
|
|
6334
|
-
if (not all) and (notebook.name.startswith('test') or notebook.name.startswith('Overview')):
|
|
6335
|
-
continue
|
|
6336
|
-
if i == 0:
|
|
6337
|
-
blanks = ''.join(
|
|
6338
|
-
[' ']*len(dir.parts))
|
|
6339
|
-
if len(dir.parts):
|
|
6340
|
-
display(HTML(f'{blanks}<b>{str(dir)}</b>'))
|
|
6341
|
-
else:
|
|
6342
|
-
display(
|
|
6343
|
-
HTML(f'{blanks}<b>{str(Path.cwd().parts[-1])} (.)</b>'))
|
|
6344
6433
|
|
|
6345
|
-
|
|
6434
|
+
if not all:
|
|
6435
|
+
filelist = [f for f in filelist
|
|
6436
|
+
if not (f.name.startswith('test') or f.name.startswith('Overview'))]
|
|
6437
|
+
|
|
6438
|
+
if not filelist:
|
|
6439
|
+
continue
|
|
6440
|
+
|
|
6441
|
+
indent = 2 * len(dir.parts) # indentation of this folder in em
|
|
6442
|
+
folder_name = str(dir) if len(dir.parts) else f'{str(Path.cwd().parts[-1])} (.)'
|
|
6443
|
+
rows.append(
|
|
6444
|
+
f'<tr><td colspan="2" style="text-align:left;padding:6px 12px 2px {indent}em">'
|
|
6445
|
+
f'<b>{folder_name}</b></td></tr>')
|
|
6446
|
+
|
|
6447
|
+
for notebook in filelist:
|
|
6346
6448
|
# print(notebook)
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6449
|
+
name = notebook.name.split('.')[0]
|
|
6450
|
+
title = Display_Mixin._notebook_title(notebook)
|
|
6451
|
+
rows.append(
|
|
6452
|
+
f'<tr><td style="text-align:left;padding:2px 24px 2px {indent+2}em;white-space:nowrap">'
|
|
6453
|
+
f'<a href="{notebook}" target="_blank">{name}</a></td>'
|
|
6454
|
+
f'<td style="text-align:left;padding:2px 12px">{title}</td></tr>')
|
|
6455
|
+
|
|
6350
6456
|
# try:
|
|
6351
6457
|
# # Replace with your method of opening a notebook
|
|
6352
6458
|
# # Example: Using jupyter command-line to open the notebook
|
|
@@ -6354,12 +6460,43 @@ class Display_Mixin():
|
|
|
6354
6460
|
# subprocess.Popen(["jupyter", "notebook", str(notebook)])
|
|
6355
6461
|
# except Exception as e:
|
|
6356
6462
|
# print(f"Error opening notebook {notebook}: {e}")
|
|
6463
|
+
if rows:
|
|
6464
|
+
display(HTML(
|
|
6465
|
+
'<table style="border-collapse:collapse;border:none">'
|
|
6466
|
+
+ ''.join(rows) + '</table>'))
|
|
6357
6467
|
|
|
6358
6468
|
|
|
6469
|
+
@staticmethod
|
|
6470
|
+
def _notebook_title(fname):
|
|
6471
|
+
'''Returns the first line starting with one or more # in the first cell of a jupyter notebook.
|
|
6472
|
+
|
|
6473
|
+
Returns an empty string if the notebook can not be read or no such line is found'''
|
|
6474
|
+
|
|
6475
|
+
import json
|
|
6476
|
+
from html import escape
|
|
6477
|
+
|
|
6478
|
+
try:
|
|
6479
|
+
with open(fname, 'rt', encoding='utf-8') as f:
|
|
6480
|
+
nb = json.load(f)
|
|
6481
|
+
cells = nb.get('cells', [])
|
|
6482
|
+
if not cells:
|
|
6483
|
+
return ''
|
|
6484
|
+
source = cells[0].get('source', '')
|
|
6485
|
+
lines = source.splitlines() if isinstance(source, str) else source
|
|
6486
|
+
for line in lines:
|
|
6487
|
+
stripped = line.strip()
|
|
6488
|
+
if stripped.startswith('#'):
|
|
6489
|
+
return escape(stripped.lstrip('#').strip())
|
|
6490
|
+
except Exception:
|
|
6491
|
+
...
|
|
6492
|
+
return ''
|
|
6493
|
+
|
|
6359
6494
|
@staticmethod
|
|
6360
6495
|
def display_toc_this(pat='*',text='**Jupyter notebooks**',path='.',ext='ipynb',showext=False):
|
|
6361
|
-
|
|
6362
|
-
'''In a jupyter notebook this function displays a clickable table of content in the folder pat with name in path
|
|
6496
|
+
|
|
6497
|
+
'''In a jupyter notebook this function displays a clickable table of content in the folder pat with name in path
|
|
6498
|
+
|
|
6499
|
+
For notebooks the first markdown header in the first cell is shown beside the name'''
|
|
6363
6500
|
|
|
6364
6501
|
from IPython.display import display, Markdown, HTML
|
|
6365
6502
|
from pathlib import Path
|
|
@@ -6367,10 +6504,18 @@ class Display_Mixin():
|
|
|
6367
6504
|
display(Markdown(text))
|
|
6368
6505
|
dir = Path(path)
|
|
6369
6506
|
# print(dir,':')
|
|
6507
|
+
rows = []
|
|
6370
6508
|
for fname in sorted(dir.glob(pat+'.'+ext)):
|
|
6371
6509
|
name = fname.name if showext else fname.name.split('.')[0]
|
|
6510
|
+
title = Display_Mixin._notebook_title(fname) if ext == 'ipynb' else ''
|
|
6511
|
+
rows.append(
|
|
6512
|
+
f'<tr><td style="text-align:left;padding:2px 24px 2px 12px;white-space:nowrap">'
|
|
6513
|
+
f'<a href="{fname}" target="_blank">{name}</a></td>'
|
|
6514
|
+
f'<td style="text-align:left;padding:2px 12px">{title}</td></tr>')
|
|
6515
|
+
if rows:
|
|
6372
6516
|
display(HTML(
|
|
6373
|
-
|
|
6517
|
+
'<table style="border-collapse:collapse;border:none">'
|
|
6518
|
+
+ ''.join(rows) + '</table>'))
|
|
6374
6519
|
|
|
6375
6520
|
|
|
6376
6521
|
@staticmethod
|
|
@@ -6423,13 +6568,25 @@ class Display_Mixin():
|
|
|
6423
6568
|
} catch (e) { /* ignore */ }
|
|
6424
6569
|
"""))
|
|
6425
6570
|
|
|
6426
|
-
# Notebook 7 / JupyterLab:
|
|
6427
|
-
#
|
|
6571
|
+
# Notebook 7 / JupyterLab: the height is set on the output area of the
|
|
6572
|
+
# cell: .jp-mod-outputsScrolled .jp-Cell-outputArea {max-height: 24em}
|
|
6573
|
+
# height:auto also undoes a height the user has dragged in place, as an
|
|
6574
|
+
# important declaration wins over an inline style
|
|
6428
6575
|
display(HTML("""
|
|
6429
6576
|
<style id="mf-scroll-off">
|
|
6577
|
+
.jp-mod-outputsScrolled .jp-Cell-outputArea {
|
|
6578
|
+
max-height: none !important;
|
|
6579
|
+
height: auto !important;
|
|
6580
|
+
overflow-y: visible !important;
|
|
6581
|
+
resize: none !important;
|
|
6582
|
+
}
|
|
6430
6583
|
.jp-OutputArea-output.jp-OutputArea-output { max-height: none !important; }
|
|
6431
|
-
|
|
6584
|
+
/* Classic Notebook (<=6) sets height, not max-height, on output_scroll */
|
|
6585
|
+
.output_scroll {
|
|
6586
|
+
height: auto !important;
|
|
6432
6587
|
max-height: none !important;
|
|
6588
|
+
overflow: visible !important;
|
|
6589
|
+
box-shadow: none !important;
|
|
6433
6590
|
}
|
|
6434
6591
|
</style>
|
|
6435
6592
|
"""))
|
|
@@ -6454,11 +6611,12 @@ class Display_Mixin():
|
|
|
6454
6611
|
} catch (e) { /* ignore */ }
|
|
6455
6612
|
"""))
|
|
6456
6613
|
|
|
6457
|
-
# Remove the override
|
|
6614
|
+
# Remove the override stylesheets we injected above, there is one for
|
|
6615
|
+
# each cell where scroll_off has been run
|
|
6458
6616
|
display(Javascript("""
|
|
6459
6617
|
try {
|
|
6460
|
-
|
|
6461
|
-
|
|
6618
|
+
document.querySelectorAll('#mf-scroll-off').forEach(
|
|
6619
|
+
function (s) { s.parentNode.removeChild(s); });
|
|
6462
6620
|
} catch (e) { /* ignore */ }
|
|
6463
6621
|
"""))
|
|
6464
6622
|
except Exception as e:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modelflowib
|
|
3
|
-
Version: 2.
|
|
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
|
|
@@ -20,22 +20,26 @@ Requires-Dist: numpy
|
|
|
20
20
|
Requires-Dist: networkx
|
|
21
21
|
Requires-Dist: tqdm
|
|
22
22
|
Requires-Dist: scipy
|
|
23
|
-
Requires-Dist: numexpr
|
|
24
|
-
Requires-Dist: bottleneck
|
|
25
23
|
Requires-Dist: XlsxWriter
|
|
26
24
|
Requires-Dist: pyarrow
|
|
27
25
|
Requires-Dist: tabulate
|
|
28
26
|
Requires-Dist: openpyxl
|
|
29
27
|
Requires-Dist: xarray
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist: dash
|
|
33
|
-
Requires-Dist: dash-bootstrap-components
|
|
34
|
-
Requires-Dist: dash-interactive-graphviz
|
|
35
|
-
Requires-Dist: lmfit
|
|
36
|
-
Requires-Dist: jupyter
|
|
28
|
+
Requires-Dist: jinja2
|
|
29
|
+
Requires-Dist: ipython
|
|
37
30
|
Requires-Dist: ipywidgets
|
|
38
31
|
Requires-Dist: ipydatagrid
|
|
32
|
+
Requires-Dist: lmfit
|
|
33
|
+
Requires-Dist: statsmodels
|
|
34
|
+
Requires-Dist: numba; sys_platform != "emscripten"
|
|
35
|
+
Requires-Dist: cvxopt; sys_platform != "emscripten"
|
|
36
|
+
Requires-Dist: numexpr; sys_platform != "emscripten"
|
|
37
|
+
Requires-Dist: bottleneck; sys_platform != "emscripten"
|
|
38
|
+
Requires-Dist: dash; sys_platform != "emscripten"
|
|
39
|
+
Requires-Dist: dash-bootstrap-components; sys_platform != "emscripten"
|
|
40
|
+
Requires-Dist: dash-interactive-graphviz; sys_platform != "emscripten"
|
|
41
|
+
Requires-Dist: plotly; sys_platform != "emscripten"
|
|
42
|
+
Requires-Dist: jupyter; sys_platform != "emscripten"
|
|
39
43
|
Provides-Extra: shiny
|
|
40
44
|
Requires-Dist: shiny; extra == "shiny"
|
|
41
45
|
Requires-Dist: shinywidgets; extra == "shiny"
|
|
@@ -6,22 +6,28 @@ numpy
|
|
|
6
6
|
networkx
|
|
7
7
|
tqdm
|
|
8
8
|
scipy
|
|
9
|
-
numexpr
|
|
10
|
-
bottleneck
|
|
11
9
|
XlsxWriter
|
|
12
10
|
pyarrow
|
|
13
11
|
tabulate
|
|
14
12
|
openpyxl
|
|
15
13
|
xarray
|
|
16
|
-
|
|
14
|
+
jinja2
|
|
15
|
+
ipython
|
|
16
|
+
ipywidgets
|
|
17
|
+
ipydatagrid
|
|
18
|
+
lmfit
|
|
19
|
+
statsmodels
|
|
20
|
+
|
|
21
|
+
[:sys_platform != "emscripten"]
|
|
17
22
|
numba
|
|
23
|
+
cvxopt
|
|
24
|
+
numexpr
|
|
25
|
+
bottleneck
|
|
18
26
|
dash
|
|
19
27
|
dash-bootstrap-components
|
|
20
28
|
dash-interactive-graphviz
|
|
21
|
-
|
|
29
|
+
plotly
|
|
22
30
|
jupyter
|
|
23
|
-
ipywidgets
|
|
24
|
-
ipydatagrid
|
|
25
31
|
|
|
26
32
|
[shiny]
|
|
27
33
|
shiny
|
|
@@ -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"
|
|
@@ -18,8 +18,15 @@ classifiers = [
|
|
|
18
18
|
"Operating System :: OS Independent",
|
|
19
19
|
]
|
|
20
20
|
|
|
21
|
-
# PyPI dependencies (pip names).
|
|
21
|
+
# PyPI dependencies (pip names).
|
|
22
|
+
#
|
|
23
|
+
# The ones marked `sys_platform != "emscripten"` have no wasm build, or need a web
|
|
24
|
+
# server, so they can not be installed in a browser (JupyterLite/Pyodide). pip on a
|
|
25
|
+
# desktop installs them as before; micropip in the browser skips them. That makes
|
|
26
|
+
# `piplite.install("modelflowib")` work *with* its dependencies, so a notebook in
|
|
27
|
+
# JupyterLite needs no list of packages of its own.
|
|
22
28
|
dependencies = [
|
|
29
|
+
# --- everywhere ---
|
|
23
30
|
"pandas",
|
|
24
31
|
"matplotlib",
|
|
25
32
|
"seaborn",
|
|
@@ -28,22 +35,27 @@ dependencies = [
|
|
|
28
35
|
"networkx",
|
|
29
36
|
"tqdm",
|
|
30
37
|
"scipy",
|
|
31
|
-
"numexpr",
|
|
32
|
-
"bottleneck",
|
|
33
38
|
"XlsxWriter",
|
|
34
39
|
"pyarrow",
|
|
35
40
|
"tabulate",
|
|
36
41
|
"openpyxl",
|
|
37
42
|
"xarray",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"dash",
|
|
41
|
-
"dash-bootstrap-components",
|
|
42
|
-
"dash-interactive-graphviz",
|
|
43
|
-
"lmfit",
|
|
44
|
-
"jupyter",
|
|
43
|
+
"jinja2", # pandas .style, used by the report and vis tables
|
|
44
|
+
"ipython", # IPython.display, imported by most modules
|
|
45
45
|
"ipywidgets",
|
|
46
46
|
"ipydatagrid",
|
|
47
|
+
"lmfit", # modelestimation, modelestimator_new
|
|
48
|
+
"statsmodels", # modelestimation, modelestimator_new
|
|
49
|
+
# --- not in a browser ---
|
|
50
|
+
"numba; sys_platform != 'emscripten'",
|
|
51
|
+
"cvxopt; sys_platform != 'emscripten'",
|
|
52
|
+
"numexpr; sys_platform != 'emscripten'", # pandas accelerators
|
|
53
|
+
"bottleneck; sys_platform != 'emscripten'",
|
|
54
|
+
"dash; sys_platform != 'emscripten'", # needs a web server
|
|
55
|
+
"dash-bootstrap-components; sys_platform != 'emscripten'",
|
|
56
|
+
"dash-interactive-graphviz; sys_platform != 'emscripten'",
|
|
57
|
+
"plotly; sys_platform != 'emscripten'", # modeldashsidebar
|
|
58
|
+
"jupyter; sys_platform != 'emscripten'", # JupyterLite is the notebook there
|
|
47
59
|
]
|
|
48
60
|
|
|
49
61
|
[project.optional-dependencies]
|
|
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
|