def test_brachistochrone_static_gravity(self): import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm import matplotlib.pyplot as plt from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE # # Initialize the Problem and the optimization driver # p = om.Problem(model=om.Group()) p.driver = om.ScipyOptimizeDriver() p.driver.declare_coloring() # # 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, ode_init_kwargs={'static_gravity': True}, transcription=dm.Radau(num_segments=10))) # # Set the variables # phase.set_time_options(initial_bounds=(0, 0), duration_bounds=(.5, 10)) phase.add_state('x', rate_source='xdot', targets=None, units='m', fix_initial=True, fix_final=True, solve_segments=False) phase.add_state('y', rate_source='ydot', targets=None, units='m', fix_initial=True, fix_final=True, solve_segments=False) phase.add_state('v', rate_source='vdot', targets=['v'], units='m/s', fix_initial=True, fix_final=False, solve_segments=False) phase.add_control('theta', targets=['theta'], continuity=True, rate_continuity=True, units='deg', lower=0.01, upper=179.9) phase.add_design_parameter('g', targets=['g'], dynamic=False, opt=False) # # Minimize time at the end of the phase # phase.add_objective('time', loc='final', scaler=10) # # Setup the Problem # p.setup() # # Set the initial values # The initial time is fixed, and we set that fixed value here. # The optimizer is allowed to modify t_duration, but an initial guess is provided here. # p.set_val('traj.phase0.t_initial', 0.0) p.set_val('traj.phase0.t_duration', 2.0) # Guesses for states are provided at all state_input nodes. # We use the phase.interpolate method to linearly interpolate values onto the state input nodes. # Since fix_initial=True for all states and fix_final=True for x and y, the initial or final # values of the interpolation provided here will not be changed by the optimizer. p.set_val('traj.phase0.states:x', phase.interpolate(ys=[0, 10], nodes='state_input')) p.set_val('traj.phase0.states:y', phase.interpolate(ys=[10, 5], nodes='state_input')) p.set_val('traj.phase0.states:v', phase.interpolate(ys=[0, 9.9], nodes='state_input')) # Guesses for controls are provided at all control_input node. # Here phase.interpolate is used to linearly interpolate values onto the control input nodes. p.set_val('traj.phase0.controls:theta', phase.interpolate(ys=[5, 100.5], nodes='control_input')) # Set the value for gravitational acceleration. p.set_val('traj.phase0.design_parameters:g', 9.80665) # # 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() # Extract the timeseries from the implicit solution and the explicit simulation x = p.get_val('traj.phase0.timeseries.states:x') y = p.get_val('traj.phase0.timeseries.states:y') t = p.get_val('traj.phase0.timeseries.time') theta = p.get_val('traj.phase0.timeseries.controls:theta') x_exp = exp_out.get_val('traj.phase0.timeseries.states:x') y_exp = exp_out.get_val('traj.phase0.timeseries.states:y') t_exp = exp_out.get_val('traj.phase0.timeseries.time') theta_exp = exp_out.get_val('traj.phase0.timeseries.controls:theta') fig, axes = plt.subplots(nrows=2, ncols=1) axes[0].plot(x, y, 'o') axes[0].plot(x_exp, y_exp, '-') axes[0].set_xlabel('x (m)') axes[0].set_ylabel('y (m)') axes[1].plot(t, theta, 'o') axes[1].plot(t_exp, theta_exp, '-') axes[1].set_xlabel('time (s)') axes[1].set_ylabel(r'$\theta$ (deg)') plt.show()
def test_doc_ssto_linear_tangent_guidance(self): 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 from dymos.examples.plotting import plot_results 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 LinearTangentGuidanceComp(om.ExplicitComponent): """ Compute pitch angle from static controls governing linear expression for pitch angle tangent as function of time. """ def initialize(self): self.options.declare('num_nodes', types=int) def setup(self): nn = self.options['num_nodes'] self.add_input('a_ctrl', val=np.zeros(nn), desc='linear tangent slope', units='1/s') self.add_input('b_ctrl', val=np.zeros(nn), desc='tangent of theta at t=0', units=None) self.add_input('time_phase', val=np.zeros(nn), desc='time', units='s') self.add_output('theta', val=np.zeros(nn), desc='pitch angle', units='rad') # Setup partials arange = np.arange(self.options['num_nodes']) self.declare_partials(of='theta', wrt='a_ctrl', rows=arange, cols=arange, val=1.0) self.declare_partials(of='theta', wrt='b_ctrl', rows=arange, cols=arange, val=1.0) self.declare_partials(of='theta', wrt='time_phase', rows=arange, cols=arange, val=1.0) def compute(self, inputs, outputs): a = inputs['a_ctrl'] b = inputs['b_ctrl'] t = inputs['time_phase'] outputs['theta'] = np.arctan(a * t + b) def compute_partials(self, inputs, jacobian): a = inputs['a_ctrl'] b = inputs['b_ctrl'] t = inputs['time_phase'] x = a * t + b denom = x ** 2 + 1.0 jacobian['theta', 'a_ctrl'] = t / denom jacobian['theta', 'b_ctrl'] = 1.0 / denom jacobian['theta', 'time_phase'] = a / denom # @dm.declare_time(units='s', targets=['guidance.time_phase']) # @dm.declare_state('x', rate_source='eom.xdot', units='m') # @dm.declare_state('y', rate_source='eom.ydot', units='m') # @dm.declare_state('vx', rate_source='eom.vxdot', targets=['eom.vx'], units='m/s') # @dm.declare_state('vy', rate_source='eom.vydot', targets=['eom.vy'], units='m/s') # @dm.declare_state('m', rate_source='eom.mdot', targets=['eom.m'], units='kg') # @dm.declare_parameter('thrust', targets=['eom.thrust'], units='N') # @dm.declare_parameter('a_ctrl', targets=['guidance.a_ctrl'], units='1/s') # @dm.declare_parameter('b_ctrl', targets=['guidance.b_ctrl'], units=None) # @dm.declare_parameter('Isp', targets=['eom.Isp'], units='s') 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', LinearTangentGuidanceComp(num_nodes=nn)) self.add_subsystem('eom', LaunchVehicle2DEOM(num_nodes=nn)) self.connect('guidance.theta', 'eom.theta') # # Setup and solve the optimal control problem # p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.declare_coloring() traj = dm.Trajectory() p.model.add_subsystem('traj', traj) phase = dm.Phase(ode_class=LaunchVehicleLinearTangentODE, transcription=dm.GaussLobatto(num_segments=10, order=5, compressed=True)) traj.add_phase('phase0', phase) phase.set_time_options(initial_bounds=(0, 0), duration_bounds=(10, 1000), units='s', targets=['guidance.time_phase']) phase.add_state('x', fix_initial=True, lower=0, rate_source='eom.xdot', units='m') phase.add_state('y', fix_initial=True, lower=0, rate_source='eom.ydot', units='m') phase.add_state('vx', fix_initial=True, lower=0, rate_source='eom.vxdot', targets=['eom.vx'], units='m/s') phase.add_state('vy', fix_initial=True, rate_source='eom.vydot', targets=['eom.vy'], units='m/s') phase.add_state('m', fix_initial=True, rate_source='eom.mdot', targets=['eom.m'], units='kg') 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_design_parameter('a_ctrl', units='1/s', opt=True, targets=['guidance.a_ctrl']) phase.add_design_parameter('b_ctrl', units=None, opt=True, targets=['guidance.b_ctrl']) phase.add_design_parameter('thrust', units='N', opt=False, val=3.0 * 50000.0 * 1.61544, targets=['eom.thrust']) phase.add_design_parameter('Isp', units='s', opt=False, val=1.0E6, targets=['eom.Isp']) phase.add_objective('time', index=-1, scaler=0.01) p.model.linear_solver = om.DirectSolver() phase.add_timeseries_output('guidance.theta', units='deg') p.setup(check=True) p['traj.phase0.t_initial'] = 0.0 p['traj.phase0.t_duration'] = 500.0 p['traj.phase0.states:x'] = phase.interpolate(ys=[0, 350000.0], nodes='state_input') p['traj.phase0.states:y'] = phase.interpolate(ys=[0, 185000.0], nodes='state_input') p['traj.phase0.states:vx'] = phase.interpolate(ys=[0, 1627.0], nodes='state_input') p['traj.phase0.states:vy'] = phase.interpolate(ys=[1.0E-6, 0], nodes='state_input') p['traj.phase0.states:m'] = phase.interpolate(ys=[50000, 50000], nodes='state_input') p['traj.phase0.design_parameters:a_ctrl'] = -0.01 p['traj.phase0.design_parameters:b_ctrl'] = 3.0 dm.run_problem(p) # # Check the results. # assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 481, tolerance=0.01) # # Get the explitly simulated results # exp_out = traj.simulate() # # Plot the results # plot_results([('traj.phase0.timeseries.states:x', 'traj.phase0.timeseries.states:y', 'range (m)', 'altitude (m)'), ('traj.phase0.timeseries.time', 'traj.phase0.timeseries.theta', 'range (m)', 'altitude (m)')], title='Single Stage to Orbit Solution Using Linear Tangent Guidance', p_sol=p, p_sim=exp_out) plt.show()
def test_for_docs(self): prob = self.trajectory_example() assert_near_equal(prob['phase2.vm.ode_integ.velocity_final'], 1.66689857, 1e-8)
def test_get_remote(self): N = 3 class DistribComp(om.ExplicitComponent): def setup(self): rank = self.comm.rank sizes, offsets = evenly_distrib_idxs(self.comm.size, N) self.add_input('x', shape=1, src_indices=[0], distributed=True) self.add_output('y', shape=sizes[rank], distributed=True) def compute(self, inputs, outputs): rank = self.comm.rank sizes, offsets = evenly_distrib_idxs(self.comm.size, N) outputs['y'] = inputs['x']*np.ones((sizes[rank],)) if rank == 0: outputs['y'][0] = 2. class MyModel(om.Group): def setup(self): self.add_subsystem('ivc', om.IndepVarComp('x', 0.), promotes_outputs=['*']) self.add_subsystem('dst', DistribComp(), promotes_inputs=['*']) self.add_subsystem('sum', om.ExecComp('z = sum(y)', y=np.zeros((N,)), z=0.0)) self.connect('dst.y', 'sum.y', src_indices=om.slicer[:]) self.add_subsystem('par', om.ParallelGroup(), promotes_inputs=['*']) self.par.add_subsystem('c1', om.ExecComp(['y=2.0*x']), promotes_inputs=['*']) self.par.add_subsystem('c2', om.ExecComp(['y=5.0*x']), promotes_inputs=['*']) prob = om.Problem(model=MyModel()) prob.setup(mode='fwd') prob['x'] = 7.0 prob.run_model() # get_remote=True assert_near_equal(prob.get_val('x', get_remote=True), [7.]) assert_near_equal(prob.get_val('dst.x', get_remote=True), [7., 7.]) # ??????? assert_near_equal(prob.get_val('dst.y', get_remote=True), [2., 7., 7.]) assert_near_equal(prob.get_val('par.c1.x', get_remote=True), [7.]) assert_near_equal(prob.get_val('par.c1.y', get_remote=True), [14.]) assert_near_equal(prob.get_val('par.c2.x', get_remote=True), [7.]) assert_near_equal(prob.get_val('par.c2.y', get_remote=True), [35.]) self.maxDiff = None remote_msg = ("<model> <class MyModel>: Variable '{name}' is not local to rank {rank}. " "You can retrieve values from other processes using " "`get_val(<name>, get_remote=True)`.") distrib_msg = ("<model> <class MyModel>: Variable '{name}' is a distributed variable. " "You can retrieve values from all processes using " "`get_val(<name>, get_remote=True)` or from the local " "process using `get_val(<name>, get_remote=False)`.") if prob.comm.rank == 0: # # get_remote=False # # get the local part of the distributed inputs/outputs assert_near_equal(prob.get_val('dst.x', get_remote=False), [7.]) # from src ('ivc.x') assert_near_equal(prob.model.get_val('dst.x', get_remote=False, from_src=False), [7.]) assert_near_equal(prob.get_val('dst.y', get_remote=False), [2., 7.]) # par.c1 is local assert_near_equal(prob.get_val('par.c1.x', get_remote=False), [7.]) # from src ('ivc.x') assert_near_equal(prob.model.get_val('par.c1.x', get_remote=False, from_src=False), [7.]) assert_near_equal(prob.get_val('par.c1.y', get_remote=False), [14.]) # par.c2 is remote assert_near_equal(prob.get_val('par.c2.x', get_remote=False), [7.]) # from src ('ivc.x') with self.assertRaises(RuntimeError) as cm: prob.model.get_val('par.c2.x', get_remote=False, from_src=False) self.assertEqual(str(cm.exception), remote_msg.format(name='par.c2.x', rank=0)) with self.assertRaises(RuntimeError) as cm: prob.get_val('par.c2.y', get_remote=False) self.assertEqual(str(cm.exception), remote_msg.format(name='par.c2.y', rank=0)) # # get_remote=None # with self.assertRaises(RuntimeError) as cm: prob['dst.x'] self.assertEqual(str(cm.exception), distrib_msg.format(name='dst.x')) with self.assertRaises(RuntimeError) as cm: prob.model.get_val('dst.x', get_remote=None, from_src=False) self.assertEqual(str(cm.exception), distrib_msg.format(name='dst.x')) with self.assertRaises(RuntimeError) as cm: prob['dst.y'] self.assertEqual(str(cm.exception), distrib_msg.format(name='dst.y')) # par.c1 is local assert_near_equal(prob['par.c1.x'], [7.]) # from src ('ivc.x') assert_near_equal(prob.model.get_val('par.c1.x', get_remote=None, from_src=False), [7.]) assert_near_equal(prob['par.c1.y'], [14.]) # par.c2 is remote assert_near_equal(prob['par.c2.x'], [7.]) # from src ('ivc.x') with self.assertRaises(RuntimeError) as cm: prob.model.get_val('par.c2.x', get_remote=None, from_src=False) self.assertEqual(str(cm.exception), remote_msg.format(name='par.c2.x', rank=0)) with self.assertRaises(RuntimeError) as cm: prob['par.c2.y'] self.assertEqual(str(cm.exception), remote_msg.format(name='par.c2.y', rank=0)) else: # # get_remote=False # # get the local part of the distributed inputs/outputs assert_near_equal(prob.get_val('dst.x', get_remote=False), [7.]) # from src ('ivc.x') assert_near_equal(prob.model.get_val('dst.x', get_remote=False, from_src=False), [7.]) assert_near_equal(prob.get_val('dst.y', get_remote=False), [7.]) # par.c1 is remote prob.get_val('par.c1.x', get_remote=False) # from src ('ivc.x') with self.assertRaises(RuntimeError) as cm: prob.model.get_val('par.c1.x', get_remote=False, from_src=False) self.assertEqual(str(cm.exception), remote_msg.format(name='par.c1.x', rank=1)) with self.assertRaises(RuntimeError) as cm: prob.get_val('par.c1.y', get_remote=False) self.assertEqual(str(cm.exception), remote_msg.format(name='par.c1.y', rank=1)) # par.c2 is local assert_near_equal(prob.get_val('par.c2.x', get_remote=False), [7.]) # from src ('ivc.x') assert_near_equal(prob.model.get_val('par.c2.x', get_remote=False, from_src=False), [7.]) assert_near_equal(prob.get_val('par.c2.y', get_remote=False), [35.]) # # get_remote=None # with self.assertRaises(RuntimeError) as cm: prob['dst.x'] self.assertEqual(str(cm.exception), distrib_msg.format(name='dst.x')) with self.assertRaises(RuntimeError) as cm: prob.model.get_val('dst.x', get_remote=None, from_src=False) self.assertEqual(str(cm.exception), distrib_msg.format(name='dst.x')) with self.assertRaises(RuntimeError) as cm: prob['dst.y'] self.assertEqual(str(cm.exception), distrib_msg.format(name='dst.y')) # par.c1 is remote assert_near_equal(prob['par.c1.x'], [7.]) # from src ('ivc.x') with self.assertRaises(RuntimeError) as cm: prob.model.get_val('par.c1.x', get_remote=None, from_src=False) self.assertEqual(str(cm.exception), remote_msg.format(name='par.c1.x', rank=1)) with self.assertRaises(RuntimeError) as cm: prob['par.c1.y'] self.assertEqual(str(cm.exception), remote_msg.format(name='par.c1.y', rank=1)) # par.c2 is local assert_near_equal(prob['par.c2.x'], [7.]) # from src ('ivc.x') assert_near_equal(prob.model.get_val('par.c2.x', get_remote=None, from_src=False), [7.]) assert_near_equal(prob['par.c2.y'], [35.])
def test_nonlinear_circuit_analysis(self): import numpy as np import openmdao.api as om class Resistor(om.ExplicitComponent): """Computes current across a resistor using Ohm's law.""" def initialize(self): self.options.declare('R', default=1., desc='Resistance in Ohms') def setup(self): self.add_input('V_in', units='V') self.add_input('V_out', units='V') self.add_output('I', units='A') # partial derivs are constant, so we can assign their values in setup R = self.options['R'] self.declare_partials('I', 'V_in', val=1 / R) self.declare_partials('I', 'V_out', val=-1 / R) def compute(self, inputs, outputs): deltaV = inputs['V_in'] - inputs['V_out'] outputs['I'] = deltaV / self.options['R'] class Diode(om.ExplicitComponent): """Computes current across a diode using the Shockley diode equation.""" def initialize(self): self.options.declare('Is', default=1e-15, desc='Saturation current in Amps') self.options.declare('Vt', default=.025875, desc='Thermal voltage in Volts') def setup(self): self.add_input('V_in', units='V') self.add_input('V_out', units='V') self.add_output('I', units='A') # non-linear component, so we'll declare the partials here but compute them in compute_partials self.declare_partials('I', 'V_in') self.declare_partials('I', 'V_out') def compute(self, inputs, outputs): deltaV = inputs['V_in'] - inputs['V_out'] Is = self.options['Is'] Vt = self.options['Vt'] outputs['I'] = Is * (np.exp(deltaV / Vt) - 1) def compute_partials(self, inputs, J): deltaV = inputs['V_in'] - inputs['V_out'] Is = self.options['Is'] Vt = self.options['Vt'] I = Is * np.exp(deltaV / Vt) J['I', 'V_in'] = I / Vt J['I', 'V_out'] = -I / Vt class Node(om.ImplicitComponent): """Computes voltage residual across a node based on incoming and outgoing current.""" def initialize(self): self.options.declare( 'n_in', default=1, types=int, desc='number of connections with + assumed in') self.options.declare( 'n_out', default=1, types=int, desc='number of current connections + assumed out') def setup(self): self.add_output('V', val=5., units='V') for i in range(self.options['n_in']): i_name = 'I_in:{}'.format(i) self.add_input(i_name, units='A') self.declare_partials('V', i_name, val=1) for i in range(self.options['n_out']): i_name = 'I_out:{}'.format(i) self.add_input(i_name, units='A') self.declare_partials('V', i_name, val=-1) # note: we don't declare any partials wrt `V` here, # because the residual doesn't directly depend on it def apply_nonlinear(self, inputs, outputs, residuals): residuals['V'] = 0. for i_conn in range(self.options['n_in']): residuals['V'] += inputs['I_in:{}'.format(i_conn)] for i_conn in range(self.options['n_out']): residuals['V'] -= inputs['I_out:{}'.format(i_conn)] class Circuit(om.Group): def setup(self): self.add_subsystem('n1', Node(n_in=1, n_out=2), promotes_inputs=[('I_in:0', 'I_in')]) self.add_subsystem('n2', Node()) # leaving defaults self.add_subsystem('R1', Resistor(R=100.), promotes_inputs=[('V_out', 'Vg')]) self.add_subsystem('R2', Resistor(R=10000.)) self.add_subsystem('D1', Diode(), promotes_inputs=[('V_out', 'Vg')]) self.connect('n1.V', ['R1.V_in', 'R2.V_in']) self.connect('R1.I', 'n1.I_out:0') self.connect('R2.I', 'n1.I_out:1') self.connect('n2.V', ['R2.V_out', 'D1.V_in']) self.connect('R2.I', 'n2.I_in:0') self.connect('D1.I', 'n2.I_out:0') self.nonlinear_solver = om.NewtonSolver() self.linear_solver = om.DirectSolver() self.nonlinear_solver.options['iprint'] = 2 self.nonlinear_solver.options['maxiter'] = 10 self.nonlinear_solver.options['solve_subsystems'] = True self.nonlinear_solver.linesearch = om.ArmijoGoldsteinLS() self.nonlinear_solver.linesearch.options['maxiter'] = 10 self.nonlinear_solver.linesearch.options['iprint'] = 2 p = om.Problem() model = p.model model.add_subsystem('circuit', Circuit()) p.setup() p.set_val('circuit.I_in', 0.1) p.set_val('circuit.Vg', 0.) # set some initial guesses p.set_val('circuit.n1.V', 10.) p.set_val('circuit.n2.V', 1e-3) p.run_model() assert_near_equal(p['circuit.n1.V'], 9.90804735, 1e-5) assert_near_equal(p['circuit.n2.V'], 0.71278185, 1e-5) assert_near_equal(p['circuit.R1.I'], 0.09908047, 1e-5) assert_near_equal(p['circuit.R2.I'], 0.00091953, 1e-5) assert_near_equal(p['circuit.D1.I'], 0.00091953, 1e-5) # sanity check: should sum to .1 Amps assert_near_equal(p['circuit.R1.I'] + p['circuit.D1.I'], .1, 1e-6)
def test_brachistochrone_explicit_shooting_path_constraint_renamed(self): for method in ['rk4', 'ralston']: with self.subTest( f"test brachistochrone explicit shooting with method '{method}'" ): prob = om.Problem() prob.driver = om.pyOptSparseDriver(optimizer='SLSQP') tx = dm.transcriptions.ExplicitShooting( num_segments=10, grid='gauss-lobatto', method=method, order=3, num_steps_per_segment=5, compressed=True) phase = dm.Phase(ode_class=BrachistochroneODE, transcription=tx) phase.set_time_options(units='s', fix_initial=True, duration_bounds=(1.0, 10.0)) # automatically discover states phase.set_state_options('x', fix_initial=True) phase.set_state_options('y', fix_initial=True) phase.set_state_options('v', fix_initial=True) phase.add_parameter('g', val=9.80665, units='m/s**2', opt=False) phase.add_control('theta', val=45.0, units='deg', opt=True, lower=1.0E-6, upper=179.9) phase.add_boundary_constraint('x', loc='final', equals=10.0) phase.add_boundary_constraint('y', loc='final', equals=5.0) phase.add_path_constraint('ydot', constraint_name='foo', lower=-100, upper=0) prob.model.add_subsystem('phase0', phase) phase.add_objective('time', loc='final') prob.setup(force_alloc_complex=True) prob.set_val('phase0.t_initial', 0.0) prob.set_val('phase0.t_duration', 2) prob.set_val('phase0.states:x', 0.0) prob.set_val('phase0.states:y', 10.0) prob.set_val('phase0.states:v', 1.0E-6) prob.set_val('phase0.controls:theta', phase.interp('theta', ys=[0.01, 90]), units='deg') prob.run_driver() x = prob.get_val('phase0.timeseries.states:x') y = prob.get_val('phase0.timeseries.states:y') ydot = prob.get_val('phase0.timeseries.foo') assert_near_equal(x[-1, ...], 10.0, tolerance=1.0E-3) self.assertTrue( np.all(ydot <= 1.0E-6), msg='Not all elements of path constraint satisfied') assert_near_equal(y[-1, ...], 5.0, tolerance=1.0E-3) with np.printoptions(linewidth=1024): cpd = prob.check_partials(compact_print=True, method='cs', out_stream=None) assert_check_partials(cpd, atol=1.0E-5, rtol=1.0E-5)
def test_explicit_shooting_timeseries_ode_output(self): for method in ['rk4', 'ralston']: with self.subTest( f"test brachistochrone explicit shooting with method '{method}'" ): prob = om.Problem() prob.driver = om.pyOptSparseDriver(optimizer='SLSQP') tx = dm.transcriptions.ExplicitShooting( num_segments=5, grid='gauss-lobatto', method=method, order=3, num_steps_per_segment=10, compressed=True) phase = dm.Phase(ode_class=BrachistochroneODE, transcription=tx) phase.set_time_options(units='s', fix_initial=True, duration_bounds=(1.0, 10.0)) # automatically discover states phase.set_state_options('x', fix_initial=True) phase.set_state_options('y', fix_initial=True) phase.set_state_options('v', fix_initial=True) phase.add_parameter('g', val=1.0, units='m/s**2', opt=True, lower=1, upper=9.80665) phase.add_control('theta', val=45.0, units='deg', opt=True, lower=1.0E-6, upper=179.9) phase.add_boundary_constraint('x', loc='final', equals=10.0) phase.add_boundary_constraint('y', loc='final', equals=5.0) phase.add_timeseries_output('*') prob.model.add_subsystem('phase0', phase) phase.add_objective('time', loc='final') prob.setup(force_alloc_complex=True) prob.set_val('phase0.t_initial', 0.0) prob.set_val('phase0.t_duration', 2) prob.set_val('phase0.states:x', 0.0) prob.set_val('phase0.states:y', 10.0) prob.set_val('phase0.states:v', 1.0E-6) prob.set_val('phase0.parameters:g', 1.0, units='m/s**2') prob.set_val('phase0.controls:theta', phase.interp('theta', ys=[0.01, 90]), units='deg') dm.run_problem(prob, run_driver=True) x = prob.get_val('phase0.timeseries.states:x') y = prob.get_val('phase0.timeseries.states:y') v = prob.get_val('phase0.timeseries.states:v') theta = prob.get_val('phase0.timeseries.controls:theta', units='rad') check = prob.get_val('phase0.timeseries.check') t = prob.get_val('phase0.timeseries.time') assert_near_equal(x[-1, ...], 10.0, tolerance=1.0E-3) assert_near_equal(y[-1, ...], 5.0, tolerance=1.0E-3) assert_near_equal(t[-1, ...], 1.8016, tolerance=1.0E-2) assert_near_equal(check, v / np.sin(theta)) prob.model.list_outputs() with np.printoptions(linewidth=1024): cpd = prob.check_partials(method='cs', out_stream=None) assert_check_partials(cpd, atol=1.0E-5, rtol=1.0E-5)
def test_brachistochrone_recording(self): import matplotlib matplotlib.use('Agg') 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.GaussLobatto(num_segments=10)) p.model.add_subsystem('phase0', phase) phase.set_time_options(fix_initial=True, duration_bounds=(.5, 10)) phase.add_state('x', fix_initial=True, fix_final=True) phase.add_state('y', fix_initial=True, fix_final=True) phase.add_state('v', fix_initial=True) 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) 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 dm.run_problem(p) # Test the results assert_near_equal(p.get_val('phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) case = om.CaseReader('dymos_solution.db').get_case('final') outputs = dict([ (o[0], o[1]) for o in case.list_outputs(units=True, shape=True, out_stream=None) ]) assert_near_equal( p['phase0.controls:theta'], outputs['phase0.control_group.indep_controls.controls:theta'] ['value'])
def test_case1(self): self.prob = Problem() self.prob.model.set_input_defaults('fl_start.P', 17., units='psi') self.prob.model.set_input_defaults('fl_start.T', 500., units='degR') self.prob.model.set_input_defaults('fl_start.MN', 0.5) self.prob.model.set_input_defaults('fl_start.W', 100., units='lbm/s') self.prob.model.add_subsystem( 'fl_start', FlowStart(thermo_data=species_data.janaf, elements=AIR_ELEMENTS)) self.prob.set_solver_print(level=-1) self.prob.setup(check=False) np.seterr(divide='raise') # 6 cases to check against for i, data in enumerate(ref_data): self.prob['fl_start.P'] = data[h_map['Pt']] self.prob['fl_start.T'] = data[h_map['Tt']] self.prob['fl_start.W'] = data[h_map['W']] self.prob['fl_start.MN'] = data[h_map['MN']] self.prob.run_model() # check outputs tol = 1.0e-3 if data[h_map[ 'MN']] >= 2.: # The Mach 2.0 case is at a ridiculously low temperature, so accuracy is questionable tol = 5e-2 print('Case: ', data[h_map['Pt']], data[h_map['Tt']], data[h_map['W']], data[h_map['MN']]) npss = data[h_map['Pt']] pyc = self.prob['fl_start.Fl_O:tot:P'] assert_near_equal(pyc, npss, tol) npss = data[h_map['Tt']] pyc = self.prob['fl_start.Fl_O:tot:T'] rel_err = abs(npss - pyc) / npss print('Tt:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['W']] pyc = self.prob['fl_start.Fl_O:stat:W'] rel_err = abs(npss - pyc) / npss print('W:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['ht']] pyc = self.prob['fl_start.Fl_O:tot:h'] rel_err = abs(npss - pyc) / npss print('ht:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['s']] pyc = self.prob['fl_start.Fl_O:tot:S'] rel_err = abs(npss - pyc) / npss print('S:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['rhot']] pyc = self.prob['fl_start.Fl_O:tot:rho'] rel_err = abs(npss - pyc) / npss print('rhot:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['gamt']] pyc = self.prob['fl_start.Fl_O:tot:gamma'] rel_err = abs(npss - pyc) / npss print('gamt:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['MN']] pyc = self.prob['fl_start.Fl_O:stat:MN'] rel_err = abs(npss - pyc) / npss print('MN:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['Ps']] pyc = self.prob['fl_start.Fl_O:stat:P'] rel_err = abs(npss - pyc) / npss print('Ps:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['Ts']] pyc = self.prob['fl_start.Fl_O:stat:T'] rel_err = abs(npss - pyc) / npss print('Ts:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['hs']] pyc = self.prob['fl_start.Fl_O:stat:h'] rel_err = abs(npss - pyc) / npss print('hs:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['rhos']] pyc = self.prob['fl_start.Fl_O:stat:rho'] rel_err = abs(npss - pyc) / npss print('rhos:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['gams']] pyc = self.prob['fl_start.Fl_O:stat:gamma'] rel_err = abs(npss - pyc) / npss print('gams:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['V']] pyc = self.prob['fl_start.Fl_O:stat:V'] rel_err = abs(npss - pyc) / npss print('V:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) npss = data[h_map['A']] pyc = self.prob['fl_start.Fl_O:stat:area'] rel_err = abs(npss - pyc) / npss print('A:', npss, pyc, rel_err) assert_near_equal(pyc, npss, tol) print()
def test_eval_f_derivs_scalar(self): gd = dm.transcriptions.grid_data.GridData( num_segments=10, transcription='gauss-lobatto', transcription_order=3) time_options = dm.phase.options.TimeOptionsDictionary() time_options['targets'] = 't' time_options['units'] = 's' state_options = {'x': dm.phase.options.StateOptionsDictionary()} state_options['x']['shape'] = (1, ) state_options['x']['units'] = 's**2' state_options['x']['rate_source'] = 'x_dot' state_options['x']['targets'] = ['x'] param_options = {'p': dm.phase.options.ParameterOptionsDictionary()} param_options['p']['shape'] = (1, ) param_options['p']['units'] = 's**2' param_options['p']['targets'] = ['p'] control_options = {} polynomial_control_options = {} prob = om.Problem() prob.model.add_subsystem( 'fixed_step_integrator', RKIntegrationComp(SimpleODE, time_options, state_options, param_options, control_options, polynomial_control_options, grid_data=gd, num_steps_per_segment=100)) prob.setup(mode='fwd', force_alloc_complex=True) prob.set_val('fixed_step_integrator.states:x', 0.5) prob.set_val('fixed_step_integrator.t_initial', 0.0) prob.set_val('fixed_step_integrator.t_duration', 2.0) prob.set_val('fixed_step_integrator.parameters:p', 1.0) prob.final_setup() prob.run_model() x = np.array([[0.5]]) t = np.array([[0.0]]) theta = np.array([[0, 2.0, 1.0]]).T f_nom = np.zeros_like(x) f_x_fd = np.zeros_like(x) f_t_fd = np.zeros_like(x) f_theta_fd = np.zeros_like(x) intg = prob.model.fixed_step_integrator f_x = intg._f_x f_t = intg._f_t f_theta = intg._f_θ intg._eval_subprob.model.ode_eval.set_segment_index(9) intg.eval_f(x, t, theta, f_nom) intg.eval_f_derivs(x, t, theta, f_x, f_t, f_theta) step = 1.0E-8 intg.eval_f(x + step, t, theta, f_x_fd) f_x_fd = (f_x_fd - f_nom) / step assert_near_equal(f_x, f_x_fd, tolerance=1.0E-6) intg.eval_f(x, t + step, theta, f_t_fd) f_t_fd = (f_t_fd - f_nom) / step assert_near_equal(f_t, f_t_fd, tolerance=1.0E-6) theta[2] = theta[2] + step prob.model.fixed_step_integrator.eval_f(x, t, theta, f_theta_fd) f_theta_fd = (f_theta_fd - f_nom) / step assert_near_equal(f_theta.real[0, 2], f_theta_fd[0, 0], tolerance=1.0E-6)
def test_fwd_parameters_controls(self): gd = dm.transcriptions.grid_data.GridData( num_segments=5, transcription='gauss-lobatto', transcription_order=5, compressed=True) time_options = dm.phase.options.TimeOptionsDictionary() time_options['units'] = 's' state_options = { 'x': dm.phase.options.StateOptionsDictionary(), 'y': dm.phase.options.StateOptionsDictionary(), 'v': dm.phase.options.StateOptionsDictionary() } state_options['x']['shape'] = (1, ) state_options['x']['units'] = 'm' state_options['x']['rate_source'] = 'xdot' state_options['x']['targets'] = [] state_options['y']['shape'] = (1, ) state_options['y']['units'] = 'm' state_options['y']['rate_source'] = 'ydot' state_options['y']['targets'] = [] state_options['v']['shape'] = (1, ) state_options['v']['units'] = 'm/s' state_options['v']['rate_source'] = 'vdot' state_options['v']['targets'] = ['v'] param_options = {'g': dm.phase.options.ParameterOptionsDictionary()} param_options['g']['shape'] = (1, ) param_options['g']['units'] = 'm/s**2' param_options['g']['targets'] = ['g'] control_options = { 'theta': dm.phase.options.ControlOptionsDictionary() } control_options['theta']['shape'] = (1, ) control_options['theta']['units'] = 'rad' control_options['theta']['targets'] = ['theta'] polynomial_control_options = {} p = om.Problem() p.model.add_subsystem( 'fixed_step_integrator', RKIntegrationComp( ode_class=BrachistochroneODE, time_options=time_options, state_options=state_options, parameter_options=param_options, control_options=control_options, polynomial_control_options=polynomial_control_options, num_steps_per_segment=10, grid_data=gd, ode_init_kwargs=None)) p.setup(mode='fwd', force_alloc_complex=True) p.set_val('fixed_step_integrator.states:x', 0.0) p.set_val('fixed_step_integrator.states:y', 10.0) p.set_val('fixed_step_integrator.states:v', 0.0) p.set_val('fixed_step_integrator.t_initial', 0.0) p.set_val('fixed_step_integrator.t_duration', 1.8016) p.set_val('fixed_step_integrator.parameters:g', 9.80665) p.set_val('fixed_step_integrator.controls:theta', np.linspace(0.01, 100.0, 21), units='deg') p.run_model() x = p.get_val('fixed_step_integrator.states_out:x') y = p.get_val('fixed_step_integrator.states_out:y') v = p.get_val('fixed_step_integrator.states_out:v') theta = p.get_val('fixed_step_integrator.control_values:theta') # These tolerances are loose since theta is not properly spaced along the lgl nodes. assert_near_equal(x[-1, ...], 10.0, tolerance=0.1) assert_near_equal(y[-1, ...], 5.0, tolerance=0.1) assert_near_equal(v[-1, ...], 9.9, tolerance=0.1) with np.printoptions(linewidth=1024): cpd = p.check_partials(compact_print=True, method='cs', show_only_incorrect=True) assert_check_partials(cpd)
def test_doc_ssto_earth(self): import matplotlib.pyplot as plt import openmdao.api as om import dymos as dm # # Setup and solve the optimal control problem # p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.declare_coloring(tol=1.0E-12) from dymos.examples.ssto.launch_vehicle_ode import LaunchVehicleODE # # Initialize our Trajectory and Phase # traj = dm.Trajectory() phase = dm.Phase(ode_class=LaunchVehicleODE, transcription=dm.GaussLobatto(num_segments=12, order=3, compressed=False)) traj.add_phase('phase0', phase) p.model.add_subsystem('traj', traj) # # Set the options for the variables # phase.set_time_options(fix_initial=True, duration_bounds=(10, 500)) phase.add_state('x', fix_initial=True, ref=1.0E5, defect_ref=10000.0, rate_source='xdot') phase.add_state('y', fix_initial=True, ref=1.0E5, defect_ref=10000.0, rate_source='ydot') phase.add_state('vx', fix_initial=True, ref=1.0E3, defect_ref=1000.0, rate_source='vxdot') phase.add_state('vy', fix_initial=True, ref=1.0E3, defect_ref=1000.0, rate_source='vydot') phase.add_state('m', fix_initial=True, ref=1.0E3, defect_ref=100.0, rate_source='mdot') phase.add_control('theta', units='rad', lower=-1.57, upper=1.57, targets=['theta']) phase.add_parameter('thrust', units='N', opt=False, val=2100000.0, targets=['thrust']) # # Set the options for our constraints and objective # phase.add_boundary_constraint('y', loc='final', equals=1.85E5, linear=True) phase.add_boundary_constraint('vx', loc='final', equals=7796.6961) phase.add_boundary_constraint('vy', loc='final', equals=0) phase.add_objective('time', loc='final', scaler=0.01) p.model.linear_solver = om.DirectSolver() # # Setup and set initial values # 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', phase.interp('x', [0, 1.15E5])) p.set_val('traj.phase0.states:y', phase.interp('y', [0, 1.85E5])) p.set_val('traj.phase0.states:vy', phase.interp('vx', [1.0E-6, 0])) p.set_val('traj.phase0.states:m', phase.interp('vy', [117000, 1163])) p.set_val('traj.phase0.controls:theta', phase.interp('theta', [1.5, -0.76])) p.set_val('traj.phase0.parameters:thrust', 2.1, units='MN') # # Solve the Problem # dm.run_problem(p) assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 143, tolerance=0.05) assert_near_equal( p.get_val('traj.phase0.timeseries.states:y')[-1], 1.85E5, 1e-4) assert_near_equal( p.get_val('traj.phase0.timeseries.states:vx')[-1], 7796.6961, 1e-4) assert_near_equal( p.get_val('traj.phase0.timeseries.states:vy')[-1], 0, 1e-4) # # Get the explicitly 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('traj.phase0.timeseries.states:x'), p.get_val('traj.phase0.timeseries.states:y'), marker='o', ms=4, linestyle='None', label='solution') axes[0].plot(exp_out.get_val('traj.phase0.timeseries.states:x'), exp_out.get_val('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('traj.phase0.timeseries.time'), p.get_val('traj.phase0.timeseries.controls:theta'), marker='o', ms=4, linestyle='None') axes[1].plot(exp_out.get_val('traj.phase0.timeseries.time'), exp_out.get_val('traj.phase0.timeseries.controls:theta'), linestyle='-', marker=None) axes[1].set_xlabel('time (s)') axes[1].set_ylabel('theta (deg)') plt.suptitle( 'Single Stage to Orbit Solution Using Linear Tangent Guidance') fig.legend(loc='lower center', ncol=2) plt.show()
def test_brachistochrone_upstream_control(self): import numpy as np import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm import matplotlib.pyplot as plt plt.switch_backend('Agg') from dymos.examples.brachistochrone.brachistochrone_ode import BrachistochroneODE # # Define the OpenMDAO problem # p = om.Problem(model=om.Group()) # Instantiate the transcription so we can get the number of nodes from it while # building the problem. tx = dm.GaussLobatto(num_segments=10, order=3) # Add an indep var comp to provide the external control values ivc = p.model.add_subsystem('control_ivc', om.IndepVarComp(), promotes_outputs=['*']) # Add the output to provide the values of theta at the control input nodes of the transcription. ivc.add_output('theta', shape=(tx.grid_data.subset_num_nodes['control_input']), units='rad') # Add this external control as a design variable p.model.add_design_var('theta', units='rad', lower=1.0E-5, upper=np.pi) # Connect this to controls:theta in the appropriate phase. # connect calls are cached, so we can do this before we actually add the trajectory to the problem. p.model.connect('theta', 'traj.phase0.controls:theta') # # Define a Trajectory object # traj = dm.Trajectory() p.model.add_subsystem('traj', subsys=traj) # # Define a Dymos Phase object with GaussLobatto Transcription # phase = dm.Phase(ode_class=BrachistochroneODE, transcription=tx) traj.add_phase(name='phase0', phase=phase) # # Set the time options # Time has no targets in our ODE. # We fix the initial time so that the it is not a design variable in the optimization. # The duration of the phase is allowed to be optimized, but is bounded on [0.5, 10]. # phase.set_time_options(fix_initial=True, duration_bounds=(0.5, 10.0), units='s') # # Set the time options # Initial values of positions and velocity are all fixed. # The final value of position are fixed, but the final velocity is a free variable. # The equations of motion are not functions of position, so 'x' and 'y' have no targets. # The rate source points to the output in the ODE which provides the time derivative of the # given state. phase.add_state('x', fix_initial=True, fix_final=True, units='m', rate_source='xdot') phase.add_state('y', fix_initial=True, fix_final=True, units='m', rate_source='ydot') phase.add_state('v', fix_initial=True, fix_final=False, units='m/s', rate_source='vdot', targets=['v']) # Define theta as a control. # Use opt=False to allow it to be connected to an external source. # Arguments lower and upper are no longer valid for an input control. phase.add_control(name='theta', units='rad', targets=['theta'], opt=False) # Minimize final time. phase.add_objective('time', loc='final') # Set the driver. p.driver = om.ScipyOptimizeDriver() # Allow OpenMDAO to automatically determine our sparsity pattern. # Doing so can significant speed up the execution of Dymos. p.driver.declare_coloring() # Setup the problem p.setup(check=True) # Now that the OpenMDAO problem is setup, we can set the values of the states. p.set_val('traj.phase0.states:x', phase.interpolate(ys=[0, 10], nodes='state_input'), units='m') p.set_val('traj.phase0.states:y', phase.interpolate(ys=[10, 5], nodes='state_input'), units='m') p.set_val('traj.phase0.states:v', phase.interpolate(ys=[0, 5], nodes='state_input'), units='m/s') p.set_val('traj.phase0.controls:theta', phase.interpolate(ys=[90, 90], nodes='control_input'), units='deg') # Run the driver to solve the problem p.run_driver() # Test the results assert_near_equal(p.get_val('traj.phase0.timeseries.time')[-1], 1.8016, tolerance=1.0E-3) # Check the validity of our results by using scipy.integrate.solve_ivp to # integrate the solution. sim_out = traj.simulate() # Plot the results fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 4.5)) axes[0].plot(p.get_val('traj.phase0.timeseries.states:x'), p.get_val('traj.phase0.timeseries.states:y'), 'ro', label='solution') axes[0].plot(sim_out.get_val('traj.phase0.timeseries.states:x'), sim_out.get_val('traj.phase0.timeseries.states:y'), 'b-', label='simulation') axes[0].set_xlabel('x (m)') axes[0].set_ylabel('y (m/s)') axes[0].legend() axes[0].grid() axes[1].plot(p.get_val('traj.phase0.timeseries.time'), p.get_val('traj.phase0.timeseries.controls:theta', units='deg'), 'ro', label='solution') axes[1].plot(sim_out.get_val('traj.phase0.timeseries.time'), sim_out.get_val('traj.phase0.timeseries.controls:theta', units='deg'), 'b-', label='simulation') axes[1].set_xlabel('time (s)') axes[1].set_ylabel(r'$\theta$ (deg)') axes[1].legend() axes[1].grid() plt.show()
def test_finite_burn_orbit_raise(self): 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 from dymos.examples.finite_burn_orbit_raise.finite_burn_eom import FiniteBurnODE p = om.Problem(model=om.Group()) p.driver = om.pyOptSparseDriver() p.driver.options['optimizer'] = 'SLSQP' p.driver.declare_coloring() traj = dm.Trajectory() 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.GaussLobatto(num_segments=5, order=3, compressed=False)) 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', units='DU') burn1.add_state('theta', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', units='rad') burn1.add_state('vr', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='vr_dot', units='DU/TU') burn1.add_state('vt', fix_initial=True, fix_final=False, defect_scaler=100.0, rate_source='vt_dot', units='DU/TU') burn1.add_state('accel', fix_initial=True, fix_final=False, rate_source='at_dot', 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) # Second Phase (Coast) coast = dm.Phase(ode_class=FiniteBurnODE, transcription=dm.GaussLobatto(num_segments=5, order=3, compressed=False)) 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_parameter('u1', opt=False, val=0.0, units='deg', targets=['u1']) # Third Phase (burn) burn2 = dm.Phase(ode_class=FiniteBurnODE, transcription=dm.GaussLobatto(num_segments=5, order=3, compressed=False)) 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', units='DU') burn2.add_state('theta', fix_initial=False, fix_final=False, defect_scaler=100.0, rate_source='theta_dot', units='rad') burn2.add_state('vr', fix_initial=False, fix_final=True, defect_scaler=1000.0, rate_source='vr_dot', units='DU/TU') burn2.add_state('vt', fix_initial=False, fix_final=True, defect_scaler=1000.0, rate_source='vt_dot', units='DU/TU') burn2.add_state('accel', fix_initial=False, fix_final=False, defect_scaler=1.0, rate_source='at_dot', 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, lower=-90, upper=90) burn1.add_timeseries_output('pos_x') coast.add_timeseries_output('pos_x') burn2.add_timeseries_output('pos_x') burn1.add_timeseries_output('pos_y') coast.add_timeseries_output('pos_y') burn2.add_timeseries_output('pos_y') # Link Phases traj.link_phases(phases=['burn1', 'coast', 'burn2'], vars=['time', 'r', 'theta', 'vr', 'vt', 'deltav']) traj.link_phases(phases=['burn1', 'burn2'], vars=['accel']) p.model.add_subsystem('traj', subsys=traj) # Finish Problem Setup # Needed to move the direct solver down into the phases for use with MPI. # - After moving down, used fewer iterations (about 30 less) p.driver.add_recorder( om.SqliteRecorder('two_burn_orbit_raise_example.db')) p.setup(check=True, mode='fwd') # Set Initial Guesses p.set_val('traj.parameters:c', value=1.5, units='DU/TU') burn1 = p.model.traj.phases.burn1 burn2 = p.model.traj.phases.burn2 coast = p.model.traj.phases.coast 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, 3.], nodes='state_input')) p.set_val('traj.burn2.states:theta', value=burn2.interpolate(ys=[0, 4.0], nodes='state_input')) p.set_val('traj.burn2.states:vr', value=burn2.interpolate(ys=[0, 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:deltav', value=burn2.interpolate(ys=[0.1, 0.2], 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.controls:u1', value=burn2.interpolate(ys=[0, 0], nodes='control_input')) dm.run_problem(p) assert_near_equal(p.get_val('traj.burn2.states:deltav')[-1], 0.3995, tolerance=2.0E-3) # # Plot results # traj = p.model.traj exp_out = traj.simulate() fig = plt.figure(figsize=(8, 4)) fig.suptitle('Two Burn Orbit Raise Solution') ax_u1 = plt.subplot2grid((2, 2), (0, 0)) ax_deltav = plt.subplot2grid((2, 2), (1, 0)) ax_xy = plt.subplot2grid((2, 2), (0, 1), rowspan=2) span = np.linspace(0, 2 * np.pi, 100) ax_xy.plot(np.cos(span), np.sin(span), 'k--', lw=1) ax_xy.plot(3 * np.cos(span), 3 * np.sin(span), 'k--', lw=1) ax_xy.set_xlim(-4.5, 4.5) ax_xy.set_ylim(-4.5, 4.5) ax_xy.set_xlabel('x ($R_e$)') ax_xy.set_ylabel('y ($R_e$)') ax_u1.set_xlabel('time ($TU$)') ax_u1.set_ylabel('$u_1$ ($deg$)') ax_u1.grid(True) ax_deltav.set_xlabel('time ($TU$)') ax_deltav.set_ylabel('${\Delta}v$ ($DU/TU$)') ax_deltav.grid(True) t_sol = dict((phs, p.get_val('traj.{0}.timeseries.time'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) x_sol = dict((phs, p.get_val('traj.{0}.timeseries.pos_x'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) y_sol = dict((phs, p.get_val('traj.{0}.timeseries.pos_y'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) dv_sol = dict( (phs, p.get_val('traj.{0}.timeseries.states:deltav'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) u1_sol = dict((phs, p.get_val('traj.{0}.timeseries.controls:u1'.format(phs), units='deg')) for phs in ['burn1', 'burn2']) t_exp = dict( (phs, exp_out.get_val('traj.{0}.timeseries.time'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) x_exp = dict( (phs, exp_out.get_val('traj.{0}.timeseries.pos_x'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) y_exp = dict( (phs, exp_out.get_val('traj.{0}.timeseries.pos_y'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) dv_exp = dict( (phs, exp_out.get_val('traj.{0}.timeseries.states:deltav'.format(phs))) for phs in ['burn1', 'coast', 'burn2']) u1_exp = dict( (phs, exp_out.get_val('traj.{0}.timeseries.controls:u1'.format(phs), units='deg')) for phs in ['burn1', 'burn2']) for phs in ['burn1', 'coast', 'burn2']: try: ax_u1.plot(t_exp[phs], u1_exp[phs], '-', marker=None, color='C0') ax_u1.plot(t_sol[phs], u1_sol[phs], 'o', mfc='C1', mec='C1', ms=3) except KeyError: pass ax_deltav.plot(t_exp[phs], dv_exp[phs], '-', marker=None, color='C0') ax_deltav.plot(t_sol[phs], dv_sol[phs], 'o', mfc='C1', mec='C1', ms=3) ax_xy.plot(x_exp[phs], y_exp[phs], '-', marker=None, color='C0', label='explicit') ax_xy.plot(x_sol[phs], y_sol[phs], 'o', mfc='C1', mec='C1', ms=3, label='implicit') plt.show()
def test_2_input_2_fidelity(self): import numpy as np import openmdao.api as om mm = om.MultiFiMetaModelUnStructuredComp(nfi=2) mm.add_input('x', np.zeros((1, 2))) mm.add_output('y', np.zeros((1, ))) # Surrrogate model that implements the multifidelity cokriging method. mm.options['default_surrogate'] = om.MultiFiCoKrigingSurrogate( normalize=False) prob = om.Problem() prob.model.add_subsystem('mm', mm) prob.setup() x_hifi = np.array([ [0.13073587, 0.24909577], # expensive (hifi) doe [0.91915571, 0.4735261], [0.75830543, 0.13321705], [0.51760477, 0.34594101], [0.03531219, 0.77765831], [0.27249206, 0.5306115], [0.62762489, 0.65778471], [0.3914706, 0.09852519], [0.86565585, 0.85350002], [0.40806563, 0.91465314] ]) y_hifi = np.array([ 69.22687251161716, 28.427292491743817, 20.36175030334259, 7.840766670948326, 23.950783505007422, 16.0326610719367, 77.32264403894713, 26.625242780670835, 135.85615334210993, 101.43980212355875 ]) x_lofi = np.array([ [0.91430235, 0.17029894], # cheap (lowfi) doe [0.99329651, 0.76431519], [0.2012252, 0.35006032], [0.61707854, 0.90210676], [0.15113004, 0.0133355], [0.07108082, 0.55344447], [0.4483159, 0.52182902], [0.5926638, 0.06595122], [0.66305449, 0.48579608], [0.47965045, 0.7407793], [0.13073587, 0.24909577], # notice hifi doe inclusion [0.91915571, 0.4735261], [0.75830543, 0.13321705], [0.51760477, 0.34594101], [0.03531219, 0.77765831], [0.27249206, 0.5306115], [0.62762489, 0.65778471], [0.3914706, 0.09852519], [0.86565585, 0.85350002], [0.40806563, 0.91465314] ]) y_lofi = list([ 18.204898470295255, 107.66640600958577, 46.11717344625053, 186.002239934648, 135.12480249921992, 65.3605467926758, 51.72316385370553, 15.541873662737451, 72.77648156410065, 100.33324800434931, 86.69974561161716, 52.63307549174382, 34.358261803342586, 28.218996970948325, 57.280532805007425, 41.9510060719367, 107.05618533894713, 39.580998480670836, 171.46115394210995, 138.87939632355875 ]) mm.options['train:x'] = x_hifi mm.options['train:y'] = y_hifi mm.options['train:x_fi2'] = x_lofi mm.options['train:y_fi2'] = y_lofi prob.set_val('mm.x', np.array([[2. / 3., 1. / 3.]])) prob.run_model() assert_near_equal(prob.get_val('mm.y'), 26.26, tolerance=0.02)
def benchmark_case1(self): prob = om.Problem() prob.model = mp_single_spool = MPSingleSpool() prob.setup(check=False) #Define the initial design point prob.set_val('DESIGN.fc.alt', 0.0, units='ft') prob.set_val('DESIGN.fc.MN', 0.000001) prob.set_val('DESIGN.balance.T4_target', 2370.0, units='degR') prob.set_val('DESIGN.balance.pwr_target', 4000.0, units='hp') prob.set_val('DESIGN.balance.nozz_PR_target', 1.2) prob.set_val('DESIGN.comp.PR', 13.5) prob.set_val('DESIGN.comp.eff', 0.83) prob.set_val('DESIGN.turb.eff', 0.86) prob.set_val('DESIGN.pt.eff', 0.9) # Set initial guesses for balances prob['DESIGN.balance.FAR'] = 0.0175506829934 prob['DESIGN.balance.W'] = 27.265 prob['DESIGN.balance.turb_PR'] = 3.8768 prob['DESIGN.balance.pt_PR'] = 2.8148 prob['DESIGN.fc.balance.Pt'] = 14.6955113159 prob['DESIGN.fc.balance.Tt'] = 518.665288153 for i, pt in enumerate(mp_single_spool.od_pts): # initial guesses prob[pt + '.balance.W'] = 27.265 prob[pt + '.balance.FAR'] = 0.0175506829934 prob[pt + '.balance.HP_Nmech'] = 8070.0 prob[pt + '.fc.balance.Pt'] = 15.703 prob[pt + '.fc.balance.Tt'] = 558.31 prob[pt + '.turb.PR'] = 3.8768 prob[pt + '.pt.PR'] = 2.8148 prob.set_solver_print(level=-1) prob.set_solver_print(level=2, depth=1) np.seterr(divide='raise') prob.run_model() tol = 1e-5 print() reg_data = 27.265342457866705 ans = prob['DESIGN.inlet.Fl_O:stat:W'][0] print('W:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 13.5 ans = prob['DESIGN.perf.OPR'][0] print('OPR:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 0.01755077946196377 ans = prob['DESIGN.balance.FAR'][0] print('Main FAR:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 3.876811443569159 ans = prob['DESIGN.balance.turb_PR'][0] print('HPT PR:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 800.8503668285215 ans = prob['DESIGN.perf.Fg'][0] print('Fg:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 2.151092078410839 ans = prob['DESIGN.perf.TSFC'][0] print('TSFC:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 1190.1777648503974 ans = prob['DESIGN.comp.Fl_O:tot:T'][0] print('Tt3:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 25.897231212494944 ans = prob['OD.inlet.Fl_O:stat:W'][0] print('W:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 12.42972778706185 ans = prob['OD.perf.OPR'][0] print('OPR:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 0.016304387482120156 ans = prob['OD.balance.FAR'][0] print('Main FAR:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 7853.753342243985 ans = prob['OD.balance.HP_Nmech'][0] print('HP Nmech:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 696.618372248896 ans = prob['OD.perf.Fg'][0] print('Fg:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 2.5052545862974696 ans = prob['OD.perf.TSFC'][0] print('TSFC:', reg_data, ans) assert_near_equal(ans, reg_data, tol) reg_data = 1158.5197002795887 ans = prob['OD.comp.Fl_O:tot:T'][0] print('Tt3:', reg_data, ans) assert_near_equal(ans, reg_data, tol) print()
def test_2_states_run_model(self): for method in [ 'rk4', 'euler', '3/8', 'ralston', 'rkf', 'rkck', 'dopri' ]: with self.subTest( f"test brachistochrone explicit shooting with method '{method}'" ): prob = om.Problem() tx = dm.transcriptions.ExplicitShooting( num_segments=2, grid='gauss-lobatto', method=method, order=3, num_steps_per_segment=50, compressed=True) phase = dm.Phase(ode_class=Simple2StateODE, transcription=tx) phase.set_time_options(targets=['t'], units='s') # automatically discover states phase.set_state_options('x', targets=['x'], rate_source='x_dot') phase.set_state_options('y', targets=['y'], rate_source='y_dot') phase.add_parameter('p', targets=['p']) prob.model.add_subsystem('phase0', phase) prob.setup(force_alloc_complex=True) prob.set_val('phase0.t_initial', 0.0) prob.set_val('phase0.t_duration', 1.0) prob.set_val('phase0.states:x', 0.5) prob.set_val('phase0.states:y', 1.0) prob.set_val('phase0.parameters:p', 1) prob.run_model() t_f = prob.get_val('phase0.integrator.t_final') x_f = prob.get_val('phase0.integrator.states_out:x') y_f = prob.get_val('phase0.integrator.states_out:y') if method == 'euler': tol = 5.0E-2 else: tol = 1.0E-3 assert_near_equal(t_f, 1.0) assert_near_equal(x_f[-1, ...], 2.64085909, tolerance=tol) assert_near_equal(y_f[-1, ...], 0.1691691, tolerance=tol) with np.printoptions(linewidth=1024): cpd = prob.check_partials(compact_print=True, method='cs') assert_check_partials(cpd, atol=1.0E-5, rtol=1.0E-5)
def test_feature_example(self): def eval_cannonball_range(gam_init, prob, complex_step=False): """ Compute distance given initial speed and angle of cannonball. Parameters ---------- gam_init : float Initial cannonball firing angle in degrees. prob : <Problem> OpenMDAO problem that contains the equations of motion. complex_step : bool Set to True to perform complex step. Returns ------- float Negative of range in m. """ dt = 0.1 # Time step h_init = 1.0 # Height of cannon. v_init = 100.0 # Initial cannonball velocity. h_target = 0.0 # v = v_init gam = gam_init h = h_init r = 0.0 t = 0.0 if complex_step: prob.set_complex_step_mode(True) while h > h_target: # Set values prob.set_val('v', v) prob.set_val('gam', gam, units='deg') # Run the model prob.run_model() # Extract rates v_dot = prob.get_val('v_dot') gam_dot = prob.get_val('gam_dot', units='deg/s') h_dot = prob.get_val('h_dot') r_dot = prob.get_val('r_dot') h_last = h r_last = r # Euler Integration v = v + dt * v_dot gam = gam + dt * gam_dot h = h + dt * h_dot r = r + dt * r_dot t += dt # print(v, gam, h, r) # Linear interpolation between last two points to get the landing point accurate. r_final = r_last + (r - r_last) * h_last / (h_last - h) if complex_step: prob.set_complex_step_mode(False) #print(f"Distance: {r_final}, Time: {t}, Angle: {gam_init}") return -r_final def gradient_cannonball_range(gam_init, prob): """ Uses complex step to compute gradient of range wrt initial angle. Parameters ---------- gam_init : float Initial cannonball firing angle in degrees. prob : <Problem> OpenMDAO problem that contains the equations of motion. Returns ------- float Derivative of range wrt initial angle in m/deg. """ step = 1.0e-14 dr_dgam = eval_cannonball_range(gam_init + step * 1j, prob, complex_step=True) return dr_dgam.imag / step prob = om.Problem(model=CannonballODE()) prob.setup(force_alloc_complex=True) # Set constants prob.set_val('CL', 0.0) # Lift Coefficient prob.set_val('CD', 0.05) # Drag Coefficient prob.set_val('S', 0.25 * np.pi, units='ft**2') # Wetted Area (1 ft diameter ball) prob.set_val('rho', 1.225) # Atmospheric Density prob.set_val('m', 5.5) # Cannonball Mass prob.set_val('alpha', 0.0) # Angle of Attack (Not Applicable) prob.set_val('T', 0.0) # Thrust (Not Applicable) result = minimize(eval_cannonball_range, 27.0, method='SLSQP', jac=gradient_cannonball_range, args=(prob)) assert_near_equal(result['x'], 42.3810579, 1e-3)
def test_brachistochrone_explicit_shooting_polynomial_control(self): prob = om.Problem() prob.driver = om.pyOptSparseDriver(optimizer='SLSQP') tx = dm.transcriptions.ExplicitShooting(num_segments=3, grid='radau-ps', method='rk4', order=3, num_steps_per_segment=10, compressed=True) phase = dm.Phase(ode_class=BrachistochroneODE, transcription=tx) phase.set_time_options(units='s', fix_initial=True, duration_bounds=(1.0, 10.0)) # automatically discover states phase.set_state_options('x', fix_initial=True) phase.set_state_options('y', fix_initial=True) phase.set_state_options('v', fix_initial=True) phase.add_parameter('g', val=1.0, units='m/s**2', opt=True, lower=1, upper=9.80665) phase.add_polynomial_control('theta', order=9, val=45.0, units='deg', opt=True, lower=1.0E-6, upper=179.9) phase.add_boundary_constraint('x', loc='final', equals=10.0) phase.add_boundary_constraint('y', loc='final', equals=5.0) prob.model.add_subsystem('phase0', phase) phase.add_objective('time', loc='final') prob.setup(force_alloc_complex=True) prob.set_val('phase0.t_initial', 0.0) prob.set_val('phase0.t_duration', 2) prob.set_val('phase0.states:x', 0.0) prob.set_val('phase0.states:y', 10.0) prob.set_val('phase0.states:v', 1.0E-6) prob.set_val('phase0.parameters:g', 1.0, units='m/s**2') prob.set_val('phase0.polynomial_controls:theta', phase.interp('theta', ys=[0.01, 90]), units='deg') prob.run_driver() x = prob.get_val('phase0.timeseries.states:x') y = prob.get_val('phase0.timeseries.states:y') t = prob.get_val('phase0.timeseries.time') tp = prob.get_val('phase0.timeseries.time_phase') assert_near_equal(x[-1, ...], 10.0, tolerance=1.0E-5) assert_near_equal(y[-1, ...], 5.0, tolerance=1.0E-5) assert_near_equal(t[-1, ...], 1.8016, tolerance=5.0E-3) assert_near_equal(tp[-1, ...], 1.8016, tolerance=5.0E-3) with np.printoptions(linewidth=1024): cpd = prob.check_partials(compact_print=False, method='cs', out_stream=None) assert_check_partials(cpd, atol=1.0E-5, rtol=1.0E-5)
def test_two_phase_cannonball_for_docs(self): import numpy as np from scipy.interpolate import interp1d import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal import dymos as dm from dymos.models.atmosphere.atmos_1976 import USatm1976Data ############################################# # Component for the design part of the model ############################################# class CannonballSizeComp(om.ExplicitComponent): """ Compute the area and mass of a cannonball with a given radius and density. Notes ----- This component is not vectorized with 'num_nodes' as is the usual way with Dymos, but is instead intended to compute a scalar mass and reference area from scalar radius and density inputs. This component does not reside in the ODE but instead its outputs are connected to the trajectory via input design parameters. """ def setup(self): self.add_input(name='radius', val=1.0, desc='cannonball radius', units='m') self.add_input(name='dens', 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='S', shape=(1, ), desc='aerodynamic reference area', units='m**2') self.declare_partials(of='mass', wrt='dens') self.declare_partials(of='mass', wrt='radius') self.declare_partials(of='S', wrt='radius') def compute(self, inputs, outputs): radius = inputs['radius'] dens = inputs['dens'] outputs['mass'] = (4 / 3.) * dens * np.pi * radius**3 outputs['S'] = np.pi * radius**2 def compute_partials(self, inputs, partials): radius = inputs['radius'] dens = inputs['dens'] partials['mass', 'dens'] = (4 / 3.) * np.pi * radius**3 partials['mass', 'radius'] = 4. * dens * np.pi * radius**2 partials['S', 'radius'] = 2 * np.pi * radius ############################################# # Build the ODE class ############################################# 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('m', units='kg') self.add_input('S', units='m**2') # 0.5 good assumption for a sphere self.add_input('CD', 0.5) # time varying inputs self.add_input('h', 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', tags=['dymos.state_rate_source:v']) self.add_output('gam_dot', shape=nn, units='rad/s', tags=['dymos.state_rate_source:gam']) self.add_output('h_dot', shape=nn, units='m/s', tags=['dymos.state_rate_source:h']) self.add_output('r_dot', shape=nn, units='m/s', tags=['dymos.state_rate_source:r']) 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, and use # a graph coloring algorithm to automatically detect the sparsity pattern. self.declare_coloring(wrt='*', method='cs') alt_data = USatm1976Data.alt * om.unit_conversion('ft', 'm')[0] rho_data = USatm1976Data.rho * om.unit_conversion( 'slug/ft**3', 'kg/m**3')[0] self.rho_interp = interp1d(np.array(alt_data, dtype=complex), np.array(rho_data, dtype=complex), kind='linear') def compute(self, inputs, outputs): gam = inputs['gam'] v = inputs['v'] h = inputs['h'] m = inputs['m'] S = inputs['S'] CD = inputs['CD'] GRAVITY = 9.80665 # m/s**2 # handle complex-step gracefully from the interpolant if np.iscomplexobj(h): rho = self.rho_interp(inputs['h']) else: rho = self.rho_interp(inputs['h']).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 ############################################# # Setup the Dymos problem ############################################# 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). # The output of the ODE which provides the rate source for each state # is obtained from the tags used on those outputs in the ODE. # The units of the states are automatically inferred by multiplying the units # of those rates by the time units. ascent.set_time_options(fix_initial=True, duration_bounds=(1, 100), 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', units='m**2', static_target=True) ascent.add_parameter('m', units='kg', static_target=True) # 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, since # they will be linked to the final states of ascent. # Final altitude is fixed, because 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') 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', units='m**2', static_target=True) descent.add_parameter('m', units='kg', static_target=True) 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, static_target=True) # 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' }, static_target=True) # 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, static_target=True) # Link Phases (link time and all state variables) traj.link_phases(phases=['ascent', 'descent'], vars=['*']) # Issue Connections p.model.connect('size_comp.mass', 'traj.parameters:m') p.model.connect('size_comp.S', 'traj.parameters:S') # A linear solver at the top level can improve performance. p.model.linear_solver = om.DirectSolver() # Finish Problem Setup p.setup() ############################################# # Set constants and 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.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') 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') ##################################################### # Run the optimization and final explicit simulation ##################################################### dm.run_problem(p) assert_near_equal(p.get_val('traj.descent.states:r')[-1], 3183.25, tolerance=1.0E-2) # use the explicit simulation to check the final collocation solution accuracy exp_out = traj.simulate() ############################################# # Plot the results ############################################# rad = p.get_val('radius', units='m')[0] print(f'optimal radius: {rad} m ') mass = p.get_val('size_comp.mass', units='kg')[0] print(f'cannonball mass: {mass} kg ') angle = p.get_val('traj.ascent.timeseries.states:gam', units='deg')[0, 0] print(f'launch angle: {angle} deg') max_range = p.get_val('traj.descent.timeseries.states:r')[-1, 0] print(f'maximum range: {max_range} m') 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(f'traj.ascent.timeseries.states:{state}'), 'descent': p.get_val(f'traj.descent.timeseries.states:{state}') } x_exp = { 'ascent': exp_out.get_val(f'traj.ascent.timeseries.states:{state}'), 'descent': exp_out.get_val(f'traj.descent.timeseries.states:{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 = ['m', 'S'] fig, axes = plt.subplots(nrows=6, ncols=1, figsize=(12, 6)) for i, param in enumerate(params): p_imp = { 'ascent': p.get_val(f'traj.ascent.timeseries.parameters:{param}'), 'descent': p.get_val(f'traj.descent.timeseries.parameters:{param}') } p_exp = { 'ascent': exp_out.get_val(f'traj.ascent.timeseries.parameters:{param}'), 'descent': exp_out.get_val(f'traj.descent.timeseries.parameters:{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_default_vals_stick(self): """ Make the balanced field problem without any set_val calls after setup. """ p = om.Problem() # First Phase: Brake release to V1 - both engines operable br_to_v1 = dm.Phase(ode_class=BalancedFieldODEComp, transcription=dm.Radau(num_segments=3), ode_init_kwargs={'mode': 'runway'}) br_to_v1.set_time_options(fix_initial=True, duration_bounds=(1, 1000), duration_ref=10.0, initial_val=0.0, duration_val=35.0) br_to_v1.add_state('r', fix_initial=True, lower=0, ref=1000.0, defect_ref=1000.0, val=br_to_v1.interpolate(ys=[0, 2500.0], nodes='state_input')) br_to_v1.add_state('v', fix_initial=True, lower=0.0001, ref=100.0, defect_ref=100.0, val=br_to_v1.interpolate(ys=[0.0001, 100.0], nodes='state_input')) br_to_v1.add_parameter('alpha', val=0.0, opt=False, units='deg') br_to_v1.add_timeseries_output('*') # Second Phase: Rejected takeoff at V1 - no engines operable rto = dm.Phase(ode_class=BalancedFieldODEComp, transcription=dm.Radau(num_segments=3), ode_init_kwargs={'mode': 'runway'}) rto.set_time_options(fix_initial=False, duration_bounds=(1, 1000), duration_ref=1.0, initial_val=35.0, duration_val=35.0) rto.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0, val=rto.interpolate(ys=[2500, 5000.0], nodes='state_input')) rto.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0, val=rto.interpolate(ys=[110, 0.0001], nodes='state_input')) rto.add_parameter('alpha', val=0.0, opt=False, units='deg') rto.add_timeseries_output('*') # Third Phase: V1 to Vr - single engine operable v1_to_vr = dm.Phase(ode_class=BalancedFieldODEComp, transcription=dm.Radau(num_segments=3), ode_init_kwargs={'mode': 'runway'}) v1_to_vr.set_time_options(fix_initial=False, duration_bounds=(1, 1000), duration_ref=1.0, initial_val=35.0, duration_val=35.0) v1_to_vr.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0, val=v1_to_vr.interpolate(ys=[2500, 300.0], nodes='state_input')) v1_to_vr.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0, val=v1_to_vr.interpolate(ys=[100, 110.0], nodes='state_input')) v1_to_vr.add_parameter('alpha', val=0.0, opt=False, units='deg') v1_to_vr.add_timeseries_output('*') # Fourth Phase: Rotate - single engine operable rotate = dm.Phase(ode_class=BalancedFieldODEComp, transcription=dm.Radau(num_segments=3), ode_init_kwargs={'mode': 'runway'}) rotate.set_time_options(fix_initial=False, duration_bounds=(1.0, 5), duration_ref=1.0, initial_val=70.0, duration_val=5.0) rotate.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0, val=rotate.interpolate(ys=[1750, 1800.0], nodes='state_input')) rotate.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0, val=rotate.interpolate(ys=[80, 85.0], nodes='state_input')) rotate.add_polynomial_control('alpha', order=1, opt=True, units='deg', lower=0, upper=10, ref=10, val=[0, 10]) rotate.add_timeseries_output('*') # Fifth Phase: Climb to target speed and altitude at end of runway. climb = dm.Phase(ode_class=BalancedFieldODEComp, transcription=dm.Radau(num_segments=5), ode_init_kwargs={'mode': 'climb'}) climb.set_time_options(fix_initial=False, duration_bounds=(1, 100), duration_ref=1.0, initial_val=75.0, duration_val=10.0) climb.add_state('r', fix_initial=False, lower=0, ref=1000.0, defect_ref=1000.0, val=climb.interpolate(ys=[1800, 2500.0], nodes='state_input')) climb.add_state('h', fix_initial=True, lower=0.0, ref=1.0, defect_ref=1.0, val=0.0) climb.add_state('v', fix_initial=False, lower=0.0001, ref=100.0, defect_ref=100.0, val=climb.interpolate(ys=[85, 90], nodes='state_input')) climb.add_state('gam', fix_initial=True, lower=0.0, ref=0.05, defect_ref=0.05, val=climb.interpolate(ys=[0, 0.05], nodes='state_input')) climb.add_control('alpha', opt=True, units='deg', lower=-10, upper=15, ref=10, val=climb.interpolate(ys=[0.01, 0.01], nodes='control_input')) climb.add_timeseries_output('*') # Instantiate the trajectory and add phases traj = dm.Trajectory() p.model.add_subsystem('traj', traj) traj.add_phase('br_to_v1', br_to_v1) traj.add_phase('rto', rto) traj.add_phase('v1_to_vr', v1_to_vr) traj.add_phase('rotate', rotate) traj.add_phase('climb', climb) # Add parameters common to multiple phases to the trajectory traj.add_parameter('m', val=174200., opt=False, units='lbm', desc='aircraft mass', targets={'br_to_v1': ['m'], 'v1_to_vr': ['m'], 'rto': ['m'], 'rotate': ['m'], 'climb': ['m']}) traj.add_parameter('T_nominal', val=27000 * 2, opt=False, units='lbf', dynamic=False, desc='nominal aircraft thrust', targets={'br_to_v1': ['T']}) traj.add_parameter('T_engine_out', val=27000, opt=False, units='lbf', dynamic=False, desc='thrust under a single engine', targets={'v1_to_vr': ['T'], 'rotate': ['T'], 'climb': ['T']}) traj.add_parameter('T_shutdown', val=0.0, opt=False, units='lbf', dynamic=False, desc='thrust when engines are shut down for rejected takeoff', targets={'rto': ['T']}) traj.add_parameter('mu_r_nominal', val=0.03, opt=False, units=None, dynamic=False, desc='nominal runway friction coeffcient', targets={'br_to_v1': ['mu_r'], 'v1_to_vr': ['mu_r'], 'rotate': ['mu_r']}) traj.add_parameter('mu_r_braking', val=0.3, opt=False, units=None, dynamic=False, desc='runway friction coefficient under braking', targets={'rto': ['mu_r']}) traj.add_parameter('h_runway', val=0., opt=False, units='ft', dynamic=True, desc='runway altitude', targets={'br_to_v1': ['h'], 'v1_to_vr': ['h'], 'rto': ['h'], 'rotate': ['h']}) traj.add_parameter('rho', val=1.225, opt=False, units='kg/m**3', dynamic=False, desc='atmospheric density', targets={'br_to_v1': ['rho'], 'v1_to_vr': ['rho'], 'rto': ['rho'], 'rotate': ['rho']}) traj.add_parameter('S', val=124.7, opt=False, units='m**2', dynamic=False, desc='aerodynamic reference area', targets={'br_to_v1': ['S'], 'v1_to_vr': ['S'], 'rto': ['S'], 'rotate': ['S'], 'climb': ['S']}) traj.add_parameter('CD0', val=0.03, opt=False, units=None, dynamic=False, desc='zero-lift drag coefficient', targets={f'{phase}': ['CD0'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) traj.add_parameter('AR', val=9.45, opt=False, units=None, dynamic=False, desc='wing aspect ratio', targets={f'{phase}': ['AR'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) traj.add_parameter('e', val=801, opt=False, units=None, dynamic=False, desc='Oswald span efficiency factor', targets={f'{phase}': ['e'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) traj.add_parameter('span', val=35.7, opt=False, units='m', dynamic=False, desc='wingspan', targets={f'{phase}': ['span'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) traj.add_parameter('h_w', val=1.0, opt=False, units='m', dynamic=False, desc='height of wing above CG', targets={f'{phase}': ['h_w'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) traj.add_parameter('CL0', val=0.5, opt=False, units=None, dynamic=False, desc='zero-alpha lift coefficient', targets={f'{phase}': ['CL0'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) traj.add_parameter('CL_max', val=2.0, opt=False, units=None, dynamic=False, desc='maximum lift coefficient for linear fit', targets={f'{phase}': ['CL_max'] for phase in ['br_to_v1', 'v1_to_vr', 'rto', 'rotate' 'climb']}) # Standard "end of first phase to beginning of second phase" linkages traj.link_phases(['br_to_v1', 'v1_to_vr'], vars=['time', 'r', 'v']) traj.link_phases(['v1_to_vr', 'rotate'], vars=['time', 'r', 'v', 'alpha']) traj.link_phases(['rotate', 'climb'], vars=['time', 'r', 'v', 'alpha']) traj.link_phases(['br_to_v1', 'rto'], vars=['time', 'r', 'v']) # Less common "final value of r must be the match at ends of two phases". traj.add_linkage_constraint(phase_a='rto', var_a='r', loc_a='final', phase_b='climb', var_b='r', loc_b='final', ref=1000) # Define the constraints and objective for the optimal control problem rto.add_boundary_constraint('v', loc='final', upper=0.001, ref=100, linear=True) rotate.add_boundary_constraint('F_r', loc='final', equals=0, ref=100000) climb.add_boundary_constraint('h', loc='final', equals=35, ref=35, units='ft', linear=True) climb.add_boundary_constraint('gam', loc='final', equals=5, ref=5, units='deg', linear=True) climb.add_path_constraint('gam', lower=0, upper=5, ref=5, units='deg') climb.add_boundary_constraint('v_over_v_stall', loc='final', lower=1.2, ref=1.2) rto.add_objective('r', loc='final', ref=1000.0) # # Setup the problem and set the initial guess # p.setup(check=True) p.run_model() assert_near_equal(p.get_val('traj.rotate.t_initial'), 70) assert_near_equal(p.get_val('traj.rotate.t_duration'), 5) assert_near_equal(p.get_val('traj.rotate.polynomial_controls:alpha'), np.array([[0, 10]]).T) assert_near_equal(p.get_val('traj.climb.controls:alpha'), p.model.traj.phases.climb.interpolate(ys=[0.01, 0.01], nodes='control_input')) assert_near_equal(p.get_val('traj.climb.states:gam'), p.model.traj.phases.climb.interpolate(ys=[0.0, 0.05], nodes='state_input')) assert_near_equal(p.get_val('traj.climb.states:h'), p.model.traj.phases.climb.interpolate(ys=[0.0, 0.0], nodes='state_input')) assert_near_equal(p.get_val('traj.v1_to_vr.parameters:alpha'), 0.0)
def test_dup_par_par_derivs(self): # duplicated output, parallel input prob = om.Problem() model = prob.model model.add_subsystem('indep', om.IndepVarComp('x', 1.0), promotes=['x']) par = model.add_subsystem('par', om.ParallelGroup(), promotes=['x']) par.add_subsystem('C1', om.ExecComp('y = 2.5 * x'), promotes=['x']) par.add_subsystem('C2', om.ExecComp('y = 7 * x'), promotes=['x']) model.add_design_var('x') model.add_constraint('par.C1.y', upper=0.0, parallel_deriv_color='parc') model.add_constraint('par.C2.y', upper=0.0, parallel_deriv_color='parc') prob.model.linear_solver = om.LinearBlockGS() prob.setup(check=False, mode='rev') prob.set_solver_print(level=0) prob.run_model() assert_near_equal(prob.get_val('par.C1.y', get_remote=True), 2.5, 1e-6) assert_near_equal(prob.get_val('par.C2.y', get_remote=True), 7., 1e-6) J = prob.driver._compute_totals() assert_near_equal(J['par.C1.y', 'indep.x'][0][0], 2.5, 1e-6) assert_near_equal(prob.get_val('par.C1.y', get_remote=True), 2.5, 1e-6) assert_near_equal(J['par.C2.y', 'indep.x'][0][0], 7., 1e-6) assert_near_equal(prob.get_val('par.C2.y', get_remote=True), 7., 1e-6)
def test_feature_vectorized_derivs(self): import numpy as np import openmdao.api as om SIZE = 5 class ExpensiveAnalysis(om.ExplicitComponent): def setup(self): self.add_input('x', val=np.ones(SIZE)) self.add_input('y', val=np.ones(SIZE)) self.add_output('f', shape=1) self.declare_partials('f', 'x') self.declare_partials('f', 'y') def compute(self, inputs, outputs): outputs['f'] = np.sum(inputs['x']**inputs['y']) def compute_partials(self, inputs, J): J['f', 'x'] = inputs['y']*inputs['x']**(inputs['y']-1) J['f', 'y'] = (inputs['x']**inputs['y'])*np.log(inputs['x']) class CheapConstraint(om.ExplicitComponent): def setup(self): self.add_input('y', val=np.ones(SIZE)) self.add_output('g', shape=SIZE) row_col = np.arange(SIZE, dtype=int) self.declare_partials('g', 'y', rows=row_col, cols=row_col) self.limit = 2*np.arange(SIZE) def compute(self, inputs, outputs): outputs['g'] = inputs['y']**2 - self.limit def compute_partials(self, inputs, J): J['g', 'y'] = 2*inputs['y'] p = om.Problem() dvs = p.model.add_subsystem('des_vars', om.IndepVarComp(), promotes=['*']) dvs.add_output('x', 2*np.ones(SIZE)) dvs.add_output('y', 2*np.ones(SIZE)) p.model.add_subsystem('obj', ExpensiveAnalysis(), promotes=['x', 'y', 'f']) p.model.add_subsystem('constraint', CheapConstraint(), promotes=['y', 'g']) p.model.add_design_var('x', lower=.1, upper=10000) p.model.add_design_var('y', lower=-1000, upper=10000) p.model.add_constraint('g', upper=0, vectorize_derivs=True) p.model.add_objective('f') p.setup(mode='rev') p.run_model() p.driver = om.ScipyOptimizeDriver() p.run_driver() assert_near_equal(p['x'], [0.10000691, 0.1, 0.1, 0.1, 0.1], 1e-5) assert_near_equal(p['y'], [0, 1.41421, 2.0, 2.44948, 2.82842], 1e-5)
def test_dup_par(self, mode, auto): # duplicated output, parallel input prob = om.Problem() model = prob.model if not auto: model.add_subsystem('indep', om.IndepVarComp('x', 1.0), promotes=['x']) par = model.add_subsystem('par', om.ParallelGroup(), promotes=['x']) par.add_subsystem('C1', om.ExecComp('y = 2.5 * x'), promotes=['x']) par.add_subsystem('C2', om.ExecComp('y = 7 * x'), promotes=['x']) wrt = ['x'] of = ['par.C1.y', 'par.C2.y'] prob.model.linear_solver = om.LinearRunOnce() prob.setup(check=False, mode=mode) prob.set_solver_print(level=0) prob.run_model() assert_near_equal(prob.get_val('par.C1.y', get_remote=True), 2.5, 1e-6) assert_near_equal(prob.get_val('par.C2.y', get_remote=True), 7., 1e-6) J = prob.compute_totals(of=of, wrt=wrt) assert_near_equal(J['par.C1.y', 'x'][0][0], 2.5, 1e-6) assert_near_equal(J['par.C2.y', 'x'][0][0], 7., 1e-6) assert_near_equal(prob.get_val('par.C1.y', get_remote=True), 2.5, 1e-6) assert_near_equal(prob.get_val('par.C2.y', get_remote=True), 7., 1e-6)
def test_case1(self): self.prob = Problem() cycle = self.prob.model = Cycle() cycle.options['thermo_method'] = 'CEA' cycle.options['thermo_data'] = janaf cycle.add_subsystem('flow_start', FlowStart()) cycle.add_subsystem('nozzle', Nozzle(lossCoef='Cfg', internal_solver=True)) cycle.set_input_defaults('nozzle.Ps_exhaust', 10.0, units='lbf/inch**2') cycle.set_input_defaults('flow_start.MN', 0.0) cycle.set_input_defaults('flow_start.T', 500.0, units='degR') cycle.set_input_defaults('flow_start.P', 17.0, units='psi') cycle.set_input_defaults('flow_start.W', 100.0, units='lbm/s') cycle.pyc_connect_flow("flow_start.Fl_O", "nozzle.Fl_I") self.prob.setup(check=False, force_alloc_complex=True) # 4 cases to check against for i, data in enumerate(ref_data): self.prob['nozzle.Cfg'] = data[h_map['Cfg']] self.prob['nozzle.Ps_exhaust'] = data[h_map['PsExh']] # input flowstation self.prob['flow_start.P'] = data[h_map['Fl_I.Pt']] self.prob['flow_start.T'] = data[h_map['Fl_I.Tt']] self.prob['flow_start.W'] = data[h_map['Fl_I.W']] self.prob['flow_start.MN'] = data[h_map['Fl_I.MN']] self.prob.run_model() # check outputs Fg, V, PR = data[h_map['Fg']], data[ h_map['Vactual']], data[h_map['PR']] MN = data[h_map['Fl_O.MN']] Ath = data[h_map['Ath']] Pt = data[h_map['Fl_O.Pt']] MN_computed = self.prob['nozzle.Fl_O:stat:MN'] Fg_computed = self.prob['nozzle.Fg'] V_computed = self.prob['nozzle.Fl_O:stat:V'] PR_computed = self.prob['nozzle.PR'] Ath_computed = self.prob['nozzle.Fl_O:stat:area'] Pt_computed = self.prob['nozzle.Fl_O:tot:P'] # Used for all tol = 5.e-3 assert_near_equal(MN_computed, MN, tol) assert_near_equal(Fg_computed, Fg, tol) assert_near_equal(V_computed, V, tol) assert_near_equal(Pt_computed, Pt, tol) assert_near_equal(PR_computed, PR, tol) assert_near_equal(Ath_computed, Ath, tol) partial_data = self.prob.check_partials(out_stream=None, method='cs', includes=['nozzle.*'], excludes=['*.base_thermo.*',]) assert_check_partials(partial_data, atol=1e-8, rtol=1e-8)
def test_continuity_comp(self): for transcription, compressed in params_list: with self.subTest(): dm.options['include_check_partials'] = True num_seg = 3 gd = GridData(num_segments=num_seg, transcription_order=[5, 3, 3], segment_ends=[0.0, 3.0, 10.0, 20], transcription=transcription, compressed=compressed == 'compressed') self.p = om.Problem(model=om.Group()) ivp = self.p.model.add_subsystem('ivc', subsys=om.IndepVarComp(), promotes_outputs=['*']) nn = gd.subset_num_nodes['all'] ivp.add_output('x', val=np.arange(nn), units='m') ivp.add_output('y', val=np.arange(nn), units='m/s') ivp.add_output('u', val=np.zeros((nn, 3)), units='deg') ivp.add_output('v', val=np.arange(nn), units='N') ivp.add_output('u_rate', val=np.zeros((nn, 3)), units='deg/s') ivp.add_output('v_rate', val=np.arange(nn), units='N/s') ivp.add_output('u_rate2', val=np.zeros((nn, 3)), units='deg/s**2') ivp.add_output('v_rate2', val=np.arange(nn), units='N/s**2') ivp.add_output('t_duration', val=121.0, units='s') self.p.model.add_design_var('x', lower=0, upper=100) state_options = { 'x': StateOptionsDictionary(), 'y': StateOptionsDictionary() } control_options = { 'u': ControlOptionsDictionary(), 'v': ControlOptionsDictionary() } state_options['x']['units'] = 'm' state_options['y']['units'] = 'm/s' # Need these for later in the test. state_options['x']['shape'] = (1, ) state_options['y']['shape'] = (1, ) control_options['u']['units'] = 'deg' control_options['u']['shape'] = (3, ) control_options['u']['continuity'] = True control_options['v']['units'] = 'N' # Need these for later in the test. state_options['x']['shape'] = (1, ) state_options['y']['shape'] = (1, ) control_options['v']['shape'] = (1, ) control_options['u']['rate_continuity'] = True control_options['v']['rate_continuity'] = True control_options['u']['rate2_continuity'] = True control_options['v']['rate2_continuity'] = True if transcription == 'gauss-lobatto': cnty_comp = GaussLobattoContinuityComp( grid_data=gd, time_units='s', state_options=state_options, control_options=control_options) elif transcription == 'radau-ps': cnty_comp = RadauPSContinuityComp( grid_data=gd, time_units='s', state_options=state_options, control_options=control_options) else: raise ValueError('unrecognized transcription') self.p.model.add_subsystem('cnty_comp', subsys=cnty_comp) # The sub-indices of state_disc indices that are segment ends num_seg_ends = gd.subset_num_nodes['segment_ends'] segment_end_idxs = np.reshape( gd.subset_node_indices['segment_ends'], newshape=(num_seg_ends, 1)) if compressed != 'compressed': self.p.model.connect('x', 'cnty_comp.states:x', src_indices=(segment_end_idxs, )) self.p.model.connect('y', 'cnty_comp.states:y', src_indices=(segment_end_idxs, )) self.p.model.connect('t_duration', 'cnty_comp.t_duration') size_u = nn * np.prod(control_options['u']['shape']) src_idxs_u = np.arange(size_u).reshape( (nn, ) + control_options['u']['shape']) src_idxs_u = src_idxs_u[gd.subset_node_indices['segment_ends'], ...] size_v = nn * np.prod(control_options['v']['shape']) src_idxs_v = np.arange(size_v).reshape( (nn, ) + control_options['v']['shape']) src_idxs_v = src_idxs_v[gd.subset_node_indices['segment_ends'], ...] src_idxs_u = (src_idxs_u, ) src_idxs_v = (src_idxs_v, ) # if transcription =='radau-ps' or compressed != 'compressed': self.p.model.connect('u', 'cnty_comp.controls:u', src_indices=src_idxs_u, flat_src_indices=True) self.p.model.connect('u_rate', 'cnty_comp.control_rates:u_rate', src_indices=src_idxs_u, flat_src_indices=True) self.p.model.connect('u_rate2', 'cnty_comp.control_rates:u_rate2', src_indices=src_idxs_u, flat_src_indices=True) # if transcription =='radau-ps' or compressed != 'compressed': self.p.model.connect('v', 'cnty_comp.controls:v', src_indices=src_idxs_v, flat_src_indices=True) self.p.model.connect('v_rate', 'cnty_comp.control_rates:v_rate', src_indices=src_idxs_v, flat_src_indices=True) self.p.model.connect('v_rate2', 'cnty_comp.control_rates:v_rate2', src_indices=src_idxs_v, flat_src_indices=True) self.p.setup(check=True, force_alloc_complex=True) self.p['x'] = np.random.rand(*self.p['x'].shape) self.p['y'] = np.random.rand(*self.p['y'].shape) self.p['u'] = np.random.rand(*self.p['u'].shape) self.p['v'] = np.random.rand(*self.p['v'].shape) self.p['u_rate'] = np.random.rand(*self.p['u'].shape) self.p['v_rate'] = np.random.rand(*self.p['v'].shape) self.p['u_rate2'] = np.random.rand(*self.p['u'].shape) self.p['v_rate2'] = np.random.rand(*self.p['v'].shape) self.p.run_model() if compressed != 'compressed': for state in ('x', 'y'): xpectd = self.p[state][segment_end_idxs, ...][2::2, ...] - \ self.p[state][segment_end_idxs, ...][1:-1:2, ...] assert_near_equal( self.p['cnty_comp.defect_states:{0}'.format( state)], xpectd.reshape((num_seg - 1, ) + state_options[state]['shape'])) for ctrl in ('u', 'v'): xpectd = self.p[ctrl][segment_end_idxs, ...][2::2, ...] - \ self.p[ctrl][segment_end_idxs, ...][1:-1:2, ...] if compressed != 'compressed': assert_near_equal( self.p['cnty_comp.defect_controls:{0}'.format( ctrl)], xpectd.reshape((num_seg - 1, ) + control_options[ctrl]['shape'])) np.set_printoptions(linewidth=1024) cpd = self.p.check_partials(method='cs', out_stream=None) assert_check_partials(cpd)
def test_results(self): assert_near_equal(self.p['ic.ode_integ.f'], 0.0*np.ones((self.nn,))) assert_near_equal(self.p['ic.ode_integ.f2'], np.linspace(0, 5, self.nn))
[0.86565585, 0.85350002], [0.40806563, 0.91465314] ] ] y = np.array([[branin(case) for case in x[0]], [branin_low_fidelity(case) for case in x[1]]]) mm.options['train:x'] = x[0] mm.options['train:y'] = y[0] mm.options['train:x_fi2'] = x[1] mm.options['train:y_fi2'] = y[1] prob['mm.x'] = np.array([[2. / 3., 1. / 3.]]) prob.run_model() assert_near_equal(prob['mm.y'], 26.26, tolerance=0.02) prob['mm.x'] = np.array([[1. / 3., 2. / 3.]]) prob.run_model() assert_near_equal(prob['mm.y'], 36.1031735, tolerance=0.02) # Now, vectorized model with both points predicted together. mm = om.MultiFiMetaModelUnStructuredComp(nfi=2, vec_size=2) mm.add_input('x', np.zeros((2, 1, 2))) mm.add_output('y', np.zeros(( 2, 1, )))
def test_results(self): self.p.run_model() x = np.linspace(0, 5, self.nn) f_exact = -10.2*x**3/3 + 4.2*x**2/2 -10.5*x f2_exact = 5.1*x**3/3 +0.5*x**2/2-7.2*x # check first phase result assert_near_equal(self.p['phase1.a.ode_integ.f'], f_exact) assert_near_equal(self.p['phase1.b.ode_integ.f'], f_exact) assert_near_equal(self.p['phase1.ode_integ.f2'], f2_exact) # check second phase result assert_near_equal(self.p['phase2.a.ode_integ.f'], f_exact+f_exact[-1]) assert_near_equal(self.p['phase2.b.ode_integ.f'], f_exact+f_exact[-1]) assert_near_equal(self.p['phase2.ode_integ.f2'], f2_exact) # check third phase result assert_near_equal(self.p['phase3.a.ode_integ.f'], f_exact+2.0*f_exact[-1]) assert_near_equal(self.p['phase3.b.ode_integ.f'], f_exact+2.0*f_exact[-1]) assert_near_equal(self.p['phase3.ode_integ.f2'], f2_exact+f2_exact[-1])
def test_doc_ssto_polynomial_control(self): 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 # p = om.Problem(model=om.Group()) traj = p.model.add_subsystem('traj', dm.Trajectory()) 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(initial_bounds=(0, 0), 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', units='m') phase.add_state('y', fix_initial=True, lower=0, rate_source='eom.ydot', units='m') 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['traj.phase0.t_initial'] = 0.0 p['traj.phase0.t_duration'] = 500.0 p['traj.phase0.states:x'] = phase.interpolate(ys=[0, 350000.0], nodes='state_input') p['traj.phase0.states:y'] = phase.interpolate(ys=[0, 185000.0], nodes='state_input') p['traj.phase0.states:vx'] = phase.interpolate(ys=[0, 1627.0], nodes='state_input') p['traj.phase0.states:vy'] = phase.interpolate(ys=[1.0E-6, 0], nodes='state_input') p['traj.phase0.states:m'] = phase.interpolate(ys=[50000, 50000], nodes='state_input') p['traj.phase0.polynomial_controls:tan_theta'] = [[0.5 * np.pi], [0.0]] # # Solve the problem. # dm.run_problem(p) # # Check the results. # assert_near_equal(p.get_val('traj.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('traj.phase0.timeseries.states:x'), p.get_val('traj.phase0.timeseries.states:y'), marker='o', ms=4, linestyle='None', label='solution') axes[0].plot(exp_out.get_val('traj.phase0.timeseries.states:x'), exp_out.get_val('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('traj.phase0.timeseries.time'), p.get_val('traj.phase0.timeseries.theta'), marker='o', ms=4, linestyle='None') axes[1].plot(exp_out.get_val('traj.phase0.timeseries.time'), exp_out.get_val('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()