pycontrails 0.57.0__cp313-cp313-macosx_10_13_x86_64.whl → 0.59.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.
- pycontrails/_version.py +3 -3
- pycontrails/core/aircraft_performance.py +1 -1
- pycontrails/core/cache.py +2 -2
- pycontrails/core/fleet.py +2 -7
- pycontrails/core/flight.py +2 -7
- pycontrails/core/interpolation.py +45 -67
- pycontrails/core/met.py +62 -37
- pycontrails/core/polygon.py +3 -3
- pycontrails/core/rgi_cython.cpython-313-darwin.so +0 -0
- pycontrails/core/vector.py +3 -8
- pycontrails/datalib/_met_utils/metsource.py +4 -7
- pycontrails/datalib/ecmwf/common.py +2 -2
- pycontrails/datalib/ecmwf/hres.py +2 -2
- pycontrails/datalib/ecmwf/ifs.py +1 -1
- pycontrails/datalib/ecmwf/model_levels.py +1 -1
- pycontrails/datalib/gfs/gfs.py +1 -1
- pycontrails/datalib/goes.py +10 -3
- pycontrails/datalib/gruan.py +343 -0
- pycontrails/datalib/himawari/header_struct.py +1 -1
- pycontrails/datalib/himawari/himawari.py +24 -7
- pycontrails/datalib/leo_utils/sentinel_metadata.py +9 -9
- pycontrails/ext/synthetic_flight.py +2 -2
- pycontrails/models/cocip/cocip_uncertainty.py +1 -1
- pycontrails/models/cocip/contrail_properties.py +1 -1
- pycontrails/models/cocip/output_formats.py +1 -1
- pycontrails/models/cocipgrid/cocip_grid.py +3 -3
- pycontrails/models/dry_advection.py +1 -1
- pycontrails/models/extended_k15.py +4 -4
- pycontrails/models/humidity_scaling/humidity_scaling.py +2 -2
- pycontrails/models/ps_model/ps_grid.py +2 -2
- pycontrails/models/sac.py +1 -1
- pycontrails/models/tau_cirrus.py +1 -1
- pycontrails/physics/thermo.py +4 -4
- pycontrails/utils/iteration.py +1 -1
- {pycontrails-0.57.0.dist-info → pycontrails-0.59.0.dist-info}/METADATA +5 -6
- {pycontrails-0.57.0.dist-info → pycontrails-0.59.0.dist-info}/RECORD +40 -39
- {pycontrails-0.57.0.dist-info → pycontrails-0.59.0.dist-info}/WHEEL +0 -0
- {pycontrails-0.57.0.dist-info → pycontrails-0.59.0.dist-info}/licenses/LICENSE +0 -0
- {pycontrails-0.57.0.dist-info → pycontrails-0.59.0.dist-info}/licenses/NOTICE +0 -0
- {pycontrails-0.57.0.dist-info → pycontrails-0.59.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.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.59.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 59, 0)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g3ff518b95'
|
|
@@ -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()
|
|
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, {})
|
|
120
|
+
fl_attrs.setdefault(flight_id, {})
|
|
126
121
|
|
|
127
122
|
extra = fl_attrs.keys() - groups.index
|
|
128
123
|
if extra:
|
pycontrails/core/flight.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
29
|
+
pycontrails linear interpolation use-cases more performant:
|
|
30
30
|
|
|
31
|
-
#. Avoid ``RegularGridInterpolator`` constructor validation
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
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** :func:`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.
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
@@ -450,8 +428,8 @@ def interp(
|
|
|
450
428
|
In particular, the dimensions of ``da`` must be ``longitude``, ``latitude``,
|
|
451
429
|
``level``, and ``time``. The three spatial dimensions must be monotonically
|
|
452
430
|
increasing with ``float64`` dtype. The ``time`` dimension must be
|
|
453
|
-
monotonically increasing with
|
|
454
|
-
Assumed to be cheap to load into memory (:attr:`
|
|
431
|
+
monotonically increasing with :class:`numpy.datetime64` dtype.
|
|
432
|
+
Assumed to be cheap to load into memory (:attr:`xarray.DataArray.values` is
|
|
455
433
|
used without hesitation).
|
|
456
434
|
method : str
|
|
457
435
|
Passed into :class:`scipy.interpolate.RegularGridInterpolator`.
|
|
@@ -464,7 +442,7 @@ def interp(
|
|
|
464
442
|
``coords``.
|
|
465
443
|
indices : tuple | None, optional
|
|
466
444
|
Experimental. Provide intermediate artifacts computed by
|
|
467
|
-
:meth
|
|
445
|
+
:meth:`scipy.interpolate.RegularGridInterpolator._find_indices`
|
|
468
446
|
to avoid redundant computation. If known and provided, this can speed
|
|
469
447
|
up interpolation by avoiding an unnecessary call to ``_find_indices``.
|
|
470
448
|
By default, None. Must be used precisely.
|
|
@@ -480,9 +458,9 @@ def interp(
|
|
|
480
458
|
|
|
481
459
|
See Also
|
|
482
460
|
--------
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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?
|
|
@@ -334,8 +354,7 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
334
354
|
def hash(self) -> str:
|
|
335
355
|
"""Generate a unique hash for this met instance.
|
|
336
356
|
|
|
337
|
-
Note this is not as robust as it could be since
|
|
338
|
-
cuts off.
|
|
357
|
+
Note this is not as robust as it could be since :func:`repr` cuts off.
|
|
339
358
|
|
|
340
359
|
Returns
|
|
341
360
|
-------
|
|
@@ -666,7 +685,7 @@ class MetBase(ABC, Generic[XArrayType]):
|
|
|
666
685
|
class MetDataset(MetBase):
|
|
667
686
|
"""Meteorological dataset with multiple variables.
|
|
668
687
|
|
|
669
|
-
Composition around
|
|
688
|
+
Composition around :class:`xarray.Dataset` to enforce certain
|
|
670
689
|
variables and dimensions for internal usage
|
|
671
690
|
|
|
672
691
|
Parameters
|
|
@@ -678,17 +697,17 @@ class MetDataset(MetBase):
|
|
|
678
697
|
Defaults to None.
|
|
679
698
|
wrap_longitude : bool, optional
|
|
680
699
|
Wrap data along the longitude dimension. If True, duplicate and shift longitude
|
|
681
|
-
values (ie,
|
|
700
|
+
values (ie, ``-180 -> 180``) to ensure that the longitude dimension covers the entire
|
|
682
701
|
interval ``[-180, 180]``. Defaults to False.
|
|
683
702
|
copy : bool, optional
|
|
684
703
|
Copy data on construction. Defaults to True.
|
|
685
704
|
attrs : dict[str, Any], optional
|
|
686
|
-
Attributes to add to :attr:`data.attrs`. Defaults to None.
|
|
687
|
-
|
|
705
|
+
Attributes to add to :attr:`data.attrs`. Defaults to None. Generally, pycontrails
|
|
706
|
+
:class:`pycontrails.core.models.Models` may use the following attributes:
|
|
688
707
|
|
|
689
|
-
- ``provider``: Name of the data provider (e.g. "ECMWF").
|
|
690
|
-
- ``dataset``: Name of the dataset (e.g. "ERA5").
|
|
691
|
-
- ``product``: Name of the product type (e.g. "reanalysis").
|
|
708
|
+
- ``provider``: Name of the data provider (e.g. ``"ECMWF"``).
|
|
709
|
+
- ``dataset``: Name of the dataset (e.g. ``"ERA5"``).
|
|
710
|
+
- ``product``: Name of the product type (e.g. ``"reanalysis"``).
|
|
692
711
|
|
|
693
712
|
**attrs_kwargs : Any
|
|
694
713
|
Keyword arguments to add to :attr:`data.attrs`. Defaults to None.
|
|
@@ -792,7 +811,7 @@ class MetDataset(MetBase):
|
|
|
792
811
|
return MetDataArray._from_fastpath(da)
|
|
793
812
|
|
|
794
813
|
def get(self, key: str, default_value: Any = None) -> Any:
|
|
795
|
-
"""Shortcut to :meth:`
|
|
814
|
+
"""Shortcut to :meth:`xarray.Dataset.get` method.
|
|
796
815
|
|
|
797
816
|
Parameters
|
|
798
817
|
----------
|
|
@@ -869,7 +888,7 @@ class MetDataset(MetBase):
|
|
|
869
888
|
|
|
870
889
|
See Also
|
|
871
890
|
--------
|
|
872
|
-
|
|
891
|
+
xarray.Dataset.update
|
|
873
892
|
"""
|
|
874
893
|
other = other or {}
|
|
875
894
|
other.update(kwargs)
|
|
@@ -937,8 +956,8 @@ class MetDataset(MetBase):
|
|
|
937
956
|
Returns
|
|
938
957
|
-------
|
|
939
958
|
list[str]
|
|
940
|
-
List of met keys verified in MetDataset
|
|
941
|
-
Returns an empty list if any MetVariable is missing.
|
|
959
|
+
List of met keys verified in :class:`MetDataset`.
|
|
960
|
+
Returns an empty list if any :class:`MetVariable` is missing.
|
|
942
961
|
|
|
943
962
|
Raises
|
|
944
963
|
------
|
|
@@ -1087,7 +1106,7 @@ class MetDataset(MetBase):
|
|
|
1087
1106
|
out[key] = da.values.ravel() # type: ignore[index]
|
|
1088
1107
|
|
|
1089
1108
|
if transfer_attrs:
|
|
1090
|
-
out.attrs.update(self.attrs)
|
|
1109
|
+
out.attrs.update(self.attrs)
|
|
1091
1110
|
|
|
1092
1111
|
return out
|
|
1093
1112
|
|
|
@@ -1121,12 +1140,12 @@ class MetDataset(MetBase):
|
|
|
1121
1140
|
|
|
1122
1141
|
@property
|
|
1123
1142
|
def provider_attr(self) -> str:
|
|
1124
|
-
"""Look up the
|
|
1143
|
+
"""Look up the ``"provider"`` attribute with a custom error message.
|
|
1125
1144
|
|
|
1126
1145
|
Returns
|
|
1127
1146
|
-------
|
|
1128
1147
|
str
|
|
1129
|
-
Provider of the data. If not one of
|
|
1148
|
+
Provider of the data. If not one of ``"ECMWF"`` or ``"NCEP"``,
|
|
1130
1149
|
a warning is issued.
|
|
1131
1150
|
"""
|
|
1132
1151
|
supported = ("ECMWF", "NCEP")
|
|
@@ -1135,13 +1154,13 @@ class MetDataset(MetBase):
|
|
|
1135
1154
|
|
|
1136
1155
|
@property
|
|
1137
1156
|
def dataset_attr(self) -> str:
|
|
1138
|
-
"""Look up the
|
|
1157
|
+
"""Look up the ``"dataset"`` attribute with a custom error message.
|
|
1139
1158
|
|
|
1140
1159
|
Returns
|
|
1141
1160
|
-------
|
|
1142
1161
|
str
|
|
1143
|
-
Dataset of the data. If not one of
|
|
1144
|
-
or
|
|
1162
|
+
Dataset of the data. If not one of ``"ERA5"``, ``"HRES"``, ``"IFS"``,
|
|
1163
|
+
or ``"GFS"``, a warning is issued.
|
|
1145
1164
|
"""
|
|
1146
1165
|
supported = ("ERA5", "HRES", "IFS", "GFS")
|
|
1147
1166
|
examples = {
|
|
@@ -1153,13 +1172,13 @@ class MetDataset(MetBase):
|
|
|
1153
1172
|
|
|
1154
1173
|
@property
|
|
1155
1174
|
def product_attr(self) -> str:
|
|
1156
|
-
"""Look up the
|
|
1175
|
+
"""Look up the ``"product"`` attribute with a custom error message.
|
|
1157
1176
|
|
|
1158
1177
|
Returns
|
|
1159
1178
|
-------
|
|
1160
1179
|
str
|
|
1161
|
-
Product of the data. If not one of
|
|
1162
|
-
a warning is issued.
|
|
1180
|
+
Product of the data. If not one of ``"forecast"``, ``"ensemble"``,
|
|
1181
|
+
or ``"reanalysis"``, a warning is issued.
|
|
1163
1182
|
|
|
1164
1183
|
"""
|
|
1165
1184
|
supported = ("reanalysis", "forecast", "ensemble")
|
|
@@ -1188,6 +1207,7 @@ class MetDataset(MetBase):
|
|
|
1188
1207
|
|
|
1189
1208
|
By default, this method returns a new :class:`MetDataset` instead
|
|
1190
1209
|
of renaming in place. To retain the old behavior, set ``inplace=True``.
|
|
1210
|
+
The ``inplace`` behavior is deprecated and will be removed in a future release.
|
|
1191
1211
|
|
|
1192
1212
|
Parameters
|
|
1193
1213
|
----------
|
|
@@ -1204,6 +1224,11 @@ class MetDataset(MetBase):
|
|
|
1204
1224
|
data_renamed = standardize_variables(self.data, variables)
|
|
1205
1225
|
|
|
1206
1226
|
if inplace:
|
|
1227
|
+
warnings.warn(
|
|
1228
|
+
"The inplace behavior is deprecated and will be removed in a future release. ",
|
|
1229
|
+
DeprecationWarning,
|
|
1230
|
+
stacklevel=2,
|
|
1231
|
+
)
|
|
1207
1232
|
self.data = data_renamed
|
|
1208
1233
|
return None
|
|
1209
1234
|
|
|
@@ -1300,7 +1325,7 @@ class MetDataset(MetBase):
|
|
|
1300
1325
|
coords: dict[str, np.ndarray] = {}
|
|
1301
1326
|
for key, val in input_data.items():
|
|
1302
1327
|
dtype = "datetime64[ns]" if key == "time" else COORD_DTYPE
|
|
1303
|
-
arr: np.ndarray = np.asarray(val, dtype=dtype)
|
|
1328
|
+
arr: np.ndarray = np.asarray(val, dtype=dtype)
|
|
1304
1329
|
|
|
1305
1330
|
if arr.ndim == 0:
|
|
1306
1331
|
arr = arr.reshape(1)
|
|
@@ -1889,7 +1914,7 @@ class MetDataArray(MetBase):
|
|
|
1889
1914
|
if not self.binary:
|
|
1890
1915
|
raise NotImplementedError("proportion method is only implemented for binary fields")
|
|
1891
1916
|
|
|
1892
|
-
return self.data.sum().values.item() / self.data.count().values.item()
|
|
1917
|
+
return self.data.sum().values.item() / self.data.count().values.item()
|
|
1893
1918
|
|
|
1894
1919
|
def find_edges(self) -> Self:
|
|
1895
1920
|
"""Find edges of regions.
|
|
@@ -2598,9 +2623,9 @@ def _extract_2d_arr_and_altitude(
|
|
|
2598
2623
|
except KeyError:
|
|
2599
2624
|
altitude = None
|
|
2600
2625
|
else:
|
|
2601
|
-
altitude = round(altitude)
|
|
2626
|
+
altitude = round(altitude)
|
|
2602
2627
|
|
|
2603
|
-
return arr, altitude
|
|
2628
|
+
return arr, altitude
|
|
2604
2629
|
|
|
2605
2630
|
|
|
2606
2631
|
def downselect(data: XArrayType, bbox: tuple[float, ...]) -> XArrayType:
|
pycontrails/core/polygon.py
CHANGED
|
@@ -238,7 +238,7 @@ def _contours_to_polygons(
|
|
|
238
238
|
latitude=latitude,
|
|
239
239
|
precision=precision,
|
|
240
240
|
buffer=buffer,
|
|
241
|
-
i=child_i,
|
|
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]
|
|
357
|
+
hierarchy = hierarchy[0]
|
|
358
358
|
|
|
359
359
|
polygons = _contours_to_polygons(
|
|
360
360
|
contours, # type: ignore[arg-type]
|
|
361
|
-
hierarchy,
|
|
361
|
+
hierarchy,
|
|
362
362
|
min_area,
|
|
363
363
|
convex_hull,
|
|
364
364
|
epsilon,
|
|
Binary file
|
pycontrails/core/vector.py
CHANGED
|
@@ -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 {})
|
|
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 {})
|
|
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
|
|
178
|
+
return out
|
|
179
179
|
|
|
180
|
-
if missing := set(out).difference(supported):
|
|
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
|
|
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()
|
|
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))
|
|
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
|
|