Writing Examples/Tutorials

CARP example simulations are created using the carputils framework in a standardised way that seeks to promote conciseness and familiarity to developers. Writers of new examples are therefore advised to first look over existing examples. Look in the directory hierarchy of the devtests or benchmarks repositories - examples are the python source files conventionally named run.py in directories whose name describes the main purpose of the example.

Note

For a complete working example simulation, please look at the ‘simple bidomain’ example in the devtests repository (devtests/bidomain/simple), which can be copied and adapted to create new examples.

The carputils framework can be used by any python script on a machine where it is installed - scripts do not need to be placed inside the devtests or benchmarks repositories. The reasons to place them there is to share them conveniently with others and for inclusion in the automatic testing system carptests.

Anatomy of an Example

A CARP run script will contain at a minimum:

from carputils import tools

@tools.carpexample()
def run(args, job):
    # Generate CARP command line and run example
    pass # Placeholder

if __name__ == '__main__':
    run()

The script defines a run function which takes as its arguments a namespace object containing the command line parameters (generated by the standard library argparse module) and a Job object. The run function should use the carputils.tools.carpexample() function decorator, as shown, which generates the above arguments and completes some important pre- and post-processing steps.

Many of the tasks of run scripts are common between examples, for example for setting up the correct CARP command line options for a particular solver. carputils therefore performs many of these common tasks automatically or by providing convenience functions. Below is a more complete example:

"""
Description of the example.
"""

EXAMPLE_DESCRIPTIVE_NAME = 'Example of Feature X'
EXAMPLE_AUTHOR = 'Joe Bloggs <joe.bloggs@example.com>'

from datetime import date
from carputils import tools

def parser():
    # Generate the standard command line parser
    parser = tools.standard_parser()
    group  = parser.add_argument_group('experiment specific options')
    # Add an extra argument
    group.add_argument('--experiment',
                        default='bidomain',
                        choices=['bidomain', 'monodomain'])
    return parser

def jobID(args):
    today = date.today()
    ID = '{}_{}_{}_np{}'.format(today.isoformat(), args.experiment,
                                args.flv, args.np)
    return ID

@tools.carpexample(parser, jobID)
def run(args, job):

    # Generate general command line
    cmd = tools.carp_cmd('example.par')

    # Set output directory
    cmd += ['-simID', job.ID]

    # Add some example-specific command line options
    cmd += ['-bidomain', int(args.experiment == 'bidomain'),
            '-meshname', '/path/to/mesh']

    # Run example
    job.carp(cmd)

if __name__ == '__main__':
    run()

Example Breakdown

Below the various parts of the above example are explained.

carputils.tools.standard_parser() returns an ArgumentParser object from the argparse module of the python standard library. You can then add your own example-specific options in the manner shown. See the argparse documentation for information on how to add different types of command line option. To make it easier to identify experiment specific options, an argument group is created and arguments added directly to it. This is optional but increases clarity.

def parser():
    # Generate the standard command line parser
    parser = tools.standard_parser()
    group  = parser.add_argument_group('experiment specific options')
    # Add an extra argument
    group.add_argument('--experiment',
                        default='bidomain',
                        choices=['bidomain', 'monodomain'])
    return parser

The jobID function generates an ID for the example when run. The ID is used to name both the example output directory and the submitted job on batch systems. The function takes the namespace object returned by the parser as an argument and should return a string, ideally containing a timestamp as shown:

def jobID(args):
    today = date.today()
    ID = '{}_{}_{}_np{}'.format(today.isoformat(), args.experiment,
                                args.flv, args.np)
    return ID

The carputils.tools.carpexample() decorator takes the above parser and jobID functions as arguments. It takes care of some important pre- and post-processing steps and ensures the correct arguments are passed to the run function at runtime:

@tools.carpexample(parser, jobID)
def run(args, job):

carputils.tools.carp_cmd() generates the initial command line for the CARP executable, including the correct solver options for the current run configuration the standard command line parser generated above. If you are using a CARP parameter file, pass the name as an optional argument to carp_cmd(), otherwise just call with no argument:

# Generate general command line
cmd = tools.carp_cmd('example.par')

The CARP command must always have a -simID argument set. When your example runs CARP only once, this will be the ID attribute of the job object passed to run, otherwise it should be a subdirectory:

# Set output directory
cmd += ['-simID', job.ID]

Your example should then add any additional command line arguments to the command list:

# Add some example-specific command line options
cmd += ['-bidomain', int(args.experiment == 'bidomain'),
        '-meshname', '/path/to/mesh']

Finally, use the carp method of the job object to execute the simulation. The reason for this, rather than executing the command directly, is that the job object takes care of additional preprocessing steps and automatically generates batch scripts on HPC systems. To run shell commands other than CARP, see the carputils.job.Job documentation.

# Run example
job.carp(cmd)

At the very end of your run script, add the below statements. This executes the run function when the file is executed directly:

if __name__ == '__main__':
    run()

Documenting Examples

Examples should be well commented, but should also start with a descriptive python docstring to serve as a starting point for new users. This should include:

  • A description of the feature(s) the example tests.
  • Relevant information about the problem setup (mesh, boundary conditions, etc.)
  • The meaning of the command line arguments defined by the example, where this is not obvious.

The docstring must be at the top of the file, before any module imports but after the script interpreter line, and is essentially a comment block started and ended by triple quotes """:

#!/usr/bin/env python

"""
An example testing the ...
"""

EXAMPLE_DESCRIPTIVE_NAME = 'Example of Feature X'
EXAMPLE_AUTHOR = 'Joe Bloggs <joe.bloggs@example.com>'

The docstring should be followed by two variables as above. EXAMPLE_DESCRIPTIVE_NAME should be a short descriptive name of the test to be used as a title in the documentation, e.g. ‘Activation Time Computation’ or ‘Mechanical Unloading’. EXAMPLE_AUTHOR should be the name and email address of the primary author(s) of the test in the format seen above. Multiple authors are specified like:

EXAMPLE_AUTHOR = ('Joe Bloggs <joe.bloggs@example.com>,'
                  'Jane Bloggs <jane.bloggs@example.com> and'
                  'Max Mustermann <max.mustermann@beispiel.at>')

Formatting

The docstring is used to build the example documentation at https://carpentry.medunigraz.at/carputils/examples/, and can include reStructuredText formatting. Some key features are summarised below, but for a more comprehensive summary of the format please see the Sphinx webstie. It is important to bear in mind that indentation and blank lines have significance in rst format.

Headings are defined by underlining them with =, -, +, ~, with the order in which they are used indicating the nesting level:
Heading 1
=========

Heading 1.1
-----------

Heading 1.1.1
+++++++++++++

Heading 1.2
-----------
Python code highlighting is done by ending a paragraph with a double colon :: followed by an indented block:
Here is an example of a run function::

    def run(argv, outdir=None):
        for list in argv:
            print argv
Highlighting of other languages is done with a code-block directive:
.. code-block:: bash

    echo "some message" > file.txt
LaTeX-formatted maths may be inserted with the math environment (info):
.. math::

    (a + b)^2 = a^2 + 2ab + b^2

    (a - b)^2 = a^2 - 2ab + b^2
or inline:
I really like the equation :math:`a^2 + b^2 = c^2`.
Images may be included, but must be stored in the doc/images directory of carputils. The path used in the image directive must then begin with /images to work correctly.
.. image:: /images/ring_rigid_bc.png