musica 0.12.1__cp311-cp311-win_amd64.whl → 0.13.0__cp311-cp311-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.

Files changed (72) hide show
  1. musica/CMakeLists.txt +4 -0
  2. musica/_musica.cp311-win_amd64.pyd +0 -0
  3. musica/_version.py +1 -1
  4. musica/binding_common.cpp +6 -9
  5. musica/binding_common.hpp +17 -1
  6. musica/examples/__init__.py +1 -1
  7. musica/examples/carma_aluminum.py +1 -0
  8. musica/examples/carma_sulfate.py +1 -0
  9. musica/examples/sulfate_box_model.py +2 -2
  10. musica/examples/ts1_latin_hypercube.py +1 -1
  11. musica/grid.cpp +206 -0
  12. musica/grid.py +98 -0
  13. musica/grid_map.cpp +117 -0
  14. musica/grid_map.py +167 -0
  15. musica/mechanism_configuration/__init__.py +18 -1
  16. musica/mechanism_configuration/ancillary.py +6 -0
  17. musica/mechanism_configuration/arrhenius.py +111 -269
  18. musica/mechanism_configuration/branched.py +116 -275
  19. musica/mechanism_configuration/emission.py +63 -52
  20. musica/mechanism_configuration/first_order_loss.py +73 -157
  21. musica/mechanism_configuration/mechanism.py +93 -0
  22. musica/mechanism_configuration/phase.py +44 -33
  23. musica/mechanism_configuration/phase_species.py +58 -0
  24. musica/mechanism_configuration/photolysis.py +77 -67
  25. musica/mechanism_configuration/reaction_component.py +54 -0
  26. musica/mechanism_configuration/reactions.py +17 -58
  27. musica/mechanism_configuration/species.py +45 -71
  28. musica/mechanism_configuration/surface.py +78 -74
  29. musica/mechanism_configuration/taylor_series.py +136 -0
  30. musica/mechanism_configuration/ternary_chemical_activation.py +138 -330
  31. musica/mechanism_configuration/troe.py +138 -330
  32. musica/mechanism_configuration/tunneling.py +105 -229
  33. musica/mechanism_configuration/user_defined.py +79 -68
  34. musica/mechanism_configuration.cpp +54 -162
  35. musica/musica.cpp +2 -5
  36. musica/profile.cpp +294 -0
  37. musica/profile.py +93 -0
  38. musica/profile_map.cpp +117 -0
  39. musica/profile_map.py +167 -0
  40. musica/test/examples/v1/full_configuration/full_configuration.json +91 -233
  41. musica/test/examples/v1/full_configuration/full_configuration.yaml +191 -290
  42. musica/test/integration/test_carma_aluminum.py +2 -1
  43. musica/test/integration/test_carma_sulfate.py +2 -1
  44. musica/test/integration/test_chapman.py +2 -2
  45. musica/test/integration/test_sulfate_box_model.py +1 -1
  46. musica/test/integration/test_tuvx.py +72 -15
  47. musica/test/unit/test_grid.py +137 -0
  48. musica/test/unit/test_grid_map.py +126 -0
  49. musica/test/unit/test_parser.py +10 -10
  50. musica/test/unit/test_profile.py +169 -0
  51. musica/test/unit/test_profile_map.py +137 -0
  52. musica/test/unit/test_serializer.py +17 -16
  53. musica/test/unit/test_state.py +338 -0
  54. musica/test/unit/test_util_full_mechanism.py +78 -298
  55. musica/tools/prepare_build_environment_linux.sh +7 -25
  56. musica/tuvx.cpp +94 -15
  57. musica/tuvx.py +92 -22
  58. musica/types.py +28 -17
  59. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/METADATA +15 -14
  60. musica-0.13.0.dist-info/RECORD +80 -0
  61. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/WHEEL +1 -1
  62. musica/mechanism_configuration/aqueous_equilibrium.py +0 -274
  63. musica/mechanism_configuration/condensed_phase_arrhenius.py +0 -309
  64. musica/mechanism_configuration/condensed_phase_photolysis.py +0 -88
  65. musica/mechanism_configuration/henrys_law.py +0 -44
  66. musica/mechanism_configuration/mechanism_configuration.py +0 -234
  67. musica/mechanism_configuration/simpol_phase_transfer.py +0 -217
  68. musica/mechanism_configuration/wet_deposition.py +0 -52
  69. musica-0.12.1.dist-info/RECORD +0 -69
  70. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/entry_points.txt +0 -0
  71. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/licenses/AUTHORS.md +0 -0
  72. {musica-0.12.1.dist-info → musica-0.13.0.dist-info}/licenses/LICENSE +0 -0
musica/CMakeLists.txt CHANGED
@@ -18,6 +18,10 @@ set(
18
18
  if (MUSICA_ENABLE_TUVX)
19
19
  list(APPEND MUSICA_PYTHON_SOURCES
20
20
  tuvx.cpp
21
+ grid.cpp
22
+ grid_map.cpp
23
+ profile.cpp
24
+ profile_map.cpp
21
25
  )
22
26
  endif()
23
27
 
Binary file
musica/_version.py CHANGED
@@ -1 +1 @@
1
- version = "0.12.1"
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
- void bind_all(py::module_ &m);
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
@@ -1 +1 @@
1
- from .examples import Examples
1
+ from .examples import Examples
@@ -11,6 +11,7 @@ import ussa1976
11
11
 
12
12
  available = musica.backend.carma_available()
13
13
 
14
+
14
15
  def run_carma_aluminum_example():
15
16
  group = musica.carma.CARMAGroupConfig(
16
17
  name="aluminum",
@@ -14,6 +14,7 @@ import musica
14
14
 
15
15
  available = musica.backend.carma_available()
16
16
 
17
+
17
18
  def run_carma_sulfate_example():
18
19
  """Test CARMA sulfate growth from gas condensation in a simple single grid box."""
19
20
 
@@ -272,8 +272,8 @@ def run_box_model():
272
272
  state.set_concentrations(initial_concentrations)
273
273
  state.set_user_defined_rate_parameters({"EMIS.SO2": np.full(NUMBER_OF_GRID_CELLS, 1.0e-8, dtype=np.float64)})
274
274
 
275
- # Run the simulation for 6 hours with a timestep of 30 seconds
276
- time_hours = 2.0
275
+ # Run the simulation for 30 minutes with a timestep of 30 seconds
276
+ time_hours = 0.5
277
277
  time_seconds = time_hours * np.float64(3600.0)
278
278
  dt = np.float64(30.0)
279
279
  num_steps = int(time_seconds / dt)
@@ -242,4 +242,4 @@ ax[2].set_ylabel('Air Density [kg m-3]')
242
242
  ax[2].set_xlabel('Grid Cell')
243
243
 
244
244
  fig.tight_layout()
245
- fig.savefig('ts1_latin_hypercube_conditions.png', dpi=300)
245
+ fig.savefig('ts1_latin_hypercube_conditions.png', dpi=300)
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
+ }