pycontrails 0.53.0__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_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.

Files changed (109) hide show
  1. pycontrails/__init__.py +70 -0
  2. pycontrails/_version.py +16 -0
  3. pycontrails/core/__init__.py +30 -0
  4. pycontrails/core/aircraft_performance.py +641 -0
  5. pycontrails/core/airports.py +226 -0
  6. pycontrails/core/cache.py +881 -0
  7. pycontrails/core/coordinates.py +174 -0
  8. pycontrails/core/fleet.py +470 -0
  9. pycontrails/core/flight.py +2312 -0
  10. pycontrails/core/flightplan.py +220 -0
  11. pycontrails/core/fuel.py +140 -0
  12. pycontrails/core/interpolation.py +721 -0
  13. pycontrails/core/met.py +2833 -0
  14. pycontrails/core/met_var.py +307 -0
  15. pycontrails/core/models.py +1181 -0
  16. pycontrails/core/polygon.py +549 -0
  17. pycontrails/core/rgi_cython.cpython-313-x86_64-linux-gnu.so +0 -0
  18. pycontrails/core/vector.py +2191 -0
  19. pycontrails/datalib/__init__.py +12 -0
  20. pycontrails/datalib/_leo_utils/search.py +250 -0
  21. pycontrails/datalib/_leo_utils/static/bq_roi_query.sql +6 -0
  22. pycontrails/datalib/_leo_utils/vis.py +59 -0
  23. pycontrails/datalib/_met_utils/metsource.py +743 -0
  24. pycontrails/datalib/ecmwf/__init__.py +53 -0
  25. pycontrails/datalib/ecmwf/arco_era5.py +527 -0
  26. pycontrails/datalib/ecmwf/common.py +109 -0
  27. pycontrails/datalib/ecmwf/era5.py +538 -0
  28. pycontrails/datalib/ecmwf/era5_model_level.py +482 -0
  29. pycontrails/datalib/ecmwf/hres.py +782 -0
  30. pycontrails/datalib/ecmwf/hres_model_level.py +495 -0
  31. pycontrails/datalib/ecmwf/ifs.py +284 -0
  32. pycontrails/datalib/ecmwf/model_levels.py +79 -0
  33. pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
  34. pycontrails/datalib/ecmwf/variables.py +256 -0
  35. pycontrails/datalib/gfs/__init__.py +28 -0
  36. pycontrails/datalib/gfs/gfs.py +646 -0
  37. pycontrails/datalib/gfs/variables.py +100 -0
  38. pycontrails/datalib/goes.py +772 -0
  39. pycontrails/datalib/landsat.py +568 -0
  40. pycontrails/datalib/sentinel.py +512 -0
  41. pycontrails/datalib/spire.py +739 -0
  42. pycontrails/ext/bada.py +41 -0
  43. pycontrails/ext/cirium.py +14 -0
  44. pycontrails/ext/empirical_grid.py +140 -0
  45. pycontrails/ext/synthetic_flight.py +426 -0
  46. pycontrails/models/__init__.py +1 -0
  47. pycontrails/models/accf.py +406 -0
  48. pycontrails/models/apcemm/__init__.py +8 -0
  49. pycontrails/models/apcemm/apcemm.py +983 -0
  50. pycontrails/models/apcemm/inputs.py +226 -0
  51. pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
  52. pycontrails/models/apcemm/utils.py +437 -0
  53. pycontrails/models/cocip/__init__.py +29 -0
  54. pycontrails/models/cocip/cocip.py +2617 -0
  55. pycontrails/models/cocip/cocip_params.py +299 -0
  56. pycontrails/models/cocip/cocip_uncertainty.py +285 -0
  57. pycontrails/models/cocip/contrail_properties.py +1517 -0
  58. pycontrails/models/cocip/output_formats.py +2261 -0
  59. pycontrails/models/cocip/radiative_forcing.py +1262 -0
  60. pycontrails/models/cocip/radiative_heating.py +520 -0
  61. pycontrails/models/cocip/unterstrasser_wake_vortex.py +403 -0
  62. pycontrails/models/cocip/wake_vortex.py +396 -0
  63. pycontrails/models/cocip/wind_shear.py +120 -0
  64. pycontrails/models/cocipgrid/__init__.py +9 -0
  65. pycontrails/models/cocipgrid/cocip_grid.py +2573 -0
  66. pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
  67. pycontrails/models/dry_advection.py +486 -0
  68. pycontrails/models/emissions/__init__.py +21 -0
  69. pycontrails/models/emissions/black_carbon.py +594 -0
  70. pycontrails/models/emissions/emissions.py +1353 -0
  71. pycontrails/models/emissions/ffm2.py +336 -0
  72. pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
  73. pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
  74. pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
  75. pycontrails/models/humidity_scaling/__init__.py +37 -0
  76. pycontrails/models/humidity_scaling/humidity_scaling.py +1025 -0
  77. pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
  78. pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
  79. pycontrails/models/issr.py +210 -0
  80. pycontrails/models/pcc.py +327 -0
  81. pycontrails/models/pcr.py +154 -0
  82. pycontrails/models/ps_model/__init__.py +17 -0
  83. pycontrails/models/ps_model/ps_aircraft_params.py +376 -0
  84. pycontrails/models/ps_model/ps_grid.py +505 -0
  85. pycontrails/models/ps_model/ps_model.py +1017 -0
  86. pycontrails/models/ps_model/ps_operational_limits.py +540 -0
  87. pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv +68 -0
  88. pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv +103 -0
  89. pycontrails/models/sac.py +459 -0
  90. pycontrails/models/tau_cirrus.py +168 -0
  91. pycontrails/physics/__init__.py +1 -0
  92. pycontrails/physics/constants.py +116 -0
  93. pycontrails/physics/geo.py +989 -0
  94. pycontrails/physics/jet.py +837 -0
  95. pycontrails/physics/thermo.py +451 -0
  96. pycontrails/physics/units.py +472 -0
  97. pycontrails/py.typed +0 -0
  98. pycontrails/utils/__init__.py +1 -0
  99. pycontrails/utils/dependencies.py +66 -0
  100. pycontrails/utils/iteration.py +13 -0
  101. pycontrails/utils/json.py +188 -0
  102. pycontrails/utils/temp.py +50 -0
  103. pycontrails/utils/types.py +165 -0
  104. pycontrails-0.53.0.dist-info/LICENSE +178 -0
  105. pycontrails-0.53.0.dist-info/METADATA +181 -0
  106. pycontrails-0.53.0.dist-info/NOTICE +43 -0
  107. pycontrails-0.53.0.dist-info/RECORD +109 -0
  108. pycontrails-0.53.0.dist-info/WHEEL +6 -0
  109. pycontrails-0.53.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,403 @@
1
+ """Wave-vortex downwash functions from Unterstrasser (2016).
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 was developed based on output from large eddy simulations, and improves agreement with
8
+ LES outputs relative to the default survival fraction parameterization used in CoCiP.
9
+
10
+ For comparison, CoCiP assumes that ``f_surv`` is equal to the change in the contrail ice water
11
+ content (by mass) before and after the wake vortex phase. However, for larger (smaller) ice
12
+ particles, their survival fraction by number could be smaller (larger) than their survival fraction
13
+ by mass. This is particularly important in the "soot-poor" scenario, for example, in cleaner
14
+ lean-burn engines where their soot emissions can be 3-4 orders of magnitude lower than conventional
15
+ RQL engines.
16
+
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import numpy as np
22
+ import numpy.typing as npt
23
+
24
+ from pycontrails.models.cocip.wake_vortex import wake_vortex_separation
25
+ from pycontrails.physics import constants, thermo
26
+
27
+
28
+ def ice_particle_number_survival_fraction(
29
+ air_temperature: npt.NDArray[np.float64],
30
+ rhi_0: npt.NDArray[np.float64],
31
+ ei_h2o: npt.NDArray[np.float64] | float,
32
+ wingspan: npt.NDArray[np.float64] | float,
33
+ true_airspeed: npt.NDArray[np.float64],
34
+ fuel_flow: npt.NDArray[np.float64],
35
+ aei_n: npt.NDArray[np.float64],
36
+ z_desc: npt.NDArray[np.float64],
37
+ ) -> npt.NDArray[np.float64]:
38
+ r"""
39
+ Calculate fraction of ice particle number surviving the wake vortex phase and required inputs.
40
+
41
+ This implementation is based on the work of :cite:`unterstrasserPropertiesYoungContrails2016`
42
+ and is an improved estimation compared with
43
+ :func:`contrail_properties.ice_particle_survival_fraction`.
44
+
45
+ Parameters
46
+ ----------
47
+ air_temperature : npt.NDArray[np.float64]
48
+ ambient temperature for each waypoint, [:math:`K`]
49
+ rhi_0: npt.NDArray[np.float64]
50
+ Relative humidity with respect to ice at the flight waypoint
51
+ ei_h2o : npt.NDArray[np.float64] | float
52
+ Emission index of water vapor, [:math:`kg \ kg^{-1}`]
53
+ wingspan : npt.NDArray[np.float64] | float
54
+ aircraft wingspan, [:math:`m`]
55
+ true_airspeed : npt.NDArray[np.float64]
56
+ true airspeed for each waypoint, [:math:`m s^{-1}`]
57
+ fuel_flow : npt.NDArray[np.float64]
58
+ Fuel mass flow rate, [:math:`kg s^{-1}`]
59
+ aei_n : npt.NDArray[np.float64]
60
+ Apparent ice crystal number emissions index at contrail formation, [:math:`kg^{-1}`]
61
+ z_desc : npt.NDArray[np.float64]
62
+ Final vertical displacement of the wake vortex, ``dz_max`` in :mod:`wake_vortex.py`,
63
+ [:math:`m`].
64
+
65
+ Returns
66
+ -------
67
+ npt.NDArray[np.float64]
68
+ Fraction of contrail ice particle number that survive the wake vortex phase.
69
+
70
+ References
71
+ ----------
72
+ - :cite:`unterstrasserPropertiesYoungContrails2016`
73
+
74
+ Notes
75
+ -----
76
+ - See eq. (3), (9), and (10) in :cite:`unterstrasserPropertiesYoungContrails2016`.
77
+ - For consistency in CoCiP, ``z_desc`` should be calculated using :func:`dz_max` instead of
78
+ using :func:`z_desc_length_scale`.
79
+ """
80
+ # Length scales
81
+ z_atm = z_atm_length_scale(air_temperature, rhi_0)
82
+ rho_emit = emitted_water_vapour_concentration(ei_h2o, wingspan, true_airspeed, fuel_flow)
83
+ z_emit = z_emit_length_scale(rho_emit, air_temperature)
84
+ z_total = z_total_length_scale(aei_n, z_atm, z_emit, z_desc)
85
+ return _survival_fraction_from_length_scale(z_total)
86
+
87
+
88
+ def z_total_length_scale(
89
+ aei_n: npt.NDArray[np.float64],
90
+ z_atm: npt.NDArray[np.float64],
91
+ z_emit: npt.NDArray[np.float64],
92
+ z_desc: npt.NDArray[np.float64],
93
+ ) -> npt.NDArray[np.float64]:
94
+ """
95
+ Calculate the total length-scale effect of the wake vortex downwash.
96
+
97
+ Parameters
98
+ ----------
99
+ aei_n : npt.NDArray[np.float64]
100
+ Apparent ice crystal number emissions index at contrail formation, [:math:`kg^{-1}`]
101
+ z_atm : npt.NDArray[np.float64]
102
+ Length-scale effect of ambient supersaturation on the ice crystal mass budget, [:math:`m`]
103
+ z_emit : npt.NDArray[np.float64]
104
+ Length-scale effect of water vapour emissions on the ice crystal mass budget, [:math:`m`]
105
+ z_desc : npt.NDArray[np.float64]
106
+ Final vertical displacement of the wake vortex, `dz_max` in `wake_vortex.py`, [:math:`m`]
107
+
108
+ Returns
109
+ -------
110
+ npt.NDArray[np.float64]
111
+ Total length-scale effect of the wake vortex downwash, [:math:`m`]
112
+ """
113
+ alpha_base = (aei_n / 2.8e14) ** (-0.18)
114
+ alpha_atm = 1.7 * alpha_base
115
+ alpha_emit = 1.15 * alpha_base
116
+
117
+ z_total = alpha_atm * z_atm + alpha_emit * z_emit - 0.6 * z_desc
118
+
119
+ z_total.clip(min=0.0, out=z_total)
120
+ return z_total
121
+
122
+
123
+ def z_atm_length_scale(
124
+ air_temperature: npt.NDArray[np.float64],
125
+ rhi_0: npt.NDArray[np.float64],
126
+ *,
127
+ n_iter: int = 10,
128
+ ) -> npt.NDArray[np.float64]:
129
+ """Calculate the length-scale effect of ambient supersaturation on the ice crystal mass budget.
130
+
131
+ Parameters
132
+ ----------
133
+ air_temperature : npt.NDArray[np.float64]
134
+ Ambient temperature for each waypoint, [:math:`K`].
135
+ rhi_0 : npt.NDArray[np.float64]
136
+ Relative humidity with respect to ice at the flight waypoint.
137
+ n_iter : int
138
+ Number of iterations, set to 10 as default where ``z_atm`` is accurate to within +-1 m.
139
+
140
+ Returns
141
+ -------
142
+ npt.NDArray[np.float64]
143
+ The effect of the ambient supersaturation on the ice crystal mass budget,
144
+ provided as a length scale equivalent, [:math:`m`].
145
+
146
+ Notes
147
+ -----
148
+ - See eq. (5) in :cite:`unterstrasserPropertiesYoungContrails2016`.
149
+ """
150
+ # Only perform operation when the ambient condition is supersaturated w.r.t. ice
151
+ issr = rhi_0 > 1.0
152
+
153
+ rhi_issr = rhi_0[issr]
154
+ air_temperature_issr = air_temperature[issr]
155
+
156
+ # Solve non-linear equation numerically using the bisection method
157
+ # Did not use scipy functions because it is unstable when dealing with np.arrays
158
+ z_1 = np.zeros_like(rhi_issr)
159
+ z_2 = np.full_like(rhi_issr, 1000.0)
160
+ lhs = rhi_issr * thermo.e_sat_ice(air_temperature_issr) / air_temperature_issr
161
+
162
+ dry_adiabatic_lapse_rate = constants.g / constants.c_pd
163
+ for _ in range(n_iter):
164
+ z_est = 0.5 * (z_1 + z_2)
165
+ rhs = (thermo.e_sat_ice(air_temperature_issr + dry_adiabatic_lapse_rate * z_est)) / (
166
+ air_temperature_issr + dry_adiabatic_lapse_rate * z_est
167
+ )
168
+ z_1[lhs > rhs] = z_est[lhs > rhs]
169
+ z_2[lhs < rhs] = z_est[lhs < rhs]
170
+
171
+ out = np.zeros_like(rhi_0)
172
+ out[issr] = 0.5 * (z_1 + z_2)
173
+ return out
174
+
175
+
176
+ def emitted_water_vapour_concentration(
177
+ ei_h2o: npt.NDArray[np.float64] | float,
178
+ wingspan: npt.NDArray[np.float64] | float,
179
+ true_airspeed: npt.NDArray[np.float64],
180
+ fuel_flow: npt.NDArray[np.float64],
181
+ ) -> npt.NDArray[np.float64]:
182
+ r"""
183
+ Calculate aircraft-emitted water vapour concentration in the plume.
184
+
185
+ Parameters
186
+ ----------
187
+ ei_h2o : npt.NDArray[np.float64] | float
188
+ Emission index of water vapor, [:math:`kg \ kg^{-1}`]
189
+ wingspan : npt.NDArray[np.float64] | float
190
+ aircraft wingspan, [:math:`m`]
191
+ true_airspeed : npt.NDArray[np.float64]
192
+ true airspeed for each waypoint, [:math:`m s^{-1}`]
193
+ fuel_flow : npt.NDArray[np.float64]
194
+ Fuel mass flow rate, [:math:`kg s^{-1}`]
195
+
196
+ Returns
197
+ -------
198
+ npt.NDArray[np.float64]
199
+ Aircraft-emitted water vapour concentration in the plume, [:math:`kg m^{-3}`]
200
+
201
+ Notes
202
+ -----
203
+ - See eq. (6) and (A8) in :cite:`unterstrasserPropertiesYoungContrails2016`.
204
+ """
205
+ h2o_per_dist = (ei_h2o * fuel_flow) / true_airspeed
206
+ area_p = plume_area(wingspan)
207
+ return h2o_per_dist / area_p
208
+
209
+
210
+ def z_emit_length_scale(
211
+ rho_emit: npt.NDArray[np.float64], air_temperature: npt.NDArray[np.float64], *, n_iter: int = 10
212
+ ) -> npt.NDArray[np.float64]:
213
+ """Calculate the length-scale effect of water vapour emissions on the ice crystal mass budget.
214
+
215
+ Parameters
216
+ ----------
217
+ rho_emit : npt.NDArray[np.float64] | float
218
+ Aircraft-emitted water vapour concentration in the plume, [:math:`kg m^{-3}`]
219
+ air_temperature : npt.NDArray[np.float64]
220
+ ambient temperature for each waypoint, [:math:`K`]
221
+ n_iter : int
222
+ Number of iterations, set to 10 as default where ``z_emit`` is accurate to within +-1 m.
223
+
224
+ Returns
225
+ -------
226
+ npt.NDArray[np.float64]
227
+ The effect of the aircraft water vapour emission on the ice crystal mass budget,
228
+ provided as a length scale equivalent, [:math:`m`]
229
+
230
+ Notes
231
+ -----
232
+ - See eq. (7) in :cite:`unterstrasserPropertiesYoungContrails2016`.
233
+ """
234
+ # Solve non-linear equation numerically using the bisection method
235
+ # Did not use scipy functions because it is unstable when dealing with np.arrays
236
+ z_1 = np.zeros_like(rho_emit)
237
+ z_2 = np.full_like(rho_emit, 1000.0)
238
+
239
+ lhs = (thermo.e_sat_ice(air_temperature) / (constants.R_v * air_temperature)) + rho_emit
240
+
241
+ dry_adiabatic_lapse_rate = constants.g / constants.c_pd
242
+ for _ in range(n_iter):
243
+ z_est = 0.5 * (z_1 + z_2)
244
+ rhs = thermo.e_sat_ice(air_temperature + dry_adiabatic_lapse_rate * z_est) / (
245
+ constants.R_v * (air_temperature + dry_adiabatic_lapse_rate * z_est)
246
+ )
247
+ z_1[lhs > rhs] = z_est[lhs > rhs]
248
+ z_2[lhs < rhs] = z_est[lhs < rhs]
249
+
250
+ return 0.5 * (z_1 + z_2)
251
+
252
+
253
+ def plume_area(wingspan: npt.NDArray[np.float64] | float) -> npt.NDArray[np.float64] | float:
254
+ """Calculate area of the wake-vortex plume.
255
+
256
+ Parameters
257
+ ----------
258
+ wingspan : npt.NDArray[np.float64] | float
259
+ aircraft wingspan, [:math:`m`]
260
+
261
+ Returns
262
+ -------
263
+ float
264
+ Area of two wake-vortex plumes, [:math:`m^{2}`]
265
+
266
+ Notes
267
+ -----
268
+ - See eq. (A6) and (A7) in :cite:`unterstrasserPropertiesYoungContrails2016`.
269
+ """
270
+ r_plume = 1.5 + 0.314 * wingspan
271
+ return 2.0 * 2.0 * np.pi * r_plume**2
272
+
273
+
274
+ def z_desc_length_scale(
275
+ wingspan: npt.NDArray[np.float64] | float,
276
+ air_temperature: npt.NDArray[np.float64],
277
+ air_pressure: npt.NDArray[np.float64],
278
+ true_airspeed: npt.NDArray[np.float64],
279
+ aircraft_mass: npt.NDArray[np.float64],
280
+ dT_dz: npt.NDArray[np.float64],
281
+ ) -> npt.NDArray[np.float64]:
282
+ """Calculate the final vertical displacement of the wake vortex.
283
+
284
+ Parameters
285
+ ----------
286
+ wingspan : npt.NDArray[np.float64] | float
287
+ aircraft wingspan, [:math:`m`]
288
+ air_temperature : npt.NDArray[np.float64]
289
+ ambient temperature for each waypoint, [:math:`K`]
290
+ air_pressure : npt.NDArray[np.float64]
291
+ pressure altitude at each waypoint, [:math:`Pa`]
292
+ true_airspeed : npt.NDArray[np.float64]
293
+ true airspeed for each waypoint, [:math:`m s^{-1}`]
294
+ aircraft_mass : npt.NDArray[np.float64] | float
295
+ aircraft mass for each waypoint, [:math:`kg`]
296
+ dT_dz : npt.NDArray[np.float64]
297
+ potential temperature gradient, [:math:`K m^{-1}`]
298
+
299
+ Returns
300
+ -------
301
+ npt.NDArray[np.float64]
302
+ Final vertical displacement of the wake vortex, [:math:`m`]
303
+
304
+ Notes
305
+ -----
306
+ - See eq. (4) in :cite:`unterstrasserPropertiesYoungContrails2016`.
307
+ """
308
+ gamma_0 = _initial_wake_vortex_circulation(
309
+ wingspan, air_temperature, air_pressure, true_airspeed, aircraft_mass
310
+ )
311
+ n_bv = thermo.brunt_vaisala_frequency(air_pressure, air_temperature, dT_dz)
312
+ return ((8.0 * gamma_0) / (np.pi * n_bv)) ** 0.5
313
+
314
+
315
+ def _initial_wake_vortex_circulation(
316
+ wingspan: npt.NDArray[np.float64] | float,
317
+ air_temperature: npt.NDArray[np.float64],
318
+ air_pressure: npt.NDArray[np.float64],
319
+ true_airspeed: npt.NDArray[np.float64],
320
+ aircraft_mass: npt.NDArray[np.float64],
321
+ ) -> npt.NDArray[np.float64]:
322
+ """Calculate initial wake vortex circulation.
323
+
324
+ Parameters
325
+ ----------
326
+ wingspan : npt.NDArray[np.float64] | float
327
+ aircraft wingspan, [:math:`m`]
328
+ air_temperature : npt.NDArray[np.float64]
329
+ ambient temperature for each waypoint, [:math:`K`]
330
+ air_pressure : npt.NDArray[np.float64]
331
+ pressure altitude at each waypoint, [:math:`Pa`]
332
+ true_airspeed : npt.NDArray[np.float64]
333
+ true airspeed for each waypoint, [:math:`m s^{-1}`]
334
+ aircraft_mass : npt.NDArray[np.float64] | float
335
+ aircraft mass for each waypoint, [:math:`kg`]
336
+
337
+ Returns
338
+ -------
339
+ npt.NDArray[np.float64]
340
+ Initial wake vortex circulation, [:math:`m^{2} s^{-1}`]
341
+
342
+ Notes
343
+ -----
344
+ - This is a measure of the strength/intensity of the wake vortex circulation.
345
+ - See eq. (A1) in :cite:`unterstrasserPropertiesYoungContrails2016`.
346
+ """
347
+ b_0 = wake_vortex_separation(wingspan)
348
+ rho_air = thermo.rho_d(air_temperature, air_pressure)
349
+ return (constants.g * aircraft_mass) / (rho_air * b_0 * true_airspeed)
350
+
351
+
352
+ def _survival_fraction_from_length_scale(
353
+ z_total: npt.NDArray[np.float64],
354
+ ) -> npt.NDArray[np.float64]:
355
+ """
356
+ Calculate fraction of ice particle number surviving the wake vortex phase.
357
+
358
+ Parameters
359
+ ----------
360
+ z_total : npt.NDArray[np.float64]
361
+ Total length-scale effect of the wake vortex downwash, [:math:`m`]
362
+
363
+ Returns
364
+ -------
365
+ npt.NDArray[np.float64]
366
+ Fraction of ice particle number surviving the wake vortex phase
367
+ """
368
+ f_surv = 0.45 + (1.19 / np.pi) * np.arctan(-1.35 + (z_total / 100.0))
369
+ np.clip(f_surv, 0.0, 1.0, out=f_surv)
370
+ return f_surv
371
+
372
+
373
+ def initial_contrail_depth(
374
+ z_desc: npt.NDArray[np.float64],
375
+ f_surv: npt.NDArray[np.float64],
376
+ ) -> npt.NDArray[np.float64]:
377
+ """Calculate initial contrail depth using :cite:`unterstrasserPropertiesYoungContrails2016`.
378
+
379
+ Parameters
380
+ ----------
381
+ z_desc : npt.NDArray[np.float64]
382
+ Final vertical displacement of the wake vortex, ``dz_max`` in :mod:`wake_vortex.py`,
383
+ [:math:`m`].
384
+ f_surv : npt.NDArray[np.float64]
385
+ Fraction of contrail ice particle number that survive the wake vortex phase.
386
+ See :func:`ice_particle_survival_fraction`.
387
+
388
+ Returns
389
+ -------
390
+ npt.NDArray[np.float64]
391
+ Initial contrail depth, [:math:`m`]
392
+
393
+ Notes
394
+ -----
395
+ - See eq. (12), and (13) in :cite:`unterstrasserPropertiesYoungContrails2016`.
396
+ - For consistency in CoCiP, `z_desc` should be calculated using :func:`dz_max` instead of
397
+ using :func:`z_desc_length_scale`.
398
+ """
399
+ return z_desc * np.where(
400
+ f_surv <= 0.2,
401
+ 6.0 * f_surv,
402
+ 0.15 * f_surv + (6.0 - 0.15) * 0.2,
403
+ )