Skip to content

Commit 8df3a2a

Browse files
committed
Extend the documentation of the nalgebra integration to discuss its likely surprising memory layout requirements.
1 parent 5e0793e commit 8df3a2a

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

src/array.rs

+4
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,8 @@ where
989989

990990
/// Try to convert this array into a [`nalgebra::MatrixView`] using the given shape and strides.
991991
///
992+
/// See [`PyReadonlyArray::try_as_matrix`] for a discussed of the memory layout requirements.
993+
///
992994
/// # Safety
993995
///
994996
/// Calling this method invalidates all exclusive references to the internal data, e.g. `ArrayViewMut` or `MatrixSliceMut`.
@@ -1011,6 +1013,8 @@ where
10111013

10121014
/// Try to convert this array into a [`nalgebra::MatrixViewMut`] using the given shape and strides.
10131015
///
1016+
/// See [`PyReadonlyArray::try_as_matrix`] for a discussed of the memory layout requirements.
1017+
///
10141018
/// # Safety
10151019
///
10161020
/// Calling this method invalidates all other references to the internal data, e.g. `ArrayView`, `MatrixSlice`, `ArrayViewMut` or `MatrixSliceMut`.

src/borrow/mod.rs

+39
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,43 @@ where
278278
D: Dimension,
279279
{
280280
/// Try to convert this array into a [`nalgebra::MatrixView`] using the given shape and strides.
281+
///
282+
/// Note that nalgebra's types default to Fortan/column-major standard strides whereas NumPy creates C/row-major strides by default.
283+
/// Furthermore, array views created by slicing into existing arrays will often have non-standard strides.
284+
///
285+
/// If you do not fully control the memory layout of a given array, e.g. at your API entry points,
286+
/// it can be useful to opt into nalgebra's support for [dynamic strides][nalgebra::Dyn], for example
287+
///
288+
/// ```rust
289+
/// # use pyo3::prelude::*;
290+
/// use pyo3::py_run;
291+
/// use numpy::{get_array_module, PyReadonlyArray2};
292+
/// use nalgebra::{MatrixView, Const, Dyn};
293+
///
294+
/// #[pyfunction]
295+
/// fn sum_standard_layout<'py>(py: Python<'py>, array: PyReadonlyArray2<'py, f64>) -> Option<f64> {
296+
/// let matrix: Option<MatrixView<f64, Const<2>, Const<2>>> = array.try_as_matrix();
297+
/// matrix.map(|matrix| matrix.sum())
298+
/// }
299+
///
300+
/// #[pyfunction]
301+
/// fn sum_dynamic_strides<'py>(py: Python<'py>, array: PyReadonlyArray2<'py, f64>) -> Option<f64> {
302+
/// let matrix: Option<MatrixView<f64, Const<2>, Const<2>, Dyn, Dyn>> = array.try_as_matrix();
303+
/// matrix.map(|matrix| matrix.sum())
304+
/// }
305+
///
306+
/// Python::with_gil(|py| {
307+
/// let np = py.eval("__import__('numpy')", None, None).unwrap();
308+
/// let sum_standard_layout = wrap_pyfunction!(sum_standard_layout)(py).unwrap();
309+
/// let sum_dynamic_strides = wrap_pyfunction!(sum_dynamic_strides)(py).unwrap();
310+
///
311+
/// py_run!(py, np sum_standard_layout, r"assert sum_standard_layout(np.ones((2, 2), order='F')) == 4.");
312+
/// py_run!(py, np sum_standard_layout, r"assert sum_standard_layout(np.ones((2, 2, 2))[:,:,0]) is None");
313+
///
314+
/// py_run!(py, np sum_dynamic_strides, r"assert sum_dynamic_strides(np.ones((2, 2), order='F')) == 4.");
315+
/// py_run!(py, np sum_dynamic_strides, r"assert sum_dynamic_strides(np.ones((2, 2, 2))[:,:,0]) == 4.");
316+
/// });
317+
/// ```
281318
#[doc(alias = "nalgebra")]
282319
pub fn try_as_matrix<R, C, RStride, CStride>(
283320
&self,
@@ -463,6 +500,8 @@ where
463500
D: Dimension,
464501
{
465502
/// Try to convert this array into a [`nalgebra::MatrixViewMut`] using the given shape and strides.
503+
///
504+
/// See [`PyReadonlyArray::try_as_matrix`] for a discussed of the memory layout requirements.
466505
#[doc(alias = "nalgebra")]
467506
pub fn try_as_matrix_mut<R, C, RStride, CStride>(
468507
&self,

src/untyped_array.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::cold;
1313
use crate::dtype::PyArrayDescr;
1414
use crate::npyffi;
1515

16-
/// A safe, untyped wrapper for NumPy's [`ndarray`][ndarray] class.
16+
/// A safe, untyped wrapper for NumPy's [`ndarray`] class.
1717
///
1818
/// Unlike [`PyArray<T,D>`][crate::PyArray], this type does not constrain either element type `T` nor the dimensionality `D`.
1919
/// This can be useful to inspect function arguments, but it prevents operating on the elements without further downcasts.

tests/borrow.rs

+20
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,26 @@ fn matrix_from_numpy() {
433433
assert!(matrix.is_none());
434434
});
435435

436+
Python::with_gil(|py| {
437+
let array = numpy::pyarray![py, [[0, 1], [2, 3]], [[4, 5], [6, 7]]];
438+
let array: &PyArray2<i32> = py
439+
.eval("a[:,:,0]", Some([("a", array)].into_py_dict(py)), None)
440+
.unwrap()
441+
.downcast()
442+
.unwrap();
443+
let array = array.readonly();
444+
445+
let matrix: nalgebra::MatrixView<
446+
'_,
447+
i32,
448+
nalgebra::Const<2>,
449+
nalgebra::Const<2>,
450+
nalgebra::Dyn,
451+
nalgebra::Dyn,
452+
> = array.try_as_matrix().unwrap();
453+
assert_eq!(matrix, nalgebra::Matrix2::new(0, 2, 4, 6));
454+
});
455+
436456
Python::with_gil(|py| {
437457
let array = numpy::pyarray![py, [0, 1, 2], [3, 4, 5], [6, 7, 8]];
438458
let array = array.readonly();

0 commit comments

Comments
 (0)