Trajectory optimisation example

This example shows how to optimise a trajectory using the GODOT cosmos module and the third-party open source optimisation package, pagmo/pygmo.

Pagmo/Pygmo is developed by the Advanced Concept Team (ACT) in ESTEC, and contains many freely available global and local optimisation algorithms.

Loading packages

First we load some python packages for plotting and linear algebra.

In [41]:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt

Then we load godot specific libraries and the pygmo optimisation package

In [42]:
from godot.core import tempo, util, constants
from godot.model import common
from godot import cosmos
import pygmo

Universe

Load the universe configuration and create the universe object. This is a barebones configuration with using JPL ephemeris for planets and some planetary axes models. The dynamics include Sun, Earth and Mars, and the spacecraft SEP engine is configured with fixed values for Isp and thrust.

In [43]:
util.suppressLogger()
uni_config = cosmos.util.load_yaml("universe.yml")
uni = cosmos.Universe(uni_config)

Trajectory

Load the trajectory configuration and create the trajectory object using the universe object.

The trajectory optimisation problem we are solving is an Earth to Mars transfer with solar electric propulsion (SEP). It contains two segments (separate propagations), one forward from Earth and one backward from Mars. There is a matching point in between. Each arc contains a continuous thrust arc with mandatory coast after launch and before arrival (30 days).

Transfer problem sketch

In [44]:
tra_config = cosmos.util.load_yaml("trajectory.yml")
tra = cosmos.Trajectory(uni, tra_config)

Plotting

We generate a plotting function to create visualization for our trajectory in the solar system.

In [45]:
def man_data(name):
    man = tra.getManoeuvreBook()[name]
    start = tra.point(man.start)
    end = tra.point(man.end)
    ran = tempo.EpochRange(start, end)
    grid = ran.createGrid(1.0 * tempo.SecondsInDay)
    mass = uni.evaluables.get("SC_mass")
    t = [e.mjd() for e in grid]
    r = np.asarray([uni.frames.vector3("Sun", "SC_center", "EMC", e) / constants.AU for e in grid])
    man.model.setActive(True)
    thr = np.asarray([np.linalg.norm(man.model.eval(e)) * mass.eval(e) for e in grid])
    man.model.setActive(False)
    return t, r, thr

def plot():
    tra.compute(False)
    ran = tempo.EpochRange(tempo.Epoch("2020-01-01 TDB"), tempo.Epoch("2022-01-01 TDB"))
    earth = np.asarray([uni.frames.vector3("Sun", "Earth", "EMC", e) / constants.AU for e in ran.createGrid(1.0 * tempo.SecondsInDay)])
    mars = np.asarray([uni.frames.vector3("Sun", "Mars", "EMC", e) / constants.AU for e in ran.createGrid(1.0 * tempo.SecondsInDay)])

    def pos(e):
        try:
            return uni.frames.vector3("Sun", "SC_center", "EMC", e) / constants.AU
        except:
            return [np.nan] * 3

    grid = tra.range().createGrid(1.0 * tempo.SecondsInDay)
    t = [e.mjd() for e in grid]
    thr = np.asarray([uni.evaluables.get("SC_sep_thrust").eval(e) for e in grid])
    x = np.asarray([pos(e) for e in grid])

    man1_t, man1_r, man1_thr = man_data("man1")
    man2_t, man2_r, man2_thr = man_data("man2")

    match1_ep = tra.point("match_inter_left")
    match1_val = uni.evaluables.get("SC_sep_thrust").eval(match1_ep)
    match2_ep = tra.point("match_inter_right")
    match2_val = uni.evaluables.get("SC_sep_thrust").eval(match2_ep)

    plt.figure(figsize=(8, 8))
    plt.xlabel("EMC X (AU)")
    plt.ylabel("EMC Y (AU)")
    plt.plot(earth[:, 0], earth[:, 1], "--", label="Earth")
    plt.plot(mars[:, 0], mars[:, 1], "--", label="Mars")
    plt.plot(x[:, 0], x[:, 1], "-k", linewidth=2, label="Transfer")
    plt.plot(man1_r[:, 0], man1_r[:, 1], "-r", linewidth=4, label="SEP 1")
    plt.plot(man2_r[:, 0], man2_r[:, 1], "-g", linewidth=4, label="SEP 2")
    plt.plot(x[0, 0], x[0, 1], "ok", ms=10)
    plt.plot(x[-1, 0], x[-1, 1], "ok", ms=10)
    plt.xlim([-2, 2])
    plt.ylim([-2, 2])
    plt.legend()
    plt.grid()

    plt.figure(figsize=(8, 4))
    plt.xlabel("Time (MJD)")
    plt.ylabel("SEP thrust (mN)")
    plt.plot(t, 1e6 * thr, "--k", linewidth=1, label="Max thrust")
    plt.plot([tra.range().start().mjd()], 1e6 * thr[0], "o", ms=10, label="Earth")
    plt.plot([tra.range().end().mjd()], 1e6 * thr[-1], "o", ms=10, label="Mars")
    plt.plot([match1_ep.mjd()], 1e6 * match1_val, "dk", ms=5, label="Match")
    plt.plot([match2_ep.mjd()], 1e6 * match2_val, "dk", ms=5)
    plt.plot(man1_t, 1e6 * man1_thr, "-r", linewidth=3, label="SEP 1")
    plt.plot(man2_t, 1e6 * man2_thr, "-g", linewidth=3, label="SEP 2")
    plt.xlim([tra.range().start().mjd(), tra.range().end().mjd()])
    plt.ylim([0, 1000])
    plt.legend()
    plt.grid()

We compute the initial guess of the transfer trajectory and visualize it. Immediately we can see that there is a large discontinuity at the matching point and this optimisation problem needs to be solved.

In [46]:
plot()

Summary

We add a function to summarize some results.

In [47]:
def summary():
    print(f"Escape velocity: {uni.parameters.get('Departure_SC_center_vin').getPhysicalValue():.3f} km/s")
    print(f"Launch mass:     {uni.parameters.get('Departure_SC_mass').getPhysicalValue():.1f} kg")
    print(f"Arrival mass:    {uni.parameters.get('Arrival_SC_mass').getPhysicalValue():.1f} kg")
    print(f"Total delta-V:   {uni.parameters.get('Arrival_SC_dv').getPhysicalValue():.3f} km/s")

summary()
Escape velocity: 2.000 km/s
Launch mass:     3000.0 kg
Arrival mass:    2500.0 kg
Total delta-V:   2.000 km/s

Problem

Load the problem configuration and create the problem object using the universe, a list of considered trajectories. The problem defines free variables and optionally their bounds/scales as well as additional constraints and the objective.

In this optimisation example we fix the departure/arrival dates and escape/arrival infinite velocities and only optimise the V-infinity directions, as well as the low-thrust maoneuvre directions.

In [48]:
pro_config = cosmos.util.load_yaml("problem.yml")
pro = cosmos.Problem(uni, [tra], pro_config)

Now it is time to create the pygmo problem class that simply takes ours, and define some constraint tolerances

In [49]:
problem = pygmo.problem(pro)
conTol = 1e-6
problem.c_tol = [conTol] * problem.get_nc()

We create the pygmo population to be optimised by grabbing the initial value of the free parameters

In [50]:
x0 = pro.get_x()
pop = pygmo.population(problem, 0)
pop.push_back(x0)

Finally we use the NLOPT open source package to optimise the problem with the well-known SLSQP algorithm

In [51]:
algo = pygmo.algorithm(pygmo.nlopt("slsqp"))
algo.set_verbosity(1)
pop = algo.evolve(pop)
 objevals:        objval:      violated:    viol. norm:
         1              2              9        3.53101 i
         2        1.99085              7       0.534959 i
         3        2.03368              7       0.289138 i
         4        2.06577              7       0.977688 i
         5        2.04982              7       0.398678 i
         6        2.04982              7       0.398678 i
         7        2.12662              7         1.8057 i
         8        2.06328              7       0.407508 i
         9        2.06328              7       0.407508 i
        10        2.19944              7       0.449734 i
        11        2.10293              7       0.235162 i
        12        2.10293              7       0.235162 i
        13        2.32217              7        1.01177 i
        14        2.14809              7       0.215692 i
        15        2.14809              7       0.215692 i
        16        2.47096              7       0.226017 i
        17        2.65355              7      0.0380097 i
        18        2.66329              6    0.000404354 i
        19        2.66331              0              0
        20        2.66163              1    3.85011e-06 i
        21        2.65811              1    2.20318e-05 i
        22        2.64448              6    0.000364159 i
        23        2.58323              7     0.00818726 i
        24        2.62635              6    0.000977531 i
        25        2.62635              6    0.000977531 i
        26        2.43506              7      0.0905748 i
        27         2.5984              6     0.00281889 i
        28         2.5984              6     0.00281889 i
        29        2.49422              7      0.0364578 i
        30        2.56317              7     0.00607901 i
        31        2.56317              7     0.00607901 i
        32        2.49876              7      0.0228701 i
        33        2.52808              7     0.00959196 i
        34        2.52808              7     0.00959196 i
        35        2.49521              7      0.0130303 i
        36        2.48937              6     0.00412001 i
        37        2.48218              6     0.00429743 i
        38        2.48046              6     0.00287633 i
        39         2.4814              5    0.000147535 i
        40        2.47989              6    0.000429569 i
        41        2.47341              6     0.00555177 i
        42        2.47731              6     0.00115584 i
        43        2.47731              6     0.00115584 i
        44        2.47206              6     0.00190137 i
        45        2.46589              6     0.00155319 i
        46        2.46305              6    0.000890497 i
        47        2.46328              5    0.000201059 i
        48        2.46217              5    0.000506508 i
        49        2.46201              6    0.000325498 i
        50        2.46205              3     3.4693e-05 i

 objevals:        objval:      violated:    viol. norm:
        51        2.46153              6     0.00010872 i
        52        2.46048              4    0.000171408 i
        53        2.45808              6    0.000365084 i
        54        2.45419              6     0.00094636 i
        55        2.45108              5     0.00160783 i
        56        2.45024              6    0.000982901 i
        57        2.44983              6    0.000492811 i
        58        2.44989              5    0.000123794 i
        59        2.44999              1    4.46646e-06 i
        60        2.44999              0              0
        61        2.44999              0              0
        62        2.44999              1    4.24966e-07 i
        63        2.44997              1    2.59517e-06 i
        64        2.44994              1    7.16272e-06 i
        65        2.44988              1     1.5329e-05 i
        66        2.44981              1    2.60144e-05 i
        67        2.44976              1     2.8463e-05 i
        68        2.44977              1    1.00242e-05 i
        69        2.44978              0              0
        70        2.44978              0              0
        71        2.44978              0              0
        72        2.44978              0              0
        73        2.44978              0              0
        74        2.44978              0              0
        75        2.44977              0              0
        76        2.44977              0              0
        77        2.44977              0              0
        78        2.44977              0              0
        79        2.44977              0              0
        80        2.44977              0              0
        81        2.44977              0              0
        82        2.44977              0              0
        83        2.44977              0              0

Optimisation return status: NLOPT_XTOL_REACHED (value = 4, Optimization stopped because xtol_rel or xtol_abs was reached)

We can visualize the final converged trajectory.

In [52]:
plot()
summary()
Escape velocity: 2.000 km/s
Launch mass:     3000.0 kg
Arrival mass:    2793.3 kg
Total delta-V:   2.450 km/s

Finally, we can save the optimised and updated trajectory configuration

In [53]:
up = tra.applyParameterChanges()
tra_config = cosmos.util.deep_update(tra_config, up)
cosmos.util.save_yaml(tra_config, "trajectory_out.yml")

Custom thrust model

We will create a new universe to show how easy it is to implement a new thrust model.

In [54]:
uni_config = cosmos.util.load_yaml("universe.yml")
uni = cosmos.Universe(uni_config)

Our "complex" thrust model will replace the fixed thrust model with one that depends on the distance from the Sun. We will use 500 mN thrust available at 1 AU, and scale it with the inverse square of the distance from the Sun.

In [55]:
class ThrustModel(common.ScalarTimeEvaluable):
    def __init__(self, uni, thrust_at_1AU):
        super().__init__()
        self.uni = uni
        self.thrust_at_1AU = thrust_at_1AU
    
    def eval(self, e):
        dist = self.uni.frames.distance("Sun", "SC_center", e) / constants.AU
        thr = self.thrust_at_1AU / (dist * dist)
        return thr

thrust_at_1AU = 500e-6  # in mN
thrust_model = ThrustModel(uni, thrust_at_1AU)
uni.evaluables.replace("SC_sep_thrust", thrust_model)

As initial guess, we us the converged trajectory using the fixed thrust model.

In [56]:
tra_config = cosmos.util.load_yaml("trajectory_out.yml")
tra = cosmos.Trajectory(uni, tra_config)

We visualize the initial guess which uses the new thrust model. We expect to see some discontinuities as we updated the model.

In [57]:
plot()

Finally, we optimise the trajectory.

In [58]:
pro_config = cosmos.util.load_yaml("problem.yml")
pro = cosmos.Problem(uni, [tra], pro_config)
problem = pygmo.problem(pro)
conTol = 1e-6
problem.c_tol = [conTol] * problem.get_nc()
x0 = pro.get_x()
pop = pygmo.population(problem, 0)
pop.push_back(x0)
algo = pygmo.algorithm(pygmo.nlopt("slsqp"))
algo.set_verbosity(1)
pop = algo.evolve(pop)
 objevals:        objval:      violated:    viol. norm:
         1        2.44977              8        1.62386 i
         2        2.46644              8       0.125902 i
         3        2.47893              8      0.0116075 i
         4        2.48297              7    0.000435743 i
         5        2.48305              0              0
         6        2.48292              0              0
         7        2.48227              1    7.61039e-06 i
         8        2.47907              5    0.000215609 i
         9        2.46493              8     0.00514449 i
        10        2.47331              7    0.000981576 i
        11        2.47331              7    0.000981576 i
        12        2.45379              8      0.0154411 i
        13        2.46787              8     0.00191033 i
        14        2.46787              8     0.00191033 i
        15        2.46423              8     0.00166728 i
        16        2.46298              7     0.00113336 i
        17        2.46328              3    0.000220485 i
        18        2.46331              3    3.75387e-05 i
        19        2.46312              5    6.60529e-05 i
        20        2.46271              6    0.000123223 i
        21        2.46177              7    0.000278053 i
        22        2.46026              5    0.000513978 i
        23        2.45922              7    0.000571451 i
        24        2.45914              5    0.000325546 i
        25        2.45935              1    1.46394e-05 i
        26        2.45934              1    3.76703e-06 i
        27        2.45928              1      1.941e-05 i
        28        2.45921              1     2.2408e-05 i
        29        2.45899              4    8.70987e-05 i
        30        2.45876              4     0.00010686 i
        31        2.45863              5    7.07844e-05 i
        32        2.45865              3    1.98953e-05 i
        33        2.45866              1    3.02225e-06 i
        34        2.45866              1    4.68158e-08 i
        35        2.45864              1    3.24429e-06 i
        36        2.45862              1    5.71624e-06 i
        37        2.45856              3    1.43695e-05 i
        38        2.45852              3    2.57182e-05 i
        39        2.45851              3    1.68632e-05 i
        40        2.45852              1    1.87783e-06 i
        41        2.45852              0              0
        42        2.45852              0              0
        43        2.45851              0              0
        44         2.4585              1    1.22327e-06 i
        45        2.45849              1    2.36813e-06 i
        46        2.45849              1    1.54079e-06 i
        47        2.45849              1    8.10162e-09 i
        48        2.45849              0              0
        49        2.45849              0              0
        50        2.45849              0              0

 objevals:        objval:      violated:    viol. norm:
        51        2.45849              0              0
        52        2.45849              0              0
        53        2.45849              0              0
        54        2.45849              0              0

Optimisation return status: NLOPT_XTOL_REACHED (value = 4, Optimization stopped because xtol_rel or xtol_abs was reached)

And visualize the new solution with the inverse square thrust model.

In [59]:
plot()
summary()
Escape velocity: 2.000 km/s
Launch mass:     3000.0 kg
Arrival mass:    2792.6 kg
Total delta-V:   2.458 km/s

We save this trajectory as well.

In [60]:
up = tra.applyParameterChanges()
tra_config = cosmos.util.deep_update(tra_config, up)
cosmos.util.save_yaml(tra_config, 'trajectory_out2.yml')

Using launcher performance model

In the following we further complicate the problem by replacing the fixed departure boundary conditions (escape velocity, mass) with one that links them through the launcher performance model.

We use a simple linear model in C3 as in most cases that is quite close to reality.

In [61]:
def launcher_model(vinf):
    c3 = vinf * vinf
    coef0 = 4000.0
    coef1 = -250.0
    mass = coef0 + coef1 * c3
    return mass

vin = np.linspace(0, 3)
mass = [launcher_model(x) for x in vin]
plt.plot(vin, mass, lw=2)
plt.grid()
plt.xlabel("Escape velocity (km/s)")
plt.ylabel("Injected mass (kg)")
plt.show()

We recreate the same universe as before including the custom thrust model

In [62]:
uni_config = cosmos.util.load_yaml("universe.yml")
uni = cosmos.Universe(uni_config)
thrust_model = ThrustModel(uni, thrust_at_1AU)
uni.evaluables.replace("SC_sep_thrust", thrust_model)

We load the converged trajectory from the previous step.

In [63]:
tra_config = cosmos.util.load_yaml("trajectory_out2.yml")
tra = cosmos.Trajectory(uni, tra_config)

In addition, now we add an evaluable that defines the constraint defect of the launcher performance. We add it to the universe evaluables so that it is avaiable to be used later in the problem definition.

In [64]:
class LauncherConstraint(common.ScalarTimeEvaluable):
    def __init__(self, uni):
        super().__init__()
        self.uni = uni
    
    def eval(self, e):
        mass = self.uni.parameters.get("Departure_SC_mass").eval(e)
        vinf = self.uni.parameters.get("Departure_SC_center_vin").eval(e)
        out = launcher_model(vinf) - mass
        return out

launcher_con = LauncherConstraint(uni)
uni.evaluables.add("launcher_con", launcher_con)

And now we load a different problem configuration, that allows the initial escape velocity and mass to be optimised, as well as defining the additional constraint. Moreover, we need to switch the objective from minimising the delta-V to optimising the final mass, as now that is our final merit value.

In [65]:
pro_config = cosmos.util.load_yaml("problem2.yml")
pro = cosmos.Problem(uni, [tra], pro_config)

# We could also add constraint directly via the problem interface
# pro.addConstraint(1e-3 * launcher_con)

Carry out the new optimisation.

In [66]:
def optimise():
    problem = pygmo.problem(pro)
    conTol = 1e-6
    problem.c_tol = [conTol] * problem.get_nc()
    x0 = pro.get_x()
    pop = pygmo.population(problem, 0)
    pop.push_back(x0)
    algo = pygmo.algorithm(pygmo.nlopt("slsqp"))
    algo.set_verbosity(1)
    pop = algo.evolve(pop)

optimise()
 objevals:        objval:      violated:    viol. norm:
         1       -2.79263              0              0
         2       -2.79268              0              0
         3       -2.79294              0              0
         4       -2.79422              0              0
         5       -2.80061              6    2.19252e-05 i
         6       -2.83208              8     0.00057912 i
         7       -2.93869              9     0.00694164 i
         8       -2.87284              9     0.00138033 i
         9       -2.87284              9     0.00138033 i
        10         -2.943              9     0.00332132 i
        11       -2.98671              9     0.00158214 i
        12       -2.98764              4    1.96134e-05 i
        13        -2.9916              6    7.20547e-05 i
        14       -2.99856              6    0.000246647 i
        15       -3.01057              8    0.000918376 i
        16       -3.04357              9     0.00808055 i
        17       -3.02735              8     0.00254892 i
        18       -3.02735              8     0.00254892 i
        19       -3.03766              8     0.00125714 i
        20       -3.05851              8      0.0052672 i
        21       -3.06245              8     0.00088752 i
        22       -3.07161              8     0.00226054 i
        23       -3.07654              8     0.00145981 i
        24       -3.07604              1    4.33122e-07 i
        25         -3.077              2     2.5974e-05 i
        26       -3.08158              8    0.000637582 i
        27       -3.09006              9     0.00289823 i
        28       -3.08907              1    2.82512e-06 i
        29       -3.08911              1     1.4259e-07 i
        30       -3.08931              1    2.63655e-05 i
        31        -3.0901              6    0.000550174 i
        32       -3.09021              6    0.000407988 i
        33       -3.09005              0              0
        34       -3.09005              0              0
        35       -3.09006              0              0
        36        -3.0901              0              0
        37       -3.09029              7    1.19843e-05 i
        38       -3.09047              8    4.42366e-05 i
        39       -3.09036              0              0
        40       -3.09036              0              0
        41       -3.09036              0              0
        42       -3.09036              0              0
        43       -3.09037              0              0
        44       -3.09037              1    1.00018e-06 i
        45       -3.09037              1    2.71476e-06 i
        46       -3.09038              1    4.99154e-07 i
        47       -3.09037              0              0
        48       -3.09037              0              0
        49       -3.09037              0              0
        50       -3.09037              0              0

 objevals:        objval:      violated:    viol. norm:
        51       -3.09037              0              0

Optimisation return status: NLOPT_XTOL_REACHED (value = 4, Optimization stopped because xtol_rel or xtol_abs was reached)

And plot the converged trajectory. We can see that the thrust arcs are not saturating the transfer, as the mass is pushed to its limit to optimise the final mass. This results in lower escape velocity and lower thrust-to-mass ratio, so the optimiser increases the thrust act durations to their limit.

In [67]:
plot()
summary()
Escape velocity: 1.606 km/s
Launch mass:     3355.6 kg
Arrival mass:    3090.4 kg
Total delta-V:   2.826 km/s

Save resulting trajectory.

In [68]:
up = tra.applyParameterChanges()
tra_config = cosmos.util.deep_update(tra_config, up)
cosmos.util.save_yaml(tra_config, "trajectory_out3.yml")

Mass dependent thrust

In the final iteration of our example, we will include a system design decision. The thrust available at 1 AU will depend on the initial wet mass of the SC. This can be thought as optimising the number of solar arrays but taking into account their additional mass. In most cases this decision variable would be discrete, but here we just use a continuous variable for simplicity.

We define the model that computes the avaialble thrust at 1 AU from the wet mass. We include some 2nd order term to account for additional dry mass needed to support the more bulky power infrastructure.

In [78]:
def thrust_at_1AU_from_mass(mass):
    m0 = 2000.0
    dm = mass - m0
    coef1 = 0.5e-6
    coef2 = -8e-11
    return coef1 * dm + coef2 * dm * dm


mass = np.linspace(2000, 5000)
thr = np.asarray([thrust_at_1AU_from_mass(x) for x in mass])
plt.plot(mass, 1e6 * thr, lw=2)
plt.grid()
plt.xlabel("Wet mass (kg)")
plt.ylabel("Thrust (mN)")
plt.plot()
plt.show()

No we create the thrust model that uses this new assumption.

In [70]:
class ComplexThrustModel(common.ScalarTimeEvaluable):
    def __init__(self, uni):
        super().__init__()
        self.uni = uni
    
    def eval(self, e):
        dist = self.uni.frames.distance("Sun", "SC_center", e) / constants.AU
        wet_mass = self.uni.parameters.get("Departure_SC_mass").eval(e)
        thr_at_1AU = thrust_at_1AU_from_mass(wet_mass)
        thr = thr_at_1AU / (dist * dist)
        return thr

Similarly set up the universe as before.

In [71]:
uni_config = cosmos.util.load_yaml("universe.yml")
uni = cosmos.Universe(uni_config)

thrust_model = ComplexThrustModel(uni)
uni.evaluables.replace("SC_sep_thrust", thrust_model)

launcher_con = LauncherConstraint(uni)
uni.evaluables.add("launcher_con", launcher_con)

As well as loading the previous converged trajectory.

In [72]:
tra_config = cosmos.util.load_yaml("trajectory_out3.yml")
tra = cosmos.Trajectory(uni, tra_config)

And plotting the initial guess, noticing the discontinuity due to the different thrust model.

In [73]:
plot()

We can reuse the same problem configuration from before.

In [74]:
pro_config = cosmos.util.load_yaml('problem2.yml')
pro = cosmos.Problem(uni, [tra], pro_config)

And optimise the problem.

In [75]:
uni.parameters.get("match_inter_left_dt").setScaledValue(1e-9)
optimise()
 objevals:        objval:      violated:    viol. norm:
         1       -3.09037              8        0.32473 i
         2       -3.08955              8    0.000875728 i
         3       -3.08957              1    4.79023e-06 i
         4       -3.08962              0              0
         5        -3.0899              0              0
         6       -3.09128              3    1.06193e-05 i
         7       -3.09815              9    0.000303933 i
         8        -3.1313              9     0.00780678 i
         9       -3.12834              6    6.88013e-05 i
        10       -3.12845              0              0
        11       -3.12915              3    1.44567e-05 i
        12       -3.13248              9    0.000365342 i
        13       -3.13459              7    0.000226372 i
        14       -3.13447              0              0
        15       -3.13462              1    8.05656e-07 i
        16       -3.13536              4    4.21106e-05 i
        17       -3.13844              7    0.000900541 i
        18       -3.13986              8     0.00121596 i
        19       -3.13905              3    1.33368e-05 i
        20       -3.13922              1    1.21175e-05 i
        21       -3.14017              6    0.000122595 i
        22       -3.14531              9     0.00185407 i
        23       -3.14838              9    0.000722637 i
        24       -3.16488              9     0.00585341 i
        25       -3.17292              9     0.00567678 i
        26       -3.17387              9     0.00210197 i
        27       -3.17476              9     0.00105395 i
        28       -3.17548              6    0.000593233 i
        29       -3.17624              8    0.000384458 i
        30       -3.17681              7    0.000232792 i
        31       -3.17767              7    0.000199819 i
        32       -3.18087              9    0.000826847 i
        33        -3.1867              9     0.00169058 i
        34       -3.19619              8     0.00394531 i
        35       -3.20122              9     0.00402934 i
        36       -3.20135              9     0.00127631 i
        37       -3.20113              6    4.36526e-05 i
        38       -3.20225              9    0.000398283 i
        39       -3.20237              7    0.000181988 i
        40       -3.20258              3    9.83516e-05 i
        41       -3.20329              7    0.000318385 i
        42       -3.20447              7     0.00061298 i
        43       -3.20618              9    0.000937515 i
        44       -3.20727              8    0.000667829 i
        45       -3.20718              8    0.000102239 i
        46       -3.20722              7    7.99436e-05 i
        47       -3.20719              1    2.04472e-05 i
        48       -3.20718              1    2.19503e-07 i
        49       -3.20718              0              0
        50       -3.20718              0              0

 objevals:        objval:      violated:    viol. norm:
        51       -3.20718              0              0
        52       -3.20718              0              0
        53       -3.20718              0              0
        54       -3.20718              0              0
        55       -3.20718              0              0
        56       -3.20718              0              0

Optimisation return status: NLOPT_XTOL_REACHED (value = 4, Optimization stopped because xtol_rel or xtol_abs was reached)

Finally, plot the solution.

In [76]:
plot()
summary()
Escape velocity: 1.398 km/s
Launch mass:     3511.7 kg
Arrival mass:    3207.2 kg
Total delta-V:   3.113 km/s

And save the final trajectory.

In [77]:
up = tra.applyParameterChanges()
tra_config = cosmos.util.deep_update(tra_config, up)
cosmos.util.save_yaml(tra_config, 'trajectory_out4.yml')