ndarray
N-dimensional numeric arrays over contiguous typed storage (1.19.0): elementwise arithmetic with broadcasting, zero-copy views, reductions, linear algebra, and seeded random generation. The compute substrate for numeric work in the NumPy mould.
import ndarray as nd;
let a = nd.array([[1.0, 2.0], [3.0, 4.0]]);
io.println(a.shape()); # [2, 2]
io.println(a.sum()); # 10
io.println(a.matmul(nd.eye(2)).toList());
Arrays come in two dtypes: float64 and int64. nd.array infers the
dtype from its input - any float element gives float64, an all-int
input gives int64; astype converts explicitly. Mixed-dtype binary
operations promote int64 to float64, and div and pow always
produce float64.
Constructors
| Function | Result |
|---|---|
array(values) |
Array from a (possibly nested) numeric list |
zeros(shape) / ones(shape) |
float64 array of zeros / ones |
full(shape, value) |
Filled array; dtype follows the fill value |
eye(n) |
n x n float64 identity |
arange(start, stop, step = 1) |
int64 range [start, stop) |
linspace(start, stop, count) |
count evenly spaced float64 values, inclusive |
random(shape, opts = {}) |
Uniform [0, 1) float64 samples |
randn(shape, opts = {}) |
Standard normal float64 samples |
random and randn accept {"seed": n} for reproducible sequences
(the same generator family as the random module; not cryptographic - use secrets for keys and tokens).
Shape, access, and views
shape(), dtype(), size(), get(index), set(index, value)
(index is one position per dimension: a.get([1, 0])).
slice([[start, stop], ...]), t() (transpose), and reshape(shape)
on a contiguous array are zero-copy views: writing through a view
mutates the underlying array. copy() materialises an independent
array; toList() converts to nested lists.
let row = a.slice([[1, 2]]); # second row, as a view
row.set([0, 0], 99.0); # a.get([1, 0]) is now 99.0
Elementwise operations and broadcasting
add, sub, mul, div, pow accept an array or a scalar and
broadcast trailing dimensions NumPy-style (a [2, 1] column and a
[1, 3] row combine to [2, 3]). addScalar / subScalar /
mulScalar / divScalar are scalar conveniences. Unary: neg, abs,
sqrt, exp, log, clip(lo, hi).
Comparisons gt, lt, gte, lte, eq, ne produce an int64 0/1
mask; where(mask) selects the masked elements as a 1-D array:
let big = a.where(a.gt(2.0));
Operators
The arithmetic operators map to the elementwise methods, with scalars on either side, and unary minus negates:
let c = a + b; # a.add(b), broadcasting included
let d = 2 * a - 1; # scalars mix on either side
let e = a ** 2; # a.pow(2)
let f = -a; # a.neg()
The ordering operators return masks, so a NumPy-style selection reads naturally:
let big = a.where(a > 2.0);
== and != keep their language-wide meaning; use eq() / ne() for
elementwise equality masks.
Reductions
sum, mean, min, max reduce the whole array, or along one axis
with {"axis": n} (a.sum({"axis": 0}) is column sums). std and
variance are sample statistics over the whole array. argmin /
argmax return the flat index of the extreme; cumsum returns the
running sum flattened to 1-D. Integer arrays keep int64 for sum /
min / max; mean, std, and axis reductions produce float64.
Linear algebra
| Call | Result |
|---|---|
a.matmul(b) |
Matrix product (blocked float64 kernel) |
a.dot(v) |
Vector dot product |
solve(a, b) |
Solves a x = b (Gaussian elimination, partial pivoting) |
inv(a) |
Matrix inverse |
det(a) |
Determinant (0.0 for a singular matrix) |
solve and inv throw a catchable RuntimeError on a singular
matrix. The kernels are pure Go; for BLAS-class performance on large
matrices, measure first - a 1000 x 1000 multiply is in the hundreds of
milliseconds.