pycontrails 0.49.5__cp310-cp310-win_amd64.whl → 0.50.1__cp310-cp310-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pycontrails might be problematic. Click here for more details.

Files changed (32) hide show
  1. pycontrails/_version.py +2 -2
  2. pycontrails/core/datalib.py +60 -38
  3. pycontrails/core/flight.py +11 -6
  4. pycontrails/core/interpolation.py +39 -1
  5. pycontrails/core/met.py +14 -16
  6. pycontrails/core/met_var.py +2 -2
  7. pycontrails/core/models.py +7 -3
  8. pycontrails/core/rgi_cython.cp310-win_amd64.pyd +0 -0
  9. pycontrails/core/vector.py +15 -13
  10. pycontrails/datalib/ecmwf/__init__.py +4 -0
  11. pycontrails/datalib/ecmwf/arco_era5.py +577 -0
  12. pycontrails/datalib/ecmwf/common.py +1 -1
  13. pycontrails/datalib/ecmwf/era5.py +2 -5
  14. pycontrails/datalib/ecmwf/variables.py +18 -0
  15. pycontrails/datalib/gfs/gfs.py +2 -2
  16. pycontrails/datalib/goes.py +14 -12
  17. pycontrails/models/cocip/cocip.py +48 -8
  18. pycontrails/models/cocip/cocip_params.py +20 -1
  19. pycontrails/models/cocip/contrail_properties.py +4 -9
  20. pycontrails/models/cocip/unterstrasser_wake_vortex.py +403 -0
  21. pycontrails/models/cocip/wake_vortex.py +22 -1
  22. pycontrails/models/cocipgrid/cocip_grid.py +103 -6
  23. pycontrails/models/cocipgrid/cocip_grid_params.py +25 -19
  24. pycontrails/models/issr.py +1 -1
  25. pycontrails/physics/constants.py +6 -0
  26. pycontrails/utils/dependencies.py +13 -11
  27. {pycontrails-0.49.5.dist-info → pycontrails-0.50.1.dist-info}/METADATA +4 -2
  28. {pycontrails-0.49.5.dist-info → pycontrails-0.50.1.dist-info}/RECORD +32 -30
  29. {pycontrails-0.49.5.dist-info → pycontrails-0.50.1.dist-info}/WHEEL +1 -1
  30. {pycontrails-0.49.5.dist-info → pycontrails-0.50.1.dist-info}/LICENSE +0 -0
  31. {pycontrails-0.49.5.dist-info → pycontrails-0.50.1.dist-info}/NOTICE +0 -0
  32. {pycontrails-0.49.5.dist-info → pycontrails-0.50.1.dist-info}/top_level.txt +0 -0
@@ -50,7 +50,7 @@ except ModuleNotFoundError as exc:
50
50
 
51
51
 
52
52
  #: Default channels to use if none are specified. These are the channels
53
- #: required by the MIT ash color scheme.
53
+ #: required by the SEVIRI (MIT) ash color scheme.
54
54
  DEFAULT_CHANNELS = "C11", "C14", "C15"
55
55
 
56
56
  #: The time at which the GOES scan mode changed from mode 3 to mode 6. This
@@ -203,10 +203,10 @@ def gcs_goes_path(
203
203
  GOES Region of interest.
204
204
  channels : str | Iterable[str]
205
205
  Set of channels or bands for CMIP data. The 16 possible channels are
206
- represented by the strings "C01" to "C16". For the MIT ash color scheme,
206
+ represented by the strings "C01" to "C16". For the SEVIRI ash color scheme,
207
207
  set ``channels=("C11", "C14", "C15")``. For the true color scheme,
208
208
  set ``channels=("C01", "C02", "C03")``. By default, the channels
209
- required by the MIT ash color scheme are used.
209
+ required by the SEVIRI ash color scheme are used.
210
210
 
211
211
  Returns
212
212
  -------
@@ -306,10 +306,10 @@ class GOES:
306
306
 
307
307
  channels : str | set[str] | None
308
308
  Set of channels or bands for CMIP data. The 16 possible channels are
309
- represented by the strings "C01" to "C16". For the MIT ash color scheme,
309
+ represented by the strings "C01" to "C16". For the SEVIRI ash color scheme,
310
310
  set ``channels=("C11", "C14", "C15")``. For the true color scheme,
311
311
  set ``channels=("C01", "C02", "C03")``. By default, the channels
312
- required by the MIT ash color scheme are used. The channels must have
312
+ required by the SEVIRI ash color scheme are used. The channels must have
313
313
  a common horizontal resolution. The resolutions are:
314
314
 
315
315
  - C01: 1.0 km
@@ -585,7 +585,7 @@ def _concat_c02(ds1: XArrayType, ds2: XArrayType) -> XArrayType:
585
585
  def extract_goes_visualization(
586
586
  da: xr.DataArray,
587
587
  color_scheme: str = "ash",
588
- ash_convention: str = "MIT",
588
+ ash_convention: str = "SEVIRI",
589
589
  gamma: float = 2.2,
590
590
  ) -> tuple[npt.NDArray[np.float32], ccrs.Geostationary, tuple[float, float, float, float]]:
591
591
  """Extract artifacts for visualizing GOES data with the given color scheme.
@@ -597,7 +597,7 @@ def extract_goes_visualization(
597
597
  required by :func:`to_ash`.
598
598
  color_scheme : str = {"ash", "true"}
599
599
  Color scheme to use for visualization.
600
- ash_convention : str = {"MIT", "standard"}
600
+ ash_convention : str = {"SEVIRI", "standard"}
601
601
  Passed into :func:`to_ash`. Only used if ``color_scheme="ash"``.
602
602
  gamma : float = 2.2
603
603
  Passed into :func:`to_true_color`. Only used if ``color_scheme="true"``.
@@ -672,17 +672,18 @@ def to_true_color(da: xr.DataArray, gamma: float = 2.2) -> npt.NDArray[np.float3
672
672
  return np.dstack([red, green, blue])
673
673
 
674
674
 
675
- def to_ash(da: xr.DataArray, convention: str = "MIT") -> npt.NDArray[np.float32]:
675
+ def to_ash(da: xr.DataArray, convention: str = "SEVIRI") -> npt.NDArray[np.float32]:
676
676
  """Compute 3d RGB array for the ASH color scheme.
677
677
 
678
678
  Parameters
679
679
  ----------
680
680
  da : xr.DataArray
681
681
  DataArray of GOES data with appropriate channels.
682
- convention : str = {"MIT", "standard"}
682
+ convention : str = {"SEVIRI", "standard"}
683
683
  Convention for color space.
684
684
 
685
- - MIT convention requires channels C11, C14, C15
685
+ - SEVIRI convention requires channels C11, C14, C15.
686
+ Used in :cite:`kulikSatellitebasedDetectionContrails2019`.
686
687
  - Standard convention requires channels C11, C13, C14, C15
687
688
 
688
689
  Returns
@@ -693,6 +694,7 @@ def to_ash(da: xr.DataArray, convention: str = "MIT") -> npt.NDArray[np.float32]
693
694
  References
694
695
  ----------
695
696
  - `Ash RGB quick guide (the color space and color interpretations) <https://rammb.cira.colostate.edu/training/visit/quick_guides/GOES_Ash_RGB.pdf>`_
697
+ - :cite:`SEVIRIRGBCal`
696
698
  - :cite:`kulikSatellitebasedDetectionContrails2019`
697
699
 
698
700
  Examples
@@ -716,7 +718,7 @@ def to_ash(da: xr.DataArray, convention: str = "MIT") -> npt.NDArray[np.float32]
716
718
  green = c14 - c11
717
719
  blue = c13
718
720
 
719
- elif convention == "MIT":
721
+ elif convention in ["SEVIRI", "MIT"]: # retain MIT for backwards compatibility
720
722
  c11 = da.sel(band_id=11).values # 8.44
721
723
  c14 = da.sel(band_id=14).values # 11.19
722
724
  c15 = da.sel(band_id=15).values # 12.27
@@ -726,7 +728,7 @@ def to_ash(da: xr.DataArray, convention: str = "MIT") -> npt.NDArray[np.float32]
726
728
  blue = c14
727
729
 
728
730
  else:
729
- raise ValueError("Convention must be either 'MIT' or 'standard'")
731
+ raise ValueError("Convention must be either 'SEVIRI' or 'standard'")
730
732
 
731
733
  # See colostate pdf for slightly wider values
732
734
  red = _clip_and_scale(red, -4.0, 2.0)
@@ -26,12 +26,13 @@ from pycontrails.models.cocip import (
26
26
  contrail_properties,
27
27
  radiative_forcing,
28
28
  radiative_heating,
29
+ unterstrasser_wake_vortex,
29
30
  wake_vortex,
30
31
  wind_shear,
31
32
  )
32
33
  from pycontrails.models.cocip.cocip_params import CocipFlightParams
33
34
  from pycontrails.models.emissions.emissions import Emissions
34
- from pycontrails.physics import geo, thermo, units
35
+ from pycontrails.physics import constants, geo, thermo, units
35
36
 
36
37
  logger = logging.getLogger(__name__)
37
38
 
@@ -534,10 +535,11 @@ class Cocip(Model):
534
535
  self.source.size,
535
536
  )
536
537
  if not intersection.any():
537
- raise ValueError(
538
+ msg = (
538
539
  "No intersection between flight waypoints and met domain. "
539
- "Rerun Cocip with `met` overlapping flight."
540
+ "Rerun Cocip with met overlapping flight."
540
541
  )
542
+ raise ValueError(msg)
541
543
 
542
544
  # STEP 4: Begin met interpolation
543
545
  # Unfortunately we use both "u_wind" and "eastward_wind" to refer to the
@@ -837,9 +839,9 @@ class Cocip(Model):
837
839
  T_critical_sac = self._sac_flight["T_critical_sac"]
838
840
 
839
841
  # Flight performance parameters
840
- fuel_dist = (
841
- self._sac_flight.get_data_or_attr("fuel_flow") / self._sac_flight["true_airspeed"]
842
- )
842
+ fuel_flow = self._sac_flight.get_data_or_attr("fuel_flow")
843
+ true_airspeed = self._sac_flight["true_airspeed"]
844
+ fuel_dist = fuel_flow / true_airspeed
843
845
 
844
846
  nvpm_ei_n = self._sac_flight.get_data_or_attr("nvpm_ei_n")
845
847
  ei_h2o = self._sac_flight.fuel.ei_h2o
@@ -889,11 +891,27 @@ class Cocip(Model):
889
891
  air_temperature, air_pressure, air_pressure_1
890
892
  )
891
893
  iwc_1 = contrail_properties.iwc_post_wake_vortex(iwc, iwc_ad)
894
+
895
+ if self.params["unterstrasser_ice_survival_fraction"]:
896
+ wingspan = self._sac_flight.get_data_or_attr("wingspan")
897
+ rhi_0 = thermo.rhi(specific_humidity, air_temperature, air_pressure)
898
+ f_surv = unterstrasser_wake_vortex.ice_particle_number_survival_fraction(
899
+ air_temperature,
900
+ rhi_0,
901
+ ei_h2o,
902
+ wingspan,
903
+ true_airspeed,
904
+ fuel_flow,
905
+ nvpm_ei_n,
906
+ 0.5 * depth, # Taking the mid-point of the contrail plume
907
+ )
908
+ else:
909
+ f_surv = contrail_properties.ice_particle_survival_fraction(iwc, iwc_1)
910
+
892
911
  n_ice_per_m_1 = contrail_properties.ice_particle_number(
893
912
  nvpm_ei_n=nvpm_ei_n,
894
913
  fuel_dist=fuel_dist,
895
- iwc=iwc,
896
- iwc_1=iwc_1,
914
+ f_surv=f_surv,
897
915
  air_temperature=air_temperature,
898
916
  T_crit_sac=T_critical_sac,
899
917
  min_ice_particle_number_nvpm_ei_n=self.params["min_ice_particle_number_nvpm_ei_n"],
@@ -1101,6 +1119,9 @@ class Cocip(Model):
1101
1119
 
1102
1120
  # Initially set energy forcing to 0 because the contrail just formed (age = 0)
1103
1121
  contrail["ef"] = np.zeros_like(contrail["n_ice_per_m"])
1122
+ if self.params["compute_atr20"]:
1123
+ contrail["global_yearly_mean_rf"] = np.zeros_like(contrail["n_ice_per_m"])
1124
+ contrail["atr20"] = np.zeros_like(contrail["n_ice_per_m"])
1104
1125
 
1105
1126
  if not self.params["filter_sac"]:
1106
1127
  contrail["sac"] = self._downwash_flight["sac"]
@@ -1226,6 +1247,10 @@ class Cocip(Model):
1226
1247
 
1227
1248
  # Perform all aggregations
1228
1249
  agg_dict = {"ef": ["sum"], "age": ["max"]}
1250
+ if self.params["compute_atr20"]:
1251
+ agg_dict["global_yearly_mean_rf"] = ["sum"]
1252
+ agg_dict["atr20"] = ["sum"]
1253
+
1229
1254
  rad_keys = ["sdr", "rsr", "olr", "rf_sw", "rf_lw", "rf_net"]
1230
1255
  for key in rad_keys:
1231
1256
  if self.params["verbose_outputs"]:
@@ -1236,6 +1261,10 @@ class Cocip(Model):
1236
1261
  aggregated = grouped.agg(agg_dict)
1237
1262
  aggregated.columns = [f"{k1}_{k2}" for k1, k2 in aggregated.columns]
1238
1263
  aggregated = aggregated.rename(columns={"ef_sum": "ef", "age_max": "contrail_age"})
1264
+ if self.params["compute_atr20"]:
1265
+ aggregated = aggregated.rename(
1266
+ columns={"global_yearly_mean_rf_sum": "global_yearly_mean_rf", "atr20_sum": "atr20"}
1267
+ )
1239
1268
 
1240
1269
  # Join the two
1241
1270
  df = df.join(aggregated)
@@ -2372,6 +2401,14 @@ def calc_timestep_contrail_evolution(
2372
2401
  contrail_2["ef"] = energy_forcing_2
2373
2402
  contrail_2["age"][energy_forcing_2 == 0.0] = np.timedelta64(0, "ns")
2374
2403
 
2404
+ if params["compute_atr20"]:
2405
+ contrail_2["global_yearly_mean_rf"] = (
2406
+ contrail_2["ef"] / constants.surface_area_earth / constants.seconds_per_year
2407
+ )
2408
+ contrail_2["atr20"] = (
2409
+ params["global_rf_to_atr20_factor"] * contrail_2["global_yearly_mean_rf"]
2410
+ )
2411
+
2375
2412
  # filter contrail_2 by persistent waypoints, if any continuous segments are left
2376
2413
  logger.debug(
2377
2414
  "Fraction of waypoints surviving: %s / %s",
@@ -2408,6 +2445,9 @@ def calc_timestep_contrail_evolution(
2408
2445
  continuous = final_contrail["continuous"]
2409
2446
  final_contrail["ef"][~continuous] = 0.0
2410
2447
  final_contrail["age"][~continuous] = np.timedelta64(0, "ns")
2448
+ if params["compute_atr20"]:
2449
+ final_contrail["global_yearly_mean_rf"][~continuous] = 0.0
2450
+ final_contrail["atr20"][~continuous] = 0.0
2411
2451
  return final_contrail
2412
2452
 
2413
2453
 
@@ -102,7 +102,7 @@ class CocipParams(ModelParams):
102
102
  met_latitude_buffer: tuple[float, float] = (10.0, 10.0)
103
103
 
104
104
  #: Met level buffer [:math:`hPa`] for Cocip initialization and evolution.
105
- met_level_buffer: tuple[float, float] = (200.0, 200.0)
105
+ met_level_buffer: tuple[float, float] = (40.0, 40.0)
106
106
 
107
107
  # ---------
108
108
  # Filtering
@@ -136,6 +136,16 @@ class CocipParams(ModelParams):
136
136
  #: :attr:`CocipGridParams.verbose_outputs_evolution`.
137
137
  verbose_outputs: bool = False
138
138
 
139
+ #: Add additional metric of ATR20 and global yearly mean RF to model output.
140
+ #: These are not standard CoCiP outputs but based on the derivation used
141
+ #: in the first supplement to :cite:`yinPredictingClimateImpact2023`. ATR20 is defined
142
+ #: as the average temperature response over a 20 year horizon.
143
+ compute_atr20: bool = False
144
+
145
+ #: Constant factor used to convert global- and year-mean RF, [:math:`W m^{-2}`],
146
+ #: to ATR20, [:math:`K`], given by :cite:`yinPredictingClimateImpact2023`.
147
+ global_rf_to_atr20_factor: float = 0.0151
148
+
139
149
  # ----------------
140
150
  # Model parameters
141
151
  # ----------------
@@ -174,6 +184,15 @@ class CocipParams(ModelParams):
174
184
  #: :attr:`radiative_heating_effects` is enabled.
175
185
  max_depth: float = 1500.0
176
186
 
187
+ #: Experimental. Improved ice crystal number survival fraction in the wake vortex phase.
188
+ #: Implement :cite:`unterstrasserPropertiesYoungContrails2016`, who developed a
189
+ #: parametric model that estimates the survival fraction of the contrail ice crystal
190
+ #: number after the wake vortex phase based on the results from large eddy simulations.
191
+ #: This replicates Fig. 4 of :cite:`karcherFormationRadiativeForcing2018`.
192
+ #:
193
+ #: .. versionadded:: 0.50.1
194
+ unterstrasser_ice_survival_fraction: bool = False
195
+
177
196
  #: Experimental. Radiative heating effects on contrail cirrus properties.
178
197
  #: Terrestrial and solar radiances warm the contrail ice particles and cause
179
198
  #: convective turbulence. This effect is expected to enhance vertical mixing
@@ -204,8 +204,7 @@ def iwc_post_wake_vortex(
204
204
  def ice_particle_number(
205
205
  nvpm_ei_n: npt.NDArray[np.float64],
206
206
  fuel_dist: npt.NDArray[np.float64],
207
- iwc: npt.NDArray[np.float64],
208
- iwc_1: npt.NDArray[np.float64],
207
+ f_surv: npt.NDArray[np.float64],
209
208
  air_temperature: npt.NDArray[np.float64],
210
209
  T_crit_sac: npt.NDArray[np.float64],
211
210
  min_ice_particle_number_nvpm_ei_n: float,
@@ -223,11 +222,8 @@ def ice_particle_number(
223
222
  black carbon number emissions index, [:math:`kg^{-1}`]
224
223
  fuel_dist : npt.NDArray[np.float64]
225
224
  fuel consumption of the flight segment per distance traveled, [:math:`kg m^{-1}`]
226
- iwc : npt.NDArray[np.float64]
227
- initial ice water content at each flight waypoint before the wake vortex
228
- phase, [:math:`kg_{H_{2}O}/kg_{air}`]
229
- iwc_1 : npt.NDArray[np.float64]
230
- ice water content after the wake vortex phase, [:math:`kg_{H_{2}O}/kg_{air}`]
225
+ f_surv : npt.NDArray[np.float64]
226
+ Fraction of contrail ice particle number that survive the wake vortex phase.
231
227
  air_temperature : npt.NDArray[np.float64]
232
228
  ambient temperature for each waypoint, [:math:`K`]
233
229
  T_crit_sac : npt.NDArray[np.float64]
@@ -241,7 +237,6 @@ def ice_particle_number(
241
237
  npt.NDArray[np.float64]
242
238
  initial number of ice particles per distance after the wake vortex phase, [:math:`# m^{-1}`]
243
239
  """
244
- f_surv = ice_particle_survival_factor(iwc, iwc_1)
245
240
  f_activation = ice_particle_activation_rate(air_temperature, T_crit_sac)
246
241
  nvpm_ei_n_activated = nvpm_ei_n * f_activation
247
242
  return fuel_dist * np.maximum(nvpm_ei_n_activated, min_ice_particle_number_nvpm_ei_n) * f_surv
@@ -289,7 +284,7 @@ def ice_particle_activation_rate(
289
284
  return -0.661 * np.exp(d_temp) + 1.0
290
285
 
291
286
 
292
- def ice_particle_survival_factor(
287
+ def ice_particle_survival_fraction(
293
288
  iwc: npt.NDArray[np.float64], iwc_1: npt.NDArray[np.float64]
294
289
  ) -> npt.NDArray[np.float64]:
295
290
  """