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.

Files changed (75) hide show
  1. musica/CMakeLists.txt +28 -2
  2. musica/__init__.py +9 -49
  3. musica/_musica.cp312-win32.pyd +0 -0
  4. musica/_version.py +1 -1
  5. musica/backend.py +41 -0
  6. musica/binding_common.cpp +23 -6
  7. musica/carma.cpp +911 -0
  8. musica/carma.py +1729 -0
  9. musica/constants.py +1 -1
  10. musica/cpu_binding.cpp +2 -1
  11. musica/cuda.py +4 -1
  12. musica/examples/__init__.py +1 -0
  13. musica/examples/carma_aluminum.py +123 -0
  14. musica/examples/carma_sulfate.py +245 -0
  15. musica/examples/examples.py +165 -0
  16. musica/examples/sulfate_box_model.py +439 -0
  17. musica/examples/ts1_latin_hypercube.py +245 -0
  18. musica/gpu_binding.cpp +2 -1
  19. musica/main.py +89 -0
  20. musica/mechanism_configuration/__init__.py +1 -1
  21. musica/mechanism_configuration/aqueous_equilibrium.py +227 -54
  22. musica/mechanism_configuration/arrhenius.py +228 -42
  23. musica/mechanism_configuration/branched.py +249 -66
  24. musica/mechanism_configuration/condensed_phase_arrhenius.py +243 -50
  25. musica/mechanism_configuration/condensed_phase_photolysis.py +16 -19
  26. musica/mechanism_configuration/emission.py +10 -6
  27. musica/mechanism_configuration/first_order_loss.py +133 -26
  28. musica/mechanism_configuration/henrys_law.py +7 -48
  29. musica/mechanism_configuration/mechanism_configuration.py +114 -41
  30. musica/mechanism_configuration/phase.py +6 -2
  31. musica/mechanism_configuration/photolysis.py +12 -7
  32. musica/mechanism_configuration/reactions.py +20 -8
  33. musica/mechanism_configuration/simpol_phase_transfer.py +180 -51
  34. musica/mechanism_configuration/species.py +23 -4
  35. musica/mechanism_configuration/surface.py +14 -9
  36. musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
  37. musica/mechanism_configuration/troe.py +259 -44
  38. musica/mechanism_configuration/tunneling.py +196 -49
  39. musica/mechanism_configuration/user_defined.py +9 -4
  40. musica/mechanism_configuration/wet_deposition.py +11 -8
  41. musica/mechanism_configuration.cpp +184 -95
  42. musica/musica.cpp +48 -61
  43. musica/test/examples/v1/full_configuration/full_configuration.json +39 -22
  44. musica/test/examples/v1/full_configuration/full_configuration.yaml +29 -20
  45. musica/test/{test_analytical.py → integration/test_analytical.py} +0 -1
  46. musica/test/integration/test_carma.py +227 -0
  47. musica/test/integration/test_carma_aluminum.py +11 -0
  48. musica/test/integration/test_carma_sulfate.py +16 -0
  49. musica/test/integration/test_sulfate_box_model.py +34 -0
  50. musica/test/integration/test_tuvx.py +62 -0
  51. musica/test/unit/test_parser.py +64 -0
  52. musica/test/{test_serializer.py → unit/test_serializer.py} +2 -2
  53. musica/test/{test_util_full_mechanism.py → unit/test_util_full_mechanism.py} +152 -122
  54. musica/tools/prepare_build_environment_linux.sh +39 -32
  55. musica/tools/prepare_build_environment_macos.sh +1 -0
  56. musica/tuvx.cpp +93 -0
  57. musica/tuvx.py +199 -0
  58. musica/types.py +104 -60
  59. {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/METADATA +40 -39
  60. musica-0.12.1.dist-info/RECORD +69 -0
  61. {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/WHEEL +1 -1
  62. musica-0.12.1.dist-info/entry_points.txt +3 -0
  63. musica/test/examples/v0/config.json +0 -7
  64. musica/test/examples/v0/config.yaml +0 -3
  65. musica/test/examples/v0/reactions.json +0 -193
  66. musica/test/examples/v0/reactions.yaml +0 -142
  67. musica/test/examples/v0/species.json +0 -40
  68. musica/test/examples/v0/species.yaml +0 -19
  69. musica/test/test_parser.py +0 -57
  70. musica/test/tuvx.py +0 -10
  71. musica/tools/prepare_build_environment_windows.sh +0 -22
  72. musica-0.12.0.dist-info/RECORD +0 -57
  73. /musica/test/{test_chapman.py → integration/test_chapman.py} +0 -0
  74. {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/licenses/AUTHORS.md +0 -0
  75. {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
- "aerosol phase": "aqueous aerosol",
119
- "aerosol-phase species": "ethanol_aq",
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
- "aerosol phase": "aqueous aerosol",
132
- "aerosol-phase water": "H2O_aq",
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
- "aerosol phase": "aqueous aerosol",
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
- "aerosol phase": "aqueous aerosol",
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
- "aerosol phase": "aqueous aerosol",
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
- "aerosol phase": "surface reacting phase",
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
- "aerosol phase": "cloud",
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
- aerosol phase: aqueous aerosol
80
- aerosol-phase species: ethanol_aq
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
- aerosol phase: aqueous aerosol
90
- aerosol-phase water: H2O_aq
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
- aerosol phase: aqueous aerosol
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
- aerosol phase: aqueous aerosol
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
- aerosol phase: aqueous aerosol
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
- aerosol phase: surface reacting phase
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
- aerosol phase: cloud
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())