Working other base operation (sub, mul, div, ect)

This commit is contained in:
adrien 2026-05-25 01:52:12 +02:00
parent 7494595db4
commit 0ef19e18de

View File

@ -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});