pycontrails 0.54.5__cp311-cp311-win_amd64.whl → 0.54.7__cp311-cp311-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.
- pycontrails/__init__.py +1 -1
- pycontrails/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +46 -46
- pycontrails/core/airports.py +7 -5
- pycontrails/core/flight.py +6 -8
- pycontrails/core/flightplan.py +11 -11
- pycontrails/core/met.py +41 -33
- pycontrails/core/met_var.py +80 -0
- pycontrails/core/models.py +80 -3
- pycontrails/core/rgi_cython.cp311-win_amd64.pyd +0 -0
- pycontrails/core/vector.py +66 -0
- pycontrails/datalib/_met_utils/metsource.py +1 -1
- pycontrails/datalib/ecmwf/era5.py +5 -6
- pycontrails/datalib/ecmwf/era5_model_level.py +4 -5
- pycontrails/datalib/ecmwf/ifs.py +1 -3
- pycontrails/datalib/gfs/gfs.py +1 -3
- pycontrails/datalib/spire/__init__.py +5 -0
- pycontrails/datalib/spire/exceptions.py +62 -0
- pycontrails/datalib/spire/spire.py +606 -0
- pycontrails/models/accf.py +4 -4
- pycontrails/models/cocip/cocip.py +116 -19
- pycontrails/models/cocip/cocip_params.py +10 -1
- pycontrails/models/cocip/output_formats.py +1 -0
- pycontrails/models/cocip/unterstrasser_wake_vortex.py +132 -30
- pycontrails/models/cocipgrid/cocip_grid.py +3 -0
- pycontrails/models/dry_advection.py +51 -19
- pycontrails/models/emissions/black_carbon.py +19 -14
- pycontrails/models/emissions/emissions.py +8 -8
- pycontrails/models/humidity_scaling/humidity_scaling.py +1 -1
- pycontrails/models/pcc.py +1 -2
- pycontrails/models/ps_model/ps_model.py +3 -31
- pycontrails/models/ps_model/ps_operational_limits.py +2 -6
- pycontrails/models/tau_cirrus.py +13 -6
- pycontrails/physics/constants.py +2 -1
- pycontrails/physics/geo.py +3 -3
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/METADATA +5 -6
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/NOTICE +1 -1
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/RECORD +41 -39
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/WHEEL +1 -1
- pycontrails/datalib/spire.py +0 -739
- {pycontrails-0.54.5.dist-info → pycontrails-0.54.7.dist-info}/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
179
|
+
vector1 = vector2 + self.source.filter(filt, copy=False)
|
|
170
180
|
|
|
171
|
-
t0 =
|
|
172
|
-
t1 =
|
|
181
|
+
t0 = vector1["time"].min()
|
|
182
|
+
t1 = vector1["time"].max()
|
|
173
183
|
met = maybe_downselect_mds(self.met, met, t0, t1)
|
|
174
184
|
|
|
175
|
-
|
|
185
|
+
vector2 = _evolve_one_step(
|
|
176
186
|
met,
|
|
177
|
-
|
|
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 = (
|
|
186
|
-
|
|
197
|
+
filt = (vector2["age"] <= max_age) & vector2.coords_intersect_met(self.met)
|
|
198
|
+
vector2 = vector2.filter(filt)
|
|
187
199
|
|
|
188
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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.
|
|
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(
|
|
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(
|
|
497
|
-
nvpm_ei_n = np.full(
|
|
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}'.
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
pycontrails/models/tau_cirrus.py
CHANGED
|
@@ -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
|
-
- "
|
|
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
|
-
#
|
|
68
|
-
#
|
|
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
|
|
72
|
-
|
|
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
|
-
|
|
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,
|
pycontrails/physics/constants.py
CHANGED
|
@@ -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
|
pycontrails/physics/geo.py
CHANGED
|
@@ -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
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: pycontrails
|
|
3
|
-
Version: 0.54.
|
|
3
|
+
Version: 0.54.7
|
|
4
4
|
Summary: Python library for modeling aviation climate impacts
|
|
5
|
-
Author-email:
|
|
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.
|
|
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
|
|
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"
|