How to Add a Kernel to moacean_parcels.kernels

In summary, the steps you need to follow to add a kernel to this package are:

  1. Put your kernel function and related particle class code in a module in the MoaceanParcels/moacean_parcels/kernels/ directory. Please see Kernel Modules for details.

  2. Register your kernel function and particle class in the MoaceanParcels/moacean_parcels/kernels/__init__.py file. Please see Kernel Function and Particle Class Registration for details.

  3. Add your kernel function and particle class to the automatic documentation generator in the MoaceanParcels/docs/kernel_functions.rst file. Please see Kernel Function and Particle Class Auto-Documentation for details.

  4. Add a notebook that explain the design of your kernel function and particle class to the MoaceanParcels/docs/kernels/kernel_example_notebooks/particle_behaviour_kernels/ directory and index.html file therein, or the MoaceanParcels/docs/kernels/kernel_example_notebooks/recovery_kernels/ directory and index.html file. Please see Kernel Example Notebooks for details.

The details of what to do and how to do it for each of those steps are provided in the sections below:

Kernel Modules

A kernel module is a .py file that contains code of your kernel function, and an associated particle class (if applicable).

Conventions

  • Use one module per kernel. MoaceanParcels/moacean_parcels/kernels/DeleteParticle.py is an example of a kernel module.

  • The name of the kernel module is the same as the name of the kernel function it contains.

  • OceanParcels style is to write kernel function names and particle class names in camel-case (capitalized words jammed together with no spaces). Examples:

    • AdvectionRK4()

    • AdvectionDiffusionM1()

    • DeleteParticle()

    • ScipyParticle

    • JITParticle

    • VectorParticle

    Note that this is different to the usual Python convention of using snake-case (lower case words separated by underscores) for function and module names and camel-case for class names. However, one of the principle of the Python style guide is the consistency within a project is important. So, we adopt the OceanParcels style in this package.

Kernel Function Signature

Kernel function definition statements must be like:

def KernelName(particle, fieldset, time):

That is, kernel function must accept exactly three arguments. The conventional names of those arguments are article, fieldset, time.

As noted above, the OceanParcels style is to write kernel function names in camel-case (capitalized words jammed together with no spaces).

Particle Class Sub-classing

Particle classes must sub-class either parcels.ScipyParticle:

class VectorParticle(ScipyParticle):

or parcels.JITParticle:

class VectorParticle(JITParticle):

Particle classes that sub-class parcels.JITParticle generally provide faster execution. Those that sub-class parcels.ScipyParticle are easier to debug so they are generally faster to develop. parcels.JITParticle sub-classes have access to a limited set of Python library modules while parcels.ScipyParticle sub-classes are less limited. A good development strategy may be to start with a parcels.ScipyParticle sub-class and then change it to a parcels.JITParticle once it is debugged and tested. Please see the OceanParcels JIT Particles and Scipy particles tutorial for more details.

Variables defined within particle classes provide the way to pass information other than fieldset and time to a kernel function operating on a particular particle.

As noted above, Python and OceanParcels style is to write particle class names in camel-case (capitalized words jammed together with no spaces).

Kernel Function Docstrings

Docstrings are triple-quoted comment blocks that follow immediately after function def statements. The docstring in your kernel function provides the documentation that is rendered in the MOAD Kernel Functions and Particle Classes section of these docs (see Kernel Function and Particle Class Auto-Documentation for details of how that happens).

Your docstring should have for parts:

  1. A description of what the kernel does

  2. A code example of how to use the kernel

  3. A reference to where the example notebook for your kernel is stored

  4. Descriptions and type annotations for the three arguments that the kernel function accepts

Here is an example of a complete kernel function docstring:

"""Delete a particle that has been lost during execution
of the simulation and print its id number as well as information
about where and when it was lost.

This kernel is intended for use as an error recovery kernel,
most likely for the
:py:exc:`parcels.tools.statuscodes.OutOfBoundsError` or
:py:exc:`parcels.tools.statuscodes.ThroughSurfaceError`
error conditions.

Example usage:

.. code-block:: python

    from moacean_parcels.kernels import DeleteParticle

    # ...

    pset.execute(
        kernels,
        # ...
        recovery={ErrorCode.ErrorOutOfBounds: DeleteParticle},
        # ...
    )

For a more detailed usage example,
please see the example notebook for this kernel in the
:ref:`RecoveryKernelExampleNotebooks` section.

:param particle: Particle that has gone out of bounds.
:type particle: :py:class:`parcels.particle.JITParticle` or
                :py:class:`parcels.particle.ScipyInteractionParticle`

:param fieldset: Hydrodynamic fields that is moving the particle.
:type fieldset: :py:class:`parcels.fieldset.FieldSet`

:param time: Current time of the particle.
:type time: :py:attr:`numpy.float64`
"""

Important

Indentation is important in docstrings, the same way it is in Python code.

Breaking that down into the four parts:

  1. The description of what the kernel does is:

    """Delete a particle that has been lost during execution
    of the simulation and print its id number as well as information
    about where and when it was lost.
    
    This kernel is intended for use as an error recovery kernel,
    most likely for the
    :py:exc:`parcels.tools.statuscodes.OutOfBoundsError` or
    :py:exc:`parcels.tools.statuscodes.ThroughSurfaceError`
    error conditions.
    

    This is mostly free text, though recovery kernels should probably make reference to the exceptions that they provide recovery for as shown here.

  2. The code example of how to use the kernel is mostly a Sphinx code-block directive containing the example code. Ellipses in the code block must be marked as comments so that the block is valid Python. In the example above, the code example part is:

    Example usage:
    
    .. code-block:: python
    
       from moacean_parcels.kernels import DeleteParticle
    
       # ...
    
       pset.execute(
           kernels,
           # ...
           recovery={ErrorCode.ErrorOutOfBounds: DeleteParticle},
           # ...
       )
    
  3. The reference to where the example notebook for your kernel is stored can be pretty much verbatim:

    For a more detailed usage example,
    please see the example notebook for this kernel in the
    :ref:`RecoveryKernelExampleNotebooks` section.
    

    For particle behaviour kernels, change the reference label to ParticleBehaviourKernelExampleNotebooks.

  4. You can copy and paste the following for the descriptions and type annotations of the three arguments that the kernel function accepts:

    :param particle: Particle that has gone out of bounds.
    :type particle: :py:class:`parcels.particle.JITParticle` or
                    :py:class:`parcels.particle.ScipyInteractionParticle`
    
    :param fieldset: Hydrodynamic fields that is moving the particle.
    :type fieldset: :py:class:`parcels.fieldset.FieldSet`
    
    :param time: Current time of the particle.
    :type time: :py:attr:`numpy.float64`
    """
    

Particle Class Documentation

Coming soon…

Kernel Function and Particle Class Registration

One of the design goals of this package is to enable kernel functions and particle classes to be imported from it using clean, intuitive import statement like:

from moacean_parcels.kernels import DeleteParticle

To make that possible with the naming convention we have adopted for kernel modules and the functions they contain, it is necessary to “register” kernel functions and particle classes in the MoaceanParcels/moacean_parcels/kernels/__init__.py file. The moacean_parcels.kernels.DeleteParticle() function is registered with the line:

from .DeleteParticle import DeleteParticle

That line is using Python relative import syntax to import the function called DeleteParticle() from the module called moacean_parcels.kernels.DeleteParticle in the MoaceanParcels/moacean_parcels/kernels/ directory. It has the effect of putting the DeleteParticle() function into the moacean_parcels.kernels namespace so that import statement like:

from moacean_parcels.kernels import DeleteParticle

just work.

If you have defined a particle class in your kernel module, it also needs to have a registration line in the MoaceanParcels/moacean_parcels/kernels/__init__.py file.

Kernel Function and Particle Class Auto-Documentation

We use the Sphinx autodoc extension pull the documentation for kernel functions and particle classes from the code docstrings.

Provided that you have followed the instruction in the Kernel Modules section about writing your docstrings, adding the documentation of your code to the MOAD Kernel Functions and Particle Classes section is a simple matter of adding a title and an autofunction directive to the appropriate section of the MoaceanParcels/docs/kernels/kernel_functions.rst file. For example:

:py:func:`DeleteParticle`
-------------------------

.. autofunction:: moacean_parcels.kernels.DeleteParticle

For a particle class:

  • use :py:class: in the title

  • use the autoclass directive

Please ensure the the underline below your title is at least as long as the title. It can be longer, but Sphinx will complain if it is shorter.

If you check the documentation, either by building it locally, or after it has been rendered on readthedocs, and find that your kernel or particle class documentation is missing or incomplete, the likely cause is a reStructuredText syntax error in your docstring. Check the docstrings of other kernel functions or particle classes or reach out for help on the #oceanparcels or #moad-python-notes Slack channels.

Kernel Example Notebooks

We use the nbsphinx extension for Sphinx to enable Jupyter notebooks to be included as pages in this documentation.

It is highly recommended that you create a notebook that explains the purpose and features of your kernels and particle classes, and provides an example of their use. To add your notebook to this documentation:

  1. Store your notebook in one of the sub-directories of MoaceanParcels/docs/kernels/kernel_example_notebooks/:

    • MoaceanParcels/docs/kernels/kernel_example_notebooks/particle_behaviour_kernels/ is for particle behaviour kernels and their associated particle classes

    • MoaceanParcels/docs/kernels/kernel_example_notebooks/recovery_kernels/ is for error recovery kernels

    To make it easy for people to find the example notebook associated with a given kernel module we use the convention of making the name of the notebook file the same as that of the module with -example appended. For example, the example notebook for the MoaceanParcels/moacean_parcels/kernels/DeleteParticle.py recovery kernel is MoaceanParcels/docs/kernels/kernel_example_notebooks/recovery_kernels/DeleteParticle-example.ipynb.

  2. Add the name of your notebook to the toctree section of the index.rst file in the directory where you stored it. Be sure to include the .ipynb extension to signal to Sphinx that it should use nbsphinx to parse the notebook instead of trying to read it as reStructuredText. Example:

    .. toctree::
    :caption: Contents:
    
    DeleteParticle-example.ipynb
    

    The title in the first cell of your notebook will be used as the section title in docs table of contents.