musica 0.14.4__cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.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.
Files changed (92) hide show
  1. musica/__init__.py +11 -0
  2. musica/_musica.cpython-310-aarch64-linux-gnu.so +0 -0
  3. musica/_version.py +1 -0
  4. musica/backend.py +58 -0
  5. musica/carma/__init__.py +20 -0
  6. musica/carma/carma.py +1727 -0
  7. musica/constants.py +3 -0
  8. musica/cuda.py +13 -0
  9. musica/examples/__init__.py +1 -0
  10. musica/examples/carma_aluminum.py +124 -0
  11. musica/examples/carma_sulfate.py +246 -0
  12. musica/examples/examples.py +175 -0
  13. musica/examples/lorenz.py +295 -0
  14. musica/examples/sulfate_box_model.py +439 -0
  15. musica/examples/ts1_latin_hypercube.py +245 -0
  16. musica/main.py +128 -0
  17. musica/mechanism_configuration/__init__.py +18 -0
  18. musica/mechanism_configuration/ancillary.py +6 -0
  19. musica/mechanism_configuration/arrhenius.py +149 -0
  20. musica/mechanism_configuration/branched.py +140 -0
  21. musica/mechanism_configuration/emission.py +82 -0
  22. musica/mechanism_configuration/first_order_loss.py +90 -0
  23. musica/mechanism_configuration/mechanism.py +93 -0
  24. musica/mechanism_configuration/phase.py +58 -0
  25. musica/mechanism_configuration/phase_species.py +58 -0
  26. musica/mechanism_configuration/photolysis.py +98 -0
  27. musica/mechanism_configuration/reaction_component.py +54 -0
  28. musica/mechanism_configuration/reactions.py +32 -0
  29. musica/mechanism_configuration/species.py +65 -0
  30. musica/mechanism_configuration/surface.py +98 -0
  31. musica/mechanism_configuration/taylor_series.py +136 -0
  32. musica/mechanism_configuration/ternary_chemical_activation.py +160 -0
  33. musica/mechanism_configuration/troe.py +160 -0
  34. musica/mechanism_configuration/tunneling.py +126 -0
  35. musica/mechanism_configuration/user_defined.py +99 -0
  36. musica/mechanism_configuration/utils.py +10 -0
  37. musica/micm/__init__.py +10 -0
  38. musica/micm/conditions.py +49 -0
  39. musica/micm/micm.py +135 -0
  40. musica/micm/solver.py +8 -0
  41. musica/micm/solver_result.py +24 -0
  42. musica/micm/state.py +220 -0
  43. musica/micm/utils.py +18 -0
  44. musica/tuvx/__init__.py +11 -0
  45. musica/tuvx/grid.py +98 -0
  46. musica/tuvx/grid_map.py +167 -0
  47. musica/tuvx/profile.py +130 -0
  48. musica/tuvx/profile_map.py +167 -0
  49. musica/tuvx/radiator.py +95 -0
  50. musica/tuvx/radiator_map.py +173 -0
  51. musica/tuvx/tuvx.py +283 -0
  52. musica-0.14.4.dist-info/METADATA +427 -0
  53. musica-0.14.4.dist-info/RECORD +92 -0
  54. musica-0.14.4.dist-info/WHEEL +6 -0
  55. musica-0.14.4.dist-info/entry_points.txt +3 -0
  56. musica-0.14.4.dist-info/licenses/AUTHORS.md +59 -0
  57. musica-0.14.4.dist-info/licenses/LICENSE +201 -0
  58. musica.libs/libaec-34bb4966.so.0.0.8 +0 -0
  59. musica.libs/libblas-8ed0a6f9.so.3.8.0 +0 -0
  60. musica.libs/libbrotlicommon-b6e6c8bd.so.1.0.6 +0 -0
  61. musica.libs/libbrotlidec-5094ef0a.so.1.0.6 +0 -0
  62. musica.libs/libcom_err-6d8d18aa.so.2.1 +0 -0
  63. musica.libs/libcrypt-258f54d5.so.1.1.0 +0 -0
  64. musica.libs/libcrypto-3eda328c.so.1.1.1k +0 -0
  65. musica.libs/libcurl-7faeef02.so.4.5.0 +0 -0
  66. musica.libs/libdf-9661c601.so.0.0.0 +0 -0
  67. musica.libs/libgfortran-e1b7dfc8.so.5.0.0 +0 -0
  68. musica.libs/libgssapi_krb5-fe951f80.so.2.2 +0 -0
  69. musica.libs/libhdf5-463e48d5.so.103.1.0 +0 -0
  70. musica.libs/libhdf5_hl-74316838.so.100.1.2 +0 -0
  71. musica.libs/libidn2-1b2a13b7.so.0.3.6 +0 -0
  72. musica.libs/libjpeg-ee25248c.so.62.2.0 +0 -0
  73. musica.libs/libk5crypto-84470bb3.so.3.1 +0 -0
  74. musica.libs/libkeyutils-fe6e95a9.so.1.6 +0 -0
  75. musica.libs/libkrb5-26ef5d84.so.3.3 +0 -0
  76. musica.libs/libkrb5support-875e89dc.so.0.1 +0 -0
  77. musica.libs/liblapack-8d137073.so.3.8.0 +0 -0
  78. musica.libs/liblber-2-86b08e65.4.so.2.10.9 +0 -0
  79. musica.libs/libldap-2-5c1dd279.4.so.2.10.9 +0 -0
  80. musica.libs/libmfhdf-9c336c5f.so.0.0.0 +0 -0
  81. musica.libs/libnetcdf-71a067be.so.15.0.1 +0 -0
  82. musica.libs/libnetcdff-6a455dd4.so.7.0.0 +0 -0
  83. musica.libs/libnghttp2-3a94c239.so.14.17.0 +0 -0
  84. musica.libs/libpcre2-8-8701a61e.so.0.7.1 +0 -0
  85. musica.libs/libpsl-130094ea.so.5.3.1 +0 -0
  86. musica.libs/libsasl2-076b3c1f.so.3.0.0 +0 -0
  87. musica.libs/libselinux-5700a1fd.so.1 +0 -0
  88. musica.libs/libssh-e0d3bd94.so.4.8.7 +0 -0
  89. musica.libs/libssl-f60bf0e2.so.1.1.1k +0 -0
  90. musica.libs/libsz-81b556a2.so.2.0.1 +0 -0
  91. musica.libs/libtirpc-1fa9018c.so.3.0.0 +0 -0
  92. musica.libs/libunistring-be03fd41.so.2.1.0 +0 -0
@@ -0,0 +1,10 @@
1
+ from .utils import species_ordering, user_defined_rate_parameters_ordering
2
+ from .state import State
3
+ from .solver_result import SolverState, SolverStats, SolverResult
4
+ from .solver import SolverType
5
+ from .micm import MICM
6
+ from .conditions import Conditions
7
+ from .. import backend
8
+ _backend = backend.get_backend()
9
+
10
+ __version__ = _backend._micm._get_micm_version()
@@ -0,0 +1,49 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from typing import Optional, Union
5
+ from ..constants import GAS_CONSTANT
6
+
7
+ from .. import backend
8
+
9
+ _backend = backend.get_backend()
10
+ Conditions = _backend._micm._Conditions
11
+ original_init = Conditions.__init__
12
+
13
+
14
+ def __init__(
15
+ self,
16
+ temperature: Optional[Union[float, int]] = None,
17
+ pressure: Optional[Union[float, int]] = None,
18
+ air_density: Optional[Union[float, int]] = None,
19
+ ):
20
+ """
21
+ Initializes the Conditions object with the given parameters.
22
+
23
+ Args:
24
+ temperature (float): Temperature in Kelvin.
25
+ pressure (float): Pressure in Pascals.
26
+ air_density (float): Air density in mol m-3
27
+ """
28
+ original_init(self)
29
+ if temperature is not None:
30
+ self.temperature = temperature
31
+ if pressure is not None:
32
+ self.pressure = pressure
33
+ if air_density is not None:
34
+ self.air_density = air_density
35
+ elif temperature is not None and pressure is not None:
36
+ self.air_density = 1.0 / (GAS_CONSTANT * temperature / pressure)
37
+
38
+
39
+ Conditions.__doc__ = """
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
+ Attributes:
44
+ temperature (float): Temperature in Kelvin.
45
+ pressure (float): Pressure in Pascals.
46
+ air_density (float): Air density in mol m-3
47
+ """
48
+
49
+ Conditions.__init__ = __init__
musica/micm/micm.py ADDED
@@ -0,0 +1,135 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from __future__ import annotations
5
+ from typing import Union, Any, TYPE_CHECKING, List
6
+ from os import PathLike
7
+
8
+ from .state import State
9
+ from .solver import SolverType
10
+ from .solver_result import SolverResult
11
+ from .. import backend
12
+
13
+ _backend = backend.get_backend()
14
+
15
+ create_solver = _backend._micm._create_solver
16
+ create_solver_from_mechanism = _backend._micm._create_solver_from_mechanism
17
+ micm_solve = _backend._micm._micm_solve
18
+ vector_size = _backend._micm._vector_size
19
+
20
+ # For type hints
21
+ if TYPE_CHECKING:
22
+ from ..mechanism_configuration import Mechanism
23
+ from .solver_result import SolverResult as SolverResultType
24
+ else:
25
+ Mechanism = _backend._mechanism_configuration._Mechanism
26
+ SolverResultType = SolverResult
27
+
28
+ FilePath = Union[str, "PathLike[str]"]
29
+
30
+
31
+ class MICM():
32
+ """
33
+ The MICM class is a wrapper around the C++ MICM solver. It provides methods to create a solver,
34
+ create a state, and solve the system of equations.
35
+
36
+ Parameters
37
+ ----------
38
+ config_path : FilePath
39
+ Path to the configuration file.
40
+ mechanism : mechanism_configuration.Mechanism
41
+ Mechanism object which specifies the chemical mechanism to use.
42
+ solver_type : SolverType
43
+ Type of solver to use.
44
+ number_of_grid_cells : int
45
+ Number of grid cells to use. The default is 1.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ config_path: FilePath = None,
51
+ mechanism: Mechanism = None,
52
+ solver_type: Any = None,
53
+ ignore_non_gas_phases: bool = True,
54
+ ):
55
+ """ Initialize the MICM solver.
56
+
57
+ Parameters
58
+ ----------
59
+ config_path : FilePath, optional
60
+ Path to the configuration file. If provided, this will be used to create the solver.
61
+ mechanism : Mechanism, optional
62
+ Mechanism object which specifies the chemical mechanism to use. If provided, this will be used
63
+ to create the solver.
64
+ solver_type : SolverType, optional
65
+ Type of solver to use. If not provided, the default Rosenbrock (with standard-ordered matrices) solver type will be used.
66
+ ignore_non_gas_phases : bool, optional
67
+ If True, non-gas phases will be ignored when configuring micm with the mechanism. This is only needed
68
+ until micm properly supports non-gas phases. This option is only supported when passing in a mechanism.
69
+ """
70
+ if solver_type is None:
71
+ solver_type = SolverType.rosenbrock_standard_order
72
+ self.__solver_type = solver_type
73
+ self.__vector_size = vector_size(solver_type)
74
+ if self.__vector_size <= 0:
75
+ raise ValueError(f"Invalid vector size: {self.__vector_size}")
76
+ if config_path is None and mechanism is None:
77
+ raise ValueError(
78
+ "Either config_path or mechanism must be provided.")
79
+ if config_path is not None and mechanism is not None:
80
+ raise ValueError(
81
+ "Only one of config_path or mechanism must be provided.")
82
+ if config_path is not None:
83
+ self.__solver = create_solver(config_path, solver_type)
84
+ elif mechanism is not None:
85
+ self.__solver = create_solver_from_mechanism(
86
+ mechanism, solver_type, ignore_non_gas_phases)
87
+
88
+ def solver_type(self) -> SolverType:
89
+ """
90
+ Get the type of solver used.
91
+
92
+ Returns
93
+ -------
94
+ SolverType
95
+ The type of solver used.
96
+ """
97
+ return self.__solver_type
98
+
99
+ def create_state(self, number_of_grid_cells: int = 1) -> State:
100
+ """
101
+ Create a new state object.
102
+
103
+ Returns
104
+ -------
105
+ State
106
+ A new state object.
107
+ """
108
+ return State(self.__solver, number_of_grid_cells, self.__vector_size)
109
+
110
+ def solve(
111
+ self,
112
+ state: State,
113
+ time_step: float,
114
+ ) -> SolverResult:
115
+ """
116
+ Solve the system of equations for the given state and time step.
117
+
118
+ Parameters
119
+ ----------
120
+ state : State
121
+ State object containing the initial conditions.
122
+ time_step : float
123
+ Time step in seconds.
124
+
125
+ Returns
126
+ -------
127
+ SolverResult
128
+ A SolverResult object containing the solver state and statistics.
129
+ """
130
+ if not isinstance(state, State):
131
+ raise TypeError("state must be an instance of State.")
132
+ if not isinstance(time_step, (int, float)):
133
+ raise TypeError("time_step must be an int or float.")
134
+
135
+ return micm_solve(self.__solver, state.get_internal_state(), time_step)
musica/micm/solver.py ADDED
@@ -0,0 +1,8 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from .. import backend
5
+
6
+ _backend = backend.get_backend()
7
+
8
+ SolverType = _backend._micm._SolverType
@@ -0,0 +1,24 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """
5
+ Module for exposing solver result types from MICM.
6
+
7
+ This module provides access to:
8
+ - SolverState: Enum representing the final state of the solver
9
+ - SolverStats: Statistics from a solver run
10
+ - SolverResult: Combined result containing both state and statistics
11
+ """
12
+
13
+ from .. import backend
14
+
15
+ _backend = backend.get_backend()
16
+
17
+ # Expose the SolverState enum
18
+ SolverState = _backend._micm._SolverState
19
+
20
+ # Expose the SolverStats class
21
+ SolverStats = _backend._micm._SolverResultsStats
22
+
23
+ # Expose the SolverResult class
24
+ SolverResult = _backend._micm._SolverResult
musica/micm/state.py ADDED
@@ -0,0 +1,220 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ from typing import Optional, Dict, List, Union, Any
5
+ import math
6
+
7
+ from ..constants import GAS_CONSTANT
8
+ from .. import backend
9
+ from .utils import is_scalar_number, species_ordering, user_defined_rate_parameters_ordering
10
+
11
+ _backend = backend.get_backend()
12
+
13
+ create_state = _backend._micm._create_state
14
+
15
+
16
+ class State():
17
+ """
18
+ State class for the MICM solver. It contains the initial conditions and species concentrations.
19
+ """
20
+
21
+ def __init__(self, solver: Any, number_of_grid_cells: int, vector_size: int = 0):
22
+ if number_of_grid_cells < 1:
23
+ raise ValueError("number_of_grid_cells must be greater than 0.")
24
+ super().__init__()
25
+ self.__state = create_state(solver, number_of_grid_cells)
26
+ self.__species_ordering = species_ordering(self.__state)
27
+ self.__user_defined_rate_parameters_ordering = user_defined_rate_parameters_ordering(self.__state)
28
+ self.__number_of_grid_cells = number_of_grid_cells
29
+ self.__vector_size = vector_size
30
+
31
+ def get_internal_state(self) -> Any:
32
+ """
33
+ Get the internal state object. This is used for passing the state to the solver.
34
+
35
+ Returns
36
+ -------
37
+ _State
38
+ Internal state object.
39
+ """
40
+ return self.__state
41
+
42
+ def set_concentrations(self, concentrations: Dict[str, Union[Union[float, int], List[Union[float, int]]]]):
43
+ """
44
+ Set the concentrations of the species in the state. Any species not in the
45
+ dictionary will be set to zero. The concentrations can be a single value when solving
46
+ for a single grid cell, or a list of values when solving for multiple grid cells.
47
+
48
+ Parameters
49
+ ----------
50
+ concentrations : Dict[str, Union[Union[float, int], List[Union[float, int]]]]
51
+ Dictionary of species names and their concentrations.
52
+ """
53
+ n_species = len(self.__species_ordering)
54
+ for name, value in concentrations.items():
55
+ if name not in self.__species_ordering:
56
+ raise ValueError(f"Species {name} not found in the mechanism.")
57
+ i_species = self.__species_ordering[name]
58
+ if is_scalar_number(value):
59
+ value = [value]
60
+ if len(value) != self.__number_of_grid_cells:
61
+ raise ValueError(f"Concentration list for {name} must have length {self.__number_of_grid_cells}.")
62
+ for i_cell in range(self.__number_of_grid_cells):
63
+ group_index = i_cell // self.__vector_size
64
+ row_in_group = i_cell % self.__vector_size
65
+ idx = (group_index * n_species + i_species) * self.__vector_size + row_in_group
66
+ self.__state.concentrations[idx] = value[i_cell]
67
+
68
+ def set_user_defined_rate_parameters(
69
+ self, user_defined_rate_parameters: Dict[str, Union[Union[float, int], List[Union[float, int]]]]):
70
+ """
71
+ Set the user-defined rate parameters in the state. Any parameter not in the
72
+ dictionary will be set to zero. The parameters can be a single value when solving
73
+ for a single grid cell, or a list of values when solving for multiple grid cells.
74
+
75
+ Parameters
76
+ ----------
77
+ user_defined_rate_parameters : Dict[str, Union[Union[float, int], List[Union[float, int]]]]
78
+ Dictionary of user-defined rate parameter names and their values.
79
+ """
80
+ n_params = len(self.__user_defined_rate_parameters_ordering)
81
+ for name, value in user_defined_rate_parameters.items():
82
+ if name not in self.__user_defined_rate_parameters_ordering:
83
+ raise ValueError(f"User-defined rate parameter {name} not found in the mechanism.")
84
+ i_param = self.__user_defined_rate_parameters_ordering[name]
85
+ if is_scalar_number(value):
86
+ value = [value]
87
+ if len(value) != self.__number_of_grid_cells:
88
+ raise ValueError(
89
+ f"User-defined rate parameter list for {name} must have length {self.__number_of_grid_cells}.")
90
+ for i_cell in range(self.__number_of_grid_cells):
91
+ group_index = i_cell // self.__vector_size
92
+ row_in_group = i_cell % self.__vector_size
93
+ idx = (group_index * n_params + i_param) * self.__vector_size + row_in_group
94
+ self.__state.user_defined_rate_parameters[idx] = value[i_cell]
95
+
96
+ def set_conditions(self,
97
+ temperatures: Optional[Union[Union[float, int], List[Union[float, int]]]] = None,
98
+ pressures: Optional[Union[Union[float, int], List[Union[float, int]]]] = None,
99
+ air_densities: Optional[Union[Union[float, int], List[Union[float, int]]]] = None):
100
+ """
101
+ Set the conditions for the state. The individual conditions can be a single value
102
+ when solving for a single grid cell, or a list of values when solving for multiple grid cells.
103
+ If air density is not provided, it will be calculated from the Ideal Gas Law using the provided
104
+ temperature and pressure. If temperature or pressure are not provided, their values will remain
105
+ unchanged.
106
+
107
+ Parameters
108
+ ----------
109
+ temperatures : Optional[Union[float, List[float]]]
110
+ Temperature in Kelvin.
111
+ pressures : Optional[Union[float, List[float]]]
112
+ Pressure in Pascals.
113
+ air_densities : Optional[Union[float, List[float]]]
114
+ Air density in mol m-3. If not provided, it will be calculated from the Ideal Gas Law.
115
+ """
116
+
117
+ empty = [None] * self.__number_of_grid_cells
118
+
119
+ def check_and_expand(param, name):
120
+ if param is not None:
121
+ if is_scalar_number(param):
122
+ if self.__number_of_grid_cells > 1:
123
+ raise ValueError(f"{name} must be a list of length {self.__number_of_grid_cells}.")
124
+ param = [param]
125
+ elif len(param) != self.__number_of_grid_cells:
126
+ raise ValueError(f"{name} must be a list of length {self.__number_of_grid_cells}.")
127
+ else:
128
+ param = empty
129
+ return param
130
+ temperatures = check_and_expand(temperatures, "temperatures")
131
+ pressures = check_and_expand(pressures, "pressures")
132
+ air_densities = check_and_expand(air_densities, "air_densities")
133
+
134
+ for condition, temp, pres, dens in zip(self.__state.conditions, temperatures, pressures, air_densities):
135
+ condition.temperature = temp if temp is not None else condition.temperature
136
+ condition.pressure = pres if pres is not None else condition.pressure
137
+ condition.air_density = dens if dens is not None else condition.pressure / \
138
+ (GAS_CONSTANT * condition.temperature)
139
+
140
+ def get_concentrations(self) -> Dict[str, List[float]]:
141
+ """
142
+ Get the concentrations of the species in the state.
143
+
144
+ Returns
145
+ -------
146
+ Dict[str, List[float]]
147
+ Dictionary of species names and their concentrations.
148
+ """
149
+ concentrations = {}
150
+ n_species = len(self.__species_ordering)
151
+
152
+ for species, i_species in self.__species_ordering.items():
153
+ concentrations[species] = []
154
+ for i_cell in range(self.__number_of_grid_cells):
155
+ group_index = i_cell // self.__vector_size
156
+ row_in_group = i_cell % self.__vector_size
157
+ idx = (group_index * n_species + i_species) * self.__vector_size + row_in_group
158
+ concentrations[species].append(self.__state.concentrations[idx])
159
+ return concentrations
160
+
161
+ def get_user_defined_rate_parameters(self) -> Dict[str, List[float]]:
162
+ """
163
+ Get the user-defined rate parameters in the state.
164
+
165
+ Returns
166
+ -------
167
+ Dict[str, List[float]]
168
+ Dictionary of user-defined rate parameter names and their values.
169
+ """
170
+ user_defined_rate_parameters = {}
171
+ n_params = len(self.__user_defined_rate_parameters_ordering)
172
+ for param, i_param in self.__user_defined_rate_parameters_ordering.items():
173
+ user_defined_rate_parameters[param] = []
174
+ for i_cell in range(self.__number_of_grid_cells):
175
+ group_index = i_cell // self.__vector_size
176
+ row_in_group = i_cell % self.__vector_size
177
+ idx = (group_index * n_params + i_param) * self.__vector_size + row_in_group
178
+ user_defined_rate_parameters[param].append(self.__state.user_defined_rate_parameters[idx])
179
+ return user_defined_rate_parameters
180
+
181
+ def get_conditions(self) -> Dict[str, List[float]]:
182
+ """
183
+ Get the conditions for the state.
184
+
185
+ Returns
186
+ -------
187
+ Dict[str, List[float]]
188
+ Dictionary of conditions names and their values.
189
+ """
190
+ conditions = {}
191
+ conditions["temperature"] = []
192
+ conditions["pressure"] = []
193
+ conditions["air_density"] = []
194
+ for i_cell in range(self.__number_of_grid_cells):
195
+ conditions["temperature"].append(self.__state.conditions[i_cell].temperature)
196
+ conditions["pressure"].append(self.__state.conditions[i_cell].pressure)
197
+ conditions["air_density"].append(self.__state.conditions[i_cell].air_density)
198
+ return conditions
199
+
200
+ def get_species_ordering(self) -> Dict[str, int]:
201
+ """
202
+ Get the species ordering for the state.
203
+
204
+ Returns
205
+ -------
206
+ Dict[str, int]
207
+ Dictionary of species names and their indices.
208
+ """
209
+ return self.__species_ordering
210
+
211
+ def get_user_defined_rate_parameters_ordering(self) -> Dict[str, int]:
212
+ """
213
+ Get the user-defined rate parameters ordering for the state.
214
+
215
+ Returns
216
+ -------
217
+ Dict[str, int]
218
+ Dictionary of user-defined rate parameter names and their indices.
219
+ """
220
+ return self.__user_defined_rate_parameters_ordering
musica/micm/utils.py ADDED
@@ -0,0 +1,18 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import numpy as np
5
+
6
+ from .. import backend
7
+
8
+ _backend = backend.get_backend()
9
+
10
+ species_ordering = _backend._micm._species_ordering
11
+ user_defined_rate_parameters_ordering = _backend._micm._user_defined_rate_parameters_ordering
12
+
13
+
14
+ def is_scalar_number(x):
15
+ return (
16
+ isinstance(x, (int, float, np.number))
17
+ and not isinstance(x, bool)
18
+ )
@@ -0,0 +1,11 @@
1
+ from .radiator import Radiator
2
+ from .radiator_map import RadiatorMap
3
+ from .profile import Profile
4
+ from .profile_map import ProfileMap
5
+ from .grid import Grid
6
+ from .grid_map import GridMap
7
+ from .tuvx import TUVX
8
+ from .. import backend
9
+ _backend = backend.get_backend()
10
+
11
+ __version__ = _backend._tuvx._get_tuvx_version() if backend.tuvx_available() else None
musica/tuvx/grid.py ADDED
@@ -0,0 +1,98 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """
4
+ TUV-x Grid class.
5
+
6
+ This module provides a class for defining grids on which TUV-x profiles exist.
7
+ Typically, this would be used to define vertical and wavelength grids.
8
+
9
+ Note: TUV-x is only available on macOS and Linux platforms.
10
+ """
11
+
12
+ from typing import Optional
13
+ import numpy as np
14
+ from .. import backend
15
+
16
+ _backend = backend.get_backend()
17
+
18
+ Grid = _backend._tuvx._Grid if backend.tuvx_available() else None
19
+
20
+ if backend.tuvx_available():
21
+ original_init = Grid.__init__
22
+
23
+ def __init__(self, *, name: str, units: str,
24
+ num_sections: Optional[int] = None,
25
+ edges: Optional[np.ndarray] = None,
26
+ midpoints: Optional[np.ndarray] = None,
27
+ **kwargs):
28
+ """Initialize a Grid instance. Note that at least one of num_sections, edges, or midpoints
29
+ must be provided.
30
+
31
+ Args:
32
+ name: Name of the grid
33
+ units: Units of the grid values
34
+ num_sections: Optional number of grid sections
35
+ edges: Optional array of edge values (length num_sections + 1)
36
+ midpoints: Optional array of midpoint values (length num_sections)
37
+ **kwargs: Additional arguments passed to the C++ constructor
38
+ """
39
+ if (num_sections is None and edges is None and midpoints is None):
40
+ raise ValueError("At least one of num_sections, edges, or midpoints must be provided.")
41
+ if (num_sections is None):
42
+ if (edges is not None):
43
+ num_sections = len(edges) - 1
44
+ elif (midpoints is not None):
45
+ num_sections = len(midpoints)
46
+ # Call the original C++ constructor correctly
47
+ original_init(self, name=name, units=units, num_sections=num_sections, **kwargs)
48
+
49
+ # Set edges or midpoints if provided
50
+ if edges is not None:
51
+ self.edges = edges
52
+ if midpoints is not None:
53
+ self.midpoints = midpoints
54
+
55
+ Grid.__init__ = __init__
56
+
57
+ def __str__(self):
58
+ """User-friendly string representation."""
59
+ return f"Grid(name={self.name}, units={self.units}, num_sections={self.num_sections})"
60
+
61
+ Grid.__str__ = __str__
62
+
63
+ def __repr__(self):
64
+ """Detailed string representation for debugging."""
65
+ return (f"Grid(name={self.name}, units={self.units}, num_sections={self.num_sections}, "
66
+ f"edges={self.edges}, midpoints={self.midpoints})")
67
+
68
+ Grid.__repr__ = __repr__
69
+
70
+ def __len__(self):
71
+ """Return the number of sections in the grid."""
72
+ return self.num_sections
73
+
74
+ Grid.__len__ = __len__
75
+
76
+ def __eq__(self, other):
77
+ """Check equality with another Grid instance."""
78
+ if not isinstance(other, Grid):
79
+ return NotImplemented
80
+ return (self.name == other.name and
81
+ self.units == other.units and
82
+ self.num_sections == other.num_sections and
83
+ np.array_equal(self.edges, other.edges) and
84
+ np.array_equal(self.midpoints, other.midpoints))
85
+
86
+ Grid.__eq__ = __eq__
87
+
88
+ def __bool__(self):
89
+ """Return True if the grid has sections."""
90
+ return self.num_sections > 0
91
+
92
+ Grid.__bool__ = __bool__
93
+
94
+ def __contains__(self, value):
95
+ """Check if a value is within the grid bounds."""
96
+ return self.edges[0] <= value <= self.edges[-1]
97
+
98
+ Grid.__contains__ = __contains__