3.5 Contributing code
3.5.1 Structure of the package
The functionality of the pde
package is split into multiple sub-package.
The domain, together with its symmetries, periodicities, and discretizations, is
described by classes defined in grids
.
Discretized fields are represented by classes in fields
, which have
methods for differential operators with various boundary conditions collected
in boundaries
.
The actual pdes are collected in pdes
and the respective solvers
are defined in solvers
.
3.5.2 Extending functionality
All code is build on a modular basis, making it easy to introduce new classes
that integrate with the rest of the package. For instance, it is simple to
define a new partial differential equation by subclassing
PDEBase
.
Alternatively, PDEs can be defined by specifying their evolution rates using
mathematical expressions by creating instances of the class
PDE
.
Moreover, new grids can be introduced by subclassing
GridBase
.
It is also possible to only use parts of the package, e.g., the discretized
differential operators from operators
.
New operators can be associated with grids by registering them using
register_operator()
.
For instance, to create a new operator for the cylindrical grid one needs to
define a factory function that creates the operator. This factory function takes
an instance of Boundaries
as an argument and
returns a function that takes as an argument the actual data array for the grid.
Note that the grid itself is an attribute of
Boundaries
.
This operator would be registered with the grid by calling
CylindricalSymGrid.register_operator("operator", make_operator)
, where the
first argument is the name of the operator and the second argument is the
factory function.
3.5.3 Design choices
The data layout of field classes (subclasses of
FieldBase
) was chosen to allow for a simple
decomposition of different fields and tensor components. Consequently, the data
is laid out in memory such that spatial indices are last. For instance, the data
of a vector field field
defined on a 2d Cartesian grid will have three
dimensions and can be accessed as field.data[vector_component, x, y]
,
where vector_component
is either 0 or 1.
3.5.4 Coding style
The coding style is enforced using isort
and black. Moreover, we use Google Style docstrings,
which might be best learned by example.
The documentation, including the docstrings, are written using reStructuredText, with examples in the
following cheatsheet.
To ensure the integrity of the code, we also try to provide many test functions,
contained in the separate sub-folder tests
.
These tests can be ran using scripts in the scripts
subfolder in the root
folder.
This folder also contain a script tests_types.sh
, which uses mypy
to check the consistency of the python type annotations.
We use these type annotations for additional documentation and they have also
already been useful for finding some bugs.
We also have some conventions that should make the package more consistent and
thus easier to use. For instance, we try to use properties
instead of getter
and setter methods as often as possible.
Because we use a lot of numba
just-in-time compilation to speed up computations,
we need to pass around (compiled) functions regularly. The names of the methods
and functions that make such functions, i.e. that return callables, should start
with ‘make_*’ where the wildcard should describe the purpose of the function
being created.
3.5.5 Running unit tests
The pde
package contains several unit tests, collection in the tests
folder in the project root. These tests ensure that basic functions work as expected,
in particular when code is changed in future versions. To run all tests, there are a
few convenience scripts in the root directory scripts
. The most basic script is
tests_run.sh
, which uses pytest
to run the tests. Clearly, the python
package pytest
needs to be installed. There are also additional scripts that for
instance run tests in parallel (needs the python package pytest-xdist
installed),
measure test coverage (needs package pytest-cov
installed), and make simple
performance measurements. Moreover, there is a script test_types.sh
, which uses
mypy
to check the consistency of the python type annotations and there is a
script format_code.sh
, which formats the code automatically to adhere to our style.
Before committing a change to the code repository, it is good practice to run the tests, check the type annotations, and the coding style with the scripts described above.