diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5d7eb7a3..5d7be837e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,8 +48,8 @@ jobs: export AF_PATH=${GITHUB_WORKSPACE}/afbin export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${AF_PATH}/lib64 echo "Using cargo version: $(cargo --version)" - cargo build --all - cargo test --no-fail-fast + cargo build --all --all-features + cargo test --no-fail-fast --all-features format: name: Format Check diff --git a/Cargo.toml b/Cargo.toml index 6e98e6e46..dcb76bd7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,15 +46,19 @@ statistics = [] vision = [] default = ["algorithm", "arithmetic", "blas", "data", "indexing", "graphics", "image", "lapack", "ml", "macros", "random", "signal", "sparse", "statistics", "vision"] +afserde = ["serde"] [dependencies] libc = "0.2" num = "0.2" lazy_static = "1.0" half = "1.5.0" +serde = { version = "1.0", features = ["derive"], optional = true } [dev-dependencies] half = "1.5.0" +serde_json = "1.0" +bincode = "1.3" [build-dependencies] serde_json = "1.0" diff --git a/README.md b/README.md index e13021a19..24126f7f1 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Only, Major(M) & Minor(m) version numbers need to match. *p1* and *p2* are patch ## Supported platforms -Linux, Windows and OSX. Rust 1.15.1 or higher is required. +Linux, Windows and OSX. Rust 1.31 or newer is required. ## Use from Crates.io [![][6]][7] [![][8]][9] diff --git a/cuda-interop/Cargo.toml b/cuda-interop/Cargo.toml index 19fa09369..0a47799f7 100644 --- a/cuda-interop/Cargo.toml +++ b/cuda-interop/Cargo.toml @@ -19,7 +19,7 @@ rustacuda = "0.1" rustacuda_core = "0.1" [[example]] -name = "custom_kernel" +name = "afcuda_custom_kernel" path = "examples/custom_kernel.rs" [[example]] diff --git a/cuda-interop/examples/cuda_af_app.rs b/cuda-interop/examples/cuda_af_app.rs index 7868402c5..b1f966cf0 100644 --- a/cuda-interop/examples/cuda_af_app.rs +++ b/cuda-interop/examples/cuda_af_app.rs @@ -1,6 +1,5 @@ use arrayfire::{af_print, dim4, info, set_device, Array}; use rustacuda::prelude::*; -use rustacuda::*; fn main() { // MAKE SURE to do all rustacuda initilization before arrayfire API's diff --git a/opencl-interop/Cargo.toml b/opencl-interop/Cargo.toml index b416d3548..6ee2a97d5 100644 --- a/opencl-interop/Cargo.toml +++ b/opencl-interop/Cargo.toml @@ -18,7 +18,7 @@ cl-sys = "0.4.2" ocl-core = "0.11.2" [[example]] -name = "custom_kernel" +name = "afocl_custom_kernel" path = "examples/custom_kernel.rs" [[example]] diff --git a/opencl-interop/examples/custom_kernel.rs b/opencl-interop/examples/custom_kernel.rs index 6075aec24..33efc0f1e 100644 --- a/opencl-interop/examples/custom_kernel.rs +++ b/opencl-interop/examples/custom_kernel.rs @@ -22,7 +22,7 @@ fn main() { let af_ctx = afcl::get_context(false); let af_que = afcl::get_queue(false); - let devid = unsafe { ocl_core::DeviceId::from_raw(af_did) }; + let _devid = unsafe { ocl_core::DeviceId::from_raw(af_did) }; let contx = unsafe { ocl_core::Context::from_raw_copied_ptr(af_ctx) }; let queue = unsafe { ocl_core::CommandQueue::from_raw_copied_ptr(af_que) }; diff --git a/src/core/array.rs b/src/core/array.rs index ce70ae896..75d61c55a 100644 --- a/src/core/array.rs +++ b/src/core/array.rs @@ -851,6 +851,73 @@ pub fn is_eval_manual() -> bool { } } +#[cfg(feature = "afserde")] +mod afserde { + // Reimport required from super scope + use super::{Array, DType, Dim4, HasAfEnum}; + + use serde::de::{Deserializer, Error, Unexpected}; + use serde::ser::Serializer; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + struct ArrayOnHost { + dtype: DType, + shape: Dim4, + data: Vec, + } + + /// Serialize Implementation of Array + impl Serialize for Array + where + T: std::default::Default + std::clone::Clone + Serialize + HasAfEnum + std::fmt::Debug, + { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut vec = vec![T::default(); self.elements()]; + self.host(&mut vec); + let arr_on_host = ArrayOnHost { + dtype: self.get_type(), + shape: self.dims().clone(), + data: vec, + }; + arr_on_host.serialize(serializer) + } + } + + /// Deserialize Implementation of Array + impl<'de, T> Deserialize<'de> for Array + where + T: Deserialize<'de> + HasAfEnum + std::fmt::Debug, + { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match ArrayOnHost::::deserialize(deserializer) { + Ok(arr_on_host) => { + let read_dtype = arr_on_host.dtype; + let expected_dtype = T::get_af_dtype(); + if expected_dtype != read_dtype { + let error_msg = format!( + "data type is {:?}, deserialized type is {:?}", + expected_dtype, read_dtype + ); + return Err(Error::invalid_value(Unexpected::Enum, &error_msg.as_str())); + } + Ok(Array::::new( + &arr_on_host.data, + arr_on_host.shape.clone(), + )) + } + Err(err) => Err(err), + } + } + } +} + #[cfg(test)] mod tests { use super::super::array::print; @@ -1082,4 +1149,37 @@ mod tests { // 8.0000 8.0000 8.0000 // ANCHOR_END: accum_using_channel } + + #[cfg(feature = "afserde")] + mod serde_tests { + use super::super::Array; + use crate::algorithm::sum_all; + use crate::randu; + + #[test] + fn array_serde_json() { + let input = randu!(u8; 2, 2); + let serd = match serde_json::to_string(&input) { + Ok(serialized_str) => serialized_str, + Err(e) => e.to_string(), + }; + + let deserd: Array = serde_json::from_str(&serd).unwrap(); + + assert_eq!(sum_all(&(input - deserd)), (0u32, 0u32)); + } + + #[test] + fn array_serde_bincode() { + let input = randu!(u8; 2, 2); + let encoded = match bincode::serialize(&input) { + Ok(encoded) => encoded, + Err(_) => vec![], + }; + + let decoded: Array = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(sum_all(&(input - decoded)), (0u32, 0u32)); + } + } } diff --git a/src/core/defines.rs b/src/core/defines.rs index 6c95f1385..d52b60b2e 100644 --- a/src/core/defines.rs +++ b/src/core/defines.rs @@ -2,9 +2,13 @@ use num::Complex; use std::fmt::Error as FmtError; use std::fmt::{Display, Formatter}; +#[cfg(feature = "afserde")] +use serde::{Deserialize, Serialize}; + /// Error codes #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum AfError { /// The function returned successfully SUCCESS = 0, @@ -52,6 +56,7 @@ pub enum AfError { /// Compute/Acceleration Backend #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum Backend { /// Default backend order: OpenCL -> CUDA -> CPU DEFAULT = 0, @@ -103,6 +108,7 @@ impl Display for AfError { /// Types of Array data type #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum DType { /// 32 bit float F32 = 0, @@ -135,6 +141,7 @@ pub enum DType { /// Dictates the interpolation method to be used by a function #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum InterpType { /// Nearest Neighbor interpolation method NEAREST = 0, @@ -161,6 +168,7 @@ pub enum InterpType { /// Helps determine how to pad kernels along borders #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum BorderType { /// Pad using zeros ZERO = 0, @@ -177,6 +185,7 @@ pub enum BorderType { /// Used by `regions` function to identify type of connectivity #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum Connectivity { /// North-East-South-West (N-E-S-W) connectivity from given pixel/point FOUR = 4, @@ -187,6 +196,7 @@ pub enum Connectivity { /// Helps determine the size of output of convolution #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum ConvMode { /// Default convolution mode where output size is same as input size DEFAULT = 0, @@ -197,6 +207,7 @@ pub enum ConvMode { /// Helps determine if convolution is in Spatial or Frequency domain #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum ConvDomain { /// ArrayFire chooses whether the convolution will be in spatial domain or frequency domain AUTO = 0, @@ -209,6 +220,7 @@ pub enum ConvDomain { /// Error metric used by `matchTemplate` function #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum MatchType { /// Sum of Absolute Differences SAD = 0, @@ -233,6 +245,7 @@ pub enum MatchType { /// Identify the color space of given image(Array) #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum ColorSpace { /// Grayscale color space GRAY = 0, @@ -245,6 +258,7 @@ pub enum ColorSpace { /// Helps determine the type of a Matrix #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum MatProp { /// Default (no-op) NONE = 0, @@ -276,6 +290,7 @@ pub enum MatProp { #[allow(non_camel_case_types)] #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum NormType { /// Treats input as a vector and return sum of absolute values VECTOR_1 = 0, @@ -298,6 +313,7 @@ pub enum NormType { /// Dictates what color map is used for Image rendering #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum ColorMap { /// Default color map is grayscale range [0-1] DEFAULT = 0, @@ -318,6 +334,7 @@ pub enum ColorMap { /// YCbCr Standards #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum YCCStd { /// ITU-R BT.601 (formerly CCIR 601) standard YCC_601 = 601, @@ -330,6 +347,7 @@ pub enum YCCStd { /// Homography type #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum HomographyType { /// RANdom SAmple Consensus algorithm RANSAC = 0, @@ -340,6 +358,7 @@ pub enum HomographyType { /// Plotting markers #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum MarkerType { /// No marker NONE = 0, @@ -362,6 +381,7 @@ pub enum MarkerType { /// Image moment types #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum MomentType { /// Central moment of order (0 + 0) M00 = 1, // 1<<0 @@ -378,6 +398,7 @@ pub enum MomentType { /// Sparse storage format type #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum SparseFormat { /// Dense format DENSE = 0, @@ -392,6 +413,7 @@ pub enum SparseFormat { /// Binary operation types for generalized scan functions #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum BinaryOp { /// Addition operation ADD = 0, @@ -406,6 +428,7 @@ pub enum BinaryOp { /// Random engine types #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum RandomEngineType { ///Philox variant with N=4, W=32 and Rounds=10 PHILOX_4X32_10 = 100, @@ -424,16 +447,27 @@ pub const MERSENNE: RandomEngineType = RandomEngineType::MERSENNE_GP11213; /// Default RandomEngine that defaults to [PHILOX](./constant.PHILOX.html) pub const DEFAULT_RANDOM_ENGINE: RandomEngineType = PHILOX; +#[cfg(feature = "afserde")] +#[derive(Serialize, Deserialize)] +#[serde(remote = "Complex")] +struct ComplexDef { + re: T, + im: T, +} + /// Scalar value types #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum Scalar { /// 32 bit float F32(f32), /// 32 bit complex float + #[cfg_attr(feature = "afserde", serde(with = "ComplexDef"))] C32(Complex), /// 64 bit float F64(f64), /// 64 bit complex float + #[cfg_attr(feature = "afserde", serde(with = "ComplexDef"))] C64(Complex), /// 8 bit boolean B8(bool), @@ -456,6 +490,7 @@ pub enum Scalar { /// Canny edge detector threshold operations types #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum CannyThresholdType { /// User has to define canny thresholds manually MANUAL = 0, @@ -466,6 +501,7 @@ pub enum CannyThresholdType { /// Anisotropic diffusion flux equation types #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum DiffusionEq { /// Quadratic flux function QUADRATIC = 1, @@ -478,6 +514,7 @@ pub enum DiffusionEq { /// Diffusion equation types #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum FluxFn { /// Quadratic flux function GRADIENT = 1, @@ -490,6 +527,7 @@ pub enum FluxFn { /// topk function ordering #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum TopkFn { /// Top k min values MIN = 1, @@ -502,6 +540,7 @@ pub enum TopkFn { /// Iterative Deconvolution Algorithm #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum IterativeDeconvAlgo { /// Land-Weber Algorithm LANDWEBER = 1, @@ -514,6 +553,7 @@ pub enum IterativeDeconvAlgo { /// Inverse Deconvolution Algorithm #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum InverseDeconvAlgo { /// Tikhonov algorithm TIKHONOV = 1, @@ -524,6 +564,7 @@ pub enum InverseDeconvAlgo { /// Gradient mode for convolution #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum ConvGradientType { /// Filter Gradient FILTER = 1, @@ -538,6 +579,7 @@ pub enum ConvGradientType { /// Gradient mode for convolution #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum VarianceBias { /// Sample variance SAMPLE = 1, @@ -550,9 +592,49 @@ pub enum VarianceBias { /// Gradient mode for convolution #[repr(u32)] #[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub enum CublasMathMode { /// To indicate use of Tensor Cores on CUDA capable GPUs TENSOR_OP = 1, /// Default i.e. tensor core operations will be avoided by the library DEFAULT = 0, } + +#[cfg(test)] +mod tests { + #[cfg(feature = "afserde")] + mod serde_tests { + #[test] + fn test_enum_serde() { + use super::super::AfError; + + let err_code = AfError::ERR_NO_MEM; + let serd = match serde_json::to_string(&err_code) { + Ok(serialized_str) => serialized_str, + Err(e) => e.to_string(), + }; + assert_eq!(serd, "\"ERR_NO_MEM\""); + + let deserd: AfError = serde_json::from_str(&serd).unwrap(); + assert_eq!(deserd, AfError::ERR_NO_MEM); + } + + #[test] + fn test_scalar_serde() { + use super::super::Scalar; + use num::Complex; + + let scalar = Scalar::C32(Complex { + re: 1.0f32, + im: 1.0f32, + }); + let serd = match serde_json::to_string(&scalar) { + Ok(serialized_str) => serialized_str, + Err(e) => e.to_string(), + }; + + let deserd: Scalar = serde_json::from_str(&serd).unwrap(); + assert_eq!(deserd, scalar); + } + } +} diff --git a/src/core/dim4.rs b/src/core/dim4.rs index 565b0f294..51a0120eb 100644 --- a/src/core/dim4.rs +++ b/src/core/dim4.rs @@ -1,8 +1,12 @@ use std::fmt; use std::ops::{Index, IndexMut}; +#[cfg(feature = "afserde")] +use serde::{Deserialize, Serialize}; + /// Dim4 is used to store [Array](./struct.Array.html) dimensions #[derive(Copy, Clone, PartialEq, Debug)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] pub struct Dim4 { dims: [u64; 4], } @@ -117,3 +121,25 @@ impl Dim4 { &self.dims } } + +#[cfg(test)] +mod tests { + #[cfg(feature = "afserde")] + mod serde_tests { + use super::super::Dim4; + use crate::dim4; + + #[test] + fn dim4_serde() { + let dims = dim4!(4, 4); + let serd = match serde_json::to_string(&dims) { + Ok(serialized_str) => serialized_str, + Err(e) => e.to_string(), + }; + assert_eq!(serd, "{\"dims\":[4,4,1,1]}"); + + let deserd: Dim4 = serde_json::from_str(&serd).unwrap(); + assert_eq!(deserd, dims); + } + } +} diff --git a/src/core/random.rs b/src/core/random.rs index 5fff7747f..d948ed9e7 100644 --- a/src/core/random.rs +++ b/src/core/random.rs @@ -221,6 +221,50 @@ impl Drop for RandomEngine { } } +#[cfg(feature = "afserde")] +mod afserde { + // Reimport required from super scope + use super::{RandomEngine, RandomEngineType}; + + use serde::de::Deserializer; + use serde::ser::Serializer; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, Serialize, Deserialize)] + struct RandEngine { + engine_type: RandomEngineType, + seed: u64, + } + + /// Serialize Implementation of Array + impl Serialize for RandomEngine { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let r = RandEngine { + engine_type: self.get_type(), + seed: self.get_seed(), + }; + r.serialize(serializer) + } + } + + /// Deserialize Implementation of Array + #[cfg(feature = "afserde")] + impl<'de> Deserialize<'de> for RandomEngine { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + match RandEngine::deserialize(deserializer) { + Ok(r) => Ok(RandomEngine::new(r.engine_type, Some(r.seed))), + Err(err) => Err(err), + } + } + } +} + /// Get default random engine pub fn get_default_random_engine() -> RandomEngine { unsafe { @@ -303,3 +347,27 @@ where temp.into() } } + +#[cfg(test)] +mod tests { + #[cfg(feature = "afserde")] + mod serde_tests { + use super::super::RandomEngine; + use crate::core::defines::RandomEngineType; + + #[test] + #[cfg(feature = "afserde")] + fn random_engine_serde_bincode() { + let input = RandomEngine::new(RandomEngineType::THREEFRY_2X32_16, Some(2047)); + let encoded = match bincode::serialize(&input) { + Ok(encoded) => encoded, + Err(_) => vec![], + }; + + let decoded: RandomEngine = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(input.get_seed(), decoded.get_seed()); + assert_eq!(input.get_type(), decoded.get_type()); + } + } +} diff --git a/src/core/seq.rs b/src/core/seq.rs index cba9f8516..06c6c2eb7 100644 --- a/src/core/seq.rs +++ b/src/core/seq.rs @@ -1,10 +1,13 @@ use num::{One, Zero}; +#[cfg(feature = "afserde")] +use serde::{Deserialize, Serialize}; use std::default::Default; use std::fmt; /// Sequences are used for indexing Arrays -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, PartialEq)] +#[cfg_attr(feature = "afserde", derive(Serialize, Deserialize))] #[repr(C)] pub struct Seq { begin: T, @@ -55,3 +58,22 @@ impl Seq { self.step } } + +#[cfg(test)] +mod tests { + #[cfg(feature = "afserde")] + #[test] + fn seq_serde() { + use super::Seq; + use crate::seq; + + let original = seq!(1:2:1); + let serd = match serde_json::to_string(&original) { + Ok(serialized_str) => serialized_str, + Err(e) => e.to_string(), + }; + + let deserd: Seq = serde_json::from_str(&serd).unwrap(); + assert_eq!(deserd, original); + } +}