musica 0.12.0__cp312-cp312-win32.whl → 0.12.1__cp312-cp312-win32.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 musica might be problematic. Click here for more details.
- musica/CMakeLists.txt +28 -2
- musica/__init__.py +9 -49
- musica/_musica.cp312-win32.pyd +0 -0
- musica/_version.py +1 -1
- musica/backend.py +41 -0
- musica/binding_common.cpp +23 -6
- musica/carma.cpp +911 -0
- musica/carma.py +1729 -0
- musica/constants.py +1 -1
- musica/cpu_binding.cpp +2 -1
- musica/cuda.py +4 -1
- musica/examples/__init__.py +1 -0
- musica/examples/carma_aluminum.py +123 -0
- musica/examples/carma_sulfate.py +245 -0
- musica/examples/examples.py +165 -0
- musica/examples/sulfate_box_model.py +439 -0
- musica/examples/ts1_latin_hypercube.py +245 -0
- musica/gpu_binding.cpp +2 -1
- musica/main.py +89 -0
- musica/mechanism_configuration/__init__.py +1 -1
- musica/mechanism_configuration/aqueous_equilibrium.py +227 -54
- musica/mechanism_configuration/arrhenius.py +228 -42
- musica/mechanism_configuration/branched.py +249 -66
- musica/mechanism_configuration/condensed_phase_arrhenius.py +243 -50
- musica/mechanism_configuration/condensed_phase_photolysis.py +16 -19
- musica/mechanism_configuration/emission.py +10 -6
- musica/mechanism_configuration/first_order_loss.py +133 -26
- musica/mechanism_configuration/henrys_law.py +7 -48
- musica/mechanism_configuration/mechanism_configuration.py +114 -41
- musica/mechanism_configuration/phase.py +6 -2
- musica/mechanism_configuration/photolysis.py +12 -7
- musica/mechanism_configuration/reactions.py +20 -8
- musica/mechanism_configuration/simpol_phase_transfer.py +180 -51
- musica/mechanism_configuration/species.py +23 -4
- musica/mechanism_configuration/surface.py +14 -9
- musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
- musica/mechanism_configuration/troe.py +259 -44
- musica/mechanism_configuration/tunneling.py +196 -49
- musica/mechanism_configuration/user_defined.py +9 -4
- musica/mechanism_configuration/wet_deposition.py +11 -8
- musica/mechanism_configuration.cpp +184 -95
- musica/musica.cpp +48 -61
- musica/test/examples/v1/full_configuration/full_configuration.json +39 -22
- musica/test/examples/v1/full_configuration/full_configuration.yaml +29 -20
- musica/test/{test_analytical.py → integration/test_analytical.py} +0 -1
- musica/test/integration/test_carma.py +227 -0
- musica/test/integration/test_carma_aluminum.py +11 -0
- musica/test/integration/test_carma_sulfate.py +16 -0
- musica/test/integration/test_sulfate_box_model.py +34 -0
- musica/test/integration/test_tuvx.py +62 -0
- musica/test/unit/test_parser.py +64 -0
- musica/test/{test_serializer.py → unit/test_serializer.py} +2 -2
- musica/test/{test_util_full_mechanism.py → unit/test_util_full_mechanism.py} +152 -122
- musica/tools/prepare_build_environment_linux.sh +39 -32
- musica/tools/prepare_build_environment_macos.sh +1 -0
- musica/tuvx.cpp +93 -0
- musica/tuvx.py +199 -0
- musica/types.py +104 -60
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/METADATA +40 -39
- musica-0.12.1.dist-info/RECORD +69 -0
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/WHEEL +1 -1
- musica-0.12.1.dist-info/entry_points.txt +3 -0
- musica/test/examples/v0/config.json +0 -7
- musica/test/examples/v0/config.yaml +0 -3
- musica/test/examples/v0/reactions.json +0 -193
- musica/test/examples/v0/reactions.yaml +0 -142
- musica/test/examples/v0/species.json +0 -40
- musica/test/examples/v0/species.yaml +0 -19
- musica/test/test_parser.py +0 -57
- musica/test/tuvx.py +0 -10
- musica/tools/prepare_build_environment_windows.sh +0 -22
- musica-0.12.0.dist-info/RECORD +0 -57
- /musica/test/{test_chapman.py → integration/test_chapman.py} +0 -0
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/licenses/AUTHORS.md +0 -0
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -101,22 +101,12 @@
|
|
|
101
101
|
}
|
|
102
102
|
],
|
|
103
103
|
"reactions": [
|
|
104
|
-
{
|
|
105
|
-
"type": "HL_PHASE_TRANSFER",
|
|
106
|
-
"gas phase": "gas",
|
|
107
|
-
"gas-phase species": "H2O2",
|
|
108
|
-
"aerosol phase": "aqueous aerosol",
|
|
109
|
-
"aerosol-phase species": "H2O2_aq",
|
|
110
|
-
"aerosol-phase water": "H2O_aq",
|
|
111
|
-
"name": "my henry's law",
|
|
112
|
-
"__irrelevant": "2"
|
|
113
|
-
},
|
|
114
104
|
{
|
|
115
105
|
"type": "SIMPOL_PHASE_TRANSFER",
|
|
116
106
|
"gas phase": "gas",
|
|
117
107
|
"gas-phase species": "ethanol",
|
|
118
|
-
"
|
|
119
|
-
"
|
|
108
|
+
"condensed phase": "aqueous aerosol",
|
|
109
|
+
"condensed-phase species": "ethanol_aq",
|
|
120
110
|
"B": [
|
|
121
111
|
-1.97E+03,
|
|
122
112
|
2.91E+00,
|
|
@@ -128,8 +118,8 @@
|
|
|
128
118
|
},
|
|
129
119
|
{
|
|
130
120
|
"type": "AQUEOUS_EQUILIBRIUM",
|
|
131
|
-
"
|
|
132
|
-
"
|
|
121
|
+
"condensed phase": "aqueous aerosol",
|
|
122
|
+
"condensed-phase water": "H2O_aq",
|
|
133
123
|
"A": 1.14e-2,
|
|
134
124
|
"C": 2300.0,
|
|
135
125
|
"k_reverse": 0.32,
|
|
@@ -154,8 +144,7 @@
|
|
|
154
144
|
},
|
|
155
145
|
{
|
|
156
146
|
"type": "CONDENSED_PHASE_ARRHENIUS",
|
|
157
|
-
"
|
|
158
|
-
"aerosol-phase water": "H2O_aq",
|
|
147
|
+
"condensed phase": "aqueous aerosol",
|
|
159
148
|
"A": 123.45,
|
|
160
149
|
"Ea": 123.45,
|
|
161
150
|
"B": 1.3,
|
|
@@ -182,8 +171,7 @@
|
|
|
182
171
|
},
|
|
183
172
|
{
|
|
184
173
|
"type": "CONDENSED_PHASE_ARRHENIUS",
|
|
185
|
-
"
|
|
186
|
-
"aerosol-phase water": "H2O_aq",
|
|
174
|
+
"condensed phase": "aqueous aerosol",
|
|
187
175
|
"A": 123.45,
|
|
188
176
|
"C": 123.45,
|
|
189
177
|
"B": 1.3,
|
|
@@ -209,8 +197,7 @@
|
|
|
209
197
|
},
|
|
210
198
|
{
|
|
211
199
|
"type": "CONDENSED_PHASE_PHOTOLYSIS",
|
|
212
|
-
"
|
|
213
|
-
"aerosol-phase water": "H2O_aq",
|
|
200
|
+
"condensed phase": "aqueous aerosol",
|
|
214
201
|
"reactants": [
|
|
215
202
|
{
|
|
216
203
|
"species name": "H2O2_aq",
|
|
@@ -287,7 +274,7 @@
|
|
|
287
274
|
"coefficient": 1
|
|
288
275
|
}
|
|
289
276
|
],
|
|
290
|
-
"
|
|
277
|
+
"condensed phase": "surface reacting phase",
|
|
291
278
|
"name": "my surface",
|
|
292
279
|
"__irrelevant": "2"
|
|
293
280
|
},
|
|
@@ -321,6 +308,36 @@
|
|
|
321
308
|
"name": "my troe",
|
|
322
309
|
"__irrelevant": "2"
|
|
323
310
|
},
|
|
311
|
+
{
|
|
312
|
+
"type": "TERNARY_CHEMICAL_ACTIVATION",
|
|
313
|
+
"gas phase": "gas",
|
|
314
|
+
"name": "my ternary chemical activation",
|
|
315
|
+
"reactants": [
|
|
316
|
+
{
|
|
317
|
+
"species name": "B",
|
|
318
|
+
"coefficient": 1
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"species name": "M",
|
|
322
|
+
"coefficient": 1
|
|
323
|
+
}
|
|
324
|
+
],
|
|
325
|
+
"products": [
|
|
326
|
+
{
|
|
327
|
+
"species name": "C",
|
|
328
|
+
"coefficient": 1
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
"k0_A": 32.1,
|
|
332
|
+
"k0_B": -2.3,
|
|
333
|
+
"k0_C": 102.3,
|
|
334
|
+
"kinf_A": 63.4,
|
|
335
|
+
"kinf_B": -1.3,
|
|
336
|
+
"kinf_C": 908.5,
|
|
337
|
+
"Fc": 1.3,
|
|
338
|
+
"N": 32.1,
|
|
339
|
+
"__irrelevant": "2"
|
|
340
|
+
},
|
|
324
341
|
{
|
|
325
342
|
"type": "BRANCHED_NO_RO2",
|
|
326
343
|
"gas phase": "gas",
|
|
@@ -372,7 +389,7 @@
|
|
|
372
389
|
},
|
|
373
390
|
{
|
|
374
391
|
"type": "WET_DEPOSITION",
|
|
375
|
-
"
|
|
392
|
+
"condensed phase": "cloud",
|
|
376
393
|
"name": "rxn cloud",
|
|
377
394
|
"scaling factor": 12.3,
|
|
378
395
|
"__irrelevant": "2"
|
|
@@ -65,19 +65,11 @@ phases:
|
|
|
65
65
|
- B
|
|
66
66
|
- C
|
|
67
67
|
reactions:
|
|
68
|
-
- type: HL_PHASE_TRANSFER
|
|
69
|
-
gas phase: gas
|
|
70
|
-
gas-phase species: H2O2
|
|
71
|
-
aerosol phase: aqueous aerosol
|
|
72
|
-
aerosol-phase species: H2O2_aq
|
|
73
|
-
aerosol-phase water: H2O_aq
|
|
74
|
-
name: my henry's law
|
|
75
|
-
__irrelevant: "2"
|
|
76
68
|
- type: SIMPOL_PHASE_TRANSFER
|
|
77
69
|
gas phase: gas
|
|
78
70
|
gas-phase species: ethanol
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
condensed phase: aqueous aerosol
|
|
72
|
+
condensed-phase species: ethanol_aq
|
|
81
73
|
B:
|
|
82
74
|
- -1970
|
|
83
75
|
- 2.91
|
|
@@ -86,8 +78,8 @@ reactions:
|
|
|
86
78
|
name: my simpol
|
|
87
79
|
__irrelevant: "2"
|
|
88
80
|
- type: AQUEOUS_EQUILIBRIUM
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
condensed phase: aqueous aerosol
|
|
82
|
+
condensed-phase water: H2O_aq
|
|
91
83
|
A: 0.0114
|
|
92
84
|
C: 2300
|
|
93
85
|
k_reverse: 0.32
|
|
@@ -102,8 +94,7 @@ reactions:
|
|
|
102
94
|
name: my aqueous eq
|
|
103
95
|
__irrelevant: "2"
|
|
104
96
|
- type: CONDENSED_PHASE_ARRHENIUS
|
|
105
|
-
|
|
106
|
-
aerosol-phase water: H2O_aq
|
|
97
|
+
condensed phase: aqueous aerosol
|
|
107
98
|
A: 123.45
|
|
108
99
|
Ea: 123.45
|
|
109
100
|
B: 1.3
|
|
@@ -120,8 +111,7 @@ reactions:
|
|
|
120
111
|
name: my condensed arrhenius
|
|
121
112
|
__irrelevant: "2"
|
|
122
113
|
- type: CONDENSED_PHASE_ARRHENIUS
|
|
123
|
-
|
|
124
|
-
aerosol-phase water: H2O_aq
|
|
114
|
+
condensed phase: aqueous aerosol
|
|
125
115
|
A: 123.45
|
|
126
116
|
C: 123.45
|
|
127
117
|
B: 1.3
|
|
@@ -137,8 +127,7 @@ reactions:
|
|
|
137
127
|
coefficient: 1
|
|
138
128
|
name: my other condensed arrhenius
|
|
139
129
|
- type: CONDENSED_PHASE_PHOTOLYSIS
|
|
140
|
-
|
|
141
|
-
aerosol-phase water: H2O_aq
|
|
130
|
+
condensed phase: aqueous aerosol
|
|
142
131
|
reactants:
|
|
143
132
|
- species name: H2O2_aq
|
|
144
133
|
coefficient: 1
|
|
@@ -184,7 +173,7 @@ reactions:
|
|
|
184
173
|
coefficient: 1
|
|
185
174
|
- species name: C
|
|
186
175
|
coefficient: 1
|
|
187
|
-
|
|
176
|
+
condensed phase: surface reacting phase
|
|
188
177
|
name: my surface
|
|
189
178
|
__irrelevant: "2"
|
|
190
179
|
- type: TROE
|
|
@@ -207,6 +196,26 @@ reactions:
|
|
|
207
196
|
N: 0.8
|
|
208
197
|
name: my troe
|
|
209
198
|
__irrelevant: "2"
|
|
199
|
+
- type: TERNARY_CHEMICAL_ACTIVATION
|
|
200
|
+
gas phase: gas
|
|
201
|
+
name: my ternary chemical activation
|
|
202
|
+
reactants:
|
|
203
|
+
- species name: B
|
|
204
|
+
coefficient: 1
|
|
205
|
+
- species name: M
|
|
206
|
+
coefficient: 1
|
|
207
|
+
products:
|
|
208
|
+
- species name: C
|
|
209
|
+
coefficient: 1
|
|
210
|
+
k0_A: 32.1
|
|
211
|
+
k0_B: -2.3
|
|
212
|
+
k0_C: 102.3
|
|
213
|
+
kinf_A: 63.4
|
|
214
|
+
kinf_B: -1.3
|
|
215
|
+
kinf_C: 908.5
|
|
216
|
+
Fc: 1.3
|
|
217
|
+
"N": 32.1
|
|
218
|
+
__irrelevant: "2"
|
|
210
219
|
- type: BRANCHED_NO_RO2
|
|
211
220
|
gas phase: gas
|
|
212
221
|
reactants:
|
|
@@ -238,7 +247,7 @@ reactions:
|
|
|
238
247
|
coefficient: 1
|
|
239
248
|
__irrelevant: "2"
|
|
240
249
|
- type: WET_DEPOSITION
|
|
241
|
-
|
|
250
|
+
condensed phase: cloud
|
|
242
251
|
name: rxn cloud
|
|
243
252
|
scaling factor: 12.3
|
|
244
253
|
__irrelevant: "2"
|
|
@@ -118,7 +118,6 @@ def TestMultipleGridCell(solver, state, num_grid_cells, time_step, places=5):
|
|
|
118
118
|
rate_constants["USER.reaction 2"].append(
|
|
119
119
|
0.002 + random.uniform(-0.0001, 0.0001))
|
|
120
120
|
|
|
121
|
-
|
|
122
121
|
state.set_conditions(temperatures, pressures) # Air density should be calculated in the state
|
|
123
122
|
state.set_concentrations(concentrations)
|
|
124
123
|
state.set_user_defined_rate_parameters(rate_constants)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import musica
|
|
3
|
+
|
|
4
|
+
available = musica.backend.carma_available()
|
|
5
|
+
pytestmark = pytest.mark.skipif(
|
|
6
|
+
not available, reason="CARMA backend is not available")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_carma_version():
|
|
10
|
+
version = musica.carma.version
|
|
11
|
+
assert version is not None
|
|
12
|
+
assert isinstance(version, str)
|
|
13
|
+
print(f"CARMA version: {version}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_carma_with_all_components():
|
|
17
|
+
"""Test CARMA with multiple groups, elements, solutes, and gases"""
|
|
18
|
+
params = musica.CARMAParameters()
|
|
19
|
+
params.nz = 1
|
|
20
|
+
params.ny = 1
|
|
21
|
+
params.nx = 1
|
|
22
|
+
params.nbin = 3
|
|
23
|
+
params.dtime = 900.0
|
|
24
|
+
params.deltaz = 500.0
|
|
25
|
+
params.zmin = 1000.0
|
|
26
|
+
|
|
27
|
+
# Set up wavelength bins
|
|
28
|
+
params.wavelength_bins = [
|
|
29
|
+
musica.carma.CARMAWavelengthBin(
|
|
30
|
+
center=550e-9, width=50e-9, do_emission=True), # 550 nm ± 25 nm
|
|
31
|
+
musica.carma.CARMAWavelengthBin(
|
|
32
|
+
center=850e-9, width=100e-9, do_emission=True) # 850 nm ± 50 nm
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# Group 1: Aluminum particles (sphere)
|
|
36
|
+
aluminum_group = musica.carma.CARMAGroupConfig(
|
|
37
|
+
name="aluminum",
|
|
38
|
+
shortname="ALUM",
|
|
39
|
+
rmin=1e-8,
|
|
40
|
+
rmrat=2.0,
|
|
41
|
+
ishape=musica.carma.ParticleShape.SPHERE,
|
|
42
|
+
eshape=1.0,
|
|
43
|
+
is_fractal=False,
|
|
44
|
+
do_vtran=True,
|
|
45
|
+
do_drydep=True,
|
|
46
|
+
df=[1.8, 1.8, 1.8] # fractal dimension per bin
|
|
47
|
+
)
|
|
48
|
+
params.groups.append(aluminum_group)
|
|
49
|
+
|
|
50
|
+
# Group 2: Sulfate particles (sphere, with swelling)
|
|
51
|
+
sulfate_group = musica.carma.CARMAGroupConfig(
|
|
52
|
+
name="sulfate",
|
|
53
|
+
shortname="SULF",
|
|
54
|
+
rmin=5e-9,
|
|
55
|
+
rmrat=2.5,
|
|
56
|
+
ishape=musica.carma.ParticleShape.SPHERE,
|
|
57
|
+
eshape=1.0,
|
|
58
|
+
swelling_approach={
|
|
59
|
+
"algorithm": musica.carma.ParticleSwellingAlgorithm.FITZGERALD,
|
|
60
|
+
"composition": musica.carma.ParticleSwellingComposition.AMMONIUM_SULFATE
|
|
61
|
+
},
|
|
62
|
+
is_sulfate=True,
|
|
63
|
+
do_wetdep=True,
|
|
64
|
+
do_vtran=True,
|
|
65
|
+
solfac=0.8,
|
|
66
|
+
df=[2.0, 2.0, 2.0]
|
|
67
|
+
)
|
|
68
|
+
params.groups.append(sulfate_group)
|
|
69
|
+
|
|
70
|
+
# Group 3: Ice particles (hexagon)
|
|
71
|
+
ice_group = musica.carma.CARMAGroupConfig(
|
|
72
|
+
name="ice",
|
|
73
|
+
shortname="ICE",
|
|
74
|
+
rmin=2e-8,
|
|
75
|
+
rmrat=3.0,
|
|
76
|
+
ishape=musica.carma.ParticleShape.HEXAGON,
|
|
77
|
+
eshape=2.0, # aspect ratio
|
|
78
|
+
is_ice=True,
|
|
79
|
+
is_cloud=True,
|
|
80
|
+
do_vtran=True,
|
|
81
|
+
df=[1.5, 1.5, 1.5]
|
|
82
|
+
)
|
|
83
|
+
params.groups.append(ice_group)
|
|
84
|
+
|
|
85
|
+
# Element 1: Aluminum core (Group 1)
|
|
86
|
+
aluminum_element = musica.carma.CARMAElementConfig(
|
|
87
|
+
igroup=1,
|
|
88
|
+
name="Aluminum",
|
|
89
|
+
shortname="AL",
|
|
90
|
+
rho=2.70, # g/cm³
|
|
91
|
+
itype=musica.carma.ParticleType.INVOLATILE,
|
|
92
|
+
icomposition=musica.carma.ParticleComposition.ALUMINUM,
|
|
93
|
+
kappa=0.0,
|
|
94
|
+
is_shell=False # core
|
|
95
|
+
)
|
|
96
|
+
params.elements.append(aluminum_element)
|
|
97
|
+
|
|
98
|
+
# Element 2: Sulfate (Group 2)
|
|
99
|
+
sulfate_element = musica.carma.CARMAElementConfig(
|
|
100
|
+
igroup=2,
|
|
101
|
+
isolute=1, # linked to first solute
|
|
102
|
+
name="Sulfate",
|
|
103
|
+
shortname="SO4",
|
|
104
|
+
rho=1.84, # g/cm³
|
|
105
|
+
itype=musica.carma.ParticleType.VOLATILE,
|
|
106
|
+
icomposition=musica.carma.ParticleComposition.SULFURIC_ACID,
|
|
107
|
+
kappa=0.61, # hygroscopicity
|
|
108
|
+
is_shell=True
|
|
109
|
+
)
|
|
110
|
+
params.elements.append(sulfate_element)
|
|
111
|
+
|
|
112
|
+
# Element 3: Water on sulfate (Group 2)
|
|
113
|
+
water_element = musica.carma.CARMAElementConfig(
|
|
114
|
+
igroup=2,
|
|
115
|
+
name="Water",
|
|
116
|
+
shortname="H2O",
|
|
117
|
+
rho=1.0, # g/cm³
|
|
118
|
+
itype=musica.carma.ParticleType.CORE_MASS,
|
|
119
|
+
icomposition=musica.carma.ParticleComposition.WATER,
|
|
120
|
+
kappa=0.0,
|
|
121
|
+
is_shell=True
|
|
122
|
+
)
|
|
123
|
+
params.elements.append(water_element)
|
|
124
|
+
|
|
125
|
+
# Element 4: Ice (Group 3)
|
|
126
|
+
ice_element = musica.carma.CARMAElementConfig(
|
|
127
|
+
igroup=3,
|
|
128
|
+
name="Ice",
|
|
129
|
+
shortname="ICE",
|
|
130
|
+
rho=0.92, # g/cm³
|
|
131
|
+
itype=musica.carma.ParticleType.INVOLATILE,
|
|
132
|
+
icomposition=musica.carma.ParticleComposition.ICE,
|
|
133
|
+
kappa=0.0,
|
|
134
|
+
is_shell=False
|
|
135
|
+
)
|
|
136
|
+
params.elements.append(ice_element)
|
|
137
|
+
|
|
138
|
+
# Solute: Sulfate
|
|
139
|
+
sulfate_solute = musica.carma.CARMASoluteConfig(
|
|
140
|
+
name="Sulfate",
|
|
141
|
+
shortname="SO4",
|
|
142
|
+
ions=2,
|
|
143
|
+
wtmol=0.1324, # kg mol-1
|
|
144
|
+
rho=1840.0 # kg m-3
|
|
145
|
+
)
|
|
146
|
+
params.solutes.append(sulfate_solute)
|
|
147
|
+
|
|
148
|
+
# Gas: Water vapor
|
|
149
|
+
water_gas = musica.carma.CARMAGasConfig(
|
|
150
|
+
name="Water Vapor",
|
|
151
|
+
shortname="H2O",
|
|
152
|
+
wtmol=0.01801528, # kg mol-1
|
|
153
|
+
ivaprtn=musica.carma.VaporizationAlgorithm.H2O_MURPHY_2005,
|
|
154
|
+
icomposition=musica.carma.GasComposition.H2O,
|
|
155
|
+
dgc_threshold=1.0e-6,
|
|
156
|
+
ds_threshold=1.0e-4
|
|
157
|
+
)
|
|
158
|
+
params.gases.append(water_gas)
|
|
159
|
+
|
|
160
|
+
# Gas: Sulfuric acid
|
|
161
|
+
h2so4_gas = musica.carma.CARMAGasConfig(
|
|
162
|
+
name="Sulfuric Acid",
|
|
163
|
+
shortname="H2SO4",
|
|
164
|
+
wtmol=0.098079, # kg mol-1
|
|
165
|
+
ivaprtn=musica.carma.VaporizationAlgorithm.H2O_BUCK_1981,
|
|
166
|
+
icomposition=musica.carma.GasComposition.H2SO4,
|
|
167
|
+
dgc_threshold=0.05,
|
|
168
|
+
ds_threshold=0.1
|
|
169
|
+
)
|
|
170
|
+
params.gases.append(h2so4_gas)
|
|
171
|
+
|
|
172
|
+
# Gas: Sulfur dioxide
|
|
173
|
+
so2_gas = musica.carma.CARMAGasConfig(
|
|
174
|
+
name="Sulfur Dioxide",
|
|
175
|
+
shortname="SO2",
|
|
176
|
+
wtmol=0.064066, # kg mol-1
|
|
177
|
+
ivaprtn=musica.carma.VaporizationAlgorithm.H2O_BUCK_1981,
|
|
178
|
+
icomposition=musica.carma.GasComposition.SO2,
|
|
179
|
+
dgc_threshold=0.05,
|
|
180
|
+
ds_threshold=0.1
|
|
181
|
+
)
|
|
182
|
+
params.gases.append(so2_gas)
|
|
183
|
+
|
|
184
|
+
# Create CARMA instance and run
|
|
185
|
+
carma = musica.CARMA(params)
|
|
186
|
+
|
|
187
|
+
assert carma is not None
|
|
188
|
+
assert isinstance(carma, musica.CARMA)
|
|
189
|
+
|
|
190
|
+
state = carma.create_state(
|
|
191
|
+
vertical_center=[16500.0],
|
|
192
|
+
vertical_levels=[16500.0, 17000.0],
|
|
193
|
+
pressure=[90000.0],
|
|
194
|
+
pressure_levels=[101325.0, 90050.0],
|
|
195
|
+
temperature=[280.0],
|
|
196
|
+
time=0.0,
|
|
197
|
+
time_step=900.0, # 15 minutes
|
|
198
|
+
longitude=0.0,
|
|
199
|
+
latitude=0.0,
|
|
200
|
+
coordinates=musica.carma.CarmaCoordinates.CARTESIAN
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
assert state is not None
|
|
204
|
+
assert isinstance(state, musica.CARMAState)
|
|
205
|
+
|
|
206
|
+
state.set_bin(1, 1, 1.0)
|
|
207
|
+
state.set_detrain(1, 1, 1.0)
|
|
208
|
+
state.set_gas(1, 1.4e-3)
|
|
209
|
+
state.set_temperature(300.0)
|
|
210
|
+
state.set_air_density(1.2)
|
|
211
|
+
state.step(land=musica.carma.CARMASurfaceProperties(surface_friction_velocity=0.42, area_fraction=0.3),
|
|
212
|
+
ocean=musica.carma.CARMASurfaceProperties(
|
|
213
|
+
aerodynamic_resistance=0.1),
|
|
214
|
+
ice=musica.carma.CARMASurfaceProperties(area_fraction=0.2))
|
|
215
|
+
print(state.get_step_statistics())
|
|
216
|
+
print(state.get_bins())
|
|
217
|
+
print(state.get_detrained_masses())
|
|
218
|
+
print(state.get_environmental_values())
|
|
219
|
+
print(state.get_gases())
|
|
220
|
+
print(carma.get_group_properties())
|
|
221
|
+
print(carma.get_element_properties())
|
|
222
|
+
print(carma.get_gas_properties())
|
|
223
|
+
print(carma.get_solute_properties())
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == '__main__':
|
|
227
|
+
pytest.main([__file__])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import musica
|
|
3
|
+
|
|
4
|
+
available = musica.backend.carma_available()
|
|
5
|
+
pytestmark = pytest.mark.skipif(
|
|
6
|
+
not available, reason="CARMA backend is not available")
|
|
7
|
+
|
|
8
|
+
def test_carma_aluminum():
|
|
9
|
+
from musica.examples import carma_aluminum
|
|
10
|
+
state = carma_aluminum.run_carma_aluminum_example()
|
|
11
|
+
assert state is not None, "State should not be None"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import musica
|
|
3
|
+
|
|
4
|
+
available = musica.backend.carma_available()
|
|
5
|
+
pytestmark = pytest.mark.skipif(
|
|
6
|
+
not available, reason="CARMA backend is not available")
|
|
7
|
+
|
|
8
|
+
def test_carma_sulfate():
|
|
9
|
+
from musica.examples import carma_sulfate
|
|
10
|
+
env_state, gas_state, bin_state = carma_sulfate.run_carma_sulfate_example()
|
|
11
|
+
# Basic assertions to verify the simulation ran successfully
|
|
12
|
+
assert env_state is not None, "Environmental state should not be None"
|
|
13
|
+
assert gas_state is not None, "Gas state should not be None"
|
|
14
|
+
assert bin_state is not None, "Bin state should not be None"
|
|
15
|
+
# Optionally, check expected dimensions or variables
|
|
16
|
+
assert hasattr(bin_state, "mass_mixing_ratio"), "Bin state should have mass_mixing_ratio"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import musica
|
|
3
|
+
|
|
4
|
+
available = musica.backend.carma_available()
|
|
5
|
+
pytestmark = pytest.mark.skipif(
|
|
6
|
+
not available, reason="CARMA backend is not available")
|
|
7
|
+
|
|
8
|
+
def test_sulfate_box_model():
|
|
9
|
+
"""Test the sulfate box model implementation."""
|
|
10
|
+
from musica.examples import sulfate_box_model
|
|
11
|
+
concentrations, times, sulfate_data = sulfate_box_model.run_box_model()
|
|
12
|
+
|
|
13
|
+
# Basic assertions to verify the simulation ran successfully
|
|
14
|
+
assert concentrations is not None, "Concentrations should not be None"
|
|
15
|
+
assert len(concentrations) > 0, "Should have concentration data"
|
|
16
|
+
assert times is not None, "Times should not be None"
|
|
17
|
+
assert len(times) > 0, "Should have time data"
|
|
18
|
+
assert sulfate_data is not None, "CARMA sulfate data should not be None"
|
|
19
|
+
|
|
20
|
+
# Check that we have the expected species
|
|
21
|
+
expected_species = ["HO2", "H2O2", "SO2", "SO3", "H2SO4", "H2O"]
|
|
22
|
+
for species in expected_species:
|
|
23
|
+
assert species in concentrations.columns, f"Missing species: {species}"
|
|
24
|
+
|
|
25
|
+
# Check that CARMA data has expected structure
|
|
26
|
+
assert hasattr(sulfate_data, "mass_mixing_ratio"), "CARMA data should have mass_mixing_ratio"
|
|
27
|
+
assert "time" in sulfate_data.dims, "CARMA data should have time dimension"
|
|
28
|
+
assert "bin" in sulfate_data.dims, "CARMA data should have bin dimension"
|
|
29
|
+
|
|
30
|
+
print(f"✅ Test passed! Simulated {len(times)} time steps over {times[-1]:.2f} hours")
|
|
31
|
+
print(f" Chemical species tracked: {list(concentrations.columns)}")
|
|
32
|
+
print(f" CARMA bins: {len(sulfate_data.bin)}")
|
|
33
|
+
print(f" Vertical levels: {len(sulfate_data.vertical_center)}")
|
|
34
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import musica
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
available = musica.backend.tuvx_available()
|
|
6
|
+
pytestmark = pytest.mark.skipif(not available, reason="TUV-x backend is not available")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_tuvx_version():
|
|
10
|
+
version = musica.tuvx.version
|
|
11
|
+
assert version is not None
|
|
12
|
+
assert isinstance(version, str)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_full_tuvx(monkeypatch):
|
|
16
|
+
monkeypatch.chdir("configs/tuvx")
|
|
17
|
+
file = "tuv_5_4.json"
|
|
18
|
+
tuvx = musica.TUVX(file)
|
|
19
|
+
assert tuvx is not None
|
|
20
|
+
|
|
21
|
+
heating_rates = tuvx.heating_rate_names
|
|
22
|
+
photolysis_rates = tuvx.photolysis_rate_names
|
|
23
|
+
rates = tuvx.run()
|
|
24
|
+
|
|
25
|
+
assert len(rates) > 0, "No photolysis rates found"
|
|
26
|
+
assert len(heating_rates) == 0, "Heating rates should be empty for this config"
|
|
27
|
+
assert len(photolysis_rates) > 0, "No photolysis rates found"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_fixed_tuvx(monkeypatch):
|
|
31
|
+
monkeypatch.chdir("src")
|
|
32
|
+
file = "test/data/tuvx/fixed/config.json"
|
|
33
|
+
tuvx = musica.TUVX(file)
|
|
34
|
+
assert tuvx is not None
|
|
35
|
+
|
|
36
|
+
# Access properties multiple times
|
|
37
|
+
photolysis_names_1 = tuvx.photolysis_rate_names
|
|
38
|
+
photolysis_names_2 = tuvx.photolysis_rate_names
|
|
39
|
+
heating_names_1 = tuvx.heating_rate_names
|
|
40
|
+
heating_names_2 = tuvx.heating_rate_names
|
|
41
|
+
|
|
42
|
+
# Verify they return the same object (cached)
|
|
43
|
+
assert photolysis_names_1 is photolysis_names_2
|
|
44
|
+
assert heating_names_1 is heating_names_2
|
|
45
|
+
|
|
46
|
+
assert len(heating_names_1) > 0, "No heating rates found"
|
|
47
|
+
assert len(photolysis_names_1) > 0, "No photolysis rates found"
|
|
48
|
+
|
|
49
|
+
# these fail to run due to missing solar zenith angle, but that's not required and these should be able to run
|
|
50
|
+
# rates = tuvx.run()
|
|
51
|
+
# print(f"Rates: {rates}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_tuvx_initialization_errors():
|
|
55
|
+
"""Test error handling during TUVX initialization."""
|
|
56
|
+
# Test with non-existent file
|
|
57
|
+
with pytest.raises(FileNotFoundError):
|
|
58
|
+
musica.TUVX("non_existent_config.json")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
pytest.main([__file__])
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import musica.mechanism_configuration as mc
|
|
3
|
+
from musica.mechanism_configuration import Parser
|
|
4
|
+
from test_util_full_mechanism import get_fully_defined_mechanism, validate_full_v1_mechanism
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_parsed_full_v1_configuration():
|
|
8
|
+
parser = Parser()
|
|
9
|
+
extensions = [".yaml", ".json"]
|
|
10
|
+
for extension in extensions:
|
|
11
|
+
path = f"musica/test/examples/v1/full_configuration/full_configuration{extension}"
|
|
12
|
+
mechanism = parser.parse(path)
|
|
13
|
+
validate_full_v1_mechanism(mechanism)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_parser_reports_bad_files():
|
|
17
|
+
parser = Parser()
|
|
18
|
+
extensions = [".yaml", ".json"]
|
|
19
|
+
for extension in extensions:
|
|
20
|
+
path = f"musica/test/examples/_missing_configuration{extension}"
|
|
21
|
+
with pytest.raises(Exception):
|
|
22
|
+
parser.parse(path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_hard_coded_full_v1_configuration():
|
|
26
|
+
MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
|
|
27
|
+
validate_full_v1_mechanism(MECHANISM_FULLY_DEFINED)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_hard_coded_default_constructed_types():
|
|
31
|
+
arrhenius = mc.Arrhenius()
|
|
32
|
+
assert arrhenius.type == mc.ReactionType.Arrhenius
|
|
33
|
+
condensed_phase_arrhenius = mc.CondensedPhaseArrhenius()
|
|
34
|
+
assert condensed_phase_arrhenius.type == mc.ReactionType.CondensedPhaseArrhenius
|
|
35
|
+
condensed_phase_photolysis = mc.CondensedPhasePhotolysis()
|
|
36
|
+
assert condensed_phase_photolysis.type == mc.ReactionType.CondensedPhasePhotolysis
|
|
37
|
+
emission = mc.Emission()
|
|
38
|
+
assert emission.type == mc.ReactionType.Emission
|
|
39
|
+
first_order_loss = mc.FirstOrderLoss()
|
|
40
|
+
assert first_order_loss.type == mc.ReactionType.FirstOrderLoss
|
|
41
|
+
henrys_law = mc.HenrysLaw()
|
|
42
|
+
assert henrys_law.type == mc.ReactionType.HenrysLaw
|
|
43
|
+
photolysis = mc.Photolysis()
|
|
44
|
+
assert photolysis.type == mc.ReactionType.Photolysis
|
|
45
|
+
simpol_phase_transfer = mc.SimpolPhaseTransfer()
|
|
46
|
+
assert simpol_phase_transfer.type == mc.ReactionType.SimpolPhaseTransfer
|
|
47
|
+
surface = mc.Surface()
|
|
48
|
+
assert surface.type == mc.ReactionType.Surface
|
|
49
|
+
troe = mc.Troe()
|
|
50
|
+
assert troe.type == mc.ReactionType.Troe
|
|
51
|
+
ternary_chemical_activation = mc.TernaryChemicalActivation()
|
|
52
|
+
assert ternary_chemical_activation.type == mc.ReactionType.TernaryChemicalActivation
|
|
53
|
+
tunneling = mc.Tunneling()
|
|
54
|
+
assert tunneling.type == mc.ReactionType.Tunneling
|
|
55
|
+
wet_deposition = mc.WetDeposition()
|
|
56
|
+
assert wet_deposition.type == mc.ReactionType.WetDeposition
|
|
57
|
+
branched = mc.Branched()
|
|
58
|
+
assert branched.type == mc.ReactionType.Branched
|
|
59
|
+
user_defined = mc.UserDefined()
|
|
60
|
+
assert user_defined.type == mc.ReactionType.UserDefined
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
pytest.main([__file__])
|
|
@@ -48,7 +48,7 @@ def test_bad_inputs():
|
|
|
48
48
|
def test_path_creation(tmp_path):
|
|
49
49
|
mechanism = Mechanism(name="Full Configuration")
|
|
50
50
|
path = f"{tmp_path}/non_existant_path/"
|
|
51
|
-
assert not os.path.exists(path)
|
|
51
|
+
assert not os.path.exists(path)
|
|
52
52
|
MechanismSerializer.serialize(mechanism, f"{path}test_mechanism.json")
|
|
53
53
|
assert os.path.exists(path)
|
|
54
54
|
|
|
@@ -57,7 +57,7 @@ def test_overwrite_file(tmp_path):
|
|
|
57
57
|
mechanism = Mechanism(name="Full Configuration")
|
|
58
58
|
file_path = f'{tmp_path}/test_mechanism.json'
|
|
59
59
|
assert not os.path.exists(file_path)
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
# write first file
|
|
62
62
|
MechanismSerializer.serialize(mechanism, file_path)
|
|
63
63
|
files = list(tmp_path.iterdir())
|