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,508 @@
|
|
|
1
|
+
"""Wave-vortex downwash functions from Lottermoser & Unterstrasser (2025).
|
|
2
|
+
|
|
3
|
+
Notes
|
|
4
|
+
-----
|
|
5
|
+
:cite:`unterstrasserPropertiesYoungContrails2016` provides a parameterized model of the
|
|
6
|
+
survival fraction of the contrail ice crystal number ``f_surv`` during the wake-vortex phase.
|
|
7
|
+
The model has since been updated in :cite:`lottermoserHighResolutionEarlyContrails2025`. This update
|
|
8
|
+
improves the goodness-of-fit between the parameterised model and LES, and expands the parameter
|
|
9
|
+
space and can now be used for very low and very high soot inputs, different fuel types (where the
|
|
10
|
+
EI H2Os are different), and higher ambient temperatures (up to 235 K) to accomodate for contrails
|
|
11
|
+
formed by liquid hydrogen aircraft. The model was developed based on output from large eddy
|
|
12
|
+
simulations, and improves agreement with LES outputs relative to the default survival fraction
|
|
13
|
+
parameterization used in CoCiP.
|
|
14
|
+
|
|
15
|
+
For comparison, CoCiP assumes that ``f_surv`` is equal to the change in the contrail ice water
|
|
16
|
+
content (by mass) before and after the wake vortex phase. However, for larger (smaller) ice
|
|
17
|
+
particles, their survival fraction by number could be smaller (larger) than their survival fraction
|
|
18
|
+
by mass. This is particularly important in the "soot-poor" scenario, for example, in cleaner
|
|
19
|
+
lean-burn engines where their soot emissions can be 3-4 orders of magnitude lower than conventional
|
|
20
|
+
RQL engines.
|
|
21
|
+
|
|
22
|
+
ADD CITATION TO BIBTEX: :cite:`lottermoserHighResolutionEarlyContrails2025`
|
|
23
|
+
Lottermoser, A. and Unterstraßer, S.: High-resolution modelling of early contrail evolution from
|
|
24
|
+
hydrogen-powered aircraft, EGUsphere [preprint], https://doi.org/10.5194/egusphere-2024-3859, 2025.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
import numpy.typing as npt
|
|
31
|
+
|
|
32
|
+
from pycontrails.models.cocip.wake_vortex import wake_vortex_separation
|
|
33
|
+
from pycontrails.physics import constants, thermo
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ice_particle_number_survival_fraction(
|
|
37
|
+
air_temperature: npt.NDArray[np.floating],
|
|
38
|
+
rhi_0: npt.NDArray[np.floating],
|
|
39
|
+
ei_h2o: npt.NDArray[np.floating] | float,
|
|
40
|
+
wingspan: npt.NDArray[np.floating] | float,
|
|
41
|
+
true_airspeed: npt.NDArray[np.floating],
|
|
42
|
+
fuel_flow: npt.NDArray[np.floating],
|
|
43
|
+
aei_n: npt.NDArray[np.floating],
|
|
44
|
+
z_desc: npt.NDArray[np.floating],
|
|
45
|
+
*,
|
|
46
|
+
analytical_solution: bool = True,
|
|
47
|
+
) -> npt.NDArray[np.floating]:
|
|
48
|
+
r"""
|
|
49
|
+
Calculate fraction of ice particle number surviving the wake vortex phase and required inputs.
|
|
50
|
+
|
|
51
|
+
This implementation is based on the work of :cite:`unterstrasserPropertiesYoungContrails2016`
|
|
52
|
+
and is an improved estimation compared with
|
|
53
|
+
:func:`contrail_properties.ice_particle_survival_fraction`.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
air_temperature : npt.NDArray[np.floating]
|
|
58
|
+
ambient temperature for each waypoint, [:math:`K`]
|
|
59
|
+
rhi_0: npt.NDArray[np.floating]
|
|
60
|
+
Relative humidity with respect to ice at the flight waypoint
|
|
61
|
+
ei_h2o : npt.NDArray[np.floating] | float
|
|
62
|
+
Emission index of water vapor, [:math:`kg \ kg^{-1}`]
|
|
63
|
+
wingspan : npt.NDArray[np.floating] | float
|
|
64
|
+
aircraft wingspan, [:math:`m`]
|
|
65
|
+
true_airspeed : npt.NDArray[np.floating]
|
|
66
|
+
true airspeed for each waypoint, [:math:`m s^{-1}`]
|
|
67
|
+
fuel_flow : npt.NDArray[np.floating]
|
|
68
|
+
Fuel mass flow rate, [:math:`kg s^{-1}`]
|
|
69
|
+
aei_n : npt.NDArray[np.floating]
|
|
70
|
+
Apparent ice crystal number emissions index at contrail formation, [:math:`kg^{-1}`]
|
|
71
|
+
z_desc : npt.NDArray[np.floating]
|
|
72
|
+
Final vertical displacement of the wake vortex, ``dz_max`` in :mod:`wake_vortex.py`,
|
|
73
|
+
[:math:`m`].
|
|
74
|
+
analytical_solution : bool
|
|
75
|
+
Use analytical solution to calculate ``z_atm`` and ``z_emit`` instead of numerical solution.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
npt.NDArray[np.floating]
|
|
80
|
+
Fraction of contrail ice particle number that survive the wake vortex phase.
|
|
81
|
+
|
|
82
|
+
References
|
|
83
|
+
----------
|
|
84
|
+
- :cite:`unterstrasserPropertiesYoungContrails2016`
|
|
85
|
+
- :cite:`lottermoserHighResolutionEarlyContrails2025`
|
|
86
|
+
|
|
87
|
+
Notes
|
|
88
|
+
-----
|
|
89
|
+
- For consistency in CoCiP, ``z_desc`` should be calculated using :func:`dz_max` instead of
|
|
90
|
+
using :func:`z_desc_length_scale`.
|
|
91
|
+
"""
|
|
92
|
+
rho_emit = emitted_water_vapour_concentration(ei_h2o, wingspan, true_airspeed, fuel_flow)
|
|
93
|
+
|
|
94
|
+
# Length scales
|
|
95
|
+
if analytical_solution:
|
|
96
|
+
z_atm = z_atm_length_scale_analytical(air_temperature, rhi_0)
|
|
97
|
+
z_emit = z_emit_length_scale_analytical(rho_emit, air_temperature)
|
|
98
|
+
|
|
99
|
+
else:
|
|
100
|
+
z_atm = z_atm_length_scale_numerical(air_temperature, rhi_0)
|
|
101
|
+
z_emit = z_emit_length_scale_numerical(rho_emit, air_temperature)
|
|
102
|
+
|
|
103
|
+
z_total = z_total_length_scale(z_atm, z_emit, z_desc, true_airspeed, fuel_flow, aei_n, wingspan)
|
|
104
|
+
return _survival_fraction_from_length_scale(z_total)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def z_total_length_scale(
|
|
108
|
+
z_atm: npt.NDArray[np.floating],
|
|
109
|
+
z_emit: npt.NDArray[np.floating],
|
|
110
|
+
z_desc: npt.NDArray[np.floating],
|
|
111
|
+
true_airspeed: npt.NDArray[np.floating],
|
|
112
|
+
fuel_flow: npt.NDArray[np.floating],
|
|
113
|
+
aei_n: npt.NDArray[np.floating],
|
|
114
|
+
wingspan: npt.NDArray[np.floating] | float,
|
|
115
|
+
) -> npt.NDArray[np.floating]:
|
|
116
|
+
"""
|
|
117
|
+
Calculate the total length-scale effect of the wake vortex downwash.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
z_atm : npt.NDArray[np.floating]
|
|
122
|
+
Length-scale effect of ambient supersaturation on the ice crystal mass budget, [:math:`m`]
|
|
123
|
+
z_emit : npt.NDArray[np.floating]
|
|
124
|
+
Length-scale effect of water vapour emissions on the ice crystal mass budget, [:math:`m`]
|
|
125
|
+
z_desc : npt.NDArray[np.floating]
|
|
126
|
+
Final vertical displacement of the wake vortex, `dz_max` in `wake_vortex.py`, [:math:`m`]
|
|
127
|
+
true_airspeed : npt.NDArray[np.floating]
|
|
128
|
+
true airspeed for each waypoint, [:math:`m s^{-1}`]
|
|
129
|
+
fuel_flow : npt.NDArray[np.floating]
|
|
130
|
+
Fuel mass flow rate, [:math:`kg s^{-1}`]
|
|
131
|
+
aei_n : npt.NDArray[np.floating]
|
|
132
|
+
Apparent ice crystal number emissions index at contrail formation, [:math:`kg^{-1}`]
|
|
133
|
+
wingspan : npt.NDArray[np.floating] | float
|
|
134
|
+
aircraft wingspan, [:math:`m`]
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
npt.NDArray[np.floating]
|
|
139
|
+
Total length-scale effect of the wake vortex downwash, [:math:`m`]
|
|
140
|
+
|
|
141
|
+
Notes
|
|
142
|
+
-----
|
|
143
|
+
- For `psi`, see Appendix A1 in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
144
|
+
- For `z_total`, see Eq. (9) and (10) in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
145
|
+
"""
|
|
146
|
+
# Calculate psi term
|
|
147
|
+
fuel_dist = fuel_flow / true_airspeed # Units: [:math:`kg m^{-1}`]
|
|
148
|
+
n_ice_dist = fuel_dist * aei_n # Units: [:math:`m^{-1}`]
|
|
149
|
+
|
|
150
|
+
n_ice_per_vol = n_ice_dist / plume_area(wingspan) # Units: [:math:`m^{-3}`]
|
|
151
|
+
n_ice_per_vol_ref = 3.38e12 / plume_area(60.3)
|
|
152
|
+
|
|
153
|
+
psi = (n_ice_per_vol_ref / n_ice_per_vol) ** 0.16
|
|
154
|
+
|
|
155
|
+
# Calculate total length-scale effect
|
|
156
|
+
return psi * (1.27 * z_atm + 0.42 * z_emit) - 0.49 * z_desc
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def z_atm_length_scale_analytical(
|
|
160
|
+
air_temperature: npt.NDArray[np.floating],
|
|
161
|
+
rhi_0: npt.NDArray[np.floating],
|
|
162
|
+
) -> npt.NDArray[np.floating]:
|
|
163
|
+
"""Calculate the length-scale effect of ambient supersaturation on the ice crystal mass budget.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
air_temperature : npt.NDArray[np.floating]
|
|
168
|
+
Ambient temperature for each waypoint, [:math:`K`].
|
|
169
|
+
rhi_0 : npt.NDArray[np.floating]
|
|
170
|
+
Relative humidity with respect to ice at the flight waypoint.
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
npt.NDArray[np.floating]
|
|
175
|
+
The effect of the ambient supersaturation on the ice crystal mass budget,
|
|
176
|
+
provided as a length scale equivalent, estimated with analytical fit [:math:`m`].
|
|
177
|
+
|
|
178
|
+
Notes
|
|
179
|
+
-----
|
|
180
|
+
- See Eq. (A2) in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
181
|
+
"""
|
|
182
|
+
z_atm = np.zeros_like(rhi_0)
|
|
183
|
+
|
|
184
|
+
# Only perform operation when the ambient condition is supersaturated w.r.t. ice
|
|
185
|
+
issr = rhi_0 > 1.0
|
|
186
|
+
|
|
187
|
+
s_i = rhi_0 - 1.0
|
|
188
|
+
z_atm[issr] = 607.46 * s_i[issr] ** 0.897 * (air_temperature[issr] / 205.0) ** 2.225
|
|
189
|
+
return z_atm
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def z_atm_length_scale_numerical(
|
|
193
|
+
air_temperature: npt.NDArray[np.floating],
|
|
194
|
+
rhi_0: npt.NDArray[np.floating],
|
|
195
|
+
*,
|
|
196
|
+
n_iter: int = 10,
|
|
197
|
+
) -> npt.NDArray[np.floating]:
|
|
198
|
+
"""Calculate the length-scale effect of ambient supersaturation on the ice crystal mass budget.
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
air_temperature : npt.NDArray[np.floating]
|
|
203
|
+
Ambient temperature for each waypoint, [:math:`K`].
|
|
204
|
+
rhi_0 : npt.NDArray[np.floating]
|
|
205
|
+
Relative humidity with respect to ice at the flight waypoint.
|
|
206
|
+
n_iter : int
|
|
207
|
+
Number of iterations, set to 10 as default where ``z_atm`` is accurate to within +-1 m.
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
npt.NDArray[np.floating]
|
|
212
|
+
The effect of the ambient supersaturation on the ice crystal mass budget,
|
|
213
|
+
provided as a length scale equivalent, estimated with numerical methods [:math:`m`].
|
|
214
|
+
|
|
215
|
+
Notes
|
|
216
|
+
-----
|
|
217
|
+
- See Eq. (6) in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
218
|
+
"""
|
|
219
|
+
# Only perform operation when the ambient condition is supersaturated w.r.t. ice
|
|
220
|
+
issr = rhi_0 > 1.0
|
|
221
|
+
|
|
222
|
+
rhi_issr = rhi_0[issr]
|
|
223
|
+
air_temperature_issr = air_temperature[issr]
|
|
224
|
+
|
|
225
|
+
# Solve non-linear equation numerically using the bisection method
|
|
226
|
+
# Did not use scipy functions because it is unstable when dealing with np.arrays
|
|
227
|
+
z_1 = np.zeros_like(rhi_issr)
|
|
228
|
+
z_2 = np.full_like(rhi_issr, 1000.0)
|
|
229
|
+
lhs = rhi_issr * thermo.e_sat_ice(air_temperature_issr) / (air_temperature_issr**3.5)
|
|
230
|
+
|
|
231
|
+
dry_adiabatic_lapse_rate = constants.g / constants.c_pd
|
|
232
|
+
for _ in range(n_iter):
|
|
233
|
+
z_est = 0.5 * (z_1 + z_2)
|
|
234
|
+
rhs = (thermo.e_sat_ice(air_temperature_issr + dry_adiabatic_lapse_rate * z_est)) / (
|
|
235
|
+
air_temperature_issr + dry_adiabatic_lapse_rate * z_est
|
|
236
|
+
) ** 3.5
|
|
237
|
+
z_1[lhs > rhs] = z_est[lhs > rhs]
|
|
238
|
+
z_2[lhs < rhs] = z_est[lhs < rhs]
|
|
239
|
+
|
|
240
|
+
out = np.zeros_like(rhi_0)
|
|
241
|
+
out[issr] = 0.5 * (z_1 + z_2)
|
|
242
|
+
return out
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def emitted_water_vapour_concentration(
|
|
246
|
+
ei_h2o: npt.NDArray[np.floating] | float,
|
|
247
|
+
wingspan: npt.NDArray[np.floating] | float,
|
|
248
|
+
true_airspeed: npt.NDArray[np.floating],
|
|
249
|
+
fuel_flow: npt.NDArray[np.floating],
|
|
250
|
+
) -> npt.NDArray[np.floating]:
|
|
251
|
+
r"""
|
|
252
|
+
Calculate aircraft-emitted water vapour concentration in the plume.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
ei_h2o : npt.NDArray[np.floating] | float
|
|
257
|
+
Emission index of water vapor, [:math:`kg \ kg^{-1}`]
|
|
258
|
+
wingspan : npt.NDArray[np.floating] | float
|
|
259
|
+
aircraft wingspan, [:math:`m`]
|
|
260
|
+
true_airspeed : npt.NDArray[np.floating]
|
|
261
|
+
true airspeed for each waypoint, [:math:`m s^{-1}`]
|
|
262
|
+
fuel_flow : npt.NDArray[np.floating]
|
|
263
|
+
Fuel mass flow rate, [:math:`kg s^{-1}`]
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
npt.NDArray[np.floating]
|
|
268
|
+
Aircraft-emitted water vapour concentration in the plume, [:math:`kg m^{-3}`]
|
|
269
|
+
|
|
270
|
+
Notes
|
|
271
|
+
-----
|
|
272
|
+
- See eq. (6) and (A8) in :cite:`unterstrasserPropertiesYoungContrails2016`.
|
|
273
|
+
"""
|
|
274
|
+
h2o_per_dist = (ei_h2o * fuel_flow) / true_airspeed
|
|
275
|
+
area_p = plume_area(wingspan)
|
|
276
|
+
return h2o_per_dist / area_p
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def z_emit_length_scale_analytical(
|
|
280
|
+
rho_emit: npt.NDArray[np.floating],
|
|
281
|
+
air_temperature: npt.NDArray[np.floating],
|
|
282
|
+
) -> npt.NDArray[np.floating]:
|
|
283
|
+
"""Calculate the length-scale effect of water vapour emissions on the ice crystal mass budget.
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
rho_emit : npt.NDArray[np.floating]
|
|
288
|
+
Aircraft-emitted water vapour concentration in the plume, [:math:`kg m^{-3}`]
|
|
289
|
+
air_temperature : npt.NDArray[np.floating]
|
|
290
|
+
ambient temperature for each waypoint, [:math:`K`]
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
npt.NDArray[np.floating]
|
|
295
|
+
The effect of the aircraft water vapour emission on the ice crystal mass budget,
|
|
296
|
+
provided as a length scale equivalent, estimated with analytical fit [:math:`m`]
|
|
297
|
+
|
|
298
|
+
Notes
|
|
299
|
+
-----
|
|
300
|
+
- See Eq. (A3) in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
301
|
+
"""
|
|
302
|
+
t_205 = air_temperature - 205.0
|
|
303
|
+
return (
|
|
304
|
+
1106.6
|
|
305
|
+
* ((rho_emit * 1e5) ** (0.678 + 0.0116 * t_205))
|
|
306
|
+
* np.exp(-(0.0807 + 0.000428 * t_205) * t_205)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def z_emit_length_scale_numerical(
|
|
311
|
+
rho_emit: npt.NDArray[np.floating],
|
|
312
|
+
air_temperature: npt.NDArray[np.floating],
|
|
313
|
+
*,
|
|
314
|
+
n_iter: int = 10,
|
|
315
|
+
) -> npt.NDArray[np.floating]:
|
|
316
|
+
"""Calculate the length-scale effect of water vapour emissions on the ice crystal mass budget.
|
|
317
|
+
|
|
318
|
+
Parameters
|
|
319
|
+
----------
|
|
320
|
+
rho_emit : npt.NDArray[np.floating]
|
|
321
|
+
Aircraft-emitted water vapour concentration in the plume, [:math:`kg m^{-3}`]
|
|
322
|
+
air_temperature : npt.NDArray[np.floating]
|
|
323
|
+
ambient temperature for each waypoint, [:math:`K`]
|
|
324
|
+
n_iter : int
|
|
325
|
+
Number of iterations, set to 10 as default where ``z_emit`` is accurate to within +-1 m.
|
|
326
|
+
|
|
327
|
+
Returns
|
|
328
|
+
-------
|
|
329
|
+
npt.NDArray[np.floating]
|
|
330
|
+
The effect of the aircraft water vapour emission on the ice crystal mass budget,
|
|
331
|
+
provided as a length scale equivalent, estimated with numerical methods [:math:`m`]
|
|
332
|
+
|
|
333
|
+
Notes
|
|
334
|
+
-----
|
|
335
|
+
- See Eq. (7) in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
336
|
+
"""
|
|
337
|
+
# Solve non-linear equation numerically using the bisection method
|
|
338
|
+
# Did not use scipy functions because it is unstable when dealing with np.arrays
|
|
339
|
+
z_1 = np.zeros_like(rho_emit)
|
|
340
|
+
z_2 = np.full_like(rho_emit, 1000.0)
|
|
341
|
+
|
|
342
|
+
lhs = (thermo.e_sat_ice(air_temperature) / (constants.R_v * air_temperature**3.5)) + (
|
|
343
|
+
rho_emit / (air_temperature**2.5)
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
dry_adiabatic_lapse_rate = constants.g / constants.c_pd
|
|
347
|
+
for _ in range(n_iter):
|
|
348
|
+
z_est = 0.5 * (z_1 + z_2)
|
|
349
|
+
rhs = thermo.e_sat_ice(air_temperature + dry_adiabatic_lapse_rate * z_est) / (
|
|
350
|
+
constants.R_v * (air_temperature + dry_adiabatic_lapse_rate * z_est) ** 3.5
|
|
351
|
+
)
|
|
352
|
+
z_1[lhs > rhs] = z_est[lhs > rhs]
|
|
353
|
+
z_2[lhs < rhs] = z_est[lhs < rhs]
|
|
354
|
+
|
|
355
|
+
return 0.5 * (z_1 + z_2)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def plume_area(wingspan: npt.NDArray[np.floating] | float) -> npt.NDArray[np.floating] | float:
|
|
359
|
+
"""Calculate area of the wake-vortex plume.
|
|
360
|
+
|
|
361
|
+
Parameters
|
|
362
|
+
----------
|
|
363
|
+
wingspan : npt.NDArray[np.floating] | float
|
|
364
|
+
aircraft wingspan, [:math:`m`]
|
|
365
|
+
|
|
366
|
+
Returns
|
|
367
|
+
-------
|
|
368
|
+
npt.NDArray[np.floating] | float
|
|
369
|
+
Area of two wake-vortex plumes, [:math:`m^{2}`]
|
|
370
|
+
|
|
371
|
+
Notes
|
|
372
|
+
-----
|
|
373
|
+
- See Appendix A2 in eq. (A6) and (A7) in :cite:`lottermoserHighResolutionEarlyContrails2025`.
|
|
374
|
+
"""
|
|
375
|
+
r_plume = 1.5 + 0.314 * wingspan
|
|
376
|
+
return 2.0 * np.pi * r_plume**2
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def z_desc_length_scale(
|
|
380
|
+
wingspan: npt.NDArray[np.floating] | float,
|
|
381
|
+
air_temperature: npt.NDArray[np.floating],
|
|
382
|
+
air_pressure: npt.NDArray[np.floating],
|
|
383
|
+
true_airspeed: npt.NDArray[np.floating],
|
|
384
|
+
aircraft_mass: npt.NDArray[np.floating],
|
|
385
|
+
dT_dz: npt.NDArray[np.floating],
|
|
386
|
+
) -> npt.NDArray[np.floating]:
|
|
387
|
+
"""Calculate the final vertical displacement of the wake vortex.
|
|
388
|
+
|
|
389
|
+
Parameters
|
|
390
|
+
----------
|
|
391
|
+
wingspan : npt.NDArray[np.floating] | float
|
|
392
|
+
aircraft wingspan, [:math:`m`]
|
|
393
|
+
air_temperature : npt.NDArray[np.floating]
|
|
394
|
+
ambient temperature for each waypoint, [:math:`K`]
|
|
395
|
+
air_pressure : npt.NDArray[np.floating]
|
|
396
|
+
pressure altitude at each waypoint, [:math:`Pa`]
|
|
397
|
+
true_airspeed : npt.NDArray[np.floating]
|
|
398
|
+
true airspeed for each waypoint, [:math:`m s^{-1}`]
|
|
399
|
+
aircraft_mass : npt.NDArray[np.floating]
|
|
400
|
+
aircraft mass for each waypoint, [:math:`kg`]
|
|
401
|
+
dT_dz : npt.NDArray[np.floating]
|
|
402
|
+
potential temperature gradient, [:math:`K m^{-1}`]
|
|
403
|
+
|
|
404
|
+
Returns
|
|
405
|
+
-------
|
|
406
|
+
npt.NDArray[np.floating]
|
|
407
|
+
Final vertical displacement of the wake vortex, [:math:`m`]
|
|
408
|
+
|
|
409
|
+
Notes
|
|
410
|
+
-----
|
|
411
|
+
- See eq. (4) in :cite:`unterstrasserPropertiesYoungContrails2016`.
|
|
412
|
+
"""
|
|
413
|
+
gamma_0 = _initial_wake_vortex_circulation(
|
|
414
|
+
wingspan, air_temperature, air_pressure, true_airspeed, aircraft_mass
|
|
415
|
+
)
|
|
416
|
+
n_bv = thermo.brunt_vaisala_frequency(air_pressure, air_temperature, dT_dz)
|
|
417
|
+
return ((8.0 * gamma_0) / (np.pi * n_bv)) ** 0.5
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _initial_wake_vortex_circulation(
|
|
421
|
+
wingspan: npt.NDArray[np.floating] | float,
|
|
422
|
+
air_temperature: npt.NDArray[np.floating],
|
|
423
|
+
air_pressure: npt.NDArray[np.floating],
|
|
424
|
+
true_airspeed: npt.NDArray[np.floating],
|
|
425
|
+
aircraft_mass: npt.NDArray[np.floating],
|
|
426
|
+
) -> npt.NDArray[np.floating]:
|
|
427
|
+
"""Calculate initial wake vortex circulation.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
wingspan : npt.NDArray[np.floating] | float
|
|
432
|
+
aircraft wingspan, [:math:`m`]
|
|
433
|
+
air_temperature : npt.NDArray[np.floating]
|
|
434
|
+
ambient temperature for each waypoint, [:math:`K`]
|
|
435
|
+
air_pressure : npt.NDArray[np.floating]
|
|
436
|
+
pressure altitude at each waypoint, [:math:`Pa`]
|
|
437
|
+
true_airspeed : npt.NDArray[np.floating]
|
|
438
|
+
true airspeed for each waypoint, [:math:`m s^{-1}`]
|
|
439
|
+
aircraft_mass : npt.NDArray[np.floating]
|
|
440
|
+
aircraft mass for each waypoint, [:math:`kg`]
|
|
441
|
+
|
|
442
|
+
Returns
|
|
443
|
+
-------
|
|
444
|
+
npt.NDArray[np.floating]
|
|
445
|
+
Initial wake vortex circulation, [:math:`m^{2} s^{-1}`]
|
|
446
|
+
|
|
447
|
+
Notes
|
|
448
|
+
-----
|
|
449
|
+
- This is a measure of the strength/intensity of the wake vortex circulation.
|
|
450
|
+
- See eq. (A1) in :cite:`unterstrasserPropertiesYoungContrails2016`.
|
|
451
|
+
"""
|
|
452
|
+
b_0 = wake_vortex_separation(wingspan)
|
|
453
|
+
rho_air = thermo.rho_d(air_temperature, air_pressure)
|
|
454
|
+
return (constants.g * aircraft_mass) / (rho_air * b_0 * true_airspeed)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _survival_fraction_from_length_scale(
|
|
458
|
+
z_total: npt.NDArray[np.floating],
|
|
459
|
+
) -> npt.NDArray[np.floating]:
|
|
460
|
+
"""
|
|
461
|
+
Calculate fraction of ice particle number surviving the wake vortex phase.
|
|
462
|
+
|
|
463
|
+
Parameters
|
|
464
|
+
----------
|
|
465
|
+
z_total : npt.NDArray[np.floating]
|
|
466
|
+
Total length-scale effect of the wake vortex downwash, [:math:`m`]
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
npt.NDArray[np.floating]
|
|
471
|
+
Fraction of ice particle number surviving the wake vortex phase
|
|
472
|
+
"""
|
|
473
|
+
f_surv = 0.42 + (1.31 / np.pi) * np.arctan(-1.00 + (z_total / 100.0))
|
|
474
|
+
np.clip(f_surv, 0.0, 1.0, out=f_surv)
|
|
475
|
+
return f_surv
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def initial_contrail_depth(
|
|
479
|
+
z_desc: npt.NDArray[np.floating],
|
|
480
|
+
f_surv: npt.NDArray[np.floating],
|
|
481
|
+
) -> npt.NDArray[np.floating]:
|
|
482
|
+
"""Calculate initial contrail depth using :cite:`unterstrasserPropertiesYoungContrails2016`.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
z_desc : npt.NDArray[np.floating]
|
|
487
|
+
Final vertical displacement of the wake vortex, ``dz_max`` in :mod:`wake_vortex.py`,
|
|
488
|
+
[:math:`m`].
|
|
489
|
+
f_surv : npt.NDArray[np.floating]
|
|
490
|
+
Fraction of contrail ice particle number that survive the wake vortex phase.
|
|
491
|
+
See :func:`ice_particle_survival_fraction`.
|
|
492
|
+
|
|
493
|
+
Returns
|
|
494
|
+
-------
|
|
495
|
+
npt.NDArray[np.floating]
|
|
496
|
+
Initial contrail depth, [:math:`m`]
|
|
497
|
+
|
|
498
|
+
Notes
|
|
499
|
+
-----
|
|
500
|
+
- See eq. (12), and (13) in :cite:`unterstrasserPropertiesYoungContrails2016`.
|
|
501
|
+
- For consistency in CoCiP, `z_desc` should be calculated using :func:`dz_max` instead of
|
|
502
|
+
using :func:`z_desc_length_scale`.
|
|
503
|
+
"""
|
|
504
|
+
return z_desc * np.where(
|
|
505
|
+
f_surv <= 0.2,
|
|
506
|
+
6.0 * f_surv,
|
|
507
|
+
0.15 * f_surv + (6.0 - 0.15) * 0.2,
|
|
508
|
+
)
|