diff --git a/GPy/kern/kern.py b/GPy/kern/kern.py index 9e930417..cf36e16c 100644 --- a/GPy/kern/kern.py +++ b/GPy/kern/kern.py @@ -1,6 +1,7 @@ # Copyright (c) 2012, GPy authors (see AUTHORS.txt). # Licensed under the BSD 3-clause license (see LICENSE.txt) +import sys import numpy as np import pylab as pb from ..core.parameterized import Parameterized @@ -577,7 +578,7 @@ class Kern_check_model(Model): def is_positive_definite(self): v = np.linalg.eig(self.kernel.K(self.X))[0] - if any(v<-1e-6): + if any(v<-sys.float_info.epsilon): return False else: return True diff --git a/GPy/kern/parts/eq_ode1.py b/GPy/kern/parts/eq_ode1.py new file mode 100644 index 00000000..1e5345fe --- /dev/null +++ b/GPy/kern/parts/eq_ode1.py @@ -0,0 +1,471 @@ +# Copyright (c) 2013, GPy Authors, see AUTHORS.txt +# Licensed under the BSD 3-clause license (see LICENSE.txt) + +from kernpart import Kernpart +import numpy as np +from GPy.util.linalg import mdot, pdinv +from GPy.util.ln_diff_erfs import ln_diff_erfs +import pdb +from scipy import weave + +class Eq_ode1(Kernpart): + """ + Covariance function for first order differential equation driven by an exponentiated quadratic covariance. + + This outputs of this kernel have the form + .. math:: + \frac{\text{d}y_j}{\text{d}t} = \sum_{i=1}^R w_{j,i} f_i(t-\delta_j) +\sqrt{\kappa_j}g_j(t) - d_jy_j(t) + + where :math:`R` is the rank of the system, :math:`w_{j,i}` is the sensitivity of the :math:`j`th output to the :math:`i`th latent function, :math:`d_j` is the decay rate of the :math:`j`th output and :math:`f_i(t)` and :math:`g_i(t)` are independent latent Gaussian processes goverened by an exponentiated quadratic covariance. + + :param output_dim: number of outputs driven by latent function. + :type output_dim: int + :param W: sensitivities of each output to the latent driving function. + :type W: ndarray (output_dim x rank). + :param rank: If rank is greater than 1 then there are assumed to be a total of rank latent forces independently driving the system, each with identical covariance. + :type rank: int + :param decay: decay rates for the first order system. + :type decay: array of length output_dim. + :param delay: delay between latent force and output response. + :type delay: array of length output_dim. + :param kappa: diagonal term that allows each latent output to have an independent component to the response. + :type kappa: array of length output_dim. + + .. Note: see first order differential equation examples in GPy.examples.regression for some usage. + """ + def __init__(self,output_dim, W=None, rank=1, kappa=None, lengthscale=1.0, decay=None, delay=None): + self.rank = rank + self.input_dim = 1 + self.name = 'eq_ode1' + self.output_dim = output_dim + self.lengthscale = lengthscale + self.num_params = self.output_dim*(1. + self.rank) + 1 + if kappa is not None: + self.num_params+=self.output_dim + if delay is not None: + self.num_params+=self.output_dim + self.rank = rank + if W is None: + self.W = 0.5*np.random.randn(self.output_dim,self.rank)/np.sqrt(self.rank) + else: + assert W.shape==(self.output_dim,self.rank) + self.W = W + if decay is None: + self.decay = np.ones(self.output_dim) + if kappa is not None: + assert kappa.shape==(self.output_dim,) + self.kappa = kappa + + if delay is not None: + assert delay.shape==(self.output_dim,) + self.delay = delay + self.is_normalized = True + self.is_stationary = False + self._set_params(self._get_params()) + + def _get_params(self): + param_list = [self.W.flatten()] + if self.kappa is not None: + param_list.append(self.kappa) + param_list.append(self.decay) + if self.delay is not None: + param_list.append(self.delay) + param_list.append(self.lengthscale) + return np.hstack(param_list) + + def _set_params(self,x): + assert x.size == self.num_params + end = self.output_dim*self.rank + self.W = x[:end].reshape(self.output_dim,self.rank) + start = end + self.B = np.dot(self.W,self.W.T) + if self.kappa is not None: + end+=self.output_dim + self.kappa = x[start:end] + self.B += np.diag(self.kappa) + start=end + end+=self.output_dim + self.decay = x[start:end] + start=end + if self.delay is not None: + end+=self.output_dim + self.delay = x[start:end] + start=end + end+=1 + self.lengthscale = x[start] + self.sigma = np.sqrt(2)*self.lengthscale + + + def _get_param_names(self): + param_names = sum([['W%i_%i'%(i,j) for j in range(self.rank)] for i in range(self.output_dim)],[]) + if self.kappa is not None: + param_names += ['kappa_%i'%i for i in range(self.output_dim)] + param_names += ['decay_%i'%i for i in range(self.output_dim)] + if self.delay is not None: + param_names += ['delay_%i'%i for i in range(self.output_dim)] + param_names+= ['lengthscale'] + return param_names + + def K(self,X,X2,target): + + if X.shape[1] > 2: + raise ValueError('Input matrix for ode1 covariance should have at most two columns, one containing times, the other output indices') + + self._K_computations(X, X2) + target += self._scales*self._dK_dvar + + if self.gaussian_initial: + # Add covariance associated with initial condition. + t1_mat = self._t[self._rorder, None] + t2_mat = self._t2[None, self._rorder2] + target+=self.initial_variance * np.exp(- self.decay * (t1_mat + t2_mat)) + + + + def Kdiag(self,index,target): + #target += np.diag(self.B)[np.asarray(index,dtype=np.int).flatten()] + pass + + def dK_dtheta(self,dL_dK,index,index2,target): + pass + + def dKdiag_dtheta(self,dL_dKdiag,index,target): + pass + + def dK_dX(self,dL_dK,X,X2,target): + pass + + def _extract_t_indices(self, X, X2=None): + """Extract times and output indices from the input matrix X. Times are ordered according to their index for convenience of computation, this ordering is stored in self._order and self.order2. These orderings are then mapped back to the original ordering (in X) using self._rorder and self._rorder2. """ + + # TODO: some fast checking here to see if this needs recomputing? + self._t = X[:, 0] + if X.shape[1]==1: + # No index passed, assume single output of ode model. + self._index = np.ones_like(X[:, 1],dtype=np.int) + self._index = np.asarray(X[:, 1],dtype=np.int) + # Sort indices so that outputs are in blocks for computational + # convenience. + self._order = self._index.argsort() + self._index = self._index[self._order] + self._t = self._t[self._order] + self._rorder = self._order.argsort() # rorder is for reversing the order + + if X2 is None: + self._t2 = None + self._index2 = None + self._rorder2 = self._rorder + else: + if X2.shape[1] > 2: + raise ValueError('Input matrix for ode1 covariance should have at most two columns, one containing times, the other output indices') + self._t2 = X2[:, 0] + if X.shape[1]==1: + # No index passed, assume single output of ode model. + self._index2 = np.ones_like(X2[:, 1],dtype=np.int) + self._index2 = np.asarray(X2[:, 1],dtype=np.int) + self._order2 = self._index2.argsort() + slef._index2 = self._index2[self._order2] + self._t2 = self._t2[self._order2] + self._rorder2 = self._order2.argsort() # rorder2 is for reversing order + + def _K_computations(self, X, X2): + """Perform main body of computations for the ode1 covariance function.""" + # First extract times and indices. + self._extract_t_indices(X, X2) + + self._K_compute_eq() + self._K_compute_ode_eq() + if X2 is None: + self._K_eq_ode = self._K_ode_eq.T + else: + self._K_compute_ode_eq(transpose=True) + self._K_compute_ode() + + # Reorder values of blocks for placing back into _K_dvar. + self._K_dvar[self._rorder, :] = np.vstack((np.hstack((self._K_eq, self._Keq_ode)), + np.hstack((self._K_ode_eq, self.K_ode))))[:, self._rorder2] + + + def _K_compute_eq(self): + """Compute covariance for latent covariance.""" + t_eq = self._t[self._index==0] + if t_eq.shape[0]==0: + self._K_eq = np.zeros((0, 0)) + return + + if self._t2 is None: + self._dist2 = np.square(t_eq[:, None] - t_eq[None, :]) + else: + t2_eq = self._t2[self._index2==0] + if t2_eq.shape[0]==0: + self._K_eq_eq = np.zeros((0, 0)) + return + self._dist2 = np.square(t_eq[:, None] - t2_eq[None, :]) + + self._K_eq = np.exp(-self._dist2/(2*self.lengthscale*self.lengthscale)) + if self.is_normalized: + self._K_eq/=(np.sqrt(2*np.pi)*self.lengthscale) + + def _K_compute_ode_eq(self, transpose=False): + """Compute the cross covariances between latent exponentiated quadratic and observed ordinary differential equations. + + :param transpose: if set to false the exponentiated quadratic is on the rows of the matrix and is computed according to self._t, if set to true it is on the columns and is computed according to self._t2 (default=False). + :type transpose: bool""" + + if transpose: + if self._t2 is not None: + t_ode = self._t2[self._index2>0] + index_ode = self._index2[self._index2>0]-1 + if t_ode.shape[0]==0: + self._K_eq_ode = np.zeros((0, 0)) + return + else: + self._K_eq_ode = np.zeros((0, 0)) + return + + t_eq = self._t[self._index==0] + if t_eq.shape[0]==0: + self._K_eq_ode = np.zeros((0, 0)) + return + else: + t_ode = self._t[self._index>0] + index_ode = self._index[self._index>0]-1 + if t_ode.shape[0]==0: + self._K_ode_eq = np.zeros((0, 0)) + return + if self._t2 is not None: + t_eq = self._t2[self._index2==0] + if t_eq.shape[0]==0: + self._K_ode_eq = np.zeros((0, 0)) + return + else: + self._K_ode_eq = np.zeros((0, 0)) + return + # Matrix giving scales of each output + # self._scale = np.zeros((t_ode.shape[0], t_eq.shape[0])) + # code=""" + # for(int i=0;i0] + index_ode = self._index[self._index>0]-1 + if t_ode.shape[0]==0: + self._K_ode = np.zeros((0, 0)) + return + + if self._t2 is None: + t2_ode = t_ode + index2_ode = index_ode + else: + t2_ode = self._t2[self._index2>0] + index2_ode = self._index2[self._index2>0]-1 + if t2_eq.shape[0]==0: + self._K_ode = np.zeros((0, 0)) + return + + if self._index2 is None: + # Matrix giving scales of each output + self._scale = np.zeros((t_ode.shape[0], t_ode.shape[0])) + code=""" + for(int i=0;i