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")]