pycontrails 0.47.3__cp311-cp311-macosx_11_0_arm64.whl → 0.48.1__cp311-cp311-macosx_11_0_arm64.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/__init__.py +2 -2
- pycontrails/_version.py +2 -2
- pycontrails/core/coordinates.py +17 -10
- pycontrails/core/datalib.py +155 -113
- pycontrails/core/flight.py +45 -28
- pycontrails/core/met.py +163 -39
- pycontrails/core/met_var.py +9 -9
- pycontrails/core/models.py +27 -0
- pycontrails/core/rgi_cython.cpython-311-darwin.so +0 -0
- pycontrails/core/vector.py +257 -33
- pycontrails/datalib/ecmwf/common.py +14 -65
- pycontrails/datalib/ecmwf/era5.py +22 -27
- pycontrails/datalib/ecmwf/hres.py +53 -88
- pycontrails/datalib/ecmwf/ifs.py +10 -2
- pycontrails/datalib/gfs/gfs.py +68 -106
- pycontrails/models/accf.py +181 -154
- pycontrails/models/cocip/cocip.py +205 -105
- pycontrails/models/cocip/cocip_params.py +0 -4
- pycontrails/models/cocip/wake_vortex.py +9 -7
- pycontrails/models/cocipgrid/cocip_grid.py +2 -6
- pycontrails/models/issr.py +29 -31
- pycontrails/models/pcr.py +5 -12
- pycontrails/models/sac.py +24 -27
- pycontrails/models/tau_cirrus.py +22 -5
- pycontrails/utils/types.py +1 -1
- {pycontrails-0.47.3.dist-info → pycontrails-0.48.1.dist-info}/METADATA +2 -2
- {pycontrails-0.47.3.dist-info → pycontrails-0.48.1.dist-info}/RECORD +31 -31
- {pycontrails-0.47.3.dist-info → pycontrails-0.48.1.dist-info}/WHEEL +1 -1
- {pycontrails-0.47.3.dist-info → pycontrails-0.48.1.dist-info}/LICENSE +0 -0
- {pycontrails-0.47.3.dist-info → pycontrails-0.48.1.dist-info}/NOTICE +0 -0
- {pycontrails-0.47.3.dist-info → pycontrails-0.48.1.dist-info}/top_level.txt +0 -0
pycontrails/models/accf.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import warnings
|
|
5
6
|
from dataclasses import dataclass
|
|
6
7
|
from typing import Any, overload
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ import xarray as xr
|
|
|
9
10
|
|
|
10
11
|
import pycontrails
|
|
11
12
|
from pycontrails.core.flight import Flight
|
|
12
|
-
from pycontrails.core.met import
|
|
13
|
+
from pycontrails.core.met import MetDataset, standardize_variables
|
|
13
14
|
from pycontrails.core.met_var import (
|
|
14
15
|
AirTemperature,
|
|
15
16
|
EastwardWind,
|
|
@@ -23,28 +24,46 @@ from pycontrails.core.vector import GeoVectorDataset
|
|
|
23
24
|
from pycontrails.datalib import ecmwf
|
|
24
25
|
from pycontrails.utils import dependencies
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
27
|
+
|
|
28
|
+
def wide_body_jets() -> set[str]:
|
|
29
|
+
"""Return a set of wide body jets."""
|
|
30
|
+
return {
|
|
31
|
+
"A332",
|
|
32
|
+
"A333",
|
|
33
|
+
"A338",
|
|
34
|
+
"A339",
|
|
35
|
+
"A342",
|
|
36
|
+
"A343",
|
|
37
|
+
"A345",
|
|
38
|
+
"A356",
|
|
39
|
+
"A359",
|
|
40
|
+
"A388",
|
|
41
|
+
"B762",
|
|
42
|
+
"B763",
|
|
43
|
+
"B764",
|
|
44
|
+
"B772",
|
|
45
|
+
"B773",
|
|
46
|
+
"B778",
|
|
47
|
+
"B779",
|
|
48
|
+
"B788",
|
|
49
|
+
"B789",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def regional_jets() -> set[str]:
|
|
54
|
+
"""Return a set of regional jets."""
|
|
55
|
+
return {
|
|
56
|
+
"CRJ1",
|
|
57
|
+
"CRJ2",
|
|
58
|
+
"CRJ7",
|
|
59
|
+
"CRJ8",
|
|
60
|
+
"CRJ9",
|
|
61
|
+
"CRJX",
|
|
62
|
+
"E135",
|
|
63
|
+
"E145",
|
|
64
|
+
"E170",
|
|
65
|
+
"E190",
|
|
66
|
+
}
|
|
48
67
|
|
|
49
68
|
|
|
50
69
|
@dataclass
|
|
@@ -102,13 +121,16 @@ class ACCF(Model):
|
|
|
102
121
|
"""Compute Algorithmic Climate Change Functions (ACCF).
|
|
103
122
|
|
|
104
123
|
This class is a wrapper over the DLR / UMadrid library
|
|
105
|
-
`climaccf <https://github.com/dlr-pa/climaccf>`
|
|
106
|
-
`DOI: 10.5281/zenodo.6977272 <https://doi.org/10.5281/zenodo.6977272>`
|
|
124
|
+
`climaccf <https://github.com/dlr-pa/climaccf>`_,
|
|
125
|
+
`DOI: 10.5281/zenodo.6977272 <https://doi.org/10.5281/zenodo.6977272>`_
|
|
107
126
|
|
|
108
127
|
Parameters
|
|
109
128
|
----------
|
|
110
129
|
met : MetDataset
|
|
111
130
|
Dataset containing "air_temperature" and "specific_humidity" variables
|
|
131
|
+
surface : MetDataset, optional
|
|
132
|
+
Dataset containing "surface_solar_downward_radiation" and
|
|
133
|
+
"top_net_thermal_radiation" variables
|
|
112
134
|
|
|
113
135
|
References
|
|
114
136
|
----------
|
|
@@ -132,47 +154,32 @@ class ACCF(Model):
|
|
|
132
154
|
sur_variables = (ecmwf.SurfaceSolarDownwardRadiation, ecmwf.TopNetThermalRadiation)
|
|
133
155
|
default_params = ACCFParams
|
|
134
156
|
|
|
135
|
-
short_vars =
|
|
157
|
+
short_vars = {v.short_name for v in (*met_variables, *sur_variables)}
|
|
136
158
|
|
|
137
|
-
|
|
138
|
-
|
|
159
|
+
# This variable won't get used since we are not writing the output
|
|
160
|
+
# anywhere, but the library will complain if it's not defined
|
|
161
|
+
path_lib = "./"
|
|
139
162
|
|
|
140
163
|
def __init__(
|
|
141
164
|
self,
|
|
142
165
|
met: MetDataset,
|
|
143
166
|
surface: MetDataset | None = None,
|
|
144
|
-
params: dict[str, Any] =
|
|
167
|
+
params: dict[str, Any] | None = None,
|
|
145
168
|
**params_kwargs: Any,
|
|
146
169
|
) -> None:
|
|
147
170
|
# Normalize ECMWF variables
|
|
148
171
|
met = standardize_variables(met, self.met_variables)
|
|
149
172
|
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
# Ignore humidity scaling warning
|
|
174
|
+
with warnings.catch_warnings():
|
|
175
|
+
warnings.filterwarnings("ignore", module="pycontrails.core.models")
|
|
176
|
+
super().__init__(met, params=params, **params_kwargs)
|
|
152
177
|
|
|
153
|
-
# Surpress warning about humdity scaling because that should be eet
|
|
154
|
-
# using ACCF config variables for this model
|
|
155
|
-
try:
|
|
156
|
-
del met.attrs["history"]
|
|
157
|
-
except KeyError:
|
|
158
|
-
pass
|
|
159
|
-
|
|
160
|
-
source = met.attrs["met_source"]
|
|
161
|
-
met.attrs["met_source"] = "not_ecmwf"
|
|
162
|
-
super().__init__(met, params=params, **params_kwargs)
|
|
163
|
-
if self.met:
|
|
164
|
-
self.met.attrs["met_source"] = source
|
|
165
|
-
|
|
166
|
-
self._update_accf_config()
|
|
167
178
|
if surface:
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
self.path_lib = "./"
|
|
173
|
-
|
|
174
|
-
self.ds_met = None
|
|
175
|
-
self.ds_sur = None
|
|
179
|
+
surface = surface.copy()
|
|
180
|
+
surface = standardize_variables(surface, self.sur_variables)
|
|
181
|
+
surface.data = _rad_instantaneous_to_accumulated(surface.data)
|
|
182
|
+
self.surface = surface
|
|
176
183
|
|
|
177
184
|
@overload
|
|
178
185
|
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
@@ -181,11 +188,11 @@ class ACCF(Model):
|
|
|
181
188
|
def eval(self, source: GeoVectorDataset, **params: Any) -> GeoVectorDataset: ...
|
|
182
189
|
|
|
183
190
|
@overload
|
|
184
|
-
def eval(self, source: MetDataset | None = ..., **params: Any) ->
|
|
191
|
+
def eval(self, source: MetDataset | None = ..., **params: Any) -> MetDataset: ...
|
|
185
192
|
|
|
186
193
|
def eval(
|
|
187
194
|
self, source: GeoVectorDataset | Flight | MetDataset | None = None, **params: Any
|
|
188
|
-
) -> GeoVectorDataset | Flight |
|
|
195
|
+
) -> GeoVectorDataset | Flight | MetDataset:
|
|
189
196
|
"""Evaluate accfs along flight trajectory or on meteorology grid.
|
|
190
197
|
|
|
191
198
|
Parameters
|
|
@@ -225,32 +232,38 @@ class ACCF(Model):
|
|
|
225
232
|
self.surface = self.source.downselect_met(self.surface)
|
|
226
233
|
|
|
227
234
|
if isinstance(self.source, MetDataset):
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
hres = abs(self.source["latitude"].data[1] - self.source["latitude"].data[0])
|
|
235
|
+
# Overwrite horizontal resolution to match met
|
|
236
|
+
longitude = self.source.data["longitude"].values
|
|
237
|
+
if longitude.size > 1:
|
|
238
|
+
hres = abs(longitude[1] - longitude[0])
|
|
233
239
|
self.params["horizontal_resolution"] = float(hres)
|
|
234
240
|
|
|
241
|
+
else:
|
|
242
|
+
latitude = self.source.data["latitude"].values
|
|
243
|
+
if latitude.size > 1:
|
|
244
|
+
hres = abs(latitude[1] - latitude[0])
|
|
245
|
+
self.params["horizontal_resolution"] = float(hres)
|
|
246
|
+
|
|
247
|
+
p_settings = _get_accf_config(self.params)
|
|
248
|
+
|
|
235
249
|
self.set_source_met()
|
|
236
|
-
self.
|
|
237
|
-
self._generate_weather_store()
|
|
250
|
+
self._generate_weather_store(p_settings)
|
|
238
251
|
|
|
239
252
|
# check aircraft type and set in config if needed
|
|
240
253
|
if self.params["nox_ei"] != "TTV":
|
|
241
254
|
if isinstance(self.source, Flight):
|
|
242
255
|
ac = self.source.attrs["aircraft_type"]
|
|
243
|
-
if ac in
|
|
244
|
-
|
|
245
|
-
elif ac in
|
|
246
|
-
|
|
256
|
+
if ac in wide_body_jets():
|
|
257
|
+
p_settings["ac_type"] = "wide-body"
|
|
258
|
+
elif ac in regional_jets():
|
|
259
|
+
p_settings["ac_type"] = "regional"
|
|
247
260
|
else:
|
|
248
|
-
|
|
261
|
+
p_settings["ac_type"] = "single-aisle"
|
|
249
262
|
else:
|
|
250
|
-
|
|
263
|
+
p_settings["ac_type"] = "wide-body"
|
|
251
264
|
|
|
252
265
|
clim_imp = GeTaCCFs(self)
|
|
253
|
-
clim_imp.get_accfs(**
|
|
266
|
+
clim_imp.get_accfs(**p_settings)
|
|
254
267
|
aCCFs, _ = clim_imp.get_xarray()
|
|
255
268
|
|
|
256
269
|
# assign ACCF outputs to source
|
|
@@ -259,66 +272,53 @@ class ACCF(Model):
|
|
|
259
272
|
# skip met variables
|
|
260
273
|
if key in self.short_vars:
|
|
261
274
|
continue
|
|
262
|
-
if not isinstance(key, str):
|
|
263
|
-
continue
|
|
264
275
|
|
|
276
|
+
assert isinstance(key, str)
|
|
265
277
|
if isinstance(self.source, GeoVectorDataset):
|
|
266
278
|
self.source[key] = self.source.intersect_met(maCCFs[key])
|
|
267
279
|
else:
|
|
268
|
-
self.source[key] =
|
|
269
|
-
|
|
270
|
-
# Tag output with additional attrs when source is MetDataset
|
|
271
|
-
if isinstance(self.source, MetDataset):
|
|
272
|
-
attrs: dict[str, Any] = {
|
|
273
|
-
"description": self.long_name,
|
|
274
|
-
"pycontrails_version": pycontrails.__version__,
|
|
275
|
-
}
|
|
276
|
-
if self.met is not None:
|
|
277
|
-
attrs["met_source"] = self.met.attrs.get("met_source", "unknown")
|
|
280
|
+
self.source[key] = arr
|
|
278
281
|
|
|
279
|
-
|
|
282
|
+
self.transfer_met_source_attrs()
|
|
283
|
+
self.source.attrs["pycontrails_version"] = pycontrails.__version__
|
|
280
284
|
|
|
281
|
-
return self.source
|
|
285
|
+
return self.source
|
|
282
286
|
|
|
283
|
-
def _generate_weather_store(self) -> None:
|
|
287
|
+
def _generate_weather_store(self, p_settings: dict[str, Any]) -> None:
|
|
284
288
|
from climaccf.weather_store import WeatherStore
|
|
285
289
|
|
|
286
290
|
# The library does not call the coordinates by name, it just slices the
|
|
287
291
|
# underlying data array, so we need to put them in the expected order.
|
|
288
292
|
# It also needs variables to have the ECMWF short name
|
|
289
293
|
if isinstance(self.met, MetDataset):
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
for var in self.ds_met.data_vars:
|
|
296
|
-
matching_variable = [v for v in self.met_variables if var == v.standard_name]
|
|
297
|
-
if matching_variable:
|
|
298
|
-
self.ds_met = self.ds_met.rename({var: matching_variable[0].short_name})
|
|
294
|
+
ds_met = self.met.data.transpose("time", "level", "latitude", "longitude")
|
|
295
|
+
name_dict = {v.standard_name: v.short_name for v in self.met_variables}
|
|
296
|
+
ds_met = ds_met.rename(name_dict)
|
|
297
|
+
else:
|
|
298
|
+
ds_met = None
|
|
299
299
|
|
|
300
300
|
if hasattr(self, "surface"):
|
|
301
|
-
|
|
302
|
-
for
|
|
303
|
-
|
|
304
|
-
if matching_variable:
|
|
305
|
-
self.ds_sur = self.ds_sur.rename({var: matching_variable[0].short_name})
|
|
301
|
+
ds_sur = self.surface.data.squeeze().transpose("time", "latitude", "longitude")
|
|
302
|
+
name_dict = {v.standard_name: v.short_name for v in self.sur_variables}
|
|
303
|
+
ds_sur = ds_sur.rename(name_dict)
|
|
306
304
|
else:
|
|
307
|
-
|
|
305
|
+
ds_sur = None
|
|
308
306
|
|
|
309
307
|
ws = WeatherStore(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
ll_resolution=
|
|
313
|
-
forecast_step=
|
|
308
|
+
ds_met,
|
|
309
|
+
ds_sur,
|
|
310
|
+
ll_resolution=p_settings["horizontal_resolution"],
|
|
311
|
+
forecast_step=p_settings["forecast_step"],
|
|
314
312
|
)
|
|
315
|
-
|
|
313
|
+
|
|
314
|
+
if p_settings["lat_bound"] and p_settings["lon_bound"]:
|
|
316
315
|
ws.reduce_domain(
|
|
317
316
|
{
|
|
318
|
-
"latitude":
|
|
319
|
-
"longitude":
|
|
317
|
+
"latitude": p_settings["lat_bound"],
|
|
318
|
+
"longitude": p_settings["lon_bound"],
|
|
320
319
|
}
|
|
321
320
|
)
|
|
321
|
+
|
|
322
322
|
self.ds = ws.get_xarray()
|
|
323
323
|
self.variable_names = ws.variable_names
|
|
324
324
|
self.pre_variable_names = ws.pre_variable_names
|
|
@@ -329,51 +329,78 @@ class ACCF(Model):
|
|
|
329
329
|
self.axes = ws.axes
|
|
330
330
|
self.var_xr = ws.var_xr
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
332
|
+
|
|
333
|
+
def _get_accf_config(params: dict[str, Any]) -> dict[str, Any]:
|
|
334
|
+
# a good portion of these will get ignored since we are not producing an
|
|
335
|
+
# output file, but the library will complain if they aren't defined
|
|
336
|
+
return {
|
|
337
|
+
"lat_bound": params["lat_bound"],
|
|
338
|
+
"lon_bound": params["lon_bound"],
|
|
339
|
+
"time_bound": None,
|
|
340
|
+
"horizontal_resolution": params["horizontal_resolution"],
|
|
341
|
+
"forecast_step": params["forecast_step"],
|
|
342
|
+
"NOx_aCCF": True,
|
|
343
|
+
"NOx&inverse_EIs": params["nox_ei"],
|
|
344
|
+
"output_format": "netCDF",
|
|
345
|
+
"mean": False,
|
|
346
|
+
"std": False,
|
|
347
|
+
"merged": params["merged"],
|
|
348
|
+
"aCCF-V": params["accf_v"],
|
|
349
|
+
"efficacy": params["efficacy"],
|
|
350
|
+
"efficacy-option": params["efficacy_option"],
|
|
351
|
+
"emission_scenario": params["emission_scenario"],
|
|
352
|
+
"climate_indicator": params["climate_indicator"],
|
|
353
|
+
"TimeHorizon": params["time_horizon"],
|
|
354
|
+
"ac_type": "wide-body",
|
|
355
|
+
"sep_ri_rw": params["sep_ri_rw"],
|
|
356
|
+
"PMO": params["PMO"],
|
|
357
|
+
"aCCF-scalingF": {
|
|
358
|
+
"CH4": params["ch4_scaling"],
|
|
359
|
+
"CO2": params["co2_scaling"],
|
|
360
|
+
"Cont.": params["cont_scaling"],
|
|
361
|
+
"H2O": params["h2o_scaling"],
|
|
362
|
+
"O3": params["o3_scaling"],
|
|
363
|
+
},
|
|
364
|
+
"PCFA": params["pfca"],
|
|
365
|
+
"PCFA-ISSR": {
|
|
366
|
+
"rhi_threshold": params["issr_rhi_threshold"],
|
|
367
|
+
"temp_threshold": params["issr_temp_threshold"],
|
|
368
|
+
},
|
|
369
|
+
"PCFA-SAC": {
|
|
370
|
+
"EI_H2O": params["sac_ei_h2o"],
|
|
371
|
+
"Q": params["sac_q"],
|
|
372
|
+
"eta": params["sac_eta"],
|
|
373
|
+
},
|
|
374
|
+
"Chotspots": False,
|
|
375
|
+
"hotspots_binary": True,
|
|
376
|
+
"color": "Reds",
|
|
377
|
+
"geojson": False,
|
|
378
|
+
"save_path": "./",
|
|
379
|
+
"save_format": "netCDF",
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _rad_instantaneous_to_accumulated(ds: xr.Dataset) -> xr.Dataset:
|
|
384
|
+
"""Convert instantaneous radiation to accumulated radiation."""
|
|
385
|
+
|
|
386
|
+
for name, da in ds.items():
|
|
387
|
+
try:
|
|
388
|
+
unit = da.attrs["units"]
|
|
389
|
+
except KeyError as e:
|
|
390
|
+
msg = (
|
|
391
|
+
f"Radiation data contains '{name}' variable "
|
|
392
|
+
"but units are not specified. Provide units in the "
|
|
393
|
+
f"rad['{name}'].attrs passed into ACCF."
|
|
394
|
+
)
|
|
395
|
+
raise KeyError(msg) from e
|
|
396
|
+
|
|
397
|
+
if unit == "J m**-2":
|
|
398
|
+
continue
|
|
399
|
+
if unit != "W m**-2":
|
|
400
|
+
msg = f"Unexpected units '{unit}' for '{name}'. Expected 'J m**-2' or 'W m**-2'."
|
|
401
|
+
raise ValueError(msg)
|
|
402
|
+
|
|
403
|
+
# Convert from W m**-2 to J m**-2
|
|
404
|
+
ds[name] = da.assign_attrs(units="J m**-2") * 3600.0
|
|
405
|
+
|
|
406
|
+
return ds
|