musica 0.14.4__cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.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.
- musica/__init__.py +11 -0
- musica/_musica.cpython-310-aarch64-linux-gnu.so +0 -0
- musica/_version.py +1 -0
- musica/backend.py +58 -0
- musica/carma/__init__.py +20 -0
- musica/carma/carma.py +1727 -0
- musica/constants.py +3 -0
- musica/cuda.py +13 -0
- musica/examples/__init__.py +1 -0
- musica/examples/carma_aluminum.py +124 -0
- musica/examples/carma_sulfate.py +246 -0
- musica/examples/examples.py +175 -0
- musica/examples/lorenz.py +295 -0
- musica/examples/sulfate_box_model.py +439 -0
- musica/examples/ts1_latin_hypercube.py +245 -0
- musica/main.py +128 -0
- musica/mechanism_configuration/__init__.py +18 -0
- musica/mechanism_configuration/ancillary.py +6 -0
- musica/mechanism_configuration/arrhenius.py +149 -0
- musica/mechanism_configuration/branched.py +140 -0
- musica/mechanism_configuration/emission.py +82 -0
- musica/mechanism_configuration/first_order_loss.py +90 -0
- musica/mechanism_configuration/mechanism.py +93 -0
- musica/mechanism_configuration/phase.py +58 -0
- musica/mechanism_configuration/phase_species.py +58 -0
- musica/mechanism_configuration/photolysis.py +98 -0
- musica/mechanism_configuration/reaction_component.py +54 -0
- musica/mechanism_configuration/reactions.py +32 -0
- musica/mechanism_configuration/species.py +65 -0
- musica/mechanism_configuration/surface.py +98 -0
- musica/mechanism_configuration/taylor_series.py +136 -0
- musica/mechanism_configuration/ternary_chemical_activation.py +160 -0
- musica/mechanism_configuration/troe.py +160 -0
- musica/mechanism_configuration/tunneling.py +126 -0
- musica/mechanism_configuration/user_defined.py +99 -0
- musica/mechanism_configuration/utils.py +10 -0
- musica/micm/__init__.py +10 -0
- musica/micm/conditions.py +49 -0
- musica/micm/micm.py +135 -0
- musica/micm/solver.py +8 -0
- musica/micm/solver_result.py +24 -0
- musica/micm/state.py +220 -0
- musica/micm/utils.py +18 -0
- musica/tuvx/__init__.py +11 -0
- musica/tuvx/grid.py +98 -0
- musica/tuvx/grid_map.py +167 -0
- musica/tuvx/profile.py +130 -0
- musica/tuvx/profile_map.py +167 -0
- musica/tuvx/radiator.py +95 -0
- musica/tuvx/radiator_map.py +173 -0
- musica/tuvx/tuvx.py +283 -0
- musica-0.14.4.dist-info/METADATA +427 -0
- musica-0.14.4.dist-info/RECORD +92 -0
- musica-0.14.4.dist-info/WHEEL +6 -0
- musica-0.14.4.dist-info/entry_points.txt +3 -0
- musica-0.14.4.dist-info/licenses/AUTHORS.md +59 -0
- musica-0.14.4.dist-info/licenses/LICENSE +201 -0
- musica.libs/libaec-34bb4966.so.0.0.8 +0 -0
- musica.libs/libblas-8ed0a6f9.so.3.8.0 +0 -0
- musica.libs/libbrotlicommon-b6e6c8bd.so.1.0.6 +0 -0
- musica.libs/libbrotlidec-5094ef0a.so.1.0.6 +0 -0
- musica.libs/libcom_err-6d8d18aa.so.2.1 +0 -0
- musica.libs/libcrypt-258f54d5.so.1.1.0 +0 -0
- musica.libs/libcrypto-3eda328c.so.1.1.1k +0 -0
- musica.libs/libcurl-7faeef02.so.4.5.0 +0 -0
- musica.libs/libdf-9661c601.so.0.0.0 +0 -0
- musica.libs/libgfortran-e1b7dfc8.so.5.0.0 +0 -0
- musica.libs/libgssapi_krb5-fe951f80.so.2.2 +0 -0
- musica.libs/libhdf5-463e48d5.so.103.1.0 +0 -0
- musica.libs/libhdf5_hl-74316838.so.100.1.2 +0 -0
- musica.libs/libidn2-1b2a13b7.so.0.3.6 +0 -0
- musica.libs/libjpeg-ee25248c.so.62.2.0 +0 -0
- musica.libs/libk5crypto-84470bb3.so.3.1 +0 -0
- musica.libs/libkeyutils-fe6e95a9.so.1.6 +0 -0
- musica.libs/libkrb5-26ef5d84.so.3.3 +0 -0
- musica.libs/libkrb5support-875e89dc.so.0.1 +0 -0
- musica.libs/liblapack-8d137073.so.3.8.0 +0 -0
- musica.libs/liblber-2-86b08e65.4.so.2.10.9 +0 -0
- musica.libs/libldap-2-5c1dd279.4.so.2.10.9 +0 -0
- musica.libs/libmfhdf-9c336c5f.so.0.0.0 +0 -0
- musica.libs/libnetcdf-71a067be.so.15.0.1 +0 -0
- musica.libs/libnetcdff-6a455dd4.so.7.0.0 +0 -0
- musica.libs/libnghttp2-3a94c239.so.14.17.0 +0 -0
- musica.libs/libpcre2-8-8701a61e.so.0.7.1 +0 -0
- musica.libs/libpsl-130094ea.so.5.3.1 +0 -0
- musica.libs/libsasl2-076b3c1f.so.3.0.0 +0 -0
- musica.libs/libselinux-5700a1fd.so.1 +0 -0
- musica.libs/libssh-e0d3bd94.so.4.8.7 +0 -0
- musica.libs/libssl-f60bf0e2.so.1.1.1k +0 -0
- musica.libs/libsz-81b556a2.so.2.0.1 +0 -0
- musica.libs/libtirpc-1fa9018c.so.3.0.0 +0 -0
- musica.libs/libunistring-be03fd41.so.2.1.0 +0 -0
musica/carma/carma.py
ADDED
|
@@ -0,0 +1,1727 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CARMA aerosol model Python interface.
|
|
3
|
+
|
|
4
|
+
This module provides a simplified Python interface to the CARMA aerosol model.
|
|
5
|
+
It allows users to create a CARMA instance and run simulations with specified parameters.
|
|
6
|
+
|
|
7
|
+
Note: CARMA is only available on macOS and Linux platforms.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Dict, Optional, List, Union, Any, Tuple
|
|
11
|
+
from ctypes import c_void_p
|
|
12
|
+
import numpy as np
|
|
13
|
+
import xarray as xr
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from .. import backend
|
|
16
|
+
|
|
17
|
+
_backend = backend.get_backend()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ParticleShape(Enum):
|
|
21
|
+
"""Enumeration for particle shapes used in CARMA."""
|
|
22
|
+
SPHERE = 1
|
|
23
|
+
HEXAGON = 2
|
|
24
|
+
CYLINDER = 3
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ParticleType(Enum):
|
|
28
|
+
"""Enumeration for particle types used in CARMA."""
|
|
29
|
+
INVOLATILE = 1
|
|
30
|
+
VOLATILE = 2
|
|
31
|
+
CORE_MASS = 3
|
|
32
|
+
VOLATILE_CORE = 4
|
|
33
|
+
CORE_MASS_TWO_MOMENTS = 5
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ParticleSwellingAlgorithm(Enum):
|
|
37
|
+
"""Enumeration for particle swelling algorithms used in CARMA."""
|
|
38
|
+
NONE = 0
|
|
39
|
+
FITZGERALD = 1
|
|
40
|
+
GERBER = 2
|
|
41
|
+
WEIGHT_PERCENT_H2SO4 = 3
|
|
42
|
+
PETTERS = 4
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ParticleSwellingComposition(Enum):
|
|
46
|
+
"""Enumeration for particle swelling compositions used in CARMA."""
|
|
47
|
+
NONE = 0
|
|
48
|
+
AMMONIUM_SULFATE = 1
|
|
49
|
+
SEA_SALT = 2
|
|
50
|
+
URBAN = 3
|
|
51
|
+
RURAL = 4
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ParticleFallVelocityAlgorithm(Enum):
|
|
55
|
+
"""Enumeration for particle fall velocity algorithms used in CARMA."""
|
|
56
|
+
NONE = 0
|
|
57
|
+
STANDARD_SPHERICAL_ONLY = 1
|
|
58
|
+
STANDARD_SHAPE_SUPPORT = 2
|
|
59
|
+
HEYMSFIELD_2010 = 3
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MieCalculationAlgorithm(Enum):
|
|
63
|
+
"""Enumeration for Mie calculation algorithms used in CARMA."""
|
|
64
|
+
TOON_1981 = 1
|
|
65
|
+
BOHREN_1983 = 2
|
|
66
|
+
BOTET_1997 = 3
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class OpticsAlgorithm(Enum):
|
|
70
|
+
"""Enumeration for optics algorithms used in CARMA."""
|
|
71
|
+
NONE = 0
|
|
72
|
+
FIXED = 1
|
|
73
|
+
MIXED_YU_2015 = 2
|
|
74
|
+
SULFATE_YU_2015 = 3
|
|
75
|
+
MIXED_H2O_YU_2015 = 4
|
|
76
|
+
MIXED_CORE_SHELL = 5
|
|
77
|
+
MIXED_VOLUME = 6
|
|
78
|
+
MIXED_MAXWELL = 7
|
|
79
|
+
SULFATE = 8
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VaporizationAlgorithm(Enum):
|
|
83
|
+
"""Enumeration for vaporization algorithms used in CARMA."""
|
|
84
|
+
NONE = 0
|
|
85
|
+
H2O_BUCK_1981 = 1
|
|
86
|
+
H2O_MURPHY_2005 = 2
|
|
87
|
+
H2O_GOFF_1946 = 3
|
|
88
|
+
H2SO4_AYERS_1980 = 4
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class GasComposition(Enum):
|
|
92
|
+
"""Enumeration for gas compositions used in CARMA."""
|
|
93
|
+
NONE = 0
|
|
94
|
+
H2O = 1
|
|
95
|
+
H2SO4 = 2
|
|
96
|
+
SO2 = 3
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ParticleComposition(Enum):
|
|
100
|
+
"""Enumeration for particle compositions used in CARMA."""
|
|
101
|
+
ALUMINUM = 1
|
|
102
|
+
SULFURIC_ACID = 2
|
|
103
|
+
DUST = 3
|
|
104
|
+
ICE = 4
|
|
105
|
+
WATER = 5
|
|
106
|
+
BLACK_CARBON = 6
|
|
107
|
+
ORGANIC_CARBON = 7
|
|
108
|
+
OTHER = 8
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ParticleCollectionAlgorithm(Enum):
|
|
112
|
+
"""Enumeration for particle collection algorithms used in CARMA."""
|
|
113
|
+
NONE = 0
|
|
114
|
+
CONSTANT = 1
|
|
115
|
+
FUCHS = 2
|
|
116
|
+
DATA = 3
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class ParticleNucleationAlgorithm(Enum):
|
|
120
|
+
"""Enumeration for particle nucleation algorithms used in CARMA."""
|
|
121
|
+
NONE = 0
|
|
122
|
+
AEROSOL_FREEZING_TABAZDEH_2000 = 1
|
|
123
|
+
AEROSOL_FREEZING_KOOP_2000 = 2
|
|
124
|
+
AEROSOL_FREEZING_MURRAY_2010 = 3
|
|
125
|
+
DROPLET_ACTIVATION = 256
|
|
126
|
+
AEROSOL_FREEZING = 512
|
|
127
|
+
DROPLET_FREEZING = 1024
|
|
128
|
+
ICE_MELTING = 2048
|
|
129
|
+
HETEROGENEOUS_NUCLEATION = 4096
|
|
130
|
+
HOMOGENEOUS_NUCLEATION = 8192
|
|
131
|
+
HETEROGENEOUS_SULFURIC_ACID_NUCLEATION = 16384
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SulfateNucleationMethod(Enum):
|
|
135
|
+
"""Enumeration for sulfate nucleation methods used in CARMA."""
|
|
136
|
+
NONE = 0
|
|
137
|
+
ZHAO_TURCO = 1
|
|
138
|
+
VEHKAMAKI = 2
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class CarmaCoordinates(Enum):
|
|
142
|
+
"""Enumeration for CARMA coordinates."""
|
|
143
|
+
CARTESIAN = 1
|
|
144
|
+
SIGMA = 2
|
|
145
|
+
LONGITUDE_LATITUDE = 3
|
|
146
|
+
LAMBERT_CONFORMAL = 4
|
|
147
|
+
POLAR_STEREOGRAPHIC = 5
|
|
148
|
+
MERCATOR = 6
|
|
149
|
+
HYBRID = 7
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class CARMAWavelengthBin:
|
|
153
|
+
"""Configuration for a CARMA wavelength bin.
|
|
154
|
+
|
|
155
|
+
A CARMA wavelength bin represents a specific wavelength range used in optical calculations.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self, center: float, width: float, do_emission: bool = True):
|
|
159
|
+
"""
|
|
160
|
+
Initialize a CARMA wavelength bin.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
center: Center wavelength in micrometers.
|
|
164
|
+
width: Width of the wavelength bin in micrometers.
|
|
165
|
+
do_emission: Whether to include this wavelength in emission calculations (default: True).
|
|
166
|
+
"""
|
|
167
|
+
self.center = center
|
|
168
|
+
self.width = width
|
|
169
|
+
self.do_emission = do_emission
|
|
170
|
+
|
|
171
|
+
def to_dict(self) -> Dict:
|
|
172
|
+
"""Convert to dictionary."""
|
|
173
|
+
return {k: v for k, v in self.__dict__.items()}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class CARMAGroupConfig:
|
|
177
|
+
"""Configuration for a CARMA particle group.
|
|
178
|
+
|
|
179
|
+
A CARMA particle group represents a collection of particles with similar properties.
|
|
180
|
+
"""
|
|
181
|
+
|
|
182
|
+
def __init__(self,
|
|
183
|
+
name: str = "default_group",
|
|
184
|
+
shortname: str = "",
|
|
185
|
+
rmin: float = 1e-9,
|
|
186
|
+
rmrat: float = 2.0,
|
|
187
|
+
rmassmin: float = 0.0,
|
|
188
|
+
ishape: int = ParticleShape.SPHERE,
|
|
189
|
+
eshape: float = 1.0,
|
|
190
|
+
swelling_approach: dict = {
|
|
191
|
+
"algorithm": ParticleSwellingAlgorithm.NONE,
|
|
192
|
+
"composition": ParticleSwellingComposition.NONE
|
|
193
|
+
},
|
|
194
|
+
fall_velocity_routine: int = ParticleFallVelocityAlgorithm.STANDARD_SPHERICAL_ONLY,
|
|
195
|
+
mie_calculation_algorithm: int = MieCalculationAlgorithm.TOON_1981,
|
|
196
|
+
optics_algorithm: int = OpticsAlgorithm.FIXED,
|
|
197
|
+
is_ice: bool = False,
|
|
198
|
+
is_fractal: bool = False,
|
|
199
|
+
is_cloud: bool = False,
|
|
200
|
+
is_sulfate: bool = False,
|
|
201
|
+
do_wetdep: bool = False,
|
|
202
|
+
do_drydep: bool = False,
|
|
203
|
+
do_vtran: bool = True,
|
|
204
|
+
solfac: float = 0.3,
|
|
205
|
+
scavcoef: float = 0.1,
|
|
206
|
+
dpc_threshold: float = 0.0,
|
|
207
|
+
rmon: float = 0.0,
|
|
208
|
+
df: Optional[List[float]] = None,
|
|
209
|
+
falpha: float = 1.0,
|
|
210
|
+
neutral_volfrc: float = 0.0):
|
|
211
|
+
"""
|
|
212
|
+
Initialize a CARMA group configuration.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
name: Name of the group (default: "default_group")
|
|
216
|
+
shortname: Short name for the group (default: "")
|
|
217
|
+
rmin: Radius of particles in the first bin [m] (default: 1e-9)
|
|
218
|
+
rmrat: Ratio of masses of particles in consecutive bins (default: 2.0)
|
|
219
|
+
rmassmin: Minimum mass of particles [kg] (default: 0.0)
|
|
220
|
+
ishape: Shape of the particles (default: ParticleShape.SPHERE)
|
|
221
|
+
eshape: Ratio of particle length / diameter (default: 1.0)
|
|
222
|
+
swelling_approach: Dictionary specifying swelling algorithm and composition (default: NONE)
|
|
223
|
+
fall_velocity_routine: Algorithm for fall velocity (default: STANDARD_SPHERICAL_ONLY)
|
|
224
|
+
mie_calculation_algorithm: Algorithm for Mie calculations (default: TOON_1981)
|
|
225
|
+
optics_algorithm: Algorithm for optics (default: FIXED)
|
|
226
|
+
is_ice: Whether the particles are ice (default: False)
|
|
227
|
+
is_fractal: Whether the particles are fractal (default: False)
|
|
228
|
+
is_cloud: Whether the group is a cloud (default: False)
|
|
229
|
+
is_sulfate: Whether the group is sulfate (default: False)
|
|
230
|
+
do_wetdep: Whether to include wet deposition (default: False)
|
|
231
|
+
do_drydep: Whether to include dry deposition (default: False)
|
|
232
|
+
do_vtran: Whether to include vertical transport (default: True)
|
|
233
|
+
solfac: Solubility factor for wet deposition (default: 0.3)
|
|
234
|
+
scavcoef: Scavenging coefficient for wet deposition (default: 0.1)
|
|
235
|
+
dpc_threshold: Threshold for dry particle collection (default: 0.0)
|
|
236
|
+
rmon: Monomer radius of fractal particles [m] (default: 0.0)
|
|
237
|
+
df: List of fractal dimensions for each size bin (default: None)
|
|
238
|
+
falpha: Fractal packing coefficient (default: 1.0)
|
|
239
|
+
neutral_volfrc: Neutral volume fraction for fractal particles (default: 0.0)
|
|
240
|
+
"""
|
|
241
|
+
self.name = name
|
|
242
|
+
self.shortname = shortname
|
|
243
|
+
self.rmin = rmin
|
|
244
|
+
self.rmrat = rmrat
|
|
245
|
+
self.rmassmin = rmassmin
|
|
246
|
+
self.ishape = ishape
|
|
247
|
+
self.eshape = eshape
|
|
248
|
+
self.swelling_approach = swelling_approach
|
|
249
|
+
self.fall_velocity_routine = fall_velocity_routine
|
|
250
|
+
self.mie_calculation_algorithm = mie_calculation_algorithm
|
|
251
|
+
self.optics_algorithm = optics_algorithm
|
|
252
|
+
self.is_ice = is_ice
|
|
253
|
+
self.is_fractal = is_fractal
|
|
254
|
+
self.is_cloud = is_cloud
|
|
255
|
+
self.is_sulfate = is_sulfate
|
|
256
|
+
self.do_wetdep = do_wetdep
|
|
257
|
+
self.do_drydep = do_drydep
|
|
258
|
+
self.do_vtran = do_vtran
|
|
259
|
+
self.solfac = solfac
|
|
260
|
+
self.scavcoef = scavcoef
|
|
261
|
+
self.dpc_threshold = dpc_threshold
|
|
262
|
+
self.rmon = rmon
|
|
263
|
+
self.df = df or []
|
|
264
|
+
self.falpha = falpha
|
|
265
|
+
self.neutral_volfrc = neutral_volfrc
|
|
266
|
+
|
|
267
|
+
def to_dict(self) -> Dict:
|
|
268
|
+
"""Convert to dictionary, serializing enums in swelling_approach as well."""
|
|
269
|
+
result = {}
|
|
270
|
+
for k, v in self.__dict__.items():
|
|
271
|
+
if k == "swelling_approach" and isinstance(v, dict):
|
|
272
|
+
# Serialize enum values inside swelling_approach dict
|
|
273
|
+
result[k] = {sk: (sv.value if isinstance(sv, Enum) else sv)
|
|
274
|
+
for sk, sv in v.items()}
|
|
275
|
+
else:
|
|
276
|
+
result[k] = v.value if isinstance(v, Enum) else v
|
|
277
|
+
return result
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class CARMAElementConfig:
|
|
281
|
+
"""Configuration for a CARMA particle element.
|
|
282
|
+
|
|
283
|
+
A CARMA particle element represents one of the components of a cloud or aerosol particle.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
def __init__(self,
|
|
287
|
+
igroup: int = 1,
|
|
288
|
+
isolute: int = 0,
|
|
289
|
+
name: str = "default_element",
|
|
290
|
+
shortname: str = "",
|
|
291
|
+
itype: int = ParticleType.INVOLATILE,
|
|
292
|
+
icomposition: int = ParticleComposition.OTHER,
|
|
293
|
+
is_shell: bool = True,
|
|
294
|
+
rho: float = 1000.0,
|
|
295
|
+
rhobin: Optional[List[float]] = None,
|
|
296
|
+
arat: Optional[List[float]] = None,
|
|
297
|
+
kappa: float = 0.0,
|
|
298
|
+
refidx: Optional[List[List[float]]] = None):
|
|
299
|
+
"""
|
|
300
|
+
Initialize a CARMA element configuration.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
igroup: Group ID this element belongs to (default: 1)
|
|
304
|
+
isolute: Index of the solute (default: 0)
|
|
305
|
+
name: Name of the element (default: "default_element")
|
|
306
|
+
shortname: Short name for the element (default: "")
|
|
307
|
+
itype: Type of the particle (default: ParticleType.INVOLATILE)
|
|
308
|
+
icomposition: Composition of the particle (default: ParticleComposition.OTHER)
|
|
309
|
+
is_shell: For core/shell optics, whether this element is part of the shell (True) or core (False) (default: True)
|
|
310
|
+
rho: Density of the element in kg/m3 (default: 1.0)
|
|
311
|
+
rhobin: List of densities for each size bin in kg/m3 (default: None)
|
|
312
|
+
arat: List of area ratios for each size bin (default: None)
|
|
313
|
+
kappa: Hygroscopicity parameter (default: 0.0)
|
|
314
|
+
refidx: List of lists of refractive indices for each wavelength bin (default: None)
|
|
315
|
+
"""
|
|
316
|
+
self.igroup = igroup
|
|
317
|
+
self.isolute = isolute
|
|
318
|
+
self.name = name
|
|
319
|
+
self.shortname = shortname
|
|
320
|
+
self.itype = itype
|
|
321
|
+
self.icomposition = icomposition
|
|
322
|
+
self.is_shell = is_shell
|
|
323
|
+
self.rho = rho
|
|
324
|
+
self.rhobin = rhobin or []
|
|
325
|
+
self.arat = arat or []
|
|
326
|
+
self.kappa = kappa
|
|
327
|
+
self.refidx = refidx or []
|
|
328
|
+
|
|
329
|
+
def to_dict(self) -> Dict:
|
|
330
|
+
"""Convert to dictionary."""
|
|
331
|
+
return {k: (v.value if isinstance(v, Enum) else v) for k, v in self.__dict__.items()}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class CARMASoluteConfig:
|
|
335
|
+
"""Configuration for a CARMA solute.
|
|
336
|
+
|
|
337
|
+
A CARMA solute represents a chemical species that can dissolve in water and affect particle properties.
|
|
338
|
+
"""
|
|
339
|
+
|
|
340
|
+
def __init__(self,
|
|
341
|
+
name: str = "default_solute",
|
|
342
|
+
shortname: str = "",
|
|
343
|
+
ions: int = 0,
|
|
344
|
+
wtmol: float = 0.0,
|
|
345
|
+
rho: float = 0.0):
|
|
346
|
+
"""
|
|
347
|
+
Initialize a CARMA solute configuration.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
name: Name of the solute (default: "default_solute")
|
|
351
|
+
shortname: Short name for the solute (default: "")
|
|
352
|
+
ions: Number of ions (default: 0)
|
|
353
|
+
wtmol: Molecular weight in kg/mol (default: 0.0)
|
|
354
|
+
rho: Density in kg/m3 (default: 0.0)
|
|
355
|
+
"""
|
|
356
|
+
self.name = name
|
|
357
|
+
self.shortname = shortname
|
|
358
|
+
self.ions = ions
|
|
359
|
+
self.wtmol = wtmol
|
|
360
|
+
self.rho = rho
|
|
361
|
+
|
|
362
|
+
def to_dict(self) -> Dict:
|
|
363
|
+
"""Convert to dictionary."""
|
|
364
|
+
return {k: v for k, v in self.__dict__.items()}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class CARMAGasConfig:
|
|
368
|
+
"""Configuration for a CARMA gas.
|
|
369
|
+
|
|
370
|
+
A CARMA gas represents a gaseous species in the atmosphere.
|
|
371
|
+
"""
|
|
372
|
+
|
|
373
|
+
def __init__(self,
|
|
374
|
+
name: str = "default_gas",
|
|
375
|
+
shortname: str = "",
|
|
376
|
+
wtmol: float = 0.0,
|
|
377
|
+
ivaprtn: VaporizationAlgorithm = VaporizationAlgorithm.NONE,
|
|
378
|
+
icomposition: GasComposition = GasComposition.NONE,
|
|
379
|
+
dgc_threshold: float = 0.0,
|
|
380
|
+
ds_threshold: float = 0.0,
|
|
381
|
+
refidx: Optional[List[List[float]]] = None):
|
|
382
|
+
"""
|
|
383
|
+
Initialize a CARMA gas configuration.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
name: Name of the gas (default: "default_gas")
|
|
387
|
+
shortname: Short name for the gas (default: "")
|
|
388
|
+
wtmol: Molecular weight in kg/mol (default: 0.0)
|
|
389
|
+
ivaprtn: Vaporization algorithm used for this gas (default: VaporizationAlgorithm.NONE)
|
|
390
|
+
icomposition: Composition of the gas (default: GasComposition.NONE)
|
|
391
|
+
dgc_threshold: Threshold for gas density gradient (default: 0.0)
|
|
392
|
+
ds_threshold: Threshold for gas saturation (default: 0.0)
|
|
393
|
+
refidx: Reference indices for gas (default: None)
|
|
394
|
+
"""
|
|
395
|
+
self.name = name
|
|
396
|
+
self.shortname = shortname
|
|
397
|
+
self.wtmol = wtmol
|
|
398
|
+
self.ivaprtn = ivaprtn
|
|
399
|
+
self.icomposition = icomposition
|
|
400
|
+
self.dgc_threshold = dgc_threshold
|
|
401
|
+
self.ds_threshold = ds_threshold
|
|
402
|
+
self.refidx = refidx or []
|
|
403
|
+
|
|
404
|
+
def to_dict(self) -> Dict:
|
|
405
|
+
"""Convert to dictionary."""
|
|
406
|
+
return {k: (v.value if isinstance(v, Enum) else v) for k, v in self.__dict__.items()}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class CARMACoagulationConfig:
|
|
410
|
+
"""Configuration for CARMA coagulation process.
|
|
411
|
+
|
|
412
|
+
This class defines how particles coagulate in the CARMA model.
|
|
413
|
+
"""
|
|
414
|
+
|
|
415
|
+
def __init__(self,
|
|
416
|
+
igroup1: int = 1,
|
|
417
|
+
igroup2: int = 1,
|
|
418
|
+
igroup3: int = 1,
|
|
419
|
+
algorithm: int = ParticleCollectionAlgorithm.CONSTANT,
|
|
420
|
+
ck0: float = -1.0,
|
|
421
|
+
grav_e_coll0: float = 0.0,
|
|
422
|
+
use_ccd: bool = False):
|
|
423
|
+
"""
|
|
424
|
+
Initialize a CARMA coagulation configuration.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
igroup1: First group index (default: 1)
|
|
428
|
+
igroup2: Second group index (default: 1)
|
|
429
|
+
igroup3: Third group index (default: 1)
|
|
430
|
+
algorithm: Coagulation algorithm (default: ParticleCollectionAlgorithm.CONSTANT)
|
|
431
|
+
ck0: Collection efficiency constant (default: -1.0). If -1.0, it will not be specified when setting up the coagulation process in carma
|
|
432
|
+
grav_e_coll0: Gravitational collection efficiency constant (default: 0.0)
|
|
433
|
+
use_ccd: Whether to use constant collection efficiency data (default: False)
|
|
434
|
+
"""
|
|
435
|
+
self.igroup1 = igroup1
|
|
436
|
+
self.igroup2 = igroup2
|
|
437
|
+
self.igroup3 = igroup3
|
|
438
|
+
self.algorithm = algorithm
|
|
439
|
+
self.ck0 = ck0
|
|
440
|
+
self.grav_e_coll0 = grav_e_coll0
|
|
441
|
+
self.use_ccd = use_ccd
|
|
442
|
+
|
|
443
|
+
def to_dict(self) -> Dict:
|
|
444
|
+
"""Convert to dictionary."""
|
|
445
|
+
return {k: (v.value if isinstance(v, Enum) else v) for k, v in self.__dict__.items()}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class CARMAGrowthConfig:
|
|
449
|
+
"""Configuration for CARMA particle growth process.
|
|
450
|
+
|
|
451
|
+
This class defines how particles grow in the CARMA model.
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
def __init__(self,
|
|
455
|
+
ielem: int = 0,
|
|
456
|
+
igas: int = 0):
|
|
457
|
+
"""
|
|
458
|
+
Initialize a CARMA growth configuration.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
ielem: Element index for the particles (default: 0)
|
|
462
|
+
igas: Index of the gas (default: 0)
|
|
463
|
+
"""
|
|
464
|
+
self.ielem = ielem
|
|
465
|
+
self.igas = igas
|
|
466
|
+
|
|
467
|
+
def to_dict(self) -> Dict:
|
|
468
|
+
"""Convert to dictionary."""
|
|
469
|
+
return {k: (v.value if isinstance(v, Enum) else v) for k, v in self.__dict__.items()}
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class CARMANucleationConfig:
|
|
473
|
+
"""Configuration for CARMA particle nucleation process.
|
|
474
|
+
|
|
475
|
+
This class defines how new particles are formed in the CARMA model.
|
|
476
|
+
"""
|
|
477
|
+
|
|
478
|
+
def __init__(self,
|
|
479
|
+
ielemfrom: int = 0,
|
|
480
|
+
ielemto: int = 0,
|
|
481
|
+
algorithm: ParticleNucleationAlgorithm = ParticleNucleationAlgorithm.NONE,
|
|
482
|
+
rlh_nuc: float = 0.0,
|
|
483
|
+
igas: int = 0,
|
|
484
|
+
ievp2elem: int = 0):
|
|
485
|
+
"""
|
|
486
|
+
Initialize a CARMA nucleation configuration.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
ielemfrom: Element index to nucleate from (default: 0)
|
|
490
|
+
ielemto: Element index to nucleate to (default: 0)
|
|
491
|
+
algorithm: Nucleation algorithm (default: ParticleNucleationAlgorithm.NONE)
|
|
492
|
+
rlh_nuc: Latent heat of nucleation [m2 s-2] (default: 0.0)
|
|
493
|
+
igas: Gas index to nucleate from (default: 0)
|
|
494
|
+
ievp2elem: Element index to evaporate to (if applicable) (default: 0)
|
|
495
|
+
"""
|
|
496
|
+
self.ielemfrom = ielemfrom
|
|
497
|
+
self.ielemto = ielemto
|
|
498
|
+
self.algorithm = algorithm
|
|
499
|
+
self.rlh_nuc = rlh_nuc
|
|
500
|
+
self.igas = igas
|
|
501
|
+
self.ievp2elem = ievp2elem
|
|
502
|
+
|
|
503
|
+
def to_dict(self) -> Dict:
|
|
504
|
+
"""Convert to dictionary."""
|
|
505
|
+
return {k: (v.value if isinstance(v, Enum) else v) for k, v in self.__dict__.items()}
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
class CARMAInitializationConfig:
|
|
509
|
+
"""Configuration for CARMA initialization.
|
|
510
|
+
|
|
511
|
+
This class defines how the CARMA model is initialized before running simulations.
|
|
512
|
+
"""
|
|
513
|
+
|
|
514
|
+
def __init__(self,
|
|
515
|
+
do_cnst_rlh: bool = False,
|
|
516
|
+
do_detrain: bool = False,
|
|
517
|
+
do_fixedinit: bool = False,
|
|
518
|
+
do_incloud: bool = False,
|
|
519
|
+
do_explised: bool = False,
|
|
520
|
+
do_substep: bool = False,
|
|
521
|
+
do_thermo: bool = False,
|
|
522
|
+
do_vdiff: bool = False,
|
|
523
|
+
do_vtran: bool = False,
|
|
524
|
+
do_drydep: bool = False,
|
|
525
|
+
do_pheat: bool = False,
|
|
526
|
+
do_pheatatm: bool = False,
|
|
527
|
+
do_clearsky: bool = False,
|
|
528
|
+
do_partialinit: bool = False,
|
|
529
|
+
do_coremasscheck: bool = False,
|
|
530
|
+
sulfnucl_method: SulfateNucleationMethod = SulfateNucleationMethod.NONE,
|
|
531
|
+
vf_const: float = 0.0,
|
|
532
|
+
minsubsteps: int = 1,
|
|
533
|
+
maxsubsteps: int = 1,
|
|
534
|
+
maxretries: int = 5,
|
|
535
|
+
conmax: float = 1.0e-1,
|
|
536
|
+
dt_threshold: float = 0.0,
|
|
537
|
+
cstick: float = 1.0,
|
|
538
|
+
gsticki: float = 0.93,
|
|
539
|
+
gstickl: float = 1.0,
|
|
540
|
+
tstick: float = 1.0):
|
|
541
|
+
"""
|
|
542
|
+
Initialize a CARMA initialization configuration.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
do_cnst_rlh: Use constant values for latent heats (default: False)
|
|
546
|
+
do_detrain: Do detrainment (default: False)
|
|
547
|
+
do_fixedinit: Use fixed initialization from reference atmosphere (default: False)
|
|
548
|
+
do_incloud: Do in-cloud processes (growth, coagulation) (default: False)
|
|
549
|
+
do_explised: Do sedimentation with substepping (default: False)
|
|
550
|
+
do_substep: Do substepping (default: False)
|
|
551
|
+
do_thermo: Do thermodynamic processes (default: False)
|
|
552
|
+
do_vdiff: Do Brownian diffusion (default: False)
|
|
553
|
+
do_vtran: Do sedimentation (default: True)
|
|
554
|
+
do_drydep: Do dry deposition (default: False)
|
|
555
|
+
do_pheat: Do particle heating (default: False)
|
|
556
|
+
do_pheatatm: Do particle heating of atmosphere (default: False)
|
|
557
|
+
do_clearsky: Do clear sky growth and coagulation (default: False)
|
|
558
|
+
do_partialinit: Do initialization of coagulation from reference atmosphere (requires do_fixedinit) (default: False)
|
|
559
|
+
do_coremasscheck: Check core mass for particles (default: False)
|
|
560
|
+
sulfnucl_method: Method for sulfate nucleation (default: SulfateNucleationMethod.NONE)
|
|
561
|
+
vf_const: Constant fall velocity [m/s] (0: off) (default: 0.0)
|
|
562
|
+
minsubsteps: Minimum number of substeps (default: 1)
|
|
563
|
+
maxsubsteps: Maximum number of substeps (default: 1)
|
|
564
|
+
maxretries: Maximum number of retries (default: 5)
|
|
565
|
+
conmax: Minimum relative concentration to consider (default: 1.0e-1)
|
|
566
|
+
dt_threshold: Convergence criteria for temperature [fraction] (0: off) (default: 0.0)
|
|
567
|
+
cstick: Accommodation coefficient for coagulation (default: 1.0)
|
|
568
|
+
gsticki: Accommodation coefficient for growth of ice (default: 0.93)
|
|
569
|
+
gstickl: Accommodation coefficient for growth of liquid (default: 1.0)
|
|
570
|
+
tstick: Accommodation coefficient temperature (default: 1.0)
|
|
571
|
+
"""
|
|
572
|
+
self.do_cnst_rlh = do_cnst_rlh
|
|
573
|
+
self.do_detrain = do_detrain
|
|
574
|
+
self.do_fixedinit = do_fixedinit
|
|
575
|
+
self.do_incloud = do_incloud
|
|
576
|
+
self.do_explised = do_explised
|
|
577
|
+
self.do_substep = do_substep
|
|
578
|
+
self.do_thermo = do_thermo
|
|
579
|
+
self.do_vdiff = do_vdiff
|
|
580
|
+
self.do_vtran = do_vtran
|
|
581
|
+
self.do_drydep = do_drydep
|
|
582
|
+
self.do_pheat = do_pheat
|
|
583
|
+
self.do_pheatatm = do_pheatatm
|
|
584
|
+
self.do_clearsky = do_clearsky
|
|
585
|
+
self.do_partialinit = do_partialinit
|
|
586
|
+
self.do_coremasscheck = do_coremasscheck
|
|
587
|
+
self.sulfnucl_method = sulfnucl_method
|
|
588
|
+
self.vf_const = vf_const
|
|
589
|
+
self.minsubsteps = minsubsteps
|
|
590
|
+
self.maxsubsteps = maxsubsteps
|
|
591
|
+
self.maxretries = maxretries
|
|
592
|
+
self.conmax = conmax
|
|
593
|
+
self.dt_threshold = dt_threshold
|
|
594
|
+
self.cstick = cstick
|
|
595
|
+
self.gsticki = gsticki
|
|
596
|
+
self.gstickl = gstickl
|
|
597
|
+
self.tstick = tstick
|
|
598
|
+
|
|
599
|
+
def to_dict(self) -> Dict:
|
|
600
|
+
"""Convert to dictionary, converting Enums to ints."""
|
|
601
|
+
return {k: (v.value if isinstance(v, Enum) else v) for k, v in self.__dict__.items()}
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
class CARMAParameters:
|
|
605
|
+
"""
|
|
606
|
+
Parameters for CARMA aerosol model simulation.
|
|
607
|
+
|
|
608
|
+
This class encapsulates all the parameters needed to configure and run
|
|
609
|
+
a CARMA simulation, including model dimensions, time stepping, and
|
|
610
|
+
spatial parameters.
|
|
611
|
+
"""
|
|
612
|
+
|
|
613
|
+
def __init__(self,
|
|
614
|
+
nbin: int = 5,
|
|
615
|
+
nz: int = 1,
|
|
616
|
+
dtime: float = 1800.0,
|
|
617
|
+
wavelength_bins: Optional[List[CARMAWavelengthBin]] = None,
|
|
618
|
+
groups: Optional[List[CARMAGroupConfig]] = None,
|
|
619
|
+
elements: Optional[List[CARMAElementConfig]] = None,
|
|
620
|
+
solutes: Optional[List[CARMASoluteConfig]] = None,
|
|
621
|
+
gases: Optional[List[CARMAGasConfig]] = None,
|
|
622
|
+
coagulations: Optional[List[CARMACoagulationConfig]] = None,
|
|
623
|
+
growths: Optional[List[CARMAGrowthConfig]] = None,
|
|
624
|
+
nucleations: Optional[List[CARMANucleationConfig]] = None,
|
|
625
|
+
initialization: Optional[CARMAInitializationConfig] = None):
|
|
626
|
+
"""
|
|
627
|
+
Initialize CARMA parameters.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
nbin: Number of size bins (default: 5)
|
|
631
|
+
nz: Number of vertical levels (default: 1)
|
|
632
|
+
dtime: Time step in seconds (default: 1800.0)
|
|
633
|
+
wavelength_bins: List of CARMAWavelengthBin objects defining the wavelength grid (default: None)
|
|
634
|
+
groups: List of group configurations (default: None)
|
|
635
|
+
elements: List of element configurations (default: None)
|
|
636
|
+
solutes: List of solute configurations (default: None)
|
|
637
|
+
gases: List of gas configurations (default: None)
|
|
638
|
+
coagulations: List of coagulation configurations (default: None)
|
|
639
|
+
growths: List of growth configurations (default: None)
|
|
640
|
+
nucleations: List of nucleation configurations (default: None)
|
|
641
|
+
initialization: Initialization configuration (default: None)
|
|
642
|
+
"""
|
|
643
|
+
self.nbin = nbin
|
|
644
|
+
self.dtime = dtime
|
|
645
|
+
self.nz = nz
|
|
646
|
+
|
|
647
|
+
# Initialize lists
|
|
648
|
+
self.wavelength_bins = wavelength_bins or []
|
|
649
|
+
self.groups = groups or []
|
|
650
|
+
self.elements = elements or []
|
|
651
|
+
self.solutes = solutes or []
|
|
652
|
+
self.gases = gases or []
|
|
653
|
+
self.coagulations = coagulations or []
|
|
654
|
+
self.growths = growths or []
|
|
655
|
+
self.nucleations = nucleations or []
|
|
656
|
+
self.initialization = initialization or CARMAInitializationConfig()
|
|
657
|
+
|
|
658
|
+
def add_wavelength_bin(self, wavelength_bin: CARMAWavelengthBin):
|
|
659
|
+
"""Add a wavelength bin configuration."""
|
|
660
|
+
self.wavelength_bins.append(wavelength_bin)
|
|
661
|
+
|
|
662
|
+
def add_group(self, group: CARMAGroupConfig):
|
|
663
|
+
"""Add a group configuration."""
|
|
664
|
+
self.groups.append(group)
|
|
665
|
+
|
|
666
|
+
def add_element(self, element: CARMAElementConfig):
|
|
667
|
+
"""Add an element configuration."""
|
|
668
|
+
self.elements.append(element)
|
|
669
|
+
|
|
670
|
+
def add_solute(self, solute: CARMASoluteConfig):
|
|
671
|
+
"""Add a solute configuration."""
|
|
672
|
+
self.solutes.append(solute)
|
|
673
|
+
|
|
674
|
+
def add_gas(self, gas: CARMAGasConfig):
|
|
675
|
+
"""Add a gas configuration."""
|
|
676
|
+
self.gases.append(gas)
|
|
677
|
+
|
|
678
|
+
def add_coagulation(self, coagulation: CARMACoagulationConfig):
|
|
679
|
+
"""Add a coagulation configuration."""
|
|
680
|
+
self.coagulations.append(coagulation)
|
|
681
|
+
|
|
682
|
+
def add_growth(self, growth: CARMAGrowthConfig):
|
|
683
|
+
"""Add a growth configuration."""
|
|
684
|
+
self.growths.append(growth)
|
|
685
|
+
|
|
686
|
+
def add_nucleation(self, nucleation: CARMANucleationConfig):
|
|
687
|
+
"""Add a nucleation configuration."""
|
|
688
|
+
self.nucleations.append(nucleation)
|
|
689
|
+
|
|
690
|
+
def set_initialization(self, initialization: CARMAInitializationConfig):
|
|
691
|
+
"""Set the initialization configuration."""
|
|
692
|
+
self.initialization = initialization
|
|
693
|
+
|
|
694
|
+
def __repr__(self):
|
|
695
|
+
"""String representation of CARMAParameters."""
|
|
696
|
+
return (f"CARMAParameters(nbin={self.nbin}, dtime={self.dtime}, nz={self.nz}, "
|
|
697
|
+
f"wavelength_bins={len(self.wavelength_bins)}, "
|
|
698
|
+
f"groups={len(self.groups)}, elements={len(self.elements)}, "
|
|
699
|
+
f"solutes={len(self.solutes)}, gases={len(self.gases)}, "
|
|
700
|
+
f"coagulations={len(self.coagulations)}, growths={len(self.growths)}, "
|
|
701
|
+
f"nucleations={len(self.nucleations)})")
|
|
702
|
+
|
|
703
|
+
def __str__(self):
|
|
704
|
+
"""String representation of CARMAParameters."""
|
|
705
|
+
return (f"CARMAParameters(nbin={self.nbin}, dtime={self.dtime}, nz={self.nz}, "
|
|
706
|
+
f"wavelength_bins={len(self.wavelength_bins)}, "
|
|
707
|
+
f"groups={len(self.groups)}, elements={len(self.elements)}, "
|
|
708
|
+
f"solutes={len(self.solutes)}, gases={len(self.gases)}, "
|
|
709
|
+
f"coagulations={len(self.coagulations)}, growths={len(self.growths)}, "
|
|
710
|
+
f"nucleations={len(self.nucleations)})")
|
|
711
|
+
|
|
712
|
+
def to_dict(self) -> Dict:
|
|
713
|
+
"""Convert parameters to dictionary for C++ interface."""
|
|
714
|
+
# Get all basic attributes
|
|
715
|
+
params_dict = {}
|
|
716
|
+
for k, v in self.__dict__.items():
|
|
717
|
+
if not k.startswith('__') and not callable(v):
|
|
718
|
+
if k == 'groups':
|
|
719
|
+
params_dict[k] = [group.to_dict() for group in v]
|
|
720
|
+
elif k == 'elements':
|
|
721
|
+
params_dict[k] = [element.to_dict() for element in v]
|
|
722
|
+
elif k == 'solutes':
|
|
723
|
+
params_dict[k] = [solute.to_dict() for solute in v]
|
|
724
|
+
elif k == 'gases':
|
|
725
|
+
params_dict[k] = [gas.to_dict() for gas in v]
|
|
726
|
+
elif k == 'coagulations':
|
|
727
|
+
params_dict[k] = [coagulation.to_dict()
|
|
728
|
+
for coagulation in v]
|
|
729
|
+
elif k == 'growths':
|
|
730
|
+
params_dict[k] = [growth.to_dict() for growth in v]
|
|
731
|
+
elif k == 'nucleations':
|
|
732
|
+
params_dict[k] = [nucleation.to_dict() for nucleation in v]
|
|
733
|
+
elif k == 'wavelength_bins':
|
|
734
|
+
params_dict[k] = [bin.to_dict() for bin in v]
|
|
735
|
+
elif k == 'initialization':
|
|
736
|
+
params_dict[k] = v.to_dict() if v else None
|
|
737
|
+
else:
|
|
738
|
+
if isinstance(v, Enum):
|
|
739
|
+
params_dict[k] = v.value
|
|
740
|
+
else:
|
|
741
|
+
params_dict[k] = v
|
|
742
|
+
return params_dict
|
|
743
|
+
|
|
744
|
+
@classmethod
|
|
745
|
+
def from_dict(cls, params_dict: Dict) -> 'CARMAParameters':
|
|
746
|
+
"""Create parameters from dictionary."""
|
|
747
|
+
# Handle lists separately
|
|
748
|
+
wavelength_bins = []
|
|
749
|
+
if 'wavelength_bins' in params_dict:
|
|
750
|
+
wavelength_bins = [CARMAWavelengthBin(**bin_dict)
|
|
751
|
+
for bin_dict in params_dict['wavelength_bins']]
|
|
752
|
+
del params_dict['wavelength_bins']
|
|
753
|
+
groups = []
|
|
754
|
+
if 'groups' in params_dict:
|
|
755
|
+
groups = [CARMAGroupConfig(**group_dict)
|
|
756
|
+
for group_dict in params_dict['groups']]
|
|
757
|
+
del params_dict['groups']
|
|
758
|
+
|
|
759
|
+
elements = []
|
|
760
|
+
if 'elements' in params_dict:
|
|
761
|
+
elements = [CARMAElementConfig(**element_dict)
|
|
762
|
+
for element_dict in params_dict['elements']]
|
|
763
|
+
del params_dict['elements']
|
|
764
|
+
|
|
765
|
+
solutes = []
|
|
766
|
+
if 'solutes' in params_dict:
|
|
767
|
+
solutes = [CARMASoluteConfig(**solute_dict)
|
|
768
|
+
for solute_dict in params_dict['solutes']]
|
|
769
|
+
del params_dict['solutes']
|
|
770
|
+
|
|
771
|
+
gases = []
|
|
772
|
+
if 'gases' in params_dict:
|
|
773
|
+
gases = [CARMAGasConfig(**gas_dict)
|
|
774
|
+
for gas_dict in params_dict['gases']]
|
|
775
|
+
del params_dict['gases']
|
|
776
|
+
|
|
777
|
+
coagulations = []
|
|
778
|
+
if 'coagulations' in params_dict:
|
|
779
|
+
coagulations = [CARMACoagulationConfig(**coag_dict)
|
|
780
|
+
for coag_dict in params_dict['coagulations']]
|
|
781
|
+
del params_dict['coagulations']
|
|
782
|
+
|
|
783
|
+
growths = []
|
|
784
|
+
if 'growths' in params_dict:
|
|
785
|
+
growths = [CARMAGrowthConfig(**growth_dict)
|
|
786
|
+
for growth_dict in params_dict['growths']]
|
|
787
|
+
del params_dict['growths']
|
|
788
|
+
|
|
789
|
+
nucleations = []
|
|
790
|
+
if 'nucleations' in params_dict:
|
|
791
|
+
nucleations = [CARMANucleationConfig(**nucleation_dict)
|
|
792
|
+
for nucleation_dict in params_dict['nucleations']]
|
|
793
|
+
del params_dict['nucleations']
|
|
794
|
+
|
|
795
|
+
initialization = None
|
|
796
|
+
if 'initialization' in params_dict and params_dict['initialization']:
|
|
797
|
+
initialization = CARMAInitializationConfig(
|
|
798
|
+
**params_dict['initialization'])
|
|
799
|
+
del params_dict['initialization']
|
|
800
|
+
|
|
801
|
+
return cls(
|
|
802
|
+
wavelength_bins=wavelength_bins,
|
|
803
|
+
groups=groups,
|
|
804
|
+
elements=elements,
|
|
805
|
+
solutes=solutes,
|
|
806
|
+
gases=gases,
|
|
807
|
+
coagulations=coagulations,
|
|
808
|
+
growths=growths,
|
|
809
|
+
nucleations=nucleations,
|
|
810
|
+
initialization=initialization,
|
|
811
|
+
**params_dict)
|
|
812
|
+
|
|
813
|
+
|
|
814
|
+
class CARMASurfaceProperties:
|
|
815
|
+
"""
|
|
816
|
+
Represents the surface properties used in CARMA simulations.
|
|
817
|
+
|
|
818
|
+
This class encapsulates the surface properties such as friction velocity,
|
|
819
|
+
aerodynamic resistance, and area fraction, which are used in CARMA simulations
|
|
820
|
+
to model the interaction between the atmosphere and the surface.
|
|
821
|
+
"""
|
|
822
|
+
|
|
823
|
+
def __init__(self,
|
|
824
|
+
surface_friction_velocity: float = 0.0,
|
|
825
|
+
aerodynamic_resistance: float = 0.0,
|
|
826
|
+
area_fraction: float = 0.0):
|
|
827
|
+
"""
|
|
828
|
+
Initialize CARMASurfaceProperties instance.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
surface_friction_velocity: Friction velocity at the surface [m/s] (default: 0.0)
|
|
832
|
+
aerodynamic_resistance: Aerodynamic resistance at the surface [s/m] (default: 0.0)
|
|
833
|
+
area_fraction: Area fraction of the surface [fraction] (default: 0.0)
|
|
834
|
+
"""
|
|
835
|
+
self.surface_friction_velocity = surface_friction_velocity
|
|
836
|
+
self.aerodynamic_resistance = aerodynamic_resistance
|
|
837
|
+
self.area_fraction = area_fraction
|
|
838
|
+
|
|
839
|
+
def __repr__(self):
|
|
840
|
+
"""Represent the surface properties as a string."""
|
|
841
|
+
return (f"CARMASurfaceProperties("
|
|
842
|
+
f"surface_friction_velocity={self.surface_friction_velocity}, "
|
|
843
|
+
f"aerodynamic_resistance={self.aerodynamic_resistance}, "
|
|
844
|
+
f"area_fraction={self.area_fraction})")
|
|
845
|
+
|
|
846
|
+
def __str__(self):
|
|
847
|
+
"""String representation of surface properties."""
|
|
848
|
+
return (f"Surface Friction Velocity: {self.surface_friction_velocity} m/s, "
|
|
849
|
+
f"Aerodynamic Resistance: {self.aerodynamic_resistance} s/m, "
|
|
850
|
+
f"Area Fraction: {self.area_fraction}")
|
|
851
|
+
|
|
852
|
+
def to_dict(self) -> Dict:
|
|
853
|
+
"""Convert surface properties to dictionary."""
|
|
854
|
+
return {
|
|
855
|
+
'surface_friction_velocity': self.surface_friction_velocity,
|
|
856
|
+
'aerodynamic_resistance': self.aerodynamic_resistance,
|
|
857
|
+
'area_fraction': self.area_fraction
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
class CARMAState:
|
|
862
|
+
"""
|
|
863
|
+
Represents the environmental variables used in CARMA simulations."""
|
|
864
|
+
|
|
865
|
+
def __init__(self,
|
|
866
|
+
carma_pointer: c_void_p,
|
|
867
|
+
vertical_center: List[float],
|
|
868
|
+
vertical_levels: List[float],
|
|
869
|
+
pressure: List[float],
|
|
870
|
+
pressure_levels: List[float],
|
|
871
|
+
temperature: List[float],
|
|
872
|
+
original_temperature: Optional[List[float]] = None,
|
|
873
|
+
relative_humidity: Optional[List[float]] = None,
|
|
874
|
+
specific_humidity: Optional[List[float]] = None,
|
|
875
|
+
radiative_intensity: Optional[List[List[float]]] = None,
|
|
876
|
+
time: float = 0.0,
|
|
877
|
+
time_step: float = 1.0,
|
|
878
|
+
latitude: float = 0.0,
|
|
879
|
+
longitude: float = 0.0,
|
|
880
|
+
coordinates: CarmaCoordinates = CarmaCoordinates.CARTESIAN,
|
|
881
|
+
gases: Optional[List[CARMAGasConfig]] = None,
|
|
882
|
+
):
|
|
883
|
+
"""
|
|
884
|
+
Initialize a CARMAState instance.
|
|
885
|
+
|
|
886
|
+
Args:
|
|
887
|
+
carma_pointer: Pointer to the CARMA C++ instance
|
|
888
|
+
vertical_center: List of vertical center heights in meters
|
|
889
|
+
vertical_levels: List of vertical levels in meters
|
|
890
|
+
pressure: List of pressures at vertical centers in Pascals
|
|
891
|
+
pressure_levels: List of pressures at vertical levels in Pascals
|
|
892
|
+
temperature: List of temperatures at vertical centers in Kelvin
|
|
893
|
+
original_temperature: List of original temperatures at vertical centers in Kelvin (default: None) If None, will use temperature
|
|
894
|
+
relative_humidity: List of relative humidity at vertical centers in percent (default: None)
|
|
895
|
+
specific_humidity: List of specific humidity at vertical centers in kg/kg (default: None)
|
|
896
|
+
radiative_intensity: List of radiative intensity at vertical centers in W/m² (wavelength, vertical_center) (default: None)
|
|
897
|
+
time: Simulation time in seconds (default: 0.0)
|
|
898
|
+
time_step: Time step in seconds (default: 1.0)
|
|
899
|
+
latitude: Latitude in degrees (default: 0.0)
|
|
900
|
+
longitude: Longitude in degrees (default: 0.0)
|
|
901
|
+
coordinates: Coordinate system for the simulation (default: Cartesian)
|
|
902
|
+
gases: List of gas configurations
|
|
903
|
+
"""
|
|
904
|
+
self.gases = gases or []
|
|
905
|
+
self.longitude = longitude
|
|
906
|
+
self.latitude = latitude
|
|
907
|
+
self.coordinates = coordinates
|
|
908
|
+
self.n_levels = len(vertical_center)
|
|
909
|
+
if original_temperature is None:
|
|
910
|
+
original_temperature = temperature
|
|
911
|
+
self.vertical_center = vertical_center
|
|
912
|
+
self.vertical_levels = vertical_levels
|
|
913
|
+
|
|
914
|
+
self.dimensions = _backend._carma._get_dimensions(carma_pointer)
|
|
915
|
+
self._carma_state_instance = _backend._carma._create_carma_state(
|
|
916
|
+
carma_pointer=carma_pointer,
|
|
917
|
+
time=time,
|
|
918
|
+
time_step=time_step,
|
|
919
|
+
latitude=latitude,
|
|
920
|
+
longitude=longitude,
|
|
921
|
+
coordinates=coordinates.value,
|
|
922
|
+
temperature=temperature,
|
|
923
|
+
original_temperature=original_temperature,
|
|
924
|
+
pressure=pressure,
|
|
925
|
+
pressure_levels=pressure_levels,
|
|
926
|
+
vertical_center=vertical_center,
|
|
927
|
+
vertical_levels=vertical_levels,
|
|
928
|
+
relative_humidity=relative_humidity,
|
|
929
|
+
specific_humidity=specific_humidity,
|
|
930
|
+
radiative_intensity=radiative_intensity
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
def __del__(self):
|
|
934
|
+
"""Clean up the CARMAState instance."""
|
|
935
|
+
if hasattr(self, '_carma_state_instance') and self._carma_state_instance is not None:
|
|
936
|
+
_backend._carma._delete_carma_state(self._carma_state_instance)
|
|
937
|
+
|
|
938
|
+
def __repr__(self):
|
|
939
|
+
"""String representation of CARMAState."""
|
|
940
|
+
return (f"CARMAState")
|
|
941
|
+
|
|
942
|
+
def __str__(self):
|
|
943
|
+
"""String representation of CARMAState."""
|
|
944
|
+
return (f"CARMAState")
|
|
945
|
+
|
|
946
|
+
def to_dict(self) -> Dict:
|
|
947
|
+
"""Convert CARMAState to dictionary."""
|
|
948
|
+
return {k: v for k, v in self.__dict__.items() if not k.startswith('__') and not callable(v)}
|
|
949
|
+
|
|
950
|
+
def set_bin(self,
|
|
951
|
+
bin_index: int,
|
|
952
|
+
element_index: int,
|
|
953
|
+
value: Union[float, List[float]],
|
|
954
|
+
surface_mass: Optional[float] = 0.0):
|
|
955
|
+
"""
|
|
956
|
+
Set the value for a specific bin and element.
|
|
957
|
+
|
|
958
|
+
Args:
|
|
959
|
+
bin_index: Index of the size bin (1-indexed)
|
|
960
|
+
element_index: Index of the element (1-indexed)
|
|
961
|
+
value: Value to set, can be a single float or a list of floats
|
|
962
|
+
surface_mass: Optional surface mass for the bin [kg m-2] (default: 0.0)
|
|
963
|
+
"""
|
|
964
|
+
if np.isscalar(value):
|
|
965
|
+
value = np.repeat(value, self.n_levels).tolist()
|
|
966
|
+
elif not isinstance(value, list):
|
|
967
|
+
# Ensure value is a list of floats with length n_levels
|
|
968
|
+
if isinstance(value, np.ndarray):
|
|
969
|
+
value = value.tolist()
|
|
970
|
+
if not isinstance(value, list):
|
|
971
|
+
# Convert to list if not already
|
|
972
|
+
value = list(value)
|
|
973
|
+
if len(value) != self.n_levels:
|
|
974
|
+
raise ValueError(
|
|
975
|
+
f"Value must be a scalar or a list of length {self.n_levels}, got length {len(value)}")
|
|
976
|
+
if not all(isinstance(v, float) for v in value):
|
|
977
|
+
raise ValueError("All elements in value must be floats")
|
|
978
|
+
_backend._carma._set_bin(
|
|
979
|
+
self._carma_state_instance, bin_index, element_index, value, surface_mass)
|
|
980
|
+
|
|
981
|
+
def set_detrain(self, bin_index: int, element_index: int, value: float):
|
|
982
|
+
"""
|
|
983
|
+
Set the mass of the detrained condensate for the bin
|
|
984
|
+
|
|
985
|
+
Args:
|
|
986
|
+
bin_index: Index of the size bin (1-indexed)
|
|
987
|
+
element_index: Index of the element (1-indexed)
|
|
988
|
+
value: Value to set
|
|
989
|
+
"""
|
|
990
|
+
if np.isscalar(value):
|
|
991
|
+
value = np.repeat(value, self.n_levels).tolist()
|
|
992
|
+
elif not isinstance(value, list):
|
|
993
|
+
value = list(value)
|
|
994
|
+
_backend._carma._set_detrain(
|
|
995
|
+
self._carma_state_instance, bin_index, element_index, value)
|
|
996
|
+
|
|
997
|
+
def set_gas(self,
|
|
998
|
+
gas_index: int,
|
|
999
|
+
value: Union[float,
|
|
1000
|
+
List[float]],
|
|
1001
|
+
old_mmr: Optional[List[float]] = None,
|
|
1002
|
+
gas_saturation_wrt_ice: Optional[List[float]] = None,
|
|
1003
|
+
gas_saturation_wrt_liquid: Optional[List[float]] = None):
|
|
1004
|
+
"""
|
|
1005
|
+
Set the value for a specific gas.
|
|
1006
|
+
|
|
1007
|
+
Args:
|
|
1008
|
+
gas_index: Index of the gas (1-indexed)
|
|
1009
|
+
value: Value to set, can be a single float or a list of floats
|
|
1010
|
+
old_mmr: Optional list of old mass mixing ratios for the gas (default: None)
|
|
1011
|
+
gas_saturation_wrt_ice: Optional list of gas saturation with respect to ice (default: None)
|
|
1012
|
+
gas_saturation_wrt_liquid: Optional list of gas saturation with respect to liquid (default: None)
|
|
1013
|
+
"""
|
|
1014
|
+
if np.isscalar(value):
|
|
1015
|
+
value = np.repeat(value, self.n_levels).tolist()
|
|
1016
|
+
elif not isinstance(value, list):
|
|
1017
|
+
value = list(value)
|
|
1018
|
+
if old_mmr is None:
|
|
1019
|
+
old_mmr = []
|
|
1020
|
+
if gas_saturation_wrt_ice is None:
|
|
1021
|
+
gas_saturation_wrt_ice = []
|
|
1022
|
+
if gas_saturation_wrt_liquid is None:
|
|
1023
|
+
gas_saturation_wrt_liquid = []
|
|
1024
|
+
if not isinstance(old_mmr, list):
|
|
1025
|
+
old_mmr = list(old_mmr)
|
|
1026
|
+
if not isinstance(gas_saturation_wrt_ice, list):
|
|
1027
|
+
gas_saturation_wrt_ice = list(gas_saturation_wrt_ice)
|
|
1028
|
+
if not isinstance(gas_saturation_wrt_liquid, list):
|
|
1029
|
+
gas_saturation_wrt_liquid = list(gas_saturation_wrt_liquid)
|
|
1030
|
+
_backend._carma._set_gas(
|
|
1031
|
+
self._carma_state_instance,
|
|
1032
|
+
gas_index,
|
|
1033
|
+
value,
|
|
1034
|
+
old_mmr,
|
|
1035
|
+
gas_saturation_wrt_ice,
|
|
1036
|
+
gas_saturation_wrt_liquid)
|
|
1037
|
+
|
|
1038
|
+
def get_step_statistics(self) -> Dict[str, Any]:
|
|
1039
|
+
"""
|
|
1040
|
+
Get the step statistics for the current CARMAState.
|
|
1041
|
+
|
|
1042
|
+
Returns:
|
|
1043
|
+
Dict[str, Any]: Dictionary containing step statistics such as
|
|
1044
|
+
number of substeps, convergence status, etc.
|
|
1045
|
+
"""
|
|
1046
|
+
return _backend._carma._get_step_statistics(self._carma_state_instance)
|
|
1047
|
+
|
|
1048
|
+
def get_bins(self) -> xr.Dataset:
|
|
1049
|
+
"""
|
|
1050
|
+
Get the CARMA aerosol state data for all bins and elements.
|
|
1051
|
+
|
|
1052
|
+
Returns:
|
|
1053
|
+
Dataset: Aerosol bin properties for all bins and elements
|
|
1054
|
+
"""
|
|
1055
|
+
|
|
1056
|
+
# Collect bin data for each property into arrays
|
|
1057
|
+
# Shape: [number_of_bins, number_of_elements, n_levels] for vertical properties
|
|
1058
|
+
# and [number_of_bins, number_of_elements] for surface/level properties
|
|
1059
|
+
|
|
1060
|
+
number_of_bins = self.dimensions["number_of_bins"]
|
|
1061
|
+
number_of_elements = self.dimensions["number_of_elements"]
|
|
1062
|
+
n_levels = len(self.vertical_center)
|
|
1063
|
+
n_edges = len(self.vertical_levels)
|
|
1064
|
+
|
|
1065
|
+
# Initialize property arrays
|
|
1066
|
+
properties = [
|
|
1067
|
+
"mass_mixing_ratio",
|
|
1068
|
+
"number_mixing_ratio",
|
|
1069
|
+
"number_density",
|
|
1070
|
+
"nucleation_rate",
|
|
1071
|
+
"wet_particle_radius",
|
|
1072
|
+
"wet_particle_density",
|
|
1073
|
+
"dry_particle_density",
|
|
1074
|
+
"particle_mass_on_surface",
|
|
1075
|
+
"sedimentation_flux",
|
|
1076
|
+
"fall_velocity",
|
|
1077
|
+
"deposition_velocity",
|
|
1078
|
+
"delta_particle_temperature",
|
|
1079
|
+
"kappa",
|
|
1080
|
+
"total_mass_mixing_ratio"
|
|
1081
|
+
]
|
|
1082
|
+
|
|
1083
|
+
# Determine which properties are per vertical_center, per vertical_level, or per bin/element only
|
|
1084
|
+
per_vertical_center = [
|
|
1085
|
+
"mass_mixing_ratio",
|
|
1086
|
+
"number_mixing_ratio",
|
|
1087
|
+
"number_density",
|
|
1088
|
+
"nucleation_rate",
|
|
1089
|
+
"wet_particle_radius",
|
|
1090
|
+
"wet_particle_density",
|
|
1091
|
+
"dry_particle_density",
|
|
1092
|
+
"delta_particle_temperature",
|
|
1093
|
+
"kappa",
|
|
1094
|
+
"total_mass_mixing_ratio"
|
|
1095
|
+
]
|
|
1096
|
+
per_vertical_level = [
|
|
1097
|
+
"fall_velocity"
|
|
1098
|
+
]
|
|
1099
|
+
per_bin_element = [
|
|
1100
|
+
"particle_mass_on_surface",
|
|
1101
|
+
"sedimentation_flux",
|
|
1102
|
+
"deposition_velocity"
|
|
1103
|
+
]
|
|
1104
|
+
|
|
1105
|
+
# Prepare arrays
|
|
1106
|
+
data = {prop: [] for prop in properties}
|
|
1107
|
+
for i_bin in range(number_of_bins):
|
|
1108
|
+
for i_elem in range(number_of_elements):
|
|
1109
|
+
bin_value = _backend._carma._get_bin(
|
|
1110
|
+
self._carma_state_instance,
|
|
1111
|
+
i_bin + 1,
|
|
1112
|
+
i_elem + 1
|
|
1113
|
+
)
|
|
1114
|
+
for prop in properties:
|
|
1115
|
+
data[prop].append(bin_value.get(prop))
|
|
1116
|
+
|
|
1117
|
+
# Reshape arrays
|
|
1118
|
+
def reshape(arr, shape):
|
|
1119
|
+
return np.array(arr).reshape(shape)
|
|
1120
|
+
|
|
1121
|
+
# Helper for units and long names
|
|
1122
|
+
def _get_attributes(prop):
|
|
1123
|
+
attributes = {
|
|
1124
|
+
"mass_mixing_ratio": {
|
|
1125
|
+
"units": "kg kg-1",
|
|
1126
|
+
"long_name": "Aerosol particle mass mixing ratio"
|
|
1127
|
+
},
|
|
1128
|
+
"number_mixing_ratio": {
|
|
1129
|
+
"units": "kg-1",
|
|
1130
|
+
"long_name": "Aerosol particle number mixing ratio"
|
|
1131
|
+
},
|
|
1132
|
+
"number_density": {
|
|
1133
|
+
"units": "m-3",
|
|
1134
|
+
"long_name": "Aerosol particle number density"
|
|
1135
|
+
},
|
|
1136
|
+
"nucleation_rate": {
|
|
1137
|
+
"units": "m-3 s-1",
|
|
1138
|
+
"long_name": "Aerosol particle nucleation rate"
|
|
1139
|
+
},
|
|
1140
|
+
"wet_particle_radius": {
|
|
1141
|
+
"units": "m",
|
|
1142
|
+
"long_name": "Wet aerosol particle radius"
|
|
1143
|
+
},
|
|
1144
|
+
"wet_particle_density": {
|
|
1145
|
+
"units": "kg m-3",
|
|
1146
|
+
"long_name": "Wet aerosol particle density"
|
|
1147
|
+
},
|
|
1148
|
+
"dry_particle_density": {
|
|
1149
|
+
"units": "kg m-3",
|
|
1150
|
+
"long_name": "Dry aerosol particle density"
|
|
1151
|
+
},
|
|
1152
|
+
"particle_mass_on_surface": {
|
|
1153
|
+
"units": "kg m-2",
|
|
1154
|
+
"long_name": "Aerosol particle mass on surface"
|
|
1155
|
+
},
|
|
1156
|
+
"sedimentation_flux": {
|
|
1157
|
+
"units": "kg m-2 s-1",
|
|
1158
|
+
"long_name": "Aerosol particle sedimentation flux"
|
|
1159
|
+
},
|
|
1160
|
+
"fall_velocity": {
|
|
1161
|
+
"units": "m s-1",
|
|
1162
|
+
"long_name": "Aerosol particle fall velocity"
|
|
1163
|
+
},
|
|
1164
|
+
"deposition_velocity": {
|
|
1165
|
+
"units": "m s-1",
|
|
1166
|
+
"long_name": "Aerosol particle deposition velocity"
|
|
1167
|
+
},
|
|
1168
|
+
"delta_particle_temperature": {
|
|
1169
|
+
"units": "K",
|
|
1170
|
+
"long_name": "Aerosol particle temperature change"
|
|
1171
|
+
},
|
|
1172
|
+
"kappa": {
|
|
1173
|
+
"units": "-",
|
|
1174
|
+
"long_name": "Aerosol particle hygroscopicity parameter"
|
|
1175
|
+
},
|
|
1176
|
+
"total_mass_mixing_ratio": {
|
|
1177
|
+
"units": "kg m-3",
|
|
1178
|
+
"long_name": "Total aerosol particle mass mixing ratio"
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return attributes.get(prop, {"units": "", "long_name": ""})
|
|
1182
|
+
|
|
1183
|
+
dataset_vars = {}
|
|
1184
|
+
|
|
1185
|
+
# Per-vertical_center properties: shape (number_of_bins, number_of_elements, n_levels)
|
|
1186
|
+
for prop in per_vertical_center:
|
|
1187
|
+
arr = reshape(data[prop], (number_of_bins, number_of_elements, n_levels))
|
|
1188
|
+
dataset_vars[prop] = (("bin", "element", "vertical_center"), arr, _get_attributes(prop))
|
|
1189
|
+
|
|
1190
|
+
# Per-vertical_level properties: shape (number_of_bins, number_of_elements, n_edges)
|
|
1191
|
+
for prop in per_vertical_level:
|
|
1192
|
+
arr = reshape(data[prop], (number_of_bins, number_of_elements, n_edges))
|
|
1193
|
+
dataset_vars[prop] = (("bin", "element", "vertical_level"), arr, _get_attributes(prop))
|
|
1194
|
+
|
|
1195
|
+
# Per-bin/element only properties: shape (number_of_bins, number_of_elements)
|
|
1196
|
+
for prop in per_bin_element:
|
|
1197
|
+
arr = reshape(data[prop], (number_of_bins, number_of_elements))
|
|
1198
|
+
dataset_vars[prop] = (("bin", "element"), arr, _get_attributes(prop))
|
|
1199
|
+
|
|
1200
|
+
return xr.Dataset(
|
|
1201
|
+
data_vars=dataset_vars,
|
|
1202
|
+
coords={
|
|
1203
|
+
"bin": np.arange(1, number_of_bins + 1),
|
|
1204
|
+
"element": np.arange(1, number_of_elements + 1),
|
|
1205
|
+
"vertical_center": self.vertical_center,
|
|
1206
|
+
"vertical_level": self.vertical_levels
|
|
1207
|
+
}
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
def get_detrained_masses(self) -> xr.Dataset:
|
|
1211
|
+
"""
|
|
1212
|
+
Get the mass of the detrained condensate for the bin for each particle in the grid
|
|
1213
|
+
|
|
1214
|
+
Returns:
|
|
1215
|
+
List[float]: Mass of the detrained condensate for the specified bin and element
|
|
1216
|
+
"""
|
|
1217
|
+
|
|
1218
|
+
number_of_bins = self.dimensions["number_of_bins"]
|
|
1219
|
+
number_of_elements = self.dimensions["number_of_elements"]
|
|
1220
|
+
n_levels = len(self.vertical_center)
|
|
1221
|
+
|
|
1222
|
+
# Initialize property arrays
|
|
1223
|
+
properties = [
|
|
1224
|
+
"mass_mixing_ratio",
|
|
1225
|
+
"number_mixing_ratio",
|
|
1226
|
+
"number_density",
|
|
1227
|
+
"wet_particle_radius",
|
|
1228
|
+
"wet_particle_density"
|
|
1229
|
+
]
|
|
1230
|
+
|
|
1231
|
+
# Prepare arrays
|
|
1232
|
+
data = {prop: [] for prop in properties}
|
|
1233
|
+
for i_bin in range(number_of_bins):
|
|
1234
|
+
for i_elem in range(number_of_elements):
|
|
1235
|
+
bin_value = _backend._carma._get_detrain(
|
|
1236
|
+
self._carma_state_instance,
|
|
1237
|
+
i_bin + 1,
|
|
1238
|
+
i_elem + 1
|
|
1239
|
+
)
|
|
1240
|
+
for prop in properties:
|
|
1241
|
+
data[prop].append(bin_value.get(prop))
|
|
1242
|
+
|
|
1243
|
+
# Reshape arrays
|
|
1244
|
+
def reshape(arr, shape):
|
|
1245
|
+
return np.array(arr).reshape(shape)
|
|
1246
|
+
|
|
1247
|
+
# Helper for units
|
|
1248
|
+
def _get_units(prop):
|
|
1249
|
+
units = {
|
|
1250
|
+
"mass_mixing_ratio": "kg kg-1",
|
|
1251
|
+
"number_mixing_ratio": "kg-1",
|
|
1252
|
+
"number_density": "m-3",
|
|
1253
|
+
"wet_particle_radius": "m",
|
|
1254
|
+
"wet_particle_density": "kg m-3"
|
|
1255
|
+
}
|
|
1256
|
+
return units.get(prop, "")
|
|
1257
|
+
|
|
1258
|
+
dataset_vars = {}
|
|
1259
|
+
|
|
1260
|
+
# Per-vertical_center properties: shape (number_of_bins, number_of_elements, n_levels)
|
|
1261
|
+
for prop in properties:
|
|
1262
|
+
arr = reshape(data[prop], (number_of_bins, number_of_elements, n_levels))
|
|
1263
|
+
dataset_vars[prop] = (("bin", "element", "vertical_center"), arr, {"units": _get_units(prop)})
|
|
1264
|
+
|
|
1265
|
+
return xr.Dataset(
|
|
1266
|
+
data_vars=dataset_vars,
|
|
1267
|
+
coords={
|
|
1268
|
+
"bin": np.arange(1, number_of_bins + 1),
|
|
1269
|
+
"element": np.arange(1, number_of_elements + 1),
|
|
1270
|
+
"vertical_center": self.vertical_center
|
|
1271
|
+
}
|
|
1272
|
+
)
|
|
1273
|
+
|
|
1274
|
+
def get_gases(self) -> Tuple[xr.Dataset, Dict[str, int]]:
|
|
1275
|
+
"""
|
|
1276
|
+
Get the values for all gases.
|
|
1277
|
+
|
|
1278
|
+
Returns:
|
|
1279
|
+
Tuple[xr.Dataset, Dict[str, int]] A dataset containing values for all gases and a mapping of gas names to their indices.
|
|
1280
|
+
"""
|
|
1281
|
+
number_of_gases = self.dimensions["number_of_gases"]
|
|
1282
|
+
n_levels = len(self.vertical_center)
|
|
1283
|
+
|
|
1284
|
+
# Initialize property arrays
|
|
1285
|
+
properties = [
|
|
1286
|
+
"mass_mixing_ratio",
|
|
1287
|
+
"gas_saturation_wrt_ice",
|
|
1288
|
+
"gas_saturation_wrt_liquid",
|
|
1289
|
+
"gas_vapor_pressure_wrt_ice",
|
|
1290
|
+
"gas_vapor_pressure_wrt_liquid",
|
|
1291
|
+
"weight_pct_aerosol_composition"
|
|
1292
|
+
]
|
|
1293
|
+
|
|
1294
|
+
# Prepare arrays
|
|
1295
|
+
data = {prop: [] for prop in properties}
|
|
1296
|
+
for i_gas in range(number_of_gases):
|
|
1297
|
+
gas_value = _backend._carma._get_gas(
|
|
1298
|
+
self._carma_state_instance,
|
|
1299
|
+
i_gas + 1
|
|
1300
|
+
)
|
|
1301
|
+
for prop in properties:
|
|
1302
|
+
data[prop].append(gas_value.get(prop))
|
|
1303
|
+
|
|
1304
|
+
# Reshape arrays
|
|
1305
|
+
def reshape(arr, shape):
|
|
1306
|
+
return np.array(arr).reshape(shape)
|
|
1307
|
+
|
|
1308
|
+
# Helper for units
|
|
1309
|
+
def _get_units(prop):
|
|
1310
|
+
units = {
|
|
1311
|
+
"mass_mixing_ratio": "kg kg-1",
|
|
1312
|
+
"gas_saturation_wrt_ice": "none",
|
|
1313
|
+
"gas_saturation_wrt_liquid": "none",
|
|
1314
|
+
"gas_vapor_pressure_wrt_ice": "none",
|
|
1315
|
+
"gas_vapor_pressure_wrt_liquid": "none",
|
|
1316
|
+
"weight_pct_aerosol_composition": "none"
|
|
1317
|
+
}
|
|
1318
|
+
return units.get(prop, "")
|
|
1319
|
+
|
|
1320
|
+
dataset_vars = {}
|
|
1321
|
+
|
|
1322
|
+
# Per-vertical_center properties: shape (number_of_gases, n_levels)
|
|
1323
|
+
for prop in properties:
|
|
1324
|
+
arr = reshape(data[prop], (number_of_gases, n_levels))
|
|
1325
|
+
if prop == 'mass_mixing_ratio':
|
|
1326
|
+
dataset_vars[f'gas_{prop}'] = (("gas", "vertical_center"), arr, {"units": _get_units(prop)})
|
|
1327
|
+
else:
|
|
1328
|
+
dataset_vars[prop] = (("gas", "vertical_center"), arr, {"units": _get_units(prop)})
|
|
1329
|
+
|
|
1330
|
+
return xr.Dataset(
|
|
1331
|
+
data_vars=dataset_vars,
|
|
1332
|
+
coords={
|
|
1333
|
+
"gas": [gas.shortname for gas in self.gases],
|
|
1334
|
+
"vertical_center": self.vertical_center
|
|
1335
|
+
}
|
|
1336
|
+
), {gas.shortname: idx for idx, gas in enumerate(self.gases)}
|
|
1337
|
+
|
|
1338
|
+
def get_environmental_values(self) -> xr.Dataset:
|
|
1339
|
+
"""
|
|
1340
|
+
Get all environmental conditions for the current CARMAState.
|
|
1341
|
+
|
|
1342
|
+
Returns:
|
|
1343
|
+
xr.Dataset: Dataset containing all environmental conditions
|
|
1344
|
+
"""
|
|
1345
|
+
n_levels = len(self.vertical_center)
|
|
1346
|
+
|
|
1347
|
+
data = _backend._carma._get_environmental_values(self._carma_state_instance)
|
|
1348
|
+
|
|
1349
|
+
# Reshape arrays
|
|
1350
|
+
def reshape(arr, shape):
|
|
1351
|
+
return np.array(arr).reshape(shape)
|
|
1352
|
+
|
|
1353
|
+
# Helper for units and long names
|
|
1354
|
+
def _get_attributes(prop):
|
|
1355
|
+
attributes = {
|
|
1356
|
+
"temperature": {
|
|
1357
|
+
"units": "K",
|
|
1358
|
+
"long_name": "Temperature"
|
|
1359
|
+
},
|
|
1360
|
+
"pressure": {
|
|
1361
|
+
"units": "Pa",
|
|
1362
|
+
"long_name": "Pressure"
|
|
1363
|
+
},
|
|
1364
|
+
"air_density": {
|
|
1365
|
+
"units": "kg m-3",
|
|
1366
|
+
"long_name": "Air density"
|
|
1367
|
+
},
|
|
1368
|
+
"latent_heat": {
|
|
1369
|
+
"units": "K s-1",
|
|
1370
|
+
"long_name": "Latent heat release rate"
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return attributes.get(prop, {"units": "", "long_name": ""})
|
|
1374
|
+
|
|
1375
|
+
dataset_vars = {}
|
|
1376
|
+
|
|
1377
|
+
for prop in data:
|
|
1378
|
+
if prop is not None:
|
|
1379
|
+
arr = reshape(data[prop], (n_levels,))
|
|
1380
|
+
dataset_vars[prop] = (("vertical_center"), arr, _get_attributes(prop))
|
|
1381
|
+
|
|
1382
|
+
return xr.Dataset(
|
|
1383
|
+
data_vars=dataset_vars,
|
|
1384
|
+
coords={
|
|
1385
|
+
"vertical_center": self.vertical_center
|
|
1386
|
+
}
|
|
1387
|
+
)
|
|
1388
|
+
|
|
1389
|
+
def set_temperature(self, temperature: Union[float, List[float]]):
|
|
1390
|
+
"""
|
|
1391
|
+
Set the temperature for the vertical levels.
|
|
1392
|
+
|
|
1393
|
+
Args:
|
|
1394
|
+
temperature: Temperature value to set, can be a single float or a list of floats
|
|
1395
|
+
"""
|
|
1396
|
+
if np.isscalar(temperature):
|
|
1397
|
+
temperature = np.repeat(temperature, self.n_levels).tolist()
|
|
1398
|
+
elif not isinstance(temperature, list):
|
|
1399
|
+
temperature = list(temperature)
|
|
1400
|
+
_backend._carma._set_temperature(
|
|
1401
|
+
self._carma_state_instance, temperature)
|
|
1402
|
+
|
|
1403
|
+
def set_air_density(self, air_density: Union[float, List[float]]):
|
|
1404
|
+
"""
|
|
1405
|
+
Set the air density for the vertical levels.
|
|
1406
|
+
|
|
1407
|
+
Args:
|
|
1408
|
+
air_density: Air density value to set, can be a single float or a list of floats
|
|
1409
|
+
"""
|
|
1410
|
+
if np.isscalar(air_density):
|
|
1411
|
+
air_density = np.repeat(air_density, self.n_levels).tolist()
|
|
1412
|
+
elif not isinstance(air_density, list):
|
|
1413
|
+
air_density = list(air_density)
|
|
1414
|
+
_backend._carma._set_air_density(
|
|
1415
|
+
self._carma_state_instance, air_density)
|
|
1416
|
+
|
|
1417
|
+
def step(
|
|
1418
|
+
self,
|
|
1419
|
+
cloud_fraction: Optional[List[float]] = None,
|
|
1420
|
+
critical_relative_humidity: Optional[List[float]] = None,
|
|
1421
|
+
land: Optional[CARMASurfaceProperties] = None,
|
|
1422
|
+
ocean: Optional[CARMASurfaceProperties] = None,
|
|
1423
|
+
ice: Optional[CARMASurfaceProperties] = None
|
|
1424
|
+
):
|
|
1425
|
+
"""
|
|
1426
|
+
Perform a single step in the CARMA simulation.
|
|
1427
|
+
|
|
1428
|
+
Args:
|
|
1429
|
+
cloud_fraction: Optional list of cloud fractions for each vertical level (default: None)
|
|
1430
|
+
critical_relative_humidity: Optional list of critical relative humidities for each vertical level (default: None)
|
|
1431
|
+
land: Optional CARMASurfaceProperties instance representing land surface properties (default: None)
|
|
1432
|
+
ocean: Optional CARMASurfaceProperties instance representing ocean surface properties (default: None)
|
|
1433
|
+
ice: Optional CARMASurfaceProperties instance representing ice surface properties (default: None)
|
|
1434
|
+
"""
|
|
1435
|
+
_backend._carma._step(
|
|
1436
|
+
self._carma_state_instance,
|
|
1437
|
+
cloud_fraction=cloud_fraction,
|
|
1438
|
+
critical_relative_humidity=critical_relative_humidity,
|
|
1439
|
+
land=land,
|
|
1440
|
+
ocean=ocean,
|
|
1441
|
+
ice=ice
|
|
1442
|
+
)
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
class CARMA:
|
|
1446
|
+
"""
|
|
1447
|
+
A Python interface to the CARMA aerosol model.
|
|
1448
|
+
|
|
1449
|
+
This class provides a simplified interface for running CARMA simulations
|
|
1450
|
+
with configurable parameters.
|
|
1451
|
+
"""
|
|
1452
|
+
|
|
1453
|
+
def __init__(self, parameters: CARMAParameters):
|
|
1454
|
+
"""
|
|
1455
|
+
Initialize a CARMA instance.
|
|
1456
|
+
|
|
1457
|
+
Raises:
|
|
1458
|
+
ValueError: If CARMA backend is not available
|
|
1459
|
+
"""
|
|
1460
|
+
if not backend.carma_available():
|
|
1461
|
+
raise ValueError(
|
|
1462
|
+
"CARMA backend is not available on this platform.")
|
|
1463
|
+
|
|
1464
|
+
self._carma_instance = _backend._carma._create_carma(
|
|
1465
|
+
parameters.to_dict())
|
|
1466
|
+
self.__parameters = parameters
|
|
1467
|
+
|
|
1468
|
+
def __del__(self):
|
|
1469
|
+
"""Clean up the CARMA instance."""
|
|
1470
|
+
if hasattr(self, '_carma_instance') and self._carma_instance is not None:
|
|
1471
|
+
_backend._carma._delete_carma(self._carma_instance)
|
|
1472
|
+
|
|
1473
|
+
def __repr__(self):
|
|
1474
|
+
"""String representation of CARMA instance."""
|
|
1475
|
+
return f"CARMA() - Version: {version if version else 'Not available'}"
|
|
1476
|
+
|
|
1477
|
+
def __str__(self):
|
|
1478
|
+
"""String representation of CARMA instance."""
|
|
1479
|
+
return f"CARMA() - Version: {version if version else 'Not available'}"
|
|
1480
|
+
|
|
1481
|
+
def create_state(self, **kwargs) -> CARMAState:
|
|
1482
|
+
"""
|
|
1483
|
+
Create a CARMAState instance based on the current parameters.
|
|
1484
|
+
|
|
1485
|
+
Args:
|
|
1486
|
+
**kwargs: Additional keyword arguments to pass to CARMAState
|
|
1487
|
+
|
|
1488
|
+
Returns:
|
|
1489
|
+
CARMAState: Instance containing environmental variables for the simulation
|
|
1490
|
+
"""
|
|
1491
|
+
|
|
1492
|
+
return CARMAState(
|
|
1493
|
+
self._carma_instance,
|
|
1494
|
+
gases=self.__parameters.gases,
|
|
1495
|
+
**kwargs
|
|
1496
|
+
)
|
|
1497
|
+
|
|
1498
|
+
def get_group_properties(self) -> Tuple[xr.Dataset, Dict[str, int]]:
|
|
1499
|
+
"""
|
|
1500
|
+
Get the group properties for all groups.
|
|
1501
|
+
|
|
1502
|
+
Returns:
|
|
1503
|
+
Tuple[xr.Dataset, Dict[str, int]]: The group properties for all groups and a dictionary with their indices
|
|
1504
|
+
"""
|
|
1505
|
+
number_of_groups = len(self.__parameters.groups)
|
|
1506
|
+
number_of_elements = len(self.__parameters.elements)
|
|
1507
|
+
number_of_bins = self.__parameters.nbin
|
|
1508
|
+
number_of_wavelength_bins = len(self.__parameters.wavelength_bins)
|
|
1509
|
+
|
|
1510
|
+
if number_of_groups == 0:
|
|
1511
|
+
return xr.Dataset(), {}
|
|
1512
|
+
|
|
1513
|
+
# Initialize the property arrays
|
|
1514
|
+
properties = [
|
|
1515
|
+
"bin_radius",
|
|
1516
|
+
"bin_radius_lower_bound",
|
|
1517
|
+
"bin_radius_upper_bound",
|
|
1518
|
+
"bin_width",
|
|
1519
|
+
"bin_mass",
|
|
1520
|
+
"bin_width_mass",
|
|
1521
|
+
"bin_volume",
|
|
1522
|
+
"projected_area_ratio",
|
|
1523
|
+
"radius_ratio",
|
|
1524
|
+
"porosity_ratio",
|
|
1525
|
+
"extinction_coefficient",
|
|
1526
|
+
"single_scattering_albedo",
|
|
1527
|
+
"asymmetry_factor",
|
|
1528
|
+
"element_index_of_core_mass_elements",
|
|
1529
|
+
"number_of_monomers_per_bin",
|
|
1530
|
+
"particle_number_element_for_group",
|
|
1531
|
+
"number_of_core_mass_elements_for_group",
|
|
1532
|
+
"last_prognostic_bin"
|
|
1533
|
+
]
|
|
1534
|
+
|
|
1535
|
+
# Group arrays by shape
|
|
1536
|
+
per_bin = [
|
|
1537
|
+
"bin_radius",
|
|
1538
|
+
"bin_radius_lower_bound",
|
|
1539
|
+
"bin_radius_upper_bound",
|
|
1540
|
+
"bin_width",
|
|
1541
|
+
"bin_mass",
|
|
1542
|
+
"bin_width_mass",
|
|
1543
|
+
"bin_volume",
|
|
1544
|
+
"projected_area_ratio",
|
|
1545
|
+
"radius_ratio",
|
|
1546
|
+
"porosity_ratio",
|
|
1547
|
+
"number_of_monomers_per_bin"
|
|
1548
|
+
]
|
|
1549
|
+
per_bin_and_wavelength = [
|
|
1550
|
+
"extinction_coefficient",
|
|
1551
|
+
"single_scattering_albedo",
|
|
1552
|
+
"asymmetry_factor"
|
|
1553
|
+
]
|
|
1554
|
+
per_element = [
|
|
1555
|
+
"element_index_of_core_mass_elements",
|
|
1556
|
+
]
|
|
1557
|
+
per_group = [
|
|
1558
|
+
"particle_number_element_for_group",
|
|
1559
|
+
"number_of_core_mass_elements_for_group",
|
|
1560
|
+
"last_prognostic_bin"
|
|
1561
|
+
]
|
|
1562
|
+
|
|
1563
|
+
# Prepare arrays
|
|
1564
|
+
data = {prop: [] for prop in properties}
|
|
1565
|
+
for i_group in range(number_of_groups):
|
|
1566
|
+
group_value = _backend._carma._get_group_properties(self._carma_instance, i_group + 1)
|
|
1567
|
+
for prop in properties:
|
|
1568
|
+
data[prop].append(group_value.get(prop))
|
|
1569
|
+
|
|
1570
|
+
# Reshape arrays
|
|
1571
|
+
def reshape(arr, shape):
|
|
1572
|
+
return np.array(arr).reshape(shape)
|
|
1573
|
+
|
|
1574
|
+
# Helper for units
|
|
1575
|
+
def _get_units(prop):
|
|
1576
|
+
units = {
|
|
1577
|
+
"bin_radius": "m",
|
|
1578
|
+
"bin_radius_lower_bound": "m",
|
|
1579
|
+
"bin_radius_upper_bound": "m",
|
|
1580
|
+
"bin_width": "m",
|
|
1581
|
+
"bin_mass": "kg",
|
|
1582
|
+
"bin_width_mass": "kg",
|
|
1583
|
+
"bin_volume": "m^3",
|
|
1584
|
+
"projected_area_ratio": "-",
|
|
1585
|
+
"radius_ratio": "-",
|
|
1586
|
+
"porosity_ratio": "-",
|
|
1587
|
+
"extinction_coefficient": "-",
|
|
1588
|
+
"single_scattering_albedo": "-",
|
|
1589
|
+
"asymmetry_factor": "-",
|
|
1590
|
+
"element_index_of_core_mass_elements": "-",
|
|
1591
|
+
"number_of_monomers_per_bin": "-",
|
|
1592
|
+
"particle_number_element_for_group": "-",
|
|
1593
|
+
"number_of_core_mass_elements_for_group": "-",
|
|
1594
|
+
"last_prognostic_bin": "-"
|
|
1595
|
+
}
|
|
1596
|
+
return units.get(prop, "")
|
|
1597
|
+
|
|
1598
|
+
dataset_vars = {}
|
|
1599
|
+
|
|
1600
|
+
# Per-bin properties: shape (number_of_groups, number_of_bins)
|
|
1601
|
+
for prop in per_bin:
|
|
1602
|
+
arr = reshape(data[prop], (number_of_groups, number_of_bins))
|
|
1603
|
+
dataset_vars[prop] = (("group", "bin"), arr, {"units": _get_units(prop)})
|
|
1604
|
+
|
|
1605
|
+
# Per-bin-and-wavelength properties: shape (number_of_groups, number_of_bins, number_of_wavelength_bins)
|
|
1606
|
+
for prop in per_bin_and_wavelength:
|
|
1607
|
+
arr = reshape(data[prop], (number_of_groups, number_of_bins, number_of_wavelength_bins))
|
|
1608
|
+
dataset_vars[prop] = (("group", "bin", "wavelength"), arr, {"units": _get_units(prop)})
|
|
1609
|
+
|
|
1610
|
+
# Per-element properties: shape (number_of_groups, number_of_elements)
|
|
1611
|
+
for prop in per_element:
|
|
1612
|
+
arr = reshape(data[prop], (number_of_groups, number_of_elements))
|
|
1613
|
+
dataset_vars[prop] = (("group", "element"), arr, {"units": _get_units(prop)})
|
|
1614
|
+
|
|
1615
|
+
# Per-group properties: shape (number_of_groups,)
|
|
1616
|
+
for prop in per_group:
|
|
1617
|
+
arr = reshape(data[prop], (number_of_groups,))
|
|
1618
|
+
dataset_vars[prop] = (("group",), arr, {"units": _get_units(prop)})
|
|
1619
|
+
|
|
1620
|
+
return (xr.Dataset(
|
|
1621
|
+
data_vars=dataset_vars,
|
|
1622
|
+
coords={
|
|
1623
|
+
"group": np.arange(1, number_of_groups + 1),
|
|
1624
|
+
"bin": np.arange(1, number_of_bins + 1),
|
|
1625
|
+
"wavelength": np.arange(1, number_of_wavelength_bins + 1),
|
|
1626
|
+
"element": np.arange(1, number_of_elements + 1)
|
|
1627
|
+
}
|
|
1628
|
+
), self.__parameters.groups)
|
|
1629
|
+
|
|
1630
|
+
def get_element_properties(self) -> Tuple[xr.Dataset, Dict[str, int]]:
|
|
1631
|
+
"""
|
|
1632
|
+
Get the element properties for all elements
|
|
1633
|
+
|
|
1634
|
+
Returns:
|
|
1635
|
+
Tuple[xr.Dataset, Dict[str, Any]]: The properties for each element and a dictionary of element indices
|
|
1636
|
+
"""
|
|
1637
|
+
number_of_elements = len(self.__parameters.elements)
|
|
1638
|
+
number_of_bins = self.__parameters.nbin
|
|
1639
|
+
number_of_wavelength_bins = len(self.__parameters.wavelength_bins)
|
|
1640
|
+
number_of_refractive_indices = None
|
|
1641
|
+
|
|
1642
|
+
if number_of_elements == 0:
|
|
1643
|
+
return xr.Dataset(), {}
|
|
1644
|
+
|
|
1645
|
+
# Initialize the property arrays
|
|
1646
|
+
properties = [
|
|
1647
|
+
"mass_density",
|
|
1648
|
+
"refractive_indices",
|
|
1649
|
+
"hygroscopicity_parameter"
|
|
1650
|
+
]
|
|
1651
|
+
|
|
1652
|
+
# Group arrays by shape
|
|
1653
|
+
per_bin = ["mass_density"]
|
|
1654
|
+
per_wavelength_and_refidx = ["refractive_indices"]
|
|
1655
|
+
per_element = ["hygroscopicity_parameter"]
|
|
1656
|
+
|
|
1657
|
+
# Prepare arrays
|
|
1658
|
+
data = {prop: [] for prop in properties}
|
|
1659
|
+
for i_elem in range(number_of_elements):
|
|
1660
|
+
element_value = _backend._carma._get_element_properties(self._carma_instance, i_elem + 1)
|
|
1661
|
+
if number_of_refractive_indices is None:
|
|
1662
|
+
number_of_refractive_indices = element_value["number_of_refractive_indices"]
|
|
1663
|
+
else:
|
|
1664
|
+
if number_of_refractive_indices != element_value["number_of_refractive_indices"]:
|
|
1665
|
+
raise ValueError("Inconsistent number of refractive indices found.")
|
|
1666
|
+
for prop in properties:
|
|
1667
|
+
data[prop].append(element_value.get(prop))
|
|
1668
|
+
|
|
1669
|
+
# Reshape arrays
|
|
1670
|
+
def reshape(arr, shape):
|
|
1671
|
+
return np.array(arr).reshape(shape)
|
|
1672
|
+
|
|
1673
|
+
# Helper for units
|
|
1674
|
+
def _get_units(prop):
|
|
1675
|
+
units = {
|
|
1676
|
+
"mass_density": "kg m-3",
|
|
1677
|
+
"refractive_indices": "-",
|
|
1678
|
+
"hygroscopicity_parameter": "-"
|
|
1679
|
+
}
|
|
1680
|
+
return units.get(prop, "")
|
|
1681
|
+
|
|
1682
|
+
dataset_vars = {}
|
|
1683
|
+
|
|
1684
|
+
# Per-bin properties: shape (number_of_elements, number_of_bins)
|
|
1685
|
+
for prop in per_bin:
|
|
1686
|
+
arr = reshape(data[prop], (number_of_elements, number_of_bins))
|
|
1687
|
+
dataset_vars[prop] = (("element", "bin"), arr, {"units": _get_units(prop)})
|
|
1688
|
+
|
|
1689
|
+
# Per-wavelength and refractive index properties: shape
|
|
1690
|
+
# (number_of_elements, number_of_refractive_indices,
|
|
1691
|
+
# number_of_wavelength_bins)
|
|
1692
|
+
for prop in per_wavelength_and_refidx:
|
|
1693
|
+
arr = reshape(data[prop], (number_of_elements, number_of_refractive_indices, number_of_wavelength_bins))
|
|
1694
|
+
dataset_vars[prop] = (("element", "refractive_index", "wavelength"), arr, {"units": _get_units(prop)})
|
|
1695
|
+
|
|
1696
|
+
# Per-element properties: shape (number_of_elements,)
|
|
1697
|
+
for prop in per_element:
|
|
1698
|
+
arr = reshape(data[prop], (number_of_elements,))
|
|
1699
|
+
dataset_vars[prop] = (("element",), arr, {"units": _get_units(prop)})
|
|
1700
|
+
|
|
1701
|
+
return (xr.Dataset(
|
|
1702
|
+
data_vars=dataset_vars,
|
|
1703
|
+
coords={
|
|
1704
|
+
"bin": np.arange(1, number_of_bins + 1),
|
|
1705
|
+
"wavelength": np.arange(1, number_of_wavelength_bins + 1),
|
|
1706
|
+
"refractive_index": np.arange(1, number_of_refractive_indices + 1),
|
|
1707
|
+
"element": np.arange(1, number_of_elements + 1)
|
|
1708
|
+
}
|
|
1709
|
+
), self.__parameters.elements)
|
|
1710
|
+
|
|
1711
|
+
def get_gas_properties(self) -> CARMAGasConfig:
|
|
1712
|
+
"""
|
|
1713
|
+
Get the gas properties for all gases.
|
|
1714
|
+
|
|
1715
|
+
Returns:
|
|
1716
|
+
CARMAGasConfig: The gas configurations
|
|
1717
|
+
"""
|
|
1718
|
+
return self.__parameters.gases
|
|
1719
|
+
|
|
1720
|
+
def get_solute_properties(self) -> CARMASoluteConfig:
|
|
1721
|
+
"""
|
|
1722
|
+
Get the solute properties for all solutes.
|
|
1723
|
+
|
|
1724
|
+
Returns:
|
|
1725
|
+
CARMASoluteConfig: The solute configurations
|
|
1726
|
+
"""
|
|
1727
|
+
return self.__parameters.solutes
|