musica 0.12.2__cp312-cp312-win_amd64.whl → 0.13.0__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 +4 -0
- musica/_musica.cp312-win_amd64.pyd +0 -0
- musica/_version.py +1 -1
- musica/binding_common.cpp +6 -9
- musica/binding_common.hpp +17 -1
- musica/grid.cpp +206 -0
- musica/grid.py +98 -0
- musica/grid_map.cpp +117 -0
- musica/grid_map.py +167 -0
- musica/mechanism_configuration/__init__.py +18 -1
- musica/mechanism_configuration/ancillary.py +6 -0
- musica/mechanism_configuration/arrhenius.py +111 -269
- musica/mechanism_configuration/branched.py +116 -275
- musica/mechanism_configuration/emission.py +63 -52
- musica/mechanism_configuration/first_order_loss.py +73 -157
- musica/mechanism_configuration/mechanism.py +93 -0
- musica/mechanism_configuration/phase.py +44 -33
- musica/mechanism_configuration/phase_species.py +58 -0
- musica/mechanism_configuration/photolysis.py +77 -67
- musica/mechanism_configuration/reaction_component.py +54 -0
- musica/mechanism_configuration/reactions.py +17 -58
- musica/mechanism_configuration/species.py +45 -71
- musica/mechanism_configuration/surface.py +78 -74
- musica/mechanism_configuration/taylor_series.py +136 -0
- musica/mechanism_configuration/ternary_chemical_activation.py +138 -330
- musica/mechanism_configuration/troe.py +138 -330
- musica/mechanism_configuration/tunneling.py +105 -229
- musica/mechanism_configuration/user_defined.py +79 -68
- musica/mechanism_configuration.cpp +54 -162
- musica/musica.cpp +2 -5
- musica/profile.cpp +294 -0
- musica/profile.py +93 -0
- musica/profile_map.cpp +117 -0
- musica/profile_map.py +167 -0
- musica/test/examples/v1/full_configuration/full_configuration.json +91 -233
- musica/test/examples/v1/full_configuration/full_configuration.yaml +191 -290
- musica/test/integration/test_chapman.py +2 -2
- musica/test/integration/test_tuvx.py +72 -15
- musica/test/unit/test_grid.py +137 -0
- musica/test/unit/test_grid_map.py +126 -0
- musica/test/unit/test_parser.py +10 -10
- musica/test/unit/test_profile.py +169 -0
- musica/test/unit/test_profile_map.py +137 -0
- musica/test/unit/test_serializer.py +17 -16
- musica/test/unit/test_state.py +17 -4
- musica/test/unit/test_util_full_mechanism.py +78 -298
- musica/tuvx.cpp +94 -15
- musica/tuvx.py +92 -22
- musica/types.py +13 -5
- {musica-0.12.2.dist-info → musica-0.13.0.dist-info}/METADATA +14 -14
- musica-0.13.0.dist-info/RECORD +80 -0
- musica/mechanism_configuration/aqueous_equilibrium.py +0 -274
- musica/mechanism_configuration/condensed_phase_arrhenius.py +0 -309
- musica/mechanism_configuration/condensed_phase_photolysis.py +0 -88
- musica/mechanism_configuration/henrys_law.py +0 -44
- musica/mechanism_configuration/mechanism_configuration.py +0 -234
- musica/mechanism_configuration/simpol_phase_transfer.py +0 -217
- musica/mechanism_configuration/wet_deposition.py +0 -52
- musica-0.12.2.dist-info/RECORD +0 -70
- {musica-0.12.2.dist-info → musica-0.13.0.dist-info}/WHEEL +0 -0
- {musica-0.12.2.dist-info → musica-0.13.0.dist-info}/entry_points.txt +0 -0
- {musica-0.12.2.dist-info → musica-0.13.0.dist-info}/licenses/AUTHORS.md +0 -0
- {musica-0.12.2.dist-info → musica-0.13.0.dist-info}/licenses/LICENSE +0 -0
musica/CMakeLists.txt
CHANGED
|
Binary file
|
musica/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
version = "0.
|
|
1
|
+
version = "0.13.0"
|
musica/binding_common.cpp
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
#include "binding_common.hpp"
|
|
2
2
|
|
|
3
|
-
void bind_cuda(py::module_ &);
|
|
4
|
-
void bind_musica(py::module_ &);
|
|
5
|
-
#ifdef MUSICA_USE_TUVX
|
|
6
|
-
void bind_tuvx(py::module_ &);
|
|
7
|
-
#endif
|
|
8
|
-
#ifdef MUSICA_USE_CARMA
|
|
9
|
-
void bind_carma(py::module_ &);
|
|
10
|
-
#endif
|
|
11
|
-
|
|
12
3
|
void bind_mechanism_configuration(py::module_ &);
|
|
13
4
|
|
|
14
5
|
void bind_all(py::module_ &m)
|
|
15
6
|
{
|
|
7
|
+
py::bind_vector<std::vector<double>>(m, "VectorDouble");
|
|
8
|
+
|
|
16
9
|
py::module_ core = m.def_submodule("_core", "Wrapper classes for MUSICA C library structs and functions");
|
|
17
10
|
py::module_ mechanism_configuration = m.def_submodule(
|
|
18
11
|
"_mechanism_configuration", "Wrapper classes for Mechanism Configuration library structs and functions");
|
|
@@ -22,6 +15,10 @@ void bind_all(py::module_ &m)
|
|
|
22
15
|
bind_cuda(core);
|
|
23
16
|
bind_musica(core);
|
|
24
17
|
#ifdef MUSICA_USE_TUVX
|
|
18
|
+
bind_tuvx_grid(tuvx);
|
|
19
|
+
bind_tuvx_grid_map(tuvx);
|
|
20
|
+
bind_tuvx_profile(tuvx);
|
|
21
|
+
bind_tuvx_profile_map(tuvx);
|
|
25
22
|
bind_tuvx(tuvx);
|
|
26
23
|
#endif
|
|
27
24
|
|
musica/binding_common.hpp
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <pybind11/pybind11.h>
|
|
4
|
+
#include <pybind11/stl.h>
|
|
5
|
+
#include <pybind11/stl_bind.h>
|
|
4
6
|
|
|
5
7
|
namespace py = pybind11;
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
PYBIND11_MAKE_OPAQUE(std::vector<double>)
|
|
10
|
+
|
|
11
|
+
void bind_all(py::module_ &m);
|
|
12
|
+
void bind_cuda(py::module_ &);
|
|
13
|
+
void bind_musica(py::module_ &);
|
|
14
|
+
#ifdef MUSICA_USE_TUVX
|
|
15
|
+
void bind_tuvx(py::module_ &);
|
|
16
|
+
void bind_tuvx_grid(py::module_ &);
|
|
17
|
+
void bind_tuvx_grid_map(py::module_ &);
|
|
18
|
+
void bind_tuvx_profile(py::module_ &);
|
|
19
|
+
void bind_tuvx_profile_map(py::module_ &);
|
|
20
|
+
#endif
|
|
21
|
+
#ifdef MUSICA_USE_CARMA
|
|
22
|
+
void bind_carma(py::module_ &);
|
|
23
|
+
#endif
|
musica/grid.cpp
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// Copyright (C) 2023-2025 National Center for Atmospheric Research
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
//
|
|
4
|
+
// This file defines the Python bindings for the TUV-x Grid class in the musica library.
|
|
5
|
+
#include "binding_common.hpp"
|
|
6
|
+
|
|
7
|
+
#include <musica/tuvx/grid.hpp>
|
|
8
|
+
|
|
9
|
+
#include <pybind11/numpy.h>
|
|
10
|
+
#include <pybind11/pybind11.h>
|
|
11
|
+
#include <pybind11/stl.h>
|
|
12
|
+
|
|
13
|
+
namespace py = pybind11;
|
|
14
|
+
|
|
15
|
+
void bind_tuvx_grid(py::module_ &grid)
|
|
16
|
+
{
|
|
17
|
+
py::class_<musica::Grid>(grid, "_Grid")
|
|
18
|
+
.def(py::init(
|
|
19
|
+
[](const py::kwargs &kwargs)
|
|
20
|
+
{
|
|
21
|
+
if (!kwargs.contains("name"))
|
|
22
|
+
throw py::value_error("Missing required argument: name");
|
|
23
|
+
if (!kwargs.contains("units"))
|
|
24
|
+
throw py::value_error("Missing required argument: units");
|
|
25
|
+
if (!kwargs.contains("num_sections"))
|
|
26
|
+
throw py::value_error("Missing required argument: num_sections");
|
|
27
|
+
if (!py::isinstance<py::str>(kwargs["name"]))
|
|
28
|
+
throw py::value_error("Argument 'name' must be a string");
|
|
29
|
+
if (!py::isinstance<py::str>(kwargs["units"]))
|
|
30
|
+
throw py::value_error("Argument 'units' must be a string");
|
|
31
|
+
if (!py::isinstance<py::int_>(kwargs["num_sections"]))
|
|
32
|
+
throw py::value_error("Argument 'num_sections' must be an integer");
|
|
33
|
+
if (kwargs["num_sections"].cast<std::size_t>() <= 0)
|
|
34
|
+
throw py::value_error("Argument 'num_sections' must be greater than 0");
|
|
35
|
+
std::string name = kwargs["name"].cast<std::string>();
|
|
36
|
+
std::string units = kwargs["units"].cast<std::string>();
|
|
37
|
+
std::size_t num_sections = kwargs["num_sections"].cast<std::size_t>();
|
|
38
|
+
musica::Error error;
|
|
39
|
+
auto grid_instance = new musica::Grid(name.c_str(), units.c_str(), num_sections, &error);
|
|
40
|
+
if (!musica::IsSuccess(error))
|
|
41
|
+
{
|
|
42
|
+
std::string message = "Error creating grid: " + std::string(error.message_.value_);
|
|
43
|
+
musica::DeleteError(&error);
|
|
44
|
+
throw py::value_error(message);
|
|
45
|
+
}
|
|
46
|
+
return grid_instance;
|
|
47
|
+
}))
|
|
48
|
+
.def("__del__", [](musica::Grid &grid) {})
|
|
49
|
+
.def_property_readonly(
|
|
50
|
+
"name",
|
|
51
|
+
[](musica::Grid &self)
|
|
52
|
+
{
|
|
53
|
+
musica::Error error;
|
|
54
|
+
std::string name = self.GetName(&error);
|
|
55
|
+
if (!musica::IsSuccess(error))
|
|
56
|
+
{
|
|
57
|
+
std::string message = "Error getting grid name: " + std::string(error.message_.value_);
|
|
58
|
+
musica::DeleteError(&error);
|
|
59
|
+
throw py::value_error(message);
|
|
60
|
+
}
|
|
61
|
+
musica::DeleteError(&error);
|
|
62
|
+
return name;
|
|
63
|
+
},
|
|
64
|
+
"The name of the grid")
|
|
65
|
+
.def_property_readonly(
|
|
66
|
+
"units",
|
|
67
|
+
[](musica::Grid &self)
|
|
68
|
+
{
|
|
69
|
+
musica::Error error;
|
|
70
|
+
std::string units = self.GetUnits(&error);
|
|
71
|
+
if (!musica::IsSuccess(error))
|
|
72
|
+
{
|
|
73
|
+
std::string message = "Error getting grid units: " + std::string(error.message_.value_);
|
|
74
|
+
musica::DeleteError(&error);
|
|
75
|
+
throw py::value_error(message);
|
|
76
|
+
}
|
|
77
|
+
musica::DeleteError(&error);
|
|
78
|
+
return units;
|
|
79
|
+
},
|
|
80
|
+
"The units of the grid")
|
|
81
|
+
.def_property_readonly(
|
|
82
|
+
"num_sections",
|
|
83
|
+
[](musica::Grid &self)
|
|
84
|
+
{
|
|
85
|
+
musica::Error error;
|
|
86
|
+
std::size_t num_sections = self.GetNumberOfSections(&error);
|
|
87
|
+
if (!musica::IsSuccess(error))
|
|
88
|
+
{
|
|
89
|
+
std::string message = "Error getting number of grid sections: " + std::string(error.message_.value_);
|
|
90
|
+
musica::DeleteError(&error);
|
|
91
|
+
throw py::value_error(message);
|
|
92
|
+
}
|
|
93
|
+
return num_sections;
|
|
94
|
+
},
|
|
95
|
+
"The number of sections in the grid")
|
|
96
|
+
.def_property(
|
|
97
|
+
"edges",
|
|
98
|
+
// Getter - converts C++ array to numpy array
|
|
99
|
+
[](musica::Grid &self)
|
|
100
|
+
{
|
|
101
|
+
musica::Error error;
|
|
102
|
+
size_t size = self.GetNumberOfSections(&error) + 1;
|
|
103
|
+
if (!musica::IsSuccess(error))
|
|
104
|
+
{
|
|
105
|
+
std::string message = "Error getting number of grid sections: " + std::string(error.message_.value_);
|
|
106
|
+
musica::DeleteError(&error);
|
|
107
|
+
throw py::value_error(message);
|
|
108
|
+
}
|
|
109
|
+
auto result = py::array_t<double>(size);
|
|
110
|
+
py::buffer_info buf = result.request();
|
|
111
|
+
double *ptr = static_cast<double *>(buf.ptr);
|
|
112
|
+
self.GetEdges(ptr, size, &error);
|
|
113
|
+
if (!musica::IsSuccess(error))
|
|
114
|
+
{
|
|
115
|
+
std::string message = "Error getting grid edges: " + std::string(error.message_.value_);
|
|
116
|
+
musica::DeleteError(&error);
|
|
117
|
+
throw py::value_error(message);
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
},
|
|
121
|
+
// Setter - converts numpy array to C++ array
|
|
122
|
+
[](musica::Grid &self, py::array_t<double, py::array::c_style | py::array::forcecast> array)
|
|
123
|
+
{
|
|
124
|
+
py::buffer_info buf = array.request();
|
|
125
|
+
musica::Error error;
|
|
126
|
+
size_t size = self.GetNumberOfSections(&error) + 1;
|
|
127
|
+
if (!musica::IsSuccess(error))
|
|
128
|
+
{
|
|
129
|
+
std::string message = "Error getting number of grid sections: " + std::string(error.message_.value_);
|
|
130
|
+
musica::DeleteError(&error);
|
|
131
|
+
throw py::value_error(message);
|
|
132
|
+
}
|
|
133
|
+
if (buf.ndim != 1)
|
|
134
|
+
{
|
|
135
|
+
throw py::value_error("Number of dimensions must be one");
|
|
136
|
+
}
|
|
137
|
+
if (static_cast<size_t>(buf.size) != size)
|
|
138
|
+
{
|
|
139
|
+
throw py::value_error("Array size must be num_sections + 1");
|
|
140
|
+
}
|
|
141
|
+
double *ptr = static_cast<double *>(buf.ptr);
|
|
142
|
+
self.SetEdges(ptr, size, &error);
|
|
143
|
+
if (!musica::IsSuccess(error))
|
|
144
|
+
{
|
|
145
|
+
std::string message = "Error setting grid edges: " + std::string(error.message_.value_);
|
|
146
|
+
musica::DeleteError(&error);
|
|
147
|
+
throw py::value_error(message);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
"Grid edges array of length num_sections + 1")
|
|
151
|
+
.def_property(
|
|
152
|
+
"midpoints",
|
|
153
|
+
// Getter - converts C++ array to numpy array
|
|
154
|
+
[](musica::Grid &self)
|
|
155
|
+
{
|
|
156
|
+
musica::Error error;
|
|
157
|
+
size_t size = self.GetNumberOfSections(&error);
|
|
158
|
+
if (!musica::IsSuccess(error))
|
|
159
|
+
{
|
|
160
|
+
std::string message = "Error getting number of grid sections: " + std::string(error.message_.value_);
|
|
161
|
+
musica::DeleteError(&error);
|
|
162
|
+
throw py::value_error(message);
|
|
163
|
+
}
|
|
164
|
+
auto result = py::array_t<double>(size);
|
|
165
|
+
py::buffer_info buf = result.request();
|
|
166
|
+
double *ptr = static_cast<double *>(buf.ptr);
|
|
167
|
+
self.GetMidpoints(ptr, size, &error);
|
|
168
|
+
if (!musica::IsSuccess(error))
|
|
169
|
+
{
|
|
170
|
+
std::string message = "Error getting grid midpoints: " + std::string(error.message_.value_);
|
|
171
|
+
musica::DeleteError(&error);
|
|
172
|
+
throw py::value_error(message);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
},
|
|
176
|
+
// Setter - converts numpy array to C++ array
|
|
177
|
+
[](musica::Grid &self, py::array_t<double, py::array::c_style | py::array::forcecast> array)
|
|
178
|
+
{
|
|
179
|
+
py::buffer_info buf = array.request();
|
|
180
|
+
musica::Error error;
|
|
181
|
+
size_t size = self.GetNumberOfSections(&error);
|
|
182
|
+
if (!musica::IsSuccess(error))
|
|
183
|
+
{
|
|
184
|
+
std::string message = "Error getting number of grid sections: " + std::string(error.message_.value_);
|
|
185
|
+
musica::DeleteError(&error);
|
|
186
|
+
throw py::value_error(message);
|
|
187
|
+
}
|
|
188
|
+
if (buf.ndim != 1)
|
|
189
|
+
{
|
|
190
|
+
throw py::value_error("Number of dimensions must be one");
|
|
191
|
+
}
|
|
192
|
+
if (static_cast<size_t>(buf.size) != size)
|
|
193
|
+
{
|
|
194
|
+
throw py::value_error("Array size must be num_sections");
|
|
195
|
+
}
|
|
196
|
+
double *ptr = static_cast<double *>(buf.ptr);
|
|
197
|
+
self.SetMidpoints(ptr, size, &error);
|
|
198
|
+
if (!musica::IsSuccess(error))
|
|
199
|
+
{
|
|
200
|
+
std::string message = "Error setting grid midpoints: " + std::string(error.message_.value_);
|
|
201
|
+
musica::DeleteError(&error);
|
|
202
|
+
throw py::value_error(message);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
"Grid midpoints array of length num_sections");
|
|
206
|
+
}
|
musica/grid.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Copyright (C) 2023-2025 National Center for Atmospheric Research
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
TUV-x Grid class.
|
|
5
|
+
|
|
6
|
+
This module provides a class for defining grids on which TUV-x profiles exist.
|
|
7
|
+
Typically, this would be used to define vertical and wavelength grids.
|
|
8
|
+
|
|
9
|
+
Note: TUV-x is only available on macOS and Linux platforms.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Dict, Optional
|
|
13
|
+
import numpy as np
|
|
14
|
+
from . import backend
|
|
15
|
+
|
|
16
|
+
_backend = backend.get_backend()
|
|
17
|
+
|
|
18
|
+
Grid = _backend._tuvx._Grid if backend.tuvx_available() else None
|
|
19
|
+
|
|
20
|
+
if backend.tuvx_available():
|
|
21
|
+
original_init = Grid.__init__
|
|
22
|
+
|
|
23
|
+
def __init__(self, *, name: str, units: str,
|
|
24
|
+
num_sections: Optional[int] = None,
|
|
25
|
+
edges: Optional[np.ndarray] = None,
|
|
26
|
+
midpoints: Optional[np.ndarray] = None,
|
|
27
|
+
**kwargs):
|
|
28
|
+
"""Initialize a Grid instance. Note that at least one of num_sections, edges, or midpoints
|
|
29
|
+
must be provided.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
name: Name of the grid
|
|
33
|
+
units: Units of the grid values
|
|
34
|
+
num_sections: Optional number of grid sections
|
|
35
|
+
edges: Optional array of edge values (length num_sections + 1)
|
|
36
|
+
midpoints: Optional array of midpoint values (length num_sections)
|
|
37
|
+
**kwargs: Additional arguments passed to the C++ constructor
|
|
38
|
+
"""
|
|
39
|
+
if (num_sections is None and edges is None and midpoints is None):
|
|
40
|
+
raise ValueError("At least one of num_sections, edges, or midpoints must be provided.")
|
|
41
|
+
if (num_sections is None):
|
|
42
|
+
if (edges is not None):
|
|
43
|
+
num_sections = len(edges) - 1
|
|
44
|
+
elif (midpoints is not None):
|
|
45
|
+
num_sections = len(midpoints)
|
|
46
|
+
# Call the original C++ constructor correctly
|
|
47
|
+
original_init(self, name=name, units=units, num_sections=num_sections, **kwargs)
|
|
48
|
+
|
|
49
|
+
# Set edges or midpoints if provided
|
|
50
|
+
if edges is not None:
|
|
51
|
+
self.edges = edges
|
|
52
|
+
if midpoints is not None:
|
|
53
|
+
self.midpoints = midpoints
|
|
54
|
+
|
|
55
|
+
Grid.__init__ = __init__
|
|
56
|
+
|
|
57
|
+
def __str__(self):
|
|
58
|
+
"""User-friendly string representation."""
|
|
59
|
+
return f"Grid(name={self.name}, units={self.units}, num_sections={self.num_sections})"
|
|
60
|
+
|
|
61
|
+
Grid.__str__ = __str__
|
|
62
|
+
|
|
63
|
+
def __repr__(self):
|
|
64
|
+
"""Detailed string representation for debugging."""
|
|
65
|
+
return (f"Grid(name={self.name}, units={self.units}, num_sections={self.num_sections}, "
|
|
66
|
+
f"edges={self.edges}, midpoints={self.midpoints})")
|
|
67
|
+
|
|
68
|
+
Grid.__repr__ = __repr__
|
|
69
|
+
|
|
70
|
+
def __len__(self):
|
|
71
|
+
"""Return the number of sections in the grid."""
|
|
72
|
+
return self.num_sections
|
|
73
|
+
|
|
74
|
+
Grid.__len__ = __len__
|
|
75
|
+
|
|
76
|
+
def __eq__(self, other):
|
|
77
|
+
"""Check equality with another Grid instance."""
|
|
78
|
+
if not isinstance(other, Grid):
|
|
79
|
+
return NotImplemented
|
|
80
|
+
return (self.name == other.name and
|
|
81
|
+
self.units == other.units and
|
|
82
|
+
self.num_sections == other.num_sections and
|
|
83
|
+
np.array_equal(self.edges, other.edges) and
|
|
84
|
+
np.array_equal(self.midpoints, other.midpoints))
|
|
85
|
+
|
|
86
|
+
Grid.__eq__ = __eq__
|
|
87
|
+
|
|
88
|
+
def __bool__(self):
|
|
89
|
+
"""Return True if the grid has sections."""
|
|
90
|
+
return self.num_sections > 0
|
|
91
|
+
|
|
92
|
+
Grid.__bool__ = __bool__
|
|
93
|
+
|
|
94
|
+
def __contains__(self, value):
|
|
95
|
+
"""Check if a value is within the grid bounds."""
|
|
96
|
+
return self.edges[0] <= value <= self.edges[-1]
|
|
97
|
+
|
|
98
|
+
Grid.__contains__ = __contains__
|
musica/grid_map.cpp
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Copyright (C) 2023-2025 National Center for Atmospheric Research
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#include "binding_common.hpp"
|
|
4
|
+
|
|
5
|
+
#include <musica/tuvx/grid.hpp>
|
|
6
|
+
#include <musica/tuvx/grid_map.hpp>
|
|
7
|
+
|
|
8
|
+
#include <pybind11/pybind11.h>
|
|
9
|
+
|
|
10
|
+
namespace py = pybind11;
|
|
11
|
+
|
|
12
|
+
void bind_tuvx_grid_map(py::module& m)
|
|
13
|
+
{
|
|
14
|
+
py::class_<musica::GridMap>(m, "_GridMap")
|
|
15
|
+
.def(py::init<>(
|
|
16
|
+
[]()
|
|
17
|
+
{
|
|
18
|
+
musica::Error error;
|
|
19
|
+
auto grid_map_instance = new musica::GridMap(&error);
|
|
20
|
+
if (!musica::IsSuccess(error))
|
|
21
|
+
{
|
|
22
|
+
std::string message = "Error creating GridMap: " + std::string(error.message_.value_);
|
|
23
|
+
musica::DeleteError(&error);
|
|
24
|
+
throw py::value_error(message);
|
|
25
|
+
}
|
|
26
|
+
return grid_map_instance;
|
|
27
|
+
}))
|
|
28
|
+
.def(
|
|
29
|
+
"add_grid",
|
|
30
|
+
[](musica::GridMap& self, musica::Grid* grid)
|
|
31
|
+
{
|
|
32
|
+
musica::Error error;
|
|
33
|
+
self.AddGrid(grid, &error);
|
|
34
|
+
if (error.code_ != 0)
|
|
35
|
+
{
|
|
36
|
+
std::string message = "Error adding grid: " + std::string(error.message_.value_);
|
|
37
|
+
musica::DeleteError(&error);
|
|
38
|
+
throw std::runtime_error(message);
|
|
39
|
+
}
|
|
40
|
+
DeleteError(&error);
|
|
41
|
+
})
|
|
42
|
+
.def(
|
|
43
|
+
"get_grid",
|
|
44
|
+
[](musica::GridMap& self, const std::string& name, const std::string& units)
|
|
45
|
+
{
|
|
46
|
+
musica::Error error;
|
|
47
|
+
musica::Grid* grid = self.GetGrid(name.c_str(), units.c_str(), &error);
|
|
48
|
+
if (!musica::IsSuccess(error))
|
|
49
|
+
{
|
|
50
|
+
std::string message = std::string(error.message_.value_);
|
|
51
|
+
musica::DeleteError(&error);
|
|
52
|
+
throw py::value_error("Error getting grid: " + message);
|
|
53
|
+
}
|
|
54
|
+
musica::DeleteError(&error);
|
|
55
|
+
return grid;
|
|
56
|
+
},
|
|
57
|
+
py::return_value_policy::reference)
|
|
58
|
+
.def(
|
|
59
|
+
"get_grid_by_index",
|
|
60
|
+
[](musica::GridMap& self, std::size_t index)
|
|
61
|
+
{
|
|
62
|
+
musica::Error error;
|
|
63
|
+
musica::Grid* grid = self.GetGridByIndex(index, &error);
|
|
64
|
+
if (!musica::IsSuccess(error))
|
|
65
|
+
{
|
|
66
|
+
std::string message = std::string(error.message_.value_);
|
|
67
|
+
musica::DeleteError(&error);
|
|
68
|
+
throw py::value_error("Error getting grid by index: " + message);
|
|
69
|
+
}
|
|
70
|
+
musica::DeleteError(&error);
|
|
71
|
+
return grid;
|
|
72
|
+
},
|
|
73
|
+
py::return_value_policy::reference)
|
|
74
|
+
.def(
|
|
75
|
+
"remove_grid",
|
|
76
|
+
[](musica::GridMap& self, const std::string& name, const std::string& units)
|
|
77
|
+
{
|
|
78
|
+
musica::Error error;
|
|
79
|
+
self.RemoveGrid(name.c_str(), units.c_str(), &error);
|
|
80
|
+
if (!musica::IsSuccess(error))
|
|
81
|
+
{
|
|
82
|
+
std::string message = std::string(error.message_.value_);
|
|
83
|
+
musica::DeleteError(&error);
|
|
84
|
+
throw py::value_error("Error removing grid: " + message);
|
|
85
|
+
}
|
|
86
|
+
musica::DeleteError(&error);
|
|
87
|
+
})
|
|
88
|
+
.def(
|
|
89
|
+
"remove_grid_by_index",
|
|
90
|
+
[](musica::GridMap& self, std::size_t index)
|
|
91
|
+
{
|
|
92
|
+
musica::Error error;
|
|
93
|
+
self.RemoveGridByIndex(index, &error);
|
|
94
|
+
if (!musica::IsSuccess(error))
|
|
95
|
+
{
|
|
96
|
+
std::string message = std::string(error.message_.value_);
|
|
97
|
+
musica::DeleteError(&error);
|
|
98
|
+
throw py::value_error("Error removing grid by index: " + message);
|
|
99
|
+
}
|
|
100
|
+
musica::DeleteError(&error);
|
|
101
|
+
})
|
|
102
|
+
.def(
|
|
103
|
+
"get_number_of_grids",
|
|
104
|
+
[](musica::GridMap& self)
|
|
105
|
+
{
|
|
106
|
+
musica::Error error;
|
|
107
|
+
std::size_t num_grids = self.GetNumberOfGrids(&error);
|
|
108
|
+
if (!musica::IsSuccess(error))
|
|
109
|
+
{
|
|
110
|
+
std::string message = std::string(error.message_.value_);
|
|
111
|
+
musica::DeleteError(&error);
|
|
112
|
+
throw py::value_error("Error getting number of grids: " + message);
|
|
113
|
+
}
|
|
114
|
+
musica::DeleteError(&error);
|
|
115
|
+
return num_grids;
|
|
116
|
+
});
|
|
117
|
+
}
|
musica/grid_map.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Copyright (C) 2023-2025 National Center for Atmospheric Research
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""
|
|
4
|
+
TUV-x GridMap class.
|
|
5
|
+
|
|
6
|
+
This module provides a class for managing collections of TUV-x grids.
|
|
7
|
+
The GridMap class allows dictionary-style access to grids using (name, units) tuples as keys.
|
|
8
|
+
|
|
9
|
+
Note: TUV-x is only available on macOS and Linux platforms.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Iterator, Sequence
|
|
13
|
+
from . import backend
|
|
14
|
+
from .grid import Grid
|
|
15
|
+
|
|
16
|
+
_backend = backend.get_backend()
|
|
17
|
+
|
|
18
|
+
GridMap = _backend._tuvx._GridMap if backend.tuvx_available() else None
|
|
19
|
+
|
|
20
|
+
if backend.tuvx_available():
|
|
21
|
+
original_init = GridMap.__init__
|
|
22
|
+
|
|
23
|
+
def __init__(self, **kwargs):
|
|
24
|
+
"""Initialize a GridMap instance.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
**kwargs: Additional arguments passed to the C++ constructor
|
|
28
|
+
"""
|
|
29
|
+
original_init(self, **kwargs)
|
|
30
|
+
|
|
31
|
+
GridMap.__init__ = __init__
|
|
32
|
+
|
|
33
|
+
def __str__(self):
|
|
34
|
+
"""User-friendly string representation."""
|
|
35
|
+
return f"GridMap(num_grids={len(self)})"
|
|
36
|
+
|
|
37
|
+
GridMap.__str__ = __str__
|
|
38
|
+
|
|
39
|
+
def __repr__(self):
|
|
40
|
+
"""Detailed string representation for debugging."""
|
|
41
|
+
grid_details = []
|
|
42
|
+
for i in range(len(self)):
|
|
43
|
+
grid = self.get_grid_by_index(i)
|
|
44
|
+
grid_details.append(f"({grid.name}, {grid.units})")
|
|
45
|
+
return f"GridMap(grids={grid_details})"
|
|
46
|
+
|
|
47
|
+
GridMap.__repr__ = __repr__
|
|
48
|
+
|
|
49
|
+
def __len__(self):
|
|
50
|
+
"""Return the number of grids in the map."""
|
|
51
|
+
return self.get_number_of_grids()
|
|
52
|
+
|
|
53
|
+
GridMap.__len__ = __len__
|
|
54
|
+
|
|
55
|
+
def __bool__(self):
|
|
56
|
+
"""Return True if the map has any grids."""
|
|
57
|
+
return len(self) > 0
|
|
58
|
+
|
|
59
|
+
GridMap.__bool__ = __bool__
|
|
60
|
+
|
|
61
|
+
def __getitem__(self, key) -> Grid:
|
|
62
|
+
"""Get a grid using dictionary-style access with (name, units) tuple as key.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
key: A tuple of (grid_name, grid_units)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The requested Grid object
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
KeyError: If no grid matches the given name and units
|
|
72
|
+
TypeError: If key is not a tuple of (str, str)
|
|
73
|
+
"""
|
|
74
|
+
if not isinstance(key, tuple) or len(key) != 2:
|
|
75
|
+
raise TypeError("Grid access requires a tuple of (name, units)")
|
|
76
|
+
name, units = key
|
|
77
|
+
try:
|
|
78
|
+
return self.get_grid(name, units)
|
|
79
|
+
except Exception as e:
|
|
80
|
+
raise KeyError(f"No grid found with name='{name}' and units='{units}'") from e
|
|
81
|
+
|
|
82
|
+
GridMap.__getitem__ = __getitem__
|
|
83
|
+
|
|
84
|
+
def __setitem__(self, key, grid):
|
|
85
|
+
"""Add a grid to the map using dictionary-style access.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
key: A tuple of (grid_name, grid_units)
|
|
89
|
+
grid: The Grid object to add
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
TypeError: If key is not a tuple, or if key components are not strings
|
|
93
|
+
TypeError: If grid is not a Grid object
|
|
94
|
+
ValueError: If grid name/units don't match the key
|
|
95
|
+
"""
|
|
96
|
+
if not isinstance(key, tuple) or len(key) != 2:
|
|
97
|
+
raise TypeError("Grid assignment requires a tuple of (name, units)")
|
|
98
|
+
name, units = key
|
|
99
|
+
if not isinstance(name, str):
|
|
100
|
+
raise TypeError("Grid name must be a string")
|
|
101
|
+
if not isinstance(units, str):
|
|
102
|
+
raise TypeError("Grid units must be a string")
|
|
103
|
+
if not isinstance(grid, Grid):
|
|
104
|
+
raise TypeError("Value must be a Grid object")
|
|
105
|
+
if grid.name != name or grid.units != units:
|
|
106
|
+
raise ValueError("Grid name/units must match the key tuple")
|
|
107
|
+
self.add_grid(grid)
|
|
108
|
+
|
|
109
|
+
GridMap.__setitem__ = __setitem__
|
|
110
|
+
|
|
111
|
+
def __iter__(self) -> Iterator:
|
|
112
|
+
"""Return an iterator over (name, units) tuples of all grids."""
|
|
113
|
+
for i in range(len(self)):
|
|
114
|
+
grid = self.get_grid_by_index(i)
|
|
115
|
+
yield (grid.name, grid.units)
|
|
116
|
+
|
|
117
|
+
GridMap.__iter__ = __iter__
|
|
118
|
+
|
|
119
|
+
def __contains__(self, key) -> bool:
|
|
120
|
+
"""Check if a grid with given name and units exists in the map.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
key: A tuple of (grid_name, grid_units)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
True if a matching grid exists, False otherwise
|
|
127
|
+
"""
|
|
128
|
+
if not isinstance(key, tuple) or len(key) != 2:
|
|
129
|
+
return False
|
|
130
|
+
name, units = key
|
|
131
|
+
try:
|
|
132
|
+
grid = self.get_grid(str(name), str(units))
|
|
133
|
+
return grid is not None
|
|
134
|
+
except (ValueError, KeyError):
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
GridMap.__contains__ = __contains__
|
|
138
|
+
|
|
139
|
+
def clear(self):
|
|
140
|
+
"""Remove all grids from the map."""
|
|
141
|
+
while len(self) > 0:
|
|
142
|
+
self.remove_grid_by_index(0)
|
|
143
|
+
|
|
144
|
+
GridMap.clear = clear
|
|
145
|
+
|
|
146
|
+
def items(self):
|
|
147
|
+
"""Return an iterator over (key, grid) pairs, where key is (name, units)."""
|
|
148
|
+
for i in range(len(self)):
|
|
149
|
+
grid = self.get_grid_by_index(i)
|
|
150
|
+
yield ((grid.name, grid.units), grid)
|
|
151
|
+
|
|
152
|
+
GridMap.items = items
|
|
153
|
+
|
|
154
|
+
def keys(self):
|
|
155
|
+
"""Return an iterator over grid keys (name, units) tuples."""
|
|
156
|
+
for i in range(len(self)):
|
|
157
|
+
grid = self.get_grid_by_index(i)
|
|
158
|
+
yield (grid.name, grid.units)
|
|
159
|
+
|
|
160
|
+
GridMap.keys = keys
|
|
161
|
+
|
|
162
|
+
def values(self):
|
|
163
|
+
"""Return an iterator over Grid objects in the map."""
|
|
164
|
+
for i in range(len(self)):
|
|
165
|
+
yield self.get_grid_by_index(i)
|
|
166
|
+
|
|
167
|
+
GridMap.values = values
|
|
@@ -1 +1,18 @@
|
|
|
1
|
-
from .
|
|
1
|
+
from .arrhenius import Arrhenius
|
|
2
|
+
from .branched import Branched
|
|
3
|
+
from .emission import Emission
|
|
4
|
+
from .first_order_loss import FirstOrderLoss
|
|
5
|
+
from .mechanism import Mechanism
|
|
6
|
+
from .ancillary import Version, Parser, ReactionType
|
|
7
|
+
from .phase import Phase
|
|
8
|
+
from .phase_species import PhaseSpecies
|
|
9
|
+
from .photolysis import Photolysis
|
|
10
|
+
from .reaction_component import ReactionComponent
|
|
11
|
+
from .reactions import Reactions
|
|
12
|
+
from .species import Species
|
|
13
|
+
from .surface import Surface
|
|
14
|
+
from .taylor_series import TaylorSeries
|
|
15
|
+
from .ternary_chemical_activation import TernaryChemicalActivation
|
|
16
|
+
from .troe import Troe
|
|
17
|
+
from .tunneling import Tunneling
|
|
18
|
+
from .user_defined import UserDefined
|