Skip to content

Commit 5daf0e3

Browse files
committed
Extend the documentation of the nalgebra integration to discuss its likely surprising memory layout requirements.
1 parent 6d38eaf commit 5daf0e3

File tree

4 files changed

+64
-1
lines changed

4 files changed

+64
-1
lines changed

src/array.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,8 @@ where
879879
{
880880
/// Try to convert this array into a [`nalgebra::MatrixView`] using the given shape and strides.
881881
///
882+
/// See [`PyReadonlyArray::try_as_matrix`] for a discussion of the memory layout requirements.
883+
///
882884
/// # Safety
883885
///
884886
/// Calling this method invalidates all exclusive references to the internal data, e.g. `ArrayViewMut` or `MatrixSliceMut`.
@@ -901,6 +903,8 @@ where
901903

902904
/// Try to convert this array into a [`nalgebra::MatrixViewMut`] using the given shape and strides.
903905
///
906+
/// See [`PyReadonlyArray::try_as_matrix`] for a discussion of the memory layout requirements.
907+
///
904908
/// # Safety
905909
///
906910
/// Calling this method invalidates all other references to the internal data, e.g. `ArrayView`, `MatrixSlice`, `ArrayViewMut` or `MatrixSliceMut`.

src/borrow/mod.rs

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

src/untyped_array.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,26 @@ fn matrix_from_numpy() {
434434
assert!(matrix.is_none());
435435
});
436436

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

0 commit comments

Comments
 (0)