diff --git a/aviary/interface/methods_for_level2.py b/aviary/interface/methods_for_level2.py index df7b1e577a..b599c1a26a 100644 --- a/aviary/interface/methods_for_level2.py +++ b/aviary/interface/methods_for_level2.py @@ -1,5 +1,6 @@ import csv import json +import math import os import warnings from copy import deepcopy @@ -1276,14 +1277,14 @@ def run_off_design_mission( ---------- problem_type : str, ProblemType The type of off-design mission to be flown. SIZING missions are not allowed. - phase_info : dict (optional) + phase_info : dict, optional The phase_info to use for the off-design mission. If not provided, the phase info used for the previous Aviary run (typically the design mission) is used. - equation_of_motion : str, EquationsOfMotion + equation_of_motion : (str, EquationsOfMotion), optional Which equations of motion to use for the off-design mission. If not provided, the equations of motion used for the previous Aviary run (typically the design mission) is used. - problem_configurator : ProblemConfigurator + problem_configurator : ProblemConfigurator, optional Problem configurator to use for the off-design mission. If not provided, the problem configurator used for the previous Aviary run (typically the design mission) is used. num_first_class : int, optional @@ -1303,7 +1304,8 @@ def run_off_design_mission( cargo_mass : float, optional Total cargo mass flying on off-design mission, in pounds-mass. Optional if using FLOPS- based mass, individual wing and/or misc cargo is defined, and no additional cargo is - being carried elsewhere. + being carried elsewhere. Note: if using FLOPS-based mass, this variable is an override + 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 @@ -1328,9 +1330,9 @@ def run_off_design_mission( Adds takeoff gross mass as a design variable. Useful for cases when operating mass can change between missions. Defaults to False. Cannot be used at the same time as fill_cargo. - verbosity : int, Verbosity + verbosity : (int, Verbosity), optional Sets the printout level for the entire off-design problem that is ran. - payload_range_controls : bool + payload_range_controls : bool, optional Flag used by run_payload_range method call. Adds a cargo variable as a design variable (chosen based on the specific problem), which is allowed to float a small amount to account for issues when hardcoding payload & fuel mass for certain points on the @@ -1382,6 +1384,15 @@ def run_off_design_mission( # update passenger count and cargo masses mass_method = inputs.get_val(Settings.MASS_METHOD) + # Sanity check passenger counts - cover specific edge case that preprocessors won't catch + # Since we inherit mission pax count from sizing mission, we need to overwrite it + if ( + mass_method is FLOPS + and num_pax is None + and not any((num_tourist, num_business, num_first_class)) + ): + num_pax = sum(filter(None, [num_tourist, num_business, num_first_class])) + # only FLOPS cares about seat class or specific cargo categories if mass_method == LegacyCode.FLOPS: if num_first_class is not None: @@ -1457,10 +1468,12 @@ def run_off_design_mission( ) off_design_prob.check_and_preprocess_inputs(verbosity=verbosity) - off_design_prob.add_pre_mission_systems(verbosity=verbosity) - off_design_prob.add_phases(verbosity=verbosity) - off_design_prob.add_post_mission_systems(verbosity=verbosity) - off_design_prob.link_phases(verbosity=verbosity) + # off_design_prob.add_pre_mission_systems(verbosity=verbosity) + # off_design_prob.add_phases(verbosity=verbosity) + # off_design_prob.add_post_mission_systems(verbosity=verbosity) + # off_design_prob.link_phases(verbosity=verbosity) + off_design_prob.build_model(verbosity=verbosity) + if optimizer is None: try: optimizer = self.driver.options['optimizer'] @@ -1563,13 +1576,10 @@ def run_payload_range(self, verbosity=None): ) return () - # Off-design missions do not currently work for GASP masses or missions. + # Off-design missions are not tested with 2DOF missions. mass_method = self.model.aviary_inputs.get_val(Settings.MASS_METHOD) equations_of_motion = self.model.aviary_inputs.get_val(Settings.EQUATIONS_OF_MOTION) - if ( - mass_method == LegacyCode.FLOPS - and equations_of_motion is EquationsOfMotion.HEIGHT_ENERGY - ): + if equations_of_motion is EquationsOfMotion.HEIGHT_ENERGY: # make a copy of the phase_info to avoid modifying the original. phase_info = self.model.mission_info.copy() phase_info['pre_mission'] = self.model.pre_mission_info.copy() @@ -1607,6 +1617,7 @@ def run_payload_range(self, verbosity=None): fuel_2 = self.get_val(Mission.Summary.FUEL_BURNED)[0] + # Operating mass includes unusable fuel, don't double count max_usable_fuel = fuel_capacity - unusable_fuel # An aircraft may be designed with fuel tank capacity that, if fully filled, would @@ -1614,8 +1625,10 @@ def run_payload_range(self, verbosity=None): # 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 - economic_mission_total_payload = gross_mass - operating_mass - max_usable_fuel + # 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 # Calculates Different payload quantities @@ -1628,23 +1641,15 @@ def run_payload_range(self, verbosity=None): * payload_frac ) economic_mission_num_first = int( - (self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS)) + self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) * payload_frac ) economic_mission_num_bus = int( - ( - self.model.aviary_inputs.get_val( - Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS - ) - ) + self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS) * payload_frac ) economic_mission_num_tourist = int( - ( - self.model.aviary_inputs.get_val( - Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS - ) - ) + self.model.aviary_inputs.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS) * payload_frac ) @@ -1679,17 +1684,22 @@ def run_payload_range(self, verbosity=None): max_usable_fuel = gross_mass - operating_mass # Point 4 (Ferry Range): maximum fuel and 0 payload + # Total cargo mass is an input in GASP, but an output in FLOPS. Avoid overriding cargo + # mass to 0 if not using GASP + if mass_method is GASP: + ferry_cargo_mass = 0 + else: + ferry_cargo_mass = None ferry_range_gross_mass = operating_mass + max_usable_fuel - # BUG 0 passengers breaks the problem, so 1 must be used ferry_range_prob = self.run_off_design_mission( problem_type=ProblemType.FALLOUT, phase_info=phase_info, num_first_class=0, num_business=0, - num_tourist=1, + num_tourist=0, wing_cargo=0, misc_cargo=0, - cargo_mass=0, + cargo_mass=ferry_cargo_mass, mission_gross_mass=ferry_range_gross_mass, name=self._name + '_ferry_range', fill_fuel=True, diff --git a/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py b/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py index 200cd72a7d..d4a2ddbad6 100644 --- a/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py +++ b/aviary/subsystems/aerodynamics/gasp_based/gaspaero.py @@ -40,66 +40,31 @@ # data from DRAG # # flap deflection angles, deg +# block auto-formatting of tables +# autopep8: off +# fmt: off adelfd = np.array( [ - 0.0, - 5.0, - 10.0, - 15.0, - 20.0, - 25.0, - 30.0, - 35.0, - 38.0, - 40.0, - 42.0, - 44.0, - 50.0, - 55.0, - 60.0, + 0.0, 5.0, 10.0, 15.0, 20.0, 25.0, 30.0, 35.0, + 38.0, 40.0, 42.0, 44.0, 50.0, 55.0, 60.0, ] ) # flap angle correction of oswald efficiency factor adel6 = np.array( [ - 1.0, - 0.995, - 0.99, - 0.98, - 0.97, - 0.955, - 0.935, - 0.90, - 0.875, - 0.855, - 0.83, - 0.80, - 0.70, - 0.54, - 0.30, + 1.0, 0.995, 0.99, 0.98, 0.97, 0.955, 0.935, 0.90, + 0.875, 0.855, 0.83, 0.80, 0.70, 0.54, 0.30, ] ) # induced drag correction factors asigma = np.array( [ - 0.0, - 0.16, - 0.285, - 0.375, - 0.435, - 0.48, - 0.52, - 0.55, - 0.575, - 0.58, - 0.59, - 0.60, - 0.62, - 0.635, - 0.65, + 0.0, 0.16, 0.285, 0.375, 0.435, 0.48, 0.52, 0.55, + 0.575, 0.58, 0.59, 0.60, 0.62, 0.635, 0.65, ] ) - +# autopep8: off +# fmt: off def deg2rad(d): """Complex step safe deg2rad.""" diff --git a/aviary/utils/preprocessors.py b/aviary/utils/preprocessors.py index 229da392bc..988299a730 100644 --- a/aviary/utils/preprocessors.py +++ b/aviary/utils/preprocessors.py @@ -87,222 +87,186 @@ def preprocess_crewpayload(aviary_options: AviaryValues, meta_data=_MetaData, ve Verbosity, optional Sets level of printouts for this function. """ + # Check and process cargo variables - confirm mass method + if Settings.MASS_METHOD in aviary_options: + mass_method = aviary_options.get_val(Settings.MASS_METHOD) + else: + raise UserWarning('MASS_METHOD not specified. Cannot preprocess cargo inputs.') + if verbosity is not None: # compatibility with being passed int for verbosity verbosity = Verbosity(verbosity) else: verbosity = aviary_options.get_val(Settings.VERBOSITY) + pax_provided = False + design_pax_provided = False - # Some tests, but not all, do not correctly set default values - # # so we need to ensure all these values are available. - - for key in ( - Aircraft.CrewPayload.NUM_PASSENGERS, - Aircraft.CrewPayload.NUM_FIRST_CLASS, - Aircraft.CrewPayload.NUM_BUSINESS_CLASS, - Aircraft.CrewPayload.NUM_TOURIST_CLASS, - Aircraft.CrewPayload.Design.NUM_PASSENGERS, - Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, - Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, - Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, - ): - if key not in aviary_options: - aviary_options.set_val(key, meta_data[key]['default_value']) - - # Sum passenger Counts for later checks and assignments - passenger_count = 0 - for key in ( - Aircraft.CrewPayload.NUM_FIRST_CLASS, - Aircraft.CrewPayload.NUM_BUSINESS_CLASS, - Aircraft.CrewPayload.NUM_TOURIST_CLASS, - ): - passenger_count += aviary_options.get_val(key) - design_passenger_count = 0 - for key in ( - Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, - Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, - Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, - ): - design_passenger_count += aviary_options.get_val(key) + if mass_method == LegacyCode.FLOPS: + pax_keys = [ + Aircraft.CrewPayload.NUM_PASSENGERS, + Aircraft.CrewPayload.NUM_FIRST_CLASS, + Aircraft.CrewPayload.NUM_BUSINESS_CLASS, + Aircraft.CrewPayload.NUM_TOURIST_CLASS, + ] - # Create summary value (num_pax) if it was not assigned by the user - # or if it was set to it's default value of zero - if passenger_count != 0 and aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) == 0: - aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, passenger_count) - if verbosity >= Verbosity.VERBOSE: - warnings.warn( - 'User has specified supporting values for NUM_PASSENGERS but has left ' - 'NUM_PASSENGERS=0. Replacing NUM_PASSENGERS with passenger_count.' - ) - if ( - design_passenger_count != 0 - and aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) == 0 - ): - aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, design_passenger_count) - if verbosity >= Verbosity.VERBOSE: - warnings.warn( - 'User has specified supporting values for Design.NUM_PASSENGERS but has ' - 'left Design.NUM_PASSENGERS=0. Replacing Design.NUM_PASSENGERS with ' - 'design_passenger_count.' - ) + design_pax_keys = [ + Aircraft.CrewPayload.Design.NUM_PASSENGERS, + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, + ] + else: + pax_keys = [Aircraft.CrewPayload.NUM_PASSENGERS] - num_pax = aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) - design_num_pax = aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) + design_pax_keys = [Aircraft.CrewPayload.Design.NUM_PASSENGERS] - # Check summary data against individual data if individual data was entered - if passenger_count != 0 and num_pax != passenger_count: - if verbosity > verbosity.BRIEF: - warnings.warn( - 'Total passenger count (' - f'{aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS)}) does not ' - 'equal the sum of first class + business class + tourist class passengers ' - f'(total of {passenger_count}). Setting total number of passengers to ' - f'{passenger_count}.' - ) - aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, passenger_count) - if design_passenger_count != 0 and design_num_pax != design_passenger_count: - if verbosity > verbosity.BRIEF: - warnings.warn( - 'Design total passenger count (' - f'{aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS)}) ' - 'does not equal the sum of design first class + business class + tourist ' - f'class passengers (total of {design_passenger_count}). Setting total number of ' - f'design passengers to {design_passenger_count}.' - ) - aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, design_passenger_count) - - # TODO these don't have to be errors, we can recover in some cases, for example - # defaulting to all economy class if passenger seat info is not provided. See the - # engine count checks for an example of this. - # Fail if incorrect data sets were provided: - # have you give us enough info to determine where people were sitting vs. designed seats - if num_pax != 0 and design_passenger_count != 0 and passenger_count == 0: - raise UserWarning( - 'The user has specified CrewPayload.NUM_PASSENGERS, and how many of what ' - 'types of seats are on the aircraft. However, the user has not specified ' - 'where those passengers are sitting. User must specify ' - 'CrewPayload.FIRST_CLASS, CrewPayload.NUM_BUSINESS_CLASS, NUM_TOURIST_CLASS ' - 'in aviary_values.' - ) - # where are the people sitting? is first class full? We know how many seats are in each class. - if design_num_pax != 0 and passenger_count != 0 and design_passenger_count == 0: - raise UserWarning( - 'The user has specified Design.NUM_PASSENGERS, and has specified how many ' - 'people are sitting in each class of seats. However, the user has not ' - 'specified how many seats of each class exist in the aircraft. User must ' - 'specify Design.FIRST_CLASS, Design.NUM_BUSINESS_CLASS, ' - 'Design.NUM_TOURIST_CLASS in aviary_values.' - ) - # we don't know which classes this aircraft has been design for. How many 1st class seats are there? + for key in pax_keys: + if key in aviary_options: + # mark that the user provided any information on mission passenger count + pax_provided = True + else: + # default all non-provided passenger info to 0 + aviary_options.set_val(key, 0) - # Copy data over if only one set of data exists - # User has given detailed values for as-flown and NO design values at all - if passenger_count != 0 and design_num_pax == 0 and design_passenger_count == 0: - if verbosity >= Verbosity.VERBOSE: - warnings.warn( - 'User has not input design passengers data. Assuming design is equal to ' - 'as-flown passenger data.' + for key in design_pax_keys: + if key in aviary_options: + # mark that the user provided any information on design passenger count + design_pax_provided = True + else: + # default all non-provided passenger info to 0 + aviary_options.set_val(key, 0) + + # no passenger info provided + if not pax_provided and not design_pax_provided: + if verbosity >= 1: + UserWarning( + 'No passenger information has been provided for the aircraft, assuming ' + 'that this aircraft is not designed to carry passengers.' ) - aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, passenger_count) - aviary_options.set_val( - Aircraft.CrewPayload.Design.NUM_FIRST_CLASS, - aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS), - ) - aviary_options.set_val( - Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS, - aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS), - ) - aviary_options.set_val( - Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, - aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS), - ) - # user has not supplied detailed information on design but has supplied summary information on passengers - elif num_pax != 0 and design_num_pax == 0: - if verbosity >= Verbosity.VERBOSE: - warnings.warn( - 'User has specified as-flown NUM_PASSENGERS but not how many passengers ' - 'the aircraft was designed for in Design.NUM_PASSENGERS. Assuming they ' - 'are equal.' + # only mission passenger info provided + if pax_provided and not design_pax_provided: + if verbosity >= 1: + UserWarning( + 'Passenger information for the aircraft as designed was not provided. ' + 'Assuming that the design passenger count is the same as the passenger ' + 'count for the flown mission.' ) - aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, num_pax) - elif design_passenger_count != 0 and num_pax == 0 and passenger_count == 0: - if verbosity >= Verbosity.VERBOSE: - warnings.warn( - 'User has specified Design.NUM_* passenger values but CrewPyaload.NUM_* ' - 'category has been left blank or set to zero. Assuming they are equal ' - 'to maintain backwards compatibility with converted GASP and FLOPS. ' - 'If you intended to have no passengers on this flight, set ' - 'Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS to zero in aviary_values.' + # set design passengers to mission passenger values + for i in range(len(pax_keys)): + mission_val = aviary_options.get_val(pax_keys[i]) + aviary_options.set_val(design_pax_keys[i], mission_val) + # only design passenger info provided + if not pax_provided and design_pax_provided: + if verbosity >= 1: + UserWarning( + 'Passenger information for the flown mission was not provided. ' + 'Assuming that the mission passenger count is the same as the design ' + 'passenger count.' ) - aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, design_passenger_count) - aviary_options.set_val( - Aircraft.CrewPayload.NUM_FIRST_CLASS, - aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS), - ) - aviary_options.set_val( - Aircraft.CrewPayload.NUM_BUSINESS_CLASS, - aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS), - ) - aviary_options.set_val( - Aircraft.CrewPayload.NUM_TOURIST_CLASS, - aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS), - ) - # user has not supplied detailed information on design but has supplied summary information on passengers - elif design_num_pax != 0 and num_pax == 0: - if verbosity >= Verbosity.VERBOSE: - warnings.warn( - 'User has specified Design.NUM_PASSENGERS but ' - 'CrewPayload.NUM_PASSENGERS has been left blank or set to zero. ' - 'Assuming they are equal to maintain backwards compatibility with ' - 'converted GASP and FLOPS files. If you intended to have no passengers ' - 'on this flight, set Aircraft.CrewPayload.TOTAL_PAYLOAD_MASS to zero in ' - 'aviary_values' + # set mission passengers to design passenger values + for i in range(len(pax_keys)): + design_val = aviary_options.get_val(design_pax_keys[i]) + aviary_options.set_val(pax_keys[i], design_val) + + # check that passenger sums match individual seat class values + design_pax = aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) + mission_pax = aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) + + mission_sum = 0 + design_sum = 0 + for i in range(1, len(pax_keys)): + mission_sum += aviary_options.get_val(pax_keys[i]) + design_sum += aviary_options.get_val(design_pax_keys[i]) + + # Resolve conflicts between seat class totals and num_passengers + # Specific beats general - trust the individual class counts over the total provided, unless + # the class counts are all zero, in which case trust the total provided and assume all economy + # design num_passengers does not match sum of seat classes for design + if design_pax != design_sum: + # if sum of seat classes is zero (design pax is not), assume all economy + if design_sum == 0: + if verbosity >= 1: + UserWarning( + 'Information on seat class distribution for aircraft design was ' + 'not provided - assuming that all passengers are economy class.' + ) + aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS, design_pax) + else: + if verbosity >= 1: + UserWarning( + 'Sum of all passenger classes does not equal total number of ' + 'passengers provided for aircraft design. Overriding ' + 'Aircraft.CrewPayload.Design.NUM_PASSENGERS with the sum of ' + 'passenger classes for design.' + ) + aviary_options.set_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS, design_sum) + + # mission num_passengers does not match sum of seat classes for mission + if mission_pax != mission_sum: + # if sum of seat classes is zero (mission pax is not), assume all economy + if mission_sum == 0: + if verbosity >= 1: + UserWarning( + 'Information on seat class distribution for current mission was ' + 'not provided - assuming that all passengers are economy class.' + ) + aviary_options.set_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS, mission_pax) + else: + if verbosity >= 1: + UserWarning( + 'Sum of all passenger classes does not equal total number of ' + 'passengers provided for current mission. Overriding ' + 'Aircraft.CrewPayload.NUM_PASSENGERS with the sum of ' + 'passenger classes for the flown mission.' + ) + aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, mission_sum) + + # Check cases where mission passengers are greater than design passengers + design_pax = aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) + mission_pax = aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) + if mission_pax > design_pax: + if verbosity >= 1: + UserWarning( + f'The aircraft is designed for {design_pax} passengers but the current ' + f'mission is being flown with {mission_pax} passengers. The mission ' + 'will be flown using the mass of these extra passengers and baggage, ' + 'but this mission may not be realistic due to lack of room.' ) - aviary_options.set_val(Aircraft.CrewPayload.NUM_PASSENGERS, design_num_pax) - - # Perform checks on the final data tables to ensure Design is always larger then As-Flown - if aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS) < aviary_options.get_val( - Aircraft.CrewPayload.NUM_FIRST_CLASS - ): - raise UserWarning( - 'NUM_FIRST_CLASS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS)}) is larger ' - 'than the number of seats set by Design.NUM_FIRST_CLASS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_FIRST_CLASS)})' - ) - if aviary_options.get_val( - Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS - ) < aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS): - raise UserWarning( - 'NUM_BUSINESS_CLASS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS)}) is ' - 'larger than the number of seats set by Design.NUM_BUSINESS_CLASS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS)})' - ) - if aviary_options.get_val( - Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS - ) < aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS): - raise UserWarning( - 'NUM_TOURIST_CLASS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS)}) is ' - 'larger than the number of seats set by Design.NUM_TOURIST_CLASS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS)})' - ) - if aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS) < aviary_options.get_val( - Aircraft.CrewPayload.NUM_PASSENGERS - ): - raise UserWarning( - 'NUM_PASSENGERS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS)}) is larger ' - 'than the number of seats set by Design.NUM_PASSENGERS (' - f'{aviary_options.get_val(Aircraft.CrewPayload.Design.NUM_PASSENGERS)})' - ) - # Check and process cargo variables - confirm mass method - if Settings.MASS_METHOD in aviary_options: - mass_method = aviary_options.get_val(Settings.MASS_METHOD) - else: - raise UserWarning('MASS_METHOD not specified. Cannot preprocess cargo inputs.') + if mass_method == LegacyCode.FLOPS: + # First Class + if aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_FIRST_CLASS + ) < aviary_options.get_val(Aircraft.CrewPayload.NUM_FIRST_CLASS): + if verbosity >= 1: + UserWarning( + 'More first class passengers are flying in this mission than there are ' + 'available first class seats on the aircraft. Assuming these passengers ' + 'have the same mass as other first class passengers, but are sitting in ' + 'different seats.' + ) + # Business Class + if aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_BUSINESS_CLASS + ) < aviary_options.get_val(Aircraft.CrewPayload.NUM_BUSINESS_CLASS): + if verbosity >= 1: + UserWarning( + 'More business class passengers are flying in this mission than there are ' + 'available business class seats on the aircraft. Assuming these passengers ' + 'have the same mass as other business class passengers, but are sitting in ' + 'different seats.' + ) + # Economy Class + if aviary_options.get_val( + Aircraft.CrewPayload.Design.NUM_TOURIST_CLASS + ) < aviary_options.get_val(Aircraft.CrewPayload.NUM_TOURIST_CLASS): + if verbosity >= 1: + UserWarning( + 'More tourist class passengers are flying in this mission than there are ' + 'available tourist class seats on the aircraft. Assuming these passengers ' + 'have the same mass as other tourist class passengers, but are sitting in ' + 'different seats.' + ) # Process GASP based cargo variables if mass_method == LegacyCode.GASP: @@ -419,10 +383,9 @@ def preprocess_crewpayload(aviary_options: AviaryValues, meta_data=_MetaData, ve # calculate and check total payload # NOTE this is only used for error messaging the calculations for analysis are subsystems/mass/gasp_based - design_passenger_payload_mass = design_num_pax * pax_mass_with_bag + design_passenger_payload_mass = design_pax * pax_mass_with_bag max_payload = design_passenger_payload_mass + max_cargo - num_pax = aviary_options.get_val(Aircraft.CrewPayload.NUM_PASSENGERS) - as_flown_passenger_payload_mass = num_pax * pax_mass_with_bag + as_flown_passenger_payload_mass = mission_pax * pax_mass_with_bag as_flown_payload = as_flown_passenger_payload_mass + cargo if as_flown_payload > max_payload and verbosity >= Verbosity.BRIEF: # BRIEF, VERBOSE, DEBUG warnings.warn( @@ -435,30 +398,31 @@ def preprocess_crewpayload(aviary_options: AviaryValues, meta_data=_MetaData, ve aviary_options.set_val(Aircraft.CrewPayload.Design.MAX_CARGO_MASS, max_cargo, 'lbm') aviary_options.set_val(Aircraft.CrewPayload.Design.CARGO_MASS, des_cargo, 'lbm') + # Check flight attendants if Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS not in aviary_options: flight_attendants_count = 0 # assume no passengers - if 0 < passenger_count: - if passenger_count < 51: + if 0 < design_pax: + if design_pax < 51: flight_attendants_count = 1 else: - flight_attendants_count = passenger_count // 40 + 1 + flight_attendants_count = design_pax // 40 + 1 aviary_options.set_val(Aircraft.CrewPayload.NUM_FLIGHT_ATTENDANTS, flight_attendants_count) if Aircraft.CrewPayload.NUM_GALLEY_CREW not in aviary_options: galley_crew_count = 0 # assume no passengers - if 150 < passenger_count: - galley_crew_count = passenger_count // 250 + 1 + if 150 < design_pax: + galley_crew_count = design_pax // 250 + 1 aviary_options.set_val(Aircraft.CrewPayload.NUM_GALLEY_CREW, galley_crew_count) if Aircraft.CrewPayload.NUM_FLIGHT_CREW not in aviary_options: flight_crew_count = 3 - if passenger_count < 151: + if design_pax < 151: flight_crew_count = 2 aviary_options.set_val(Aircraft.CrewPayload.NUM_FLIGHT_CREW, flight_crew_count) 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 f9977c0a46..d4d27aeb01 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_off_design.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_off_design.py @@ -524,8 +524,8 @@ def test_payload_range(self): 38025.0, 38025.0, 24365.60919974074, - 225.0, - ], # due to bug ferry mission must carry 1 passenger + 0, + ], tolerance=1e-10, ) assert_near_equal( @@ -535,7 +535,7 @@ def test_payload_range(self): ) assert_near_equal( prob.payload_range_data.get_val('Range', 'NM'), - [0, 2500, 3973.34, 4415.77], + [0, 2500, 3973.34, 4421.13575083], tolerance=1e-6, ) @@ -547,7 +547,7 @@ def test_payload_range(self): ) assert_near_equal( off_design_probs[1].get_val(Mission.Summary.GROSS_MASS, 'lbm'), - 140868.42657438, + 140540.92087337, tolerance=1e-12, )