module Narray

Overview

Operations for NArray

This module provides various array manipulation operations for NArray, including:

Defined in:

narray.cr
narray/broadcast.cr
narray/linalg.cr
narray/math.cr
narray/math/exponential.cr
narray/math/hyperbolic.cr
narray/math/trig.cr
narray/operations.cr

Constant Summary

VERSION = "0.1.0"

Class Method Summary

Class Method Detail

def self.arange(start : Number, stop : Number, step : Number, type : T.class) forall T #

Creates a new array with evenly spaced values within a given interval with the specified type.

arr = Narray.arange(0, 5, 1_f64, Float64)
arr.data          # => [0.0, 1.0, 2.0, 3.0, 4.0]
arr.data[0].class # => Float64

See also: Narray.linspace.


[View source]
def self.arange(start : Number, stop : Number, step = 1) #

Creates a new array with evenly spaced values within a given interval.

arr = Narray.arange(0, 10, 2)
arr.shape # => [5]
arr.ndim  # => 1
arr.size  # => 5
arr.data  # => [0, 2, 4, 6, 8]

See also: Narray.linspace.


[View source]
def self.array(shape : ::Array(Int32), data : ::Array(T)) forall T #

Creates a new array with the given shape and data.

arr = Narray.array([2, 2], [1, 2, 3, 4])
arr.shape # => [2, 2]
arr.ndim  # => 2
arr.size  # => 4
arr.data  # => [1, 2, 3, 4]

Raises ArgumentError if the data size does not match the shape.

See also: Narray.zeros, Narray.ones.


[View source]
def self.broadcast(array : Array(T), new_shape : ::Array(Int32)) : Array(T) forall T #

Broadcasts an array to a new shape.

The new shape must be broadcast compatible with the original shape. If the shapes are already the same, returns the original array.

# Original array with shape [3]
arr = Narray.array([3], [1, 2, 3])

# Broadcast to shape [2, 3]
result = Narray.broadcast(arr, [2, 3])
result.shape # => [2, 3]
result.data  # => [1, 2, 3, 1, 2, 3]

# Broadcasting with dimension size 1
arr2 = Narray.array([2, 1], [1, 2])
result2 = Narray.broadcast(arr2, [2, 3])
result2.shape # => [2, 3]
result2.data  # => [1, 1, 1, 2, 2, 2]

Raises ArgumentError if the shapes are incompatible for broadcasting.

See also: Narray.broadcast_shapes, Array#broadcast_to.


[View source]
def self.broadcast_shapes(shape1 : ::Array(Int32), shape2 : ::Array(Int32)) : ::Array(Int32) | Nil #

Checks if two shapes are broadcast compatible and returns the resulting shape.

Broadcasting rules:

  • Arrays with fewer dimensions are padded with ones on the left.
  • Arrays with dimension size 1 are stretched to match the other array's size.
  • If dimensions are incompatible (neither equal nor one is 1), returns nil.
# Same shapes
Narray.broadcast_shapes([2, 3], [2, 3]) # => [2, 3]

# Broadcasting scalar to array
Narray.broadcast_shapes([2, 3], [] of Int32) # => [2, 3]

# Broadcasting 1D array to 2D array
Narray.broadcast_shapes([2, 3], [3]) # => [2, 3]

# Broadcasting when one dimension is 1
Narray.broadcast_shapes([2, 1], [1, 3]) # => [2, 3]

# Incompatible shapes
Narray.broadcast_shapes([2, 3], [4, 5]) # => nil

See also: Narray.broadcast, Narray.can_broadcast?.


[View source]
def self.can_broadcast?(from_shape : ::Array(Int32), to_shape : ::Array(Int32)) : Bool #

Checks if a shape can be broadcast to another shape.

Returns true if the shapes are compatible for broadcasting and the result of broadcasting would match the target shape.

Narray.can_broadcast?([2, 1], [2, 3]) # => true
Narray.can_broadcast?([3], [2, 3])    # => true
Narray.can_broadcast?([2, 3], [4, 5]) # => false

See also: Narray.broadcast_shapes, Narray.broadcast.


[View source]
def self.concatenate(arrays : ::Array(Array(T)), axis = 0) : Array(T) forall T #

Concatenates arrays along the specified axis.

The arrays must have the same shape except for the dimension corresponding to axis.

# 1D arrays
a = Narray.array([3], [1, 2, 3])
b = Narray.array([3], [4, 5, 6])
c = Narray.concatenate([a, b])
c.shape # => [6]
c.data  # => [1, 2, 3, 4, 5, 6]

# 2D arrays along axis 0 (rows)
a = Narray.array([2, 3], [1, 2, 3, 4, 5, 6])
b = Narray.array([1, 3], [7, 8, 9])
c = Narray.concatenate([a, b])
c.shape # => [3, 3]
c.data  # => [1, 2, 3, 4, 5, 6, 7, 8, 9]

# 2D arrays along axis 1 (columns)
a = Narray.array([2, 2], [1, 2, 3, 4])
b = Narray.array([2, 3], [5, 6, 7, 8, 9, 10])
c = Narray.concatenate([a, b], 1)
c.shape # => [2, 5]
c.data  # => [1, 2, 5, 6, 7, 3, 4, 8, 9, 10]

Raises ArgumentError if:

  • The array of arrays is empty
  • The axis is out of bounds
  • The arrays have different numbers of dimensions
  • The arrays have different shapes except for the concatenation axis

See also: Narray.vstack, Narray.hstack.


[View source]
def self.det(a : Array(T)) : Float64 forall T #

Computes the determinant of a square matrix.

The determinant is a scalar value that can be computed from the elements of a square matrix and encodes certain properties of the linear transformation described by the matrix.

For small matrices, direct formulas are used:

  • 1x1 matrix: the single element
  • 2x2 matrix: ad - bc for matrix [[a, b], [c, d]]
  • 3x3 matrix: uses the cofactor expansion formula

For larger matrices, Gaussian elimination with partial pivoting is used.

# 1x1 matrix
a = Narray.array([1, 1], [5])
Narray.det(a) # => 5.0

# 2x2 matrix
a = Narray.array([2, 2], [1, 2, 3, 4])
Narray.det(a) # => -2.0  # 1*4 - 2*3 = -2

# 3x3 matrix
a = Narray.array([3, 3], [1, 2, 3, 4, 5, 6, 7, 8, 9])
Narray.det(a) # => 0.0  # Singular matrix

# 4x4 diagonal matrix
a = Narray.array([4, 4], [
  1, 0, 0, 0,
  0, 2, 0, 0,
  0, 0, 3, 0,
  0, 0, 0, 4,
])
Narray.det(a) # => 24.0  # Product of diagonal elements

Raises ArgumentError if the matrix is not 2-dimensional or not square.

See also: Narray.determinant, Narray.inv.


[View source]
def self.determinant(a : Array(T)) : Float64 forall T #

Alias for determinant.

a = Narray.array([2, 2], [1, 2, 3, 4])
Narray.determinant(a) # => -2.0

See also: Narray.det.


[View source]
def self.dot(a : Array(T), b : Array(U)) : Array(Float64) forall T, U #

Computes the matrix multiplication (dot product) of two matrices.

For 2D arrays, this is the standard matrix multiplication. The inner dimensions must match: if a has shape [m, n] and b has shape [n, p], the result will have shape [m, p].

a = Narray.array([2, 3], [1, 2, 3, 4, 5, 6])
b = Narray.array([3, 2], [7, 8, 9, 10, 11, 12])
c = Narray.dot(a, b)
c.shape # => [2, 2]
c.data  # => [58, 64, 139, 154]

Raises ArgumentError if the arrays are not 2-dimensional or if the inner dimensions don't match.

See also: Narray.matmul.


[View source]
def self.dot(a : Array(T), b : Array(T)) : Array(T) forall T #

Original dot product for same type (for backward compatibility)


[View source]
def self.eig(a : Array(T)) : Tuple(Array(Float64), Array(Float64)) forall T #

Computes the eigenvalues and eigenvectors of a square matrix.

The eigenvalues and eigenvectors of a matrix A satisfy the equation A * v = λ * v, where v is an eigenvector and λ is the corresponding eigenvalue.

This method returns a tuple containing:

  1. An array of eigenvalues
  2. An array of eigenvectors (as columns of a matrix)

For small matrices, direct formulas are used:

  • 1x1 matrix: the eigenvalue is the single element, and the eigenvector is [1]
  • 2x2 matrix: uses the quadratic formula to find eigenvalues

For 3x3 symmetric matrices, a specialized approach is used to match NumPy results. For larger symmetric matrices, the QR algorithm is used.

# 1x1 matrix
a = Narray.array([1, 1], [5])
eigenvalues, eigenvectors = Narray.eig(a)
eigenvalues[[0]]     # => 5.0
eigenvectors[[0, 0]] # => 1.0

# 2x2 symmetric matrix
a = Narray.array([2, 2], [2, 1, 1, 2])
eigenvalues, eigenvectors = Narray.eig(a)
# Eigenvalues should be 1.0 and 3.0
# Eigenvectors are orthogonal and normalized

Raises ArgumentError if the matrix is not 2-dimensional, not square, or not symmetric. Raises ArgumentError if the matrix has complex eigenvalues (not supported).

See also: Narray.eigen.


[View source]
def self.eigen(a : Array(T)) : Tuple(Array(Float64), Array(Float64)) forall T #

Alias for eigenvalues and eigenvectors.

a = Narray.array([2, 2], [2, 1, 1, 2])
eigenvalues, eigenvectors = Narray.eigen(a)
# Same as Narray.eig(a)

See also: Narray.eig.


[View source]
def self.hstack(arrays : ::Array(Array(T))) : Array(T) forall T #

Stacks arrays horizontally (along the second axis).

For 1D arrays, this concatenates them along the first axis. For 2D arrays, this concatenates them along the second axis (columns). For higher dimensions, this is equivalent to .concatenate(arrays, 1).

# 1D arrays
a = Narray.array([3], [1, 2, 3])
b = Narray.array([3], [4, 5, 6])
c = Narray.hstack([a, b])
c.shape # => [6]
c.data  # => [1, 2, 3, 4, 5, 6]

# 2D arrays
a = Narray.array([2, 2], [1, 2, 3, 4])
b = Narray.array([2, 3], [5, 6, 7, 8, 9, 10])
c = Narray.hstack([a, b])
c.shape # => [2, 5]
c.data  # => [1, 2, 5, 6, 7, 3, 4, 8, 9, 10]

See also: Narray.vstack, Narray.concatenate.


[View source]
def self.inv(a : Array(T)) : Array(Float64) forall T #

Computes the inverse of a square matrix.

The inverse of a matrix A is a matrix A^(-1) such that A * A^(-1) = I, where I is the identity matrix.

For small matrices, direct formulas are used:

  • 1x1 matrix: 1/a for matrix [a]
  • 2x2 matrix: [[d, -b], [-c, a]]/(ad-bc) for matrix [[a, b], [c, d]]

For larger matrices, Gaussian elimination with an augmented matrix [A|I] is used.

# 1x1 matrix
a = Narray.array([1, 1], [2])
inv_a = Narray.inv(a)
inv_a[[0, 0]] # => 0.5

# 2x2 matrix
a = Narray.array([2, 2], [4, 7, 2, 6])
inv_a = Narray.inv(a)
# Expected inverse of [[4, 7], [2, 6]] is [[0.6, -0.7], [-0.2, 0.4]]

Raises ArgumentError if the matrix is not 2-dimensional, not square, or singular.

See also: Narray.inverse, Narray.det.


[View source]
def self.inverse(a : Array(T)) : Array(Float64) forall T #

Alias for inverse.

a = Narray.array([2, 2], [1, 2, 3, 4])
Narray.inverse(a) # Same as Narray.inv(a)

See also: Narray.inv.


[View source]
def self.linspace(start : Number, stop : Number, num : Int32, type : T.class) forall T #

Creates a new array with evenly spaced values over a specified interval with the specified type.

arr = Narray.linspace(0, 1, 3, Float32)
arr.data[0].class # => Float32

See also: Narray.arange.


[View source]
def self.linspace(start : Number, stop : Number, num = 50) #

Creates a new array with evenly spaced values over a specified interval.

arr = Narray.linspace(0, 1, 5)
arr.shape # => [5]
arr.ndim  # => 1
arr.size  # => 5
arr.data  # => [0.0, 0.25, 0.5, 0.75, 1.0]

See also: Narray.arange.


[View source]
def self.matmul(a : Array(T), b : Array(U)) : Array(Float64) forall T, U #

Matrix multiplication (matmul)


[View source]
def self.matmul(a : Array(T), b : Array(T)) : Array(T) forall T #

Original matmul for same type (for backward compatibility)


[View source]
def self.ones(shape : ::Array(Int32), type : T.class) forall T #

Creates a new array filled with ones with the specified type.

arr = Narray.ones([2, 2], Int32)
arr.data          # => [1, 1, 1, 1]
arr.data[0].class # => Int32

See also: Narray.zeros, Narray.array.


[View source]
def self.ones(shape : ::Array(Int32)) #

Creates a new array filled with ones.

arr = Narray.ones([2, 3])
arr.shape # => [2, 3]
arr.ndim  # => 2
arr.size  # => 6
arr.data  # => [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

See also: Narray.zeros, Narray.array.


[View source]
def self.svd(a : Array(T)) : Tuple(Array(Float64), Array(Float64), Array(Float64)) forall T #

Computes the singular value decomposition (SVD) of a matrix.

The SVD decomposes a matrix A into three matrices U, S, and V^T such that: A = U * S * V^T

Where:

  • U is an orthogonal matrix containing the left singular vectors
  • S is a diagonal matrix containing the singular values
  • V^T is the transpose of an orthogonal matrix containing the right singular vectors

This method returns a tuple containing (U, S, V^T).

For a matrix of shape (m, n):

  • U has shape (m, min(m, n))
  • S has shape (min(m, n))
  • V^T has shape (min(m, n), n)
# 2x2 matrix
a = Narray.array([2, 2], [1, 2, 3, 4])
u, s, vt = Narray.svd(a)

# Verify that U and V^T are orthogonal matrices
# (U * U^T and V^T * V should be close to identity matrices)

# Verify that A ≈ U * S * V^T
# Create diagonal matrix from singular values
s_diag = Narray.zeros([2, 2])
2.times do |i|
  s_diag[[i, i]] = s[[i]]
end
reconstructed = Narray.dot(Narray.dot(u, s_diag), vt)
# reconstructed should be close to the original matrix a

Raises ArgumentError if the matrix is not 2-dimensional.


[View source]
def self.vstack(arrays : ::Array(Array(T))) : Array(T) forall T #

Stacks arrays vertically (along the first axis).

For 1D arrays, this converts them to 2D arrays with one row each. For higher dimensions, this is equivalent to .concatenate(arrays, 0).

# 1D arrays
a = Narray.array([3], [1, 2, 3])
b = Narray.array([3], [4, 5, 6])
c = Narray.vstack([a, b])
c.shape # => [2, 3]
c.data  # => [1, 2, 3, 4, 5, 6]

# 2D arrays
a = Narray.array([2, 3], [1, 2, 3, 4, 5, 6])
b = Narray.array([1, 3], [7, 8, 9])
c = Narray.vstack([a, b])
c.shape # => [3, 3]
c.data  # => [1, 2, 3, 4, 5, 6, 7, 8, 9]

See also: Narray.hstack, Narray.concatenate.


[View source]
def self.zeros(shape : ::Array(Int32), type : T.class) forall T #

Creates a new array filled with zeros with the specified type.

arr = Narray.zeros([2, 2], Int32)
arr.data          # => [0, 0, 0, 0]
arr.data[0].class # => Int32

See also: Narray.ones, Narray.array.


[View source]
def self.zeros(shape : ::Array(Int32)) #

Creates a new array filled with zeros.

arr = Narray.zeros([2, 3])
arr.shape # => [2, 3]
arr.ndim  # => 2
arr.size  # => 6
arr.data  # => [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

See also: Narray.ones, Narray.array.


[View source]