Repository: cornellius-gp/gpytorch

Issue:
Multitask KISS-GP single test point error
The problem from #247 seems to preserve for the particular single test points, e.g., if the grid is
 `[-3.3333, -1.6667,  0.0000,  1.6667,  3.3333]` when provided `grid_size=5` and `grid_bounds=[(-2,2)]`, then prediction will fail on any single test point outside of the range of `[0.0000,  1.6667]` (notebook attached):

```
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-14-dd2c9e04445d> in <module>()
      1 # Does not work
      2 test_x = torch.FloatTensor([-0.1]).unsqueeze(1)
----> 3 model(test_x)

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/models/exact_gp.py in __call__(self, *args, **kwargs)
    142                 n_train=n_train,
    143                 likelihood=self.likelihood,
--> 144                 precomputed_cache=self.mean_cache,
    145             )
    146             predictive_covar, covar_cache = exact_predictive_covar(

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/functions/__init__.py in exact_predictive_mean(full_covar, full_mean, train_labels, n_train, likelihood, precomputed_cache)
     89 
     90         full_covar = NonLazyVariable(full_covar)
---> 91     return full_covar.exact_predictive_mean(full_mean, train_labels, n_train, likelihood, precomputed_cache)
     92 
     93 

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/lazy/lazy_evaluated_kernel_variable.py in exact_predictive_mean(self, full_mean, train_labels, n_train, likelihood, precomputed_cache)
    124         else:
    125             return super(LazyEvaluatedKernelVariable, self).exact_predictive_mean(
--> 126                 full_mean, train_labels, n_train, likelihood, precomputed_cache
    127             )
    128 

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/lazy/lazy_variable.py in exact_predictive_mean(self, full_mean, train_labels, n_train, likelihood, precomputed_cache)
    427         else:
    428             test_train_covar = self[n_train:, :n_train]
--> 429         res = test_train_covar.matmul(precomputed_cache)
    430         if res.ndimension() == 3:
    431             res = res.squeeze(-1)

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/lazy/lazy_variable.py in matmul(self, tensor)
    632             raise RuntimeError
    633 
--> 634         func = Matmul(self.representation_tree())
    635         return func(tensor, *self.representation())
    636 

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/lazy/lazy_evaluated_kernel_variable.py in representation_tree(self)
    112 
    113     def representation_tree(self):
--> 114         return LazyVariableRepresentationTree(self.evaluate_kernel())
    115 
    116     def evaluate(self):

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/lazy/lazy_evaluated_kernel_variable.py in evaluate_kernel(self)
     96                 x2 = self.x2
     97 
---> 98             self._cached_kernel_eval = super(Kernel, self.kernel).__call__(x1, x2, **self.params)
     99             if self.squeeze_row:
    100                 self._cached_kernel_eval.squeeze_(-2)

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/module.py in __call__(self, *inputs, **kwargs)
    160 
    161     def __call__(self, *inputs, **kwargs):
--> 162         outputs = self.forward(*inputs, **kwargs)
    163         if torch.is_tensor(outputs) or isinstance(outputs, RandomVariable) or isinstance(outputs, LazyVariable):
    164             return outputs

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/kernels/multitask_kernel.py in forward(self, x1, x2)
     61     def forward(self, x1, x2):
     62         covar_i = self.task_covar_module.covar_matrix
---> 63         covar_x = self.data_covar_module.forward(x1, x2)
     64         if covar_x.size(0) == 1:
     65             covar_x = covar_x[0]

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/kernels/grid_interpolation_kernel.py in forward(self, x1, x2, **kwargs)
     56             base_lazy_var = base_lazy_var.repeat(x1.size(0), 1, 1)
     57 
---> 58         left_interp_indices, left_interp_values = self._compute_grid(x1)
     59         if torch.equal(x1.data, x2.data):
     60             right_interp_indices = left_interp_indices

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/kernels/grid_interpolation_kernel.py in _compute_grid(self, inputs)
     39         batch_size, n_data, n_dimensions = inputs.size()
     40         inputs = inputs.view(batch_size * n_data, n_dimensions)
---> 41         interp_indices, interp_values = Interpolation().interpolate(Variable(self.grid), inputs)
     42         interp_indices = interp_indices.view(batch_size, n_data, -1)
     43         interp_values = interp_values.view(batch_size, n_data, -1)

~/anaconda/envs/py3/lib/python3.6/site-packages/gpytorch/utils/interpolation.py in interpolate(self, x_grid, x_target, interp_points)
    114                     dim_interp_values[left_boundary_pts[j], :] = 0
    115                     dim_interp_values[left_boundary_pts[j], closest_from_first[j]] = 1
--> 116                     lower_grid_pt_idxs[left_boundary_pts[j]] = 0
    117 
    118             right_boundary_pts = torch.nonzero(lower_grid_pt_idxs > num_grid_points - num_coefficients)

IndexError: too many indices for tensor of dimension 0
```
[KISS-GP-multitask.ipynb.zip](https://github.com/cornellius-gp/gpytorch/files/2343245/KISS-GP-multitask.ipynb.zip)


Discussion:
Looking in to this a bit, this appears to be an issue exclusive to having a single point in `Interpolation`. Predicting on two copies of `-0.1` instead of 1 copy works (e.g. if `test_x` is `2 x 1`

Resolving patch:
diff --git a/gpytorch/utils/interpolation.py b/gpytorch/utils/interpolation.py
--- a/gpytorch/utils/interpolation.py
+++ b/gpytorch/utils/interpolation.py
@@ -100,6 +100,9 @@ def interpolate(self, x_grid, x_target, interp_points=range(-2, 2)):
             lower_grid_pt_idxs = lower_grid_pt_idxs - interp_points.max()
             lower_grid_pt_idxs.detach_()
 
+            if len(lower_grid_pt_idxs.shape) == 0:
+                lower_grid_pt_idxs = lower_grid_pt_idxs.unsqueeze(0)
+
             scaled_dist = lower_pt_rel_dists.unsqueeze(-1) + interp_points_flip.unsqueeze(-2)
             dim_interp_values = self._cubic_interpolation_kernel(scaled_dist)
 


Test patch: