pycontrails 0.54.3__cp313-cp313-win_amd64.whl → 0.54.5__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.

Files changed (62) 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 +58 -58
  5. pycontrails/core/cache.py +7 -7
  6. pycontrails/core/fleet.py +54 -29
  7. pycontrails/core/flight.py +218 -301
  8. pycontrails/core/interpolation.py +63 -60
  9. pycontrails/core/met.py +193 -125
  10. pycontrails/core/models.py +27 -13
  11. pycontrails/core/polygon.py +15 -15
  12. pycontrails/core/rgi_cython.cp313-win_amd64.pyd +0 -0
  13. pycontrails/core/vector.py +119 -96
  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/gfs/__init__.py +6 -6
  21. pycontrails/datalib/gfs/gfs.py +2 -2
  22. pycontrails/datalib/goes.py +5 -5
  23. pycontrails/ext/empirical_grid.py +1 -1
  24. pycontrails/models/apcemm/apcemm.py +5 -5
  25. pycontrails/models/apcemm/utils.py +1 -1
  26. pycontrails/models/cocip/__init__.py +2 -2
  27. pycontrails/models/cocip/cocip.py +23 -24
  28. pycontrails/models/cocip/cocip_params.py +2 -11
  29. pycontrails/models/cocip/cocip_uncertainty.py +24 -18
  30. pycontrails/models/cocip/contrail_properties.py +331 -316
  31. pycontrails/models/cocip/output_formats.py +53 -53
  32. pycontrails/models/cocip/radiative_forcing.py +135 -131
  33. pycontrails/models/cocip/radiative_heating.py +135 -135
  34. pycontrails/models/cocip/unterstrasser_wake_vortex.py +90 -87
  35. pycontrails/models/cocip/wake_vortex.py +92 -92
  36. pycontrails/models/cocip/wind_shear.py +8 -8
  37. pycontrails/models/cocipgrid/cocip_grid.py +37 -96
  38. pycontrails/models/dry_advection.py +60 -19
  39. pycontrails/models/emissions/__init__.py +2 -2
  40. pycontrails/models/emissions/black_carbon.py +108 -108
  41. pycontrails/models/emissions/emissions.py +87 -87
  42. pycontrails/models/emissions/ffm2.py +35 -35
  43. pycontrails/models/humidity_scaling/humidity_scaling.py +23 -23
  44. pycontrails/models/issr.py +2 -2
  45. pycontrails/models/ps_model/__init__.py +1 -1
  46. pycontrails/models/ps_model/ps_aircraft_params.py +8 -4
  47. pycontrails/models/ps_model/ps_grid.py +76 -66
  48. pycontrails/models/ps_model/ps_model.py +16 -16
  49. pycontrails/models/ps_model/ps_operational_limits.py +20 -18
  50. pycontrails/models/tau_cirrus.py +8 -1
  51. pycontrails/physics/geo.py +67 -67
  52. pycontrails/physics/jet.py +79 -79
  53. pycontrails/physics/units.py +14 -14
  54. pycontrails/utils/json.py +1 -2
  55. pycontrails/utils/types.py +12 -7
  56. {pycontrails-0.54.3.dist-info → pycontrails-0.54.5.dist-info}/METADATA +2 -2
  57. {pycontrails-0.54.3.dist-info → pycontrails-0.54.5.dist-info}/NOTICE +1 -1
  58. pycontrails-0.54.5.dist-info/RECORD +111 -0
  59. pycontrails-0.54.3.dist-info/RECORD +0 -111
  60. {pycontrails-0.54.3.dist-info → pycontrails-0.54.5.dist-info}/LICENSE +0 -0
  61. {pycontrails-0.54.3.dist-info → pycontrails-0.54.5.dist-info}/WHEEL +0 -0
  62. {pycontrails-0.54.3.dist-info → pycontrails-0.54.5.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ import numpy as np
10
10
  import pycontrails
11
11
  from pycontrails.core.flight import Flight
12
12
  from pycontrails.core.met import MetDataset
13
- from pycontrails.core.met_var import AirTemperature, SpecificHumidity
13
+ from pycontrails.core.met_var import AirTemperature, MetVariable, SpecificHumidity
14
14
  from pycontrails.core.models import Model, ModelParams
15
15
  from pycontrails.core.vector import GeoVectorDataset
16
16
  from pycontrails.models.humidity_scaling import HumidityScaling
@@ -70,7 +70,7 @@ class ISSR(Model):
70
70
 
71
71
  name = "issr"
72
72
  long_name = "Ice super-saturated regions"
73
- met_variables = AirTemperature, SpecificHumidity
73
+ met_variables: tuple[MetVariable, ...] = AirTemperature, SpecificHumidity
74
74
  default_params = ISSRParams
75
75
 
76
76
  @overload
@@ -8,9 +8,9 @@ from pycontrails.models.ps_model.ps_grid import PSGrid, ps_nominal_grid, ps_nomi
8
8
  from pycontrails.models.ps_model.ps_model import PSFlight, PSFlightParams
9
9
 
10
10
  __all__ = [
11
+ "PSAircraftEngineParams",
11
12
  "PSFlight",
12
13
  "PSFlightParams",
13
- "PSAircraftEngineParams",
14
14
  "PSGrid",
15
15
  "load_aircraft_engine_params",
16
16
  "ps_nominal_grid",
@@ -13,6 +13,7 @@ import pandas as pd
13
13
 
14
14
  from pycontrails.core.aircraft_performance import AircraftPerformanceParams
15
15
  from pycontrails.physics import constants as c
16
+ from pycontrails.utils.types import ArrayOrFloat
16
17
 
17
18
  #: Path to the Poll-Schumann aircraft parameters CSV file.
18
19
  PS_FILE_PATH = pathlib.Path(__file__).parent / "static" / "ps-aircraft-params-20240524.csv"
@@ -260,18 +261,18 @@ def load_aircraft_engine_params(
260
261
  return dict(_row_to_aircraft_engine_params(tup) for tup in df.itertuples(index=False))
261
262
 
262
263
 
263
- def turbine_entry_temperature_at_max_take_off(first_flight: float) -> float:
264
+ def turbine_entry_temperature_at_max_take_off(first_flight: ArrayOrFloat) -> ArrayOrFloat:
264
265
  """
265
266
  Calculate turbine entry temperature at maximum take-off rating.
266
267
 
267
268
  Parameters
268
269
  ----------
269
- first_flight: float
270
+ first_flight: ArrayOrFloat
270
271
  Year of first flight
271
272
 
272
273
  Returns
273
274
  -------
274
- float
275
+ ArrayOrFloat
275
276
  Turbine entry temperature at maximum take-off rating, ``tet_mto``, [:math:`K`]
276
277
 
277
278
  Notes
@@ -284,7 +285,10 @@ def turbine_entry_temperature_at_max_take_off(first_flight: float) -> float:
284
285
  ----------
285
286
  - :cite:`cumpstyJetPropulsion2015`
286
287
  """
287
- return 2000.0 * (1 - np.exp(62.8 - 0.0325 * first_flight))
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()
288
292
 
289
293
 
290
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,
@@ -20,7 +19,7 @@ from pycontrails.core.aircraft_performance import (
20
19
  from pycontrails.core.flight import Flight
21
20
  from pycontrails.core.fuel import JetA
22
21
  from pycontrails.core.met import MetDataset
23
- from pycontrails.core.met_var import AirTemperature
22
+ from pycontrails.core.met_var import AirTemperature, MetVariable
24
23
  from pycontrails.core.vector import GeoVectorDataset
25
24
  from pycontrails.models.ps_model import ps_model, ps_operational_limits
26
25
  from pycontrails.models.ps_model.ps_aircraft_params import PSAircraftEngineParams
@@ -59,7 +58,7 @@ class PSGrid(AircraftPerformanceGrid):
59
58
 
60
59
  name = "PSGrid"
61
60
  long_name = "Poll-Schumann Aircraft Performance evaluated at arbitrary points"
62
- met_variables = (AirTemperature,)
61
+ met_variables: tuple[MetVariable, ...] = (AirTemperature,)
63
62
  default_params = PSGridParams
64
63
 
65
64
  met: MetDataset
@@ -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.float64] | float
182
- air_temperature: npt.NDArray[np.float64] | float
183
- mach_number: npt.NDArray[np.float64] | float
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
- theta = 0.0
197
- dv_dt = 0.0
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.float64], npt.NDArray[np.float64]]:
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,40 +297,53 @@ def _estimate_mass_extremes(
296
297
 
297
298
 
298
299
  def _parse_variables(
299
- level: npt.NDArray[np.float64] | None,
300
- air_temperature: xr.DataArray | npt.NDArray[np.float64] | None,
301
- ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
302
- """Parse the level and air temperature arguments."""
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
- level_da = air_temperature["level"]
310
- air_temperature, level_da = xr.broadcast(air_temperature, level_da)
311
- return np.asarray(level_da), np.asarray(air_temperature)
312
-
313
- if air_temperature is None:
314
- if level is None:
315
- msg = "The 'level' argument must be specified"
316
- raise ValueError(msg)
317
- altitude_m = units.pl_to_m(level)
318
- air_temperature = units.m_to_T_isa(altitude_m)
319
- return level, air_temperature
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 specified"
332
+ msg = "The 'level' argument must be provided"
323
333
  raise ValueError(msg)
324
334
 
325
- return level, air_temperature
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.float64] | None = None,
332
- air_temperature: xr.DataArray | npt.NDArray[np.float64] | None = None,
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,
@@ -345,13 +359,13 @@ def ps_nominal_grid(
345
359
  ----------
346
360
  aircraft_type : str
347
361
  The aircraft type.
348
- level : npt.NDArray[np.float64] | None, optional
362
+ level : npt.NDArray[np.floating] | None, optional
349
363
  The pressure level, [:math:`hPa`]. If None, the ``air_temperature``
350
- argument must be a :class:`xarray.DataArray` with a ``level`` coordinate.
351
- air_temperature : xr.DataArray | npt.NDArray[np.float64] | None, optional
364
+ argument must be a :class:`xarray.DataArray` with an ``air_pressure`` coordinate.
365
+ air_temperature : xr.DataArray | npt.NDArray[np.floating] | None, optional
352
366
  The ambient air temperature, [:math:`K`]. If None (default), the ISA
353
367
  temperature is computed from the ``level`` argument. If a :class:`xarray.DataArray`,
354
- the ``level`` coordinate must be present and the ``level`` argument must be None
368
+ an ``air_pressure`` coordinate must be present and the ``level`` argument must be None
355
369
  to avoid ambiguity. If a :class:`numpy.ndarray` is passed, it is assumed to be 1
356
370
  dimensional with the same shape as the ``level`` argument.
357
371
  q_fuel : float, optional
@@ -380,6 +394,10 @@ def ps_nominal_grid(
380
394
  KeyError
381
395
  If "aircraft_type" is not supported by the PS model.
382
396
 
397
+ See Also
398
+ --------
399
+ ps_nominal_optimize_mach
400
+
383
401
  Examples
384
402
  --------
385
403
  >>> level = np.arange(200, 300, 10, dtype=float)
@@ -393,16 +411,16 @@ def ps_nominal_grid(
393
411
  >>> perf.to_dataframe()
394
412
  aircraft_mass engine_efficiency fuel_flow
395
413
  level
396
- 200.0 58416.230843 0.300958 0.575635
414
+ 200.0 58416.230844 0.300958 0.575635
397
415
  210.0 61617.676624 0.300958 0.604417
398
- 220.0 64829.702583 0.300958 0.633199
399
- 230.0 68026.415695 0.300958 0.662998
416
+ 220.0 64829.702584 0.300958 0.633199
417
+ 230.0 68026.415694 0.300958 0.662998
400
418
  240.0 71187.897060 0.300958 0.694631
401
- 250.0 71775.399825 0.300824 0.703349
402
- 260.0 71765.716737 0.300363 0.708259
403
- 270.0 71752.405400 0.299671 0.714514
404
- 280.0 71736.129079 0.298823 0.721878
405
- 290.0 71717.392170 0.297875 0.730169
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
406
424
 
407
425
  >>> # Now compute it for a higher Mach number
408
426
  >>> perf = ps_nominal_grid("A320", level=level, mach_number=0.78)
@@ -411,26 +429,16 @@ def ps_nominal_grid(
411
429
  level
412
430
  200.0 57941.825236 0.306598 0.596100
413
431
  210.0 60626.062062 0.306605 0.621331
414
- 220.0 63818.498306 0.306605 0.650918
415
- 230.0 66993.691517 0.306605 0.681551
416
- 240.0 70129.930503 0.306605 0.714069
417
- 250.0 71703.009059 0.306560 0.732944
418
- 260.0 71690.188652 0.306239 0.739276
419
- 270.0 71673.392089 0.305694 0.747052
420
- 280.0 71653.431321 0.304997 0.755990
421
- 290.0 71630.901315 0.304201 0.765883
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
422
440
  """
423
- coords: dict[str, Any] | xrcc.DataArrayCoordinates
424
- if isinstance(air_temperature, xr.DataArray):
425
- dims = air_temperature.dims
426
- coords = air_temperature.coords
427
- else:
428
- dims = ("level",)
429
- coords = {"level": level}
430
-
431
- level, air_temperature = _parse_variables(level, air_temperature)
432
-
433
- air_pressure = level * 100.0
441
+ dims, coords, air_pressure, air_temperature = _parse_variables(level, air_temperature)
434
442
 
435
443
  aircraft_engine_params = ps_model.load_aircraft_engine_params(engine_deterioration_factor)
436
444
 
@@ -475,7 +483,7 @@ def ps_nominal_grid(
475
483
  func=_newton_func,
476
484
  args=(perf,),
477
485
  x0=x0,
478
- tol=1.0,
486
+ tol=80.0, # use roughly the weight of a passenger as a tolerance
479
487
  disp=False,
480
488
  maxiter=maxiter,
481
489
  )
@@ -551,7 +559,7 @@ def ps_nominal_optimize_mach(
551
559
  """Calculate the nominal optimal mach number for a given aircraft type.
552
560
 
553
561
  This function is similar to the :class:`ps_nominal_grid` method, but rather than
554
- maximizing engine efficiecy by adjusting aircraft, we are minimizing cost by adjusting
562
+ maximizing engine efficiency by adjusting aircraft, we are minimizing cost by adjusting
555
563
  mach number.
556
564
 
557
565
  Parameters
@@ -598,8 +606,7 @@ def ps_nominal_optimize_mach(
598
606
  - ``"mach_number"``: The mach number that minimizes segment cost
599
607
  - ``"fuel_flow"`` : Fuel flow rate, [:math:`kg/s`]
600
608
  - ``"engine_efficiency"`` : Engine efficiency
601
- - ``"aircraft_mass"`` : Aircraft mass,
602
- [:math:`kg`]
609
+ - ``"aircraft_mass"`` : Aircraft mass, [:math:`kg`]
603
610
 
604
611
  Raises
605
612
  ------
@@ -607,6 +614,10 @@ def ps_nominal_optimize_mach(
607
614
  If "aircraft_type" is not supported by the PS model.
608
615
  ValueError
609
616
  If wind data is provided without segment angles.
617
+
618
+ See Also
619
+ --------
620
+ ps_nominal_grid
610
621
  """
611
622
  dims = ("level",)
612
623
  coords = {"level": level}
@@ -624,12 +635,11 @@ def ps_nominal_optimize_mach(
624
635
  altitude_m = units.pl_to_m(level)
625
636
  air_temperature = units.m_to_T_isa(altitude_m)
626
637
 
627
- headwind: ArrayOrFloat
628
638
  if northward_wind is not None and eastward_wind is not None:
629
639
  if sin_a is None or cos_a is None:
630
640
  msg = "Segment angles must be provide if wind data is specified"
631
641
  raise ValueError(msg)
632
- headwind = -(northward_wind * cos_a + eastward_wind * sin_a)
642
+ headwind = -(northward_wind * cos_a + eastward_wind * sin_a) # type: ignore[misc]
633
643
  else:
634
644
  headwind = 0.0 # type: ignore
635
645
 
@@ -28,7 +28,7 @@ from pycontrails.core.aircraft_performance import (
28
28
  from pycontrails.core.fleet import Fleet
29
29
  from pycontrails.core.flight import Flight
30
30
  from pycontrails.core.met import MetDataset
31
- from pycontrails.core.met_var import AirTemperature, EastwardWind, NorthwardWind
31
+ from pycontrails.core.met_var import AirTemperature, EastwardWind, MetVariable, NorthwardWind
32
32
  from pycontrails.models.ps_model import ps_operational_limits as ps_lims
33
33
  from pycontrails.models.ps_model.ps_aircraft_params import (
34
34
  PSAircraftEngineParams,
@@ -71,7 +71,7 @@ class PSFlight(AircraftPerformance):
71
71
 
72
72
  name = "PSFlight"
73
73
  long_name = "Poll-Schumann Aircraft Performance Model"
74
- met_variables = (AirTemperature,)
74
+ met_variables: tuple[MetVariable, ...] = (AirTemperature,)
75
75
  optional_met_variables = EastwardWind, NorthwardWind
76
76
  default_params = PSFlightParams
77
77
 
@@ -224,14 +224,14 @@ class PSFlight(AircraftPerformance):
224
224
  self,
225
225
  *,
226
226
  aircraft_type: str,
227
- altitude_ft: npt.NDArray[np.float64],
228
- air_temperature: npt.NDArray[np.float64],
227
+ altitude_ft: npt.NDArray[np.floating],
228
+ air_temperature: npt.NDArray[np.floating],
229
229
  time: npt.NDArray[np.datetime64] | None,
230
- true_airspeed: npt.NDArray[np.float64] | float | None,
231
- aircraft_mass: npt.NDArray[np.float64] | float,
232
- engine_efficiency: npt.NDArray[np.float64] | float | None,
233
- fuel_flow: npt.NDArray[np.float64] | float | None,
234
- thrust: npt.NDArray[np.float64] | float | None,
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,
235
235
  q_fuel: float,
236
236
  **kwargs: Any,
237
237
  ) -> AircraftPerformanceData:
@@ -266,8 +266,8 @@ class PSFlight(AircraftPerformance):
266
266
  rn = reynolds_number(atyp_param.wing_surface_area, mach_num, air_temperature, air_pressure)
267
267
 
268
268
  # Allow array or None time
269
- dv_dt: npt.NDArray[np.float64] | float
270
- theta: npt.NDArray[np.float64] | float
269
+ dv_dt: npt.NDArray[np.floating] | float
270
+ theta: npt.NDArray[np.floating] | float
271
271
  if time is None:
272
272
  # Assume a nominal cruising state
273
273
  dt_sec = None
@@ -780,7 +780,7 @@ def overall_propulsion_efficiency(
780
780
  c_t_eta_b: ArrayOrFloat,
781
781
  atyp_param: PSAircraftEngineParams,
782
782
  eta_over_eta_b_min: float | None = None,
783
- ) -> npt.NDArray[np.float64]:
783
+ ) -> npt.NDArray[np.floating]:
784
784
  """Calculate overall propulsion efficiency.
785
785
 
786
786
  Parameters
@@ -800,7 +800,7 @@ def overall_propulsion_efficiency(
800
800
 
801
801
  Returns
802
802
  -------
803
- npt.NDArray[np.float64]
803
+ npt.NDArray[np.floating]
804
804
  Overall propulsion efficiency
805
805
  """
806
806
  eta_over_eta_b = propulsion_efficiency_over_max_propulsion_efficiency(mach_num, c_t, c_t_eta_b)
@@ -816,7 +816,7 @@ def propulsion_efficiency_over_max_propulsion_efficiency(
816
816
  mach_num: ArrayOrFloat,
817
817
  c_t: ArrayOrFloat,
818
818
  c_t_eta_b: ArrayOrFloat,
819
- ) -> npt.NDArray[np.float64]:
819
+ ) -> npt.NDArray[np.floating]:
820
820
  """Calculate ratio of OPE to maximum OPE that can be attained for a given Mach number.
821
821
 
822
822
  Parameters
@@ -830,7 +830,7 @@ def propulsion_efficiency_over_max_propulsion_efficiency(
830
830
 
831
831
  Returns
832
832
  -------
833
- npt.NDArray[np.float64]
833
+ npt.NDArray[np.floating]
834
834
  Ratio of OPE to maximum OPE, ``eta / eta_b``
835
835
 
836
836
  Notes
@@ -840,7 +840,7 @@ def propulsion_efficiency_over_max_propulsion_efficiency(
840
840
  """
841
841
  c_t_over_c_t_eta_b = c_t / c_t_eta_b
842
842
 
843
- 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
844
844
 
845
845
  eta_over_eta_b_low = (
846
846
  10.0 * (1.0 + 0.8 * (sigma - 0.43) - 0.6027 * sigma * 0.43) * c_t_over_c_t_eta_b
@@ -155,34 +155,34 @@ def max_available_thrust_coefficient(
155
155
 
156
156
 
157
157
  def get_excess_thrust_available(
158
- mach_number: float | npt.NDArray[np.float64],
159
- air_temperature: float | npt.NDArray[np.float64],
160
- air_pressure: float | npt.NDArray[np.float64],
161
- aircraft_mass: float | npt.NDArray[np.float64],
162
- theta: float | npt.NDArray[np.float64],
158
+ mach_number: ArrayOrFloat,
159
+ air_temperature: ArrayOrFloat,
160
+ air_pressure: ArrayOrFloat,
161
+ aircraft_mass: ArrayOrFloat,
162
+ theta: ArrayOrFloat,
163
163
  atyp_param: PSAircraftEngineParams,
164
- ) -> float | npt.NDArray[np.float64]:
164
+ ) -> ArrayOrFloat:
165
165
  r"""
166
166
  Calculate the excess thrust coefficient available at specified operation condition.
167
167
 
168
168
  Parameters
169
169
  ----------
170
- mach_number : float | npt.NDArray[np.float64]
170
+ mach_number : ArrayOrFloat
171
171
  Mach number at each waypoint
172
- air_temperature : float | npt.NDArray[np.float64]
172
+ air_temperature : ArrayOrFloat
173
173
  Ambient temperature at each waypoint, [:math:`K`]
174
- air_pressure : float | npt.NDArray[np.float64]
174
+ air_pressure : ArrayOrFloat
175
175
  Ambient pressure, [:math:`Pa`]
176
- aircraft_mass : float | npt.NDArray[np.float64]
176
+ aircraft_mass : ArrayOrFloat
177
177
  Aircraft mass at each waypoint, [:math:`kg`]
178
- theta : float | npt.NDArray[np.float64]
178
+ theta : ArrayOrFloat
179
179
  Climb (positive value) or descent (negative value) angle, [:math:`\deg`]
180
180
  atyp_param : PSAircraftEngineParams
181
181
  Extracted aircraft and engine parameters.
182
182
 
183
183
  Returns
184
184
  -------
185
- float | npt.NDArray[np.float64]
185
+ ArrayOrFloat
186
186
  The difference between the maximum rated thrust coefficient and the thrust coefficient
187
187
  required to maintain the current mach_number.
188
188
  """
@@ -217,7 +217,7 @@ def get_excess_thrust_available(
217
217
  )
218
218
 
219
219
  tas = units.mach_number_to_tas(mach_number, air_temperature)
220
- req_thrust_coeff = required_thrust_coefficient(c_lift, c_drag, tas)
220
+ req_thrust_coeff = required_thrust_coefficient(c_lift, c_drag, tas) # type: ignore[type-var]
221
221
 
222
222
  c_t_eta_b = thrust_coefficient_at_max_efficiency(
223
223
  mach_number, atyp_param.m_des, atyp_param.c_t_des
@@ -226,7 +226,7 @@ def get_excess_thrust_available(
226
226
  air_temperature, mach_number, c_t_eta_b, atyp_param
227
227
  )
228
228
 
229
- return max_thrust_coeff - req_thrust_coeff
229
+ return max_thrust_coeff - req_thrust_coeff # type: ignore[return-value]
230
230
 
231
231
 
232
232
  def _normalised_max_throttle_parameter(
@@ -466,7 +466,9 @@ def maximum_mach_num(
466
466
  # ----------------
467
467
 
468
468
 
469
- def fuel_flow_idle(fuel_flow_idle_sls: float, altitude_ft: ArrayOrFloat) -> npt.NDArray[np.float64]:
469
+ def fuel_flow_idle(
470
+ fuel_flow_idle_sls: float, altitude_ft: ArrayOrFloat
471
+ ) -> npt.NDArray[np.floating]:
470
472
  r"""Calculate minimum fuel mass flow rate at flight idle conditions.
471
473
 
472
474
  Parameters
@@ -478,7 +480,7 @@ def fuel_flow_idle(fuel_flow_idle_sls: float, altitude_ft: ArrayOrFloat) -> npt.
478
480
 
479
481
  Returns
480
482
  -------
481
- npt.NDArray[np.float64]
483
+ npt.NDArray[np.floating]
482
484
  Fuel mass flow rate at flight idle conditions, [:math:`kg \ s^{-1}`]
483
485
  """
484
486
  x = altitude_ft / 10000.0
@@ -491,7 +493,7 @@ def max_fuel_flow(
491
493
  mach_number: ArrayOrFloat,
492
494
  fuel_flow_max_sls: float,
493
495
  flight_phase: npt.NDArray[np.uint8] | flight.FlightPhase,
494
- ) -> npt.NDArray[np.float64]:
496
+ ) -> npt.NDArray[np.floating]:
495
497
  r"""Correct maximum fuel mass flow rate that can be supplied by the engine.
496
498
 
497
499
  Parameters
@@ -509,7 +511,7 @@ def max_fuel_flow(
509
511
 
510
512
  Returns
511
513
  -------
512
- npt.NDArray[np.float64]
514
+ npt.NDArray[np.floating]
513
515
  Maximum allowable fuel mass flow rate, [:math:`kg \ s^{-1}`]
514
516
  """
515
517
  ff_max = jet.equivalent_fuel_flow_rate_at_cruise(
@@ -80,7 +80,14 @@ def tau_cirrus(met: MetDataset) -> xr.DataArray:
80
80
  met.data["air_pressure"],
81
81
  )
82
82
 
83
- dz = -dask.array.gradient(geopotential_height, axis=geopotential_height.get_axis_num("level"))
83
+ # dask.array.gradient expects at least 2 elements in each chunk
84
+ level_axis = geopotential_height.get_axis_num("level")
85
+ if geopotential_height.chunks:
86
+ level_chunks = geopotential_height.chunks[level_axis] # type: ignore[call-overload, index]
87
+ if any(chunk < 2 for chunk in level_chunks):
88
+ geopotential_height = geopotential_height.chunk(level=-1)
89
+
90
+ dz = -dask.array.gradient(geopotential_height, axis=level_axis)
84
91
  dz = xr.DataArray(dz, dims=geopotential_height.dims)
85
92
 
86
93
  da = beta_e * dz