pycontrails 0.57.0__cp313-cp313-macosx_10_13_x86_64.whl → 0.58.0__cp313-cp313-macosx_10_13_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 +3 -3
  2. pycontrails/core/aircraft_performance.py +1 -1
  3. pycontrails/core/cache.py +2 -2
  4. pycontrails/core/fleet.py +2 -7
  5. pycontrails/core/flight.py +2 -7
  6. pycontrails/core/interpolation.py +42 -64
  7. pycontrails/core/met.py +36 -16
  8. pycontrails/core/polygon.py +3 -3
  9. pycontrails/core/rgi_cython.cpython-313-darwin.so +0 -0
  10. pycontrails/core/vector.py +3 -8
  11. pycontrails/datalib/_met_utils/metsource.py +4 -7
  12. pycontrails/datalib/ecmwf/common.py +2 -2
  13. pycontrails/datalib/ecmwf/hres.py +2 -2
  14. pycontrails/datalib/ecmwf/ifs.py +1 -1
  15. pycontrails/datalib/gfs/gfs.py +1 -1
  16. pycontrails/datalib/himawari/header_struct.py +1 -1
  17. pycontrails/datalib/himawari/himawari.py +20 -7
  18. pycontrails/datalib/leo_utils/sentinel_metadata.py +9 -9
  19. pycontrails/ext/synthetic_flight.py +2 -2
  20. pycontrails/models/cocip/cocip_uncertainty.py +1 -1
  21. pycontrails/models/cocip/contrail_properties.py +1 -1
  22. pycontrails/models/cocip/output_formats.py +1 -1
  23. pycontrails/models/cocipgrid/cocip_grid.py +3 -3
  24. pycontrails/models/dry_advection.py +1 -1
  25. pycontrails/models/extended_k15.py +4 -4
  26. pycontrails/models/humidity_scaling/humidity_scaling.py +2 -2
  27. pycontrails/models/ps_model/ps_grid.py +2 -2
  28. pycontrails/models/sac.py +1 -1
  29. pycontrails/models/tau_cirrus.py +1 -1
  30. pycontrails/physics/thermo.py +1 -1
  31. pycontrails/utils/iteration.py +1 -1
  32. {pycontrails-0.57.0.dist-info → pycontrails-0.58.0.dist-info}/METADATA +5 -5
  33. {pycontrails-0.57.0.dist-info → pycontrails-0.58.0.dist-info}/RECORD +37 -37
  34. {pycontrails-0.57.0.dist-info → pycontrails-0.58.0.dist-info}/WHEEL +0 -0
  35. {pycontrails-0.57.0.dist-info → pycontrails-0.58.0.dist-info}/licenses/LICENSE +0 -0
  36. {pycontrails-0.57.0.dist-info → pycontrails-0.58.0.dist-info}/licenses/NOTICE +0 -0
  37. {pycontrails-0.57.0.dist-info → pycontrails-0.58.0.dist-info}/top_level.txt +0 -0
pycontrails/_version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.57.0'
32
- __version_tuple__ = version_tuple = (0, 57, 0)
31
+ __version__ = version = '0.58.0'
32
+ __version_tuple__ = version_tuple = (0, 58, 0)
33
33
 
34
- __commit_id__ = commit_id = 'g7b8b60b87'
34
+ __commit_id__ = commit_id = 'g142ff7355'
@@ -550,7 +550,7 @@ class AircraftPerformance(Model):
550
550
  if self.met is None:
551
551
  cond = np.isnan(u) & np.isnan(v)
552
552
  else:
553
- met_level_max = self.met.data["level"][-1].item() # type: ignore[union-attr]
553
+ met_level_max = self.met.data["level"][-1].item()
554
554
  cond = self.source.level > met_level_max
555
555
 
556
556
  # We DON'T overwrite the original u and v arrays already attached to the source
pycontrails/core/cache.py CHANGED
@@ -189,7 +189,7 @@ class DiskCacheStore(CacheStore):
189
189
  self,
190
190
  cache_dir: str | pathlib.Path | None = None,
191
191
  allow_clear: bool = False,
192
- ):
192
+ ) -> None:
193
193
  if cache_dir is None:
194
194
  # Avoid unnecessary import of platformdirs (called in _get_user_cache_dir)
195
195
  cache_dir = os.getenv("PYCONTRAILS_CACHE_DIR") or _get_user_cache_dir()
@@ -461,7 +461,7 @@ class GCPCacheStore(CacheStore):
461
461
  timeout: int = 300,
462
462
  show_progress: bool = False,
463
463
  chunk_size: int = 64 * 262144,
464
- ):
464
+ ) -> None:
465
465
  try:
466
466
  from google.cloud import storage
467
467
  except ModuleNotFoundError as e:
pycontrails/core/fleet.py CHANGED
@@ -5,12 +5,7 @@ from __future__ import annotations
5
5
  import sys
6
6
  import warnings
7
7
  from collections.abc import Iterable
8
- from typing import Any, NoReturn
9
-
10
- if sys.version_info >= (3, 11):
11
- from typing import Self
12
- else:
13
- from typing_extensions import Self
8
+ from typing import Any, NoReturn, Self
14
9
 
15
10
  if sys.version_info >= (3, 12):
16
11
  from typing import override
@@ -122,7 +117,7 @@ class Fleet(Flight):
122
117
  # Set default fl_attrs if not provided
123
118
  fl_attrs = fl_attrs or {}
124
119
  for flight_id in groups.index:
125
- fl_attrs.setdefault(flight_id, {}) # type: ignore[call-overload]
120
+ fl_attrs.setdefault(flight_id, {})
126
121
 
127
122
  extra = fl_attrs.keys() - groups.index
128
123
  if extra:
@@ -6,18 +6,13 @@ import enum
6
6
  import logging
7
7
  import sys
8
8
  import warnings
9
- from typing import TYPE_CHECKING, Any, NoReturn
9
+ from typing import TYPE_CHECKING, Any, NoReturn, Self
10
10
 
11
11
  if sys.version_info >= (3, 12):
12
12
  from typing import override
13
13
  else:
14
14
  from typing_extensions import override
15
15
 
16
- if sys.version_info >= (3, 11):
17
- from typing import Self
18
- else:
19
- from typing_extensions import Self
20
-
21
16
 
22
17
  import numpy as np
23
18
  import numpy.typing as npt
@@ -2138,7 +2133,7 @@ def segment_rocd(
2138
2133
  T_correction[:-1] = (air_temperature[:-1] + air_temperature[1:]) / (T_isa[:-1] + T_isa[1:])
2139
2134
  T_correction[-1] = np.nan
2140
2135
 
2141
- return T_correction * out # type: ignore[return-value]
2136
+ return T_correction * out
2142
2137
 
2143
2138
 
2144
2139
  def _resample_to_freq_or_time(
@@ -26,57 +26,74 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
26
26
 
27
27
  This class is a thin wrapper around the
28
28
  :class:`scipy.interpolate.RegularGridInterpolator` in order to make typical
29
- ``pycontrails`` use-cases more efficient.
29
+ ``pycontrails`` linear interpolation use-cases more performant:
30
30
 
31
- #. Avoid ``RegularGridInterpolator`` constructor validation. In :func:`interp`,
32
- parameters are carefully crafted to fit into the intended form, thereby making
33
- validation unnecessary.
31
+ #. Avoid ``RegularGridInterpolator`` constructor validation when `method="linear"`.
32
+ In :func:`interp`, parameters are carefully crafted to fit into the intended form,
33
+ thereby making validation unnecessary.
34
34
  #. Override the :meth:`_evaluate_linear` method with a faster implementation. See
35
- the :meth:`_evaluate_linear` docstring for more information.
35
+ the :meth:`_evaluate_linear` docstring for more information.
36
36
 
37
- This class should not be used directly. Instead, use the :func:`interp` function.
37
+ **This class should not be used directly. Instead, use the ``interp`` function.**
38
38
 
39
39
  .. versionchanged:: 0.40.0
40
40
 
41
41
  The :meth:`_evaluate_linear` method now uses a Cython implementation. The dtype
42
42
  of the output is now consistent with the dtype of the underlying :attr:`values`
43
43
 
44
+ .. versionchanged:: 0.58.0
45
+
46
+ Any ``method`` other than ``"linear"`` now uses the
47
+ :class:`scipy.interpolate.RegularGridInterpolator` implementation. This
48
+ allows for greater flexibility in the ``method`` parameter.
49
+
44
50
  Parameters
45
51
  ----------
46
52
  points : tuple[npt.NDArray[np.floating], ...]
47
53
  Coordinates of the grid points.
48
54
  values : npt.NDArray[np.floating]
49
55
  Grid values. The shape of this array must be compatible with the
50
- coordinates. An error is raised if the dtype is not ``np.float32``
51
- or ``np.float64``.
56
+ coordinates.
52
57
  method : str
53
58
  Passed into :class:`scipy.interpolate.RegularGridInterpolator`
54
59
  bounds_error : bool
55
60
  Passed into :class:`scipy.interpolate.RegularGridInterpolator`
56
61
  fill_value : float | np.float64 | None
57
62
  Passed into :class:`scipy.interpolate.RegularGridInterpolator`
63
+
64
+ See Also
65
+ --------
66
+ scipy.interpolate.RegularGridInterpolator
67
+ interp
58
68
  """
59
69
 
60
70
  def __init__(
61
71
  self,
62
72
  points: tuple[npt.NDArray[np.floating], ...],
63
73
  values: npt.NDArray[np.floating],
74
+ *,
64
75
  method: str,
65
76
  bounds_error: bool,
66
77
  fill_value: float | np.float64 | None,
67
- ):
68
- if values.dtype not in (np.float32, np.float64):
69
- msg = f"values must be a float array, not {values.dtype}"
70
- raise ValueError(msg)
71
-
78
+ ) -> None:
79
+ if method != "linear" or values.dtype not in (np.float32, np.float64):
80
+ # Slow path: use parent class
81
+ super().__init__(
82
+ points,
83
+ values,
84
+ method=method,
85
+ bounds_error=bounds_error,
86
+ fill_value=fill_value,
87
+ )
88
+ return
89
+
90
+ # Fast path: no validation
72
91
  self.grid = points
73
92
  self.values = values
74
- # TODO: consider supporting updated tensor-product spline methods
75
- # see https://github.com/scipy/scipy/releases/tag/v1.13.0
76
- self.method = _pick_method(scipy.__version__, method)
93
+ self.method = method
77
94
  self.bounds_error = bounds_error
78
95
  self.fill_value = fill_value
79
- self._spline = None
96
+ self._spline = None # XXX: setting private attribute on RGI
80
97
 
81
98
  def _prepare_xi_simple(self, xi: npt.NDArray[np.floating]) -> npt.NDArray[np.bool_]:
82
99
  """Run looser version of :meth:`_prepare_xi`.
@@ -103,7 +120,7 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
103
120
 
104
121
  return np.zeros(xi.shape[0], dtype=bool)
105
122
 
106
- return self._find_out_of_bounds(xi.T)
123
+ return self._find_out_of_bounds(xi.T) # XXX: calling private method on RGI
107
124
 
108
125
  def __call__(
109
126
  self, xi: npt.NDArray[np.floating], method: str | None = None
@@ -130,7 +147,7 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
130
147
  return super().__call__(xi, method)
131
148
 
132
149
  out_of_bounds = self._prepare_xi_simple(xi)
133
- xi_indices, norm_distances = rgi_cython.find_indices(self.grid, xi.T)
150
+ xi_indices, norm_distances = self._find_indices(xi.T) # XXX: calling private method on RGI
134
151
 
135
152
  out = self._evaluate_linear(xi_indices, norm_distances)
136
153
  return self._set_out_of_bounds(out, out_of_bounds)
@@ -223,45 +240,6 @@ class PycontrailsRegularGridInterpolator(scipy.interpolate.RegularGridInterpolat
223
240
  raise ValueError(msg)
224
241
 
225
242
 
226
- def _pick_method(scipy_version: str, method: str) -> str:
227
- """Select an interpolation method.
228
-
229
- For scipy versions 1.13.0 and later, fall back on legacy implementations
230
- of tensor-product spline methods. The default implementations in 1.13.0
231
- and later are incompatible with this class.
232
-
233
- Parameters
234
- ----------
235
- scipy_version : str
236
- scipy version (major.minor.patch)
237
-
238
- method : str
239
- Interpolation method. Passed into :class:`scipy.interpolate.RegularGridInterpolator`
240
- as-is unless ``scipy_version`` is 1.13.0 or later and ``method`` is ``"slinear"``,
241
- ``"cubic"``, or ``"quintic"``. In this case, ``"_legacy"`` is appended to ``method``.
242
-
243
- Returns
244
- -------
245
- str
246
- Interpolation method adjusted for compatibility with this class.
247
- """
248
- if method == "linear":
249
- return method
250
-
251
- try:
252
- version = scipy_version.split(".")
253
- major = int(version[0])
254
- minor = int(version[1])
255
- except (IndexError, ValueError) as exc:
256
- msg = f"Failed to parse major and minor version from {scipy_version}"
257
- raise ValueError(msg) from exc
258
-
259
- reimplemented_methods = ["slinear", "cubic", "quintic"]
260
- if major > 1 or ((major == 1 and minor >= 13) and method in reimplemented_methods):
261
- return method + "_legacy"
262
- return method
263
-
264
-
265
243
  def _floatize_time(
266
244
  time: npt.NDArray[np.datetime64], offset: np.datetime64
267
245
  ) -> npt.NDArray[np.floating]:
@@ -431,7 +409,7 @@ def interp(
431
409
  Include ``indices`` and ``return_indices`` experimental parameters.
432
410
  Currently, nan values in ``longitude``, ``latitude``, ``level``, or ``time``
433
411
  are always propagated through to the output, regardless of ``bounds_error``.
434
- In other words, a ValueError for an out of bounds coordinate is only raised
412
+ In other words, a ``ValueError`` for an out of bounds coordinate is only raised
435
413
  if a non-nan value is out of bounds.
436
414
 
437
415
  .. versionchanged:: 0.40.0
@@ -480,9 +458,9 @@ def interp(
480
458
 
481
459
  See Also
482
460
  --------
483
- - :meth:`MetDataArray.interpolate`
484
- - :func:`scipy.interpolate.interpn`
485
- - :class:`scipy.interpolate.RegularGridInterpolator`
461
+ pycontrails.MetDataArray.interpolate
462
+ scipy.interpolate.interpn
463
+ scipy.interpolate.RegularGridInterpolator
486
464
  """
487
465
  if localize:
488
466
  coords = {"longitude": longitude, "latitude": latitude, "level": level, "time": time}
@@ -579,7 +557,7 @@ def _linear_interp_with_indices(
579
557
  if indices is None:
580
558
  assert xi is not None, "xi must be provided if indices is None"
581
559
  out_of_bounds = interp._prepare_xi_simple(xi)
582
- xi_indices, norm_distances = rgi_cython.find_indices(interp.grid, xi.T)
560
+ xi_indices, norm_distances = interp._find_indices(xi.T)
583
561
  indices = RGIArtifacts(xi_indices, norm_distances, out_of_bounds)
584
562
 
585
563
  out = interp._evaluate_linear(indices.xi_indices, indices.norm_distances)
@@ -606,7 +584,7 @@ class EmissionsProfileInterpolator:
606
584
 
607
585
  This class simply wraps :func:`numpy.interp` with fixed values for the
608
586
  ``xp`` and ``fp`` arguments. Unlike :class:`xarray.DataArray` interpolation,
609
- the `numpy.interp` automatically clips values outside the range of the
587
+ the :func:`numpy.interp` automatically clips values outside the range of the
610
588
  ``xp`` array.
611
589
 
612
590
  Parameters
pycontrails/core/met.py CHANGED
@@ -26,15 +26,11 @@ from typing import (
26
26
  Any,
27
27
  Generic,
28
28
  Literal,
29
+ Self,
29
30
  TypeVar,
30
31
  overload,
31
32
  )
32
33
 
33
- if sys.version_info >= (3, 11):
34
- from typing import Self
35
- else:
36
- from typing_extensions import Self
37
-
38
34
  if sys.version_info >= (3, 12):
39
35
  from typing import override
40
36
  else:
@@ -211,14 +207,22 @@ class MetBase(ABC, Generic[XArrayType]):
211
207
  Raises
212
208
  ------
213
209
  ValueError
214
- If one of the coordinates is not sorted.
210
+ If one of the coordinates is not sorted or contains duplicate values.
215
211
  """
216
212
  indexes = self.indexes
217
- if not np.all(np.diff(indexes["time"]) > np.timedelta64(0, "ns")):
218
- raise ValueError("Coordinate 'time' not sorted. Instantiate with 'copy=True'.")
219
- for coord in self.dim_order[:3]: # exclude time, the 4th dimension
220
- if not np.all(np.diff(indexes[coord]) > 0.0):
221
- raise ValueError(f"Coordinate '{coord}' not sorted. Instantiate with 'copy=True'.")
213
+ for coord in self.dim_order:
214
+ arr = indexes[coord]
215
+ d = np.diff(arr)
216
+ zero = np.zeros((), dtype=d.dtype) # ensure same dtype
217
+
218
+ if np.any(d <= zero):
219
+ if np.any(d == zero):
220
+ msg = f"Coordinate '{coord}' contains duplicate values."
221
+ else:
222
+ msg = f"Coordinate '{coord}' not sorted."
223
+
224
+ msg += " Instantiate with 'copy=True'."
225
+ raise ValueError(msg)
222
226
 
223
227
  def _validate_transpose(self) -> None:
224
228
  """Check that data is transposed according to :attr:`dim_order`."""
@@ -271,6 +275,10 @@ class MetBase(ABC, Generic[XArrayType]):
271
275
  Auxiliary coordinates (altitude and air_pressure) are now cast to the same
272
276
  dtype as the underlying grid data.
273
277
 
278
+ .. versionchanged:: 0.58.0
279
+
280
+ Duplicate dimension values are dropped, keeping the first occurrence.
281
+
274
282
 
275
283
  Parameters
276
284
  ----------
@@ -297,6 +305,18 @@ class MetBase(ABC, Generic[XArrayType]):
297
305
  # sortby to ensure each coordinate has ascending order
298
306
  self.data = self.data.sortby(list(self.dim_order), ascending=True)
299
307
 
308
+ # Drop any duplicated dimension values
309
+ indexes = self.indexes
310
+ for coord in self.dim_order:
311
+ arr = indexes[coord]
312
+ d = np.diff(arr)
313
+ zero = np.zeros((), dtype=d.dtype) # ensure same dtype
314
+
315
+ if np.any(d == zero):
316
+ # Remove duplicates
317
+ filt = np.r_[True, d > zero] # prepend True keeps the first occurrence
318
+ self.data = self.data.isel({coord: filt})
319
+
300
320
  if not self.is_wrapped:
301
321
  # Ensure longitude is contained in interval [-180, 180)
302
322
  # If longitude has value at 180, we might not want to shift it?
@@ -1087,7 +1107,7 @@ class MetDataset(MetBase):
1087
1107
  out[key] = da.values.ravel() # type: ignore[index]
1088
1108
 
1089
1109
  if transfer_attrs:
1090
- out.attrs.update(self.attrs) # type: ignore[arg-type]
1110
+ out.attrs.update(self.attrs)
1091
1111
 
1092
1112
  return out
1093
1113
 
@@ -1300,7 +1320,7 @@ class MetDataset(MetBase):
1300
1320
  coords: dict[str, np.ndarray] = {}
1301
1321
  for key, val in input_data.items():
1302
1322
  dtype = "datetime64[ns]" if key == "time" else COORD_DTYPE
1303
- arr: np.ndarray = np.asarray(val, dtype=dtype) # type: ignore[call-overload]
1323
+ arr: np.ndarray = np.asarray(val, dtype=dtype)
1304
1324
 
1305
1325
  if arr.ndim == 0:
1306
1326
  arr = arr.reshape(1)
@@ -1889,7 +1909,7 @@ class MetDataArray(MetBase):
1889
1909
  if not self.binary:
1890
1910
  raise NotImplementedError("proportion method is only implemented for binary fields")
1891
1911
 
1892
- return self.data.sum().values.item() / self.data.count().values.item() # type: ignore[operator]
1912
+ return self.data.sum().values.item() / self.data.count().values.item()
1893
1913
 
1894
1914
  def find_edges(self) -> Self:
1895
1915
  """Find edges of regions.
@@ -2598,9 +2618,9 @@ def _extract_2d_arr_and_altitude(
2598
2618
  except KeyError:
2599
2619
  altitude = None
2600
2620
  else:
2601
- altitude = round(altitude) # type: ignore[call-overload]
2621
+ altitude = round(altitude)
2602
2622
 
2603
- return arr, altitude # type: ignore[return-value]
2623
+ return arr, altitude
2604
2624
 
2605
2625
 
2606
2626
  def downselect(data: XArrayType, bbox: tuple[float, ...]) -> XArrayType:
@@ -238,7 +238,7 @@ def _contours_to_polygons(
238
238
  latitude=latitude,
239
239
  precision=precision,
240
240
  buffer=buffer,
241
- i=child_i, # type: ignore[arg-type]
241
+ i=child_i,
242
242
  )
243
243
 
244
244
  candidate = shapely.Polygon(polygon.exterior, [h.exterior for h in holes])
@@ -354,11 +354,11 @@ def find_multipolygon(
354
354
  return shapely.MultiPolygon()
355
355
 
356
356
  assert len(hierarchy) == 1
357
- hierarchy = hierarchy[0] # type: ignore[index]
357
+ hierarchy = hierarchy[0]
358
358
 
359
359
  polygons = _contours_to_polygons(
360
360
  contours, # type: ignore[arg-type]
361
- hierarchy, # type: ignore[arg-type]
361
+ hierarchy,
362
362
  min_area,
363
363
  convex_hull,
364
364
  epsilon,
@@ -8,12 +8,7 @@ import logging
8
8
  import sys
9
9
  import warnings
10
10
  from collections.abc import Generator, Iterable, Iterator, Sequence
11
- from typing import Any, overload
12
-
13
- if sys.version_info >= (3, 11):
14
- from typing import Self
15
- else:
16
- from typing_extensions import Self
11
+ from typing import Any, Self, overload
17
12
 
18
13
  if sys.version_info >= (3, 12):
19
14
  from typing import override
@@ -315,7 +310,7 @@ class VectorDataset: # noqa: PLW1641
315
310
  # Set attributes: always shallow copy
316
311
  # -----------------------------------
317
312
 
318
- self.attrs = AttrDict(attrs or {}) # type: ignore[arg-type]
313
+ self.attrs = AttrDict(attrs or {})
319
314
  self.attrs.update(attrs_kwargs)
320
315
 
321
316
  @classmethod
@@ -1389,7 +1384,7 @@ class GeoVectorDataset(VectorDataset):
1389
1384
  ):
1390
1385
  keys = *self.required_keys, "altitude"
1391
1386
  self.data = VectorDataDict(_empty_vector_dict(keys))
1392
- self.attrs = AttrDict(attrs or {}) # type: ignore[arg-type]
1387
+ self.attrs = AttrDict(attrs or {})
1393
1388
  self.attrs.update(attrs_kwargs)
1394
1389
  return
1395
1390
 
@@ -175,16 +175,13 @@ def parse_pressure_levels(
175
175
 
176
176
  out = arr.tolist()
177
177
  if supported is None:
178
- return out # type: ignore[return-value]
178
+ return out
179
179
 
180
- if missing := set(out).difference(supported): # type: ignore[arg-type]
181
- msg = (
182
- f"Pressure levels {sorted(missing)} are not supported. " # type: ignore[type-var]
183
- f"Supported levels: {supported}"
184
- )
180
+ if missing := set(out).difference(supported):
181
+ msg = f"Pressure levels {sorted(missing)} are not supported. Supported levels: {supported}"
185
182
  raise ValueError(msg)
186
183
 
187
- return out # type: ignore[return-value]
184
+ return out
188
185
 
189
186
 
190
187
  def parse_variables(variables: VariableInput, supported: list[MetVariable]) -> list[MetVariable]:
@@ -61,14 +61,14 @@ class ECMWFAPI(metsource.MetDataSource):
61
61
 
62
62
  # downselect times
63
63
  if not self.timesteps:
64
- self.timesteps = ds["time"].values.astype("datetime64[ns]").tolist() # type: ignore[assignment]
64
+ self.timesteps = ds["time"].values.astype("datetime64[ns]").tolist()
65
65
  else:
66
66
  try:
67
67
  ds = ds.sel(time=self.timesteps)
68
68
  except KeyError as exc:
69
69
  # this snippet shows the missing times for convenience
70
70
  np_timesteps = {np.datetime64(t, "ns") for t in self.timesteps}
71
- missing_times = sorted(np_timesteps.difference(ds["time"].values)) # type: ignore[type-var]
71
+ missing_times = sorted(np_timesteps.difference(ds["time"].values))
72
72
  msg = f"Input dataset is missing time coordinates {[str(t) for t in missing_times]}"
73
73
  raise KeyError(msg) from exc
74
74
 
@@ -708,7 +708,7 @@ class HRES(ECMWFAPI):
708
708
 
709
709
  # set forecast time if it's not defined (this occurs when only the paths param is provided)
710
710
  if not hasattr(self, "forecast_time"):
711
- self.forecast_time = ds["time"].values.astype("datetime64[s]").tolist() # type: ignore[assignment]
711
+ self.forecast_time = ds["time"].values.astype("datetime64[s]").tolist()
712
712
 
713
713
  # check that forecast_time is correct if defined
714
714
  # note the "time" coordinate here is the HRES forecast_time
@@ -723,7 +723,7 @@ class HRES(ECMWFAPI):
723
723
  # set timesteps if not defined
724
724
  # note that "time" is now the actual timestep coordinates
725
725
  if not self.timesteps:
726
- self.timesteps = ds["time"].values.astype("datetime64[s]").tolist() # type: ignore[assignment]
726
+ self.timesteps = ds["time"].values.astype("datetime64[s]").tolist()
727
727
 
728
728
  self.cache_dataset(ds)
729
729
 
@@ -149,7 +149,7 @@ class IFS(metsource.MetDataSource):
149
149
  else:
150
150
  # set timesteps from dataset "time" coordinates
151
151
  # np.datetime64 doesn't covert to list[datetime] unless its unit is us
152
- self.timesteps = ds["time"].values.astype("datetime64[us]").tolist() # type: ignore[assignment]
152
+ self.timesteps = ds["time"].values.astype("datetime64[us]").tolist()
153
153
 
154
154
  # downselect hyam/hybm coefficients by the "lev" coordinate
155
155
  # (this is a 1-indexed verison of nhym)
@@ -595,7 +595,7 @@ class GFSForecast(metsource.MetDataSource):
595
595
  else:
596
596
  # set timesteps from dataset "time" coordinates
597
597
  # np.datetime64 doesn't covert to list[datetime] unless its unit is us
598
- self.timesteps = ds["time"].values.astype("datetime64[us]").tolist() # type: ignore[assignment]
598
+ self.timesteps = ds["time"].values.astype("datetime64[us]").tolist()
599
599
 
600
600
  # if "level" is not in dims and
601
601
  # length of the requested pressure levels is 1
@@ -205,7 +205,7 @@ HEADER_STRUCT_SCHEMA: dict[int, _HeaderBlock] = {
205
205
  }
206
206
 
207
207
 
208
- def parse_himawari_header(content: bytes) -> dict:
208
+ def parse_himawari_header(content: bytes) -> dict[str, dict[str, Any]]:
209
209
  """Parse the Himawari header data.
210
210
 
211
211
  Skips variable-length fields and spares.
@@ -160,7 +160,10 @@ def _extract_band_from_rpath(rpath: str) -> str:
160
160
  return f"B{suffix[:2]}" # B??
161
161
 
162
162
 
163
- def _mask_invalid(data: npt.NDArray[np.uint16], calib_info: dict) -> npt.NDArray[np.float32]:
163
+ def _mask_invalid(
164
+ data: npt.NDArray[np.uint16],
165
+ calib_info: dict[str, Any],
166
+ ) -> npt.NDArray[np.float32]:
164
167
  """Mask invalid data."""
165
168
  error_pixel = calib_info["count_error_pixels"]
166
169
  outside_pixel = calib_info["count_outside_scan_area"]
@@ -218,7 +221,9 @@ def _counts_to_radiance(
218
221
  return counts * gain + const
219
222
 
220
223
 
221
- def _load_image_data(content: bytes, metadata: dict) -> npt.NDArray[np.float32]:
224
+ def _load_image_data(
225
+ content: bytes, metadata: dict[str, dict[str, Any]]
226
+ ) -> npt.NDArray[np.float32]:
222
227
  counts = _load_raw_counts(content, metadata)
223
228
 
224
229
  calib_info = metadata["calibration_information"]
@@ -230,7 +235,10 @@ def _load_image_data(content: bytes, metadata: dict) -> npt.NDArray[np.float32]:
230
235
  return _radiance_to_brightness_temperature(radiance, calib_info)
231
236
 
232
237
 
233
- def _ahi_fixed_grid(proj_info: dict, arr: np.ndarray) -> tuple[xr.DataArray, xr.DataArray]:
238
+ def _ahi_fixed_grid(
239
+ proj_info: dict[str, Any],
240
+ arr: np.ndarray,
241
+ ) -> tuple[xr.DataArray, xr.DataArray]:
234
242
  n_lines, n_columns = arr.shape
235
243
 
236
244
  i = np.arange(n_columns, dtype=np.float32)
@@ -277,7 +285,11 @@ def _himawari_proj4_string(proj_info: dict[str, Any]) -> str:
277
285
  return f"+proj=geos +h={h} +a={a} +b={b} +lon_0={lon} +sweep=x +units=m +no_defs"
278
286
 
279
287
 
280
- def _earth_disk_mask(proj_info: dict, x: xr.DataArray, y: xr.DataArray) -> npt.NDArray[np.bool_]:
288
+ def _earth_disk_mask(
289
+ proj_info: dict[str, Any],
290
+ x: xr.DataArray,
291
+ y: xr.DataArray,
292
+ ) -> npt.NDArray[np.bool_]:
281
293
  """Return a boolean mask where True indicates pixels over the Earth disk."""
282
294
  a = proj_info["equatorial_radius"] * 1000.0 # km -> m
283
295
  b = proj_info["polar_radius"] * 1000.0 # km -> m
@@ -301,7 +313,7 @@ def _earth_disk_mask(proj_info: dict, x: xr.DataArray, y: xr.DataArray) -> npt.N
301
313
  return discriminant >= 0.0
302
314
 
303
315
 
304
- def _parse_start_time(metadata: dict) -> datetime.datetime:
316
+ def _parse_start_time(metadata: dict[str, dict[str, Any]]) -> datetime.datetime:
305
317
  """Parse the start time from the metadata."""
306
318
  mjd_value = metadata["basic_information"]["obs_start_time"]
307
319
  mjd_epoch = datetime.datetime(1858, 11, 17)
@@ -355,9 +367,10 @@ def _parse_s3_raw_data(raw_data: list[bytes]) -> xr.DataArray:
355
367
 
356
368
 
357
369
  class Himawari:
358
- """Support for Himawari-8/9 satellite data accessed via AWS S3.
370
+ """Support for Himawari-8/9 satellite data access via AWS S3.
359
371
 
360
- This interface requires the ``s3fs`` package.
372
+ This interface requires the ``s3fs`` package to download data from the
373
+ `AWS Public Dataset <https://registry.opendata.aws/himawari/>`_.
361
374
 
362
375
  Parameters
363
376
  ----------
@@ -1,10 +1,10 @@
1
1
  """Download and parse Sentinel metadata."""
2
2
 
3
+ import datetime
3
4
  import os
4
5
  import re
5
6
  import xml.etree.ElementTree as ET
6
7
  from collections.abc import Collection
7
- from datetime import datetime, timedelta, timezone
8
8
 
9
9
  import numpy as np
10
10
  import numpy.typing as npt
@@ -464,10 +464,10 @@ def parse_ephemeris_sentinel(datatsrip_metadata_path: str) -> pd.DataFrame:
464
464
  if position_elem is None or position_elem.text is None:
465
465
  continue # skip if missing
466
466
 
467
- gps_time = datetime.strptime(gps_time_elem.text, "%Y-%m-%dT%H:%M:%S")
467
+ gps_time = datetime.datetime.strptime(gps_time_elem.text, "%Y-%m-%dT%H:%M:%S")
468
468
 
469
469
  # Convert GPS to UTC time as there is a few seconds between them
470
- utc_time = gps_to_utc(gps_time).replace(tzinfo=timezone.utc)
470
+ utc_time = gps_to_utc(gps_time).replace(tzinfo=datetime.UTC)
471
471
 
472
472
  # Parse positions in ECEF coordinate system
473
473
  x, y, z = map(float, position_elem.text.split())
@@ -643,30 +643,30 @@ def get_time_delay_detectors(
643
643
  # Time helper functions
644
644
 
645
645
 
646
- def gps_to_utc(gps_time: datetime) -> datetime:
646
+ def gps_to_utc(gps_time: datetime.datetime) -> datetime.datetime:
647
647
  """Convert GPS time (datetime object) to UTC time.
648
648
 
649
649
  https://gssc.esa.int/navipedia/index.php/Transformations_between_Time_Systems
650
650
  """
651
651
 
652
- gps_tai_offset = timedelta(seconds=19)
653
- utc_tai_offset = timedelta(seconds=37)
652
+ gps_tai_offset = datetime.timedelta(seconds=19)
653
+ utc_tai_offset = datetime.timedelta(seconds=37)
654
654
 
655
655
  # Convert GPS time to UTC
656
656
  return gps_time + gps_tai_offset - utc_tai_offset
657
657
 
658
658
 
659
- def _calculate_average_time(times: Collection[datetime]) -> datetime:
659
+ def _calculate_average_time(times: Collection[datetime.datetime]) -> datetime.datetime:
660
660
  """Return the average time from a list of times."""
661
661
  # Compute the average time
662
662
  avg_timestamp = sum(t.timestamp() for t in times) / len(times)
663
- return datetime.fromtimestamp(avg_timestamp)
663
+ return datetime.datetime.fromtimestamp(avg_timestamp)
664
664
 
665
665
 
666
666
  def _calculate_timedeltas(detector_times: dict[int, str]) -> dict[int, pd.Timedelta]:
667
667
  """Calculate the time difference between a detector and the average time."""
668
668
  detector_times_dt = {
669
- detector_id: datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S.%f")
669
+ detector_id: datetime.datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S.%f")
670
670
  for detector_id, time_str in detector_times.items()
671
671
  }
672
672
 
@@ -305,8 +305,8 @@ class SyntheticFlight:
305
305
  *src,
306
306
  az,
307
307
  npts,
308
- m_per_timestep, # type: ignore
309
- return_back_azimuth=False, # type: ignore
308
+ m_per_timestep,
309
+ return_back_azimuth=False,
310
310
  )
311
311
  longitude = np.asarray(result.lons)
312
312
  latitude = np.asarray(result.lats)
@@ -30,7 +30,7 @@ class habit_dirichlet(rv_frozen):
30
30
  - Table 2 in :cite:`schumannEffectiveRadiusIce2011`
31
31
  """
32
32
 
33
- def __init__(self, C: float = 96.0):
33
+ def __init__(self, C: float = 96.0) -> None:
34
34
  self.C = C
35
35
 
36
36
  def rvs(self, *args: Any, **kwds: Any) -> npt.NDArray[np.float32]:
@@ -236,7 +236,7 @@ def initial_ice_particle_number(
236
236
  phase, [:math:`# m^{-1}`]
237
237
  """
238
238
  if min_aei is not None:
239
- aei = np.clip(aei, min=min_aei) # type: ignore[arg-type,call-overload]
239
+ aei = np.clip(aei, min=min_aei) # type: ignore[call-overload]
240
240
  return fuel_dist * aei
241
241
 
242
242
 
@@ -2228,7 +2228,7 @@ def compare_cocip_with_goes(
2228
2228
  fig = plt.figure(figsize=(1.2 * x_dim, y_dim))
2229
2229
  pc = ccrs.PlateCarree()
2230
2230
  ax = fig.add_subplot(projection=pc, extent=bbox)
2231
- ax.coastlines() # type: ignore[attr-defined]
2231
+ ax.coastlines()
2232
2232
  ax.imshow(rgb, extent=extent, transform=transform)
2233
2233
 
2234
2234
  ax.set_xticks([spatial_bbox[0], spatial_bbox[2]], crs=ccrs.PlateCarree())
@@ -114,7 +114,7 @@ class CocipGrid(models.Model):
114
114
  rad: MetDataset,
115
115
  params: dict[str, Any] | None = None,
116
116
  **params_kwargs: Any,
117
- ):
117
+ ) -> None:
118
118
  super().__init__(met, params=params, **params_kwargs)
119
119
 
120
120
  compute_tau_cirrus = self.params["compute_tau_cirrus_in_model_init"]
@@ -386,7 +386,7 @@ class CocipGrid(models.Model):
386
386
  "dt_integration": dt_integration_str,
387
387
  "aircraft_type": self.get_source_param("aircraft_type"),
388
388
  "pycontrails_version": pycontrails.__version__,
389
- **self.source.attrs, # type: ignore[dict-item]
389
+ **self.source.attrs,
390
390
  }
391
391
  if ap_model := self.params["aircraft_performance"]:
392
392
  attrs["ap_model"] = type(ap_model).__name__
@@ -2210,7 +2210,7 @@ def result_to_metdataset(
2210
2210
  # Update source
2211
2211
  for k, v in data_vars.items(): # type: ignore[assignment]
2212
2212
  source[k] = v
2213
- source.attrs.update(attrs) # type: ignore[arg-type]
2213
+ source.attrs.update(attrs)
2214
2214
 
2215
2215
  # Return reference to source
2216
2216
  return source
@@ -590,7 +590,7 @@ def _evolve_one_step(
590
590
  vector,
591
591
  dz_m=dz_m,
592
592
  dt=dt, # type: ignore[arg-type]
593
- max_depth=max_depth, # type: ignore[arg-type]
593
+ max_depth=max_depth,
594
594
  verbose_outputs=verbose_outputs,
595
595
  )
596
596
  out["azimuth"] = azimuth_2
@@ -24,12 +24,12 @@ DEFAULT_EXHAUST_T = 600.0 # Exhaust temperature, [K]
24
24
  EXPERIMENTAL_WARNING = True
25
25
 
26
26
 
27
- class ParticleType(enum.Enum):
27
+ class ParticleType(enum.StrEnum):
28
28
  """Enumeration of particle types."""
29
29
 
30
- NVPM = "nvPM"
31
- VPM = "vPM"
32
- AMBIENT = "ambient"
30
+ NVPM = enum.auto()
31
+ VPM = enum.auto()
32
+ AMBIENT = enum.auto()
33
33
 
34
34
 
35
35
  @dataclasses.dataclass(frozen=True)
@@ -442,7 +442,7 @@ class ExponentialBoostLatitudeCorrectionHumidityScaling(HumidityScaling):
442
442
  met: MetDataset | None = None,
443
443
  params: dict[str, Any] | None = None,
444
444
  **params_kwargs: Any,
445
- ):
445
+ ) -> None:
446
446
  if (params is None or "level_type" not in params) and ("level_type" not in params_kwargs):
447
447
  msg = (
448
448
  "The default level_type will change from 'pressure' to 'model' "
@@ -863,7 +863,7 @@ class HistogramMatching(HumidityScaling):
863
863
  met: MetDataset | None = None,
864
864
  params: dict[str, Any] | None = None,
865
865
  **params_kwargs: Any,
866
- ):
866
+ ) -> None:
867
867
  if (params is None or "level_type" not in params) and (
868
868
  params_kwargs is None or "level_type" not in params_kwargs
869
869
  ):
@@ -639,9 +639,9 @@ def ps_nominal_optimize_mach(
639
639
  if sin_a is None or cos_a is None:
640
640
  msg = "Segment angles must be provide if wind data is specified"
641
641
  raise ValueError(msg)
642
- headwind = -(northward_wind * cos_a + eastward_wind * sin_a) # type: ignore[misc]
642
+ headwind = -(northward_wind * cos_a + eastward_wind * sin_a)
643
643
  else:
644
- headwind = 0.0 # type: ignore
644
+ headwind = 0.0
645
645
 
646
646
  min_mach = ps_operational_limits.minimum_mach_num(
647
647
  air_pressure=level * 100.0,
pycontrails/models/sac.py CHANGED
@@ -133,7 +133,7 @@ class SAC(Model):
133
133
 
134
134
  G = slope_mixing_line(specific_humidity, air_pressure, engine_efficiency, ei_h2o, q_fuel)
135
135
  T_sat_liquid_ = T_sat_liquid(G)
136
- rh_crit_sac = rh_critical_sac(air_temperature, T_sat_liquid_, G) # type: ignore[type-var]
136
+ rh_crit_sac = rh_critical_sac(air_temperature, T_sat_liquid_, G)
137
137
  rh = thermo.rh(specific_humidity, air_temperature, air_pressure) # type: ignore[type-var]
138
138
  sac_ = sac(rh, rh_crit_sac)
139
139
 
@@ -91,7 +91,7 @@ def tau_cirrus(met: MetDataset) -> xr.DataArray:
91
91
  # dask.array.gradient expects at least 2 elements in each chunk
92
92
  level_axis = geopotential_height.get_axis_num("level")
93
93
  if geopotential_height.chunks:
94
- level_chunks = geopotential_height.chunks[level_axis] # type: ignore[call-overload, index]
94
+ level_chunks = geopotential_height.chunks[level_axis]
95
95
  if any(chunk < 2 for chunk in level_chunks):
96
96
  geopotential_height = geopotential_height.chunk(level=-1)
97
97
 
@@ -269,7 +269,7 @@ def mk05_e_sat_liquid_prime(T: ArrayScalarLike) -> ArrayScalarLike:
269
269
  Derivative of :func:`mk05_e_sat_liquid`
270
270
  """
271
271
  tanh_term = np.tanh(0.0415 * (T - 218.8))
272
- return mk05_e_sat_liquid(T) * ( # type: ignore[return-value]
272
+ return mk05_e_sat_liquid(T) * (
273
273
  6763.22 / T**2
274
274
  - 4.21 / T
275
275
  + 0.000367
@@ -6,7 +6,7 @@ from collections.abc import Iterator
6
6
  from typing import Any
7
7
 
8
8
 
9
- def chunk_list(lst: list, n: int) -> Iterator[list[Any]]:
9
+ def chunk_list(lst: list[Any], n: int) -> Iterator[list[Any]]:
10
10
  """Yield successive n-sized chunks from list."""
11
11
 
12
12
  for i in range(0, len(lst), n):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycontrails
3
- Version: 0.57.0
3
+ Version: 0.58.0
4
4
  Summary: Python library for modeling aviation climate impacts
5
5
  Author-email: "Contrails.org" <py@contrails.org>
6
6
  License-Expression: Apache-2.0
@@ -13,23 +13,23 @@ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Science/Research
14
14
  Classifier: Operating System :: OS Independent
15
15
  Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3.10
17
16
  Classifier: Programming Language :: Python :: 3.11
18
17
  Classifier: Programming Language :: Python :: 3.12
19
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
20
  Classifier: Programming Language :: Python :: 3 :: Only
21
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
22
  Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
23
23
  Classifier: Topic :: Scientific/Engineering :: GIS
24
24
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
25
+ Requires-Python: >=3.11
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  License-File: NOTICE
29
29
  Requires-Dist: dask>=2022.3
30
30
  Requires-Dist: numpy>=1.22
31
31
  Requires-Dist: pandas>=2.0
32
- Requires-Dist: scipy>=1.10
32
+ Requires-Dist: scipy>=1.12
33
33
  Requires-Dist: typing-extensions>=4.5; python_version < "3.12"
34
34
  Requires-Dist: xarray>=2022.3
35
35
  Provides-Extra: complete
@@ -140,7 +140,7 @@ Documentation and examples available at [py.contrails.org](https://py.contrails.
140
140
 
141
141
  ### Install with pip
142
142
 
143
- You can install pycontrails from PyPI with `pip` (Python 3.10 or later required):
143
+ You can install pycontrails from PyPI with `pip` (Python 3.11 or later required):
144
144
 
145
145
  ```bash
146
146
  $ pip install pycontrails
@@ -1,25 +1,25 @@
1
- pycontrails-0.57.0.dist-info/RECORD,,
2
- pycontrails-0.57.0.dist-info/WHEEL,sha256=0rn5ODYhsjI3KEHrk9RXNWLZT7Rowo6wA0Jh2iAIJLk,138
3
- pycontrails-0.57.0.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
4
- pycontrails-0.57.0.dist-info/METADATA,sha256=hMlpeGFk6SKCi-56vcSantrPjYPn4lD_eF9LyZaxetE,9129
5
- pycontrails-0.57.0.dist-info/licenses/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
6
- pycontrails-0.57.0.dist-info/licenses/NOTICE,sha256=fiBPdjYibMpDzf8hqcn7TvAQ-yeK10q_Nqq24DnskYg,1962
7
- pycontrails/_version.py,sha256=nChqOuolnzO53ha9g4Di1DOVXj5DhnPGthV03BPY4tY,714
1
+ pycontrails-0.58.0.dist-info/RECORD,,
2
+ pycontrails-0.58.0.dist-info/WHEEL,sha256=0rn5ODYhsjI3KEHrk9RXNWLZT7Rowo6wA0Jh2iAIJLk,138
3
+ pycontrails-0.58.0.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
4
+ pycontrails-0.58.0.dist-info/METADATA,sha256=-pOZxATs-SGOiS7FppQtQYehP9MYFbQV2l_VnbptyJg,9129
5
+ pycontrails-0.58.0.dist-info/licenses/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
6
+ pycontrails-0.58.0.dist-info/licenses/NOTICE,sha256=fiBPdjYibMpDzf8hqcn7TvAQ-yeK10q_Nqq24DnskYg,1962
7
+ pycontrails/_version.py,sha256=ife3IDQF-DAoy1CwSUU90Z0xGG9P1GsYeBh-Ic3uecI,714
8
8
  pycontrails/__init__.py,sha256=9ypSB2fKZlKghTvSrjWo6OHm5qfASwiTIvlMew3Olu4,2037
9
9
  pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- pycontrails/core/vector.py,sha256=mQG1NmULztZjTK7O4waiggiR_4dA7LhTeuyQnLJRHsE,73650
10
+ pycontrails/core/vector.py,sha256=hms3hea2Y86LOuZFfOqzGZToqpOVksgXikM_S5w355w,73498
11
11
  pycontrails/core/models.py,sha256=3mDTqp1V5aae9akuYwbMGIUEkESKSYTjZeyu2IiMW7s,43915
12
- pycontrails/core/interpolation.py,sha256=wovjj3TAf3xonVxjarclpvZLyLq6N7wZQQXsI9hT3YA,25713
13
- pycontrails/core/fleet.py,sha256=3uRByONZW8yHiS2gmgwOozVm6ksUVdsy6Gyf6jYjSPY,16703
14
- pycontrails/core/flight.py,sha256=_a4AJjewA_lcW_qjBPga9z6JlVAev-es_M8TFVyX_1E,81590
12
+ pycontrails/core/interpolation.py,sha256=TpLDfx-Bx2tGDjWJOyU4n9Dkz6Obl05dB7Ol5diYdsk,24816
13
+ pycontrails/core/fleet.py,sha256=a_vVwAjMbjkhszg7ejP3V0yly_wJ9Va_OQEATG-9UHw,16572
14
+ pycontrails/core/flight.py,sha256=dmqO1PxADMHIcK9U8XSnuXbP59ftQgeKvdI2xGP04ig,81460
15
15
  pycontrails/core/fuel.py,sha256=kJZ3P1lPm1L6rdPREM55XQ-VfJ_pt35cP4sO2Nnvmjs,4332
16
- pycontrails/core/polygon.py,sha256=g7YqWzUbOHWT65XrLqLUZLrQXYcx_x1NcJ041-Cj7UY,18070
17
- pycontrails/core/cache.py,sha256=Zng2edpSKYNyrkD4j1omYMCzJ7Lm762YJnUmqoVu1Z4,28161
16
+ pycontrails/core/polygon.py,sha256=kXYwj1Xy-mo8GEWXFAO_OJEtZbGx11DE_sZw6iyNvN4,17995
17
+ pycontrails/core/cache.py,sha256=JQIy1sQf0Vil7wlEk9ZIvVacnOD4wM3X8-UkMFjR2wQ,28177
18
18
  pycontrails/core/__init__.py,sha256=p0O09HxdeXU0X5Z3zrHMlTfXa92YumT3fJ8wJBI5ido,856
19
- pycontrails/core/rgi_cython.cpython-313-darwin.so,sha256=qnILk5Y3Ggn29xtH8odfBx1A3NM7Zu17SrTVWzZwf14,340752
19
+ pycontrails/core/rgi_cython.cpython-313-darwin.so,sha256=fAgkmk74eiudQSAW4EC-HYOztPMRphPQaXBsc-ZPo-Y,325816
20
20
  pycontrails/core/flightplan.py,sha256=0mvA3IO19Sap-7gwpmEIV35_mg6ChvajwhurvjZZt_U,7521
21
- pycontrails/core/met.py,sha256=O9W6RaEwUsg7ZERR47Q-6fYjg13BzOZtcQdw92444xg,103987
22
- pycontrails/core/aircraft_performance.py,sha256=Kk_Rb61jDOWPmCQHwn2jR5vMPmB8b3aq1iTWfiUMj9U,28232
21
+ pycontrails/core/met.py,sha256=JosS0DXGEB4NBQ40WU1b4yG04h3DkT7Vs-P0jK-UkCY,104453
22
+ pycontrails/core/aircraft_performance.py,sha256=CPIgIi5nUuCHiNVLAvZcWECRfakmMd-wUWd3lMA6oGM,28204
23
23
  pycontrails/core/airports.py,sha256=CzZrgJNZ7wtNv8vg9sJczMhFov7k0gmrGR4tRKCH8i8,6782
24
24
  pycontrails/core/met_var.py,sha256=g69vqbxpJeXEQU8vrrcoUR1PX3zCo2-k3au1Lv2TiIw,12027
25
25
  pycontrails/core/coordinates.py,sha256=0ySsHtqTon7GMbuwmmxMbI92j3ueMteJZh4xxNm5zto,5391
@@ -28,52 +28,52 @@ pycontrails/datalib/landsat.py,sha256=6ylDkAjnyX7b4ZbHn4bprO8HB8ADPFyMkwWehIs8FL
28
28
  pycontrails/datalib/geo_utils.py,sha256=w6VYhJQeMpBXaBclqANv4Nn0yqPIxlQr6GTUpjArTj0,9070
29
29
  pycontrails/datalib/__init__.py,sha256=hW9NWdFPC3y_2vHMteQ7GgQdop3917MkDaf5ZhU2RBY,369
30
30
  pycontrails/datalib/sentinel.py,sha256=ed1l1avq8lBvQinY_vNSsWRcpqxUdAPY61AGyPcLawo,23532
31
- pycontrails/datalib/himawari/himawari.py,sha256=G1YHa3b_l1J0tnk_TOz4dUCn-Q522rT6RR7lS3-NPE8,23120
31
+ pycontrails/datalib/himawari/himawari.py,sha256=uJXhu1URdu3Hen9wMwkgxyPHdjd_xyIt9E7WblVy2xQ,23327
32
32
  pycontrails/datalib/himawari/__init__.py,sha256=SWupVbeuyK07IPDCgiNjN6hoLB7hlceabJ3fixhDkl0,619
33
- pycontrails/datalib/himawari/header_struct.py,sha256=Eu2tf5BXNDkW7VH-bSGCOwnrOiKNv73eCBrY09r0UPo,9954
34
- pycontrails/datalib/_met_utils/metsource.py,sha256=B4Gd9gkfMMlXe-xc_xcNNZAJ0gOeRelvrBsFyk6tEs4,24151
33
+ pycontrails/datalib/himawari/header_struct.py,sha256=WbPkNBNUVm8tGKU8wpj4rldY17g5MHQ_OfbWicZSokc,9975
34
+ pycontrails/datalib/_met_utils/metsource.py,sha256=mlKcRko5ZKuYK5uwWn6AAgUSJLMQAYq1nFqskVMGgYo,23999
35
35
  pycontrails/datalib/ecmwf/arco_era5.py,sha256=7HXQU5S02PzX9Ew2ZrDKSp0tDEG1eeVAvbP3decmm20,12437
36
36
  pycontrails/datalib/ecmwf/era5.py,sha256=4ULNdDlUN0kP6Tbp8D_-Bc12nAsLf0iNfZaDoj_AoZU,18952
37
37
  pycontrails/datalib/ecmwf/era5_model_level.py,sha256=AO7ePIGZtavx5nQSPYP4p07RNZeg3bbzmoZC7RUC4Gg,19354
38
- pycontrails/datalib/ecmwf/hres.py,sha256=isRQkybVZyKxVvqpQyGQsMtePFfv_qqAMBQ98wDLYtc,29680
38
+ pycontrails/datalib/ecmwf/hres.py,sha256=tvIXPJjXgijtVdsSyPGtvKf0h5GUczBSsovKEbCgBxg,29624
39
39
  pycontrails/datalib/ecmwf/variables.py,sha256=lU3BNe265XVhCXvdMwZqfkWQwtsetZxVRLSfPqHFKAE,9913
40
40
  pycontrails/datalib/ecmwf/hres_model_level.py,sha256=CcxMKiFJyLvM9njmBVywAXJxyWE7atsgHXBubKJQqHM,17779
41
41
  pycontrails/datalib/ecmwf/__init__.py,sha256=wdfhplEaW2UKTItIoshTtVEjbPyfDYoprTJNxbKZuvA,2021
42
- pycontrails/datalib/ecmwf/common.py,sha256=qRMSzDQikGMi3uqvz-Y57e3biHPzSoVMfUwOu9iTxHc,4024
42
+ pycontrails/datalib/ecmwf/common.py,sha256=axOxvdrey9YD34uk0Ocav08MxKvC2uVaiwvyQgFZMEw,3970
43
43
  pycontrails/datalib/ecmwf/model_levels.py,sha256=_kgpnogaS6MlfvTX9dB5ASTHFUlZuQ_DRb-VADwEa0k,16996
44
- pycontrails/datalib/ecmwf/ifs.py,sha256=0swHe6tFc5Fbu9e4_jREW0H-xYHYLtxjNoE3aUUlgvc,10761
44
+ pycontrails/datalib/ecmwf/ifs.py,sha256=_1UarorPp9VlgFZc-NnZy8YnfEqBdp7GV1A-ye6JqS8,10733
45
45
  pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv,sha256=PmvGLRzn6uuCKSwiasSuVcehvvmSaqP7cnLuN6hhCQQ,9788
46
- pycontrails/datalib/gfs/gfs.py,sha256=ROFxCiLudxyMqdSJAVNecKbP1nWZktiRK8Gyjede2wg,22266
46
+ pycontrails/datalib/gfs/gfs.py,sha256=VqS0MRLawgzkBDpjDUYoswXByIy6XUqA9XP7lM1ueBk,22238
47
47
  pycontrails/datalib/gfs/variables.py,sha256=4ALR4zhYW8tQVlNVHrd0CK8oRNSe_2OkW3ELeaImtAI,3135
48
48
  pycontrails/datalib/gfs/__init__.py,sha256=pXNjb9cJC6ngpuCnoHnmVZ2RHzbHZ0AlsyGvgcdcl2E,684
49
49
  pycontrails/datalib/spire/spire.py,sha256=h25BVgSr7E71Ox3-y9WgqFvp-54L08yzb2Ou-iMl7wM,24242
50
50
  pycontrails/datalib/spire/__init__.py,sha256=3-My8yQItS6PL0DqXgNaltLqvN6T7nbnNnLD-sy7kt4,186
51
51
  pycontrails/datalib/spire/exceptions.py,sha256=U0V_nZTLhxJwrzldvU9PdESx8-zLddRH3FmzkJyFyrI,1714
52
- pycontrails/datalib/leo_utils/sentinel_metadata.py,sha256=h5ieEwtmeAAtiIBdfOF8HaoE121ba0DrKb7dLvuaAeI,25506
52
+ pycontrails/datalib/leo_utils/sentinel_metadata.py,sha256=UXpv0XPmtQezVfprcFYNlRpV7nherCDW4oKStBZTdT8,25552
53
53
  pycontrails/datalib/leo_utils/landsat_metadata.py,sha256=B455-Yq6HTj0Se0dS4c_2F5ZjcATu2yNK1gyoIlgLMg,10628
54
54
  pycontrails/datalib/leo_utils/__init__.py,sha256=-SEAc1f7zEbJHcKjgwLuhnIwte9W-ystFNLvfC4RE94,213
55
55
  pycontrails/datalib/leo_utils/vis.py,sha256=-fLcm1D5cP6lThVHovV3MJSiadWyTUAvYDMvr4drMU4,1802
56
56
  pycontrails/datalib/leo_utils/search.py,sha256=KbHQ2GARacDuUz3zEJuATSga-R32dQFVTqhZgndHUZI,8686
57
57
  pycontrails/datalib/leo_utils/correction.py,sha256=cHf4PhHNYMqdVAFYNiTnjcVyqr1vCBMCKi0IjKB_3pw,9564
58
58
  pycontrails/datalib/leo_utils/static/bq_roi_query.sql,sha256=xq6-tJyz0-bUwW0KjQymqygjH3WlQBmyBtP7Ci7SBe8,260
59
- pycontrails/ext/synthetic_flight.py,sha256=wROBQErfr_IhEPndC97fuWbnZQega2Z89VhzoXzZMO8,16802
59
+ pycontrails/ext/synthetic_flight.py,sha256=DdDy1gih8bwdBjsTsvi4mK3lJhcNKlhyllpOWaaASio,16770
60
60
  pycontrails/ext/cirium.py,sha256=DFPfRwLDwddpucAPRQhyT4bDGh0VvvoViMUd3pidam8,415
61
61
  pycontrails/ext/empirical_grid.py,sha256=FPNQA0x4nVwBXFlbs3DgIapSrXFYhoc8b8IX0M4xhBc,4363
62
62
  pycontrails/ext/bada.py,sha256=YlQq4nnFyWza1Am2e2ZucpaICHDuUFRTrtVzIKMzf9s,1091
63
- pycontrails/utils/iteration.py,sha256=q_vb39VjxRr4hqTyPYko3gK4sboJOJf_Evq6m_2DL-g,319
63
+ pycontrails/utils/iteration.py,sha256=YGcex8pBDegU9dbDJmarxqdPzebAk_Gnc8DK3khY9SY,324
64
64
  pycontrails/utils/__init__.py,sha256=Gt_57sBgfliFSxx9sDpuchykFDxmM11Wg9xAeSqPcnI,32
65
65
  pycontrails/utils/types.py,sha256=1AaY1x_qGlYAl08xg6PS0MPKm3OZwFBM7xLI_nHK7EY,4869
66
66
  pycontrails/utils/temp.py,sha256=lGU0b_R8ze4yKlsOusHIIBaoNFBrmrB3vBjgHRlfcXk,1109
67
67
  pycontrails/utils/json.py,sha256=oTiO8xh603esfBGaGVmA5eUzR0NhAqNpQCegMMgnSbg,5896
68
68
  pycontrails/utils/dependencies.py,sha256=ATP45xYdUbIyGFzgbOe5SbokMytvB84TcexUEFnEUZE,2559
69
- pycontrails/models/extended_k15.py,sha256=uZ32wC5HNCJ5M9u4V4x10QoXn2hZRZy1tKcSvNJE0K4,47978
69
+ pycontrails/models/extended_k15.py,sha256=ZNL1XDvw-aG24_zGbP6Xkn203oVNqIwMau2exkiADS0,47994
70
70
  pycontrails/models/pcc.py,sha256=0Qdl4u8PmUEpNYd398glTChkbTwsh83wYPt0Bmi8qd8,11068
71
- pycontrails/models/tau_cirrus.py,sha256=2Z4egt-QFprkyITRgtarA5alOTTQRQbjzgmSqE49_1g,5778
71
+ pycontrails/models/tau_cirrus.py,sha256=wMhh8xQ1byW9WdzRSJIAVDMeyZTBl_PUKMXdF6Zy1uE,5740
72
72
  pycontrails/models/__init__.py,sha256=dQTOLQb7RdUdUwslt5se__5y_ymbInBexQmNrmAeOdE,33
73
73
  pycontrails/models/issr.py,sha256=_qIKDgO0Owxeb0Q4WJlxcn1FJEvF3QDU-cqh2fpDsBo,7404
74
- pycontrails/models/sac.py,sha256=8Vx5wg4-Kb8l4GK67wp7VNVpdFM4Wyux1xKuNrjZ_IQ,15516
74
+ pycontrails/models/sac.py,sha256=xTPTuCwYf8_goC5xJXxRl0NRZADuNOzyGJh13cuunQM,15490
75
75
  pycontrails/models/accf.py,sha256=_tunWpw1sYW8ES8RvpdhNahXwaf4LwdHMEdXhv7-cCI,13566
76
- pycontrails/models/dry_advection.py,sha256=3Vf-oug6an4WRHBOMlXZMsD7B6lx-ieMr-8mkSQyP3c,20496
76
+ pycontrails/models/dry_advection.py,sha256=CwR37hwQEAWNrFKBrCjUmaaB3D-ToaQrxq2x7Ek5Hc4,20470
77
77
  pycontrails/models/pcr.py,sha256=Xde0aF8cMV9jTQ_uI2UvdHSLqotVUgPutb1Wgq7LtfY,5374
78
78
  pycontrails/models/emissions/__init__.py,sha256=CZB2zIkLUI3NGNmq2ddvRYjEtiboY6PWJjiEiXj_zII,478
79
79
  pycontrails/models/emissions/ffm2.py,sha256=mAvBHnp-p3hIn2fjKGq50eaMHi0jcb5hA5uXbJGeE9I,12068
@@ -87,36 +87,36 @@ pycontrails/models/apcemm/inputs.py,sha256=88GylkiaymEW_XZeFxLsICI9wV6kl8wVYsuyT
87
87
  pycontrails/models/apcemm/utils.py,sha256=Ex6EqXin6yoJv2WWhBotSzhjzUlFNZm2MDgL4CvvX6E,17082
88
88
  pycontrails/models/apcemm/apcemm.py,sha256=rKvIaEsqtLbZ5h4o4EOY4Ge4-HdPn2X4M1lEUFDvr68,39975
89
89
  pycontrails/models/apcemm/static/apcemm_yaml_template.yaml,sha256=uAZkc57OUvDMjgX6F5f6hgDh3Hgg1NbHWRUFSiv0DEI,6745
90
- pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=ntNnhqo-lLvuQ6ntApDAtalZF6vmF8bK0PKUgmIye8c,38579
90
+ pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=l5S63mH6K5DOWk8LYtlbOz8wNbJQBcWd-DqCBUxKsik,38595
91
91
  pycontrails/models/humidity_scaling/__init__.py,sha256=nqsab_j9BCwMbTfCn4BjXMdhItlvNKkgUJ9-lb8RyIo,1119
92
92
  pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq,sha256=tfYhbafF9Z-gGCg6VQ1YBlOaK_01e65Dc6s9b-hQ6Zo,286375
93
93
  pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq,sha256=pShCvNUo0NYtAHhT9IBRuj38X9jejdlKfv-ZoOKmtKI,35943
94
94
  pycontrails/models/cocip/radiative_forcing.py,sha256=WleEc6hqrAlqJYtL3oZjRW7gJr_pPQNESJdOXi6oKqE,44686
95
95
  pycontrails/models/cocip/wind_shear.py,sha256=m6ZlWjORfI-lI-D74Z_dIMOHnK4FDYmkb0S6vSpKTO8,3868
96
96
  pycontrails/models/cocip/cocip.py,sha256=PgNuBzvTdkrANCjS_N9GscOkT9WXTaQUkI-IGxmMG9g,104847
97
- pycontrails/models/cocip/output_formats.py,sha256=dBT5-1yJsX_T_EoVhuja8ow4u-WlJRJ-7DihCgkyl7U,83980
97
+ pycontrails/models/cocip/output_formats.py,sha256=nnEfwBdWRE7InoL9f8YLVtqK3cN4izbKuRMYds_6qOU,83950
98
98
  pycontrails/models/cocip/__init__.py,sha256=CWrkNd6S3ZJq04pjTc2W22sVAJeJD3bJJRy_zLW8Kkc,962
99
99
  pycontrails/models/cocip/cocip_params.py,sha256=BWmTt6yE4m-LM7lyCtj05FK3wVvU9n7iVnuauGq3jtA,12808
100
100
  pycontrails/models/cocip/wake_vortex.py,sha256=F5S8n4eBrBM-7qNcVUtX3IrXD7Kt9pWnrKj6UK-HGeA,14555
101
- pycontrails/models/cocip/cocip_uncertainty.py,sha256=jjejXM6Cd3icJuiQ9_T_qU0B983t6_2sRwQJiZxQ36o,12249
101
+ pycontrails/models/cocip/cocip_uncertainty.py,sha256=TZ85xAbDc5zRgQKP7wb3AfHoUIvkfHycuX86dwZCqwM,12257
102
102
  pycontrails/models/cocip/radiative_heating.py,sha256=1U4SQWwogtyQ2u6J996kAHP0OfpZ3hH2_x4Cyt3Cy8U,18984
103
- pycontrails/models/cocip/contrail_properties.py,sha256=TpKzY7ZXLjbSKKGU6CVRLiFB_bp2O9YU6PXuLkAs1T0,55741
103
+ pycontrails/models/cocip/contrail_properties.py,sha256=BRldMsxNIYlKHg5ozD5_wWtJ7OGTcQvinkQUioHqaqk,55732
104
104
  pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=bIRS-Z4MRMdkYtth2RaDe5h1ZN0HvCE_Sw96PXQEHKQ,18931
105
105
  pycontrails/models/ps_model/__init__.py,sha256=Fuum5Rq8ya8qkvbeq2wh6NDo-42RCRnK1Y-2syYy0Ck,553
106
106
  pycontrails/models/ps_model/ps_model.py,sha256=fgFekJpGuAu73KvpfLhlAbIwR7JJGwQpLILWmrONywc,31925
107
107
  pycontrails/models/ps_model/ps_aircraft_params.py,sha256=I2nBkdnRo9YGMn-0k35ooYpzPNJkHyEH5cU3K-Cz8b0,13350
108
108
  pycontrails/models/ps_model/ps_operational_limits.py,sha256=XwMHO8yu8EZUWtxRgjRKwxmCrmKGoHO7Ob6nlfkrthI,16441
109
- pycontrails/models/ps_model/ps_grid.py,sha256=rBsCEQkGY4cTf57spF0sqCfOo4oC4auE8ngS_mcl0VM,26207
109
+ pycontrails/models/ps_model/ps_grid.py,sha256=IvBkPAJI3bAv8xx4pRZ6x4Sv8Pei1gmp5utTWhY9wgI,26169
110
110
  pycontrails/models/ps_model/static/ps-aircraft-params-20250328.csv,sha256=LUYuWozE8fv4ZxuPhQIyVi0Kz4aYGyRjPcH5bSl4oNs,26185
111
111
  pycontrails/models/ps_model/static/ps-synonym-list-20250328.csv,sha256=phtrf0m-UYQ7gjoKtIIwINzftTSNd-Bwe9CPen_Gvc8,1048
112
112
  pycontrails/models/cocipgrid/cocip_grid_params.py,sha256=l4vBPrOKCJDz5Y1uMjmOGVyUcSWgfZtFWbjW968OPz8,5875
113
113
  pycontrails/models/cocipgrid/__init__.py,sha256=ar6bF_8Pusbb-myujz_q5ntFylQTNH8yiM8fxP7Zk30,262
114
- pycontrails/models/cocipgrid/cocip_grid.py,sha256=k0IfdWJcCXdsuLZUEczPp3-QeKUPy7zpzk3nZnFG8Y8,92678
114
+ pycontrails/models/cocipgrid/cocip_grid.py,sha256=4BOmSMQEKxl4DYluq7q8bh1DWKlgNGw0N--0qaguomA,92633
115
115
  pycontrails/physics/geo.py,sha256=ITK23l1A2lzjNPTFC8ZKyQH59I5Cy_TvuvM_gbALo94,36297
116
116
  pycontrails/physics/units.py,sha256=p-6PzFLpVCMpvmfrhXVh3Hs-nMJw9Y1x-hvgnL9Lo9c,12281
117
117
  pycontrails/physics/constants.py,sha256=JHYL2IJY7del2BE_1QfKaEwtIwkbtyHvyxlm_JPHR90,3201
118
118
  pycontrails/physics/__init__.py,sha256=_1eWbEy6evEWdfJCEkwDiSdpiDNzNWEPVqaPekHyhwU,44
119
- pycontrails/physics/thermo.py,sha256=v7-66PE31SJXz45MXAB9Iq9XfPg1Sn5FpOsTngLaDDI,15406
119
+ pycontrails/physics/thermo.py,sha256=mGVduIY1c1DM6dVAdGcklGIJmscHXo3glDXmdeUtAZk,15376
120
120
  pycontrails/physics/jet.py,sha256=Je1d3vgbBEaVIAL1WZ3C-4p2f9fy9dWOjP5vFVsGGh8,30358
121
121
  pycontrails/physics/static/iata-cargo-load-factors-20250221.csv,sha256=ixsnQk1DyGxHMo0pDy4aOoQIwgOyrGfhMRPumEwPMBc,3841
122
122
  pycontrails/physics/static/iata-passenger-load-factors-20250221.csv,sha256=Q2olRIqUpbOaavvM5ikG8m1v1YQAN3KLNHeFDPvM53Q,3835