diff --git a/aviary/core/aviary_group.py b/aviary/core/aviary_group.py index 69f19ca325..b0255d1f1d 100644 --- a/aviary/core/aviary_group.py +++ b/aviary/core/aviary_group.py @@ -1290,7 +1290,7 @@ def add_design_variables(self, problem_type: ProblemType = None, verbosity=None) and another for the gross mass of the aircraft computed during the mission. A constraint is also added to ensure that the residual range is zero. - If solving an alternate problem, only a design variable for the gross mass of the aircraft + If solving an OFF_DESIGN_MIN_FUEL problem, only a design variable for the gross mass of the aircraft computed during the mission is added. A constraint is also added to ensure that the residual range is zero. @@ -1364,7 +1364,7 @@ def add_design_variables(self, problem_type: ProblemType = None, verbosity=None) if self.require_range_residual: self.add_constraint(Mission.Constraints.RANGE_RESIDUAL, equals=0, ref=1000) - elif problem_type is ProblemType.ALTERNATE: + elif problem_type is ProblemType.OFF_DESIGN_MIN_FUEL: # target range problem # fixed vehicle (design GTOW) but variable actual GTOW for off-design # get the design gross mass and set as the upper bound for the gross mass design variable @@ -1379,10 +1379,12 @@ def add_design_variables(self, problem_type: ProblemType = None, verbosity=None) self.add_constraint(Mission.Constraints.RANGE_RESIDUAL, equals=0, ref=1000) - elif problem_type is ProblemType.FALLOUT: + elif problem_type is ProblemType.OFF_DESIGN_MAX_RANGE: # fixed vehicle gross mass aviary finds optimal trajectory and maximum range if verbosity >= Verbosity.VERBOSE: - print('No additional aircraft design variables added for Fallout missions') + print( + 'No additional aircraft design variables added for OFF_DESIGN_MAX_RANGE missions' + ) elif problem_type is ProblemType.MULTI_MISSION: self.add_design_var( diff --git a/aviary/core/aviary_problem.py b/aviary/core/aviary_problem.py index 53244d6786..efe80d5173 100644 --- a/aviary/core/aviary_problem.py +++ b/aviary/core/aviary_problem.py @@ -560,7 +560,7 @@ def add_design_variables(self, verbosity=None): and another for the gross mass of the aircraft computed during the mission. A constraint is also added to ensure that the residual range is zero. - If solving an alternate problem, only a design variable for the gross mass of the aircraft + If solving an OFF_DESIGN_MIN_FUEL problem, only a design variable for the gross mass of the aircraft computed during the mission is added. A constraint is also added to ensure that the residual range is zero. @@ -694,10 +694,10 @@ def add_objective(self, objective_type=None, ref=None, verbosity=None): if self.problem_type is ProblemType.SIZING: self.model.add_objective(Mission.Objectives.FUEL, ref=ref) - elif self.problem_type is ProblemType.ALTERNATE: + elif self.problem_type is ProblemType.OFF_DESIGN_MIN_FUEL: self.model.add_objective(Mission.Objectives.FUEL, ref=ref) - elif self.problem_type is ProblemType.FALLOUT: + elif self.problem_type is ProblemType.OFF_DESIGN_MAX_RANGE: # if ref > 0: # # Maximize range. # ref = -ref @@ -786,8 +786,8 @@ def add_composite_objective(self, *args, ref: float = None): If 2-tuple: (model, output) or (output, weight) If 1-tuple: (output) or 'fuel', 'fuel_burned', 'mass', 'range', 'time' If empty, information will be populated based on problem_type: - - If ProblemType = FALLOUT, objective = Mission.Objectives.RANGE - - If ProblemType = Sizing or Alternate, objective = Mission.Objectives.FUEL + - If ProblemType = OFF_DESIGN_MAX_RANGE, objective = Mission.Objectives.RANGE + - If ProblemType = Sizing or OFF_DESIGN_MIN_FUEL, objective = Mission.Objectives.FUEL Example inputs can be any of the following: ('fuel') @@ -858,9 +858,9 @@ def add_composite_objective(self, *args, ref: float = None): # in some cases the users provides no input and we can derive the objectie from the problem type: elif self.model.problem_type is ProblemType.SIZING: model, output, weight = default_model, Mission.Objectives.FUEL, default_weight - elif self.model.problem_type is ProblemType.ALTERNATE: + elif self.model.problem_type is ProblemType.OFF_DESIGN_MIN_FUEL: model, output, weight = default_model, Mission.Objectives.FUEL, default_weight - elif self.model.problem_type is ProblemType.FALLOUT: + elif self.model.problem_type is ProblemType.OFF_DESIGN_MAX_RANGE: model, output, weight = default_model, Mission.Objectives.RANGE, default_weight else: raise ValueError( @@ -868,7 +868,7 @@ def add_composite_objective(self, *args, ref: float = None): f'Each argument must be one of the following: ' f'(output), (output, weight), (model, output), or (model, output, weight).' f'Outputs can be from the variable meta data, or can be: fuel_burned, fuel' - f'Or problem type must be set to SIZING, ALTERNATE, or FALLOUT' + f'Or problem type must be set to SIZING, OFF_DESIGN_MIN_FUEL, or OFF_DESIGN_MAX_RANGE' ) objectives.append((model, output, weight)) # objectives = [ @@ -1321,10 +1321,10 @@ def run_off_design_mission( for total cargo mass. mission_gross_mass : float, optional Gross mass of aircraft flying off-design mission, in pounds-mass. Defaults to design - gross mass. For missions where mass is solved for (such as ALTERNATE missions), this is + gross mass. For missions where mass is solved for (such as OFF_DESIGN_MIN_FUEL missions), this is the initial guess. mission_range : float, optional - [ALTERNATE missions only] + [OFF_DESIGN_MIN_FUEL missions only] Sets fixed range of flown off-design mission, in nautical miles. Unused for other mission types. optimizer : string, optional @@ -1434,12 +1434,12 @@ def run_off_design_mission( # NOTE once load_inputs is run, phase info details are stored in prob.model.configurator, # meaning any phase_info changes that happen after load inputs is ignored - if problem_type is ProblemType.ALTERNATE: + if problem_type is ProblemType.OFF_DESIGN_MIN_FUEL: # Set mission range, aviary will calculate required fuel if mission_range is None: if verbosity >= Verbosity.VERBOSE: warnings.warn( - 'Alternate problem type requested with no specified range. Using design ' + 'OFF_DESIGN_MIN_FUEL problem type requested with no specified range. Using design ' 'mission range for the off-design mission.' ) mission_range = self.get_val(Mission.RANGE, units='NM')[0] @@ -1453,9 +1453,9 @@ def run_off_design_mission( off_design_prob.load_inputs(inputs, phase_info, verbosity=verbosity) # Update inputs that are specific to problem type - # Some Alternate problem changes had to happen before load_inputs, all fallout problem + # Some OFF_DESIGN_MIN_FUEL problem changes had to happen before load_inputs, all OFF_DESIGN_MAX_RANGE problem # changes must come after load_inputs - if problem_type is ProblemType.ALTERNATE: + if problem_type is ProblemType.OFF_DESIGN_MIN_FUEL: off_design_prob.aviary_inputs.set_val(Mission.RANGE, mission_range, units='NM') # set initial guess for Mission.GROSS_MASS to help optimizer with new design # variable bounds. @@ -1467,12 +1467,12 @@ def run_off_design_mission( Mission.GROSS_MASS, mission_gross_mass * 0.9, units='lbm' ) - elif problem_type is ProblemType.FALLOUT: + elif problem_type is ProblemType.OFF_DESIGN_MAX_RANGE: # Set mission fuel and calculate gross weight, aviary will calculate range if mission_gross_mass is None: if verbosity >= Verbosity.VERBOSE: warnings.warn( - 'Fallout problem type requested with no specified gross mass. Using design ' + 'OFF_DESIGN_MAX_RANGE problem type requested with no specified gross mass. Using design ' 'takeoff gross mass for the off-design mission.' ) mission_gross_mass = self.get_val(Aircraft.Design.GROSS_MASS, units='lbm')[0] @@ -1567,10 +1567,10 @@ def run_payload_range(self, verbosity=None): """ This function runs Payload/Range analysis for the aircraft model. - For an aircraft model that has been sized with a mission has has successfully converged, + For an aircraft model that has been sized with a mission and has successfully converged, this function will adjust the given phase information, assuming that there is a phase named 'cruise' and elongates the duration bounds to allow the optimizer - to converge for the max economic and ferry missions. + to converge for the 'max fuel + payload' and 'ferry' missions. Parameters ---------- @@ -1580,7 +1580,7 @@ def run_payload_range(self, verbosity=None): Returns ------- payload_range_problems : tuple - Tuple containing the off-design AviaryProblems for the max economic and ferry ranges + Tuple containing the off-design AviaryProblems for the 'max fuel + payload' and 'ferry' ranges TODO currently does not account for reserve fuel """ @@ -1612,7 +1612,7 @@ def run_payload_range(self, verbosity=None): phase_info['pre_mission'] = self.model.pre_mission_info.copy() phase_info['post_mission'] = self.model.post_mission_info.copy() # This checks if the 'cruise' phase exists, then automatically extends duration bounds - # of the cruise stage to allow for the longer economic and ferry missions. + # of the cruise stage to allow for the longer off design missions. if phase_info['cruise']: min_duration = phase_info['cruise']['user_options']['time_duration_bounds'][0][0] max_duration = phase_info['cruise']['user_options']['time_duration_bounds'][0][1] @@ -1648,34 +1648,34 @@ def run_payload_range(self, verbosity=None): max_usable_fuel = fuel_capacity - unusable_fuel # An aircraft may be designed with fuel tank capacity that, if fully filled, would - # exceed MTOW. In that scenario, Max Economic Range and Ferry Range are the same, and + # exceed MTOW. In that scenario, 'Max Fuel + Payload' range and 'Ferry' range are the same, and # the point only needs to be run once. if operating_mass + max_usable_fuel < gross_mass: - # Point 3 (Max Economic Range): max fuel and remaining payload capacity + # Point 3 (Max Fuel + Payload Range): max fuel and remaining payload capacity # Assume proportional decrease in all cargo types (including number of passengers) # to make room for maximum fuel. Round pax count down to avoid loading over TOGW - economic_mission_total_payload = gross_mass - operating_mass - max_usable_fuel - payload_frac = economic_mission_total_payload / max_payload + max_fuel_pyld_total_payload = gross_mass - operating_mass - max_usable_fuel + payload_frac = max_fuel_pyld_total_payload / max_payload # Calculates Different payload quantities - economic_mission_wing_cargo = ( + max_fuel_pyld_wing_cargo = ( self.model.aviary_inputs.get_val(Aircraft.CrewPayload.WING_CARGO, 'lbm') * payload_frac ) - economic_mission_misc_cargo = ( + max_fuel_pyld_misc_cargo = ( self.model.aviary_inputs.get_val(Aircraft.CrewPayload.MISC_CARGO, 'lbm') * payload_frac ) - economic_mission_num_first = int( + max_fuel_pyld_num_first = int( self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) * payload_frac ) - economic_mission_num_bus = int( + max_fuel_pyld_num_bus = int( self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) * payload_frac ) - economic_mission_num_economy = int( + max_fuel_pyld_num_economy = int( self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_ECONOMY_CLASS) * payload_frac ) @@ -1683,26 +1683,28 @@ def run_payload_range(self, verbosity=None): # Passenger number rounding and potentially cargo container mass changing means # we don't know if we actually filled the aircraft to exactly TOGW yet. Need to use # "fill_cargo" flag in off-design call - economic_range_prob = self.economic_range_prob = self.run_off_design_mission( - problem_type=ProblemType.FALLOUT, - phase_info=phase_info, - num_first_class=economic_mission_num_first, - num_business=economic_mission_num_bus, - num_economy=economic_mission_num_economy, - wing_cargo=economic_mission_wing_cargo, - misc_cargo=economic_mission_misc_cargo, - name=self._name + '_max_economic_range', - fill_cargo=True, - verbosity=verbosity, + max_fuel_pyld_range_prob = self.max_fuel_pyld_range_prob = ( + self.run_off_design_mission( + problem_type=ProblemType.OFF_DESIGN_MAX_RANGE, + phase_info=phase_info, + num_first_class=max_fuel_pyld_num_first, + num_business=max_fuel_pyld_num_bus, + num_economy=max_fuel_pyld_num_economy, + wing_cargo=max_fuel_pyld_wing_cargo, + misc_cargo=max_fuel_pyld_misc_cargo, + name=self._name + '_max_fuel_plus_payload_range', + fill_cargo=True, + verbosity=verbosity, + ) ) - # Pull the payload and range values from the fallout mission - payload_3 = float( - economic_range_prob.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS) - ) + # Pull the payload and range values from the OFF_DESIGN_MAX_RANGE mission + payload_3 = max_fuel_pyld_range_prob.get_val( + Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS + )[0] - range_3 = float(economic_range_prob.get_val(Mission.RANGE)) - fuel_3 = economic_range_prob.get_val(Mission.FUEL)[0] + range_3 = max_fuel_pyld_range_prob.get_val(Mission.RANGE)[0] + fuel_3 = max_fuel_pyld_range_prob.get_val(Mission.FUEL)[0] prob_3_skip = False else: @@ -1719,7 +1721,7 @@ def run_payload_range(self, verbosity=None): ferry_cargo_mass = None ferry_range_gross_mass = operating_mass + max_usable_fuel ferry_range_prob = self.ferry_range_prob = self.run_off_design_mission( - problem_type=ProblemType.FALLOUT, + problem_type=ProblemType.OFF_DESIGN_MAX_RANGE, phase_info=phase_info, num_first_class=0, num_business=0, @@ -1733,23 +1735,23 @@ def run_payload_range(self, verbosity=None): verbosity=verbosity, ) - payload_4 = float(ferry_range_prob.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)) - range_4 = float(ferry_range_prob.get_val(Mission.RANGE)) + payload_4 = ferry_range_prob.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0] + range_4 = ferry_range_prob.get_val(Mission.RANGE)[0] fuel_4 = ferry_range_prob.get_val(Mission.FUEL)[0] - # if economic mission was skipped, economic_range_prob is the same as ferry_range_prob + # if max fuel + payload mission was skipped, max_fuel_pyld_range_prob is the same as ferry_range_prob if prob_3_skip: - economic_range_prob = ferry_range_prob + max_fuel_pyld_range_prob = ferry_range_prob payload_3 = payload_4 range_3 = range_4 - # Check if fallout missions ran successfully before writing to csv file + # Check if OFF_DESIGN_MAX_RANGE missions ran successfully before writing to csv file # If both missions ran successfully, writes the payload/range data to a csv file self.payload_range_data = payload_range_data = NamedValues() - if ferry_range_prob.result.success and economic_range_prob.result.success: + if ferry_range_prob.result.success and max_fuel_pyld_range_prob.result.success: payload_range_data.set_val( 'Mission Name', - ['Zero Fuel', 'Design Mission', 'Max Economic Mission', 'Ferry Mission'], + ['Zero Fuel', 'Design Mission', 'Max Fuel + Payload Mission', 'Ferry Mission'], ) payload_range_data.set_val( 'Payload', [payload_1, payload_2, payload_3, payload_4], 'lbm' @@ -1767,10 +1769,10 @@ def run_payload_range(self, verbosity=None): for item in payload_range_data: print(f'{item[0]} ({item[1][1]}): {item[1][0]}') - return (economic_range_prob, ferry_range_prob) + return (max_fuel_pyld_range_prob, ferry_range_prob) else: warnings.warn( - 'One or both of the fallout missions did not run successfully; payload/range ' + 'One or both of the OFF_DESIGN_MAX_RANGE missions did not run successfully; payload/range ' 'diagram was not generated.' ) else: diff --git a/aviary/docs/build_source_docs.py b/aviary/docs/build_source_docs.py index 0d623e786a..8f1d3bb13b 100644 --- a/aviary/docs/build_source_docs.py +++ b/aviary/docs/build_source_docs.py @@ -3,6 +3,7 @@ IGNORE_LIST = [] packages = [ + 'core', 'interface', 'utils', 'variable_info', diff --git a/aviary/docs/examples/off_design_missions.ipynb b/aviary/docs/examples/off_design_missions.ipynb index 69f34a7880..a26b90af58 100644 --- a/aviary/docs/examples/off_design_missions.ipynb +++ b/aviary/docs/examples/off_design_missions.ipynb @@ -27,10 +27,11 @@ "metadata": {}, "source": [ "# Off-Design Missions\n", - "Once an aircraft has been sized, it is common to test how it performs on missions different than its design mission. In Aviary, these missions (which do not modify the aircraft design in any way) are referred to as off-design missions. There are two main ways that off-design missions can be run: immediately following a design mission run in a Python script, or at a later time by loading the results of a perviously run design mission.\n", + "Once an aircraft has been sized, it is common to test how it performs on missions different than its design mission. In Aviary, these missions (which do not modify the aircraft design in any way) are referred to as off-design missions.\n", + "There are two main ways that off-design missions can be run: immediately following a design mission run in a Python script, or at a later time by loading the results of a perviously run design mission.\n", "\n", "## Running Off-Design Missions in the Same Script\n", - "It is fairly simple to run the initial aircraft sizing and all desired off-design missions in the same script. The following script runs the inital design problem - it is almost identical to the [simple mission example](./simple_mission.ipynb), but in this case we modify the target range to be longer. This is done so when run the aircraft on a shorter range off-design mission later there is a more obvious change in takeoff gross mass and mission fuel burn. We also save the {glue:md}`AviaryProblem` object returned by the {glue:md}`run_aviary` function call. We will use this later to run our off-design missions." + "It is fairly simple to run the initial aircraft sizing and all desired off-design missions in the same script. The following script runs the inital design problem - it is almost identical to the [simple mission example](./simple_mission.ipynb), but in this case we modify the target range to be longer. This is done so when we run the aircraft on a shorter range off-design mission later there is a more obvious change in takeoff gross mass and mission fuel burn. We also save the {glue:md}`AviaryProblem` object returned by the {glue:md}`run_aviary` function call. We will use this later to run our off-design missions." ] }, { @@ -60,7 +61,7 @@ "id": "f1a098f9", "metadata": {}, "source": [ - "Now we will run two off-design missions, one of each pre-defined type in Aviary. First is a \"fallout\" mission, which is a mission where the takeoff gross mass of the aircraft is pre-defined, and the aircraft is flown as far as it can with the amount of fuel allowed under that gross mass limit. Second is an \"alternate\" mission, where the range of the mission is known, and Aviary solves the exact amount of fuel (and therefore the takeoff gross mass) needed for the aircraft to fly that range.\n", + "Now we will run two off-design missions, one of each pre-defined type in Aviary. First is an \"off_deign_max_range\" mission, which is a mission where the takeoff gross mass of the aircraft is pre-defined, and the aircraft is flown as far as it can with the amount of fuel allowed under that gross mass limit. Second is an \"off_design_min_fuel\" mission, where the range of the mission is known, and Aviary solves the minimum amount of fuel (and therefore the takeoff gross mass) needed for the aircraft to fly that range.\n", "\n", "An off-design mission is run using the {glue:md}`run_off_design_mission()` method of {glue:md}`AviaryProblem`. This method has significantly more flexibility than demonstrated in this example, including the ability to change the passengers and cargo loaded on the aircraft, and the option to change the mission profile flown via providing new phase_info. \n", "\n", @@ -74,14 +75,16 @@ "metadata": {}, "outputs": [], "source": [ - "# Fallout Mission - fixed-mass, varying range\n", - "fallout_prob = design_prob.run_off_design_mission(\n", - " problem_type='fallout', mission_gross_mass=115000, name='fallout_mission'\n", + "# OFF_DESIGN_MAX_RANGE Mission - fixed mass, solve for max range\n", + "off_design_max_range_prob = design_prob.run_off_design_mission(\n", + " problem_type='off_design_max_range',\n", + " mission_gross_mass=115000,\n", + " name='off_design_max_range_mission',\n", ")\n", "\n", - "# Alternate Mission - fixed range, varying fuel\n", - "alternate_prob = design_prob.run_off_design_mission(\n", - " problem_type='alternate', mission_range=1250, name='alternate_mission'\n", + "# OFF_DESIGN_MIN_FUEL Mission - fixed range, solve for min fuel\n", + "off_design_min_fuel_prob = design_prob.run_off_design_mission(\n", + " problem_type='off_design_min_fuel', mission_range=1250, name='off_design_min_fuel_mission'\n", ")" ] }, @@ -90,7 +93,7 @@ "id": "d4f57227", "metadata": {}, "source": [ - "Once both missions are complete, we can compare the difference between the design and off-design missions. We are expecting fundamental design parameters of the aircraft, such as its maximum takeoff gross mass and its design range to remain unchanged. However, the takeoff gross mass for each particular mission should change, due to a change in the amount of fuel the aircraft is loaded with before takeoff, as well as mission range. We should see a reduction in both of these quantities for the off-design missions, with the pre-defined properties (gross mass for the fallout mission, mission range for the alternate mission) matching what we asked Aviary to fly." + "Once both missions are complete, we can compare the difference between the design and off-design missions. We are expecting fundamental design parameters of the aircraft, such as its maximum takeoff gross mass and its design range to remain unchanged. However, the takeoff gross mass for each particular mission should change, due to a change in the amount of fuel the aircraft is loaded with before takeoff, as well as mission range. We should see a reduction in both of these quantities for the off-design missions, with the pre-defined properties (gross mass for the off_design_max_range mission, mission range for the off_design_min_fuel mission) matching what we asked Aviary to fly." ] }, { @@ -114,25 +117,37 @@ "print(f'Design Gross Mass = {design_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm')\n", "print(f'Mission Gross Mass = {design_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')\n", "\n", - "print('\\nFallout Results')\n", + "print('\\nOFF_DESIGN_MAX_RANGE Results')\n", "print('---------------')\n", - "print(f'Design Range = {fallout_prob.get_val(av.Aircraft.Design.RANGE)[0]} nmi')\n", - "print(f'Mission Range = {fallout_prob.get_val(av.Mission.RANGE)[0]} nmi')\n", - "print(f'Fuel Mass = {fallout_prob.get_val(av.Mission.TOTAL_FUEL)[0]} lbm')\n", - "print(f'Operating Empty Mass = {fallout_prob.get_val(av.Mission.OPERATING_MASS)[0]} lbm')\n", - "print(f'Payload Mass = {fallout_prob.get_val(av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0]} lbm')\n", - "print(f'Design Gross Mass = {fallout_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm')\n", - "print(f'Mission Gross Mass = {fallout_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')\n", - "\n", - "print('\\nAlternate Results')\n", + "print(f'Design Range = {off_design_max_range_prob.get_val(av.Aircraft.Design.RANGE)[0]} nmi')\n", + "print(f'Mission Range = {off_design_max_range_prob.get_val(av.Mission.RANGE)[0]} nmi')\n", + "print(f'Fuel Mass = {off_design_max_range_prob.get_val(av.Mission.TOTAL_FUEL)[0]} lbm')\n", + "print(\n", + " f'Operating Empty Mass = {off_design_max_range_prob.get_val(av.Mission.OPERATING_MASS)[0]} lbm'\n", + ")\n", + "print(\n", + " f'Payload Mass = {off_design_max_range_prob.get_val(av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0]} lbm'\n", + ")\n", + "print(\n", + " f'Design Gross Mass = {off_design_max_range_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm'\n", + ")\n", + "print(f'Mission Gross Mass = {off_design_max_range_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')\n", + "\n", + "print('\\nOFF_DESIGN_MIN_FUEL Results')\n", "print('-----------------')\n", - "print(f'Design Range = {alternate_prob.get_val(av.Aircraft.Design.RANGE)[0]} nmi')\n", - "print(f'Mission Range = {alternate_prob.get_val(av.Mission.RANGE)[0]} nmi')\n", - "print(f'Fuel Mass = {alternate_prob.get_val(av.Mission.TOTAL_FUEL)[0]} lbm')\n", - "print(f'Operating Empty Mass = {alternate_prob.get_val(av.Mission.OPERATING_MASS)[0]} lbm')\n", - "print(f'Payload Mass = {alternate_prob.get_val(av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0]} lbm')\n", - "print(f'Design Gross Mass = {alternate_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm')\n", - "print(f'Mission Gross Mass = {alternate_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')" + "print(f'Design Range = {off_design_min_fuel_prob.get_val(av.Aircraft.Design.RANGE)[0]} nmi')\n", + "print(f'Mission Range = {off_design_min_fuel_prob.get_val(av.Mission.RANGE)[0]} nmi')\n", + "print(f'Fuel Mass = {off_design_min_fuel_prob.get_val(av.Mission.TOTAL_FUEL)[0]} lbm')\n", + "print(\n", + " f'Operating Empty Mass = {off_design_min_fuel_prob.get_val(av.Mission.OPERATING_MASS)[0]} lbm'\n", + ")\n", + "print(\n", + " f'Payload Mass = {off_design_min_fuel_prob.get_val(av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0]} lbm'\n", + ")\n", + "print(\n", + " f'Design Gross Mass = {off_design_min_fuel_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm'\n", + ")\n", + "print(f'Mission Gross Mass = {off_design_min_fuel_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')" ] }, { @@ -145,7 +160,7 @@ "\n", "This is done with a helper function {glue:md}`reload_aviary_problem()`. This function returns a fresh {glue:md}`AviaryProblem` loaded with all of the information from the sizing run. To reconstruct the design aircraft, {glue:md}`reload_aviary_problem` needs two pieces of information: a specific results file generated by Aviary after sizing an aircraft, and the original phase info used by the design mission.\n", "\n", - "Here we will reload the design problem, then use that reloaded problem to run the same \"fallout\" mission as before. When viewing the results, we can see they exactly match the results we got earlier." + "Here we will reload the design problem, then use that reloaded problem to run the same \"off_design_max_range\" mission as before. When viewing the results, we can see they exactly match the results we got earlier." ] }, { @@ -159,8 +174,10 @@ " 'advanced_single_aisle_FLOPS_out/reports/sizing_results.json', phase_info\n", ")\n", "\n", - "new_fallout_prob = design_prob.run_off_design_mission(\n", - " problem_type='fallout', mission_gross_mass=115000, name='fallout_mission'\n", + "new_off_design_max_range_prob = design_prob.run_off_design_mission(\n", + " problem_type='off_design_max_range',\n", + " mission_gross_mass=115000,\n", + " name='off_design_max_range_mission',\n", ")" ] }, @@ -175,17 +192,21 @@ }, "outputs": [], "source": [ - "print('\\nNew Fallout Results')\n", + "print('\\nNew OFF_DESIGN_MAX_RANGE Results')\n", "print('---------------')\n", - "print(f'Design Range = {new_fallout_prob.get_val(av.Aircraft.Design.RANGE)[0]} nmi')\n", - "print(f'Mission Range = {new_fallout_prob.get_val(av.Mission.RANGE)[0]} nmi')\n", - "print(f'Fuel Mass = {new_fallout_prob.get_val(av.Mission.TOTAL_FUEL)[0]} lbm')\n", - "print(f'Operating Empty Mass = {new_fallout_prob.get_val(av.Mission.OPERATING_MASS)[0]} lbm')\n", + "print(f'Design Range = {new_off_design_max_range_prob.get_val(av.Aircraft.Design.RANGE)[0]} nmi')\n", + "print(f'Mission Range = {new_off_design_max_range_prob.get_val(av.Mission.RANGE)[0]} nmi')\n", + "print(f'Fuel Mass = {new_off_design_max_range_prob.get_val(av.Mission.TOTAL_FUEL)[0]} lbm')\n", + "print(\n", + " f'Operating Empty Mass = {new_off_design_max_range_prob.get_val(av.Mission.OPERATING_MASS)[0]} lbm'\n", + ")\n", + "print(\n", + " f'Payload Mass = {new_off_design_max_range_prob.get_val(av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0]} lbm'\n", + ")\n", "print(\n", - " f'Payload Mass = {new_fallout_prob.get_val(av.Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS)[0]} lbm'\n", + " f'Design Gross Mass = {new_off_design_max_range_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm'\n", ")\n", - "print(f'Design Gross Mass = {new_fallout_prob.get_val(av.Aircraft.Design.GROSS_MASS)[0]} lbm')\n", - "print(f'Mission Gross Mass = {new_fallout_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')" + "print(f'Mission Gross Mass = {new_off_design_max_range_prob.get_val(av.Mission.GROSS_MASS)[0]} lbm')" ] }, { @@ -202,14 +223,14 @@ "if not design_prob.result.success:\n", " raise ValueError('Design problem failed')\n", "\n", - "if not fallout_prob.result.success:\n", - " raise ValueError('Fallout problem failed')\n", + "if not off_design_max_range_prob.result.success:\n", + " raise ValueError('OFF_DESIGN_MAX_RANGE problem failed')\n", "\n", - "if not alternate_prob.result.success:\n", - " raise ValueError('Alternate problem failed')\n", + "if not off_design_min_fuel_prob.result.success:\n", + " raise ValueError('OFF_DESIGN_MIN_FUEL problem failed')\n", "\n", - "if not new_fallout_prob.result.success:\n", - " raise ValueError('Fallout problem 2 failed')\n", + "if not new_off_design_max_range_prob.result.success:\n", + " raise ValueError('OFF_DESIGN_MAX_RANGE problem 2 failed')\n", "\n", "from openmdao.utils.assert_utils import assert_near_equal\n", "\n", @@ -224,13 +245,17 @@ "]\n", "\n", "for var in comparison_list:\n", - " assert_near_equal(fallout_prob.get_val(var), new_fallout_prob.get_val(var), tolerance=1e-6)" + " assert_near_equal(\n", + " off_design_max_range_prob.get_val(var),\n", + " new_off_design_max_range_prob.get_val(var),\n", + " tolerance=1e-6,\n", + " )" ] } ], "metadata": { "kernelspec": { - "display_name": "aviary", + "display_name": "aviary_dev", "language": "python", "name": "python3" }, @@ -244,7 +269,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.12" + "version": "3.14.3" } }, "nbformat": 4, diff --git a/aviary/docs/theory_guide/comparison_to_flops.md b/aviary/docs/theory_guide/comparison_to_flops.md index f8a9367bd6..9ef9110da9 100644 --- a/aviary/docs/theory_guide/comparison_to_flops.md +++ b/aviary/docs/theory_guide/comparison_to_flops.md @@ -190,11 +190,12 @@ performance calculation with additional trajectory segments. ### Off-design missions -Off-design, or economic, missions (defined in FLOPS using the $RERUN namelist) -are possible by manually setting up additional mission segments and linking the -starting conditions to the results of the design mission analysis. This process -will become more automated in the future, including the ability to assemble -payload-range diagrams. +Off-design missions (defined in FLOPS using the $RERUN namelist) are possible +utilizing Aviary's off-design analysis capability using the Settings.PROBLEM_TYPE +variable. There is no automatic functionality for translating these $RERUN +namelists so the user must do this manually. Payload-range diagrams can be +automatically generated using the Settings.PAYLOAD_RANGE variable for +ENERGY_STATE equations of motion. ### Optimization and parametric variation diff --git a/aviary/docs/user_guide_unreviewed/UI_levels.md b/aviary/docs/user_guide_unreviewed/UI_levels.md index 328dbd6f57..89a036b280 100644 --- a/aviary/docs/user_guide_unreviewed/UI_levels.md +++ b/aviary/docs/user_guide_unreviewed/UI_levels.md @@ -68,7 +68,7 @@ Levels 2 and 3 will require code to control the more detailed aspects of the ana | **Level 1** | **Level 2** | **Level 3** | |:--------------------------------------------:|:---------------------------------:|:----------------------:| -| Mission Type (Sizing, Fallout) | All Level 1 Controls | All Level 2 Controls | +| Mission Type (Sizing, OFF_DESIGN_MAX_RANGE) | All Level 1 Controls | All Level 2 Controls | | Optimizer (IPOPT, SNOPT) | Specify phase order and options | Custom ODEs and EOMs | | Mission Description (Altitude, Mach, etc) | | | diff --git a/aviary/docs/user_guide_unreviewed/images/payload-range-envelope.png b/aviary/docs/user_guide_unreviewed/images/payload-range-envelope.png index 100b13c062..4c2dcec281 100644 Binary files a/aviary/docs/user_guide_unreviewed/images/payload-range-envelope.png and b/aviary/docs/user_guide_unreviewed/images/payload-range-envelope.png differ diff --git a/aviary/docs/user_guide_unreviewed/off_design_missions.ipynb b/aviary/docs/user_guide_unreviewed/off_design_missions.ipynb index 664f25e1ff..4f7ca2a2ff 100644 --- a/aviary/docs/user_guide_unreviewed/off_design_missions.ipynb +++ b/aviary/docs/user_guide_unreviewed/off_design_missions.ipynb @@ -12,54 +12,57 @@ "source": [ "# Testing Cell\n", "import aviary.api as av\n", - "from aviary.api import Settings\n", - "from aviary.utils.doctape import glue_variable, check_value\n", + "from aviary.utils.doctape import glue_variable, glue_keys\n", "\n", - "str_problem_type = Settings.PROBLEM_TYPE\n", - "str_sizing = av.ProblemType.SIZING.value\n", - "str_alternate = av.ProblemType.ALTERNATE.value\n", - "str_fallout = av.ProblemType.FALLOUT.value\n", "\n", + "glue_keys(dict(av.ProblemType.__members__))\n", + "ProblemType = av.ProblemType\n", + "glue_variable(ProblemType.__name__, md_code=True)\n", + "glue_variable('Settings.PROBLEM_TYPE', av.Settings.PROBLEM_TYPE, md_code=True)\n", "\n", - "str_alternate_snippet = f'```\\n{str_problem_type}, {str_alternate}\\n```'\n", - "glue_variable('alternate_snippet', str_alternate_snippet, md_code=False)\n", + "# glue design variables\n", + "glue_variable('Mission.GROSS_MASS', av.Mission.GROSS_MASS, md_code=True)\n", + "glue_variable('Aircraft.Design.GROSS_MASS', av.Aircraft.Design.GROSS_MASS, md_code=True)\n", "\n", - "fallout_snippet = f'```\\n{str_problem_type}, {str_fallout}\\n```'\n", - "glue_variable('fallout_snippet', fallout_snippet, md_code=False)\n", - "\n", - "check_value(av.EquationsOfMotion.ENERGY_STATE.value, 'energy_state')\n", - "check_value(av.EquationsOfMotion.TWO_DEGREES_OF_FREEDOM.value, '2DOF')\n", + "# glue objectives\n", + "glue_variable('Mission.Objectives.FUEL', av.Mission.Objectives.FUEL, md_code=True)\n", + "glue_variable('Mission.Objectives.RANGE', av.Mission.Objectives.RANGE, md_code=True)\n", "\n", + "# glue constraints\n", + "glue_variable(\n", + " 'Mission.Constraints.RANGE_RESIDUAL', av.Mission.Constraints.RANGE_RESIDUAL, md_code=True\n", + ")\n", + "glue_variable(\n", + " 'Mission.Constraints.MASS_RESIDUAL', av.Mission.Constraints.MASS_RESIDUAL, md_code=True\n", + ")\n", "\n", - "ENERGY_STATE = av.EquationsOfMotion.ENERGY_STATE\n", - "glue_variable('energy_state', ENERGY_STATE.name, md_code=True)\n", - "TWO_DEGREES_OF_FREEDOM = av.EquationsOfMotion.TWO_DEGREES_OF_FREEDOM\n", - "glue_variable('2DOF', TWO_DEGREES_OF_FREEDOM.name, md_code=True)\n", "\n", - "# file_path = av.get_path('examples/run_off_design_example.py').relative_to(av.top_dir.parent)\n", - "# glue_variable('run_off_design_example.py', file_path, md_code=True)\n", + "str_problem_type = av.Settings.PROBLEM_TYPE\n", + "str_off_design_min_fuel = av.ProblemType.OFF_DESIGN_MIN_FUEL.value\n", + "str_off_design_max_range = av.ProblemType.OFF_DESIGN_MAX_RANGE.value\n", "\n", - "# file_path2 = av.get_path('examples/run_level2_example.py').relative_to(av.top_dir.parent)\n", - "# glue_variable('run_level2_example.py', file_path2, md_code=True)\n", + "str_off_design_min_fuel_snippet = (\n", + " f'```\\n{str_problem_type}, {str_off_design_min_fuel},unitless\\n```'\n", + ")\n", + "glue_variable('off_design_min_fuel_csv', str_off_design_min_fuel_snippet, md_code=False)\n", "\n", - "glue_variable('num_first', av.Aircraft.CrewPayload.NUM_FIRST_CLASS, md_code=False)\n", - "glue_variable('num_business', av.Aircraft.CrewPayload.NUM_BUSINESS_CLASS, md_code=False)\n", - "glue_variable('num_economy', av.Aircraft.CrewPayload.NUM_ECONOMY_CLASS, md_code=False)\n", - "glue_variable('num_pax', av.Aircraft.CrewPayload.NUM_PASSENGERS, md_code=False)\n", - "glue_variable('wing_cargo', av.Aircraft.CrewPayload.WING_CARGO, md_code=False)\n", - "glue_variable('misc_cargo', av.Aircraft.CrewPayload.MISC_CARGO, md_code=False)\n", - "glue_variable('cargo_mass', av.Aircraft.CrewPayload.CARGO_MASS, md_code=False)\n", + "off_design_max_range_snippet = f'```\\n{str_problem_type}, {str_off_design_max_range}\\n```'\n", + "glue_variable('off_design_max_range_csv', off_design_max_range_snippet, md_code=False)\n", "\n", - "glue_variable('operating_mass', av.Mission.OPERATING_MASS, md_code=True)\n", - "glue_variable('empty_mass', av.Aircraft.Design.EMPTY_MASS, md_code=True)\n", - "glue_variable('cargo_container_mass', av.Aircraft.CrewPayload.CARGO_CONTAINER_MASS, md_code=True)\n", - "glue_variable('design_gross_mass', av.Aircraft.Design.GROSS_MASS, md_code=True)\n", + "file_path = av.get_path('docs/examples/off_design_missions.ipynb').relative_to(av.top_dir.parent)\n", + "glue_variable('off_design_missions', file_path, md_code=True)\n", "\n", - "file_path3 = av.get_path('models/aircraft/test_aircraft/aircraft_for_bench_FwFm.csv')\n", "\n", - "glue_variable('objectives_fuel', av.Mission.Objectives.FUEL, md_code=True)\n", - "glue_variable('objectives_range', av.Mission.Objectives.RANGE, md_code=True)\n", - "glue_variable('summary_gross_mass', av.Mission.GROSS_MASS, md_code=True)" + "glue_variable(\n", + " 'Aircraft.CrewPayload.CARGO_CONTAINER_MASS',\n", + " av.Aircraft.CrewPayload.CARGO_CONTAINER_MASS,\n", + " md_code=True,\n", + ")\n", + "glue_variable(\n", + " 'Aircraft.CrewPayload.NUM_FIRST_CLASS',\n", + " av.Aircraft.CrewPayload.NUM_FIRST_CLASS,\n", + " md_code=True,\n", + ")" ] }, { @@ -72,274 +75,119 @@ "\n", "Off-design missions are missions that take an already designed and sized aircraft and attempt to run different mission trajectories, different payload quantities, or both.\n", "\n", - "Off-design missions are enabled for the following mission types:\n", + "There are two types of off-design missions supported in Aviary:\n", "\n", - "* {glue:md}`energy_state`\n", - "* {glue:md}`2DOF`\n", + "- OFF_DESIGN_MIN_FUEL Missions: the mission's target range and aircraft payload mass are inputs and the fuel mass required is solved for. \n", + "- OFF_DESIGN_MAX_RANGE Missions: the aircraft payload and gross mass are inputs and the range of the aircraft is solved for.\n", "\n", - "There are currently two types of off-design missions supported in Aviary:\n", + "There are 3 methods of running off-design missions\n", + "1. Modifying Settings.PROBLEM_TYPE variable in an aviary input .csv or aviary values object\n", + "2. Calling run_off_design_mission() after sizing an aircraft using the python api\n", "\n", - "- Alternate Missions: the mission's target range and aircraft payload mass are inputs and the fuel mass required is solved for. \n", - "- Fallout Missions: the aircraft payload and gross mass as inputs and the range of the aircraft is solved for. \n", + "### 1. Using the PROBLEM_TYPE Variable:\n", "\n", - "The off-design missions correspond to the different problem types that can have differing objectives which are discussed in detail in Level 2.\n", - "The problem type determines what the optimizer can control to find a valid solution. \n", - "- Sizing Missions allow the optimizer to control both the {glue:md}`summary_gross_mass` and {glue:md}`design_gross_mass` for the given mission and objective. \n", - "- Alternate Missions allow the optimizer to only control the {glue:md}`summary_gross_mass` for the mission.\n", - "- Fallout Missions don't allow the optimizer to control either {glue:md}`summary_gross_mass` or {glue:md}`design_gross_mass` but allows the optimizer to extend the range until the summary matches the design.\n", + "The PROBLEM_TYPE variable provides a convenient way for the user to set common combinations of design variables, constraints and objective function for solving aircraft design or performance evaluation problems.\n", "\n", - "There are currently 3 different methods for running an off-design mission within Aviary. \n", + "- SIZING Missions allow the optimizer to control both the {glue:md}`Mission.GROSS_MASS` and {glue:md}`Aircraft.Design.GROSS_MASS` for the given mission and objective. \n", + "- OFF_DESIGN_MIN_FUEL Missions allow the optimizer to only control the {glue:md}`Mission.GROSS_MASS` for the mission.\n", + "- OFF_DESIGN_MAX_RANGE Missions don't allow the optimizer to control either {glue:md}`Mission.GROSS_MASS` or {glue:md}`Aircraft.Design.GROSS_MASS` but allows the optimizer to extend the range until the summary matches the design.\n", + "- MULTI_MISSION typically allow the optimizer to control {glue:md}`Aircraft.Design.GROSS_MASS` and the {glue:md}`Mission.GROSS_MASS` for each of the missions within multimission. See Documentation for full details.\n", "\n", - "The first method is to take the input deck of an already sized aircraft and change its problem type to either `fallout` or `alternate`. \n", + "This table contains a summary of the preset combinations of objectives, design variables and constraints.\n", + "\n", + "| {glue:md}`Settings.PROBLEM_TYPE` | objective | design variables | constraints |\n", + "| ------------------------| --------- | ---------------- | ----------- |\n", + "| {glue:md}`SIZING` | {glue:md}`Mission.Objectives.FUEL` | {glue:md}`Mission.GROSS_MASS`, {glue:md}`Aircraft.Design.GROSS_MASS` | {glue:md}`Mission.Constraints.RANGE_RESIDUAL`, {glue:md}`Mission.Constraints.MASS_RESIDUAL` |\n", + "| {glue:md}`OFF_DESIGN_MIN_FUEL` | {glue:md}`Mission.Objectives.FUEL`| {glue:md}`Mission.GROSS_MASS` | {glue:md}`Mission.Constraints.RANGE_RESIDUAL` |\n", + "| {glue:md}`OFF_DESIGN_MAX_RANGE` | {glue:md}`Mission.Objectives.RANGE` | No additional design variables | No additional constraints |\n", + "| {glue:md}`MULTI_MISSION` | No Default, user specified | Custom setup - see multimission documentation | Custom setup - see multimission documentation |\n", "\n", "```{note}\n", - "The user may need to revise some of the values in their input deck for the off-design mission.\n", - "Since the aircraft is not re-designed, it is assumed that provided inputs constitute a valid aircraft.\n", + "This table does not show all the design variables for the problem, only those altered by the problem_type variable.\n", + "Dymos will add additional design variables and constraints depending on number of phases, whether mach and altitude are to be optimized during those phases and any additional constraints in each phase.\n", + "For 2 DOF equations of motion also have additional design variables due to additional fidelity of the trajectory modelling.\n", "```\n", - "The second method is to run off-design missions in the same script used to solve the design mission.\n", - "An example of this is shown in `run_off_design_example.py`.\n", "\n", - "The third method is to run a sizing mission to design the aircraft, save the sizing information to a JSON file, then load the off-design mission in another script. \n", + "The Settings.PROBLEM_TYPE variable can be set for off design missions in the aviary input .csv file:\n", + "{glue:md}`off_design_min_fuel_csv`\n", + "or\n", + "{glue:md}`off_design_max_range_csv`\n", + "\n", + "If manually creating an aviary values object then it can be set before calling prob.load_inputs():\n", + "\n", + "aviary_values['settings:problem_type'] = 'off_design_min_fuel'\n", + "or if you directly import the enum\n", + "from aviary.variable_info.enums import ProblemType\n", + "aviary_values['settings:problem_type'] = ProblemType.OFF_DESIGN_MIN_FUEL\n", "\n", "```{note}\n", - "If the sizing mission did not converge to a valid aircraft design, any off-design analysis will be invalid even if the off-design missions themselves converged.\n", - "Therefore the validity of off-design analysis depends entirely on the validity of its source sizing mission.\n", + "{glue:md}`MULTI_MISSION` problem type must be set earlier when initializing the Aviary problem object itself, see multi mission documentation for more details.\n", "```\n", "\n", - "## Off-Design from an already sized aircraft. \n", + "Run the rest of the aviary problem as normal from this point and Aviary will take care of adding the appropriate objective, design variables, and constraints as detailed in the table above.\n", "\n", - "The first method of running an off-design mission requires that the user has an input deck of a fully sized and valid aircraft.\n", - "ex: `aircraft_for_bench_FwFm.csv`.\n", - "This is done by adding one of the following lines to its csv file:\n", + "```{note}\n", + "Since Aviary is not re-sizing the aircraft it is important that all the variables in the aviary_values or .csv input contian the appropriate values to define an already sized aircraft.\n", + "```\n", "\n", - "{glue:md}`fallout_snippet`\n", + "### 2. Python API run_off-design_mission() method\n", "\n", - "or\n", + "Following the successful convergence of a 'SIZING' type aircraft and misison optimization, the run_off_design_mission() method can be run from the same python script.\n", + "\n", + "```{note}\n", + "If the sizing mission did not converge to a valid aircraft design, any off-design analysis will be invalid even if the off-design missions themselves converged.\n", + "```\n", "\n", - "{glue:md}`alternate_snippet`\n", + "This method has a number of arguments that tell aviary the conditions required for the off_design_mission a simple example is shown in the {glue:md}`off_design_missions` example.\n", + "If any of the arguments are unspecified or None then Aviary will assume the same values as for the 'SIZING' problem. Full details of all the arguments can be found in the [source docs](../_srcdocs/packages/core/aviary_problem.md).\n", + "The key arguments allow the user to set the problem_type, phase_info, payload (passengers + cargo) and fuel loading or mission range depending on the problem type.\n", + "There are 2 additional flags that allow the user to add aditional design variables to maximise gross mass or maximise cargo mass for a particular off_design_mission.\n", "\n", - "Once the problem type is specified, run Aviary as you would any other mission. \n", + "The `phase_info` argument allows the user to specify different phase information for an off-design mission trajectory, more information can be found in [The `phase_info` Format](../source_docs/phase_info_detailed.ipynb).\n", + "This can be required to achieve convergence when the off-design mission has a significantly different trajectory shape or duration compared to the the sizing mission. \n", + "For example, an aircraft sized for a very long-range mission might need its climb and descent profiles modified to operate on a shorter route.\n", "\n", "```{note}\n", - "Off-design missions are run with the assumption that the sizing mission's {glue:md}`design_gross_mass` is the maximum structural mass the aircraft can support. \n", - "However, Aviary is capable of running a converging off-design missions with {glue:md}`summary_gross_mass` that exceed that structural constraint. \n", - "Therefore the user should check the {glue:md}`summary_gross_mass` of the off-design to ensure the optimization is valid. \n", + "Off-design missions are run with the assumption that the sizing mission's {glue:md}`Aircraft.Design.GROSS_MASS` is the maximum structural mass the aircraft can support. \n", + "However, since {glue:md}`Mission.Constraints.RANGE_RESIDUAL` is not added as a constraint for off-design, Aviary is capable of running and converging off-design missions with {glue:md}`Mission.GROSS_MASS` values greater than {glue:md}`Aircraft.Design.GROSS_MASS`. The user is responsible for checking that they have run valid off-design missions.\n", "```\n", - "## Sizing & off-design in the same script.\n", - "\n", - "Firstly, we highly recommend users first read through and understand the Level 2 onboarding guide before attempting an off-design mission. \n", "\n", - "This is the Aviary Team's preferred method for running off-design missions as it allows the user to interrogate both the sizing and off-design missions together in the same script.\n", - "Off-design functionality within the script involves first running a sizing mission then transferring the sizing parameters via JSON file into a new `AviaryProblem()` object.\n", - "User input payload, `phase_info`, and `mission_mass` or `mission_range` is parsed to the new problem object and the problem is executed within the level 2 method. \n", + "### Saving and Loading the sized aircraft\n", "\n", - "Running an off-design mission first requires a sizing mission setup similar to \n", - "`run_level2_example.py` then the addition of `save_sizing_to_json()` to the `AviaryProblem`.\n", - "- `prob.save_sizing_to_json(json_filename = 'sizing_problem.json')` \n", + "The save_results() method can be used to save the Aviary problem to Jason file so that it can be loaded and used for off_design mission analysis.\n", "\n", - "The `save_sizing_to_json()` argument saves the sizing mission's parameters to a JSON filename of the user's choosing where `sizing_problem.json` is the default filename.\n", + "- `prob.save_results(json_filename = 'sizing_problem.json')` \n", "\n", - "To run a fallout or alternate mission these level 2 functions can be added to the end of the script.\n", + "```{note}\n", + "This doesn't actually save all the value sin the Aviary problem, but saves the aviary_inputs and value of the Aircraft.Design.GROSS_MASS variable (which is all that is required for an off_design_mission).\n", + "The Aviary team is working on a more complete method of saving the design aircraft details.\n", + "``` \n", "\n", - "- `prob.fallout_mission(json_filename = 'sizing_problem.json')` \n", + "The read_sizing_json() method can be used to create an aviary values object from the data saved into a json file using the save_results() method.\n", "\n", - "- `prob.alternate_mission(json_filename = 'sizing_problem.json')`\n", + "- `_read_sizing_json()`\n", "\n", - "If an argument is left empty Aviary will assume it is the same as that of the sizing mission. \n", - "Both off-design mission types take payload as an input parameter. \n", - "The mission's payload is split up into the 3 passenger classes and the 2 cargo loadings. \n", - "The respective Aviary variables are then updated with the user's off-design passenger and cargo quantities.\n", + "This aviary values object can then be fed into an aviary problem using the load_inputs() method.\n", + "The off_design mission analysis can then be run using method 1 above.\n", "\n", "```{note} \n", "As of v0.9.10 Aviary's off-design capabilities do not allow missions to be run with 0 passengers, so a minimum of 1 passenger must be specified. \n", "This is a known bug and is currently being investigated. \n", "```\n", "\n", - "| `Inputs` | `Aviary Variable Name` | `Description`\n", - "| ----------------- | ----------------------------- | ----------------------------------------- |\n", - "| `num_first` | {glue:md}`num_first` | The number of first class passengers |\n", - "| `num_business` | {glue:md}`num_business` | The number of business class passengers |\n", - "| `num_economy` | {glue:md}`num_economy` | The number of economy class passengers |\n", - "| `num_pax` | {glue:md}`num_pax` | Total number of passengers |\n", - "| `wing_cargo` | {glue:md}`wing_cargo` | Cargo carried in wing |\n", - "| `misc_cargo` | {glue:md}`misc_cargo` | Additional cargo carried in fuselage |\n", - "| `cargo_mass` | {glue:md}`cargo_mass` | Total mass of as-flown cargo |\n", - "| `phase_info` | phase_info | The mission trajectory for the aircraft |\n", - "| `mission_range` | `target_range` | The target range for alternate missions |\n", - "| `mission_mass` | {glue:md}`summary_gross_mass` | The mission mass for fallout missions |\n", - "\n", - "\n", "```{note} \n", "Off-design missions **cannot** be run with more passengers than the original sizing mission. \n", - "For example, if {glue:md}`num_first` within the Aviary inputs csv was set to 3, an off-design mission cannot be run where `num_first = 8` as the cabin for first class was sized for precisely 3 passengers.\n", + "For example, if {glue:md}`Aircraft.CrewPayload.NUM_FIRST_CLASS` within the Aviary inputs csv was set to 3, an off-design mission cannot be run where {glue:md}`Aircraft.CrewPayload.NUM_FIRST_CLASS` = 8 as the cabin for first class was sized for precisely 3 passengers.\n", "```\n", "\n", - "The `phase_info` argument allows the user to specify a different phase information for an off-design mission trajectory, more information can be found in [The `phase_info` Format](../source_docs/phase_info_detailed.ipynb).\n", - "This can be due to the off-design mission requiring a different trajectory shape or phase durations than the sizing mission. \n", - "For example, an aircraft sized for long-range cruise might need its climb and descent profiles modified to operate on shorter routes. \n", - "\n", "```{note}\n", - "In cases where passengers or cargo are changed and the user has not specified a {glue:md}`cargo_container_mass` in the Aviary input deck, off-design missions recalculates it in the off-design mission.\n", - "It is the only parameter within the operating mass that gets recalculated between off-design and sizing.\n", - "```\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "\"\"\"\n", - "This is an example of a sizing mission and alternate mission being run in the same script. \n", - "This is done with \n", - "\"\"\"\n", - "import aviary.api as av\n", - "\n", - "# inputs that run_aviary() requires\n", - "aircraft_data = 'models/aircraft/test_aircraft/aircraft_for_bench_FwFm.csv'\n", - "optimizer = 'SLSQP'\n", - "phase_info = av.default_energy_state_phase_info\n", - "max_iter = 15\n", - "\n", - "# Set up and run avairy problem.\n", - "prob = av.AviaryProblem(verbosity=0)\n", - "\n", - "# This is the default FwFm (Flops weights Flops mission) aircraft.\n", - "prob.load_inputs(aircraft_data, phase_info)\n", - "\n", - "prob.check_and_preprocess_inputs()\n", - "\n", - "prob.add_pre_mission_systems()\n", - "\n", - "prob.add_phases()\n", - "\n", - "prob.add_post_mission_systems()\n", - "\n", - "prob.link_phases()\n", - "\n", - "prob.add_driver(optimizer, max_iter=max_iter)\n", - "\n", - "prob.add_design_variables()\n", - "\n", - "prob.add_objective()\n", - "\n", - "prob.setup()\n", - "\n", - "prob.set_initial_guesses()\n", - "\n", - "prob.run_aviary_problem()\n", - "\n", - "# save the sizing mission to a JSON file\n", - "prob.save_sizing_to_json(json_filename='off_design_documentation_example.json')\n", - "\n", - "# initialize alternate mission flying a much shorter distance.\n", - "# if payload values are unspecified, off-design will uses the values from the sizing mission.\n", - "# the mission_range argument is much shorter than the sizing mission's 1906 NM range.\n", - "prob.alternate_mission(\n", - " json_filename='off_design_documentation_example.json',\n", - " num_first=0,\n", - " num_business=0,\n", - " num_economy=50,\n", - " num_pax=50,\n", - " wing_cargo=0,\n", - " misc_cargo=0,\n", - " cargo_mass=0,\n", - " mission_range=1000,\n", - " phase_info=phase_info,\n", - " verbosity=0,\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Off-design missions separately\n", - "\n", - "The third method in running an off-design mission requires saving the sizing information as a JSON file using `save_sizing_to_json()` then calls the JSON file into an off-design mission in another script.\n", - "This method allows for off-design missions to be run without re-executing the sizing mission every time. \n", - "Once saved, the third method works identically to the second method except that the user must manually run the `_load_off_design()` function to create a new Aviary problem. \n", - "\n", - "\n", - "The `_load_off_design()` function then requires the `json_filename` input along with 3 additional problem definition parameters `problem_type`, `equations_of_motion`, and `mass_method`. \n", - "Once defined, the `_load_off_design()` function essentially replaces the `load_inputs()` step of a standard level 2 problem. \n", - "The user can then adjust payload quantities and `mission_range`/`mission_gross_mass` then complete the level 2 script as shown below. \n", - "\n", - "- `problem_type`: Determines which type of off-design analysis the user would like to run. (alternate or fallout)\n", - "- `equations_of_motion`: Determines the type of trajectory calculations the user is running. ({glue:md}`energy_state` or {glue:md}`2DOF`)\n", - "- `mass_method`: Specifies how mass calculations are handled (FLOPS or GASP)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "\n", - "\"\"\"\n", - "This is an example of running off-design mission directly form a saved sized aircraft JSON file\n", - "Note: If there were external subsystems in the original sized aircraft model \n", - "these will NOT be transferred (since these are not saved in the JSON file).\n", - "\"\"\"\n", - "\n", - "from aviary.core.aviary_problem import _load_off_design\n", - "from aviary.variable_info.enums import LegacyCode\n", - "import aviary.api as av\n", - "\n", - "optimizer = 'SLSQP'\n", - "phase_info = av.default_energy_state_phase_info\n", - "max_iter = 15\n", - "# Load aircraft and options data from provided sources\n", - "\n", - "# To run an alternate mission, we need the sized aircraft JSON file from the sizing mission.\n", - "# Set ProblemType to ALTERNATE, and specify the mission range and payload mass.\n", - "# mission_gross_mass does nothing).\n", - "\n", - "# Use specific _load_off_design function to define the off-design mission.\n", - "prob_alternate = _load_off_design(\n", - " json_filename='off_design_documentation_example.json',\n", - " problem_type=av.ProblemType.ALTERNATE,\n", - " equations_of_motion=av.EquationsOfMotion.ENERGY_STATE,\n", - " mass_method=LegacyCode.FLOPS,\n", - " phase_info=phase_info,\n", - " num_first=0,\n", - " num_business=0,\n", - " num_economy=50,\n", - " num_pax=50,\n", - " wing_cargo=0,\n", - " misc_cargo=0,\n", - " cargo_mass=0,\n", - " mission_range=1000,\n", - " verbosity=0,\n", - ")\n", - "\n", - "# Run off-design mission the same way as any Level 2 aviary problem.\n", - "prob_alternate.check_and_preprocess_inputs()\n", - "\n", - "prob_alternate.add_pre_mission_systems()\n", - "\n", - "prob_alternate.add_phases()\n", - "\n", - "prob_alternate.add_post_mission_systems()\n", - "\n", - "prob_alternate.link_phases()\n", - "\n", - "prob_alternate.add_driver(optimizer=optimizer, max_iter=max_iter)\n", - "\n", - "prob_alternate.add_design_variables()\n", - "\n", - "prob_alternate.add_objective()\n", - "\n", - "prob_alternate.setup()\n", + "In cases where passengers or cargo are changed and the user has not specified a {glue:md}`Aircraft.CrewPayload.CARGO_CONTAINER_MASS` in the Aviary input deck, this value is recalculated in the off-design mission.\n", + "It is the only parameter within the operating mass that gets recalculated between off-design and sizing. If this behavior is undesirable then a fixed override for {glue:md}`Aircraft.CrewPayload.CARGO_CONTAINER_MASS` should be used.\n", + "```\n", "\n", - "prob_alternate.set_initial_guesses()\n", + "### Payload Range Diagram Functionality\n", "\n", - "prob_alternate.run_aviary_problem()" + "The Off-Design functionality is key for the generation of Payload Range Diagrams. See [payload range documentation](../user_guide_unreviewed/payload_range_functionality.ipynb) for more details." ] }, { @@ -352,7 +200,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "aviary_dev", "language": "python", "name": "python3" }, @@ -366,7 +214,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.14.3" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide_unreviewed/onboarding_level2.ipynb b/aviary/docs/user_guide_unreviewed/onboarding_level2.ipynb index bfa47f9275..5c2f5a4630 100644 --- a/aviary/docs/user_guide_unreviewed/onboarding_level2.ipynb +++ b/aviary/docs/user_guide_unreviewed/onboarding_level2.ipynb @@ -127,8 +127,8 @@ "| {glue:md}`problem_type` | `objective` |\n", "| ------------ | --------- |\n", "| {glue:md}`SIZING` | {glue:md}`Mission.Objectives.FUEL` |\n", - "| {glue:md}`ALTERNATE` | {glue:md}`Mission.Objectives.FUEL`|\n", - "| {glue:md}`FALLOUT` | {glue:md}`Mission.Objectives.RANGE` |\n", + "| {glue:md}`OFF_DESIGN_MIN_FUEL` | {glue:md}`Mission.Objectives.FUEL`|\n", + "| {glue:md}`OFF_DESIGN_MAX_RANGE` | {glue:md}`Mission.Objectives.RANGE` |\n", "| {glue:md}`MULTI_MISSION` | No Default, user specified |" ] }, @@ -627,8 +627,8 @@ "EOM.ENERGY_STATE\n", "EOM.TWO_DEGREES_OF_FREEDOM\n", "PT.SIZING\n", - "PT.ALTERNATE\n", - "PT.FALLOUT" + "PT.OFF_DESIGN_MIN_FUEL\n", + "PT.OFF_DESIGN_MAX_RANGE" ] }, { @@ -644,7 +644,7 @@ "| ----------- | ----------- | ----------- | --------------- | ----- |\n", "| Aircraft.Design.GROSS_MASS | 10 | 900.e3| 175.e3 | lbm |\n", "\n", - "For default {glue:md}`2DOF` mission model, the design variables and constraints depend on the type of problems ({glue:md}`SIZING`, {glue:md}`ALTERNATE`, or {glue:md}`FALLOUT`, see {glue:md}`ProblemType` class in `aviary/variable_info/enums.py` for details). First, there are four common design variables and two common constraints. There are two more design variables and two constraints for sizing problems.\n", + "For default {glue:md}`2DOF` mission model, the design variables and constraints depend on the type of problems ({glue:md}`SIZING`, {glue:md}`OFF_DESIGN_MIN_FUEL`, or {glue:md}`OFF_DESIGN_MAX_RANGE`, see {glue:md}`ProblemType` class in `aviary/variable_info/enums.py` for details). First, there are four common design variables and two common constraints. There are two more design variables and two constraints for sizing problems.\n", "\n", "| **Problem Type** | **Design Variables** | **Lower Bound** | **Upper Bound** | **Reference Value** | **Units** |\n", "| ----------- | ----------- | ----------- | ----------- | --------------- | ----- |\n", @@ -652,14 +652,14 @@ "| Any | Mission.Takeoff.ASCENT_DURATION | 1 | 1000 | 10.0 | s |\n", "| Any | tau_gear | 0.01 | 1.0 | 1 | s |\n", "| Any | tau_flaps | 0.01 | 1.0 | 1 | s |\n", - "| SIZING | Aircraft.Design.GROSS_MASS | 10. | None | 175_000 | lbm |\n", - "| SIZING | Mission.GROSS_MASS | 10. | None | 175_000 | lbm |\n", - "| ALTERNATE | Mission.GROSS_MASS | 0 | infinite | 175_000 | lbm |\n", + "| {glue:md}'SIZING' | Aircraft.Design.GROSS_MASS | 10. | None | 175_000 | lbm |\n", + "| {glue:md}'SIZING' | Mission.GROSS_MASS | 10. | None | 175_000 | lbm |\n", + "| {glue:md}'OFF_DESIGN_MIN_FUEL' | Mission.GROSS_MASS | 0 | infinite | 175_000 | lbm |\n", "| **Problem Type** | **Constraint** | **Relation** | **Value** | **Reference Value** | **Units** |\n", "| Any | h_fit.h_init_gear | = | 50.0 | 50.0 | ft |\n", "| Any | h_fit.h_init_flaps | = | 400.0 | 400.0 | ft |\n", - "| SIZING | Mission.Constraints.RANGE_RESIDUAL | = | 0 | 10 | unitless |\n", - "| ALTERNATE | Mission.Constraints.RANGE_RESIDUAL | = | 0 | 10 | lbm |\n", + "| {glue:md}'SIZING' | Mission.Constraints.RANGE_RESIDUAL | = | 0 | 10 | unitless |\n", + "| {glue:md}'OFF_DESIGN_MIN_FUEL' | Mission.Constraints.RANGE_RESIDUAL | = | 0 | 10 | lbm |\n", "\n", "In the above table, there are two hard-coded design variables: `tau_gear` and `tau_flaps`. They represent fractions of ascent time to start gear retraction and flaps retraction. There are two hard-coded constraints: `h_fit.h_init_gear` and `h_fit.h_init_flaps`. They are the altitudes of initial gear retraction and initial flaps retraction. The underscore in number '175_000' is for readability only. \n", "\n", @@ -1006,7 +1006,7 @@ ], "metadata": { "kernelspec": { - "display_name": "aviary", + "display_name": "aviary_dev", "language": "python", "name": "python3" }, @@ -1020,7 +1020,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.12" + "version": "3.14.3" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide_unreviewed/payload_range_functionality.ipynb b/aviary/docs/user_guide_unreviewed/payload_range_functionality.ipynb index 0583a512d1..0d2a0e3ed3 100644 --- a/aviary/docs/user_guide_unreviewed/payload_range_functionality.ipynb +++ b/aviary/docs/user_guide_unreviewed/payload_range_functionality.ipynb @@ -21,8 +21,8 @@ "\n", "PAYLOAD_RANGE = av.Settings.PAYLOAD_RANGE\n", "\n", - "str_alternate_snippet = f'```\\n{PAYLOAD_RANGE}, {True}\\n```'\n", - "glue_variable('payload_range_toggle', str_alternate_snippet, md_code=True)\n", + "str_off_design_min_fuel_snippet = f'```\\n{PAYLOAD_RANGE}, {True}\\n```'\n", + "glue_variable('payload_range_toggle', str_off_design_min_fuel_snippet, md_code=True)\n", "glue_variable('payload_range', av.Settings.PAYLOAD_RANGE, md_code=True)\n", "\n", "\n", @@ -49,14 +49,14 @@ "Aviary has the capability to generate payload range diagrams for models utilizing the {glue:md}`energy_state` equations of motion. Support for the {glue:md}`2DOF` equations of motion is planned in a future release.\n", "\n", "A payload-range diagram is an aircraft performance visualization that illustrates the trade-off between how much mass the aircraft can carry in terms of passengers and cargo (payload) vs how far it can fly (range).\n", - "The payload-range shows 3 different points:\n", - "- **Maximum Payload Point (\"Design Range\")**: The range the aircraft can fly with maximum payload mass and fuel mass up to the maximum gross mass. This range is typically the design point of the aircraft (the \"sizing\" mission).\n", - "- **Maximum Fuel Point (\"Max Economic Range\")**: The range the aircraft can fly at maximum fuel capacity and payload mass up to maximum gross mass. This is the furthest range the aircraft can fly at its maximum gross mass.\n", - "- **Maximum Range Point (\"Ferry Range\")**: The range the aircraft can fly at maximum fuel capacity and zero payload. This is the furthest the aircraft can fly. \n", + "A payload-range diagram typically shows 3 salient points:\n", + "- **Maximum Payload + Fuel Point**: The range the aircraft can fly with maximum payload mass and fuel mass up to the maximum gross mass. This is typically used as the design point of the aircraft (the \"sizing\" mission).\n", + "- **Maximum Fuel + Payload Point**: The range the aircraft can fly with maximum fuel mass (equal to total_fuel_capacity) and payload mass up to maximum gross mass. This is the furthest range the aircraft can fly at its maximum gross mass.\n", + "- **Maximum Range Point**: The range the aircraft can fly at maximum fuel capacity and zero payload. This is the furthest the aircraft can fly.\n", "\n", "![Payload Range Envelope Example](./images/payload-range-envelope.png)\n", "\n", - "After a successful sizing mission (**Maximum Payload Point**) the payload-range function calculates the required aircraft loading conditions and runs an off-design fallout mission for the \"Max Economic\" \"Ferry\" range points.\n", + "After a successful sizing mission (**Maximum Payload + Fuel Point**) the payload-range function calculates the required aircraft loading conditions and runs an OFF_DESIGN_MAX_RANGE mission for the \"Maximum Fuel + Payload\" and \"Maximum Range\" points.\n", "\n", "When a user requests for a payload-range diagram to be generated, the additional points are run using the design mission's phase info. It is assumed that this `phase_info` contains a cruise phase named `cruise`, and Aviary will automatically adjust the `time_duration_bound` of the cruise phase to account for the longer off-design missions.\n", "\n", @@ -65,14 +65,14 @@ "```\n", "\n", "Each point is configured using the following methods:\n", - "- **Maximum Payload Point**: The results of the sizing mission are used to plot this point.\n", + "- **Maximum Payload + Fuel Point**: The results of the sizing mission are used to plot this point.\n", "\n", - "- **Maximum Fuel Point**: The aircraft's mission fuel is assumed to match {glue:md}`total_fuel_capacity`. The available payload is computed as {glue:md}`design_gross_mass` minus {glue:md}`operating_mass` minus {glue:md}`total_fuel_capacity`. Therefore the aircraft's mission mass is equal to its design gross mass.\n", + "- **Maximum Fuel + Payload Point**: The aircraft's mission fuel is assumed to match {glue:md}`total_fuel_capacity`. The available payload is computed as {glue:md}`design_gross_mass` minus {glue:md}`operating_mass` minus {glue:md}`total_fuel_capacity`. Therefore the aircraft's mission mass is equal to its {glue:md}`design_gross_mass`.\n", "\n", - "- **Maximum Range Point**: The aircraft is flown at a gross mass equal to {glue:md}`design_gross_mass` plus {glue:md}`total_fuel_capacity`.\n", + "- **Maximum Range Point**: The aircraft is flown at a gross mass equal to {glue:md}`operating_mass` plus {glue:md}`total_fuel_capacity`.\n", "\n", "```{note}\n", - "There is an edge case where an aircraft's fuel capacity could exceed its design gross mass if fully fueled to that capacity. In this case, at the maximum fuel point the aircraft is filled with fuel up to its design gross mass. Because there is no room for additional payload, this point is also equivalent to the maximum range point. Aviary will not run that last point as it is redundant, and instead re-use the results of the maximum fuel point for building the payload-range data table.\n", + "Not all aircraft designs will have 3 distinct points. If an aircraft's fuel capacity is very large, it could exceed its design gross mass when fully fueled to that capacity. In this case, the \"Maximum Fuel + Payload\" point would coincide with the \"Maximum Range\" Point. Aviary will not run that last point as it is redundant, and instead re-use the results of the maximum fuel point for building the payload-range data table.\n", "```\n", "\n", "## Outputs\n", @@ -92,9 +92,9 @@ "```\n", "\n", "### Method Call\n", - "The second way to generate the payload-range diagram is to manually call the `run_payload_range()` method after running a sizing mission. If the payload range points run successfully, then the method returns a tuple containing the AviaryProblems used to create the maximum economic range and ferry range points.\n", + "The second way to generate the payload-range diagram is to manually call the `run_payload_range()` method after running a sizing mission. If the payload range points run successfully, then the method returns a tuple containing the AviaryProblems used to create the 'maximum fuel + payload range' and 'ferry range' points.\n", "\n", - "`(max_econ_range_prob, ferry_range_prob) = prob.run_payload_range()`\n", + "`(max_fuel_pyld_range_prob, ferry_range_prob) = prob.run_payload_range()`\n", "\n", "## Output and visualization. \n", "The data for the payload-range points can be found in `payload_range_data.csv`, located within the main problem's reports folder. \n", diff --git a/aviary/interface/test/test_reports.py b/aviary/interface/test/test_reports.py index 4568915e4f..2ef99afcbe 100644 --- a/aviary/interface/test/test_reports.py +++ b/aviary/interface/test/test_reports.py @@ -149,8 +149,8 @@ def test_multiple_off_design_report_directories(self): prob.add_objective() prob.setup() prob.run_aviary_problem() - prob.run_off_design_mission(problem_type='fallout', mission_gross_mass=115000) - prob.run_off_design_mission(problem_type='alternate', mission_range=1250) + prob.run_off_design_mission(problem_type='off_design_max_range', mission_gross_mass=115000) + prob.run_off_design_mission(problem_type='off_design_min_fuel', mission_range=1250) assert Path('testflo_off_design_1_out').is_dir() assert Path('testflo_off_design_out').is_dir() diff --git a/aviary/interface/test/test_save_results.py b/aviary/interface/test/test_save_results.py index 8ff5fad86c..e066e02d9b 100644 --- a/aviary/interface/test/test_save_results.py +++ b/aviary/interface/test/test_save_results.py @@ -53,18 +53,20 @@ def test_save_json(self): ) @require_pyoptsparse(optimizer='IPOPT') - def test_alternate(self): + def test_off_design_min_fuel(self): local_phase_info = deepcopy(phase_info) prob = reload_aviary_problem('interface/test/sizing_results_for_test.json') - prob.run_off_design_mission(problem_type='alternate', phase_info=local_phase_info) + prob.run_off_design_mission(problem_type='off_design_min_fuel', phase_info=local_phase_info) @require_pyoptsparse(optimizer='IPOPT') - def test_fallout(self): + def test_off_design_max_range(self): local_phase_info = deepcopy(phase_info) prob = reload_aviary_problem('interface/test/sizing_results_for_test.json') - prob.run_off_design_mission(problem_type='fallout', phase_info=local_phase_info) + prob.run_off_design_mission( + problem_type='off_design_max_range', phase_info=local_phase_info + ) def compare_files(self, test_file, validation_file): """ @@ -98,4 +100,4 @@ def compare_files(self, test_file, validation_file): # test = TestSizingResults() # test.test_save_json() - # test.test_fallout() + # test.test_off_design_max_range() diff --git a/aviary/models/aircraft/large_turboprop_freighter/large_turboprop_freighter_GASP.csv b/aviary/models/aircraft/large_turboprop_freighter/large_turboprop_freighter_GASP.csv index 97d850cfef..b5384298d8 100644 --- a/aviary/models/aircraft/large_turboprop_freighter/large_turboprop_freighter_GASP.csv +++ b/aviary/models/aircraft/large_turboprop_freighter/large_turboprop_freighter_GASP.csv @@ -1,7 +1,7 @@ ############ # SETTINGS # ############ -settings:problem_type, fallout, unitless +settings:problem_type, off_design_max_range, unitless settings:equations_of_motion, 2DOF settings:aerodynamics_method, GASP settings:mass_method, GASP diff --git a/aviary/utils/fortran_to_aviary.py b/aviary/utils/fortran_to_aviary.py index 9803ffd784..c9997c1dc7 100644 --- a/aviary/utils/fortran_to_aviary.py +++ b/aviary/utils/fortran_to_aviary.py @@ -510,20 +510,20 @@ def update_gasp_options(vehicle_data, verbosity=Verbosity.BRIEF): problem_type = 'sizing' if isinstance(design_range, list): - # if the design range target_range value is 0, set the problem_type to fallout + # if the design range target_range value is 0, set the problem_type to off_design_max_range if design_range[0] == 0: - problem_type = 'fallout' + problem_type = 'off_design_max_range' input_values.set_val(Settings.PROBLEM_TYPE, [problem_type]) design_range = 0 if problem_type == 'sizing': design_range = design_range[0] elif problem_type == 'alternate': design_range = design_range[2] - elif problem_type == 'fallout': + elif problem_type == 'off_design_max_range': design_range = 0 else: if design_range == 0: - input_values.set_val(Settings.PROBLEM_TYPE, ['fallout']) + input_values.set_val(Settings.PROBLEM_TYPE, ['off_design_max_range']) input_values.set_val(Aircraft.Design.RANGE, [design_range], distance_units) ## Passengers ## diff --git a/aviary/utils/process_input_decks.py b/aviary/utils/process_input_decks.py index eeb67e4a30..f141756849 100644 --- a/aviary/utils/process_input_decks.py +++ b/aviary/utils/process_input_decks.py @@ -39,8 +39,8 @@ } problem_types = { 'sizing': ProblemType.SIZING, - 'alternate': ProblemType.ALTERNATE, - 'fallout': ProblemType.FALLOUT, + 'off_design_min_fuel': ProblemType.OFF_DESIGN_MIN_FUEL, + 'off_design_max_range': ProblemType.OFF_DESIGN_MAX_RANGE, } @@ -359,7 +359,7 @@ def initialization_guessing(aircraft_values: AviaryValues, initialization_guesse # takeoff mass not given if mission_mass <= 0: - if problem_type == ProblemType.ALTERNATE: + if problem_type == ProblemType.OFF_DESIGN_MIN_FUEL: fuel_mass = ( num_pax * ( @@ -378,14 +378,14 @@ def initialization_guessing(aircraft_values: AviaryValues, initialization_guesse ) + fuel_mass ) - elif problem_type == ProblemType.FALLOUT or problem_type == ProblemType.SIZING: + elif problem_type == ProblemType.OFF_DESIGN_MAX_RANGE or problem_type == ProblemType.SIZING: mission_mass = aircraft_values.get_val(Aircraft.Design.GROSS_MASS, units='lbm') initialization_guesses['actual_takeoff_mass'] = mission_mass if cruise_mass_final == 0: # no guess given if problem_type == ProblemType.SIZING: cruise_mass_final = 0.8 - elif problem_type == ProblemType.ALTERNATE: + elif problem_type == ProblemType.OFF_DESIGN_MIN_FUEL: cruise_mass_final = -1 # estimation based on payload and fuel if cruise_mass_final <= 0: diff --git a/aviary/validation_cases/benchmark_tests/test_bench_off_design.py b/aviary/validation_cases/benchmark_tests/test_bench_off_design.py index 48ce28a227..25e57aa548 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_off_design.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_off_design.py @@ -13,7 +13,7 @@ @use_tempdirs class TestEnergyStateOffDesign(unittest.TestCase): - """Test off-design capability for both fallout and alternate missions.""" + """Test off-design capability for both OFF_DESIGN_MAX_RANGE and OFF_DESIGN_MIN_FUEL missions.""" def setUp(self): # run design case @@ -86,18 +86,20 @@ def compare_results(self, comparison_prob): ) @require_pyoptsparse(optimizer='SNOPT') - def test_fallout_mission_match(self): - # run a fallout mission with no changes, essentially recreating the design mission with + def test_off_design_max_range_mission_match(self): + # run a off_design_max_range mission with no changes, essentially recreating the design mission with # different constraints/design variables - prob_fallout = self.prob.run_off_design_mission(problem_type='fallout') - self.compare_results(prob_fallout) - self.assertTrue(prob_fallout.result.success) + prob_off_design_max_range = self.prob.run_off_design_mission( + problem_type='off_design_max_range' + ) + self.compare_results(prob_off_design_max_range) + self.assertTrue(prob_off_design_max_range.result.success) @require_pyoptsparse(optimizer='SNOPT') - def test_fallout_mission_changed(self): - # run a fallout mission with modified payload and gross mass (and therefore different fuel) - prob_fallout = self.prob.run_off_design_mission( - problem_type='fallout', + def test_off_design_max_range_mission_changed(self): + # run a off_design_max_range mission with modified payload and gross mass (and therefore different fuel) + prob_off_design_max_range = self.prob.run_off_design_mission( + problem_type='off_design_max_range', cargo_mass=5000, mission_gross_mass=150_000, num_first_class=1, @@ -105,85 +107,89 @@ def test_fallout_mission_changed(self): num_economy=75, ) assert_near_equal( - prob_fallout.get_val(Aircraft.Design.RANGE), + prob_off_design_max_range.get_val(Aircraft.Design.RANGE), self.prob.get_val(Aircraft.Design.RANGE), tolerance=1e-12, ) - assert_near_equal(prob_fallout.get_val(Mission.RANGE), 2377.4, tolerance=1e-3) + assert_near_equal(prob_off_design_max_range.get_val(Mission.RANGE), 2377.4, tolerance=1e-3) assert_near_equal( - prob_fallout.get_val(Mission.TOTAL_FUEL, 'lbm'), + prob_off_design_max_range.get_val(Mission.TOTAL_FUEL, 'lbm'), 28976.71270599, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Mission.OPERATING_MASS, 'lbm'), + prob_off_design_max_range.get_val(Mission.OPERATING_MASS, 'lbm'), 97798.28729401, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), 5000, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), 23225, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), 18225, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), self.prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), tolerance=1e-12, ) assert_near_equal( - prob_fallout.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), self.prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), tolerance=1e-12, ) assert_near_equal( - prob_fallout.get_val(Mission.GROSS_MASS, 'lbm'), + prob_off_design_max_range.get_val(Mission.GROSS_MASS, 'lbm'), 150000, tolerance=1e-12, ) assert_near_equal( - prob_fallout.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), + prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), 1, tolerance=1e-12, ) assert_near_equal( - prob_fallout.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS), + prob_off_design_max_range.aviary_inputs.get_val( + Aircraft.CrewPayload.NUM_BUSINESS_CLASS + ), 5, tolerance=1e-12, ) assert_near_equal( - prob_fallout.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_ECONOMY_CLASS), + prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_ECONOMY_CLASS), 75, tolerance=1e-12, ) assert_near_equal( - prob_fallout.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), + prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), 81, tolerance=1e-12, ) - self.assertTrue(prob_fallout.result.success) + self.assertTrue(prob_off_design_max_range.result.success) @require_pyoptsparse(optimizer='SNOPT') - def test_alternate_mission_match(self): - # run an alternate mission with no changes, essentially recreating the design mission with + def test_off_design_min_fuel_mission_match(self): + # run an off_design_min_fuel mission with no changes, essentially recreating the design mission with # different constraints/design variables - prob_alternate = self.prob.run_off_design_mission(problem_type='alternate') - self.compare_results(prob_alternate) + prob_off_design_min_fuel = self.prob.run_off_design_mission( + problem_type='off_design_min_fuel' + ) + self.compare_results(prob_off_design_min_fuel) @require_pyoptsparse(optimizer='SNOPT') - def test_alternate_mission_changed(self): - # run an alternate mission with modified range and payload - prob_alternate = self.prob.run_off_design_mission( - problem_type='alternate', + def test_off_design_min_fuel_mission_changed(self): + # run an off_design_min_fuel mission with modified range and payload + prob_off_design_min_fuel = self.prob.run_off_design_mission( + problem_type='off_design_min_fuel', cargo_mass=2500, mission_range=1800, num_first_class=1, @@ -191,77 +197,77 @@ def test_alternate_mission_changed(self): num_economy=144, ) assert_near_equal( - prob_alternate.get_val(Aircraft.Design.RANGE), + prob_off_design_min_fuel.get_val(Aircraft.Design.RANGE), self.prob.get_val(Aircraft.Design.RANGE), tolerance=1e-12, ) - assert_near_equal(prob_alternate.get_val(Mission.RANGE), 1800, tolerance=1e-6) + assert_near_equal(prob_off_design_min_fuel.get_val(Mission.RANGE), 1800, tolerance=1e-6) assert_near_equal( - prob_alternate.get_val(Mission.TOTAL_FUEL, 'lbm'), + prob_off_design_min_fuel.get_val(Mission.TOTAL_FUEL, 'lbm'), 24245.7724282, tolerance=1e-5, ) assert_near_equal( - prob_alternate.get_val(Mission.OPERATING_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Mission.OPERATING_MASS, 'lbm'), 97798.34840008, tolerance=1e-5, ) assert_near_equal( - prob_alternate.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), 2500, tolerance=1e-12, ) assert_near_equal( - prob_alternate.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), 36250, tolerance=1e-5, ) assert_near_equal( - prob_alternate.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), 33750, tolerance=1e-5, ) assert_near_equal( - prob_alternate.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), self.prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), tolerance=1e-12, ) assert_near_equal( - prob_alternate.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), self.prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), tolerance=1e-12, ) assert_near_equal( - prob_alternate.get_val(Mission.GROSS_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Mission.GROSS_MASS, 'lbm'), 158294.12082828, tolerance=1e-5, ) assert_near_equal( - prob_alternate.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), + prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), 1, tolerance=1e-12, ) assert_near_equal( - prob_alternate.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS), + prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS), 5, tolerance=1e-12, ) assert_near_equal( - prob_alternate.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_ECONOMY_CLASS), + prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_ECONOMY_CLASS), 144, tolerance=1e-12, ) assert_near_equal( - prob_alternate.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), + prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), 150, tolerance=1e-12, ) - self.assertTrue(prob_alternate.result.success) + self.assertTrue(prob_off_design_min_fuel.result.success) @use_tempdirs class Test2DOFOffDesign(unittest.TestCase): - """Test off-design capability for both fallout and alternate missions.""" + """Test off-design capability for both off_design_max_range and off_design_min_fuel missions.""" # TODO this test needs more manual verification to root out any remaining bugs @@ -330,149 +336,155 @@ def compare_results(self, comparison_prob): ) @require_pyoptsparse(optimizer='SNOPT') - def test_fallout_mission_match(self): - # run a fallout mission with no changes, essentially recreating the design mission with + def test_off_design_max_range_mission_match(self): + # run a off_design_max_range mission with no changes, essentially recreating the design mission with # different constraints/design variables - prob_fallout = self.prob.run_off_design_mission(problem_type='fallout') - self.compare_results(prob_fallout) + prob_off_design_max_range = self.prob.run_off_design_mission( + problem_type='off_design_max_range' + ) + self.compare_results(prob_off_design_max_range) @require_pyoptsparse(optimizer='SNOPT') - def test_fallout_mission_changed(self): - # run a fallout mission with modified payload and gross mass (and therefore different fuel) + def test_off_design_max_range_mission_changed(self): + # run a off_design_max_range mission with modified payload and gross mass (and therefore different fuel) prob = self.prob - prob_fallout = prob.run_off_design_mission( - problem_type='fallout', + prob_off_design_max_range = prob.run_off_design_mission( + problem_type='off_design_max_range', cargo_mass=5000, mission_gross_mass=155000.0, num_pax=75, ) assert_near_equal( - prob_fallout.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), + prob_off_design_max_range.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), prob.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), tolerance=1e-12, ) - assert_near_equal(prob_fallout.get_val(Mission.RANGE), 3994.25223046, tolerance=1e-4) assert_near_equal( - prob_fallout.get_val(Mission.TOTAL_FUEL, 'lbm'), + prob_off_design_max_range.get_val(Mission.RANGE), 3994.25223046, tolerance=1e-4 + ) + assert_near_equal( + prob_off_design_max_range.get_val(Mission.TOTAL_FUEL, 'lbm'), 39909.74193096, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Mission.OPERATING_MASS, 'lbm'), + prob_off_design_max_range.get_val(Mission.OPERATING_MASS, 'lbm'), 95090.25806904, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), 5000, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), 20000, tolerance=1e-5, ) assert_near_equal( - prob_fallout.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), 15000, tolerance=1e-6, ) # currently not a GASP variable # assert_near_equal( - # prob_fallout.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), + # prob_off_design_max_range.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # tolerance=1e-12, # ) assert_near_equal( - prob_fallout.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), + prob_off_design_max_range.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), tolerance=1e-12, ) assert_near_equal( - prob_fallout.get_val(Mission.GROSS_MASS, 'lbm'), + prob_off_design_max_range.get_val(Mission.GROSS_MASS, 'lbm'), 155000, tolerance=1e-12, ) assert_near_equal( - prob_fallout.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), + prob_off_design_max_range.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), 75, tolerance=1e-12, ) - self.assertTrue(prob_fallout.result.success) + self.assertTrue(prob_off_design_max_range.result.success) @require_pyoptsparse(optimizer='SNOPT') - def test_alternate_mission_match(self): - # run an alternate mission with no changes, essentially recreating the design mission with + def test_off_design_min_fuel_mission_match(self): + # run an off_design_min_fuel mission with no changes, essentially recreating the design mission with # different constraints/design variables - prob_alternate = self.prob.run_off_design_mission(problem_type='alternate') - self.compare_results(prob_alternate) + prob_off_design_min_fuel = self.prob.run_off_design_mission( + problem_type='off_design_min_fuel' + ) + self.compare_results(prob_off_design_min_fuel) @require_pyoptsparse(optimizer='SNOPT') - def test_alternate_mission_changed(self): - # run an alternate mission with modified range and payload + def test_off_design_min_fuel_mission_changed(self): + # run an off_design_min_fuel mission with modified range and payload prob = self.prob - alternate_phase_info = deepcopy(twodof_phase_info) - alternate_phase_info['desc1']['time_duration_bounds'] = ((200.0, 900.0), 's') + off_design_min_fuel_phase_info = deepcopy(twodof_phase_info) + off_design_min_fuel_phase_info['desc1']['time_duration_bounds'] = ((200.0, 900.0), 's') - prob_alternate = prob.run_off_design_mission( - problem_type='alternate', + prob_off_design_min_fuel = prob.run_off_design_mission( + problem_type='off_design_min_fuel', cargo_mass=2100, mission_range=1800, num_pax=150, ) assert_near_equal( - prob_alternate.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), + prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), prob.aviary_inputs.get_val(Aircraft.Design.RANGE, 'nmi'), tolerance=1e-12, ) - assert_near_equal(prob_alternate.get_val(Mission.RANGE), 1800, tolerance=1e-6) + assert_near_equal(prob_off_design_min_fuel.get_val(Mission.RANGE), 1800, tolerance=1e-6) assert_near_equal( - prob_alternate.get_val(Mission.TOTAL_FUEL, 'lbm'), + prob_off_design_min_fuel.get_val(Mission.TOTAL_FUEL, 'lbm'), 21484.97566914, tolerance=1e-6, ) assert_near_equal( - prob_alternate.get_val(Mission.OPERATING_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Mission.OPERATING_MASS, 'lbm'), 95090.25806904, tolerance=1e-6, ) assert_near_equal( - prob_alternate.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.CARGO_MASS, 'lbm'), 2100, tolerance=1e-12, ) assert_near_equal( - prob_alternate.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS, 'lbm'), 32100, tolerance=1e-6, ) assert_near_equal( - prob_alternate.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.CrewPayload.PASSENGER_PAYLOAD_MASS, 'lbm'), 30000, tolerance=1e-6, ) # currently not a GASP variable # assert_near_equal( - # prob_alternate.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), + # prob_off_design_min_fuel.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # prob.get_val(Aircraft.Design.EMPTY_MASS, 'lbm'), # tolerance=1e-12, # ) assert_near_equal( - prob_alternate.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), prob.get_val(Aircraft.Design.GROSS_MASS, 'lbm'), tolerance=1e-12, ) assert_near_equal( - prob_alternate.get_val(Mission.GROSS_MASS, 'lbm'), + prob_off_design_min_fuel.get_val(Mission.GROSS_MASS, 'lbm'), 148675.23373818, tolerance=1e-6, ) assert_near_equal( - prob_alternate.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), + prob_off_design_min_fuel.aviary_inputs.get_val(Aircraft.CrewPayload.NUM_PASSENGERS), 150, tolerance=1e-12, ) - self.assertTrue(prob_alternate.result.success) + self.assertTrue(prob_off_design_min_fuel.result.success) @use_tempdirs @@ -539,7 +551,7 @@ def test_payload_range(self): # verify TOGW for each payload range problem assert_near_equal( - prob.economic_range_prob.get_val(Mission.GROSS_MASS, 'lbm'), + prob.max_fuel_pyld_range_prob.get_val(Mission.GROSS_MASS, 'lbm'), 166539.46027154, tolerance=1e-8, ) @@ -549,7 +561,7 @@ def test_payload_range(self): tolerance=1e-8, ) self.assertTrue(prob.result.success) - self.assertTrue(prob.economic_range_prob.result.success) + self.assertTrue(prob.max_fuel_pyld_range_prob.result.success) self.assertTrue(prob.ferry_range_prob.result.success) @@ -558,7 +570,7 @@ def test_payload_range(self): test = Test2DOFOffDesign() # test = TestEnergyStateOffDesign() test.setUp() - test.test_alternate_mission_match() + test.test_off_design_min_fuel_mission_match() # test = PayloadRangeTest() # test.test_payload_range() diff --git a/aviary/validation_cases/benchmark_tests/test_problem_types_GwGm.py b/aviary/validation_cases/benchmark_tests/test_problem_types_GwGm.py index 00df256fa0..fefa1b5b71 100644 --- a/aviary/validation_cases/benchmark_tests/test_problem_types_GwGm.py +++ b/aviary/validation_cases/benchmark_tests/test_problem_types_GwGm.py @@ -20,119 +20,131 @@ def setUp(self) -> None: class TestOffDesign(TwoDOFTestCase): """ Build the model using a large single aisle commercial transport aircraft data using - GASP mass method and TWO_DEGREES_OF_FREEDOM mission method. Run a fallout mission to test off design. + GASP mass method and TWO_DEGREES_OF_FREEDOM mission method. Run a OFF_DESIGN_MAX_RANGE mission to test off design. """ @require_pyoptsparse(optimizer='IPOPT') def test_off_design_IPOPT(self): - # Fallout Mission - prob_fallout = av.AviaryProblem() - prob_fallout.load_inputs( + # OFF_DESIGN_MAX_RANGE Mission + prob_off_design_max_range = av.AviaryProblem() + prob_off_design_max_range.load_inputs( 'models/aircraft/test_aircraft/aircraft_for_bench_GwGm.csv', self.phase_info, verbosity=Verbosity.QUIET, ) - prob_fallout.problem_type = ProblemType.FALLOUT - prob_fallout.aviary_inputs.set_val('problem_type', ProblemType.FALLOUT, units='unitless') - prob_fallout.aviary_inputs.set_val( + prob_off_design_max_range.problem_type = ProblemType.OFF_DESIGN_MAX_RANGE + prob_off_design_max_range.aviary_inputs.set_val( + 'problem_type', ProblemType.OFF_DESIGN_MAX_RANGE, units='unitless' + ) + prob_off_design_max_range.aviary_inputs.set_val( 'aircraft:design:gross_mass', self.sized_mass, units='lbm' ) - prob_fallout.aviary_inputs.set_val('mission:gross_mass', self.sized_mass, units='lbm') + prob_off_design_max_range.aviary_inputs.set_val( + 'mission:gross_mass', self.sized_mass, units='lbm' + ) - prob_fallout.check_and_preprocess_inputs() + prob_off_design_max_range.check_and_preprocess_inputs() - prob_fallout.build_model() - prob_fallout.add_driver('IPOPT', max_iter=100) - prob_fallout.add_design_variables() - prob_fallout.add_objective() - prob_fallout.setup() - prob_fallout.run_aviary_problem() + prob_off_design_max_range.build_model() + prob_off_design_max_range.add_driver('IPOPT', max_iter=100) + prob_off_design_max_range.add_design_variables() + prob_off_design_max_range.add_objective() + prob_off_design_max_range.setup() + prob_off_design_max_range.run_aviary_problem() - # Alternate Mission - prob_alternate = av.AviaryProblem() - prob_alternate.load_inputs( + # off_design_min_fuel Mission + prob_off_design_min_fuel = av.AviaryProblem() + prob_off_design_min_fuel.load_inputs( 'models/aircraft/test_aircraft/aircraft_for_bench_GwGm.csv', self.phase_info, verbosity=Verbosity.QUIET, ) - prob_alternate.problem_type = ProblemType.ALTERNATE - prob_alternate.aviary_inputs.set_val( - 'problem_type', ProblemType.ALTERNATE, units='unitless' + prob_off_design_min_fuel.problem_type = ProblemType.OFF_DESIGN_MIN_FUEL + prob_off_design_min_fuel.aviary_inputs.set_val( + 'problem_type', ProblemType.OFF_DESIGN_MIN_FUEL, units='unitless' ) - prob_alternate.aviary_inputs.set_val( + prob_off_design_min_fuel.aviary_inputs.set_val( 'aircraft:design:gross_mass', self.sized_mass, units='lbm' ) - prob_alternate.aviary_inputs.set_val('mission:gross_mass', self.sized_mass, units='lbm') + prob_off_design_min_fuel.aviary_inputs.set_val( + 'mission:gross_mass', self.sized_mass, units='lbm' + ) - prob_alternate.check_and_preprocess_inputs() - prob_alternate.build_model() - prob_alternate.add_driver('IPOPT', max_iter=100) - prob_alternate.add_design_variables() - prob_alternate.add_objective() - prob_alternate.setup() - prob_alternate.run_aviary_problem() + prob_off_design_min_fuel.check_and_preprocess_inputs() + prob_off_design_min_fuel.build_model() + prob_off_design_min_fuel.add_driver('IPOPT', max_iter=100) + prob_off_design_min_fuel.add_design_variables() + prob_off_design_min_fuel.add_objective() + prob_off_design_min_fuel.setup() + prob_off_design_min_fuel.run_aviary_problem() - fallout_range = prob_fallout.get_val(av.Mission.RANGE) - alternate_mass = prob_alternate.get_val(av.Mission.GROSS_MASS) - assert_near_equal(fallout_range, self.sized_range, tolerance=0.02) - assert_near_equal(alternate_mass, self.sized_mass, tolerance=0.02) + off_design_max_range_range = prob_off_design_max_range.get_val(av.Mission.RANGE) + off_design_min_fuel_mass = prob_off_design_min_fuel.get_val(av.Mission.GROSS_MASS) + assert_near_equal(off_design_max_range_range, self.sized_range, tolerance=0.02) + assert_near_equal(off_design_min_fuel_mass, self.sized_mass, tolerance=0.02) @require_pyoptsparse(optimizer='SNOPT') def test_off_design_SNOPT(self): - # Fallout Mission - prob_fallout = av.AviaryProblem() - prob_fallout.load_inputs( + # off_design_max_range Mission + prob_off_design_max_range = av.AviaryProblem() + prob_off_design_max_range.load_inputs( 'models/aircraft/test_aircraft/aircraft_for_bench_GwGm.csv', self.phase_info, verbosity=Verbosity.QUIET, ) - prob_fallout.problem_type = ProblemType.FALLOUT - prob_fallout.aviary_inputs.set_val('problem_type', ProblemType.FALLOUT, units='unitless') - prob_fallout.aviary_inputs.set_val( + prob_off_design_max_range.problem_type = ProblemType.OFF_DESIGN_MAX_RANGE + prob_off_design_max_range.aviary_inputs.set_val( + 'problem_type', ProblemType.OFF_DESIGN_MAX_RANGE, units='unitless' + ) + prob_off_design_max_range.aviary_inputs.set_val( 'aircraft:design:gross_mass', self.sized_mass, units='lbm' ) - prob_fallout.aviary_inputs.set_val('mission:gross_mass', self.sized_mass, units='lbm') - - prob_fallout.check_and_preprocess_inputs() - prob_fallout.build_model() - prob_fallout.add_driver('SNOPT', max_iter=100) - prob_fallout.add_design_variables() - prob_fallout.add_objective() - prob_fallout.setup() - prob_fallout.run_aviary_problem() + prob_off_design_max_range.aviary_inputs.set_val( + 'mission:gross_mass', self.sized_mass, units='lbm' + ) - # Alternate Mission - prob_alternate = av.AviaryProblem() - prob_alternate.load_inputs( + prob_off_design_max_range.check_and_preprocess_inputs() + prob_off_design_max_range.build_model() + prob_off_design_max_range.add_driver('SNOPT', max_iter=100) + prob_off_design_max_range.add_design_variables() + prob_off_design_max_range.add_objective() + prob_off_design_max_range.setup() + prob_off_design_max_range.run_aviary_problem() + + # off_design_min_fuel Mission + prob_off_design_min_fuel = av.AviaryProblem() + prob_off_design_min_fuel.load_inputs( 'models/aircraft/test_aircraft/aircraft_for_bench_GwGm.csv', self.phase_info, verbosity=Verbosity.QUIET, ) - prob_alternate.problem_type = ProblemType.ALTERNATE - prob_alternate.aviary_inputs.set_val( - 'problem_type', ProblemType.ALTERNATE, units='unitless' + prob_off_design_min_fuel.problem_type = ProblemType.OFF_DESIGN_MIN_FUEL + prob_off_design_min_fuel.aviary_inputs.set_val( + 'problem_type', ProblemType.OFF_DESIGN_MIN_FUEL, units='unitless' ) - prob_alternate.aviary_inputs.set_val( + prob_off_design_min_fuel.aviary_inputs.set_val( 'aircraft:design:gross_mass', self.sized_mass, units='lbm' ) - prob_alternate.aviary_inputs.set_val('mission:gross_mass', self.sized_mass, units='lbm') - - prob_alternate.check_and_preprocess_inputs() - prob_alternate.build_model() - prob_alternate.add_driver('SNOPT', max_iter=100) - prob_alternate.add_design_variables() - prob_alternate.add_objective() - prob_alternate.setup() - prob_alternate.run_aviary_problem() + prob_off_design_min_fuel.aviary_inputs.set_val( + 'mission:gross_mass', self.sized_mass, units='lbm' + ) - fallout_range = prob_fallout.get_val(av.Mission.RANGE) - alternate_mass = prob_alternate.get_val(av.Mission.GROSS_MASS) - assert_near_equal(fallout_range, self.sized_range, tolerance=0.02) - assert_near_equal(alternate_mass, self.sized_mass, tolerance=0.02) + prob_off_design_min_fuel.check_and_preprocess_inputs() + prob_off_design_min_fuel.build_model() + prob_off_design_min_fuel.add_driver('SNOPT', max_iter=100) + prob_off_design_min_fuel.add_design_variables() + prob_off_design_min_fuel.add_objective() + prob_off_design_min_fuel.setup() + prob_off_design_min_fuel.run_aviary_problem() + + off_design_max_range_range = prob_off_design_max_range.get_val(av.Mission.RANGE) + off_design_min_fuel_mass = prob_off_design_min_fuel.get_val(av.Mission.GROSS_MASS) + assert_near_equal(off_design_max_range_range, self.sized_range, tolerance=0.02) + assert_near_equal(off_design_min_fuel_mass, self.sized_mass, tolerance=0.02) if __name__ == '__main__': diff --git a/aviary/variable_info/enums.py b/aviary/variable_info/enums.py index 49c86e8c7c..e17005f1dd 100644 --- a/aviary/variable_info/enums.py +++ b/aviary/variable_info/enums.py @@ -199,12 +199,12 @@ class ProblemType(Enum): close to design range. This causes the empty weight and the fuel weight to change. - ALTERNATE: Requires a pre-sized aircraft. It holds the design gross + OFF_DESIGN_MIN_FUEL: Requires a pre-sized aircraft. It holds the design gross weight and empty weight constant. It then varies the fuel weight and actual gross weight until the range closes to the off-design range. - FALLOUT: Requires a pre-sized aircraft. It holds the design gross + OFF_DESIGN_MAX_RANGE: Requires a pre-sized aircraft. It holds the design gross weight and empty weight constant. Using the specified actual gross weight, it will then find the maximum distance the off-design aircraft can fly. @@ -218,8 +218,8 @@ class ProblemType(Enum): """ SIZING = 'sizing' - ALTERNATE = 'alternate' - FALLOUT = 'fallout' + OFF_DESIGN_MIN_FUEL = 'off_design_min_fuel' + OFF_DESIGN_MAX_RANGE = 'off_design_max_range' MULTI_MISSION = 'multimission' diff --git a/aviary/variable_info/variable_meta_data.py b/aviary/variable_info/variable_meta_data.py index 69158dadb6..333d629be5 100644 --- a/aviary/variable_info/variable_meta_data.py +++ b/aviary/variable_info/variable_meta_data.py @@ -8231,7 +8231,7 @@ Settings.PROBLEM_TYPE, meta_data=_MetaData, historical_name={'GASP': None, 'FLOPS': None, 'LEAPS1': None}, - desc="Select from Aviary's built in problem types: SIZING, ALTERNATE, FALLOUT and MULTI_MISSION", + desc="Select from Aviary's built in problem types: SIZING, OFF_DESIGN_MIN_FUEL, OFF_DESIGN_MAX_RANGE and MULTI_MISSION", option=True, types=ProblemType, default_value=None, diff --git a/aviary/visualization/dashboard.py b/aviary/visualization/dashboard.py index 1d7f0bc897..3d430e6989 100644 --- a/aviary/visualization/dashboard.py +++ b/aviary/visualization/dashboard.py @@ -1314,7 +1314,7 @@ def dashboard(script_name, port=0, run_in_background=False): 'Payload/Range Diagram', results_tabs_list, """ - Defines key operating points on the aircraft's payload-range envelope from Design and Fallout missions. + Defines key operating points on the aircraft's payload-range envelope from Design and OFF_DESIGN_MAX_RANGE missions. """, reports_dir / 'payload_range_data.csv', )