musica 0.12.2__cp313-cp313-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.
- musica/CMakeLists.txt +68 -0
- musica/__init__.py +11 -0
- musica/_musica.cpython-313-i386-linux-gnu.so +0 -0
- musica/_version.py +1 -0
- musica/backend.py +41 -0
- musica/binding_common.cpp +33 -0
- musica/binding_common.hpp +7 -0
- musica/carma.cpp +911 -0
- musica/carma.py +1729 -0
- musica/constants.py +3 -0
- musica/cpu_binding.cpp +11 -0
- musica/cuda.cpp +12 -0
- musica/cuda.py +13 -0
- musica/examples/__init__.py +1 -0
- musica/examples/carma_aluminum.py +124 -0
- musica/examples/carma_sulfate.py +246 -0
- musica/examples/examples.py +165 -0
- musica/examples/sulfate_box_model.py +439 -0
- musica/examples/ts1_latin_hypercube.py +245 -0
- musica/gpu_binding.cpp +11 -0
- musica/main.py +89 -0
- musica/mechanism_configuration/__init__.py +1 -0
- musica/mechanism_configuration/aqueous_equilibrium.py +274 -0
- musica/mechanism_configuration/arrhenius.py +307 -0
- musica/mechanism_configuration/branched.py +299 -0
- musica/mechanism_configuration/condensed_phase_arrhenius.py +309 -0
- musica/mechanism_configuration/condensed_phase_photolysis.py +88 -0
- musica/mechanism_configuration/emission.py +71 -0
- musica/mechanism_configuration/first_order_loss.py +174 -0
- musica/mechanism_configuration/henrys_law.py +44 -0
- musica/mechanism_configuration/mechanism_configuration.py +234 -0
- musica/mechanism_configuration/phase.py +47 -0
- musica/mechanism_configuration/photolysis.py +88 -0
- musica/mechanism_configuration/reactions.py +73 -0
- musica/mechanism_configuration/simpol_phase_transfer.py +217 -0
- musica/mechanism_configuration/species.py +91 -0
- musica/mechanism_configuration/surface.py +94 -0
- musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
- musica/mechanism_configuration/troe.py +352 -0
- musica/mechanism_configuration/tunneling.py +250 -0
- musica/mechanism_configuration/user_defined.py +88 -0
- musica/mechanism_configuration/utils.py +10 -0
- musica/mechanism_configuration/wet_deposition.py +52 -0
- musica/mechanism_configuration.cpp +607 -0
- musica/musica.cpp +201 -0
- musica/test/examples/v1/full_configuration/full_configuration.json +466 -0
- musica/test/examples/v1/full_configuration/full_configuration.yaml +295 -0
- musica/test/integration/test_analytical.py +324 -0
- musica/test/integration/test_carma.py +227 -0
- musica/test/integration/test_carma_aluminum.py +12 -0
- musica/test/integration/test_carma_sulfate.py +17 -0
- musica/test/integration/test_chapman.py +139 -0
- musica/test/integration/test_sulfate_box_model.py +34 -0
- musica/test/integration/test_tuvx.py +62 -0
- musica/test/unit/test_parser.py +64 -0
- musica/test/unit/test_serializer.py +69 -0
- musica/test/unit/test_state.py +325 -0
- musica/test/unit/test_util_full_mechanism.py +698 -0
- musica/tools/prepare_build_environment_linux.sh +32 -0
- musica/tools/prepare_build_environment_macos.sh +1 -0
- musica/tools/repair_wheel_gpu.sh +40 -0
- musica/tuvx.cpp +93 -0
- musica/tuvx.py +199 -0
- musica/types.py +407 -0
- musica-0.12.2.dist-info/METADATA +473 -0
- musica-0.12.2.dist-info/RECORD +70 -0
- musica-0.12.2.dist-info/WHEEL +6 -0
- musica-0.12.2.dist-info/entry_points.txt +3 -0
- musica-0.12.2.dist-info/licenses/AUTHORS.md +59 -0
- musica-0.12.2.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
set -x
|
|
4
|
+
|
|
5
|
+
target_arch="$(uname -m)"
|
|
6
|
+
echo "Detected target_arch: $target_arch"
|
|
7
|
+
|
|
8
|
+
dnf -y update
|
|
9
|
+
|
|
10
|
+
# For 64 bit systems can enable our fortran components, but we require netcdf
|
|
11
|
+
if [[ "$target_arch" == "x86_64" || "$target_arch" == "aarch64" ]]; then
|
|
12
|
+
dnf install -y epel-release
|
|
13
|
+
dnf install -y netcdf-devel netcdf-fortran-devel
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
dnf install -y tree wget zip lapack-devel
|
|
17
|
+
|
|
18
|
+
# 64 bit intel and amd systems support building cuda
|
|
19
|
+
if [ "$target_arch" = "x86_64" ]; then
|
|
20
|
+
# Install CUDA 12.8 for x86_64 on AlmaLinux 8 (manylinux_2_28) - supports GCC 14
|
|
21
|
+
dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo
|
|
22
|
+
dnf install --setopt=obsoletes=0 -y \
|
|
23
|
+
cuda-nvcc-12-8 \
|
|
24
|
+
cuda-cudart-devel-12-8 \
|
|
25
|
+
libcurand-devel-12-8 \
|
|
26
|
+
libcublas-devel-12-8
|
|
27
|
+
ln -sf cuda-12.8 /usr/local/cuda
|
|
28
|
+
|
|
29
|
+
# Verify CUDA installation
|
|
30
|
+
echo "=== CUDA Installation Verification ==="
|
|
31
|
+
/usr/local/cuda/bin/nvcc --version
|
|
32
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brew install netcdf netcdf-fortran lapack
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
auditwheel repair --exclude libcublas --exclude libcublasLt --exclude libcudart -w "$2" "$1"
|
|
5
|
+
|
|
6
|
+
for whl in "$2"/*.whl; do
|
|
7
|
+
tmpdir=$(mktemp -d)
|
|
8
|
+
unzip -q "$whl" -d "$tmpdir"
|
|
9
|
+
tree "$tmpdir"
|
|
10
|
+
|
|
11
|
+
so_files=("$tmpdir"/musica/_musica_gpu*.so)
|
|
12
|
+
|
|
13
|
+
if [[ -f "${so_files[0]}" ]]; then
|
|
14
|
+
so_path="${so_files[0]}"
|
|
15
|
+
ls $so_path
|
|
16
|
+
echo "Before patchelf:"
|
|
17
|
+
readelf -d $so_path
|
|
18
|
+
# Use patchelf to fix the rpath and library dependencies
|
|
19
|
+
patchelf --remove-rpath $so_path
|
|
20
|
+
patchelf --set-rpath "\$ORIGIN:\$ORIGIN/../nvidia/cublas/lib:\$ORIGIN/../nvidia/cuda_runtime/lib" --force-rpath $so_path
|
|
21
|
+
# these may need to be periodically updated
|
|
22
|
+
patchelf --replace-needed libcudart-b5a066d7.so.12.2.140 libcudart.so.12 $so_path
|
|
23
|
+
patchelf --replace-needed libcublas-e779a79d.so.12.2.5.6 libcublas.so.12 $so_path
|
|
24
|
+
patchelf --replace-needed libcublasLt-fbfbc8a1.so.12.2.5.6 libcublasLt.so.12 $so_path
|
|
25
|
+
# Remove bundled CUDA libraries
|
|
26
|
+
rm -f "$tmpdir"/musica.libs/libcudart-*.so*
|
|
27
|
+
rm -f "$tmpdir"/musica.libs/libcublas-*.so*
|
|
28
|
+
rm -f "$tmpdir"/musica.libs/libcublasLt-*.so*
|
|
29
|
+
echo "After patchelf:"
|
|
30
|
+
readelf -d $so_path
|
|
31
|
+
else
|
|
32
|
+
echo "No GPU .so file found, skipping patchelf steps"
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# Repack the wheel with correct structure
|
|
36
|
+
(cd "$tmpdir" && zip -qr "${whl%.whl}.patched.whl" .)
|
|
37
|
+
rm -rf "$tmpdir"
|
|
38
|
+
# Replace the original wheel with the patched one
|
|
39
|
+
mv "${whl%.whl}.patched.whl" "$whl"
|
|
40
|
+
done
|
musica/tuvx.cpp
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#include "binding_common.hpp"
|
|
2
|
+
|
|
3
|
+
#include <musica/tuvx/tuvx.hpp>
|
|
4
|
+
|
|
5
|
+
#include <pybind11/numpy.h>
|
|
6
|
+
#include <pybind11/pybind11.h>
|
|
7
|
+
#include <pybind11/stl.h>
|
|
8
|
+
|
|
9
|
+
namespace py = pybind11;
|
|
10
|
+
|
|
11
|
+
void bind_tuvx(py::module_& tuvx)
|
|
12
|
+
{
|
|
13
|
+
tuvx.def("_get_tuvx_version", []() { return musica::TUVX::GetVersion(); }, "Get the version of the TUV-x instance");
|
|
14
|
+
|
|
15
|
+
tuvx.def(
|
|
16
|
+
"_create_tuvx",
|
|
17
|
+
[](const char* config_path)
|
|
18
|
+
{
|
|
19
|
+
try
|
|
20
|
+
{
|
|
21
|
+
auto tuvx_instance = new musica::TUVX();
|
|
22
|
+
tuvx_instance->CreateFromConfigOnly(config_path);
|
|
23
|
+
return reinterpret_cast<std::uintptr_t>(tuvx_instance);
|
|
24
|
+
}
|
|
25
|
+
catch (const std::exception& e)
|
|
26
|
+
{
|
|
27
|
+
throw py::value_error(
|
|
28
|
+
"Error creating TUV-x instance from config file: " + std::string(config_path) + " - " + e.what());
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"Create a TUV-x instance from a JSON configuration file");
|
|
32
|
+
|
|
33
|
+
tuvx.def(
|
|
34
|
+
"_delete_tuvx",
|
|
35
|
+
[](std::uintptr_t tuvx_ptr)
|
|
36
|
+
{
|
|
37
|
+
musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
|
|
38
|
+
delete tuvx_instance;
|
|
39
|
+
},
|
|
40
|
+
"Delete a TUV-x instance");
|
|
41
|
+
|
|
42
|
+
tuvx.def(
|
|
43
|
+
"_run_tuvx",
|
|
44
|
+
[](std::uintptr_t tuvx_ptr)
|
|
45
|
+
{
|
|
46
|
+
musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
|
|
47
|
+
|
|
48
|
+
// Get dimensions
|
|
49
|
+
int n_photolysis = tuvx_instance->GetPhotolysisRateCount();
|
|
50
|
+
|
|
51
|
+
int n_heating = tuvx_instance->GetHeatingRateCount();
|
|
52
|
+
|
|
53
|
+
int n_layers = tuvx_instance->GetNumberOfLayers();
|
|
54
|
+
|
|
55
|
+
int n_sza_steps = tuvx_instance->GetNumberOfSzaSteps();
|
|
56
|
+
|
|
57
|
+
// Allocate output arrays (3D: sza_step, layer, reaction/heating_type)
|
|
58
|
+
std::vector<double> photolysis_rates(n_sza_steps * n_layers * n_photolysis);
|
|
59
|
+
std::vector<double> heating_rates(n_sza_steps * n_layers * n_heating);
|
|
60
|
+
|
|
61
|
+
// Run TUV-x (everything comes from the JSON config)
|
|
62
|
+
tuvx_instance->RunFromConfig(photolysis_rates.data(), heating_rates.data());
|
|
63
|
+
|
|
64
|
+
// Return as numpy arrays with shape (n_sza_steps, n_layers, n_reactions/n_heating)
|
|
65
|
+
py::array_t<double> py_photolysis =
|
|
66
|
+
py::array_t<double>({ n_sza_steps, n_layers, n_photolysis }, photolysis_rates.data());
|
|
67
|
+
py::array_t<double> py_heating = py::array_t<double>({ n_sza_steps, n_layers, n_heating }, heating_rates.data());
|
|
68
|
+
|
|
69
|
+
return py::make_tuple(py_photolysis, py_heating);
|
|
70
|
+
},
|
|
71
|
+
"Run TUV-x (all parameters come from JSON config)",
|
|
72
|
+
py::arg("tuvx_instance"));
|
|
73
|
+
|
|
74
|
+
tuvx.def(
|
|
75
|
+
"_get_photolysis_rate_names",
|
|
76
|
+
[](std::uintptr_t tuvx_ptr)
|
|
77
|
+
{
|
|
78
|
+
musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
|
|
79
|
+
|
|
80
|
+
return tuvx_instance->GetPhotolysisRateNames();
|
|
81
|
+
},
|
|
82
|
+
"Get photolysis rate names");
|
|
83
|
+
|
|
84
|
+
tuvx.def(
|
|
85
|
+
"_get_heating_rate_names",
|
|
86
|
+
[](std::uintptr_t tuvx_ptr)
|
|
87
|
+
{
|
|
88
|
+
musica::TUVX* tuvx_instance = reinterpret_cast<musica::TUVX*>(tuvx_ptr);
|
|
89
|
+
|
|
90
|
+
return tuvx_instance->GetHeatingRateNames();
|
|
91
|
+
},
|
|
92
|
+
"Get heating rate names");
|
|
93
|
+
}
|
musica/tuvx.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
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, Tuple, List
|
|
15
|
+
import numpy as np
|
|
16
|
+
from . import backend
|
|
17
|
+
|
|
18
|
+
_backend = backend.get_backend()
|
|
19
|
+
|
|
20
|
+
version = _backend._tuvx._get_tuvx_version() if backend.tuvx_available() else None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TUVX:
|
|
24
|
+
"""
|
|
25
|
+
A Python interface to the TUV-x photolysis calculator.
|
|
26
|
+
|
|
27
|
+
This class provides a simplified interface that only requires a JSON configuration
|
|
28
|
+
file to set up and run TUV-x calculations. All parameters (solar zenith angle,
|
|
29
|
+
earth-sun distance, atmospheric profiles, etc.) are specified in the JSON config.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, config_path: str):
|
|
33
|
+
"""
|
|
34
|
+
Initialize a TUV-x instance from a configuration file.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
config_path: Path to the JSON configuration file
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
FileNotFoundError: If the configuration file doesn't exist
|
|
41
|
+
ValueError: If TUV-x initialization fails or TUVX is not available
|
|
42
|
+
"""
|
|
43
|
+
if not backend.tuvx_available():
|
|
44
|
+
raise ValueError("TUV-x backend is not available on windows.")
|
|
45
|
+
|
|
46
|
+
if not os.path.exists(config_path):
|
|
47
|
+
raise FileNotFoundError(
|
|
48
|
+
f"Configuration file not found: {config_path}")
|
|
49
|
+
|
|
50
|
+
self._tuvx_instance = _backend._tuvx._create_tuvx(config_path)
|
|
51
|
+
self._config_path = config_path
|
|
52
|
+
|
|
53
|
+
# Cache the names for efficiency
|
|
54
|
+
self._photolysis_names = None
|
|
55
|
+
self._heating_names = None
|
|
56
|
+
|
|
57
|
+
def __del__(self):
|
|
58
|
+
"""Clean up the TUV-x instance."""
|
|
59
|
+
if hasattr(self, '_tuvx_instance') and self._tuvx_instance is not None:
|
|
60
|
+
_backend._tuvx._delete_tuvx(self._tuvx_instance)
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def photolysis_rate_names(self) -> List[str]:
|
|
64
|
+
"""
|
|
65
|
+
Get the names of photolysis rates.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of photolysis rate names
|
|
69
|
+
"""
|
|
70
|
+
if self._photolysis_names is None:
|
|
71
|
+
self._photolysis_names = _backend._tuvx._get_photolysis_rate_names(
|
|
72
|
+
self._tuvx_instance)
|
|
73
|
+
return self._photolysis_names
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def heating_rate_names(self) -> List[str]:
|
|
77
|
+
"""
|
|
78
|
+
Get the names of heating rates.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
List of heating rate names
|
|
82
|
+
"""
|
|
83
|
+
if self._heating_names is None:
|
|
84
|
+
self._heating_names = _backend._tuvx._get_heating_rate_names(
|
|
85
|
+
self._tuvx_instance)
|
|
86
|
+
return self._heating_names
|
|
87
|
+
|
|
88
|
+
def run(self) -> Tuple[np.ndarray, np.ndarray]:
|
|
89
|
+
"""
|
|
90
|
+
Run the TUV-x photolysis calculator.
|
|
91
|
+
|
|
92
|
+
All parameters (solar zenith angle, Earth-Sun distance, atmospheric profiles,
|
|
93
|
+
etc.) are read from the JSON configuration file.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
Tuple of (photolysis_rate_constants, heating_rates) as numpy arrays
|
|
97
|
+
- photolysis_rate_constants: Shape (n_layers, n_reactions) [s^-1]
|
|
98
|
+
- heating_rates: Shape (n_layers, n_heating_rates) [K s^-1]
|
|
99
|
+
"""
|
|
100
|
+
photolysis_rates, heating_rates = _backend._tuvx._run_tuvx(
|
|
101
|
+
self._tuvx_instance)
|
|
102
|
+
|
|
103
|
+
return photolysis_rates, heating_rates
|
|
104
|
+
|
|
105
|
+
def get_photolysis_rate_constant(
|
|
106
|
+
self,
|
|
107
|
+
reaction_name: str,
|
|
108
|
+
photolysis_rates: np.ndarray
|
|
109
|
+
) -> np.ndarray:
|
|
110
|
+
"""
|
|
111
|
+
Extract photolysis rate constants for a specific reaction.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
reaction_name: Name of the photolysis reaction
|
|
115
|
+
photolysis_rates: Output from run() method
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
1D array of photolysis rate constants for all layers [s^-1]
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
KeyError: If reaction_name is not found
|
|
122
|
+
"""
|
|
123
|
+
names = self.photolysis_rate_names
|
|
124
|
+
if reaction_name not in names:
|
|
125
|
+
raise KeyError(
|
|
126
|
+
f"Reaction '{reaction_name}' not found. "
|
|
127
|
+
f"Available reactions: {names}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
reaction_index = names.index(reaction_name)
|
|
131
|
+
return photolysis_rates[:, reaction_index]
|
|
132
|
+
|
|
133
|
+
def get_heating_rate(
|
|
134
|
+
self,
|
|
135
|
+
rate_name: str,
|
|
136
|
+
heating_rates: np.ndarray
|
|
137
|
+
) -> np.ndarray:
|
|
138
|
+
"""
|
|
139
|
+
Extract heating rates for a specific rate type.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
rate_name: Name of the heating rate
|
|
143
|
+
heating_rates: Output from run() method
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
1D array of heating rates for all layers [K s^-1]
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
KeyError: If rate_name is not found
|
|
150
|
+
"""
|
|
151
|
+
names = self.heating_rate_names
|
|
152
|
+
if rate_name not in names:
|
|
153
|
+
raise KeyError(
|
|
154
|
+
f"Heating rate '{rate_name}' not found. "
|
|
155
|
+
f"Available rates: {names}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
rate_index = names.index(rate_name)
|
|
159
|
+
return heating_rates[:, rate_index]
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def create_config_from_dict(config_dict: Dict) -> 'TUVX':
|
|
163
|
+
"""
|
|
164
|
+
Create a TUVX instance from a configuration dictionary.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
config_dict: Configuration dictionary
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
TUVX instance initialized with the configuration
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: If TUV-x backend is not available
|
|
174
|
+
FileNotFoundError: If required data files are not found
|
|
175
|
+
"""
|
|
176
|
+
with tempfile.NamedTemporaryFile(
|
|
177
|
+
mode='w', suffix='.json', delete=True) as temp_file:
|
|
178
|
+
json.dump(config_dict, temp_file, indent=2)
|
|
179
|
+
temp_file.flush() # Ensure all data is written to disk
|
|
180
|
+
return TUVX(temp_file.name)
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def create_config_from_json_string(json_string: str) -> 'TUVX':
|
|
184
|
+
"""
|
|
185
|
+
Create a TUVX instance from a JSON configuration string.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
json_string: JSON configuration as string
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
TUVX instance initialized with the configuration
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
json.JSONDecodeError: If json_string is not valid JSON
|
|
195
|
+
ValueError: If TUV-x backend is not available
|
|
196
|
+
FileNotFoundError: If required data files are not found
|
|
197
|
+
"""
|
|
198
|
+
config_dict = json.loads(json_string)
|
|
199
|
+
return TUVX.create_config_from_dict(config_dict)
|