diff --git a/aviary/docs/_config.yml b/aviary/docs/_config.yml index 0de6df752..688a3daba 100644 --- a/aviary/docs/_config.yml +++ b/aviary/docs/_config.yml @@ -13,6 +13,8 @@ execute: timeout: 120 # execute_notebooks: auto +exclude_patterns: ["*/reports/*"] + # Define the name of the latex output file for PDF builds latex: latex_documents: diff --git a/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb b/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb index 6f92f388f..cb33605c1 100644 --- a/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb +++ b/aviary/docs/examples/coupled_aircraft_mission_optimization.ipynb @@ -451,7 +451,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.10" + "version": "3.8.17" } }, "nbformat": 4, diff --git a/aviary/docs/user_guide/outputs_and_how_to_read_them.md b/aviary/docs/user_guide/outputs_and_how_to_read_them.md index 87672db50..7bc0d67ab 100644 --- a/aviary/docs/user_guide/outputs_and_how_to_read_them.md +++ b/aviary/docs/user_guide/outputs_and_how_to_read_them.md @@ -21,7 +21,8 @@ The dashboard assumes these locations for the various reports that are embedded | Optimization | SNOPT Output (similarly for other optimizers) | ./reports/*name_of_run_script*/SNOPT_print.out | | Optimization | Desvars, cons, opt plot | Derived from Case Recorder file specified by `driver_recorder` command option | | Results | Trajectory Results Report | ./reports/*name_of_run_script*/traj_results_report.html | -| Results | Aviary Variables | Derived from Case Recorder file specified by `problem_recorder` command option | +| Results | Subsystem Results | ./reports/subsystems/*name_of_subsystem.md (or .html)* | +| Results | Mission Results | ./reports/subsystems/mission_summary.md | As an example of the workflow for the dashboard, assume that the user has run an Aviary script, `test_full_mission_solved_level3`, which records both the `Problem` final case and also all the cases of the optimization done by the `Driver`. (To record both the Problem final case and also the Driver optimization iterations, the user must make use of the `optimization_history_filename` option in the call to `run_aviary_problem`.) diff --git a/aviary/interface/methods_for_level2.py b/aviary/interface/methods_for_level2.py index de18ddd5f..38c2866ac 100644 --- a/aviary/interface/methods_for_level2.py +++ b/aviary/interface/methods_for_level2.py @@ -15,6 +15,7 @@ from openmdao.core.component import Component from openmdao.utils.mpi import MPI from openmdao.utils.units import convert_units +from openmdao.utils.reports_system import _default_reports from aviary.constants import GRAV_ENGLISH_LBM, RHO_SEA_LEVEL_ENGLISH from aviary.mission.flops_based.phases.build_landing import Landing @@ -210,6 +211,12 @@ class AviaryProblem(om.Problem): """ def __init__(self, analysis_scheme=AnalysisScheme.COLLOCATION, **kwargs): + # Modify OpenMDAO's default_reports for this session. + new_reports = ['subsystems', 'mission'] + for report in new_reports: + if report not in _default_reports: + _default_reports.append(report) + super().__init__(**kwargs) self.timestamp = datetime.now() @@ -1668,8 +1675,6 @@ def add_design_variables(self): self.model.add_constraint("h_fit.h_init_flaps", equals=400.0, units="ft", ref=400.0) - self.problem_type = self.aviary_inputs.get_val('problem_type') - # vehicle sizing problem # size the vehicle (via design GTOW) to meet a target range using all fuel capacity if self.problem_type is ProblemType.SIZING: diff --git a/aviary/interface/reports.py b/aviary/interface/reports.py index 76889b95e..9238ddcd9 100644 --- a/aviary/interface/reports.py +++ b/aviary/interface/reports.py @@ -1,7 +1,11 @@ from pathlib import Path - from openmdao.utils.reports_system import register_report +import numpy as np + +from aviary.interface.utils.markdown_utils import write_markdown_variable_table +from aviary.utils.named_values import NamedValues + def register_custom_reports(): """ @@ -9,7 +13,6 @@ def register_custom_reports(): added to the same reports folder as other default reports """ # TODO top-level aircraft report? - # TODO mission report? # TODO add flag to skip registering reports? # register per-subsystem report generation @@ -17,22 +20,29 @@ def register_custom_reports(): func=subsystem_report, desc='Generates reports for each subsystem builder in the ' 'Aviary Problem', - class_name='Problem', - method='run_model', + class_name='AviaryProblem', + method='run_driver', pre_or_post='post', # **kwargs ) + register_report(name='mission', + func=mission_report, + desc='Generates report for mission results from Aviary problem', + class_name='AviaryProblem', + method='run_driver', + pre_or_post='post') + def subsystem_report(prob, **kwargs): """ Loops through all subsystem builders in the AviaryProblem calls their write_report - method. All generated report files are placed in the ./reports/subsystem_reports folder + method. All generated report files are placed in the "reports/subsystem_reports" folder Parameters ---------- prob : AviaryProblem - The AviaryProblem that will be used to generate this report + The AviaryProblem used to generate this report """ reports_folder = Path(prob.get_reports_dir() / 'subsystems') reports_folder.mkdir(exist_ok=True) @@ -42,3 +52,97 @@ def subsystem_report(prob, **kwargs): for subsystem in core_subsystems.values(): subsystem.report(prob, reports_folder, **kwargs) + + +def mission_report(prob, **kwargs): + """ + Creates a basic mission summary report that is place in the "reports" folder + + Parameters + ---------- + prob : AviaryProblem + The AviaryProblem used to generate this report + """ + def _get_phase_value(traj, phase, var_name, units, indices=None): + try: + vals = prob.get_val(f"{traj}.{phase}.timeseries.{var_name}", + units=units, + indices=indices,) + except KeyError: + try: + vals = prob.get_val(f"{traj}.{phase}.{var_name}", + units=units, + indices=indices,) + # 2DOF breguet range cruise uses time integration to track mass + except TypeError: + vals = prob.get_val(f"{traj}.{phase}.timeseries.time", + units=units, + indices=indices,) + except KeyError: + vals = None + + return vals + + def _get_phase_diff(traj, phase, var_name, units, indices=[0, -1]): + vals = _get_phase_value(traj, phase, var_name, units, indices) + + if vals is not None: + diff = vals[-1]-vals[0] + if isinstance(diff, np.ndarray): + diff = diff[0] + return diff + else: + return None + + reports_folder = Path(prob.get_reports_dir()) + report_file = reports_folder / 'mission_summary.md' + + # read per-phase data from trajectory + data = {} + for idx, phase in enumerate(prob.phase_info): + # TODO for traj in trajectories, currently assuming single one named "traj" + # TODO delta mass and fuel consumption need to be tracked separately + fuel_burn = _get_phase_diff('traj', phase, 'mass', 'lbm', [-1, 0]) + time = _get_phase_diff('traj', phase, 't', 'min') + range = _get_phase_diff('traj', phase, 'distance', 'nmi') + + # get initial values, first in traj + if idx == 0: + initial_mass = _get_phase_value('traj', phase, 'mass', 'lbm', 0)[0] + initial_time = _get_phase_value('traj', phase, 't', 'min', 0) + initial_range = _get_phase_value('traj', phase, 'distance', 'nmi', 0)[0] + + outputs = NamedValues() + # Fuel burn is negative of delta mass + outputs.set_val('Fuel Burn', fuel_burn, 'lbm') + outputs.set_val('Elapsed Time', time, 'min') + outputs.set_val('Ground Distance', range, 'nmi') + data[phase] = outputs + + # get final values, last in traj + final_mass = _get_phase_value('traj', phase, 'mass', 'lbm', -1)[0] + final_time = _get_phase_value('traj', phase, 't', 'min', -1) + final_range = _get_phase_value('traj', phase, 'distance', 'nmi', -1)[0] + + totals = NamedValues() + totals.set_val('Total Fuel Burn', initial_mass - final_mass, 'lbm') + totals.set_val('Total Time', final_time - initial_time, 'min') + totals.set_val('Total Ground Distance', final_range - initial_range, 'nmi') + + with open(report_file, mode='w') as f: + f.write('# MISSION SUMMARY') + write_markdown_variable_table(f, totals, + ['Total Fuel Burn', + 'Total Time', + 'Total Ground Distance'], + {'Total Fuel Burn': {'units': 'lbm'}, + 'Total Time': {'units': 'min'}, + 'Total Ground Distance': {'units': 'nmi'}}) + + f.write('\n# MISSION SEGMENTS') + for phase in data: + f.write(f'\n## {phase}') + write_markdown_variable_table(f, data[phase], ['Fuel Burn', 'Elapsed Time', 'Ground Distance'], + {'Fuel Burn': {'units': 'lbm'}, + 'Elapsed Time': {'units': 'min'}, + 'Ground Distance': {'units': 'nmi'}}) diff --git a/aviary/interface/utils/markdown_utils.py b/aviary/interface/utils/markdown_utils.py index 4928d617a..009098174 100644 --- a/aviary/interface/utils/markdown_utils.py +++ b/aviary/interface/utils/markdown_utils.py @@ -1,13 +1,18 @@ import numpy as np from math import floor, log10 -# TODO openMDAO has generate_table() that can eventually replace this +# TODO openMDAO has generate_table() that might be able to replace this -# TODO this might have other use cases, move to utils if so +# TODO rounding might have other use cases, move to utils if so def round_it(x, sig=None): # default sig figs to 2 decimal places out + if isinstance(x, str): + try: + x = float(x) + except ValueError: + return x if not sig: sig = len(str(round(x)))+2 if x != 0: @@ -44,6 +49,8 @@ def write_markdown_variable_table(open_file, problem, outputs, metadata): val = problem.aviary_inputs.get_val(var_name, units) else: val, units = problem.aviary_inputs.get_item(var_name) + if (val, units) == (None, None): + raise KeyError except KeyError: val = 'Not Found in Model' units = None @@ -54,11 +61,10 @@ def write_markdown_variable_table(open_file, problem, outputs, metadata): if len(val) == 1: val = val[0] else: - round_it(val) + val = round_it(val) if not units: units = 'unknown' - summary_line = f'| {var_name} | {val} |' - if units != 'unitless': - summary_line = summary_line + f' {units}' - summary_line = summary_line + ' |\n' + if units == 'unitless': + units = '-' + summary_line = f'| {var_name} | {val} | {units} |\n' open_file.write(summary_line) diff --git a/aviary/subsystems/geometry/geometry_builder.py b/aviary/subsystems/geometry/geometry_builder.py index 16cbefd34..ea8aa4eaa 100644 --- a/aviary/subsystems/geometry/geometry_builder.py +++ b/aviary/subsystems/geometry/geometry_builder.py @@ -102,10 +102,11 @@ def report(self, prob, reports_folder, **kwargs): Aircraft.Fuselage.AVG_DIAMETER] with open(filepath, mode='w') as f: - method = self.code_origin + ' METHOD' if self.use_both_geometries: - method = ('FLOPS AND GASP METHODS') - f.write(f'# GEOMETRY: {method}\n') + method = ('FLOPS and GASP methods') + else: + method = self.code_origin.value + ' method' + f.write(f'# Geometry: {method}\n') f.write('## Wing') write_markdown_variable_table(f, prob, wing_outputs, self.meta_data) f.write('\n## Empennage\n') diff --git a/aviary/subsystems/mass/mass_builder.py b/aviary/subsystems/mass/mass_builder.py index 3574eda1b..e99e0ac3d 100644 --- a/aviary/subsystems/mass/mass_builder.py +++ b/aviary/subsystems/mass/mass_builder.py @@ -119,6 +119,6 @@ def report(self, prob, reports_folder, **kwargs): ] with open(filepath, mode='w') as f: - method = self.code_origin + ' ESTIMATING RELATIONS' - f.write(f'# MASS ESTIMATION: {method}') + method = self.code_origin.value + '-derived relations' + f.write(f'# Mass estimation: {method}') write_markdown_variable_table(f, prob, outputs, self.meta_data) diff --git a/aviary/subsystems/propulsion/engine_deck.py b/aviary/subsystems/propulsion/engine_deck.py index bb5df6da4..64b1416cb 100644 --- a/aviary/subsystems/propulsion/engine_deck.py +++ b/aviary/subsystems/propulsion/engine_deck.py @@ -40,6 +40,7 @@ from aviary.variable_info.variable_meta_data import _MetaData from aviary.variable_info.variables import Aircraft, Dynamic, Mission from aviary.utils.csv_data_file import read_data_file +from aviary.interface.utils.markdown_utils import round_it MACH = EngineModelVariables.MACH @@ -891,6 +892,84 @@ def build_mission(self, num_nodes, aviary_inputs): return engine_group + def report(self, problem, reports_file, **kwargs): + meta_data = kwargs['meta_data'] + + outputs = [Aircraft.Engine.NUM_ENGINES, + Aircraft.Engine.SCALED_SLS_THRUST, + Aircraft.Engine.SCALE_FACTOR] + + # determine which index in problem-level aviary values corresponds to this engine + engine_idx = None + for idx, engine in enumerate(problem.aviary_inputs.get_val('engine_models')): + if engine.name == self.name: + engine_idx = idx + + if engine_idx is None: + with open(reports_file, mode='a') as f: + f.write(f'\n### {self.name}') + f.write(f'\nEngine deck {self.name} not found\n') + return + + # modified version of markdown table util adjusted to handle engine decks + with open(reports_file, mode='a') as f: + f.write(f'\n### {self.name}') + f.write('\n| Variable Name | Value | Units |\n') + f.write('| :- | :- | :- |\n') + for var_name in outputs: + # get default units from metadata + try: + units = meta_data[var_name]['units'] + except KeyError: + units = None + # try to get value from engine + try: + if units: + val = self.get_val(var_name, units) + else: + val, units = self.get_item(var_name) + if (val, units) == (None, None): + raise KeyError + except KeyError: + # get value from problem + try: + if units: + val = problem.get_val(var_name, units) + else: + # TODO find units for variable in problem? + val = problem.get_val(var_name) + units = 'unknown' + # variable not in problem, get from aviary_inputs instead + except KeyError: + try: + if units: + val = problem.aviary_inputs.get_val(var_name, units) + else: + val, units = problem.aviary_inputs.get_item(var_name) + if (val, units) == (None, None): + raise KeyError + except KeyError: + val = 'Not Found in Model' + units = None + else: + val = val[engine_idx] + else: + val = val[engine_idx] + # handle rounding + formatting + if isinstance(val, (np.ndarray, list, tuple)): + val = [round_it(item) for item in val] + # if an interable with a length of 1, remove bracket/paretheses, etc. + if len(val) == 1: + val = val[0] + else: + round_it(val) + if not units: + units = 'unknown' + if units == 'unitless': + units = '-' + summary_line = f'| {var_name} | {val} | {units} |\n' + f.write(summary_line) + def _set_reference_thrust(self): """ Determine maximum sea-level static thrust produced by the engine (unscaled). diff --git a/aviary/subsystems/propulsion/engine_model.py b/aviary/subsystems/propulsion/engine_model.py index 41346de49..28b2a5c82 100644 --- a/aviary/subsystems/propulsion/engine_model.py +++ b/aviary/subsystems/propulsion/engine_model.py @@ -30,6 +30,7 @@ class EngineModel(SubsystemBuilderBase): build_mission build_post_mission get_val + get_item set_val update """ @@ -178,6 +179,26 @@ def get_val(self, key, units='unitless'): """ return self.options.get_val(key, units) + def get_item(self, key, default=(None, None)): + ''' + Return the named value and its associated units. + + Note, this method never raises `KeyError` or `TypeError`. + + Parameters + ---------- + key : str + the name of the item + + default : OptionalValueAndUnits (None, None) + if the item does not exist, return this object + + Returns + ------- + OptionalValueAndUnits + ''' + return self.options.get_item(key, default) + def set_val(self, key, val, units='unitless'): """ Updates desired value in options with specified units. diff --git a/aviary/subsystems/propulsion/propulsion_builder.py b/aviary/subsystems/propulsion/propulsion_builder.py index d59af880a..2f691c468 100644 --- a/aviary/subsystems/propulsion/propulsion_builder.py +++ b/aviary/subsystems/propulsion/propulsion_builder.py @@ -61,14 +61,12 @@ def report(self, prob, reports_folder, **kwargs): propulsion_outputs = [Aircraft.Propulsion.TOTAL_NUM_ENGINES, Aircraft.Propulsion.TOTAL_SCALED_SLS_THRUST] - engine_outputs = [Aircraft.Engine.NUM_ENGINES, - Aircraft.Engine.SCALE_FACTOR, - Aircraft.Engine.SCALED_SLS_THRUST] - with open(filepath, mode='w') as f: - f.write('# PROPULSION') + f.write('# Propulsion') write_markdown_variable_table(f, prob, propulsion_outputs, self.meta_data) - f.write('\n## ENGINES') - for engine in prob.aviary_inputs.get_val('engine_models'): - f.write(f'\n### {engine.name}') - write_markdown_variable_table(f, engine, engine_outputs, self.meta_data) + f.write('\n## Engines') + + # each engine can append to this file + kwargs['meta_data'] = self.meta_data + for engine in prob.aviary_inputs.get_val('engine_models'): + engine.report(prob, filepath, **kwargs) diff --git a/aviary/utils/report.py b/aviary/utils/report.py deleted file mode 100644 index 2e4c07b74..000000000 --- a/aviary/utils/report.py +++ /dev/null @@ -1,343 +0,0 @@ -import numpy as np -from openmdao.utils.assert_utils import assert_near_equal - -from aviary.variable_info.variables import Aircraft, Dynamic, Mission - - -def report_cruise(prob, remote=False, file=None): - variables = [ - ("mass", "lbm", "mass_initial", "mass_final"), - ("alpha", "deg", "aero_initial.alpha", "aero_final.alpha"), - ("CL", "", "aero_initial.CL", "aero_final.CL"), - ("CD", "", "aero_initial.CD", "aero_final.CD"), - (Dynamic.Mission.THRUST_TOTAL, "lbf", "prop_initial.thrust_net_total", - "prop_final.thrust_net_total"), - (Dynamic.Mission.FUEL_FLOW_RATE_TOTAL, "lbm/s", - "fuel_flow_initial", "fuel_flow_final"), - ("TSFC", "1/h", "prop_initial.sfc", "prop_final.sfc"), - ] - - print(60 * "=", file=file) - print("{:^60}".format("cruise performance"), file=file) - print("{:<21} {:>15} {:>15}".format("variable", "initial", "final"), file=file) - print(60 * "-", file=file) - for name, unstr, pathi, pathf in variables: - units = None if unstr == "" else unstr - vali = prob.get_val(f"cruise.{pathi}", units=units, get_remote=remote)[0] - valf = prob.get_val(f"cruise.{pathf}", units=units, get_remote=remote)[0] - print(f"{name:<10} {unstr:>10} | {vali:15.4f} | {valf:15.4f}", file=file) - print(60 * "=", file=file) - - -def report_fuelburn(prob, remote=False, file=None): - traj_phases = { - "traj": [ - "groundroll", - "rotation", - "ascent", - "accel", - "climb1", - "climb2", - "cruise", - "desc1", - "desc2", - ] - } - - print(40 * "=", file=file) - print("{:^40}".format("fuel burn in each phase"), file=file) - print(40 * "-", file=file) - for traj, phaselist in traj_phases.items(): - for phase in phaselist: - if phase == 'cruise': - vals = prob.get_val( - f"{traj}.{phase}.timeseries.mass", - units="lbm", - indices=[-1, 0], - get_remote=remote, - ) - else: - vals = prob.get_val( - f"{traj}.{phase}.states:mass", - units="lbm", - indices=[-1, 0], - get_remote=remote, - ) - diff = np.diff(vals, axis=0)[0, 0] - print(f"{phase:<12} {diff:>8.2f} lbm", file=file) - print(40 * "=", file=file) - - -def report_gasp_comparison(prob, rtol=0.1, remote=False, file=None): - # values from GASP output of large single aisle 1 at constant altitude using (I think) turbofan_23k_1 - expected_vals = [ - ( - "Design GTOW (lb)", - 175400.0, - prob.get_val(Mission.Design.GROSS_MASS, get_remote=remote)[0], - ), - ( - "Actual GTOW (lb)", - 175400.0, - prob.get_val(Mission.Summary.GROSS_MASS, get_remote=remote)[0], - ), - ( - "OEM (lb)", - 96348.0, - prob.get_val(Aircraft.Design.OPERATING_MASS, get_remote=remote)[0], - ), - # mass at end of descent - ( - "final mass (lb)", - 137348.0, - prob.get_val(Mission.Landing.TOUCHDOWN_MASS, get_remote=remote)[0], - ), - # block fuel, includes reserves - ( - "block fuel (lb)", - 43049.6, - prob.get_val(Mission.Summary.TOTAL_FUEL_MASS, get_remote=remote)[0], - ), - ( - "landing distance (ft)", - 2980.0, - prob.get_val('landing.'+Mission.Landing.GROUND_DISTANCE, - get_remote=remote)[0], - ), - ( - "flight distance (NM)", - 3675.0, - prob.get_val( - "traj.desc2.timeseries.distance", units="NM", get_remote=remote - )[-1][0], - ), - # ROC @ 49 seconds to get from 456.3 ft to 500 ft minus liftoff time - ( - "ascent duration (s)", - 49 + (500 - 465.3) / (2674.4 / 60) - 31.2, - prob.get_val("traj.ascent.t_duration", get_remote=remote)[0], - ), - # AEO takeoff table - ( - "gear retraction time (s)", - 37.3, - prob.get_val("h_fit.t_init_gear", get_remote=remote)[0], - ), - ( - "flaps retraction time (s)", - 47.6, - prob.get_val("h_fit.t_init_flaps", get_remote=remote)[0], - ), - # block time minus taxi time, no final descent - ( - "flight time (hr)", - 8.295 - 10 / 60, - prob.get_val("traj.desc2.timeseries.time", units="h", get_remote=remote)[ - -1 - ][0], - ), - ] - - print(80 * "=", file=file) - print("{:^80}".format("comparison with reference GASP Large Single Aisle 1"), file=file) - print( - "{:<28} | {:>15} | {:>15} | {:>10}".format( - "variable", "GASP vals", "Aviary actual", "rel. err." - ), - file=file, - ) - print(80 * "-", file=file) - results = { - item[0]: { - "desired": item[1], - "actual": item[2], - "rerr": (item[2] - item[1]) / item[1], - } - for item in expected_vals - } - for label, desired, actual in expected_vals: - mark = "" - rerr = (actual - desired) / desired - if abs(rerr) > rtol: - mark = "X" - try: - assert_near_equal(actual, desired, tolerance=rtol) - except ValueError: - mark = "X" - print( - f"{label:<28} | {desired:15.4f} | {actual:15.4f} | {rerr:> .2e} {mark}", - file=file, - ) - print(80 * "=", file=file) - return results - - -def report_benchmark_comparison( - prob, rtol=0.1, remote=False, file=None, size_engine=False, base='GASP' -): - # values from GASP output of large single aisle 1 at constant altitude using (I think) turbofan_23k_1 - - no_size = [ - 175310.9512, - 175310.9512, - 96457.2320, - 137455.2320, - 42853.7192, - 2615.5252, - 3675.0000, - 17.3416, - 38.2458, - 48.2047, - 8.1171, - ] - yes_size = [ - 175539.0746, - 175539.0746, - 96587.0999, - 137585.1013, - 42951.9734, - 2568.6457, - 3675.0000, - 21.4275, - 37.9418, - 49.8459, - 8.1145, - ] - # no_size_yes_pyCycle = [ - # 175394.6488, - # 175394.6488, - # 95820.0936, - # 136997.9090, - # 43394.7398, - # 2560.4481, - # 3675.0000, - # 10.0131, - # 33.2441, - # 40.2571, - # 9.5767, - # ] - - FLOPS_base = [ - 173370.02714369, - 0.000001, # not exactly zero to avoid divide by zero errors - 95569.5191, - 136425.51908379, - 39944.5081, - 7732.7314, - 3378.7, - ] - if size_engine: - check_val = yes_size - elif not size_engine: - check_val = no_size - # elif not size_engine and pyCycle_used: - # check_val = no_size_yes_pyCycle - if base == 'FLOPS': - check_val = FLOPS_base - distance_name = 'traj.descent.timeseries.distance' - landing_dist_name = Mission.Landing.GROUND_DISTANCE - else: - distance_name = "traj.desc2.timeseries.distance" - landing_dist_name = 'landing.'+Mission.Landing.GROUND_DISTANCE - - expected_vals = [ - ( - "Design GTOW (lb)", - check_val[0], - prob.get_val(Mission.Design.GROSS_MASS, get_remote=remote)[0], - ), - ( - "Actual GTOW (lb)", - check_val[1], - prob.get_val(Mission.Summary.GROSS_MASS, get_remote=remote)[0], - ), - ( - "OEW (lb)", - check_val[2], - prob.get_val(Aircraft.Design.OPERATING_MASS, get_remote=remote)[0], - ), - # mass at end of descent - ( - "final mass (lb)", - check_val[3], - prob.get_val(Mission.Landing.TOUCHDOWN_MASS, get_remote=remote)[0], - ), - # block fuel, includes reserves - ( - "block fuel (lb)", - check_val[4], - prob.get_val(Mission.Summary.TOTAL_FUEL_MASS, get_remote=remote)[0], - ), - ( - "landing distance (ft)", - check_val[5], - prob.get_val(landing_dist_name, - get_remote=remote)[0], - ), - ( - "flight distance (NM)", - check_val[6], - prob.get_val( - distance_name, units="NM", get_remote=remote - )[-1][0], - ), - ] - - if base != 'FLOPS': - GASP_vals = [ - # ROC @ 49 seconds to get from 456.3 ft to 500 ft minus liftoff time - ( - "ascent duration (s)", - check_val[7], - prob.get_val("traj.ascent.t_duration", get_remote=remote)[0], - ), - # AEO takeoff table - ( - "gear retraction time (s)", - check_val[8], - prob.get_val("h_fit.t_init_gear", get_remote=remote)[0], - ), - ( - "flaps retraction time (s)", - check_val[9], - prob.get_val("h_fit.t_init_flaps", get_remote=remote)[0], - ), - # block time minus taxi time, no final descent - ( - "flight time (hr)", - check_val[10], - prob.get_val("traj.desc2.timeseries.time", units="h", get_remote=remote)[ - -1 - ][0], - ), - ] - expected_vals = expected_vals + GASP_vals - - print(80 * "=", file=file) - print( - "{:^80}".format( - "comparison with reference value (not a validated value, just a point to check against)" - ), - file=file, - ) - print( - "{:<28} | {:>15} | {:>15} | {:>10}".format( - "variable", "original vals", "Aviary actual", "rel. err." - ), - file=file, - ) - print(80 * "-", file=file) - for label, desired, actual in expected_vals: - mark = "" - rerr = (actual - desired) / desired - if abs(rerr) > rtol: - mark = "X" - try: - assert_near_equal(actual, desired, tolerance=rtol) - except ValueError: - mark = "X" - print( - f"{label:<28} | {desired:15.4f} | {actual:15.4f} | {rerr:> .2e} {mark}", - file=file, - ) - print(80 * "=", file=file) diff --git a/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py b/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py index 8774def13..5a5d0d775 100644 --- a/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py +++ b/aviary/validation_cases/benchmark_tests/test_bench_FwFm.py @@ -389,6 +389,6 @@ def test_bench_FwFm_SNOPT_MPI(self): if __name__ == '__main__': - test = ProblemPhaseTestCase() + test = TestBenchFwFmSerial() test.setUp() test.test_bench_FwFm_SNOPT() diff --git a/setup.py b/setup.py index 695e3949f..d4b958fcf 100644 --- a/setup.py +++ b/setup.py @@ -81,7 +81,7 @@ 'aviary=aviary.interface.cmd_entry_points:aviary_cmd', ], 'openmdao_report': [ - 'summary=aviary.interface.reports:register_custom_reports', + 'aviary_reports=aviary.interface.reports:register_custom_reports', ] } )