def get_hodographic_trajectory(shaping_object: tudatpy.kernel.simulation.
                               shape_based_thrust.HodographicShaping,
                               trajectory_parameters: list,
                               specific_impulse: float,
                               output_path: str = None):
    """
    It computes the analytical hodographic trajectory and saves the results to a file, if desired.

    This function analytically calculates the hodographic trajectory from the Hodographic Shaping object. It
    retrieves both the trajectory and the acceleration profile; if desired, both are saved to files as follows:

    * hodographic_trajectory.dat: Cartesian states of semi-analytical trajectory;
    * hodographic_thrust_acceleration.dat: Thrust acceleration in inertial, Cartesian, coordinates, along the
    semi-analytical trajectory.

    NOTE: The independent variable (first column) does not represent the usual time (seconds since J2000), but instead
    denotes the time since departure.

    Parameters
    ----------
    shaping_object: tudatpy.kernel.simulation.shape_based_thrust.HodographicShaping
        Hodographic shaping object.
    trajectory_parameters : list of floats
        List of trajectory parameters to be optimized.
    specific_impulse : float
        Constant specific impulse of the spacecraft.
    output_path : str (default: None)
        If and where to save the benchmark results (if None, results are NOT written).

    Returns
    -------
    none
    """
    # Set time parameters
    start_time = 0.0
    final_time = get_trajectory_time_of_flight(trajectory_parameters)
    # Set number of data points
    number_of_data_points = 10000
    # Compute step size
    step_size = (final_time - start_time) / (number_of_data_points - 1)
    # Create epochs vector
    epochs = np.linspace(start_time, final_time, number_of_data_points)
    # Create specific impulse lambda function
    specific_impulse_function = lambda t: specific_impulse
    # Retrieve thrust acceleration profile from shaping object
    # NOTE TO THE STUDENTS: do not uncomment
    # thrust_acceleration_profile = shaping_object.get_thrust_acceleration_profile(
    #     epochs,
    #     specific_impulse_function)
    # Retrieve trajectory from shaping object
    trajectory_shape = shaping_object.get_trajectory(epochs)
    # If desired, save results to files
    if output_path is not None:
        # NOTE TO THE STUDENTS: do not uncomment
        # save2txt(thrust_acceleration_profile,
        #          'hodographic_thrust_acceleration.dat',
        #          output_path)
        save2txt(trajectory_shape, 'hodographic_trajectory.dat', output_path)
Exemple #2
0
def compare_benchmarks(first_benchmark: dict,
                       second_benchmark: dict,
                       output_path: str,
                       filename: str) -> dict:
    """
    It compares the results of two benchmark runs.

    It uses an 8th-order Lagrange interpolator to compare the state (or the dependent variable, depending on what is
    given as input) history. The difference is returned in form of a dictionary and, if desired, written to a file named
    filename and placed in the directory output_path.

    Parameters
    ----------
    first_benchmark : dict
        State (or dependent variable history) from the first benchmark.
    second_benchmark : dict
        State (or dependent variable history) from the second benchmark.
    output_path : str
        If and where to save the benchmark results (if None, results are NOT written).
    filename : str
        Name of the output file.

    Returns
    -------
    benchmark_difference : dict
        Interpolated difference between the two benchmarks' state (or dependent variable) history.
    """
    # Create 8th-order Lagrange interpolator for first benchmark
    benchmark_interpolator = interpolators.create_one_dimensional_interpolator(first_benchmark,
                                                                               interpolators.lagrange_interpolation(8))
    # Calculate the difference between the benchmarks
    print('Calculating benchmark differences...')
    # Initialize difference dictionaries
    benchmark_difference = dict()
    # Calculate the difference between the states and dependent variables in an iterative manner
    for second_epoch in second_benchmark.keys():
        benchmark_difference[second_epoch] = benchmark_interpolator.interpolate(second_epoch) - \
                                         second_benchmark[second_epoch]
    # Write results to files
    if output_path is not None:
        save2txt(benchmark_difference,
                 filename,
                 output_path)
    # Return the interpolator
    return benchmark_difference
def generate_benchmarks(
        benchmark_step_size,
        simulation_start_epoch: float,
        specific_impulse: float,
        bodies: tudatpy.kernel.simulation.environment_setup.SystemOfBodies,
        benchmark_propagator_settings: tudatpy.kernel.simulation.
    propagation_setup.propagator.MultiTypePropagatorSettings,
        thrust_parameters: list,
        are_dependent_variables_present: bool,
        output_path: str = None):
    """
    Function to generate to accurate benchmarks.

    This function runs two propagations with two different integrator settings that serve as benchmarks for
    the nominal runs. The state and dependent variable history for both benchmarks are returned and, if desired, 
    they are also written to files (to the directory ./SimulationOutput/benchmarks/) in the following way:
    * benchmark_1_states.dat, benchmark_2_states.dat
        The numerically propagated states from the two benchmarks.
    * benchmark_1_dependent_variables.dat, benchmark_2_dependent_variables.dat
        The dependent variables from the two benchmarks.

    Parameters
    ----------
    simulation_start_epoch : float
        The start time of the simulation in seconds.
    bodies : tudatpy.kernel.simulation.environment_setup.SystemOfBodies,
        System of bodies present in the simulation.
    benchmark_propagator_settings
        Propagator settings object which is used to run the benchmark propagations.
    thrust_parameters
        List that represents the thrust parameters for the spacecraft.
    are_dependent_variables_present : bool
        If there are dependent variables to save.
    output_path : str (default: None)
        If and where to save the benchmark results (if None, results are NOT written).

    Returns
    -------
    return_list : list
        List of state and dependent variable history in this order: state_1, state_2, dependent_1_ dependent_2.
    """
    ### CREATION OF THE TWO BENCHMARKS ###
    # Define benchmarks' step sizes
    first_benchmark_step_size = 2.0  # s
    second_benchmark_step_size = 2.0 * first_benchmark_step_size

    # Create integrator settings for the first benchmark, using a fixed step size RKDP8(7) integrator
    # (the minimum and maximum step sizes are set equal, while both tolerances are set to inf)
    benchmark_integrator = propagation_setup.integrator
    benchmark_integrator_settings = benchmark_integrator.runge_kutta_variable_step_size(
        simulation_start_epoch, first_benchmark_step_size,
        propagation_setup.integrator.RKCoefficientSets.rkdp_87,
        first_benchmark_step_size, first_benchmark_step_size, np.inf, np.inf)

    # Create Shape Optimization Problem object for first benchmark
    first_benchmark = ShapeOptimizationProblem(bodies,
                                               benchmark_integrator_settings,
                                               benchmark_propagator_settings,
                                               specific_impulse)
    print('Running first benchmark...')
    # Set new thrust parameters and evaluate fitness
    first_benchmark.fitness(thrust_parameters)

    # Create integrator settings for the second benchmark in the same way
    benchmark_integrator = propagation_setup.integrator
    benchmark_integrator_settings = benchmark_integrator.runge_kutta_variable_step_size(
        simulation_start_epoch, second_benchmark_step_size,
        propagation_setup.integrator.RKCoefficientSets.rkdp_87,
        second_benchmark_step_size, second_benchmark_step_size, np.inf, np.inf)

    # Create Shape Optimization Problem object for second benchmark
    second_benchmark = ShapeOptimizationProblem(bodies,
                                                benchmark_integrator_settings,
                                                benchmark_propagator_settings,
                                                specific_impulse)
    print('Running second benchmark...')
    # Set new thrust parameters and evaluate fitness
    second_benchmark.fitness(thrust_parameters)

    ### WRITE BENCHMARK RESULTS TO FILE ###
    # Retrieve state history
    first_benchmark_states = first_benchmark.get_last_run_propagated_state_history(
    )
    second_benchmark_states = second_benchmark.get_last_run_propagated_state_history(
    )
    # Write results to files
    if output_path is not None:
        save2txt(first_benchmark_states, 'benchmark_1_states.dat', output_path)
        save2txt(second_benchmark_states, 'benchmark_2_states.dat',
                 output_path)
    # Add items to be returned
    return_list = [first_benchmark_states, second_benchmark_states]

    ### DO THE SAME FOR DEPENDENT VARIABLES ###
    if are_dependent_variables_present:
        # Retrieve dependent variable history
        first_benchmark_dependent_variable = first_benchmark.get_last_run_dependent_variable_history(
        )
        second_benchmark_dependent_variable = second_benchmark.get_last_run_dependent_variable_history(
        )
        # Write results to file
        if output_path is not None:
            save2txt(first_benchmark_dependent_variable,
                     'benchmark_1_dependent_variables.dat', output_path)
            save2txt(second_benchmark_dependent_variable,
                     'benchmark_2_dependent_variables.dat', output_path)
        # Add items to be returned
        return_list.append(first_benchmark_dependent_variable)
        return_list.append(second_benchmark_dependent_variable)

    return return_list
Exemple #4
0
            }
            # Check if the propagation was run successfully
            propagation_outcome = dynamics_simulator.integration_completed_successfully(
            )
            dict_to_write['Propagation run successfully'] = propagation_outcome
            # Note if results were written to files
            dict_to_write['Results written to file'] = write_results_to_file
            # Note if benchmark was run
            dict_to_write['Benchmark run'] = use_benchmark
            # Note if dependent variables were present
            dict_to_write[
                'Dependent variables present'] = are_dependent_variables_to_save

            # Save results to a file
            if write_results_to_file:
                save2txt(state_history, 'state_history.dat', output_path)
                save2txt(dependent_variable_history,
                         'dependent_variable_history.dat', output_path)
                save2txt(dict_to_write, 'ancillary_simulation_info.txt',
                         output_path)

            ### BENCHMARK COMPARISON ####
            # Compare the simulation to the benchmarks and write differences to files
            if use_benchmark:
                # Initialize containers
                state_difference = dict()
                # Loop over the propagated states and use the benchmark interpolators
                # NOTE TO STUDENTS: it can happen that the benchmark ends earlier than the regular simulation, due to
                # the shorter step size. Therefore, the following lines of code will be forced to extrapolate the
                # benchmark states (or dependent variables), producing a warning. Be aware of it!
                for epoch in state_history.keys():
def main():
    # Load spice kernels.
    spice_interface.load_standard_spice_kernels()

    # Set simulation start epoch.
    simulation_start_epoch = 0.0

    # Set numerical integration fixed step size.
    fixed_step_size = 1.0

    # Set simulation end epoch.
    simulation_end_epoch = constants.JULIAN_DAY

    ###########################################################################
    # CREATE ENVIRONMENT ######################################################
    ###########################################################################

    bodies_to_create = ["Sun", "Earth", "Moon", "Mars", "Venus"]

    env = Environment(bodies_to_create,
                      user_defined_bodies=None,
                      frame_origin="SSB",
                      frame_orientation="ECLIPJ2000",
                      custom_body_settings=None,
                      alternative_kernels=None,
                      start_epoch=simulation_start_epoch,
                      end_epoch=simulation_end_epoch,
                      fixed_dt=fixed_step_size,
                      epoch_margin=300.0)

    ###########################################################################
    # CREATE VEHICLE ##########################################################
    ###########################################################################

    env.vehicles = ["Delfi-C3"]

    env.bodies["Delfi-C3"].set_constant_body_mass(400.0)

    ###########################################################################
    # CREATE VEHICLE - ENVIRONMENT INTERFACE ##################################
    ###########################################################################

    # Create aerodynamic interface settings for Delfi-C3.
    env.set_aerodynamic_coefficient_interface(
        target_body_name="Delfi-C3",
        reference_area=4.0,
        coefficients=1.2 * np.ones(3),
        coefficients_in_aerodynamic_frame=True,
        coefficients_in_negative_axis_direction=True,
        model="constant_coefficients")

    # Create radiation pressure interface settings for Delfi-C3.
    env.set_radiation_pressure_interface(target_body_name="Delfi-C3",
                                         reference_area=4.0,
                                         coefficient=1.2,
                                         occulting=["Earth"],
                                         source="Sun",
                                         model="cannon_ball")

    ###########################################################################
    # FINALIZE BODIES #########################################################
    ###########################################################################

    # Finalize body creation.
    env.finalize_environment(frame_origin="SSB", frame_orientation="J2000")

    ###########################################################################
    # CREATE ACCELERATIONS ####################################################
    ###########################################################################

    # Define bodies that are propagated.
    bodies_to_propagate = env.vehicles

    # Define central bodies.
    central_bodies = ["Earth"]

    # Define unique (Sun, Earth) accelerations acting on Delfi-C3.
    accelerations_of_delfi_c3 = dict(
        Sun=[
            simulation_setup.Acceleration.canon_ball_radiation_pressure()
            # AccelerationSettings(AvailableAcceleration.cannon_ball_radiation_pressure) # LEGACY DESIGN.
        ],
        Earth=[
            simulation_setup.Acceleration.spherical_harmonic_gravity(5, 5),
            # SphericalHarmonicAccelerationSettings(5, 5), # LEGACY DESIGN.
            simulation_setup.Acceleration.aerodynamic()
            # AccelerationSettings(AvailableAcceleration.aerodynamic) # LEGACY DESIGN.
        ])

    # Define other point mass accelerations acting on Delfi-C3.
    for other in set(bodies_to_create).difference({"Sun", "Earth"}):
        accelerations_of_delfi_c3[other] = [
            simulation_setup.Acceleration.point_mass_gravity()
        ]

    # Create global accelerations dictionary.
    acceleration_dict = {"Delfi-C3": accelerations_of_delfi_c3}

    # Create acceleration models.
    acceleration_models = simulation_setup.create_acceleration_models_dict(
        bodies_to_create, acceleration_dict, bodies_to_propagate,
        central_bodies)

    ###########################################################################
    # CREATE PROPAGATION SETTINGS #############################################
    ###########################################################################

    # Set initial conditions for the Asterix satellite that will be
    # propagated in this simulation. The initial conditions are given in
    # Keplerian elements and later on converted to Cartesian elements.

    # Set Keplerian elements for Delfi-C3
    earth_gravitational_parameter = env.bodies[
        "Earth"].gravity_field_model.get_gravitational_parameter()

    # LEGACY DESIGN.
    # KEI = orbital_element_conversions.KeplerianElementIndices
    # asterix_initial_state_in_keplerian_elements = np.zeros(6)
    # kep_state = asterix_initial_state_in_keplerian_elements
    # kep_state[int(KEI.semi_major_axis_index)] = 7500.0E3
    # kep_state[int(KEI.eccentricity_index)] = 0.1
    # kep_state[int(KEI.inclination_index)] = np.deg2rad(85.3)
    # kep_state[int(KEI.argument_of_periapsis_index)] = np.deg2rad(235.7)
    # kep_state[int(KEI.longitude_of_ascending_node_index)] = np.deg2rad(23.4)
    # kep_state[int(KEI.true_anomaly_index)] = np.deg2rad(139.87)
    # system_initial_state = corbital_element_conversions.onvert_keplerian_to_cartesian_elements(
    #     kep_state, earth_gravitational_parameter)

    # REVISED CONTEMPORARY DESIGN.
    system_initial_state = elements.keplerian2cartesian(
        mu=earth_gravitational_parameter,
        a=7500.0E3,
        ecc=0.1,
        inc=np.deg2rad(85.3),
        raan=np.deg2rad(23.4),
        argp=np.deg2rad(235.7),
        nu=np.deg2rad(139.87))

    # Create propagation settings.
    propagator_settings = propagators.TranslationalStatePropagatorSettings(
        central_bodies, acceleration_models, bodies_to_propagate,
        system_initial_state, simulation_end_epoch)
    # Create numerical integrator settings.
    integrator_settings = numerical_integrators.IntegratorSettings(
        numerical_integrators.AvailableIntegrators.rungeKutta4,
        simulation_start_epoch, fixed_step_size)

    ###########################################################################
    # PROPAGATE ORBIT #########################################################
    ###########################################################################

    # Create simulation object and propagate dynamics.
    dynamics_simulator = propagators.SingleArcDynamicsSimulator(
        bodies, integrator_settings, propagator_settings, True)
    result = dynamics_simulator.get_equations_of_motion_numerical_solution()

    ###########################################################################
    # PRINT INITIAL AND FINAL STATES ##########################################
    ###########################################################################

    print(f"""
Single Earth-Orbiting Satellite Example.
The initial position vector of Delfi-C3 is [km]: \n{
        result[simulation_start_epoch][:3] / 1E3}
The initial velocity vector of Delfi-C3 is [km]: \n{
        result[simulation_start_epoch][3:] / 1E3}
After {simulation_end_epoch} seconds the position vector of Delfi-C3 is [km]: \n{
        result[simulation_end_epoch][:3] / 1E3}
And the velocity vector of Delfi-C3 is [km]: \n{
        result[simulation_start_epoch][3:] / 1E3}
        """)

    ###########################################################################
    # SAVE RESULTS ############################################################
    ###########################################################################

    io.save2txt(
        solution=result,
        filename="singlePerturbedSatellitePropagationHistory.dat",
        directory="./tutorial_2",
    )

    # Final statement (not required, though good practice in a __main__).
    return 0
Exemple #6
0
# define the variables we need
dependent_variables_to_save = [
    propagation_setup.dependent_variable.total_acceleration("CubeSat"),
    propagation_setup.dependent_variable.keplerian_state("CubeSat", "Sun"),
]
# create propagation settings
propagator_settings = propagation_setup.propagator.translational(
    central_bodies,
    acceleration_models,
    bodies_to_propagate,
    initial_state,
    simulation_end_epoch,
    output_variables=dependent_variables_to_save)
# numerical integrator settings
fixed_step_size = 10.0

integrator_settings = propagation_setup.integrator.runge_kutta_4(
    simulation_start_epoch, fixed_step_size)
# # propagate orbit
dynamics_simulator = propagation_setup.SingleArcDynamicsSimulator(
    bodies, integrator_settings, propagator_settings)

states = dynamics_simulator.state_history
dependent_variables = dynamics_simulator.dependent_variable_history

save2txt(
    solution=states,
    filename="PropagationHistory_3U_CubeSat_thrust.dat",
    directory="./",  # default = "./"  # default = None
)
def main():
    # Load spice kernels.
    spice_interface.load_standard_spice_kernels()

    # Set simulation start epoch.
    simulation_start_epoch = 0.0

    # Set numerical integration fixed step size.
    fixed_step_size = 1.0

    # Set simulation end epoch.
    simulation_end_epoch = constants.JULIAN_DAY

    ###########################################################################
    # CREATE ENVIRONMENT ######################################################
    ###########################################################################

    bodies_to_create = ["Sun", "Earth", "Moon", "Mars", "Venus"]

    env = Environment(tudatpy_bodies=bodies_to_create,
                      custom_bodies=None,
                      frame_origin="SSB",
                      frame_orientation="ECLIPJ2000",
                      start_epoch=simulation_start_epoch,
                      end_epoch=simulation_end_epoch,
                      epoch_margin=300.0)

    ###########################################################################
    # CREATE VEHICLE ##########################################################
    ###########################################################################

    env["Delfi-C3"] = Body(env)

    env["Delfi-C3"].set_constant_body_mass(400.0)

    ###########################################################################
    # CREATE VEHICLE - ENVIRONMENT INTERFACE ##################################
    ###########################################################################

    # Create aerodynamic coefficient interface settings
    reference_area = 4.0
    aerodynamic_coefficient = 1.2
    aero_c_settings = simulation_setup.ConstantAerodynamicCoefficientSettings(
        reference_area,
        aerodynamic_coefficient * np.ones(3),
        are_coefficients_in_aerodynamic_frame=True,
        are_coefficients_in_negative_axis_direction=True)
    # Create and set aerodynamic coefficients object
    env._bodies["Delfi-C3"].set_aerodynamic_coefficient_interface(
        simulation_setup.create_aerodynamic_coefficient_interface(
            aero_c_settings, "Delfi-C3"))
    # TODO: Simplify (post 1.0.0 work)

    # Create radiation pressure settings
    reference_area_radiation = 4.0
    radiation_pressure_coefficient = 1.2
    occulting_bodies = ["Earth"]
    rad_press_settings = simulation_setup.CannonBallRadiationPressureInterfaceSettings(
        "Sun", reference_area_radiation, radiation_pressure_coefficient,
        occulting_bodies)

    # Create and set radiation pressure settings
    env._bodies["Delfi-C3"].set_radiation_pressure_interface(
        "Sun",
        simulation_setup.create_radiation_pressure_interface(
            rad_press_settings, "Delfi-C3", env._bodies))

    # # Create aerodynamic interface settings for Delfi-C3.
    # env["Delfi-C3"]._set_aerodynamic_coefficient_interface(
    #     reference_area=4.0,
    #     coefficients=1.2 * np.ones(3),
    #     coefficients_in_aerodynamic_frame=True,
    #     coefficients_in_negative_axis_direction=True,
    #     model="constant_coefficients")
    #
    # # Create radiation pressure interface settings for Delfi-C3.
    # env["Delfi-C3"]._set_radiation_pressure_interface(
    #     reference_area=4.0,
    #     coefficient=1.2,
    #     occulting=["Earth"],
    #     source="Sun",
    #     model="cannon_ball")

    ###########################################################################
    # FINALIZE BODIES #########################################################
    ###########################################################################

    # Finalize body creation.
    env.finalize_environment()

    ###########################################################################
    # CREATE ACCELERATIONS ####################################################
    ###########################################################################

    # Define bodies that are propagated.
    bodies_to_propagate = ["Delfi-C3"]

    # Define central bodies.
    central_bodies = ["Earth"]

    # Define unique (Sun, Earth) accelerations acting on Delfi-C3.
    accelerations_of_delfi_c3 = dict(
        Sun=[
            simulation_setup.Acceleration.canon_ball_radiation_pressure()
            # AccelerationSettings(AvailableAcceleration.cannon_ball_radiation_pressure) # LEGACY DESIGN.
        ],
        Earth=[
            simulation_setup.Acceleration.spherical_harmonic_gravity(5, 5),
            # SphericalHarmonicAccelerationSettings(5, 5), # LEGACY DESIGN.
            simulation_setup.Acceleration.aerodynamic()
            # AccelerationSettings(AvailableAcceleration.aerodynamic) # LEGACY DESIGN.
        ])

    # Define other point mass accelerations acting on Delfi-C3.
    for other in set(bodies_to_create).difference({"Sun", "Earth"}):
        accelerations_of_delfi_c3[other] = [
            simulation_setup.Acceleration.point_mass_gravity()
        ]

    # Create global accelerations dictionary.
    acceleration_dict = {"Delfi-C3": accelerations_of_delfi_c3}

    # Create acceleration models.

    acceleration_models = simulation_setup.create_acceleration_models_dict(
        env._bodies, acceleration_dict, bodies_to_propagate, central_bodies)

    ###########################################################################
    # CREATE PROPAGATION SETTINGS #############################################
    ###########################################################################

    # Set initial conditions for the Asterix satellite that will be
    # propagated in this simulation. The initial conditions are given in
    # Keplerian elements and later on converted to Cartesian elements.

    # Set Keplerian elements for Delfi-C3
    earth_gravitational_parameter = env[
        "Earth"].gravity_field_model.get_gravitational_parameter()

    # REVISED CONTEMPORARY DESIGN.
    system_initial_state = elements.keplerian2cartesian(
        mu=earth_gravitational_parameter,
        a=7500.0E3,
        ecc=0.1,
        inc=np.deg2rad(85.3),
        raan=np.deg2rad(23.4),
        argp=np.deg2rad(235.7),
        nu=np.deg2rad(139.87))

    # Create propagation settings.
    propagator_settings = propagators.TranslationalStatePropagatorSettings(
        central_bodies, acceleration_models, bodies_to_propagate,
        system_initial_state, simulation_end_epoch)
    # Create numerical integrator settings.
    integrator_settings = numerical_integrators.IntegratorSettings(
        numerical_integrators.AvailableIntegrators.rungeKutta4,
        simulation_start_epoch, fixed_step_size)

    ###########################################################################
    # PROPAGATE ORBIT #########################################################
    ###########################################################################

    t0 = time.time()
    # Create simulation object and propagate dynamics.
    print(env._bodies)
    dynamics_simulator = propagators.SingleArcDynamicsSimulator(
        env._bodies, integrator_settings, propagator_settings, True)
    print("dynamics_simulator: ", time.time() - t0)
    result = dynamics_simulator.get_equations_of_motion_numerical_solution()

    ###########################################################################
    # PRINT INITIAL AND FINAL STATES ##########################################
    ###########################################################################

    print(f"""
Single Earth-Orbiting Satellite Example.
The initial position vector of Delfi-C3 is [km]: \n{
        result[simulation_start_epoch][:3] / 1E3}
The initial velocity vector of Delfi-C3 is [km/s]: \n{
        result[simulation_start_epoch][3:] / 1E3}
After {simulation_end_epoch} seconds the position vector of Delfi-C3 is [km]: \n{
        result[simulation_end_epoch][:3] / 1E3}
And the velocity vector of Delfi-C3 is [km/s]: \n{
        result[simulation_start_epoch][3:] / 1E3}
        """)

    ###########################################################################
    # SAVE RESULTS ############################################################
    ###########################################################################

    io.save2txt(
        solution=result,
        filename="singlePerturbedSatellitePropagationHistory.dat",
        directory="./tutorial_2_prototype",
    )

    # Final statement (not required, though good practice in a __main__).
    return 0
def main():
    """
    The problem describes the orbit design around a small body (asteroid Itokawa).

    DYNAMICAL MODEL
    Itokawa spherical harmonics, cannonball radiation pressure from Sun, point-mass third-body
    from Sun, Jupiter, Saturn, Earth, Mars

    PROPAGATION TIME
    5 days

    INTEGRATOR
    RKF7(8) with tolerances 1E-8

    TERMINATION CONDITIONS
    In addition to 5 day time, minimum distance from Itokaw's center of mass: 150 m (no crashing),
    maximum distance from center of mass: 5 km (no escaping)

    DESIGN VARIABLES
    Initial values of semi-major axis, eccentricity, inclination, and longitude of node

    OBJECTIVES
    1. good coverage: the mean value of the absolute longitude w.r.t. Itokawa over the full propagation should be
       maximized;
    2. close orbit: the mean value of the distance should be minimized.
    """
    ###########################################################################
    # CREATE SIMULATION SETTINGS ##############################################
    ###########################################################################

    # Load spice kernels
    spice_interface.load_standard_kernels()

    # Define Itokawa radius
    itokawa_radius = 161.915

    # Set simulation start and end epochs
    mission_initial_time = 0.0
    mission_duration = 5.0 * 86400.0

    # Set boundaries on the design variables
    design_variable_lb = (300, 0.0, 0.0, 0.0)
    design_variable_ub = (2000, 0.3, 180, 360)

    # Set termination conditions
    minimum_distance_from_com = 150.0 + itokawa_radius
    maximum_distance_from_com = 5.0E3 + itokawa_radius

    # Create simulation bodies
    bodies = create_simulation_bodies(itokawa_radius)

    ###########################################################################
    # CREATE ACCELERATIONS ####################################################
    ###########################################################################

    bodies_to_propagate = ["Spacecraft"]
    central_bodies = ["Itokawa"]

    # Create acceleration models.
    acceleration_models = get_acceleration_models(bodies_to_propagate,
                                                  central_bodies, bodies)

    # Create numerical integrator settings.
    integrator_settings = propagation_setup.integrator.runge_kutta_variable_step_size(
        mission_initial_time, 1.0,
        propagation_setup.integrator.RKCoefficientSets.rkf_78, 1.0E-6, 86400.0,
        1.0E-8, 1.0E-8)

    ###########################################################################
    # CREATE PROPAGATION SETTINGS #############################################
    ###########################################################################

    # Define list of dependent variables to save
    dependent_variables_to_save = get_dependent_variables_to_save()

    # Create propagation settings
    termination_settings = get_termination_settings(mission_initial_time,
                                                    mission_duration,
                                                    minimum_distance_from_com,
                                                    maximum_distance_from_com)

    # Define (Cowell) propagator settings with mock initial state
    propagator_settings = propagation_setup.propagator.translational(
        central_bodies,
        acceleration_models,
        bodies_to_propagate,
        np.zeros(6),
        termination_settings,
        output_variables=dependent_variables_to_save)

    ###########################################################################
    # OPTIMIZE ORBIT WITH PYGMO ###############################################
    ###########################################################################

    # Fix seed for reproducibility
    fixed_seed = 17031861
    # Instantiate orbit problem
    orbitProblem = AsteroidOrbitProblem(bodies, integrator_settings,
                                        propagator_settings,
                                        mission_initial_time, mission_duration,
                                        design_variable_lb, design_variable_ub)

    # Select Moead algorithm from pygmo, with one generation
    algo = pg.algorithm(pg.nsga2(gen=1, seed=fixed_seed))
    # Create pygmo problem using the UDP instantiated above
    prob = pg.problem(orbitProblem)
    # Initialize pygmo population with 48 individuals
    population_size = 48
    pop = pg.population(prob, size=population_size, seed=fixed_seed)
    # Set the number of evolutions
    number_of_evolutions = 50
    # Initialize containers
    fitness_list = []
    population_list = []
    # Evolve the population recursively
    for gen in range(number_of_evolutions):
        print('Evolving population; at generation ' + str(gen))
        # Evolve the population
        pop = algo.evolve(pop)
        # Store the fitness values and design variables for all individuals
        fitness_list.append(pop.get_f())
        population_list.append(pop.get_x())

    ###########################################################################
    # ANALYZE FIRST AND LAST GENERATIONS ######################################
    ###########################################################################

    dump_results_to_file = False

    # Get output path
    output_path = os.getcwd() + '/PygmoExampleSimulationOutput/'

    # Retrieve first and last generations for further analysis
    pops_to_analyze = {0: 'initial', number_of_evolutions - 1: 'final'}
    # Initialize containers
    simulation_output = dict()
    # Loop over first and last generations
    for population_index, population_name in pops_to_analyze.items():
        current_population = population_list[population_index]
        # Save fitness and population members
        if dump_results_to_file:
            # Create directory
            if not os.path.isdir(output_path):
                os.mkdir(output_path)
            np.savetxt(output_path + 'Fitness_' + population_name + '.dat',
                       fitness_list[population_index])
            np.savetxt(output_path + 'Population_' + population_name + '.dat',
                       population_list[population_index])
        # Current generation's dictionary
        generation_output = dict()
        # Loop over all individuals of the populations
        for individual in range(population_size):
            # Retrieve orbital parameters
            current_orbit_parameters = current_population[individual]
            # Propagate orbit and compute fitness
            orbitProblem.fitness(current_orbit_parameters)
            # Retrieve state and dependent variable history
            current_states = orbitProblem.get_last_run_dynamics_simulator(
            ).state_history
            current_dependent_variables = orbitProblem.get_last_run_dynamics_simulator(
            ).dependent_variable_history
            # Save results to dict
            generation_output[individual] = [
                current_states, current_dependent_variables
            ]
            # Write data to files
            if dump_results_to_file:
                save2txt(
                    current_dependent_variables, population_name +
                    '_dependent_variables' + str(individual) + '.dat',
                    output_path)
                save2txt(
                    current_states,
                    population_name + '_states' + str(individual) + '.dat',
                    output_path)
        # Append to global dictionary
        simulation_output[population_index] = [
            generation_output, fitness_list[population_index],
            population_list[population_index]
        ]

    ###########################################################################
    # ANALYZE RESULTS #########################################################
    ###########################################################################

    # Set font size for plots
    font = {'size': 12}
    matplotlib.rc('font', **font)

    # Create dictionaries
    decision_variable_names = {
        0: 'Semi-major axis [m]',
        1: 'Eccentricity',
        2: 'Inclination [deg]',
        3: 'Longitude of the node [deg]'
    }
    decision_variable_range = {
        0: [800.0, 1300.0],
        1: [0.10, 0.17],
        2: [90.0, 95.0],
        3: [250.0, 270.0]
    }
    decision_variable_symbols = {
        0: r'$a$',
        1: r'$e$',
        2: r'$i$',
        3: r'$\Omega$'
    }
    decision_variable_units = {0: r' m', 1: r' ', 2: r' deg', 3: r' deg'}
    # Loop over populations
    for population_index in simulation_output.keys():
        # Retrieve current population
        current_generation = simulation_output[population_index]
        # Plot Pareto fronts for all design variables
        fig, axs = plt.subplots(2, 2, figsize=(14, 8))
        fig.suptitle('Generation ' + str(population_index),
                     fontweight='bold',
                     y=0.95)
        current_fitness = current_generation[1]
        current_population = current_generation[2]
        for ax_index, ax in enumerate(axs.flatten()):
            cs = ax.scatter(np.deg2rad(current_fitness[:, 0]),
                            current_fitness[:, 1],
                            40,
                            current_population[:, ax_index],
                            marker='.')
            cbar = fig.colorbar(cs, ax=ax)
            cbar.ax.set_ylabel(decision_variable_names[ax_index])
            ax.grid('major')
            if ax_index > 1:
                ax.set_xlabel(r'Objective 1: coverage [$deg^{-1}$] ')
            if ax_index == 0 or ax_index == 2:
                ax.set_ylabel(r'Objective 2: proximity [$m$]')
        # Save figure
        fig.savefig('pareto_generation_' + str(population_index) + '.png',
                    bbox_inches='tight')

    # Plot histogram for last generation, semi-major axis
    fig, axs = plt.subplots(2, 2, figsize=(12, 8))
    fig.suptitle('Final orbits by decision variable',
                 fontweight='bold',
                 y=0.95)
    last_pop = simulation_output[number_of_evolutions - 1][2]
    for ax_index, ax in enumerate(axs.flatten()):
        ax.hist(last_pop[:, ax_index], bins=30)
        # Prettify
        ax.set_xlabel(decision_variable_names[ax_index])
        if ax_index % 2 == 0:
            ax.set_ylabel('Occurrences in the population')
    # Save figure
    fig.savefig('histograms_final_generation.png', bbox_inches='tight')

    # Plot orbits of initial and final generation
    fig = plt.figure(figsize=(12, 6))
    fig.suptitle('Initial and final orbit bundle', fontweight='bold', y=0.95)
    title = {0: 'Initial orbit bundle', 1: 'Final orbit bundle'}
    # Loop over populations
    for ax_index, population_index in enumerate(simulation_output.keys()):
        current_ax = fig.add_subplot(1, 2, 1 + ax_index, projection='3d')
        # Retrieve current population
        current_generation = simulation_output[population_index]
        current_population = current_generation[2]
        # Loop over individuals
        for ind_index, individual in enumerate(current_population):
            # Plot orbit
            state_history = list(current_generation[0][ind_index][0].values())
            state_history = np.vstack(state_history)
            current_ax.plot(state_history[:, 0],
                            state_history[:, 1],
                            state_history[:, 2],
                            linewidth=0.5)
        # Prettify
        current_ax.set_xlabel('X [m]')
        current_ax.set_ylabel('Y [m]')
        current_ax.set_zlabel('Z [m]')
        current_ax.set_title(title[ax_index], y=1.0, pad=15)
    # Save figure
    fig.savefig('orbit_bundles_initial_final_gen.png', bbox_inches='tight')

    # Plot orbits of final generation divided by parameters
    fig = plt.figure(figsize=(12, 8))
    fig.suptitle('Final orbit bundle by decision variable',
                 fontweight='bold',
                 y=0.95)
    # Retrieve current population
    current_generation = simulation_output[number_of_evolutions - 1]
    # Plot Pareto fronts for all design variables
    current_population = current_generation[2]
    # Loop over decision variables
    for var in range(4):
        # Create axis
        current_ax = fig.add_subplot(2, 2, 1 + var, projection='3d')
        # Loop over individuals
        for ind_index, individual in enumerate(current_population):
            # Set plot color according to boundaries
            if individual[var] < decision_variable_range[var][0]:
                plt_color = 'r'
                label = decision_variable_symbols[var] + ' < ' + str(decision_variable_range[var][0]) + \
                        decision_variable_units[var]
            elif decision_variable_range[var][0] < individual[
                    var] < decision_variable_range[var][1]:
                plt_color = 'b'
                label = str(decision_variable_range[var][0]) + ' < ' + \
                        decision_variable_symbols[var] + \
                        ' < ' + str(decision_variable_range[var][1]) + decision_variable_units[var]
            else:
                plt_color = 'g'
                label = decision_variable_symbols[var] + ' > ' + str(decision_variable_range[var][1]) + \
                        decision_variable_units[var]

            # Plot orbit
            state_history = list(current_generation[0][ind_index][0].values())
            state_history = np.vstack(state_history)
            current_ax.plot(state_history[:, 0],
                            state_history[:, 1],
                            state_history[:, 2],
                            color=plt_color,
                            linewidth=0.5,
                            label=label)
        # Prettify
        current_ax.set_xlabel('X [m]')
        current_ax.set_ylabel('Y [m]')
        current_ax.set_zlabel('Z [m]')
        current_ax.set_title(decision_variable_names[var], y=1.0, pad=10)
        handles, decision_variable_legend = current_ax.get_legend_handles_labels(
        )
        decision_variable_legend, ids = np.unique(decision_variable_legend,
                                                  return_index=True)
        handles = [handles[i] for i in ids]
        current_ax.legend(handles,
                          decision_variable_legend,
                          loc='lower right',
                          bbox_to_anchor=(0.3, 0.6))
    # Save figure
    fig.savefig('orbit_bundle_final_gen_by_variable.png', bbox_inches='tight')

    # Show plot
    plt.show()