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.
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)