From a9f2c4558761151bdd59e0b930eb49590add3983 Mon Sep 17 00:00:00 2001 From: felixfern Date: Sun, 22 Mar 2026 14:59:06 +0700 Subject: [PATCH 1/5] feat: array creation function --- src/core/ndarray.zig | 111 +++++++++++++++++++++++++++++++++++++++++++ src/wasm_api.zig | 25 +++++++--- tests/num-wasm.wasm | Bin 0 -> 39220 bytes 3 files changed, 129 insertions(+), 7 deletions(-) create mode 100755 tests/num-wasm.wasm diff --git a/src/core/ndarray.zig b/src/core/ndarray.zig index e1b3c98..1436e61 100644 --- a/src/core/ndarray.zig +++ b/src/core/ndarray.zig @@ -51,6 +51,50 @@ pub const NDArray = struct { return flat_index; } + + pub fn zeros(allocator: Allocator, shape: []const usize) !NDArray { + const arr = try NDArray.init(allocator, shape); + @memset(arr.data, 0.0); + return arr; + } + + pub fn ones(allocator: Allocator, shape: []const usize) !NDArray { + const arr = try NDArray.init(allocator, shape); + @memset(arr.data, 1.0); + return arr; + } + + pub fn full(allocator: Allocator, shape: []const usize, value: f64) !NDArray { + const arr = try NDArray.init(allocator, shape); + @memset(arr.data, value); + return arr; + } + + pub fn arange(allocator: Allocator, start: f64, stop: f64, step: f64) !NDArray { + const size: usize = @intFromFloat(@ceil((stop - start) / step)); + const arr = try NDArray.init(allocator, &[_]usize{size}); + + for (arr.data, 0..) |*val, idx| { + val.* = start + @as(f64, @floatFromInt(idx)) * step; + } + + return arr; + } + + pub fn linspace(allocator: Allocator, start: f64, stop: f64, count: usize) !NDArray { + const arr = try NDArray.init(allocator, &[_]usize{count}); + + if (count == 1) { + arr.data[0] = start; + return arr; + } + + const step = (stop - start) / @as(f64, @floatFromInt(count - 1)); + for (arr.data, 0..) |*val, idx| { + val.* = start + step * @as(f64, @floatFromInt(idx)); + } + return arr; + } }; test "init allocates correct size" { @@ -126,3 +170,70 @@ test "shape is independent copy" { shape[0] = 999; try testing.expectEqual(@as(usize, 3), arr.shape[0]); } + +test "zeros" { + var arr = try NDArray.zeros(testing.allocator, &[_]usize{ 2, 3 }); + defer arr.deinit(); + + for (arr.data) |val| { + try testing.expectEqual(@as(f64, 0.0), val); + } +} + +test "ones" { + var arr = try NDArray.ones(testing.allocator, &[_]usize{ 2, 3 }); + defer arr.deinit(); + + for (arr.data) |val| { + try testing.expectEqual(@as(f64, 1.0), val); + } +} + +test "full" { + var arr = try NDArray.full(testing.allocator, &[_]usize{ 2, 2 }, 7.5); + defer arr.deinit(); + + for (arr.data) |val| { + try testing.expectEqual(@as(f64, 7.5), val); + } +} + +test "arange integer step" { + var arr = try NDArray.arange(testing.allocator, 0, 5, 1); + defer arr.deinit(); + + try testing.expectEqual(@as(usize, 5), arr.data.len); + try testing.expectEqual(@as(f64, 0.0), arr.data[0]); + try testing.expectEqual(@as(f64, 1.0), arr.data[1]); + try testing.expectEqual(@as(f64, 4.0), arr.data[4]); +} + +test "arange with step > 1" { + var arr = try NDArray.arange(testing.allocator, 1, 10, 3); + defer arr.deinit(); + + try testing.expectEqual(@as(usize, 3), arr.data.len); + try testing.expectEqual(@as(f64, 1.0), arr.data[0]); + try testing.expectEqual(@as(f64, 4.0), arr.data[1]); + try testing.expectEqual(@as(f64, 7.0), arr.data[2]); +} + +test "linspace" { + var arr = try NDArray.linspace(testing.allocator, 0, 1, 5); + defer arr.deinit(); + + try testing.expectEqual(@as(usize, 5), arr.data.len); + try testing.expectApproxEqAbs(@as(f64, 0.0), arr.data[0], 1e-10); + try testing.expectApproxEqAbs(@as(f64, 0.25), arr.data[1], 1e-10); + try testing.expectApproxEqAbs(@as(f64, 0.5), arr.data[2], 1e-10); + try testing.expectApproxEqAbs(@as(f64, 0.75), arr.data[3], 1e-10); + try testing.expectApproxEqAbs(@as(f64, 1.0), arr.data[4], 1e-10); +} + +test "linspace single point" { + var arr = try NDArray.linspace(testing.allocator, 5, 5, 1); + defer arr.deinit(); + + try testing.expectEqual(@as(usize, 1), arr.data.len); + try testing.expectEqual(@as(f64, 5.0), arr.data[0]); +} diff --git a/src/wasm_api.zig b/src/wasm_api.zig index 5e537c4..a453521 100644 --- a/src/wasm_api.zig +++ b/src/wasm_api.zig @@ -1,8 +1,11 @@ const std = @import("std"); const wasm_allocator = std.heap.wasm_allocator; -const NDArray = @import("core/ndarray.zig").NDArray; -const creation = @import("core/creation.zig"); +const NDArray = @import("ndarray.zig").NDArray; + +// ------------------------------------------------------- +// Low-level memory helpers (Phase 1) +// ------------------------------------------------------- export fn wasm_alloc(len: usize) usize { const slice = wasm_allocator.alloc(u8, len) catch return 0; @@ -19,6 +22,8 @@ fn writeResult(arr: *NDArray, out_ptr: usize) void { out[0] = @intFromPtr(arr.data.ptr); out[1] = arr.data.len; + // Free the shape — JS already knows it, and the NDArray struct + // itself is on the stack (about to disappear). We only keep data alive. wasm_allocator.free(arr.shape); } @@ -27,35 +32,41 @@ fn shapeSlice(shape_ptr: usize, shape_len: usize) []const usize { return start[0..shape_len]; } +/// zeros(shape) — all elements 0.0 export fn wasm_zeros(shape_ptr: usize, shape_len: usize, out_ptr: usize) i32 { const shape = shapeSlice(shape_ptr, shape_len); - var arr = creation.zeros(wasm_allocator, shape) catch return -1; + var arr = NDArray.zeros(wasm_allocator, shape) catch return -1; writeResult(&arr, out_ptr); return 0; } +/// ones(shape) — all elements 1.0 export fn wasm_ones(shape_ptr: usize, shape_len: usize, out_ptr: usize) i32 { const shape = shapeSlice(shape_ptr, shape_len); - var arr = creation.ones(wasm_allocator, shape) catch return -1; + var arr = NDArray.ones(wasm_allocator, shape) catch return -1; writeResult(&arr, out_ptr); return 0; } +/// full(shape, value) — all elements set to value export fn wasm_full(shape_ptr: usize, shape_len: usize, value: f64, out_ptr: usize) i32 { const shape = shapeSlice(shape_ptr, shape_len); - var arr = creation.full(wasm_allocator, shape, value) catch return -1; + var arr = NDArray.full(wasm_allocator, shape, value) catch return -1; writeResult(&arr, out_ptr); return 0; } +/// arange(start, stop, step) — 1D array [start, start+step, ...] +/// JS doesn't know the output length ahead of time, reads it from out[1]. export fn wasm_arange(start: f64, stop: f64, step: f64, out_ptr: usize) i32 { - var arr = creation.arange(wasm_allocator, start, stop, step) catch return -1; + var arr = NDArray.arange(wasm_allocator, start, stop, step) catch return -1; writeResult(&arr, out_ptr); return 0; } +/// linspace(start, stop, count) — 1D array of count evenly spaced values export fn wasm_linspace(start: f64, stop: f64, count: usize, out_ptr: usize) i32 { - var arr = creation.linspace(wasm_allocator, start, stop, count) catch return -1; + var arr = NDArray.linspace(wasm_allocator, start, stop, count) catch return -1; writeResult(&arr, out_ptr); return 0; } diff --git a/tests/num-wasm.wasm b/tests/num-wasm.wasm new file mode 100755 index 0000000000000000000000000000000000000000..b6ddf3e99be8597b700999245bda522de9ec2401 GIT binary patch literal 39220 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 Date: Sun, 22 Mar 2026 15:26:02 +0700 Subject: [PATCH 2/5] feat: cleaup --- src/core/ndarray.zig | 111 ------------------------------------------- src/wasm_api.zig | 25 +++------- tests/num-wasm.wasm | Bin 39220 -> 0 bytes 3 files changed, 7 insertions(+), 129 deletions(-) delete mode 100755 tests/num-wasm.wasm diff --git a/src/core/ndarray.zig b/src/core/ndarray.zig index 1436e61..e1b3c98 100644 --- a/src/core/ndarray.zig +++ b/src/core/ndarray.zig @@ -51,50 +51,6 @@ pub const NDArray = struct { return flat_index; } - - pub fn zeros(allocator: Allocator, shape: []const usize) !NDArray { - const arr = try NDArray.init(allocator, shape); - @memset(arr.data, 0.0); - return arr; - } - - pub fn ones(allocator: Allocator, shape: []const usize) !NDArray { - const arr = try NDArray.init(allocator, shape); - @memset(arr.data, 1.0); - return arr; - } - - pub fn full(allocator: Allocator, shape: []const usize, value: f64) !NDArray { - const arr = try NDArray.init(allocator, shape); - @memset(arr.data, value); - return arr; - } - - pub fn arange(allocator: Allocator, start: f64, stop: f64, step: f64) !NDArray { - const size: usize = @intFromFloat(@ceil((stop - start) / step)); - const arr = try NDArray.init(allocator, &[_]usize{size}); - - for (arr.data, 0..) |*val, idx| { - val.* = start + @as(f64, @floatFromInt(idx)) * step; - } - - return arr; - } - - pub fn linspace(allocator: Allocator, start: f64, stop: f64, count: usize) !NDArray { - const arr = try NDArray.init(allocator, &[_]usize{count}); - - if (count == 1) { - arr.data[0] = start; - return arr; - } - - const step = (stop - start) / @as(f64, @floatFromInt(count - 1)); - for (arr.data, 0..) |*val, idx| { - val.* = start + step * @as(f64, @floatFromInt(idx)); - } - return arr; - } }; test "init allocates correct size" { @@ -170,70 +126,3 @@ test "shape is independent copy" { shape[0] = 999; try testing.expectEqual(@as(usize, 3), arr.shape[0]); } - -test "zeros" { - var arr = try NDArray.zeros(testing.allocator, &[_]usize{ 2, 3 }); - defer arr.deinit(); - - for (arr.data) |val| { - try testing.expectEqual(@as(f64, 0.0), val); - } -} - -test "ones" { - var arr = try NDArray.ones(testing.allocator, &[_]usize{ 2, 3 }); - defer arr.deinit(); - - for (arr.data) |val| { - try testing.expectEqual(@as(f64, 1.0), val); - } -} - -test "full" { - var arr = try NDArray.full(testing.allocator, &[_]usize{ 2, 2 }, 7.5); - defer arr.deinit(); - - for (arr.data) |val| { - try testing.expectEqual(@as(f64, 7.5), val); - } -} - -test "arange integer step" { - var arr = try NDArray.arange(testing.allocator, 0, 5, 1); - defer arr.deinit(); - - try testing.expectEqual(@as(usize, 5), arr.data.len); - try testing.expectEqual(@as(f64, 0.0), arr.data[0]); - try testing.expectEqual(@as(f64, 1.0), arr.data[1]); - try testing.expectEqual(@as(f64, 4.0), arr.data[4]); -} - -test "arange with step > 1" { - var arr = try NDArray.arange(testing.allocator, 1, 10, 3); - defer arr.deinit(); - - try testing.expectEqual(@as(usize, 3), arr.data.len); - try testing.expectEqual(@as(f64, 1.0), arr.data[0]); - try testing.expectEqual(@as(f64, 4.0), arr.data[1]); - try testing.expectEqual(@as(f64, 7.0), arr.data[2]); -} - -test "linspace" { - var arr = try NDArray.linspace(testing.allocator, 0, 1, 5); - defer arr.deinit(); - - try testing.expectEqual(@as(usize, 5), arr.data.len); - try testing.expectApproxEqAbs(@as(f64, 0.0), arr.data[0], 1e-10); - try testing.expectApproxEqAbs(@as(f64, 0.25), arr.data[1], 1e-10); - try testing.expectApproxEqAbs(@as(f64, 0.5), arr.data[2], 1e-10); - try testing.expectApproxEqAbs(@as(f64, 0.75), arr.data[3], 1e-10); - try testing.expectApproxEqAbs(@as(f64, 1.0), arr.data[4], 1e-10); -} - -test "linspace single point" { - var arr = try NDArray.linspace(testing.allocator, 5, 5, 1); - defer arr.deinit(); - - try testing.expectEqual(@as(usize, 1), arr.data.len); - try testing.expectEqual(@as(f64, 5.0), arr.data[0]); -} diff --git a/src/wasm_api.zig b/src/wasm_api.zig index a453521..5e537c4 100644 --- a/src/wasm_api.zig +++ b/src/wasm_api.zig @@ -1,11 +1,8 @@ const std = @import("std"); const wasm_allocator = std.heap.wasm_allocator; -const NDArray = @import("ndarray.zig").NDArray; - -// ------------------------------------------------------- -// Low-level memory helpers (Phase 1) -// ------------------------------------------------------- +const NDArray = @import("core/ndarray.zig").NDArray; +const creation = @import("core/creation.zig"); export fn wasm_alloc(len: usize) usize { const slice = wasm_allocator.alloc(u8, len) catch return 0; @@ -22,8 +19,6 @@ fn writeResult(arr: *NDArray, out_ptr: usize) void { out[0] = @intFromPtr(arr.data.ptr); out[1] = arr.data.len; - // Free the shape — JS already knows it, and the NDArray struct - // itself is on the stack (about to disappear). We only keep data alive. wasm_allocator.free(arr.shape); } @@ -32,41 +27,35 @@ fn shapeSlice(shape_ptr: usize, shape_len: usize) []const usize { return start[0..shape_len]; } -/// zeros(shape) — all elements 0.0 export fn wasm_zeros(shape_ptr: usize, shape_len: usize, out_ptr: usize) i32 { const shape = shapeSlice(shape_ptr, shape_len); - var arr = NDArray.zeros(wasm_allocator, shape) catch return -1; + var arr = creation.zeros(wasm_allocator, shape) catch return -1; writeResult(&arr, out_ptr); return 0; } -/// ones(shape) — all elements 1.0 export fn wasm_ones(shape_ptr: usize, shape_len: usize, out_ptr: usize) i32 { const shape = shapeSlice(shape_ptr, shape_len); - var arr = NDArray.ones(wasm_allocator, shape) catch return -1; + var arr = creation.ones(wasm_allocator, shape) catch return -1; writeResult(&arr, out_ptr); return 0; } -/// full(shape, value) — all elements set to value export fn wasm_full(shape_ptr: usize, shape_len: usize, value: f64, out_ptr: usize) i32 { const shape = shapeSlice(shape_ptr, shape_len); - var arr = NDArray.full(wasm_allocator, shape, value) catch return -1; + var arr = creation.full(wasm_allocator, shape, value) catch return -1; writeResult(&arr, out_ptr); return 0; } -/// arange(start, stop, step) — 1D array [start, start+step, ...] -/// JS doesn't know the output length ahead of time, reads it from out[1]. export fn wasm_arange(start: f64, stop: f64, step: f64, out_ptr: usize) i32 { - var arr = NDArray.arange(wasm_allocator, start, stop, step) catch return -1; + var arr = creation.arange(wasm_allocator, start, stop, step) catch return -1; writeResult(&arr, out_ptr); return 0; } -/// linspace(start, stop, count) — 1D array of count evenly spaced values export fn wasm_linspace(start: f64, stop: f64, count: usize, out_ptr: usize) i32 { - var arr = NDArray.linspace(wasm_allocator, start, stop, count) catch return -1; + var arr = creation.linspace(wasm_allocator, start, stop, count) catch return -1; writeResult(&arr, out_ptr); return 0; } diff --git a/tests/num-wasm.wasm b/tests/num-wasm.wasm deleted file mode 100755 index b6ddf3e99be8597b700999245bda522de9ec2401..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39220 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 Date: Sun, 22 Mar 2026 15:55:42 +0700 Subject: [PATCH 3/5] feat: shape transformation --- PLAN.md | 6 +++--- src/core/ndarray.zig | 2 +- src/core/shape.zig | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 src/core/shape.zig diff --git a/PLAN.md b/PLAN.md index 834774a..45df15e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -81,9 +81,9 @@ General formula: `flat = Σ(indices[d] * product(shape[d+1..]))` **Goal**: `reshape`, `transpose`, `flatten`. All return new arrays (copies). -- [ ] **4.1** `reshape(self, allocator, new_shape) → NDArray` — validate `product(new_shape) == product(old_shape)`, create new NDArray, copy data (`@memcpy`), assign new shape -- [ ] **4.2** `transpose(self, allocator) → NDArray` — 2D only. Create new `(cols, rows)` array, copy `result[j][i] = self[i][j]` -- [ ] **4.3** `flatten(self, allocator) → NDArray` — reshape to `[total_elements]` +- [X] **4.1** `reshape(self, allocator, new_shape) → NDArray` — validate `product(new_shape) == product(old_shape)`, create new NDArray, copy data (`@memcpy`), assign new shape +- [X] **4.2** `transpose(self, allocator) → NDArray` — 2D only. Create new `(cols, rows)` array, copy `result[j][i] = self[i][j]` +- [X] **4.3** `flatten(self, allocator) → NDArray` — reshape to `[total_elements]` - [ ] **4.4** `squeeze(self, allocator) → NDArray` — remove dimensions of size 1 from shape, copy data - [ ] **4.5** Tests: reshape `(2, 6)` → `(3, 4)`, verify values preserved. Transpose `(3, 4)` → verify `(4, 3)` with correct element positions diff --git a/src/core/ndarray.zig b/src/core/ndarray.zig index e1b3c98..17674f0 100644 --- a/src/core/ndarray.zig +++ b/src/core/ndarray.zig @@ -38,7 +38,7 @@ pub const NDArray = struct { self.data[flat_index] = value; } - fn flatIndex(self: *const NDArray, indices: []const usize) usize { + pub fn flatIndex(self: *const NDArray, indices: []const usize) usize { var flat_index: usize = 0; var multiplier: usize = 1; var i: usize = self.ndim; diff --git a/src/core/shape.zig b/src/core/shape.zig new file mode 100644 index 0000000..f7a8a9b --- /dev/null +++ b/src/core/shape.zig @@ -0,0 +1,39 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const testing = std.testing; + +const NDArray = @import("ndarray.zig").NDArray; + +pub fn reshape(allocator: Allocator, arr: *const NDArray, new_shape: []const usize) !NDArray { + var new_total: usize = 1; + for (new_shape) |size| { + new_total *= size; + } + + if (new_total != arr.data.len) return error.ShapeMismatch; + + const res = try NDArray.init(allocator, new_shape); + @memcpy(res.data, arr.data); + + return res; +} + +pub fn transpose(allocator: Allocator, arr: *const NDArray) !NDArray { + if (arr.ndim != 2) return error.MismatchDimension; + + const rows = arr.shape[0]; + const cols = arr.shape[1]; + const res = try NDArray.init(allocator, &[_]usize{ cols, rows }); + + for (0..cols) |c| { + for (0..rows) |r| { + res.setItem(&[_]usize{ c, r }, arr.getItem(&[_]usize{ r, c })); + } + } + + return res; +} + +pub fn flatten(allocator: Allocator, arr: *const NDArray) !NDArray { + return reshape(allocator, arr, &[_]usize{arr.data.len}); +} From 179fea6cc7d5749af1cc17badcb632d1f1c2f849 Mon Sep 17 00:00:00 2001 From: felixfern Date: Mon, 23 Mar 2026 19:13:17 +0700 Subject: [PATCH 4/5] chore: remove pub --- src/core/ndarray.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ndarray.zig b/src/core/ndarray.zig index 17674f0..e1b3c98 100644 --- a/src/core/ndarray.zig +++ b/src/core/ndarray.zig @@ -38,7 +38,7 @@ pub const NDArray = struct { self.data[flat_index] = value; } - pub fn flatIndex(self: *const NDArray, indices: []const usize) usize { + fn flatIndex(self: *const NDArray, indices: []const usize) usize { var flat_index: usize = 0; var multiplier: usize = 1; var i: usize = self.ndim; From 979e3c5cb0a841cbe14691a713926cacb8d52f0c Mon Sep 17 00:00:00 2001 From: felixfern Date: Mon, 23 Mar 2026 19:39:31 +0700 Subject: [PATCH 5/5] chore: update readme --- README.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8ffbbf6..3cf1090 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# num-wasm +# nw.js A NumPy-like array library written in Zig, compiled to WebAssembly for JavaScript/TypeScript. @@ -68,17 +68,17 @@ These simplifications keep the codebase approachable. They can be upgraded later ## Roadmap -| Phase | Feature | Status | -|-------|---------|--------| -| 1 | Toolchain setup, hello WASM | Done | -| 2 | NDArray core data structure | Done | -| 3 | Array creation functions | Done | -| 4 | Shape manipulation (reshape, transpose, flatten) | Upcoming | -| 5 | Broadcasting | Upcoming | -| 6 | Element-wise operations (add, multiply, sqrt, etc.) | Upcoming | -| 7 | Reduction operations (sum, mean, max, min) | Upcoming | -| 8 | Slicing and indexing | Upcoming | -| 9 | Linear algebra (dot, matmul) | Upcoming | -| 10 | JS glue library (clean API) | Upcoming | +| Phase | Feature | Status | +| ----- | --------------------------------------------------- | -------- | +| 1 | Toolchain setup, hello WASM | Done | +| 2 | NDArray core data structure | Done | +| 3 | Array creation functions | Done | +| 4 | Shape manipulation (reshape, transpose, flatten) | Upcoming | +| 5 | Broadcasting | Upcoming | +| 6 | Element-wise operations (add, multiply, sqrt, etc.) | Upcoming | +| 7 | Reduction operations (sum, mean, max, min) | Upcoming | +| 8 | Slicing and indexing | Upcoming | +| 9 | Linear algebra (dot, matmul) | Upcoming | +| 10 | JS glue library (clean API) | Upcoming | See [PLAN.md](./PLAN.md) for detailed implementation plans.