SERiF 0.0.1a
3+1D Stellar Structure and Evolution
Loading...
Searching...
No Matches
bindings.cpp
Go to the documentation of this file.
1#include <pybind11/pybind11.h>
2#include <pybind11/stl.h>
3#include <pybind11/numpy.h>
4#include <pybind11/operators.h> // For operator overloads
5#include <memory>
6#include <sstream>
7
8// Include your trampoline class header. The implementation will be in a separate .cpp file.
9#include "bindings.h"
10
14
15#include "mfem.hpp"
16
17
18namespace py = pybind11;
19
20// This function registers all the mfem-related classes to the python module
21void register_mfem_bindings(py::module &mfem_submodule) {
22 using namespace mfem;
23 register_operator_bindings(mfem_submodule);
24 register_matrix_bindings(mfem_submodule);
25
26 register_vector_bindings(mfem_submodule);
27 register_array_bindings(mfem_submodule);
28 register_table_bindings(mfem_submodule);
29
30 register_mesh_bindings(mfem_submodule);
31
32 auto formsModule = mfem_submodule.def_submodule("forms", "MFEM forms module");
35
36 auto fecModule = mfem_submodule.def_submodule("fec", "MFEM finite element collection module");
42
43 auto fesModule = mfem_submodule.def_submodule("fes", "MFEM finite element space module");
45
46 register_coefficient_bindings(mfem_submodule);
47 register_intrule_bindings(mfem_submodule);
48 register_eltrans_bindings(mfem_submodule);
49
50 register_grid_function_bindings(mfem_submodule);
51}
52
53void register_operator_bindings(py::module &mfem_submodule) {
54 using namespace mfem;
55 // Use the PyOperator trampoline when binding mfem::Operator
56 // This allows Python classes to inherit from mfem::Operator
57 py::class_<Operator, serif::pybind::PyOperator /* Trampoline */>(mfem_submodule, "Operator")
58 // NOTE: We DO NOT define an __init__ method because Operator is abstract.
59 // Python users will instantiate concrete derived classes instead.
60
61 // --- Bind Properties ---
62 .def_property_readonly("height", &Operator::Height, "Get the height (number of rows) of the Operator.")
63 .def_property_readonly("width", &Operator::Width, "Get the width (number of columns) of the Operator.")
64
65 // --- Bind Core Virtual Methods ---
66 // We bind the methods of the C++ base class so they can be called from Python.
67 // The trampoline handles redirecting to a Python override if one exists.
68
69 // Mult: y = A(x)
70 .def("Mult", py::overload_cast<const Vector&, Vector&>(&Operator::Mult, py::const_),
71 py::arg("x"), py::arg("y"), "Calculates y = A(x). y must be pre-allocated.")
72
73 // Pythonic overload for Mult that returns a new vector
74 .def("Mult", [](const Operator &op, const Vector &x) {
75 Vector y(op.Height());
76 op.Mult(x, y);
77 return y;
78 }, py::arg("x"), "Calculates and returns a new vector y = A(x).")
79
80 // MultTranspose: y = A^T(x)
81 .def("MultTranspose", py::overload_cast<const Vector&, Vector&>(&Operator::MultTranspose, py::const_),
82 py::arg("x"), py::arg("y"), "Calculates y = A^T(x). y must be pre-allocated.")
83
84 .def("MultTranspose", [](const Operator &op, const Vector &x) {
85 Vector y(op.Width());
86 op.MultTranspose(x, y);
87 return y;
88 }, py::arg("x"), "Calculates and returns a new vector y = A^T(x).")
89
90 // Additive versions
91 .def("AddMult", &Operator::AddMult, py::arg("x"), py::arg("y"), py::arg("a") = 1.0, "Performs y += a * A(x).")
92 .def("AddMultTranspose", &Operator::AddMultTranspose, py::arg("x"), py::arg("y"), py::arg("a") = 1.0, "Performs y += a * A^T(x).")
93
94 // Other core virtual methods
95 .def("AssembleDiagonal", &Operator::AssembleDiagonal, py::arg("diag"), "Assembles the operator diagonal into the given Vector.")
96 .def("RecoverFEMSolution", &Operator::RecoverFEMSolution, py::arg("X"), py::arg("b"), py::arg("x"), "Recovers the full FE solution.")
97 .def("GetGradient", &Operator::GetGradient, py::return_value_policy::reference, "Returns the Gradient of a non-linear operator.")
98
99 // Methods returning other operators (e.g., for parallel/BCs)
100 .def("GetProlongation", &Operator::GetProlongation, py::return_value_policy::reference, "Returns the prolongation operator.")
101 .def("GetRestriction", &Operator::GetRestriction, py::return_value_policy::reference, "Returns the restriction operator.")
102
103 // --- Pythonic Operator Overloading ---
104 .def("__matmul__", [](const Operator &op, const Vector &x) {
105 Vector y(op.Height());
106 op.Mult(x, y);
107 return y;
108 }, py::is_operator());
109}
110
111void register_matrix_bindings(py::module &mfem_submodule) {
112 using namespace mfem;
113 py::class_<Matrix, Operator, serif::pybind::PyMatrix>(mfem_submodule, "Matrix")
114 // No constructor since it's an abstract base class.
115 .def_property_readonly("is_square", &Matrix::IsSquare,
116 "Returns true if the matrix is square.")
117
118 .def("finalize", &Matrix::Finalize, py::arg("skip_zeros") = 1,
119 "Finalizes the matrix initialization.")
120
121 .def("inverse", &Matrix::Inverse,
122 "Returns a pointer to (an approximation) of the matrix inverse.",
123 py::return_value_policy::take_ownership) // The caller owns the returned pointer
124
125 // Pythonic element access: mat[i, j]
126 .def("__getitem__", [](const Matrix &m, py::tuple t) {
127 if (t.size() != 2) {
128 throw py::index_error("Matrix index must be a 2-tuple (i, j)");
129 }
130 return m.Elem(t[0].cast<int>(), t[1].cast<int>());
131 })
132
133 // Pythonic element assignment: mat[i, j] = value
134 .def("__setitem__", [](Matrix &m, py::tuple t, real_t value) {
135 if (t.size() != 2) {
136 throw py::index_error("Matrix index must be a 2-tuple (i, j)");
137 }
138 m.Elem(t[0].cast<int>(), t[1].cast<int>()) = value;
139 })
140
141 .def("__repr__", [](const Matrix &m) {
142 return "<mfem.Matrix (Abstract) " +
143 std::to_string(m.Height()) + "x" +
144 std::to_string(m.Width()) + ">";
145 });
146}
147
148void register_vector_bindings(py::module &mfem_submodule) {
149 using namespace mfem;
150 // Register the mfem::Vector class
151 py::class_<mfem::Vector>(mfem_submodule, "Vector")
152 .def(py::init<int>(), py::arg("size"))
153 .def(py::init<const mfem::Vector &>(), py::arg("other"))
154 .def(py::init([](py::array_t<double> arr) {
155 py::buffer_info info = arr.request();
156 if (info.ndim != 1) {
157 throw std::runtime_error("Vector(): expected a 1-D numpy array");
158 }
159 mfem::Vector v(info.size);
160 std::memcpy(v.GetData(), info.ptr, info.size * sizeof(double));
161 return v;
162 }), py::arg("array"))
163 .def("GetData", &mfem::Vector::GetData, py::return_value_policy::reference_internal)
164 .def("Size", &mfem::Vector::Size)
165 .def("__getitem__", [](const mfem::Vector &v, int i) { return v[i]; })
166 .def("__len__", &mfem::Vector::Size)
167 .def("__setitem__", [](mfem::Vector &v, int i, double value) { v[i] = value; })
168 .def("__repr__", [](const mfem::Vector &v) {
169 return "<mfem.Vector(size=" + std::to_string(v.Size()) + ")>";
170 })
171 .def("as_numpy", [](mfem::Vector &self) {
172 return py::array_t<double>(self.Size(), self.GetData());
173 });
174}
175
176void register_array_bindings(py::module &mfem_submodule) {
177 using namespace mfem;
178 py::class_<Array<int>>(mfem_submodule, "IntArray")
179 // --- Constructors ---
180 .def(py::init<>(), "Default constructor.")
181 .def(py::init<int>(), py::arg("size"), "Constructor with size.")
182 .def(py::init([](const std::vector<int> &v) {
183 auto *arr = new Array<int>(v.size());
184 for (size_t i = 0; i < v.size(); ++i) {
185 (*arr)[i] = v[i];
186 }
187 return arr;
188 }), py::arg("list"), "Constructor from a Python list.")
189
190 // --- Pythonic Features ---
191 .def("__len__", &Array<int>::Size)
192 .def("__getitem__", [](const Array<int> &self, int i) {
193 if (i < 0) i += self.Size(); // Handle negative indices
194 if (i < 0 || i >= self.Size()) throw py::index_error();
195 return self[i];
196 })
197 .def("__setitem__", [](Array<int> &self, int i, int value) {
198 if (i < 0) i += self.Size(); // Handle negative indices
199 if (i < 0 || i >= self.Size()) throw py::index_error();
200 self[i] = value;
201 })
202 .def("__iter__", [](Array<int> &self) {
203 return py::make_iterator(self.begin(), self.end());
204 }, py::keep_alive<0, 1>()) // Keep array alive while iterator is used
205 .def("__repr__", [](const Array<int> &self) {
206 std::stringstream ss;
207 ss << "[";
208 for (int i = 0; i < self.Size(); ++i) {
209 ss << self[i] << (i == self.Size() - 1 ? "" : ", ");
210 }
211 ss << "]";
212 return ss.str();
213 })
214
215 // --- Core Methods ---
216 .def("Size", &Array<int>::Size)
217 .def("SetSize", py::overload_cast<int>(&Array<int>::SetSize), py::arg("size"))
218 .def("Append", py::overload_cast<const int &>(&Array<int>::Append), py::arg("el"))
219 .def("Last", py::overload_cast<>(&Array<int>::Last, py::const_))
220 .def("DeleteLast", &Array<int>::DeleteLast)
221 .def("DeleteAll", &Array<int>::DeleteAll)
222 .def("Sort", [](Array<int> &self) { self.Sort(); })
223 .def("Unique", &Array<int>::Unique)
224 .def("Assign", [](Array<int> &self, const Array<int> &other) {
225 self.Assign(other.GetData());
226 })
227 .def("as_numpy", [](Array<int> &self) {
228 // Create a Python-owned copy to avoid memory issues
229 return py::array_t<int>(self.Size(), self.GetData());
230 });
231}
232
233// Main function to register BilinearForm
234void register_bilinear_form_bindings(py::module &mfem_submodule) {
235 using namespace mfem;
236
237 // It's good practice to bind enums used by the class
238 bind_assembly_level_enum(mfem_submodule);
239
240 // Bind the mfem::BilinearForm class, inheriting from mfem::Matrix
241 // No trampoline is needed because this is a concrete class.
242 py::class_<BilinearForm, Matrix>(mfem_submodule, "BilinearForm")
243
244 // --- Constructor ---
245 .def(py::init<FiniteElementSpace *>(), py::arg("fespace"),
246 // The keep_alive policy ensures the Python object for the
247 // FiniteElementSpace is not garbage-collected while the
248 // BilinearForm is still using it.
249 py::keep_alive<1, 2>(),
250 "Constructs a bilinear form on the given FiniteElementSpace.")
251
252 // --- Setup Methods ---
253 .def("SetAssemblyLevel", &BilinearForm::SetAssemblyLevel, py::arg("assembly_level"),
254 "Set the assembly level (e.g., LEGACY, FULL, PARTIAL, NONE).")
255 .def("EnableStaticCondensation", &BilinearForm::EnableStaticCondensation,
256 "Enable static condensation to reduce system size.")
257 .def("EnableHybridization", &BilinearForm::EnableHybridization,
258 "Enable hybridization.")
259 .def("UsePrecomputedSparsity", &BilinearForm::UsePrecomputedSparsity, py::arg("ps") = 1,
260 "Enable use of precomputed sparsity pattern.")
261 .def("SetDiagonalPolicy", &BilinearForm::SetDiagonalPolicy, py::arg("policy"),
262 "Set the policy for handling diagonal entries of essential DOFs.")
263
264 // --- Integrator Methods ---
265 .def("AddDomainIntegrator", py::overload_cast<BilinearFormIntegrator *>(&BilinearForm::AddDomainIntegrator),
266 py::arg("bfi"), py::keep_alive<1, 2>(),
267 "Adds a domain integrator to the form.")
268 .def("AddBoundaryIntegrator", py::overload_cast<BilinearFormIntegrator *>(&BilinearForm::AddBoundaryIntegrator),
269 py::arg("bfi"), py::keep_alive<1, 2>(),
270 "Adds a boundary integrator to the form.")
271 .def("AddInteriorFaceIntegrator", &BilinearForm::AddInteriorFaceIntegrator,
272 py::arg("bfi"), py::keep_alive<1, 2>(),
273 "Adds an interior face integrator (e.g., for DG methods).")
274 .def("AddBdrFaceIntegrator", py::overload_cast<BilinearFormIntegrator *>(&BilinearForm::AddBdrFaceIntegrator),
275 py::arg("bfi"), py::keep_alive<1, 2>(),
276 "Adds a boundary face integrator.")
277
278 // --- Assembly and System Formulation ---
279 .def("Assemble", &BilinearForm::Assemble, py::arg("skip_zeros") = 1,
280 "Assembles the bilinear form into a sparse matrix.")
281 .def("AssembleDiagonal", &BilinearForm::AssembleDiagonal, py::arg("diag"),
282 "Assembles the diagonal of the operator into a Vector.")
283 .def("FormLinearSystem",
284 [](BilinearForm &self, const Array<int> &ess_tdof_list, Vector &x,
285 Vector &b, OperatorHandle &A, Vector &X, Vector &B, int copy_interior) {
286 self.FormLinearSystem(ess_tdof_list, x, b, A, X, B, copy_interior);
287 },
288 py::arg("ess_tdof_list"), py::arg("x"), py::arg("b"), py::arg("A"),
289 py::arg("X"), py::arg("B"), py::arg("copy_interior") = 0,
290 "Forms the linear system AX=B, applying boundary conditions and other transformations.")
291 .def("FormSystemMatrix",
292 [](BilinearForm &self, const Array<int> &ess_tdof_list, OperatorHandle &A) {
293 self.FormSystemMatrix(ess_tdof_list, A);
294 },
295 py::arg("ess_tdof_list"), py::arg("A"),
296 "Forms the system matrix A, applying necessary transformations.")
297 .def("RecoverFEMSolution", py::overload_cast<const Vector&, const Vector&, Vector&>(&BilinearForm::RecoverFEMSolution),
298 py::arg("X"), py::arg("b"), py::arg("x"),
299 "Recovers the full FE solution vector after solving a linear system.")
300
301 // --- Accessor Methods ---
302 .def("FESpace", py::overload_cast<>(&BilinearForm::FESpace, py::const_), py::return_value_policy::reference_internal,
303 "Returns a pointer to the associated FiniteElementSpace.")
304 .def("SpMat", py::overload_cast<>(&BilinearForm::SpMat), py::return_value_policy::reference_internal,
305 "Returns a reference to the internal sparse matrix.")
306 .def("Update", &BilinearForm::Update, py::arg("nfes") = nullptr,
307 "Update the BilinearForm after the FE space has changed.");
308
309 // You will also need to bind the OperatorHandle and DiagonalPolicy enum
310 // if you haven't already. Example for DiagonalPolicy:
311 py::enum_<mfem::Matrix::DiagonalPolicy>(mfem_submodule, "DiagonalPolicy")
312 .value("DIAG_ZERO", mfem::Matrix::DiagonalPolicy::DIAG_ZERO)
313 .value("DIAG_ONE", mfem::Matrix::DiagonalPolicy::DIAG_ONE)
314 .value("DIAG_KEEP", mfem::Matrix::DiagonalPolicy::DIAG_KEEP)
315 .export_values();
316}
317
318// Helper function to bind the AssemblyLevel enum
319void bind_assembly_level_enum(py::module &m) {
320 using namespace mfem;
321 py::enum_<AssemblyLevel>(m, "AssemblyLevel")
322 .value("LEGACY", AssemblyLevel::LEGACY)
323 .value("FULL", AssemblyLevel::FULL)
324 .value("ELEMENT", AssemblyLevel::ELEMENT)
325 .value("PARTIAL", AssemblyLevel::PARTIAL)
326 .value("NONE", AssemblyLevel::NONE)
327 .export_values();
328}
329
330// Main function to register MixedBilinearForm
331void register_mixed_bilinear_form_bindings(py::module &mfem_submodule) {
332 using namespace mfem;
333
334 // Bind the mfem::MixedBilinearForm class, inheriting from mfem::Matrix
335 // No trampoline is needed because this is a concrete class.
336 py::class_<MixedBilinearForm, Matrix>(mfem_submodule, "MixedBilinearForm")
337
338 // --- Constructor ---
339 .def(py::init<FiniteElementSpace *, FiniteElementSpace *>(),
340 py::arg("trial_fespace"), py::arg("test_fespace"),
341 // Keep alive policies ensure the FE space objects are not garbage
342 // collected while the MixedBilinearForm is still using them.
343 py::keep_alive<1, 2>(), py::keep_alive<1, 3>(),
344 "Constructs a mixed bilinear form on the given trial and test FE spaces.")
345
346 // --- Setup Methods ---
347 .def("SetAssemblyLevel", &MixedBilinearForm::SetAssemblyLevel, py::arg("assembly_level"),
348 "Set the assembly level (e.g., LEGACY, FULL, PARTIAL, NONE).")
349
350 // --- Integrator Methods ---
351 .def("AddDomainIntegrator", py::overload_cast<BilinearFormIntegrator *>(&MixedBilinearForm::AddDomainIntegrator),
352 py::arg("bfi"), py::keep_alive<1, 2>(),
353 "Adds a domain integrator to the form.")
354 .def("AddBoundaryIntegrator", py::overload_cast<BilinearFormIntegrator *>(&MixedBilinearForm::AddBoundaryIntegrator),
355 py::arg("bfi"), py::keep_alive<1, 2>(),
356 "Adds a boundary integrator to the form.")
357 .def("AddInteriorFaceIntegrator", &MixedBilinearForm::AddInteriorFaceIntegrator,
358 py::arg("bfi"), py::keep_alive<1, 2>(),
359 "Adds an interior face integrator.")
360 .def("AddBdrFaceIntegrator", py::overload_cast<BilinearFormIntegrator *>(&MixedBilinearForm::AddBdrFaceIntegrator),
361 py::arg("bfi"), py::keep_alive<1, 2>(),
362 "Adds a boundary face integrator.")
363 .def("AddTraceFaceIntegrator", &MixedBilinearForm::AddTraceFaceIntegrator,
364 py::arg("bfi"), py::keep_alive<1, 2>(),
365 "Adds a trace face integrator.")
366
367
368 // --- Assembly and System Formulation ---
369 .def("Assemble", &MixedBilinearForm::Assemble, py::arg("skip_zeros") = 1,
370 "Assembles the mixed bilinear form into a sparse matrix.")
371 .def("FormRectangularSystemMatrix",
372 [](MixedBilinearForm &self, const Array<int> &trial_tdof_list, const Array<int> &test_tdof_list, OperatorHandle &A) {
373 self.FormRectangularSystemMatrix(trial_tdof_list, test_tdof_list, A);
374 },
375 py::arg("trial_tdof_list"), py::arg("test_tdof_list"), py::arg("A"),
376 "Forms the rectangular system matrix A, applying necessary transformations.")
377 .def("FormRectangularLinearSystem",
378 [](MixedBilinearForm &self, const Array<int> &trial_tdof_list, const Array<int> &test_tdof_list,
379 Vector &x, Vector &b, OperatorHandle &A, Vector &X, Vector &B) {
380 self.FormRectangularLinearSystem(trial_tdof_list, test_tdof_list, x, b, A, X, B);
381 },
382 py::arg("trial_tdof_list"), py::arg("test_tdof_list"), py::arg("x"), py::arg("b"),
383 py::arg("A"), py::arg("X"), py::arg("B"),
384 "Forms the rectangular linear system AX=B.")
385
386 // --- Accessor Methods ---
387 .def("TrialFESpace", py::overload_cast<>(&MixedBilinearForm::TrialFESpace, py::const_),
388 py::return_value_policy::reference_internal,
389 "Returns a pointer to the associated trial FiniteElementSpace.")
390 .def("TestFESpace", py::overload_cast<>(&MixedBilinearForm::TestFESpace, py::const_),
391 py::return_value_policy::reference_internal,
392 "Returns a pointer to the associated test FiniteElementSpace.")
393 .def("SpMat", py::overload_cast<>(&MixedBilinearForm::SpMat),
394 py::return_value_policy::reference_internal,
395 "Returns a reference to the internal sparse matrix.")
396 .def("Update", &MixedBilinearForm::Update,
397 "Update the MixedBilinearForm after the FE spaces have changed.");
398}
399
400// This function can be called from your main registration function.
401void register_mesh_bindings(py::module &mfem_submodule) {
402 using namespace mfem;
403 // Bind the mfem::Mesh class. No trampoline needed.
404 py::class_<Mesh>(mfem_submodule, "Mesh")
405
406 // --- Constructors & Loading ---
407 // Default constructor for creating an empty mesh object
408 .def(py::init<>())
409 // Constructor to load from a file path
410 .def(py::init<const std::string &, int, int, bool>(),
411 py::arg("filename"), py::arg("generate_edges") = 0,
412 py::arg("refine") = 1, py::arg("fix_orientation") = true)
413 // Static factory method for a more Pythonic loading interface
414 .def_static("LoadFromFile", &Mesh::LoadFromFile,
415 py::arg("filename"), py::arg("generate_edges") = 0,
416 py::arg("refine") = 1, py::arg("fix_orientation") = true,
417 "Creates a mesh by reading a file.")
418
419 // --- Basic Properties & Stats ---
420 .def_property_readonly("dim", &Mesh::Dimension)
421 .def_property_readonly("space_dim", &Mesh::SpaceDimension)
422 .def_property_readonly("nv", &Mesh::GetNV, "Number of Vertices")
423 .def_property_readonly("ne", &Mesh::GetNE, "Number of Elements")
424 .def_property_readonly("nbe", &Mesh::GetNBE, "Number of Boundary Elements")
425 .def_property_readonly("n_edges", &Mesh::GetNEdges)
426 .def_property_readonly("n_faces", &Mesh::GetNFaces)
427 .def_readonly("attributes", &Mesh::attributes)
428 .def_readonly("bdr_attributes", &Mesh::bdr_attributes)
429 .def("GetBoundingBox",
430 [](Mesh &self, int ref) {
431 Vector min, max;
432 self.GetBoundingBox(min, max, ref);
433 // Here you might want to return a tuple of numpy arrays instead
434 return py::make_tuple(min, max);
435 }, py::arg("ref") = 2,
436 "Returns the min and max corners of the mesh bounding box.")
437
438 // --- Connectivity Data ---
439 .def("GetElementVertices",
440 [](const Mesh &self, int i) {
441 Array<int> v;
442 self.GetElementVertices(i, v);
443 return v;
444 }, py::arg("i"), "Returns a list of vertex indices for element i.")
445 .def("GetBdrElementVertices",
446 [](const Mesh &self, int i) {
447 Array<int> v;
448 self.GetBdrElementVertices(i, v);
449 return v;
450 }, py::arg("i"), "Returns a list of vertex indices for boundary element i.")
451 .def("GetFaceElements",
452 [](const Mesh &self, int i) {
453 int e1, e2;
454 self.GetFaceElements(i, &e1, &e2);
455 return py::make_tuple(e1, e2);
456 }, py::arg("face_idx"), "Returns a tuple of the two elements sharing a face.")
457 .def("GetBdrElementFace",
458 [](const Mesh &self, int i) {
459 int f, o;
460 self.GetBdrElementFace(i, &f, &o);
461 return py::make_tuple(f, o);
462 }, py::arg("bdr_elem_idx"), "Returns the face index and orientation for a boundary element.")
463 .def("ElementToEdgeTable", &Mesh::ElementToEdgeTable, py::return_value_policy::reference_internal)
464 .def("ElementToFaceTable", &Mesh::ElementToFaceTable, py::return_value_policy::reference_internal)
465
466 // --- Coordinate Transformations & Curvature ---
467 .def("GetNodes", py::overload_cast<>(&Mesh::GetNodes, py::const_), py::return_value_policy::reference_internal,
468 "Returns the GridFunction for the mesh nodes (if any).")
469 .def("SetCurvature", &Mesh::SetCurvature, py::arg("order"), py::arg("discontinuous") = false,
470 py::arg("space_dim") = -1, py::arg("ordering") = 1,
471 "Set the curvature of the mesh, creating a high-order nodal GridFunction.")
472 // Use py::overload_cast to resolve ambiguity for overloaded methods
473 .def("GetElementTransformation", py::overload_cast<int>(&Mesh::GetElementTransformation), py::arg("i"),
474 py::return_value_policy::reference_internal)
475 .def("GetFaceElementTransformations", py::overload_cast<int, int>(&Mesh::GetFaceElementTransformations), py::arg("i"),
476 py::arg("mask")=31, py::return_value_policy::reference_internal)
477
478 // --- I/O & Visualization ---
479 .def("Save", &Mesh::Save, py::arg("filename"), py::arg("precision") = 16);
480
481 // Bind the VTKFormat enum used in PrintVTU
482 py::enum_<VTKFormat>(mfem_submodule, "VTKFormat")
483 .value("ASCII", VTKFormat::ASCII)
484 .value("BINARY", VTKFormat::BINARY)
485 .value("BINARY32", VTKFormat::BINARY32)
486 .export_values();
487}
488
489void register_table_bindings(pybind11::module &mfem_submodule) {
490 using namespace mfem;
491 // Bind mfem::Table first, as it's a return type for some Mesh methods.
492 py::class_<Table>(mfem_submodule, "Table")
493 .def("GetRow", [](const Table &self, int row) {
494 Array<int> row_data;
495 self.GetRow(row, row_data);
496 // pybind11 will automatically convert Array<int> to a Python list
497 return row_data;
498 }, py::arg("row"), "Get a row of the table as a list.")
499 .def("RowSize", &Table::RowSize, py::arg("row"), "Get the number of entries in a specific row.")
500 .def_property_readonly("height", &Table::Size, "Number of rows in the table.")
501 .def_property_readonly("width", &Table::Width, "Number of columns in the table.")
502 .def("__len__", &Table::Size)
503 .def("__getitem__", [](const Table &self, int row) {
504 if (row < 0 || row >= self.Size()) {
505 throw py::index_error("Row index out of bounds");
506 }
507 Array<int> row_data;
508 self.GetRow(row, row_data);
509 return row_data;
510 })
511 .def("__repr__", [](const Table &self) {
512 return "<mfem.Table (" + std::to_string(self.Size()) + "x" +
513 std::to_string(self.Width()) + ")>";
514 });
515}
516
517void register_finite_element_collection_bindings(pybind11::module &mfem_submodule) {
518 using namespace mfem;
519 py::class_<FiniteElementCollection>(mfem_submodule, "FiniteElementCollection")
520 // No constructor for abstract base classes
521 .def("Name", &FiniteElementCollection::Name)
522 .def("GetOrder", &FiniteElementCollection::GetOrder);
523}
524
525// Binds the mfem::BasisType class and its internal anonymous enum values
526// as static properties of a Python class. This should be called before
527// binding any class that uses BasisType values in its constructor.
528void register_basis_type_bindings(py::module &m) {
529 using namespace mfem;
530 py::class_<BasisType> basis_type(m, "BasisType", "Possible basis types.");
531
532 basis_type.def_property_readonly_static("Invalid", [](py::object) { return BasisType::Invalid; });
533 basis_type.def_property_readonly_static("GaussLegendre", [](py::object) { return BasisType::GaussLegendre; });
534 basis_type.def_property_readonly_static("GaussLobatto", [](py::object) { return BasisType::GaussLobatto; });
535 basis_type.def_property_readonly_static("Positive", [](py::object) { return BasisType::Positive; });
536 basis_type.def_property_readonly_static("OpenUniform", [](py::object) { return BasisType::OpenUniform; });
537 basis_type.def_property_readonly_static("ClosedUniform", [](py::object) { return BasisType::ClosedUniform; });
538 basis_type.def_property_readonly_static("OpenHalfUniform", [](py::object) { return BasisType::OpenHalfUniform; });
539 basis_type.def_property_readonly_static("Serendipity", [](py::object) { return BasisType::Serendipity; });
540 basis_type.def_property_readonly_static("ClosedGL", [](py::object) { return BasisType::ClosedGL; });
541 basis_type.def_property_readonly_static("IntegratedGLL", [](py::object) { return BasisType::IntegratedGLL; });
542}
543
544
545
547 using namespace mfem;
548 py::class_<H1_FECollection, FiniteElementCollection>(m, "H1_FECollection")
549 .def(py::init([](int p, int dim) {
550 // The lambda explicitly calls the constructor, avoiding the
551 // default argument parsing issue.
552 return new H1_FECollection(p, dim);
553 }),
554 py::arg("p"),
555 py::arg("dim") = 3,
556 "Constructs an H1 finite element collection.")
557 .def("GetBasisType", &H1_FECollection::GetBasisType);
558}
559
560
562 using namespace mfem;
563 py::class_<RT_FECollection, FiniteElementCollection>(m, "RT_FECollection")
564 .def(py::init<const int, const int>(),
565 py::arg("p"), py::arg("dim"),
566 "Constructs a Raviart-Thomas H(div)-conforming FE collection.")
567 .def("GetClosedBasisType", &RT_FECollection::GetClosedBasisType)
568 .def("GetOpenBasisType", &RT_FECollection::GetOpenBasisType);
569}
570
572 using namespace mfem;
573 py::class_<ND_FECollection, FiniteElementCollection>(m, "ND_FECollection")
574 .def(py::init<const int, const int>(),
575 py::arg("p"), py::arg("dim"),
576 "Constructs a Nedelec H(curl)-conforming FE collection.")
577 .def("GetClosedBasisType", &ND_FECollection::GetClosedBasisType)
578 .def("GetOpenBasisType", &ND_FECollection::GetOpenBasisType);
579}
580
581
582void register_finite_element_space_bindings(py::module &mfem_submodule) {
583 using namespace mfem;
584 // Bind dependent enums first
585 bind_ordering_enum(mfem_submodule);
586
587 // Bind the mfem::FiniteElementSpace class
588 py::class_<FiniteElementSpace>(mfem_submodule, "FiniteElementSpace")
589 // --- Constructors ---
590 .def(py::init<Mesh *, const FiniteElementCollection *, int, int>(),
591 py::arg("mesh"), py::arg("fec"), py::arg("vdim") = 1, py::arg("ordering") = Ordering::byNODES,
592 // Keep alive policies prevent the mesh and fec from being
593 // garbage collected by Python while the C++ FESpace object exists.
594 py::keep_alive<1, 2>(), py::keep_alive<1, 3>())
595
596 // --- Core Properties & Stats ---
597 .def_property_readonly("ndofs", &FiniteElementSpace::GetNDofs, "Number of local scalar degrees of freedom.")
598 .def_property_readonly("vdim", &FiniteElementSpace::GetVDim, "Vector dimension of the space.")
599 .def_property_readonly("vsize", &FiniteElementSpace::GetVSize, "Total number of local vector degrees of freedom.")
600 .def_property_readonly("true_vsize", &FiniteElementSpace::GetTrueVSize, "Number of true (conforming) vector degrees of freedom.")
601 .def("GetMesh", &FiniteElementSpace::GetMesh, py::return_value_policy::reference_internal, "Get the associated Mesh.")
602 .def("FEColl", &FiniteElementSpace::FEColl, py::return_value_policy::reference_internal, "Get the associated FiniteElementCollection.")
603
604 // --- DOF Management ---
605 .def("GetElementDofs",
606 [](const FiniteElementSpace &self, int i) {
607 Array<int> dofs;
608 self.GetElementDofs(i, dofs);
609 return dofs;
610 }, py::arg("elem_idx"), "Get the local scalar DOFs for a given element.")
611 .def("GetElementVDofs",
612 [](const FiniteElementSpace &self, int i) {
613 Array<int> vdofs;
614 self.GetElementVDofs(i, vdofs);
615 return vdofs;
616 }, py::arg("elem_idx"), "Get the local vector DOFs for a given element.")
617 .def("GetBdrElementDofs",
618 [](const FiniteElementSpace &self, int i) {
619 Array<int> dofs;
620 self.GetBdrElementDofs(i, dofs);
621 return dofs;
622 }, py::arg("bdr_elem_idx"), "Get the local scalar DOFs for a given boundary element.")
623 .def("GetEssentialVDofs",
624 [](const FiniteElementSpace &self, const Array<int> &bdr_attr_is_ess, int component) {
625 Array<int> ess_vdofs;
626 self.GetEssentialVDofs(bdr_attr_is_ess, ess_vdofs, component);
627 return ess_vdofs;
628 }, py::arg("bdr_attr_is_ess"), py::arg("component") = -1,
629 "Get a list of essential (Dirichlet) vector DOFs based on boundary attributes.")
630 .def("GetEssentialTrueDofs",
631 [](const FiniteElementSpace &self, const Array<int> &bdr_attr_is_ess, int component) {
632 Array<int> ess_tdof_list;
633 self.GetEssentialTrueDofs(bdr_attr_is_ess, ess_tdof_list, component);
634 return ess_tdof_list;
635 }, py::arg("bdr_attr_is_ess"), py::arg("component") = -1,
636 "Get a list of essential true (conforming) DOFs for use in linear systems.")
637
638 // --- Updating & Operators ---
639 .def("Update", &FiniteElementSpace::Update, py::arg("want_transform") = true,
640 "Update the FE space after the mesh has been modified (e.g., refined).")
641 .def("GetUpdateOperator", py::overload_cast<>(&FiniteElementSpace::GetUpdateOperator),
642 py::return_value_policy::reference_internal,
643 "Get the operator that maps GridFunctions from the old space to the new space after an Update.")
644 .def("GetProlongationMatrix", &FiniteElementSpace::GetProlongationMatrix,
645 py::return_value_policy::reference_internal, "Get the P operator (true DOFs to local DOFs).")
646 .def("GetRestrictionOperator", &FiniteElementSpace::GetRestrictionOperator,
647 py::return_value_policy::reference_internal, "Get the R operator (local DOFs to true DOFs).")
648
649 .def("__repr__", [](const FiniteElementSpace &fes) {
650 std::stringstream ss;
651 ss << "<mfem.FiniteElementSpace with " << fes.GetNDofs() << " DOFs, vdim=" << fes.GetVDim() << ">";
652 return ss.str();
653 });
654}
655
656void bind_ordering_enum(py::module &mfem_submodule) {
657 using namespace mfem;
658 py::enum_<Ordering::Type>(mfem_submodule, "Ordering")
659 .value("byNODES", Ordering::byNODES)
660 .value("byVDIM", Ordering::byVDIM)
661 .export_values();
662}
663
666 using namespace mfem;
667 // Assumes that Vector, FiniteElementSpace, Coefficient, VectorCoefficient,
668 // IntegrationRule, and ElementTransformation are already bound.
669
670 py::class_<GridFunction, Vector>(m, "GridFunction")
671 .def(py::init<FiniteElementSpace *>(), py::arg("fespace"),
672 py::keep_alive<1, 2>(), // Keep FE space alive
673 "Construct a GridFunction on a given FiniteElementSpace.")
674
675 .def(py::init<FiniteElementSpace *, real_t *>(),
676 py::arg("fespace"), py::arg("data"),
677 py::keep_alive<1, 2>(),
678 "Construct a GridFunction using previously allocated data.")
679
680 .def("FESpace", py::overload_cast<>(&GridFunction::FESpace, py::const_),
681 py::return_value_policy::reference_internal,
682 "Returns the associated FiniteElementSpace.")
683 .def("Update", &GridFunction::Update,
684 "Update the GridFunction after its FE space has been modified.")
685 .def("SetSpace", &GridFunction::SetSpace, py::arg("fespace"),
686 py::keep_alive<1, 2>(), "Associate a new FE space with the GridFunction.")
687
688 // --- Projection Methods ---
689 .def("ProjectCoefficient",
690 py::overload_cast<Coefficient &>(&GridFunction::ProjectCoefficient),
691 py::arg("coeff"),
692 "Project a scalar Coefficient onto the GridFunction.")
693 .def("ProjectCoefficient",
694 py::overload_cast<VectorCoefficient &>(&GridFunction::ProjectCoefficient),
695 py::arg("vcoeff"),
696 "Project a vector Coefficient onto the GridFunction.")
697 .def("ProjectBdrCoefficient",
698 py::overload_cast<Coefficient &, const Array<int> &>(&GridFunction::ProjectBdrCoefficient),
699 py::arg("coeff"), py::arg("attr"),
700 "Project a scalar Coefficient onto the boundary degrees of freedom.")
701 .def("ProjectBdrCoefficient",
702 py::overload_cast<VectorCoefficient &, const Array<int> &>(&GridFunction::ProjectBdrCoefficient),
703 py::arg("vcoeff"), py::arg("attr"),
704 "Project a vector Coefficient onto the boundary degrees of freedom.")
705
706 // --- Evaluation Methods ---
707 .def("GetValue", py::overload_cast<ElementTransformation &, const IntegrationPoint &, int, Vector *>(&GridFunction::GetValue, py::const_),
708 py::arg("T"), py::arg("ip"), py::arg("comp") = 0, py::arg("tr") = nullptr,
709 "Get the scalar value at a point described by an ElementTransformation.")
710 .def("GetVectorValue",
711 [](const GridFunction &self, ElementTransformation &T, const IntegrationPoint &ip) {
712 Vector val;
713 self.GetVectorValue(T, ip, val);
714 return val;
715 },
716 py::arg("T"), py::arg("ip"),
717 "Get the vector value at a point described by an ElementTransformation.")
718 .def("GetValues",
719 [](const GridFunction &self, ElementTransformation &T, const IntegrationRule &ir, int comp) {
720 Vector vals;
721 self.GetValues(T, ir, vals, comp);
722 return vals;
723 },
724 py::arg("T"), py::arg("ir"), py::arg("comp") = 0,
725 "Get scalar values at all points of an IntegrationRule.")
726 .def("GetVectorValues",
727 [](const GridFunction &self, ElementTransformation &T, const IntegrationRule &ir) {
728 DenseMatrix vals;
729 self.GetVectorValues(T, ir, vals);
730 return vals; // pybind11 handles DenseMatrix
731 },
732 py::arg("T"), py::arg("ir"),
733 "Get vector values at all points of an IntegrationRule.")
734
735 // --- True DOF (constrained system) Methods ---
736 .def("GetTrueDofs", &GridFunction::GetTrueDofs, py::arg("tv"),
737 "Extract the true-dofs from the GridFunction.")
738 .def("SetFromTrueDofs", &GridFunction::SetFromTrueDofs, py::arg("tv"),
739 "Set the GridFunction from a true-dof vector.")
740
741 // --- I/O ---
742 .def("Save", py::overload_cast<const char *, int>(&GridFunction::Save, py::const_),
743 py::arg("fname"), py::arg("precision") = 16)
744 .def("__repr__", [](const GridFunction &gf) {
745 std::stringstream ss;
746 ss << "<mfem.GridFunction with " << gf.Size() << " DOFs>";
747 return ss.str();
748 })
749 .def("as_numpy", [](const GridFunction &self) {
750 // Convert the GridFunction to a numpy array
751 return py::array_t<real_t>(self.Size(), self.GetData());
752 }, "Convert the GridFunction to a numpy array.");
753}
754
755// This function registers the coefficient classes to the python module
756void register_coefficient_bindings(py::module &m) {
757 using namespace mfem;
758
759 // --- Bind abstract base classes using trampolines ---
760 py::class_<Coefficient, serif::pybind::PyCoefficient> coefficient(m, "Coefficient");
761 coefficient
762 .def(py::init<>())
763 .def("SetTime", &Coefficient::SetTime, py::arg("t"))
764 .def("GetTime", &Coefficient::GetTime)
765 .def("Eval", py::overload_cast<ElementTransformation &, const IntegrationPoint&>(&Coefficient::Eval),
766 "Evaluate the coefficient at a point in an element.",
767 py::arg("T"), py::arg("ip"));
768
769 py::class_<VectorCoefficient, serif::pybind::PyVectorCoefficient> vector_coefficient(m, "VectorCoefficient");
770 vector_coefficient
771 .def(py::init<int>(), py::arg("vdim"))
772 .def("SetTime", &VectorCoefficient::SetTime, py::arg("t"))
773 .def("GetTime", &VectorCoefficient::GetTime)
774 .def("GetVDim", &VectorCoefficient::GetVDim)
775 .def("Eval", py::overload_cast<Vector &, ElementTransformation &, const IntegrationPoint &>(&VectorCoefficient::Eval),
776 "Evaluate the vector coefficient at a point in an element.",
777 py::arg("V"), py::arg("T"), py::arg("ip"));
778
779 // --- Bind useful concrete classes ---
780
781 // ConstantCoefficient
782 py::class_<ConstantCoefficient, Coefficient>(m, "ConstantCoefficient")
783 .def(py::init<real_t>(), py::arg("c") = 1.0)
784 .def_readwrite("constant", &ConstantCoefficient::constant);
785
786 // FunctionCoefficient (allows using Python functions as coefficients)
787 py::class_<FunctionCoefficient, Coefficient>(m, "FunctionCoefficient")
788 .def(py::init<std::function<real_t(const Vector &)>>(),
789 py::arg("F"), "Create a coefficient from a Python function of space.")
790 .def(py::init<std::function<real_t(const Vector &, real_t)>>(),
791 py::arg("TDF"), "Create a coefficient from a Python function of space and time.");
792
793 // VectorConstantCoefficient
794 py::class_<VectorConstantCoefficient, VectorCoefficient>(m, "VectorConstantCoefficient")
795 .def(py::init<const Vector &>(), py::arg("v"));
796
797 // VectorFunctionCoefficient
798 py::class_<VectorFunctionCoefficient, VectorCoefficient>(m, "VectorFunctionCoefficient")
799 .def(py::init<int, std::function<void(const Vector &, Vector &)>>(),
800 py::arg("dim"), py::arg("F"), "Create a vector coefficient from a Python function of space.")
801 .def(py::init<int, std::function<void(const Vector &, real_t, Vector &)>>(),
802 py::arg("dim"), py::arg("TDF"), "Create a vector coefficient from a Python function of space and time.");
803
804 // GridFunctionCoefficient (useful for source terms that depend on the solution)
805 py::class_<GridFunctionCoefficient, Coefficient>(m, "GridFunctionCoefficient")
806 .def(py::init<>())
807 .def(py::init<const GridFunction *>(), py::arg("gf"))
808 .def("SetGridFunction", &GridFunctionCoefficient::SetGridFunction, py::arg("gf"))
809 .def("GetGridFunction", &GridFunctionCoefficient::GetGridFunction, py::return_value_policy::reference);
810}
811
813void register_intrule_bindings(py::module &m) {
814 using namespace mfem;
815 py::class_<IntegrationPoint>(m, "IntegrationPoint")
816 .def(py::init<>())
817 .def_readwrite("x", &IntegrationPoint::x)
818 .def_readwrite("y", &IntegrationPoint::y)
819 .def_readwrite("z", &IntegrationPoint::z)
820 .def_readwrite("weight", &IntegrationPoint::weight)
821 .def("__repr__", [](const IntegrationPoint &ip) {
822 std::stringstream ss;
823 ss << "IP(x=" << ip.x << ", y=" << ip.y << ", z=" << ip.z << ", w=" << ip.weight << ")";
824 return ss.str();
825 });
826
827 py::class_<IntegrationRule>(m, "IntegrationRule")
828 .def(py::init<>())
829 .def(py::init<int>(), py::arg("NumPoints"))
830 .def("GetNPoints", &IntegrationRule::GetNPoints)
831 .def("GetOrder", &IntegrationRule::GetOrder)
832 .def("__len__", &IntegrationRule::GetNPoints)
833 .def("__getitem__", [](const IntegrationRule &self, int i) {
834 if (i < 0 || i >= self.GetNPoints()) throw py::index_error();
835 return self.IntPoint(i);
836 })
837 .def("__iter__", [](const IntegrationRule &self) {
838 return py::make_iterator(self.begin(), self.end());
839 }, py::keep_alive<0, 1>());
840}
841
843void register_eltrans_bindings(py::module &m) {
844 using namespace mfem;
845 // Bind the base class. Users get pointers to this, but never create it.
846 // It is not abstract in the C++ sense, but we treat it as such for bindings.
847 py::class_<ElementTransformation> eltrans(m, "ElementTransformation");
848 eltrans
849 .def_readonly("Attribute", &ElementTransformation::Attribute)
850 .def_readonly("ElementNo", &ElementTransformation::ElementNo)
851 .def("SetIntPoint", &ElementTransformation::SetIntPoint, py::arg("ip"))
852 .def("Transform", py::overload_cast<const IntegrationPoint &, Vector &>(&ElementTransformation::Transform),
853 py::arg("ip"), py::arg("transip"))
854 .def("Weight", &ElementTransformation::Weight)
855 // Properties that return references to internal data should use reference_internal policy
856 .def_property_readonly("Jacobian", &ElementTransformation::Jacobian, py::return_value_policy::reference_internal)
857 .def_property_readonly("InverseJacobian", &ElementTransformation::InverseJacobian, py::return_value_policy::reference_internal)
858 .def_property_readonly("AdjugateJacobian", &ElementTransformation::AdjugateJacobian, py::return_value_policy::reference_internal);
859
860 // Bind IsoparametricTransformation, which is a concrete type of ElementTransformation
861 py::class_<IsoparametricTransformation, ElementTransformation>(m, "IsoparametricTransformation")
862 .def(py::init<>());
863
864 // Bind FaceElementTransformations, crucial for DG methods
865 py::class_<FaceElementTransformations, ElementTransformation>(m, "FaceElementTransformations")
866 .def(py::init<>())
867 .def_readonly("Elem1No", &FaceElementTransformations::Elem1No)
868 .def_readonly("Elem2No", &FaceElementTransformations::Elem2No)
869 .def_property_readonly("Elem1", [](FaceElementTransformations &self) { return self.Elem1; }, py::return_value_policy::reference)
870 .def_property_readonly("Elem2", [](FaceElementTransformations &self) { return self.Elem2; }, py::return_value_policy::reference)
871 .def("GetElement1IntPoint", &FaceElementTransformations::GetElement1IntPoint)
872 .def("GetElement2IntPoint", &FaceElementTransformations::GetElement2IntPoint);
873
874 // Bind the enum used for selecting transformations
875 py::enum_<FaceElementTransformations::ConfigMasks>(eltrans, "ConfigMasks")
876 .value("HAVE_ELEM1", FaceElementTransformations::HAVE_ELEM1)
877 .value("HAVE_ELEM2", FaceElementTransformations::HAVE_ELEM2)
878 .value("HAVE_LOC1", FaceElementTransformations::HAVE_LOC1)
879 .value("HAVE_LOC2", FaceElementTransformations::HAVE_LOC2)
880 .value("HAVE_FACE", FaceElementTransformations::HAVE_FACE)
881 .export_values();
882}
Defines pybind11 trampoline classes for mfem::Coefficient and mfem::VectorCoefficient.
Defines a pybind11 trampoline class for mfem::Matrix.
Defines a pybind11 trampoline class for mfem::Operator.
Trampoline class for mfem::Operator.
Definition PyOperator.h:83
void register_operator_bindings(py::module &mfem_submodule)
Definition bindings.cpp:53
void bind_ordering_enum(py::module &mfem_submodule)
Definition bindings.cpp:656
void register_mfem_bindings(py::module &mfem_submodule)
Definition bindings.cpp:21
void register_array_bindings(py::module &mfem_submodule)
Definition bindings.cpp:176
void register_RT_FECollection_bindings(py::module &m)
Definition bindings.cpp:561
void register_grid_function_bindings(py::module &m)
Binds mfem::GridFunction.
Definition bindings.cpp:665
void register_coefficient_bindings(py::module &m)
Definition bindings.cpp:756
void bind_assembly_level_enum(py::module &m)
Definition bindings.cpp:319
void register_bilinear_form_bindings(py::module &mfem_submodule)
Definition bindings.cpp:234
void register_ND_FECollection_bindings(py::module &m)
Definition bindings.cpp:571
void register_H1_FECollection_bindings(py::module &m)
Definition bindings.cpp:546
void register_matrix_bindings(py::module &mfem_submodule)
Definition bindings.cpp:111
void register_intrule_bindings(py::module &m)
Binds mfem::IntegrationPoint and mfem::IntegrationRule.
Definition bindings.cpp:813
void register_finite_element_space_bindings(py::module &mfem_submodule)
Definition bindings.cpp:582
void register_mixed_bilinear_form_bindings(py::module &mfem_submodule)
Definition bindings.cpp:331
void register_basis_type_bindings(py::module &m)
Definition bindings.cpp:528
void register_finite_element_collection_bindings(pybind11::module &mfem_submodule)
Registers mfem::FiniteElementCollection base class.
Definition bindings.cpp:517
void register_vector_bindings(py::module &mfem_submodule)
Definition bindings.cpp:148
void register_table_bindings(pybind11::module &mfem_submodule)
Registers mfem::Table.
Definition bindings.cpp:489
void register_eltrans_bindings(py::module &m)
Binds mfem::ElementTransformation and related classes.
Definition bindings.cpp:843
void register_mesh_bindings(py::module &mfem_submodule)
Definition bindings.cpp:401
Declares functions to register MFEM core library components with pybind11.