pycontrails 0.54.0__cp312-cp312-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.

Files changed (109) hide show
  1. pycontrails/__init__.py +70 -0
  2. pycontrails/_version.py +16 -0
  3. pycontrails/core/__init__.py +30 -0
  4. pycontrails/core/aircraft_performance.py +641 -0
  5. pycontrails/core/airports.py +226 -0
  6. pycontrails/core/cache.py +881 -0
  7. pycontrails/core/coordinates.py +174 -0
  8. pycontrails/core/fleet.py +470 -0
  9. pycontrails/core/flight.py +2314 -0
  10. pycontrails/core/flightplan.py +220 -0
  11. pycontrails/core/fuel.py +140 -0
  12. pycontrails/core/interpolation.py +721 -0
  13. pycontrails/core/met.py +2833 -0
  14. pycontrails/core/met_var.py +307 -0
  15. pycontrails/core/models.py +1181 -0
  16. pycontrails/core/polygon.py +549 -0
  17. pycontrails/core/rgi_cython.cpython-312-darwin.so +0 -0
  18. pycontrails/core/vector.py +2190 -0
  19. pycontrails/datalib/__init__.py +12 -0
  20. pycontrails/datalib/_leo_utils/search.py +250 -0
  21. pycontrails/datalib/_leo_utils/static/bq_roi_query.sql +6 -0
  22. pycontrails/datalib/_leo_utils/vis.py +59 -0
  23. pycontrails/datalib/_met_utils/metsource.py +746 -0
  24. pycontrails/datalib/ecmwf/__init__.py +73 -0
  25. pycontrails/datalib/ecmwf/arco_era5.py +340 -0
  26. pycontrails/datalib/ecmwf/common.py +109 -0
  27. pycontrails/datalib/ecmwf/era5.py +550 -0
  28. pycontrails/datalib/ecmwf/era5_model_level.py +487 -0
  29. pycontrails/datalib/ecmwf/hres.py +782 -0
  30. pycontrails/datalib/ecmwf/hres_model_level.py +459 -0
  31. pycontrails/datalib/ecmwf/ifs.py +284 -0
  32. pycontrails/datalib/ecmwf/model_levels.py +434 -0
  33. pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
  34. pycontrails/datalib/ecmwf/variables.py +267 -0
  35. pycontrails/datalib/gfs/__init__.py +28 -0
  36. pycontrails/datalib/gfs/gfs.py +646 -0
  37. pycontrails/datalib/gfs/variables.py +100 -0
  38. pycontrails/datalib/goes.py +772 -0
  39. pycontrails/datalib/landsat.py +569 -0
  40. pycontrails/datalib/sentinel.py +511 -0
  41. pycontrails/datalib/spire.py +739 -0
  42. pycontrails/ext/bada.py +41 -0
  43. pycontrails/ext/cirium.py +14 -0
  44. pycontrails/ext/empirical_grid.py +140 -0
  45. pycontrails/ext/synthetic_flight.py +430 -0
  46. pycontrails/models/__init__.py +1 -0
  47. pycontrails/models/accf.py +406 -0
  48. pycontrails/models/apcemm/__init__.py +8 -0
  49. pycontrails/models/apcemm/apcemm.py +982 -0
  50. pycontrails/models/apcemm/inputs.py +226 -0
  51. pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
  52. pycontrails/models/apcemm/utils.py +437 -0
  53. pycontrails/models/cocip/__init__.py +29 -0
  54. pycontrails/models/cocip/cocip.py +2616 -0
  55. pycontrails/models/cocip/cocip_params.py +299 -0
  56. pycontrails/models/cocip/cocip_uncertainty.py +285 -0
  57. pycontrails/models/cocip/contrail_properties.py +1517 -0
  58. pycontrails/models/cocip/output_formats.py +2261 -0
  59. pycontrails/models/cocip/radiative_forcing.py +1262 -0
  60. pycontrails/models/cocip/radiative_heating.py +520 -0
  61. pycontrails/models/cocip/unterstrasser_wake_vortex.py +403 -0
  62. pycontrails/models/cocip/wake_vortex.py +396 -0
  63. pycontrails/models/cocip/wind_shear.py +120 -0
  64. pycontrails/models/cocipgrid/__init__.py +9 -0
  65. pycontrails/models/cocipgrid/cocip_grid.py +2573 -0
  66. pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
  67. pycontrails/models/dry_advection.py +494 -0
  68. pycontrails/models/emissions/__init__.py +21 -0
  69. pycontrails/models/emissions/black_carbon.py +594 -0
  70. pycontrails/models/emissions/emissions.py +1353 -0
  71. pycontrails/models/emissions/ffm2.py +336 -0
  72. pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
  73. pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
  74. pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
  75. pycontrails/models/humidity_scaling/__init__.py +37 -0
  76. pycontrails/models/humidity_scaling/humidity_scaling.py +1025 -0
  77. pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
  78. pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
  79. pycontrails/models/issr.py +210 -0
  80. pycontrails/models/pcc.py +327 -0
  81. pycontrails/models/pcr.py +154 -0
  82. pycontrails/models/ps_model/__init__.py +17 -0
  83. pycontrails/models/ps_model/ps_aircraft_params.py +376 -0
  84. pycontrails/models/ps_model/ps_grid.py +505 -0
  85. pycontrails/models/ps_model/ps_model.py +1017 -0
  86. pycontrails/models/ps_model/ps_operational_limits.py +540 -0
  87. pycontrails/models/ps_model/static/ps-aircraft-params-20240524.csv +68 -0
  88. pycontrails/models/ps_model/static/ps-synonym-list-20240524.csv +103 -0
  89. pycontrails/models/sac.py +459 -0
  90. pycontrails/models/tau_cirrus.py +168 -0
  91. pycontrails/physics/__init__.py +1 -0
  92. pycontrails/physics/constants.py +116 -0
  93. pycontrails/physics/geo.py +989 -0
  94. pycontrails/physics/jet.py +837 -0
  95. pycontrails/physics/thermo.py +451 -0
  96. pycontrails/physics/units.py +472 -0
  97. pycontrails/py.typed +0 -0
  98. pycontrails/utils/__init__.py +1 -0
  99. pycontrails/utils/dependencies.py +66 -0
  100. pycontrails/utils/iteration.py +13 -0
  101. pycontrails/utils/json.py +188 -0
  102. pycontrails/utils/temp.py +50 -0
  103. pycontrails/utils/types.py +165 -0
  104. pycontrails-0.54.0.dist-info/LICENSE +178 -0
  105. pycontrails-0.54.0.dist-info/METADATA +179 -0
  106. pycontrails-0.54.0.dist-info/NOTICE +43 -0
  107. pycontrails-0.54.0.dist-info/RECORD +109 -0
  108. pycontrails-0.54.0.dist-info/WHEEL +5 -0
  109. pycontrails-0.54.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,594 @@
1
+ """Non-volatile particulate matter (nvPM) calculations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ import numpy.typing as npt
7
+
8
+ from pycontrails.physics import constants, jet, units
9
+ from pycontrails.utils.types import ArrayScalarLike
10
+
11
+ # ---------------------------------------------------------
12
+ # nvPM Mass Emissions Index: Formation and Oxidation Method
13
+ # ---------------------------------------------------------
14
+
15
+
16
+ def mass_emissions_index_fox(
17
+ air_pressure: npt.NDArray[np.float64],
18
+ air_temperature: npt.NDArray[np.float64],
19
+ true_airspeed: npt.NDArray[np.float64],
20
+ fuel_flow_per_engine: npt.NDArray[np.float64],
21
+ thrust_setting: npt.NDArray[np.float64],
22
+ pressure_ratio: float,
23
+ *,
24
+ comp_efficiency: float = 0.9,
25
+ ) -> npt.NDArray[np.float64]:
26
+ r"""
27
+ Calculate the black carbon mass emissions index using the Formation and Oxidation Method (FOX).
28
+
29
+ Parameters
30
+ ----------
31
+ air_pressure: npt.NDArray[np.float64]
32
+ Pressure altitude at each waypoint, [:math:`Pa`]
33
+ air_temperature: npt.NDArray[np.float64]
34
+ Ambient temperature for each waypoint, [:math:`K`]
35
+ true_airspeed: npt.NDArray[np.float64]
36
+ True airspeed for each waypoint, [:math:`m s^{-1}`]
37
+ fuel_flow_per_engine: npt.NDArray[np.float64]
38
+ Fuel mass flow rate per engine, [:math:`kg s^{-1}`]
39
+ thrust_setting: npt.NDArray[np.float64]
40
+ Engine thrust setting, which is the fuel mass flow rate divided by
41
+ the maximum fuel mass flow rate
42
+ pressure_ratio: float
43
+ Engine pressure ratio from the ICAO EDB
44
+ comp_efficiency: float
45
+ Engine compressor efficiency, assumed to be 0.9
46
+
47
+ Returns
48
+ -------
49
+ npt.NDArray[np.float64]
50
+ Black carbon mass emissions index, [:math:`mg \ kg_{fuel}^{-1}`]
51
+
52
+ References
53
+ ----------
54
+ - :cite:`stettlerGlobalCivilAviation2013`
55
+ """
56
+ # Reference conditions: 100% thrust setting
57
+ p_3_ref = jet.combustor_inlet_pressure(pressure_ratio, constants.p_surface, 1.0)
58
+ t_3_ref = jet.combustor_inlet_temperature(
59
+ comp_efficiency, constants.T_msl, constants.p_surface, p_3_ref
60
+ )
61
+ t_fl_ref = flame_temperature(t_3_ref)
62
+ afr_ref = jet.air_to_fuel_ratio(1.0, cruise=False)
63
+ fuel_flow_max = fuel_flow_per_engine / thrust_setting
64
+ c_bc_ref = bc_mass_concentration_fox(fuel_flow_max, t_fl_ref, afr_ref)
65
+
66
+ # Cruise conditions
67
+ mach_num = units.tas_to_mach_number(true_airspeed, air_temperature)
68
+ t_2_cru = jet.compressor_inlet_temperature(air_temperature, mach_num)
69
+ p_2_cru = jet.compressor_inlet_pressure(air_pressure, mach_num)
70
+ p_3_cru = jet.combustor_inlet_pressure(pressure_ratio, p_2_cru, thrust_setting)
71
+ t_3_cru = jet.combustor_inlet_temperature(comp_efficiency, t_2_cru, p_2_cru, p_3_cru)
72
+ t_fl_cru = flame_temperature(t_3_cru)
73
+ afr_cru = jet.air_to_fuel_ratio(thrust_setting, cruise=True, T_compressor_inlet=t_2_cru)
74
+ c_bc_cru = bc_mass_concentration_cruise_fox(
75
+ c_bc_ref=c_bc_ref,
76
+ t_fl_cru=t_fl_cru,
77
+ t_fl_ref=t_fl_ref,
78
+ p_3_cru=p_3_cru,
79
+ p_3_ref=p_3_ref,
80
+ afr_cru=afr_cru,
81
+ afr_ref=afr_ref,
82
+ )
83
+ q_exhaust_cru = exhaust_gas_volume_per_kg_fuel(afr_cru)
84
+ return bc_mass_emissions_index(c_bc_cru, q_exhaust_cru)
85
+
86
+
87
+ def flame_temperature(t_3: ArrayScalarLike) -> ArrayScalarLike:
88
+ """
89
+ Calculate the flame temperature at the combustion chamber (t_fl).
90
+
91
+ Parameters
92
+ ----------
93
+ t_3: ArrayScalarLike
94
+ Combustor inlet temperature, [:math:`K`]
95
+
96
+ Returns
97
+ -------
98
+ ArrayScalarLike
99
+ Flame temperature at the combustion chamber, [:math:`K`]
100
+ """
101
+ return 0.9 * t_3 + 2120
102
+
103
+
104
+ def bc_mass_concentration_fox(
105
+ fuel_flow: npt.NDArray[np.float64],
106
+ t_fl: npt.NDArray[np.float64] | float,
107
+ afr: npt.NDArray[np.float64] | float,
108
+ ) -> npt.NDArray[np.float64]:
109
+ """Calculate the black carbon mass concentration for ground conditions (``c_bc_ref``).
110
+
111
+ This quantity is computed at the instrument sampling point without correcting
112
+ for particle line losses.
113
+
114
+ Parameters
115
+ ----------
116
+ fuel_flow: npt.NDArray[np.float64]
117
+ Fuel mass flow rate, [:math:`kg s^{-1}`]
118
+ t_fl: npt.NDArray[np.float64] | float
119
+ Flame temperature at the combustion chamber, [:math:`K`]
120
+ afr: npt.NDArray[np.float64] | float
121
+ Air-to-fuel ratio
122
+
123
+ Returns
124
+ -------
125
+ npt.NDArray[np.float64]:
126
+ Black carbon mass concentration for ground conditions, [:math:`mg m^{-3}`]
127
+ """
128
+ return fuel_flow * (356 * np.exp(-6390 / t_fl) - 608 * afr * np.exp(-19778 / t_fl))
129
+
130
+
131
+ def bc_mass_concentration_cruise_fox(
132
+ c_bc_ref: npt.NDArray[np.float64],
133
+ t_fl_cru: npt.NDArray[np.float64],
134
+ t_fl_ref: npt.NDArray[np.float64] | float,
135
+ p_3_cru: npt.NDArray[np.float64],
136
+ p_3_ref: npt.NDArray[np.float64] | float,
137
+ afr_cru: npt.NDArray[np.float64],
138
+ afr_ref: npt.NDArray[np.float64] | float,
139
+ ) -> npt.NDArray[np.float64]:
140
+ """Calculate the black carbon mass concentration for cruise conditions (``c_bc_cru``).
141
+
142
+ This quantity is computed at the instrument sampling point without correcting
143
+ for particle line losses.
144
+
145
+ Parameters
146
+ ----------
147
+ c_bc_ref: npt.NDArray[np.float64]
148
+ Black carbon mass concentration at reference conditions, [:math:`mg m^{-3}`]
149
+ t_fl_cru: npt.NDArray[np.float64]
150
+ Flame temperature at cruise conditions, [:math:`K`]
151
+ t_fl_ref: npt.NDArray[np.float64] | float
152
+ Flame temperature at reference conditions, [:math:`K`]
153
+ p_3_cru: npt.NDArray[np.float64]
154
+ Combustor inlet pressure at cruise conditions, [:math:`Pa`]
155
+ p_3_ref: npt.NDArray[np.float64] | float
156
+ Combustor inlet pressure at reference conditions, [:math:`Pa`]
157
+ afr_cru: npt.NDArray[np.float64]
158
+ Air-to-fuel ratio at cruise conditions
159
+ afr_ref: npt.NDArray[np.float64] | float
160
+ Air-to-fuel ratio at reference conditions
161
+
162
+ Returns
163
+ -------
164
+ npt.NDArray[np.float64]:
165
+ Black carbon mass concentration for cruise conditions, [:math:`mg m^{-3}`]
166
+ """
167
+ scaling_factor = dopelheuer_lecht_scaling_factor(
168
+ t_fl_cru=t_fl_cru,
169
+ t_fl_ref=t_fl_ref,
170
+ p_3_cru=p_3_cru,
171
+ p_3_ref=p_3_ref,
172
+ afr_cru=afr_cru,
173
+ afr_ref=afr_ref,
174
+ )
175
+ return c_bc_ref * scaling_factor
176
+
177
+
178
+ def dopelheuer_lecht_scaling_factor(
179
+ t_fl_cru: npt.NDArray[np.float64],
180
+ t_fl_ref: npt.NDArray[np.float64] | float,
181
+ p_3_cru: npt.NDArray[np.float64],
182
+ p_3_ref: npt.NDArray[np.float64] | float,
183
+ afr_cru: npt.NDArray[np.float64],
184
+ afr_ref: npt.NDArray[np.float64] | float,
185
+ ) -> npt.NDArray[np.float64]:
186
+ """Estimate scaling factor to convert the reference BC mass concentration from ground to cruise.
187
+
188
+ Parameters
189
+ ----------
190
+ t_fl_cru: npt.NDArray[np.float64]
191
+ Flame temperature at cruise conditions, [:math:`K`]
192
+ t_fl_ref: npt.NDArray[np.float64] | float
193
+ Flame temperature at reference conditions, [:math:`K`]
194
+ p_3_cru: npt.NDArray[np.float64]
195
+ Combustor inlet pressure at cruise conditions, [:math:`Pa`]
196
+ p_3_ref: npt.NDArray[np.float64] | float
197
+ Combustor inlet pressure at reference conditions, [:math:`Pa`]
198
+ afr_cru: npt.NDArray[np.float64]
199
+ Air-to-fuel ratio at cruise conditions
200
+ afr_ref: npt.NDArray[np.float64] | float
201
+ Air-to-fuel ratio at reference conditions
202
+
203
+ Returns
204
+ -------
205
+ npt.NDArray[np.float64]
206
+ Dopelheuer & Lecht scaling factor
207
+
208
+ References
209
+ ----------
210
+ - :cite:`dopelheuerInfluenceEnginePerformance1998`
211
+ """
212
+ exp_term = np.exp(20000 / t_fl_cru) / np.exp(20000 / t_fl_ref)
213
+ return (afr_ref / afr_cru) ** 2.5 * (p_3_cru / p_3_ref) ** 1.35 * exp_term
214
+
215
+
216
+ # ----------------------------------------------------------------------------
217
+ # nvPM Mass Emissions Index: "Improved" Formation and Oxidation Method (ImFOX)
218
+ # ----------------------------------------------------------------------------
219
+
220
+
221
+ def mass_emissions_index_imfox(
222
+ fuel_flow_per_engine: npt.NDArray[np.float64],
223
+ thrust_setting: npt.NDArray[np.float64],
224
+ fuel_hydrogen: float,
225
+ ) -> npt.NDArray[np.float64]:
226
+ r"""Calculate the BC mass EI using the "Improved" Formation and Oxidation Method (ImFOX).
227
+
228
+ Parameters
229
+ ----------
230
+ fuel_flow_per_engine: npt.NDArray[np.float64]
231
+ Fuel mass flow rate per engine, [:math:`kg s^{-1}`]
232
+ thrust_setting: npt.NDArray[np.float64]
233
+ Engine thrust setting, which is the fuel mass flow rate divided by the
234
+ maximum fuel mass flow rate
235
+ fuel_hydrogen: float
236
+ Percentage of hydrogen mass content in the fuel (13.8% for conventional Jet A-1 fuel)
237
+
238
+ Returns
239
+ -------
240
+ npt.NDArray[np.float64]
241
+ Black carbon mass emissions index, [:math:`mg \ kg_{fuel}^{-1}`]
242
+
243
+ References
244
+ ----------
245
+ - :cite:`abrahamsonPredictiveModelDevelopment2016`
246
+ """
247
+ afr_cru = air_to_fuel_ratio_imfox(thrust_setting)
248
+ t_4_cru = turbine_inlet_temperature_imfox(afr_cru)
249
+ c_bc_cru = bc_mass_concentration_imfox(
250
+ fuel_flow_per_engine, afr_cru, t_4_cru, fuel_hydrogen=fuel_hydrogen
251
+ )
252
+ q_exhaust_cru = exhaust_gas_volume_per_kg_fuel(afr_cru)
253
+ return bc_mass_emissions_index(c_bc_cru, q_exhaust_cru)
254
+
255
+
256
+ def air_to_fuel_ratio_imfox(thrust_setting: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
257
+ """Calculate the air-to-fuel ratio at cruise conditions via Abrahamson's method.
258
+
259
+ See Eq. (11) in :cite:`abrahamsonPredictiveModelDevelopment2016`.
260
+
261
+ Parameters
262
+ ----------
263
+ thrust_setting: npt.NDArray[np.float64]
264
+ Engine thrust setting, which is the fuel mass flow rate divided by
265
+ the maximum fuel mass flow rate
266
+
267
+ Returns
268
+ -------
269
+ npt.NDArray[np.float64]
270
+ Air-to-fuel ratio at cruise conditions
271
+
272
+ References
273
+ ----------
274
+ - :cite:`abrahamsonPredictiveModelDevelopment2016`
275
+ """
276
+ return 55.4 - 30.8 * thrust_setting
277
+
278
+
279
+ def turbine_inlet_temperature_imfox(afr: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
280
+ """Calculate the turbine inlet temperature using Abrahamson's method.
281
+
282
+ See Eq. (13) in :cite:`abrahamsonPredictiveModelDevelopment2016`.
283
+
284
+ Parameters
285
+ ----------
286
+ afr: npt.NDArray[np.float64]
287
+ air-to-fuel ratio at cruise conditions
288
+
289
+ Returns
290
+ -------
291
+ npt.NDArray[np.float64]
292
+ turbine inlet temperature, [:math:`K`]
293
+
294
+ References
295
+ ----------
296
+ - :cite:`abrahamsonPredictiveModelDevelopment2016`
297
+ """
298
+ return 490 + 42266 / afr
299
+
300
+
301
+ def bc_mass_concentration_imfox(
302
+ fuel_flow_per_engine: npt.NDArray[np.float64],
303
+ afr: npt.NDArray[np.float64],
304
+ t_4: npt.NDArray[np.float64],
305
+ fuel_hydrogen: float,
306
+ ) -> npt.NDArray[np.float64]:
307
+ """Calculate the BC mass concentration for ground and cruise conditions with ImFOX methodology.
308
+
309
+ This quantity is computed at the instrument sampling point without
310
+ correcting for particle line losses.
311
+
312
+ Parameters
313
+ ----------
314
+ fuel_flow_per_engine: npt.NDArray[np.float64]
315
+ fuel mass flow rate per engine, [:math:`kg s^{-1}`]
316
+ afr: npt.NDArray[np.float64]
317
+ air-to-fuel ratio
318
+ t_4: npt.NDArray[np.float64]
319
+ turbine inlet temperature, [:math:`K`]
320
+ fuel_hydrogen: float
321
+ percentage of hydrogen mass content in the fuel (13.8% for conventional Jet A-1 fuel)
322
+
323
+ Returns
324
+ -------
325
+ npt.NDArray[np.float64]
326
+ Black carbon mass concentration, [:math:`mg m^{-3}`]
327
+ """
328
+ exp_term = np.exp(13.6 - fuel_hydrogen)
329
+ formation_term = 295 * np.exp(-6390 / t_4)
330
+ oxidation_term = 608 * afr * np.exp(-19778 / t_4)
331
+ return fuel_flow_per_engine * exp_term * (formation_term - oxidation_term)
332
+
333
+
334
+ # ---------------------------------------------------------
335
+ # Commonly Used Black Carbon Mass Emissions Index Functions
336
+ # ---------------------------------------------------------
337
+
338
+
339
+ def exhaust_gas_volume_per_kg_fuel(afr: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
340
+ """
341
+ Calculate the volume of exhaust gas per mass of fuel burnt.
342
+
343
+ Parameters
344
+ ----------
345
+ afr: npt.NDArray[np.float64]
346
+ Air-to-fuel ratio
347
+
348
+ Returns
349
+ -------
350
+ npt.NDArray[np.float64]
351
+ Volume of exhaust gas per mass of fuel burnt, [:math:`m^{3}/kg_{fuel}`]
352
+
353
+ References
354
+ ----------
355
+ - :cite:`stettlerGlobalCivilAviation2013`
356
+ """
357
+ return 0.776 * afr + 0.877
358
+
359
+
360
+ def bc_mass_emissions_index(
361
+ c_bc: npt.NDArray[np.float64], q_exhaust: npt.NDArray[np.float64]
362
+ ) -> npt.NDArray[np.float64]:
363
+ """
364
+ Calculate the black carbon mass emissions index.
365
+
366
+ Parameters
367
+ ----------
368
+ c_bc: npt.NDArray[np.float64]
369
+ Black carbon mass concentration, [:math:`mg m^{-3}`]
370
+ q_exhaust: npt.NDArray[np.float64]
371
+ Volume of exhaust gas per mass of fuel burnt, [:math:`m^{3}/kg_{fuel}`]
372
+
373
+ Returns
374
+ -------
375
+ npt.NDArray[np.float64]
376
+ Black carbon mass emissions index, [:math:`mg/kg_{fuel}`]
377
+
378
+ References
379
+ ----------
380
+ - :cite:`stettlerGlobalCivilAviation2013`
381
+ """
382
+ return c_bc * q_exhaust
383
+
384
+
385
+ # ----------------------------------------------------------
386
+ # nvPM Number Emissions Index: Fractal Aggregates (FA) model
387
+ # ----------------------------------------------------------
388
+
389
+
390
+ def geometric_mean_diameter_sac(
391
+ air_pressure: npt.NDArray[np.float64],
392
+ air_temperature: npt.NDArray[np.float64],
393
+ true_airspeed: npt.NDArray[np.float64],
394
+ thrust_setting: npt.NDArray[np.float64],
395
+ pressure_ratio: float,
396
+ q_fuel: float,
397
+ *,
398
+ comp_efficiency: float = 0.9,
399
+ delta_loss: float = 5.75,
400
+ cruise: bool = True,
401
+ ) -> npt.NDArray[np.float64]:
402
+ r"""Calculate the BC GMD for singular annular combustor (SAC) engines.
403
+
404
+ The BC (black carbon) GMD (geometric mean diameter) is estimated using
405
+ the non-dimensionalized engine thrust setting, the ratio of turbine inlet
406
+ to the compressor inlet temperature (``t4_t2``).
407
+
408
+ Parameters
409
+ ----------
410
+ air_pressure: npt.NDArray[np.float64]
411
+ Pressure altitude at each waypoint, [:math:`Pa`]
412
+ air_temperature: npt.NDArray[np.float64]
413
+ Ambient temperature for each waypoint, [:math:`K`]
414
+ true_airspeed: npt.NDArray[np.float64]
415
+ True airspeed for each waypoint, [:math:`m s^{-1}`]
416
+ thrust_setting: npt.NDArray[np.float64]
417
+ Engine thrust setting, which is the fuel mass flow rate divided by the
418
+ maximum fuel mass flow rate
419
+ pressure_ratio: float
420
+ Engine pressure ratio from the ICAO EDB
421
+ q_fuel : float
422
+ Lower calorific value (LCV) of fuel, [:math:`J \ kg_{fuel}^{-1}`]
423
+ comp_efficiency: float
424
+ Engine compressor efficiency (assumed to be 0.9)
425
+ delta_loss: float
426
+ Correction factor accounting for particle line losses (assumed to be 5.75 nm), [:math:`nm`]
427
+ cruise: bool
428
+ Set to true when the aircraft is not on the ground.
429
+
430
+ Returns
431
+ -------
432
+ npt.NDArray[np.float64]
433
+ black carbon geometric mean diameter, [:math:`nm`]
434
+
435
+ References
436
+ ----------
437
+ - :cite:`teohMitigatingClimateForcing2020`
438
+ """
439
+ t4_t2 = jet.thrust_setting_nd(
440
+ true_airspeed,
441
+ thrust_setting,
442
+ air_temperature,
443
+ air_pressure,
444
+ pressure_ratio,
445
+ q_fuel,
446
+ comp_efficiency=comp_efficiency,
447
+ cruise=cruise,
448
+ )
449
+ return (2.5883 * t4_t2**2) - (5.3723 * t4_t2) + 16.721 - delta_loss
450
+
451
+
452
+ def number_emissions_index_fractal_aggregates(
453
+ nvpm_ei_m: npt.NDArray[np.float64],
454
+ gmd: npt.NDArray[np.float64],
455
+ *,
456
+ gsd: float | npt.NDArray[np.float64] = 1.80,
457
+ rho_bc: float = 1770,
458
+ k_tem: float = 1.621e-5,
459
+ d_tem: float = 0.39,
460
+ d_fm: float = 2.76,
461
+ ) -> npt.NDArray[np.float64]:
462
+ """
463
+ Estimate the black carbon number emission index using the fractal aggregates (FA) model.
464
+
465
+ The FA model estimates the number emissions index from the mass emissions index,
466
+ particle size distribution, and morphology.
467
+
468
+ Parameters
469
+ ----------
470
+ nvpm_ei_m: npt.NDArray[np.float64]
471
+ Black carbon mass emissions index, [:math:`kg/kg_{fuel}`]
472
+ gmd: npt.NDArray[np.float64]
473
+ Black carbon geometric mean diameter, [:math:`m`]
474
+ gsd: float
475
+ Black carbon geometric standard deviation (assumed to be 1.80)
476
+ rho_bc: float
477
+ Black carbon material density (1770 kg/m**3), [:math:`kg m^{-3}`]
478
+ k_tem: float
479
+ Transmission electron microscopy prefactor coefficient (assumed to be 1.621e-5)
480
+ d_tem: float
481
+ Transmission electron microscopy exponent coefficient (assumed to be 0.39)
482
+ d_fm: float
483
+ Mass-mobility exponent (assumed to be 2.76)
484
+
485
+ Returns
486
+ -------
487
+ npt.NDArray[np.float64]
488
+ Black carbon number emissions index, [:math:`kg_{fuel}^{-1}`]
489
+
490
+ References
491
+ ----------
492
+ - FA model: :cite:`teohMethodologyRelateBlack2019`
493
+ - ``gmd``, ``gsd``, ``d_fm``: :cite:`teohMitigatingClimateForcing2020`
494
+ - ``rho_bc``: :cite:`parkMeasurementInherentMaterial2004`
495
+ - ``k_tem``, ``d_tem``: :cite:`dastanpourObservationsCorrelationPrimary2014`
496
+ """
497
+ phi = 3 * d_tem + (1 - d_tem) * d_fm
498
+ exponential_term = np.exp(0.5 * phi**2 * np.log(gsd) ** 2)
499
+ denom = rho_bc * (np.pi / 6) * k_tem ** (3 - d_fm) * gmd**phi * exponential_term
500
+ return nvpm_ei_m / denom
501
+
502
+
503
+ # -------------------------------------------------------------
504
+ # Scale nvPM emissions indices due to sustainable aviation fuel
505
+ # -------------------------------------------------------------
506
+
507
+
508
+ def nvpm_number_ei_pct_reduction_due_to_saf(
509
+ hydrogen_content: float | npt.NDArray[np.float64],
510
+ thrust_setting: npt.NDArray[np.float64],
511
+ ) -> npt.NDArray[np.float64]:
512
+ """
513
+ Adjust nvPM number emissions index to account for the effects of sustainable aviation fuels.
514
+
515
+ Parameters
516
+ ----------
517
+ hydrogen_content: float
518
+ The percentage of hydrogen mass content in the fuel.
519
+ thrust_setting: npt.NDArray[np.float64]
520
+ Engine thrust setting, where the equivalent fuel mass flow rate per engine at
521
+ sea level, :math:`[0 - 1]`.
522
+
523
+ Returns
524
+ -------
525
+ npt.NDArray[np.float64]
526
+ Percentage reduction in nvPM number emissions index
527
+
528
+ References
529
+ ----------
530
+ - :cite:`teohTargetedUseSustainable2022`
531
+ - :cite:`bremEffectsFuelAromatic2015`
532
+ """
533
+ a0 = -114.21
534
+ a1 = 1.06
535
+ a2 = 0.5
536
+ return _template_saf_reduction(hydrogen_content, thrust_setting, a0, a1, a2)
537
+
538
+
539
+ def nvpm_mass_ei_pct_reduction_due_to_saf(
540
+ hydrogen_content: float | npt.NDArray[np.float64],
541
+ thrust_setting: npt.NDArray[np.float64],
542
+ ) -> npt.NDArray[np.float64]:
543
+ """
544
+ Adjust nvPM mass emissions index to account for the effects of sustainable aviation fuels.
545
+
546
+ For fuel with hydrogen mass content > 14.3, the adjustment factor is adopted from
547
+ Teoh et al. (2022), which was used to calculate the change in nvPM EIn.
548
+
549
+ Parameters
550
+ ----------
551
+ hydrogen_content: float
552
+ The percentage of hydrogen mass content in the fuel.
553
+ thrust_setting: npt.NDArray[np.float64]
554
+ Engine thrust setting, where the equivalent fuel mass flow rate per engine at
555
+ sea level, :math:`[0 - 1]`.
556
+
557
+ Returns
558
+ -------
559
+ npt.NDArray[np.float64]
560
+ Percentage reduction in nvPM number emissions index
561
+
562
+ References
563
+ ----------
564
+ - :cite:`teohTargetedUseSustainable2022`
565
+ - :cite:`bremEffectsFuelAromatic2015`
566
+ """
567
+ a0 = -124.05
568
+ a1 = 1.02
569
+ a2 = 0.6
570
+ return _template_saf_reduction(hydrogen_content, thrust_setting, a0, a1, a2)
571
+
572
+
573
+ def _template_saf_reduction(
574
+ hydrogen_content: float | npt.NDArray[np.float64],
575
+ thrust_setting: npt.NDArray[np.float64],
576
+ a0: float,
577
+ a1: float,
578
+ a2: float,
579
+ ) -> npt.NDArray[np.float64]:
580
+ # Thrust setting cannot be computed when engine data is not provided in
581
+ # the ICAO EDB, so set default to 45% thrust.
582
+ thrust_setting = np.nan_to_num(thrust_setting, nan=0.45)
583
+ delta_h = hydrogen_content - 13.80
584
+ d_nvpm_ein_pct = (a0 + a1 * (thrust_setting * 100.0)) * delta_h
585
+
586
+ # Adjust when delta_h is large
587
+ if isinstance(delta_h, np.ndarray):
588
+ filt = delta_h > 0.5
589
+ d_nvpm_ein_pct[filt] *= np.exp(0.5 * (a2 - delta_h[filt]))
590
+ elif delta_h > 0.5:
591
+ d_nvpm_ein_pct *= np.exp(0.5 * (a2 - delta_h))
592
+
593
+ d_nvpm_ein_pct.clip(min=-90.0, max=0.0, out=d_nvpm_ein_pct)
594
+ return d_nvpm_ein_pct