modelflowib 2.73__py3-none-any.whl
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.
- modelBLfunk.py +180 -0
- model_Excel.py +332 -0
- model_cvx.py +139 -0
- model_dynare.py +173 -0
- model_financial_stability.py +88 -0
- model_latex.py +497 -0
- model_latex_class.py +808 -0
- model_parquet_mixin.py +424 -0
- modelclass.py +9828 -0
- modelconstruct.py +1496 -0
- modelconstruct_estimation.py +2872 -0
- modeldash.py +265 -0
- modeldashboot.py +202 -0
- modeldashsidebar.py +456 -0
- modeldekom.py +651 -0
- modeldiff.py +561 -0
- modeldisplay.py +550 -0
- modelestimation.py +1776 -0
- modelestimator_new.py +2613 -0
- modelflowib-2.73.dist-info/METADATA +156 -0
- modelflowib-2.73.dist-info/RECORD +44 -0
- modelflowib-2.73.dist-info/WHEEL +5 -0
- modelflowib-2.73.dist-info/licenses/license.md +10 -0
- modelflowib-2.73.dist-info/top_level.txt +39 -0
- modelgrab.py +318 -0
- modelgrabgdx.py +584 -0
- modelgrabwf2.py +1107 -0
- modelhelp.py +543 -0
- modelhtml.py +606 -0
- modelinvert.py +250 -0
- modeljupyter.py +824 -0
- modeljupytermagic.py +813 -0
- modelmacrograb.py +98 -0
- modelmanipulation.py +1461 -0
- modelmf.py +349 -0
- modelnet.py +114 -0
- modelnewton.py +2178 -0
- modelnormalize.py +430 -0
- modelpattern.py +428 -0
- modelreport.py +2187 -0
- modeluserfunk.py +97 -0
- modelvis.py +1038 -0
- modelwidget.py +718 -0
- modelwidget_input.py +1933 -0
model_latex_class.py
ADDED
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on October 2 2022
|
|
4
|
+
Based on model_latex module
|
|
5
|
+
using dataclases
|
|
6
|
+
|
|
7
|
+
@author: hanseni
|
|
8
|
+
|
|
9
|
+
Mostly to eat latex models and translate to business logic
|
|
10
|
+
|
|
11
|
+
The routines are specific to a style of latex and should be inspected before use
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
from IPython.display import display, Math, Latex, Markdown , Image
|
|
15
|
+
import re
|
|
16
|
+
from IPython.lib.latextools import latex_to_png
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
from dataclasses import dataclass,field
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
import modelmanipulation as mp
|
|
24
|
+
from modelclass import model
|
|
25
|
+
import modelpattern as pt
|
|
26
|
+
from modelnormalize import normal
|
|
27
|
+
from modelhelp import debug_var
|
|
28
|
+
|
|
29
|
+
def rebank(model):
|
|
30
|
+
''' All variable names are decorated by a {bank}
|
|
31
|
+
The {bank} is injected as the first dimension '''
|
|
32
|
+
|
|
33
|
+
ypat = re.compile(pt.namepat+pt.lagpat)
|
|
34
|
+
funk = set(pt.funkname) | {'SUM'}
|
|
35
|
+
nobank = set('N S T __{bank} NORM PPF CDF'.split()) # specific variable names not to be decorated
|
|
36
|
+
notouch = funk | nobank # names not to be decorated
|
|
37
|
+
def trans(matchobj):
|
|
38
|
+
''' The function recieves a matchobj entity. The matching groups can be accesed by matchobj.group()
|
|
39
|
+
it returns a string with a bankname added at the end or at the first __ which marks dimensions '''
|
|
40
|
+
var = matchobj.group(1)
|
|
41
|
+
lag = '(' + matchobj.group(2) + ')'if matchobj.group(2) else ''
|
|
42
|
+
if var.upper() in notouch:
|
|
43
|
+
return var+lag
|
|
44
|
+
else:
|
|
45
|
+
if '__' in var:
|
|
46
|
+
pre,post = var.split('__',1)
|
|
47
|
+
post = ''+post
|
|
48
|
+
else:
|
|
49
|
+
pre = var
|
|
50
|
+
post = ''
|
|
51
|
+
|
|
52
|
+
return (pre+'__{bank}'+post + lag)
|
|
53
|
+
|
|
54
|
+
banked = ypat.sub(trans, model)
|
|
55
|
+
return banked
|
|
56
|
+
def addcountry(model,country='KEN'):
|
|
57
|
+
''' All variable names are decorated by a {country name}
|
|
58
|
+
'''
|
|
59
|
+
|
|
60
|
+
ypat = re.compile(pt.namepat+pt.lagpat)
|
|
61
|
+
funk = set(pt.funkname) | {'SUM','MAX'}
|
|
62
|
+
nocountry = set('NORM PPF CDF'.split()) # specific variable names not to be decorated
|
|
63
|
+
notouch = funk | nocountry # names not to be decorated
|
|
64
|
+
def trans(matchobj):
|
|
65
|
+
''' The function recieves a matchobj entity. The matching groups can be accesed by matchobj.group()
|
|
66
|
+
it returns a string with a bankname added at the end or at the first __ which marks dimensions '''
|
|
67
|
+
var = matchobj.group(1)
|
|
68
|
+
lag = '(' + matchobj.group(2) + ')'if matchobj.group(2) else ''
|
|
69
|
+
if var.upper() in notouch:
|
|
70
|
+
return var+lag
|
|
71
|
+
else:
|
|
72
|
+
if '__' in var:
|
|
73
|
+
pre,post = var.split('__',1)
|
|
74
|
+
post = ''+post
|
|
75
|
+
else:
|
|
76
|
+
pre = var
|
|
77
|
+
post = ''
|
|
78
|
+
|
|
79
|
+
return f'{country}{var}{lag}'
|
|
80
|
+
|
|
81
|
+
countryfied = ypat.sub(trans, model)
|
|
82
|
+
return countryfied
|
|
83
|
+
# addcountry('a+b+c')
|
|
84
|
+
|
|
85
|
+
def defrack(streng):
|
|
86
|
+
'''
|
|
87
|
+
\frac{xxx}{yyy} = ((xxx)/(yyy))
|
|
88
|
+
|
|
89
|
+
'''
|
|
90
|
+
tstreng = streng[:]
|
|
91
|
+
tfunks = [r'\frac{',r'\dfrac{']
|
|
92
|
+
for tfunk in tfunks :
|
|
93
|
+
while tfunk in tstreng:
|
|
94
|
+
start = tstreng.find(tfunk)
|
|
95
|
+
# first find the first matching {}
|
|
96
|
+
match = tstreng[start + len(tfunk):] # the rest of the string in which we have to match }
|
|
97
|
+
open = 1 # we already found the first {
|
|
98
|
+
for index1 in range(len(match)):
|
|
99
|
+
if match[index1] in '{}':
|
|
100
|
+
open = (open + 1) if match[index1] == '{' else (open - 1)
|
|
101
|
+
if not open:
|
|
102
|
+
break
|
|
103
|
+
# now find the second matching {}
|
|
104
|
+
match2 = match[index1 + 1+1:] # the string from the location of second { to the end of string
|
|
105
|
+
open=1
|
|
106
|
+
for index2 in range(len(match2)):
|
|
107
|
+
if match2[index2] in '{}':
|
|
108
|
+
open = (open + 1) if match2[index2] == '{' else (open - 1)
|
|
109
|
+
if not open:
|
|
110
|
+
break
|
|
111
|
+
tstreng = tstreng[:start]+ '(('+ match[:index1] +')/('+ match2[:index2]+'))'+match2[index2+1:]
|
|
112
|
+
return tstreng
|
|
113
|
+
|
|
114
|
+
def depower(streng):
|
|
115
|
+
'''
|
|
116
|
+
^{(xxx)} = **(xxx)
|
|
117
|
+
|
|
118
|
+
'''
|
|
119
|
+
tstreng = streng[:]
|
|
120
|
+
tfunk = r'^{'
|
|
121
|
+
while tfunk in tstreng:
|
|
122
|
+
start = tstreng.find(tfunk)
|
|
123
|
+
match = tstreng[start + len(tfunk):]
|
|
124
|
+
open=1
|
|
125
|
+
for index in range(len(match)):
|
|
126
|
+
if match[index] in '{}':
|
|
127
|
+
open = (open + 1) if match[index] == '{' else (open - 1)
|
|
128
|
+
if not open:
|
|
129
|
+
break
|
|
130
|
+
tstreng = tstreng[:start]+ '**('+ match[:index] +')' + match[index+1:]
|
|
131
|
+
return tstreng
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def debrace(streng):
|
|
135
|
+
'''
|
|
136
|
+
Eliminates underbrace{xxx}_{yyy} in a string
|
|
137
|
+
underbrace{xxx}_{yyy} => (xxx)
|
|
138
|
+
As there can be nested {} we need to match the braces
|
|
139
|
+
|
|
140
|
+
'''
|
|
141
|
+
tstreng = streng[:]
|
|
142
|
+
for tfunk in [ r'\underbrace{', r'\overbrace{']:
|
|
143
|
+
# tfunk = r'\underbrace{'
|
|
144
|
+
while tfunk in tstreng:
|
|
145
|
+
start = tstreng.find(tfunk)
|
|
146
|
+
match = tstreng[start + len(tfunk):]
|
|
147
|
+
open = 1
|
|
148
|
+
for index1 in range(len(match)):
|
|
149
|
+
if match[index1] in '{}':
|
|
150
|
+
open = (open + 1) if match[index1] == '{' else (open - 1)
|
|
151
|
+
if not open:
|
|
152
|
+
break
|
|
153
|
+
goodstuf = tstreng[:start]+match[index1]
|
|
154
|
+
match2 = match[index1 + 1+2:]
|
|
155
|
+
open=1
|
|
156
|
+
for index2 in range(len(match2)):
|
|
157
|
+
if match2[index2] in '{}':
|
|
158
|
+
open = (open + 1) if match2[index2] == '{' else (open - 1)
|
|
159
|
+
if not open:
|
|
160
|
+
break
|
|
161
|
+
tstreng = tstreng[:start]+ ''+ match[:index1] +''+ match2[index2+1:]
|
|
162
|
+
# debug_var(tstreng)
|
|
163
|
+
return tstreng
|
|
164
|
+
|
|
165
|
+
def defunk(funk, subs , streng,startp='{',slutp='}'):
|
|
166
|
+
'''
|
|
167
|
+
\funk{xxx} => subs(xxx)
|
|
168
|
+
|
|
169
|
+
in a string
|
|
170
|
+
'''
|
|
171
|
+
tfunk, tstreng = funk[:] , streng[:]
|
|
172
|
+
tfunk = tfunk + startp
|
|
173
|
+
while tfunk in tstreng:
|
|
174
|
+
start = tstreng.find(tfunk)
|
|
175
|
+
match = tstreng[start + len(tfunk):]
|
|
176
|
+
open = 1
|
|
177
|
+
for index in range(len(match)):
|
|
178
|
+
if match[index] in startp+slutp:
|
|
179
|
+
open = (open + 1) if match[index] == startp else (open - 1)
|
|
180
|
+
if not open:
|
|
181
|
+
break
|
|
182
|
+
tstreng = tstreng[:start]+subs+'(' + match[:index] +')' + match[index + 1:]
|
|
183
|
+
return tstreng
|
|
184
|
+
#print(defunk(r'\sqrt',r'sqrt',r'a=\sqrt{b+ \sqrt{f+y}}'))
|
|
185
|
+
#print(defunk(r'\log',r'log',r'a=\log(b(-1)+ \log({f+y}))',startp='(',slutp=')'))
|
|
186
|
+
|
|
187
|
+
def findindex(ind):
|
|
188
|
+
''' find the index variables on the left hand side. meaning variables braced by {} '''
|
|
189
|
+
lhs=ind.split('=')[0]
|
|
190
|
+
return re.findall(r'\{([A-Za-z][\w]*)\}',lhs ) # all the index variables
|
|
191
|
+
|
|
192
|
+
def doableold(ind,show=False):
|
|
193
|
+
''' find all dimensions in the left hand side of = and and decorate with the nessecary do .. enddo '''
|
|
194
|
+
|
|
195
|
+
indexvar = findindex(ind) # all the index variables
|
|
196
|
+
if indexvar :
|
|
197
|
+
pre = ' $ '.join(['Do '+i for level,i in enumerate(indexvar)])+' $ \n '
|
|
198
|
+
post = '\n' + 'enddo $ '*len(indexvar)
|
|
199
|
+
out = pre+ind + post
|
|
200
|
+
if show:
|
|
201
|
+
print('Before doable',ind,sep='\n')
|
|
202
|
+
print('After doable',out,sep='\n')
|
|
203
|
+
print()
|
|
204
|
+
else:
|
|
205
|
+
out=ind
|
|
206
|
+
return out
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def findlists(input):
|
|
210
|
+
'''extracte list with sublist from latex'''
|
|
211
|
+
relevant = re.findall(r'\$LIST\s*\\;\s*[^$]*\$',input.upper())
|
|
212
|
+
# print(f'{relevant=}')
|
|
213
|
+
temp1 = [l.replace('$','').replace('\\','')
|
|
214
|
+
.replace(',',' ').replace(';',' ')
|
|
215
|
+
.replace('{','').replace('}','').replace('\n','/ \n')
|
|
216
|
+
for l in relevant]
|
|
217
|
+
# print(f'\n{temp1=}\n')
|
|
218
|
+
temp2 = ['LIST ' + l.split('=')[0][4:].strip() +' = '
|
|
219
|
+
+ l.split('=')[0][4:]
|
|
220
|
+
+' : '+ l.split('=')[1]+'$' for l in temp1]
|
|
221
|
+
|
|
222
|
+
# print(f'{temp2=}')
|
|
223
|
+
return ('\n'.join(temp2)+'\n')
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def findallindex(ind0):
|
|
228
|
+
'''
|
|
229
|
+
- an equation looks like this
|
|
230
|
+
- <frmlname> [do_condition,...] lhs = rhs
|
|
231
|
+
- indicies are identified as __{} on the left hand side.
|
|
232
|
+
|
|
233
|
+
this function find frmlname and index variables on the left hand side. meaning variables braced by {} '''
|
|
234
|
+
if ind0.startswith('<'):
|
|
235
|
+
frmlname = re.findall(r'\<.*?\>',ind0)[0]
|
|
236
|
+
ind = ind0[ind0.index('>')+1:].strip()
|
|
237
|
+
else:
|
|
238
|
+
frmlname='<>'
|
|
239
|
+
ind=ind0.strip()
|
|
240
|
+
# print(f'{ind=}')
|
|
241
|
+
# breakpoint()
|
|
242
|
+
if ind.startswith('['):
|
|
243
|
+
do_conditions = ind[1:ind.index(']')]
|
|
244
|
+
rest = ind[ind.index(']')+1:].strip()
|
|
245
|
+
|
|
246
|
+
all_do_condition = {condition.strip().split(' ',1)[0].strip() :
|
|
247
|
+
condition.strip().split(' ',1)[1].strip().replace('=',' = ')
|
|
248
|
+
for condition in do_conditions.split(',')}
|
|
249
|
+
# print(f'{do_conditions=}')
|
|
250
|
+
# print(f'{all_do_condition=}')
|
|
251
|
+
else:
|
|
252
|
+
all_do_condition = dict()
|
|
253
|
+
rest = ind.strip()
|
|
254
|
+
|
|
255
|
+
lhs=rest.split('=')[0]
|
|
256
|
+
do_indicies = re.findall(r'__\{([A-Za-z][\w]*)\}',lhs ) # all the index variables
|
|
257
|
+
|
|
258
|
+
do_indicies_dict = {ind : all_do_condition.get(ind) for ind in do_indicies}
|
|
259
|
+
|
|
260
|
+
return frmlname,do_indicies_dict,rest
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def findallindex(ind0): #old
|
|
264
|
+
'''
|
|
265
|
+
- an equation looks like this
|
|
266
|
+
- <frmlname> [do_condition,...] lhs = rhs
|
|
267
|
+
- indicies are identified as __{} on the left hand side.
|
|
268
|
+
|
|
269
|
+
this function find frmlname and index variables on the left hand side. meaning variables braced by {} '''
|
|
270
|
+
if ind0.startswith('<'):
|
|
271
|
+
frmlname = re.findall(r'\<.*?\>',ind0)[0]
|
|
272
|
+
ind = ind0[ind0.index('>')+1:].strip()
|
|
273
|
+
else:
|
|
274
|
+
frmlname='<>'
|
|
275
|
+
ind=ind0.strip()
|
|
276
|
+
# print(f'{ind=}')
|
|
277
|
+
# breakpoint()
|
|
278
|
+
if ind.startswith('['):
|
|
279
|
+
do_conditions = ind[1:ind.index(']')]
|
|
280
|
+
rest = ind[ind.index(']')+1:].strip()
|
|
281
|
+
all_do_condition = {condition.split('=',1)[0] : condition.split('=',1)[1].replace('=',' = ') for condition in do_conditions.split(',')}
|
|
282
|
+
# print(f'{do_conditions=}')
|
|
283
|
+
else:
|
|
284
|
+
all_do_condition = dict()
|
|
285
|
+
rest = ind.strip()
|
|
286
|
+
|
|
287
|
+
lhs=rest.split('=')[0]
|
|
288
|
+
do_indicies = re.findall(r'__\{([A-Za-z][\w]*)\}',lhs ) # all the index variables
|
|
289
|
+
|
|
290
|
+
do_indicies_dict = {ind : all_do_condition.get(ind) for ind in do_indicies}
|
|
291
|
+
|
|
292
|
+
return frmlname,do_indicies_dict,rest
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def do_list(do_index,do_condition):
|
|
296
|
+
''' do do_index_list do_condition = 1 $ '''
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
if do_condition:
|
|
300
|
+
if '=' in do_condition:
|
|
301
|
+
out = f'do {do_index} {do_condition} $'
|
|
302
|
+
else:
|
|
303
|
+
out = f'do {do_index} {do_condition} = 1 $'
|
|
304
|
+
else:
|
|
305
|
+
out = f'do {do_index} $'
|
|
306
|
+
return out
|
|
307
|
+
|
|
308
|
+
def sum_lists(do_index,do_condition):
|
|
309
|
+
''' do do_index_list do_condition = 1 $ '''
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if do_condition:
|
|
313
|
+
if '=' in do_condition:
|
|
314
|
+
out = f' {do_condition}'
|
|
315
|
+
else:
|
|
316
|
+
out = f' {do_condition} = 1'
|
|
317
|
+
else:
|
|
318
|
+
out = f''
|
|
319
|
+
return out
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def doable(ind,funks=[],show=False):
|
|
323
|
+
''' find all dimensions in the left hand side of = and and decorate with the nessecary do .. enddo '''
|
|
324
|
+
|
|
325
|
+
def endovar(f,funks=[]): # Finds the first variable in a expression
|
|
326
|
+
# print(f)
|
|
327
|
+
for t in pt.udtryk_parse(f,funks=funks):
|
|
328
|
+
if t.var:
|
|
329
|
+
ud=t.var
|
|
330
|
+
break
|
|
331
|
+
return ud
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
if show:
|
|
335
|
+
print('\nBefore doable',ind,sep='\n')
|
|
336
|
+
|
|
337
|
+
frmlname,do_indicies_dict,rest = findallindex(ind.upper() ) # all the index variables
|
|
338
|
+
# debug_var(frmlname,do_indicies_dict,rest)
|
|
339
|
+
# breakpoint()
|
|
340
|
+
if show:
|
|
341
|
+
print(f'{do_indicies_dict=}*******************')
|
|
342
|
+
if do_indicies_dict :
|
|
343
|
+
pre = ' '.join([do_list(i,c) for i,c in do_indicies_dict.items() ])
|
|
344
|
+
# print(f'{pre=}*******************')
|
|
345
|
+
|
|
346
|
+
post = 'enddo $ '*len(do_indicies_dict)
|
|
347
|
+
out = [f'{pre}\n frml {frmlname} {rest} $\n{post}']
|
|
348
|
+
else:
|
|
349
|
+
out=[f'frml {frmlname} {rest.strip()} $']
|
|
350
|
+
# print()
|
|
351
|
+
if ind.startswith('<'):
|
|
352
|
+
sumname = mp.kw_frml_name(frmlname, 'sum')
|
|
353
|
+
if sumname:
|
|
354
|
+
sep = ']' if ']' in ind else '>'
|
|
355
|
+
lhs= ind.split(sep,1)[1].split('=',1)[0].strip()
|
|
356
|
+
# debug_var(lhs,)
|
|
357
|
+
lhsvar = endovar(lhs,funks=funks)
|
|
358
|
+
lhsvar_stub = lhsvar.split('{',1)[0]
|
|
359
|
+
sums = ''.join([f'sum({i}{sum_lists(i,c)},' for i,c in do_indicies_dict.items() ])+lhsvar+')'*len(do_indicies_dict)
|
|
360
|
+
out.append(f'frml {frmlname} {lhsvar_stub}{sumname} = {sums} $ \n\n')
|
|
361
|
+
out_str = '\n'.join(out).upper()
|
|
362
|
+
if show:
|
|
363
|
+
print('\nAfter doable',out_str,sep='\n')
|
|
364
|
+
return out_str
|
|
365
|
+
# xx = doable('<sum=_sum,HEST> LOSS__{BANKS}__{SECTORs} =HOLDING__{BANKS}__{SECTORs} * PD__{BANKS}__{SECTORs}'.upper()
|
|
366
|
+
# ,show=True)
|
|
367
|
+
# xx = doable('<sum=abe,HEST> [banks=country = denmark ] LOSS__{BANKS}__{SECTORs} =HOLDING__{BANKS}__{SECTORs} * PD__{BANKS}__{SECTORs}'.upper() ,show=True)
|
|
368
|
+
def normalize_lists(text: str) -> str:
|
|
369
|
+
"""
|
|
370
|
+
Finds all LIST ... $ blocks (possibly spanning multiple lines)
|
|
371
|
+
and rewrites them so that each block is on a single line
|
|
372
|
+
with clean spacing.
|
|
373
|
+
|
|
374
|
+
Will NOT match across another line starting with LIST.
|
|
375
|
+
"""
|
|
376
|
+
pattern = re.compile(
|
|
377
|
+
r'LIST' # start marker
|
|
378
|
+
r'((?:(?!^\s*LIST).)*?)' # content: anything, but stop if line starts with LIST
|
|
379
|
+
r'\$', # end marker
|
|
380
|
+
flags=re.DOTALL | re.MULTILINE
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
def replacer(match):
|
|
384
|
+
content = match.group(1)
|
|
385
|
+
flattened = " ".join(content.split())
|
|
386
|
+
return f"LIST {flattened} "
|
|
387
|
+
|
|
388
|
+
return pattern.sub(replacer, text)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@dataclass
|
|
392
|
+
class a_latex_model:
|
|
393
|
+
'''a model in latex '''
|
|
394
|
+
modeltext : str()
|
|
395
|
+
modelname : str = 'Latexmodel'
|
|
396
|
+
modelequations : str = field(init=False)
|
|
397
|
+
modellists : str = field(init=False)
|
|
398
|
+
model_template : str = field(init=False)
|
|
399
|
+
model_exploded : str = field(init=False)
|
|
400
|
+
|
|
401
|
+
def __post_init__(self):
|
|
402
|
+
self.modelequation_blocks = [(name,block) for name,block in re.findall(r'\\label\{eq:(.*?)\}\n(.*?)\\end\{',self.modeltext,re.DOTALL)
|
|
403
|
+
if not name.endswith('Exclude')] # select the relevant equations
|
|
404
|
+
self.modelequations = [(equation_name,equation)
|
|
405
|
+
for equation_name,block in self.modelequation_blocks
|
|
406
|
+
for equation in [e for e in block.split(r'\\')] ]
|
|
407
|
+
self.modellisttext = findlists(self.modeltext )
|
|
408
|
+
self.modellists = mp.list_extract(self.modellisttext)
|
|
409
|
+
|
|
410
|
+
self.eq_list = [a_latex_equation(equation_name,eq,self.modellists) for equation_name,eq in self.modelequations]
|
|
411
|
+
|
|
412
|
+
self.model_template = '\n'.join(eq.doable_equation for eq in self.eq_list) + '\n' + self.modellisttext
|
|
413
|
+
|
|
414
|
+
self.model_exploded = '\n'.join(eq.exploded for eq in self.eq_list) + '\n' + self.modellisttext
|
|
415
|
+
# print(f'{self.model_exploded=}')
|
|
416
|
+
try:
|
|
417
|
+
self.mmodel = model( self.model_exploded,modelname = self.modelname )
|
|
418
|
+
self.mmodel.equations_original = self.model_template
|
|
419
|
+
except:
|
|
420
|
+
raise Exception
|
|
421
|
+
self.pprint()
|
|
422
|
+
|
|
423
|
+
@property
|
|
424
|
+
def pprint(self):
|
|
425
|
+
print('\nModel in latex\n',self.modeltext)
|
|
426
|
+
print('\nModel before explode\n',self.model_template)
|
|
427
|
+
print('\nModel after explode\n',self.model_exploded)
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def show(self):
|
|
431
|
+
|
|
432
|
+
display(Markdown(self.modeltext))
|
|
433
|
+
try:
|
|
434
|
+
print(f'Model:{name} is created from these segments:\n'+
|
|
435
|
+
f"{temp.join([s for s in globals()[f'{name}_dict'].keys()])} \n")
|
|
436
|
+
except:
|
|
437
|
+
...
|
|
438
|
+
display(Markdown('## Creating this Template model'))
|
|
439
|
+
display(Markdown("```"+self.model_template+"```"))
|
|
440
|
+
display(Markdown('## And this Business Logic Language model'))
|
|
441
|
+
print(self.model_exploded)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
@dataclass
|
|
446
|
+
class a_latex_equation():
|
|
447
|
+
"""
|
|
448
|
+
A class that represents a LaTeX equation and provides methods for transforming it to a modelflow equation.
|
|
449
|
+
|
|
450
|
+
Attributes:
|
|
451
|
+
equation_name (str): The name of the equation.
|
|
452
|
+
original_equation (str): The equation in LaTeX format.
|
|
453
|
+
modellists (list): A list of model objects used in the equation.
|
|
454
|
+
|
|
455
|
+
Methods:
|
|
456
|
+
__post_init__(self): Initializes the object by transforming the original equation, making it "doable",
|
|
457
|
+
and performing calculations on the equation.
|
|
458
|
+
"""
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
equation_name : str = '' # The name
|
|
464
|
+
original_equation : str = '' # an equation in latex
|
|
465
|
+
partial : bool = False
|
|
466
|
+
modellists : list = ''
|
|
467
|
+
|
|
468
|
+
def __post_init__(self):
|
|
469
|
+
|
|
470
|
+
self.transformed_equation = self.straighten_eq(self.original_equation)
|
|
471
|
+
if self.partial:
|
|
472
|
+
return
|
|
473
|
+
# breakpoint()
|
|
474
|
+
self.doable_equation = doable(f'<{self.equation_name}> {self.transformed_equation}',show=0)
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
normalizedeq = mp.normalize(self.doable_equation)
|
|
478
|
+
except:
|
|
479
|
+
print('problem')
|
|
480
|
+
print(self.doable_equation)
|
|
481
|
+
assert 1==2
|
|
482
|
+
self.exploded = normalizedeq
|
|
483
|
+
self.exploded = mp.sumunroll(self.exploded,listin=self.modellists)
|
|
484
|
+
self.exploded = mp.funkunroll(self.exploded ,listin=self.modellists,funk='LMAX',replacefunk='MAX')
|
|
485
|
+
self.exploded = mp.funkunroll(self.exploded ,listin=self.modellists,funk='lMIN',replacefunk='MIN')
|
|
486
|
+
self.exploded = mp.dounloop(self.exploded,listin=self.modellists)
|
|
487
|
+
|
|
488
|
+
@property
|
|
489
|
+
def pprint(self):
|
|
490
|
+
print(f'\nriginal \n{self.original_equation}\n')
|
|
491
|
+
print(f'dooable\n{self.doable_equation}\n')
|
|
492
|
+
print(f'exploded\n{self.transformed_equation}\n')
|
|
493
|
+
|
|
494
|
+
def straighten_eq(self,temp):
|
|
495
|
+
"""
|
|
496
|
+
Given a LaTeX equation string in `temp`, this function processes and converts it to a more standardized format.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
temp (str): A LaTeX equation string to be processed and standardized.
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
str: A processed and standardized version of the input `temp` string.
|
|
503
|
+
|
|
504
|
+
Raises:
|
|
505
|
+
None.
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
if type(temp) == type(None):
|
|
509
|
+
return None
|
|
510
|
+
trans={r'\left':'',
|
|
511
|
+
r'\right':'',
|
|
512
|
+
# r'\min':'min',
|
|
513
|
+
# r'\max':'max',
|
|
514
|
+
r'\rho':'rho',
|
|
515
|
+
r'\alpha':'alpha',
|
|
516
|
+
r'\beta':'beta',
|
|
517
|
+
r'\tau':'tau',
|
|
518
|
+
r'\sigma':'sigma',
|
|
519
|
+
r'\exp':'exp',
|
|
520
|
+
r'&':'',
|
|
521
|
+
r'\\':'',
|
|
522
|
+
r'\nonumber' : '',
|
|
523
|
+
r'\_' : '_',
|
|
524
|
+
r'_{t}' : '',
|
|
525
|
+
r"_t(?![a-zA-Z0-9])" :'',
|
|
526
|
+
'logit^{-1}' : 'logit_inverse',
|
|
527
|
+
r'\{' : '{',
|
|
528
|
+
r'\}' : '}',
|
|
529
|
+
r'\begin{split}' : '',
|
|
530
|
+
'\n' :'',
|
|
531
|
+
r'\forall' :'',
|
|
532
|
+
r'\;' :'',
|
|
533
|
+
r'\:' :'',
|
|
534
|
+
r' ' :' ',
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
}
|
|
538
|
+
ftrans = {
|
|
539
|
+
r'\sqrt':'sqrt',
|
|
540
|
+
r'\Delta':'diff',
|
|
541
|
+
r'\Phi':'NORM.CDF',
|
|
542
|
+
r'\Phi^{-1}':'NORM.PDF'
|
|
543
|
+
}
|
|
544
|
+
regtrans = {
|
|
545
|
+
r'\\Delta ([A-Za-z_][\w{},\^]*)':r'diff(\1)', # \Delta xy => diff(xy)
|
|
546
|
+
r'_{t-([1-9]+)}' : r'(-\1)', # _{t-x} => (-x)
|
|
547
|
+
r'_{t\+([1-9]+)}' : r'(+\1)', # _{t+x} => (+x)
|
|
548
|
+
|
|
549
|
+
# r'\^([\w])' : r'_\1', # ^x => _x
|
|
550
|
+
# r'\^\{([\w]+)\}(\w)' : r'_\1_\2', # ^{xx}y => _xx_y
|
|
551
|
+
r'\^{([\w+-]+)}' : r'__{\1}', # ^{xx} => _xx
|
|
552
|
+
r'\^{([\w+-]+),([\w+-]+)\}' : r'__{\1}__{\2}', # ^{xx,yy} => _xx_yy
|
|
553
|
+
r'\s*\\times\s*':'*' ,
|
|
554
|
+
r'\s*\\cdot\s*':'*' ,
|
|
555
|
+
r'\\text{\[([\w+-,.]+)\]}' : r'[\1]',
|
|
556
|
+
r'\\sum_{('+pt.namepat+r')}\(' : r'sum(\1,',
|
|
557
|
+
r"\\sum_{([a-zA-Z][a-zA-Z0-9_]*)=([a-zA-Z][a-zA-Z0-9_]*)}\(": r'sum(\1 \2=1,',
|
|
558
|
+
|
|
559
|
+
r'\\max_{('+pt.namepat+r')}\(' : r'lmax(\1,',
|
|
560
|
+
r"\\max_{([a-zA-Z][a-zA-Z0-9_]*)=([a-zA-Z][a-zA-Z0-9_]*)}\(": r'lmax(\1 \2=1,',
|
|
561
|
+
r'\\min_{('+pt.namepat+r')}\(' : r'lmin(\1,',
|
|
562
|
+
r"\\min_{([a-zA-Z][a-zA-Z0-9_]*)=([a-zA-Z][a-zA-Z0-9_]*)}\(": r'lmin(\1 \2=1,',
|
|
563
|
+
|
|
564
|
+
}
|
|
565
|
+
# breakpoint()
|
|
566
|
+
try:
|
|
567
|
+
for before,to in ftrans.items():
|
|
568
|
+
temp = defunk(before,to,temp)
|
|
569
|
+
except:
|
|
570
|
+
print(f'{before=} {to=} {temp=}')
|
|
571
|
+
for before,to in trans.items():
|
|
572
|
+
temp = temp.replace(before,to)
|
|
573
|
+
for before,to in regtrans.items():
|
|
574
|
+
temp = re.sub(before,to,temp)
|
|
575
|
+
temp = debrace(temp)
|
|
576
|
+
temp = defrack(temp)
|
|
577
|
+
temp = depower(temp)
|
|
578
|
+
temp = ' '.join(temp.split())
|
|
579
|
+
return temp
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
if __name__ == '__main__' :
|
|
584
|
+
|
|
585
|
+
assert 1==1
|
|
586
|
+
test1 =r'''
|
|
587
|
+
$List \; agegroup= \{16, 17, 18, 19, 20, 99, 100 ,101,102\}$
|
|
588
|
+
$List \; agegroupwww= \{16, 17, 18, 19, 20, 99, 100 \}$
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
\begin{equation}
|
|
592
|
+
\label{eq:mod_another}
|
|
593
|
+
\begin{split}
|
|
594
|
+
\forall [agegroup=agegroup_noend]\;& \underbrace{QC^{agegroup}_t}_{ddd} & &= \left(\dfrac{PCTOT_t}{PCTOT_{t+1}}\right) * QC^{agegroup+1}_{t+1}
|
|
595
|
+
\\
|
|
596
|
+
& & &=
|
|
597
|
+
\dfrac{VB^{agegroup-1}_{t-1} * \dfrac{NPOP^{agegroup-1}_{t-1}}{NPOP^{agegroup}_t}
|
|
598
|
+
* (1+R) + VY^{agegroup}_t - VB^{agegroup}_{t+1} )}{ PCTOT_{t}}
|
|
599
|
+
\end{split}
|
|
600
|
+
\end{equation}
|
|
601
|
+
|
|
602
|
+
\begin{equation}
|
|
603
|
+
\label{eq:mod_another}
|
|
604
|
+
\begin{split}
|
|
605
|
+
\forall [agegroup=agegroup_middle]\;& \underbrace{QCx^{agegroup}_t}_{ddd} & &= \left(\dfrac{PCTOT_t}{PCTOT_{t+1}}\right) * QC^{agegroup+1}_{t+1}
|
|
606
|
+
\\
|
|
607
|
+
& & &=
|
|
608
|
+
\dfrac{VB^{agegroup-1}_{t-1} * \dfrac{NPOP^{agegroup-1}_{t-1}}{NPOP^{agegroup}_t}
|
|
609
|
+
* (1+R) + VY^{agegroup}_t - VB^{agegroup}_{t+1} )}{ PCTOT_{t}}
|
|
610
|
+
\end{split}
|
|
611
|
+
\end{equation}
|
|
612
|
+
|
|
613
|
+
\begin{equation}
|
|
614
|
+
\label{eq:mod_another}
|
|
615
|
+
\begin{split}
|
|
616
|
+
\forall [agegroup=agegroup_end]\;& \underbrace{QCx^{agegroup}_t}_{ddd} & &= \left(\dfrac{PCTOT_t}{PCTOT_{t+1}}\right) * QC^{agegroup+1}_{t+1}
|
|
617
|
+
\\
|
|
618
|
+
& & &=
|
|
619
|
+
\dfrac{VB^{agegroup-1}_{t-1} * \dfrac{NPOP^{agegroup-1}_{t-1}}{NPOP^{agegroup}_t}
|
|
620
|
+
* (1+R) + VY^{agegroup}_t - VB^{agegroup}_{t+1} )}{ PCTOT_{t}}
|
|
621
|
+
\end{split}
|
|
622
|
+
\end{equation}
|
|
623
|
+
|
|
624
|
+
\begin{equation}
|
|
625
|
+
\label{eq:mod_another}
|
|
626
|
+
\begin{split}
|
|
627
|
+
\forall [agegroup=agegroup_start]\;& \underbrace{QCx^{agegroup}_t}_{ddd} & &= \left(\dfrac{PCTOT_t}{PCTOT_{t+1}}\right) * QC^{agegroup+1}_{t+1}
|
|
628
|
+
\\
|
|
629
|
+
& & &=
|
|
630
|
+
\dfrac{VB^{agegroup-1}_{t-1} * \dfrac{NPOP^{agegroup-1}_{t-1}}{NPOP^{agegroup}_t}
|
|
631
|
+
* (1+R) + VY^{agegroup}_t - VB^{agegroup}_{t+1} )}{ PCTOT_{t}}
|
|
632
|
+
\end{split}
|
|
633
|
+
\end{equation}
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
'''
|
|
637
|
+
#%% test
|
|
638
|
+
|
|
639
|
+
this = a_latex_model(test1)
|
|
640
|
+
print('\nOutput\n',this.model_exploded)
|
|
641
|
+
|
|
642
|
+
test2 =r'''
|
|
643
|
+
$List \; agegroup= \{16, 17, 18, 19, 20, 99, 100 ,101,102\}$
|
|
644
|
+
|
|
645
|
+
\begin{equation}
|
|
646
|
+
\label{eq:ddd}
|
|
647
|
+
QC_t = \sum_{agegroup}\right(xx^{agegroup}\left) + 33
|
|
648
|
+
\end{equation}
|
|
649
|
+
|
|
650
|
+
'''
|
|
651
|
+
# this2 = a_latex_model(test2)
|
|
652
|
+
# print('\nOutput\n',this2.model_exploded)
|
|
653
|
+
#%% test2
|
|
654
|
+
test3 =r'''
|
|
655
|
+
|
|
656
|
+
$List \; agegroup= \{0 , 1, 2, 16, 17, 18, 19, 20, 99, 100 \}$
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
\begin{equation}
|
|
660
|
+
\label{eq:mod_another}
|
|
661
|
+
\forall [agegroup.nostart]\; Population^{agegroup}_t = Population^{agegroup-1}_{t-1} - Dead^{agegroup-1}_{t-1} - Emigration^{agegroup-1}_{t-1}
|
|
662
|
+
\end{equation}
|
|
663
|
+
|
|
664
|
+
\begin{equation}
|
|
665
|
+
\label{eq:mod_another2}
|
|
666
|
+
Born_t = \sum_{agegroup}(Fertility^{agegroup}_t \times Population^{agegroup}_t)
|
|
667
|
+
\end{equation}
|
|
668
|
+
|
|
669
|
+
\begin{equation}
|
|
670
|
+
\label{eq:mod_another24}
|
|
671
|
+
Dead^{agegroup} = Dear\_rate^{agegroup}_t*Population^{agegroup}_t)
|
|
672
|
+
\end{equation}
|
|
673
|
+
|
|
674
|
+
\begin{equation}
|
|
675
|
+
\label{eq:mod_another3}
|
|
676
|
+
\forall [agegroup.start]\;Population^{agegroup}_t =^{agegroup}_t = born_t
|
|
677
|
+
\end{equation}
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
\begin{equation}
|
|
681
|
+
\label{eq:mod_another25}
|
|
682
|
+
population = \sum_{agegroup}(Population^{agegroup}_t )
|
|
683
|
+
\end{equation}
|
|
684
|
+
|
|
685
|
+
\begin{equation}
|
|
686
|
+
\label{eq:mod_another26}
|
|
687
|
+
Dead = \sum_{agegroup}(Dead^{agegroup}_t )
|
|
688
|
+
\end{equation}
|
|
689
|
+
|
|
690
|
+
'''
|
|
691
|
+
if 0:
|
|
692
|
+
this3 = a_latex_model(test3)
|
|
693
|
+
print('\nModel before explode\n',this3.model_template)
|
|
694
|
+
print('\nModel after explode\n',this3.model_exploded)
|
|
695
|
+
#this3.mmodel.drawmodel(HR=1,sink='POPULATION',svg=1,browser=1)
|
|
696
|
+
|
|
697
|
+
#%% test ftt
|
|
698
|
+
test4= r'''
|
|
699
|
+
FTT model with limits
|
|
700
|
+
|
|
701
|
+
Two lists of technology are defined:
|
|
702
|
+
|
|
703
|
+
$List \; i = \{Oil, Coal, Gas, Biomass, Solar, Wind, Hydro, Geothermal\} \\
|
|
704
|
+
fosile: \{ 1, 1, 1, 0, 0, 0, 0, 0 \}$
|
|
705
|
+
|
|
706
|
+
$List \; j = \{Oil, Coal, Gas, Biomass, Solar, Wind, Hydro, Geothermal\}$
|
|
707
|
+
|
|
708
|
+
$List \; stage=\{s1, s2,s3\} \\
|
|
709
|
+
stagened:\{ 0, 0, 1,\} $
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
In this example we only use 4 technologies. Any number of technology can be specified (limited by the avaiable memory)
|
|
713
|
+
|
|
714
|
+
Also the time index $_t$ is implicit.
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
### preferences
|
|
718
|
+
|
|
719
|
+
Each technology is compared to all other based on the percieved costs and the preferences (choice likelihood) $F^{i,j}$ are calculated.
|
|
720
|
+
|
|
721
|
+
For all technologies $F^{i,j}+F^{j,i} = 1 $
|
|
722
|
+
|
|
723
|
+
\begin{equation}
|
|
724
|
+
\label{eq:preferences}
|
|
725
|
+
\underbrace{F^{i,j}}_{Preferences} = \frac{1}{
|
|
726
|
+
1+exp(
|
|
727
|
+
\frac{(Cost^{i}-Cost^{j})}{\sigma^{i,j}} )}
|
|
728
|
+
\end{equation}
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
### Share dynamic
|
|
732
|
+
|
|
733
|
+
\begin{equation}
|
|
734
|
+
\label{eq:SHARES2}
|
|
735
|
+
\Delta Share^{i} = \sum_{j}(Share^{i} \times Share^{j} \times
|
|
736
|
+
(\underbrace{F^{i,j}}_{Preferences}/\underbrace{\tau^{j}}_{Life expectancy}
|
|
737
|
+
- F^{j,i}/\tau^{i}))
|
|
738
|
+
\end{equation}
|
|
739
|
+
|
|
740
|
+
\begin{equation}
|
|
741
|
+
\label{eq:SHARES3}
|
|
742
|
+
\forall [i=fosile] \Delta Share2^{i} = Share^{i}
|
|
743
|
+
\end{equation}
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
'''
|
|
747
|
+
if 1:
|
|
748
|
+
this_ftt = a_latex_model(test4)
|
|
749
|
+
this_ftt.pprint
|
|
750
|
+
#%% sum test
|
|
751
|
+
testsum = r'''
|
|
752
|
+
|
|
753
|
+
$List \; i = \{Oil, Coal, Gas, Biomass, Solar, Wind, Hydro, Geothermal\} \\
|
|
754
|
+
fosile: \{ 1, 1, 1, 0, 0, 0, 0, 0 \}$
|
|
755
|
+
$List \; j = \{Oil, Coal, Gas, Biomass, Solar, Wind, Hydro, Geothermal\} \\
|
|
756
|
+
fosile: \{ 1, 1, 1, 0, 0, 0, 0, 0 \}$
|
|
757
|
+
|
|
758
|
+
\begin{equation}
|
|
759
|
+
\label{eq:check_shares}
|
|
760
|
+
Share\_total = \sum_{i=fosile}(Share\_{i}) \\
|
|
761
|
+
a=b
|
|
762
|
+
\end{equation}
|
|
763
|
+
|
|
764
|
+
\begin{equation}
|
|
765
|
+
\label{eq:SHARES3}
|
|
766
|
+
\forall [i=fosile] \Delta Share2^{i} = Share^{i}
|
|
767
|
+
\end{equation}
|
|
768
|
+
|
|
769
|
+
\begin{equation}
|
|
770
|
+
\label{eq:SHARES2}
|
|
771
|
+
\Delta Share^{i} = \sum_{j}(Share^{i} \times Share^{j} \times
|
|
772
|
+
(\frac{F^{i,j}}{\tau^{j}}
|
|
773
|
+
- \frac{F^{j,i}}{\tau^{i}}))
|
|
774
|
+
\end{equation}
|
|
775
|
+
|
|
776
|
+
'''
|
|
777
|
+
if 0:
|
|
778
|
+
msumtest = a_latex_model(testsum)
|
|
779
|
+
msumtest.pprint
|
|
780
|
+
#%% max test
|
|
781
|
+
testmax = r'''
|
|
782
|
+
|
|
783
|
+
$List \; i = \{Oil, Coal, Gas, Biomass, Solar, Wind, Hydro, Geothermal\} \\
|
|
784
|
+
fosile: \{ 1, 1, 1, 0, 0, 0, 0, 0 \}$
|
|
785
|
+
$List \; j = \{Oil, Coal, Gas, Biomass, Solar, Wind, Hydro, Geothermal\} \\
|
|
786
|
+
fosile: \{ 1, 1, 1, 0, 0, 0, 0, 0 \}$
|
|
787
|
+
|
|
788
|
+
\begin{equation}
|
|
789
|
+
\label{eq:check_shares}
|
|
790
|
+
Share\_max = \max_{i=fosile}(Share^{i}) \\
|
|
791
|
+
hest = max(2,3,4)
|
|
792
|
+
\end{equation}
|
|
793
|
+
|
|
794
|
+
\begin{equation}
|
|
795
|
+
\label{eq:SHARES3}
|
|
796
|
+
\forall [i=fosile] \Delta Share2^{i} = Share^{i}
|
|
797
|
+
\end{equation}
|
|
798
|
+
|
|
799
|
+
'''
|
|
800
|
+
|
|
801
|
+
mmaxtest = a_latex_model(testmax)
|
|
802
|
+
mmaxtest.pprint
|
|
803
|
+
|
|
804
|
+
#%% doable test
|
|
805
|
+
doable('[i=i_all,j=j_noend] a__{i}__{j} = x ',show=True);
|
|
806
|
+
|
|
807
|
+
ftest = 'LIST AGEGROUP = AGEGROUP : 16 17 18 19 20 99 100 101 102$ '
|
|
808
|
+
xx = mp.list_extract(ftest)
|