Source code for fastoad.models.performances.mission.mission_definition.schema

"""
Schema for mission definition files.
"""
#  This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design
#  Copyright (C) 2022 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
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.

import json
from collections import OrderedDict
from os import PathLike
from typing import Union

from ensure import Ensure
from jsonschema import validate
from ruamel.yaml import YAML

from fastoad._utils.resource_management.contents import PackageReader

from . import resources

JSON_SCHEMA_NAME = "mission_schema.json"

# Tags
SEGMENT_TAG = "segment"
ROUTE_TAG = "route"
PHASE_TAG = "phase"
RESERVE_TAG = "reserve"
PARTS_TAG = "parts"
CLIMB_PARTS_TAG = "climb_parts"
CRUISE_PART_TAG = "cruise_part"
DESCENT_PARTS_TAG = "descent_parts"
MISSION_DEFINITION_TAG = "missions"
ROUTE_DEFINITIONS_TAG = "routes"
PHASE_DEFINITIONS_TAG = "phases"
POLAR_TAG = "polar"


[docs]class MissionDefinition(OrderedDict): def __init__(self, file_path: Union[str, PathLike] = None): """ Class for reading a mission definition from a YAML file. Path of YAML file should be provided at instantiation, or in :meth:`load`. :param file_path: path of YAML file to read. """ super().__init__() if file_path: self.load(file_path)
[docs] def load(self, file_path: Union[str, PathLike]): """ Loads a mission definition from provided file path. Any existing definition will be overwritten. :param file_path: path of YAML file to read. """ self.clear() yaml = YAML(typ="safe", pure=True) with open(file_path) as yaml_file: data = yaml.load(yaml_file) with PackageReader(resources).open_text(JSON_SCHEMA_NAME) as json_file: json_schema = json.loads(json_file.read()) validate(data, json_schema) self._validate(data) self._convert_none_values(data) self.update(data) for mission_name in self[MISSION_DEFINITION_TAG]: if self[MISSION_DEFINITION_TAG][mission_name].get("use_all_block_fuel"): self.force_all_block_fuel_usage(mission_name)
[docs] def force_all_block_fuel_usage(self, mission_name): """Sets target fuel consumption to variable "~:block_fuel".""" mission_definitions = self[MISSION_DEFINITION_TAG] if mission_name in mission_definitions: mission_definitions[mission_name]["target_fuel_consumption"] = { "value": "~:block_fuel", "unit": "kg", }
@classmethod def _validate(cls, content: dict): """ Does a second pass validation of file content. Errors are raised if file content is incorrect. :param content: """ phase_names = set(content[PHASE_DEFINITIONS_TAG].keys()) if ROUTE_DEFINITIONS_TAG in content: for route_definition in content[ROUTE_DEFINITIONS_TAG].values(): for part in list(route_definition[CLIMB_PARTS_TAG]) + list( route_definition[DESCENT_PARTS_TAG] ): Ensure(part[PHASE_TAG]).is_in(phase_names) for mission_definition in content[MISSION_DEFINITION_TAG].values(): reserve_count = 0 for part in mission_definition[PARTS_TAG]: part_type, value = tuple(*part.items()) if part_type == PHASE_TAG: Ensure(value).is_in(content[PHASE_DEFINITIONS_TAG].keys()) elif part_type == ROUTE_TAG: Ensure(value).is_in(content[ROUTE_DEFINITIONS_TAG].keys()) elif part_type == RESERVE_TAG: reserve_count += 1 Ensure(value["ref"]).is_in(content[ROUTE_DEFINITIONS_TAG].keys()) Ensure(reserve_count).is_less_than_or_equal_to(1) if reserve_count == 1: # reserve definition should be the last part Ensure(part_type).equals(RESERVE_TAG) @classmethod def _convert_none_values(cls, struct: Union[dict, list]): """ Recursively transforms any None value in struct to "~" """ if isinstance(struct, dict): for key, value in struct.items(): if value is None: struct[key] = "~" else: cls._convert_none_values(value) elif isinstance(struct, list): for item in struct: cls._convert_none_values(item)