Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bbd2556
Hessian correction adapted for narrowband SAIL
NPounder May 22, 2018
ef84d0b
LAI propagator and prior for narrow band SAIL
NPounder May 24, 2018
d227efa
Add hessian calculation to the broadband SAIL observation operator
NPounder Jun 4, 2018
e4330f2
warning in logfile when hessian correction results in negative values
NPounder Jun 15, 2018
4150bbc
separate broadband SAIL utils into seperate file
NPounder Jun 15, 2018
4baa423
example script to run with latest propagators
NPounder Jun 15, 2018
d19d703
Added some comments
NPounder Jun 15, 2018
8ed511d
example scripts to run with latest propagators
NPounder Jun 15, 2018
d9c6df9
remove unecessary tile logging
NPounder Jun 15, 2018
daba582
match filepaths to latest output
NPounder Jun 15, 2018
addc941
broadband sail tools
NPounder Jun 20, 2018
2ac9a43
Delete empty file
NPounder Jun 20, 2018
7955fa2
Save state to npz files
GerardoLopez Jun 21, 2018
a50576c
Merge branch 'hess_corr_state_mapper' of https://github.com/Assimila/…
GerardoLopez Jun 21, 2018
8119c6c
Hessian calc for prosail handles case with masked elements
NPounder Aug 20, 2018
b4504b4
place error in logfile when a retrieval of a fully masked observation…
NPounder Aug 20, 2018
34256d9
return 0 when value error
TonioF Nov 29, 2018
0a9dacd
actually bail out after 25 iterations
TonioF Nov 29, 2018
d9119a7
fix
TonioF Nov 30, 2018
29389fa
removed unused imports
TonioF Nov 30, 2018
0394856
performance improvement
TonioF Nov 30, 2018
ca1cab9
fix
TonioF Nov 30, 2018
b32339e
also write out state mask
TonioF Dec 3, 2018
f289504
advance at the end of the loop not at the beginning
TonioF Dec 4, 2018
f2fc4b1
do not perform hessian correction
TonioF Dec 11, 2018
29170e2
added version
TonioF Jun 27, 2019
0432dfe
updated requirements
TonioF Nov 7, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kafka/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .version import __version__
from .inference import *
from .input_output import *
from .linear_kf import LinearKalman
Expand Down
97 changes: 97 additions & 0 deletions kafka/inference/broadbandSAIL_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import logging
import gdal
import numpy as np
import os

from .utils import block_diag
from .kf_tools import propagate_single_parameter


def tip_prior_values():
"""The JRC-TIP prior in a convenient function which is fun for the whole
family. Note that the effective LAI is here defined in transformed space
where TLAI = exp(-0.5*LAIe).

Returns
-------
The mean prior vector, covariance and inverse covariance matrices."""
# broadly TLAI 0->7 for 1sigma
sigma = np.array([0.12, 0.7, 0.0959, 0.15, 1.5, 0.2, 0.5])
x0 = np.array([0.17, 1.0, 0.1, 0.7, 2.0, 0.18, np.exp(-0.5*1.5)])
# The individual covariance matrix
little_p = np.diag(sigma**2).astype(np.float32)
little_p[5, 2] = 0.8862*0.0959*0.2
little_p[2, 5] = 0.8862*0.0959*0.2

inv_p = np.linalg.inv(little_p)
return x0, little_p, inv_p


class JRCPrior(object):
"""Dummpy 2.7/3.6 prior class following the same interface as 3.6 only
version."""

def __init__(self, parameter_list, state_mask):
"""It makes sense to have the list of parameters and state mask
defined at this point, as they won't change during processing."""
self.mean, self.covar, self.inv_covar = self._tip_prior()
self.parameter_list = parameter_list
if isinstance(state_mask, (np.ndarray, np.generic) ):
self.state_mask = state_mask
else:
self.state_mask = self._read_mask(state_mask)

def _read_mask(self, fname):
"""Tries to read the mask as a GDAL dataset"""
if not os.path.exists(fname):
raise IOError("State mask is neither an array or a file that exists!")
g = gdal.Open(fname)
if g is None:
raise IOError("{:s} can't be opened with GDAL!".format(fname))
mask = g.ReadAsArray()
return mask

def _tip_prior(self):
"""The JRC-TIP prior in a convenient function which is fun for the whole
family. Note that the effective LAI is here defined in transformed space
where TLAI = exp(-0.5*LAIe).

Returns
-------
The mean prior vector, covariance and inverse covariance matrices."""
mean, c_prior, c_inv_prior = tip_prior_values()
self.mean = mean
self.covar = c_prior
self.inv_covar = c_inv_prior
return mean, c_prior, c_inv_prior

def process_prior ( self, time, inv_cov=True):
# Presumably, self._inference_prior has some method to retrieve
# a bunch of files for a given date...
n_pixels = self.state_mask.sum()
x0 = np.array([self.mean for i in range(n_pixels)]).flatten()
if inv_cov:
inv_covar_list = [self.inv_covar for m in range(n_pixels)]
inv_covar = block_diag(inv_covar_list, dtype=np.float32)
return x0, inv_covar
else:
covar_list = [self.covar for m in range(n_pixels)]
covar = block_diag(covar_list, dtype=np.float32)
return x0, covar


def propagate_LAI_broadbandSAIL(x_analysis, P_analysis,
P_analysis_inverse,
M_matrix, Q_matrix,
prior=None, state_propagator=None, date=None):
"""Propagate a single parameter and
set the rest of the parameter propagations to the prior.
This should be used with a prior for the remaining parameters"""
nparameters = 7
lai_position = 6
x_prior, c_prior, c_inv_prior = tip_prior_values()
return propagate_single_parameter(x_analysis, P_analysis,
P_analysis_inverse,
M_matrix, Q_matrix,
nparameters, lai_position,
x_prior, c_inv_prior)
134 changes: 49 additions & 85 deletions kafka/inference/kf_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,60 +16,55 @@ class NoHessianMethod(Exception):
def __init__(self, message):
self.message = message

def band_selecta(band):
if band == 0:
return np.array([0, 1, 6, 2])
else:
return np.array([3, 4, 6, 5])


def hessian_correction_pixel(gp, x0, C_obs_inv, innovation, band, nparams):
selecta = band_selecta(band)
ddH = gp.hessian(np.atleast_2d(x0[selecta]))
big_ddH = np.zeros((nparams, nparams))
for i, ii in enumerate(selecta):
for j, jj in enumerate(selecta):
big_ddH[ii, jj] = ddH.squeeze()[i, j]
big_hessian_corr = big_ddH*C_obs_inv*innovation
return big_hessian_corr
def hessian_correction_pixel(hessian, C_obs_inv, innovation):
hessian_corr = hessian*C_obs_inv*innovation
return hessian_corr


def hessian_correction(gp, x0, R_mat, innovation, mask, state_mask, band,
def hessian_correction(hessian, R_mat, innovation, mask, state_mask,
nparams):
"""Calculates higher order Hessian correction for the likelihood term.
Needs the GP, the Observational uncertainty, the mask...."""
if not hasattr(gp, "hessian"):
if hessian is None:
# The observation operator does not provide a Hessian method. We just
# return 0, meaning no Hessian correction.
return 0.
C_obs_inv = R_mat.diagonal()[state_mask.flatten()]
mask = mask[state_mask].flatten()

little_hess = []
for i, (innov, C, m) in enumerate(zip(innovation, C_obs_inv, mask)):
for i, (innov, C, m, hess) in enumerate(zip(innovation, C_obs_inv, mask, hessian)):
if not m:
# Pixel is masked
hessian_corr = np.zeros((nparams, nparams))
else:
# Get state for current pixel
x0_pixel = x0.squeeze()[(nparams*i):(nparams*(i + 1))]
# Calculate the Hessian correction for this pixel
hessian_corr = m * hessian_correction_pixel(gp, x0_pixel, C,
innov, band, nparams)
hessian_corr = m * hessian_correction_pixel(hess, C, innov)
little_hess.append(hessian_corr)
hessian_corr = block_diag(little_hess)

hessian_corr = block_diag(hessian)
return hessian_corr


def hessian_correction_multiband(gp, x0, R_mats, innovations, masks, state_mask, n_bands,
nparams):
def hessian_correction_multiband(hessians, R_mats, innovations,
masks, state_mask, n_bands, nparams):
""" Non linear correction for the Hessian of the cost function. This handles
multiple bands. """
little_hess_cor = []
for R, innovation, mask, band in zip(R_mats, innovations, masks, range(n_bands)):
little_hess_cor.append(hessian_correction(gp, x0, R, innovation, mask, state_mask, band,
nparams))
hessian_corr = sum(little_hess_cor) #block_diag(little_hess_cor)
return hessian_corr
R = []
hessian = []
innovation = []
mask = []
try:
little_hess_cor = []
for R, hessian, innovation, mask in zip(
R_mats, hessians, innovations, masks):
little_hess_cor.append(hessian_correction(hessian, R, innovation, mask, state_mask, nparams))
hessian_corr = sum(little_hess_cor)
return hessian_corr
except ValueError:
logging.info(R.shape, hessian.shape, innovation.shape, mask.shape)
return 0


def blend_prior(prior_mean, prior_cov_inverse, x_forecast, P_forecast_inverse):
Expand All @@ -96,43 +91,6 @@ def blend_prior(prior_mean, prior_cov_inverse, x_forecast, P_forecast_inverse):
return x_combined, combined_cov_inv


def tip_prior():
"""The JRC-TIP prior in a convenient function which is fun for the whole
family. Note that the effective LAI is here defined in transformed space
where TLAI = exp(-0.5*LAIe).

Returns
-------
The mean prior vector, covariance and inverse covariance matrices."""
# broadly TLAI 0->7 for 1sigma
sigma = np.array([0.12, 0.7, 0.0959, 0.15, 1.5, 0.2, 0.5])
x0 = np.array([0.17, 1.0, 0.1, 0.7, 2.0, 0.18, np.exp(-0.5*1.5)])
# The individual covariance matrix
little_p = np.diag(sigma**2).astype(np.float32)
little_p[5, 2] = 0.8862*0.0959*0.2
little_p[2, 5] = 0.8862*0.0959*0.2

inv_p = np.linalg.inv(little_p)
return x0, little_p, inv_p

def tip_prior_noLAI(prior):
n_pixels = prior['n_pixels']
mean, prior_cov_inverse = tip_prior(prior)


def tip_prior_full(prior):
# This is yet to be properly defined. For now it will create the TIP prior and
# prior just contains the size of the array - this function will be replaced with
# the real code when we know what the priors look like.
x_prior, c_prior, c_inv_prior = tip_prior()
n_pixels = prior['n_pixels']
mean = np.array([x_prior for i in range(n_pixels)]).flatten()
c_inv_prior_mat = [c_inv_prior for n in range(n_pixels)]
prior_cov_inverse=block_diag(c_inv_prior_mat, dtype=np.float32)

return mean, prior_cov_inverse


def propagate_and_blend_prior(x_analysis, P_analysis, P_analysis_inverse,
M_matrix, Q_matrix,
prior=None, state_propagator=None, date=None):
Expand Down Expand Up @@ -240,10 +198,11 @@ def propagate_information_filter_SLOW(x_analysis, P_analysis, P_analysis_inverse
S= P_analysis_inverse.dot(Q_matrix)
A = (sp.eye(n) + S).tocsc()
P_forecast_inverse = spl.spsolve(A, P_analysis_inverse)
logging.info("DOne with propagation")
logging.info("Done with propagation")

return x_forecast, None, P_forecast_inverse


def propagate_information_filter_approx_SLOW(x_analysis, P_analysis, P_analysis_inverse,
M_matrix, Q_matrix,
prior=None, state_propagator=None, date=None):
Expand Down Expand Up @@ -289,35 +248,40 @@ def propagate_information_filter_approx_SLOW(x_analysis, P_analysis, P_analysis_
return x_forecast, None, P_forecast_inverse


def propagate_information_filter_LAI(x_analysis, P_analysis,
P_analysis_inverse,
M_matrix, Q_matrix,
prior=None, state_propagator=None, date=None):

def propagate_single_parameter(x_analysis, P_analysis, P_analysis_inverse,
M_matrix, Q_matrix, n_param, location,
x_prior, c_inv_prior):
""" Propagate a single parameter and
set the rest of the parameter propagations to the prior.
This should be used with a prior for the remaining parameters"""
x_forecast = M_matrix.dot(x_analysis)
x_prior, c_prior, c_inv_prior = tip_prior()
n_pixels = len(x_analysis)//7
x0 = np.array([x_prior for i in range(n_pixels)]).flatten()
x0[6::7] = x_forecast[6::7] # Update LAI
lai_post_cov = P_analysis_inverse.diagonal()[6::7]
lai_Q = Q_matrix.diagonal()[6::7]
n_pixels = len(x_analysis)//n_param
x0 = np.tile(x_prior, n_pixels)
x0[location::n_param] = x_forecast[location::n_param] # Update LAI
lai_post_cov = P_analysis_inverse.diagonal()[location::n_param]
lai_Q = Q_matrix.diagonal()[location::n_param]

c_inv_prior_mat = []
for n in range(n_pixels):
for cov, Q in zip(lai_post_cov, lai_Q):
# inflate uncertainty
lai_inv_cov = 1.0/((1.0/lai_post_cov[n])+lai_Q[n])
lai_inv_cov = 1.0/((1.0/cov)+Q)
little_P_forecast_inverse = c_inv_prior.copy()
little_P_forecast_inverse[6, 6] = lai_inv_cov
little_P_forecast_inverse[location::location] = lai_inv_cov
c_inv_prior_mat.append(little_P_forecast_inverse)
P_forecast_inverse=block_diag(c_inv_prior_mat, dtype=np.float32)

return x0, None, P_forecast_inverse


def no_propagation(x_analysis, P_analysis,
P_analysis_inverse,
M_matrix, Q_matrix,
prior=None, state_propagator=None, date=None):
"""No propagation. In this case, we return the original prior. As the
"""
THIS PROPAGATOR SHOULD NOT BE USED ANY MORE. It is better to set
the state_propagator to None and to use the Prior exlicitly.

THIS IS ONLY SUITABLE FOR BROADBAND SAIL uses TIP prior
No propagation. In this case, we return the original prior. As the
information filter behaviour is the standard behaviour in KaFKA, we
only return the inverse covariance matrix. **NOTE** the input parameters
are there to comply with the API, but are **UNUSED**.
Expand Down
82 changes: 82 additions & 0 deletions kafka/inference/narrowbandSAIL_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import logging
import gdal
import numpy as np
import os

from .utils import block_diag
from .kf_tools import propagate_single_parameter

def sail_prior_values():
"""
:returns
-------
The mean prior vector, covariance and inverse covariance matrices."""
#parameter_list = ['n', 'cab', 'car', 'cbrown', 'cw', 'cm',
# 'lai', 'ala', 'bsoil', 'psoil']
mean = np.array([2.1, np.exp(-60. / 100.),
np.exp(-7.0 / 100.), 0.1,
np.exp(-50 * 0.0176), np.exp(-100. * 0.002),
np.exp(-4. / 2.), 70. / 90., 0.5, 0.9])
sigma = np.array([0.01, 0.2,
0.01, 0.05,
0.01, 0.01,
0.50, 0.1, 0.1, 0.1])

covar = np.diag(sigma ** 2).astype(np.float32)
inv_covar = np.diag(1. / sigma ** 2).astype(np.float32)
return mean, covar, inv_covar

class SAILPrior(object):
def __init__(self, parameter_list, state_mask):
self.parameter_list = parameter_list
if isinstance(state_mask, (np.ndarray, np.generic)):
self.state_mask = state_mask
else:
self.state_mask = self._read_mask(state_mask)
mean, c_prior, c_inv_prior = sail_prior_values()
self.mean = mean
self.covar = c_prior
self.inv_covar = c_inv_prior


def _read_mask(self, fname):
"""Tries to read the mask as a GDAL dataset"""
if not os.path.exists(fname):
raise IOError("State mask is neither an array or a file that exists!")
g = gdal.Open(fname)
if g is None:
raise IOError("{:s} can't be opened with GDAL!".format(fname))
mask = g.ReadAsArray()
return mask

def process_prior ( self, time, inv_cov=True):
# Presumably, self._inference_prior has some method to retrieve
# a bunch of files for a given date...
n_pixels = self.state_mask.sum()
x0 = np.array([self.mean for i in range(n_pixels)]).flatten()
if inv_cov:
inv_covar_list = [self.inv_covar for m in range(n_pixels)]
inv_covar = block_diag(inv_covar_list, dtype=np.float32)
return x0, inv_covar
else:
covar_list = [self.covar for m in range(n_pixels)]
covar = block_diag(covar_list, dtype=np.float32)
return x0, covar


def propagate_LAI_narrowbandSAIL(x_analysis, P_analysis,
P_analysis_inverse,
M_matrix, Q_matrix,
prior=None, state_propagator=None, date=None):
''' Propagate a single parameter and
set the rest of the parameter propagations to zero
This should be used with a prior for the remaining parameters'''
nparameters = 10
lai_position = 6

x_prior, c_prior, c_inv_prior = sail_prior_values()
return propagate_single_parameter(x_analysis, P_analysis,
P_analysis_inverse,
M_matrix, Q_matrix,
nparameters, lai_position,
x_prior, c_inv_prior)
Loading