pycontrails 0.59.0__cp314-cp314-macosx_10_15_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/__init__.py +70 -0
- pycontrails/_version.py +34 -0
- pycontrails/core/__init__.py +30 -0
- pycontrails/core/aircraft_performance.py +679 -0
- pycontrails/core/airports.py +228 -0
- pycontrails/core/cache.py +889 -0
- pycontrails/core/coordinates.py +174 -0
- pycontrails/core/fleet.py +483 -0
- pycontrails/core/flight.py +2185 -0
- pycontrails/core/flightplan.py +228 -0
- pycontrails/core/fuel.py +140 -0
- pycontrails/core/interpolation.py +702 -0
- pycontrails/core/met.py +2936 -0
- pycontrails/core/met_var.py +387 -0
- pycontrails/core/models.py +1321 -0
- pycontrails/core/polygon.py +549 -0
- pycontrails/core/rgi_cython.cpython-314-darwin.so +0 -0
- pycontrails/core/vector.py +2249 -0
- pycontrails/datalib/__init__.py +12 -0
- pycontrails/datalib/_met_utils/metsource.py +746 -0
- pycontrails/datalib/ecmwf/__init__.py +73 -0
- pycontrails/datalib/ecmwf/arco_era5.py +345 -0
- pycontrails/datalib/ecmwf/common.py +114 -0
- pycontrails/datalib/ecmwf/era5.py +554 -0
- pycontrails/datalib/ecmwf/era5_model_level.py +490 -0
- pycontrails/datalib/ecmwf/hres.py +804 -0
- pycontrails/datalib/ecmwf/hres_model_level.py +466 -0
- pycontrails/datalib/ecmwf/ifs.py +287 -0
- pycontrails/datalib/ecmwf/model_levels.py +435 -0
- pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
- pycontrails/datalib/ecmwf/variables.py +268 -0
- pycontrails/datalib/geo_utils.py +261 -0
- pycontrails/datalib/gfs/__init__.py +28 -0
- pycontrails/datalib/gfs/gfs.py +656 -0
- pycontrails/datalib/gfs/variables.py +104 -0
- pycontrails/datalib/goes.py +764 -0
- pycontrails/datalib/gruan.py +343 -0
- pycontrails/datalib/himawari/__init__.py +27 -0
- pycontrails/datalib/himawari/header_struct.py +266 -0
- pycontrails/datalib/himawari/himawari.py +671 -0
- pycontrails/datalib/landsat.py +589 -0
- pycontrails/datalib/leo_utils/__init__.py +5 -0
- pycontrails/datalib/leo_utils/correction.py +266 -0
- pycontrails/datalib/leo_utils/landsat_metadata.py +300 -0
- pycontrails/datalib/leo_utils/search.py +250 -0
- pycontrails/datalib/leo_utils/sentinel_metadata.py +748 -0
- pycontrails/datalib/leo_utils/static/bq_roi_query.sql +6 -0
- pycontrails/datalib/leo_utils/vis.py +59 -0
- pycontrails/datalib/sentinel.py +650 -0
- pycontrails/datalib/spire/__init__.py +5 -0
- pycontrails/datalib/spire/exceptions.py +62 -0
- pycontrails/datalib/spire/spire.py +604 -0
- pycontrails/ext/bada.py +42 -0
- pycontrails/ext/cirium.py +14 -0
- pycontrails/ext/empirical_grid.py +140 -0
- pycontrails/ext/synthetic_flight.py +431 -0
- pycontrails/models/__init__.py +1 -0
- pycontrails/models/accf.py +425 -0
- pycontrails/models/apcemm/__init__.py +8 -0
- pycontrails/models/apcemm/apcemm.py +983 -0
- pycontrails/models/apcemm/inputs.py +226 -0
- pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
- pycontrails/models/apcemm/utils.py +437 -0
- pycontrails/models/cocip/__init__.py +29 -0
- pycontrails/models/cocip/cocip.py +2742 -0
- pycontrails/models/cocip/cocip_params.py +305 -0
- pycontrails/models/cocip/cocip_uncertainty.py +291 -0
- pycontrails/models/cocip/contrail_properties.py +1530 -0
- pycontrails/models/cocip/output_formats.py +2270 -0
- pycontrails/models/cocip/radiative_forcing.py +1260 -0
- pycontrails/models/cocip/radiative_heating.py +520 -0
- pycontrails/models/cocip/unterstrasser_wake_vortex.py +508 -0
- pycontrails/models/cocip/wake_vortex.py +396 -0
- pycontrails/models/cocip/wind_shear.py +120 -0
- pycontrails/models/cocipgrid/__init__.py +9 -0
- pycontrails/models/cocipgrid/cocip_grid.py +2552 -0
- pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
- pycontrails/models/dry_advection.py +602 -0
- pycontrails/models/emissions/__init__.py +21 -0
- pycontrails/models/emissions/black_carbon.py +599 -0
- pycontrails/models/emissions/emissions.py +1353 -0
- pycontrails/models/emissions/ffm2.py +336 -0
- pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
- pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
- pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
- pycontrails/models/extended_k15.py +1327 -0
- pycontrails/models/humidity_scaling/__init__.py +37 -0
- pycontrails/models/humidity_scaling/humidity_scaling.py +1075 -0
- pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
- pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
- pycontrails/models/issr.py +210 -0
- pycontrails/models/pcc.py +326 -0
- pycontrails/models/pcr.py +154 -0
- pycontrails/models/ps_model/__init__.py +18 -0
- pycontrails/models/ps_model/ps_aircraft_params.py +381 -0
- pycontrails/models/ps_model/ps_grid.py +701 -0
- pycontrails/models/ps_model/ps_model.py +1000 -0
- pycontrails/models/ps_model/ps_operational_limits.py +525 -0
- pycontrails/models/ps_model/static/ps-aircraft-params-20250328.csv +69 -0
- pycontrails/models/ps_model/static/ps-synonym-list-20250328.csv +104 -0
- pycontrails/models/sac.py +442 -0
- pycontrails/models/tau_cirrus.py +183 -0
- pycontrails/physics/__init__.py +1 -0
- pycontrails/physics/constants.py +117 -0
- pycontrails/physics/geo.py +1138 -0
- pycontrails/physics/jet.py +968 -0
- pycontrails/physics/static/iata-cargo-load-factors-20250221.csv +74 -0
- pycontrails/physics/static/iata-passenger-load-factors-20250221.csv +74 -0
- pycontrails/physics/thermo.py +551 -0
- pycontrails/physics/units.py +472 -0
- pycontrails/py.typed +0 -0
- pycontrails/utils/__init__.py +1 -0
- pycontrails/utils/dependencies.py +66 -0
- pycontrails/utils/iteration.py +13 -0
- pycontrails/utils/json.py +187 -0
- pycontrails/utils/temp.py +50 -0
- pycontrails/utils/types.py +163 -0
- pycontrails-0.59.0.dist-info/METADATA +179 -0
- pycontrails-0.59.0.dist-info/RECORD +123 -0
- pycontrails-0.59.0.dist-info/WHEEL +6 -0
- pycontrails-0.59.0.dist-info/licenses/LICENSE +178 -0
- pycontrails-0.59.0.dist-info/licenses/NOTICE +43 -0
- pycontrails-0.59.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Ice super-saturated regions (ISSR)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, overload
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
import pycontrails
|
|
11
|
+
from pycontrails.core.flight import Flight
|
|
12
|
+
from pycontrails.core.met import MetDataset
|
|
13
|
+
from pycontrails.core.met_var import AirTemperature, MetVariable, SpecificHumidity
|
|
14
|
+
from pycontrails.core.models import Model, ModelParams
|
|
15
|
+
from pycontrails.core.vector import GeoVectorDataset
|
|
16
|
+
from pycontrails.models.humidity_scaling import HumidityScaling
|
|
17
|
+
from pycontrails.physics import constants, thermo
|
|
18
|
+
from pycontrails.utils.types import ArrayLike, apply_nan_mask_to_arraylike
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ISSRParams(ModelParams):
|
|
23
|
+
"""Default ISSR model parameters."""
|
|
24
|
+
|
|
25
|
+
#: RHI Threshold
|
|
26
|
+
rhi_threshold: float = 1.0
|
|
27
|
+
|
|
28
|
+
#: Humidity scaling
|
|
29
|
+
humidity_scaling: HumidityScaling | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ISSR(Model):
|
|
33
|
+
"""Ice super-saturated regions over a :class:`Flight` trajectory or :class:`MetDataset` grid.
|
|
34
|
+
|
|
35
|
+
This model calculates points where the relative humidity over ice is greater than 1.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
met : MetDataset
|
|
40
|
+
Dataset containing "air_temperature" and "specific_humidity" variables
|
|
41
|
+
|
|
42
|
+
Examples
|
|
43
|
+
--------
|
|
44
|
+
>>> from datetime import datetime
|
|
45
|
+
>>> from pycontrails.datalib.ecmwf import ERA5
|
|
46
|
+
>>> from pycontrails.models.issr import ISSR
|
|
47
|
+
>>> from pycontrails.models.humidity_scaling import ConstantHumidityScaling
|
|
48
|
+
|
|
49
|
+
>>> # Get met data
|
|
50
|
+
>>> time = datetime(2022, 3, 1, 0), datetime(2022, 3, 1, 2)
|
|
51
|
+
>>> variables = ["air_temperature", "specific_humidity"]
|
|
52
|
+
>>> pressure_levels = [200, 250, 300]
|
|
53
|
+
>>> era5 = ERA5(time, variables, pressure_levels)
|
|
54
|
+
>>> met = era5.open_metdataset()
|
|
55
|
+
|
|
56
|
+
>>> # Instantiate and run model
|
|
57
|
+
>>> scaling = ConstantHumidityScaling(rhi_adj=0.98)
|
|
58
|
+
>>> model = ISSR(met, humidity_scaling=scaling)
|
|
59
|
+
>>> out1 = model.eval()
|
|
60
|
+
>>> issr1 = out1["issr"]
|
|
61
|
+
>>> issr1.proportion # Get proportion of values with ice supersaturation
|
|
62
|
+
0.114...
|
|
63
|
+
|
|
64
|
+
>>> # Run with a lower threshold
|
|
65
|
+
>>> out2 = model.eval(rhi_threshold=0.95)
|
|
66
|
+
>>> issr2 = out2["issr"]
|
|
67
|
+
>>> issr2.proportion
|
|
68
|
+
0.146...
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
name = "issr"
|
|
72
|
+
long_name = "Ice super-saturated regions"
|
|
73
|
+
met_variables: tuple[MetVariable, ...] = AirTemperature, SpecificHumidity
|
|
74
|
+
default_params = ISSRParams
|
|
75
|
+
|
|
76
|
+
@overload
|
|
77
|
+
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
78
|
+
|
|
79
|
+
@overload
|
|
80
|
+
def eval(self, source: GeoVectorDataset, **params: Any) -> GeoVectorDataset: ...
|
|
81
|
+
|
|
82
|
+
@overload
|
|
83
|
+
def eval(self, source: MetDataset | None = ..., **params: Any) -> MetDataset: ...
|
|
84
|
+
|
|
85
|
+
def eval(
|
|
86
|
+
self, source: GeoVectorDataset | Flight | MetDataset | None = None, **params: Any
|
|
87
|
+
) -> GeoVectorDataset | Flight | MetDataset:
|
|
88
|
+
"""Evaluate ice super-saturated regions along flight trajectory or on meteorology grid.
|
|
89
|
+
|
|
90
|
+
.. versionchanged:: 0.27.0
|
|
91
|
+
|
|
92
|
+
Humidity scaling now handled automatically. This is controlled by
|
|
93
|
+
model parameter ``humidity_scaling``.
|
|
94
|
+
|
|
95
|
+
.. versionchanged:: 0.48.0
|
|
96
|
+
|
|
97
|
+
If the ``source`` is a :class:`MetDataset`, the returned object will
|
|
98
|
+
also be a :class:`MetDataset`. Previous the "issr" :class:`MetDataArray`
|
|
99
|
+
was returned.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
source : GeoVectorDataset | Flight | MetDataset | None, optional
|
|
104
|
+
Input GeoVectorDataset or Flight.
|
|
105
|
+
If None, evaluates at the :attr:`met` grid points.
|
|
106
|
+
**params : Any
|
|
107
|
+
Overwrite model parameters before eval
|
|
108
|
+
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
GeoVectorDataset | Flight | MetDataset
|
|
112
|
+
Returns 1 in ISSR, 0 everywhere else.
|
|
113
|
+
Returns `np.nan` if interpolating outside meteorology grid.
|
|
114
|
+
|
|
115
|
+
Raises
|
|
116
|
+
------
|
|
117
|
+
NotImplementedError
|
|
118
|
+
Raises if input ``source`` is not supported.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
self.update_params(params)
|
|
122
|
+
self.set_source(source)
|
|
123
|
+
|
|
124
|
+
if isinstance(self.source, GeoVectorDataset):
|
|
125
|
+
self.downselect_met()
|
|
126
|
+
self.source.setdefault("air_pressure", self.source.air_pressure)
|
|
127
|
+
|
|
128
|
+
humidity_scaling = self.params["humidity_scaling"]
|
|
129
|
+
scale_humidity = humidity_scaling is not None and "specific_humidity" not in self.source
|
|
130
|
+
|
|
131
|
+
self.set_source_met()
|
|
132
|
+
|
|
133
|
+
# apply humidity scaling, warn if no scaling is provided for ECMWF data
|
|
134
|
+
if scale_humidity:
|
|
135
|
+
humidity_scaling.eval(self.source, copy_source=False)
|
|
136
|
+
|
|
137
|
+
self.source["issr"] = issr( # type: ignore[type-var]
|
|
138
|
+
air_temperature=self.source.data["air_temperature"],
|
|
139
|
+
specific_humidity=self.source.data["specific_humidity"],
|
|
140
|
+
air_pressure=self.source.data["air_pressure"],
|
|
141
|
+
rhi=self.source.data.get("rhi", None), # if rhi already known, pass it in
|
|
142
|
+
rhi_threshold=self.params["rhi_threshold"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Tag output with additional metadata attrs
|
|
146
|
+
self.transfer_met_source_attrs()
|
|
147
|
+
self.source.attrs["pycontrails_version"] = pycontrails.__version__
|
|
148
|
+
if scale_humidity:
|
|
149
|
+
for k, v in humidity_scaling.description.items():
|
|
150
|
+
self.source.attrs[f"humidity_scaling_{k}"] = v
|
|
151
|
+
|
|
152
|
+
return self.source
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def issr(
|
|
156
|
+
air_temperature: ArrayLike,
|
|
157
|
+
specific_humidity: ArrayLike | None = None,
|
|
158
|
+
air_pressure: ArrayLike | None = None,
|
|
159
|
+
rhi: ArrayLike | None = None,
|
|
160
|
+
rhi_threshold: float = 1.0,
|
|
161
|
+
) -> ArrayLike:
|
|
162
|
+
r"""Calculate ice super-saturated regions.
|
|
163
|
+
|
|
164
|
+
Regions where the atmospheric relative humidity over ice is greater than 1.
|
|
165
|
+
|
|
166
|
+
Parameters ``air_temperature``, ``specific_humidity``, ``air_pressure``,
|
|
167
|
+
and ``rhi`` must have compatible shapes when defined.
|
|
168
|
+
|
|
169
|
+
Either ``specific_humidity`` and ``air_pressure`` must both be provided, or
|
|
170
|
+
``rhi`` must be provided.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
air_temperature : ArrayLike
|
|
175
|
+
A sequence or array of temperature values, :math:`[K]`.
|
|
176
|
+
specific_humidity : ArrayLike | None
|
|
177
|
+
A sequence or array of specific humidity values, [:math:`kg_{H_{2}O} \ kg_{moist air}`]
|
|
178
|
+
None by default.
|
|
179
|
+
air_pressure : ArrayLike | None
|
|
180
|
+
A sequence or array of atmospheric pressure values, [:math:`Pa`]. None by default.
|
|
181
|
+
rhi : ArrayLike | None, optional
|
|
182
|
+
A sequence of array of RHi values, if already known. If not provided, this function
|
|
183
|
+
will compute RHi from `air_temperature`, `specific_humidity`, and `air_pressure`.
|
|
184
|
+
None by default.
|
|
185
|
+
rhi_threshold : float, optional
|
|
186
|
+
Relative humidity over ice threshold for determining ISSR state
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
ArrayLike
|
|
191
|
+
ISSR state of each point indexed by the parameters.
|
|
192
|
+
"""
|
|
193
|
+
if rhi is None:
|
|
194
|
+
if specific_humidity is None or air_pressure is None:
|
|
195
|
+
raise TypeError(
|
|
196
|
+
"If 'rhi' is not specified, both 'specific_humidity' "
|
|
197
|
+
"and 'air_pressure' must be provided."
|
|
198
|
+
)
|
|
199
|
+
rhi = thermo.rhi(specific_humidity, air_temperature, air_pressure)
|
|
200
|
+
|
|
201
|
+
# store nan values to refill after casting
|
|
202
|
+
nan_mask = np.isnan(rhi)
|
|
203
|
+
|
|
204
|
+
# compute issr as int
|
|
205
|
+
sufficiently_cold = air_temperature < -constants.absolute_zero
|
|
206
|
+
sufficiently_humid = rhi > rhi_threshold
|
|
207
|
+
|
|
208
|
+
issr_ = (sufficiently_cold & sufficiently_humid).astype(rhi.dtype)
|
|
209
|
+
|
|
210
|
+
return apply_nan_mask_to_arraylike(issr_, nan_mask)
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"""Probability of persistent contrail coverage (PCC)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import xarray as xr
|
|
10
|
+
|
|
11
|
+
from pycontrails.core.fuel import Fuel, JetA
|
|
12
|
+
from pycontrails.core.met import MetDataArray, MetDataset
|
|
13
|
+
from pycontrails.core.met_var import AirTemperature, SpecificHumidity
|
|
14
|
+
from pycontrails.core.models import Model, ModelParams
|
|
15
|
+
from pycontrails.datalib.ecmwf.variables import SpecificCloudIceWaterContent
|
|
16
|
+
from pycontrails.models import sac
|
|
17
|
+
from pycontrails.models.humidity_scaling import HumidityScaling
|
|
18
|
+
from pycontrails.physics import thermo
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclasses.dataclass
|
|
22
|
+
class PCCParams(ModelParams):
|
|
23
|
+
"""PCC Model Parameters."""
|
|
24
|
+
|
|
25
|
+
#: Cloud model
|
|
26
|
+
#: Options include "Smith1990", "Sundqvist1989", "Slingo1980"
|
|
27
|
+
cloud_model: str = "Smith1990"
|
|
28
|
+
|
|
29
|
+
#: Critical RH Factor for the model to cirrus clouds
|
|
30
|
+
rh_crit_factor: float = 0.7
|
|
31
|
+
|
|
32
|
+
#: Fuel type
|
|
33
|
+
fuel: Fuel = dataclasses.field(default_factory=JetA)
|
|
34
|
+
|
|
35
|
+
#: Engine efficiency
|
|
36
|
+
engine_efficiency: float = 0.35
|
|
37
|
+
|
|
38
|
+
#: Humidity scaling
|
|
39
|
+
humidity_scaling: HumidityScaling | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PCC(Model):
|
|
43
|
+
r"""Potential Contrail Coverage Algorithm.
|
|
44
|
+
|
|
45
|
+
Determines the potential of ambient atmosphere to allow contrail formation at grid points.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
met : MetDataset
|
|
50
|
+
Dataset containing :attr:`met_variables` variables.
|
|
51
|
+
surface : MetDataset
|
|
52
|
+
Surface level dataset containing "air_pressure".
|
|
53
|
+
params : dict[str, Any], optional
|
|
54
|
+
Override PCC model parameters with dictionary.
|
|
55
|
+
See :class:`PCCParams` for model parameters.
|
|
56
|
+
**params_kwargs
|
|
57
|
+
Override PCC model parameters with keyword arguments.
|
|
58
|
+
See :class:`PCCParams` for model parameters.
|
|
59
|
+
|
|
60
|
+
Notes
|
|
61
|
+
-----
|
|
62
|
+
Based on Ponater et al. (2002)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
__slots__ = ("_cloud_model", "surface")
|
|
66
|
+
name = "pcc"
|
|
67
|
+
long_name = "Potential contrail coverage"
|
|
68
|
+
met_variables = (AirTemperature, SpecificHumidity, SpecificCloudIceWaterContent)
|
|
69
|
+
_cloud_model: Any
|
|
70
|
+
source: MetDataset
|
|
71
|
+
surface: MetDataset
|
|
72
|
+
default_params = PCCParams
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
met: MetDataset,
|
|
77
|
+
surface: MetDataset,
|
|
78
|
+
params: dict[str, Any] | None = None,
|
|
79
|
+
**params_kwargs: Any,
|
|
80
|
+
) -> None:
|
|
81
|
+
super().__init__(met, params=params, **params_kwargs)
|
|
82
|
+
|
|
83
|
+
# set cloud model by method
|
|
84
|
+
self._cloud_model = getattr(self, self.params["cloud_model"], None)
|
|
85
|
+
if self._cloud_model is None:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Cloud model must be one of 'Smith1990', 'Sundqvist1989', 'Slingo1980'"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# make sure surface level dataset is valid
|
|
91
|
+
if not isinstance(surface, MetDataset):
|
|
92
|
+
raise TypeError("Surface air pressure required as input parameter 'surface'")
|
|
93
|
+
|
|
94
|
+
self.surface = surface.copy()
|
|
95
|
+
self.surface.ensure_vars(["surface_air_pressure"])
|
|
96
|
+
|
|
97
|
+
def eval(self, source: MetDataset | None = None, **params: Any) -> MetDataArray:
|
|
98
|
+
"""Evaluate PCC model.
|
|
99
|
+
|
|
100
|
+
Currently only implemented to work on the :attr:`met` data input.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
source : MetDataset | None, optional
|
|
105
|
+
Input MetDataset.
|
|
106
|
+
If None, evaluates at the :attr:`met` grid points.
|
|
107
|
+
**params : Any
|
|
108
|
+
Overwrite model parameters before eval
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
MetDataArray
|
|
114
|
+
PCC model output
|
|
115
|
+
"""
|
|
116
|
+
self.update_params(params)
|
|
117
|
+
self.set_source(source)
|
|
118
|
+
|
|
119
|
+
# apply humidity scaling
|
|
120
|
+
scale_humidity = (self.params["humidity_scaling"] is not None) and (
|
|
121
|
+
"specific_humidity" not in self.source
|
|
122
|
+
)
|
|
123
|
+
self.set_source_met()
|
|
124
|
+
|
|
125
|
+
if scale_humidity:
|
|
126
|
+
self.params["humidity_scaling"].eval(self.source, copy_source=False)
|
|
127
|
+
|
|
128
|
+
return MetDataArray(self.b_contr(), name="pcc")
|
|
129
|
+
|
|
130
|
+
def b_contr(self) -> xr.DataArray:
|
|
131
|
+
"""Calculate critical relative humidity threshold of contrail formation.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
xr.DataArray
|
|
136
|
+
Critical relative humidity of contrail formation, [:math:`[0 - 1]`]
|
|
137
|
+
|
|
138
|
+
Notes
|
|
139
|
+
-----
|
|
140
|
+
Instead of using a prescribed threshold relative humidity for ``rh_crit_old``
|
|
141
|
+
the threshold relative humidity now change with pressure.
|
|
142
|
+
|
|
143
|
+
This equation is described in Roeckner et al. 1996, Eq.57
|
|
144
|
+
THE ATMOSPHERIC GENERAL CIRCULATION MODEL ECHAM-4: MODEL DESCRIPTION AND
|
|
145
|
+
SIMULATION OF PRESENT-DAY CLIMATE
|
|
146
|
+
"""
|
|
147
|
+
sp = self.surface["surface_air_pressure"].data.loc[dict(level=-1)] # surface air pressure
|
|
148
|
+
|
|
149
|
+
def _apply_b_contr_plev(_ds: xr.Dataset) -> xr.Dataset:
|
|
150
|
+
p = _ds["air_pressure"]
|
|
151
|
+
|
|
152
|
+
G = sac.slope_mixing_line(
|
|
153
|
+
_ds["specific_humidity"],
|
|
154
|
+
_ds["air_pressure"],
|
|
155
|
+
self.params["engine_efficiency"],
|
|
156
|
+
self.params["fuel"].ei_h2o,
|
|
157
|
+
self.params["fuel"].q_fuel,
|
|
158
|
+
)
|
|
159
|
+
T_sat_liquid = sac.T_sat_liquid(G)
|
|
160
|
+
rh_crit_sac = sac.rh_critical_sac(_ds["air_temperature"], T_sat_liquid, G)
|
|
161
|
+
|
|
162
|
+
# rh_crit_old = np.ones(p.shape) * self.rh_crit_factor
|
|
163
|
+
rh_crit_old = 0.99 + (0.6 - 0.99) * np.exp(1 - (sp / p) ** 4)
|
|
164
|
+
rh_crit_new = rh_crit_sac * rh_crit_old
|
|
165
|
+
|
|
166
|
+
b_crit = self._cloud_model(
|
|
167
|
+
_ds["air_temperature"],
|
|
168
|
+
_ds["air_pressure"],
|
|
169
|
+
_ds["specific_cloud_ice_water_content"],
|
|
170
|
+
_ds["specific_humidity"],
|
|
171
|
+
rh_crit_old,
|
|
172
|
+
rh_crit_old,
|
|
173
|
+
)
|
|
174
|
+
b_crit_contr = self._cloud_model(
|
|
175
|
+
_ds["air_temperature"],
|
|
176
|
+
_ds["air_pressure"],
|
|
177
|
+
_ds["specific_cloud_ice_water_content"],
|
|
178
|
+
_ds["specific_humidity"],
|
|
179
|
+
rh_crit_old,
|
|
180
|
+
rh_crit_new,
|
|
181
|
+
)
|
|
182
|
+
b_crit_potential = b_crit_contr - b_crit
|
|
183
|
+
|
|
184
|
+
b_crit_potential = xr.where((b_crit_potential > 1), 1, b_crit_potential)
|
|
185
|
+
b_crit_potential = xr.where((b_crit_potential < 0), 0, b_crit_potential)
|
|
186
|
+
|
|
187
|
+
# issue recombining groups arises if "level" is in dims
|
|
188
|
+
# convert "level" dimension to coordinate
|
|
189
|
+
return b_crit_potential.squeeze("level")
|
|
190
|
+
|
|
191
|
+
# apply calculation per pressure level
|
|
192
|
+
return (
|
|
193
|
+
self.source.data.groupby("level", squeeze=False) # type: ignore
|
|
194
|
+
.map(_apply_b_contr_plev)
|
|
195
|
+
.transpose(*self.source.dim_order)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def Smith1990(
|
|
199
|
+
self,
|
|
200
|
+
T: xr.DataArray,
|
|
201
|
+
p: xr.DataArray,
|
|
202
|
+
iwc: xr.DataArray,
|
|
203
|
+
q: xr.DataArray,
|
|
204
|
+
rh_crit_old: xr.DataArray,
|
|
205
|
+
rh_crit_new: xr.DataArray,
|
|
206
|
+
) -> xr.DataArray:
|
|
207
|
+
r"""Apply Smith Scheme described in Rap et al. (2009).
|
|
208
|
+
|
|
209
|
+
Parameterization of contrails in the UK Met OfficeClimate Model;
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
T : :class:`xarray:DataArray`
|
|
214
|
+
Air Temperature, [:math:`K`]
|
|
215
|
+
p : :class:`xarray:DataArray`
|
|
216
|
+
Air Pressure, [:math:`Pa`]
|
|
217
|
+
iwc : :class:`xarray:DataArray`
|
|
218
|
+
Cloud ice water content, [:math:`kg \ kg^{-1}`]
|
|
219
|
+
q : :class:`xarray:DataArray`
|
|
220
|
+
Specific humidity
|
|
221
|
+
rh_crit_old : :class:`xarray:DataArray`
|
|
222
|
+
Critical relative humidity, [:math:`[0 - 1]`]
|
|
223
|
+
rh_crit_new : :class:`xarray:DataArray`
|
|
224
|
+
Critical relative humidity, [:math:`[0 - 1]`]
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
-------
|
|
228
|
+
:class:`xarray:DataArray`
|
|
229
|
+
Probability of cirrus formation, [:math:`[0 - 1]`]
|
|
230
|
+
"""
|
|
231
|
+
r = thermo.rh(q, T, p)
|
|
232
|
+
q_sw = thermo.q_sat(T, p)
|
|
233
|
+
b_crit = (1 - rh_crit_old) * q_sw
|
|
234
|
+
Q_n = iwc / b_crit - (1 - r) / (1 - rh_crit_new)
|
|
235
|
+
|
|
236
|
+
b_cirrus = xr.DataArray(np.zeros(Q_n.shape), coords=Q_n.coords)
|
|
237
|
+
b_cirrus = xr.where((Q_n <= -1), 0, b_cirrus)
|
|
238
|
+
b_cirrus = xr.where(
|
|
239
|
+
(Q_n > -1) & (Q_n <= 0), 0.5 * (1 + Q_n.where((Q_n > -1) & (Q_n <= 0))) ** 2, b_cirrus
|
|
240
|
+
)
|
|
241
|
+
b_cirrus = xr.where(
|
|
242
|
+
(Q_n > 0) & (Q_n <= 1), 1 - 0.5 * (1 - Q_n.where((Q_n > 0) & (Q_n <= 1))) ** 2, b_cirrus
|
|
243
|
+
)
|
|
244
|
+
return xr.where((Q_n > 1), 1, b_cirrus)
|
|
245
|
+
|
|
246
|
+
def Slingo1980(
|
|
247
|
+
self,
|
|
248
|
+
T: xr.DataArray,
|
|
249
|
+
p: xr.DataArray,
|
|
250
|
+
iwc: xr.DataArray,
|
|
251
|
+
q: xr.DataArray,
|
|
252
|
+
rh_crit_old: xr.DataArray,
|
|
253
|
+
rh_crit_new: xr.DataArray,
|
|
254
|
+
) -> xr.DataArray:
|
|
255
|
+
r"""Apply Slingo scheme described in Wood and Field, 1999.
|
|
256
|
+
|
|
257
|
+
Relationships between Total Water, Condensed Water, and Cloud Fraction in
|
|
258
|
+
Stratiform Clouds Examined Using Aircraft Data
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
T : :class:`xarray:DataArray`
|
|
263
|
+
Air Temperature, [:math:`K`]
|
|
264
|
+
p : :class:`xarray:DataArray`
|
|
265
|
+
Air Pressure, [:math:`Pa`]
|
|
266
|
+
iwc : :class:`xarray:DataArray`
|
|
267
|
+
Cloud ice water content, [:math:`kg \ kg^{-1}`]
|
|
268
|
+
q : :class:`xarray:DataArray`
|
|
269
|
+
Specific humidity
|
|
270
|
+
rh_crit_old : :class:`xarray:DataArray`
|
|
271
|
+
Critical relative humidity, [:math:`[0 - 1]`]
|
|
272
|
+
rh_crit_new : :class:`xarray:DataArray`
|
|
273
|
+
Critical relative humidity, [:math:`[0 - 1]`]
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
:class:`xarray:DataArray`
|
|
278
|
+
Probability of cirrus formation, [:math:`[0 - 1]`]
|
|
279
|
+
"""
|
|
280
|
+
r = thermo.rh(q, T, p)
|
|
281
|
+
b_cirrus = ((r - rh_crit_new) / (1 - rh_crit_new)) ** 2
|
|
282
|
+
b_cirrus = xr.where(r < rh_crit_new, 0, b_cirrus)
|
|
283
|
+
return xr.where(r >= 1.0, 1.0, b_cirrus)
|
|
284
|
+
|
|
285
|
+
def Sundqvist1989(
|
|
286
|
+
self,
|
|
287
|
+
T: xr.DataArray,
|
|
288
|
+
p: xr.DataArray,
|
|
289
|
+
iwc: xr.DataArray,
|
|
290
|
+
q: xr.DataArray,
|
|
291
|
+
rh_crit_old: xr.DataArray,
|
|
292
|
+
rh_crit_new: xr.DataArray,
|
|
293
|
+
) -> xr.DataArray:
|
|
294
|
+
r"""Apply Sundqvist scheme described in Ponater et al. (2002).
|
|
295
|
+
|
|
296
|
+
Contrails in a comprehensive global climate model: Parameterization and radiative
|
|
297
|
+
forcing results
|
|
298
|
+
|
|
299
|
+
Parameters
|
|
300
|
+
----------
|
|
301
|
+
T : :class:`xarray:DataArray`
|
|
302
|
+
Air Temperature, [:math:`K`]
|
|
303
|
+
p : :class:`xarray:DataArray`
|
|
304
|
+
Air Pressure, [:math:`Pa`]
|
|
305
|
+
iwc : :class:`xarray:DataArray`
|
|
306
|
+
Cloud ice water content, [:math:`kg \ kg^{-1}`]
|
|
307
|
+
q : :class:`xarray:DataArray`
|
|
308
|
+
Specific humidity
|
|
309
|
+
rh_crit_old : :class:`xarray:DataArray`
|
|
310
|
+
Critical relative humidity, [:math:`[0 - 1]`]
|
|
311
|
+
rh_crit_new : :class:`xarray:DataArray`
|
|
312
|
+
Critical relative humidity, [:math:`[0 - 1]`]
|
|
313
|
+
|
|
314
|
+
Returns
|
|
315
|
+
-------
|
|
316
|
+
:class:`xarray:DataArray`
|
|
317
|
+
Probability of cirrus formation, [:math:`[0 - 1]`]
|
|
318
|
+
"""
|
|
319
|
+
r = thermo.rh(q, T, p)
|
|
320
|
+
|
|
321
|
+
# clipping ratio at 1 to prevent np.sqrt from taking negative arguments
|
|
322
|
+
ratio = (r - rh_crit_new) / (1 - rh_crit_new)
|
|
323
|
+
ratio = xr.where(ratio > 1, 1, ratio)
|
|
324
|
+
|
|
325
|
+
b_cirrus = 1 - (1 - ratio) ** 0.5
|
|
326
|
+
return xr.where(r < rh_crit_new, 0, b_cirrus)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Persistent contrail regions (PCR = SAC & ISSR).
|
|
3
|
+
|
|
4
|
+
Equivalent to (SAC & ISSR)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, overload
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from pycontrails.core.flight import Flight
|
|
15
|
+
from pycontrails.core.met import MetDataset
|
|
16
|
+
from pycontrails.core.met_var import AirTemperature, SpecificHumidity
|
|
17
|
+
from pycontrails.core.models import Model
|
|
18
|
+
from pycontrails.core.vector import GeoVectorDataset
|
|
19
|
+
from pycontrails.models import issr, sac
|
|
20
|
+
from pycontrails.physics import thermo
|
|
21
|
+
from pycontrails.utils.types import ArrayLike, apply_nan_mask_to_arraylike
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class PCRParams(sac.SACParams, issr.ISSRParams):
|
|
26
|
+
"""Persistent Contrail Regions (PCR) parameters."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class PCR(Model):
|
|
30
|
+
"""Determine points with likely persistent contrails (PCR).
|
|
31
|
+
|
|
32
|
+
Intersection of Ice Super Saturated Regions (ISSR) with regions in which the Schmidt-Appleman
|
|
33
|
+
Criteria (SAC) is satisfied.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
met : MetDataset
|
|
38
|
+
Dataset containing "air_temperature", "specific_humidity" variables
|
|
39
|
+
params : dict[str, Any], optional
|
|
40
|
+
Override PCR model parameters with dictionary.
|
|
41
|
+
See :class:`PCRGridParams` for model parameters.
|
|
42
|
+
**params_kwargs
|
|
43
|
+
Override PCR model parameters with keyword arguments.
|
|
44
|
+
See :class:`PCRGridParams` for model parameters.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
name = "pcr"
|
|
48
|
+
long_name = "Persistent contrail regions"
|
|
49
|
+
met_variables = AirTemperature, SpecificHumidity
|
|
50
|
+
default_params = PCRParams
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
54
|
+
|
|
55
|
+
@overload
|
|
56
|
+
def eval(self, source: GeoVectorDataset, **params: Any) -> GeoVectorDataset: ...
|
|
57
|
+
|
|
58
|
+
@overload
|
|
59
|
+
def eval(self, source: MetDataset | None = ..., **params: Any) -> MetDataset: ...
|
|
60
|
+
|
|
61
|
+
def eval(
|
|
62
|
+
self, source: GeoVectorDataset | Flight | MetDataset | None = None, **params: Any
|
|
63
|
+
) -> GeoVectorDataset | Flight | MetDataset:
|
|
64
|
+
"""Evaluate potential contrails regions of the :attr:`met` grid.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
source : GeoVectorDataset | Flight | MetDataset | None, optional
|
|
69
|
+
Input GeoVectorDataset or Flight.
|
|
70
|
+
If None, evaluates at the :attr:`met` grid points.
|
|
71
|
+
**params : Any
|
|
72
|
+
Overwrite model parameters.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
GeoVectorDataset | Flight | MetDataset
|
|
77
|
+
Returns 1 in potential contrail regions, 0 everywhere else.
|
|
78
|
+
Returns ``np.nan`` if interpolating outside meteorology grid.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
self.update_params(params)
|
|
83
|
+
self.set_source(source)
|
|
84
|
+
issr_params = {k: v for k, v in self.params.items() if hasattr(issr.ISSR.default_params, k)}
|
|
85
|
+
issr_model = issr.ISSR(self.met, params=issr_params, copy_source=False)
|
|
86
|
+
issr_model.eval(self.source)
|
|
87
|
+
|
|
88
|
+
sac_params = {k: v for k, v in self.params.items() if hasattr(sac.SAC.default_params, k)}
|
|
89
|
+
# NOTE: met is not needed here: ISSR already used it
|
|
90
|
+
sac_model = sac.SAC(met=None, params=sac_params, copy_source=False)
|
|
91
|
+
sac_model.eval(self.source)
|
|
92
|
+
|
|
93
|
+
pcr_ = _pcr_from_issr_and_sac(self.source.data["issr"], self.source.data["sac"]) # type: ignore[type-var]
|
|
94
|
+
self.source["pcr"] = pcr_
|
|
95
|
+
|
|
96
|
+
return self.source
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def pcr(
|
|
100
|
+
air_temperature: ArrayLike,
|
|
101
|
+
specific_humidity: ArrayLike,
|
|
102
|
+
air_pressure: ArrayLike,
|
|
103
|
+
engine_efficiency: float | ArrayLike,
|
|
104
|
+
ei_h2o: float,
|
|
105
|
+
q_fuel: float,
|
|
106
|
+
) -> tuple[ArrayLike, ArrayLike, ArrayLike]:
|
|
107
|
+
r"""Calculate regions of persistent contrail formation.
|
|
108
|
+
|
|
109
|
+
Ice Super Saturated Regions (ISSR) where the Schmidt-Appleman Criteria (SAC) is satisfied.
|
|
110
|
+
|
|
111
|
+
Parameters of type :class:`ArrayLike` must have compatible shapes.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
air_temperature : ArrayLike
|
|
116
|
+
A sequence or array of temperature values, [:math:`K`]
|
|
117
|
+
specific_humidity : ArrayLike
|
|
118
|
+
A sequence or array of specific humidity values, [:math:`kg_{H_{2}O} \ kg_{air}^{-1}`]
|
|
119
|
+
air_pressure : ArrayLike
|
|
120
|
+
A sequence or array of atmospheric pressure values, [:math:`Pa`].
|
|
121
|
+
engine_efficiency: float | ArrayLike
|
|
122
|
+
Engine efficiency, [:math:`0 - 1`]
|
|
123
|
+
ei_h2o : float
|
|
124
|
+
Emission index of water vapor, [:math:`kg \ kg^{-1}`]
|
|
125
|
+
q_fuel : float
|
|
126
|
+
Specific combustion heat of fuel combustion, [:math:`J \ kg^{-1} \ K^{-1}`]
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
pcr : ArrayLike
|
|
131
|
+
PCR state of each point indexed by the :class:`ArrayLike` parameters.
|
|
132
|
+
sac : ArrayLike
|
|
133
|
+
SAC state
|
|
134
|
+
issr : ArrayLike
|
|
135
|
+
ISSR state
|
|
136
|
+
"""
|
|
137
|
+
issr_ = issr.issr(air_temperature, specific_humidity, air_pressure)
|
|
138
|
+
G = sac.slope_mixing_line(specific_humidity, air_pressure, engine_efficiency, ei_h2o, q_fuel)
|
|
139
|
+
T_sat_liquid_ = sac.T_sat_liquid(G)
|
|
140
|
+
rh = thermo.rh(specific_humidity, air_temperature, air_pressure)
|
|
141
|
+
rh_crit_sac = sac.rh_critical_sac(air_temperature, T_sat_liquid_, G)
|
|
142
|
+
sac_ = sac.sac(rh, rh_crit_sac)
|
|
143
|
+
|
|
144
|
+
pcr_ = _pcr_from_issr_and_sac(issr_, sac_)
|
|
145
|
+
return pcr_, sac_, issr_
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _pcr_from_issr_and_sac(issr_: ArrayLike, sac_: ArrayLike) -> ArrayLike:
|
|
149
|
+
# store nan values to refill after casting
|
|
150
|
+
nan_mask = np.isnan(issr_) | np.isnan(sac_)
|
|
151
|
+
|
|
152
|
+
dtype = np.result_type(issr_, sac_)
|
|
153
|
+
pcr_ = ((issr_ > 0.0) & (sac_ > 0.0)).astype(dtype)
|
|
154
|
+
return apply_nan_mask_to_arraylike(pcr_, nan_mask)
|