pycontrails 0.52.3__cp312-cp312-macosx_10_9_x86_64.whl → 0.53.1__cp312-cp312-macosx_10_9_x86_64.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 (37) hide show
  1. pycontrails/_version.py +2 -2
  2. pycontrails/core/cache.py +1 -1
  3. pycontrails/core/flight.py +8 -5
  4. pycontrails/core/flightplan.py +1 -1
  5. pycontrails/core/interpolation.py +1 -0
  6. pycontrails/core/met.py +11 -13
  7. pycontrails/core/met_var.py +1 -1
  8. pycontrails/core/models.py +5 -5
  9. pycontrails/core/rgi_cython.cpython-312-darwin.so +0 -0
  10. pycontrails/core/vector.py +5 -5
  11. pycontrails/datalib/_leo_utils/vis.py +10 -11
  12. pycontrails/datalib/_met_utils/metsource.py +20 -15
  13. pycontrails/datalib/ecmwf/common.py +1 -1
  14. pycontrails/datalib/ecmwf/era5.py +21 -10
  15. pycontrails/datalib/ecmwf/era5_model_level.py +15 -8
  16. pycontrails/datalib/ecmwf/hres_model_level.py +3 -3
  17. pycontrails/datalib/ecmwf/variables.py +3 -3
  18. pycontrails/datalib/gfs/gfs.py +4 -3
  19. pycontrails/datalib/landsat.py +10 -9
  20. pycontrails/ext/synthetic_flight.py +1 -1
  21. pycontrails/models/accf.py +1 -1
  22. pycontrails/models/apcemm/apcemm.py +5 -5
  23. pycontrails/models/cocip/cocip.py +3 -3
  24. pycontrails/models/cocip/output_formats.py +2 -2
  25. pycontrails/models/cocip/radiative_forcing.py +3 -3
  26. pycontrails/models/cocipgrid/cocip_grid.py +8 -8
  27. pycontrails/models/ps_model/ps_model.py +4 -4
  28. pycontrails/models/sac.py +2 -2
  29. pycontrails/physics/thermo.py +1 -1
  30. pycontrails/utils/json.py +16 -18
  31. pycontrails/utils/types.py +7 -6
  32. {pycontrails-0.52.3.dist-info → pycontrails-0.53.1.dist-info}/METADATA +6 -6
  33. {pycontrails-0.52.3.dist-info → pycontrails-0.53.1.dist-info}/RECORD +37 -37
  34. {pycontrails-0.52.3.dist-info → pycontrails-0.53.1.dist-info}/WHEEL +1 -1
  35. {pycontrails-0.52.3.dist-info → pycontrails-0.53.1.dist-info}/LICENSE +0 -0
  36. {pycontrails-0.52.3.dist-info → pycontrails-0.53.1.dist-info}/NOTICE +0 -0
  37. {pycontrails-0.52.3.dist-info → pycontrails-0.53.1.dist-info}/top_level.txt +0 -0
pycontrails/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.52.3'
16
- __version_tuple__ = version_tuple = (0, 52, 3)
15
+ __version__ = version = '0.53.1'
16
+ __version_tuple__ = version_tuple = (0, 53, 1)
pycontrails/core/cache.py CHANGED
@@ -146,7 +146,7 @@ class CacheStore(ABC):
146
146
  """
147
147
 
148
148
  # TODO: run in parallel?
149
- return [self.put(d, cp) for d, cp in zip(data_path, cache_path)]
149
+ return [self.put(d, cp) for d, cp in zip(data_path, cache_path, strict=True)]
150
150
 
151
151
  # In the three methods below, child classes have a complete docstring.
152
152
 
@@ -1356,7 +1356,7 @@ class Flight(GeoVectorDataset):
1356
1356
  # NOTE: geod.npts does not return the initial or terminal points
1357
1357
  lonlats: list[tuple[float, float]] = geod.npts(lon0, lat0, lon1, lat1, n_steps)
1358
1358
 
1359
- lons, lats = zip(*lonlats)
1359
+ lons, lats = zip(*lonlats, strict=True)
1360
1360
  longitudes.extend(lons)
1361
1361
  latitudes.extend(lats)
1362
1362
 
@@ -1657,10 +1657,11 @@ def _return_linestring(data: dict[str, npt.NDArray[np.float64]]) -> list[list[fl
1657
1657
  The list of coordinates
1658
1658
  """
1659
1659
  # rounding to reduce the size of resultant json arrays
1660
- points = zip( # pylint: disable=zip-builtin-not-iterating
1660
+ points = zip(
1661
1661
  np.round(data["longitude"], decimals=4),
1662
1662
  np.round(data["latitude"], decimals=4),
1663
1663
  np.round(data["altitude"], decimals=4),
1664
+ strict=True,
1664
1665
  )
1665
1666
  return [list(p) for p in points]
1666
1667
 
@@ -1949,7 +1950,9 @@ def _altitude_interpolation_climb_descend_middle(
1949
1950
  # Form array of cumulative altitude values if the flight were to climb
1950
1951
  # at nominal_rocd over each group of nan
1951
1952
  cumalt_list = []
1952
- for start_na_idx, end_na_idx, size in zip(start_na_idxs, end_na_idxs, na_group_size):
1953
+ for start_na_idx, end_na_idx, size in zip(
1954
+ start_na_idxs, end_na_idxs, na_group_size, strict=True
1955
+ ):
1953
1956
  if s[start_na_idx] <= s[end_na_idx]:
1954
1957
  cumalt_list.append(np.arange(1, size, dtype=float))
1955
1958
  else:
@@ -2053,7 +2056,7 @@ def filter_altitude(
2053
2056
  --------
2054
2057
  :meth:`traffic.core.flight.Flight.filter`
2055
2058
  :func:`scipy.signal.medfilt`
2056
- """ # noqa: E501
2059
+ """
2057
2060
  if not len(altitude_ft):
2058
2061
  raise ValueError("Altitude must have non-zero length to filter")
2059
2062
 
@@ -2114,7 +2117,7 @@ def filter_altitude(
2114
2117
 
2115
2118
  result = np.copy(altitude_ft)
2116
2119
  if np.any(start_idxs):
2117
- for i0, i1 in zip(start_idxs, end_idxs):
2120
+ for i0, i1 in zip(start_idxs, end_idxs, strict=True):
2118
2121
  result[i0:i1] = altitude_filt[i0:i1]
2119
2122
 
2120
2123
  # reapply Savitzky-Golay filter to smooth climb and descent
@@ -93,7 +93,7 @@ def parse_atc_plan(atc_plan: str) -> dict[str, str]:
93
93
  See Also
94
94
  --------
95
95
  :func:`to_atc_plan`
96
- """ # noqa: E501
96
+ """
97
97
  atc_plan = atc_plan.replace("\r", "")
98
98
  atc_plan = atc_plan.replace("\n", "")
99
99
  atc_plan = atc_plan.upper()
@@ -76,6 +76,7 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
76
76
  self.method = _pick_method(scipy.__version__, method)
77
77
  self.bounds_error = bounds_error
78
78
  self.fill_value = fill_value
79
+ self._spline = None
79
80
 
80
81
  def _prepare_xi_simple(self, xi: npt.NDArray[np.float64]) -> npt.NDArray[np.bool_]:
81
82
  """Run looser version of :meth:`_prepare_xi`.
pycontrails/core/met.py CHANGED
@@ -70,12 +70,12 @@ class MetBase(ABC, Generic[XArrayType]):
70
70
  cachestore: CacheStore | None
71
71
 
72
72
  #: Default dimension order for DataArray or Dataset (x, y, z, t)
73
- dim_order: list[Hashable] = [
73
+ dim_order: tuple[Hashable, Hashable, Hashable, Hashable] = (
74
74
  "longitude",
75
75
  "latitude",
76
76
  "level",
77
77
  "time",
78
- ]
78
+ )
79
79
 
80
80
  def __repr__(self) -> str:
81
81
  data = getattr(self, "data", None)
@@ -200,10 +200,8 @@ class MetBase(ABC, Generic[XArrayType]):
200
200
  def _validate_transpose(self) -> None:
201
201
  """Check that data is transposed according to :attr:`dim_order`."""
202
202
 
203
- dims_tuple = tuple(self.dim_order)
204
-
205
203
  def _check_da(da: xr.DataArray, key: Hashable | None = None) -> None:
206
- if da.dims != dims_tuple:
204
+ if da.dims != self.dim_order:
207
205
  if key is not None:
208
206
  msg = (
209
207
  f"Data dimension not transposed on variable '{key}'. Initiate with"
@@ -271,7 +269,7 @@ class MetBase(ABC, Generic[XArrayType]):
271
269
  self.data["time"] = self.data["time"].astype("datetime64[ns]", copy=False)
272
270
 
273
271
  # sortby to ensure each coordinate has ascending order
274
- self.data = self.data.sortby(self.dim_order, ascending=True)
272
+ self.data = self.data.sortby(list(self.dim_order), ascending=True)
275
273
 
276
274
  if not self.is_wrapped:
277
275
  # Ensure longitude is contained in interval [-180, 180)
@@ -293,7 +291,7 @@ class MetBase(ABC, Generic[XArrayType]):
293
291
  self._validate_latitude()
294
292
 
295
293
  # transpose to have ordering (x, y, z, t, ...)
296
- dim_order = self.dim_order + [d for d in self.data.dims if d not in self.dim_order]
294
+ dim_order = [*self.dim_order, *(d for d in self.data.dims if d not in self.dim_order)]
297
295
  self.data = self.data.transpose(*dim_order)
298
296
 
299
297
  # single level data
@@ -489,7 +487,7 @@ class MetBase(ABC, Generic[XArrayType]):
489
487
  self.cachestore = self.cachestore or DiskCacheStore()
490
488
 
491
489
  # group by hour and save one dataset for each hour to temp file
492
- times, datasets = zip(*dataset.groupby("time", squeeze=False))
490
+ times, datasets = zip(*dataset.groupby("time", squeeze=False), strict=True)
493
491
 
494
492
  # Open ExitStack to control temp_file context manager
495
493
  with ExitStack() as stack:
@@ -920,7 +918,7 @@ class MetDataset(MetBase):
920
918
  KeyError
921
919
  Raises when dataset does not contain variable in ``vars``
922
920
  """
923
- if isinstance(vars, (MetVariable, str)):
921
+ if isinstance(vars, MetVariable | str):
924
922
  vars = (vars,)
925
923
 
926
924
  met_keys: list[str] = []
@@ -1022,7 +1020,7 @@ class MetDataset(MetBase):
1022
1020
 
1023
1021
  @overrides
1024
1022
  def broadcast_coords(self, name: str) -> xr.DataArray:
1025
- da = xr.ones_like(self.data[list(self.data.keys())[0]]) * self.data[name]
1023
+ da = xr.ones_like(self.data[next(iter(self.data.keys()))]) * self.data[name]
1026
1024
  da.name = name
1027
1025
 
1028
1026
  return da
@@ -1074,7 +1072,7 @@ class MetDataset(MetBase):
1074
1072
  coords_vals = [indexes[key].values for key in coords_keys]
1075
1073
  coords_meshes = np.meshgrid(*coords_vals, indexing="ij")
1076
1074
  raveled_coords = (mesh.ravel() for mesh in coords_meshes)
1077
- data = dict(zip(coords_keys, raveled_coords))
1075
+ data = dict(zip(coords_keys, raveled_coords, strict=True))
1078
1076
 
1079
1077
  out = vector_module.GeoVectorDataset(data, copy=False)
1080
1078
  for key, da in self.data.items():
@@ -1865,7 +1863,7 @@ class MetDataArray(MetBase):
1865
1863
  cachestore = cachestore or DiskCacheStore()
1866
1864
  chunks = chunks or {}
1867
1865
  data = _load(hash, cachestore, chunks)
1868
- return cls(data[list(data.data_vars)[0]])
1866
+ return cls(data[next(iter(data.data_vars))])
1869
1867
 
1870
1868
  @property
1871
1869
  def proportion(self) -> float:
@@ -2258,7 +2256,7 @@ class MetDataArray(MetBase):
2258
2256
  -----
2259
2257
  Uses the `scikit-image Marching Cubes <https://scikit-image.org/docs/dev/auto_examples/edges/plot_marching_cubes.html>`_
2260
2258
  algorithm to reconstruct a surface from the point-cloud like arrays.
2261
- """ # noqa: E501
2259
+ """
2262
2260
  try:
2263
2261
  from skimage import measure
2264
2262
  except ModuleNotFoundError as e:
@@ -22,7 +22,7 @@ class MetVariable:
22
22
  - `NCEP Grib v2 Code Table <https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_table4-2.shtml>`_
23
23
 
24
24
  Used for defining support parameters in a grib-like fashion.
25
- """ # noqa: E501
25
+ """
26
26
 
27
27
  #: Short variable name.
28
28
  #: Chosen for greatest consistency between data sources.
@@ -11,7 +11,7 @@ import warnings
11
11
  from abc import ABC, abstractmethod
12
12
  from collections.abc import Sequence
13
13
  from dataclasses import dataclass, fields
14
- from typing import Any, NoReturn, TypeVar, Union, overload
14
+ from typing import Any, NoReturn, TypeVar, overload
15
15
 
16
16
  import numpy as np
17
17
  import numpy.typing as npt
@@ -30,13 +30,13 @@ from pycontrails.utils.types import type_guard
30
30
  logger = logging.getLogger(__name__)
31
31
 
32
32
  #: Model input source types
33
- ModelInput = Union[MetDataset, GeoVectorDataset, Flight, Sequence[Flight], None]
33
+ ModelInput = MetDataset | GeoVectorDataset | Flight | Sequence[Flight] | None
34
34
 
35
35
  #: Model output source types
36
- ModelOutput = Union[MetDataArray, MetDataset, GeoVectorDataset, Flight, list[Flight], NoReturn]
36
+ ModelOutput = MetDataArray | MetDataset | GeoVectorDataset | Flight | list[Flight]
37
37
 
38
38
  #: Model attribute source types
39
- SourceType = Union[MetDataset, GeoVectorDataset, Flight, Fleet]
39
+ SourceType = MetDataset | GeoVectorDataset | Flight | Fleet
40
40
 
41
41
  _Source = TypeVar("_Source")
42
42
 
@@ -453,7 +453,7 @@ class Model(ABC):
453
453
  return Fleet.from_seq(source)
454
454
 
455
455
  # Raise error if source is not a MetDataset or GeoVectorDataset
456
- if not isinstance(source, (MetDataset, GeoVectorDataset)):
456
+ if not isinstance(source, MetDataset | GeoVectorDataset):
457
457
  msg = f"Unknown source type: {type(source)}"
458
458
  raise TypeError(msg)
459
459
 
@@ -847,7 +847,7 @@ class VectorDataset:
847
847
  ------
848
848
  TypeError
849
849
  If ``mask`` is not a boolean array.
850
- """ # noqa: E501
850
+ """
851
851
  self.data._validate_array(mask)
852
852
  if mask.dtype != bool:
853
853
  raise TypeError("Parameter `mask` must be a boolean array.")
@@ -983,7 +983,7 @@ class VectorDataset:
983
983
  numeric_attrs = (
984
984
  attr
985
985
  for attr, val in self.attrs.items()
986
- if (isinstance(val, (int, float, np.number)) and attr not in ignore_keys)
986
+ if (isinstance(val, int | float | np.number) and attr not in ignore_keys)
987
987
  )
988
988
  self.broadcast_attrs(numeric_attrs, overwrite)
989
989
 
@@ -1072,7 +1072,7 @@ class VectorDataset:
1072
1072
  obj = obj.to_numpy()
1073
1073
 
1074
1074
  # Convert numpy objects to python objects
1075
- if isinstance(obj, (np.ndarray, np.generic)):
1075
+ if isinstance(obj, np.ndarray | np.generic):
1076
1076
 
1077
1077
  # round time to unix seconds
1078
1078
  if key == "time":
@@ -1166,7 +1166,7 @@ class VectorDataset:
1166
1166
  attrs = {}
1167
1167
 
1168
1168
  for k, v in {**obj, **obj_kwargs}.items():
1169
- if isinstance(v, (list, np.ndarray)):
1169
+ if isinstance(v, list | np.ndarray):
1170
1170
  data[k] = v
1171
1171
  else:
1172
1172
  attrs[k] = v
@@ -1194,7 +1194,7 @@ class VectorDataset:
1194
1194
  See Also
1195
1195
  --------
1196
1196
  :func:`numpy.array_split`
1197
- """ # noqa: E501
1197
+ """
1198
1198
  full_index = np.arange(self.size)
1199
1199
  index_splits = np.array_split(full_index, n_splits)
1200
1200
  for index in index_splits:
@@ -6,16 +6,6 @@ import numpy as np
6
6
 
7
7
  from pycontrails.utils import dependencies
8
8
 
9
- try:
10
- import skimage as ski
11
- except ModuleNotFoundError as exc:
12
- dependencies.raise_module_not_found_error(
13
- name="landsat module",
14
- package_name="scikit-image",
15
- module_not_found_error=exc,
16
- pycontrails_optional_package="sat",
17
- )
18
-
19
9
 
20
10
  def normalize(channel: np.ndarray) -> np.ndarray:
21
11
  """Normalize channel values to range [0, 1], preserving ``np.nan`` in output.
@@ -53,8 +43,17 @@ def equalize(channel: np.ndarray, **equalize_kwargs: Any) -> np.ndarray:
53
43
  NaN values are converted to 0 before passing to :py:func:`ski.exposure.equalize_adapthist`
54
44
  and may affect equalized values in the neighborhood where they occur.
55
45
  """
46
+ try:
47
+ import skimage.exposure
48
+ except ModuleNotFoundError as exc:
49
+ dependencies.raise_module_not_found_error(
50
+ name="landsat module",
51
+ package_name="scikit-image",
52
+ module_not_found_error=exc,
53
+ pycontrails_optional_package="sat",
54
+ )
56
55
  return np.where(
57
56
  np.isnan(channel),
58
57
  np.nan,
59
- ski.exposure.equalize_adapthist(np.nan_to_num(channel, nan=0), **equalize_kwargs),
58
+ skimage.exposure.equalize_adapthist(np.nan_to_num(channel, nan=0.0), **equalize_kwargs),
60
59
  )
@@ -8,7 +8,7 @@ import logging
8
8
  import pathlib
9
9
  from collections.abc import Sequence
10
10
  from datetime import datetime
11
- from typing import Any, Union
11
+ from typing import Any, TypeAlias
12
12
 
13
13
  import numpy as np
14
14
  import pandas as pd
@@ -20,11 +20,13 @@ from pycontrails.utils.types import DatetimeLike
20
20
 
21
21
  logger = logging.getLogger(__name__)
22
22
 
23
- TimeInput = Union[str, DatetimeLike, Sequence[Union[str, DatetimeLike]]]
24
- VariableInput = Union[
25
- str, int, MetVariable, np.ndarray, Sequence[Union[str, int, MetVariable, Sequence[MetVariable]]]
26
- ]
27
- PressureLevelInput = Union[int, float, np.ndarray, Sequence[Union[int, float]]]
23
+ # https://github.com/python/mypy/issues/14824
24
+ TimeInput: TypeAlias = str | DatetimeLike | Sequence[str | DatetimeLike]
25
+ VariableInput = (
26
+ str | int | MetVariable | np.ndarray | Sequence[str | int | MetVariable | Sequence[MetVariable]]
27
+ )
28
+
29
+ PressureLevelInput = int | float | np.ndarray | Sequence[int | float]
28
30
 
29
31
  #: NetCDF engine to use for parsing netcdf files
30
32
  NETCDF_ENGINE: str = "netcdf4"
@@ -66,23 +68,23 @@ def parse_timesteps(time: TimeInput | None, freq: str | None = "1h") -> list[dat
66
68
  ------
67
69
  ValueError
68
70
  Raises when the time has len > 2 or when time elements fail to be parsed with pd.to_datetime
69
- """ # noqa: E501
71
+ """
70
72
 
71
73
  if time is None:
72
74
  return []
73
75
 
74
76
  # confirm input is tuple or list-like of length 2
75
- if isinstance(time, (str, datetime, pd.Timestamp, np.datetime64)):
77
+ if isinstance(time, str | datetime | pd.Timestamp | np.datetime64):
76
78
  time = (time, time)
77
79
  elif len(time) == 1:
78
80
  time = (time[0], time[0])
79
81
  elif len(time) != 2:
80
- msg = f"Input time bounds must have length < 2 and > 0, got {len(time)}"
82
+ msg = f"Input time bounds must have length 1 or 2, got {len(time)}"
81
83
  raise ValueError(msg)
82
84
 
83
85
  # convert all to pandas Timestamp
84
86
  try:
85
- timestamps = [pd.to_datetime(t) for t in time]
87
+ t0, t1 = (pd.to_datetime(t) for t in time)
86
88
  except ValueError as e:
87
89
  msg = (
88
90
  f"Failed to parse time input {time}. "
@@ -91,10 +93,13 @@ def parse_timesteps(time: TimeInput | None, freq: str | None = "1h") -> list[dat
91
93
  raise ValueError(msg) from e
92
94
 
93
95
  if freq is None:
94
- daterange = pd.DatetimeIndex([timestamps[0], timestamps[1]])
96
+ daterange = pd.DatetimeIndex([t0, t1])
95
97
  else:
96
98
  # get date range that encompasses all whole hours
97
- daterange = pd.date_range(timestamps[0].floor(freq), timestamps[1].ceil(freq), freq=freq)
99
+ daterange = pd.date_range(t0.floor(freq), t1.ceil(freq), freq=freq)
100
+ if len(daterange) == 0:
101
+ msg = f"Time range {t0} to {t1} with freq {freq} has no valid time steps."
102
+ raise ValueError(msg)
98
103
 
99
104
  # return list of datetimes
100
105
  return daterange.to_pydatetime().tolist()
@@ -151,7 +156,7 @@ def parse_pressure_levels(
151
156
  Raises ValueError if pressure level is not supported by ECMWF data source
152
157
  """
153
158
  # Ensure pressure_levels is array-like
154
- if isinstance(pressure_levels, (int, float)):
159
+ if isinstance(pressure_levels, int | float):
155
160
  pressure_levels = [pressure_levels]
156
161
 
157
162
  # Cast array-like to int dtype and sort
@@ -212,7 +217,7 @@ def parse_variables(variables: VariableInput, supported: list[MetVariable]) -> l
212
217
  met_var_list: list[MetVariable] = []
213
218
 
214
219
  # ensure input variables are a list of str
215
- if isinstance(variables, (str, int, MetVariable)):
220
+ if isinstance(variables, str | int | MetVariable):
216
221
  parsed_variables = [variables]
217
222
  elif isinstance(variables, np.ndarray):
218
223
  parsed_variables = variables.tolist()
@@ -257,7 +262,7 @@ def _find_match(
257
262
 
258
263
  # list of MetVariable options
259
264
  # here we extract the first MetVariable in var that is supported
260
- elif isinstance(var, (list, tuple)):
265
+ elif isinstance(var, list | tuple):
261
266
  for v in var:
262
267
  # sanity check since we don't support other types as lists
263
268
  if not isinstance(v, MetVariable):
@@ -63,7 +63,7 @@ class ECMWFAPI(metsource.MetDataSource):
63
63
  except KeyError as exc:
64
64
  # this snippet shows the missing times for convenience
65
65
  np_timesteps = {np.datetime64(t, "ns") for t in self.timesteps}
66
- missing_times = sorted(np_timesteps.difference(ds["time"].values))
66
+ missing_times = sorted(np_timesteps.difference(ds["time"].values)) # type: ignore[type-var]
67
67
  msg = f"Input dataset is missing time coordinates {[str(t) for t in missing_times]}"
68
68
  raise KeyError(msg) from exc
69
69
 
@@ -80,10 +80,17 @@ class ERA5(ECMWFAPI):
80
80
  Cache data store for staging ECMWF ERA5 files.
81
81
  Defaults to :class:`cache.DiskCacheStore`.
82
82
  If None, cache is turned off.
83
- url : str
84
- Override `cdsapi <https://github.com/ecmwf/cdsapi>`_ url
85
- key : str
86
- Override `cdsapi <https://github.com/ecmwf/cdsapi>`_ key
83
+ url : str | None
84
+ Override the default `cdsapi <https://github.com/ecmwf/cdsapi>`_ url.
85
+ As of August 2024, the url for the `CDS-Beta <https://cds-beta.climate.copernicus.eu>`_
86
+ is "https://cds-beta.climate.copernicus.eu/api", and the url for the legacy server is
87
+ "https://cds.climate.copernicus.eu/api/v2". If None, the url is set
88
+ by the ``CDSAPI_URL`` environment variable. If this is not defined, the
89
+ ``cdsapi`` package will determine the url.
90
+ key : str | None
91
+ Override default `cdsapi <https://github.com/ecmwf/cdsapi>`_ key. If None,
92
+ the key is set by the ``CDSAPI_KEY`` environment variable. If this is not defined,
93
+ the ``cdsapi`` package will determine the key.
87
94
 
88
95
  Notes
89
96
  -----
@@ -121,7 +128,7 @@ class ERA5(ECMWFAPI):
121
128
  ... pressure_levels=[350, 300],
122
129
  ... cachestore=gcp_cache
123
130
  ... )
124
- """ # noqa: E501
131
+ """
125
132
 
126
133
  __slots__ = (
127
134
  "product_type",
@@ -522,17 +529,21 @@ class ERA5(ECMWFAPI):
522
529
  xr.Dataset
523
530
  Processed :class:`xr.Dataset`
524
531
  """
525
-
526
532
  if "pycontrails_version" in ds.attrs:
527
533
  LOG.debug("Input dataset processed with pycontrails > 0.29")
528
534
  return ds
529
535
 
530
- # not pre-processed source file from `download` or `paths`
531
-
532
- # for "reanalysis-era5-single-levels" or if self.pressure_levels length == 1,
536
+ # For "reanalysis-era5-single-levels" or if self.pressure_levels length == 1,
533
537
  # then the netcdf file does not contain the dimension "level"
534
538
  if len(self.pressure_levels) == 1:
535
- ds = ds.expand_dims({"level": self.pressure_levels})
539
+ ds = ds.expand_dims(level=self.pressure_levels)
540
+
541
+ # New CDS-Beta gives "valid_time" instead of "time"
542
+ # and "pressure_level" instead of "level"
543
+ if "valid_time" in ds:
544
+ ds = ds.rename(valid_time="time")
545
+ if "pressure_level" in ds:
546
+ ds = ds.rename(pressure_level="level")
536
547
 
537
548
  ds.attrs["pycontrails_version"] = pycontrails.__version__
538
549
  return ds
@@ -54,8 +54,8 @@ ALL_ENSEMBLE_MEMBERS = list(range(10))
54
54
  class ERA5ModelLevel(ECMWFAPI):
55
55
  """Class to support model-level ERA5 data access, download, and organization.
56
56
 
57
- The interface is similar to :class:`pycontrails.datalib.ecmwf.ERA5`, which downloads pressure-level
58
- with much lower vertical resolution.
57
+ The interface is similar to :class:`pycontrails.datalib.ecmwf.ERA5`, which downloads
58
+ pressure-level with much lower vertical resolution.
59
59
 
60
60
  Requires account with
61
61
  `Copernicus Data Portal <https://cds.climate.copernicus.eu/cdsapp#!/home>`_
@@ -114,11 +114,18 @@ class ERA5ModelLevel(ECMWFAPI):
114
114
  cache_grib: bool, optional
115
115
  If True, cache downloaded GRIB files rather than storing them in a temporary file.
116
116
  By default, False.
117
- url : str
118
- Override `cdsapi <https://github.com/ecmwf/cdsapi>`_ url
119
- key : str
120
- Override `cdsapi <https://github.com/ecmwf/cdsapi>`_ key
121
- """ # noqa: E501
117
+ url : str | None
118
+ Override the default `cdsapi <https://github.com/ecmwf/cdsapi>`_ url.
119
+ As of August 2024, the url for the `CDS-Beta <https://cds-beta.climate.copernicus.eu>`_
120
+ is "https://cds-beta.climate.copernicus.eu/api", and the url for the legacy server is
121
+ "https://cds.climate.copernicus.eu/api/v2". If None, the url is set
122
+ by the ``CDSAPI_URL`` environment variable. If this is not defined, the
123
+ ``cdsapi`` package will determine the url.
124
+ key : str | None
125
+ Override default `cdsapi <https://github.com/ecmwf/cdsapi>`_ key. If None,
126
+ the key is set by the ``CDSAPI_KEY`` environment variable. If this is not defined,
127
+ the ``cdsapi`` package will determine the key.
128
+ """
122
129
 
123
130
  __marker = object()
124
131
 
@@ -349,7 +356,7 @@ class ERA5ModelLevel(ECMWFAPI):
349
356
  unique_dates = set(t.strftime("%Y-%m-%d") for t in times)
350
357
  unique_times = set(t.strftime("%H:%M:%S") for t in times)
351
358
  # param 152 = log surface pressure, needed for metview level conversion
352
- grib_params = set(self.variable_ecmwfids + [152])
359
+ grib_params = set((*self.variable_ecmwfids, 152))
353
360
  common = {
354
361
  "class": "ea",
355
362
  "date": "/".join(sorted(unique_dates)),
@@ -377,7 +377,7 @@ class HRESModelLevel(ECMWFAPI):
377
377
  time = self.forecast_time.strftime("%H:%M:%S")
378
378
  steps = self.get_forecast_steps(times)
379
379
  # param 152 = log surface pressure, needed for metview level conversion
380
- grib_params = set(self.variable_ecmwfids + [152])
380
+ grib_params = set((*self.variable_ecmwfids, 152))
381
381
  return (
382
382
  f"retrieve,\n"
383
383
  f"class=od,\n"
@@ -473,8 +473,8 @@ class HRESModelLevel(ECMWFAPI):
473
473
  LOG.debug("Opening GRIB file")
474
474
  fs_ml = mv.read(target)
475
475
 
476
- # reduce memory overhead by cacheing one timestep at a time
477
- for time, step in zip(times, self.get_forecast_steps(times)):
476
+ # reduce memory overhead by caching one timestep at a time
477
+ for time, step in zip(times, self.get_forecast_steps(times), strict=True):
478
478
  fs_pl = mv.Fieldset()
479
479
  selection = dict(step=step)
480
480
  lnsp = fs_ml.select(shortName="lnsp", **selection)
@@ -118,7 +118,7 @@ RelativeHumidity = MetVariable(
118
118
  "At temperatures below -23°C it is calculated for saturation over ice. "
119
119
  "Between -23°C and 0°C this parameter is calculated by interpolating between the ice and"
120
120
  " water values using a quadratic function."
121
- "See https://www.ecmwf.int/sites/default/files/elibrary/2016/17117-part-iv-physical-processes.pdf#subsection.7.4.2" # noqa: E501
121
+ "See https://www.ecmwf.int/sites/default/files/elibrary/2016/17117-part-iv-physical-processes.pdf#subsection.7.4.2"
122
122
  ),
123
123
  )
124
124
 
@@ -166,7 +166,7 @@ TopNetSolarRadiation = MetVariable(
166
166
  "The incoming solar radiation is the amount received from the Sun. "
167
167
  "The outgoing solar radiation is the amount reflected and scattered by the Earth's"
168
168
  " atmosphere and surface"
169
- "See https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf" # noqa: E501
169
+ "See https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf"
170
170
  ),
171
171
  )
172
172
 
@@ -183,7 +183,7 @@ TopNetThermalRadiation = MetVariable(
183
183
  "radiation emitted to space at the top of the atmosphere is commonly known as the Outgoing"
184
184
  " Longwave Radiation (OLR). "
185
185
  "The top net thermal radiation (this parameter) is equal to the negative of OLR."
186
- "See https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf" # noqa: E501
186
+ "See https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf"
187
187
  ),
188
188
  )
189
189
 
@@ -13,8 +13,9 @@ import hashlib
13
13
  import logging
14
14
  import pathlib
15
15
  import warnings
16
+ from collections.abc import Callable
16
17
  from datetime import datetime
17
- from typing import TYPE_CHECKING, Any, Callable
18
+ from typing import TYPE_CHECKING, Any
18
19
 
19
20
  import numpy as np
20
21
  import pandas as pd
@@ -113,7 +114,7 @@ class GFSForecast(metsource.MetDataSource):
113
114
  - `Documentation <https://www.ncei.noaa.gov/products/weather-climate-models/global-forecast>`_
114
115
  - `Parameter sets <https://www.nco.ncep.noaa.gov/pmb/products/gfs/>`_
115
116
  - `GFS Documentation <https://www.emc.ncep.noaa.gov/emc/pages/numerical_forecast_systems/gfs/documentation.php>`_
116
- """ # noqa: E501
117
+ """
117
118
 
118
119
  __slots__ = ("client", "grid", "cachestore", "show_progress", "forecast_time")
119
120
 
@@ -495,7 +496,7 @@ class GFSForecast(metsource.MetDataSource):
495
496
  GFS dataset
496
497
  """
497
498
  # translate into netcdf from grib
498
- logger.debug(f"Translating {filepath} for timestep {str(t)} into netcdf")
499
+ logger.debug(f"Translating {filepath} for timestep {t!s} into netcdf")
499
500
 
500
501
  # get step for timestep
501
502
  step = pd.Timedelta(t - self.forecast_time) // pd.Timedelta(1, "h")
@@ -33,15 +33,6 @@ except ModuleNotFoundError as exc:
33
33
  pycontrails_optional_package="sat",
34
34
  )
35
35
 
36
- try:
37
- import rasterio
38
- except ModuleNotFoundError as exc:
39
- dependencies.raise_module_not_found_error(
40
- name="landsat module",
41
- package_name="rasterio",
42
- module_not_found_error=exc,
43
- pycontrails_optional_package="sat",
44
- )
45
36
 
46
37
  #: BigQuery table with imagery metadata
47
38
  BQ_TABLE = "bigquery-public-data.cloud_storage_geo_index.landsat_index"
@@ -313,6 +304,16 @@ def _check_band_resolution(bands: set[str]) -> None:
313
304
 
314
305
  def _read(path: str, meta: str, band: str, processing: str) -> xr.DataArray:
315
306
  """Read imagery data from Landsat files."""
307
+ try:
308
+ import rasterio
309
+ except ModuleNotFoundError as exc:
310
+ dependencies.raise_module_not_found_error(
311
+ name="landsat module",
312
+ package_name="rasterio",
313
+ module_not_found_error=exc,
314
+ pycontrails_optional_package="sat",
315
+ )
316
+
316
317
  src = rasterio.open(path)
317
318
  img = src.read(1)
318
319
  crs = pyproj.CRS.from_epsg(src.crs.to_epsg())
@@ -417,7 +417,7 @@ class SyntheticFlight:
417
417
  times_arr = np.asarray(times).T
418
418
  data = [
419
419
  {"longitude": lon, "latitude": lat, "level": level, "time": time}
420
- for lon, lat, level, time in zip(lons_arr, lats_arr, level, times_arr)
420
+ for lon, lat, level, time in zip(lons_arr, lats_arr, level, times_arr, strict=True)
421
421
  ]
422
422
  dfs = [pd.DataFrame(d).dropna() for d in data]
423
423
  dfs = [df for df in dfs if len(df) >= self.min_n_waypoints]
@@ -154,7 +154,7 @@ class ACCF(Model):
154
154
  sur_variables = (ecmwf.SurfaceSolarDownwardRadiation, ecmwf.TopNetThermalRadiation)
155
155
  default_params = ACCFParams
156
156
 
157
- short_vars = {v.short_name for v in (*met_variables, *sur_variables)}
157
+ short_vars = frozenset(v.short_name for v in (*met_variables, *sur_variables))
158
158
 
159
159
  # This variable won't get used since we are not writing the output
160
160
  # anywhere, but the library will complain if it's not defined
@@ -84,10 +84,10 @@ class APCEMMParams(models.ModelParams):
84
84
  engine_uid: str | None = None
85
85
 
86
86
  #: Aircraft performance model
87
- aircraft_performance: AircraftPerformance = PSFlight()
87
+ aircraft_performance: AircraftPerformance = dataclasses.field(default_factory=PSFlight)
88
88
 
89
89
  #: Fuel type
90
- fuel: Fuel = JetA()
90
+ fuel: Fuel = dataclasses.field(default_factory=JetA)
91
91
 
92
92
  #: List of flight waypoints to simulate in APCEMM.
93
93
  #: By default, runs a simulation for every waypoint.
@@ -371,7 +371,7 @@ class APCEMM(models.Model):
371
371
  @overload
372
372
  def eval(self, source: None = ..., **params: Any) -> NoReturn: ...
373
373
 
374
- def eval(self, source: Flight | None = None, **params: Any) -> Flight | NoReturn:
374
+ def eval(self, source: Flight | None = None, **params: Any) -> Flight:
375
375
  """Set up and run APCEMM simulations initialized at flight waypoints.
376
376
 
377
377
  Simulates the formation and evolution of contrails from a Flight
@@ -776,8 +776,8 @@ class APCEMM(models.Model):
776
776
  # Compute azimuth
777
777
  # Use forward and backward differences for first and last waypoints
778
778
  # and centered differences elsewhere
779
- ileft = [0] + list(range(self.source.size - 1))
780
- iright = list(range(1, self.source.size)) + [self.source.size - 1]
779
+ ileft = [0, *range(self.source.size - 1)]
780
+ iright = [*range(1, self.source.size), self.source.size - 1]
781
781
  lon0 = self.source["longitude"][ileft]
782
782
  lat0 = self.source["latitude"][ileft]
783
783
  lon1 = self.source["longitude"][iright]
@@ -335,7 +335,7 @@ class Cocip(Model):
335
335
  self,
336
336
  source: Flight | Sequence[Flight] | None = None,
337
337
  **params: Any,
338
- ) -> Flight | list[Flight] | NoReturn:
338
+ ) -> Flight | list[Flight]:
339
339
  """Run CoCiP simulation on flight.
340
340
 
341
341
  Simulates the formation and evolution of contrails from a Flight
@@ -1132,7 +1132,7 @@ class Cocip(Model):
1132
1132
  )
1133
1133
  vector = GeoVectorDataset(
1134
1134
  {
1135
- key: np.concat((latest_contrail[key], future_contrails[key]))
1135
+ key: np.concatenate((latest_contrail[key], future_contrails[key]))
1136
1136
  for key in ("longitude", "latitude", "level", "time")
1137
1137
  }
1138
1138
  )
@@ -1516,7 +1516,7 @@ def _process_rad(rad: MetDataset) -> MetDataset:
1516
1516
  -----
1517
1517
  - https://www.ecmwf.int/sites/default/files/elibrary/2015/18490-radiation-quantities-ecmwf-model-and-mars.pdf
1518
1518
  - https://confluence.ecmwf.int/pages/viewpage.action?pageId=155337784
1519
- """ # noqa: E501
1519
+ """
1520
1520
  # If the time coordinate has already been shifted, early return
1521
1521
  if "shift_radiation_time" in rad["time"].attrs:
1522
1522
  return rad
@@ -638,7 +638,7 @@ def regional_statistics(da_var: xr.DataArray, *, agg: str) -> pd.Series:
638
638
  -----
639
639
  - The spatial bounding box for each region is defined in Teoh et al. (2023)
640
640
  - Teoh, R., Engberg, Z., Shapiro, M., Dray, L., and Stettler, M.: A high-resolution Global
641
- Aviation emissions Inventory based on ADS-B (GAIA) for 20192021, EGUsphere [preprint],
641
+ Aviation emissions Inventory based on ADS-B (GAIA) for 2019-2021, EGUsphere [preprint],
642
642
  https://doi.org/10.5194/egusphere-2023-724, 2023.
643
643
  """
644
644
  if (agg == "mean") and (len(da_var.time) > 1):
@@ -715,7 +715,7 @@ def _regional_data_arrays(da_global: xr.DataArray) -> dict[str, xr.DataArray]:
715
715
  -----
716
716
  - The spatial bounding box for each region is defined in Teoh et al. (2023)
717
717
  - Teoh, R., Engberg, Z., Shapiro, M., Dray, L., and Stettler, M.: A high-resolution Global
718
- Aviation emissions Inventory based on ADS-B (GAIA) for 20192021, EGUsphere [preprint],
718
+ Aviation emissions Inventory based on ADS-B (GAIA) for 2019-2021, EGUsphere [preprint],
719
719
  https://doi.org/10.5194/egusphere-2023-724, 2023.
720
720
  """
721
721
  return {
@@ -10,6 +10,7 @@ References
10
10
  from __future__ import annotations
11
11
 
12
12
  import dataclasses
13
+ import itertools
13
14
 
14
15
  import numpy as np
15
16
  import numpy.typing as npt
@@ -941,7 +942,7 @@ def contrail_contrail_overlap_radiative_effects(
941
942
  References
942
943
  ----------
943
944
  - Schumann et al. (2021) Air traffic and contrail changes over Europe during COVID-19:
944
- A model study, Atmos. Chem. Phys., 21, 74297450, https://doi.org/10.5194/ACP-21-7429-2021.
945
+ A model study, Atmos. Chem. Phys., 21, 7429-7450, https://doi.org/10.5194/ACP-21-7429-2021.
945
946
  - Teoh et al. (2023) Global aviation contrail climate effects from 2019 to 2021.
946
947
 
947
948
  Notes
@@ -999,8 +1000,7 @@ def contrail_contrail_overlap_radiative_effects(
999
1000
  # Account for contrail overlapping starting from bottom to top layers
1000
1001
  altitude_layers = np.arange(min_altitude_m, max_altitude_m + 1.0, dz_overlap_m)
1001
1002
 
1002
- # TODO: replace zip with itertools.pairwise after we drop python3.9 support
1003
- for alt_layer0, alt_layer1 in zip(altitude_layers, altitude_layers[1:]):
1003
+ for alt_layer0, alt_layer1 in itertools.pairwise(altitude_layers):
1004
1004
  is_in_layer = (altitude >= alt_layer0) & (altitude < alt_layer1)
1005
1005
 
1006
1006
  # Get contrail waypoints at current altitude layer
@@ -143,7 +143,7 @@ class CocipGrid(models.Model):
143
143
 
144
144
  def eval(
145
145
  self, source: GeoVectorDataset | MetDataset | None = None, **params: Any
146
- ) -> GeoVectorDataset | MetDataset | NoReturn:
146
+ ) -> GeoVectorDataset | MetDataset:
147
147
  """Run CoCiP simulation on a 4d coordinate grid or arbitrary set of 4d points.
148
148
 
149
149
  If the :attr:`params` ``verbose_outputs_evolution`` is True, the model holds
@@ -322,7 +322,7 @@ class CocipGrid(models.Model):
322
322
 
323
323
  if met is None:
324
324
  # idx is the first index at which self.met.variables["time"].to_numpy() >= time_end
325
- idx = np.searchsorted(self.met.indexes["time"].to_numpy(), time_end)
325
+ idx = np.searchsorted(self.met.indexes["time"].to_numpy(), time_end).item()
326
326
  sl = slice(max(0, idx - 1), idx + 1)
327
327
  logger.debug("Select met slice %s", sl)
328
328
  met = MetDataset(self.met.data.isel(time=sl), copy=False)
@@ -331,7 +331,7 @@ class CocipGrid(models.Model):
331
331
  current_times = met.indexes["time"].to_numpy()
332
332
  all_times = self.met.indexes["time"].to_numpy()
333
333
  # idx is the first index at which all_times >= time_end
334
- idx = np.searchsorted(all_times, time_end)
334
+ idx = np.searchsorted(all_times, time_end).item()
335
335
  sl = slice(max(0, idx - 1), idx + 1)
336
336
 
337
337
  # case 1: cannot re-use end of current met as start of new met
@@ -353,7 +353,7 @@ class CocipGrid(models.Model):
353
353
 
354
354
  if rad is None:
355
355
  # idx is the first index at which self.rad.variables["time"].to_numpy() >= time_end
356
- idx = np.searchsorted(self.rad.indexes["time"].to_numpy(), time_end)
356
+ idx = np.searchsorted(self.rad.indexes["time"].to_numpy(), time_end).item()
357
357
  sl = slice(max(0, idx - 1), idx + 1)
358
358
  logger.debug("Select rad slice %s", sl)
359
359
  rad = MetDataset(self.rad.data.isel(time=sl), copy=False)
@@ -362,7 +362,7 @@ class CocipGrid(models.Model):
362
362
  current_times = rad.indexes["time"].to_numpy()
363
363
  all_times = self.rad.indexes["time"].to_numpy()
364
364
  # idx is the first index at which all_times >= time_end
365
- idx = np.searchsorted(all_times, time_end)
365
+ idx = np.searchsorted(all_times, time_end).item()
366
366
  sl = slice(max(0, idx - 1), idx + 1)
367
367
 
368
368
  # case 1: cannot re-use end of current rad as start of new rad
@@ -446,9 +446,9 @@ class CocipGrid(models.Model):
446
446
  if ap_model := self.params["aircraft_performance"]:
447
447
  attrs["ap_model"] = type(ap_model).__name__
448
448
 
449
- if isinstance(azimuth, (np.floating, np.integer)):
449
+ if isinstance(azimuth, np.floating | np.integer):
450
450
  attrs["azimuth"] = azimuth.item()
451
- elif isinstance(azimuth, (float, int)):
451
+ elif isinstance(azimuth, float | int):
452
452
  attrs["azimuth"] = azimuth
453
453
 
454
454
  if isinstance(self.source, MetDataset):
@@ -897,7 +897,7 @@ def _setdefault_from_params(key: str, vector: GeoVectorDataset, params: dict[str
897
897
  if scalar is None:
898
898
  return
899
899
 
900
- if not isinstance(scalar, (int, float)):
900
+ if not isinstance(scalar, int | float):
901
901
  msg = (
902
902
  f"Parameter {key} must be a scalar. For non-scalar values, directly "
903
903
  "set the data on the 'source'."
@@ -312,7 +312,7 @@ class PSFlight(AircraftPerformance):
312
312
  atyp_param.wing_surface_area,
313
313
  q_fuel,
314
314
  )
315
- elif isinstance(fuel_flow, (int, float)):
315
+ elif isinstance(fuel_flow, int | float):
316
316
  fuel_flow = np.full_like(true_airspeed, fuel_flow)
317
317
 
318
318
  # Flight phase
@@ -339,11 +339,11 @@ class PSFlight(AircraftPerformance):
339
339
 
340
340
  # XXX: Explicitly broadcast scalar inputs as needed to keep a consistent
341
341
  # output spec.
342
- if isinstance(aircraft_mass, (int, float)):
342
+ if isinstance(aircraft_mass, int | float):
343
343
  aircraft_mass = np.full_like(true_airspeed, aircraft_mass)
344
- if isinstance(engine_efficiency, (int, float)):
344
+ if isinstance(engine_efficiency, int | float):
345
345
  engine_efficiency = np.full_like(true_airspeed, engine_efficiency)
346
- if isinstance(thrust, (int, float)):
346
+ if isinstance(thrust, int | float):
347
347
  thrust = np.full_like(true_airspeed, thrust)
348
348
 
349
349
  return AircraftPerformanceData(
pycontrails/models/sac.py CHANGED
@@ -150,7 +150,7 @@ class SAC(Model):
150
150
  if scale_humidity:
151
151
  for k, v in humidity_scaling.description.items():
152
152
  self.source.attrs[f"humidity_scaling_{k}"] = v
153
- if isinstance(engine_efficiency, (int, float)):
153
+ if isinstance(engine_efficiency, int | float):
154
154
  self.source.attrs["engine_efficiency"] = engine_efficiency
155
155
 
156
156
  return self.source
@@ -236,7 +236,7 @@ def T_sat_liquid(G: ArrayLike) -> ArrayLike:
236
236
  # This comment is pasted several places in `pycontrails` -- they should all be
237
237
  # addressed at the same time.
238
238
  log_ = np.log(G - 0.053)
239
- return -46.46 - constants.absolute_zero + 9.43 * log_ + 0.72 * log_**2 # type: ignore[return-value] # noqa: E501
239
+ return -46.46 - constants.absolute_zero + 9.43 * log_ + 0.72 * log_**2 # type: ignore[return-value]
240
240
 
241
241
 
242
242
  def _e_sat_liquid_prime(T: ArrayScalarLike) -> ArrayScalarLike:
@@ -163,7 +163,7 @@ def e_sat_liquid(T: ArrayScalarLike) -> ArrayScalarLike:
163
163
  # 6.1121 * np.exp((18.678 * (T - 273.15) / 234.5) * (T - 273.15) / (257.14 + (T - 273.15)))
164
164
 
165
165
  # Magnus Tetens (Murray, 1967)
166
- # 6.1078 * np.exp(17.269388 * (T - 273.16) / (T 35.86))
166
+ # 6.1078 * np.exp(17.269388 * (T - 273.16) / (T - 35.86))
167
167
 
168
168
  # Guide to Meteorological Instruments and Methods of Observation (CIMO Guide) (WMO, 2008)
169
169
  # 6.112 * np.exp(17.62 * (T - 273.15) / (243.12 + T - 273.15))
pycontrails/utils/json.py CHANGED
@@ -48,23 +48,21 @@ class NumpyEncoder(json.JSONEncoder):
48
48
  """
49
49
  if isinstance(
50
50
  obj,
51
- (
52
- np.int_,
53
- np.intc,
54
- np.intp,
55
- np.int8,
56
- np.int16,
57
- np.int32,
58
- np.int64,
59
- np.uint8,
60
- np.uint16,
61
- np.uint32,
62
- np.uint64,
63
- ),
51
+ np.int_
52
+ | np.intc
53
+ | np.intp
54
+ | np.int8
55
+ | np.int16
56
+ | np.int32
57
+ | np.int64
58
+ | np.uint8
59
+ | np.uint16
60
+ | np.uint32
61
+ | np.uint64,
64
62
  ):
65
63
  return int(obj)
66
64
 
67
- if isinstance(obj, (np.float16, np.float32, np.float64)):
65
+ if isinstance(obj, np.float16 | np.float32 | np.float64):
68
66
  return float(obj)
69
67
 
70
68
  # TODO: this is not easily reversible - np.timedelta64(str(np.timedelta64(1, "h"))) raises
@@ -74,10 +72,10 @@ class NumpyEncoder(json.JSONEncoder):
74
72
  if isinstance(obj, (np.datetime64)):
75
73
  return str(obj)
76
74
 
77
- if isinstance(obj, (np.complex64, np.complex128)):
75
+ if isinstance(obj, np.complex64 | np.complex128):
78
76
  return {"real": obj.real, "imag": obj.imag}
79
77
 
80
- if isinstance(obj, (np.ndarray,)):
78
+ if isinstance(obj, np.ndarray):
81
79
  return obj.tolist()
82
80
 
83
81
  if isinstance(obj, (np.bool_)):
@@ -86,7 +84,7 @@ class NumpyEncoder(json.JSONEncoder):
86
84
  if isinstance(obj, (np.void)):
87
85
  return None
88
86
 
89
- if isinstance(obj, (pd.Series, pd.Index)):
87
+ if isinstance(obj, pd.Series | pd.Index):
90
88
  return obj.to_numpy().tolist()
91
89
 
92
90
  try:
@@ -146,7 +144,7 @@ def dataframe_to_geojson_points(
146
144
  )
147
145
 
148
146
  # downselect dataframe
149
- cols = ["longitude", "latitude", "altitude", "time"] + properties
147
+ cols = ["longitude", "latitude", "altitude", "time", *properties]
150
148
  df = df[cols]
151
149
 
152
150
  # filter out coords with nan values, or filter just on "filter_nan" labels
@@ -3,8 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import functools
6
+ from collections.abc import Callable
6
7
  from datetime import datetime
7
- from typing import Any, Callable, TypeVar, Union
8
+ from typing import Any, TypeVar
8
9
 
9
10
  import numpy as np
10
11
  import numpy.typing as npt
@@ -12,11 +13,11 @@ import pandas as pd
12
13
  import xarray as xr
13
14
 
14
15
  #: Array like (np.ndarray, xr.DataArray)
15
- ArrayLike = TypeVar("ArrayLike", np.ndarray, xr.DataArray, Union[xr.DataArray, np.ndarray])
16
+ ArrayLike = TypeVar("ArrayLike", np.ndarray, xr.DataArray, xr.DataArray | np.ndarray)
16
17
 
17
18
  #: Array or Float (np.ndarray, float)
18
19
  ArrayOrFloat = TypeVar(
19
- "ArrayOrFloat", npt.NDArray[np.float64], float, Union[float, npt.NDArray[np.float64]]
20
+ "ArrayOrFloat", npt.NDArray[np.float64], float, float | npt.NDArray[np.float64]
20
21
  )
21
22
 
22
23
  #: Array like input (np.ndarray, xr.DataArray, np.float64, float)
@@ -26,8 +27,8 @@ ArrayScalarLike = TypeVar(
26
27
  xr.DataArray,
27
28
  np.float64,
28
29
  float,
29
- Union[np.ndarray, float],
30
- Union[xr.DataArray, np.ndarray],
30
+ np.ndarray | float,
31
+ xr.DataArray | np.ndarray,
31
32
  )
32
33
 
33
34
  #: Datetime like input (datetime, pd.Timestamp, np.datetime64)
@@ -71,7 +72,7 @@ def support_arraylike(
71
72
  return ret
72
73
 
73
74
  # Keep python native numeric types native
74
- if isinstance(arr, (float, int, np.float64)):
75
+ if isinstance(arr, float | int | np.float64):
75
76
  return ret.item()
76
77
 
77
78
  # Recreate pd.Series
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pycontrails
3
- Version: 0.52.3
3
+ Version: 0.53.1
4
4
  Summary: Python library for modeling aviation climate impacts
5
5
  Author-email: Breakthrough Energy <py@contrails.org>
6
6
  License: Apache-2.0
@@ -14,16 +14,16 @@ Classifier: Intended Audience :: Science/Research
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
- Classifier: Programming Language :: Python :: 3.9
18
17
  Classifier: Programming Language :: Python :: 3.10
19
18
  Classifier: Programming Language :: Python :: 3.11
20
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
21
  Classifier: Programming Language :: Python :: 3 :: Only
22
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
23
  Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
24
24
  Classifier: Topic :: Scientific/Engineering :: GIS
25
25
  Classifier: Typing :: Typed
26
- Requires-Python: >=3.9
26
+ Requires-Python: >=3.10
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
29
  License-File: NOTICE
@@ -36,7 +36,7 @@ Requires-Dist: xarray>=2022.3
36
36
  Provides-Extra: complete
37
37
  Requires-Dist: pycontrails[ecmwf,gcp,gfs,jupyter,pyproj,sat,vis,zarr]; extra == "complete"
38
38
  Provides-Extra: dev
39
- Requires-Dist: black[jupyter]==24.4.2; extra == "dev"
39
+ Requires-Dist: black[jupyter]==24.8.0; extra == "dev"
40
40
  Requires-Dist: dep-license; extra == "dev"
41
41
  Requires-Dist: fastparquet>=0.8; extra == "dev"
42
42
  Requires-Dist: ipdb>=0.13; extra == "dev"
@@ -50,7 +50,7 @@ Requires-Dist: pyarrow>=5.0; extra == "dev"
50
50
  Requires-Dist: pytest>=8.2; extra == "dev"
51
51
  Requires-Dist: pytest-cov>=2.11; extra == "dev"
52
52
  Requires-Dist: requests>=2.25; extra == "dev"
53
- Requires-Dist: ruff==0.5.3; extra == "dev"
53
+ Requires-Dist: ruff==0.5.7; extra == "dev"
54
54
  Requires-Dist: setuptools; extra == "dev"
55
55
  Provides-Extra: docs
56
56
  Requires-Dist: doc8>=1.1; extra == "docs"
@@ -141,7 +141,7 @@ Documentation and examples available at [py.contrails.org](https://py.contrails.
141
141
 
142
142
  ### Install with pip
143
143
 
144
- You can install pycontrails from PyPI with `pip` (Python 3.9 or later required):
144
+ You can install pycontrails from PyPI with `pip` (Python 3.10 or later required):
145
145
 
146
146
  ```bash
147
147
  $ pip install pycontrails
@@ -1,67 +1,67 @@
1
- pycontrails-0.52.3.dist-info/RECORD,,
2
- pycontrails-0.52.3.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
3
- pycontrails-0.52.3.dist-info/WHEEL,sha256=wTFHiv6xiwExbw_CBGcw1b3hAKzFq_QaWIv4Plh5P0w,110
4
- pycontrails-0.52.3.dist-info/NOTICE,sha256=gKI8DcN1WhiXB2SFRKDogcjONldGubTvBxiOYdC4CXU,1926
5
- pycontrails-0.52.3.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
6
- pycontrails-0.52.3.dist-info/METADATA,sha256=0OlDUND65RdPbrTL_Zt4hXkn_A3nPptiZ5vXLk1gfaU,9170
7
- pycontrails/_version.py,sha256=oUlYgy_HesUiXOJT6LNUWojwIHRLzqLUsE1WSH4KNos,413
1
+ pycontrails-0.53.1.dist-info/RECORD,,
2
+ pycontrails-0.53.1.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
3
+ pycontrails-0.53.1.dist-info/WHEEL,sha256=Yg9h4QNT-1nYmU7BoP5fPg60gfiPD4f77j-IPR5YwEc,110
4
+ pycontrails-0.53.1.dist-info/NOTICE,sha256=gKI8DcN1WhiXB2SFRKDogcjONldGubTvBxiOYdC4CXU,1926
5
+ pycontrails-0.53.1.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
6
+ pycontrails-0.53.1.dist-info/METADATA,sha256=zCTO7zFOkvqI0JzbmFthdOuZs7Mdt5vl66qd3z7_x0Q,9173
7
+ pycontrails/_version.py,sha256=vV71ixZB5LSC5mFURNjkJpZgkyEImidyN2Ez0p0YzQk,413
8
8
  pycontrails/__init__.py,sha256=O2T9kXCMhcELcMZz7HEnwiBhh4Gfcj-yG1HtrotOKHQ,2001
9
9
  pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- pycontrails/core/vector.py,sha256=ZvI-hQylANy6X8cSBMPa51FnMkmkjASsPyKLflZQJOg,71571
11
- pycontrails/core/models.py,sha256=z06ggBjtWJyDXUexGtoaUgHJA6e7jajqPvZvMXvlSwk,39310
12
- pycontrails/core/interpolation.py,sha256=w3F6G1vmmBgBnjlL06G01CaRYpfm2wrQLbkwBhX_Xx8,25589
10
+ pycontrails/core/vector.py,sha256=v_oO7if8SbNPMRci0TR8hLGSdAZ_fGZOVIT47e4hA58,71541
11
+ pycontrails/core/models.py,sha256=mB3fhmBorFxt7uEhBFcuu0PIMWmBRB4KBRsPiFpPcvo,39282
12
+ pycontrails/core/interpolation.py,sha256=yxVLO9lzNcNFeLwDyrQ7yfz4JEHLHTpgIRBrcOezsXg,25617
13
13
  pycontrails/core/fleet.py,sha256=wqYY_2xD9X-Og0_oxU8ZPqTHYDau9TOPLQcmEnB1kiQ,16140
14
- pycontrails/core/flight.py,sha256=EdB8_P8BELSyXx01CXw_js0zgv-bdsqj3_mt28lhKMU,85144
14
+ pycontrails/core/flight.py,sha256=_1K6tgwAJZwe18xmkOE7l0JQXzr6XgiqRQGzS6qHpxk,85159
15
15
  pycontrails/core/fuel.py,sha256=kJZ3P1lPm1L6rdPREM55XQ-VfJ_pt35cP4sO2Nnvmjs,4332
16
16
  pycontrails/core/polygon.py,sha256=gosyZBX1XBKD2EcHycIZb7uM-xGs8rCfdpiSZlhc2Hc,18028
17
- pycontrails/core/cache.py,sha256=9TaHyLtCpDazgN5zEt0aa4xNgvNcMc4oO_6nEz0XhYE,27971
17
+ pycontrails/core/cache.py,sha256=ly2Prq5CUxxc2pClZUXDeH-E8zkj3zZkLoKpdKUCyGs,27984
18
18
  pycontrails/core/__init__.py,sha256=x1z6x8w3sYmEqYcNWyWHuNkS9lPUPbHUoYJZs1K0q98,856
19
- pycontrails/core/flightplan.py,sha256=s7tHbjMFbHAJkSWV6hPkghuW6jDb1n5UhWAo9XbJ9z0,7349
20
- pycontrails/core/met.py,sha256=vQl2Xenr0Ar-UFofzHOnCDS1NjnP6w5PbupTXDZY6ZI,101325
19
+ pycontrails/core/flightplan.py,sha256=UO4vL087d5TZMlU984-FxfotGTxFbqK78w2fLDRiel4,7335
20
+ pycontrails/core/met.py,sha256=0lGZqGu-_EnulU9Df05xo0I-IYX2MRQXvJ7PgCjU6p0,101342
21
21
  pycontrails/core/aircraft_performance.py,sha256=4KnLj0zK-mk8Oo3As1CXUkQWBQGMeDdrKi5TeOhOmUA,26107
22
22
  pycontrails/core/airports.py,sha256=aeyAXVkioIRomrP79UtNrxindL4f1DJyXFaojZCuBBw,6758
23
- pycontrails/core/met_var.py,sha256=EhrLGdrCAp8cKb-3Whd_ttLMZn4_zLMhE-QyFinESqo,9197
24
- pycontrails/core/rgi_cython.cpython-312-darwin.so,sha256=CgijjYpsGjKlkQUaZysjVsKvUjhUpIs1QzaOAsZ0NSc,309272
23
+ pycontrails/core/met_var.py,sha256=GC5ijw4oGuIefmFOSz4vmxMEBj_SVs5Z75IMhDP56Cw,9183
24
+ pycontrails/core/rgi_cython.cpython-312-darwin.so,sha256=JesjLsxSdduHfS4g3CZXm7jF9tqfn67rdOeWsiUFhDM,309272
25
25
  pycontrails/core/coordinates.py,sha256=0ySsHtqTon7GMbuwmmxMbI92j3ueMteJZh4xxNm5zto,5391
26
26
  pycontrails/datalib/goes.py,sha256=Muh_pqAXSqUlM4ssStUT9QmPxGPEKK21LHFroaqTq7k,26533
27
- pycontrails/datalib/landsat.py,sha256=jV6E9TMmo3x_EEb-4BnD3L7nB5C3NCLT7FaXoMxXP64,19645
27
+ pycontrails/datalib/landsat.py,sha256=WBOXcVgkoWmEM1jeUnOX1IKBOzzUbHWFU3lL3pJ8rfE,19682
28
28
  pycontrails/datalib/spire.py,sha256=66SnMdA8KOS69USjKmqrJmTKPK08Ehih9tnlsCt-AJw,25331
29
29
  pycontrails/datalib/__init__.py,sha256=hW9NWdFPC3y_2vHMteQ7GgQdop3917MkDaf5ZhU2RBY,369
30
30
  pycontrails/datalib/sentinel.py,sha256=Rzsp5Hv6Rh3XVEfvFeofmClId4Eq2KhdYiEhIqFPE3U,17222
31
- pycontrails/datalib/_met_utils/metsource.py,sha256=mh7nWfpnP5l7unuQHDSYjar4hN7k9iSDNd6cYZcwwWM,23844
31
+ pycontrails/datalib/_met_utils/metsource.py,sha256=1MLLnMDHlzXgRD-oeuZObgOoY8sGde81hluTm8oILPg,23965
32
32
  pycontrails/datalib/ecmwf/arco_era5.py,sha256=YuoPmPlP9TpZ6qhUPLbb30y3D3dTNDasTLZqP5MAWtw,18624
33
- pycontrails/datalib/ecmwf/era5.py,sha256=TMX9bJHvALvSbxFI0BpwI9l3yMoxI1ecc2ROfvPaNIE,18242
34
- pycontrails/datalib/ecmwf/era5_model_level.py,sha256=0R96ATNVp1UB63M_ER6aEqdY60dfkj_0SNVjyD6a8fE,18945
33
+ pycontrails/datalib/ecmwf/era5.py,sha256=MvL8TgC8Wg-Q3pZSGVsvGRNL-HT_ndQT7CRdv2bGK7k,19016
34
+ pycontrails/datalib/ecmwf/era5_model_level.py,sha256=8ewGjs0LU_sySPMCk0B1xcAeMGydDx31AOXao9vrbNE,19519
35
35
  pycontrails/datalib/ecmwf/hres.py,sha256=p_l0ytCEEWGam7G7aVynpLmH4H4LQNeVe0Ay7Tw6fp8,28240
36
- pycontrails/datalib/ecmwf/variables.py,sha256=S5SV_kPrWRhwOpOYcWw3V5VRUB_HOVCTw3Sf7UClJEk,9605
37
- pycontrails/datalib/ecmwf/hres_model_level.py,sha256=XomrGw7FdjmXRWLAgYSqvB3Nd00dz9HyZt0fH5ZyA6I,19135
36
+ pycontrails/datalib/ecmwf/variables.py,sha256=jsyHxQ8YTjLA_28DrKvplFn7pOC4T6SrO4j9d2wpkic,9563
37
+ pycontrails/datalib/ecmwf/hres_model_level.py,sha256=-xh6BIEvsyddqJjuV7tygV85H5J8k4gCUKjj2kdkJkY,19147
38
38
  pycontrails/datalib/ecmwf/__init__.py,sha256=kwgk9P4RYdLgOYcBLXX5rWz1T_yL7aO8nt2Eb1PB-eE,1455
39
- pycontrails/datalib/ecmwf/common.py,sha256=p6zTQcojmTNF2FYGztxhX7cYEg9d29JH6_v2mrKteW0,3878
39
+ pycontrails/datalib/ecmwf/common.py,sha256=PIkEdYEmlmwxQ7v4TenW_BaHX7mslnmdJW3iZYXb7Kg,3904
40
40
  pycontrails/datalib/ecmwf/model_levels.py,sha256=x83WJtjC6OnHcUsiNgvYIrVX4lY-pkXR-YlvUo9vYis,2712
41
41
  pycontrails/datalib/ecmwf/ifs.py,sha256=2heema398PoEVCfiTZSBawN25PXAa_CpWm_pGLZ1GuY,10662
42
42
  pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv,sha256=PmvGLRzn6uuCKSwiasSuVcehvvmSaqP7cnLuN6hhCQQ,9788
43
- pycontrails/datalib/_leo_utils/vis.py,sha256=5DgfRFZGqLm-lm72UlSobWbYk6stJ96o804kRG5S2cI,1759
43
+ pycontrails/datalib/_leo_utils/vis.py,sha256=-fLcm1D5cP6lThVHovV3MJSiadWyTUAvYDMvr4drMU4,1802
44
44
  pycontrails/datalib/_leo_utils/search.py,sha256=r87T2OV4qH1pYI2YznvsBL042f4RKxD3OA2snd3-kDI,8687
45
45
  pycontrails/datalib/_leo_utils/static/bq_roi_query.sql,sha256=xq6-tJyz0-bUwW0KjQymqygjH3WlQBmyBtP7Ci7SBe8,260
46
- pycontrails/datalib/gfs/gfs.py,sha256=yektP3gDKpjdmh1FnCOw5D8fq4kQDg4Yti4zqM4wYlo,21639
46
+ pycontrails/datalib/gfs/gfs.py,sha256=fmYFf7KOGr91sFFCfThG61A3vauNis8TGIAZItIOsrY,21649
47
47
  pycontrails/datalib/gfs/variables.py,sha256=KESzB1sTD3hsU8T-qZFD29oFM3l2ZcEtjAE_H7BHKIE,2861
48
48
  pycontrails/datalib/gfs/__init__.py,sha256=tWxgqmlW8Uo07J-3fBTXPrteatzTka9mSXomhWy3NVA,684
49
- pycontrails/ext/synthetic_flight.py,sha256=65LxC1ZZNJrd8YcSgg6739ouPNf0VgVCOfEhK4W4YkI,16679
49
+ pycontrails/ext/synthetic_flight.py,sha256=ypv_vUwpG-DyzD-uswk9MrJdj-k4NgsoJGJPvRkTFuo,16692
50
50
  pycontrails/ext/cirium.py,sha256=DFPfRwLDwddpucAPRQhyT4bDGh0VvvoViMUd3pidam8,415
51
51
  pycontrails/ext/empirical_grid.py,sha256=WSC266aKsQLzCmtrZJCpLdDBykZ9rlFE9xEXmZjbgHo,4362
52
52
  pycontrails/ext/bada.py,sha256=j4Tj7oWSV_6UxYYa9_OjC1yTVzJMQdNRDI4aUQam_xM,1063
53
53
  pycontrails/utils/iteration.py,sha256=q_vb39VjxRr4hqTyPYko3gK4sboJOJf_Evq6m_2DL-g,319
54
54
  pycontrails/utils/__init__.py,sha256=Gt_57sBgfliFSxx9sDpuchykFDxmM11Wg9xAeSqPcnI,32
55
- pycontrails/utils/types.py,sha256=skrZuOB1yQivtg7rUi8bZwBzOD9ZINu93Cx0NaLzCkw,4773
55
+ pycontrails/utils/types.py,sha256=QeJQwpdyjd3OGU9bz86mIuZvvqgV3WoF0QpAZenD6u8,4769
56
56
  pycontrails/utils/temp.py,sha256=lGU0b_R8ze4yKlsOusHIIBaoNFBrmrB3vBjgHRlfcXk,1109
57
- pycontrails/utils/json.py,sha256=4PL7xkt-O9VGvngHh8F1nfbSbgoxjNlDZDUQRxYfcCM,5982
57
+ pycontrails/utils/json.py,sha256=b3JQ6cZ4Uy4-5e-uVAXr8t-KXC_bj_a1qHmXM0Nof58,5914
58
58
  pycontrails/utils/dependencies.py,sha256=ATP45xYdUbIyGFzgbOe5SbokMytvB84TcexUEFnEUZE,2559
59
59
  pycontrails/models/pcc.py,sha256=7hIlg_4-F6Ce7KVFyuIZBZY6uDr1h4KRMqBDlpGkzHE,11116
60
60
  pycontrails/models/tau_cirrus.py,sha256=yNYw4ukT68w2ATGFZr3p8AZxB6A2xufXQq7XP2U51y0,5026
61
61
  pycontrails/models/__init__.py,sha256=dQTOLQb7RdUdUwslt5se__5y_ymbInBexQmNrmAeOdE,33
62
62
  pycontrails/models/issr.py,sha256=k8yCKCtKLW0ECC0QIs-ID1zbr6vHJfeBm9Shq3oaA3U,7351
63
- pycontrails/models/sac.py,sha256=B-0acGuYs2ajFYtSMGP4LO328OsAjQ7v2bnQ5HCw4Ak,15961
64
- pycontrails/models/accf.py,sha256=20XWID_6aoFLDOfanf5ncay_azPbZSA2NoowJSZbM20,12559
63
+ pycontrails/models/sac.py,sha256=lV1Or0AaLxuS1Zo5V8h5c1fkSKC-hKEgiFm7bmmusWw,15946
64
+ pycontrails/models/accf.py,sha256=uPu8EB30Zbd3kd-3BuTnJkFNzpFqQ4u0HftFOJOX51s,12568
65
65
  pycontrails/models/dry_advection.py,sha256=jToOXFwv0V-ZbgpJBvUgTq-AiKP4El_uq1SWS6Oi6YY,16556
66
66
  pycontrails/models/pcr.py,sha256=ZzbEuTOuDdUmmL5T3Wk3HL-O8XzX3HMnn98WcPbASaU,5348
67
67
  pycontrails/models/emissions/__init__.py,sha256=N_EE768TNRDbdmXaxly2Pwun7UmVBTVPc4k89VBz5ys,478
@@ -74,16 +74,16 @@ pycontrails/models/emissions/static/default-engine-uids.csv,sha256=3blb0aqtM8YRs
74
74
  pycontrails/models/apcemm/__init__.py,sha256=M-hrJklbSgBckclm526MiBAhpKPLHgJbB58ArbJuGIk,175
75
75
  pycontrails/models/apcemm/inputs.py,sha256=88GylkiaymEW_XZeFxLsICI9wV6kl8wVYsuyTe8zIQ8,6585
76
76
  pycontrails/models/apcemm/utils.py,sha256=xlEVe0RKFXrqDr4V77mbb2HxY8IK42EX4K86tN1sLQs,17094
77
- pycontrails/models/apcemm/apcemm.py,sha256=E6Ov1akN7SCa_Y2Atgw85eaoV9uML6tRZ5cKHztphqs,39894
77
+ pycontrails/models/apcemm/apcemm.py,sha256=PGuTSfeeA5kOzdTIeHV0svTht7walVoQ8foqf5iR8Ls,39937
78
78
  pycontrails/models/apcemm/static/apcemm_yaml_template.yaml,sha256=uAZkc57OUvDMjgX6F5f6hgDh3Hgg1NbHWRUFSiv0DEI,6745
79
79
  pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=9619kSgNIBjkM8vFWcwny7t4mhg00pX0aKabrDGPn7I,36658
80
80
  pycontrails/models/humidity_scaling/__init__.py,sha256=nqsab_j9BCwMbTfCn4BjXMdhItlvNKkgUJ9-lb8RyIo,1119
81
81
  pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq,sha256=tfYhbafF9Z-gGCg6VQ1YBlOaK_01e65Dc6s9b-hQ6Zo,286375
82
82
  pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq,sha256=pShCvNUo0NYtAHhT9IBRuj38X9jejdlKfv-ZoOKmtKI,35943
83
- pycontrails/models/cocip/radiative_forcing.py,sha256=ERuFcYMo0_1iiOricnZ8D4ext23bMnTCeZwg9vd6Vzs,44944
83
+ pycontrails/models/cocip/radiative_forcing.py,sha256=aA4ZHaVOsg0lro04LwwKaBf3mXljRAzbwQpDLaxk4qU,44873
84
84
  pycontrails/models/cocip/wind_shear.py,sha256=p8d3iaNzxPA3MoxFEM1ZDKt0aticoD6U9cv0QmbuBzs,3860
85
- pycontrails/models/cocip/cocip.py,sha256=yJQqlOuPNq_XFxhX8UtAAi7djxrh9r5bbClRE8zP2cE,100109
86
- pycontrails/models/cocip/output_formats.py,sha256=TfMHBKtj0BGwfXpu37vqOXS9K8oBXo1t8_XDJxdSHY4,83645
85
+ pycontrails/models/cocip/cocip.py,sha256=zUvWQi77N13mx6nlIEbpvmEMPkdJoU2lsfK3iWovGXM,100089
86
+ pycontrails/models/cocip/output_formats.py,sha256=7P0j-UX4NNw56Gkd3ZsWDt0ctorJTZ4aPmUiibAh1FM,83641
87
87
  pycontrails/models/cocip/__init__.py,sha256=jd-9Tq20s1kwQBlxsYfZLi3hlT5MnWOY2XsPazq1fgE,962
88
88
  pycontrails/models/cocip/cocip_params.py,sha256=kKTeF1vVQr361XBR79q4mQHYI7UUQ6C5Ik5Z5pJDtag,12703
89
89
  pycontrails/models/cocip/wake_vortex.py,sha256=i_OF193KK5BCMdVCgK0_4Aqn55f6rnL4WDWEac8um-w,14421
@@ -92,7 +92,7 @@ pycontrails/models/cocip/radiative_heating.py,sha256=YRpwfXgFnf89iuJiIM96q-jbdcM
92
92
  pycontrails/models/cocip/contrail_properties.py,sha256=tycCxKf8j9GvVYDQBPxjtp6xLll-r00C0XW-w1jGbMI,55594
93
93
  pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=kDxFpAIkcqqhGmwXoxv3_cSESj1Ur45GbLJF56IACJs,14573
94
94
  pycontrails/models/ps_model/__init__.py,sha256=5L-HympF1gJaZ6xiNkIQJygJhkDxM3-ejS_T2z-83hQ,495
95
- pycontrails/models/ps_model/ps_model.py,sha256=jOznGL37sWlQWjV2DpwCLDKdvw2U0XQdvQ_jRm4hj_w,33017
95
+ pycontrails/models/ps_model/ps_model.py,sha256=Cj79eJxHfq6205zNPxrwSs_Cx337MU3qvc7ds8sHAjA,33013
96
96
  pycontrails/models/ps_model/ps_aircraft_params.py,sha256=-PfT2JC6RckVi_zTDVTqAMyaS-id6I2klUoXoEXreAc,13077
97
97
  pycontrails/models/ps_model/ps_operational_limits.py,sha256=_vFJiPqGuZJRzwuY10-z07-7eEyomnpxPm_Js1Cd5So,16832
98
98
  pycontrails/models/ps_model/ps_grid.py,sha256=AqZCEytWhrNbyujlJTufI4cxDonkPchGnrB3IvtRID4,18667
@@ -100,10 +100,10 @@ pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv,sha256=ksrpQTHkx
100
100
  pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv,sha256=3eNhSwzut0gon04k2EYKKaXRvQSUlau3yBAbHS0EBao,25784
101
101
  pycontrails/models/cocipgrid/cocip_grid_params.py,sha256=l4vBPrOKCJDz5Y1uMjmOGVyUcSWgfZtFWbjW968OPz8,5875
102
102
  pycontrails/models/cocipgrid/__init__.py,sha256=ar6bF_8Pusbb-myujz_q5ntFylQTNH8yiM8fxP7Zk30,262
103
- pycontrails/models/cocipgrid/cocip_grid.py,sha256=MLsh3rZb-6Q7vnad6YYTfZs83-kSJEbhjfWTS9oooXI,94333
103
+ pycontrails/models/cocipgrid/cocip_grid.py,sha256=B1-f3D62fg3OeLp7xnxANAsneg9JRiiRVMqs-REFaII,94347
104
104
  pycontrails/physics/geo.py,sha256=9ZWIXyEEgrBNqsoeBBlYLTA-8GUTgyc-jgeVgchxXa8,30288
105
105
  pycontrails/physics/units.py,sha256=j-G5AC9eWIvv2MTOq9lUOoOQKFNJJuHzWLanHRji2tE,12272
106
106
  pycontrails/physics/constants.py,sha256=pHQQmccMUwuNnY4hFtm3L8G2rnUQcfJnroyQr8HAVeM,3146
107
107
  pycontrails/physics/__init__.py,sha256=_1eWbEy6evEWdfJCEkwDiSdpiDNzNWEPVqaPekHyhwU,44
108
- pycontrails/physics/thermo.py,sha256=UA7jLApcbu53fo1ACtgF578Np17efn0UN1yxwus4gEQ,12789
108
+ pycontrails/physics/thermo.py,sha256=sWGpKa12daSpqZYNgyXd8Ii5nfA_1Mm5mMbnM5GsW-E,12787
109
109
  pycontrails/physics/jet.py,sha256=JX-o5dDjyITMNUIOcD_7UBt_4ldc9v-5gI6bmwf0wQ4,25624
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (72.1.0)
2
+ Generator: setuptools (73.0.1)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp312-cp312-macosx_10_9_x86_64
5
5