pycontrails 0.53.0__cp313-cp313-manylinux_2_17_x86_64.manylinux2014_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/__init__.py +70 -0
- pycontrails/_version.py +16 -0
- pycontrails/core/__init__.py +30 -0
- pycontrails/core/aircraft_performance.py +641 -0
- pycontrails/core/airports.py +226 -0
- pycontrails/core/cache.py +881 -0
- pycontrails/core/coordinates.py +174 -0
- pycontrails/core/fleet.py +470 -0
- pycontrails/core/flight.py +2312 -0
- pycontrails/core/flightplan.py +220 -0
- pycontrails/core/fuel.py +140 -0
- pycontrails/core/interpolation.py +721 -0
- pycontrails/core/met.py +2833 -0
- pycontrails/core/met_var.py +307 -0
- pycontrails/core/models.py +1181 -0
- pycontrails/core/polygon.py +549 -0
- pycontrails/core/rgi_cython.cpython-313-x86_64-linux-gnu.so +0 -0
- pycontrails/core/vector.py +2191 -0
- pycontrails/datalib/__init__.py +12 -0
- pycontrails/datalib/_leo_utils/search.py +250 -0
- pycontrails/datalib/_leo_utils/static/bq_roi_query.sql +6 -0
- pycontrails/datalib/_leo_utils/vis.py +59 -0
- pycontrails/datalib/_met_utils/metsource.py +743 -0
- pycontrails/datalib/ecmwf/__init__.py +53 -0
- pycontrails/datalib/ecmwf/arco_era5.py +527 -0
- pycontrails/datalib/ecmwf/common.py +109 -0
- pycontrails/datalib/ecmwf/era5.py +538 -0
- pycontrails/datalib/ecmwf/era5_model_level.py +482 -0
- pycontrails/datalib/ecmwf/hres.py +782 -0
- pycontrails/datalib/ecmwf/hres_model_level.py +495 -0
- pycontrails/datalib/ecmwf/ifs.py +284 -0
- pycontrails/datalib/ecmwf/model_levels.py +79 -0
- pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
- pycontrails/datalib/ecmwf/variables.py +256 -0
- pycontrails/datalib/gfs/__init__.py +28 -0
- pycontrails/datalib/gfs/gfs.py +646 -0
- pycontrails/datalib/gfs/variables.py +100 -0
- pycontrails/datalib/goes.py +772 -0
- pycontrails/datalib/landsat.py +568 -0
- pycontrails/datalib/sentinel.py +512 -0
- pycontrails/datalib/spire.py +739 -0
- pycontrails/ext/bada.py +41 -0
- pycontrails/ext/cirium.py +14 -0
- pycontrails/ext/empirical_grid.py +140 -0
- pycontrails/ext/synthetic_flight.py +426 -0
- pycontrails/models/__init__.py +1 -0
- pycontrails/models/accf.py +406 -0
- pycontrails/models/apcemm/__init__.py +8 -0
- pycontrails/models/apcemm/apcemm.py +983 -0
- pycontrails/models/apcemm/inputs.py +226 -0
- pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
- pycontrails/models/apcemm/utils.py +437 -0
- pycontrails/models/cocip/__init__.py +29 -0
- pycontrails/models/cocip/cocip.py +2617 -0
- pycontrails/models/cocip/cocip_params.py +299 -0
- pycontrails/models/cocip/cocip_uncertainty.py +285 -0
- pycontrails/models/cocip/contrail_properties.py +1517 -0
- pycontrails/models/cocip/output_formats.py +2261 -0
- pycontrails/models/cocip/radiative_forcing.py +1262 -0
- pycontrails/models/cocip/radiative_heating.py +520 -0
- pycontrails/models/cocip/unterstrasser_wake_vortex.py +403 -0
- pycontrails/models/cocip/wake_vortex.py +396 -0
- pycontrails/models/cocip/wind_shear.py +120 -0
- pycontrails/models/cocipgrid/__init__.py +9 -0
- pycontrails/models/cocipgrid/cocip_grid.py +2573 -0
- pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
- pycontrails/models/dry_advection.py +486 -0
- pycontrails/models/emissions/__init__.py +21 -0
- pycontrails/models/emissions/black_carbon.py +594 -0
- pycontrails/models/emissions/emissions.py +1353 -0
- pycontrails/models/emissions/ffm2.py +336 -0
- pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
- pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
- pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
- pycontrails/models/humidity_scaling/__init__.py +37 -0
- pycontrails/models/humidity_scaling/humidity_scaling.py +1025 -0
- pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
- pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
- pycontrails/models/issr.py +210 -0
- pycontrails/models/pcc.py +327 -0
- pycontrails/models/pcr.py +154 -0
- pycontrails/models/ps_model/__init__.py +17 -0
- pycontrails/models/ps_model/ps_aircraft_params.py +376 -0
- pycontrails/models/ps_model/ps_grid.py +505 -0
- pycontrails/models/ps_model/ps_model.py +1017 -0
- pycontrails/models/ps_model/ps_operational_limits.py +540 -0
- pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv +68 -0
- pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv +103 -0
- pycontrails/models/sac.py +459 -0
- pycontrails/models/tau_cirrus.py +168 -0
- pycontrails/physics/__init__.py +1 -0
- pycontrails/physics/constants.py +116 -0
- pycontrails/physics/geo.py +989 -0
- pycontrails/physics/jet.py +837 -0
- pycontrails/physics/thermo.py +451 -0
- pycontrails/physics/units.py +472 -0
- pycontrails/py.typed +0 -0
- pycontrails/utils/__init__.py +1 -0
- pycontrails/utils/dependencies.py +66 -0
- pycontrails/utils/iteration.py +13 -0
- pycontrails/utils/json.py +188 -0
- pycontrails/utils/temp.py +50 -0
- pycontrails/utils/types.py +165 -0
- pycontrails-0.53.0.dist-info/LICENSE +178 -0
- pycontrails-0.53.0.dist-info/METADATA +181 -0
- pycontrails-0.53.0.dist-info/NOTICE +43 -0
- pycontrails-0.53.0.dist-info/RECORD +109 -0
- pycontrails-0.53.0.dist-info/WHEEL +6 -0
- pycontrails-0.53.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Default :class:`CocipGrid` parameters."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
|
|
7
|
+
from pycontrails.core.aircraft_performance import AircraftPerformanceGrid
|
|
8
|
+
from pycontrails.core.fuel import Fuel, JetA
|
|
9
|
+
from pycontrails.models.cocip.cocip_params import CocipParams
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclasses.dataclass
|
|
13
|
+
class CocipGridParams(CocipParams):
|
|
14
|
+
"""Default parameters for :class:`CocipGrid`."""
|
|
15
|
+
|
|
16
|
+
# ---------
|
|
17
|
+
# Algorithm
|
|
18
|
+
# ---------
|
|
19
|
+
|
|
20
|
+
#: Approximate size of a typical :class:`numpy.ndarray` used with in CoCiP calculations.
|
|
21
|
+
#: The 4-dimensional array defining the waypoint is raveled and split into
|
|
22
|
+
#: batches of this size.
|
|
23
|
+
#: A smaller number for this parameter will reduce memory footprint at the
|
|
24
|
+
#: expense of a longer compute time.
|
|
25
|
+
target_split_size: int = 100_000
|
|
26
|
+
|
|
27
|
+
#: Additional boost to target split size before SAC is computed. For typical meshes, only
|
|
28
|
+
#: 10% of waypoints will survive SAC and initial downwash filtering. Accordingly, this parameter
|
|
29
|
+
#: magnifies mesh split size before SAC is computed. See :attr:`target_split_size`.
|
|
30
|
+
target_split_size_pre_SAC_boost: float = 3.0
|
|
31
|
+
|
|
32
|
+
#: Display ``tqdm`` progress bar showing batch evaluation progress.
|
|
33
|
+
show_progress: bool = True
|
|
34
|
+
|
|
35
|
+
# ------------------
|
|
36
|
+
# Simulated Aircraft
|
|
37
|
+
# ------------------
|
|
38
|
+
|
|
39
|
+
#: Nominal segment length to place at each grid point [:math:`m`]. Round-off error
|
|
40
|
+
#: can be problematic with a small nominal segment length and a large
|
|
41
|
+
#: :attr:`dt_integration` parameter. On the other hand,
|
|
42
|
+
#: too large of a nominal segment length diminishes the "locality" of the grid point.
|
|
43
|
+
#:
|
|
44
|
+
#: .. versionadded:: 0.32.2
|
|
45
|
+
#:
|
|
46
|
+
#: EXPERIMENTAL: If None, run CoCiP in "segment-free"
|
|
47
|
+
#: mode. This mode does not include any terms involving segments (wind shear,
|
|
48
|
+
#: segment length, any derived terms). See :attr:`azimuth`
|
|
49
|
+
#: and :attr:`dsn_dz_factor` for more details.
|
|
50
|
+
segment_length: float | None = 1000.0
|
|
51
|
+
|
|
52
|
+
#: Fuel type
|
|
53
|
+
fuel: Fuel = dataclasses.field(default_factory=JetA)
|
|
54
|
+
|
|
55
|
+
#: ICAO code designating simulated aircraft type. Needed for the
|
|
56
|
+
#: :attr:`aircraft_performance` and :class:`Emissions` models.
|
|
57
|
+
aircraft_type: str = "B737"
|
|
58
|
+
|
|
59
|
+
#: Engine unique identification number for the ICAO Aircraft Emissions Databank (EDB)
|
|
60
|
+
#: If None, an assumed engine_uid is used in :class:`Emissions`.
|
|
61
|
+
engine_uid: str | None = None
|
|
62
|
+
|
|
63
|
+
#: Navigation bearing [:math:`\deg`] measured in clockwise direction from
|
|
64
|
+
#: true north, by default 0.0.
|
|
65
|
+
#:
|
|
66
|
+
#: .. versionadded:: 0.32.2
|
|
67
|
+
#:
|
|
68
|
+
#: EXPERIMENTAL: If None, run CoCiP in "segment-free"
|
|
69
|
+
#: mode. This mode does not include any terms involving segments (wind shear,
|
|
70
|
+
#: segment_length, any derived terms), unless :attr:`dsn_dz_factor`
|
|
71
|
+
#: is non-zero.
|
|
72
|
+
azimuth: float | None = 0.0
|
|
73
|
+
|
|
74
|
+
#: Experimental parameter used to approximate ``dsn_dz`` from ``ds_dz`` via
|
|
75
|
+
#: ``dsn_dz = ds_dz * dsn_dz_factor``.
|
|
76
|
+
#: A value of 0.0 disables any normal wind shear effects.
|
|
77
|
+
#: An initial unpublished experiment suggests that
|
|
78
|
+
#: ``dsn_dz_factor = 0.665`` adequately approximates the mean EF predictions
|
|
79
|
+
#: of :class:`CocipGrid` over all azimuths.
|
|
80
|
+
#:
|
|
81
|
+
#: .. versionadded:: 0.32.2
|
|
82
|
+
dsn_dz_factor: float = 0.0
|
|
83
|
+
|
|
84
|
+
#: --------------------
|
|
85
|
+
#: Aircraft Performance
|
|
86
|
+
#: --------------------
|
|
87
|
+
|
|
88
|
+
#: Aircraft wingspan, [:math:`m`]. If included in :attr:`CocipGrid.source`,
|
|
89
|
+
#: this parameter is unused. Otherwise, if this parameter is None, the
|
|
90
|
+
#: :attr:`aircraft_performance` model is used to estimate the wingspan.
|
|
91
|
+
wingspan: float | None = None
|
|
92
|
+
|
|
93
|
+
#: Nominal aircraft mass, [:math:`kg`]. If included in :attr:`CocipGrid.source`,
|
|
94
|
+
#: this parameter is unused. Otherwise, if this parameter is None, the
|
|
95
|
+
#: :attr:`aircraft_performance` model is used to estimate the aircraft mass.
|
|
96
|
+
aircraft_mass: float | None = None
|
|
97
|
+
|
|
98
|
+
#: Cruising true airspeed, [:math:`m \ s^{-1}`]. If included in :attr:`CocipGrid.source`,
|
|
99
|
+
#: this parameter is unused. Otherwise, if this parameter is None, the
|
|
100
|
+
#: :attr:`aircraft_performance` model is used to estimate the true airspeed.
|
|
101
|
+
true_airspeed: float | None = None
|
|
102
|
+
|
|
103
|
+
#: Nominal engine efficiency, [:math:`0 - 1`]. If included in :attr:`CocipGrid.source`,
|
|
104
|
+
#: this parameter is unused. Otherwise, if this parameter is None, the
|
|
105
|
+
#: :attr:`aircraft_performance` model is used to estimate the engine efficiency.
|
|
106
|
+
engine_efficiency: float | None = None
|
|
107
|
+
|
|
108
|
+
#: Nominal fuel flow, [:math:`kg \ s^{-1}`]. If included in :attr:`CocipGrid.source`,
|
|
109
|
+
#: this parameter is unused. Otherwise, if this parameter is None, the
|
|
110
|
+
#: :attr:`aircraft_performance` model is used to estimate the fuel flow.
|
|
111
|
+
fuel_flow: float | None = None
|
|
112
|
+
|
|
113
|
+
#: Aircraft performance model. Required unless ``source`` or ``params``
|
|
114
|
+
#: provide all of the following variables:
|
|
115
|
+
#:
|
|
116
|
+
#: - wingspan
|
|
117
|
+
#: - true_airspeed (or mach_number)
|
|
118
|
+
#: - fuel_flow
|
|
119
|
+
#: - engine_efficiency
|
|
120
|
+
#: - aircraft_mass
|
|
121
|
+
#:
|
|
122
|
+
#: If None and :attr:`CocipGrid.source` or :class:`CocipGridParams` do not provide
|
|
123
|
+
#: the above variables, a ValueError is raised. See :class:`PSGrid` for an open-source
|
|
124
|
+
#: implementation of a :class:`AircraftPerformanceGrid` model.
|
|
125
|
+
aircraft_performance: AircraftPerformanceGrid | None = None
|
|
126
|
+
|
|
127
|
+
# ------------
|
|
128
|
+
# Model output
|
|
129
|
+
# ------------
|
|
130
|
+
|
|
131
|
+
#: Attach additional formation specific data to the output. If True, attach
|
|
132
|
+
#: all possible formation data. See :mod:`pycontrails.models.cocipgrid.cocip_grid`
|
|
133
|
+
#: for a list of supported formation data.
|
|
134
|
+
verbose_outputs_formation: bool | set[str] = False
|
|
135
|
+
|
|
136
|
+
#: Attach contrail evolution data to :attr:`CocipGrid.contrail_list`. Requires
|
|
137
|
+
#: substantial memory overhead.
|
|
138
|
+
verbose_outputs_evolution: bool = False
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
"""Simulate dry advection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
from typing import Any, NoReturn, overload
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from pycontrails.core import models
|
|
11
|
+
from pycontrails.core.flight import Flight
|
|
12
|
+
from pycontrails.core.met import MetDataset
|
|
13
|
+
from pycontrails.core.met_var import AirTemperature, EastwardWind, NorthwardWind, VerticalVelocity
|
|
14
|
+
from pycontrails.core.vector import GeoVectorDataset
|
|
15
|
+
from pycontrails.models.cocip import contrail_properties, wind_shear
|
|
16
|
+
from pycontrails.physics import geo, thermo
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclasses.dataclass
|
|
20
|
+
class DryAdvectionParams(models.ModelParams):
|
|
21
|
+
"""Parameters for the :class:`DryAdvection` model."""
|
|
22
|
+
|
|
23
|
+
#: Apply Euler's method with a fixed step size of ``dt_integration``. Advected waypoints
|
|
24
|
+
#: are interpolated against met data once each ``dt_integration``.
|
|
25
|
+
dt_integration: np.timedelta64 = np.timedelta64(30, "m")
|
|
26
|
+
|
|
27
|
+
#: Max age of plume evolution.
|
|
28
|
+
max_age: np.timedelta64 = np.timedelta64(20, "h")
|
|
29
|
+
|
|
30
|
+
#: Rate of change of pressure due to sedimentation [:math:`Pa/s`]
|
|
31
|
+
sedimentation_rate: float = 0.0
|
|
32
|
+
|
|
33
|
+
#: Difference in altitude between top and bottom layer for stratification calculations,
|
|
34
|
+
#: [:math:`m`]. Used to approximate derivative of "lagrangian_tendency_of_air_pressure"
|
|
35
|
+
#: (upward component of air velocity) with respect to altitude.
|
|
36
|
+
dz_m: float = 200.0
|
|
37
|
+
|
|
38
|
+
#: Upper bound for evolved plume depth, constraining it to realistic values.
|
|
39
|
+
max_depth: float | None = 1500.0
|
|
40
|
+
|
|
41
|
+
#: Initial plume width, [:math:`m`]. Overridden by "width" key on :attr:`source`.
|
|
42
|
+
# If None, only pointwise advection is simulated without wind shear effects.
|
|
43
|
+
width: float | None = 100.0
|
|
44
|
+
|
|
45
|
+
#: Initial plume depth, [:math:`m`]. Overridden by "depth" key on :attr:`source`.
|
|
46
|
+
# If None, only pointwise advection is simulated without wind shear effects.
|
|
47
|
+
depth: float | None = 100.0
|
|
48
|
+
|
|
49
|
+
#: Initial plume direction, [:math:`m`]. Overridden by "azimuth" key on :attr:`source`.
|
|
50
|
+
# If None, only pointwise advection is simulated without wind shear effects.
|
|
51
|
+
azimuth: float | None = 0.0
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class DryAdvection(models.Model):
|
|
55
|
+
"""Simulate "dry advection" of an emissions plume with an elliptical cross section.
|
|
56
|
+
|
|
57
|
+
The model simulates both horizontal and vertical advection of a weightless
|
|
58
|
+
plume without any sedimentation effects. Unlike :class:`Cocip`, humidity is
|
|
59
|
+
not considered, and radiative forcing is not simulated. The model is
|
|
60
|
+
therefore useful simulating plume advection and dispersion itself.
|
|
61
|
+
|
|
62
|
+
.. versionadded:: 0.46.0
|
|
63
|
+
|
|
64
|
+
This model has two distinct modes of operation:
|
|
65
|
+
|
|
66
|
+
- **Pointwise only**: If ``azimuth`` is None, then the model will only advect
|
|
67
|
+
points without any wind shear effects. This mode is useful for testing
|
|
68
|
+
the advection algorithm itself, and for simulating the evolution of
|
|
69
|
+
a single point.
|
|
70
|
+
- **Wind shear effects**: If ``azimuth`` is not None, then the model will
|
|
71
|
+
advect points with wind shear effects. At each time step, the model
|
|
72
|
+
will evolve the plume geometry according to diffusion and wind shear
|
|
73
|
+
effects. This mode is also used in :class:`CocipGrid` and :class:`Cocip`.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
met : MetDataset
|
|
78
|
+
Meteorological data.
|
|
79
|
+
params : dict[str, Any]
|
|
80
|
+
Model parameters. See :class:`DryAdvectionParams` for details.
|
|
81
|
+
**kwargs : Any
|
|
82
|
+
Additional parameters passed as keyword arguments.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
name = "dry_advection"
|
|
86
|
+
long_name = "Emission plume advection without sedimentation"
|
|
87
|
+
met_variables = AirTemperature, EastwardWind, NorthwardWind, VerticalVelocity
|
|
88
|
+
default_params = DryAdvectionParams
|
|
89
|
+
|
|
90
|
+
met: MetDataset
|
|
91
|
+
met_required = True
|
|
92
|
+
source: GeoVectorDataset
|
|
93
|
+
|
|
94
|
+
@overload
|
|
95
|
+
def eval(self, source: Flight, **params: Any) -> Flight: ...
|
|
96
|
+
|
|
97
|
+
@overload
|
|
98
|
+
def eval(self, source: GeoVectorDataset, **params: Any) -> GeoVectorDataset: ...
|
|
99
|
+
|
|
100
|
+
@overload
|
|
101
|
+
def eval(self, source: None = ..., **params: Any) -> NoReturn: ...
|
|
102
|
+
|
|
103
|
+
def eval(self, source: GeoVectorDataset | None = None, **params: Any) -> GeoVectorDataset:
|
|
104
|
+
"""Simulate dry advection (no sedimentation) of arbitrary points.
|
|
105
|
+
|
|
106
|
+
Like :class:`Cocip`, this model adds a "waypoint" column to the :attr:`source`.
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
source : GeoVectorDataset
|
|
111
|
+
Arbitrary points to advect.
|
|
112
|
+
params : Any
|
|
113
|
+
Overwrite model parameters defined in ``__init__``.
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
GeoVectorDataset
|
|
118
|
+
Advected points.
|
|
119
|
+
"""
|
|
120
|
+
self.update_params(params)
|
|
121
|
+
self.set_source(source)
|
|
122
|
+
self.source = self.require_source_type(GeoVectorDataset)
|
|
123
|
+
|
|
124
|
+
self._prepare_source()
|
|
125
|
+
|
|
126
|
+
interp_kwargs = self.interp_kwargs
|
|
127
|
+
|
|
128
|
+
dt_integration = self.params["dt_integration"]
|
|
129
|
+
max_age = self.params["max_age"]
|
|
130
|
+
sedimentation_rate = self.params["sedimentation_rate"]
|
|
131
|
+
dz_m = self.params["dz_m"]
|
|
132
|
+
max_depth = self.params["max_depth"]
|
|
133
|
+
|
|
134
|
+
source_time = self.source["time"]
|
|
135
|
+
t0 = source_time.min()
|
|
136
|
+
t1 = source_time.max()
|
|
137
|
+
timesteps = np.arange(t0 + dt_integration, t1 + dt_integration + max_age, dt_integration)
|
|
138
|
+
|
|
139
|
+
vector = None
|
|
140
|
+
|
|
141
|
+
evolved = []
|
|
142
|
+
for t in timesteps:
|
|
143
|
+
filt = (source_time < t) & (source_time >= t - dt_integration)
|
|
144
|
+
vector = self.source.filter(filt) + vector
|
|
145
|
+
vector = _evolve_one_step(
|
|
146
|
+
self.met,
|
|
147
|
+
vector,
|
|
148
|
+
t,
|
|
149
|
+
sedimentation_rate=sedimentation_rate,
|
|
150
|
+
dz_m=dz_m,
|
|
151
|
+
max_depth=max_depth,
|
|
152
|
+
**interp_kwargs,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
filt = (vector["age"] <= max_age) & vector.coords_intersect_met(self.met)
|
|
156
|
+
vector = vector.filter(filt)
|
|
157
|
+
|
|
158
|
+
evolved.append(vector)
|
|
159
|
+
if not vector and np.all(source_time < t):
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
return GeoVectorDataset.sum(evolved, fill_value=np.nan)
|
|
163
|
+
|
|
164
|
+
def _prepare_source(self) -> None:
|
|
165
|
+
r"""Prepare :attr:`source` vector for advection by wind-shear-derived variables.
|
|
166
|
+
|
|
167
|
+
This method adds the following variables to :attr:`source` if the `"azimuth"`
|
|
168
|
+
parameter is not None:
|
|
169
|
+
|
|
170
|
+
- ``age``: Age of plume.
|
|
171
|
+
- ``azimuth``: Initial plume direction, measured in clockwise direction from
|
|
172
|
+
true north, [:math:`\deg`].
|
|
173
|
+
- ``width``: Initial plume width, [:math:`m`].
|
|
174
|
+
- ``depth``: Initial plume depth, [:math:`m`].
|
|
175
|
+
- ``sigma_yz``: All zeros for cross-term term in covariance matrix of plume.
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
self.source.setdefault("level", self.source.level)
|
|
179
|
+
|
|
180
|
+
columns: tuple[str, ...] = ("longitude", "latitude", "level", "time")
|
|
181
|
+
if "azimuth" in self.source:
|
|
182
|
+
columns += ("azimuth",)
|
|
183
|
+
self.source = GeoVectorDataset(self.source.select(columns, copy=False))
|
|
184
|
+
|
|
185
|
+
# Get waypoint index if not already set
|
|
186
|
+
self.source.setdefault("waypoint", np.arange(self.source.size))
|
|
187
|
+
|
|
188
|
+
self.source["age"] = np.full(self.source.size, np.timedelta64(0, "ns"))
|
|
189
|
+
|
|
190
|
+
if "azimuth" not in self.source:
|
|
191
|
+
if isinstance(self.source, Flight):
|
|
192
|
+
pointwise_only = False
|
|
193
|
+
self.source["azimuth"] = self.source.segment_azimuth()
|
|
194
|
+
else:
|
|
195
|
+
try:
|
|
196
|
+
self.source.broadcast_attrs("azimuth")
|
|
197
|
+
except KeyError:
|
|
198
|
+
if (azimuth := self.params["azimuth"]) is not None:
|
|
199
|
+
pointwise_only = False
|
|
200
|
+
self.source["azimuth"] = np.full_like(self.source["longitude"], azimuth)
|
|
201
|
+
else:
|
|
202
|
+
pointwise_only = True
|
|
203
|
+
else:
|
|
204
|
+
pointwise_only = False
|
|
205
|
+
else:
|
|
206
|
+
pointwise_only = False
|
|
207
|
+
|
|
208
|
+
for key in ("width", "depth"):
|
|
209
|
+
if key in self.source:
|
|
210
|
+
continue
|
|
211
|
+
if key in self.source.attrs:
|
|
212
|
+
self.source.broadcast_attrs(key)
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
val = self.params[key]
|
|
216
|
+
if val is None and not pointwise_only:
|
|
217
|
+
raise ValueError(f"If '{key}' is None, then 'azimuth' must also be None.")
|
|
218
|
+
|
|
219
|
+
if val is not None and pointwise_only:
|
|
220
|
+
raise ValueError(f"Cannot specify '{key}' without specifying 'azimuth'.")
|
|
221
|
+
|
|
222
|
+
if not pointwise_only:
|
|
223
|
+
self.source[key] = np.full_like(self.source["longitude"], val)
|
|
224
|
+
|
|
225
|
+
if pointwise_only:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
self.source["sigma_yz"] = np.zeros_like(self.source["longitude"])
|
|
229
|
+
width = self.source["width"]
|
|
230
|
+
depth = self.source["depth"]
|
|
231
|
+
self.source["area_eff"] = contrail_properties.plume_effective_cross_sectional_area(
|
|
232
|
+
width, depth, sigma_yz=0.0
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _perform_interp_for_step(
|
|
237
|
+
met: MetDataset,
|
|
238
|
+
vector: GeoVectorDataset,
|
|
239
|
+
dz_m: float,
|
|
240
|
+
**interp_kwargs: Any,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Perform all interpolation required for one step of advection."""
|
|
243
|
+
|
|
244
|
+
vector.setdefault("level", vector.level)
|
|
245
|
+
air_pressure = vector.setdefault("air_pressure", vector.air_pressure)
|
|
246
|
+
|
|
247
|
+
air_temperature = models.interpolate_met(met, vector, "air_temperature", **interp_kwargs)
|
|
248
|
+
models.interpolate_met(met, vector, "northward_wind", "v_wind", **interp_kwargs)
|
|
249
|
+
models.interpolate_met(met, vector, "eastward_wind", "u_wind", **interp_kwargs)
|
|
250
|
+
models.interpolate_met(
|
|
251
|
+
met,
|
|
252
|
+
vector,
|
|
253
|
+
"lagrangian_tendency_of_air_pressure",
|
|
254
|
+
"vertical_velocity",
|
|
255
|
+
**interp_kwargs,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
az = vector.get("azimuth")
|
|
259
|
+
if az is None:
|
|
260
|
+
# Early exit for pointwise only simulation
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
air_pressure_lower = thermo.pressure_dz(air_temperature, air_pressure, dz_m)
|
|
264
|
+
vector["air_pressure_lower"] = air_pressure_lower
|
|
265
|
+
level_lower = air_pressure_lower / 100.0
|
|
266
|
+
|
|
267
|
+
models.interpolate_met(
|
|
268
|
+
met,
|
|
269
|
+
vector,
|
|
270
|
+
"eastward_wind",
|
|
271
|
+
"u_wind_lower",
|
|
272
|
+
level=level_lower,
|
|
273
|
+
**interp_kwargs,
|
|
274
|
+
)
|
|
275
|
+
models.interpolate_met(
|
|
276
|
+
met,
|
|
277
|
+
vector,
|
|
278
|
+
"northward_wind",
|
|
279
|
+
"v_wind_lower",
|
|
280
|
+
level=level_lower,
|
|
281
|
+
**interp_kwargs,
|
|
282
|
+
)
|
|
283
|
+
models.interpolate_met(
|
|
284
|
+
met,
|
|
285
|
+
vector,
|
|
286
|
+
"air_temperature",
|
|
287
|
+
"air_temperature_lower",
|
|
288
|
+
level=level_lower,
|
|
289
|
+
**interp_kwargs,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
lons = vector["longitude"]
|
|
293
|
+
lats = vector["latitude"]
|
|
294
|
+
dist = 1000.0
|
|
295
|
+
|
|
296
|
+
# These should probably not be included in model input ... so
|
|
297
|
+
# we'll get a warning if they get overwritten
|
|
298
|
+
longitude_head, latitude_head = geo.forward_azimuth(lons=lons, lats=lats, az=az, dist=dist)
|
|
299
|
+
longitude_tail, latitude_tail = geo.forward_azimuth(lons=lons, lats=lats, az=az, dist=-dist)
|
|
300
|
+
vector["longitude_head"] = longitude_head
|
|
301
|
+
vector["latitude_head"] = latitude_head
|
|
302
|
+
vector["longitude_tail"] = longitude_tail
|
|
303
|
+
vector["latitude_tail"] = latitude_tail
|
|
304
|
+
|
|
305
|
+
for met_key in ("eastward_wind", "northward_wind"):
|
|
306
|
+
vector_key = f"{met_key}_head"
|
|
307
|
+
models.interpolate_met(
|
|
308
|
+
met,
|
|
309
|
+
vector,
|
|
310
|
+
met_key,
|
|
311
|
+
vector_key,
|
|
312
|
+
**interp_kwargs,
|
|
313
|
+
longitude=longitude_head,
|
|
314
|
+
latitude=latitude_head,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
vector_key = f"{met_key}_tail"
|
|
318
|
+
models.interpolate_met(
|
|
319
|
+
met,
|
|
320
|
+
vector,
|
|
321
|
+
met_key,
|
|
322
|
+
vector_key,
|
|
323
|
+
**interp_kwargs,
|
|
324
|
+
longitude=longitude_tail,
|
|
325
|
+
latitude=latitude_tail,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _calc_geometry(
|
|
330
|
+
vector: GeoVectorDataset,
|
|
331
|
+
dz_m: float,
|
|
332
|
+
dt: np.timedelta64,
|
|
333
|
+
max_depth: float | None,
|
|
334
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
335
|
+
"""Calculate wind-shear-derived geometry of evolved plume."""
|
|
336
|
+
|
|
337
|
+
u_wind = vector["u_wind"]
|
|
338
|
+
v_wind = vector["v_wind"]
|
|
339
|
+
u_wind_lower = vector.data.pop("u_wind_lower")
|
|
340
|
+
v_wind_lower = vector.data.pop("v_wind_lower")
|
|
341
|
+
|
|
342
|
+
air_temperature = vector["air_temperature"]
|
|
343
|
+
air_temperature_lower = vector.data.pop("air_temperature_lower")
|
|
344
|
+
air_pressure = vector["air_pressure"]
|
|
345
|
+
air_pressure_lower = vector.data.pop("air_pressure_lower")
|
|
346
|
+
|
|
347
|
+
ds_dz = wind_shear.wind_shear(u_wind, u_wind_lower, v_wind, v_wind_lower, dz_m)
|
|
348
|
+
|
|
349
|
+
azimuth = vector["azimuth"]
|
|
350
|
+
latitude = vector["latitude"]
|
|
351
|
+
cos_a, sin_a = geo.azimuth_to_direction(azimuth, latitude)
|
|
352
|
+
|
|
353
|
+
width = vector["width"]
|
|
354
|
+
depth = vector["depth"]
|
|
355
|
+
sigma_yz = vector["sigma_yz"]
|
|
356
|
+
area_eff = vector["area_eff"]
|
|
357
|
+
|
|
358
|
+
dsn_dz = wind_shear.wind_shear_normal(
|
|
359
|
+
u_wind_top=u_wind,
|
|
360
|
+
u_wind_btm=u_wind_lower,
|
|
361
|
+
v_wind_top=v_wind,
|
|
362
|
+
v_wind_btm=v_wind_lower,
|
|
363
|
+
cos_a=cos_a,
|
|
364
|
+
sin_a=sin_a,
|
|
365
|
+
dz=dz_m,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
dT_dz = thermo.T_potential_gradient(
|
|
369
|
+
air_temperature,
|
|
370
|
+
air_pressure,
|
|
371
|
+
air_temperature_lower,
|
|
372
|
+
air_pressure_lower,
|
|
373
|
+
dz_m,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
depth_eff = contrail_properties.plume_effective_depth(width, area_eff)
|
|
377
|
+
|
|
378
|
+
diffuse_h = contrail_properties.horizontal_diffusivity(ds_dz, depth)
|
|
379
|
+
diffuse_v = contrail_properties.vertical_diffusivity(
|
|
380
|
+
air_pressure,
|
|
381
|
+
air_temperature,
|
|
382
|
+
dT_dz,
|
|
383
|
+
depth_eff,
|
|
384
|
+
terminal_fall_speed=0.0,
|
|
385
|
+
sedimentation_impact_factor=0.0,
|
|
386
|
+
eff_heat_rate=None,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
sigma_yy_2, sigma_zz_2, sigma_yz_2 = contrail_properties.plume_temporal_evolution(
|
|
390
|
+
width,
|
|
391
|
+
depth,
|
|
392
|
+
sigma_yz,
|
|
393
|
+
dsn_dz,
|
|
394
|
+
diffuse_h,
|
|
395
|
+
diffuse_v,
|
|
396
|
+
seg_ratio=1.0,
|
|
397
|
+
dt=dt,
|
|
398
|
+
max_depth=max_depth,
|
|
399
|
+
)
|
|
400
|
+
width_2, depth_2 = contrail_properties.new_contrail_dimensions(sigma_yy_2, sigma_zz_2)
|
|
401
|
+
area_eff_2 = contrail_properties.plume_effective_cross_sectional_area(
|
|
402
|
+
width_2, depth_2, sigma_yz_2
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
longitude_head = vector.data.pop("longitude_head")
|
|
406
|
+
latitude_head = vector.data.pop("latitude_head")
|
|
407
|
+
longitude_tail = vector.data.pop("longitude_tail")
|
|
408
|
+
latitude_tail = vector.data.pop("latitude_tail")
|
|
409
|
+
u_wind_head = vector.data.pop("eastward_wind_head")
|
|
410
|
+
v_wind_head = vector.data.pop("northward_wind_head")
|
|
411
|
+
u_wind_tail = vector.data.pop("eastward_wind_tail")
|
|
412
|
+
v_wind_tail = vector.data.pop("northward_wind_tail")
|
|
413
|
+
|
|
414
|
+
longitude_head_t2 = geo.advect_longitude(
|
|
415
|
+
longitude=longitude_head, latitude=latitude_head, u_wind=u_wind_head, dt=dt
|
|
416
|
+
)
|
|
417
|
+
latitude_head_t2 = geo.advect_latitude(latitude=latitude_head, v_wind=v_wind_head, dt=dt)
|
|
418
|
+
|
|
419
|
+
longitude_tail_t2 = geo.advect_longitude(
|
|
420
|
+
longitude=longitude_tail, latitude=latitude_tail, u_wind=u_wind_tail, dt=dt
|
|
421
|
+
)
|
|
422
|
+
latitude_tail_t2 = geo.advect_latitude(latitude=latitude_tail, v_wind=v_wind_tail, dt=dt)
|
|
423
|
+
|
|
424
|
+
azimuth_2 = geo.azimuth(
|
|
425
|
+
lons0=longitude_tail_t2,
|
|
426
|
+
lats0=latitude_tail_t2,
|
|
427
|
+
lons1=longitude_head_t2,
|
|
428
|
+
lats1=latitude_head_t2,
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return azimuth_2, width_2, depth_2, sigma_yz_2, area_eff_2
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def _evolve_one_step(
|
|
435
|
+
met: MetDataset,
|
|
436
|
+
vector: GeoVectorDataset,
|
|
437
|
+
t: np.datetime64,
|
|
438
|
+
*,
|
|
439
|
+
sedimentation_rate: float,
|
|
440
|
+
dz_m: float,
|
|
441
|
+
max_depth: float | None,
|
|
442
|
+
**interp_kwargs: Any,
|
|
443
|
+
) -> GeoVectorDataset:
|
|
444
|
+
"""Evolve plume geometry by one step."""
|
|
445
|
+
|
|
446
|
+
_perform_interp_for_step(met, vector, dz_m, **interp_kwargs)
|
|
447
|
+
u_wind = vector["u_wind"]
|
|
448
|
+
v_wind = vector["v_wind"]
|
|
449
|
+
vertical_velocity = vector["vertical_velocity"] + sedimentation_rate
|
|
450
|
+
|
|
451
|
+
latitude = vector["latitude"]
|
|
452
|
+
longitude = vector["longitude"]
|
|
453
|
+
|
|
454
|
+
dt = t - vector["time"]
|
|
455
|
+
longitude_2 = geo.advect_longitude(longitude, latitude, u_wind, dt) # type: ignore[arg-type]
|
|
456
|
+
latitude_2 = geo.advect_latitude(latitude, v_wind, dt) # type: ignore[arg-type]
|
|
457
|
+
level_2 = geo.advect_level(
|
|
458
|
+
vector.level, vertical_velocity, 0.0, 0.0, dt # type: ignore[arg-type]
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
out = GeoVectorDataset(
|
|
462
|
+
longitude=longitude_2,
|
|
463
|
+
latitude=latitude_2,
|
|
464
|
+
level=level_2,
|
|
465
|
+
time=np.full(longitude_2.shape, t),
|
|
466
|
+
copy=False,
|
|
467
|
+
)
|
|
468
|
+
out["age"] = vector["age"] + dt
|
|
469
|
+
out["waypoint"] = vector["waypoint"]
|
|
470
|
+
|
|
471
|
+
azimuth = vector.get("azimuth")
|
|
472
|
+
if azimuth is None:
|
|
473
|
+
# Early exit for "pointwise only" simulation
|
|
474
|
+
return out
|
|
475
|
+
|
|
476
|
+
# Attach wind-shear-derived geometry to output vector
|
|
477
|
+
azimuth_2, width_2, depth_2, sigma_yz_2, area_eff_2 = _calc_geometry(
|
|
478
|
+
vector, dz_m, dt, max_depth # type: ignore[arg-type]
|
|
479
|
+
)
|
|
480
|
+
out["azimuth"] = azimuth_2
|
|
481
|
+
out["width"] = width_2
|
|
482
|
+
out["depth"] = depth_2
|
|
483
|
+
out["sigma_yz"] = sigma_yz_2
|
|
484
|
+
out["area_eff"] = area_eff_2
|
|
485
|
+
|
|
486
|
+
return out
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Aircraft Emissions modeling."""
|
|
2
|
+
|
|
3
|
+
from pycontrails.models.emissions.emissions import (
|
|
4
|
+
EDBGaseous,
|
|
5
|
+
EDBnvpm,
|
|
6
|
+
Emissions,
|
|
7
|
+
EmissionsParams,
|
|
8
|
+
load_default_aircraft_engine_mapping,
|
|
9
|
+
load_engine_nvpm_profile_from_edb,
|
|
10
|
+
load_engine_params_from_edb,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Emissions",
|
|
15
|
+
"EmissionsParams",
|
|
16
|
+
"EDBGaseous",
|
|
17
|
+
"EDBnvpm",
|
|
18
|
+
"load_default_aircraft_engine_mapping",
|
|
19
|
+
"load_engine_nvpm_profile_from_edb",
|
|
20
|
+
"load_engine_params_from_edb",
|
|
21
|
+
]
|