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,173 @@
1
+ # Copyright (C) 2023-2026 University Corporation for Atmospheric Research
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ """
4
+ TUV-x RadiatorMap class.
5
+
6
+ This module provides a class for managing collections of TUV-x radiators.
7
+ The RadiatorMap class allows dictionary-style access to radiators using their names as keys.
8
+
9
+ Note: TUV-x is only available on macOS and Linux platforms.
10
+ """
11
+
12
+ from typing import Iterator
13
+ from .. import backend
14
+ from .radiator import Radiator
15
+
16
+ _backend = backend.get_backend()
17
+
18
+ RadiatorMap = _backend._tuvx._RadiatorMap if backend.tuvx_available() else None
19
+
20
+ if backend.tuvx_available():
21
+ original_init = RadiatorMap.__init__
22
+
23
+ def __init__(self, **kwargs):
24
+ """Initialize a RadiatorMap instance.
25
+
26
+ Args:
27
+ **kwargs: Additional arguments passed to the C++ constructor
28
+ """
29
+ original_init(self, **kwargs)
30
+
31
+ RadiatorMap.__init__ = __init__
32
+
33
+ def __str__(self):
34
+ """User-friendly string representation."""
35
+ return f"RadiatorMap(num_radiators={len(self)})"
36
+
37
+ RadiatorMap.__str__ = __str__
38
+
39
+ def __repr__(self):
40
+ """Detailed string representation for debugging."""
41
+ radiator_details = []
42
+ for i in range(len(self)):
43
+ radiator = self.get_radiator_by_index(i)
44
+ radiator_details.append(f"{radiator.name}")
45
+ return f"RadiatorMap(radiators={radiator_details})"
46
+
47
+ RadiatorMap.__repr__ = __repr__
48
+
49
+ def __len__(self):
50
+ """Return the number of radiators in the map."""
51
+ return self.get_number_of_radiators()
52
+
53
+ RadiatorMap.__len__ = __len__
54
+
55
+ def __bool__(self):
56
+ """Return True if the map has any radiators."""
57
+ return len(self) > 0
58
+
59
+ RadiatorMap.__bool__ = __bool__
60
+
61
+ def __getitem__(self, key) -> Radiator:
62
+ """Get a radiator by name.
63
+
64
+ Args:
65
+ key: Name of the radiator to retrieve
66
+
67
+ Returns:
68
+ Radiator instance corresponding to the given name
69
+
70
+ Raises:
71
+ KeyError: If no radiator with the given name exists
72
+ """
73
+ if not isinstance(key, str):
74
+ raise TypeError("Radiator name must be a string.")
75
+ try:
76
+ radiator = self.get_radiator(key)
77
+ if radiator is None:
78
+ raise KeyError(f"Radiator with name '{key}' not found.")
79
+ return radiator
80
+ except Exception as e:
81
+ raise KeyError(f"No radiator found with name='{key}'") from e
82
+
83
+ RadiatorMap.__getitem__ = __getitem__
84
+
85
+ def __setitem__(self, key: str, radiator: Radiator):
86
+ """Set a radiator in the map by name.
87
+
88
+ Args:
89
+ key: Name of the radiator to set
90
+ radiator: Radiator instance to associate with the given name
91
+ """
92
+ if not isinstance(key, str):
93
+ raise TypeError("Radiator name must be a string.")
94
+ if not isinstance(radiator, Radiator):
95
+ raise TypeError("Value must be a Radiator instance.")
96
+ if radiator.name != key:
97
+ raise ValueError("Radiator name does not match the key.")
98
+ self.add_radiator(radiator)
99
+
100
+ RadiatorMap.__setitem__ = __setitem__
101
+
102
+ def __iter__(self) -> Iterator[Radiator]:
103
+ """Iterator over the radiators in the map.
104
+
105
+ Yields:
106
+ Radiator instances in the map
107
+ """
108
+ for i in range(len(self)):
109
+ yield self.get_radiator_by_index(i).name
110
+
111
+ RadiatorMap.__iter__ = __iter__
112
+
113
+ def __contains__(self, key: str) -> bool:
114
+ """Check if a radiator with the given name exists in the map.
115
+
116
+ Args:
117
+ key: Name of the radiator to check
118
+
119
+ Returns:
120
+ True if a radiator with the given name exists, False otherwise
121
+ """
122
+ if not isinstance(key, str):
123
+ return False
124
+ try:
125
+ radiator = self.get_radiator(str(key))
126
+ return radiator is not None
127
+ except (ValueError, KeyError):
128
+ return False
129
+
130
+ RadiatorMap.__contains__ = __contains__
131
+
132
+ def clear(self):
133
+ """Remove all radiators from the map."""
134
+ num_radiators = len(self)
135
+ for _ in range(num_radiators):
136
+ self.remove_radiator_by_index(0)
137
+
138
+ RadiatorMap.clear = clear
139
+
140
+ def items(self):
141
+ """Iterator over (name, radiator) pairs in the map.
142
+
143
+ Yields:
144
+ Tuples of (radiator_name, Radiator instance)
145
+ """
146
+ for i in range(len(self)):
147
+ radiator = self.get_radiator_by_index(i)
148
+ yield (radiator.name, radiator)
149
+
150
+ RadiatorMap.items = items
151
+
152
+ def keys(self):
153
+ """Iterator over radiator names in the map.
154
+
155
+ Yields:
156
+ Radiator names as strings
157
+ """
158
+ for i in range(len(self)):
159
+ radiator = self.get_radiator_by_index(i)
160
+ yield radiator.name
161
+
162
+ RadiatorMap.keys = keys
163
+
164
+ def values(self):
165
+ """Iterator over Radiator instances in the map.
166
+
167
+ Yields:
168
+ Radiator instances
169
+ """
170
+ for i in range(len(self)):
171
+ yield self.get_radiator_by_index(i)
172
+
173
+ RadiatorMap.values = values
musica/tuvx/tuvx.py ADDED
@@ -0,0 +1,283 @@
1
+ """
2
+ TUV-x photolysis calculator Python interface.
3
+
4
+ This module provides a simplified Python interface to the TUV-x photolysis calculator.
5
+ It allows users to create a TUV-x instance from a JSON configuration file and
6
+ calculate photolysis rates and heating rates.
7
+
8
+ Note: TUV-x is only available on macOS and Linux platforms.
9
+ """
10
+
11
+ import os
12
+ import json
13
+ import tempfile
14
+ from typing import Dict, Optional, Tuple
15
+ import numpy as np
16
+ from .. import backend
17
+
18
+ _backend = backend.get_backend()
19
+
20
+ # Import the GridMap class from the backend
21
+ if backend.tuvx_available():
22
+ from .grid_map import GridMap
23
+ from .profile import Profile
24
+ from .profile_map import ProfileMap
25
+ from .radiator import Radiator
26
+ from .radiator_map import RadiatorMap
27
+ else:
28
+ GridMap = None
29
+ Profile = None
30
+ ProfileMap = None
31
+ Radiator = None
32
+ RadiatorMap = None
33
+
34
+
35
+ class TUVX:
36
+ """
37
+ A Python interface to the TUV-x photolysis calculator.
38
+
39
+ This class provides a simplified interface that only requires a JSON/YAML configuration
40
+ to set up and run TUV-x calculations. All parameters (solar zenith angle,
41
+ earth-sun distance, atmospheric profiles, etc.) are specified in the JSON/YAML configuration.
42
+
43
+ The configuration can be in a file or provided as a string. Exactly one of `config_path` or
44
+ `config_string` must be provided.
45
+ """
46
+
47
+ def __init__(self,
48
+ grid_map: GridMap,
49
+ profile_map: ProfileMap,
50
+ radiator_map: RadiatorMap,
51
+ config_path: Optional[str] = None,
52
+ config_string: Optional[str] = None):
53
+ """
54
+ Initialize a TUV-x instance from a configuration file.
55
+
56
+ Args:
57
+ grid_map: GridMap instance containing grid definitions (height, wavelength)
58
+ profile_map: ProfileMap instance containing atmospheric profiles (temperature, species concentrations, surface albedo, ET flux)
59
+ radiator_map: RadiatorMap instance containing radiator definitions (optically active species)
60
+ config_path: Path to the JSON configuration file
61
+ config_string: JSON configuration as a string
62
+
63
+ Raises:
64
+ FileNotFoundError: If the configuration file doesn't exist
65
+ ValueError: If TUV-x initialization fails or TUVX is not available
66
+ """
67
+ if not backend.tuvx_available():
68
+ raise ValueError("TUV-x backend is not available.")
69
+
70
+ if (config_path is None and config_string is None) or \
71
+ (config_path is not None and config_string is not None):
72
+ raise ValueError(
73
+ "Exactly one of config_path or config_string must be provided.")
74
+
75
+ if config_path is not None and not os.path.exists(config_path):
76
+ raise FileNotFoundError(
77
+ f"Configuration file not found: {config_path}")
78
+
79
+ if config_path is not None:
80
+ self._tuvx_instance = _backend._tuvx._create_tuvx_from_file(
81
+ config_path, grid_map, profile_map, radiator_map)
82
+ self._config_path = config_path
83
+ self._config_string = None
84
+ elif config_string is not None:
85
+ self._tuvx_instance = _backend._tuvx._create_tuvx_from_string(
86
+ config_string, grid_map, profile_map, radiator_map)
87
+ self._config_path = None
88
+ self._config_string = config_string
89
+
90
+ # Cache the names for efficiency
91
+ self._photolysis_names = None
92
+ self._heating_names = None
93
+ self._dose_names = None
94
+
95
+ def __del__(self):
96
+ """Clean up the TUV-x instance."""
97
+ if hasattr(self, '_tuvx_instance') and self._tuvx_instance is not None:
98
+ _backend._tuvx._delete_tuvx(self._tuvx_instance)
99
+
100
+ @property
101
+ def photolysis_rate_names(self) -> dict[str, int]:
102
+ """
103
+ Get the names of photolysis rates.
104
+
105
+ Returns:
106
+ Dictionary mapping photolysis rate names to their indices in the output arrays
107
+ """
108
+ if self._photolysis_names is None:
109
+ self._photolysis_names = _backend._tuvx._get_photolysis_rate_constants_ordering(
110
+ self._tuvx_instance)
111
+ return self._photolysis_names
112
+
113
+ @property
114
+ def heating_rate_names(self) -> dict[str, int]:
115
+ """
116
+ Get the names of heating rates.
117
+
118
+ Returns:
119
+ Dictionary mapping heating rate names to their indices in the output arrays
120
+ """
121
+ if self._heating_names is None:
122
+ self._heating_names = _backend._tuvx._get_heating_rates_ordering(
123
+ self._tuvx_instance)
124
+ return self._heating_names
125
+
126
+ @property
127
+ def dose_rate_names(self) -> dict[str, int]:
128
+ """
129
+ Get the names of dose rates.
130
+
131
+ Returns:
132
+ Dictionary mapping dose rate names to their indices in the output arrays
133
+ """
134
+ if self._dose_names is None:
135
+ self._dose_names = _backend._tuvx._get_dose_rates_ordering(
136
+ self._tuvx_instance)
137
+ return self._dose_names
138
+
139
+ def run(self, sza: float, earth_sun_distance: float) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
140
+ """
141
+ Run the TUV-x photolysis calculator.
142
+
143
+ All parameters (solar zenith angle, Earth-Sun distance, atmospheric profiles,
144
+ etc.) are read from the JSON configuration file.
145
+
146
+ Args:
147
+ sza: Solar zenith angle in radians
148
+ earth_sun_distance: Earth-Sun distance in astronomical units (AU)
149
+
150
+ Returns:
151
+ Tuple of (photolysis_rate_constants, heating_rates) as numpy arrays
152
+ - photolysis_rate_constants: Shape (n_layers, n_reactions) [s^-1]
153
+ - heating_rates: Shape (n_layers, n_heating_rates) [K s^-1]
154
+ - dose_rates: Shape (n_layers, n_dose_rates) [W m^-2]
155
+ """
156
+ photolysis_rates, heating_rates, dose_rates = _backend._tuvx._run_tuvx(
157
+ self._tuvx_instance, sza, earth_sun_distance)
158
+
159
+ return photolysis_rates, heating_rates, dose_rates
160
+
161
+ def get_photolysis_rate_constant(
162
+ self,
163
+ reaction_name: str,
164
+ photolysis_rates: np.ndarray
165
+ ) -> np.ndarray:
166
+ """
167
+ Extract photolysis rate constants for a specific reaction.
168
+
169
+ Args:
170
+ reaction_name: Name of the photolysis reaction
171
+ photolysis_rates: Output from run() method
172
+
173
+ Returns:
174
+ 1D array of photolysis rate constants for all layers [s^-1]
175
+
176
+ Raises:
177
+ KeyError: If reaction_name is not found
178
+ """
179
+ names = self.photolysis_rate_names
180
+ if reaction_name not in names:
181
+ raise KeyError(
182
+ f"Reaction '{reaction_name}' not found. "
183
+ f"Available reactions: {names}"
184
+ )
185
+
186
+ reaction_index = names[reaction_name]
187
+ return photolysis_rates[:, reaction_index]
188
+
189
+ def get_heating_rate(
190
+ self,
191
+ rate_name: str,
192
+ heating_rates: np.ndarray
193
+ ) -> np.ndarray:
194
+ """
195
+ Extract heating rates for a specific rate type.
196
+
197
+ Args:
198
+ rate_name: Name of the heating rate
199
+ heating_rates: Output from run() method
200
+
201
+ Returns:
202
+ 1D array of heating rates for all layers [K s^-1]
203
+
204
+ Raises:
205
+ KeyError: If rate_name is not found
206
+ """
207
+ names = self.heating_rate_names
208
+ if rate_name not in names:
209
+ raise KeyError(
210
+ f"Heating rate '{rate_name}' not found. "
211
+ f"Available rates: {names}"
212
+ )
213
+
214
+ rate_index = names[rate_name]
215
+ return heating_rates[:, rate_index]
216
+
217
+ def get_dose_rate(
218
+ self,
219
+ rate_name: str,
220
+ dose_rates: np.ndarray
221
+ ) -> np.ndarray:
222
+ """
223
+ Extract dose rates for a specific rate type.
224
+
225
+ Args:
226
+ rate_name: Name of the dose rate
227
+ dose_rates: Output from run() method
228
+
229
+ Returns:
230
+ 1D array of dose rates for all layers [W m^-2]
231
+
232
+ Raises:
233
+ KeyError: If rate_name is not found
234
+ """
235
+ names = self.dose_rate_names
236
+ if rate_name not in names:
237
+ raise KeyError(
238
+ f"Dose rate '{rate_name}' not found. "
239
+ f"Available rates: {names}"
240
+ )
241
+
242
+ rate_index = names[rate_name]
243
+ return dose_rates[:, rate_index]
244
+
245
+ @staticmethod
246
+ def create_config_from_dict(config_dict: Dict) -> 'TUVX':
247
+ """
248
+ Create a TUVX instance from a configuration dictionary.
249
+
250
+ Args:
251
+ config_dict: Configuration dictionary
252
+
253
+ Returns:
254
+ TUVX instance initialized with the configuration
255
+
256
+ Raises:
257
+ ValueError: If TUV-x backend is not available
258
+ FileNotFoundError: If required data files are not found
259
+ """
260
+ with tempfile.NamedTemporaryFile(
261
+ mode='w', suffix='.json', delete=True) as temp_file:
262
+ json.dump(config_dict, temp_file, indent=2)
263
+ temp_file.flush() # Ensure all data is written to disk
264
+ return TUVX(temp_file.name)
265
+
266
+ @staticmethod
267
+ def create_config_from_json_string(json_string: str) -> 'TUVX':
268
+ """
269
+ Create a TUVX instance from a JSON configuration string.
270
+
271
+ Args:
272
+ json_string: JSON configuration as string
273
+
274
+ Returns:
275
+ TUVX instance initialized with the configuration
276
+
277
+ Raises:
278
+ json.JSONDecodeError: If json_string is not valid JSON
279
+ ValueError: If TUV-x backend is not available
280
+ FileNotFoundError: If required data files are not found
281
+ """
282
+ config_dict = json.loads(json_string)
283
+ return TUVX.create_config_from_dict(config_dict)