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.
modelmanipulation.py ADDED
@@ -0,0 +1,1461 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Mon Sep 02 19:41:11 2013
4
+
5
+ This module is a textprocessing module which is used to transforms a *template model* for a generic bank into into a unrolled and
6
+ expande model which covers all banks - under control of a list feature.
7
+
8
+ The resulting model can be solved after beeing proccesed in the *modelclass* module.
9
+
10
+ In addition to creating a model for forecasting, the module can also create a model which calculates residulas and and variables
11
+ in historic periods. This model can also be solved by the *modelclass* module.
12
+
13
+
14
+
15
+ @author: Ib
16
+ """
17
+ import re
18
+ from collections import defaultdict
19
+ from collections import namedtuple
20
+ from itertools import groupby,chain
21
+ from sympy import solve, sympify
22
+ import ast
23
+
24
+
25
+
26
+
27
+ from modelpattern import find_statements,split_frml,find_frml,list_extract,udtryk_parse,kw_frml_name,commentchar,split_frml_reqopts
28
+ from modelhelp import debug_var
29
+
30
+
31
+ class oldsafesub(dict):
32
+ '''A subclass of dict.
33
+ if a *safesub* is indexed by a nonexisting keyword it just return the keyword
34
+ this alows missing keywords when substitution text inspired by Python cookbook '''
35
+
36
+ def __missing__(self, key):
37
+ return '{' + key +'}'
38
+
39
+ class safesub(dict):
40
+ '''A subclass of dict.
41
+ if a *safesub* is indexed by a nonexisting keyword it just return the keyword
42
+
43
+ - key-<number> where key is in integer returns the numeric value of key-number
44
+ - key+<number> where key is in integer returns the numeric value of key+number
45
+ -
46
+
47
+ this alows missing keywords when substitution text inspired by Python cookbook '''
48
+
49
+ def __missing__(self, key):
50
+ # breakpoint()
51
+ if '-' in key:
52
+ try:
53
+ testkey,ofset = key.split('-')
54
+ res = f'{str(int(self.get(testkey.strip()))-int(ofset))}'
55
+ return res
56
+
57
+ except:
58
+ print(f'Wrong key or value in list ofset:<{key}>')
59
+ raise
60
+
61
+ if '+' in key:
62
+ try:
63
+ testkey,ofset = key.split('+')
64
+ res = f'{str(int(self.get(testkey.strip()))+int(ofset))}'
65
+ return res
66
+
67
+ except:
68
+ print(f'Wrong key or value in list ofset:<{key}>')
69
+ raise
70
+ return '{' + key +'}'
71
+
72
+
73
+
74
+ def sub(text, katalog):
75
+ '''Substitutes keywords from dictionary by returning
76
+ text.format_map(safesub(katalog))
77
+ Allows missing keywords by using safesub subclass'''
78
+ return text.format_map(safesub(katalog))
79
+
80
+ def oldsub_frml(ibdic, text, plus='', var='', lig='', sep='\n'):
81
+ ''' to repeat substitution from list
82
+
83
+ * *plus* is a seperator, used for creating sums \n
84
+
85
+ * *var* and *lig* Determins for which items the substitution should take place
86
+ by var=abe, lig='ko' substitution is only performed for entries where var=='ko' '''
87
+
88
+
89
+ katalog = dict()
90
+ outlist = []
91
+ try:
92
+ keys = [k for k in ibdic.keys()] # find keywords
93
+ # print('var, lig',var,lig)'
94
+ if var == '':
95
+ values = select = ibdic[keys[0]] # no selection criteri
96
+ elif lig != '':
97
+ # We have a selecton criteria and select the situations where it apply
98
+ values = ibdic[var] # the values for thes sublist
99
+ select = [t for t in values if t == lig]
100
+ for taller, k in enumerate(values): # loop over number of values
101
+ if k in select:
102
+ for j in ibdic.keys(): # for each keywords
103
+ ud = ibdic.get(j)
104
+ # print('*****************test',j,taller)
105
+ katalog[j] = ud[taller]
106
+ # make a list with the new text
107
+ outlist.append(sub(text, katalog) + sep)
108
+ except:
109
+ print('***** problem',ibdic, '<'+text+ '>')
110
+ print(' ')
111
+ return plus.join(outlist)
112
+
113
+ def sub_frml(ibdic, text, plus='', xvar='', lig='', sep='\n'):
114
+ ''' to repeat substitution from list
115
+
116
+ * *plus* is a seperator, used for creating sums \n
117
+
118
+ * *xvar* and *lig* Determins for which items the substitution should take place
119
+ by var=abe, lig='ko' substitution is only performed for entries where var=='ko'
120
+
121
+ * *xvar* is the variable to chek against selected in list
122
+ * *select list* is a list of elements in the xvar list to be included
123
+ * *matc* is the entry in *select list* from which to select from xvar
124
+
125
+
126
+ '''
127
+ # print(f'{text=}')
128
+ # print(f'{ibdic=}')
129
+ katalog = dict()
130
+ outlist = []
131
+ try:
132
+ keys = [k for k in ibdic.keys()] # find keywords
133
+ # print('var, lig',xvar,lig)
134
+ if xvar == '':
135
+ values = select = ibdic[keys[0]] # no selection criteri
136
+ else:
137
+ if xvar not in list(ibdic.keys()):
138
+ raise Exception(f'**** Trying to condition on a sublist which is not there\nCondition:{xvar}\nAllowed sublists:{list(ibdic.keys())}')
139
+ values = ibdic[xvar]
140
+ if lig != '':
141
+ # We have a selecton criteria and select the situations where it apply
142
+ select = [t for t in values if t == lig]
143
+ # print('values:',[t for t in values])
144
+ for taller, k in enumerate(values): # loop over number of values in the list
145
+ katalog={}
146
+ if k in select:
147
+ katalog = {j: ibdic.get(j)[taller] for j in ibdic.keys()}
148
+ outlist.append(sub(text, katalog) + sep)
149
+ # print('>',katalog)
150
+ # print(err1)
151
+ except:
152
+ err1 = f'**** when substitution in formulas:\nDictionary from which values are taken:\n{ibdic}\n{text=} \nSelection:{xvar} == {lig}\n{katalog=}'
153
+ raise Exception(err1)
154
+ return plus.join(outlist)
155
+
156
+ a={'bankdic': {'bank':['Danske','Nordea'],'danske':['yes','no'],'nordisk':['yes','no']}}
157
+
158
+
159
+ def find_res(f):
160
+ ''' Finds the expression which calculates the residual in a formel. FRML <res=a,endo=b> x=a*b+c $ '''
161
+ from sympy import solve, sympify
162
+ a, fr, n, udtryk = split_frml(f.upper())
163
+ udres = ''
164
+ tres = kw_frml_name(n, 'RES')
165
+ if tres:
166
+ lhs, rhs = udtryk.split('=', 1)
167
+ res = lhs.strip() + '_J' if tres == 'J' else lhs.strip() + \
168
+ '_JR' if tres == 'JR' else tres
169
+ # we take the the $ out
170
+ kat = sympify('Eq(' + lhs + ',' + rhs[0:-1] + ')')
171
+ res_frml = sympify('res_frml')
172
+ res_frml = solve(kat, res)
173
+ udres = 'FRML ' + 'RES' + ' ' + \
174
+ res.ljust(25) + ' = ' + str(res_frml)[1:-1] + ' $'
175
+ return udres
176
+
177
+ def find_res_dynare(equations):
178
+ ''' equations to calculat _res formulas
179
+ FRML <> x=a*b+c +x_RES $ -> FRML <> x_res =x-a*b+c $'''
180
+ from sympy import solve, sympify
181
+ out=[]
182
+ for f in find_frml(equations):
183
+ a, fr, n, udtryk = split_frml(f.upper())
184
+ lhs, rhs = udtryk.split('=', 1)
185
+ res= lhs.strip()+'_RES'
186
+ if res in rhs:
187
+ # we take the the $ out
188
+ kat = sympify('Eq(' + lhs + ',' + rhs[0:-1] + ')')
189
+ res_frml = sympify('res_frml')
190
+ res_frml = solve(kat, res)
191
+ udres = 'FRML ' + 'RES' + ' ' + res + ' = ' + str(res_frml)[1:-1] + ' $'
192
+ out.append(udres)
193
+ return '\n'.join(out)
194
+
195
+ def find_res_dynare_new(equations):
196
+ ''' equations to calculat _res formulas
197
+ FRML <> x=a*b+c +x_RES $ -> FRML <> x_res =x-a*b+c $
198
+ not finished to speed time up '''
199
+ out=[]
200
+ for f in find_frml(equations):
201
+ a, fr, n, udtryk = split_frml(f.upper())
202
+ lhs,rhs=udtryk.split('=',1)
203
+ lhs = lhs.strip()
204
+ rhs=rhs.strip()[:-1]
205
+ res= lhs+'_RES'
206
+ if res in rhs:
207
+ rep = rhs.replace(res,'').strip()[:-1]
208
+ new = f'frml <> {res} = {lhs} - ({rep}) $'
209
+ out.append(new)
210
+
211
+ return '\n'.join(out)
212
+
213
+ def find_hist_model(equations):
214
+ ''' takes a unrolled model and create a model which can be run for historic periode \n
215
+ and the identities are also calculeted'''
216
+ hist = []
217
+ for f in find_frml(equations):
218
+ a, fr, n, udtryk = split_frml(f.upper())
219
+ if kw_frml_name(n, 'IDENT') or kw_frml_name(n, 'I'):
220
+ # identites are just replicated in order to calculate backdata
221
+ hist.append(f.strip())
222
+ return('\n'.join(hist))
223
+
224
+
225
+ def exounroll(in_equations):
226
+ ''' takes a model and makes a new model by enhancing frml's with <exo=,j=,jr=>
227
+ in their frml name.
228
+
229
+ :exo: the value can be fixed in to a value valuename_x by setting valuename_d=1
230
+
231
+ :jled: a additiv adjustment element is added to the frml
232
+
233
+ :jrled: a multiplicativ adjustment element is added to the frml
234
+ '''
235
+
236
+ nymodel = []
237
+ equations = in_equations[:] # we want do change the e
238
+ for comment, command, value in find_statements(equations.upper()):
239
+ # print('>>',comment,'<',command,'>',value)
240
+ if comment:
241
+ nymodel.append(comment)
242
+ elif command == 'FRML':
243
+ a, fr, n, udtryk = split_frml((command + ' ' + value).upper())
244
+ #print(fr,n, kw_frml_name(n.upper(),'EXO'))
245
+ # we want a relatov adjustment
246
+ if kw_frml_name(n, 'JRLED') or kw_frml_name(n, 'JR'):
247
+ lhs, rhs = udtryk.split('=', 1)
248
+ udtryk = lhs + \
249
+ '= (' + rhs[:-1] + ')*(1+' + lhs.strip() + '_JR )' + '$'
250
+ if kw_frml_name(n, 'JLED') or kw_frml_name(n, 'J'):
251
+ lhs, rhs = udtryk.split('=', 1)
252
+ # we want a absolute adjustment
253
+ udtryk = lhs + '=' + rhs[:-1] + '+' + lhs.strip() + '_J' + '$'
254
+ if kw_frml_name(n, 'EXO'):
255
+ lhs, rhs = udtryk.split('=', 1)
256
+ endogen = lhs.strip()
257
+ dummy = endogen + '_D'
258
+ exogen = endogen + '_X'
259
+ udtryk = lhs + \
260
+ '=(' + rhs[:-1] + ')*(1-' + dummy + ')+' + exogen + '*' + dummy + '$'
261
+
262
+ nymodel.append(command + ' ' + n + ' ' + udtryk)
263
+ else:
264
+ nymodel.append(command + ' ' + value)
265
+
266
+ equations = '\n'.join(nymodel)
267
+ return equations
268
+
269
+
270
+
271
+ def tofrml(expressions,sep='\n'):
272
+ ''' a function, wich adds FRML to all expressions seperated by <sep>
273
+ if no start is specified the max lag will be used '''
274
+
275
+ notrans = {'DO','ENDDO','LIST','FRML','DOABLE'}
276
+
277
+ def trans(eq):
278
+ test= eq.strip().upper()+' '
279
+ if len(test.strip()) == 0:
280
+ return ''
281
+ if test.split()[0] in notrans:
282
+ return eq + '$'
283
+ elif test[0] == commentchar:
284
+ return eq
285
+ else:
286
+ return ('FRML ' + (eq if test.startswith('<') else ' <> ' +eq) +' $' )
287
+
288
+ # debug_var(expressions)
289
+ if '$' in expressions:
290
+ return expressions
291
+ else:
292
+ # exp = re.sub(commentchar+'!(.*)\n',r'!\1\n',expressions)
293
+ eqlist = [trans(e) for e in expressions.split(sep)]
294
+ # debug_var(expressions.split(sep))
295
+ return '\n'.join(eqlist)
296
+
297
+
298
+ def dounloop(in_equations,listin=False):
299
+ ''' Expands (unrolls do loops in a model template
300
+ goes trough a model template until there is no more nested do loops '''
301
+ equations = in_equations[:].upper() # we want do change the equations
302
+ # print(f'equations \n{equations} \n ')
303
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
304
+ rest = 1
305
+ while rest: # We want to eat all doo loops
306
+ nymodel = []
307
+ domodel = []
308
+ dolevel = 0
309
+ rest = 0
310
+ liste_name, liste_key, liste_select = '', '', ''
311
+ for comment, command, value in find_statements(equations):
312
+ # print('>>',comment,'<',command,'>',value)
313
+ if command.upper() == 'DO':
314
+ dolevel = dolevel + 1
315
+ if dolevel >= 2:
316
+ rest = 1 # do we have more doolops
317
+ domodel.append(command + ' ' + value)
318
+ #=========================================================================
319
+ # else: # find listname key=select
320
+ #=========================================================================
321
+ elif dolevel == 1:
322
+ liste_name = [
323
+ t.strip().upper() for t in re.split(
324
+ r'[\s,]\s*',
325
+ value[:-1]) if t != '']
326
+ current_dict = liste_dict[liste_name[0]]
327
+ # print('listenavn ',liste_name, current_dict)
328
+ #ibdic, text, plus='', xvar='', lig='', sep='\n',selectlist=None,match=''
329
+ if len(liste_name) == 1:
330
+ liste_key, liste_select = '', ''
331
+ elif len(liste_name) == 4:
332
+ liste_key, liste_select = liste_name[1], liste_name[3]
333
+ # current_dict=liste_dict[value[0:-2].strip()] # find the name of the list
334
+ else:
335
+ raise Exception(f' *** error in DO statement either 1 or 4 arguments:\n{comment=}\n{command=}\nArguments= {value}')
336
+ domodel = []
337
+ elif(command.upper() == 'ENDDO'):
338
+ dolevel = dolevel - 1
339
+ if dolevel == 0:
340
+ if len(liste_name) == 1 or len(liste_name) == 4 or len(liste_name) == 5 :
341
+ ibsud = sub_frml(current_dict, '\n'.join(domodel),plus='',sep='\n',
342
+ xvar=liste_key, lig=liste_select)
343
+ else:
344
+ print('Fejl i liste', liste_name)
345
+ print('Ibsud>',ibsud)
346
+ nymodel.append('\n' + ibsud + '\n')
347
+ elif dolevel >= 1:
348
+ domodel.append(command + ' ' + value + '\n')
349
+ elif dolevel >= 1: # a command to store for do looping
350
+ if comment:
351
+ domodel.append(comment + '')
352
+ else:
353
+ domodel.append(command + ' ' + value)
354
+ else: # a command outside a dooloop
355
+ if comment:
356
+ nymodel.append(comment)
357
+ else:
358
+ nymodel.append(command + ' ' + value)
359
+ equations = '\n'.join(nymodel)
360
+ return equations
361
+
362
+ #%
363
+ def find_arg(funk, streng):
364
+ ''' chops a string in 3 parts \n
365
+ 1. before 'funk('
366
+
367
+ 2. in the matching parantesis
368
+
369
+ 3. after the last matching parenthesis '''
370
+ tfunk, tstreng = funk.upper(), streng.upper()
371
+ tfunk = tfunk + '('
372
+ if tfunk in tstreng:
373
+ start = tstreng.find(tfunk)
374
+ match = tstreng[start + len(tfunk):]
375
+ open = 1
376
+ for index in range(len(match)):
377
+ if match[index] in '()':
378
+ open = (open + 1) if match[index] == '(' else (open - 1)
379
+ if not open:
380
+ return tstreng[:start], match[:index], match[index + 1:]
381
+
382
+
383
+ def sumunroll_old(in_equations,listin=False):
384
+ ''' expands all sum(list,'expression') in a model
385
+ returns a new model'''
386
+ nymodel = []
387
+ equations = in_equations[:].upper() # we want do change the e
388
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
389
+ for comment, command, value in find_statements(equations):
390
+ # print('>>',comment,'<',command,'>',value)
391
+ if comment:
392
+ nymodel.append(comment)
393
+ else:
394
+ while 'SUM(' in value.upper():
395
+ forsum, sumudtryk, eftersum = find_arg('sum', value.upper())
396
+ sumover, sumled = sumudtryk.split(',', 1)
397
+ current_dict = liste_dict[sumover]
398
+ ibsud = sub_frml(current_dict, sumled, '+', '', sep='')
399
+ value = forsum + '(' + ibsud + ')' + eftersum
400
+ nymodel.append(command + ' ' + value)
401
+ equations = '\n'.join(nymodel)
402
+ return equations
403
+
404
+ def lagarray_unroll(in_equations,funks=[]):
405
+ ''' expands all sum(list,'expression') in a model
406
+ returns a new model'''
407
+ nymodel = []
408
+ equations = in_equations[:].upper() # we want do change the e
409
+ for comment, command, value in find_statements(equations):
410
+ # print('>>',comment,'<',command,'>',value)
411
+ if comment:
412
+ nymodel.append(comment)
413
+ else:
414
+ while 'LAG_ARRAY(' in value:
415
+ forlag, lagudtryk, efterlag = find_arg('LAG_ARRAY', value.upper())
416
+ lagnumber, lagled = lagudtryk.split(',', 1)
417
+ ibsud = lag_n_tup(lagled,int(lagnumber),funks=funks)
418
+ value = forlag + 'ARRAY([' + ibsud + '])' + efterlag
419
+ nymodel.append(command + ' ' + value)
420
+ equations = '\n'.join(nymodel)
421
+ return equations
422
+
423
+ def sumunroll(in_equations,listin=False):
424
+ ''' expands all sum(list,'expression') in a model
425
+ if sum(list xvar=lig,'expression') only list elements where the condition is
426
+ satisfied wil be summed
427
+
428
+ returns a new model'''
429
+ nymodel = []
430
+ equations = in_equations[:].upper() # we want do change the e
431
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
432
+ for comment, command, value in find_statements(equations):
433
+ # print('>>',comment,'<',command,'>',value)
434
+ if comment:
435
+ nymodel.append(comment)
436
+ else:
437
+ while 'SUM(' in value.upper():
438
+ forsum, sumudtryk, eftersum = find_arg('sum', value.upper())
439
+ suminit, sumled = sumudtryk.split(',', 1)
440
+
441
+ if '=' in suminit:
442
+ sumover,remain = suminit.split(' ',1)
443
+ xvar,lig =remain.replace(' ','').split('=')
444
+ elif ' ' in suminit.strip():
445
+ sumover,remain = suminit.split(' ',1)
446
+ xvar =remain.replace(' ','')
447
+ lig = '1'
448
+ else:
449
+ sumover = suminit
450
+ xvar=''
451
+ lig=''
452
+
453
+ current_dict = liste_dict[sumover.strip()]
454
+ ibsud = sub_frml(current_dict, sumled, '+', xvar=xvar,lig=lig,sep='')
455
+ value = forsum + '(' + ibsud + ')' + eftersum
456
+ nymodel.append(command + ' ' + value)
457
+ equations = '\n'.join(nymodel)
458
+ return equations
459
+
460
+
461
+ def funkunroll(in_equations,funk='MAX',listin=False,replacefunk=''):
462
+ """
463
+ Expands all funk(list, 'expression') in a model.
464
+
465
+ If funk(list xvar=lig, 'expression') is used, only list elements where the condition is
466
+ satisfied will be summed.
467
+
468
+ Parameters:
469
+ in_equations (str): A string representing the model to be modified.
470
+ funk (str, optional): The name of the function to be expanded. Defaults to 'MAX'.
471
+ listin (list, optional): A list of dictionaries representing the lists in the model.
472
+ If not provided, the function will search the model for lists.
473
+ replacefunk (str, optional): The name of the function to replace the expanded funk.
474
+ If not provided, the original name will be used.
475
+
476
+ Returns:
477
+ str: A string representing the modified model.
478
+ """
479
+
480
+ nymodel = []
481
+ equations = in_equations[:].upper() # we want do change the e
482
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
483
+ for comment, command, value in find_statements(equations):
484
+ # print('>>',comment,'<',command,'>',value)
485
+ if comment:
486
+ nymodel.append(comment)
487
+ else:
488
+ hit=False
489
+ while f'{funk.upper()}(' in value.upper():
490
+ hit=True
491
+ forsum, sumudtryk, eftersum = find_arg(f'{funk.upper()}', value.upper())
492
+ suminit, sumled = sumudtryk.split(',', 1)
493
+
494
+ if '=' in suminit:
495
+ sumover,remain = suminit.split(' ',1)
496
+ xvar,lig =remain.replace(' ','').split('=')
497
+ else:
498
+ sumover = suminit
499
+ xvar=''
500
+ lig=''
501
+
502
+ current_dict = liste_dict[sumover.strip()]
503
+ # print(f'{value=}')
504
+ ibsud = sub_frml(current_dict, sumled, ',', xvar=xvar,lig=lig,sep='')
505
+ value = forsum + 'xxxx_funk_ibhansen(' + ibsud + ')' + eftersum # as we dont replace the funk name
506
+ if hit:
507
+ nymodel.append(f'{command} {value}'.replace('xxxx_funk_ibhansen',replacefunk if replacefunk else funk))
508
+ else:
509
+ nymodel.append(command + ' ' + value)
510
+
511
+
512
+ equations = '\n'.join(nymodel)
513
+ # print(equations)
514
+ return equations
515
+
516
+ # print(funkunroll(dounloop(
517
+ # '''list BANKDIC = bank : Danske , Nordea $
518
+ # do BANKDIC $
519
+ # frml x {bank}_income = {bank}_a +{bank}_b $
520
+ # enddo $
521
+ # frml x ialt=max(bankdic,{bank}_income) $''')))
522
+
523
+
524
+ def argunroll(in_equations,listin=False):
525
+ ''' expands all ARGEXPAND(list,'expression') in a model
526
+ returns a new model'''
527
+ nymodel = []
528
+ equations = in_equations[:].upper() # we want do change the e
529
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
530
+ for comment, command, value in find_statements(equations):
531
+ # print('>>',comment,'<',command,'>',value)
532
+ if comment:
533
+ nymodel.append(comment)
534
+ else:
535
+ while 'ARGEXPAND(' in value.upper():
536
+ forsum, sumudtryk, eftersum = find_arg('ARGEXPAND', value.upper())
537
+ sumover, sumled = sumudtryk.split(',', 1)
538
+ current_dict = liste_dict[sumover]
539
+ ibsud = sub_frml(current_dict, sumled, ',', '', sep='')
540
+ value = forsum + '' + ibsud + '' + eftersum
541
+ nymodel.append(command + ' ' + value)
542
+ equations = '\n'.join(nymodel)
543
+ return equations
544
+
545
+ def creatematrix(in_equations,listin=False):
546
+ ''' expands all ARGEXPAND(list,'expression') in a model
547
+ returns a new model'''
548
+ nymodel = []
549
+ equations = in_equations[:].upper() # we want do change the e
550
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
551
+ for comment, command, value in find_statements(equations):
552
+ # print('>>',comment,'<',command,'>',value)
553
+ if comment:
554
+ nymodel.append(comment)
555
+ else:
556
+ while 'TO_MATRIX(' in value.upper():
557
+ forcreate, createudtryk, eftercreate = find_arg('TO_MATRIX', value.upper())
558
+ temp = createudtryk.split(',', 2)
559
+ mat_name = temp[-1]
560
+ create_row = temp[0]
561
+ row_dict = liste_dict[create_row]
562
+ if len(temp)==2:
563
+ ibsud = '['+sub_frml(row_dict, mat_name, ',', '', sep='')+']'
564
+ else:
565
+ create_column= temp[1]
566
+ column_dict = liste_dict[create_column]
567
+ ibsud0 = '['+sub_frml(column_dict, mat_name, ',', '', sep='')+']'
568
+ ibsud = '['+sub_frml(row_dict, ibsud0 , ',\n', '', sep='')+']'
569
+
570
+ value = forcreate + 'matrix(\n' + ibsud + ')' + eftercreate
571
+ nymodel.append(command + ' ' + value)
572
+ equations = '\n'.join(nymodel)
573
+ return equations
574
+ def createarray(in_equations,listin=False):
575
+ ''' expands all to_array(list) in a model
576
+ returns a new model'''
577
+ nymodel = []
578
+ equations = in_equations[:].upper() # we want do change the e
579
+ liste_dict = listin if listin else list_extract(equations) # Search the whold model for lists
580
+ for comment, command, value in find_statements(equations):
581
+ # print('>>',comment,'<',command,'>',value)
582
+ if comment:
583
+ nymodel.append(comment)
584
+ else:
585
+ while 'TO_ARRAY(' in value.upper():
586
+ forcreate, createudtryk, eftercreate = find_arg('TO_ARRAY', value.upper())
587
+ temp = createudtryk.split(',', 2)
588
+ mat_name = temp[-1]
589
+ create_row = temp[0]
590
+ row_dict = liste_dict[create_row]
591
+ if len(temp)==2:
592
+ ibsud = '['+sub_frml(row_dict, mat_name, ',', '', sep='')+']'
593
+ else:
594
+ create_column= temp[1]
595
+ column_dict = liste_dict[create_column]
596
+ ibsud0 = '['+sub_frml(column_dict, mat_name, ',', '', sep='')+']'
597
+ ibsud = '['+sub_frml(row_dict, ibsud0 , ',\n', '', sep='')+']'
598
+
599
+ value = forcreate + 'array(\n' + ibsud + ')' + eftercreate
600
+ nymodel.append(command + ' ' + value)
601
+ equations = '\n'.join(nymodel)
602
+ return equations
603
+
604
+ def kaedeunroll(in_equations,funks=[]):
605
+ ''' unrolls a chain (kaede) expression - used in the SMEC moedel '''
606
+ nymodel=[]
607
+ equations=in_equations[:] # we want do change the e
608
+ # modelprint(equations)
609
+ for comment,command,value in find_statements(equations.upper()):
610
+ # print('>>',comment,'<',command,'>',value)
611
+ if comment:
612
+ nymodel.append(comment)
613
+ elif command=='FRML':
614
+ a,fr,n,udtryk=split_frml((command+' '+value).upper())
615
+ while 'KAEDE(' in udtryk.upper():
616
+ forkaede,kaedeudtryk,efterkaede=find_arg('KAEDE',udtryk)
617
+ arg=kaedeudtryk.split(',')
618
+ taller,navner='(','('
619
+ for i in range(len(arg)/2):
620
+ j=i*2
621
+ taller=taller+arg[j]+'*('+lagone(arg[j+1]+'/'+arg[j],funks=funks)+')+'
622
+ navner=navner+lagone(arg[j+1],funks=funks)+'+'
623
+ navner=navner[:-1]+')'
624
+ taller=taller[:-1]+')'
625
+ #print(taller,'/',navner)
626
+ udtryk=forkaede+'('+taller+'/'+navner+')'+efterkaede
627
+ while 'KAEDEP(' in udtryk.upper():
628
+ forkaede,kaedeudtryk,efterkaede=find_arg('KAEDEP',udtryk)
629
+ arg=kaedeudtryk.split(',')
630
+ #print(arg,len(arg)/2)
631
+ taller,navner='(','('
632
+ for i in range(len(arg)/2):
633
+ j=i*2
634
+ taller=taller+arg[j]+'*'+lagone(arg[j+1],funks=funks)+'+'
635
+ navner=navner+lagone(arg[j],funks=funks)+'+'+lagone(arg[j+1],funks=funks)+'+'
636
+ navner=navner[:-1]+')'
637
+ taller=taller[:-1]+')'
638
+ #print(taller,'/',navner)
639
+ udtryk=forkaede+'('+taller+'/'+navner+')'+efterkaede
640
+ while 'MOVAVG(' in udtryk.upper():
641
+ forkaede,kaedeudtryk,efterkaede=find_arg('MOVAVG',udtryk)
642
+ arg=kaedeudtryk.split(',',1)
643
+ avg='(('
644
+ term=arg[0]
645
+ #print(arg,len(arg)/2)
646
+ # breakpoint()
647
+ antal=int(arg[1])
648
+ for i in range(antal):
649
+ avg=avg[:]+term[:]+'+'
650
+ term=lagone(term,funks=funks)
651
+ avg=avg[:-1]+')/'+str(antal)+'.0)'
652
+ #print(taller,'/',navner)
653
+ udtryk=forkaede+avg+efterkaede
654
+ nymodel.append(command+' '+n+' '+udtryk)
655
+
656
+ else:
657
+ nymodel.append(command+' '+value)
658
+ equations='\n'.join(nymodel)
659
+ return equations
660
+
661
+ def check_syntax_frml(frml):
662
+ ''' check syntax of frml '''
663
+ try:
664
+ a, fr, n, udtryk = split_frml(frml)
665
+ ast.parse(re.sub(r'\n','',re.sub(' ','',udtryk[:-1])))
666
+ return True
667
+ except:
668
+ return False
669
+
670
+ def check_syntax_udtryk(udtryk):
671
+ ''' check syntax of frml '''
672
+ try:
673
+ ast.parse(re.sub(r'\n','',re.sub(' ','',udtryk)))
674
+ return True
675
+ except:
676
+ return False
677
+
678
+ def check_syntax(expression_list, noprint=False, raise_error=True):
679
+ """
680
+ Check the Python syntax of one or more expressions.
681
+
682
+ Parameters
683
+ ----------
684
+ expression_list : str or iterable of str
685
+ A single expression or a collection of expressions to check.
686
+ Each expression is passed to `check_syntax_udtryk_new`.
687
+
688
+ noprint : bool, default False
689
+ If False, syntax errors are printed when found.
690
+ If True, printing is suppressed.
691
+
692
+
693
+ raise_error : bool, default True
694
+ If True, a SyntaxError is raised when one or more syntax errors
695
+ are detected.
696
+ If False, errors are not raised.
697
+
698
+
699
+ Returns
700
+ -------
701
+ None
702
+ This function does not return a value.
703
+
704
+ Notes
705
+ -----
706
+ If `expression_list` is a string, it is treated as a single expression.
707
+ If syntax errors are found, they can be printed and/or raised depending
708
+ on the values of `noprint` and `raise_error`.
709
+ """
710
+ expressions = [expression_list] if isinstance(expression_list, str) else expression_list
711
+
712
+ errlist = []
713
+ for udtryk in expressions:
714
+ ok, text = check_syntax_udtryk_new(udtryk)
715
+ if not ok:
716
+ errlist.append(text)
717
+
718
+ if errlist:
719
+ message = "\n\n".join(errlist)
720
+ if not noprint:
721
+ print(message)
722
+ if raise_error:
723
+ if noprint:
724
+ raise SyntaxError(message)
725
+ else:
726
+ raise SyntaxError() from None
727
+
728
+
729
+ def check_syntax_udtryk_new(udtryk):
730
+ '''Check syntax of an expression, pinpoint errors, and show error location.'''
731
+ try:
732
+ # Parse the original string without modifying it
733
+ ast.parse(udtryk)
734
+ return True, "No syntax errors found."
735
+ except SyntaxError as e:
736
+ # Extract the offending line from the original string
737
+ lines = udtryk.splitlines()
738
+ error_line = lines[e.lineno - 1] if e.lineno and e.lineno <= len(lines) else ""
739
+ marker = " " * (e.offset - 1) + "^" if e.offset else ""
740
+
741
+ # Build the error message with context
742
+ error_message = (
743
+ f"Syntax error at line {e.lineno}, column {e.offset}: {e.msg}\n"
744
+ f"{error_line}\n{marker}"
745
+ )
746
+ return False, error_message
747
+ except Exception as e:
748
+ return False, f"Unexpected error: {e}"
749
+
750
+
751
+ def normalize_a_frml(frml,show=False):
752
+ ''' Normalize and show a frml'''
753
+ new = normalize(frml,sym=True)
754
+ if show:
755
+ print(f'Try normalizing :{frml} ')
756
+ print(f'After normalization :{new} ')
757
+ if not check_syntax_frml(new):
758
+ print(f'** ERROR in :{new} ')
759
+ print(f'**Normalization did not solve the problem in this formula')
760
+
761
+ return new
762
+
763
+ def nomalize_a_model(equations):
764
+ ''' a symbolic normalization is performed if there is a syntaxerror '''
765
+
766
+ out= '\n'.join([frml if check_syntax_frml(frml) else normalize_a_frml(frml,True) for frml in find_frml(equations)])
767
+ return out
768
+
769
+
770
+ def normalize(in_equations,sym=False,funks=[]):
771
+ ''' Normalize an equation with log or several variables at the left hand side, the first variable is considerd the endogeneeus'''
772
+ def findendo(ind):
773
+ ''' finds the equation for the first variable of a string'''
774
+ def endovar(f): # Finds the first variable in a expression
775
+ for t in udtryk_parse(f,funks=funks):
776
+ if t.var:
777
+ ud=t.var
778
+ break
779
+ return ud
780
+ ind=re.sub(r'LOG\(','log(',ind) # sypy uses lover case for log and exp
781
+ ind=re.sub(r'EXP\(','exp(',ind)
782
+ lhs,rhs=ind.split('=',1)
783
+ if len(udtryk_parse(lhs,funks=funks)) >=2: # we have an expression on the left hand side
784
+ # print('Before >>',ind)
785
+ endo=sympify(endovar(lhs))
786
+ kat=sympify('Eq('+lhs+','+rhs[0:-1]+')') # we take the the $ out
787
+ endo_frml=solve(kat,endo,simplify=False,rational=False)
788
+ # print('After >>', str(endo)+'=' + str(endo_frml[0])+'$')
789
+ return str(endo)+'=' + str(endo_frml[0])+'$'
790
+ else: # no need to solve this equation
791
+ return ind
792
+ def normalizesym(in_equations):
793
+ ''' Normalizes equations by using sympy '''
794
+ nymodel=[]
795
+ equations=in_equations[:] # we want do change the e
796
+ # modelprint(equations)
797
+ for comment,command,value in find_statements(equations.upper()):
798
+ # print('>>',comment,'<',command,'>',value)
799
+ if comment:
800
+ nymodel.append(comment)
801
+ elif command=='FRML':
802
+ a,fr,n,udtryk=split_frml((command+' '+value).upper())
803
+ while 'DLOG(' in udtryk.upper():
804
+ fordlog,dlogudtryk,efterdlog=find_arg('dlog',udtryk)
805
+ udtryk=fordlog+'diff(log('+dlogudtryk+'))'+efterdlog
806
+ while 'LOGIT(' in udtryk.upper():
807
+ forlogit,logitudtryk,efterlogit=find_arg('LOGIT',udtryk)
808
+ udtryk=forlogit+'(log('+logitudtryk+'/(1.0 -'+logitudtryk+')))'+efterlogit
809
+ while 'DIFF(' in udtryk.upper():
810
+ fordif,difudtryk,efterdif=find_arg('diff',udtryk)
811
+ udtryk=fordif+'(('+difudtryk+')-('+lagone(difudtryk+'',funks=funks)+'))'+efterdif
812
+ nymodel.append(command+' '+n+' '+findendo(udtryk))
813
+ else:
814
+ nymodel.append(command+' '+value)
815
+ equations='\n'.join(nymodel)
816
+ # modelprint(equations)
817
+ return equations
818
+ def normalizehift(in_equations):
819
+ ''' Normalizes equations by shuffeling terms around \n
820
+ can handel LOG, DLOG and DIF in the left hans side of = '''
821
+ nymodel=[]
822
+ equations=in_equations[:] # we want do change the e
823
+ # modelprint(equations)
824
+ for comment,command,value in find_statements(equations.upper()):
825
+ # print('>>',comment,'<',command,'>',value)
826
+ if comment:
827
+ nymodel.append(comment)
828
+ elif command=='FRML':
829
+ a,fr,n,udtryk=split_frml((command+' '+value).upper())
830
+ lhs,rhs=udtryk.split('=',1)
831
+ lhsterms=udtryk_parse(lhs,funks=funks)
832
+ if len(lhsterms) == 1: # only one term on the left hand side, no need to shuffel around
833
+ pass
834
+ elif len(lhsterms) == 4: # We need to normalixe expect funk(x) on the levt hand side funk=LOG,DLOG or DIFF
835
+ rhs=rhs[:-1].strip() # discharge the $ strip blanks for nice look
836
+ lhs=lhsterms[2].var # name of the dependent variablem, no syntax check here
837
+ if lhsterms[0].op == 'LOG':
838
+ rhs='EXP('+rhs+')'
839
+ udtryk=lhs+'='+rhs+'$' # now the equation is normalized
840
+ elif lhsterms[0].op == 'DIFF':
841
+ rhs=lagone(lhs,funks=funks)+'+('+rhs+')'
842
+ udtryk=lhs+'='+rhs+'$' # now the equation is normalized
843
+ elif lhsterms[0].op == 'DLOG':
844
+ rhs='EXP(LOG('+lagone(lhs,funks=funks)+')+'+rhs+')'
845
+ udtryk=lhs+'='+rhs+'$' # now the equation is normalized
846
+ elif lhsterms[0].op == 'LOGIT':
847
+ rhs='(exp('+rhs+')/(1+EXP('+rhs+')))'
848
+ udtryk=lhs+'='+rhs+'$' # now the equation is normalized
849
+ # else:
850
+ # print('*** ERROR operand not allowed left of =',lhs)
851
+ # print(lhsterms)
852
+ else:
853
+ pass
854
+ # print('*** Try to normalize relation for:',lhs)
855
+ # now the right hand side is expanded for DLOG and DIFF
856
+ while 'DLOG(' in udtryk.upper():
857
+ fordlog,dlogudtryk,efterdlog=find_arg('dlog',udtryk)
858
+ udtryk=fordlog+'diff(log('+dlogudtryk+'))'+efterdlog
859
+ while 'DIFF(' in udtryk.upper():
860
+ fordif,difudtryk,efterdif=find_arg('diff',udtryk)
861
+ udtryk=fordif+'(('+difudtryk+')-('+lagone(difudtryk+'',funks=funks)+'))'+efterdif
862
+ nymodel.append(command+' '+n+' '+udtryk)
863
+ else:
864
+ nymodel.append(command+' '+value)
865
+ equations='\n'.join(nymodel)
866
+ # modelprint(in_equations,'Before normalization and expansion')
867
+ # modelprint(equations, 'After normalization and expansion')
868
+ return equations
869
+ if sym :
870
+ equations1=normalizesym(in_equations)
871
+ else:
872
+ equations1=normalizehift(in_equations)
873
+ #equations2=normalizesym(equations1) # to speed up
874
+ return equations1
875
+
876
+
877
+ def udrul_model(model,norm=True):
878
+
879
+ return explode(model, norm)
880
+
881
+ def explode(model,norm=True,sym=False,funks=[],sep='\n'):
882
+ '''prepares a model from a model template.
883
+
884
+ Returns a expanded model which is ready to solve
885
+
886
+ Eksempel: model = udrul_model(MinModel.txt)'''
887
+ # breakpoint()
888
+ udrullet=tofrml(model,sep=sep)
889
+ modellist = list_extract(udrullet)
890
+ if norm : udrullet = normalize(udrullet,sym,funks=funks ) # to save time if we know that normalization is not needed
891
+ udrullet = lagarray_unroll(udrullet,funks=funks )
892
+ udrullet = exounroll(udrullet) # finaly the exogeneous and adjustment terms - if present - are handled
893
+ udrullet = dounloop(udrullet,listin=modellist) # we unroll the do loops
894
+ # breakpoint()
895
+ udrullet = sumunroll(udrullet,listin=modellist) # then we unroll the sum
896
+ udrullet = creatematrix(udrullet,listin=modellist)
897
+ udrullet = createarray(udrullet,listin=modellist)
898
+ udrullet = argunroll(udrullet,listin=modellist)
899
+
900
+ return udrullet
901
+
902
+ def explode_new(model,norm=True,sym=False,funks=[],sep='\n'):
903
+ '''prepares a model from a model template.
904
+
905
+ Returns a expanded model which is ready to solve
906
+
907
+ Eksempel: model = udrul_model(MinModel.txt)'''
908
+ # breakpoint()
909
+ udrullet=tofrml(model,sep=sep)
910
+ udrullet = doable_unroll(udrullet,funks)
911
+ udrullet = dounloop(udrullet) # we unroll the do loops
912
+ # modellist = list_extract(udrullet)
913
+ # if norm : udrullet = normalize(udrullet,sym,funks=funks ) # to save time if we know that normalization is not needed
914
+ # udrullet = lagarray_unroll(udrullet,funks=funks )
915
+ # udrullet = exounroll(udrullet) # finaly the exogeneous and adjustment terms - if present - are handled
916
+ # # breakpoint()
917
+ # udrullet = sumunroll(udrullet,listin=modellist) # then we unroll the sum
918
+ # udrullet = creatematrix(udrullet,listin=modellist)
919
+ # udrullet = createarray(udrullet,listin=modellist)
920
+ # udrullet = argunroll(udrullet,listin=modellist)
921
+
922
+ return udrullet
923
+
924
+
925
+ def modelprint(ind, title=' A model', udfil='', short=0):
926
+ ''' prettyprinter for a a model.
927
+ :udfil: if present is output file
928
+ :short: if present condences the model
929
+ Can handle both model templates and models '''
930
+ import sys
931
+ f = sys.stdout
932
+ if udfil:
933
+ f = open(udfil, 'w+')
934
+ maxlenfnavn, maxlenlhs, maxlenrhs = 0, 0, 0
935
+ for comment, command, value in find_statements(ind):
936
+ if command.upper() == 'FRML':
937
+ a, fr, n, udtryk = split_frml(command.upper() + ' ' + value)
938
+ lhs, rhs = udtryk.split('=', 1)
939
+ maxlenfnavn = max(maxlenfnavn, len(n)) # Finds the max length of frml name
940
+ maxlenlhs = max(maxlenlhs, len(lhs)) # finds the max length of left hand variable
941
+ print('! '+ title, file=f)
942
+ dolevel = 0
943
+ for comment, command, value in find_statements(ind):
944
+ print(' ' * dolevel, end='', file=f)
945
+ if comment:
946
+ print(comment, file=f)
947
+ else:
948
+ if command.upper() == 'FRML':
949
+ a, fr, n, udtryk = split_frml(
950
+ command.upper() + ' ' + value)
951
+ lhs , rhs = udtryk.split('=', 1)
952
+ if short:
953
+ print(fr.ljust(5),'X',lhs.strip().ljust(maxlenlhs),
954
+ '=', rhs , file=f)
955
+ else:
956
+ rhs=re.sub(' +',' ',rhs)
957
+ print(
958
+ fr.ljust(5),
959
+ n.strip().ljust(maxlenfnavn),
960
+ lhs.strip().ljust(maxlenlhs),
961
+ '=',
962
+ rhs.replace('\n','\n'+(' ' * dolevel)+(' '*(10+maxlenfnavn+maxlenlhs))),
963
+ file=f)
964
+ elif command.upper() == 'DO':
965
+ print(command, ' ', value, file=f)
966
+ dolevel = dolevel + 1
967
+ elif command.upper() == 'ENDDO':
968
+ print(command, ' ', value, file=f)
969
+ dolevel = dolevel - 1
970
+ else:
971
+ print(command, ' ', value, file=f)
972
+
973
+
974
+
975
+ def lagone(ind,funks=[],laglead=-1):
976
+ ''' All variables in a string i
977
+ s lagged one more time '''
978
+ nt=udtryk_parse(ind,funks=funks)
979
+ fib=[]
980
+ for t in nt:
981
+ if t.op:
982
+ ud=t.op
983
+ elif t.number:
984
+ ud=t.number
985
+ elif t.var:
986
+ lag=t.lag if t.lag else '0'
987
+ org_lag = int(lag)
988
+ new_lag = org_lag+laglead
989
+ if new_lag == 0:
990
+ ud = t.var
991
+ else:
992
+ ud= t.var+ f'({new_lag:+})'
993
+ fib.append(ud)
994
+ return ''.join(fib)
995
+
996
+ def lag_n(udtryk,n=1,funks=[],laglead=-1):
997
+ new=udtryk
998
+ for i in range(n):
999
+ new=lagone(new,funks=funks,laglead=laglead)
1000
+ return f'({new})'
1001
+ lag_n('a+b',n=3,laglead=1)
1002
+
1003
+ def lag_n_tup(udtryk,n=-1,funks=[]):
1004
+ ''' return a tuppel og lagged expressions from lag = 0 to lag = n)'''
1005
+ new=udtryk.upper()
1006
+ res=''
1007
+ for i in range(abs(n)):
1008
+ new=lagone(new,funks=funks,laglead=-1 if n < 0 else 1 )
1009
+ res = res + new +','
1010
+ return f'{res[:-1]}'
1011
+ lag_n_tup('a',n=-3)
1012
+
1013
+
1014
+
1015
+ def pastestring(ind,post,funks=[],onlylags = False):
1016
+ ''' All variable names in a in a string **ind** is pasted with the string **post**
1017
+
1018
+ This function can be used to awoid variable name conflict with the internal variable names in sympy.
1019
+
1020
+ an advanced function
1021
+ '''
1022
+ nt=udtryk_parse(ind,funks=funks)
1023
+ fib=[]
1024
+ for t in nt:
1025
+ if t.op:
1026
+ ud=t.op
1027
+ elif t.number:
1028
+ ud=t.number
1029
+ elif t.var:
1030
+ if onlylags:
1031
+ if t.lag:
1032
+ ud= t.var+ post.upper()+ ('('+(str(int(t.lag)))+')' if t.lag else '')
1033
+ else:
1034
+ ud= t.var
1035
+ else:
1036
+ ud= t.var+ post+ ('('+(str(int(t.lag)))+')' if t.lag else '')
1037
+
1038
+ fib.append(ud)
1039
+ return ''.join(fib)
1040
+
1041
+ def stripstring(ind,post,funks=[]):
1042
+ ''' All variable names in a in a string is **ind** is stripped of the string **post**.
1043
+
1044
+ This function reverses the pastestring process
1045
+ '''
1046
+ nt=udtryk_parse(ind,funks=funks)
1047
+ fib=[]
1048
+ lenpost=len(post)
1049
+ for t in nt:
1050
+ if t.op:
1051
+ ud=t.op
1052
+ elif t.number:
1053
+ ud=t.number
1054
+ elif t.var:
1055
+ if t.var.endswith(post.upper()):
1056
+ ud= t.var[:-lenpost]+ ('('+(str(int(t.lag)))+')' if t.lag else '')
1057
+ else:
1058
+ ud= t.var+ ('('+(str(int(t.lag)))+')' if t.lag else '')
1059
+
1060
+ # print('Tries to strip '+post +' from '+ind)
1061
+ fib.append(ud)
1062
+ return ''.join(fib)
1063
+
1064
+
1065
+ def findindex(ind00):
1066
+ ''' find the index variables meaning variables on the left hand side of = braced by {} '''
1067
+ ind0 = ind00.strip()
1068
+ if ind0.startswith('<'):
1069
+ ind = ind0[ind0.index('>')+1:].strip()
1070
+ else:
1071
+ ind = ind0
1072
+ lhs=ind.split('=')[0]
1073
+ return (re.findall(r'\{([A-Za-z][\w]*)\}',lhs )) # all the index variables
1074
+
1075
+ def doablelist(expressions,sep='\n'):
1076
+ ''' create a list of tupels from expressions seperated by sep,
1077
+ each element in the list is a tupel (index, number og expression, the expression)
1078
+
1079
+ we want make group the expressions acording to index
1080
+ index is elements on the left of = braced by {}
1081
+ '''
1082
+
1083
+ lt = [(findindex(l.strip()),i,l.strip()) for i,l in enumerate(expressions.split(sep))]
1084
+ out = sorted(lt,key = lambda x: (x[0],1./(x[1]+1.)),reverse=True)
1085
+ return out
1086
+
1087
+ def dosubst(index,formular):
1088
+ out=formular[:]
1089
+ for nr,i in enumerate(index):
1090
+ assert nr <= 9 , 'To many indeces in'+formular
1091
+ out = re.sub(r'\%'+str(nr),'{'+i+'}',out)
1092
+ out = re.sub(r'\%i','__'.join(['{'+i+'}' for i in index]),out)
1093
+ return out
1094
+
1095
+
1096
+ def doablekeep(formulars):
1097
+ ''' takes index in the lhs and creates a do loop around the lines with same indexes
1098
+ on the right side you can use %0_, %1_ an so on to indicate the index, just to awoid typing to much
1099
+
1100
+ Also %i_ will be changed to all the indexes'''
1101
+
1102
+
1103
+ sep = '$' if '$' in formulars else '\n'
1104
+ xx = doablelist(formulars,sep)
1105
+ out = []
1106
+ listout = []
1107
+ for xxx,ee in groupby(xx,key=lambda x : x[0]):
1108
+ if xxx:
1109
+ pre = ''.join([' '*i+ 'Do '+x+'_list $\n' for i,x in enumerate(xxx)])
1110
+ post = ''.join([' '*(len(xxx)-1-i)+'Enddo $ \n' for i,x in enumerate(xxx)]) +'\n'
1111
+ frml = ''.join([' '*(len(xxx))+dosubst(xxx,e[2])+ ' $ \n' for e in ee ])
1112
+ out.append([pre + frml + post])
1113
+ else:
1114
+ for l,number,exp in ee:
1115
+ if exp.upper().startswith('LIST'):
1116
+ listout.append([exp + ' $ \n'])
1117
+ elif len(exp) :
1118
+ # print(exp)
1119
+ out.append([exp+ ' $ \n'])
1120
+ return ''.join(chain(*listout,*out))
1121
+ ##%%
1122
+ def doable(formulars,funks=[],replace_usd=True):
1123
+ ''' takes index in the lhs and creates a do loop around the line
1124
+ on the right side you can use %0_, %1_ an so on to indicate the index, just to awoid typing to much
1125
+
1126
+ Also %i_ will be changed to all the indexes'''
1127
+
1128
+ def endovar(f,funks=[]): # Finds the first variable in a expression
1129
+ for t in udtryk_parse(f,funks=funks):
1130
+ if t.var:
1131
+ ud=t.var
1132
+ break
1133
+ return ud
1134
+
1135
+
1136
+ sep = '$' if '$' in formulars else '\n'
1137
+ lt = [(findindex(l.strip()),i,l.strip()) for i,l in enumerate(formulars.split(sep))]
1138
+
1139
+ out = []
1140
+ for xxx,number,exp in lt:
1141
+ if xxx:
1142
+ pre = ''.join([' '*i+ 'Do '+x+'_list $\n' for i,x in enumerate(xxx)])
1143
+ post = ''.join([' '* (len(xxx)-1-i)+'Enddo $ \n' for i,x in enumerate(xxx)]) +'\n'
1144
+ frml = ' '*(len(xxx)) + dosubst(xxx,exp)+ ' $ \n'
1145
+ out.append(pre + frml + post)
1146
+ ind = exp.strip()
1147
+
1148
+ if ind.startswith('<'):
1149
+ frml_name = re.findall(r'\<.*?\>',ind)[0]
1150
+ sumname = kw_frml_name(frml_name, 'sum')
1151
+ if sumname:
1152
+ lhs= ind.split('>',1)[1].split('=',1)[0]
1153
+ lhsvar = endovar(lhs,funks=funks)
1154
+ lhsvar_stub = lhsvar.split('{',1)[0]
1155
+ sumlist = ''.join([f'sum({x}_list,' for x in xxx])+lhsvar+')'*len(xxx)
1156
+ out.append(f'{lhsvar_stub}{sumname} = {sumlist} $ \n\n')
1157
+ elif len(exp) :
1158
+ out.append(exp+ ' $ \n')
1159
+ if replace_usd:
1160
+ res = ''.join(out).replace('$','') # the replace is a quick
1161
+ else:
1162
+ res = ''.join(out)
1163
+ return res
1164
+
1165
+
1166
+ ##%%
1167
+ def findindex_gams(ind00):
1168
+ '''
1169
+ - an equation looks like this
1170
+ - <frmlname> [index] lhs = rhs
1171
+
1172
+ this function find frmlname and index variables on the left hand side. meaning variables braced by {} '''
1173
+ ind0 = ind00.strip()
1174
+ if ind0.startswith('<'):
1175
+ frmlname = re.findall(r'\<.*?\>',ind0)[0]
1176
+ ind = ind0[ind0.index('>')+1:].strip()
1177
+ else:
1178
+ frmlname='<>'
1179
+ ind=ind0.strip()
1180
+
1181
+ if ind.startswith('['):
1182
+ allindex = re.findall(r'\[.*?\]',ind0)[0]
1183
+ index = allindex[1:-1].split(',')
1184
+ rest = ind[ind.index(']')+1:]
1185
+ else:
1186
+ index = []
1187
+ rest = ind
1188
+ return frmlname,index,rest
1189
+
1190
+ def un_normalize_expression(frml,implicit=False,endo='') :
1191
+ '''This function makes sure that all formulas are unnormalized.
1192
+ if the formula is already decorated with <endo=name> this is kept
1193
+ else the lhs_varriable is used in <endo=>
1194
+ '''
1195
+ frml_name,frml_index,frml_rest = findindex_gams(frml.upper())
1196
+ # debug_var(frml_name,frml_index,frml_rest)
1197
+
1198
+ if kw_frml_name(frml_name.upper(), 'IMPLICIT') or implicit:
1199
+
1200
+ this_endo = endo.upper() if endo else kw_frml_name(frml_name.upper(), 'ENDO')
1201
+ # debug_var(this_endo)
1202
+ lhs,rhs = frml_rest.split('=')
1203
+ if this_endo:
1204
+ lhs_var = this_endo.strip()
1205
+ frml_name_out = frml_name
1206
+ else:
1207
+ lhs_var = lhs.strip()
1208
+ # frml_name_out = f'<endo={lhs_var}>' if frml_name == '<>' else f'{frml_name[:-1]},endo={lhs_var}>'
1209
+ frml_name_out = frml_name[:]
1210
+ new_rest = f'{lhs_var}___res = ( {rhs.strip()} ) - ( {lhs.strip()} )'
1211
+ res = f'{frml_name_out} {frml_index if len(frml_index) else ""} {new_rest}'
1212
+ return res
1213
+
1214
+ else:
1215
+ return frml
1216
+
1217
+ def un_normalize_model(in_equations,implicit=False,funks=[]):
1218
+ ''' un normalize a model '''
1219
+ nymodel=[]
1220
+ equations=in_equations.upper() # we want do change the e
1221
+ # modelprint(equations)
1222
+ for comment,command,value in find_statements(equations):
1223
+ # print('>>',comment,'<',command,'>',value)
1224
+ # breakpoint()
1225
+ if comment:
1226
+ nymodel.append(comment)
1227
+ elif command=='FRML':
1228
+ un_frml = un_normalize_expression(value[:-1],implicit=implicit)
1229
+ nymodel.append(f'FRML {un_frml} $')
1230
+ else:
1231
+ nymodel.append(command+' '+value)
1232
+ equations='\n'.join(nymodel)
1233
+ return equations
1234
+
1235
+ def un_normalize_simpel(in_equations,funks=[]):
1236
+ ''' un-normalize expressions delimeted by linebreaks'''
1237
+ edm = '\n'.join(un_normalize_expression(f) for f in in_equations.split('\n') if len(f.strip()))
1238
+ fdm = explode(edm)
1239
+ return fdm
1240
+
1241
+
1242
+ def eksempel(ind):
1243
+ ''' takes a template model as input, creates a model and a histmodel and prints the models'''
1244
+ abe = udrul_model(ind)
1245
+ ko = find_hist_model(abe)
1246
+ modelprint(ind, 'Model template')
1247
+ modelprint(abe, 'Unrolled model')
1248
+ modelprint(ko, 'Historic model')
1249
+ # print('Hej fra modelmanipulation')
1250
+ if __name__ == '__main__' and 0 :
1251
+ #%%
1252
+ print(sub_frml(a['bankdic'],'Dette er {bank}'))
1253
+ print(sub_frml(a['bankdic'],'Dette er {bank}',sep=' and '))
1254
+ print(sub_frml(a['bankdic'],'Dette er {bank}',plus='+',sep=''))
1255
+ print(sub_frml(a['bankdic'],'Dette er {bank}',xvar='danske',lig='no'))
1256
+ print(sub_frml(a['bankdic'],'Dette er {bank}',xvar='danske',lig='yes'))
1257
+ print(sub_frml(a['bankdic'],'Dette er {bank}'))
1258
+
1259
+ #%%
1260
+ fmodel= '''
1261
+ list BANKDIC = bank : Danske , Nordea $
1262
+ list countryDIC = country : Uk , DK, SE , NO , IR, GE US AS AT CA$
1263
+ list countrydanske = country : uk , DK, IR $
1264
+ list countrynordea = country: SE , DK, AT $
1265
+ do bankdic$
1266
+ frml x {bank}_income = {bank}_a +{bank}_b $
1267
+ do country{bank} $
1268
+ frml x {bank}_{country} = 4242 $
1269
+ enddo $
1270
+ do countrydic $
1271
+ frml x {bank}_{country}_all = 42 $
1272
+ enddo $
1273
+ enddo $ '''
1274
+
1275
+ print(dounloop(fmodel))
1276
+ #%%
1277
+ print(stripstring(pastestring('a+b+log(x)','_xx'),'_xx'))
1278
+ split_frml ('FRML x ib =1+gris $')
1279
+ split_frml ('FRML <res> ib =1+gris $')
1280
+ find_statements(' FRML x ib =1+gris $ frml <exo> hane= 27*ged$')
1281
+ find_statements('! FRML x ib =1+gris $ \n frml <exo> hane=27*ged $')
1282
+ find_statements('FRML <x> ib =1+gris*(ko+1) $ ! Comment \n frml <exo> hane= ged $')
1283
+ sub('O {who} of {from}',{'who':'Knights','from':'Ni'})
1284
+ sub('O {who} of {from}, , we have brought you your {weed}',{'who':'Knights','from':'Ni'})
1285
+ sub_frml({'weed':['scrubbery','herring']},'we have brought you your {weed}')
1286
+ sub_frml({'weed':['scrubbery','herring'],'where':['land','sea']},'we have brought you your {weed} from {where} ')
1287
+ sub_frml({'weed':['scrubbery','herring'],'where':['land','sea']},'we have brought you your {weed} from {where} ')
1288
+ a={'bankdic': {'bank':['Danske','Nordea'],'danske':['yes','no']}}
1289
+ sub_frml(a['bankdic'],'Dette er {bank}')
1290
+ sub_frml(a['bankdic'],'Dette er {bank}',sep=' and ')
1291
+ sub_frml(a['bankdic'],'Dette er {bank}',plus='+',sep='')
1292
+ sub_frml(a['bankdic'],'Dette er {bank}',xvar='danske',lig='yes')
1293
+ sub_frml(a['bankdic'],'Dette er {bank}',xvar='danske',lig='no')
1294
+ sub_frml(a['bankdic'],'Dette er {bank}')
1295
+ list_extract('list bankdic = bank : Danske , Nordea / danske : yes , no $')
1296
+ kw_frml_name('<res=abe>','res')
1297
+ kw_frml_name('<res=abe,animal>','animal')
1298
+ find_res('FRML <res=x> ib =x+y+v +ib(-1)$')
1299
+ find_res('FRML <res=J> ib =x+y+v + ib_j $')
1300
+ find_res('FRML <res=JR> ib =(x+y+v + ib_j)*(1+ ib_JR) $)')
1301
+ find_hist_model('FRML <res=x> ib =x+y+v $ frml <ident> y=c+i+x-m $')
1302
+ (exounroll('frml <j> ib=x+y $ frml <jr> ib=x+y $ frml <j,jr'))
1303
+ find_arg('log','x=log(a+b)+77')
1304
+ find_arg('log','x=log(a+log(b))+77')
1305
+
1306
+ kaedeunroll('FRML <res=x> ib =x+y+lAG_ARRAY(v,3) $')
1307
+ #%%
1308
+ x= (dounloop(
1309
+ '''list bankdic = bank : Danske , Nordea /
1310
+ ko : yes , no $
1311
+ do bankdic ko = no $
1312
+ frml x {bank}_income = {bank}_a +{bank}_b $
1313
+ enddo $
1314
+ frml x ialt=sum(bankdic ko= yes,{bank}_income ) $'''))
1315
+ # breakpoint()
1316
+ print(sumunroll(x))
1317
+ #%% sumunroll
1318
+ print(sumunroll(dounloop(
1319
+ '''list BANKDIC = bank : Danske , Nordea $
1320
+ do BANKDIC $
1321
+ frml x {bank}_income = {bank}_a +{bank}_b $
1322
+ enddo $
1323
+ frml x ialt=sum(bankdic,{bank}_income) $''')))
1324
+ #%% funkunroll
1325
+ print(funkunroll(dounloop(
1326
+ '''list BANKDIC = bank : Danske , Nordea $
1327
+ do BANKDIC $
1328
+ frml x {bank}_income = {bank}_a +{bank}_b $
1329
+ enddo $
1330
+ frml x ialt=max(bankdic,{bank}_income) $''')))
1331
+ #%%
1332
+ # Trying some of the features:
1333
+ fmodel='''
1334
+ list BANKDIC = currentbank : Danske , Nordea , Jyske $
1335
+ list BANKDIC2 = loantobank : Danske , Nordea , Jyske $
1336
+ list BANKDIC3 = loanfrombank : Danske , Nordea , Jyske $
1337
+ frml <matrix> msigma = to_matrix(bankdic,bankdic2,cov_{currentbank}_{loantobank} ) $
1338
+ frml <matrix> vreturn = to_matrix(bankdic2,return_{loantobank}) $
1339
+ frml <matrix> vcapitalweights = to_matrix(bankdic2,capitalweights_{loantobank}) $
1340
+
1341
+ do BANKDIC $
1342
+ do bankdic2 $
1343
+ frml setmax loanmax_{currentbank}_to_{loantobank} = {currentbank}_capital*0.25 $
1344
+ frml setmin loanmin_{currentbank}_to_{loantobank} = 0.0 $
1345
+ enddo$
1346
+ frml setmax loanmax_{currentbank}_to_{currentbank} = 0.0 $
1347
+ frml <matrix> {currentbank}_loan_max =to_matrix(bankdic2,loan_max_{currentbank}_to_{loantobank}) $
1348
+ frml <matrix> {currentbank}_loan_min =to_matrix(bankdic2,loan_min_{currentbank}_to_{loantobank}) $
1349
+ enddo $
1350
+
1351
+ do Bankdic $
1352
+ frml <matrix> loan_{currentbank}_to
1353
+ = mv_opt(msigma,vreturn,riskaversion, {currentbank}_totalloan, vcapitalweights, {currentbank}_capital,
1354
+ {currentbank}_loan_max,{currentbank}_loan_min) $
1355
+ enddo $
1356
+ do Bankdic $
1357
+ frml x {currentbank}_loancheck=sum(bankdic2,{currentbank}_to_{loantobank}_loan)$
1358
+ enddo $ '''
1359
+ modelprint(fmodel)
1360
+ modelprint(sumunroll(argunroll(creatematrix(dounloop(fmodel)))))
1361
+ #%%
1362
+ def f1(x):
1363
+ return x
1364
+
1365
+ eq='''FRML <> logit(pd_country_sector) =
1366
+ 0.008 + 0.44 * logit(pd_country_sector(-1)) - 0.18 * gdp_country -0.05*gdp_country(-1) + 0.12 * URX_country + 0.02 * sltn_country + 0.11 * nsltn_country(-1) $ '''
1367
+ # eq='''FRML <> logit(cow) = logit(1+1) + diff(a) $ '''
1368
+ # eq='''FRML <> x = diff(f1(a)) $ '''
1369
+ print(eq)
1370
+ print('After normalization' )
1371
+ neq=normalize(eq,funks=[f1]).lower()
1372
+ print(neq)
1373
+ toteq=eq.lower()+neq
1374
+
1375
+ #%%
1376
+
1377
+ org ='''
1378
+ ! credit risk
1379
+ ! calculate pd for each portefolio sector based on bank specific starting point
1380
+ ! remember to do a .replace('%seg%','{bank}__{CrCountry}__{PortSeg}')
1381
+ do banklist $
1382
+ do {bank}CrCoList $
1383
+ do PortSegList $
1384
+ ! non defaultet stock
1385
+ non_def_exp_end_y__%seg% = non_def_exp_end_y__%seg%(-1)*(1-pd_pit__Q__%seg%) $
1386
+
1387
+ ! for this prototype we just use last period LGD
1388
+ Lgd_pit_new_scen__%seg% = Lgd_pit_new_scen__%seg%(-1) $
1389
+
1390
+ ! gross impairemens (losses) newly defaulted stock
1391
+ gross_imp_loss_new_def__%seg% = non_def_exp_end_y__%seg%(-1)*pd_pit__Q__%seg%*Lgd_pit_new_scen__%seg% $
1392
+
1393
+ ! share of provision stock which can ofset the impairements
1394
+ alfa__%seg% = pd_pit__Q__%seg% $
1395
+
1396
+ part of provision stock which can ofset losses
1397
+ prov_release_new_def__%seg% = alfa__%seg% * prov_stock_end_y__%seg%(-1) $
1398
+
1399
+ ! net impairemens on newly defaulted stock, can only contribute to loss
1400
+ imp_loss_new_def__%seg% = max(0.0, gross_imp_loss_new_def__%seg% - prov_release_new_def__%seg%) $
1401
+
1402
+ ! update the stock of provisions relateed to newly defaulted amount after release to cover impairement
1403
+ prov_stock_end_y__%seg% = prov_stock_end_y__%seg%(-1) + diff(imp_loss_new_def__%seg%) $
1404
+
1405
+ ! now we look at previous defaulted loans
1406
+
1407
+ ! defaulted exposure
1408
+ def_stock_end_y__%seg% = def_stock_end_y__%seg%(-1) + non_def_exp_end_y__%seg%(-1)*pd_pit__Q__%seg% $
1409
+
1410
+ ! impairement old defaulted stock, can not contribute to profit only loss
1411
+ imp_loss_old_def__%seg% = max(0,diff(Lgd_pit_old_scen__%seg%)* def_stock_end_y__%seg%(-1)) $
1412
+
1413
+ ! amount of provisions stock relateed to old defaulted stock
1414
+ prov_stock_end_y_def__%seg% = prov_stock_end_y_def__%seg%(-1)+imp_loss_old_def__%seg% $
1415
+
1416
+ ! total impairment
1417
+ imp_loss_total_def__%seg% = imp_loss_old_def__%seg% +imp_loss_new_def__%seg% $
1418
+
1419
+ enddo $
1420
+ enddo $
1421
+ frml <> imp_loss_total_def__{bank} = sum({bank}CrCoList,sum(PortSegList,imp_loss_total_def__%seg%)) $
1422
+
1423
+ enddo $
1424
+
1425
+
1426
+ '''
1427
+
1428
+ print((doable(org)))
1429
+ print(tofrml(doable(org),sep='$'))
1430
+
1431
+ print(tofrml('a=b \n c=55 ',sep='\n'))
1432
+ #%%
1433
+ model = '''\
1434
+ ! jjjfj
1435
+ list alist = al : a b c
1436
+ a = c(-1) + b(-1)
1437
+ x = 0.5 * c
1438
+ d = x + 3 * a(-1)
1439
+ '''
1440
+ print(tofrml(model))
1441
+
1442
+
1443
+ # testtofrml
1444
+ mtest = '''
1445
+ list banklist = bank : ib soren marie /
1446
+ country : denmark sweden denmark $
1447
+
1448
+ do banklist $
1449
+ ! kkdddk
1450
+ frml <> profit_{bank} = revenue_{bank} - lag_array(-3,expenses_{bank}) $
1451
+ frml <> expenses_{bank} = factor_{country} * revenue_{bank} $
1452
+ enddo $
1453
+ '''
1454
+
1455
+ print(tofrml(mtest))
1456
+ print(explode(mtest))
1457
+ #%% unnormlize
1458
+ if __name__ == '__main__' :
1459
+
1460
+ print(un_normalize_expression('a=b',implicit=True,endo='xx' ))
1461
+