musica 0.12.2__cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.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 (70) hide show
  1. musica/CMakeLists.txt +68 -0
  2. musica/__init__.py +11 -0
  3. musica/_musica.cpython-311-i386-linux-gnu.so +0 -0
  4. musica/_version.py +1 -0
  5. musica/backend.py +41 -0
  6. musica/binding_common.cpp +33 -0
  7. musica/binding_common.hpp +7 -0
  8. musica/carma.cpp +911 -0
  9. musica/carma.py +1729 -0
  10. musica/constants.py +3 -0
  11. musica/cpu_binding.cpp +11 -0
  12. musica/cuda.cpp +12 -0
  13. musica/cuda.py +13 -0
  14. musica/examples/__init__.py +1 -0
  15. musica/examples/carma_aluminum.py +124 -0
  16. musica/examples/carma_sulfate.py +246 -0
  17. musica/examples/examples.py +165 -0
  18. musica/examples/sulfate_box_model.py +439 -0
  19. musica/examples/ts1_latin_hypercube.py +245 -0
  20. musica/gpu_binding.cpp +11 -0
  21. musica/main.py +89 -0
  22. musica/mechanism_configuration/__init__.py +1 -0
  23. musica/mechanism_configuration/aqueous_equilibrium.py +274 -0
  24. musica/mechanism_configuration/arrhenius.py +307 -0
  25. musica/mechanism_configuration/branched.py +299 -0
  26. musica/mechanism_configuration/condensed_phase_arrhenius.py +309 -0
  27. musica/mechanism_configuration/condensed_phase_photolysis.py +88 -0
  28. musica/mechanism_configuration/emission.py +71 -0
  29. musica/mechanism_configuration/first_order_loss.py +174 -0
  30. musica/mechanism_configuration/henrys_law.py +44 -0
  31. musica/mechanism_configuration/mechanism_configuration.py +234 -0
  32. musica/mechanism_configuration/phase.py +47 -0
  33. musica/mechanism_configuration/photolysis.py +88 -0
  34. musica/mechanism_configuration/reactions.py +73 -0
  35. musica/mechanism_configuration/simpol_phase_transfer.py +217 -0
  36. musica/mechanism_configuration/species.py +91 -0
  37. musica/mechanism_configuration/surface.py +94 -0
  38. musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
  39. musica/mechanism_configuration/troe.py +352 -0
  40. musica/mechanism_configuration/tunneling.py +250 -0
  41. musica/mechanism_configuration/user_defined.py +88 -0
  42. musica/mechanism_configuration/utils.py +10 -0
  43. musica/mechanism_configuration/wet_deposition.py +52 -0
  44. musica/mechanism_configuration.cpp +607 -0
  45. musica/musica.cpp +201 -0
  46. musica/test/examples/v1/full_configuration/full_configuration.json +466 -0
  47. musica/test/examples/v1/full_configuration/full_configuration.yaml +295 -0
  48. musica/test/integration/test_analytical.py +324 -0
  49. musica/test/integration/test_carma.py +227 -0
  50. musica/test/integration/test_carma_aluminum.py +12 -0
  51. musica/test/integration/test_carma_sulfate.py +17 -0
  52. musica/test/integration/test_chapman.py +139 -0
  53. musica/test/integration/test_sulfate_box_model.py +34 -0
  54. musica/test/integration/test_tuvx.py +62 -0
  55. musica/test/unit/test_parser.py +64 -0
  56. musica/test/unit/test_serializer.py +69 -0
  57. musica/test/unit/test_state.py +325 -0
  58. musica/test/unit/test_util_full_mechanism.py +698 -0
  59. musica/tools/prepare_build_environment_linux.sh +32 -0
  60. musica/tools/prepare_build_environment_macos.sh +1 -0
  61. musica/tools/repair_wheel_gpu.sh +40 -0
  62. musica/tuvx.cpp +93 -0
  63. musica/tuvx.py +199 -0
  64. musica/types.py +407 -0
  65. musica-0.12.2.dist-info/METADATA +473 -0
  66. musica-0.12.2.dist-info/RECORD +70 -0
  67. musica-0.12.2.dist-info/WHEEL +6 -0
  68. musica-0.12.2.dist-info/entry_points.txt +3 -0
  69. musica-0.12.2.dist-info/licenses/AUTHORS.md +59 -0
  70. musica-0.12.2.dist-info/licenses/LICENSE +201 -0
@@ -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,12 @@
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_aluminum():
10
+ from musica.examples import carma_aluminum
11
+ state = carma_aluminum.run_carma_aluminum_example()
12
+ assert state is not None, "State should not be None"
@@ -0,0 +1,17 @@
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_sulfate():
10
+ from musica.examples import carma_sulfate
11
+ env_state, gas_state, bin_state = carma_sulfate.run_carma_sulfate_example()
12
+ # Basic assertions to verify the simulation ran successfully
13
+ assert env_state is not None, "Environmental state should not be None"
14
+ assert gas_state is not None, "Gas state should not be None"
15
+ assert bin_state is not None, "Bin state should not be None"
16
+ # Optionally, check expected dimensions or variables
17
+ assert hasattr(bin_state, "mass_mixing_ratio"), "Bin state should have mass_mixing_ratio"
@@ -0,0 +1,139 @@
1
+ import pytest
2
+ import musica
3
+ import musica.mechanism_configuration as mc
4
+
5
+
6
+ def test_solve_with_config_path_v0():
7
+ solver = musica.MICM(
8
+ config_path="configs/v0/chapman",
9
+ solver_type=musica.SolverType.rosenbrock_standard_order,
10
+ )
11
+ TestSolve(solver)
12
+
13
+
14
+ def test_solve_with_config_path_v1_json():
15
+ solver = musica.MICM(
16
+ config_path="configs/v1/chapman/config.json",
17
+ solver_type=musica.SolverType.rosenbrock_standard_order,
18
+ )
19
+ TestSolve(solver)
20
+
21
+
22
+ def test_solve_with_config_path_v1_yaml():
23
+ solver = musica.MICM(
24
+ config_path="configs/v1/chapman/config.yaml",
25
+ solver_type=musica.SolverType.rosenbrock_standard_order,
26
+ )
27
+ TestSolve(solver)
28
+
29
+
30
+ def test_solve_with_mechanism():
31
+ solver = musica.MICM(
32
+ mechanism=GetMechanism(),
33
+ solver_type=musica.SolverType.rosenbrock_standard_order,
34
+ )
35
+ TestSolve(solver)
36
+
37
+
38
+ def TestSolve(solver):
39
+ state = solver.create_state()
40
+
41
+ time_step = 200.0
42
+ temperature = 272.5
43
+ pressure = 101253.3
44
+
45
+ rate_constants = {
46
+ "PHOTO.jO2": 2.42e-17,
47
+ "PHOTO.jO3->O": 1.15e-5,
48
+ "PHOTO.jO3->O1D": 6.61e-9
49
+ }
50
+
51
+ initial_concentrations = {
52
+ "O2": 0.75,
53
+ "O": 0.0,
54
+ "O1D": 0.0,
55
+ "O3": 0.0000081
56
+ }
57
+
58
+ # Test setting int values
59
+ state.set_conditions(temperatures=272, pressures=101325)
60
+
61
+ # Set actual test conditions
62
+ state.set_conditions(temperatures=temperature, pressures=pressure)
63
+ state.set_concentrations(initial_concentrations)
64
+ state.set_user_defined_rate_parameters(rate_constants)
65
+
66
+ solver.solve(state, time_step)
67
+ concentrations = state.get_concentrations()
68
+
69
+ assert pytest.approx(concentrations["O2"][0], rel=1e-5) == 0.75
70
+ assert concentrations["O"][0] > 0.0
71
+ assert concentrations["O1D"][0] > 0.0
72
+ assert concentrations["O3"][0] != 0.0000081
73
+
74
+
75
+ def GetMechanism():
76
+ M = mc.Species(tracer_type="THIRD_BODY")
77
+ O2 = mc.Species(name="O2", tracer_type="CONSTANT")
78
+ O = mc.Species(name="O", other_properties={
79
+ "__absolute_tolerance": "1e-12"})
80
+ O1D = mc.Species(name="O1D", other_properties={
81
+ "__absolute_tolerance": "1e-12"})
82
+ O3 = mc.Species(
83
+ name="O3",
84
+ molecular_weight_kg_mol=0.048,
85
+ other_properties={
86
+ "__absolute_tolerance": "1e-12",
87
+ "__long_name": "ozone",
88
+ "__atmos": "3",
89
+ "__do_advect": "True",
90
+ })
91
+ gas = mc.Phase(
92
+ name="gas",
93
+ species=[O2, O, O1D, O3, M],
94
+ )
95
+ jO2 = mc.Photolysis(
96
+ name="jO2",
97
+ reactants=[O2],
98
+ products=[(2, O)],
99
+ )
100
+ R2 = mc.Arrhenius(
101
+ name="R2",
102
+ A=8.018e-17,
103
+ reactants=[O, O2],
104
+ products=[O3],
105
+ )
106
+ jO31 = mc.Photolysis(
107
+ name="jO3->O",
108
+ reactants=[O3],
109
+ products=[O, O2],
110
+ )
111
+ R4 = mc.Arrhenius(
112
+ name="R4",
113
+ A=1.576e-15,
114
+ reactants=[O, O3],
115
+ products=[(2, O2)],
116
+ )
117
+ jO32 = mc.Photolysis(
118
+ name="jO3->O1D",
119
+ reactants=[O3],
120
+ products=[O1D, O2],
121
+ )
122
+ R6 = mc.Arrhenius(
123
+ name="R6",
124
+ A=7.11e-11,
125
+ reactants=[O1D, M],
126
+ products=[O, M],
127
+ )
128
+ R7 = mc.Arrhenius(
129
+ name="R7",
130
+ A=1.2e-10,
131
+ reactants=[O1D, O3],
132
+ products=[(2, O2)],
133
+ )
134
+ return mc.Mechanism(
135
+ name="Chapman",
136
+ species=[O2, O, O1D, O3, M],
137
+ phases=[gas],
138
+ reactions=[jO2, R2, jO31, R4, jO32, R6, R7],
139
+ )
@@ -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
+
9
+ def test_sulfate_box_model():
10
+ """Test the sulfate box model implementation."""
11
+ from musica.examples import sulfate_box_model
12
+ concentrations, times, sulfate_data = sulfate_box_model.run_box_model()
13
+
14
+ # Basic assertions to verify the simulation ran successfully
15
+ assert concentrations is not None, "Concentrations should not be None"
16
+ assert len(concentrations) > 0, "Should have concentration data"
17
+ assert times is not None, "Times should not be None"
18
+ assert len(times) > 0, "Should have time data"
19
+ assert sulfate_data is not None, "CARMA sulfate data should not be None"
20
+
21
+ # Check that we have the expected species
22
+ expected_species = ["HO2", "H2O2", "SO2", "SO3", "H2SO4", "H2O"]
23
+ for species in expected_species:
24
+ assert species in concentrations.columns, f"Missing species: {species}"
25
+
26
+ # Check that CARMA data has expected structure
27
+ assert hasattr(sulfate_data, "mass_mixing_ratio"), "CARMA data should have mass_mixing_ratio"
28
+ assert "time" in sulfate_data.dims, "CARMA data should have time dimension"
29
+ assert "bin" in sulfate_data.dims, "CARMA data should have bin dimension"
30
+
31
+ print(f"✅ Test passed! Simulated {len(times)} time steps over {times[-1]:.2f} hours")
32
+ print(f" Chemical species tracked: {list(concentrations.columns)}")
33
+ print(f" CARMA bins: {len(sulfate_data.bin)}")
34
+ print(f" Vertical levels: {len(sulfate_data.vertical_center)}")
@@ -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__])
@@ -0,0 +1,69 @@
1
+ import pytest
2
+ import os
3
+ from musica.mechanism_configuration import MechanismSerializer, Mechanism, Parser
4
+ from test_util_full_mechanism import get_fully_defined_mechanism, validate_full_v1_mechanism
5
+
6
+
7
+ def test_mechanism_export_loop(tmp_path):
8
+ parser = Parser()
9
+ MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
10
+ extensions = [".yml", ".yaml", ".json"]
11
+ for extension in extensions:
12
+ path = f"{tmp_path}/test_mechanism{extension}"
13
+ MECHANISM_FULLY_DEFINED.export(path)
14
+ mechanism = parser.parse(path)
15
+ validate_full_v1_mechanism(mechanism)
16
+
17
+
18
+ def test_serialize_parser_loop(tmp_path):
19
+ parser = Parser()
20
+ MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
21
+ extensions = [".yml", ".yaml", ".json"]
22
+ for extension in extensions:
23
+ path = f"{tmp_path}/test_mechanism{extension}"
24
+ MechanismSerializer.serialize(MECHANISM_FULLY_DEFINED, path)
25
+ mechanism = parser.parse(path)
26
+ validate_full_v1_mechanism(mechanism)
27
+
28
+
29
+ def test_serialize_to_file(tmp_path):
30
+ MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
31
+ extensions = [".yml", ".yaml", ".json"]
32
+ for extension in extensions:
33
+ file_path = f'{tmp_path}/test_mechanism{extension}'
34
+ assert not os.path.exists(file_path)
35
+ MechanismSerializer.serialize(MECHANISM_FULLY_DEFINED, file_path)
36
+ assert os.path.exists(file_path)
37
+
38
+
39
+ def test_bad_inputs():
40
+ with pytest.raises(TypeError):
41
+ MechanismSerializer.serialize(None)
42
+ with pytest.raises(TypeError):
43
+ MechanismSerializer.serialize('not a mechanism')
44
+ with pytest.raises(Exception):
45
+ MechanismSerializer.serialize(get_fully_defined_mechanism(), 'unsupported.txt')
46
+
47
+
48
+ def test_path_creation(tmp_path):
49
+ mechanism = Mechanism(name="Full Configuration")
50
+ path = f"{tmp_path}/non_existant_path/"
51
+ assert not os.path.exists(path)
52
+ MechanismSerializer.serialize(mechanism, f"{path}test_mechanism.json")
53
+ assert os.path.exists(path)
54
+
55
+
56
+ def test_overwrite_file(tmp_path):
57
+ mechanism = Mechanism(name="Full Configuration")
58
+ file_path = f'{tmp_path}/test_mechanism.json'
59
+ assert not os.path.exists(file_path)
60
+
61
+ # write first file
62
+ MechanismSerializer.serialize(mechanism, file_path)
63
+ files = list(tmp_path.iterdir())
64
+ assert len(files) == 1
65
+
66
+ # overwrite file
67
+ MechanismSerializer.serialize(mechanism, file_path)
68
+ files = list(tmp_path.iterdir())
69
+ assert len(files) == 1