pycontrails 0.54.5__cp310-cp310-win_amd64.whl → 0.54.7__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 (42) hide show
  1. pycontrails/__init__.py +1 -1
  2. pycontrails/_version.py +2 -2
  3. pycontrails/core/aircraft_performance.py +46 -46
  4. pycontrails/core/airports.py +7 -5
  5. pycontrails/core/flight.py +6 -8
  6. pycontrails/core/flightplan.py +11 -11
  7. pycontrails/core/met.py +41 -33
  8. pycontrails/core/met_var.py +80 -0
  9. pycontrails/core/models.py +80 -3
  10. pycontrails/core/rgi_cython.cp310-win_amd64.pyd +0 -0
  11. pycontrails/core/vector.py +66 -0
  12. pycontrails/datalib/_met_utils/metsource.py +1 -1
  13. pycontrails/datalib/ecmwf/era5.py +5 -6
  14. pycontrails/datalib/ecmwf/era5_model_level.py +4 -5
  15. pycontrails/datalib/ecmwf/ifs.py +1 -3
  16. pycontrails/datalib/gfs/gfs.py +1 -3
  17. pycontrails/datalib/spire/__init__.py +5 -0
  18. pycontrails/datalib/spire/exceptions.py +62 -0
  19. pycontrails/datalib/spire/spire.py +606 -0
  20. pycontrails/models/accf.py +4 -4
  21. pycontrails/models/cocip/cocip.py +116 -19
  22. pycontrails/models/cocip/cocip_params.py +10 -1
  23. pycontrails/models/cocip/output_formats.py +1 -0
  24. pycontrails/models/cocip/unterstrasser_wake_vortex.py +132 -30
  25. pycontrails/models/cocipgrid/cocip_grid.py +3 -0
  26. pycontrails/models/dry_advection.py +51 -19
  27. pycontrails/models/emissions/black_carbon.py +19 -14
  28. pycontrails/models/emissions/emissions.py +8 -8
  29. pycontrails/models/humidity_scaling/humidity_scaling.py +1 -1
  30. pycontrails/models/pcc.py +1 -2
  31. pycontrails/models/ps_model/ps_model.py +3 -31
  32. pycontrails/models/ps_model/ps_operational_limits.py +2 -6
  33. pycontrails/models/tau_cirrus.py +13 -6
  34. pycontrails/physics/constants.py +2 -1
  35. pycontrails/physics/geo.py +3 -3
  36. {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/METADATA +5 -6
  37. {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/NOTICE +1 -1
  38. {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/RECORD +41 -39
  39. {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/WHEEL +1 -1
  40. pycontrails/datalib/spire.py +0 -739
  41. {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/LICENSE +0 -0
  42. {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/top_level.txt +0 -0
@@ -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
@@ -98,7 +98,7 @@ def flame_temperature(t_3: ArrayScalarLike) -> ArrayScalarLike:
98
98
  ArrayScalarLike
99
99
  Flame temperature at the combustion chamber, [:math:`K`]
100
100
  """
101
- return 0.9 * t_3 + 2120
101
+ return 0.9 * t_3 + 2120.0
102
102
 
103
103
 
104
104
  def bc_mass_concentration_fox(
@@ -125,7 +125,11 @@ def bc_mass_concentration_fox(
125
125
  npt.NDArray[np.floating]:
126
126
  Black carbon mass concentration for ground conditions, [:math:`mg m^{-3}`]
127
127
  """
128
- return fuel_flow * (356 * np.exp(-6390 / t_fl) - 608 * afr * np.exp(-19778 / t_fl))
128
+ # avoid float32 -> float64 promotion
129
+ coeff = 356.0 * np.exp(np.float32(-6390.0) / t_fl) - 608.0 * afr * np.exp(
130
+ np.float32(-19778.0) / t_fl
131
+ )
132
+ return fuel_flow * coeff
129
133
 
130
134
 
131
135
  def bc_mass_concentration_cruise_fox(
@@ -209,7 +213,7 @@ def dopelheuer_lecht_scaling_factor(
209
213
  ----------
210
214
  - :cite:`dopelheuerInfluenceEnginePerformance1998`
211
215
  """
212
- exp_term = np.exp(20000 / t_fl_cru) / np.exp(20000 / t_fl_ref)
216
+ exp_term = np.exp(20000.0 / t_fl_cru - 20000.0 / t_fl_ref)
213
217
  return (afr_ref / afr_cru) ** 2.5 * (p_3_cru / p_3_ref) ** 1.35 * exp_term
214
218
 
215
219
 
@@ -295,7 +299,7 @@ def turbine_inlet_temperature_imfox(afr: npt.NDArray[np.floating]) -> npt.NDArra
295
299
  ----------
296
300
  - :cite:`abrahamsonPredictiveModelDevelopment2016`
297
301
  """
298
- return 490 + 42266 / afr
302
+ return 490.0 + 42266.0 / afr
299
303
 
300
304
 
301
305
  def bc_mass_concentration_imfox(
@@ -325,9 +329,10 @@ def bc_mass_concentration_imfox(
325
329
  npt.NDArray[np.floating]
326
330
  Black carbon mass concentration, [:math:`mg m^{-3}`]
327
331
  """
328
- exp_term = np.exp(13.6 - fuel_hydrogen)
329
- formation_term = 295 * np.exp(-6390 / t_4)
330
- oxidation_term = 608 * afr * np.exp(-19778 / t_4)
332
+ # avoid float32 -> float64 promotion
333
+ exp_term = np.exp(np.float32(13.6) - fuel_hydrogen)
334
+ formation_term = 295.0 * np.exp(np.float32(-6390.0) / t_4)
335
+ oxidation_term = 608.0 * afr * np.exp(np.float32(-19778.0) / t_4)
331
336
  return fuel_flow_per_engine * exp_term * (formation_term - oxidation_term)
332
337
 
333
338
 
@@ -453,11 +458,11 @@ def number_emissions_index_fractal_aggregates(
453
458
  nvpm_ei_m: npt.NDArray[np.floating],
454
459
  gmd: npt.NDArray[np.floating],
455
460
  *,
456
- gsd: float | npt.NDArray[np.floating] = 1.80,
457
- rho_bc: float = 1770,
458
- k_tem: float = 1.621e-5,
459
- d_tem: float = 0.39,
460
- d_fm: float = 2.76,
461
+ gsd: float | np.floating | npt.NDArray[np.floating] = np.float32(1.80), # avoid promotion
462
+ rho_bc: float | np.floating = np.float32(1770.0),
463
+ k_tem: float | np.floating = np.float32(1.621e-5),
464
+ d_tem: float | np.floating = np.float32(0.39),
465
+ d_fm: float | np.floating = np.float32(2.76),
461
466
  ) -> npt.NDArray[np.floating]:
462
467
  """
463
468
  Estimate the black carbon number emission index using the fractal aggregates (FA) model.
@@ -494,9 +499,9 @@ def number_emissions_index_fractal_aggregates(
494
499
  - ``rho_bc``: :cite:`parkMeasurementInherentMaterial2004`
495
500
  - ``k_tem``, ``d_tem``: :cite:`dastanpourObservationsCorrelationPrimary2014`
496
501
  """
497
- phi = 3 * d_tem + (1 - d_tem) * d_fm
502
+ phi = 3.0 * d_tem + (1.0 - d_tem) * d_fm
498
503
  exponential_term = np.exp(0.5 * phi**2 * np.log(gsd) ** 2)
499
- denom = rho_bc * (np.pi / 6) * k_tem ** (3 - d_fm) * gmd**phi * exponential_term
504
+ denom = rho_bc * (np.pi / 6.0) * k_tem ** (3.0 - d_fm) * gmd**phi * exponential_term
500
505
  return nvpm_ei_m / denom
501
506
 
502
507
 
@@ -169,7 +169,7 @@ class Emissions(Model):
169
169
  )
170
170
 
171
171
  if "n_engine" not in self.source.attrs:
172
- aircraft_type = self.source.attrs.get("aircraft_type")
172
+ aircraft_type = self.source.get_constant("aircraft_type", None)
173
173
  self.source.attrs["n_engine"] = self.default_engines.at[aircraft_type, "n_engine"]
174
174
 
175
175
  try:
@@ -192,7 +192,7 @@ class Emissions(Model):
192
192
  try:
193
193
  edb_gaseous = self.edb_engine_gaseous[engine_uid] # type: ignore[index]
194
194
  except KeyError:
195
- self.source["thrust_setting"] = np.full(shape=len(self.source), fill_value=np.nan)
195
+ self.source["thrust_setting"] = np.full(len(self.source), np.nan, dtype=np.float32)
196
196
  else:
197
197
  self.source["thrust_setting"] = get_thrust_setting(
198
198
  edb_gaseous,
@@ -297,9 +297,9 @@ class Emissions(Model):
297
297
  """
298
298
  self.source.attrs["gaseous_data_source"] = "Constant"
299
299
 
300
- nox_ei = np.full(shape=len(self.source), fill_value=15.14)
301
- co_ei = np.full(shape=len(self.source), fill_value=3.61)
302
- hc_ei = np.full(shape=len(self.source), fill_value=0.520)
300
+ nox_ei = np.full(shape=len(self.source), fill_value=15.14, dtype=np.float32)
301
+ co_ei = np.full(shape=len(self.source), fill_value=3.61, dtype=np.float32)
302
+ hc_ei = np.full(shape=len(self.source), fill_value=0.520, dtype=np.float32)
303
303
 
304
304
  self.source["nox_ei"] = nox_ei * 1e-3 # g-NOx/kg-fuel to kg-NOx/kg-fuel
305
305
  self.source["co_ei"] = co_ei * 1e-3 # g-CO/kg-fuel to kg-CO/kg-fuel
@@ -493,8 +493,8 @@ class Emissions(Model):
493
493
  - :cite:`schumannDehydrationEffectsContrails2015`
494
494
  """
495
495
  nvpm_data_source = "Constant"
496
- nvpm_ei_m = np.full(shape=len(self.source), fill_value=(0.088 * 1e-3)) # g to kg
497
- nvpm_ei_n = np.full(shape=len(self.source), fill_value=self.params["default_nvpm_ei_n"])
496
+ nvpm_ei_m = np.full(len(self.source), 0.088 * 1e-3, dtype=np.float32) # g to kg
497
+ nvpm_ei_n = np.full(len(self.source), self.params["default_nvpm_ei_n"], dtype=np.float32)
498
498
  return nvpm_data_source, nvpm_ei_m, nvpm_ei_n
499
499
 
500
500
  def _total_pollutant_emissions(self) -> None:
@@ -916,7 +916,7 @@ def get_thrust_setting(
916
916
  )
917
917
 
918
918
  thrust_setting = fuel_flow_per_engine / edb_gaseous.ff_100
919
- thrust_setting.clip(0.03, 1, out=thrust_setting) # clip in place
919
+ thrust_setting.clip(0.03, 1.0, out=thrust_setting) # clip in place
920
920
  return thrust_setting
921
921
 
922
922
 
@@ -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,38 +117,11 @@ 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
- aircraft_type = fl.attrs["aircraft_type"]
124
+ aircraft_type = fl.get_constant("aircraft_type")
153
125
  except KeyError as exc:
154
126
  msg = "`aircraft_type` required on flight attrs"
155
127
  raise KeyError(msg) from exc
@@ -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,
@@ -112,5 +112,6 @@ c_r: float = 0.9
112
112
  # Flight
113
113
  # ------
114
114
 
115
- #: Nominal rate of climb/descent of aircraft [:math:`m \ s^{-1}``]
115
+ #: Nominal rate of climb/descent of aircraft [:math:`m \ s^{-1}`].
116
+ #: Note [:math:`12.7 m \ s^{-1} = 2500 ft \ min^{-1}`].
116
117
  nominal_rocd: float = 12.7
@@ -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,8 +1,8 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: pycontrails
3
- Version: 0.54.5
3
+ Version: 0.54.7
4
4
  Summary: Python library for modeling aviation climate impacts
5
- Author-email: Breakthrough Energy <py@contrails.org>
5
+ Author-email: "Contrails.org" <py@contrails.org>
6
6
  License: Apache-2.0
7
7
  Project-URL: Changelog, https://py.contrails.org/changelog.html
8
8
  Project-URL: Documentation, https://py.contrails.org
@@ -29,7 +29,7 @@ License-File: LICENSE
29
29
  License-File: NOTICE
30
30
  Requires-Dist: dask>=2022.3
31
31
  Requires-Dist: numpy>=1.22
32
- Requires-Dist: pandas>=2.2
32
+ Requires-Dist: pandas>=2.0
33
33
  Requires-Dist: scipy>=1.10
34
34
  Requires-Dist: typing-extensions>=4.5; python_version < "3.12"
35
35
  Requires-Dist: xarray>=2022.3
@@ -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,4 +1,4 @@
1
- Copyright (c) 2021-present Breakthrough Energy
1
+ Copyright (c) 2021-present Contrails.org and the Breakthrough Energy Foundation
2
2
 
3
3
 
4
4
  Attribution