Skip to content

Commit

Permalink
Merge pull request #482 from fast-aircraft-design/mission-outputs
Browse files Browse the repository at this point in the history
Modification of mission outputs
  • Loading branch information
ScottDelbecq committed Mar 22, 2023
2 parents a537939 + 96f4719 commit ddc07f6
Show file tree
Hide file tree
Showing 12 changed files with 412 additions and 177 deletions.
3 changes: 2 additions & 1 deletion src/fastoad/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""This module gathers key FAST-OAD classes and functions for convenient import."""
# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
# Copyright (C) 2022 ONERA & ISAE-SUPAERO
# Copyright (C) 2023 ONERA & ISAE-SUPAERO
# FAST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
Expand Down Expand Up @@ -67,4 +67,5 @@
mass_breakdown_bar_plot,
mass_breakdown_sun_plot,
wing_geometry_plot,
payload_range_plot,
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Mission generator."""
# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
# Copyright (C) 2022 ONERA & ISAE-SUPAERO
# Copyright (C) 2023 ONERA & ISAE-SUPAERO
# FAST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
Expand Down Expand Up @@ -77,8 +77,7 @@ def __init__(
"""
self._structure_builders: Dict[str, AbstractStructureBuilder] = {}

#: The prefix for auto-generated variable names
self.variable_prefix: str = variable_prefix
self._variable_prefix: str = variable_prefix

#: The definition of missions as provided in input file.
self.definition: MissionDefinition = mission_definition
Expand Down Expand Up @@ -142,6 +141,16 @@ def mission_name(self, value):
f'No Mission named "{value}" in provided mission file.'
)

@property
def variable_prefix(self):
"""The prefix for auto-generated variable names."""
return self._variable_prefix

@variable_prefix.setter
def variable_prefix(self, value):
self._variable_prefix = value
self._update_structure_builders()

def build(self, inputs: Optional[Mapping] = None, mission_name: str = None) -> Mission:
"""
Builds the flight sequence from definition file.
Expand Down Expand Up @@ -272,7 +281,7 @@ def get_input_weight_variable_name(self, mission_name: str = None) -> Optional[s
def _update_structure_builders(self):
for mission_name in self._definition[MISSION_DEFINITION_TAG]:
self._structure_builders[mission_name] = MissionStructureBuilder(
self._definition, mission_name, variable_prefix=self.variable_prefix
self._definition, mission_name, variable_prefix=self._variable_prefix
)

if self.get_input_weight_variable_name(mission_name) is None:
Expand Down Expand Up @@ -526,10 +535,12 @@ def _add_default_taxi_takeoff(self, mission_name):
}
}

taxi_out = PhaseStructureBuilder(definition, "taxi_out", mission_name, self.variable_prefix)
taxi_out = PhaseStructureBuilder(
definition, "taxi_out", mission_name, self._variable_prefix
)
taxi_out_structure = self._structure_builders[mission_name].process_builder(taxi_out)
self._structure_builders[mission_name].structure[PARTS_TAG].insert(0, taxi_out_structure)

takeoff = PhaseStructureBuilder(definition, "takeoff", mission_name, self.variable_prefix)
takeoff = PhaseStructureBuilder(definition, "takeoff", mission_name, self._variable_prefix)
takeoff_structure = self._structure_builders[mission_name].process_builder(takeoff)
self._structure_builders[mission_name].structure[PARTS_TAG].insert(1, takeoff_structure)
32 changes: 23 additions & 9 deletions src/fastoad/models/performances/mission/openmdao/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,24 @@ def initialize(self):

@property
def name_provider(self) -> Enum:
"""Enum class that provide mission variable names."""
"""Enum class that provides mission variable names."""
return self._name_provider

@property
def variable_prefix(self) -> str:
"""The prefix of variable names dedicated to the mission ."""
return self._mission_wrapper.variable_prefix

@property
def mission_name(self) -> str:
"""The name of considered mission."""
return self._mission_wrapper.mission_name

@property
def first_route_name(self) -> str:
"""The name of first route (and normally the main one) in the mission."""
return self._mission_wrapper.get_route_names()[0]

@staticmethod
def get_mission_definition(
mission_file_path: Optional[Union[str, MissionDefinition]]
Expand Down Expand Up @@ -191,19 +206,17 @@ def _update_mission_wrapper(self, name, value):
mission_name=mission_name,
)

self._mission_wrapper.variable_prefix = variable_prefix
self._name_provider = self._get_variable_name_provider()

def _get_variable_name_provider(self) -> Optional[type]:
"""Factory that returns an enum class that provide mission variable names."""

try:
mission_name = self._mission_wrapper.mission_name
self._mission_wrapper.variable_prefix = variable_prefix
self._name_provider = self._get_variable_name_provider()
except FastMissionFileMissingMissionNameError:
return

def _get_variable_name_provider(self) -> Optional[type]:
"""Factory that returns an enum class that provide mission variable names."""

def get_variable_name(suffix):
return f"{self._mission_wrapper.variable_prefix}:{mission_name}:{suffix}"
return f"{self.variable_prefix}:{self.mission_name}:{suffix}"

class VariableNames(Enum):
"""Enum with mission-related variable names."""
Expand All @@ -215,5 +228,6 @@ class VariableNames(Enum):
CONSUMED_FUEL_BEFORE_INPUT_WEIGHT = get_variable_name(
"consumed_fuel_before_input_weight"
)
SPECIFIC_BURNED_FUEL = get_variable_name("specific_burned_fuel")

return VariableNames
95 changes: 79 additions & 16 deletions src/fastoad/models/performances/mission/openmdao/mission.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
FAST-OAD model for mission computation.
"""
# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
# Copyright (C) 2022 ONERA & ISAE-SUPAERO
# Copyright (C) 2023 ONERA & ISAE-SUPAERO
# FAST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
Expand All @@ -15,7 +15,9 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import logging
from enum import EnumMeta

import numpy as np
import openmdao.api as om
import pandas as pd

Expand Down Expand Up @@ -135,6 +137,17 @@ def setup(self):
promotes=["*"],
)

self.add_subsystem(
"specific_burned_fuel",
SpecificBurnedFuelComputation(
name_provider=self.name_provider,
mission_name=self.mission_name,
first_route_name=self.first_route_name,
payload_variable=self._get_payload_variable(),
),
promotes=["*"],
)

@property
def flight_points(self) -> pd.DataFrame:
"""Dataframe that lists all computed flight point data."""
Expand All @@ -145,19 +158,14 @@ def _get_zfw_component(self) -> om.AddSubtractComp:
:return: component that computes Zero Fuel Weight from OWE and mission payload
"""
mission_name = self._mission_wrapper.mission_name

if self.options["is_sizing"]:
payload_var = "data:weight:aircraft:payload"
else:
payload_var = self.name_provider.PAYLOAD.value
payload_var = self._get_payload_variable()

zfw_computation = om.AddSubtractComp()
zfw_computation.add_equation(
self.name_provider.ZFW.value,
[self.options["OWE_variable"], payload_var],
units="kg",
desc=f'Zero Fuel Weight for mission "{mission_name}"',
desc=f'Zero Fuel Weight for mission "{self.mission_name}"',
)
return zfw_computation

Expand All @@ -166,9 +174,9 @@ def _get_input_weight_component(self):
:return: component that computes input weight
"""
mission_name = self._mission_wrapper.mission_name

input_weight_variable = self._mission_wrapper.get_input_weight_variable_name(mission_name)
input_weight_variable = self._mission_wrapper.get_input_weight_variable_name(
self.mission_name
)
if not input_weight_variable:
return None

Expand All @@ -182,7 +190,7 @@ def _get_input_weight_component(self):
],
units="kg",
scaling_factors=[1, 1, -1],
desc=f'Loaded fuel at beginning for mission "{mission_name}"',
desc=f'Loaded fuel at beginning for mission "{self.mission_name}"',
)

return computation
Expand All @@ -192,9 +200,9 @@ def _get_block_fuel_component(self) -> om.AddSubtractComp:
:return: component that computes block fuel from ramp weight and ZFW
"""
mission_name = self._mission_wrapper.mission_name

input_weight_variable = self._mission_wrapper.get_input_weight_variable_name(mission_name)
input_weight_variable = self._mission_wrapper.get_input_weight_variable_name(
self.mission_name
)

block_fuel_computation = om.AddSubtractComp()
block_fuel_computation.add_equation(
Expand All @@ -206,7 +214,62 @@ def _get_block_fuel_component(self) -> om.AddSubtractComp:
],
units="kg",
scaling_factors=[1, 1, -1],
desc=f'Loaded fuel at beginning for mission "{mission_name}"',
desc=f'Loaded fuel at beginning for mission "{self.mission_name}"',
)

return block_fuel_computation

def _get_payload_variable(self):
if self.options["is_sizing"]:
return "data:weight:aircraft:payload"

return self.name_provider.PAYLOAD.value


class SpecificBurnedFuelComputation(
om.ExplicitComponent,
):
"""Computation of specific burned fuel (mission fuel / payload / mission range)."""

def initialize(self):
self.options.declare("name_provider", types=EnumMeta)
self.options.declare("mission_name", types=str)
self.options.declare("first_route_name", types=str)
self.options.declare("payload_variable", types=str)

@property
def range_variable(self):
"""Name of range variable."""
return (
"data:mission:"
f"{self.options['mission_name']}:{self.options['first_route_name']}:"
"distance"
)

@property
def burned_fuel_variable(self):
"""Name of burned fuel variable."""
return self.options["name_provider"].NEEDED_BLOCK_FUEL.value

@property
def specific_burned_fuel_variable(self):
"""Name of specific burned fuel variable (mission fuel / payload / mission range)."""
return self.options["name_provider"].SPECIFIC_BURNED_FUEL.value

@property
def payload_variable(self):
"""Name of payload variable."""
return self.options["payload_variable"]

def setup(self):
self.add_input(self.payload_variable, units="kg")
self.add_input(self.burned_fuel_variable, val=np.nan, units="kg")
self.add_input(self.range_variable, val=np.nan, units="NM")
self.add_output(self.specific_burned_fuel_variable, units="NM**-1")

def compute(self, inputs, outputs, discrete_inputs=None, discrete_outputs=None):
payload = inputs[self.payload_variable]
burned_fuel = inputs[self.burned_fuel_variable]
mission_range = inputs[self.range_variable]

outputs[self.specific_burned_fuel_variable] = burned_fuel / payload / mission_range
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
# Copyright (C) 2022 ONERA & ISAE-SUPAERO
# Copyright (C) 2023 ONERA & ISAE-SUPAERO
# FAST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
Expand Down Expand Up @@ -56,10 +56,9 @@ def setup(self):
self._engine_wrapper.setup(self)

self._mission_wrapper.setup(self)
mission_name = self._mission_wrapper.mission_name

self._input_weight_variable_name = self._mission_wrapper.get_input_weight_variable_name(
mission_name
self.mission_name
)

try:
Expand All @@ -71,12 +70,12 @@ def setup(self):
self.add_output(
self.name_provider.NEEDED_BLOCK_FUEL.value,
units="kg",
desc=f'Needed fuel to complete mission "{mission_name}", including reserve fuel',
desc=f'Needed fuel to complete mission "{self.mission_name}", including reserve fuel',
)
self.add_output(
self.name_provider.CONSUMED_FUEL_BEFORE_INPUT_WEIGHT.value,
units="kg",
desc=f'consumed fuel quantity before target mass defined for "{mission_name}",'
desc=f'consumed fuel quantity before target mass defined for "{self.mission_name}",'
f" if any (e.g. TakeOff Weight)",
)

Expand Down

0 comments on commit ddc07f6

Please sign in to comment.