cheatah
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 the
4// per-function compile-run tests (tests/purrc/ndarray_cr_test.cpp), this drives a
5// single cohesive numeric program through EVERY purr-callable ndarray function and
6// asserts its exact stdout, so the functions are exercised together (factories feed
7// 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 cheatah
15// `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"
20TEST(StdlibE2E, Ndarray) {
21 e2e::expect_e2e("ndarray_sys", R"PURR(import io
22import ndarray
24# --- factories ---
25let a = ndarray.array([1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
26let s = ndarray.scalar(2.0)
27let z = ndarray.zeros([2, 3])
28let o = ndarray.ones([2, 3])
29let f = ndarray.full([2, 3], 4.0)
30let r = ndarray.arange(0.0, 6.0, 1.0)
32# --- reshape into a 2x3 matrix ---
33let m = ndarray.reshape(a, [2, 3])
34io.print(ndarray.to_string(m))
36# --- elementwise ops (broadcasting against scalar / same shape) ---
37let summ = ndarray.add(m, o)
38let diff = ndarray.sub(m, s)
39let prod = ndarray.mul(m, s)
40let quot = ndarray.divide(m, s)
41io.print(ndarray.to_string(summ))
42io.print(ndarray.to_string(diff))
43io.print(ndarray.to_string(prod))
44io.print(ndarray.to_string(quot))
46# combine zeros / full / arange
47let combo = ndarray.add(ndarray.add(z, f), ndarray.reshape(r, [2, 3]))
48io.print(ndarray.to_string(combo))
50# --- reductions ---
51io.print(ndarray.sum(m))
52io.print(ndarray.mean(m))
54# --- access / shape introspection ---
55io.print(ndarray.get(m, [1, 2]))
56let sh = ndarray.shape_of(m)
57io.print(sh[0], sh[1])
58io.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");
73// Complex support (complex/real/imag/conj) exercised together end-to-end: build a
74// complex vector from real & imaginary parts, pull the parts back out, conjugate it.
75TEST(StdlibE2E, NdarrayComplex) {
76 e2e::expect_e2e("ndarray_complex_sys", R"PURR(import io
77import ndarray
79let re = ndarray.array([0.0, 1.0, 2.0])
80let im = ndarray.array([1.0, 0.0, -3.0])
81let z = ndarray.complex(re, im)
83io.print(ndarray.to_string(z))
84io.print(ndarray.to_string(ndarray.conj(z)))
85io.print(ndarray.to_string(ndarray.real(z)))
86io.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");
94// Element-wise math ufuncs exercised together (the array forms of the math module).
95TEST(StdlibE2E, NdarrayMath) {
96 e2e::expect_e2e("ndarray_math_sys", R"PURR(import io
97import ndarray
99let a = ndarray.array([1.0, 4.0, 9.0, 16.0])
100io.print(ndarray.to_string(ndarray.sqrt(a)))
101io.print(ndarray.to_string(ndarray.cbrt(ndarray.array([1.0, 8.0, 27.0]))))
102io.print(ndarray.to_string(ndarray.abs(ndarray.array([-2.0, 3.0, -4.0]))))
103io.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");