From 5056ef8b336bd200f884bd7f345c83733ac0d298 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sat, 10 Jan 2026 11:54:56 -0500 Subject: [PATCH 1/2] Add traits and types to capture dimensionality (#1568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ndarray has a history of encoding arrays’ dimensionality in the type system; this turns out to be useful both for debugging and for writing complex array libraries. However, some arrays don’t (or can’t) have their dimensionality known at compile time. One good example of this is the output of ArrayBase::squeeze: even if the array’s dimensionality is known before the operation, the dimensionality of the output depends on how many axes have length one. The Dimensionality trait is intended to unify both the known- and unknown-dimensionality cases. Compile-time dimensionalities are represented using NDim, while cases where the dimensionality cannot be known at compile time are represented using DDyn. A key design choice is that there is no way to recover the number of dimensions as a runtime value from a type implementing Dimensionality. A dynamic dimensionality here does not mean “known at runtime”, but rather “cannot be known at compile time”. The actual number of axes is always taken from the array’s shape, which avoids having two separate sources of truth. --- Cargo.toml | 2 + src/dimension/dimension_trait.rs | 18 ++ src/layout/dimensionality.rs | 331 +++++++++++++++++++++++++++++++ src/layout/mod.rs | 12 ++ src/lib.rs | 2 +- 5 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 src/layout/dimensionality.rs diff --git a/Cargo.toml b/Cargo.toml index efc035f00..12560040f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,6 +74,8 @@ matrixmultiply-threading = ["matrixmultiply/threading"] portable-atomic-critical-section = ["portable-atomic/critical-section"] +unstable = [] + [target.'cfg(not(target_has_atomic = "ptr"))'.dependencies] portable-atomic = { version = "1.6.0" } diff --git a/src/dimension/dimension_trait.rs b/src/dimension/dimension_trait.rs index 3544a7f3c..36b538243 100644 --- a/src/dimension/dimension_trait.rs +++ b/src/dimension/dimension_trait.rs @@ -17,6 +17,8 @@ use super::conversion::Convert; use super::ops::DimAdd; use super::{stride_offset, stride_offset_checked}; use crate::itertools::{enumerate, zip}; +#[cfg(feature = "unstable")] +use crate::layout::dimensionality::*; use crate::IntoDimension; use crate::RemoveAxis; use crate::{ArrayView1, ArrayViewMut1}; @@ -76,6 +78,10 @@ pub trait Dimension: /// Next larger dimension type Larger: Dimension + RemoveAxis; + /// The dimensionality of the type, under the new, unstable API. + #[cfg(feature = "unstable")] + type Rank: Dimensionality; + /// Returns the number of dimensions (number of axes). fn ndim(&self) -> usize; @@ -420,6 +426,8 @@ impl Dimension for Dim<[Ix; 0]> type Pattern = (); type Smaller = Self; type Larger = Ix1; + #[cfg(feature = "unstable")] + type Rank = D0; // empty product is 1 -> size is 1 #[inline] fn ndim(&self) -> usize @@ -470,6 +478,8 @@ impl Dimension for Dim<[Ix; 1]> type Pattern = Ix; type Smaller = Ix0; type Larger = Ix2; + #[cfg(feature = "unstable")] + type Rank = D1; #[inline] fn ndim(&self) -> usize { @@ -603,6 +613,8 @@ impl Dimension for Dim<[Ix; 2]> type Pattern = (Ix, Ix); type Smaller = Ix1; type Larger = Ix3; + #[cfg(feature = "unstable")] + type Rank = D2; #[inline] fn ndim(&self) -> usize { @@ -778,6 +790,8 @@ impl Dimension for Dim<[Ix; 3]> type Pattern = (Ix, Ix, Ix); type Smaller = Ix2; type Larger = Ix4; + #[cfg(feature = "unstable")] + type Rank = D3; #[inline] fn ndim(&self) -> usize { @@ -910,6 +924,8 @@ macro_rules! large_dim { type Pattern = $pattern; type Smaller = Dim<[Ix; $n - 1]>; type Larger = $larger; + #[cfg(feature = "unstable")] + type Rank = NDim<$n>; #[inline] fn ndim(&self) -> usize { $n } #[inline] @@ -960,6 +976,8 @@ impl Dimension for IxDyn type Pattern = Self; type Smaller = Self; type Larger = Self; + #[cfg(feature = "unstable")] + type Rank = DDyn; #[inline] fn ndim(&self) -> usize { diff --git a/src/layout/dimensionality.rs b/src/layout/dimensionality.rs new file mode 100644 index 000000000..ed62e5fe8 --- /dev/null +++ b/src/layout/dimensionality.rs @@ -0,0 +1,331 @@ +//! Type-level representations of array dimensionality. +//! +//! This module defines the [`Dimensionality`] trait and related types used to represent +//! the number of axes an array has, either at compile time ([`NDim`]) or dynamically +//! ([`DDyn`]). These types support basic type-level operations such as addition and +//! maximum, which are used to model array operations like concatenation and broadcasting. + +use core::fmt::Debug; + +/// A trait representing a dimensionality, i.e., an unsigned integer indicating how many axes an array has. +/// +/// `ndarray` encodes an array’s dimensionality in the type system when possible, which is useful for +/// debugging and for writing generic array code. However, some operations produce arrays whose +/// dimensionality cannot be known at compile time. This trait provides a common abstraction for both +/// statically known and dynamic dimensionalities. +/// +/// Compile-time dimensionalities are currently supported for values from 0 to 12, inclusive. +/// Any dimensionality above 12 must be represented with [`DDyn`], even if it is known at compile time. +/// +/// The `Smaller` and `Larger` associated types allow users to move to adjacent dimensionalities at the type level. +/// +/// ## Dynamic dimensionalities +/// A type implementing `Dimensionality` does not expose its dimensionality as a runtime value. +/// In dynamic cases, `DDyn` means that the dimensionality is not known at compile time. +/// The actual number of axes is taken directly from the array’s shape. +pub trait Dimensionality: + Copy + + Eq + + Debug + + Send + + Sync + + DMax + + DMax + + DMax + + DMax + + DMax + + DAdd + + DAdd + + DAdd + + DAdd + + DAdd + + DAdd +{ + /// The dimensionality as a constant `usize`, or `None` if it is dynamic. + const N: Option; + + /// The next-smaller possible dimensionality. + /// + /// For the smallest possible dimensionality (currently 0-dimensional), there + /// is of course no "smaller" dimensionality. Instead, `NDim::<0>::Smaller` just + /// refers back to `NDim<0>`; in other words, it uses a "base case" of 0-dimensionality. + type Smaller: Dimensionality; + + /// The next-larger dimensionality. + /// + /// For the largest compile-time dimensionality (currently 12-dimensional), there + /// is no "larger" compile-time dimensionality. Instead, `NDim::<12>::Larger` just + /// refers to `DDyn`; in other words, it "escapes" to a dynamically-determined dimensionality. + type Larger: Dimensionality; +} + +/// Adds two dimensionalities at compile time. +/// +/// The addition of a constant dimensionality with a dynamic dimensionality +/// will always result in a dynamic dimensionality, effectively "erasing" +/// the compile-time knowledge. +/// +/// This type is analogous to the existing [`crate::DimAdd`], but specifically +/// for dimensionality instead of `Dimension` types. +/// +/// ## Example +/// ``` +/// use ndarray::layout::dimensionality::*; +/// use core::any::TypeId; +/// +/// type Added = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// +/// type AddedDyn = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// ``` +pub trait DAdd +{ + /// The result of the type-level addition of two dimensionalities. + type Output: Dimensionality; +} + +/// Takes the maximum of two dimensionalities at compile time. +/// +/// The maximum of a constant dimensionality and a dynamic dimensionality +/// will always result in a dynamic dimensionality, effectively "erasing" +/// the compile-time knowledge. +/// +/// This type is analogous to the existing [`crate::DimMax`], but specifically +/// for dimensionality instead of `Dimension` types. +/// +/// ## Example +/// ``` +/// use ndarray::layout::dimensionality::*; +/// use core::any::TypeId; +/// +/// type Added = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// +/// type AddedDyn = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// ``` +pub trait DMax +{ + /// The result of the type-level maximum of two dimensionalities. + type Output: Dimensionality; +} + +/// The N-dimensional static dimensionality. +/// +/// This type captures dimensionalities that are known at compile-time. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct NDim; + +/// The 0-dimensionality, for "dimensionless" arrays with a single value. +/// +/// See [`Dimensionality`] and [`NDim`] for more information. +pub type D0 = NDim<0>; + +macro_rules! def_d_aliases { + ($(($alias:ident, $N:literal)),*) => { + $( + /// A dimensionality for arrays that are + #[doc = stringify!($N)] + /// D. + /// + /// See [`Dimensionality`] and [`NDim`] for more information. + pub type $alias = NDim<$N>; + )+ + }; +} + +def_d_aliases!( + (D1, 1), + (D2, 2), + (D3, 3), + (D4, 4), + (D5, 5), + (D6, 6), + (D7, 7), + (D8, 8), + (D9, 9), + (D10, 10), + (D11, 11), + (D12, 12) +); + +/// Implement addition for a given dimensionality. +macro_rules! impl_add { + ($left:literal, ($($right:literal),*), ddyn: ($($rightd:literal),*)) => { + // $left + $right still gets you a compile-time dimension + $( + impl DAdd> for NDim<$left> + { + type Output = NDim<{$left + $right}>; + } + )* + + // $left + $rightd gets you a dynamic dimensionality + $( + impl DAdd> for NDim<$left> + { + type Output = DDyn; + } + )* + }; +} + +// There's got to be a macro way to do this in one line to help with +// any future additions of extra dimenions, although it might +// also slow down compile times. +impl_add!(0, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), ddyn: ()); +impl_add!(1, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), ddyn: (12)); +impl_add!(2, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), ddyn: (11, 12)); +impl_add!(3, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9), ddyn: (10, 11, 12)); +impl_add!(4, (0, 1, 2, 3, 4, 5, 6, 7, 8), ddyn: (9, 10, 11, 12)); +impl_add!(5, (0, 1, 2, 3, 4, 5, 6, 7), ddyn: (8, 9, 10, 11, 12)); +impl_add!(6, (0, 1, 2, 3, 4, 5, 6), ddyn: (7, 8, 9, 10, 11, 12)); +impl_add!(7, (0, 1, 2, 3, 4, 5), ddyn: (6, 7, 8, 9, 10, 11, 12)); +impl_add!(8, (0, 1, 2, 3, 4), ddyn: (5, 6, 7, 8, 9, 10, 11, 12)); +impl_add!(9, (0, 1, 2, 3), ddyn: (4, 5, 6, 7, 8, 9, 10, 11, 12)); +impl_add!(10, (0, 1, 2), ddyn: (3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); +impl_add!(11, (0, 1), ddyn: (2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); +impl_add!(12, (0), ddyn: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); + +macro_rules! impl_max { + // Base case, just a target with some lowers + ($($lower:literal),+, target: $target:literal) => { + $( + impl DMax> for NDim<$target> + { + type Output = NDim<$target>; + } + )+ + }; + // General case: at least one lower, at least one upper + ($($lower:literal),+$(,)? target: $target:literal, $first_upper:literal$(, $($upper:literal),+)?) => { + $( + impl DMax> for NDim<$target> + { + type Output = NDim<$target>; + } + )+ + impl DMax> for NDim<$target> + { + type Output = NDim<$first_upper>; + } + $( + $( + impl DMax> for NDim<$target> + { + type Output = NDim<$upper>; + } + )+ + )? + impl_max!($($lower),+, $target, target: $first_upper$(, $($upper),+)?); + }; + // Helper syntax: zero lowers, target, at least one upper + (target: $target:literal, $first_upper:literal, $($upper:literal),+) => { + impl DMax> for NDim<$target> + { + type Output = NDim<$first_upper>; + } + $( + impl DMax> for NDim<$target> + { + type Output = NDim<$upper>; + } + )+ + impl_max!($target, target: $first_upper, $($upper),+); + }; +} + +impl_max!(target: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); + +impl DMax> for NDim +where NDim: Dimensionality +{ + type Output = Self; +} + +macro_rules! impl_dimensionality { + ($($d:literal),+) => { + $( + impl Dimensionality for NDim<$d> + { + const N: Option = Some($d); + + type Smaller = NDim<{$d - 1}>; + + type Larger = NDim<{$d + 1}>; + } + )+ + }; +} + +impl Dimensionality for D0 +{ + const N: Option = Some(0); + + type Smaller = Self; + + type Larger = D1; +} + +impl_dimensionality!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); + +impl Dimensionality for NDim<12> +{ + const N: Option = Some(12); + + type Smaller = D11; + + type Larger = DDyn; +} + +/// The dynamic dimensionality. +/// +/// This type captures dimensionalities that are unknown at compile-time. +/// See [`Dimensionality`] for more information. +/// +/// This type does not carry any information about runtime dimensionality, +/// it just indicate that dimensionality is not known at compile-time. +/// This is done to avoid multiple sources of truth for runtime array +/// dimensionality. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct DDyn; + +impl Dimensionality for DDyn +{ + const N: Option = None; + + type Smaller = Self; + + type Larger = Self; +} + +impl DAdd for DDyn +{ + type Output = DDyn; +} + +impl DAdd> for DDyn +{ + type Output = DDyn; +} + +impl DAdd for NDim +{ + type Output = DDyn; +} + +impl DMax for DDyn +{ + type Output = DDyn; +} + +impl DMax> for DDyn +{ + type Output = DDyn; +} + +impl DMax for NDim +{ + type Output = DDyn; +} diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 7f549ebb2..df936ac2c 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,4 +1,16 @@ +//! Building blocks for describing array layout. +//! +//! This module contains types and traits used to describe how an array is structured in memory. +//! At present, it includes utilities for compactly encoding layout information +//! and abstractions for representing an array’s dimensionality. +//! +//! Over time, this module will also define traits and types for shapes, strides, and complete +//! array layouts, providing a clearer separation between these concerns and enabling more +//! flexible and expressive layout representations. + mod bitset; +#[cfg(feature = "unstable")] +pub mod dimensionality; #[allow(deprecated)] pub use bitset::{Layout, LayoutBitset}; diff --git a/src/lib.rs b/src/lib.rs index 2b9b656e3..f12417ce7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,7 +197,7 @@ mod extension; mod geomspace; mod indexes; mod iterators; -mod layout; +pub mod layout; mod linalg_traits; mod linspace; #[cfg(feature = "std")] From 81756e669ac487b2089a7d61754ded76dbd013b2 Mon Sep 17 00:00:00 2001 From: akern40 Date: Sat, 7 Feb 2026 20:08:24 -0500 Subject: [PATCH 2/2] Add a trait to tie together compile-time and runtime number of dimensions (#1575) This is the next PR to address #1506. In the previous PR (#1568) we established a trait for capturing the "dimensionality" or "rank" of an array at the type level. This PR is focused on providing a bridge from type-level dimensionality to runtime dimensionality, which we do through the new `Ranked` trait. The `Ranked` trait is pretty simple: require an associated type (`NDim`) that carries compile-time dimensionality, and a function `fn ndim(&self) -> usize` that tells the user runtime dimensionality. The `src/layout/ranked.rs` file contains the definition, implementations, and blanket implementations. This commit also does some moves and renames of the existing `Dimensionality` trait, renaming it to `Rank`, as well as many of the types in that file. --- src/dimension/dimension_trait.rs | 74 +++----- src/dimension/dynindeximpl.rs | 1 + src/dimension/ndindex.rs | 1 + src/dimension/remove_axis.rs | 2 +- src/indexes.rs | 1 + src/layout/mod.rs | 4 +- src/layout/{dimensionality.rs => rank.rs} | 192 ++++++++++----------- src/layout/ranked.rs | 195 ++++++++++++++++++++++ 8 files changed, 323 insertions(+), 147 deletions(-) rename src/layout/{dimensionality.rs => rank.rs} (60%) create mode 100644 src/layout/ranked.rs diff --git a/src/dimension/dimension_trait.rs b/src/dimension/dimension_trait.rs index 36b538243..c1bbfa86a 100644 --- a/src/dimension/dimension_trait.rs +++ b/src/dimension/dimension_trait.rs @@ -17,8 +17,8 @@ use super::conversion::Convert; use super::ops::DimAdd; use super::{stride_offset, stride_offset_checked}; use crate::itertools::{enumerate, zip}; -#[cfg(feature = "unstable")] -use crate::layout::dimensionality::*; +use crate::layout::rank::*; +use crate::layout::ranked::Ranked; use crate::IntoDimension; use crate::RemoveAxis; use crate::{ArrayView1, ArrayViewMut1}; @@ -61,6 +61,7 @@ pub trait Dimension: + DimAdd + DimAdd::Larger> + DimAdd + + Ranked { /// For fixed-size dimension representations (e.g. `Ix2`), this should be /// `Some(ndim)`, and for variable-size dimension representations (e.g. @@ -78,13 +79,6 @@ pub trait Dimension: /// Next larger dimension type Larger: Dimension + RemoveAxis; - /// The dimensionality of the type, under the new, unstable API. - #[cfg(feature = "unstable")] - type Rank: Dimensionality; - - /// Returns the number of dimensions (number of axes). - fn ndim(&self) -> usize; - /// Convert the dimension into a pattern matching friendly value. fn into_pattern(self) -> Self::Pattern; @@ -420,21 +414,26 @@ macro_rules! impl_insert_axis_array( ); ); +impl Ranked for Dim<[Ix; N]> +where ConstRank: Rank // Limit us to < 12, since Rank must impl Dimensionality +{ + type NDim = ConstRank; + + #[inline] + fn ndim(&self) -> usize + { + N + } +} + impl Dimension for Dim<[Ix; 0]> { const NDIM: Option = Some(0); type Pattern = (); type Smaller = Self; type Larger = Ix1; - #[cfg(feature = "unstable")] - type Rank = D0; // empty product is 1 -> size is 1 #[inline] - fn ndim(&self) -> usize - { - 0 - } - #[inline] fn slice(&self) -> &[Ix] { &[] @@ -478,13 +477,6 @@ impl Dimension for Dim<[Ix; 1]> type Pattern = Ix; type Smaller = Ix0; type Larger = Ix2; - #[cfg(feature = "unstable")] - type Rank = D1; - #[inline] - fn ndim(&self) -> usize - { - 1 - } #[inline] fn slice(&self) -> &[Ix] { @@ -613,13 +605,6 @@ impl Dimension for Dim<[Ix; 2]> type Pattern = (Ix, Ix); type Smaller = Ix1; type Larger = Ix3; - #[cfg(feature = "unstable")] - type Rank = D2; - #[inline] - fn ndim(&self) -> usize - { - 2 - } #[inline] fn into_pattern(self) -> Self::Pattern { @@ -790,13 +775,6 @@ impl Dimension for Dim<[Ix; 3]> type Pattern = (Ix, Ix, Ix); type Smaller = Ix2; type Larger = Ix4; - #[cfg(feature = "unstable")] - type Rank = D3; - #[inline] - fn ndim(&self) -> usize - { - 3 - } #[inline] fn into_pattern(self) -> Self::Pattern { @@ -924,10 +902,6 @@ macro_rules! large_dim { type Pattern = $pattern; type Smaller = Dim<[Ix; $n - 1]>; type Larger = $larger; - #[cfg(feature = "unstable")] - type Rank = NDim<$n>; - #[inline] - fn ndim(&self) -> usize { $n } #[inline] fn into_pattern(self) -> Self::Pattern { self.ix().convert() @@ -968,6 +942,17 @@ large_dim!(6, Ix6, (Ix, Ix, Ix, Ix, Ix, Ix), IxDyn, { } }); +impl Ranked for IxDyn +{ + type NDim = DynRank; + + #[inline] + fn ndim(&self) -> usize + { + self.ix().len() + } +} + /// IxDyn is a "dynamic" index, pretty hard to use when indexing, /// and memory wasteful, but it allows an arbitrary and dynamic number of axes. impl Dimension for IxDyn @@ -976,13 +961,6 @@ impl Dimension for IxDyn type Pattern = Self; type Smaller = Self; type Larger = Self; - #[cfg(feature = "unstable")] - type Rank = DDyn; - #[inline] - fn ndim(&self) -> usize - { - self.ix().len() - } #[inline] fn slice(&self) -> &[Ix] { diff --git a/src/dimension/dynindeximpl.rs b/src/dimension/dynindeximpl.rs index 60aeacd80..6f8ad1d49 100644 --- a/src/dimension/dynindeximpl.rs +++ b/src/dimension/dynindeximpl.rs @@ -1,4 +1,5 @@ use crate::imp_prelude::*; +use crate::layout::ranked::Ranked; #[cfg(not(feature = "std"))] use alloc::boxed::Box; use alloc::vec; diff --git a/src/dimension/ndindex.rs b/src/dimension/ndindex.rs index ca2a3ea69..4ed368c20 100644 --- a/src/dimension/ndindex.rs +++ b/src/dimension/ndindex.rs @@ -2,6 +2,7 @@ use std::fmt::Debug; use super::{stride_offset, stride_offset_checked}; use crate::itertools::zip; +use crate::layout::ranked::Ranked; use crate::{Dim, Dimension, IntoDimension, Ix, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn, IxDynImpl}; /// Tuple or fixed size arrays that can be used to index an array. diff --git a/src/dimension/remove_axis.rs b/src/dimension/remove_axis.rs index 7ba3b5330..5510c6d64 100644 --- a/src/dimension/remove_axis.rs +++ b/src/dimension/remove_axis.rs @@ -6,7 +6,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use crate::{Axis, Dim, Dimension, Ix, Ix0, Ix1}; +use crate::{layout::ranked::Ranked, Axis, Dim, Dimension, Ix, Ix0, Ix1}; /// Array shape with a next smaller dimension. /// diff --git a/src/indexes.rs b/src/indexes.rs index a73c7fcb4..5b0346208 100644 --- a/src/indexes.rs +++ b/src/indexes.rs @@ -7,6 +7,7 @@ // except according to those terms. use super::Dimension; use crate::dimension::IntoDimension; +use crate::layout::ranked::Ranked; use crate::split_at::SplitAt; use crate::zip::Offset; use crate::Axis; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index df936ac2c..9f5d1e4e1 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -9,8 +9,8 @@ //! flexible and expressive layout representations. mod bitset; -#[cfg(feature = "unstable")] -pub mod dimensionality; +pub mod rank; +pub mod ranked; #[allow(deprecated)] pub use bitset::{Layout, LayoutBitset}; diff --git a/src/layout/dimensionality.rs b/src/layout/rank.rs similarity index 60% rename from src/layout/dimensionality.rs rename to src/layout/rank.rs index ed62e5fe8..2881b593d 100644 --- a/src/layout/dimensionality.rs +++ b/src/layout/rank.rs @@ -1,8 +1,8 @@ //! Type-level representations of array dimensionality. //! -//! This module defines the [`Dimensionality`] trait and related types used to represent -//! the number of axes an array has, either at compile time ([`NDim`]) or dynamically -//! ([`DDyn`]). These types support basic type-level operations such as addition and +//! This module defines the [`Rank`] trait and related types used to represent +//! the number of axes an array has, either at compile time ([`ConstRank`]) or dynamically +//! ([`DynRank`]). These types support basic type-level operations such as addition and //! maximum, which are used to model array operations like concatenation and broadcasting. use core::fmt::Debug; @@ -15,31 +15,31 @@ use core::fmt::Debug; /// statically known and dynamic dimensionalities. /// /// Compile-time dimensionalities are currently supported for values from 0 to 12, inclusive. -/// Any dimensionality above 12 must be represented with [`DDyn`], even if it is known at compile time. +/// Any dimensionality above 12 must be represented with [`DynRank`], even if it is known at compile time. /// /// The `Smaller` and `Larger` associated types allow users to move to adjacent dimensionalities at the type level. /// /// ## Dynamic dimensionalities -/// A type implementing `Dimensionality` does not expose its dimensionality as a runtime value. -/// In dynamic cases, `DDyn` means that the dimensionality is not known at compile time. +/// A type implementing `Rank` does not expose its dimensionality as a runtime value. +/// In dynamic cases, `DynRank` means that the dimensionality is not known at compile time. /// The actual number of axes is taken directly from the array’s shape. -pub trait Dimensionality: +pub trait Rank: Copy + Eq + Debug + Send + Sync - + DMax - + DMax - + DMax - + DMax - + DMax - + DAdd - + DAdd - + DAdd - + DAdd - + DAdd - + DAdd + + RMax + + RMax + + RMax + + RMax + + RMax + + RAdd + + RAdd + + RAdd + + RAdd + + RAdd + + RAdd { /// The dimensionality as a constant `usize`, or `None` if it is dynamic. const N: Option; @@ -47,16 +47,16 @@ pub trait Dimensionality: /// The next-smaller possible dimensionality. /// /// For the smallest possible dimensionality (currently 0-dimensional), there - /// is of course no "smaller" dimensionality. Instead, `NDim::<0>::Smaller` just - /// refers back to `NDim<0>`; in other words, it uses a "base case" of 0-dimensionality. - type Smaller: Dimensionality; + /// is of course no "smaller" dimensionality. Instead, `ConstRank::<0>::Smaller` just + /// refers back to `ConstRank<0>`; in other words, it uses a "base case" of 0-dimensionality. + type Smaller: Rank; /// The next-larger dimensionality. /// /// For the largest compile-time dimensionality (currently 12-dimensional), there - /// is no "larger" compile-time dimensionality. Instead, `NDim::<12>::Larger` just - /// refers to `DDyn`; in other words, it "escapes" to a dynamically-determined dimensionality. - type Larger: Dimensionality; + /// is no "larger" compile-time dimensionality. Instead, `ConstRank::<12>::Larger` just + /// refers to `DynRank`; in other words, it "escapes" to a dynamically-determined dimensionality. + type Larger: Rank; } /// Adds two dimensionalities at compile time. @@ -70,19 +70,19 @@ pub trait Dimensionality: /// /// ## Example /// ``` -/// use ndarray::layout::dimensionality::*; +/// use ndarray::layout::rank::*; /// use core::any::TypeId; /// -/// type Added = >::Output; -/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// type Added = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); /// -/// type AddedDyn = >::Output; -/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// type AddedDyn = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); /// ``` -pub trait DAdd +pub trait RAdd { /// The result of the type-level addition of two dimensionalities. - type Output: Dimensionality; + type Output: Rank; } /// Takes the maximum of two dimensionalities at compile time. @@ -96,31 +96,31 @@ pub trait DAdd /// /// ## Example /// ``` -/// use ndarray::layout::dimensionality::*; +/// use ndarray::layout::rank::*; /// use core::any::TypeId; /// -/// type Added = >::Output; -/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// type Added = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); /// -/// type AddedDyn = >::Output; -/// assert_eq!(TypeId::of::(), TypeId::of::()); +/// type AddedDyn = >::Output; +/// assert_eq!(TypeId::of::(), TypeId::of::()); /// ``` -pub trait DMax +pub trait RMax { /// The result of the type-level maximum of two dimensionalities. - type Output: Dimensionality; + type Output: Rank; } /// The N-dimensional static dimensionality. /// /// This type captures dimensionalities that are known at compile-time. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct NDim; +pub struct ConstRank; /// The 0-dimensionality, for "dimensionless" arrays with a single value. /// -/// See [`Dimensionality`] and [`NDim`] for more information. -pub type D0 = NDim<0>; +/// See [`Rank`] and [`ConstRank`] for more information. +pub type R0 = ConstRank<0>; macro_rules! def_d_aliases { ($(($alias:ident, $N:literal)),*) => { @@ -129,25 +129,25 @@ macro_rules! def_d_aliases { #[doc = stringify!($N)] /// D. /// - /// See [`Dimensionality`] and [`NDim`] for more information. - pub type $alias = NDim<$N>; + /// See [`Rank`] and [`ConstRank`] for more information. + pub type $alias = ConstRank<$N>; )+ }; } def_d_aliases!( - (D1, 1), - (D2, 2), - (D3, 3), - (D4, 4), - (D5, 5), - (D6, 6), - (D7, 7), - (D8, 8), - (D9, 9), - (D10, 10), - (D11, 11), - (D12, 12) + (R1, 1), + (R2, 2), + (R3, 3), + (R4, 4), + (R5, 5), + (R6, 6), + (R7, 7), + (R8, 8), + (R9, 9), + (R10, 10), + (R11, 11), + (R12, 12) ); /// Implement addition for a given dimensionality. @@ -155,17 +155,17 @@ macro_rules! impl_add { ($left:literal, ($($right:literal),*), ddyn: ($($rightd:literal),*)) => { // $left + $right still gets you a compile-time dimension $( - impl DAdd> for NDim<$left> + impl RAdd> for ConstRank<$left> { - type Output = NDim<{$left + $right}>; + type Output = ConstRank<{$left + $right}>; } )* // $left + $rightd gets you a dynamic dimensionality $( - impl DAdd> for NDim<$left> + impl RAdd> for ConstRank<$left> { - type Output = DDyn; + type Output = DynRank; } )* }; @@ -192,29 +192,29 @@ macro_rules! impl_max { // Base case, just a target with some lowers ($($lower:literal),+, target: $target:literal) => { $( - impl DMax> for NDim<$target> + impl RMax> for ConstRank<$target> { - type Output = NDim<$target>; + type Output = ConstRank<$target>; } )+ }; // General case: at least one lower, at least one upper ($($lower:literal),+$(,)? target: $target:literal, $first_upper:literal$(, $($upper:literal),+)?) => { $( - impl DMax> for NDim<$target> + impl RMax> for ConstRank<$target> { - type Output = NDim<$target>; + type Output = ConstRank<$target>; } )+ - impl DMax> for NDim<$target> + impl RMax> for ConstRank<$target> { - type Output = NDim<$first_upper>; + type Output = ConstRank<$first_upper>; } $( $( - impl DMax> for NDim<$target> + impl RMax> for ConstRank<$target> { - type Output = NDim<$upper>; + type Output = ConstRank<$upper>; } )+ )? @@ -222,14 +222,14 @@ macro_rules! impl_max { }; // Helper syntax: zero lowers, target, at least one upper (target: $target:literal, $first_upper:literal, $($upper:literal),+) => { - impl DMax> for NDim<$target> + impl RMax> for ConstRank<$target> { - type Output = NDim<$first_upper>; + type Output = ConstRank<$first_upper>; } $( - impl DMax> for NDim<$target> + impl RMax> for ConstRank<$target> { - type Output = NDim<$upper>; + type Output = ConstRank<$upper>; } )+ impl_max!($target, target: $first_upper, $($upper),+); @@ -238,8 +238,8 @@ macro_rules! impl_max { impl_max!(target: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); -impl DMax> for NDim -where NDim: Dimensionality +impl RMax> for ConstRank +where ConstRank: Rank { type Output = Self; } @@ -247,51 +247,51 @@ where NDim: Dimensionality macro_rules! impl_dimensionality { ($($d:literal),+) => { $( - impl Dimensionality for NDim<$d> + impl Rank for ConstRank<$d> { const N: Option = Some($d); - type Smaller = NDim<{$d - 1}>; + type Smaller = ConstRank<{$d - 1}>; - type Larger = NDim<{$d + 1}>; + type Larger = ConstRank<{$d + 1}>; } )+ }; } -impl Dimensionality for D0 +impl Rank for R0 { const N: Option = Some(0); type Smaller = Self; - type Larger = D1; + type Larger = R1; } impl_dimensionality!(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11); -impl Dimensionality for NDim<12> +impl Rank for ConstRank<12> { const N: Option = Some(12); - type Smaller = D11; + type Smaller = R11; - type Larger = DDyn; + type Larger = DynRank; } /// The dynamic dimensionality. /// /// This type captures dimensionalities that are unknown at compile-time. -/// See [`Dimensionality`] for more information. +/// See [`Rank`] for more information. /// /// This type does not carry any information about runtime dimensionality, /// it just indicate that dimensionality is not known at compile-time. /// This is done to avoid multiple sources of truth for runtime array /// dimensionality. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub struct DDyn; +pub struct DynRank; -impl Dimensionality for DDyn +impl Rank for DynRank { const N: Option = None; @@ -300,32 +300,32 @@ impl Dimensionality for DDyn type Larger = Self; } -impl DAdd for DDyn +impl RAdd for DynRank { - type Output = DDyn; + type Output = DynRank; } -impl DAdd> for DDyn +impl RAdd> for DynRank { - type Output = DDyn; + type Output = DynRank; } -impl DAdd for NDim +impl RAdd for ConstRank { - type Output = DDyn; + type Output = DynRank; } -impl DMax for DDyn +impl RMax for DynRank { - type Output = DDyn; + type Output = DynRank; } -impl DMax> for DDyn +impl RMax> for DynRank { - type Output = DDyn; + type Output = DynRank; } -impl DMax for NDim +impl RMax for ConstRank { - type Output = DDyn; + type Output = DynRank; } diff --git a/src/layout/ranked.rs b/src/layout/ranked.rs new file mode 100644 index 000000000..f71cce097 --- /dev/null +++ b/src/layout/ranked.rs @@ -0,0 +1,195 @@ +//! Unified trait for type- and runtime-level array rank. +//! +//! This module defines the [`Ranked`] trait, which bridges compile-time and runtime representations +//! of array dimensionality. It enables generic code to query the number of dimensions (rank) of an +//! array, whether known statically (via [`Rank`]) or only at runtime. Blanket +//! implementations are provided for common pointer and container types. + +use alloc::boxed::Box; +use alloc::vec::Vec; + +use crate::{ + layout::rank::{Rank, R1}, + ArrayBase, + ArrayParts, + ArrayRef, + LayoutRef, + RawData, + RawRef, +}; + +/// A trait to unify type- and runtime-level number of dimensions. +/// +/// The [`Rank`] trait captures array rank at the type level; however it +/// is limited at runtime. If the `Rank` is dynamic (i.e., [`DynRank`][DynRank]) +/// then the dimensionality cannot be known at compile time. This trait unifies type- +/// and runtime-level dimensionality by providing: +/// 1. An associated type, [`NDim`][NDim], with type-level dimensionality +/// 2. A function, [`ndim`][ndim], that can give the dimensionality at runtime. +/// +/// [DynRank]: crate::layout::rank::DynRank +/// [NDim]: Ranked::NDim +/// [ndim]: Ranked::ndim +pub trait Ranked +{ + /// The compile-time rank of the type; can be [`DynRank`][DynRank] if unknown. + /// + /// [DynRank]: crate::layout::rank::DynRank + type NDim: Rank; + + /// The runtime number of dimensions of the type. + fn ndim(&self) -> usize; +} + +mod blanket_impls +{ + use super::*; + use alloc::rc::Rc; + + #[cfg(target_has_atomic = "ptr")] + use alloc::sync::Arc; + #[cfg(not(target_has_atomic = "ptr"))] + use portable_atomic_util::Arc; + + impl Ranked for &T + where T: Ranked + { + type NDim = T::NDim; + + fn ndim(&self) -> usize + { + (*self).ndim() + } + } + + impl Ranked for &mut T + where T: Ranked + { + type NDim = T::NDim; + + fn ndim(&self) -> usize + { + (**self).ndim() + } + } + + impl Ranked for Arc + where T: Ranked + { + type NDim = T::NDim; + + fn ndim(&self) -> usize + { + (**self).ndim() + } + } + + impl Ranked for Rc + where T: Ranked + { + type NDim = T::NDim; + + fn ndim(&self) -> usize + { + (**self).ndim() + } + } + + impl Ranked for Box + where T: Ranked + { + type NDim = T::NDim; + + fn ndim(&self) -> usize + { + (**self).ndim() + } + } +} + +impl Ranked for [T] +{ + type NDim = R1; + + fn ndim(&self) -> usize + { + 1 + } +} + +impl Ranked for Vec +{ + type NDim = R1; + + fn ndim(&self) -> usize + { + 1 + } +} + +impl Ranked for [T; N] +{ + type NDim = R1; + + fn ndim(&self) -> usize + { + 1 + } +} + +impl Ranked for ArrayParts +where D: Ranked +{ + type NDim = D::NDim; + + fn ndim(&self) -> usize + { + self.dim.ndim() + } +} + +impl Ranked for ArrayBase +where + S: RawData, + D: Ranked, +{ + type NDim = D::NDim; + + fn ndim(&self) -> usize + { + self.parts.ndim() + } +} + +impl Ranked for LayoutRef +where D: Ranked +{ + type NDim = D::NDim; + + fn ndim(&self) -> usize + { + self.0.ndim() + } +} + +impl Ranked for ArrayRef +where D: Ranked +{ + type NDim = D::NDim; + + fn ndim(&self) -> usize + { + self.0.ndim() + } +} + +impl Ranked for RawRef +where D: Ranked +{ + type NDim = D::NDim; + + fn ndim(&self) -> usize + { + self.0.ndim() + } +}