pycontrails 0.54.2__cp310-cp310-macosx_11_0_arm64.whl → 0.54.4__cp310-cp310-macosx_11_0_arm64.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 (68) hide show
  1. pycontrails/__init__.py +2 -2
  2. pycontrails/_version.py +2 -2
  3. pycontrails/core/__init__.py +1 -1
  4. pycontrails/core/aircraft_performance.py +75 -61
  5. pycontrails/core/cache.py +7 -7
  6. pycontrails/core/fleet.py +25 -21
  7. pycontrails/core/flight.py +215 -301
  8. pycontrails/core/interpolation.py +56 -56
  9. pycontrails/core/met.py +48 -39
  10. pycontrails/core/models.py +25 -11
  11. pycontrails/core/polygon.py +15 -15
  12. pycontrails/core/rgi_cython.cpython-310-darwin.so +0 -0
  13. pycontrails/core/vector.py +22 -22
  14. pycontrails/datalib/_met_utils/metsource.py +8 -5
  15. pycontrails/datalib/ecmwf/__init__.py +14 -14
  16. pycontrails/datalib/ecmwf/common.py +1 -1
  17. pycontrails/datalib/ecmwf/era5.py +7 -7
  18. pycontrails/datalib/ecmwf/hres.py +3 -3
  19. pycontrails/datalib/ecmwf/ifs.py +1 -1
  20. pycontrails/datalib/ecmwf/variables.py +1 -0
  21. pycontrails/datalib/gfs/__init__.py +6 -6
  22. pycontrails/datalib/gfs/gfs.py +2 -2
  23. pycontrails/datalib/goes.py +5 -5
  24. pycontrails/datalib/landsat.py +5 -8
  25. pycontrails/datalib/sentinel.py +7 -11
  26. pycontrails/ext/bada.py +3 -2
  27. pycontrails/ext/empirical_grid.py +1 -1
  28. pycontrails/ext/synthetic_flight.py +3 -2
  29. pycontrails/models/accf.py +40 -19
  30. pycontrails/models/apcemm/apcemm.py +5 -4
  31. pycontrails/models/cocip/__init__.py +2 -2
  32. pycontrails/models/cocip/cocip.py +16 -17
  33. pycontrails/models/cocip/cocip_params.py +2 -11
  34. pycontrails/models/cocip/cocip_uncertainty.py +24 -18
  35. pycontrails/models/cocip/contrail_properties.py +331 -316
  36. pycontrails/models/cocip/output_formats.py +53 -53
  37. pycontrails/models/cocip/radiative_forcing.py +135 -131
  38. pycontrails/models/cocip/radiative_heating.py +135 -135
  39. pycontrails/models/cocip/unterstrasser_wake_vortex.py +90 -87
  40. pycontrails/models/cocip/wake_vortex.py +92 -92
  41. pycontrails/models/cocip/wind_shear.py +8 -8
  42. pycontrails/models/cocipgrid/cocip_grid.py +118 -107
  43. pycontrails/models/dry_advection.py +59 -58
  44. pycontrails/models/emissions/__init__.py +2 -2
  45. pycontrails/models/emissions/black_carbon.py +108 -108
  46. pycontrails/models/emissions/emissions.py +85 -85
  47. pycontrails/models/emissions/ffm2.py +35 -35
  48. pycontrails/models/humidity_scaling/humidity_scaling.py +23 -23
  49. pycontrails/models/ps_model/__init__.py +3 -2
  50. pycontrails/models/ps_model/ps_aircraft_params.py +11 -6
  51. pycontrails/models/ps_model/ps_grid.py +256 -60
  52. pycontrails/models/ps_model/ps_model.py +18 -21
  53. pycontrails/models/ps_model/ps_operational_limits.py +58 -69
  54. pycontrails/models/tau_cirrus.py +8 -1
  55. pycontrails/physics/geo.py +216 -67
  56. pycontrails/physics/jet.py +220 -90
  57. pycontrails/physics/static/iata-cargo-load-factors-20241115.csv +71 -0
  58. pycontrails/physics/static/iata-passenger-load-factors-20241115.csv +71 -0
  59. pycontrails/physics/units.py +14 -14
  60. pycontrails/utils/json.py +1 -2
  61. pycontrails/utils/types.py +12 -7
  62. {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/METADATA +10 -10
  63. {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/NOTICE +1 -1
  64. pycontrails-0.54.4.dist-info/RECORD +111 -0
  65. {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/WHEEL +1 -1
  66. pycontrails-0.54.2.dist-info/RECORD +0 -109
  67. {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/LICENSE +0 -0
  68. {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/top_level.txt +0 -0
@@ -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
  # -------------------
@@ -25,25 +31,25 @@ logger = logging.getLogger(__name__)
25
31
 
26
32
 
27
33
  def acceleration(
28
- true_airspeed: npt.NDArray[np.float64], segment_duration: npt.NDArray[np.float64]
29
- ) -> npt.NDArray[np.float64]:
34
+ true_airspeed: npt.NDArray[np.floating], segment_duration: npt.NDArray[np.floating]
35
+ ) -> npt.NDArray[np.floating]:
30
36
  r"""Calculate the acceleration/deceleration at each waypoint.
31
37
 
32
38
  Parameters
33
39
  ----------
34
- true_airspeed : npt.NDArray[np.float64]
40
+ true_airspeed : npt.NDArray[np.floating]
35
41
  True airspeed, [:math:`m \ s^{-1}`]
36
- segment_duration : npt.NDArray[np.float64]
42
+ segment_duration : npt.NDArray[np.floating]
37
43
  Time difference between waypoints, [:math:`s`]
38
44
 
39
45
  Returns
40
46
  -------
41
- npt.NDArray[np.float64]
47
+ npt.NDArray[np.floating]
42
48
  Acceleration/deceleration, [:math:`m \ s^{-2}`]
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]
@@ -53,26 +59,26 @@ def acceleration(
53
59
 
54
60
 
55
61
  def climb_descent_angle(
56
- true_airspeed: npt.NDArray[np.float64], rocd: npt.NDArray[np.float64]
57
- ) -> npt.NDArray[np.float64]:
62
+ true_airspeed: npt.NDArray[np.floating], rocd: npt.NDArray[np.floating]
63
+ ) -> npt.NDArray[np.floating]:
58
64
  r"""Calculate angle between the horizontal plane and the actual flight path.
59
65
 
60
66
  Parameters
61
67
  ----------
62
- true_airspeed : npt.NDArray[np.float64]
68
+ true_airspeed : npt.NDArray[np.floating]
63
69
  True airspeed, [:math:`m \ s^{-1}`]
64
- rocd : npt.NDArray[np.float64]
70
+ rocd : npt.NDArray[np.floating]
65
71
  Rate of climb/descent, [:math:`ft min^{-1}`]
66
72
 
67
73
  Returns
68
74
  -------
69
- npt.NDArray[np.float64]
75
+ npt.NDArray[np.floating]
70
76
  Climb (positive value) or descent (negative value) angle, [:math:`\deg`]
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
@@ -80,10 +86,10 @@ def climb_descent_angle(
80
86
 
81
87
 
82
88
  def clip_mach_number(
83
- true_airspeed: npt.NDArray[np.float64],
84
- air_temperature: npt.NDArray[np.float64],
89
+ true_airspeed: npt.NDArray[np.floating],
90
+ air_temperature: npt.NDArray[np.floating],
85
91
  max_mach_number: ArrayOrFloat,
86
- ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
92
+ ) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
87
93
  r"""Compute the Mach number from the true airspeed and ambient temperature.
88
94
 
89
95
  This method clips the computed Mach number to the value of ``max_mach_number``.
@@ -93,9 +99,9 @@ def clip_mach_number(
93
99
 
94
100
  Parameters
95
101
  ----------
96
- true_airspeed : npt.NDArray[np.float64]
102
+ true_airspeed : npt.NDArray[np.floating]
97
103
  Array of true airspeed, [:math:`m \ s^{-1}`]
98
- air_temperature : npt.NDArray[np.float64]
104
+ air_temperature : npt.NDArray[np.floating]
99
105
  Array of ambient temperature, [:math: `K`]
100
106
  max_mach_number : ArrayOrFloat
101
107
  Maximum mach number associated to aircraft. If no clipping
@@ -103,10 +109,10 @@ def clip_mach_number(
103
109
 
104
110
  Returns
105
111
  -------
106
- true_airspeed : npt.NDArray[np.float64]
112
+ true_airspeed : npt.NDArray[np.floating]
107
113
  Array of true airspeed, [:math:`m \ s^{-1}`]. All values are clipped at
108
114
  ``max_mach_number``.
109
- mach_num : npt.NDArray[np.float64]
115
+ mach_num : npt.NDArray[np.floating]
110
116
  Array of Mach numbers, [:math:`Ma`]. All values are clipped at
111
117
  ``max_mach_number``.
112
118
  """
@@ -131,13 +137,13 @@ def clip_mach_number(
131
137
 
132
138
 
133
139
  def overall_propulsion_efficiency(
134
- true_airspeed: npt.NDArray[np.float64],
135
- F_thrust: npt.NDArray[np.float64],
136
- fuel_flow: npt.NDArray[np.float64],
140
+ true_airspeed: npt.NDArray[np.floating],
141
+ F_thrust: npt.NDArray[np.floating],
142
+ fuel_flow: npt.NDArray[np.floating],
137
143
  q_fuel: float,
138
144
  is_descent: npt.NDArray[np.bool_] | bool | None,
139
145
  threshold: float = 0.5,
140
- ) -> npt.NDArray[np.float64]:
146
+ ) -> npt.NDArray[np.floating]:
141
147
  r"""Calculate the overall propulsion efficiency (OPE).
142
148
 
143
149
  Negative OPE values can occur during the descent phase and is clipped to a
@@ -146,22 +152,22 @@ def overall_propulsion_efficiency(
146
152
 
147
153
  Parameters
148
154
  ----------
149
- true_airspeed: npt.NDArray[np.float64]
155
+ true_airspeed: npt.NDArray[np.floating]
150
156
  True airspeed for each waypoint, [:math:`m s^{-1}`].
151
- F_thrust: npt.NDArray[np.float64]
157
+ F_thrust: npt.NDArray[np.floating]
152
158
  Thrust force provided by the engine, [:math:`N`].
153
- fuel_flow: npt.NDArray[np.float64]
159
+ fuel_flow: npt.NDArray[np.floating]
154
160
  Fuel mass flow rate, [:math:`kg s^{-1}`].
155
161
  q_fuel : float
156
162
  Lower calorific value (LCV) of fuel, [:math:`J \ kg_{fuel}^{-1}`].
157
- is_descent : npt.NDArray[np.float64] | None
163
+ is_descent : npt.NDArray[np.floating] | None
158
164
  Boolean array that indicates if a waypoint is in a descent phase.
159
165
  threshold : float
160
166
  Upper bound for realistic engine efficiency.
161
167
 
162
168
  Returns
163
169
  -------
164
- npt.NDArray[np.float64]
170
+ npt.NDArray[np.floating]
165
171
  Overall propulsion efficiency (OPE)
166
172
 
167
173
  References
@@ -190,49 +196,49 @@ def overall_propulsion_efficiency(
190
196
 
191
197
 
192
198
  def fuel_burn(
193
- fuel_flow: npt.NDArray[np.float64], segment_duration: npt.NDArray[np.float64]
194
- ) -> npt.NDArray[np.float64]:
199
+ fuel_flow: npt.NDArray[np.floating], segment_duration: npt.NDArray[np.floating]
200
+ ) -> npt.NDArray[np.floating]:
195
201
  """Calculate the fuel consumption at each waypoint.
196
202
 
197
203
  Parameters
198
204
  ----------
199
- fuel_flow: npt.NDArray[np.float64]
205
+ fuel_flow: npt.NDArray[np.floating]
200
206
  Fuel mass flow rate, [:math:`kg s^{-1}`]
201
- segment_duration: npt.NDArray[np.float64]
207
+ segment_duration: npt.NDArray[np.floating]
202
208
  Time difference between waypoints, [:math:`s`]
203
209
 
204
210
  Returns
205
211
  -------
206
- npt.NDArray[np.float64]
212
+ npt.NDArray[np.floating]
207
213
  Fuel consumption at each waypoint, [:math:`kg`]
208
214
  """
209
215
  return fuel_flow * segment_duration
210
216
 
211
217
 
212
218
  def equivalent_fuel_flow_rate_at_sea_level(
213
- fuel_flow_cruise: npt.NDArray[np.float64],
214
- theta_amb: npt.NDArray[np.float64],
215
- delta_amb: npt.NDArray[np.float64],
216
- mach_num: npt.NDArray[np.float64],
217
- ) -> npt.NDArray[np.float64]:
219
+ fuel_flow_cruise: npt.NDArray[np.floating],
220
+ theta_amb: npt.NDArray[np.floating],
221
+ delta_amb: npt.NDArray[np.floating],
222
+ mach_num: npt.NDArray[np.floating],
223
+ ) -> npt.NDArray[np.floating]:
218
224
  r"""Convert fuel mass flow rate at cruise conditions to equivalent flow rate at sea level.
219
225
 
220
226
  Refer to Eq. (40) in :cite:`duboisFuelFlowMethod22006`.
221
227
 
222
228
  Parameters
223
229
  ----------
224
- fuel_flow_cruise : npt.NDArray[np.float64]
230
+ fuel_flow_cruise : npt.NDArray[np.floating]
225
231
  Fuel mass flow rate per engine, [:math:`kg s^{-1}`]
226
- theta_amb : npt.NDArray[np.float64]
232
+ theta_amb : npt.NDArray[np.floating]
227
233
  Ratio of the ambient temperature to the temperature at mean sea-level.
228
- delta_amb : npt.NDArray[np.float64]
234
+ delta_amb : npt.NDArray[np.floating]
229
235
  Ratio of the pressure altitude to the surface pressure.
230
- mach_num : npt.NDArray[np.float64]
236
+ mach_num : npt.NDArray[np.floating]
231
237
  Mach number, [:math: `Ma`]
232
238
 
233
239
  Returns
234
240
  -------
235
- npt.NDArray[np.float64]
241
+ npt.NDArray[np.floating]
236
242
  Estimate of fuel flow per engine at sea level, [:math:`kg \ s^{-1}`].
237
243
 
238
244
  References
@@ -243,18 +249,18 @@ def equivalent_fuel_flow_rate_at_sea_level(
243
249
 
244
250
 
245
251
  def equivalent_fuel_flow_rate_at_cruise(
246
- fuel_flow_sls: npt.NDArray[np.float64] | float,
252
+ fuel_flow_sls: npt.NDArray[np.floating] | float,
247
253
  theta_amb: ArrayOrFloat,
248
254
  delta_amb: ArrayOrFloat,
249
255
  mach_num: ArrayOrFloat,
250
- ) -> npt.NDArray[np.float64]:
256
+ ) -> npt.NDArray[np.floating]:
251
257
  r"""Convert fuel mass flow rate at sea level to equivalent fuel flow rate at cruise conditions.
252
258
 
253
259
  Refer to Eq. (40) in :cite:`duboisFuelFlowMethod22006`.
254
260
 
255
261
  Parameters
256
262
  ----------
257
- fuel_flow_sls : npt.NDArray[np.float64] | float
263
+ fuel_flow_sls : npt.NDArray[np.floating] | float
258
264
  Fuel mass flow rate, [:math:`kg s^{-1}`]
259
265
  theta_amb : ArrayOrFloat
260
266
  Ratio of the ambient temperature to the temperature at mean sea-level.
@@ -265,7 +271,7 @@ def equivalent_fuel_flow_rate_at_cruise(
265
271
 
266
272
  Returns
267
273
  -------
268
- npt.NDArray[np.float64]
274
+ npt.NDArray[np.floating]
269
275
  Estimate of fuel mass flow rate at sea level, [:math:`kg \ s^{-1}`]
270
276
 
271
277
  References
@@ -279,28 +285,28 @@ def equivalent_fuel_flow_rate_at_cruise(
279
285
 
280
286
 
281
287
  def reserve_fuel_requirements(
282
- rocd: npt.NDArray[np.float64],
283
- altitude_ft: npt.NDArray[np.float64],
284
- fuel_flow: npt.NDArray[np.float64],
285
- fuel_burn: npt.NDArray[np.float64],
288
+ rocd: npt.NDArray[np.floating],
289
+ altitude_ft: npt.NDArray[np.floating],
290
+ fuel_flow: npt.NDArray[np.floating],
291
+ fuel_burn: npt.NDArray[np.floating],
286
292
  ) -> float:
287
293
  r"""
288
294
  Estimate reserve fuel requirements.
289
295
 
290
296
  Parameters
291
297
  ----------
292
- rocd: npt.NDArray[np.float64]
298
+ rocd: npt.NDArray[np.floating]
293
299
  Rate of climb and descent, [:math:`ft \ min^{-1}`]
294
- altitude_ft: npt.NDArray[np.float64]
300
+ altitude_ft: npt.NDArray[np.floating]
295
301
  Altitude, [:math:`ft`]
296
- fuel_flow: npt.NDArray[np.float64]
302
+ fuel_flow: npt.NDArray[np.floating]
297
303
  Fuel mass flow rate, [:math:`kg \ s^{-1}`].
298
- fuel_burn: npt.NDArray[np.float64]
304
+ fuel_burn: npt.NDArray[np.floating]
299
305
  Fuel consumption for each waypoint, [:math:`kg`]
300
306
 
301
307
  Returns
302
308
  -------
303
- npt.NDArray[np.float64]
309
+ npt.NDArray[np.floating]
304
310
  Reserve fuel requirements, [:math:`kg`]
305
311
 
306
312
  References
@@ -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)
@@ -424,11 +553,11 @@ def update_aircraft_mass(
424
553
  operating_empty_weight: float,
425
554
  max_takeoff_weight: float,
426
555
  max_payload: float,
427
- fuel_burn: npt.NDArray[np.float64],
556
+ fuel_burn: npt.NDArray[np.floating],
428
557
  total_reserve_fuel: float,
429
558
  load_factor: float,
430
559
  takeoff_mass: float | None,
431
- ) -> npt.NDArray[np.float64]:
560
+ ) -> npt.NDArray[np.floating]:
432
561
  """Update aircraft mass based on the simulated total fuel consumption.
433
562
 
434
563
  Used internally for finding aircraft mass iteratively.
@@ -444,7 +573,7 @@ def update_aircraft_mass(
444
573
  Aircraft maximum take-off weight, [:math:`kg`].
445
574
  max_payload: float
446
575
  Aircraft maximum payload, [:math:`kg`]
447
- fuel_burn: npt.NDArray[np.float64]
576
+ fuel_burn: npt.NDArray[np.floating]
448
577
  Fuel consumption for each waypoint, [:math:`kg`]
449
578
  total_reserve_fuel: float
450
579
  Total reserve fuel requirements, [:math:`kg`]
@@ -458,14 +587,15 @@ def update_aircraft_mass(
458
587
 
459
588
  Returns
460
589
  -------
461
- npt.NDArray[np.float64]
590
+ npt.NDArray[np.floating]
462
591
  Updated aircraft mass, [:math:`kg`]
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(
@@ -631,30 +761,30 @@ def turbine_inlet_temperature(
631
761
 
632
762
 
633
763
  def thrust_force(
634
- altitude: npt.NDArray[np.float64],
635
- true_airspeed: npt.NDArray[np.float64],
636
- segment_duration: npt.NDArray[np.float64],
637
- aircraft_mass: npt.NDArray[np.float64],
638
- F_drag: npt.NDArray[np.float64],
639
- ) -> npt.NDArray[np.float64]:
764
+ altitude: npt.NDArray[np.floating],
765
+ true_airspeed: npt.NDArray[np.floating],
766
+ segment_duration: npt.NDArray[np.floating],
767
+ aircraft_mass: npt.NDArray[np.floating],
768
+ F_drag: npt.NDArray[np.floating],
769
+ ) -> npt.NDArray[np.floating]:
640
770
  r"""Calculate the thrust force at each waypoint.
641
771
 
642
772
  Parameters
643
773
  ----------
644
- altitude : npt.NDArray[np.float64]
774
+ altitude : npt.NDArray[np.floating]
645
775
  Waypoint altitude, [:math:`m`]
646
- true_airspeed : npt.NDArray[np.float64]
776
+ true_airspeed : npt.NDArray[np.floating]
647
777
  True airspeed, [:math:`m \ s^{-1}`]
648
- segment_duration : npt.NDArray[np.float64]
778
+ segment_duration : npt.NDArray[np.floating]
649
779
  Time difference between waypoints, [:math:`s`]
650
- aircraft_mass : npt.NDArray[np.float64]
780
+ aircraft_mass : npt.NDArray[np.floating]
651
781
  Aircraft mass, [:math:`kg`]
652
- F_drag : npt.NDArray[np.float64]
782
+ F_drag : npt.NDArray[np.floating]
653
783
  Draft force, [:math:`N`]
654
784
 
655
785
  Returns
656
786
  -------
657
- npt.NDArray[np.float64]
787
+ npt.NDArray[np.floating]
658
788
  Thrust force, [:math:`N`]
659
789
 
660
790
  References
@@ -789,49 +919,49 @@ def air_to_fuel_ratio(
789
919
  # -------------------
790
920
 
791
921
 
792
- def temperature_ratio(T: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
922
+ def temperature_ratio(T: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
793
923
  """Calculate the ratio of ambient temperature relative to the temperature at mean sea level.
794
924
 
795
925
  Parameters
796
926
  ----------
797
- T : npt.NDArray[np.float64]
927
+ T : npt.NDArray[np.floating]
798
928
  Air temperature, [:math:`K`]
799
929
 
800
930
  Returns
801
931
  -------
802
- npt.NDArray[np.float64]
932
+ npt.NDArray[np.floating]
803
933
  Ratio of the temperature to the temperature at mean sea-level (MSL).
804
934
  """
805
935
  return T / constants.T_msl
806
936
 
807
937
 
808
- def pressure_ratio(p: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
938
+ def pressure_ratio(p: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
809
939
  """Calculate the ratio of ambient pressure relative to the surface pressure.
810
940
 
811
941
  Parameters
812
942
  ----------
813
- p : npt.NDArray[np.float64]
943
+ p : npt.NDArray[np.floating]
814
944
  Air pressure, [:math:`Pa`]
815
945
 
816
946
  Returns
817
947
  -------
818
- npt.NDArray[np.float64]
948
+ npt.NDArray[np.floating]
819
949
  Ratio of the pressure altitude to the surface pressure.
820
950
  """
821
951
  return p / constants.p_surface
822
952
 
823
953
 
824
- def density_ratio(rho: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
954
+ def density_ratio(rho: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
825
955
  r"""Calculate the ratio of air density relative to the air density at mean-sea-level.
826
956
 
827
957
  Parameters
828
958
  ----------
829
- rho : npt.NDArray[np.float64]
959
+ rho : npt.NDArray[np.floating]
830
960
  Air density, [:math:`kg \ m^{3}`]
831
961
 
832
962
  Returns
833
963
  -------
834
- npt.NDArray[np.float64]
964
+ npt.NDArray[np.floating]
835
965
  Ratio of the density to the air density at mean sea-level (MSL).
836
966
  """
837
967
  return rho / constants.rho_msl
@@ -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