pycontrails 0.54.1__cp310-cp310-win_amd64.whl → 0.54.3__cp310-cp310-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.

Files changed (43) hide show
  1. pycontrails/_version.py +2 -2
  2. pycontrails/core/aircraft_performance.py +24 -5
  3. pycontrails/core/cache.py +14 -10
  4. pycontrails/core/fleet.py +22 -12
  5. pycontrails/core/flight.py +25 -15
  6. pycontrails/core/met.py +34 -22
  7. pycontrails/core/rgi_cython.cp310-win_amd64.pyd +0 -0
  8. pycontrails/core/vector.py +38 -38
  9. pycontrails/datalib/ecmwf/arco_era5.py +10 -5
  10. pycontrails/datalib/ecmwf/common.py +7 -2
  11. pycontrails/datalib/ecmwf/era5.py +9 -4
  12. pycontrails/datalib/ecmwf/era5_model_level.py +9 -5
  13. pycontrails/datalib/ecmwf/hres.py +12 -7
  14. pycontrails/datalib/ecmwf/hres_model_level.py +10 -5
  15. pycontrails/datalib/ecmwf/ifs.py +11 -6
  16. pycontrails/datalib/ecmwf/variables.py +1 -0
  17. pycontrails/datalib/gfs/gfs.py +52 -34
  18. pycontrails/datalib/gfs/variables.py +6 -2
  19. pycontrails/datalib/landsat.py +5 -8
  20. pycontrails/datalib/sentinel.py +7 -11
  21. pycontrails/ext/bada.py +3 -2
  22. pycontrails/ext/synthetic_flight.py +3 -2
  23. pycontrails/models/accf.py +40 -19
  24. pycontrails/models/apcemm/apcemm.py +2 -1
  25. pycontrails/models/cocip/cocip.py +8 -4
  26. pycontrails/models/cocipgrid/cocip_grid.py +25 -20
  27. pycontrails/models/dry_advection.py +50 -54
  28. pycontrails/models/humidity_scaling/humidity_scaling.py +12 -7
  29. pycontrails/models/ps_model/__init__.py +2 -1
  30. pycontrails/models/ps_model/ps_aircraft_params.py +3 -2
  31. pycontrails/models/ps_model/ps_grid.py +187 -1
  32. pycontrails/models/ps_model/ps_model.py +12 -10
  33. pycontrails/models/ps_model/ps_operational_limits.py +39 -52
  34. pycontrails/physics/geo.py +149 -0
  35. pycontrails/physics/jet.py +141 -11
  36. pycontrails/physics/static/iata-cargo-load-factors-20241115.csv +71 -0
  37. pycontrails/physics/static/iata-passenger-load-factors-20241115.csv +71 -0
  38. {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/METADATA +12 -11
  39. {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/RECORD +43 -41
  40. {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/WHEEL +1 -1
  41. {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/LICENSE +0 -0
  42. {pycontrails-0.54.1.dist-info → pycontrails-0.54.3.dist-info}/NOTICE +0 -0
  43. {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: float,
354
- aircraft_mass: float,
353
+ air_pressure: ArrayOrFloat,
354
+ aircraft_mass: ArrayOrFloat,
355
355
  atyp_param: PSAircraftEngineParams,
356
- ) -> float:
356
+ ) -> ArrayOrFloat:
357
357
  """
358
358
  Calculate minimum mach number to avoid stall.
359
359
 
360
360
  Parameters
361
361
  ----------
362
- air_pressure : float
362
+ air_pressure : ArrayOrFloat
363
363
  Ambient pressure, [:math:`Pa`]
364
- aircraft_mass : float
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
- float
372
- Maximum usable lift coefficient.
371
+ ArrayOrFloat
372
+ Minimum mach number to avoid stall.
373
373
  """
374
374
 
375
375
  def excess_mass(
376
- mach_number: float,
377
- air_pressure: float,
378
- aircraft_mass: float,
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
- ) -> float:
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.root_scalar(
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.5,
405
- x1=0.6,
406
- ).root
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: float,
413
- air_pressure: float,
414
- aircraft_mass: float,
415
- air_temperature: float,
416
- theta: float,
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
- ) -> float:
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 : float
426
+ altitude_ft : ArrayOrFloat
428
427
  Altitude, [:math:`ft`]
429
- air_pressure : float
428
+ air_pressure : ArrayOrFloat
430
429
  Ambient pressure, [:math:`Pa`]
431
- aircraft_mass : float
430
+ aircraft_mass : ArrayOrFloat
432
431
  Aircraft mass at each waypoint, [:math:`kg`]
433
- air_temperature : npt.NDArray[np.float64]
432
+ air_temperature : ArrayOrFloat
434
433
  Array of ambient temperature, [:math: `K`]
435
- theta : float | npt.NDArray[np.float64]
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
- float
443
- Maximum usable lift coefficient.
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
- # If the max mach number ignoring thrust limits is possible, return that value
455
- if (
456
- get_excess_thrust_available(
457
- mach_num_op_lim, air_temperature, air_pressure, aircraft_mass, theta, atyp_param
458
- )
459
- > 0
460
- ):
461
- return mach_num_op_lim
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
  # ----------------
@@ -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
  # ---------------
@@ -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 propulsion efficiency.
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
- :func:`flight.segment_duration`
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
- :func:`flight.segment_rocd`
75
- :func:`flight.segment_true_airspeed`
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
- :func:`flight.segment_phase`
323
- :func:`fuel_burn`
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
- :func:`reserve_fuel_requirements`
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
- :func:`fuel_burn`
467
- :func:`reserve_fuel_requirements`
468
- :func:`initial_aircraft_mass`
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