We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Missing Reference to Manipulating Kernels Page
# Documentation/tutorial notebooks

In the [Kernel Design page](https://gpflow.github.io/GPflow/2.7.0/notebooks/tailor/kernel_design.html), there is a missing reference to the "Manipulating Kernels" notebook at the end. This notebook seems accessible from the old documentations, up to [2.6.4](https://gpflow.github.io/GPflow/2.6.4/notebooks/advanced/kernels.html).

It seems for some reason, this page was removed. Maybe it was considered unnecessary, as some information is given in the getting started page but I disagree. I believe it gives a more comprehensive review of the available kernel implementations, so it would be nice to have it back.

As a side note, for some reason 2.6.4 documentation insist on having dark theme for me, but I like the light theme better. Is there an option to change this? I am forced to clean the cookies to get a light background.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `doc/sphinx/notebooks/tailor/kernel_design.pct.py`
Content:
```
1 # ---
2 # jupyter:
3 #   jupytext:
4 #     formats: ipynb,.pct.py:percent
5 #     text_representation:
6 #       extension: .py
7 #       format_name: percent
8 #       format_version: '1.3'
9 #       jupytext_version: 1.3.3
10 #   kernelspec:
11 #     display_name: Python 3
12 #     language: python
13 #     name: python3
14 # ---
15 
16 # %% [markdown]
17 # # Kernel Design
18 #
19 # It's easy to make new kernels in GPflow. To demonstrate, we'll have a look at the Brownian motion kernel, whose function is
20 # \begin{equation}
21 # k(x, x') = \sigma^2 \text{min}(x, x')
22 # \end{equation}
23 # where $\sigma^2$ is a variance parameter.
24 
25 # %%
26 import matplotlib.pyplot as plt
27 import numpy as np
28 import tensorflow as tf
29 
30 import gpflow
31 from gpflow.utilities import positive, print_summary
32 
33 plt.style.use("ggplot")
34 # %matplotlib inline
35 
36 # %% [markdown]
37 # To make this new kernel class, we inherit from the base class `gpflow.kernels.Kernel` and implement the three functions below. **NOTE:** Depending on the kernel to be implemented, other classes can be more adequate. For example, if the kernel to be implemented is isotropic stationary, you can immediately subclass `gpflow.kernels.IsotropicStationary` (at which point you
38 # only have to override `K_r` or `K_r2`; see the `IsotropicStationary` class docstring). Stationary but anisotropic kernels should subclass `gpflow.kernels.AnisotropicStationary` and override `K_d`.
39 #
40 # #### `__init__`
41 # In this simple example, the constructor takes no argument (though it could, if that was convenient, for example to pass in an initial value for `variance`). It *must* call the constructor of the superclass with appropriate arguments. Brownian motion is only defined in one dimension, and we'll assume that the `active_dims` are `[0]`, for simplicity.
42 #
43 # We've added a parameter to the kernel using the `Parameter` class. Using this class lets the parameter be used in computing the kernel function, and it will automatically be recognised for optimization (or MCMC). Here, the variance parameter is initialized at 1, and constrained to be positive.
44 #
45 # #### `K`
46 # This is where you implement the kernel function itself. This takes two arguments, `X` and `X2`. By convention, we make the second argument optional (it defaults to `None`).
47 #
48 # Inside `K`, all the computation must be done with TensorFlow - here we've used `tf.minimum`. When GPflow executes the `K` function, `X` and `X2` will be TensorFlow tensors, and parameters such as `self.variance` behave like TensorFlow tensors as well.
49 #
50 # #### `K_diag`
51 # This convenience function allows GPflow to save memory at predict time. It's simply the diagonal of the `K` function, in the case where `X2` is `None`. It must return a one-dimensional vector, so we use TensorFlow's reshape command.
52 
53 # %%
54 class Brownian(gpflow.kernels.Kernel):
55     def __init__(self):
56         super().__init__(active_dims=[0])
57         self.variance = gpflow.Parameter(1.0, transform=positive())
58 
59     def K(self, X, X2=None):
60         if X2 is None:
61             X2 = X
62         return self.variance * tf.minimum(
63             X, tf.transpose(X2)
64         )  # this returns a 2D tensor
65 
66     def K_diag(self, X):
67         return self.variance * tf.reshape(X, (-1,))  # this returns a 1D tensor
68 
69 
70 k_brownian = Brownian()
71 print_summary(k_brownian, fmt="notebook")
72 
73 # %% [markdown]
74 # We can now evaluate our new kernel function and draw samples from a Gaussian process with this covariance:
75 
76 # %%
77 np.random.seed(23)  # for reproducibility
78 
79 
80 def plotkernelsample(k, ax, xmin=0, xmax=3):
81     xx = np.linspace(xmin, xmax, 300)[:, None]
82     K = k(xx)
83     ax.plot(xx, np.random.multivariate_normal(np.zeros(300), K, 5).T)
84     ax.set_title("Samples " + k.__class__.__name__)
85 
86 
87 def plotkernelfunction(k, ax, xmin=0, xmax=3, other=0):
88     xx = np.linspace(xmin, xmax, 100)[:, None]
89     ax.plot(xx, k(xx, np.zeros((1, 1)) + other))
90     ax.set_title(k.__class__.__name__ + " k(x, %.1f)" % other)
91 
92 
93 f, axes = plt.subplots(1, 2, figsize=(12, 4), sharex=True)
94 plotkernelfunction(k_brownian, axes[0], other=2.0)
95 plotkernelsample(k_brownian, axes[1])
96 
97 # %% [markdown]
98 # ## Using the kernel in a model
99 #
100 # Because we've inherited from the `Kernel` base class, this new kernel has all the properties needed to be used in GPflow. It also has some convenience features such as allowing the user to call
101 #
102 # `k(X, X2)`
103 #
104 # which computes the kernel matrix.
105 #
106 # To show that this kernel works, let's use it inside GP regression. We'll see that Brownian motion has quite interesting properties. To add a little flexibility, we'll add a `Constant` kernel to our `Brownian` kernel, and the `GPR` class will handle the noise.
107 
108 # %%
109 np.random.seed(42)
110 X = np.random.rand(5, 1)
111 Y = np.sin(X * 6) + np.random.randn(*X.shape) * 0.001
112 
113 k1 = Brownian()
114 k2 = gpflow.kernels.Constant()
115 k = k1 + k2
116 
117 m = gpflow.models.GPR((X, Y), kernel=k)
118 # m.likelihood.variance.assign(1e-6)
119 
120 opt = gpflow.optimizers.Scipy()
121 opt.minimize(m.training_loss, variables=m.trainable_variables)
122 print_summary(m, fmt="notebook")
123 
124 xx = np.linspace(0, 1.1, 100).reshape(100, 1)
125 mean, var = m.predict_y(xx)
126 plt.plot(X, Y, "kx", mew=2)
127 (line,) = plt.plot(xx, mean, lw=2)
128 _ = plt.fill_between(
129     xx[:, 0],
130     mean[:, 0] - 2 * np.sqrt(var[:, 0]),
131     mean[:, 0] + 2 * np.sqrt(var[:, 0]),
132     color=line.get_color(),
133     alpha=0.2,
134 )
135 
136 # %% [markdown]
137 # ## See also
138 #
139 # For more details on how to manipulate existing kernels (or the one you just created!), we refer to the [Manipulating kernels](../advanced/kernels.ipynb) notebook.
140
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/doc/sphinx/notebooks/tailor/kernel_design.pct.py b/doc/sphinx/notebooks/tailor/kernel_design.pct.py
--- a/doc/sphinx/notebooks/tailor/kernel_design.pct.py
+++ b/doc/sphinx/notebooks/tailor/kernel_design.pct.py
@@ -136,4 +136,4 @@
 # %% [markdown]
 # ## See also
 #
-# For more details on how to manipulate existing kernels (or the one you just created!), we refer to the [Manipulating kernels](../advanced/kernels.ipynb) notebook.
+# For more details on how to manipulate existing kernels (or the one you just created!), please refer to the [kernels](../getting_started/kernels.ipynb) notebook.

{"golden_diff": "diff --git a/doc/sphinx/notebooks/tailor/kernel_design.pct.py b/doc/sphinx/notebooks/tailor/kernel_design.pct.py\n--- a/doc/sphinx/notebooks/tailor/kernel_design.pct.py\n+++ b/doc/sphinx/notebooks/tailor/kernel_design.pct.py\n@@ -136,4 +136,4 @@\n # %% [markdown]\n # ## See also\n #\n-# For more details on how to manipulate existing kernels (or the one you just created!), we refer to the [Manipulating kernels](../advanced/kernels.ipynb) notebook.\n+# For more details on how to manipulate existing kernels (or the one you just created!), please refer to the [kernels](../getting_started/kernels.ipynb) notebook.\n", "issue": "Missing Reference to Manipulating Kernels Page\n# Documentation/tutorial notebooks\r\n\r\nIn the [Kernel Design page](https://gpflow.github.io/GPflow/2.7.0/notebooks/tailor/kernel_design.html), there is a missing reference to the \"Manipulating Kernels\" notebook at the end. This notebook seems accessible from the old documentations, up to [2.6.4](https://gpflow.github.io/GPflow/2.6.4/notebooks/advanced/kernels.html).\r\n\r\nIt seems for some reason, this page was removed. Maybe it was considered unnecessary, as some information is given in the getting started page but I disagree. I believe it gives a more comprehensive review of the available kernel implementations, so it would be nice to have it back.\r\n\r\nAs a side note, for some reason 2.6.4 documentation insist on having dark theme for me, but I like the light theme better. Is there an option to change this? I am forced to clean the cookies to get a light background.\n", "before_files": [{"content": "# ---\n# jupyter:\n#   jupytext:\n#     formats: ipynb,.pct.py:percent\n#     text_representation:\n#       extension: .py\n#       format_name: percent\n#       format_version: '1.3'\n#       jupytext_version: 1.3.3\n#   kernelspec:\n#     display_name: Python 3\n#     language: python\n#     name: python3\n# ---\n\n# %% [markdown]\n# # Kernel Design\n#\n# It's easy to make new kernels in GPflow. To demonstrate, we'll have a look at the Brownian motion kernel, whose function is\n# \\begin{equation}\n# k(x, x') = \\sigma^2 \\text{min}(x, x')\n# \\end{equation}\n# where $\\sigma^2$ is a variance parameter.\n\n# %%\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport tensorflow as tf\n\nimport gpflow\nfrom gpflow.utilities import positive, print_summary\n\nplt.style.use(\"ggplot\")\n# %matplotlib inline\n\n# %% [markdown]\n# To make this new kernel class, we inherit from the base class `gpflow.kernels.Kernel` and implement the three functions below. **NOTE:** Depending on the kernel to be implemented, other classes can be more adequate. For example, if the kernel to be implemented is isotropic stationary, you can immediately subclass `gpflow.kernels.IsotropicStationary` (at which point you\n# only have to override `K_r` or `K_r2`; see the `IsotropicStationary` class docstring). Stationary but anisotropic kernels should subclass `gpflow.kernels.AnisotropicStationary` and override `K_d`.\n#\n# #### `__init__`\n# In this simple example, the constructor takes no argument (though it could, if that was convenient, for example to pass in an initial value for `variance`). It *must* call the constructor of the superclass with appropriate arguments. Brownian motion is only defined in one dimension, and we'll assume that the `active_dims` are `[0]`, for simplicity.\n#\n# We've added a parameter to the kernel using the `Parameter` class. Using this class lets the parameter be used in computing the kernel function, and it will automatically be recognised for optimization (or MCMC). Here, the variance parameter is initialized at 1, and constrained to be positive.\n#\n# #### `K`\n# This is where you implement the kernel function itself. This takes two arguments, `X` and `X2`. By convention, we make the second argument optional (it defaults to `None`).\n#\n# Inside `K`, all the computation must be done with TensorFlow - here we've used `tf.minimum`. When GPflow executes the `K` function, `X` and `X2` will be TensorFlow tensors, and parameters such as `self.variance` behave like TensorFlow tensors as well.\n#\n# #### `K_diag`\n# This convenience function allows GPflow to save memory at predict time. It's simply the diagonal of the `K` function, in the case where `X2` is `None`. It must return a one-dimensional vector, so we use TensorFlow's reshape command.\n\n# %%\nclass Brownian(gpflow.kernels.Kernel):\n    def __init__(self):\n        super().__init__(active_dims=[0])\n        self.variance = gpflow.Parameter(1.0, transform=positive())\n\n    def K(self, X, X2=None):\n        if X2 is None:\n            X2 = X\n        return self.variance * tf.minimum(\n            X, tf.transpose(X2)\n        )  # this returns a 2D tensor\n\n    def K_diag(self, X):\n        return self.variance * tf.reshape(X, (-1,))  # this returns a 1D tensor\n\n\nk_brownian = Brownian()\nprint_summary(k_brownian, fmt=\"notebook\")\n\n# %% [markdown]\n# We can now evaluate our new kernel function and draw samples from a Gaussian process with this covariance:\n\n# %%\nnp.random.seed(23)  # for reproducibility\n\n\ndef plotkernelsample(k, ax, xmin=0, xmax=3):\n    xx = np.linspace(xmin, xmax, 300)[:, None]\n    K = k(xx)\n    ax.plot(xx, np.random.multivariate_normal(np.zeros(300), K, 5).T)\n    ax.set_title(\"Samples \" + k.__class__.__name__)\n\n\ndef plotkernelfunction(k, ax, xmin=0, xmax=3, other=0):\n    xx = np.linspace(xmin, xmax, 100)[:, None]\n    ax.plot(xx, k(xx, np.zeros((1, 1)) + other))\n    ax.set_title(k.__class__.__name__ + \" k(x, %.1f)\" % other)\n\n\nf, axes = plt.subplots(1, 2, figsize=(12, 4), sharex=True)\nplotkernelfunction(k_brownian, axes[0], other=2.0)\nplotkernelsample(k_brownian, axes[1])\n\n# %% [markdown]\n# ## Using the kernel in a model\n#\n# Because we've inherited from the `Kernel` base class, this new kernel has all the properties needed to be used in GPflow. It also has some convenience features such as allowing the user to call\n#\n# `k(X, X2)`\n#\n# which computes the kernel matrix.\n#\n# To show that this kernel works, let's use it inside GP regression. We'll see that Brownian motion has quite interesting properties. To add a little flexibility, we'll add a `Constant` kernel to our `Brownian` kernel, and the `GPR` class will handle the noise.\n\n# %%\nnp.random.seed(42)\nX = np.random.rand(5, 1)\nY = np.sin(X * 6) + np.random.randn(*X.shape) * 0.001\n\nk1 = Brownian()\nk2 = gpflow.kernels.Constant()\nk = k1 + k2\n\nm = gpflow.models.GPR((X, Y), kernel=k)\n# m.likelihood.variance.assign(1e-6)\n\nopt = gpflow.optimizers.Scipy()\nopt.minimize(m.training_loss, variables=m.trainable_variables)\nprint_summary(m, fmt=\"notebook\")\n\nxx = np.linspace(0, 1.1, 100).reshape(100, 1)\nmean, var = m.predict_y(xx)\nplt.plot(X, Y, \"kx\", mew=2)\n(line,) = plt.plot(xx, mean, lw=2)\n_ = plt.fill_between(\n    xx[:, 0],\n    mean[:, 0] - 2 * np.sqrt(var[:, 0]),\n    mean[:, 0] + 2 * np.sqrt(var[:, 0]),\n    color=line.get_color(),\n    alpha=0.2,\n)\n\n# %% [markdown]\n# ## See also\n#\n# For more details on how to manipulate existing kernels (or the one you just created!), we refer to the [Manipulating kernels](../advanced/kernels.ipynb) notebook.\n", "path": "doc/sphinx/notebooks/tailor/kernel_design.pct.py"}], "after_files": [{"content": "# ---\n# jupyter:\n#   jupytext:\n#     formats: ipynb,.pct.py:percent\n#     text_representation:\n#       extension: .py\n#       format_name: percent\n#       format_version: '1.3'\n#       jupytext_version: 1.3.3\n#   kernelspec:\n#     display_name: Python 3\n#     language: python\n#     name: python3\n# ---\n\n# %% [markdown]\n# # Kernel Design\n#\n# It's easy to make new kernels in GPflow. To demonstrate, we'll have a look at the Brownian motion kernel, whose function is\n# \\begin{equation}\n# k(x, x') = \\sigma^2 \\text{min}(x, x')\n# \\end{equation}\n# where $\\sigma^2$ is a variance parameter.\n\n# %%\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport tensorflow as tf\n\nimport gpflow\nfrom gpflow.utilities import positive, print_summary\n\nplt.style.use(\"ggplot\")\n# %matplotlib inline\n\n# %% [markdown]\n# To make this new kernel class, we inherit from the base class `gpflow.kernels.Kernel` and implement the three functions below. **NOTE:** Depending on the kernel to be implemented, other classes can be more adequate. For example, if the kernel to be implemented is isotropic stationary, you can immediately subclass `gpflow.kernels.IsotropicStationary` (at which point you\n# only have to override `K_r` or `K_r2`; see the `IsotropicStationary` class docstring). Stationary but anisotropic kernels should subclass `gpflow.kernels.AnisotropicStationary` and override `K_d`.\n#\n# #### `__init__`\n# In this simple example, the constructor takes no argument (though it could, if that was convenient, for example to pass in an initial value for `variance`). It *must* call the constructor of the superclass with appropriate arguments. Brownian motion is only defined in one dimension, and we'll assume that the `active_dims` are `[0]`, for simplicity.\n#\n# We've added a parameter to the kernel using the `Parameter` class. Using this class lets the parameter be used in computing the kernel function, and it will automatically be recognised for optimization (or MCMC). Here, the variance parameter is initialized at 1, and constrained to be positive.\n#\n# #### `K`\n# This is where you implement the kernel function itself. This takes two arguments, `X` and `X2`. By convention, we make the second argument optional (it defaults to `None`).\n#\n# Inside `K`, all the computation must be done with TensorFlow - here we've used `tf.minimum`. When GPflow executes the `K` function, `X` and `X2` will be TensorFlow tensors, and parameters such as `self.variance` behave like TensorFlow tensors as well.\n#\n# #### `K_diag`\n# This convenience function allows GPflow to save memory at predict time. It's simply the diagonal of the `K` function, in the case where `X2` is `None`. It must return a one-dimensional vector, so we use TensorFlow's reshape command.\n\n# %%\nclass Brownian(gpflow.kernels.Kernel):\n    def __init__(self):\n        super().__init__(active_dims=[0])\n        self.variance = gpflow.Parameter(1.0, transform=positive())\n\n    def K(self, X, X2=None):\n        if X2 is None:\n            X2 = X\n        return self.variance * tf.minimum(\n            X, tf.transpose(X2)\n        )  # this returns a 2D tensor\n\n    def K_diag(self, X):\n        return self.variance * tf.reshape(X, (-1,))  # this returns a 1D tensor\n\n\nk_brownian = Brownian()\nprint_summary(k_brownian, fmt=\"notebook\")\n\n# %% [markdown]\n# We can now evaluate our new kernel function and draw samples from a Gaussian process with this covariance:\n\n# %%\nnp.random.seed(23)  # for reproducibility\n\n\ndef plotkernelsample(k, ax, xmin=0, xmax=3):\n    xx = np.linspace(xmin, xmax, 300)[:, None]\n    K = k(xx)\n    ax.plot(xx, np.random.multivariate_normal(np.zeros(300), K, 5).T)\n    ax.set_title(\"Samples \" + k.__class__.__name__)\n\n\ndef plotkernelfunction(k, ax, xmin=0, xmax=3, other=0):\n    xx = np.linspace(xmin, xmax, 100)[:, None]\n    ax.plot(xx, k(xx, np.zeros((1, 1)) + other))\n    ax.set_title(k.__class__.__name__ + \" k(x, %.1f)\" % other)\n\n\nf, axes = plt.subplots(1, 2, figsize=(12, 4), sharex=True)\nplotkernelfunction(k_brownian, axes[0], other=2.0)\nplotkernelsample(k_brownian, axes[1])\n\n# %% [markdown]\n# ## Using the kernel in a model\n#\n# Because we've inherited from the `Kernel` base class, this new kernel has all the properties needed to be used in GPflow. It also has some convenience features such as allowing the user to call\n#\n# `k(X, X2)`\n#\n# which computes the kernel matrix.\n#\n# To show that this kernel works, let's use it inside GP regression. We'll see that Brownian motion has quite interesting properties. To add a little flexibility, we'll add a `Constant` kernel to our `Brownian` kernel, and the `GPR` class will handle the noise.\n\n# %%\nnp.random.seed(42)\nX = np.random.rand(5, 1)\nY = np.sin(X * 6) + np.random.randn(*X.shape) * 0.001\n\nk1 = Brownian()\nk2 = gpflow.kernels.Constant()\nk = k1 + k2\n\nm = gpflow.models.GPR((X, Y), kernel=k)\n# m.likelihood.variance.assign(1e-6)\n\nopt = gpflow.optimizers.Scipy()\nopt.minimize(m.training_loss, variables=m.trainable_variables)\nprint_summary(m, fmt=\"notebook\")\n\nxx = np.linspace(0, 1.1, 100).reshape(100, 1)\nmean, var = m.predict_y(xx)\nplt.plot(X, Y, \"kx\", mew=2)\n(line,) = plt.plot(xx, mean, lw=2)\n_ = plt.fill_between(\n    xx[:, 0],\n    mean[:, 0] - 2 * np.sqrt(var[:, 0]),\n    mean[:, 0] + 2 * np.sqrt(var[:, 0]),\n    color=line.get_color(),\n    alpha=0.2,\n)\n\n# %% [markdown]\n# ## See also\n#\n# For more details on how to manipulate existing kernels (or the one you just created!), please refer to the [kernels](../getting_started/kernels.ipynb) notebook.\n", "path": "doc/sphinx/notebooks/tailor/kernel_design.pct.py"}]}