Source code for EnKF
#!/usr/bin/env/ python3
"""
An implementation of the Ensemble Kalman filter is provided for sequential data
assimilation. Use is through the Ensemble_Kalman class.
The EnKF aproximates the probability distribution of the solution through a
finite ensemble of members, (see also Markov Chain Monte Carlo). This estimate
is updated at each prediction step in a Bayesian fashion, with the predicted
ensemble forming the prior. Upon incorporating the measurement data, we have a
posterior distribution.
Classes
-------
- `Ensemble_Kalman` : Implements the filter.
| Author: Adam G. Peddle
| Contact: ap553@exeter.ac.uk
| Version: 1.0
"""
import sys
import os
import pickle
import numpy as np
import matplotlib.pyplot as plt
import cyclops_control
import RSWE_direct
import time
[docs]class Ensemble_Kalman:
"""
This class implements the update step of the ensemble Kalman filter. Once
initialised, the point of access for the user is through the apply method.
**Attributes**
- `Nx` : The spatial domain size
- `N` : the number of ensemble members
- `H` : the measurement operator such that
.. math :: d = H\psi
where :math:`d` is the vector of observations, :math:`H` is the measurement
operator, and :math:`\psi` is the full solution vector.
**Methods**
- `_ensemble_perturbation` : Computes the ensemble perturbation
- `_perturb_observations` : Computes the perturbed observations and
ensemble of perturbations
- `apply`: Applies the EnKF to a predicted solution for some measurement
**Parameters**
- `x_meas` : The indices where measurements are known on the solution grid
- `Nx` : The number of gridpoints
- `ensemble_size` : The number of ensemble members
**See Also**
- G. Evensen, The Ensemble Kalman Filter: theoretical formulation and practical implementation. 2003. Ocean Dynamics. DOI:10.1007/s10236-003-0036-9.
"""
def __init__(self, x_meas, Nx, ensemble_size):
self.Nx = Nx
self.N = ensemble_size
self.H = np.zeros((x_meas.shape[0], self.Nx))
self.H[np.arange(x_meas.shape[0]), x_meas] = 1
np.random.seed(0)
def _ensemble_perturbation(self, A):
"""
Computes the ensemble perturbation matrix of A, i.e.
.. math :: A' = A - \\overline{A}
**Parameters**
- `A` : Array holding the ensemble members. Is of size :math:`n\timesN`
where n is the size of the model state vector and N is the
number of ensemble members.
**Returns**
- The ensemble perturbation matrix.
"""
X = np.identity(self.N) - (1/self.N)*np.ones((self.N, self.N))
return np.dot(A, X)
def _perturb_observations(self, d):
"""
Creates a perturbed observation matrix from a vector of observations.
**Parameters**
- `d` : The vector of measurements (length m = number of measurements)
**Returns**
- `D` : The perturbed observation matrix
(size m x N, N is num ensemble members)
- `ens_perts` : The perturbations employed (size m x N)
**Notes**
The perturbations should have mean zero, which is hardcoded. Variance
can in theory be varied, but this is not implemented here.
"""
mean = 0
var = 0.10
D = np.tile(d, (1,self.N))
ens_perts = np.random.normal(mean, var, D.shape)
return D, ens_perts
[docs] def apply(self, A, d):
"""
Applies the EnKF, updating the predicted ensemble matrix with the
measured data. The result is the posterior distribution, i.e. the best
estimate for the state of the system.
Let n be the size of the model state vector, N be the number of
ensemble members, and m be the number of measurements.
**Parameters**
- `A` : the prior distribution (real, size n x N)
- `d` : the measurement vector (real, size m)
**Returns**
- The posterior distribution after data has been assimilated. This
matrix is suitable for continued use in MCMC solvers.
"""
A_p = self._ensemble_perturbation(A)
P_e = self._ensemble_perturbation(A_p)
D, ens_perts = self._perturb_observations(d)
R_e = np.dot(ens_perts, ens_perts.T)/(self.N-1)
D_p = D - np.dot(self.H, A)
A2 = np.dot(A_p, A_p.T)
X = np.dot(self.H, A2)
X = np.dot(X, self.H.T) + np.dot(ens_perts, ens_perts.T)
X = np.linalg.inv(X) # May need Moore-Penrose pseudo-inverse
RHS = np.dot(A2, self.H.T)
RHS = np.dot(RHS, X)
RHS = np.dot(RHS, D_p)
return A + RHS