musica 0.12.0__cp310-cp310-win_amd64.whl → 0.12.1__cp310-cp310-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.cp310-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 +1 -1
- 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 -1
- musica/mechanism_configuration/aqueous_equilibrium.py +227 -54
- musica/mechanism_configuration/arrhenius.py +228 -42
- musica/mechanism_configuration/branched.py +249 -66
- musica/mechanism_configuration/condensed_phase_arrhenius.py +243 -50
- musica/mechanism_configuration/condensed_phase_photolysis.py +16 -19
- musica/mechanism_configuration/emission.py +10 -6
- musica/mechanism_configuration/first_order_loss.py +133 -26
- musica/mechanism_configuration/henrys_law.py +7 -48
- musica/mechanism_configuration/mechanism_configuration.py +114 -41
- musica/mechanism_configuration/phase.py +6 -2
- musica/mechanism_configuration/photolysis.py +12 -7
- musica/mechanism_configuration/reactions.py +20 -8
- musica/mechanism_configuration/simpol_phase_transfer.py +180 -51
- musica/mechanism_configuration/species.py +23 -4
- musica/mechanism_configuration/surface.py +14 -9
- musica/mechanism_configuration/ternary_chemical_activation.py +352 -0
- musica/mechanism_configuration/troe.py +259 -44
- musica/mechanism_configuration/tunneling.py +196 -49
- musica/mechanism_configuration/user_defined.py +9 -4
- musica/mechanism_configuration/wet_deposition.py +11 -8
- musica/mechanism_configuration.cpp +184 -95
- musica/musica.cpp +48 -61
- musica/test/examples/v1/full_configuration/full_configuration.json +39 -22
- musica/test/examples/v1/full_configuration/full_configuration.yaml +29 -20
- musica/test/{test_analytical.py → integration/test_analytical.py} +0 -1
- 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/{test_serializer.py → unit/test_serializer.py} +2 -2
- musica/test/{test_util_full_mechanism.py → unit/test_util_full_mechanism.py} +152 -122
- 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 -60
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/METADATA +40 -39
- musica-0.12.1.dist-info/RECORD +69 -0
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/WHEEL +1 -1
- musica-0.12.1.dist-info/entry_points.txt +3 -0
- 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/test_parser.py +0 -57
- musica/test/tuvx.py +0 -10
- musica/tools/prepare_build_environment_windows.sh +0 -22
- musica-0.12.0.dist-info/RECORD +0 -57
- /musica/test/{test_chapman.py → integration/test_chapman.py} +0 -0
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/licenses/AUTHORS.md +0 -0
- {musica-0.12.0.dist-info → musica-0.12.1.dist-info}/licenses/LICENSE +0 -0
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,47 +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
|
-
from musica.constants import GAS_CONSTANT
|
|
24
11
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
----------
|
|
34
|
-
row_index : int
|
|
35
|
-
Row index of the vector.
|
|
36
|
-
column_index : int
|
|
37
|
-
Column index of the vector.
|
|
38
|
-
vector_size : int
|
|
39
|
-
Size of the vector.
|
|
40
|
-
|
|
41
|
-
Returns
|
|
42
|
-
-------
|
|
43
|
-
tuple[int, int]
|
|
44
|
-
Index for which state matrix to use and the index in that matrix'x underlying data vector.
|
|
45
|
-
"""
|
|
46
|
-
return (row_index // vector_size, column_index * (vector_size - 1) + row_index % vector_size)
|
|
35
|
+
FilePath = Union[str, "PathLike[str]"]
|
|
47
36
|
|
|
48
37
|
|
|
49
38
|
class Conditions(_Conditions):
|
|
@@ -89,20 +78,22 @@ class State():
|
|
|
89
78
|
State class for the MICM solver. It contains the initial conditions and species concentrations.
|
|
90
79
|
"""
|
|
91
80
|
|
|
92
|
-
def __init__(self, solver:
|
|
81
|
+
def __init__(self, solver: Any, number_of_grid_cells: int, vector_size: int = 0):
|
|
93
82
|
if number_of_grid_cells < 1:
|
|
94
83
|
raise ValueError("number_of_grid_cells must be greater than 0.")
|
|
95
84
|
super().__init__()
|
|
96
85
|
self.__states = [
|
|
97
|
-
_create_state(solver, min(
|
|
86
|
+
_create_state(solver, min(
|
|
87
|
+
vector_size, number_of_grid_cells - i * vector_size))
|
|
98
88
|
for i in range(math.ceil(number_of_grid_cells / vector_size))
|
|
99
89
|
] if vector_size > 0 else [_create_state(solver, number_of_grid_cells)]
|
|
100
90
|
self.__species_ordering = _species_ordering(self.__states[0])
|
|
101
|
-
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])
|
|
102
93
|
self.__number_of_grid_cells = number_of_grid_cells
|
|
103
94
|
self.__vector_size = vector_size
|
|
104
95
|
|
|
105
|
-
def get_internal_states(self) -> List[
|
|
96
|
+
def get_internal_states(self) -> List[Any]:
|
|
106
97
|
"""
|
|
107
98
|
Get the internal states of the MICM solver.
|
|
108
99
|
|
|
@@ -131,13 +122,15 @@ class State():
|
|
|
131
122
|
if isinstance(value, float) or isinstance(value, int):
|
|
132
123
|
value = [value]
|
|
133
124
|
if len(value) != self.__number_of_grid_cells:
|
|
134
|
-
raise ValueError(
|
|
125
|
+
raise ValueError(
|
|
126
|
+
f"Concentration list for {name} must have length {self.__number_of_grid_cells}.")
|
|
135
127
|
# Counter 'k' is used to map grid cell indices across multiple state segments.
|
|
136
128
|
k = 0
|
|
137
129
|
for state in self.__states:
|
|
138
130
|
cell_stride, species_stride = state.concentration_strides()
|
|
139
131
|
for i_cell in range(state.number_of_grid_cells()):
|
|
140
|
-
state.concentrations[i_species *
|
|
132
|
+
state.concentrations[i_species *
|
|
133
|
+
species_stride + i_cell * cell_stride] = value[k]
|
|
141
134
|
k += 1
|
|
142
135
|
|
|
143
136
|
def set_user_defined_rate_parameters(
|
|
@@ -154,7 +147,8 @@ class State():
|
|
|
154
147
|
"""
|
|
155
148
|
for name, value in user_defined_rate_parameters.items():
|
|
156
149
|
if name not in self.__user_defined_rate_parameters_ordering:
|
|
157
|
-
raise ValueError(
|
|
150
|
+
raise ValueError(
|
|
151
|
+
f"User-defined rate parameter {name} not found in the mechanism.")
|
|
158
152
|
i_param = self.__user_defined_rate_parameters_ordering[name]
|
|
159
153
|
if isinstance(value, float) or isinstance(value, int):
|
|
160
154
|
value = [value]
|
|
@@ -166,10 +160,10 @@ class State():
|
|
|
166
160
|
for state in self.__states:
|
|
167
161
|
cell_stride, param_stride = state.user_defined_rate_parameter_strides()
|
|
168
162
|
for i_cell in range(state.number_of_grid_cells()):
|
|
169
|
-
state.user_defined_rate_parameters[i_param *
|
|
163
|
+
state.user_defined_rate_parameters[i_param *
|
|
164
|
+
param_stride + i_cell * cell_stride] = value[k]
|
|
170
165
|
k += 1
|
|
171
166
|
|
|
172
|
-
|
|
173
167
|
def set_conditions(self,
|
|
174
168
|
temperatures: Union[Union[float, int], List[Union[float, int]]],
|
|
175
169
|
pressures: Union[Union[float, int], List[Union[float, int]]],
|
|
@@ -191,22 +185,28 @@ class State():
|
|
|
191
185
|
"""
|
|
192
186
|
if isinstance(temperatures, float) or isinstance(temperatures, int):
|
|
193
187
|
if self.__number_of_grid_cells > 1:
|
|
194
|
-
raise ValueError(
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"temperatures must be a list of length {self.__number_of_grid_cells}.")
|
|
195
190
|
temperatures = [temperatures]
|
|
196
191
|
if isinstance(pressures, float) or isinstance(pressures, int):
|
|
197
192
|
if self.__number_of_grid_cells > 1:
|
|
198
|
-
raise ValueError(
|
|
193
|
+
raise ValueError(
|
|
194
|
+
f"pressures must be a list of length {self.__number_of_grid_cells}.")
|
|
199
195
|
pressures = [pressures]
|
|
200
196
|
if air_densities is not None and (isinstance(air_densities, float) or isinstance(air_densities, int)):
|
|
201
197
|
if self.__number_of_grid_cells > 1:
|
|
202
|
-
raise ValueError(
|
|
198
|
+
raise ValueError(
|
|
199
|
+
f"air_densities must be a list of length {self.__number_of_grid_cells}.")
|
|
203
200
|
air_densities = [air_densities]
|
|
204
201
|
if len(temperatures) != self.__number_of_grid_cells:
|
|
205
|
-
raise ValueError(
|
|
202
|
+
raise ValueError(
|
|
203
|
+
f"temperatures must be a list of length {self.__number_of_grid_cells}.")
|
|
206
204
|
if len(pressures) != self.__number_of_grid_cells:
|
|
207
|
-
raise ValueError(
|
|
205
|
+
raise ValueError(
|
|
206
|
+
f"pressures must be a list of length {self.__number_of_grid_cells}.")
|
|
208
207
|
if air_densities is not None and len(air_densities) != self.__number_of_grid_cells:
|
|
209
|
-
raise ValueError(
|
|
208
|
+
raise ValueError(
|
|
209
|
+
f"air_densities must be a list of length {self.__number_of_grid_cells}.")
|
|
210
210
|
k = 0
|
|
211
211
|
for state in self.__states:
|
|
212
212
|
for condition in state.conditions:
|
|
@@ -269,11 +269,36 @@ class State():
|
|
|
269
269
|
conditions["air_density"] = []
|
|
270
270
|
for state in self.__states:
|
|
271
271
|
for i_cell in range(state.number_of_grid_cells()):
|
|
272
|
-
conditions["temperature"].append(
|
|
273
|
-
|
|
274
|
-
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)
|
|
275
278
|
return conditions
|
|
276
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
|
+
|
|
277
302
|
|
|
278
303
|
class MICM():
|
|
279
304
|
"""
|
|
@@ -295,19 +320,38 @@ class MICM():
|
|
|
295
320
|
def __init__(
|
|
296
321
|
self,
|
|
297
322
|
config_path: FilePath = None,
|
|
298
|
-
mechanism:
|
|
299
|
-
solver_type:
|
|
323
|
+
mechanism: Mechanism = None,
|
|
324
|
+
solver_type: Any = None,
|
|
325
|
+
ignore_non_gas_phases: bool = True,
|
|
300
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
|
+
"""
|
|
301
342
|
self.__solver_type = solver_type
|
|
302
343
|
self.__vector_size = _vector_size(solver_type)
|
|
303
344
|
if config_path is None and mechanism is None:
|
|
304
|
-
raise ValueError(
|
|
345
|
+
raise ValueError(
|
|
346
|
+
"Either config_path or mechanism must be provided.")
|
|
305
347
|
if config_path is not None and mechanism is not None:
|
|
306
|
-
raise ValueError(
|
|
348
|
+
raise ValueError(
|
|
349
|
+
"Only one of config_path or mechanism must be provided.")
|
|
307
350
|
if config_path is not None:
|
|
308
351
|
self.__solver = _create_solver(config_path, solver_type)
|
|
309
352
|
elif mechanism is not None:
|
|
310
|
-
self.__solver = _create_solver_from_mechanism(
|
|
353
|
+
self.__solver = _create_solver_from_mechanism(
|
|
354
|
+
mechanism, solver_type, ignore_non_gas_phases)
|
|
311
355
|
|
|
312
356
|
def solver_type(self) -> SolverType:
|
|
313
357
|
"""
|