Rat bindings

Rat is split into a suite of interdependent C++ libraries which define many classes and items to be used in together to model magnets and compute 3D magnetic simulations.

Rat classes

Each Rat class is usually defined in their own translation units in each repository, for instance rat-common/include/log.hh defines the interface for the rat::cmn::Log class which is a utility to log information to console during calculations. Its implementation is done in rat-common/src/log.cpp.

Most Rat classes use type definitions to implement a factory pattern that returns a shared pointer to the underlying instance of class. This eases memory management for most users by avoiding to manually free memory afterwards.

log.hh example
namespace rat{namespace cmn{
    // shared pointer definition for log
    typedef std::shared_ptr<class Log> ShLogPr;

    // shared pointer definition for no-output log
    typedef std::shared_ptr<class NullLog> ShNullLogPr;

    // logging to the terminal
    class Log{
        // properties
        protected:
            ...

        // methods
        public:
            // logo options
            enum LogoType{RAT,NONE};

            // constructor
            explicit Log(
                LogoType logo = NONE,
                const std::shared_ptr<std::atomic_bool>& cancel_flag = nullptr);

            // factory
            static ShLogPr create(LogoType logo = NONE);

    ...

This factory pattern utilizes the create method to return the corresponding shared pointer to a Log instance. pyRat takes advantage of this workflow by using the shared pointer as the container type for pybind11. That way Python users can interact with Rat C++ libraries through the shared pointer.

Binding Rat classes

pyRat follows the Project-Rat structures by defining translation units for all classes to bind within each separated submodules: common, distmesh, etc…

pyRat also splits:

  • pyrat/include for bindings documentations and docstrings,

  • pyrat/src for bindings implementations and modules.

Header files in pyrat/include are also used for linking implementations when building pyRat.

For instance the pyrat/src/common submodule for binding Rat-Common items has the following structure:

pyrat/src/common
├── common.cpp     ← Submodule binding
├── elements.cpp   ← Elements namespace bindings
├── extra.cpp      ← Extra namespace bindings
├── freecad.cpp    ← FreeCAD class bindings
├── gauss.cpp      ← Gauss class bindings
├── gmshfile.cpp   ← GmshFile class bindings
├── log.cpp        ← Log class bindings
├── ...

Binding a class then looks something like this to begin with:

log.cpp binding example
// Bind
void bind(py::module_ &m) {

    // Rat shorthands
    using rat::cmn::Log;
    using rat::cmn::ShLogPr;

    // Class wrapper for Log
    auto Log_cls = py::class_<Log, ShLogPr>(m, "Log", doc::Log);

    // Constructors for Log
    utils::add_constructors(
        Log_cls,            // class wrapper defined above
        &Log::create,       // rat::mdl::Log original constructor method
        doc::Log_create,    // documentation from pyrat/src/common/log.hh
        py::arg("logo") = rat::cmn::Log::LogoType::NONE) // default agument;

The utils::add_constructors function is a pyRat utility to bind the Log::create constructor method in Python in two ways:

  • using the Log.create() static method

  • using the Log() constructor syntax.

Both ways are identical but the latter enables a more pythonic way of instantiating classes.

The Log class can then be added to the common submodule among other classes:

common.cpp example
// Init
void init(py::module_ &m) {

    // Common module doc
    m.doc() = doc::module_doc;

    // Bind Typecast test class
    Typecast::bind(m);

    // Bind Common module items
    Quadrilateral::bind(m); // Elements

    Node::bind(m);
    Log::bind(m);
    Gauss::bind(m);
    Serializer::bind(m);
    FreeCAD::bind(m);
    Opera::bind(m);
    GmshFile::bind(m);

    Extra::bind(m); // Standalone, might use other items as types
}}}

which can itself be added to the rat core module:

rat.cpp example
// Python core module
PYBIND11_MODULE(rat, m) {

    // Module documentation
    m.doc() = pyrat::doc::module_doc;

    // Module metadata
    ...

    // Common submodule
    #ifdef ENABLE_COMMON
    auto common = m.def_submodule(pyrat::cmn::doc::module_name);
    pyrat::cmn::init(common);
    #endif // ENABLE_COMMON

    ...

Each Rat submodule has its own binding of the same name, here common.cpp, which aggregates all bindings defined in other source files, exposing a common submodule which will is later added to the top pyrat.rat submodule in the main pyrat/src/rat.cpp source file.

For instance the Log class should be accessed in Python with the pyrat.rat.cmn.Log dot syntax for members.

Namespaces

pyRat makes use a C++ namespaces to have clean scopes.

The general structure is as follow:

  • pyrat::<module>::<item>::bind for the main bind function,

  • pyrat::<module>::<item>::doc::<name> for the docstring.

For instance:

  • pyrat::cmn::Log::bind for the main bind function,

  • pyrat::cmn::Log::doc::create for the constructors documentation docstring,

  • pyrat::cmn::Log::doc::msg for the msg method documentation docstring,

and many others.

This clarifies where items come from and generalizes imports and usages.