diff --git a/doc/concepts/mechanisms.rst b/doc/concepts/mechanisms.rst
index 11bb5eda7ee6255fec3143ff1fb87f84ee22d140..36d17164753996cdeeeb9401b6b2a349cb8494a1 100644
--- a/doc/concepts/mechanisms.rst
+++ b/doc/concepts/mechanisms.rst
@@ -56,7 +56,7 @@ the `BBP mechanisms <https://github.com/arbor-sim/arbor/tree/master/mechanisms/b
 Built-in Catalogues
 '''''''''''''''''''
 
-Arbor provides the *default* catalogue with the following mechanisms:
+Arbor provides the ``default_catalogue`` with the following mechanisms:
 
 * *pas*: Leaky current (:ref:`density mechanism <mechanisms-density>`).
 * *hh*: Classic Hodgkin-Huxley dynamics (:ref:`density mechanism
@@ -74,8 +74,8 @@ With the exception of *nernst*, these mechanisms are the same as those available
 
 Two catalogues are provided that collect mechanisms associated with specific projects and model databases:
 
-* *bbp* For models published by the Blue Brain Project (BBP).
-* *allen* For models published on the Allen Brain Atlas Database.
+* ``bbp_catalogue`` For models published by the Blue Brain Project (BBP).
+* ``allen_catalogue`` For models published on the Allen Brain Atlas Database.
 
 .. _mechanisms_dynamic:
 
diff --git a/doc/contrib/index.rst b/doc/contrib/index.rst
index 72c1aca3ae86edd039b03bdab0c51555627f6d8d..661e4de9a0efc11779be28bcf16e0e51607cb0a2 100644
--- a/doc/contrib/index.rst
+++ b/doc/contrib/index.rst
@@ -13,7 +13,7 @@ on something.
 Types of contributions
 ----------------------
 
-There are many ways to contribute: writing tutorials or blog posts,
+There are many ways to contribute: sharing models, writing tutorials or blog posts,
 improving the documentation, helping other users, submitting bug reports
 and feature requests or writing code which can be incorporated into
 Arbor itself.
@@ -115,6 +115,10 @@ See :ref:`contribpr`.
 Other contributions
 -------------------
 
+We try to collect models scientists have built in our `contributor space <https://github.com/arbor-contrib/>`_.
+In addition to the tutorials, browsing these models should give you a good idea of what's possible with Arbor
+and find get in contact with other Arbor users.
+
 We're always happy to hear about Arbor being used! Have you used Arbor to create a model?
 Have you used Arbor in a publication? Do you have a workflow or tool that involves Arbor?
 Get in touch and we'd be very happy to showcase your work through our channels.
diff --git a/doc/ecosystem/index.rst b/doc/ecosystem/index.rst
index 3f9ea1f9e9d9604968470bd094c69796983ef0a6..bf8a7769631ba18e0191fdf3ae5bd9e18fb5ae33 100644
--- a/doc/ecosystem/index.rst
+++ b/doc/ecosystem/index.rst
@@ -3,7 +3,14 @@
 Ecosystem
 =========
 
-Arbor exists in a wider computational neuroscience software ecosystem. The Arbor simulator is also not the only component written by the Arbor developers.
+Arbor exists in a wider computational neuroscience software ecosystem. In addition to the Arbor simulator, the Arbor development team writes and maintains a set of useful tools to be used in conjuction with Arbor.
+
+Arbor models
+------------
+
+We try to collect models scientists have built in our `contributor space <https://github.com/arbor-contrib/>`_.
+In addition to the tutorials, browsing these models should give you a good idea of what's possible with Arbor
+and find get in contact with other Arbor users.
 
 Arbor software
 --------------
diff --git a/doc/install/python.rst b/doc/install/python.rst
index 3b931ad38ea1319125289433abeda37b15cd7989..c4f7ab46893a3a349f6f80f144243ad015687c0c 100644
--- a/doc/install/python.rst
+++ b/doc/install/python.rst
@@ -74,6 +74,8 @@ If you want to work on Arbor's code, you can get a copy of our repo and point `p
 
 Every time you make changes to the code, you'll have to repeat the second step.
 
+.. _in_python_adv:
+
 Advanced options
 ^^^^^^^^^^^^^^^^
 
diff --git a/doc/python/recipe.rst b/doc/python/recipe.rst
index 479f473b01d6e1707fc4030d8d98fd699c27fe05..d50ed3fdcb20257df6bd0ab7219a57961d9b5f7b 100644
--- a/doc/python/recipe.rst
+++ b/doc/python/recipe.rst
@@ -18,11 +18,27 @@ Recipe
     Describe a model by describing the cells and network, without any information about how the model is to be represented or executed.
 
     All recipes derive from this abstract base class.
-
+    
     Recipes provide a cell-centric interface for describing a model.
     This means that model properties, such as connections, are queried using the global identifier ``gid`` of a cell.
     In the description below, the term ``gid`` is used as shorthand for the cell with global identifier.
 
+    **Required Constructor**
+
+    The constructor must be implemented and call the base class constructor, as at the moment there is no way
+    to instruct Python to do that automatically.
+    
+    .. note:: 
+        Arbor's Python binding is that: a thin wrappper around the Arbor library which is written in C++.
+        Calling the base class constructor ensures correct initialization of memory in the underlying C++ class.
+    
+    A minimal constructor therefore looks like this:
+
+    .. code-block:: python
+
+        def __init__(self):
+            arbor.recipe.__init__(self)
+
     **Required Member Functions**
 
     The following member functions (besides a constructor) must be implemented by every recipe:
@@ -87,7 +103,7 @@ Recipe
         This method needs to be implemented for :class:`arbor.cell_kind.cable`, where the
         properties include ion concentrations and reversal potentials; initial membrane voltage;
         temperature; axial resistivity; membrane capacitance; cv_policy; and a pointer
-        to the mechanism catalogue.
+        to the mechanism catalogue. Also see :ref:`mechanisms_builtins`.
 
         By default returns an empty object.
 
diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst
index 70b92e5ddd3da27b7393c2e60758591b5a862509..0e54ec31b2f0768829ca84f8afabf42181156889 100644
--- a/doc/tutorial/index.rst
+++ b/doc/tutorial/index.rst
@@ -21,10 +21,18 @@ Cells
    :maxdepth: 1
 
    single_cell_model
-   single_cell_recipe
    single_cell_detailed
-   single_cell_detailed_recipe
    single_cell_cable
+   single_cell_allen
+
+Recipes
+-------
+
+.. toctree::
+   :maxdepth: 1
+   
+   single_cell_recipe
+   single_cell_detailed_recipe
 
 Networks
 --------
@@ -33,9 +41,7 @@ Networks
    :maxdepth: 1
 
    network_ring
-   network_ring_mpi
-   single_cell_allen
-   two_cells_gap_junctions
+   network_two_cells_gap_junctions
 
 Probes
 ------
@@ -43,4 +49,20 @@ Probes
 .. toctree::
    :maxdepth: 1
 
-   tutorial_lfpykit
+   probe_lfpykit
+
+Hardware
+--------
+
+.. toctree::
+   :maxdepth: 1
+
+   network_ring_mpi
+   network_ring_gpu
+
+Demonstrations
+--------------
+
+We try to collect models scientists have built in our `contributor space <https://github.com/arbor-contrib/>`_.
+In addition to the tutorials, browsing these models should give you a good idea of what's possible with Arbor
+and find get in contact with other Arbor users.
diff --git a/doc/tutorial/network_ring.rst b/doc/tutorial/network_ring.rst
index 7a70fe65ce01f5cae389dca4104dc5368eda3edc..4e0ba577a4e57c00642be443a63c17e774d5b384 100644
--- a/doc/tutorial/network_ring.rst
+++ b/doc/tutorial/network_ring.rst
@@ -27,7 +27,7 @@ We construct the following :term:`morphology` and label the soma and dendrite:
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 18-37
+   :lines: 20-46
 
 In step **(2)** we create a :term:`label` for both the root and the site of the synapse.
 These locations will form the endpoints of the connections between the cells.
@@ -40,7 +40,7 @@ These locations will form the endpoints of the connections between the cells.
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 39-42
+   :lines: 48-51
 
 After we've created a basic :py:class:`arbor.decor`, step **(3)** places a synapse with an exponential decay (``'expsyn'``) on the ``'synapse_site'``.
 The synapse is given the label ``'syn'``, which is later used to form :py:class:`arbor.connection` objects terminating *at* the cell.
@@ -63,7 +63,7 @@ Step **(4)** places a spike detector at the ``'root'``. The detector is given th
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 44-59
+   :lines: 53-73
 
 The recipe
 **********
@@ -107,21 +107,23 @@ Step **(11)** instantiates the recipe with 4 cells.
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 61-109
+   :lines: 76-124
 
 The execution
 *************
 
-To create a simulation, we must create an :class:`arbor.context` and :py:class:`arbor.domain_decomposition`.
+To create a simulation, we need at minimum to supply the recipe, and in addition can supply a :class:`arbor.context`
+and :py:class:`arbor.domain_decomposition`. The first lets Arbor know what hardware it should use, the second how to
+destribute the work over that hardware. By default, contexts are configured to use 1 thread and domain decompositons to
+divide work equally over all threads.
 
 Step **(12)** initalizes the ``threads`` parameter of :class:`arbor.context` with the ``avail_threads`` flag. By supplying
 this flag, a context is constructed that will use all locally available threads. On your local machine this will match the
 number of logical cores in your system. Especially with large numbers
 of cells you will notice the speed-up. (You could instantiate the recipe with 5000 cells and observe the difference. Don't
 forget to turn of plotting if you do; it will take more time to generate the image then to run the actual simulation!)
-:func:`arbor.partition_load_balance` creates a default domain decomposition, which 
-for contexts initialized with ``threads=avail_threads`` distributes cells evenly over the available cores. You can print the
-objects to see what defaults they produce on your system.
+If no domain decomposition is specified, a default one distributing work equally is created. This is sufficient for now.
+You can print the objects to see what defaults they produce on your system.
 
 Step **(13)** sets all spike generators to record using the :py:class:`arbor.spike_recording.all` policy.
 This means the timestamps of the generated events will be kept in memory. Be default, these are discarded.
@@ -141,7 +143,7 @@ Step **(15)** executes the simulation for a duration of 100 ms.
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 111-124
+   :lines: 126-138
 
 The results
 ***********
@@ -150,7 +152,7 @@ Step **(16)** prints the timestamps of the spikes:
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 126-129
+   :lines: 140-143
 
 Step **(17)** generates a plot of the sampling data.
 :py:func:`arbor.simulation.samples` takes a ``handle`` of the probe we wish to examine. It returns a list
@@ -162,7 +164,7 @@ It could have described a :term:`locset`.)
 
 .. literalinclude:: ../../python/example/network_ring.py
    :language: python
-   :lines: 131-
+   :lines: 145-
 
 Since we have created ``ncells`` cells, we have ``ncells`` traces. We should be seeing phase shifted traces, as the action potential propagated through the network.
 
diff --git a/doc/tutorial/network_ring_gpu.rst b/doc/tutorial/network_ring_gpu.rst
new file mode 100644
index 0000000000000000000000000000000000000000..1f649ac7758973bd47195c15e2fce6861ddcc1d5
--- /dev/null
+++ b/doc/tutorial/network_ring_gpu.rst
@@ -0,0 +1,135 @@
+.. _tutorialmpi:
+
+GPU and profiling
+=================
+
+In this example, the ring network created in an :ref:`earlier tutorial <tutorialnetworkring>` will be used to run the model with a GPU. In addition, it is shown how to profile the performance difference. Only the differences with that tutorial will be described.
+
+.. Note::
+
+   **Concepts covered in this example:**
+
+   1. Building a :py:class:`arbor.context` that'll use a GPU.
+      This requires that you have built Arbor with GPU support enabled.
+   2. Build a :class:`arbor.domain_decomposition` and provide a :class:`arbor.partition_hint`.
+   3. Profile an Arbor simulation using :class:`arbor.meter_manager`.
+
+The hardware context
+********************
+
+An :ref:`execution context <modelcontext>` describes the hardware resources on which the simulation will run.
+It contains the thread pool used to parallelise work on the local CPU, and optionally describes GPU resources
+and the MPI communicator for distributed simulations. In some other examples, the :class:`arbor.single_cell_model`
+object created the execution context :class:`arbor.context` behind the scenes. The details of the execution
+context can be customized by the user. We may specify the number of threads in the thread pool; determine the
+id of the GPU to be used; or create our own MPI communicator.
+
+Step **(11)** creates a hardware context where we set the :py:attr:`~arbor.proc_allocation.gpu_id`. This requires 
+that you have built Arbor manually, with GPU support (See :ref:`here <in_python_adv>` how to do that). On a regular 
+consumer device with a single GPU, the index you should pass is ``0``. Change the value to run the example with and 
+without GPU.
+
+.. literalinclude:: ../../python/example/network_ring_gpu.py
+   :language: python
+   :lines: 123-127
+
+Profiling
+*********
+
+Arbor comes with a :class:`arbor.meter_manager` to help you profile your simulations. In this case, you can run the 
+example with ``gpu_id=None`` and ``gpu_id=0`` and observe the difference with the :class:`~arbor.meter_manager`.
+
+Step **(12)** sets up the meter manager and starts it using the (only) context. This way, only Arbor related execution is measured, not Python code.
+
+Step **(13)** instantiates the recipe and sets the first checkpoint on the meter manager. We now have the time it took to construct the recipe.
+
+.. literalinclude:: ../../python/example/network_ring_gpu.py
+   :language: python
+   :lines: 129-136
+
+The domain decomposition
+************************
+
+The domain decomposition describes the distribution of the cells over the available computational resources.
+The :class:`arbor.single_cell_model` also handled that without our knowledge in the previous examples.
+Now, we have to define it ourselves.
+
+The :class:`arbor.domain_decomposition` class can be manually created by the user, by deciding which cells
+go on which ranks. Or we can use a load balancer that can partition the cells across ranks according to
+some rules. Arbor provides :class:`arbor.partition_load_balance`, which, using the recipe and execution
+context, creates the :class:`arbor.domain_decomposition` object for us.
+
+A way to customize :class:`arbor.partition_load_balance` is by providing a :class:`arbor.partition_hint`. They let
+you configure how cells are distributed over the resources in the :class:`~arbor.context`, but without requiring you
+to know the precise configuration of a :class:`~arbor.context` up front. Whether you run your simulation on your 
+laptop CPU, desktop GPU, CPU cluster of GPU cluster, using :class:`partition hints<arbor.partition_hint>` you can
+just say: use GPUs, if available. You only have to change the :class:`~arbor.context` to actually define which 
+hardware Arbor will execute on.
+
+Step **(14)** creates a :class:`arbor.partition_hint`, and tells it to put 1000 cells in a groups allocated to GPUs, 
+and to prefer the utilisation of the GPU if present. In fact, the default distribution strategy of 
+:class:`arbor.partition_load_balance` already spreads out cells as evenly as possible over CPUs, and groups
+(up to 1000) on GPUs, so strictly speaking it was not necesary to give that part of the hint.
+Lastly, a dictionary is created with which hints are assigned to a particular :class:`arbor.cell_kind`.
+Different kinds may favor different execution, hence the option.
+In this simulation, there are only :class:`arbor.cell_kind.cable`, so we assign the hint to that kind.
+
+Step **(15)** creates a :class:`arbor.partition_load_balance` with the recipe, context and hints created above.
+Another checkpoint will help us understand how long creating the load balancer took.
+
+.. literalinclude:: ../../python/example/network_ring_gpu.py
+   :language: python
+   :lines: 138-148
+
+
+The simulation
+**************
+
+Step **(16)** creates a :class:`arbor.simulation`, sets the spike recorders to record, creates a :term:`handle`
+to their eventual results and makes another checkpoint.
+
+.. literalinclude:: ../../python/example/network_ring_gpu.py
+   :language: python
+   :lines: 150-154
+
+The execution
+*************
+
+Step **(17)** runs the simulation. Since we have more cells this time, which are connected in series,
+it will take some time for the action potential to propagate. In the :ref:`ring network <tutorialnetworkring>`
+we could see it takes about 5 ms for the signal to propagate through one cell, so let's set the runtime to
+``5*ncells``. Then, another checkpoint, so that we'll know how long the simulation took.
+
+.. literalinclude:: ../../python/example/network_ring_gpu.py
+   :language: python
+   :lines: 156-159
+
+The results
+***********
+
+The scientific results should be similar, other than number of cells, to those in :ref:`ring network <tutorialnetworkring>`,
+so we'll not discuss them here. Let's turn our attention to the :class:`~arbor.meter_manager`.
+
+.. literalinclude:: ../../python/example/network_ring_gpu.py
+   :language: python
+   :lines: 161-163
+
+Step **(18)** shows how :class:`arbor.meter_report` can be used to read out the :class:`~arbor.meter_manager`.
+It generates a table with the time between checkpoints. As an example, the following table is the result of a run
+on a 2019 laptop CPU:
+
+::
+
+   ---- meters -------------------------------------------------------------------------------
+   meter                         time(s)      memory(MB)
+   -------------------------------------------------------------------------------------------
+   recipe-create                   0.000           0.059
+   load-balance                    0.000           0.007
+   simulation-init                 0.012           0.662
+   simulation-run                  0.037           0.319
+   meter-total                     0.049           1.048
+
+The full code
+*************
+
+You can find the full code of the example at ``python/examples/network_ring_gpu.py``.
diff --git a/doc/tutorial/network_ring_mpi.rst b/doc/tutorial/network_ring_mpi.rst
index e629113e0c1eaa7e1c8e63a6890580df6e33cdf7..08a01b44155a88c53f847bf593e6f94d85886a06 100644
--- a/doc/tutorial/network_ring_mpi.rst
+++ b/doc/tutorial/network_ring_mpi.rst
@@ -21,12 +21,19 @@ Step **(11)** is changed to generate a network with five hundred cells.
 
 .. literalinclude:: ../../python/example/network_ring_mpi.py
    :language: python
-   :lines: 109-111
+   :lines: 124-126
 
 The hardware context
 ********************
 
-The configuration of the :py:class:`arbor.context` will need to be changed to reflect the change in hardware.
+An :ref:`execution context <modelcontext>` describes the hardware resources on which the simulation will run.
+It contains the thread pool used to parallelise work on the local CPU, and optionally describes GPU resources
+and the MPI communicator for distributed simulations. In some other examples, the :class:`arbor.single_cell_model`
+object created the execution context :class:`arbor.context` behind the scenes. The details of the execution
+context can be customized by the user. We may specify the number of threads in the thread pool; determine the
+id of the GPU to be used; or create our own MPI communicator.
+
+The configuration of the context will need to be changed to reflect the change in hardware.
 First of all, we scrap setting `threads="avail_threads"` and instead use 
 `MPI <https://en.wikipedia.org/wiki/Message_Passing_Interface#Overview>`_ to distribute the work over nodes, cores and threads.
 
@@ -35,9 +42,11 @@ Step **(12)** uses the Arbor-built-in :py:class:`MPI communicator <arbor.mpi_com
 communicator for its ``mpi`` parameter. Note that you can also pass in communicators created with ``mpi4py``.
 We print both the communicator and context to observe how Arbor configures their defaults.
 
+Step **(13)** creates the simulation using the recipe and the context created in the previous step.
+
 .. literalinclude:: ../../python/example/network_ring_mpi.py
    :language: python
-   :lines: 113-118
+   :lines: 128-136
 
 The execution
 *************
@@ -46,7 +55,7 @@ Step **(16)** runs the simulation. Since we have more cells this time, which are
 
 .. literalinclude:: ../../python/example/network_ring_mpi.py
    :language: python
-   :lines: 131-133
+   :lines: 145-147
 
 An important change in the execution is how the script is run. Whereas normally you run the Python script by passing
 it as an argument to the ``python`` command, you need to use ``srun`` or ``mpirun`` (depending on your MPI
@@ -80,7 +89,7 @@ analyse them later. We query :py:attr:`arbor.context.rank` to generate a unique
 
 .. literalinclude:: ../../python/example/network_ring_mpi.py
    :language: python
-   :lines: 135-
+   :lines: 149-
 
 In a second script, ``network_ring_mpi_plot.py``, we load the results stored to disk into a pandas table, and plot the concatenated table as before:
 
diff --git a/doc/tutorial/two_cells_gap_junctions.rst b/doc/tutorial/network_two_cells_gap_junctions.rst
similarity index 68%
rename from doc/tutorial/two_cells_gap_junctions.rst
rename to doc/tutorial/network_two_cells_gap_junctions.rst
index ea867b5c72f17dd28eb055db5ac84c07224e96f7..2770da8eb1d30b648e4e0585c6406f7825821f8b 100644
--- a/doc/tutorial/two_cells_gap_junctions.rst
+++ b/doc/tutorial/network_two_cells_gap_junctions.rst
@@ -7,6 +7,12 @@ In this example, we will set up two cells connected via a gap junction.
 The cells have different leak potentials.
 We will investigate how the equilibrium potentials of the two cells change because of the gap junction connection.
 
+.. figure:: network_two_cells_gap_junctions_circuit.svg
+    :width: 400
+    :align: center
+
+    The equivalent circuit used to calculate the equilibrium potentials.
+
 .. Note::
 
    **Concepts covered in this example:**
@@ -16,50 +22,43 @@ We will investigate how the equilibrium potentials of the two cells change becau
    3. Running the simulation and extracting the results.
    4. Adding a gap junction connection.
 
-The full code
-*************
-
-You can find the full code of the example at ``python/examples/two_cell_gap_junctions.py``
-
-Executing the script will run the simulation with default parameters.
-
-
 Walk-through
 ************
 
 We set up a recipe for the simulation of two cells with some parameters.
 
-.. literalinclude:: ../../python/example/two_cell_gap_junctions.py
+.. literalinclude:: ../../python/example/network_two_cells_gap_junctions.py
    :language: python
-   :lines: -66
+   :lines: 12-64
 
 Implementing the ``cell_description`` member function constructs the morphology and sets the properties of the cells as well as the gap junction mechanisms and the discretization policy.
 
-.. literalinclude:: ../../python/example/two_cell_gap_junctions.py
+.. literalinclude:: ../../python/example/network_two_cells_gap_junctions.py
    :language: python
-   :lines: 61-101
+   :lines: 66-101
 
 The bidirection gap junction is created in the function ``gap_junctions_on``.
 
-.. literalinclude:: ../../python/example/two_cell_gap_junctions.py
+.. literalinclude:: ../../python/example/network_two_cells_gap_junctions.py
    :language: python
-   :lines: 103-107
+   :lines: 103-117
 
 We parse the command line arguments, instantiate the recipe, run the simulation, extract results and plot:
 
-.. literalinclude:: ../../python/example/two_cell_gap_junctions.py
+.. literalinclude:: ../../python/example/network_two_cells_gap_junctions.py
    :language: python
-   :lines: 110-
+   :lines: 114-
 
 The output plot below shows how the potential of the two cells approaches their equilibrium potentials.
 The expected values are denoted by dashed lines.
 
-.. figure:: two_cell_gap_junctions_result.svg
+.. figure:: network_two_cells_gap_junctions_result.svg
     :width: 800
     :align: center
 
-The equivalent circuit used to calculate the equilibrium potentials is depicted below.
+The full code
+*************
+
+You can find the full code of the example at ``python/examples/network_two_cells_gap_junctions.py``
 
-.. figure:: gap_junction_circuit.svg
-    :width: 800
-    :align: center
+Executing the script will run the simulation with default parameters.
diff --git a/doc/tutorial/gap_junction_circuit.svg b/doc/tutorial/network_two_cells_gap_junctions_circuit.svg
similarity index 100%
rename from doc/tutorial/gap_junction_circuit.svg
rename to doc/tutorial/network_two_cells_gap_junctions_circuit.svg
diff --git a/doc/tutorial/two_cell_gap_junctions_result.svg b/doc/tutorial/network_two_cells_gap_junctions_result.svg
similarity index 100%
rename from doc/tutorial/two_cell_gap_junctions_result.svg
rename to doc/tutorial/network_two_cells_gap_junctions_result.svg
diff --git a/doc/tutorial/tutorial_lfpykit.rst b/doc/tutorial/probe_lfpykit.rst
similarity index 85%
rename from doc/tutorial/tutorial_lfpykit.rst
rename to doc/tutorial/probe_lfpykit.rst
index 31b1080efb59912c299dc85abc0689cc0251be5a..ac2a076fc9326013794010352054b0a802220033 100644
--- a/doc/tutorial/tutorial_lfpykit.rst
+++ b/doc/tutorial/probe_lfpykit.rst
@@ -1,7 +1,7 @@
 .. _tutorial_lfpykit:
 
-Extracellular signals
-=====================
+Extracellular signals (LFPykit)
+===============================
 
 This example takes elements from other tutorials to create a geometrically
 detailed single cell model from an SWC morphology file, and adds predictions of
@@ -83,13 +83,13 @@ input in one arbitrary chosen control volume (CV).
 
 First we import some required modules:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
    :lines: 14-17
 
 We define a basic :class:`Recipe <arbor.recipe>` class, holding a cell and three probes (voltage, stimulus current and total current):
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
    :lines: 22-54
 
@@ -105,13 +105,13 @@ we use the morphology file in ``python/example/single_cell_detailed.swc``:
 
 Pass the filename as an argument to the simulation script:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
    :lines: 57-66
 
 As a target for a current stimuli we define an :class:`arbor.location` :
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
    :lines: 68-69
 
@@ -123,37 +123,37 @@ sets sinusoid current clamp as stimuli using :class:`~arbor.iclamp`
 defines discretization policy (:class:`~arbor.cv_policy_fixed_per_branch`)
 and returns the corresponding :class:`~arbor.place_pwlin` and :class:`~arbor.cable_cell` objects for use later:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 72-115
+   :lines: 72-116
 
 Store the function output:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 119
+   :lines: 119-120
 
-Next, we instantiate :class:`Recipe`, a :class:`~arbor.context` etc. and execute cell model for a few hundred ms,
+Next, we instantiate :class:`Recipe`, and execute the model for a few hundred ms,
 sampling the different signals every 1 ms:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 121-136
+   :lines: 122-135
 
 Extract recorded membrane voltages, electrode and transmembrane currents.
 Note that membrane voltages at branch points and intersections between CVs are dropped as
 we only illustrate membrane voltages of segments with finite lengths.
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 138-153
+   :lines: 137-152
 
 Finally we sum the stimulation and transmembrane currents, allowing the stimuli to mimic a synapse
 current embedded in the membrane itself rather than an intracellular electrode current:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 161
+   :lines: 154-160
 
 .. _tutorial_lfpykit-lfpykit:
 
@@ -169,23 +169,23 @@ First we define a couple of inherited classes to interface LFPykit
 (as this library is not solely written for Arbor).
 Starting with a class inherited from :class:`lfpykit.CellGeometry`:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 171-204
+   :lines: 170-202
 
 Then, a class inherited from :class:`lfpykit.LineSourcePotential`.
 Other use cases may inherit from any other parent class defined in :mod:`lfpykit.models` in a similar manner:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 207-254
+   :lines: 205-253
 
 With these two classes one may then compute extracellular potentials from transmembrane
 currents in space with a few lines of code:
 
-.. literalinclude:: ../../python/example/single_cell_extracellular_potentials.py
+.. literalinclude:: ../../python/example/probe_lfpykit.py
    :language: python
-   :lines: 257-280
+   :lines: 256-278
 
 .. _tutorial_lfpykit-illustration:
 
@@ -197,7 +197,7 @@ of the extracellular potential (``V_e``) in a plane.
 Each part (CV) of the cell is shown with some color coding for the membrane potential (``V_m``).
 The stimulus location is denoted by the black marker.
 
-.. figure:: tutorial_lfpykit.svg
+.. figure:: probe_lfpykit.svg
     :width: 1600
     :align: center
 
@@ -217,7 +217,7 @@ The stimulus location is denoted by the black marker.
 
 The full code
 -------------
-You can find the full code of the example at ``python/examples/single_cell_extracellular_potentials.py``.
+You can find the full code of the example at ``python/examples/probe_lfpykit.py``.
 
 Find the source of LFPykit `here <https://github.com/LFPy/LFPykit/>`_.
 
diff --git a/doc/tutorial/tutorial_lfpykit.svg b/doc/tutorial/probe_lfpykit.svg
similarity index 100%
rename from doc/tutorial/tutorial_lfpykit.svg
rename to doc/tutorial/probe_lfpykit.svg
diff --git a/doc/tutorial/single_cell_allen.rst b/doc/tutorial/single_cell_allen.rst
index d9736b9a30633e1805724e052dd112de4c5e7b50..16c53748183e7d51063a868b045c78a2a03c354c 100644
--- a/doc/tutorial/single_cell_allen.rst
+++ b/doc/tutorial/single_cell_allen.rst
@@ -40,13 +40,10 @@ We will replicate the "Sweep 35" experiment, which applies a current of 150 nA f
 The morphology
 --------------
 
-:ref:`In an earlier tutorial <tutorialsinglecellswc-cell>` we've seen how an ``swc`` file can be loaded **(1)**
-and how the labels can be set **(2)**. Let's being a new function that will build and return the cable cell in Arbor format:
-
 .. literalinclude:: ../../python/example/single_cell_allen.py
    :language: python
    :dedent:
-   :lines: 70-78
+   :lines: 74-80
 
 Step **(1)** loads the ``swc`` file using :func:`arbor.load_swc_neuron`. Since the ``swc`` specification is informal, a few different interpretations exist, and we use the appropriate one. The interpretations are described :ref:`here <formatswc-arbor>`.
 
@@ -74,7 +71,7 @@ region specific properties, reversal potentials, and mechanism parameters. This
 .. literalinclude:: ../../python/example/single_cell_allen.py
    :language: python
    :dedent:
-   :lines: 12-68
+   :lines: 12-72,82,83
 
 The decor
 ---------
@@ -84,7 +81,7 @@ With the ingredients for the :class:`arbor.decor` extracted, we continue with th
 .. literalinclude:: ../../python/example/single_cell_allen.py
    :language: python
    :dedent:
-   :lines: 80-114
+   :lines: 85-117
 
 Step **(4)** creates an empty :class:`arbor.decor`.
 
@@ -124,7 +121,7 @@ The model
 .. literalinclude:: ../../python/example/single_cell_allen.py
    :language: python
    :dedent:
-   :lines: 116-127
+   :lines: 120-131
 
 Step **(12)** instantiates the :class:`arbor.cable_cell` and an :class:`arbor.single_cell_model`.
 
@@ -144,7 +141,7 @@ Then, we extract Arbor's output, accessible after the simulation ran at
 .. literalinclude:: ../../python/example/single_cell_allen.py
    :language: python
    :dedent:
-   :lines: 129-
+   :lines: 133-
 
 .. figure:: single_cell_allen_result.svg
     :width: 400
diff --git a/doc/tutorial/single_cell_cable.rst b/doc/tutorial/single_cell_cable.rst
index 391da857c56d80c1bfc39b2303b0487a70c87ff2..5f224256e2cebd7540a79e8bf7454d857a17235f 100644
--- a/doc/tutorial/single_cell_cable.rst
+++ b/doc/tutorial/single_cell_cable.rst
@@ -31,19 +31,19 @@ We set up a recipe for the simulation of a single dendrite with some parameters.
 
 .. literalinclude:: ../../python/example/single_cell_cable.py
    :language: python
-   :lines: 11-31
+   :lines: 11-75
 
 Implementing the ``cell_description`` member function constructs the morphology and sets the properties of the dendrite as well as the current stimulus and the discretization policy.
 
 .. literalinclude:: ../../python/example/single_cell_cable.py
    :language: python
-   :lines: 63-98
+   :lines: 77-113
 
 We parse the command line arguments, instantiate the recipe, run the simulation, extract results and plot:
 
 .. literalinclude:: ../../python/example/single_cell_cable.py
    :language: python
-   :lines: 145-202
+   :lines: 153-229
 
 The output plot below shows how the current pulse is attenuated and
 gets broader the further it travels along the dendrite from the current
@@ -57,9 +57,9 @@ The conduction velocity in simulation is calculated from the time of the maximum
 
 .. literalinclude:: ../../python/example/single_cell_cable.py
    :language: python
-   :lines: 204-
+   :lines: 231-
 
-Please have in mind that the calculated (idealized) conduction velocity is only correct for an infinite cable.
+Keep in mind that the calculated (idealized) conduction velocity is only correct for an infinite cable.
 
 ::
 
diff --git a/doc/tutorial/single_cell_detailed.rst b/doc/tutorial/single_cell_detailed.rst
index 806bcf56d8f2b6d9fa4bed4640c991eb6d3f148b..68c7b9c8e80a99549925e9f8ee0f3467812a5ded 100644
--- a/doc/tutorial/single_cell_detailed.rst
+++ b/doc/tutorial/single_cell_detailed.rst
@@ -172,7 +172,7 @@ in the following way:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 33-36
+   :lines: 29-32
 
 This will generate the following regions when applied to the previously defined morphology:
 
@@ -190,7 +190,7 @@ be done as follows:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 37-38
+   :lines: 33-34
 
 This will generate the following region when applied to the previously defined morphology:
 
@@ -204,7 +204,7 @@ terminal points of the morphology.
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 42-44
+   :lines: 38-40
 
 This will generate the following **locsets** (sets of one or more locations) when applied to the
 previously defined morphology:
@@ -221,7 +221,7 @@ previously defined "custom" region; and, separately, the terminal points which b
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 45-48
+   :lines: 41-44
 
 This will generate the following 2 locsets when applied to the previously defined morphology:
 
@@ -254,7 +254,7 @@ step:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 50-57
+   :lines: 46-53
 
 We have set the default initial membrane voltage to -55 mV; the default initial
 temperature to 300 K; the default axial resistivity to 35.4 Ω·cm; and the default membrane
@@ -274,7 +274,7 @@ We can override the default properties by *painting* new values on the relevant
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 59-61
+   :lines: 55-57
 
 With the default and initial values taken care of, we now add some density mechanisms. Let's *paint*
 a *pas* density mechanism everywhere on the cell using the previously defined "all" region; an *hh*
@@ -283,7 +283,7 @@ mechanism has a custom 'gbar' parameter.
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 8,62-66
+   :lines: 8,59-62
 
 The decor object is also used to *place* stimuli and spike detectors on the cell using :meth:`arbor.decor.place`.
 We place 3 current clamps of 2 nA on the "root" locset defined earlier, starting at time = 10, 30, 50 ms and
@@ -293,7 +293,7 @@ in the recipe.
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 68-72
+   :lines: 64-68
 
 .. Note::
 
@@ -313,13 +313,13 @@ to be a single CV, and the rest of the morphology to be comprised of CVs with a
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 74-81
+   :lines: 70-77
 
 Finally, we create the cell.
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 83-85
+   :lines: 79-81
 
 The model
 *********
@@ -328,7 +328,7 @@ Having created the cell, we construct an :class:`arbor.single_cell_model`.
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 87-89
+   :lines: 83-85
 
 The global properties
 ^^^^^^^^^^^^^^^^^^^^^
@@ -370,7 +370,7 @@ model:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 91-95
+   :lines: 87-91
 
 We set the same properties as we did earlier when we were creating the *decor* of the cell, except
 for the initial membrane voltage, which is -65 mV as opposed to -55 mV.
@@ -381,7 +381,7 @@ the "allen" catalogue. We can extend the default catalogue as follow:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 97-101
+   :lines: 93-97
 
 Now all three mechanisms in the *decor* object have been made available to the model.
 
@@ -396,7 +396,7 @@ We can indicate the location we would like to probe using labels from the :class
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 103-107
+   :lines: 99-103
 
 The simulation
 ^^^^^^^^^^^^^^
@@ -405,7 +405,7 @@ The cell and model descriptions are now complete and we can run the simulation:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 109-111
+   :lines: 105-107
 
 The results
 ^^^^^^^^^^^
@@ -416,7 +416,7 @@ spikes on the cell from all spike detectors on the cell and saves the times at w
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 113-117
+   :lines: 109-113
 
 A more interesting result of the simulation is perhaps the output of the voltage probe previously
 placed on the "custom_terminal" locset. The model saves the output of the probes as [time, value]
@@ -425,7 +425,7 @@ choose the any other library:
 
 .. literalinclude:: ../../python/example/single_cell_detailed.py
    :language: python
-   :lines: 5,6,118-125
+   :lines: 5,6,114-
 
 The following plot is generated. The orange line is slightly delayed from the blue line, which is
 what we'd expect because branch 4 is longer than branch 3 of the morphology. We also see 3 spikes,
diff --git a/doc/tutorial/single_cell_detailed_recipe.rst b/doc/tutorial/single_cell_detailed_recipe.rst
index f6f8863fb80af0dcc44d4e2378e6f34e3b30d889..451e28cda8a422dd318e668572d4a53aba897242 100644
--- a/doc/tutorial/single_cell_detailed_recipe.rst
+++ b/doc/tutorial/single_cell_detailed_recipe.rst
@@ -3,127 +3,76 @@
 A detailed single cell recipe
 =============================
 
-This example builds the same single cell model as
-:ref:`the previous tutorial <tutorialsinglecellswc>`, except using a :class:`arbor.recipe`
-and :class:`arbor.simulation` instead of a :class:`arbor.single_cell_model`.
+This example builds the same single cell model as :ref:`tutorialsinglecellswc`,
+except using a :class:`arbor.recipe` and :class:`arbor.simulation` instead of a :class:`arbor.single_cell_model`.
+
+This time, we'll learn a bit more about setting up advanced features using a :class:`arbor.recipe`.
 
 .. Note::
 
    **Concepts covered in this example:**
 
    1. Building a :class:`arbor.recipe`.
-   2. Building an :class:`arbor.context` and a :class:`arbor.domain_decomposition`
-   3. Using the recipe, context and domain decomposition to create a :class:`arbor.simulation`
+   2. Building an :class:`arbor.context`.
+   3. Create a :class:`arbor.simulation`.
    4. Running the simulation and visualizing the results,
 
-
-Recipes are an important concept in Arbor. They represent the most versatile tool
-for building a complex network of cells. We will go though this example of a model
-of a single cell, before using the recipe to represent more complex networks in
-subsequent examples.
-
-We outline the following steps of this example:
-
-1. Define the **cell**. This is the same cell we have seen before.
-2. Define the **recipe** of the model.
-3. Define the **execution context** of the model: a description of the underlying system
-   on which the simulation will run.
-4. Define the **domain decomposition** of the network: how the cells are distributed on
-   the different ranks of the system.
-5. Define the **simulation**.
-6. **Run** the simulation.
-7. Collect and visualize the **results**.
-
 The cell
 ********
 
 We can copy the cell description code or reuse ``single_cell_detailed.swc`` from the
-:ref:`previous example <tutorialsinglecellswc-cell>` where it is explained in detail.
-
-We will need to add one more thing to the cell. We will create the voltage probe at the "custom_terminal" locset.
-In the previous example, this probe was registered directly using the :class:`arbor.single_cell_model` object.
-Now it has to be explicitly created and registered in the recipe.
-
-.. _tutorialsinglecellswcrecipe-probe:
-
-.. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
-   :language: python
-   :lines: 87-89
+:ref:`original example <tutorialsinglecellswc-cell>` where it is explained in detail.
 
 The recipe
 **********
 
-The :class:`arbor.single_cell_model` of the previous example created a :class:`arbor.recipe` under
+The :class:`arbor.single_cell_model` of the original example created a :class:`arbor.recipe` under
 the hood, and abstracted away the details so we were unaware of its existence. In this example, we will
 examine the recipe in detail: how to create one, and why it is needed.
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 91-139
+   :lines: 84-120
 
 Let's go through the recipe point by point.
 
-Step **(6)** creates a ``single_recipe`` class that inherits from :class:`arbor.recipe`. The base recipe
-implements all the methods defined above with default values except :meth:`arbor.recipe.num_cells`,
-:meth:`arbor.recipe.cell_kind` and :meth:`arbor.recipe.cell_description` which always have to be implemented
-by the user. The :meth:`arbor.recipe.global_properties` also needs to be implemented for
-:class:`arbor.cell_kind.cable` cells. The inherited recipe can implement any number of additional methods and
-have any number of instance or class variables.
-
-Step **(6.1)** defines the class constructor. In this case, we pass a ``cell`` and a set of ``probes`` as
-arguments. These will be used to initialize the instance variables ``self.the_cell`` and ``self.the_probes``,
-which will be used in the overloaded ``cell_description`` and ``get_probes`` methods. Before variable
-initialization, we call the base C++ class constructor ``arbor.recipe.__init__(self)``. This ensures correct
-initialization of memory in the C++ class.
-
-We also create the ``self.the_cat`` variable and set it to arbor's default mechanism catalogue. This will expose
-the *hh* and *pas* mechanisms but not the *Ih* mechanism, which is present in the allen catalogue. To be able
-to use *Ih*, we extend ``self.the_cat`` to include the allen catalogue.
+Step **(5)** creates a ``single_recipe`` class that inherits from :class:`arbor.recipe`.
+:meth:`arbor.recipe.num_cells`, :meth:`arbor.recipe.cell_kind` and :meth:`arbor.recipe.cell_description`
+always have to be implemented by the user. We'll also implement :meth:`arbor.recipe.global_properties` to be able
+to decorate :class:`arbor.cell_kind.cable` cells with mechanisms and :meth:`arbor.recipe.probes` to be able to
+insert the probe.
 
-Finally we create the ``self.the_props`` variable. This will hold the global properties of the model, which apply
-to all the cells in the network. Initially it is empty. We set all the properties of the system similar to
-what we did in the :ref:`previous example <tutorialsinglecellswc-gprop>`. One last important step is to register
-``self.the_cat`` with ``self.the_props``.
+Step **(5.1)** defines the class constructor. As per :class:`arbor.recipe` instructions, we call
+``arbor.recipe.__init__(self)`` to ensure correct initialization of memory in the C++ class.
 
-.. Note::
-
-   The mechanism catalogue needs to live in the recipe as an instance variable. Its lifetime needs to extend
-   to the entire duration of the simulation.
+We then create the ``self.the_props`` variable. This will hold the global properties of the model, which apply
+to all the cells in the network. We initialize it with :class:`arbor.cable_global_properties`, which comes with the 
+``default`` mechanism catalogue built-in. We set all the properties of the system similar to what we did in
+the :ref:`original example <tutorialsinglecellswc-gprop>`. One last important step is to extend ``self.the_props``
+to include the Allen catalogue, because it holds the *Ih* mechanism. The *hh* and *pas* mechanisms came with the
+default catalogue.
 
-Step **(6.2)** overrides the :meth:`arbor.recipe.num_cells` method. It takes no arguments. We simply return 1,
+Step **(5.2)** overrides the :meth:`~arbor.recipe.num_cells` method. It takes no arguments. We simply return 1,
 as we are only simulating one cell in this example.
 
-Step **(6.3)** overrides the :meth:`arbor.recipe.cell_kind` method. It takes one argument: ``gid``.
+Step **(5.3)** overrides the :meth:`~arbor.recipe.cell_kind` method. It takes one argument: ``gid``.
 Given the gid, this method returns the kind of the cell. Our defined cell is a
 :class:`arbor.cell_kind.cable`, so we simply return that.
 
-Step **(6.4)** overrides the :meth:`arbor.recipe.cell_description` method. It takes one argument: ``gid``.
+Step **(5.4)** overrides the :meth:`~arbor.recipe.cell_description` method. It takes one argument: ``gid``.
 Given the gid, this method returns the cell description which is the cell object passed to the constructor
-of the recipe. We return ``self.the_cell``.
+of the recipe. We return ``cell``, the cell created just above.
 
-Step **(6.5)** overrides the :meth:`arbor.recipe.get_probes` method. It takes one argument: ``gid``.
+Step **(5.5)** overrides the :meth:`~arbor.recipe.probes` method. It takes one argument: ``gid``.
 Given the gid, this method returns all the probes on the cell. The probes can be of many different kinds
-measuring different quantities on different locations of the cell. We pass these probes explicitly to the recipe
-and they are stored in ``self.the_probes``, so we return that variable.
-
-Step **(6.6)** overrides the :meth:`arbor.recipe.connections_on` method. It takes one argument: ``gid``.
-Given the gid, this method returns all the connections ending on that cell. These are typically synapse
-connections from other cell *sources* to specific *targets* on the cell with id ``gid``. Since we are
-simulating a single cell, and self-connections are not possible, we return an empty list.
-
-Step **(6.7)** overrides the :meth:`arbor.recipe.gap_junctions_on` method. It takes one argument: ``gid``.
-Given the gid, this method returns all the gap junctions on that cell. Gap junctions require 2 separate cells.
-Since we are simulating a single cell, we return an empty list.
+measuring different quantities on different locations of the cell. Like in the original example, we will
+create the voltage probe at the ``"custom_terminal"`` locset. This probe was registered directly using the 
+:class:`arbor.single_cell_model` object. Now it has to be explicitly created and registered in the recipe.
 
-Step **(6.8)** overrides the :meth:`arbor.recipe.event_generators` method. It takes one argument: ``gid``.
-Given the gid, this method returns *event generators* on that cell. These generators trigger events (or
-spikes) on specific *targets* on the cell. They can be used to simulate spikes from other cells, to kick-start
-a simulation for example. Our cell uses a current clamp as a stimulus, and has no targets, so we return
-an empty list.
-
-Step **(6.9)** overrides the :meth:`arbor.recipe.global_properties` method. It takes one argument: ``kind``.
+Step **(5.6)** overrides the :meth:`~arbor.recipe.global_properties` method. It takes one argument: ``kind``.
 This method returns the default global properties of the model which apply to all cells in the network of
-that kind. We return ``self.the_props`` which we defined in step **(1)**.
+that kind. We only use ``cable`` cells in this example (but there are more) and thus always return a
+``cable_cell_properties`` object. We return ``self.the_props`` which we defined in step **(1)**.
 
 .. Note::
 
@@ -135,85 +84,44 @@ that kind. We return ``self.the_props`` which we defined in step **(1)**.
 
    More information on the recipe can be found :ref:`here <modelrecipe>`.
 
-Now we can instantiate a ``single_recipe`` object using the ``cell`` and ``probe`` we created in the
-previous section:
-
-.. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
-   :language: python
-   :lines: 141-143
-
-The execution context
-*********************
-
-An :ref:`execution context <modelcontext>` describes the hardware resources on which the simulation will run.
-It contains the thread pool used to parallelise work on the local CPU, and optionally describes GPU resources
-and the MPI communicator for distributed simulations. In the previous
-examples, the :class:`arbor.single_cell_model` object created the execution context :class:`arbor.context`
-behind the scenes.
-
-The details of the execution context can be customized by the user. We may specify the number of threads
-in the thread pool; determine the id of the GPU to be used; or create our own MPI communicator. However,
-the ideal settings can usually be inferred from the system, and Arbor can do that with a simple command.
-
-.. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
-   :language: python
-   :lines: 145-146
-
-The domain decomposition
-************************
-
-The domain decomposition describes the distribution of the cells over the available computational resources.
-The :class:`arbor.single_cell_model` also handled that without our knowledge in the previous examples.
-Now, we have to define it ourselves.
-
-The :class:`arbor.domain_decomposition` class can be manually created by the user, by deciding which cells
-go on which ranks. Or we can use a load balancer that can partition the cells across ranks according to
-some rules. Arbor provides :class:`arbor.partition_load_balance`, which, using the recipe and execution
-context, creates the :class:`arbor.domain_decomposition` object for us.
-
-Our example is a simple one, with just one cell. We don't need any sophisticated partitioning algorithms, so
-we can use the load balancer, which does a good job distributing simple networks.
+Now we can instantiate a ``single_recipe`` object.
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 148-149
+   :lines: 123-124
 
 The simulation
 **************
 
-Finally we have the 3 components needed to create a :class:`arbor.simulation` object.
+We have all we need to create a :class:`arbor.simulation` object.
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 151-152
+   :lines: 126-127
 
 Before we run the simulation, however, we need to register what results we expect once execution is over.
-This was handled by the :class:`arbor.single_cell_model` object in the previous example.
+This was handled by the :class:`arbor.single_cell_model` object in the original example.
 
 We would like to get a list of the spikes on the cell during the runtime of the simulation, and we would like
 to plot the voltage registered by the probe on the "custom_terminal" locset.
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 154-158
+   :lines: 129-133
 
-The lines handling probe sampling warrant a second look. First, we declared ``probe_id`` to be a
+The lines handling probe sampling warrant a second look. First, we declared :term:`probe_id` to be a
 :class:`arbor.cell_member`, with :class:`arbor.cell_member.gid` = 0 and :class:`arbor.cell_member.index` = 0.
 This variable serves as a global identifier of a probe on a cell, namely the first declared probe on the
-cell with gid = 0, which is id of the :ref:`only probe <tutorialsinglecellswcrecipe-probe>` we created on
-the only cell in the model.
+cell with ``gid = 0``, which is id of the only probe we created on the only cell in the model.
 
-Next, we instructed the simulation to sample ``probe_id`` at a frequency of 50 kHz. That function returns a
-``handle`` which we will use to extract the results of the sampling after running the simulation.
-
-The execution
-*************
+Next, we instructed the simulation to sample :term:`probe_id` at a frequency of 50 kHz. That function returns a
+:term:`handle` which we will use to :ref:`extract the results <pycablecell-probesample>` of the sampling after running the simulation.
 
 We can now run the simulation we just instantiated for a duration of 100 ms with a time step of 0.025 ms.
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 169-161
+   :lines: 135-136
 
 The results
 ***********
@@ -225,13 +133,13 @@ We can print the times of the spikes:
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 163-167
+   :lines: 138-142
 
 The probe results, again, warrant some more explanation:
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 169-173
+   :lines: 144-148
 
 ``sim.samples()`` takes a ``handle`` of the probe we wish to examine. It returns a list
 of ``(data, meta)`` terms: ``data`` being the time and value series of the probed quantity; and
@@ -240,13 +148,13 @@ discrete locations pointed to by the handle. We placed the probe on the "custom_
 represented by 2 locations on the morphology. We therefore expect the length of ``sim.samples(handle)``
 to be 2.
 
-We plot the results using pandas and seaborn as we did in the previous example, and expect the same results:
+We plot the results using pandas and seaborn as we did in the original example, and expect the same results:
 
 .. literalinclude:: ../../python/example/single_cell_detailed_recipe.py
    :language: python
-   :lines: 175-179
+   :lines: 150-
 
-The following plot is generated. Identical to the plot of the previous example.
+The following plot is generated. Identical to the plot of the original example.
 
 .. figure:: single_cell_detailed_result.svg
     :width: 400
diff --git a/doc/tutorial/single_cell_recipe.rst b/doc/tutorial/single_cell_recipe.rst
index 33061c4b1f5b9e842a7983bc33d143e32d512475..c9e77771bed071c5ae2577f86a97d31c004cbea6 100644
--- a/doc/tutorial/single_cell_recipe.rst
+++ b/doc/tutorial/single_cell_recipe.rst
@@ -3,9 +3,13 @@
 A simple single cell recipe
 ===========================
 
-This example builds the same single cell model as
-:ref:`the previous tutorial <tutorialsinglecell>`, except using a :class:`arbor.recipe`
-and :class:`arbor.simulation` instead of a :class:`arbor.single_cell_model`.
+This example builds the same single cell model as :ref:`tutorialsinglecell`,
+except using a :class:`arbor.recipe` and :class:`arbor.simulation` instead of a :class:`arbor.single_cell_model`.
+
+Recipes are an important concept in Arbor. They represent the most versatile tool
+for building a complex network of cells. We will go though this example of a model
+of a single cell, before using the recipe to represent more complex networks in
+subsequent examples.
 
 .. Note::
 
@@ -18,29 +22,32 @@ and :class:`arbor.simulation` instead of a :class:`arbor.single_cell_model`.
 The cell
 --------
 
-Let's copy the cell description from the :ref:`previous example <tutorialsinglecell-cell>`,
+Let's copy the cell description from the :ref:`original example <tutorialsinglecell-cell>`,
 where construction of the cell is explained in detail.
 
 .. literalinclude:: ../../python/example/single_cell_recipe.py
    :language: python
-   :lines: 4,8-23
+   :lines: 4,10-26
 
 The recipe
 ----------
 
-In the :ref:`previous example <tutorialsinglecell-cell>`, the :class:`arbor.single_cell_model` creates
+In the :ref:`original example <tutorialsinglecell-cell>`, the :class:`arbor.single_cell_model` creates
 a :class:`arbor.recipe` under the hood, and abstracts away a few details that you may want control over
 in more complex simulations. Let's go into those abstractions and create an analogous :class:`arbor.recipe`
 manually.
 
-Creating a recipe starts with creating a class that inherits from :class:`arbor.recipe`
-and overrides and implements some of :class:`arbor.recipe` methods. Not all methods
-have to be overridden, but some will always have to be, such as :meth:`arbor.recipe.num_cells`.
-It returns `0` by default and models without cells are quite boring!
+Creating a recipe starts with creating a class that inherits from :class:`arbor.recipe`. There are a number of
+methods that *must* be overridden, and a number than *can optionally* be overridden, as explained in the
+:class:`arbor.recipe` documentation. Beyond this, it is up to you, the user, to structure your code as you
+find convenient.
+
+One of the methods that must be overridden is :meth:`arbor.recipe.num_cells`. It returns `0` by default and 
+models without cells are quite boring!
 
 .. literalinclude:: ../../python/example/single_cell_recipe.py
    :language: python
-   :lines: 25-60
+   :lines: 28-63
 
 Step **(4)** describes the recipe that will reflect our single cell model.
 
@@ -59,59 +66,55 @@ associated with the cable cell defined above. If you mix multiple cell kinds and
 descriptions in one recipe, make sure a particular ``gid`` returns matching cell kinds
 and descriptions.
 
-Step **(4.4)** returns the cell description passed in on class initialisation. If we
+Step **(4.4)** returns the cell description defined earlier. If we
 were modelling multiple cells of different kinds, we would need to make sure that the
 cell returned by :meth:`arbor.recipe.cell_description` has the same cell kind as
 returned by :meth:`arbor.recipe.cell_kind` for every :gen:`gid`.
 
-Step **(4.5)** returns the probes passed in at class initialisation.
+Step **(4.5)** returns the same probe as in the ``single_cell_model``: a single voltage probe located at "midpoint".
 
 Step **(4.6)** returns the properties that will be applied to all cells of that kind in the model.
 
 More methods can be overridden if your model requires that, see :class:`arbor.recipe` for options.
 
-Step **(5)** instantiates the recipe with the cable cell described earlier, and a single voltage probe located at "midpoint".
+Step **(5)** instantiates the recipe.
 
-The context and domain decomposition
-------------------------------------
+The simulation
+--------------
 
 :class:`arbor.single_cell_model` does not only take care of the recipe, it also takes
 care of defining how the simulation will be run. When you create and use your own
-recipe, you'll need to do this manually, in the form of defining a execution context
+recipe, you can to do this manually, in the form of defining a execution context
 and a domain decomposition. Fortunately, the default constructors of
 :class:`arbor.context` and :class:`arbor.partition_load_balance` are sufficient for
-this model, and is what :class:`arbor.single_cell_model` does under the hood! We'll
-leave the details of this subject for another tutorial.
+this model, and is what :class:`arbor.single_cell_model` does under the hood!
+In addition, if all you need is the default context and domain decomposition, they can be
+left out and the :class:`arbor.simulation` object can be contructed from just the recipe.
 
-.. literalinclude:: ../../python/example/single_cell_recipe.py
-   :language: python
-   :lines: 62-65
-
-Step **(6)** sets up a default context and domains.
-
-The simulation
---------------
+The details of manual hardware configuration will be left for another tutorial.
 
 .. literalinclude:: ../../python/example/single_cell_recipe.py
    :language: python
-   :lines: 67-73
+   :lines: 65-75
+
+Step **(6)** instantiates the simulation.
 
-Step **(7)** instantiates the simulation and sets up the probe added in step 5. In the
+Step **(7)** sets up the probe added in step 5. In the
 :class:`arbor.single_cell_model` version of this example, the probe frequency and
 simulation duration are the same. Note that the frequency is set with a :class:`arbor.regular_schedule`,
 which takes a time and not a frequency. Also note that spike recording must be
-switched on. For extraction of the probe traces later on, we store a handle.
+switched on. For :ref:`extraction of the probe traces <pycablecell-probesample>` later on, we store a :term:`handle`.
 
 The results
 ----------------------------------------------------
 
 Apart from creating :class:`arbor.recipe` ourselves, we have changed nothing
-about this simulation compared to :ref:`the previous tutorial <tutorialsinglecell>`.
+about this simulation compared to :ref:`the original tutorial <tutorialsinglecell>`.
 If we create the same analysis of the results we therefore expect the same results.
 
 .. literalinclude:: ../../python/example/single_cell_recipe.py
    :language: python
-   :lines: 75-92
+   :lines: 77-
 
 Step **(8)** plots the measured potentials during the runtime of the simulation.
 Retrieving the sampled quantities is a little different, these have to be accessed
diff --git a/python/example/network_ring.py b/python/example/network_ring.py
index 3de7bfb74effbc5a080b634587f1fc86a5fcd7f9..692f44433cd719f5d9fc02afd40897b5d517d477 100755
--- a/python/example/network_ring.py
+++ b/python/example/network_ring.py
@@ -2,8 +2,8 @@
 # This script is included in documentation. Adapt line numbers if touched.
 
 import arbor
-import pandas
-import seaborn
+import pandas  # You may have to pip install these
+import seaborn  # You may have to pip install these
 from math import sqrt
 
 # Construct a cell with the following morphology.
@@ -123,9 +123,9 @@ class ring_recipe(arbor.recipe):
 ncells = 4
 recipe = ring_recipe(ncells)
 
-# (12) Create an execution context using all locally available threads, domain decomposition and simulation
+# (12) Create an execution context using all locally available threads and simulation
 ctx = arbor.context("avail_threads")
-sim = arbor.simulation(recipe, context=ctx)
+sim = arbor.simulation(recipe, ctx)
 
 # (13) Set spike generators to record
 sim.record(arbor.spike_recording.all)
diff --git a/python/example/network_ring_gpu.py b/python/example/network_ring_gpu.py
new file mode 100644
index 0000000000000000000000000000000000000000..3d071d1649bf3faf9a60a197f318c2d53cab7586
--- /dev/null
+++ b/python/example/network_ring_gpu.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+# This script is included in documentation. Adapt line numbers if touched.
+
+import arbor
+import pandas  # You may have to pip install these
+import seaborn  # You may have to pip install these
+from math import sqrt
+
+# Construct a cell with the following morphology.
+# The soma (at the root of the tree) is marked 's', and
+# the end of each branch i is marked 'bi'.
+#
+#         b1
+#        /
+# s----b0
+#        \
+#         b2
+
+
+def make_cable_cell(gid):
+    # (1) Build a segment tree
+    tree = arbor.segment_tree()
+
+    # Soma (tag=1) with radius 6 μm, modelled as cylinder of length 2*radius
+    s = tree.append(
+        arbor.mnpos, arbor.mpoint(-12, 0, 0, 6), arbor.mpoint(0, 0, 0, 6), tag=1
+    )
+
+    # Single dendrite (tag=3) of length 50 μm and radius 2 μm attached to soma.
+    b0 = tree.append(s, arbor.mpoint(0, 0, 0, 2), arbor.mpoint(50, 0, 0, 2), tag=3)
+
+    # Attach two dendrites (tag=3) of length 50 μm to the end of the first dendrite.
+    # As there's no further use for them, we discard the returned handles.
+    # (b1) Radius tapers from 2 to 0.5 μm over the length of the dendrite.
+    _ = tree.append(
+        b0,
+        arbor.mpoint(50, 0, 0, 2),
+        arbor.mpoint(50 + 50 / sqrt(2), 50 / sqrt(2), 0, 0.5),
+        tag=3,
+    )
+    # (b2) Constant radius of 1 μm over the length of the dendrite.
+    _ = tree.append(
+        b0,
+        arbor.mpoint(50, 0, 0, 1),
+        arbor.mpoint(50 + 50 / sqrt(2), -50 / sqrt(2), 0, 1),
+        tag=3,
+    )
+
+    # Associate labels to tags
+    labels = arbor.label_dict()
+    labels["soma"] = "(tag 1)"
+    labels["dend"] = "(tag 3)"
+
+    # (2) Mark location for synapse at the midpoint of branch 1 (the first dendrite).
+    labels["synapse_site"] = "(location 1 0.5)"
+    # Mark the root of the tree.
+    labels["root"] = "(root)"
+
+    # (3) Create a decor and a cable_cell
+    decor = arbor.decor()
+
+    # Put hh dynamics on soma, and passive properties on the dendrites.
+    decor.paint('"soma"', arbor.density("hh"))
+    decor.paint('"dend"', arbor.density("pas"))
+
+    # (4) Attach a single synapse.
+    decor.place('"synapse_site"', arbor.synapse("expsyn"), "syn")
+
+    # Attach a spike detector with threshold of -10 mV.
+    decor.place('"root"', arbor.spike_detector(-10), "detector")
+
+    cell = arbor.cable_cell(tree, labels, decor)
+
+    return cell
+
+
+# (5) Create a recipe that generates a network of connected cells.
+class ring_recipe(arbor.recipe):
+    def __init__(self, ncells):
+        # The base C++ class constructor must be called first, to ensure that
+        # all memory in the C++ class is initialized correctly.
+        arbor.recipe.__init__(self)
+        self.ncells = ncells
+        self.props = arbor.neuron_cable_properties()
+
+    # (6) The num_cells method that returns the total number of cells in the model
+    # must be implemented.
+    def num_cells(self):
+        return self.ncells
+
+    # (7) The cell_description method returns a cell
+    def cell_description(self, gid):
+        return make_cable_cell(gid)
+
+    # The kind method returns the type of cell with gid.
+    # Note: this must agree with the type returned by cell_description.
+    def cell_kind(self, gid):
+        return arbor.cell_kind.cable
+
+    # (8) Make a ring network. For each gid, provide a list of incoming connections.
+    def connections_on(self, gid):
+        src = (gid - 1) % self.ncells
+        w = 0.01  # 0.01 μS on expsyn
+        d = 5  # ms delay
+        return [arbor.connection((src, "detector"), "syn", w, d)]
+
+    # (9) Attach a generator to the first cell in the ring.
+    def event_generators(self, gid):
+        if gid == 0:
+            sched = arbor.explicit_schedule([1])  # one event at 1 ms
+            weight = 0.1  # 0.1 μS on expsyn
+            return [arbor.event_generator("syn", weight, sched)]
+        return []
+
+    # (10) Place a probe at the root of each cell.
+    def probes(self, gid):
+        return [arbor.cable_probe_membrane_voltage('"root"')]
+
+    def global_properties(self, kind):
+        return self.props
+
+
+# (11) Set up the hardware context
+# gpu_id set to None will not use a GPU.
+# gpu_id=0 instructs Arbor to the first GPU present in your system
+context = arbor.context(threads="avail_threads", gpu_id=None)
+print(context)
+
+# (12) Set up and start the meter manager
+meters = arbor.meter_manager()
+meters.start(context)
+
+# (13) Instantiate recipe
+ncells = 50
+recipe = ring_recipe(ncells)
+meters.checkpoint("recipe-create", context)
+
+# (14) Define a hint at to the execution.
+hint = arbor.partition_hint()
+hint.prefer_gpu = True
+hint.gpu_group_size = 1000
+print(hint)
+hints = {arbor.cell_kind.cable: hint}
+
+# (15) Domain decomp
+decomp = arbor.partition_load_balance(recipe, context, hints)
+print(decomp)
+meters.checkpoint("load-balance", context)
+
+# (16) Simulation init and set spike generators to record
+sim = arbor.simulation(recipe, context, decomp)
+sim.record(arbor.spike_recording.all)
+handles = [sim.sample((gid, 0), arbor.regular_schedule(1)) for gid in range(ncells)]
+meters.checkpoint("simulation-init", context)
+
+# (17) Run simulation
+sim.run(ncells * 5)
+print("Simulation finished")
+meters.checkpoint("simulation-run", context)
+
+# (18) Results
+# Print profiling information
+print(f"{arbor.meter_report(meters, context)}")
+
+# Print spike times
+print("spikes:")
+for sp in sim.spikes():
+    print(" ", sp)
+
+# Plot the recorded voltages over time.
+print("Plotting results ...")
+df_list = []
+for gid in range(ncells):
+    samples, meta = sim.samples(handles[gid])[0]
+    df_list.append(
+        pandas.DataFrame(
+            {"t/ms": samples[:, 0], "U/mV": samples[:, 1], "Cell": f"cell {gid}"}
+        )
+    )
+
+df = pandas.concat(df_list, ignore_index=True)
+seaborn.relplot(data=df, kind="line", x="t/ms", y="U/mV", hue="Cell", ci=None).savefig(
+    "network_ring_gpu_result.svg"
+)
diff --git a/python/example/network_ring_mpi.py b/python/example/network_ring_mpi.py
index b56e81e8cd2699a177d76953eb9c77694b165ffa..2ac4f014380f19339a35be803c6d029201264d04 100644
--- a/python/example/network_ring_mpi.py
+++ b/python/example/network_ring_mpi.py
@@ -146,7 +146,7 @@ handles = [sim.sample((gid, 0), arbor.regular_schedule(1)) for gid in range(ncel
 sim.run(ncells * 5)
 print("Simulation finished")
 
-# (17) Plot the recorded voltages over time.
+# (17) Store the recorded voltages
 print("Storing results ...")
 df_list = []
 for gid in range(ncells):
diff --git a/python/example/two_cell_gap_junctions.py b/python/example/network_two_cells_gap_junctions.py
similarity index 99%
rename from python/example/two_cell_gap_junctions.py
rename to python/example/network_two_cells_gap_junctions.py
index c303802cb5a3bee410341f96d102f191f2618f6d..54fa53c0810a2a741afebc04a329e0a6be3d3dbf 100755
--- a/python/example/two_cell_gap_junctions.py
+++ b/python/example/network_two_cells_gap_junctions.py
@@ -4,7 +4,7 @@ import arbor
 import argparse
 import numpy as np
 
-import pandas
+import pandas  # You may have to pip install these.
 import seaborn  # You may have to pip install these.
 import matplotlib.pyplot as plt
 
diff --git a/python/example/single_cell_extracellular_potentials.py b/python/example/probe_lfpykit.py
similarity index 100%
rename from python/example/single_cell_extracellular_potentials.py
rename to python/example/probe_lfpykit.py
diff --git a/python/example/single_cell_allen.py b/python/example/single_cell_allen.py
index 273d5dc2f1cf09acf69e4464fd938a534d171bb3..eac24649175f0d8d0f8b7e6f66066a4663357e6a 100644
--- a/python/example/single_cell_allen.py
+++ b/python/example/single_cell_allen.py
@@ -72,6 +72,7 @@ def load_allen_fit(fit):
 
 
 def make_cell(swc, fit):
+    # (1) Load the swc file passed into this function
     morphology = arbor.load_swc_neuron(swc)
     # (2) Label the region tags found in the swc with the names used in the parameter fit file.
     # In addition, label the midpoint of the somarbor.
diff --git a/python/example/single_cell_cable.py b/python/example/single_cell_cable.py
index b375ec377bb9f176abf46c2c3fb0d7f955151fa4..adf2e4e7bbb775a3e63321d01fea7d23ffc7323c 100755
--- a/python/example/single_cell_cable.py
+++ b/python/example/single_cell_cable.py
@@ -40,8 +40,6 @@ class Cable(arbor.recipe):
         cv_policy_max_extent -- maximum extent of control volume in μm
         """
 
-        # (4.1) The base C++ class constructor must be called first, to ensure that
-        # all memory in the C++ class is initialized correctly.
         arbor.recipe.__init__(self)
 
         self.the_probes = probes
@@ -65,20 +63,23 @@ class Cable(arbor.recipe):
         return 1
 
     def num_sources(self, gid):
-        assert gid == 0
         return 0
 
     def cell_kind(self, gid):
-        assert gid == 0
         return arbor.cell_kind.cable
 
+    def probes(self, gid):
+        return self.the_probes
+
+    def global_properties(self, kind):
+        return self.the_props
+
     def cell_description(self, gid):
         """A high level description of the cell with global identifier gid.
 
         For example the morphology, synapses and ion channels required
         to build a multi-compartment neuron.
         """
-        assert gid == 0
 
         tree = arbor.segment_tree()
 
@@ -111,13 +112,6 @@ class Cable(arbor.recipe):
 
         return arbor.cable_cell(tree, labels, decor)
 
-    def probes(self, gid):
-        assert gid == 0
-        return self.the_probes
-
-    def global_properties(self, kind):
-        return self.the_props
-
 
 def get_rm(g):
     """Return membrane resistivity in Ohm*m^2
diff --git a/python/example/single_cell_detailed_recipe.py b/python/example/single_cell_detailed_recipe.py
index 5bcd19f8d6693e31f17d8d271fe4498bd8ec575d..86aa9ce49f59c0ecf7b86c7a927e3f6d5fa9a065 100644
--- a/python/example/single_cell_detailed_recipe.py
+++ b/python/example/single_cell_detailed_recipe.py
@@ -80,21 +80,15 @@ decor.discretization(policy)
 
 cell = arbor.cable_cell(morph, labels, decor)
 
-# (5) Declare a probe.
 
-probe = arbor.cable_probe_membrane_voltage('"custom_terminal"')
-
-
-# (6) Create a class that inherits from arbor.recipe
+# (5) Create a class that inherits from arbor.recipe
 class single_recipe(arbor.recipe):
 
-    # (6.1) Define the class constructor
-    def __init__(self, cell, probes):
+    # (5.1) Define the class constructor
+    def __init__(self):
         # The base C++ class constructor must be called first, to ensure that
         # all memory in the C++ class is initialized correctly.
         arbor.recipe.__init__(self)
-        self.the_cell = cell
-        self.the_probes = probes
 
         self.the_props = arbor.cable_global_properties()
         self.the_props.set_property(Vm=-65, tempK=300, rL=35.4, cm=0.01)
@@ -105,44 +99,31 @@ class single_recipe(arbor.recipe):
         self.the_props.set_ion(ion="ca", int_con=5e-5, ext_con=2, rev_pot=132.5)
         self.the_props.catalogue.extend(arbor.allen_catalogue(), "")
 
-    # (6.2) Override the num_cells method
+    # (5.2) Override the num_cells method
     def num_cells(self):
         return 1
 
-    # (6.3) Override the num_targets method
+    # (5.3) Override the cell_kind method
     def cell_kind(self, gid):
         return arbor.cell_kind.cable
 
-    # (6.4) Override the cell_description method
+    # (5.4) Override the cell_description method
     def cell_description(self, gid):
-        return self.the_cell
+        return cell
 
-    # (6.5) Override the probes method
+    # (5.5) Override the probes method
     def probes(self, gid):
-        return self.the_probes
-
-    # (6.6) Override the connections_on method
-    def connections_on(self, gid):
-        return []
-
-    # (6.7) Override the gap_junction_on method
-    def gap_junction_on(self, gid):
-        return []
-
-    # (6.8) Override the event_generators method
-    def event_generators(self, gid):
-        return []
+        return [arbor.cable_probe_membrane_voltage('"custom_terminal"')]
 
-    # (6.9) Overrode the global_properties method
+    # (5.6) Override the global_properties method
     def global_properties(self, gid):
         return self.the_props
 
 
 # Instantiate recipe
-# Pass the probe in a list because that it what single_recipe expects.
-recipe = single_recipe(cell, [probe])
+recipe = single_recipe()
 
-# (7) Create a simulation
+# (6) Create a simulation
 sim = arbor.simulation(recipe)
 
 # Instruct the simulation to record the spikes and sample the probe
@@ -151,10 +132,10 @@ sim.record(arbor.spike_recording.all)
 probe_id = arbor.cell_member(0, 0)
 handle = sim.sample(probe_id, arbor.regular_schedule(0.02))
 
-# (8) Run the simulation
+# (7) Run the simulation
 sim.run(tfinal=100, dt=0.025)
 
-# (9) Print or display the results
+# (8) Print or display the results
 spikes = sim.spikes()
 print(len(spikes), "spikes recorded:")
 for s in spikes:
diff --git a/python/example/single_cell_recipe.py b/python/example/single_cell_recipe.py
index cace98de20d25ea1458307b906bdefe7dfd98564..1a2f0334b370270e038b45f250050e91ae022182 100644
--- a/python/example/single_cell_recipe.py
+++ b/python/example/single_cell_recipe.py
@@ -8,13 +8,16 @@ import seaborn  # You may have to pip install these.
 # The corresponding generic recipe version of `single_cell_model.py`.
 
 # (1) Create a morphology with a single (cylindrical) segment of length=diameter=6 μm
+
 tree = arbor.segment_tree()
 tree.append(arbor.mnpos, arbor.mpoint(-3, 0, 0, 3), arbor.mpoint(3, 0, 0, 3), tag=1)
 
 # (2) Define the soma and its midpoint
+
 labels = arbor.label_dict({"soma": "(tag 1)", "midpoint": "(location 0 0.5)"})
 
 # (3) Create cell and set properties
+
 decor = arbor.decor()
 decor.set_property(Vm=-40)
 decor.paint('"soma"', arbor.density("hh"))
@@ -28,12 +31,10 @@ cell = arbor.cable_cell(tree, labels, decor)
 
 
 class single_recipe(arbor.recipe):
-    def __init__(self, cell, probes):
+    def __init__(self):
         # (4.1) The base C++ class constructor must be called first, to ensure that
         # all memory in the C++ class is initialized correctly.
         arbor.recipe.__init__(self)
-        self.the_cell = cell
-        self.the_probes = probes
         self.the_props = arbor.neuron_cable_properties()
 
     def num_cells(self):
@@ -46,25 +47,29 @@ class single_recipe(arbor.recipe):
 
     def cell_description(self, gid):
         # (4.4) Override the cell_description method
-        return self.the_cell
+        return cell
 
     def probes(self, gid):
-        # (4.5) Override the probes method
-        return self.the_probes
+        # (4.5) Override the probes method with a voltage probe located on "midpoint"
+        return [arbor.cable_probe_membrane_voltage('"midpoint"')]
 
     def global_properties(self, kind):
         # (4.6) Override the global_properties method
         return self.the_props
 
 
-# (5) Instantiate recipe with a voltage probe located on "midpoint".
+# (5) Instantiate recipe.
+
+recipe = single_recipe()
 
-recipe = single_recipe(cell, [arbor.cable_probe_membrane_voltage('"midpoint"')])
+# (6) Create simulation. When their defaults are sufficient, context and domain decomposition don't
+# have to be manually specified and the simulation can be created with just the recipe as argument.
+
+sim = arbor.simulation(recipe)
 
 # (7) Create and run simulation and set up 10 kHz (every 0.1 ms) sampling on the probe.
 # The probe is located on cell 0, and is the 0th probe on that cell, thus has probe_id (0, 0).
 
-sim = arbor.simulation(recipe)
 sim.record(arbor.spike_recording.all)
 handle = sim.sample((0, 0), arbor.regular_schedule(0.1))
 sim.run(tfinal=30)
diff --git a/scripts/run_python_examples.sh b/scripts/run_python_examples.sh
index 557bd5e345e39c7585b9265f713f1e93711e83fa..ff26908335b911feca7fa2150e16d78ab0082467 100755
--- a/scripts/run_python_examples.sh
+++ b/scripts/run_python_examples.sh
@@ -15,17 +15,18 @@ $PREFIX python -m pip install -r python/example/example_requirements.txt
 $PREFIX python python/example/brunel.py -n 400 -m 100 -e 20 -p 0.1 -w 1.2 -d 1 -g 0.5 -l 5 -t 100 -s 1 -G 50 -S 123
 # $PREFIX python python/dynamic-catalogue.py # arbor-build-catalog is a test already
 $PREFIX python python/example/gap_junctions.py
-# $PREFIX python python/example/network_ring_mpi_plot.py # requires MPI
-# $PREFIX python python/example/network_ring_mpi.py # requires MPI
-$PREFIX python python/example/network_ring.py
 $PREFIX python python/example/single_cell_cable.py
 $PREFIX python python/example/single_cell_detailed_recipe.py python/example/single_cell_detailed.swc
 $PREFIX python python/example/single_cell_detailed.py python/example/single_cell_detailed.swc
-$PREFIX python python/example/single_cell_extracellular_potentials.py python/example/single_cell_detailed.swc
+$PREFIX python python/example/probe_lfpykit.py python/example/single_cell_detailed.swc
 $PREFIX python python/example/single_cell_model.py
 $PREFIX python python/example/single_cell_nml.py python/example/morph.nml
 $PREFIX python python/example/single_cell_recipe.py
 $PREFIX python python/example/single_cell_stdp.py
 $PREFIX python python/example/single_cell_swc.py python/example/single_cell_detailed.swc
-$PREFIX python python/example/two_cell_gap_junctions.py
+$PREFIX python python/example/network_ring.py
+# $PREFIX python python/example/network_ring_mpi.py # requires MPI
+# $PREFIX python python/example/network_ring_mpi_plot.py # no need to test
+$PREFIX python python/example/network_ring_gpu.py # by default, gpu_id=None
+$PREFIX python python/example/network_two_cells_gap_junctions.py
 $PREFIX python python/example/diffusion.py