.. _sbpymod:
***********************************************
Python modules with sbldpy (and how to migrate)
***********************************************
The compiled Python modules in dgcode are being moved from ``pycpp_*``
subdirectories (based on `pybind11 `__) to
``pymod_*`` subdirectories, which are based on the much simpler `sbldpy
`__ approach of
simplebuild. Modules based on sbldpy are faster to compile and do not depend on
the version of Python. New code should therefore use ``pymod_*``
subdirectories.
Existing ``pycpp_*`` modules in your own projects will keep working for now, and
the two kinds of modules can be used together (for instance, a geometry from a
``pycpp_*`` module can be used together with a generator from a ``pymod_*``
module in the same simulation). However, support for ``pycpp_*`` modules will
be removed in a future release, so you are encouraged to migrate your code as
described below.
Geometry, generator and filter modules
======================================
Most projects only have compiled Python modules which provide a geometry, a
particle generator or a step filter, like the
:sbpkg:`GeoSkeletonSP`
geometry used in new projects created with ``dgcode_newsimproject``. Such a
module can be migrated with a few simple steps, here shown for a geometry module
``GeoMyStuff`` in a package ``G4GeoMyStuff``:
#. Rename the directory ``G4GeoMyStuff/pycpp_GeoMyStuff/`` to
``G4GeoMyStuff/pymod__GeoMyStuff/`` (note the two underscores, which makes
it a private module named ``_GeoMyStuff``).
#. In the code, include ``G4Interfaces/GeoConstructToPy.hh`` instead of
``G4Interfaces/GeoConstructPyExport.hh``, and replace:
.. code-block:: c++
PYTHON_MODULE( mod )
{
GeoConstructPyExport::exportGeo(mod, "GeoMyStuff");
}
with:
.. code-block:: c++
SBLDPY_MODULE( _GeoMyStuff ) { G4Interfaces::geoToPy( m ); }
#. Add a file ``G4GeoMyStuff/python/GeoMyStuff.py``, providing the Python
module ``G4GeoMyStuff.GeoMyStuff`` with the usual ``create()`` function:
.. code-block:: python
from ._GeoMyStuff import create_handle as _create_handle
from G4Interfaces.ToPy import component_creator as _component_creator
create = _component_creator( _create_handle )
That is all. Python code using the module (e.g. ``geo =
G4GeoMyStuff.GeoMyStuff.create()``, setting parameters like ``geo.thickness_mm =
2.0``, or calling methods like ``geo.dump()``) works as before, and the C++ code
of the geometry class itself is unchanged.
Generators and step filters are migrated in exactly the same way, using
``G4Interfaces/ParticleGenToPy.hh`` and ``G4Interfaces::genToPy`` (instead of
``G4Interfaces/ParticleGenPyExport.hh`` and ``ParticleGenPyExport::exportGen``),
or ``G4Interfaces/StepFilterToPy.hh`` and ``G4Interfaces::filterToPy`` (instead
of ``G4Interfaces/StepFilterPyExport.hh`` and
``StepFilterPyExport::exportFilter``).
If the pybind11 based module added extra methods to the Python objects, like:
.. code-block:: c++
PYTHON_MODULE( mod )
{
GeoConstructPyExport::exportGeo(mod, "GeoMyStuff")
.def("detectorArea",&GeoMyStuff::detectorArea);
}
the same can be achieved with:
.. code-block:: c++
SBLDPY_MODULE( _GeoMyStuff )
{
G4Interfaces::geoToPy( m )
.def( "detectorArea", &GeoMyStuff::detectorArea );
}
The methods must be member functions of the class, taking and returning values
of the types supported by sbldpy (except that any integer type can be used,
and that parameters can be of type float).
The objects created by such modules are instances of the Python class
``G4Interfaces.ToPy.Component``, which provides the same methods for accessing,
tying and exposing parameters as the pybind11 based objects (and they can be
used together with those). One difference is that they are not instances of the
pybind11 based classes, so code which checks the type of these objects (e.g.
with ``isinstance(geo,G4Interfaces.GeoConstructBase)``) must be updated. In
particular, pybind11 based code of your own (in ``pycpp_*`` folders) which
receives such objects from Python must use
``G4Interfaces::componentFromPy(o)`` from
:sbpkg:`G4Interfaces/ComponentFromPy.hh`
(which supports both kinds of objects) rather than e.g.
``py::cast(o)``.
Other modules
=============
Other modules can be migrated by following the `sbldpy documentation
`__. Note in particular the
following differences compared to pybind11:
* Integer parameters must have the type ``std::int64_t``.
* Functions can not have default values of arguments or several overloads. If
needed, put the compiled code in a private module (e.g. ``pymod__impl/``,
providing the module ``_impl``) and add the missing features in Python code
using it (e.g. ``python/mymodule.py``). For examples of this, see
:sbpkg:`GriffFormat.dumpfile` or
:sbpkg:`G4DataCollect`.
* A ``pycpp__init/`` module was actually a module named ``_init``, imported
automatically by the ``__init__.py`` file of the package. A ``pymod__init/``
module directly provides the ``__init__.py`` file of the package, so if you
also have a ``python/__init__.py`` file, use a private module (e.g.
``pymod__impl/``) instead.
* Objects of C++ classes can not be passed between sbldpy modules. For
Geant4 user actions (``G4UserSteppingAction`` and ``G4UserEventAction``)
and hooks (``G4Interfaces::PreGenCallBack``,
``G4Interfaces::PostGenCallBack`` and ``G4Interfaces::HookFct``), which are
typically passed to the launcher (e.g. with
``launcher.setUserSteppingAction(obj)``), register the objects with
``G4Interfaces::registerObject`` from
:sbpkg:`G4Interfaces/CppObjectHandles.hh`,
and wrap the resulting handles in ``G4Interfaces.ToPy.CppObject`` objects
on the Python side. For an example, see
:sbpkg:`G4Tests.CppObjTest`.
* Similarly, :ref:`SimpleHists ` histograms and collections are
shared between modules with handles (see
:sbpkg:`SimpleHists/Handles.hh`). The Python
histograms and collections have a ``handle()`` method, and a C++ function
receiving the handle (as a ``std::int64_t`` parameter) can access the very
same object with ``SimpleHists::getHist(handle)`` or
``SimpleHists::getCollection(handle)``, which return a ``std::shared_ptr``.
Conversely, C++ code can register a histogram or collection with
``SimpleHists::registerHist`` or ``SimpleHists::registerCollection`` and
return the handle, which Python code can turn into a Python object with
``SimpleHists.histFromHandle(handle)`` or
``SimpleHists.HistCollection.fromHandle(handle)``. For an example, see
:sbpkg:`SimpleHistsTests._handletest`.
* The launcher is an sbldpy based module, and Python's global interpreter lock
(GIL) is released while it runs a simulation. Therefore, if C++ code of your
own in ``pycpp_*`` modules calls Python code (or otherwise uses Python
objects) during a simulation, for instance from a particle generator, it
must first acquire the GIL by adding the line ``py::gil_scoped_acquire
gil;`` at the beginning of the functions in question. Python code called
via sbldpy callbacks (like launcher hooks) does not need this.