Writing “plugins” with C++#
New functionality may be added through new Python code and/or new C++ code.
Further, you may use the existing C++ types in Python extensions depending on
fwdpy11
. For example, you could write a custom “evolve” function for
non-Wright-Fisher models. Or, you could write custom genetic value objects.
There are several examples of custom genetic value objects in the unit tests.
Finding the headers#
You can find the location of the installed header files using Python:
import fwdpy11
print(fwdpy11.get_includes())
print(fwdpy11.get_fwdpp_includes())
/home/runner/work/fwdpy11/fwdpy11/fwdpy11/headers
/home/runner/work/fwdpy11/fwdpy11/fwdpy11/headers/fwdpp
The above is useful for generating a functioning setup.py
file. Note that you will have
to join the output with the proper compiler flags indicating include paths (typically -I
).
If using a Makefile
, it is handy to get the above info via the shell, which is done as follows:
python3 -m fwdpy11 --includes
The above command prepends the paths with -I
. If that is not desired,
you may get the paths separately for fwdpp
and fwdpy11
:
python3 -m fwdpy11 --fwdpp_headers
python3 -m fwdpy11 --fwdpy11_headers
Building with cmake
#
The above two commands are useful when using tools like cmake
to configure build systems. Here is an example
from one of the examples that comes with fwdpy11
:
cmake_minimum_required(VERSION 2.8.12)
project(gavlue_recorder)
# As of 0.8.0, fwdpy11
# is compiled with the C++14 language
# standard (-std=c++14)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD 14)
find_package(pybind11)
message(STATUS "Found pybind11: ${pybind11_VERSION}")
if(${pybind11_VERSION} VERSION_LESS '2.4.3')
message(FATAL_ERROR "pybind11 version must be >= '2.4.3'")
endif()
execute_process(COMMAND python3 -m fwdpy11 --fwdpy11_headers OUTPUT_VARIABLE FP11HEADERS)
execute_process(COMMAND python3 -m fwdpy11 --fwdpp_headers OUTPUT_VARIABLE FWDPPHEADERS)
find_package(GSL REQUIRED)
include_directories(BEFORE ${FP11HEADERS} ${FWDPPHEADERS})
message(STATUS "GSL headers in ${GSL_INCLUDE_DIRS}")
include_directories(BEFORE ${GSL_INCLUDE_DIRS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
pybind11_add_module(gvalue_recorder MODULE gvalue_recorder.cc)
target_link_libraries(gvalue_recorder PRIVATE GSL::gsl GSL::gslcblas)
Mako headers for cppimport#
Extensions using cppimport require mako
headers to guide compilation. You make get a minimal header via the shell:
python3 -m fwdpy11 --mako
Dealing with GSL
errors in custom C++ code#
The GSL
uses C-like error handling. Here, this means that there is a global error handling function
that will print the error to screen and then abort the running process. The behavior of abort-on-error is not
acceptable here, as the Python session itself will abort!
When fwdpy11
is imported, the default GSL
behavior changes. Instead of aborting, a RuntimeError
will be raised.
This exception will contain the entire string of text from the GSL
error message.
However, if you write and C++ code using the GSL
, you may wish to handle errors locally and maybe return None
or throw
an exception with your own message in it. To do so, you must do the following in your C++ code:
Temporarily disable
GSL
error handling.Restore the
fwdpy11
error handler at all possible exit paths from your code.
On paper, one could do all of the above using the C API found in the GSL
. However, fwdpy11
provides a class that
provides an idiomatic C++ approach to managing the error handler. The C++ class fwdpy11::gsl_error_handler_wrapper
,
defined in <fwdpy11/gsl/gsl_error_handler_wrapper.hpp>
provide a “smart pointer”-like wrapper around the error
handler. The constructor disables the error handler, storing the value of the disabled handler. The destructor restores
the handler.
To see this in action, check out the unit test file tests/test_GSLerror.py
and its associated C++ file
tests/gsl_error.cc
.