diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fe9399a8..a231d2e2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,15 @@ Changelog ========= + +4.1.4 - 2025-11-12 +------------------ + +**Bug fix:** + +- Fixed :meth:`CategoricalMatrix.transpose_matvec` to operate on read-only buffers as well. + + 4.1.3 - 2025-10-14 ------------------ diff --git a/src/tabmat/ext/cat_split_helpers-tmpl.cpp b/src/tabmat/ext/cat_split_helpers-tmpl.cpp index df7a21c6..af5793cc 100644 --- a/src/tabmat/ext/cat_split_helpers-tmpl.cpp +++ b/src/tabmat/ext/cat_split_helpers-tmpl.cpp @@ -5,7 +5,7 @@ template void _transpose_matvec_${type}( Int n_rows, - Int* indices, + const Int* indices, F* other, F* res, Int res_size diff --git a/src/tabmat/ext/categorical.pyx b/src/tabmat/ext/categorical.pyx index be97844b..5852fc0e 100644 --- a/src/tabmat/ext/categorical.pyx +++ b/src/tabmat/ext/categorical.pyx @@ -16,12 +16,12 @@ ctypedef fused win_numeric: long long cdef extern from "cat_split_helpers.cpp": - void _transpose_matvec_all_rows_fast[Int, F](Int, Int*, F*, F*, Int) - void _transpose_matvec_all_rows_complex[Int, F](Int, Int*, F*, F*, Int, bool) + void _transpose_matvec_all_rows_fast[Int, F](Int, const int*, F*, F*, Int) + void _transpose_matvec_all_rows_complex[Int, F](Int, const int*, F*, F*, Int, bool) def transpose_matvec_fast( - int[:] indices, + const int[:] indices, floating[:] other, int n_cols, dtype, @@ -68,7 +68,7 @@ def transpose_matvec_fast( def transpose_matvec_complex( - int[:] indices, + const int[:] indices, floating[:] other, int n_cols, dtype, diff --git a/tests/test_transpose_matvec.py b/tests/test_transpose_matvec.py new file mode 100644 index 00000000..ef8b3b06 --- /dev/null +++ b/tests/test_transpose_matvec.py @@ -0,0 +1,24 @@ +import narwhals as nw +import numpy as np +import pandas as pd + +import tabmat as tm + + +def test_transpose_matvec_does_not_crash(): + # With high n_categories CategoricalMatrix.indices is read-only. + # As a result, transpose_matvec method crashed with "buffer + # source array is read-only". + + n = 797_586 + n_categories = 58_059 + categories = [f"cat[{i}]" for i in range(n_categories)] + indices = np.linspace(0, n_categories - 1, n).round().astype(int) + cat_vec = pd.Series(pd.Categorical.from_codes(indices, categories=categories)) + cat_vec_nw = nw.from_native(cat_vec, allow_series=True) + weights = np.ones(n) + cat_matrix_tm = tm.CategoricalMatrix(cat_vec_nw) + + result = cat_matrix_tm.transpose_matvec(weights) + + assert result is not None