kalman-inversion-lib 0.0.1__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2026] [Konstantin Ibadullaev]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalman_inversion_lib
3
+ Version: 0.0.1
4
+ Summary: The package implements various Kalman Inversion algorithms in Python.
5
+ Author-email: Konstantin Ibadullaev <konstantin.ibadullaev.post@gmail.com>
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # Kalman Inversion Lib Package
@@ -0,0 +1 @@
1
+ # Kalman Inversion Lib Package
@@ -0,0 +1,20 @@
1
+ class EnsembleMonitor:
2
+ def __init__(self, kalman_object):
3
+ self.kalman_object = kalman_object
4
+ self.ensemble_list = []
5
+ self.posterior_mean_list = []
6
+ self.posterior_covariance_list = []
7
+
8
+ def collect_stats(self):
9
+ self.ensemble_list.append(self.kalman_object.ensemble.copy())
10
+ self.posterior_mean_list.append(self.kalman_object.posterior_mean.copy())
11
+ self.posterior_covariance_list.append(self.kalman_object.posterior_covariance.copy())
12
+
13
+ def get_ensemble_list(self):
14
+ return self.ensemble_list
15
+
16
+ def get_posterior_mean_list(self):
17
+ return self.posterior_mean_list
18
+
19
+ def get_posterior_covariance_list(self):
20
+ return self.posterior_covariance_list
@@ -0,0 +1,349 @@
1
+ import numpy as np
2
+ import scipy as sp
3
+ from abc import ABC, abstractmethod
4
+ from tqdm.notebook import tqdm
5
+
6
+ #from timestep_schedulers import *
7
+
8
+ from diagnostics import EnsembleMonitor
9
+
10
+
11
+
12
+ ################### Base Class For Learning Rate/Time Step Schedulers ############################################
13
+ class BaseTimeStepScheduler:
14
+ def __init__(self, kalman_object:type(BaseKalmanInversion)| type(BaseKalmanSampler)):
15
+ self.kalman_object = kalman_object
16
+
17
+ def compute_timestep(self):
18
+ raise NotImplementedError("Subclasses must implement this method" )
19
+
20
+
21
+ ################### Class Data Container ############################################
22
+ class KalmanInversionDataContainer:
23
+ def __init__(
24
+ self,
25
+ y_observations:np.ndarray,
26
+ Gamma_noise_matrix:np.ndarray,
27
+ prior_mean:np.ndarray,
28
+ prior_covariance:np.ndarray,
29
+
30
+
31
+ ):
32
+
33
+
34
+
35
+ self.Gamma_noise_matrix = Gamma_noise_matrix
36
+ self.prior_mean = prior_mean
37
+ self.prior_covariance = prior_covariance
38
+ self.dim_parameter = len(self.prior_mean)
39
+ self.y_observations = y_observations
40
+ self.dim_y = len(self.y_observations)
41
+
42
+
43
+
44
+
45
+ ################### Base Class for Kalman Inversion (Optimizers) ############################################
46
+
47
+ class BaseKalmanInversion(ABC):
48
+ def __init__(
49
+ self,
50
+ time_step_scheduler:type(BaseTimeStepScheduler),
51
+ kalman_data_container:type(KalmanInversionDataContainer),
52
+ seed:int = 42,
53
+ if_extend_space:bool=False,
54
+ if_impose_prior:bool=False,
55
+ eps = 1e-10,
56
+ max_step :float = 0.75,
57
+
58
+ ):
59
+
60
+ self.monitor = EnsembleMonitor(self)
61
+
62
+ self.eps = eps
63
+ self.max_step = max_step
64
+ self.ki_random_gen = np.random.default_rng(seed)
65
+
66
+ self.kalman_data_container = kalman_data_container
67
+ # scheduler reuses internal state of the kalman object
68
+ self.time_step_scheduler = time_step_scheduler(self)
69
+
70
+ self.y_observations = None
71
+ self.ensemble = None
72
+ self.posterior_mean = self.kalman_data_container.prior_mean
73
+ self.posterior_covariance = self.kalman_data_container.prior_covariance
74
+ self.total_time = 0.0
75
+
76
+ self.if_extend_space = if_extend_space
77
+ self.if_impose_prior = False if self.if_extend_space else if_impose_prior
78
+ # self.if_impose_prior = if_impose_prior
79
+
80
+
81
+
82
+
83
+ # @abstractmethod
84
+ def ensemble_transform(self):
85
+ pass
86
+
87
+
88
+
89
+
90
+
91
+
92
+ def compute_forward_model(self, y_forward, ensemble):
93
+ # we might need this function for general purposes as well
94
+ for j in range(0, self.J):
95
+ y_forward[j,:] = np.hstack([self.forward_model(ensemble[j,:].flatten(), **self.kwargs_object), ensemble[j,:].flatten()]) if self.if_extend_space else self.forward_model(ensemble[j,:].flatten(), **self.kwargs_object).flatten()
96
+ return y_forward
97
+
98
+
99
+
100
+
101
+ def create_observation_space(self):
102
+ if self.if_extend_space :
103
+ # extend observation space
104
+ self.y_observations = np.hstack([self.kalman_data_container.y_observations, self.kalman_data_container.prior_mean])
105
+ # extend error matrix
106
+ self.sigma_nu_const = np.zeros((self.kalman_data_container.dim_y + self.kalman_data_container.dim_parameter, self.kalman_data_container.dim_y + self.kalman_data_container.dim_parameter))
107
+ self.sigma_nu_const[:self.kalman_data_container.dim_y, :self.kalman_data_container.dim_y ] = self.kalman_data_container.Gamma_noise_matrix
108
+ self.sigma_nu_const[self.kalman_data_container.dim_y :, -self.kalman_data_container.dim_parameter: ] = self.kalman_data_container.prior_covariance
109
+ else:
110
+ self.y_observations = self.kalman_data_container.y_observations
111
+ self.sigma_nu_const = self.kalman_data_container.Gamma_noise_matrix
112
+ pass
113
+
114
+
115
+
116
+ @abstractmethod
117
+ def predict_step(self):
118
+ """
119
+ Predict step, the method should be implemented for each type of algorithms
120
+ """
121
+ pass
122
+
123
+ @abstractmethod
124
+ def analysis_step(self):
125
+ """
126
+ Analysis step, the method should be implemented for each type of algorithms
127
+ """
128
+ pass
129
+
130
+
131
+
132
+ def update_sigmas(self):
133
+ self.omega_gamma = ( (self.time_step/(1.0 - self.time_step)) + 1.0 - self.alpha_regularizer**2 ) if (self.if_impose_prior and not self.if_extend_space) else (self.time_step/(1.0 - self.time_step ))
134
+ self.sigma_omega = self.omega_gamma * self.kalman_data_container.prior_covariance if (self.if_impose_prior and not self.if_extend_space) else (self.time_step/(1.0 - self.time_step )) * self.posterior_covariance
135
+ # self.omega_gamma = (self.time_step/(1.0 - self.time_step ))
136
+ # self.sigma_omega = self.omega_gamma * self.posterior_covariance
137
+ self.sigma_nu = self.sigma_nu_const/(self.time_step)
138
+
139
+ def inversion_step(self, **kwargs):
140
+ step_counter = 0.0
141
+
142
+
143
+ # perform evaluation until step is 1.0
144
+ while step_counter < 1.0 - self.eps:
145
+ # # compute ensemble for the scheduler
146
+ self.ensemble_transform()
147
+ # # compute data for the schedulers
148
+ self.y_forward = self.compute_forward_model(self.y_forward, self.ensemble)
149
+ # get a time step for the next iteration: cap the values to hold dt in range(0, 1)
150
+ a_max = min( (1.0 - step_counter), self.max_step)
151
+ self.time_step = np.clip( self.time_step_scheduler.compute_timestep(), a_min=self.eps , a_max=a_max)
152
+ step_counter += self.time_step
153
+
154
+ # update artificial error matrix in the parameter space
155
+ self.update_sigmas()
156
+ # predict step
157
+ self.predict_step()
158
+ # transformation before the analysis step
159
+ self.ensemble_transform()
160
+ # analysis step
161
+ self.analysis_step(**kwargs)
162
+ self.total_time += self.time_step
163
+
164
+
165
+
166
+
167
+
168
+
169
+ def run_inversion(self, forward_model:callable, time_step: float=0.5, num_iterations :int = 1 , alpha_regularizer:float=1.0, **kwargs):
170
+
171
+ self.time_step = time_step
172
+ self.init_step = time_step
173
+
174
+
175
+
176
+ self.alpha_regularizer = 1.0 if self.if_extend_space else np.clip(alpha_regularizer, a_min=0.0, a_max=1.0)
177
+
178
+
179
+ self.num_iterations = num_iterations
180
+
181
+
182
+ self.create_observation_space()
183
+ self.M = len(self.y_observations.flatten())
184
+
185
+
186
+
187
+ self.sigma_omega = self.posterior_covariance
188
+
189
+ self.forward_model = forward_model
190
+
191
+ # kwargs for the forward model function
192
+ self.kwargs_object = kwargs
193
+
194
+ self.y_forward = np.zeros((self.J, self.M) )
195
+
196
+ # self.ensemble_transform()
197
+ # self.y_forward = self.compute_forward_model(y_forward = self.y_forward, ensemble=self.ensemble)
198
+
199
+
200
+
201
+ for i in tqdm( range(0, self.num_iterations)):
202
+ self.inversion_step()
203
+ self.monitor.collect_stats()
204
+
205
+
206
+ def get_posterior_mean(self):
207
+ return self.posterior_mean
208
+
209
+ def get_posterior_covariance(self):
210
+ return self.posterior_covariance
211
+
212
+ ################### Base Class for Kalman Inversion (Samplers) ############################################
213
+
214
+ class BaseKalmanSampler(ABC):
215
+ def __init__(
216
+ self,
217
+ initial_ensemble:np.ndarray,
218
+ J:int,
219
+ time_step_scheduler:type(BaseTimeStepScheduler),
220
+ kalman_data_container:type(KalmanInversionDataContainer),
221
+ seed:int = 42,
222
+ eps = 1e-10,
223
+ max_step :float = 1.0,
224
+ if_centered:bool=True,
225
+
226
+ ):
227
+
228
+ self.monitor = EnsembleMonitor(self)
229
+ self.if_centered = if_centered
230
+ self.eps = eps
231
+ self.max_step = max_step
232
+ self.ki_random_gen = np.random.default_rng(seed)
233
+
234
+ self.kalman_data_container = kalman_data_container
235
+ # scheduler reuses internal state of the kalman object
236
+ self.time_step_scheduler = time_step_scheduler(self)
237
+
238
+ self.y_observations = self.kalman_data_container.y_observations
239
+ self.ensemble = initial_ensemble
240
+ self.J = J
241
+
242
+ self.y_forward = np.zeros((self.J, self.kalman_data_container.dim_y))
243
+
244
+ self.posterior_mean = self.kalman_data_container.prior_mean
245
+ self.posterior_covariance = self.kalman_data_container.prior_covariance
246
+ self.total_time = 0.0
247
+
248
+ if self.if_centered:
249
+ # center the ensemble
250
+ self.ensemble = self.ensemble - self.kalman_data_container.prior_mean
251
+
252
+
253
+ def compute_forward_model(self, y_forward, ensemble, J):
254
+ if self.if_centered:
255
+ ensemble = ensemble + self.kalman_data_container.prior_mean
256
+ # we might need this function for general purposes as well
257
+ for j in range(0, J):
258
+ y_forward[j,:] = self.forward_model(ensemble[j,:].flatten() , **self.kwargs_object).flatten()
259
+ return y_forward
260
+
261
+
262
+ def get_posterior_mean(self,):
263
+ return self.posterior_mean
264
+
265
+ def get_posterior_covariance(self,):
266
+ return self.posterior_covariance
267
+
268
+ def compute_mean(self, ensemble, y_forward, covariance, J):
269
+ raise NotImplementedError("Subclass must implement this method")
270
+
271
+ def draw_proposal(self,proposed_mean, covariance , J):
272
+ raise NotImplementedError("Subclass must implement this method")
273
+
274
+
275
+ def one_step_inverse(self, ensemble, y_forward, J,covariance):
276
+
277
+
278
+
279
+ # # compute mean and covariance
280
+ # covariance = self.compute_covariance(ensemble=ensemble)
281
+ proposed_mean = self.compute_mean( ensemble=ensemble, y_forward=y_forward, covariance=covariance, J=J)
282
+
283
+ # draw a proposal ensemble
284
+ proposed_ensemble = self.draw_proposal( proposed_mean=proposed_mean, covariance = covariance, J=J)
285
+
286
+ # update ensemble
287
+ ensemble = self.update_ensemble(ensemble=proposed_ensemble)
288
+ return (ensemble, proposed_mean)
289
+
290
+ def block_wrapper(self, ensemble, y_forward, J):
291
+ # compute covariance and pass it into inverse
292
+ covariance = self.compute_covariance(ensemble=ensemble)
293
+ # this one is for MA extension
294
+
295
+ # if we have several blocks -> write for loop and choose corresponding ensemble members
296
+ ensemble, _ = self.one_step_inverse( ensemble, y_forward, J, covariance)
297
+ return ensemble
298
+
299
+
300
+ def compute_covariance(self, ensemble):
301
+ # compute covariance P x P
302
+ return np.cov(ensemble.T, bias=True)
303
+
304
+ def get_posterior_mean(self):
305
+ return self.ensemble.mean(0) + self.kalman_data_container.prior_mean
306
+
307
+ def get_posterior_covariance(self):
308
+ return self.compute_covariance(self.ensemble)
309
+
310
+ def update_ensemble(self, ensemble):
311
+ # can be extended for accept/reject step
312
+ return ensemble
313
+
314
+ def inversion_step(self,ensemble, y_forward, J):
315
+ step_counter = 0.0
316
+
317
+ # perform evaluation until step is 1.0
318
+ while step_counter < 1.0 - self.eps:
319
+ # compute data for the schedulers
320
+ self.y_forward = self.compute_forward_model( ensemble=ensemble, y_forward=y_forward, J=J)
321
+
322
+ # get a time step for the next iteration: cap the values to hold dt in range(0, 1)
323
+ a_max = min( (1.0 - step_counter), self.max_step)
324
+ self.time_step = np.clip( self.time_step_scheduler.compute_timestep(), a_min=self.eps , a_max=a_max)
325
+ step_counter += self.time_step
326
+ self.total_time += self.time_step
327
+ # get a new ensemble
328
+ ensemble = self.block_wrapper(ensemble, y_forward, J)
329
+ return ensemble
330
+
331
+
332
+
333
+ def run_inversion(self, forward_model:callable, time_step: float=0.5, num_iterations :int = 1 , **kwargs ):
334
+
335
+
336
+ self.forward_model = forward_model
337
+
338
+ # kwargs for the forward model function
339
+ self.kwargs_object = kwargs
340
+
341
+
342
+
343
+ self.time_step = time_step
344
+ self.init_step = time_step
345
+ self.num_iterations = num_iterations
346
+
347
+ for i in tqdm( range(0, self.num_iterations)):
348
+ self.ensemble = self.inversion_step(ensemble=self.ensemble, y_forward = self.y_forward, J=self.J)
349
+ self.monitor.collect_stats()
@@ -0,0 +1,133 @@
1
+ import numpy as np
2
+ import scipy as sp
3
+ #from tqdm.notebook import tqdm
4
+ from kalman_base_classes import BaseKalmanInversion, KalmanInversionDataContainer, BaseTimeStepScheduler
5
+
6
+
7
+
8
+ ######## Ensemble Kalman Inversion #########
9
+ class EnsembleKalmanInversion(BaseKalmanInversion):
10
+ def __init__(
11
+ self,
12
+ kalman_data_container:type(KalmanInversionDataContainer),
13
+ initial_ensemble:np.ndarray,
14
+ time_step_scheduler:type(BaseTimeStepScheduler),
15
+ eps = 1e-10,
16
+ max_step :float = 0.75,
17
+ seed:int = 42,
18
+ if_extend_space:bool=False,
19
+ if_impose_prior:bool=False,
20
+
21
+ ):
22
+
23
+
24
+ super().__init__( kalman_data_container=kalman_data_container, seed=seed, time_step_scheduler=time_step_scheduler, eps=eps , if_extend_space = if_extend_space, max_step =max_step, if_impose_prior=if_impose_prior )
25
+ self.ensemble = initial_ensemble
26
+
27
+ self.J = self.ensemble.shape[0]
28
+
29
+
30
+ def predict_step(self):
31
+ # self.m_hat = self.kalman_data_container.prior_mean + self.alpha_regularizer * (self.ensemble.mean(0) - self.kalman_data_container.prior_mean)
32
+ # self.ensemble = self.m_hat + np.sqrt( self.alpha_regularizer**2 + self.omega_gamma) * (self.ensemble - self.ensemble.mean(0) )
33
+ self.ensemble = self.alpha_regularizer * self.ensemble + (1 - self.alpha_regularizer) * self.kalman_data_container.prior_mean + self.ki_random_gen.multivariate_normal(np.zeros(self.kalman_data_container.dim_parameter), self.sigma_omega, self.J)
34
+ self.m_hat = np.mean(self.ensemble, axis=0)
35
+
36
+ def get_posterior_mean(self):
37
+ return self.ensemble.mean(0)
38
+
39
+ def get_posterior_covariance(self):
40
+ return np.cov(self.ensemble.T, bias=False)
41
+
42
+
43
+ def analysis_step(self):
44
+
45
+ # forward computation
46
+ self.y_forward = self.compute_forward_model(self.y_forward,self.ensemble)
47
+
48
+ # compute cross/ covariances
49
+ cov_theta_gamma = ( self.ensemble - self.m_hat ).T @ (self.y_forward - self.y_forward.mean(0) ) / (self.J-1) # P x D
50
+ cov_gamma_gamma = ( self.y_forward - self.y_forward.mean(0) ).T @ (self.y_forward - self.y_forward.mean(0) ) / (self.J-1) + self.sigma_nu # D x D
51
+
52
+ # residuals
53
+ Z = self.y_observations - self.y_forward - self.ki_random_gen.multivariate_normal(np.zeros(self.M), self.sigma_nu, self.J) # J x D
54
+
55
+ # Kalman update
56
+ K = ( cov_theta_gamma @ np.linalg.solve( cov_gamma_gamma, Z.T) ).T # J x P
57
+ # Ensemble update
58
+ self.ensemble = self.ensemble + K
59
+ self.posterior_covariance = np.cov(self.ensemble.T, bias=False)
60
+ self.posterior_mean = np.mean(self.ensemble, axis=0)
61
+
62
+
63
+ ######## Unscented Kalman Inversion #########
64
+ class UnscentedKalmanInversion(BaseKalmanInversion):
65
+ def __init__(
66
+ self,
67
+ kalman_data_container:type(KalmanInversionDataContainer),
68
+
69
+ time_step_scheduler:type(BaseTimeStepScheduler),
70
+ eps = 1e-10,
71
+ max_step :float = 0.75,
72
+ seed:int = 42,
73
+ if_extend_space:bool=False,
74
+ if_impose_prior:bool=False,
75
+
76
+ ):
77
+
78
+
79
+ super().__init__( kalman_data_container=kalman_data_container, seed=seed, time_step_scheduler=time_step_scheduler, eps=eps , if_extend_space = if_extend_space, max_step =max_step,if_impose_prior=if_impose_prior
80
+
81
+ )
82
+ self.J = self.kalman_data_container.dim_parameter * 2 + 1
83
+ self.ensemble = np.zeros((self.J, self.kalman_data_container.dim_parameter ))
84
+ self.ensemble_transform()
85
+
86
+
87
+ def ensemble_transform(self):
88
+
89
+ # quadrature coefficients
90
+ self.coeff_a = min(np.sqrt(4 / (self.kalman_data_container.dim_parameter)), 1)
91
+ self.coeff_c = self.coeff_a * np.sqrt(self.kalman_data_container.dim_parameter)
92
+ self.coeff_w = 1 / (2 * (self.kalman_data_container.dim_parameter) * self.coeff_a**2)
93
+
94
+ # cholesky factorization
95
+ cholesky_C = np.linalg.cholesky( self.posterior_covariance + self.eps * np.eye(self.kalman_data_container.dim_parameter))
96
+
97
+ # create symmetric ensemble
98
+ self.ensemble[0,:] = self.posterior_mean
99
+ self.ensemble[1:self.kalman_data_container.dim_parameter+1, :] = np.vstack([ self.posterior_mean + self.coeff_c * cholesky_C[:,j] for j in range(0,self.kalman_data_container.dim_parameter)])
100
+ self.ensemble[self.kalman_data_container.dim_parameter+1:, :] = np.vstack([ self.posterior_mean - self.coeff_c * cholesky_C[:,j] for j in range(0,self.kalman_data_container.dim_parameter)])
101
+
102
+
103
+ def predict_step(self):
104
+ # predict mean and covariance
105
+ self.posterior_mean = self.kalman_data_container.prior_mean + self.alpha_regularizer * (self.posterior_mean - self.kalman_data_container.prior_mean)
106
+ self.posterior_covariance = self.alpha_regularizer**2 * self.posterior_covariance + self.sigma_omega
107
+
108
+
109
+
110
+ def analysis_step(self):
111
+
112
+ # forward computation
113
+ self.y_forward = self.compute_forward_model(self.y_forward, self.ensemble)
114
+
115
+ # compute cross/ covariances
116
+ cov_theta_gamma = self.coeff_w * (self.ensemble[1:,:] - self.posterior_mean ).T @ (self.y_forward[1:,:] - self.y_forward[0,:]) # P x D
117
+ cov_gamma_gamma = self.coeff_w * (self.y_forward[1:,:] - self.y_forward[0,:]).T @ (self.y_forward[1:,:] - self.y_forward[0,:]) + self.sigma_nu # D x D
118
+
119
+ # residuals
120
+ Z = self.y_observations - self.y_forward[0,:] # J x D
121
+
122
+ # Kalman update
123
+ K = ( cov_theta_gamma @ np.linalg.solve( cov_gamma_gamma, Z.T) ).T # J x P
124
+
125
+ # parameters update
126
+ self.posterior_mean = self.posterior_mean + K
127
+ self.posterior_covariance = self.posterior_covariance - cov_theta_gamma @ np.linalg.solve( cov_gamma_gamma, cov_theta_gamma.T)
128
+
129
+
130
+
131
+
132
+
133
+
@@ -0,0 +1,96 @@
1
+ import numpy as np
2
+ import scipy as sp
3
+ #from tqdm.notebook import tqdm
4
+ from kalman_base_classes import BaseKalmanSampler, KalmanInversionDataContainer,BaseTimeStepScheduler
5
+
6
+
7
+
8
+
9
+ class EnsembleKalmanSampler(BaseKalmanSampler):
10
+ def __init__( self,
11
+
12
+ kalman_data_container:type(KalmanInversionDataContainer),
13
+ seed,
14
+ time_step_scheduler:type(BaseTimeStepScheduler),
15
+ eps,
16
+ max_step,
17
+ initial_ensemble,
18
+ J:int,
19
+ if_centered:bool=True,
20
+ if_cholesky_sqrt:bool=True,
21
+ ):
22
+
23
+ super().__init__(
24
+ kalman_data_container=kalman_data_container,
25
+ seed=seed,
26
+ time_step_scheduler=time_step_scheduler,
27
+ eps=eps,
28
+ max_step=max_step,
29
+ initial_ensemble=initial_ensemble,
30
+ if_centered=if_centered,
31
+ J=J
32
+ )
33
+
34
+
35
+
36
+ # covariance matrix factorization type
37
+ self.if_cholesky_sqrt = if_cholesky_sqrt
38
+
39
+ def compute_mean(self, y_forward, ensemble, covariance, J):
40
+
41
+ # y_forward = self.compute_forward_model(
42
+ # y_forward=y_forward,
43
+
44
+ # ensemble=(ensemble ),
45
+ # J=J,
46
+
47
+ # )
48
+ # ensemble covariance P x P
49
+ covariance_theta_theta = covariance
50
+
51
+ # residuals (J x D).T
52
+ Z = (y_forward - self.y_observations).T
53
+
54
+
55
+ # this is the gamma weighted product of < ( G(u) - \bar{G(u)}) x Gamma^{-1} x ( G(u) - y) > /J JxJ
56
+ gamma_weighted_product = (y_forward - y_forward.mean(0)) @ np.linalg.solve(self.kalman_data_container.Gamma_noise_matrix, Z) / J
57
+
58
+ # split-step discretization
59
+
60
+ # 1 step
61
+
62
+ # left handside of u*_{n+1}:
63
+ # (dt * C(U) * Sigma_0 ^{-1}):
64
+
65
+ left_handside = ( np.eye(self.kalman_data_container.dim_parameter) + self.time_step * np.linalg.solve(self.kalman_data_container.prior_covariance, covariance_theta_theta.T).T)
66
+
67
+
68
+ # right handside u^j_n - dt * gamma weighted product * U
69
+ right_handside = ensemble - self.time_step * gamma_weighted_product.T @ ensemble
70
+
71
+ # inverse of left handside: J x P
72
+ theta_mean = np.linalg.solve(left_handside, right_handside.T).T
73
+
74
+ # return mean and covariance
75
+ return theta_mean
76
+
77
+
78
+ def draw_proposal(self, proposed_mean, covariance, J):
79
+ # 2 step draw proposal
80
+ brown_noise = self.ki_random_gen.multivariate_normal(np.zeros(self.kalman_data_container.dim_parameter), np.eye(self.kalman_data_container.dim_parameter), J)
81
+
82
+ # matrix factorization
83
+ if self.if_cholesky_sqrt:
84
+ L = np.linalg.cholesky(covariance + self.eps * np.eye(self.kalman_data_container.dim_parameter))
85
+ else:
86
+ L = sp.linalg.sqrtm(0.5 * (covariance + covariance.T))
87
+
88
+
89
+ proposed_ensemble = proposed_mean + (np.sqrt(2 * self.time_step) * L @ brown_noise.T).T
90
+ return proposed_ensemble
91
+
92
+
93
+
94
+
95
+
96
+
@@ -0,0 +1,50 @@
1
+
2
+ import numpy as np
3
+ from kalman_base_classes import BaseTimeStepScheduler
4
+
5
+
6
+
7
+
8
+ # class BaseTimeStepScheduler:
9
+ # def __init__(self,kalman_object):
10
+ # self.kalman_object = kalman_object
11
+
12
+ # def compute_timestep(self):
13
+ # raise NotImplementedError("Subclasses must implement this method" )
14
+
15
+
16
+ class DataMisfitTimeStepScheduler(BaseTimeStepScheduler):
17
+
18
+ def compute_timestep(self):
19
+ self.misfit = 0.5 * (self.kalman_object.kalman_data_container.y_observations - self.kalman_object.y_forward[:, :self.kalman_object.kalman_data_container.dim_y ]) @ np.linalg.solve(self.kalman_object.kalman_data_container.Gamma_noise_matrix ,(self.kalman_object.kalman_data_container.y_observations - self.kalman_object.y_forward[:, :self.kalman_object.kalman_data_container.dim_y ]).T )
20
+
21
+ self.misfit_norm = np.diag(self.misfit)
22
+ step = max( self.kalman_object.kalman_data_container.dim_y/(2 * self.misfit_norm.mean(0)), np.sqrt(self.kalman_object.kalman_data_container.dim_y/(2 * self.misfit_norm.var(0) )))
23
+
24
+
25
+ return step
26
+
27
+ class AdaptiveTimeStepScheduler(BaseTimeStepScheduler):
28
+ def compute_timestep(self):
29
+
30
+ # this is the gamma weighted product of < ( G(u) - \bar{G(u)}) x Gamma^{-1} x ( G(u) - y) >/J
31
+ self.gamma_weighted_product = (self.kalman_object.y_forward[:, :self.kalman_object.kalman_data_container.dim_y ] - self.kalman_object.y_forward[:, :self.kalman_object.kalman_data_container.dim_y ].mean(0) ) @ np.linalg.solve(self.kalman_object.kalman_data_container.Gamma_noise_matrix ,(self.kalman_object.y_forward[:, :self.kalman_object.kalman_data_container.dim_y ] - self.kalman_object.kalman_data_container.y_observations ).T ) /self.kalman_object.J
32
+
33
+
34
+
35
+ # use gamma weighted product to update artificial time step
36
+ step = self.kalman_object.init_step / (np.linalg.norm( self.gamma_weighted_product, ord='fro') + 1e-8)
37
+
38
+
39
+ return step
40
+
41
+
42
+
43
+ class FixedTimeStepScheduler(BaseTimeStepScheduler):
44
+
45
+ def compute_timestep(self):
46
+ step = self.kalman_object.init_step
47
+
48
+
49
+ return step
50
+
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: kalman_inversion_lib
3
+ Version: 0.0.1
4
+ Summary: The package implements various Kalman Inversion algorithms in Python.
5
+ Author-email: Konstantin Ibadullaev <konstantin.ibadullaev.post@gmail.com>
6
+ License-Expression: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Operating System :: OS Independent
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Dynamic: license-file
13
+
14
+ # Kalman Inversion Lib Package
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ kalman_inversion_lib.egg-info/PKG-INFO
5
+ kalman_inversion_lib.egg-info/SOURCES.txt
6
+ kalman_inversion_lib.egg-info/dependency_links.txt
7
+ kalman_inversion_lib.egg-info/top_level.txt
8
+ kalman_inversion_lib/src/__init__.py
9
+ kalman_inversion_lib/src/diagnostics.py
10
+ kalman_inversion_lib/src/kalman_base_classes.py
11
+ kalman_inversion_lib/src/kalman_inversion_optimizers.py
12
+ kalman_inversion_lib/src/kalman_inversion_samplers.py
13
+ kalman_inversion_lib/src/timestep_schedulers.py
@@ -0,0 +1 @@
1
+ kalman_inversion_lib
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kalman_inversion_lib"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="Konstantin Ibadullaev", email="konstantin.ibadullaev.post@gmail.com" },
10
+ ]
11
+ description = "The package implements various Kalman Inversion algorithms in Python."
12
+ readme = "README.md"
13
+ requires-python = ">=3.10"
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ ]
18
+ license = "MIT"
19
+ license-files = ["LICEN[CS]E*"]
20
+
21
+ #[project.urls]
22
+ #Homepage = ""
23
+ #Issues = ""
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+