diff --git a/aviary/mission/base_ode.py b/aviary/mission/base_ode.py index b3c8142601..363a8570fa 100644 --- a/aviary/mission/base_ode.py +++ b/aviary/mission/base_ode.py @@ -1,6 +1,8 @@ import openmdao.api as om +from aviary.subsystems.aerodynamics.aerodynamics_builder import AerodynamicsBuilder from aviary.subsystems.atmosphere.atmosphere import Atmosphere +from aviary.subsystems.propulsion.propulsion_builder import PropulsionBuilder from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.variable_meta_data import CoreMetaData @@ -52,31 +54,36 @@ def add_atmosphere(self, **kwargs): promotes=['*'], ) - def add_subsystems(self, solver_group=None): + def add_subsystems_and_solver( + self, solver_sub=None, couple_propulsion=False, couple_aero=False, aero_solver_sub=False + ): """ - Adds all specified subsystems to ODE in their own group. + Adds all specified subsystems to this ODE. Subsystems that need a solver due to coupling + are instead added to a group called "solver_sub". Parameters ---------- - solver_group : om.Group - If not None, subsystems that require a solver (subsystem.needs_mission_solver() == True) - are placed inside solver_group. - - If None, all subsystems are added to BaseODE regardless of if they request a solver. - TODO add solver compatibility to all ODEs - + solver_sub: None or om.Group + Pre-created group to add the solver. + couple_propulsion : bool + When True, the ODE couples with any propulsion subsystems via a throttle to commanded + thrust balance. + couple_aero : bool + When True, the ODE couples with any aerodynamics subsystems via a force balance. + aero_solver_sub : None or om.Group + Some ODEs (like solved 2DOF) place the aerodynamics and propulsion cycles in separate + groups. When this is specified, the aerodynamics subsystem is placed in this sub. Returns ------- - use_mission_solver : bool - Flag that communicates that one or more subsystem requests to be placed inside a solver - (independent of the needs of an individual ODE's setup) + om.Group + Target group for the ODE. This will be self unless a solver is needed, in which case it + will be solver_sub. """ nn = self.options['num_nodes'] aviary_options = self.options['aviary_options'] all_subsystems = self.options['subsystems'] all_subsystem_options = self.options['subsystem_options'] user_options = self.options['user_options'] - use_mission_solver = False for subsystem in all_subsystems: # check if subsystem_options has entry for a subsystem of this name @@ -100,9 +107,36 @@ def add_subsystems(self, solver_group=None): subsystem_options=subsystem_options, ) - if needs_solver and solver_group is not None: - target = solver_group - use_mission_solver = True + # ODE couples with propulsion. + if couple_propulsion and isinstance(subsystem, PropulsionBuilder): + needs_solver = True + elif couple_aero and isinstance(subsystem, AerodynamicsBuilder): + needs_solver = True + + if needs_solver: + if solver_sub is None: + solver_sub = self.add_subsystem('solver_sub', om.Group(), promotes=['*']) + solver_sub.options['auto_order'] = True + + solver_sub.nonlinear_solver = om.NewtonSolver( + solve_subsystems=True, + atol=1.0e-10, + rtol=1.0e-10, + err_on_non_converge=True, + iprint=2, + ) + solver_sub.nonlinear_solver.linesearch = om.BoundsEnforceLS() + + solver_sub.linear_solver = om.DirectSolver(assemble_jac=True) + + if ( + aero_solver_sub + and couple_aero + and isinstance(subsystem, AerodynamicsBuilder) + ): + target = aero_solver_sub + else: + target = solver_sub mission_in = subsystem.mission_inputs( aviary_inputs=aviary_options, @@ -121,4 +155,4 @@ def add_subsystems(self, solver_group=None): promotes_outputs=mission_out, ) - return use_mission_solver + return solver_sub if solver_sub else self diff --git a/aviary/mission/energy_state/ode/energy_state_ODE.py b/aviary/mission/energy_state/ode/energy_state_ODE.py index 31c05d8d08..30fa7495ea 100644 --- a/aviary/mission/energy_state/ode/energy_state_ODE.py +++ b/aviary/mission/energy_state/ode/energy_state_ODE.py @@ -58,12 +58,11 @@ def setup(self): throttle_enforcement = options['throttle_enforcement'] - sub1 = self.add_subsystem('solver_sub', om.Group(), promotes=['*']) - sub1.options['auto_order'] = True - - use_mission_solver = self.add_subsystems(solver_group=sub1) + ode_sub = self.add_subsystems_and_solver( + couple_propulsion=throttle_enforcement != 'control' + ) - sub1.add_subsystem( + ode_sub.add_subsystem( name='mission_EOM', subsys=MissionEOM(num_nodes=nn), promotes_inputs=[ @@ -89,7 +88,7 @@ def setup(self): if num_engine_type > 1: # Multi Engine - sub1.add_subsystem( + ode_sub.add_subsystem( name='throttle_balance', subsys=om.BalanceComp( name='aggregate_throttle', @@ -105,7 +104,7 @@ def setup(self): promotes_outputs=['*'], ) - sub1.add_subsystem( + ode_sub.add_subsystem( 'throttle_allocator', ThrottleAllocator( num_nodes=nn, throttle_allocation=self.options['throttle_allocation'] @@ -136,7 +135,7 @@ def setup(self): self.add_constraint('thrust_residual', ref=thrust_res_ref, equals=0.0) else: # Add a balance comp to compute throttle based on the required thrust. - sub1.add_subsystem( + ode_sub.add_subsystem( name='throttle_balance', subsys=om.BalanceComp( name=Dynamic.Vehicle.Propulsion.THROTTLE, @@ -163,16 +162,3 @@ def setup(self): self.set_input_defaults(Dynamic.Mission.VELOCITY, val=np.ones(nn), units='m/s') self.set_input_defaults(Dynamic.Mission.ALTITUDE, val=np.ones(nn), units='m') self.set_input_defaults(Dynamic.Mission.ALTITUDE_RATE, val=np.ones(nn), units='m/s') - - if use_mission_solver or throttle_enforcement != 'control': - sub1.nonlinear_solver = om.NewtonSolver( - solve_subsystems=True, - atol=1.0e-10, - rtol=1.0e-10, - ) - print_level = 2 - - sub1.nonlinear_solver.linesearch = om.BoundsEnforceLS() - sub1.linear_solver = om.DirectSolver(assemble_jac=True) - sub1.nonlinear_solver.options['err_on_non_converge'] = True - sub1.nonlinear_solver.options['iprint'] = print_level diff --git a/aviary/mission/energy_state/ode/landing_ode.py b/aviary/mission/energy_state/ode/landing_ode.py index 3482bd48ad..794687e813 100644 --- a/aviary/mission/energy_state/ode/landing_ode.py +++ b/aviary/mission/energy_state/ode/landing_ode.py @@ -51,7 +51,7 @@ def setup(self): promotes_outputs=[('stall_speed', 'v_stall')], ) - self.add_subsystems() + self.add_subsystems_and_solver() self.add_subsystem( 'landing_eom', diff --git a/aviary/mission/energy_state/ode/takeoff_ode.py b/aviary/mission/energy_state/ode/takeoff_ode.py index c369c8543d..5fa86b0fb9 100644 --- a/aviary/mission/energy_state/ode/takeoff_ode.py +++ b/aviary/mission/energy_state/ode/takeoff_ode.py @@ -55,7 +55,7 @@ def setup(self): promotes_outputs=[('stall_speed', 'v_stall')], ) - self.add_subsystems() + self.add_subsystems_and_solver() kwargs = { 'num_nodes': nn, diff --git a/aviary/mission/solved_two_dof/ode/groundroll_ode.py b/aviary/mission/solved_two_dof/ode/groundroll_ode.py index 3281eb9cec..9d1dc97ed4 100644 --- a/aviary/mission/solved_two_dof/ode/groundroll_ode.py +++ b/aviary/mission/solved_two_dof/ode/groundroll_ode.py @@ -23,10 +23,6 @@ def initialize(self): def setup(self): nn = self.options['num_nodes'] - aviary_options = self.options['aviary_options'] - subsystems = self.options['subsystems'] - subsystem_options = self.options['subsystem_options'] - user_options = self.options['user_options'] self.add_atmosphere() @@ -44,43 +40,7 @@ def setup(self): ], ) - kwargs = { - 'method': 'low_speed', - } - for subsystem in subsystems: - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs.update(subsystem_options[subsystem.name]) - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - if system is not None: - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - self.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) - - if isinstance(subsystem, AerodynamicsBuilder): - self.promotes( - subsystem.name, - inputs=[Dynamic.Vehicle.ANGLE_OF_ATTACK], - src_indices=np.zeros(nn, dtype=int), - ) + self.add_subsystems_and_solver() self.add_subsystem('groundroll_eom', GroundrollEOM(num_nodes=nn), promotes=['*']) diff --git a/aviary/mission/solved_two_dof/ode/test/test_groundroll_ode.py b/aviary/mission/solved_two_dof/ode/test/test_groundroll_ode.py index a6bbdb1593..8fdc35bba0 100644 --- a/aviary/mission/solved_two_dof/ode/test/test_groundroll_ode.py +++ b/aviary/mission/solved_two_dof/ode/test/test_groundroll_ode.py @@ -28,10 +28,13 @@ def setUp(self): 'GASP', [build_engine_deck(aviary_options)] ) + subsystem_options = {'aerodynamics': {'method': 'low_speed'}} + self.prob.model = GroundrollODE( num_nodes=2, aviary_options=get_option_defaults(), subsystems=default_mission_subsystems, + subsystem_options=subsystem_options, ) setup_model_options(self.prob, aviary_options) diff --git a/aviary/mission/solved_two_dof/ode/unsteady_solved_ode.py b/aviary/mission/solved_two_dof/ode/unsteady_solved_ode.py index 4780b481ab..5af1e3c7d7 100644 --- a/aviary/mission/solved_two_dof/ode/unsteady_solved_ode.py +++ b/aviary/mission/solved_two_dof/ode/unsteady_solved_ode.py @@ -77,10 +77,6 @@ def setup(self): nn = self.options['num_nodes'] ground_roll = self.options['ground_roll'] input_speed_type = self.options['input_speed_type'] - aviary_options = self.options['aviary_options'] - subsystem_options = self.options['subsystem_options'] - user_options = self.options['user_options'] - subsystems = self.options['subsystems'] throttle_enforcement = self.options['throttle_enforcement'] self.add_subsystem( @@ -154,60 +150,12 @@ def setup(self): throttle_balance_group.linear_solver = om.DirectSolver(assemble_jac=True) throttle_balance_group.nonlinear_solver.options['err_on_non_converge'] = True - kwargs = { - 'method': 'low_speed', - } - if self.options['clean']: - kwargs['method'] = 'cruise' - for subsystem in subsystems: - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs.update(subsystem_options[subsystem.name]) - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - if system is not None: - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - if isinstance(subsystem, AerodynamicsBuilder): - mission_inputs = mission_in.copy() - if ( - subsystem.code_origin is LegacyCode.FLOPS - and 'angle_of_attack' in mission_inputs - ): - mission_inputs.remove('angle_of_attack') - mission_inputs.append(('angle_of_attack', Dynamic.Vehicle.ANGLE_OF_ATTACK)) - control_iter_group.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_inputs, - promotes_outputs=mission_out, - ) - elif isinstance(subsystem, PropulsionBuilder): - throttle_balance_group.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) - else: - self.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) + self.add_subsystems_and_solver( + solver_sub=throttle_balance_group, + couple_propulsion=True, + couple_aero=True, + aero_solver_sub=control_iter_group, + ) eom_comp = UnsteadySolvedEOM(num_nodes=nn, ground_roll=ground_roll) diff --git a/aviary/mission/test/test_external_subsystems_in_mission.py b/aviary/mission/test/test_external_subsystems_in_mission.py index a1049e36d9..ea59fe604a 100644 --- a/aviary/mission/test/test_external_subsystems_in_mission.py +++ b/aviary/mission/test/test_external_subsystems_in_mission.py @@ -106,10 +106,9 @@ def test_mission_solver_2DOF(self): prob.run_model() - # NOTE currently 2DOF ODEs do not use the solver subsystem self.assertTrue( hasattr( - prob.model.traj.phases.cruise.rhs_all, + prob.model.traj.phases.cruise.rhs_all.solver_sub, 'solve_me', ) ) diff --git a/aviary/mission/two_dof/ode/accel_ode.py b/aviary/mission/two_dof/ode/accel_ode.py index 29780dc631..970a4439e7 100644 --- a/aviary/mission/two_dof/ode/accel_ode.py +++ b/aviary/mission/two_dof/ode/accel_ode.py @@ -26,13 +26,7 @@ def setup(self): promotes_outputs=['weight'], ) - kwargs = { - 'method': 'cruise', - 'output_alpha': True, - } - self.options['subsystem_options'].setdefault('aerodynamics', {}).update(kwargs) - - self.add_subsystems() + self.add_subsystems_and_solver() self.add_subsystem( 'accel_eom', diff --git a/aviary/mission/two_dof/ode/breguet_cruise_ode.py b/aviary/mission/two_dof/ode/breguet_cruise_ode.py index de08a2aca8..f5571ff557 100644 --- a/aviary/mission/two_dof/ode/breguet_cruise_ode.py +++ b/aviary/mission/two_dof/ode/breguet_cruise_ode.py @@ -17,10 +17,6 @@ class BreguetCruiseODE(TwoDOFODE): def setup(self): nn = self.options['num_nodes'] - aviary_options = self.options['aviary_options'] - subsystems = self.options['subsystems'] - subsystem_options = self.options['subsystem_options'] - user_options = self.options['user_options'] self.add_atmosphere(input_speed_type=SpeedType.MACH) @@ -31,49 +27,7 @@ def setup(self): promotes_outputs=['weight'], ) - prop_group = om.Group() - - for subsystem in subsystems: - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs = subsystem_options[subsystem.name] - else: - kwargs = {} - - if isinstance(subsystem, AerodynamicsBuilder): - # set default options for Aero if not specified by user - base_kwargs = {'method': 'cruise', 'output_alpha': True} - kwargs.update(base_kwargs) - - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - - if system is not None: - if isinstance(subsystem, PropulsionBuilder): - target = prop_group - else: - target = self - - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - target.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) + prop_group = self.add_subsystems_and_solver(couple_propulsion=True) bal = om.BalanceComp( name=Dynamic.Vehicle.Propulsion.THROTTLE, @@ -90,27 +44,13 @@ def setup(self): 'thrust_balance', subsys=bal, promotes_inputs=['*'], promotes_outputs=['*'] ) - prop_group.linear_solver = om.DirectSolver() - - prop_group.nonlinear_solver = om.NewtonSolver( - solve_subsystems=True, - maxiter=20, - rtol=1e-12, - atol=1e-12, - err_on_non_converge=False, - ) - prop_group.nonlinear_solver.linesearch = om.BoundsEnforceLS() - - prop_group.nonlinear_solver.options['iprint'] = 2 - prop_group.linear_solver.options['iprint'] = 2 - - self.add_subsystem( - 'prop_group', subsys=prop_group, promotes_inputs=['*'], promotes_outputs=['*'] - ) + # Preserving original options. + prop_group.nonlinear_solver.options['rtol'] = 1e-12 + prop_group.nonlinear_solver.options['atol'] = 1e-12 + prop_group.nonlinear_solver.options['maxiter'] = 20 + prop_group.nonlinear_solver.options['err_on_non_converge'] = False - # # collect initial/final outputs - # self.add_subsystem( 'breguet_eom', RangeComp(num_nodes=nn), @@ -170,10 +110,6 @@ class ElectricBreguetCruiseODE(TwoDOFODE): def setup(self): nn = self.options['num_nodes'] - aviary_options = self.options['aviary_options'] - subsystems = self.options['subsystems'] - subsystem_options = self.options['subsystem_options'] - user_options = self.options['user_options'] self.add_atmosphere(input_speed_type=SpeedType.MACH) @@ -184,48 +120,7 @@ def setup(self): promotes_outputs=['weight'], ) - prop_group = om.Group() - - for subsystem in subsystems: - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs = subsystem_options[subsystem.name] - else: - kwargs = {} - - if isinstance(subsystem, AerodynamicsBuilder): - # set default options for Aero if not specified by user - base_kwargs = {'method': 'cruise', 'output_alpha': True} - kwargs.update(base_kwargs) - - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - if system is not None: - if isinstance(subsystem, PropulsionBuilder): - target = prop_group - else: - target = self - - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - target.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) + prop_group = self.add_subsystems_and_solver(couple_propulsion=True) bal = om.BalanceComp( name=Dynamic.Vehicle.Propulsion.THROTTLE, @@ -242,27 +137,13 @@ def setup(self): 'thrust_balance', subsys=bal, promotes_inputs=['*'], promotes_outputs=['*'] ) - prop_group.linear_solver = om.DirectSolver() - - prop_group.nonlinear_solver = om.NewtonSolver( - solve_subsystems=True, - maxiter=20, - rtol=1e-12, - atol=1e-12, - err_on_non_converge=False, - ) - prop_group.nonlinear_solver.linesearch = om.BoundsEnforceLS() - - prop_group.nonlinear_solver.options['iprint'] = 2 - prop_group.linear_solver.options['iprint'] = 2 - - self.add_subsystem( - 'prop_group', subsys=prop_group, promotes_inputs=['*'], promotes_outputs=['*'] - ) + # Preserving original options. + prop_group.nonlinear_solver.options['rtol'] = 1e-12 + prop_group.nonlinear_solver.options['atol'] = 1e-12 + prop_group.nonlinear_solver.options['maxiter'] = 20 + prop_group.nonlinear_solver.options['err_on_non_converge'] = False - # # collect initial/final outputs - # self.add_subsystem( 'electric_breguet_eom', ElectricRangeComp(num_nodes=nn), diff --git a/aviary/mission/two_dof/ode/flight_ode.py b/aviary/mission/two_dof/ode/flight_ode.py index ec10f9c3e0..a5d92bc9bb 100644 --- a/aviary/mission/two_dof/ode/flight_ode.py +++ b/aviary/mission/two_dof/ode/flight_ode.py @@ -34,10 +34,6 @@ def initialize(self): def setup(self): nn = self.options['num_nodes'] - aviary_options = self.options['aviary_options'] - subsystems = self.options['subsystems'] - subsystem_options = self.options['subsystem_options'] - user_options = self.options['user_options'] input_speed_type = self.options['input_speed_type'] if input_speed_type is SpeedType.EAS: @@ -74,8 +70,8 @@ def setup(self): mach_balance_group.nonlinear_solver = om.NewtonSolver() mach_balance_group.nonlinear_solver.options['solve_subsystems'] = True mach_balance_group.nonlinear_solver.options['iprint'] = 0 - mach_balance_group.nonlinear_solver.options['atol'] = 1e-7 - mach_balance_group.nonlinear_solver.options['rtol'] = 1e-7 + mach_balance_group.nonlinear_solver.options['atol'] = 1e-10 + mach_balance_group.nonlinear_solver.options['rtol'] = 1e-10 mach_balance_group.nonlinear_solver.linesearch = om.BoundsEnforceLS() mach_balance_group.linear_solver = om.DirectSolver(assemble_jac=True) @@ -133,8 +129,8 @@ def setup(self): lift_balance_group.nonlinear_solver = om.NewtonSolver() lift_balance_group.nonlinear_solver.options['solve_subsystems'] = True lift_balance_group.nonlinear_solver.options['iprint'] = 0 - lift_balance_group.nonlinear_solver.options['atol'] = 1e-7 - lift_balance_group.nonlinear_solver.options['rtol'] = 1e-7 + lift_balance_group.nonlinear_solver.options['atol'] = 1e-10 + lift_balance_group.nonlinear_solver.options['rtol'] = 1e-10 lift_balance_group.nonlinear_solver.linesearch = om.BoundsEnforceLS() lift_balance_group.linear_solver = om.DirectSolver(assemble_jac=True) @@ -171,48 +167,11 @@ def setup(self): promotes_outputs=['theta', 'TAS_violation'], ) - # collect the propulsion group names for later use - for subsystem in subsystems: - kwargs = {} - - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs = subsystem_options[subsystem.name] - if isinstance(subsystem, AerodynamicsBuilder): - # set default options for Aero if not specified by user - base_kwargs = { - 'method': 'cruise', - } - kwargs.update(base_kwargs) - - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) + self.add_subsystems_and_solver( + solver_sub=lift_balance_group, + couple_aero=True, + ) - if system is not None: - if isinstance(subsystem, AerodynamicsBuilder): - target = lift_balance_group - else: - target = self - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - target.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) self.add_alpha_control( alpha_group=lift_balance_group, alpha_mode=AlphaModes.REQUIRED_LIFT, diff --git a/aviary/mission/two_dof/ode/flight_path_eom.py b/aviary/mission/two_dof/ode/flight_path_eom.py deleted file mode 100644 index 5299e3b1d5..0000000000 --- a/aviary/mission/two_dof/ode/flight_path_eom.py +++ /dev/null @@ -1,393 +0,0 @@ -import numpy as np -import openmdao.api as om - -from aviary.constants import GRAV_ENGLISH_LBM -from aviary.variable_info.functions import add_aviary_input, add_aviary_output, add_aviary_option -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - - -class FlightPathEOM(om.ExplicitComponent): - """2-degrees-of-freedom flight path EOM.""" - - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def initialize(self): - self.options.declare('num_nodes', types=int) - self.options.declare( - 'ground_roll', - types=bool, - default=False, - desc='True if the aircraft is confined to the ground. Removes altitude rate as an ' - 'output and adjust the TAS rate equation.', - ) - - add_aviary_option(self, Mission.GRAVITY, units='ft/s**2') - - def setup(self): - nn = self.options['num_nodes'] - ground_roll = self.options['ground_roll'] - - add_aviary_input(self, Dynamic.Vehicle.MASS, shape=(nn), units='lbm') - add_aviary_input(self, Dynamic.Vehicle.Propulsion.THRUST_TOTAL, shape=(nn), units='lbf') - add_aviary_input(self, Dynamic.Vehicle.LIFT, shape=(nn), units='lbf') - add_aviary_input(self, Dynamic.Vehicle.DRAG, shape=(nn), units='lbf') - add_aviary_input(self, Dynamic.Mission.VELOCITY, shape=(nn), units='ft/s') - add_aviary_input(self, Dynamic.Mission.FLIGHT_PATH_ANGLE, shape=(nn), units='rad') - add_aviary_input(self, Aircraft.Wing.INCIDENCE, units='deg') - add_aviary_input(self, Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, units='unitless') - - add_aviary_output( - self, - Dynamic.Mission.VELOCITY_RATE, - shape=(nn), - units='ft/s**2', - tags=['dymos.state_rate_source:velocity', 'dymos.state_units:kn'], - ) - - if not ground_roll: - add_aviary_output( - self, - Dynamic.Mission.ALTITUDE_RATE, - shape=(nn), - units='ft/s', - # tags=['dymos.state_rate_source:altitude', 'dymos.state_units:ft'], - ) - add_aviary_output( - self, - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - shape=(nn), - units='rad/s', - tags=[ - 'dymos.state_rate_source:flight_path_angle', - 'dymos.state_units:rad', - ], - ) - add_aviary_input(self, Dynamic.Vehicle.ANGLE_OF_ATTACK, shape=(nn), units='deg') - - add_aviary_output( - self, - Dynamic.Mission.DISTANCE_RATE, - shape=(nn), - units='ft/s', - tags=['dymos.state_rate_source:distance', 'dymos.state_units:ft'], - ) - self.add_output('normal_force', val=np.ones(nn), desc='normal forces', units='lbf') - self.add_output('fuselage_pitch', val=np.ones(nn), desc='fuselage pitch angle', units='deg') - self.add_output('load_factor', val=np.ones(nn), desc='load factor', units='unitless') - # Possible nice-to-have TODO: alpha as an output for groundroll - - def setup_partials(self): - arange = np.arange(self.options['num_nodes'], dtype=int) - ground_roll = self.options['ground_roll'] - - self.declare_partials( - 'load_factor', - [ - Dynamic.Vehicle.LIFT, - Dynamic.Vehicle.MASS, - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Dynamic.Vehicle.Propulsion.THRUST_TOTAL, - ], - rows=arange, - cols=arange, - ) - self.declare_partials('load_factor', [Aircraft.Wing.INCIDENCE]) - - self.declare_partials( - Dynamic.Mission.VELOCITY_RATE, - [ - Dynamic.Vehicle.Propulsion.THRUST_TOTAL, - Dynamic.Vehicle.DRAG, - Dynamic.Vehicle.MASS, - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Dynamic.Vehicle.LIFT, - ], - rows=arange, - cols=arange, - ) - - self.declare_partials(Dynamic.Mission.VELOCITY_RATE, [Aircraft.Wing.INCIDENCE]) - if ground_roll: - self.declare_partials( - Dynamic.Mission.VELOCITY_RATE, [Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT] - ) - - if not ground_roll: - self.declare_partials( - Dynamic.Mission.ALTITUDE_RATE, - [Dynamic.Mission.VELOCITY, Dynamic.Mission.FLIGHT_PATH_ANGLE], - rows=arange, - cols=arange, - ) - self.declare_partials( - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - [ - Dynamic.Vehicle.Propulsion.THRUST_TOTAL, - Dynamic.Vehicle.ANGLE_OF_ATTACK, - Dynamic.Vehicle.LIFT, - Dynamic.Vehicle.MASS, - Dynamic.Mission.FLIGHT_PATH_ANGLE, - Dynamic.Mission.VELOCITY, - ], - rows=arange, - cols=arange, - ) - self.declare_partials(Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, [Aircraft.Wing.INCIDENCE]) - self.declare_partials( - 'normal_force', - Dynamic.Vehicle.ANGLE_OF_ATTACK, - rows=arange, - cols=arange, - ) - self.declare_partials( - 'fuselage_pitch', - Dynamic.Vehicle.ANGLE_OF_ATTACK, - rows=arange, - cols=arange, - ) - - self.declare_partials( - 'load_factor', - Dynamic.Vehicle.ANGLE_OF_ATTACK, - rows=arange, - cols=arange, - ) - self.declare_partials('load_factor', [Aircraft.Wing.INCIDENCE]) - - self.declare_partials( - Dynamic.Mission.VELOCITY_RATE, - Dynamic.Vehicle.ANGLE_OF_ATTACK, - rows=arange, - cols=arange, - ) - - self.declare_partials( - Dynamic.Mission.DISTANCE_RATE, - [Dynamic.Mission.VELOCITY, Dynamic.Mission.FLIGHT_PATH_ANGLE], - rows=arange, - cols=arange, - ) - # self.declare_partials("angle_of_attack_rate", ["*"], val=0.0) - self.declare_partials( - 'normal_force', - [ - Dynamic.Vehicle.MASS, - Dynamic.Vehicle.LIFT, - Dynamic.Vehicle.Propulsion.THRUST_TOTAL, - ], - rows=arange, - cols=arange, - ) - self.declare_partials('normal_force', [Aircraft.Wing.INCIDENCE]) - self.declare_partials( - 'fuselage_pitch', - Dynamic.Mission.FLIGHT_PATH_ANGLE, - rows=arange, - cols=arange, - val=180 / np.pi, - ) - self.declare_partials('fuselage_pitch', [Aircraft.Wing.INCIDENCE]) - - def compute(self, inputs, outputs): - grav_english = self.options[Mission.GRAVITY][0] - if self.options['ground_roll']: - mu = inputs[Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT] - else: - mu = 0.0 - - weight = inputs[Dynamic.Vehicle.MASS] * GRAV_ENGLISH_LBM - thrust = inputs[Dynamic.Vehicle.Propulsion.THRUST_TOTAL] - incremented_lift = inputs[Dynamic.Vehicle.LIFT] - incremented_drag = inputs[Dynamic.Vehicle.DRAG] - TAS = inputs[Dynamic.Mission.VELOCITY] - gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] - i_wing = inputs[Aircraft.Wing.INCIDENCE] - if self.options['ground_roll']: - alpha = inputs[Aircraft.Wing.INCIDENCE] - else: - alpha = inputs[Dynamic.Vehicle.ANGLE_OF_ATTACK] - - thrust_along_flightpath = thrust * np.cos((alpha - i_wing) * np.pi / 180) - thrust_across_flightpath = thrust * np.sin((alpha - i_wing) * np.pi / 180) - normal_force = weight - incremented_lift - thrust_across_flightpath - - outputs[Dynamic.Mission.VELOCITY_RATE] = ( - ( - thrust_along_flightpath - - incremented_drag - - weight * np.sin(gamma) - - mu * normal_force - ) - * grav_english - / weight - ) - - if not self.options['ground_roll']: - outputs[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE] = ( - (thrust_across_flightpath + incremented_lift - weight * np.cos(gamma)) - * grav_english - / (TAS * weight) - ) - outputs[Dynamic.Mission.ALTITUDE_RATE] = TAS * np.sin(gamma) - - outputs[Dynamic.Mission.DISTANCE_RATE] = TAS * np.cos(gamma) - - outputs['normal_force'] = normal_force - - outputs['fuselage_pitch'] = gamma * 180 / np.pi - i_wing + alpha - - load_factor = (incremented_lift + thrust_across_flightpath) / (weight * np.cos(gamma)) - - outputs['load_factor'] = load_factor - - def compute_partials(self, inputs, J): - grav_english = self.options[Mission.GRAVITY][0] - if self.options['ground_roll']: - mu = inputs[Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT] - else: - mu = 0.0 - - weight = inputs[Dynamic.Vehicle.MASS] * GRAV_ENGLISH_LBM - thrust = inputs[Dynamic.Vehicle.Propulsion.THRUST_TOTAL] - incremented_lift = inputs[Dynamic.Vehicle.LIFT] - incremented_drag = inputs[Dynamic.Vehicle.DRAG] - TAS = inputs[Dynamic.Mission.VELOCITY] - gamma = inputs[Dynamic.Mission.FLIGHT_PATH_ANGLE] - i_wing = inputs[Aircraft.Wing.INCIDENCE] - if self.options['ground_roll']: - alpha = i_wing - else: - alpha = inputs[Dynamic.Vehicle.ANGLE_OF_ATTACK] - - nn = self.options['num_nodes'] - - thrust_along_flightpath = thrust * np.cos((alpha - i_wing) * np.pi / 180) - thrust_across_flightpath = thrust * np.sin((alpha - i_wing) * np.pi / 180) - - dTAlF_dThrust = np.cos((alpha - i_wing) * np.pi / 180) - dTAlF_dAlpha = -thrust * np.sin((alpha - i_wing) * np.pi / 180) * np.pi / 180 - dTAlF_dIwing = thrust * np.sin((alpha - i_wing) * np.pi / 180) * np.pi / 180 - - dTAcF_dThrust = np.sin((alpha - i_wing) * np.pi / 180) - dTAcF_dAlpha = thrust * np.cos((alpha - i_wing) * np.pi / 180) * np.pi / 180 - dTAcF_dIwing = -thrust * np.cos((alpha - i_wing) * np.pi / 180) * np.pi / 180 - - J['load_factor', Dynamic.Vehicle.LIFT] = 1 / (weight * np.cos(gamma)) - J['load_factor', Dynamic.Vehicle.MASS] = ( - -(incremented_lift + thrust_across_flightpath) - / (weight**2 * np.cos(gamma)) - * GRAV_ENGLISH_LBM - ) - J['load_factor', Dynamic.Mission.FLIGHT_PATH_ANGLE] = ( - -(incremented_lift + thrust_across_flightpath) - / (weight * (np.cos(gamma)) ** 2) - * (-np.sin(gamma)) - ) - J['load_factor', Dynamic.Vehicle.Propulsion.THRUST_TOTAL] = dTAcF_dThrust / ( - weight * np.cos(gamma) - ) - - normal_force = weight - incremented_lift - thrust_across_flightpath - # normal_force = np.where(normal_force1 < 0, np.zeros(nn), normal_force1) - - dNF_dWeight = np.ones(nn) - # dNF_dWeight[normal_force1 < 0] = 0 - - dNF_dLift = -np.ones(nn) - # dNF_dLift[normal_force1 < 0] = 0 - - dNF_dThrust = -np.ones(nn) * dTAcF_dThrust - # dNF_dThrust[normal_force1 < 0] = 0 - - dNF_dIwing = -np.ones(nn) * dTAcF_dIwing - # dNF_dIwing[normal_force1 < 0] = 0 - - J[Dynamic.Mission.VELOCITY_RATE, Dynamic.Vehicle.Propulsion.THRUST_TOTAL] = ( - (dTAlF_dThrust - mu * dNF_dThrust) * grav_english / weight - ) - - J[Dynamic.Mission.VELOCITY_RATE, Dynamic.Vehicle.DRAG] = -grav_english / weight - J[Dynamic.Mission.VELOCITY_RATE, Dynamic.Vehicle.MASS] = ( - grav_english - * GRAV_ENGLISH_LBM - * ( - weight * (-np.sin(gamma) - mu * dNF_dWeight) - - ( - thrust_along_flightpath - - incremented_drag - - weight * np.sin(gamma) - - mu * normal_force - ) - ) - / weight**2 - ) - J[Dynamic.Mission.VELOCITY_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = ( - -np.cos(gamma) * grav_english - ) - J[Dynamic.Mission.VELOCITY_RATE, Dynamic.Vehicle.LIFT] = ( - grav_english * (-mu * dNF_dLift) / weight - ) - if self.options['ground_roll']: - J[Dynamic.Mission.VELOCITY_RATE, Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT] = ( - -normal_force * grav_english / weight - ) - - # TODO: check partials, esp. for alphas - if not self.options['ground_roll']: - J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.VELOCITY] = np.sin(gamma) - J[Dynamic.Mission.ALTITUDE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = TAS * np.cos( - gamma - ) - - J[ - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - Dynamic.Vehicle.Propulsion.THRUST_TOTAL, - ] = dTAcF_dThrust * grav_english / (TAS * weight) - J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Dynamic.Vehicle.ANGLE_OF_ATTACK] = ( - dTAcF_dAlpha * grav_english / (TAS * weight) - ) - J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Aircraft.Wing.INCIDENCE] = ( - dTAcF_dIwing * grav_english / (TAS * weight) - ) - J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Dynamic.Vehicle.LIFT] = grav_english / ( - TAS * weight - ) - J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Dynamic.Vehicle.MASS] = ( - (grav_english / TAS) - * GRAV_ENGLISH_LBM - * (-thrust_across_flightpath / weight**2 - incremented_lift / weight**2) - ) - J[ - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - Dynamic.Mission.FLIGHT_PATH_ANGLE, - ] = weight * np.sin(gamma) * grav_english / (TAS * weight) - J[Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, Dynamic.Mission.VELOCITY] = -( - (thrust_across_flightpath + incremented_lift - weight * np.cos(gamma)) - * grav_english - / (TAS**2 * weight) - ) - - dNF_dAlpha = -np.ones(nn) * dTAcF_dAlpha - # dNF_dAlpha[normal_force1 < 0] = 0 - J[Dynamic.Mission.VELOCITY_RATE, Dynamic.Vehicle.ANGLE_OF_ATTACK] = ( - (dTAlF_dAlpha - mu * dNF_dAlpha) * grav_english / weight - ) - J['normal_force', Dynamic.Vehicle.ANGLE_OF_ATTACK] = dNF_dAlpha - J['fuselage_pitch', Dynamic.Vehicle.ANGLE_OF_ATTACK] = 1 - J['load_factor', Dynamic.Vehicle.ANGLE_OF_ATTACK] = dTAcF_dAlpha / ( - weight * np.cos(gamma) - ) - J[Dynamic.Mission.VELOCITY_RATE, Aircraft.Wing.INCIDENCE] = ( - (dTAlF_dIwing - mu * dNF_dIwing) * grav_english / weight - ) - J['normal_force', Aircraft.Wing.INCIDENCE] = dNF_dIwing - J['fuselage_pitch', Aircraft.Wing.INCIDENCE] = -1 - J['load_factor', Aircraft.Wing.INCIDENCE] = dTAcF_dIwing / (weight * np.cos(gamma)) - - J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.VELOCITY] = np.cos(gamma) - J[Dynamic.Mission.DISTANCE_RATE, Dynamic.Mission.FLIGHT_PATH_ANGLE] = -TAS * np.sin(gamma) - - J['normal_force', Dynamic.Vehicle.MASS] = dNF_dWeight * GRAV_ENGLISH_LBM - J['normal_force', Dynamic.Vehicle.LIFT] = dNF_dLift - J['normal_force', Dynamic.Vehicle.Propulsion.THRUST_TOTAL] = dNF_dThrust diff --git a/aviary/mission/two_dof/ode/flight_path_ode.py b/aviary/mission/two_dof/ode/flight_path_ode.py deleted file mode 100644 index 17c8c7c4a7..0000000000 --- a/aviary/mission/two_dof/ode/flight_path_ode.py +++ /dev/null @@ -1,174 +0,0 @@ -import numpy as np -import openmdao.api as om - -from aviary.mission.two_dof.ode.flight_path_eom import FlightPathEOM -from aviary.mission.two_dof.ode.two_dof_ode import TwoDOFODE -from aviary.subsystems.mass.mass_to_weight import MassToWeight -from aviary.variable_info.enums import AlphaModes, SpeedType -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - - -class FlightPathODE(TwoDOFODE): - """ODE using 2D aircraft equations of motion with states distance, alt, TAS, and gamma. - - Control is managed via angle-of-attack (alpha). - """ - - def initialize(self): - super().initialize() - self.options.declare('alpha_mode', default=AlphaModes.DEFAULT, types=AlphaModes) - self.options.declare( - 'input_speed_type', - default=SpeedType.TAS, - types=SpeedType, - desc='Whether the speed is given as a equivalent airspeed, true airspeed, or Mach number', - ) - self.options.declare( - 'ground_roll', - types=bool, - default=False, - desc='True if the aircraft is confined to the ground. Removes altitude rate as an ' - 'output and adjusts the TAS rate equation.', - ) - self.options.declare( - 'clean', - types=bool, - default=False, - desc='If true then no flaps or gear are included. Useful for high-speed flight phases.', - ) - - def setup(self): - nn = self.options['num_nodes'] - aviary_options = self.options['aviary_options'] - alpha_mode = self.options['alpha_mode'] - input_speed_type = self.options['input_speed_type'] - user_options = self.options['user_options'] - - print_level = 0 - - kwargs = {'method': 'low_speed'} - if self.options['clean']: - kwargs['method'] = 'cruise' - kwargs['output_alpha'] = False - - EOM_inputs = [ - Dynamic.Vehicle.MASS, - Dynamic.Vehicle.Propulsion.THRUST_TOTAL, - Dynamic.Vehicle.LIFT, - Dynamic.Vehicle.DRAG, - Dynamic.Mission.VELOCITY, - Dynamic.Mission.FLIGHT_PATH_ANGLE, - ] + ['aircraft:*'] - if not self.options['ground_roll']: - EOM_inputs.append(Dynamic.Vehicle.ANGLE_OF_ATTACK) - else: - EOM_inputs.append(Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT) - - subsystems = self.options['subsystems'] - - self.add_atmosphere(input_speed_type=input_speed_type) - - if alpha_mode is AlphaModes.DEFAULT: - # alpha as input - pass - else: - if alpha_mode is AlphaModes.REQUIRED_LIFT: - self.add_subsystem( - 'calc_weight', - MassToWeight(num_nodes=nn), - promotes_inputs=[('mass', Dynamic.Vehicle.MASS)], - promotes_outputs=['weight'], - ) - self.add_subsystem( - 'calc_lift', - om.ExecComp( - 'required_lift = weight*cos(alpha + gamma) - thrust*sin(i_wing)', - required_lift={'val': 0, 'units': 'lbf'}, - weight={'val': 0, 'units': 'lbf'}, - thrust={'val': 0, 'units': 'lbf'}, - alpha={'val': 0, 'units': 'rad'}, - gamma={'val': 0, 'units': 'rad'}, - i_wing={'val': 0, 'units': 'rad'}, - ), - promotes_inputs=[ - 'weight', - ('thrust', Dynamic.Vehicle.Propulsion.THRUST_TOTAL), - ('alpha', Dynamic.Vehicle.ANGLE_OF_ATTACK), - ('gamma', Dynamic.Mission.FLIGHT_PATH_ANGLE), - ('i_wing', Aircraft.Wing.INCIDENCE), - ], - promotes_outputs=['required_lift'], - ) - self.add_alpha_control( - alpha_mode=alpha_mode, - target_load_factor=1, - atol=1e-6, - rtol=1e-12, - num_nodes=nn, - print_level=print_level, - ) - - for subsystem in subsystems: - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - if system is not None: - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - self.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) - - self.add_subsystem( - 'flight_path_eom', - FlightPathEOM( - num_nodes=nn, - ground_roll=self.options['ground_roll'], - ), - promotes_inputs=EOM_inputs, - promotes_outputs=[ - Dynamic.Mission.VELOCITY_RATE, - Dynamic.Mission.DISTANCE_RATE, - 'normal_force', - 'fuselage_pitch', - 'load_factor', - ], - ) - - if not self.options['ground_roll']: - self.promotes( - 'flight_path_eom', - outputs=[ - Dynamic.Mission.ALTITUDE_RATE, - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE, - ], - ) - - self.add_excess_rate_comps(nn) - - if not self.options['clean']: - self.set_input_defaults('t_init_flaps', val=47.5) - self.set_input_defaults('t_init_gear', val=37.3) - self.set_input_defaults('t_curr', val=np.zeros(nn), units='s') - self.set_input_defaults(Dynamic.Vehicle.ANGLE_OF_ATTACK, val=np.zeros(nn), units='rad') - self.set_input_defaults(Dynamic.Mission.FLIGHT_PATH_ANGLE, val=np.zeros(nn), units='deg') - self.set_input_defaults(Dynamic.Mission.ALTITUDE, val=np.zeros(nn), units='ft') - self.set_input_defaults(Dynamic.Atmosphere.MACH, val=np.zeros(nn), units='unitless') - self.set_input_defaults(Dynamic.Vehicle.MASS, val=np.zeros(nn), units='lbm') - self.set_input_defaults(Dynamic.Mission.VELOCITY, val=np.zeros(nn), units='kn') - if self.options['ground_roll']: - self.set_input_defaults(Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, 0.02) diff --git a/aviary/mission/two_dof/ode/simple_cruise_ode.py b/aviary/mission/two_dof/ode/simple_cruise_ode.py index 75c325f1de..c8efec37a1 100644 --- a/aviary/mission/two_dof/ode/simple_cruise_ode.py +++ b/aviary/mission/two_dof/ode/simple_cruise_ode.py @@ -17,10 +17,6 @@ class SimpleCruiseODE(TwoDOFODE): def setup(self): nn = self.options['num_nodes'] - aviary_options = self.options['aviary_options'] - subsystems = self.options['subsystems'] - subsystem_options = self.options['subsystem_options'] - user_options = self.options['user_options'] self.add_atmosphere(input_speed_type=SpeedType.MACH) @@ -31,51 +27,7 @@ def setup(self): promotes_outputs=['weight'], ) - prop_group = om.Group() - - for subsystem in subsystems: - kwargs = {} - - # check if subsystem_options has entry for a subsystem of this name - if subsystem.name in subsystem_options: - kwargs = subsystem_options[subsystem.name] - if isinstance(subsystem, AerodynamicsBuilder): - # set default options for Aero if not specified by user - base_kwargs = {'method': 'cruise', 'output_alpha': True} - kwargs.update(base_kwargs) - - system = subsystem.build_mission( - num_nodes=nn, - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - - if system is not None: - mission_in = subsystem.mission_inputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - mission_out = subsystem.mission_outputs( - aviary_inputs=aviary_options, - user_options=user_options, - subsystem_options=kwargs, - ) - if isinstance(subsystem, PropulsionBuilder): - prop_group.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) - else: - self.add_subsystem( - subsystem.name, - system, - promotes_inputs=mission_in, - promotes_outputs=mission_out, - ) + prop_group = self.add_subsystems_and_solver(couple_propulsion=True) bal = om.BalanceComp( name=Dynamic.Vehicle.Propulsion.THROTTLE, @@ -92,27 +44,14 @@ def setup(self): 'thrust_balance', subsys=bal, promotes_inputs=['*'], promotes_outputs=['*'] ) + # Preserving original options. + prop_group.nonlinear_solver.options['rtol'] = 1e-12 + prop_group.nonlinear_solver.options['atol'] = 1e-12 + prop_group.nonlinear_solver.options['maxiter'] = 20 + prop_group.nonlinear_solver.options['err_on_non_converge'] = False prop_group.linear_solver = om.DirectSolver() - prop_group.nonlinear_solver = om.NewtonSolver( - solve_subsystems=True, - maxiter=20, - rtol=1e-12, - atol=1e-12, - err_on_non_converge=False, - ) - prop_group.nonlinear_solver.linesearch = om.BoundsEnforceLS() - - prop_group.nonlinear_solver.options['iprint'] = 2 - prop_group.linear_solver.options['iprint'] = 2 - - self.add_subsystem( - 'prop_group', subsys=prop_group, promotes_inputs=['*'], promotes_outputs=['*'] - ) - - # # collect initial/final outputs - # self.add_subsystem( 'distance_eom', DistanceComp(num_nodes=nn), diff --git a/aviary/mission/two_dof/ode/takeoff_ode.py b/aviary/mission/two_dof/ode/takeoff_ode.py index 4997955e1b..1f9d830e4b 100644 --- a/aviary/mission/two_dof/ode/takeoff_ode.py +++ b/aviary/mission/two_dof/ode/takeoff_ode.py @@ -145,13 +145,6 @@ def setup(self): if isinstance(subsystem, AerodynamicsBuilder): kwargs = {'method': 'low_speed'} - if self.options['clean']: - kwargs['method'] = 'cruise' - kwargs['output_alpha'] = False - - if not (ground_roll or rotation): - kwargs['retract_gear'] = True - kwargs['retract_flaps'] = True if name in subsystem_options: kwargs.update(subsystem_options[name]) diff --git a/aviary/mission/two_dof/ode/taxi_ode.py b/aviary/mission/two_dof/ode/taxi_ode.py index b6a43c3220..0864339e18 100644 --- a/aviary/mission/two_dof/ode/taxi_ode.py +++ b/aviary/mission/two_dof/ode/taxi_ode.py @@ -49,6 +49,7 @@ def setup(self): self.add_atmosphere(input_speed_type=SpeedType.MACH) + # Taxi only supports propulsion. for subsystem in subsystems: if isinstance(subsystem, PropulsionBuilder): system = subsystem.build_mission( diff --git a/aviary/mission/two_dof/ode/test/test_accel_ode.py b/aviary/mission/two_dof/ode/test/test_accel_ode.py index f29af30fe9..8f621f4b78 100644 --- a/aviary/mission/two_dof/ode/test/test_accel_ode.py +++ b/aviary/mission/two_dof/ode/test/test_accel_ode.py @@ -28,8 +28,13 @@ def setUp(self): 'GASP', [build_engine_deck(aviary_options)] ) + subsystem_options = {'aerodynamics': {'method': 'cruise', 'output_alpha': True}} + self.sys = self.prob.model = AccelODE( - num_nodes=2, aviary_options=aviary_options, subsystems=default_mission_subsystems + num_nodes=2, + aviary_options=aviary_options, + subsystems=default_mission_subsystems, + subsystem_options=subsystem_options, ) def test_accel(self): diff --git a/aviary/mission/two_dof/ode/test/test_breguet_cruise_ode.py b/aviary/mission/two_dof/ode/test/test_breguet_cruise_ode.py index 3bfb1c518b..abef17829d 100644 --- a/aviary/mission/two_dof/ode/test/test_breguet_cruise_ode.py +++ b/aviary/mission/two_dof/ode/test/test_breguet_cruise_ode.py @@ -25,10 +25,13 @@ def setUp(self): 'GASP', [build_engine_deck(aviary_options)] ) + subsystem_options = {'aerodynamics': {'method': 'cruise', 'output_alpha': True}} + self.prob.model = BreguetCruiseODE( num_nodes=2, aviary_options=aviary_options, subsystems=default_mission_subsystems, + subsystem_options=subsystem_options, ) self.prob.model.set_input_defaults( @@ -93,10 +96,13 @@ def setUp(self): 'GASP', build_engine_deck(aviary_options) ) + subsystem_options = {'aerodynamics': {'method': 'cruise', 'output_alpha': True}} + self.prob.model = ElectricBreguetCruiseODE( num_nodes=2, aviary_options=aviary_options, subsystems=default_mission_subsystems, + subsystem_options=subsystem_options, ) self.prob.model.set_input_defaults( diff --git a/aviary/mission/two_dof/ode/test/test_flight_path_eom.py b/aviary/mission/two_dof/ode/test/test_flight_path_eom.py deleted file mode 100644 index a4d3e88028..0000000000 --- a/aviary/mission/two_dof/ode/test/test_flight_path_eom.py +++ /dev/null @@ -1,145 +0,0 @@ -import unittest - -import numpy as np -import openmdao.api as om -from openmdao.utils.assert_utils import assert_check_partials, assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs - -from aviary.mission.two_dof.ode.flight_path_eom import FlightPathEOM -from aviary.variable_info.variables import Dynamic, Mission - - -@use_tempdirs -class FlightPathEOMTestCase(unittest.TestCase): - def setUp(self): - self.ground_roll = False - self.prob = om.Problem() - options = {Mission.GRAVITY: (32.2, 'ft/s**2')} - self.fp = self.prob.model.add_subsystem( - 'group', - FlightPathEOM(num_nodes=2, ground_roll=self.ground_roll, **options), - promotes=['*'], - ) - self.prob.model.set_input_defaults(Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, 0.02) - self.prob.setup(check=False, force_alloc_complex=True) - self.prob.set_val(Dynamic.Vehicle.MASS, [1.0, 1.0], units='lbm') - self.prob.set_val(Dynamic.Mission.FLIGHT_PATH_ANGLE, [1.0, 1.0], units='rad') - self.prob.set_val(Dynamic.Mission.VELOCITY_RATE, [1.0, 1.0], units='ft/s**2') - self.prob.set_val(Dynamic.Mission.VELOCITY, [1.0, 1.0], units='ft/s') - self.prob.set_val(Dynamic.Vehicle.Propulsion.THRUST_TOTAL, [1.0, 1.0], units='lbf') - self.prob.set_val(Dynamic.Vehicle.LIFT, [1.0, 1.0], units='lbf') - self.prob.set_val(Dynamic.Vehicle.DRAG, [1.0, 1.0], units='lbf') - self.prob.set_val(Dynamic.Vehicle.ANGLE_OF_ATTACK, [1.0, 1.0], units='deg') - - def test_case1(self): - # ground_roll = False (the aircraft is not confined to the ground) - - tol = 1e-6 - self.prob.run_model() - - expected_values = { - Dynamic.Mission.VELOCITY_RATE: np.array([-27.10027, -27.10027]), - Dynamic.Mission.DISTANCE_RATE: np.array([0.5403023, 0.5403023]), - 'normal_force': np.array([-0.0174524, -0.0174524]), - 'fuselage_pitch': np.array([58.2958, 58.2958]), - 'load_factor': np.array([1.883117, 1.883117]), - Dynamic.Mission.ALTITUDE_RATE: np.array([0.841471, 0.841471]), - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE: np.array([15.36423, 15.36423]), - } - - for var_name, expected in expected_values.items(): - with self.subTest(var=var_name): - assert_near_equal(self.prob[var_name], expected, tol) - - partial_data = self.prob.check_partials(out_stream=None, method='cs') - assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - - def test_case2(self): - """ground_roll = True (the aircraft is confined to the ground).""" - self.fp.options['ground_roll'] = True - self.prob.setup(force_alloc_complex=True) - self.prob.set_val(Dynamic.Vehicle.MASS, [1.0, 1.0], units='lbm') - self.prob.set_val(Dynamic.Mission.FLIGHT_PATH_ANGLE, [1.0, 1.0], units='rad') - self.prob.set_val(Dynamic.Mission.VELOCITY_RATE, [1.0, 1.0], units='ft/s**2') - self.prob.set_val(Dynamic.Mission.VELOCITY, [1.0, 1.0], units='ft/s') - self.prob.set_val(Dynamic.Vehicle.Propulsion.THRUST_TOTAL, [1.0, 1.0], units='lbf') - self.prob.set_val(Dynamic.Vehicle.LIFT, [1.0, 1.0], units='lbf') - self.prob.set_val(Dynamic.Vehicle.DRAG, [1.0, 1.0], units='lbf') - - tol = 1e-6 - self.prob.run_model() - - expected_values = { - Dynamic.Mission.VELOCITY_RATE: np.array([-27.09537, -27.09537]), - Dynamic.Mission.DISTANCE_RATE: np.array([0.5403023, 0.5403023]), - 'normal_force': np.array([-0.0, -0.0]), - 'fuselage_pitch': np.array([57.29578, 57.29578]), - 'load_factor': np.array([1.850816, 1.850816]), - } - - for var_name, expected in expected_values.items(): - with self.subTest(var=var_name): - assert_near_equal(self.prob[var_name], expected, tol) - - partial_data = self.prob.check_partials(out_stream=None, method='cs') - assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - - -class FlightPathEOMTestCase2(unittest.TestCase): - """Test mass-weight conversion.""" - - def setUp(self): - import aviary.mission.two_dof.ode.flight_path_eom as fp - - fp.GRAV_ENGLISH_LBM = 1.1 - - def tearDown(self): - import aviary.mission.two_dof.ode.flight_path_eom as fp - - fp.GRAV_ENGLISH_LBM = 1.0 - - def test_case1(self): - """ground_roll = False (the aircraft is not confined to the ground).""" - prob = om.Problem() - prob.model.add_subsystem( - 'group', FlightPathEOM(num_nodes=2, ground_roll=False), promotes=['*'] - ) - prob.model.set_input_defaults(Mission.Takeoff.ROLLING_FRICTION_COEFFICIENT, 0.02) - prob.setup(check=False, force_alloc_complex=True) - prob.set_val(Dynamic.Vehicle.MASS, [1.0, 1.0], units='lbm') - prob.set_val(Dynamic.Mission.FLIGHT_PATH_ANGLE, [1.0, 1.0], units='rad') - prob.set_val(Dynamic.Mission.VELOCITY_RATE, [1.0, 1.0], units='ft/s**2') - prob.set_val(Dynamic.Mission.VELOCITY, [1.0, 1.0], units='ft/s') - prob.set_val(Dynamic.Vehicle.Propulsion.THRUST_TOTAL, [1.0, 1.0], units='lbf') - prob.set_val(Dynamic.Vehicle.LIFT, [1.0, 1.0], units='lbf') - prob.set_val(Dynamic.Vehicle.DRAG, [1.0, 1.0], units='lbf') - prob.set_val(Dynamic.Vehicle.ANGLE_OF_ATTACK, [1.0, 1.0], units='deg') - - partial_data = prob.check_partials(out_stream=None, method='cs') - assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - - def test_case2(self): - """ground_roll = True (the aircraft is confined to the ground).""" - prob = om.Problem() - prob.model.add_subsystem( - 'group', FlightPathEOM(num_nodes=2, ground_roll=True), promotes=['*'] - ) - prob.setup(check=False, force_alloc_complex=True) - prob.setup(force_alloc_complex=True) - prob.set_val(Dynamic.Vehicle.MASS, [1.0, 1.0], units='lbm') - prob.set_val(Dynamic.Mission.FLIGHT_PATH_ANGLE, [1.0, 1.0], units='rad') - prob.set_val(Dynamic.Mission.VELOCITY_RATE, [1.0, 1.0], units='ft/s**2') - prob.set_val(Dynamic.Mission.VELOCITY, [1.0, 1.0], units='ft/s') - prob.set_val(Dynamic.Vehicle.Propulsion.THRUST_TOTAL, [1.0, 1.0], units='lbf') - prob.set_val(Dynamic.Vehicle.LIFT, [1.0, 1.0], units='lbf') - prob.set_val(Dynamic.Vehicle.DRAG, [1.0, 1.0], units='lbf') - - partial_data = prob.check_partials(out_stream=None, method='cs') - assert_check_partials(partial_data, atol=1e-12, rtol=1e-12) - - -if __name__ == '__main__': - unittest.main() - # test = FlightPathEOMTestCase() - # test.setUp() - # test.test_case1() diff --git a/aviary/mission/two_dof/ode/test/test_flight_path_ode.py b/aviary/mission/two_dof/ode/test/test_flight_path_ode.py deleted file mode 100644 index e89f8a98c8..0000000000 --- a/aviary/mission/two_dof/ode/test/test_flight_path_ode.py +++ /dev/null @@ -1,115 +0,0 @@ -import unittest - -import numpy as np -import openmdao.api as om -from openmdao.utils.assert_utils import assert_check_partials, assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs - -from aviary.mission.two_dof.ode.flight_path_ode import FlightPathODE -from aviary.mission.two_dof.ode.test.params import set_params_for_unit_tests -from aviary.subsystems.propulsion.utils import build_engine_deck -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems -from aviary.utils.test_utils.IO_test_util import check_prob_outputs -from aviary.variable_info.functions import setup_model_options -from aviary.variable_info.options import get_option_defaults -from aviary.variable_info.variables import Aircraft, Dynamic, Mission -from aviary.utils.preprocessors import preprocess_options - - -@use_tempdirs -class FlightPathODETestCase(unittest.TestCase): - """Test 2-degrees-of-freedom flight path ODE.""" - - def setUp(self): - self.prob = om.Problem() - - aviary_options = get_option_defaults() - aviary_options.set_val(Mission.GRAVITY, val=32.2, units='ft/s**2') - aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) - - self.fp = self.prob.model = FlightPathODE( - num_nodes=2, - aviary_options=aviary_options, - subsystems=default_mission_subsystems, - ) - - setup_model_options(self.prob, aviary_options) - - def test_case1(self): - # ground_roll = False (the aircraft is not confined to the ground) - - self.prob.setup(check=False, force_alloc_complex=True) - - set_params_for_unit_tests(self.prob) - - self.prob.set_val(Dynamic.Mission.VELOCITY, [100, 100], units='kn') - self.prob.set_val(Dynamic.Vehicle.MASS, [100000, 100000], units='lbm') - self.prob.set_val(Dynamic.Mission.ALTITUDE, [500, 500], units='ft') - self.prob.set_val('interference_independent_of_shielded_area', 1.89927266) - self.prob.set_val('drag_loss_due_to_shielded_wing_area', 68.02065834) - self.prob.set_val(Aircraft.Wing.FORM_FACTOR, 1.25) - self.prob.set_val(Aircraft.VerticalTail.FORM_FACTOR, 1.25) - self.prob.set_val(Aircraft.HorizontalTail.FORM_FACTOR, 1.25) - self.prob.set_val(Aircraft.Fuselage.FORM_FACTOR, 1.05557953) - - self.prob.run_model() - testvals = { - Dynamic.Mission.VELOCITY_RATE: [14.09033832, 14.09033832], - Dynamic.Mission.FLIGHT_PATH_ANGLE_RATE: [-0.14291897, -0.14291897], - Dynamic.Mission.ALTITUDE_RATE: [0.0, 0.0], - Dynamic.Mission.DISTANCE_RATE: [168.781, 168.781], - 'normal_force': [74913.05769336, 74913.05769336], - 'fuselage_pitch': [0.0, 0.0], - 'load_factor': [0.25086942, 0.25086942], - Dynamic.Mission.ALTITUDE_RATE: [0.0, 0.0], - Dynamic.Mission.ALTITUDE_RATE_MAX: [-0.018143, -0.018143], - } - check_prob_outputs(self.prob, testvals, rtol=1e-6) - - tol = 1e-6 - assert_near_equal(self.prob[Dynamic.Mission.ALTITUDE_RATE], np.array([0, 0]), tol) - - partial_data = self.prob.check_partials( - out_stream=None, method='cs', excludes=['*USatm*', '*params*', '*aero*'] - ) - assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - - def test_case2(self): - # ground_roll = True (the aircraft is confined to the ground) - - self.fp.options['ground_roll'] = True - self.prob.setup(check=False, force_alloc_complex=True) - - set_params_for_unit_tests(self.prob) - - self.prob.set_val(Dynamic.Mission.VELOCITY, [100, 100], units='kn') - self.prob.set_val(Dynamic.Vehicle.MASS, [100000, 100000], units='lbm') - self.prob.set_val(Dynamic.Mission.ALTITUDE, [500, 500], units='ft') - self.prob.set_val('interference_independent_of_shielded_area', 1.89927266) - self.prob.set_val('drag_loss_due_to_shielded_wing_area', 68.02065834) - self.prob.set_val(Aircraft.Wing.FORM_FACTOR, 1.25) - self.prob.set_val(Aircraft.VerticalTail.FORM_FACTOR, 1.25) - self.prob.set_val(Aircraft.HorizontalTail.FORM_FACTOR, 1.25) - - self.prob.run_model() - testvals = { - Dynamic.Mission.VELOCITY_RATE: [13.60789823, 13.60789823], - Dynamic.Mission.DISTANCE_RATE: [168.781, 168.781], - 'normal_force': [74913.05769336, 74913.05769336], - 'fuselage_pitch': [0.0, 0.0], - 'load_factor': [0.25086942, 0.25086942], - Dynamic.Mission.ALTITUDE_RATE_MAX: [0.75262913, 0.75262913], - } - check_prob_outputs(self.prob, testvals, rtol=1e-6) - - partial_data = self.prob.check_partials( - out_stream=None, method='cs', excludes=['*USatm*', '*params*', '*aero*'] - ) - assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) - - -if __name__ == '__main__': - unittest.main() diff --git a/aviary/mission/two_dof/ode/test/test_simple_cruise_ode.py b/aviary/mission/two_dof/ode/test/test_simple_cruise_ode.py index ceb3c36458..dee098fdfd 100644 --- a/aviary/mission/two_dof/ode/test/test_simple_cruise_ode.py +++ b/aviary/mission/two_dof/ode/test/test_simple_cruise_ode.py @@ -25,10 +25,13 @@ def setUp(self): 'GASP', [build_engine_deck(aviary_options)] ) + subsystem_options = {'aerodynamics': {'method': 'cruise', 'output_alpha': True}} + self.prob.model = SimpleCruiseODE( num_nodes=2, aviary_options=aviary_options, subsystems=default_mission_subsystems, + subsystem_options=subsystem_options, ) self.prob.model.set_input_defaults( diff --git a/aviary/mission/two_dof/ode/two_dof_ode.py b/aviary/mission/two_dof/ode/two_dof_ode.py index ca71530cfc..49f91fe32c 100644 --- a/aviary/mission/two_dof/ode/two_dof_ode.py +++ b/aviary/mission/two_dof/ode/two_dof_ode.py @@ -23,8 +23,8 @@ def add_alpha_control( target_tas_rate=0, # target_alt_rate=0, # target_flight_path_angle=0, - atol=1e-7, - rtol=1e-7, + atol=1e-10, + rtol=1e-10, add_default_solver=True, print_level=0, ): diff --git a/aviary/mission/two_dof/phases/test/test_phases.py b/aviary/mission/two_dof/phases/test/test_phases.py index 9b37502971..9f87c6e544 100644 --- a/aviary/mission/two_dof/phases/test/test_phases.py +++ b/aviary/mission/two_dof/phases/test/test_phases.py @@ -30,7 +30,7 @@ def test_breguet_error_message(self): local_phase_info = deepcopy(two_dof_phase_info) local_phase_info['cruise'] = { - 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.BREGUET_RANGE, 'alt_cruise': (37.5e3, 'ft'), diff --git a/aviary/models/aircraft/large_turboprop_freighter/electrified_phase_info.py b/aviary/models/aircraft/large_turboprop_freighter/electrified_phase_info.py index 26a63e99d8..32bddc82b6 100644 --- a/aviary/models/aircraft/large_turboprop_freighter/electrified_phase_info.py +++ b/aviary/models/aircraft/large_turboprop_freighter/electrified_phase_info.py @@ -82,6 +82,7 @@ # 2DOF two_dof_phase_info = { 'groundroll': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'ground_roll': True, @@ -108,6 +109,7 @@ }, }, 'rotation': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'rotation': True, @@ -139,6 +141,7 @@ }, }, 'ascent': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'num_segments': 4, @@ -178,6 +181,7 @@ }, }, 'accel': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.ACCEL, 'num_segments': 1, @@ -257,6 +261,7 @@ }, }, 'cruise': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.SIMPLE_CRUISE, 'alt_cruise': (21_000, 'ft'), diff --git a/aviary/models/aircraft/large_turboprop_freighter/phase_info.py b/aviary/models/aircraft/large_turboprop_freighter/phase_info.py index 58e37ea647..772b81dc50 100644 --- a/aviary/models/aircraft/large_turboprop_freighter/phase_info.py +++ b/aviary/models/aircraft/large_turboprop_freighter/phase_info.py @@ -76,6 +76,7 @@ # 2DOF two_dof_phase_info = { 'groundroll': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'ground_roll': True, @@ -102,6 +103,7 @@ }, }, 'rotation': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'rotation': True, @@ -133,6 +135,7 @@ }, }, 'ascent': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'num_segments': 4, @@ -173,6 +176,7 @@ }, }, 'accel': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.ACCEL, 'num_segments': 1, @@ -251,6 +255,7 @@ }, }, 'cruise': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.SIMPLE_CRUISE, 'num_segments': 1, diff --git a/aviary/models/missions/solved2dof_default.py b/aviary/models/missions/solved2dof_default.py index a966d3882d..2a01e808ab 100644 --- a/aviary/models/missions/solved2dof_default.py +++ b/aviary/models/missions/solved2dof_default.py @@ -19,6 +19,7 @@ }, }, 'rotate': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -58,6 +59,7 @@ }, }, 'BC': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -87,6 +89,7 @@ }, }, 'C_to_P2': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -115,6 +118,7 @@ }, }, 'P2_to_D': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -143,6 +147,7 @@ }, }, 'DE': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -178,6 +183,7 @@ }, }, 'E_to_P1': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -220,6 +226,7 @@ }, }, 'P1_to_F': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 3, 'order': 3, diff --git a/aviary/models/missions/solved2dof_landing_default.py b/aviary/models/missions/solved2dof_landing_default.py index 72068408ad..205c2102a4 100644 --- a/aviary/models/missions/solved2dof_landing_default.py +++ b/aviary/models/missions/solved2dof_landing_default.py @@ -1,6 +1,7 @@ phase_info = { 'pre_mission': {'include_takeoff': False, 'optimize_mass': False}, 'GH': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -40,6 +41,7 @@ }, }, 'HI': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -79,6 +81,7 @@ }, }, 'IJ': { + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'num_segments': 5, 'order': 3, diff --git a/aviary/models/missions/two_dof_default.py b/aviary/models/missions/two_dof_default.py index 24fafbda14..f7306c6f92 100644 --- a/aviary/models/missions/two_dof_default.py +++ b/aviary/models/missions/two_dof_default.py @@ -105,7 +105,7 @@ }, }, 'accel': { - 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.ACCEL, 'num_segments': 1, @@ -185,7 +185,7 @@ }, }, 'cruise': { - 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.SIMPLE_CRUISE, 'alt_cruise': (37.5e3, 'ft'), diff --git a/aviary/subsystems/aerodynamics/aerodynamics_builder.py b/aviary/subsystems/aerodynamics/aerodynamics_builder.py index ca8376b7e9..9ad62acf31 100644 --- a/aviary/subsystems/aerodynamics/aerodynamics_builder.py +++ b/aviary/subsystems/aerodynamics/aerodynamics_builder.py @@ -212,7 +212,7 @@ def build_mission(self, num_nodes, aviary_inputs, user_options, subsystem_option aero_supergroup.linear_solver = om.DirectSolver() newton = aero_supergroup.nonlinear_solver = om.NewtonSolver(solve_subsystems=True) newton.options['iprint'] = 2 - newton.options['atol'] = 1e-9 + newton.options['atol'] = 1e-12 newton.options['rtol'] = 1e-12 # return the supergroup instead of the individual aero method group diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/L_and_D_increments.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/L_and_D_increments.py index 0e86afdb1e..133de0ba42 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/L_and_D_increments.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/L_and_D_increments.py @@ -113,7 +113,6 @@ def setup_partials(self): ], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( 'delta_CL', @@ -130,7 +129,6 @@ def setup_partials(self): ], dependent=True, method='cs', - step=1e-8, ) def compute(self, inputs, outputs): diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/basic_calculations.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/basic_calculations.py index 402c4a2993..a5d34ab86a 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/basic_calculations.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/basic_calculations.py @@ -77,7 +77,10 @@ def setup(self): def setup_partials(self): # output partials self.declare_partials( - 'VLAM8', [Aircraft.Wing.SWEEP], dependent=True, method='cs', step=1e-8 + 'VLAM8', + [Aircraft.Wing.SWEEP], + dependent=True, + method='cs', ) self.declare_partials( 'VDEL4', @@ -89,7 +92,6 @@ def setup_partials(self): ], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( 'VDEL5', @@ -101,10 +103,12 @@ def setup_partials(self): ], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( - 'VLAM9', [Aircraft.Wing.SLAT_CHORD_RATIO], dependent=True, method='cs', step=1e-8 + 'VLAM9', + [Aircraft.Wing.SLAT_CHORD_RATIO], + dependent=True, + method='cs', ) self.declare_partials( Aircraft.Wing.SLAT_SPAN_RATIO, @@ -116,14 +120,12 @@ def setup_partials(self): ], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( 'chord_to_body_ratio', [Aircraft.Wing.ROOT_CHORD, Aircraft.Fuselage.LENGTH], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( 'body_to_span_ratio', @@ -135,14 +137,12 @@ def setup_partials(self): ], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( 'VLAM12', [Aircraft.Wing.LEADING_EDGE_SWEEP], dependent=True, method='cs', - step=1e-8, ) def compute(self, inputs, outputs): @@ -228,7 +228,6 @@ def setup_partials(self): ['slat_defl', Aircraft.Wing.OPTIMUM_SLAT_DEFLECTION], dependent=True, method='cs', - step=1e-8, ) self.declare_partials( 'flap_defl_ratio', ['flap_defl', Aircraft.Wing.OPTIMUM_FLAP_DEFLECTION], method='cs' diff --git a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/flaps_model.py b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/flaps_model.py index e3e42e9a4a..9654c8a7af 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/flaps_model/flaps_model.py +++ b/aviary/subsystems/aerodynamics/gasp_based/flaps_model/flaps_model.py @@ -134,5 +134,5 @@ def setup(self): self.nonlinear_solver.options['iprint'] = 0 self.nonlinear_solver.options['maxiter'] = 25 - self.nonlinear_solver.options['atol'] = 1e-8 - self.nonlinear_solver.options['rtol'] = 1e-8 + self.nonlinear_solver.options['atol'] = 1e-10 + self.nonlinear_solver.options['rtol'] = 1e-10 diff --git a/aviary/subsystems/mass/gasp_based/mass_premission.py b/aviary/subsystems/mass/gasp_based/mass_premission.py index 4b1f456e51..04a4029c9e 100644 --- a/aviary/subsystems/mass/gasp_based/mass_premission.py +++ b/aviary/subsystems/mass/gasp_based/mass_premission.py @@ -135,8 +135,8 @@ def setup(self): ) newton = self.nonlinear_solver = om.NewtonSolver() - newton.options['atol'] = 1e-9 - newton.options['rtol'] = 1e-9 + newton.options['atol'] = 1e-10 + newton.options['rtol'] = 1e-10 newton.options['iprint'] = 2 newton.options['maxiter'] = 10 newton.options['solve_subsystems'] = True diff --git a/aviary/subsystems/mass/gasp_based/wing.py b/aviary/subsystems/mass/gasp_based/wing.py index a7dca01cb8..e6ec1fbd0a 100644 --- a/aviary/subsystems/mass/gasp_based/wing.py +++ b/aviary/subsystems/mass/gasp_based/wing.py @@ -736,8 +736,8 @@ def setup(self): newton = isolated_mass.nonlinear_solver = om.NewtonSolver() - newton.options['atol'] = 1e-9 - newton.options['rtol'] = 1e-9 + newton.options['atol'] = 1e-10 + newton.options['rtol'] = 1e-10 newton.options['iprint'] = 2 newton.options['maxiter'] = 10 newton.options['solve_subsystems'] = True @@ -796,8 +796,8 @@ def setup(self): newton = isolated_mass.nonlinear_solver = om.NewtonSolver() - newton.options['atol'] = 1e-9 - newton.options['rtol'] = 1e-9 + newton.options['atol'] = 1e-10 + newton.options['rtol'] = 1e-10 newton.options['iprint'] = 2 newton.options['maxiter'] = 10 newton.options['solve_subsystems'] = True diff --git a/aviary/subsystems/propulsion/engine_deck.py b/aviary/subsystems/propulsion/engine_deck.py index 777e3bcb06..fe21644fb2 100644 --- a/aviary/subsystems/propulsion/engine_deck.py +++ b/aviary/subsystems/propulsion/engine_deck.py @@ -861,8 +861,7 @@ def needs_mission_solver(self, aviary_inputs, user_options, subsystem_options): Dictionary of optional arguments for this subsystem in this phase. """ - # The engine is generally part of the throttle balance loop if throttle is being solved. - return True + return False def build_mission(self, num_nodes, aviary_inputs, user_options, subsystem_options) -> om.Group: """ diff --git a/aviary/subsystems/propulsion/test/test_custom_engine_model.py b/aviary/subsystems/propulsion/test/test_custom_engine_model.py index 8f336834b5..3ae1711dd5 100644 --- a/aviary/subsystems/propulsion/test/test_custom_engine_model.py +++ b/aviary/subsystems/propulsion/test/test_custom_engine_model.py @@ -245,8 +245,7 @@ def test_no_solver_loop(self): prob.final_setup() - subsolver = prob.model.traj.phases.cruise.rhs_all.solver_sub.nonlinear_solver - self.assertTrue(isinstance(subsolver, om.NonlinearRunOnce)) + self.assertFalse(hasattr(prob.model.traj.phases.cruise.rhs_all, 'solver_sub')) if __name__ == '__main__': diff --git a/aviary/subsystems/subsystem_builder.py b/aviary/subsystems/subsystem_builder.py index 7130a3411d..3252c3ee10 100644 --- a/aviary/subsystems/subsystem_builder.py +++ b/aviary/subsystems/subsystem_builder.py @@ -43,9 +43,9 @@ def needs_mission_solver( self, aviary_inputs: AviaryValues, user_options: dict, subsystem_options: dict ) -> bool: """ - Return True if the mission subsystem needs to be in the solver loop in mission, otherwise - return False. Aviary will only place it in the solver loop when True. The default is - True. + Return True if the mission subsystem needs to be in the solver loop in the mission ODE, + otherwise return False. Aviary will only place it in the solver loop when True. The default + is False. Parameters ---------- @@ -57,7 +57,7 @@ def needs_mission_solver( Dictionary of optional arguments for this subsystem in this phase. """ - return True + return False def build_pre_mission( self, aviary_inputs: AviaryValues | None = None, subsystem_options: dict | None = None diff --git a/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py b/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py index d3116045fd..211d40b9f4 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py @@ -81,7 +81,7 @@ def test_bench_GwGm_IPOPT_Breguet_Cruise(self): local_phase_info = deepcopy(phase_info) local_phase_info['cruise'] = { - 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.BREGUET_RANGE, 'alt_cruise': (37.5e3, 'ft'), diff --git a/aviary/validation_cases/benchmark_tests/test_bench_optimize_throttle.py b/aviary/validation_cases/benchmark_tests/test_bench_optimize_throttle.py index e6c4ac192d..5d70fe5b46 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_optimize_throttle.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_optimize_throttle.py @@ -15,7 +15,7 @@ phase_info = { 'pre_mission': {'include_takeoff': False, 'optimize_mass': True}, 'climb': { - 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}}, + 'subsystem_options': {'aerodynamics': {'method': 'computed'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -40,7 +40,7 @@ }, }, 'cruise': { - 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}}, + 'subsystem_options': {'aerodynamics': {'method': 'computed'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -67,7 +67,7 @@ }, }, 'descent': { - 'subsystem_options': {'core_aerodynamics': {'method': 'computed'}}, + 'subsystem_options': {'aerodynamics': {'method': 'computed'}}, 'user_options': { 'num_segments': 5, 'order': 3, diff --git a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py index f791b1facc..f1d4faa025 100644 --- a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py +++ b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py @@ -11,7 +11,6 @@ phase_info = { 'pre_mission': {'include_takeoff': False, 'optimize_mass': True}, 'climb': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise', 'solve_alpha': 'true'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -33,7 +32,6 @@ }, }, 'cruise': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise', 'solve_alpha': 'true'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -55,7 +53,6 @@ }, }, 'descent': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise', 'solve_alpha': 'true'}}, 'user_options': { 'num_segments': 5, 'order': 3, diff --git a/aviary/validation_cases/validation_data/test_data/generic_BWB_2dof_phase_info.py b/aviary/validation_cases/validation_data/test_data/generic_BWB_2dof_phase_info.py index 27f7148ef4..2ddd5c31fc 100644 --- a/aviary/validation_cases/validation_data/test_data/generic_BWB_2dof_phase_info.py +++ b/aviary/validation_cases/validation_data/test_data/generic_BWB_2dof_phase_info.py @@ -3,7 +3,7 @@ # 2DOF phase_info = { 'groundroll': { - 'subsystem_options': {'core_aerodynamics': {'method': 'low_speed'}}, + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'ground_roll': True, @@ -31,7 +31,7 @@ }, }, 'rotation': { - 'subsystem_options': {'core_aerodynamics': {'method': 'low_speed'}}, + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'rotation': True, @@ -61,7 +61,7 @@ }, }, 'ascent': { - 'subsystem_options': {'core_aerodynamics': {'method': 'low_speed'}}, + 'subsystem_options': {'aerodynamics': {'method': 'low_speed'}}, 'user_options': { 'phase_type': PhaseType.TWO_DOF_TAKEOFF, 'num_segments': 4, @@ -101,7 +101,7 @@ }, }, 'accel': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.ACCEL, 'num_segments': 1, @@ -127,7 +127,7 @@ }, }, 'climb1': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, 'user_options': { 'num_segments': 2, 'order': 3, @@ -152,7 +152,7 @@ }, }, 'climb2': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, 'user_options': { 'num_segments': 3, 'order': 3, @@ -178,7 +178,7 @@ }, }, 'cruise': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise', 'output_alpha': True}}, 'user_options': { 'phase_type': PhaseType.SIMPLE_CRUISE, 'alt_cruise': (41_000, 'ft'), @@ -193,7 +193,7 @@ }, }, 'desc1': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, 'user_options': { 'num_segments': 5, 'order': 3, @@ -222,7 +222,7 @@ }, }, 'desc2': { - 'subsystem_options': {'core_aerodynamics': {'method': 'cruise'}}, + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, 'user_options': { 'num_segments': 1, 'order': 7,