Pybind11¶
Method injections¶
Sometimes, we want to write methods in python, and add them to a pybind11-wrapped
C++ class. This can be done with the class decorator ksgpu.inject_methods:
# Assume MyClass is a C++ class bound via Pybind11
from ../ import MyClass
@ksgpu.inject_methods(MyClass)
class MyClassInjections:
# 1. Injecting a standard method
def nice_print(self):
print(f"Value: {self.get_value()}")
# 2. Injecting/Overriding the Constructor (__init__)
# We save the original C++ init so we can call it.
_cpp_init = MyClass.__init__
def __init__(self, value, label="Default"):
# Call the C++ constructor
self._cpp_init(value)
# Add pure-Python attributes
# (Requires py::dynamic_attr() in C++)
self.label = label
Source file organization¶
Pybind11 code is in the following source files:
src_pybind11/pirate_pybind11_casm.cpp
src_pybind11/pirate_pybind11_core.cpp
src_pybind11/pirate_pybind11_kernels.cpp
src_pybind11/pirate_pybind11_loose_ends.cpp
src_pybind11/pirate_pybind11.cpp # toplevel
These get compiled into a single extension module pirate_pybind11.so.
On the python side, the pirate_frb toplevel package is divided into subpackages pirate_frb.casm, pirate_frb.core, etc.
Each subpackage imports the contents of the corresponding pybind11 file.
For example, pirate_frb/casm/__init__.py contains the line:
# Note: CasmBeamformer is the only class in src_pybind11/pirate_pybind11_casm.cu.
from ..pirate_pybind11 import CasmBeamformer
Note that each pybind11 class will appear with two names – for example pirate_frb.pirate_pybind11.CasmBeamformer
is the same as pirate_frb.casm.CasmBeamformer. In python code, always use the latter “non-pybind11” name if possible.
If you are asked to python-bind a new class, please make sure that it is also imported into a python subpackage,
and documented (with autoclass) in the sphinx docs.
General notes¶
Please write docstrings in the pybind11 code, but keep them concise and avoid superficial comments. If the meaning of a member/method is self-evident, then don’t write a docstring.
When writing docstrings, comments in the larger C++ codebase may be useful. For example, when writing python bindings for
class pirate::BumpAllocator, comments in the source filesinclude/pirate/BumpAllocator.hppandsrc_lib/BumpAllocator.cppmay be useful for writing docstrings.If it’s technically challenging (or awkward) to python-bind a C++ class member/method, or if the member/method seems unlikely to be useful from python, then skip it. Please list in the chat all “skipped” members/methods.
Put all method injections in
pirate_frb/pybind11_injections.py.If a class has method injections, then add a C++ comment to the pybind11 code with a concise description of the injections.
Don’t use lambda-functions in cases where a named function (or constructor) would be equivalent.
Some C++ classes have protected constructors and a public static method
shared_ptr<X> X::create(...). In such cases, the python syntax should bex = X(...), notx = X.create(...).
Specific argument types¶
The rules below usually require method injections to implement. They apply to both constructors and non-constructor methods.
If a C++ function takes an
aflagsargument (fromksgpu/mem_utils.hpp), then callksgpu.parse_aflags(aflags)before passing it to the C++ function.If a C++ function takes a
ksgpu::Dtypeargument, then call theksgpu.Dtypeconstructor onxbefore passing it to the C++ function.If a C++ function returns a bare pointer or
shared_ptr<void>, then don’t python-wrap it unless specifically requested.If a C++ member has type
dim3, then don’t python-wrap it unless specifically requested.If a C++ function has an argument of type
ostream &,YAML::Emitter &, orYamlFile &, then don’t python-wrap it unless specifically requested.C++ atomics must be converted to non-atomic types before converting to python.
If a C++ function has a
cudaStream_targument, it should appear in python asstream=None, wherestreamis acupy.cuda.stream, and the default is the current cupy stream.If a C++ function returns a
ksgpu::CudaStreamWrapper, then the pybind11 binding will return typeksgpu_pybind11._CudaStreamWrapperBase. It should appear to a python caller as returning typeksgpu.CudaStreamWrapperinstead. Define a python wrapper which does the conversion. (This is a one-liner: theksgpu.CudaStreamWrapperconstructor takes a_CudaStreamWrapperBaseargument. Seeksgpu/ksgpu/CudaStreamWrapper.pyfor more context.)