Skip to content

Commit

Permalink
Adds some aliases to avoid breaking changes, and adds an example of h…
Browse files Browse the repository at this point in the history
…ow to write functionality with the new types
  • Loading branch information
akern40 committed Oct 14, 2024
1 parent fb098d3 commit 3156346
Show file tree
Hide file tree
Showing 15 changed files with 513 additions and 136 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ target/

# Editor settings
.vscode

# Apple details
**/.DS_Store
178 changes: 178 additions & 0 deletions examples/functions_and_traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//! Examples of how to write functions and traits that operate on `ndarray` types.
//!
//! `ndarray` has four kinds of array types that users may interact with:
//! 1. [`ArrayBase`], the owner of the layout that describes an array in memory;
//! this includes [`ndarray::Array`], [`ndarray::ArcArray`], [`ndarray::ArrayView`],
//! [`ndarray::RawArrayView`], and other variants.
//! 2. [`ArrayRef`], which represents a read-safe, uniquely-owned look at an array.
//! 3. [`RawRef`], which represents a read-unsafe, possibly-shared look at an array.
//! 4. [`LayoutRef`], which represents a look at an array's underlying structure,
//! but does not allow data reading of any kind.
//!
//! Below, we illustrate how to write functions and traits for most variants of these types.
use ndarray::{ArrayBase, ArrayRef, Data, DataMut, Dimension, LayoutRef, RawData, RawDataMut, RawRef};

/// Take an array with the most basic requirements.
///
/// This function takes its data as owning. It is very rare that a user will need to specifically
/// take a reference to an `ArrayBase`, rather than to one of the other four types.
#[rustfmt::skip]
fn takes_base_raw<S: RawData, D>(arr: ArrayBase<S, D>) -> ArrayBase<S, D>
{
// These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsRef`
takes_rawref(arr.as_ref()); // Caller uses `.as_ref`
takes_rawref_asref(&arr); // Implementor uses `.as_ref`
takes_layout(arr.as_ref()); // Caller uses `.as_ref`
takes_layout_asref(&arr); // Implementor uses `.as_ref`

arr
}

/// Similar to above, but allow us to read the underlying data.
#[rustfmt::skip]
fn takes_base_raw_mut<S: RawDataMut, D>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
{
// These skip from a possibly-raw array to `RawRef` and `LayoutRef`, and so must go through `AsMut`
takes_rawref_mut(arr.as_mut()); // Caller uses `.as_mut`
takes_rawref_asmut(&mut arr); // Implementor uses `.as_mut`
takes_layout_mut(arr.as_mut()); // Caller uses `.as_mut`
takes_layout_asmut(&mut arr); // Implementor uses `.as_mut`

arr
}

/// Now take an array whose data is safe to read.
#[allow(dead_code)]
fn takes_base<S: Data, D>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
{
// Raw call
arr = takes_base_raw(arr);

// No need for AsRef, since data is safe
takes_arrref(&arr);
takes_rawref(&arr);
takes_rawref_asref(&arr);
takes_layout(&arr);
takes_layout_asref(&arr);

arr
}

/// Now, an array whose data is safe to read and that we can mutate.
///
/// Notice that we include now a trait bound on `D: Dimension`; this is necessary in order
/// for the `ArrayBase` to dereference to an `ArrayRef` (or to any of the other types).
#[allow(dead_code)]
fn takes_base_mut<S: DataMut, D: Dimension>(mut arr: ArrayBase<S, D>) -> ArrayBase<S, D>
{
// Raw call
arr = takes_base_raw_mut(arr);

// No need for AsMut, since data is safe
takes_arrref_mut(&mut arr);
takes_rawref_mut(&mut arr);
takes_rawref_asmut(&mut arr);
takes_layout_mut(&mut arr);
takes_layout_asmut(&mut arr);

arr
}

/// Now for new stuff: we want to read (but not alter) any array whose data is safe to read.
///
/// This is probably the most common functionality that one would want to write.
/// As we'll see below, calling this function is very simple for `ArrayBase<S: Data, D>`.
fn takes_arrref<A, D>(arr: &ArrayRef<A, D>)
{
// No need for AsRef, since data is safe
takes_rawref(arr);
takes_rawref_asref(arr);
takes_layout(arr);
takes_layout_asref(arr);
}

/// Now we want any array whose data is safe to mutate.
///
/// **Importantly**, any array passed to this function is guaranteed to uniquely point to its data.
/// As a result, passing a shared array to this function will **silently** un-share the array.
#[allow(dead_code)]
fn takes_arrref_mut<A, D>(arr: &mut ArrayRef<A, D>)
{
// Immutable call
takes_arrref(arr);

// No need for AsMut, since data is safe
takes_rawref_mut(arr);
takes_rawref_asmut(arr);
takes_layout_mut(arr);
takes_rawref_asmut(arr);
}

/// Now, we no longer care about whether we can safely read data.
///
/// This is probably the rarest type to deal with, since `LayoutRef` can access and modify an array's
/// shape and strides, and even do in-place slicing. As a result, `RawRef` is only for functionality
/// that requires unsafe data access, something that `LayoutRef` can't do.
///
/// Writing functions and traits that deal with `RawRef`s and `LayoutRef`s can be done two ways:
/// 1. Directly on the types; calling these functions on arrays whose data are not known to be safe
/// to dereference (i.e., raw array views or `ArrayBase<S: RawData, D>`) must explicitly call `.as_ref()`.
/// 2. Via a generic with `: AsRef<RawRef<A, D>>`; doing this will allow direct calling for all `ArrayBase` and
/// `ArrayRef` instances.
/// We'll demonstrate #1 here for both immutable and mutable references, then #2 directly below.
#[allow(dead_code)]
fn takes_rawref<A, D>(arr: &RawRef<A, D>)
{
takes_layout(arr);
takes_layout_asref(arr);
}

/// Mutable, directly take `RawRef`
#[allow(dead_code)]
fn takes_rawref_mut<A, D>(arr: &mut RawRef<A, D>)
{
takes_layout(arr);
takes_layout_asmut(arr);
}

/// Immutable, take a generic that implements `AsRef` to `RawRef`
#[allow(dead_code)]
fn takes_rawref_asref<T, A, D>(_arr: &T)
where T: AsRef<RawRef<A, D>>
{
takes_layout(_arr.as_ref());
takes_layout_asref(_arr.as_ref());
}

/// Mutable, take a generic that implements `AsMut` to `RawRef`
#[allow(dead_code)]
fn takes_rawref_asmut<T, A, D>(_arr: &mut T)
where T: AsMut<RawRef<A, D>>
{
takes_layout_mut(_arr.as_mut());
takes_layout_asmut(_arr.as_mut());
}

/// Finally, there's `LayoutRef`: this type provides read and write access to an array's *structure*, but not its *data*.
///
/// Practically, this means that functions that only read/modify an array's shape or strides,
/// such as checking dimensionality or slicing, should take `LayoutRef`.
///
/// Like `RawRef`, functions can be written either directly on `LayoutRef` or as generics with `: AsRef<LayoutRef<A, D>>>`.
#[allow(dead_code)]
fn takes_layout<A, D>(_arr: &LayoutRef<A, D>) {}

/// Mutable, directly take `LayoutRef`
#[allow(dead_code)]
fn takes_layout_mut<A, D>(_arr: &mut LayoutRef<A, D>) {}

/// Immutable, take a generic that implements `AsRef` to `LayoutRef`
#[allow(dead_code)]
fn takes_layout_asref<T: AsRef<LayoutRef<A, D>>, A, D>(_arr: &T) {}

/// Mutable, take a generic that implements `AsMut` to `LayoutRef`
#[allow(dead_code)]
fn takes_layout_asmut<T: AsMut<LayoutRef<A, D>>, A, D>(_arr: &mut T) {}

fn main() {}
142 changes: 137 additions & 5 deletions src/alias_slicing.rs → src/alias_asref.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
use crate::{iter::Axes, ArrayBase, Axis, AxisDescription, Dimension, LayoutRef, RawData, Slice, SliceArg};
use crate::{
iter::Axes,
ArrayBase,
Axis,
AxisDescription,
Dimension,
LayoutRef,
RawArrayView,
RawData,
RawRef,
Slice,
SliceArg,
};

impl<S: RawData, D: Dimension> ArrayBase<S, D>
{
Expand Down Expand Up @@ -69,19 +81,19 @@ impl<S: RawData, D: Dimension> ArrayBase<S, D>
/// contiguous in memory, it has custom strides, etc.
pub fn is_standard_layout(&self) -> bool
{
self.as_ref().is_standard_layout()
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).is_standard_layout()
}

/// Return true if the array is known to be contiguous.
pub(crate) fn is_contiguous(&self) -> bool
{
self.as_ref().is_contiguous()
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).is_contiguous()
}

/// Return an iterator over the length and stride of each axis.
pub fn axes(&self) -> Axes<'_, D>
{
self.as_ref().axes()
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).axes()
}

/*
Expand Down Expand Up @@ -170,9 +182,129 @@ impl<S: RawData, D: Dimension> ArrayBase<S, D>
self.as_mut().merge_axes(take, into)
}

/// Return a raw view of the array.
#[inline]
pub fn raw_view(&self) -> RawArrayView<S::Elem, D>
{
<Self as AsRef<RawRef<_, _>>>::as_ref(self).raw_view()
}

/// Return a pointer to the first element in the array.
///
/// Raw access to array elements needs to follow the strided indexing
/// scheme: an element at multi-index *I* in an array with strides *S* is
/// located at offset
///
/// *Σ<sub>0 ≤ k < d</sub> I<sub>k</sub> × S<sub>k</sub>*
///
/// where *d* is `self.ndim()`.
#[inline(always)]
pub fn as_ptr(&self) -> *const S::Elem
{
<Self as AsRef<RawRef<_, _>>>::as_ref(self).as_ptr()
}

/// Return the total number of elements in the array.
pub fn len(&self) -> usize
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).len()
}

/// Return the length of `axis`.
///
/// The axis should be in the range `Axis(` 0 .. *n* `)` where *n* is the
/// number of dimensions (axes) of the array.
///
/// ***Panics*** if the axis is out of bounds.
#[track_caller]
pub fn len_of(&self, axis: Axis) -> usize
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).len_of(axis)
}

/// Return whether the array has any elements
pub fn is_empty(&self) -> bool
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).is_empty()
}

/// Return the number of dimensions (axes) in the array
pub fn ndim(&self) -> usize
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).ndim()
}

/// Return the shape of the array in its “pattern” form,
/// an integer in the one-dimensional case, tuple in the n-dimensional cases
/// and so on.
pub fn dim(&self) -> D::Pattern
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).dim()
}

/// Return the shape of the array as it's stored in the array.
///
/// This is primarily useful for passing to other `ArrayBase`
/// functions, such as when creating another array of the same
/// shape and dimensionality.
///
/// ```
/// use ndarray::Array;
///
/// let a = Array::from_elem((2, 3), 5.);
///
/// // Create an array of zeros that's the same shape and dimensionality as `a`.
/// let b = Array::<f64, _>::zeros(a.raw_dim());
/// ```
pub fn raw_dim(&self) -> D
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).raw_dim()
}

/// Return the shape of the array as a slice.
///
/// Note that you probably don't want to use this to create an array of the
/// same shape as another array because creating an array with e.g.
/// [`Array::zeros()`](ArrayBase::zeros) using a shape of type `&[usize]`
/// results in a dynamic-dimensional array. If you want to create an array
/// that has the same shape and dimensionality as another array, use
/// [`.raw_dim()`](ArrayBase::raw_dim) instead:
///
/// ```rust
/// use ndarray::{Array, Array2};
///
/// let a = Array2::<i32>::zeros((3, 4));
/// let shape = a.shape();
/// assert_eq!(shape, &[3, 4]);
///
/// // Since `a.shape()` returned `&[usize]`, we get an `ArrayD` instance:
/// let b = Array::zeros(shape);
/// assert_eq!(a.clone().into_dyn(), b);
///
/// // To get the same dimension type, use `.raw_dim()` instead:
/// let c = Array::zeros(a.raw_dim());
/// assert_eq!(a, c);
/// ```
pub fn shape(&self) -> &[usize]
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).shape()
}

/// Return the strides of the array as a slice.
pub fn strides(&self) -> &[isize]
{
self.as_ref().strides()
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).strides()
}

/// Return the stride of `axis`.
///
/// The axis should be in the range `Axis(` 0 .. *n* `)` where *n* is the
/// number of dimensions (axes) of the array.
///
/// ***Panics*** if the axis is out of bounds.
#[track_caller]
pub fn stride_of(&self, axis: Axis) -> isize
{
<Self as AsRef<LayoutRef<_, _>>>::as_ref(self).stride_of(axis)
}
}
6 changes: 3 additions & 3 deletions src/impl_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl<A> LayoutRef<A, Ix2>
/// ```
pub fn nrows(&self) -> usize
{
self.as_ref().len_of(Axis(0))
self.len_of(Axis(0))
}
}

Expand Down Expand Up @@ -124,7 +124,7 @@ impl<A> LayoutRef<A, Ix2>
/// ```
pub fn ncols(&self) -> usize
{
self.as_ref().len_of(Axis(1))
self.len_of(Axis(1))
}

/// Return true if the array is square, false otherwise.
Expand All @@ -144,7 +144,7 @@ impl<A> LayoutRef<A, Ix2>
/// ```
pub fn is_square(&self) -> bool
{
let (m, n) = self.as_ref().dim();
let (m, n) = self.dim();
m == n
}
}
Loading

0 comments on commit 3156346

Please sign in to comment.