From 0ef19e18de562b3ba5f3e24613ec8207109122c2 Mon Sep 17 00:00:00 2001 From: adrien Date: Mon, 25 May 2026 01:52:12 +0200 Subject: [PATCH] Working other base operation (sub, mul, div, ect) --- src/TensorAlloc.zig | 552 ++++++++++++++++++++++++++++++++------------ 1 file changed, 399 insertions(+), 153 deletions(-) diff --git a/src/TensorAlloc.zig b/src/TensorAlloc.zig index 67d514e..e1b8dc6 100644 --- a/src/TensorAlloc.zig +++ b/src/TensorAlloc.zig @@ -98,6 +98,196 @@ pub fn Tensor( return TargetType{ .data = vec_ptr }; } + /// Element-wise sub. Dimensions must match; scales resolve to finer. + /// RHS must have the same shape as self, or total == 1 (broadcast). + pub inline fn sub(self: *const Self, alloc: Allocator, rhs: anytype) !Tensor( + T, + dims.argsOpt(), + sh.finerScales(Self, @TypeOf(rhs)).argsOpt(), + shape, + ) { + const RhsType = @TypeOf(rhs); + if (comptime !sh.isTensor(RhsType)) + @compileError("rhs can only be a Tensor "); + if (comptime !dims.eql(RhsType.dims)) + @compileError("Dimension mismatch in sub: " ++ dims.str() ++ " vs " ++ RhsType.dims.str()); + if (comptime RhsType.total != 1 and !sh.shapeEql(shape, RhsType.shape)) + @compileError("Shape mismatch in sub: element-wise operations require identical shapes, or a scalar RHS."); + + const TargetType = Tensor(T, dims.argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + const l: TargetType = try self.to(alloc, TargetType); + defer l.deinit(alloc); + const r: TargetType = try rhs.to(alloc, TargetType); + defer r.deinit(alloc); + + const result_vec = if (comptime sh.isInt(T)) + l.data.* -| r.data.* + else + l.data.* - r.data.*; + + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + + return TargetType{ .data = vec_ptr }; + } + + /// Element-wise multiply. Dimension exponents summed. + /// Shape {1} RHS is automatically broadcast across all elements. + pub inline fn mul(self: *const Self, alloc: Allocator, rhs: anytype) !Tensor( + T, + dims.add(@TypeOf(rhs).dims).argsOpt(), + sh.finerScales(Self, @TypeOf(rhs)).argsOpt(), + shape, + ) { + const RhsType = @TypeOf(rhs); + if (comptime !sh.isTensor(RhsType)) + @compileError("rhs can only be a Tensor "); + if (comptime RhsType.total != 1 and !sh.shapeEql(shape, RhsType.shape)) + @compileError("Shape mismatch in mul: element-wise operations require identical shapes, or a scalar RHS."); + + const SelfNorm = Tensor(T, dims.argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + const RhsNorm = Tensor(T, RhsType.dims.argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + const TargetType = Tensor(T, dims.add(RhsType.dims).argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + + const l: SelfNorm = try self.to(alloc, SelfNorm); + defer l.deinit(alloc); + const r: RhsNorm = try rhs.to(alloc, RhsNorm); + defer r.deinit(alloc); + + const result_vec = if (comptime sh.isInt(T)) + l.data.* *| r.data.* + else + l.data.* * r.data.*; + + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + + return TargetType{ .data = vec_ptr }; + } + + /// Element-wise divide. Dimension exponents subtracted. + /// Shape {1} RHS is automatically broadcast across all elements. + pub inline fn div(self: *const Self, alloc: Allocator, rhs: anytype) !Tensor( + T, + dims.sub(@TypeOf(rhs).dims).argsOpt(), + sh.finerScales(Self, @TypeOf(rhs)).argsOpt(), + shape, + ) { + const RhsType = @TypeOf(rhs); + if (comptime !sh.isTensor(RhsType)) + @compileError("rhs can only be a Tensor "); + if (comptime RhsType.total != 1 and !sh.shapeEql(shape, RhsType.shape)) + @compileError("Shape mismatch in div: element-wise operations require identical shapes, or a scalar RHS."); + + const SelfNorm = Tensor(T, dims.argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + const RhsNorm = Tensor(T, RhsType.dims.argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + const TargetType = Tensor(T, dims.sub(RhsType.dims).argsOpt(), sh.finerScales(Self, RhsType).argsOpt(), shape); + + const l: SelfNorm = try self.to(alloc, SelfNorm); + defer l.deinit(alloc); + const r: RhsNorm = try rhs.to(alloc, RhsNorm); + defer r.deinit(alloc); + + const result_vec = if (comptime sh.isInt(T)) + @divTrunc(l.data.*, r.data.*) + else + l.data.* / r.data.*; + + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + + return TargetType{ .data = vec_ptr }; + } + + /// Absolute value of every element. + pub inline fn abs(self: *const Self, alloc: Allocator) !Self { + const result_vec = @as(Vec, @bitCast(@abs(self.data.*))); + + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + + return Self{ .data = vec_ptr }; + } + + /// Raise every element to a comptime integer exponent. + pub inline fn pow(self: *const Self, alloc: Allocator, comptime exp: comptime_int) !Tensor( + T, + dims.scale(exp).argsOpt(), + scales.argsOpt(), + shape, + ) { + if (comptime exp < 0) @compileError("Pow only support exp >= 0"); + + const TargetType = Tensor(T, dims.scale(exp).argsOpt(), scales.argsOpt(), shape); + + if (comptime exp == 0) { + const result_vec: Vec = @splat(1); + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + return TargetType{ .data = vec_ptr }; + } + + if (comptime exp == 1) { + // Copy allocation to ensure `.deinit(alloc)` works cleanly for the caller + const vec_ptr = try alloc.create(Vec); + vec_ptr.* = self.data.*; + return TargetType{ .data = vec_ptr }; + } + + var data: Vec = self.data.*; + for (0..exp - 1) |_| + data = data * self.data.*; + + const vec_ptr = try alloc.create(@TypeOf(data)); + vec_ptr.* = data; + + return TargetType{ .data = vec_ptr }; + } + + /// Square root of every element. All dimension exponents must be even. + pub inline fn sqrt(self: *const Self, alloc: Allocator) !Tensor( + T, + dims.div(2).argsOpt(), + scales.argsOpt(), + shape, + ) { + if (comptime !dims.isSquare()) + @compileError("Cannot take sqrt of " ++ dims.str() ++ ": exponents must be even."); + + const TargetType = Tensor(T, dims.div(2).argsOpt(), scales.argsOpt(), shape); + + if (comptime @typeInfo(T) == .float) { + const result_vec = @sqrt(self.data.*); + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + return TargetType{ .data = vec_ptr }; + } + + const arr: [total]T = self.data.*; + var res_arr: [total]T = undefined; + const UnsignedT = @Int(.unsigned, @typeInfo(T).int.bits); + + for (0..total) |i| { + const v = arr[i]; + res_arr[i] = if (v < 0) 0 else @as(T, @intCast(std.math.sqrt(@as(UnsignedT, @intCast(v))))); + } + + const vec_ptr = try alloc.create(Vec); + vec_ptr.* = res_arr; + + return TargetType{ .data = vec_ptr }; + } + + /// Negate every element. + pub inline fn negate(self: *const Self, alloc: Allocator) !Self { + const result_vec = -self.data.*; + + const vec_ptr = try alloc.create(@TypeOf(result_vec)); + vec_ptr.* = result_vec; + + return Self{ .data = vec_ptr }; + } + /// Convert to a compatible Tensor type. /// • Dimension mismatch → compile error. /// • Dest.shape must equal self.shape, or total == 1 -> splat to Dest shape (scalar pattern). @@ -349,165 +539,221 @@ test "TensorAlloc | Scalar Add" { 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); + const added3_tmp = try distance3.add(alloc, distance); + const added3 = try added3_tmp.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); + const added4_tmp = try distance4.add(alloc, distance); + const added4 = try added4_tmp.to(alloc, KiloMeter_f); try std.testing.expectApproxEqAbs(2.01, added4.data[0], 0.000001); } -// test "TensorAlloc | Scalar Sub" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); -// const KiloMeter_f = Tensor(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// -// const a = Meter.splat(500); -// const b = Meter.splat(200); -// const diff = a.sub(b); -// try std.testing.expectEqual(300, diff.data[0]); -// const diff2 = b.sub(a); -// try std.testing.expectEqual(-300, diff2.data[0]); -// -// const km_f = KiloMeter_f.splat(2.5); -// const m_f = Meter.splat(500); -// const diff3 = km_f.sub(m_f); -// try std.testing.expectApproxEqAbs(2000, diff3.data[0], 1e-4); -// } -// -// test "TensorAlloc | Scalar MulBy" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); -// const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); -// -// const d = Meter.splat(3); -// const t = Second.splat(4); -// const at = d.mul(t); -// try std.testing.expectEqual(12, at.data[0]); -// try std.testing.expectEqual(1, @TypeOf(at).dims.get(.L)); -// try std.testing.expectEqual(1, @TypeOf(at).dims.get(.T)); -// -// const d2 = Meter.splat(5); -// const area = d.mul(d2); -// try std.testing.expectEqual(15, area.data[0]); -// try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L)); -// } -// -// test "TensorAlloc | Scalar MulBy with scale" { -// const KiloMeter = Tensor(f32, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// const KiloGram = Tensor(f32, .{ .M = 1 }, .{ .M = .k }, &.{1}); -// -// const dist = KiloMeter.splat(2.0); -// const mass = KiloGram.splat(3.0); -// const prod = dist.mul(mass); -// try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.L)); -// try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.M)); -// } -// -// test "TensorAlloc | Scalar MulBy with type change" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// const Second = Tensor(f64, .{ .T = 1 }, .{}, &.{1}); -// const KmSec = Tensor(i64, .{ .L = 1, .T = 1 }, .{ .L = .k }, &.{1}); -// const KmSec_f = Tensor(f32, .{ .L = 1, .T = 1 }, .{ .L = .k }, &.{1}); -// -// const d = Meter.splat(3); -// const t = Second.splat(4); -// -// try std.testing.expectEqual(12, d.mul(t).to(KmSec).data[0]); -// try std.testing.expectApproxEqAbs(12.0, d.mul(t).to(KmSec_f).data[0], 0.0001); -// } -// -// test "TensorAlloc | Scalar MulBy small" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{ .L = .n }, &.{1}); -// const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); -// const d = Meter.splat(3); -// const t = Second.splat(4); -// try std.testing.expectEqual(12, d.mul(t).data[0]); -// } -// -// test "TensorAlloc | Scalar MulBy dimensionless" { -// const DimLess = Tensor(i128, .{}, .{}, &.{1}); -// const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); -// const d = Meter.splat(7); -// const scaled = d.mul(DimLess.splat(3)); -// try std.testing.expectEqual(21, scaled.data[0]); -// } -// -// test "TensorAlloc | Scalar Sqrt" { -// const MeterSquare = Tensor(i128, .{ .L = 2 }, .{}, &.{1}); -// const MeterSquare_f = Tensor(f64, .{ .L = 2 }, .{}, &.{1}); -// -// var d = MeterSquare.splat(9); -// var scaled = d.sqrt(); -// try std.testing.expectEqual(3, scaled.data[0]); -// try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); -// -// d = MeterSquare.splat(-5); -// scaled = d.sqrt(); -// try std.testing.expectEqual(0, scaled.data[0]); -// -// const d2 = MeterSquare_f.splat(20); -// const scaled2 = d2.sqrt(); -// try std.testing.expectApproxEqAbs(4.472135955, scaled2.data[0], 1e-4); -// } -// -// test "TensorAlloc | Scalar Chained: velocity and acceleration" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); -// const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); -// -// const dist = Meter.splat(100); -// const t1 = Second.splat(5); -// const velocity = dist.div(t1); -// try std.testing.expectEqual(20, velocity.data[0]); -// -// const t2 = Second.splat(4); -// const accel = velocity.div(t2); -// try std.testing.expectEqual(5, accel.data[0]); -// } -// -// test "TensorAlloc | Scalar DivBy integer exact" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); -// const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); -// -// const dist = Meter.splat(120); -// const time = Second.splat(4); -// const vel = dist.div(time); -// try std.testing.expectEqual(30, vel.data[0]); -// } -// -// test "TensorAlloc | Scalar Finer scales skip dim 0" { -// const Dimless = Tensor(i128, .{}, .{}, &.{1}); -// const KiloMetre = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// -// const r = Dimless.splat(30); -// const km = KiloMetre.splat(4); -// const vel = r.mul(km); -// try std.testing.expectEqual(120, vel.data[0]); -// try std.testing.expectEqual(Scales.UnitScale.k, @TypeOf(vel).scales.get(.L)); -// } -// -// test "TensorAlloc | Scalar Conversion chain: km -> m -> cm" { -// const KiloMeter = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); -// const CentiMeter = Tensor(i128, .{ .L = 1 }, .{ .L = .c }, &.{1}); -// -// const km = KiloMeter.splat(15); -// const m = km.to(Meter); -// const cm = m.to(CentiMeter); -// try std.testing.expectEqual(15_000, m.data[0]); -// try std.testing.expectEqual(1_500_000, cm.data[0]); -// } -// -// test "TensorAlloc | Scalar Conversion: hours -> minutes -> seconds" { -// const Hour = Tensor(i128, .{ .T = 1 }, .{ .T = .hour }, &.{1}); -// const Minute = Tensor(i128, .{ .T = 1 }, .{ .T = .min }, &.{1}); -// const Second = Tensor(i128, .{ .T = 1 }, .{}, &.{1}); -// -// const h = Hour.splat(1); -// const min = h.to(Minute); -// const sec = min.to(Second); -// try std.testing.expectEqual(60, min.data[0]); -// try std.testing.expectEqual(3600, sec.data[0]); -// } -// +test "TensorAlloc | Scalar Sub" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); + const KiloMeter_f = Tensor(f64, .{ .L = 1 }, .{ .L = .k }, &.{1}); + + const a = try Meter.splat(alloc, 500); + const b = try Meter.splat(alloc, 200); + const diff = try a.sub(alloc, b); + try std.testing.expectEqual(300, diff.data[0]); + const diff2 = try b.sub(alloc, a); + try std.testing.expectEqual(-300, diff2.data[0]); + + const km_f = try KiloMeter_f.splat(alloc, 2.5); + const m_f = try Meter.splat(alloc, 500); + const diff3 = try km_f.sub(alloc, m_f); + try std.testing.expectApproxEqAbs(2000.0, diff3.data[0], 1e-4); +} + +test "TensorAlloc | Scalar MulBy" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); + const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); + + const d = try Meter.splat(alloc, 3); + const t = try Second.splat(alloc, 4); + const at = try d.mul(alloc, t); + try std.testing.expectEqual(12, at.data[0]); + try std.testing.expectEqual(1, @TypeOf(at).dims.get(.L)); + try std.testing.expectEqual(1, @TypeOf(at).dims.get(.T)); + + const d2 = try Meter.splat(alloc, 5); + const area = try d.mul(alloc, d2); + try std.testing.expectEqual(15, area.data[0]); + try std.testing.expectEqual(2, @TypeOf(area).dims.get(.L)); +} + +test "TensorAlloc | Scalar MulBy with scale" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const KiloMeter = Tensor(f32, .{ .L = 1 }, .{ .L = .k }, &.{1}); + const KiloGram = Tensor(f32, .{ .M = 1 }, .{ .M = .k }, &.{1}); + + const dist = try KiloMeter.splat(alloc, 2.0); + const mass = try KiloGram.splat(alloc, 3.0); + const prod = try dist.mul(alloc, mass); + try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.L)); + try std.testing.expectEqual(1, @TypeOf(prod).dims.get(.M)); +} + +test "TensorAlloc | Scalar MulBy with type change" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); + const Second = Tensor(f64, .{ .T = 1 }, .{}, &.{1}); + const KmSec = Tensor(i64, .{ .L = 1, .T = 1 }, .{ .L = .k }, &.{1}); + const KmSec_f = Tensor(f32, .{ .L = 1, .T = 1 }, .{ .L = .k }, &.{1}); + + const d = try Meter.splat(alloc, 3); + const t = try Second.splat(alloc, 4); + const dt_prod = try d.mul(alloc, t); + + const kmsec_val = try dt_prod.to(alloc, KmSec); + try std.testing.expectEqual(12, kmsec_val.data[0]); + + const kmsec_f_val = try dt_prod.to(alloc, KmSec_f); + try std.testing.expectApproxEqAbs(12.0, kmsec_f_val.data[0], 0.0001); +} + +test "TensorAlloc | Scalar MulBy small" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = Tensor(i128, .{ .L = 1 }, .{ .L = .n }, &.{1}); + const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); + const d = try Meter.splat(alloc, 3); + const t = try Second.splat(alloc, 4); + const dt = try d.mul(alloc, t); + try std.testing.expectEqual(12, dt.data[0]); +} + +test "TensorAlloc | Scalar MulBy dimensionless" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const DimLess = Tensor(i128, .{}, .{}, &.{1}); + const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); + const d = try Meter.splat(alloc, 7); + const dl = try DimLess.splat(alloc, 3); + const scaled = try d.mul(alloc, dl); + try std.testing.expectEqual(21, scaled.data[0]); +} + +test "TensorAlloc | Scalar Sqrt" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const MeterSquare = Tensor(i128, .{ .L = 2 }, .{}, &.{1}); + const MeterSquare_f = Tensor(f64, .{ .L = 2 }, .{}, &.{1}); + + var d = try MeterSquare.splat(alloc, 9); + var scaled = try d.sqrt(alloc); + try std.testing.expectEqual(3, scaled.data[0]); + try std.testing.expectEqual(1, @TypeOf(scaled).dims.get(.L)); + + d = try MeterSquare.splat(alloc, -5); + scaled = try d.sqrt(alloc); + try std.testing.expectEqual(0, scaled.data[0]); + + const d2 = try MeterSquare_f.splat(alloc, 20); + const scaled2 = try d2.sqrt(alloc); + try std.testing.expectApproxEqAbs(4.472135955, scaled2.data[0], 1e-4); +} + +test "TensorAlloc | Scalar Chained: velocity and acceleration" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); + const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); + + const dist = try Meter.splat(alloc, 100); + const t1 = try Second.splat(alloc, 5); + const velocity = try dist.div(alloc, t1); + try std.testing.expectEqual(20, velocity.data[0]); + + const t2 = try Second.splat(alloc, 4); + const accel = try velocity.div(alloc, t2); + try std.testing.expectEqual(5, accel.data[0]); +} + +test "TensorAlloc | Scalar DivBy integer exact" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); + const Second = Tensor(f32, .{ .T = 1 }, .{}, &.{1}); + + const dist = try Meter.splat(alloc, 120); + const time = try Second.splat(alloc, 4); + const vel = try dist.div(alloc, time); + try std.testing.expectEqual(30, vel.data[0]); +} + +test "TensorAlloc | Scalar Finer scales skip dim 0" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Dimless = Tensor(i128, .{}, .{}, &.{1}); + const KiloMetre = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); + + const r = try Dimless.splat(alloc, 30); + const km = try KiloMetre.splat(alloc, 4); + const vel = try r.mul(alloc, km); + try std.testing.expectEqual(120, vel.data[0]); + try std.testing.expectEqual(Scales.UnitScale.k, @TypeOf(vel).scales.get(.L)); +} + +test "TensorAlloc | Scalar Conversion chain: km -> m -> cm" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const KiloMeter = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{1}); + const Meter = Tensor(i128, .{ .L = 1 }, .{}, &.{1}); + const CentiMeter = Tensor(i128, .{ .L = 1 }, .{ .L = .c }, &.{1}); + + const km = try KiloMeter.splat(alloc, 15); + const m = try km.to(alloc, Meter); + const cm = try m.to(alloc, CentiMeter); + try std.testing.expectEqual(15_000, m.data[0]); + try std.testing.expectEqual(1_500_000, cm.data[0]); +} + +test "TensorAlloc | Scalar Conversion: hours -> minutes -> seconds" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Hour = Tensor(i128, .{ .T = 1 }, .{ .T = .hour }, &.{1}); + const Minute = Tensor(i128, .{ .T = 1 }, .{ .T = .min }, &.{1}); + const Second = Tensor(i128, .{ .T = 1 }, .{}, &.{1}); + + const h = try Hour.splat(alloc, 1); + const min = try h.to(alloc, Minute); + const sec = try min.to(alloc, Second); + try std.testing.expectEqual(60, min.data[0]); + try std.testing.expectEqual(3600, sec.data[0]); +} + // test "TensorAlloc | Scalar Format" { // const MeterPerSecondSq = Tensor(f32, .{ .L = 1, .T = -2 }, .{ .T = .n }, &.{1}); // const Meter = Tensor(f32, .{ .L = 1 }, .{}, &.{1});