The modified SSTO code is for just one phase (just for proper comparison)
import matplotlib.pyplot as plt
import openmdao.api as om
import dymos as dm
from dymos.examples.plotting import plot_results
p = om.Problem(model=om.Group())
p.driver = om.pyOptSparseDriver()
p.driver.options['optimizer'] = 'IPOPT'
p.driver.opt_settings['derivative_test'] = 'first-order'
p.driver.opt_settings['mu_strategy'] = 'adaptive'
p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas'
p.driver.opt_settings['bound_mult_init_method'] = 'mu-based'
p.driver.opt_settings['mu_init'] = 0.01
p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based'
p.driver.opt_settings['max_iter']= 1000
p.driver.opt_settings['tol']= 1e-2
p.driver.declare_coloring()
from launch_vehicle_ode import LaunchVehicleODE
traj = dm.Trajectory()
phase0 = dm.Phase(ode_class=LaunchVehicleODE, ode_init_kwargs={'CD': 1.0, 'S': 0.2},
transcription=dm.Radau(num_segments=10, order=3, compressed=False))
traj.add_phase('phase0', phase0)
p.model.add_subsystem('traj', traj)
#
phase0.set_time_options( fix_initial=True, duration_bounds=(1, 500))
phase0.add_state('x', fix_initial=True, rate_source='xdot', fix_final=True, lower = 0.0)
phase0.add_state('y', fix_initial=True,rate_source='ydot', fix_final=True, lower = 0.0)
phase0.add_state('vx', fix_initial=True,rate_source='vxdot', fix_final=True, lower = 0.0)
phase0.add_state('vy', fix_initial=True, rate_source='vydot',fix_final=True, lower = 0.0)
phase0.add_state('m', fix_final=True,rate_source='mdot', lower = 500.0)
phase0.add_control('theta', fix_initial=True, units='deg', lower=-90.0, upper=90.0, targets=['theta'])
phase0.add_parameter('thrust', val = 25000.0, opt=False, targets=['thrust'])
#
phase0.add_objective('time', loc='final')
p.model.linear_solver = om.DirectSolver()
#
p.setup(check=True)
p.set_val('traj.phase0.t_initial', 0.0)
p.set_val('traj.phase0.t_duration', 150.0)
p.set_val('traj.phase0.states:x', phase0.interp('x', [0, 80.0E3]))
p.set_val('traj.phase0.states:y', phase0.interp('y', [0, 50.0E3]))
p.set_val('traj.phase0.states:vx', phase0.interp('vx', [0.0, 2000.0]))
p.set_val('traj.phase0.states:vy', phase0.interp('vy', [10.0, 0.0]))
p.set_val('traj.phase0.states:m', phase0.interp('m', [1500.0, 500.0]))
p.set_val('traj.phase0.controls:theta', phase0.interp('theta', [89.0, 0.0]))
p.set_val('traj.phase0.parameters:thrust', 25000, units='N')
#
phase0.set_refine_options(tol=1.0E-2)
dm.run_problem(p, simulate=True, run_driver=True, refine_iteration_limit=1)
#POST
sol = om.CaseReader('dymos_solution.db').get_case('final')
sim = om.CaseReader('dymos_simulation.db').get_case('final')
plot_results([('traj.phase0.timeseries.x', 'traj.phase0.timeseries.y',
'range (m)', 'altitude (m)'),
('traj.phase0.timeseries.time', 'traj.phase0.timeseries.y',
'time (s)', 'altitude (m)'),
('traj.phase0.timeseries.time', 'traj.phase0.timeseries.vx',
'time (s)', 'xspeed (m/s)'),
('traj.phase0.timeseries.time', 'traj.phase0.timeseries.vy',
'time (s)', 'yspeed (m/s)'),
('traj.phase0.timeseries.time', 'traj.phase0.timeseries.theta',
'time (s)', 'theta (deg)'),
('traj.phase0.timeseries.time', 'traj.phase0.timeseries.m',
'time (s)', 'm (kg)'),
],
title='Supersonic Minimum Time-to-Climb Solution',
p_sol=sol, p_sim=sim)
plt.show()
The code for two phases is just a split it two and link them:
import matplotlib.pyplot as plt
import openmdao.api as om
import dymos as dm
from dymos.examples.plotting import plot_results
p = om.Problem(model=om.Group())
p.driver = om.pyOptSparseDriver()
p.driver.options['optimizer'] = 'IPOPT'
p.driver.opt_settings['derivative_test'] = 'first-order'
p.driver.opt_settings['mu_strategy'] = 'adaptive'
p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas'
p.driver.opt_settings['bound_mult_init_method'] = 'mu-based'
p.driver.opt_settings['mu_init'] = 0.01
p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based'
p.driver.opt_settings['max_iter']= 1000
p.driver.opt_settings['tol']= 1e-2
p.driver.declare_coloring()
from launch_vehicle_ode import LaunchVehicleODE
traj = dm.Trajectory()
phase0 = dm.Phase(ode_class=LaunchVehicleODE, ode_init_kwargs={'CD': 1.0, 'S': 0.2},
transcription=dm.Radau(num_segments=10, order=3, compressed=False))
phase1 = dm.Phase(ode_class=LaunchVehicleODE, ode_init_kwargs={'CD': 1.0, 'S': 0.2},
transcription=dm.Radau(num_segments=10, order=3, compressed=False))
traj.add_phase('phase0', phase0)
traj.add_phase('phase1', phase1)
p.model.add_subsystem('traj', traj)
phase0.set_time_options(fix_initial=True, duration_bounds=(1, 500))
phase0.add_state('x', fix_initial=True, rate_source='xdot', lower = 0.0)
phase0.add_state('y', fix_initial=True,rate_source='ydot', lower = 0.0)
phase0.add_state('vx', fix_initial=True,rate_source='vxdot', lower = 0.0)
phase0.add_state('vy', fix_initial=True, rate_source='vydot', lower = 0.0)
phase0.add_state('m', rate_source='mdot', lower=1100.0)
phase0.add_control('theta', fix_initial=True, units='deg', lower=-90.0, upper=90.0, targets=['theta'])
phase0.add_parameter('thrust', val = 25000.0, opt=False, targets=['thrust'])
phase1.set_time_options(duration_bounds=(1, 500))
phase1.add_state('x', rate_source='xdot', fix_final=True, lower = 0.0)
phase1.add_state('y', rate_source='ydot', fix_final=True, lower = 0.0)
phase1.add_state('vx',rate_source='vxdot', fix_final=True, lower = 0.0)
phase1.add_state('vy',rate_source='vydot', fix_final=True, lower = 0.0)
phase1.add_state('m', rate_source='mdot', fix_final=True, lower = 500.0)
phase1.add_control('theta', units='deg', lower=-90.0, upper=90.0, targets=['theta'])
phase1.add_parameter('thrust', val = 25000.0, opt=False, targets=['thrust'])
#
# Set the options for our constraints and objective
#
phase1.add_objective('time', loc='final')
p.model.linear_solver = om.DirectSolver()
p.setup(check=True)
p.set_val('traj.phase0.t_initial', 0.0)
p.set_val('traj.phase0.t_duration', 150.0)
p.set_val('traj.phase0.states:x', phase0.interp('x', [0, 50.0E3]))
p.set_val('traj.phase0.states:y', phase0.interp('y', [0, 45.0E3]))
p.set_val('traj.phase0.states:vx', phase0.interp('vx', [0.0, 1000.0]))
p.set_val('traj.phase0.states:vy', phase0.interp('vy', [10.0, 0.0]))
p.set_val('traj.phase0.states:m', phase0.interp('m', [1500.0, 1100.0]))
p.set_val('traj.phase0.controls:theta', phase0.interp('theta', [89.0, 0.0]))
p.set_val('traj.phase0.parameters:thrust', 25000, units='N')
p.set_val('traj.phase1.t_initial', 100.0)
p.set_val('traj.phase1.t_duration', 150.0)
p.set_val('traj.phase1.states:x', phase1.interp('x', [50.0E3, 80.0E3]))
p.set_val('traj.phase1.states:y', phase1.interp('y', [45.0E3, 50.0E3]))
p.set_val('traj.phase1.states:vx', phase1.interp('vx', [1000.0, 2000.0]))
p.set_val('traj.phase1.states:vy', phase1.interp('vy', [0.0, 0.0]))
p.set_val('traj.phase1.states:m', phase1.interp('m', [1100.0, 500.0]))
p.set_val('traj.phase1.controls:theta', phase1.interp('theta', [0.0, 0.0]))
p.set_val('traj.phase1.parameters:thrust', 25000, units='N')
traj.link_phases(phases=['phase0', 'phase1'], vars=['*'], connected=True)
phase0.set_refine_options(tol=1.0E-2)
phase1.set_refine_options(tol=1.0E-2)
dm.run_problem(p, simulate=False, run_driver=True, refine_iteration_limit=1,
simulate_kwargs={'times_per_seg' : 100, 'rtol': 1.0E-2})
p.driver.scaling_report()
sol = om.CaseReader('dymos_solution.db').get_case('final')
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(10, 6))
time_imp = {'phase0': p.get_val('traj.phase0.timeseries.time'),
'phase1': p.get_val('traj.phase1.timeseries.time')}
r_imp = {'phase0': p.get_val('traj.phase0.timeseries.x'),
'phase1': p.get_val('traj.phase1.timeseries.x')}
h_imp = {'phase0': p.get_val('traj.phase0.timeseries.y'),
'phase1': p.get_val('traj.phase1.timeseries.y')}
axes.plot(r_imp['phase0'], h_imp['phase0'], 'bo')
axes.plot(r_imp['phase1'], h_imp['phase1'], 'ro')
axes.set_xlabel('range (m)')
axes.set_ylabel('altitude (m)')
axes.grid(True)
fig, axes = plt.subplots(nrows=6, ncols=1, figsize=(10, 6))
states = ['x', 'y', 'vx', 'vy', 'm','theta']
for i, state in enumerate(states):
x_imp = {'phase0': sol.get_val(f'traj.phase0.timeseries.{state}'),
'phase1': sol.get_val(f'traj.phase1.timeseries.{state}')}
axes[i].set_ylabel(state)
axes[i].grid(True)
axes[i].plot(time_imp['phase0'], x_imp['phase0'], 'bo')
axes[i].plot(time_imp['phase1'], x_imp['phase1'], 'ro')
plt.show()
Linking the phases with connected=True is ok and I got convergence.
Nevertheless my purpose is a "jump" in mass to simulate the drop of the burned first stage in this two stage rocket. So I am trying to
link them as (also changing the mass initial conditions):
traj.link_phases(phases=['phase0', 'phase1'], vars=['time','x','y','vx','vy'], connected=True)
traj.add_linkage_constraint('phase0', 'phase1', 'm', 'm', loc_a='final', loc_b='initial',
mult_a=1.0, mult_b=-1.0, equals = 200.0, units='kg', connected=True)
Although it seems to interpret the right mass "jump" I cannot get it converged. I would like to remove the "connected" setting in the mass (it is now fully continuous as expected) but either I got no convergence and/or negative times in the second phase, or the plotting error appears.
For example I cannot converge this try:
import matplotlib.pyplot as plt
import openmdao.api as om
import dymos as dm
from dymos.examples.plotting import plot_results
p = om.Problem(model=om.Group())
p.driver = om.pyOptSparseDriver()
p.driver.options['optimizer'] = 'IPOPT'
p.driver.opt_settings['derivative_test'] = 'first-order'
p.driver.opt_settings['mu_strategy'] = 'adaptive'
p.driver.opt_settings['alpha_for_y'] = 'safer-min-dual-infeas'
p.driver.opt_settings['bound_mult_init_method'] = 'mu-based'
p.driver.opt_settings['mu_init'] = 0.01
p.driver.opt_settings['nlp_scaling_method'] = 'gradient-based'
p.driver.opt_settings['max_iter']= 1000
p.driver.declare_coloring()
from launch_vehicle_ode import LaunchVehicleODE
traj = dm.Trajectory()
phase0 = dm.Phase(ode_class=LaunchVehicleODE, ode_init_kwargs={'CD': 1.0, 'S': 0.2},
transcription=dm.Radau(num_segments=10, order=3, compressed=False))
phase1 = dm.Phase(ode_class=LaunchVehicleODE, ode_init_kwargs={'CD': 1.0, 'S': 0.2},
transcription=dm.Radau(num_segments=10, order=3, compressed=False))
traj.add_phase('phase0', phase0)
traj.add_phase('phase1', phase1)
p.model.add_subsystem('traj', traj)
phase0.set_time_options(fix_initial=True, duration_bounds=(1, 500))
phase0.add_state('x', fix_initial=True, rate_source='xdot', lower = 0.0, ref=1.0E5, defect_ref=1.0E4)
phase0.add_state('y', fix_initial=True,rate_source='ydot', lower = 0.0, ref=1.0E5, defect_ref=1.0E4)
phase0.add_state('vx', fix_initial=True,rate_source='vxdot', lower = 0.0, ref=1.0E3, defect_ref=1.0E3)
phase0.add_state('vy', fix_initial=True, rate_source='vydot', lower = 0.0, ref=1.0E3, defect_ref=1.0E3)
phase0.add_state('m', rate_source='mdot', lower=1100.0, ref=1.0E3, defect_ref=1.0E2)
phase0.add_control('theta', fix_initial=True, units='deg', lower=-90.0, upper=90.0, targets=['theta'])
phase0.add_parameter('thrust', val = 25000.0, opt=False, targets=['thrust'])
phase1.set_time_options(duration_bounds=(1, 500))
phase1.add_state('x', rate_source='xdot', fix_final=True, lower = 0.0, ref=1.0E5, defect_ref=1.0E4)
phase1.add_state('y', rate_source='ydot', fix_final=True, lower = 0.0, ref=1.0E5, defect_ref=1.0E4)
phase1.add_state('vx',rate_source='vxdot', fix_final=True, lower = 0.0, ref=1.0E3, defect_ref=1.0E3)
phase1.add_state('vy',rate_source='vydot', fix_final=True, lower = 0.0, ref=1.0E3, defect_ref=1.0E3)
phase1.add_state('m', rate_source='mdot', fix_final=True, upper = 900.0, ref=1.0E3, defect_ref=1.0E2)
phase1.add_control('theta', units='deg', lower=-90.0, upper=90.0, targets=['theta'])
phase1.add_parameter('thrust', val = 25000.0, opt=False, targets=['thrust'])
phase1.add_objective('time', loc='final')
p.model.linear_solver = om.DirectSolver()
p.setup(check=True)
phase0.set_time_val(initial=0.0, duration=150.0)
phase0.set_state_val('x', [0, 50.0E3])
phase0.set_state_val('y', [0, 45.0E3])
phase0.set_state_val('vx', [0.0, 1000.0])
phase0.set_state_val('vy', [10.0, 0.0])
phase0.set_state_val('m', [1500.0, 1100.0])
phase0.set_control_val('theta', [89.0, 0.0],units='deg')
phase0.set_parameter_val('thrust', 25000.0, units='N')
phase1.set_time_val(initial=0.0, duration=150.0)
phase1.set_state_val('x', [50.0E3, 80.0E3])
phase1.set_state_val('y', [45.0E3, 50.0E3])
phase1.set_state_val('vx', [1000.0, 2000.0])
phase1.set_state_val('vy', [0.0, 0.0])
phase1.set_state_val('m', [900.0, 400.0])
phase1.set_control_val('theta', [0.0, 0.0],units='deg')
phase1.set_parameter_val('thrust', 25000.0, units='N')
traj.link_phases(phases=['phase0', 'phase1'], vars=['time','x','y','vx','vy'], connected=True)
traj.add_linkage_constraint('phase0', 'phase1', 'm', 'm', loc_a='final', loc_b='initial',
mult_a=1.0, mult_b=-1.0, equals = 200.0, units='kg', ref=1.0E3, connected=True)
dm.run_problem(p, simulate=False, run_driver=True, refine_iteration_limit=5,
simulate_kwargs={'times_per_seg' : 100, 'rtol': 1.0E-3})
p.driver.scaling_report()
sol = om.CaseReader('dymos_solution.db').get_case('final')
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(10, 6))
time_imp = {'phase0': p.get_val('traj.phase0.timeseries.time'),
'phase1': p.get_val('traj.phase1.timeseries.time')}
r_imp = {'phase0': p.get_val('traj.phase0.timeseries.x'),
'phase1': p.get_val('traj.phase1.timeseries.x')}
h_imp = {'phase0': p.get_val('traj.phase0.timeseries.y'),
'phase1': p.get_val('traj.phase1.timeseries.y')}
axes.plot(r_imp['phase0'], h_imp['phase0'], 'bo')
axes.plot(r_imp['phase1'], h_imp['phase1'], 'ro')
axes.set_xlabel('range (m)')
axes.set_ylabel('altitude (m)')
axes.grid(True)
fig, axes = plt.subplots(nrows=6, ncols=1, figsize=(10, 6))
states = ['x', 'y', 'vx', 'vy', 'm','theta']
for i, state in enumerate(states):
x_imp = {'phase0': sol.get_val(f'traj.phase0.timeseries.{state}'),
'phase1': sol.get_val(f'traj.phase1.timeseries.{state}')}
axes[i].set_ylabel(state)
axes[i].grid(True)
axes[i].plot(time_imp['phase0'], x_imp['phase0'], 'bo')
axes[i].plot(time_imp['phase1'], x_imp['phase1'], 'ro')
plt.show()
I am puzzled about why appears to be such a difficult problem. Any clues?
thank you