pycontrails 0.42.0__cp39-cp39-macosx_11_0_arm64.whl → 0.42.2__cp39-cp39-macosx_11_0_arm64.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 (32) hide show
  1. pycontrails/_version.py +2 -2
  2. pycontrails/core/cache.py +4 -6
  3. pycontrails/core/datalib.py +5 -2
  4. pycontrails/core/fleet.py +59 -7
  5. pycontrails/core/flight.py +175 -49
  6. pycontrails/core/flightplan.py +238 -0
  7. pycontrails/core/interpolation.py +11 -15
  8. pycontrails/core/met.py +5 -5
  9. pycontrails/core/models.py +4 -0
  10. pycontrails/core/rgi_cython.cpython-39-darwin.so +0 -0
  11. pycontrails/core/vector.py +17 -12
  12. pycontrails/datalib/ecmwf/common.py +14 -19
  13. pycontrails/ext/bada/__init__.py +6 -6
  14. pycontrails/ext/cirium/__init__.py +2 -2
  15. pycontrails/models/cocip/cocip.py +37 -39
  16. pycontrails/models/cocip/cocip_params.py +37 -30
  17. pycontrails/models/cocip/cocip_uncertainty.py +47 -58
  18. pycontrails/models/cocip/radiative_forcing.py +220 -193
  19. pycontrails/models/cocip/wake_vortex.py +96 -91
  20. pycontrails/models/humidity_scaling.py +265 -8
  21. pycontrails/models/issr.py +1 -1
  22. pycontrails/models/quantiles/era5_ensemble_quantiles.npy +0 -0
  23. pycontrails/models/quantiles/iagos_quantiles.npy +0 -0
  24. pycontrails/models/sac.py +2 -0
  25. pycontrails/physics/geo.py +2 -1
  26. pycontrails/utils/json.py +3 -3
  27. {pycontrails-0.42.0.dist-info → pycontrails-0.42.2.dist-info}/METADATA +4 -7
  28. {pycontrails-0.42.0.dist-info → pycontrails-0.42.2.dist-info}/RECORD +32 -29
  29. {pycontrails-0.42.0.dist-info → pycontrails-0.42.2.dist-info}/LICENSE +0 -0
  30. {pycontrails-0.42.0.dist-info → pycontrails-0.42.2.dist-info}/NOTICE +0 -0
  31. {pycontrails-0.42.0.dist-info → pycontrails-0.42.2.dist-info}/WHEEL +0 -0
  32. {pycontrails-0.42.0.dist-info → pycontrails-0.42.2.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from __future__ import annotations
12
12
  from dataclasses import dataclass
13
13
 
14
14
  import numpy as np
15
+ import numpy.typing as npt
15
16
 
16
17
 
17
18
  @dataclass
@@ -150,8 +151,10 @@ rf_const = RFConstants()
150
151
 
151
152
 
152
153
  def habit_weights(
153
- r_vol_um: np.ndarray, habit_distributions: np.ndarray, radius_threshold_um: np.ndarray
154
- ) -> np.ndarray:
154
+ r_vol_um: npt.NDArray[np.float_],
155
+ habit_distributions: npt.NDArray[np.float_],
156
+ radius_threshold_um: npt.NDArray[np.float_],
157
+ ) -> npt.NDArray[np.float_]:
155
158
  r"""Assign weights to different ice particle habits for each waypoint.
156
159
 
157
160
  For each waypoint, the distinct mix of ice particle habits are approximated
@@ -167,18 +170,18 @@ def habit_weights(
167
170
 
168
171
  Parameters
169
172
  ----------
170
- r_vol_um : np.ndarray
173
+ r_vol_um : npt.NDArray[np.float_]
171
174
  Contrail ice particle volume mean radius, [:math:`\mu m`]
172
- habit_distributions : np.ndarray
175
+ habit_distributions : npt.NDArray[np.float_]
173
176
  Habit weight distributions.
174
177
  See :attr:`CocipParams().habit_distributions`
175
- radius_threshold_um : np.ndarray
178
+ radius_threshold_um : npt.NDArray[np.float_]
176
179
  Radius thresholds for habit distributions.
177
180
  See :attr:`CocipParams.radius_threshold_um`
178
181
 
179
182
  Returns
180
183
  -------
181
- np.ndarray
184
+ npt.NDArray[np.float_]
182
185
  Array with shape ``n_waypoints x 8 columns``, where each column is the weights to the ice
183
186
  particle habits, [:math:`[0 - 1]`], and the sum of each column should be equal to 1.
184
187
 
@@ -189,7 +192,7 @@ def habit_weights(
189
192
  if there is a size mistmatch with ``radius_threshold_um``.
190
193
  """
191
194
  # all rows of the habit weights should sum to 1
192
- if not np.all(np.around(np.sum(habit_distributions, axis=1), 3) == 1):
195
+ if not np.all(np.round(np.sum(habit_distributions, axis=1), 3) == 1):
193
196
  raise ValueError("Habit weight distributions must sum to 1 across columns")
194
197
 
195
198
  if habit_distributions.shape[0] != (radius_threshold_um.size + 1):
@@ -199,24 +202,27 @@ def habit_weights(
199
202
  )
200
203
 
201
204
  # assign ice particle habits for each waypoint
202
- return habit_distributions[habit_weight_regime_idx(r_vol_um, radius_threshold_um)]
205
+ idx = habit_weight_regime_idx(r_vol_um, radius_threshold_um)
206
+ return habit_distributions[idx]
203
207
 
204
208
 
205
- def habit_weight_regime_idx(r_vol_um: np.ndarray, radius_threshold_um: np.ndarray) -> np.ndarray:
209
+ def habit_weight_regime_idx(
210
+ r_vol_um: npt.NDArray[np.float_], radius_threshold_um: npt.NDArray[np.float_]
211
+ ) -> npt.NDArray[np.intp]:
206
212
  r"""
207
213
  Determine regime of ice particle habits based on contrail ice particle volume mean radius.
208
214
 
209
215
  Parameters
210
216
  ----------
211
- r_vol_um : np.ndarray
217
+ r_vol_um : npt.NDArray[np.float_]
212
218
  Contrail ice particle volume mean radius, [:math:`\mu m`]
213
- radius_threshold_um : np.ndarray
219
+ radius_threshold_um : npt.NDArray[np.float_]
214
220
  Radius thresholds for habit distributions.
215
221
  See :attr:`CocipParams.radius_threshold_um`
216
222
 
217
223
  Returns
218
224
  -------
219
- np.ndarray
225
+ npt.NDArray[np.intp]
220
226
  Row index of the habit distribution in array :attr:`CocipParams().habit_distributions`
221
227
  """
222
228
  # find the regime for each waypoint using thresholds
@@ -228,7 +234,9 @@ def habit_weight_regime_idx(r_vol_um: np.ndarray, radius_threshold_um: np.ndarra
228
234
  return idx
229
235
 
230
236
 
231
- def effective_radius_by_habit(r_vol_um: np.ndarray, habit_idx: np.ndarray) -> np.ndarray:
237
+ def effective_radius_by_habit(
238
+ r_vol_um: npt.NDArray[np.float_], habit_idx: npt.NDArray[np.intp]
239
+ ) -> np.ndarray:
232
240
  r"""Calculate the effective radius ``r_eff_um`` via the mean ice particle radius and habit type.
233
241
 
234
242
  The ``habit_idx`` corresponds to the habit types in ``rf_const.habits``.
@@ -237,15 +245,15 @@ def effective_radius_by_habit(r_vol_um: np.ndarray, habit_idx: np.ndarray) -> np
237
245
 
238
246
  Parameters
239
247
  ----------
240
- r_vol_um : np.ndarray
248
+ r_vol_um : npt.NDArray[np.float_]
241
249
  Contrail ice particle volume mean radius, [:math:`\mu m`]
242
- habit_idx : np.ndarray
250
+ habit_idx : npt.NDArray[np.intp]
243
251
  Habit type index for the contrail ice particle, corresponding to the
244
252
  habits in ``rf_const.habits``.
245
253
 
246
254
  Returns
247
255
  -------
248
- np.ndarray
256
+ npt.NDArray[np.float_]
249
257
  Effective radius of ice particles for each combination of ``r_vol_um``
250
258
  and ``habit_idx``, [:math:`\mu m`]
251
259
 
@@ -253,167 +261,180 @@ def effective_radius_by_habit(r_vol_um: np.ndarray, habit_idx: np.ndarray) -> np
253
261
  ----------
254
262
  - :cite:`schumannEffectiveRadiusIce2011`
255
263
  """
256
- r_eff_um = np.zeros_like(r_vol_um)
257
- r_eff_um[habit_idx == 0] = effective_radius_sphere(r_vol_um[habit_idx == 0])
258
- r_eff_um[habit_idx == 1] = effective_radius_solid_column(r_vol_um[habit_idx == 1])
259
- r_eff_um[habit_idx == 2] = effective_radius_hollow_column(r_vol_um[habit_idx == 2])
260
- r_eff_um[habit_idx == 3] = effective_radius_rough_aggregate(r_vol_um[habit_idx == 3])
261
- r_eff_um[habit_idx == 4] = effective_radius_rosette(r_vol_um[habit_idx == 4])
262
- r_eff_um[habit_idx == 5] = effective_radius_plate(r_vol_um[habit_idx == 5])
263
- r_eff_um[habit_idx == 6] = effective_radius_droxtal(r_vol_um[habit_idx == 6])
264
- r_eff_um[habit_idx == 7] = effective_radius_myhre(r_vol_um[habit_idx == 7])
265
-
266
- return r_eff_um
267
-
268
-
269
- def effective_radius_sphere(r_vol_um: np.ndarray) -> np.ndarray:
264
+ cond_list = [
265
+ habit_idx == 0,
266
+ habit_idx == 1,
267
+ habit_idx == 2,
268
+ habit_idx == 3,
269
+ habit_idx == 4,
270
+ habit_idx == 5,
271
+ habit_idx == 6,
272
+ habit_idx == 7,
273
+ ]
274
+ func_list = [
275
+ effective_radius_sphere,
276
+ effective_radius_solid_column,
277
+ effective_radius_hollow_column,
278
+ effective_radius_rough_aggregate,
279
+ effective_radius_rosette,
280
+ effective_radius_plate,
281
+ effective_radius_droxtal,
282
+ effective_radius_myhre,
283
+ 0.0,
284
+ ]
285
+ return np.piecewise(r_vol_um, cond_list, func_list)
286
+
287
+
288
+ def effective_radius_sphere(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
270
289
  r"""
271
290
  Calculate the effective radius of contrail ice particles assuming a sphere particle habit.
272
291
 
273
292
  Parameters
274
293
  ----------
275
- r_vol_um : np.ndarray
294
+ r_vol_um : npt.NDArray[np.float_]
276
295
  Contrail ice particle volume mean radius, [:math:`\mu m`]
277
296
 
278
297
  Returns
279
298
  -------
280
- np.ndarray
299
+ npt.NDArray[np.float_]
281
300
  Effective radius, [:math:`\mu m`]
282
301
  """
283
- return np.minimum(r_vol_um, 25)
302
+ return np.minimum(r_vol_um, 25.0)
284
303
 
285
304
 
286
- def effective_radius_solid_column(r_vol_um: np.ndarray) -> np.ndarray:
305
+ def effective_radius_solid_column(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
287
306
  r"""
288
307
  Calculate the effective radius of contrail ice particles assuming a solid column particle habit.
289
308
 
290
309
  Parameters
291
310
  ----------
292
- r_vol_um : np.ndarray
311
+ r_vol_um : npt.NDArray[np.float_]
293
312
  Contrail ice particle volume mean radius, [:math:`\mu m`]
294
313
 
295
314
  Returns
296
315
  -------
297
- np.ndarray
316
+ npt.NDArray[np.float_]
298
317
  Effective radius, [:math:`\mu m`]
299
318
  """
300
319
  r_eff_um = (
301
320
  0.2588 * np.exp(-(6.912e-3 * r_vol_um)) + 0.6372 * np.exp(-(3.142e-4 * r_vol_um))
302
321
  ) * r_vol_um
303
- r_eff_um[r_vol_um <= 42.2] = 0.824 * r_vol_um[r_vol_um <= 42.2]
304
- return np.minimum(r_eff_um, 45)
322
+ is_small = r_vol_um <= 42.2
323
+ r_eff_um[is_small] = 0.824 * r_vol_um[is_small]
324
+ return np.minimum(r_eff_um, 45.0)
305
325
 
306
326
 
307
- def effective_radius_hollow_column(r_vol_um: np.ndarray) -> np.ndarray:
327
+ def effective_radius_hollow_column(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
308
328
  r"""Calculate the effective radius of ice particles assuming a hollow column particle habit.
309
329
 
310
330
  Parameters
311
331
  ----------
312
- r_vol_um : np.ndarray
332
+ r_vol_um : npt.NDArray[np.float_]
313
333
  Contrail ice particle volume mean radius, [:math:`\mu m`]
314
334
 
315
335
  Returns
316
336
  -------
317
- np.ndarray
337
+ npt.NDArray[np.float_]
318
338
  Effective radius, [:math:`\mu m`]
319
339
  """
320
340
  r_eff_um = (
321
341
  0.2281 * np.exp(-(7.359e-3 * r_vol_um)) + 0.5651 * np.exp(-(3.350e-4 * r_vol_um))
322
342
  ) * r_vol_um
323
- r_eff_um[r_vol_um <= 39.7] = 0.729 * r_vol_um[r_vol_um <= 39.7]
324
- return np.minimum(r_eff_um, 45)
343
+ is_small = r_vol_um <= 39.7
344
+ r_eff_um[is_small] = 0.729 * r_vol_um[is_small]
345
+ return np.minimum(r_eff_um, 45.0)
325
346
 
326
347
 
327
- def effective_radius_rough_aggregate(r_vol_um: np.ndarray) -> np.ndarray:
348
+ def effective_radius_rough_aggregate(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
328
349
  r"""Calculate the effective radius of ice particles assuming a rough aggregate particle habit.
329
350
 
330
351
  Parameters
331
352
  ----------
332
- r_vol_um : np.ndarray
353
+ r_vol_um : npt.NDArray[np.float_]
333
354
  Contrail ice particle volume mean radius, [:math:`\mu m`]
334
355
 
335
356
  Returns
336
357
  -------
337
- np.ndarray
358
+ npt.NDArray[np.float_]
338
359
  Effective radius, [:math:`\mu m`]
339
360
  """
340
361
  r_eff_um = 0.574 * r_vol_um
341
- return np.minimum(r_eff_um, 45)
362
+ return np.minimum(r_eff_um, 45.0)
342
363
 
343
364
 
344
- def effective_radius_rosette(r_vol_um: np.ndarray) -> np.ndarray:
365
+ def effective_radius_rosette(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
345
366
  r"""
346
367
  Calculate the effective radius of contrail ice particles assuming a rosette particle habit.
347
368
 
348
369
  Parameters
349
370
  ----------
350
- r_vol_um : np.ndarray
371
+ r_vol_um : npt.NDArray[np.float_]
351
372
  Contrail ice particle volume mean radius, [:math:`\mu m`]
352
373
 
353
374
  Returns
354
375
  -------
355
- np.ndarray
376
+ npt.NDArray[np.float_]
356
377
  Effective radius, [:math:`\mu m`]
357
378
  """
358
379
  r_eff_um = r_vol_um * (
359
380
  0.1770 * np.exp(-(2.144e-2 * r_vol_um)) + 0.4267 * np.exp(-(3.562e-4 * r_vol_um))
360
381
  )
361
- return np.minimum(r_eff_um, 45)
382
+ return np.minimum(r_eff_um, 45.0)
362
383
 
363
384
 
364
- def effective_radius_plate(r_vol_um: np.ndarray) -> np.ndarray:
385
+ def effective_radius_plate(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
365
386
  r"""
366
387
  Calculate the effective radius of contrail ice particles assuming a plate particle habit.
367
388
 
368
389
  Parameters
369
390
  ----------
370
- r_vol_um : np.ndarray
391
+ r_vol_um : npt.NDArray[np.float_]
371
392
  Contrail ice particle volume mean radius, [:math:`\mu m`]
372
393
 
373
394
  Returns
374
395
  -------
375
- np.ndarray
396
+ npt.NDArray[np.float_]
376
397
  Effective radius, [:math:`\mu m`]
377
398
  """
378
399
  r_eff_um = r_vol_um * (
379
400
  0.1663 + 0.3713 * np.exp(-(0.0336 * r_vol_um)) + 0.3309 * np.exp(-(0.0035 * r_vol_um))
380
401
  )
381
- return np.minimum(r_eff_um, 45)
402
+ return np.minimum(r_eff_um, 45.0)
382
403
 
383
404
 
384
- def effective_radius_droxtal(r_vol_um: np.ndarray) -> np.ndarray:
405
+ def effective_radius_droxtal(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
385
406
  r"""
386
407
  Calculate the effective radius of contrail ice particles assuming a droxtal particle habit.
387
408
 
388
409
  Parameters
389
410
  ----------
390
- r_vol_um : np.ndarray
411
+ r_vol_um : npt.NDArray[np.float_]
391
412
  Contrail ice particle volume mean radius, [:math:`\mu m`]
392
413
 
393
414
  Returns
394
415
  -------
395
- np.ndarray
416
+ npt.NDArray[np.float_]
396
417
  Effective radius, [:math:`\mu m`]
397
418
  """
398
419
  r_eff_um = 0.94 * r_vol_um
399
- return np.minimum(r_eff_um, 45)
420
+ return np.minimum(r_eff_um, 45.0)
400
421
 
401
422
 
402
- def effective_radius_myhre(r_vol_um: np.ndarray) -> np.ndarray:
423
+ def effective_radius_myhre(r_vol_um: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
403
424
  r"""
404
425
  Calculate the effective radius of contrail ice particles assuming a sphere particle habit.
405
426
 
406
427
  Parameters
407
428
  ----------
408
- r_vol_um : np.ndarray
429
+ r_vol_um : npt.NDArray[np.float_]
409
430
  Contrail ice particle volume mean radius, [:math:`\mu m`]
410
431
 
411
432
  Returns
412
433
  -------
413
- np.ndarray
434
+ npt.NDArray[np.float_]
414
435
  Effective radius, [:math:`\mu m`]
415
436
  """
416
- return np.minimum(r_vol_um, 45)
437
+ return np.minimum(r_vol_um, 45.0)
417
438
 
418
439
 
419
440
  # -----------------
@@ -422,14 +443,14 @@ def effective_radius_myhre(r_vol_um: np.ndarray) -> np.ndarray:
422
443
 
423
444
 
424
445
  def longwave_radiative_forcing(
425
- r_vol_um: np.ndarray,
426
- olr: np.ndarray,
427
- air_temperature: np.ndarray,
428
- tau_contrail: np.ndarray,
429
- tau_cirrus: np.ndarray,
430
- habit_weights_: np.ndarray,
431
- r_eff_um: np.ndarray | None = None,
432
- ) -> np.ndarray:
446
+ r_vol_um: npt.NDArray[np.float_],
447
+ olr: npt.NDArray[np.float_],
448
+ air_temperature: npt.NDArray[np.float_],
449
+ tau_contrail: npt.NDArray[np.float_],
450
+ tau_cirrus: npt.NDArray[np.float_],
451
+ habit_weights_: npt.NDArray[np.float_],
452
+ r_eff_um: npt.NDArray[np.float_] | None = None,
453
+ ) -> npt.NDArray[np.float_]:
433
454
  r"""
434
455
  Calculate the local contrail longwave radiative forcing (:math:`RF_{LW}`).
435
456
 
@@ -437,28 +458,28 @@ def longwave_radiative_forcing(
437
458
 
438
459
  Parameters
439
460
  ----------
440
- r_vol_um : np.ndarray
461
+ r_vol_um : npt.NDArray[np.float_]
441
462
  Contrail ice particle volume mean radius, [:math:`\mu m`]
442
- olr : np.ndarray
463
+ olr : npt.NDArray[np.float_]
443
464
  Outgoing longwave radiation at each waypoint, [:math:`W m^{-2}`]
444
- air_temperature : np.ndarray
465
+ air_temperature : npt.NDArray[np.float_]
445
466
  Ambient temperature at each waypoint, [:math:`K`]
446
- tau_contrail : np.ndarray
467
+ tau_contrail : npt.NDArray[np.float_]
447
468
  Contrail optical depth at each waypoint
448
- tau_cirrus : np.ndarray
469
+ tau_cirrus : npt.NDArray[np.float_]
449
470
  Optical depth of numerical weather prediction (NWP) cirrus above the
450
471
  contrail at each waypoint
451
- habit_weights_ : np.ndarray
472
+ habit_weights_ : npt.NDArray[np.float_]
452
473
  Weights to different ice particle habits for each waypoint,
453
474
  ``n_waypoints x 8`` (habit) columns, [:math:`[0 - 1]`]
454
- r_eff_um : np.ndarray, optional
475
+ r_eff_um : npt.NDArray[np.float_], optional
455
476
  Provide effective radius corresponding to elements in ``r_vol_um``, [:math:`\mu m`].
456
477
  Defaults to None, which means the effective radius will be calculated using ``r_vol_um``
457
478
  and habit types in :func:`effective_radius_by_habit`.
458
479
 
459
480
  Returns
460
481
  -------
461
- np.ndarray
482
+ npt.NDArray[np.float_]
462
483
  Local contrail longwave radiative forcing (positive), [:math:`W m^{-2}`]
463
484
 
464
485
  Raises
@@ -472,25 +493,25 @@ def longwave_radiative_forcing(
472
493
  """
473
494
  # get list of habit weight indexs where the weights > 0
474
495
  # this is a tuple of (np.array[waypoint index], np.array[habit type index])
475
- habit_weight_mask = habit_weights_ > 0
476
- habit_weight_idxs = np.where(habit_weight_mask)
496
+ habit_weight_mask = habit_weights_ > 0.0
497
+ idx0, idx1 = np.nonzero(habit_weight_mask)
477
498
 
478
499
  # Convert parametric coefficients for vectorized operations
479
- delta_t = rf_const.delta_t[habit_weight_idxs[1]]
480
- delta_lc = rf_const.delta_lc[habit_weight_idxs[1]]
481
- delta_lr = rf_const.delta_lr[habit_weight_idxs[1]]
482
- k_t = rf_const.k_t[habit_weight_idxs[1]]
483
- T_0 = rf_const.T_0[habit_weight_idxs[1]]
500
+ delta_t = rf_const.delta_t[idx1]
501
+ delta_lc = rf_const.delta_lc[idx1]
502
+ delta_lr = rf_const.delta_lr[idx1]
503
+ k_t = rf_const.k_t[idx1]
504
+ T_0 = rf_const.T_0[idx1]
484
505
 
485
- olr_h = olr[habit_weight_idxs[0]]
486
- tau_cirrus_h = tau_cirrus[habit_weight_idxs[0]]
487
- tau_contrail_h = tau_contrail[habit_weight_idxs[0]]
488
- air_temperature_h = air_temperature[habit_weight_idxs[0]]
506
+ olr_h = olr[idx0]
507
+ tau_cirrus_h = tau_cirrus[idx0]
508
+ tau_contrail_h = tau_contrail[idx0]
509
+ air_temperature_h = air_temperature[idx0]
489
510
 
490
511
  # effective radius
491
512
  if r_eff_um is None:
492
- r_vol_um_h = r_vol_um[habit_weight_idxs[0]]
493
- r_eff_um_h = effective_radius_by_habit(r_vol_um_h, habit_weight_idxs[1])
513
+ r_vol_um_h = r_vol_um[idx0]
514
+ r_eff_um_h = effective_radius_by_habit(r_vol_um_h, idx1)
494
515
  else:
495
516
  if not isinstance(r_eff_um, np.ndarray) or r_eff_um.shape != olr.shape:
496
517
  raise ValueError(
@@ -498,7 +519,7 @@ def longwave_radiative_forcing(
498
519
  f" {olr.shape}"
499
520
  )
500
521
 
501
- r_eff_um_h = r_eff_um[habit_weight_idxs[0]]
522
+ r_eff_um_h = r_eff_um[idx0]
502
523
 
503
524
  # Longwave radiation calculations
504
525
  e_lw = olr_reduction_natural_cirrus(tau_cirrus_h, delta_lc)
@@ -509,29 +530,29 @@ def longwave_radiative_forcing(
509
530
  rf_lw_per_habit = (
510
531
  (olr_h - k_t * (air_temperature_h - T_0))
511
532
  * e_lw
512
- * (1 - np.exp(-delta_t * f_lw * tau_contrail_h))
533
+ * (1.0 - np.exp(-delta_t * f_lw * tau_contrail_h))
513
534
  )
514
- rf_lw_per_habit = np.maximum(rf_lw_per_habit, 0)
535
+ rf_lw_per_habit = np.maximum(rf_lw_per_habit, 0.0)
515
536
 
516
537
  # Weight and sum the RF contributions of each habit type according the habit weight
517
538
  # regime at the waypoint
518
539
  # see eqn (12) in :cite:`schumannParametricRadiativeForcing2012`
519
540
  # use fancy indexing to re-assign values to 2d array of waypoint x habit type
520
541
  rf_lw_weighted = np.zeros_like(habit_weights_)
521
- rf_lw_weighted[habit_weight_idxs] = rf_lw_per_habit * habit_weights_[habit_weight_mask]
542
+ rf_lw_weighted[idx0, idx1] = rf_lw_per_habit * habit_weights_[habit_weight_mask]
522
543
  return np.sum(rf_lw_weighted, axis=1)
523
544
 
524
545
 
525
546
  def shortwave_radiative_forcing(
526
- r_vol_um: np.ndarray,
527
- sdr: np.ndarray,
528
- rsr: np.ndarray,
529
- sd0: np.ndarray,
530
- tau_contrail: np.ndarray,
531
- tau_cirrus: np.ndarray,
532
- habit_weights_: np.ndarray,
533
- r_eff_um: np.ndarray | None = None,
534
- ) -> np.ndarray:
547
+ r_vol_um: npt.NDArray[np.float_],
548
+ sdr: npt.NDArray[np.float_],
549
+ rsr: npt.NDArray[np.float_],
550
+ sd0: npt.NDArray[np.float_],
551
+ tau_contrail: npt.NDArray[np.float_],
552
+ tau_cirrus: npt.NDArray[np.float_],
553
+ habit_weights_: npt.NDArray[np.float_],
554
+ r_eff_um: npt.NDArray[np.float_] | None = None,
555
+ ) -> npt.NDArray[np.float_]:
535
556
  r"""
536
557
  Calculate the local contrail shortwave radiative forcing (:math:`RF_{SW}`).
537
558
 
@@ -539,30 +560,30 @@ def shortwave_radiative_forcing(
539
560
 
540
561
  Parameters
541
562
  ----------
542
- r_vol_um : np.ndarray
563
+ r_vol_um : npt.NDArray[np.float_]
543
564
  Contrail ice particle volume mean radius, [:math:`\mu m`]
544
- sdr : np.ndarray
565
+ sdr : npt.NDArray[np.float_]
545
566
  Solar direct radiation, [:math:`W m^{-2}`]
546
- rsr : np.ndarray
567
+ rsr : npt.NDArray[np.float_]
547
568
  Reflected solar radiation, [:math:`W m^{-2}`]
548
- sd0 : np.ndarray
569
+ sd0 : npt.NDArray[np.float_]
549
570
  Solar constant, [:math:`W m^{-2}`]
550
- tau_contrail : np.ndarray
571
+ tau_contrail : npt.NDArray[np.float_]
551
572
  Contrail optical depth for each waypoint
552
- tau_cirrus : np.ndarray
573
+ tau_cirrus : npt.NDArray[np.float_]
553
574
  Optical depth of numerical weather prediction (NWP) cirrus above the
554
575
  contrail for each waypoint.
555
- habit_weights_ : np.ndarray
576
+ habit_weights_ : npt.NDArray[np.float_]
556
577
  Weights to different ice particle habits for each waypoint,
557
578
  ``n_waypoints x 8`` (habit) columns, [:math:`[0 - 1]`]
558
- r_eff_um : np.ndarray, optional
579
+ r_eff_um : npt.NDArray[np.float_], optional
559
580
  Provide effective radius corresponding to elements in ``r_vol_um``, [:math:`\mu m`].
560
581
  Defaults to None, which means the effective radius will be calculated using ``r_vol_um``
561
582
  and habit types in :func:`effective_radius_by_habit`.
562
583
 
563
584
  Returns
564
585
  -------
565
- np.ndarray
586
+ npt.NDArray[np.float_]
566
587
  Local contrail shortwave radiative forcing (negative), [:math:`W m^{-2}`]
567
588
 
568
589
  Raises
@@ -575,7 +596,7 @@ def shortwave_radiative_forcing(
575
596
  - :cite:`schumannParametricRadiativeForcing2012`
576
597
  """
577
598
  # create mask for daytime (sdr > 0)
578
- day = sdr > 0
599
+ day = sdr > 0.0
579
600
 
580
601
  # short circuit if no waypoints occur during the day
581
602
  if not day.any():
@@ -583,34 +604,34 @@ def shortwave_radiative_forcing(
583
604
 
584
605
  # get list of habit weight indexs where the weights > 0
585
606
  # this is a tuple of (np.array[waypoint index], np.array[habit type index])
586
- habit_weight_mask = day.reshape(day.size, 1) & (habit_weights_ > 0)
587
- habit_weight_idxs = np.where(habit_weight_mask)
607
+ habit_weight_mask = day.reshape(day.size, 1) & (habit_weights_ > 0.0)
608
+ idx0, idx1 = np.nonzero(habit_weight_mask)
588
609
 
589
610
  # Convert parametric coefficients for vectorized operations
590
- t_a = rf_const.t_a[habit_weight_idxs[1]]
591
- A_mu = rf_const.A_mu[habit_weight_idxs[1]]
592
- B_mu = rf_const.B_mu[habit_weight_idxs[1]]
593
- C_mu = rf_const.C_mu[habit_weight_idxs[1]]
594
- delta_sr = rf_const.delta_sr[habit_weight_idxs[1]]
595
- F_r = rf_const.F_r[habit_weight_idxs[1]]
596
- gamma_lower = rf_const.gamma_lower[habit_weight_idxs[1]]
597
- gamma_upper = rf_const.gamma_upper[habit_weight_idxs[1]]
598
- delta_sc = rf_const.delta_sc[habit_weight_idxs[1]]
599
- delta_sc_aps = rf_const.delta_sc_aps[habit_weight_idxs[1]]
600
-
601
- sdr_h = sdr[habit_weight_idxs[0]]
602
- rsr_h = rsr[habit_weight_idxs[0]]
603
- sd0_h = sd0[habit_weight_idxs[0]]
604
- tau_contrail_h = tau_contrail[habit_weight_idxs[0]]
605
- tau_cirrus_h = tau_cirrus[habit_weight_idxs[0]]
611
+ t_a = rf_const.t_a[idx1]
612
+ A_mu = rf_const.A_mu[idx1]
613
+ B_mu = rf_const.B_mu[idx1]
614
+ C_mu = rf_const.C_mu[idx1]
615
+ delta_sr = rf_const.delta_sr[idx1]
616
+ F_r = rf_const.F_r[idx1]
617
+ gamma_lower = rf_const.gamma_lower[idx1]
618
+ gamma_upper = rf_const.gamma_upper[idx1]
619
+ delta_sc = rf_const.delta_sc[idx1]
620
+ delta_sc_aps = rf_const.delta_sc_aps[idx1]
621
+
622
+ sdr_h = sdr[idx0]
623
+ rsr_h = rsr[idx0]
624
+ sd0_h = sd0[idx0]
625
+ tau_contrail_h = tau_contrail[idx0]
626
+ tau_cirrus_h = tau_cirrus[idx0]
606
627
 
607
628
  albedo_ = albedo(sdr_h, rsr_h)
608
- mue = np.minimum((sdr_h / sd0_h), 1)
629
+ mue = np.minimum(sdr_h / sd0_h, 1.0)
609
630
 
610
631
  # effective radius
611
632
  if r_eff_um is None:
612
- r_vol_um_h = r_vol_um[habit_weight_idxs[0]]
613
- r_eff_um_h = effective_radius_by_habit(r_vol_um_h, habit_weight_idxs[1])
633
+ r_vol_um_h = r_vol_um[idx0]
634
+ r_eff_um_h = effective_radius_by_habit(r_vol_um_h, idx1)
614
635
  else:
615
636
  if not isinstance(r_eff_um, np.ndarray) or r_eff_um.shape != sdr.shape:
616
637
  raise ValueError(
@@ -618,7 +639,7 @@ def shortwave_radiative_forcing(
618
639
  f" {sdr.shape}"
619
640
  )
620
641
 
621
- r_eff_um_h = r_eff_um[habit_weight_idxs[0]]
642
+ r_eff_um_h = r_eff_um[idx0]
622
643
 
623
644
  # Local contrail shortwave radiative forcing calculations
624
645
  alpha_c = contrail_albedo(
@@ -638,19 +659,21 @@ def shortwave_radiative_forcing(
638
659
 
639
660
  # calculate the RF SW per habit type
640
661
  # see eqn (5) in :cite:`schumannParametricRadiativeForcing2012`
641
- rf_sw_per_habit = np.minimum(-sdr_h * ((t_a - albedo_) ** 2) * alpha_c * e_sw, 0)
662
+ rf_sw_per_habit = np.minimum(-sdr_h * ((t_a - albedo_) ** 2) * alpha_c * e_sw, 0.0)
642
663
 
643
664
  # Weight and sum the RF contributions of each habit type according the
644
665
  # habit weight regime at the waypoint
645
666
  # see eqn (12) in :cite:`schumannParametricRadiativeForcing2012`
646
667
  # use fancy indexing to re-assign values to 2d array of waypoint x habit type
647
668
  rf_sw_weighted = np.zeros_like(habit_weights_)
648
- rf_sw_weighted[habit_weight_idxs] = rf_sw_per_habit * habit_weights_[habit_weight_mask]
669
+ rf_sw_weighted[idx0, idx1] = rf_sw_per_habit * habit_weights_[habit_weight_mask]
649
670
 
650
671
  return np.sum(rf_sw_weighted, axis=1)
651
672
 
652
673
 
653
- def net_radiative_forcing(rf_lw: np.ndarray, rf_sw: np.ndarray) -> np.ndarray:
674
+ def net_radiative_forcing(
675
+ rf_lw: npt.NDArray[np.float_], rf_sw: npt.NDArray[np.float_]
676
+ ) -> npt.NDArray[np.float_]:
654
677
  """
655
678
  Calculate the local contrail net radiative forcing (rf_net).
656
679
 
@@ -658,20 +681,22 @@ def net_radiative_forcing(rf_lw: np.ndarray, rf_sw: np.ndarray) -> np.ndarray:
658
681
 
659
682
  Parameters
660
683
  ----------
661
- rf_lw : np.ndarray
684
+ rf_lw : npt.NDArray[np.float_]
662
685
  local contrail longwave radiative forcing, [:math:`W m^{-2}`]
663
- rf_sw : np.ndarray
686
+ rf_sw : npt.NDArray[np.float_]
664
687
  local contrail shortwave radiative forcing, [:math:`W m^{-2}`]
665
688
 
666
689
  Returns
667
690
  -------
668
- np.ndarray
691
+ npt.NDArray[np.float_]
669
692
  local contrail net radiative forcing, [:math:`W m^{-2}`]
670
693
  """
671
694
  return rf_lw + rf_sw
672
695
 
673
696
 
674
- def olr_reduction_natural_cirrus(tau_cirrus: np.ndarray, delta_lc: np.ndarray) -> np.ndarray:
697
+ def olr_reduction_natural_cirrus(
698
+ tau_cirrus: npt.NDArray[np.float_], delta_lc: npt.NDArray[np.float_]
699
+ ) -> npt.NDArray[np.float_]:
675
700
  """
676
701
  Calculate reduction in outgoing longwave radiation (OLR) due to the presence of natural cirrus.
677
702
 
@@ -680,45 +705,47 @@ def olr_reduction_natural_cirrus(tau_cirrus: np.ndarray, delta_lc: np.ndarray) -
680
705
 
681
706
  Parameters
682
707
  ----------
683
- tau_cirrus : np.ndarray
708
+ tau_cirrus : npt.NDArray[np.float_]
684
709
  Optical depth of numerical weather prediction (NWP) cirrus above the
685
710
  contrail for each waypoint.
686
- delta_lc : np.ndarray
711
+ delta_lc : npt.NDArray[np.float_]
687
712
  Habit specific parameter to approximate the reduction of the outgoing
688
713
  longwave radiation at the contrail level due to natural cirrus above the contrail.
689
714
 
690
715
  Returns
691
716
  -------
692
- np.ndarray
717
+ npt.NDArray[np.float_]
693
718
  Reduction of outgoing longwave radiation
694
719
  """
695
720
  # e_lw calculations
696
721
  return np.exp(-delta_lc * tau_cirrus)
697
722
 
698
723
 
699
- def contrail_effective_emissivity(r_eff_um: np.ndarray, delta_lr: np.ndarray) -> np.ndarray:
724
+ def contrail_effective_emissivity(
725
+ r_eff_um: npt.NDArray[np.float_], delta_lr: npt.NDArray[np.float_]
726
+ ) -> npt.NDArray[np.float_]:
700
727
  r"""Calculate the effective emissivity of the contrail, ``f_lw``.
701
728
 
702
729
  Refer to Eq. (3) of Schumann et al. (2012).
703
730
 
704
731
  Parameters
705
732
  ----------
706
- r_eff_um : np.ndarray
733
+ r_eff_um : npt.NDArray[np.float_]
707
734
  Effective radius for each waypoint, n_waypoints x 8 (habit) columns, [:math:`\mu m`]
708
735
  See :func:`effective_radius_habit`.
709
- delta_lr : np.ndarray
736
+ delta_lr : npt.NDArray[np.float_]
710
737
  Habit specific parameter to approximate the effective emissivity of the contrail.
711
738
 
712
739
  Returns
713
740
  -------
714
- np.ndarray
741
+ npt.NDArray[np.float_]
715
742
  Effective emissivity of the contrail
716
743
  """
717
744
  # f_lw calculations
718
- return 1 - np.exp(-delta_lr * r_eff_um)
745
+ return 1.0 - np.exp(-delta_lr * r_eff_um)
719
746
 
720
747
 
721
- def albedo(sdr: np.ndarray, rsr: np.ndarray) -> np.ndarray:
748
+ def albedo(sdr: npt.NDArray[np.float_], rsr: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
722
749
  """
723
750
  Calculate albedo along contrail waypoint.
724
751
 
@@ -730,35 +757,35 @@ def albedo(sdr: np.ndarray, rsr: np.ndarray) -> np.ndarray:
730
757
 
731
758
  Parameters
732
759
  ----------
733
- sdr : np.ndarray
760
+ sdr : npt.NDArray[np.float_]
734
761
  Solar direct radiation, [:math:`W m^{-2}`]
735
- rsr : np.ndarray
762
+ rsr : npt.NDArray[np.float_]
736
763
  Reflected solar radiation, [:math:`W m^{-2}`]
737
764
 
738
765
  Returns
739
766
  -------
740
- np.ndarray
767
+ npt.NDArray[np.float_]
741
768
  Albedo value, [:math:`[0 - 1]`]
742
769
  """
743
- day = sdr > 0
770
+ day = sdr > 0.0
744
771
  albedo_ = np.zeros(sdr.shape)
745
772
  albedo_[day] = rsr[day] / sdr[day]
746
- albedo_.clip(0, 1, out=albedo_)
773
+ albedo_.clip(0.0, 1.0, out=albedo_)
747
774
  return albedo_
748
775
 
749
776
 
750
777
  def contrail_albedo(
751
- tau_contrail: np.ndarray,
752
- mue: np.ndarray,
753
- r_eff_um: np.ndarray,
754
- A_mu: np.ndarray,
755
- B_mu: np.ndarray,
756
- C_mu: np.ndarray,
757
- delta_sr: np.ndarray,
758
- F_r: np.ndarray,
759
- gamma_lower: np.ndarray,
760
- gamma_upper: np.ndarray,
761
- ) -> np.ndarray:
778
+ tau_contrail: npt.NDArray[np.float_],
779
+ mue: npt.NDArray[np.float_],
780
+ r_eff_um: npt.NDArray[np.float_],
781
+ A_mu: npt.NDArray[np.float_],
782
+ B_mu: npt.NDArray[np.float_],
783
+ C_mu: npt.NDArray[np.float_],
784
+ delta_sr: npt.NDArray[np.float_],
785
+ F_r: npt.NDArray[np.float_],
786
+ gamma_lower: npt.NDArray[np.float_],
787
+ gamma_upper: npt.NDArray[np.float_],
788
+ ) -> npt.NDArray[np.float_]:
762
789
  r"""
763
790
  Calculate the contrail albedo, ``alpha_c``.
764
791
 
@@ -766,48 +793,48 @@ def contrail_albedo(
766
793
 
767
794
  Parameters
768
795
  ----------
769
- tau_contrail : np.ndarray
796
+ tau_contrail : npt.NDArray[np.float_]
770
797
  Contrail optical depth for each waypoint
771
- mue : np.ndarray
798
+ mue : npt.NDArray[np.float_]
772
799
  Cosine of the solar zenith angle (theta), mue = cos(theta) = sdr/sd0
773
- r_eff_um : np.ndarray
800
+ r_eff_um : npt.NDArray[np.float_]
774
801
  Effective radius for each waypoint, n_waypoints x 8 (habit) columns, [:math:`\mu m`]
775
802
  See :func:`effective_radius_habit`.
776
- A_mu : np.ndarray
803
+ A_mu : npt.NDArray[np.float_]
777
804
  Habit-specific parameter to approximate the albedo of the contrail
778
- B_mu : np.ndarray
805
+ B_mu : npt.NDArray[np.float_]
779
806
  Habit-specific parameter to approximate the SZA-dependent contrail sideward scattering
780
- C_mu : np.ndarray
807
+ C_mu : npt.NDArray[np.float_]
781
808
  Habit-specific parameter to approximate the albedo of the contrail
782
- delta_sr : np.ndarray
809
+ delta_sr : npt.NDArray[np.float_]
783
810
  Habit-specific parameter to approximate the effective contrail optical depth
784
- F_r : np.ndarray
811
+ F_r : npt.NDArray[np.float_]
785
812
  Habit-specific parameter to approximate the effective contrail optical depth
786
- gamma_lower : np.ndarray
813
+ gamma_lower : npt.NDArray[np.float_]
787
814
  Habit-specific parameter to approximate the contrail reflectances
788
- gamma_upper : np.ndarray
815
+ gamma_upper : npt.NDArray[np.float_]
789
816
  Habit-specific parameter to approximate the contrail reflectances
790
817
 
791
818
  Returns
792
819
  -------
793
- np.ndarray
820
+ npt.NDArray[np.float_]
794
821
  Contrail albedo for each waypoint and ice particle habit
795
822
  """
796
- tau_aps = tau_contrail * (1 - F_r * (1 - np.exp(-delta_sr * r_eff_um)))
823
+ tau_aps = tau_contrail * (1.0 - F_r * (1 - np.exp(-delta_sr * r_eff_um)))
797
824
  tau_eff = tau_aps / (mue + 1e-6)
798
- r_c = 1 - np.exp(-gamma_upper * tau_eff)
825
+ r_c = 1.0 - np.exp(-gamma_upper * tau_eff)
799
826
  r_c_aps = np.exp(-gamma_lower * tau_eff)
800
827
 
801
- f_mu = (2 * (1 - mue)) ** B_mu - 1
828
+ f_mu = (2.0 * (1.0 - mue)) ** B_mu - 1.0
802
829
  return r_c * (C_mu + (A_mu * r_c_aps * f_mu))
803
830
 
804
831
 
805
832
  def effective_tau_cirrus(
806
- tau_cirrus: np.ndarray,
807
- mue: np.ndarray,
808
- delta_sc: np.ndarray,
809
- delta_sc_aps: np.ndarray,
810
- ) -> np.ndarray:
833
+ tau_cirrus: npt.NDArray[np.float_],
834
+ mue: npt.NDArray[np.float_],
835
+ delta_sc: npt.NDArray[np.float_],
836
+ delta_sc_aps: npt.NDArray[np.float_],
837
+ ) -> npt.NDArray[np.float_]:
811
838
  r"""
812
839
  Calculate the effective optical depth of natural cirrus above the contrail, ``e_sw``.
813
840
 
@@ -815,21 +842,21 @@ def effective_tau_cirrus(
815
842
 
816
843
  Parameters
817
844
  ----------
818
- tau_cirrus : np.ndarray
845
+ tau_cirrus : npt.NDArray[np.float_]
819
846
  Optical depth of numerical weather prediction (NWP) cirrus above the
820
847
  contrail for each waypoint.
821
- mue : np.ndarray
848
+ mue : npt.NDArray[np.float_]
822
849
  Cosine of the solar zenith angle (theta), mue = cos(theta) = sdr/sd0
823
- delta_sc : np.ndarray
850
+ delta_sc : npt.NDArray[np.float_]
824
851
  Habit-specific parameter to account for the optical depth of natural
825
852
  cirrus above the contrail
826
- delta_sc_aps : np.ndarray
853
+ delta_sc_aps : npt.NDArray[np.float_]
827
854
  Habit-specific parameter to account for the optical depth of natural
828
855
  cirrus above the contrail
829
856
 
830
857
  Returns
831
858
  -------
832
- np.ndarray
859
+ npt.NDArray[np.float_]
833
860
  Effective optical depth of natural cirrus above the contrail,
834
861
  ``n_waypoints x 8`` (habit) columns.
835
862
  """