Source
tests/purrc/ndarray_sys_test.cpp
1
// Copyright (c) 2026 BigBrain LLC. MIT-licensed (see LICENSE).2
// Original work; see ACKNOWLEDGMENTS.md for the open-source ideas we build upon.3
// System-level (whole-program) test for the `ndarray` stdlib module. Unlike the4
// per-function compile-run tests (tests/purrc/ndarray_cr_test.cpp), this drives a5
// single cohesive numeric program through EVERY purr-callable ndarray function and6
// asserts its exact stdout, so the functions are exercised together (factories feed7
// reshape, reshape feeds elementwise ops, those feed reductions / indexing).8
//9
// Coverage — every purr-callable function in stdlib/ndarray/ndarray.hpp:10
// array, scalar, zeros, ones, full, arange, reshape, add, sub, mul, divide,11
// sum, mean, get, shape_of, size_of, to_string.12
//13
// Skipped (not callable from .purr, same as the cr test):14
// - broadcast_to / broadcast_shapes: take std::vector<std::size_t>, but cheatah15
// `list<int>` lowers to std::vector<long long>, which doesn't convert.16
// - the NDArray class methods (shape/strides/ndim/size/at/buffer/offset/ctors)17
// are C++-side internals reached only through the free functions above.18
#include "e2e_harness.hpp"20
TEST(StdlibE2E, Ndarray) {21
e2e::expect_e2e("ndarray_sys", R"PURR(import io22
import ndarray24
# --- factories ---25
let a = ndarray.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])26
let s = ndarray.scalar(2.0)27
let z = ndarray.zeros([2, 3])28
let o = ndarray.ones([2, 3])29
let f = ndarray.full([2, 3], 4.0)30
let r = ndarray.arange(0.0, 6.0, 1.0)32
# --- reshape into a 2x3 matrix ---33
let m = ndarray.reshape(a, [2, 3])34
io.print(ndarray.to_string(m))36
# --- elementwise ops (broadcasting against scalar / same shape) ---37
let summ = ndarray.add(m, o)38
let diff = ndarray.sub(m, s)39
let prod = ndarray.mul(m, s)40
let quot = ndarray.divide(m, s)41
io.print(ndarray.to_string(summ))42
io.print(ndarray.to_string(diff))43
io.print(ndarray.to_string(prod))44
io.print(ndarray.to_string(quot))46
# combine zeros / full / arange47
let combo = ndarray.add(ndarray.add(z, f), ndarray.reshape(r, [2, 3]))48
io.print(ndarray.to_string(combo))50
# --- reductions ---51
io.print(ndarray.sum(m))52
io.print(ndarray.mean(m))54
# --- access / shape introspection ---55
io.print(ndarray.get(m, [1, 2]))56
let sh = ndarray.shape_of(m)57
io.print(sh[0], sh[1])58
io.print(ndarray.size_of(m))59
)PURR",60
"[[1, 2, 3], [4, 5, 6]]\n"61
"[[2, 3, 4], [5, 6, 7]]\n"62
"[[-1, 0, 1], [2, 3, 4]]\n"63
"[[2, 4, 6], [8, 10, 12]]\n"64
"[[0.5, 1, 1.5], [2, 2.5, 3]]\n"65
"[[4, 5, 6], [7, 8, 9]]\n"66
"21\n"67
"3.5\n"68
"6\n"69
"2 3\n"70
"6\n");71
}73
// Complex support (complex/real/imag/conj) exercised together end-to-end: build a74
// complex vector from real & imaginary parts, pull the parts back out, conjugate it.75
TEST(StdlibE2E, NdarrayComplex) {76
e2e::expect_e2e("ndarray_complex_sys", R"PURR(import io77
import ndarray79
let re = ndarray.array([0.0, 1.0, 2.0])80
let im = ndarray.array([1.0, 0.0, -3.0])81
let z = ndarray.complex(re, im)83
io.print(ndarray.to_string(z))84
io.print(ndarray.to_string(ndarray.conj(z)))85
io.print(ndarray.to_string(ndarray.real(z)))86
io.print(ndarray.to_string(ndarray.imag(z)))87
)PURR",88
"[0+1j, 1+0j, 2-3j]\n"89
"[0-1j, 1+0j, 2+3j]\n"90
"[0, 1, 2]\n"91
"[1, 0, -3]\n");92
}94
// Element-wise math ufuncs exercised together (the array forms of the math module).95
TEST(StdlibE2E, NdarrayMath) {96
e2e::expect_e2e("ndarray_math_sys", R"PURR(import io97
import ndarray99
let a = ndarray.array([1.0, 4.0, 9.0, 16.0])100
io.print(ndarray.to_string(ndarray.sqrt(a)))101
io.print(ndarray.to_string(ndarray.cbrt(ndarray.array([1.0, 8.0, 27.0]))))102
io.print(ndarray.to_string(ndarray.abs(ndarray.array([-2.0, 3.0, -4.0]))))103
io.print(ndarray.to_string(ndarray.sin(ndarray.array([0.0]))))104
)PURR",105
"[1, 2, 3, 4]\n"106
"[1, 2, 3]\n"107
"[2, 3, 4]\n"108
"[0]\n");109
}