Source code for noads.core.models.fleet.aircraft_operation

# Copyright 2025 ISAE-SUPAERO, https://www.isae-supaero.fr/en/
# Copyright 2025 IRT Saint Exupéry, https://www.irt-saintexupery.com
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# version 3 as published by the Free Software Foundation.
#
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

"""Operate an existing aircraft."""

from __future__ import annotations

from typing import TYPE_CHECKING

from jax.numpy import exp
from jax.numpy import log
from jax.numpy import where
from jax.numpy import zeros

from noads.core.model import JAXModel

if TYPE_CHECKING:
    from collections.abc import Mapping

    from noads.core.models.energy.energy import EnergyCarrier


[docs] class PropulsionSystem: # noqa: B903 """A propulsion system, mapping an architecture to the carriers it embarks. Associates a power-system type (e.g. ``turbofan``, ``lh2_fuel_cell``, ``electric_propulsion``) with the energy carriers it consumes and their shares of the embarked energy, linking each aircraft to the energy-mix models. """ name: str """Name of the propulsion architecture.""" energy_carrier_mix: Mapping[EnergyCarrier:float] """Energy carriers and relative part in the mission energy.""" def __init__(self, name: str, energy_carrier_mix: Mapping[EnergyCarrier:float]): """Initialize PropulsionSystem from name and EnergyCarrier's share.""" self.name = name self.energy_carrier_mix = energy_carrier_mix
[docs] class AircraftOperation: """An aircraft operated with a fixed, exogenous energy consumption. Used for the current fleet of each market, whose energy consumption per ASK is initialized from the 2019 AeroSCOPE statistics (quartile depending on the technology scenario) instead of being designed. Provides the operation models computing covered supply and energy consumption per embarked carrier. """ name: str """Aircraft name.""" propulsion: PropulsionSystem """Aircraft propulsion system.""" energy_per_ask: float """Aircraft energy consumption.""" lifetime: float """Aircraft lifetime.""" recent: bool """Boolean to indicate if aircraft is recent, or a new design.""" def __init__( self, name: str, propulsion: PropulsionSystem, energy_per_ask: float = 0.87, lifetime: float = 25.0, recent: bool = False, ): """Initialize AircraftOperation.""" self.name = name self.propulsion = propulsion self.energy_per_ask = energy_per_ask self.lifetime = lifetime self.recent = recent @property def models(self): """List of models.""" if self.recent: return [self.consumption_model()] return [self.share_model(), self.consumption_model()] def _sigmoid_fleet_share(self, years_since_introduction, max_share, cut_below=2): growth_rate = log(100 / cut_below - 1) / (self.lifetime / 2) year_inflection = 0 if self.recent else self.lifetime / 2 calc_share = ( 1e2 * max_share / (1 + exp(-growth_rate * (years_since_introduction - year_inflection))) ) calc_share_max = 100 / ( 1 + exp(-growth_rate * (years_since_introduction - year_inflection)) ) return 1e-2 * where( calc_share_max < cut_below, zeros(calc_share.shape), calc_share ) def _fleet_share(self, input_data): year = input_data["year"] entry_into_service = input_data[f"{self.name}.entry_into_service"] max_share = input_data[f"{self.name}.max_share"] return { f"{self.name}.share": self._sigmoid_fleet_share( year - entry_into_service, max_share ) }
[docs] def share_model(self): """Aircraft market share based on sigmoid functions.""" default_inputs = { "year": 2025.0, f"{self.name}.entry_into_service": 2035.0, f"{self.name}.max_share": 0.0, } output_names = [f"{self.name}.share"] return JAXModel( function=self._fleet_share, input_names=list(default_inputs.keys()), output_names=output_names, default_inputs=default_inputs, name=f"{self.name} ASK share", )
def _energy_consumption(self, input_data): ask = input_data[f"{self.name}.ask"] return { f"{self.name}.{carrier.name}.consumption": carrier_ratio * ask * self.energy_per_ask for carrier, carrier_ratio in self.propulsion.energy_carrier_mix.items() }
[docs] def consumption_model(self): """Energy carrier consumption model.""" default_inputs = {f"{self.name}.ask": 0.0} output_names = [ f"{self.name}.{carrier.name}.consumption" for carrier in self.propulsion.energy_carrier_mix ] return JAXModel( function=self._energy_consumption, input_names=list(default_inputs.keys()), output_names=output_names, default_inputs=default_inputs, name=f"{self.name} consumption", )