PyChaste Developer Guide
On this page
This is a developer guide on generating Python wrappers for cell-based Chaste and fixing common problems related to wrappers.
Running tests
To get started, see the install guide for detailed instructions on building PyChaste from source.
From the build directory run all PyChaste tests with:
ctest -L pychasteIf working in a docker container or other headless environment, run the above with xvfb-run for off-screen rendering:
xvfb-run --server-args="-screen 0 1024x768x24" ctest -L pychasteTo check for C++ classes that are missing Python wrappers, run:
ctest -V -R TestPyWrapperChanges$This will flag any classes in cell-based Chaste that need wrappers. For example:
Unknown class NewCellCycleModel from /Chaste/cell_based/NewCellCycleModel.hpp
Wrapping classes
During cmake configuration, Python wrappers are automatically generated from the settings in Chaste/pychaste/dynamic/config.yaml. The generated wrappers are stored under the build directory in build/pychaste/wrappers.
Wrapping a new class
To add a new class, put a new entry with the name of the class in the config.yaml settings. For example, to create Python wrappers for a NewCellCycleModel class, put in the following entry under classes:
- name: NewCellCycleModelNext, add an import for the class (or its template instantiations) in the appropriate __init__.py under pychaste/src/py/chaste. For example, if it is templated on spatial dimensions and belongs in cell_based, add the following to the imports list in pychaste/src/py/chaste/cell_based/__init__.py:
from ... import (
...
NewCellCycleModel_2
NewCellCycleModel_3
...
)If the class is templated, add a template syntax entry for it further down in the same __init__.py. For example:
NewCellCycleModel = TemplateClassDict(
{
("2",): NewCellCycleModel_2,
("3",): NewCellCycleModel_3,
}
)This enables using the class via the more convenient ccm = NewCellCycleModel[2] notation.
If cmake configuration has already been run, re-generate the wrappers with:
make pychaste_wrappersThe new wrappers will now be stored under build/pychaste/wrappers as NewCellCycleModel_[1|2].cppwg.[c|h]pp.
Adding template instantiations
The configuration in config.yaml has defaults for templated classes that cover common
instantiations such as Foo<2>, Foo<3> or Foo<2,2> Foo<3,3>. If the class being
added has additional template instantiations beyond these common cases, extra configuration
should be added to cover its instantiations in config.yaml.
For example, if the new class Foo has the template signature
<unsigned ELEMENT_DIM, unsigned SPACE_DIM=ELEMENT_DIM> and the following instantiations:
template class Foo<1,1>;
template class Foo<1,2>;
template class Foo<2,2>;
template class Foo<1,3>;
template class Foo<2,3>;
template class Foo<3,3>;then it can be added to wrappers with the following configuration:
- name: Foo
template_substitutions:
- signature: <unsigned ELEMENT_DIM, unsigned SPACE_DIM=ELEMENT_DIM>
replacement: [[1, 1], [1, 2], [2, 2], [1, 3], [2, 3], [3, 3]]After making the configuration changes, re-generate the wrappers with make pychaste_wrappers, then
re-run cmake configuration to add the newly generated files to the compilation set.
See structure of config.yaml for a full list of configuration options.
Excluding a class from wrapping
To exclude a class from wrapping, add an excluded flag to the entry for the class e.g.
- name: NewCellCycleModel
excluded: TrueThis will ensure that wrappers are not created for the class. It will also prevent tests from flagging it as a class that is missing Python wrappers.
Removing or renaming an existing class
If a class has been removed or renamed in the main codebase, it should be removed or renamed in config.yaml as well. Otherwise, errors will arise during cmake configuration such as:
fatal error: 'NewCellCycleModel.hpp' file not found
All other references to the removed/renamed class and its instantiations should be removed from the code as well.
Troubleshooting Problems
Reproducing PyChaste CI Errors
Start a docker container
docker run -it --init --rm ubuntu:nobleInstall system dependencies
apt-get update
apt-get install -y castxml clang cmake pkg-config libx11-dev xvfb libosmesa6-dev libpthread-stubs0-dev wget git build-essentialInstall conda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.shStart a new shell to activate conda
bashClone Chaste
git clone https://github.com/Chaste/Chaste.gitCreate a conda environment
conda env create -n py311 -f Chaste/pychaste/src/py/conda/envs/env_python3.11.yaml
conda activate py311Configure and build PyChaste
cd Chaste
mkdir build
cd build
cmake \
-DChaste_ENABLE_PYCHASTE=ON \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DCMAKE_INSTALL_PREFIX="${CONDA_PREFIX}" \
-DCMAKE_PREFIX_PATH="${CONDA_PREFIX}" \
-DCMAKE_LIBRARY_PATH="${CONDA_PREFIX}/lib" \
-DBOOST_ROOT="${CONDA_PREFIX}" \
-DHDF5_C_COMPILER_EXECUTABLE="${CONDA_PREFIX}/bin/h5pcc" \
-DPETSC_DIR="${CONDA_PREFIX}" \
-DPYTHON_EXECUTABLE="$(which python)" \
-DVTK_DIR="${CONDA_PREFIX}" \
-DXERCESC_INCLUDE="${CONDA_PREFIX}/include" \
-DXERCESC_LIBRARY="${CONDA_PREFIX}/lib/libxerces-c.so" \
-DXSD_EXECUTABLE="${CONDA_PREFIX}/bin/xsd" \
..
make -j4 pychasteInstall the built package
python3 -m pip install -v pychaste/packageRun PyChaste tests
xvfb-run --server-args="-screen 0 1024x768x24" ctest -j $(nproc) -L pychaste --output-on-failurePETSc-related Errors
Error:
/usr/include/c++/11/type_traits:
In instantiation of ‘struct std::is_base_of<pybind11::detail::pyobject_tag, _p_Vec>’:
/.../Chaste/build/_deps/pybind11-src/include/pybind11/cast.h:2098:68:
recursively required by substitution of
‘template<class T> class pybind11::detail::type_caster<T, typename std::enable_if<
std::is_base_of<
pybind11::detail::pyobject_tag, typename std::remove_reference<_Tp>::type>::value, void>::type>
[with T = _p_Vec]’
Solution:
In config.yaml, add the following to Foo
- name: Foo
source_includes:
- PybindPetscTypeCaster.hppUBLAS-related Errors
Error:
TypeError: Unregistered type : boost::numeric::ublas::c_vector<double, 3ul>
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
TypeError: Unable to convert function return value to a Python type! The signature was
(self: chaste._pychaste_lib.Foo_3) -> boost::numeric::ublas::c_vector<double, 3ul>
Failed
Solution:
In config.yaml, add the following to Foo
- name: Foo
source_includes:
- PybindUblasTypeCaster.hppVTK-related Errors
Error:
TypeError: Unregistered type : vtkSmartPointer<vtkRenderer>
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
...
TypeError: Unable to convert function return value to a Python type! The signature was
(self: chaste._pychaste_lib.Foo) -> vtkSmartPointer<vtkRenderer>
Failed
Solution:
Ensure that your system VTK version matches the version installed in Python.
In config.yaml, add the following to Foo
- name: Foo
source_includes:
- PybindVTKTypeCaster.hppOther Errors
Error:
pygccxml.declarations.runtime_errors.declaration_not_found_t: Unable to find declaration.
Matcher: [(decl type==class_t) and (name==Foo)]
Solution:
Check if the template args of class Foo are in the template_substitutions list of config.yaml and add it if missing.
For example, if Foo is defined as <unsigned DIMENSIONS> class Foo {...}, and Foo is explicitly instantiated for DIMENSIONS=2 and DIMENSIONS=3:
template_substitutions:
- signature: <unsigned FOO_DIM>
replacement: [[2], [3]]Error
import chasteImportError: /.../lib.so: undefined symbol: _ZN11Foo7BarEv
Solution:
Foo::Bar has been declared but not implemented. If Foo::Bar is not implemented, it can be excluded from wrapping by modifying config.yaml:
- name: Foo
excluded_methods:
- BarError
undefined symbol: _Z62register_Foo_1_1_classRN8pybind117module_E
Solution
A new wrapper has been created for Foo<1,1> but this hasn’t been added to the set of source
files included in the compile. This can be resolved by re-running cmake configuration so that
it discovers the newly generated wrapper files.
Error
ImportError: cannot import name 'Foo_1_1' from 'chaste._pychaste_all'
Solution
- Check if
Foo_1_1is defined in__init__.py. See wrapping a new class for details. - Check if wrappers have been generated for
Foo_1_1inbuild/pychaste/wrappers/all. If not, check if all template instantiations ofFoohave been included inconfig.yaml. See adding template instantiations for details.
Structure of config.yaml
The example below shows a list of the configuration keys available and explains the role that each plays in generating wrappers.
# Package name: prepended to all modules.
name: pychaste
# Smart pointer type for PYBIND11_DECLARE_HOLDER_TYPE in all wrappers.
smart_ptr_type: std::shared_ptr
# Default value of pybind11::return_value_policy for pointers.
pointer_call_policy: reference
# Default value of pybind11::return_value_policy for references.
reference_call_policy: reference_internal
# Set False to not include the common include file (all headers) in all wrappers.
common_include_file: True
# Headers to include in all wrappers.
source_includes:
- <memory>
# Exclude default arguments from wrapped methods.
exclude_default_args: False
# Signature/replacement settings for explicit template instantiations.
template_substitutions:
- signature: <unsigned DIM>
replacement: [[2], [3]]
- signature: <unsigned ELEMENT_DIM, unsigned SPACE_DIM=ELEMENT_DIM>
replacement: [[2, 2], [3, 3]]
modules:
# Module name
- name: all
# List of source directories for this module relative to the source root.
# Restrict to headers from these directories. Blank means unrestricted.
source_locations:
# List of free functions to wrap. Blank means none, CPPWG_ALL means discover all.
free_functions: CPPWG_ALL
# List of classes to wrap. Blank means none, CPPWG_ALL means discover all.
classes:
# Name of class.
- name: RelativeTo
# Name of class source file. Not required if class name matches file name.
source_file: FileFinder.hpp
# Optional path to the class source file, relative to the source root.
source_file_path: global/src/FileFinder.hpp
# Extra includes to add in the wrapper
source_includes:
- <map>
# List of methods that should not be wrapped.
excluded_methods:
- ExcludedMethod
# Exclude any methods that have these arg types.
arg_type_excludes:
- double
# Exclude any constructors that have these arg types.
constructor_arg_type_excludes:
- double
# Exclude any constructors that have these signatures.
constructor_signature_excludes:
- [int, int, int]
# Path to a custom script for generating wrappers for this class.
# The custom generator must extend cppwg.templates.custom.Custom.
# CPPWG_SOURCEROOT can be used in the path for the source root directory.
custom_generator: "CPPWG_SOURCEROOT/pychaste/dynamic/templates/RelativeToCustomTemplate.py"
# Custom C++ code to place at the top of the class wrapper.
prefix_code:
# Custom C++ code to place at the bottom of the class wrapper.
suffix_code:
- name: CellwiseOdeSystemInformation
excluded: True # Exclude this class from wrapping.
# Text to add at the top of all wrappers
prefix_text: |
// This file is auto-generated; manual changes will be overwritten.