TensorAlloc add and to compilable but still error for basic add test

This commit is contained in:
adrien 2026-05-14 22:25:35 +02:00
parent f702c1e09a
commit e6d0f62929
2 changed files with 222 additions and 128 deletions

View File

@ -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);
if (comptime ratio == 1.0 and T == DestT)
return .{ .data = vec.data };
// 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);
return .{
.data = if (comptime T_info == .int and Dest_info == .int)
@as(DestVec, @intCast(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(DestVec, @floatCast(vec.data))
@as(DestT, @floatCast(self.data[0]))
else if (comptime T_info == .int and Dest_info == .float)
@as(DestVec, @floatFromInt(vec.data))
@as(DestT, @floatFromInt(self.data[0]))
else if (comptime T_info == .float and Dest_info == .int)
@as(DestVec, @intFromFloat(vec.data))
@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);
const new = try Dest.init(alloc);
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]);
// }

View File

@ -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,