prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  [Docs] Missing Grid.py documentation
  # 📚 Documentation/Examples
  
  ** Is there documentation missing? **
  The utils section of [GPyTorch documentation](https://gpytorch.readthedocs.io) does not include any information on grid.py, which is referenced [elsewhere in the docs](https://docs.gpytorch.ai/en/stable/kernels.html?highlight=choose_grid_size#gpytorch.kernels.GridKernel.update_grid).
  
  
  </issue>
  <code>
  [start of gpytorch/utils/grid.py]
  1 #!/usr/bin/env python3
  2 
  3 import math
  4 from typing import List, Tuple
  5 
  6 import torch
  7 
  8 
  9 def scale_to_bounds(x, lower_bound, upper_bound):
  10     """
  11     Scale the input data so that it lies in between the lower and upper bounds.
  12 
  13     Args:
  14         :attr:`x` (Tensor `n` or `b x n`):
  15             the input
  16         :attr:`lower_bound` (float)
  17         :attr:`upper_bound` (float)
  18 
  19     Returns:
  20         :obj:`torch.Tensor`
  21     """
  22     # Scale features so they fit inside grid bounds
  23     min_val = x.min()
  24     max_val = x.max()
  25     diff = max_val - min_val
  26     x = (x - min_val) * (0.95 * (upper_bound - lower_bound) / diff) + 0.95 * lower_bound
  27     return x
  28 
  29 
  30 def choose_grid_size(train_inputs, ratio=1.0, kronecker_structure=True):
  31     """
  32     Given some training inputs, determine a good grid size for KISS-GP.
  33 
  34     Args:
  35         :attr:`train_inputs` (Tensor `n` or `n x d` or `b x n x d`):
  36             training data
  37         :attr:`ratio` (float, optional):
  38             Ratio - number of grid points to the amount of data (default: 1.)
  39         :attr:`kronecker_structure` (bool, default=True):
  40             Whether or not the model will use Kronecker structure in the grid
  41             (set to True unless there is an additive or product decomposition in the prior)
  42 
  43     Returns:
  44         :obj:`int`
  45     """
  46     # Scale features so they fit inside grid bounds
  47     num_data = train_inputs.numel() if train_inputs.dim() == 1 else train_inputs.size(-2)
  48     num_dim = 1 if train_inputs.dim() == 1 else train_inputs.size(-1)
  49     if kronecker_structure:
  50         return int(ratio * math.pow(num_data, 1.0 / num_dim))
  51     else:
  52         return ratio * num_data
  53 
  54 
  55 def convert_legacy_grid(grid: torch.Tensor) -> List[torch.Tensor]:
  56     return [grid[:, i] for i in range(grid.size(-1))]
  57 
  58 
  59 def create_data_from_grid(grid: List[torch.Tensor]) -> torch.Tensor:
  60     """
  61     Args:
  62         :attr:`grid` (List[Tensor])
  63             Each Tensor is a 1D set of increments for the grid in that dimension
  64     Returns:
  65         `grid_data` (Tensor)
  66             Returns the set of points on the grid going by column-major order
  67             (due to legacy reasons).
  68     """
  69     if torch.is_tensor(grid):
  70         grid = convert_legacy_grid(grid)
  71     ndims = len(grid)
  72     assert all(axis.dim() == 1 for axis in grid)
  73     projections = torch.meshgrid(*grid)
  74     grid_tensor = torch.stack(projections, axis=-1)
  75     # Note that if we did
  76     #     grid_data = grid_tensor.reshape(-1, ndims)
  77     # instead, we would be iterating through the points of our grid from the
  78     # last data dimension to the first data dimension. However, due to legacy
  79     # reasons, we need to iterate from the first data dimension to the last data
  80     # dimension when creating grid_data
  81     grid_data = grid_tensor.permute(*(reversed(range(ndims + 1)))).reshape(ndims, -1).transpose(0, 1)
  82     return grid_data
  83 
  84 
  85 def create_grid(
  86     grid_sizes: List[int], grid_bounds: List[Tuple[float, float]], extend: bool = True, device="cpu", dtype=torch.float,
  87 ) -> List[torch.Tensor]:
  88     """
  89     Creates a grid represented by a list of 1D Tensors representing the
  90     projections of the grid into each dimension
  91 
  92     If `extend`, we extend the grid by two points past the specified boundary
  93     which can be important for getting good grid interpolations
  94     """
  95     grid = []
  96     for i in range(len(grid_bounds)):
  97         grid_diff = float(grid_bounds[i][1] - grid_bounds[i][0]) / (grid_sizes[i] - 2)
  98         if extend:
  99             proj = torch.linspace(
  100                 grid_bounds[i][0] - grid_diff, grid_bounds[i][1] + grid_diff, grid_sizes[i], device=device, dtype=dtype,
  101             )
  102         else:
  103             proj = torch.linspace(grid_bounds[i][0], grid_bounds[i][1], grid_sizes[i], device=device, dtype=dtype,)
  104         grid.append(proj)
  105     return grid
  106
  [end of gpytorch/utils/grid.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/gpytorch/utils/grid.py b/gpytorch/utils/grid.py
  --- a/gpytorch/utils/grid.py
  +++ b/gpytorch/utils/grid.py
  @@ -10,14 +10,12 @@
       """
       Scale the input data so that it lies in between the lower and upper bounds.
   
  -    Args:
  -        :attr:`x` (Tensor `n` or `b x n`):
  -            the input
  -        :attr:`lower_bound` (float)
  -        :attr:`upper_bound` (float)
  -
  -    Returns:
  -        :obj:`torch.Tensor`
  +    :param x: the input data
  +    :type x: torch.Tensor (... x n x d)
  +    :param float lower_bound: lower bound of scaled data
  +    :param float upper_bound: upper bound of scaled data
  +    :return: scaled data
  +    :rtype: torch.Tensor (... x n x d)
       """
       # Scale features so they fit inside grid bounds
       min_val = x.min()
  @@ -31,17 +29,15 @@
       """
       Given some training inputs, determine a good grid size for KISS-GP.
   
  -    Args:
  -        :attr:`train_inputs` (Tensor `n` or `n x d` or `b x n x d`):
  -            training data
  -        :attr:`ratio` (float, optional):
  -            Ratio - number of grid points to the amount of data (default: 1.)
  -        :attr:`kronecker_structure` (bool, default=True):
  -            Whether or not the model will use Kronecker structure in the grid
  -            (set to True unless there is an additive or product decomposition in the prior)
  -
  -    Returns:
  -        :obj:`int`
  +    :param x: the input data
  +    :type x: torch.Tensor (... x n x d)
  +    :param ratio: Amount of grid points per data point (default: 1.)
  +    :type ratio: float, optional
  +    :param kronecker_structure: Whether or not the model will use Kronecker structure in the grid
  +        (set to True unless there is an additive or product decomposition in the prior)
  +    :type kronecker_structure: bool, optional
  +    :return: Grid size
  +    :rtype: int
       """
       # Scale features so they fit inside grid bounds
       num_data = train_inputs.numel() if train_inputs.dim() == 1 else train_inputs.size(-2)
  @@ -58,13 +54,10 @@
   
   def create_data_from_grid(grid: List[torch.Tensor]) -> torch.Tensor:
       """
  -    Args:
  -        :attr:`grid` (List[Tensor])
  -            Each Tensor is a 1D set of increments for the grid in that dimension
  -    Returns:
  -        `grid_data` (Tensor)
  -            Returns the set of points on the grid going by column-major order
  -            (due to legacy reasons).
  +    :param grid: Each Tensor is a 1D set of increments for the grid in that dimension
  +    :type grid: List[torch.Tensor]
  +    :return: The set of points on the grid going by column-major order
  +    :rtype: torch.Tensor
       """
       if torch.is_tensor(grid):
           grid = convert_legacy_grid(grid)
  @@ -90,7 +83,18 @@
       projections of the grid into each dimension
   
       If `extend`, we extend the grid by two points past the specified boundary
  -    which can be important for getting good grid interpolations
  +    which can be important for getting good grid interpolations.
  +
  +    :param grid_sizes: Sizes of each grid dimension
  +    :type grid_sizes: List[int]
  +    :param grid_bounds: Lower and upper bounds of each grid dimension
  +    :type grid_sizes: List[Tuple[float, float]]
  +    :param device: target device for output (default: cpu)
  +    :type device: torch.device, optional
  +    :param dtype: target dtype for output (default: torch.float)
  +    :type dtype: torch.dtype, optional
  +    :return: Grid points for each dimension. Grid points are stored in a :obj:`torch.Tensor` with shape `grid_sizes[i]`.
  +    :rtype: List[torch.Tensor]
       """
       grid = []
       for i in range(len(grid_bounds)):
  
verification_info: {"golden_diff": "diff --git a/gpytorch/utils/grid.py b/gpytorch/utils/grid.py\n--- a/gpytorch/utils/grid.py\n+++ b/gpytorch/utils/grid.py\n@@ -10,14 +10,12 @@\n     \"\"\"\n     Scale the input data so that it lies in between the lower and upper bounds.\n \n-    Args:\n-        :attr:`x` (Tensor `n` or `b x n`):\n-            the input\n-        :attr:`lower_bound` (float)\n-        :attr:`upper_bound` (float)\n-\n-    Returns:\n-        :obj:`torch.Tensor`\n+    :param x: the input data\n+    :type x: torch.Tensor (... x n x d)\n+    :param float lower_bound: lower bound of scaled data\n+    :param float upper_bound: upper bound of scaled data\n+    :return: scaled data\n+    :rtype: torch.Tensor (... x n x d)\n     \"\"\"\n     # Scale features so they fit inside grid bounds\n     min_val = x.min()\n@@ -31,17 +29,15 @@\n     \"\"\"\n     Given some training inputs, determine a good grid size for KISS-GP.\n \n-    Args:\n-        :attr:`train_inputs` (Tensor `n` or `n x d` or `b x n x d`):\n-            training data\n-        :attr:`ratio` (float, optional):\n-            Ratio - number of grid points to the amount of data (default: 1.)\n-        :attr:`kronecker_structure` (bool, default=True):\n-            Whether or not the model will use Kronecker structure in the grid\n-            (set to True unless there is an additive or product decomposition in the prior)\n-\n-    Returns:\n-        :obj:`int`\n+    :param x: the input data\n+    :type x: torch.Tensor (... x n x d)\n+    :param ratio: Amount of grid points per data point (default: 1.)\n+    :type ratio: float, optional\n+    :param kronecker_structure: Whether or not the model will use Kronecker structure in the grid\n+        (set to True unless there is an additive or product decomposition in the prior)\n+    :type kronecker_structure: bool, optional\n+    :return: Grid size\n+    :rtype: int\n     \"\"\"\n     # Scale features so they fit inside grid bounds\n     num_data = train_inputs.numel() if train_inputs.dim() == 1 else train_inputs.size(-2)\n@@ -58,13 +54,10 @@\n \n def create_data_from_grid(grid: List[torch.Tensor]) -> torch.Tensor:\n     \"\"\"\n-    Args:\n-        :attr:`grid` (List[Tensor])\n-            Each Tensor is a 1D set of increments for the grid in that dimension\n-    Returns:\n-        `grid_data` (Tensor)\n-            Returns the set of points on the grid going by column-major order\n-            (due to legacy reasons).\n+    :param grid: Each Tensor is a 1D set of increments for the grid in that dimension\n+    :type grid: List[torch.Tensor]\n+    :return: The set of points on the grid going by column-major order\n+    :rtype: torch.Tensor\n     \"\"\"\n     if torch.is_tensor(grid):\n         grid = convert_legacy_grid(grid)\n@@ -90,7 +83,18 @@\n     projections of the grid into each dimension\n \n     If `extend`, we extend the grid by two points past the specified boundary\n-    which can be important for getting good grid interpolations\n+    which can be important for getting good grid interpolations.\n+\n+    :param grid_sizes: Sizes of each grid dimension\n+    :type grid_sizes: List[int]\n+    :param grid_bounds: Lower and upper bounds of each grid dimension\n+    :type grid_sizes: List[Tuple[float, float]]\n+    :param device: target device for output (default: cpu)\n+    :type device: torch.device, optional\n+    :param dtype: target dtype for output (default: torch.float)\n+    :type dtype: torch.dtype, optional\n+    :return: Grid points for each dimension. Grid points are stored in a :obj:`torch.Tensor` with shape `grid_sizes[i]`.\n+    :rtype: List[torch.Tensor]\n     \"\"\"\n     grid = []\n     for i in range(len(grid_bounds)):\n", "issue": "[Docs] Missing Grid.py documentation\n# \ud83d\udcda Documentation/Examples\r\n\r\n** Is there documentation missing? **\r\nThe utils section of [GPyTorch documentation](https://gpytorch.readthedocs.io) does not include any information on grid.py, which is referenced [elsewhere in the docs](https://docs.gpytorch.ai/en/stable/kernels.html?highlight=choose_grid_size#gpytorch.kernels.GridKernel.update_grid).\r\n\n", "before_files": [{"content": "#!/usr/bin/env python3\n\nimport math\nfrom typing import List, Tuple\n\nimport torch\n\n\ndef scale_to_bounds(x, lower_bound, upper_bound):\n    \"\"\"\n    Scale the input data so that it lies in between the lower and upper bounds.\n\n    Args:\n        :attr:`x` (Tensor `n` or `b x n`):\n            the input\n        :attr:`lower_bound` (float)\n        :attr:`upper_bound` (float)\n\n    Returns:\n        :obj:`torch.Tensor`\n    \"\"\"\n    # Scale features so they fit inside grid bounds\n    min_val = x.min()\n    max_val = x.max()\n    diff = max_val - min_val\n    x = (x - min_val) * (0.95 * (upper_bound - lower_bound) / diff) + 0.95 * lower_bound\n    return x\n\n\ndef choose_grid_size(train_inputs, ratio=1.0, kronecker_structure=True):\n    \"\"\"\n    Given some training inputs, determine a good grid size for KISS-GP.\n\n    Args:\n        :attr:`train_inputs` (Tensor `n` or `n x d` or `b x n x d`):\n            training data\n        :attr:`ratio` (float, optional):\n            Ratio - number of grid points to the amount of data (default: 1.)\n        :attr:`kronecker_structure` (bool, default=True):\n            Whether or not the model will use Kronecker structure in the grid\n            (set to True unless there is an additive or product decomposition in the prior)\n\n    Returns:\n        :obj:`int`\n    \"\"\"\n    # Scale features so they fit inside grid bounds\n    num_data = train_inputs.numel() if train_inputs.dim() == 1 else train_inputs.size(-2)\n    num_dim = 1 if train_inputs.dim() == 1 else train_inputs.size(-1)\n    if kronecker_structure:\n        return int(ratio * math.pow(num_data, 1.0 / num_dim))\n    else:\n        return ratio * num_data\n\n\ndef convert_legacy_grid(grid: torch.Tensor) -> List[torch.Tensor]:\n    return [grid[:, i] for i in range(grid.size(-1))]\n\n\ndef create_data_from_grid(grid: List[torch.Tensor]) -> torch.Tensor:\n    \"\"\"\n    Args:\n        :attr:`grid` (List[Tensor])\n            Each Tensor is a 1D set of increments for the grid in that dimension\n    Returns:\n        `grid_data` (Tensor)\n            Returns the set of points on the grid going by column-major order\n            (due to legacy reasons).\n    \"\"\"\n    if torch.is_tensor(grid):\n        grid = convert_legacy_grid(grid)\n    ndims = len(grid)\n    assert all(axis.dim() == 1 for axis in grid)\n    projections = torch.meshgrid(*grid)\n    grid_tensor = torch.stack(projections, axis=-1)\n    # Note that if we did\n    #     grid_data = grid_tensor.reshape(-1, ndims)\n    # instead, we would be iterating through the points of our grid from the\n    # last data dimension to the first data dimension. However, due to legacy\n    # reasons, we need to iterate from the first data dimension to the last data\n    # dimension when creating grid_data\n    grid_data = grid_tensor.permute(*(reversed(range(ndims + 1)))).reshape(ndims, -1).transpose(0, 1)\n    return grid_data\n\n\ndef create_grid(\n    grid_sizes: List[int], grid_bounds: List[Tuple[float, float]], extend: bool = True, device=\"cpu\", dtype=torch.float,\n) -> List[torch.Tensor]:\n    \"\"\"\n    Creates a grid represented by a list of 1D Tensors representing the\n    projections of the grid into each dimension\n\n    If `extend`, we extend the grid by two points past the specified boundary\n    which can be important for getting good grid interpolations\n    \"\"\"\n    grid = []\n    for i in range(len(grid_bounds)):\n        grid_diff = float(grid_bounds[i][1] - grid_bounds[i][0]) / (grid_sizes[i] - 2)\n        if extend:\n            proj = torch.linspace(\n                grid_bounds[i][0] - grid_diff, grid_bounds[i][1] + grid_diff, grid_sizes[i], device=device, dtype=dtype,\n            )\n        else:\n            proj = torch.linspace(grid_bounds[i][0], grid_bounds[i][1], grid_sizes[i], device=device, dtype=dtype,)\n        grid.append(proj)\n    return grid\n", "path": "gpytorch/utils/grid.py"}]}