Skip to content

aeromaps.models.air_transport.aircraft_fleet_and_operations.fleet.fleet_model

fleet_model

Module for modeling aircraft fleet composition and renewal over time.

This module provides data structures and models for representing aircraft fleets, including individual aircraft, subcategories (e.g., narrow-body, wide-body), and categories (e.g., short-range, medium-range, long-range). It supports fleet evolution modeling using S-shaped logistic functions for aircraft market share transitions, and computes energy consumption, emissions (NOx, soot), and operating costs based on fleet composition.

The module uses YAML configuration files to define aircraft inventories and fleet structures, allowing flexible customization of fleet scenarios.

AircraftParameters dataclass

AircraftParameters(entry_into_service_year=None, consumption_evolution=None, energy_per_ask=None, continuous_improvement_factor_energy=None, share=None, nox_evolution=None, emission_index_nox=None, soot_evolution=None, emission_index_soot=None, doc_non_energy_evolution=None, doc_non_energy_base=None, cruise_altitude=None, hybridization_factor=0.0, ask_year=None, nrc_cost=None, rc_cost=None, oew=None, full_name=None)

Parameters defining an aircraft's characteristics and performance.

Performance metrics (energy per ASK, NOx, soot, non-energy DOC) can be declared in one of two modes per metric, independently:

  • Relative: *_evolution field, expressed as a percentage delta vs the subcategory's recent reference aircraft.
  • Absolute: same field name as on the reference-aircraft card (energy_per_ask, emission_index_nox, emission_index_soot, doc_non_energy_base), expressed in absolute units.

For each of the four metrics, exactly one of the relative or absolute field must be set. Both-set or neither-set raises ValueError at YAML load time (see :func:Fleet._load_aircraft_inventory).

Attributes:

Name Type Description
entry_into_service_year Optional[float]

Year when the aircraft enters service [yr].

consumption_evolution Optional[float]

Relative change in energy consumption compared to reference aircraft [%].

energy_per_ask Optional[float]

Absolute energy consumption per ASK [MJ/ASK]. Alternative to consumption_evolution.

continuous_improvement_factor_energy Optional[object]

Optional per-year multiplicative factor applied to the resolved energy_per_ask (base × factor(year)). Declared as an !AeroMapsCustomDataType (years/values); absent → no improvement (factor 1.0). Models a generation whose energy intensity improves over time on top of its base performance.

share Optional[object]

Optional per-year fleet share series (!AeroMapsCustomDataType, [%]). When set, the fleet runs in share-decoupling mode: the S-curve assignment is bypassed and this series populates the aircraft's aircraft_share column directly. See FleetAssignmentMixin._compute_decoupled_aircraft_share.

nox_evolution Optional[float]

Relative change in NOx emissions compared to reference aircraft [%].

emission_index_nox Optional[float]

Absolute NOx emission index per ASK [kg/ASK]. Alternative to nox_evolution.

soot_evolution Optional[float]

Relative change in soot emissions compared to reference aircraft [%].

emission_index_soot Optional[float]

Absolute soot emission index per ASK [kg/ASK]. Alternative to soot_evolution.

doc_non_energy_evolution Optional[float]

Relative change in non-energy direct operating costs compared to reference aircraft [%].

doc_non_energy_base Optional[float]

Absolute non-energy DOC per ASK [€/ASK]. Alternative to doc_non_energy_evolution.

cruise_altitude Optional[float]

Typical cruise altitude of the aircraft [m].

hybridization_factor float

Degree of hybridization for hybrid-electric aircraft, from 0 (conventional) to 1 (fully electric) [-].

ask_year Optional[float]

Average number of Available Seat Kilometers produced per aircraft per year [ASK/yr].

nrc_cost Optional[float]

Non-recurring costs (development costs) [€].

rc_cost Optional[float]

Recurring costs (manufacturing cost per unit) [€].

oew Optional[float]

Operational Empty Weight of the aircraft [t].

full_name Optional[str]

Full qualified name including category and subcategory path.

ReferenceAircraftParameters dataclass

ReferenceAircraftParameters(energy_per_ask=None, emission_index_nox=None, emission_index_soot=None, doc_non_energy_base=None, entry_into_service_year=None, cruise_altitude=None, hybridization_factor=0.0, ask_year=None, nrc_cost=None, rc_cost=None, oew=None, full_name=None, share=None, continuous_improvement_factor_energy=None)

Parameters defining a reference aircraft used as baseline for comparisons.

Reference aircraft serve as the baseline against which new aircraft performance improvements are measured. Each subcategory has an "old" and a "recent" reference.

Attributes:

Name Type Description
energy_per_ask Optional[float]

Energy consumption per Available Seat Kilometer [MJ/ASK].

emission_index_nox Optional[float]

NOx emission index per ASK [kg/ASK].

emission_index_soot Optional[float]

Soot emission index per ASK [kg/ASK].

doc_non_energy_base Optional[float]

Base non-energy direct operating cost per ASK [€/ASK].

entry_into_service_year Optional[float]

Year when the reference aircraft entered service [yr].

cruise_altitude Optional[float]

Typical cruise altitude of the aircraft [m].

hybridization_factor float

Degree of hybridization, from 0 (conventional) to 1 (fully electric) [-].

ask_year Optional[float]

Average number of Available Seat Kilometers produced per aircraft per year [ASK/yr].

nrc_cost Optional[float]

Non-recurring costs (development costs) [€].

rc_cost Optional[float]

Recurring costs (manufacturing cost per unit) [€].

oew Optional[float]

Operational Empty Weight of the aircraft [t].

full_name Optional[str]

Full qualified name including category and subcategory path.

SubcategoryParameters dataclass

SubcategoryParameters(share=None)

Parameters for an aircraft subcategory.

Attributes:

Name Type Description
share Optional[float]

Market share of this subcategory within its parent category [%].

CategoryParameters dataclass

CategoryParameters(life, limit=2)

Parameters for an aircraft category.

Attributes:

Name Type Description
life float

Average operational lifetime of aircraft in this category [yr].

limit float

Lower threshold for market share below which aircraft share is set to zero [%] (needed for S-curve parametrization).

Aircraft

Aircraft(name=None, parameters=None, energy_type='DROP_IN_FUEL')

Bases: object

Represents an individual aircraft type in the fleet.

An aircraft belongs to a subcategory and has parameters that define its performance relative to a reference aircraft.

Parameters:

Name Type Description Default
name str

Name identifier for the aircraft type.

None
parameters AircraftParameters

Aircraft performance and cost parameters.

None
energy_type

Type of energy used: 'DROP_IN_FUEL', 'HYDROGEN', 'ELECTRIC', or 'HYBRID_ELECTRIC'.

'DROP_IN_FUEL'

Attributes:

Name Type Description
name str

Name identifier for the aircraft type.

parameters AircraftParameters

Aircraft performance and cost parameters.

energy_type str

Type of energy used by the aircraft.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
285
286
287
288
289
290
291
292
293
294
295
def __init__(
    self,
    name: str = None,
    parameters: AircraftParameters = None,
    energy_type="DROP_IN_FUEL",
):
    self.name = name
    if parameters is None:
        parameters = AircraftParameters()
    self.parameters = parameters
    self.energy_type = energy_type

from_dataframe_row

from_dataframe_row(row)

Populate aircraft attributes from a DataFrame row.

Parameters:

Name Type Description Default
row

DataFrame row containing aircraft data with columns matching AIRCRAFT_COLUMNS.

required

Returns:

Type Description
Aircraft

Self, with attributes populated from the row data.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
def from_dataframe_row(self, row):
    """Populate aircraft attributes from a DataFrame row.

    Parameters
    ----------
    row
        DataFrame row containing aircraft data with columns matching AIRCRAFT_COLUMNS.

    Returns
    -------
    Aircraft
        Self, with attributes populated from the row data.
    """
    self.name = row[AIRCRAFT_COLUMNS[0]]
    self.parameters.entry_into_service_year = row[AIRCRAFT_COLUMNS[1]]
    self.parameters.consumption_evolution = row[AIRCRAFT_COLUMNS[2]]
    self.parameters.nox_evolution = row[AIRCRAFT_COLUMNS[3]]
    self.parameters.soot_evolution = row[AIRCRAFT_COLUMNS[4]]
    self.parameters.doc_non_energy_evolution = row[AIRCRAFT_COLUMNS[5]]
    self.parameters.cruise_altitude = row[AIRCRAFT_COLUMNS[6]]
    self.energy_type = row[AIRCRAFT_COLUMNS[7]]
    self.parameters.hybridization_factor = row[AIRCRAFT_COLUMNS[8]]
    self.parameters.ask_year = row[AIRCRAFT_COLUMNS[9]]
    self.parameters.rc_cost = row[AIRCRAFT_COLUMNS[10]]
    self.parameters.nrc_cost = row[AIRCRAFT_COLUMNS[11]]
    self.parameters.oew = row[AIRCRAFT_COLUMNS[12]]

    return self

resolved

resolved(metric, recent_ref)

Return the absolute value of a performance metric for this aircraft.

metric is one of the absolute field names from :data:_PERF_PAIRS (energy_per_ask, emission_index_nox, emission_index_soot, doc_non_energy_base). When the aircraft card sets that field directly, its value is returned; otherwise the paired relative-evolution field is applied to recent_ref's value for the same metric. :func:_validate_perf_mode guarantees exactly one branch fires.

For an aircraft serving multiple markets, relative mode yields different absolute values per market (each has its own recent reference); absolute mode yields the user-provided value as-is in every market.

Relative mode anchors to the reference's static baseline (recent_ref.energy_per_ask as written on the card), not its continuous-improvement-adjusted "moving" value. Any per-year continuous improvement factor is applied separately and per aircraft on top of this resolved value (see FleetPerformanceModel._compute_energy_consumption_and_share_wrt_energy_type and _compute_aircraft_performance_contributions). Consequences:

  • A relatively-defined aircraft does not track the reference's own improvement over time — only its own continuous_improvement_factor_energy moves it after t0.
  • To hold a constant relative gap to an improving reference, give the aircraft the reference's improvement trajectory as its own CIF.
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def resolved(self, metric: str, recent_ref: "ReferenceAircraftParameters") -> float:
    """Return the absolute value of a performance metric for this aircraft.

    ``metric`` is one of the absolute field names from :data:`_PERF_PAIRS`
    (``energy_per_ask``, ``emission_index_nox``, ``emission_index_soot``,
    ``doc_non_energy_base``). When the aircraft card sets that field
    directly, its value is returned; otherwise the paired relative-evolution
    field is applied to ``recent_ref``'s value for the same metric.
    :func:`_validate_perf_mode` guarantees exactly one branch fires.

    For an aircraft serving multiple markets, relative mode yields different
    absolute values per market (each has its own recent reference); absolute
    mode yields the user-provided value as-is in every market.

    Relative mode anchors to the reference's **static** baseline
    (``recent_ref.energy_per_ask`` as written on the card), *not* its
    continuous-improvement-adjusted "moving" value. Any per-year continuous
    improvement factor is applied **separately and per aircraft** on top of
    this resolved value (see
    ``FleetPerformanceModel._compute_energy_consumption_and_share_wrt_energy_type``
    and ``_compute_aircraft_performance_contributions``). Consequences:

    * A relatively-defined aircraft does **not** track the reference's own
      improvement over time — only its own
      ``continuous_improvement_factor_energy`` moves it after t0.
    * To hold a constant *relative* gap to an improving reference, give the
      aircraft the reference's improvement trajectory as its own CIF.
    """
    abs_value = getattr(self.parameters, metric)
    if abs_value is not None:
        return float(abs_value)
    evolution = getattr(self.parameters, _RELATIVE_BY_ABSOLUTE[metric])
    return float(getattr(recent_ref, metric)) * (1 + float(evolution) / 100)

SubCategory

SubCategory(name=None, parameters=None)

Bases: object

Represents a subcategory of aircraft within a category.

Subcategories group similar aircraft types (e.g., conventional narrow-body, hydrogen narrow-body) within a category. Each subcategory has reference aircraft (old and recent) that serve as baselines for performance comparisons.

Parameters:

Name Type Description Default
name Optional[str]

Name identifier for the subcategory.

None
parameters Optional[SubcategoryParameters]

Subcategory parameters including market share.

None

Attributes:

Name Type Description
name str

Name identifier for the subcategory.

parameters SubcategoryParameters

Subcategory parameters including market share.

aircraft Dict[int, Aircraft]

Dictionary of aircraft belonging to this subcategory.

old_reference_aircraft ReferenceAircraftParameters

Parameters for the older reference aircraft baseline.

recent_reference_aircraft ReferenceAircraftParameters

Parameters for the more recent reference aircraft baseline.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
389
390
391
392
393
394
395
396
397
398
def __init__(
    self,
    name: Optional[str] = None,
    parameters: Optional[SubcategoryParameters] = None,
):
    self.name = name
    self.parameters = parameters or SubcategoryParameters()
    self.aircraft: Dict[int, Aircraft] = {}
    self.old_reference_aircraft = ReferenceAircraftParameters()
    self.recent_reference_aircraft = ReferenceAircraftParameters()

add_aircraft

add_aircraft(aircraft)

Add an aircraft to this subcategory.

Parameters:

Name Type Description Default
aircraft Aircraft

Aircraft instance to add to the subcategory.

required
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
400
401
402
403
404
405
406
407
408
def add_aircraft(self, aircraft: Aircraft) -> None:
    """Add an aircraft to this subcategory.

    Parameters
    ----------
    aircraft
        Aircraft instance to add to the subcategory.
    """
    self.aircraft[len(self.aircraft)] = aircraft

remove_aircraft

remove_aircraft(aircraft_name)

Remove an aircraft from this subcategory by name.

Parameters:

Name Type Description Default
aircraft_name str

Name of the aircraft to remove.

required
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
410
411
412
413
414
415
416
417
418
419
420
421
422
423
def remove_aircraft(self, aircraft_name: str) -> None:
    """Remove an aircraft from this subcategory by name.

    Parameters
    ----------
    aircraft_name
        Name of the aircraft to remove.
    """
    self.aircraft = {
        i: aircraft
        for i, aircraft in enumerate(
            [a for a in self.aircraft.values() if a.name != aircraft_name]
        )
    }

compute

compute()

Execute compute method on all aircraft in the subcategory.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
425
426
427
428
429
430
def compute(self) -> None:
    """Execute compute method on all aircraft in the subcategory."""
    for aircraft in self.aircraft.values():
        compute_method = getattr(aircraft, "compute", None)
        if callable(compute_method):
            compute_method()

Category

Category(name, parameters, market_id)

Bases: object

Represents a category of aircraft in the fleet (e.g., Short Range, Medium Range).

Categories group subcategories of aircraft that operate in similar market segments. Each category has parameters defining aircraft lifetime and market share thresholds.

Parameters:

Name Type Description Default
name str

Human-readable display name for the category (e.g., 'Short Range', 'Medium Range', 'Long Range'), populated from :class:~aeromaps.models.air_transport.markets.market.Market.name when a :class:MarketManager is available, otherwise from the market_id.

required
parameters CategoryParameters

Category parameters including aircraft lifetime.

required
market_id str

Market identifier that references this category's entry in markets.yaml (e.g., short_range).

required

Attributes:

Name Type Description
name str

Human-readable display name for the category.

market_id str

Market identifier string (e.g., "short_range").

parameters CategoryParameters

Category parameters including aircraft lifetime.

subcategories Dict[int, SubCategory]

Dictionary of subcategories within this category.

total_shares float

Sum of all subcategory market shares (should equal 100%).

calibration_subcategory_id str or None

ID of the subcategory used for reference aircraft calibration.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
468
469
470
471
472
473
474
def __init__(self, name: str, parameters: CategoryParameters, market_id: str):
    self.name = name
    self.market_id = market_id
    self.parameters = parameters
    self.subcategories: Dict[int, SubCategory] = {}
    self.total_shares = 0.0
    self.calibration_subcategory_id: Optional[str] = None

add_subcategory

add_subcategory(subcategory)

Add a subcategory to this category.

Parameters:

Name Type Description Default
subcategory SubCategory

SubCategory instance to add.

required
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
482
483
484
485
486
487
488
489
490
def add_subcategory(self, subcategory: SubCategory) -> None:
    """Add a subcategory to this category.

    Parameters
    ----------
    subcategory
        SubCategory instance to add.
    """
    self.subcategories[len(self.subcategories)] = subcategory

remove_subcategory

remove_subcategory(subcategory_name)

Remove a subcategory from this category by name.

Parameters:

Name Type Description Default
subcategory_name str

Name of the subcategory to remove.

required
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def remove_subcategory(self, subcategory_name: str) -> None:
    """Remove a subcategory from this category by name.

    Parameters
    ----------
    subcategory_name
        Name of the subcategory to remove.
    """
    self.subcategories = {
        i: subcat
        for i, subcat in enumerate(
            [sub for sub in self.subcategories.values() if sub.name != subcategory_name]
        )
    }
    self._check_shares()

Fleet

Fleet(parameters=None, aircraft_inventory_path=None, fleet_config_path=None, markets=None)

Bases: object

Represents the complete aircraft fleet structure.

The Fleet class manages the hierarchical structure of aircraft categories, subcategories, and individual aircraft types. It loads configuration from YAML files and provides methods for fleet manipulation and display.

Parameters:

Name Type Description Default
parameters

External parameters used for reference aircraft calibration (e.g., energy shares).

None
aircraft_inventory_path Optional[Path]

Path to the YAML file containing the aircraft inventory definitions. Defaults to the package's default aircraft inventory.

None
fleet_config_path Optional[Path]

Path to the YAML file containing the fleet structure configuration. Defaults to the package's default fleet configuration.

None
markets

:class:~aeromaps.models.air_transport.markets.market_manager.MarketManager instance used to look up market display names and validate the market_served: field of each category entry in fleet.yaml. When None, validation is skipped and the market_id is used as the display name (used by lightweight unit tests that bypass the process-level wiring).

None

Attributes:

Name Type Description
categories Dict[str, Category]

Dictionary of aircraft categories indexed by category name.

parameters

External parameters for reference aircraft calibration.

markets

The :class:~aeromaps.models.air_transport.markets.market_manager.MarketManager passed at construction time (or None).

aircraft_inventory_path Path

Path to the aircraft inventory YAML file.

fleet_config_path Path

Path to the fleet configuration YAML file.

all_aircraft_elements dict

Flattened dictionary of all aircraft elements per category.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
def __init__(
    self,
    parameters=None,
    aircraft_inventory_path: Optional[Path] = None,
    fleet_config_path: Optional[Path] = None,
    markets=None,
):
    self._categories: Dict[str, Category] = {}
    self.parameters = parameters
    self.markets = markets
    # True when aircraft cards carry a `share` series: the S-curve assignment
    # and reference-aircraft calibration are bypassed (set in _build_fleet_from_yaml).
    self.share_decoupled = False
    # Populated during _build_fleet_from_yaml: subcategory id → display name.
    self._subcategory_name_by_id: Dict[str, str] = {}
    self.aircraft_inventory_path = (
        Path(aircraft_inventory_path)
        if aircraft_inventory_path is not None
        else DEFAULT_AIRCRAFT_INVENTORY_CONFIG_FILE
    )
    self.fleet_config_path = (
        Path(fleet_config_path) if fleet_config_path is not None else DEFAULT_FLEET_CONFIG_FILE
    )

    self._build_default_fleet()
    self.all_aircraft_elements = self.get_all_aircraft_elements()

categories property writable

categories

Dict[str, Category]: Dictionary of aircraft categories.

compute

compute()

Execute compute on all categories in the fleet.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
592
593
594
595
def compute(self):
    """Execute compute on all categories in the fleet."""
    for cat in self.categories.values():
        cat._compute()

get_all_aircraft_elements

get_all_aircraft_elements()

Retrieve all aircraft elements organized by category.

Creates a flattened view of all aircraft in the fleet, including reference aircraft, with their full qualified names set.

Returns:

Type Description
dict

Dictionary mapping category names to lists of aircraft elements (reference aircraft parameters and Aircraft instances).

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
def get_all_aircraft_elements(self):
    """Retrieve all aircraft elements organized by category.

    Creates a flattened view of all aircraft in the fleet, including reference
    aircraft, with their full qualified names set.

    Returns
    -------
    dict
        Dictionary mapping category names to lists of aircraft elements
        (reference aircraft parameters and Aircraft instances).
    """
    all_aircraft_elements = {}

    for category in self.categories.values():
        if not category.subcategories:
            continue

        aircraft_per_category = []
        subcategory = category.subcategories[0]

        ref_old_aircraft_name = f"{category.name}:{subcategory.name}:old_reference"
        subcategory.old_reference_aircraft.full_name = ref_old_aircraft_name
        aircraft_per_category.append(subcategory.old_reference_aircraft)

        ref_recent_aircraft_name = f"{category.name}:{subcategory.name}:recent_reference"
        subcategory.recent_reference_aircraft.full_name = ref_recent_aircraft_name
        aircraft_per_category.append(subcategory.recent_reference_aircraft)

        for subcategory in category.subcategories.values():
            for aircraft in subcategory.aircraft.values():
                aircraft_name = f"{category.name}:{subcategory.name}:{aircraft.name}"
                aircraft.parameters.full_name = aircraft_name
                aircraft_per_category.append(aircraft)

        all_aircraft_elements[category.name] = aircraft_per_category

    return all_aircraft_elements

pretty_print

pretty_print(include_aircraft=True, indent=2, display=True, absolute=False, reference='recent')

Return (and optionally print) a summary of the fleet.

Parameters:

Name Type Description Default
include_aircraft bool

Whether to list individual aircraft under each subcategory.

True
indent int

Number of spaces used for indentation when printing nested entries.

2
display bool

If True, print the generated summary; otherwise only return the string.

True
absolute bool

When True, convert aircraft deltas (consumption, DOC, NOx, soot) into absolute values using the selected reference aircraft as the baseline.

False
reference str

Which reference aircraft to use for absolute conversions; accepts "recent" (default) or "old". If the requested reference is missing, the method falls back to the other available reference.

'recent'
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
def pretty_print(
    self,
    include_aircraft=True,
    indent=2,
    display=True,
    absolute=False,
    reference="recent",
):
    """Return (and optionally print) a summary of the fleet.

    Parameters
    ----------
    include_aircraft : bool
        Whether to list individual aircraft under each subcategory.
    indent : int
        Number of spaces used for indentation when printing nested entries.
    display : bool
        If True, print the generated summary; otherwise only return the string.
    absolute : bool
        When True, convert aircraft deltas (consumption, DOC, NOx, soot) into
        absolute values using the selected reference aircraft as the baseline.
    reference : str
        Which reference aircraft to use for absolute conversions; accepts
        "recent" (default) or "old". If the requested reference is missing,
        the method falls back to the other available reference.
    """

    reference_mode = reference.lower()
    if reference_mode not in {"recent", "old"}:
        raise ValueError("reference must be 'recent' or 'old'")

    def _format_value(label, value, suffix=""):
        if value is None:
            return None
        if isinstance(value, float) and value.is_integer():
            value = int(value)
        return f"{label}={value}{suffix}"

    def _format_absolute(label, base_value, delta_percent, unit=""):
        if base_value is None or delta_percent is None:
            return None
        absolute_value = base_value * (1 + delta_percent / 100.0)
        return f"{label}={absolute_value:.4g}{unit}"

    def _format_reference_line(label, reference_obj):
        if reference_obj is None:
            return None
        bits = list(
            filter(
                None,
                [
                    _format_value("EIS", reference_obj.entry_into_service_year, " y"),
                    _format_value("energy/ASK", reference_obj.energy_per_ask, " MJ/ASK"),
                    _format_value("DOC", reference_obj.doc_non_energy_base, " €/ASK"),
                    _format_value("NOx", reference_obj.emission_index_nox, " kg/ASK"),
                    _format_value("soot", reference_obj.emission_index_soot, " kg/ASK"),
                ],
            )
        )
        descriptor = ", ".join(bits) if bits else "no data"
        return f"{level_two}- {label} reference ({descriptor})"

    def _select_base_reference(subcategory):
        preferred = (
            subcategory.recent_reference_aircraft
            if reference_mode == "recent"
            else subcategory.old_reference_aircraft
        )
        fallback = (
            subcategory.old_reference_aircraft
            if reference_mode == "recent"
            else subcategory.recent_reference_aircraft
        )
        return preferred or fallback

    def _format_aircraft_line(aircraft, base_reference=None):
        params = aircraft.parameters or AircraftParameters()
        if absolute and base_reference is not None:
            bits = list(
                filter(
                    None,
                    [
                        aircraft.energy_type or "UNKNOWN",
                        _format_value("EIS", params.entry_into_service_year, "y"),
                        _format_absolute(
                            "energy/ASK",
                            base_reference.energy_per_ask,
                            params.consumption_evolution,
                            " MJ/ASK",
                        ),
                        _format_absolute(
                            "DOC",
                            base_reference.doc_non_energy_base,
                            params.doc_non_energy_evolution,
                            " €/ASK",
                        ),
                        _format_absolute(
                            "NOx",
                            base_reference.emission_index_nox,
                            params.nox_evolution,
                            " kg/ASK",
                        ),
                        _format_absolute(
                            "soot",
                            base_reference.emission_index_soot,
                            params.soot_evolution,
                            " kg/ASK",
                        ),
                    ],
                )
            )
        else:
            bits = list(
                filter(
                    None,
                    [
                        aircraft.energy_type or "UNKNOWN",
                        _format_value("EIS", params.entry_into_service_year, "y"),
                        _format_value("cons", params.consumption_evolution, "%"),
                        _format_value("NOx", params.nox_evolution, "%"),
                        _format_value("soot", params.soot_evolution, "%"),
                        _format_value("DOC", params.doc_non_energy_evolution, "%"),
                    ],
                )
            )
        descriptor = ", ".join(bits) if bits else "no data"
        return f"{level_two}- {aircraft.name} ({descriptor})"

    lines = []
    indent = max(indent, 0)
    level_one = " " * indent
    level_two = " " * (indent * 2)

    if not self.categories:
        lines.append("Fleet has no categories configured.")
    for category in self.categories.values():
        subcategories = list(category.subcategories.values())
        share_sum = sum(float(sub.parameters.share or 0.0) for sub in subcategories)
        meta_bits = []
        if category.parameters is not None:
            if category.parameters.life is not None:
                meta_bits.append(f"life={category.parameters.life:g}y")
            if category.parameters.limit is not None:
                meta_bits.append(f"limit={category.parameters.limit:g}")
        meta_bits.append(f"subcategories={len(subcategories)}")
        if subcategories:
            meta_bits.append(f"share_sum={share_sum:.1f}%")
        category_header = f"{category.name} ({', '.join(meta_bits)})"
        lines.append(category_header)

        for subcategory in subcategories:
            share_value = subcategory.parameters.share if subcategory.parameters else None
            share_str = f"{share_value:.1f}%" if share_value is not None else "n/a"
            aircraft_count = len(subcategory.aircraft)
            sub_line = (
                f"{level_one}- {subcategory.name} "
                f"(share={share_str}, aircraft={aircraft_count})"
            )
            lines.append(sub_line)

            # Reference aircraft details
            ref_old_line = _format_reference_line("old", subcategory.old_reference_aircraft)
            if ref_old_line:
                lines.append(ref_old_line)
            ref_recent_line = _format_reference_line(
                "recent", subcategory.recent_reference_aircraft
            )
            if ref_recent_line:
                lines.append(ref_recent_line)

            if include_aircraft and aircraft_count:
                base_reference = _select_base_reference(subcategory) if absolute else None
                for aircraft in subcategory.aircraft.values():
                    lines.append(_format_aircraft_line(aircraft, base_reference=base_reference))

    output = "\n".join(lines)
    if display:
        print(output)
    return output

FleetModel

FleetModel(name='fleet_model', fleet=None, markets=None, *args, **kwargs)

Bases: FleetAssignmentMixin, FleetPerformanceMixin, AeroMAPSModel

AeroMAPS model for computing fleet evolution and characteristics over time.

This model computes the temporal evolution of the aircraft fleet composition, including market shares for each aircraft type, energy consumption, emissions (NOx, soot), and non-energy direct operating costs. It uses S-shaped logistic functions to model the gradual introduction and retirement of aircraft types.

Parameters:

Name Type Description Default
name

Name of the model instance ('fleet_model' by default).

'fleet_model'
fleet

Fleet instance containing the fleet structure and aircraft definitions.

None
*args

Additional positional arguments passed to parent class.

()
**kwargs

Additional keyword arguments passed to parent class.

{}

Attributes:

Name Type Description
fleet Fleet

The Fleet instance used for computations.

Notes

The model computes several categories of outputs stored in self.df:

  • Single aircraft shares: Individual aircraft cumulative market penetration
  • Aircraft shares: Actual market share for each aircraft type
  • Energy consumption: Energy per ASK by subcategory and energy type
  • DOC non-energy: Non-energy direct operating costs by subcategory
  • Non-CO2 emissions: NOx and soot emission indices by subcategory
  • Category means: Weighted averages across subcategories for each category
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
1254
1255
1256
1257
def __init__(self, name="fleet_model", fleet=None, markets=None, *args, **kwargs):
    super().__init__(name, *args, **kwargs)
    self.fleet = fleet
    self.markets = markets

compute

compute()

Compute fleet evolution and all derived metrics.

Executes the complete fleet model computation pipeline:

  1. Single aircraft share computation (cumulative S-curve penetration)
  2. Aircraft share computation (differential market shares)
  3. Energy consumption and share by energy type
  4. Non-energy direct operating costs (DOC)
  5. Non-CO2 emission indices (NOx, soot)
  6. Category-level mean energy consumption
  7. Category-level mean DOC
  8. Category-level mean emission indices

Returns:

Type Description
ndarray

Dummy output array (actual results stored in self.df).

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
def compute(
    self,
):
    """Compute fleet evolution and all derived metrics.

    Executes the complete fleet model computation pipeline:

    1. Single aircraft share computation (cumulative S-curve penetration)
    2. Aircraft share computation (differential market shares)
    3. Energy consumption and share by energy type
    4. Non-energy direct operating costs (DOC)
    5. Non-CO2 emission indices (NOx, soot)
    6. Category-level mean energy consumption
    7. Category-level mean DOC
    8. Category-level mean emission indices

    Returns
    -------
    np.ndarray
        Dummy output array (actual results stored in self.df).
    """
    # Start from empty dataframe (necessary for multiple runs of the model)
    self.df = pd.DataFrame(index=self.df.index)

    # Aircraft shares: either user-driven (share-decoupling) or S-curve derived.
    if getattr(self.fleet, "share_decoupled", False):
        # Populate aircraft_share columns directly from the per-aircraft share series.
        self._compute_decoupled_aircraft_share()
    else:
        # Compute single aircraft shares (cumulative S-curve), then differential shares.
        self._compute_single_aircraft_share()
        self._compute_aircraft_share()

    # Compute energy consumption and share per subcategory with respect to energy type
    self._compute_energy_consumption_and_share_wrt_energy_type()

    # Compute non energy direct operating costs (DOC) and share per subcategory with respect to energy type
    self._compute_doc_non_energy()

    # Compute non-CO2 (NOx and soot) emission index and share per subcategory with respect to energy type
    self._compute_non_co2_emission_index()

    # Compute mean energy consumption per category with respect to energy type
    self._compute_mean_energy_consumption_per_category_wrt_energy_type()

    # Compute mean non energy direct operating cost (DOC) per category with respect to energy type
    self._compute_mean_doc_non_energy()

    # Compute mean non-CO2 emission index per category with respect to energy type
    self._compute_mean_non_co2_emission_index()

    # Compute individual aircraft contributions to all performance metrics
    self._compute_aircraft_performance_contributions()

    # Compute fleet-renewal-only counterfactual performance (no new aircraft)
    self._compute_fleet_renewal_performance()

plot

plot()

Generate fleet renewal visualization plots.

Creates a 2-row matplotlib figure with one column per category. The top row shows stacked area plots of aircraft shares over time (old reference, recent reference, and new aircraft types); the bottom row shows the evolution of mean fleet energy consumption. Data spans prospection_start_year to end_year.

Shares are read from the canonical non-cumulative *:aircraft_share columns and drawn with stackplot. Those columns are written in both the S-curve path (_compute_aircraft_share) and the share-decoupling path (_compute_decoupled_aircraft_share), so this works in either mode — including the custom workflow, where the cumulative *:single_aircraft_share columns do not exist.

Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
def plot(self):
    """Generate fleet renewal visualization plots.

    Creates a 2-row matplotlib figure with one column per category. The top
    row shows stacked area plots of aircraft shares over time (old reference,
    recent reference, and new aircraft types); the bottom row shows the
    evolution of mean fleet energy consumption. Data spans
    ``prospection_start_year`` to ``end_year``.

    Shares are read from the canonical non-cumulative ``*:aircraft_share``
    columns and drawn with ``stackplot``. Those columns are written in *both*
    the S-curve path (``_compute_aircraft_share``) and the share-decoupling
    path (``_compute_decoupled_aircraft_share``), so this works in either mode
    — including the custom workflow, where the cumulative
    ``*:single_aircraft_share`` columns do not exist.
    """
    x = np.linspace(
        self.prospection_start_year,
        self.end_year,
        self.end_year - self.prospection_start_year + 1,
    )

    categories = list(self.fleet.categories.values())

    f, axs = plt.subplots(2, len(categories), figsize=(20, 10), squeeze=False)

    for i, category in enumerate(categories):
        # Top plot: stacked non-cumulative shares (bottom -> top).
        ax = axs[0, i]
        labels = []
        series = []

        # Reference aircraft live on the first subcategory (matches the
        # _compute_* consumers).
        subcategory = category.subcategories[0]
        for ref_key, ref_label in (
            ("old_reference", "Old reference aircraft"),
            ("recent_reference", "Recent reference aircraft"),
        ):
            var_name = f"{category.name}:{subcategory.name}:{ref_key}:aircraft_share"
            if var_name in self.df:
                labels.append(f"{subcategory.name} - {ref_label}")
                series.append(
                    self.df.loc[self.prospection_start_year : self.end_year, var_name].values
                )

        # New aircraft, in subcategory then insertion order.
        for j, subcategory in category.subcategories.items():
            for aircraft in subcategory.aircraft.values():
                var_name = f"{category.name}:{subcategory.name}:{aircraft.name}:aircraft_share"
                if var_name in self.df:
                    labels.append(f"{subcategory.name} - {aircraft.name}")
                    series.append(
                        self.df.loc[
                            self.prospection_start_year : self.end_year, var_name
                        ].values
                    )

        # Stack from the top: the old reference sits on top of the stack and
        # the newest aircraft at the bottom. stackplot draws the first series
        # at the bottom, so reverse the natural (old -> new) order for
        # stacking, then restore it for the legend. Colors are assigned in the
        # natural order first (old_reference -> C0) and reversed alongside the
        # series, so every band keeps a stable color regardless of stacking
        # order.
        prop_colors = plt.rcParams["axes.prop_cycle"].by_key().get("color", [])
        if prop_colors:
            colors = [prop_colors[k % len(prop_colors)] for k in range(len(series))]
            ax.stackplot(x, np.vstack(series[::-1]), labels=labels[::-1], colors=colors[::-1])
        else:
            ax.stackplot(x, np.vstack(series[::-1]), labels=labels[::-1])

        ax.set_xlim(self.prospection_start_year, self.end_year)
        ax.set_ylim(0, 100)
        handles, leg_labels = ax.get_legend_handles_labels()
        ax.legend(handles[::-1], leg_labels[::-1], loc="upper left", prop={"size": 8})
        ax.set_xlabel("Year")
        ax.set_ylabel("Share in fleet [%]")
        ax.set_title(category.name)

        # Bottom plot: mean fleet energy consumption.
        ax = axs[1, i]
        ax.plot(
            x,
            self.df.loc[
                self.prospection_start_year : self.end_year,
                category.name + ":energy_consumption",
            ],
        )

        ax.set_xlim(self.prospection_start_year, self.end_year)
        ax.set_xlabel("Year")
        ax.set_ylabel("Fleet mean energy consumption [MJ/ASK]")

    plt.plot()

plot_performance_contributions

plot_performance_contributions(metric='energy')

Plot individual aircraft contributions to a fleet performance metric.

For each category shows:

  • Dashed grey line: recent reference value (neutral baseline).
  • Dashed orange line: fleet-renewal-only counterfactual (old aircraft phased out, replaced by recent reference — no new technology).
  • Red area above the baseline: penalty from old aircraft still in service.
  • Stacked coloured areas below the baseline: gain from each new aircraft type (stacked oldest-EIS first).
  • Black line: actual fleet mean.

Parameters:

Name Type Description Default
metric str

One of "energy", "doc", "nox", "soot".

'energy'

Returns:

Type Description
Figure
Source code in aeromaps/models/air_transport/aircraft_fleet_and_operations/fleet/fleet_model.py
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
def plot_performance_contributions(self, metric="energy"):
    """Plot individual aircraft contributions to a fleet performance metric.

    For each category shows:

    - Dashed grey line: recent reference value (neutral baseline).
    - Dashed orange line: fleet-renewal-only counterfactual (old aircraft
      phased out, replaced by recent reference — no new technology).
    - Red area above the baseline: penalty from old aircraft still in service.
    - Stacked coloured areas below the baseline: gain from each new aircraft
      type (stacked oldest-EIS first).
    - Black line: actual fleet mean.

    Parameters
    ----------
    metric : str
        One of ``"energy"``, ``"doc"``, ``"nox"``, ``"soot"``.

    Returns
    -------
    matplotlib.figure.Figure
    """
    _cfg = {
        "energy": {
            "fleet_col": lambda cat: f"{cat}:energy_consumption",
            "renewal_col": lambda cat: f"{cat}:energy_renewal_only",
            "contrib_col": lambda cat,
            sub,
            ac: f"{cat}:{sub}:{ac}:energy_efficiency_contribution",
            "old_col": lambda cat,
            sub: f"{cat}:{sub}:old_reference:energy_efficiency_contribution",
            "baseline_col": lambda cat,
            sub: f"{cat}:{sub}:recent_reference:energy_efficiency_contribution_baseline",
            "ylabel": "Energy per ASK [MJ/ASK]",
        },
        "doc": {
            "fleet_col": lambda cat: f"{cat}:doc_non_energy",
            "renewal_col": lambda cat: f"{cat}:doc_renewal_only",
            "contrib_col": lambda cat, sub, ac: f"{cat}:{sub}:{ac}:doc_contribution",
            "old_col": lambda cat, sub: f"{cat}:{sub}:old_reference:doc_contribution",
            "baseline_col": lambda cat,
            sub: f"{cat}:{sub}:recent_reference:doc_contribution_baseline",
            "ylabel": "Non-energy DOC [€/ASK]",
        },
        "nox": {
            "fleet_col": lambda cat: f"{cat}:emission_index_nox",
            "renewal_col": lambda cat: f"{cat}:nox_renewal_only",
            "contrib_col": lambda cat, sub, ac: f"{cat}:{sub}:{ac}:nox_contribution",
            "old_col": lambda cat, sub: f"{cat}:{sub}:old_reference:nox_contribution",
            "baseline_col": lambda cat,
            sub: f"{cat}:{sub}:recent_reference:nox_contribution_baseline",
            "ylabel": "NOx emission index [kg/ASK]",
        },
        "soot": {
            "fleet_col": lambda cat: f"{cat}:emission_index_soot",
            "renewal_col": lambda cat: f"{cat}:soot_renewal_only",
            "contrib_col": lambda cat, sub, ac: f"{cat}:{sub}:{ac}:soot_contribution",
            "old_col": lambda cat, sub: f"{cat}:{sub}:old_reference:soot_contribution",
            "baseline_col": lambda cat,
            sub: f"{cat}:{sub}:recent_reference:soot_contribution_baseline",
            "ylabel": "Soot emission index [kg/ASK]",
        },
    }
    if metric not in _cfg:
        raise ValueError(f"metric must be one of {list(_cfg)}; got {metric!r}")
    cfg = _cfg[metric]

    years = self.df.loc[self.prospection_start_year : self.end_year].index
    categories = list(self.fleet.categories.values())
    fig, axs = plt.subplots(1, len(categories), figsize=(8 * len(categories), 5), squeeze=False)
    cmap = plt.get_cmap("tab10")

    for col_idx, category in enumerate(categories):
        ax = axs[0, col_idx]
        first_subcategory = category.subcategories[0]
        # Recent-reference baseline, as used by the mean/decomposition. For
        # energy this is time-varying (continuous improvement factor); for the
        # other metrics it is constant. Reading the stored series keeps the plot
        # consistent with _compute_aircraft_performance_contributions.
        ref_recent_val = self.df.loc[
            years, cfg["baseline_col"](category.name, first_subcategory.name)
        ].values

        # Old-reference penalty (above baseline)
        old_penalty = -self.df.loc[
            years, cfg["old_col"](category.name, first_subcategory.name)
        ].values
        ax.fill_between(
            years,
            ref_recent_val,
            ref_recent_val + old_penalty,
            color="tomato",
            alpha=0.75,
            label="Old reference aircraft (penalty)",
        )

        # New aircraft gains (below baseline, stacked oldest → newest EIS)
        y_top = ref_recent_val.copy()
        color_idx = 0
        for subcategory in category.subcategories.values():
            for aircraft in self._sorted_aircraft(subcategory):
                gain = self.df.loc[
                    years,
                    cfg["contrib_col"](category.name, subcategory.name, aircraft.name),
                ].values
                ax.fill_between(
                    years,
                    y_top - gain,
                    y_top,
                    color=cmap(color_idx),
                    alpha=0.75,
                    label=f"{aircraft.name} (gain)",
                )
                y_top = y_top - gain
                color_idx += 1

        # Recent reference baseline (time-varying for energy, flat otherwise)
        ax.plot(
            years,
            ref_recent_val,
            color="grey",
            linestyle="--",
            linewidth=1,
            label="Recent reference baseline",
        )

        # Fleet-renewal-only counterfactual
        renewal = self.df.loc[years, cfg["renewal_col"](category.name)].values
        ax.plot(
            years,
            renewal,
            color="orange",
            linestyle="--",
            linewidth=1.5,
            label="Fleet renewal only (no new aircraft)",
        )

        # Actual fleet mean
        fleet_mean = self.df.loc[years, cfg["fleet_col"](category.name)].values
        ax.plot(years, fleet_mean, color="black", linewidth=2, label="Fleet mean")

        ax.set_xlim(self.prospection_start_year, self.end_year)
        ax.set_xlabel("Year")
        ax.set_ylabel(cfg["ylabel"])
        ax.set_title(category.name)
        ax.legend(loc="upper right", prop={"size": 8})

    fig.tight_layout()
    return fig