pycontrails 0.54.1__cp313-cp313-win_amd64.whl → 0.54.3__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pycontrails might be problematic. Click here for more details.
- pycontrails/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +24 -5
- pycontrails/core/cache.py +14 -10
- pycontrails/core/fleet.py +22 -12
- pycontrails/core/flight.py +25 -15
- pycontrails/core/met.py +34 -22
- pycontrails/core/rgi_cython.cp313-win_amd64.pyd +0 -0
- pycontrails/core/vector.py +38 -38
- pycontrails/datalib/ecmwf/arco_era5.py +10 -5
- pycontrails/datalib/ecmwf/common.py +7 -2
- pycontrails/datalib/ecmwf/era5.py +9 -4
- pycontrails/datalib/ecmwf/era5_model_level.py +9 -5
- pycontrails/datalib/ecmwf/hres.py +12 -7
- pycontrails/datalib/ecmwf/hres_model_level.py +10 -5
- pycontrails/datalib/ecmwf/ifs.py +11 -6
- pycontrails/datalib/ecmwf/variables.py +1 -0
- pycontrails/datalib/gfs/gfs.py +52 -34
- pycontrails/datalib/gfs/variables.py +6 -2
- pycontrails/datalib/landsat.py +5 -8
- pycontrails/datalib/sentinel.py +7 -11
- pycontrails/ext/bada.py +3 -2
- pycontrails/ext/synthetic_flight.py +3 -2
- pycontrails/models/accf.py +40 -19
- pycontrails/models/apcemm/apcemm.py +2 -1
- pycontrails/models/cocip/cocip.py +8 -4
- pycontrails/models/cocipgrid/cocip_grid.py +25 -20
- pycontrails/models/dry_advection.py +50 -54
- pycontrails/models/humidity_scaling/humidity_scaling.py +12 -7
- pycontrails/models/ps_model/__init__.py +2 -1
- pycontrails/models/ps_model/ps_aircraft_params.py +3 -2
- pycontrails/models/ps_model/ps_grid.py +187 -1
- pycontrails/models/ps_model/ps_model.py +12 -10
- pycontrails/models/ps_model/ps_operational_limits.py +39 -52
- pycontrails/physics/geo.py +149 -0
- pycontrails/physics/jet.py +141 -11
- pycontrails/physics/static/iata-cargo-load-factors-20241115.csv +71 -0
- pycontrails/physics/static/iata-passenger-load-factors-20241115.csv +71 -0
- {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/METADATA +12 -11
- {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/RECORD +43 -41
- {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/WHEEL +1 -1
- {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/LICENSE +0 -0
- {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/NOTICE +0 -0
- {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/top_level.txt +0 -0
|
@@ -350,49 +350,47 @@ def max_usable_lift_coefficient(
|
|
|
350
350
|
|
|
351
351
|
|
|
352
352
|
def minimum_mach_num(
|
|
353
|
-
air_pressure:
|
|
354
|
-
aircraft_mass:
|
|
353
|
+
air_pressure: ArrayOrFloat,
|
|
354
|
+
aircraft_mass: ArrayOrFloat,
|
|
355
355
|
atyp_param: PSAircraftEngineParams,
|
|
356
|
-
) ->
|
|
356
|
+
) -> ArrayOrFloat:
|
|
357
357
|
"""
|
|
358
358
|
Calculate minimum mach number to avoid stall.
|
|
359
359
|
|
|
360
360
|
Parameters
|
|
361
361
|
----------
|
|
362
|
-
air_pressure :
|
|
362
|
+
air_pressure : ArrayOrFloat
|
|
363
363
|
Ambient pressure, [:math:`Pa`]
|
|
364
|
-
aircraft_mass :
|
|
364
|
+
aircraft_mass : ArrayOrFloat
|
|
365
365
|
Aircraft mass at each waypoint, [:math:`kg`]
|
|
366
366
|
atyp_param : PSAircraftEngineParams
|
|
367
367
|
Extracted aircraft and engine parameters.
|
|
368
368
|
|
|
369
369
|
Returns
|
|
370
370
|
-------
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
ArrayOrFloat
|
|
372
|
+
Minimum mach number to avoid stall.
|
|
373
373
|
"""
|
|
374
374
|
|
|
375
375
|
def excess_mass(
|
|
376
|
-
mach_number:
|
|
377
|
-
air_pressure:
|
|
378
|
-
aircraft_mass:
|
|
376
|
+
mach_number: ArrayOrFloat,
|
|
377
|
+
air_pressure: ArrayOrFloat,
|
|
378
|
+
aircraft_mass: ArrayOrFloat,
|
|
379
379
|
mach_num_des: float,
|
|
380
380
|
c_l_do: float,
|
|
381
381
|
wing_surface_area: float,
|
|
382
|
-
) ->
|
|
382
|
+
) -> ArrayOrFloat:
|
|
383
383
|
amass_max = max_allowable_aircraft_mass(
|
|
384
384
|
air_pressure,
|
|
385
385
|
mach_number,
|
|
386
386
|
mach_num_des,
|
|
387
387
|
c_l_do,
|
|
388
388
|
wing_surface_area,
|
|
389
|
-
1e10,
|
|
389
|
+
1e10, # clipped to this value which we want to ignore
|
|
390
390
|
)
|
|
391
|
-
if amass_max < 0:
|
|
392
|
-
return np.nan
|
|
393
391
|
return amass_max - aircraft_mass
|
|
394
392
|
|
|
395
|
-
m = scipy.optimize.
|
|
393
|
+
m = scipy.optimize.newton(
|
|
396
394
|
excess_mass,
|
|
397
395
|
args=(
|
|
398
396
|
air_pressure,
|
|
@@ -401,21 +399,22 @@ def minimum_mach_num(
|
|
|
401
399
|
atyp_param.c_l_do,
|
|
402
400
|
atyp_param.wing_surface_area,
|
|
403
401
|
),
|
|
404
|
-
x0=0.
|
|
405
|
-
x1=0.
|
|
406
|
-
|
|
402
|
+
x0=np.full_like(air_pressure, 0.4),
|
|
403
|
+
x1=np.full_like(air_pressure, 0.5),
|
|
404
|
+
tol=1e-4,
|
|
405
|
+
)
|
|
407
406
|
|
|
408
407
|
return m
|
|
409
408
|
|
|
410
409
|
|
|
411
410
|
def maximum_mach_num(
|
|
412
|
-
altitude_ft:
|
|
413
|
-
air_pressure:
|
|
414
|
-
aircraft_mass:
|
|
415
|
-
air_temperature:
|
|
416
|
-
theta:
|
|
411
|
+
altitude_ft: ArrayOrFloat,
|
|
412
|
+
air_pressure: ArrayOrFloat,
|
|
413
|
+
aircraft_mass: ArrayOrFloat,
|
|
414
|
+
air_temperature: ArrayOrFloat,
|
|
415
|
+
theta: ArrayOrFloat,
|
|
417
416
|
atyp_param: PSAircraftEngineParams,
|
|
418
|
-
) ->
|
|
417
|
+
) -> ArrayOrFloat:
|
|
419
418
|
r"""
|
|
420
419
|
Return the maximum mach number at the current operating conditions.
|
|
421
420
|
|
|
@@ -424,23 +423,23 @@ def maximum_mach_num(
|
|
|
424
423
|
|
|
425
424
|
Parameters
|
|
426
425
|
----------
|
|
427
|
-
altitude_ft :
|
|
426
|
+
altitude_ft : ArrayOrFloat
|
|
428
427
|
Altitude, [:math:`ft`]
|
|
429
|
-
air_pressure :
|
|
428
|
+
air_pressure : ArrayOrFloat
|
|
430
429
|
Ambient pressure, [:math:`Pa`]
|
|
431
|
-
aircraft_mass :
|
|
430
|
+
aircraft_mass : ArrayOrFloat
|
|
432
431
|
Aircraft mass at each waypoint, [:math:`kg`]
|
|
433
|
-
air_temperature :
|
|
432
|
+
air_temperature : ArrayOrFloat
|
|
434
433
|
Array of ambient temperature, [:math: `K`]
|
|
435
|
-
theta :
|
|
434
|
+
theta : ArrayOrFloat
|
|
436
435
|
Climb (positive value) or descent (negative value) angle, [:math:`\deg`]
|
|
437
436
|
atyp_param : PSAircraftEngineParams
|
|
438
437
|
Extracted aircraft and engine parameters.
|
|
439
438
|
|
|
440
439
|
Returns
|
|
441
440
|
-------
|
|
442
|
-
|
|
443
|
-
Maximum
|
|
441
|
+
ArrayOrFloat
|
|
442
|
+
Maximum mach number given thrust limiations.
|
|
444
443
|
"""
|
|
445
444
|
# Max speed ignoring thrust limits
|
|
446
445
|
mach_num_op_lim = max_mach_number_by_altitude(
|
|
@@ -451,27 +450,15 @@ def maximum_mach_num(
|
|
|
451
450
|
atyp_param.p_inf_co,
|
|
452
451
|
)
|
|
453
452
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
# Numerically solve for the speed where drag == max thrust
|
|
464
|
-
try:
|
|
465
|
-
m_max = scipy.optimize.root_scalar(
|
|
466
|
-
get_excess_thrust_available,
|
|
467
|
-
args=(air_temperature, air_pressure, aircraft_mass, theta, atyp_param),
|
|
468
|
-
x0=mach_num_op_lim,
|
|
469
|
-
x1=mach_num_op_lim - 0.05,
|
|
470
|
-
).root
|
|
471
|
-
except ValueError:
|
|
472
|
-
return np.nan
|
|
473
|
-
|
|
474
|
-
return m_max
|
|
453
|
+
max_mach = scipy.optimize.newton(
|
|
454
|
+
func=get_excess_thrust_available,
|
|
455
|
+
args=(air_temperature, air_pressure, aircraft_mass, theta, atyp_param),
|
|
456
|
+
x0=mach_num_op_lim,
|
|
457
|
+
x1=mach_num_op_lim - 0.01,
|
|
458
|
+
tol=1e-4,
|
|
459
|
+
).clip(max=mach_num_op_lim)
|
|
460
|
+
|
|
461
|
+
return max_mach
|
|
475
462
|
|
|
476
463
|
|
|
477
464
|
# ----------------
|
pycontrails/physics/geo.py
CHANGED
|
@@ -855,6 +855,155 @@ def advect_level(
|
|
|
855
855
|
return (level * 100.0 + (dt_s * dp_dt)) / 100.0
|
|
856
856
|
|
|
857
857
|
|
|
858
|
+
def advect_longitude_and_latitude_near_poles(
|
|
859
|
+
longitude: npt.NDArray[np.floating],
|
|
860
|
+
latitude: npt.NDArray[np.floating],
|
|
861
|
+
u_wind: npt.NDArray[np.floating],
|
|
862
|
+
v_wind: npt.NDArray[np.floating],
|
|
863
|
+
dt: npt.NDArray[np.timedelta64] | np.timedelta64,
|
|
864
|
+
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
|
|
865
|
+
r"""Advect a particle near the poles.
|
|
866
|
+
|
|
867
|
+
This function calculates the longitude and latitude of a particle after time ``dt``
|
|
868
|
+
caused by advection due to wind near the poles (above 80 degrees North and South).
|
|
869
|
+
|
|
870
|
+
Automatically wrap over the antimeridian if necessary.
|
|
871
|
+
|
|
872
|
+
Parameters
|
|
873
|
+
----------
|
|
874
|
+
longitude : npt.NDArray[np.floating]
|
|
875
|
+
Original longitude, [:math:`\deg`]
|
|
876
|
+
latitude : npt.NDArray[np.floating]
|
|
877
|
+
Original latitude, [:math:`\deg`]
|
|
878
|
+
u_wind : npt.NDArray[np.floating]
|
|
879
|
+
Wind speed in the longitudinal direction, [:math:`m s^{-1}`]
|
|
880
|
+
v_wind : npt.NDArray[np.floating]
|
|
881
|
+
Wind speed in the latitudinal direction, [:math:`m s^{-1}`]
|
|
882
|
+
dt : npt.NDArray[np.timedelta64] | np.timedelta64
|
|
883
|
+
Advection timestep
|
|
884
|
+
|
|
885
|
+
Returns
|
|
886
|
+
-------
|
|
887
|
+
tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]
|
|
888
|
+
New longitude and latitude values, [:math:`\deg`]
|
|
889
|
+
|
|
890
|
+
Notes
|
|
891
|
+
-----
|
|
892
|
+
Near the poles, the longitude and latitude is converted to a 2-D Cartesian-like coordinate
|
|
893
|
+
system to avoid numerical instabilities and singularities caused by convergence of meridians.
|
|
894
|
+
|
|
895
|
+
See Also
|
|
896
|
+
--------
|
|
897
|
+
advect_longitude
|
|
898
|
+
advect_latitude
|
|
899
|
+
advect_horizontal
|
|
900
|
+
"""
|
|
901
|
+
# Determine hemisphere sign (1 for Northern Hemisphere, -1 for Southern Hemisphere)
|
|
902
|
+
hemisphere_sign = np.where(latitude > 0.0, 1.0, -1.0)
|
|
903
|
+
|
|
904
|
+
# Convert longitude and latitude to radians
|
|
905
|
+
sin_lon_rad = np.sin(units.degrees_to_radians(longitude))
|
|
906
|
+
cos_lon_rad = np.cos(units.degrees_to_radians(longitude))
|
|
907
|
+
|
|
908
|
+
# Convert longitude and latitude to 2-D Cartesian-like coordinate system, [:math:`\deg`]
|
|
909
|
+
polar_radius = 90.0 - np.abs(latitude)
|
|
910
|
+
x_cartesian = sin_lon_rad * polar_radius
|
|
911
|
+
y_cartesian = -cos_lon_rad * polar_radius * hemisphere_sign
|
|
912
|
+
|
|
913
|
+
# Convert winds from eastward and northward direction (u, v) to (X, Y), [:math:`\deg s^{-1}`]
|
|
914
|
+
x_wind = units.radians_to_degrees(
|
|
915
|
+
(u_wind * cos_lon_rad - v_wind * sin_lon_rad * hemisphere_sign) / constants.radius_earth
|
|
916
|
+
)
|
|
917
|
+
y_wind = units.radians_to_degrees(
|
|
918
|
+
(u_wind * sin_lon_rad * hemisphere_sign + v_wind * cos_lon_rad) / constants.radius_earth
|
|
919
|
+
)
|
|
920
|
+
|
|
921
|
+
# Advect contrails in 2-D Cartesian-like plane, [:math:`\deg`]
|
|
922
|
+
dtype = np.result_type(latitude, v_wind)
|
|
923
|
+
dt_s = units.dt_to_seconds(dt, dtype)
|
|
924
|
+
x_cartesian_new = x_cartesian + dt_s * x_wind
|
|
925
|
+
y_cartesian_new = y_cartesian + dt_s * y_wind
|
|
926
|
+
|
|
927
|
+
# Convert `y_cartesian_new` back to `latitude`, [:math:`\deg`]
|
|
928
|
+
dist_squared = x_cartesian_new**2 + y_cartesian_new**2
|
|
929
|
+
new_latitude = (90.0 - np.sqrt(dist_squared)) * hemisphere_sign
|
|
930
|
+
|
|
931
|
+
# Convert `x_cartesian_new` back to `longitude`, [:math:`\deg`]
|
|
932
|
+
new_lon_rad = np.arctan2(y_cartesian_new, x_cartesian_new)
|
|
933
|
+
|
|
934
|
+
new_longitude = np.where(
|
|
935
|
+
(x_wind == 0.0) & (y_wind == 0.0),
|
|
936
|
+
longitude,
|
|
937
|
+
90.0 + units.radians_to_degrees(new_lon_rad) * hemisphere_sign,
|
|
938
|
+
)
|
|
939
|
+
# new_longitude = 90.0 + units.radians_to_degrees(new_lon_rad) * hemisphere_sign
|
|
940
|
+
new_longitude = (new_longitude + 180.0) % 360.0 - 180.0 # wrap antimeridian
|
|
941
|
+
return new_longitude, new_latitude
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
def advect_horizontal(
|
|
945
|
+
longitude: npt.NDArray[np.floating],
|
|
946
|
+
latitude: npt.NDArray[np.floating],
|
|
947
|
+
u_wind: npt.NDArray[np.floating],
|
|
948
|
+
v_wind: npt.NDArray[np.floating],
|
|
949
|
+
dt: npt.NDArray[np.timedelta64] | np.timedelta64,
|
|
950
|
+
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
|
|
951
|
+
r"""Advect a particle in the horizontal plane.
|
|
952
|
+
|
|
953
|
+
This function calls :func:`advect_longitude` and :func:`advect_latitude` when
|
|
954
|
+
the position is far from the poles (<= 80.0 degrees). When the position is near
|
|
955
|
+
the poles (> 80.0 degrees), :func:`advect_longitude_and_latitude_near_poles`
|
|
956
|
+
is used instead.
|
|
957
|
+
|
|
958
|
+
Parameters
|
|
959
|
+
----------
|
|
960
|
+
longitude : npt.NDArray[np.floating]
|
|
961
|
+
Original longitude, [:math:`\deg`]
|
|
962
|
+
latitude : npt.NDArray[np.floating]
|
|
963
|
+
Original latitude, [:math:`\deg`]
|
|
964
|
+
u_wind : npt.NDArray[np.floating]
|
|
965
|
+
Wind speed in the longitudinal direction, [:math:`m s^{-1}`]
|
|
966
|
+
v_wind : npt.NDArray[np.floating]
|
|
967
|
+
Wind speed in the latitudinal direction, [:math:`m s^{-1}`]
|
|
968
|
+
dt : npt.NDArray[np.timedelta64] | np.timedelta64
|
|
969
|
+
Advection timestep
|
|
970
|
+
|
|
971
|
+
Returns
|
|
972
|
+
-------
|
|
973
|
+
tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]
|
|
974
|
+
New longitude and latitude values, [:math:`\deg`]
|
|
975
|
+
"""
|
|
976
|
+
near_poles = np.abs(latitude) > 80.0
|
|
977
|
+
|
|
978
|
+
longitude_out = np.empty_like(longitude)
|
|
979
|
+
latitude_out = np.empty_like(latitude)
|
|
980
|
+
|
|
981
|
+
# Use simple spherical advection if position is far from the poles (<= 80.0 degrees)
|
|
982
|
+
cond = ~near_poles
|
|
983
|
+
lon_cond = longitude[cond]
|
|
984
|
+
lat_cond = latitude[cond]
|
|
985
|
+
u_wind_cond = u_wind[cond]
|
|
986
|
+
v_wind_cond = v_wind[cond]
|
|
987
|
+
dt_cond = dt if isinstance(dt, np.timedelta64) else dt[cond]
|
|
988
|
+
longitude_out[cond] = advect_longitude(lon_cond, lat_cond, u_wind_cond, dt_cond)
|
|
989
|
+
latitude_out[cond] = advect_latitude(lat_cond, v_wind_cond, dt_cond)
|
|
990
|
+
|
|
991
|
+
# And use Cartesian-like advection if position is near the poles (> 80.0 degrees)
|
|
992
|
+
cond = near_poles
|
|
993
|
+
lon_cond = longitude[cond]
|
|
994
|
+
lat_cond = latitude[cond]
|
|
995
|
+
u_wind_cond = u_wind[cond]
|
|
996
|
+
v_wind_cond = v_wind[cond]
|
|
997
|
+
dt_cond = dt if isinstance(dt, np.timedelta64) else dt[cond]
|
|
998
|
+
lon_out_cond, lat_out_cond = advect_longitude_and_latitude_near_poles(
|
|
999
|
+
lon_cond, lat_cond, u_wind_cond, v_wind_cond, dt_cond
|
|
1000
|
+
)
|
|
1001
|
+
longitude_out[cond] = lon_out_cond
|
|
1002
|
+
latitude_out[cond] = lat_out_cond
|
|
1003
|
+
|
|
1004
|
+
return longitude_out, latitude_out
|
|
1005
|
+
|
|
1006
|
+
|
|
858
1007
|
# ---------------
|
|
859
1008
|
# Grid properties
|
|
860
1009
|
# ---------------
|
pycontrails/physics/jet.py
CHANGED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
"""Jet aircraft trajectory and performance parameters.
|
|
2
2
|
|
|
3
3
|
This module includes common functions to calculate jet aircraft trajectory
|
|
4
|
-
and performance parameters, including fuel quantities, mass, thrust setting
|
|
5
|
-
and
|
|
4
|
+
and performance parameters, including fuel quantities, mass, thrust setting,
|
|
5
|
+
propulsion efficiency and load factors.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
|
+
import functools
|
|
10
11
|
import logging
|
|
12
|
+
import pathlib
|
|
11
13
|
|
|
12
14
|
import numpy as np
|
|
13
15
|
import numpy.typing as npt
|
|
16
|
+
import pandas as pd
|
|
14
17
|
|
|
15
18
|
from pycontrails.core import flight
|
|
16
19
|
from pycontrails.physics import constants, units
|
|
17
20
|
from pycontrails.utils.types import ArrayOrFloat, ArrayScalarLike
|
|
18
21
|
|
|
19
22
|
logger = logging.getLogger(__name__)
|
|
23
|
+
_path_to_static = pathlib.Path(__file__).parent / "static"
|
|
24
|
+
PLF_PATH = _path_to_static / "iata-passenger-load-factors-20241115.csv"
|
|
25
|
+
CLF_PATH = _path_to_static / "iata-cargo-load-factors-20241115.csv"
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
# -------------------
|
|
@@ -43,7 +49,7 @@ def acceleration(
|
|
|
43
49
|
|
|
44
50
|
See Also
|
|
45
51
|
--------
|
|
46
|
-
|
|
52
|
+
pycontrails.Flight.segment_duration
|
|
47
53
|
"""
|
|
48
54
|
dv_dt = np.empty_like(true_airspeed)
|
|
49
55
|
dv_dt[:-1] = np.diff(true_airspeed) / segment_duration[:-1]
|
|
@@ -71,8 +77,8 @@ def climb_descent_angle(
|
|
|
71
77
|
|
|
72
78
|
See Also
|
|
73
79
|
--------
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
pycontrails.Flight.segment_rocd
|
|
81
|
+
pycontrails.Flight.segment_true_airspeed
|
|
76
82
|
"""
|
|
77
83
|
rocd_ms = units.ft_to_m(rocd) / 60.0
|
|
78
84
|
sin_theta = rocd_ms / true_airspeed
|
|
@@ -319,8 +325,8 @@ def reserve_fuel_requirements(
|
|
|
319
325
|
|
|
320
326
|
See Also
|
|
321
327
|
--------
|
|
322
|
-
|
|
323
|
-
|
|
328
|
+
pycontrails.Flight.segment_phase
|
|
329
|
+
fuel_burn
|
|
324
330
|
"""
|
|
325
331
|
segment_phase = flight.segment_phase(rocd, altitude_ft)
|
|
326
332
|
|
|
@@ -345,6 +351,128 @@ def reserve_fuel_requirements(
|
|
|
345
351
|
# -------------
|
|
346
352
|
|
|
347
353
|
|
|
354
|
+
@functools.cache
|
|
355
|
+
def _historical_regional_load_factor(path: pathlib.Path) -> pd.DataFrame:
|
|
356
|
+
"""Load the historical regional load factor database.
|
|
357
|
+
|
|
358
|
+
Daily load factors are estimated from linearly interpolating the monthly statistics.
|
|
359
|
+
|
|
360
|
+
Returns
|
|
361
|
+
-------
|
|
362
|
+
pd.DataFrame
|
|
363
|
+
Historical regional load factor for each day.
|
|
364
|
+
|
|
365
|
+
Notes
|
|
366
|
+
-----
|
|
367
|
+
The monthly **passenger load factor** for each region is compiled from IATA's monthly
|
|
368
|
+
publication of the Air Passenger Market Analysis, where the static file will be continuously
|
|
369
|
+
updated. The report estimates the regional passenger load factor by dividing the revenue
|
|
370
|
+
passenger-km (RPK) by the available seat-km (ASK).
|
|
371
|
+
|
|
372
|
+
The monthly **cargo load factor** for each region is compiled from IATA's monthly publication
|
|
373
|
+
of the Air Cargo Market Analysis, where the static file will be continuously updated.
|
|
374
|
+
The report estimates the regional cargo load factor by dividing the freight tonne-km (FTK)
|
|
375
|
+
by the available freight tonne-km (AFTK).
|
|
376
|
+
"""
|
|
377
|
+
df = pd.read_csv(path, index_col="Date", parse_dates=True, date_format="%d/%m/%Y")
|
|
378
|
+
return df.resample("D").interpolate()
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
AIRPORT_TO_REGION = {
|
|
382
|
+
"A": "Asia Pacific",
|
|
383
|
+
"B": "Europe",
|
|
384
|
+
"C": "North America",
|
|
385
|
+
"D": "Africa",
|
|
386
|
+
"E": "Europe",
|
|
387
|
+
"F": "Africa",
|
|
388
|
+
"G": "Africa",
|
|
389
|
+
"H": "Africa",
|
|
390
|
+
"K": "North America",
|
|
391
|
+
"L": "Europe",
|
|
392
|
+
"M": "Latin America",
|
|
393
|
+
"N": "Asia Pacific",
|
|
394
|
+
"O": "Middle East",
|
|
395
|
+
"P": "Asia Pacific",
|
|
396
|
+
"R": "Asia Pacific",
|
|
397
|
+
"S": "Latin America",
|
|
398
|
+
"T": "Latin America",
|
|
399
|
+
"U": "Asia Pacific",
|
|
400
|
+
"V": "Asia Pacific",
|
|
401
|
+
"W": "Asia Pacific",
|
|
402
|
+
"Y": "Asia Pacific",
|
|
403
|
+
"Z": "Asia Pacific",
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def aircraft_load_factor(
|
|
408
|
+
origin_airport_icao: str | None = None,
|
|
409
|
+
first_waypoint_time: pd.Timestamp | None = None,
|
|
410
|
+
*,
|
|
411
|
+
freighter: bool = False,
|
|
412
|
+
) -> float:
|
|
413
|
+
"""
|
|
414
|
+
Estimate passenger/cargo load factor based on historical data.
|
|
415
|
+
|
|
416
|
+
Accounts for regional and seasonal differences.
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
origin_airport_icao : str | None
|
|
421
|
+
ICAO code of origin airport. If None is provided, then globally averaged values will be
|
|
422
|
+
assumed at `first_waypoint_time`.
|
|
423
|
+
first_waypoint_time : pd.Timestamp | None
|
|
424
|
+
First waypoint UTC time. If None is provided, then regionally or globally averaged values
|
|
425
|
+
from the trailing twelve months will be used.
|
|
426
|
+
freighter: bool
|
|
427
|
+
Historical cargo load factor will be used if true, otherwise use passenger load factor.
|
|
428
|
+
|
|
429
|
+
Returns
|
|
430
|
+
-------
|
|
431
|
+
float
|
|
432
|
+
Passenger/cargo load factor [0 - 1], unitless
|
|
433
|
+
"""
|
|
434
|
+
# If origin airport is provided, use regional load factor
|
|
435
|
+
if origin_airport_icao is not None:
|
|
436
|
+
first_letter = origin_airport_icao[0]
|
|
437
|
+
region = AIRPORT_TO_REGION.get(first_letter, "Global")
|
|
438
|
+
else:
|
|
439
|
+
region = "Global"
|
|
440
|
+
|
|
441
|
+
# Use passenger or cargo database
|
|
442
|
+
if freighter:
|
|
443
|
+
lf_database = _historical_regional_load_factor(CLF_PATH)
|
|
444
|
+
else:
|
|
445
|
+
lf_database = _historical_regional_load_factor(PLF_PATH)
|
|
446
|
+
|
|
447
|
+
# If `first_waypoint_time` is None, global/regional averages for the trailing twelve months
|
|
448
|
+
# will be assumed.
|
|
449
|
+
if first_waypoint_time is None:
|
|
450
|
+
t1 = lf_database.index[-1]
|
|
451
|
+
t0 = t1 - pd.DateOffset(months=12) + pd.DateOffset(days=1)
|
|
452
|
+
return lf_database.loc[t0:t1, region].mean().item()
|
|
453
|
+
|
|
454
|
+
date = first_waypoint_time.floor("D")
|
|
455
|
+
|
|
456
|
+
# If `date` is more recent than the historical data, then use most recent load factors
|
|
457
|
+
# from trailing twelve months as seasonal values are stable except in COVID years (2020-22).
|
|
458
|
+
if date > lf_database.index[-1]:
|
|
459
|
+
if date.month == 2 and date.day == 29: # remove any leap day
|
|
460
|
+
date = date.replace(day=28)
|
|
461
|
+
|
|
462
|
+
filt = (lf_database.index.month == date.month) & (lf_database.index.day == date.day)
|
|
463
|
+
date = lf_database.index[filt][-1]
|
|
464
|
+
|
|
465
|
+
# (2) If `date` is before the historical data, then use 2019 load factors.
|
|
466
|
+
elif date < lf_database.index[0]:
|
|
467
|
+
if date.month == 2 and date.day == 29: # remove any leap day
|
|
468
|
+
date = date.replace(day=28)
|
|
469
|
+
|
|
470
|
+
filt = (lf_database.index.month == date.month) & (lf_database.index.day == date.day)
|
|
471
|
+
date = lf_database.index[filt][0]
|
|
472
|
+
|
|
473
|
+
return lf_database.at[date, region].item()
|
|
474
|
+
|
|
475
|
+
|
|
348
476
|
def aircraft_weight(aircraft_mass: ArrayOrFloat) -> ArrayOrFloat:
|
|
349
477
|
"""Calculate the aircraft weight at each waypoint.
|
|
350
478
|
|
|
@@ -413,7 +541,8 @@ def initial_aircraft_mass(
|
|
|
413
541
|
|
|
414
542
|
See Also
|
|
415
543
|
--------
|
|
416
|
-
|
|
544
|
+
reserve_fuel_requirements
|
|
545
|
+
aircraft_load_factor
|
|
417
546
|
"""
|
|
418
547
|
tom = operating_empty_weight + load_factor * max_payload + total_fuel_burn + total_reserve_fuel
|
|
419
548
|
return min(tom, max_takeoff_weight)
|
|
@@ -463,9 +592,10 @@ def update_aircraft_mass(
|
|
|
463
592
|
|
|
464
593
|
See Also
|
|
465
594
|
--------
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
595
|
+
fuel_burn
|
|
596
|
+
reserve_fuel_requirements
|
|
597
|
+
initial_aircraft_mass
|
|
598
|
+
aircraft_load_factor
|
|
469
599
|
"""
|
|
470
600
|
if takeoff_mass is None:
|
|
471
601
|
takeoff_mass = initial_aircraft_mass(
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Date,Global,Africa,Asia Pacific,Europe,Latin America,Middle East,North America
|
|
2
|
+
15/12/2018,0.488,0.381,0.54,0.567,0.291,0.488,0.414
|
|
3
|
+
15/1/2019,0.451,0.354,0.501,0.501,0.299,0.421,0.4
|
|
4
|
+
15/2/2019,0.447,0.363,0.473,0.53,0.297,0.466,0.379
|
|
5
|
+
15/3/2019,0.495,0.384,0.556,0.56,0.323,0.488,0.416
|
|
6
|
+
15/4/2019,0.463,0.374,0.518,0.496,0.325,0.458,0.405
|
|
7
|
+
15/5/2019,0.468,0.386,0.52,0.513,0.353,0.469,0.398
|
|
8
|
+
15/6/2019,0.454,0.324,0.522,0.498,0.337,0.44,0.382
|
|
9
|
+
15/7/2019,0.45,0.323,0.519,0.485,0.354,0.453,0.373
|
|
10
|
+
15/8/2019,0.446,0.302,0.516,0.477,0.372,0.435,0.377
|
|
11
|
+
15/9/2019,0.464,0.329,0.539,0.501,0.379,0.459,0.381
|
|
12
|
+
15/10/2019,0.477,0.361,0.539,0.533,0.364,0.477,0.394
|
|
13
|
+
15/11/2019,0.496,0.404,0.538,0.569,0.403,0.497,0.413
|
|
14
|
+
15/12/2019,0.467,0.368,0.519,0.53,0.3,0.47,0.395
|
|
15
|
+
15/1/2020,0.45,0.356,0.474,0.501,0.311,0.426,0.424
|
|
16
|
+
15/2/2020,0.464,0.368,0.543,0.531,0.342,0.461,0.372
|
|
17
|
+
15/3/2020,0.545,0.425,0.656,0.63,0.411,0.532,0.429
|
|
18
|
+
15/4/2020,0.58,0.486,0.691,0.648,0.554,0.525,0.487
|
|
19
|
+
15/5/2020,0.576,0.612,0.643,0.625,0.561,0.483,0.526
|
|
20
|
+
15/6/2020,0.573,0.547,0.645,0.62,0.512,0.494,0.521
|
|
21
|
+
15/7/2020,0.564,0.489,0.639,0.594,0.464,0.53,0.506
|
|
22
|
+
15/8/2020,0.548,0.502,0.616,0.568,0.478,0.535,0.489
|
|
23
|
+
15/9/2020,0.569,0.507,0.642,0.62,0.456,0.579,0.484
|
|
24
|
+
15/10/2020,0.576,0.502,0.617,0.651,0.443,0.606,0.496
|
|
25
|
+
15/11/2020,0.582,0.496,0.631,0.655,0.436,0.6,0.5
|
|
26
|
+
15/12/2020,0.573,0.51,0.639,0.653,0.367,0.597,0.482
|
|
27
|
+
15/1/2021,0.589,0.48,0.665,0.627,0.39,0.569,0.532
|
|
28
|
+
15/2/2021,0.575,0.476,0.692,0.641,0.429,0.598,0.453
|
|
29
|
+
15/3/2021,0.588,0.499,0.661,0.685,0.453,0.613,0.472
|
|
30
|
+
15/4/2021,0.578,0.504,0.633,0.681,0.457,0.598,0.473
|
|
31
|
+
15/5/2021,0.572,0.502,0.646,0.656,0.423,0.589,0.469
|
|
32
|
+
15/6/2021,0.565,0.48,0.676,0.626,0.381,0.581,0.458
|
|
33
|
+
15/7/2021,0.544,0.455,0.654,0.598,0.387,0.536,0.443
|
|
34
|
+
15/8/2021,0.542,0.43,0.698,0.575,0.404,0.529,0.437
|
|
35
|
+
15/9/2021,0.553,0.428,0.68,0.604,0.37,0.558,0.447
|
|
36
|
+
15/10/2021,0.561,0.45,0.661,0.626,0.421,0.572,0.449
|
|
37
|
+
15/11/2021,0.559,0.434,0.654,0.631,0.446,0.572,0.444
|
|
38
|
+
15/12/2021,0.542,0.502,0.634,0.623,0.413,0.556,0.43
|
|
39
|
+
15/1/2022,0.541,0.492,0.609,0.584,0.417,0.513,0.474
|
|
40
|
+
15/2/2022,0.532,0.502,0.592,0.636,0.476,0.529,0.429
|
|
41
|
+
15/3/2022,0.549,0.494,0.638,0.671,0.448,0.526,0.442
|
|
42
|
+
15/4/2022,0.516,0.49,0.631,0.578,0.419,0.504,0.419
|
|
43
|
+
15/5/2022,0.505,0.495,0.627,0.548,0.387,0.487,0.411
|
|
44
|
+
15/6/2022,0.492,0.447,0.608,0.507,0.383,0.488,0.404
|
|
45
|
+
15/7/2022,0.472,0.452,0.563,0.493,0.374,0.469,0.398
|
|
46
|
+
15/8/2022,0.467,0.418,0.547,0.502,0.374,0.466,0.393
|
|
47
|
+
15/9/2022,0.481,0.451,0.572,0.528,0.381,0.478,0.396
|
|
48
|
+
15/10/2022,0.487,0.437,0.561,0.558,0.384,0.48,0.401
|
|
49
|
+
15/11/2022,0.491,0.458,0.545,0.569,0.382,0.475,0.419
|
|
50
|
+
15/12/2022,0.472,0.432,0.528,0.559,0.322,0.454,0.406
|
|
51
|
+
15/1/2023,0.448,0.439,0.452,0.541,0.325,0.411,0.423
|
|
52
|
+
15/2/2023,0.456,0.468,0.464,0.574,0.361,0.445,0.4
|
|
53
|
+
15/3/2023,0.462,0.489,0.485,0.57,0.366,0.456,0.393
|
|
54
|
+
15/4/2023,0.427,0.482,0.442,0.497,0.364,0.431,0.373
|
|
55
|
+
15/5/2023,0.415,0.448,0.422,0.489,0.333,0.41,0.373
|
|
56
|
+
15/6/2023,0.432,0.446,0.468,0.476,0.337,0.446,0.374
|
|
57
|
+
15/7/2023,0.421,0.417,0.457,0.472,0.322,0.411,0.37
|
|
58
|
+
15/8/2023,0.42,0.388,0.443,0.484,0.326,0.407,0.377
|
|
59
|
+
15/9/2023,0.438,0.436,0.466,0.5,0.319,0.424,0.392
|
|
60
|
+
15/10/2023,0.452,0.416,0.472,0.53,0.354,0.46,0.392
|
|
61
|
+
15/11/2023,0.467,0.421,0.479,0.57,0.363,0.469,0.408
|
|
62
|
+
15/12/2023,0.459,0.41,0.479,0.562,0.316,0.455,0.403
|
|
63
|
+
15/1/2024,0.457,0.431,0.446,0.555,0.344,0.439,0.435
|
|
64
|
+
15/2/2024,0.451,0.451,0.432,0.584,0.376,0.463,0.396
|
|
65
|
+
15/3/2024,0.473,0.473,0.475,0.581,0.402,0.496,0.404
|
|
66
|
+
15/4/2024,0.439,0.429,0.445,0.515,0.387,0.447,0.387
|
|
67
|
+
15/5/2024,0.446,0.438,0.453,0.518,0.362,0.461,0.397
|
|
68
|
+
15/6/2024,0.458,0.385,0.496,0.507,0.336,0.473,0.388
|
|
69
|
+
15/7/2024,0.444,0.4,0.48,0.496,0.338,0.458,0.382
|
|
70
|
+
15/8/2024,0.44,0.378,0.466,0.501,0.359,0.445,0.387
|
|
71
|
+
15/9/2024,0.456,0.392,0.485,0.525,0.368,0.474,0.389
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Date,Global,Africa,Asia Pacific,Europe,Latin America,Middle East,North America
|
|
2
|
+
15/12/2018,0.804,0.724,0.81,0.81,0.818,0.736,0.825
|
|
3
|
+
15/1/2019,0.796,0.709,0.81,0.796,0.825,0.76,0.795
|
|
4
|
+
15/2/2019,0.806,0.704,0.826,0.815,0.813,0.726,0.808
|
|
5
|
+
15/3/2019,0.817,0.72,0.812,0.837,0.815,0.739,0.85
|
|
6
|
+
15/4/2019,0.828,0.733,0.817,0.851,0.822,0.803,0.839
|
|
7
|
+
15/5/2019,0.815,0.676,0.802,0.837,0.832,0.732,0.851
|
|
8
|
+
15/6/2019,0.844,0.706,0.821,0.875,0.832,0.767,0.887
|
|
9
|
+
15/7/2019,0.857,0.735,0.831,0.89,0.853,0.812,0.888
|
|
10
|
+
15/8/2019,0.857,0.755,0.839,0.889,0.833,0.821,0.875
|
|
11
|
+
15/9/2019,0.819,0.721,0.801,0.866,0.819,0.75,0.828
|
|
12
|
+
15/10/2019,0.82,0.697,0.815,0.855,0.819,0.734,0.841
|
|
13
|
+
15/11/2019,0.818,0.708,0.813,0.833,0.822,0.732,0.828
|
|
14
|
+
15/12/2019,0.823,0.724,0.816,0.828,0.825,0.78,0.859
|
|
15
|
+
15/1/2020,0.803,0.702,0.799,0.816,0.826,0.785,0.812
|
|
16
|
+
15/2/2020,0.759,0.668,0.678,0.813,0.812,0.725,0.811
|
|
17
|
+
15/3/2020,0.606,0.609,0.589,0.67,0.681,0.599,0.557
|
|
18
|
+
15/4/2020,0.366,0.111,0.538,0.32,0.55,0.284,0.15
|
|
19
|
+
15/5/2020,0.507,0.071,0.62,0.427,0.623,0.255,0.381
|
|
20
|
+
15/6/2020,0.576,0.162,0.638,0.555,0.666,0.357,0.524
|
|
21
|
+
15/7/2020,0.579,0.296,0.657,0.609,0.631,0.396,0.476
|
|
22
|
+
15/8/2020,0.585,0.39,0.65,0.635,0.639,0.372,0.477
|
|
23
|
+
15/9/2020,0.601,0.378,0.692,0.586,0.706,0.365,0.525
|
|
24
|
+
15/10/2020,0.602,0.482,0.687,0.552,0.721,0.387,0.558
|
|
25
|
+
15/11/2020,0.58,0.474,0.664,0.523,0.74,0.372,0.518
|
|
26
|
+
15/12/2020,0.575,0.549,0.616,0.578,0.73,0.44,0.516
|
|
27
|
+
15/1/2021,0.541,0.544,0.566,0.576,0.685,0.422,0.484
|
|
28
|
+
15/2/2021,0.554,0.516,0.591,0.563,0.683,0.398,0.527
|
|
29
|
+
15/3/2021,0.623,0.53,0.669,0.593,0.708,0.422,0.624
|
|
30
|
+
15/4/2021,0.633,0.476,0.678,0.563,0.723,0.404,0.668
|
|
31
|
+
15/5/2021,0.658,0.53,0.678,0.593,0.768,0.389,0.728
|
|
32
|
+
15/6/2021,0.696,0.587,0.657,0.658,0.784,0.459,0.806
|
|
33
|
+
15/7/2021,0.731,0.614,0.675,0.725,0.793,0.513,0.841
|
|
34
|
+
15/8/2021,0.7,0.64,0.545,0.746,0.774,0.56,0.786
|
|
35
|
+
15/9/2021,0.676,0.56,0.605,0.719,0.773,0.524,0.727
|
|
36
|
+
15/10/2021,0.706,0.558,0.629,0.741,0.809,0.577,0.769
|
|
37
|
+
15/11/2021,0.713,0.616,0.597,0.752,0.822,0.616,0.786
|
|
38
|
+
15/12/2021,0.723,0.647,0.625,0.745,0.816,0.663,0.793
|
|
39
|
+
15/1/2022,0.645,0.623,0.576,0.682,0.782,0.591,0.663
|
|
40
|
+
15/2/2022,0.698,0.648,0.629,0.721,0.795,0.648,0.745
|
|
41
|
+
15/3/2022,0.747,0.657,0.642,0.739,0.808,0.718,0.839
|
|
42
|
+
15/4/2022,0.778,0.68,0.67,0.795,0.809,0.713,0.858
|
|
43
|
+
15/5/2022,0.794,0.696,0.696,0.807,0.807,0.762,0.86
|
|
44
|
+
15/6/2022,0.824,0.743,0.729,0.86,0.817,0.772,0.891
|
|
45
|
+
15/7/2022,0.835,0.753,0.764,0.87,0.831,0.812,0.882
|
|
46
|
+
15/8/2022,0.818,0.757,0.74,0.862,0.824,0.796,0.856
|
|
47
|
+
15/9/2022,0.816,0.743,0.747,0.847,0.823,0.795,0.855
|
|
48
|
+
15/10/2022,0.82,0.726,0.755,0.848,0.833,0.791,0.864
|
|
49
|
+
15/11/2022,0.808,0.748,0.77,0.838,0.82,0.775,0.832
|
|
50
|
+
15/12/2022,0.811,0.769,0.772,0.836,0.785,0.8,0.842
|
|
51
|
+
15/1/2023,0.777,0.742,0.774,0.762,0.813,0.791,0.784
|
|
52
|
+
15/2/2023,0.778,0.756,0.792,0.752,0.811,0.798,0.771
|
|
53
|
+
15/3/2023,0.807,0.739,0.792,0.805,0.812,0.794,0.837
|
|
54
|
+
15/4/2023,0.813,0.708,0.784,0.838,0.814,0.76,0.856
|
|
55
|
+
15/5/2023,0.818,0.699,0.773,0.848,0.811,0.799,0.863
|
|
56
|
+
15/6/2023,0.842,0.689,0.804,0.877,0.825,0.794,0.887
|
|
57
|
+
15/7/2023,0.852,0.746,0.816,0.877,0.867,0.821,0.897
|
|
58
|
+
15/8/2023,0.846,0.764,0.822,0.876,0.851,0.83,0.858
|
|
59
|
+
15/9/2023,0.826,0.731,0.8,0.86,0.839,0.816,0.83
|
|
60
|
+
15/10/2023,0.831,0.707,0.821,0.856,0.848,0.806,0.836
|
|
61
|
+
15/11/2023,0.818,0.704,0.814,0.837,0.844,0.777,0.827
|
|
62
|
+
15/12/2023,0.821,0.732,0.812,0.851,0.827,0.782,0.829
|
|
63
|
+
15/1/2024,0.799,0.731,0.808,0.782,0.85,0.799,0.799
|
|
64
|
+
15/2/2024,0.806,0.744,0.844,0.761,0.827,0.808,0.795
|
|
65
|
+
15/3/2024,0.82,0.721,0.835,0.809,0.831,0.775,0.837
|
|
66
|
+
15/4/2024,0.824,0.734,0.824,0.838,0.822,0.792,0.83
|
|
67
|
+
15/5/2024,0.834,0.729,0.818,0.852,0.834,0.808,0.858
|
|
68
|
+
15/6/2024,0.85,0.771,0.829,0.877,0.842,0.795,0.876
|
|
69
|
+
15/7/2024,0.86,0.75,0.834,0.882,0.862,0.84,0.889
|
|
70
|
+
15/8/2024,0.862,0.779,0.86,0.879,0.84,0.823,0.871
|
|
71
|
+
15/9/2024,0.836,0.765,0.831,0.865,0.834,0.814,0.824
|