musica 0.12.1__cp311-cp311-win32.whl → 0.13.0__cp311-cp311-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 (72) hide show
  1. musica/CMakeLists.txt +4 -0
  2. musica/_musica.cp311-win32.pyd +0 -0
  3. musica/_version.py +1 -1
  4. musica/binding_common.cpp +6 -9
  5. musica/binding_common.hpp +17 -1
  6. musica/examples/__init__.py +1 -1
  7. musica/examples/carma_aluminum.py +1 -0
  8. musica/examples/carma_sulfate.py +1 -0
  9. musica/examples/sulfate_box_model.py +2 -2
  10. musica/examples/ts1_latin_hypercube.py +1 -1
  11. musica/grid.cpp +206 -0
  12. musica/grid.py +98 -0
  13. musica/grid_map.cpp +117 -0
  14. musica/grid_map.py +167 -0
  15. musica/mechanism_configuration/__init__.py +18 -1
  16. musica/mechanism_configuration/ancillary.py +6 -0
  17. musica/mechanism_configuration/arrhenius.py +111 -269
  18. musica/mechanism_configuration/branched.py +116 -275
  19. musica/mechanism_configuration/emission.py +63 -52
  20. musica/mechanism_configuration/first_order_loss.py +73 -157
  21. musica/mechanism_configuration/mechanism.py +93 -0
  22. musica/mechanism_configuration/phase.py +44 -33
  23. musica/mechanism_configuration/phase_species.py +58 -0
  24. musica/mechanism_configuration/photolysis.py +77 -67
  25. musica/mechanism_configuration/reaction_component.py +54 -0
  26. musica/mechanism_configuration/reactions.py +17 -58
  27. musica/mechanism_configuration/species.py +45 -71
  28. musica/mechanism_configuration/surface.py +78 -74
  29. musica/mechanism_configuration/taylor_series.py +136 -0
  30. musica/mechanism_configuration/ternary_chemical_activation.py +138 -330
  31. musica/mechanism_configuration/troe.py +138 -330
  32. musica/mechanism_configuration/tunneling.py +105 -229
  33. musica/mechanism_configuration/user_defined.py +79 -68
  34. musica/mechanism_configuration.cpp +54 -162
  35. musica/musica.cpp +2 -5
  36. musica/profile.cpp +294 -0
  37. musica/profile.py +93 -0
  38. musica/profile_map.cpp +117 -0
  39. musica/profile_map.py +167 -0
  40. musica/test/examples/v1/full_configuration/full_configuration.json +91 -233
  41. musica/test/examples/v1/full_configuration/full_configuration.yaml +191 -290
  42. musica/test/integration/test_carma_aluminum.py +2 -1
  43. musica/test/integration/test_carma_sulfate.py +2 -1
  44. musica/test/integration/test_chapman.py +2 -2
  45. musica/test/integration/test_sulfate_box_model.py +1 -1
  46. musica/test/integration/test_tuvx.py +72 -15
  47. musica/test/unit/test_grid.py +137 -0
  48. musica/test/unit/test_grid_map.py +126 -0
  49. musica/test/unit/test_parser.py +10 -10
  50. musica/test/unit/test_profile.py +169 -0
  51. musica/test/unit/test_profile_map.py +137 -0
  52. musica/test/unit/test_serializer.py +17 -16
  53. musica/test/unit/test_state.py +338 -0
  54. musica/test/unit/test_util_full_mechanism.py +78 -298
  55. musica/tools/prepare_build_environment_linux.sh +7 -25
  56. musica/tuvx.cpp +94 -15
  57. musica/tuvx.py +92 -22
  58. musica/types.py +28 -17
  59. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/METADATA +15 -14
  60. musica-0.13.0.dist-info/RECORD +80 -0
  61. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/WHEEL +1 -1
  62. musica/mechanism_configuration/aqueous_equilibrium.py +0 -274
  63. musica/mechanism_configuration/condensed_phase_arrhenius.py +0 -309
  64. musica/mechanism_configuration/condensed_phase_photolysis.py +0 -88
  65. musica/mechanism_configuration/henrys_law.py +0 -44
  66. musica/mechanism_configuration/mechanism_configuration.py +0 -234
  67. musica/mechanism_configuration/simpol_phase_transfer.py +0 -217
  68. musica/mechanism_configuration/wet_deposition.py +0 -52
  69. musica-0.12.1.dist-info/RECORD +0 -69
  70. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/entry_points.txt +0 -0
  71. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/licenses/AUTHORS.md +0 -0
  72. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,55 +1,52 @@
1
1
  import pytest
2
2
  import os
3
- from musica.mechanism_configuration import MechanismSerializer, Mechanism, Parser
3
+ from musica.mechanism_configuration import Mechanism, Parser
4
4
  from test_util_full_mechanism import get_fully_defined_mechanism, validate_full_v1_mechanism
5
5
 
6
6
 
7
7
  def test_mechanism_export_loop(tmp_path):
8
8
  parser = Parser()
9
- MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
9
+ mechanism = get_fully_defined_mechanism()
10
10
  extensions = [".yml", ".yaml", ".json"]
11
11
  for extension in extensions:
12
12
  path = f"{tmp_path}/test_mechanism{extension}"
13
- MECHANISM_FULLY_DEFINED.export(path)
13
+ mechanism.export(path)
14
14
  mechanism = parser.parse(path)
15
15
  validate_full_v1_mechanism(mechanism)
16
16
 
17
17
 
18
18
  def test_serialize_parser_loop(tmp_path):
19
19
  parser = Parser()
20
- MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
21
- extensions = [".yml", ".yaml", ".json"]
20
+ mechanism = get_fully_defined_mechanism()
21
+ extensions = [".json", ".yml", ".yaml"]
22
22
  for extension in extensions:
23
23
  path = f"{tmp_path}/test_mechanism{extension}"
24
- MechanismSerializer.serialize(MECHANISM_FULLY_DEFINED, path)
24
+ mechanism.export(path)
25
25
  mechanism = parser.parse(path)
26
26
  validate_full_v1_mechanism(mechanism)
27
27
 
28
28
 
29
29
  def test_serialize_to_file(tmp_path):
30
- MECHANISM_FULLY_DEFINED = get_fully_defined_mechanism()
30
+ mechanism = get_fully_defined_mechanism()
31
31
  extensions = [".yml", ".yaml", ".json"]
32
32
  for extension in extensions:
33
33
  file_path = f'{tmp_path}/test_mechanism{extension}'
34
34
  assert not os.path.exists(file_path)
35
- MechanismSerializer.serialize(MECHANISM_FULLY_DEFINED, file_path)
35
+ mechanism.export(file_path)
36
36
  assert os.path.exists(file_path)
37
37
 
38
38
 
39
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')
40
+ mechanism = Mechanism(name="Full Configuration")
44
41
  with pytest.raises(Exception):
45
- MechanismSerializer.serialize(get_fully_defined_mechanism(), 'unsupported.txt')
42
+ mechanism.export("unsupported.txt")
46
43
 
47
44
 
48
45
  def test_path_creation(tmp_path):
49
46
  mechanism = Mechanism(name="Full Configuration")
50
47
  path = f"{tmp_path}/non_existant_path/"
51
48
  assert not os.path.exists(path)
52
- MechanismSerializer.serialize(mechanism, f"{path}test_mechanism.json")
49
+ mechanism.export(f"{path}test_mechanism.json")
53
50
  assert os.path.exists(path)
54
51
 
55
52
 
@@ -59,11 +56,15 @@ def test_overwrite_file(tmp_path):
59
56
  assert not os.path.exists(file_path)
60
57
 
61
58
  # write first file
62
- MechanismSerializer.serialize(mechanism, file_path)
59
+ mechanism.export(file_path)
63
60
  files = list(tmp_path.iterdir())
64
61
  assert len(files) == 1
65
62
 
66
63
  # overwrite file
67
- MechanismSerializer.serialize(mechanism, file_path)
64
+ mechanism.export(file_path)
68
65
  files = list(tmp_path.iterdir())
69
66
  assert len(files) == 1
67
+
68
+
69
+ if __name__ == "__main__":
70
+ pytest.main([__file__])
@@ -0,0 +1,338 @@
1
+ """Tests for the State class."""
2
+ from __future__ import annotations
3
+ import pytest # type: ignore # pylint: disable=import-error
4
+ import numpy as np # type: ignore # pylint: disable=import-error
5
+ from musica.types import State, MICM
6
+ import musica.mechanism_configuration as mc
7
+ from musica.mechanism_configuration import Mechanism
8
+
9
+
10
+ def create_test_mechanism() -> Mechanism:
11
+ """Helper function to create a test mechanism."""
12
+ # Chemical species
13
+ A = mc.Species(
14
+ name="A",
15
+ molecular_weight_kg_mol=32.1,
16
+ other_properties={
17
+ "__absolute tolerance": "1.0e-30"})
18
+ B = mc.Species(name="B")
19
+ C = mc.Species(name="C")
20
+ M = mc.Species(name="M", is_third_body=True)
21
+
22
+ # Chemical phases
23
+ gas = mc.Phase(name="gas", species=[mc.PhaseSpecies(name=A.name, diffusion_coefficient_m2_s=1.0), B, C, M])
24
+
25
+ # Reactions
26
+ my_arrhenius = mc.Arrhenius(
27
+ name="my arrhenius",
28
+ A=32.1, B=-2.3, C=102.3, D=63.4, E=-1.3,
29
+ gas_phase=gas,
30
+ reactants=[B],
31
+ products=[C],
32
+ other_properties={"__irrelevant": "2"},
33
+ )
34
+
35
+ my_other_arrhenius = mc.Arrhenius(
36
+ name="my other arrhenius",
37
+ A=29.3, B=-1.5, Ea=101.2, D=82.6, E=-0.98,
38
+ gas_phase=gas,
39
+ reactants=[A],
40
+ products=[(1.2, B)]
41
+ )
42
+
43
+ my_troe = mc.Troe(
44
+ name="my troe",
45
+ gas_phase=gas,
46
+ k0_A=1.2e-12,
47
+ k0_B=167,
48
+ k0_C=3,
49
+ kinf_A=136,
50
+ kinf_B=5,
51
+ kinf_C=24,
52
+ Fc=0.9,
53
+ N=0.8,
54
+ reactants=[B, A],
55
+ products=[C],
56
+ other_properties={"__irrelevant": "2"},
57
+ )
58
+
59
+ my_ternary = mc.TernaryChemicalActivation(
60
+ name="my ternary chemical activation",
61
+ gas_phase=gas,
62
+ k0_A=32.1,
63
+ k0_B=-2.3,
64
+ k0_C=102.3,
65
+ kinf_A=63.4,
66
+ kinf_B=-1.3,
67
+ kinf_C=908.5,
68
+ Fc=1.3,
69
+ N=32.1,
70
+ reactants=[B, A],
71
+ products=[C],
72
+ other_properties={"__irrelevant": "2"},
73
+ )
74
+
75
+ my_branched = mc.Branched(
76
+ name="my branched",
77
+ gas_phase=gas,
78
+ reactants=[A],
79
+ alkoxy_products=[B],
80
+ nitrate_products=[C],
81
+ X=1.2e-4,
82
+ Y=167,
83
+ a0=0.15,
84
+ n=9,
85
+ other_properties={"__irrelevant": "2"},
86
+ )
87
+
88
+ my_tunneling = mc.Tunneling(
89
+ name="my tunneling",
90
+ gas_phase=gas,
91
+ reactants=[B],
92
+ products=[C],
93
+ A=123.45,
94
+ B=1200.0,
95
+ C=1.0e8,
96
+ other_properties={"__irrelevant": "2"},
97
+ )
98
+
99
+ my_surface = mc.Surface(
100
+ name="my surface",
101
+ gas_phase=gas,
102
+ gas_phase_species=A,
103
+ reaction_probability=2.0e-2,
104
+ gas_phase_products=[B, C],
105
+ other_properties={"__irrelevant": "2"},
106
+ )
107
+
108
+ photo_b = mc.Photolysis(
109
+ name="photo B",
110
+ gas_phase=gas,
111
+ reactants=[B],
112
+ products=[C],
113
+ scaling_factor=12.3,
114
+ other_properties={"__irrelevant": "2"},
115
+ )
116
+
117
+ my_emission = mc.Emission(
118
+ name="my emission",
119
+ gas_phase=gas,
120
+ products=[B],
121
+ scaling_factor=12.3,
122
+ other_properties={"__irrelevant": "2"},
123
+ )
124
+
125
+ my_first_order_loss = mc.FirstOrderLoss(
126
+ name="my first order loss",
127
+ gas_phase=gas,
128
+ reactants=[C],
129
+ scaling_factor=12.3,
130
+ other_properties={"__irrelevant": "2"},
131
+ )
132
+
133
+ user_defined = mc.UserDefined(
134
+ name="my user defined",
135
+ gas_phase=gas,
136
+ reactants=[A, B],
137
+ products=[(1.3, C)],
138
+ scaling_factor=12.3,
139
+ other_properties={"__irrelevant": "2"}
140
+ )
141
+
142
+ # Mechanism
143
+ return mc.Mechanism(
144
+ name="Full Configuration",
145
+ species=[A, B, C, M],
146
+ phases=[gas],
147
+ reactions=[my_arrhenius, my_other_arrhenius, my_troe, my_ternary,
148
+ my_branched, my_tunneling,
149
+ my_surface, photo_b,
150
+ my_emission, my_first_order_loss, user_defined],
151
+ version=mc.Version(1, 0, 0),
152
+ )
153
+
154
+
155
+ def get_test_solver(mech: Mechanism) -> MICM:
156
+ """Helper function to create a test solver."""
157
+ return MICM(mechanism=mech)
158
+
159
+
160
+ def test_state_initialization():
161
+ """Test State initialization with various grid cell configurations."""
162
+ # Use the test mechanism
163
+ mech = create_test_mechanism()
164
+
165
+ # Create MICM instance
166
+ solver = get_test_solver(mech)
167
+
168
+ # Test with valid input
169
+ state = solver.create_state(number_of_grid_cells=1)
170
+ assert isinstance(state, State)
171
+
172
+ # Test with multiple grid cells
173
+ state_multi = solver.create_state(number_of_grid_cells=3)
174
+ assert isinstance(state_multi, State)
175
+
176
+ # Test with invalid input
177
+ with pytest.raises(ValueError, match="number_of_grid_cells must be greater than 0"):
178
+ solver.create_state(number_of_grid_cells=0)
179
+
180
+
181
+ def test_set_get_concentrations():
182
+ """Test setting and getting concentrations."""
183
+ # Use the test mechanism
184
+ mech = create_test_mechanism()
185
+ solver = get_test_solver(mech)
186
+
187
+ # Test single grid cell
188
+ state = solver.create_state(number_of_grid_cells=1)
189
+ concentrations = {"A": 1.0, "B": 2.0, "C": 3.0}
190
+ state.set_concentrations(concentrations)
191
+ result = state.get_concentrations()
192
+ assert result["A"][0] == 1.0
193
+ assert result["B"][0] == 2.0
194
+ assert result["C"][0] == 3.0
195
+
196
+ # Test multiple grid cells
197
+ state_multi = solver.create_state(number_of_grid_cells=2)
198
+ concentrations_multi = {"A": [1.0, 2.0], "B": [3.0, 4.0], "C": [5.0, 6.0]}
199
+ state_multi.set_concentrations(concentrations_multi)
200
+ result_multi = state_multi.get_concentrations()
201
+ assert result_multi["A"] == [1.0, 2.0]
202
+ assert result_multi["B"] == [3.0, 4.0]
203
+ assert result_multi["C"] == [5.0, 6.0]
204
+
205
+ # Test invalid species
206
+ with pytest.raises(ValueError, match="Species D not found in the mechanism"):
207
+ state.set_concentrations({"D": 1.0})
208
+
209
+ # Test invalid length
210
+ with pytest.raises(ValueError, match="must have length"):
211
+ state_multi.set_concentrations({"A": [1.0]})
212
+
213
+ # Test cannot set third-body species
214
+ with pytest.raises(ValueError, match="Species M not found in the mechanism"):
215
+ state.set_concentrations({"M": 1.0})
216
+
217
+
218
+ def test_set_get_conditions():
219
+ """Test setting and getting environmental conditions."""
220
+ # Use the test mechanism
221
+ mech = create_test_mechanism()
222
+ solver = get_test_solver(mech)
223
+
224
+ # Test single grid cell
225
+ state = solver.create_state(number_of_grid_cells=1)
226
+ state.set_conditions(temperatures=300.0, pressures=101325.0)
227
+ conditions = state.get_conditions()
228
+ assert conditions["temperature"][0] == 300.0
229
+ assert conditions["pressure"][0] == 101325.0
230
+ assert np.isclose(conditions["air_density"][0], 40.9, rtol=0.1) # Approximate value from ideal gas law
231
+
232
+ # Test multiple grid cells
233
+ state_multi = solver.create_state(number_of_grid_cells=2)
234
+ state_multi.set_conditions(
235
+ temperatures=[300.0, 310.0],
236
+ pressures=[101325.0, 101325.0],
237
+ air_densities=[40.9, 39.5]
238
+ )
239
+ conditions_multi = state_multi.get_conditions()
240
+ assert conditions_multi["temperature"] == [300.0, 310.0]
241
+ assert conditions_multi["pressure"] == [101325.0, 101325.0]
242
+ assert conditions_multi["air_density"] == [40.9, 39.5]
243
+
244
+ # Test invalid input length
245
+ with pytest.raises(ValueError, match="must be a list of length"):
246
+ state_multi.set_conditions(temperatures=[300.0])
247
+
248
+
249
+ def test_set_get_user_defined_rate_parameters():
250
+ """Test setting and getting user-defined rate parameters."""
251
+ # Use the test mechanism which includes a user-defined reaction
252
+ mech = create_test_mechanism()
253
+ solver = get_test_solver(mech)
254
+
255
+ # Test single grid cell
256
+ state = solver.create_state(number_of_grid_cells=1)
257
+ params = {"EMIS.my emission": 1.0}
258
+ state.set_user_defined_rate_parameters(params)
259
+ result = state.get_user_defined_rate_parameters()
260
+ assert result["EMIS.my emission"][0] == 1.0
261
+
262
+ params = {
263
+ "SURF.my surface.effective radius [m]": 0.5,
264
+ "SURF.my surface.particle number concentration [# m-3]": 1000.0,
265
+ }
266
+ state.set_user_defined_rate_parameters(params)
267
+ result = state.get_user_defined_rate_parameters()
268
+ assert result["SURF.my surface.effective radius [m]"][0] == 0.5
269
+ assert result["SURF.my surface.particle number concentration [# m-3]"][0] == 1000.0
270
+
271
+ # Test multiple grid cells
272
+ state_multi = solver.create_state(number_of_grid_cells=2)
273
+ params_multi = {"PHOTO.photo B": [1.0, 2.0]}
274
+ state_multi.set_user_defined_rate_parameters(params_multi)
275
+ result_multi = state_multi.get_user_defined_rate_parameters()
276
+ assert result_multi["PHOTO.photo B"] == [1.0, 2.0]
277
+
278
+ # Test invalid parameter
279
+ with pytest.raises(ValueError, match="User-defined rate parameter invalid_param not found"):
280
+ state.set_user_defined_rate_parameters({"invalid_param": 1.0})
281
+
282
+ solver = get_test_solver(mech)
283
+
284
+ state = solver.create_state(number_of_grid_cells=1)
285
+
286
+ # Test species ordering
287
+ species_ordering = state.get_species_ordering()
288
+ assert isinstance(species_ordering, dict)
289
+ assert len(species_ordering) == 3 # A, B, C (M is third-body and not included)
290
+
291
+ # Dictionary style access
292
+ assert species_ordering["A"] >= 0
293
+ assert species_ordering["B"] >= 0
294
+ assert species_ordering["C"] >= 0
295
+
296
+ # Using get() method with default value
297
+ assert species_ordering.get("A", -1) >= 0 # returns value if key exists
298
+ assert species_ordering.get("Z", -1) == -1 # returns -1 since Z doesn't exist
299
+
300
+ # Test key membership
301
+ assert "A" in species_ordering
302
+ assert "B" in species_ordering
303
+ assert "C" in species_ordering
304
+ assert "M" not in species_ordering # third-body not included
305
+
306
+ # Test parameter ordering
307
+ param_ordering = state.get_user_defined_rate_parameters_ordering()
308
+ assert isinstance(param_ordering, dict)
309
+ assert len(param_ordering) == 6
310
+
311
+ # Convert dict keys to list using list() function
312
+ param_names = list(param_ordering.keys())
313
+ assert len(param_names) == 6
314
+ assert isinstance(param_names[0], str)
315
+
316
+ # Alternative way using list comprehension
317
+ param_names_alt = [key for key in param_ordering]
318
+ assert param_names_alt == param_names
319
+
320
+ # Sort the keys if needed - useful for consistent ordering in tests
321
+ sorted_param_names = sorted(param_ordering.keys())
322
+ assert len(sorted_param_names) == 6
323
+ assert all(isinstance(name, str) for name in sorted_param_names)
324
+
325
+ # Verify all expected keys are present
326
+ expected_params = [
327
+ "PHOTO.photo B",
328
+ "EMIS.my emission",
329
+ "LOSS.my first order loss",
330
+ "SURF.my surface.effective radius [m]",
331
+ "SURF.my surface.particle number concentration [# m-3]",
332
+ "USER.my user defined"
333
+ ]
334
+ assert sorted(expected_params) == sorted(param_names)
335
+
336
+
337
+ if __name__ == "__main__":
338
+ pytest.main([__file__])