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++-basedtudat
source code.anybody interested in seeing a concurrent C++ / Python development workflow.
Learning Objectives
Get your own
tudat-bundle
environment from the tudat-team.Understand the structure of the
tudat-bundle
and the purpose of its components.Familiarize with the mapping between
tudat
andtudatpy
source code.Understand the higher level functions of the
tudat-api
.Familiarize with the available build configurations for
tudat
andtudatpy
.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 tudatC++
source code.tudatpy
, containing thetudatpy/kernel
directory in which the exposure ofC++
source code to the tudatpy package is facilitated.<build>
, the build directory containing the compiledC++
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.
* 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.
// 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:
directly expose
tudat
source code in the module namespace (see<module_B>
in schematic tudatpy/kernel directory)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
:
// 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
make available
tudat
source code andpybind11
functionalitydefine module definition function
expose_constants( )
in module namespacedefine
C++
topython
interfaces using thepybind
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
:
// 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:
make available
tudat
source code andpybind11
functionalitydefine module definition function
expose_astro( )
in module namespaceinclude selected submodules
fundamentals
&ephemerides
via pybind’smodule.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:
// 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:
# ...
# +============================================================================
# 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.