Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of porkchop plots and hybrid C++/Python kernel modules #116

Merged

Conversation

alopezrivera
Copy link
Contributor

@alopezrivera alopezrivera commented Oct 12, 2023

This pull request implements top of the line porkchop plot support in tudatpy, and implements hybrid C++/Python modules in tudatpy. The first hybrid module as of this pull request is trajectory_design, which includes

  • transfer_trajectory (C++)
  • shape_based_thrust (C++)
  • porkchop (Python)

Table of contents

Summary

Porkchop plots

  • ΔV calculation function
  • API
  • Plotting a porkchop plot without recalculating

Hybrid C++/Python modules

  • How kernel modules are exposed
  • How Python extensions are added to the exposed kernel modules
  • Tudatpy build diagram

Summary

This pull request implements two important features:

  1. Top notch porkchop plots in tudatpy
  2. Support for hybrid kernel modules, that is, support for Python extensions for the kernel modules, and a unified interface to access all of them from tudatpy

The second is of special significance for users as it removes the need to import kernel modules with the usual from tudatpy.kernel.<module_path> import <any C++ kernel module>, which can now be done with from tudatpy.<module_path> import <any C++ kernel module or Python extension>.

The first hybrid C++/Python module is the trajectory_design module, which with this pull request includes the following submodules:

  • transfer_trajectory (C++)
  • shape_based_thrust (C++)
  • porkchop (Python)

The members of the module can be imported as follows:

from tudatpy.trajectory_design.transfer_trajectory import <submodule>
from tudatpy.trajectory_design.shape_based_thrustimport <submodule>
from tudatpy.trajectory_design.porkchop import porkchop

Porkchop plots


Source files: https://github.com/alopezrivera/tudatpy/tree/porkchop_and_hybrid_modules/tudatpy/kernel_hybrid/trajectory_design/porkchop


Porkchop plots display either ΔV or C3 (specific energy) as a function of the departure and arrival date from one body to another, and are widely used for space mission design. This pull request implements porkchop plots in tudat in the trajectory_design.porkchop module.

ΔV
C3
C3_tot
Δ_tot

ΔV calculation function

There is one very important detail to note: the function used to calculate the ΔV required for the transfer is user-defined, with the following API. The function may return either a pair of ΔVs, understood to be [ΔV_departure, ΔV_arrival] (the case for a transfer with a departure and an insertion burn), or a single, total ΔV for the manoeuvre (for more complex, multiple burn or constant burn manoeuvres).

This gives users wide flexibility to estimate ΔV according to the requirements of their use case, and obtain a porkchop plot to determine the optimal departure and arrival windows for a mission.

def function_to_calculate_DV(
        bodies: environment.SystemOfBodies,
        global_frame_orientation: str,
        departure_body: str,
        target_body: str,
        departure_epoch: int,
        arrival_epoch: int,
        central_body: str = 'Sun' ) -> list[float]:
        ...

API

The API of the trajectory_design.porkchop.porkchop function follows.

def porkchop(
        # ΔV calculation arguments
        bodies: environment.SystemOfBodies,
        global_frame_orientation: str,
        departure_body: str,
        target_body: str,
        earliest_departure_time: time_conversion.DateTime,
        latest_departure_time: time_conversion.DateTime,
        earliest_arrival_time: time_conversion.DateTime,
        latest_arrival_time: time_conversion.DateTime,
        time_resolution: float,
        function_to_calculate_delta_v: callable,
        # Plot arguments
        C3: bool = False,
        total: bool = False,
        threshold: float = 10,
        upscale: bool = False,
        number_of_levels: int = 10,
        # Figure arguments
        percent_margin: float = 5,
        figsize: tuple[int, int] = (8, 8),
        show: bool = True,
        save: bool = False,
        filename: str = 'porkchop.png') -> None:
        ...

Plotting a porkchop plot without recalculating

It can be convenient to save the ΔV map returned by porkchop, which can be expensive to compute, and plot it directly without having to recalculate it again. This is supported through the trajectory_design.porkchop.plot_porkchop function, with the following API:

def plot_porkchop(
    departure_body: str,
    target_body: str,
    departure_epochs: np.ndarray, 
    arrival_epochs: np.ndarray, 
    delta_v: np.ndarray,
    C3: bool = False,
    total: bool = False,
    threshold: float = 10,
    upscale: bool = False,
    # Plot arguments
    number_of_levels: int = 20,
    # Figure arguments
    percent_margin: float = 5,
    figsize: tuple[int, int] = (8, 8),
    show: bool = True,
    save: bool = False,
    filename: str = 'porkchop.png') -> None:
    ...

Hybrid C++/Python modules

Hybrid C++/Python modules are implemented by merging the compiled Tudat kernel modules and their Python extensions in the tudatpy submodules.

The merging is implemented with the Python script setup_hybrid_modules.py, which is called after the kernel (kernel.so) has been linked, that is, as the last step in the build process.

How kernel modules are exposed

We will use the module tudatpy.kernel.astro to illustrate how kernel modules are exposed and merged with their Python extensions.

  1. A top-level tudatpy Python module, tudatpy.astro, is created to wrap tudatpy.kernel.astro. This top-level module consists of an empty directory and a an __init__.py
  2. In the __init__.py we import the kernel module tudatpy.kernel.astro as follows: from tudatpy.kernel.astro import *. This makes it possible to import the kernel module in Python like this: import tudatpy.astro
  3. We add a disclaimer, clearly stating that the import statement has been automatically generated, and why.
  4. This process repeats for all submodules of tudatpy.kernel.astro!

How Python extensions are added to the exposed kernel modules

We expose kernel modules by wrapping them in tudapy Python modules, which import the kernel modules in their __init__.py.
Merging their Python extensions in is as simple as copying them to the Python module wrapping the kernel module:

Compiled:
tudatpy/trajectory_design

  • __init__.py
    • from tudatpy.kernel.trajectory_design import *
  • porkchop/

Python extensions are kept in the kernel_hybrid directory of tudatpy, under the name of their parent kernel module:

Source:
tudatpy/kernel_hybrid/trajectory_design

  • __init__.py (possibly! It could include documentation, etc.)
  • porkchop/

To ensure no data is lost, we merge the modules as follows:

  1. The entire tudatpy/kernel_hybrid/trajectory_design is copied to the compiled tudatpy directory tudatpy/trajectory_design
  2. After this is done, we check if tudatpy/trajectory_design already contains a top-level __init__.py file copied from the extensions. This could be useful! For example if we want to make the tudatpy.trajectory_design.porkchop.porkchop function directly accessible from tudatpy.trajectory_design, by adding from tudatpy.trajectory_design.porkchop import porkchop to tudatpy.trajectory_design's __init__.py file
    • IF an __init__.py EXISTS, the kernel module import statement and disclaimer are APPENDED to the __init__.py
    • Else, an __init__.py is created inside tudatpy/trajectory_design with the kernel module import statement and disclaimer

Tudatpy build diagram

The following diagram expresses the steps taken during compilation to merge the C++ kernel modules and their Python extensions:

hybrid module CMakeLists

DominicDirkx and others added 19 commits June 5, 2023 08:25
Temporarily disbaling docs build to update boost version in conda packages
Now it is possible for the delta V calculation function to return either
- Total delta V, as a float
- Or departure and arrival delta V, as a list/tuple/numpy array of floats
Drafted inclusion of porkchop module into C++ trajectory_design module
It now takes places after `kernel.so` is linked.
- ΔV calculation function default type and default value correctly set
- Refactored porkchop components to solve import bug
@DominicDirkx DominicDirkx changed the base branch from master to develop October 13, 2023 19:41
@DominicDirkx
Copy link
Member

Hi Antonio, the code for merging the modules seems to work well on my laptop as well :) I've verified that the new porkchop module can be accessed from the compiled kernel. Great work! This is going to open up a lot of options for extending tudatpy more flexibly

Could you share the script you used to generate the figures here? Before merging I'd like to verify that they do indeed work as intended.

observer_body_name = central_body,
reference_frame_name = global_frame_orientation,
aberration_corrections = "NONE",
ephemeris_time = arrival_epoch)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part here should be modified so that the code uses the ephemeris in the bodies, rather than the spice interface, to determine the states at the initial and final time. To get the state from a single body in the global frame, use:

bodies.at( body_name ).state_in_base_frame_from_ephemeris( current_epoch )


def calculate_lambert_arc_impulsive_delta_v(
bodies: environment.SystemOfBodies,
global_frame_orientation: str,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see next comment; the global_frame_orientation input can be removed (it is inside the bodies)

line_color: str = 'black',
font_size: float = 7,
label: str = False,
# Figure arguments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I propose we leave this part as it is. But, for a future extension I'd like to propose putting the various plotting options into a (custom-defined?) data structure. The list of input arguments is very long, and may need to grow even further depending on how we want to extend this code. We can merge this now, and easily add a link between a (possible) future update with data structure input for plotting functionality and this function to ensure backwards compatibility. So, not something to address now, but something to discuss when we investigate the best options for future extensions

# corresponding to this `__init__.py` file: `tudatpy.{module_name}`).
"""

def expose_hybrid_module(module):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic :)

@DominicDirkx
Copy link
Member

I've added a few comments in the code. Nothing major, will be great to see this merged into Tudat soon!

Also, there is now this output:

===================================== EXPOSING MODULE: astro ======================================
- element_conversion
- frame_conversion
- fundamentals
- gravitation
- polyhedron_utilities
- time_conversion
- two_body_dynamics

....

after linking tudatpy (I think for debugging purposes?). Could you remove/comment this output generation in CMake?

@DominicDirkx DominicDirkx merged commit 14ff2d0 into tudat-team:develop Oct 14, 2023
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants