pycontrails 0.58.0__cp314-cp314-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pycontrails might be problematic. Click here for more details.
- pycontrails/__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 +2931 -0
- pycontrails/core/met_var.py +387 -0
- pycontrails/core/models.py +1321 -0
- pycontrails/core/polygon.py +549 -0
- pycontrails/core/rgi_cython.cp314-win_amd64.pyd +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 +757 -0
- pycontrails/datalib/himawari/__init__.py +27 -0
- pycontrails/datalib/himawari/header_struct.py +266 -0
- pycontrails/datalib/himawari/himawari.py +667 -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.58.0.dist-info/METADATA +180 -0
- pycontrails-0.58.0.dist-info/RECORD +122 -0
- pycontrails-0.58.0.dist-info/WHEEL +5 -0
- pycontrails-0.58.0.dist-info/licenses/LICENSE +178 -0
- pycontrails-0.58.0.dist-info/licenses/NOTICE +43 -0
- pycontrails-0.58.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
ICAO Aircraft Code,PS ATYP
|
|
2
|
+
A178,E190
|
|
3
|
+
A19N,A20N
|
|
4
|
+
A20N,A20N
|
|
5
|
+
A21N,A21N
|
|
6
|
+
A306,A306
|
|
7
|
+
A30B,A30B
|
|
8
|
+
A310,A310
|
|
9
|
+
A313,A313
|
|
10
|
+
A318,A318
|
|
11
|
+
A319,A319
|
|
12
|
+
A320,A320
|
|
13
|
+
A321,A321
|
|
14
|
+
A332,A332
|
|
15
|
+
A333,A333
|
|
16
|
+
A338,A338
|
|
17
|
+
A339,A339
|
|
18
|
+
A342,A342
|
|
19
|
+
A343,A343
|
|
20
|
+
A345,A345
|
|
21
|
+
A346,A346
|
|
22
|
+
A359,A359
|
|
23
|
+
A35K,A35K
|
|
24
|
+
A388,A388
|
|
25
|
+
B37M,B37M
|
|
26
|
+
B38M,B38M
|
|
27
|
+
B39M,B39M
|
|
28
|
+
B3XM,B3XM
|
|
29
|
+
B701,B752
|
|
30
|
+
B712,B712
|
|
31
|
+
B720,B752
|
|
32
|
+
B721,B752
|
|
33
|
+
B722,B722
|
|
34
|
+
B732,B732
|
|
35
|
+
B733,B733
|
|
36
|
+
B734,B734
|
|
37
|
+
B735,B735
|
|
38
|
+
B736,B736
|
|
39
|
+
B737,B737
|
|
40
|
+
B738,B738
|
|
41
|
+
B739,B739
|
|
42
|
+
B741,B743
|
|
43
|
+
B742,B742
|
|
44
|
+
B743,B743
|
|
45
|
+
B744,B744
|
|
46
|
+
B748,B748
|
|
47
|
+
B74D,B743
|
|
48
|
+
B74R,B743
|
|
49
|
+
B74S,B742
|
|
50
|
+
B752,B752
|
|
51
|
+
B753,B753
|
|
52
|
+
B762,B762
|
|
53
|
+
B763,B763
|
|
54
|
+
B764,B764
|
|
55
|
+
B772,B772
|
|
56
|
+
B773,B773
|
|
57
|
+
B77L,B77L
|
|
58
|
+
B77W,B77W
|
|
59
|
+
B788,B788
|
|
60
|
+
B789,B789
|
|
61
|
+
B78X,B78X
|
|
62
|
+
BCS1,BCS1
|
|
63
|
+
BCS3,BCS3
|
|
64
|
+
BLCF,B77W
|
|
65
|
+
C141,A310
|
|
66
|
+
C17,B764
|
|
67
|
+
C5,A345
|
|
68
|
+
C5M,A345
|
|
69
|
+
C919,A20N
|
|
70
|
+
CRJ7,CRJ9
|
|
71
|
+
CRJ9,CRJ9
|
|
72
|
+
DC91,B712
|
|
73
|
+
DC93,DC93
|
|
74
|
+
E135,E135
|
|
75
|
+
E145,E145
|
|
76
|
+
E170,E170
|
|
77
|
+
E190,E190
|
|
78
|
+
E195,E195
|
|
79
|
+
E290,E290
|
|
80
|
+
E295,E295
|
|
81
|
+
E3CF,B762
|
|
82
|
+
E3TF,B762
|
|
83
|
+
E737,B738
|
|
84
|
+
E75L,E75L
|
|
85
|
+
E75S,E75S
|
|
86
|
+
E767,B763
|
|
87
|
+
GLF5,GLF5
|
|
88
|
+
IL62,A30B
|
|
89
|
+
J328,E135
|
|
90
|
+
KC39,A321
|
|
91
|
+
MC23,A20N
|
|
92
|
+
MD81,MD82
|
|
93
|
+
MD82,MD82
|
|
94
|
+
MD83,MD83
|
|
95
|
+
MD87,MD82
|
|
96
|
+
MD88,MD82
|
|
97
|
+
MD90,MD83
|
|
98
|
+
NIM,B738
|
|
99
|
+
P1,B38M
|
|
100
|
+
P8,B738
|
|
101
|
+
R721,B722
|
|
102
|
+
R722,B722
|
|
103
|
+
RJ1H,RJ1H
|
|
104
|
+
SLCH,A388
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Schmidt-Appleman criteria (SAC)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
from typing import Any, overload
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import scipy.optimize
|
|
10
|
+
|
|
11
|
+
import pycontrails
|
|
12
|
+
from pycontrails.core.flight import Flight
|
|
13
|
+
from pycontrails.core.fuel import Fuel, JetA
|
|
14
|
+
from pycontrails.core.met import MetDataset
|
|
15
|
+
from pycontrails.core.met_var import AirTemperature, SpecificHumidity
|
|
16
|
+
from pycontrails.core.models import Model, ModelParams
|
|
17
|
+
from pycontrails.core.vector import GeoVectorDataset
|
|
18
|
+
from pycontrails.models.humidity_scaling import HumidityScaling
|
|
19
|
+
from pycontrails.physics import constants, thermo
|
|
20
|
+
from pycontrails.utils.types import ArrayLike, apply_nan_mask_to_arraylike
|
|
21
|
+
|
|
22
|
+
# -----------------
|
|
23
|
+
# Models as classes
|
|
24
|
+
# -----------------
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclasses.dataclass
|
|
28
|
+
class SACParams(ModelParams):
|
|
29
|
+
"""Parameters for :class:`SAC`."""
|
|
30
|
+
|
|
31
|
+
#: Jet engine efficiency, [:math:`0 - 1`]
|
|
32
|
+
engine_efficiency: float = 0.3
|
|
33
|
+
|
|
34
|
+
#: Fuel type.
|
|
35
|
+
#: Overridden by Fuel provided on input ``source`` attributes
|
|
36
|
+
fuel: Fuel = dataclasses.field(default_factory=JetA)
|
|
37
|
+
|
|
38
|
+
#: Humidity scaling
|
|
39
|
+
humidity_scaling: HumidityScaling | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class SAC(Model):
|
|
43
|
+
"""Determine points where Schmidt-Appleman Criteria is satisfied.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
met : MetDataset
|
|
48
|
+
Dataset containing "air_temperature", "specific_humidity" variables.
|
|
49
|
+
params : dict[str, Any], optional
|
|
50
|
+
Override :class:`SACParams` with dictionary.
|
|
51
|
+
**params_kwargs
|
|
52
|
+
Override :class:`SACParams` with keyword arguments.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
name = "sac"
|
|
56
|
+
long_name = "Schmidt-Appleman contrail formation criteria"
|
|
57
|
+
met_variables = AirTemperature, SpecificHumidity
|
|
58
|
+
default_params = SACParams
|
|
59
|
+
|
|
60
|
+
@overload
|
|
61
|
+
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
62
|
+
|
|
63
|
+
@overload
|
|
64
|
+
def eval(self, source: GeoVectorDataset, **params: Any) -> GeoVectorDataset: ...
|
|
65
|
+
|
|
66
|
+
@overload
|
|
67
|
+
def eval(self, source: MetDataset | None = ..., **params: Any) -> MetDataset: ...
|
|
68
|
+
|
|
69
|
+
def eval(
|
|
70
|
+
self, source: GeoVectorDataset | Flight | MetDataset | None = None, **params: Any
|
|
71
|
+
) -> GeoVectorDataset | Flight | MetDataset:
|
|
72
|
+
"""Evaluate the Schmidt-Appleman criteria along flight trajectory or on meteorology grid.
|
|
73
|
+
|
|
74
|
+
.. versionchanged:: 0.27.0
|
|
75
|
+
|
|
76
|
+
Humidity scaling now handled automatically. This is controlled by
|
|
77
|
+
model parameter ``humidity_scaling``.
|
|
78
|
+
|
|
79
|
+
.. versionchanged:: 0.48.0
|
|
80
|
+
|
|
81
|
+
If the ``source`` is a :class:`MetDataset`, the returned object will
|
|
82
|
+
also be a :class:`MetDataset`. Previous the "sac" :class:`MetDataArray`
|
|
83
|
+
was returned.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
source : GeoVectorDataset | Flight | MetDataset | None, optional
|
|
88
|
+
Input GeoVectorDataset or Flight.
|
|
89
|
+
If None, evaluates at the :attr:`met` grid points.
|
|
90
|
+
**params : Any
|
|
91
|
+
Overwrite model parameters before eval
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
GeoVectorDataset | Flight | MetDataset
|
|
96
|
+
Returns 1 where SAC is satisfied, 0 everywhere else.
|
|
97
|
+
Returns ``np.nan`` if interpolating outside meteorology grid.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
self.update_params(params)
|
|
102
|
+
self.set_source(source)
|
|
103
|
+
self.require_source_type((GeoVectorDataset, MetDataset))
|
|
104
|
+
|
|
105
|
+
if isinstance(self.source, GeoVectorDataset):
|
|
106
|
+
self.downselect_met()
|
|
107
|
+
self.source.setdefault("air_pressure", self.source.air_pressure)
|
|
108
|
+
|
|
109
|
+
humidity_scaling = self.params["humidity_scaling"]
|
|
110
|
+
scale_humidity = humidity_scaling is not None and "specific_humidity" not in self.source
|
|
111
|
+
self.set_source_met()
|
|
112
|
+
|
|
113
|
+
# apply humidity scaling, warn if no scaling is provided for ECMWF data
|
|
114
|
+
if scale_humidity:
|
|
115
|
+
humidity_scaling.eval(self.source, copy_source=False)
|
|
116
|
+
|
|
117
|
+
# Extract source data
|
|
118
|
+
air_temperature = self.source.data["air_temperature"]
|
|
119
|
+
specific_humidity = self.source.data["specific_humidity"]
|
|
120
|
+
air_pressure = self.source.data["air_pressure"]
|
|
121
|
+
engine_efficiency = self.get_source_param("engine_efficiency")
|
|
122
|
+
|
|
123
|
+
# Flight class has fuel attribute, use this instead of params
|
|
124
|
+
if isinstance(self.source, Flight):
|
|
125
|
+
fuel = self.source.fuel
|
|
126
|
+
else:
|
|
127
|
+
# NOTE: Not setting fuel on MetDataset source
|
|
128
|
+
fuel = self.get_source_param("fuel", set_attr=False)
|
|
129
|
+
assert isinstance(fuel, Fuel), "The fuel attribute must be of type Fuel"
|
|
130
|
+
|
|
131
|
+
ei_h2o = fuel.ei_h2o
|
|
132
|
+
q_fuel = fuel.q_fuel
|
|
133
|
+
|
|
134
|
+
G = slope_mixing_line(specific_humidity, air_pressure, engine_efficiency, ei_h2o, q_fuel)
|
|
135
|
+
T_sat_liquid_ = T_sat_liquid(G)
|
|
136
|
+
rh_crit_sac = rh_critical_sac(air_temperature, T_sat_liquid_, G)
|
|
137
|
+
rh = thermo.rh(specific_humidity, air_temperature, air_pressure) # type: ignore[type-var]
|
|
138
|
+
sac_ = sac(rh, rh_crit_sac)
|
|
139
|
+
|
|
140
|
+
# Attaching some intermediate artifacts onto the source
|
|
141
|
+
self.source["G"] = G
|
|
142
|
+
self.source["T_sat_liquid"] = T_sat_liquid_
|
|
143
|
+
self.source["rh"] = rh
|
|
144
|
+
self.source["rh_critical_sac"] = rh_crit_sac
|
|
145
|
+
self.source["sac"] = sac_
|
|
146
|
+
|
|
147
|
+
# Tag output with additional metadata attrs
|
|
148
|
+
self.transfer_met_source_attrs()
|
|
149
|
+
self.source.attrs["pycontrails_version"] = pycontrails.__version__
|
|
150
|
+
if scale_humidity:
|
|
151
|
+
for k, v in humidity_scaling.description.items():
|
|
152
|
+
self.source.attrs[f"humidity_scaling_{k}"] = v
|
|
153
|
+
if isinstance(engine_efficiency, int | float):
|
|
154
|
+
self.source.attrs["engine_efficiency"] = engine_efficiency
|
|
155
|
+
|
|
156
|
+
return self.source
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# -------------------
|
|
160
|
+
# Models as functions
|
|
161
|
+
# -------------------
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def slope_mixing_line(
|
|
165
|
+
specific_humidity: ArrayLike,
|
|
166
|
+
air_pressure: ArrayLike,
|
|
167
|
+
engine_efficiency: float | ArrayLike,
|
|
168
|
+
ei_h2o: float,
|
|
169
|
+
q_fuel: float,
|
|
170
|
+
) -> ArrayLike:
|
|
171
|
+
r"""Calculate the slope of the mixing line in a temperature-humidity diagram.
|
|
172
|
+
|
|
173
|
+
This quantity is often notated with ``G`` in the literature.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
specific_humidity : ArrayLike
|
|
178
|
+
A sequence or array of specific humidity values, [:math:`kg_{H_{2}O} \ kg_{air}`]
|
|
179
|
+
air_pressure : ArrayLike
|
|
180
|
+
A sequence or array of atmospheric pressure values, [:math:`Pa`].
|
|
181
|
+
engine_efficiency: float | ArrayLike
|
|
182
|
+
Engine efficiency, [:math:`0 - 1`]
|
|
183
|
+
ei_h2o : float
|
|
184
|
+
Emission index of water vapor, [:math:`kg \ kg^{-1}`]
|
|
185
|
+
q_fuel : float
|
|
186
|
+
Specific combustion heat of fuel combustion, [:math:`J \ kg^{-1} \ K^{-1}`]
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
ArrayLike
|
|
191
|
+
Slope of the mixing line in a temperature-humidity diagram, [:math:`Pa \ K^{-1}`]
|
|
192
|
+
"""
|
|
193
|
+
c_pm = thermo.c_pm(specific_humidity) # Often taken as 1004 (= constants.c_pd)
|
|
194
|
+
return (ei_h2o * c_pm * air_pressure) / (constants.epsilon * q_fuel * (1.0 - engine_efficiency))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def T_sat_liquid(G: ArrayLike) -> ArrayLike:
|
|
198
|
+
r"""Calculate temperature at which liquid saturation curve has slope G.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
G : ArrayLike
|
|
203
|
+
Slope of the mixing line in a temperature-humidity diagram.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
ArrayLike
|
|
208
|
+
Maximum threshold temperature for 100% relative humidity with respect to liquid,
|
|
209
|
+
[:math:`K`]. This can also be interpreted as the temperature at which the liquid
|
|
210
|
+
saturation curve has slope G.
|
|
211
|
+
|
|
212
|
+
References
|
|
213
|
+
----------
|
|
214
|
+
- :cite:`schumannConditionsContrailFormation1996`
|
|
215
|
+
|
|
216
|
+
See Also
|
|
217
|
+
--------
|
|
218
|
+
:func:`T_sat_liquid_high_accuracy`
|
|
219
|
+
|
|
220
|
+
Notes
|
|
221
|
+
-----
|
|
222
|
+
Defined (using notation T_LM) in :cite:`schumannConditionsContrailFormation1996`
|
|
223
|
+
in the first full paragraph on page 10 as
|
|
224
|
+
|
|
225
|
+
for T = T_LC, the mixing line just touches [is tangent to]
|
|
226
|
+
the saturation curve. See equation (10).
|
|
227
|
+
|
|
228
|
+
The formula used here is taken from equation (31).
|
|
229
|
+
"""
|
|
230
|
+
# FIXME: Presently, mypy is not aware that numpy ufuncs will return `xr.DataArray``
|
|
231
|
+
# when xr.DataArray is passed in. This will get fixed at some point in the future
|
|
232
|
+
# as `numpy` their typing patterns, after which the "type: ignore" comment can
|
|
233
|
+
# get ripped out.
|
|
234
|
+
# We could explicitly check for `xr.DataArray` then use `xr.apply_ufunc`, but
|
|
235
|
+
# this only renders our code more boilerplate and less performant.
|
|
236
|
+
# This comment is pasted several places in `pycontrails` -- they should all be
|
|
237
|
+
# addressed at the same time.
|
|
238
|
+
log_ = np.log(G - 0.053)
|
|
239
|
+
return -46.46 - constants.absolute_zero + 9.43 * log_ + 0.72 * log_**2 # type: ignore[return-value]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def T_sat_liquid_high_accuracy(
|
|
243
|
+
G: ArrayLike,
|
|
244
|
+
maxiter: int = 5,
|
|
245
|
+
) -> ArrayLike:
|
|
246
|
+
"""Calculate temperature at which liquid saturation curve has slope G.
|
|
247
|
+
|
|
248
|
+
The function :func:`T_sat_liquid` gives a first order approximation to equation (10)
|
|
249
|
+
of the Schumann paper referenced below. This function uses Newton's method to
|
|
250
|
+
compute the numeric solution to (10).
|
|
251
|
+
|
|
252
|
+
Parameters
|
|
253
|
+
----------
|
|
254
|
+
G : ArrayLike
|
|
255
|
+
Slope of the mixing line
|
|
256
|
+
maxiter : int, optional
|
|
257
|
+
Passed into :func:`scipy.optimize.newton`. Because ``T_sat_liquid`` is already
|
|
258
|
+
fairly accurate, few iterations are needed for Newton's method to converge.
|
|
259
|
+
By default, 5.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
ArrayLike
|
|
264
|
+
Maximum threshold temperature for 100% relative humidity with respect to liquid,
|
|
265
|
+
[:math:`K`].
|
|
266
|
+
|
|
267
|
+
References
|
|
268
|
+
----------
|
|
269
|
+
- :cite:`schumannConditionsContrailFormation1996`
|
|
270
|
+
|
|
271
|
+
See Also
|
|
272
|
+
--------
|
|
273
|
+
:func:`T_sat_liquid_high`
|
|
274
|
+
"""
|
|
275
|
+
init_guess = T_sat_liquid(G)
|
|
276
|
+
|
|
277
|
+
def func(T: ArrayLike) -> ArrayLike:
|
|
278
|
+
"""Equation (10) from Schumann 1996."""
|
|
279
|
+
return thermo.e_sat_liquid_prime(T) - G
|
|
280
|
+
|
|
281
|
+
return scipy.optimize.newton(func, init_guess, maxiter=maxiter)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def rh_critical_sac(air_temperature: ArrayLike, T_sat_liquid: ArrayLike, G: ArrayLike) -> ArrayLike:
|
|
285
|
+
r"""Calculate critical relative humidity threshold of contrail formation.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
air_temperature : ArrayLike
|
|
290
|
+
A sequence or array of temperature values, [:math:`K`]
|
|
291
|
+
T_sat_liquid : ArrayLike
|
|
292
|
+
Maximum threshold temperature for 100% relative humidity with respect to liquid, [:math:`K`]
|
|
293
|
+
G : ArrayLike
|
|
294
|
+
Slope of the mixing line in a temperature-humidity diagram.
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
ArrayLike
|
|
299
|
+
Critical relative humidity of contrail formation, [:math:`[0 - 1]`]
|
|
300
|
+
|
|
301
|
+
References
|
|
302
|
+
----------
|
|
303
|
+
- :cite:`ponaterContrailsComprehensiveGlobal2002`
|
|
304
|
+
"""
|
|
305
|
+
e_sat_T_sat_liquid = thermo.e_sat_liquid(T_sat_liquid) # always positive
|
|
306
|
+
e_sat_T = thermo.e_sat_liquid(air_temperature) # always positive
|
|
307
|
+
|
|
308
|
+
# Below, `rh_crit` can be negative
|
|
309
|
+
rh_crit = (G * (air_temperature - T_sat_liquid) + e_sat_T_sat_liquid) / e_sat_T
|
|
310
|
+
|
|
311
|
+
# Per Ponater, section 2.3:
|
|
312
|
+
# >>> The critical relative humidity `r_contr` can range from 0 to 1
|
|
313
|
+
# The "first order" term `G * (air_temperature - T_sat_liquid)` can be negative.
|
|
314
|
+
# Consequently, we clip rh_crit at 0 and 1.
|
|
315
|
+
# After clipping, when rh_crit = 0 the SAC is guaranteed to be satisfied.
|
|
316
|
+
|
|
317
|
+
# Per Ponater, beginning of section 2.3:
|
|
318
|
+
# >>> "No contrails are possible if T > T_contr" <<<
|
|
319
|
+
# In our notation, this inequality is `air_temperature > T_sat_liquid`
|
|
320
|
+
# We set the corresponding rh_crit to infinity, indicating no SAC is possible
|
|
321
|
+
|
|
322
|
+
# numpy case
|
|
323
|
+
if isinstance(rh_crit, np.ndarray):
|
|
324
|
+
rh_crit.clip(0.0, 1.0, out=rh_crit) # clip in place
|
|
325
|
+
rh_crit[air_temperature > T_sat_liquid] = np.inf
|
|
326
|
+
return rh_crit
|
|
327
|
+
|
|
328
|
+
# xarray case
|
|
329
|
+
# FIXME: the two cases handle nans differently. Unfortunately, unit tests break
|
|
330
|
+
# if I try to consolidate to a single condition
|
|
331
|
+
rh_crit = rh_crit.clip(0.0, 1.0)
|
|
332
|
+
return rh_crit.where(air_temperature <= T_sat_liquid, np.inf)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def sac(
|
|
336
|
+
rh: ArrayLike,
|
|
337
|
+
rh_crit_sac: ArrayLike,
|
|
338
|
+
) -> ArrayLike:
|
|
339
|
+
r"""Points at which the Schmidt-Appleman Criteria is satisfied.
|
|
340
|
+
|
|
341
|
+
Parameters of type :class:`ArrayLike` must have compatible shapes.
|
|
342
|
+
|
|
343
|
+
Parameters
|
|
344
|
+
----------
|
|
345
|
+
rh : ArrayLike
|
|
346
|
+
Relative humidity values
|
|
347
|
+
rh_crit_sac: ArrayLike
|
|
348
|
+
Critical relative humidity threshold of contrail formation
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
ArrayLike
|
|
353
|
+
SAC state of each point indexed by the :class:`ArrayLike` parameters.
|
|
354
|
+
Returned array has floating ``dtype`` with values
|
|
355
|
+
|
|
356
|
+
- 0.0 signifying SAC fails
|
|
357
|
+
- 1.0 signifying SAC holds
|
|
358
|
+
|
|
359
|
+
NaN entries of parameters propagate into the returned array.
|
|
360
|
+
"""
|
|
361
|
+
nan_mask = np.isnan(rh) | np.isnan(rh_crit_sac)
|
|
362
|
+
|
|
363
|
+
dtype = np.result_type(rh, rh_crit_sac)
|
|
364
|
+
sac_ = (rh > rh_crit_sac).astype(dtype)
|
|
365
|
+
return apply_nan_mask_to_arraylike(sac_, nan_mask)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def T_critical_sac(
|
|
369
|
+
T_LM: ArrayLike,
|
|
370
|
+
relative_humidity: ArrayLike,
|
|
371
|
+
G: ArrayLike,
|
|
372
|
+
maxiter: int = 10,
|
|
373
|
+
) -> ArrayLike:
|
|
374
|
+
r"""Estimate temperature threshold for persistent contrail formation.
|
|
375
|
+
|
|
376
|
+
This quantity is defined as ``T_LC`` in Schumann (see reference below). Equation (11)
|
|
377
|
+
of this paper implicitly defines ``T_LC`` as the solution to the equation
|
|
378
|
+
::
|
|
379
|
+
|
|
380
|
+
T_LC = T_LM - (e_L(T_LM) - rh * e_L(T_LC)) / G
|
|
381
|
+
|
|
382
|
+
For relative humidity above 0.999, the corresponding entry from ``T_LM``
|
|
383
|
+
is returned (page 10, top of the right-hand column). Otherwise, the solution
|
|
384
|
+
to the equation above is approximated via Newton's method.
|
|
385
|
+
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
T_LM : ArrayLike
|
|
389
|
+
Output of :func:`T_sat_liquid` calculation.
|
|
390
|
+
relative_humidity : ArrayLike
|
|
391
|
+
Relative humidity values
|
|
392
|
+
G : ArrayLike
|
|
393
|
+
Slope of the mixing line in a temperature-humidity diagram.
|
|
394
|
+
maxiter : int, optional
|
|
395
|
+
Passed into :func:`scipy.optimize.newton`. By default, 10.
|
|
396
|
+
|
|
397
|
+
Returns
|
|
398
|
+
-------
|
|
399
|
+
ArrayLike
|
|
400
|
+
Critical temperature threshold values.
|
|
401
|
+
|
|
402
|
+
References
|
|
403
|
+
----------
|
|
404
|
+
- :cite:`schumannConditionsContrailFormation1996`
|
|
405
|
+
"""
|
|
406
|
+
# Near U = 1, Newton's method is slow to converge (I believe this is because
|
|
407
|
+
# the function `func` has a double root at T_LM when U = 1, so Newton's method
|
|
408
|
+
# is somewhat degenerate here)
|
|
409
|
+
# But the answer is known in this case. This is discussed at the top of the
|
|
410
|
+
# right hand column on page 10 in Schumann 1996.
|
|
411
|
+
# We only apply Newton's method at points with rh bounded below 1 (scipy will
|
|
412
|
+
# raise an error if Newton's method is not converging well).
|
|
413
|
+
filt = (relative_humidity < 0.999) & np.isfinite(T_LM)
|
|
414
|
+
if not np.any(filt):
|
|
415
|
+
return T_LM
|
|
416
|
+
|
|
417
|
+
U_filt = relative_humidity[filt]
|
|
418
|
+
T_LM_filt = T_LM[filt]
|
|
419
|
+
e_L_of_T_LM_filt = thermo.e_sat_liquid(T_LM_filt)
|
|
420
|
+
G_filt = G[filt]
|
|
421
|
+
|
|
422
|
+
def func(T: ArrayLike) -> ArrayLike:
|
|
423
|
+
"""Equation (11) from Schumann."""
|
|
424
|
+
return T - T_LM_filt + (e_L_of_T_LM_filt - U_filt * thermo.e_sat_liquid(T)) / G_filt
|
|
425
|
+
|
|
426
|
+
def fprime(T: ArrayLike) -> ArrayLike:
|
|
427
|
+
return 1.0 - U_filt * thermo.e_sat_liquid_prime(T) / G_filt
|
|
428
|
+
|
|
429
|
+
# This initial guess should be less than T_LM.
|
|
430
|
+
# For relative_humidity away from 1, Newton's method converges quickly, and so
|
|
431
|
+
# any initial guess will work.
|
|
432
|
+
init_guess = T_LM_filt - 1.0
|
|
433
|
+
newton_approx = scipy.optimize.newton(
|
|
434
|
+
func, init_guess, fprime=fprime, maxiter=maxiter, disp=False
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# For relative_humidity > 0.999, we just use T_LM
|
|
438
|
+
# We copy over the entire array T_LM here instead of using np.empty_like
|
|
439
|
+
# in order to keep typing compatible with xarray types
|
|
440
|
+
out = T_LM.copy()
|
|
441
|
+
out[filt] = newton_approx
|
|
442
|
+
return out
|