ferray-core 0.2.10

N-dimensional array type and foundational primitives for ferray
Documentation
1
2
3
4
5
6
7
8
9
{"timestamp":"1775712102","file_path":"/home/doll/ferray/ferray-core/src/layout.rs","entry":{"original_text":"// ferray-core: Memory layout enum (REQ-1)\n\n/// Describes the memory layout of an N-dimensional array.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum MemoryLayout {\n    /// Row-major (C-style) contiguous layout.\n    C,\n    /// Column-major (Fortran-style) contiguous layout.\n    Fortran,\n    /// Non-contiguous or custom stride layout.\n    Custom,\n}\n\nimpl MemoryLayout {\n    /// Returns `true` if the layout is C-contiguous (row-major).\n    #[inline]\n    pub fn is_c_contiguous(self) -> bool {\n        self == Self::C\n    }\n\n    /// Returns `true` if the layout is Fortran-contiguous (column-major).\n    #[inline]\n    pub fn is_f_contiguous(self) -> bool {\n        self == Self::Fortran\n    }\n\n    /// Returns `true` if the layout is neither C nor Fortran contiguous.\n    #[inline]\n    pub fn is_custom(self) -> bool {\n        self == Self::Custom\n    }\n}\n\nimpl core::fmt::Display for MemoryLayout {\n    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n        match self {\n            Self::C => write!(f, \"C\"),\n            Self::Fortran => write!(f, \"F\"),\n            Self::Custom => write!(f, \"Custom\"),\n        }\n    }\n}\n\n/// Classify the memory layout for an ndarray-backed array given\n/// whether it is already known to be C-standard and its current shape +\n/// element strides. Used by `Array::layout`, `ArrayView::layout` and\n/// `ArrayViewMut::layout` so all three share one implementation\n/// (see issue #127).\n#[cfg(not(feature = \"no_std\"))]\n#[inline]\npub(crate) fn classify_layout(\n    is_standard: bool,\n    shape: &[usize],\n    strides: &[isize],\n) -> MemoryLayout {\n    if is_standard {\n        MemoryLayout::C\n    } else {\n        detect_layout(shape, strides)\n    }\n}\n\n/// Determine memory layout from shape and strides.\n#[cfg(not(feature = \"no_std\"))]\npub(crate) fn detect_layout(shape: &[usize], strides: &[isize]) -> MemoryLayout {\n    if shape.is_empty() {\n        return MemoryLayout::C; // scalar-like\n    }\n\n    let is_c = is_c_contiguous(shape, strides);\n    let is_f = is_f_contiguous(shape, strides);\n\n    if is_c {\n        MemoryLayout::C\n    } else if is_f {\n        MemoryLayout::Fortran\n    } else {\n        MemoryLayout::Custom\n    }\n}\n\n#[cfg(not(feature = \"no_std\"))]\nfn is_c_contiguous(shape: &[usize], strides: &[isize]) -> bool {\n    if shape.len() != strides.len() {\n        return false;\n    }\n    let ndim = shape.len();\n    if ndim == 0 {\n        return true;\n    }\n    let mut expected: isize = 1;\n    for i in (0..ndim).rev() {\n        if shape[i] == 0 {\n            return true; // empty array is contiguous by convention\n        }\n        if shape[i] != 1 && strides[i] != expected {\n            return false;\n        }\n        expected = strides[i] * shape[i] as isize;\n    }\n    true\n}\n\n#[cfg(not(feature = \"no_std\"))]\nfn is_f_contiguous(shape: &[usize], strides: &[isize]) -> bool {\n    if shape.len() != strides.len() {\n        return false;\n    }\n    let ndim = shape.len();\n    if ndim == 0 {\n        return true;\n    }\n    let mut expected: isize = 1;\n    for i in 0..ndim {\n        if shape[i] == 0 {\n            return true; // empty array is contiguous by convention\n        }\n        if shape[i] != 1 && strides[i] != expected {\n            return false;\n        }\n        expected = strides[i] * shape[i] as isize;\n    }\n    true\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn detect_c_contiguous() {\n        // 3x4 C-contiguous: strides = [4, 1]\n        assert_eq!(detect_layout(&[3, 4], &[4, 1]), MemoryLayout::C);\n    }\n\n    #[test]\n    fn detect_f_contiguous() {\n        // 3x4 F-contiguous: strides = [1, 3]\n        assert_eq!(detect_layout(&[3, 4], &[1, 3]), MemoryLayout::Fortran);\n    }\n\n    #[test]\n    fn detect_custom() {\n        // non-contiguous strides\n        assert_eq!(detect_layout(&[3, 4], &[8, 2]), MemoryLayout::Custom);\n    }\n\n    #[test]\n    fn detect_empty() {\n        assert_eq!(detect_layout(&[], &[]), MemoryLayout::C);\n    }\n\n    #[test]\n    fn display() {\n        assert_eq!(MemoryLayout::C.to_string(), \"C\");\n        assert_eq!(MemoryLayout::Fortran.to_string(), \"F\");\n        assert_eq!(MemoryLayout::Custom.to_string(), \"Custom\");\n    }\n}\n"}}
{"timestamp":"1775712102","file_path":"/home/doll/ferray/ferray-core/src/error.rs","entry":{"original_text":"// ferray-core: Error types (REQ-27)\n\nuse core::fmt;\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n#[cfg(feature = \"no_std\")]\nuse alloc::{string::String, string::ToString, vec::Vec};\n\n/// The primary error type for all ferray operations.\n///\n/// This enum is `#[non_exhaustive]`, so new variants may be added\n/// in minor releases without breaking downstream code.\n#[derive(Debug, Clone, thiserror::Error)]\n#[non_exhaustive]\npub enum FerrayError {\n    /// Operand shapes are incompatible for the requested operation.\n    #[error(\"shape mismatch: {message}\")]\n    ShapeMismatch {\n        /// Human-readable description of the mismatch.\n        message: String,\n    },\n\n    /// Broadcasting failed because shapes cannot be reconciled.\n    #[error(\"broadcast failure: cannot broadcast shapes {shape_a:?} and {shape_b:?}\")]\n    BroadcastFailure {\n        /// First shape.\n        shape_a: Vec<usize>,\n        /// Second shape.\n        shape_b: Vec<usize>,\n    },\n\n    /// An axis index exceeded the array's dimensionality.\n    #[error(\"axis {axis} is out of bounds for array with {ndim} dimensions\")]\n    AxisOutOfBounds {\n        /// The invalid axis.\n        axis: usize,\n        /// Number of dimensions.\n        ndim: usize,\n    },\n\n    /// An element index exceeded the array's extent along some axis.\n    #[error(\"index {index} is out of bounds for axis {axis} with size {size}\")]\n    IndexOutOfBounds {\n        /// The invalid index.\n        index: isize,\n        /// The axis along which the index was applied.\n        axis: usize,\n        /// The size of that axis.\n        size: usize,\n    },\n\n    /// A matrix was singular when an invertible one was required.\n    #[error(\"singular matrix: {message}\")]\n    SingularMatrix {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// An iterative algorithm did not converge within its budget.\n    #[error(\"convergence failure after {iterations} iterations: {message}\")]\n    ConvergenceFailure {\n        /// Number of iterations attempted.\n        iterations: usize,\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// The requested dtype is invalid or unsupported for this operation.\n    #[error(\"invalid dtype: {message}\")]\n    InvalidDtype {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// A computation produced NaN / Inf when finite results were required.\n    #[error(\"numerical instability: {message}\")]\n    NumericalInstability {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// An I/O operation failed.\n    #[error(\"I/O error: {message}\")]\n    IoError {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// A function argument was invalid.\n    #[error(\"invalid value: {message}\")]\n    InvalidValue {\n        /// Diagnostic context.\n        message: String,\n    },\n}\n\n/// Convenience alias used throughout ferray.\npub type FerrayResult<T> = Result<T, FerrayError>;\n\nimpl FerrayError {\n    /// Create a `ShapeMismatch` error with a formatted message.\n    pub fn shape_mismatch(msg: impl fmt::Display) -> Self {\n        Self::ShapeMismatch {\n            message: msg.to_string(),\n        }\n    }\n\n    /// Create a `BroadcastFailure` error.\n    pub fn broadcast_failure(a: &[usize], b: &[usize]) -> Self {\n        Self::BroadcastFailure {\n            shape_a: a.to_vec(),\n            shape_b: b.to_vec(),\n        }\n    }\n\n    /// Create an `AxisOutOfBounds` error.\n    pub fn axis_out_of_bounds(axis: usize, ndim: usize) -> Self {\n        Self::AxisOutOfBounds { axis, ndim }\n    }\n\n    /// Create an `IndexOutOfBounds` error.\n    pub fn index_out_of_bounds(index: isize, axis: usize, size: usize) -> Self {\n        Self::IndexOutOfBounds { index, axis, size }\n    }\n\n    /// Create an `InvalidDtype` error with a formatted message.\n    pub fn invalid_dtype(msg: impl fmt::Display) -> Self {\n        Self::InvalidDtype {\n            message: msg.to_string(),\n        }\n    }\n\n    /// Create an `InvalidValue` error with a formatted message.\n    pub fn invalid_value(msg: impl fmt::Display) -> Self {\n        Self::InvalidValue {\n            message: msg.to_string(),\n        }\n    }\n\n    /// Create an `IoError` from a formatted message.\n    pub fn io_error(msg: impl fmt::Display) -> Self {\n        Self::IoError {\n            message: msg.to_string(),\n        }\n    }\n}\n\n#[cfg(not(feature = \"no_std\"))]\nimpl From<std::io::Error> for FerrayError {\n    fn from(e: std::io::Error) -> Self {\n        Self::IoError {\n            message: e.to_string(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn error_display_shape_mismatch() {\n        let e = FerrayError::shape_mismatch(\"expected (3,4), got (3,5)\");\n        assert!(e.to_string().contains(\"expected (3,4), got (3,5)\"));\n    }\n\n    #[test]\n    fn error_display_axis_out_of_bounds() {\n        let e = FerrayError::axis_out_of_bounds(5, 3);\n        assert!(e.to_string().contains(\"axis 5\"));\n        assert!(e.to_string().contains(\"3 dimensions\"));\n    }\n\n    #[test]\n    fn error_display_broadcast_failure() {\n        let e = FerrayError::broadcast_failure(&[4, 3], &[2, 5]);\n        let s = e.to_string();\n        assert!(s.contains(\"[4, 3]\"));\n        assert!(s.contains(\"[2, 5]\"));\n    }\n\n    #[test]\n    fn error_is_non_exhaustive() {\n        // Verify the enum is non_exhaustive by using a wildcard\n        // in a match from an \"external\" perspective. Inside this crate\n        // the compiler knows all variants, so we just verify construction.\n        let e = FerrayError::invalid_value(\"test\");\n        assert!(matches!(e, FerrayError::InvalidValue { .. }));\n\n        let e2 = FerrayError::shape_mismatch(\"bad shape\");\n        assert!(matches!(e2, FerrayError::ShapeMismatch { .. }));\n    }\n\n    #[test]\n    fn from_io_error() {\n        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, \"file missing\");\n        let ferray_err: FerrayError = io_err.into();\n        assert!(ferray_err.to_string().contains(\"file missing\"));\n    }\n}\n"}}
{"timestamp":"1775712102","file_path":"/home/doll/ferray/ferray-core/src/dtype/casting.rs","entry":{"original_text":"// ferray-core: Type casting and inspection (REQ-25, REQ-26)\n//\n// Provides:\n// - CastKind enum (No, Equiv, Safe, SameKind, Unsafe) mirroring NumPy's casting rules\n// - can_cast(from, to, casting) — check if a cast is allowed\n// - promote_types(a, b) — runtime promotion (delegates to promotion::result_type)\n// - common_type(a, b) — alias for promote_types\n// - min_scalar_type(dtype) — smallest type that can hold the range of dtype\n// - issubdtype(child, parent) — type hierarchy check\n// - isrealobj / iscomplexobj — type inspection predicates\n// - astype() / view_cast() — methods on Array for explicit casting\n\n#[cfg(not(feature = \"no_std\"))]\nuse crate::dimension::Dimension;\nuse crate::dtype::{DType, Element};\n#[cfg(not(feature = \"no_std\"))]\nuse crate::error::FerrayError;\nuse crate::error::FerrayResult;\n\n#[cfg(not(feature = \"no_std\"))]\nuse super::promotion::PromoteTo;\n\n// ---------------------------------------------------------------------------\n// CastKind — mirrors NumPy's casting parameter\n// ---------------------------------------------------------------------------\n\n/// Describes the safety level of a type cast.\n///\n/// Mirrors NumPy's `casting` parameter for `np.can_cast`, `np.result_type`, etc.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum CastKind {\n    /// No casting allowed — types must be identical.\n    No,\n    /// Types must have the same byte-level representation (e.g., byte order only).\n    /// In ferray (native endian only), this is the same as `No`.\n    Equiv,\n    /// Safe cast: no information loss (e.g., i32 -> i64, f32 -> f64).\n    Safe,\n    /// Same kind: both are the same category (int->int, float->float) regardless\n    /// of size (e.g., i64 -> i32 is allowed but may truncate).\n    SameKind,\n    /// Any cast is allowed (may lose information, e.g., f64 -> i32).\n    Unsafe,\n}\n\n// ---------------------------------------------------------------------------\n// can_cast — check if a cast between DTypes is allowed\n// ---------------------------------------------------------------------------\n\n/// Check whether a cast from `from` to `to` is allowed under the given `casting` rule.\n///\n/// # Errors\n/// Returns `FerrayError::InvalidDtype` if the combination is not recognized.\npub fn can_cast(from: DType, to: DType, casting: CastKind) -> FerrayResult<bool> {\n    Ok(match casting {\n        CastKind::No | CastKind::Equiv => from == to,\n        CastKind::Safe => is_safe_cast(from, to),\n        CastKind::SameKind => is_same_kind(from, to),\n        CastKind::Unsafe => true, // anything goes\n    })\n}\n\n/// Check if `from` can be safely cast to `to` without information loss.\nfn is_safe_cast(from: DType, to: DType) -> bool {\n    if from == to {\n        return true;\n    }\n    // A safe cast exists when promoting from + to yields `to`.\n    // That means `to` can represent all values of `from`.\n    match super::promotion::result_type(from, to) {\n        Ok(promoted) => promoted == to,\n        Err(_) => false,\n    }\n}\n\n/// Check if `from` and `to` are the same \"kind\" (integer, float, complex).\nfn is_same_kind(from: DType, to: DType) -> bool {\n    if from == to {\n        return true;\n    }\n    dtype_kind(from) == dtype_kind(to)\n}\n\n/// Return a \"kind\" category for a DType.\nfn dtype_kind(dt: DType) -> u8 {\n    if dt == DType::Bool {\n        0 // bool is its own kind but also considered integer-compatible\n    } else if dt.is_integer() {\n        1\n    } else if dt.is_float() {\n        2\n    } else if dt.is_complex() {\n        3\n    } else {\n        255\n    }\n}\n\n// ---------------------------------------------------------------------------\n// promote_types — runtime type promotion\n// ---------------------------------------------------------------------------\n\n/// Determine the promoted type for two dtypes (runtime version).\n///\n/// This is the runtime equivalent of the `promoted_type!()` compile-time macro.\n/// Returns the smallest type that can represent both `a` and `b` without\n/// precision loss.\n///\n/// # Errors\n/// Returns `FerrayError::InvalidDtype` if promotion fails.\npub fn promote_types(a: DType, b: DType) -> FerrayResult<DType> {\n    super::promotion::result_type(a, b)\n}\n\n// ---------------------------------------------------------------------------\n// common_type — alias for promote_types\n// ---------------------------------------------------------------------------\n\n/// Determine the common type for two dtypes. This is an alias for [`promote_types`].\n///\n/// # Errors\n/// Returns `FerrayError::InvalidDtype` if promotion fails.\npub fn common_type(a: DType, b: DType) -> FerrayResult<DType> {\n    promote_types(a, b)\n}\n\n// ---------------------------------------------------------------------------\n// min_scalar_type — smallest type for a given dtype\n// ---------------------------------------------------------------------------\n\n/// Return the smallest scalar type that can hold the full range of `dt`.\n///\n/// This is analogous to NumPy's `np.min_scalar_type`. For example:\n/// - `min_scalar_type(DType::I64)` returns `DType::I8` (the smallest signed int)\n/// - `min_scalar_type(DType::F64)` returns `DType::F32` (smallest float that is lossless for the kind)\n///\n/// Note: without a concrete value, we return the smallest type of the same kind.\npub fn min_scalar_type(dt: DType) -> DType {\n    use DType::*;\n    match dt {\n        Bool => Bool,\n        U8 | U16 | U32 | U64 | U128 => U8,\n        I8 | I16 | I32 | I64 | I128 | I256 => I8,\n        F32 | F64 => F32,\n        Complex32 | Complex64 => Complex32,\n        #[cfg(feature = \"f16\")]\n        F16 => F16,\n        #[cfg(feature = \"bf16\")]\n        BF16 => BF16,\n    }\n}\n\n// ---------------------------------------------------------------------------\n// issubdtype — type hierarchy check (REQ-26)\n// ---------------------------------------------------------------------------\n\n/// Category for dtype hierarchy checks, matching NumPy's abstract type categories.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum DTypeCategory {\n    /// Any numeric type (integer, float, or complex).\n    Number,\n    /// Integer types (signed or unsigned, including bool).\n    Integer,\n    /// Signed integer types.\n    SignedInteger,\n    /// Unsigned integer types (including bool).\n    UnsignedInteger,\n    /// Floating-point types.\n    Floating,\n    /// Complex floating-point types.\n    ComplexFloating,\n}\n\n/// Check if a dtype is a sub-type of a given category.\n///\n/// Analogous to NumPy's `np.issubdtype(dtype, category)`.\n///\n/// # Examples\n/// ```\n/// use ferray_core::dtype::DType;\n/// use ferray_core::dtype::casting::{issubdtype, DTypeCategory};\n///\n/// assert!(issubdtype(DType::I32, DTypeCategory::Integer));\n/// assert!(issubdtype(DType::I32, DTypeCategory::SignedInteger));\n/// assert!(issubdtype(DType::I32, DTypeCategory::Number));\n/// assert!(!issubdtype(DType::I32, DTypeCategory::Floating));\n/// ```\npub fn issubdtype(dt: DType, category: DTypeCategory) -> bool {\n    match category {\n        DTypeCategory::Number => true, // all our dtypes are numeric\n        DTypeCategory::Integer => dt.is_integer(),\n        DTypeCategory::SignedInteger => dt.is_signed() && dt.is_integer(),\n        DTypeCategory::UnsignedInteger => dt.is_integer() && !dt.is_signed(),\n        DTypeCategory::Floating => dt.is_float(),\n        DTypeCategory::ComplexFloating => dt.is_complex(),\n    }\n}\n\n// ---------------------------------------------------------------------------\n// isrealobj / iscomplexobj — type inspection predicates (REQ-26)\n// ---------------------------------------------------------------------------\n\n/// Check if an array's element type is a real (non-complex) type.\n///\n/// Analogous to NumPy's `np.isrealobj`.\npub fn isrealobj<T: Element>() -> bool {\n    !T::dtype().is_complex()\n}\n\n/// Check if an array's element type is a complex type.\n///\n/// Analogous to NumPy's `np.iscomplexobj`.\npub fn iscomplexobj<T: Element>() -> bool {\n    T::dtype().is_complex()\n}\n\n// ===========================================================================\n// Everything below requires std (depends on Array / ndarray)\n// ===========================================================================\n\n// ---------------------------------------------------------------------------\n// astype() — explicit type casting on Array (REQ-25)\n// ---------------------------------------------------------------------------\n\n/// Trait providing the `astype()` method for arrays.\n///\n/// This is intentionally a separate trait so that mixed-type binary operations\n/// do NOT compile implicitly (REQ-24). Users must call `.astype::<T>()` or use\n/// `add_promoted()` etc.\n#[cfg(not(feature = \"no_std\"))]\npub trait AsType<D: Dimension> {\n    /// Cast all elements to type `U`, returning a new array.\n    ///\n    /// # Errors\n    /// Returns `FerrayError::InvalidDtype` if the cast is not possible.\n    fn astype<U: Element>(&self) -> FerrayResult<crate::array::owned::Array<U, D>>\n    where\n        Self: AsTypeInner<U, D>;\n}\n\n/// Internal trait that performs the actual casting. Implemented for specific\n/// (T, U) pairs where `T: PromoteTo<U>` or where unsafe casting is valid.\n#[cfg(not(feature = \"no_std\"))]\npub trait AsTypeInner<U: Element, D: Dimension> {\n    /// Perform the cast.\n    fn astype_inner(&self) -> FerrayResult<crate::array::owned::Array<U, D>>;\n}\n\n#[cfg(not(feature = \"no_std\"))]\nimpl<T: Element, D: Dimension> AsType<D> for crate::array::owned::Array<T, D> {\n    fn astype<U: Element>(&self) -> FerrayResult<crate::array::owned::Array<U, D>>\n    where\n        Self: AsTypeInner<U, D>,\n    {\n        self.astype_inner()\n    }\n}\n\n/// Blanket implementation: any T that can PromoteTo<U> can astype.\n#[cfg(not(feature = \"no_std\"))]\nimpl<T, U, D> AsTypeInner<U, D> for crate::array::owned::Array<T, D>\nwhere\n    T: Element + PromoteTo<U>,\n    U: Element,\n    D: Dimension,\n{\n    fn astype_inner(&self) -> FerrayResult<crate::array::owned::Array<U, D>> {\n        let mapped = self.inner.mapv(|x| x.promote());\n        Ok(crate::array::owned::Array::from_ndarray(mapped))\n    }\n}\n\n// ---------------------------------------------------------------------------\n// cast() — explicit type casting with safety check (issue #361)\n//\n// `astype<U>` above is restricted to safe (PromoteTo) conversions. NumPy\n// users want to be able to cast lossily as well: `arr.astype(np.int8)` works\n// even when the source is f64. To support that, we provide `cast::<U>(kind)`\n// which uses the `CastTo` trait (covers every Element pair) and validates\n// the cast against the chosen safety level via `can_cast`.\n// ---------------------------------------------------------------------------\n\n#[cfg(not(feature = \"no_std\"))]\nimpl<T, D> crate::array::owned::Array<T, D>\nwhere\n    T: Element,\n    D: Dimension,\n{\n    /// Cast this array to element type `U` with the given safety check.\n    ///\n    /// This is the general-purpose casting method matching NumPy's\n    /// `arr.astype(dtype, casting=...)`. Unlike [`AsType::astype`] (which is\n    /// restricted to safe widening), this method permits any cast as long\n    /// as `can_cast(T::dtype(), U::dtype(), casting)` returns `true`.\n    ///\n    /// # Arguments\n    /// * `casting` — controls which casts are permitted:\n    ///   - [`CastKind::No`] / [`CastKind::Equiv`]: only `T == U` allowed\n    ///   - [`CastKind::Safe`]: information-preserving widening only\n    ///   - [`CastKind::SameKind`]: int↔int, float↔float, etc. (may narrow)\n    ///   - [`CastKind::Unsafe`]: any cast (the NumPy default)\n    ///\n    /// # Errors\n    /// Returns `FerrayError::InvalidDtype` if the requested cast is not\n    /// permitted at the chosen safety level.\n    ///\n    /// # Examples\n    /// ```\n    /// # use ferray_core::Array;\n    /// # use ferray_core::dimension::Ix1;\n    /// # use ferray_core::dtype::casting::CastKind;\n    /// let a = Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![1.5, 2.7, -3.9]).unwrap();\n    /// // f64 -> i32 truncates per `as` semantics\n    /// let b = a.cast::<i32>(CastKind::Unsafe).unwrap();\n    /// assert_eq!(b.as_slice().unwrap(), &[1, 2, -3]);\n    /// ```\n    pub fn cast<U: Element>(\n        &self,\n        casting: CastKind,\n    ) -> FerrayResult<crate::array::owned::Array<U, D>>\n    where\n        T: super::unsafe_cast::CastTo<U>,\n    {\n        if !can_cast(T::dtype(), U::dtype(), casting)? {\n            return Err(FerrayError::invalid_dtype(format!(\n                \"cannot cast {} -> {} with casting={:?}\",\n                T::dtype(),\n                U::dtype(),\n                casting\n            )));\n        }\n        let mapped = self\n            .inner\n            .mapv(|x| <T as super::unsafe_cast::CastTo<U>>::cast_to(x));\n        Ok(crate::array::owned::Array::from_ndarray(mapped))\n    }\n}\n\n// ---------------------------------------------------------------------------\n// view_cast() — reinterpret cast (zero-copy where possible)\n// ---------------------------------------------------------------------------\n\n/// Reinterpret the raw bytes of an array as a different element type.\n///\n/// This is analogous to NumPy's `.view(dtype)`. It requires that the element\n/// sizes match (or the last dimension is adjusted accordingly).\n///\n/// # Safety\n/// This is a reinterpret cast: the bit patterns are preserved but interpreted\n/// as a different type. The caller must ensure this is meaningful.\n///\n/// # Errors\n/// Returns `FerrayError::InvalidDtype` if the element sizes are incompatible.\n#[cfg(not(feature = \"no_std\"))]\npub fn view_cast<T: Element, U: Element, D: Dimension>(\n    arr: &crate::array::owned::Array<T, D>,\n) -> FerrayResult<crate::array::owned::Array<U, D>> {\n    let t_size = core::mem::size_of::<T>();\n    let u_size = core::mem::size_of::<U>();\n\n    if t_size != u_size {\n        return Err(FerrayError::invalid_dtype(format!(\n            \"view cast requires equal element sizes: {} ({} bytes) vs {} ({} bytes)\",\n            T::dtype(),\n            t_size,\n            U::dtype(),\n            u_size,\n        )));\n    }\n\n    let t_align = core::mem::align_of::<T>();\n    let u_align = core::mem::align_of::<U>();\n    if u_align > t_align {\n        return Err(FerrayError::invalid_dtype(format!(\n            \"view cast requires compatible alignment: {} (align {}) -> {} (align {})\",\n            T::dtype(),\n            t_align,\n            U::dtype(),\n            u_align,\n        )));\n    }\n\n    // Both types have the same size and compatible alignment\n    let data: Vec<T> = arr.inner.iter().cloned().collect();\n    let len = data.len();\n\n    // SAFETY: T and U have the same size and U's alignment <= T's alignment.\n    // The Vec<T> allocation satisfies U's alignment requirement.\n    let reinterpreted: Vec<U> = unsafe {\n        let mut data = core::mem::ManuallyDrop::new(data);\n        Vec::from_raw_parts(data.as_mut_ptr() as *mut U, len, len)\n    };\n\n    crate::array::owned::Array::from_vec(arr.dim().clone(), reinterpreted)\n}\n\n// ---------------------------------------------------------------------------\n// add_promoted / mul_promoted etc. — promoted binary operations (REQ-24)\n// ---------------------------------------------------------------------------\n\n#[cfg(not(feature = \"no_std\"))]\nimpl<T: Element, D: Dimension> crate::array::owned::Array<T, D> {\n    /// Add two arrays after promoting both to their common type.\n    ///\n    /// This is the explicit way to perform mixed-type addition (REQ-24).\n    /// Implicit mixed-type `+` does not compile.\n    pub fn add_promoted<U>(\n        &self,\n        other: &crate::array::owned::Array<U, D>,\n    ) -> FerrayResult<crate::array::owned::Array<<T as super::promotion::Promoted<U>>::Output, D>>\n    where\n        U: Element,\n        T: super::promotion::Promoted<U> + PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        U: PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        <T as super::promotion::Promoted<U>>::Output:\n            Element + core::ops::Add<Output = <T as super::promotion::Promoted<U>>::Output>,\n    {\n        type Out<A, B> = <A as super::promotion::Promoted<B>>::Output;\n\n        let a_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> = self.inner.mapv(|x| x.promote());\n        let b_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> =\n            other.inner.mapv(|x| x.promote());\n\n        let result = a_promoted + b_promoted;\n        Ok(crate::array::owned::Array::from_ndarray(result))\n    }\n\n    /// Subtract two arrays after promoting both to their common type.\n    pub fn sub_promoted<U>(\n        &self,\n        other: &crate::array::owned::Array<U, D>,\n    ) -> FerrayResult<crate::array::owned::Array<<T as super::promotion::Promoted<U>>::Output, D>>\n    where\n        U: Element,\n        T: super::promotion::Promoted<U> + PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        U: PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        <T as super::promotion::Promoted<U>>::Output:\n            Element + core::ops::Sub<Output = <T as super::promotion::Promoted<U>>::Output>,\n    {\n        type Out<A, B> = <A as super::promotion::Promoted<B>>::Output;\n\n        let a_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> = self.inner.mapv(|x| x.promote());\n        let b_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> =\n            other.inner.mapv(|x| x.promote());\n\n        let result = a_promoted - b_promoted;\n        Ok(crate::array::owned::Array::from_ndarray(result))\n    }\n\n    /// Multiply two arrays after promoting both to their common type.\n    pub fn mul_promoted<U>(\n        &self,\n        other: &crate::array::owned::Array<U, D>,\n    ) -> FerrayResult<crate::array::owned::Array<<T as super::promotion::Promoted<U>>::Output, D>>\n    where\n        U: Element,\n        T: super::promotion::Promoted<U> + PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        U: PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        <T as super::promotion::Promoted<U>>::Output:\n            Element + core::ops::Mul<Output = <T as super::promotion::Promoted<U>>::Output>,\n    {\n        type Out<A, B> = <A as super::promotion::Promoted<B>>::Output;\n\n        let a_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> = self.inner.mapv(|x| x.promote());\n        let b_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> =\n            other.inner.mapv(|x| x.promote());\n\n        let result = a_promoted * b_promoted;\n        Ok(crate::array::owned::Array::from_ndarray(result))\n    }\n\n    /// Divide two arrays after promoting both to their common type.\n    pub fn div_promoted<U>(\n        &self,\n        other: &crate::array::owned::Array<U, D>,\n    ) -> FerrayResult<crate::array::owned::Array<<T as super::promotion::Promoted<U>>::Output, D>>\n    where\n        U: Element,\n        T: super::promotion::Promoted<U> + PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        U: PromoteTo<<T as super::promotion::Promoted<U>>::Output>,\n        <T as super::promotion::Promoted<U>>::Output:\n            Element + core::ops::Div<Output = <T as super::promotion::Promoted<U>>::Output>,\n    {\n        type Out<A, B> = <A as super::promotion::Promoted<B>>::Output;\n\n        let a_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> = self.inner.mapv(|x| x.promote());\n        let b_promoted: ndarray::Array<Out<T, U>, D::NdarrayDim> =\n            other.inner.mapv(|x| x.promote());\n\n        let result = a_promoted / b_promoted;\n        Ok(crate::array::owned::Array::from_ndarray(result))\n    }\n}\n\n// ---------------------------------------------------------------------------\n// Tests\n// ---------------------------------------------------------------------------\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::dimension::Ix1;\n    use num_complex::Complex;\n\n    #[test]\n    fn can_cast_no() {\n        assert!(can_cast(DType::F64, DType::F64, CastKind::No).unwrap());\n        assert!(!can_cast(DType::F32, DType::F64, CastKind::No).unwrap());\n    }\n\n    #[test]\n    fn can_cast_safe() {\n        assert!(can_cast(DType::F32, DType::F64, CastKind::Safe).unwrap());\n        assert!(can_cast(DType::I32, DType::I64, CastKind::Safe).unwrap());\n        assert!(can_cast(DType::U8, DType::I16, CastKind::Safe).unwrap());\n        // Downcast is not safe\n        assert!(!can_cast(DType::F64, DType::F32, CastKind::Safe).unwrap());\n        assert!(!can_cast(DType::I64, DType::I32, CastKind::Safe).unwrap());\n    }\n\n    #[test]\n    fn can_cast_same_kind() {\n        // int -> int (different size) is same_kind\n        assert!(can_cast(DType::I64, DType::I32, CastKind::SameKind).unwrap());\n        assert!(can_cast(DType::F64, DType::F32, CastKind::SameKind).unwrap());\n        // float -> int is not same_kind\n        assert!(!can_cast(DType::F64, DType::I32, CastKind::SameKind).unwrap());\n    }\n\n    #[test]\n    fn can_cast_unsafe_allows_all() {\n        assert!(can_cast(DType::F64, DType::I8, CastKind::Unsafe).unwrap());\n        assert!(can_cast(DType::Complex64, DType::Bool, CastKind::Unsafe).unwrap());\n    }\n\n    #[test]\n    fn promote_types_basic() {\n        assert_eq!(promote_types(DType::F32, DType::F64).unwrap(), DType::F64);\n        assert_eq!(promote_types(DType::I32, DType::F32).unwrap(), DType::F64);\n        assert_eq!(promote_types(DType::U8, DType::I8).unwrap(), DType::I16);\n    }\n\n    #[test]\n    fn common_type_is_promote_types() {\n        assert_eq!(\n            common_type(DType::I32, DType::F64).unwrap(),\n            promote_types(DType::I32, DType::F64).unwrap()\n        );\n    }\n\n    #[test]\n    fn min_scalar_type_basics() {\n        assert_eq!(min_scalar_type(DType::I64), DType::I8);\n        assert_eq!(min_scalar_type(DType::F64), DType::F32);\n        assert_eq!(min_scalar_type(DType::Complex64), DType::Complex32);\n        assert_eq!(min_scalar_type(DType::Bool), DType::Bool);\n        assert_eq!(min_scalar_type(DType::U32), DType::U8);\n    }\n\n    #[test]\n    fn issubdtype_checks() {\n        assert!(issubdtype(DType::I32, DTypeCategory::Integer));\n        assert!(issubdtype(DType::I32, DTypeCategory::SignedInteger));\n        assert!(issubdtype(DType::I32, DTypeCategory::Number));\n        assert!(!issubdtype(DType::I32, DTypeCategory::Floating));\n        assert!(!issubdtype(DType::I32, DTypeCategory::ComplexFloating));\n\n        assert!(issubdtype(DType::U16, DTypeCategory::UnsignedInteger));\n        assert!(!issubdtype(DType::U16, DTypeCategory::SignedInteger));\n\n        assert!(issubdtype(DType::F64, DTypeCategory::Floating));\n        assert!(issubdtype(DType::F64, DTypeCategory::Number));\n        assert!(!issubdtype(DType::F64, DTypeCategory::Integer));\n\n        assert!(issubdtype(DType::Complex64, DTypeCategory::ComplexFloating));\n        assert!(issubdtype(DType::Complex64, DTypeCategory::Number));\n    }\n\n    #[test]\n    fn isrealobj_iscomplexobj() {\n        assert!(isrealobj::<f64>());\n        assert!(isrealobj::<i32>());\n        assert!(!isrealobj::<Complex<f64>>());\n\n        assert!(iscomplexobj::<Complex<f64>>());\n        assert!(iscomplexobj::<Complex<f32>>());\n        assert!(!iscomplexobj::<f64>());\n    }\n\n    #[test]\n    fn astype_widen() {\n        let arr =\n            crate::array::owned::Array::<i32, Ix1>::from_vec(Ix1::new([3]), vec![1, 2, 3]).unwrap();\n        let result = arr.astype::<f64>().unwrap();\n        assert_eq!(result.as_slice().unwrap(), &[1.0, 2.0, 3.0]);\n        assert_eq!(result.dtype(), DType::F64);\n    }\n\n    #[test]\n    fn astype_same_type() {\n        let arr = crate::array::owned::Array::<f64, Ix1>::from_vec(Ix1::new([2]), vec![1.5, 2.5])\n            .unwrap();\n        let result = arr.astype::<f64>().unwrap();\n        assert_eq!(result.as_slice().unwrap(), &[1.5, 2.5]);\n    }\n\n    #[test]\n    fn view_cast_same_size() {\n        // f32 and i32 are both 4 bytes\n        let arr = crate::array::owned::Array::<f32, Ix1>::from_vec(Ix1::new([2]), vec![1.0, 2.0])\n            .unwrap();\n        let result = view_cast::<f32, i32, Ix1>(&arr);\n        assert!(result.is_ok());\n        let casted = result.unwrap();\n        assert_eq!(casted.shape(), &[2]);\n    }\n\n    #[test]\n    fn view_cast_different_size_fails() {\n        let arr = crate::array::owned::Array::<f64, Ix1>::from_vec(Ix1::new([2]), vec![1.0, 2.0])\n            .unwrap();\n        let result = view_cast::<f64, f32, Ix1>(&arr);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn add_promoted_i32_f64() {\n        let a =\n            crate::array::owned::Array::<i32, Ix1>::from_vec(Ix1::new([3]), vec![1, 2, 3]).unwrap();\n        let b =\n            crate::array::owned::Array::<f64, Ix1>::from_vec(Ix1::new([3]), vec![0.5, 1.5, 2.5])\n                .unwrap();\n        let result = a.add_promoted(&b).unwrap();\n        assert_eq!(result.dtype(), DType::F64);\n        assert_eq!(result.as_slice().unwrap(), &[1.5, 3.5, 5.5]);\n    }\n\n    #[test]\n    fn mul_promoted_u8_i16() {\n        let a =\n            crate::array::owned::Array::<u8, Ix1>::from_vec(Ix1::new([3]), vec![2, 3, 4]).unwrap();\n        let b = crate::array::owned::Array::<i16, Ix1>::from_vec(Ix1::new([3]), vec![10, 20, 30])\n            .unwrap();\n        let result = a.mul_promoted(&b).unwrap();\n        // u8 + i16 => i16\n        assert_eq!(result.dtype(), DType::I16);\n        assert_eq!(result.as_slice().unwrap(), &[20i16, 60, 120]);\n    }\n\n    #[test]\n    fn sub_promoted_f32_f64() {\n        let a = crate::array::owned::Array::<f32, Ix1>::from_vec(Ix1::new([2]), vec![10.0, 20.0])\n            .unwrap();\n        let b = crate::array::owned::Array::<f64, Ix1>::from_vec(Ix1::new([2]), vec![1.0, 2.0])\n            .unwrap();\n        let result = a.sub_promoted(&b).unwrap();\n        assert_eq!(result.dtype(), DType::F64);\n        assert_eq!(result.as_slice().unwrap(), &[9.0, 18.0]);\n    }\n\n    // -----------------------------------------------------------------------\n    // Array::cast<U>(casting) — issue #361\n    // -----------------------------------------------------------------------\n\n    #[test]\n    fn cast_unsafe_f64_to_i32_truncates() {\n        let a = crate::array::owned::Array::<f64, Ix1>::from_vec(\n            Ix1::new([4]),\n            vec![1.5, 2.7, -3.9, 4.2],\n        )\n        .unwrap();\n        let b = a.cast::<i32>(CastKind::Unsafe).unwrap();\n        assert_eq!(b.as_slice().unwrap(), &[1, 2, -3, 4]);\n        assert_eq!(b.dtype(), DType::I32);\n    }\n\n    #[test]\n    fn cast_safe_widening_succeeds() {\n        let a =\n            crate::array::owned::Array::<i32, Ix1>::from_vec(Ix1::new([3]), vec![1, 2, 3]).unwrap();\n        let b = a.cast::<i64>(CastKind::Safe).unwrap();\n        assert_eq!(b.as_slice().unwrap(), &[1i64, 2, 3]);\n    }\n\n    #[test]\n    fn cast_safe_narrowing_errors() {\n        let a = crate::array::owned::Array::<f64, Ix1>::from_vec(\n            Ix1::new([3]),\n            vec![1.0, 2.0, 3.0],\n        )\n        .unwrap();\n        let result = a.cast::<i32>(CastKind::Safe);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn cast_same_kind_int_narrowing_succeeds() {\n        let a = crate::array::owned::Array::<i64, Ix1>::from_vec(\n            Ix1::new([3]),\n            vec![1, 2, 3],\n        )\n        .unwrap();\n        let b = a.cast::<i32>(CastKind::SameKind).unwrap();\n        assert_eq!(b.as_slice().unwrap(), &[1, 2, 3]);\n    }\n\n    #[test]\n    fn cast_same_kind_float_to_int_errors() {\n        let a = crate::array::owned::Array::<f64, Ix1>::from_vec(\n            Ix1::new([2]),\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let result = a.cast::<i32>(CastKind::SameKind);\n        assert!(result.is_err());\n    }\n\n    #[test]\n    fn cast_no_requires_identity() {\n        let a = crate::array::owned::Array::<f64, Ix1>::from_vec(\n            Ix1::new([2]),\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        // Same type allowed\n        assert!(a.cast::<f64>(CastKind::No).is_ok());\n        // Different type rejected\n        assert!(a.cast::<f32>(CastKind::No).is_err());\n    }\n\n    #[test]\n    fn cast_complex_to_real_unsafe() {\n        let a = crate::array::owned::Array::<Complex<f64>, Ix1>::from_vec(\n            Ix1::new([2]),\n            vec![Complex::new(1.5, 2.0), Complex::new(3.5, -1.0)],\n        )\n        .unwrap();\n        let b = a.cast::<f64>(CastKind::Unsafe).unwrap();\n        assert_eq!(b.as_slice().unwrap(), &[1.5, 3.5]);\n    }\n\n    #[test]\n    fn cast_real_to_complex_safe() {\n        let a = crate::array::owned::Array::<f32, Ix1>::from_vec(\n            Ix1::new([2]),\n            vec![1.0, 2.0],\n        )\n        .unwrap();\n        let b = a.cast::<Complex<f64>>(CastKind::Safe).unwrap();\n        assert_eq!(\n            b.as_slice().unwrap(),\n            &[Complex::new(1.0_f64, 0.0), Complex::new(2.0, 0.0)]\n        );\n    }\n\n    #[test]\n    fn cast_int_to_bool_unsafe() {\n        let a = crate::array::owned::Array::<i32, Ix1>::from_vec(\n            Ix1::new([4]),\n            vec![0, 1, -3, 0],\n        )\n        .unwrap();\n        let b = a.cast::<bool>(CastKind::Unsafe).unwrap();\n        assert_eq!(b.as_slice().unwrap(), &[false, true, true, false]);\n    }\n}\n"}}
{"timestamp":"1775712102","file_path":"/home/doll/ferray/ferray-core/src/dimension/mod.rs","entry":{"original_text":"// ferray-core: Dimension trait and concrete dimension types\n//\n// These types mirror ndarray's Ix1..Ix6 and IxDyn but live in ferray-core's\n// namespace so that ndarray never appears in the public API.\n\n#[cfg(not(feature = \"no_std\"))]\npub mod broadcast;\n// static_shape depends on Array, Vec, format!, and the ndarray-gated methods\n// on Dimension, so it's only available when std is in scope.\n#[cfg(all(feature = \"const_shapes\", not(feature = \"no_std\")))]\npub mod static_shape;\n\nuse core::fmt;\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n#[cfg(feature = \"no_std\")]\nuse alloc::vec::Vec;\n\n// We need ndarray's Dimension trait in scope for `as_array_view()` etc.\n#[cfg(not(feature = \"no_std\"))]\nuse ndarray::Dimension as NdDimension;\n\n/// Trait for types that describe the dimensionality of an array.\n///\n/// Each dimension type knows its number of axes at the type level\n/// (except [`IxDyn`] which carries it at runtime).\npub trait Dimension: Clone + PartialEq + Eq + fmt::Debug + Send + Sync + 'static {\n    /// The number of axes, or `None` for dynamic-rank arrays.\n    const NDIM: Option<usize>;\n\n    /// The corresponding `ndarray` dimension type (private, not exposed in public API).\n    #[doc(hidden)]\n    #[cfg(not(feature = \"no_std\"))]\n    type NdarrayDim: ndarray::Dimension;\n\n    /// Return the shape as a slice.\n    fn as_slice(&self) -> &[usize];\n\n    /// Return the shape as a mutable slice.\n    fn as_slice_mut(&mut self) -> &mut [usize];\n\n    /// Number of dimensions.\n    fn ndim(&self) -> usize {\n        self.as_slice().len()\n    }\n\n    /// Total number of elements (product of all dimension sizes).\n    fn size(&self) -> usize {\n        self.as_slice().iter().product()\n    }\n\n    /// Convert to the internal ndarray dimension type.\n    #[doc(hidden)]\n    #[cfg(not(feature = \"no_std\"))]\n    fn to_ndarray_dim(&self) -> Self::NdarrayDim;\n\n    /// Create from the internal ndarray dimension type.\n    #[doc(hidden)]\n    #[cfg(not(feature = \"no_std\"))]\n    fn from_ndarray_dim(dim: &Self::NdarrayDim) -> Self;\n\n    /// Construct a dimension from a slice of axis lengths.\n    ///\n    /// Returns `None` if the slice length does not match `Self::NDIM`\n    /// for fixed-rank dimensions. Always succeeds for [`IxDyn`].\n    ///\n    /// This is the inverse of [`Dimension::as_slice`].\n    fn from_dim_slice(shape: &[usize]) -> Option<Self>;\n}\n\n// ---------------------------------------------------------------------------\n// Fixed-rank dimension types\n// ---------------------------------------------------------------------------\n\nmacro_rules! impl_fixed_dimension {\n    ($name:ident, $n:expr, $ndarray_ty:ty) => {\n        /// A fixed-rank dimension with\n        #[doc = concat!(stringify!($n), \" axes.\")]\n        #[derive(Clone, PartialEq, Eq, Hash)]\n        pub struct $name {\n            shape: [usize; $n],\n        }\n\n        impl $name {\n            /// Create a new dimension from a fixed-size array.\n            #[inline]\n            pub fn new(shape: [usize; $n]) -> Self {\n                Self { shape }\n            }\n        }\n\n        impl fmt::Debug for $name {\n            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n                write!(f, \"{:?}\", &self.shape[..])\n            }\n        }\n\n        impl From<[usize; $n]> for $name {\n            #[inline]\n            fn from(shape: [usize; $n]) -> Self {\n                Self::new(shape)\n            }\n        }\n\n        impl Dimension for $name {\n            const NDIM: Option<usize> = Some($n);\n\n            #[cfg(not(feature = \"no_std\"))]\n            type NdarrayDim = $ndarray_ty;\n\n            #[inline]\n            fn as_slice(&self) -> &[usize] {\n                &self.shape\n            }\n\n            #[inline]\n            fn as_slice_mut(&mut self) -> &mut [usize] {\n                &mut self.shape\n            }\n\n            #[cfg(not(feature = \"no_std\"))]\n            fn to_ndarray_dim(&self) -> Self::NdarrayDim {\n                // ndarray::Dim implements From<[usize; N]> for N=1..6\n                ndarray::Dim(self.shape)\n            }\n\n            #[cfg(not(feature = \"no_std\"))]\n            fn from_ndarray_dim(dim: &Self::NdarrayDim) -> Self {\n                let view = dim.as_array_view();\n                let s = view.as_slice().expect(\"ndarray dim should be contiguous\");\n                let mut shape = [0usize; $n];\n                shape.copy_from_slice(s);\n                Self { shape }\n            }\n\n            fn from_dim_slice(shape: &[usize]) -> Option<Self> {\n                if shape.len() != $n {\n                    return None;\n                }\n                let mut arr = [0usize; $n];\n                arr.copy_from_slice(shape);\n                Some(Self { shape: arr })\n            }\n        }\n    };\n}\n\nimpl_fixed_dimension!(Ix1, 1, ndarray::Ix1);\nimpl_fixed_dimension!(Ix2, 2, ndarray::Ix2);\nimpl_fixed_dimension!(Ix3, 3, ndarray::Ix3);\nimpl_fixed_dimension!(Ix4, 4, ndarray::Ix4);\nimpl_fixed_dimension!(Ix5, 5, ndarray::Ix5);\nimpl_fixed_dimension!(Ix6, 6, ndarray::Ix6);\n\n// ---------------------------------------------------------------------------\n// Ix0: scalar (0-dimensional)\n// ---------------------------------------------------------------------------\n\n/// A zero-dimensional (scalar) dimension.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct Ix0;\n\nimpl fmt::Debug for Ix0 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"[]\")\n    }\n}\n\nimpl Dimension for Ix0 {\n    const NDIM: Option<usize> = Some(0);\n\n    #[cfg(not(feature = \"no_std\"))]\n    type NdarrayDim = ndarray::Ix0;\n\n    #[inline]\n    fn as_slice(&self) -> &[usize] {\n        &[]\n    }\n\n    #[inline]\n    fn as_slice_mut(&mut self) -> &mut [usize] {\n        &mut []\n    }\n\n    #[cfg(not(feature = \"no_std\"))]\n    fn to_ndarray_dim(&self) -> Self::NdarrayDim {\n        ndarray::Dim(())\n    }\n\n    #[cfg(not(feature = \"no_std\"))]\n    fn from_ndarray_dim(_dim: &Self::NdarrayDim) -> Self {\n        Ix0\n    }\n\n    fn from_dim_slice(shape: &[usize]) -> Option<Self> {\n        if shape.is_empty() { Some(Ix0) } else { None }\n    }\n}\n\n// ---------------------------------------------------------------------------\n// IxDyn: dynamic-rank dimension\n// ---------------------------------------------------------------------------\n\n/// A dynamic-rank dimension whose number of axes is determined at runtime.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct IxDyn {\n    shape: Vec<usize>,\n}\n\nimpl IxDyn {\n    /// Create a new dynamic dimension from a slice.\n    pub fn new(shape: &[usize]) -> Self {\n        Self {\n            shape: shape.to_vec(),\n        }\n    }\n}\n\nimpl fmt::Debug for IxDyn {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{:?}\", &self.shape[..])\n    }\n}\n\nimpl From<Vec<usize>> for IxDyn {\n    fn from(shape: Vec<usize>) -> Self {\n        Self { shape }\n    }\n}\n\nimpl From<&[usize]> for IxDyn {\n    fn from(shape: &[usize]) -> Self {\n        Self::new(shape)\n    }\n}\n\nimpl Dimension for IxDyn {\n    const NDIM: Option<usize> = None;\n\n    #[cfg(not(feature = \"no_std\"))]\n    type NdarrayDim = ndarray::IxDyn;\n\n    #[inline]\n    fn as_slice(&self) -> &[usize] {\n        &self.shape\n    }\n\n    #[inline]\n    fn as_slice_mut(&mut self) -> &mut [usize] {\n        &mut self.shape\n    }\n\n    #[cfg(not(feature = \"no_std\"))]\n    fn to_ndarray_dim(&self) -> Self::NdarrayDim {\n        ndarray::IxDyn(&self.shape)\n    }\n\n    #[cfg(not(feature = \"no_std\"))]\n    fn from_ndarray_dim(dim: &Self::NdarrayDim) -> Self {\n        let view = dim.as_array_view();\n        let s = view.as_slice().expect(\"ndarray IxDyn should be contiguous\");\n        Self { shape: s.to_vec() }\n    }\n\n    fn from_dim_slice(shape: &[usize]) -> Option<Self> {\n        Some(Self { shape: shape.to_vec() })\n    }\n}\n\n/// Newtype for axis indices used throughout ferray.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Axis(pub usize);\n\nimpl Axis {\n    /// Return the axis index.\n    #[inline]\n    pub fn index(self) -> usize {\n        self.0\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn ix1_basics() {\n        let d = Ix1::new([5]);\n        assert_eq!(d.ndim(), 1);\n        assert_eq!(d.size(), 5);\n        assert_eq!(d.as_slice(), &[5]);\n    }\n\n    #[test]\n    fn ix2_basics() {\n        let d = Ix2::new([3, 4]);\n        assert_eq!(d.ndim(), 2);\n        assert_eq!(d.size(), 12);\n    }\n\n    #[test]\n    fn ix0_basics() {\n        let d = Ix0;\n        assert_eq!(d.ndim(), 0);\n        assert_eq!(d.size(), 1);\n    }\n\n    #[test]\n    fn ixdyn_basics() {\n        let d = IxDyn::new(&[2, 3, 4]);\n        assert_eq!(d.ndim(), 3);\n        assert_eq!(d.size(), 24);\n    }\n\n    #[test]\n    fn roundtrip_ix2_ndarray() {\n        let d = Ix2::new([3, 7]);\n        let nd = d.to_ndarray_dim();\n        let d2 = Ix2::from_ndarray_dim(&nd);\n        assert_eq!(d, d2);\n    }\n\n    #[test]\n    fn roundtrip_ixdyn_ndarray() {\n        let d = IxDyn::new(&[2, 5, 3]);\n        let nd = d.to_ndarray_dim();\n        let d2 = IxDyn::from_ndarray_dim(&nd);\n        assert_eq!(d, d2);\n    }\n\n    #[test]\n    fn axis_index() {\n        let a = Axis(2);\n        assert_eq!(a.index(), 2);\n    }\n}\n"}}
{"timestamp":"1775712102","file_path":"/home/doll/ferray/ferray-core/src/lib.rs","entry":{"original_text":"// ferray-core: N-dimensional array type and foundational primitives\n//\n// This is the root crate for the ferray workspace. It provides NdArray<T, D>,\n// the full ownership model (Array, ArrayView, ArrayViewMut, ArcArray, CowArray),\n// the Element trait, DType system, FerrayError, and array introspection/iteration.\n//\n// ndarray is used internally but is NOT part of the public API.\n//\n// When the `no_std` feature is enabled, only the subset of functionality that\n// does not depend on `std` or `ndarray` is compiled: DType, Element, FerrayError\n// (simplified), dimension types (without ndarray conversions), constants, and\n// layout enums. The full Array type and related features require `std`.\n\n#![cfg_attr(feature = \"no_std\", no_std)]\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n\n// Modules that work in both std and no_std modes\npub mod constants;\npub mod dimension;\npub mod dtype;\npub mod error;\npub mod layout;\npub mod record;\n\n// Modules that require std (depend on ndarray or std-only features)\n#[cfg(not(feature = \"no_std\"))]\npub mod array;\n#[cfg(not(feature = \"no_std\"))]\npub mod buffer;\n#[cfg(not(feature = \"no_std\"))]\npub mod creation;\n#[cfg(not(feature = \"no_std\"))]\npub mod dynarray;\n#[cfg(not(feature = \"no_std\"))]\npub mod indexing;\n#[cfg(not(feature = \"no_std\"))]\npub mod manipulation;\n#[cfg(not(feature = \"no_std\"))]\npub mod nditer;\n#[cfg(not(feature = \"no_std\"))]\npub mod ops;\n#[cfg(not(feature = \"no_std\"))]\npub mod prelude;\n\n// Re-export key types at crate root for ergonomics (std only)\n#[cfg(not(feature = \"no_std\"))]\npub use array::ArrayFlags;\n#[cfg(not(feature = \"no_std\"))]\npub use array::aliases;\n#[cfg(not(feature = \"no_std\"))]\npub use array::arc::ArcArray;\n#[cfg(not(feature = \"no_std\"))]\npub use array::cow::CowArray;\n#[cfg(not(feature = \"no_std\"))]\npub use array::display::{get_print_options, set_print_options};\n#[cfg(not(feature = \"no_std\"))]\npub use array::owned::Array;\n#[cfg(not(feature = \"no_std\"))]\npub use array::view::ArrayView;\n#[cfg(not(feature = \"no_std\"))]\npub use array::view_mut::ArrayViewMut;\n\npub use dimension::{Axis, Dimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn};\n\n#[cfg(all(feature = \"const_shapes\", not(feature = \"no_std\")))]\npub use dimension::static_shape::{\n    Assert, DefaultNdarrayDim, IsTrue, Shape1, Shape2, Shape3, Shape4, Shape5, Shape6,\n    StaticBroadcast, StaticMatMul, StaticSize, static_reshape_array,\n};\n\npub use dtype::{DType, Element, SliceInfoElem};\n\npub use error::{FerrayError, FerrayResult};\n\npub use layout::MemoryLayout;\n\n#[cfg(not(feature = \"no_std\"))]\npub use buffer::AsRawBuffer;\n\n#[cfg(not(feature = \"no_std\"))]\npub use dynarray::DynArray;\n\n#[cfg(not(feature = \"no_std\"))]\npub use nditer::NdIter;\n\npub use record::FieldDescriptor;\n\n// Re-export proc macros from ferray-core-macros.\n// The derive macro FerrayRecord shares its name with the trait in record::FerrayRecord.\n// Both are re-exported: the derive macro lives in macro namespace, the trait in type namespace.\npub use ferray_core_macros::{FerrayRecord, promoted_type, s};\npub use record::FerrayRecord;\n\n// Kani formal verification harnesses (only compiled during `cargo kani`)\n#[cfg(kani)]\nmod verification_kani;\n"}}
{"timestamp":"1775712107","file_path":"/home/doll/ferray/ferray-core/src/error.rs","entry":{"original_text":"// ferray-core: Error types (REQ-27)\n\nuse core::fmt;\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n#[cfg(feature = \"no_std\")]\nuse alloc::{string::String, string::ToString, vec::Vec};\n\n/// The primary error type for all ferray operations.\n///\n/// This enum is `#[non_exhaustive]`, so new variants may be added\n/// in minor releases without breaking downstream code.\n#[derive(Debug, Clone, thiserror::Error)]\n#[non_exhaustive]\npub enum FerrayError {\n    /// Operand shapes are incompatible for the requested operation.\n    #[error(\"shape mismatch: {message}\")]\n    ShapeMismatch {\n        /// Human-readable description of the mismatch.\n        message: String,\n    },\n\n    /// Broadcasting failed because shapes cannot be reconciled.\n    #[error(\"broadcast failure: cannot broadcast shapes {shape_a:?} and {shape_b:?}\")]\n    BroadcastFailure {\n        /// First shape.\n        shape_a: Vec<usize>,\n        /// Second shape.\n        shape_b: Vec<usize>,\n    },\n\n    /// An axis index exceeded the array's dimensionality.\n    #[error(\"axis {axis} is out of bounds for array with {ndim} dimensions\")]\n    AxisOutOfBounds {\n        /// The invalid axis.\n        axis: usize,\n        /// Number of dimensions.\n        ndim: usize,\n    },\n\n    /// An element index exceeded the array's extent along some axis.\n    #[error(\"index {index} is out of bounds for axis {axis} with size {size}\")]\n    IndexOutOfBounds {\n        /// The invalid index.\n        index: isize,\n        /// The axis along which the index was applied.\n        axis: usize,\n        /// The size of that axis.\n        size: usize,\n    },\n\n    /// A matrix was singular when an invertible one was required.\n    #[error(\"singular matrix: {message}\")]\n    SingularMatrix {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// An iterative algorithm did not converge within its budget.\n    #[error(\"convergence failure after {iterations} iterations: {message}\")]\n    ConvergenceFailure {\n        /// Number of iterations attempted.\n        iterations: usize,\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// The requested dtype is invalid or unsupported for this operation.\n    #[error(\"invalid dtype: {message}\")]\n    InvalidDtype {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// A computation produced NaN / Inf when finite results were required.\n    #[error(\"numerical instability: {message}\")]\n    NumericalInstability {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// An I/O operation failed.\n    #[error(\"I/O error: {message}\")]\n    IoError {\n        /// Diagnostic context.\n        message: String,\n    },\n\n    /// A function argument was invalid.\n    #[error(\"invalid value: {message}\")]\n    InvalidValue {\n        /// Diagnostic context.\n        message: String,\n    },\n}\n\n/// Convenience alias used throughout ferray.\npub type FerrayResult<T> = Result<T, FerrayError>;\n\nimpl FerrayError {\n    /// Create a `ShapeMismatch` error with a formatted message.\n    pub fn shape_mismatch(msg: impl fmt::Display) -> Self {\n        Self::ShapeMismatch {\n            message: msg.to_string(),\n        }\n    }\n\n    /// Create a `BroadcastFailure` error.\n    pub fn broadcast_failure(a: &[usize], b: &[usize]) -> Self {\n        Self::BroadcastFailure {\n            shape_a: a.to_vec(),\n            shape_b: b.to_vec(),\n        }\n    }\n\n    /// Create an `AxisOutOfBounds` error.\n    pub fn axis_out_of_bounds(axis: usize, ndim: usize) -> Self {\n        Self::AxisOutOfBounds { axis, ndim }\n    }\n\n    /// Create an `IndexOutOfBounds` error.\n    pub fn index_out_of_bounds(index: isize, axis: usize, size: usize) -> Self {\n        Self::IndexOutOfBounds { index, axis, size }\n    }\n\n    /// Create an `InvalidDtype` error with a formatted message.\n    pub fn invalid_dtype(msg: impl fmt::Display) -> Self {\n        Self::InvalidDtype {\n            message: msg.to_string(),\n        }\n    }\n\n    /// Create an `InvalidValue` error with a formatted message.\n    pub fn invalid_value(msg: impl fmt::Display) -> Self {\n        Self::InvalidValue {\n            message: msg.to_string(),\n        }\n    }\n\n    /// Create an `IoError` from a formatted message.\n    pub fn io_error(msg: impl fmt::Display) -> Self {\n        Self::IoError {\n            message: msg.to_string(),\n        }\n    }\n}\n\n#[cfg(feature = \"std\")]\nimpl From<std::io::Error> for FerrayError {\n    fn from(e: std::io::Error) -> Self {\n        Self::IoError {\n            message: e.to_string(),\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn error_display_shape_mismatch() {\n        let e = FerrayError::shape_mismatch(\"expected (3,4), got (3,5)\");\n        assert!(e.to_string().contains(\"expected (3,4), got (3,5)\"));\n    }\n\n    #[test]\n    fn error_display_axis_out_of_bounds() {\n        let e = FerrayError::axis_out_of_bounds(5, 3);\n        assert!(e.to_string().contains(\"axis 5\"));\n        assert!(e.to_string().contains(\"3 dimensions\"));\n    }\n\n    #[test]\n    fn error_display_broadcast_failure() {\n        let e = FerrayError::broadcast_failure(&[4, 3], &[2, 5]);\n        let s = e.to_string();\n        assert!(s.contains(\"[4, 3]\"));\n        assert!(s.contains(\"[2, 5]\"));\n    }\n\n    #[test]\n    fn error_is_non_exhaustive() {\n        // Verify the enum is non_exhaustive by using a wildcard\n        // in a match from an \"external\" perspective. Inside this crate\n        // the compiler knows all variants, so we just verify construction.\n        let e = FerrayError::invalid_value(\"test\");\n        assert!(matches!(e, FerrayError::InvalidValue { .. }));\n\n        let e2 = FerrayError::shape_mismatch(\"bad shape\");\n        assert!(matches!(e2, FerrayError::ShapeMismatch { .. }));\n    }\n\n    #[test]\n    fn from_io_error() {\n        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, \"file missing\");\n        let ferray_err: FerrayError = io_err.into();\n        assert!(ferray_err.to_string().contains(\"file missing\"));\n    }\n}\n"}}
{"timestamp":"1775712107","file_path":"/home/doll/ferray/ferray-core/src/dtype/promotion.rs","entry":{"original_text":"// ferray-core: Type promotion rules (REQ-23)\n//\n// Implements NumPy's type promotion rules: the smallest type that can represent\n// both inputs without precision loss.\n//\n// - `promoted_type!()` is a compile-time proc macro (in ferray-core-macros)\n// - `result_type()` is the runtime equivalent using DType\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n#[cfg(feature = \"no_std\")]\nuse alloc::format;\n\nuse num_complex::Complex;\n\nuse crate::dtype::{DType, Element};\nuse crate::error::{FerrayError, FerrayResult};\n\n// ---------------------------------------------------------------------------\n// Promoted trait — compile-time type promotion via trait resolution\n// ---------------------------------------------------------------------------\n\n/// Trait that resolves the promoted type of two `Element` types at compile time.\n///\n/// This is the trait-based counterpart to `promoted_type!()`. The macro is\n/// generally more convenient, but this trait can be useful in generic contexts.\npub trait Promoted<Rhs: Element>: Element {\n    /// The promoted type that can represent both `Self` and `Rhs`.\n    type Output: Element;\n}\n\n// ---------------------------------------------------------------------------\n// PromoteTo — explicit conversion for promoted operations\n// ---------------------------------------------------------------------------\n\n/// Trait for converting a value to a promoted type.\n///\n/// This is used by `add_promoted()`, `mul_promoted()`, etc. to explicitly\n/// convert operands before performing mixed-type operations.\npub trait PromoteTo<Target: Element>: Element {\n    /// Convert this value to the target type.\n    fn promote(self) -> Target;\n}\n\n// Implement PromoteTo for same-type (identity)\nmacro_rules! impl_promote_identity {\n    ($ty:ty) => {\n        impl PromoteTo<$ty> for $ty {\n            #[inline]\n            fn promote(self) -> $ty {\n                self\n            }\n        }\n    };\n}\n\nimpl_promote_identity!(bool);\nimpl_promote_identity!(u8);\nimpl_promote_identity!(u16);\nimpl_promote_identity!(u32);\nimpl_promote_identity!(u64);\nimpl_promote_identity!(u128);\nimpl_promote_identity!(i8);\nimpl_promote_identity!(i16);\nimpl_promote_identity!(i32);\nimpl_promote_identity!(i64);\nimpl_promote_identity!(i128);\nimpl_promote_identity!(f32);\nimpl_promote_identity!(f64);\nimpl_promote_identity!(Complex<f32>);\nimpl_promote_identity!(Complex<f64>);\n\n// Implement PromoteTo for numeric widening conversions\nmacro_rules! impl_promote_as {\n    ($from:ty => $to:ty) => {\n        impl PromoteTo<$to> for $from {\n            #[inline]\n            fn promote(self) -> $to {\n                self as $to\n            }\n        }\n    };\n}\n\n// bool -> everything (can't use `as` cast for bool -> float)\nmacro_rules! impl_promote_bool_to_int {\n    ($to:ty) => {\n        impl PromoteTo<$to> for bool {\n            #[inline]\n            fn promote(self) -> $to {\n                self as $to\n            }\n        }\n    };\n}\n\nimpl_promote_bool_to_int!(u8);\nimpl_promote_bool_to_int!(u16);\nimpl_promote_bool_to_int!(u32);\nimpl_promote_bool_to_int!(u64);\nimpl_promote_bool_to_int!(u128);\nimpl_promote_bool_to_int!(i8);\nimpl_promote_bool_to_int!(i16);\nimpl_promote_bool_to_int!(i32);\nimpl_promote_bool_to_int!(i64);\nimpl_promote_bool_to_int!(i128);\n\nimpl PromoteTo<f32> for bool {\n    #[inline]\n    fn promote(self) -> f32 {\n        if self { 1.0 } else { 0.0 }\n    }\n}\n\nimpl PromoteTo<f64> for bool {\n    #[inline]\n    fn promote(self) -> f64 {\n        if self { 1.0 } else { 0.0 }\n    }\n}\n\nimpl PromoteTo<Complex<f32>> for bool {\n    #[inline]\n    fn promote(self) -> Complex<f32> {\n        Complex::new(if self { 1.0 } else { 0.0 }, 0.0)\n    }\n}\n\nimpl PromoteTo<Complex<f64>> for bool {\n    #[inline]\n    fn promote(self) -> Complex<f64> {\n        Complex::new(if self { 1.0 } else { 0.0 }, 0.0)\n    }\n}\n\n// Unsigned integer widening\nimpl_promote_as!(u8 => u16);\nimpl_promote_as!(u8 => u32);\nimpl_promote_as!(u8 => u64);\nimpl_promote_as!(u8 => u128);\nimpl_promote_as!(u8 => i16);\nimpl_promote_as!(u8 => i32);\nimpl_promote_as!(u8 => i64);\nimpl_promote_as!(u8 => i128);\nimpl_promote_as!(u8 => f32);\nimpl_promote_as!(u8 => f64);\nimpl_promote_as!(u16 => u32);\nimpl_promote_as!(u16 => u64);\nimpl_promote_as!(u16 => u128);\nimpl_promote_as!(u16 => i32);\nimpl_promote_as!(u16 => i64);\nimpl_promote_as!(u16 => i128);\nimpl_promote_as!(u16 => f32);\nimpl_promote_as!(u16 => f64);\nimpl_promote_as!(u32 => u64);\nimpl_promote_as!(u32 => u128);\nimpl_promote_as!(u32 => i64);\nimpl_promote_as!(u32 => i128);\nimpl_promote_as!(u32 => f64);\nimpl_promote_as!(u64 => u128);\nimpl_promote_as!(u64 => i128);\nimpl_promote_as!(u64 => f64);\nimpl_promote_as!(u128 => f64);\n\n// Signed integer widening\nimpl_promote_as!(i8 => i16);\nimpl_promote_as!(i8 => i32);\nimpl_promote_as!(i8 => i64);\nimpl_promote_as!(i8 => i128);\nimpl_promote_as!(i8 => f32);\nimpl_promote_as!(i8 => f64);\nimpl_promote_as!(i16 => i32);\nimpl_promote_as!(i16 => i64);\nimpl_promote_as!(i16 => i128);\nimpl_promote_as!(i16 => f32);\nimpl_promote_as!(i16 => f64);\nimpl_promote_as!(i32 => i64);\nimpl_promote_as!(i32 => i128);\nimpl_promote_as!(i32 => f64);\nimpl_promote_as!(i64 => i128);\nimpl_promote_as!(i64 => f64);\nimpl_promote_as!(i128 => f64);\n\n// Float widening\nimpl_promote_as!(f32 => f64);\n\n// Integer -> Complex\nmacro_rules! impl_promote_int_to_complex {\n    ($from:ty) => {\n        impl PromoteTo<Complex<f32>> for $from {\n            #[inline]\n            fn promote(self) -> Complex<f32> {\n                Complex::new(self as f32, 0.0)\n            }\n        }\n        impl PromoteTo<Complex<f64>> for $from {\n            #[inline]\n            fn promote(self) -> Complex<f64> {\n                Complex::new(self as f64, 0.0)\n            }\n        }\n    };\n}\n\nimpl_promote_int_to_complex!(u8);\nimpl_promote_int_to_complex!(u16);\nimpl_promote_int_to_complex!(u32);\nimpl_promote_int_to_complex!(u64);\nimpl_promote_int_to_complex!(u128);\nimpl_promote_int_to_complex!(i8);\nimpl_promote_int_to_complex!(i16);\nimpl_promote_int_to_complex!(i32);\nimpl_promote_int_to_complex!(i64);\nimpl_promote_int_to_complex!(i128);\n\n// Float -> Complex\nimpl PromoteTo<Complex<f32>> for f32 {\n    #[inline]\n    fn promote(self) -> Complex<f32> {\n        Complex::new(self, 0.0)\n    }\n}\n\nimpl PromoteTo<Complex<f64>> for f32 {\n    #[inline]\n    fn promote(self) -> Complex<f64> {\n        Complex::new(self as f64, 0.0)\n    }\n}\n\nimpl PromoteTo<Complex<f64>> for f64 {\n    #[inline]\n    fn promote(self) -> Complex<f64> {\n        Complex::new(self, 0.0)\n    }\n}\n\nimpl PromoteTo<Complex<f64>> for Complex<f32> {\n    #[inline]\n    fn promote(self) -> Complex<f64> {\n        Complex::new(self.re as f64, self.im as f64)\n    }\n}\n\n// ---------------------------------------------------------------------------\n// Promoted trait implementations (compile-time promotion pairs)\n// ---------------------------------------------------------------------------\n\n// Macro for implementing Promoted where both sides promote to the same Output\nmacro_rules! impl_promoted {\n    ($a:ty, $b:ty => $out:ty) => {\n        impl Promoted<$b> for $a {\n            type Output = $out;\n        }\n        impl Promoted<$a> for $b {\n            type Output = $out;\n        }\n    };\n}\n\n// Same-type promotions\nmacro_rules! impl_promoted_self {\n    ($ty:ty) => {\n        impl Promoted<$ty> for $ty {\n            type Output = $ty;\n        }\n    };\n}\n\nimpl_promoted_self!(bool);\nimpl_promoted_self!(u8);\nimpl_promoted_self!(u16);\nimpl_promoted_self!(u32);\nimpl_promoted_self!(u64);\nimpl_promoted_self!(u128);\nimpl_promoted_self!(i8);\nimpl_promoted_self!(i16);\nimpl_promoted_self!(i32);\nimpl_promoted_self!(i64);\nimpl_promoted_self!(i128);\nimpl_promoted_self!(f32);\nimpl_promoted_self!(f64);\nimpl_promoted_self!(Complex<f32>);\nimpl_promoted_self!(Complex<f64>);\n\n// bool + anything -> anything\nimpl_promoted!(bool, u8 => u8);\nimpl_promoted!(bool, u16 => u16);\nimpl_promoted!(bool, u32 => u32);\nimpl_promoted!(bool, u64 => u64);\nimpl_promoted!(bool, u128 => u128);\nimpl_promoted!(bool, i8 => i8);\nimpl_promoted!(bool, i16 => i16);\nimpl_promoted!(bool, i32 => i32);\nimpl_promoted!(bool, i64 => i64);\nimpl_promoted!(bool, i128 => i128);\nimpl_promoted!(bool, f32 => f32);\nimpl_promoted!(bool, f64 => f64);\nimpl_promoted!(bool, Complex<f32> => Complex<f32>);\nimpl_promoted!(bool, Complex<f64> => Complex<f64>);\n\n// Unsigned + Unsigned\nimpl_promoted!(u8, u16 => u16);\nimpl_promoted!(u8, u32 => u32);\nimpl_promoted!(u8, u64 => u64);\nimpl_promoted!(u8, u128 => u128);\nimpl_promoted!(u16, u32 => u32);\nimpl_promoted!(u16, u64 => u64);\nimpl_promoted!(u16, u128 => u128);\nimpl_promoted!(u32, u64 => u64);\nimpl_promoted!(u32, u128 => u128);\nimpl_promoted!(u64, u128 => u128);\n\n// Signed + Signed\nimpl_promoted!(i8, i16 => i16);\nimpl_promoted!(i8, i32 => i32);\nimpl_promoted!(i8, i64 => i64);\nimpl_promoted!(i8, i128 => i128);\nimpl_promoted!(i16, i32 => i32);\nimpl_promoted!(i16, i64 => i64);\nimpl_promoted!(i16, i128 => i128);\nimpl_promoted!(i32, i64 => i64);\nimpl_promoted!(i32, i128 => i128);\nimpl_promoted!(i64, i128 => i128);\n\n// Unsigned + Signed (need next-size signed to hold both ranges)\nimpl_promoted!(u8, i8 => i16);\nimpl_promoted!(u8, i16 => i16);\nimpl_promoted!(u8, i32 => i32);\nimpl_promoted!(u8, i64 => i64);\nimpl_promoted!(u8, i128 => i128);\nimpl_promoted!(u16, i8 => i32);\nimpl_promoted!(u16, i16 => i32);\nimpl_promoted!(u16, i32 => i32);\nimpl_promoted!(u16, i64 => i64);\nimpl_promoted!(u16, i128 => i128);\nimpl_promoted!(u32, i8 => i64);\nimpl_promoted!(u32, i16 => i64);\nimpl_promoted!(u32, i32 => i64);\nimpl_promoted!(u32, i64 => i64);\nimpl_promoted!(u32, i128 => i128);\nimpl_promoted!(u64, i8 => i128);\nimpl_promoted!(u64, i16 => i128);\nimpl_promoted!(u64, i32 => i128);\nimpl_promoted!(u64, i64 => i128);\nimpl_promoted!(u64, i128 => i128);\nimpl_promoted!(u128, i8 => f64);\nimpl_promoted!(u128, i16 => f64);\nimpl_promoted!(u128, i32 => f64);\nimpl_promoted!(u128, i64 => f64);\nimpl_promoted!(u128, i128 => f64);\n\n// Integer + Float (ensure enough precision)\n// Integers up to 16 bits fit in f32 (24-bit mantissa). Larger need f64.\nimpl_promoted!(u8, f32 => f32);\nimpl_promoted!(u8, f64 => f64);\nimpl_promoted!(u16, f32 => f32);\nimpl_promoted!(u16, f64 => f64);\nimpl_promoted!(u32, f32 => f64);\nimpl_promoted!(u32, f64 => f64);\nimpl_promoted!(u64, f32 => f64);\nimpl_promoted!(u64, f64 => f64);\nimpl_promoted!(u128, f32 => f64);\nimpl_promoted!(u128, f64 => f64);\nimpl_promoted!(i8, f32 => f32);\nimpl_promoted!(i8, f64 => f64);\nimpl_promoted!(i16, f32 => f32);\nimpl_promoted!(i16, f64 => f64);\nimpl_promoted!(i32, f32 => f64);\nimpl_promoted!(i32, f64 => f64);\nimpl_promoted!(i64, f32 => f64);\nimpl_promoted!(i64, f64 => f64);\nimpl_promoted!(i128, f32 => f64);\nimpl_promoted!(i128, f64 => f64);\n\n// Float + Float\nimpl_promoted!(f32, f64 => f64);\n\n// Real + Complex\nimpl_promoted!(u8, Complex<f32> => Complex<f32>);\nimpl_promoted!(u8, Complex<f64> => Complex<f64>);\nimpl_promoted!(u16, Complex<f32> => Complex<f32>);\nimpl_promoted!(u16, Complex<f64> => Complex<f64>);\nimpl_promoted!(u32, Complex<f32> => Complex<f64>);\nimpl_promoted!(u32, Complex<f64> => Complex<f64>);\nimpl_promoted!(u64, Complex<f32> => Complex<f64>);\nimpl_promoted!(u64, Complex<f64> => Complex<f64>);\nimpl_promoted!(u128, Complex<f32> => Complex<f64>);\nimpl_promoted!(u128, Complex<f64> => Complex<f64>);\nimpl_promoted!(i8, Complex<f32> => Complex<f32>);\nimpl_promoted!(i8, Complex<f64> => Complex<f64>);\nimpl_promoted!(i16, Complex<f32> => Complex<f32>);\nimpl_promoted!(i16, Complex<f64> => Complex<f64>);\nimpl_promoted!(i32, Complex<f32> => Complex<f64>);\nimpl_promoted!(i32, Complex<f64> => Complex<f64>);\nimpl_promoted!(i64, Complex<f32> => Complex<f64>);\nimpl_promoted!(i64, Complex<f64> => Complex<f64>);\nimpl_promoted!(i128, Complex<f32> => Complex<f64>);\nimpl_promoted!(i128, Complex<f64> => Complex<f64>);\nimpl_promoted!(f32, Complex<f32> => Complex<f32>);\nimpl_promoted!(f32, Complex<f64> => Complex<f64>);\nimpl_promoted!(f64, Complex<f32> => Complex<f64>);\nimpl_promoted!(f64, Complex<f64> => Complex<f64>);\n\n// Complex + Complex\nimpl_promoted!(Complex<f32>, Complex<f64> => Complex<f64>);\n\n// ---------------------------------------------------------------------------\n// result_type() — runtime type promotion\n// ---------------------------------------------------------------------------\n\n/// Determine the result type of a binary operation between two dtypes at runtime.\n///\n/// Follows NumPy's type promotion rules: returns the smallest type that can\n/// represent both inputs without precision loss.\n///\n/// # 128-bit promotion behaviour\n///\n/// ferray supports `u128`/`i128` as a NumPy-incompatible extension.\n/// No native Rust integer wider than 128 bits exists on stable, so\n/// ferray provides [`crate::dtype::I256`] — a 256-bit\n/// two's-complement type — and uses it as the promoted dtype for\n/// mixed `u128` + any signed int. This closes the former lossy\n/// `F64` fallback (issue #375, #562). The result of arithmetic on\n/// `(u128, i128)` arrays is therefore fully lossless.\n///\n/// `U128` or `I128` mixed with a float type still promotes to `F64`\n/// because the caller already asked for a float result — the\n/// precision-above-`2^53` loss is the standard IEEE-754 contract.\n///\n/// # Errors\n/// Returns `FerrayError::InvalidDtype` if promotion is not possible (should\n/// not happen for valid DType values).\npub fn result_type(a: DType, b: DType) -> FerrayResult<DType> {\n    if a == b {\n        return Ok(a);\n    }\n\n    // Use a static lookup table for the promotion result.\n    let result = promote_dtypes(a, b);\n    result.ok_or_else(|| FerrayError::invalid_dtype(format!(\"cannot promote {a} and {b}\")))\n}\n\n/// Internal promotion function returning Option.\nfn promote_dtypes(a: DType, b: DType) -> Option<DType> {\n    use DType::*;\n\n    if a == b {\n        return Some(a);\n    }\n\n    // Ensure canonical ordering: if b < a in our enum order, swap them\n    // so we only need to handle (smaller, larger) pairs.\n    let (lo, hi) = if (a as u32) <= (b as u32) {\n        (a, b)\n    } else {\n        (b, a)\n    };\n\n    // Bool promotes to anything\n    if lo == Bool {\n        return Some(hi);\n    }\n\n    let result = match (lo, hi) {\n        // Unsigned + Unsigned\n        (U8, U16) => U16,\n        (U8, U32) => U32,\n        (U8, U64) => U64,\n        (U8, U128) => U128,\n        (U16, U32) => U32,\n        (U16, U64) => U64,\n        (U16, U128) => U128,\n        (U32, U64) => U64,\n        (U32, U128) => U128,\n        (U64, U128) => U128,\n\n        // Signed + Signed\n        (I8, I16) => I16,\n        (I8, I32) => I32,\n        (I8, I64) => I64,\n        (I8, I128) => I128,\n        (I16, I32) => I32,\n        (I16, I64) => I64,\n        (I16, I128) => I128,\n        (I32, I64) => I64,\n        (I32, I128) => I128,\n        (I64, I128) => I128,\n\n        // Unsigned + Signed\n        (U8, I8) => I16,\n        (U8, I16) => I16,\n        (U8, I32) => I32,\n        (U8, I64) => I64,\n        (U8, I128) => I128,\n        (U16, I8) => I32,\n        (U16, I16) => I32,\n        (U16, I32) => I32,\n        (U16, I64) => I64,\n        (U16, I128) => I128,\n        (U32, I8) => I64,\n        (U32, I16) => I64,\n        (U32, I32) => I64,\n        (U32, I64) => I64,\n        (U32, I128) => I128,\n        (U64, I8) => I128,\n        (U64, I16) => I128,\n        (U64, I32) => I128,\n        (U64, I64) => I128,\n        (U64, I128) => I128,\n        // `U128 + <any signed int>` promotes to the custom\n        // `I256` type which can losslessly hold the full union\n        // (129 signed bits would suffice; we round up to 256 for\n        // alignment and arithmetic simplicity). This closes the\n        // former lossy-`F64` fallback (#375, #562).\n        (U128, I8) => I256,\n        (U128, I16) => I256,\n        (U128, I32) => I256,\n        (U128, I64) => I256,\n        (U128, I128) => I256,\n\n        // Integer + Float\n        (U8, F32) | (U16, F32) | (I8, F32) | (I16, F32) => F32,\n        (U8, F64) | (U16, F64) | (U32, F64) | (U64, F64) | (U128, F64) => F64,\n        (I8, F64) | (I16, F64) | (I32, F64) | (I64, F64) | (I128, F64) => F64,\n        (U32, F32) | (U64, F32) | (U128, F32) => F64,\n        (I32, F32) | (I64, F32) | (I128, F32) => F64,\n\n        // Float + Float\n        (F32, F64) => F64,\n\n        // Real + Complex\n        (U8, Complex32)\n        | (U16, Complex32)\n        | (I8, Complex32)\n        | (I16, Complex32)\n        | (F32, Complex32) => Complex32,\n        (U32, Complex32) | (U64, Complex32) | (U128, Complex32) => Complex64,\n        (I32, Complex32) | (I64, Complex32) | (I128, Complex32) => Complex64,\n        (F64, Complex32) => Complex64,\n\n        // Complex + Complex, and anything + Complex64\n        (Complex32, Complex64) => Complex64,\n        (_, Complex64) => Complex64,\n\n        _ => return None,\n    };\n\n    Some(result)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn result_type_same() {\n        assert_eq!(result_type(DType::F64, DType::F64).unwrap(), DType::F64);\n        assert_eq!(result_type(DType::I32, DType::I32).unwrap(), DType::I32);\n    }\n\n    #[test]\n    fn result_type_float_promotion() {\n        assert_eq!(result_type(DType::F32, DType::F64).unwrap(), DType::F64);\n    }\n\n    #[test]\n    fn result_type_int_float() {\n        assert_eq!(result_type(DType::I32, DType::F32).unwrap(), DType::F64);\n        assert_eq!(result_type(DType::I16, DType::F32).unwrap(), DType::F32);\n        assert_eq!(result_type(DType::U8, DType::F64).unwrap(), DType::F64);\n    }\n\n    #[test]\n    fn result_type_u128_signed_promotes_to_i256() {\n        // Issue #375 / #562: `u128 + any signed int` has no lossless\n        // native common type, so ferray promotes to its custom I256\n        // (256-bit two's-complement) which can hold the full union.\n        assert_eq!(result_type(DType::U128, DType::I8).unwrap(), DType::I256);\n        assert_eq!(result_type(DType::U128, DType::I16).unwrap(), DType::I256);\n        assert_eq!(result_type(DType::U128, DType::I32).unwrap(), DType::I256);\n        assert_eq!(result_type(DType::U128, DType::I64).unwrap(), DType::I256);\n        assert_eq!(result_type(DType::U128, DType::I128).unwrap(), DType::I256);\n        // Symmetric check.\n        assert_eq!(result_type(DType::I64, DType::U128).unwrap(), DType::I256);\n    }\n\n    #[test]\n    fn result_type_u128_float_still_f64() {\n        // `U128 + F*` still promotes to F64 because the caller asked\n        // for a float result — precision loss above 2^53 is the\n        // standard IEEE-754 contract.\n        assert_eq!(result_type(DType::U128, DType::F32).unwrap(), DType::F64);\n        assert_eq!(result_type(DType::U128, DType::F64).unwrap(), DType::F64);\n        assert_eq!(result_type(DType::I128, DType::F32).unwrap(), DType::F64);\n        assert_eq!(result_type(DType::I128, DType::F64).unwrap(), DType::F64);\n    }\n\n    #[test]\n    fn result_type_complex() {\n        assert_eq!(\n            result_type(DType::Complex32, DType::F64).unwrap(),\n            DType::Complex64\n        );\n        assert_eq!(\n            result_type(DType::F32, DType::Complex32).unwrap(),\n            DType::Complex32\n        );\n        assert_eq!(\n            result_type(DType::Complex32, DType::Complex64).unwrap(),\n            DType::Complex64\n        );\n    }\n\n    #[test]\n    fn result_type_unsigned_signed() {\n        assert_eq!(result_type(DType::U8, DType::I8).unwrap(), DType::I16);\n        assert_eq!(result_type(DType::U16, DType::I16).unwrap(), DType::I32);\n        assert_eq!(result_type(DType::U32, DType::I32).unwrap(), DType::I64);\n        assert_eq!(result_type(DType::U64, DType::I64).unwrap(), DType::I128);\n    }\n\n    #[test]\n    fn result_type_bool() {\n        assert_eq!(result_type(DType::Bool, DType::F64).unwrap(), DType::F64);\n        assert_eq!(result_type(DType::Bool, DType::I32).unwrap(), DType::I32);\n        assert_eq!(result_type(DType::Bool, DType::Bool).unwrap(), DType::Bool);\n    }\n\n    #[test]\n    fn promoted_trait_compile_time() {\n        // Verify the trait resolves correctly\n        fn check_promotion<A, B>() -> DType\n        where\n            A: Promoted<B>,\n            B: Element,\n            <A as Promoted<B>>::Output: Element,\n        {\n            <A as Promoted<B>>::Output::dtype()\n        }\n\n        assert_eq!(check_promotion::<f32, f64>(), DType::F64);\n        assert_eq!(check_promotion::<i32, f32>(), DType::F64);\n        assert_eq!(check_promotion::<Complex<f32>, f64>(), DType::Complex64);\n        assert_eq!(check_promotion::<u8, i8>(), DType::I16);\n    }\n\n    #[test]\n    fn promote_to_identity() {\n        let a: i32 = PromoteTo::<i32>::promote(42i32);\n        assert_eq!(a, 42i32);\n        let b: f64 = PromoteTo::<f64>::promote(2.5f64);\n        assert_eq!(b, 2.5f64);\n    }\n\n    #[test]\n    fn promote_to_widening() {\n        let x: f64 = PromoteTo::<f64>::promote(42i32);\n        assert_eq!(x, 42.0);\n\n        let y: i16 = PromoteTo::<i16>::promote(255u8);\n        assert_eq!(y, 255);\n\n        let z: Complex<f64> = PromoteTo::<Complex<f64>>::promote(2.5f32);\n        assert_eq!(z, Complex::new(2.5f32 as f64, 0.0));\n    }\n}\n"}}
{"timestamp":"1775712108","file_path":"/home/doll/ferray/ferray-core/src/dimension/mod.rs","entry":{"original_text":"// ferray-core: Dimension trait and concrete dimension types\n//\n// These types mirror ndarray's Ix1..Ix6 and IxDyn but live in ferray-core's\n// namespace so that ndarray never appears in the public API.\n\n#[cfg(feature = \"std\")]\npub mod broadcast;\n// static_shape depends on Array, Vec, format!, and the ndarray-gated methods\n// on Dimension, so it's only available when std is in scope.\n#[cfg(all(feature = \"const_shapes\", feature = \"std\"))]\npub mod static_shape;\n\nuse core::fmt;\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n#[cfg(feature = \"no_std\")]\nuse alloc::vec::Vec;\n\n// We need ndarray's Dimension trait in scope for `as_array_view()` etc.\n#[cfg(feature = \"std\")]\nuse ndarray::Dimension as NdDimension;\n\n/// Trait for types that describe the dimensionality of an array.\n///\n/// Each dimension type knows its number of axes at the type level\n/// (except [`IxDyn`] which carries it at runtime).\npub trait Dimension: Clone + PartialEq + Eq + fmt::Debug + Send + Sync + 'static {\n    /// The number of axes, or `None` for dynamic-rank arrays.\n    const NDIM: Option<usize>;\n\n    /// The corresponding `ndarray` dimension type (private, not exposed in public API).\n    #[doc(hidden)]\n    #[cfg(feature = \"std\")]\n    type NdarrayDim: ndarray::Dimension;\n\n    /// Return the shape as a slice.\n    fn as_slice(&self) -> &[usize];\n\n    /// Return the shape as a mutable slice.\n    fn as_slice_mut(&mut self) -> &mut [usize];\n\n    /// Number of dimensions.\n    fn ndim(&self) -> usize {\n        self.as_slice().len()\n    }\n\n    /// Total number of elements (product of all dimension sizes).\n    fn size(&self) -> usize {\n        self.as_slice().iter().product()\n    }\n\n    /// Convert to the internal ndarray dimension type.\n    #[doc(hidden)]\n    #[cfg(feature = \"std\")]\n    fn to_ndarray_dim(&self) -> Self::NdarrayDim;\n\n    /// Create from the internal ndarray dimension type.\n    #[doc(hidden)]\n    #[cfg(feature = \"std\")]\n    fn from_ndarray_dim(dim: &Self::NdarrayDim) -> Self;\n\n    /// Construct a dimension from a slice of axis lengths.\n    ///\n    /// Returns `None` if the slice length does not match `Self::NDIM`\n    /// for fixed-rank dimensions. Always succeeds for [`IxDyn`].\n    ///\n    /// This is the inverse of [`Dimension::as_slice`].\n    fn from_dim_slice(shape: &[usize]) -> Option<Self>;\n}\n\n// ---------------------------------------------------------------------------\n// Fixed-rank dimension types\n// ---------------------------------------------------------------------------\n\nmacro_rules! impl_fixed_dimension {\n    ($name:ident, $n:expr, $ndarray_ty:ty) => {\n        /// A fixed-rank dimension with\n        #[doc = concat!(stringify!($n), \" axes.\")]\n        #[derive(Clone, PartialEq, Eq, Hash)]\n        pub struct $name {\n            shape: [usize; $n],\n        }\n\n        impl $name {\n            /// Create a new dimension from a fixed-size array.\n            #[inline]\n            pub fn new(shape: [usize; $n]) -> Self {\n                Self { shape }\n            }\n        }\n\n        impl fmt::Debug for $name {\n            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n                write!(f, \"{:?}\", &self.shape[..])\n            }\n        }\n\n        impl From<[usize; $n]> for $name {\n            #[inline]\n            fn from(shape: [usize; $n]) -> Self {\n                Self::new(shape)\n            }\n        }\n\n        impl Dimension for $name {\n            const NDIM: Option<usize> = Some($n);\n\n            #[cfg(feature = \"std\")]\n            type NdarrayDim = $ndarray_ty;\n\n            #[inline]\n            fn as_slice(&self) -> &[usize] {\n                &self.shape\n            }\n\n            #[inline]\n            fn as_slice_mut(&mut self) -> &mut [usize] {\n                &mut self.shape\n            }\n\n            #[cfg(feature = \"std\")]\n            fn to_ndarray_dim(&self) -> Self::NdarrayDim {\n                // ndarray::Dim implements From<[usize; N]> for N=1..6\n                ndarray::Dim(self.shape)\n            }\n\n            #[cfg(feature = \"std\")]\n            fn from_ndarray_dim(dim: &Self::NdarrayDim) -> Self {\n                let view = dim.as_array_view();\n                let s = view.as_slice().expect(\"ndarray dim should be contiguous\");\n                let mut shape = [0usize; $n];\n                shape.copy_from_slice(s);\n                Self { shape }\n            }\n\n            fn from_dim_slice(shape: &[usize]) -> Option<Self> {\n                if shape.len() != $n {\n                    return None;\n                }\n                let mut arr = [0usize; $n];\n                arr.copy_from_slice(shape);\n                Some(Self { shape: arr })\n            }\n        }\n    };\n}\n\nimpl_fixed_dimension!(Ix1, 1, ndarray::Ix1);\nimpl_fixed_dimension!(Ix2, 2, ndarray::Ix2);\nimpl_fixed_dimension!(Ix3, 3, ndarray::Ix3);\nimpl_fixed_dimension!(Ix4, 4, ndarray::Ix4);\nimpl_fixed_dimension!(Ix5, 5, ndarray::Ix5);\nimpl_fixed_dimension!(Ix6, 6, ndarray::Ix6);\n\n// ---------------------------------------------------------------------------\n// Ix0: scalar (0-dimensional)\n// ---------------------------------------------------------------------------\n\n/// A zero-dimensional (scalar) dimension.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct Ix0;\n\nimpl fmt::Debug for Ix0 {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"[]\")\n    }\n}\n\nimpl Dimension for Ix0 {\n    const NDIM: Option<usize> = Some(0);\n\n    #[cfg(feature = \"std\")]\n    type NdarrayDim = ndarray::Ix0;\n\n    #[inline]\n    fn as_slice(&self) -> &[usize] {\n        &[]\n    }\n\n    #[inline]\n    fn as_slice_mut(&mut self) -> &mut [usize] {\n        &mut []\n    }\n\n    #[cfg(feature = \"std\")]\n    fn to_ndarray_dim(&self) -> Self::NdarrayDim {\n        ndarray::Dim(())\n    }\n\n    #[cfg(feature = \"std\")]\n    fn from_ndarray_dim(_dim: &Self::NdarrayDim) -> Self {\n        Ix0\n    }\n\n    fn from_dim_slice(shape: &[usize]) -> Option<Self> {\n        if shape.is_empty() { Some(Ix0) } else { None }\n    }\n}\n\n// ---------------------------------------------------------------------------\n// IxDyn: dynamic-rank dimension\n// ---------------------------------------------------------------------------\n\n/// A dynamic-rank dimension whose number of axes is determined at runtime.\n#[derive(Clone, PartialEq, Eq, Hash)]\npub struct IxDyn {\n    shape: Vec<usize>,\n}\n\nimpl IxDyn {\n    /// Create a new dynamic dimension from a slice.\n    pub fn new(shape: &[usize]) -> Self {\n        Self {\n            shape: shape.to_vec(),\n        }\n    }\n}\n\nimpl fmt::Debug for IxDyn {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{:?}\", &self.shape[..])\n    }\n}\n\nimpl From<Vec<usize>> for IxDyn {\n    fn from(shape: Vec<usize>) -> Self {\n        Self { shape }\n    }\n}\n\nimpl From<&[usize]> for IxDyn {\n    fn from(shape: &[usize]) -> Self {\n        Self::new(shape)\n    }\n}\n\nimpl Dimension for IxDyn {\n    const NDIM: Option<usize> = None;\n\n    #[cfg(feature = \"std\")]\n    type NdarrayDim = ndarray::IxDyn;\n\n    #[inline]\n    fn as_slice(&self) -> &[usize] {\n        &self.shape\n    }\n\n    #[inline]\n    fn as_slice_mut(&mut self) -> &mut [usize] {\n        &mut self.shape\n    }\n\n    #[cfg(feature = \"std\")]\n    fn to_ndarray_dim(&self) -> Self::NdarrayDim {\n        ndarray::IxDyn(&self.shape)\n    }\n\n    #[cfg(feature = \"std\")]\n    fn from_ndarray_dim(dim: &Self::NdarrayDim) -> Self {\n        let view = dim.as_array_view();\n        let s = view.as_slice().expect(\"ndarray IxDyn should be contiguous\");\n        Self { shape: s.to_vec() }\n    }\n\n    fn from_dim_slice(shape: &[usize]) -> Option<Self> {\n        Some(Self { shape: shape.to_vec() })\n    }\n}\n\n/// Newtype for axis indices used throughout ferray.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub struct Axis(pub usize);\n\nimpl Axis {\n    /// Return the axis index.\n    #[inline]\n    pub fn index(self) -> usize {\n        self.0\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn ix1_basics() {\n        let d = Ix1::new([5]);\n        assert_eq!(d.ndim(), 1);\n        assert_eq!(d.size(), 5);\n        assert_eq!(d.as_slice(), &[5]);\n    }\n\n    #[test]\n    fn ix2_basics() {\n        let d = Ix2::new([3, 4]);\n        assert_eq!(d.ndim(), 2);\n        assert_eq!(d.size(), 12);\n    }\n\n    #[test]\n    fn ix0_basics() {\n        let d = Ix0;\n        assert_eq!(d.ndim(), 0);\n        assert_eq!(d.size(), 1);\n    }\n\n    #[test]\n    fn ixdyn_basics() {\n        let d = IxDyn::new(&[2, 3, 4]);\n        assert_eq!(d.ndim(), 3);\n        assert_eq!(d.size(), 24);\n    }\n\n    #[test]\n    fn roundtrip_ix2_ndarray() {\n        let d = Ix2::new([3, 7]);\n        let nd = d.to_ndarray_dim();\n        let d2 = Ix2::from_ndarray_dim(&nd);\n        assert_eq!(d, d2);\n    }\n\n    #[test]\n    fn roundtrip_ixdyn_ndarray() {\n        let d = IxDyn::new(&[2, 5, 3]);\n        let nd = d.to_ndarray_dim();\n        let d2 = IxDyn::from_ndarray_dim(&nd);\n        assert_eq!(d, d2);\n    }\n\n    #[test]\n    fn axis_index() {\n        let a = Axis(2);\n        assert_eq!(a.index(), 2);\n    }\n}\n"}}
{"timestamp":"1775712108","file_path":"/home/doll/ferray/ferray-core/src/lib.rs","entry":{"original_text":"// ferray-core: N-dimensional array type and foundational primitives\n//\n// This is the root crate for the ferray workspace. It provides NdArray<T, D>,\n// the full ownership model (Array, ArrayView, ArrayViewMut, ArcArray, CowArray),\n// the Element trait, DType system, FerrayError, and array introspection/iteration.\n//\n// ndarray is used internally but is NOT part of the public API.\n//\n// When the `no_std` feature is enabled, only the subset of functionality that\n// does not depend on `std` or `ndarray` is compiled: DType, Element, FerrayError\n// (simplified), dimension types (without ndarray conversions), constants, and\n// layout enums. The full Array type and related features require `std`.\n\n#![cfg_attr(feature = \"no_std\", no_std)]\n\n#[cfg(feature = \"no_std\")]\nextern crate alloc;\n\n// Modules that work in both std and no_std modes\npub mod constants;\npub mod dimension;\npub mod dtype;\npub mod error;\npub mod layout;\npub mod record;\n\n// Modules that require std (depend on ndarray or std-only features)\n#[cfg(feature = \"std\")]\npub mod array;\n#[cfg(feature = \"std\")]\npub mod buffer;\n#[cfg(feature = \"std\")]\npub mod creation;\n#[cfg(feature = \"std\")]\npub mod dynarray;\n#[cfg(feature = \"std\")]\npub mod indexing;\n#[cfg(feature = \"std\")]\npub mod manipulation;\n#[cfg(feature = \"std\")]\npub mod nditer;\n#[cfg(feature = \"std\")]\npub mod ops;\n#[cfg(feature = \"std\")]\npub mod prelude;\n\n// Re-export key types at crate root for ergonomics (std only)\n#[cfg(feature = \"std\")]\npub use array::ArrayFlags;\n#[cfg(feature = \"std\")]\npub use array::aliases;\n#[cfg(feature = \"std\")]\npub use array::arc::ArcArray;\n#[cfg(feature = \"std\")]\npub use array::cow::CowArray;\n#[cfg(feature = \"std\")]\npub use array::display::{get_print_options, set_print_options};\n#[cfg(feature = \"std\")]\npub use array::owned::Array;\n#[cfg(feature = \"std\")]\npub use array::view::ArrayView;\n#[cfg(feature = \"std\")]\npub use array::view_mut::ArrayViewMut;\n\npub use dimension::{Axis, Dimension, Ix0, Ix1, Ix2, Ix3, Ix4, Ix5, Ix6, IxDyn};\n\n#[cfg(all(feature = \"const_shapes\", feature = \"std\"))]\npub use dimension::static_shape::{\n    Assert, DefaultNdarrayDim, IsTrue, Shape1, Shape2, Shape3, Shape4, Shape5, Shape6,\n    StaticBroadcast, StaticMatMul, StaticSize, static_reshape_array,\n};\n\npub use dtype::{DType, Element, SliceInfoElem};\n\npub use error::{FerrayError, FerrayResult};\n\npub use layout::MemoryLayout;\n\n#[cfg(feature = \"std\")]\npub use buffer::AsRawBuffer;\n\n#[cfg(feature = \"std\")]\npub use dynarray::DynArray;\n\n#[cfg(feature = \"std\")]\npub use nditer::NdIter;\n\npub use record::FieldDescriptor;\n\n// Re-export proc macros from ferray-core-macros.\n// The derive macro FerrayRecord shares its name with the trait in record::FerrayRecord.\n// Both are re-exported: the derive macro lives in macro namespace, the trait in type namespace.\npub use ferray_core_macros::{FerrayRecord, promoted_type, s};\npub use record::FerrayRecord;\n\n// Kani formal verification harnesses (only compiled during `cargo kani`)\n#[cfg(kani)]\nmod verification_kani;\n"}}