pycontrails 0.58.0__cp314-cp314-macosx_10_13_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 +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.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 +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 +6 -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,1260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module for calculating radiative forcing of contrail cirrus.
|
|
3
|
+
|
|
4
|
+
References
|
|
5
|
+
----------
|
|
6
|
+
- :cite:`schumannEffectiveRadiusIce2011`
|
|
7
|
+
- :cite:`schumannParametricRadiativeForcing2012`
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import dataclasses
|
|
13
|
+
import itertools
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import numpy.typing as npt
|
|
17
|
+
import xarray as xr
|
|
18
|
+
|
|
19
|
+
from pycontrails.core.met import MetDataArray, MetDataset
|
|
20
|
+
from pycontrails.core.vector import GeoVectorDataset
|
|
21
|
+
from pycontrails.physics import geo
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclasses.dataclass(frozen=True)
|
|
25
|
+
class RFConstants:
|
|
26
|
+
"""
|
|
27
|
+
Constants that are used to calculate the local contrail radiative forcing.
|
|
28
|
+
|
|
29
|
+
See Table 1 of :cite:`schumannParametricRadiativeForcing2012`.
|
|
30
|
+
|
|
31
|
+
Each coefficient has 8 elements, one corresponding to each contrail ice particle habit (shape)::
|
|
32
|
+
|
|
33
|
+
[
|
|
34
|
+
Sphere,
|
|
35
|
+
Solid column,
|
|
36
|
+
Hollow column,
|
|
37
|
+
Rough aggregate,
|
|
38
|
+
Rosette-6,
|
|
39
|
+
Plate,
|
|
40
|
+
Droxtal,
|
|
41
|
+
Myhre,
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
For each waypoint, the distinct mix of ice particle habits are approximated using the mean
|
|
45
|
+
contrail ice particle radius (``r_vol_um``) relative to ``radius_threshold_um``.
|
|
46
|
+
|
|
47
|
+
For example:
|
|
48
|
+
|
|
49
|
+
- if ``r_vol_um`` for a waypoint < 5 um, the mix of ice particle habits will be 100% droxtals.
|
|
50
|
+
- if ``r_vol_um`` for a waypoint between 5 and 9.5 um, the mix of ice particle habits will
|
|
51
|
+
be 30% solid columns, 70% droxtals.
|
|
52
|
+
|
|
53
|
+
See Table 2 from :cite:`schumannEffectiveRadiusIce2011`.
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
References
|
|
57
|
+
----------
|
|
58
|
+
- :cite:`schumannEffectiveRadiusIce2011`
|
|
59
|
+
- :cite:`schumannParametricRadiativeForcing2012`
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
# -----
|
|
63
|
+
# Variables/coefficients used to calculate the local contrail longwave radiative forcing.
|
|
64
|
+
# -----
|
|
65
|
+
|
|
66
|
+
#: Linear approximation of Stefan-Boltzmann Law
|
|
67
|
+
#: :math:`k_t` in Eq. (2) in :cite:`schumannParametricRadiativeForcing2012`
|
|
68
|
+
k_t = np.array([1.93466, 1.95456, 1.95994, 1.95906, 1.94397, 1.95123, 2.30363, 1.94611])
|
|
69
|
+
|
|
70
|
+
#: Approximates the temperature of the atmosphere without contrails
|
|
71
|
+
#: :math:`T_{0}` in Eq. (2) in :cite:`schumannParametricRadiativeForcing2012`
|
|
72
|
+
T_0 = np.array([152.237, 152.724, 152.923, 152.360, 151.879, 152.318, 165.692, 153.073])
|
|
73
|
+
|
|
74
|
+
#: Approximate the effective emmissivity factor
|
|
75
|
+
#: :math:`\delta_{\tau} in :cite:`schumannParametricRadiativeForcing2012`
|
|
76
|
+
delta_t = np.array(
|
|
77
|
+
[0.940846, 0.808397, 0.736222, 0.675591, 0.748757, 0.708515, 0.927592, 0.795527]
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
#: Effective radius scaling factor for optical properties (extinction relative to scattering)
|
|
81
|
+
#: :math:`\delta_{lr} in Eq. (3) in :cite:`schumannParametricRadiativeForcing2012`
|
|
82
|
+
delta_lr = np.array([0.211276, 0.341194, 0.325496, 0.255921, 0.170265, 1.65441, 0.201949, 0])
|
|
83
|
+
|
|
84
|
+
#: Optical depth scaling factor for reduction of the OLR at the contrail level due
|
|
85
|
+
#: to existing cirrus above the contrail
|
|
86
|
+
#: :math:`\delta_{lc} in Eq. (4) in :cite:`schumannParametricRadiativeForcing2012`
|
|
87
|
+
delta_lc = np.array(
|
|
88
|
+
[0.159942, 0.0958129, 0.0924850, 0.0462023, 0.132925, 0.0870067, 0.0626339, 0.0665289]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# -----
|
|
92
|
+
# Variables/coefficients used to calculate the local contrail shortwave radiative forcing.
|
|
93
|
+
# -----
|
|
94
|
+
|
|
95
|
+
#: Approximates the dependence on the effective albedo
|
|
96
|
+
#: :math:`t_a`: Eq. (5) in :cite:`schumannParametricRadiativeForcing2012`
|
|
97
|
+
t_a = np.array([0.879119, 0.901701, 0.881812, 0.899144, 0.879896, 0.883212, 0.899096, 1.00744])
|
|
98
|
+
|
|
99
|
+
# Approximates the albedo of the contrail
|
|
100
|
+
#: :math:`A_{\mu}` in Eq. (6) in :cite:`schumannParametricRadiativeForcing2012`
|
|
101
|
+
A_mu = np.array(
|
|
102
|
+
[0.361226, 0.294072, 0.343894, 0.317866, 0.337227, 0.310978, 0.342593, 0.269179]
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Approximates the albedo of the contrail
|
|
106
|
+
#: :math:`C_{\mu}` in Eq. (6) in :cite:`schumannParametricRadiativeForcing2012`
|
|
107
|
+
C_mu = np.array(
|
|
108
|
+
[0.709300, 0.678016, 0.687546, 0.675315, 0.712041, 0.713317, 0.660267, 0.545716]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
#: Approximates the effective contrail optical depth
|
|
112
|
+
#: :math:`delta_sr` in Eq. (7) and (8) in :cite:`schumannParametricRadiativeForcing2012`
|
|
113
|
+
delta_sr = np.array(
|
|
114
|
+
[0.149851, 0.0254270, 0.0238836, 0.0463724, 0.0478892, 0.0700234, 0.0517942, 0]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
#: Approximates the effective contrail optical depth
|
|
118
|
+
#: :math:`F_r` in Eq. (7) and (8) in :cite:`schumannParametricRadiativeForcing2012`
|
|
119
|
+
F_r = np.array([0.511852, 0.576911, 0.597351, 0.225750, 0.550734, 0.817858, 0.249004, 0])
|
|
120
|
+
|
|
121
|
+
#: Approximates the contrail reflectances
|
|
122
|
+
#: :math:`\gamma` in Eq. (9) in :cite:`schumannParametricRadiativeForcing2012`
|
|
123
|
+
gamma_lower = np.array(
|
|
124
|
+
[0.323166, 0.392598, 0.356189, 0.345040, 0.407515, 0.523604, 0.310853, 0.274741]
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
#: Approximates the contrail reflectances
|
|
128
|
+
#: :math:`\Gamma` in Eq. (9) in :cite:`schumannParametricRadiativeForcing2012`
|
|
129
|
+
gamma_upper = np.array(
|
|
130
|
+
[0.241507, 0.347023, 0.288452, 0.296813, 0.327857, 0.437560, 0.274710, 0.208154]
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
#: Approximate the SZA-dependent contrail sideward scattering
|
|
134
|
+
#: :math:`B_{\mu}` in Eq. (10) in :cite:`schumannParametricRadiativeForcing2012`
|
|
135
|
+
B_mu = np.array([1.67592, 1.55687, 1.71065, 1.55843, 1.70782, 1.71789, 1.56399, 1.59015])
|
|
136
|
+
|
|
137
|
+
#: Account for the optical depth of natural cirrus above the contrail
|
|
138
|
+
#: :math:`\delta_{sc}` in Eq. (11) in :cite:`schumannParametricRadiativeForcing2012`
|
|
139
|
+
delta_sc = np.array(
|
|
140
|
+
[0.157017, 0.143274, 0.167995, 0.148547, 0.173036, 0.162442, 0.171855, 0.213488]
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
#: Account for the optical depth of natural cirrus above the contrail
|
|
144
|
+
# :math:`\delta'_{sc}` in Eq. (11) in :cite:`schumannParametricRadiativeForcing2012`
|
|
145
|
+
delta_sc_aps = np.array(
|
|
146
|
+
[0.229574, 0.197611, 0.245036, 0.204875, 0.248328, 0.254029, 0.244051, 0.302246]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# create a new constants class to use within module
|
|
151
|
+
RF_CONST = RFConstants()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ----------
|
|
155
|
+
# Ice Habits
|
|
156
|
+
# ----------
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def habit_weights(
|
|
160
|
+
r_vol_um: npt.NDArray[np.floating],
|
|
161
|
+
habit_distributions: npt.NDArray[np.floating],
|
|
162
|
+
radius_threshold_um: npt.NDArray[np.floating],
|
|
163
|
+
) -> npt.NDArray[np.floating]:
|
|
164
|
+
r"""Assign weights to different ice particle habits for each waypoint.
|
|
165
|
+
|
|
166
|
+
For each waypoint, the distinct mix of ice particle habits are approximated
|
|
167
|
+
using the mean contrail ice particle radius (``r_vol_um``) binned by ``radius_threshold_um``.
|
|
168
|
+
|
|
169
|
+
For example:
|
|
170
|
+
|
|
171
|
+
- For waypoints with r_vol_um < 5 um, the mix of ice particle habits will
|
|
172
|
+
be from Group 1 (100% Droxtals, refer to :attr:`CocipParams().habit_distributions`).
|
|
173
|
+
- For waypoints with 5 um <= ``r_vol_um`` < 9.5 um, the mix of ice particle
|
|
174
|
+
habits will be from Group 2 (30% solid columns, 70% droxtals)
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
179
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
180
|
+
habit_distributions : npt.NDArray[np.floating]
|
|
181
|
+
Habit weight distributions.
|
|
182
|
+
See :attr:`CocipParams().habit_distributions`
|
|
183
|
+
radius_threshold_um : npt.NDArray[np.floating]
|
|
184
|
+
Radius thresholds for habit distributions.
|
|
185
|
+
See :attr:`CocipParams.radius_threshold_um`
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
npt.NDArray[np.floating]
|
|
190
|
+
Array with shape ``n_waypoints x 8 columns``, where each column is the weights to the ice
|
|
191
|
+
particle habits, [:math:`[0 - 1]`], and the sum of each column should be equal to 1.
|
|
192
|
+
|
|
193
|
+
Raises
|
|
194
|
+
------
|
|
195
|
+
ValueError
|
|
196
|
+
Raises when ``habit_distributions`` do not sum to 1 across columns or
|
|
197
|
+
if there is a size mismatch with ``radius_threshold_um``.
|
|
198
|
+
"""
|
|
199
|
+
# all rows of the habit weights should sum to 1
|
|
200
|
+
if not np.allclose(np.sum(habit_distributions, axis=1), 1.0, atol=1e-3):
|
|
201
|
+
raise ValueError("Habit weight distributions must sum to 1 across columns")
|
|
202
|
+
|
|
203
|
+
if habit_distributions.shape[0] != (radius_threshold_um.size + 1):
|
|
204
|
+
raise ValueError(
|
|
205
|
+
"The number of rows in `habit_distributions` must equal 1 + the "
|
|
206
|
+
"size of `radius_threshold_um`"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# assign ice particle habits for each waypoint
|
|
210
|
+
idx = habit_weight_regime_idx(r_vol_um, radius_threshold_um)
|
|
211
|
+
return habit_distributions[idx]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def habit_weight_regime_idx(
|
|
215
|
+
r_vol_um: npt.NDArray[np.floating], radius_threshold_um: npt.NDArray[np.floating]
|
|
216
|
+
) -> npt.NDArray[np.intp]:
|
|
217
|
+
r"""
|
|
218
|
+
Determine regime of ice particle habits based on contrail ice particle volume mean radius.
|
|
219
|
+
|
|
220
|
+
Parameters
|
|
221
|
+
----------
|
|
222
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
223
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
224
|
+
radius_threshold_um : npt.NDArray[np.floating]
|
|
225
|
+
Radius thresholds for habit distributions.
|
|
226
|
+
See :attr:`CocipParams.radius_threshold_um`
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
npt.NDArray[np.intp]
|
|
231
|
+
Row index of the habit distribution in array :attr:`CocipParams().habit_distributions`
|
|
232
|
+
"""
|
|
233
|
+
# find the regime for each waypoint using thresholds
|
|
234
|
+
idx = np.digitize(r_vol_um, radius_threshold_um)
|
|
235
|
+
|
|
236
|
+
# set any nan values to the "0" type
|
|
237
|
+
idx[np.isnan(r_vol_um)] = 0
|
|
238
|
+
|
|
239
|
+
return idx
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def effective_radius_by_habit(
|
|
243
|
+
r_vol_um: npt.NDArray[np.floating], habit_idx: npt.NDArray[np.intp]
|
|
244
|
+
) -> npt.NDArray[np.floating]:
|
|
245
|
+
r"""Calculate the effective radius ``r_eff_um`` via the mean ice particle radius and habit type.
|
|
246
|
+
|
|
247
|
+
The ``habit_idx`` corresponds to the habit types in ``rf_const.habits``.
|
|
248
|
+
Each habit type has a specific parameterization to calculate ``r_eff_um`` based on ``r_vol_um``.
|
|
249
|
+
derived from :cite:`schumannEffectiveRadiusIce2011`.
|
|
250
|
+
|
|
251
|
+
Parameters
|
|
252
|
+
----------
|
|
253
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
254
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
255
|
+
habit_idx : npt.NDArray[np.intp]
|
|
256
|
+
Habit type index for the contrail ice particle, corresponding to the
|
|
257
|
+
habits in ``rf_const.habits``.
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
npt.NDArray[np.floating]
|
|
262
|
+
Effective radius of ice particles for each combination of ``r_vol_um``
|
|
263
|
+
and ``habit_idx``, [:math:`\mu m`]
|
|
264
|
+
|
|
265
|
+
References
|
|
266
|
+
----------
|
|
267
|
+
- :cite:`schumannEffectiveRadiusIce2011`
|
|
268
|
+
"""
|
|
269
|
+
cond_list = [
|
|
270
|
+
habit_idx == 0,
|
|
271
|
+
habit_idx == 1,
|
|
272
|
+
habit_idx == 2,
|
|
273
|
+
habit_idx == 3,
|
|
274
|
+
habit_idx == 4,
|
|
275
|
+
habit_idx == 5,
|
|
276
|
+
habit_idx == 6,
|
|
277
|
+
habit_idx == 7,
|
|
278
|
+
]
|
|
279
|
+
func_list = [
|
|
280
|
+
effective_radius_sphere,
|
|
281
|
+
effective_radius_solid_column,
|
|
282
|
+
effective_radius_hollow_column,
|
|
283
|
+
effective_radius_rough_aggregate,
|
|
284
|
+
effective_radius_rosette,
|
|
285
|
+
effective_radius_plate,
|
|
286
|
+
effective_radius_droxtal,
|
|
287
|
+
effective_radius_myhre,
|
|
288
|
+
0.0,
|
|
289
|
+
]
|
|
290
|
+
return np.piecewise(r_vol_um, cond_list, func_list)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def effective_radius_sphere(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
294
|
+
r"""
|
|
295
|
+
Calculate the effective radius of contrail ice particles assuming a sphere particle habit.
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
300
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
npt.NDArray[np.floating]
|
|
305
|
+
Effective radius, [:math:`\mu m`]
|
|
306
|
+
"""
|
|
307
|
+
return np.minimum(r_vol_um, 25.0)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def effective_radius_solid_column(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
311
|
+
r"""
|
|
312
|
+
Calculate the effective radius of contrail ice particles assuming a solid column particle habit.
|
|
313
|
+
|
|
314
|
+
Parameters
|
|
315
|
+
----------
|
|
316
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
317
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
npt.NDArray[np.floating]
|
|
322
|
+
Effective radius, [:math:`\mu m`]
|
|
323
|
+
"""
|
|
324
|
+
r_eff_um = (
|
|
325
|
+
0.2588 * np.exp(-(6.912e-3 * r_vol_um)) + 0.6372 * np.exp(-(3.142e-4 * r_vol_um))
|
|
326
|
+
) * r_vol_um
|
|
327
|
+
is_small = r_vol_um <= 42.2
|
|
328
|
+
r_eff_um[is_small] = 0.824 * r_vol_um[is_small]
|
|
329
|
+
return np.minimum(r_eff_um, 45.0)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def effective_radius_hollow_column(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
333
|
+
r"""Calculate the effective radius of ice particles assuming a hollow column particle habit.
|
|
334
|
+
|
|
335
|
+
Parameters
|
|
336
|
+
----------
|
|
337
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
338
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
339
|
+
|
|
340
|
+
Returns
|
|
341
|
+
-------
|
|
342
|
+
npt.NDArray[np.floating]
|
|
343
|
+
Effective radius, [:math:`\mu m`]
|
|
344
|
+
"""
|
|
345
|
+
r_eff_um = (
|
|
346
|
+
0.2281 * np.exp(-(7.359e-3 * r_vol_um)) + 0.5651 * np.exp(-(3.350e-4 * r_vol_um))
|
|
347
|
+
) * r_vol_um
|
|
348
|
+
is_small = r_vol_um <= 39.7
|
|
349
|
+
r_eff_um[is_small] = 0.729 * r_vol_um[is_small]
|
|
350
|
+
return np.minimum(r_eff_um, 45.0)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def effective_radius_rough_aggregate(
|
|
354
|
+
r_vol_um: npt.NDArray[np.floating],
|
|
355
|
+
) -> npt.NDArray[np.floating]:
|
|
356
|
+
r"""Calculate the effective radius of ice particles assuming a rough aggregate particle habit.
|
|
357
|
+
|
|
358
|
+
Parameters
|
|
359
|
+
----------
|
|
360
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
361
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
362
|
+
|
|
363
|
+
Returns
|
|
364
|
+
-------
|
|
365
|
+
npt.NDArray[np.floating]
|
|
366
|
+
Effective radius, [:math:`\mu m`]
|
|
367
|
+
"""
|
|
368
|
+
r_eff_um = 0.574 * r_vol_um
|
|
369
|
+
return np.minimum(r_eff_um, 45.0)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def effective_radius_rosette(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
373
|
+
r"""
|
|
374
|
+
Calculate the effective radius of contrail ice particles assuming a rosette particle habit.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
379
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
380
|
+
|
|
381
|
+
Returns
|
|
382
|
+
-------
|
|
383
|
+
npt.NDArray[np.floating]
|
|
384
|
+
Effective radius, [:math:`\mu m`]
|
|
385
|
+
"""
|
|
386
|
+
r_eff_um = r_vol_um * (
|
|
387
|
+
0.1770 * np.exp(-(2.144e-2 * r_vol_um)) + 0.4267 * np.exp(-(3.562e-4 * r_vol_um))
|
|
388
|
+
)
|
|
389
|
+
return np.minimum(r_eff_um, 45.0)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def effective_radius_plate(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
393
|
+
r"""
|
|
394
|
+
Calculate the effective radius of contrail ice particles assuming a plate particle habit.
|
|
395
|
+
|
|
396
|
+
Parameters
|
|
397
|
+
----------
|
|
398
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
399
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
npt.NDArray[np.floating]
|
|
404
|
+
Effective radius, [:math:`\mu m`]
|
|
405
|
+
"""
|
|
406
|
+
r_eff_um = r_vol_um * (
|
|
407
|
+
0.1663 + 0.3713 * np.exp(-(0.0336 * r_vol_um)) + 0.3309 * np.exp(-(0.0035 * r_vol_um))
|
|
408
|
+
)
|
|
409
|
+
return np.minimum(r_eff_um, 45.0)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def effective_radius_droxtal(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
413
|
+
r"""
|
|
414
|
+
Calculate the effective radius of contrail ice particles assuming a droxtal particle habit.
|
|
415
|
+
|
|
416
|
+
Parameters
|
|
417
|
+
----------
|
|
418
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
419
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
420
|
+
|
|
421
|
+
Returns
|
|
422
|
+
-------
|
|
423
|
+
npt.NDArray[np.floating]
|
|
424
|
+
Effective radius, [:math:`\mu m`]
|
|
425
|
+
"""
|
|
426
|
+
r_eff_um = 0.94 * r_vol_um
|
|
427
|
+
return np.minimum(r_eff_um, 45.0)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def effective_radius_myhre(r_vol_um: npt.NDArray[np.floating]) -> npt.NDArray[np.floating]:
|
|
431
|
+
r"""
|
|
432
|
+
Calculate the effective radius of contrail ice particles assuming a sphere particle habit.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
437
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
438
|
+
|
|
439
|
+
Returns
|
|
440
|
+
-------
|
|
441
|
+
npt.NDArray[np.floating]
|
|
442
|
+
Effective radius, [:math:`\mu m`]
|
|
443
|
+
"""
|
|
444
|
+
return np.minimum(r_vol_um, 45.0)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
# -----------------
|
|
448
|
+
# Radiative Forcing
|
|
449
|
+
# -----------------
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def longwave_radiative_forcing(
|
|
453
|
+
r_vol_um: npt.NDArray[np.floating],
|
|
454
|
+
olr: npt.NDArray[np.floating],
|
|
455
|
+
air_temperature: npt.NDArray[np.floating],
|
|
456
|
+
tau_contrail: npt.NDArray[np.floating],
|
|
457
|
+
tau_cirrus: npt.NDArray[np.floating],
|
|
458
|
+
habit_weights_: npt.NDArray[np.floating],
|
|
459
|
+
r_eff_um: npt.NDArray[np.floating] | None = None,
|
|
460
|
+
) -> npt.NDArray[np.floating]:
|
|
461
|
+
r"""
|
|
462
|
+
Calculate the local contrail longwave radiative forcing (:math:`RF_{LW}`).
|
|
463
|
+
|
|
464
|
+
All returned values are positive.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
469
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
470
|
+
olr : npt.NDArray[np.floating]
|
|
471
|
+
Outgoing longwave radiation at each waypoint, [:math:`W m^{-2}`]
|
|
472
|
+
air_temperature : npt.NDArray[np.floating]
|
|
473
|
+
Ambient temperature at each waypoint, [:math:`K`]
|
|
474
|
+
tau_contrail : npt.NDArray[np.floating]
|
|
475
|
+
Contrail optical depth at each waypoint
|
|
476
|
+
tau_cirrus : npt.NDArray[np.floating]
|
|
477
|
+
Optical depth of numerical weather prediction (NWP) cirrus above the
|
|
478
|
+
contrail at each waypoint
|
|
479
|
+
habit_weights_ : npt.NDArray[np.floating]
|
|
480
|
+
Weights to different ice particle habits for each waypoint,
|
|
481
|
+
``n_waypoints x 8`` (habit) columns, [:math:`[0 - 1]`]
|
|
482
|
+
r_eff_um : npt.NDArray[np.floating] | None, optional
|
|
483
|
+
Provide effective radius corresponding to elements in ``r_vol_um``, [:math:`\mu m`].
|
|
484
|
+
Defaults to None, which means the effective radius will be calculated using ``r_vol_um``
|
|
485
|
+
and habit types in :func:`effective_radius_by_habit`.
|
|
486
|
+
|
|
487
|
+
Returns
|
|
488
|
+
-------
|
|
489
|
+
npt.NDArray[np.floating]
|
|
490
|
+
Local contrail longwave radiative forcing (positive), [:math:`W m^{-2}`]
|
|
491
|
+
|
|
492
|
+
Raises
|
|
493
|
+
------
|
|
494
|
+
ValueError
|
|
495
|
+
If `r_eff_um` and `olr` have different shapes.
|
|
496
|
+
|
|
497
|
+
References
|
|
498
|
+
----------
|
|
499
|
+
- :cite:`schumannParametricRadiativeForcing2012`
|
|
500
|
+
"""
|
|
501
|
+
# get list of habit weight indexs where the weights > 0
|
|
502
|
+
# this is a tuple of (np.array[waypoint index], np.array[habit type index])
|
|
503
|
+
habit_weight_mask = habit_weights_ > 0.0
|
|
504
|
+
idx0, idx1 = np.nonzero(habit_weight_mask)
|
|
505
|
+
|
|
506
|
+
# Convert parametric coefficients for vectorized operations
|
|
507
|
+
delta_t = RF_CONST.delta_t[idx1]
|
|
508
|
+
delta_lc = RF_CONST.delta_lc[idx1]
|
|
509
|
+
delta_lr = RF_CONST.delta_lr[idx1]
|
|
510
|
+
k_t = RF_CONST.k_t[idx1]
|
|
511
|
+
T_0 = RF_CONST.T_0[idx1]
|
|
512
|
+
|
|
513
|
+
olr_h = olr[idx0]
|
|
514
|
+
tau_cirrus_h = tau_cirrus[idx0]
|
|
515
|
+
tau_contrail_h = tau_contrail[idx0]
|
|
516
|
+
air_temperature_h = air_temperature[idx0]
|
|
517
|
+
|
|
518
|
+
# effective radius
|
|
519
|
+
if r_eff_um is None:
|
|
520
|
+
r_vol_um_h = r_vol_um[idx0]
|
|
521
|
+
r_eff_um_h = effective_radius_by_habit(r_vol_um_h, idx1)
|
|
522
|
+
else:
|
|
523
|
+
if r_eff_um.shape != olr.shape:
|
|
524
|
+
raise ValueError(
|
|
525
|
+
"User provided effective radius (`r_eff_um`) must have the same shape as `olr`"
|
|
526
|
+
f" {olr.shape}"
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
r_eff_um_h = r_eff_um[idx0]
|
|
530
|
+
|
|
531
|
+
# Longwave radiation calculations
|
|
532
|
+
e_lw = olr_reduction_natural_cirrus(tau_cirrus_h, delta_lc)
|
|
533
|
+
f_lw = contrail_effective_emissivity(r_eff_um_h, delta_lr)
|
|
534
|
+
|
|
535
|
+
# calculate the RF LW per habit type
|
|
536
|
+
# see eqn (2) in :cite:`schumannParametricRadiativeForcing2012`
|
|
537
|
+
rf_lw_per_habit = (
|
|
538
|
+
(olr_h - k_t * (air_temperature_h - T_0))
|
|
539
|
+
* e_lw
|
|
540
|
+
* (1.0 - np.exp(-delta_t * f_lw * tau_contrail_h))
|
|
541
|
+
)
|
|
542
|
+
rf_lw_per_habit.clip(min=0.0, out=rf_lw_per_habit)
|
|
543
|
+
|
|
544
|
+
# Weight and sum the RF contributions of each habit type according the habit weight
|
|
545
|
+
# regime at the waypoint
|
|
546
|
+
# see eqn (12) in :cite:`schumannParametricRadiativeForcing2012`
|
|
547
|
+
# use fancy indexing to re-assign values to 2d array of waypoint x habit type
|
|
548
|
+
rf_lw_weighted = np.zeros_like(habit_weights_)
|
|
549
|
+
rf_lw_weighted[idx0, idx1] = rf_lw_per_habit * habit_weights_[habit_weight_mask]
|
|
550
|
+
return np.sum(rf_lw_weighted, axis=1)
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
def shortwave_radiative_forcing(
|
|
554
|
+
r_vol_um: npt.NDArray[np.floating],
|
|
555
|
+
sdr: npt.NDArray[np.floating],
|
|
556
|
+
rsr: npt.NDArray[np.floating],
|
|
557
|
+
sd0: npt.NDArray[np.floating],
|
|
558
|
+
tau_contrail: npt.NDArray[np.floating],
|
|
559
|
+
tau_cirrus: npt.NDArray[np.floating],
|
|
560
|
+
habit_weights_: npt.NDArray[np.floating],
|
|
561
|
+
r_eff_um: npt.NDArray[np.floating] | None = None,
|
|
562
|
+
) -> npt.NDArray[np.floating]:
|
|
563
|
+
r"""
|
|
564
|
+
Calculate the local contrail shortwave radiative forcing (:math:`RF_{SW}`).
|
|
565
|
+
|
|
566
|
+
All returned values are negative.
|
|
567
|
+
|
|
568
|
+
Parameters
|
|
569
|
+
----------
|
|
570
|
+
r_vol_um : npt.NDArray[np.floating]
|
|
571
|
+
Contrail ice particle volume mean radius, [:math:`\mu m`]
|
|
572
|
+
sdr : npt.NDArray[np.floating]
|
|
573
|
+
Solar direct radiation, [:math:`W m^{-2}`]
|
|
574
|
+
rsr : npt.NDArray[np.floating]
|
|
575
|
+
Reflected solar radiation, [:math:`W m^{-2}`]
|
|
576
|
+
sd0 : npt.NDArray[np.floating]
|
|
577
|
+
Solar constant, [:math:`W m^{-2}`]
|
|
578
|
+
tau_contrail : npt.NDArray[np.floating]
|
|
579
|
+
Contrail optical depth for each waypoint
|
|
580
|
+
tau_cirrus : npt.NDArray[np.floating]
|
|
581
|
+
Optical depth of numerical weather prediction (NWP) cirrus above the
|
|
582
|
+
contrail for each waypoint.
|
|
583
|
+
habit_weights_ : npt.NDArray[np.floating]
|
|
584
|
+
Weights to different ice particle habits for each waypoint,
|
|
585
|
+
``n_waypoints x 8`` (habit) columns, [:math:`[0 - 1]`]
|
|
586
|
+
r_eff_um : npt.NDArray[np.floating] | None, optional
|
|
587
|
+
Provide effective radius corresponding to elements in ``r_vol_um``, [:math:`\mu m`].
|
|
588
|
+
Defaults to None, which means the effective radius will be calculated using ``r_vol_um``
|
|
589
|
+
and habit types in :func:`effective_radius_by_habit`.
|
|
590
|
+
|
|
591
|
+
Returns
|
|
592
|
+
-------
|
|
593
|
+
npt.NDArray[np.floating]
|
|
594
|
+
Local contrail shortwave radiative forcing (negative), [:math:`W m^{-2}`]
|
|
595
|
+
|
|
596
|
+
Raises
|
|
597
|
+
------
|
|
598
|
+
ValueError
|
|
599
|
+
If `r_eff_um` and `sdr` have different shapes.
|
|
600
|
+
|
|
601
|
+
References
|
|
602
|
+
----------
|
|
603
|
+
- :cite:`schumannParametricRadiativeForcing2012`
|
|
604
|
+
"""
|
|
605
|
+
# create mask for daytime (sdr > 0)
|
|
606
|
+
day = sdr > 0.0
|
|
607
|
+
|
|
608
|
+
# short circuit if no waypoints occur during the day
|
|
609
|
+
if not day.any():
|
|
610
|
+
return np.zeros_like(sdr)
|
|
611
|
+
|
|
612
|
+
# get list of habit weight indexs where the weights > 0
|
|
613
|
+
# this is a tuple of (np.array[waypoint index], np.array[habit type index])
|
|
614
|
+
habit_weight_mask = day.reshape(day.size, 1) & (habit_weights_ > 0.0)
|
|
615
|
+
idx0, idx1 = np.nonzero(habit_weight_mask)
|
|
616
|
+
|
|
617
|
+
# Convert parametric coefficients for vectorized operations
|
|
618
|
+
t_a = RF_CONST.t_a[idx1]
|
|
619
|
+
A_mu = RF_CONST.A_mu[idx1]
|
|
620
|
+
B_mu = RF_CONST.B_mu[idx1]
|
|
621
|
+
C_mu = RF_CONST.C_mu[idx1]
|
|
622
|
+
delta_sr = RF_CONST.delta_sr[idx1]
|
|
623
|
+
F_r = RF_CONST.F_r[idx1]
|
|
624
|
+
gamma_lower = RF_CONST.gamma_lower[idx1]
|
|
625
|
+
gamma_upper = RF_CONST.gamma_upper[idx1]
|
|
626
|
+
delta_sc = RF_CONST.delta_sc[idx1]
|
|
627
|
+
delta_sc_aps = RF_CONST.delta_sc_aps[idx1]
|
|
628
|
+
|
|
629
|
+
sdr_h = sdr[idx0]
|
|
630
|
+
rsr_h = rsr[idx0]
|
|
631
|
+
sd0_h = sd0[idx0]
|
|
632
|
+
tau_contrail_h = tau_contrail[idx0]
|
|
633
|
+
tau_cirrus_h = tau_cirrus[idx0]
|
|
634
|
+
|
|
635
|
+
albedo_ = albedo(sdr_h, rsr_h)
|
|
636
|
+
mue = np.minimum(sdr_h / sd0_h, 1.0)
|
|
637
|
+
|
|
638
|
+
# effective radius
|
|
639
|
+
if r_eff_um is None:
|
|
640
|
+
r_vol_um_h = r_vol_um[idx0]
|
|
641
|
+
r_eff_um_h = effective_radius_by_habit(r_vol_um_h, idx1)
|
|
642
|
+
else:
|
|
643
|
+
if r_eff_um.shape != sdr.shape:
|
|
644
|
+
raise ValueError(
|
|
645
|
+
"User provided effective radius (`r_eff_um`) must have the same shape as `sdr`"
|
|
646
|
+
f" {sdr.shape}"
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
r_eff_um_h = r_eff_um[idx0]
|
|
650
|
+
|
|
651
|
+
# Local contrail shortwave radiative forcing calculations
|
|
652
|
+
alpha_c = contrail_albedo(
|
|
653
|
+
tau_contrail_h,
|
|
654
|
+
mue,
|
|
655
|
+
r_eff_um_h,
|
|
656
|
+
A_mu,
|
|
657
|
+
B_mu,
|
|
658
|
+
C_mu,
|
|
659
|
+
delta_sr,
|
|
660
|
+
F_r,
|
|
661
|
+
gamma_lower,
|
|
662
|
+
gamma_upper,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
e_sw = effective_tau_cirrus(tau_cirrus_h, mue, delta_sc, delta_sc_aps)
|
|
666
|
+
|
|
667
|
+
# calculate the RF SW per habit type
|
|
668
|
+
# see eqn (5) in :cite:`schumannParametricRadiativeForcing2012`
|
|
669
|
+
rf_sw_per_habit = np.minimum(-sdr_h * ((t_a - albedo_) ** 2) * alpha_c * e_sw, 0.0)
|
|
670
|
+
|
|
671
|
+
# Weight and sum the RF contributions of each habit type according the
|
|
672
|
+
# habit weight regime at the waypoint
|
|
673
|
+
# see eqn (12) in :cite:`schumannParametricRadiativeForcing2012`
|
|
674
|
+
# use fancy indexing to re-assign values to 2d array of waypoint x habit type
|
|
675
|
+
rf_sw_weighted = np.zeros_like(habit_weights_)
|
|
676
|
+
rf_sw_weighted[idx0, idx1] = rf_sw_per_habit * habit_weights_[habit_weight_mask]
|
|
677
|
+
|
|
678
|
+
return np.sum(rf_sw_weighted, axis=1)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def net_radiative_forcing(
|
|
682
|
+
rf_lw: npt.NDArray[np.floating], rf_sw: npt.NDArray[np.floating]
|
|
683
|
+
) -> npt.NDArray[np.floating]:
|
|
684
|
+
"""
|
|
685
|
+
Calculate the local contrail net radiative forcing (rf_net).
|
|
686
|
+
|
|
687
|
+
RF Net = Longwave RF (positive) + Shortwave RF (negative)
|
|
688
|
+
|
|
689
|
+
Parameters
|
|
690
|
+
----------
|
|
691
|
+
rf_lw : npt.NDArray[np.floating]
|
|
692
|
+
local contrail longwave radiative forcing, [:math:`W m^{-2}`]
|
|
693
|
+
rf_sw : npt.NDArray[np.floating]
|
|
694
|
+
local contrail shortwave radiative forcing, [:math:`W m^{-2}`]
|
|
695
|
+
|
|
696
|
+
Returns
|
|
697
|
+
-------
|
|
698
|
+
npt.NDArray[np.floating]
|
|
699
|
+
local contrail net radiative forcing, [:math:`W m^{-2}`]
|
|
700
|
+
"""
|
|
701
|
+
return rf_lw + rf_sw
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def olr_reduction_natural_cirrus(
|
|
705
|
+
tau_cirrus: npt.NDArray[np.floating], delta_lc: npt.NDArray[np.floating]
|
|
706
|
+
) -> npt.NDArray[np.floating]:
|
|
707
|
+
"""
|
|
708
|
+
Calculate reduction in outgoing longwave radiation (OLR) due to the presence of natural cirrus.
|
|
709
|
+
|
|
710
|
+
Natural cirrus has optical depth ``tau_cirrus`` above the contrail.
|
|
711
|
+
See ``e_lw`` in Eq. (4) of Schumann et al. (2012).
|
|
712
|
+
|
|
713
|
+
Parameters
|
|
714
|
+
----------
|
|
715
|
+
tau_cirrus : npt.NDArray[np.floating]
|
|
716
|
+
Optical depth of numerical weather prediction (NWP) cirrus above the
|
|
717
|
+
contrail for each waypoint.
|
|
718
|
+
delta_lc : npt.NDArray[np.floating]
|
|
719
|
+
Habit specific parameter to approximate the reduction of the outgoing
|
|
720
|
+
longwave radiation at the contrail level due to natural cirrus above the contrail.
|
|
721
|
+
|
|
722
|
+
Returns
|
|
723
|
+
-------
|
|
724
|
+
npt.NDArray[np.floating]
|
|
725
|
+
Reduction of outgoing longwave radiation
|
|
726
|
+
"""
|
|
727
|
+
# e_lw calculations
|
|
728
|
+
return np.exp(-delta_lc * tau_cirrus)
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def contrail_effective_emissivity(
|
|
732
|
+
r_eff_um: npt.NDArray[np.floating], delta_lr: npt.NDArray[np.floating]
|
|
733
|
+
) -> npt.NDArray[np.floating]:
|
|
734
|
+
r"""Calculate the effective emissivity of the contrail, ``f_lw``.
|
|
735
|
+
|
|
736
|
+
Refer to Eq. (3) of Schumann et al. (2012).
|
|
737
|
+
|
|
738
|
+
Parameters
|
|
739
|
+
----------
|
|
740
|
+
r_eff_um : npt.NDArray[np.floating]
|
|
741
|
+
Effective radius for each waypoint, n_waypoints x 8 (habit) columns, [:math:`\mu m`]
|
|
742
|
+
See :func:`effective_radius_habit`.
|
|
743
|
+
delta_lr : npt.NDArray[np.floating]
|
|
744
|
+
Habit specific parameter to approximate the effective emissivity of the contrail.
|
|
745
|
+
|
|
746
|
+
Returns
|
|
747
|
+
-------
|
|
748
|
+
npt.NDArray[np.floating]
|
|
749
|
+
Effective emissivity of the contrail
|
|
750
|
+
"""
|
|
751
|
+
# f_lw calculations
|
|
752
|
+
return 1.0 - np.exp(-delta_lr * r_eff_um)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def albedo(
|
|
756
|
+
sdr: npt.NDArray[np.floating], rsr: npt.NDArray[np.floating]
|
|
757
|
+
) -> npt.NDArray[np.floating]:
|
|
758
|
+
"""
|
|
759
|
+
Calculate albedo along contrail waypoint.
|
|
760
|
+
|
|
761
|
+
Albedo, the diffuse reflection of solar radiation out of the total solar radiation,
|
|
762
|
+
is computed based on the solar direct radiation (`sdr`) and reflected solar radiation (`rsr`).
|
|
763
|
+
|
|
764
|
+
Output values range between 0 (corresponding to a black body that absorbs
|
|
765
|
+
all incident radiation) and 1 (a body that reflects all incident radiation).
|
|
766
|
+
|
|
767
|
+
Parameters
|
|
768
|
+
----------
|
|
769
|
+
sdr : npt.NDArray[np.floating]
|
|
770
|
+
Solar direct radiation, [:math:`W m^{-2}`]
|
|
771
|
+
rsr : npt.NDArray[np.floating]
|
|
772
|
+
Reflected solar radiation, [:math:`W m^{-2}`]
|
|
773
|
+
|
|
774
|
+
Returns
|
|
775
|
+
-------
|
|
776
|
+
npt.NDArray[np.floating]
|
|
777
|
+
Albedo value, [:math:`[0 - 1]`]
|
|
778
|
+
"""
|
|
779
|
+
day = sdr > 0.0
|
|
780
|
+
albedo_ = np.zeros(sdr.shape)
|
|
781
|
+
albedo_[day] = rsr[day] / sdr[day]
|
|
782
|
+
albedo_.clip(0.0, 1.0, out=albedo_)
|
|
783
|
+
return albedo_
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def contrail_albedo(
|
|
787
|
+
tau_contrail: npt.NDArray[np.floating],
|
|
788
|
+
mue: npt.NDArray[np.floating],
|
|
789
|
+
r_eff_um: npt.NDArray[np.floating],
|
|
790
|
+
A_mu: npt.NDArray[np.floating],
|
|
791
|
+
B_mu: npt.NDArray[np.floating],
|
|
792
|
+
C_mu: npt.NDArray[np.floating],
|
|
793
|
+
delta_sr: npt.NDArray[np.floating],
|
|
794
|
+
F_r: npt.NDArray[np.floating],
|
|
795
|
+
gamma_lower: npt.NDArray[np.floating],
|
|
796
|
+
gamma_upper: npt.NDArray[np.floating],
|
|
797
|
+
) -> npt.NDArray[np.floating]:
|
|
798
|
+
r"""
|
|
799
|
+
Calculate the contrail albedo, ``alpha_c``.
|
|
800
|
+
|
|
801
|
+
Refer to Eq. (6) of Schumann et al. (2012),
|
|
802
|
+
|
|
803
|
+
Parameters
|
|
804
|
+
----------
|
|
805
|
+
tau_contrail : npt.NDArray[np.floating]
|
|
806
|
+
Contrail optical depth for each waypoint
|
|
807
|
+
mue : npt.NDArray[np.floating]
|
|
808
|
+
Cosine of the solar zenith angle (theta), mue = cos(theta) = sdr/sd0
|
|
809
|
+
r_eff_um : npt.NDArray[np.floating]
|
|
810
|
+
Effective radius for each waypoint, n_waypoints x 8 (habit) columns, [:math:`\mu m`]
|
|
811
|
+
See :func:`effective_radius_habit`.
|
|
812
|
+
A_mu : npt.NDArray[np.floating]
|
|
813
|
+
Habit-specific parameter to approximate the albedo of the contrail
|
|
814
|
+
B_mu : npt.NDArray[np.floating]
|
|
815
|
+
Habit-specific parameter to approximate the SZA-dependent contrail sideward scattering
|
|
816
|
+
C_mu : npt.NDArray[np.floating]
|
|
817
|
+
Habit-specific parameter to approximate the albedo of the contrail
|
|
818
|
+
delta_sr : npt.NDArray[np.floating]
|
|
819
|
+
Habit-specific parameter to approximate the effective contrail optical depth
|
|
820
|
+
F_r : npt.NDArray[np.floating]
|
|
821
|
+
Habit-specific parameter to approximate the effective contrail optical depth
|
|
822
|
+
gamma_lower : npt.NDArray[np.floating]
|
|
823
|
+
Habit-specific parameter to approximate the contrail reflectances
|
|
824
|
+
gamma_upper : npt.NDArray[np.floating]
|
|
825
|
+
Habit-specific parameter to approximate the contrail reflectances
|
|
826
|
+
|
|
827
|
+
Returns
|
|
828
|
+
-------
|
|
829
|
+
npt.NDArray[np.floating]
|
|
830
|
+
Contrail albedo for each waypoint and ice particle habit
|
|
831
|
+
"""
|
|
832
|
+
tau_aps = tau_contrail * (1.0 - F_r * (1 - np.exp(-delta_sr * r_eff_um)))
|
|
833
|
+
tau_eff = tau_aps / (mue + 1e-6)
|
|
834
|
+
r_c = 1.0 - np.exp(-gamma_upper * tau_eff)
|
|
835
|
+
r_c_aps = np.exp(-gamma_lower * tau_eff)
|
|
836
|
+
|
|
837
|
+
f_mu = (2.0 * (1.0 - mue)) ** B_mu - 1.0
|
|
838
|
+
return r_c * (C_mu + (A_mu * r_c_aps * f_mu))
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def effective_tau_cirrus(
|
|
842
|
+
tau_cirrus: npt.NDArray[np.floating],
|
|
843
|
+
mue: npt.NDArray[np.floating],
|
|
844
|
+
delta_sc: npt.NDArray[np.floating],
|
|
845
|
+
delta_sc_aps: npt.NDArray[np.floating],
|
|
846
|
+
) -> npt.NDArray[np.floating]:
|
|
847
|
+
r"""
|
|
848
|
+
Calculate the effective optical depth of natural cirrus above the contrail, ``e_sw``.
|
|
849
|
+
|
|
850
|
+
Refer to Eq. (11) of :cite:`schumannParametricRadiativeForcing2012`. See Notes for
|
|
851
|
+
a correction to the equation.
|
|
852
|
+
|
|
853
|
+
Parameters
|
|
854
|
+
----------
|
|
855
|
+
tau_cirrus : npt.NDArray[np.floating]
|
|
856
|
+
Optical depth of numerical weather prediction (NWP) cirrus above the
|
|
857
|
+
contrail for each waypoint.
|
|
858
|
+
mue : npt.NDArray[np.floating]
|
|
859
|
+
Cosine of the solar zenith angle (theta), mue = cos(theta) = sdr/sd0
|
|
860
|
+
delta_sc : npt.NDArray[np.floating]
|
|
861
|
+
Habit-specific parameter to account for the optical depth of natural
|
|
862
|
+
cirrus above the contrail
|
|
863
|
+
delta_sc_aps : npt.NDArray[np.floating]
|
|
864
|
+
Habit-specific parameter to account for the optical depth of natural
|
|
865
|
+
cirrus above the contrail
|
|
866
|
+
|
|
867
|
+
Returns
|
|
868
|
+
-------
|
|
869
|
+
npt.NDArray[np.floating]
|
|
870
|
+
Effective optical depth of natural cirrus above the contrail,
|
|
871
|
+
``n_waypoints x 8`` (habit) columns.
|
|
872
|
+
|
|
873
|
+
Notes
|
|
874
|
+
-----
|
|
875
|
+
- In a personal correspondence, Dr. Schumann identified a print error in Eq. (11) in
|
|
876
|
+
:cite:`schumannParametricRadiativeForcing2012`, where the positions of ``delta_sc_aps``
|
|
877
|
+
and ``delta_sc`` should be swapped. The correct function is provided below.
|
|
878
|
+
"""
|
|
879
|
+
tau_cirrus_eff = tau_cirrus / (mue + 1e-6)
|
|
880
|
+
return np.exp(tau_cirrus * delta_sc_aps - tau_cirrus_eff * delta_sc)
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
# -----------------------------
|
|
884
|
+
# Contrail-contrail overlapping
|
|
885
|
+
# -----------------------------
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
def contrail_contrail_overlap_radiative_effects(
|
|
889
|
+
contrails: GeoVectorDataset,
|
|
890
|
+
habit_distributions: npt.NDArray[np.floating],
|
|
891
|
+
radius_threshold_um: npt.NDArray[np.floating],
|
|
892
|
+
*,
|
|
893
|
+
min_altitude_m: float = 6000.0,
|
|
894
|
+
max_altitude_m: float = 13000.0,
|
|
895
|
+
dz_overlap_m: float = 500.0,
|
|
896
|
+
spatial_grid_res: float = 0.25,
|
|
897
|
+
) -> GeoVectorDataset:
|
|
898
|
+
r"""
|
|
899
|
+
Calculate radiative properties after accounting for contrail overlapping.
|
|
900
|
+
|
|
901
|
+
This function mutates the ``contrails`` parameter.
|
|
902
|
+
|
|
903
|
+
Parameters
|
|
904
|
+
----------
|
|
905
|
+
contrails : GeoVectorDataset
|
|
906
|
+
Contrail waypoints at a given time. Must include the following variables:
|
|
907
|
+
- segment_length
|
|
908
|
+
- width
|
|
909
|
+
- r_ice_vol
|
|
910
|
+
- tau_contrail
|
|
911
|
+
- tau_cirrus
|
|
912
|
+
- air_temperature
|
|
913
|
+
- sdr
|
|
914
|
+
- rsr
|
|
915
|
+
- olr
|
|
916
|
+
|
|
917
|
+
habit_distributions : npt.NDArray[np.floating]
|
|
918
|
+
Habit weight distributions.
|
|
919
|
+
See :attr:`CocipParams.habit_distributions`
|
|
920
|
+
radius_threshold_um : npt.NDArray[np.floating]
|
|
921
|
+
Radius thresholds for habit distributions.
|
|
922
|
+
See :attr:`CocipParams.radius_threshold_um`
|
|
923
|
+
min_altitude_m : float
|
|
924
|
+
Minimum altitude domain in simulation, [:math:`m`]
|
|
925
|
+
See :attr:`CocipParams.min_altitude_m`
|
|
926
|
+
max_altitude_m : float
|
|
927
|
+
Maximum altitude domain in simulation, [:math:`m`]
|
|
928
|
+
See :attr:`CocipParams.min_altitude_m`
|
|
929
|
+
dz_overlap_m : float
|
|
930
|
+
Altitude interval used to segment contrail waypoints, [:math:`m`]
|
|
931
|
+
See :attr:`CocipParams.dz_overlap_m`
|
|
932
|
+
spatial_grid_res : float
|
|
933
|
+
Spatial grid resolution, [:math:`\deg`]
|
|
934
|
+
|
|
935
|
+
Returns
|
|
936
|
+
-------
|
|
937
|
+
GeoVectorDataset
|
|
938
|
+
Contrail waypoints at a given time with additional variables attached, including
|
|
939
|
+
- rsr_overlap
|
|
940
|
+
- olr_overlap
|
|
941
|
+
- tau_cirrus_overlap
|
|
942
|
+
- rf_sw_overlap
|
|
943
|
+
- rf_lw_overlap
|
|
944
|
+
- rf_net_overlap
|
|
945
|
+
|
|
946
|
+
References
|
|
947
|
+
----------
|
|
948
|
+
- Schumann et al. (2021) Air traffic and contrail changes over Europe during COVID-19:
|
|
949
|
+
A model study, Atmos. Chem. Phys., 21, 7429-7450, https://doi.org/10.5194/ACP-21-7429-2021.
|
|
950
|
+
- Teoh et al. (2023) Global aviation contrail climate effects from 2019 to 2021.
|
|
951
|
+
|
|
952
|
+
Notes
|
|
953
|
+
-----
|
|
954
|
+
- The radiative effects of contrail-contrail overlapping is approximated by changing the
|
|
955
|
+
background RSR and OLR fields, and the overlying cirrus optical depth above the contrail.
|
|
956
|
+
- All contrail segments within each altitude interval are treated as one contrail layer, where
|
|
957
|
+
they do not overlap. Contrail layers are processed starting from the bottom to the top.
|
|
958
|
+
- Refer to the Supporting Information (S4.3) of Teoh et al. (2023)
|
|
959
|
+
"""
|
|
960
|
+
assert "segment_length" in contrails
|
|
961
|
+
assert "width" in contrails
|
|
962
|
+
assert "r_ice_vol" in contrails
|
|
963
|
+
assert "tau_contrail" in contrails
|
|
964
|
+
assert "tau_cirrus" in contrails
|
|
965
|
+
assert "air_temperature" in contrails
|
|
966
|
+
assert "sdr" in contrails
|
|
967
|
+
assert "rsr" in contrails
|
|
968
|
+
assert "olr" in contrails
|
|
969
|
+
|
|
970
|
+
if not contrails:
|
|
971
|
+
raise ValueError("Parameter 'contrails' must be non-empty.")
|
|
972
|
+
|
|
973
|
+
time = contrails["time"]
|
|
974
|
+
time0 = time[0]
|
|
975
|
+
if not np.all(time == time0):
|
|
976
|
+
raise ValueError("Contrail waypoints must have a constant time.")
|
|
977
|
+
|
|
978
|
+
longitude = contrails["longitude"]
|
|
979
|
+
latitude = contrails["latitude"]
|
|
980
|
+
altitude = contrails.altitude
|
|
981
|
+
|
|
982
|
+
spatial_bbox = geo.spatial_bounding_box(longitude, latitude)
|
|
983
|
+
west, south, east, north = spatial_bbox
|
|
984
|
+
|
|
985
|
+
assert spatial_grid_res > 0.01
|
|
986
|
+
lon_coords = np.arange(west, east + 0.01, spatial_grid_res)
|
|
987
|
+
lat_coords = np.arange(south, north + 0.01, spatial_grid_res)
|
|
988
|
+
|
|
989
|
+
dims = ["longitude", "latitude", "level", "time"]
|
|
990
|
+
shape = (len(lon_coords), len(lat_coords), 1, 1)
|
|
991
|
+
delta_rad_t = xr.Dataset(
|
|
992
|
+
data_vars={"rsr": (dims, np.zeros(shape)), "olr": (dims, np.zeros(shape))},
|
|
993
|
+
coords={"longitude": lon_coords, "latitude": lat_coords, "level": [-1.0], "time": [time0]},
|
|
994
|
+
)
|
|
995
|
+
|
|
996
|
+
# Initialise radiation fields to store change in background RSR and OLR due to contrails
|
|
997
|
+
rsr_overlap = np.zeros_like(longitude)
|
|
998
|
+
olr_overlap = np.zeros_like(longitude)
|
|
999
|
+
tau_cirrus_overlap = np.zeros_like(longitude)
|
|
1000
|
+
rf_sw_overlap = np.zeros_like(longitude)
|
|
1001
|
+
rf_lw_overlap = np.zeros_like(longitude)
|
|
1002
|
+
rf_net_overlap = np.zeros_like(longitude)
|
|
1003
|
+
|
|
1004
|
+
# Account for contrail overlapping starting from bottom to top layers
|
|
1005
|
+
altitude_layers = np.arange(min_altitude_m, max_altitude_m + 1.0, dz_overlap_m)
|
|
1006
|
+
|
|
1007
|
+
for alt_layer0, alt_layer1 in itertools.pairwise(altitude_layers):
|
|
1008
|
+
is_in_layer = (altitude >= alt_layer0) & (altitude < alt_layer1)
|
|
1009
|
+
|
|
1010
|
+
# Get contrail waypoints at current altitude layer
|
|
1011
|
+
contrails_level = contrails.filter(is_in_layer, copy=True)
|
|
1012
|
+
|
|
1013
|
+
# Skip altitude layer if no contrails are present
|
|
1014
|
+
if not contrails_level:
|
|
1015
|
+
continue
|
|
1016
|
+
|
|
1017
|
+
# Get contrails above altitude layer
|
|
1018
|
+
is_above_layer = (altitude >= alt_layer1) & (altitude <= max_altitude_m)
|
|
1019
|
+
contrails_above = contrails.filter(is_above_layer, copy=True)
|
|
1020
|
+
|
|
1021
|
+
contrails_level = _contrail_optical_depth_above_contrail_layer(
|
|
1022
|
+
contrails_level,
|
|
1023
|
+
contrails_above,
|
|
1024
|
+
spatial_bbox=spatial_bbox,
|
|
1025
|
+
spatial_grid_res=spatial_grid_res,
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
# Calculate updated RSR and OLR with contrail overlapping
|
|
1029
|
+
contrails_level = _rsr_and_olr_with_contrail_overlap(contrails_level, delta_rad_t)
|
|
1030
|
+
|
|
1031
|
+
# Calculate local contrail SW and LW RF with contrail overlapping
|
|
1032
|
+
contrails_level = _local_sw_and_lw_rf_with_contrail_overlap(
|
|
1033
|
+
contrails_level, habit_distributions, radius_threshold_um
|
|
1034
|
+
)
|
|
1035
|
+
|
|
1036
|
+
# Cumulative change in background RSR and OLR fields
|
|
1037
|
+
delta_rad_t = _change_in_background_rsr_and_olr(
|
|
1038
|
+
contrails_level,
|
|
1039
|
+
delta_rad_t,
|
|
1040
|
+
spatial_bbox=spatial_bbox,
|
|
1041
|
+
spatial_grid_res=spatial_grid_res,
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
# Save values
|
|
1045
|
+
rsr_overlap[is_in_layer] = contrails_level["rsr_overlap"]
|
|
1046
|
+
olr_overlap[is_in_layer] = contrails_level["olr_overlap"]
|
|
1047
|
+
tau_cirrus_overlap[is_in_layer] = (
|
|
1048
|
+
contrails_level["tau_cirrus"] + contrails_level["tau_contrails_above"]
|
|
1049
|
+
)
|
|
1050
|
+
rf_sw_overlap[is_in_layer] = contrails_level["rf_sw_overlap"]
|
|
1051
|
+
rf_lw_overlap[is_in_layer] = contrails_level["rf_lw_overlap"]
|
|
1052
|
+
rf_net_overlap[is_in_layer] = contrails_level["rf_net_overlap"]
|
|
1053
|
+
|
|
1054
|
+
# Add new variables to contrails
|
|
1055
|
+
contrails["rsr_overlap"] = rsr_overlap
|
|
1056
|
+
contrails["olr_overlap"] = olr_overlap
|
|
1057
|
+
contrails["tau_cirrus_overlap"] = tau_cirrus_overlap
|
|
1058
|
+
contrails["rf_sw_overlap"] = rf_sw_overlap
|
|
1059
|
+
contrails["rf_lw_overlap"] = rf_lw_overlap
|
|
1060
|
+
contrails["rf_net_overlap"] = rf_net_overlap
|
|
1061
|
+
|
|
1062
|
+
return contrails
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def _contrail_optical_depth_above_contrail_layer(
|
|
1066
|
+
contrails_level: GeoVectorDataset,
|
|
1067
|
+
contrails_above: GeoVectorDataset,
|
|
1068
|
+
spatial_bbox: tuple[float, float, float, float],
|
|
1069
|
+
spatial_grid_res: float,
|
|
1070
|
+
) -> GeoVectorDataset:
|
|
1071
|
+
r"""
|
|
1072
|
+
Calculate the contrail optical depth above the contrail waypoints.
|
|
1073
|
+
|
|
1074
|
+
Parameters
|
|
1075
|
+
----------
|
|
1076
|
+
contrails_level : GeoVectorDataset
|
|
1077
|
+
Contrail waypoints at the current altitude layer.
|
|
1078
|
+
contrails_above : GeoVectorDataset
|
|
1079
|
+
Contrail waypoints above the current altitude layer.
|
|
1080
|
+
spatial_bbox: tuple[float, float, float, float]
|
|
1081
|
+
Spatial bounding box, ``(lon_min, lat_min, lon_max, lat_max)``, [:math:`\deg`]
|
|
1082
|
+
spatial_grid_res : float
|
|
1083
|
+
Spatial grid resolution, [:math:`\deg`]
|
|
1084
|
+
|
|
1085
|
+
Returns
|
|
1086
|
+
-------
|
|
1087
|
+
GeoVectorDataset
|
|
1088
|
+
Contrail waypoints at the current altitude layer with `tau_contrails_above` attached.
|
|
1089
|
+
"""
|
|
1090
|
+
contrails_above["tau_contrails_above"] = (
|
|
1091
|
+
contrails_above["tau_contrail"]
|
|
1092
|
+
* contrails_above["segment_length"]
|
|
1093
|
+
* contrails_above["width"]
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
# Aggregate contrail optical depth to a longitude-latitude grid
|
|
1097
|
+
da = contrails_above.to_lon_lat_grid(
|
|
1098
|
+
agg={"tau_contrails_above": "sum"},
|
|
1099
|
+
spatial_bbox=spatial_bbox,
|
|
1100
|
+
spatial_grid_res=spatial_grid_res,
|
|
1101
|
+
)["tau_contrails_above"]
|
|
1102
|
+
da = da.expand_dims(level=[-1.0], time=[contrails_level["time"][0]])
|
|
1103
|
+
da = da.transpose("longitude", "latitude", "level", "time")
|
|
1104
|
+
|
|
1105
|
+
da_surface_area = geo.grid_surface_area(da["longitude"].values, da["latitude"].values)
|
|
1106
|
+
da = da / da_surface_area
|
|
1107
|
+
mda = MetDataArray(da)
|
|
1108
|
+
|
|
1109
|
+
# Interpolate to contrails_level
|
|
1110
|
+
contrails_level["tau_contrails_above"] = contrails_level.intersect_met(mda)
|
|
1111
|
+
return contrails_level
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
def _rsr_and_olr_with_contrail_overlap(
|
|
1115
|
+
contrails_level: GeoVectorDataset, delta_rad_t: xr.Dataset
|
|
1116
|
+
) -> GeoVectorDataset:
|
|
1117
|
+
"""
|
|
1118
|
+
Calculate RSR and OLR at contrail waypoints after accounting for contrail overlapping.
|
|
1119
|
+
|
|
1120
|
+
Parameters
|
|
1121
|
+
----------
|
|
1122
|
+
contrails_level : GeoVectorDataset
|
|
1123
|
+
Contrail waypoints at the current altitude layer.
|
|
1124
|
+
delta_rad_t : xr.Dataset
|
|
1125
|
+
Radiation fields with cumulative change in RSR and OLR due to contrail overlapping.
|
|
1126
|
+
|
|
1127
|
+
Returns
|
|
1128
|
+
-------
|
|
1129
|
+
GeoVectorDataset
|
|
1130
|
+
Contrail waypoints at the current altitude layer with `rsr_overlap` and
|
|
1131
|
+
`olr_overlap` attached.
|
|
1132
|
+
"""
|
|
1133
|
+
mds = MetDataset(delta_rad_t)
|
|
1134
|
+
|
|
1135
|
+
# Interpolate radiation fields to obtain `rsr_overlap` and `olr_overlap`
|
|
1136
|
+
delta_rsr = contrails_level.intersect_met(mds["rsr"])
|
|
1137
|
+
delta_olr = contrails_level.intersect_met(mds["olr"])
|
|
1138
|
+
|
|
1139
|
+
# Constrain RSR so it is not larger than the SDR
|
|
1140
|
+
contrails_level["rsr_overlap"] = np.minimum(
|
|
1141
|
+
contrails_level["sdr"],
|
|
1142
|
+
contrails_level["rsr"] + delta_rsr,
|
|
1143
|
+
)
|
|
1144
|
+
|
|
1145
|
+
# Constrain OLR so it is not smaller than 80% of the original value
|
|
1146
|
+
contrails_level["olr_overlap"] = np.maximum(
|
|
1147
|
+
0.8 * contrails_level["olr"],
|
|
1148
|
+
contrails_level["olr"] + delta_olr,
|
|
1149
|
+
)
|
|
1150
|
+
return contrails_level
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
def _local_sw_and_lw_rf_with_contrail_overlap(
|
|
1154
|
+
contrails_level: GeoVectorDataset,
|
|
1155
|
+
habit_distributions: npt.NDArray[np.floating],
|
|
1156
|
+
radius_threshold_um: npt.NDArray[np.floating],
|
|
1157
|
+
) -> GeoVectorDataset:
|
|
1158
|
+
"""
|
|
1159
|
+
Calculate local contrail SW and LW RF after accounting for contrail overlapping.
|
|
1160
|
+
|
|
1161
|
+
Parameters
|
|
1162
|
+
----------
|
|
1163
|
+
contrails_level : GeoVectorDataset
|
|
1164
|
+
Contrail waypoints at the current altitude layer.
|
|
1165
|
+
habit_distributions : npt.NDArray[np.floating]
|
|
1166
|
+
Habit weight distributions.
|
|
1167
|
+
See :attr:`CocipParams().habit_distributions`
|
|
1168
|
+
radius_threshold_um : npt.NDArray[np.floating]
|
|
1169
|
+
Radius thresholds for habit distributions.
|
|
1170
|
+
See :attr:`CocipParams.radius_threshold_um`
|
|
1171
|
+
|
|
1172
|
+
Returns
|
|
1173
|
+
-------
|
|
1174
|
+
GeoVectorDataset
|
|
1175
|
+
Contrail waypoints at the current altitude layer with `rf_sw_overlap`,
|
|
1176
|
+
`rf_lw_overlap`, and `rf_net_overlap` attached.
|
|
1177
|
+
"""
|
|
1178
|
+
r_vol_um = contrails_level["r_ice_vol"] * 1e6
|
|
1179
|
+
habit_w = habit_weights(r_vol_um, habit_distributions, radius_threshold_um)
|
|
1180
|
+
|
|
1181
|
+
# Calculate solar constant
|
|
1182
|
+
theta_rad = geo.orbital_position(contrails_level["time"])
|
|
1183
|
+
sd0 = geo.solar_constant(theta_rad)
|
|
1184
|
+
tau_contrail = contrails_level["tau_contrail"]
|
|
1185
|
+
tau_cirrus = contrails_level["tau_cirrus"] + contrails_level["tau_contrails_above"]
|
|
1186
|
+
|
|
1187
|
+
# Calculate local SW and LW RF
|
|
1188
|
+
contrails_level["rf_sw_overlap"] = shortwave_radiative_forcing(
|
|
1189
|
+
r_vol_um,
|
|
1190
|
+
contrails_level["sdr"],
|
|
1191
|
+
contrails_level["rsr_overlap"],
|
|
1192
|
+
sd0,
|
|
1193
|
+
tau_contrail,
|
|
1194
|
+
tau_cirrus,
|
|
1195
|
+
habit_w,
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
contrails_level["rf_lw_overlap"] = longwave_radiative_forcing(
|
|
1199
|
+
r_vol_um,
|
|
1200
|
+
contrails_level["olr_overlap"],
|
|
1201
|
+
contrails_level["air_temperature"],
|
|
1202
|
+
tau_contrail,
|
|
1203
|
+
tau_cirrus,
|
|
1204
|
+
habit_w,
|
|
1205
|
+
)
|
|
1206
|
+
contrails_level["rf_net_overlap"] = (
|
|
1207
|
+
contrails_level["rf_lw_overlap"] + contrails_level["rf_sw_overlap"]
|
|
1208
|
+
)
|
|
1209
|
+
return contrails_level
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
def _change_in_background_rsr_and_olr(
|
|
1213
|
+
contrails_level: GeoVectorDataset,
|
|
1214
|
+
delta_rad_t: xr.Dataset,
|
|
1215
|
+
*,
|
|
1216
|
+
spatial_bbox: tuple[float, float, float, float] = (-180.0, -90.0, 180.0, 90.0),
|
|
1217
|
+
spatial_grid_res: float = 0.5,
|
|
1218
|
+
) -> xr.Dataset:
|
|
1219
|
+
r"""
|
|
1220
|
+
Calculate change in background RSR and OLR fields.
|
|
1221
|
+
|
|
1222
|
+
Parameters
|
|
1223
|
+
----------
|
|
1224
|
+
contrails_level : GeoVectorDataset
|
|
1225
|
+
Contrail waypoints at the current altitude layer.
|
|
1226
|
+
delta_rad_t : xr.Dataset
|
|
1227
|
+
Radiation fields with cumulative change in RSR and OLR due to contrail overlapping.
|
|
1228
|
+
spatial_bbox: tuple[float, float, float, float]
|
|
1229
|
+
Spatial bounding box, ``(lon_min, lat_min, lon_max, lat_max)``, [:math:`\deg`]
|
|
1230
|
+
spatial_grid_res : float
|
|
1231
|
+
Spatial grid resolution, [:math:`\deg`]
|
|
1232
|
+
|
|
1233
|
+
Returns
|
|
1234
|
+
-------
|
|
1235
|
+
xr.Dataset
|
|
1236
|
+
Radiation fields with cumulative change in RSR and OLR due to contrail overlapping.
|
|
1237
|
+
"""
|
|
1238
|
+
# Calculate SW and LW radiative flux (Units: W)
|
|
1239
|
+
segment_length = contrails_level["segment_length"]
|
|
1240
|
+
width = contrails_level["width"]
|
|
1241
|
+
|
|
1242
|
+
contrails_level["sw_radiative_flux"] = (
|
|
1243
|
+
np.abs(contrails_level["rf_sw_overlap"]) * segment_length * width
|
|
1244
|
+
)
|
|
1245
|
+
|
|
1246
|
+
contrails_level["lw_radiative_flux"] = contrails_level["rf_lw_overlap"] * segment_length * width
|
|
1247
|
+
|
|
1248
|
+
# Aggregate SW and LW radiative flux to a longitude-latitude grid
|
|
1249
|
+
ds = contrails_level.to_lon_lat_grid(
|
|
1250
|
+
agg={"sw_radiative_flux": "sum", "lw_radiative_flux": "sum"},
|
|
1251
|
+
spatial_bbox=spatial_bbox,
|
|
1252
|
+
spatial_grid_res=spatial_grid_res,
|
|
1253
|
+
)
|
|
1254
|
+
ds = ds.expand_dims(level=[-1.0], time=[contrails_level["time"][0]])
|
|
1255
|
+
da_surface_area = geo.grid_surface_area(ds["longitude"].values, ds["latitude"].values)
|
|
1256
|
+
|
|
1257
|
+
# Cumulative change in RSR and OLR
|
|
1258
|
+
delta_rad_t["rsr"] += ds["sw_radiative_flux"] / da_surface_area
|
|
1259
|
+
delta_rad_t["olr"] -= ds["lw_radiative_flux"] / da_surface_area
|
|
1260
|
+
return delta_rad_t
|