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.
modelinvert.py ADDED
@@ -0,0 +1,250 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Thu Sep 21 12:41:10 2017
4
+
5
+ @author: IBH
6
+
7
+ Class to handle general target/instrument problems.
8
+
9
+ Number of targets should be equal to number of instruments
10
+
11
+ An instrument can comprice of severeral variables
12
+ instruments are inputtet as a list of instruments
13
+
14
+ """
15
+
16
+ import pandas as pd
17
+ import numpy as np
18
+ from tqdm.auto import tqdm
19
+
20
+
21
+ from modelhelp import update_var
22
+
23
+
24
+ class targets_instruments():
25
+ ''' Class to handle general target/instrument problems.
26
+ Where the response is delayed specify this with delay.
27
+
28
+ Number of targets should be equal to number of instruments
29
+
30
+ An instrument can comprice of severeral variables
31
+
32
+ **Instruments** are inputtet as a list of instruments
33
+
34
+ To calculate the jacobian each instrument variable has a impuls,
35
+ which is used as delta when evaluating the jacobi matrix::
36
+
37
+ [ 'QO_J','TG'] Simple list each variable are shocked by the default impulse
38
+ [ ('QO_J',0.5), 'TG'] Here QO_J is getting its own impuls (0.5)
39
+ [ [('QO_J',0.5),('ORLOV',1.)] , ('TG',0.01)] here an impuls is given for each variable, and the first instrument consiste of two variables
40
+
41
+ **Targets** are list of variables
42
+
43
+ Convergence is achieved when all targets are within convergens distance from the target value
44
+
45
+ Convergencedistance can be set individual for a target variable by setting a value in <modelinstance>.targetconv
46
+
47
+ Targets and target values are provided by a dataframe.
48
+
49
+ '''
50
+
51
+ def __init__(self,databank,targets,instruments,model,defaultimpuls=0.01,defaultconv=0.01, delay=0,
52
+ nonlin=False,silent = True, maxiter=30,solveopt={},varimpulse=False,progressbar= True):
53
+ '''
54
+
55
+
56
+ Parameters
57
+ ----------
58
+ databank : TYPE
59
+ values to run on .
60
+ targets : TYPE
61
+ dataframe with a column for each target.
62
+ instruments : TYPE
63
+ list of instruments .
64
+ model : TYPE
65
+ the model to use .
66
+ defaultimpuls : TYPE, optional
67
+ default delta . The default is 0.01.
68
+ defaultconv : TYPE, optional
69
+ default convergence . The default is 0.01.
70
+ delay : TYPE, optional
71
+ delay in effects . The default is 0.
72
+ nonlin : TYPE, optional
73
+ if a number the number of iterations to trigger recalculation of jacobi. The default is False.
74
+ silent : TYPE, optional
75
+ show iterations if false. The default is True.
76
+ maxiter : TYPE, optional
77
+ max newton iteration. The default is 30.
78
+ solveopt : TYPE, optional
79
+ options to bring to the solver. The default is {}.
80
+ varimpulse : TYPE, optional
81
+ if True only update the current period, else update into the future. The default is False.
82
+
83
+ Returns
84
+ -------
85
+ None.
86
+
87
+ '''
88
+ self.model = model
89
+ self.df = model.lastdf
90
+ self.targetvars = targets if isinstance(targets,list) else targets.columns
91
+ self.targetconv = {t: defaultconv for t in self.targetvars} # make sure there is a convergence criteria ]
92
+ self.targets = targets
93
+ self.instruments = {}
94
+ self.solveopt = {**solveopt, **{'keep':''}}
95
+ self.silent = silent
96
+ self.debug=False
97
+ self.maxiter = maxiter
98
+ self.nonlin=nonlin
99
+ self.databank = databank.copy()
100
+ self.varimpulse = varimpulse
101
+ self.progressbar = progressbar
102
+ self.savesolvearg = model.oldkwargs if hasattr(model,'oldkwargs') else {}
103
+ for inumber,i in enumerate(instruments):
104
+ vars = i if isinstance(i,list) else [i] # make it a list even if one variable
105
+ xx = [v if isinstance(v,tuple) else (v,defaultimpuls) for v in vars] # make sure there is a impuls
106
+ name = ','.join(n for n,i in xx)
107
+ name = f'Instrument_{inumber}' if len(name)>500 else name
108
+ self.instruments[inumber] = {'name':name , 'vars': xx, }
109
+ self.defaultimpuls = defaultimpuls
110
+ # breakpoint()
111
+
112
+
113
+ def jacobi(self,per,delay=0):
114
+ ''' Calculates a jecobi matrix of derivatives based on the instruments and targets
115
+
116
+ returns a dataframe '''
117
+ # breakpoint()
118
+
119
+ iper = self.df.index.get_loc(per)
120
+ iper_delayed = iper-delay
121
+ per_delayed = self.df.index[iper_delayed]
122
+
123
+
124
+ self.jac = jac=pd.DataFrame(0.000,index=self.targetvars, columns=[v['name'] for v in self.instruments.values()])
125
+ with self.model.set_smpl(per_delayed,per):
126
+ mul = self.model(self.df,setlast=False, **self.solveopt) # start point for this quarter
127
+ basis = mul.copy(deep=True) # make a reference point for the calculation the derivatives.
128
+ if not self.silent: print(f'Update jacobi: {per} effects from {per_delayed}')
129
+ oldsave = self.model.save
130
+ for instrument in self.instruments.values():
131
+ # print('instrument: ',instrument['name'])
132
+ # set the instrument
133
+ for var,impuls in instrument['vars']:
134
+ if self.varimpulse:
135
+ mul.loc[per_delayed,var] = mul.loc[per_delayed,var] + impuls # increase loan growth
136
+ else:
137
+ mul.loc[per_delayed:,var] = mul.loc[per_delayed:,var] + impuls # increase loan growth
138
+ # calculate the effect
139
+ with self.model.set_smpl(per_delayed,per):
140
+
141
+ res = self.model(mul,save=False,**self.solveopt) #antal=600,first_test=20,ljit=1) # solve model
142
+
143
+ # breakpoint()
144
+ jac.loc[self.targetvars,instrument['name']] = res.loc[per,self.targetvars]-basis.loc[per,self.targetvars] # store difference in original bank
145
+ # reset the instrument
146
+ for var,impuls in instrument['vars']:
147
+ mul.loc[per_delayed:,var]=basis.loc[per_delayed:,var]
148
+ self.model.oldkwargs = self.savesolvearg
149
+ self.model.save = oldsave
150
+
151
+ return jac
152
+
153
+ def invjacobi(self,per,diag=False,delay=0):
154
+ ''' Calculates the inverted jacobi matrix
155
+
156
+ returns a dataframe '''
157
+
158
+ x = self.jacobi(per,delay=delay)
159
+ # print(x)
160
+ if diag:
161
+ out = pd.DataFrame(np.diag(1.0/x.values.diagonal()),x.columns,x.index)
162
+ else:
163
+ out= pd.DataFrame(np.linalg.inv(x),x.columns,x.index)
164
+ return out
165
+
166
+ def targetseek(self,databank=None,shortfall=False,ti_damp=1.0,delay=0,**kwargs):
167
+ ''' Calculates the instruments as a function of targets '''
168
+ silent = kwargs.get('silent',self.silent)
169
+ self.maxiter = kwargs.get('maxiter',self.maxiter)
170
+ self.nonlin = kwargs.get('nonlin',self.nonlin)
171
+ progressbar = kwargs.get('progressbar',self.progressbar)
172
+
173
+ tindex = self.model.current_per.copy()
174
+ res = self.databank.copy()
175
+ # self.inv = inv = self.invjacobi(self.targets.index[0])
176
+ self.inv = inv = self.invjacobi(self.targets.index[0],diag=shortfall,delay=delay)
177
+
178
+ self.conv = pd.Series([self.targetconv[v] for v in self.targetvars],self.targetvars)
179
+ # print(inv)
180
+ oldsave = self.model.save
181
+ bars = '{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt}'
182
+
183
+ with tqdm(total=len(self.targets.index),disable = not progressbar,desc=f'Finding instruments ',bar_format=bars) as pbar:
184
+
185
+ for per in self.targets.index:
186
+
187
+ iper = self.df.index.get_loc(per)
188
+ iper_delayed = iper-delay
189
+ per_delayed = self.df.index[iper_delayed]
190
+
191
+
192
+ if not silent or self.debug:
193
+ print('Period:',per)
194
+ res = self.model(res,per_delayed ,per ,save=False, **self.solveopt)
195
+ orgdistance = self.targets.loc[per,self.targetvars] - res.loc[per,self.targetvars]
196
+ shortfallvar = (shortfall * orgdistance) >= 0
197
+ for iterations in range(self.maxiter):
198
+ startdistance = self.targets.loc[per,self.targetvars] - res.loc[per,self.targetvars]
199
+ self.distance = distance = startdistance*shortfallvar if shortfall else startdistance
200
+ if not silent: print('Period:',per,' Target instrument iteration:',iterations,
201
+ f' Max distance: {distance.abs().max():.3f}')
202
+ if self.debug:
203
+ print(f'\nTarget instrument {per}, iteration {iterations}')
204
+ print(f'Distance to target :\n{startdistance}\n')
205
+ if shortfall:
206
+ print(f'Distance to shortfall target :\n{distance}\n')
207
+
208
+ if (distance.abs()>=self.conv).any():
209
+ if self.nonlin:
210
+ if type(self.nonlin) == int:
211
+ if iterations >= self.nonlin:
212
+ if not iterations%self.nonlin:
213
+ self.inv = inv = self.invjacobi(per,diag=shortfall,delay=delay)
214
+ else:
215
+ self.inv = inv = self.invjacobi(per,diag=shortfall,delay=delay)
216
+
217
+ update = inv.dot(distance) * ti_damp
218
+ if self.debug :
219
+ print('Update instruments:')
220
+ print(update)
221
+ for instrument in self.instruments.values():
222
+ for var,impuls in instrument['vars']:
223
+ if self.varimpulse:
224
+ res.loc[per_delayed,var] = res.loc[per_delayed,var] + update[instrument['name']] * impuls # increase loan growth
225
+ else:
226
+ res.loc[per_delayed:,var] = res.loc[per_delayed:,var] + update[instrument['name']] * impuls # increase loan growth
227
+
228
+ res = self.model(res,per_delayed ,per ,setlast=False,**self.solveopt)
229
+ else:
230
+ break
231
+ else:
232
+ print(f'No convergense in target instrument in {per}, maxiter={self.maxiter}')
233
+ raise Exception('No convergence ')
234
+ pbar.update()
235
+ self.model.lastdf = res
236
+ self.model.oldkwargs = self.savesolvearg
237
+ self.model.save = oldsave
238
+ self.model.current_per = tindex
239
+
240
+ return res
241
+
242
+
243
+ def __call__(self, *args, **kwargs ):
244
+ '''Uses :any:`targetseek` '''
245
+ return self.targetseek( *args, **kwargs)
246
+
247
+
248
+ #%% running
249
+ if __name__ == '__main__':
250
+ pass