pycontrails 0.52.1__cp312-cp312-win_amd64.whl → 0.52.2__cp312-cp312-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/_version.py +2 -2
- pycontrails/core/aircraft_performance.py +115 -19
- pycontrails/core/fleet.py +11 -10
- pycontrails/core/flight.py +98 -57
- pycontrails/core/models.py +25 -16
- pycontrails/core/rgi_cython.cp312-win_amd64.pyd +0 -0
- pycontrails/core/vector.py +25 -28
- pycontrails/models/cocip/cocip.py +20 -26
- pycontrails/models/cocip/output_formats.py +11 -4
- pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +1 -1
- {pycontrails-0.52.1.dist-info → pycontrails-0.52.2.dist-info}/METADATA +4 -4
- {pycontrails-0.52.1.dist-info → pycontrails-0.52.2.dist-info}/RECORD +16 -16
- {pycontrails-0.52.1.dist-info → pycontrails-0.52.2.dist-info}/WHEEL +1 -1
- {pycontrails-0.52.1.dist-info → pycontrails-0.52.2.dist-info}/LICENSE +0 -0
- {pycontrails-0.52.1.dist-info → pycontrails-0.52.2.dist-info}/NOTICE +0 -0
- {pycontrails-0.52.1.dist-info → pycontrails-0.52.2.dist-info}/top_level.txt +0 -0
pycontrails/_version.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Any, Generic, NoReturn, overload
|
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import numpy.typing as npt
|
|
12
|
+
from overrides import overrides
|
|
12
13
|
|
|
13
14
|
from pycontrails.core import flight, fuel
|
|
14
15
|
from pycontrails.core.flight import Flight
|
|
@@ -39,6 +40,19 @@ class AircraftPerformanceParams(ModelParams):
|
|
|
39
40
|
#: The default value of 3 is sufficient for most cases.
|
|
40
41
|
n_iter: int = 3
|
|
41
42
|
|
|
43
|
+
#: Experimental. If True, fill waypoints below the lowest altitude met
|
|
44
|
+
#: level with ISA temperature when interpolating "air_temperature" or "t".
|
|
45
|
+
#: If the ``met`` data is not provided, the entire air temperature array
|
|
46
|
+
#: is approximated with the ISA temperature. Enabling this does NOT
|
|
47
|
+
#: remove any NaN values in the ``met`` data itself.
|
|
48
|
+
fill_low_altitude_with_isa_temperature: bool = False
|
|
49
|
+
|
|
50
|
+
#: Experimental. If True, fill waypoints below the lowest altitude met
|
|
51
|
+
#: level with zero wind when computing true airspeed. In other words,
|
|
52
|
+
#: approximate low-altitude true airspeed with the ground speed. Enabling
|
|
53
|
+
#: this does NOT remove any NaN values in the ``met`` data itself.
|
|
54
|
+
fill_low_altitude_with_zero_wind: bool = False
|
|
55
|
+
|
|
42
56
|
|
|
43
57
|
class AircraftPerformance(Model):
|
|
44
58
|
"""
|
|
@@ -104,6 +118,23 @@ class AircraftPerformance(Model):
|
|
|
104
118
|
Flight trajectory with aircraft performance data.
|
|
105
119
|
"""
|
|
106
120
|
|
|
121
|
+
@overrides
|
|
122
|
+
def set_source_met(self, *args: Any, **kwargs: Any) -> None:
|
|
123
|
+
fill_with_isa = self.params["fill_low_altitude_with_isa_temperature"]
|
|
124
|
+
if fill_with_isa and (self.met is None or "air_temperature" not in self.met):
|
|
125
|
+
if "air_temperature" in self.source:
|
|
126
|
+
_fill_low_altitude_with_isa_temperature(self.source, 0.0)
|
|
127
|
+
else:
|
|
128
|
+
self.source["air_temperature"] = self.source.T_isa()
|
|
129
|
+
fill_with_isa = False # we've just filled it
|
|
130
|
+
|
|
131
|
+
super().set_source_met(*args, **kwargs)
|
|
132
|
+
if not fill_with_isa:
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
met_level_0 = self.met.data["level"][-1].item() # type: ignore[union-attr]
|
|
136
|
+
_fill_low_altitude_with_isa_temperature(self.source, met_level_0)
|
|
137
|
+
|
|
107
138
|
def simulate_fuel_and_performance(
|
|
108
139
|
self,
|
|
109
140
|
*,
|
|
@@ -426,27 +457,41 @@ class AircraftPerformance(Model):
|
|
|
426
457
|
on :attr:`source`, this is returned directly. Otherwise, it is calculated
|
|
427
458
|
using :meth:`Flight.segment_true_airspeed`.
|
|
428
459
|
"""
|
|
460
|
+
tas = self.source.get("true_airspeed")
|
|
461
|
+
fill_with_groundspeed = self.params["fill_low_altitude_with_zero_wind"]
|
|
462
|
+
|
|
463
|
+
if tas is not None:
|
|
464
|
+
if not fill_with_groundspeed:
|
|
465
|
+
return tas
|
|
466
|
+
cond = np.isnan(tas)
|
|
467
|
+
tas[cond] = self.source.segment_groundspeed()[cond]
|
|
468
|
+
return tas
|
|
469
|
+
|
|
470
|
+
met_incomplete = (
|
|
471
|
+
self.met is None or "eastward_wind" not in self.met or "northward_wind" not in self.met
|
|
472
|
+
)
|
|
473
|
+
if met_incomplete:
|
|
474
|
+
if fill_with_groundspeed:
|
|
475
|
+
tas = self.source.segment_groundspeed()
|
|
476
|
+
self.source["true_airspeed"] = tas
|
|
477
|
+
return tas
|
|
478
|
+
msg = (
|
|
479
|
+
"Cannot compute 'true_airspeed' without 'eastward_wind' and 'northward_wind' "
|
|
480
|
+
"met data. Either include met data in the model constructor, define "
|
|
481
|
+
"'true_airspeed' data on the flight, or set "
|
|
482
|
+
"'fill_low_altitude_with_zero_wind' to True."
|
|
483
|
+
)
|
|
484
|
+
raise ValueError(msg)
|
|
429
485
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
except KeyError:
|
|
433
|
-
pass
|
|
434
|
-
|
|
435
|
-
if not isinstance(self.source, Flight):
|
|
436
|
-
raise TypeError("Model source must be a Flight to calculate true airspeed.")
|
|
437
|
-
|
|
438
|
-
# Two step fallback: try to find u_wind and v_wind.
|
|
439
|
-
try:
|
|
440
|
-
u = interpolate_met(self.met, self.source, "eastward_wind", **self.interp_kwargs)
|
|
441
|
-
v = interpolate_met(self.met, self.source, "northward_wind", **self.interp_kwargs)
|
|
486
|
+
u = interpolate_met(self.met, self.source, "eastward_wind", **self.interp_kwargs)
|
|
487
|
+
v = interpolate_met(self.met, self.source, "northward_wind", **self.interp_kwargs)
|
|
442
488
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
) from exc
|
|
489
|
+
if fill_with_groundspeed:
|
|
490
|
+
met_level_max = self.met.data["level"][-1].item() # type: ignore[union-attr]
|
|
491
|
+
cond = self.source.level > met_level_max
|
|
492
|
+
# We DON'T overwrite the original u and v arrays already attached to the source
|
|
493
|
+
u = np.where(cond, 0.0, u)
|
|
494
|
+
v = np.where(cond, 0.0, v)
|
|
450
495
|
|
|
451
496
|
out = self.source.segment_true_airspeed(u, v)
|
|
452
497
|
self.source["true_airspeed"] = out
|
|
@@ -543,3 +588,54 @@ class AircraftPerformanceGridData(Generic[ArrayOrFloat]):
|
|
|
543
588
|
|
|
544
589
|
#: Engine efficiency, [:math:`0-1`]
|
|
545
590
|
engine_efficiency: ArrayOrFloat
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _fill_low_altitude_with_isa_temperature(vector: GeoVectorDataset, met_level_max: float) -> None:
|
|
594
|
+
"""Fill low-altitude NaN values in ``air_temperature`` with ISA values.
|
|
595
|
+
|
|
596
|
+
The ``air_temperature`` param is assumed to have been computed by
|
|
597
|
+
interpolating against a gridded air temperature field that did not
|
|
598
|
+
necessarily extend to the surface. This function fills points below the
|
|
599
|
+
lowest altitude in the gridded data with ISA temperature values.
|
|
600
|
+
|
|
601
|
+
This function operates in-place and modifies the ``air_temperature`` field.
|
|
602
|
+
|
|
603
|
+
Parameters
|
|
604
|
+
----------
|
|
605
|
+
vector : GeoVectorDataset
|
|
606
|
+
GeoVectorDataset instance associated with the ``air_temperature`` data.
|
|
607
|
+
met_level_max : float
|
|
608
|
+
The maximum level in the met data, [:math:`hPa`].
|
|
609
|
+
"""
|
|
610
|
+
air_temperature = vector["air_temperature"]
|
|
611
|
+
is_nan = np.isnan(air_temperature)
|
|
612
|
+
low_alt = vector.level > met_level_max
|
|
613
|
+
cond = is_nan & low_alt
|
|
614
|
+
|
|
615
|
+
t_isa = vector.T_isa()
|
|
616
|
+
air_temperature[cond] = t_isa[cond]
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def _fill_low_altitude_tas_with_true_groundspeed(fl: Flight, met_level_max: float) -> None:
|
|
620
|
+
"""Fill low-altitude NaN values in ``true_airspeed`` with ground speed.
|
|
621
|
+
|
|
622
|
+
The ``true_airspeed`` param is assumed to have been computed by
|
|
623
|
+
interpolating against a gridded wind field that did not necessarily
|
|
624
|
+
extend to the surface. This function fills points below the lowest
|
|
625
|
+
altitude in the gridded data with ground speed values.
|
|
626
|
+
|
|
627
|
+
This function operates in-place and modifies the ``true_airspeed`` field.
|
|
628
|
+
|
|
629
|
+
Parameters
|
|
630
|
+
----------
|
|
631
|
+
fl : Flight
|
|
632
|
+
Flight instance associated with the ``true_airspeed`` data.
|
|
633
|
+
met_level_max : float
|
|
634
|
+
The maximum level in the met data, [:math:`hPa`].
|
|
635
|
+
"""
|
|
636
|
+
tas = fl["true_airspeed"]
|
|
637
|
+
is_nan = np.isnan(tas)
|
|
638
|
+
low_alt = fl.level > met_level_max
|
|
639
|
+
cond = is_nan & low_alt
|
|
640
|
+
|
|
641
|
+
tas[cond] = fl.segment_groundspeed()[cond]
|
pycontrails/core/fleet.py
CHANGED
|
@@ -226,28 +226,29 @@ class Fleet(Flight):
|
|
|
226
226
|
return len(self.fl_attrs)
|
|
227
227
|
|
|
228
228
|
def to_flight_list(self, copy: bool = True) -> list[Flight]:
|
|
229
|
-
"""De-concatenate merged waypoints into a list of Flight instances.
|
|
229
|
+
"""De-concatenate merged waypoints into a list of :class:`Flight` instances.
|
|
230
230
|
|
|
231
231
|
Any global :attr:`attrs` are lost.
|
|
232
232
|
|
|
233
233
|
Parameters
|
|
234
234
|
----------
|
|
235
235
|
copy : bool, optional
|
|
236
|
-
If True, make copy of each
|
|
236
|
+
If True, make copy of each :class:`Flight` instance.
|
|
237
237
|
|
|
238
238
|
Returns
|
|
239
239
|
-------
|
|
240
240
|
list[Flight]
|
|
241
|
-
List of Flights in the same order as was passed into the
|
|
241
|
+
List of Flights in the same order as was passed into the ``Fleet`` instance.
|
|
242
242
|
"""
|
|
243
|
-
|
|
244
|
-
# Avoid self.dataframe to purposely drop global attrs
|
|
245
|
-
tmp = pd.DataFrame(self.data, copy=copy)
|
|
246
|
-
grouped = tmp.groupby("flight_id", sort=False)
|
|
247
|
-
|
|
243
|
+
indices = self.dataframe.groupby("flight_id", sort=False).indices
|
|
248
244
|
return [
|
|
249
|
-
Flight(
|
|
250
|
-
|
|
245
|
+
Flight(
|
|
246
|
+
data=VectorDataDict({k: v[idx] for k, v in self.data.items()}),
|
|
247
|
+
attrs=self.fl_attrs[flight_id],
|
|
248
|
+
copy=copy,
|
|
249
|
+
fuel=self.fuel,
|
|
250
|
+
)
|
|
251
|
+
for flight_id, idx in indices.items()
|
|
251
252
|
]
|
|
252
253
|
|
|
253
254
|
###################################
|
pycontrails/core/flight.py
CHANGED
|
@@ -954,28 +954,13 @@ class Flight(GeoVectorDataset):
|
|
|
954
954
|
# STEP 3: Set the time index, and sort it
|
|
955
955
|
df = df.set_index("time", verify_integrity=True).sort_index()
|
|
956
956
|
|
|
957
|
-
# STEP 4:
|
|
958
|
-
#
|
|
959
|
-
#
|
|
960
|
-
#
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
# would instead wrap the other way). For this flights spanning the
|
|
964
|
-
# antimeridian, we translate them to a common "chart" away from the
|
|
965
|
-
# antimeridian (see variable `shift`), then apply the interpolation,
|
|
966
|
-
# then shift back to their original position.
|
|
967
|
-
lon = df["longitude"].to_numpy()
|
|
968
|
-
sign_ = np.sign(lon)
|
|
969
|
-
min_pos = np.min(lon[sign_ == 1.0], initial=np.inf)
|
|
970
|
-
max_neg = np.max(lon[sign_ == -1.0], initial=-np.inf)
|
|
971
|
-
|
|
972
|
-
if (180.0 - min_pos) + (180.0 + max_neg) < 180.0 and min_pos < np.inf and max_neg > -np.inf:
|
|
973
|
-
# In this case, we believe the flight crosses the antimeridian
|
|
974
|
-
shift = min_pos
|
|
975
|
-
# So we shift the longitude "chart"
|
|
957
|
+
# STEP 4: handle antimeridian crossings
|
|
958
|
+
# For flights spanning the antimeridian, we translate them to a
|
|
959
|
+
# common "chart" away from the antimeridian (see variable `shift`),
|
|
960
|
+
# then apply the interpolation, then shift back to their original position.
|
|
961
|
+
shift = self._antimeridian_shift()
|
|
962
|
+
if shift is not None:
|
|
976
963
|
df["longitude"] = (df["longitude"] - shift) % 360.0
|
|
977
|
-
else:
|
|
978
|
-
shift = None
|
|
979
964
|
|
|
980
965
|
# STEP 5: Resample flight to freq
|
|
981
966
|
# Save altitudes to copy over - these just get rounded down in time.
|
|
@@ -1189,19 +1174,12 @@ class Flight(GeoVectorDataset):
|
|
|
1189
1174
|
"""
|
|
1190
1175
|
|
|
1191
1176
|
# Check if flight crosses antimeridian line
|
|
1177
|
+
# If it does, shift longitude chart to remove jump
|
|
1192
1178
|
lon_ = self["longitude"]
|
|
1193
1179
|
lat_ = self["latitude"]
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
max_neg = np.max(lon_[sign_ == -1.0], initial=-np.inf)
|
|
1197
|
-
|
|
1198
|
-
if (180.0 - min_pos) + (180.0 + max_neg) < 180.0 and min_pos < np.inf and max_neg > -np.inf:
|
|
1199
|
-
# In this case, we believe the flight crosses the antimeridian
|
|
1200
|
-
shift = min_pos
|
|
1201
|
-
# So we shift the longitude "chart"
|
|
1180
|
+
shift = self._antimeridian_shift()
|
|
1181
|
+
if shift is not None:
|
|
1202
1182
|
lon_ = (lon_ - shift) % 360.0
|
|
1203
|
-
else:
|
|
1204
|
-
shift = None
|
|
1205
1183
|
|
|
1206
1184
|
# Make a fake flight that flies at constant height so distance is just
|
|
1207
1185
|
# distance traveled across groud
|
|
@@ -1262,6 +1240,55 @@ class Flight(GeoVectorDataset):
|
|
|
1262
1240
|
|
|
1263
1241
|
return lat, lon, seg_idx
|
|
1264
1242
|
|
|
1243
|
+
def _antimeridian_shift(self) -> float | None:
|
|
1244
|
+
"""Determine shift required for resampling trajectories that cross antimeridian.
|
|
1245
|
+
|
|
1246
|
+
Because flights sometimes span more than 180 degree longitude (for example,
|
|
1247
|
+
when flight-level winds favor travel in a specific direction, typically eastward),
|
|
1248
|
+
antimeridian crossings cannot reliably be detected by looking only at minimum
|
|
1249
|
+
and maximum longitudes.
|
|
1250
|
+
|
|
1251
|
+
Instead, this function checks each flight segment for an antimeridian crossing,
|
|
1252
|
+
and if it finds one returns the coordinate of a meridian that is not crossed by
|
|
1253
|
+
the flight.
|
|
1254
|
+
|
|
1255
|
+
Returns
|
|
1256
|
+
-------
|
|
1257
|
+
float | None
|
|
1258
|
+
Longitude shift for handling antimeridian crossings, or None if the
|
|
1259
|
+
flight does not cross the antimeridian.
|
|
1260
|
+
"""
|
|
1261
|
+
|
|
1262
|
+
# logic for detecting crossings is consistent with _antimeridian_crossing,
|
|
1263
|
+
# but implementation is separate to keep performance costs as low as possible
|
|
1264
|
+
lon = self["longitude"]
|
|
1265
|
+
if np.any(np.isnan(lon)):
|
|
1266
|
+
warnings.warn("Anti-meridian crossings can't be reliably detected with nan longitudes")
|
|
1267
|
+
|
|
1268
|
+
s1 = (lon >= -180) & (lon <= -90)
|
|
1269
|
+
s2 = (lon <= 180) & (lon >= 90)
|
|
1270
|
+
jump12 = s1[:-1] & s2[1:] # westward
|
|
1271
|
+
jump21 = s2[:-1] & s1[1:] # eastward
|
|
1272
|
+
if not np.any(jump12 | jump21):
|
|
1273
|
+
return None
|
|
1274
|
+
|
|
1275
|
+
# separate flight into segments that are east and west of crossings
|
|
1276
|
+
net_westward = np.insert(np.cumsum(jump12.astype(int) - jump21.astype(int)), 0, 0)
|
|
1277
|
+
max_westward = net_westward.max()
|
|
1278
|
+
if max_westward - net_westward.min() > 1:
|
|
1279
|
+
msg = "Cannot handle consecutive antimeridian crossings in the same direction"
|
|
1280
|
+
raise ValueError(msg)
|
|
1281
|
+
east = (net_westward == 0) if max_westward == 1 else (net_westward == -1)
|
|
1282
|
+
|
|
1283
|
+
# shift must be between maximum longitude east of crossings
|
|
1284
|
+
# and minimum longitude west of crossings
|
|
1285
|
+
shift_min = np.nanmax(lon[east])
|
|
1286
|
+
shift_max = np.nanmin(lon[~east])
|
|
1287
|
+
if shift_min >= shift_max:
|
|
1288
|
+
msg = "Cannot handle flight that spans more than 360 degrees longitude"
|
|
1289
|
+
raise ValueError(msg)
|
|
1290
|
+
return (shift_min + shift_max) / 2
|
|
1291
|
+
|
|
1265
1292
|
def _geodesic_interpolation(self, geodesic_threshold: float) -> pd.DataFrame | None:
|
|
1266
1293
|
"""Geodesic interpolate between large gaps between waypoints.
|
|
1267
1294
|
|
|
@@ -1506,25 +1533,25 @@ class Flight(GeoVectorDataset):
|
|
|
1506
1533
|
|
|
1507
1534
|
>>> # Build flight
|
|
1508
1535
|
>>> df = pd.DataFrame()
|
|
1509
|
-
>>> df[
|
|
1510
|
-
>>> df[
|
|
1511
|
-
>>> df[
|
|
1512
|
-
>>> df[
|
|
1513
|
-
>>> fl = Flight(df).resample_and_fill(
|
|
1536
|
+
>>> df["time"] = pd.date_range("2022-03-01T00", "2022-03-01T03", periods=11)
|
|
1537
|
+
>>> df["longitude"] = np.linspace(-20, 20, 11)
|
|
1538
|
+
>>> df["latitude"] = np.linspace(-20, 20, 11)
|
|
1539
|
+
>>> df["altitude"] = np.linspace(9500, 10000, 11)
|
|
1540
|
+
>>> fl = Flight(df).resample_and_fill("10s")
|
|
1514
1541
|
|
|
1515
1542
|
>>> # Intersect and attach
|
|
1516
|
-
>>> fl["air_temperature"] = fl.intersect_met(met[
|
|
1543
|
+
>>> fl["air_temperature"] = fl.intersect_met(met["air_temperature"])
|
|
1517
1544
|
>>> fl["air_temperature"]
|
|
1518
|
-
array([235.94657007, 235.
|
|
1545
|
+
array([235.94657007, 235.55745645, 235.56709768, ..., 234.59917962,
|
|
1519
1546
|
234.60387402, 234.60845312])
|
|
1520
1547
|
|
|
1521
1548
|
>>> # Length (in meters) of waypoints whose temperature exceeds 236K
|
|
1522
1549
|
>>> fl.length_met("air_temperature", threshold=236)
|
|
1523
|
-
np.float64(
|
|
1550
|
+
np.float64(3589705.998...)
|
|
1524
1551
|
|
|
1525
1552
|
>>> # Proportion (with respect to distance) of waypoints whose temperature exceeds 236K
|
|
1526
1553
|
>>> fl.proportion_met("air_temperature", threshold=236)
|
|
1527
|
-
np.float64(0.
|
|
1554
|
+
np.float64(0.576...)
|
|
1528
1555
|
"""
|
|
1529
1556
|
if key not in self.data:
|
|
1530
1557
|
raise KeyError(f"Column {key} does not exist in data.")
|
|
@@ -1591,10 +1618,30 @@ class Flight(GeoVectorDataset):
|
|
|
1591
1618
|
:class:`matplotlib.axes.Axes`
|
|
1592
1619
|
Plot
|
|
1593
1620
|
"""
|
|
1594
|
-
|
|
1621
|
+
kwargs.setdefault("legend", False)
|
|
1622
|
+
ax = self.dataframe.plot(x="longitude", y="latitude", **kwargs)
|
|
1595
1623
|
ax.set(xlabel="longitude", ylabel="latitude")
|
|
1596
1624
|
return ax
|
|
1597
1625
|
|
|
1626
|
+
def plot_profile(self, **kwargs: Any) -> matplotlib.axes.Axes:
|
|
1627
|
+
"""Plot flight trajectory time-altitude values.
|
|
1628
|
+
|
|
1629
|
+
Parameters
|
|
1630
|
+
----------
|
|
1631
|
+
**kwargs : Any
|
|
1632
|
+
Additional plot properties to passed to `pd.DataFrame.plot`
|
|
1633
|
+
|
|
1634
|
+
Returns
|
|
1635
|
+
-------
|
|
1636
|
+
:class:`matplotlib.axes.Axes`
|
|
1637
|
+
Plot
|
|
1638
|
+
"""
|
|
1639
|
+
kwargs.setdefault("legend", False)
|
|
1640
|
+
df = self.dataframe.assign(altitude_ft=self.altitude_ft)
|
|
1641
|
+
ax = df.plot(x="time", y="altitude_ft", **kwargs)
|
|
1642
|
+
ax.set(xlabel="time", ylabel="altitude_ft")
|
|
1643
|
+
return ax
|
|
1644
|
+
|
|
1598
1645
|
|
|
1599
1646
|
def _return_linestring(data: dict[str, npt.NDArray[np.float64]]) -> list[list[float]]:
|
|
1600
1647
|
"""Return list of coordinates for geojson constructions.
|
|
@@ -1631,18 +1678,14 @@ def _antimeridian_index(longitude: pd.Series, crs: str = "EPSG:4326") -> list[in
|
|
|
1631
1678
|
|
|
1632
1679
|
Returns
|
|
1633
1680
|
-------
|
|
1634
|
-
int
|
|
1635
|
-
|
|
1681
|
+
list[int]
|
|
1682
|
+
Indices after jump, or empty list of flight does not cross antimeridian.
|
|
1636
1683
|
|
|
1637
1684
|
Raises
|
|
1638
1685
|
------
|
|
1639
1686
|
ValueError
|
|
1640
1687
|
CRS is not supported.
|
|
1641
|
-
Flight crosses antimeridian several times.
|
|
1642
1688
|
"""
|
|
1643
|
-
# FIXME: This logic here is somewhat outdated - the _interpolate_altitude
|
|
1644
|
-
# method handles this somewhat more reliably
|
|
1645
|
-
# This function should get updated to follow the logic there.
|
|
1646
1689
|
# WGS84
|
|
1647
1690
|
if crs in ["EPSG:4326"]:
|
|
1648
1691
|
l1 = (-180.0, -90.0)
|
|
@@ -1878,7 +1921,7 @@ def _altitude_interpolation_climb_descend_middle(
|
|
|
1878
1921
|
s = pd.Series(altitude)
|
|
1879
1922
|
|
|
1880
1923
|
# Check to see if we have gaps greater than two hours
|
|
1881
|
-
step_threshold =
|
|
1924
|
+
step_threshold = np.timedelta64(2, "h") / freq
|
|
1882
1925
|
step_groups = na_group_size > step_threshold
|
|
1883
1926
|
if np.any(step_groups):
|
|
1884
1927
|
# If there are gaps greater than two hours, step through one by one
|
|
@@ -2214,16 +2257,14 @@ def segment_rocd(
|
|
|
2214
2257
|
if air_temperature is None:
|
|
2215
2258
|
return out
|
|
2216
2259
|
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
T_isa = units.m_to_T_isa(altitude_m)
|
|
2260
|
+
altitude_m = units.ft_to_m(altitude_ft)
|
|
2261
|
+
T_isa = units.m_to_T_isa(altitude_m)
|
|
2220
2262
|
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
return T_correction * out
|
|
2263
|
+
T_correction = np.empty_like(altitude_ft)
|
|
2264
|
+
T_correction[:-1] = (air_temperature[:-1] + air_temperature[1:]) / (T_isa[:-1] + T_isa[1:])
|
|
2265
|
+
T_correction[-1] = np.nan
|
|
2266
|
+
|
|
2267
|
+
return T_correction * out
|
|
2227
2268
|
|
|
2228
2269
|
|
|
2229
2270
|
def _resample_to_freq(df: pd.DataFrame, freq: str) -> tuple[pd.DataFrame, pd.DatetimeIndex]:
|
pycontrails/core/models.py
CHANGED
|
@@ -362,6 +362,8 @@ class Model(ABC):
|
|
|
362
362
|
def interp_kwargs(self) -> dict[str, Any]:
|
|
363
363
|
"""Shortcut to create interpolation arguments from :attr:`params`.
|
|
364
364
|
|
|
365
|
+
The output of this is useful for passing to :func:`interpolate_met`.
|
|
366
|
+
|
|
365
367
|
Returns
|
|
366
368
|
-------
|
|
367
369
|
dict[str, Any]
|
|
@@ -376,13 +378,14 @@ class Model(ABC):
|
|
|
376
378
|
|
|
377
379
|
as determined by :attr:`params`.
|
|
378
380
|
"""
|
|
381
|
+
params = self.params
|
|
379
382
|
return {
|
|
380
|
-
"method":
|
|
381
|
-
"bounds_error":
|
|
382
|
-
"fill_value":
|
|
383
|
-
"localize":
|
|
384
|
-
"use_indices":
|
|
385
|
-
"q_method":
|
|
383
|
+
"method": params["interpolation_method"],
|
|
384
|
+
"bounds_error": params["interpolation_bounds_error"],
|
|
385
|
+
"fill_value": params["interpolation_fill_value"],
|
|
386
|
+
"localize": params["interpolation_localize"],
|
|
387
|
+
"use_indices": params["interpolation_use_indices"],
|
|
388
|
+
"q_method": params["interpolation_q_method"],
|
|
386
389
|
}
|
|
387
390
|
|
|
388
391
|
def require_met(self) -> MetDataset:
|
|
@@ -585,16 +588,7 @@ class Model(ABC):
|
|
|
585
588
|
KeyError
|
|
586
589
|
Variable not found in :attr:`source` or :attr:`met`.
|
|
587
590
|
"""
|
|
588
|
-
variables
|
|
589
|
-
if variable is None:
|
|
590
|
-
if optional:
|
|
591
|
-
variables = (*self.met_variables, *self.optional_met_variables)
|
|
592
|
-
else:
|
|
593
|
-
variables = self.met_variables
|
|
594
|
-
elif isinstance(variable, MetVariable):
|
|
595
|
-
variables = (variable,)
|
|
596
|
-
else:
|
|
597
|
-
variables = variable
|
|
591
|
+
variables = self._determine_relevant_variables(optional, variable)
|
|
598
592
|
|
|
599
593
|
q_method = self.params["interpolation_q_method"]
|
|
600
594
|
|
|
@@ -640,6 +634,20 @@ class Model(ABC):
|
|
|
640
634
|
met_key, da, self.source, self.params, q_method
|
|
641
635
|
)
|
|
642
636
|
|
|
637
|
+
def _determine_relevant_variables(
|
|
638
|
+
self,
|
|
639
|
+
optional: bool,
|
|
640
|
+
variable: MetVariable | Sequence[MetVariable] | None,
|
|
641
|
+
) -> Sequence[MetVariable | tuple[MetVariable, ...]]:
|
|
642
|
+
"""Determine the relevant variables used in :meth:`set_source_met`."""
|
|
643
|
+
if variable is None:
|
|
644
|
+
if optional:
|
|
645
|
+
return (*self.met_variables, *self.optional_met_variables)
|
|
646
|
+
return self.met_variables
|
|
647
|
+
if isinstance(variable, MetVariable):
|
|
648
|
+
return (variable,)
|
|
649
|
+
return variable
|
|
650
|
+
|
|
643
651
|
# Following python implementation
|
|
644
652
|
# https://github.com/python/cpython/blob/618b7a8260bb40290d6551f24885931077309590/Lib/collections/__init__.py#L231
|
|
645
653
|
__marker = object()
|
|
@@ -814,6 +822,7 @@ def interpolate_met(
|
|
|
814
822
|
vector: GeoVectorDataset,
|
|
815
823
|
met_key: str,
|
|
816
824
|
vector_key: str | None = None,
|
|
825
|
+
*,
|
|
817
826
|
q_method: str | None = None,
|
|
818
827
|
**interp_kwargs: Any,
|
|
819
828
|
) -> npt.NDArray[np.float64]:
|
|
Binary file
|
pycontrails/core/vector.py
CHANGED
|
@@ -657,7 +657,7 @@ class VectorDataset:
|
|
|
657
657
|
8 15 18
|
|
658
658
|
|
|
659
659
|
"""
|
|
660
|
-
vectors = [v for v in vectors if v] # remove
|
|
660
|
+
vectors = [v for v in vectors if v is not None] # remove None values
|
|
661
661
|
|
|
662
662
|
if not vectors:
|
|
663
663
|
return cls()
|
|
@@ -707,36 +707,33 @@ class VectorDataset:
|
|
|
707
707
|
bool
|
|
708
708
|
True if both instances have identical :attr:`data` and :attr:`attrs`.
|
|
709
709
|
"""
|
|
710
|
-
if isinstance(other, VectorDataset):
|
|
711
|
-
|
|
712
|
-
for key in self.attrs:
|
|
713
|
-
if isinstance(self.attrs[key], np.ndarray):
|
|
714
|
-
# equal_nan not supported for non-numeric data
|
|
715
|
-
equal_nan = not np.issubdtype(self.attrs[key].dtype, "O")
|
|
716
|
-
try:
|
|
717
|
-
eq = np.array_equal(self.attrs[key], other.attrs[key], equal_nan=equal_nan)
|
|
718
|
-
except KeyError:
|
|
719
|
-
return False
|
|
720
|
-
else:
|
|
721
|
-
eq = self.attrs[key] == other.attrs[key]
|
|
722
|
-
|
|
723
|
-
if not eq:
|
|
724
|
-
return False
|
|
710
|
+
if not isinstance(other, VectorDataset):
|
|
711
|
+
return False
|
|
725
712
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
equal_nan = not np.issubdtype(self[key].dtype, "O")
|
|
730
|
-
try:
|
|
731
|
-
eq = np.array_equal(self[key], other[key], equal_nan=equal_nan)
|
|
732
|
-
except KeyError:
|
|
733
|
-
return False
|
|
713
|
+
# Check attrs
|
|
714
|
+
if self.attrs.keys() != other.attrs.keys():
|
|
715
|
+
return False
|
|
734
716
|
|
|
735
|
-
|
|
717
|
+
for key, val in self.attrs.items():
|
|
718
|
+
if isinstance(val, np.ndarray):
|
|
719
|
+
# equal_nan not supported for non-numeric data
|
|
720
|
+
equal_nan = not np.issubdtype(val.dtype, "O")
|
|
721
|
+
if not np.array_equal(val, other.attrs[key], equal_nan=equal_nan):
|
|
736
722
|
return False
|
|
723
|
+
elif val != other.attrs[key]:
|
|
724
|
+
return False
|
|
725
|
+
|
|
726
|
+
# Check data
|
|
727
|
+
if self.data.keys() != other.data.keys():
|
|
728
|
+
return False
|
|
737
729
|
|
|
738
|
-
|
|
739
|
-
|
|
730
|
+
for key, val in self.data.items():
|
|
731
|
+
# equal_nan not supported for non-numeric data (e.g. strings)
|
|
732
|
+
equal_nan = not np.issubdtype(val.dtype, "O")
|
|
733
|
+
if not np.array_equal(val, other[key], equal_nan=equal_nan):
|
|
734
|
+
return False
|
|
735
|
+
|
|
736
|
+
return True
|
|
740
737
|
|
|
741
738
|
@property
|
|
742
739
|
def size(self) -> int:
|
|
@@ -986,7 +983,7 @@ class VectorDataset:
|
|
|
986
983
|
numeric_attrs = (
|
|
987
984
|
attr
|
|
988
985
|
for attr, val in self.attrs.items()
|
|
989
|
-
if (isinstance(val, (int, float)) and attr not in ignore_keys)
|
|
986
|
+
if (isinstance(val, (int, float, np.number)) and attr not in ignore_keys)
|
|
990
987
|
)
|
|
991
988
|
self.broadcast_attrs(numeric_attrs, overwrite)
|
|
992
989
|
|
|
@@ -1166,49 +1166,43 @@ class Cocip(Model):
|
|
|
1166
1166
|
# ---
|
|
1167
1167
|
# Create contrail dataframe (self.contrail)
|
|
1168
1168
|
# ---
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1169
|
+
self.contrail = GeoVectorDataset.sum(self.contrail_list).dataframe
|
|
1170
|
+
self.contrail["timestep"] = np.concatenate(
|
|
1171
|
+
[np.full(c.size, i) for i, c in enumerate(self.contrail_list)]
|
|
1172
|
+
)
|
|
1172
1173
|
|
|
1173
1174
|
# add age in hours to the contrail waypoint outputs
|
|
1174
1175
|
age_hours = np.empty_like(self.contrail["ef"])
|
|
1175
1176
|
np.divide(self.contrail["age"], np.timedelta64(1, "h"), out=age_hours)
|
|
1176
1177
|
self.contrail["age_hours"] = age_hours
|
|
1177
1178
|
|
|
1178
|
-
|
|
1179
|
+
verbose_outputs = self.params["verbose_outputs"]
|
|
1180
|
+
if verbose_outputs:
|
|
1179
1181
|
# Compute dt_integration -- logic is somewhat complicated, but
|
|
1180
1182
|
# we're simply addressing that the first dt_integration
|
|
1181
1183
|
# is different from the rest
|
|
1182
1184
|
|
|
1183
|
-
# We call reset_index
|
|
1184
|
-
#
|
|
1185
|
-
# is a RangeIndex, which we use in the `groupby` to identify the
|
|
1185
|
+
# We call reset_index to introduces an `index` RangeIndex column,
|
|
1186
|
+
# Which we use in the `groupby` to identify the
|
|
1186
1187
|
# index of the first evolution step at each waypoint.
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
cols = ["formation_time", "time", "level_0"]
|
|
1191
|
-
first_form_time = seq_index.groupby("waypoint")[cols].first()
|
|
1188
|
+
tmp = self.contrail.reset_index()
|
|
1189
|
+
cols = ["formation_time", "time", "index"]
|
|
1190
|
+
first_form_time = tmp.groupby("waypoint")[cols].first()
|
|
1192
1191
|
first_dt = first_form_time["time"] - first_form_time["formation_time"]
|
|
1193
|
-
first_dt
|
|
1194
|
-
|
|
1195
|
-
seq_index = seq_index.set_index("level_0")
|
|
1196
|
-
seq_index["dt_integration"] = first_dt
|
|
1197
|
-
seq_index.fillna({"dt_integration": self.params["dt_integration"]}, inplace=True)
|
|
1192
|
+
first_dt = first_dt.set_axis(first_form_time["index"])
|
|
1198
1193
|
|
|
1199
|
-
self.contrail =
|
|
1194
|
+
self.contrail = tmp.set_index("index")
|
|
1195
|
+
self.contrail["dt_integration"] = first_dt
|
|
1196
|
+
self.contrail.fillna({"dt_integration": self.params["dt_integration"]}, inplace=True)
|
|
1200
1197
|
|
|
1201
1198
|
# ---
|
|
1202
1199
|
# Create contrail xr.Dataset (self.contrail_dataset)
|
|
1203
1200
|
# ---
|
|
1204
1201
|
if isinstance(self.source, Fleet):
|
|
1205
|
-
|
|
1206
|
-
self.contrail.set_index(["flight_id", "timestep", "waypoint"])
|
|
1207
|
-
)
|
|
1202
|
+
keys = ["flight_id", "timestep", "waypoint"]
|
|
1208
1203
|
else:
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
)
|
|
1204
|
+
keys = ["timestep", "waypoint"]
|
|
1205
|
+
self.contrail_dataset = xr.Dataset.from_dataframe(self.contrail.set_index(keys))
|
|
1212
1206
|
|
|
1213
1207
|
# ---
|
|
1214
1208
|
# Create output Flight / Fleet (self.source)
|
|
@@ -1229,7 +1223,7 @@ class Cocip(Model):
|
|
|
1229
1223
|
]
|
|
1230
1224
|
|
|
1231
1225
|
# add additional columns
|
|
1232
|
-
if
|
|
1226
|
+
if verbose_outputs:
|
|
1233
1227
|
sac_cols += ["dT_dz", "ds_dz", "dz_max"]
|
|
1234
1228
|
|
|
1235
1229
|
downwash_cols = ["rho_air_1", "iwc_1", "n_ice_per_m_1"]
|
|
@@ -1253,7 +1247,7 @@ class Cocip(Model):
|
|
|
1253
1247
|
|
|
1254
1248
|
rad_keys = ["sdr", "rsr", "olr", "rf_sw", "rf_lw", "rf_net"]
|
|
1255
1249
|
for key in rad_keys:
|
|
1256
|
-
if
|
|
1250
|
+
if verbose_outputs:
|
|
1257
1251
|
agg_dict[key] = ["mean", "min", "max"]
|
|
1258
1252
|
else:
|
|
1259
1253
|
agg_dict[key] = ["mean"]
|
|
@@ -1191,6 +1191,7 @@ def meteorological_time_slice_statistics(
|
|
|
1191
1191
|
# ISSR: Volume of airspace with RHi > 100% between FL300 and FL450
|
|
1192
1192
|
met = humidity_scaling.eval(met)
|
|
1193
1193
|
rhi = met["rhi"].data.sel(level=slice(150, 300))
|
|
1194
|
+
rhi = rhi.interp(time=time)
|
|
1194
1195
|
is_issr = rhi > 1
|
|
1195
1196
|
|
|
1196
1197
|
# Cirrus in a longitude-latitude grid
|
|
@@ -1245,9 +1246,15 @@ def radiation_time_slice_statistics(
|
|
|
1245
1246
|
surface_area = geo.grid_surface_area(rad["longitude"].values, rad["latitude"].values)
|
|
1246
1247
|
weights = surface_area.values / np.nansum(surface_area)
|
|
1247
1248
|
stats = {
|
|
1248
|
-
"mean_sdr_domain": np.nansum(
|
|
1249
|
-
|
|
1250
|
-
|
|
1249
|
+
"mean_sdr_domain": np.nansum(
|
|
1250
|
+
np.squeeze(rad["sdr"].data.interp(time=time).values) * weights
|
|
1251
|
+
),
|
|
1252
|
+
"mean_rsr_domain": np.nansum(
|
|
1253
|
+
np.squeeze(rad["rsr"].data.interp(time=time).values) * weights
|
|
1254
|
+
),
|
|
1255
|
+
"mean_olr_domain": np.nansum(
|
|
1256
|
+
np.squeeze(rad["olr"].data.interp(time=time).values) * weights
|
|
1257
|
+
),
|
|
1251
1258
|
}
|
|
1252
1259
|
return pd.Series(stats)
|
|
1253
1260
|
|
|
@@ -1598,7 +1605,7 @@ def contrails_to_hi_res_grid(
|
|
|
1598
1605
|
module_not_found_error=exc,
|
|
1599
1606
|
)
|
|
1600
1607
|
|
|
1601
|
-
for i in tqdm(heads_t.index
|
|
1608
|
+
for i in tqdm(heads_t.index):
|
|
1602
1609
|
contrail_segment = GeoVectorDataset(
|
|
1603
1610
|
pd.concat([heads_t[cols_req].loc[i], tails_t[cols_req].loc[i]], axis=1).T, copy=True
|
|
1604
1611
|
)
|
|
@@ -561,7 +561,7 @@ UID No,Manufacturer,Engine Identification,Combustor Description,Eng Type,B/P Rat
|
|
|
561
561
|
1TL002,Textron Lycoming,ALF 502R-3,,TF,5.7,11.4,29.8,0.3476,0.288,0.1027,0.0432,11.2,9.94,6.15,3.3,0.433,0.5,8.43,44.67,0.056,0.053,0.287,6.51,12.63,12,5.47,2.133,13,101.3,102.4,288,293,0.0088,0.0108
|
|
562
562
|
1TL003,Textron Lycoming,ALF 502R-5,,TF,5.6,12,31,0.3581,0.2955,0.1034,0.0408,13.35,10.56,6.6,3.78,0.3,0.25,7.1,40.93,0.06,0.053,0.217,5.39,13.5,12.7,5.7,2.3,15.4,101.3,102.4,288,293,0.0088,0.0108
|
|
563
563
|
1TL004,Textron Lycoming,"LF507-1F, -1H",,TF,5.1,13,31,0.3578,0.2961,0.1083,0.0453,14.52,12.02,6.39,3.28,0.2,0.3,4.43,37.83,0.01,0.01,0.12,4.72,10.3,10.2,6.9,6.8,10.6,101.3,102.4,276,280,0.0023,0.0038
|
|
564
|
-
1ZM001,IVCHENKO PROGRESS ZMBK,D-36,,TF,5,19.9,63.765,0.634,0.533,0.211
|
|
564
|
+
1ZM001,IVCHENKO PROGRESS ZMBK,D-36,,TF,5,19.9,63.765,0.634,0.533,0.211,0.092,26,22,9,5.5,0.5,0.4,2.7,20.7,0,0,0,5.4,14.8,,,,14.8,99.9,101.5,268,295,0.0017,0.0083
|
|
565
565
|
13ZM002,IVCHENKO PROGRESS ZMBK,D-36 ser. 4A,,TF,5,19.9,63.77,0.634,0.533,0.211,0.092,26,22,9,5.5,0.5,0.4,2.7,20.7,0,0,0,5.4,14.8,,,,14.8,99.9,101.5,268,295,0.0017,0.0083
|
|
566
566
|
13ZM003,IVCHENKO PROGRESS ZMBK,D-436-148 F1,,TF,4.9,19.8,64.43,0.548,0.468,0.218,0.093,18.93,16,7.26,3.64,0.54,0.54,2.99,23.46,0.1,0.04,0.07,2.26,6.7,,,,6.7,99.02,102.66,266,301,0.00248,0.01624
|
|
567
567
|
13ZM004,IVCHENKO PROGRESS ZMBK,D-436-148 F2,,TF,4.9,20.73,68.72,0.581,0.493,0.225,0.099,19.76,16.64,7.31,3.78,0.48,0.4,2.71,19.56,0.09,0.05,0.08,1.39,6.7,,,,6.9,99.02,102.66,266,301,0.00248,0.01624
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pycontrails
|
|
3
|
-
Version: 0.52.
|
|
3
|
+
Version: 0.52.2
|
|
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
|
|
@@ -36,7 +36,7 @@ Requires-Dist: xarray >=2022.3
|
|
|
36
36
|
Provides-Extra: complete
|
|
37
37
|
Requires-Dist: pycontrails[ecmwf,gcp,gfs,jupyter,pyproj,sat,vis,zarr] ; extra == 'complete'
|
|
38
38
|
Provides-Extra: dev
|
|
39
|
-
Requires-Dist: black[jupyter] ==24.4.
|
|
39
|
+
Requires-Dist: black[jupyter] ==24.4.2 ; extra == 'dev'
|
|
40
40
|
Requires-Dist: dep-license ; extra == 'dev'
|
|
41
41
|
Requires-Dist: fastparquet >=0.8 ; extra == 'dev'
|
|
42
42
|
Requires-Dist: ipdb >=0.13 ; extra == 'dev'
|
|
@@ -50,7 +50,7 @@ Requires-Dist: pyarrow >=5.0 ; extra == 'dev'
|
|
|
50
50
|
Requires-Dist: pytest >=8.2 ; extra == 'dev'
|
|
51
51
|
Requires-Dist: pytest-cov >=2.11 ; extra == 'dev'
|
|
52
52
|
Requires-Dist: requests >=2.25 ; extra == 'dev'
|
|
53
|
-
Requires-Dist: ruff ==0.
|
|
53
|
+
Requires-Dist: ruff ==0.5.3 ; extra == 'dev'
|
|
54
54
|
Requires-Dist: setuptools ; extra == 'dev'
|
|
55
55
|
Provides-Extra: docs
|
|
56
56
|
Requires-Dist: doc8 >=1.1 ; extra == 'docs'
|
|
@@ -122,7 +122,7 @@ Requires-Dist: zarr >=2.12 ; extra == 'zarr'
|
|
|
122
122
|
|---------------|-------------------------------------------------------------------|
|
|
123
123
|
| **Version** | [](https://pypi.python.org/pypi/pycontrails) [](https://anaconda.org/conda-forge/pycontrails) [](https://pypi.python.org/pypi/pycontrails) |
|
|
124
124
|
| **Citation** | [](https://zenodo.org/badge/latestdoi/617248930) |
|
|
125
|
-
| **Tests** | [](https://github.com/contrailcirrus/pycontrails/actions/workflows/test.yaml) [](https://github.com/contrailcirrus/pycontrails/actions/workflows/docs.yaml) [](https://github.com/contrailcirrus/pycontrails/actions/workflows/release.yaml) [](https://securityscorecards.dev/viewer?uri=github.com/contrailcirrus/pycontrails)|
|
|
125
|
+
| **Tests** | [](https://github.com/contrailcirrus/pycontrails/actions/workflows/test.yaml) [](https://github.com/contrailcirrus/pycontrails/actions/workflows/docs.yaml) [](https://github.com/contrailcirrus/pycontrails/actions/workflows/release.yaml) [](https://securityscorecards.dev/viewer?uri=github.com/contrailcirrus/pycontrails)|
|
|
126
126
|
| **License** | [](https://github.com/contrailcirrus/pycontrails/blob/main/LICENSE) |
|
|
127
127
|
| **Community** | [](https://github.com/contrailcirrus/pycontrails/discussions) [](https://github.com/contrailcirrus/pycontrails/issues) [](https://github.com/contrailcirrus/pycontrails/pulls) |
|
|
128
128
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
pycontrails/__init__.py,sha256=EpPulx2dBYpqZNsyh6HTwGGnFsvBVHBXabG5VInwSg4,2071
|
|
2
|
-
pycontrails/_version.py,sha256=
|
|
2
|
+
pycontrails/_version.py,sha256=4pmj8jsxVwfVqZJgR1h1AgtFc2HbrVerdrhzrtIYe-U,429
|
|
3
3
|
pycontrails/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
pycontrails/core/__init__.py,sha256=X0DX2FGboPN_svwN2xiBzoPpHhDtg0oKFjXQfmCqMWU,886
|
|
5
|
-
pycontrails/core/aircraft_performance.py,sha256=
|
|
5
|
+
pycontrails/core/aircraft_performance.py,sha256=osV68kKLpvnzBX8fqZcBefbKoYqTxhAf2EWLRdTA7Ec,26748
|
|
6
6
|
pycontrails/core/airports.py,sha256=nGKXN3jOtzsDCaJZVFNO3e3w-U3lqMTz5Ww5jALiRJY,6984
|
|
7
7
|
pycontrails/core/cache.py,sha256=5B8h6gqhn5Sy_pQR0wGn7QX-Cou8GdjwDUM59SRuDns,28852
|
|
8
8
|
pycontrails/core/coordinates.py,sha256=J5qjGuXgbLUw_U9_qREdgOaHl0ngK6Hbbjj3uw7FwNE,5565
|
|
9
|
-
pycontrails/core/fleet.py,sha256=
|
|
10
|
-
pycontrails/core/flight.py,sha256=
|
|
9
|
+
pycontrails/core/fleet.py,sha256=vB1XHcajtTLlelY_u72SAYUCt3h2smzqZIpeLFJaW0M,16610
|
|
10
|
+
pycontrails/core/flight.py,sha256=lLqS0lWLCaN8YPusi1AzPZqtyHr4dZTYJ9SC3rosd7Q,87453
|
|
11
11
|
pycontrails/core/flightplan.py,sha256=cpMZ6VCYbfwh3vnew2XgVEHnqBx1NzeAhrTVCvlbbss,7569
|
|
12
12
|
pycontrails/core/fuel.py,sha256=06YUDhvC8Rx6KbUXRB9qLTsJX2V7tLbzjwAfDH0R6l8,4472
|
|
13
13
|
pycontrails/core/interpolation.py,sha256=JY97IkHIgLRExxHN0B28R2CM5xXRYB1n4xT3gr4xPgU,26238
|
|
14
14
|
pycontrails/core/met.py,sha256=ihIP3kxat_OpgsUwVtol-esctzoaWtTCbHCFkMa3RB8,96625
|
|
15
15
|
pycontrails/core/met_var.py,sha256=JzB7UhBLQyU4TuKZqemhpBHA6Dbt89BPYO2sYBLMkL4,9504
|
|
16
|
-
pycontrails/core/models.py,sha256=
|
|
16
|
+
pycontrails/core/models.py,sha256=D5pnAxejpDbHfh_wszxTA4cq6dr2L0Sn3OBmSL4zSsA,40491
|
|
17
17
|
pycontrails/core/polygon.py,sha256=F403uzql_c47MPM2Qdmec6WwtFaXZyb48h-4gK-K4EU,18577
|
|
18
|
-
pycontrails/core/rgi_cython.cp312-win_amd64.pyd,sha256=
|
|
19
|
-
pycontrails/core/vector.py,sha256=
|
|
18
|
+
pycontrails/core/rgi_cython.cp312-win_amd64.pyd,sha256=GPWcLucRIhHAUx_vOk6cgFUW2Z7Al3o2lQ5b6nHhEbA,264704
|
|
19
|
+
pycontrails/core/vector.py,sha256=YdzU47DSilECjYAvqZb2M_Lgs7OP7JvrDceneq4cKCU,73762
|
|
20
20
|
pycontrails/datalib/__init__.py,sha256=Q2RrnjwtFzfsmJ2tEojDCzDMkd8R0MYw4mQz3YwUsqI,381
|
|
21
21
|
pycontrails/datalib/goes.py,sha256=UMxXXCiRL6SHY5_3cXs8GmG19eeKOOi3gKCimkyZSuc,27305
|
|
22
22
|
pycontrails/datalib/landsat.py,sha256=ptcI5d7Hk7KM-LUuZUaUxnQMwb_8z70ezTx1ErKfBhU,20212
|
|
@@ -58,11 +58,11 @@ pycontrails/models/apcemm/inputs.py,sha256=zHRSWVVlwYw6ms7PpC0p0I-xFsRDUVY9eDZ1g
|
|
|
58
58
|
pycontrails/models/apcemm/utils.py,sha256=6pKQbS5EAzTnI_edVtUvGrzM0xwNq1t9MBGgCRJtg_0,17531
|
|
59
59
|
pycontrails/models/apcemm/static/apcemm_yaml_template.yaml,sha256=A3H_FWVOtqkZhG91TWLdblMKaLWIcjRMsKqkfTN6mB4,6928
|
|
60
60
|
pycontrails/models/cocip/__init__.py,sha256=miDxSFxN9PzL_ieSJb3BYeHmbKqZwGicCz1scNB5eW0,991
|
|
61
|
-
pycontrails/models/cocip/cocip.py,sha256=
|
|
61
|
+
pycontrails/models/cocip/cocip.py,sha256=wnzAoBxKnmGhJcUDYeWuHCmnjRfDCJmE20UYpUPnkBI,99731
|
|
62
62
|
pycontrails/models/cocip/cocip_params.py,sha256=T4IseK6KtY4hG3BuGZBtFgM90HCYecUXsb_QVEK6uGo,11670
|
|
63
63
|
pycontrails/models/cocip/cocip_uncertainty.py,sha256=7W586BJEAY_wpSpfVdcdX-HpZG4twk3cMLhUR2ELTMA,12176
|
|
64
64
|
pycontrails/models/cocip/contrail_properties.py,sha256=u6SvucHC6VtF2kujfSVFTfv0263t5uYpNOUJZAroEzc,57111
|
|
65
|
-
pycontrails/models/cocip/output_formats.py,sha256=
|
|
65
|
+
pycontrails/models/cocip/output_formats.py,sha256=kJ2qexnl4yBZJpgxHGHvIjiH5CRWFx9quOiL-DEQvOw,85604
|
|
66
66
|
pycontrails/models/cocip/radiative_forcing.py,sha256=SYmQ8lL8gpWbf6he2C9mKSjODtytbFcdnMdBM-LtBKE,46206
|
|
67
67
|
pycontrails/models/cocip/radiative_heating.py,sha256=N7FTR20luERmokprdqMOl-d8-cTYZZ2ZSsTdxZnLHfs,19368
|
|
68
68
|
pycontrails/models/cocip/unterstrasser_wake_vortex.py,sha256=Ymz-uO9vVhLIFwT9yuF5g1g3hcT-XWdryLsebSBqoVU,14976
|
|
@@ -76,7 +76,7 @@ pycontrails/models/emissions/black_carbon.py,sha256=9DRqB487pH8Iq83FXggA5mPLYEAA
|
|
|
76
76
|
pycontrails/models/emissions/emissions.py,sha256=TqRPC15hC0gvL4-D4jDBgCOzOiSij6h0vvo7_GDkjI8,48917
|
|
77
77
|
pycontrails/models/emissions/ffm2.py,sha256=wtiWk00_Rby2_xJN-pMY6Ays0CZwFhvqDRFmIIkwunU,12368
|
|
78
78
|
pycontrails/models/emissions/static/default-engine-uids.csv,sha256=6e-0Fjbka1www4o2CNtw2pW-g0s_E7hZQ6vOaR84Q5Y,6456
|
|
79
|
-
pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv,sha256=
|
|
79
|
+
pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv,sha256=s-3_KGQyVoypXCHeQgsTDwdri-e3JVJn5SDxZo60m_s,128119
|
|
80
80
|
pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv,sha256=MwLLrcATd38KPddTpHpMGBrZuA4I7he-1B5otTp4ar8,77533
|
|
81
81
|
pycontrails/models/humidity_scaling/__init__.py,sha256=-xqDCJzKJx2nX6yl-gglHheQHWDhkvb8X7atbMJT2LA,1156
|
|
82
82
|
pycontrails/models/humidity_scaling/humidity_scaling.py,sha256=WEe-0rMTJFPzbsXuHVHLeUCHr552C73TlxIUhdKCOmA,37683
|
|
@@ -101,9 +101,9 @@ pycontrails/utils/iteration.py,sha256=En2YY4NiNwCNtAVO8HL6tv9byBGKs8MKSI7R8P-gZy
|
|
|
101
101
|
pycontrails/utils/json.py,sha256=xCv71CKVZNHk4MyoYC-hl7dXObXXbI7P8gcNCn3AUoU,6172
|
|
102
102
|
pycontrails/utils/temp.py,sha256=5XXqQoEfWjz1OrhoOBZD5vkkCFeuq9LpZkyhc38gIeY,1159
|
|
103
103
|
pycontrails/utils/types.py,sha256=gNG9cSZ3djW7jufg0h1fXM3kD24sBY6ENE6wsxY_Q6o,4937
|
|
104
|
-
pycontrails-0.52.
|
|
105
|
-
pycontrails-0.52.
|
|
106
|
-
pycontrails-0.52.
|
|
107
|
-
pycontrails-0.52.
|
|
108
|
-
pycontrails-0.52.
|
|
109
|
-
pycontrails-0.52.
|
|
104
|
+
pycontrails-0.52.2.dist-info/LICENSE,sha256=HVr8JnZfTaA-12BfKUQZi5hdrB3awOwLWs5X_ga5QzA,10353
|
|
105
|
+
pycontrails-0.52.2.dist-info/METADATA,sha256=rxyQhAnaf7KzOKQA_15Z0_H2zL2E_I7V5qbK7XcHASA,9488
|
|
106
|
+
pycontrails-0.52.2.dist-info/NOTICE,sha256=qYeNEp8OjDK5jSW3hTlr9LQRjZeEhXQm0zDei5UFaYs,1969
|
|
107
|
+
pycontrails-0.52.2.dist-info/WHEEL,sha256=KNRoynpGu-d6mheJI-zfvcGl1iN-y8BewbiCDXsF3cY,101
|
|
108
|
+
pycontrails-0.52.2.dist-info/top_level.txt,sha256=Z8J1R_AiBAyCVjNw6jYLdrA68PrQqTg0t3_Yek_IZ0Q,29
|
|
109
|
+
pycontrails-0.52.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|