pycontrails 0.54.5__cp312-cp312-macosx_11_0_arm64.whl → 0.54.6__cp312-cp312-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/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +34 -16
- pycontrails/core/airports.py +3 -4
- pycontrails/core/flight.py +2 -4
- pycontrails/core/flightplan.py +11 -11
- pycontrails/core/met_var.py +62 -0
- pycontrails/core/models.py +1 -0
- pycontrails/core/rgi_cython.cpython-312-darwin.so +0 -0
- 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/cocip/cocip.py +78 -18
- pycontrails/models/cocip/output_formats.py +1 -0
- pycontrails/models/dry_advection.py +51 -19
- pycontrails/models/humidity_scaling/humidity_scaling.py +1 -1
- pycontrails/models/pcc.py +1 -2
- pycontrails/models/ps_model/ps_model.py +2 -30
- 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.5.dist-info → pycontrails-0.54.6.dist-info}/METADATA +3 -4
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.6.dist-info}/RECORD +28 -28
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.6.dist-info}/WHEEL +1 -1
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.6.dist-info}/LICENSE +0 -0
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.6.dist-info}/NOTICE +0 -0
- {pycontrails-0.54.5.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/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:
|
|
@@ -1982,9 +1982,7 @@ def filter_altitude(
|
|
|
1982
1982
|
result[i0:i1] = altitude_filt[i0:i1]
|
|
1983
1983
|
|
|
1984
1984
|
# reapply Savitzky-Golay filter to smooth climb and descent
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
return result
|
|
1985
|
+
return _sg_filter(result, window_length=kernel_size)
|
|
1988
1986
|
|
|
1989
1987
|
|
|
1990
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
|
|
pycontrails/core/met_var.py
CHANGED
|
@@ -282,6 +282,35 @@ VerticalVelocity = MetVariable(
|
|
|
282
282
|
),
|
|
283
283
|
)
|
|
284
284
|
|
|
285
|
+
MassFractionOfCloudLiquidWaterInAir = MetVariable(
|
|
286
|
+
short_name="clw",
|
|
287
|
+
standard_name="mass_fraction_of_cloud_liquid_water_in_air",
|
|
288
|
+
long_name="Mass fraction of cloud liquid water in air",
|
|
289
|
+
units="kg kg**-1",
|
|
290
|
+
level_type="isobaricInhPa",
|
|
291
|
+
amip="clw",
|
|
292
|
+
description=("The mass fraction of cloud liquid water in moist air."),
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
MassFractionOfCloudIceInAir = MetVariable(
|
|
296
|
+
short_name="cli",
|
|
297
|
+
standard_name="mass_fraction_of_cloud_ice_in_air",
|
|
298
|
+
long_name="Mass fraction of cloud ice in air",
|
|
299
|
+
units="kg kg**-1",
|
|
300
|
+
level_type="isobaricInhPa",
|
|
301
|
+
amip="cli",
|
|
302
|
+
description=("The mass fraction of cloud ice in moist air."),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
CloudAreaFractionInAtmosphereLayer = MetVariable(
|
|
306
|
+
short_name="cl",
|
|
307
|
+
standard_name="cloud_area_fraction_in_atmosphere_layer",
|
|
308
|
+
long_name="Cloud area fraction in atmosphere layer",
|
|
309
|
+
units="[0 - 1]",
|
|
310
|
+
level_type="isobaricInhPa",
|
|
311
|
+
description=("The fraction of the horizontal area of a grid cell that contains cloud."),
|
|
312
|
+
)
|
|
313
|
+
|
|
285
314
|
|
|
286
315
|
# ----
|
|
287
316
|
# Single level variables
|
|
@@ -305,3 +334,36 @@ SurfacePressure = MetVariable(
|
|
|
305
334
|
"Earth's surface represented at a fixed point."
|
|
306
335
|
),
|
|
307
336
|
)
|
|
337
|
+
|
|
338
|
+
TOANetDownwardShortwaveFlux = MetVariable(
|
|
339
|
+
short_name="rst",
|
|
340
|
+
standard_name="toa_net_downward_shortwave_flux",
|
|
341
|
+
long_name="TOA net downward shortwave flux",
|
|
342
|
+
units="W m**-2",
|
|
343
|
+
level_type="nominalTop",
|
|
344
|
+
amip="rst",
|
|
345
|
+
description=(
|
|
346
|
+
'"shortwave" means shortwave radiation. "toa" means top of atmosphere. '
|
|
347
|
+
'"Downward" indicates a vector component which is positive when directed '
|
|
348
|
+
"downward (negative upward). Net downward radiation is the difference "
|
|
349
|
+
"between radiation from above (downwelling) and radiation from below (upwelling). "
|
|
350
|
+
"In accordance with common usage in geophysical disciplines, "
|
|
351
|
+
'"flux" implies per unit area, called "flux density" in physics.'
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
TOAOutgoingLongwaveFlux = MetVariable(
|
|
356
|
+
short_name="rlut",
|
|
357
|
+
standard_name="toa_outgoing_longwave_flux",
|
|
358
|
+
long_name="TOA outgoing longwave_flux",
|
|
359
|
+
units="W m**-2",
|
|
360
|
+
level_type="nominalTop",
|
|
361
|
+
amip="rlut",
|
|
362
|
+
description=(
|
|
363
|
+
'"longwave" means longwave radiation. "toa" means top of atmosphere. '
|
|
364
|
+
"The TOA outgoing longwave flux is the upwelling thermal radiative flux, "
|
|
365
|
+
'often called the "outgoing longwave radiation" or "OLR". '
|
|
366
|
+
"In accordance with common usage in geophysical disciplines, "
|
|
367
|
+
'"flux" implies per unit area, called "flux density" in physics.'
|
|
368
|
+
),
|
|
369
|
+
)
|
pycontrails/core/models.py
CHANGED
|
Binary file
|
|
@@ -265,7 +265,7 @@ def _find_match(
|
|
|
265
265
|
|
|
266
266
|
# list of MetVariable options
|
|
267
267
|
# here we extract the first MetVariable in var that is supported
|
|
268
|
-
|
|
268
|
+
if isinstance(var, list | tuple):
|
|
269
269
|
for v in var:
|
|
270
270
|
# sanity check since we don't support other types as lists
|
|
271
271
|
if not isinstance(v, MetVariable):
|
|
@@ -88,9 +88,8 @@ class ERA5(ECMWFAPI):
|
|
|
88
88
|
If None, cache is turned off.
|
|
89
89
|
url : str | None
|
|
90
90
|
Override the default `cdsapi <https://github.com/ecmwf/cdsapi>`_ url.
|
|
91
|
-
As of
|
|
92
|
-
is "https://cds
|
|
93
|
-
"https://cds.climate.copernicus.eu/api/v2". If None, the url is set
|
|
91
|
+
As of January 2025, the url for the `CDS Server <https://cds.climate.copernicus.eu>`_
|
|
92
|
+
is "https://cds.climate.copernicus.eu/api". If None, the url is set
|
|
94
93
|
by the ``CDSAPI_URL`` environment variable. If this is not defined, the
|
|
95
94
|
``cdsapi`` package will determine the url.
|
|
96
95
|
key : str | None
|
|
@@ -539,12 +538,12 @@ class ERA5(ECMWFAPI):
|
|
|
539
538
|
LOG.debug("Input dataset processed with pycontrails > 0.29")
|
|
540
539
|
return ds
|
|
541
540
|
|
|
542
|
-
# For "reanalysis-era5-single-levels"
|
|
543
|
-
#
|
|
541
|
+
# For "reanalysis-era5-single-levels",
|
|
542
|
+
# the netcdf file does not contain the dimension "level"
|
|
544
543
|
if self.is_single_level:
|
|
545
544
|
ds = ds.expand_dims(level=self.pressure_levels)
|
|
546
545
|
|
|
547
|
-
# New CDS
|
|
546
|
+
# New CDS (Aug 2024) gives "valid_time" instead of "time"
|
|
548
547
|
# and "pressure_level" instead of "level"
|
|
549
548
|
if "valid_time" in ds:
|
|
550
549
|
ds = ds.rename(valid_time="time")
|
|
@@ -119,9 +119,8 @@ class ERA5ModelLevel(ECMWFAPI):
|
|
|
119
119
|
By default, False.
|
|
120
120
|
url : str | None
|
|
121
121
|
Override the default `cdsapi <https://github.com/ecmwf/cdsapi>`_ url.
|
|
122
|
-
As of
|
|
123
|
-
is "https://cds
|
|
124
|
-
"https://cds.climate.copernicus.eu/api/v2". If None, the url is set
|
|
122
|
+
As of January 2025, the url for the `CDS Server <https://cds.climate.copernicus.eu>`_
|
|
123
|
+
is "https://cds.climate.copernicus.eu/api". If None, the url is set
|
|
125
124
|
by the ``CDSAPI_URL`` environment variable. If this is not defined, the
|
|
126
125
|
``cdsapi`` package will determine the url.
|
|
127
126
|
key : str | None
|
|
@@ -465,13 +464,13 @@ class ERA5ModelLevel(ECMWFAPI):
|
|
|
465
464
|
ds_ml = xr.open_dataset(ml_target)
|
|
466
465
|
lnsp = xr.open_dataarray(lnsp_target)
|
|
467
466
|
|
|
468
|
-
# New CDS
|
|
467
|
+
# New CDS (Aug 2024) gives "valid_time" instead of "time"
|
|
469
468
|
if "valid_time" in ds_ml:
|
|
470
469
|
ds_ml = ds_ml.rename(valid_time="time")
|
|
471
470
|
if "valid_time" in lnsp.dims:
|
|
472
471
|
lnsp = lnsp.rename(valid_time="time")
|
|
473
472
|
|
|
474
|
-
#
|
|
473
|
+
# Legacy CDS (prior to Aug 2024) gives "level" instead of "model_level"
|
|
475
474
|
if "level" in ds_ml.dims:
|
|
476
475
|
ds_ml = ds_ml.rename(level="model_level")
|
|
477
476
|
|
pycontrails/datalib/ecmwf/ifs.py
CHANGED
|
@@ -247,9 +247,7 @@ class IFS(metsource.MetDataSource):
|
|
|
247
247
|
ds_fl = ds_fl.drop_vars(names=["hyai", "hybi", "hyam", "hybm"])
|
|
248
248
|
|
|
249
249
|
# merge all datasets using the "ds_fl" dimensions as the join keys
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return ds
|
|
250
|
+
return xr.merge([ds_fl, ds_full, ds_surface, ds_rad], join="left") # order matters!
|
|
253
251
|
|
|
254
252
|
def _calc_geopotential(self, ds: xr.Dataset) -> xr.DataArray:
|
|
255
253
|
warnings.warn(
|
pycontrails/datalib/gfs/gfs.py
CHANGED
|
@@ -570,9 +570,7 @@ class GFSForecast(metsource.MetDataSource):
|
|
|
570
570
|
ds = ds.expand_dims("time")
|
|
571
571
|
|
|
572
572
|
# drop step/number
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
return ds
|
|
573
|
+
return ds.drop_vars(["step", "nominalTop", "surface"], errors="ignore")
|
|
576
574
|
|
|
577
575
|
def _process_dataset(self, ds: xr.Dataset, **kwargs: Any) -> met.MetDataset:
|
|
578
576
|
"""Process the :class:`xr.Dataset` opened from cache or local files.
|
|
@@ -68,12 +68,26 @@ class Cocip(Model):
|
|
|
68
68
|
-----
|
|
69
69
|
**Inputs**
|
|
70
70
|
|
|
71
|
-
The required meteorology variables depend on the data source
|
|
71
|
+
The required meteorology variables depend on the data source. :class:`Cocip`
|
|
72
|
+
supports data-source-specific variables from ECMWF models (HRES, ERA5) and the NCEP GFS, plus
|
|
73
|
+
a generic set of model-agnostic variables.
|
|
72
74
|
|
|
73
75
|
See :attr:`met_variables` and :attr:`rad_variables` for the list of required variables
|
|
74
76
|
to the ``met`` and ``rad`` parameters, respectively.
|
|
75
77
|
When an item in one of these arrays is a :class:`tuple`, variable keys depend on data source.
|
|
76
78
|
|
|
79
|
+
A warning will be raised if meteorology data is from a source not currently supported by
|
|
80
|
+
a pycontrails datalib. In this case it is the responsibility of the user to ensure that
|
|
81
|
+
meteorology data is formatted correctly. The warning can be suppressed with a context manager:
|
|
82
|
+
|
|
83
|
+
.. code-block:: python
|
|
84
|
+
:emphasize-lines: 2,3
|
|
85
|
+
|
|
86
|
+
import warnings
|
|
87
|
+
with warnings.catch_warnings():
|
|
88
|
+
warnings.simplefilter("ignore", category=UserWarning, message="Unknown provider")
|
|
89
|
+
cocip = Cocip(met, rad, ...)
|
|
90
|
+
|
|
77
91
|
The current list of required variables (labelled by ``"standard_name"``):
|
|
78
92
|
|
|
79
93
|
.. list-table:: Variable keys for pressure level data
|
|
@@ -82,24 +96,31 @@ class Cocip(Model):
|
|
|
82
96
|
* - Parameter
|
|
83
97
|
- ECMWF
|
|
84
98
|
- GFS
|
|
99
|
+
- Generic
|
|
85
100
|
* - Air Temperature
|
|
86
101
|
- ``air_temperature``
|
|
87
102
|
- ``air_temperature``
|
|
103
|
+
- ``air_temperature``
|
|
88
104
|
* - Specific Humidity
|
|
89
105
|
- ``specific_humidity``
|
|
90
106
|
- ``specific_humidity``
|
|
107
|
+
- ``specific_humidity``
|
|
91
108
|
* - Eastward wind
|
|
92
109
|
- ``eastward_wind``
|
|
93
110
|
- ``eastward_wind``
|
|
111
|
+
- ``eastward_wind``
|
|
94
112
|
* - Northward wind
|
|
95
113
|
- ``northward_wind``
|
|
96
114
|
- ``northward_wind``
|
|
115
|
+
- ``northward_wind``
|
|
97
116
|
* - Vertical velocity
|
|
98
117
|
- ``lagrangian_tendency_of_air_pressure``
|
|
99
118
|
- ``lagrangian_tendency_of_air_pressure``
|
|
119
|
+
- ``lagrangian_tendency_of_air_pressure``
|
|
100
120
|
* - Ice water content
|
|
101
121
|
- ``specific_cloud_ice_water_content``
|
|
102
122
|
- ``ice_water_mixing_ratio``
|
|
123
|
+
- ``mass_fraction_of_cloud_ice_in_air``
|
|
103
124
|
|
|
104
125
|
.. list-table:: Variable keys for single-level radiation data
|
|
105
126
|
:header-rows: 1
|
|
@@ -107,12 +128,15 @@ class Cocip(Model):
|
|
|
107
128
|
* - Parameter
|
|
108
129
|
- ECMWF
|
|
109
130
|
- GFS
|
|
131
|
+
- Generic
|
|
110
132
|
* - Top solar radiation
|
|
111
133
|
- ``top_net_solar_radiation``
|
|
112
134
|
- ``toa_upward_shortwave_flux``
|
|
135
|
+
- ``toa_net_downward_shortwave_flux``
|
|
113
136
|
* - Top thermal radiation
|
|
114
137
|
- ``top_net_thermal_radiation``
|
|
115
138
|
- ``toa_upward_longwave_flux``
|
|
139
|
+
- ``toa_outgoing_longwave_flux``
|
|
116
140
|
|
|
117
141
|
**Modifications**
|
|
118
142
|
|
|
@@ -214,14 +238,26 @@ class Cocip(Model):
|
|
|
214
238
|
met_var.EastwardWind,
|
|
215
239
|
met_var.NorthwardWind,
|
|
216
240
|
met_var.VerticalVelocity,
|
|
217
|
-
(
|
|
241
|
+
(
|
|
242
|
+
met_var.MassFractionOfCloudIceInAir,
|
|
243
|
+
ecmwf.SpecificCloudIceWaterContent,
|
|
244
|
+
gfs.CloudIceWaterMixingRatio,
|
|
245
|
+
),
|
|
218
246
|
)
|
|
219
247
|
|
|
220
248
|
#: Required single-level top of atmosphere radiation variables.
|
|
221
249
|
#: Variable keys depend on data source (e.g. ECMWF, GFS).
|
|
222
250
|
rad_variables = (
|
|
223
|
-
(
|
|
224
|
-
|
|
251
|
+
(
|
|
252
|
+
met_var.TOANetDownwardShortwaveFlux,
|
|
253
|
+
ecmwf.TopNetSolarRadiation,
|
|
254
|
+
gfs.TOAUpwardShortwaveRadiation,
|
|
255
|
+
),
|
|
256
|
+
(
|
|
257
|
+
met_var.TOAOutgoingLongwaveFlux,
|
|
258
|
+
ecmwf.TopNetThermalRadiation,
|
|
259
|
+
gfs.TOAUpwardLongwaveRadiation,
|
|
260
|
+
),
|
|
225
261
|
)
|
|
226
262
|
|
|
227
263
|
#: Minimal set of met variables needed to run the model after pre-processing.
|
|
@@ -242,7 +278,11 @@ class Cocip(Model):
|
|
|
242
278
|
#: Moved Geopotential from :attr:`met_variables` to :attr:`optional_met_variables`
|
|
243
279
|
optional_met_variables = (
|
|
244
280
|
(met_var.Geopotential, met_var.GeopotentialHeight),
|
|
245
|
-
(
|
|
281
|
+
(
|
|
282
|
+
met_var.CloudAreaFractionInAtmosphereLayer,
|
|
283
|
+
ecmwf.CloudAreaFractionInLayer,
|
|
284
|
+
gfs.TotalCloudCoverIsobaric,
|
|
285
|
+
),
|
|
246
286
|
)
|
|
247
287
|
|
|
248
288
|
#: Met data is not optional
|
|
@@ -575,10 +615,12 @@ class Cocip(Model):
|
|
|
575
615
|
if verbose_outputs:
|
|
576
616
|
interpolate_met(met, self.source, "tau_cirrus", **interp_kwargs)
|
|
577
617
|
|
|
578
|
-
# handle ECMWF/GFS ciwc variables
|
|
618
|
+
# handle ECMWF/GFS/generic ciwc variables
|
|
579
619
|
if (key := "specific_cloud_ice_water_content") in met: # noqa: SIM114
|
|
580
620
|
interpolate_met(met, self.source, key, **interp_kwargs)
|
|
581
|
-
elif (key := "ice_water_mixing_ratio") in met:
|
|
621
|
+
elif (key := "ice_water_mixing_ratio") in met: # noqa: SIM114
|
|
622
|
+
interpolate_met(met, self.source, key, **interp_kwargs)
|
|
623
|
+
elif (key := "mass_fraction_of_cloud_ice_in_air") in met:
|
|
582
624
|
interpolate_met(met, self.source, key, **interp_kwargs)
|
|
583
625
|
|
|
584
626
|
self.source["rho_air"] = thermo.rho_d(
|
|
@@ -1587,8 +1629,7 @@ def _process_rad(rad: MetDataset) -> MetDataset:
|
|
|
1587
1629
|
rad.data["time"].attrs["shift_radiation_time"] = "variable"
|
|
1588
1630
|
return rad
|
|
1589
1631
|
|
|
1590
|
-
|
|
1591
|
-
shift_radiation_time = -np.timedelta64(30, "m")
|
|
1632
|
+
shift_radiation_time = -np.timedelta64(30, "m")
|
|
1592
1633
|
|
|
1593
1634
|
elif dataset == "ERA5" and product == "ensemble":
|
|
1594
1635
|
shift_radiation_time = -np.timedelta64(90, "m")
|
|
@@ -1893,8 +1934,8 @@ def calc_shortwave_radiation(
|
|
|
1893
1934
|
Raises
|
|
1894
1935
|
------
|
|
1895
1936
|
ValueError
|
|
1896
|
-
If ``rad`` does not contain ``"
|
|
1897
|
-
``"top_net_solar_radiation"`` variable.
|
|
1937
|
+
If ``rad`` does not contain ``"toa_net_downward_shortwave_flux"``,
|
|
1938
|
+
``"toa_upward_shortwave_flux"`` or ``"top_net_solar_radiation"`` variable.
|
|
1898
1939
|
|
|
1899
1940
|
Notes
|
|
1900
1941
|
-----
|
|
@@ -1918,6 +1959,13 @@ def calc_shortwave_radiation(
|
|
|
1918
1959
|
sdr = geo.solar_direct_radiation(longitude, latitude, time, threshold_cos_sza=0.01)
|
|
1919
1960
|
vector["sdr"] = sdr
|
|
1920
1961
|
|
|
1962
|
+
# Generic contains net downward shortwave flux at TOA (SDR - RSR) in W/m2
|
|
1963
|
+
generic_key = "toa_net_downward_shortwave_flux"
|
|
1964
|
+
if generic_key in rad:
|
|
1965
|
+
tnsr = interpolate_met(rad, vector, generic_key, **interp_kwargs)
|
|
1966
|
+
vector["rsr"] = np.maximum(sdr - tnsr, 0.0)
|
|
1967
|
+
return
|
|
1968
|
+
|
|
1921
1969
|
# GFS contains RSR (toa_upward_shortwave_flux) variable directly
|
|
1922
1970
|
gfs_key = "toa_upward_shortwave_flux"
|
|
1923
1971
|
if gfs_key in rad:
|
|
@@ -1926,10 +1974,13 @@ def calc_shortwave_radiation(
|
|
|
1926
1974
|
|
|
1927
1975
|
ecmwf_key = "top_net_solar_radiation"
|
|
1928
1976
|
if ecmwf_key not in rad:
|
|
1929
|
-
msg =
|
|
1977
|
+
msg = (
|
|
1978
|
+
f"'rad' data must contain either '{generic_key}' (generic), "
|
|
1979
|
+
f"'{gfs_key}' (GFS), or '{ecmwf_key}' (ECMWF) variable."
|
|
1980
|
+
)
|
|
1930
1981
|
raise ValueError(msg)
|
|
1931
1982
|
|
|
1932
|
-
# ECMWF contains
|
|
1983
|
+
# ECMWF also contains net downward shortwave flux at TOA, but possibly as an accumulation
|
|
1933
1984
|
tnsr = interpolate_met(rad, vector, ecmwf_key, **interp_kwargs)
|
|
1934
1985
|
tnsr = _rad_accumulation_to_average_instantaneous(rad, ecmwf_key, tnsr)
|
|
1935
1986
|
vector.update({ecmwf_key: tnsr})
|
|
@@ -1958,14 +2009,20 @@ def calc_outgoing_longwave_radiation(
|
|
|
1958
2009
|
Raises
|
|
1959
2010
|
------
|
|
1960
2011
|
ValueError
|
|
1961
|
-
If ``rad`` does not contain a ``"
|
|
1962
|
-
or ``"top_net_thermal_radiation"`` variable.
|
|
2012
|
+
If ``rad`` does not contain a ``"toa_outgoing_longwave_flux"``,
|
|
2013
|
+
``"toa_upward_longwave_flux"`` or ``"top_net_thermal_radiation"`` variable.
|
|
1963
2014
|
"""
|
|
1964
2015
|
|
|
1965
2016
|
if "olr" in vector:
|
|
1966
|
-
return
|
|
2017
|
+
return
|
|
2018
|
+
|
|
2019
|
+
# Generic contains OLR (toa_outgoing_longwave_flux) directly
|
|
2020
|
+
generic_key = "toa_outgoing_longwave_flux"
|
|
2021
|
+
if generic_key in rad:
|
|
2022
|
+
interpolate_met(rad, vector, generic_key, "olr", **interp_kwargs)
|
|
2023
|
+
return
|
|
1967
2024
|
|
|
1968
|
-
# GFS contains OLR (toa_upward_longwave_flux)
|
|
2025
|
+
# GFS contains OLR (toa_upward_longwave_flux) directly
|
|
1969
2026
|
gfs_key = "toa_upward_longwave_flux"
|
|
1970
2027
|
if gfs_key in rad:
|
|
1971
2028
|
interpolate_met(rad, vector, gfs_key, "olr", **interp_kwargs)
|
|
@@ -1974,7 +2031,10 @@ def calc_outgoing_longwave_radiation(
|
|
|
1974
2031
|
# ECMWF contains "top_net_thermal_radiation" which is -1 * OLR
|
|
1975
2032
|
ecmwf_key = "top_net_thermal_radiation"
|
|
1976
2033
|
if ecmwf_key not in rad:
|
|
1977
|
-
msg =
|
|
2034
|
+
msg = (
|
|
2035
|
+
f"'rad' data must contain either '{generic_key}' (generic), "
|
|
2036
|
+
f"'{gfs_key}' (GFS), or '{ecmwf_key}' (ECMWF) variable."
|
|
2037
|
+
)
|
|
1978
2038
|
raise ValueError(msg)
|
|
1979
2039
|
|
|
1980
2040
|
tntr = interpolate_met(rad, vector, ecmwf_key, **interp_kwargs)
|
|
@@ -63,6 +63,15 @@ class DryAdvectionParams(models.AdvectionBuffers):
|
|
|
63
63
|
# If None, only pointwise advection is simulated without wind shear effects.
|
|
64
64
|
azimuth: float | None = 0.0
|
|
65
65
|
|
|
66
|
+
#: Add additional intermediate variables to the output vector.
|
|
67
|
+
#: This includes interpolated met variables and wind-shear-derived geometry.
|
|
68
|
+
verbose_outputs: bool = False
|
|
69
|
+
|
|
70
|
+
#: Whether to include ``source`` points in the output vector. Enabling allows
|
|
71
|
+
#: the user to view additional data (e.g., interpolated met variables) for
|
|
72
|
+
#: source points as well as evolved points.
|
|
73
|
+
include_source_in_output: bool = False
|
|
74
|
+
|
|
66
75
|
|
|
67
76
|
class DryAdvection(models.Model):
|
|
68
77
|
"""Simulate "dry advection" of an emissions plume with an elliptical cross section.
|
|
@@ -154,42 +163,51 @@ class DryAdvection(models.Model):
|
|
|
154
163
|
sedimentation_rate = self.params["sedimentation_rate"]
|
|
155
164
|
dz_m = self.params["dz_m"]
|
|
156
165
|
max_depth = self.params["max_depth"]
|
|
166
|
+
verbose_outputs = self.params["verbose_outputs"]
|
|
157
167
|
|
|
158
168
|
source_time = self.source["time"]
|
|
159
169
|
t0 = pd.Timestamp(source_time.min()).floor(pd.Timedelta(dt_integration)).to_numpy()
|
|
160
170
|
t1 = source_time.max()
|
|
161
171
|
timesteps = np.arange(t0 + dt_integration, t1 + dt_integration + max_age, dt_integration)
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
vector2 = GeoVectorDataset()
|
|
164
174
|
met = None
|
|
165
175
|
|
|
166
176
|
evolved = []
|
|
167
177
|
for t in timesteps:
|
|
168
178
|
filt = (source_time < t) & (source_time >= t - dt_integration)
|
|
169
|
-
|
|
179
|
+
vector1 = vector2 + self.source.filter(filt, copy=False)
|
|
170
180
|
|
|
171
|
-
t0 =
|
|
172
|
-
t1 =
|
|
181
|
+
t0 = vector1["time"].min()
|
|
182
|
+
t1 = vector1["time"].max()
|
|
173
183
|
met = maybe_downselect_mds(self.met, met, t0, t1)
|
|
174
184
|
|
|
175
|
-
|
|
185
|
+
vector2 = _evolve_one_step(
|
|
176
186
|
met,
|
|
177
|
-
|
|
187
|
+
vector1,
|
|
178
188
|
t,
|
|
179
189
|
sedimentation_rate=sedimentation_rate,
|
|
180
190
|
dz_m=dz_m,
|
|
181
191
|
max_depth=max_depth,
|
|
192
|
+
verbose_outputs=verbose_outputs,
|
|
182
193
|
**interp_kwargs,
|
|
183
194
|
)
|
|
195
|
+
evolved.append(vector1)
|
|
184
196
|
|
|
185
|
-
filt = (
|
|
186
|
-
|
|
197
|
+
filt = (vector2["age"] <= max_age) & vector2.coords_intersect_met(self.met)
|
|
198
|
+
vector2 = vector2.filter(filt)
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
if not vector and np.all(source_time < t):
|
|
200
|
+
if not vector2 and np.all(source_time < t):
|
|
190
201
|
break
|
|
191
202
|
|
|
192
|
-
|
|
203
|
+
evolved.append(vector2)
|
|
204
|
+
out = GeoVectorDataset.sum(evolved, fill_value=np.nan)
|
|
205
|
+
|
|
206
|
+
if self.params["include_source_in_output"]:
|
|
207
|
+
return out
|
|
208
|
+
|
|
209
|
+
filt = out["age"] > np.timedelta64(0, "ns")
|
|
210
|
+
return out.filter(filt)
|
|
193
211
|
|
|
194
212
|
def _prepare_source(self) -> GeoVectorDataset:
|
|
195
213
|
r"""Prepare :attr:`source` vector for advection by wind-shear-derived variables.
|
|
@@ -364,8 +382,12 @@ def _calc_geometry(
|
|
|
364
382
|
dz_m: float,
|
|
365
383
|
dt: npt.NDArray[np.timedelta64] | np.timedelta64,
|
|
366
384
|
max_depth: float | None,
|
|
385
|
+
verbose_outputs: bool,
|
|
367
386
|
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
368
|
-
"""Calculate wind-shear-derived geometry of evolved plume.
|
|
387
|
+
"""Calculate wind-shear-derived geometry of evolved plume.
|
|
388
|
+
|
|
389
|
+
This method mutates the input ``vector`` in place.
|
|
390
|
+
"""
|
|
369
391
|
|
|
370
392
|
u_wind = vector["u_wind"]
|
|
371
393
|
v_wind = vector["v_wind"]
|
|
@@ -419,6 +441,11 @@ def _calc_geometry(
|
|
|
419
441
|
eff_heat_rate=None,
|
|
420
442
|
)
|
|
421
443
|
|
|
444
|
+
if verbose_outputs:
|
|
445
|
+
vector["ds_dz"] = ds_dz
|
|
446
|
+
vector["dsn_dz"] = dsn_dz
|
|
447
|
+
vector["dT_dz"] = dT_dz
|
|
448
|
+
|
|
422
449
|
sigma_yy_2, sigma_zz_2, sigma_yz_2 = contrail_properties.plume_temporal_evolution(
|
|
423
450
|
width,
|
|
424
451
|
depth,
|
|
@@ -477,9 +504,13 @@ def _evolve_one_step(
|
|
|
477
504
|
sedimentation_rate: float,
|
|
478
505
|
dz_m: float,
|
|
479
506
|
max_depth: float | None,
|
|
507
|
+
verbose_outputs: bool,
|
|
480
508
|
**interp_kwargs: Any,
|
|
481
509
|
) -> GeoVectorDataset:
|
|
482
|
-
"""Evolve plume geometry by one step.
|
|
510
|
+
"""Evolve plume geometry by one step.
|
|
511
|
+
|
|
512
|
+
This method mutates the input ``vector`` in place.
|
|
513
|
+
"""
|
|
483
514
|
|
|
484
515
|
_perform_interp_for_step(met, vector, dz_m, **interp_kwargs)
|
|
485
516
|
u_wind = vector["u_wind"]
|
|
@@ -494,9 +525,9 @@ def _evolve_one_step(
|
|
|
494
525
|
level_2 = geo.advect_level(
|
|
495
526
|
vector.level,
|
|
496
527
|
vertical_velocity,
|
|
497
|
-
0.0,
|
|
498
|
-
0.0,
|
|
499
|
-
dt, # type: ignore[arg-type]
|
|
528
|
+
rho_air=0.0,
|
|
529
|
+
terminal_fall_speed=0.0,
|
|
530
|
+
dt=dt, # type: ignore[arg-type]
|
|
500
531
|
)
|
|
501
532
|
|
|
502
533
|
out = GeoVectorDataset._from_fastpath(
|
|
@@ -518,9 +549,10 @@ def _evolve_one_step(
|
|
|
518
549
|
# Attach wind-shear-derived geometry to output vector
|
|
519
550
|
azimuth_2, width_2, depth_2, sigma_yz_2, area_eff_2 = _calc_geometry(
|
|
520
551
|
vector,
|
|
521
|
-
dz_m,
|
|
522
|
-
dt, # type: ignore[arg-type]
|
|
523
|
-
max_depth, # type: ignore[arg-type]
|
|
552
|
+
dz_m=dz_m,
|
|
553
|
+
dt=dt, # type: ignore[arg-type]
|
|
554
|
+
max_depth=max_depth, # type: ignore[arg-type]
|
|
555
|
+
verbose_outputs=verbose_outputs,
|
|
524
556
|
)
|
|
525
557
|
out["azimuth"] = azimuth_2
|
|
526
558
|
out["width"] = width_2
|
|
@@ -654,7 +654,7 @@ def histogram_matching(
|
|
|
654
654
|
as a numpy array with the same shape and dtype as ``era5_rhi``.
|
|
655
655
|
"""
|
|
656
656
|
if level_type not in ["pressure", "model"]:
|
|
657
|
-
msg = f"Invalid 'level_type' value '{level_type}'.
|
|
657
|
+
msg = f"Invalid 'level_type' value '{level_type}'. Must be one of ['pressure', 'model']."
|
|
658
658
|
raise ValueError(msg)
|
|
659
659
|
df = _load_quantiles(level_type)
|
|
660
660
|
iagos_quantiles = df[("iagos", "iagos")]
|
pycontrails/models/pcc.py
CHANGED
|
@@ -186,8 +186,7 @@ class PCC(Model):
|
|
|
186
186
|
|
|
187
187
|
# issue recombining groups arises if "level" is in dims
|
|
188
188
|
# convert "level" dimension to coordinate
|
|
189
|
-
|
|
190
|
-
return b_crit_potential
|
|
189
|
+
return b_crit_potential.squeeze("level")
|
|
191
190
|
|
|
192
191
|
# apply calculation per pressure level
|
|
193
192
|
return (
|
|
@@ -7,7 +7,7 @@ import functools
|
|
|
7
7
|
import pathlib
|
|
8
8
|
import sys
|
|
9
9
|
from collections.abc import Mapping
|
|
10
|
-
from typing import Any
|
|
10
|
+
from typing import Any
|
|
11
11
|
|
|
12
12
|
if sys.version_info >= (3, 12):
|
|
13
13
|
from typing import override
|
|
@@ -25,7 +25,6 @@ from pycontrails.core.aircraft_performance import (
|
|
|
25
25
|
AircraftPerformanceData,
|
|
26
26
|
AircraftPerformanceParams,
|
|
27
27
|
)
|
|
28
|
-
from pycontrails.core.fleet import Fleet
|
|
29
28
|
from pycontrails.core.flight import Flight
|
|
30
29
|
from pycontrails.core.met import MetDataset
|
|
31
30
|
from pycontrails.core.met_var import AirTemperature, EastwardWind, MetVariable, NorthwardWind
|
|
@@ -118,35 +117,8 @@ class PSFlight(AircraftPerformance):
|
|
|
118
117
|
raise KeyError(msg)
|
|
119
118
|
return False
|
|
120
119
|
|
|
121
|
-
@overload
|
|
122
|
-
def eval(self, source: Fleet, **params: Any) -> Fleet: ...
|
|
123
|
-
|
|
124
|
-
@overload
|
|
125
|
-
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
126
|
-
|
|
127
|
-
@overload
|
|
128
|
-
def eval(self, source: None = ..., **params: Any) -> NoReturn: ...
|
|
129
|
-
|
|
130
120
|
@override
|
|
131
|
-
def
|
|
132
|
-
self.update_params(params)
|
|
133
|
-
self.set_source(source)
|
|
134
|
-
self.source = self.require_source_type(Flight)
|
|
135
|
-
self.downselect_met()
|
|
136
|
-
self.set_source_met()
|
|
137
|
-
|
|
138
|
-
# Calculate true airspeed if not included on source
|
|
139
|
-
self.ensure_true_airspeed_on_source()
|
|
140
|
-
|
|
141
|
-
if isinstance(self.source, Fleet):
|
|
142
|
-
fls = [self._eval_flight(fl) for fl in self.source.to_flight_list()]
|
|
143
|
-
self.source = Fleet.from_seq(fls, attrs=self.source.attrs, broadcast_numeric=False)
|
|
144
|
-
return self.source
|
|
145
|
-
|
|
146
|
-
self.source = self._eval_flight(self.source)
|
|
147
|
-
return self.source
|
|
148
|
-
|
|
149
|
-
def _eval_flight(self, fl: Flight) -> Flight:
|
|
121
|
+
def eval_flight(self, fl: Flight) -> Flight:
|
|
150
122
|
# Ensure aircraft type is available
|
|
151
123
|
try:
|
|
152
124
|
aircraft_type = fl.attrs["aircraft_type"]
|
|
@@ -390,7 +390,7 @@ def minimum_mach_num(
|
|
|
390
390
|
)
|
|
391
391
|
return amass_max - aircraft_mass
|
|
392
392
|
|
|
393
|
-
|
|
393
|
+
return scipy.optimize.newton(
|
|
394
394
|
excess_mass,
|
|
395
395
|
args=(
|
|
396
396
|
air_pressure,
|
|
@@ -404,8 +404,6 @@ def minimum_mach_num(
|
|
|
404
404
|
tol=1e-4,
|
|
405
405
|
)
|
|
406
406
|
|
|
407
|
-
return m
|
|
408
|
-
|
|
409
407
|
|
|
410
408
|
def maximum_mach_num(
|
|
411
409
|
altitude_ft: ArrayOrFloat,
|
|
@@ -450,7 +448,7 @@ def maximum_mach_num(
|
|
|
450
448
|
atyp_param.p_inf_co,
|
|
451
449
|
)
|
|
452
450
|
|
|
453
|
-
|
|
451
|
+
return scipy.optimize.newton(
|
|
454
452
|
func=get_excess_thrust_available,
|
|
455
453
|
args=(air_temperature, air_pressure, aircraft_mass, theta, atyp_param),
|
|
456
454
|
x0=mach_num_op_lim,
|
|
@@ -458,8 +456,6 @@ def maximum_mach_num(
|
|
|
458
456
|
tol=1e-4,
|
|
459
457
|
).clip(max=mach_num_op_lim)
|
|
460
458
|
|
|
461
|
-
return max_mach
|
|
462
|
-
|
|
463
459
|
|
|
464
460
|
# ----------------
|
|
465
461
|
# Fuel flow limits
|
pycontrails/models/tau_cirrus.py
CHANGED
|
@@ -45,7 +45,8 @@ def tau_cirrus(met: MetDataset) -> xr.DataArray:
|
|
|
45
45
|
met : MetDataset
|
|
46
46
|
A MetDataset with the following variables:
|
|
47
47
|
- "air_temperature"
|
|
48
|
-
- "
|
|
48
|
+
- "mass_fraction_of_cloud_ice_in_air", "specific_cloud_ice_water_content",
|
|
49
|
+
or "ice_water_mixing_ratio"
|
|
49
50
|
|
|
50
51
|
Returns
|
|
51
52
|
-------
|
|
@@ -64,15 +65,21 @@ def tau_cirrus(met: MetDataset) -> xr.DataArray:
|
|
|
64
65
|
geopotential_height = _geopotential_height(met)
|
|
65
66
|
|
|
66
67
|
# TODO: these are not *quite* the same, though we treat them the same for now
|
|
67
|
-
#
|
|
68
|
-
#
|
|
68
|
+
# The generic "mass_fraction_of_cloud_ice_in_air" and ECMWF "specific_cloud_ice_water_content"
|
|
69
|
+
# are mass ice per mass of *moist* air,
|
|
70
|
+
# whereas GFS "ice_water_mixing_ratio" is mass ice per mass of *dry* air
|
|
69
71
|
#
|
|
70
72
|
# The method `cirrus_effective_extinction_coef` uses input of mass ice per mass of *dry* air,
|
|
71
|
-
# so the
|
|
72
|
-
|
|
73
|
+
# so only the GFS data is exactly right.
|
|
74
|
+
if "mass_fraction_of_cloud_ice_in_air" in met.data:
|
|
75
|
+
ciwc = met.data["mass_fraction_of_cloud_ice_in_air"]
|
|
76
|
+
elif "specific_cloud_ice_water_content" in met.data:
|
|
73
77
|
ciwc = met.data["specific_cloud_ice_water_content"]
|
|
74
|
-
|
|
78
|
+
elif "ice_water_mixing_ratio" in met.data:
|
|
75
79
|
ciwc = met.data["ice_water_mixing_ratio"]
|
|
80
|
+
else:
|
|
81
|
+
msg = "Could not find cloud ice variable"
|
|
82
|
+
raise KeyError(msg)
|
|
76
83
|
|
|
77
84
|
beta_e = cirrus_effective_extinction_coef(
|
|
78
85
|
ciwc,
|
pycontrails/physics/geo.py
CHANGED
|
@@ -516,7 +516,7 @@ def solar_constant(theta_rad: ArrayLike) -> ArrayLike:
|
|
|
516
516
|
+ (0.000077 * np.sin(theta_rad * 2))
|
|
517
517
|
)
|
|
518
518
|
|
|
519
|
-
return constants.solar_constant * orbital_effect
|
|
519
|
+
return constants.solar_constant * orbital_effect # type: ignore[return-value]
|
|
520
520
|
|
|
521
521
|
|
|
522
522
|
def cosine_solar_zenith_angle(
|
|
@@ -662,7 +662,7 @@ def solar_declination_angle(theta_rad: ArrayLike) -> ArrayLike:
|
|
|
662
662
|
:func:`cosine_solar_zenith_angle`
|
|
663
663
|
"""
|
|
664
664
|
return (
|
|
665
|
-
0.396372
|
|
665
|
+
0.396372 # type: ignore[return-value]
|
|
666
666
|
- (22.91327 * np.cos(theta_rad))
|
|
667
667
|
+ (4.02543 * np.sin(theta_rad))
|
|
668
668
|
- (0.387205 * np.cos(2 * theta_rad))
|
|
@@ -729,7 +729,7 @@ def orbital_correction_for_solar_hour_angle(theta_rad: ArrayLike) -> ArrayLike:
|
|
|
729
729
|
Tested against :cite:`noaaSolarCalculationDetails`
|
|
730
730
|
"""
|
|
731
731
|
return (
|
|
732
|
-
0.004297
|
|
732
|
+
0.004297 # type: ignore[return-value]
|
|
733
733
|
+ (0.107029 * np.cos(theta_rad))
|
|
734
734
|
- (1.837877 * np.sin(theta_rad))
|
|
735
735
|
- (0.837378 * np.cos(2 * theta_rad))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: pycontrails
|
|
3
|
-
Version: 0.54.
|
|
3
|
+
Version: 0.54.6
|
|
4
4
|
Summary: Python library for modeling aviation climate impacts
|
|
5
5
|
Author-email: Breakthrough Energy <py@contrails.org>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -49,8 +49,7 @@ Requires-Dist: pyarrow>=5.0; extra == "dev"
|
|
|
49
49
|
Requires-Dist: pytest>=8.2; extra == "dev"
|
|
50
50
|
Requires-Dist: pytest-cov>=2.11; extra == "dev"
|
|
51
51
|
Requires-Dist: requests>=2.25; extra == "dev"
|
|
52
|
-
Requires-Dist: ruff
|
|
53
|
-
Requires-Dist: setuptools; extra == "dev"
|
|
52
|
+
Requires-Dist: ruff>=0.9.0; extra == "dev"
|
|
54
53
|
Provides-Extra: docs
|
|
55
54
|
Requires-Dist: doc8>=1.1; extra == "docs"
|
|
56
55
|
Requires-Dist: furo>=2023.3; extra == "docs"
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
pycontrails-0.54.
|
|
2
|
-
pycontrails-0.54.
|
|
3
|
-
pycontrails-0.54.
|
|
4
|
-
pycontrails-0.54.
|
|
5
|
-
pycontrails-0.54.
|
|
6
|
-
pycontrails-0.54.
|
|
7
|
-
pycontrails/_version.py,sha256=
|
|
1
|
+
pycontrails-0.54.6.dist-info/RECORD,,
|
|
2
|
+
pycontrails-0.54.6.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
|
|
3
|
+
pycontrails-0.54.6.dist-info/WHEEL,sha256=VujM3ypTCyUW6hcTDdK2ej0ARVMxlU1Djlh_zWnDgqk,109
|
|
4
|
+
pycontrails-0.54.6.dist-info/NOTICE,sha256=TeY5lUhEbf5ouzABkrRUsUDJzS5v9jDEYADpwku4mgA,1929
|
|
5
|
+
pycontrails-0.54.6.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
|
|
6
|
+
pycontrails-0.54.6.dist-info/METADATA,sha256=M7bIMAsry7V5uDqs2GHOi2g-gN3Q0L-24BywhVyTAeM,9113
|
|
7
|
+
pycontrails/_version.py,sha256=3tdcrURWC_cFIzJJ0AUbLpq4j7N9WBPvv2IFchdGJSw,413
|
|
8
8
|
pycontrails/__init__.py,sha256=vomSUHQ1d7ru2-3LgnmczzuPJw0kHmBSaCSVOpqvEtQ,2004
|
|
9
9
|
pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
pycontrails/core/vector.py,sha256=N-B-VSNX5bz-FwzSRBdbO3fZ1A8Xh3odbTs_6rnZty4,71263
|
|
11
|
-
pycontrails/core/models.py,sha256=
|
|
11
|
+
pycontrails/core/models.py,sha256=5ppjLNbxWvS3OfiWfhfVDWjbjw5YhOPqFMedOpV4tBk,39808
|
|
12
12
|
pycontrails/core/interpolation.py,sha256=wovjj3TAf3xonVxjarclpvZLyLq6N7wZQQXsI9hT3YA,25713
|
|
13
13
|
pycontrails/core/fleet.py,sha256=0hi_N4R93St-7iD29SE0EnadpBEl_p9lSGtDwpWvGkk,16704
|
|
14
|
-
pycontrails/core/flight.py,sha256=
|
|
14
|
+
pycontrails/core/flight.py,sha256=Eba7XmfjGEoFSdWJfSnOmWknAU0c0PPNZtTYrb3u_m4,80659
|
|
15
15
|
pycontrails/core/fuel.py,sha256=kJZ3P1lPm1L6rdPREM55XQ-VfJ_pt35cP4sO2Nnvmjs,4332
|
|
16
16
|
pycontrails/core/polygon.py,sha256=EmfHPj0e58whsHvR-3YvDgMWkvMFgp_BgwaoG8IZ4n0,18044
|
|
17
17
|
pycontrails/core/cache.py,sha256=XG_RCIemv1xatDBmaVyxnoYdi8gD2gkUvjfvRi9RsJA,28068
|
|
18
18
|
pycontrails/core/__init__.py,sha256=p0O09HxdeXU0X5Z3zrHMlTfXa92YumT3fJ8wJBI5ido,856
|
|
19
|
-
pycontrails/core/flightplan.py,sha256=
|
|
19
|
+
pycontrails/core/flightplan.py,sha256=_7j4puAMiSe2aqHXcENR58c-N8crjUp4nbi3O2u7Adg,7335
|
|
20
20
|
pycontrails/core/met.py,sha256=5b2K4PQWcAO5X8Mb3LLJ224_06WDyDEGUIeE3_EB3zA,103599
|
|
21
|
-
pycontrails/core/aircraft_performance.py,sha256=
|
|
22
|
-
pycontrails/core/airports.py,sha256=
|
|
23
|
-
pycontrails/core/met_var.py,sha256=
|
|
24
|
-
pycontrails/core/rgi_cython.cpython-312-darwin.so,sha256=
|
|
21
|
+
pycontrails/core/aircraft_performance.py,sha256=quODa49uv0DVwl-qEYQ2ePLeapOD0GjyNBO3HoSD3M0,28004
|
|
22
|
+
pycontrails/core/airports.py,sha256=3IGP337nqzXAXktT7Ju95i0I8GDVwHMlCaCv2bs4Kkk,6738
|
|
23
|
+
pycontrails/core/met_var.py,sha256=U3q5Ddc1wN5EtT7a5Iy5lixCT3Mmv75C9wScY9MLnc8,11544
|
|
24
|
+
pycontrails/core/rgi_cython.cpython-312-darwin.so,sha256=7TC07GN_-IH_1EKMwnePJocTxjD1MqhDCwXvfjLh8Gs,312000
|
|
25
25
|
pycontrails/core/coordinates.py,sha256=0ySsHtqTon7GMbuwmmxMbI92j3ueMteJZh4xxNm5zto,5391
|
|
26
26
|
pycontrails/datalib/goes.py,sha256=mCEuDYdt1GIBA-sbDq5LdC6ZRvWJ28uaaBTnsXE4syc,26555
|
|
27
27
|
pycontrails/datalib/landsat.py,sha256=r6366rEF7fOA7mT5KySCPGJplgGE5LvBw5fMqk-U1oM,19697
|
|
28
28
|
pycontrails/datalib/spire.py,sha256=66SnMdA8KOS69USjKmqrJmTKPK08Ehih9tnlsCt-AJw,25331
|
|
29
29
|
pycontrails/datalib/__init__.py,sha256=hW9NWdFPC3y_2vHMteQ7GgQdop3917MkDaf5ZhU2RBY,369
|
|
30
30
|
pycontrails/datalib/sentinel.py,sha256=hYSxIlQnyJHqtHWlKn73HOK_1pm-_IbGebmkHnh4UcA,17172
|
|
31
|
-
pycontrails/datalib/_met_utils/metsource.py,sha256=
|
|
31
|
+
pycontrails/datalib/_met_utils/metsource.py,sha256=omgrBrAap11G5hV8a9qS3umJVuwoX_Mca6QctRa6xn8,24116
|
|
32
32
|
pycontrails/datalib/ecmwf/arco_era5.py,sha256=7HXQU5S02PzX9Ew2ZrDKSp0tDEG1eeVAvbP3decmm20,12437
|
|
33
|
-
pycontrails/datalib/ecmwf/era5.py,sha256=
|
|
34
|
-
pycontrails/datalib/ecmwf/era5_model_level.py,sha256=
|
|
33
|
+
pycontrails/datalib/ecmwf/era5.py,sha256=4ULNdDlUN0kP6Tbp8D_-Bc12nAsLf0iNfZaDoj_AoZU,18952
|
|
34
|
+
pycontrails/datalib/ecmwf/era5_model_level.py,sha256=AO7ePIGZtavx5nQSPYP4p07RNZeg3bbzmoZC7RUC4Gg,19354
|
|
35
35
|
pycontrails/datalib/ecmwf/hres.py,sha256=9QHYxMLK7zyQEOFpbVrZfIht9WqVXnhhyOd7YKEgAe0,28381
|
|
36
36
|
pycontrails/datalib/ecmwf/variables.py,sha256=lU3BNe265XVhCXvdMwZqfkWQwtsetZxVRLSfPqHFKAE,9913
|
|
37
37
|
pycontrails/datalib/ecmwf/hres_model_level.py,sha256=EjBDYbbPZotTsveFlEiAAWJhhPYiao1DQrLyS4kVCrA,17657
|
|
38
38
|
pycontrails/datalib/ecmwf/__init__.py,sha256=wdfhplEaW2UKTItIoshTtVEjbPyfDYoprTJNxbKZuvA,2021
|
|
39
39
|
pycontrails/datalib/ecmwf/common.py,sha256=qRMSzDQikGMi3uqvz-Y57e3biHPzSoVMfUwOu9iTxHc,4024
|
|
40
40
|
pycontrails/datalib/ecmwf/model_levels.py,sha256=_kgpnogaS6MlfvTX9dB5ASTHFUlZuQ_DRb-VADwEa0k,16996
|
|
41
|
-
pycontrails/datalib/ecmwf/ifs.py,sha256=
|
|
41
|
+
pycontrails/datalib/ecmwf/ifs.py,sha256=0swHe6tFc5Fbu9e4_jREW0H-xYHYLtxjNoE3aUUlgvc,10761
|
|
42
42
|
pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv,sha256=PmvGLRzn6uuCKSwiasSuVcehvvmSaqP7cnLuN6hhCQQ,9788
|
|
43
43
|
pycontrails/datalib/_leo_utils/vis.py,sha256=-fLcm1D5cP6lThVHovV3MJSiadWyTUAvYDMvr4drMU4,1802
|
|
44
44
|
pycontrails/datalib/_leo_utils/search.py,sha256=r87T2OV4qH1pYI2YznvsBL042f4RKxD3OA2snd3-kDI,8687
|
|
45
45
|
pycontrails/datalib/_leo_utils/static/bq_roi_query.sql,sha256=xq6-tJyz0-bUwW0KjQymqygjH3WlQBmyBtP7Ci7SBe8,260
|
|
46
|
-
pycontrails/datalib/gfs/gfs.py,sha256=
|
|
46
|
+
pycontrails/datalib/gfs/gfs.py,sha256=3tFiR7IObHcFmhGOdb-SJ7QQJSk6tF_6qkyi-pLrIdE,22393
|
|
47
47
|
pycontrails/datalib/gfs/variables.py,sha256=4ALR4zhYW8tQVlNVHrd0CK8oRNSe_2OkW3ELeaImtAI,3135
|
|
48
48
|
pycontrails/datalib/gfs/__init__.py,sha256=pXNjb9cJC6ngpuCnoHnmVZ2RHzbHZ0AlsyGvgcdcl2E,684
|
|
49
49
|
pycontrails/ext/synthetic_flight.py,sha256=ByuJDfpuK5WaGMj41wflfzH6zwI1nejVcQXC4JoMvSI,16795
|
|
@@ -56,13 +56,13 @@ pycontrails/utils/types.py,sha256=dN2oYVNNbekqvM89Lfs0FmmhavRQGC7NgGhi_7m6UBU,49
|
|
|
56
56
|
pycontrails/utils/temp.py,sha256=lGU0b_R8ze4yKlsOusHIIBaoNFBrmrB3vBjgHRlfcXk,1109
|
|
57
57
|
pycontrails/utils/json.py,sha256=oTiO8xh603esfBGaGVmA5eUzR0NhAqNpQCegMMgnSbg,5896
|
|
58
58
|
pycontrails/utils/dependencies.py,sha256=ATP45xYdUbIyGFzgbOe5SbokMytvB84TcexUEFnEUZE,2559
|
|
59
|
-
pycontrails/models/pcc.py,sha256=
|
|
60
|
-
pycontrails/models/tau_cirrus.py,sha256=
|
|
59
|
+
pycontrails/models/pcc.py,sha256=0Qdl4u8PmUEpNYd398glTChkbTwsh83wYPt0Bmi8qd8,11068
|
|
60
|
+
pycontrails/models/tau_cirrus.py,sha256=0K7cdHBGaahuWDM0FG1HGqbvhkwyWKHMInwctkBWYbo,5777
|
|
61
61
|
pycontrails/models/__init__.py,sha256=dQTOLQb7RdUdUwslt5se__5y_ymbInBexQmNrmAeOdE,33
|
|
62
62
|
pycontrails/models/issr.py,sha256=AYLYLHxtG8je5UG6x1zLV0ul89MJPqe5Xk0oWIyZ7b0,7378
|
|
63
63
|
pycontrails/models/sac.py,sha256=lV1Or0AaLxuS1Zo5V8h5c1fkSKC-hKEgiFm7bmmusWw,15946
|
|
64
64
|
pycontrails/models/accf.py,sha256=meIcgojYvHgm3de9iro2Bv0M4y9ta5VPwcqGAMEbBp8,13663
|
|
65
|
-
pycontrails/models/dry_advection.py,sha256=
|
|
65
|
+
pycontrails/models/dry_advection.py,sha256=FqUvRFbnwe4esHBYDayn3iu7R2UUuaQwY8x2oToxNI0,19164
|
|
66
66
|
pycontrails/models/pcr.py,sha256=ZzbEuTOuDdUmmL5T3Wk3HL-O8XzX3HMnn98WcPbASaU,5348
|
|
67
67
|
pycontrails/models/emissions/__init__.py,sha256=CZB2zIkLUI3NGNmq2ddvRYjEtiboY6PWJjiEiXj_zII,478
|
|
68
68
|
pycontrails/models/emissions/ffm2.py,sha256=mAvBHnp-p3hIn2fjKGq50eaMHi0jcb5hA5uXbJGeE9I,12068
|
|
@@ -76,14 +76,14 @@ pycontrails/models/apcemm/inputs.py,sha256=88GylkiaymEW_XZeFxLsICI9wV6kl8wVYsuyT
|
|
|
76
76
|
pycontrails/models/apcemm/utils.py,sha256=Ex6EqXin6yoJv2WWhBotSzhjzUlFNZm2MDgL4CvvX6E,17082
|
|
77
77
|
pycontrails/models/apcemm/apcemm.py,sha256=rKvIaEsqtLbZ5h4o4EOY4Ge4-HdPn2X4M1lEUFDvr68,39975
|
|
78
78
|
pycontrails/models/apcemm/static/apcemm_yaml_template.yaml,sha256=uAZkc57OUvDMjgX6F5f6hgDh3Hgg1NbHWRUFSiv0DEI,6745
|
|
79
|
-
pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=
|
|
79
|
+
pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=pYpJHi9dcuGjJASdZkbc49pIl2cQkVlRu80Gy3ZN5bA,36767
|
|
80
80
|
pycontrails/models/humidity_scaling/__init__.py,sha256=nqsab_j9BCwMbTfCn4BjXMdhItlvNKkgUJ9-lb8RyIo,1119
|
|
81
81
|
pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq,sha256=tfYhbafF9Z-gGCg6VQ1YBlOaK_01e65Dc6s9b-hQ6Zo,286375
|
|
82
82
|
pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq,sha256=pShCvNUo0NYtAHhT9IBRuj38X9jejdlKfv-ZoOKmtKI,35943
|
|
83
83
|
pycontrails/models/cocip/radiative_forcing.py,sha256=0zTXQXANYC3oIDyYEUHkaJxuwTe0-GojxBipEutWGxU,45031
|
|
84
84
|
pycontrails/models/cocip/wind_shear.py,sha256=m6ZlWjORfI-lI-D74Z_dIMOHnK4FDYmkb0S6vSpKTO8,3868
|
|
85
|
-
pycontrails/models/cocip/cocip.py,sha256=
|
|
86
|
-
pycontrails/models/cocip/output_formats.py,sha256=
|
|
85
|
+
pycontrails/models/cocip/cocip.py,sha256=QbbgSPEI2Lw8mftaxznpUgdNytnMk7O89VsTKBDvQ2w,102407
|
|
86
|
+
pycontrails/models/cocip/output_formats.py,sha256=YXYfm32NsI3OkZn4htAOceMIDG31ulehUiMUCqu8hEQ,83713
|
|
87
87
|
pycontrails/models/cocip/__init__.py,sha256=CWrkNd6S3ZJq04pjTc2W22sVAJeJD3bJJRy_zLW8Kkc,962
|
|
88
88
|
pycontrails/models/cocip/cocip_params.py,sha256=rwVW1SnjKnzztlxcxxaCztgzRbIPkMJqC2GefUKQZ6w,12341
|
|
89
89
|
pycontrails/models/cocip/wake_vortex.py,sha256=YmOuv_oWJ9-fmTx9PVHr6gsXwex0qzLhvoZIJNB9rsk,14515
|
|
@@ -92,16 +92,16 @@ pycontrails/models/cocip/radiative_heating.py,sha256=1U4SQWwogtyQ2u6J996kAHP0Ofp
|
|
|
92
92
|
pycontrails/models/cocip/contrail_properties.py,sha256=H0D2FQodX6jW3EmAxQNxGS8erOU0EW2MSAAOLB0LyQc,56202
|
|
93
93
|
pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=M6tdl5ZfNDtiLwJOrXg3sBesf6Larg-5JchsVlJNsG4,14675
|
|
94
94
|
pycontrails/models/ps_model/__init__.py,sha256=Fuum5Rq8ya8qkvbeq2wh6NDo-42RCRnK1Y-2syYy0Ck,553
|
|
95
|
-
pycontrails/models/ps_model/ps_model.py,sha256=
|
|
95
|
+
pycontrails/models/ps_model/ps_model.py,sha256=e-rd3b61futqHFQPXbOj3o-7hIuxHZH0fUzmYI4JDow,32255
|
|
96
96
|
pycontrails/models/ps_model/ps_aircraft_params.py,sha256=pD1xpTBX6ML2Pie78kypNibzE5AkvqnAIaTyEMfciuY,13350
|
|
97
|
-
pycontrails/models/ps_model/ps_operational_limits.py,sha256=
|
|
97
|
+
pycontrails/models/ps_model/ps_operational_limits.py,sha256=XwMHO8yu8EZUWtxRgjRKwxmCrmKGoHO7Ob6nlfkrthI,16441
|
|
98
98
|
pycontrails/models/ps_model/ps_grid.py,sha256=DTUXTxYIQ-6iquiCGtScOqkPvakz9F57DxUHQ3JmXIA,26071
|
|
99
99
|
pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv,sha256=ksrpQTHkxSt1Va_R0rHdenEz6DlIs-Sfk1KFBzHKjhk,1038
|
|
100
100
|
pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv,sha256=3eNhSwzut0gon04k2EYKKaXRvQSUlau3yBAbHS0EBao,25784
|
|
101
101
|
pycontrails/models/cocipgrid/cocip_grid_params.py,sha256=l4vBPrOKCJDz5Y1uMjmOGVyUcSWgfZtFWbjW968OPz8,5875
|
|
102
102
|
pycontrails/models/cocipgrid/__init__.py,sha256=ar6bF_8Pusbb-myujz_q5ntFylQTNH8yiM8fxP7Zk30,262
|
|
103
103
|
pycontrails/models/cocipgrid/cocip_grid.py,sha256=uLQ86xK12LJ43KKb-CdSaDDamj4GyfDbiHp_NefotZU,91151
|
|
104
|
-
pycontrails/physics/geo.py,sha256=
|
|
104
|
+
pycontrails/physics/geo.py,sha256=5THIXgpaHBQdSYWLgtK4mV_8e1hWW9XeTsSHOShFMeA,36323
|
|
105
105
|
pycontrails/physics/units.py,sha256=BC0e0l_pDeijqN179tXl8eX_Qpw8d17MVujBu1SV3IE,12293
|
|
106
106
|
pycontrails/physics/constants.py,sha256=pHQQmccMUwuNnY4hFtm3L8G2rnUQcfJnroyQr8HAVeM,3146
|
|
107
107
|
pycontrails/physics/__init__.py,sha256=_1eWbEy6evEWdfJCEkwDiSdpiDNzNWEPVqaPekHyhwU,44
|
|
File without changes
|
|
File without changes
|
|
File without changes
|