Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Use buffer protocol to make Mat compatible with numpy
  • Loading branch information
alxbilger committed Jun 19, 2026
commit 58b8bbcb28a6c28611ca24e679d7792096b720d0
38 changes: 32 additions & 6 deletions bindings/SofaTypes/src/SofaPython3/SofaTypes/Binding_Mat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "Binding_Mat.h"
#include <functional>
#include <pybind11/operators.h>
#include <pybind11/eigen.h>

#define BINDING_MAT_MAKE_NAME(R, C) \
std::string(std::string("Mat") + std::to_string(R) + "x" + std::to_string(C))
Expand Down Expand Up @@ -59,6 +60,31 @@ static void addMat(py::module & /*m*/, py::class_<Mat<R, C, double>> &p) {
typedef typename MatClass::Line Row;
p.def(py::init());
p.def(py::init<const MatClass &>());
p.def(py::init([](py::buffer b) {

/* Request a buffer descriptor from Python */
py::buffer_info info = b.request();

/* Some basic validation checks ... */
if (!info.item_type_is_equivalent_to<double>())
throw std::runtime_error(
"Incompatible format: expected a double array!");

if (info.ndim != 2)
throw std::runtime_error("Incompatible buffer dimension!");

return Mat<R, C, double>(static_cast<double*>(info.ptr));
}));
p.def_buffer([](Mat<R, C, double>& self) -> py::buffer_info {
return py::buffer_info(
self.elems.data(), // pointer
sizeof(double), // itemsize
py::format_descriptor<double>::format(), // format
2, // ndim
{static_cast<py::ssize_t>(R), static_cast<py::ssize_t>(C)}, // shape
{sizeof(double) * static_cast<py::ssize_t>(C), sizeof(double)} // strides (row-major)
);
});
p.def_property_readonly("rows", &MatClass::getNbLines);
p.def_property_readonly("cols", &MatClass::getNbCols);
p.def("clear", &MatClass::clear);
Expand Down Expand Up @@ -184,7 +210,7 @@ static void addMatProduct(py::module & /*m*/, py::class_<Mat<R_1, C_1, double>>
template <sofa::Size R, sofa::Size C> struct MATRIX {
static void addMat(py::module &m) {
typedef Mat<R, C, double> MatClass;
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(R, C).c_str());
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(R, C).c_str(), py::buffer_protocol());
::addMat(m, p);
}
};
Expand All @@ -203,7 +229,7 @@ template <> struct MATRIX<1, 1> {
// typedef typename MatClass::Line Row;
// typedef typename MatClass::Col Col;

py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(1, 1).c_str());
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(1, 1).c_str(), py::buffer_protocol());
p.def(py::init([](py::list l) {
MatClass *mat = new MatClass(sofa::type::NOINIT);
if (py::isinstance<py::list>(l[0])) // 2D array
Expand Down Expand Up @@ -233,7 +259,7 @@ template <> struct MATRIX<2, 2> {
typedef typename MatClass::Line Row;
// typedef typename MatClass::Col Col;

py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(2, 2).c_str());
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(2, 2).c_str(), py::buffer_protocol());
p.def(py::init<Row, Row>());
p.def(py::init([](py::list l) {
MatClass *mat = new MatClass(sofa::type::NOINIT);
Expand Down Expand Up @@ -264,7 +290,7 @@ template <> struct MATRIX<3, 3> {
typedef typename MatClass::Line Row;
// typedef typename MatClass::Col Col;

py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(3, 3).c_str());
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(3, 3).c_str(), py::buffer_protocol());
p.def(py::init<Row, Row, Row>());
p.def(py::init([](py::list l) {
MatClass *mat = new MatClass(sofa::type::NOINIT);
Expand Down Expand Up @@ -295,7 +321,7 @@ template <> struct MATRIX<4, 4> {
typedef typename MatClass::Line Row;
// typedef typename MatClass::Col Col;

py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(4, 4).c_str());
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(4, 4).c_str(), py::buffer_protocol());
p.def(py::init<Row, Row, Row, Row>());
p.def(py::init([](py::list l) {
MatClass *mat = new MatClass(sofa::type::NOINIT);
Expand Down Expand Up @@ -326,7 +352,7 @@ template <> struct MATRIX<3, 4> {
typedef typename MatClass::Line Row;
// typedef typename MatClass::Col Col;

py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(3, 4).c_str());
py::class_<MatClass> p(m, BINDING_MAT_MAKE_NAME(3, 4).c_str(), py::buffer_protocol());
p.def(py::init<Row, Row, Row>());
p.def(py::init([](py::list l) {
MatClass *mat = new MatClass(sofa::type::NOINIT);
Expand Down
22 changes: 22 additions & 0 deletions bindings/SofaTypes/tests/pyfiles/mat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import Sofa
from SofaTypes import Mat1x1, Mat2x2, Mat3x3, Vec2d

import numpy as np

class TestMaterialMatrix(unittest.TestCase):

def test_Constructors(self):
Expand Down Expand Up @@ -62,3 +64,23 @@ def test_Operators(self):
m_test /= 2.0
expected_inplace_div = Mat2x2([[0.5, 1.0], [1.5, 2.0]])
self.assertEqual(m_test, expected_inplace_div)

def test_to_numpy(self):
m1 = Mat2x2([[1.0, 2.0], [3.0, 4.0]])
n1 = np.array(m1, copy = False) #n1 is a view on m1
n1[0, 0] = 9.0
self.assertEqual(m1[0][0], 9.0)

m2 = Mat2x2([[5.0, 6.0], [7.0, 8.0]])
n2 = np.array(m2, copy = False)

prod = n1 @ n2
expected_prod = np.array([[59.0, 70.0], [43.0, 50.0]])
np.testing.assert_array_equal(prod, expected_prod)

m3_prod = Mat2x2(prod)

self.assertEqual(m3_prod[0][0], prod[0,0])
self.assertEqual(m3_prod[0][1], prod[0,1])
self.assertEqual(m3_prod[1][0], prod[1,0])
self.assertEqual(m3_prod[1][1], prod[1,1])