pycontrails 0.54.5__cp312-cp312-macosx_10_13_x86_64.whl → 0.54.6__cp312-cp312-macosx_10_13_x86_64.whl

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

Potentially problematic release.


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

pycontrails/_version.py CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.54.5'
16
- __version_tuple__ = version_tuple = (0, 54, 5)
15
+ __version__ = version = '0.54.6'
16
+ __version_tuple__ = version_tuple = (0, 54, 6)
@@ -96,22 +96,52 @@ class AircraftPerformance(Model):
96
96
 
97
97
  source: Flight
98
98
 
99
- @abc.abstractmethod
100
99
  @overload
101
100
  def eval(self, source: Fleet, **params: Any) -> Fleet: ...
102
101
 
103
- @abc.abstractmethod
104
102
  @overload
105
103
  def eval(self, source: Flight, **params: Any) -> Flight: ...
106
104
 
107
- @abc.abstractmethod
108
105
  @overload
109
106
  def eval(self, source: None = ..., **params: Any) -> NoReturn: ...
110
107
 
111
- @abc.abstractmethod
112
108
  def eval(self, source: Flight | None = None, **params: Any) -> Flight:
113
109
  """Evaluate the aircraft performance model.
114
110
 
111
+ Parameters
112
+ ----------
113
+ source : Flight
114
+ Flight trajectory to evaluate. Can be a :class:`Flight` or :class:`Fleet`.
115
+ params : Any
116
+ Override :attr:`params` with keyword arguments.
117
+
118
+ Returns
119
+ -------
120
+ Flight
121
+ Flight trajectory with aircraft performance data.
122
+ """
123
+ self.update_params(params)
124
+ self.set_source(source)
125
+ self.source = self.require_source_type(Flight)
126
+ self.downselect_met()
127
+ self.set_source_met()
128
+ self._cleanup_indices()
129
+
130
+ # Calculate true airspeed if not included on source
131
+ self.ensure_true_airspeed_on_source()
132
+
133
+ if isinstance(self.source, Fleet):
134
+ fls = [self.eval_flight(fl) for fl in self.source.to_flight_list()]
135
+ self.source = Fleet.from_seq(fls, attrs=self.source.attrs, broadcast_numeric=False)
136
+ return self.source
137
+
138
+ self.source = self.eval_flight(self.source)
139
+ return self.source
140
+
141
+ @abc.abstractmethod
142
+ def eval_flight(self, fl: Flight) -> Flight:
143
+ """Evaluate the aircraft performance model on a single flight trajectory.
144
+
115
145
  The implementing model adds the following fields to the source flight:
116
146
 
117
147
  - ``aircraft_mass``: aircraft mass at each waypoint, [:math:`kg`]
@@ -128,18 +158,6 @@ class AircraftPerformance(Model):
128
158
  - ``max_mach``: maximum Mach number
129
159
  - ``max_altitude``: maximum altitude, [:math:`m`]
130
160
  - ``total_fuel_burn``: total fuel burn, [:math:`kg`]
131
-
132
- Parameters
133
- ----------
134
- source : Flight
135
- Flight trajectory to evaluate.
136
- params : Any
137
- Override :attr:`params` with keyword arguments.
138
-
139
- Returns
140
- -------
141
- Flight
142
- Flight trajectory with aircraft performance data.
143
161
  """
144
162
 
145
163
  @override
@@ -162,7 +162,7 @@ def find_nearest_airport(
162
162
  ) & airports["latitude"].between((latitude - bbox), (latitude + bbox))
163
163
 
164
164
  # Find the nearest airport from largest to smallest airport type
165
- search_priority = ["large_airport", "medium_airport", "small_airport"]
165
+ search_priority = ("large_airport", "medium_airport", "small_airport")
166
166
 
167
167
  for airport_type in search_priority:
168
168
  is_airport_type = airports["type"] == airport_type
@@ -171,7 +171,7 @@ def find_nearest_airport(
171
171
  if len(nearest_airports) == 1:
172
172
  return nearest_airports["icao_code"].values[0]
173
173
 
174
- elif len(nearest_airports) > 1:
174
+ if len(nearest_airports) > 1:
175
175
  distance = distance_to_airports(
176
176
  nearest_airports,
177
177
  longitude,
@@ -181,8 +181,7 @@ def find_nearest_airport(
181
181
  i_nearest = np.argmin(distance)
182
182
  return nearest_airports["icao_code"].values[i_nearest]
183
183
 
184
- else:
185
- continue
184
+ continue
186
185
 
187
186
  return None
188
187
 
@@ -891,7 +891,7 @@ class Flight(GeoVectorDataset):
891
891
  """
892
892
  methods = "geodesic", "linear"
893
893
  if fill_method not in methods:
894
- raise ValueError(f'Unknown `fill_method`. Supported methods: {", ".join(methods)}')
894
+ raise ValueError(f"Unknown `fill_method`. Supported methods: {', '.join(methods)}")
895
895
 
896
896
  # STEP 0: If self is empty, return an empty flight
897
897
  if not self:
@@ -1982,9 +1982,7 @@ def filter_altitude(
1982
1982
  result[i0:i1] = altitude_filt[i0:i1]
1983
1983
 
1984
1984
  # reapply Savitzky-Golay filter to smooth climb and descent
1985
- result = _sg_filter(result, window_length=kernel_size)
1986
-
1987
- return result
1985
+ return _sg_filter(result, window_length=kernel_size)
1988
1986
 
1989
1987
 
1990
1988
  def segment_duration(
@@ -21,24 +21,24 @@ def to_atc_plan(plan: dict[str, Any]) -> str:
21
21
  --------
22
22
  :func:`parse_atc_plan`
23
23
  """
24
- ret = f'(FPL-{plan["callsign"]}-{plan["flight_rules"]}'
25
- ret += f'{plan["type_of_flight"]}\n'
24
+ ret = f"(FPL-{plan['callsign']}-{plan['flight_rules']}"
25
+ ret += f"{plan['type_of_flight']}\n"
26
26
  ret += "-"
27
27
  if "number_aircraft" in plan and plan["number_aircraft"] <= 10:
28
28
  ret += plan["number_aircraft"]
29
- ret += f'{plan["type_of_aircraft"]}/{plan["wake_category"]}-'
30
- ret += f'{plan["equipment"]}/{plan["transponder"]}\n'
31
- ret += f'-{plan["departure_icao"]}{plan["time"]}\n'
32
- ret += f'-{plan["speed_type"]}{plan["speed"]}{plan["level_type"]}'
33
- ret += f'{plan["level"]} {plan["route"]}\n'
29
+ ret += f"{plan['type_of_aircraft']}/{plan['wake_category']}-"
30
+ ret += f"{plan['equipment']}/{plan['transponder']}\n"
31
+ ret += f"-{plan['departure_icao']}{plan['time']}\n"
32
+ ret += f"-{plan['speed_type']}{plan['speed']}{plan['level_type']}"
33
+ ret += f"{plan['level']} {plan['route']}\n"
34
34
  if "destination_icao" in plan and "duration" in plan:
35
- ret += f'-{plan["destination_icao"]}{plan["duration"]}'
35
+ ret += f"-{plan['destination_icao']}{plan['duration']}"
36
36
  if "alt_icao" in plan:
37
- ret += f' {plan["alt_icao"]}'
37
+ ret += f" {plan['alt_icao']}"
38
38
  if "second_alt_icao" in plan:
39
- ret += f' {plan["second_alt_icao"]}'
39
+ ret += f" {plan['second_alt_icao']}"
40
40
  ret += "\n"
41
- ret += f'-{plan["other_info"]})\n'
41
+ ret += f"-{plan['other_info']})\n"
42
42
  if "supplementary_info" in plan:
43
43
  ret += " ".join([f"{i[0]}/{i[1]}" for i in plan["supplementary_info"].items()])
44
44
 
@@ -282,6 +282,35 @@ VerticalVelocity = MetVariable(
282
282
  ),
283
283
  )
284
284
 
285
+ MassFractionOfCloudLiquidWaterInAir = MetVariable(
286
+ short_name="clw",
287
+ standard_name="mass_fraction_of_cloud_liquid_water_in_air",
288
+ long_name="Mass fraction of cloud liquid water in air",
289
+ units="kg kg**-1",
290
+ level_type="isobaricInhPa",
291
+ amip="clw",
292
+ description=("The mass fraction of cloud liquid water in moist air."),
293
+ )
294
+
295
+ MassFractionOfCloudIceInAir = MetVariable(
296
+ short_name="cli",
297
+ standard_name="mass_fraction_of_cloud_ice_in_air",
298
+ long_name="Mass fraction of cloud ice in air",
299
+ units="kg kg**-1",
300
+ level_type="isobaricInhPa",
301
+ amip="cli",
302
+ description=("The mass fraction of cloud ice in moist air."),
303
+ )
304
+
305
+ CloudAreaFractionInAtmosphereLayer = MetVariable(
306
+ short_name="cl",
307
+ standard_name="cloud_area_fraction_in_atmosphere_layer",
308
+ long_name="Cloud area fraction in atmosphere layer",
309
+ units="[0 - 1]",
310
+ level_type="isobaricInhPa",
311
+ description=("The fraction of the horizontal area of a grid cell that contains cloud."),
312
+ )
313
+
285
314
 
286
315
  # ----
287
316
  # Single level variables
@@ -305,3 +334,36 @@ SurfacePressure = MetVariable(
305
334
  "Earth's surface represented at a fixed point."
306
335
  ),
307
336
  )
337
+
338
+ TOANetDownwardShortwaveFlux = MetVariable(
339
+ short_name="rst",
340
+ standard_name="toa_net_downward_shortwave_flux",
341
+ long_name="TOA net downward shortwave flux",
342
+ units="W m**-2",
343
+ level_type="nominalTop",
344
+ amip="rst",
345
+ description=(
346
+ '"shortwave" means shortwave radiation. "toa" means top of atmosphere. '
347
+ '"Downward" indicates a vector component which is positive when directed '
348
+ "downward (negative upward). Net downward radiation is the difference "
349
+ "between radiation from above (downwelling) and radiation from below (upwelling). "
350
+ "In accordance with common usage in geophysical disciplines, "
351
+ '"flux" implies per unit area, called "flux density" in physics.'
352
+ ),
353
+ )
354
+
355
+ TOAOutgoingLongwaveFlux = MetVariable(
356
+ short_name="rlut",
357
+ standard_name="toa_outgoing_longwave_flux",
358
+ long_name="TOA outgoing longwave_flux",
359
+ units="W m**-2",
360
+ level_type="nominalTop",
361
+ amip="rlut",
362
+ description=(
363
+ '"longwave" means longwave radiation. "toa" means top of atmosphere. '
364
+ "The TOA outgoing longwave flux is the upwelling thermal radiative flux, "
365
+ 'often called the "outgoing longwave radiation" or "OLR". '
366
+ "In accordance with common usage in geophysical disciplines, "
367
+ '"flux" implies per unit area, called "flux density" in physics.'
368
+ ),
369
+ )
@@ -986,6 +986,7 @@ def _prepare_q(
986
986
  return _prepare_q_cubic_spline(da, level)
987
987
 
988
988
  raise_invalid_q_method_error(q_method)
989
+ return None
989
990
 
990
991
 
991
992
  def _prepare_q_log_q_log_p(
@@ -265,7 +265,7 @@ def _find_match(
265
265
 
266
266
  # list of MetVariable options
267
267
  # here we extract the first MetVariable in var that is supported
268
- elif isinstance(var, list | tuple):
268
+ if isinstance(var, list | tuple):
269
269
  for v in var:
270
270
  # sanity check since we don't support other types as lists
271
271
  if not isinstance(v, MetVariable):
@@ -88,9 +88,8 @@ class ERA5(ECMWFAPI):
88
88
  If None, cache is turned off.
89
89
  url : str | None
90
90
  Override the default `cdsapi <https://github.com/ecmwf/cdsapi>`_ url.
91
- As of August 2024, the url for the `CDS-Beta <https://cds-beta.climate.copernicus.eu>`_
92
- is "https://cds-beta.climate.copernicus.eu/api", and the url for the legacy server is
93
- "https://cds.climate.copernicus.eu/api/v2". If None, the url is set
91
+ As of January 2025, the url for the `CDS Server <https://cds.climate.copernicus.eu>`_
92
+ is "https://cds.climate.copernicus.eu/api". If None, the url is set
94
93
  by the ``CDSAPI_URL`` environment variable. If this is not defined, the
95
94
  ``cdsapi`` package will determine the url.
96
95
  key : str | None
@@ -539,12 +538,12 @@ class ERA5(ECMWFAPI):
539
538
  LOG.debug("Input dataset processed with pycontrails > 0.29")
540
539
  return ds
541
540
 
542
- # For "reanalysis-era5-single-levels"
543
- # then the netcdf file does not contain the dimension "level"
541
+ # For "reanalysis-era5-single-levels",
542
+ # the netcdf file does not contain the dimension "level"
544
543
  if self.is_single_level:
545
544
  ds = ds.expand_dims(level=self.pressure_levels)
546
545
 
547
- # New CDS-Beta gives "valid_time" instead of "time"
546
+ # New CDS (Aug 2024) gives "valid_time" instead of "time"
548
547
  # and "pressure_level" instead of "level"
549
548
  if "valid_time" in ds:
550
549
  ds = ds.rename(valid_time="time")
@@ -119,9 +119,8 @@ class ERA5ModelLevel(ECMWFAPI):
119
119
  By default, False.
120
120
  url : str | None
121
121
  Override the default `cdsapi <https://github.com/ecmwf/cdsapi>`_ url.
122
- As of August 2024, the url for the `CDS-Beta <https://cds-beta.climate.copernicus.eu>`_
123
- is "https://cds-beta.climate.copernicus.eu/api", and the url for the legacy server is
124
- "https://cds.climate.copernicus.eu/api/v2". If None, the url is set
122
+ As of January 2025, the url for the `CDS Server <https://cds.climate.copernicus.eu>`_
123
+ is "https://cds.climate.copernicus.eu/api". If None, the url is set
125
124
  by the ``CDSAPI_URL`` environment variable. If this is not defined, the
126
125
  ``cdsapi`` package will determine the url.
127
126
  key : str | None
@@ -465,13 +464,13 @@ class ERA5ModelLevel(ECMWFAPI):
465
464
  ds_ml = xr.open_dataset(ml_target)
466
465
  lnsp = xr.open_dataarray(lnsp_target)
467
466
 
468
- # New CDS-Beta gives "valid_time" instead of "time"
467
+ # New CDS (Aug 2024) gives "valid_time" instead of "time"
469
468
  if "valid_time" in ds_ml:
470
469
  ds_ml = ds_ml.rename(valid_time="time")
471
470
  if "valid_time" in lnsp.dims:
472
471
  lnsp = lnsp.rename(valid_time="time")
473
472
 
474
- # The legacy CDS gives "level" instead of "model_level"
473
+ # Legacy CDS (prior to Aug 2024) gives "level" instead of "model_level"
475
474
  if "level" in ds_ml.dims:
476
475
  ds_ml = ds_ml.rename(level="model_level")
477
476
 
@@ -247,9 +247,7 @@ class IFS(metsource.MetDataSource):
247
247
  ds_fl = ds_fl.drop_vars(names=["hyai", "hybi", "hyam", "hybm"])
248
248
 
249
249
  # merge all datasets using the "ds_fl" dimensions as the join keys
250
- ds = xr.merge([ds_fl, ds_full, ds_surface, ds_rad], join="left") # order matters!
251
-
252
- return ds
250
+ return xr.merge([ds_fl, ds_full, ds_surface, ds_rad], join="left") # order matters!
253
251
 
254
252
  def _calc_geopotential(self, ds: xr.Dataset) -> xr.DataArray:
255
253
  warnings.warn(
@@ -570,9 +570,7 @@ class GFSForecast(metsource.MetDataSource):
570
570
  ds = ds.expand_dims("time")
571
571
 
572
572
  # drop step/number
573
- ds = ds.drop_vars(["step", "nominalTop", "surface"], errors="ignore")
574
-
575
- return ds
573
+ return ds.drop_vars(["step", "nominalTop", "surface"], errors="ignore")
576
574
 
577
575
  def _process_dataset(self, ds: xr.Dataset, **kwargs: Any) -> met.MetDataset:
578
576
  """Process the :class:`xr.Dataset` opened from cache or local files.
@@ -68,12 +68,26 @@ class Cocip(Model):
68
68
  -----
69
69
  **Inputs**
70
70
 
71
- The required meteorology variables depend on the data source (e.g. ECMWF, GFS).
71
+ The required meteorology variables depend on the data source. :class:`Cocip`
72
+ supports data-source-specific variables from ECMWF models (HRES, ERA5) and the NCEP GFS, plus
73
+ a generic set of model-agnostic variables.
72
74
 
73
75
  See :attr:`met_variables` and :attr:`rad_variables` for the list of required variables
74
76
  to the ``met`` and ``rad`` parameters, respectively.
75
77
  When an item in one of these arrays is a :class:`tuple`, variable keys depend on data source.
76
78
 
79
+ A warning will be raised if meteorology data is from a source not currently supported by
80
+ a pycontrails datalib. In this case it is the responsibility of the user to ensure that
81
+ meteorology data is formatted correctly. The warning can be suppressed with a context manager:
82
+
83
+ .. code-block:: python
84
+ :emphasize-lines: 2,3
85
+
86
+ import warnings
87
+ with warnings.catch_warnings():
88
+ warnings.simplefilter("ignore", category=UserWarning, message="Unknown provider")
89
+ cocip = Cocip(met, rad, ...)
90
+
77
91
  The current list of required variables (labelled by ``"standard_name"``):
78
92
 
79
93
  .. list-table:: Variable keys for pressure level data
@@ -82,24 +96,31 @@ class Cocip(Model):
82
96
  * - Parameter
83
97
  - ECMWF
84
98
  - GFS
99
+ - Generic
85
100
  * - Air Temperature
86
101
  - ``air_temperature``
87
102
  - ``air_temperature``
103
+ - ``air_temperature``
88
104
  * - Specific Humidity
89
105
  - ``specific_humidity``
90
106
  - ``specific_humidity``
107
+ - ``specific_humidity``
91
108
  * - Eastward wind
92
109
  - ``eastward_wind``
93
110
  - ``eastward_wind``
111
+ - ``eastward_wind``
94
112
  * - Northward wind
95
113
  - ``northward_wind``
96
114
  - ``northward_wind``
115
+ - ``northward_wind``
97
116
  * - Vertical velocity
98
117
  - ``lagrangian_tendency_of_air_pressure``
99
118
  - ``lagrangian_tendency_of_air_pressure``
119
+ - ``lagrangian_tendency_of_air_pressure``
100
120
  * - Ice water content
101
121
  - ``specific_cloud_ice_water_content``
102
122
  - ``ice_water_mixing_ratio``
123
+ - ``mass_fraction_of_cloud_ice_in_air``
103
124
 
104
125
  .. list-table:: Variable keys for single-level radiation data
105
126
  :header-rows: 1
@@ -107,12 +128,15 @@ class Cocip(Model):
107
128
  * - Parameter
108
129
  - ECMWF
109
130
  - GFS
131
+ - Generic
110
132
  * - Top solar radiation
111
133
  - ``top_net_solar_radiation``
112
134
  - ``toa_upward_shortwave_flux``
135
+ - ``toa_net_downward_shortwave_flux``
113
136
  * - Top thermal radiation
114
137
  - ``top_net_thermal_radiation``
115
138
  - ``toa_upward_longwave_flux``
139
+ - ``toa_outgoing_longwave_flux``
116
140
 
117
141
  **Modifications**
118
142
 
@@ -214,14 +238,26 @@ class Cocip(Model):
214
238
  met_var.EastwardWind,
215
239
  met_var.NorthwardWind,
216
240
  met_var.VerticalVelocity,
217
- (ecmwf.SpecificCloudIceWaterContent, gfs.CloudIceWaterMixingRatio),
241
+ (
242
+ met_var.MassFractionOfCloudIceInAir,
243
+ ecmwf.SpecificCloudIceWaterContent,
244
+ gfs.CloudIceWaterMixingRatio,
245
+ ),
218
246
  )
219
247
 
220
248
  #: Required single-level top of atmosphere radiation variables.
221
249
  #: Variable keys depend on data source (e.g. ECMWF, GFS).
222
250
  rad_variables = (
223
- (ecmwf.TopNetSolarRadiation, gfs.TOAUpwardShortwaveRadiation),
224
- (ecmwf.TopNetThermalRadiation, gfs.TOAUpwardLongwaveRadiation),
251
+ (
252
+ met_var.TOANetDownwardShortwaveFlux,
253
+ ecmwf.TopNetSolarRadiation,
254
+ gfs.TOAUpwardShortwaveRadiation,
255
+ ),
256
+ (
257
+ met_var.TOAOutgoingLongwaveFlux,
258
+ ecmwf.TopNetThermalRadiation,
259
+ gfs.TOAUpwardLongwaveRadiation,
260
+ ),
225
261
  )
226
262
 
227
263
  #: Minimal set of met variables needed to run the model after pre-processing.
@@ -242,7 +278,11 @@ class Cocip(Model):
242
278
  #: Moved Geopotential from :attr:`met_variables` to :attr:`optional_met_variables`
243
279
  optional_met_variables = (
244
280
  (met_var.Geopotential, met_var.GeopotentialHeight),
245
- (ecmwf.CloudAreaFractionInLayer, gfs.TotalCloudCoverIsobaric),
281
+ (
282
+ met_var.CloudAreaFractionInAtmosphereLayer,
283
+ ecmwf.CloudAreaFractionInLayer,
284
+ gfs.TotalCloudCoverIsobaric,
285
+ ),
246
286
  )
247
287
 
248
288
  #: Met data is not optional
@@ -575,10 +615,12 @@ class Cocip(Model):
575
615
  if verbose_outputs:
576
616
  interpolate_met(met, self.source, "tau_cirrus", **interp_kwargs)
577
617
 
578
- # handle ECMWF/GFS ciwc variables
618
+ # handle ECMWF/GFS/generic ciwc variables
579
619
  if (key := "specific_cloud_ice_water_content") in met: # noqa: SIM114
580
620
  interpolate_met(met, self.source, key, **interp_kwargs)
581
- elif (key := "ice_water_mixing_ratio") in met:
621
+ elif (key := "ice_water_mixing_ratio") in met: # noqa: SIM114
622
+ interpolate_met(met, self.source, key, **interp_kwargs)
623
+ elif (key := "mass_fraction_of_cloud_ice_in_air") in met:
582
624
  interpolate_met(met, self.source, key, **interp_kwargs)
583
625
 
584
626
  self.source["rho_air"] = thermo.rho_d(
@@ -1587,8 +1629,7 @@ def _process_rad(rad: MetDataset) -> MetDataset:
1587
1629
  rad.data["time"].attrs["shift_radiation_time"] = "variable"
1588
1630
  return rad
1589
1631
 
1590
- else:
1591
- shift_radiation_time = -np.timedelta64(30, "m")
1632
+ shift_radiation_time = -np.timedelta64(30, "m")
1592
1633
 
1593
1634
  elif dataset == "ERA5" and product == "ensemble":
1594
1635
  shift_radiation_time = -np.timedelta64(90, "m")
@@ -1893,8 +1934,8 @@ def calc_shortwave_radiation(
1893
1934
  Raises
1894
1935
  ------
1895
1936
  ValueError
1896
- If ``rad`` does not contain ``"toa_upward_shortwave_flux"`` or
1897
- ``"top_net_solar_radiation"`` variable.
1937
+ If ``rad`` does not contain ``"toa_net_downward_shortwave_flux"``,
1938
+ ``"toa_upward_shortwave_flux"`` or ``"top_net_solar_radiation"`` variable.
1898
1939
 
1899
1940
  Notes
1900
1941
  -----
@@ -1918,6 +1959,13 @@ def calc_shortwave_radiation(
1918
1959
  sdr = geo.solar_direct_radiation(longitude, latitude, time, threshold_cos_sza=0.01)
1919
1960
  vector["sdr"] = sdr
1920
1961
 
1962
+ # Generic contains net downward shortwave flux at TOA (SDR - RSR) in W/m2
1963
+ generic_key = "toa_net_downward_shortwave_flux"
1964
+ if generic_key in rad:
1965
+ tnsr = interpolate_met(rad, vector, generic_key, **interp_kwargs)
1966
+ vector["rsr"] = np.maximum(sdr - tnsr, 0.0)
1967
+ return
1968
+
1921
1969
  # GFS contains RSR (toa_upward_shortwave_flux) variable directly
1922
1970
  gfs_key = "toa_upward_shortwave_flux"
1923
1971
  if gfs_key in rad:
@@ -1926,10 +1974,13 @@ def calc_shortwave_radiation(
1926
1974
 
1927
1975
  ecmwf_key = "top_net_solar_radiation"
1928
1976
  if ecmwf_key not in rad:
1929
- msg = f"'rad' data must contain either '{gfs_key}' or '{ecmwf_key}' (ECMWF) variable."
1977
+ msg = (
1978
+ f"'rad' data must contain either '{generic_key}' (generic), "
1979
+ f"'{gfs_key}' (GFS), or '{ecmwf_key}' (ECMWF) variable."
1980
+ )
1930
1981
  raise ValueError(msg)
1931
1982
 
1932
- # ECMWF contains "top_net_solar_radiation" which is SDR - RSR
1983
+ # ECMWF also contains net downward shortwave flux at TOA, but possibly as an accumulation
1933
1984
  tnsr = interpolate_met(rad, vector, ecmwf_key, **interp_kwargs)
1934
1985
  tnsr = _rad_accumulation_to_average_instantaneous(rad, ecmwf_key, tnsr)
1935
1986
  vector.update({ecmwf_key: tnsr})
@@ -1958,14 +2009,20 @@ def calc_outgoing_longwave_radiation(
1958
2009
  Raises
1959
2010
  ------
1960
2011
  ValueError
1961
- If ``rad`` does not contain a ``"toa_upward_longwave_flux"``
1962
- or ``"top_net_thermal_radiation"`` variable.
2012
+ If ``rad`` does not contain a ``"toa_outgoing_longwave_flux"``,
2013
+ ``"toa_upward_longwave_flux"`` or ``"top_net_thermal_radiation"`` variable.
1963
2014
  """
1964
2015
 
1965
2016
  if "olr" in vector:
1966
- return None
2017
+ return
2018
+
2019
+ # Generic contains OLR (toa_outgoing_longwave_flux) directly
2020
+ generic_key = "toa_outgoing_longwave_flux"
2021
+ if generic_key in rad:
2022
+ interpolate_met(rad, vector, generic_key, "olr", **interp_kwargs)
2023
+ return
1967
2024
 
1968
- # GFS contains OLR (toa_upward_longwave_flux) variable directly
2025
+ # GFS contains OLR (toa_upward_longwave_flux) directly
1969
2026
  gfs_key = "toa_upward_longwave_flux"
1970
2027
  if gfs_key in rad:
1971
2028
  interpolate_met(rad, vector, gfs_key, "olr", **interp_kwargs)
@@ -1974,7 +2031,10 @@ def calc_outgoing_longwave_radiation(
1974
2031
  # ECMWF contains "top_net_thermal_radiation" which is -1 * OLR
1975
2032
  ecmwf_key = "top_net_thermal_radiation"
1976
2033
  if ecmwf_key not in rad:
1977
- msg = f"'rad' data must contain either '{gfs_key}' or '{ecmwf_key}' (ECMWF) variable."
2034
+ msg = (
2035
+ f"'rad' data must contain either '{generic_key}' (generic), "
2036
+ f"'{gfs_key}' (GFS), or '{ecmwf_key}' (ECMWF) variable."
2037
+ )
1978
2038
  raise ValueError(msg)
1979
2039
 
1980
2040
  tntr = interpolate_met(rad, vector, ecmwf_key, **interp_kwargs)
@@ -2259,3 +2259,4 @@ def compare_cocip_with_goes(
2259
2259
  plt.close()
2260
2260
 
2261
2261
  return output_path
2262
+ return None
@@ -63,6 +63,15 @@ class DryAdvectionParams(models.AdvectionBuffers):
63
63
  # If None, only pointwise advection is simulated without wind shear effects.
64
64
  azimuth: float | None = 0.0
65
65
 
66
+ #: Add additional intermediate variables to the output vector.
67
+ #: This includes interpolated met variables and wind-shear-derived geometry.
68
+ verbose_outputs: bool = False
69
+
70
+ #: Whether to include ``source`` points in the output vector. Enabling allows
71
+ #: the user to view additional data (e.g., interpolated met variables) for
72
+ #: source points as well as evolved points.
73
+ include_source_in_output: bool = False
74
+
66
75
 
67
76
  class DryAdvection(models.Model):
68
77
  """Simulate "dry advection" of an emissions plume with an elliptical cross section.
@@ -154,42 +163,51 @@ class DryAdvection(models.Model):
154
163
  sedimentation_rate = self.params["sedimentation_rate"]
155
164
  dz_m = self.params["dz_m"]
156
165
  max_depth = self.params["max_depth"]
166
+ verbose_outputs = self.params["verbose_outputs"]
157
167
 
158
168
  source_time = self.source["time"]
159
169
  t0 = pd.Timestamp(source_time.min()).floor(pd.Timedelta(dt_integration)).to_numpy()
160
170
  t1 = source_time.max()
161
171
  timesteps = np.arange(t0 + dt_integration, t1 + dt_integration + max_age, dt_integration)
162
172
 
163
- vector = GeoVectorDataset()
173
+ vector2 = GeoVectorDataset()
164
174
  met = None
165
175
 
166
176
  evolved = []
167
177
  for t in timesteps:
168
178
  filt = (source_time < t) & (source_time >= t - dt_integration)
169
- vector = vector + self.source.filter(filt, copy=False)
179
+ vector1 = vector2 + self.source.filter(filt, copy=False)
170
180
 
171
- t0 = vector["time"].min()
172
- t1 = vector["time"].max()
181
+ t0 = vector1["time"].min()
182
+ t1 = vector1["time"].max()
173
183
  met = maybe_downselect_mds(self.met, met, t0, t1)
174
184
 
175
- vector = _evolve_one_step(
185
+ vector2 = _evolve_one_step(
176
186
  met,
177
- vector,
187
+ vector1,
178
188
  t,
179
189
  sedimentation_rate=sedimentation_rate,
180
190
  dz_m=dz_m,
181
191
  max_depth=max_depth,
192
+ verbose_outputs=verbose_outputs,
182
193
  **interp_kwargs,
183
194
  )
195
+ evolved.append(vector1)
184
196
 
185
- filt = (vector["age"] <= max_age) & vector.coords_intersect_met(self.met)
186
- vector = vector.filter(filt)
197
+ filt = (vector2["age"] <= max_age) & vector2.coords_intersect_met(self.met)
198
+ vector2 = vector2.filter(filt)
187
199
 
188
- evolved.append(vector)
189
- if not vector and np.all(source_time < t):
200
+ if not vector2 and np.all(source_time < t):
190
201
  break
191
202
 
192
- return GeoVectorDataset.sum(evolved, fill_value=np.nan)
203
+ evolved.append(vector2)
204
+ out = GeoVectorDataset.sum(evolved, fill_value=np.nan)
205
+
206
+ if self.params["include_source_in_output"]:
207
+ return out
208
+
209
+ filt = out["age"] > np.timedelta64(0, "ns")
210
+ return out.filter(filt)
193
211
 
194
212
  def _prepare_source(self) -> GeoVectorDataset:
195
213
  r"""Prepare :attr:`source` vector for advection by wind-shear-derived variables.
@@ -364,8 +382,12 @@ def _calc_geometry(
364
382
  dz_m: float,
365
383
  dt: npt.NDArray[np.timedelta64] | np.timedelta64,
366
384
  max_depth: float | None,
385
+ verbose_outputs: bool,
367
386
  ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
368
- """Calculate wind-shear-derived geometry of evolved plume."""
387
+ """Calculate wind-shear-derived geometry of evolved plume.
388
+
389
+ This method mutates the input ``vector`` in place.
390
+ """
369
391
 
370
392
  u_wind = vector["u_wind"]
371
393
  v_wind = vector["v_wind"]
@@ -419,6 +441,11 @@ def _calc_geometry(
419
441
  eff_heat_rate=None,
420
442
  )
421
443
 
444
+ if verbose_outputs:
445
+ vector["ds_dz"] = ds_dz
446
+ vector["dsn_dz"] = dsn_dz
447
+ vector["dT_dz"] = dT_dz
448
+
422
449
  sigma_yy_2, sigma_zz_2, sigma_yz_2 = contrail_properties.plume_temporal_evolution(
423
450
  width,
424
451
  depth,
@@ -477,9 +504,13 @@ def _evolve_one_step(
477
504
  sedimentation_rate: float,
478
505
  dz_m: float,
479
506
  max_depth: float | None,
507
+ verbose_outputs: bool,
480
508
  **interp_kwargs: Any,
481
509
  ) -> GeoVectorDataset:
482
- """Evolve plume geometry by one step."""
510
+ """Evolve plume geometry by one step.
511
+
512
+ This method mutates the input ``vector`` in place.
513
+ """
483
514
 
484
515
  _perform_interp_for_step(met, vector, dz_m, **interp_kwargs)
485
516
  u_wind = vector["u_wind"]
@@ -494,9 +525,9 @@ def _evolve_one_step(
494
525
  level_2 = geo.advect_level(
495
526
  vector.level,
496
527
  vertical_velocity,
497
- 0.0,
498
- 0.0,
499
- dt, # type: ignore[arg-type]
528
+ rho_air=0.0,
529
+ terminal_fall_speed=0.0,
530
+ dt=dt, # type: ignore[arg-type]
500
531
  )
501
532
 
502
533
  out = GeoVectorDataset._from_fastpath(
@@ -518,9 +549,10 @@ def _evolve_one_step(
518
549
  # Attach wind-shear-derived geometry to output vector
519
550
  azimuth_2, width_2, depth_2, sigma_yz_2, area_eff_2 = _calc_geometry(
520
551
  vector,
521
- dz_m,
522
- dt, # type: ignore[arg-type]
523
- max_depth, # type: ignore[arg-type]
552
+ dz_m=dz_m,
553
+ dt=dt, # type: ignore[arg-type]
554
+ max_depth=max_depth, # type: ignore[arg-type]
555
+ verbose_outputs=verbose_outputs,
524
556
  )
525
557
  out["azimuth"] = azimuth_2
526
558
  out["width"] = width_2
@@ -654,7 +654,7 @@ def histogram_matching(
654
654
  as a numpy array with the same shape and dtype as ``era5_rhi``.
655
655
  """
656
656
  if level_type not in ["pressure", "model"]:
657
- msg = f"Invalid 'level_type' value '{level_type}'. " "Must be one of ['pressure', 'model']."
657
+ msg = f"Invalid 'level_type' value '{level_type}'. Must be one of ['pressure', 'model']."
658
658
  raise ValueError(msg)
659
659
  df = _load_quantiles(level_type)
660
660
  iagos_quantiles = df[("iagos", "iagos")]
pycontrails/models/pcc.py CHANGED
@@ -186,8 +186,7 @@ class PCC(Model):
186
186
 
187
187
  # issue recombining groups arises if "level" is in dims
188
188
  # convert "level" dimension to coordinate
189
- b_crit_potential = b_crit_potential.squeeze("level")
190
- return b_crit_potential
189
+ return b_crit_potential.squeeze("level")
191
190
 
192
191
  # apply calculation per pressure level
193
192
  return (
@@ -7,7 +7,7 @@ import functools
7
7
  import pathlib
8
8
  import sys
9
9
  from collections.abc import Mapping
10
- from typing import Any, NoReturn, overload
10
+ from typing import Any
11
11
 
12
12
  if sys.version_info >= (3, 12):
13
13
  from typing import override
@@ -25,7 +25,6 @@ from pycontrails.core.aircraft_performance import (
25
25
  AircraftPerformanceData,
26
26
  AircraftPerformanceParams,
27
27
  )
28
- from pycontrails.core.fleet import Fleet
29
28
  from pycontrails.core.flight import Flight
30
29
  from pycontrails.core.met import MetDataset
31
30
  from pycontrails.core.met_var import AirTemperature, EastwardWind, MetVariable, NorthwardWind
@@ -118,35 +117,8 @@ class PSFlight(AircraftPerformance):
118
117
  raise KeyError(msg)
119
118
  return False
120
119
 
121
- @overload
122
- def eval(self, source: Fleet, **params: Any) -> Fleet: ...
123
-
124
- @overload
125
- def eval(self, source: Flight, **params: Any) -> Flight: ...
126
-
127
- @overload
128
- def eval(self, source: None = ..., **params: Any) -> NoReturn: ...
129
-
130
120
  @override
131
- def eval(self, source: Flight | None = None, **params: Any) -> Flight:
132
- self.update_params(params)
133
- self.set_source(source)
134
- self.source = self.require_source_type(Flight)
135
- self.downselect_met()
136
- self.set_source_met()
137
-
138
- # Calculate true airspeed if not included on source
139
- self.ensure_true_airspeed_on_source()
140
-
141
- if isinstance(self.source, Fleet):
142
- fls = [self._eval_flight(fl) for fl in self.source.to_flight_list()]
143
- self.source = Fleet.from_seq(fls, attrs=self.source.attrs, broadcast_numeric=False)
144
- return self.source
145
-
146
- self.source = self._eval_flight(self.source)
147
- return self.source
148
-
149
- def _eval_flight(self, fl: Flight) -> Flight:
121
+ def eval_flight(self, fl: Flight) -> Flight:
150
122
  # Ensure aircraft type is available
151
123
  try:
152
124
  aircraft_type = fl.attrs["aircraft_type"]
@@ -390,7 +390,7 @@ def minimum_mach_num(
390
390
  )
391
391
  return amass_max - aircraft_mass
392
392
 
393
- m = scipy.optimize.newton(
393
+ return scipy.optimize.newton(
394
394
  excess_mass,
395
395
  args=(
396
396
  air_pressure,
@@ -404,8 +404,6 @@ def minimum_mach_num(
404
404
  tol=1e-4,
405
405
  )
406
406
 
407
- return m
408
-
409
407
 
410
408
  def maximum_mach_num(
411
409
  altitude_ft: ArrayOrFloat,
@@ -450,7 +448,7 @@ def maximum_mach_num(
450
448
  atyp_param.p_inf_co,
451
449
  )
452
450
 
453
- max_mach = scipy.optimize.newton(
451
+ return scipy.optimize.newton(
454
452
  func=get_excess_thrust_available,
455
453
  args=(air_temperature, air_pressure, aircraft_mass, theta, atyp_param),
456
454
  x0=mach_num_op_lim,
@@ -458,8 +456,6 @@ def maximum_mach_num(
458
456
  tol=1e-4,
459
457
  ).clip(max=mach_num_op_lim)
460
458
 
461
- return max_mach
462
-
463
459
 
464
460
  # ----------------
465
461
  # Fuel flow limits
@@ -45,7 +45,8 @@ def tau_cirrus(met: MetDataset) -> xr.DataArray:
45
45
  met : MetDataset
46
46
  A MetDataset with the following variables:
47
47
  - "air_temperature"
48
- - "specific_cloud_ice_water_content" or "ice_water_mixing_ratio"
48
+ - "mass_fraction_of_cloud_ice_in_air", "specific_cloud_ice_water_content",
49
+ or "ice_water_mixing_ratio"
49
50
 
50
51
  Returns
51
52
  -------
@@ -64,15 +65,21 @@ def tau_cirrus(met: MetDataset) -> xr.DataArray:
64
65
  geopotential_height = _geopotential_height(met)
65
66
 
66
67
  # TODO: these are not *quite* the same, though we treat them the same for now
67
- # ECMWF "specific_cloud_ice_water_content" is mass ice per mass of *moist* air
68
- # GFS "ice_water_mixing_ratio" is mass ice per mass of *dry* air
68
+ # The generic "mass_fraction_of_cloud_ice_in_air" and ECMWF "specific_cloud_ice_water_content"
69
+ # are mass ice per mass of *moist* air,
70
+ # whereas GFS "ice_water_mixing_ratio" is mass ice per mass of *dry* air
69
71
  #
70
72
  # The method `cirrus_effective_extinction_coef` uses input of mass ice per mass of *dry* air,
71
- # so the ECMWF data is not quite right.
72
- try:
73
+ # so only the GFS data is exactly right.
74
+ if "mass_fraction_of_cloud_ice_in_air" in met.data:
75
+ ciwc = met.data["mass_fraction_of_cloud_ice_in_air"]
76
+ elif "specific_cloud_ice_water_content" in met.data:
73
77
  ciwc = met.data["specific_cloud_ice_water_content"]
74
- except KeyError:
78
+ elif "ice_water_mixing_ratio" in met.data:
75
79
  ciwc = met.data["ice_water_mixing_ratio"]
80
+ else:
81
+ msg = "Could not find cloud ice variable"
82
+ raise KeyError(msg)
76
83
 
77
84
  beta_e = cirrus_effective_extinction_coef(
78
85
  ciwc,
@@ -516,7 +516,7 @@ def solar_constant(theta_rad: ArrayLike) -> ArrayLike:
516
516
  + (0.000077 * np.sin(theta_rad * 2))
517
517
  )
518
518
 
519
- return constants.solar_constant * orbital_effect
519
+ return constants.solar_constant * orbital_effect # type: ignore[return-value]
520
520
 
521
521
 
522
522
  def cosine_solar_zenith_angle(
@@ -662,7 +662,7 @@ def solar_declination_angle(theta_rad: ArrayLike) -> ArrayLike:
662
662
  :func:`cosine_solar_zenith_angle`
663
663
  """
664
664
  return (
665
- 0.396372
665
+ 0.396372 # type: ignore[return-value]
666
666
  - (22.91327 * np.cos(theta_rad))
667
667
  + (4.02543 * np.sin(theta_rad))
668
668
  - (0.387205 * np.cos(2 * theta_rad))
@@ -729,7 +729,7 @@ def orbital_correction_for_solar_hour_angle(theta_rad: ArrayLike) -> ArrayLike:
729
729
  Tested against :cite:`noaaSolarCalculationDetails`
730
730
  """
731
731
  return (
732
- 0.004297
732
+ 0.004297 # type: ignore[return-value]
733
733
  + (0.107029 * np.cos(theta_rad))
734
734
  - (1.837877 * np.sin(theta_rad))
735
735
  - (0.837378 * np.cos(2 * theta_rad))
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pycontrails
3
- Version: 0.54.5
3
+ Version: 0.54.6
4
4
  Summary: Python library for modeling aviation climate impacts
5
5
  Author-email: Breakthrough Energy <py@contrails.org>
6
6
  License: Apache-2.0
@@ -49,8 +49,7 @@ Requires-Dist: pyarrow>=5.0; extra == "dev"
49
49
  Requires-Dist: pytest>=8.2; extra == "dev"
50
50
  Requires-Dist: pytest-cov>=2.11; extra == "dev"
51
51
  Requires-Dist: requests>=2.25; extra == "dev"
52
- Requires-Dist: ruff==0.8.0; extra == "dev"
53
- Requires-Dist: setuptools; extra == "dev"
52
+ Requires-Dist: ruff>=0.9.0; extra == "dev"
54
53
  Provides-Extra: docs
55
54
  Requires-Dist: doc8>=1.1; extra == "docs"
56
55
  Requires-Dist: furo>=2023.3; extra == "docs"
@@ -1,49 +1,49 @@
1
- pycontrails-0.54.5.dist-info/RECORD,,
2
- pycontrails-0.54.5.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
3
- pycontrails-0.54.5.dist-info/WHEEL,sha256=kD-LhIT6lUKGukNhSkqq4OnwDJg6bUoeCVUytlUwUF8,111
4
- pycontrails-0.54.5.dist-info/NOTICE,sha256=TeY5lUhEbf5ouzABkrRUsUDJzS5v9jDEYADpwku4mgA,1929
5
- pycontrails-0.54.5.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
6
- pycontrails-0.54.5.dist-info/METADATA,sha256=Tabqvf9lZ6tBPR5HdM4WA-NIhMj63APhKpIqvST89kU,9155
7
- pycontrails/_version.py,sha256=As6esyAJK0I-dknf0mXTw-4gYl46DB5WJ1UEk7dWCTo,413
1
+ pycontrails-0.54.6.dist-info/RECORD,,
2
+ pycontrails-0.54.6.dist-info/LICENSE,sha256=gJ-h7SFFD1mCfR6a7HILvEtodDT6Iig8bLXdgqR6ucA,10175
3
+ pycontrails-0.54.6.dist-info/WHEEL,sha256=FBd6an5AFoPq_6i7rX7RY9hnbsVzpIPkc8xRof4mLK4,111
4
+ pycontrails-0.54.6.dist-info/NOTICE,sha256=TeY5lUhEbf5ouzABkrRUsUDJzS5v9jDEYADpwku4mgA,1929
5
+ pycontrails-0.54.6.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
6
+ pycontrails-0.54.6.dist-info/METADATA,sha256=M7bIMAsry7V5uDqs2GHOi2g-gN3Q0L-24BywhVyTAeM,9113
7
+ pycontrails/_version.py,sha256=3tdcrURWC_cFIzJJ0AUbLpq4j7N9WBPvv2IFchdGJSw,413
8
8
  pycontrails/__init__.py,sha256=vomSUHQ1d7ru2-3LgnmczzuPJw0kHmBSaCSVOpqvEtQ,2004
9
9
  pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  pycontrails/core/vector.py,sha256=N-B-VSNX5bz-FwzSRBdbO3fZ1A8Xh3odbTs_6rnZty4,71263
11
- pycontrails/core/models.py,sha256=9sFTumUN6WmOP_wbXLu8bYoCCk-6zVQTE_KGtJ9OnIY,39792
11
+ pycontrails/core/models.py,sha256=5ppjLNbxWvS3OfiWfhfVDWjbjw5YhOPqFMedOpV4tBk,39808
12
12
  pycontrails/core/interpolation.py,sha256=wovjj3TAf3xonVxjarclpvZLyLq6N7wZQQXsI9hT3YA,25713
13
13
  pycontrails/core/fleet.py,sha256=0hi_N4R93St-7iD29SE0EnadpBEl_p9lSGtDwpWvGkk,16704
14
- pycontrails/core/flight.py,sha256=DRQx2pd06OYAxjlJajyquirpxHRzOu9a7YahW_kSaF0,80680
14
+ pycontrails/core/flight.py,sha256=Eba7XmfjGEoFSdWJfSnOmWknAU0c0PPNZtTYrb3u_m4,80659
15
15
  pycontrails/core/fuel.py,sha256=kJZ3P1lPm1L6rdPREM55XQ-VfJ_pt35cP4sO2Nnvmjs,4332
16
16
  pycontrails/core/polygon.py,sha256=EmfHPj0e58whsHvR-3YvDgMWkvMFgp_BgwaoG8IZ4n0,18044
17
17
  pycontrails/core/cache.py,sha256=XG_RCIemv1xatDBmaVyxnoYdi8gD2gkUvjfvRi9RsJA,28068
18
18
  pycontrails/core/__init__.py,sha256=p0O09HxdeXU0X5Z3zrHMlTfXa92YumT3fJ8wJBI5ido,856
19
- pycontrails/core/flightplan.py,sha256=UO4vL087d5TZMlU984-FxfotGTxFbqK78w2fLDRiel4,7335
19
+ pycontrails/core/flightplan.py,sha256=_7j4puAMiSe2aqHXcENR58c-N8crjUp4nbi3O2u7Adg,7335
20
20
  pycontrails/core/met.py,sha256=5b2K4PQWcAO5X8Mb3LLJ224_06WDyDEGUIeE3_EB3zA,103599
21
- pycontrails/core/aircraft_performance.py,sha256=P3_StjXJJqtBOYWbyHyVNuxdOZzuVZDfeRxtNQmSpFY,27236
22
- pycontrails/core/airports.py,sha256=aeyAXVkioIRomrP79UtNrxindL4f1DJyXFaojZCuBBw,6758
23
- pycontrails/core/met_var.py,sha256=GC5ijw4oGuIefmFOSz4vmxMEBj_SVs5Z75IMhDP56Cw,9183
24
- pycontrails/core/rgi_cython.cpython-312-darwin.so,sha256=xkSgUP6QphNSdL_DBu3XeFUknu9_DJzr20spH7ZnRp8,309272
21
+ pycontrails/core/aircraft_performance.py,sha256=quODa49uv0DVwl-qEYQ2ePLeapOD0GjyNBO3HoSD3M0,28004
22
+ pycontrails/core/airports.py,sha256=3IGP337nqzXAXktT7Ju95i0I8GDVwHMlCaCv2bs4Kkk,6738
23
+ pycontrails/core/met_var.py,sha256=U3q5Ddc1wN5EtT7a5Iy5lixCT3Mmv75C9wScY9MLnc8,11544
24
+ pycontrails/core/rgi_cython.cpython-312-darwin.so,sha256=USG3Rr_qfW4IOMPddhGhAMpE1YnvrODmsmiCCwqlECY,309272
25
25
  pycontrails/core/coordinates.py,sha256=0ySsHtqTon7GMbuwmmxMbI92j3ueMteJZh4xxNm5zto,5391
26
26
  pycontrails/datalib/goes.py,sha256=mCEuDYdt1GIBA-sbDq5LdC6ZRvWJ28uaaBTnsXE4syc,26555
27
27
  pycontrails/datalib/landsat.py,sha256=r6366rEF7fOA7mT5KySCPGJplgGE5LvBw5fMqk-U1oM,19697
28
28
  pycontrails/datalib/spire.py,sha256=66SnMdA8KOS69USjKmqrJmTKPK08Ehih9tnlsCt-AJw,25331
29
29
  pycontrails/datalib/__init__.py,sha256=hW9NWdFPC3y_2vHMteQ7GgQdop3917MkDaf5ZhU2RBY,369
30
30
  pycontrails/datalib/sentinel.py,sha256=hYSxIlQnyJHqtHWlKn73HOK_1pm-_IbGebmkHnh4UcA,17172
31
- pycontrails/datalib/_met_utils/metsource.py,sha256=5t2hjSmEG4jg-n8BD5tJy1Fp_SSgRsqYJdOGtuctRNY,24118
31
+ pycontrails/datalib/_met_utils/metsource.py,sha256=omgrBrAap11G5hV8a9qS3umJVuwoX_Mca6QctRa6xn8,24116
32
32
  pycontrails/datalib/ecmwf/arco_era5.py,sha256=7HXQU5S02PzX9Ew2ZrDKSp0tDEG1eeVAvbP3decmm20,12437
33
- pycontrails/datalib/ecmwf/era5.py,sha256=wTXjQ3pkrKg5qFlu8n8Zy6HUU4JuhywK_BgKNN4jnYI,19046
34
- pycontrails/datalib/ecmwf/era5_model_level.py,sha256=KP4a9l2YbEhBPraN1LDx_I_OsOaJemAheW3gICqe5uA,19428
33
+ pycontrails/datalib/ecmwf/era5.py,sha256=4ULNdDlUN0kP6Tbp8D_-Bc12nAsLf0iNfZaDoj_AoZU,18952
34
+ pycontrails/datalib/ecmwf/era5_model_level.py,sha256=AO7ePIGZtavx5nQSPYP4p07RNZeg3bbzmoZC7RUC4Gg,19354
35
35
  pycontrails/datalib/ecmwf/hres.py,sha256=9QHYxMLK7zyQEOFpbVrZfIht9WqVXnhhyOd7YKEgAe0,28381
36
36
  pycontrails/datalib/ecmwf/variables.py,sha256=lU3BNe265XVhCXvdMwZqfkWQwtsetZxVRLSfPqHFKAE,9913
37
37
  pycontrails/datalib/ecmwf/hres_model_level.py,sha256=EjBDYbbPZotTsveFlEiAAWJhhPYiao1DQrLyS4kVCrA,17657
38
38
  pycontrails/datalib/ecmwf/__init__.py,sha256=wdfhplEaW2UKTItIoshTtVEjbPyfDYoprTJNxbKZuvA,2021
39
39
  pycontrails/datalib/ecmwf/common.py,sha256=qRMSzDQikGMi3uqvz-Y57e3biHPzSoVMfUwOu9iTxHc,4024
40
40
  pycontrails/datalib/ecmwf/model_levels.py,sha256=_kgpnogaS6MlfvTX9dB5ASTHFUlZuQ_DRb-VADwEa0k,16996
41
- pycontrails/datalib/ecmwf/ifs.py,sha256=bAxkHXIMUXP8T8JmGp1PXdm24hoJoIHyJ3IDLLHl8ho,10778
41
+ pycontrails/datalib/ecmwf/ifs.py,sha256=0swHe6tFc5Fbu9e4_jREW0H-xYHYLtxjNoE3aUUlgvc,10761
42
42
  pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv,sha256=PmvGLRzn6uuCKSwiasSuVcehvvmSaqP7cnLuN6hhCQQ,9788
43
43
  pycontrails/datalib/_leo_utils/vis.py,sha256=-fLcm1D5cP6lThVHovV3MJSiadWyTUAvYDMvr4drMU4,1802
44
44
  pycontrails/datalib/_leo_utils/search.py,sha256=r87T2OV4qH1pYI2YznvsBL042f4RKxD3OA2snd3-kDI,8687
45
45
  pycontrails/datalib/_leo_utils/static/bq_roi_query.sql,sha256=xq6-tJyz0-bUwW0KjQymqygjH3WlQBmyBtP7Ci7SBe8,260
46
- pycontrails/datalib/gfs/gfs.py,sha256=kGRcmolxbl_5R13N_SOvXr2wAcaqFCb-PUIFhP5whBM,22410
46
+ pycontrails/datalib/gfs/gfs.py,sha256=3tFiR7IObHcFmhGOdb-SJ7QQJSk6tF_6qkyi-pLrIdE,22393
47
47
  pycontrails/datalib/gfs/variables.py,sha256=4ALR4zhYW8tQVlNVHrd0CK8oRNSe_2OkW3ELeaImtAI,3135
48
48
  pycontrails/datalib/gfs/__init__.py,sha256=pXNjb9cJC6ngpuCnoHnmVZ2RHzbHZ0AlsyGvgcdcl2E,684
49
49
  pycontrails/ext/synthetic_flight.py,sha256=ByuJDfpuK5WaGMj41wflfzH6zwI1nejVcQXC4JoMvSI,16795
@@ -56,13 +56,13 @@ pycontrails/utils/types.py,sha256=dN2oYVNNbekqvM89Lfs0FmmhavRQGC7NgGhi_7m6UBU,49
56
56
  pycontrails/utils/temp.py,sha256=lGU0b_R8ze4yKlsOusHIIBaoNFBrmrB3vBjgHRlfcXk,1109
57
57
  pycontrails/utils/json.py,sha256=oTiO8xh603esfBGaGVmA5eUzR0NhAqNpQCegMMgnSbg,5896
58
58
  pycontrails/utils/dependencies.py,sha256=ATP45xYdUbIyGFzgbOe5SbokMytvB84TcexUEFnEUZE,2559
59
- pycontrails/models/pcc.py,sha256=7hIlg_4-F6Ce7KVFyuIZBZY6uDr1h4KRMqBDlpGkzHE,11116
60
- pycontrails/models/tau_cirrus.py,sha256=GjCkGMf6_zRyzdWRSpO7WieNjY8b-RpPmss0WO3e1HM,5381
59
+ pycontrails/models/pcc.py,sha256=0Qdl4u8PmUEpNYd398glTChkbTwsh83wYPt0Bmi8qd8,11068
60
+ pycontrails/models/tau_cirrus.py,sha256=0K7cdHBGaahuWDM0FG1HGqbvhkwyWKHMInwctkBWYbo,5777
61
61
  pycontrails/models/__init__.py,sha256=dQTOLQb7RdUdUwslt5se__5y_ymbInBexQmNrmAeOdE,33
62
62
  pycontrails/models/issr.py,sha256=AYLYLHxtG8je5UG6x1zLV0ul89MJPqe5Xk0oWIyZ7b0,7378
63
63
  pycontrails/models/sac.py,sha256=lV1Or0AaLxuS1Zo5V8h5c1fkSKC-hKEgiFm7bmmusWw,15946
64
64
  pycontrails/models/accf.py,sha256=meIcgojYvHgm3de9iro2Bv0M4y9ta5VPwcqGAMEbBp8,13663
65
- pycontrails/models/dry_advection.py,sha256=haV2Lfzop6DUEpb69sR-7OVdvFAmTiFWUfwzIfgoBRs,18028
65
+ pycontrails/models/dry_advection.py,sha256=FqUvRFbnwe4esHBYDayn3iu7R2UUuaQwY8x2oToxNI0,19164
66
66
  pycontrails/models/pcr.py,sha256=ZzbEuTOuDdUmmL5T3Wk3HL-O8XzX3HMnn98WcPbASaU,5348
67
67
  pycontrails/models/emissions/__init__.py,sha256=CZB2zIkLUI3NGNmq2ddvRYjEtiboY6PWJjiEiXj_zII,478
68
68
  pycontrails/models/emissions/ffm2.py,sha256=mAvBHnp-p3hIn2fjKGq50eaMHi0jcb5hA5uXbJGeE9I,12068
@@ -76,14 +76,14 @@ pycontrails/models/apcemm/inputs.py,sha256=88GylkiaymEW_XZeFxLsICI9wV6kl8wVYsuyT
76
76
  pycontrails/models/apcemm/utils.py,sha256=Ex6EqXin6yoJv2WWhBotSzhjzUlFNZm2MDgL4CvvX6E,17082
77
77
  pycontrails/models/apcemm/apcemm.py,sha256=rKvIaEsqtLbZ5h4o4EOY4Ge4-HdPn2X4M1lEUFDvr68,39975
78
78
  pycontrails/models/apcemm/static/apcemm_yaml_template.yaml,sha256=uAZkc57OUvDMjgX6F5f6hgDh3Hgg1NbHWRUFSiv0DEI,6745
79
- pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=Mpy6Jd0UNta4nUhS01kkm6uCg0Yo5EhHIrP656lLpnI,36770
79
+ pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=pYpJHi9dcuGjJASdZkbc49pIl2cQkVlRu80Gy3ZN5bA,36767
80
80
  pycontrails/models/humidity_scaling/__init__.py,sha256=nqsab_j9BCwMbTfCn4BjXMdhItlvNKkgUJ9-lb8RyIo,1119
81
81
  pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq,sha256=tfYhbafF9Z-gGCg6VQ1YBlOaK_01e65Dc6s9b-hQ6Zo,286375
82
82
  pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq,sha256=pShCvNUo0NYtAHhT9IBRuj38X9jejdlKfv-ZoOKmtKI,35943
83
83
  pycontrails/models/cocip/radiative_forcing.py,sha256=0zTXQXANYC3oIDyYEUHkaJxuwTe0-GojxBipEutWGxU,45031
84
84
  pycontrails/models/cocip/wind_shear.py,sha256=m6ZlWjORfI-lI-D74Z_dIMOHnK4FDYmkb0S6vSpKTO8,3868
85
- pycontrails/models/cocip/cocip.py,sha256=fqEP5zxvPsprYcKVXNnIXwu-y7QzQMBs5KhMHeWafJ4,100112
86
- pycontrails/models/cocip/output_formats.py,sha256=Tyu4080PO5tqZuXjZ0XIxoFF2Nik-stttEMBpu33b-s,83697
85
+ pycontrails/models/cocip/cocip.py,sha256=QbbgSPEI2Lw8mftaxznpUgdNytnMk7O89VsTKBDvQ2w,102407
86
+ pycontrails/models/cocip/output_formats.py,sha256=YXYfm32NsI3OkZn4htAOceMIDG31ulehUiMUCqu8hEQ,83713
87
87
  pycontrails/models/cocip/__init__.py,sha256=CWrkNd6S3ZJq04pjTc2W22sVAJeJD3bJJRy_zLW8Kkc,962
88
88
  pycontrails/models/cocip/cocip_params.py,sha256=rwVW1SnjKnzztlxcxxaCztgzRbIPkMJqC2GefUKQZ6w,12341
89
89
  pycontrails/models/cocip/wake_vortex.py,sha256=YmOuv_oWJ9-fmTx9PVHr6gsXwex0qzLhvoZIJNB9rsk,14515
@@ -92,16 +92,16 @@ pycontrails/models/cocip/radiative_heating.py,sha256=1U4SQWwogtyQ2u6J996kAHP0Ofp
92
92
  pycontrails/models/cocip/contrail_properties.py,sha256=H0D2FQodX6jW3EmAxQNxGS8erOU0EW2MSAAOLB0LyQc,56202
93
93
  pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=M6tdl5ZfNDtiLwJOrXg3sBesf6Larg-5JchsVlJNsG4,14675
94
94
  pycontrails/models/ps_model/__init__.py,sha256=Fuum5Rq8ya8qkvbeq2wh6NDo-42RCRnK1Y-2syYy0Ck,553
95
- pycontrails/models/ps_model/ps_model.py,sha256=_6SvkL2jL1Awa6ehR1XFZe5NdHBZbgc6jJp5jaUDNLo,33259
95
+ pycontrails/models/ps_model/ps_model.py,sha256=e-rd3b61futqHFQPXbOj3o-7hIuxHZH0fUzmYI4JDow,32255
96
96
  pycontrails/models/ps_model/ps_aircraft_params.py,sha256=pD1xpTBX6ML2Pie78kypNibzE5AkvqnAIaTyEMfciuY,13350
97
- pycontrails/models/ps_model/ps_operational_limits.py,sha256=jPjzjwOENOHdcS2yU_UhAKABlyJ0_kDcHqj-egN3gTc,16477
97
+ pycontrails/models/ps_model/ps_operational_limits.py,sha256=XwMHO8yu8EZUWtxRgjRKwxmCrmKGoHO7Ob6nlfkrthI,16441
98
98
  pycontrails/models/ps_model/ps_grid.py,sha256=DTUXTxYIQ-6iquiCGtScOqkPvakz9F57DxUHQ3JmXIA,26071
99
99
  pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv,sha256=ksrpQTHkxSt1Va_R0rHdenEz6DlIs-Sfk1KFBzHKjhk,1038
100
100
  pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv,sha256=3eNhSwzut0gon04k2EYKKaXRvQSUlau3yBAbHS0EBao,25784
101
101
  pycontrails/models/cocipgrid/cocip_grid_params.py,sha256=l4vBPrOKCJDz5Y1uMjmOGVyUcSWgfZtFWbjW968OPz8,5875
102
102
  pycontrails/models/cocipgrid/__init__.py,sha256=ar6bF_8Pusbb-myujz_q5ntFylQTNH8yiM8fxP7Zk30,262
103
103
  pycontrails/models/cocipgrid/cocip_grid.py,sha256=uLQ86xK12LJ43KKb-CdSaDDamj4GyfDbiHp_NefotZU,91151
104
- pycontrails/physics/geo.py,sha256=Br0w1-Yfzin1a_IZDeKDT21YtYqoQC8FQGRxcPyoPcU,36233
104
+ pycontrails/physics/geo.py,sha256=5THIXgpaHBQdSYWLgtK4mV_8e1hWW9XeTsSHOShFMeA,36323
105
105
  pycontrails/physics/units.py,sha256=BC0e0l_pDeijqN179tXl8eX_Qpw8d17MVujBu1SV3IE,12293
106
106
  pycontrails/physics/constants.py,sha256=pHQQmccMUwuNnY4hFtm3L8G2rnUQcfJnroyQr8HAVeM,3146
107
107
  pycontrails/physics/__init__.py,sha256=_1eWbEy6evEWdfJCEkwDiSdpiDNzNWEPVqaPekHyhwU,44
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp312-cp312-macosx_10_13_x86_64
5
5