musica 0.11.1.4__cp312-cp312-win_amd64.whl → 0.12.1__cp312-cp312-win_amd64.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 +28 -2
- musica/__init__.py +9 -49
- musica/_musica.cp312-win_amd64.pyd +0 -0
- musica/_version.py +1 -1
- musica/backend.py +41 -0
- musica/binding_common.cpp +23 -6
- musica/carma.cpp +911 -0
- musica/carma.py +1729 -0
- musica/constants.py +3 -0
- musica/cpu_binding.cpp +2 -1
- musica/cuda.py +4 -1
- musica/examples/__init__.py +1 -0
- musica/examples/carma_aluminum.py +123 -0
- musica/examples/carma_sulfate.py +245 -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 +2 -1
- 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 +184 -96
- musica/musica.cpp +48 -61
- musica/test/examples/v1/full_configuration/full_configuration.json +67 -35
- musica/test/examples/v1/full_configuration/full_configuration.yaml +44 -20
- musica/test/{test_analytical.py → integration/test_analytical.py} +1 -2
- musica/test/integration/test_carma.py +227 -0
- musica/test/integration/test_carma_aluminum.py +11 -0
- musica/test/integration/test_carma_sulfate.py +16 -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/{test_parser.py → unit/test_util_full_mechanism.py} +409 -404
- musica/tools/prepare_build_environment_linux.sh +39 -32
- musica/tools/prepare_build_environment_macos.sh +1 -0
- musica/tuvx.cpp +93 -0
- musica/tuvx.py +199 -0
- musica/types.py +104 -63
- {musica-0.11.1.4.dist-info → musica-0.12.1.dist-info}/METADATA +100 -84
- musica-0.12.1.dist-info/RECORD +69 -0
- {musica-0.11.1.4.dist-info → musica-0.12.1.dist-info}/WHEEL +1 -1
- musica-0.12.1.dist-info/entry_points.txt +3 -0
- musica-0.12.1.dist-info/licenses/AUTHORS.md +59 -0
- musica/mechanism_configuration.py +0 -1291
- musica/test/examples/v0/config.json +0 -7
- musica/test/examples/v0/config.yaml +0 -3
- musica/test/examples/v0/reactions.json +0 -193
- musica/test/examples/v0/reactions.yaml +0 -142
- musica/test/examples/v0/species.json +0 -40
- musica/test/examples/v0/species.yaml +0 -19
- musica/test/tuvx.py +0 -10
- musica/tools/prepare_build_environment_windows.sh +0 -22
- musica-0.11.1.4.dist-info/RECORD +0 -33
- /musica/test/{test_chapman.py → integration/test_chapman.py} +0 -0
- {musica-0.11.1.4.dist-info → musica-0.12.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,43 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
#!/bin/bash
|
|
3
2
|
set -e
|
|
4
3
|
set -x
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo
|
|
9
|
-
sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo
|
|
5
|
+
target_arch="$(uname -m)"
|
|
6
|
+
echo "Detected target_arch: $target_arch"
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
# Set package manager based on architecture
|
|
9
|
+
# x86_64 and aarch64 use manylinux_2_28 (AlmaLinux 8) with dnf
|
|
10
|
+
# i686 uses manylinux2014 (CentOS 7) with yum
|
|
11
|
+
if [ "$target_arch" = "i686" ]; then
|
|
12
|
+
PKG_MGR="yum"
|
|
13
|
+
|
|
14
|
+
# CentOS 7 is EOL, so we need to use vault.centos.org for i686 builds
|
|
15
|
+
# Replace the repo files to point to vault.centos.org
|
|
16
|
+
sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*.repo
|
|
17
|
+
sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*.repo
|
|
18
|
+
else
|
|
19
|
+
PKG_MGR="dnf"
|
|
20
|
+
fi
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
echo "Using package manager: $PKG_MGR"
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
target_arch="$(uname -m)"
|
|
24
|
+
$PKG_MGR -y update
|
|
25
|
+
|
|
26
|
+
if [ "$target_arch" = "x86_64" ] || [ "$target_arch" = "aarch64" ]; then
|
|
27
|
+
# For manylinux_2_28 (AlmaLinux 8), epel-release is required to get netcdf, for some reason
|
|
28
|
+
$PKG_MGR install -y epel-release
|
|
29
|
+
$PKG_MGR install -y netcdf-devel netcdf-fortran-devel
|
|
23
30
|
fi
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
$PKG_MGR install -y tree wget zip lapack-devel
|
|
26
33
|
|
|
27
34
|
if [ "$target_arch" = "x86_64" ]; then
|
|
28
|
-
# Install CUDA 12.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
# Install CUDA 12.8 for x86_64 on AlmaLinux 8 (manylinux_2_28) - supports GCC 14
|
|
36
|
+
dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo
|
|
37
|
+
dnf install --setopt=obsoletes=0 -y \
|
|
38
|
+
cuda-nvcc-12-8 \
|
|
39
|
+
cuda-cudart-devel-12-8 \
|
|
40
|
+
libcurand-devel-12-8 \
|
|
41
|
+
libcublas-devel-12-8
|
|
42
|
+
ln -sf cuda-12.8 /usr/local/cuda
|
|
43
|
+
|
|
44
|
+
# Verify CUDA installation
|
|
45
|
+
echo "=== CUDA Installation Verification ==="
|
|
46
|
+
/usr/local/cuda/bin/nvcc --version
|
|
47
|
+
|
|
41
48
|
# list the installed CUDA packages
|
|
42
|
-
tree -L 4 /usr/local/cuda
|
|
43
|
-
fi
|
|
49
|
+
# tree -L 4 /usr/local/cuda
|
|
50
|
+
fi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
brew install netcdf netcdf-fortran lapack
|
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)
|
musica/types.py
CHANGED
|
@@ -3,50 +3,36 @@
|
|
|
3
3
|
#
|
|
4
4
|
# This file is part of the musica Python package.
|
|
5
5
|
# For more information, see the LICENSE file in the top-level directory of this distribution.
|
|
6
|
-
from
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
from .constants import GAS_CONSTANT
|
|
8
|
+
from typing import Optional, Dict, List, Union, Tuple, TYPE_CHECKING, Any
|
|
7
9
|
from os import PathLike
|
|
8
10
|
import math
|
|
9
|
-
from musica import (
|
|
10
|
-
_Conditions,
|
|
11
|
-
_SolverType,
|
|
12
|
-
_Solver,
|
|
13
|
-
_State,
|
|
14
|
-
_create_solver,
|
|
15
|
-
_create_solver_from_mechanism,
|
|
16
|
-
_create_state,
|
|
17
|
-
_micm_solve,
|
|
18
|
-
_vector_size,
|
|
19
|
-
_species_ordering,
|
|
20
|
-
_user_defined_rate_parameters_ordering,
|
|
21
|
-
)
|
|
22
|
-
import musica.mechanism_configuration as mc
|
|
23
|
-
|
|
24
|
-
AVOGADRO = 6.02214076e23 # mol^-1
|
|
25
|
-
BOLTZMANN = 1.380649e-23 # J K^-1
|
|
26
|
-
GAS_CONSTANT = AVOGADRO * BOLTZMANN # J K^-1 mol^-1
|
|
27
11
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
34
|
|
|
35
|
-
|
|
36
|
-
----------
|
|
37
|
-
row_index : int
|
|
38
|
-
Row index of the vector.
|
|
39
|
-
column_index : int
|
|
40
|
-
Column index of the vector.
|
|
41
|
-
vector_size : int
|
|
42
|
-
Size of the vector.
|
|
43
|
-
|
|
44
|
-
Returns
|
|
45
|
-
-------
|
|
46
|
-
tuple[int, int]
|
|
47
|
-
Index for which state matrix to use and the index in that matrix'x underlying data vector.
|
|
48
|
-
"""
|
|
49
|
-
return (row_index // vector_size, column_index * (vector_size - 1) + row_index % vector_size)
|
|
35
|
+
FilePath = Union[str, "PathLike[str]"]
|
|
50
36
|
|
|
51
37
|
|
|
52
38
|
class Conditions(_Conditions):
|
|
@@ -92,20 +78,22 @@ class State():
|
|
|
92
78
|
State class for the MICM solver. It contains the initial conditions and species concentrations.
|
|
93
79
|
"""
|
|
94
80
|
|
|
95
|
-
def __init__(self, solver:
|
|
81
|
+
def __init__(self, solver: Any, number_of_grid_cells: int, vector_size: int = 0):
|
|
96
82
|
if number_of_grid_cells < 1:
|
|
97
83
|
raise ValueError("number_of_grid_cells must be greater than 0.")
|
|
98
84
|
super().__init__()
|
|
99
85
|
self.__states = [
|
|
100
|
-
_create_state(solver, min(
|
|
86
|
+
_create_state(solver, min(
|
|
87
|
+
vector_size, number_of_grid_cells - i * vector_size))
|
|
101
88
|
for i in range(math.ceil(number_of_grid_cells / vector_size))
|
|
102
89
|
] if vector_size > 0 else [_create_state(solver, number_of_grid_cells)]
|
|
103
90
|
self.__species_ordering = _species_ordering(self.__states[0])
|
|
104
|
-
self.__user_defined_rate_parameters_ordering = _user_defined_rate_parameters_ordering(
|
|
91
|
+
self.__user_defined_rate_parameters_ordering = _user_defined_rate_parameters_ordering(
|
|
92
|
+
self.__states[0])
|
|
105
93
|
self.__number_of_grid_cells = number_of_grid_cells
|
|
106
94
|
self.__vector_size = vector_size
|
|
107
95
|
|
|
108
|
-
def get_internal_states(self) -> List[
|
|
96
|
+
def get_internal_states(self) -> List[Any]:
|
|
109
97
|
"""
|
|
110
98
|
Get the internal states of the MICM solver.
|
|
111
99
|
|
|
@@ -134,13 +122,15 @@ class State():
|
|
|
134
122
|
if isinstance(value, float) or isinstance(value, int):
|
|
135
123
|
value = [value]
|
|
136
124
|
if len(value) != self.__number_of_grid_cells:
|
|
137
|
-
raise ValueError(
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"Concentration list for {name} must have length {self.__number_of_grid_cells}.")
|
|
138
127
|
# Counter 'k' is used to map grid cell indices across multiple state segments.
|
|
139
128
|
k = 0
|
|
140
129
|
for state in self.__states:
|
|
141
130
|
cell_stride, species_stride = state.concentration_strides()
|
|
142
131
|
for i_cell in range(state.number_of_grid_cells()):
|
|
143
|
-
state.concentrations[i_species *
|
|
132
|
+
state.concentrations[i_species *
|
|
133
|
+
species_stride + i_cell * cell_stride] = value[k]
|
|
144
134
|
k += 1
|
|
145
135
|
|
|
146
136
|
def set_user_defined_rate_parameters(
|
|
@@ -157,7 +147,8 @@ class State():
|
|
|
157
147
|
"""
|
|
158
148
|
for name, value in user_defined_rate_parameters.items():
|
|
159
149
|
if name not in self.__user_defined_rate_parameters_ordering:
|
|
160
|
-
raise ValueError(
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"User-defined rate parameter {name} not found in the mechanism.")
|
|
161
152
|
i_param = self.__user_defined_rate_parameters_ordering[name]
|
|
162
153
|
if isinstance(value, float) or isinstance(value, int):
|
|
163
154
|
value = [value]
|
|
@@ -169,10 +160,10 @@ class State():
|
|
|
169
160
|
for state in self.__states:
|
|
170
161
|
cell_stride, param_stride = state.user_defined_rate_parameter_strides()
|
|
171
162
|
for i_cell in range(state.number_of_grid_cells()):
|
|
172
|
-
state.user_defined_rate_parameters[i_param *
|
|
163
|
+
state.user_defined_rate_parameters[i_param *
|
|
164
|
+
param_stride + i_cell * cell_stride] = value[k]
|
|
173
165
|
k += 1
|
|
174
166
|
|
|
175
|
-
|
|
176
167
|
def set_conditions(self,
|
|
177
168
|
temperatures: Union[Union[float, int], List[Union[float, int]]],
|
|
178
169
|
pressures: Union[Union[float, int], List[Union[float, int]]],
|
|
@@ -194,22 +185,28 @@ class State():
|
|
|
194
185
|
"""
|
|
195
186
|
if isinstance(temperatures, float) or isinstance(temperatures, int):
|
|
196
187
|
if self.__number_of_grid_cells > 1:
|
|
197
|
-
raise ValueError(
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"temperatures must be a list of length {self.__number_of_grid_cells}.")
|
|
198
190
|
temperatures = [temperatures]
|
|
199
191
|
if isinstance(pressures, float) or isinstance(pressures, int):
|
|
200
192
|
if self.__number_of_grid_cells > 1:
|
|
201
|
-
raise ValueError(
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"pressures must be a list of length {self.__number_of_grid_cells}.")
|
|
202
195
|
pressures = [pressures]
|
|
203
196
|
if air_densities is not None and (isinstance(air_densities, float) or isinstance(air_densities, int)):
|
|
204
197
|
if self.__number_of_grid_cells > 1:
|
|
205
|
-
raise ValueError(
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"air_densities must be a list of length {self.__number_of_grid_cells}.")
|
|
206
200
|
air_densities = [air_densities]
|
|
207
201
|
if len(temperatures) != self.__number_of_grid_cells:
|
|
208
|
-
raise ValueError(
|
|
202
|
+
raise ValueError(
|
|
203
|
+
f"temperatures must be a list of length {self.__number_of_grid_cells}.")
|
|
209
204
|
if len(pressures) != self.__number_of_grid_cells:
|
|
210
|
-
raise ValueError(
|
|
205
|
+
raise ValueError(
|
|
206
|
+
f"pressures must be a list of length {self.__number_of_grid_cells}.")
|
|
211
207
|
if air_densities is not None and len(air_densities) != self.__number_of_grid_cells:
|
|
212
|
-
raise ValueError(
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"air_densities must be a list of length {self.__number_of_grid_cells}.")
|
|
213
210
|
k = 0
|
|
214
211
|
for state in self.__states:
|
|
215
212
|
for condition in state.conditions:
|
|
@@ -272,11 +269,36 @@ class State():
|
|
|
272
269
|
conditions["air_density"] = []
|
|
273
270
|
for state in self.__states:
|
|
274
271
|
for i_cell in range(state.number_of_grid_cells()):
|
|
275
|
-
conditions["temperature"].append(
|
|
276
|
-
|
|
277
|
-
conditions["
|
|
272
|
+
conditions["temperature"].append(
|
|
273
|
+
state.conditions[i_cell].temperature)
|
|
274
|
+
conditions["pressure"].append(
|
|
275
|
+
state.conditions[i_cell].pressure)
|
|
276
|
+
conditions["air_density"].append(
|
|
277
|
+
state.conditions[i_cell].air_density)
|
|
278
278
|
return conditions
|
|
279
279
|
|
|
280
|
+
def get_species_ordering(self) -> Dict[str, int]:
|
|
281
|
+
"""
|
|
282
|
+
Get the species ordering for the state.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
Dict[str, int]
|
|
287
|
+
Dictionary of species names and their indices.
|
|
288
|
+
"""
|
|
289
|
+
return self.__species_ordering
|
|
290
|
+
|
|
291
|
+
def get_user_defined_rate_parameters_ordering(self) -> Dict[str, int]:
|
|
292
|
+
"""
|
|
293
|
+
Get the user-defined rate parameters ordering for the state.
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
Dict[str, int]
|
|
298
|
+
Dictionary of user-defined rate parameter names and their indices.
|
|
299
|
+
"""
|
|
300
|
+
return self.__user_defined_rate_parameters_ordering
|
|
301
|
+
|
|
280
302
|
|
|
281
303
|
class MICM():
|
|
282
304
|
"""
|
|
@@ -298,19 +320,38 @@ class MICM():
|
|
|
298
320
|
def __init__(
|
|
299
321
|
self,
|
|
300
322
|
config_path: FilePath = None,
|
|
301
|
-
mechanism:
|
|
302
|
-
solver_type:
|
|
323
|
+
mechanism: Mechanism = None,
|
|
324
|
+
solver_type: Any = None,
|
|
325
|
+
ignore_non_gas_phases: bool = True,
|
|
303
326
|
):
|
|
327
|
+
""" Initialize the MICM solver.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
config_path : FilePath, optional
|
|
332
|
+
Path to the configuration file. If provided, this will be used to create the solver.
|
|
333
|
+
mechanism : Mechanism, optional
|
|
334
|
+
Mechanism object which specifies the chemical mechanism to use. If provided, this will be used
|
|
335
|
+
to create the solver.
|
|
336
|
+
solver_type : SolverType, optional
|
|
337
|
+
Type of solver to use. If not provided, the default solver type will be used.
|
|
338
|
+
ignore_non_gas_phases : bool, optional
|
|
339
|
+
If True, non-gas phases will be ignored when configuring micm with the mechanism. This is only needed
|
|
340
|
+
until micm properly supports non-gas phases. This option is only supported when passing in a mechanism.
|
|
341
|
+
"""
|
|
304
342
|
self.__solver_type = solver_type
|
|
305
343
|
self.__vector_size = _vector_size(solver_type)
|
|
306
344
|
if config_path is None and mechanism is None:
|
|
307
|
-
raise ValueError(
|
|
345
|
+
raise ValueError(
|
|
346
|
+
"Either config_path or mechanism must be provided.")
|
|
308
347
|
if config_path is not None and mechanism is not None:
|
|
309
|
-
raise ValueError(
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"Only one of config_path or mechanism must be provided.")
|
|
310
350
|
if config_path is not None:
|
|
311
351
|
self.__solver = _create_solver(config_path, solver_type)
|
|
312
352
|
elif mechanism is not None:
|
|
313
|
-
self.__solver = _create_solver_from_mechanism(
|
|
353
|
+
self.__solver = _create_solver_from_mechanism(
|
|
354
|
+
mechanism, solver_type, ignore_non_gas_phases)
|
|
314
355
|
|
|
315
356
|
def solver_type(self) -> SolverType:
|
|
316
357
|
"""
|