musica 0.12.2__cp313-cp313-macosx_15_0_arm64.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 (80) hide show
  1. musica/.dylibs/libaec.0.1.4.dylib +0 -0
  2. musica/.dylibs/libgcc_s.1.1.dylib +0 -0
  3. musica/.dylibs/libgfortran.5.dylib +0 -0
  4. musica/.dylibs/libhdf5.310.5.1.dylib +0 -0
  5. musica/.dylibs/libhdf5_hl.310.0.6.dylib +0 -0
  6. musica/.dylibs/libnetcdf.22.dylib +0 -0
  7. musica/.dylibs/libnetcdff.7.1.0.dylib +0 -0
  8. musica/.dylibs/libquadmath.0.dylib +0 -0
  9. musica/.dylibs/libsz.2.0.1.dylib +0 -0
  10. musica/.dylibs/libzstd.1.5.7.dylib +0 -0
  11. musica/CMakeLists.txt +68 -0
  12. musica/__init__.py +11 -0
  13. musica/_musica.cpython-313-darwin.so +0 -0
  14. musica/_version.py +1 -0
  15. musica/backend.py +41 -0
  16. musica/binding_common.cpp +33 -0
  17. musica/binding_common.hpp +7 -0
  18. musica/carma.cpp +911 -0
  19. musica/carma.py +1729 -0
  20. musica/constants.py +3 -0
  21. musica/cpu_binding.cpp +11 -0
  22. musica/cuda.cpp +12 -0
  23. musica/cuda.py +13 -0
  24. musica/examples/__init__.py +1 -0
  25. musica/examples/carma_aluminum.py +124 -0
  26. musica/examples/carma_sulfate.py +246 -0
  27. musica/examples/examples.py +165 -0
  28. musica/examples/sulfate_box_model.py +439 -0
  29. musica/examples/ts1_latin_hypercube.py +245 -0
  30. musica/gpu_binding.cpp +11 -0
  31. musica/main.py +89 -0
  32. musica/mechanism_configuration/__init__.py +1 -0
  33. musica/mechanism_configuration/aqueous_equilibrium.py +274 -0
  34. musica/mechanism_configuration/arrhenius.py +307 -0
  35. musica/mechanism_configuration/branched.py +299 -0
  36. musica/mechanism_configuration/condensed_phase_arrhenius.py +309 -0
  37. musica/mechanism_configuration/condensed_phase_photolysis.py +88 -0
  38. musica/mechanism_configuration/emission.py +71 -0
  39. musica/mechanism_configuration/first_order_loss.py +174 -0
  40. musica/mechanism_configuration/henrys_law.py +44 -0
  41. musica/mechanism_configuration/mechanism_configuration.py +234 -0
  42. musica/mechanism_configuration/phase.py +47 -0
  43. musica/mechanism_configuration/photolysis.py +88 -0
  44. musica/mechanism_configuration/reactions.py +73 -0
  45. musica/mechanism_configuration/simpol_phase_transfer.py +217 -0
  46. musica/mechanism_configuration/species.py +91 -0
  47. musica/mechanism_configuration/surface.py +94 -0
  48. musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
  49. musica/mechanism_configuration/troe.py +352 -0
  50. musica/mechanism_configuration/tunneling.py +250 -0
  51. musica/mechanism_configuration/user_defined.py +88 -0
  52. musica/mechanism_configuration/utils.py +10 -0
  53. musica/mechanism_configuration/wet_deposition.py +52 -0
  54. musica/mechanism_configuration.cpp +607 -0
  55. musica/musica.cpp +201 -0
  56. musica/test/examples/v1/full_configuration/full_configuration.json +466 -0
  57. musica/test/examples/v1/full_configuration/full_configuration.yaml +295 -0
  58. musica/test/integration/test_analytical.py +324 -0
  59. musica/test/integration/test_carma.py +227 -0
  60. musica/test/integration/test_carma_aluminum.py +12 -0
  61. musica/test/integration/test_carma_sulfate.py +17 -0
  62. musica/test/integration/test_chapman.py +139 -0
  63. musica/test/integration/test_sulfate_box_model.py +34 -0
  64. musica/test/integration/test_tuvx.py +62 -0
  65. musica/test/unit/test_parser.py +64 -0
  66. musica/test/unit/test_serializer.py +69 -0
  67. musica/test/unit/test_state.py +325 -0
  68. musica/test/unit/test_util_full_mechanism.py +698 -0
  69. musica/tools/prepare_build_environment_linux.sh +32 -0
  70. musica/tools/prepare_build_environment_macos.sh +1 -0
  71. musica/tools/repair_wheel_gpu.sh +40 -0
  72. musica/tuvx.cpp +93 -0
  73. musica/tuvx.py +199 -0
  74. musica/types.py +407 -0
  75. musica-0.12.2.dist-info/METADATA +473 -0
  76. musica-0.12.2.dist-info/RECORD +80 -0
  77. musica-0.12.2.dist-info/WHEEL +6 -0
  78. musica-0.12.2.dist-info/entry_points.txt +3 -0
  79. musica-0.12.2.dist-info/licenses/AUTHORS.md +59 -0
  80. musica-0.12.2.dist-info/licenses/LICENSE +201 -0
musica/types.py ADDED
@@ -0,0 +1,407 @@
1
+ # Copyright (C) 2023-2025 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # This file is part of the musica Python package.
5
+ # For more information, see the LICENSE file in the top-level directory of this distribution.
6
+ from __future__ import annotations
7
+ from .constants import GAS_CONSTANT
8
+ from typing import Optional, Dict, List, Union, Tuple, TYPE_CHECKING, Any
9
+ from os import PathLike
10
+ import math
11
+
12
+ # Import backend symbols from the backend module
13
+ from . import backend
14
+
15
+ # Get all the backend symbols we need
16
+ _backend = backend.get_backend()
17
+ _Conditions = _backend._core._Conditions
18
+ _SolverType = _backend._core._SolverType
19
+ _create_solver = _backend._core._create_solver
20
+ _create_solver_from_mechanism = _backend._core._create_solver_from_mechanism
21
+ _create_state = _backend._core._create_state
22
+ _micm_solve = _backend._core._micm_solve
23
+ _vector_size = _backend._core._vector_size
24
+ _species_ordering = _backend._core._species_ordering
25
+ _user_defined_rate_parameters_ordering = _backend._core._user_defined_rate_parameters_ordering
26
+ mc = _backend._mechanism_configuration
27
+
28
+
29
+ # For type hints
30
+ if TYPE_CHECKING:
31
+ from .mechanism_configuration import Mechanism
32
+ else:
33
+ Mechanism = mc._Mechanism
34
+
35
+ FilePath = Union[str, "PathLike[str]"]
36
+
37
+
38
+ class Conditions(_Conditions):
39
+ """
40
+ Conditions class for the MICM solver. If air density is not provided,
41
+ it will be calculated from the Ideal Gas Law using the provided temperature and pressure.
42
+
43
+ Parameters
44
+ ----------
45
+ temperature : float
46
+ Temperature in Kelvin.
47
+ pressure : float
48
+ Pressure in Pascals.
49
+ air_density : float
50
+ Air density in mol m-3
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ temperature: Optional[Union[float, int]] = None,
56
+ pressure: Optional[Union[float, int]] = None,
57
+ air_density: Optional[Union[float, int]] = None,
58
+ ):
59
+ super().__init__()
60
+ if temperature is not None:
61
+ self.temperature = temperature
62
+ if pressure is not None:
63
+ self.pressure = pressure
64
+ if air_density is not None:
65
+ self.air_density = air_density
66
+ elif temperature is not None and pressure is not None:
67
+ self.air_density = 1.0 / (GAS_CONSTANT * temperature / pressure)
68
+
69
+
70
+ class SolverType(_SolverType):
71
+ """
72
+ Enum class for the type of solver to use.
73
+ """
74
+
75
+
76
+ class State():
77
+ """
78
+ State class for the MICM solver. It contains the initial conditions and species concentrations.
79
+ """
80
+
81
+ def __init__(self, solver: Any, number_of_grid_cells: int, vector_size: int = 0):
82
+ if number_of_grid_cells < 1:
83
+ raise ValueError("number_of_grid_cells must be greater than 0.")
84
+ super().__init__()
85
+ self.__states = [
86
+ _create_state(solver, min(
87
+ vector_size, number_of_grid_cells - i * vector_size))
88
+ for i in range(math.ceil(number_of_grid_cells / vector_size))
89
+ ] if vector_size > 0 else [_create_state(solver, number_of_grid_cells)]
90
+ self.__species_ordering = _species_ordering(self.__states[0])
91
+ self.__user_defined_rate_parameters_ordering = _user_defined_rate_parameters_ordering(
92
+ self.__states[0])
93
+ self.__number_of_grid_cells = number_of_grid_cells
94
+ self.__vector_size = vector_size
95
+
96
+ def get_internal_states(self) -> List[Any]:
97
+ """
98
+ Get the internal states of the MICM solver.
99
+
100
+ Returns
101
+ -------
102
+ List[_State]
103
+ List of internal states.
104
+ """
105
+ return self.__states
106
+
107
+ def set_concentrations(self, concentrations: Dict[str, Union[Union[float, int], List[Union[float, int]]]]):
108
+ """
109
+ Set the concentrations of the species in the state. Any species not in the
110
+ dictionary will be set to zero. The concentrations can be a single value when solving
111
+ for a single grid cell, or a list of values when solving for multiple grid cells.
112
+
113
+ Parameters
114
+ ----------
115
+ concentrations : Dict[str, Union[Union[float, int], List[Union[float, int]]]]
116
+ Dictionary of species names and their concentrations.
117
+ """
118
+ for name, value in concentrations.items():
119
+ if name not in self.__species_ordering:
120
+ raise ValueError(f"Species {name} not found in the mechanism.")
121
+ i_species = self.__species_ordering[name]
122
+ if isinstance(value, float) or isinstance(value, int):
123
+ value = [value]
124
+ if len(value) != self.__number_of_grid_cells:
125
+ raise ValueError(
126
+ f"Concentration list for {name} must have length {self.__number_of_grid_cells}.")
127
+ # Counter 'k' is used to map grid cell indices across multiple state segments.
128
+ k = 0
129
+ for state in self.__states:
130
+ cell_stride, species_stride = state.concentration_strides()
131
+ for i_cell in range(state.number_of_grid_cells()):
132
+ state.concentrations[i_species *
133
+ species_stride + i_cell * cell_stride] = value[k]
134
+ k += 1
135
+
136
+ def set_user_defined_rate_parameters(
137
+ self, user_defined_rate_parameters: Dict[str, Union[Union[float, int], List[Union[float, int]]]]):
138
+ """
139
+ Set the user-defined rate parameters in the state. Any parameter not in the
140
+ dictionary will be set to zero. The parameters can be a single value when solving
141
+ for a single grid cell, or a list of values when solving for multiple grid cells.
142
+
143
+ Parameters
144
+ ----------
145
+ user_defined_rate_parameters : Dict[str, Union[Union[float, int], List[Union[float, int]]]]
146
+ Dictionary of user-defined rate parameter names and their values.
147
+ """
148
+ for name, value in user_defined_rate_parameters.items():
149
+ if name not in self.__user_defined_rate_parameters_ordering:
150
+ raise ValueError(
151
+ f"User-defined rate parameter {name} not found in the mechanism.")
152
+ i_param = self.__user_defined_rate_parameters_ordering[name]
153
+ if isinstance(value, float) or isinstance(value, int):
154
+ value = [value]
155
+ if len(value) != self.__number_of_grid_cells:
156
+ raise ValueError(
157
+ f"User-defined rate parameter list for {name} must have length {self.__number_of_grid_cells}.")
158
+ # Initialize `k` to index the grid cells when assigning user-defined rate parameters.
159
+ k = 0
160
+ for state in self.__states:
161
+ cell_stride, param_stride = state.user_defined_rate_parameter_strides()
162
+ for i_cell in range(state.number_of_grid_cells()):
163
+ state.user_defined_rate_parameters[i_param *
164
+ param_stride + i_cell * cell_stride] = value[k]
165
+ k += 1
166
+
167
+ def set_conditions(self,
168
+ temperatures: Optional[Union[Union[float, int], List[Union[float, int]]]] = None,
169
+ pressures: Optional[Union[Union[float, int], List[Union[float, int]]]] = None,
170
+ air_densities: Optional[Union[Union[float, int], List[Union[float, int]]]] = None):
171
+ """
172
+ Set the conditions for the state. The individual conditions can be a single value
173
+ when solving for a single grid cell, or a list of values when solving for multiple grid cells.
174
+ If air density is not provided, it will be calculated from the Ideal Gas Law using the provided
175
+ temperature and pressure. If temperature or pressure are not provided, their values will remain
176
+ unchanged.
177
+
178
+ Parameters
179
+ ----------
180
+ temperatures : Optional[Union[float, List[float]]]
181
+ Temperature in Kelvin.
182
+ pressures : Optional[Union[float, List[float]]]
183
+ Pressure in Pascals.
184
+ air_densities : Optional[Union[float, List[float]]]
185
+ Air density in mol m-3. If not provided, it will be calculated from the Ideal Gas Law.
186
+ """
187
+ if temperatures is not None and (isinstance(temperatures, float) or isinstance(temperatures, int)):
188
+ if self.__number_of_grid_cells > 1:
189
+ raise ValueError(
190
+ f"temperatures must be a list of length {self.__number_of_grid_cells}.")
191
+ temperatures = [temperatures]
192
+ if pressures is not None and (isinstance(pressures, float) or isinstance(pressures, int)):
193
+ if self.__number_of_grid_cells > 1:
194
+ raise ValueError(
195
+ f"pressures must be a list of length {self.__number_of_grid_cells}.")
196
+ pressures = [pressures]
197
+ if air_densities is not None and (isinstance(air_densities, float) or isinstance(air_densities, int)):
198
+ if self.__number_of_grid_cells > 1:
199
+ raise ValueError(
200
+ f"air_densities must be a list of length {self.__number_of_grid_cells}.")
201
+ air_densities = [air_densities]
202
+ if temperatures is not None and len(temperatures) != self.__number_of_grid_cells:
203
+ raise ValueError(
204
+ f"temperatures must be a list of length {self.__number_of_grid_cells}.")
205
+ if pressures is not None and len(pressures) != self.__number_of_grid_cells:
206
+ raise ValueError(
207
+ f"pressures must be a list of length {self.__number_of_grid_cells}.")
208
+ if air_densities is not None and len(air_densities) != self.__number_of_grid_cells:
209
+ raise ValueError(
210
+ f"air_densities must be a list of length {self.__number_of_grid_cells}.")
211
+ k = 0
212
+ for state in self.__states:
213
+ for condition in state.conditions:
214
+ condition.temperature = temperatures[k] if temperatures is not None else condition.temperature
215
+ condition.pressure = pressures[k] if pressures is not None else condition.pressure
216
+ condition.air_density = air_densities[k] if air_densities is not None else condition.pressure / (
217
+ GAS_CONSTANT * condition.temperature)
218
+ k += 1
219
+
220
+ def get_concentrations(self) -> Dict[str, List[float]]:
221
+ """
222
+ Get the concentrations of the species in the state.
223
+
224
+ Returns
225
+ -------
226
+ Dict[str, List[float]]
227
+ Dictionary of species names and their concentrations.
228
+ """
229
+ concentrations = {}
230
+ for species, i_species in self.__species_ordering.items():
231
+ concentrations[species] = []
232
+ for state in self.__states:
233
+ cell_stride, species_stride = state.concentration_strides()
234
+ for i_cell in range(state.number_of_grid_cells()):
235
+ concentrations[species].append(
236
+ state.concentrations[i_species * species_stride + i_cell * cell_stride])
237
+ return concentrations
238
+
239
+ def get_user_defined_rate_parameters(self) -> Dict[str, List[float]]:
240
+ """
241
+ Get the user-defined rate parameters in the state.
242
+
243
+ Returns
244
+ -------
245
+ Dict[str, List[float]]
246
+ Dictionary of user-defined rate parameter names and their values.
247
+ """
248
+ user_defined_rate_parameters = {}
249
+ for param, i_param in self.__user_defined_rate_parameters_ordering.items():
250
+ user_defined_rate_parameters[param] = []
251
+ for state in self.__states:
252
+ cell_stride, param_stride = state.user_defined_rate_parameter_strides()
253
+ for i_cell in range(state.number_of_grid_cells()):
254
+ user_defined_rate_parameters[param].append(
255
+ state.user_defined_rate_parameters[i_param * param_stride + i_cell * cell_stride])
256
+ return user_defined_rate_parameters
257
+
258
+ def get_conditions(self) -> Dict[str, List[float]]:
259
+ """
260
+ Get the conditions for the state.
261
+
262
+ Returns
263
+ -------
264
+ Dict[str, List[float]]
265
+ Dictionary of conditions names and their values.
266
+ """
267
+ conditions = {}
268
+ conditions["temperature"] = []
269
+ conditions["pressure"] = []
270
+ conditions["air_density"] = []
271
+ for state in self.__states:
272
+ for i_cell in range(state.number_of_grid_cells()):
273
+ conditions["temperature"].append(
274
+ state.conditions[i_cell].temperature)
275
+ conditions["pressure"].append(
276
+ state.conditions[i_cell].pressure)
277
+ conditions["air_density"].append(
278
+ state.conditions[i_cell].air_density)
279
+ return conditions
280
+
281
+ def get_species_ordering(self) -> Dict[str, int]:
282
+ """
283
+ Get the species ordering for the state.
284
+
285
+ Returns
286
+ -------
287
+ Dict[str, int]
288
+ Dictionary of species names and their indices.
289
+ """
290
+ return self.__species_ordering
291
+
292
+ def get_user_defined_rate_parameters_ordering(self) -> Dict[str, int]:
293
+ """
294
+ Get the user-defined rate parameters ordering for the state.
295
+
296
+ Returns
297
+ -------
298
+ Dict[str, int]
299
+ Dictionary of user-defined rate parameter names and their indices.
300
+ """
301
+ return self.__user_defined_rate_parameters_ordering
302
+
303
+
304
+ class MICM():
305
+ """
306
+ The MICM class is a wrapper around the C++ MICM solver. It provides methods to create a solver,
307
+ create a state, and solve the system of equations.
308
+
309
+ Parameters
310
+ ----------
311
+ config_path : FilePath
312
+ Path to the configuration file.
313
+ mechanism : mechanism_configuration.Mechanism
314
+ Mechanism object which specifies the chemical mechanism to use.
315
+ solver_type : SolverType
316
+ Type of solver to use.
317
+ number_of_grid_cells : int
318
+ Number of grid cells to use. The default is 1.
319
+ """
320
+
321
+ def __init__(
322
+ self,
323
+ config_path: FilePath = None,
324
+ mechanism: Mechanism = None,
325
+ solver_type: Any = None,
326
+ ignore_non_gas_phases: bool = True,
327
+ ):
328
+ """ Initialize the MICM solver.
329
+
330
+ Parameters
331
+ ----------
332
+ config_path : FilePath, optional
333
+ Path to the configuration file. If provided, this will be used to create the solver.
334
+ mechanism : Mechanism, optional
335
+ Mechanism object which specifies the chemical mechanism to use. If provided, this will be used
336
+ to create the solver.
337
+ solver_type : SolverType, optional
338
+ Type of solver to use. If not provided, the default Rosenbrock (with standard-ordered matrices) solver type will be used.
339
+ ignore_non_gas_phases : bool, optional
340
+ If True, non-gas phases will be ignored when configuring micm with the mechanism. This is only needed
341
+ until micm properly supports non-gas phases. This option is only supported when passing in a mechanism.
342
+ """
343
+ if solver_type is None:
344
+ solver_type = SolverType.rosenbrock_standard_order
345
+ self.__solver_type = solver_type
346
+ self.__vector_size = _vector_size(solver_type)
347
+ if config_path is None and mechanism is None:
348
+ raise ValueError(
349
+ "Either config_path or mechanism must be provided.")
350
+ if config_path is not None and mechanism is not None:
351
+ raise ValueError(
352
+ "Only one of config_path or mechanism must be provided.")
353
+ if config_path is not None:
354
+ self.__solver = _create_solver(config_path, solver_type)
355
+ elif mechanism is not None:
356
+ self.__solver = _create_solver_from_mechanism(
357
+ mechanism, solver_type, ignore_non_gas_phases)
358
+
359
+ def solver_type(self) -> SolverType:
360
+ """
361
+ Get the type of solver used.
362
+
363
+ Returns
364
+ -------
365
+ SolverType
366
+ The type of solver used.
367
+ """
368
+ return self.__solver_type
369
+
370
+ def create_state(self, number_of_grid_cells: int = 1) -> State:
371
+ """
372
+ Create a new state object.
373
+
374
+ Returns
375
+ -------
376
+ State
377
+ A new state object.
378
+ """
379
+ return State(self.__solver, number_of_grid_cells, self.__vector_size)
380
+
381
+ def solve(
382
+ self,
383
+ state: State,
384
+ time_step: float,
385
+ ):
386
+ """
387
+ Solve the system of equations for the given state and time step.
388
+
389
+ Parameters
390
+ ----------
391
+ state : State
392
+ State object containing the initial conditions.
393
+ time_step : float
394
+ Time step in seconds.
395
+
396
+ Returns
397
+ -------
398
+ State
399
+ Updated state object after solving the system of equations.
400
+ """
401
+ if not isinstance(state, State):
402
+ raise TypeError("state must be an instance of State.")
403
+ if not isinstance(time_step, (int, float)):
404
+ raise TypeError("time_step must be an int or float.")
405
+ states = state.get_internal_states()
406
+ for _state in states:
407
+ _micm_solve(self.__solver, _state, time_step)