diff --git a/aviary/api.py b/aviary/api.py index c7bb78a037..ed06c2e55e 100644 --- a/aviary/api.py +++ b/aviary/api.py @@ -64,9 +64,9 @@ ) from aviary.subsystems.propulsion.utils import build_engine_deck -################### -# Level 3 Imports # -################### +#################### +# Advanced Imports # +#################### # Model Setup from aviary.variable_info.functions import ( override_aviary_vars, @@ -185,6 +185,9 @@ # Mass from aviary.subsystems.mass.mass_builder import MassBuilder, CoreMassBuilder +# Energy +from aviary.subsystems.energy.energy_builder import EnergyBuilder, CoreEnergyBuilder + # Performance from aviary.subsystems.performance.performance_builder import ( PerformanceBuilder, @@ -194,7 +197,6 @@ # Propulsion from aviary.subsystems.propulsion.engine_deck import EngineDeck from aviary.subsystems.propulsion.engine_model import EngineModel -from aviary.subsystems.propulsion.motor.motor_builder import MotorBuilder from aviary.subsystems.propulsion.propulsion_builder import ( PropulsionBuilder, CorePropulsionBuilder, diff --git a/aviary/constants.py b/aviary/constants.py index f0a1903167..c3674b70b6 100644 --- a/aviary/constants.py +++ b/aviary/constants.py @@ -45,3 +45,6 @@ # sea level standard temperature in deg R TSLS_DEGR = 518.67 + +# fuel properties +DENSITY_JET_A = (6.7, 'lbm/galUS') diff --git a/aviary/core/aviary_group.py b/aviary/core/aviary_group.py index 3c0f6a5567..eadc8e0717 100644 --- a/aviary/core/aviary_group.py +++ b/aviary/core/aviary_group.py @@ -48,7 +48,7 @@ Verbosity, ) from aviary.variable_info.functions import setup_trajectory_params -from aviary.variable_info.variables import Aircraft, Mission, Settings +from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings TWO_DEGREES_OF_FREEDOM = EquationsOfMotion.TWO_DEGREES_OF_FREEDOM ENERGY_STATE = EquationsOfMotion.ENERGY_STATE @@ -1026,6 +1026,15 @@ def link_phases(self, verbosity=None, comm=None): upstream_analytic = [item for item in vars1 if item.startswith('initial_')] downstream_analytic = [item for item in vars2 if item.startswith('initial_')] + # TODO 2DOF simple cruise breaks this, so it currently can't support discontinuous reserves + if self.mission_method is ENERGY_STATE: + # If the user specifies a specific initial mach/altitude for the first reserve phase, + # allow discontinuities for those variables between the main and reserve mission + if self.reserve_phases and phase2 == self.reserve_phases[0]: + for var in (Dynamic.Mission.ALTITUDE, Dynamic.Atmosphere.MACH): + if phase_info2.get(f'{var}_initial', (None, None))[0]: + common = common - {var} + # Sort because of MPI for var in sorted(common): # Controls: True or False, everything else: None diff --git a/aviary/docs/examples/reserve_mission.ipynb b/aviary/docs/examples/reserve_mission.ipynb index 1a0b318383..e1d7c1ddfa 100644 --- a/aviary/docs/examples/reserve_mission.ipynb +++ b/aviary/docs/examples/reserve_mission.ipynb @@ -184,8 +184,14 @@ } ], "metadata": { + "kernelspec": { + "display_name": "aviary", + "language": "python", + "name": "python3" + }, "language_info": { - "name": "python" + "name": "python", + "version": "3.13.13" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide_unreviewed/FLOPS_based_detailed_takeoff_and_landing.ipynb b/aviary/docs/user_guide_unreviewed/FLOPS_based_detailed_takeoff_and_landing.ipynb index c8d733398c..7c7ca87682 100644 --- a/aviary/docs/user_guide_unreviewed/FLOPS_based_detailed_takeoff_and_landing.ipynb +++ b/aviary/docs/user_guide_unreviewed/FLOPS_based_detailed_takeoff_and_landing.ipynb @@ -353,7 +353,7 @@ " takeoff_mic_p2_to_engine_cutback_builder,\n", " takeoff_rotate_builder,\n", ")\n", - "from aviary.utils.test_utils.default_subsystems import get_default_premission_subsystems\n", + "from aviary.utils.test_utils.default_subsystems import get_default_subsystems\n", "from aviary.variable_info.functions import setup_model_options\n", "\n", "takeoff_trajectory_builder = av.DetailedTakeoffTrajectoryBuilder('detailed_takeoff')\n", @@ -379,7 +379,7 @@ "takeoff = om.Problem()\n", "\n", "# default subsystems\n", - "default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines)\n", + "default_premission_subsystems = get_default_subsystems('FLOPS', engines).values()\n", "\n", "# Upstream pre-mission analysis for aero\n", "takeoff.model.add_subsystem(\n", diff --git a/aviary/docs/user_guide_unreviewed/subsystems/fuel.ipynb b/aviary/docs/user_guide_unreviewed/subsystems/fuel.ipynb index 919c159edc..87d355d177 100644 --- a/aviary/docs/user_guide_unreviewed/subsystems/fuel.ipynb +++ b/aviary/docs/user_guide_unreviewed/subsystems/fuel.ipynb @@ -72,7 +72,7 @@ "Caution should be taken when converting GASP aircraft and evaluating fuel mass.\n", "FRESF, in GASP can be assigned to three differen values: an extra fuel weight in lbs, an extra fuel percentage, or an extra reserve flight time.\n", "FRESF values between 0 and 10 will not evaluate to a extra reserve flight time. Instead this must be accomplished by setting up a reserve mission and a `target_time`. Doing this will help Aviary calculate {glue:md}`Mission.TOTAL_RESERVE_FUEL_MASS` which is the fuel burn for all of the reserve phases. [Read more about generating reserve missions.](../reserve_missions.ipynb)\n", - "FRESF values at or below 0 will convert to a percentage of mission fuel burn reserive and bookept under {glue:md}`Mission.RESERVE_FUEL_MARGIN`. (`FRESHF = -.1` -> `RESERVE_FUEL_MARGIN = 10`)\n", + "FRESF values at or below 0 will convert to a percentage of mission fuel burn reserive and bookept under {glue:md}`Mission.RESERVE_FUEL_MARGIN`. (`FRESHF = -.1` -> `RESERVE_FUEL_MARGIN = 0.1`)\n", "FRESF values at or above 10 will convert directly to lbm fuel burn reserves and accounted for in {glue:md}`Mission.RESERVE_FUEL_MASS_ADDITIONAL`. \n", "Further details on these conversiosn can be viewed in `fortran_to_aviary.py`." ] diff --git a/aviary/interface/test/test_reports.py b/aviary/interface/test/test_reports.py index 33cc08e55f..0e2f13606e 100644 --- a/aviary/interface/test/test_reports.py +++ b/aviary/interface/test/test_reports.py @@ -36,6 +36,7 @@ def test_timeseries_report(self): 'time (s)', 'altitude (ft)', 'altitude_rate (ft/s)', + 'cumulative_fuel_burn (lbm)', 'distance (m)', 'drag (lbf)', 'drag_coefficient (unitless)', @@ -58,6 +59,7 @@ def test_timeseries_report(self): '0.0', '8.333333333333337', '1.0', + '1.0', '21108.341035874902', '0.260025166162475', '0.0', @@ -171,6 +173,6 @@ def test_multiple_off_design_report_directories(self): if __name__ == '__main__': - # unittest.main() - test = TestReports() - test.test_multiple_off_design_report_directories() + unittest.main() + # test = TestReports() + # test.test_multiple_off_design_report_directories() diff --git a/aviary/interface/test/test_reserve_support.py b/aviary/interface/test/test_reserve_support.py deleted file mode 100644 index 7829fe4f35..0000000000 --- a/aviary/interface/test/test_reserve_support.py +++ /dev/null @@ -1,54 +0,0 @@ -import unittest -from copy import deepcopy - -from openmdao.utils.assert_utils import assert_near_equal -from openmdao.utils.testing_utils import use_tempdirs - -from aviary.models.missions.two_dof_default import phase_info as ph_in_gasp -from aviary.core.aviary_problem import AviaryProblem -from aviary.validation_cases.validation_data.test_models.GwFm_phase_info import ( - phase_info as ph_in_flops, -) -from aviary.variable_info.variables import Aircraft, Mission - - -# NOTE this test is probably in the wrong place, it isn't really testing a part of the -# interface. Also test name is `PreMissionGroupTest` which is completely inaccurate. -# This test is for checking if reserve missions are being properly applied, which -# it only partially does (should be checking that the reserve mission properly -# exists in traj as well) -@use_tempdirs -class ReserveTest(unittest.TestCase): - def test_gasp_relative_reserve(self): - phase_info = deepcopy(ph_in_gasp) - - prob = AviaryProblem() - - csv_path = 'models/aircraft/small_single_aisle/small_single_aisle_GASP.csv' - - prob.load_inputs(csv_path, phase_info) - - prob.aviary_inputs.set_val(Mission.GROSS_MASS, 140000.0, units='lbm') - - prob.check_and_preprocess_inputs() - - prob.build_model() - - prob.add_design_variables() - prob.add_objective(objective_type='mass', ref=-1e5) - - prob.setup() - - prob.run_model() - - reserve_percentage = prob.aviary_inputs.get_val( - Mission.RESERVE_FUEL_MARGIN, units='unitless' - ) - - td_mass = prob.model.get_val(Mission.FINAL_MASS, units='lbm') - reserve = prob.model.get_val(Mission.TOTAL_RESERVE_FUEL_MASS, units='lbm') - assert_near_equal(reserve, reserve_percentage / 100 * (140000.0 - td_mass), 1e-3) - - -if __name__ == '__main__': - unittest.main() diff --git a/aviary/mission/energy_state/ode/test/test_landing_ode.py b/aviary/mission/energy_state/ode/test/test_landing_ode.py index c10effb05a..9b361447f7 100644 --- a/aviary/mission/energy_state/ode/test/test_landing_ode.py +++ b/aviary/mission/energy_state/ode/test/test_landing_ode.py @@ -9,7 +9,7 @@ from aviary.utils.aviary_values import AviaryValues from aviary.utils.functions import set_aviary_initial_values from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_data.test_data.advanced_single_aisle_data import ( detailed_landing_flare, inputs, @@ -35,7 +35,9 @@ def test_case(self): preprocess_options(aviary_options, engine_models=engines) - default_mission_subsystems = get_default_mission_subsystems('FLOPS', engines) + default_mission_subsystems = [ + get_default_subsystems('FLOPS', engines)[k] for k in ['propulsion', 'aerodynamics'] + ] prob.model.add_subsystem( 'landing_flare_ode', diff --git a/aviary/mission/energy_state/ode/test/test_takeoff_ode.py b/aviary/mission/energy_state/ode/test/test_takeoff_ode.py index 3ba993ebdf..305bc55b3a 100644 --- a/aviary/mission/energy_state/ode/test/test_takeoff_ode.py +++ b/aviary/mission/energy_state/ode/test/test_takeoff_ode.py @@ -5,17 +5,17 @@ from openmdao.utils.testing_utils import use_tempdirs from aviary.mission.energy_state.ode.takeoff_ode import TakeoffODE +from aviary.subsystems.propulsion.utils import build_engine_deck +from aviary.utils.aviary_values import AviaryValues +from aviary.utils.functions import set_aviary_initial_values +from aviary.utils.preprocessors import preprocess_options +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_data.test_data.advanced_single_aisle_data import ( detailed_takeoff_climbing, detailed_takeoff_ground, inputs, takeoff_subsystem_options, ) -from aviary.subsystems.propulsion.utils import build_engine_deck -from aviary.utils.aviary_values import AviaryValues -from aviary.utils.functions import set_aviary_initial_values -from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems from aviary.validation_cases.validation_tests import do_validation_test from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variables import Aircraft, Dynamic, Mission @@ -99,7 +99,9 @@ def _make_prob(climbing): preprocess_options(aviary_options, engine_models=engines) - default_mission_subsystems = get_default_mission_subsystems('FLOPS', engines) + default_mission_subsystems = [ + get_default_subsystems('FLOPS', engines)[k] for k in ['propulsion', 'aerodynamics'] + ] prob.model.add_subsystem( 'takeoff_ode', diff --git a/aviary/mission/energy_state_problem_configurator.py b/aviary/mission/energy_state_problem_configurator.py index 208e16dcdb..1463adc4b4 100644 --- a/aviary/mission/energy_state_problem_configurator.py +++ b/aviary/mission/energy_state_problem_configurator.py @@ -280,6 +280,10 @@ def check_trajectory(self, aviary_group): continue if initial != final: + # the first reserve mission phase is allowed to have discontinuous mach and alt + if aviary_group.reserve_phases and right_name == aviary_group.reserve_phases[0]: + if stem in (Dynamic.Atmosphere.MACH, Dynamic.Mission.ALTITUDE): + continue msg += ' Constraint mismatch across phase boundary:\n' msg += f' {left_name} {stem}_final: {final}\n' msg += f' {right_name} {stem}_initial: {initial}\n' 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..c4243b6429 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 @@ -7,7 +7,7 @@ from aviary.mission.solved_two_dof.ode.groundroll_ode import GroundrollODE 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.default_subsystems import get_default_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 @@ -24,9 +24,10 @@ def setUp(self): aviary_options = get_option_defaults() aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) aviary_options.set_val(Mission.GRAVITY, val=32.2, units='ft/s**2') - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = GroundrollODE( num_nodes=2, diff --git a/aviary/mission/solved_two_dof/ode/test/test_unsteady_solved_ode.py b/aviary/mission/solved_two_dof/ode/test/test_unsteady_solved_ode.py index c5ac559a71..3096350bae 100644 --- a/aviary/mission/solved_two_dof/ode/test/test_unsteady_solved_ode.py +++ b/aviary/mission/solved_two_dof/ode/test/test_unsteady_solved_ode.py @@ -9,7 +9,7 @@ from aviary.mission.solved_two_dof.ode.unsteady_solved_ode import UnsteadySolvedODE 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.default_subsystems import get_default_subsystems from aviary.variable_info.enums import SpeedType from aviary.variable_info.functions import setup_model_options from aviary.variable_info.options import get_option_defaults @@ -28,9 +28,10 @@ def _test_unsteady_solved_ode( p = om.Problem() aviary_options = get_option_defaults() - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] ode = UnsteadySolvedODE( num_nodes=nn, 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..85aeb5ac5b 100644 --- a/aviary/mission/two_dof/ode/test/test_accel_ode.py +++ b/aviary/mission/two_dof/ode/test/test_accel_ode.py @@ -8,7 +8,7 @@ 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.aviary_values import AviaryValues -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_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 @@ -24,9 +24,10 @@ def setUp(self): aviary_options = get_option_defaults() aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.sys = self.prob.model = AccelODE( num_nodes=2, aviary_options=aviary_options, subsystems=default_mission_subsystems 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..5e1e4da1e7 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 @@ -8,7 +8,7 @@ from aviary.mission.two_dof.ode.breguet_cruise_ode import BreguetCruiseODE, ElectricBreguetCruiseODE 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.default_subsystems import get_default_subsystems 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 @@ -21,9 +21,10 @@ def setUp(self): aviary_options = get_option_defaults() aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = BreguetCruiseODE( num_nodes=2, @@ -89,10 +90,10 @@ def setUp(self): Aircraft.Engine.DATA_FILE, 'mission/two_dof/ode/test/test_data/turbofan_23k_electrified.csv', ) - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', build_engine_deck(aviary_options) - ) - + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = ElectricBreguetCruiseODE( num_nodes=2, aviary_options=aviary_options, diff --git a/aviary/mission/two_dof/ode/test/test_flight_ode.py b/aviary/mission/two_dof/ode/test/test_flight_ode.py index 2fdcd8a372..1957905224 100644 --- a/aviary/mission/two_dof/ode/test/test_flight_ode.py +++ b/aviary/mission/two_dof/ode/test/test_flight_ode.py @@ -10,7 +10,7 @@ from aviary.mission.two_dof.ode.flight_ode import FlightODE 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.default_subsystems import get_default_subsystems from aviary.utils.test_utils.IO_test_util import check_prob_outputs from aviary.variable_info.enums import SpeedType, Verbosity from aviary.variable_info.functions import setup_model_options @@ -29,9 +29,10 @@ def setUp(self): aviary_options.set_val('verbosity', Verbosity.QUIET) aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.sys = self.prob.model = FlightODE( num_nodes=1, @@ -143,9 +144,10 @@ def setUp(self): self.prob = om.Problem() aviary_options = get_option_defaults() - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.sys = self.prob.model = FlightODE( num_nodes=1, 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 index e89f8a98c8..2c687190e7 100644 --- a/aviary/mission/two_dof/ode/test/test_flight_path_ode.py +++ b/aviary/mission/two_dof/ode/test/test_flight_path_ode.py @@ -8,7 +8,7 @@ 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.default_subsystems import get_default_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 @@ -26,9 +26,10 @@ def setUp(self): 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)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.fp = self.prob.model = FlightPathODE( num_nodes=2, diff --git a/aviary/mission/two_dof/ode/test/test_landing_ode.py b/aviary/mission/two_dof/ode/test/test_landing_ode.py index 2a802c1f02..6cff6ecb2d 100644 --- a/aviary/mission/two_dof/ode/test/test_landing_ode.py +++ b/aviary/mission/two_dof/ode/test/test_landing_ode.py @@ -9,7 +9,7 @@ from aviary.mission.two_dof.ode.landing_ode import LandingSegment 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.default_subsystems import get_default_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 @@ -25,9 +25,13 @@ def setUp(self): options = get_option_defaults() engines = [build_engine_deck(options)] - subsystems = get_default_mission_subsystems('GASP', engines) + default_mission_subsystems = [ + get_default_subsystems('GASP', engines)[k] for k in ['propulsion', 'aerodynamics'] + ] - self.prob.model = LandingSegment(aviary_options=options, subsystems=subsystems) + self.prob.model = LandingSegment( + aviary_options=options, subsystems=default_mission_subsystems + ) setup_model_options(self.prob, options) self.prob.model.set_input_defaults(Mission.Landing.AIRPORT_ALTITUDE, 0, units='ft') 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..cc92321d23 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 @@ -8,7 +8,7 @@ from aviary.mission.two_dof.ode.simple_cruise_ode import SimpleCruiseODE 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.default_subsystems import get_default_subsystems 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 @@ -21,9 +21,10 @@ def setUp(self): aviary_options = get_option_defaults() aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = SimpleCruiseODE( num_nodes=2, diff --git a/aviary/mission/two_dof/ode/test/test_takeoff_ode.py b/aviary/mission/two_dof/ode/test/test_takeoff_ode.py index b2552ec7bf..dced79d7c1 100644 --- a/aviary/mission/two_dof/ode/test/test_takeoff_ode.py +++ b/aviary/mission/two_dof/ode/test/test_takeoff_ode.py @@ -9,7 +9,7 @@ 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.aviary_values import AviaryValues -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_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 @@ -26,9 +26,10 @@ def setUp(self): aviary_options = get_option_defaults() aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) aviary_options.set_val(Mission.GRAVITY, val=32.2, units='ft/s**2') - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = TakeOffODE( num_nodes=2, @@ -85,9 +86,10 @@ def setUp(self): aviary_options = get_option_defaults() aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) aviary_options.set_val(Mission.GRAVITY, val=32.2, units='ft/s**2') - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = TakeOffODE( num_nodes=2, @@ -150,9 +152,10 @@ def setUp(self): aviary_options.set_val(Aircraft.Engine.GLOBAL_THROTTLE, True) aviary_options.set_val(Mission.GRAVITY, val=32.2, units='ft/s**2') aviary_options.set_val(Aircraft.Engine.NUM_ENGINES, val=[2], units='unitless') - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(aviary_options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(aviary_options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = TakeOffODE( num_nodes=2, aviary_options=aviary_options, subsystems=default_mission_subsystems diff --git a/aviary/mission/two_dof/ode/test/test_taxi_ode.py b/aviary/mission/two_dof/ode/test/test_taxi_ode.py index 2535215b5e..e87034e9a2 100644 --- a/aviary/mission/two_dof/ode/test/test_taxi_ode.py +++ b/aviary/mission/two_dof/ode/test/test_taxi_ode.py @@ -9,7 +9,7 @@ from aviary.mission.two_dof.ode.taxi_ode import TaxiSegment 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.default_subsystems import get_default_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 @@ -25,9 +25,10 @@ def setUp(self): options = get_option_defaults() options.set_val(Mission.Taxi.DURATION, 0.1677, units='h') - default_mission_subsystems = get_default_mission_subsystems( - 'GASP', [build_engine_deck(options)] - ) + default_mission_subsystems = [ + get_default_subsystems('GASP', [build_engine_deck(options)])[k] + for k in ['propulsion', 'aerodynamics'] + ] self.prob.model = TaxiSegment(aviary_options=options, subsystems=default_mission_subsystems) diff --git a/aviary/models/aircraft/blended_wing_body/generic_BWB_GASP.csv b/aviary/models/aircraft/blended_wing_body/generic_BWB_GASP.csv index 87d14a05b2..0d4e264fd0 100644 --- a/aviary/models/aircraft/blended_wing_body/generic_BWB_GASP.csv +++ b/aviary/models/aircraft/blended_wing_body/generic_BWB_GASP.csv @@ -32,7 +32,7 @@ aircraft:design:emergency_equipment_mass,100,lbm aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless mission:reserve_fuel_mass_additional,0,lbm -mission:reserve_fuel_margin,10.0,unitless +mission:reserve_fuel_margin,0.1,unitless aircraft:design:static_margin,0.05,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:drag_divergence_shift,0.025,unitless diff --git a/aviary/models/aircraft/small_single_aisle/small_single_aisle_GASP.csv b/aviary/models/aircraft/small_single_aisle/small_single_aisle_GASP.csv index da2b662cb0..412ef90c64 100644 --- a/aviary/models/aircraft/small_single_aisle/small_single_aisle_GASP.csv +++ b/aviary/models/aircraft/small_single_aisle/small_single_aisle_GASP.csv @@ -22,7 +22,7 @@ aircraft:design:emergency_equipment_mass,50,lbm aircraft:design:max_structural_speed,402.5,mi/h aircraft:design:part25_structural_category,3,unitless mission:reserve_fuel_mass_additional,0,lbm -mission:reserve_fuel_margin,12.5,unitless +mission:reserve_fuel_margin,0.125,unitless aircraft:design:static_margin,0.05,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:drag_divergence_shift,0.025,unitless diff --git a/aviary/subsystems/aerodynamics/flops_based/test/test_computed_aero_group.py b/aviary/subsystems/aerodynamics/flops_based/test/test_computed_aero_group.py index 41ce92c698..2662e80f7d 100644 --- a/aviary/subsystems/aerodynamics/flops_based/test/test_computed_aero_group.py +++ b/aviary/subsystems/aerodynamics/flops_based/test/test_computed_aero_group.py @@ -9,7 +9,7 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import get_default_premission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_tests import get_flops_inputs, get_flops_outputs from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variables import Aircraft, Dynamic, Settings @@ -34,10 +34,13 @@ def test_basic_large_single_aisle_1(self): engines = [build_engine_deck(flops_inputs)] preprocess_options(flops_inputs, engine_models=engines) - # don't need mass subsystem, so we skip it - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines)[:-1] + # only want prop, geom, aero + default_premission_subsystems = get_default_subsystems('FLOPS', engines) # we just want aero for mission, make a copy by itself - aero = default_premission_subsystems[-1] + aero = default_premission_subsystems['aerodynamics'] + premission_subsystems = [ + default_premission_subsystems[k] for k in ['propulsion', 'geometry', 'aerodynamics'] + ] # Design conditions: # alt = 41000 @@ -73,7 +76,7 @@ def test_basic_large_single_aisle_1(self): 'pre_mission', CorePreMission( aviary_options=flops_inputs, - subsystems=default_premission_subsystems, + subsystems=premission_subsystems, subsystem_options={}, ), promotes_inputs=['aircraft:*'], @@ -200,9 +203,12 @@ def test_n3cc_drag(self): preprocess_options(flops_inputs, engine_models=engines) # don't need mass subsystem, so we skip it - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines)[:-1] + default_premission_subsystems = get_default_subsystems('FLOPS', engines) # we just want aero for mission, make a copy by itself - aero = default_premission_subsystems[-1] + aero = default_premission_subsystems['aerodynamics'] + premission_subsystems = [ + default_premission_subsystems[k] for k in ['propulsion', 'geometry', 'aerodynamics'] + ] Sref = 1220.0 @@ -233,7 +239,7 @@ def test_n3cc_drag(self): 'pre_mission', CorePreMission( aviary_options=flops_inputs, - subsystems=default_premission_subsystems, + subsystems=premission_subsystems, subsystem_options={}, ), promotes_inputs=['aircraft:*'], @@ -357,9 +363,12 @@ def test_large_single_aisle_2_drag(self): preprocess_options(flops_inputs, engine_models=engines) # don't need mass subsystem, so we skip it - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines)[:-1] + default_premission_subsystems = get_default_subsystems('FLOPS', engines) # we just want aero for mission, make a copy by itself - aero = default_premission_subsystems[-1] + aero = default_premission_subsystems['aerodynamics'] + premission_subsystems = [ + default_premission_subsystems[k] for k in ['propulsion', 'geometry', 'aerodynamics'] + ] Sref = 1341.0 @@ -390,7 +399,7 @@ def test_large_single_aisle_2_drag(self): 'pre_mission', CorePreMission( aviary_options=flops_inputs, - subsystems=default_premission_subsystems, + subsystems=premission_subsystems, subsystem_options={}, ), promotes_inputs=['aircraft:*'], diff --git a/aviary/subsystems/aerodynamics/flops_based/test/test_tabular_aero_group.py b/aviary/subsystems/aerodynamics/flops_based/test/test_tabular_aero_group.py index e133ad0801..d85708014a 100644 --- a/aviary/subsystems/aerodynamics/flops_based/test/test_tabular_aero_group.py +++ b/aviary/subsystems/aerodynamics/flops_based/test/test_tabular_aero_group.py @@ -7,8 +7,8 @@ from openmdao.utils.testing_utils import use_tempdirs from parameterized import parameterized -from aviary.models.missions.energy_state_default import phase_info from aviary.core.aviary_problem import AviaryProblem +from aviary.models.missions.energy_state_default import phase_info from aviary.subsystems.aerodynamics.aerodynamics_builder import CoreAerodynamicsBuilder from aviary.subsystems.atmosphere.atmosphere import Atmosphere from aviary.subsystems.premission import CorePreMission @@ -16,7 +16,7 @@ from aviary.utils.aviary_values import AviaryValues from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults from aviary.utils.named_values import NamedValues -from aviary.utils.test_utils.default_subsystems import get_default_premission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_tests import get_flops_inputs, get_flops_outputs, print_case from aviary.variable_info.enums import LegacyCode from aviary.variable_info.functions import setup_model_options @@ -681,14 +681,17 @@ def setup(self): engines = [build_engine_deck(aviary_options)] # don't need mass, skip it - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines)[:-1] + default_premission_subsystems = get_default_subsystems('FLOPS', engines) + premission_subsystems = [ + default_premission_subsystems[k] for k in ['propulsion', 'geometry', 'aerodynamics'] + ] # Upstream pre-mission analysis for aero pre_mission: om.Group = self.add_subsystem( 'pre_mission', CorePreMission( aviary_options=aviary_options, - subsystems=default_premission_subsystems, + subsystems=premission_subsystems, subsystem_options={}, ), promotes_inputs=['aircraft:*'], diff --git a/aviary/subsystems/energy/energy_builder.py b/aviary/subsystems/energy/energy_builder.py index 7a98e05660..b92d4dfb43 100644 --- a/aviary/subsystems/energy/energy_builder.py +++ b/aviary/subsystems/energy/energy_builder.py @@ -11,14 +11,16 @@ # import warnings import openmdao.api as om +from openmdao.core.system import System from aviary.mission.utils import separate_reserve_phases +from aviary.subsystems.energy.flops_based.flops_tank import FuelTankFLOPS from aviary.subsystems.energy.fuel_summation import FuelSummationGroup +from aviary.subsystems.energy.gasp_based.gasp_tank import FuelTankGASP from aviary.subsystems.subsystem_builder import SubsystemBuilder - -# from aviary.utils.aviary_values import AviaryValues -# from aviary.variable_info.enums import Verbosity -from aviary.variable_info.variables import Mission +from aviary.utils.aviary_values import AviaryValues +from aviary.variable_info.enums import LegacyCode +from aviary.variable_info.variables import Dynamic, Mission, Settings class EnergyBuilder(SubsystemBuilder): @@ -37,13 +39,29 @@ class EnergyBuilder(SubsystemBuilder): class CoreEnergyBuilder(EnergyBuilder): """Core energy subsystem builder.""" + def build_pre_mission( + self, aviary_inputs: AviaryValues | None = None, subsystem_options: dict | None = None + ) -> None | System: + if aviary_inputs.get_val(Settings.MASS_METHOD) is LegacyCode.FLOPS: + tank = FuelTankFLOPS(meta_data=self.meta_data) + # TODO subsystem options needs sub-dict like propulsion + return tank.build_pre_mission( + aviary_inputs=aviary_inputs, subsystem_options=subsystem_options + ) + if aviary_inputs.get_val(Settings.MASS_METHOD) is LegacyCode.GASP: + tank = FuelTankGASP(meta_data=self.meta_data) + # TODO subsystem options needs sub-dict like propulsion + return tank.build_pre_mission( + aviary_inputs=aviary_inputs, subsystem_options=subsystem_options + ) + def build_post_mission( self, - aviary_inputs=None, + aviary_inputs: AviaryValues | None = None, mission_info=None, - subsystem_options=None, - phase_mission_bus_lengths=None, - ): + subsystem_options: dict | None = None, + phase_mission_bus_lengths: dict | None = None, + ) -> None | System: energy_group = om.Group() fuelgroup = FuelSummationGroup(mission_info=mission_info) @@ -63,33 +81,30 @@ def build_post_mission( # Location of the subsystems_report folder this report will be placed in # """ - def get_post_mission_bus_variables(self, aviary_inputs=None, mission_info=None): + def get_post_mission_bus_variables( + self, aviary_inputs: AviaryValues | None = None, mission_info: dict | None = None + ) -> dict: post_mission_bus = {} main_phases, reserve_phases = separate_reserve_phases(mission_info) - post_mission_names = [ - f'{self.name}.fuel_burned.mass_final', - ] - if aviary_inputs.get_val(Mission.RESERVE_FUEL_MARGIN) != 0: - post_mission_names.append(f'{self.name}.reserve_fuel_frac.final_mass') - post_mission_bus[main_phases[-1]] = { - f'mass': { - 'post_mission_name': post_mission_names, + Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED: { + 'post_mission_name': f'{self.name}.main_mission_fuel.mission_fuel', 'src_indices': [-1], } } if reserve_phases: post_mission_bus[reserve_phases[0]] = { - f'mass': { - 'post_mission_name': f'{self.name}.reserve_fuel_burned.mass_initial', + Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED: { + 'post_mission_name': f'{self.name}.reserve_mission_fuel.fuel_initial', 'src_indices': [0], } } + post_mission_bus[reserve_phases[-1]] = { - f'mass': { - 'post_mission_name': f'{self.name}.reserve_fuel_burned.mass_final', + Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED: { + 'post_mission_name': f'{self.name}.reserve_mission_fuel.fuel_final', 'src_indices': [-1], } } @@ -151,3 +166,32 @@ def get_post_mission_bus_variables(self, aviary_inputs=None, mission_info=None): # # ) # return constraints + + ### TODO ### + ### v-- The following should only happen when fuel is actually present on the aircraft!! --v ### + + def get_states( + self, + aviary_inputs: AviaryValues | None = None, + user_options: dict | None = None, + subsystem_options: dict | None = None, + ) -> dict: + state_dict = { + Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED: { + 'fix_initial': True, + 'fix_final': False, + 'lower': 0.0, + 'ref': -1e4, + 'defect_ref': 1e6, + 'units': 'lbm', + 'rate_source': Dynamic.Vehicle.Propulsion.FUEL_MASS_FLOW_RATE_NEGATIVE_TOTAL, + 'input_initial': 0.0, + # 'targets': Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED, + } + } + + return state_dict + + def get_linked_variables(self, aviary_inputs=None, user_options=None, subsystem_options=None): + # link cumulative fuel burn between phases + return [Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED] diff --git a/aviary/subsystems/energy/flops_based/flops_tank.py b/aviary/subsystems/energy/flops_based/flops_tank.py new file mode 100644 index 0000000000..d7610dc5ef --- /dev/null +++ b/aviary/subsystems/energy/flops_based/flops_tank.py @@ -0,0 +1,12 @@ +from openmdao.core.system import System + +from aviary.subsystems.energy.fuel_tank_model import FuelTankModel +from aviary.subsystems.mass.flops_based.fuel_capacity import FuelCapacityGroup +from aviary.utils.aviary_values import AviaryValues + + +class FuelTankFLOPS(FuelTankModel): + def build_pre_mission( + self, aviary_inputs: AviaryValues | None = None, subsystem_options: dict | None = None + ) -> None | System: + return FuelCapacityGroup() diff --git a/aviary/subsystems/energy/fuel_summation.py b/aviary/subsystems/energy/fuel_summation.py index 2fe1a17d5c..bbce72573e 100644 --- a/aviary/subsystems/energy/fuel_summation.py +++ b/aviary/subsystems/energy/fuel_summation.py @@ -34,65 +34,70 @@ def setup(self): main_phases, reserve_phases = separate_reserve_phases(mission_info) # Check if main_phases[] is accessible - try: - main_phases[0] - except BaseException: + if len(main_phases) == 0: raise ValueError( - 'main_phases[] dictionary is not accessible. For ENERGY_STATE and ' - 'SOLVED_2DOF missions, check_and_preprocess_inputs() must be called ' - 'before add_post_mission_systems().' + 'main_phases dictionary is not accessible. For ENERGY_STATE and SOLVED_2DOF ' + 'missions, check_and_preprocess_inputs() must be called before ' + 'add_post_mission_systems().' ) - # Fuel burn in taxi + takeoff + regular phases + # Fuel burn in taxi + takeoff + regular phases + landing self.add_subsystem( - 'fuel_burned', + 'main_mission_fuel', om.ExecComp( - 'fuel_burned = mass_initial - mass_final', - mass_initial={'units': 'lbm'}, - mass_final={ - 'units': 'lbm' - }, # this final mass already includes fuel burned in taxi and takeoff - fuel_burned={'units': 'lbm'}, + # mission fuel is currently negative + 'fuel_burned = taxi_fuel - mission_fuel + takeoff_fuel', # + landing_fuel', + taxi_fuel={'units': 'lbm', 'val': 0}, + mission_fuel={'units': 'lbm', 'val': 0}, + takeoff_fuel={'units': 'lbm', 'val': 0}, + # landing_fuel={'units': 'lbm', 'val': 0}, + fuel_burned={'units': 'lbm', 'val': 0}, ), - promotes_inputs=[('mass_initial', Mission.GROSS_MASS)], + # NOTE taxi and takeoff fuel vars not used for 2DOF takeoff + promotes_inputs=[ + ('taxi_fuel', Mission.Taxi.FUEL_MASS_TAXI_OUT), + ('takeoff_fuel', Mission.Takeoff.FUEL_MASS), + # no landing mass? + # ('landing_fuel', Mission.Landing.FUEL_MASS), + ], promotes_outputs=[('fuel_burned', Mission.FUEL_MASS)], ) # Fuel burn in reserve phases if reserve_phases: ecomp = om.ExecComp( - 'reserve_fuel_burned = mass_initial - mass_final', - mass_initial={'units': 'lbm'}, - mass_final={'units': 'lbm'}, - reserve_fuel_burned={'units': 'lbm'}, + # fuel flow is currently always negative + 'reserve_fuel = -(fuel_final - fuel_initial)', + fuel_initial={'units': 'lbm', 'val': 0}, + fuel_final={'units': 'lbm', 'val': 0}, + reserve_fuel={'units': 'lbm', 'val': 0}, ) self.add_subsystem( - 'reserve_fuel_burned', + 'reserve_mission_fuel', ecomp, - promotes=[('reserve_fuel_burned', Mission.RESERVE_FUEL_MASS)], + promotes=[('reserve_fuel', Mission.RESERVE_FUEL_MASS)], ) reserve_fuel_margin = self.options[Mission.RESERVE_FUEL_MARGIN] if reserve_fuel_margin != 0: # Originally tried to reference Mission.FUEL_MASS for fuel burn but in some tests this led to errors reserve_fuel_frac = om.ExecComp( - 'reserve_fuel_margin_mass = reserve_fuel_margin / 100 * (mass_initial - final_mass)', - reserve_fuel_margin_mass={'units': 'lbm'}, + 'reserve_fuel_margin_mass = reserve_fuel_margin * mission_fuel', + reserve_fuel_margin_mass={'units': 'lbm', 'val': 0}, reserve_fuel_margin={ 'units': 'unitless', 'val': reserve_fuel_margin, }, - mass_initial={'units': 'lbm'}, - final_mass={'units': 'lbm'}, + mission_fuel={'units': 'lbm', 'val': 0}, ) self.add_subsystem( 'reserve_fuel_frac', reserve_fuel_frac, promotes_inputs=[ - ('mass_initial', Mission.GROSS_MASS), ('reserve_fuel_margin', Mission.RESERVE_FUEL_MARGIN), + ('mission_fuel', Mission.FUEL_MASS), ], promotes_outputs=['reserve_fuel_margin_mass'], ) @@ -100,7 +105,7 @@ def setup(self): reserve_fuel_mass_additional, units = self.options[Mission.RESERVE_FUEL_MASS_ADDITIONAL] reserve_fuel_mass = om.ExecComp( 'reserve_fuel_mass = reserve_fuel_margin_mass + reserve_fuel_mass_additional + reserve_fuel_burned', - reserve_fuel_mass={'units': 'lbm', 'shape': 1}, + reserve_fuel_mass={'units': 'lbm', 'val': 0}, reserve_fuel_margin_mass={'units': 'lbm', 'val': 0}, reserve_fuel_mass_additional={'units': 'lbm', 'val': reserve_fuel_mass_additional}, reserve_fuel_burned={'units': 'lbm', 'val': 0}, @@ -123,10 +128,10 @@ def setup(self): 'total_fuel_mass_con', om.ExecComp( 'total_fuel_mass_constraint = total_fuel_mass - mission_fuel_burned - reserve_fuel_mass', - total_fuel_mass_constraint={'units': 'lbm'}, - total_fuel_mass={'units': 'lbm'}, - mission_fuel_burned={'units': 'lbm'}, - reserve_fuel_mass={'units': 'lbm'}, + total_fuel_mass_constraint={'units': 'lbm', 'val': 0}, + total_fuel_mass={'units': 'lbm', 'val': 0}, + mission_fuel_burned={'units': 'lbm', 'val': 0}, + reserve_fuel_mass={'units': 'lbm', 'val': 0}, ), promotes_inputs=[ ('total_fuel_mass', Mission.TOTAL_FUEL_MASS), @@ -138,10 +143,10 @@ def setup(self): ecomp = om.ExecComp( 'excess_fuel_capacity = total_fuel_capacity - unusable_fuel - overall_fuel', - total_fuel_capacity={'units': 'lbm'}, - unusable_fuel={'units': 'lbm'}, - overall_fuel={'units': 'lbm'}, - excess_fuel_capacity={'units': 'lbm'}, + total_fuel_capacity={'units': 'lbm', 'val': 0}, + unusable_fuel={'units': 'lbm', 'val': 0}, + overall_fuel={'units': 'lbm', 'val': 0}, + excess_fuel_capacity={'units': 'lbm', 'val': 0}, ) self.add_subsystem( @@ -176,8 +181,8 @@ def setup(self): om.ExecComp( 'block_fuel = mission_fuel_burned + fuel_burned_taxi_in', block_fuel={'units': 'lbm'}, - mission_fuel_burned={'units': 'lbm'}, - fuel_burned_taxi_in={'units': 'lbm'}, + mission_fuel_burned={'units': 'lbm', 'val': 0}, + fuel_burned_taxi_in={'units': 'lbm', 'val': 0}, ), promotes_inputs=[ ('mission_fuel_burned', Mission.FUEL_MASS), diff --git a/aviary/subsystems/energy/fuel_tank_model.py b/aviary/subsystems/energy/fuel_tank_model.py new file mode 100644 index 0000000000..2215914961 --- /dev/null +++ b/aviary/subsystems/energy/fuel_tank_model.py @@ -0,0 +1,76 @@ +""" +Define utilities for building fuel tank models. + +Classes +------- +TankModel : the interface for a fuel tank model builder. +""" + +import openmdao.api as om +from openmdao.core.system import System + +from aviary.subsystems.subsystem_builder import SubsystemBuilder +from aviary.utils.aviary_values import AviaryValues +from aviary.variable_info.enums import FuelType, LegacyCode, Verbosity +from aviary.variable_info.variables import Settings + + +class FuelTankModel(SubsystemBuilder): + """ + Define the interface for a fuel tank model builder. + + Attributes + ---------- + name : str ('tank_model') + object label. + options : AviaryValues () + inputs and options related to engine model. + + Methods + ------- + build_pre_mission + build_mission + build_post_mission + get_val + get_item + set_val + update + """ + + _default_name = 'tank_model' + + def __init__( + self, + name: str | None = None, + meta_data: dict | None = None, + fuel_type: FuelType = FuelType.JET_A, + **kwargs, + ): + # by default, name tank after fuel type + if name is None: + name = fuel_type.value + '_tank_model' + + super().__init__(name, meta_data=meta_data) + + self.fuel_type = fuel_type + + # def build_pre_mission( + # self, aviary_inputs: AviaryValues | None = None, subsystem_options: dict | None = None + # ) -> None | System: + # if aviary_inputs.get_val(Settings.MASS_METHOD) is LegacyCode.FLOPS: + # pre_mission = om.Group() + # return pre_mission + # elif aviary_inputs.get_val(Settings.MASS_METHOD) is LegacyCode.GASP: + # pre_mission = om.Group() + # return pre_mission + # else: + # return None + + def build_mission( + self, + num_nodes: int, + aviary_inputs: AviaryValues | None = None, + user_options: dict | None = None, + subsystem_options: dict | None = None, + ) -> None | System: + return super().build_mission(num_nodes, aviary_inputs, user_options, subsystem_options) diff --git a/aviary/subsystems/energy/gasp_based/gasp_tank.py b/aviary/subsystems/energy/gasp_based/gasp_tank.py new file mode 100644 index 0000000000..62dec0ec98 --- /dev/null +++ b/aviary/subsystems/energy/gasp_based/gasp_tank.py @@ -0,0 +1,12 @@ +from openmdao.core.system import System + +from aviary.subsystems.energy.fuel_tank_model import FuelTankModel +from aviary.subsystems.mass.gasp_based.fuel import TankCapacity +from aviary.utils.aviary_values import AviaryValues + + +class FuelTankGASP(FuelTankModel): + def build_pre_mission( + self, aviary_inputs: AviaryValues | None = None, subsystem_options: dict | None = None + ) -> None | System: + return TankCapacity() diff --git a/aviary/subsystems/energy/test/test_fuel_summation.py b/aviary/subsystems/energy/test/test_fuel_summation.py index 049b3363df..f9feef3020 100644 --- a/aviary/subsystems/energy/test/test_fuel_summation.py +++ b/aviary/subsystems/energy/test/test_fuel_summation.py @@ -1,11 +1,9 @@ -from copy import deepcopy import unittest +from copy import deepcopy -import numpy as np import openmdao.api as om from openmdao.utils.assert_utils import assert_check_partials, assert_near_equal -import aviary.api as av from aviary.models.missions.energy_state_default import phase_info from aviary.subsystems.energy.fuel_summation import FuelSummationGroup from aviary.utils.aviary_values import AviaryValues @@ -27,7 +25,7 @@ def test_fuel_group(self): prob, AviaryValues( { - Mission.RESERVE_FUEL_MARGIN: (0.2, 'unitless'), + Mission.RESERVE_FUEL_MARGIN: (0.02, 'unitless'), Mission.RESERVE_FUEL_MASS_ADDITIONAL: (300, 'lbm'), Aircraft.Fuel.IGNORE_FUEL_CAPACITY_CONSTRAINT: (False, 'unitless'), } @@ -36,20 +34,19 @@ def test_fuel_group(self): prob.setup(force_alloc_complex=True) - prob.set_val(Mission.GROSS_MASS, 140_000, units='lbm') - prob.set_val('fuel_burned.mass_final', 120_000, units='lbm') - prob.set_val('reserve_fuel_burned.mass_initial', 120_000, 'lbm') - prob.set_val('reserve_fuel_burned.mass_final', 110_000, 'lbm') - prob.set_val('reserve_fuel_frac.final_mass', 110_000, 'lbm') + # prob.set_val(Mission.GROSS_MASS, 140_000, units='lbm') + prob.set_val('main_mission_fuel.mission_fuel', -20_000, units='lbm') + prob.set_val('reserve_mission_fuel.fuel_initial', -20_000, 'lbm') + prob.set_val('reserve_mission_fuel.fuel_final', -30_000, 'lbm') prob.set_val(Mission.TOTAL_FUEL_MASS, 30_000, 'lbm') prob.set_val(Aircraft.Fuel.MAX_CAPACITY_MASS, 20_000, units='lbm') prob.set_val(Aircraft.Fuel.UNUSABLE_FUEL_MASS, 750, units='lbm') - prob.set_val(Mission.Taxi.FUEL_MASS_TAXI_IN, 20, units='lbm') + prob.set_val(Mission.Taxi.FUEL_MASS_TAXI_OUT, 20, units='lbm') prob.run_model() expected_values = { - Mission.Constraints.MASS_RESIDUAL: (-360, 'lbm'), + Mission.Constraints.MASS_RESIDUAL: (-720.4, 'lbm'), Mission.Constraints.EXCESS_FUEL_MASS_CAPACITY: (-10750, 'lbm'), Mission.BLOCK_FUEL_MASS: (20020, 'lbm'), } diff --git a/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py b/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py index 536eac45b5..1a946e1a45 100644 --- a/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py +++ b/aviary/subsystems/geometry/flops_based/test/test_wing_detailed_bwb.py @@ -131,7 +131,7 @@ def test_case1(self): exp3 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 42.9, 42.9, 42.9, 42.9, 42.9, 42.9] assert_near_equal(out3, exp3, tolerance=1e-10) - partial_data = self.prob.check_partials(out_stream=None, method='cs', step=1.1e-40) + partial_data = self.prob.check_partials(out_stream=None, method='fd') assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) def test_case2(self): @@ -182,7 +182,7 @@ def test_case2(self): exp3 = [0.0, 0.0, 10.0, 17.0, 17.0, 17.0] assert_near_equal(out3, exp3, tolerance=1e-10) - partial_data = self.prob.check_partials(out_stream=None, method='cs') + partial_data = self.prob.check_partials(out_stream=None, method='fd') assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) def test_case3(self): @@ -265,7 +265,7 @@ def test_case3(self): exp3 = prob.get_val(Aircraft.Wing.LOAD_PATH_SWEEP_DISTRIBUTION) assert_near_equal(out3, exp3, tolerance=1e-10) - partial_data = self.prob.check_partials(out_stream=None, method='cs', step=1.1e-40) + partial_data = self.prob.check_partials(out_stream=None, method='fd') assert_check_partials(partial_data, atol=1e-9, rtol=1e-8) @@ -313,7 +313,7 @@ def test_case1(self): exp3 = [0.0, 15.33732285330093] assert_near_equal(out3, exp3, tolerance=1e-10) - # Had to switch to FD checking here because one of the underlying groups is using 'CS' + # Had to switch to FD checking here because one of the underlying groups is using 'fd' partial_data = self.prob.check_partials(out_stream=None, method='fd', form='central') assert_check_partials(partial_data, atol=1e-8, rtol=1e-8) diff --git a/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py b/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py index 7293e373af..9c54ef2fe9 100644 --- a/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py +++ b/aviary/subsystems/geometry/flops_based/wing_detailed_bwb.py @@ -173,13 +173,18 @@ def compute(self, inputs, outputs): side_tc = inputs[Aircraft.Fuselage.SIDEBODY_THICKNESS_TO_CHORD][0] root_chord = inputs[Aircraft.Wing.ROOT_CHORD][0] - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1:] = inputs[ - Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION - ] - idx = np.where(outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'] < 5.0) - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][idx] *= rate_span - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][0] = length - outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'][1] = root_chord + chord_dist = inputs[Aircraft.Wing.CHORD_PER_SEMISPAN_DISTRIBUTION] + n_stations = len(outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION']) + + bwb_chord_dist = np.zeros(n_stations, dtype=chord_dist.dtype) + bwb_chord_dist[0] = length + bwb_chord_dist[1] = root_chord + bwb_chord_dist[2:] = chord_dist[1:] + + idx = np.where(bwb_chord_dist < 5.0) + bwb_chord_dist[idx] *= rate_span + + outputs['BWB_CHORD_PER_SEMISPAN_DISTRIBUTION'] = bwb_chord_dist outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][0] = cl_tc outputs['BWB_THICKNESS_TO_CHORD_DISTRIBUTION'][1] = side_tc diff --git a/aviary/subsystems/geometry/gasp_based/test/test_override.py b/aviary/subsystems/geometry/gasp_based/test/test_override.py index afc986b0c3..dab49d6041 100644 --- a/aviary/subsystems/geometry/gasp_based/test/test_override.py +++ b/aviary/subsystems/geometry/gasp_based/test/test_override.py @@ -11,7 +11,7 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.preprocessors import preprocess_propulsion from aviary.utils.process_input_decks import create_vehicle -from aviary.utils.test_utils.default_subsystems import get_default_premission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variable_meta_data import CoreMetaData from aviary.variable_info.variables import Aircraft, Dynamic @@ -26,7 +26,7 @@ def setUp(self): engines = [build_engine_deck(aviary_inputs)] - subsystems = get_default_premission_subsystems('GASP', engines) + subsystems = get_default_subsystems('GASP', engines).values() preprocess_propulsion(aviary_inputs, engines) prob = om.Problem() diff --git a/aviary/subsystems/mass/flops_based/mass_premission.py b/aviary/subsystems/mass/flops_based/mass_premission.py index fcfcca5b4d..1cde556989 100644 --- a/aviary/subsystems/mass/flops_based/mass_premission.py +++ b/aviary/subsystems/mass/flops_based/mass_premission.py @@ -12,7 +12,8 @@ from aviary.subsystems.mass.flops_based.engine_controls import TransportEngineCtrlsMass from aviary.subsystems.mass.flops_based.engine_oil import AltEngineOilMass, TransportEngineOilMass from aviary.subsystems.mass.flops_based.fin import FinMass -from aviary.subsystems.mass.flops_based.fuel_capacity import FuelCapacityGroup + +# from aviary.subsystems.mass.flops_based.fuel_capacity import FuelCapacityGroup from aviary.subsystems.mass.flops_based.fuel_system import ( AltFuelSystemMass, TransportFuelSystemMass, @@ -99,12 +100,12 @@ def setup(self): 'avionics', TransportAvionicsMass(), promotes_inputs=['*'], promotes_outputs=['*'] ) - self.add_subsystem( - 'fuel_capacity_group', - FuelCapacityGroup(), - promotes_inputs=['*'], - promotes_outputs=['*'], - ) + # self.add_subsystem( + # 'fuel_capacity_group', + # FuelCapacityGroup(), + # promotes_inputs=['*'], + # promotes_outputs=['*'], + # ) self.add_subsystem( 'engine_mass', EngineMass(), promotes_inputs=['*'], promotes_outputs=['*'] @@ -325,5 +326,3 @@ def setup(self): self.add_subsystem( 'mass_summation', MassSummation(), promotes_inputs=['*'], promotes_outputs=['*'] ) - - self.set_input_defaults(Aircraft.Fuel.DENSITY, units='lbm/galUS') diff --git a/aviary/subsystems/mass/gasp_based/fuel.py b/aviary/subsystems/mass/gasp_based/fuel.py index 5b36d17f31..2fde5ee8df 100644 --- a/aviary/subsystems/mass/gasp_based/fuel.py +++ b/aviary/subsystems/mass/gasp_based/fuel.py @@ -226,17 +226,24 @@ def setup(self): # WFXTRA: extra amount of fuel that is required but does not fit in wings add_aviary_output(self, Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY, units='lbm') + + # NOTE this is not used in Aviary self.add_output( 'extra_fuel_volume', units='ft**3', desc='FVOLXTRA: excess required design fuel volume (including fuel margin) greater than geometric fuel volume of wings', ) # there is no FVOLXTRA in GASP + + # NOTE this is not used in Aviary self.add_output( 'max_extra_fuel_mass', units='lbm', desc='WFXTRAMX: mass of fuel that fits in extra_fuel_volume', ) # there is no WFXTRAMX in GASP + add_aviary_output(self, Aircraft.Fuel.MAX_CAPACITY_MASS, units='lbm', desc='WFAMAX') + + # NOTE this is not used in Aviary self.add_output( 'payload_mass_max_fuel', units='lbm', diff --git a/aviary/subsystems/mass/gasp_based/mass_premission.py b/aviary/subsystems/mass/gasp_based/mass_premission.py index 4b1f456e51..5efd6038b5 100644 --- a/aviary/subsystems/mass/gasp_based/mass_premission.py +++ b/aviary/subsystems/mass/gasp_based/mass_premission.py @@ -127,12 +127,12 @@ def setup(self): promotes_outputs=['*'], ) - self.add_subsystem( - 'tank_capacity', - TankCapacity(), - promotes_inputs=['*'], - promotes_outputs=['*'], - ) + # self.add_subsystem( + # 'tank_capacity', + # TankCapacity(), + # promotes_inputs=['*'], + # promotes_outputs=['*'], + # ) newton = self.nonlinear_solver = om.NewtonSolver() newton.options['atol'] = 1e-9 diff --git a/aviary/subsystems/mass/gasp_based/test/test_mass_premission.py b/aviary/subsystems/mass/gasp_based/test/test_mass_premission.py index 634ea322d8..942be0e38e 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_mass_premission.py +++ b/aviary/subsystems/mass/gasp_based/test/test_mass_premission.py @@ -109,11 +109,11 @@ def test_case1(self): Aircraft.Fuel.WING_VOLUME_DESIGN: 871.44181691, # modified from GASP value to account for updated crew mass. GASP value is 856.4910800459031 'OEM_fuel_vol': 1591.12164393, # modified from GASP value to account for updated crew mass. GASP value is 1576.1710061411081 Mission.OPERATING_MASS: 95808.52469272, # modified from GASP value to account for updated crew mass. GASP value is 96556.0 - 'volume_wingfuel_mass': 57066.3, # extra_fuel_mass calculated differently in this version, so test for payload_mass_max_fuel not included - 'max_wingfuel_mass': 57066.3, - Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY: 0, # always zero when no body tank - 'extra_fuel_volume': 0, # always zero when no body tank - 'max_extra_fuel_mass': 0, # always zero when no body tank + # 'volume_wingfuel_mass': 57066.3, # extra_fuel_mass calculated differently in this version, so test for payload_mass_max_fuel not included + # 'max_wingfuel_mass': 57066.3, + # Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY: 0, # always zero when no body tank + # 'extra_fuel_volume': 0, # always zero when no body tank + # 'max_extra_fuel_mass': 0, # always zero when no body tank Aircraft.Design.USEFUL_LOAD_MASS: 85002.98039399, } @@ -492,11 +492,11 @@ def test_case1(self): # FuelComponents assert_near_equal(prob[Aircraft.Fuel.UNUSABLE_FUEL_MASS], 619.8317956, tol) # BodyTankCalculations - assert_near_equal(prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) - assert_near_equal(prob['extra_fuel_volume'], 0, tol) - assert_near_equal(prob['max_extra_fuel_mass'], 0, tol) - assert_near_equal(prob['wingfuel_mass_min'], 33716.89535681, tol) - assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 44376.72715242, tol) + # assert_near_equal(prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) + # assert_near_equal(prob['extra_fuel_volume'], 0, tol) + # assert_near_equal(prob['max_extra_fuel_mass'], 0, tol) + # assert_near_equal(prob['wingfuel_mass_min'], 33716.89535681, tol) + # assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 44376.72715242, tol) # EmpennageMass assert_near_equal(prob[Aircraft.Design.EMPENNAGE_MASS], 4574.10130526, tol) # StructureMass @@ -582,13 +582,13 @@ def test_case1(self): self.prob[Mission.OPERATING_MASS], 95643.26913924, tol ) # modified from GASP value to account for updated crew mass. GASP value is 96253.0 # extra_fuel_mass calculated differently in this version, so payload_mass_max_fuel test not included - assert_near_equal(self.prob['volume_wingfuel_mass'], 55725.1, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 55725.1, tol) - assert_near_equal( - self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol - ) # always zero when no body tank - assert_near_equal(self.prob['extra_fuel_volume'], 0, tol) # always zero when no body tank - assert_near_equal(self.prob['max_extra_fuel_mass'], 0, tol) # always zero when no body tank + # assert_near_equal(self.prob['volume_wingfuel_mass'], 55725.1, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 55725.1, tol) + # assert_near_equal( + # self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol + # ) # always zero when no body tank + # assert_near_equal(self.prob['extra_fuel_volume'], 0, tol) # always zero when no body tank + # assert_near_equal(self.prob['max_extra_fuel_mass'], 0, tol) # always zero when no body tank partial_data = self.prob.check_partials(out_stream=None, method='cs') assert_check_partials(partial_data, atol=2e-10, rtol=1e-12) @@ -949,16 +949,16 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 95643.26913924, tol ) # modified from GASP value to account for updated crew mass. GASP value is 96253.0 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 36000, tol - ) # note: value came from running the GASP code on my own and printing it out - assert_near_equal(self.prob['volume_wingfuel_mass'], 55725.1, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 55725.1, tol) - assert_near_equal( - self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol - ) # always zero when no body tank - assert_near_equal(self.prob['extra_fuel_volume'], 0, tol) # always zero when no body tank - assert_near_equal(self.prob['max_extra_fuel_mass'], 0, tol) # always zero when no body tank + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 36000, tol + # ) # note: value came from running the GASP code on my own and printing it out + # assert_near_equal(self.prob['volume_wingfuel_mass'], 55725.1, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 55725.1, tol) + # assert_near_equal( + # self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol + # ) # always zero when no body tank + # assert_near_equal(self.prob['extra_fuel_volume'], 0, tol) # always zero when no body tank + # assert_near_equal(self.prob['max_extra_fuel_mass'], 0, tol) # always zero when no body tank partial_data = self.prob.check_partials(out_stream=None, method='cs') assert_check_partials(partial_data, atol=2e-10, rtol=1e-12) @@ -1320,16 +1320,16 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 95826.33734069, tol ) # modified from GASP value to account for updated crew mass. GASP value is 96433.0 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 36000, tol - ) # note: value came from running the GASP code on my own and printing it out - assert_near_equal(self.prob['volume_wingfuel_mass'], 55725.1, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 55725.1, tol) - assert_near_equal( - self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol - ) # always zero when no body tank - assert_near_equal(self.prob['extra_fuel_volume'], 0, tol) # always zero when no body tank - assert_near_equal(self.prob['max_extra_fuel_mass'], 0, tol) # always zero when no body tank + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 36000, tol + # ) # note: value came from running the GASP code on my own and printing it out + # assert_near_equal(self.prob['volume_wingfuel_mass'], 55725.1, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 55725.1, tol) + # assert_near_equal( + # self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol + # ) # always zero when no body tank + # assert_near_equal(self.prob['extra_fuel_volume'], 0, tol) # always zero when no body tank + # assert_near_equal(self.prob['max_extra_fuel_mass'], 0, tol) # always zero when no body tank partial_data = self.prob.check_partials(out_stream=None, method='cs') assert_check_partials(partial_data, atol=2e-10, rtol=1e-12) @@ -1683,20 +1683,20 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 94142.100596, tol ) # modified from GASP value to account for updated crew mass. GASP value is 93975 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 34594.23826045, tol - ) # note: value came from running the GASP code on my own and printing it out, #modified from GASP value to account for updated crew mass. GASP value is 34427.4 - assert_near_equal(self.prob['volume_wingfuel_mass'], 43852.1, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 43852.1, tol) - assert_near_equal( - self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 1405.76173955, tol - ) # modified from GASP value to account for updated crew mass. GASP value is 1572.6 - assert_near_equal( - self.prob['extra_fuel_volume'], 28.10273238, tol - ) # slightly different from GASP value, likely a rounding error, #modified from GASP value to account for updated crew mass. GASP value is 31.43 - assert_near_equal( - self.prob['max_extra_fuel_mass'], 1405.76173955, tol - ) # modified from GASP value to account for updated crew mass. GASP value is 1572.6 + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 34594.23826045, tol + # ) # note: value came from running the GASP code on my own and printing it out, #modified from GASP value to account for updated crew mass. GASP value is 34427.4 + # assert_near_equal(self.prob['volume_wingfuel_mass'], 43852.1, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 43852.1, tol) + # assert_near_equal( + # self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 1405.76173955, tol + # ) # modified from GASP value to account for updated crew mass. GASP value is 1572.6 + # assert_near_equal( + # self.prob['extra_fuel_volume'], 28.10273238, tol + # ) # slightly different from GASP value, likely a rounding error, #modified from GASP value to account for updated crew mass. GASP value is 31.43 + # assert_near_equal( + # self.prob['max_extra_fuel_mass'], 1405.76173955, tol + # ) # modified from GASP value to account for updated crew mass. GASP value is 1572.6 partial_data = self.prob.check_partials(out_stream=None, method='cs') assert_check_partials(partial_data, atol=3e-10, rtol=1e-12) @@ -2049,20 +2049,20 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 94593.56550555, tol ) # modified from GASP value to account for updated crew mass. GASP value is 94417 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 35045.70322352, tol - ) # note: value came from running the GASP code on my own and printing it out, #modified from GASP value to account for updated crew mass. GASP value is 34879.2 - assert_near_equal(self.prob['volume_wingfuel_mass'], 43852.1, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 43852.1, tol) - assert_near_equal( - self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 954.29683, tol - ) # modified from GASP value to account for updated crew mass. GASP value is 1120.9 - assert_near_equal( - self.prob['extra_fuel_volume'], 108.65046889, tol - ) # modified from GASP value to account for updated crew mass. GASP value is 112.3 - assert_near_equal( - self.prob['max_extra_fuel_mass'], 5434.94027945, tol - ) # modified from GASP value to account for updated crew mass. GASP value is 5618.2 + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 35045.70322352, tol + # ) # note: value came from running the GASP code on my own and printing it out, #modified from GASP value to account for updated crew mass. GASP value is 34879.2 + # assert_near_equal(self.prob['volume_wingfuel_mass'], 43852.1, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 43852.1, tol) + # assert_near_equal( + # self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 954.29683, tol + # ) # modified from GASP value to account for updated crew mass. GASP value is 1120.9 + # assert_near_equal( + # self.prob['extra_fuel_volume'], 108.65046889, tol + # ) # modified from GASP value to account for updated crew mass. GASP value is 112.3 + # assert_near_equal( + # self.prob['max_extra_fuel_mass'], 5434.94027945, tol + # ) # modified from GASP value to account for updated crew mass. GASP value is 5618.2 partial_data = self.prob.check_partials(out_stream=None, method='cs') assert_check_partials(partial_data, atol=3e-10, rtol=1e-12) @@ -2437,18 +2437,18 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 82640.88682213, tol ) # modified from GASP value to account for updated crew mass. GASP value is 82961.0 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 30800.0039, tol - ) # note: value came from running the GASP code on my own and printing it out - assert_near_equal(self.prob['volume_wingfuel_mass'], 33892.8, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 33892.8, tol) - assert_near_equal(self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) - assert_near_equal( - self.prob['extra_fuel_volume'], 24.8957623, tol - ) # note: higher tol because slightly different from GASP value, likely numerical issues, #modified from GASP value to account for updated crew mass. GASP value is 17.9 - assert_near_equal( - self.prob['max_extra_fuel_mass'], 1245.34189936, tol - ) # note: higher tol because slightly different from GASP value, likely numerical issues, #modified from GASP value to account for updated crew mass. GASP value is 897.2 + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 30800.0039, tol + # ) # note: value came from running the GASP code on my own and printing it out + # assert_near_equal(self.prob['volume_wingfuel_mass'], 33892.8, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 33892.8, tol) + # assert_near_equal(self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) + # assert_near_equal( + # self.prob['extra_fuel_volume'], 24.8957623, tol + # ) # note: higher tol because slightly different from GASP value, likely numerical issues, #modified from GASP value to account for updated crew mass. GASP value is 17.9 + # assert_near_equal( + # self.prob['max_extra_fuel_mass'], 1245.34189936, tol + # ) # note: higher tol because slightly different from GASP value, likely numerical issues, #modified from GASP value to account for updated crew mass. GASP value is 897.2 assert_near_equal(self.prob[Aircraft.Fuel.WING_VOLUME_STRUCTURAL_MAX], 677.554, tol) assert_near_equal(self.prob[Aircraft.Fuel.WING_VOLUME_GEOMETRIC_MAX], 677.554, tol) @@ -2825,18 +2825,18 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 83732.50812502, tol ) # modified from GASP value to account for updated crew mass. GASP value is 83728.0 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 30800.0, tol - ) # note:printed out from GASP code - assert_near_equal(self.prob['volume_wingfuel_mass'], 31051.6, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 31051.6, tol) - assert_near_equal(self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) - assert_near_equal( - self.prob['extra_fuel_volume'], 7.44643516, tol - ) # note: printed out from the GASP code, #modified from GASP value to account for updated crew mass. GASP value is 7.5568 - assert_near_equal( - self.prob['max_extra_fuel_mass'], 372.48739763, tol - ) # note: printed out from the GASP code, #modified from GASP value to account for updated crew mass. GASP value is 378.0062 + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 30800.0, tol + # ) # note:printed out from GASP code + # assert_near_equal(self.prob['volume_wingfuel_mass'], 31051.6, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 31051.6, tol) + # assert_near_equal(self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) + # assert_near_equal( + # self.prob['extra_fuel_volume'], 7.44643516, tol + # ) # note: printed out from the GASP code, #modified from GASP value to account for updated crew mass. GASP value is 7.5568 + # assert_near_equal( + # self.prob['max_extra_fuel_mass'], 372.48739763, tol + # ) # note: printed out from the GASP code, #modified from GASP value to account for updated crew mass. GASP value is 378.0062 partial_data = self.prob.check_partials(out_stream=None, method='cs') assert_check_partials(partial_data, atol=3e-9, rtol=6e-11) @@ -3230,14 +3230,14 @@ def test_case1(self): assert_near_equal( self.prob[Mission.OPERATING_MASS], 102648.03610637, tol ) # modified from GASP value to account for updated crew mass. GASP value is 102392.0 - assert_near_equal( - self.prob['payload_mass_max_fuel'], 30800.0, tol - ) # (printed out from GASP code) - assert_near_equal(self.prob['volume_wingfuel_mass'], 35042.1, tol) - assert_near_equal(self.prob['max_wingfuel_mass'], 35042.1, tol) - assert_near_equal(self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) - assert_near_equal(self.prob['extra_fuel_volume'], 0.69314718, tol) - assert_near_equal(self.prob['max_extra_fuel_mass'], 34.67277748, tol) + # assert_near_equal( + # self.prob['payload_mass_max_fuel'], 30800.0, tol + # ) # (printed out from GASP code) + # assert_near_equal(self.prob['volume_wingfuel_mass'], 35042.1, tol) + # assert_near_equal(self.prob['max_wingfuel_mass'], 35042.1, tol) + # assert_near_equal(self.prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 0, tol) + # assert_near_equal(self.prob['extra_fuel_volume'], 0.69314718, tol) + # assert_near_equal(self.prob['max_extra_fuel_mass'], 34.67277748, tol) assert_near_equal(self.prob[Aircraft.Electrical.HYBRID_CABLE_LENGTH], 65.6, tol) assert_near_equal( @@ -3661,20 +3661,20 @@ def test_case1(self): # FuelAndOEMOutputs assert_near_equal(prob['OEM_wingfuel_mass'], 69012.87713501, tol) assert_near_equal(prob['OEM_fuel_vol'], 1379.6437633, tol) - assert_near_equal(prob['payload_mass_max_fuel'], 28795.99162868, tol) - assert_near_equal(prob['volume_wingfuel_mass'], 30308.8688, tol) + # assert_near_equal(prob['payload_mass_max_fuel'], 28795.99162868, tol) + # assert_near_equal(prob['volume_wingfuel_mass'], 30308.8688, tol) assert_near_equal(prob['max_wingfuel_mass'], 30308.86876369, tol) assert_near_equal(prob[Aircraft.Fuel.WING_VOLUME_DESIGN], 775.43831801, tol) assert_near_equal(prob[Mission.OPERATING_MASS], 80987.12286499, tol) # TankCapacity / WingFuelMin - assert_near_equal(prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 4954.00837132, tol) - - total_cap = prob[Aircraft.Fuel.MAX_CAPACITY_MASS] - unusable = prob[Aircraft.Fuel.UNUSABLE_FUEL_MASS] - assert_near_equal(total_cap - unusable, 40216.88550633, tol) - assert_near_equal(prob['extra_fuel_volume'], 169.53050054, tol) - assert_near_equal(prob['max_extra_fuel_mass'], 8480.29608482, tol) - assert_near_equal(prob['wingfuel_mass_min'], 11782.58105019, tol) + # assert_near_equal(prob[Aircraft.Fuel.AUXILIARY_FUEL_MASS_CAPACITY], 4954.00837132, tol) + + # total_cap = prob[Aircraft.Fuel.MAX_CAPACITY_MASS] + # unusable = prob[Aircraft.Fuel.UNUSABLE_FUEL_MASS] + # assert_near_equal(total_cap - unusable, 40216.88550633, tol) + # assert_near_equal(prob['extra_fuel_volume'], 169.53050054, tol) + # assert_near_equal(prob['max_extra_fuel_mass'], 8480.29608482, tol) + # assert_near_equal(prob['wingfuel_mass_min'], 11782.58105019, tol) # this is the large single aisle 1 V3 test case diff --git a/aviary/subsystems/propulsion/engine_deck.py b/aviary/subsystems/propulsion/engine_deck.py index 777e3bcb06..1e0d805e83 100644 --- a/aviary/subsystems/propulsion/engine_deck.py +++ b/aviary/subsystems/propulsion/engine_deck.py @@ -29,7 +29,7 @@ import openmdao.api as om from openmdao.utils.units import convert_units -from aviary.utils.utils import round_it +from aviary.subsystems.atmosphere.utils.get_atmosphere_data import get_atmosphere_data from aviary.subsystems.propulsion.engine_model import EngineModel from aviary.subsystems.propulsion.engine_scaling import EngineScaling from aviary.subsystems.propulsion.engine_sizing import SizeEngine @@ -42,11 +42,10 @@ ) from aviary.utils.aviary_values import AviaryValues, NamedValues from aviary.utils.csv_data_file import read_data_file +from aviary.utils.utils import round_it from aviary.variable_info.enums import Verbosity from aviary.variable_info.variable_meta_data import CoreMetaData -from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings -from aviary.subsystems.atmosphere.utils.get_atmosphere_data import get_atmosphere_data -from aviary.variable_info.enums import AtmosphereModel +from aviary.variable_info.variables import Aircraft, Dynamic, Settings MACH = EngineModelVariables.MACH ALTITUDE = EngineModelVariables.ALTITUDE @@ -148,10 +147,10 @@ class EngineDeck(EngineModel): def __init__( self, name='engine_deck', + meta_data: dict = CoreMetaData, options: AviaryValues = None, data: NamedValues = None, required_variables: set = default_required_variables, - meta_data: dict = CoreMetaData, ): if data is not None: self.read_from_file = False @@ -160,7 +159,7 @@ def __init__( # TODO update default name to be based on filename # also calls _preprocess_inputs() as part of EngineModel __init__ - super().__init__(name, options, meta_data=meta_data) + super().__init__(name, options=options, meta_data=meta_data) # custom error messages depending on data type if self.read_from_file: diff --git a/aviary/subsystems/propulsion/engine_model.py b/aviary/subsystems/propulsion/engine_model.py index b8848b3d77..6f412208da 100644 --- a/aviary/subsystems/propulsion/engine_model.py +++ b/aviary/subsystems/propulsion/engine_model.py @@ -39,13 +39,14 @@ class EngineModel(SubsystemBuilder): """ _default_name = 'engine_model' + # Flag that sets if this engine computes maximum values (e.g. max thrust, shaft power) for a # given flight condition. If False, during mission Aviary will create a duplicate copy of the # engine that is given max throttle and hybrid throttle (1.0) to compute max values. compute_max_values = False def __init__( - self, name: str = None, options: AviaryValues = None, meta_data: dict = None, **kwargs + self, name: str = None, meta_data: dict = None, options: AviaryValues = None, **kwargs ): super().__init__(name, meta_data=meta_data) if options is not None: diff --git a/aviary/subsystems/propulsion/propulsion_builder.py b/aviary/subsystems/propulsion/propulsion_builder.py index a7c61ceb8d..85316e99e2 100644 --- a/aviary/subsystems/propulsion/propulsion_builder.py +++ b/aviary/subsystems/propulsion/propulsion_builder.py @@ -79,6 +79,9 @@ class CorePropulsionBuilder(PropulsionBuilder): def __init__(self, name=None, meta_data=None, engine_models=None, **kwargs): super().__init__(name=name, meta_data=meta_data) + if engine_models is None: + engine_models = [] + if not isinstance(engine_models, (list, np.ndarray)): engine_models = [engine_models] diff --git a/aviary/subsystems/propulsion/test/test_engine_deck.py b/aviary/subsystems/propulsion/test/test_engine_deck.py index 0fb346e88e..05e2f82d8c 100644 --- a/aviary/subsystems/propulsion/test/test_engine_deck.py +++ b/aviary/subsystems/propulsion/test/test_engine_deck.py @@ -119,7 +119,7 @@ def test_load_from_memory(self): data_input.set_val('thrust', expected_thrust, 'lbf') data_input.set_val('fuel_flow', expected_fuel_flow_rate, 'lbm/h') - model = EngineDeck('engine', aviary_values, data_input) + model = EngineDeck('engine', options=aviary_values, data=data_input) mach_number = model.data[keys.MACH] altitude = model.data[keys.ALTITUDE] diff --git a/aviary/subsystems/propulsion/turboprop_model.py b/aviary/subsystems/propulsion/turboprop_model.py index 81fab4c2e1..bec5a6e5ea 100644 --- a/aviary/subsystems/propulsion/turboprop_model.py +++ b/aviary/subsystems/propulsion/turboprop_model.py @@ -11,6 +11,7 @@ from aviary.subsystems.subsystem_builder import SubsystemBuilder from aviary.utils.aviary_values import AviaryValues from aviary.variable_info.enums import Verbosity +from aviary.variable_info.variable_meta_data import CoreMetaData from aviary.variable_info.variables import Aircraft, Dynamic, Settings @@ -60,13 +61,14 @@ class TurbopropModel(EngineModel): def __init__( self, name='turboprop_model', + meta_data=CoreMetaData, options: AviaryValues = None, shaft_power_model: SubsystemBuilder = None, propeller_model: SubsystemBuilder = None, gearbox_model: SubsystemBuilder = None, ): # also calls _preprocess_inputs() as part of EngineModel __init__ - super().__init__(name, options) + super().__init__(name, meta_data=meta_data, options=options) self.shaft_power_model = shaft_power_model self.propeller_model = propeller_model diff --git a/aviary/subsystems/subsystem_builder.py b/aviary/subsystems/subsystem_builder.py index 7130a3411d..e9ca42e580 100644 --- a/aviary/subsystems/subsystem_builder.py +++ b/aviary/subsystems/subsystem_builder.py @@ -24,7 +24,7 @@ class SubsystemBuilder(ABC): Dictionary containing the variable metadata used by this Aviary problem """ - __slots__ = ('name', 'meta_data') + __slots__ = ('meta_data', 'name') # derived type customization point _default_name = 'default_subsystem_name' diff --git a/aviary/subsystems/test/test_flops_based_premission.py b/aviary/subsystems/test/test_flops_based_premission.py index dad6e8e4b0..d2f3a86a80 100644 --- a/aviary/subsystems/test/test_flops_based_premission.py +++ b/aviary/subsystems/test/test_flops_based_premission.py @@ -1,5 +1,6 @@ -from copy import deepcopy import unittest +import warnings +from copy import deepcopy import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal @@ -12,10 +13,7 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.functions import set_aviary_initial_values from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import ( - get_default_premission_subsystems, - get_geom_and_mass_subsystems, -) +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_tests import ( Version, flops_validation_test, @@ -47,7 +45,7 @@ def test_case(self, case_name): engines = [build_engine_deck(flops_inputs)] preprocess_options(flops_inputs, engine_models=engines) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines) + default_premission_subsystems = get_default_subsystems('FLOPS', engines).values() prob = self.prob @@ -63,13 +61,19 @@ def test_case(self, case_name): ) setup_model_options(prob, flops_inputs) + prob.model.set_input_defaults(Aircraft.Fuel.DENSITY, units='lbm/galUS') # prob.model.set_input_defaults( # Aircraft.Engine.SCALE_FACTOR, # flops_inputs.get_val( # Aircraft.Engine.SCALE_FACTOR)) - prob.setup(check=False, force_alloc_complex=True) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', om.OpenMDAOWarning) + warnings.simplefilter('ignore', om.PromotionWarning) + + prob.setup(check=False, force_alloc_complex=True) + prob.set_solver_print(2) # Initial guess for gross weight. @@ -123,7 +127,7 @@ def test_diff_configuration_mass(self): engines = [build_engine_deck(flops_inputs)] preprocess_options(flops_inputs, engine_models=engines) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines) + default_premission_subsystems = get_default_subsystems('FLOPS', engines).values() prob.model.add_subsystem( 'pre_mission', @@ -137,8 +141,13 @@ def test_diff_configuration_mass(self): ) setup_model_options(prob, flops_inputs) + prob.model.set_input_defaults(Aircraft.Fuel.DENSITY, units='lbm/galUS') - prob.setup(check=False) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', om.OpenMDAOWarning) + warnings.simplefilter('ignore', om.PromotionWarning) + + prob.setup(check=False) set_aviary_initial_values(prob, flops_inputs) @@ -167,22 +176,29 @@ def test_mass_aero_only(self): preprocess_options(flops_inputs, engine_models=engines) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines=engines)[ - 1: + default_premission_subsystems = get_default_subsystems('FLOPS', engines=engines) + premission_subsystems = [ + default_premission_subsystems[k] for k in ['geometry', 'mass', 'aerodynamics'] ] prob.model.add_subsystem( 'mass_and_aero_premission', CorePreMission( aviary_options=flops_inputs, - subsystems=default_premission_subsystems, + subsystems=premission_subsystems, subsystem_options={}, ), promotes=['*'], ) setup_model_options(prob, flops_inputs) - prob.setup() + prob.model.set_input_defaults(Aircraft.Fuel.DENSITY, units='lbm/galUS') + + with warnings.catch_warnings(): + warnings.simplefilter('ignore', om.OpenMDAOWarning) + warnings.simplefilter('ignore', om.PromotionWarning) + + prob.setup(check=False) set_aviary_initial_values(prob, flops_inputs) prob.set_val( @@ -248,7 +264,7 @@ def test_case_all_subsystems( engines = [build_engine_deck(flops_inputs)] preprocess_options(flops_inputs, engine_models=engines) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines) + default_premission_subsystems = get_default_subsystems('FLOPS', engines).values() prob = self.prob @@ -264,13 +280,18 @@ def test_case_all_subsystems( ) setup_model_options(prob, flops_inputs) + prob.model.set_input_defaults(Aircraft.Fuel.DENSITY, units='lbm/galUS') # prob.model.set_input_defaults( # Aircraft.Engine.SCALE_FACTOR, # flops_inputs.get_val( # Aircraft.Engine.SCALE_FACTOR)) - prob.setup(check=False, force_alloc_complex=True) + with warnings.catch_warnings(): + warnings.simplefilter('ignore', om.OpenMDAOWarning) + warnings.simplefilter('ignore', om.PromotionWarning) + prob.setup(check=False, force_alloc_complex=True) + prob.set_solver_print(2) # Initial guess for gross weight. @@ -452,7 +473,7 @@ def test_case_geom(self): engines = [build_engine_deck(flops_inputs)] preprocess_options(flops_inputs, engine_models=engines) - default_premission_subsystems = get_geom_and_mass_subsystems('FLOPS')[0:1] + geom_subsystem = [get_default_subsystems('FLOPS')['geometry']] prob = self.prob @@ -460,7 +481,7 @@ def test_case_geom(self): 'pre_mission', CorePreMission( aviary_options=flops_inputs, - subsystems=default_premission_subsystems, + subsystems=geom_subsystem, subsystem_options={}, ), promotes_inputs=['*'], @@ -586,9 +607,7 @@ def test_case_root_chord_idx_2(self): @use_tempdirs class BWBPreMissionGroupCSVTest1(unittest.TestCase): - """ - testing using bwb_simple_FLOPS.csv - """ + """testing using bwb_simple_FLOPS.csv.""" def setUp(self): prob = self.prob = AviaryProblem() @@ -598,13 +617,11 @@ def setUp(self): prob.check_and_preprocess_inputs() def test_case_geom(self): - """ - premission: geometry - """ + """premission: geometry.""" prob = self.prob preprocess_options(self.flops_inputs) - geom_subsystem = get_geom_and_mass_subsystems('FLOPS')[0:1] + geom_subsystem = [get_default_subsystems('FLOPS')['geometry']] prob.model.add_subsystem( 'pre_mission', @@ -619,6 +636,7 @@ def test_case_geom(self): setup_model_options(prob, self.flops_inputs) prob.setup(check=False) + prob.set_solver_print(2) set_aviary_initial_values(prob, self.flops_inputs) prob.run_model() @@ -670,13 +688,11 @@ def test_case_geom(self): assert_near_equal(prob[Aircraft.Design.TOTAL_WETTED_AREA], 35311.53118076, tol) def test_case_geom_mass(self): - """ - premission: geometry + mass - """ + """premission: geometry + mass.""" prob = self.prob preprocess_options(self.flops_inputs) - geom_mass_subsystems = get_geom_and_mass_subsystems('FLOPS') + geom_mass_subsystems = [get_default_subsystems('FLOPS')[k] for k in ['geometry', 'mass']] prob.model.add_subsystem( 'pre_mission', @@ -690,10 +706,17 @@ def test_case_geom_mass(self): ) setup_model_options(prob, self.flops_inputs) - prob.setup(check=False) + + with warnings.catch_warnings(): + warnings.simplefilter('ignore', om.OpenMDAOWarning) + warnings.simplefilter('ignore', om.PromotionWarning) + + prob.setup(check=False) + set_aviary_initial_values(prob, self.flops_inputs) prob.set_val(Aircraft.Propulsion.TOTAL_SCALED_SLS_THRUST, val=70000.0 * 3, units='lbf') + prob.set_val(Aircraft.Fuel.MAX_CAPACITY_MASS, val=2385712.4988316689, units='lbm') prob.run_model() @@ -712,8 +735,8 @@ def test_case_geom_mass(self): # TransportAvionicsMass assert_near_equal(prob[Aircraft.Avionics.MASS], 2896.223816950469, tol) # FuelCapacityGroup - assert_near_equal(prob[Aircraft.Fuel.WING_FUEL_MASS_CAPACITY], 2385712.4988316689, tol) - assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 2385712.4988316689, tol) + # assert_near_equal(prob[Aircraft.Fuel.WING_FUEL_MASS_CAPACITY], 2385712.4988316689, tol) + # assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 2385712.4988316689, tol) # EngineMass assert_near_equal(prob[Aircraft.Engine.MASS], 17825.63336233, tol) assert_near_equal(prob[Aircraft.Engine.ADDITIONAL_MASS], 0.0, tol) @@ -796,14 +819,12 @@ def test_case_geom_mass(self): assert_near_equal(prob[Mission.ZERO_FUEL_MASS], 556426.76964429, tol) def test_case_all_subsystems(self): - """ - premission: propulsion + geometry + aerodynamics + mass - """ + """premission: propulsion + geometry + aerodynamics + mass + energy + performance.""" prob = self.prob engines = [build_engine_deck(self.flops_inputs)] preprocess_options(self.flops_inputs) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines=engines) + default_premission_subsystems = get_default_subsystems('FLOPS', engines=engines).values() prob.model.add_subsystem( 'pre_mission', @@ -832,9 +853,7 @@ def test_case_all_subsystems(self): @use_tempdirs class BWBPreMissionGroupCSVTest2(unittest.TestCase): - """ - testing using bwb_detailed_FLOPS.csv - """ + """testing using bwb_detailed_FLOPS.csv.""" def setUp(self): prob = self.prob = AviaryProblem() @@ -844,13 +863,11 @@ def setUp(self): prob.check_and_preprocess_inputs() def test_case_geom(self): - """ - premission: geometry - """ + """premission: geometry.""" prob = self.prob preprocess_options(self.flops_inputs) - geom_subsystem = get_geom_and_mass_subsystems('FLOPS')[0:1] + geom_subsystem = [get_default_subsystems('FLOPS')['geometry']] prob.model.add_subsystem( 'pre_mission', @@ -916,13 +933,11 @@ def test_case_geom(self): assert_near_equal(prob[Aircraft.Design.TOTAL_WETTED_AREA], 26208.46595187, tol) def test_case_geom_mass(self): - """ - premission: geometry + mass - """ + """premission: geometry + mass.""" prob = self.prob preprocess_options(self.flops_inputs) - geom_mass_subsystems = get_geom_and_mass_subsystems('FLOPS') + geom_mass_subsystems = [get_default_subsystems('FLOPS')[k] for k in ['geometry', 'mass']] prob.model.add_subsystem( 'pre_mission', @@ -940,6 +955,7 @@ def test_case_geom_mass(self): set_aviary_initial_values(prob, self.flops_inputs) prob.set_val(Aircraft.Propulsion.TOTAL_SCALED_SLS_THRUST, val=70000.0 * 3, units='lbf') + prob.set_val(Aircraft.Fuel.MAX_CAPACITY_MASS, val=1197720.2419621395, units='lbm') prob.run_model() @@ -958,8 +974,8 @@ def test_case_geom_mass(self): # TransportAvionicsMass assert_near_equal(prob[Aircraft.Avionics.MASS], 2778.5110590964073, tol) # FuelCapacityGroup - assert_near_equal(prob[Aircraft.Fuel.WING_FUEL_MASS_CAPACITY], 1197720.2419621395, tol) - assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 1197720.2419621395, tol) + # assert_near_equal(prob[Aircraft.Fuel.WING_FUEL_MASS_CAPACITY], 1197720.2419621395, tol) + # assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 1197720.2419621395, tol) # EngineMass assert_near_equal(prob[Aircraft.Engine.MASS], 17825.63336233, tol) assert_near_equal(prob[Aircraft.Engine.ADDITIONAL_MASS], 0.0, tol) @@ -1044,14 +1060,12 @@ def test_case_geom_mass(self): assert_near_equal(prob[Mission.ZERO_FUEL_MASS], 509364.22633412, tol) def test_case_all_subsystems(self): - """ - premission: propulsion + geometry + aerodynamics + mass - """ + """premission: propulsion + geometry + aerodynamics + mass + energy + performance.""" prob = self.prob engines = [build_engine_deck(self.flops_inputs)] preprocess_options(self.flops_inputs) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines=engines) + default_premission_subsystems = get_default_subsystems('FLOPS', engines=engines).values() prob.model.add_subsystem( 'pre_mission', @@ -1080,9 +1094,7 @@ def test_case_all_subsystems(self): @use_tempdirs class BWB300PreMissionGroupCSVTest(unittest.TestCase): - """ - testing using bwb_detailed_FLOPS.csv - """ + """testing using bwb_detailed_FLOPS.csv.""" def setUp(self): prob = self.prob = AviaryProblem() @@ -1092,13 +1104,11 @@ def setUp(self): prob.check_and_preprocess_inputs() def test_case_geom(self): - """ - premission: geometry - """ + """premission: geometry.""" prob = self.prob preprocess_options(self.flops_inputs) - geom_subsystem = get_geom_and_mass_subsystems('FLOPS')[0:1] + geom_subsystem = [get_default_subsystems('FLOPS')['geometry']] prob.model.add_subsystem( 'pre_mission', @@ -1165,13 +1175,11 @@ def test_case_geom(self): assert_near_equal(prob[Aircraft.Design.TOTAL_WETTED_AREA], 19637.79833526, tol) def test_case_geom_mass(self): - """ - premission: geometry + mass - """ + """premission: geometry + mass.""" prob = self.prob preprocess_options(self.flops_inputs) - geom_mass_subsystems = get_geom_and_mass_subsystems('FLOPS') + geom_mass_subsystems = [get_default_subsystems('FLOPS')[k] for k in ['geometry', 'mass']] prob.model.add_subsystem( 'pre_mission', @@ -1189,6 +1197,7 @@ def test_case_geom_mass(self): set_aviary_initial_values(prob, self.flops_inputs) prob.set_val(Aircraft.Propulsion.TOTAL_SCALED_SLS_THRUST, val=87500.0 * 2, units='lbf') + prob.set_val(Aircraft.Fuel.MAX_CAPACITY_MASS, val=787493.65267018, units='lbm') prob.run_model() @@ -1208,7 +1217,7 @@ def test_case_geom_mass(self): # TransportAvionicsMass assert_near_equal(prob[Aircraft.Avionics.MASS], 2280.13561342, tol) # FuelCapacityGroup - assert_near_equal(prob[Aircraft.Fuel.WING_FUEL_MASS_CAPACITY], 787493.65267018, tol) + # assert_near_equal(prob[Aircraft.Fuel.WING_FUEL_MASS_CAPACITY], 787493.65267018, tol) assert_near_equal(prob[Aircraft.Fuel.MAX_CAPACITY_MASS], 787493.65267018, tol) # EngineMass assert_near_equal(prob[Aircraft.Engine.MASS], 44541.857940875525 / 2, tol) @@ -1292,14 +1301,12 @@ def test_case_geom_mass(self): assert_near_equal(prob[Aircraft.Fins.MASS], 2822.14154503, tol) def test_case_all_subsystems(self): - """ - premission: propulsion + geometry + aerodynamics + mass - """ + """premission: propulsion + geometry + aerodynamics + mass + energy + performance.""" prob = self.prob engines = [build_engine_deck(self.flops_inputs)] preprocess_options(self.flops_inputs) - default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines=engines) + default_premission_subsystems = get_default_subsystems('FLOPS', engines=engines).values() prob.model.add_subsystem( 'pre_mission', @@ -1327,6 +1334,7 @@ def test_case_all_subsystems(self): if __name__ == '__main__': unittest.main() - # z = BWBPreMissionGroupTest() - # z.setUp() - # z.test_case_all_subsystems() + # test = BWB300PreMissionGroupCSVTest() + # test.setUp() + # test.test_case_all_subsystems() + # test.test_case_AdvancedSingleAisle() diff --git a/aviary/subsystems/test/test_gasp_based_premission.py b/aviary/subsystems/test/test_gasp_based_premission.py index 680e269446..8e5e3a09be 100644 --- a/aviary/subsystems/test/test_gasp_based_premission.py +++ b/aviary/subsystems/test/test_gasp_based_premission.py @@ -8,10 +8,7 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.functions import set_aviary_initial_values from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import ( - get_default_premission_subsystems, - get_geom_and_mass_subsystems, -) +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variables import Aircraft, Mission @@ -32,7 +29,9 @@ def test_case1(self): """premission: geometry + mass.""" prob = self.prob preprocess_options(self.gasp_inputs) - geom_and_mass_subsystems = get_geom_and_mass_subsystems('GASP') + geom_and_mass_subsystems = [ + get_default_subsystems('GASP')[k] for k in ['geometry', 'mass', 'energy'] + ] prob.model.add_subsystem( 'pre_mission', @@ -121,7 +120,7 @@ def test_case2(self): prob = self.prob engines = [build_engine_deck(self.gasp_inputs)] preprocess_options(self.gasp_inputs, engine_models=engines) - default_premission_subsystems = get_default_premission_subsystems('GASP', engines) + default_premission_subsystems = get_default_subsystems('GASP', engines).values() prob.model.add_subsystem( 'pre_mission', @@ -262,7 +261,7 @@ def test_case1(self): engines = [build_engine_deck(self.gasp_inputs)] preprocess_options(self.gasp_inputs, engine_models=engines) - default_premission_subsystems = get_default_premission_subsystems('GASP', engines=engines) + default_premission_subsystems = get_default_subsystems('GASP', engines=engines).values() prob.model.add_subsystem( 'pre_mission', @@ -353,7 +352,7 @@ def test_case_geom(self): prob = self.prob preprocess_options(self.gasp_inputs) - geom_subsystem = get_geom_and_mass_subsystems('GASP')[0:1] + geom_subsystem = [get_default_subsystems('GASP')['geometry']] prob.model.add_subsystem( 'pre_mission', @@ -412,7 +411,9 @@ def test_case_geom_mass(self): prob = self.prob preprocess_options(self.gasp_inputs) - geom_and_mass_subsystems = get_geom_and_mass_subsystems('GASP') + geom_and_mass_subsystems = [ + get_default_subsystems('GASP')[k] for k in ['geometry', 'mass', 'energy'] + ] prob.model.add_subsystem( 'pre_mission', diff --git a/aviary/subsystems/test/test_premission.py b/aviary/subsystems/test/test_premission.py index 8911e1f411..d04b1c4c51 100644 --- a/aviary/subsystems/test/test_premission.py +++ b/aviary/subsystems/test/test_premission.py @@ -4,11 +4,8 @@ from openmdao.utils.assert_utils import assert_check_partials, assert_near_equal from openmdao.utils.testing_utils import use_tempdirs -from aviary.validation_cases.validation_data.test_data.V3_bug_fixed_IO import ( - V3_bug_fixed_non_metadata, - V3_bug_fixed_options, -) from aviary.subsystems.aerodynamics.aerodynamics_builder import CoreAerodynamicsBuilder +from aviary.subsystems.energy.energy_builder import CoreEnergyBuilder from aviary.subsystems.geometry.geometry_builder import CoreGeometryBuilder from aviary.subsystems.mass.mass_builder import CoreMassBuilder from aviary.subsystems.premission import CorePreMission @@ -16,11 +13,15 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.functions import set_aviary_initial_values from aviary.utils.preprocessors import preprocess_options +from aviary.validation_cases.validation_data.test_data.V3_bug_fixed_IO import ( + V3_bug_fixed_non_metadata, + V3_bug_fixed_options, +) from aviary.validation_cases.validation_tests import get_flops_case_names, get_flops_inputs from aviary.variable_info.enums import LegacyCode from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variable_meta_data import CoreMetaData -from aviary.variable_info.variables import Aircraft, Mission +from aviary.variable_info.variables import Aircraft, Mission, Settings FLOPS = LegacyCode.FLOPS GASP = LegacyCode.GASP @@ -56,10 +57,15 @@ def setUp(self): self.prob = om.Problem() + # TODO setup_options causes problems (lots of manual manipulation of variables needed + # aferwords and fragile to changes in variables or subsystems - we should transition away from it) input_options = setup_options(GASP_input, FLOPS_input) # Note: if a parameter is not a GASP option but a FLOPS option, it is loaded in using default # value in meta data (see V3_bug_fixed_options). So, its value in FLOPS option is never loaded in. + # manually set values + input_options.set_val(Settings.MASS_METHOD, 'GASP') + # delete the options that would override values input_options.delete(Aircraft.Wing.AREA) input_options.delete(Aircraft.Fuselage.LENGTH) @@ -83,8 +89,9 @@ def setUp(self): code_origin=(FLOPS, GASP), code_origin_to_prioritize=GASP, ) + energy = CoreEnergyBuilder('energy', CoreMetaData) - subsystems = [prop, geom, mass, aero] + subsystems = [prop, geom, mass, energy, aero] self.prob.model.add_subsystem( 'pre_mission', @@ -241,8 +248,9 @@ def test_code_origin_override(self): code_origin=(FLOPS, GASP), code_origin_to_prioritize=FLOPS, ) + energy = CoreEnergyBuilder('energy', CoreMetaData) - subsystems = [prop, geom, mass, aero] + subsystems = [prop, geom, mass, energy, aero] model.add_subsystem( 'pre_mission', @@ -296,4 +304,7 @@ def test_code_origin_override(self): if __name__ == '__main__': - unittest.main() + # unittest.main() + test = PreMissionTestCase() + test.setUp() + test.test_GASP_mass_FLOPS_everything_else() diff --git a/aviary/utils/fortran_to_aviary.py b/aviary/utils/fortran_to_aviary.py index 04d1ae9e62..5541ca2abd 100644 --- a/aviary/utils/fortran_to_aviary.py +++ b/aviary/utils/fortran_to_aviary.py @@ -728,8 +728,8 @@ def update_gasp_options(vehicle_data, verbosity=Verbosity.BRIEF): if reserve_fuel_mass_additional <= 0: # This is a percentage of mission fuel input_values.set_val( - Mission.RESERVE_FUEL_MARGIN, [-reserve_fuel_mass_additional * 100], units='unitless' - ) # flip the value and multipy by 100 because it is a percentage + Mission.RESERVE_FUEL_MARGIN, [-reserve_fuel_mass_additional], units='unitless' + ) # flip the value input_values.set_val( Mission.RESERVE_FUEL_MASS_ADDITIONAL, [0], units='lbm' ) # then clear out the unused value diff --git a/aviary/utils/preprocessors.py b/aviary/utils/preprocessors.py index 9d86203a11..bcce6aad83 100644 --- a/aviary/utils/preprocessors.py +++ b/aviary/utils/preprocessors.py @@ -47,6 +47,8 @@ def preprocess_options( verbosity = meta_data[Settings.VERBOSITY]['default_value'] aviary_options.set_val(Settings.VERBOSITY, verbosity) + preprocess_deprecated(aviary_options) + preprocess_crewpayload(aviary_options, meta_data, verbosity) preprocess_fuel_capacities(aviary_options, verbosity) @@ -131,6 +133,18 @@ def preprocess_options( ) +def preprocess_deprecated(aviary_options: AviaryValues): + if Mission.RESERVE_FUEL_MARGIN in aviary_options: + margin = aviary_options.get_val(Mission.RESERVE_FUEL_MARGIN) + if margin > 1: + raise DeprecationWarning( + 'For consistency with all other Aviary variables that define percentages/fractions, ' + 'Mission.RESERVE_FUEL_MARGIN was changed to directly define the multiplicative ' + f'factor (and not percentage points). Currently reserve margin is defined as ' + f'{margin}, or {margin * 100} percentage points of main mission fuel.' + ) + + def preprocess_fuselage_layout(aviary_options: AviaryValues, verbosity=None): """ Set option values that are needed for fuselage layout computationbut are not direct inputs. diff --git a/aviary/utils/test/data/converter_test_BWB_GASP.csv b/aviary/utils/test/data/converter_test_BWB_GASP.csv index 1caa7b1eed..f1a07142f2 100644 --- a/aviary/utils/test/data/converter_test_BWB_GASP.csv +++ b/aviary/utils/test/data/converter_test_BWB_GASP.csv @@ -173,7 +173,7 @@ mission:landing:maximum_flare_load_factor,1.15,unitless mission:landing:maximum_sink_rate,900.0,ft/min mission:landing:obstacle_height,50.0,ft mission:landing:touchdown_sink_rate,5.0,ft/s -mission:reserve_fuel_margin,10.0,unitless +mission:reserve_fuel_margin,0.1,unitless mission:reserve_fuel_mass_additional,0,lbm mission:takeoff:decision_speed_increment,10.0,kn mission:takeoff:rolling_friction_coefficient,0.02,unitless diff --git a/aviary/utils/test/data/converter_test_configuration_GASP.csv b/aviary/utils/test/data/converter_test_configuration_GASP.csv index c67f0f3af1..90507573bd 100644 --- a/aviary/utils/test/data/converter_test_configuration_GASP.csv +++ b/aviary/utils/test/data/converter_test_configuration_GASP.csv @@ -160,7 +160,7 @@ mission:landing:maximum_flare_load_factor,1.15,unitless mission:landing:maximum_sink_rate,900.0,ft/min mission:landing:obstacle_height,50.0,ft mission:landing:touchdown_sink_rate,5.0,ft/s -mission:reserve_fuel_margin,15.0,unitless +mission:reserve_fuel_margin,0.15,unitless mission:reserve_fuel_mass_additional,0,lbm mission:takeoff:decision_speed_increment,10.0,kn mission:takeoff:rolling_friction_coefficient,0.02,unitless diff --git a/aviary/utils/test/data/converter_test_small_single_aisle_GASP.csv b/aviary/utils/test/data/converter_test_small_single_aisle_GASP.csv index 246e565471..aedd257553 100644 --- a/aviary/utils/test/data/converter_test_small_single_aisle_GASP.csv +++ b/aviary/utils/test/data/converter_test_small_single_aisle_GASP.csv @@ -162,7 +162,7 @@ mission:landing:maximum_flare_load_factor,1.15,unitless mission:landing:maximum_sink_rate,900.0,ft/min mission:landing:obstacle_height,50.0,ft mission:landing:touchdown_sink_rate,5.0,ft/s -mission:reserve_fuel_margin,12.5,unitless +mission:reserve_fuel_margin,0.125,unitless mission:reserve_fuel_mass_additional,0,lbm mission:takeoff:decision_speed_increment,10.0,kn mission:takeoff:rolling_friction_coefficient,0.02,unitless diff --git a/aviary/utils/test_utils/default_subsystems.py b/aviary/utils/test_utils/default_subsystems.py index 81906b9a40..f0133fccb0 100644 --- a/aviary/utils/test_utils/default_subsystems.py +++ b/aviary/utils/test_utils/default_subsystems.py @@ -1,15 +1,16 @@ from aviary.subsystems.aerodynamics.aerodynamics_builder import CoreAerodynamicsBuilder +from aviary.subsystems.energy.energy_builder import CoreEnergyBuilder from aviary.subsystems.geometry.geometry_builder import CoreGeometryBuilder from aviary.subsystems.mass.mass_builder import CoreMassBuilder +from aviary.subsystems.performance.performance_builder import CorePerformanceBuilder from aviary.subsystems.propulsion.propulsion_builder import CorePropulsionBuilder from aviary.variable_info.enums import LegacyCode from aviary.variable_info.variable_meta_data import CoreMetaData -def get_default_premission_subsystems(legacy_code, engines=None): +def get_default_subsystems(legacy_code, engines=None): """ - Get default premission subsystems propulsion, geometry, aerodynamics, and mass - in this order. + Get default premission subsystems. Arguments: ---------- @@ -23,39 +24,14 @@ def get_default_premission_subsystems(legacy_code, engines=None): mass = CoreMassBuilder('mass', CoreMetaData, legacy_code) aero = CoreAerodynamicsBuilder('aerodynamics', CoreMetaData, legacy_code) geom = CoreGeometryBuilder('geometry', CoreMetaData, legacy_code) - - return [prop, geom, aero, mass] - - -def get_default_mission_subsystems(legacy_code, engines=None): - """ - Get default mission subsystems aerodynamics and propulsion in this order. - - Arguments: - ---------- - legacy_code : str, LegacyCode - either FLOPS or GASP LegacyCode Enums, or their strings equivalents ('FLOPS', 'GASP') - engine : - List of EngineDecks - """ - legacy_code = LegacyCode(legacy_code) - prop = CorePropulsionBuilder('propulsion', CoreMetaData, engine_models=engines) - aero = CoreAerodynamicsBuilder('aerodynamics', CoreMetaData, legacy_code) - - return [aero, prop] - - -def get_geom_and_mass_subsystems(legacy_code): - """ - Get geometry and mass premission subsystems in this order. - - Arguments: - ---------- - legacy_code : str, LegacyCode - either FLOPS or GASP LegacyCode Enums, or their strings equivalents ('FLOPS', 'GASP') - """ - legacy_code = LegacyCode(legacy_code) - mass = CoreMassBuilder('mass', CoreMetaData, legacy_code) - geom = CoreGeometryBuilder('geometry', CoreMetaData, legacy_code) - - return [geom, mass] + perf = CorePerformanceBuilder('performance', CoreMetaData) + engy = CoreEnergyBuilder('energy', CoreMetaData) + + return { + 'propulsion': prop, + 'geometry': geom, + 'energy': engy, + 'mass': mass, + 'aerodynamics': aero, + 'performance': perf, + } diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py index c716c40854..1be021cf0c 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_balanced_field_length.py @@ -10,6 +10,11 @@ from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs +from aviary.subsystems.premission import CorePreMission +from aviary.subsystems.propulsion.utils import build_engine_deck +from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults +from aviary.utils.preprocessors import preprocess_options +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_data.test_data.advanced_single_aisle_data import ( balanced_liftoff_user_options as _takeoff_liftoff_user_options, ) @@ -19,11 +24,6 @@ from aviary.validation_cases.validation_data.test_data.advanced_single_aisle_data import ( inputs as _inputs, ) -from aviary.subsystems.premission import CorePreMission -from aviary.subsystems.propulsion.utils import build_engine_deck -from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults -from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems from aviary.validation_cases.benchmark_utils import print_benchmark_results from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variables import Aircraft, Dynamic @@ -80,7 +80,10 @@ def _do_run(self, driver: Driver, optimizer, *args): driver.recording_options['record_derivatives'] = False - default_mission_subsystems = get_default_mission_subsystems('FLOPS', engines) + # test encounters issues needing set_input_defaults() if any other subsystems are loaded here + default_mission_subsystems = [ + get_default_subsystems('FLOPS', engines)[k] for k in ['propulsion', 'aerodynamics'] + ] # Upstream static analysis for aero takeoff.model.add_subsystem( diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py index cd5951aa2f..52120d0439 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_landing.py @@ -8,6 +8,11 @@ from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs +from aviary.subsystems.premission import CorePreMission +from aviary.subsystems.propulsion.utils import build_engine_deck +from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults +from aviary.utils.preprocessors import preprocess_options +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.validation_data.test_data.advanced_single_aisle_data import ( inputs as _inputs, ) @@ -17,11 +22,6 @@ from aviary.validation_cases.validation_data.test_data.advanced_single_aisle_data import ( landing_trajectory_builder as _landing_trajectory_builder, ) -from aviary.subsystems.premission import CorePreMission -from aviary.subsystems.propulsion.utils import build_engine_deck -from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults -from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems from aviary.validation_cases.benchmark_utils import print_benchmark_results from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variables import Aircraft, Dynamic @@ -77,14 +77,17 @@ def _do_run(self, driver: Driver, optimizer, *args): engines = [build_engine_deck(aviary_options)] preprocess_options(aviary_options, engine_models=engines) - default_premission_subsystems = get_default_mission_subsystems('FLOPS', engines) + # test encounters issues needing set_input_defaults() if any other subsystems are loaded here + default_mission_subsystems = [ + get_default_subsystems('FLOPS', engines)[k] for k in ['propulsion', 'aerodynamics'] + ] # Upstream static analysis for aero landing.model.add_subsystem( 'pre_mission', CorePreMission( aviary_options=aviary_options, - subsystems=default_premission_subsystems, + subsystems=default_mission_subsystems, subsystem_options={}, ), promotes_inputs=['aircraft:*'], diff --git a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py index ebdd8fcbc2..0dd898da3d 100644 --- a/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py +++ b/aviary/validation_cases/benchmark_tests/test_FLOPS_detailed_takeoff.py @@ -21,7 +21,7 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.functions import set_aviary_initial_values, set_aviary_input_defaults from aviary.utils.preprocessors import preprocess_options -from aviary.utils.test_utils.default_subsystems import get_default_mission_subsystems +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.validation_cases.benchmark_utils import print_benchmark_results from aviary.variable_info.functions import setup_model_options from aviary.variable_info.variables import Aircraft, Dynamic @@ -83,14 +83,17 @@ def _do_run(self, driver: Driver, optimizer, *args): engines = [build_engine_deck(aviary_options)] preprocess_options(aviary_options, engine_models=engines) - default_premission_subsystems = get_default_mission_subsystems('FLOPS', engines) + # test encounters issues needing set_input_defaults() if any other subsystems are loaded here + default_mission_subsystems = [ + get_default_subsystems('FLOPS', engines)[k] for k in ['propulsion', 'aerodynamics'] + ] # Upstream static analysis for aero takeoff.model.add_subsystem( 'pre_mission', CorePreMission( aviary_options=aviary_options, - subsystems=default_premission_subsystems, + subsystems=default_mission_subsystems, subsystem_options={}, ), promotes_inputs=['aircraft:*'], diff --git a/aviary/validation_cases/benchmark_tests/test_battery_in_a_mission.py b/aviary/validation_cases/benchmark_tests/test_battery_in_a_mission.py index 4d568d0ef5..9e135c91f4 100644 --- a/aviary/validation_cases/benchmark_tests/test_battery_in_a_mission.py +++ b/aviary/validation_cases/benchmark_tests/test_battery_in_a_mission.py @@ -101,8 +101,8 @@ def test_subsystems_in_a_mission(self): f'traj.cruise2.timeseries.{av.Dynamic.Vehicle.CUMULATIVE_ELECTRIC_ENERGY_USED}' ) expected_scalar_values = { - cumulative_energy_var: (38.61409156, 'kW*h'), - av.Mission.FUEL_MASS: (1254.14061154, 'lbm'), + cumulative_energy_var: (38.61396066, 'kW*h'), + av.Mission.FUEL_MASS: (1253.1365799, 'lbm'), } for var_name, (expected, units) in expected_scalar_values.items(): @@ -120,50 +120,50 @@ def test_subsystems_in_a_mission(self): soc_cruise1_var: ( [ 0.99999578, - 0.98775148, - 0.97085772, - 0.96551112, - 0.96551112, - 0.94050842, - 0.90601345, - 0.8950968, - 0.8950968, - 0.86537877, - 0.82437912, - 0.81140412, - 0.81140412, - 0.78641514, - 0.75193911, - 0.74102844, - 0.74102844, - 0.72879544, - 0.71191726, - 0.70657558, + 0.98775152, + 0.97085782, + 0.96551124, + 0.96551124, + 0.94050862, + 0.90601377, + 0.89509716, + 0.89509716, + 0.86537923, + 0.82437972, + 0.81140476, + 0.81140476, + 0.78641587, + 0.75193995, + 0.74102932, + 0.74102932, + 0.72879636, + 0.71191823, + 0.70657658, ], None, ), soc_cruise2_var: ( [ - 0.70657558, - 0.69434408, - 0.67746797, - 0.67212695, - 0.67212695, - 0.64715037, - 0.61269144, - 0.60178619, - 0.60178619, - 0.5720992, - 0.53114236, - 0.51818091, - 0.51818091, - 0.49321803, - 0.45877799, - 0.44787871, - 0.44787871, - 0.43565848, - 0.41879792, - 0.41346182, + 0.70657658, + 0.69434511, + 0.67746906, + 0.67212806, + 0.67212806, + 0.64715157, + 0.61269275, + 0.60178754, + 0.60178754, + 0.57210065, + 0.53114395, + 0.51818254, + 0.51818254, + 0.49321974, + 0.45877982, + 0.44788059, + 0.44788059, + 0.43566039, + 0.41879989, + 0.41346381, ], None, ), @@ -214,7 +214,7 @@ def test_subsystems_in_a_mission_2dof(self): if __name__ == '__main__': - # unittest.main() - test = TestBatteryMission() - test.setUp() - test.test_subsystems_in_a_mission() + unittest.main() + # test = TestBatteryMission() + # test.setUp() + # test.test_subsystems_in_a_mission() diff --git a/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py b/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py index fa67909b3d..53d39565ad 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_FwGm.py @@ -5,8 +5,8 @@ from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs -from aviary.models.missions.two_dof_default import phase_info from aviary.interface.run_aviary import run_aviary +from aviary.models.missions.two_dof_default import phase_info from aviary.validation_cases.benchmark_utils import print_benchmark_results from aviary.variable_info.variables import Aircraft, Mission diff --git a/aviary/validation_cases/benchmark_tests/test_bench_electric_large_turboprop_freighter.py b/aviary/validation_cases/benchmark_tests/test_bench_electric_large_turboprop_freighter.py index 511b8aa11e..6222c335d5 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_electric_large_turboprop_freighter.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_electric_large_turboprop_freighter.py @@ -17,7 +17,7 @@ from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings -# @use_tempdirs +@use_tempdirs @require_pyoptsparse(optimizer='IPOPT') # TODO need to add asserts with "truth" values class LargeElectricTurbopropFreighterBenchmark(unittest.TestCase): diff --git a/aviary/validation_cases/benchmark_tests/test_bench_hybrid_large_turboprop_freighter.py b/aviary/validation_cases/benchmark_tests/test_bench_hybrid_large_turboprop_freighter.py index 12fffb1a5a..3485bef327 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_hybrid_large_turboprop_freighter.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_hybrid_large_turboprop_freighter.py @@ -18,7 +18,7 @@ from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings -# @use_tempdirs +@use_tempdirs @require_pyoptsparse(optimizer='IPOPT') # TODO need to add asserts with "truth" values class LargeHybridTurbopropFreighterBenchmark(unittest.TestCase): diff --git a/aviary/validation_cases/benchmark_tests/test_bench_off_design.py b/aviary/validation_cases/benchmark_tests/test_bench_off_design.py index 90c901be98..e5c981d515 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_off_design.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_off_design.py @@ -1,10 +1,10 @@ import unittest -import aviary.api as av from copy import deepcopy from openmdao.utils.assert_utils import assert_near_equal from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs +import aviary.api as av from aviary.core.aviary_problem import AviaryProblem from aviary.models.missions.energy_state_default import phase_info as energy_phase_info from aviary.models.missions.two_dof_default import phase_info as twodof_phase_info @@ -107,76 +107,48 @@ def test_off_design_max_range_mission_changed(self): num_business=5, num_economy=75, ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.Design.RANGE), - self.prob.get_val(Aircraft.Design.RANGE), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.RANGE), 2406.74846166, tolerance=1e-3 - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.TOTAL_FUEL_MASS, 'lbm'), - 29252.33210511, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.OPERATING_MASS, 'lbm'), - self.prob.get_val(Mission.OPERATING_MASS, 'lbm'), - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), - 5000, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), - 23225, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), - 18225, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), - self.prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - self.prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.GROSS_MASS, 'lbm'), - 150000, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), - 1, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.aviary_inputs.get_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS - ), - 5, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_ECONOMY_CLASS), - 75, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), - 81, - tolerance=1e-12, - ) + + expected_values = { + Mission.RANGE: (2406.74846166, 'unitless', 1e-3), + Mission.TOTAL_FUEL_MASS: (29252.33210511, 'lbm', 1e-5), + Aircraft.CrewPayload.CARGO_MASS: (5000, 'lbm', 1e-5), + Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS: (23225, 'lbm', 1e-5), + Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS: (18225, 'lbm', 1e-5), + Mission.GROSS_MASS: (150000, 'lbm', 1e-12), + } + + for var_name, (expected, units, tol) in expected_values.items(): + with self.subTest(var=var_name): + actual = prob_off_design_max_range.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) + + # Variables that should match design problem + design_match_values = { + Aircraft.Design.RANGE: ('unitless', 1e-12), + Mission.OPERATING_MASS: ('lbm', 1e-5), + Aircraft.Design.EMPTY_MASS: ('lbm', 1e-12), + Aircraft.Design.GROSS_MASS: ('lbm', 1e-12), + } + + for var_name, (units, tol) in design_match_values.items(): + with self.subTest(var=var_name): + actual = prob_off_design_max_range.get_val(var_name, units=units) + expected = self.prob.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) + + # Input values + expected_inputs = { + Aircraft.CrewPayload.NUM_FIRST_CLASS: (1, 1e-12), + Aircraft.CrewPayload.NUM_BUSINESS_CLASS: (5, 1e-12), + Aircraft.CrewPayload.NUM_ECONOMY_CLASS: (75, 1e-12), + Aircraft.CrewPayload.NUM_PASSENGERS: (81, 1e-12), + } + + for var_name, (expected, tol) in expected_inputs.items(): + with self.subTest(var=var_name): + actual = prob_off_design_max_range.aviary_inputs.get_val(var_name) + assert_near_equal(actual, expected, tol) + self.assertTrue(prob_off_design_max_range.result.success) @require_pyoptsparse(optimizer='SNOPT') @@ -199,72 +171,48 @@ def test_off_design_min_fuel_mission_changed(self): num_business=5, num_economy=144, ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.Design.RANGE), - self.prob.get_val(Aircraft.Design.RANGE), - tolerance=1e-12, - ) - assert_near_equal(prob_off_design_min_fuel.get_val(Mission.RANGE), 1800, tolerance=1e-6) - assert_near_equal( - prob_off_design_min_fuel.get_val(Mission.TOTAL_FUEL_MASS, 'lbm'), - 24216.80682191, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Mission.OPERATING_MASS, 'lbm'), - self.prob.get_val(Mission.OPERATING_MASS, 'lbm'), - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), - 2500, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), - 36250, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), - 33750, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), - self.prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - self.prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Mission.GROSS_MASS, 'lbm'), - 157989.476521, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), - 1, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS), - 5, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_ECONOMY_CLASS), - 144, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), - 150, - tolerance=1e-12, - ) + + expected_values = { + Mission.RANGE: (1800, 'unitless', 1e-6), + Mission.TOTAL_FUEL_MASS: (24216.80682191, 'lbm', 1e-5), + Aircraft.CrewPayload.CARGO_MASS: (2500, 'lbm', 1e-12), + Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS: (36250, 'lbm', 1e-5), + Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS: (33750, 'lbm', 1e-5), + Mission.GROSS_MASS: (157989.476521, 'lbm', 1e-5), + } + + for var_name, (expected, units, tol) in expected_values.items(): + with self.subTest(var=var_name): + actual = prob_off_design_min_fuel.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) + + # Variables that should match design problem + design_match_values = { + Aircraft.Design.RANGE: ('unitless', 1e-12), + Mission.OPERATING_MASS: ('lbm', 1e-5), + Aircraft.Design.EMPTY_MASS: ('lbm', 1e-12), + Aircraft.Design.GROSS_MASS: ('lbm', 1e-12), + } + + for var_name, (units, tol) in design_match_values.items(): + with self.subTest(var=var_name): + actual = prob_off_design_min_fuel.get_val(var_name, units=units) + expected = self.prob.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) + + # Input values + expected_inputs = { + Aircraft.CrewPayload.NUM_FIRST_CLASS: (1, 1e-12), + Aircraft.CrewPayload.NUM_BUSINESS_CLASS: (5, 1e-12), + Aircraft.CrewPayload.NUM_ECONOMY_CLASS: (144, 1e-12), + Aircraft.CrewPayload.NUM_PASSENGERS: (150, 1e-12), + } + + for var_name, (expected, tol) in expected_inputs.items(): + with self.subTest(var=var_name): + actual = prob_off_design_min_fuel.aviary_inputs.get_val(var_name) + assert_near_equal(actual, expected, tol) + self.assertTrue(prob_off_design_min_fuel.result.success) @@ -359,60 +307,50 @@ def test_off_design_max_range_mission_changed(self): mission_gross_mass=155000.0, num_pax=75, ) - assert_near_equal( - prob_off_design_max_range.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), - prob.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.RANGE), 4013.45700631, tolerance=1e-4 - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.TOTAL_FUEL_MASS, 'lbm'), - 40019.62660076, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.OPERATING_MASS, 'lbm'), - 94980.37339924, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), - 5000, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), - 20000, - tolerance=1e-5, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), - 15000, - tolerance=1e-6, - ) + + expected_values = { + Mission.RANGE: (4013.45700631, 'unitless', 1e-4), + Mission.TOTAL_FUEL_MASS: (40019.62660076, 'lbm', 1e-5), + Mission.OPERATING_MASS: (94980.37339924, 'lbm', 1e-5), + Aircraft.CrewPayload.CARGO_MASS: (5000, 'lbm', 1e-5), + Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS: (20000, 'lbm', 1e-5), + Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS: (15000, 'lbm', 1e-6), + Mission.GROSS_MASS: (155000, 'lbm', 1e-12), + } + + for var_name, (expected, units, tol) in expected_values.items(): + with self.subTest(var=var_name): + actual = prob_off_design_max_range.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) + + # Variables that should match design problem + with self.subTest(var=Aircraft.Design.GROSS_MASS): + actual = prob_off_design_max_range.get_val(Aircraft.Design.GROSS_MASS, 'lbm') + expected = prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm') + assert_near_equal(actual, expected, 1e-12) + # currently not a GASP variable # assert_near_equal( # prob_off_design_max_range.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # tolerance=1e-12, # ) - assert_near_equal( - prob_off_design_max_range.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.get_val(Mission.GROSS_MASS, 'lbm'), - 155000, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), - 75, - tolerance=1e-12, - ) + + # Input values + expected_inputs = { + Aircraft.Design.RANGE: ( + prob.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), + 'nmi', + 1e-12, + ), + Aircraft.CrewPayload.NUM_PASSENGERS: (75, 'unitless', 1e-12), + } + + for var_name, (expected, units, tol) in expected_inputs.items(): + with self.subTest(var=var_name): + actual = prob_off_design_max_range.aviary_inputs.get_val(var_name, units) + assert_near_equal(actual, expected, tol) + # self.assertTrue(prob_off_design_max_range.result.success) @require_pyoptsparse(optimizer='SNOPT') @@ -437,58 +375,50 @@ def test_off_design_min_fuel_mission_changed(self): mission_range=1800, num_pax=150, ) - assert_near_equal( - prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), - prob.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), - tolerance=1e-12, - ) - assert_near_equal(prob_off_design_min_fuel.get_val(Mission.RANGE), 1800, tolerance=1e-6) - assert_near_equal( - prob_off_design_min_fuel.get_val(Mission.TOTAL_FUEL_MASS, 'lbm'), - 21452.85145652, - tolerance=1e-6, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Mission.OPERATING_MASS, 'lbm'), - 94980.37339924, - tolerance=1e-6, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), - 2100, - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), - 32100, - tolerance=1e-6, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), - 30000, - tolerance=1e-6, - ) + + expected_values = { + Mission.RANGE: (1800, 'unitless', 1e-6), + Mission.TOTAL_FUEL_MASS: (21452.85145652, 'lbm', 1e-6), + Mission.OPERATING_MASS: (94980.37339924, 'lbm', 1e-6), + Aircraft.CrewPayload.CARGO_MASS: (2100, 'lbm', 1e-12), + Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS: (32100, 'lbm', 1e-6), + Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS: (30000, 'lbm', 1e-6), + Mission.GROSS_MASS: (148533.22485577, 'lbm', 1e-6), + } + + for var_name, (expected, units, tol) in expected_values.items(): + with self.subTest(var=var_name): + actual = prob_off_design_min_fuel.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) + + # Variables that should match design problem + with self.subTest(var=Aircraft.Design.GROSS_MASS): + actual = prob_off_design_min_fuel.get_val(Aircraft.Design.GROSS_MASS, 'lbm') + expected = prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm') + assert_near_equal(actual, expected, 1e-12) + # currently not a GASP variable # assert_near_equal( # prob_off_design_min_fuel.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # tolerance=1e-12, # ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), - tolerance=1e-12, - ) - assert_near_equal( - prob_off_design_min_fuel.get_val(Mission.GROSS_MASS, 'lbm'), - 148533.22485577, - tolerance=1e-6, - ) - assert_near_equal( - prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), - 150, - tolerance=1e-12, - ) + + # Input values + expected_inputs = { + Aircraft.Design.RANGE: ( + prob.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), + 'nmi', + 1e-12, + ), + Aircraft.CrewPayload.NUM_PASSENGERS: (150, 'unitless', 1e-12), + } + + for var_name, (expected, units, tol) in expected_inputs.items(): + with self.subTest(var=var_name): + actual = prob_off_design_min_fuel.aviary_inputs.get_val(var_name, units) + assert_near_equal(actual, expected, tol) + self.assertTrue(prob_off_design_min_fuel.result.success) @@ -533,38 +463,27 @@ def test_payload_range(self): prob.run_aviary_problem() # test outputted payload-range data - assert_near_equal( - prob.payload_range_data.get_val('Payload', 'lbm'), - [ - 38025.0, - 38025.0, - 24529.3, - 0, - ], - tolerance=1e-3, - ) - assert_near_equal( - prob.payload_range_data.get_val('Fuel', 'lbm'), - [0, 28697.02, 42192.69, 42192.69], - tolerance=1e-3, - ) - assert_near_equal( - prob.payload_range_data.get_val('Range', 'NM'), - [0, 2500, 3910.17, 4339.7], - tolerance=1e-3, - ) + expected_payload_range = { + 'Payload': ([38025.0, 38025.0, 24529.3, 0], 'lbm', 1e-3), + 'Fuel': ([0, 28697.02, 42192.69, 42192.69], 'lbm', 1e-3), + 'Range': ([0, 2500, 3910.17, 4339.7], 'NM', 1e-3), + } + + for var_name, (expected, units, tol) in expected_payload_range.items(): + with self.subTest(var=var_name): + actual = prob.payload_range_data.get_val(var_name, units=units) + assert_near_equal(actual, expected, tol) # verify TOGW for each payload range problem - assert_near_equal( - prob.max_fuel_pyld_range_prob.get_val(Mission.GROSS_MASS, 'lbm'), - 166539.46027154, - tolerance=1e-8, - ) - assert_near_equal( - prob.ferry_range_prob.get_val(Mission.GROSS_MASS, 'lbm'), - 142010.12881933, - tolerance=1e-8, - ) + expected_togw = { + 'max_fuel_pyld_range_prob': (166539.46027154, 1e-8), + 'ferry_range_prob': (142010.12881933, 1e-8), + } + + for prob_name, (expected, tol) in expected_togw.items(): + with self.subTest(var=prob_name): + actual = getattr(prob, prob_name).get_val(Mission.GROSS_MASS, 'lbm') + assert_near_equal(actual, expected, tol) self.assertTrue(prob.result.success) self.assertTrue(prob.max_fuel_pyld_range_prob.result.success) self.assertTrue(prob.ferry_range_prob.result.success) diff --git a/aviary/validation_cases/benchmark_tests/test_bench_reserves.py b/aviary/validation_cases/benchmark_tests/test_bench_reserves.py new file mode 100644 index 0000000000..5b48380d7b --- /dev/null +++ b/aviary/validation_cases/benchmark_tests/test_bench_reserves.py @@ -0,0 +1,203 @@ +import unittest +from copy import deepcopy + +from openmdao.utils.assert_utils import assert_near_equal +from openmdao.utils.testing_utils import require_pyoptsparse, use_tempdirs + +from aviary.core.aviary_problem import AviaryProblem +from aviary.interface.run_aviary import run_aviary +from aviary.models.missions.energy_state_default import phase_info as energy_phase_info +from aviary.models.missions.two_dof_default import phase_info as twodof_phase_info +from aviary.variable_info.enums import PhaseType, SpeedType +from aviary.variable_info.variables import Aircraft, Mission, Settings + + +@use_tempdirs +class ReserveTest(unittest.TestCase): + @unittest.skip('This test currently has convergence issues') + def test_reserves_2dof(self): + phase_info_local = deepcopy(twodof_phase_info) + + phase_info_local.update( + { + 'reserve_climb': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'user_options': { + 'reserve': True, + 'num_segments': 3, + 'order': 3, + 'EAS_target': (250, 'kn'), + 'mach_target': 0.5, + 'required_available_climb_rate': (0.1, 'ft/min'), + 'time_duration_bounds': ((200, 17_000), 's'), + 'time_duration_ref': (5000, 's'), + 'altitude_final': (20_000, 'ft'), + 'altitude_bounds': ((500.0, 21_000.0), 'ft'), + 'altitude_ref': (20_000, 'ft'), + 'mass_bounds': ((0, None), 'lbm'), + 'mass_ref': (100_000, 'lbm'), + 'mass_defect_ref': (100_000, 'lbm'), + 'distance_bounds': ((0, 500.0), 'NM'), + 'distance_ref': (100, 'NM'), + 'distance_defect_ref': (100, 'NM'), + }, + 'initial_guesses': { + 'time': ([1504.0, 1300.0], 's'), + 'distance': ([0.0, 50.0], 'NM'), + 'altitude': ([1000.0, 20_000], 'ft'), + 'throttle': ([0.956, 0.956], 'unitless'), + }, + }, + 'reserve_cruise': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'user_options': { + 'reserve': True, + 'phase_type': PhaseType.SIMPLE_CRUISE, + 'target_distance': (300, 'km'), + 'alt_cruise': (20_000, 'ft'), + 'mach_cruise': 0.5, + 'mass_ref': (100_000, 'lbm'), + 'time_duration_bounds': ((0, 300), 'min'), + }, + 'initial_guesses': { + 'mass': ([168500.0, 135000], 'lbm'), + 'time': ([2804.0, 18000.0], 's'), + }, + }, + 'reserve_descent': { + 'subsystem_options': {'aerodynamics': {'method': 'cruise'}}, + 'user_options': { + 'reserve': True, + 'num_segments': 1, + 'order': 7, + 'EAS_target': (250, 'kn'), + 'mach_target': 0.5, + 'input_speed_type': SpeedType.EAS, + 'time_duration_bounds': ((100.0, 5000), 's'), + 'time_duration_ref': (500, 's'), + 'altitude_final': (1000, 'ft'), + 'altitude_bounds': ((500.0, 21_000.0), 'ft'), + 'altitude_ref': (20_000, 'ft'), + 'altitude_ref0': (1000, 'ft'), + 'altitude_constraint_ref': (1000, 'ft'), + 'mass_bounds': ((0, None), 'lbm'), + 'mass_ref': (100_000, 'lbm'), + 'mass_defect_ref': (100_000, 'lbm'), + 'distance_bounds': ((0.0, 5000.0), 'NM'), + 'distance_ref': (200, 'NM'), + 'distance_defect_ref': (100, 'NM'), + }, + 'initial_guesses': { + 'mass': (135000.0, 'lbm'), + 'altitude': ([20_000, 1.0e3], 'ft'), + 'throttle': ([0.0, 0.0], 'unitless'), + 'distance': ([200.0, 220.0], 'NM'), + 'time': ([20804.0, 500.0], 's'), + }, + }, + } + ) + + prob = AviaryProblem(verbosity=1) + prob.load_inputs( + 'large_single_aisle_1_GASP.csv', + phase_info_local, + ) + + prob.aviary_inputs.set_val(Mission.RESERVE_FUEL_MARGIN, 0.05) + prob.aviary_inputs.set_val(Mission.RESERVE_FUEL_MASS_ADDITIONAL, 125, units='lbm') + + prob.check_and_preprocess_inputs() + + prob.build_model() + + prob.add_driver('SNOPT') + + prob.add_design_variables() + + prob.add_objective() + + prob.setup() + + prob.run_aviary_problem() + + expected_values = { + 'energy.reserve_fuel_margin_mass': (1887.21889734, 'lbm'), + Mission.RESERVE_FUEL_MASS: (37744.37794685, 'lbm'), + Mission.TOTAL_RESERVE_FUEL_MASS: (39756.5968442, 'lbm'), + } + + for var_name, (expected, units) in expected_values.items(): + with self.subTest(var=var_name): + assert_near_equal(prob.get_val(var_name, units=units), expected, 1e-10) + + def test_reserves_energy(self): + phase_info_local = deepcopy(energy_phase_info) + phase_info_local.update( + { + 'reserve_cruise': { + 'subsystem_options': {'aerodynamics': {'method': 'computed'}}, + 'user_options': { + 'reserve': True, + # Distance traveled in this phase + 'target_distance': (300, 'km'), + 'num_segments': 5, + 'order': 3, + 'mach_optimize': False, + 'mach_polynomial_order': 1, + 'mach_initial': (0.5, 'unitless'), + 'mach_final': (0.5, 'unitless'), + 'altitude_optimize': False, + 'altitude_polynomial_order': 1, + 'altitude_initial': (20_000, 'ft'), + 'altitude_final': (20_000, 'ft'), + 'throttle_enforcement': 'boundary_constraint', + 'time_initial_bounds': ((149.5, 448.5), 'min'), + 'time_duration_bounds': ((0, 300), 'min'), + }, + 'initial_guesses': { + 'time': ([30, 120], 'min'), + }, + } + } + ) + + prob = AviaryProblem(verbosity=0) + prob.load_inputs( + 'advanced_single_aisle_FLOPS.csv', + phase_info_local, + ) + + prob.aviary_inputs.set_val(Mission.RESERVE_FUEL_MARGIN, 0.05) + prob.aviary_inputs.set_val(Mission.RESERVE_FUEL_MASS_ADDITIONAL, 125, units='lbm') + + prob.check_and_preprocess_inputs() + + prob.build_model() + + prob.add_driver() + + prob.add_design_variables() + + prob.add_objective() + + prob.setup() + + prob.run_aviary_problem() + + expected_values = { + 'energy.reserve_fuel_margin_mass': (754.09222299, 'lbm'), + Mission.RESERVE_FUEL_MASS: (15804.46869892, 'lbm'), + Mission.TOTAL_RESERVE_FUEL_MASS: (16683.56092191, 'lbm'), + } + + for var_name, (expected, units) in expected_values.items(): + with self.subTest(var=var_name): + assert_near_equal(prob.get_val(var_name, units=units), expected, 1e-10) + + +if __name__ == '__main__': + unittest.main() + # test = ReserveTest() + # test.test_reserves_energy() + # test.test_reserves_2dof() diff --git a/aviary/validation_cases/benchmark_tests/test_bench_throttle_allocation.py b/aviary/validation_cases/benchmark_tests/test_bench_throttle_allocation.py index d76702ac9d..53d3cc6937 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_throttle_allocation.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_throttle_allocation.py @@ -180,4 +180,7 @@ def test_multiengine_dynamic(self): if __name__ == '__main__': - unittest.main() + # unittest.main() + test = ThrottleAllocationTestcase() + test.setUp() + test.test_multiengine_dynamic() diff --git a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py index f791b1facc..1cb852e56b 100644 --- a/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py +++ b/aviary/validation_cases/benchmark_tests/test_bwb_FwFm.py @@ -104,6 +104,8 @@ def test_bench_bwb_FwFm_SNOPT(self): verbosity=1, max_iter=60, ) + with open('temp_vars.txt', 'w') as f: + prob.model.list_vars(units=True, print_arrays=True, out_stream=f) rtol = 1e-3 @@ -167,7 +169,7 @@ def test_bench_bwb_FwFm_SNOPT(self): # assert_near_equal(prob.get_val(Mission.RANGE, units='NM'), 7750.0, tolerance=rtol) -# @use_tempdirs +@use_tempdirs class BWB300ProblemPhaseTestCase(unittest.TestCase): """ Test the setup and run of a BWB aircraft using FLOPS mass and aero method diff --git a/aviary/validation_cases/validation_data/test_data/advanced_single_aisle_data.py b/aviary/validation_cases/validation_data/test_data/advanced_single_aisle_data.py index ed9c2ca9db..b3f80a9916 100644 --- a/aviary/validation_cases/validation_data/test_data/advanced_single_aisle_data.py +++ b/aviary/validation_cases/validation_data/test_data/advanced_single_aisle_data.py @@ -34,10 +34,7 @@ from aviary.subsystems.propulsion.utils import build_engine_deck from aviary.utils.aviary_values import AviaryValues from aviary.utils.functions import get_path -from aviary.utils.test_utils.default_subsystems import ( - get_default_mission_subsystems, - get_default_premission_subsystems, -) +from aviary.utils.test_utils.default_subsystems import get_default_subsystems from aviary.variable_info.enums import EquationsOfMotion, LegacyCode, ProblemType from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings @@ -448,8 +445,10 @@ # preprocess_options(inputs, engine_models=engine) # build subsystems -default_premission_subsystems = get_default_premission_subsystems('FLOPS', engines) -default_mission_subsystems = get_default_mission_subsystems('FLOPS', engines) +default_premission_subsystems = get_default_subsystems('FLOPS', engines).values() +default_mission_subsystems = [ + get_default_subsystems('FLOPS', engines)[k] for k in ['propulsion', 'aerodynamics'] +] # region - detailed takeoff takeoff_trajectory_builder = TakeoffTrajectory('detailed_takeoff') diff --git a/aviary/validation_cases/validation_data/test_models/configuration_test_GASP.csv b/aviary/validation_cases/validation_data/test_models/configuration_test_GASP.csv index c5d1f04446..b9b37483bf 100644 --- a/aviary/validation_cases/validation_data/test_models/configuration_test_GASP.csv +++ b/aviary/validation_cases/validation_data/test_models/configuration_test_GASP.csv @@ -22,7 +22,7 @@ aircraft:design:emergency_equipment_mass,0,lbm aircraft:design:max_structural_speed,440,mi/h aircraft:design:part25_structural_category,3,unitless mission:reserve_fuel_mass_additional,0,lbm -mission:reserve_fuel_margin,15,unitless +mission:reserve_fuel_margin,0.15,unitless aircraft:design:static_margin,0.05,unitless aircraft:design:structural_mass_increment,0,lbm aircraft:design:drag_divergence_shift,0.033,unitless diff --git a/aviary/variable_info/enums.py b/aviary/variable_info/enums.py index 5d6487927e..295f4287e4 100644 --- a/aviary/variable_info/enums.py +++ b/aviary/variable_info/enums.py @@ -163,6 +163,14 @@ def get_element_by_name(cls, val: str): return next((c for c in cls if c.name == val), None) +class FuelType(Enum): + HYDROGEN_GASEOUS = 'hydrogen_liquid' + HYDROGEN_LIQUID = 'hydrogen_gaseous' + JET_A = 'jet_a' + METHANE_GASEOUS = 'methane_gaseous' + METHANE_LIQUID = 'methane_liquid' # LNG + + class LegacyCode(Enum): """Flag for legacy codebases.""" diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index 76f32adfbe..aad4e0cc11 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -3,21 +3,23 @@ """ from copy import deepcopy + import numpy as np +import aviary.constants as Constants from aviary.utils.develop_metadata import add_meta_data from aviary.variable_info.enums import ( AircraftTypes, + AtmosphereModel, EquationsOfMotion, FlapType, + FuelType, GASPEngineType, LegacyCode, ProblemType, Verbosity, - AtmosphereModel, ) from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings -import aviary.constants as Constants # --------------------------- # Meta data associated with variables in the aircraft data hierarchy. @@ -1365,7 +1367,7 @@ 'FLOPS': None, # 'DARM.WMARG', }, units='lbm', - desc='empty mass margin', + desc='Fraction of calculated empty mass to be added as additional margin.', default_value=0.0, ) @@ -1379,7 +1381,7 @@ 'FLOPS': 'WTIN.EWMARG', # ['&DEFINE.WTIN.EWMARG', 'DARM.EWMARG'], }, units='unitless', - desc='empty mass margin scaler', + desc='Empty mass margin scaler', default_value=0.0, ) @@ -2951,6 +2953,20 @@ default_value=7, ) +add_meta_data( + Aircraft.Fuel.TYPE, + meta_data=_MetaData, + historical_name={ + 'GASP': None, + 'FLOPS': None, + }, + units='unitless', + desc='Fuel types used on the aircraft. Fuel variables are assumed to be vectorized in order ' + 'corresponding to the order of fuel types specified here.', + default_value=FuelType.JET_A, + # multivalue=True, +) + add_meta_data( # Note user override # - see also: Aircraft.Fuel.UNUSABLE_FUEL_MASS_SCALER @@ -6580,8 +6596,16 @@ meta_data=_MetaData, historical_name={'GASP': None, 'FLOPS': None}, units='kJ', - desc='Total amount of electric energy consumed by the vehicle up until this point ' - 'in the mission', + desc='Total amount of electric energy consumed by the vehicle up until this point in the mission', + multivalue=True, +) + +add_meta_data( + Dynamic.Vehicle.CUMULATIVE_FUEL_BURNED, + meta_data=_MetaData, + historical_name={'GASP': None, 'FLOPS': None}, + units='lbm', + desc='Total amount of fuel consumed by the vehicle up until this point in the mission', multivalue=True, ) @@ -6907,22 +6931,20 @@ meta_data=_MetaData, historical_name={'GASP': None, 'FLOPS': None}, units='lbm', - desc='Fuel burned from taxi-out through all regular phases of the mission (e.g. takeoff, climb, ' + desc='Fuel burned from taxi-out through all phases of the main mission (e.g. takeoff, climb, ' 'cruse, descent, landing). This does not include fuel burned in reserve phases or taxi-in. The ' 'only time taxi-in would be included in this is if the user specifies a taxi phase as part of ' - 'the regular mission phases.', + 'the main mission phases.', ) add_meta_data( Mission.GRAVITY, meta_data=_MetaData, historical_name={'GASP': None, 'FLOPS': None}, - desc='Gravitational acceleration of the planet. This model is updated' - 'in preprocess_options() based on which atmosphere is selected.' - 'This ensures the gravity model matches the planet.', + desc='Gravitational acceleration of the planet. This model is updated in preprocess_options() ' + 'based on which atmosphere is selected. This ensures the gravity model matches the planet.', types=float, option=True, - # units=Constants.GRAV_EARTH[1], default_value=Constants.GRAV_EARTH[0], ) @@ -6984,8 +7006,8 @@ historical_name={'GASP': None, 'FLOPS': None}, option=True, units='unitless', - desc='Required fuel reserves, given as a precentage of mission fuel.' - 'Mission fuel only includes normal phases and excludes reserve phases.', + desc='Required fuel reserves, given as a fraction of mission fuel. Mission fuel only includes ' + 'main phases and excludes reserve phases.', default_value=0.0, ) diff --git a/aviary/variable_info/variables.py b/aviary/variable_info/variables.py index 1f5d8a8814..8b909e7136 100644 --- a/aviary/variable_info/variables.py +++ b/aviary/variable_info/variables.py @@ -280,6 +280,7 @@ class Fuel: MAX_CAPACITY_MASS = 'aircraft:fuel:max_capacity_mass' MAX_CAPACITY_VOLUME = 'aircraft:fuel:max_capacity_volume' NUM_TANKS = 'aircraft:fuel:num_tanks' + TYPE = 'aircraft:fuel:type' UNUSABLE_FUEL_MASS = 'aircraft:fuel:unusable_fuel_mass' UNUSABLE_FUEL_MASS_COEFFICIENT = 'aircraft:fuel:unusable_fuel_mass_coefficient' UNUSABLE_FUEL_MASS_SCALER = 'aircraft:fuel:unusable_fuel_mass_scaler' @@ -638,6 +639,7 @@ class Vehicle: ANGLE_OF_ATTACK = 'angle_of_attack' BATTERY_STATE_OF_CHARGE = 'battery_state_of_charge' CUMULATIVE_ELECTRIC_ENERGY_USED = 'cumulative_electric_energy_used' + CUMULATIVE_FUEL_BURNED = 'cumulative_fuel_burn' DRAG = 'drag' DRAG_COEFFICIENT = 'drag_coefficient' LIFT = 'lift'