diff --git a/src/TensorAlloc.zig b/src/TensorAlloc.zig index d1c47b8..68b400d 100644 --- a/src/TensorAlloc.zig +++ b/src/TensorAlloc.zig @@ -3,6 +3,7 @@ const Scales = @import("Scales.zig"); const UnitScale = Scales.UnitScale; const Dimensions = @import("Dimensions.zig"); const Dimension = Dimensions.Dimension; +const Allocator = std.mem.Allocator; const sh = @import("shared.zig"); // ───────────────────────────────────────────────────────────────────────────── @@ -50,11 +51,11 @@ pub fn TensorAlloc( // Specific to Alloc - pub fn init(allocator: std.mem.Allocator) !@This() { + pub fn init(allocator: Allocator) !@This() { return .{ .data = try allocator.create(Vec) }; } - pub fn deinit(self: @This(), allocator: std.mem.Allocator) void { + pub fn deinit(self: @This(), allocator: Allocator) void { allocator.destroy(self.data); } @@ -72,7 +73,7 @@ pub fn TensorAlloc( } /// Broadcast a single value across all elements. - pub inline fn splat(alloc: std.mem.Allocator, v: T) !Self { + pub inline fn splat(alloc: Allocator, v: T) !Self { const new = try @This().init(alloc); new.data.* = @splat(v); return new; @@ -88,7 +89,7 @@ pub fn TensorAlloc( /// Element-wise add. Dimensions must match; scales resolve to finer. /// RHS must have the same shape as self, or total == 1 (broadcast). - pub inline fn add(self: *const Self, rhs: anytype) TensorAlloc( + pub inline fn add(self: *const Self, alloc: Allocator, rhs: anytype) !TensorAlloc( T, dims.argsOpt(), sh.finerScales(Self, @TypeOf(rhs)).argsOpt(), @@ -102,10 +103,16 @@ pub fn TensorAlloc( if (comptime RhsType.total != 1 and !sh.shapeEql(shape, RhsType.shape)) @compileError("Shape mismatch in add: element-wise operations require identical shapes, or a scalar RHS."); + var area = std.heap.ArenaAllocator.init(alloc); + defer area.deinit(); + const TargetType = TensorAlloc(T, dims.argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); - const l: Vec = self.to(TargetType).data; - const r: Vec = rhs.to(TargetType).data; - return .{ .data = if (comptime sh.isInt(T)) l +| r else l + r }; + const l: TargetType = try self.to(area.allocator(), TargetType); + const r: TargetType = try rhs.to(area.allocator(), TargetType); + + const new = try TargetType.init(alloc); + new.data.* = if (comptime sh.isInt(T)) l.data.* +| r.data.* else l.data.* + r.data.*; + return new; } /// Element-wise sub. Dimensions must match; scales resolve to finer. @@ -226,8 +233,9 @@ pub fn TensorAlloc( /// • Scale ratio is computed fully at comptime; only a SIMD multiply at runtime. pub inline fn to( self: *const Self, + alloc: Allocator, comptime Dest: type, - ) Dest { + ) !Dest { if (comptime Self == Dest) return self.*; // Run validation checks FIRST before dealing with types @@ -236,74 +244,85 @@ pub fn TensorAlloc( if (comptime total != 1 and !sh.shapeEql(shape, Dest.shape)) @compileError("Shape mismatch in to: destination type must have the identical shape, or be a scalar."); - const vec = if (comptime total == 1 and Dest.total != 1) - TensorAlloc(Dest.ValueType, dims.argsOpt(), scales.argsOpt(), Dest.shape){ .data = @splat(self.data[0]) } - else - self; - const ratio = comptime (scales.getFactor(dims) / Dest.scales.getFactor(Dest.dims)); const DestT = Dest.ValueType; const DestVec = @Vector(Dest.total, DestT); + const T_info = @typeInfo(T); + const Dest_info = @typeInfo(DestT); - if (comptime ratio == 1.0 and T == DestT) - return .{ .data = vec.data }; + const vec = try TensorAlloc(Dest.ValueType, dims.argsOpt(), scales.argsOpt(), Dest.shape).splat( + alloc, + if (comptime T_info == .int and Dest_info == .int) + @as(DestT, @intCast(self.data[0])) + else if (comptime T_info == .float and Dest_info == .float) + @as(DestT, @floatCast(self.data[0])) + else if (comptime T_info == .int and Dest_info == .float) + @as(DestT, @floatFromInt(self.data[0])) + else if (comptime T_info == .float and Dest_info == .int) + @as(DestT, @intFromFloat(self.data[0])) + else + unreachable, + ); + if (comptime total != 1 and total == Dest.total) + vec.data.* = if (comptime T_info == .int and Dest_info == .int) + @as(DestVec, @intCast(self.data.*)) + else if (comptime T_info == .float and Dest_info == .float) + @as(DestVec, @floatCast(self.data.*)) + else if (comptime T_info == .int and Dest_info == .float) + @as(DestVec, @floatFromInt(self.data.*)) + else if (comptime T_info == .float and Dest_info == .int) + @as(DestVec, @intFromFloat(self.data.*)) + else + unreachable; + defer vec.deinit(alloc); - // If ratio is 1, handle type conversion correctly based on BOTH source and dest types - if (comptime ratio == 1.0) { - const T_info = @typeInfo(T); - const Dest_info = @typeInfo(DestT); + const new = try Dest.init(alloc); - return .{ - .data = if (comptime T_info == .int and Dest_info == .int) - @as(DestVec, @intCast(vec.data)) - else if (comptime T_info == .float and Dest_info == .float) - @as(DestVec, @floatCast(vec.data)) - else if (comptime T_info == .int and Dest_info == .float) - @as(DestVec, @floatFromInt(vec.data)) - else if (comptime T_info == .float and Dest_info == .int) - @as(DestVec, @intFromFloat(vec.data)) - else - unreachable, - }; - } + if (comptime ratio == 1.0) + return new; if (comptime T == DestT) { - if (comptime @typeInfo(T) == .float) - return .{ .data = vec.data * @as(DestVec, @splat(@as(T, @floatCast(ratio)))) }; + if (comptime @typeInfo(T) == .float) { + new.data.* = vec.data.* * @as(DestVec, @splat(@as(T, @floatCast(ratio)))); + return new; + } if (comptime ratio >= 1.0) { const mult: T = comptime @intFromFloat(@round(ratio)); - return .{ .data = vec.data *| @as(Vec, @splat(mult)) }; + new.data.* = vec.data.* *| @as(Vec, @splat(mult)); } else { const div_val: T = comptime @intFromFloat(@round(1.0 / ratio)); const half: T = comptime @divTrunc(div_val, 2); if (comptime @typeInfo(T).int.signedness == .unsigned) { - return .{ .data = @divTrunc(vec.data + @as(Vec, @splat(half)), @as(Vec, @splat(div_val))) }; + new.data.* = @divTrunc(vec.data.* + @as(Vec, @splat(half)), @as(Vec, @splat(div_val))); } else { - // Vectorized branchless negative handling - const is_pos = self.data >= @as(Vec, @splat(0)); + const is_pos = self.data.* >= @as(Vec, @splat(0)); const offsets = @select(T, is_pos, @as(Vec, @splat(half)), @as(Vec, @splat(-half))); - return .{ .data = @divTrunc(vec.data + offsets, @as(Vec, @splat(div_val))) }; + new.data.* = @divTrunc(vec.data.* + offsets, @as(Vec, @splat(div_val))); } } + + return new; } // Cross-type fully vectorized casting with scales const FVec = @Vector(total, f64); const float_vec: FVec = switch (comptime @typeInfo(T)) { - .float => @floatCast(vec.data), - .int => @floatFromInt(vec.data), + .float => @floatCast(vec.data.*), + .int => @floatFromInt(vec.data.*), else => unreachable, }; const scaled = float_vec * @as(FVec, @splat(ratio)); - return switch (comptime @typeInfo(DestT)) { - .float => .{ .data = @floatCast(scaled) }, - .int => .{ .data = @intFromFloat(@round(scaled)) }, + new.data.* = switch (comptime @typeInfo(DestT)) { + .float => @floatCast(scaled), + .int => @intFromFloat(@round(scaled)), else => unreachable, }; + + return new; } const CmpResult = if (total == 1) bool else [total]bool; @@ -313,7 +332,7 @@ pub fn TensorAlloc( } /// Resolve both sides to the finer scale, broadcasting shape {1} RHS if needed. - inline fn resolveScalePair(self: *const Self, rhs: anytype) struct { l: Vec, r: Vec } { + inline fn resolveScalePair(self: *const Self, rhs: anytype) struct { l: *Vec, r: *Vec } { const RhsType = @TypeOf(rhs); if (comptime !isTensor(RhsType)) @compileError("rhs can only be a Tensor "); @@ -622,28 +641,32 @@ pub fn TensorAlloc( // ─── Scalar tests ───────────────────────────────────────────────────────── -test "Scalar initiat" { - const alloc = std.testing.allocator; +test "TensorAlloc | Scalar initiat" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); const Meter = TensorAlloc(i128, .{ .L = 1 }, .{ .L = @enumFromInt(-3) }, &.{1}); const Second = TensorAlloc(f32, .{ .T = 1 }, .{ .T = .n }, &.{1}); const distance = try Meter.splat(alloc, 10); - defer distance.deinit(alloc); const time = try Second.splat(alloc, 2); - defer time.deinit(alloc); try std.testing.expectEqual(10, distance.data[0]); try std.testing.expectEqual(2, time.data[0]); } -// test "Scalar comparisons (eq, ne, gt, gte, lt, lte)" { +// test "TensorAlloc | Scalar comparisons (eq, ne, gt, gte, lt, lte)" { +// var arena = std.heap.ArenaAllocator.init(std.testing.allocator); +// defer arena.deinit(); +// const alloc = arena.allocator(); +// // const Meter = TensorAlloc(i128, .{ .L = 1 }, .{}, &.{1}); // const KiloMeter = TensorAlloc(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); // -// const m1000 = Meter.splat(1000); -// const km1 = KiloMeter.splat(1); -// const km2 = KiloMeter.splat(2); +// const m1000 = try Meter.splat(alloc, 1000); +// const km1 = try KiloMeter.splat(alloc, 1); +// const km2 = try KiloMeter.splat(alloc, 2); // // try std.testing.expect(m1000.eq(km1)); // try std.testing.expect(km1.eq(m1000)); @@ -659,30 +682,34 @@ test "Scalar initiat" { // try std.testing.expect(km1.lte(m1000)); // try std.testing.expect(m1000.lte(km2)); // } -// -// test "Scalar Add" { -// const Meter = TensorAlloc(i128, .{ .L = 1 }, .{}, &.{1}); -// const KiloMeter = TensorAlloc(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// const KiloMeter_f = TensorAlloc(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// -// const distance = Meter.splat(10); -// const distance2 = Meter.splat(20); -// const added = distance.add(distance2); -// try std.testing.expectEqual(30, added.data[0]); -// try std.testing.expectEqual(1, @TypeOf(added).dims.get(.L)); -// -// const distance3 = KiloMeter.splat(2); -// const added2 = distance.add(distance3); -// try std.testing.expectEqual(2010, added2.data[0]); -// -// const added3 = distance3.add(distance).to(KiloMeter); -// try std.testing.expectEqual(2, added3.data[0]); -// -// const distance4 = KiloMeter_f.splat(2); -// const added4 = distance4.add(distance).to(KiloMeter_f); -// try std.testing.expectApproxEqAbs(2.01, added4.data[0], 0.000001); -// } -// + +test "TensorAlloc | Scalar Add" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = TensorAlloc(i128, .{ .L = 1 }, .{}, &.{1}); + const KiloMeter = TensorAlloc(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); + const KiloMeter_f = TensorAlloc(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); + + const distance = try Meter.splat(alloc, 10); + const distance2 = try Meter.splat(alloc, 20); + const added = try distance.add(alloc, distance2); + try std.testing.expectEqual(30, added.data[0]); + try std.testing.expectEqual(1, @TypeOf(added).dims.get(.L)); + + const distance3 = try KiloMeter.splat(alloc, 2); + const added2 = try distance.add(alloc, distance3); + try std.testing.expectEqual(2010, added2.data[0]); + + const added3 = try (try distance3.add(alloc, distance)).to(alloc, KiloMeter); + try std.testing.expectEqual(2, added3.data[0]); + + const distance4 = try KiloMeter_f.splat(alloc, 2); + const added4 = try (try distance4.add(alloc, distance)).to(alloc, KiloMeter_f); + try std.testing.expectApproxEqAbs(2.01, added4.data[0], 0.000001); +} + // test "Scalar Sub" { // const Meter = TensorAlloc(i128, .{ .L = 1 }, .{}, &.{1}); // const KiloMeter_f = TensorAlloc(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); @@ -1129,3 +1156,70 @@ test "Scalar initiat" { // try std.testing.expectEqual(4, T3.strides_arr[1]); // try std.testing.expectEqual(1, T3.strides_arr[2]); // } +// +// test "Slice 1D negative start" { +// const Vec = TensorStatic(i32, .{}, .{}, &.{5}); +// const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; +// const s = v.slice(.{.{ .start = -3, .end = 5 }}); // [2,5) → 30,40,50 +// try std.testing.expectEqual(3, @TypeOf(s).total); +// try std.testing.expectEqual(30, s.data[0]); +// try std.testing.expectEqual(40, s.data[1]); +// try std.testing.expectEqual(50, s.data[2]); +// } +// +// test "Slice 1D negative end" { +// const Vec = TensorStatic(i32, .{}, .{}, &.{5}); +// const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; +// const s = v.slice(.{.{ .start = 1, .end = -1 }}); // [1,4) → 20,30,40 +// try std.testing.expectEqual(3, @TypeOf(s).total); +// try std.testing.expectEqual(20, s.data[0]); +// try std.testing.expectEqual(30, s.data[1]); +// try std.testing.expectEqual(40, s.data[2]); +// } +// +// test "Slice 1D both negative" { +// const Vec = TensorStatic(i64, .{}, .{}, &.{6}); +// const v = Vec{ .data = .{ 5, 10, 15, 20, 25, 30 } }; +// const s = v.slice(.{.{ .start = -4, .end = -1 }}); // [2,5) → 15,20,25 +// try std.testing.expectEqual(3, @TypeOf(s).total); +// try std.testing.expectEqual(15, s.data[0]); +// try std.testing.expectEqual(20, s.data[1]); +// try std.testing.expectEqual(25, s.data[2]); +// } +// +// test "Slice 1D null start" { +// const Vec = TensorStatic(i32, .{}, .{}, &.{5}); +// const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; +// const s = v.slice(.{.{ .end = -2 }}); // [:-2] → 10,20,30 +// try std.testing.expectEqual(3, @TypeOf(s).total); +// try std.testing.expectEqual(10, s.data[0]); +// try std.testing.expectEqual(20, s.data[1]); +// try std.testing.expectEqual(30, s.data[2]); +// } +// +// test "Slice 1D null end" { +// const Vec = TensorStatic(i32, .{}, .{}, &.{5}); +// const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; +// const s = v.slice(.{.{ .start = -3 }}); // [-3:] → 30,40,50 +// try std.testing.expectEqual(3, @TypeOf(s).total); +// try std.testing.expectEqual(30, s.data[0]); +// try std.testing.expectEqual(40, s.data[1]); +// try std.testing.expectEqual(50, s.data[2]); +// } +// +// test "Slice 2D negative & null indices" { +// const Mat = TensorStatic(i32, .{}, .{}, &.{ 4, 4 }); +// const m = Mat{ .data = .{ +// 1, 2, 3, 4, +// 5, 6, 7, 8, +// 9, 10, 11, 12, +// 13, 14, 15, 16, +// } }; +// // last 2 rows, last 2 cols → same as subblock test [2,4)x[2,4) +// const s = m.slice(.{ .{ .start = -2, .end = 4 }, .{ .start = -2 } }); +// try std.testing.expectEqual(4, @TypeOf(s).total); +// try std.testing.expectEqual(11, s.data[0]); +// try std.testing.expectEqual(12, s.data[1]); +// try std.testing.expectEqual(15, s.data[2]); +// try std.testing.expectEqual(16, s.data[3]); +// } diff --git a/src/TensorStatic.zig b/src/TensorStatic.zig index d63bb47..1e02ff7 100644 --- a/src/TensorStatic.zig +++ b/src/TensorStatic.zig @@ -669,7 +669,7 @@ pub fn TensorStatic( // ─── Scalar tests ───────────────────────────────────────────────────────── -test "Scalar initiat" { +test "TensorStatic | Scalar initiat" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{ .L = @enumFromInt(-3) }, &.{1}); const Second = TensorStatic(f32, .{ .T = 1 }, .{ .T = .n }, &.{1}); @@ -680,7 +680,7 @@ test "Scalar initiat" { try std.testing.expectEqual(2, time.data[0]); } -test "Scalar comparisons (eq, ne, gt, gte, lt, lte)" { +test "TensorStatic | Scalar comparisons (eq, ne, gt, gte, lt, lte)" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const KiloMeter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); @@ -703,7 +703,7 @@ test "Scalar comparisons (eq, ne, gt, gte, lt, lte)" { try std.testing.expect(m1000.lte(km2)); } -test "Scalar Add" { +test "TensorStatic | Scalar Add" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const KiloMeter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); const KiloMeter_f = TensorStatic(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); @@ -726,7 +726,7 @@ test "Scalar Add" { try std.testing.expectApproxEqAbs(2.01, added4.data[0], 0.000001); } -test "Scalar Sub" { +test "TensorStatic | Scalar Sub" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const KiloMeter_f = TensorStatic(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); @@ -743,7 +743,7 @@ test "Scalar Sub" { try std.testing.expectApproxEqAbs(2000, diff3.data[0], 1e-4); } -test "Scalar MulBy" { +test "TensorStatic | Scalar MulBy" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const Second = TensorStatic(f32, .{ .T = 1 }, .{}, &.{1}); @@ -760,7 +760,7 @@ test "Scalar MulBy" { try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L)); } -test "Scalar MulBy with scale" { +test "TensorStatic | Scalar MulBy with scale" { const KiloMeter = TensorStatic(f32, .{ .L = 1 }, .{ .L = .k }, &.{1}); const KiloGram = TensorStatic(f32, .{ .M = 1 }, .{ .M = .k }, &.{1}); @@ -771,7 +771,7 @@ test "Scalar MulBy with scale" { try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.M)); } -test "Scalar MulBy with type change" { +test "TensorStatic | Scalar MulBy with type change" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); const Second = TensorStatic(f64, .{ .T = 1 }, .{}, &.{1}); const KmSec = TensorStatic(i64, .{ .L = 1, .T = 1 }, .{ .L = .k }, &.{1}); @@ -784,7 +784,7 @@ test "Scalar MulBy with type change" { try std.testing.expectApproxEqAbs(12.0, d.mul(t).to(KmSec_f).data[0], 0.0001); } -test "Scalar MulBy small" { +test "TensorStatic | Scalar MulBy small" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .n }, &.{1}); const Second = TensorStatic(f32, .{ .T = 1 }, .{}, &.{1}); const d = Meter.splat(3); @@ -792,7 +792,7 @@ test "Scalar MulBy small" { try std.testing.expectEqual(12, d.mul(t).data[0]); } -test "Scalar MulBy dimensionless" { +test "TensorStatic | Scalar MulBy dimensionless" { const DimLess = TensorStatic(i128, .{}, .{}, &.{1}); const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const d = Meter.splat(7); @@ -800,7 +800,7 @@ test "Scalar MulBy dimensionless" { try std.testing.expectEqual(21, scaled.data[0]); } -test "Scalar Sqrt" { +test "TensorStatic | Scalar Sqrt" { const MeterSquare = TensorStatic(i128, .{ .L = 2 }, .{}, &.{1}); const MeterSquare_f = TensorStatic(f64, .{ .L = 2 }, .{}, &.{1}); @@ -818,7 +818,7 @@ test "Scalar Sqrt" { try std.testing.expectApproxEqAbs(4.472135955, scaled2.data[0], 1e-4); } -test "Scalar Chained: velocity and acceleration" { +test "TensorStatic | Scalar Chained: velocity and acceleration" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const Second = TensorStatic(f32, .{ .T = 1 }, .{}, &.{1}); @@ -832,7 +832,7 @@ test "Scalar Chained: velocity and acceleration" { try std.testing.expectEqual(5, accel.data[0]); } -test "Scalar DivBy integer exact" { +test "TensorStatic | Scalar DivBy integer exact" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const Second = TensorStatic(f32, .{ .T = 1 }, .{}, &.{1}); @@ -842,7 +842,7 @@ test "Scalar DivBy integer exact" { try std.testing.expectEqual(30, vel.data[0]); } -test "Scalar Finer scales skip dim 0" { +test "TensorStatic | Scalar Finer scales skip dim 0" { const Dimless = TensorStatic(i128, .{}, .{}, &.{1}); const KiloMetre = TensorStatic(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); @@ -853,7 +853,7 @@ test "Scalar Finer scales skip dim 0" { try std.testing.expectEqual(Scales.UnitScale.k, @TypeOf(vel).scales.get(.L)); } -test "Scalar Conversion chain: km -> m -> cm" { +test "TensorStatic | Scalar Conversion chain: km -> m -> cm" { const KiloMeter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const CentiMeter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .c }, &.{1}); @@ -865,7 +865,7 @@ test "Scalar Conversion chain: km -> m -> cm" { try std.testing.expectEqual(1_500_000, cm.data[0]); } -test "Scalar Conversion: hours -> minutes -> seconds" { +test "TensorStatic | Scalar Conversion: hours -> minutes -> seconds" { const Hour = TensorStatic(i128, .{ .T = 1 }, .{ .T = .hour }, &.{1}); const Minute = TensorStatic(i128, .{ .T = 1 }, .{ .T = .min }, &.{1}); const Second = TensorStatic(i128, .{ .T = 1 }, .{}, &.{1}); @@ -877,7 +877,7 @@ test "Scalar Conversion: hours -> minutes -> seconds" { try std.testing.expectEqual(3600, sec.data[0]); } -test "Scalar Format" { +test "TensorStatic | Scalar Format" { const MeterPerSecondSq = TensorStatic(f32, .{ .L = 1, .T = -2 }, .{ .T = .n }, &.{1}); const Meter = TensorStatic(f32, .{ .L = 1 }, .{}, &.{1}); @@ -892,7 +892,7 @@ test "Scalar Format" { try std.testing.expectEqualStrings("9.81m.ns⁻²", res); } -test "Scalar Abs" { +test "TensorStatic | Scalar Abs" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const MeterF = TensorStatic(f32, .{ .L = 1 }, .{}, &.{1}); @@ -900,21 +900,21 @@ test "Scalar Abs" { try std.testing.expectEqual(42.5, MeterF.splat(-42.5).abs().data[0]); } -test "Scalar Pow" { +test "TensorStatic | Scalar Pow" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{}, &.{1}); const d = Meter.splat(4); try std.testing.expectEqual(16, d.pow(2).data[0]); try std.testing.expectEqual(64, d.pow(3).data[0]); } -test "Scalar add/sub bare number on dimensionless scalar" { +test "TensorStatic | Scalar add/sub bare number on dimensionless scalar" { const DimLess = TensorStatic(i128, .{}, .{}, &.{1}); const a = DimLess.splat(10); try std.testing.expectEqual(15, a.add(DimLess.splat(5)).data[0]); try std.testing.expectEqual(7, a.sub(DimLess.splat(3)).data[0]); } -test "Scalar Imperial length scales" { +test "TensorStatic | Scalar Imperial length scales" { const Foot = TensorStatic(f64, .{ .L = 1 }, .{ .L = .ft }, &.{1}); const Meter = TensorStatic(f64, .{ .L = 1 }, .{}, &.{1}); const Inch = TensorStatic(f64, .{ .L = 1 }, .{ .L = .inch }, &.{1}); @@ -923,7 +923,7 @@ test "Scalar Imperial length scales" { try std.testing.expectApproxEqAbs(1.0, Inch.splat(12.0).to(Foot).data[0], 1e-9); } -test "Scalar Imperial mass scales" { +test "TensorStatic | Scalar Imperial mass scales" { const Pound = TensorStatic(f64, .{ .M = 1 }, .{ .M = .lb }, &.{1}); const Ounce = TensorStatic(f64, .{ .M = 1 }, .{ .M = .oz }, &.{1}); @@ -933,14 +933,14 @@ test "Scalar Imperial mass scales" { // ─── Vector / Tensor tests ──────────────────────────────────────────────── -test "Vector initiate" { +test "TensorStatic | Vector initiate" { const Meter4 = TensorStatic(f32, .{ .L = 1 }, .{}, &.{4}); const m = Meter4.splat(1); try std.testing.expect(m.data[0] == 1); try std.testing.expect(m.data[3] == 1); } -test "Vector format" { +test "TensorStatic | Vector format" { const MeterPerSecondSq = TensorStatic(f32, .{ .L = 1, .T = -2 }, .{ .T = .n }, &.{3}); const KgMeterPerSecond = TensorStatic(f32, .{ .M = 1, .L = 1, .T = -1 }, .{ .M = .k }, &.{3}); @@ -955,7 +955,7 @@ test "Vector format" { try std.testing.expectEqualStrings("(43.00, 0.00, 11.00)m.kg.s⁻¹", res); } -test "Vector Vec3 Init and Basic Arithmetic" { +test "TensorStatic | Vector Vec3 Init and Basic Arithmetic" { const Meter3 = TensorStatic(i32, .{ .L = 1 }, .{}, &.{3}); const v_zero = Meter3.zero; @@ -987,7 +987,7 @@ test "Vector Vec3 Init and Basic Arithmetic" { try std.testing.expectEqual(-30, neg.data[2]); } -test "Vector Kinematics (scalar mul/div broadcast)" { +test "TensorStatic | Vector Kinematics (scalar mul/div broadcast)" { const Meter3 = TensorStatic(i32, .{ .L = 1 }, .{}, &.{3}); const Second1 = TensorStatic(i32, .{ .T = 1 }, .{}, &.{1}); @@ -1006,7 +1006,7 @@ test "Vector Kinematics (scalar mul/div broadcast)" { try std.testing.expectEqual(0, @TypeOf(new_pos).dims.get(.T)); } -test "Vector Element-wise Math and Scaling" { +test "TensorStatic | Vector Element-wise Math and Scaling" { const Meter3 = TensorStatic(i32, .{ .L = 1 }, .{}, &.{3}); const v1 = Meter3{ .data = .{ 10, 20, 30 } }; @@ -1018,7 +1018,7 @@ test "Vector Element-wise Math and Scaling" { try std.testing.expectEqual(0, @TypeOf(dv).dims.get(.L)); } -test "Vector Conversions" { +test "TensorStatic | Vector Conversions" { const KiloMeter3 = TensorStatic(i32, .{ .L = 1 }, .{ .L = .k }, &.{3}); const Meter3 = TensorStatic(i32, .{ .L = 1 }, .{}, &.{3}); @@ -1030,7 +1030,7 @@ test "Vector Conversions" { try std.testing.expectEqual(UnitScale.none, @TypeOf(v_m).scales.get(.L)); } -test "Vector Length" { +test "TensorStatic | Vector Length" { const MeterInt3 = TensorStatic(i32, .{ .L = 1 }, .{}, &.{3}); const MeterFloat3 = TensorStatic(f32, .{ .L = 1 }, .{}, &.{3}); @@ -1043,7 +1043,7 @@ test "Vector Length" { try std.testing.expectApproxEqAbs(@as(f32, 5.0), v_float.length(), 1e-4); } -test "Vector Comparisons" { +test "TensorStatic | Vector Comparisons" { const Meter3 = TensorStatic(f32, .{ .L = 1 }, .{}, &.{3}); const KiloMeter3 = TensorStatic(f32, .{ .L = 1 }, .{ .L = .k }, &.{3}); @@ -1068,7 +1068,7 @@ test "Vector Comparisons" { try std.testing.expect(low_eq[0] and low_eq[1] and low_eq[2]); } -test "Vector vs Scalar broadcast comparison" { +test "TensorStatic | Vector vs Scalar broadcast comparison" { const Meter3 = TensorStatic(f32, .{ .L = 1 }, .{}, &.{3}); const KiloMeter1 = TensorStatic(f32, .{ .L = 1 }, .{ .L = .k }, &.{1}); @@ -1086,7 +1086,7 @@ test "Vector vs Scalar broadcast comparison" { try std.testing.expect(exact[1] == false); } -test "Vector contract — dot product (rank-1 * rank-1)" { +test "TensorStatic | Vector contract — dot product (rank-1 * rank-1)" { const Meter3 = TensorStatic(f32, .{ .L = 1 }, .{}, &.{3}); const Newton3 = TensorStatic(f32, .{ .M = 1, .L = 1, .T = -2 }, .{}, &.{3}); @@ -1100,7 +1100,7 @@ test "Vector contract — dot product (rank-1 * rank-1)" { try std.testing.expectEqual(-2, @TypeOf(work).dims.get(.T)); } -test "Vector contract — matrix multiply (rank-2 * rank-2)" { +test "TensorStatic | Vector contract — matrix multiply (rank-2 * rank-2)" { const A = TensorStatic(f32, .{}, .{}, &.{ 2, 3 }); const B = TensorStatic(f32, .{}, .{}, &.{ 3, 2 }); @@ -1114,7 +1114,7 @@ test "Vector contract — matrix multiply (rank-2 * rank-2)" { try std.testing.expectEqual(154, c.data[TensorStatic(f32, .{}, .{}, &.{ 2, 2 }).idx(.{ 1, 1 })]); } -test "Vector Abs, Pow, Sqrt and Product" { +test "TensorStatic | Vector Abs, Pow, Sqrt and Product" { const Meter3 = TensorStatic(f32, .{ .L = 1 }, .{}, &.{3}); const v1 = Meter3{ .data = .{ -2.0, 3.0, -4.0 } }; @@ -1137,7 +1137,7 @@ test "Vector Abs, Pow, Sqrt and Product" { try std.testing.expectEqual(1, @TypeOf(sqrted).dims.get(.L)); } -test "Vector eq broadcast on dimensionless" { +test "TensorStatic | Vector eq broadcast on dimensionless" { const DimLess3 = TensorStatic(i32, .{}, .{}, &.{3}); const v = DimLess3{ .data = .{ 1, 2, 3 } }; @@ -1147,7 +1147,7 @@ test "Vector eq broadcast on dimensionless" { try std.testing.expectEqual(false, eq_res[2]); } -test "Tensor idx helper and matrix access" { +test "TensorStatic | Tensor idx helper and matrix access" { const Mat3x3 = TensorStatic(f32, .{}, .{}, &.{ 3, 3 }); var m: Mat3x3 = Mat3x3.zero; m.data[Mat3x3.idx(.{ 0, 0 })] = 1.0; @@ -1160,7 +1160,7 @@ test "Tensor idx helper and matrix access" { try std.testing.expectEqual(0.0, m.data[1]); } -test "Tensor strides_arr correctness" { +test "TensorStatic | Tensor strides_arr correctness" { const T1 = TensorStatic(f32, .{}, .{}, &.{3}); const T2 = TensorStatic(f32, .{}, .{}, &.{ 3, 4 }); const T3 = TensorStatic(f32, .{}, .{}, &.{ 2, 3, 4 }); @@ -1173,7 +1173,7 @@ test "Tensor strides_arr correctness" { try std.testing.expectEqual(1, T3.strides_arr[2]); } -test "Slice 1D basic" { +test "TensorStatic | Slice 1D basic" { const Vec = TensorStatic(i32, .{}, .{}, &.{5}); var v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; const s = v.slice(.{.{ .start = 1, .end = 4 }}); @@ -1183,7 +1183,7 @@ test "Slice 1D basic" { try std.testing.expectEqual(40, s.data[2]); } -test "Slice 1D full range" { +test "TensorStatic | Slice 1D full range" { const Vec = TensorStatic(f32, .{}, .{}, &.{4}); const v = Vec{ .data = .{ 1.0, 2.0, 3.0, 4.0 } }; const s = v.slice(.{.{ .start = 0, .end = 4 }}); @@ -1191,7 +1191,7 @@ test "Slice 1D full range" { inline for (0..4) |i| try std.testing.expectEqual(v.data[i], s.data[i]); } -test "Slice 1D single element" { +test "TensorStatic | Slice 1D single element" { const Vec = TensorStatic(i64, .{}, .{}, &.{6}); const v = Vec{ .data = .{ 5, 10, 15, 20, 25, 30 } }; const s = v.slice(.{.{ .start = 3, .end = 4 }}); @@ -1199,7 +1199,7 @@ test "Slice 1D single element" { try std.testing.expectEqual(20, s.data[0]); } -test "Slice 1D preserves dims and scales" { +test "TensorStatic | Slice 1D preserves dims and scales" { const Meter = TensorStatic(i128, .{ .L = 1 }, .{ .L = .k }, &.{5}); const v = Meter{ .data = .{ 1, 2, 3, 4, 5 } }; const s = v.slice(.{.{ .start = 0, .end = 3 }}); @@ -1208,7 +1208,7 @@ test "Slice 1D preserves dims and scales" { try std.testing.expectEqual(Meter.scales.get(.L), S.scales.get(.L)); } -test "Slice 2D rows" { +test "TensorStatic | Slice 2D rows" { const Mat = TensorStatic(i32, .{}, .{}, &.{ 4, 3 }); const m = Mat{ .data = .{ 1, 2, 3, @@ -1227,7 +1227,7 @@ test "Slice 2D rows" { try std.testing.expectEqual(9, s.data[5]); } -test "Slice 2D cols" { +test "TensorStatic | Slice 2D cols" { const Mat = TensorStatic(i32, .{}, .{}, &.{ 3, 4 }); const m = Mat{ .data = .{ 1, 2, 3, 4, @@ -1247,7 +1247,7 @@ test "Slice 2D cols" { try std.testing.expectEqual(11, s.data[5]); } -test "Slice 2D subblock" { +test "TensorStatic | Slice 2D subblock" { const Mat = TensorStatic(f64, .{}, .{}, &.{ 4, 4 }); const m = Mat{ .data = .{ 1, 2, 3, 4, @@ -1264,7 +1264,7 @@ test "Slice 2D subblock" { try std.testing.expectApproxEqAbs(11.0, s.data[3], 1e-9); } -test "Slice then add" { +test "TensorStatic | Slice then add" { const Meter = TensorStatic(i32, .{ .L = 1 }, .{}, &.{5}); const a = Meter{ .data = .{ 1, 2, 3, 4, 5 } }; const b = Meter{ .data = .{ 10, 20, 30, 40, 50 } }; @@ -1276,7 +1276,7 @@ test "Slice then add" { try std.testing.expectEqual(53, r.data[2]); // 3+50 } -test "Slice then scale convert" { +test "TensorStatic | Slice then scale convert" { const KiloMeter = TensorStatic(i64, .{ .L = 1 }, .{ .L = .k }, &.{4}); const Meter = TensorStatic(i64, .{ .L = 1 }, .{}, &.{2}); const v = KiloMeter{ .data = .{ 1, 2, 3, 4 } }; @@ -1286,7 +1286,7 @@ test "Slice then scale convert" { try std.testing.expectEqual(3000, converted.data[1]); } -test "Slice 1D negative start" { +test "TensorStatic | Slice 1D negative start" { const Vec = TensorStatic(i32, .{}, .{}, &.{5}); const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; const s = v.slice(.{.{ .start = -3, .end = 5 }}); // [2,5) → 30,40,50 @@ -1296,7 +1296,7 @@ test "Slice 1D negative start" { try std.testing.expectEqual(50, s.data[2]); } -test "Slice 1D negative end" { +test "TensorStatic | Slice 1D negative end" { const Vec = TensorStatic(i32, .{}, .{}, &.{5}); const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; const s = v.slice(.{.{ .start = 1, .end = -1 }}); // [1,4) → 20,30,40 @@ -1306,7 +1306,7 @@ test "Slice 1D negative end" { try std.testing.expectEqual(40, s.data[2]); } -test "Slice 1D both negative" { +test "TensorStatic | Slice 1D both negative" { const Vec = TensorStatic(i64, .{}, .{}, &.{6}); const v = Vec{ .data = .{ 5, 10, 15, 20, 25, 30 } }; const s = v.slice(.{.{ .start = -4, .end = -1 }}); // [2,5) → 15,20,25 @@ -1316,7 +1316,7 @@ test "Slice 1D both negative" { try std.testing.expectEqual(25, s.data[2]); } -test "Slice 1D null start" { +test "TensorStatic | Slice 1D null start" { const Vec = TensorStatic(i32, .{}, .{}, &.{5}); const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; const s = v.slice(.{.{ .end = -2 }}); // [:-2] → 10,20,30 @@ -1326,7 +1326,7 @@ test "Slice 1D null start" { try std.testing.expectEqual(30, s.data[2]); } -test "Slice 1D null end" { +test "TensorStatic | Slice 1D null end" { const Vec = TensorStatic(i32, .{}, .{}, &.{5}); const v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; const s = v.slice(.{.{ .start = -3 }}); // [-3:] → 30,40,50 @@ -1336,7 +1336,7 @@ test "Slice 1D null end" { try std.testing.expectEqual(50, s.data[2]); } -test "Slice 2D negative & null indices" { +test "TensorStatic | Slice 2D negative & null indices" { const Mat = TensorStatic(i32, .{}, .{}, &.{ 4, 4 }); const m = Mat{ .data = .{ 1, 2, 3, 4,