Developer Environment#

The tudat-bundle build configuration allows developers to simultaneously work on tudat and tudatpy for a better flow in your end to end development.

Note

This topic is relevant for:

  • developers who want to expose their updated tudat code to the upcoming tudatpy package release.

  • users who like to extend tudatpy functionality locally via modification of the C++-based tudat source code.

  • anybody interested in seeing a concurrent C++ / Python development workflow.

Learning Objectives

  1. Get your own tudat-bundle environment from the tudat-team.

  2. Understand the structure of the tudat-bundle and the purpose of its components.

  3. Familiarize with the mapping between tudat and tudatpy source code.

  4. Understand the higher level functions of the tudat-api.

  5. Familiarize with the available build configurations for tudat and tudatpy.

  6. Know how to build the tudat-bundle and recognize some common problems that can be encountered.

Cloning tudat-bundle#

The tudat-bundle environment is available on the tudat-team GitHub repository.

Note

Detailed instructions for the download, setup and verification of your own tudat-bundle can be found in the repository’s README (steps 1-4).

Warning

If your machine is running on an Apple M1 processor, you may have to follow a slightly different procedure. Please refer to this discussion.

Introduction to tudat-bundle#

The tudat-bundle consists of three subdirectories:

  • tudat, containing the tudat C++ source code.

  • tudatpy, containing the tudatpy/kernel directory in which the exposure of C++ source code to the tudatpy package is facilitated.

  • <build>, the build directory containing the compiled C++ tudat code (<build>/tudat), as well as the compiled tudatpy package at <build>/tudatpy/tudatpy/kernel.so.

The entirety of exposed C++ functionality in tudatpy is contained within the tudatpy/kernel source directory.

For reference during this guide, the architecture of this directory is as follows:

Note

This module / submodule tree structure always aspires to mimic the structure of the tudat/src directory.

schematic tudatpy/kernel directory#
*  kernel
   *  ├── expose_<module_A>.cpp
   *  ├── expose_<module_A>.h
   *  ├── expose_<module_A>

        *  ├── expose_<submodule_A1>.cpp
        *  ├── expose_<submodule_A1>.h
        *  ├── expose_<submodule_A2>.cpp
        *  ├── expose_<submodule_A2>.h
        *  ├──          ...

   *  ├── expose_<module_B>.cpp
   *  ├── expose_<module_B>.h
   *  ├──          ...

   *  └── kernel.cpp

Note

The terms Package/Module/Submodule are intended to be hierarchical descriptions, used mostly in the context of directory structure. In the Python interpreter, everything is treated as a module object.

The tudatpy Package#

The tudatpy package is a collection of modules, in which the C++-based tudat source code is exposed into Python bindings.

Note

The interfaces of C++-based tudat source code and the Python-based tudatpy modules are managed by the Pybind11 library. The rules for defining C++ to Python interfaces using Pybind11 will be presented in detail under Exposing C++ in Python.

In kernel.cpp (see schematic tudatpy/kernel directory) tudatpy modules are bundled into the tudatpy package. The following folded code shows the core elements of kernel.cpp. It would serve the reader to have a glance through before we walk through the elements in detail.

tudatpy/kernel/kernel.cpp#
// expose tudat versioning
#include <tudat/config.hpp>

// include all exposition headers
#include "expose_simulation.h"
// other submodule headers...

// standard pybind11 usage
#include <pybind11/pybind11.h>
namespace py = pybind11;

PYBIND11_MODULE(kernel, m) {

    // Disable automatic function signatures in the docs.
    // NOTE: the 'options' object needs to stay alive
    // throughout the whole definition of the module.
    py::options options;
    options.disable_function_signatures();
    options.enable_user_defined_docstrings();

    // export the tudat version.
    m.attr("_tudat_version_major") = TUDAT_VERSION_MAJOR;
    m.attr("_tudat_version_minor") = TUDAT_VERSION_MINOR;
    m.attr("_tudat_version_patch") = TUDAT_VERSION_PATCH;

    // simulation module definition
    auto simulation = m.def_submodule("simulation");
    tudatpy::expose_simulation(simulation);

    // other submodule definitions...

    // versioning of kernel module
    #ifdef VERSION_INFO
      m.attr("__version__") = VERSION_INFO;
    #else
      m.attr("__version__") = "dev";
    #endif
}

Starting with the end in mind, compiling the previous will create a shared library named kernel.so, making available all modules included in kernel.cpp. With the kernel.so library added to the Python path variable, users can then import tudatpy modules such as the astro module, by executing from kernel import astro.

Warning

The Python interpreter searches the sys.path in its order. Inspect the sys.path list to verify that the desired variant of a module is imported.

All tudatpy modules included in the kernel namespace have previously defined in their respective expose_<module_A>.cpp (and expose_<module_A>.h) files.

Module Definition#

Note

A tudatpy module can be thought of as collection of tudat source code, which has been exposed to python.

Modules are defined by their respective exposition functions expose_<module_X>( ). These exposition functions fulfill one of two (or sometimes both) tasks:

  1. directly expose tudat source code in the module namespace (see <module_B> in schematic tudatpy/kernel directory)

  2. include selected submodules, where tudat source code has been exposed in nested namespaces (see <module_A> in schematic tudatpy/kernel directory)

1. Source Code Exposition in Module Namespace#

Exposition functions may directly expose tudat source code content (module classes, functions and attributes) from the respective tudat namespace to the tudatpy module namespace. In this case, the C++ to python interfaces are defined directly in the tudatpy module namespace. One example of this usage is the tudatpy constants module. Consider below the definition of the module constants:

tudatpy/kernel/expose_constants.cpp#
// include .h
#include "expose_constants.h"

// include .h of considered source content
#include "tudatpy/docstrings.h"
#include "tudat/constants.h"
#include "tudat/astro/basic_astro/timeConversions.h"

// pybind11 usage
#include <pybind11/complex.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;

// aliasing namespaces of considered source content
namespace tbc = tudat::celestial_body_constants;
namespace tpc = tudat::physical_constants;
// ...

// namespace package level
namespace tudatpy {
// namespace module level
namespace constants {

// module definition function
void expose_constants(py::module &m) {

  // tudat source code (C++) to tudatpy (python) interfaces defined in module namespace:

  // docstrings (no source code interface here)
  m.attr("__doc__") = tudatpy::get_docstring("constants").c_str();

  // celestialBodyConstants.h
  m.attr("EARTH_EQUATORIAL_RADIUS") = tbc::EARTH_EQUATORIAL_RADIUS;
  m.attr("EARTH_FLATTENING_FACTOR") = tbc::EARTH_FLATTENING_FACTOR;
  m.attr("EARTH_GEODESY_NORMALIZED_J2") = tbc::EARTH_GEODESY_NORMALIZED_J2;
  // ...

  // physicalConstants.h
  m.attr("SEA_LEVEL_GRAVITATIONAL_ACCELERATION") = tpc::SEA_LEVEL_GRAVITATIONAL_ACCELERATION;
  m.attr("JULIAN_DAY") = tpc::JULIAN_DAY;
  m.attr("JULIAN_DAY_LONG") = tpc::JULIAN_DAY_LONG;
  // ...

  // ...

};

}// namespace module level
}// namespace package level

The procedure can be summarized in three easy steps

  1. make available tudat source code and pybind11 functionality

  2. define module definition function expose_constants( ) in module namespace

  3. define C++ to python interfaces using the pybind syntax

Note

In the case of the constants module, the exposed source code content is limited to attributes.

2. Source Code Exposition in Nested Namespace#

For large tudatpy modules, the exposition of the tudat source code is divided over submodules. In this case, the C++ to python interfaces are defined in the submodule namespace or even lower-level nested namespaces. One example of this usage is the tudatpy astro module, which includes exposed tudat source code from submodules such as fundamentals, ephemerides and more. Consider below the definition of the module astro:

tudatpy/kernel/expose_astro.cpp#
// include .h
#include "expose_astro.h"

// include .h of selected submodule definition
#include "expose_astro/expose_fundamentals.h"
#include "expose_astro/expose_ephemerides.h"
// ...

// pybind11 usage
#include <pybind11/pybind11.h>
namespace py = pybind11;

// namespace package level
namespace tudatpy {
// namespace module level
namespace astro {

// module definition function
void expose_astro(py::module &m) {

  // include selected submodules (source code exposition in nested namespaces 'fundamentals', 'ephemerides', etc):

  // expose_fundamentals.h
  auto fundamentals = m.def_submodule("fundamentals");
  expose_fundamentals(fundamentals);

  // expose_ephemerides.h
  auto ephemerides = m.def_submodule("ephemerides");
  expose_ephemerides(ephemerides);

  // ...

};

} // namespace module level
} // namespace package level

The procedure is largely analogous to the that of Source Code exposition in module namespace:

  1. make available tudat source code and pybind11 functionality

  2. define module definition function expose_astro( ) in module namespace

  3. include selected submodules fundamentals & ephemerides via pybind’s module.add_submodule( ) function

Since the tudatpy submodules fundamentals & ephemerides define the C++ to python interfaces, the definition of these submodules follows the exact same structure as in case 1 (Source Code Exposition in Module Namespace). For the sake of completeness the definition of the ephemerides submodule is presented below:

tudatpy/kernel/expose_astro.cpp#
// include .h
#include "expose_ephemerides.h"

// include .h of considered source content
#include <tudat/astro/ephemerides.h>
#include <tudat/simulation/simulation.h> // TODO: EphemerisType should be in <tudat/astro/ephemerides.h>

// pybind11 usage
#include <pybind11/eigen.h>
#include <pybind11/functional.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;

// aliasing namespaces of considered source content
namespace te = tudat::ephemerides;
namespace tss = tudat::simulation_setup;

// namespace package level
namespace tudatpy {
// namespace submodule level
namespace ephemerides {

void expose_ephemerides(py::module &m) {

  // tudat source code (C++) to tudatpy (python) interfaces defined in submodule namespace:

  py::class_<te::Ephemeris, std::shared_ptr<te::Ephemeris>>(m, "Ephemeris")
      .def("get_cartesian_state", &te::Ephemeris::getCartesianState, py::arg("seconds_since_epoch") = 0.0)
      .def("get_cartesian_position", &te::Ephemeris::getCartesianPosition, py::arg("seconds_since_epoch") = 0.0)
      .def("get_cartesian_velocity", &te::Ephemeris::getCartesianVelocity, py::arg("seconds_since_epoch") = 0.0);

  py::enum_<tss::EphemerisType>(m.attr("Ephemeris"), "EphemerisType")
      .value("approximate_planet_positions", tss::approximate_planet_positions)
      .value("direct_spice_ephemeris", tss::direct_spice_ephemeris)
      // ...

  py::class_<te::RotationalEphemeris,
             std::shared_ptr<te::RotationalEphemeris>>
      RotationalEphemeris_(m, "RotationalEphemeris");

  // ...

};

} // namespace submodule level
} // namespace package level

In principle, it is possible for the ephemerides submodule to delegate the C++ to python interfaces to even lower-level namespaces. In this case, the ephemerides submodule definition (and any lower levels that delegate the interfaces) would follow the logic of case 2 (Source Code Exposition in Nested Namespace), while at the lowest level of this module / submodule tree the definition would again follow the logic of case 1 (Source Code Exposition in Module Namespace).

The tudat(py) API in tudat-bundle#

Warning

WIP - show how to use docstrings in tudat-bundle to contribute to tudat(py)-api

Build Configurations#

The tudat source code can be build using various build configurations. These configurations are listed in tudat-bundle/CMakeLists.txt (l. 43 ff.). The user can select the build options by use of the ‘ON’/’OFF’ keywords. See below a section of the CMakeLists file, which gives an example for an enabled test-suite build option and a disabled boost build option:

tudat-bundle/CMakeLists.txt#
# ...

# +============================================================================
# BUILD OPTIONS
#  Offer the user the choice of defining the build variation.
# +============================================================================

# Build option: enable the test suite.
option(TUDAT_BUILD_TESTS "Build the test suite." ON)

option(TUDAT_DOWNLOAD_AND_BUILD_BOOST "Downloads and builds boost" OFF)

# more Build options:
# ...

# ...

Warning

Options that toggle the use of SOFA amd SPICE can break the build of tudatpy.

Note

For more information on the workings of CMake as a build system, please refer to Build System.

Building the Project and Known Issues#

For most users the project build is very easy and described in the README (steps 5 ff.)

Warning

If your machine is running on an Apple M1 processor, you may have to follow a slightly different. Please refer to this discussion. You may also encounter issues with tudat-test, which can be resolved as described here.