def test_brachistochrone_for_docs_radau(self): from openmdao.api import Problem, Group, ScipyOptimizeDriver, DirectSolver, SqliteRecorder from openmdao.utils.assert_utils import assert_rel_error import dymos as dm from dymos.examples.plotting import plot_results from dymos.examples.brachistochrone import BrachistochroneODE # # Initialize the Problem and the optimization driver # p = Problem(model=Group()) p.driver = ScipyOptimizeDriver() p.driver.options['dynamic_simul_derivs'] = True # # Create a trajectory and add a phase to it # traj = p.model.add_subsystem('traj', dm.Trajectory()) phase = traj.add_phase( 'phase0', dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments=10))) # # Set the variables # phase.set_time_options(initial_bounds=(0, 0), duration_bounds=(.5, 10)) phase.set_state_options('x', fix_initial=True, fix_final=True) phase.set_state_options('y', fix_initial=True, fix_final=True) phase.set_state_options('v', fix_initial=True) phase.add_control('theta', units='deg', lower=0.01, upper=179.9) phase.add_design_parameter('g', units='m/s**2', opt=False, val=9.80665) # # Minimize time at the end of the phase # phase.add_objective('time', loc='final', scaler=10) p.model.linear_solver = DirectSolver() # # Setup the Problem # p.setup() # # Set the initial values # p['traj.phase0.t_initial'] = 0.0 p['traj.phase0.t_duration'] = 2.0 p['traj.phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['traj.phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['traj.phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['traj.phase0.controls:theta'] = phase.interpolate( ys=[5, 100.5], nodes='control_input') # # Solve for the optimal trajectory # p.run_driver() # Test the results assert_rel_error(self, p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) # Generate the explicitly simulated trajectory exp_out = traj.simulate() plot_results( [('traj.phase0.timeseries.states:x', 'traj.phase0.timeseries.states:y', 'x (m)', 'y (m)'), ('traj.phase0.timeseries.time', 'traj.phase0.timeseries.controls:theta', 'time (s)', 'theta (deg)')], title='Brachistochrone Solution\nRadau Pseudospectral Method', p_sol=p, p_sim=exp_out) plt.show()
def flying_robot_direct_collocation(transcription='gauss-lobatto', compressed=True): p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() if transcription == 'gauss-lobatto': t = dm.GaussLobatto(num_segments=8, order=5, compressed=compressed) elif transcription == "radau-ps": t = dm.Radau(num_segments=8, order=5, compressed=compressed) else: raise ValueError('invalid transcription') traj = p.model.add_subsystem('traj', dm.Trajectory()) phase = traj.add_phase('phase0', dm.Phase(ode_class=FlyingRobotODE, transcription=t)) phase.set_time_options(fix_initial=True, fix_duration=False, duration_bounds=(0.1, 1E4), units='s') phase.add_state('x', shape=(2, ), fix_initial=True, fix_final=True, rate_source='v', units='m') phase.add_state('v', shape=(2, ), fix_initial=True, fix_final=True, rate_source='u', units='m/s') phase.add_state('J', fix_initial=True, fix_final=False, rate_source='u_mag2', units='m**2/s**3') phase.add_control('u', units='m/s**2', shape=(2, ), scaler=0.01, continuity=False, rate_continuity=False, rate2_continuity=False, lower=-1, upper=1) # Minimize the control effort phase.add_objective('time', loc='final') p.model.linear_solver = om.DirectSolver() p.setup(check=True) p['traj.phase0.t_initial'] = 0.0 p['traj.phase0.t_duration'] = 1.0 p['traj.phase0.states:x'] = phase.interp('x', [[0.0, 0.0], [-100.0, 100.0]]) p['traj.phase0.states:v'] = phase.interp('v', [[0.0, 0.0], [0.0, 0.0]]) p['traj.phase0.controls:u'] = phase.interp('u', [[1, 1], [-1, -1]]) p.run_driver() return p
def test_brachistochrone_for_docs_coloring_demo_solve_segments(self): import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm from dymos.examples.plotting import plot_results from dymos.examples.brachistochrone import BrachistochroneODE # # Initialize the Problem and the optimization driver # p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver(optimizer='IPOPT') p.driver.opt_settings['print_level'] = 4 # p.driver.declare_coloring() # # Create a trajectory and add a phase to it # traj = p.model.add_subsystem('traj', dm.Trajectory()) # # In this case the phase has many segments to demonstrate the impact of coloring. # phase = traj.add_phase( 'phase0', dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments=100, solve_segments='forward'))) # # Set the variables # phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) phase.add_state('x', fix_initial=True) phase.add_state('y', fix_initial=True) phase.add_state('v', fix_initial=True) phase.add_control('theta', continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9) phase.add_parameter('g', units='m/s**2', val=9.80665) # # Replace state terminal bounds with nonlinear constraints # phase.add_boundary_constraint('x', loc='final', equals=10) phase.add_boundary_constraint('y', loc='final', equals=5) # # Minimize time at the end of the phase # phase.add_objective('time', loc='final', scaler=10) p.model.linear_solver = om.DirectSolver() # # Setup the Problem # p.setup() # # Set the initial values # p['traj.phase0.t_initial'] = 0.0 p['traj.phase0.t_duration'] = 2.0 p.set_val('traj.phase0.states:x', phase.interp('x', ys=[0, 10])) p.set_val('traj.phase0.states:y', phase.interp('y', ys=[10, 5])) p.set_val('traj.phase0.states:v', phase.interp('v', ys=[0, 9.9])) p.set_val('traj.phase0.controls:theta', phase.interp('theta', ys=[5, 100.5])) # # Solve for the optimal trajectory # dm.run_problem(p) # Test the results assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) # Generate the explicitly simulated trajectory exp_out = traj.simulate() plot_results( [('traj.phase0.timeseries.states:x', 'traj.phase0.timeseries.states:y', 'x (m)', 'y (m)'), ('traj.phase0.timeseries.time', 'traj.phase0.timeseries.controls:theta', 'time (s)', 'theta (deg)')], title='Brachistochrone Solution\nRadau Pseudospectral Method', p_sol=p, p_sim=exp_out) plt.show()
def test_brachistochrone_vector_boundary_constraints_radau_partial_indices( self): p = om.Problem(model=om.Group()) p.driver = om.ScipyOptimizeDriver() p.driver.declare_coloring() phase = dm.Phase(ode_class=BrachistochroneVectorStatesODE, transcription=dm.Radau(num_segments=20, order=3)) p.model.add_subsystem('phase0', phase) phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) phase.add_state( 'pos', shape=(2, ), rate_source=BrachistochroneVectorStatesODE.states['pos'] ['rate_source'], units=BrachistochroneVectorStatesODE.states['pos']['units'], fix_initial=True, fix_final=[True, False]) phase.add_state( 'v', rate_source=BrachistochroneVectorStatesODE.states['v'] ['rate_source'], targets=BrachistochroneVectorStatesODE.states['v']['targets'], units=BrachistochroneVectorStatesODE.states['v']['units'], fix_initial=True, fix_final=False) phase.add_control( 'theta', units='deg', targets=BrachistochroneVectorStatesODE.parameters['theta'] ['targets'], rate_continuity=False, lower=0.01, upper=179.9) phase.add_design_parameter( 'g', targets=BrachistochroneVectorStatesODE.parameters['g']['targets'], units='m/s**2', opt=False, val=9.80665) phase.add_boundary_constraint('pos', loc='final', equals=5, indices=[1]) # Minimize time at the end of the phase phase.add_objective('time', loc='final', scaler=10) p.model.linear_solver = om.DirectSolver() p.setup(check=True, force_alloc_complex=True) p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 2.0 pos0 = [0, 10] posf = [10, 5] p['phase0.states:pos'] = phase.interpolate(ys=[pos0, posf], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100], nodes='control_input') p['phase0.design_parameters:g'] = 9.80665 p.run_driver() assert_rel_error(self, p.get_val('phase0.time')[-1], 1.8016, tolerance=1.0E-3) # Plot results if SHOW_PLOTS: p.run_driver() exp_out = phase.simulate(times_per_seg=20) fig, ax = plt.subplots() fig.suptitle('Brachistochrone Solution') x_imp = p.get_val('phase0.timeseries.states:pos')[:, 0] y_imp = p.get_val('phase0.timeseries.states:pos')[:, 1] x_exp = exp_out.get_val('phase0.timeseries.states:pos')[:, 0] y_exp = exp_out.get_val('phase0.timeseries.states:pos')[:, 1] ax.plot(x_imp, y_imp, 'ro', label='implicit') ax.plot(x_exp, y_exp, 'b-', label='explicit') ax.set_xlabel('x (m)') ax.set_ylabel('y (m)') ax.grid(True) ax.legend(loc='upper right') fig, ax = plt.subplots() fig.suptitle('Brachistochrone Solution') x_imp = p.get_val('phase0.timeseries.time') y_imp = p.get_val('phase0.timeseries.control_rates:theta_rate2') x_exp = exp_out.get_val('phase0.timeseries.time') y_exp = exp_out.get_val( 'phase0.timeseries.control_rates:theta_rate2') ax.plot(x_imp, y_imp, 'ro', label='implicit') ax.plot(x_exp, y_exp, 'b-', label='explicit') ax.set_xlabel('time (s)') ax.set_ylabel('theta rate2 (rad/s**2)') ax.grid(True) ax.legend(loc='lower right') plt.show() return p
def test_brachistochrone_integrated_parameter_radau_ps(self): import numpy as np import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() phase = dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments=10)) p.model.add_subsystem('phase0', phase) phase.set_time_options(initial_bounds=(0, 0), duration_bounds=(.5, 10), units='s') phase.add_state('x', fix_initial=True, fix_final=True, rate_source='xdot', units='m') phase.add_state('y', fix_initial=True, fix_final=True, rate_source='ydot', units='m') phase.add_state('v', fix_initial=True, rate_source='vdot', units='m/s') phase.add_state('theta', fix_initial=False, rate_source='theta_dot', lower=1E-3) # theta_dot has no target, therefore we need to explicitly set the units and shape. phase.add_parameter('theta_dot', units='deg/s', shape=(1, ), opt=True, lower=0, upper=100) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) # Minimize time at the end of the phase phase.add_objective('time', loc='final', scaler=10) p.model.linear_solver = om.DirectSolver() p.setup() p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 2.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.states:theta'] = np.radians( phase.interpolate(ys=[0.05, 100.0], nodes='state_input')) p['phase0.parameters:theta_dot'] = 60.0 # Solve for the optimal trajectory dm.run_problem(p, refine_iteration_limit=5) # Test the results assert_near_equal(p.get_val('phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) sim_out = phase.simulate(times_per_seg=20) t_sol = p.get_val('phase0.timeseries.time') x_sol = p.get_val('phase0.timeseries.states:x') y_sol = p.get_val('phase0.timeseries.states:y') v_sol = p.get_val('phase0.timeseries.states:v') theta_sol = p.get_val('phase0.timeseries.states:theta') t_sim = sim_out.get_val('phase0.timeseries.time') x_sim = sim_out.get_val('phase0.timeseries.states:x') y_sim = sim_out.get_val('phase0.timeseries.states:y') v_sim = sim_out.get_val('phase0.timeseries.states:v') theta_sim = sim_out.get_val('phase0.timeseries.states:theta') assert_timeseries_near_equal(t_sol, x_sol, t_sim, x_sim, tolerance=1.0E-3, num_points=10) assert_timeseries_near_equal(t_sol, y_sol, t_sim, y_sim, tolerance=1.0E-3, num_points=10) assert_timeseries_near_equal(t_sol, v_sol, t_sim, v_sim, tolerance=1.0E-3, num_points=10) assert_timeseries_near_equal(t_sol, theta_sol, t_sim, theta_sim, tolerance=1.0E-3, num_points=10)
import dymos as dm from donner_sub_ode import DonnerSubODE # Create the problem p = om.Problem(model=om.Group()) # Add the trajectory (optional for single phase problems) traj = dm.Trajectory() # Create the phases phase0 = dm.Phase(ode_class=DonnerSubODE, transcription=dm.GaussLobatto(num_segments=10, order=3, compressed=False)) phase1 = dm.Phase(ode_class=DonnerSubODE, transcription=dm.Radau(num_segments=10, order=3, compressed=False)) # Add the phase to the trajectory, and the trajectory to the model traj.add_phase('phase0', phase=phase0) traj.add_phase('phase1', phase=phase1) traj.link_phases(phases=['phase0', 'phase1'], vars=['time', 'x', 'y', 'phi']) p.model.add_subsystem('traj', traj) # # Configure the first phase # phase0.set_time_options(units=None, targets=['threat_comp.time'], fix_initial=True, duration_bounds=(0.1, 100)) phase0.add_state('x', rate_source='eom_comp.dx_dt', targets=['nav_comp.x'], fix_initial=True, fix_final=False) phase0.add_state('y', rate_source='eom_comp.dy_dt', targets=['nav_comp.y'], fix_initial=True, fix_final=False) # phase0.add_input_parameter('v', targets=['eom_comp.v'])
def test_objective_parameter_radau(self): p = om.Problem(model=om.Group()) p.driver = om.ScipyOptimizeDriver() p.driver.declare_coloring() phase = dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments=20, order=3, compressed=True)) p.model.add_subsystem('phase0', phase) phase.set_time_options(fix_initial=True, duration_bounds=(4, 10)) phase.add_state( 'x', rate_source=BrachistochroneODE.states['x']['rate_source'], units=BrachistochroneODE.states['x']['units'], fix_initial=True, fix_final=True, solve_segments=False) phase.add_state( 'y', rate_source=BrachistochroneODE.states['y']['rate_source'], units=BrachistochroneODE.states['y']['units'], fix_initial=True, fix_final=True, solve_segments=False) phase.add_state( 'v', rate_source=BrachistochroneODE.states['v']['rate_source'], targets=BrachistochroneODE.states['v']['targets'], units=BrachistochroneODE.states['v']['units'], fix_initial=True, fix_final=False, solve_segments=False) phase.add_control( 'theta', continuity=True, rate_continuity=True, opt=True, targets=BrachistochroneODE.parameters['theta']['targets'], units='deg', lower=0.01, upper=179.9, ref=1, ref0=0) phase.add_parameter( 'g', targets=BrachistochroneODE.parameters['g']['targets'], opt=True, units='m/s**2', val=9.80665) # Minimize time at the end of the phase phase.add_objective('g') p.model.options['assembled_jac_type'] = 'csc' p.model.linear_solver = om.DirectSolver() p.setup(check=True) p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 2.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100], nodes='control_input') p['phase0.parameters:g'] = 9.80665 p.run_driver() assert_near_equal(p['phase0.t_duration'], 10, tolerance=1.0E-3)
n_nodes = 60 seed_perturbation = np.zeros(n_nodes) # seed_perturbation[2] = 1.e-6 # Add an indep_var_comp that will talk to external calls from pyStatReduce random_perturbations = p.model.add_subsystem('random_perturbations', IndepVarComp()) random_perturbations.add_output('rho_pert', val=seed_perturbation, units='kg/m**3', desc="perturbations introduced into the density data") # # Instantiate the trajectory and phase # traj = dm.Trajectory() phase = dm.Phase(ode_class=MinTimeClimbODE, transcription=dm.Radau(num_segments=15, compressed=True)) traj.add_phase('phase0', phase) p.model.add_subsystem('traj', traj) # # Set the options on the optimization variables # phase.set_time_options(fix_initial=True, duration_bounds=(50, 400), duration_ref=100.0) phase.set_state_options('r', fix_initial=True, lower=0, upper=1.0E6, ref=1.0E3, defect_ref=1.0E3, units='m') phase.set_state_options('h', fix_initial=True, lower=0, upper=20000.0,
def test_results(self): # begin code for paper import numpy as np from scipy.interpolate import interp1d import matplotlib.pyplot as plt import openmdao.api as om import dymos as dm from dymos.models.atmosphere.atmos_1976 import USatm1976Data as USatm1976Data # CREATE an atmosphere interpolant english_to_metric_rho = om.unit_conversion('slug/ft**3', 'kg/m**3')[0] english_to_metric_alt = om.unit_conversion('ft', 'm')[0] rho_interp = interp1d(np.array(USatm1976Data.alt * english_to_metric_alt, dtype=complex), np.array(USatm1976Data.rho * english_to_metric_rho, dtype=complex), kind='linear') class CannonballSize(om.ExplicitComponent): """ Static calculations performed before the dynamic model """ def setup(self): self.add_input(name='radius', val=1.0, desc='cannonball radius', units='m') self.add_input(name='density', val=7870., desc='cannonball density', units='kg/m**3') self.add_output(name='mass', shape=(1,), desc='cannonball mass', units='kg') self.add_output(name='area', shape=(1,), desc='aerodynamic reference area', units='m**2') self.declare_partials(of='*', wrt='*', method='cs') def compute(self, inputs, outputs): radius = inputs['radius'] rho = inputs['density'] outputs['mass'] = (4 / 3.) * rho * np.pi * radius ** 3 outputs['area'] = np.pi * radius ** 2 class CannonballODE(om.ExplicitComponent): """ Cannonball ODE assuming flat earth and accounting for air resistance """ def initialize(self): self.options.declare('num_nodes', types=int) def setup(self): nn = self.options['num_nodes'] # static parameters self.add_input('mass', units='kg') self.add_input('area', units='m**2') # time varying inputs self.add_input('alt', units='m', shape=nn) self.add_input('v', units='m/s', shape=nn) self.add_input('gam', units='rad', shape=nn) # state rates self.add_output('v_dot', shape=nn, units='m/s**2') self.add_output('gam_dot', shape=nn, units='rad/s') self.add_output('h_dot', shape=nn, units='m/s') self.add_output('r_dot', shape=nn, units='m/s') self.add_output('ke', shape=nn, units='J') # Ask OpenMDAO to compute the partial derivatives using complex-step # with a partial coloring algorithm for improved performance self.declare_partials('*', '*', method='cs') self.declare_coloring(wrt='*', method='cs') def compute(self, inputs, outputs): gam = inputs['gam'] v = inputs['v'] alt = inputs['alt'] m = inputs['mass'] S = inputs['area'] CD = 0.5 # good assumption for a sphere GRAVITY = 9.80665 # m/s**2 # handle complex-step gracefully from the interpolant if np.iscomplexobj(alt): rho = rho_interp(inputs['alt']) else: rho = rho_interp(inputs['alt']).real q = 0.5 * rho * inputs['v'] ** 2 qS = q * S D = qS * CD cgam = np.cos(gam) sgam = np.sin(gam) outputs['v_dot'] = - D / m - GRAVITY * sgam outputs['gam_dot'] = -(GRAVITY / v) * cgam outputs['h_dot'] = v * sgam outputs['r_dot'] = v * cgam outputs['ke'] = 0.5 * m * v ** 2 p = om.Problem() ################################### # Co-design part of the model, # static analysis outside of Dymos ################################### static_calcs = p.model.add_subsystem('static_calcs', CannonballSize()) static_calcs.add_design_var('radius', lower=0.01, upper=0.10, ref0=0.01, ref=0.10) p.model.connect('static_calcs.mass', 'traj.parameters:mass') p.model.connect('static_calcs.area', 'traj.parameters:area') traj = p.model.add_subsystem('traj', dm.Trajectory()) # Declare parameters that will be constant across # the two phases of the trajectory, so we can connect to it only once traj.add_parameter('mass', units='kg', val=0.01, static_target=True) traj.add_parameter('area', units='m**2', static_target=True) tx = dm.Radau(num_segments=5, order=3, compressed=True) ascent = dm.Phase(transcription=tx, ode_class=CannonballODE) traj.add_phase('ascent', ascent) ################################### # first phase: ascent ################################### # All initial states except flight path angle are fixed ascent.add_state('r', units='m', rate_source='r_dot', fix_initial=True, fix_final=False) ascent.add_state('h', units='m', rate_source='h_dot', fix_initial=True, fix_final=False) ascent.add_state('v', units='m/s', rate_source='v_dot', fix_initial=False, fix_final=False) # Final flight path angle is fixed ( # we will set it to zero so that the phase ends at apogee) ascent.add_state('gam', units='rad', rate_source='gam_dot', fix_initial=False, fix_final=True) ascent.set_time_options(fix_initial=True, duration_bounds=(1, 100), duration_ref=100, units='s') ascent.add_parameter('mass', units='kg', val=0.01, static_target=True) ascent.add_parameter('area', units='m**2', static_target=True) # Limit the initial muzzle energy to create a well posed problem # with respect to cannonball size and initial velocity ascent.add_boundary_constraint('ke', loc='initial', units='J', upper=400000, lower=0, ref=100000) ################################### # second phase: descent ################################### tx = dm.GaussLobatto(num_segments=5, order=3, compressed=True) descent = dm.Phase(transcription=tx, ode_class=CannonballODE) traj.add_phase('descent', descent) # All initial states and time are free so their # values can be linked to the final ascent values # Final altitude is fixed to 0 to ensure final impact on the ground descent.add_state('r', units='m', rate_source='r_dot', fix_initial=False, fix_final=False) descent.add_state('h', units='m', rate_source='h_dot', fix_initial=False, fix_final=True) descent.add_state('gam', units='rad', rate_source='gam_dot', fix_initial=False, fix_final=False) descent.add_state('v', units='m/s', rate_source='v_dot', fix_initial=False, fix_final=False) descent.set_time_options(initial_bounds=(.5, 100), duration_bounds=(.5, 100), duration_ref=100, units='s') descent.add_parameter('mass', units='kg', val=0.01, static_target=True) descent.add_parameter('area', units='m**2', static_target=True) # Link Phases (link time and all state variables) traj.link_phases(phases=['ascent', 'descent'], vars=['*']) # maximize range descent.add_objective('r', loc='final', ref=-1.0) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() # Finish Problem Setup p.setup() # Set Initial guesses for static dvs and ascent p.set_val('static_calcs.radius', 0.05, units='m') p.set_val('traj.ascent.t_duration', 10.0) p.set_val('traj.ascent.states:r', ascent.interp('r', [0, 100])) p.set_val('traj.ascent.states:h', ascent.interp('h', [0, 100])) p.set_val('traj.ascent.states:v', ascent.interp('v', [200, 150])) p.set_val('traj.ascent.states:gam', ascent.interp('gam', [25, 0]), units='deg') # more intitial guesses for descent p.set_val('traj.descent.t_initial', 10.0) p.set_val('traj.descent.t_duration', 10.0) p.set_val('traj.descent.states:r', descent.interp('r', [100, 200])) p.set_val('traj.descent.states:h', descent.interp('h', [100, 0])) p.set_val('traj.descent.states:v', descent.interp('v', [150, 200])) p.set_val('traj.descent.states:gam', descent.interp('gam', [0, -45]), units='deg') dm.run_problem(p, simulate=True, make_plots=True) fig, ax = plt.subplots() x0 = p.get_val('traj.ascent.timeseries.states:r', units='m') y0 = p.get_val('traj.ascent.timeseries.states:h', units='m') x1 = p.get_val('traj.descent.timeseries.states:r', units='m') y1 = p.get_val('traj.descent.timeseries.states:h', units='m') tab20 = plt.cm.get_cmap('tab20').colors ax.plot(x0, y0, marker='o', label='ascent', color=tab20[0]) ax.plot(x1, y1, marker='o', label='descent', color=tab20[1]) ax.legend(loc='best') ax.set_xlabel('range (m)') ax.set_ylabel('height (m)') fig.savefig('cannonball_hr.png', bbox_inches='tight') # End code for paper assert_near_equal(x1[-1], 3064, tolerance=1.0E-4)
def test_ex_double_integrator_input_times_compressed(self): """ Tests that externally connected t_initial and t_duration function as expected. """ compressed = True p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.declare_coloring() times_ivc = p.model.add_subsystem('times_ivc', om.IndepVarComp(), promotes_outputs=['t0', 'tp']) times_ivc.add_output(name='t0', val=0.0, units='s') times_ivc.add_output(name='tp', val=1.0, units='s') transcription = dm.Radau(num_segments=20, order=3, compressed=compressed) phase = dm.Phase(ode_class=DoubleIntegratorODE, transcription=transcription) p.model.add_subsystem('phase0', phase) p.model.connect('t0', 'phase0.t_initial') p.model.connect('tp', 'phase0.t_duration') phase.set_time_options(input_initial=True, input_duration=True, units='s') phase.add_state('v', fix_initial=True, fix_final=True, rate_source='u', units='m/s') phase.add_state('x', fix_initial=True, rate_source='v', units='m') phase.add_control('u', units='m/s**2', scaler=0.01, continuity=False, rate_continuity=False, rate2_continuity=False, shape=(1, ), lower=-1.0, upper=1.0) # Maximize distance travelled in one second. phase.add_objective('x', loc='final', scaler=-1) p.model.linear_solver = om.DirectSolver() p.setup(check=True) p['t0'] = 0.0 p['tp'] = 1.0 p['phase0.states:x'] = phase.interp('x', [0, 0.25]) p['phase0.states:v'] = phase.interp('v', [0, 0]) p['phase0.controls:u'] = phase.interp('u', [1, -1]) p.run_driver() assert_near_equal(p.get_val('phase0.timeseries.states:x')[-1, ...], [0.25], tolerance=1.0E-8)
def _make_problem(self, transcription, num_seg, transcription_order=3): p = om.Problem(model=om.Group()) p.driver = om.ScipyOptimizeDriver() # Compute sparsity/coloring when run_driver is called p.driver.declare_coloring() t = { 'gauss-lobatto': dm.GaussLobatto(num_segments=num_seg, order=transcription_order), 'radau-ps': dm.Radau(num_segments=num_seg, order=transcription_order) } phase = dm.Phase(ode_class=_BrachistochroneTestODE, transcription=t[transcription]) p.model.add_subsystem('phase0', phase) phase.set_time_options(initial_bounds=(1, 1), duration_bounds=(.5, 10), units='s', time_phase_targets=['time_phase'], t_duration_targets=['t_duration'], t_initial_targets=['t_initial'], targets=['time']) phase.add_state('x', fix_initial=True, rate_source='xdot', units='m') phase.add_state('y', fix_initial=True, rate_source='ydot', units='m') phase.add_state('v', fix_initial=True, rate_source='vdot', targets=['v'], units='m/s') phase.add_control('theta', units='deg', rate_continuity=True, lower=0.01, upper=179.9, targets=['theta']) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665, targets=['g']) phase.add_boundary_constraint('x', loc='final', equals=10) phase.add_boundary_constraint('y', loc='final', equals=5) # Minimize time at the end of the phase phase.add_objective('time', loc='final', scaler=10) p.model.linear_solver = om.DirectSolver() p.setup(check=True) p['phase0.t_initial'] = 1.0 p['phase0.t_duration'] = 3.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100.5], nodes='control_input') return p
def test_link_bounded_times_final_to_initial(self): """ Test that linking phases with times that are fixed at the linkage point raises an exception. """ import openmdao.api as om import dymos as dm from dymos.examples.cannonball.size_comp import CannonballSizeComp from dymos.examples.cannonball.cannonball_phase import CannonballPhase p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() external_params = p.model.add_subsystem('external_params', om.IndepVarComp()) external_params.add_output('radius', val=0.10, units='m') external_params.add_output('dens', val=7.87, units='g/cm**3') external_params.add_design_var('radius', lower=0.01, upper=0.10, ref0=0.01, ref=0.10) p.model.add_subsystem('size_comp', CannonballSizeComp()) traj = p.model.add_subsystem('traj', dm.Trajectory()) transcription = dm.Radau(num_segments=5, order=3, compressed=True) ascent = CannonballPhase(transcription=transcription) ascent = traj.add_phase('ascent', ascent) # All initial states except flight path angle are fixed # Final flight path angle is fixed (we will set it to zero so that the phase ends at apogee) ascent.set_time_options(initial_bounds=(0, 0), duration_bounds=(10, 10), duration_ref=100, units='s') ascent.set_state_options('r', fix_initial=True, fix_final=False) ascent.set_state_options('h', fix_initial=True, fix_final=False) ascent.set_state_options('gam', fix_initial=False, fix_final=True) ascent.set_state_options('v', fix_initial=False, fix_final=False) ascent.add_parameter('S', targets=['aero.S'], units='m**2') ascent.add_parameter('mass', targets=['eom.m', 'kinetic_energy.m'], units='kg') # Limit the muzzle energy ascent.add_boundary_constraint('kinetic_energy.ke', loc='initial', units='J', upper=400000, lower=0, ref=100000, shape=(1, )) # Second Phase (descent) transcription = dm.GaussLobatto(num_segments=5, order=3, compressed=True) descent = CannonballPhase(transcription=transcription) traj.add_phase('descent', descent) # All initial states and time are free (they will be linked to the final states of ascent. # Final altitude is fixed (we will set it to zero so that the phase ends at ground impact) descent.set_time_options(initial_bounds=(10, 10), duration_bounds=(10, 10), duration_ref=100, units='s') descent.add_state('r', ) descent.add_state('h', fix_initial=False, fix_final=True) descent.add_state('gam', fix_initial=False, fix_final=False) descent.add_state('v', fix_initial=False, fix_final=False) descent.add_parameter('S', targets=['aero.S'], units='m**2') descent.add_parameter('mass', targets=['eom.m', 'kinetic_energy.m'], units='kg') descent.add_objective('r', loc='final', scaler=-1.0) # Add internally-managed design parameters to the trajectory. traj.add_parameter('CD', targets={ 'ascent': ['aero.CD'], 'descent': ['aero.CD'] }, val=0.5, units=None, opt=False) traj.add_parameter('CL', targets={ 'ascent': ['aero.CL'], 'descent': ['aero.CL'] }, val=0.0, units=None, opt=False) traj.add_parameter('T', targets={ 'ascent': ['eom.T'], 'descent': ['eom.T'] }, val=0.0, units='N', opt=False) traj.add_parameter('alpha', targets={ 'ascent': ['eom.alpha'], 'descent': ['eom.alpha'] }, val=0.0, units='deg', opt=False) # Add externally-provided design parameters to the trajectory. # In this case, we connect 'm' to pre-existing input parameters named 'mass' in each phase. traj.add_parameter('m', units='kg', val=1.0, targets={ 'ascent': 'mass', 'descent': 'mass' }) # In this case, by omitting targets, we're connecting these parameters to parameters # with the same name in each phase. traj.add_parameter('S', units='m**2', val=0.005) # Link Phases (link time and all state variables) # Note velocity is not included here. Doing so is equivalent to linking kinetic energy, # and causes a duplicate row in the constraint jacobian. traj.link_phases(phases=['ascent', 'descent'], vars=['time', 'r', 'h', 'gam'], connected=False) traj.add_linkage_constraint('ascent', 'descent', 'kinetic_energy.ke', 'kinetic_energy.ke', ref=100000, connected=False) # Issue Connections p.model.connect('external_params.radius', 'size_comp.radius') p.model.connect('external_params.dens', 'size_comp.dens') p.model.connect('size_comp.mass', 'traj.parameters:m') p.model.connect('size_comp.S', 'traj.parameters:S') with self.assertRaises(ValueError) as e: p.setup() self.assertEqual( str(e.exception), 'Invalid linkage in Trajectory traj: Cannot link final ' 'value of "time" in ascent to initial value of "time" in ' 'descent. Values on both sides of the linkage are fixed.')
def brachistochrone_min_time(transcription='gauss-lobatto', num_segments=8, transcription_order=3, compressed=True, optimizer='SLSQP', simul_derivs=True): p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() OPT, OPTIMIZER = set_pyoptsparse_opt(optimizer, fallback=True) p.driver.options['optimizer'] = OPTIMIZER if simul_derivs: p.driver.declare_coloring() if transcription == 'gauss-lobatto': t = dm.GaussLobatto(num_segments=num_segments, order=transcription_order, compressed=compressed) elif transcription == 'radau-ps': t = dm.Radau(num_segments=num_segments, order=transcription_order, compressed=compressed) elif transcription == 'runge-kutta': t = dm.RungeKutta(num_segments=num_segments, order=transcription_order, compressed=compressed) phase = dm.Phase(ode_class=BrachistochroneODE, transcription=t) p.model.add_subsystem('phase0', phase) phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10), units='s') phase.add_state('x', fix_initial=True, fix_final=False, solve_segments=False, units='m', rate_source='xdot') phase.add_state('y', fix_initial=True, fix_final=False, solve_segments=False, units='m', rate_source='ydot') phase.add_state('v', fix_initial=True, fix_final=False, solve_segments=False, units='m/s', rate_source='vdot', targets=['v']) phase.add_control('theta', continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9, targets=['theta']) phase.add_input_parameter('g', units='m/s**2', val=9.80665, targets=['g']) phase.add_boundary_constraint('x', loc='final', equals=10) phase.add_boundary_constraint('y', loc='final', equals=5) # Minimize time at the end of the phase phase.add_objective('time_phase', loc='final', scaler=10) p.model.linear_solver = om.DirectSolver() p.setup(check=True) p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 2.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100], nodes='control_input') p['phase0.input_parameters:g'] = 9.80665 p.run_driver() return p
def test_two_burn_orbit_raise_radau_wildcard_add_timeseries_output(self): import numpy as np import matplotlib.pyplot as plt import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.general_utils import set_pyoptsparse_opt import dymos as dm from dymos.examples.finite_burn_orbit_raise.finite_burn_eom import FiniteBurnODE traj = dm.Trajectory() p = om.Problem(model=om.Group()) p.model.add_subsystem('traj', traj) p.driver = om.pyOptSparseDriver() _, optimizer = set_pyoptsparse_opt('IPOPT', fallback=True) p.driver.options['optimizer'] = optimizer p.driver.declare_coloring() traj.add_parameter('c', opt=False, val=1.5, units='DU/TU', targets={'burn1': ['c'], 'coast': ['c'], 'burn2': ['c']}) # First Phase (burn) burn1 = dm.Phase(ode_class=FiniteBurnODE, transcription=dm.Radau(num_segments=10, order=3, compressed=True)) burn1 = traj.add_phase('burn1', burn1) burn1.set_time_options(fix_initial=True, duration_bounds=(.5, 10), units='TU') burn1.add_state('r', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='r_dot', targets=['r'], units='DU') burn1.add_state('theta', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', targets=['theta'], units='rad') burn1.add_state('vr', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='vr_dot', targets=['vr'], units='DU/TU') burn1.add_state('vt', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='vt_dot', targets=['vt'], units='DU/TU') burn1.add_state('accel', fix_initial=True, fix_final=False, rate_source='at_dot', targets=['accel'], units='DU/TU**2') burn1.add_state('deltav', fix_initial=True, fix_final=False, rate_source='deltav_dot', units='DU/TU') burn1.add_control('u1', rate_continuity=True, rate2_continuity=True, units='deg', scaler=0.01, rate_continuity_scaler=0.001, rate2_continuity_scaler=0.001, lower=-30, upper=30, targets=['u1']) # Second Phase (Coast) coast = dm.Phase(ode_class=FiniteBurnODE, transcription=dm.RungeKutta(num_segments=20)) traj.add_phase('coast', coast) coast.set_time_options(initial_bounds=(0.5, 20), duration_bounds=(.5, 10), duration_ref=10, units='TU') coast.add_state('r', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='r_dot', targets=['r'], units='DU') coast.add_state('theta', fix_initial=False, fix_final=False, defect_scaler=100.0, units='rad', rate_source='theta_dot', targets=['theta']) coast.add_state('vr', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='vr_dot', targets=['vr'], units='DU/TU') coast.add_state('vt', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='vt_dot', targets=['vt'], units='DU/TU') coast.add_state('accel', fix_initial=True, fix_final=False, ref=1.0E-12, defect_ref=1.0E-12, rate_source='at_dot', targets=['accel'], units='DU/TU**2') coast.add_state('deltav', fix_initial=False, fix_final=False, rate_source='deltav_dot', units='DU/TU') coast.add_parameter('u1', targets=['u1'], opt=False, val=0.0, units='deg') # Third Phase (burn) burn2 = dm.Phase(ode_class=FiniteBurnODE, transcription=dm.Radau(num_segments=10, order=3, compressed=True)) traj.add_phase('burn2', burn2) burn2.set_time_options(initial_bounds=(0.5, 20), duration_bounds=(.5, 10), initial_ref=10, units='TU') burn2.add_state('r', fix_initial=False, fix_final=True, rate_source='r_dot', targets=['r'], units='DU') burn2.add_state('theta', fix_initial=False, fix_final=False, rate_source='theta_dot', targets=['theta'], units='rad') burn2.add_state('vr', fix_initial=False, fix_final=True, rate_source='vr_dot', targets=['vr'], units='DU/TU') burn2.add_state('vt', fix_initial=False, fix_final=True, rate_source='vt_dot', targets=['vt'], units='DU/TU') burn2.add_state('accel', fix_initial=False, fix_final=False, defect_ref=1.0E-6, rate_source='at_dot', targets=['accel'], units='DU/TU**2') burn2.add_state('deltav', fix_initial=False, fix_final=False, rate_source='deltav_dot', units='DU/TU') burn2.add_control('u1', targets=['u1'], rate_continuity=True, rate2_continuity=True, units='deg', scaler=0.01, lower=-30, upper=30) burn2.add_objective('deltav', loc='final', scaler=1.0) # demonstrate adding multiple variables at once using wildcard in add_timeseries_output burn1.add_timeseries_output('pos_*') # match partial variable in ODE outputs (Radau) coast.add_timeseries_output('pos_*') # match partial variable in ODE outputs (Runge Kutta) burn2.add_timeseries_output('*') # add all ODE outputs # Link Phases traj.link_phases(phases=['burn1', 'coast', 'burn2'], vars=['time', 'r', 'vr', 'vt', 'deltav']) traj.link_phases(phases=['burn1', 'burn2'], vars=['accel']) # Finish Problem Setup p.model.linear_solver = om.DirectSolver() p.setup(check=True, force_alloc_complex=True) # Set Initial Guesses p.set_val('traj.parameters:c', value=1.5) p.set_val('traj.burn1.t_initial', value=0.0) p.set_val('traj.burn1.t_duration', value=2.25) p.set_val('traj.burn1.states:r', value=burn1.interpolate(ys=[1, 1.5], nodes='state_input')) p.set_val('traj.burn1.states:theta', value=burn1.interpolate(ys=[0, 1.7], nodes='state_input')) p.set_val('traj.burn1.states:vr', value=burn1.interpolate(ys=[0, 0], nodes='state_input')) p.set_val('traj.burn1.states:vt', value=burn1.interpolate(ys=[1, 1], nodes='state_input')) p.set_val('traj.burn1.states:accel', value=burn1.interpolate(ys=[0.1, 0], nodes='state_input')) p.set_val('traj.burn1.states:deltav', value=burn1.interpolate(ys=[0, 0.1], nodes='state_input'), ) p.set_val('traj.burn1.controls:u1', value=burn1.interpolate(ys=[-3.5, 13.0], nodes='control_input')) p.set_val('traj.coast.t_initial', value=2.25) p.set_val('traj.coast.t_duration', value=3.0) p.set_val('traj.coast.states:r', value=coast.interpolate(ys=[1.3, 1.5], nodes='state_input')) p.set_val('traj.coast.states:theta', value=coast.interpolate(ys=[2.1767, 1.7], nodes='state_input')) p.set_val('traj.coast.states:vr', value=coast.interpolate(ys=[0.3285, 0], nodes='state_input')) p.set_val('traj.coast.states:vt', value=coast.interpolate(ys=[0.97, 1], nodes='state_input')) p.set_val('traj.coast.states:accel', value=coast.interpolate(ys=[0, 0], nodes='state_input')) p.set_val('traj.burn2.t_initial', value=5.25) p.set_val('traj.burn2.t_duration', value=1.75) p.set_val('traj.burn2.states:r', value=burn2.interpolate(ys=[1.8, 3], nodes='state_input')) p.set_val('traj.burn2.states:theta', value=burn2.interpolate(ys=[3.2, 4.0], nodes='state_input')) p.set_val('traj.burn2.states:vr', value=burn2.interpolate(ys=[.5, 0], nodes='state_input')) p.set_val('traj.burn2.states:vt', value=burn2.interpolate(ys=[1, np.sqrt(1 / 3)], nodes='state_input')) p.set_val('traj.burn2.states:accel', value=burn2.interpolate(ys=[0.1, 0], nodes='state_input')) p.set_val('traj.burn2.states:deltav', value=burn2.interpolate(ys=[0.1, 0.2], nodes='state_input')) p.set_val('traj.burn2.controls:u1', value=burn2.interpolate(ys=[1, 1], nodes='control_input')) p.run_driver() assert_near_equal(p.get_val('traj.burn2.timeseries.states:deltav')[-1], 0.3995, tolerance=2.0E-3) # test ODE output wildcard matching in solve_ivp exp_out = traj.simulate() for prob in (p, exp_out): # check timeseries: pos_x = r * cos(theta) and pos_y = r * sin(theta) for phs in ['burn1', 'coast', 'burn2']: x = np.array(prob.get_val('traj.{0}.timeseries.pos_x'.format(phs))).flatten() y = np.array(prob.get_val('traj.{0}.timeseries.pos_y'.format(phs))).flatten() t = np.array(prob.get_val('traj.{0}.timeseries.states:theta'.format(phs))).flatten() r = np.array(prob.get_val('traj.{0}.timeseries.states:r'.format(phs))).flatten() xerr = x - r * np.cos(t) yerr = y - r * np.sin(t) assert_near_equal(np.sqrt(np.mean(xerr*xerr)), 0.0) assert_near_equal(np.sqrt(np.mean(yerr*yerr)), 0.0)
ORDER = 5 NUM_FRAMES = 20 ORDER = 3 # Instantiate an OpenMDAO Problem instance. prob = om.Problem() # We need an optimization driver. To solve this simple problem ScipyOptimizerDriver will work. prob.driver = om.ScipyOptimizeDriver() # Instantiate a Dymos Trajectory and add it to the Problem model. traj = dm.Trajectory() prob.model.add_subsystem('traj', traj) # Instantiate a Phase and add it to the Trajectory. phase = dm.Phase(ode_class=OscillatorODE, transcription=dm.Radau(num_segments=NUM_SEG, order=ORDER, compressed=False)) traj.add_phase('phase0', phase) # Tell Dymos that the duration of the phase is bounded. phase.set_time_options(fix_initial=True, fix_duration=True) # Tell Dymos the states to be propagated using the given ODE. phase.add_state('x', fix_initial=True, rate_source='v', targets=['x'], units='m') phase.add_state('v', fix_initial=True, rate_source='v_dot', targets=['v'], units='m/s') # The spring constant, damping coefficient, and mass are inputs to the system that are constant throughout the phase. phase.add_parameter('k', units='N/m', targets=['k']) phase.add_parameter('c', units='N*s/m', targets=['c']) phase.add_parameter('m', units='kg', targets=['m']) # secondary "dense" timeseries
def test_ssto_simulate_root_trajectory(self): """ Tests that we can properly simulate a trajectory even if the trajectory is the root group of the model. In this case the name of the trajectory in the output will be 'sim_traj'. """ import numpy as np import matplotlib.pyplot as plt import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm g = 1.61544 # lunar gravity, m/s**2 class LaunchVehicle2DEOM(om.ExplicitComponent): """ Simple 2D Cartesian Equations of Motion for a launch vehicle subject to thrust and drag. """ def initialize(self): self.options.declare('num_nodes', types=int) def setup(self): nn = self.options['num_nodes'] # Inputs self.add_input('vx', val=np.zeros(nn), desc='x velocity', units='m/s') self.add_input('vy', val=np.zeros(nn), desc='y velocity', units='m/s') self.add_input('m', val=np.zeros(nn), desc='mass', units='kg') self.add_input('theta', val=np.zeros(nn), desc='pitch angle', units='rad') self.add_input('thrust', val=2100000 * np.ones(nn), desc='thrust', units='N') self.add_input('Isp', val=265.2 * np.ones(nn), desc='specific impulse', units='s') # Outputs self.add_output('xdot', val=np.zeros(nn), desc='velocity component in x', units='m/s') self.add_output('ydot', val=np.zeros(nn), desc='velocity component in y', units='m/s') self.add_output('vxdot', val=np.zeros(nn), desc='x acceleration magnitude', units='m/s**2') self.add_output('vydot', val=np.zeros(nn), desc='y acceleration magnitude', units='m/s**2') self.add_output('mdot', val=np.zeros(nn), desc='mass rate of change', units='kg/s') # Setup partials ar = np.arange(self.options['num_nodes']) self.declare_partials(of='xdot', wrt='vx', rows=ar, cols=ar, val=1.0) self.declare_partials(of='ydot', wrt='vy', rows=ar, cols=ar, val=1.0) self.declare_partials(of='vxdot', wrt='vx', rows=ar, cols=ar) self.declare_partials(of='vxdot', wrt='m', rows=ar, cols=ar) self.declare_partials(of='vxdot', wrt='theta', rows=ar, cols=ar) self.declare_partials(of='vxdot', wrt='thrust', rows=ar, cols=ar) self.declare_partials(of='vydot', wrt='m', rows=ar, cols=ar) self.declare_partials(of='vydot', wrt='theta', rows=ar, cols=ar) self.declare_partials(of='vydot', wrt='vy', rows=ar, cols=ar) self.declare_partials(of='vydot', wrt='thrust', rows=ar, cols=ar) self.declare_partials(of='mdot', wrt='thrust', rows=ar, cols=ar) self.declare_partials(of='mdot', wrt='Isp', rows=ar, cols=ar) def compute(self, inputs, outputs): theta = inputs['theta'] cos_theta = np.cos(theta) sin_theta = np.sin(theta) vx = inputs['vx'] vy = inputs['vy'] m = inputs['m'] F_T = inputs['thrust'] Isp = inputs['Isp'] outputs['xdot'] = vx outputs['ydot'] = vy outputs['vxdot'] = F_T * cos_theta / m outputs['vydot'] = F_T * sin_theta / m - g outputs['mdot'] = -F_T / (g * Isp) def compute_partials(self, inputs, jacobian): theta = inputs['theta'] cos_theta = np.cos(theta) sin_theta = np.sin(theta) m = inputs['m'] F_T = inputs['thrust'] Isp = inputs['Isp'] # jacobian['vxdot', 'vx'] = -CDA * rho * vx / m jacobian['vxdot', 'm'] = -(F_T * cos_theta) / m ** 2 jacobian['vxdot', 'theta'] = -(F_T / m) * sin_theta jacobian['vxdot', 'thrust'] = cos_theta / m # jacobian['vydot', 'vy'] = -CDA * rho * vy / m jacobian['vydot', 'm'] = -(F_T * sin_theta) / m ** 2 jacobian['vydot', 'theta'] = (F_T / m) * cos_theta jacobian['vydot', 'thrust'] = sin_theta / m jacobian['mdot', 'thrust'] = -1.0 / (g * Isp) jacobian['mdot', 'Isp'] = F_T / (g * Isp ** 2) class LaunchVehicleLinearTangentODE(om.Group): """ The LaunchVehicleLinearTangentODE for this case consists of a guidance component and the EOM. Guidance is simply an OpenMDAO ExecComp which computes the arctangent of the tan_theta variable. """ def initialize(self): self.options.declare('num_nodes', types=int, desc='Number of nodes to be evaluated in the RHS') def setup(self): nn = self.options['num_nodes'] self.add_subsystem('guidance', om.ExecComp('theta=arctan(tan_theta)', theta={'value': np.ones(nn), 'units': 'rad'}, tan_theta={'value': np.ones(nn)})) self.add_subsystem('eom', LaunchVehicle2DEOM(num_nodes=nn)) self.connect('guidance.theta', 'eom.theta') # # Setup and solve the optimal control problem # traj = dm.Trajectory() p = om.Problem(model=traj) phase = dm.Phase(ode_class=LaunchVehicleLinearTangentODE, transcription=dm.Radau(num_segments=20, order=3, compressed=False)) traj.add_phase('phase0', phase) phase.set_time_options(fix_initial=True, duration_bounds=(10, 1000), units='s') # # Set the state options. We include rate_source, units, and targets here since the ODE # is not decorated with their default values. # phase.add_state('x', fix_initial=True, lower=0, rate_source='eom.xdot') phase.add_state('y', fix_initial=True, lower=0, rate_source='eom.ydot') phase.add_state('vx', fix_initial=True, lower=0, rate_source='eom.vxdot', units='m/s', targets=['eom.vx']) phase.add_state('vy', fix_initial=True, rate_source='eom.vydot', units='m/s', targets=['eom.vy']) phase.add_state('m', fix_initial=True, rate_source='eom.mdot', units='kg', targets=['eom.m']) # # The tangent of theta is modeled as a linear polynomial over the duration of the phase. # phase.add_polynomial_control('tan_theta', order=1, units=None, opt=True, targets=['guidance.tan_theta']) # # Parameters values for thrust and specific impulse are design parameters. They are # provided by an IndepVarComp in the phase, but with opt=False their values are not # design variables in the optimization problem. # phase.add_parameter('thrust', units='N', opt=False, val=3.0 * 50000.0 * 1.61544, targets=['eom.thrust']) phase.add_parameter('Isp', units='s', opt=False, val=1.0E6, targets=['eom.Isp']) # # Set the boundary constraints. These are all states which could also be handled # by setting fix_final=True and including the correct final value in the initial guess. # phase.add_boundary_constraint('y', loc='final', equals=1.85E5, linear=True) phase.add_boundary_constraint('vx', loc='final', equals=1627.0) phase.add_boundary_constraint('vy', loc='final', equals=0) phase.add_objective('time', index=-1, scaler=0.01) # # Add theta as a timeseries output since it's not included by default. # phase.add_timeseries_output('guidance.theta', units='deg') # # Set the optimizer # p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() # # We don't strictly need to define a linear solver here since our problem is entirely # feed-forward with no iterative loops. It's good practice to add one, however, since # failing to do so can cause incorrect derivatives if iterative processes are ever # introduced to the system. # p.model.linear_solver = om.DirectSolver() p.setup(check=True) # # Assign initial guesses for the independent variables in the problem. # p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 500.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 350000.0], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[0, 185000.0], nodes='state_input') p['phase0.states:vx'] = phase.interpolate(ys=[0, 1627.0], nodes='state_input') p['phase0.states:vy'] = phase.interpolate(ys=[1.0E-6, 0], nodes='state_input') p['phase0.states:m'] = phase.interpolate(ys=[50000, 50000], nodes='state_input') p['phase0.polynomial_controls:tan_theta'] = [[0.5 * np.pi], [0.0]] # # Solve the problem. # p.run_driver() # # Check the results. # assert_near_equal(p.get_val('phase0.timeseries.time')[-1], 481, tolerance=0.01) # # Get the explitly simulated results # exp_out = traj.simulate() # # Plot the results # fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(10, 8)) axes[0].plot(p.get_val('phase0.timeseries.states:x'), p.get_val('phase0.timeseries.states:y'), marker='o', ms=4, linestyle='None', label='solution') axes[0].plot(exp_out.get_val('sim_traj.phase0.timeseries.states:x'), exp_out.get_val('sim_traj.phase0.timeseries.states:y'), marker=None, linestyle='-', label='simulation') axes[0].set_xlabel('range (m)') axes[0].set_ylabel('altitude (m)') axes[0].set_aspect('equal') axes[1].plot(p.get_val('phase0.timeseries.time'), p.get_val('phase0.timeseries.theta'), marker='o', ms=4, linestyle='None') axes[1].plot(exp_out.get_val('sim_traj.phase0.timeseries.time'), exp_out.get_val('sim_traj.phase0.timeseries.theta'), linestyle='-', marker=None) axes[1].set_xlabel('time (s)') axes[1].set_ylabel('theta (deg)') plt.suptitle('Single Stage to Orbit Solution Using Polynomial Controls') fig.legend(loc='lower center', ncol=2) plt.show()
def test_brachistochrone_undecorated_ode_radau(self): import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm p = om.Problem(model=om.Group()) p.driver = om.ScipyOptimizeDriver() phase = dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments=10)) p.model.add_subsystem('phase0', phase) phase.set_time_options(initial_bounds=(0, 0), duration_bounds=(.5, 10), units='s') phase.add_state('x', fix_initial=True, fix_final=True, rate_source='xdot', units='m') phase.add_state('y', fix_initial=True, fix_final=True, rate_source='ydot', units='m') phase.add_state('v', fix_initial=True, rate_source='vdot', targets=['v'], units='m/s') phase.add_control('theta', units='deg', rate_continuity=False, lower=0.01, upper=179.9, targets=['theta']) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665, targets=['g']) # Minimize time at the end of the phase phase.add_objective('time', loc='final', scaler=10) p.model.linear_solver = om.DirectSolver() p.setup() p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 2.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100.5], nodes='control_input') # Solve for the optimal trajectory p.run_driver() # Test the results assert_near_equal(p.get_val('phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3)
def test_two_phase_cannonball_ode_output_linkage(self): import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm from dymos.examples.cannonball.size_comp import CannonballSizeComp from dymos.examples.cannonball.cannonball_ode import CannonballODE p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() p.model.add_subsystem('size_comp', CannonballSizeComp(), promotes_inputs=['radius', 'dens']) p.model.set_input_defaults('dens', val=7.87, units='g/cm**3') p.model.add_design_var('radius', lower=0.01, upper=0.10, ref0=0.01, ref=0.10, units='m') traj = p.model.add_subsystem('traj', dm.Trajectory()) transcription = dm.Radau(num_segments=5, order=3, compressed=True) ascent = dm.Phase(ode_class=CannonballODE, transcription=transcription) ascent = traj.add_phase('ascent', ascent) # All initial states except flight path angle are fixed # Final flight path angle is fixed (we will set it to zero so that the phase ends at apogee) ascent.set_time_options(fix_initial=True, duration_bounds=(1, 100), duration_ref=100, units='s') ascent.add_state('r', fix_initial=True, fix_final=False, units='m', rate_source='r_dot') ascent.add_state('h', fix_initial=True, fix_final=False, units='m', rate_source='h_dot') ascent.add_state('gam', fix_initial=False, fix_final=True, units='rad', rate_source='gam_dot') ascent.add_state('v', fix_initial=False, fix_final=False, units='m/s', rate_source='v_dot') ascent.add_parameter('S', targets=['S'], units='m**2', dynamic=False) ascent.add_parameter('mass', targets=['m'], units='kg', dynamic=False) # Limit the muzzle energy ascent.add_boundary_constraint('ke', loc='initial', upper=400000, lower=0, ref=100000) # Second Phase (descent) transcription = dm.GaussLobatto(num_segments=5, order=3, compressed=True) descent = dm.Phase(ode_class=CannonballODE, transcription=transcription) traj.add_phase('descent', descent) # All initial states and time are free (they will be linked to the final states of ascent. # Final altitude is fixed (we will set it to zero so that the phase ends at ground impact) descent.set_time_options(initial_bounds=(.5, 100), duration_bounds=(.5, 100), duration_ref=100, units='s') descent.add_state('r', units='m', rate_source='r_dot') descent.add_state('h', fix_initial=False, fix_final=True, units='m', rate_source='h_dot') descent.add_state('gam', fix_initial=False, fix_final=False, units='rad', rate_source='gam_dot') descent.add_state('v', fix_initial=False, fix_final=False, units='m/s', rate_source='v_dot') descent.add_parameter('S', targets=['S'], units='m**2', dynamic=False) descent.add_parameter('mass', targets=['m'], units='kg', dynamic=False) descent.add_objective('r', loc='final', scaler=-1.0) # Add internally-managed design parameters to the trajectory. traj.add_parameter('CD', targets={ 'ascent': ['CD'], 'descent': ['CD'] }, val=0.5, units=None, opt=False, dynamic=False) # Add externally-provided design parameters to the trajectory. # In this case, we connect 'm' to pre-existing input parameters named 'mass' in each phase. traj.add_parameter('m', units='kg', val=1.0, targets={ 'ascent': 'mass', 'descent': 'mass' }) # In this case, by omitting targets, we're connecting these parameters to parameters # with the same name in each phase. traj.add_parameter('S', units='m**2', val=0.005) # Link Phases (link time and all state variables) # Note velocity is not included here. Doing so is equivalent to linking kinetic energy, # and causes a duplicate row in the constraint jacobian. traj.link_phases(phases=['ascent', 'descent'], vars=['time', 'r', 'h', 'gam'], connected=True) traj.add_linkage_constraint('ascent', 'descent', 'ke', 'ke', ref=100000, connected=False) p.model.connect('size_comp.mass', 'traj.parameters:m') p.model.connect('size_comp.S', 'traj.parameters:S') # Finish Problem Setup p.model.linear_solver = om.DirectSolver() p.driver.add_recorder(om.SqliteRecorder('ex_two_phase_cannonball.db')) p.setup() # Set Initial Guesses p.set_val('radius', 0.05, units='m') p.set_val('dens', 7.87, units='g/cm**3') p.set_val('traj.parameters:CD', 0.5) p.set_val('traj.ascent.t_initial', 0.0) p.set_val('traj.ascent.t_duration', 10.0) p.set_val('traj.ascent.states:r', ascent.interpolate(ys=[0, 100], nodes='state_input')) p.set_val('traj.ascent.states:h', ascent.interpolate(ys=[0, 100], nodes='state_input')) p.set_val('traj.ascent.states:v', ascent.interpolate(ys=[200, 150], nodes='state_input')) p.set_val('traj.ascent.states:gam', ascent.interpolate(ys=[25, 0], nodes='state_input'), units='deg') p.set_val('traj.descent.t_initial', 10.0) p.set_val('traj.descent.t_duration', 10.0) p.set_val('traj.descent.states:r', descent.interpolate(ys=[100, 200], nodes='state_input')) p.set_val('traj.descent.states:h', descent.interpolate(ys=[100, 0], nodes='state_input')) p.set_val('traj.descent.states:v', descent.interpolate(ys=[150, 200], nodes='state_input')) p.set_val('traj.descent.states:gam', descent.interpolate(ys=[0, -45], nodes='state_input'), units='deg') dm.run_problem(p) assert_near_equal(p.get_val('traj.descent.states:r')[-1], 3183.25, tolerance=1.0E-2) exp_out = traj.simulate() print('optimal radius: {0:6.4f} m '.format( p.get_val('radius', units='m')[0])) print('cannonball mass: {0:6.4f} kg '.format( p.get_val('size_comp.mass', units='kg')[0])) print('launch angle: {0:6.4f} ' 'deg '.format( p.get_val('traj.ascent.timeseries.states:gam', units='deg')[0, 0])) print('maximum range: {0:6.4f} ' 'm '.format( p.get_val('traj.descent.timeseries.states:r')[-1, 0])) fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(10, 6)) time_imp = { 'ascent': p.get_val('traj.ascent.timeseries.time'), 'descent': p.get_val('traj.descent.timeseries.time') } time_exp = { 'ascent': exp_out.get_val('traj.ascent.timeseries.time'), 'descent': exp_out.get_val('traj.descent.timeseries.time') } r_imp = { 'ascent': p.get_val('traj.ascent.timeseries.states:r'), 'descent': p.get_val('traj.descent.timeseries.states:r') } r_exp = { 'ascent': exp_out.get_val('traj.ascent.timeseries.states:r'), 'descent': exp_out.get_val('traj.descent.timeseries.states:r') } h_imp = { 'ascent': p.get_val('traj.ascent.timeseries.states:h'), 'descent': p.get_val('traj.descent.timeseries.states:h') } h_exp = { 'ascent': exp_out.get_val('traj.ascent.timeseries.states:h'), 'descent': exp_out.get_val('traj.descent.timeseries.states:h') } axes.plot(r_imp['ascent'], h_imp['ascent'], 'bo') axes.plot(r_imp['descent'], h_imp['descent'], 'ro') axes.plot(r_exp['ascent'], h_exp['ascent'], 'b--') axes.plot(r_exp['descent'], h_exp['descent'], 'r--') axes.set_xlabel('range (m)') axes.set_ylabel('altitude (m)') fig, axes = plt.subplots(nrows=4, ncols=1, figsize=(10, 6)) states = ['r', 'h', 'v', 'gam'] for i, state in enumerate(states): x_imp = { 'ascent': p.get_val('traj.ascent.timeseries.states:{0}'.format(state)), 'descent': p.get_val('traj.descent.timeseries.states:{0}'.format(state)) } x_exp = { 'ascent': exp_out.get_val( 'traj.ascent.timeseries.states:{0}'.format(state)), 'descent': exp_out.get_val( 'traj.descent.timeseries.states:{0}'.format(state)) } axes[i].set_ylabel(state) axes[i].plot(time_imp['ascent'], x_imp['ascent'], 'bo') axes[i].plot(time_imp['descent'], x_imp['descent'], 'ro') axes[i].plot(time_exp['ascent'], x_exp['ascent'], 'b--') axes[i].plot(time_exp['descent'], x_exp['descent'], 'r--') params = ['CD', 'mass', 'S'] fig, axes = plt.subplots(nrows=3, ncols=1, figsize=(12, 6)) for i, param in enumerate(params): p_imp = { 'ascent': p.get_val( 'traj.ascent.timeseries.parameters:{0}'.format(param)), 'descent': p.get_val( 'traj.descent.timeseries.parameters:{0}'.format(param)) } p_exp = { 'ascent': exp_out.get_val('traj.ascent.timeseries.' 'parameters:{0}'.format(param)), 'descent': exp_out.get_val('traj.descent.timeseries.' 'parameters:{0}'.format(param)) } axes[i].set_ylabel(param) axes[i].plot(time_imp['ascent'], p_imp['ascent'], 'bo') axes[i].plot(time_imp['descent'], p_imp['descent'], 'ro') axes[i].plot(time_exp['ascent'], p_exp['ascent'], 'b--') axes[i].plot(time_exp['descent'], p_exp['descent'], 'r--') plt.show()
def test_control_rate2_path_constraint_radau(self): import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE p = om.Problem(model=om.Group()) p.driver = om.ScipyOptimizeDriver() phase = dm.Phase(ode_class=BrachistochroneODE, transcription=dm.Radau(num_segments=10, compressed=False)) p.model.add_subsystem('phase0', phase) phase.set_time_options(initial_bounds=(0, 0), duration_bounds=(.5, 10)) phase.add_state( 'x', fix_initial=True, fix_final=True, rate_source=BrachistochroneODE.states['x']['rate_source']) phase.add_state( 'y', fix_initial=True, fix_final=True, rate_source=BrachistochroneODE.states['y']['rate_source']) phase.add_state( 'v', fix_initial=True, rate_source=BrachistochroneODE.states['v']['rate_source']) phase.add_control('theta', units='deg', rate_continuity=False, lower=0.01, upper=179.9) phase.add_parameter('g', units='m/s**2', opt=False, val=9.80665) # Minimize time at the end of the phase phase.add_objective('time', loc='final', scaler=10) phase.add_path_constraint('theta_rate2', lower=-200, upper=200, units='rad/s**2') p.model.linear_solver = om.DirectSolver() p.model.options['assembled_jac_type'] = 'csc' p.setup() p['phase0.t_initial'] = 0.0 p['phase0.t_duration'] = 2.0 p['phase0.states:x'] = phase.interpolate(ys=[0, 10], nodes='state_input') p['phase0.states:y'] = phase.interpolate(ys=[10, 5], nodes='state_input') p['phase0.states:v'] = phase.interpolate(ys=[0, 9.9], nodes='state_input') p['phase0.controls:theta'] = phase.interpolate(ys=[5, 100.5], nodes='control_input') # Solve for the optimal trajectory p.run_driver() # Test the results assert_near_equal(p.get_val('phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3)
def test_traj_param_target_none(self): # Tests a bug where you couldn't specify None as a target for a specific phase. import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm from dymos.examples.cannonball.size_comp import CannonballSizeComp from dymos.examples.cannonball.cannonball_ode import CannonballODE p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() p.model.add_subsystem('size_comp', CannonballSizeComp(), promotes_inputs=['radius', 'dens']) p.model.set_input_defaults('dens', val=7.87, units='g/cm**3') p.model.add_design_var('radius', lower=0.01, upper=0.10, ref0=0.01, ref=0.10, units='m') traj = p.model.add_subsystem('traj', dm.Trajectory()) transcription = dm.Radau(num_segments=5, order=3, compressed=True) ascent = dm.Phase(ode_class=CannonballODE, transcription=transcription) ascent = traj.add_phase('ascent', ascent) # All initial states except flight path angle are fixed # Final flight path angle is fixed (we will set it to zero so that the phase ends at apogee) ascent.set_time_options(fix_initial=True, duration_bounds=(1, 100), duration_ref=100, units='s') ascent.add_state('r', fix_initial=True, fix_final=False, units='m', rate_source='r_dot') ascent.add_state('h', fix_initial=True, fix_final=False, units='m', rate_source='h_dot') ascent.add_state('gam', fix_initial=False, fix_final=True, units='rad', rate_source='gam_dot') ascent.add_state('v', fix_initial=False, fix_final=False, units='m/s', rate_source='v_dot') ascent.add_parameter('S', targets=['S'], units='m**2', dynamic=False) ascent.add_parameter('mass', targets=['m'], units='kg', dynamic=False) # Limit the muzzle energy ascent.add_boundary_constraint('ke', loc='initial', units='J', upper=400000, lower=0, ref=100000, shape=(1, )) # Second Phase (descent) transcription = dm.GaussLobatto(num_segments=5, order=3, compressed=True) descent = dm.Phase(ode_class=CannonballODE, transcription=transcription) traj.add_phase('descent', descent) # All initial states and time are free (they will be linked to the final states of ascent. # Final altitude is fixed (we will set it to zero so that the phase ends at ground impact) descent.set_time_options(initial_bounds=(.5, 100), duration_bounds=(.5, 100), duration_ref=100, units='s') descent.add_state('r', units='m', rate_source='r_dot') descent.add_state('h', fix_initial=False, fix_final=True, units='m', rate_source='h_dot') descent.add_state('gam', fix_initial=False, fix_final=False, units='rad', rate_source='gam_dot') descent.add_state('v', fix_initial=False, fix_final=False, units='m/s', rate_source='v_dot') descent.add_parameter('S', targets=['S'], units='m**2', dynamic=False) descent.add_parameter('mass', targets=['m'], units='kg', dynamic=False) descent.add_objective('r', loc='final', scaler=-1.0) # Add internally-managed design parameters to the trajectory. traj.add_parameter('CD', targets={ 'ascent': ['CD'], 'descent': ['CD'] }, val=0.5, units=None, opt=False, dynamic=False) # Add externally-provided design parameters to the trajectory. # In this case, we connect 'm' to pre-existing input parameters named 'mass' in each phase. traj.add_parameter('m', units='kg', val=1.0, targets={ 'ascent': 'mass', 'descent': 'mass' }, dynamic=False) # In this case, by omitting targets, we're connecting these parameters to parameters # with the same name in each phase. traj.add_parameter('S', units='m**2', val=0.005, dynamic=False) # Link Phases (link time and all state variables) # Note velocity is not included here. Doing so is equivalent to linking kinetic energy, # and causes a duplicate row in the constraint jacobian. traj.link_phases(phases=['ascent', 'descent'], vars=['time', 'r', 'h', 'gam'], connected=True) traj.add_linkage_constraint('ascent', 'descent', 'ke', 'ke', ref=100000, connected=False) p.model.connect('size_comp.mass', 'traj.parameters:m') p.model.connect('size_comp.S', 'traj.parameters:S') # Finish Problem Setup p.model.linear_solver = om.DirectSolver() p.driver.add_recorder(om.SqliteRecorder('ex_two_phase_cannonball.db')) p.setup() # Set Initial Guesses p.set_val('radius', 0.05, units='m') p.set_val('dens', 7.87, units='g/cm**3') p.set_val('traj.parameters:CD', 0.5) p.set_val('traj.ascent.t_initial', 0.0) p.set_val('traj.ascent.t_duration', 10.0) p.set_val('traj.ascent.states:r', ascent.interpolate(ys=[0, 100], nodes='state_input')) p.set_val('traj.ascent.states:h', ascent.interpolate(ys=[0, 100], nodes='state_input')) p.set_val('traj.ascent.states:v', ascent.interpolate(ys=[200, 150], nodes='state_input')) p.set_val('traj.ascent.states:gam', ascent.interpolate(ys=[25, 0], nodes='state_input'), units='deg') p.set_val('traj.descent.t_initial', 10.0) p.set_val('traj.descent.t_duration', 10.0) p.set_val('traj.descent.states:r', descent.interpolate(ys=[100, 200], nodes='state_input')) p.set_val('traj.descent.states:h', descent.interpolate(ys=[100, 0], nodes='state_input')) p.set_val('traj.descent.states:v', descent.interpolate(ys=[150, 200], nodes='state_input')) p.set_val('traj.descent.states:gam', descent.interpolate(ys=[0, -45], nodes='state_input'), units='deg') dm.run_problem(p) assert_near_equal(p.get_val('traj.descent.states:r')[-1], 3183.25, tolerance=1.0E-2)
def test_run_HS_problem_radau(self): p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.declare_coloring() p.driver.options['optimizer'] = optimizer if optimizer == 'SNOPT': p.driver.opt_settings['Major iterations limit'] = 200 p.driver.opt_settings['Major feasibility tolerance'] = 1.0E-6 p.driver.opt_settings['Major optimality tolerance'] = 1.0E-6 elif optimizer == 'IPOPT': p.driver.opt_settings['hessian_approximation'] = 'limited-memory' # p.driver.opt_settings['nlp_scaling_method'] = 'user-scaling' p.driver.opt_settings['print_level'] = 4 p.driver.opt_settings['max_iter'] = 200 p.driver.opt_settings['linear_solver'] = 'mumps' traj = p.model.add_subsystem('traj', dm.Trajectory()) phase0 = traj.add_phase( 'phase0', dm.Phase(ode_class=HyperSensitiveODE, transcription=dm.Radau(num_segments=10, order=3))) phase0.set_time_options(fix_initial=True, fix_duration=True) phase0.add_state('x', fix_initial=True, fix_final=False, rate_source='x_dot', targets=['x']) phase0.add_state('xL', fix_initial=True, fix_final=False, rate_source='L', targets=['xL']) phase0.add_control('u', opt=True, targets=['u'], rate_continuity=False) phase0.add_boundary_constraint('x', loc='final', equals=1) phase0.add_objective('xL', loc='final') phase0.set_refine_options(refine=True, tol=1e-6) p.setup(check=True) tf = np.float128(20) p.set_val('traj.phase0.states:x', phase0.interp('x', [1.5, 1])) p.set_val('traj.phase0.states:xL', phase0.interp('xL', [0, 1])) p.set_val('traj.phase0.t_initial', 0) p.set_val('traj.phase0.t_duration', tf) p.set_val('traj.phase0.controls:u', phase0.interp('u', [-0.6, 2.4])) dm.run_problem(p, refine_method='hp', refine_iteration_limit=10) sqrt_two = np.sqrt(2) val = sqrt_two * tf c1 = (1.5 * np.exp(-val) - 1) / (np.exp(-val) - np.exp(val)) c2 = (1 - 1.5 * np.exp(val)) / (np.exp(-val) - np.exp(val)) ui = c1 * (1 + sqrt_two) + c2 * (1 - sqrt_two) uf = c1 * (1 + sqrt_two) * np.exp(val) + c2 * (1 - sqrt_two) * np.exp(-val) J = 0.5 * (c1**2 * (1 + sqrt_two) * np.exp(2 * val) + c2**2 * (1 - sqrt_two) * np.exp(-2 * val) - (1 + sqrt_two) * c1**2 - (1 - sqrt_two) * c2**2) assert_near_equal(p.get_val('traj.phase0.timeseries.controls:u')[0], ui, tolerance=5e-4) assert_near_equal(p.get_val('traj.phase0.timeseries.controls:u')[-1], uf, tolerance=5e-4) assert_near_equal(p.get_val('traj.phase0.timeseries.states:xL')[-1], J, tolerance=5e-4)
def test_basic(self): import matplotlib.pyplot as plt import openmdao.api as om from openmdao.utils.assert_utils import assert_rel_error import dymos as dm from dymos.examples.battery_multibranch.battery_multibranch_ode import BatteryODE from dymos.utils.lgl import lgl prob = om.Problem() opt = prob.driver = om.ScipyOptimizeDriver() opt.declare_coloring() opt.options['optimizer'] = 'SLSQP' num_seg = 5 seg_ends, _ = lgl(num_seg + 1) traj = prob.model.add_subsystem('traj', dm.Trajectory()) # First phase: normal operation. transcription = dm.Radau(num_segments=num_seg, order=5, segment_ends=seg_ends, compressed=False) phase0 = dm.Phase(ode_class=BatteryODE, transcription=transcription) traj_p0 = traj.add_phase('phase0', phase0) traj_p0.set_time_options(fix_initial=True, fix_duration=True) traj_p0.add_state('state_of_charge', fix_initial=True, fix_final=False, targets=['SOC'], rate_source='dXdt:SOC') # Second phase: normal operation. phase1 = dm.Phase(ode_class=BatteryODE, transcription=transcription) traj_p1 = traj.add_phase('phase1', phase1) traj_p1.set_time_options(fix_initial=False, fix_duration=True) traj_p1.add_state('state_of_charge', fix_initial=False, fix_final=False, targets=['SOC'], rate_source='dXdt:SOC') traj_p1.add_objective('time', loc='final') # Second phase, but with battery failure. phase1_bfail = dm.Phase(ode_class=BatteryODE, ode_init_kwargs={'num_battery': 2}, transcription=transcription) traj_p1_bfail = traj.add_phase('phase1_bfail', phase1_bfail) traj_p1_bfail.set_time_options(fix_initial=False, fix_duration=True) traj_p1_bfail.add_state('state_of_charge', fix_initial=False, fix_final=False, targets=['SOC'], rate_source='dXdt:SOC') # Second phase, but with motor failure. phase1_mfail = dm.Phase(ode_class=BatteryODE, ode_init_kwargs={'num_motor': 2}, transcription=transcription) traj_p1_mfail = traj.add_phase('phase1_mfail', phase1_mfail) traj_p1_mfail.set_time_options(fix_initial=False, fix_duration=True) traj_p1_mfail.add_state('state_of_charge', fix_initial=False, fix_final=False, targets=['SOC'], rate_source='dXdt:SOC') traj.link_phases(phases=['phase0', 'phase1'], vars=['state_of_charge', 'time']) traj.link_phases(phases=['phase0', 'phase1_bfail'], vars=['state_of_charge', 'time']) traj.link_phases(phases=['phase0', 'phase1_mfail'], vars=['state_of_charge', 'time']) prob.model.options['assembled_jac_type'] = 'csc' prob.model.linear_solver = om.DirectSolver(assemble_jac=True) prob.setup() prob['traj.phase0.t_initial'] = 0 prob['traj.phase0.t_duration'] = 1.0*3600 prob['traj.phase1.t_initial'] = 1.0*3600 prob['traj.phase1.t_duration'] = 1.0*3600 prob['traj.phase1_bfail.t_initial'] = 1.0*3600 prob['traj.phase1_bfail.t_duration'] = 1.0*3600 prob['traj.phase1_mfail.t_initial'] = 1.0*3600 prob['traj.phase1_mfail.t_duration'] = 1.0*3600 prob.set_solver_print(level=0) prob.run_driver() soc0 = prob['traj.phase0.states:state_of_charge'] soc1 = prob['traj.phase1.states:state_of_charge'] soc1b = prob['traj.phase1_bfail.states:state_of_charge'] soc1m = prob['traj.phase1_mfail.states:state_of_charge'] # Final value for State of Chrage in each segment should be a good test. print('State of Charge after 1 hour') assert_rel_error(self, soc0[-1], 0.63464982, 1e-6) print('State of Charge after 2 hours') assert_rel_error(self, soc1[-1], 0.23794217, 1e-6) print('State of Charge after 2 hours, battery fails at 1 hour') assert_rel_error(self, soc1b[-1], 0.0281523, 1e-6) print('State of Charge after 2 hours, motor fails at 1 hour') assert_rel_error(self, soc1m[-1], 0.18625395, 1e-6) # Plot Results t0 = prob['traj.phases.phase0.time.time']/3600 t1 = prob['traj.phases.phase1.time.time']/3600 t1b = prob['traj.phases.phase1_bfail.time.time']/3600 t1m = prob['traj.phases.phase1_mfail.time.time']/3600 plt.subplot(2, 1, 1) plt.plot(t0, soc0, 'b') plt.plot(t1, soc1, 'b') plt.plot(t1b, soc1b, 'r') plt.plot(t1m, soc1m, 'c') plt.xlabel('Time (hour)') plt.ylabel('State of Charge (percent)') I_Li0 = prob['traj.phases.phase0.rhs_all.pwr_balance.I_Li'] I_Li1 = prob['traj.phases.phase1.rhs_all.pwr_balance.I_Li'] I_Li1b = prob['traj.phases.phase1_bfail.rhs_all.pwr_balance.I_Li'] I_Li1m = prob['traj.phases.phase1_mfail.rhs_all.pwr_balance.I_Li'] plt.subplot(2, 1, 2) plt.plot(t0, I_Li0, 'b') plt.plot(t1, I_Li1, 'b') plt.plot(t1b, I_Li1b, 'r') plt.plot(t1m, I_Li1m, 'c') plt.xlabel('Time (hour)') plt.ylabel('Line Current (A)') plt.legend(['Phase 1', 'Phase 2', 'Phase 2 Battery Fail', 'Phase 2 Motor Fail'], loc=2) plt.show()
def min_time_climb(optimizer='SLSQP', num_seg=3, transcription='gauss-lobatto', transcription_order=3, force_alloc_complex=False): p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = optimizer p.driver.declare_coloring() if optimizer == 'SNOPT': p.driver.opt_settings['Major iterations limit'] = 1000 p.driver.opt_settings['iSumm'] = 6 p.driver.opt_settings['Major feasibility tolerance'] = 1.0E-6 p.driver.opt_settings['Major optimality tolerance'] = 1.0E-6 p.driver.opt_settings['Function precision'] = 1.0E-12 p.driver.opt_settings['Linesearch tolerance'] = 0.1 p.driver.opt_settings['Major step limit'] = 0.5 t = { 'gauss-lobatto': dm.GaussLobatto(num_segments=num_seg, order=transcription_order), 'radau-ps': dm.Radau(num_segments=num_seg, order=transcription_order) } traj = dm.Trajectory() phase = dm.Phase(ode_class=_TestODE, transcription=t[transcription]) traj.add_phase('phase0', phase) p.model.add_subsystem('traj', traj) phase.set_time_options(fix_initial=True, duration_bounds=(50, 400), duration_ref=100.0) phase.add_state('r', fix_initial=True, lower=0, upper=1.0E6, ref=1.0E3, defect_ref=1.0E3, units='m', rate_source='flight_dynamics.r_dot') phase.add_state('h', fix_initial=True, lower=0, upper=20000.0, ref=1.0E2, defect_ref=1.0E2, units='m', rate_source='flight_dynamics.h_dot', targets=['h']) phase.add_state('v', fix_initial=True, lower=10.0, ref=1.0E2, defect_ref=1.0E2, units='m/s', rate_source='flight_dynamics.v_dot', targets=['v']) phase.add_state('gam', fix_initial=True, lower=-1.5, upper=1.5, ref=1.0, defect_ref=1.0, units='rad', rate_source='flight_dynamics.gam_dot', targets=['gam']) phase.add_state('m', fix_initial=True, lower=10.0, upper=1.0E5, ref=1.0E3, defect_ref=1.0E3, units='kg', rate_source='prop.m_dot', targets=['m']) phase.add_control('alpha', units='deg', lower=-8.0, upper=8.0, scaler=1.0, rate_continuity=True, rate_continuity_scaler=100.0, rate2_continuity=False, targets=['alpha']) phase.add_parameter('S', val=49.2386, units='m**2', opt=False, targets=['S']) phase.add_parameter('Isp', val=1600.0, units='s', opt=False, targets=['Isp']) phase.add_parameter('throttle', val=1.0, opt=False, targets=['throttle']) phase.add_parameter('test', val=40 * [1], opt=False, static_target=True, targets=['test']) phase.add_boundary_constraint('h', loc='final', equals=20000, scaler=1.0E-3) phase.add_boundary_constraint('aero.mach', loc='final', equals=1.0) phase.add_boundary_constraint('gam', loc='final', equals=0.0) phase.add_path_constraint(name='h', lower=100.0, upper=20000, ref=20000) phase.add_path_constraint(name='aero.mach', lower=0.1, upper=1.8) # Unnecessary but included to test capability phase.add_path_constraint(name='alpha', lower=-8, upper=8, units='deg') # Minimize time at the end of the phase phase.add_objective('time', loc='final', ref=1.0) # test mixing wildcard ODE variable expansion and unit overrides phase.add_timeseries_output(['aero.*', 'prop.thrust', 'prop.m_dot'], units={ 'aero.f_lift': 'lbf', 'prop.thrust': 'lbf' }) phase.set_refine_options(max_order=5) p.model.linear_solver = om.DirectSolver() p.setup(check=True, force_alloc_complex=force_alloc_complex) p['traj.phase0.t_initial'] = 0.0 p['traj.phase0.t_duration'] = 300.0 p['traj.phase0.states:r'] = phase.interp('r', [0.0, 111319.54]) p['traj.phase0.states:h'] = phase.interp('h', [100.0, 20000.0]) p['traj.phase0.states:v'] = phase.interp('v', [135.964, 283.159]) p['traj.phase0.states:gam'] = phase.interp('gam', [0.0, 0.0]) p['traj.phase0.states:m'] = phase.interp('m', [19030.468, 16841.431]) p['traj.phase0.controls:alpha'] = phase.interp('alpha', [0.0, 0.0]) dm.run_problem(p, refine_iteration_limit=1) return p
jacobian['xdot', 'theta'] = v * cos_theta jacobian['ydot', 'v'] = -cos_theta jacobian['ydot', 'theta'] = v * sin_theta # Define the OpenMDAO problem p = om.Problem(model=om.Group()) # Define a Trajectory object traj = dm.Trajectory() p.model.add_subsystem('traj', subsys=traj) # Define a Dymos Phase object with Radau Transcription phase = dm.Phase(ode_class=BrachistochroneEOM, transcription=dm.Radau(num_segments=10, order=3)) traj.add_phase(name='phase0', phase=phase) # Set the time options phase.set_time_options(fix_initial=True, duration_bounds=(0.5, 10.0)) # Set the state options phase.add_state('x', rate_source='xdot', fix_initial=True, fix_final=True) phase.add_state('y', rate_source='ydot', fix_initial=True, fix_final=True) phase.add_state('v', rate_source='vdot', fix_initial=True, fix_final=False) # Define theta as a control.
def test_ivp_driver_10_segs(self): import openmdao.api as om import dymos as dm import matplotlib.pyplot as plt plt.switch_backend('Agg') # disable plotting to the screen from dymos.examples.oscillator.doc.oscillator_ode import OscillatorODE # Instantiate an OpenMDAO Problem instance. prob = om.Problem() # We need an optimization driver. To solve this simple problem ScipyOptimizerDriver will work. prob.driver = om.ScipyOptimizeDriver() # Instantiate a Dymos Trajectory and add it to the Problem model. traj = dm.Trajectory() prob.model.add_subsystem('traj', traj) # Instantiate a Phase and add it to the Trajectory. phase = dm.Phase(ode_class=OscillatorODE, transcription=dm.Radau(num_segments=10)) traj.add_phase('phase0', phase) # Tell Dymos that the duration of the phase is bounded. phase.set_time_options(fix_initial=True, fix_duration=True) # Tell Dymos the states to be propagated using the given ODE. phase.add_state('x', fix_initial=True, rate_source='v', targets=['x'], units='m') phase.add_state('v', fix_initial=True, rate_source='v_dot', targets=['v'], units='m/s') # The spring constant, damping coefficient, and mass are inputs to the system that # are constant throughout the phase. phase.add_parameter('k', units='N/m', targets=['k']) phase.add_parameter('c', units='N*s/m', targets=['c']) phase.add_parameter('m', units='kg', targets=['m']) # Since we're using an optimization driver, an objective is required. We'll minimize # the final time in this case. phase.add_objective('time', loc='final') # Setup the OpenMDAO problem prob.setup() # Assign values to the times and states prob.set_val('traj.phase0.t_initial', 0.0) prob.set_val('traj.phase0.t_duration', 15.0) prob.set_val('traj.phase0.states:x', 10.0) prob.set_val('traj.phase0.states:v', 0.0) prob.set_val('traj.phase0.parameters:k', 1.0) prob.set_val('traj.phase0.parameters:c', 0.5) prob.set_val('traj.phase0.parameters:m', 1.0) # Now we're using the optimization driver to iteratively run the model and vary the # phase duration until the final y value is 0. prob.run_driver() # Perform an explicit simulation of our ODE from the initial conditions. sim_out = traj.simulate(times_per_seg=50) # Plot the state values obtained from the phase timeseries objects in the simulation output. t_sol = prob.get_val('traj.phase0.timeseries.time') t_sim = sim_out.get_val('traj.phase0.timeseries.time') states = ['x', 'v'] fig, axes = plt.subplots(len(states), 1) for i, state in enumerate(states): sol = axes[i].plot( t_sol, prob.get_val(f'traj.phase0.timeseries.states:{state}'), 'o') sim = axes[i].plot( t_sim, sim_out.get_val(f'traj.phase0.timeseries.states:{state}'), '-') axes[i].set_ylabel(state) axes[-1].set_xlabel('time (s)') fig.legend((sol[0], sim[0]), ('solution', 'simulation'), 'lower right', ncol=2) plt.tight_layout() plt.show()
def make_traj(transcription='gauss-lobatto', transcription_order=3, compressed=False, connected=False): t = {'gauss-lobatto': dm.GaussLobatto(num_segments=5, order=transcription_order, compressed=compressed), 'radau': dm.Radau(num_segments=20, order=transcription_order, compressed=compressed), 'runge-kutta': dm.RungeKutta(num_segments=5, compressed=compressed)} traj = dm.Trajectory() traj.add_design_parameter('c', opt=False, val=1.5, units='DU/TU', targets={'burn1': ['c'], 'coast': ['c'], 'burn2': ['c']}) # First Phase (burn) burn1 = dm.Phase(ode_class=FiniteBurnODE, transcription=t[transcription]) burn1 = traj.add_phase('burn1', burn1) burn1.set_time_options(fix_initial=True, duration_bounds=(.5, 10), units='TU') burn1.add_state('r', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='r_dot', targets=['r'], units='DU') burn1.add_state('theta', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', targets=['theta'], units='rad') burn1.add_state('vr', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='vr_dot', targets=['vr'], units='DU/TU') burn1.add_state('vt', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='vt_dot', targets=['vt'], units='DU/TU') burn1.add_state('accel', fix_initial=True, fix_final=False, rate_source='at_dot', targets=['accel'], units='DU/TU**2') burn1.add_state('deltav', fix_initial=True, fix_final=False, rate_source='deltav_dot', units='DU/TU') burn1.add_control('u1', rate_continuity=True, rate2_continuity=True, units='deg', scaler=0.01, rate_continuity_scaler=0.001, rate2_continuity_scaler=0.001, lower=-30, upper=30, targets=['u1']) # Second Phase (Coast) coast = dm.Phase(ode_class=FiniteBurnODE, transcription=t[transcription]) coast.set_time_options(initial_bounds=(0.5, 20), duration_bounds=(.5, 50), duration_ref=50, units='TU') coast.add_state('r', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='r_dot', targets=['r'], units='DU') coast.add_state('theta', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', targets=['theta'], units='rad') coast.add_state('vr', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='vr_dot', targets=['vr'], units='DU/TU') coast.add_state('vt', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='vt_dot', targets=['vt'], units='DU/TU') coast.add_state('accel', fix_initial=True, fix_final=True, rate_source='at_dot', targets=['accel'], units='DU/TU**2') coast.add_state('deltav', fix_initial=False, fix_final=False, rate_source='deltav_dot', units='DU/TU') coast.add_design_parameter('u1', opt=False, val=0.0, units='deg', targets=['u1']) # Third Phase (burn) burn2 = dm.Phase(ode_class=FiniteBurnODE, transcription=t[transcription]) if connected: traj.add_phase('burn2', burn2) traj.add_phase('coast', coast) burn2.set_time_options(initial_bounds=(1.0, 60), duration_bounds=(-10.0, -0.5), initial_ref=10, units='TU') burn2.add_state('r', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='r_dot', targets=['r'], units='DU') burn2.add_state('theta', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', targets=['theta'], units='rad') burn2.add_state('vr', fix_initial=True, fix_final=False, defect_scaler=1000.0, rate_source='vr_dot', targets=['vr'], units='DU/TU') burn2.add_state('vt', fix_initial=True, fix_final=False, defect_scaler=1000.0, rate_source='vt_dot', targets=['vt'], units='DU/TU') burn2.add_state('accel', fix_initial=False, fix_final=False, defect_scaler=1.0, rate_source='at_dot', targets=['accel'], units='DU/TU**2') burn2.add_state('deltav', fix_initial=False, fix_final=False, defect_scaler=1.0, rate_source='deltav_dot', units='DU/TU') burn2.add_objective('deltav', loc='initial', scaler=100.0) burn2.add_control('u1', rate_continuity=True, rate2_continuity=True, units='deg', scaler=0.01, lower=-180, upper=180, targets=['u1']) else: traj.add_phase('coast', coast) traj.add_phase('burn2', burn2) burn2.set_time_options(initial_bounds=(0.5, 50), duration_bounds=(.5, 10), initial_ref=10, units='TU') burn2.add_state('r', fix_initial=False, fix_final=True, defect_scaler=100.0, rate_source='r_dot', targets=['r'], units='DU') burn2.add_state('theta', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', targets=['theta'], units='rad') burn2.add_state('vr', fix_initial=False, fix_final=True, defect_scaler=1000.0, rate_source='vr_dot', targets=['vr'], units='DU/TU') burn2.add_state('vt', fix_initial=False, fix_final=True, defect_scaler=1000.0, rate_source='vt_dot', targets=['vt'], units='DU/TU') burn2.add_state('accel', fix_initial=False, fix_final=False, defect_scaler=1.0, rate_source='at_dot', targets=['accel'], units='DU/TU**2') burn2.add_state('deltav', fix_initial=False, fix_final=False, defect_scaler=1.0, rate_source='deltav_dot', units='DU/TU') burn2.add_objective('deltav', loc='final', scaler=100.0) burn2.add_control('u1', rate_continuity=True, rate2_continuity=True, units='deg', scaler=0.01, targets=['u1']) burn1.add_timeseries_output('pos_x', units='DU') coast.add_timeseries_output('pos_x', units='DU') burn2.add_timeseries_output('pos_x', units='DU') burn1.add_timeseries_output('pos_y', units='DU') coast.add_timeseries_output('pos_y', units='DU') burn2.add_timeseries_output('pos_y', units='DU') # Link Phases if connected: traj.link_phases(phases=['burn1', 'coast'], vars=['time', 'r', 'theta', 'vr', 'vt', 'deltav'], connected=True) # No direct connections to the end of a phase. traj.link_phases(phases=['burn2', 'coast'], vars=['r', 'theta', 'vr', 'vt', 'deltav'], locs=('++', '++')) traj.link_phases(phases=['burn2', 'coast'], vars=['time'], locs=('++', '++')) traj.link_phases(phases=['burn1', 'burn2'], vars=['accel'], locs=('++', '++')) else: traj.link_phases(phases=['burn1', 'coast', 'burn2'], vars=['time', 'r', 'theta', 'vr', 'vt', 'deltav']) traj.link_phases(phases=['burn1', 'burn2'], vars=['accel']) return traj