pycontrails 0.54.2__cp313-cp313-macosx_11_0_arm64.whl → 0.54.4__cp313-cp313-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.
- pycontrails/__init__.py +2 -2
- pycontrails/_version.py +2 -2
- pycontrails/core/__init__.py +1 -1
- pycontrails/core/aircraft_performance.py +75 -61
- pycontrails/core/cache.py +7 -7
- pycontrails/core/fleet.py +25 -21
- pycontrails/core/flight.py +215 -301
- pycontrails/core/interpolation.py +56 -56
- pycontrails/core/met.py +48 -39
- pycontrails/core/models.py +25 -11
- pycontrails/core/polygon.py +15 -15
- pycontrails/core/rgi_cython.cpython-313-darwin.so +0 -0
- pycontrails/core/vector.py +22 -22
- pycontrails/datalib/_met_utils/metsource.py +8 -5
- pycontrails/datalib/ecmwf/__init__.py +14 -14
- pycontrails/datalib/ecmwf/common.py +1 -1
- pycontrails/datalib/ecmwf/era5.py +7 -7
- pycontrails/datalib/ecmwf/hres.py +3 -3
- pycontrails/datalib/ecmwf/ifs.py +1 -1
- pycontrails/datalib/ecmwf/variables.py +1 -0
- pycontrails/datalib/gfs/__init__.py +6 -6
- pycontrails/datalib/gfs/gfs.py +2 -2
- pycontrails/datalib/goes.py +5 -5
- pycontrails/datalib/landsat.py +5 -8
- pycontrails/datalib/sentinel.py +7 -11
- pycontrails/ext/bada.py +3 -2
- pycontrails/ext/empirical_grid.py +1 -1
- pycontrails/ext/synthetic_flight.py +3 -2
- pycontrails/models/accf.py +40 -19
- pycontrails/models/apcemm/apcemm.py +5 -4
- pycontrails/models/cocip/__init__.py +2 -2
- pycontrails/models/cocip/cocip.py +16 -17
- pycontrails/models/cocip/cocip_params.py +2 -11
- pycontrails/models/cocip/cocip_uncertainty.py +24 -18
- pycontrails/models/cocip/contrail_properties.py +331 -316
- pycontrails/models/cocip/output_formats.py +53 -53
- pycontrails/models/cocip/radiative_forcing.py +135 -131
- pycontrails/models/cocip/radiative_heating.py +135 -135
- pycontrails/models/cocip/unterstrasser_wake_vortex.py +90 -87
- pycontrails/models/cocip/wake_vortex.py +92 -92
- pycontrails/models/cocip/wind_shear.py +8 -8
- pycontrails/models/cocipgrid/cocip_grid.py +118 -107
- pycontrails/models/dry_advection.py +59 -58
- pycontrails/models/emissions/__init__.py +2 -2
- pycontrails/models/emissions/black_carbon.py +108 -108
- pycontrails/models/emissions/emissions.py +85 -85
- pycontrails/models/emissions/ffm2.py +35 -35
- pycontrails/models/humidity_scaling/humidity_scaling.py +23 -23
- pycontrails/models/ps_model/__init__.py +3 -2
- pycontrails/models/ps_model/ps_aircraft_params.py +11 -6
- pycontrails/models/ps_model/ps_grid.py +256 -60
- pycontrails/models/ps_model/ps_model.py +18 -21
- pycontrails/models/ps_model/ps_operational_limits.py +58 -69
- pycontrails/models/tau_cirrus.py +8 -1
- pycontrails/physics/geo.py +216 -67
- pycontrails/physics/jet.py +220 -90
- pycontrails/physics/static/iata-cargo-load-factors-20241115.csv +71 -0
- pycontrails/physics/static/iata-passenger-load-factors-20241115.csv +71 -0
- pycontrails/physics/units.py +14 -14
- pycontrails/utils/json.py +1 -2
- pycontrails/utils/types.py +12 -7
- {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/METADATA +10 -10
- {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/NOTICE +1 -1
- pycontrails-0.54.4.dist-info/RECORD +111 -0
- {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/WHEEL +1 -1
- pycontrails-0.54.2.dist-info/RECORD +0 -109
- {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/LICENSE +0 -0
- {pycontrails-0.54.2.dist-info → pycontrails-0.54.4.dist-info}/top_level.txt +0 -0
|
@@ -11,7 +11,9 @@ from typing import Any
|
|
|
11
11
|
import numpy as np
|
|
12
12
|
import pandas as pd
|
|
13
13
|
|
|
14
|
+
from pycontrails.core.aircraft_performance import AircraftPerformanceParams
|
|
14
15
|
from pycontrails.physics import constants as c
|
|
16
|
+
from pycontrails.utils.types import ArrayOrFloat
|
|
15
17
|
|
|
16
18
|
#: Path to the Poll-Schumann aircraft parameters CSV file.
|
|
17
19
|
PS_FILE_PATH = pathlib.Path(__file__).parent / "static" / "ps-aircraft-params-20240524.csv"
|
|
@@ -193,7 +195,7 @@ def _row_to_aircraft_engine_params(tup: Any) -> tuple[str, PSAircraftEngineParam
|
|
|
193
195
|
|
|
194
196
|
@functools.cache
|
|
195
197
|
def load_aircraft_engine_params(
|
|
196
|
-
engine_deterioration_factor: float =
|
|
198
|
+
engine_deterioration_factor: float = AircraftPerformanceParams.engine_deterioration_factor,
|
|
197
199
|
) -> Mapping[str, PSAircraftEngineParams]:
|
|
198
200
|
"""
|
|
199
201
|
Extract aircraft-engine parameters for each aircraft type supported by the PS model.
|
|
@@ -254,23 +256,23 @@ def load_aircraft_engine_params(
|
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
df = pd.read_csv(PS_FILE_PATH, dtype=dtypes)
|
|
257
|
-
df["eta_1"]
|
|
259
|
+
df["eta_1"] *= 1.0 - engine_deterioration_factor
|
|
258
260
|
|
|
259
261
|
return dict(_row_to_aircraft_engine_params(tup) for tup in df.itertuples(index=False))
|
|
260
262
|
|
|
261
263
|
|
|
262
|
-
def turbine_entry_temperature_at_max_take_off(first_flight:
|
|
264
|
+
def turbine_entry_temperature_at_max_take_off(first_flight: ArrayOrFloat) -> ArrayOrFloat:
|
|
263
265
|
"""
|
|
264
266
|
Calculate turbine entry temperature at maximum take-off rating.
|
|
265
267
|
|
|
266
268
|
Parameters
|
|
267
269
|
----------
|
|
268
|
-
first_flight:
|
|
270
|
+
first_flight: ArrayOrFloat
|
|
269
271
|
Year of first flight
|
|
270
272
|
|
|
271
273
|
Returns
|
|
272
274
|
-------
|
|
273
|
-
|
|
275
|
+
ArrayOrFloat
|
|
274
276
|
Turbine entry temperature at maximum take-off rating, ``tet_mto``, [:math:`K`]
|
|
275
277
|
|
|
276
278
|
Notes
|
|
@@ -283,7 +285,10 @@ def turbine_entry_temperature_at_max_take_off(first_flight: float) -> float:
|
|
|
283
285
|
----------
|
|
284
286
|
- :cite:`cumpstyJetPropulsion2015`
|
|
285
287
|
"""
|
|
286
|
-
|
|
288
|
+
out = 2000.0 * (1.0 - np.exp(62.8 - 0.0325 * first_flight))
|
|
289
|
+
if isinstance(first_flight, np.ndarray):
|
|
290
|
+
return out
|
|
291
|
+
return out.item()
|
|
287
292
|
|
|
288
293
|
|
|
289
294
|
def turbine_entry_temperature_at_max_continuous_climb(tet_mto: float) -> float:
|
|
@@ -10,7 +10,6 @@ import numpy as np
|
|
|
10
10
|
import numpy.typing as npt
|
|
11
11
|
import scipy.optimize
|
|
12
12
|
import xarray as xr
|
|
13
|
-
import xarray.core.coordinates as xrcc
|
|
14
13
|
|
|
15
14
|
from pycontrails.core.aircraft_performance import (
|
|
16
15
|
AircraftPerformanceGrid,
|
|
@@ -178,9 +177,9 @@ class PSGrid(AircraftPerformanceGrid):
|
|
|
178
177
|
@dataclasses.dataclass
|
|
179
178
|
class _PerfVariables:
|
|
180
179
|
atyp_param: PSAircraftEngineParams
|
|
181
|
-
air_pressure: npt.NDArray[np.
|
|
182
|
-
air_temperature: npt.NDArray[np.
|
|
183
|
-
mach_number: npt.NDArray[np.
|
|
180
|
+
air_pressure: npt.NDArray[np.floating] | float
|
|
181
|
+
air_temperature: npt.NDArray[np.floating] | float
|
|
182
|
+
mach_number: npt.NDArray[np.floating] | float
|
|
184
183
|
q_fuel: float
|
|
185
184
|
|
|
186
185
|
|
|
@@ -193,8 +192,10 @@ def _nominal_perf(aircraft_mass: ArrayOrFloat, perf: _PerfVariables) -> Aircraft
|
|
|
193
192
|
mach_number = perf.mach_number
|
|
194
193
|
q_fuel = perf.q_fuel
|
|
195
194
|
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
# Using np.float32 here avoids scalar promotion to float64 via numpy 2.0 and NEP50
|
|
196
|
+
# In other words, the dtype of the perf variables is maintained
|
|
197
|
+
theta = np.float32(0.0)
|
|
198
|
+
dv_dt = np.float32(0.0)
|
|
198
199
|
|
|
199
200
|
rn = ps_model.reynolds_number(
|
|
200
201
|
atyp_param.wing_surface_area, mach_number, air_temperature, air_pressure
|
|
@@ -271,7 +272,7 @@ def _estimate_mass_extremes(
|
|
|
271
272
|
atyp_param: PSAircraftEngineParams,
|
|
272
273
|
perf: _PerfVariables,
|
|
273
274
|
n_iter: int = 3,
|
|
274
|
-
) -> tuple[npt.NDArray[np.
|
|
275
|
+
) -> tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]]:
|
|
275
276
|
"""Calculate the minimum and maximum mass for a given aircraft type."""
|
|
276
277
|
|
|
277
278
|
oem = atyp_param.amass_oew # operating empty mass
|
|
@@ -296,43 +297,57 @@ def _estimate_mass_extremes(
|
|
|
296
297
|
|
|
297
298
|
|
|
298
299
|
def _parse_variables(
|
|
299
|
-
level: npt.NDArray[np.
|
|
300
|
-
air_temperature: xr.DataArray | npt.NDArray[np.
|
|
301
|
-
) -> tuple[
|
|
302
|
-
|
|
303
|
-
|
|
300
|
+
level: npt.NDArray[np.floating] | None,
|
|
301
|
+
air_temperature: xr.DataArray | npt.NDArray[np.floating] | None,
|
|
302
|
+
) -> tuple[
|
|
303
|
+
tuple[str],
|
|
304
|
+
dict[str, npt.NDArray[np.floating]],
|
|
305
|
+
npt.NDArray[np.floating],
|
|
306
|
+
npt.NDArray[np.floating],
|
|
307
|
+
]:
|
|
308
|
+
"""Parse the level and air temperature arguments.
|
|
309
|
+
|
|
310
|
+
Returns a tuple of ``(dims, coords, air_pressure, air_temperature)``.
|
|
311
|
+
"""
|
|
304
312
|
if isinstance(air_temperature, xr.DataArray):
|
|
305
313
|
if level is not None:
|
|
306
314
|
msg = "If 'air_temperature' is a DataArray, 'level' must be None"
|
|
307
315
|
raise ValueError(msg)
|
|
308
316
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
317
|
+
try:
|
|
318
|
+
pressure_da = air_temperature["air_pressure"]
|
|
319
|
+
except KeyError as exc:
|
|
320
|
+
msg = "An 'air_pressure' coordinate must be present in 'air_temperature'"
|
|
321
|
+
raise KeyError(msg) from exc
|
|
322
|
+
|
|
323
|
+
air_temperature, pressure_da = xr.broadcast(air_temperature, pressure_da)
|
|
324
|
+
return ( # type: ignore[return-value]
|
|
325
|
+
air_temperature.dims,
|
|
326
|
+
air_temperature.coords,
|
|
327
|
+
np.asarray(pressure_da),
|
|
328
|
+
np.asarray(air_temperature),
|
|
329
|
+
)
|
|
320
330
|
|
|
321
331
|
if level is None:
|
|
322
|
-
msg = "The 'level' argument must be
|
|
332
|
+
msg = "The 'level' argument must be provided"
|
|
323
333
|
raise ValueError(msg)
|
|
324
334
|
|
|
325
|
-
|
|
335
|
+
air_pressure = level * 100.0
|
|
336
|
+
if air_temperature is None:
|
|
337
|
+
altitude_m = units.pl_to_m(level)
|
|
338
|
+
air_temperature = units.m_to_T_isa(altitude_m)
|
|
339
|
+
return ("level",), {"level": level}, air_pressure, air_temperature
|
|
326
340
|
|
|
327
341
|
|
|
328
342
|
def ps_nominal_grid(
|
|
329
343
|
aircraft_type: str,
|
|
330
344
|
*,
|
|
331
|
-
level: npt.NDArray[np.
|
|
332
|
-
air_temperature: xr.DataArray | npt.NDArray[np.
|
|
345
|
+
level: npt.NDArray[np.floating] | None = None,
|
|
346
|
+
air_temperature: xr.DataArray | npt.NDArray[np.floating] | None = None,
|
|
333
347
|
q_fuel: float = JetA.q_fuel,
|
|
334
348
|
mach_number: float | None = None,
|
|
335
349
|
maxiter: int = PSGridParams.maxiter,
|
|
350
|
+
engine_deterioration_factor: float = PSGridParams.engine_deterioration_factor,
|
|
336
351
|
) -> xr.Dataset:
|
|
337
352
|
"""Calculate the nominal performance grid for a given aircraft type.
|
|
338
353
|
|
|
@@ -344,13 +359,13 @@ def ps_nominal_grid(
|
|
|
344
359
|
----------
|
|
345
360
|
aircraft_type : str
|
|
346
361
|
The aircraft type.
|
|
347
|
-
level : npt.NDArray[np.
|
|
362
|
+
level : npt.NDArray[np.floating] | None, optional
|
|
348
363
|
The pressure level, [:math:`hPa`]. If None, the ``air_temperature``
|
|
349
|
-
argument must be a :class:`xarray.DataArray` with
|
|
350
|
-
air_temperature : xr.DataArray | npt.NDArray[np.
|
|
364
|
+
argument must be a :class:`xarray.DataArray` with an ``air_pressure`` coordinate.
|
|
365
|
+
air_temperature : xr.DataArray | npt.NDArray[np.floating] | None, optional
|
|
351
366
|
The ambient air temperature, [:math:`K`]. If None (default), the ISA
|
|
352
367
|
temperature is computed from the ``level`` argument. If a :class:`xarray.DataArray`,
|
|
353
|
-
|
|
368
|
+
an ``air_pressure`` coordinate must be present and the ``level`` argument must be None
|
|
354
369
|
to avoid ambiguity. If a :class:`numpy.ndarray` is passed, it is assumed to be 1
|
|
355
370
|
dimensional with the same shape as the ``level`` argument.
|
|
356
371
|
q_fuel : float, optional
|
|
@@ -359,6 +374,9 @@ def ps_nominal_grid(
|
|
|
359
374
|
The Mach number. If None (default), the PS design Mach number is used.
|
|
360
375
|
maxiter : int, optional
|
|
361
376
|
Passed into :func:`scipy.optimize.newton`.
|
|
377
|
+
engine_deterioration_factor : float, optional
|
|
378
|
+
The engine deterioration factor,
|
|
379
|
+
by default :attr:`PSGridParams.engine_deterioration_factor`.
|
|
362
380
|
|
|
363
381
|
Returns
|
|
364
382
|
-------
|
|
@@ -376,6 +394,10 @@ def ps_nominal_grid(
|
|
|
376
394
|
KeyError
|
|
377
395
|
If "aircraft_type" is not supported by the PS model.
|
|
378
396
|
|
|
397
|
+
See Also
|
|
398
|
+
--------
|
|
399
|
+
ps_nominal_optimize_mach
|
|
400
|
+
|
|
379
401
|
Examples
|
|
380
402
|
--------
|
|
381
403
|
>>> level = np.arange(200, 300, 10, dtype=float)
|
|
@@ -389,16 +411,16 @@ def ps_nominal_grid(
|
|
|
389
411
|
>>> perf.to_dataframe()
|
|
390
412
|
aircraft_mass engine_efficiency fuel_flow
|
|
391
413
|
level
|
|
392
|
-
200.0 58416.
|
|
414
|
+
200.0 58416.230844 0.300958 0.575635
|
|
393
415
|
210.0 61617.676624 0.300958 0.604417
|
|
394
|
-
220.0 64829.
|
|
395
|
-
230.0 68026.
|
|
416
|
+
220.0 64829.702584 0.300958 0.633199
|
|
417
|
+
230.0 68026.415694 0.300958 0.662998
|
|
396
418
|
240.0 71187.897060 0.300958 0.694631
|
|
397
|
-
250.0 71775.
|
|
398
|
-
260.0 71765.
|
|
399
|
-
270.0 71752.
|
|
400
|
-
280.0 71736.
|
|
401
|
-
290.0 71717.
|
|
419
|
+
250.0 71775.399880 0.300824 0.703349
|
|
420
|
+
260.0 71765.716789 0.300363 0.708259
|
|
421
|
+
270.0 71752.405449 0.299671 0.714514
|
|
422
|
+
280.0 71736.129125 0.298823 0.721878
|
|
423
|
+
290.0 71717.392213 0.297875 0.730169
|
|
402
424
|
|
|
403
425
|
>>> # Now compute it for a higher Mach number
|
|
404
426
|
>>> perf = ps_nominal_grid("A320", level=level, mach_number=0.78)
|
|
@@ -407,28 +429,18 @@ def ps_nominal_grid(
|
|
|
407
429
|
level
|
|
408
430
|
200.0 57941.825236 0.306598 0.596100
|
|
409
431
|
210.0 60626.062062 0.306605 0.621331
|
|
410
|
-
220.0 63818.
|
|
411
|
-
230.0 66993.
|
|
412
|
-
240.0 70129.
|
|
413
|
-
250.0 71703.
|
|
414
|
-
260.0 71690.
|
|
415
|
-
270.0 71673.
|
|
416
|
-
280.0 71653.
|
|
417
|
-
290.0 71630.
|
|
432
|
+
220.0 63818.498305 0.306605 0.650918
|
|
433
|
+
230.0 66993.691515 0.306605 0.681551
|
|
434
|
+
240.0 70129.930502 0.306605 0.714069
|
|
435
|
+
250.0 71703.009114 0.306560 0.732944
|
|
436
|
+
260.0 71690.188703 0.306239 0.739276
|
|
437
|
+
270.0 71673.392137 0.305694 0.747052
|
|
438
|
+
280.0 71653.431366 0.304997 0.755990
|
|
439
|
+
290.0 71630.901358 0.304201 0.765883
|
|
418
440
|
"""
|
|
419
|
-
coords
|
|
420
|
-
if isinstance(air_temperature, xr.DataArray):
|
|
421
|
-
dims = air_temperature.dims
|
|
422
|
-
coords = air_temperature.coords
|
|
423
|
-
else:
|
|
424
|
-
dims = ("level",)
|
|
425
|
-
coords = {"level": level}
|
|
441
|
+
dims, coords, air_pressure, air_temperature = _parse_variables(level, air_temperature)
|
|
426
442
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
air_pressure = level * 100.0
|
|
430
|
-
|
|
431
|
-
aircraft_engine_params = ps_model.load_aircraft_engine_params()
|
|
443
|
+
aircraft_engine_params = ps_model.load_aircraft_engine_params(engine_deterioration_factor)
|
|
432
444
|
|
|
433
445
|
try:
|
|
434
446
|
atyp_param = aircraft_engine_params[aircraft_type]
|
|
@@ -471,7 +483,7 @@ def ps_nominal_grid(
|
|
|
471
483
|
func=_newton_func,
|
|
472
484
|
args=(perf,),
|
|
473
485
|
x0=x0,
|
|
474
|
-
tol=
|
|
486
|
+
tol=80.0, # use roughly the weight of a passenger as a tolerance
|
|
475
487
|
disp=False,
|
|
476
488
|
maxiter=maxiter,
|
|
477
489
|
)
|
|
@@ -503,3 +515,187 @@ def ps_nominal_grid(
|
|
|
503
515
|
coords=coords,
|
|
504
516
|
attrs=attrs,
|
|
505
517
|
)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _newton_mach(
|
|
521
|
+
mach_number: ArrayOrFloat,
|
|
522
|
+
perf: _PerfVariables,
|
|
523
|
+
aircraft_mass: ArrayOrFloat,
|
|
524
|
+
headwind: ArrayOrFloat,
|
|
525
|
+
cost_index: ArrayOrFloat,
|
|
526
|
+
) -> ArrayOrFloat:
|
|
527
|
+
"""Approximate the derivative of the cost of a segment based on mach number.
|
|
528
|
+
|
|
529
|
+
This is used to find the mach number at which cost in minimized.
|
|
530
|
+
"""
|
|
531
|
+
perf.mach_number = mach_number + 1e-4
|
|
532
|
+
tas = units.mach_number_to_tas(perf.mach_number, perf.air_temperature)
|
|
533
|
+
groundspeed = tas - headwind
|
|
534
|
+
ff1 = _nominal_perf(aircraft_mass, perf).fuel_flow
|
|
535
|
+
eccf1 = (cost_index + ff1 * 60) / groundspeed
|
|
536
|
+
|
|
537
|
+
perf.mach_number = mach_number - 1e-4
|
|
538
|
+
tas = units.mach_number_to_tas(perf.mach_number, perf.air_temperature)
|
|
539
|
+
groundspeed = tas - headwind
|
|
540
|
+
ff2 = _nominal_perf(aircraft_mass, perf).fuel_flow
|
|
541
|
+
eccf2 = (cost_index + ff2 * 60) / groundspeed
|
|
542
|
+
return eccf1 - eccf2
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def ps_nominal_optimize_mach(
|
|
546
|
+
aircraft_type: str,
|
|
547
|
+
aircraft_mass: ArrayOrFloat,
|
|
548
|
+
cost_index: ArrayOrFloat,
|
|
549
|
+
level: ArrayOrFloat,
|
|
550
|
+
*,
|
|
551
|
+
air_temperature: ArrayOrFloat | None = None,
|
|
552
|
+
northward_wind: ArrayOrFloat | None = None,
|
|
553
|
+
eastward_wind: ArrayOrFloat | None = None,
|
|
554
|
+
sin_a: ArrayOrFloat | None = None,
|
|
555
|
+
cos_a: ArrayOrFloat | None = None,
|
|
556
|
+
q_fuel: float = JetA.q_fuel,
|
|
557
|
+
engine_deterioration_factor: float = PSGridParams.engine_deterioration_factor,
|
|
558
|
+
) -> xr.Dataset:
|
|
559
|
+
"""Calculate the nominal optimal mach number for a given aircraft type.
|
|
560
|
+
|
|
561
|
+
This function is similar to the :class:`ps_nominal_grid` method, but rather than
|
|
562
|
+
maximizing engine efficiency by adjusting aircraft, we are minimizing cost by adjusting
|
|
563
|
+
mach number.
|
|
564
|
+
|
|
565
|
+
Parameters
|
|
566
|
+
----------
|
|
567
|
+
aircraft_type : str
|
|
568
|
+
The aircraft type.
|
|
569
|
+
aircraft_mass: ArrayOrFloat
|
|
570
|
+
The aircraft mass, [:math:`kg`].
|
|
571
|
+
cost_index: ArrayOrFloat
|
|
572
|
+
The cost index, [:math:`kg/min`], or non-fuel cost of one minute of flight time
|
|
573
|
+
level : ArrayOrFloat
|
|
574
|
+
The pressure level, [:math:`hPa`]. If a :class:`numpy.ndarray` is passed, it is
|
|
575
|
+
assumed to be one dimensional and the same length as the``aircraft_mass`` argument.
|
|
576
|
+
air_temperature : ArrayOrFloat | None, optional
|
|
577
|
+
The ambient air temperature, [:math:`K`]. If None (default), the ISA
|
|
578
|
+
temperature is computed from the ``level`` argument. If a :class:`numpy.ndarray`
|
|
579
|
+
is passed, it is assumed to be one dimensional and the same length as the
|
|
580
|
+
``aircraft_mass`` argument.
|
|
581
|
+
air_temperature : ArrayOrFloat | None, optional
|
|
582
|
+
northward_wind: ArrayOrFloat | None = None, optional
|
|
583
|
+
The northward component of winds, [:math:`m/s`]. If None (default) assumed to be
|
|
584
|
+
zero.
|
|
585
|
+
eastward_wind: ArrayOrFloat | None = None, optional
|
|
586
|
+
The eastward component of winds, [:math:`m/s`]. If None (default) assumed to be
|
|
587
|
+
zero.
|
|
588
|
+
sin_a: ArrayOrFloat | None = None, optional
|
|
589
|
+
The sine between the true bearing of flight and the longitudinal axis. Must be
|
|
590
|
+
specified if wind data is provided. Will be ignored if wind data is not provided.
|
|
591
|
+
cos_a: ArrayOrFloat | None = None, optional
|
|
592
|
+
The cosine between the true bearing of flight and the longitudinal axis. Must be
|
|
593
|
+
specified if wind data is provided. Will be ignored if wind data is not provided.
|
|
594
|
+
q_fuel : float, optional
|
|
595
|
+
The fuel heating value, by default :attr:`JetA.q_fuel`.
|
|
596
|
+
engine_deterioration_factor : float, optional
|
|
597
|
+
The engine deterioration factor,
|
|
598
|
+
by default :attr:`PSGridParams.engine_deterioration_factor`.
|
|
599
|
+
|
|
600
|
+
Returns
|
|
601
|
+
-------
|
|
602
|
+
xr.Dataset
|
|
603
|
+
The nominal performance grid. The grid is indexed by altitude.
|
|
604
|
+
Contains the following variables:
|
|
605
|
+
|
|
606
|
+
- ``"mach_number"``: The mach number that minimizes segment cost
|
|
607
|
+
- ``"fuel_flow"`` : Fuel flow rate, [:math:`kg/s`]
|
|
608
|
+
- ``"engine_efficiency"`` : Engine efficiency
|
|
609
|
+
- ``"aircraft_mass"`` : Aircraft mass, [:math:`kg`]
|
|
610
|
+
|
|
611
|
+
Raises
|
|
612
|
+
------
|
|
613
|
+
KeyError
|
|
614
|
+
If "aircraft_type" is not supported by the PS model.
|
|
615
|
+
ValueError
|
|
616
|
+
If wind data is provided without segment angles.
|
|
617
|
+
|
|
618
|
+
See Also
|
|
619
|
+
--------
|
|
620
|
+
ps_nominal_grid
|
|
621
|
+
"""
|
|
622
|
+
dims = ("level",)
|
|
623
|
+
coords = {"level": level}
|
|
624
|
+
aircraft_engine_params = ps_model.load_aircraft_engine_params(engine_deterioration_factor)
|
|
625
|
+
try:
|
|
626
|
+
atyp_param = aircraft_engine_params[aircraft_type]
|
|
627
|
+
except KeyError as exc:
|
|
628
|
+
msg = (
|
|
629
|
+
f"The aircraft type {aircraft_type} is not currently supported by the PS model. "
|
|
630
|
+
f"Available aircraft types are: {list(aircraft_engine_params)}"
|
|
631
|
+
)
|
|
632
|
+
raise KeyError(msg) from exc
|
|
633
|
+
|
|
634
|
+
if air_temperature is None:
|
|
635
|
+
altitude_m = units.pl_to_m(level)
|
|
636
|
+
air_temperature = units.m_to_T_isa(altitude_m)
|
|
637
|
+
|
|
638
|
+
if northward_wind is not None and eastward_wind is not None:
|
|
639
|
+
if sin_a is None or cos_a is None:
|
|
640
|
+
msg = "Segment angles must be provide if wind data is specified"
|
|
641
|
+
raise ValueError(msg)
|
|
642
|
+
headwind = -(northward_wind * cos_a + eastward_wind * sin_a) # type: ignore[misc]
|
|
643
|
+
else:
|
|
644
|
+
headwind = 0.0 # type: ignore
|
|
645
|
+
|
|
646
|
+
min_mach = ps_operational_limits.minimum_mach_num(
|
|
647
|
+
air_pressure=level * 100.0,
|
|
648
|
+
aircraft_mass=aircraft_mass,
|
|
649
|
+
atyp_param=atyp_param,
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
max_mach = ps_operational_limits.maximum_mach_num(
|
|
653
|
+
altitude_ft=units.pl_to_ft(level),
|
|
654
|
+
air_pressure=level * 100.0,
|
|
655
|
+
aircraft_mass=aircraft_mass,
|
|
656
|
+
air_temperature=air_temperature,
|
|
657
|
+
theta=np.full_like(aircraft_mass, 0.0),
|
|
658
|
+
atyp_param=atyp_param,
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
x0 = (min_mach + max_mach) / 2.0 # type: ignore
|
|
662
|
+
|
|
663
|
+
perf = _PerfVariables(
|
|
664
|
+
atyp_param=atyp_param,
|
|
665
|
+
air_pressure=level * 100.0,
|
|
666
|
+
air_temperature=air_temperature,
|
|
667
|
+
mach_number=x0,
|
|
668
|
+
q_fuel=q_fuel,
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
opt_mach = scipy.optimize.newton(
|
|
672
|
+
func=_newton_mach,
|
|
673
|
+
args=(perf, aircraft_mass, headwind, cost_index),
|
|
674
|
+
x0=x0,
|
|
675
|
+
tol=1e-4,
|
|
676
|
+
disp=False,
|
|
677
|
+
).clip(min=min_mach, max=max_mach)
|
|
678
|
+
|
|
679
|
+
perf.mach_number = opt_mach
|
|
680
|
+
output = _nominal_perf(aircraft_mass, perf)
|
|
681
|
+
|
|
682
|
+
engine_efficiency = output.engine_efficiency
|
|
683
|
+
fuel_flow = output.fuel_flow
|
|
684
|
+
|
|
685
|
+
attrs = {
|
|
686
|
+
"aircraft_type": aircraft_type,
|
|
687
|
+
"q_fuel": q_fuel,
|
|
688
|
+
"wingspan": atyp_param.wing_span,
|
|
689
|
+
"n_engine": atyp_param.n_engine,
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return xr.Dataset(
|
|
693
|
+
{
|
|
694
|
+
"mach_number": (dims, opt_mach),
|
|
695
|
+
"aircraft_mass": (dims, aircraft_mass),
|
|
696
|
+
"engine_efficiency": (dims, engine_efficiency),
|
|
697
|
+
"fuel_flow": (dims, fuel_flow),
|
|
698
|
+
},
|
|
699
|
+
coords=coords,
|
|
700
|
+
attrs=attrs,
|
|
701
|
+
)
|
|
@@ -51,13 +51,6 @@ class PSFlightParams(AircraftPerformanceParams):
|
|
|
51
51
|
#: efficiency to always exceed this value.
|
|
52
52
|
eta_over_eta_b_min: float | None = 0.5
|
|
53
53
|
|
|
54
|
-
#: Account for "in-service" engine deterioration between maintenance cycles.
|
|
55
|
-
#: Default value is set to +2.5% increase in fuel consumption.
|
|
56
|
-
# Reference:
|
|
57
|
-
# Gurrola Arrieta, M.D.J., Botez, R.M. and Lasne, A., 2024. An Engine Deterioration Model for
|
|
58
|
-
# Predicting Fuel Consumption Impact in a Regional Aircraft. Aerospace, 11(6), p.426.
|
|
59
|
-
engine_deterioration_factor: float = 0.025
|
|
60
|
-
|
|
61
54
|
|
|
62
55
|
class PSFlight(AircraftPerformance):
|
|
63
56
|
"""Simulate aircraft performance using Poll-Schumann (PS) model.
|
|
@@ -70,6 +63,10 @@ class PSFlight(AircraftPerformance):
|
|
|
70
63
|
Poll & Schumann (2022). An estimation method for the fuel burn and other performance
|
|
71
64
|
characteristics of civil transport aircraft. Part 3 Generalisation to cover climb,
|
|
72
65
|
descent and holding. Aero. J., submitted.
|
|
66
|
+
|
|
67
|
+
See Also
|
|
68
|
+
--------
|
|
69
|
+
pycontrails.physics.jet.aircraft_load_factor
|
|
73
70
|
"""
|
|
74
71
|
|
|
75
72
|
name = "PSFlight"
|
|
@@ -227,14 +224,14 @@ class PSFlight(AircraftPerformance):
|
|
|
227
224
|
self,
|
|
228
225
|
*,
|
|
229
226
|
aircraft_type: str,
|
|
230
|
-
altitude_ft: npt.NDArray[np.
|
|
231
|
-
air_temperature: npt.NDArray[np.
|
|
227
|
+
altitude_ft: npt.NDArray[np.floating],
|
|
228
|
+
air_temperature: npt.NDArray[np.floating],
|
|
232
229
|
time: npt.NDArray[np.datetime64] | None,
|
|
233
|
-
true_airspeed: npt.NDArray[np.
|
|
234
|
-
aircraft_mass: npt.NDArray[np.
|
|
235
|
-
engine_efficiency: npt.NDArray[np.
|
|
236
|
-
fuel_flow: npt.NDArray[np.
|
|
237
|
-
thrust: npt.NDArray[np.
|
|
230
|
+
true_airspeed: npt.NDArray[np.floating] | float | None,
|
|
231
|
+
aircraft_mass: npt.NDArray[np.floating] | float,
|
|
232
|
+
engine_efficiency: npt.NDArray[np.floating] | float | None,
|
|
233
|
+
fuel_flow: npt.NDArray[np.floating] | float | None,
|
|
234
|
+
thrust: npt.NDArray[np.floating] | float | None,
|
|
238
235
|
q_fuel: float,
|
|
239
236
|
**kwargs: Any,
|
|
240
237
|
) -> AircraftPerformanceData:
|
|
@@ -269,8 +266,8 @@ class PSFlight(AircraftPerformance):
|
|
|
269
266
|
rn = reynolds_number(atyp_param.wing_surface_area, mach_num, air_temperature, air_pressure)
|
|
270
267
|
|
|
271
268
|
# Allow array or None time
|
|
272
|
-
dv_dt: npt.NDArray[np.
|
|
273
|
-
theta: npt.NDArray[np.
|
|
269
|
+
dv_dt: npt.NDArray[np.floating] | float
|
|
270
|
+
theta: npt.NDArray[np.floating] | float
|
|
274
271
|
if time is None:
|
|
275
272
|
# Assume a nominal cruising state
|
|
276
273
|
dt_sec = None
|
|
@@ -783,7 +780,7 @@ def overall_propulsion_efficiency(
|
|
|
783
780
|
c_t_eta_b: ArrayOrFloat,
|
|
784
781
|
atyp_param: PSAircraftEngineParams,
|
|
785
782
|
eta_over_eta_b_min: float | None = None,
|
|
786
|
-
) -> npt.NDArray[np.
|
|
783
|
+
) -> npt.NDArray[np.floating]:
|
|
787
784
|
"""Calculate overall propulsion efficiency.
|
|
788
785
|
|
|
789
786
|
Parameters
|
|
@@ -803,7 +800,7 @@ def overall_propulsion_efficiency(
|
|
|
803
800
|
|
|
804
801
|
Returns
|
|
805
802
|
-------
|
|
806
|
-
npt.NDArray[np.
|
|
803
|
+
npt.NDArray[np.floating]
|
|
807
804
|
Overall propulsion efficiency
|
|
808
805
|
"""
|
|
809
806
|
eta_over_eta_b = propulsion_efficiency_over_max_propulsion_efficiency(mach_num, c_t, c_t_eta_b)
|
|
@@ -819,7 +816,7 @@ def propulsion_efficiency_over_max_propulsion_efficiency(
|
|
|
819
816
|
mach_num: ArrayOrFloat,
|
|
820
817
|
c_t: ArrayOrFloat,
|
|
821
818
|
c_t_eta_b: ArrayOrFloat,
|
|
822
|
-
) -> npt.NDArray[np.
|
|
819
|
+
) -> npt.NDArray[np.floating]:
|
|
823
820
|
"""Calculate ratio of OPE to maximum OPE that can be attained for a given Mach number.
|
|
824
821
|
|
|
825
822
|
Parameters
|
|
@@ -833,7 +830,7 @@ def propulsion_efficiency_over_max_propulsion_efficiency(
|
|
|
833
830
|
|
|
834
831
|
Returns
|
|
835
832
|
-------
|
|
836
|
-
npt.NDArray[np.
|
|
833
|
+
npt.NDArray[np.floating]
|
|
837
834
|
Ratio of OPE to maximum OPE, ``eta / eta_b``
|
|
838
835
|
|
|
839
836
|
Notes
|
|
@@ -843,7 +840,7 @@ def propulsion_efficiency_over_max_propulsion_efficiency(
|
|
|
843
840
|
"""
|
|
844
841
|
c_t_over_c_t_eta_b = c_t / c_t_eta_b
|
|
845
842
|
|
|
846
|
-
sigma = np.where(mach_num < 0.4, 1.3 * (0.4 - mach_num), 0.0)
|
|
843
|
+
sigma = np.where(mach_num < 0.4, 1.3 * (0.4 - mach_num), np.float32(0.0)) # avoid promotion
|
|
847
844
|
|
|
848
845
|
eta_over_eta_b_low = (
|
|
849
846
|
10.0 * (1.0 + 0.8 * (sigma - 0.43) - 0.6027 * sigma * 0.43) * c_t_over_c_t_eta_b
|