From 1c542cbeb67dee12fb09c18ccad570903cb0763d Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Mon, 31 Mar 2025 20:14:04 +0530 Subject: [PATCH 1/7] remove the Float associated type and only implement exp, log, and trig functions for floating-point types --- src/array.rs | 90 ++++++++++++-------------- src/host/ops/mod.rs | 63 +++++++++--------- src/host/platform.rs | 16 +++-- src/lib.rs | 141 ++++++++++++++++------------------------- src/opencl/mod.rs | 70 ++++++++++++++------ src/opencl/ops.rs | 65 ++++++++++--------- src/opencl/platform.rs | 16 +++-- src/ops/mod.rs | 18 ++++-- src/platform.rs | 16 +++-- tests/construct.rs | 32 ++++++++++ tests/reference.rs | 6 +- 11 files changed, 291 insertions(+), 242 deletions(-) diff --git a/src/array.rs b/src/array.rs index c089549..3853e4f 100644 --- a/src/array.rs +++ b/src/array.rs @@ -929,7 +929,7 @@ pub trait NDArrayUnary: NDArray + Sized { impl NDArrayUnary for Array where - T: Number, + T: Float, A: Access, P: ElementwiseUnary, { @@ -1359,7 +1359,9 @@ pub trait NDArrayMath>: NDArray + Sized { fn div(self, rhs: O) -> Result, Error>; /// Construct a logarithm operation with the given `base`. - fn log(self, base: O) -> Result, Error>; + fn log(self, base: O) -> Result, Error> + where + Self::DType: Float; /// Construct a multiplication operation with the given `rhs`. fn mul(self, rhs: O) -> Result, Error>; @@ -1397,14 +1399,17 @@ where self, rhs: Array, ) -> Result, Error> { - same_shape("div", self.shape(), rhs.shape())?; + same_shape("divide", self.shape(), rhs.shape())?; self.apply_dual(rhs, |platform, left, right| platform.div(left, right)) } fn log( self, base: Array, - ) -> Result, Error> { + ) -> Result, Error> + where + T: Float, + { same_shape("log", self.shape(), base.shape())?; self.apply_dual(base, |platform, left, right| platform.log(left, right)) } @@ -1413,7 +1418,7 @@ where self, rhs: Array, ) -> Result, Error> { - same_shape("mul", self.shape(), rhs.shape())?; + same_shape("multiply", self.shape(), rhs.shape())?; self.apply_dual(rhs, |platform, left, right| platform.mul(left, right)) } @@ -1421,7 +1426,7 @@ where self, exp: Array, ) -> Result, Error> { - same_shape("pow", self.shape(), exp.shape())?; + same_shape("exponentiate", self.shape(), exp.shape())?; self.apply_dual(exp, |platform, left, right| platform.pow(left, right)) } @@ -1429,7 +1434,7 @@ where self, rhs: Array, ) -> Result, Error> { - same_shape("sub", self.shape(), rhs.shape())?; + same_shape("subtract", self.shape(), rhs.shape())?; self.apply_dual(rhs, |platform, left, right| platform.sub(left, right)) } @@ -1440,7 +1445,7 @@ where where T: Real, { - same_shape("rem", self.shape(), rhs.shape())?; + same_shape("remainder", self.shape(), rhs.shape())?; self.apply_dual(rhs, |platform, left, right| platform.rem(left, right)) } } @@ -1465,7 +1470,9 @@ pub trait NDArrayMathScalar: NDArray + Sized { fn log_scalar( self, base: Self::DType, - ) -> Result, Error>; + ) -> Result, Error> + where + Self::DType: Float; /// Construct a scalar multiplication operation. fn mul_scalar( @@ -1525,7 +1532,10 @@ where fn log_scalar( self, base: Self::DType, - ) -> Result, Error> { + ) -> Result, Error> + where + Self::DType: Float, + { self.apply(|platform, arg| platform.log_scalar(arg, base)) } @@ -1677,95 +1687,77 @@ impl fmt::Debug for Array { /// Array trigonometry methods pub trait NDArrayTrig: NDArray + Sized { - type Output: Access<::Float>; + type Output: Access; /// Construct a new sine operation. - fn sin( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn sin(self) -> Result, Error>; /// Construct a new arcsine operation. - fn asin( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn asin(self) -> Result, Error>; /// Construct a new hyperbolic sine operation. - fn sinh( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn sinh(self) -> Result, Error>; /// Construct a new cos operation. - fn cos( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn cos(self) -> Result, Error>; /// Construct a new arccosine operation. - fn acos( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn acos(self) -> Result, Error>; /// Construct a new hyperbolic cosine operation. - fn cosh( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn cosh(self) -> Result, Error>; /// Construct a new tangent operation. - fn tan( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn tan(self) -> Result, Error>; /// Construct a new arctangent operation. - fn atan( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn atan(self) -> Result, Error>; /// Construct a new hyperbolic tangent operation. - fn tanh( - self, - ) -> Result::Float, Self::Output, Self::Platform>, Error>; + fn tanh(self) -> Result, Error>; } impl NDArrayTrig for Array where - T: Number, + T: Float, A: Access, P: ElementwiseTrig, { type Output = AccessOp; - fn sin(self) -> Result, Error> { + fn sin(self) -> Result, Error> { self.apply(|platform, access| platform.sin(access)) } - fn asin(self) -> Result, Error> { + fn asin(self) -> Result, Error> { self.apply(|platform, access| platform.asin(access)) } - fn sinh(self) -> Result, Error> { + fn sinh(self) -> Result, Error> { self.apply(|platform, access| platform.sinh(access)) } - fn cos(self) -> Result, Error> { + fn cos(self) -> Result, Error> { self.apply(|platform, access| platform.cos(access)) } - fn acos(self) -> Result, Error> { + fn acos(self) -> Result, Error> { self.apply(|platform, access| platform.acos(access)) } - fn cosh(self) -> Result, Error> { + fn cosh(self) -> Result, Error> { self.apply(|platform, access| platform.cosh(access)) } - fn tan(self) -> Result, Error> { + fn tan(self) -> Result, Error> { self.apply(|platform, access| platform.tan(access)) } - fn atan(self) -> Result, Error> { + fn atan(self) -> Result, Error> { self.apply(|platform, access| platform.atan(access)) } - fn tanh(self) -> Result, Error> { + fn tanh(self) -> Result, Error> { self.apply(|platform, access| platform.tanh(access)) } } @@ -1998,7 +1990,7 @@ fn reduce_axes(shape: &[usize], axes: &[usize], keepdims: bool) -> Result Result<(), Error> { +pub fn same_shape(op_name: &'static str, left: &[usize], right: &[usize]) -> Result<(), Error> { if left == right { Ok(()) } else if can_broadcast(left, right) { diff --git a/src/host/ops/mod.rs b/src/host/ops/mod.rs index cf12cc7..92c2a52 100644 --- a/src/host/ops/mod.rs +++ b/src/host/ops/mod.rs @@ -309,14 +309,6 @@ impl Dual { } } - pub fn log(left: L, right: R) -> Self { - Self { - left, - right, - zip: |a, b| T::from_float(a.to_float().log(b.to_float())), - } - } - pub fn mul(left: L, right: R) -> Self { Self { left, @@ -353,6 +345,17 @@ impl Dual { } } +// floating-point arithmetic +impl Dual { + pub fn log(left: L, right: R) -> Self { + Self { + left, + right, + zip: T::log, + } + } +} + // boolean operations impl Dual { pub fn and(left: L, right: R) -> Self { @@ -928,12 +931,6 @@ impl Scalar { Self::new(access, scalar, T::div) } - pub fn log(access: A, scalar: T) -> Self { - Self::new(access, scalar, |a, b| { - T::from_float(a.to_float().log(b.to_float())) - }) - } - pub fn mul(access: A, scalar: T) -> Self { Self::new(access, scalar, T::mul) } @@ -954,6 +951,12 @@ impl Scalar { } } +impl Scalar { + pub fn log(access: A, scalar: T) -> Self { + Self::new(access, scalar, T::log) + } +} + impl Scalar where T: Number, @@ -1550,19 +1553,13 @@ pub struct Unary { op: fn(IT) -> OT, } -impl, T: Number> Unary { +impl, T: Float> Unary { pub fn exp(access: A) -> Self { - Self { - access, - op: |n| T::from_float(n.to_float().exp()), - } + Self { access, op: T::exp } } pub fn ln(access: A) -> Self { - Self { - access, - op: |n| T::from_float(n.to_float().ln()), - } + Self { access, op: T::ln } } } @@ -1584,67 +1581,67 @@ impl, T: Real> Unary { } } -impl, T: Number> Unary { +impl, T: Float> Unary { pub fn sin(access: A) -> Self { Self { access, - op: |n| n.to_float().sin(), + op: |n| n.sin(), } } pub fn asin(access: A) -> Self { Self { access, - op: |n| n.to_float().asin(), + op: |n| n.asin(), } } pub fn sinh(access: A) -> Self { Self { access, - op: |n| n.to_float().sinh(), + op: |n| n.sinh(), } } pub fn cos(access: A) -> Self { Self { access, - op: |n| n.to_float().cos(), + op: |n| n.cos(), } } pub fn acos(access: A) -> Self { Self { access, - op: |n| n.to_float().acos(), + op: |n| n.acos(), } } pub fn cosh(access: A) -> Self { Self { access, - op: |n| n.to_float().cosh(), + op: |n| n.cosh(), } } pub fn tan(access: A) -> Self { Self { access, - op: |n| n.to_float().tan(), + op: |n| n.tan(), } } pub fn atan(access: A) -> Self { Self { access, - op: |n| n.to_float().atan(), + op: |n| n.atan(), } } pub fn tanh(access: A) -> Self { Self { access, - op: |n| n.to_float().tanh(), + op: |n| n.tanh(), } } } diff --git a/src/host/platform.rs b/src/host/platform.rs index 59300a7..a7a7820 100644 --- a/src/host/platform.rs +++ b/src/host/platform.rs @@ -458,7 +458,10 @@ where Ok(Dual::div(left, right).into()) } - fn log(self, arg: L, base: R) -> Result, Error> { + fn log(self, arg: L, base: R) -> Result, Error> + where + T: Float, + { Ok(Dual::log(arg, base).into()) } @@ -493,7 +496,10 @@ impl, T: Number> ElementwiseScalar for Host { Ok(Scalar::div(left, right).into()) } - fn log_scalar(self, arg: A, base: T) -> Result, Error> { + fn log_scalar(self, arg: A, base: T) -> Result, Error> + where + T: Float, + { Ok(Scalar::log(arg, base).into()) } @@ -529,8 +535,8 @@ impl, T: Float> ElementwiseNumeric for Host { } } -impl, T: Number> ElementwiseTrig for Host { - type Op = Unary; +impl, T: Float> ElementwiseTrig for Host { + type Op = Unary; fn sin(self, access: A) -> Result, Error> { Ok(Unary::sin(access).into()) @@ -569,7 +575,7 @@ impl, T: Number> ElementwiseTrig for Host { } } -impl, T: Number> ElementwiseUnary for Host { +impl, T: Float> ElementwiseUnary for Host { type Op = Unary; fn exp(self, access: A) -> Result, Error> { diff --git a/src/lib.rs b/src/lib.rs index 2878410..2ce9d0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ use smallvec::SmallVec; pub use access::*; pub use array::{ - MatrixDual, MatrixUnary, NDArray, NDArrayAbs, NDArrayBoolean, NDArrayBooleanScalar, + same_shape, MatrixDual, MatrixUnary, NDArray, NDArrayAbs, NDArrayBoolean, NDArrayBooleanScalar, NDArrayCast, NDArrayCompare, NDArrayCompareScalar, NDArrayMath, NDArrayMathScalar, NDArrayNumeric, NDArrayRead, NDArrayReduce, NDArrayReduceAll, NDArrayReduceBoolean, NDArrayTransform, NDArrayTrig, NDArrayUnary, NDArrayUnaryBoolean, NDArrayWhere, NDArrayWrite, @@ -75,23 +75,9 @@ pub trait Number: CLType + Into + CastFrom + Default { /// The one value of this data type. const ONE: Self; - /// Whether this is a floating-point data type. - const IS_FLOAT: bool; - - /// Whether this is a read-valued data type. - const IS_REAL: bool; - /// The absolute value type of this [`Number`]. type Abs: Number; - /// The floating-point type used to represent this type in floating-point-only operations. - type Float: Float; - - // constructors - - /// Construct an instance of this type from an instance of its floating-point type. - fn from_float(float: Self::Float) -> Self; - // arithmetic /// Construct an instance of this type from a [`f64`]. @@ -111,32 +97,17 @@ pub trait Number: CLType + Into + CastFrom + Default { /// Raise this value to the power of the given `exp`onent. fn pow(self, exp: Self) -> Self; - - // conversions - - /// Convert this value to a floating-point value. - fn to_float(self) -> Self::Float; } macro_rules! number { - ($t:ty, $is_float:expr, $is_real:expr, $abs_t:ty, $one:expr, $zero:expr, $float:ty, $abs:expr, $add:expr, $div:expr, $mul:expr, $sub:expr, $pow:expr) => { + ($t:ty, $abs_t:ty, $one:expr, $zero:expr, $abs:expr, $add:expr, $div:expr, $mul:expr, $sub:expr, $pow:expr) => { impl Number for $t { const ONE: Self = $one; const ZERO: Self = $zero; - const IS_FLOAT: bool = $is_float; - - const IS_REAL: bool = $is_float; - type Abs = $abs_t; - type Float = $float; - - fn from_float(float: $float) -> Self { - float as $t - } - fn abs(self) -> Self::Abs { $abs(self) } @@ -160,10 +131,6 @@ macro_rules! number { fn pow(self, exp: Self) -> Self { ($pow)(self, exp) } - - fn to_float(self) -> $float { - self as $float - } } }; } @@ -171,12 +138,9 @@ macro_rules! number { #[cfg(feature = "complex")] number!( Complex32, - true, - false, f32, Complex32::new(1., 0.), Complex32::new(0., 0.), - Self, Complex32::norm, Add::add, Div::div, @@ -188,12 +152,9 @@ number!( #[cfg(feature = "complex")] number!( Complex64, - true, - false, f64, Complex64::new(1., 0.), Complex64::new(0., 0.), - Self, Complex64::norm, Add::add, Div::div, @@ -204,12 +165,9 @@ number!( number!( f32, - true, - true, Self, 1., 0., - Self, f32::abs, Add::add, Div::div, @@ -220,12 +178,9 @@ number!( number!( f64, - true, - true, Self, 1., 0., - Self, f64::abs, Add::add, Div::div, @@ -236,12 +191,9 @@ number!( number!( i8, - false, - true, Self, 1, 0, - f32, Self::wrapping_abs, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -252,12 +204,9 @@ number!( number!( i16, - false, - true, Self, 1, 0, - f32, Self::wrapping_abs, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -268,12 +217,9 @@ number!( number!( i32, - false, - true, Self, 1, 0, - f32, Self::wrapping_abs, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -284,12 +230,9 @@ number!( number!( i64, - false, - true, Self, 1, 0, - f64, Self::wrapping_abs, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -303,12 +246,9 @@ number!( number!( u8, - false, - true, Self, 1, 0, - f32, id, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -319,12 +259,9 @@ number!( number!( u16, - false, - true, Self, 1, 0, - f32, id, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -335,12 +272,9 @@ number!( number!( u32, - false, - true, Self, 1, 0, - f32, id, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -351,12 +285,9 @@ number!( number!( u64, - false, - true, Self, 1, 0, - f64, id, Self::wrapping_add, |l, r| if r == 0 { 0 } else { Self::wrapping_div(l, r) }, @@ -389,8 +320,7 @@ pub trait Real: Number + PartialOrd { #[cfg(feature = "opencl")] /// A real-valued [`Number`] -// TODO: move the CLElementTrig boundary to Number after implementing complex trigonometry -pub trait Real: Number + PartialOrd + opencl::CLElementReal + opencl::CLElementTrig { +pub trait Real: Number + PartialOrd + opencl::CLElementReal { /// The maximum value of this data type. const MAX: Self; @@ -453,8 +383,58 @@ real!(u16, Self::wrapping_rem, Ord::cmp, id); real!(u32, Self::wrapping_rem, Ord::cmp, id); real!(u64, Self::wrapping_rem, Ord::cmp, id); +#[cfg(not(feature = "opencl"))] /// A floating-point [`Number`] -pub trait Float: Number { +pub trait Float: Number { + // numeric methods + /// Return `true` if this [`Float`] is infinite (positive or negative infinity). + fn is_inf(self) -> bool; + + /// Return `true` if this [`Float`] is not a number (e.g. a float representation of `1.0 / 0.0`). + fn is_nan(self) -> bool; + + // logarithms + /// Exponentiate this number (equivalent to `consts::E.pow(self)`). + fn exp(self) -> Self; + + /// Return the natural logarithm of this [`Float`]. + fn ln(self) -> Self; + + /// Calculate the logarithm of this [`Float`] w/r/t the given `base`. + fn log(self, base: Self) -> Self; + + // trigonometry + /// Return the sine of this [`Float`] (in radians). + fn sin(self) -> Self; + + /// Return the arcsine of this [`Float`] (in radians). + fn asin(self) -> Self; + + /// Return the hyperbolic sine of this [`Float`] (in radians). + fn sinh(self) -> Self; + + /// Return the cosine of this [`Float`] (in radians). + fn cos(self) -> Self; + + /// Return the arcsine of this [`Float`] (in radians). + fn acos(self) -> Self; + + /// Return the hyperbolic cosine of this [`Float`] (in radians). + fn cosh(self) -> Self; + + /// Return the tangent of this [`Float`] (in radians). + fn tan(self) -> Self; + + /// Return the arctangent of this [`Float`] (in radians). + fn atan(self) -> Self; + + /// Return the hyperbolic tangent of this [`Float`] (in radians). + fn tanh(self) -> Self; +} + +#[cfg(feature = "opencl")] +/// A floating-point [`Number`] +pub trait Float: Number + opencl::CLElementTrig { // numeric methods /// Return `true` if this [`Float`] is infinite (positive or negative infinity). fn is_inf(self) -> bool; @@ -631,15 +611,13 @@ complex_type!(Complex64, f64); /// An array math error pub enum Error { Bounds(String), - #[cfg(feature = "opencl")] - Format(String), Unsupported(String), #[cfg(feature = "opencl")] OCL(std::sync::Arc), } impl Error { - fn bounds(msg: String) -> Self { + pub fn bounds(msg: String) -> Self { #[cfg(feature = "debug_crash")] panic!("{}", msg); @@ -647,8 +625,7 @@ impl Error { Self::Bounds(msg) } - #[allow(dead_code)] - fn unsupported(msg: String) -> Self { + pub fn unsupported(msg: String) -> Self { #[cfg(feature = "debug_crash")] panic!("{}", msg); @@ -663,8 +640,6 @@ impl Clone for Error { fn clone(&self) -> Self { match self { Self::Bounds(msg) => Self::Bounds(msg.clone()), - #[cfg(feature = "opencl")] - Self::Format(msg) => Self::Format(msg.clone()), Self::Unsupported(msg) => Self::Unsupported(msg.clone()), #[cfg(feature = "opencl")] Self::OCL(cause) => Self::OCL(cause.clone()), @@ -687,8 +662,6 @@ impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Bounds(cause) => f.write_str(cause), - #[cfg(feature = "opencl")] - Self::Format(cause) => cause.fmt(f), Self::Unsupported(cause) => f.write_str(cause), #[cfg(feature = "opencl")] Self::OCL(cause) => cause.fmt(f), @@ -700,8 +673,6 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Bounds(cause) => f.write_str(cause), - #[cfg(feature = "opencl")] - Self::Format(cause) => cause.fmt(f), Self::Unsupported(cause) => f.write_str(cause), #[cfg(feature = "opencl")] Self::OCL(cause) => cause.fmt(f), diff --git a/src/opencl/mod.rs b/src/opencl/mod.rs index d3f6e1d..1ae49e1 100644 --- a/src/opencl/mod.rs +++ b/src/opencl/mod.rs @@ -5,7 +5,7 @@ use ocl::OclPrm; use crate::access::{AccessBuf, AccessOp}; use crate::host::VEC_MIN_SIZE; -use crate::Number; +use crate::Float; use programs::{ElementDual, ElementUnary}; @@ -37,9 +37,9 @@ fn real_cmp(op: &'static str) -> String { format!("return lhs {op} rhs;") } -fn real_trig(name: &'static str) -> ElementUnary { +fn real_trig(name: &'static str) -> ElementUnary { debug_assert!(name.starts_with('_')); - ElementUnary::new::(name, format!("return {}(n);", &name[1..])) + ElementUnary::new::(name, format!("return {}(n);", &name[1..])) } #[cfg(feature = "complex")] @@ -322,8 +322,6 @@ impl CLElement for i8 { impl CLElementReal for i8 {} -cl_trig_real!(i8); - impl CLElement for i16 { const REAL: bool = true; const TYPE: &'static str = "short"; @@ -331,8 +329,6 @@ impl CLElement for i16 { impl CLElementReal for i16 {} -cl_trig_real!(i16); - impl CLElement for i32 { const REAL: bool = true; const TYPE: &'static str = "int"; @@ -340,8 +336,6 @@ impl CLElement for i32 { impl CLElementReal for i32 {} -cl_trig_real!(i32); - impl CLElement for i64 { const REAL: bool = true; const TYPE: &'static str = "long"; @@ -349,8 +343,6 @@ impl CLElement for i64 { impl CLElementReal for i64 {} -cl_trig_real!(i64); - impl CLElement for u8 { const REAL: bool = true; const TYPE: &'static str = "uchar"; @@ -358,8 +350,6 @@ impl CLElement for u8 { impl CLElementReal for u8 {} -cl_trig_real!(u8); - impl CLElement for u16 { const REAL: bool = true; const TYPE: &'static str = "ushort"; @@ -367,8 +357,6 @@ impl CLElement for u16 { impl CLElementReal for u16 {} -cl_trig_real!(u16); - impl CLElement for u32 { const REAL: bool = true; const TYPE: &'static str = "uint"; @@ -376,8 +364,6 @@ impl CLElement for u32 { impl CLElementReal for u32 {} -cl_trig_real!(u32); - impl CLElement for u64 { const REAL: bool = true; const TYPE: &'static str = "ulong"; @@ -385,8 +371,6 @@ impl CLElement for u64 { impl CLElementReal for u64 {} -cl_trig_real!(u64); - #[cfg(feature = "complex")] macro_rules! cl_complex { ($t:ty, $ct:expr) => { @@ -540,6 +524,54 @@ cl_complex!(f32, "float2"); #[cfg(feature = "complex")] cl_complex!(f64, "double2"); +#[cfg(feature = "complex")] +macro_rules! cl_trig_complex { + ($t:ty) => { + impl CLElementTrig for $t { + fn cl_sin() -> ElementUnary { + todo!() + } + + fn cl_asin() -> ElementUnary { + todo!() + } + + fn cl_sinh() -> ElementUnary { + todo!() + } + + fn cl_cos() -> ElementUnary { + todo!() + } + + fn cl_acos() -> ElementUnary { + todo!() + } + + fn cl_cosh() -> ElementUnary { + todo!() + } + + fn cl_tan() -> ElementUnary { + todo!() + } + + fn cl_atan() -> ElementUnary { + todo!() + } + + fn cl_tanh() -> ElementUnary { + todo!() + } + } + }; +} + +#[cfg(feature = "complex")] +cl_trig_complex!(num_complex::Complex); +#[cfg(feature = "complex")] +cl_trig_complex!(num_complex::Complex); + lazy_static! { pub static ref CL_PLATFORM: platform::CLPlatform = { assert!(VEC_MIN_SIZE < GPU_MIN_SIZE); diff --git a/src/opencl/ops.rs b/src/opencl/ops.rs index 1f6db62..9563696 100644 --- a/src/opencl/ops.rs +++ b/src/opencl/ops.rs @@ -14,7 +14,7 @@ use crate::{ }; use super::platform::OpenCL; -use super::{programs, CLElementTrig, TILE_SIZE, WG_SIZE}; +use super::{programs, TILE_SIZE, WG_SIZE}; pub struct Cast { access: A, @@ -141,13 +141,6 @@ impl Dual { Self::new(left, right, program, T::div) } - pub fn log(arg: L, exp: R) -> Result { - let program = programs::elementwise::dual(T::cl_log())?; - Self::new(arg, exp, program, |a, e| { - T::from_float(a.to_float().log(e.to_float())) - }) - } - pub fn mul(left: L, right: R) -> Result { let program = programs::elementwise::dual(T::cl_mul())?; Self::new(left, right, program, T::mul) @@ -164,6 +157,14 @@ impl Dual { } } +// floating-point arithmetic +impl Dual { + pub fn log(arg: L, exp: R) -> Result { + let program = programs::elementwise::dual(T::cl_log())?; + Self::new(arg, exp, program, T::log) + } +} + impl Dual { pub fn rem(left: L, right: R) -> Result { let program = programs::elementwise::dual(T::cl_rem())?; @@ -1144,12 +1145,6 @@ impl Scalar { Self::new(access, scalar, T::cl_div(), T::div) } - pub fn log(access: A, scalar: T) -> Result { - Self::new(access, scalar, T::cl_log(), |a, e| { - T::from_float(a.to_float().log(e.to_float())) - }) - } - pub fn mul(access: A, scalar: T) -> Result { Self::new(access, scalar, T::cl_mul(), T::mul) } @@ -1163,6 +1158,12 @@ impl Scalar { } } +impl Scalar { + pub fn log(access: A, scalar: T) -> Result { + Self::new(access, scalar, T::cl_log(), T::log) + } +} + impl Scalar where A: Access, @@ -1493,19 +1494,20 @@ impl Unary { } } -impl Unary { +impl Unary { pub fn exp(access: A) -> Result { - Self::new(access, T::cl_exp(), |n| T::from_float(n.to_float().ln())) + Self::new(access, T::cl_exp(), T::ln) } pub fn ln(access: A) -> Result { - Self::new(access, T::cl_ln(), |n| T::from_float(n.to_float().ln())) + Self::new(access, T::cl_ln(), T::ln) } -} -impl Unary { - pub fn round(access: A) -> Result { - Self::new(access, T::cl_round(), |n| T::from_float(n.to_float().ln())) + pub fn round(access: A) -> Result + where + T: Real, + { + Self::new(access, T::cl_round(), T::round) } } @@ -1515,40 +1517,41 @@ impl Unary { } } -impl Unary { +impl Unary { pub fn sin(access: A) -> Result { - Self::new(access, T::cl_sin(), |n| n.to_float().sin()) + Self::new(access, T::cl_sin(), T::sin) } pub fn sinh(access: A) -> Result { - Self::new(access, T::cl_sinh(), |n| n.to_float().sinh()) + Self::new(access, T::cl_sinh(), T::sinh) } pub fn asin(access: A) -> Result { - Self::new(access, T::cl_asin(), |n| n.to_float().asin()) + Self::new(access, T::cl_asin(), T::asin) } pub fn cos(access: A) -> Result { - Self::new(access, T::cl_cos(), |n| n.to_float().cos()) + Self::new(access, T::cl_cos(), T::cos) } pub fn cosh(access: A) -> Result { - Self::new(access, T::cl_cosh(), |n| n.to_float().cosh()) + Self::new(access, T::cl_cosh(), T::cosh) } pub fn acos(access: A) -> Result { - Self::new(access, T::cl_acos(), |n| n.to_float().acos()) + Self::new(access, T::cl_acos(), T::acos) } + pub fn tan(access: A) -> Result { - Self::new(access, T::cl_tan(), |n| n.to_float().tan()) + Self::new(access, T::cl_tan(), T::tan) } pub fn tanh(access: A) -> Result { - Self::new(access, T::cl_tanh(), |n| n.to_float().tanh()) + Self::new(access, T::cl_tanh(), T::tanh) } pub fn atan(access: A) -> Result { - Self::new(access, T::cl_atan(), |n| n.to_float().atan()) + Self::new(access, T::cl_atan(), T::atan) } } diff --git a/src/opencl/platform.rs b/src/opencl/platform.rs index a3f6930..23ce55f 100644 --- a/src/opencl/platform.rs +++ b/src/opencl/platform.rs @@ -463,7 +463,10 @@ where Dual::div(left, right).map(AccessOp::from) } - fn log(self, arg: L, base: R) -> Result, Error> { + fn log(self, arg: L, base: R) -> Result, Error> + where + T: Float, + { Dual::log(arg, base).map(AccessOp::from) } @@ -498,7 +501,10 @@ impl, T: Number> ElementwiseScalar for OpenCL { Scalar::div(left, right).map(AccessOp::from) } - fn log_scalar(self, arg: A, base: T) -> Result, Error> { + fn log_scalar(self, arg: A, base: T) -> Result, Error> + where + T: Float, + { Scalar::log(arg, base).map(AccessOp::from) } @@ -535,8 +541,8 @@ impl, T: Float> ElementwiseNumeric for OpenCL { } // TODO: implement this trait separately per-type and remote the CLElementTrig boundary -impl, T: Number + CLElementTrig> ElementwiseTrig for OpenCL { - type Op = Unary; +impl, T: Float + CLElementTrig> ElementwiseTrig for OpenCL { + type Op = Unary; fn sin(self, access: A) -> Result, Error> { Unary::sin(access).map(AccessOp::from) @@ -575,7 +581,7 @@ impl, T: Number + CLElementTrig> ElementwiseTrig for OpenCL { } } -impl, T: Number> ElementwiseUnary for OpenCL { +impl, T: Float> ElementwiseUnary for OpenCL { type Op = Unary; fn exp(self, access: A) -> Result, Error> { diff --git a/src/ops/mod.rs b/src/ops/mod.rs index 296109d..2e31ca5 100644 --- a/src/ops/mod.rs +++ b/src/ops/mod.rs @@ -8,8 +8,8 @@ use crate::buffer::Buffer; use crate::opencl; use crate::platform::{Platform, PlatformInstance}; use crate::{ - host, range_shape, strides_for, Axes, AxisRange, BufferConverter, Error, Number, Range, Real, - Shape, Strides, + host, range_shape, strides_for, Axes, AxisRange, BufferConverter, Error, Float, Number, Range, + Real, Shape, Strides, }; #[cfg(feature = "complex")] @@ -189,7 +189,9 @@ where fn div(self, left: L, right: R) -> Result, Error>; - fn log(self, arg: L, base: R) -> Result, Error>; + fn log(self, arg: L, base: R) -> Result, Error> + where + T: Float; fn mul(self, left: L, right: R) -> Result, Error>; @@ -213,7 +215,9 @@ where fn div_scalar(self, left: A, right: T) -> Result, Error>; - fn log_scalar(self, arg: A, base: T) -> Result, Error>; + fn log_scalar(self, arg: A, base: T) -> Result, Error> + where + T: Float; fn mul_scalar(self, left: A, right: T) -> Result, Error>; @@ -241,9 +245,9 @@ where pub trait ElementwiseTrig: PlatformInstance where A: Access, - T: Number, + T: Float, { - type Op: ReadOp; + type Op: ReadOp; fn sin(self, access: A) -> Result, Error>; @@ -267,7 +271,7 @@ where pub trait ElementwiseUnary: PlatformInstance where A: Access, - T: Number, + T: Float, { type Op: ReadOp; diff --git a/src/platform.rs b/src/platform.rs index 7d190ba..a1841c1 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -409,7 +409,10 @@ where } } - fn log(self, arg: L, base: R) -> Result, Error> { + fn log(self, arg: L, base: R) -> Result, Error> + where + T: Float, + { match self { #[cfg(feature = "opencl")] Self::CL(cl) => cl.log(arg, base).map(AccessOp::wrap), @@ -472,7 +475,10 @@ impl, T: Number> ElementwiseScalar for Platform { } } - fn log_scalar(self, arg: A, base: T) -> Result, Error> { + fn log_scalar(self, arg: A, base: T) -> Result, Error> + where + T: Float, + { match self { #[cfg(feature = "opencl")] Self::CL(cl) => cl.log_scalar(arg, base).map(AccessOp::wrap), @@ -536,8 +542,8 @@ impl, T: Float> ElementwiseNumeric for Platform { } } -impl, T: Real> ElementwiseTrig for Platform { - type Op = Unary; +impl, T: Float> ElementwiseTrig for Platform { + type Op = Unary; fn sin(self, access: A) -> Result, Error> { match self { @@ -612,7 +618,7 @@ impl, T: Real> ElementwiseTrig for Platform { } } -impl, T: Number> ElementwiseUnary for Platform { +impl, T: Float> ElementwiseUnary for Platform { type Op = Unary; fn exp(self, access: A) -> Result, Error> { diff --git a/tests/construct.rs b/tests/construct.rs index b7c45c6..333f10f 100644 --- a/tests/construct.rs +++ b/tests/construct.rs @@ -1,5 +1,37 @@ use ha_ndarray::*; +#[test] +fn test_concat() -> Result<(), Error> { + let first = ArrayBuf::new(vec![1, 2, 3], shape![1, 3])?; + let second = ArrayBuf::new(vec![4, 5, 6], shape![1, 3])?; + + let concatenated = Array::concat(vec![first, second])?; + assert_eq!( + concatenated.buffer()?.to_slice()?.into_vec(), + vec![1, 2, 3, 4, 5, 6] + ); + + assert_eq!(concatenated.shape(), &[2, 3]); + + Ok(()) +} + +#[test] +fn test_transpose_concat() -> Result<(), Error> { + let first = ArrayBuf::new(vec![1, 2, 3, 4, 5, 6], shape![2, 3])?; + let second = ArrayBuf::new(vec![7, 8, 9, 10, 11, 12], shape![2, 3])?; + let concatenated = Array::transpose_concat(vec![first, second], 1)?; + + assert_eq!(concatenated.shape(), &[2, 6]); + + assert_eq!( + concatenated.buffer()?.to_slice()?.into_vec(), + vec![1, 2, 3, 7, 8, 9, 4, 5, 6, 10, 11, 12], + ); + + Ok(()) +} + #[test] fn test_range() -> Result<(), Error> { use rayon::prelude::*; diff --git a/tests/reference.rs b/tests/reference.rs index 14c7d88..5e59c41 100644 --- a/tests/reference.rs +++ b/tests/reference.rs @@ -1,6 +1,6 @@ use ha_ndarray::{ - shape, Access, AccessBuf, Array, ArrayBuf, Buffer, Error, NDArrayMath, NDArrayMathScalar, - NDArrayUnary, Number, + shape, Access, AccessBuf, Array, ArrayBuf, Buffer, Error, Float, NDArrayMath, + NDArrayMathScalar, NDArrayUnary, }; // the accuracy of these operations is covered by the other test modules @@ -9,7 +9,7 @@ use ha_ndarray::{ fn logit(p: Array) -> Result>, Error> where A: Access + Clone, - T: Number + std::ops::Neg, + T: Float + std::ops::Neg, { p.clone().div(p.add_scalar(-T::ONE)?)?.ln() } From 427cc892952e65dd5898ce94db434e0ef2280685 Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Tue, 1 Apr 2025 13:04:32 +0530 Subject: [PATCH 2/7] add convenience methods for matrix transposition --- src/array.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- src/host/ops/mod.rs | 9 ++++++--- src/lib.rs | 2 +- tests/linalg.rs | 20 ++++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/array.rs b/src/array.rs index 3853e4f..afbf6c1 100644 --- a/src/array.rs +++ b/src/array.rs @@ -1858,6 +1858,10 @@ where /// Matrix unary operations pub trait MatrixUnary: NDArray + fmt::Debug { type Diag: Access; + type Transpose: Access; + + /// Transpose a matrix or batch of matrices (i.e., transpose the last two dimensions). + fn mt(self) -> Result, Error>; /// Construct an operation to read the diagonal(s) of this matrix or batch of matrices. /// This will return an error if the last two dimensions of the batch are unequal. @@ -1868,9 +1872,19 @@ impl MatrixUnary for Array where T: Number, A: Access, - P: LinAlgUnary, + P: LinAlgUnary + Transform, { - type Diag = AccessOp; + type Diag = AccessOp<

>::Op, P>; + type Transpose = AccessOp<

>::Transpose, P>; + + fn mt(self) -> Result, Error> { + let ndim = self.ndim(); + let mut permutation = Axes::with_capacity(ndim); + permutation.extend((0..self.ndim() - 2).into_iter()); + permutation.push(ndim - 1); + permutation.push(ndim - 2); + self.transpose(permutation) + } fn diag(self) -> Result, P>, Error> { if self.ndim() >= 2 && self.shape.last() == self.shape.iter().nth_back(1) { @@ -1896,6 +1910,32 @@ where } } +#[cfg(feature = "complex")] +/// Complex matrix unary operations +pub trait MatrixUnaryComplex: MatrixUnary +where + Self::DType: Complex, +{ + type Hermitian: Access; + + /// Construct the conjugate transpose of a matrix or batch of matrices. + fn mh(self) -> Result, Error>; +} + +#[cfg(feature = "complex")] +impl MatrixUnaryComplex for Array +where + T: Complex, + A: Access, + P: complex::ElementwiseUnaryComplex + LinAlgUnary + Transform, +{ + type Hermitian = AccessOp; + + fn mh(self) -> Result, Error> { + self.mt().and_then(|array| array.conj()) + } +} + #[inline] fn can_broadcast(left: &[usize], right: &[usize]) -> bool { if left.len() < right.len() { diff --git a/src/host/ops/mod.rs b/src/host/ops/mod.rs index 92c2a52..1334ee0 100644 --- a/src/host/ops/mod.rs +++ b/src/host/ops/mod.rs @@ -791,14 +791,17 @@ where let mut product = StackVec::with_capacity(self.batch_size * a * c); - for _batch in 0..self.batch_size { + for batch in 0..self.batch_size { + let l_start = batch * a * b; + let r_start = batch * b * c; + for x in 0..a { for z in 0..c { let mut sum = T::ZERO; for y in 0..b { - let l_offset = (x * b) + y; - let r_offset = (y * c) + z; + let l_offset = l_start + (x * b) + y; + let r_offset = r_start + (y * c) + z; sum = T::add(sum, T::mul(left[l_offset], right[r_offset])); } diff --git a/src/lib.rs b/src/lib.rs index 2ce9d0e..feaf1b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,7 @@ pub use array::{ NDArrayTransform, NDArrayTrig, NDArrayUnary, NDArrayUnaryBoolean, NDArrayWhere, NDArrayWrite, }; #[cfg(feature = "complex")] -pub use array::{NDArrayComplex, NDArrayFourier}; +pub use array::{MatrixUnaryComplex, NDArrayComplex, NDArrayFourier}; pub use buffer::{Buffer, BufferConverter, BufferInstance, BufferMut}; pub use host::StackVec; pub use platform::*; diff --git a/tests/linalg.rs b/tests/linalg.rs index e3f520e..7269b82 100644 --- a/tests/linalg.rs +++ b/tests/linalg.rs @@ -29,3 +29,23 @@ fn test_matmul_12x20() -> Result<(), Error> { Ok(()) } + +#[test] +fn test_dot_product() -> Result<(), Error> { + let buffer = (0..16).into_iter().collect::>(); + let vectors = ArrayBuf::>::new(buffer.to_vec().into(), shape![4, 1, 4])?; + + let l: ArrayBuf> = vectors.as_ref(); + let r = l.clone().mt()?; + + let actual = l.matmul(r)?.buffer()?.to_slice()?.to_vec(); + + let mut expected = Vec::with_capacity(4 * 4); + for vector in buffer.chunks(4) { + expected.push(vector.iter().zip(vector.iter()).map(|(l, r)| l * r).sum()); + } + + assert_eq!(actual, expected); + + Ok(()) +} From c64923bc3986902b313ed95a79b9265bef3a8a0f Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Tue, 1 Apr 2025 15:37:07 +0530 Subject: [PATCH 3/7] improve validation logic in Array::transpose_concat --- src/array.rs | 45 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/array.rs b/src/array.rs index afbf6c1..41e1158 100644 --- a/src/array.rs +++ b/src/array.rs @@ -237,20 +237,41 @@ where arrays: Vec, axis: usize, ) -> Result, P>, Error> { - let permutation = if let Some(array) = arrays.first() { - if axis < array.ndim() { - let mut permutation: Axes = (0..array.ndim()).into_iter().collect(); - permutation.swap(0, axis); - Ok(permutation) - } else { - Err(Error::bounds(format!("{array:?} has no axis {axis}"))) - } + let shape = if let Some(first) = arrays.first() { + Ok(first.shape()) } else { Err(Error::bounds( - "cannot concatenate an empty list of arrays".into(), + "cannot concatenate an empty list of arrays".to_string(), )) }?; + for array in arrays.iter().skip(1) { + if array.ndim() == shape.len() { + for (x, (dim, a_dim)) in shape.iter().zip(array.shape()).enumerate() { + if x == axis { + // pass + } else if dim == a_dim { + // pass + } else { + return Err(Error::bounds(format!( + "cannot concatenate {:?} with {:?} at axis {axis}", + shape, + array.shape() + ))); + } + } + } else { + return Err(Error::bounds(format!( + "cannot concatenate {:?} with {:?}", + shape, + array.shape() + ))); + } + } + + let mut permutation = (0..shape.len()).into_iter().collect::(); + permutation.swap(0, axis); + let arrays = arrays .into_iter() .map(|array| array.transpose(permutation.clone())) @@ -279,6 +300,12 @@ where shape, next.shape() ))); + } else if shape.len() > 1 && shape[1..] != next.shape()[1..] { + return Err(Error::bounds(format!( + "cannot concatenate shapes {:?} and {:?}", + shape, + next.shape() + ))); } else { shape[0] += next.shape()[0]; } From df7180bed36bead974e1e20e574e6e6029ff939f Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Thu, 21 Aug 2025 09:41:44 +0530 Subject: [PATCH 4/7] re-export num_complex --- src/lib.rs | 38 +++++++++++++++++++------------------- tests/transform.rs | 7 ------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index feaf1b3..2a68d20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,11 +2,11 @@ use std::cmp::Ordering; use std::fmt; use std::ops::{Add, Div, Mul, Rem, Sub}; -#[cfg(feature = "complex")] -use num_complex::{Complex32, Complex64}; use number_general as ng; use safecast::CastFrom; +#[cfg(feature = "complex")] +pub use num_complex as complex; pub use smallvec::smallvec as axes; pub use smallvec::smallvec as coord; pub use smallvec::smallvec as range; @@ -63,9 +63,9 @@ impl CLType for u16 {} impl CLType for u32 {} impl CLType for u64 {} #[cfg(feature = "complex")] -impl CLType for num_complex::Complex {} +impl CLType for complex::Complex {} #[cfg(feature = "complex")] -impl CLType for num_complex::Complex {} +impl CLType for complex::Complex {} /// A numeric type supported by ha-ndarray pub trait Number: CLType + Into + CastFrom + Default { @@ -137,30 +137,30 @@ macro_rules! number { #[cfg(feature = "complex")] number!( - Complex32, + complex::Complex32, f32, - Complex32::new(1., 0.), - Complex32::new(0., 0.), - Complex32::norm, + complex::Complex32::ONE, + complex::Complex32::ZERO, + complex::Complex32::norm, Add::add, Div::div, Mul::mul, Sub::sub, - Complex32::powc + complex::Complex32::powc ); #[cfg(feature = "complex")] number!( - Complex64, + complex::Complex64, f64, - Complex64::new(1., 0.), - Complex64::new(0., 0.), - Complex64::norm, + complex::Complex64::ONE, + complex::Complex64::ZERO, + complex::Complex64::norm, Add::add, Div::div, Mul::mul, Sub::sub, - Complex64::powc + complex::Complex64::powc ); number!( @@ -544,9 +544,9 @@ macro_rules! float_type { } #[cfg(feature = "complex")] -float_type!(Complex32, |_| false, |_| false); +float_type!(complex::Complex32, |_| false, |_| false); #[cfg(feature = "complex")] -float_type!(Complex64, |_| false, |_| false); +float_type!(complex::Complex64, |_| false, |_| false); float_type!(f32, f32::is_infinite, f32::is_nan); float_type!(f64, f64::is_infinite, f64::is_nan); @@ -589,7 +589,7 @@ macro_rules! complex_type { } fn conj(self) -> Self { - num_complex::Complex::<$r>::conj(&self) + complex::Complex::<$r>::conj(&self) } fn im(self) -> $r { @@ -604,9 +604,9 @@ macro_rules! complex_type { } #[cfg(feature = "complex")] -complex_type!(Complex32, f32); +complex_type!(complex::Complex32, f32); #[cfg(feature = "complex")] -complex_type!(Complex64, f64); +complex_type!(complex::Complex64, f64); /// An array math error pub enum Error { diff --git a/tests/transform.rs b/tests/transform.rs index 7da54b9..7666419 100644 --- a/tests/transform.rs +++ b/tests/transform.rs @@ -25,13 +25,6 @@ fn test_flip() -> Result<(), Error> { let flip0_expected = ArrayBuf::new(vec![3, 4, 5, 0, 1, 2], shape.clone())?; let flip0_actual = source.clone().flip(0)?; - - for x in 0..shape[0] { - for y in 0..shape[1] { - println!("{x}, {y}: {}", flip0_actual.read_value(&[x, y])?); - } - } - assert!(flip0_expected.eq(flip0_actual)?.all()?); let flip1_expected = ArrayBuf::new(vec![2, 1, 0, 5, 4, 3], shape)?; From 7af537c79b43e4282fcf42f5e3f2401a76c5823a Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Thu, 21 Aug 2025 09:45:11 +0530 Subject: [PATCH 5/7] fix rustc 1.89 warnings --- src/access.rs | 6 +++--- src/array.rs | 4 ++-- src/buffer.rs | 8 ++++---- src/host/buffer.rs | 10 +++++----- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/access.rs b/src/access.rs index 9d67618..91c8162 100644 --- a/src/access.rs +++ b/src/access.rs @@ -11,7 +11,7 @@ use crate::{Buffer, Error, Number, Platform}; /// A type which allows accessing array data pub trait Access: Send + Sync { /// Read the data of this accessor as a [`BufferConverter`]. - fn read(&self) -> Result, Error>; + fn read(&self) -> Result, Error>; /// Access a single value. fn read_value(&self, offset: usize) -> Result; @@ -125,7 +125,7 @@ where T: Number, B: BufferInstance, { - fn read(&self) -> Result, Error> { + fn read(&self) -> Result, Error> { Ok(self.buffer.read()) } @@ -301,7 +301,7 @@ pub enum Accessor<'a, T: Number> { } impl<'a, T: Number> Access for Accessor<'a, T> { - fn read(&self) -> Result, Error> { + fn read(&self) -> Result, Error> { match self { Self::Buffer(buf) => Ok(buf.read()), Self::Op(op) => op.enqueue().map(BufferConverter::from), diff --git a/src/array.rs b/src/array.rs index 41e1158..10dba67 100644 --- a/src/array.rs +++ b/src/array.rs @@ -525,7 +525,7 @@ where /// Access methods for an [`NDArray`] pub trait NDArrayRead: NDArray + fmt::Debug + Sized { /// Read the value of this [`NDArray`] into a [`BufferConverter`]. - fn buffer(&self) -> Result, Error>; + fn buffer(&self) -> Result, Error>; /// Buffer this [`NDArray`] into a new, owned array, allocating only if needed. fn into_read( @@ -551,7 +551,7 @@ where A: Access, P: PlatformInstance, { - fn buffer(&self) -> Result, Error> { + fn buffer(&self) -> Result, Error> { self.access.read() } diff --git a/src/buffer.rs b/src/buffer.rs index cd1e3c6..149304a 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -11,7 +11,7 @@ use crate::{host, Error, Number}; /// A data buffer pub trait BufferInstance: Send + Sync { /// Borrow this buffer as a [`BufferConverter`]. - fn read(&self) -> BufferConverter; + fn read(&self) -> BufferConverter<'_, T>; /// Read a single value in this buffer. fn read_value(&self, offset: usize) -> Result; @@ -62,7 +62,7 @@ impl GetSize for Buffer { } impl BufferInstance for Buffer { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { BufferConverter::from(self) } @@ -119,7 +119,7 @@ impl BufferMut for Buffer { } impl<'a, T: Number> BufferInstance for &'a Buffer { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { BufferConverter::from(*self) } @@ -133,7 +133,7 @@ impl<'a, T: Number> BufferInstance for &'a Buffer { } impl<'a, T: Number> BufferInstance for &'a mut Buffer { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { BufferConverter::from(&**self) } diff --git a/src/host/buffer.rs b/src/host/buffer.rs index 0e78ff1..f10e9c1 100644 --- a/src/host/buffer.rs +++ b/src/host/buffer.rs @@ -13,7 +13,7 @@ use super::VEC_MIN_SIZE; pub type StackVec = SmallVec<[T; VEC_MIN_SIZE]>; impl BufferInstance for StackVec { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { self.as_slice().into() } @@ -41,7 +41,7 @@ impl BufferMut for StackVec { } impl BufferInstance for Vec { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { self.as_slice().into() } @@ -69,7 +69,7 @@ impl BufferMut for Vec { } impl<'a, T: Number> BufferInstance for &'a [T] { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { (*self).into() } @@ -88,7 +88,7 @@ impl<'a, T: Number> BufferInstance for &'a [T] { } impl<'a, T: Number> BufferInstance for &'a mut [T] { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { (&**self).into() } @@ -175,7 +175,7 @@ impl AsMut<[T]> for Buffer { } impl BufferInstance for Buffer { - fn read(&self) -> BufferConverter { + fn read(&self) -> BufferConverter<'_, T> { BufferConverter::Host(self.into()) } From f4e727674141fe22c69a2c9294280b790627911f Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Thu, 21 Aug 2025 10:25:39 +0530 Subject: [PATCH 6/7] return a real dtype from NDArrayComplex::angle --- src/array.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/array.rs b/src/array.rs index 10dba67..cacb183 100644 --- a/src/array.rs +++ b/src/array.rs @@ -1291,7 +1291,9 @@ where type Complex: Access; /// Calculate the angle in the complex plane elementwise. - fn angle(self) -> Result, Error>; + fn angle( + self, + ) -> Result::Real, Self::Real, Self::Platform>, Error>; /// Calculate the angle in the complex plane elementwise. fn conj(self) -> Result, Error>; @@ -1313,7 +1315,7 @@ where type Real = AccessOp; type Complex = AccessOp; - fn angle(self) -> Result, Error> { + fn angle(self) -> Result, Error> { self.apply(|platform, access| platform.angle(access)) } @@ -1375,6 +1377,7 @@ where } } +// TODO: it should be possible to implement this with a different other DType, e.g. C32 * f32 /// Array arithmetic operations pub trait NDArrayMath>: NDArray + Sized { type Output: Access; From 0c80da830cd39b8e3ff623065b0a8683519ac2fe Mon Sep 17 00:00:00 2001 From: Haydn Vestal Date: Thu, 28 Aug 2025 11:06:34 +0530 Subject: [PATCH 7/7] docs: add AGENTS.md contributor guide and CI workflow --- .github/workflows/ci.yml | 52 ++++++++++++++++++++++++++++++++++++++++ AGENTS.md | 36 ++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 AGENTS.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d805b08 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + branches: [ main, master ] + pull_request: + +jobs: + rust: + name: Lint and Test (stable, ${{ matrix.features }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + features: + - "" + - complex + - stream + - complex,stream + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + + - name: Cache cargo + uses: Swatinem/rust-cache@v2 + + - name: Format check + run: cargo fmt --all -- --check + + - name: Clippy (warnings as errors) + run: | + if [ -n "${{ matrix.features }}" ]; then + cargo clippy --all-targets --features "${{ matrix.features }}" -D warnings + else + cargo clippy --all-targets -D warnings + fi + + - name: Test + run: | + if [ -n "${{ matrix.features }}" ]; then + cargo test --features "${{ matrix.features }}" + else + cargo test + fi + +# Note: OpenCL-related tests are not run in CI by default. +# They require GPU drivers and an OpenCL ICD on the runner. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fe9b85d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- Source: `src/` with host and GPU backends: `src/host/`, `src/opencl/`, and core types in `src/array.rs`, `src/buffer.rs`, `src/ops/`. +- Library entry: `src/lib.rs` (crate type `rlib`/`cdylib`). +- OpenCL kernels: `src/opencl/programs/`. +- Tests: integration tests in `tests/*.rs` (e.g., `tests/construct.rs`). +- Packaging/build metadata: `Cargo.toml`, `Cargo.lock`; container setup in `Dockerfile`. + +## Build, Test, and Development Commands +- Build (CPU/host only): `cargo build`. +- Enable features (e.g., OpenCL): `cargo build --features opencl` or all: `cargo build --features all`. +- Run tests: `cargo test` (host) or `cargo test --features opencl`. +- Docs: `cargo doc --no-deps` (add `--open` locally to view). +- Format and lint: `cargo fmt --all` and `cargo clippy --all-targets --all-features -D warnings`. + +## Coding Style & Naming Conventions +- Rust 2021 edition; 4-space indentation; keep lines reasonably short. +- Modules/files: `snake_case` (e.g., `array.rs`); types/traits: `PascalCase` (e.g., `ArrayBuf`); functions/fields: `snake_case`. +- Prefer explicit types and small, focused modules; keep unsafe blocks minimal and well-justified. +- Run `cargo fmt` and `cargo clippy` before pushing. + +## Testing Guidelines +- Framework: Rust `#[test]` with `cargo test`; integration tests live in `tests/*.rs`. +- Name tests descriptively (e.g., `#[test] fn transpose_concat_validates_dims()`), assert both values and shapes. +- For GPU-specific logic, guard with feature flags and provide host fallbacks when possible. +- Keep tests deterministic and fast; seed randomness when used. + +## Commit & Pull Request Guidelines +- Commits: short, imperative subject (e.g., "implement Array::concat"), reference issues when relevant (e.g., `(#28)`). +- PRs: include a clear description, motivation, feature flags used (`opencl`, `complex`, etc.), and test coverage notes; add benchmarks only if necessary. +- Required: passing `cargo test`, `cargo fmt`, and `cargo clippy` with no new warnings. + +## Security & Configuration Tips +- OpenCL support is optional (`--features opencl`) and requires drivers/ICD on the host; verify with `clinfo`. +- The provided `Dockerfile` can build with GPU support; run with `docker run --gpus=all` when testing OpenCL.