diff --git a/aviary/docs/_toc.yml b/aviary/docs/_toc.yml index 4c53a7e9c..37137df5a 100644 --- a/aviary/docs/_toc.yml +++ b/aviary/docs/_toc.yml @@ -45,6 +45,7 @@ parts: - file: user_guide/features/overriding - file: user_guide/FLOPS_based_detailed_takeoff_and_landing - file: user_guide/reserve_missions + - file: user_guide/multi_mission - file: user_guide/off_design_missions - file: user_guide/SGM_capabilities - file: user_guide/troubleshooting @@ -57,6 +58,7 @@ parts: - file: examples/coupled_aircraft_mission_optimization - file: examples/additional_flight_phases - file: examples/reserve_missions + - file: examples/multi_mission - file: examples/off_design_example - file: examples/OAS_subsystem - file: examples/level_2_detailed_takeoff_and_landing diff --git a/aviary/docs/examples/images/multi_mission.png b/aviary/docs/examples/images/multi_mission.png new file mode 100644 index 000000000..1aab73d2f Binary files /dev/null and b/aviary/docs/examples/images/multi_mission.png differ diff --git a/aviary/docs/examples/multi_mission.ipynb b/aviary/docs/examples/multi_mission.ipynb new file mode 100644 index 000000000..65c0dab52 --- /dev/null +++ b/aviary/docs/examples/multi_mission.ipynb @@ -0,0 +1,165 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [], + "source": [ + "# Testing Cell\n", + "from aviary.docs.tests.utils import glue_variable\n", + "from aviary.api import Aircraft, Mission, AviaryProblem\n", + "from aviary.examples.multi_mission import run_multimission_example_large_single_aisle\n", + "\n", + "glue_variable(f'{Mission.Design.RANGE=}'.split('=')[0], md_code=True)\n", + "\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_PASSENGERS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_BUSINESS_CLASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_FIRST_CLASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_TOURIST_CLASS=}'.split('=')[0], md_code=True)\n", + "\n", + "glue_variable(f'{Aircraft.CrewPayload.Design.NUM_PASSENGERS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.Design.NUM_FIRST_CLASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS=}'.split('=')[0], md_code=True)\n", + "\n", + "glue_variable('capi',AviaryProblem.check_and_preprocess_inputs.__name__+'()',md_code=True)\n", + "glue_variable(f'{Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Mission.Summary.CRUISE_MACH=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.LandingGear.MAIN_GEAR_MASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.LandingGear.NOSE_GEAR_MASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.Wing.SWEEP=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Mission.Design.GROSS_MASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Mission.Summary.GROSS_MASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.Design.EMPTY_MASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Mission.Summary.FUEL_BURNED=}'.split('=')[0], md_code=True)\n", + "\n", + "glue_variable(f'{Aircraft.Furnishings.MASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.PASSENGER_SERVICE_MASS=}'.split('=')[0], md_code=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multi-Mission Example\n", + "\n", + "The [Multi-mission Example](\n", + "https://github.com/OpenMDAO/Aviary/tree/main/aviary/examples/multi_mission/run_multimission_example_large_single_aisle.py) demonstrates the capability to optimize an aircraft design considering two missions that the aircraft will perform. For a background on this example see [Multi-Mission Overview](../user_guide/multi_mission).\n", + "\n", + "## Implementation\n", + "At a minimum, the user must supply the following inputs for a multi-mission:\n", + "* 2 aircraft configuration examples (i.e. .csv files)\n", + "* 2 `phase_info` describing the different aircraft missions\n", + "* a weighting of the relative importance of each mission\n", + "* {glue:md}`Mission.Design.RANGE`\n", + "* {glue:md}`Aircraft.CrewPayload.Design.NUM_PASSENGERS`\n", + "* {glue:md}`Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO`\n", + "\n", + "### Aircraft Configuration\n", + "In the example, we import a single aircraft configuration (LargeSingleAisle2FLOPS) and then modify it to create a primary mission which carries 162 passengers and a deadhead mission. The deadhead mission is a mission without passengers, but it still has the same number of seats in the aircraft, even though those seats are empty. The number of seats for passenters in the aircraft, as well as some other systems like passenger airconditioning mass, is set by values of {glue:md}`Aircraft.CrewPayload.Design.NUM_PASSENGERS`, {glue:md}`Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS`, {glue:md}`Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS`, and {glue:md}`Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS`. Whereas the actual number of passengers on the flight is specified by variables of {glue:md}`Aircraft.CrewPayload.NUM_PASSENGERS`, {glue:md}`Aircraft.CrewPayload.NUM_TOURIST_CLASS`, {glue:md}`Aircraft.CrewPayload.NUM_BUSINESS_CLASS`, and {glue:md}`Aircraft.CrewPayload.NUM_BUSINESS_CLASS`.\n", + "\n", + "### Phase Info\n", + "The same mission distance and profile (takeoff, climb, cruise, descent, landing) is being flown for both missions. To enable this, a single phase_info is imported and then deepcopied. The user could modify the deadhead mission to be different from the primary mission by changing the target_range of the deadhead mission to a different value, for example by changing `phase_info_deadhead['post_mission']['target_range'] = [1500, \"nmi\"]` \n", + "\n", + "### Weighting\n", + "The `weights` input value describes the relative importance or frequence of one mission over the other. In the example, the the weigting is [9,1] indicating that for every nine times the aircraft flies a full passenger load, it flies a single deadhead leg. These weightings are based on user input and are converted into fractions. This weighting can be estimated from examining historical passenger loads on a typical aircraf route of interest. The objective function is based on combining the fuel-burn values from both missions and multiplying that by the weights. Other objectives, like max range, have not been tested yet.\n", + "\n", + "### Setting Values\n", + "The {glue:md}`Mission.Design.RANGE` value must be set to size some of Aviary's subsystems. These subsystems, such as avionics, have increasing mass as {glue:md}`Mission.Design.RANGE` increases. These are first order approximations that come with aviary. But because of these, we must ensure that both pre-missions have the same {glue:md}`Mission.Design.RANGE`, even if the actual range flown buy each mission (target_rage) is different. Without this, the avoinics mass calculated in pre-mission would be different for the two missions, resulting in a different aircraft design, which is counter to what is intended with the multi-mission feature. \n", + "\n", + "The total number of passengers ({glue:md}`Aircraft.CrewPayload.Design.NUM_PASSENGERS`) and the design number of passengers of each type (business, tourist, first class), help to define the passenger air conditioning subsystems and the passenger support mass (seats) respectively. Thus when these values are set equal in the primary and deadhead missions, we ensure the aircraft will be designed similarly. \n", + "\n", + "It is good practice, but not required, to set {glue:md}`Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO` in Aviary Values to ensure consistent design of the landing gear for both missions. This combined with Design.GROSS_MASS helps to ensure that {glue:md}`Aircraft.LandingGear.MAIN_GEAR_MASS` and {glue:md}`Aircraft.LandingGear.NOSE_GEAR_MASS` are the same for both missions. If {glue:md}`Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO` is not set, Landing Gear Masses will be caluclated based on {glue:md}`Mission.Summary.CRUISE_MACH` and {glue:md}`Mission.Design.RANGE`. This is potentially problematic because {glue:md}`Mission.Summary.CRUISE_MACH` may not be set, and instead cruse mach may be optimized. In that case, {glue:md}`Mission.Summary.CRUISE_MACH` could vary between the Primary and Deadhead missions, which would then cascade into differeing {glue:md}`Aircraft.LandingGear.MAIN_GEAR_MASS` which causes the aircraft designs to diverge.\n", + "\n", + "## Theory\n", + "Each of the two missions in the example are instantiated as separate aviary problems before copying those two groups over to a single `super_prob`. This means there are two pre-missions, two missions run in parallel. Two get the pre-missions to have the same aircraft design, {glue:md}`Mission.Design.GROSS_MASS`, {glue:md}`Mission.Design.RANGE`, {glue:md}`Aircraft.Wing.SWEEP`, are promoted out of the pre-missions to a single values. This ensures that the aircrafts in both pre-missions have the same design even though their passenger count and fuel mass are different. There is no post-mission for the example, but if one was required for calculating cost or acoustic constraints, there would need to be two post-mission systems as well.\n", + "\n", + "To impact the structure of aviary problems as little as possible, after instantiation of the pre-mission, mission, and post-mission systems, the connections between those systems are created. Then those groups are then copied over into a regular openmdao problem called `super_prob`. This enables the use all the basic aviary connection and checking functions with minimal modification. There originally was a desire to use openmdao subproblems for this implementation but derivatives through subproblems were not available at that time.\n", + "\n", + "Initialization of states and variables is conducted last through `prob.set_initial_guesses()`. This has to be completed after the aviary groups are added to `super_prob`. Setting initial guesses and then copying over a group into `super_prob` will not work in this case because initial guesses is set on the problem, not the group. \n", + "\n", + "Some custom graphing and print functions were added to this example because the basic aviary graphing programs have not yet been modified to handle two database file from two separate missions. The user can see detailed info of each mission result using the `super_prob.model.group_1.list_vars()` commands listed in the comments at the bottom of the example.\n", + "\n", + "## Best Pratices\n", + "The user should be cognizant of the implications of having two pre-mission systems, one for each mission. Both of the pre-mission systems should be nearly identical in setup, save fuel-mass, passenger, and payload calculations. There are numerous opportunities for the user to get this wrong, and accidentally create two different aircraft as a result. For example, in a previous iteration of this example, {glue:md}`Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO` was not specified, which resulted in two different landing gears being designed, one for the Primary mission, one for the Deadhead mission.\n", + "\n", + "If you are having trouble getting your {glue:md}`Aircraft.Design.EMPTY_MASS` (the final drymass mass summation from pre-mission) to be equal for both pre-missions, use the following OpenMDAO commends at the end of the example to list out and compare the mass from each subsystem.\n", + "\n", + "```\n", + "super_prob.model.group_1.list_vars(val=True, units=True, print_arrays=False)\n", + "super_prob.model.group_2.list_vars(val=True, units=True, print_arrays=False)\n", + "```\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Results\n", + "The results of the [Multi-mission Example](\n", + "https://github.com/OpenMDAO/Aviary/tree/main/aviary/examples/multi_mission/run_multimission_example_large_single_aisle.py) are included in the data table and plots below.\n", + "\n", + "From the table results we can see that the Primary mission have the same {glue:md}`Aircraft.Design.EMPTY_MASS` and {glue:md}`Mission.Design.GROSS_MASS`. However, the {glue:md}`Mission.Summary.GROSS_MASS` varies as expected because these represent \"as-flown\" values. The Primary mission has the higher {glue:md}`Mission.Summary.GROSS_MASS` which corresponds to the full passenger load and bags. Consequently, the {glue:md}`Mission.Summary.FUEL_BURNED` for each mission is different, higher for the Primary mission, as expected because this mission is carrying more mass for the same mission. {glue:md}`Aircraft.Wing.SWEEP`is the same for both missions, indicating that the aircraft has been designed similarly in both cases. We do not want to see different values for the wing design because it would mean that the two pre-mission systems are not mirroring eachother. If they were not the same it would mean we are designing two different aircraft. \n", + "\n", + "The Landing_gear masses were also displayed because they are sensitive to {glue:md}`Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO` and {glue:md}`Mission.Summary.CRUISE_MACH`, which the user may or may not have specified in Aviary_values. We expect these landing gear masses to be the same and they are which is good news for us and indicates that both pre-mission designs are mirroring eachother.\n", + "\n", + "Lastly the {glue:md}`Aircraft.Furnishings.MASS` and {glue:md}`Aircraft.CrewPayload.PASSENGER_SERVICE_MASS` are displayed. These values represent the weight of the seats and the air conditioning system for the passengers. They are both the same which is what we expect to see.\n", + "\n", + "A summary colum called 'Expectations' is included as a summary of what we want to see when evaluating this data. \n", + "\n", + "| Variable | Primary | Deadhead | Expectations |\n", + "|:-------------------------------------------------|:-----------------------------:|:--------------------:|---:|\n", + "|Variable: MISSION.DESIGN.GROSS_MASS | 157271.61813452866 (lbm) | 157271.61813452866 (lbm) | Equal |\n", + "|Variable: AIRCRAFT.DESIGN.EMPTY_MASS | 87405.28415563758 (lbm) | 87405.28415563758 (lbm) | Equal |\n", + "|Variable: MISSION.SUMMARY.GROSS_MASS | 157271.61813452866 (lbm) | 120124.3931958877 (lbm) | Different |\n", + "|Variable: MISSION.SUMMARY.FUEL_BURNED | 26891.72449433127 (lbm) | 22994.499555690258 (lbm) | Different |\n", + "|Variable: MISSION.DESIGN.FUEL_MASS | 26891.724494351714 (lbm) | 60141.724494351714 (lbm) | Different |\n", + "|Variable: MISSION.SUMMARY.TOTAL_FUEL_MASS| 26891.72449433127 (lbm) | 22994.499555690258 (lbm) | Different |\n", + "|Variable: AIRCRAFT.LANDING_GEAR.MAIN_GEAR_MASS| 5761.149085460596 (lbm) | 5761.149085460596 (lbm) | Equal |\n", + "|Variable: AIRCRAFT.LANDING_GEAR.NOSE_GEAR_MASS| 746.6143746941743 (lbm) | 746.6143746941743 (lbm) | Equal |\n", + "|Variable: AIRCRAFT.DESIGN.LANDING_TO_TAKEOFF_MASS_RATIO| 0.84 (unitless) | 0.84 (unitless) | Equal |\n", + "|Variable: MISSION.SUMMARY.CRUISE_MACH | 0.785 (unitless) | 0.785 (unitless) | Equal |\n", + "|Variable: AIRCRAFT.FURNISHINGS.MASS | 14690.33988 (lbm) | 14690.33988 (lbm) | Equal |\n", + "|Variable: AIRCRAFT.CREW_AND_PAYLOAD.PASSENGER_SERVICE_MASS| 2524.475592961527 (lbm) | 2524.475592961527 (lbm) | Equal |\n", + "|Variable: AIRCRAFT.WING.SWEEP | 20.0 (deg) | 20.0 (deg) | Equal |\n", + "\n", + "\n", + "\n", + "In the graph below The Altitude, Drag force, Throttle command, Mass, Distance, and Mach number of the Primary and Deadhead missions are displayed. The Deadhead mission shows a characteristic smaller mass throughout the flight as expected since we have fewer passengers, and a slightly lower throttle profile to match, indicating the engine is not being pushed as hard to meet the demands of a lighter plane. Otherwise the missions themselves match, showing Mach, Distance, and Altitude all identical for every part of the mission. We did not allow the mach or altitude to be optimized for this mission so these results are not surprising. \n", + "\n", + "![Results](images/multi_mission.png \"Primary vs. Deadhead mission results\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "latest_env", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb b/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb index e346a650b..c33d2dbdf 100644 --- a/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb +++ b/aviary/docs/user_guide/FLOPS_based_detailed_takeoff_and_landing.ipynb @@ -38,13 +38,25 @@ "execution_count": null, "id": "5fe75e1d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mRunning cells with 'base (Python 3.12.5)' requires the ipykernel package.\n", + "\u001b[1;31mRun the following command to install 'ipykernel' into the Python environment. \n", + "\u001b[1;31mCommand: 'conda install -n base ipykernel --update-deps --force-reinstall'" + ] + } + ], "source": [ "from aviary.api import Dynamic, Mission\n", "\n", "import aviary.api as av\n", "\n", "from aviary.models.N3CC.N3CC_data import inputs\n", + "from aviary.utils.preprocessors import preprocess_options\n", "\n", "aviary_options = inputs.deepcopy()\n", "\n", @@ -82,6 +94,7 @@ "# We also need propulsion analysis for takeoff and landing. No additional configuration\n", "# is needed for this builder\n", "engine = av.build_engine_deck(aviary_options)\n", + "preprocess_options(aviary_options, engine_models=engine)\n", "prop_builder = av.CorePropulsionBuilder(engine_models=engine)" ] }, @@ -633,7 +646,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "base", "language": "python", "name": "python3" }, @@ -647,7 +660,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.12.5" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide/multi_mission.ipynb b/aviary/docs/user_guide/multi_mission.ipynb new file mode 100644 index 000000000..d05b50796 --- /dev/null +++ b/aviary/docs/user_guide/multi_mission.ipynb @@ -0,0 +1,83 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "remove-cell" + ] + }, + "outputs": [ + { + "ename": "", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[1;31mRunning cells with 'base (Python 3.12.5)' requires the ipykernel package.\n", + "\u001b[1;31mRun the following command to install 'ipykernel' into the Python environment. \n", + "\u001b[1;31mCommand: 'conda install -n base ipykernel --update-deps --force-reinstall'" + ] + } + ], + "source": [ + "# Testing Cell\n", + "\n", + "from aviary.docs.tests.utils import glue_variable\n", + "from aviary.api import Aircraft, AviaryProblem\n", + "\n", + "glue_variable(f'{Aircraft.CrewPayload.Design.NUM_PASSENGERS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_PASSENGERS=}'.split('=')[0], md_code=True)\n", + "\n", + "glue_variable('capi',AviaryProblem.check_and_preprocess_inputs.__name__+'()',md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_BUSINESS_CLASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_FIRST_CLASS=}'.split('=')[0], md_code=True)\n", + "glue_variable(f'{Aircraft.CrewPayload.NUM_TOURIST_CLASS=}'.split('=')[0], md_code=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multi-Mission Optimization\n", + "\n", + "## Overview\n", + "\n", + "Multi-missions adds the capability to optimize an aircraft design considering two or more missions that the aircraft will perform. This is in contrast to designing an aircraft for a single mission, and hopefully helps to represent the conditions the aircraft will be flying in better. In the example provided, a large single aisle passenger aircraft is designed based on a mission with every seat filled, and a second 'deadhead' mission where there are no passengers. A weighting is provided by the user to determine how often the missions are full of passengers vs. how often the flights are empty. A cargo example could be constructed where one mission is designed for maximum range and a second mission designed for maximum payload. \n", + "\n", + "To impact the structure of aviaryproblems as little as possible, after instantiation of the pre-mission, mission, and post-mission systems, the connections between those systems are created. Then those groups are then copied over into a regular openmdao problem. This enables the use all the basic aviary connection and checking functions with minimal modification. \n", + "\n", + "The objective function is based on combining the fuel-burn values from both missions and the optimizers objective is to minimize the weighted fuel-burn. Other objectives, like max range, have not been tested yet.\n", + "\n", + "## Design vs. As-Flown\n", + "To support the need to design an aircraft with a certain number of seats, but then possibly fly missions with unfilled seats, a distinction in the metadata was introduced between {glue:md}`Aircraft.CrewPayload.Design.NUM_PASSENGERS` and {glue:md}`Aircraft.CrewPayload.NUM_PASSENGERS`. The individual passenger classes ({glue:md}`Aircraft.CrewPayload.NUM_FIRST_CLASS`, {glue:md}`Aircraft.CrewPayload.NUM_BUSINESS_CLASS`, {glue:md}`Aircraft.CrewPayload.NUM_TOURIST_CLASS`) also have these distinctions. The Design values represent how many seats are available in the aircraft. Whereas the non-design values represent an as-flow value of how many passengers are on a particular flight. \n", + "\n", + "A number of checks exist in {glue:md}`capi` to help the user in the case that incomplete as-flow or design passenger information is provided. This was done to provide backward compatability for older aircraft models.\n", + "\n", + "## Example\n", + "An example of a multi-mission as well as Setup, Theory, and Results, is presented in [Multi-Mission Examples](../examples/multi_mission)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/aviary/examples/multi_mission/run_multimission_example_large_single_aisle.py b/aviary/examples/multi_mission/run_multimission_example_large_single_aisle.py new file mode 100644 index 000000000..b0316bf8b --- /dev/null +++ b/aviary/examples/multi_mission/run_multimission_example_large_single_aisle.py @@ -0,0 +1,328 @@ +""" +authors: Jatin Soni, Eliot Aretskin +Multi Mission Optimization Example using Aviary + +In this example, Two seperate aviary problems are instantiated using the typical aviaryProblem calls like load_inputs, +check_and_preprocess_payload, etc.. Once those problems are setup and all of their phases are linked together, +we copy those problems as group into a super_problem. We then promote GROSS_MASS, RANGE, and wing SWEEP from each +of those sub-groups (group1 and group2) up to the super_probem so the optimizer can control them. The fuel_burn results +from each of the group1 and group2 dymos missions are summed and weighted to create the objective function the +optimizer sees. + +For the deadhead mission: +aircraft:crew_and_payload:num_passengers,0,unitless +aircraft:crew_and_payload:num_tourist_class,0,unitless +aircraft:crew_and_payload:num_first_class,0,unitless + +""" +import copy as copy +from aviary.examples.example_phase_info import phase_info +from aviary.variable_info.variables import Mission, Aircraft, Settings +from aviary.variable_info.enums import ProblemType +import aviary.api as av +import openmdao.api as om +import matplotlib.pyplot as plt +from os.path import join +import numpy as np +import dymos as dm +import warnings +import sys +from aviary.subsystems.mass.flops_based.furnishings import TransportFurnishingsGroupMass +from aviary.api import SubsystemBuilderBase +from aviary.validation_cases.validation_tests import get_flops_inputs + +# fly the same mission twice with two different passenger loads +phase_info_primary = copy.deepcopy(phase_info) +phase_info_deadhead = copy.deepcopy(phase_info) +# get large single aisle values +aviary_inputs_primary = get_flops_inputs('LargeSingleAisle2FLOPS') +aviary_inputs_primary.set_val(Mission.Design.GROSS_MASS, val=100000, units='lbm') +aviary_inputs_primary.set_val(Settings.VERBOSITY, val=1) + +aviary_inputs_deadhead = copy.deepcopy(aviary_inputs_primary) +aviary_inputs_deadhead.set_val('aircraft:crew_and_payload:num_passengers', 1, 'unitless') +aviary_inputs_deadhead.set_val( + 'aircraft:crew_and_payload:num_tourist_class', 1, 'unitless') +aviary_inputs_deadhead.set_val( + 'aircraft:crew_and_payload:num_business_class', 0, 'unitless') +aviary_inputs_deadhead.set_val( + 'aircraft:crew_and_payload:num_first_class', 0, 'unitless') + +Optimizer = 'SLSQP' # SLSQP or SNOPT + + +class MultiMissionProblem(om.Problem): + def __init__(self, aviary_values, phase_infos, weights): + super().__init__() + self.num_missions = len(aviary_values) + # phase infos and aviary_values length must match - this maybe unnecessary if + # different aviary_values (payloads) fly same mission (say pax vs cargo) + # or if same payload flies 2 different missions (altitude/mach differences) + if self.num_missions != len(phase_infos): + raise Exception("Length of aviary_values and phase_infos must be the same!") + + # if fewer weights than aviary_values are provided, assign equal weights for all aviary_values + if len(weights) < self.num_missions: + weights = [1]*self.num_missions + # if more weights than aviary_values, raise exception + elif len(weights) > self.num_missions: + raise Exception("Length of weights cannot exceed length of aviary_values!") + self.weights = weights + self.phase_infos = phase_infos + + self.group_prefix = 'group' + self.probs = [] + self.fuel_vars = [] + self.phases = {} + # define individual aviary problems + for i, (aviary_values, phase_info) in enumerate(zip(aviary_values, phase_infos)): + prob = av.AviaryProblem() + prob.load_inputs(aviary_values, phase_info) + prob.check_and_preprocess_inputs() + prob.add_pre_mission_systems() + prob.add_phases() + prob.add_post_mission_systems() + prob.link_phases() + + # alternate prevents use of equality constraint b/w design and summary gross mass + prob.problem_type = ProblemType.MULTI_MISSION + prob.add_design_variables() + self.probs.append(prob) + # phase names for each traj (can be used later to make plots/print outputs) + self.phases[f"{self.group_prefix}_{i}"] = list(prob.traj._phases.keys()) + + # design range and gross mass are promoted, these are Max Range/Max Takeoff Mass + # and must be the same for each aviary problem. Subsystems within aviary are sized + # using these - empty mass is same across all aviary problems. + # the fuel objective is also promoted since that's used in the compound objective + promoted_name = f"{self.group_prefix}_{i}_fuelobj" + self.fuel_vars.append(promoted_name) + self.model.add_subsystem( + self.group_prefix + f'_{i}', prob.model, + promotes_inputs=[Mission.Design.GROSS_MASS, + Mission.Design.RANGE, + Aircraft.Wing.SWEEP], + promotes_outputs=[(Mission.Summary.FUEL_BURNED, promoted_name)]) + + def add_design_variables(self): + self.model.add_design_var(Mission.Design.GROSS_MASS, + lower=10., upper=900e3, units='lbm') + self.model.add_design_var(Aircraft.Wing.SWEEP, lower=20., upper=35., units='deg') + + def add_driver(self): + self.driver = om.pyOptSparseDriver() + if Optimizer == "SLSQP": + self.driver.options["optimizer"] = "SLSQP" + elif Optimizer == "SNOPT": + self.driver.options["optimizer"] = "SNOPT" + self.driver.opt_settings["Major optimality tolerance"] = 1e-7 + self.driver.opt_settings["Major feasibility tolerance"] = 1e-7 + # self.driver.opt_settings["Major iterations"] = 0 + self.driver.opt_settings["iSumm"] = 6 + self.driver.opt_settings["iPrint"] = 9 + self.driver.opt_settings['Verify level'] = -1 + self.driver.opt_settings["Nonderivative linesearch"] = None + self.driver.declare_coloring() + # linear solver causes nan entry error for landing to takeoff mass ratio param + # self.model.linear_solver = om.DirectSolver() + + def add_objective(self): + # weights are normalized - e.g. for given weights 3:1, the normalized + # weights are 0.75:0.25 + weights = [float(weight/sum(self.weights)) for weight in self.weights] + weighted_str = "+".join([f"{fuelobj}*{weight}" + for fuelobj, weight in zip(self.fuel_vars, weights)]) + # weighted_str looks like: fuel_0 * weight[0] + fuel_1 * weight[1] + # note that the fuel objective itself is the base aviary fuel objective + # which is also a function of climb time becuse climb is not very sensitive to fuel + + # adding compound execComp to super problem + self.model.add_subsystem('compound_fuel_burn_objective', om.ExecComp( + "compound = "+weighted_str, has_diag_partials=True), + promotes_inputs=self.fuel_vars, + promotes_outputs=["compound"]) + self.model.add_objective('compound') + + def setup_wrapper(self): + """Wrapper for om.Problem setup with warning ignoring and setting options""" + for prob in self.probs: + prob.model.options['aviary_options'] = prob.aviary_inputs + prob.model.options['aviary_metadata'] = prob.meta_data + prob.model.options['phase_info'] = prob.phase_info + + # Aviary's problem setup wrapper uses these ignored warnings to suppress + # some warnings related to variable promotion. Replicating that here with + # setup for the super problem + with warnings.catch_warnings(): + warnings.simplefilter("ignore", om.OpenMDAOWarning) + warnings.simplefilter("ignore", om.PromotionWarning) + self.setup(check='all') + + def run(self): + self.model.set_solver_print(0) + dm.run_problem(self, make_plots=False) + + def get_design_range(self): + """Finds the longest mission and sets its range as the design range for all + Aviary problems. Used within Aviary for sizing subsystems (avionics and AC).""" + design_range = [] + for phase_info in self.phase_infos: + design_range.append(phase_info['post_mission'] + ['target_range'][0]) # TBD add units + design_range_min = np.min(design_range) + design_range_max = np.max(design_range) + return design_range_max, design_range_min # design_range_min + + def create_timeseries_plots(self, plotvars=[], show=True): + """ + Temporary create plots manually because graphing won't work for dual-trajectories. + Creates timeseries plots for any variables within timeseries. Specify variables + and units by setting plotvars = [('altitude','ft')]. Any number of vars can be added. + """ + plt.figure() + for plotidx, (var, unit) in enumerate(plotvars): + plt.subplot(int(np.ceil(len(plotvars)/2)), 2, plotidx+1) + for i in range(self.num_missions): + time = np.array([]) + yvar = np.array([]) + # this loop concatenates data from all phases + for phase in self.phases[f"{self.group_prefix}_{i}"]: + rawt = self.get_val( + f"{self.group_prefix}_{i}.traj.{phase}.timeseries.time", + units='s') + rawy = self.get_val( + f"{self.group_prefix}_{i}.traj.{phase}.timeseries.{var}", + units=unit) + time = np.hstack([time, np.ndarray.flatten(rawt)]) + yvar = np.hstack([yvar, np.ndarray.flatten(rawy)]) + plt.plot(time, yvar, linewidth=self.num_missions-i) + plt.xlabel("Time (s)") + plt.ylabel(f"{var.title()} ({unit})") + plt.grid() + plt.figlegend([f"Mission {i}" for i in range(self.num_missions)]) + if show: + plt.show() + + def create_payload_range_plot(self, show=True): + """Creates payload range diagram for the super problem. Appends a point for max payload + and 0 range. """ + payloads = [] + ranges = [] + for i in range(self.num_missions): + ref = f"{self.group_prefix}_{i}" + payloads.append( + self.get_val( + f"{ref}.{Aircraft.CrewPayload.CARGO_MASS}", units='lbm')) + lastphase = self.phases[ref][-1] + ranges.append( + self.get_val( + f"{ref}.traj.{lastphase}.timeseries.distance", + units='nmi', indices=-1)[0]) + payloads, ranges = zip(*sorted(zip(payloads, ranges))) + payloads, ranges = list(payloads), list(ranges) + payloads.append(payloads[-1]) + ranges.append(0) + plt.figure() + plt.plot(ranges, payloads) + plt.xlabel("Range (nmi)") + plt.ylabel("Payload (lbm)") + plt.grid() + if show: + plt.show() + + def print_vars(self, vars=[]): + """Specify vars with name and unit in a tuple, e.g. vars = [ (Mission.Summary.FUEL_BURNED, 'lbm') ]""" + + print("\n\n=========================\n") + print(f"{'':40}", end=': ') + for i in range(self.num_missions): + name = f"Mission {i}" + print(f"{name:^30}", end='| ') + print() + for var, unit in vars: + varname = f"Variable: {var.replace(':', '.').upper()}" + print(f"{varname:40}", end=": ") + for i in range(self.num_missions): + val = self.get_val(f'group_{i}.{var}', units=unit)[0] + printstatement = f"{val} ({unit})" + print(f"{printstatement:^30}", end="| ") + print() + + +def large_single_aisle_example(makeN2=False): + aviary_values = [aviary_inputs_primary, + aviary_inputs_deadhead] + phase_infos = [phase_info_primary, + phase_info_deadhead] + optalt, optmach = False, False + for phaseinfo in phase_infos: + for key in phaseinfo.keys(): + if "user_options" in phaseinfo[key].keys(): + phaseinfo[key]["user_options"]["optimize_mach"] = optmach + phaseinfo[key]["user_options"]["optimize_altitude"] = optalt + + # how much each mission should be valued by the optimizer, larger numbers = more significance + weights = [9, 1] + + super_prob = MultiMissionProblem(aviary_values, phase_infos, weights) + super_prob.add_driver() + super_prob.add_design_variables() + super_prob.add_objective() + # set input default to prevent error, value doesn't matter since set val is used later + super_prob.model.set_input_defaults(Mission.Design.RANGE, val=1.) + super_prob.setup_wrapper() + super_prob.set_val(Mission.Design.RANGE, super_prob.get_design_range()[0]) + + for i, prob in enumerate(super_prob.probs): + prob.set_initial_guesses(super_prob, super_prob.group_prefix+f"_{i}.") + + if makeN2: + # TODO: Not sure we need this at all. + from openmdao.api import n2 + from os.path import basename, dirname, join, abspath + + def createN2(fileref, prob): + n2folder = join(dirname(abspath(__file__)), "N2s") + n2(prob, outfile=join(n2folder, + f"n2_{basename(fileref).split('.')[0]}.html")) + + createN2(__file__, super_prob) + + super_prob.run() + printoutputs = [ + (Mission.Design.GROSS_MASS, 'lbm'), + (Aircraft.Design.EMPTY_MASS, 'lbm'), + (Mission.Summary.GROSS_MASS, 'lbm'), + (Mission.Summary.FUEL_BURNED, 'lbm'), + (Mission.Summary.TOTAL_FUEL_MASS, 'lbm'), + (Aircraft.LandingGear.MAIN_GEAR_MASS, 'lbm'), + (Aircraft.LandingGear.NOSE_GEAR_MASS, 'lbm'), + (Aircraft.Design.LANDING_TO_TAKEOFF_MASS_RATIO, 'unitless'), + (Mission.Summary.CRUISE_MACH, 'unitless'), + (Aircraft.Furnishings.MASS, 'lbm'), + (Aircraft.CrewPayload.PASSENGER_SERVICE_MASS, 'lbm'), + (Aircraft.Wing.SWEEP, 'deg')] + super_prob.print_vars(vars=printoutputs) + + plotvars = [('altitude', 'ft'), + ('mass', 'lbm'), + ('drag', 'lbf'), + ('distance', 'nmi'), + ('throttle', 'unitless'), + ('mach', 'unitless')] + super_prob.create_timeseries_plots(plotvars=plotvars, show=False) + + super_prob.create_payload_range_plot(show=False) + plt.show() + + return super_prob + + +if __name__ == '__main__': + makeN2 = True if (len(sys.argv) > 1 and "n2" in sys.argv[1]) else False + + super_prob = large_single_aisle_example(makeN2=makeN2) + + # Uncomment the following lines to see mass breakdown details for each mission. + # super_prob.model.group_1.list_vars(val=True, units=True, print_arrays=False) + # super_prob.model.group_2.list_vars(val=True, units=True, print_arrays=False) diff --git a/aviary/interface/methods_for_level1.py b/aviary/interface/methods_for_level1.py index 363f58e6c..b93d39c47 100644 --- a/aviary/interface/methods_for_level1.py +++ b/aviary/interface/methods_for_level1.py @@ -101,7 +101,9 @@ def run_aviary(aircraft_filename, phase_info, optimizer=None, prob.set_initial_guesses() prob.run_aviary_problem( - record_filename, restart_filename=restart_filename, run_driver=run_driver, make_plots=make_plots, optimization_history_filename=optimization_history_filename) + record_filename, restart_filename=restart_filename, run_driver=run_driver, + make_plots=make_plots, + optimization_history_filename=optimization_history_filename) return prob @@ -155,9 +157,8 @@ def run_level_1( def _setup_level1_parser(parser): def_outdir = os.path.join(os.getcwd(), "output") - parser.add_argument( - 'input_deck', metavar='indeck', type=str, nargs=1, help='Name of vehicle input deck file' - ) + parser.add_argument('input_deck', metavar='indeck', type=str, + nargs=1, help='Name of vehicle input deck file') parser.add_argument( "-o", "--outdir", default=def_outdir, help="Directory to write outputs" ) diff --git a/aviary/interface/methods_for_level2.py b/aviary/interface/methods_for_level2.py index f1e95ec82..01d141f07 100644 --- a/aviary/interface/methods_for_level2.py +++ b/aviary/interface/methods_for_level2.py @@ -54,7 +54,7 @@ from aviary.utils.functions import create_opts2vals, add_opts2vals, promote_aircraft_and_mission_vars, wrapped_convert_units from aviary.utils.functions import convert_strings_to_data, set_value from aviary.utils.merge_variable_metadata import merge_meta_data -from aviary.utils.preprocessors import preprocess_crewpayload, preprocess_propulsion +from aviary.utils.preprocessors import preprocess_options from aviary.utils.process_input_decks import create_vehicle, update_GASP_options, initialization_guessing from aviary.variable_info.enums import AnalysisScheme, ProblemType, EquationsOfMotion, LegacyCode, Verbosity @@ -62,7 +62,6 @@ from aviary.variable_info.variables import Aircraft, Mission, Dynamic, Settings from aviary.variable_info.variable_meta_data import _MetaData as BaseMetaData - FLOPS = LegacyCode.FLOPS GASP = LegacyCode.GASP @@ -260,7 +259,9 @@ def __init__(self, analysis_scheme=AnalysisScheme.COLLOCATION, **kwargs): self.regular_phases = [] self.reserve_phases = [] - def load_inputs(self, aviary_inputs, phase_info=None, engine_builders=None, meta_data=BaseMetaData, verbosity=Verbosity.BRIEF): + def load_inputs( + self, aviary_inputs, phase_info=None, engine_builders=None, + meta_data=BaseMetaData, verbosity=Verbosity.BRIEF): """ This method loads the aviary_values inputs and options that the user specifies. They could specify files to load and values to @@ -371,8 +372,17 @@ def load_inputs(self, aviary_inputs, phase_info=None, engine_builders=None, meta self.cruise_mass_final = aviary_inputs.get_val( Mission.Summary.CRUISE_MASS_FINAL, units='lbm') - self.target_range = aviary_inputs.get_val( - Mission.Design.RANGE, units='NM') + + if self.post_mission_info is True and 'target_range' in self.post_mission_info: + self.target_range = wrapped_convert_units( + phase_info['post_mission']['target_range'], 'NM') + aviary_inputs.set_val(Mission.Summary.RANGE, + self.target_range, units='NM') + else: + self.target_range = aviary_inputs.get_val( + Mission.Design.RANGE, units='NM') + aviary_inputs.set_val(Mission.Summary.RANGE, aviary_inputs.get_val( + Mission.Design.RANGE, units='NM'), units='NM') self.cruise_mach = aviary_inputs.get_val(Mission.Design.MACH) self.require_range_residual = True @@ -381,14 +391,16 @@ def load_inputs(self, aviary_inputs, phase_info=None, engine_builders=None, meta aviary_inputs.set_val(Mission.Summary.GROSS_MASS, val=self.initialization_guesses['actual_takeoff_mass'], units='lbm') if 'target_range' in self.post_mission_info: - aviary_inputs.set_val(Mission.Design.RANGE, wrapped_convert_units( + aviary_inputs.set_val(Mission.Summary.RANGE, wrapped_convert_units( phase_info['post_mission']['target_range'], 'NM'), units='NM') self.require_range_residual = True + self.target_range = wrapped_convert_units( + phase_info['post_mission']['target_range'], 'NM') else: self.require_range_residual = False - - self.target_range = aviary_inputs.get_val( - Mission.Design.RANGE, units='NM') + # still instantiate target_range because it is used for default guesses for phase comps + self.target_range = aviary_inputs.get_val( + Mission.Design.RANGE, units='NM') return aviary_inputs @@ -457,7 +469,8 @@ def check_and_preprocess_inputs(self): for idx, phase_name in enumerate(self.phase_info): if 'user_options' in self.phase_info[phase_name]: if 'target_distance' in self.phase_info[phase_name]["user_options"]: - target_distance = self.phase_info[phase_name]["user_options"]["target_distance"] + target_distance = self.phase_info[phase_name]["user_options"][ + "target_distance"] if target_distance[0] <= 0: raise ValueError( f"Invalid target_distance in [{phase_name}].[user_options]. " @@ -473,16 +486,20 @@ def check_and_preprocess_inputs(self): if (self.analysis_scheme is AnalysisScheme.COLLOCATION) and (self.mission_method is EquationsOfMotion.TWO_DEGREES_OF_FREEDOM): try: # if the user provided an option, use it - analytic = self.phase_info[phase_name]["user_options"]['analytic'] + analytic = self.phase_info[phase_name]["user_options"][ + 'analytic'] except KeyError: # if it isn't specified, only the default 2DOF cruise for collocation is analytic if 'cruise' in phase_name: - analytic = self.phase_info[phase_name]["user_options"]['analytic'] = True + analytic = self.phase_info[phase_name]["user_options"][ + 'analytic'] = True else: - analytic = self.phase_info[phase_name]["user_options"]['analytic'] = False + analytic = self.phase_info[phase_name]["user_options"][ + 'analytic'] = False if 'target_duration' in self.phase_info[phase_name]["user_options"]: - target_duration = self.phase_info[phase_name]["user_options"]["target_duration"] + target_duration = self.phase_info[phase_name]["user_options"][ + "target_duration"] if target_duration[0] <= 0: raise ValueError( f'Invalid target_duration in phase_info[{phase_name}]' @@ -495,8 +512,8 @@ def check_and_preprocess_inputs(self): # Set duration_bounds and initial_guesses for time: self.phase_info[phase_name]["user_options"].update({ "duration_bounds": ((target_duration[0], target_duration[0]), target_duration[1])}) - self.phase_info[phase_name].update({ - "initial_guesses": {"time": ((target_duration[0], target_duration[0]), target_duration[1])}}) + self.phase_info[phase_name].update({"initial_guesses": {"time": ( + (target_duration[0], target_duration[0]), target_duration[1])}}) # Set Fixed_duration to true: self.phase_info[phase_name]["user_options"].update({ "fix_duration": True}) @@ -511,8 +528,7 @@ def check_and_preprocess_inputs(self): # PREPROCESSORS # # Fill in anything missing in the options with computed defaults. - preprocess_propulsion(aviary_inputs, self.engine_builders) - preprocess_crewpayload(aviary_inputs) + preprocess_options(aviary_inputs, engine_models=self.engine_builders) mission_method = aviary_inputs.get_val(Settings.EQUATIONS_OF_MOTION) mass_method = aviary_inputs.get_val(Settings.MASS_METHOD) @@ -602,8 +618,9 @@ def add_pre_mission_systems(self): # Propulsion isn't included in core pre-mission group to avoid override step in # configure() - instead add it now - pre_mission.add_subsystem('core_propulsion', - subsystems['propulsion'].build_pre_mission(self.aviary_inputs),) + pre_mission.add_subsystem( + 'core_propulsion', subsystems['propulsion'].build_pre_mission( + self.aviary_inputs),) default_subsystems = [subsystems['geometry'], subsystems['aerodynamics'], @@ -779,14 +796,16 @@ def _add_premission_external_subsystems(self): # Define the expression for computing the sum of masses expr = 'subsystem_mass = ' + ' + '.join(formatted_names) - promotes_inputs_list = [(formatted_name, original_name) - for formatted_name, original_name in zip(formatted_names, mass_names)] + promotes_inputs_list = [ + (formatted_name, original_name) for formatted_name, + original_name in zip(formatted_names, mass_names)] # Create the ExecComp - self.pre_mission.add_subsystem('external_comp_sum', om.ExecComp(expr, units='kg'), - promotes_inputs=promotes_inputs_list, - promotes_outputs=[ - ('subsystem_mass', Aircraft.Design.EXTERNAL_SUBSYSTEMS_MASS)]) + self.pre_mission.add_subsystem( + 'external_comp_sum', om.ExecComp(expr, units='kg'), + promotes_inputs=promotes_inputs_list, + promotes_outputs=[('subsystem_mass', Aircraft.Design. + EXTERNAL_SUBSYSTEMS_MASS)]) def _add_groundroll_eq_constraint(self): """ @@ -866,7 +885,8 @@ def _get_phase(self, phase_name, phase_idx): phase_builder = TwoDOFPhase phase_object = phase_builder.from_phase_info( - phase_name, phase_options, default_mission_subsystems, meta_data=self.meta_data) + phase_name, phase_options, default_mission_subsystems, + meta_data=self.meta_data) phase = phase_object.build_phase(aviary_options=self.aviary_inputs) @@ -996,9 +1016,8 @@ def _get_phase(self, phase_name, phase_idx): if 'cruise' not in phase_name and self.mission_method is TWO_DEGREES_OF_FREEDOM: phase.add_control( - Dynamic.Mission.THROTTLE, targets=Dynamic.Mission.THROTTLE, units='unitless', - opt=False, - ) + Dynamic.Mission.THROTTLE, targets=Dynamic.Mission.THROTTLE, + units='unitless', opt=False,) return phase @@ -1017,9 +1036,8 @@ def add_phases(self, phase_info_parameterization=None): traj: The Dymos Trajectory object containing the added mission phases. """ if phase_info_parameterization is not None: - self.phase_info, self.post_mission_info = phase_info_parameterization(self.phase_info, - self.post_mission_info, - self.aviary_inputs) + self.phase_info, self.post_mission_info = phase_info_parameterization( + self.phase_info, self.post_mission_info, self.aviary_inputs) phase_info = self.phase_info @@ -1059,12 +1077,11 @@ def add_phases(self, phase_info_parameterization=None): ('altitude_initial', Mission.Design.CRUISE_ALTITUDE)]) self.model.add_subsystem( - 'actual_descent_fuel', - om.ExecComp('actual_descent_fuel = traj_cruise_mass_final - traj_mass_final', - actual_descent_fuel={'units': 'lbm'}, - traj_cruise_mass_final={'units': 'lbm'}, - traj_mass_final={'units': 'lbm'}, - )) + 'actual_descent_fuel', om.ExecComp( + 'actual_descent_fuel = traj_cruise_mass_final - traj_mass_final', + actual_descent_fuel={'units': 'lbm'}, + traj_cruise_mass_final={'units': 'lbm'}, + traj_mass_final={'units': 'lbm'},)) self.model.connect('start_of_descent_mass', 'traj.SGMCruise_mass_trigger') self.model.connect( @@ -1197,8 +1214,9 @@ def add_post_mission_systems(self, include_landing=True): mass_final={'units': 'lbm'}, fuel_burned={'units': 'lbm'}) - self.post_mission.add_subsystem('fuel_burned', ecomp, - promotes=[('fuel_burned', Mission.Summary.FUEL_BURNED)]) + self.post_mission.add_subsystem( + 'fuel_burned', ecomp, + promotes=[('fuel_burned', Mission.Summary.FUEL_BURNED)]) if self.analysis_scheme is AnalysisScheme.SHOOTING: # shooting method currently doesn't have timeseries @@ -1226,22 +1244,25 @@ def add_post_mission_systems(self, include_landing=True): mass_final={'units': 'lbm'}, reserve_fuel_burned={'units': 'lbm'}) - self.post_mission.add_subsystem('reserve_fuel_burned', ecomp, - promotes=[('reserve_fuel_burned', Mission.Summary.RESERVE_FUEL_BURNED)]) + self.post_mission.add_subsystem('reserve_fuel_burned', ecomp, promotes=[ + ('reserve_fuel_burned', Mission.Summary.RESERVE_FUEL_BURNED)]) if self.analysis_scheme is AnalysisScheme.SHOOTING: # shooting method currently doesn't have timeseries self.post_mission.promotes('reserve_fuel_burned', [ ('initial_mass', Mission.Landing.TOUCHDOWN_MASS), ]) - self.model.connect(f"traj.{self.reserve_phases[-1]}.states:mass", - "reserve_fuel_burned.mass_final", src_indices=[-1]) + self.model.connect( + f"traj.{self.reserve_phases[-1]}.states:mass", + "reserve_fuel_burned.mass_final", src_indices=[-1]) else: # timeseries has to be used because Breguet cruise phases don't have states - self.model.connect(f"traj.{self.reserve_phases[0]}.timeseries.mass", - "reserve_fuel_burned.initial_mass", src_indices=[0]) - self.model.connect(f"traj.{self.reserve_phases[-1]}.timeseries.mass", - "reserve_fuel_burned.mass_final", src_indices=[-1]) + self.model.connect( + f"traj.{self.reserve_phases[0]}.timeseries.mass", + "reserve_fuel_burned.initial_mass", src_indices=[0]) + self.model.connect( + f"traj.{self.reserve_phases[-1]}.timeseries.mass", + "reserve_fuel_burned.mass_final", src_indices=[-1]) self._add_fuel_reserve_component() @@ -1268,22 +1289,24 @@ def add_post_mission_systems(self, include_landing=True): for phase_name in self.phase_info: if 'target_distance' in self.phase_info[phase_name]["user_options"]: target_distance = wrapped_convert_units( - self.phase_info[phase_name]["user_options"]["target_distance"], 'nmi') + self.phase_info[phase_name]["user_options"] + ["target_distance"], + 'nmi') self.post_mission.add_subsystem( - f"{phase_name}_distance_constraint", - om.ExecComp( + f"{phase_name}_distance_constraint", om.ExecComp( "distance_resid = target_distance - (final_distance - initial_distance)", distance_resid={'units': 'nmi'}, target_distance={'val': target_distance, 'units': 'nmi'}, final_distance={'units': 'nmi'}, - initial_distance={'units': 'nmi'}, - )) + initial_distance={'units': 'nmi'},)) self.model.connect( f"traj.{phase_name}.timeseries.distance", - f"{phase_name}_distance_constraint.final_distance", src_indices=[-1]) + f"{phase_name}_distance_constraint.final_distance", + src_indices=[-1]) self.model.connect( f"traj.{phase_name}.timeseries.distance", - f"{phase_name}_distance_constraint.initial_distance", src_indices=[0]) + f"{phase_name}_distance_constraint.initial_distance", + src_indices=[0]) self.model.add_constraint( f"{phase_name}_distance_constraint.distance_resid", equals=0.0, ref=1e2) @@ -1291,22 +1314,23 @@ def add_post_mission_systems(self, include_landing=True): if 'target_duration' in self.phase_info[phase_name]["user_options"] and \ self.phase_info[phase_name]["user_options"].get("analytic", False): target_duration = wrapped_convert_units( - self.phase_info[phase_name]["user_options"]["target_duration"], 'min') + self.phase_info[phase_name]["user_options"] + ["target_duration"], + 'min') self.post_mission.add_subsystem( - f"{phase_name}_duration_constraint", - om.ExecComp( + f"{phase_name}_duration_constraint", om.ExecComp( "duration_resid = target_duration - (final_time - initial_time)", duration_resid={'units': 'min'}, target_duration={'val': target_duration, 'units': 'min'}, final_time={'units': 'min'}, - initial_time={'units': 'min'}, - )) + initial_time={'units': 'min'},)) self.model.connect( f"traj.{phase_name}.timeseries.time", f"{phase_name}_duration_constraint.final_time", src_indices=[-1]) self.model.connect( f"traj.{phase_name}.timeseries.time", - f"{phase_name}_duration_constraint.initial_time", src_indices=[0]) + f"{phase_name}_duration_constraint.initial_time", + src_indices=[0]) self.model.add_constraint( f"{phase_name}_duration_constraint.duration_resid", equals=0.0, ref=1e2) @@ -1341,7 +1365,7 @@ def add_post_mission_systems(self, include_landing=True): Mission.Constraints.MASS_RESIDUAL, equals=0.0, ref=1.e5) if self.mission_method is HEIGHT_ENERGY: - + # connect summary mass to the initial guess of mass in the first phase if not self.pre_mission_info['include_takeoff']: first_flight_phase_name = list(self.phase_info.keys())[0] eq = self.model.add_subsystem(f'link_{first_flight_phase_name}_mass', @@ -1474,13 +1498,14 @@ def link_phases(self): connected=true_unless_mpi) self.model.connect(f'traj.{self.regular_phases[-1]}.timeseries.distance', - Mission.Summary.RANGE, + 'actual_range', src_indices=[-1], flat_src_indices=True) elif self.mission_method is SOLVED_2DOF: self.traj.link_phases(phases, [Dynamic.Mission.MASS], connected=True) self.traj.link_phases( - phases, [Dynamic.Mission.DISTANCE], units='ft', ref=1.e3, connected=False) + phases, [Dynamic.Mission.DISTANCE], + units='ft', ref=1.e3, connected=False) self.traj.link_phases(phases, ["time"], connected=False) if len(phases) > 2: @@ -1513,7 +1538,10 @@ def link_phases(self): # if either phase is rotation, we need to connect velocity # ascent to accel also requires velocity - if 'rotation' in (phase1, phase2) or ('ascent', 'accel') == (phase1, phase2): + if 'rotation' in ( + phase1, phase2) or ( + 'ascent', 'accel') == ( + phase1, phase2): states_to_link[Dynamic.Mission.VELOCITY] = true_unless_mpi # if the first phase is rotation, we also need alpha if phase1 == 'rotation': @@ -1546,7 +1574,8 @@ def link_phases(self): self.traj.add_linkage_constraint( phase1, phase2, 'time', prefix+'time', connected=True) self.traj.add_linkage_constraint( - phase1, phase2, 'distance', prefix+'distance', connected=True) + phase1, phase2, 'distance', prefix + 'distance', + connected=True) self.traj.add_linkage_constraint( phase1, phase2, 'mass', 'mass', connected=False, ref=1.0e5) @@ -1592,14 +1621,14 @@ def link_phases(self): Mission.Landing.TOUCHDOWN_MASS, src_indices=[-1]) connect_map = { - f"traj.{self.regular_phases[-1]}.timeseries.distance": Mission.Summary.RANGE, + f"traj.{self.regular_phases[-1]}.timeseries.distance": 'actual_range', } else: connect_map = { "taxi.mass": "traj.mass_initial", Mission.Takeoff.ROTATION_VELOCITY: "traj.SGMGroundroll_velocity_trigger", - "traj.distance_final": Mission.Summary.RANGE, + "traj.distance_final": 'actual_range', "traj.mass_final": Mission.Landing.TOUCHDOWN_MASS, } @@ -1626,7 +1655,9 @@ def connect_with_common_params(self, source, target): for source, target in connect_map.items(): connect_with_common_params(self, source, target) - def add_driver(self, optimizer=None, use_coloring=None, max_iter=50, verbosity=Verbosity.BRIEF): + def add_driver( + self, optimizer=None, use_coloring=None, max_iter=50, + verbosity=Verbosity.BRIEF): """ Add an optimization driver to the Aviary problem. @@ -1812,8 +1843,8 @@ def add_design_variables(self): elif self.problem_type is ProblemType.ALTERNATE: self.model.add_design_var( Mission.Summary.GROSS_MASS, - lower=0, - upper=None, + lower=10., + upper=900e3, units='lbm', ref=175e3, ) @@ -1825,6 +1856,42 @@ def add_design_variables(self): elif self.problem_type is ProblemType.FALLOUT: print('No design variables for Fallout missions') + elif self.problem_type is ProblemType.MULTI_MISSION: + self.model.add_design_var( + Mission.Summary.GROSS_MASS, + lower=10., + upper=900e3, + units='lbm', + ref=175e3, + ) + + self.model.add_constraint( + Mission.Constraints.RANGE_RESIDUAL, equals=0, ref=10 + ) + + # We must ensure that design.gross_mass is greater than mission.summary.gross_mass + # and this must hold true for each of the different missions that is flown + # the result will be the design.gross_mass should be equal to the mission.summary.gross_mass + # of the heaviest mission + self.model.add_subsystem( + "GROSS_MASS_constraint", + om.ExecComp( + "gross_mass_resid = design_mass - actual_mass", + design_mass={"val": 1, "units": "kg"}, + actual_mass={"val": 0, "units": "kg"}, + gross_mass_resid={"val": 30, "units": "kg"}, + ), + promotes_inputs=[ + ("design_mass", Mission.Design.GROSS_MASS), + ("actual_mass", Mission.Summary.GROSS_MASS), + ], + promotes_outputs=["gross_mass_resid"], + ) + + self.model.add_constraint( + "gross_mass_resid", lower=0 + ) + if self.mission_method is TWO_DEGREES_OF_FREEDOM and self.analysis_scheme is AnalysisScheme.COLLOCATION: # problem formulation to make the trajectory work self.model.add_design_var(Mission.Takeoff.ASCENT_T_INTIIAL, @@ -1878,7 +1945,8 @@ def add_objective(self, objective_type=None, ref=None): if objective_type == 'mass': if self.analysis_scheme is AnalysisScheme.COLLOCATION: self.model.add_objective( - f"traj.{final_phase_name}.timeseries.{Dynamic.Mission.MASS}", index=-1, ref=ref) + f"traj.{final_phase_name}.timeseries.{Dynamic.Mission.MASS}", + index=-1, ref=ref) else: last_phase = self.traj._phases.items()[final_phase_name] last_phase.add_objective( @@ -1894,8 +1962,9 @@ def add_objective(self, objective_type=None, ref=None): elif objective_type == "fuel": self.model.add_objective(Mission.Objectives.FUEL, ref=ref) else: - raise ValueError(f"{objective_type} is not a valid objective.\nobjective_type must" - " be one of mass, time, hybrid_objective, fuel_burned, or fuel") + raise ValueError( + f"{objective_type} is not a valid objective.\nobjective_type must" + " be one of mass, time, hybrid_objective, fuel_burned, or fuel") else: # If no 'objective_type' is specified, we handle based on 'problem_type' # If 'ref' is not specified, assign a default value @@ -1978,12 +2047,11 @@ def setup(self, **kwargs): warnings.simplefilter("ignore", om.PromotionWarning) super().setup(**kwargs) - def set_initial_guesses(self): + def set_initial_guesses(self, parent_prob=None, parent_prefix=""): """ Call `set_val` on the trajectory for states and controls to seed the problem with reasonable initial guesses. This is especially important for collocation methods. - This method first identifies all phases in the trajectory then loops over each phase. Specific initial guesses are added depending on the phase and mission method. Cruise is treated @@ -1993,14 +2061,17 @@ def set_initial_guesses(self): guesses for states and controls according to the information available in the 'initial_guesses' attribute of the phase. """ + setvalprob = self + if parent_prob is not None and parent_prefix != "": + setvalprob = parent_prob # Grab the trajectory object from the model if self.analysis_scheme is AnalysisScheme.SHOOTING: if self.problem_type is ProblemType.SIZING: - self.set_val(Mission.Summary.GROSS_MASS, - self.get_val(Mission.Design.GROSS_MASS)) + setvalprob.set_val(parent_prefix+Mission.Summary.GROSS_MASS, + self.get_val(Mission.Design.GROSS_MASS)) - self.set_val("traj.SGMClimb_"+Dynamic.Mission.ALTITUDE + - "_trigger", val=self.cruise_alt, units="ft") + setvalprob.set_val(parent_prefix+"traj.SGMClimb_"+Dynamic.Mission.ALTITUDE + + "_trigger", val=self.cruise_alt, units="ft") return @@ -2031,37 +2102,35 @@ def set_initial_guesses(self): if 'mass' == guess_key: # Set initial and duration mass for the analytic cruise phase. # Note we are integrating over mass, not time for this phase. - self.set_val(f'traj.{phase_name}.t_initial', - val[0], units=units) - self.set_val(f'traj.{phase_name}.t_duration', - val[1], units=units) + setvalprob.set_val(parent_prefix+f'traj.{phase_name}.t_initial', + val[0], units=units) + setvalprob.set_val(parent_prefix+f'traj.{phase_name}.t_duration', + val[1], units=units) else: # Otherwise, set the value of the parameter in the trajectory phase - self.set_val(f'traj.{phase_name}.parameters:{guess_key}', - val, units=units) + setvalprob.set_val( + parent_prefix + f'traj.{phase_name}.parameters:{guess_key}', + val, units=units) continue # If not cruise and GASP, add subsystem guesses - self._add_subsystem_guesses(phase_name, phase) + self._add_subsystem_guesses(phase_name, phase, setvalprob, parent_prefix) # Set initial guesses for states and controls for each phase - self._add_guesses(phase_name, phase, guesses) + self._add_guesses(phase_name, phase, guesses, setvalprob, parent_prefix) def _process_guess_var(self, val, key, phase): """ Process the guess variable, which can either be a float or an array of floats. - This method is responsible for interpolating initial guesses when the user provides a list or array of values rather than a single float. It interpolates the guess values across the phase's domain for a given variable, be it a control or a state variable. The interpolation is performed between -1 and 1 (representing the normalized phase time domain), using the numpy linspace function. - The result of this method is a single value or an array of interpolated values that can be used to seed the optimization problem with initial guesses. - Parameters ---------- val : float or list/array of floats @@ -2070,13 +2139,11 @@ def _process_guess_var(self, val, key, phase): The key identifying the variable for which the initial guess is provided. phase : Phase The phase for which the variable is being set. - Returns ------- val : float or array of floats The processed guess value(s) to be used in the optimization problem. """ - # Check if val is not a single float if not isinstance(val, float): # If val is an array of values @@ -2102,16 +2169,14 @@ def _process_guess_var(self, val, key, phase): # Return the processed guess value(s) return val - def _add_subsystem_guesses(self, phase_name, phase): + def _add_subsystem_guesses(self, phase_name, phase, setvalprob, parent_prefix): """ Adds the initial guesses for each subsystem of a given phase to the problem. - This method first fetches all subsystems associated with the given phase. It then loops over each subsystem and fetches its initial guesses. For each guess, it identifies whether the guess corresponds to a state or a control variable and then processes the guess variable. After this, the initial guess is set in the problem using the `set_val` method. - Parameters ---------- phase_name : str @@ -2142,18 +2207,17 @@ def _add_subsystem_guesses(self, phase_name, phase): val['val'] = self._process_guess_var(val['val'], key, phase) # Set the initial guess in the problem - self.set_val(f'traj.{phase_name}.{path_string}:{key}', **val) + setvalprob.set_val( + parent_prefix+f'traj.{phase_name}.{path_string}:{key}', **val) - def _add_guesses(self, phase_name, phase, guesses): + def _add_guesses(self, phase_name, phase, guesses, setvalprob, parent_prefix): """ Adds the initial guesses for each variable of a given phase to the problem. - This method sets the initial guesses for time, control, state, and problem-specific variables for a given phase. If using the GASP model, it also handles some special cases that are not covered in the `phase_info` object. These include initial guesses for mass, time, and distance, which are determined based on the phase name and other mission-related variables. - Parameters ---------- phase_name : str @@ -2163,7 +2227,6 @@ def _add_guesses(self, phase_name, phase, guesses): guesses : dict A dictionary containing the initial guesses for the phase. """ - # If using the GASP model, set initial guesses for the rotation mass and flight duration if self.mission_method is TWO_DEGREES_OF_FREEDOM: rotation_mass = self.initialization_guesses['rotation_mass'] @@ -2174,8 +2237,8 @@ def _add_guesses(self, phase_name, phase, guesses): state_keys = ["mass", Dynamic.Mission.DISTANCE] else: control_keys = ["velocity_rate", "throttle"] - state_keys = ["altitude", "mass", - Dynamic.Mission.DISTANCE, Dynamic.Mission.VELOCITY, "flight_path_angle", "alpha"] + state_keys = ["altitude", "mass", Dynamic.Mission.DISTANCE, + Dynamic.Mission.VELOCITY, "flight_path_angle", "alpha"] if self.mission_method is TWO_DEGREES_OF_FREEDOM and phase_name == 'ascent': # Alpha is a control for ascent. control_keys.append('alpha') @@ -2206,8 +2269,10 @@ def _add_guesses(self, phase_name, phase, guesses): # if time not in initial guesses, set it to the average of the initial_bounds and the duration_bounds if 'time' not in guesses: - initial_bounds = self.phase_info[phase_name]['user_options']['initial_bounds'] - duration_bounds = self.phase_info[phase_name]['user_options']['duration_bounds'] + initial_bounds = self.phase_info[phase_name]['user_options'][ + 'initial_bounds'] + duration_bounds = self.phase_info[phase_name]['user_options'][ + 'duration_bounds'] # Add a check for the initial and duration bounds, raise an error if they are not consistent if initial_bounds[1] != duration_bounds[1]: raise ValueError( @@ -2220,24 +2285,33 @@ def _add_guesses(self, phase_name, phase, guesses): # Set initial guess for time variables if 'time' == guess_key and self.mission_method is not SOLVED_2DOF: - self.set_val(f'traj.{phase_name}.t_initial', - val[0], units=units) - self.set_val(f'traj.{phase_name}.t_duration', - val[1], units=units) + setvalprob.set_val(parent_prefix+f'traj.{phase_name}.t_initial', + val[0], units=units) + setvalprob.set_val(parent_prefix+f'traj.{phase_name}.t_duration', + val[1], units=units) else: # Set initial guess for control variables if guess_key in control_keys: try: - self.set_val(f'traj.{phase_name}.controls:{guess_key}', self._process_guess_var( - val, guess_key, phase), units=units) + setvalprob.set_val( + parent_prefix + f'traj.{phase_name}.controls:{guess_key}', + self._process_guess_var(val, guess_key, phase), + units=units) except KeyError: try: - self.set_val(f'traj.{phase_name}.polynomial_controls:{guess_key}', self._process_guess_var( - val, guess_key, phase), units=units) + setvalprob.set_val( + parent_prefix + + f'traj.{phase_name}.polynomial_controls:{guess_key}', + self._process_guess_var(val, guess_key, phase), + units=units) except KeyError: - self.set_val(f'traj.{phase_name}.bspline_controls:{guess_key}', self._process_guess_var( - val, guess_key, phase), units=units) + setvalprob.set_val(parent_prefix + + f'traj.{phase_name}.bspline_controls:', + {guess_key}, + self._process_guess_var( + val, guess_key, phase), + units=units) if self.mission_method is SOLVED_2DOF: continue @@ -2246,13 +2320,17 @@ def _add_guesses(self, phase_name, phase, guesses): pass # Set initial guess for state variables elif guess_key in state_keys: - self.set_val(f'traj.{phase_name}.states:{guess_key}', self._process_guess_var( - val, guess_key, phase), units=units) + setvalprob.set_val(parent_prefix + + f'traj.{phase_name}.states:{guess_key}', self. + _process_guess_var(val, guess_key, phase), + units=units) elif guess_key in prob_keys: - self.set_val(guess_key, val, units=units) + setvalprob.set_val(parent_prefix+guess_key, val, units=units) elif ":" in guess_key: - self.set_val(f'traj.{phase_name}.{guess_key}', self._process_guess_var( - val, guess_key, phase), units=units) + setvalprob.set_val(parent_prefix + + f'traj.{phase_name}.{guess_key}', self._process_guess_var( + val, guess_key, phase), + units=units) else: # raise error if the guess key is not recognized raise ValueError( @@ -2282,8 +2360,8 @@ def _add_guesses(self, phase_name, phase, guesses): mass_guess = self.aviary_inputs.get_val( Mission.Design.GROSS_MASS, units='lbm') # Set the mass guess as the initial value for the mass state variable - self.set_val(f'traj.{phase_name}.states:mass', - mass_guess, units='lbm') + setvalprob.set_val(parent_prefix+f'traj.{phase_name}.states:mass', + mass_guess, units='lbm') if 'time' not in guesses: # Determine initial time and duration guesses depending on the phase name @@ -2294,10 +2372,10 @@ def _add_guesses(self, phase_name, phase, guesses): t_initial = flight_duration*.94 t_duration = 5000 # Set the time guesses as the initial values for the time-related trajectory variables - self.set_val(f"traj.{phase_name}.t_initial", - t_initial, units='s') - self.set_val(f"traj.{phase_name}.t_duration", - t_duration, units='s') + setvalprob.set_val(parent_prefix+f"traj.{phase_name}.t_initial", + t_initial, units='s') + setvalprob.set_val(parent_prefix+f"traj.{phase_name}.t_duration", + t_duration, units='s') if self.mission_method is TWO_DEGREES_OF_FREEDOM: if 'distance' not in guesses: @@ -2307,15 +2385,15 @@ def _add_guesses(self, phase_name, phase, guesses): elif 'desc2' in base_phase: ys = [self.target_range*.99, self.target_range] # Set the distance guesses as the initial values for the distance state variable - self.set_val( - f"traj.{phase_name}.states:distance", phase.interp( - Dynamic.Mission.DISTANCE, ys=ys) - ) - - def run_aviary_problem(self, - record_filename="problem_history.db", - optimization_history_filename=None, - restart_filename=None, suppress_solver_print=True, run_driver=True, simulate=False, make_plots=True): + setvalprob.set_val(parent_prefix + + f"traj.{phase_name}.states:distance", phase.interp( + Dynamic.Mission.DISTANCE, ys=ys) + ) + + def run_aviary_problem(self, record_filename="problem_history.db", + optimization_history_filename=None, restart_filename=None, + suppress_solver_print=True, run_driver=True, simulate=False, + make_plots=True): """ This function actually runs the Aviary problem, which could be a simulation, optimization, or a driver execution, depending on the arguments provided. @@ -2351,8 +2429,9 @@ def run_aviary_problem(self, # and run mission, and dynamics if run_driver: - failed = dm.run_problem(self, run_driver=run_driver, simulate=simulate, make_plots=make_plots, - solution_record_file=record_filename, restart=restart_filename) + failed = dm.run_problem( + self, run_driver=run_driver, simulate=simulate, make_plots=make_plots, + solution_record_file=record_filename, restart=restart_filename) else: # prevent UserWarning that is displayed when an event is triggered warnings.filterwarnings('ignore', category=UserWarning) @@ -2548,12 +2627,14 @@ def _add_vrotate_comp(self): 'vrot_comp.mass', src_indices=om.slicer[0, ...]) vrot_eq_comp = self.model.add_subsystem("vrot_eq_comp", om.EQConstraintComp()) - vrot_eq_comp.add_eq_output("v_rotate_error", eq_units="kn", - lhs_name="v_rot_computed", rhs_name="groundroll_v_final", add_constraint=True) + vrot_eq_comp.add_eq_output( + "v_rotate_error", eq_units="kn", lhs_name="v_rot_computed", + rhs_name="groundroll_v_final", add_constraint=True) self.model.connect('vrot_comp.Vrot', 'vrot_eq_comp.v_rot_computed') - self.model.connect('traj.groundroll.timeseries.velocity', - 'vrot_eq_comp.groundroll_v_final', src_indices=om.slicer[-1, ...]) + self.model.connect( + 'traj.groundroll.timeseries.velocity', 'vrot_eq_comp.groundroll_v_final', + src_indices=om.slicer[-1, ...]) def _save_to_csv_file(self, filename): with open(filename, 'w', newline='') as csvfile: @@ -2591,7 +2672,8 @@ def _add_height_energy_landing_systems(self): last_flight_phase_name = list(self.phase_info.keys())[-1] control_type_string = 'control_values' - if self.phase_info[last_flight_phase_name]['user_options'].get('use_polynomial_control', True): + if self.phase_info[last_flight_phase_name]['user_options'].get( + 'use_polynomial_control', True): if not use_new_dymos_syntax: control_type_string = 'polynomial_control_values' @@ -2604,8 +2686,8 @@ def _add_height_energy_landing_systems(self): def _add_post_mission_takeoff_systems(self): first_flight_phase_name = list(self.phase_info.keys())[0] - connect_takeoff_to_climb = not self.phase_info[first_flight_phase_name]['user_options'].get( - 'add_initial_mass_constraint', True) + connect_takeoff_to_climb = not self.phase_info[first_flight_phase_name][ + 'user_options'].get('add_initial_mass_constraint', True) if connect_takeoff_to_climb: self.model.connect(Mission.Takeoff.FINAL_MASS, @@ -2614,11 +2696,13 @@ def _add_post_mission_takeoff_systems(self): f'traj.{first_flight_phase_name}.initial_states:distance') control_type_string = 'control_values' - if self.phase_info[first_flight_phase_name]['user_options'].get('use_polynomial_control', True): + if self.phase_info[first_flight_phase_name]['user_options'].get( + 'use_polynomial_control', True): if not use_new_dymos_syntax: control_type_string = 'polynomial_control_values' - if self.phase_info[first_flight_phase_name]['user_options'].get('optimize_mach', False): + if self.phase_info[first_flight_phase_name]['user_options'].get( + 'optimize_mach', False): # Create an ExecComp to compute the difference in mach mach_diff_comp = om.ExecComp( 'mach_resid_for_connecting_takeoff = final_mach - initial_mach') @@ -2627,23 +2711,27 @@ def _add_post_mission_takeoff_systems(self): # Connect the inputs to the mach difference component self.model.connect(Mission.Takeoff.FINAL_MACH, 'mach_diff_comp.final_mach') - self.model.connect(f'traj.{first_flight_phase_name}.{control_type_string}:mach', - 'mach_diff_comp.initial_mach', src_indices=[0]) + self.model.connect( + f'traj.{first_flight_phase_name}.{control_type_string}:mach', + 'mach_diff_comp.initial_mach', src_indices=[0]) # Add constraint for mach difference self.model.add_constraint( 'mach_diff_comp.mach_resid_for_connecting_takeoff', equals=0.0) - if self.phase_info[first_flight_phase_name]['user_options'].get('optimize_altitude', False): + if self.phase_info[first_flight_phase_name]['user_options'].get( + 'optimize_altitude', False): # Similar steps for altitude difference alt_diff_comp = om.ExecComp( - 'altitude_resid_for_connecting_takeoff = final_altitude - initial_altitude', units='ft') + 'altitude_resid_for_connecting_takeoff = final_altitude - initial_altitude', + units='ft') self.model.add_subsystem('alt_diff_comp', alt_diff_comp) self.model.connect(Mission.Takeoff.FINAL_ALTITUDE, 'alt_diff_comp.final_altitude') - self.model.connect(f'traj.{first_flight_phase_name}.{control_type_string}:altitude', - 'alt_diff_comp.initial_altitude', src_indices=[0]) + self.model.connect( + f'traj.{first_flight_phase_name}.{control_type_string}:altitude', + 'alt_diff_comp.initial_altitude', src_indices=[0]) self.model.add_constraint( 'alt_diff_comp.altitude_resid_for_connecting_takeoff', equals=0.0) @@ -2691,7 +2779,7 @@ def _add_objectives(self): "val": self.target_range, "units": "NM"}, ), promotes_inputs=[ - ("actual_range", Mission.Summary.RANGE), + "actual_range", ("ascent_duration", Mission.Takeoff.ASCENT_DURATION), ], promotes_outputs=[("reg_objective", Mission.Objectives.RANGE)], @@ -2713,12 +2801,12 @@ def _add_objectives(self): om.ExecComp( "range_resid = target_range - actual_range", target_range={"val": self.target_range, "units": "NM"}, - actual_range={"val": self.target_range - 25, "units": "NM"}, + actual_range={"val": self.target_range, "units": "NM"}, range_resid={"val": 30, "units": "NM"}, ), promotes_inputs=[ - ("actual_range", Mission.Summary.RANGE), - ("target_range", Mission.Design.RANGE), + "actual_range", + ("target_range", Mission.Summary.RANGE), ], promotes_outputs=[ ("range_resid", Mission.Constraints.RANGE_RESIDUAL)], @@ -2734,19 +2822,21 @@ def _add_fuel_reserve_component(self, post_mission=True, RESERVE_FUEL_FRACTION = self.aviary_inputs.get_val( Aircraft.Design.RESERVE_FUEL_FRACTION, units='unitless') if RESERVE_FUEL_FRACTION != 0: - reserve_fuel_frac = om.ExecComp('reserve_fuel_frac_mass = reserve_fuel_fraction * (takeoff_mass - final_mass)', - reserve_fuel_frac_mass={"units": "lbm"}, - reserve_fuel_fraction={ - "units": "unitless", "val": RESERVE_FUEL_FRACTION}, - final_mass={"units": "lbm"}, - takeoff_mass={"units": "lbm"}) - - reserve_calc_location.add_subsystem("reserve_fuel_frac", reserve_fuel_frac, - promotes_inputs=[("takeoff_mass", Mission.Summary.GROSS_MASS), - ("final_mass", - Mission.Landing.TOUCHDOWN_MASS), - ("reserve_fuel_fraction", Aircraft.Design.RESERVE_FUEL_FRACTION)], - promotes_outputs=["reserve_fuel_frac_mass"]) + reserve_fuel_frac = om.ExecComp( + 'reserve_fuel_frac_mass = reserve_fuel_fraction * (takeoff_mass - final_mass)', + reserve_fuel_frac_mass={"units": "lbm"}, + reserve_fuel_fraction={"units": "unitless", + "val": RESERVE_FUEL_FRACTION}, + final_mass={"units": "lbm"}, + takeoff_mass={"units": "lbm"}) + + reserve_calc_location.add_subsystem( + "reserve_fuel_frac", reserve_fuel_frac, + promotes_inputs=[("takeoff_mass", Mission.Summary.GROSS_MASS), + ("final_mass", Mission.Landing.TOUCHDOWN_MASS), + ("reserve_fuel_fraction", Aircraft.Design. + RESERVE_FUEL_FRACTION)], + promotes_outputs=["reserve_fuel_frac_mass"]) RESERVE_FUEL_ADDITIONAL = self.aviary_inputs.get_val( Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm') diff --git a/aviary/interface/test/test_phase_info.py b/aviary/interface/test/test_phase_info.py index dd3b8ac82..9d4c0aca9 100644 --- a/aviary/interface/test/test_phase_info.py +++ b/aviary/interface/test/test_phase_info.py @@ -146,8 +146,8 @@ def test_phase_info_parameterization_height_energy(self): prob.run_model() - range_resid = prob.get_val(Mission.Constraints.RANGE_RESIDUAL, units='km')[-1] - assert_near_equal(range_resid, 5000.0 - 1.e-3) + range_resid = prob.get_val(Mission.Constraints.RANGE_RESIDUAL, units='nmi')[-1] + assert_near_equal(range_resid, 1906, tolerance=1e-3) assert_near_equal(prob.get_val("traj.cruise.timeseries.altitude", units='ft')[0], 31000.0) assert_near_equal(prob.get_val("traj.cruise.timeseries.mach")[0], diff --git a/aviary/mission/flops_based/ode/test/test_landing_eom.py b/aviary/mission/flops_based/ode/test/test_landing_eom.py index 1b0a58be7..220effc62 100644 --- a/aviary/mission/flops_based/ode/test/test_landing_eom.py +++ b/aviary/mission/flops_based/ode/test/test_landing_eom.py @@ -12,6 +12,8 @@ from aviary.utils.test_utils.variable_test import assert_match_varnames from aviary.validation_cases.validation_tests import do_validation_test from aviary.variable_info.variables import Dynamic +from aviary.subsystems.propulsion.utils import build_engine_deck +from aviary.utils.preprocessors import preprocess_options class FlareEOMTest(unittest.TestCase): @@ -25,6 +27,8 @@ def setUp(self): time, _ = detailed_landing_flare.get_item('time') nn = len(time) aviary_options = inputs + engine = build_engine_deck(aviary_options) + preprocess_options(aviary_options, engine_models=engine) prob.model.add_subsystem( "landing_flare_eom", diff --git a/aviary/mission/flops_based/ode/test/test_landing_ode.py b/aviary/mission/flops_based/ode/test/test_landing_ode.py index 0c863e6d5..00310a904 100644 --- a/aviary/mission/flops_based/ode/test/test_landing_ode.py +++ b/aviary/mission/flops_based/ode/test/test_landing_ode.py @@ -11,6 +11,7 @@ detailed_landing_flare, inputs, landing_subsystem_options) from aviary.validation_cases.validation_tests import do_validation_test from aviary.variable_info.variables import Dynamic, Aircraft +from aviary.utils.preprocessors import preprocess_options class FlareODETest(unittest.TestCase): @@ -24,8 +25,12 @@ def test_case(self): nn = len(time) aviary_options = inputs + engine = build_engine_deck(aviary_options) + + preprocess_options(aviary_options, engine_models=engine) + default_mission_subsystems = get_default_mission_subsystems( - 'FLOPS', build_engine_deck(aviary_options)) + 'FLOPS', engine) prob.model.add_subsystem( "landing_flare_ode", diff --git a/aviary/mission/flops_based/ode/test/test_takeoff_ode.py b/aviary/mission/flops_based/ode/test/test_takeoff_ode.py index daecf73cb..460680614 100644 --- a/aviary/mission/flops_based/ode/test/test_takeoff_ode.py +++ b/aviary/mission/flops_based/ode/test/test_takeoff_ode.py @@ -11,6 +11,7 @@ detailed_takeoff_climbing, detailed_takeoff_ground, takeoff_subsystem_options, inputs) from aviary.validation_cases.validation_tests import do_validation_test from aviary.variable_info.variables import Dynamic, Mission, Aircraft +from aviary.utils.preprocessors import preprocess_options takeoff_subsystem_options = deepcopy(takeoff_subsystem_options) @@ -75,9 +76,12 @@ def _make_prob(climbing): time, _ = detailed_takeoff_climbing.get_item('time') nn = len(time) aviary_options = inputs + engine = build_engine_deck(aviary_options) + + preprocess_options(aviary_options, engine_models=engine) default_mission_subsystems = get_default_mission_subsystems( - 'FLOPS', build_engine_deck(aviary_options)) + 'FLOPS', engine) prob.model.add_subsystem( "takeoff_ode", diff --git a/aviary/models/N3CC/N3CC_data.py b/aviary/models/N3CC/N3CC_data.py index 4b4d66971..ccea7fb00 100644 --- a/aviary/models/N3CC/N3CC_data.py +++ b/aviary/models/N3CC/N3CC_data.py @@ -20,10 +20,10 @@ 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_premission_subsystems, get_default_mission_subsystems -from aviary.utils.preprocessors import preprocess_options from aviary.utils.functions import get_path from aviary.variable_info.variables import Aircraft, Dynamic, Mission, Settings from aviary.variable_info.enums import EquationsOfMotion, LegacyCode +# from aviary.utils.preprocessors import preprocess_options N3CC = {} inputs = N3CC['inputs'] = AviaryValues() @@ -62,15 +62,20 @@ # Crew and Payload # --------------------------- +inputs.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, 20) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, 16) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, 154, units='unitless') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, 118) +inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 20) +inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 16) +inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 154, units='unitless') +inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 118) + inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35.0, 'lbm') inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 0.0) inputs.set_val(Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 20) -inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 16) inputs.set_val(Aircraft.CrewPayload.MISC_CARGO, 0., 'lbm') -inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 154, units='unitless') -inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 118) inputs.set_val(Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER, 1.) inputs.set_val(Aircraft.CrewPayload.MASS_PER_PASSENGER, 165., 'lbm') inputs.set_val(Aircraft.CrewPayload.WING_CARGO, 0., 'lbm') @@ -442,7 +447,9 @@ # Create engine model engine = build_engine_deck(aviary_options=inputs) -preprocess_options(inputs, engine_models=engine) +# Calls to preprocess_options() in this location should be avoided because they +# # will trigger when get_flops_inputs() is imported +# preprocess_options(inputs, engine_models=engine) # build subsystems default_premission_subsystems = get_default_premission_subsystems('FLOPS', engine) diff --git a/aviary/models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv b/aviary/models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv index 5a9b874c6..98cf16cf5 100644 --- a/aviary/models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv +++ b/aviary/models/N3CC/N3CC_generic_low_speed_polars_FLOPSinp.csv @@ -10,16 +10,16 @@ aircraft:canard:laminar_flow_lower,0,unitless aircraft:canard:laminar_flow_upper,0,unitless aircraft:canard:mass_scaler,1,unitless aircraft:crew_and_payload:baggage_mass_per_passenger,35,lbm +aircraft:crew_and_payload:design:num_business_class,20,unitless +aircraft:crew_and_payload:design:num_first_class,16,unitless +aircraft:crew_and_payload:design:num_tourist_class,118,unitless aircraft:crew_and_payload:flight_crew_mass_scaler,1,unitless aircraft:crew_and_payload:mass_per_passenger,165,lbm aircraft:crew_and_payload:misc_cargo,0,lbm aircraft:crew_and_payload:non_flight_crew_mass_scaler,1,unitless -aircraft:crew_and_payload:num_business_class,20,unitless -aircraft:crew_and_payload:num_first_class,16,unitless aircraft:crew_and_payload:num_flight_attendants,-1,unitless aircraft:crew_and_payload:num_flight_crew,-1,unitless aircraft:crew_and_payload:num_galley_crew,-1,unitless -aircraft:crew_and_payload:num_tourist_class,118,unitless aircraft:crew_and_payload:passenger_service_mass_scaler,1,unitless aircraft:crew_and_payload:wing_cargo,0,lbm aircraft:design:base_area,0,ft**2 diff --git a/aviary/models/large_single_aisle_1/V3_bug_fixed_IO.py b/aviary/models/large_single_aisle_1/V3_bug_fixed_IO.py index 5686a33a0..229a932d0 100644 --- a/aviary/models/large_single_aisle_1/V3_bug_fixed_IO.py +++ b/aviary/models/large_single_aisle_1/V3_bug_fixed_IO.py @@ -12,6 +12,10 @@ V3_bug_fixed_options = get_option_defaults() V3_bug_fixed_options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') +V3_bug_fixed_options.set_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, val=180, units='unitless') +# we keep CrewPayload.NUM_PASSENGERS here because preprocess_crewpayload is often not run in these +# tests which prevents these values being assigned from Design.NUM_PASSENGERS as would normally happen V3_bug_fixed_options.set_val( Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') V3_bug_fixed_options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37500, units='ft') diff --git a/aviary/models/large_single_aisle_1/large_single_aisle_1_FLOPS_data.py b/aviary/models/large_single_aisle_1/large_single_aisle_1_FLOPS_data.py index c5fd70877..9c74cfbd5 100644 --- a/aviary/models/large_single_aisle_1/large_single_aisle_1_FLOPS_data.py +++ b/aviary/models/large_single_aisle_1/large_single_aisle_1_FLOPS_data.py @@ -45,18 +45,23 @@ # Crew and Payload # --------------------------- +inputs.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, 11) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, 169, units='unitless') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, 158) inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) -inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 11) +inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 169, units='unitless') +inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 158) + +inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS, 3) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW, 2) inputs.set_val(Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_GALLEY_CREW, 0) inputs.set_val(Aircraft.CrewPayload.MISC_CARGO, 0., 'lbm') inputs.set_val(Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 169, units='unitless') inputs.set_val(Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER, 1.) -inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 158) inputs.set_val(Aircraft.CrewPayload.MASS_PER_PASSENGER, 180., 'lbm') inputs.set_val(Aircraft.CrewPayload.WING_CARGO, 0., 'lbm') diff --git a/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv b/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv index bcdd6d0a0..7a0a23fd2 100644 --- a/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv +++ b/aviary/models/large_single_aisle_1/large_single_aisle_1_GwGm.csv @@ -9,7 +9,7 @@ aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:controls:stability_augmentation_system_mass_scaler,1,unitless aircraft:crew_and_payload:cargo_mass,10040,lbm aircraft:crew_and_payload:catering_items_mass_per_passenger,7.6,lbm -aircraft:crew_and_payload:num_passengers,180,unitless +aircraft:crew_and_payload:design:num_passengers,180,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:crew_and_payload:passenger_service_mass_per_passenger,5,lbm aircraft:crew_and_payload:water_mass_per_occupant,3,lbm diff --git a/aviary/models/large_single_aisle_2/large_single_aisle_2_FLOPS_data.py b/aviary/models/large_single_aisle_2/large_single_aisle_2_FLOPS_data.py index c6c0f8e34..3787f9ddc 100644 --- a/aviary/models/large_single_aisle_2/large_single_aisle_2_FLOPS_data.py +++ b/aviary/models/large_single_aisle_2/large_single_aisle_2_FLOPS_data.py @@ -50,19 +50,23 @@ # Crew and Payload # --------------------------- -inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, 150) inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) + +inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS, 5) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW, 2) inputs.set_val(Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) inputs.set_val(Aircraft.CrewPayload.NUM_GALLEY_CREW, 1) -inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) inputs.set_val(Aircraft.CrewPayload.MISC_CARGO, 4077., 'lbm') inputs.set_val(Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) -inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') inputs.set_val(Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER, 1.) inputs.set_val(Aircraft.CrewPayload.MASS_PER_PASSENGER, 165., 'lbm') inputs.set_val(Aircraft.CrewPayload.WING_CARGO, 0., 'lbm') diff --git a/aviary/models/large_single_aisle_2/large_single_aisle_2_altwt_FLOPS_data.py b/aviary/models/large_single_aisle_2/large_single_aisle_2_altwt_FLOPS_data.py index c08f43020..454b605f4 100644 --- a/aviary/models/large_single_aisle_2/large_single_aisle_2_altwt_FLOPS_data.py +++ b/aviary/models/large_single_aisle_2/large_single_aisle_2_altwt_FLOPS_data.py @@ -50,19 +50,23 @@ # Crew and Payload # --------------------------- -inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, 150) inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) + +inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS, 5) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW, 2) inputs.set_val(Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) inputs.set_val(Aircraft.CrewPayload.NUM_GALLEY_CREW, 1) -inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) inputs.set_val(Aircraft.CrewPayload.MISC_CARGO, 4077., 'lbm') inputs.set_val(Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) -inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') inputs.set_val(Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER, 1.) inputs.set_val(Aircraft.CrewPayload.MASS_PER_PASSENGER, 165., 'lbm') inputs.set_val(Aircraft.CrewPayload.WING_CARGO, 0., 'lbm') diff --git a/aviary/models/large_single_aisle_2/large_single_aisle_2_detailwing_FLOPS_data.py b/aviary/models/large_single_aisle_2/large_single_aisle_2_detailwing_FLOPS_data.py index 2aca006c4..63721e4a4 100644 --- a/aviary/models/large_single_aisle_2/large_single_aisle_2_detailwing_FLOPS_data.py +++ b/aviary/models/large_single_aisle_2/large_single_aisle_2_detailwing_FLOPS_data.py @@ -46,19 +46,18 @@ # Crew and Payload # --------------------------- inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') -inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, 0) inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS, 5) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW, 2) inputs.set_val(Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, 12) inputs.set_val(Aircraft.CrewPayload.NUM_GALLEY_CREW, 1) -inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) inputs.set_val(Aircraft.CrewPayload.MISC_CARGO, 4077., 'lbm') inputs.set_val(Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, 162, units='unitless') inputs.set_val(Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER, 1.) -inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, 150) inputs.set_val(Aircraft.CrewPayload.MASS_PER_PASSENGER, 165., 'lbm') inputs.set_val(Aircraft.CrewPayload.WING_CARGO, 0., 'lbm') diff --git a/aviary/models/multi_engine_single_aisle/multi_engine_single_aisle_data.py b/aviary/models/multi_engine_single_aisle/multi_engine_single_aisle_data.py index 000d8098e..0ff0dc67a 100644 --- a/aviary/models/multi_engine_single_aisle/multi_engine_single_aisle_data.py +++ b/aviary/models/multi_engine_single_aisle/multi_engine_single_aisle_data.py @@ -45,20 +45,24 @@ # Crew and Payload # --------------------------- -inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, 150) inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) +inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) +inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') +inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) + +inputs.set_val(Aircraft.CrewPayload.BAGGAGE_MASS_PER_PASSENGER, 35., 'lbm') inputs.set_val(Aircraft.CrewPayload.CARGO_CONTAINER_MASS_SCALER, 1.0) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS, 5) inputs.set_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW, 2) inputs.set_val(Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, 12) inputs.set_val(Aircraft.CrewPayload.NUM_GALLEY_CREW, 1) -inputs.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, 0) inputs.set_val(Aircraft.CrewPayload.MISC_CARGO, 4077., 'lbm') inputs.set_val(Aircraft.CrewPayload.NON_FLIGHT_CREW_MASS_SCALER, 1.0) -inputs.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, 162, units='unitless') inputs.set_val(Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER, 1.) -inputs.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, 150) inputs.set_val(Aircraft.CrewPayload.MASS_PER_PASSENGER, 165., 'lbm') inputs.set_val(Aircraft.CrewPayload.WING_CARGO, 0., 'lbm') diff --git a/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv b/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv index df8ea4f96..505465acd 100644 --- a/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv +++ b/aviary/models/small_single_aisle/small_single_aisle_GwGm.csv @@ -9,7 +9,7 @@ aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:controls:stability_augmentation_system_mass_scaler,1,unitless aircraft:crew_and_payload:cargo_mass,8598,lbm aircraft:crew_and_payload:catering_items_mass_per_passenger,6,lbm -aircraft:crew_and_payload:num_passengers,96,unitless +aircraft:crew_and_payload:design:num_passengers,96,unitless aircraft:crew_and_payload:passenger_mass_with_bags,210,lbm aircraft:crew_and_payload:passenger_service_mass_per_passenger,7.6,lbm aircraft:crew_and_payload:water_mass_per_occupant,3,lbm diff --git a/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv b/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv index c2fa1d8a1..bd9fa3412 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_FwFm.csv @@ -7,17 +7,17 @@ aircraft:canard:aspect_ratio,0.0,unitless aircraft:canard:thickness_to_chord,0.0,unitless aircraft:crew_and_payload:baggage_mass_per_passenger,45.0,lbm aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless +aircraft:crew_and_payload:design:num_business_class,0,unitless +aircraft:crew_and_payload:design:num_first_class,11,unitless +aircraft:crew_and_payload:design:num_passengers,169,unitless +aircraft:crew_and_payload:design:num_tourist_class,158,unitless aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless aircraft:crew_and_payload:mass_per_passenger,180.0,lbm aircraft:crew_and_payload:misc_cargo,0.0,lbm aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:num_business_class,0,unitless -aircraft:crew_and_payload:num_first_class,11,unitless aircraft:crew_and_payload:num_flight_attendants,3,unitless aircraft:crew_and_payload:num_flight_crew,2,unitless aircraft:crew_and_payload:num_galley_crew,0,unitless -aircraft:crew_and_payload:num_passengers,169,unitless -aircraft:crew_and_payload:num_tourist_class,158,unitless aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless aircraft:crew_and_payload:wing_cargo,0.0,lbm aircraft:design:base_area,0.0,ft**2 diff --git a/aviary/models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv b/aviary/models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv index 1f408f98b..7dcd99288 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv @@ -7,17 +7,17 @@ aircraft:canard:aspect_ratio,0.0,unitless aircraft:canard:thickness_to_chord,0.0,unitless aircraft:crew_and_payload:baggage_mass_per_passenger,45.0,lbm aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless +aircraft:crew_and_payload:design:num_business_class,0,unitless +aircraft:crew_and_payload:design:num_first_class,11,unitless +aircraft:crew_and_payload:design:num_passengers,169,unitless +aircraft:crew_and_payload:design:num_tourist_class,158,unitless aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless aircraft:crew_and_payload:mass_per_passenger,180.0,lbm aircraft:crew_and_payload:misc_cargo,0.0,lbm aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:num_business_class,0,unitless -aircraft:crew_and_payload:num_first_class,11,unitless aircraft:crew_and_payload:num_flight_attendants,3,unitless aircraft:crew_and_payload:num_flight_crew,2,unitless aircraft:crew_and_payload:num_galley_crew,0,unitless -aircraft:crew_and_payload:num_passengers,169,unitless -aircraft:crew_and_payload:num_tourist_class,158,unitless aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless aircraft:crew_and_payload:wing_cargo,0.0,lbm aircraft:design:base_area,0.0,ft**2 diff --git a/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv b/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv index fdd379976..64f8adbfa 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_FwGm.csv @@ -2,7 +2,6 @@ aircraft:controls:cockpit_control_mass_scaler,1,unitless aircraft:controls:control_mass_increment,0,lbm aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:controls:stability_augmentation_system_mass_scaler,1,unitless -aircraft:crew_and_payload:num_passengers,169,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:design:cg_delta,0.25,unitless aircraft:design:cockpit_control_mass_coefficient,16.5,unitless @@ -163,16 +162,17 @@ aircraft:canard:area,0.0,ft**2 aircraft:canard:aspect_ratio,0.0,unitless aircraft:canard:thickness_to_chord,0.0,unitless aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless +aircraft:crew_and_payload:design:num_business_class,0,unitless +aircraft:crew_and_payload:design:num_first_class,11,unitless +aircraft:crew_and_payload:design:num_passengers,169,unitless +aircraft:crew_and_payload:design:num_tourist_class,158,unitless aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless aircraft:crew_and_payload:mass_per_passenger,180.0,lbm aircraft:crew_and_payload:misc_cargo,0.0,lbm aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:num_business_class,0,unitless -aircraft:crew_and_payload:num_first_class,11,unitless aircraft:crew_and_payload:num_flight_attendants,3,unitless aircraft:crew_and_payload:num_flight_crew,2,unitless aircraft:crew_and_payload:num_galley_crew,0,unitless -aircraft:crew_and_payload:num_tourist_class,158,unitless aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless aircraft:crew_and_payload:wing_cargo,0.0,lbm aircraft:design:base_area,0.0,ft**2 diff --git a/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv b/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv index d27a939e6..703c223d2 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_GwFm.csv @@ -16,17 +16,17 @@ aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless aircraft:crew_and_payload:cargo_mass,10040,lbm aircraft:crew_and_payload:catering_items_mass_per_passenger,7.6,lbm +aircraft:crew_and_payload:design:num_business_class,0,unitless +aircraft:crew_and_payload:design:num_first_class,11,unitless +aircraft:crew_and_payload:design:num_passengers,180,unitless +aircraft:crew_and_payload:design:num_tourist_class,169,unitless aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless aircraft:crew_and_payload:mass_per_passenger,180.0,lbm aircraft:crew_and_payload:misc_cargo,0.0,lbm aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:num_business_class,0,unitless -aircraft:crew_and_payload:num_first_class,11,unitless aircraft:crew_and_payload:num_flight_attendants,3,unitless aircraft:crew_and_payload:num_flight_crew,2,unitless aircraft:crew_and_payload:num_galley_crew,0,unitless -aircraft:crew_and_payload:num_passengers,180,unitless -aircraft:crew_and_payload:num_tourist_class,169,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:crew_and_payload:passenger_service_mass_per_passenger,5,lbm aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless diff --git a/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv b/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv index cf6ab17bc..2f3c32e0d 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_GwGm.csv @@ -8,7 +8,7 @@ aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:controls:stability_augmentation_system_mass_scaler,1,unitless aircraft:crew_and_payload:cargo_mass,10040,lbm aircraft:crew_and_payload:catering_items_mass_per_passenger,7.6,lbm -aircraft:crew_and_payload:num_passengers,180,unitless +aircraft:crew_and_payload:design:num_passengers,180,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:crew_and_payload:passenger_service_mass_per_passenger,5,lbm aircraft:crew_and_payload:water_mass_per_occupant,3,lbm diff --git a/aviary/models/test_aircraft/aircraft_for_bench_GwGm_lbm_s.csv b/aviary/models/test_aircraft/aircraft_for_bench_GwGm_lbm_s.csv index b9f818cbd..50a63d10d 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_GwGm_lbm_s.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_GwGm_lbm_s.csv @@ -8,7 +8,7 @@ aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:controls:stability_augmentation_system_mass_scaler,1,unitless aircraft:crew_and_payload:cargo_mass,10040,lbm aircraft:crew_and_payload:catering_items_mass_per_passenger,7.6,lbm -aircraft:crew_and_payload:num_passengers,180,unitless +aircraft:crew_and_payload:design:num_passengers,180,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:crew_and_payload:passenger_service_mass_per_passenger,5,lbm aircraft:crew_and_payload:water_mass_per_occupant,3,lbm diff --git a/aviary/models/test_aircraft/aircraft_for_bench_solved2dof.csv b/aviary/models/test_aircraft/aircraft_for_bench_solved2dof.csv index 1d2c97329..b6848994f 100644 --- a/aviary/models/test_aircraft/aircraft_for_bench_solved2dof.csv +++ b/aviary/models/test_aircraft/aircraft_for_bench_solved2dof.csv @@ -7,17 +7,17 @@ aircraft:canard:aspect_ratio,0.0,unitless aircraft:canard:thickness_to_chord,0.0,unitless aircraft:crew_and_payload:baggage_mass_per_passenger,45.0,lbm aircraft:crew_and_payload:cargo_container_mass_scaler,1.0,unitless +aircraft:crew_and_payload:design:num_business_class,0,unitless +aircraft:crew_and_payload:design:num_first_class,11,unitless +aircraft:crew_and_payload:design:num_passengers,169,unitless +aircraft:crew_and_payload:design:num_tourist_class,158,unitless aircraft:crew_and_payload:flight_crew_mass_scaler,1.0,unitless aircraft:crew_and_payload:mass_per_passenger,180.0,lbm aircraft:crew_and_payload:misc_cargo,0.0,lbm aircraft:crew_and_payload:non_flight_crew_mass_scaler,1.0,unitless -aircraft:crew_and_payload:num_business_class,0,unitless -aircraft:crew_and_payload:num_first_class,11,unitless aircraft:crew_and_payload:num_flight_attendants,3,unitless aircraft:crew_and_payload:num_flight_crew,2,unitless aircraft:crew_and_payload:num_galley_crew,0,unitless -aircraft:crew_and_payload:num_passengers,169,unitless -aircraft:crew_and_payload:num_tourist_class,158,unitless aircraft:crew_and_payload:passenger_service_mass_scaler,1.0,unitless aircraft:crew_and_payload:wing_cargo,0.0,lbm aircraft:design:base_area,0.0,ft**2 diff --git a/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv b/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv index 7626f4481..71e5cea39 100644 --- a/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv +++ b/aviary/models/test_aircraft/converter_configuration_test_data_GwGm.csv @@ -9,7 +9,7 @@ aircraft:controls:stability_augmentation_system_mass,0,lbm aircraft:controls:stability_augmentation_system_mass_scaler,1,unitless aircraft:crew_and_payload:cargo_mass,15970,lbm aircraft:crew_and_payload:catering_items_mass_per_passenger,10,lbm -aircraft:crew_and_payload:num_passengers,154,unitless +aircraft:crew_and_payload:design:num_passengers,154,unitless aircraft:crew_and_payload:passenger_mass_with_bags,200,lbm aircraft:crew_and_payload:passenger_service_mass_per_passenger,5,lbm aircraft:crew_and_payload:water_mass_per_occupant,3,lbm diff --git a/aviary/subsystems/energy/battery_builder.py b/aviary/subsystems/energy/battery_builder.py index eede97a15..ffc25bd20 100644 --- a/aviary/subsystems/energy/battery_builder.py +++ b/aviary/subsystems/energy/battery_builder.py @@ -59,48 +59,23 @@ def build_mission(self, num_nodes, aviary_inputs=None) -> om.Group: return battery_group def get_states(self): - # need to add subsystem name to target name ('battery.') for state due - # to issue where non aircraft or mission variables are not fully promoted - # TODO fix this by not promoting only 'aircraft:*' and 'mission:*' - state_dict = { - Dynamic.Mission.CUMULATIVE_ELECTRIC_ENERGY_USED: { - 'fix_initial': True, - 'fix_final': False, - 'lower': 0.0, - 'ref': 1e4, - 'defect_ref': 1e6, - 'units': 'kJ', - 'rate_source': Dynamic.Mission.ELECTRIC_POWER_IN_TOTAL, - 'input_initial': 0.0, - 'targets': f'{self.name}.{Dynamic.Mission.CUMULATIVE_ELECTRIC_ENERGY_USED}', - } - } + state_dict = {Dynamic.Mission.CUMULATIVE_ELECTRIC_ENERGY_USED: {'fix_initial': True, + 'fix_final': False, + 'lower': 0.0, + 'ref': 1e4, + 'defect_ref': 1e6, + 'units': 'kJ', + 'rate_source': Dynamic.Mission.ELECTRIC_POWER_IN_TOTAL, + 'input_initial': 0.0}} return state_dict def get_constraints(self): constraint_dict = { # Can add constraints here; state of charge is a common one in many battery applications - f'{self.name}.{Dynamic.Mission.BATTERY_STATE_OF_CHARGE}': { - 'type': 'boundary', - 'loc': 'final', - 'lower': 0.2, - }, + f'battery.{Dynamic.Mission.BATTERY_STATE_OF_CHARGE}': + {'type': 'boundary', + 'loc': 'final', + 'lower': 0.2}, } return constraint_dict - - def get_parameters(self, aviary_inputs=None, phase_info=None): - params = { - Aircraft.Battery.ENERGY_CAPACITY: { - 'val': 0.0, - 'units': 'kJ', - 'static_target': True, - }, - Aircraft.Battery.EFFICIENCY: { - 'val': 0.0, - 'units': 'unitless', - 'static_target': True, - }, - } - - return params diff --git a/aviary/subsystems/geometry/gasp_based/fuselage.py b/aviary/subsystems/geometry/gasp_based/fuselage.py index 3e27a1b5a..9e6c3ffd0 100644 --- a/aviary/subsystems/geometry/gasp_based/fuselage.py +++ b/aviary/subsystems/geometry/gasp_based/fuselage.py @@ -61,8 +61,7 @@ def compute(self, inputs, outputs): seat_width = aviary_options.get_val(Aircraft.Fuselage.SEAT_WIDTH, units='inch') num_aisle = aviary_options.get_val(Aircraft.Fuselage.NUM_AISLES) aisle_width = aviary_options.get_val(Aircraft.Fuselage.AISLE_WIDTH, units='inch') - PAX = self.options['aviary_options'].get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + PAX = aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) seat_pitch = aviary_options.get_val(Aircraft.Fuselage.SEAT_PITCH, units='inch') delta_diameter = inputs[Aircraft.Fuselage.DELTA_DIAMETER] diff --git a/aviary/subsystems/geometry/gasp_based/test/test_fuselage.py b/aviary/subsystems/geometry/gasp_based/test/test_fuselage.py index b40f34f9d..ed0d33693 100644 --- a/aviary/subsystems/geometry/gasp_based/test/test_fuselage.py +++ b/aviary/subsystems/geometry/gasp_based/test/test_fuselage.py @@ -18,7 +18,7 @@ class FuselageParametersTestCase1(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, val=180) options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) options.set_val(Aircraft.Fuselage.NUM_SEATS_ABREAST, 6) @@ -57,7 +57,8 @@ class FuselageParametersTestCase2(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=30, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=30, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) options.set_val(Aircraft.Fuselage.NUM_SEATS_ABREAST, 1) @@ -93,7 +94,7 @@ def test_case2(self): class FuselageSizeTestCase1(unittest.TestCase): - """ + """ this is the GASP test case, input and output values based on large single aisle 1 v3 without bug fix """ @@ -186,7 +187,8 @@ class FuselageGroupTestCase1( def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) options.set_val(Aircraft.Fuselage.NUM_SEATS_ABREAST, 6) @@ -235,7 +237,8 @@ class FuselageGroupTestCase2(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") # not actual GASP value options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) # not actual GASP value @@ -295,7 +298,8 @@ class FuselageGroupTestCase3(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=30, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=30, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") # not actual GASP value options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) # not actual GASP value @@ -355,7 +359,8 @@ class FuselageGroupTestCase4(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=30, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=30, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") # not actual GASP value options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) # not actual GASP value diff --git a/aviary/subsystems/geometry/gasp_based/test/test_size_group.py b/aviary/subsystems/geometry/gasp_based/test/test_size_group.py index 0a9530218..dc7c1bb1f 100644 --- a/aviary/subsystems/geometry/gasp_based/test/test_size_group.py +++ b/aviary/subsystems/geometry/gasp_based/test/test_size_group.py @@ -17,7 +17,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) options.set_val(Aircraft.Fuselage.NUM_SEATS_ABREAST, 6) @@ -164,7 +165,8 @@ def setUp(self): options.set_val(Aircraft.Wing.HAS_STRUT, val=True, units='unitless') options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') options.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, val=True, units='unitless') @@ -384,7 +386,8 @@ def setUp(self): val=True, units='unitless') options.set_val(Aircraft.Design.COMPUTE_VTAIL_VOLUME_COEFF, val=True, units='unitless') - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, val=True, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") @@ -607,7 +610,8 @@ def setUp(self): val=False, units='unitless') options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.Fuselage.AISLE_WIDTH, 24, units="inch") options.set_val(Aircraft.Fuselage.NUM_AISLES, 1) options.set_val(Aircraft.Fuselage.NUM_SEATS_ABREAST, 1) diff --git a/aviary/subsystems/mass/flops_based/air_conditioning.py b/aviary/subsystems/mass/flops_based/air_conditioning.py index 98605525e..894ca14dd 100644 --- a/aviary/subsystems/mass/flops_based/air_conditioning.py +++ b/aviary/subsystems/mass/flops_based/air_conditioning.py @@ -37,7 +37,7 @@ def setup_partials(self): def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): aviary_options: AviaryValues = self.options['aviary_options'] pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.AirConditioning.MASS_SCALER] avionics_wt = inputs[Aircraft.Avionics.MASS] * GRAV_ENGLISH_LBM @@ -52,7 +52,7 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.AirConditioning.MASS_SCALER] avionics_wt = inputs[Aircraft.Avionics.MASS] * GRAV_ENGLISH_LBM @@ -101,7 +101,7 @@ def setup_partials(self): def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): aviary_options: AviaryValues = self.options['aviary_options'] num_pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.AirConditioning.MASS_SCALER] @@ -111,7 +111,7 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] num_pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') J[Aircraft.AirConditioning.MASS, Aircraft.AirConditioning.MASS_SCALER] = \ 26.0 * num_pax / GRAV_ENGLISH_LBM diff --git a/aviary/subsystems/mass/flops_based/apu.py b/aviary/subsystems/mass/flops_based/apu.py index c4858be1f..3a8fec051 100644 --- a/aviary/subsystems/mass/flops_based/apu.py +++ b/aviary/subsystems/mass/flops_based/apu.py @@ -30,7 +30,7 @@ def setup_partials(self): def compute(self, inputs, outputs): aviary_options: AviaryValues = self.options['aviary_options'] pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.APU.MASS_SCALER] planform = inputs[Aircraft.Fuselage.PLANFORM_AREA] @@ -40,7 +40,7 @@ def compute(self, inputs, outputs): def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.APU.MASS_SCALER] planform = inputs[Aircraft.Fuselage.PLANFORM_AREA] diff --git a/aviary/subsystems/mass/flops_based/electrical.py b/aviary/subsystems/mass/flops_based/electrical.py index d3b6e13ff..1430945f7 100644 --- a/aviary/subsystems/mass/flops_based/electrical.py +++ b/aviary/subsystems/mass/flops_based/electrical.py @@ -33,7 +33,8 @@ def compute(self, inputs, outputs): options: AviaryValues = self.options['aviary_options'] nfuse = options.get_val(Aircraft.Fuselage.NUM_FUSELAGES) ncrew = options.get_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW) - npass = options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + npass = options.get_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') length = inputs[Aircraft.Fuselage.LENGTH] width = inputs[Aircraft.Fuselage.MAX_WIDTH] num_eng = options.get_val(Aircraft.Propulsion.TOTAL_NUM_ENGINES) @@ -48,7 +49,8 @@ def compute_partials(self, inputs, J): options: AviaryValues = self.options['aviary_options'] nfuse = options.get_val(Aircraft.Fuselage.NUM_FUSELAGES) ncrew = options.get_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW) - npass = options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + npass = options.get_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') length = inputs[Aircraft.Fuselage.LENGTH] width = inputs[Aircraft.Fuselage.MAX_WIDTH] num_eng = options.get_val(Aircraft.Propulsion.TOTAL_NUM_ENGINES) @@ -93,7 +95,7 @@ def setup_partials(self): def compute(self, inputs, outputs): aviary_options: AviaryValues = self.options['aviary_options'] npass = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') mass_scaler = inputs[Aircraft.Electrical.MASS_SCALER] outputs[Aircraft.Electrical.MASS] = 16.3 * \ @@ -102,7 +104,7 @@ def compute(self, inputs, outputs): def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] npass = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') J[Aircraft.Electrical.MASS, Aircraft.Electrical.MASS_SCALER] = \ 16.3 * npass / GRAV_ENGLISH_LBM diff --git a/aviary/subsystems/mass/flops_based/engine_oil.py b/aviary/subsystems/mass/flops_based/engine_oil.py index 79cfde263..d82cd8e63 100644 --- a/aviary/subsystems/mass/flops_based/engine_oil.py +++ b/aviary/subsystems/mass/flops_based/engine_oil.py @@ -87,7 +87,7 @@ def setup_partials(self): def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): aviary_options: AviaryValues = self.options['aviary_options'] pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.Propulsion.ENGINE_OIL_MASS_SCALER] @@ -97,7 +97,7 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] pax = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') J[Aircraft.Propulsion.TOTAL_ENGINE_OIL_MASS, Aircraft.Propulsion.ENGINE_OIL_MASS_SCALER diff --git a/aviary/subsystems/mass/flops_based/furnishings.py b/aviary/subsystems/mass/flops_based/furnishings.py index 62fd9f7bf..3dfa8416b 100644 --- a/aviary/subsystems/mass/flops_based/furnishings.py +++ b/aviary/subsystems/mass/flops_based/furnishings.py @@ -38,13 +38,14 @@ def compute( aviary_options: AviaryValues = self.options['aviary_options'] flight_crew_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW) - first_class_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) + first_class_count = aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) business_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS) + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) tourist_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_TOURIST_CLASS) + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) fuse_count = aviary_options.get_val(Aircraft.Fuselage.NUM_FUSELAGES) @@ -67,13 +68,14 @@ def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] flight_crew_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW) - first_class_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) + first_class_count = aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) business_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS) + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) tourist_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_TOURIST_CLASS) + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) fuse_count = aviary_options.get_val(Aircraft.Fuselage.NUM_FUSELAGES) @@ -135,13 +137,14 @@ def compute( aviary_options: AviaryValues = self.options['aviary_options'] flight_crew_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW) - first_class_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) + first_class_count = aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) business_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS) + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) tourist_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_TOURIST_CLASS) + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) scaler = inputs[Aircraft.Furnishings.MASS_SCALER] fuse_max_width = inputs[Aircraft.Fuselage.MAX_WIDTH] @@ -173,13 +176,14 @@ def compute_partials(self, inputs, J): aviary_options: AviaryValues = self.options['aviary_options'] flight_crew_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW) - first_class_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) + first_class_count = aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) business_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS) + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) tourist_class_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_TOURIST_CLASS) + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) scaler = inputs[Aircraft.Furnishings.MASS_SCALER] @@ -275,7 +279,7 @@ def compute( ): aviary_options: AviaryValues = self.options['aviary_options'] pax_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') scaler = inputs[Aircraft.Furnishings.MASS_SCALER] outputs[Aircraft.Furnishings.MASS_BASE] = \ @@ -284,7 +288,7 @@ def compute( def compute_partials(self, inputs, J, discrete_inputs=None): aviary_options: AviaryValues = self.options['aviary_options'] pax_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') J[ Aircraft.Furnishings.MASS_BASE, diff --git a/aviary/subsystems/mass/flops_based/passenger_service.py b/aviary/subsystems/mass/flops_based/passenger_service.py index 21c5a5ae2..7d1688085 100644 --- a/aviary/subsystems/mass/flops_based/passenger_service.py +++ b/aviary/subsystems/mass/flops_based/passenger_service.py @@ -46,13 +46,14 @@ def setup_partials(self): def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): aviary_options: AviaryValues = self.options['aviary_options'] - first_class_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) + first_class_count = aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) business_class_count = \ - aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS) + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) tourist_class_count = \ - aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS) + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) design_range = inputs[Mission.Design.RANGE] max_mach = aviary_options.get_val(Mission.Constraints.MAX_MACH) @@ -72,13 +73,14 @@ def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None): def compute_partials(self, inputs, J, discrete_inputs=None): aviary_options: AviaryValues = self.options['aviary_options'] - first_class_count = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) + first_class_count = aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) business_class_count = \ - aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS) + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) tourist_class_count = \ - aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS) + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) design_range = inputs[Mission.Design.RANGE] max_mach = aviary_options.get_val(Mission.Constraints.MAX_MACH) @@ -132,7 +134,7 @@ def compute( ): aviary_options: AviaryValues = self.options['aviary_options'] passenger_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') passenger_service_mass_scaler = \ inputs[Aircraft.CrewPayload.PASSENGER_SERVICE_MASS_SCALER] @@ -146,7 +148,7 @@ def compute( def compute_partials(self, inputs, J, discrete_inputs=None): aviary_options: AviaryValues = self.options['aviary_options'] passenger_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') J[ Aircraft.CrewPayload.PASSENGER_SERVICE_MASS, diff --git a/aviary/subsystems/mass/flops_based/test/test_air_conditioning.py b/aviary/subsystems/mass/flops_based/test/test_air_conditioning.py index a168955a5..b0becd20f 100644 --- a/aviary/subsystems/mass/flops_based/test/test_air_conditioning.py +++ b/aviary/subsystems/mass/flops_based/test/test_air_conditioning.py @@ -43,7 +43,7 @@ def test_case(self, case_name): Aircraft.Fuselage.MAX_HEIGHT, Aircraft.Fuselage.PLANFORM_AREA], output_keys=Aircraft.AirConditioning.MASS, - aviary_option_keys=[Aircraft.CrewPayload.NUM_PASSENGERS], + aviary_option_keys=[Aircraft.CrewPayload.Design.NUM_PASSENGERS], version=Version.TRANSPORT, tol=3.0e-4, atol=1e-11) @@ -115,7 +115,7 @@ def test_case(self, case_name): case_name, input_keys=Aircraft.AirConditioning.MASS_SCALER, output_keys=Aircraft.AirConditioning.MASS, - aviary_option_keys=Aircraft.CrewPayload.NUM_PASSENGERS, + aviary_option_keys=Aircraft.CrewPayload.Design.NUM_PASSENGERS, version=Version.ALTERNATE) def test_IO(self): diff --git a/aviary/subsystems/mass/flops_based/test/test_landing_gear.py b/aviary/subsystems/mass/flops_based/test/test_landing_gear.py index a02d8b923..59a056984 100644 --- a/aviary/subsystems/mass/flops_based/test/test_landing_gear.py +++ b/aviary/subsystems/mass/flops_based/test/test_landing_gear.py @@ -1,3 +1,5 @@ +from aviary.subsystems.propulsion.utils import build_engine_deck +from aviary.utils.preprocessors import preprocess_options import unittest import openmdao.api as om @@ -157,6 +159,8 @@ def test_derivs(self, case_name): prob = self.prob model = prob.model flops_inputs = get_flops_inputs(case_name) + engine = build_engine_deck(flops_inputs) + preprocess_options(flops_inputs, engine_models=engine) model.add_subsystem( 'main', MainGearLength(aviary_options=flops_inputs), promotes=['*']) diff --git a/aviary/subsystems/mass/flops_based/wing_detailed.py b/aviary/subsystems/mass/flops_based/wing_detailed.py index c6f825bd8..521f3c9d0 100644 --- a/aviary/subsystems/mass/flops_based/wing_detailed.py +++ b/aviary/subsystems/mass/flops_based/wing_detailed.py @@ -189,6 +189,7 @@ def compute(self, inputs, outputs): bt = btb / (ar**(0.25*fstrt) * (1.0 + (0.5*faert - 0.16*fstrt) * sa**2 + 0.03*caya * (1.0-0.5*faert)*sa)) + outputs[Aircraft.Wing.BENDING_FACTOR] = bt inertia_factor = np.zeros(num_engine_type, dtype=chord.dtype) diff --git a/aviary/subsystems/mass/gasp_based/equipment_and_useful_load.py b/aviary/subsystems/mass/gasp_based/equipment_and_useful_load.py index 90e7014aa..c229c11a4 100644 --- a/aviary/subsystems/mass/gasp_based/equipment_and_useful_load.py +++ b/aviary/subsystems/mass/gasp_based/equipment_and_useful_load.py @@ -81,7 +81,8 @@ def setup(self): def compute(self, inputs, outputs): options: AviaryValues = self.options["aviary_options"] - PAX = options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + PAX = options.get_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') smooth = options.get_val( Aircraft.Design.SMOOTH_MASS_DISCONTINUITIES, units='unitless') @@ -392,7 +393,8 @@ def compute(self, inputs, outputs): def compute_partials(self, inputs, partials): options = self.options['aviary_options'] - PAX = options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS, units='unitless') + PAX = options.get_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, units='unitless') smooth = options.get_val( Aircraft.Design.SMOOTH_MASS_DISCONTINUITIES, units='unitless') gross_wt_initial = inputs[Mission.Design.GROSS_MASS] * GRAV_ENGLISH_LBM diff --git a/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py b/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py index 97549f437..bb1328cf9 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py +++ b/aviary/subsystems/mass/gasp_based/test/test_equipment_and_useful_load.py @@ -17,7 +17,8 @@ class FixedEquipMassTestCase1(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, val=False, units='unitless') @@ -106,7 +107,8 @@ class FixedEquipMassTestCase2(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=5, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=5, units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, val=False, units='unitless') @@ -289,7 +291,8 @@ class FixedEquipMassTestCase4smooth(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, val=False, units='unitless') options.set_val(Aircraft.Design.SMOOTH_MASS_DISCONTINUITIES, @@ -383,7 +386,8 @@ class FixedEquipMassTestCase5smooth(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=5, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=5, units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, val=False, units='unitless') options.set_val(Aircraft.Design.SMOOTH_MASS_DISCONTINUITIES, @@ -477,7 +481,8 @@ class FixedEquipMassTestCase6smooth(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=5, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=5, units='unitless') options.set_val(Aircraft.Engine.TYPE, val=[GASPEngineType.RECIP_CARB], units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, @@ -575,7 +580,8 @@ class EquipAndUsefulMassGroupTestCase1(unittest.TestCase): def setUp(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, val=False, units='unitless') @@ -677,7 +683,8 @@ def tearDown(self): def test_case1(self): options = get_option_defaults() - options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.LandingGear.FIXED_GEAR, val=False, units='unitless') diff --git a/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py b/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py index 4101e5325..b1924dac6 100644 --- a/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py +++ b/aviary/subsystems/mass/gasp_based/test/test_mass_summation.py @@ -174,6 +174,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37500, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') @@ -563,6 +565,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37500, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') @@ -943,6 +947,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37500, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') @@ -1324,6 +1330,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37500, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') @@ -1704,6 +1712,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=180, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=180, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37500, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') @@ -2086,6 +2096,8 @@ def setUp(self): options.set_val(Aircraft.Wing.HAS_FOLD, val=True, units='unitless') options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=154, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=154, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=37100, units='ft') options.set_val(Aircraft.Wing.FOLD_DIMENSIONAL_LOCATION_SPECIFIED, @@ -2474,6 +2486,8 @@ def setUp(self): options.set_val(Aircraft.Wing.HAS_STRUT, val=True, units='unitless') options.set_val(Aircraft.Electrical.HAS_HYBRID_SYSTEM, val=False, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=154, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=154, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=43000, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') @@ -2868,6 +2882,8 @@ def setUp(self): options = get_option_defaults() options.set_val(Aircraft.Wing.HAS_FOLD, val=True, units='unitless') options.set_val(Aircraft.Wing.HAS_STRUT, val=True, units='unitless') + options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, + val=154, units='unitless') options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, val=154, units='unitless') options.set_val(Mission.Design.CRUISE_ALTITUDE, val=43000, units='ft') options.set_val(Aircraft.Wing.CHOOSE_FOLD_LOCATION, val=False, units='unitless') diff --git a/aviary/utils/preprocessors.py b/aviary/utils/preprocessors.py index e1d2f747d..89fef61af 100644 --- a/aviary/utils/preprocessors.py +++ b/aviary/utils/preprocessors.py @@ -6,7 +6,7 @@ from aviary.utils.aviary_values import AviaryValues from aviary.utils.named_values import get_keys from aviary.variable_info.variable_meta_data import _MetaData -from aviary.variable_info.variables import Aircraft, Mission +from aviary.variable_info.variables import Aircraft, Mission, Settings from aviary.utils.test_utils.variable_test import get_names_from_hierarchy @@ -24,6 +24,10 @@ def preprocess_options(aviary_options: AviaryValues, **kwargs): except KeyError: engine_models = None + if Settings.VERBOSITY not in aviary_options: + aviary_options.set_val( + Settings.VERBOSITY, _MetaData[Settings.VERBOSITY]['default_value']) + preprocess_crewpayload(aviary_options) preprocess_propulsion(aviary_options, engine_models) @@ -35,30 +39,128 @@ def preprocess_crewpayload(aviary_options: AviaryValues): returns the modified collection. """ - if Aircraft.CrewPayload.NUM_PASSENGERS not in aviary_options: - passenger_count = 0 - for key in (Aircraft.CrewPayload.NUM_FIRST_CLASS, - Aircraft.CrewPayload.NUM_BUSINESS_CLASS, - Aircraft.CrewPayload.NUM_TOURIST_CLASS): - if key in aviary_options: - passenger_count += aviary_options.get_val(key) - if passenger_count == 0: - passenger_count = 1 - + verbosity = aviary_options.get_val(Settings.VERBOSITY) + + # Some tests, but not all, do not correctly set default values + # # so we need to ensure all these values are available. + + for key in ( + Aircraft.CrewPayload.NUM_PASSENGERS, + Aircraft.CrewPayload.NUM_FIRST_CLASS, + Aircraft.CrewPayload.NUM_BUSINESS_CLASS, + Aircraft.CrewPayload.NUM_TOURIST_CLASS, + Aircraft.CrewPayload.Design.NUM_PASSENGERS, + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS,): + if key not in aviary_options: + aviary_options.set_val(key, _MetaData[key]['default_value']) + + # Sum passenger Counts for later checks and assignments + passenger_count = 0 + for key in (Aircraft.CrewPayload.NUM_FIRST_CLASS, + Aircraft.CrewPayload.NUM_BUSINESS_CLASS, + Aircraft.CrewPayload.NUM_TOURIST_CLASS): + passenger_count += aviary_options.get_val(key) + design_passenger_count = 0 + for key in (Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS): + design_passenger_count += aviary_options.get_val(key) + + # Create summary value (num_pax) if it was not assigned by the user + # or if it was set to it's default value of zero + if passenger_count != 0 and aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) == 0: + aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, passenger_count) + if verbosity >= 2: + print("User has specified supporting values for NUM_PASSENGERS but has left NUM_PASSENGERS=0. Replacing NUM_PASSENGERS with passenger_count.") + if design_passenger_count != 0 and aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) == 0: + aviary_options.set_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, design_passenger_count) + if verbosity >= 2: + print("User has specified supporting values for Design.NUM_PASSENGERS but has left Design.NUM_PASSENGERS=0. Replacing Design.NUM_PASSENGERS with design_passenger_count.") + + num_pax = aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) + design_num_pax = aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) + + # Check summary data against individual data if individual data was entered + if passenger_count != 0 and num_pax != passenger_count: + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: NUM_PASSENGERS ({aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS)}) does not equal the sum of first class + business class + tourist class passengers (total of {passenger_count}).") + if design_passenger_count != 0 and design_num_pax != design_passenger_count: + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: Design.NUM_PASSENGERS ({aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS)}) does not equal the sum of design first class + business class + tourist class passengers (total of {design_passenger_count}).") + + # Fail if incorrect data sets were provided: + # have you give us enough info to determine where people were sitting vs. designed seats + if num_pax != 0 and design_passenger_count != 0 and passenger_count == 0: + raise om.AnalysisError( + f"ERROR: In preprocessor.py: The user has specified CrewPayload.NUM_PASSENGERS, and how many of what types of seats are on the aircraft." + f"However, the user has not specified where those passengers are sitting." + f"User must specify CrewPayload.FIRST_CLASS, CrewPayload.NUM_BUSINESS_CLASS, NUM_TOURIST_CLASS in aviary_values.") + # where are the people sitting? is first class full? We know how many seats are in each class. + if design_num_pax != 0 and passenger_count != 0 and design_passenger_count == 0: + raise om.AnalysisError( + f"ERROR: In preprocessor.py: The user has specified Design.NUM_PASSENGERS, and has specified how many people are sitting in each class of seats." + f"However, the user has not specified how many seats of each class exist in the aircraft." + f"User must specify Design.FIRST_CLASS, Design.NUM_BUSINESS_CLASS, Design.NUM_TOURIST_CLASS in aviary_values.") + # we don't know which classes this aircraft has been design for. How many 1st class seats are there? + + # Copy data over if only one set of data exists + # User has given detailed values for 1TB as flow and NO design values at all + if passenger_count != 0 and design_num_pax == 0 and design_passenger_count == 0: + if verbosity >= 2: + print( + "User has not input design passengers data. Assuming design is equal to as-flow passenger data.") + aviary_options.set_val( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, passenger_count) + aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, + aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS)) + aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, + aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS)) + aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, + aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS)) + # user has not supplied detailed information on design but has supplied summary information on passengers + elif num_pax != 0 and design_num_pax == 0: + if verbosity >= 2: + print("User has specified as-flown NUM_PASSENGERS but not how many passengers the aircraft was designed for in Design.NUM_PASSENGERS. Assuming they are equal.") + aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, num_pax) + elif design_passenger_count != 0 and num_pax == 0 and passenger_count == 0: + if verbosity >= 1: + print("User has specified Design.NUM_* passenger values but CrewPyaload.NUM_* has been left blank or set to zero.") + print( + "Assuming they are equal to maintain backwards compatibility with GASP and FLOPS output files.") + print("If you intended to have no passengers on this flight, please set Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS to zero in aviary_values.") aviary_options.set_val( - Aircraft.CrewPayload.NUM_PASSENGERS, passenger_count) - else: - passenger_count = aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS) - # check in here to ensure that in this case passenger count is the sum of the first class, business class, and tourist class counts. - passenger_check = aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS) - passenger_check += aviary_options.get_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS) - passenger_check += aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS) - # only perform check if at least one passenger class is entered - if passenger_check > 0 and passenger_count != passenger_check: - raise om.AnalysisError( - f"ERROR: In preprocesssors.py: passenger_count ({passenger_count}) does not equal the sum of first class + business class + tourist class passengers (total of {passenger_check}).") + Aircraft.CrewPayload.NUM_PASSENGERS, design_passenger_count) + aviary_options.set_val(Aircraft.CrewPayload.NUM_FIRST_CLASS, + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS)) + aviary_options.set_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS, + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS)) + aviary_options.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, + aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS)) + # user has not supplied detailed information on design but has supplied summary information on passengers + elif design_num_pax != 0 and num_pax == 0: + if verbosity >= 1: + print("User has specified Design.NUM_PASSENGERS but CrewPayload.NUM_PASSENGERS has been left blank or set to zero.") + print( + "Assuming they are equal to maintain backwards compatibility with GASP and FLOPS output files.") + print("If you intended to have no passengers on this flight, please set Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS to zero in aviary_values.") + aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, design_num_pax) + + # Performe checks on the final data tables to ensure Design is always large then As-Flow + if aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) < aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS): + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: NUM_FIRST_CLASS ({aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS)}) is larger than the number of seats set by Design.NUM_FIRST_CLASS ({aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS)}) .") + if aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) < aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS): + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: NUM_BUSINESS_CLASS ({aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS)}) is larger than the number of seats set by Design.NUM_BUSINESS_CLASS ({aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS)}) .") + if aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) < aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS): + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: NUM_TOURIST_CLASS ({aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS)}) is larger than the number of seats set by Design.NUM_TOURIST_CLASS ({aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS)}) .") + if aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) < aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS): + raise om.AnalysisError( + f"ERROR: In preprocesssors.py: NUM_PASSENGERS ({aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS)}) is larger than the number of seats set by Design.NUM_PASSENGERS ({aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS)}) .") if Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS not in aviary_options: flight_attendants_count = 0 # assume no passengers diff --git a/aviary/utils/process_input_decks.py b/aviary/utils/process_input_decks.py index 6057925fe..634b21140 100644 --- a/aviary/utils/process_input_decks.py +++ b/aviary/utils/process_input_decks.py @@ -286,7 +286,7 @@ def initialization_guessing(aircraft_values: AviaryValues, initialization_guesse Updated aircraft values and initial guesses. """ problem_type = aircraft_values.get_val(Settings.PROBLEM_TYPE) - num_pax = aircraft_values.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) + num_pax = aircraft_values.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) reserve_val = aircraft_values.get_val( Aircraft.Design.RESERVE_FUEL_ADDITIONAL, units='lbm') reserve_frac = aircraft_values.get_val( 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 4ac7d0990..8489b3a93 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 @@ -41,13 +41,13 @@ def setUp(self): "constrain_final": False, "fix_duration": False, "initial_bounds": ((0.0, 0.0), "min"), - "duration_bounds": ((10.0, 30.0), "min"), + "duration_bounds": ((10., 30.), "min"), }, }, 'post_mission': { 'include_landing': False, 'external_subsystems': [], - }, + } } def test_subsystems_in_a_mission(self): @@ -56,8 +56,7 @@ def test_subsystems_in_a_mission(self): prob = av.AviaryProblem() prob.load_inputs( - "models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv", phase_info - ) + "models/test_aircraft/aircraft_for_bench_FwFm_with_electric.csv", phase_info) # Preprocess inputs prob.check_and_preprocess_inputs() @@ -84,7 +83,6 @@ def test_subsystems_in_a_mission(self): prob.set_val(av.Aircraft.Battery.PACK_ENERGY_DENSITY, 550, units='kJ/kg') prob.set_val(av.Aircraft.Battery.PACK_MASS, 1000, units='lbm') prob.set_val(av.Aircraft.Battery.ADDITIONAL_MASS, 115, units='lbm') - prob.set_val(av.Aircraft.Battery.EFFICIENCY, 0.95, units='unitless') prob.run_aviary_problem() @@ -94,23 +92,10 @@ def test_subsystems_in_a_mission(self): units='kW*h', ) fuel_burned = prob.get_val(av.Mission.Summary.FUEL_BURNED, units='lbm') - soc = prob.get_val( - 'traj.cruise.rhs_all.battery.battery_state_of_charge', units='unitless' - ) # Check outputs - # indirectly check mission trajectory by checking total fuel/electric split assert_near_equal(electric_energy_used[-1], 38.60538132, 1.e-7) assert_near_equal(fuel_burned, 676.87235486, 1.e-7) - # check battery state-of-charge over mission - assert_near_equal( - soc, - [0.99999578, 0.97551324, 0.94173584, 0.93104625, 0.93104625, - 0.8810605, 0.81210498, 0.79028433, 0.79028433, 0.73088701, - 0.64895148, 0.62302415, 0.62302415, 0.57309323, 0.50421334, - 0.48241661, 0.48241661, 0.45797918, 0.42426402, 0.41359413], - 1e-7, - ) if __name__ == "__main__": diff --git a/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py b/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py index 34b883d7f..1c10dc162 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_GwGm.py @@ -212,7 +212,7 @@ def test_bench_GwGm_shooting(self): ) assert_near_equal( - prob.get_val(Mission.Summary.RANGE, units='NM'), 3774.3, tolerance=rtol + prob.get_val(Mission.Summary.RANGE, units='NM'), 3675.0, tolerance=rtol ) assert_near_equal( diff --git a/aviary/validation_cases/validation_data/flops_data/FLOPS_Test_Data.py b/aviary/validation_cases/validation_data/flops_data/FLOPS_Test_Data.py index 94e0388e6..fc2c906b5 100644 --- a/aviary/validation_cases/validation_data/flops_data/FLOPS_Test_Data.py +++ b/aviary/validation_cases/validation_data/flops_data/FLOPS_Test_Data.py @@ -16,6 +16,7 @@ FLOPS_Test_Data['LargeSingleAisle2FLOPS'] = LargeSingleAisle2FLOPS FLOPS_Test_Data['LargeSingleAisle2FLOPSdw'] = LargeSingleAisle2FLOPSdw FLOPS_Test_Data['LargeSingleAisle2FLOPSalt'] = LargeSingleAisle2FLOPSalt +# when importing get_flops_inputs(), this is the data file that is loaded as the default aviary_values FLOPS_Test_Data['N3CC'] = N3CC # We don't have full date for this yet, but might still want to run one in a single unit test. diff --git a/aviary/variable_info/enums.py b/aviary/variable_info/enums.py index e4583609f..75d7392b7 100644 --- a/aviary/variable_info/enums.py +++ b/aviary/variable_info/enums.py @@ -151,10 +151,18 @@ class ProblemType(Enum): weight and empty weight constant. Using the specified actual gross weight, it will then find the maximum distance the off-design aircraft can fly. + + MULTI_MISSION: Similar to a SIZING mission, however it varies the + design gross weight and actual gross weight across multiple missions + to and closes design range for each mission. This causes the empty + weight and the fuel weight to change. The final result will be a + single empty weight, for all the different missions, and multiple + values for fuel weight, unique to each mission. """ SIZING = 'sizing' ALTERNATE = 'alternate' FALLOUT = 'fallout' + MULTI_MISSION = 'multimission' class SpeedType(Enum): diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index ac028334e..673383eb3 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -699,6 +699,70 @@ default_value=0.7, ) +# ___ _ +# | \ ___ ___ (_) __ _ _ _ +# | |) | / -_) (_-< | | / _` | | ' \ +# |___/ \___| /__/ |_| \__, | |_||_| +# ====================== |___/ ====== + +add_meta_data( + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": 'WTIN.NPB', # ['&DEFINE.WTIN.NPB', 'WTS.NPB'], + "LEAPS1": 'aircraft.inputs.L0_crew_and_payload.business_class_count' + }, + units='unitless', + desc='number of business class passengers that the aircraft is designed to accommodate', + types=int, + option=True, + default_value=0, # AviaryValues.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS), +) + +add_meta_data( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": 'WTIN.NPF', # ['&DEFINE.WTIN.NPF', 'WTS.NPF'], + "LEAPS1": 'aircraft.inputs.L0_crew_and_payload.first_class_count' + }, + units='unitless', + desc='number of first class passengers that the aircraft is designed to accommodate', + types=int, + option=True, + default_value=0, +) + +add_meta_data( + Aircraft.CrewPayload.Design.NUM_PASSENGERS, + meta_data=_MetaData, + historical_name={"GASP": 'INGASP.PAX', # number of passenger seats excluding crew + "FLOPS": None, # ['CSTDAT.NSV', '~WEIGHT.NPASS', '~WTSTAT.NPASS'], + "LEAPS1": 'aircraft.outputs.L0_crew_and_payload.passenger_count' + }, + units='unitless', + desc='total number of passengers that the aircraft is designed to accommodate', + option=True, + default_value=0, + types=int, +) + + +# TODO rename to economy? +add_meta_data( + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, + meta_data=_MetaData, + historical_name={"GASP": None, + "FLOPS": 'WTIN.NPT', # ['&DEFINE.WTIN.NPT', 'WTS.NPT'], + "LEAPS1": 'aircraft.inputs.L0_crew_and_payload.tourist_class_count' + }, + units='unitless', + desc='number of tourist class passengers that the aircraft is designed to accommodate', + types=int, + option=True, + default_value=0, +) + add_meta_data( # Note user override # - see also: Aircraft.CrewPayload.FLIGHT_CREW_MASS_SCALER @@ -788,8 +852,8 @@ Aircraft.CrewPayload.NUM_BUSINESS_CLASS, meta_data=_MetaData, historical_name={"GASP": None, - "FLOPS": 'WTIN.NPB', # ['&DEFINE.WTIN.NPB', 'WTS.NPB'], - "LEAPS1": 'aircraft.inputs.L0_crew_and_payload.business_class_count' + "FLOPS": None, # ['&DEFINE.WTIN.NPB', 'WTS.NPB'], + "LEAPS1": None, # 'aircraft.inputs.L0_crew_and_payload.business_class_count' }, units='unitless', desc='number of business class passengers', @@ -802,8 +866,8 @@ Aircraft.CrewPayload.NUM_FIRST_CLASS, meta_data=_MetaData, historical_name={"GASP": None, - "FLOPS": 'WTIN.NPF', # ['&DEFINE.WTIN.NPF', 'WTS.NPF'], - "LEAPS1": 'aircraft.inputs.L0_crew_and_payload.first_class_count' + "FLOPS": None, # ['&DEFINE.WTIN.NPF', 'WTS.NPF'], + "LEAPS1": None, # 'aircraft.inputs.L0_crew_and_payload.first_class_count' }, units='unitless', desc='number of first class passengers', @@ -864,9 +928,9 @@ add_meta_data( Aircraft.CrewPayload.NUM_PASSENGERS, meta_data=_MetaData, - historical_name={"GASP": 'INGASP.PAX', + historical_name={"GASP": None, # 'INGASP.PAX' here we assume previous studies were changing Design.num_pax not as-flown "FLOPS": None, # ['CSTDAT.NSV', '~WEIGHT.NPASS', '~WTSTAT.NPASS'], - "LEAPS1": 'aircraft.outputs.L0_crew_and_payload.passenger_count' + "LEAPS1": None, # 'aircraft.outputs.L0_crew_and_payload.passenger_count' }, units='unitless', desc='total number of passengers', @@ -880,8 +944,8 @@ Aircraft.CrewPayload.NUM_TOURIST_CLASS, meta_data=_MetaData, historical_name={"GASP": None, - "FLOPS": 'WTIN.NPT', # ['&DEFINE.WTIN.NPT', 'WTS.NPT'], - "LEAPS1": 'aircraft.inputs.L0_crew_and_payload.tourist_class_count' + "FLOPS": None, # ['&DEFINE.WTIN.NPT', 'WTS.NPT'], + "LEAPS1": None, # 'aircraft.inputs.L0_crew_and_payload.tourist_class_count' }, units='unitless', desc='number of tourist class passengers', @@ -1015,7 +1079,6 @@ # __/ | # |___/ # ========================================= - add_meta_data( Aircraft.Design.BASE_AREA, meta_data=_MetaData, @@ -1270,7 +1333,7 @@ }, units='unitless', desc='ratio of maximum landing mass to maximum takeoff mass', - default_value=None, + default_value=0.9, ) add_meta_data( @@ -3889,7 +3952,8 @@ "LEAPS1": 'aircraft.inputs.L0_overrides.landing_gear_main_weight' }, units='unitless', - desc='mass scaler of the main landing gear structure' + desc='mass scaler of the main landing gear structure', + default_value=1.0, ) add_meta_data( diff --git a/aviary/variable_info/variables.py b/aviary/variable_info/variables.py index 9e576cd1a..2cdb61d30 100644 --- a/aviary/variable_info/variables.py +++ b/aviary/variable_info/variables.py @@ -128,6 +128,12 @@ class CrewPayload: WATER_MASS_PER_OCCUPANT = 'aircraft:crew_and_payload:water_mass_per_occupant' WING_CARGO = 'aircraft:crew_and_payload:wing_cargo' + class Design: + NUM_BUSINESS_CLASS = 'aircraft:crew_and_payload:design:num_business_class' + NUM_FIRST_CLASS = 'aircraft:crew_and_payload:design:num_first_class' + NUM_TOURIST_CLASS = 'aircraft:crew_and_payload:design:num_tourist_class' + NUM_PASSENGERS = 'aircraft:crew_and_payload:design:num_passengers' + class Design: # These variables are values that do not fall into a particular aircraft # component.