pycontrails 0.54.4__cp312-cp312-win_amd64.whl → 0.54.6__cp312-cp312-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pycontrails might be problematic. Click here for more details.
- pycontrails/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +34 -16
- pycontrails/core/airports.py +3 -4
- pycontrails/core/fleet.py +30 -9
- pycontrails/core/flight.py +8 -5
- pycontrails/core/flightplan.py +11 -11
- pycontrails/core/interpolation.py +7 -4
- pycontrails/core/met.py +145 -86
- pycontrails/core/met_var.py +62 -0
- pycontrails/core/models.py +3 -2
- pycontrails/core/rgi_cython.cp312-win_amd64.pyd +0 -0
- pycontrails/core/vector.py +97 -74
- pycontrails/datalib/_met_utils/metsource.py +1 -1
- pycontrails/datalib/ecmwf/era5.py +5 -6
- pycontrails/datalib/ecmwf/era5_model_level.py +4 -5
- pycontrails/datalib/ecmwf/ifs.py +1 -3
- pycontrails/datalib/gfs/gfs.py +1 -3
- pycontrails/models/apcemm/apcemm.py +2 -2
- pycontrails/models/apcemm/utils.py +1 -1
- pycontrails/models/cocip/cocip.py +86 -27
- pycontrails/models/cocip/output_formats.py +1 -0
- pycontrails/models/cocipgrid/cocip_grid.py +8 -73
- pycontrails/models/dry_advection.py +99 -31
- pycontrails/models/emissions/emissions.py +2 -2
- pycontrails/models/humidity_scaling/humidity_scaling.py +1 -1
- pycontrails/models/issr.py +2 -2
- pycontrails/models/pcc.py +1 -2
- pycontrails/models/ps_model/ps_grid.py +2 -2
- pycontrails/models/ps_model/ps_model.py +4 -32
- pycontrails/models/ps_model/ps_operational_limits.py +2 -6
- pycontrails/models/tau_cirrus.py +13 -6
- pycontrails/physics/geo.py +3 -3
- {pycontrails-0.54.4.dist-info → pycontrails-0.54.6.dist-info}/METADATA +3 -4
- {pycontrails-0.54.4.dist-info → pycontrails-0.54.6.dist-info}/RECORD +38 -38
- {pycontrails-0.54.4.dist-info → pycontrails-0.54.6.dist-info}/WHEEL +1 -1
- {pycontrails-0.54.4.dist-info → pycontrails-0.54.6.dist-info}/LICENSE +0 -0
- {pycontrails-0.54.4.dist-info → pycontrails-0.54.6.dist-info}/NOTICE +0 -0
- {pycontrails-0.54.4.dist-info → pycontrails-0.54.6.dist-info}/top_level.txt +0 -0
pycontrails/_version.py
CHANGED
|
@@ -96,22 +96,52 @@ class AircraftPerformance(Model):
|
|
|
96
96
|
|
|
97
97
|
source: Flight
|
|
98
98
|
|
|
99
|
-
@abc.abstractmethod
|
|
100
99
|
@overload
|
|
101
100
|
def eval(self, source: Fleet, **params: Any) -> Fleet: ...
|
|
102
101
|
|
|
103
|
-
@abc.abstractmethod
|
|
104
102
|
@overload
|
|
105
103
|
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
106
104
|
|
|
107
|
-
@abc.abstractmethod
|
|
108
105
|
@overload
|
|
109
106
|
def eval(self, source: None = ..., **params: Any) -> NoReturn: ...
|
|
110
107
|
|
|
111
|
-
@abc.abstractmethod
|
|
112
108
|
def eval(self, source: Flight | None = None, **params: Any) -> Flight:
|
|
113
109
|
"""Evaluate the aircraft performance model.
|
|
114
110
|
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
source : Flight
|
|
114
|
+
Flight trajectory to evaluate. Can be a :class:`Flight` or :class:`Fleet`.
|
|
115
|
+
params : Any
|
|
116
|
+
Override :attr:`params` with keyword arguments.
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
Flight
|
|
121
|
+
Flight trajectory with aircraft performance data.
|
|
122
|
+
"""
|
|
123
|
+
self.update_params(params)
|
|
124
|
+
self.set_source(source)
|
|
125
|
+
self.source = self.require_source_type(Flight)
|
|
126
|
+
self.downselect_met()
|
|
127
|
+
self.set_source_met()
|
|
128
|
+
self._cleanup_indices()
|
|
129
|
+
|
|
130
|
+
# Calculate true airspeed if not included on source
|
|
131
|
+
self.ensure_true_airspeed_on_source()
|
|
132
|
+
|
|
133
|
+
if isinstance(self.source, Fleet):
|
|
134
|
+
fls = [self.eval_flight(fl) for fl in self.source.to_flight_list()]
|
|
135
|
+
self.source = Fleet.from_seq(fls, attrs=self.source.attrs, broadcast_numeric=False)
|
|
136
|
+
return self.source
|
|
137
|
+
|
|
138
|
+
self.source = self.eval_flight(self.source)
|
|
139
|
+
return self.source
|
|
140
|
+
|
|
141
|
+
@abc.abstractmethod
|
|
142
|
+
def eval_flight(self, fl: Flight) -> Flight:
|
|
143
|
+
"""Evaluate the aircraft performance model on a single flight trajectory.
|
|
144
|
+
|
|
115
145
|
The implementing model adds the following fields to the source flight:
|
|
116
146
|
|
|
117
147
|
- ``aircraft_mass``: aircraft mass at each waypoint, [:math:`kg`]
|
|
@@ -128,18 +158,6 @@ class AircraftPerformance(Model):
|
|
|
128
158
|
- ``max_mach``: maximum Mach number
|
|
129
159
|
- ``max_altitude``: maximum altitude, [:math:`m`]
|
|
130
160
|
- ``total_fuel_burn``: total fuel burn, [:math:`kg`]
|
|
131
|
-
|
|
132
|
-
Parameters
|
|
133
|
-
----------
|
|
134
|
-
source : Flight
|
|
135
|
-
Flight trajectory to evaluate.
|
|
136
|
-
params : Any
|
|
137
|
-
Override :attr:`params` with keyword arguments.
|
|
138
|
-
|
|
139
|
-
Returns
|
|
140
|
-
-------
|
|
141
|
-
Flight
|
|
142
|
-
Flight trajectory with aircraft performance data.
|
|
143
161
|
"""
|
|
144
162
|
|
|
145
163
|
@override
|
pycontrails/core/airports.py
CHANGED
|
@@ -162,7 +162,7 @@ def find_nearest_airport(
|
|
|
162
162
|
) & airports["latitude"].between((latitude - bbox), (latitude + bbox))
|
|
163
163
|
|
|
164
164
|
# Find the nearest airport from largest to smallest airport type
|
|
165
|
-
search_priority =
|
|
165
|
+
search_priority = ("large_airport", "medium_airport", "small_airport")
|
|
166
166
|
|
|
167
167
|
for airport_type in search_priority:
|
|
168
168
|
is_airport_type = airports["type"] == airport_type
|
|
@@ -171,7 +171,7 @@ def find_nearest_airport(
|
|
|
171
171
|
if len(nearest_airports) == 1:
|
|
172
172
|
return nearest_airports["icao_code"].values[0]
|
|
173
173
|
|
|
174
|
-
|
|
174
|
+
if len(nearest_airports) > 1:
|
|
175
175
|
distance = distance_to_airports(
|
|
176
176
|
nearest_airports,
|
|
177
177
|
longitude,
|
|
@@ -181,8 +181,7 @@ def find_nearest_airport(
|
|
|
181
181
|
i_nearest = np.argmin(distance)
|
|
182
182
|
return nearest_airports["icao_code"].values[i_nearest]
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
continue
|
|
184
|
+
continue
|
|
186
185
|
|
|
187
186
|
return None
|
|
188
187
|
|
pycontrails/core/fleet.py
CHANGED
|
@@ -133,18 +133,19 @@ class Fleet(Flight):
|
|
|
133
133
|
|
|
134
134
|
@override
|
|
135
135
|
def copy(self, **kwargs: Any) -> Self:
|
|
136
|
-
kwargs.setdefault("fuel", self.fuel)
|
|
137
136
|
kwargs.setdefault("fl_attrs", self.fl_attrs)
|
|
137
|
+
kwargs.setdefault("final_waypoints", self.final_waypoints)
|
|
138
138
|
return super().copy(**kwargs)
|
|
139
139
|
|
|
140
140
|
@override
|
|
141
141
|
def filter(self, mask: npt.NDArray[np.bool_], copy: bool = True, **kwargs: Any) -> Self:
|
|
142
|
-
kwargs.setdefault("fuel", self.fuel)
|
|
143
|
-
|
|
144
142
|
flight_ids = set(np.unique(self["flight_id"][mask]))
|
|
145
143
|
fl_attrs = {k: v for k, v in self.fl_attrs.items() if k in flight_ids}
|
|
146
144
|
kwargs.setdefault("fl_attrs", fl_attrs)
|
|
147
145
|
|
|
146
|
+
final_waypoints = np.array(self.final_waypoints[mask], copy=copy)
|
|
147
|
+
kwargs.setdefault("final_waypoints", final_waypoints)
|
|
148
|
+
|
|
148
149
|
return super().filter(mask, copy=copy, **kwargs)
|
|
149
150
|
|
|
150
151
|
@override
|
|
@@ -187,8 +188,9 @@ class Fleet(Flight):
|
|
|
187
188
|
in ``seq``.
|
|
188
189
|
"""
|
|
189
190
|
|
|
191
|
+
# Create a shallow copy because we add additional keys in _validate_fl
|
|
190
192
|
def _shallow_copy(fl: Flight) -> Flight:
|
|
191
|
-
return Flight(
|
|
193
|
+
return Flight._from_fastpath(fl.data, fl.attrs, fuel=fl.fuel)
|
|
192
194
|
|
|
193
195
|
def _maybe_warn(fl: Flight) -> Flight:
|
|
194
196
|
if not fl:
|
|
@@ -217,7 +219,18 @@ class Fleet(Flight):
|
|
|
217
219
|
)
|
|
218
220
|
|
|
219
221
|
data = {var: np.concatenate([fl[var] for fl in seq]) for var in seq[0]}
|
|
220
|
-
|
|
222
|
+
|
|
223
|
+
final_waypoints = np.zeros(data["time"].size, dtype=bool)
|
|
224
|
+
final_waypoint_indices = np.cumsum([fl.size for fl in seq]) - 1
|
|
225
|
+
final_waypoints[final_waypoint_indices] = True
|
|
226
|
+
|
|
227
|
+
return cls._from_fastpath(
|
|
228
|
+
data,
|
|
229
|
+
attrs,
|
|
230
|
+
fuel=fuel,
|
|
231
|
+
fl_attrs=fl_attrs,
|
|
232
|
+
final_waypoints=final_waypoints,
|
|
233
|
+
)
|
|
221
234
|
|
|
222
235
|
@property
|
|
223
236
|
def n_flights(self) -> int:
|
|
@@ -246,11 +259,19 @@ class Fleet(Flight):
|
|
|
246
259
|
List of Flights in the same order as was passed into the ``Fleet`` instance.
|
|
247
260
|
"""
|
|
248
261
|
indices = self.dataframe.groupby("flight_id", sort=False).indices
|
|
262
|
+
if copy:
|
|
263
|
+
return [
|
|
264
|
+
Flight._from_fastpath(
|
|
265
|
+
{k: v[idx] for k, v in self.data.items()},
|
|
266
|
+
self.fl_attrs[flight_id],
|
|
267
|
+
fuel=self.fuel,
|
|
268
|
+
).copy()
|
|
269
|
+
for flight_id, idx in indices.items()
|
|
270
|
+
]
|
|
249
271
|
return [
|
|
250
|
-
Flight(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
copy=copy,
|
|
272
|
+
Flight._from_fastpath(
|
|
273
|
+
{k: v[idx] for k, v in self.data.items()},
|
|
274
|
+
self.fl_attrs[flight_id],
|
|
254
275
|
fuel=self.fuel,
|
|
255
276
|
)
|
|
256
277
|
for flight_id, idx in indices.items()
|
pycontrails/core/flight.py
CHANGED
|
@@ -891,7 +891,7 @@ class Flight(GeoVectorDataset):
|
|
|
891
891
|
"""
|
|
892
892
|
methods = "geodesic", "linear"
|
|
893
893
|
if fill_method not in methods:
|
|
894
|
-
raise ValueError(f
|
|
894
|
+
raise ValueError(f"Unknown `fill_method`. Supported methods: {', '.join(methods)}")
|
|
895
895
|
|
|
896
896
|
# STEP 0: If self is empty, return an empty flight
|
|
897
897
|
if not self:
|
|
@@ -957,7 +957,12 @@ class Flight(GeoVectorDataset):
|
|
|
957
957
|
msg = f"{msg} Pass 'keep_original_index=True' to keep the original index."
|
|
958
958
|
warnings.warn(msg)
|
|
959
959
|
|
|
960
|
-
|
|
960
|
+
# Reorder columns (this is unimportant but makes the output more canonical)
|
|
961
|
+
coord_names = ("longitude", "latitude", "altitude", "time")
|
|
962
|
+
df = df[[*coord_names, *[c for c in df.columns if c not in set(coord_names)]]]
|
|
963
|
+
|
|
964
|
+
data = {k: v.to_numpy() for k, v in df.items()}
|
|
965
|
+
return type(self)._from_fastpath(data, attrs=self.attrs, fuel=self.fuel)
|
|
961
966
|
|
|
962
967
|
def clean_and_resample(
|
|
963
968
|
self,
|
|
@@ -1977,9 +1982,7 @@ def filter_altitude(
|
|
|
1977
1982
|
result[i0:i1] = altitude_filt[i0:i1]
|
|
1978
1983
|
|
|
1979
1984
|
# reapply Savitzky-Golay filter to smooth climb and descent
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
return result
|
|
1985
|
+
return _sg_filter(result, window_length=kernel_size)
|
|
1983
1986
|
|
|
1984
1987
|
|
|
1985
1988
|
def segment_duration(
|
pycontrails/core/flightplan.py
CHANGED
|
@@ -21,24 +21,24 @@ def to_atc_plan(plan: dict[str, Any]) -> str:
|
|
|
21
21
|
--------
|
|
22
22
|
:func:`parse_atc_plan`
|
|
23
23
|
"""
|
|
24
|
-
ret = f
|
|
25
|
-
ret += f
|
|
24
|
+
ret = f"(FPL-{plan['callsign']}-{plan['flight_rules']}"
|
|
25
|
+
ret += f"{plan['type_of_flight']}\n"
|
|
26
26
|
ret += "-"
|
|
27
27
|
if "number_aircraft" in plan and plan["number_aircraft"] <= 10:
|
|
28
28
|
ret += plan["number_aircraft"]
|
|
29
|
-
ret += f
|
|
30
|
-
ret += f
|
|
31
|
-
ret += f
|
|
32
|
-
ret += f
|
|
33
|
-
ret += f
|
|
29
|
+
ret += f"{plan['type_of_aircraft']}/{plan['wake_category']}-"
|
|
30
|
+
ret += f"{plan['equipment']}/{plan['transponder']}\n"
|
|
31
|
+
ret += f"-{plan['departure_icao']}{plan['time']}\n"
|
|
32
|
+
ret += f"-{plan['speed_type']}{plan['speed']}{plan['level_type']}"
|
|
33
|
+
ret += f"{plan['level']} {plan['route']}\n"
|
|
34
34
|
if "destination_icao" in plan and "duration" in plan:
|
|
35
|
-
ret += f
|
|
35
|
+
ret += f"-{plan['destination_icao']}{plan['duration']}"
|
|
36
36
|
if "alt_icao" in plan:
|
|
37
|
-
ret += f
|
|
37
|
+
ret += f" {plan['alt_icao']}"
|
|
38
38
|
if "second_alt_icao" in plan:
|
|
39
|
-
ret += f
|
|
39
|
+
ret += f" {plan['second_alt_icao']}"
|
|
40
40
|
ret += "\n"
|
|
41
|
-
ret += f
|
|
41
|
+
ret += f"-{plan['other_info']})\n"
|
|
42
42
|
if "supplementary_info" in plan:
|
|
43
43
|
ret += " ".join([f"{i[0]}/{i[1]}" for i in plan["supplementary_info"].items()])
|
|
44
44
|
|
|
@@ -245,6 +245,9 @@ def _pick_method(scipy_version: str, method: str) -> str:
|
|
|
245
245
|
str
|
|
246
246
|
Interpolation method adjusted for compatibility with this class.
|
|
247
247
|
"""
|
|
248
|
+
if method == "linear":
|
|
249
|
+
return method
|
|
250
|
+
|
|
248
251
|
try:
|
|
249
252
|
version = scipy_version.split(".")
|
|
250
253
|
major = int(version[0])
|
|
@@ -486,15 +489,15 @@ def interp(
|
|
|
486
489
|
da = _localize(da, coords)
|
|
487
490
|
|
|
488
491
|
indexes = da._indexes
|
|
489
|
-
x = indexes["longitude"].index.
|
|
490
|
-
y = indexes["latitude"].index.
|
|
491
|
-
z = indexes["level"].index.
|
|
492
|
+
x = indexes["longitude"].index.values # type: ignore[attr-defined]
|
|
493
|
+
y = indexes["latitude"].index.values # type: ignore[attr-defined]
|
|
494
|
+
z = indexes["level"].index.values # type: ignore[attr-defined]
|
|
492
495
|
if any(v.dtype != np.float64 for v in (x, y, z)):
|
|
493
496
|
msg = "da must have float64 dtype for longitude, latitude, and level coordinates"
|
|
494
497
|
raise ValueError(msg)
|
|
495
498
|
|
|
496
499
|
# Convert t and time to float64
|
|
497
|
-
t = indexes["time"].index.
|
|
500
|
+
t = indexes["time"].index.values # type: ignore[attr-defined]
|
|
498
501
|
offset = t[0]
|
|
499
502
|
t = _floatize_time(t, offset)
|
|
500
503
|
|