def K(self, X, X2=None):
if X2 is None:<EOL><INDENT>K_xx = np.zeros([X.shape[<NUM_LIT:0>],X.shape[<NUM_LIT:0>]])<EOL>for i,x in enumerate(X):<EOL><INDENT>for j,x2 in enumerate(X):<EOL><INDENT>K_xx[i,j] = self.k_xx(x[<NUM_LIT:0>],x2[<NUM_LIT:0>],x[<NUM_LIT:1>],x2[<NUM_LIT:1>],self.lengthscale[<NUM_LIT:0>])<EOL><DEDENT><DEDENT>return K_xx * self.variances[<NUM_LIT:0>]<EOL><DEDENT>else:<EOL><INDENT>K_xf = np.zeros([X.shape[<NUM_LIT:0>],X2.shape[<NUM_LIT:0>]])<EOL>for i,x in enumerate(X):<EOL><INDENT>for j,x2 in enumerate(X2):<EOL><INDENT>K_xf[i,j] = self.k_xf(x[<NUM_LIT:0>],x2[<NUM_LIT:0>],x[<NUM_LIT:1>],self.lengthscale[<NUM_LIT:0>]) <EOL><DEDENT><DEDENT>return K_xf * self.variances[<NUM_LIT:0>]<EOL><DEDENT>
Note: We have a latent function and an output function. We want to be able to find:
          - the covariance between values of the output function
          - the covariance between values of the latent function
          - the "cross covariance" between values of the output function and the latent function
        This method is used by GPy to either get the covariance between the outputs (K_xx) or
        is used to get the cross covariance (between the latent function and the outputs (K_xf).
        We take advantage of the places where this function is used:
         - if X2 is none, then we know that the items being compared (to get the covariance for)
         are going to be both from the OUTPUT FUNCTION.
         - if X2 is not none, then we know that the items being compared are from two different
         sets (the OUTPUT FUNCTION and the LATENT FUNCTION).

        If we want the covariance between values of the LATENT FUNCTION, we take advantage of
        the fact that we only need that when we do prediction, and this only calls Kdiag (not K).
        So the covariance between LATENT FUNCTIONS is available from Kdiag.