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.
modelnormalize.py ADDED
@@ -0,0 +1,430 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Sat Nov 28 13:32:47 2020
4
+
5
+ This Module is used transforming model specifications to modelflow business language.
6
+
7
+ - preprocessing expressions to resolve functions like
8
+ dlog, log, pct, movavg
9
+ - replace function names
10
+ - normalize formulas
11
+
12
+ @author: bruger
13
+ """
14
+
15
+ from sympy import sympify, solve, Symbol
16
+ import re
17
+ from dataclasses import dataclass, field, asdict
18
+ import dataclasses
19
+ from IPython.display import SVG, display, Image, IFrame, HTML
20
+
21
+
22
+ from modelmanipulation import lagone, find_arg,pastestring,stripstring
23
+ from modelpattern import udtryk_parse, namepat
24
+ from modelhelp import debug_var
25
+
26
+
27
+ @dataclass
28
+ class Normalized_frml:
29
+ '''
30
+ Class defining the result from normalization of an expression.
31
+
32
+ Attributes:
33
+ endo_var (str): The endogenous variable.
34
+ eviews (str): The EViews representation of the expression.
35
+ original (str): The original Business logic expression.
36
+ preprocessed (str): The preprocessed expression.
37
+ normalized (str): The normalized expression.
38
+ calc_add_factor (str): The calculated additional factor.
39
+ un_normalized (str): The un-normalized expression.
40
+ fitted (str): The fitted expression.
41
+ '''
42
+ endo_var : str = ''
43
+ eviews : str = ''
44
+ original : str = ''
45
+ preprocessed : str = ''
46
+ normalized : str = ''
47
+ calc_add_factor : str = ''
48
+ un_normalized : str = ''
49
+ fitted : str = ''
50
+
51
+ def __str__(self):
52
+ maxkey = max(len(k) for k in vars(self).keys())
53
+ output = "\n".join([f'{k.capitalize():<{maxkey}} : {f}' for k,f in vars(self).items() if len(f)])
54
+ return output
55
+
56
+ @property
57
+ def fprint(self):
58
+ print(f'\n{self}')
59
+
60
+ @property
61
+ def fdict(self):
62
+ return dataclasses.asdict(self)
63
+
64
+ # def _repr_html_(self):
65
+ # print(f'{self}')
66
+
67
+ def __repr__(self):
68
+ return f'{self}'
69
+
70
+
71
+
72
+ def endovar(f):
73
+ '''Finds the first variable in a expression'''
74
+ for t in udtryk_parse(f):
75
+ if t.var:
76
+ ud=t.var
77
+ break
78
+ return ud
79
+
80
+
81
+
82
+
83
+ def funk_in(funk,a_string):
84
+ '''Find the first location of a function in a string
85
+
86
+ if found returns a match object where the group 2 is the interesting stuff used in funk_find_arg
87
+ '''
88
+ return re.search(fr'([^A-Z0-9_{{}}]|^)({funk})\(',a_string.upper(),re.MULTILINE)
89
+
90
+ def funk_replace(funk1,funk2,a_string):
91
+ '''
92
+ replace funk1( with funk2(
93
+
94
+ takes care that funk1 embedded in variable name is not replaced
95
+ '''
96
+ return re.sub(fr'(^|[^A-Z0-9_{{}}])({funk1.upper()})\(',
97
+ fr'\1{funk2.upper()}(',a_string.upper(),
98
+ re.MULTILINE)
99
+
100
+ def funk_replace_list(replacelist,a_string):
101
+ '''Replaces a list of funk1( , funk2(
102
+ '''
103
+
104
+ out_string = a_string[:]
105
+ for funk1,funk2 in replacelist:
106
+ out_string = funk_replace(funk1,funk2,out_string)
107
+ return out_string
108
+
109
+
110
+ funk_replace_list([('@D','DIFF'),('DLOG','DXLOG')],'d(a) = @d(v) x = dlog(y)')
111
+ funk_replace_list([('@D','DIFF'),('D','DIFF'),('DLOG','DXLOG')],'d(a) = @d(v) x = dlog(y)')
112
+ funk_in('D','+d(b)'.upper())
113
+
114
+ def funk_find_arg(funk_match, streng):
115
+ ''' chops a string in 3 parts \n
116
+ 1. before 'funk('
117
+
118
+ 2. in the matching parantesis
119
+
120
+ 3. after the last matching parenthesis '''
121
+ # breakpoint()
122
+ start = funk_match.start(2)
123
+ match = streng[funk_match.end(2):]
124
+ open = 0
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
+ return streng[:start], match[1:index], match[index + 1:]
130
+ assert 1==2,f'Parantese mismatch in {streng}'
131
+
132
+
133
+
134
+ def preprocess(udtryk,funks=[]):
135
+ '''
136
+ test processing expanding dlog,diff,movavg,pct,logit functions
137
+
138
+ Args:
139
+ udtryk (str): model we want to do template expansion on
140
+ funks (list, optional): list of user defined functions . Defaults to [].
141
+
142
+ Returns:
143
+ None.
144
+
145
+ has to be changed to (= for when the transition to 3.8 is finished.
146
+
147
+ '''
148
+ udtryk_up = udtryk.upper().replace(' ','')
149
+ while dlog_match := funk_in('DLOG',udtryk_up):
150
+ fordlog,dlogudtryk_up,efterdlog=funk_find_arg(dlog_match,udtryk_up)
151
+ udtryk_up=fordlog+'DIFF(LOG('+dlogudtryk_up+'))'+efterdlog
152
+
153
+ while logit_match := funk_in('LOGIT',udtryk_up):
154
+ forlogit,logitudtryk_up,efterlogit=funk_find_arg(logit_match,udtryk_up)
155
+ udtryk_up=forlogit+'(LOG('+logitudtryk_up+'/(1.0 -'+logitudtryk_up+')))'+efterlogit
156
+
157
+ while movavg_match := funk_in('MOVAVG', udtryk_up):
158
+ forkaede,kaedeudtryk,efterkaede=funk_find_arg(movavg_match,udtryk_up)
159
+ arg=kaedeudtryk.split(',',1)
160
+ avg='(('
161
+ term=arg[0]
162
+ antal=int(arg[1])
163
+ for i in range(antal):
164
+ avg=f'{avg}{term}+'
165
+ term=lagone(term,funks=funks)
166
+ avg=avg[:-1]+')/'+str(antal)+'.0)'
167
+ udtryk_up=forkaede+avg+efterkaede
168
+
169
+ while pct_match := funk_in('PCT_GROWTH',udtryk_up):
170
+ forpc,pcudtryk,efterpc=funk_find_arg(pct_match,udtryk_up)
171
+ udtryk_up=f'{forpc} (100 * ( ({pcudtryk}) / ({lagone(pcudtryk,funks=funks)}) -1)) {efterpc}'
172
+
173
+ while pct_match := funk_in('PCY',udtryk_up):
174
+ forpc,pcudtryk,efterpc=funk_find_arg(pct_match,udtryk_up)
175
+ udtryk_up=f'{forpc} (100 * ( ({pcudtryk}) / ({lagone(pcudtryk,funks=funks,laglead=-4)}) -1)) {efterpc}'
176
+
177
+ while diff_match := funk_in('DIFF' , udtryk_up):
178
+ fordif,difudtryk_up,efterdif=funk_find_arg(diff_match,udtryk_up)
179
+ udtryk_up=fordif+'(('+difudtryk_up+')-('+lagone(difudtryk_up+'',funks=funks)+'))'+efterdif
180
+
181
+ while diff_match := funk_in('D' , udtryk_up):
182
+ # print(f'{udtryk_up} on D')
183
+ fordif,difudtryk_up,efterdif=funk_find_arg(diff_match,udtryk_up)
184
+ difudtryk_up = difudtryk_up.replace(' ','').replace(',0,1','') if difudtryk_up.endswith(',0,1') else difudtryk_up
185
+ udtryk_up=fordif+'(('+difudtryk_up+')-('+lagone(difudtryk_up+'',funks=funks)+'))'+efterdif
186
+
187
+ return udtryk_up
188
+
189
+ def fixleads(eq,check=False):
190
+ leadpat = r'(?:\(([0-9]+)\))'
191
+ this = eq.replace(' ','').replace('\n','')
192
+ res = re.sub(namepat+leadpat,r'\g<1>(+\g<2>)',this)
193
+ if check:
194
+ print(f"Before {this}")
195
+ print(f"After {res}")
196
+ return res
197
+
198
+ def normal(ind_o,the_endo='',add_add_factor=True,do_preprocess = True,add_suffix = '_A',endo_lhs = True,make_fixable =False,make_fitted=False,eviews='',implicit=False):
199
+ '''
200
+ normalize an expression g(y,x) = f(y,x) ==> y = F(x,z)
201
+
202
+ Default find the expression for the first variable on the left hand side (lhs)
203
+
204
+ The variable - without lags- should not be on rhs.
205
+
206
+ Args:
207
+ ind_o (str): input expression, no $ and no frml name just lhs=rhs
208
+ the_endo (str, optional): the endogeneous to isolate on the left hans side. if the first variable in the lhs.
209
+ It shoud be on the left hand side.
210
+ add_add_factor (bool, optional): force introduction aof adjustment term, and an expression to calculate it
211
+ do_preprocess (bool, optional): DESCRIPTION. preprocess the expression
212
+ endo_lhs (bool, optional): If false, accept to normalize for a rhs endogeneous variable
213
+ make_fixable (bool, optional): also make this equation exogenizable
214
+ fitted (bool,optional) : create a fitted equations, without exo and adjustment
215
+ implicit (bool,optional) : This is an implicit frml so to transform to endo__res = (lhs)-(rhs)
216
+
217
+ preprocessing handels
218
+
219
+
220
+ Returns:
221
+ An instance of the class: Normalized_frml which will contain the different relevant expressions
222
+
223
+ '''
224
+ def getclash(f):
225
+ '''Builds a list of variable names from expression. Ensures that sympy is not confused with build in
226
+ names like e '''
227
+
228
+ lhs_var = {t.var for t in udtryk_parse(f) if t.var }
229
+ lhs_var_clash = {var : Symbol(var) for var in lhs_var}
230
+ return lhs_var_clash
231
+ # return {}
232
+ post = '___LAG'
233
+ # breakpoint()
234
+ preprocessed = preprocess(fixleads(ind_o)) if do_preprocess else fixleads(ind_o[:])
235
+ ind = preprocessed.upper().replace('LOG(','log(').replace('EXP(','exp(')
236
+ lhs,rhs=ind.strip().split('=',1)
237
+ lhs = lhs.strip()
238
+ # debug_var(len(udtryk_parse(lhs)))
239
+
240
+ if implicit:
241
+ endo_name = the_endo.upper() if the_endo else endovar(lhs)
242
+ res = f'{endo_name}___res = ( {rhs.strip()} ) - ( {lhs.strip()} )'.upper()
243
+ result = Normalized_frml(
244
+ endo_var=endo_name,
245
+ original=ind_o,
246
+ preprocessed = preprocessed,
247
+ normalized = res ,
248
+ un_normalized= res)
249
+
250
+ return result
251
+
252
+
253
+
254
+
255
+
256
+
257
+ elif len(udtryk_parse(lhs)) >=2 or not endo_lhs : # we have an expression on the left hand side
258
+ try:
259
+ clash = getclash(ind)
260
+
261
+ endo_name = the_endo.upper() if the_endo else endovar(lhs)
262
+ endo = sympify(endo_name,clash)
263
+ a_name = f'{endo_name}{add_suffix}' if add_add_factor else ''
264
+ thiseq = f'({lhs}-(__RHS__ {"+" if add_add_factor else ""}{a_name}))' if endo_lhs else \
265
+ f'({lhs}- ({rhs} {"+" if add_add_factor else ""}{a_name}))'
266
+ # print(thiseq)
267
+
268
+ transeq = pastestring(thiseq,post,onlylags=True).replace('LOG(','log(').replace('EXP(','exp(')
269
+ kat=sympify(transeq,clash)
270
+ # debug_var(endo_name,thiseq,endo_lhs)
271
+
272
+ endo_frml = solve(kat,endo ,simplify=False,rational=False,warn=False)
273
+ # breakpoint()
274
+ res_rhs =stripstring(str(endo_frml[0]),post).replace('__RHS__',f' ({rhs.strip()}) ') if endo_lhs else \
275
+ stripstring(str(endo_frml[0]),post)
276
+ if make_fitted:
277
+ thiseq_fit = f'({lhs}-__RHS__ )' if endo_lhs else \
278
+ f'({lhs}- {rhs} )'
279
+ transeq_fit = pastestring(thiseq_fit,post,onlylags=True).replace('LOG(','log(').replace('EXP(','exp(')
280
+ kat_fit=sympify(transeq_fit,clash)
281
+ # breakpoint()
282
+
283
+ endo_frml_fit = solve(kat_fit,endo ,simplify=False,rational=False,warn=False)
284
+ res_rhs_fit =stripstring(str(endo_frml_fit[0]),post).replace('__RHS__',f' ({rhs.strip()}) ') if endo_lhs else \
285
+ stripstring(str(endo_frml_fit[0]),post)
286
+
287
+ if make_fixable :
288
+ out_frml = f'{endo} = ({res_rhs}) * (1-{endo}_D)+ {endo}_X*{endo}_D '.upper()
289
+ else:
290
+ out_frml = f'{endo} = {res_rhs}'.upper()
291
+
292
+
293
+ if add_add_factor:
294
+ a_sym = sympify(a_name,clash)
295
+ a_frml = solve(kat,a_sym,simplify=False,rational=False)
296
+ res_rhs_a = stripstring(str(a_frml[0]),post).replace('__RHS__',f' (({rhs.strip()})) ')
297
+ out_a = f'{a_name} = {res_rhs_a}'.upper()
298
+ # breakpoint()
299
+ else:
300
+ out_a = ''
301
+
302
+ out_fitted = f'{endo}_fitted = {res_rhs_fit}'.upper() if make_fitted else ''
303
+
304
+ result = Normalized_frml(
305
+ endo_var=str(endo),
306
+ original=ind_o,
307
+ preprocessed=preprocessed,
308
+ normalized=out_frml,
309
+ calc_add_factor=out_a,
310
+ fitted=out_fitted,
311
+ eviews=eviews)
312
+
313
+ return result
314
+ except Exception as e:
315
+ print(f'Problem normalizing:\n{ind}')
316
+ if not endo_lhs:
317
+ print('When trying to normalize a right hand side variable the normalizing is more difficult.')
318
+ print('Especially with user defined functions, that might be the problem \n')
319
+ debug_var(endo_name,thiseq,endo_lhs)
320
+ print('The error message:',e)
321
+
322
+ raise Exception('problem normalizing')
323
+
324
+ else: # no need to normalize this equation
325
+ out_frml = preprocessed
326
+ out_fitted = f'{lhs}_fitted = {rhs}'.upper() if make_fitted else ''
327
+ if add_add_factor:
328
+ if make_fixable :
329
+ result = Normalized_frml(
330
+ endo_var=lhs,
331
+ original=ind_o,
332
+ preprocessed = preprocessed,
333
+ normalized = f'{lhs} = ({rhs} + {lhs}{add_suffix})* (1-{lhs}_D)+ {lhs}_X*{lhs}_D ',
334
+ calc_add_factor=f'{lhs}{add_suffix} = ({lhs}) - ({rhs})',
335
+ fitted=out_fitted,
336
+ eviews=eviews)
337
+ else:
338
+ result = Normalized_frml(
339
+ endo_var=lhs,
340
+ original=ind_o,
341
+ preprocessed = preprocessed,
342
+ normalized =f'{lhs} = ({rhs} + {lhs}{add_suffix}) ',
343
+ calc_add_factor=f'{lhs}{add_suffix} = ({lhs}) - ({rhs})',fitted=out_fitted,
344
+ eviews=eviews)
345
+ else:
346
+ if make_fixable :
347
+ result = Normalized_frml(
348
+ endo_var=lhs,
349
+ original=ind_o,
350
+ preprocessed = preprocessed,
351
+ normalized =f'{lhs} = ({rhs})* (1-{lhs}_D)+ {lhs}_X*{lhs}_D',
352
+ fitted=out_fitted,
353
+ eviews=eviews)
354
+ else:
355
+ result =Normalized_frml(
356
+ endo_var=lhs,
357
+ original=ind_o,
358
+ preprocessed = preprocessed,
359
+ normalized = f'{lhs} = {rhs}',
360
+ fitted=out_fitted,
361
+ eviews=eviews)
362
+
363
+ return result
364
+
365
+ def elem_trans(udtryk, df=None):
366
+ '''Handeles expression with @elem '''
367
+ from modelpattern import namepat
368
+
369
+ def trans_elem(input,number):
370
+ ''' changes @elem( ot elem of parts '''
371
+ def sub_elem(matchobj):
372
+ variable = str(matchobj.group(1))
373
+ out = f'{variable}_value_{number}'
374
+ return out
375
+
376
+ strout = re.sub(namepat,sub_elem,input)
377
+
378
+ return strout
379
+
380
+ def sub_elem(input,number):
381
+ ''' changes @elem( ot elem of parts
382
+ still work in progress this is for pluggin in the actual value from the database '''
383
+ def sub_elem(matchobj):
384
+ variable = str(matchobj.group(1))
385
+ value = df.loc[number,variable]
386
+ out = f'{value}'
387
+ return out
388
+
389
+ strout = re.sub(namepat,sub_elem,input)
390
+
391
+ return strout
392
+
393
+ udtryk_up = udtryk.upper()
394
+ while elem_match := funk_in('@ELEM',udtryk_up):
395
+ forelem,elemudtryk_up,efterelem=funk_find_arg(elem_match,udtryk_up)
396
+ elemtext,elemnumber = elemudtryk_up.replace(' ','').split(',')
397
+ udtryk_up = f'{forelem}({trans_elem(elemtext,elemnumber)}){efterelem}'
398
+
399
+ return udtryk_up
400
+
401
+
402
+ if __name__ == '__main__':
403
+
404
+
405
+ normal('DELRFF=RFF-RFF(-1)',add_add_factor=1,add_suffix= '_AERR').fprint
406
+ normal('a = n(-1)',add_add_factor=0,make_fitted = 1).fprint
407
+ normal('a+b = c',add_add_factor=1,make_fitted=1).fprint
408
+ normal('PCT_growth(a) = n(-1)',add_add_factor=0).fprint
409
+ normal('a = movavg(pct(b),2)',add_add_factor=0).fprint
410
+ normal('pct_growth(c) = pct_growth(d)',add_add_factor=0).fprint
411
+ normal('pct_growth(c) = z+pct(b) + pct(e)').fprint
412
+ normal('pct_growth(c) = z+pct(b) + pct(e)').fprint
413
+ normal('a = pct_growth(b)',add_add_factor=0).fprint
414
+ normal("DLOG(SAUNECONGOVTXN) = -0.323583422052*(LOG(SAUNECONGOVTXN(-1))-GOVSHAREWB*LOG(SAUNEYWRPGOVCN(-1))-(1-GOVSHAREWB)*LOG(SAUNECONPRVTXN(-1)))+0.545415878897*DLOG(SAUNECONGOVTXN(-1))+(1-0.545415878897)*(GOVSHAREWB)*DLOG(SAUNEYWRPGOVCN) +(1-0.545415878897)*(1-GOVSHAREWB)*DLOG(SAUNECONPRVTXN)-1.56254616684-0.0613991001064*@DURING(""2011"")").fprint
415
+ normal("D(a,0,1) = b").fprint
416
+ normal('a = D( LOG(QLHP(+1)), 0, 1 )').fprint
417
+ normal('a = D( LOG(QLHP(+1)))').fprint
418
+ normal('a = gamma+ f+O',the_endo='f',endo_lhs=False,make_fixable =True).fprint
419
+ # breakpoint()
420
+ normal('zlhp-zlhp(-1) = 81 * D( LOG(QLHP(1)) ,0, 1) ',add_add_factor=1,make_fitted=1,make_fixable=1).fprint
421
+ fixleads('zlhp - ddd = 81 * D( LOG(QLHP(1)),0,1) ')
422
+ #%%
423
+
424
+ elem_trans('DLOG(PAKNVRENPRODXN)=DLOG((WLDHYDROPOWER*PAKPANUSATLS)/(@ELEM(WLDHYDROPOWER,2011)*@ELEM(PAKPANUSATLS,2011)))-0.00421833463034*DUMH')
425
+ fixleads('a = b(1) + v(33)'.upper(),1)
426
+ fixleads(' 0.2121303706720161 * D( LOG(QLHP), 0, 1 ) + -0.04133299713432281 * D( LOG(QLHP(1)), 0, 1 ) + 0.9805787292172398 * ZLHP(1) + -0.1948471451936957 * ZLHP(2) ')
427
+ xx = normal('a = n(-1)',add_add_factor=0,make_fitted = 1)
428
+ xx.eviews = 'ffff '
429
+ normal('a_{b} = D( LOG(QLHP_{ee}(+1)), 0, 1 )').fprint
430
+ normal('a =b ',implicit=1,the_endo='B')