pycontrails 0.58.0__cp314-cp314-macosx_10_13_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pycontrails might be problematic. Click here for more details.
- pycontrails/__init__.py +70 -0
- pycontrails/_version.py +34 -0
- pycontrails/core/__init__.py +30 -0
- pycontrails/core/aircraft_performance.py +679 -0
- pycontrails/core/airports.py +228 -0
- pycontrails/core/cache.py +889 -0
- pycontrails/core/coordinates.py +174 -0
- pycontrails/core/fleet.py +483 -0
- pycontrails/core/flight.py +2185 -0
- pycontrails/core/flightplan.py +228 -0
- pycontrails/core/fuel.py +140 -0
- pycontrails/core/interpolation.py +702 -0
- pycontrails/core/met.py +2931 -0
- pycontrails/core/met_var.py +387 -0
- pycontrails/core/models.py +1321 -0
- pycontrails/core/polygon.py +549 -0
- pycontrails/core/rgi_cython.cpython-314-darwin.so +0 -0
- pycontrails/core/vector.py +2249 -0
- pycontrails/datalib/__init__.py +12 -0
- pycontrails/datalib/_met_utils/metsource.py +746 -0
- pycontrails/datalib/ecmwf/__init__.py +73 -0
- pycontrails/datalib/ecmwf/arco_era5.py +345 -0
- pycontrails/datalib/ecmwf/common.py +114 -0
- pycontrails/datalib/ecmwf/era5.py +554 -0
- pycontrails/datalib/ecmwf/era5_model_level.py +490 -0
- pycontrails/datalib/ecmwf/hres.py +804 -0
- pycontrails/datalib/ecmwf/hres_model_level.py +466 -0
- pycontrails/datalib/ecmwf/ifs.py +287 -0
- pycontrails/datalib/ecmwf/model_levels.py +435 -0
- pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
- pycontrails/datalib/ecmwf/variables.py +268 -0
- pycontrails/datalib/geo_utils.py +261 -0
- pycontrails/datalib/gfs/__init__.py +28 -0
- pycontrails/datalib/gfs/gfs.py +656 -0
- pycontrails/datalib/gfs/variables.py +104 -0
- pycontrails/datalib/goes.py +757 -0
- pycontrails/datalib/himawari/__init__.py +27 -0
- pycontrails/datalib/himawari/header_struct.py +266 -0
- pycontrails/datalib/himawari/himawari.py +667 -0
- pycontrails/datalib/landsat.py +589 -0
- pycontrails/datalib/leo_utils/__init__.py +5 -0
- pycontrails/datalib/leo_utils/correction.py +266 -0
- pycontrails/datalib/leo_utils/landsat_metadata.py +300 -0
- pycontrails/datalib/leo_utils/search.py +250 -0
- pycontrails/datalib/leo_utils/sentinel_metadata.py +748 -0
- pycontrails/datalib/leo_utils/static/bq_roi_query.sql +6 -0
- pycontrails/datalib/leo_utils/vis.py +59 -0
- pycontrails/datalib/sentinel.py +650 -0
- pycontrails/datalib/spire/__init__.py +5 -0
- pycontrails/datalib/spire/exceptions.py +62 -0
- pycontrails/datalib/spire/spire.py +604 -0
- pycontrails/ext/bada.py +42 -0
- pycontrails/ext/cirium.py +14 -0
- pycontrails/ext/empirical_grid.py +140 -0
- pycontrails/ext/synthetic_flight.py +431 -0
- pycontrails/models/__init__.py +1 -0
- pycontrails/models/accf.py +425 -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 +2742 -0
- pycontrails/models/cocip/cocip_params.py +305 -0
- pycontrails/models/cocip/cocip_uncertainty.py +291 -0
- pycontrails/models/cocip/contrail_properties.py +1530 -0
- pycontrails/models/cocip/output_formats.py +2270 -0
- pycontrails/models/cocip/radiative_forcing.py +1260 -0
- pycontrails/models/cocip/radiative_heating.py +520 -0
- pycontrails/models/cocip/unterstrasser_wake_vortex.py +508 -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 +2552 -0
- pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
- pycontrails/models/dry_advection.py +602 -0
- pycontrails/models/emissions/__init__.py +21 -0
- pycontrails/models/emissions/black_carbon.py +599 -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/extended_k15.py +1327 -0
- pycontrails/models/humidity_scaling/__init__.py +37 -0
- pycontrails/models/humidity_scaling/humidity_scaling.py +1075 -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 +326 -0
- pycontrails/models/pcr.py +154 -0
- pycontrails/models/ps_model/__init__.py +18 -0
- pycontrails/models/ps_model/ps_aircraft_params.py +381 -0
- pycontrails/models/ps_model/ps_grid.py +701 -0
- pycontrails/models/ps_model/ps_model.py +1000 -0
- pycontrails/models/ps_model/ps_operational_limits.py +525 -0
- pycontrails/models/ps_model/static/ps-aircraft-params-20250328.csv +69 -0
- pycontrails/models/ps_model/static/ps-synonym-list-20250328.csv +104 -0
- pycontrails/models/sac.py +442 -0
- pycontrails/models/tau_cirrus.py +183 -0
- pycontrails/physics/__init__.py +1 -0
- pycontrails/physics/constants.py +117 -0
- pycontrails/physics/geo.py +1138 -0
- pycontrails/physics/jet.py +968 -0
- pycontrails/physics/static/iata-cargo-load-factors-20250221.csv +74 -0
- pycontrails/physics/static/iata-passenger-load-factors-20250221.csv +74 -0
- pycontrails/physics/thermo.py +551 -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 +187 -0
- pycontrails/utils/temp.py +50 -0
- pycontrails/utils/types.py +163 -0
- pycontrails-0.58.0.dist-info/METADATA +180 -0
- pycontrails-0.58.0.dist-info/RECORD +122 -0
- pycontrails-0.58.0.dist-info/WHEEL +6 -0
- pycontrails-0.58.0.dist-info/licenses/LICENSE +178 -0
- pycontrails-0.58.0.dist-info/licenses/NOTICE +43 -0
- pycontrails-0.58.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""ATC Flight Plan Parser."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def to_atc_plan(plan: dict[str, Any]) -> str:
|
|
8
|
+
"""Write dictionary from :func:`parse_atc_plan` as ATC flight plan string.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
plan: dict[str, Any]
|
|
13
|
+
Dictionary representation of ATC flight plan returned from :func:`parse_atc_plan`.
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
str
|
|
18
|
+
ATC flight plan string conforming to ICAO Doc 4444-ATM/501
|
|
19
|
+
|
|
20
|
+
See Also
|
|
21
|
+
--------
|
|
22
|
+
:func:`parse_atc_plan`
|
|
23
|
+
"""
|
|
24
|
+
ret = f"(FPL-{plan['callsign']}-{plan['flight_rules']}"
|
|
25
|
+
ret += f"{plan['type_of_flight']}\n"
|
|
26
|
+
ret += "-"
|
|
27
|
+
if "number_aircraft" in plan and plan["number_aircraft"] <= 10:
|
|
28
|
+
ret += plan["number_aircraft"]
|
|
29
|
+
ret += f"{plan['type_of_aircraft']}/{plan['wake_category']}-"
|
|
30
|
+
ret += f"{plan['equipment']}/{plan['transponder']}\n"
|
|
31
|
+
ret += f"-{plan['departure_icao']}{plan['time']}\n"
|
|
32
|
+
ret += f"-{plan['speed_type']}{plan['speed']}{plan['level_type']}"
|
|
33
|
+
ret += f"{plan['level']} {plan['route']}\n"
|
|
34
|
+
if "destination_icao" in plan and "duration" in plan:
|
|
35
|
+
ret += f"-{plan['destination_icao']}{plan['duration']}"
|
|
36
|
+
if "alt_icao" in plan:
|
|
37
|
+
ret += f" {plan['alt_icao']}"
|
|
38
|
+
if "second_alt_icao" in plan:
|
|
39
|
+
ret += f" {plan['second_alt_icao']}"
|
|
40
|
+
ret += "\n"
|
|
41
|
+
ret += f"-{plan['other_info']}"
|
|
42
|
+
if "supplementary_info" in plan:
|
|
43
|
+
ret += "\n-"
|
|
44
|
+
ret += " ".join([f"{i[0]}/{i[1]}" for i in plan["supplementary_info"].items()])
|
|
45
|
+
|
|
46
|
+
ret += ")"
|
|
47
|
+
|
|
48
|
+
if ret[-1] == "\n":
|
|
49
|
+
ret = ret[:-1]
|
|
50
|
+
|
|
51
|
+
return ret
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def parse_atc_plan(atc_plan: str) -> dict[str, str]:
|
|
55
|
+
"""Parse an ATC flight plan string into a dictionary.
|
|
56
|
+
|
|
57
|
+
The route string is not converted to lat/lon in this process.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
atc_plan : str
|
|
62
|
+
An ATC flight plan string conforming to ICAO Doc 4444-ATM/501 (Appendix 2)
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
dict[str, str]
|
|
67
|
+
A dictionary consisting of parsed components of the ATC flight plan.
|
|
68
|
+
A full ATC plan will contain the keys:
|
|
69
|
+
|
|
70
|
+
- ``callsign``: ICAO flight callsign
|
|
71
|
+
- ``flight_rules``: Flight rules ("I", "V", "Y", "Z")
|
|
72
|
+
- ``type_of_flight``: Type of flight ("S", "N", "G", "M", "X")
|
|
73
|
+
- ``number_aircraft``: The number of aircraft, if more than one
|
|
74
|
+
- ``type_of_aircraft``: ICAO aircraft type
|
|
75
|
+
- ``wake_category``: Wake turbulence category
|
|
76
|
+
- ``equipment``: Radiocommunication, navigation and approach aid equipment and capabilities
|
|
77
|
+
- ``transponder``: Surveillance equipment and capabilities
|
|
78
|
+
- ``departure_icao``: ICAO departure airport
|
|
79
|
+
- ``time``: Estimated off-block (departure) time (UTC)
|
|
80
|
+
- ``speed_type``: Speed units ("K": km / hr, "N": knots)
|
|
81
|
+
- ``speed``: Cruise true airspeed in ``speed_type`` units
|
|
82
|
+
- ``level_type``: Level units ("F", "S", "A", "M")
|
|
83
|
+
- ``level``: Cruise level
|
|
84
|
+
- ``route``: Route string
|
|
85
|
+
- ``destination_icao``: ICAO destination airport
|
|
86
|
+
- ``duration``: The total estimated elapsed time for the flight plan
|
|
87
|
+
- ``alt_icao``: ICAO alternate destination airport
|
|
88
|
+
- ``second_alt_icao``: ICAO second alternate destination airport
|
|
89
|
+
- ``other_info``: Other information
|
|
90
|
+
- ``supplementary_info``: Supplementary information
|
|
91
|
+
|
|
92
|
+
References
|
|
93
|
+
----------
|
|
94
|
+
- https://applications.icao.int/tools/ATMiKIT/story_content/external_files/story_content/external_files/DOC%204444_PANS%20ATM_en.pdf
|
|
95
|
+
|
|
96
|
+
See Also
|
|
97
|
+
--------
|
|
98
|
+
:func:`to_atc_plan`
|
|
99
|
+
"""
|
|
100
|
+
atc_plan = atc_plan.replace("\r", " ")
|
|
101
|
+
atc_plan = atc_plan.replace("\n", " ")
|
|
102
|
+
atc_plan = atc_plan.upper()
|
|
103
|
+
atc_plan = atc_plan.strip()
|
|
104
|
+
|
|
105
|
+
if len(atc_plan) == 0:
|
|
106
|
+
raise ValueError("Empty or invalid flight plan")
|
|
107
|
+
|
|
108
|
+
atc_plan = atc_plan.replace("(FPL", "")
|
|
109
|
+
atc_plan = atc_plan.replace(")", "")
|
|
110
|
+
atc_plan = atc_plan.replace("--", "-")
|
|
111
|
+
|
|
112
|
+
basic = atc_plan.split("-")
|
|
113
|
+
|
|
114
|
+
flightplan: dict[str, Any] = {}
|
|
115
|
+
|
|
116
|
+
# Callsign
|
|
117
|
+
if len(basic) > 1:
|
|
118
|
+
flightplan["callsign"] = basic[1]
|
|
119
|
+
|
|
120
|
+
# Flight Rules
|
|
121
|
+
if len(basic) > 2:
|
|
122
|
+
flightplan["flight_rules"] = basic[2][0]
|
|
123
|
+
flightplan["type_of_flight"] = basic[2][1]
|
|
124
|
+
|
|
125
|
+
# Aircraft
|
|
126
|
+
if len(basic) > 3:
|
|
127
|
+
aircraft = basic[3].split("/")
|
|
128
|
+
matches = re.match(r"(\d{1})(\S{3,4})", aircraft[0])
|
|
129
|
+
groups = matches.groups() if matches else ()
|
|
130
|
+
|
|
131
|
+
if matches and len(groups) > 2:
|
|
132
|
+
flightplan["number_aircraft"] = groups[1]
|
|
133
|
+
flightplan["type_of_aircraft"] = groups[2]
|
|
134
|
+
else:
|
|
135
|
+
flightplan["type_of_aircraft"] = aircraft[0]
|
|
136
|
+
|
|
137
|
+
if len(aircraft) > 1:
|
|
138
|
+
flightplan["wake_category"] = aircraft[1]
|
|
139
|
+
|
|
140
|
+
# Equipment
|
|
141
|
+
if len(basic) > 4:
|
|
142
|
+
equip = basic[4].split("/")
|
|
143
|
+
flightplan["equipment"] = equip[0]
|
|
144
|
+
if len(equip) > 1:
|
|
145
|
+
flightplan["transponder"] = equip[1]
|
|
146
|
+
|
|
147
|
+
# Dep. airport info
|
|
148
|
+
if len(basic) > 5:
|
|
149
|
+
matches = re.match(r"(\D*)(\d*)", basic[5])
|
|
150
|
+
groups = matches.groups() if matches else ()
|
|
151
|
+
|
|
152
|
+
if groups:
|
|
153
|
+
flightplan["departure_icao"] = groups[0]
|
|
154
|
+
if len(groups) > 1:
|
|
155
|
+
flightplan["time"] = groups[1]
|
|
156
|
+
|
|
157
|
+
# Speed and route info
|
|
158
|
+
if len(basic) > 6:
|
|
159
|
+
matches = re.match(r"(\D*)(\d*)(\D*)(\d*)", basic[6])
|
|
160
|
+
groups = matches.groups() if matches else ()
|
|
161
|
+
|
|
162
|
+
# match speed and level
|
|
163
|
+
if groups:
|
|
164
|
+
flightplan["speed_type"] = groups[0]
|
|
165
|
+
if len(groups) > 1:
|
|
166
|
+
flightplan["speed"] = groups[1]
|
|
167
|
+
if len(groups) > 2:
|
|
168
|
+
flightplan["level_type"] = groups[2]
|
|
169
|
+
if len(groups) > 3:
|
|
170
|
+
flightplan["level"] = groups[3]
|
|
171
|
+
|
|
172
|
+
flightplan["route"] = basic[6][len("".join(groups)) :].strip()
|
|
173
|
+
else:
|
|
174
|
+
flightplan["route"] = basic[6].strip()
|
|
175
|
+
|
|
176
|
+
# Dest. airport info
|
|
177
|
+
if len(basic) > 7:
|
|
178
|
+
matches = re.match(r"(\D{4})(\d{4})", basic[7])
|
|
179
|
+
groups = matches.groups() if matches else ()
|
|
180
|
+
|
|
181
|
+
if groups:
|
|
182
|
+
flightplan["destination_icao"] = groups[0]
|
|
183
|
+
if len(groups) > 1:
|
|
184
|
+
flightplan["duration"] = groups[1]
|
|
185
|
+
|
|
186
|
+
matches = re.match(r"(\D{4})(\d{4})(\s{1})(\D{4})", basic[7])
|
|
187
|
+
groups = matches.groups() if matches else ()
|
|
188
|
+
|
|
189
|
+
if len(groups) > 3:
|
|
190
|
+
flightplan["alt_icao"] = groups[3]
|
|
191
|
+
|
|
192
|
+
matches = re.match(r"(\D{4})(\d{4})(\s{1})(\D{4})(\s{1})(\D{4})", basic[7])
|
|
193
|
+
groups = matches.groups() if matches else ()
|
|
194
|
+
|
|
195
|
+
if len(groups) > 5:
|
|
196
|
+
flightplan["second_alt_icao"] = groups[5]
|
|
197
|
+
|
|
198
|
+
# Other info
|
|
199
|
+
if len(basic) > 8:
|
|
200
|
+
info = basic[8]
|
|
201
|
+
idx = info.find("DOF")
|
|
202
|
+
if idx != -1:
|
|
203
|
+
flightplan["departure_date"] = info[idx + 4 : idx + 10]
|
|
204
|
+
|
|
205
|
+
flightplan["other_info"] = info.strip()
|
|
206
|
+
|
|
207
|
+
# Supl. Info
|
|
208
|
+
if len(basic) > 9:
|
|
209
|
+
sup_match = re.findall(r"(\D{1}[\/]{1})", basic[9])
|
|
210
|
+
if sup_match:
|
|
211
|
+
suplInfo = {}
|
|
212
|
+
for i in range(len(sup_match) - 1):
|
|
213
|
+
this_key = sup_match[i]
|
|
214
|
+
this_idx = basic[9].find(this_key)
|
|
215
|
+
|
|
216
|
+
next_key = sup_match[i + 1]
|
|
217
|
+
next_idx = basic[9].find(next_key)
|
|
218
|
+
|
|
219
|
+
val = basic[9][this_idx + 2 : next_idx - 1]
|
|
220
|
+
suplInfo[this_key[0]] = val
|
|
221
|
+
|
|
222
|
+
last_key = sup_match[-1]
|
|
223
|
+
last_idx = basic[9].find(last_key)
|
|
224
|
+
suplInfo[last_key[0]] = basic[9][last_idx + 2 :]
|
|
225
|
+
|
|
226
|
+
flightplan["supplementary_info"] = suplInfo
|
|
227
|
+
|
|
228
|
+
return flightplan
|
pycontrails/core/fuel.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Fuel data support."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclasses.dataclass(frozen=True)
|
|
11
|
+
class Fuel:
|
|
12
|
+
"""Base class for the physical parameters of the fuel."""
|
|
13
|
+
|
|
14
|
+
#: Fuel Name
|
|
15
|
+
fuel_name: str
|
|
16
|
+
|
|
17
|
+
#: Lower calorific value (LCV) of fuel, :math:`[J \ kg_{fuel}^{-1}]`
|
|
18
|
+
q_fuel: float
|
|
19
|
+
|
|
20
|
+
#: Percentage of hydrogen mass content in the fuel
|
|
21
|
+
hydrogen_content: float
|
|
22
|
+
|
|
23
|
+
#: CO2 emissions index for fuel, :math:`[kg_{CO_{2}} \ kg_{fuel}^{-1}]`
|
|
24
|
+
ei_co2: float
|
|
25
|
+
|
|
26
|
+
#: Water vapour emissions index for fuel, :math:`[kg_{H_{2}O} \ kg_{fuel}^{-1}]`
|
|
27
|
+
ei_h2o: float
|
|
28
|
+
|
|
29
|
+
#: Sulphur oxide, SO2-S gas, emissions index for fuel, :math:`[kg_{SO_{2}} \ kg_{fuel}^{-1}]`
|
|
30
|
+
ei_so2: float
|
|
31
|
+
|
|
32
|
+
#: Sulphates, S(VI)-S particle, emissions index for fuel, :math:`[kg_{S} \ kg_{fuel}^{-1}]`
|
|
33
|
+
ei_sulphates: float
|
|
34
|
+
|
|
35
|
+
#: Organic carbon emissions index for fuel, :math:`[kg_{OC} \ kg_{fuel}^{-1}]`
|
|
36
|
+
ei_oc: float
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclasses.dataclass(frozen=True)
|
|
40
|
+
class JetA(Fuel):
|
|
41
|
+
"""Jet A-1 Fuel.
|
|
42
|
+
|
|
43
|
+
References
|
|
44
|
+
----------
|
|
45
|
+
- :cite:`celikel2001forecasting`
|
|
46
|
+
- :cite:`leeContributionGlobalAviation2021`
|
|
47
|
+
- :cite:`stettlerAirQualityPublic2011`
|
|
48
|
+
- :cite:`wilkersonAnalysisEmissionData2010`
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
fuel_name: str = "Jet A-1"
|
|
52
|
+
q_fuel: float = 43.13e6
|
|
53
|
+
hydrogen_content: float = 13.8
|
|
54
|
+
ei_co2: float = 3.159
|
|
55
|
+
ei_h2o: float = 1.23
|
|
56
|
+
|
|
57
|
+
#: Sulphur oxide, SO2-S gas, emissions index for fuel, :math:`[kg_{SO_{2}} \ kg_{fuel}^{-1}]`
|
|
58
|
+
#: - The EI SO2 is proportional to the fuel sulphur content
|
|
59
|
+
#: - Celikel (2001): EI_SO2 = 0.84 g/kg-fuel for 450 ppm fuel
|
|
60
|
+
#: - Lee et al. (2021): EI_SO2 = 1.2 g/kg-fuel for 600 ppm fuel
|
|
61
|
+
ei_so2: float = 0.0012
|
|
62
|
+
|
|
63
|
+
#: Sulphates, S(VI)-S particle, emissions index for fuel, :math:`[kg_{S} \ kg_{fuel}^{-1}]`
|
|
64
|
+
#: - The SOx-S is partitioned into 98% SO2-S gas and 2% S(VI)-S particle
|
|
65
|
+
#: - References: Wilkerson et al. (2010) & Stettler et al. (2011)
|
|
66
|
+
ei_sulphates: float = ei_so2 / 0.98 * 0.02
|
|
67
|
+
|
|
68
|
+
#: Organic carbon emissions index for fuel, :math:`[kg_{OC} \ kg_{fuel}^{-1}]`
|
|
69
|
+
#: - High uncertainty
|
|
70
|
+
#: - Wilkerson et al. (2010): EI_OC = 15 mg/kg-fuel
|
|
71
|
+
#: - Stettler et al. (2011): EI_OC = 20 [1, 40] mg/kg-fuel
|
|
72
|
+
ei_oc: float = 20 * 1e-6
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class SAFBlend(Fuel):
|
|
76
|
+
"""Jet A-1 / Sustainable Aviation Fuel Blend.
|
|
77
|
+
|
|
78
|
+
SAF only changes the CO2 lifecycle emissions, not the CO2 emissions emitted at the
|
|
79
|
+
aircraft exhaust. We assume that the EI OC stays the same as Jet A-1 fuel due to lack
|
|
80
|
+
of data.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
pct_blend : float
|
|
85
|
+
Sustainable aviation fuel percentage blend ratio by volume, %. Expected
|
|
86
|
+
to be in the interval ``[0, 100]``.
|
|
87
|
+
|
|
88
|
+
References
|
|
89
|
+
----------
|
|
90
|
+
- :cite:`teohTargetedUseSustainable2022`
|
|
91
|
+
- :cite:`schrippAircraftEngineParticulate2022`
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(self, pct_blend: float) -> None:
|
|
95
|
+
if pct_blend < 0.0 or pct_blend > 100.0:
|
|
96
|
+
raise ValueError("pct_blend only accepts a value of between 0 and 100.")
|
|
97
|
+
|
|
98
|
+
self.pct_blend = pct_blend
|
|
99
|
+
|
|
100
|
+
fuel_name = "Jet A-1 / Sustainable Aviation Fuel Blend"
|
|
101
|
+
|
|
102
|
+
# We take the default values for Jet-A and modify them for a custom blend
|
|
103
|
+
base_fuel = JetA()
|
|
104
|
+
q_fuel = base_fuel.q_fuel + (10700.0 * self.pct_blend)
|
|
105
|
+
hydrogen_content = base_fuel.hydrogen_content + 0.015 * self.pct_blend
|
|
106
|
+
ei_co2 = base_fuel.ei_co2
|
|
107
|
+
ei_h2o = base_fuel.ei_h2o * (hydrogen_content / base_fuel.hydrogen_content)
|
|
108
|
+
ei_so2 = base_fuel.ei_so2 * (1.0 - self.pct_blend / 100.0)
|
|
109
|
+
ei_sulphates = ei_so2 / 0.98 * 0.02
|
|
110
|
+
ei_oc = base_fuel.ei_oc
|
|
111
|
+
|
|
112
|
+
super().__init__(
|
|
113
|
+
fuel_name=fuel_name,
|
|
114
|
+
q_fuel=q_fuel,
|
|
115
|
+
hydrogen_content=hydrogen_content,
|
|
116
|
+
ei_co2=ei_co2,
|
|
117
|
+
ei_h2o=ei_h2o,
|
|
118
|
+
ei_so2=ei_so2,
|
|
119
|
+
ei_sulphates=ei_sulphates,
|
|
120
|
+
ei_oc=ei_oc,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclasses.dataclass(frozen=True)
|
|
125
|
+
class HydrogenFuel(Fuel):
|
|
126
|
+
"""Hydrogen Fuel.
|
|
127
|
+
|
|
128
|
+
References
|
|
129
|
+
----------
|
|
130
|
+
- :cite:`khanEmissionsWaterVapour2022`
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
fuel_name: str = "Hydrogen"
|
|
134
|
+
q_fuel: float = 122.8e6
|
|
135
|
+
hydrogen_content: float = np.nan
|
|
136
|
+
ei_co2: float = 0.0
|
|
137
|
+
ei_h2o: float = 9.21
|
|
138
|
+
ei_so2: float = 0.0
|
|
139
|
+
ei_sulphates: float = 0.0
|
|
140
|
+
ei_oc: float = 0.0
|