pycontrails 0.58.0__cp314-cp314-macosx_11_0_arm64.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 (122) hide show
  1. pycontrails/__init__.py +70 -0
  2. pycontrails/_version.py +34 -0
  3. pycontrails/core/__init__.py +30 -0
  4. pycontrails/core/aircraft_performance.py +679 -0
  5. pycontrails/core/airports.py +228 -0
  6. pycontrails/core/cache.py +889 -0
  7. pycontrails/core/coordinates.py +174 -0
  8. pycontrails/core/fleet.py +483 -0
  9. pycontrails/core/flight.py +2185 -0
  10. pycontrails/core/flightplan.py +228 -0
  11. pycontrails/core/fuel.py +140 -0
  12. pycontrails/core/interpolation.py +702 -0
  13. pycontrails/core/met.py +2931 -0
  14. pycontrails/core/met_var.py +387 -0
  15. pycontrails/core/models.py +1321 -0
  16. pycontrails/core/polygon.py +549 -0
  17. pycontrails/core/rgi_cython.cpython-314-darwin.so +0 -0
  18. pycontrails/core/vector.py +2249 -0
  19. pycontrails/datalib/__init__.py +12 -0
  20. pycontrails/datalib/_met_utils/metsource.py +746 -0
  21. pycontrails/datalib/ecmwf/__init__.py +73 -0
  22. pycontrails/datalib/ecmwf/arco_era5.py +345 -0
  23. pycontrails/datalib/ecmwf/common.py +114 -0
  24. pycontrails/datalib/ecmwf/era5.py +554 -0
  25. pycontrails/datalib/ecmwf/era5_model_level.py +490 -0
  26. pycontrails/datalib/ecmwf/hres.py +804 -0
  27. pycontrails/datalib/ecmwf/hres_model_level.py +466 -0
  28. pycontrails/datalib/ecmwf/ifs.py +287 -0
  29. pycontrails/datalib/ecmwf/model_levels.py +435 -0
  30. pycontrails/datalib/ecmwf/static/model_level_dataframe_v20240418.csv +139 -0
  31. pycontrails/datalib/ecmwf/variables.py +268 -0
  32. pycontrails/datalib/geo_utils.py +261 -0
  33. pycontrails/datalib/gfs/__init__.py +28 -0
  34. pycontrails/datalib/gfs/gfs.py +656 -0
  35. pycontrails/datalib/gfs/variables.py +104 -0
  36. pycontrails/datalib/goes.py +757 -0
  37. pycontrails/datalib/himawari/__init__.py +27 -0
  38. pycontrails/datalib/himawari/header_struct.py +266 -0
  39. pycontrails/datalib/himawari/himawari.py +667 -0
  40. pycontrails/datalib/landsat.py +589 -0
  41. pycontrails/datalib/leo_utils/__init__.py +5 -0
  42. pycontrails/datalib/leo_utils/correction.py +266 -0
  43. pycontrails/datalib/leo_utils/landsat_metadata.py +300 -0
  44. pycontrails/datalib/leo_utils/search.py +250 -0
  45. pycontrails/datalib/leo_utils/sentinel_metadata.py +748 -0
  46. pycontrails/datalib/leo_utils/static/bq_roi_query.sql +6 -0
  47. pycontrails/datalib/leo_utils/vis.py +59 -0
  48. pycontrails/datalib/sentinel.py +650 -0
  49. pycontrails/datalib/spire/__init__.py +5 -0
  50. pycontrails/datalib/spire/exceptions.py +62 -0
  51. pycontrails/datalib/spire/spire.py +604 -0
  52. pycontrails/ext/bada.py +42 -0
  53. pycontrails/ext/cirium.py +14 -0
  54. pycontrails/ext/empirical_grid.py +140 -0
  55. pycontrails/ext/synthetic_flight.py +431 -0
  56. pycontrails/models/__init__.py +1 -0
  57. pycontrails/models/accf.py +425 -0
  58. pycontrails/models/apcemm/__init__.py +8 -0
  59. pycontrails/models/apcemm/apcemm.py +983 -0
  60. pycontrails/models/apcemm/inputs.py +226 -0
  61. pycontrails/models/apcemm/static/apcemm_yaml_template.yaml +183 -0
  62. pycontrails/models/apcemm/utils.py +437 -0
  63. pycontrails/models/cocip/__init__.py +29 -0
  64. pycontrails/models/cocip/cocip.py +2742 -0
  65. pycontrails/models/cocip/cocip_params.py +305 -0
  66. pycontrails/models/cocip/cocip_uncertainty.py +291 -0
  67. pycontrails/models/cocip/contrail_properties.py +1530 -0
  68. pycontrails/models/cocip/output_formats.py +2270 -0
  69. pycontrails/models/cocip/radiative_forcing.py +1260 -0
  70. pycontrails/models/cocip/radiative_heating.py +520 -0
  71. pycontrails/models/cocip/unterstrasser_wake_vortex.py +508 -0
  72. pycontrails/models/cocip/wake_vortex.py +396 -0
  73. pycontrails/models/cocip/wind_shear.py +120 -0
  74. pycontrails/models/cocipgrid/__init__.py +9 -0
  75. pycontrails/models/cocipgrid/cocip_grid.py +2552 -0
  76. pycontrails/models/cocipgrid/cocip_grid_params.py +138 -0
  77. pycontrails/models/dry_advection.py +602 -0
  78. pycontrails/models/emissions/__init__.py +21 -0
  79. pycontrails/models/emissions/black_carbon.py +599 -0
  80. pycontrails/models/emissions/emissions.py +1353 -0
  81. pycontrails/models/emissions/ffm2.py +336 -0
  82. pycontrails/models/emissions/static/default-engine-uids.csv +239 -0
  83. pycontrails/models/emissions/static/edb-gaseous-v29b-engines.csv +596 -0
  84. pycontrails/models/emissions/static/edb-nvpm-v29b-engines.csv +215 -0
  85. pycontrails/models/extended_k15.py +1327 -0
  86. pycontrails/models/humidity_scaling/__init__.py +37 -0
  87. pycontrails/models/humidity_scaling/humidity_scaling.py +1075 -0
  88. pycontrails/models/humidity_scaling/quantiles/era5-model-level-quantiles.pq +0 -0
  89. pycontrails/models/humidity_scaling/quantiles/era5-pressure-level-quantiles.pq +0 -0
  90. pycontrails/models/issr.py +210 -0
  91. pycontrails/models/pcc.py +326 -0
  92. pycontrails/models/pcr.py +154 -0
  93. pycontrails/models/ps_model/__init__.py +18 -0
  94. pycontrails/models/ps_model/ps_aircraft_params.py +381 -0
  95. pycontrails/models/ps_model/ps_grid.py +701 -0
  96. pycontrails/models/ps_model/ps_model.py +1000 -0
  97. pycontrails/models/ps_model/ps_operational_limits.py +525 -0
  98. pycontrails/models/ps_model/static/ps-aircraft-params-20250328.csv +69 -0
  99. pycontrails/models/ps_model/static/ps-synonym-list-20250328.csv +104 -0
  100. pycontrails/models/sac.py +442 -0
  101. pycontrails/models/tau_cirrus.py +183 -0
  102. pycontrails/physics/__init__.py +1 -0
  103. pycontrails/physics/constants.py +117 -0
  104. pycontrails/physics/geo.py +1138 -0
  105. pycontrails/physics/jet.py +968 -0
  106. pycontrails/physics/static/iata-cargo-load-factors-20250221.csv +74 -0
  107. pycontrails/physics/static/iata-passenger-load-factors-20250221.csv +74 -0
  108. pycontrails/physics/thermo.py +551 -0
  109. pycontrails/physics/units.py +472 -0
  110. pycontrails/py.typed +0 -0
  111. pycontrails/utils/__init__.py +1 -0
  112. pycontrails/utils/dependencies.py +66 -0
  113. pycontrails/utils/iteration.py +13 -0
  114. pycontrails/utils/json.py +187 -0
  115. pycontrails/utils/temp.py +50 -0
  116. pycontrails/utils/types.py +163 -0
  117. pycontrails-0.58.0.dist-info/METADATA +180 -0
  118. pycontrails-0.58.0.dist-info/RECORD +122 -0
  119. pycontrails-0.58.0.dist-info/WHEEL +6 -0
  120. pycontrails-0.58.0.dist-info/licenses/LICENSE +178 -0
  121. pycontrails-0.58.0.dist-info/licenses/NOTICE +43 -0
  122. pycontrails-0.58.0.dist-info/top_level.txt +3 -0
@@ -0,0 +1,425 @@
1
+ """Algorithmic Climate Change Functions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import warnings
6
+ from dataclasses import dataclass
7
+ from typing import Any, overload
8
+
9
+ import xarray as xr
10
+
11
+ import pycontrails
12
+ from pycontrails.core.flight import Flight
13
+ from pycontrails.core.met import MetDataset
14
+ from pycontrails.core.met_var import (
15
+ AirTemperature,
16
+ EastwardWind,
17
+ Geopotential,
18
+ NorthwardWind,
19
+ RelativeHumidity,
20
+ SpecificHumidity,
21
+ )
22
+ from pycontrails.core.models import Model, ModelParams
23
+ from pycontrails.core.vector import GeoVectorDataset
24
+ from pycontrails.datalib import ecmwf
25
+
26
+
27
+ def wide_body_jets() -> set[str]:
28
+ """Return a set of wide body jets."""
29
+ return {
30
+ "A332",
31
+ "A333",
32
+ "A338",
33
+ "A339",
34
+ "A342",
35
+ "A343",
36
+ "A345",
37
+ "A356",
38
+ "A359",
39
+ "A388",
40
+ "B762",
41
+ "B763",
42
+ "B764",
43
+ "B772",
44
+ "B773",
45
+ "B778",
46
+ "B779",
47
+ "B788",
48
+ "B789",
49
+ }
50
+
51
+
52
+ def regional_jets() -> set[str]:
53
+ """Return a set of regional jets."""
54
+ return {
55
+ "CRJ1",
56
+ "CRJ2",
57
+ "CRJ7",
58
+ "CRJ8",
59
+ "CRJ9",
60
+ "CRJX",
61
+ "E135",
62
+ "E145",
63
+ "E170",
64
+ "E190",
65
+ }
66
+
67
+
68
+ @dataclass
69
+ class ACCFParams(ModelParams):
70
+ """Default ACCF model parameters.
71
+
72
+ See `config-user.yml` definition at
73
+ https://github.com/dlr-pa/climaccf
74
+ """
75
+
76
+ lat_bound: tuple[float, float] | None = None
77
+ lon_bound: tuple[float, float] | None = None
78
+
79
+ efficacy: bool = True
80
+ efficacy_option: str = "lee_2021"
81
+
82
+ accf_v: str = "V1.0"
83
+
84
+ ch4_scaling: float = 1.0
85
+ co2_scaling: float = 1.0
86
+ cont_scaling: float = 1.0
87
+ h2o_scaling: float = 1.0
88
+ o3_scaling: float = 1.0
89
+
90
+ forecast_step: float | None = None
91
+
92
+ sep_ri_rw: bool = False
93
+
94
+ climate_indicator: str = "ATR"
95
+
96
+ #: The horizontal resolution of the meteorological data in degrees.
97
+ #: If None, it will be inferred from the ``met`` dataset for :class:`MetDataset`
98
+ #: source, otherwise it will be set to 0.5.
99
+ horizontal_resolution: float | None = None
100
+
101
+ emission_scenario: str = "pulse"
102
+
103
+ time_horizon: int = 20
104
+
105
+ pfca: str = "PCFA-ISSR"
106
+
107
+ merged: bool = True
108
+
109
+ #: RHI Threshold
110
+ issr_rhi_threshold: float = 0.9
111
+ issr_temp_threshold: float = 235
112
+
113
+ sac_ei_h2o: float = 1.25
114
+ sac_q: float = 43000000.0
115
+ sac_eta: float = 0.3
116
+
117
+ nox_ei: str = "TTV"
118
+
119
+ PMO: bool = False
120
+
121
+ unit_K_per_kg_fuel: bool = False
122
+
123
+
124
+ class ACCF(Model):
125
+ """Compute Algorithmic Climate Change Functions (ACCF).
126
+
127
+ This class is a wrapper over the DLR / UMadrid library
128
+ `climaccf <https://github.com/dlr-pa/climaccf>`_,
129
+ `DOI: 10.5281/zenodo.6977272 <https://doi.org/10.5281/zenodo.6977272>`_
130
+
131
+ Parameters
132
+ ----------
133
+ met : MetDataset
134
+ Dataset containing "air_temperature" and "specific_humidity" variables
135
+ surface : MetDataset, optional
136
+ Dataset containing "surface_solar_downward_radiation" and
137
+ "top_net_thermal_radiation" variables
138
+
139
+ References
140
+ ----------
141
+ - :cite:`dietmullerDlrpaClimaccfDataset2022`
142
+ - :cite:`dietmullerPythonLibraryComputing2022`
143
+
144
+ """
145
+
146
+ name = "accr"
147
+ long_name = "algorithmic climate change functions"
148
+ met_variables = (
149
+ AirTemperature,
150
+ SpecificHumidity,
151
+ ecmwf.PotentialVorticity,
152
+ Geopotential,
153
+ (RelativeHumidity, ecmwf.RelativeHumidity),
154
+ NorthwardWind,
155
+ EastwardWind,
156
+ )
157
+ sur_variables = (ecmwf.SurfaceSolarDownwardRadiation, ecmwf.TopNetThermalRadiation)
158
+ default_params = ACCFParams
159
+
160
+ # This variable won't get used since we are not writing the output
161
+ # anywhere, but the library will complain if it's not defined
162
+ path_lib = "./"
163
+
164
+ def __init__(
165
+ self,
166
+ met: MetDataset,
167
+ surface: MetDataset | None = None,
168
+ params: dict[str, Any] | None = None,
169
+ **params_kwargs: Any,
170
+ ) -> None:
171
+ # Normalize ECMWF variables
172
+ variables = self.ecmwf_met_variables()
173
+ met = met.standardize_variables(variables)
174
+
175
+ # If relative humidity is in percentage, convert to a proportion
176
+ if met["relative_humidity"].attrs.get("units") == "%":
177
+ met.data["relative_humidity"] /= 100.0
178
+ met.data["relative_humidity"].attrs["units"] = "1"
179
+
180
+ # Ignore humidity scaling warning
181
+ with warnings.catch_warnings():
182
+ warnings.filterwarnings("ignore", module="pycontrails.core.models")
183
+ super().__init__(met, params=params, **params_kwargs)
184
+
185
+ if surface:
186
+ surface = surface.copy()
187
+ surface = surface.standardize_variables(self.sur_variables)
188
+ surface.data = _rad_instantaneous_to_accumulated(surface.data)
189
+ self.surface = surface
190
+
191
+ @overload
192
+ def eval(self, source: Flight, **params: Any) -> Flight: ...
193
+
194
+ @overload
195
+ def eval(self, source: GeoVectorDataset, **params: Any) -> GeoVectorDataset: ...
196
+
197
+ @overload
198
+ def eval(self, source: MetDataset | None = ..., **params: Any) -> MetDataset: ...
199
+
200
+ def eval(
201
+ self, source: GeoVectorDataset | Flight | MetDataset | None = None, **params: Any
202
+ ) -> GeoVectorDataset | Flight | MetDataset:
203
+ """Evaluate accfs along flight trajectory or on meteorology grid.
204
+
205
+ Parameters
206
+ ----------
207
+ source : GeoVectorDataset | Flight | MetDataset | None, optional
208
+ Input GeoVectorDataset or Flight.
209
+ If None, evaluates at the :attr:`met` grid points.
210
+ **params : Any
211
+ Overwrite model parameters before eval
212
+
213
+ Returns
214
+ -------
215
+ GeoVectorDataset | Flight | MetDataArray
216
+ Returns `np.nan` if interpolating outside meteorology grid.
217
+
218
+ Raises
219
+ ------
220
+ NotImplementedError
221
+ Raises if input ``source`` is not supported.
222
+ """
223
+ try:
224
+ from climaccf.accf import GeTaCCFs
225
+ except ModuleNotFoundError as e:
226
+ msg = (
227
+ "ACCF.eval method requires the 'climaccf' package. This can be installed "
228
+ "with 'pip install git+https://github.com/dlr-pa/climaccf.git'."
229
+ )
230
+ raise ModuleNotFoundError(msg) from e
231
+
232
+ self.update_params(params)
233
+ self.set_source(source)
234
+
235
+ if isinstance(self.source, GeoVectorDataset):
236
+ self.downselect_met()
237
+ if hasattr(self, "surface"):
238
+ self.surface = self.source.downselect_met(self.surface)
239
+
240
+ if self.params["horizontal_resolution"] is None:
241
+ if isinstance(self.source, MetDataset):
242
+ # Overwrite horizontal resolution to match met
243
+ longitude = self.source.data["longitude"].values
244
+ latitude = self.source.data["latitude"].values
245
+ if longitude.size > 1:
246
+ hres = abs(longitude[1] - longitude[0])
247
+ self.params["horizontal_resolution"] = float(hres)
248
+ elif latitude.size > 1:
249
+ hres = abs(latitude[1] - latitude[0])
250
+ self.params["horizontal_resolution"] = float(hres)
251
+ else:
252
+ self.params["horizontal_resolution"] = 0.5
253
+ else:
254
+ self.params["horizontal_resolution"] = 0.5
255
+
256
+ p_settings = _get_accf_config(self.params)
257
+
258
+ self.set_source_met()
259
+ self._generate_weather_store(p_settings)
260
+
261
+ # check aircraft type and set in config if needed
262
+ if self.params["nox_ei"] != "TTV":
263
+ if isinstance(self.source, Flight):
264
+ ac = self.source.attrs["aircraft_type"]
265
+ if ac in wide_body_jets():
266
+ p_settings["ac_type"] = "wide-body"
267
+ elif ac in regional_jets():
268
+ p_settings["ac_type"] = "regional"
269
+ else:
270
+ p_settings["ac_type"] = "single-aisle"
271
+ else:
272
+ p_settings["ac_type"] = "wide-body"
273
+
274
+ clim_imp = GeTaCCFs(self)
275
+ clim_imp.get_accfs(**p_settings)
276
+ aCCFs, _ = clim_imp.get_xarray()
277
+
278
+ # assign ACCF outputs to source
279
+ skip = {
280
+ v[0].short_name if isinstance(v, tuple) else v.short_name
281
+ for v in (*self.met_variables, *self.sur_variables)
282
+ }
283
+ maCCFs = MetDataset(aCCFs)
284
+ for key, arr in maCCFs.data.items():
285
+ # skip met variables
286
+ if key in skip:
287
+ continue
288
+
289
+ assert isinstance(key, str)
290
+ if isinstance(self.source, GeoVectorDataset):
291
+ self.source[key] = self.source.intersect_met(maCCFs[key])
292
+ else:
293
+ self.source[key] = arr
294
+
295
+ self.transfer_met_source_attrs()
296
+ self.source.attrs["pycontrails_version"] = pycontrails.__version__
297
+
298
+ return self.source
299
+
300
+ def _generate_weather_store(self, p_settings: dict[str, Any]) -> None:
301
+ from climaccf.weather_store import WeatherStore
302
+
303
+ # The library does not call the coordinates by name, it just slices the
304
+ # underlying data array, so we need to put them in the expected order.
305
+ # It also needs variables to have the ECMWF short name
306
+ if isinstance(self.met, MetDataset):
307
+ ds_met = self.met.data.transpose("time", "level", "latitude", "longitude")
308
+ name_dict = {
309
+ v[0].standard_name if isinstance(v, tuple) else v.standard_name: v[0].short_name
310
+ if isinstance(v, tuple)
311
+ else v.short_name
312
+ for v in self.met_variables
313
+ }
314
+ ds_met = ds_met.rename(name_dict)
315
+ else:
316
+ ds_met = None
317
+
318
+ if hasattr(self, "surface"):
319
+ ds_sur = self.surface.data.squeeze().transpose("time", "latitude", "longitude")
320
+ name_dict = {v.standard_name: v.short_name for v in self.sur_variables}
321
+ ds_sur = ds_sur.rename(name_dict)
322
+ else:
323
+ ds_sur = None
324
+
325
+ ws = WeatherStore(
326
+ ds_met,
327
+ ds_sur,
328
+ ll_resolution=p_settings["horizontal_resolution"],
329
+ forecast_step=p_settings["forecast_step"],
330
+ )
331
+
332
+ if p_settings["lat_bound"] and p_settings["lon_bound"]:
333
+ ws.reduce_domain(
334
+ {
335
+ "latitude": p_settings["lat_bound"],
336
+ "longitude": p_settings["lon_bound"],
337
+ }
338
+ )
339
+
340
+ self.ds = ws.get_xarray()
341
+ self.variable_names = ws.variable_names
342
+ self.pre_variable_names = ws.pre_variable_names
343
+ self.coordinate_names = ws.coordinate_names
344
+ self.pre_coordinate_names = ws.pre_coordinate_names
345
+ self.coordinates_bool = ws.coordinates_bool
346
+ self.aCCF_bool = ws.aCCF_bool
347
+ self.axes = ws.axes
348
+ self.var_xr = ws.var_xr
349
+
350
+
351
+ def _get_accf_config(params: dict[str, Any]) -> dict[str, Any]:
352
+ # a good portion of these will get ignored since we are not producing an
353
+ # output file, but the library will complain if they aren't defined
354
+ return {
355
+ "lat_bound": params["lat_bound"],
356
+ "lon_bound": params["lon_bound"],
357
+ "time_bound": None,
358
+ "horizontal_resolution": params["horizontal_resolution"],
359
+ "forecast_step": params["forecast_step"],
360
+ "NOx_aCCF": True,
361
+ "NOx_EI&F_km": params["nox_ei"],
362
+ "output_format": "netCDF",
363
+ "mean": False,
364
+ "std": False,
365
+ "merged": params["merged"],
366
+ "aCCF-V": params["accf_v"],
367
+ "efficacy": params["efficacy"],
368
+ "efficacy-option": params["efficacy_option"],
369
+ "emission_scenario": params["emission_scenario"],
370
+ "climate_indicator": params["climate_indicator"],
371
+ "TimeHorizon": params["time_horizon"],
372
+ "ac_type": "wide-body",
373
+ "sep_ri_rw": params["sep_ri_rw"],
374
+ "PMO": params["PMO"],
375
+ "aCCF-scalingF": {
376
+ "CH4": params["ch4_scaling"],
377
+ "CO2": params["co2_scaling"],
378
+ "Cont.": params["cont_scaling"],
379
+ "H2O": params["h2o_scaling"],
380
+ "O3": params["o3_scaling"],
381
+ },
382
+ "unit_K/kg(fuel)": params["unit_K_per_kg_fuel"],
383
+ "PCFA": params["pfca"],
384
+ "PCFA-ISSR": {
385
+ "rhi_threshold": params["issr_rhi_threshold"],
386
+ "temp_threshold": params["issr_temp_threshold"],
387
+ },
388
+ "PCFA-SAC": {
389
+ "EI_H2O": params["sac_ei_h2o"],
390
+ "Q": params["sac_q"],
391
+ "eta": params["sac_eta"],
392
+ },
393
+ "Chotspots": False,
394
+ "hotspots_binary": True,
395
+ "color": "Reds",
396
+ "geojson": False,
397
+ "save_path": "./",
398
+ "save_format": "netCDF",
399
+ }
400
+
401
+
402
+ def _rad_instantaneous_to_accumulated(ds: xr.Dataset) -> xr.Dataset:
403
+ """Convert instantaneous radiation to accumulated radiation."""
404
+
405
+ for name, da in ds.items():
406
+ try:
407
+ unit = da.attrs["units"]
408
+ except KeyError as e:
409
+ msg = (
410
+ f"Radiation data contains '{name}' variable "
411
+ "but units are not specified. Provide units in the "
412
+ f"rad['{name}'].attrs passed into ACCF."
413
+ )
414
+ raise KeyError(msg) from e
415
+
416
+ if unit == "J m**-2":
417
+ continue
418
+ if unit != "W m**-2":
419
+ msg = f"Unexpected units '{unit}' for '{name}'. Expected 'J m**-2' or 'W m**-2'."
420
+ raise ValueError(msg)
421
+
422
+ # Convert from W m**-2 to J m**-2
423
+ ds[name] = da.assign_attrs(units="J m**-2") * 3600.0
424
+
425
+ return ds
@@ -0,0 +1,8 @@
1
+ """Contrail Cirrus Prediction (CoCiP) modeling support."""
2
+
3
+ from pycontrails.models.apcemm.apcemm import APCEMM, APCEMMParams
4
+
5
+ __all__ = [
6
+ "APCEMM",
7
+ "APCEMMParams",
8
+ ]