diff --git a/src/TensorAlloc.zig b/src/TensorAlloc.zig index eb4c93f..26ada40 100644 --- a/src/TensorAlloc.zig +++ b/src/TensorAlloc.zig @@ -301,6 +301,68 @@ pub fn Tensor( return Self{ .data = vec_ptr }; } + /// Extract sub-tensor by half-open ranges [start, end) per axis. + /// All bounds comptime. Dims and scales preserved. + /// Negative indices count from end: -1 = last element. + pub inline fn slice( + self: *const Self, + alloc: Allocator, + comptime ranges: [rank]struct { start: ?isize = null, end: ?isize = null }, + ) !blk: { + var ns: [rank]comptime_int = undefined; + for (0..rank) |i| { + const dim = @as(isize, @intCast(shape[i])); + const s: isize = blk2: { + const raw = ranges[i].start orelse 0; + break :blk2 if (raw < 0) raw + dim else raw; + }; + const e: isize = blk2: { + const raw = ranges[i].end orelse dim; + break :blk2 if (raw < 0) raw + dim else raw; + }; + if (s < 0) @compileError("slice: start out of bounds after normalization"); + if (e < 0) @compileError("slice: end out of bounds after normalization"); + if (s >= e) @compileError("slice: start must be < end"); + if (e > dim) @compileError("slice: end exceeds shape"); + ns[i] = e - s; + } + const new_shape: [rank]comptime_int = ns; + break :blk Tensor(T, dims.argsOpt(), scales.argsOpt(), &new_shape); + } { + const new_shape: [rank]comptime_int = comptime blk: { + var ns: [rank]comptime_int = undefined; + for (0..rank) |i| { + const dim = @as(isize, @intCast(shape[i])); + const raw_s = ranges[i].start orelse 0; + const raw_e = ranges[i].end orelse dim; + const s: isize = if (raw_s < 0) raw_s + dim else raw_s; + const e: isize = if (raw_e < 0) raw_e + dim else raw_e; + ns[i] = e - s; + } + break :blk ns; + }; + const ResultType = Tensor(T, dims.argsOpt(), scales.argsOpt(), &new_shape); + const DestVec = @Vector(ResultType.total, T); + + const src: [total]T = self.data.*; + var dst: [ResultType.total]T = undefined; + for (0..ResultType.total) |flat| { + var src_flat: usize = 0; + inline for (0..rank) |i| { + const dim = @as(isize, @intCast(shape[i])); + const raw_s = ranges[i].start orelse 0; + const s: isize = if (raw_s < 0) raw_s + dim else raw_s; + const coord = (flat / ResultType.strides_arr[i]) % new_shape[i]; + src_flat += (coord + @as(usize, @intCast(s))) * strides_arr[i]; + } + dst[flat] = src[src_flat]; + } + + const vec_ptr = try alloc.create(DestVec); + vec_ptr.* = dst; + return ResultType{ .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). @@ -1389,262 +1451,295 @@ test "TensorAlloc | Tensor strides_arr correctness" { try std.testing.expectEqual(4, T3.strides_arr[1]); try std.testing.expectEqual(1, T3.strides_arr[2]); } -// -// test "TensorAlloc | Slice 1D basic" { -// const Vec = Tensor(i32, .{}, .{}, &.{5}); -// var v = Vec{ .data = .{ 10, 20, 30, 40, 50 } }; -// const s = v.slice(.{.{ .start = 1, .end = 4 }}); -// 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 "TensorAlloc | Slice 1D full range" { -// const Vec = Tensor(f32, .{}, .{}, &.{4}); -// const v = Vec{ .data = .{ 1.0, 2.0, 3.0, 4.0 } }; -// const s = v.slice(.{.{ .start = 0, .end = 4 }}); -// try std.testing.expectEqual(4, @TypeOf(s).total); -// inline for (0..4) |i| try std.testing.expectEqual(v.data[i], s.data[i]); -// } -// -// test "TensorAlloc | Slice 1D single element" { -// const Vec = Tensor(i64, .{}, .{}, &.{6}); -// const v = Vec{ .data = .{ 5, 10, 15, 20, 25, 30 } }; -// const s = v.slice(.{.{ .start = 3, .end = 4 }}); -// try std.testing.expectEqual(1, @TypeOf(s).total); -// try std.testing.expectEqual(20, s.data[0]); -// } -// -// test "TensorAlloc | Slice 1D preserves dims and scales" { -// const Meter = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{5}); -// const v = Meter{ .data = .{ 1, 2, 3, 4, 5 } }; -// const s = v.slice(.{.{ .start = 0, .end = 3 }}); -// const S = @TypeOf(s); -// try std.testing.expectEqual(1, S.dims.get(.L)); -// try std.testing.expectEqual(Meter.scales.get(.L), S.scales.get(.L)); -// } -// -// test "TensorAlloc | Slice 2D rows" { -// const Mat = Tensor(i32, .{}, .{}, &.{ 4, 3 }); -// const m = Mat{ .data = .{ -// 1, 2, 3, -// 4, 5, 6, -// 7, 8, 9, -// 10, 11, 12, -// } }; -// // rows [1,3), all cols -// const s = m.slice(.{ .{ .start = 1, .end = 3 }, .{ .start = 0, .end = 3 } }); -// try std.testing.expectEqual(6, @TypeOf(s).total); -// try std.testing.expectEqual(4, s.data[0]); -// try std.testing.expectEqual(5, s.data[1]); -// try std.testing.expectEqual(6, s.data[2]); -// try std.testing.expectEqual(7, s.data[3]); -// try std.testing.expectEqual(8, s.data[4]); -// try std.testing.expectEqual(9, s.data[5]); -// } -// -// test "TensorAlloc | Slice 2D cols" { -// const Mat = Tensor(i32, .{}, .{}, &.{ 3, 4 }); -// const m = Mat{ .data = .{ -// 1, 2, 3, 4, -// 5, 6, 7, 8, -// 9, 10, 11, 12, -// } }; -// // all rows, cols [1,3) -// const s = m.slice(.{ .{ .start = 0, .end = 3 }, .{ .start = 1, .end = 3 } }); -// const S = @TypeOf(s); -// try std.testing.expectEqual(3, S.shape[0]); -// try std.testing.expectEqual(2, S.shape[1]); -// try std.testing.expectEqual(2, s.data[0]); -// try std.testing.expectEqual(3, s.data[1]); -// try std.testing.expectEqual(6, s.data[2]); -// try std.testing.expectEqual(7, s.data[3]); -// try std.testing.expectEqual(10, s.data[4]); -// try std.testing.expectEqual(11, s.data[5]); -// } -// -// test "TensorAlloc | Slice 2D subblock" { -// const Mat = Tensor(f64, .{}, .{}, &.{ 4, 4 }); -// const m = Mat{ .data = .{ -// 1, 2, 3, 4, -// 5, 6, 7, 8, -// 9, 10, 11, 12, -// 13, 14, 15, 16, -// } }; -// // centre 2x2 -// const s = m.slice(.{ .{ .start = 1, .end = 3 }, .{ .start = 1, .end = 3 } }); -// try std.testing.expectEqual(4, @TypeOf(s).total); -// try std.testing.expectApproxEqAbs(6.0, s.data[0], 1e-9); -// try std.testing.expectApproxEqAbs(7.0, s.data[1], 1e-9); -// try std.testing.expectApproxEqAbs(10.0, s.data[2], 1e-9); -// try std.testing.expectApproxEqAbs(11.0, s.data[3], 1e-9); -// } -// -// test "TensorAlloc | Slice then add" { -// const Meter = Tensor(i32, .{ .L = 1 }, .{}, &.{5}); -// const a = Meter{ .data = .{ 1, 2, 3, 4, 5 } }; -// const b = Meter{ .data = .{ 10, 20, 30, 40, 50 } }; -// const sa = a.slice(.{.{ .start = 0, .end = 3 }}); -// const sb = b.slice(.{.{ .start = 2, .end = 5 }}); -// const r = sa.add(sb); -// try std.testing.expectEqual(31, r.data[0]); // 1+30 -// try std.testing.expectEqual(42, r.data[1]); // 2+40 -// try std.testing.expectEqual(53, r.data[2]); // 3+50 -// } -// -// test "TensorAlloc | Slice then scale convert" { -// const KiloMeter = Tensor(i64, .{ .L = 1 }, .{ .L = .k }, &.{4}); -// const Meter = Tensor(i64, .{ .L = 1 }, .{}, &.{2}); -// const v = KiloMeter{ .data = .{ 1, 2, 3, 4 } }; -// const s = v.slice(.{.{ .start = 1, .end = 3 }}); // {2, 3} km -// const converted = s.to(Meter); -// try std.testing.expectEqual(2000, converted.data[0]); -// try std.testing.expectEqual(3000, converted.data[1]); -// } -// -// test "TensorAlloc | Slice 1D negative start" { -// const Vec = Tensor(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 "TensorAlloc | Slice 1D negative end" { -// const Vec = Tensor(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 "TensorAlloc | Slice 1D both negative" { -// const Vec = Tensor(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 "TensorAlloc | Slice 1D null start" { -// const Vec = Tensor(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 "TensorAlloc | Slice 1D null end" { -// const Vec = Tensor(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 "TensorAlloc | Slice 2D negative & null indices" { -// const Mat = Tensor(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]); -// } -// -// test "TensorAlloc | RLS heap basic" { -// const allocator = std.testing.allocator; -// const Meter = Tensor(f32, .{ .L = 1 }, .{}, &.{1}); -// -// const c = try allocator.create(Meter); -// defer allocator.destroy(c); -// c.* = Meter.splat(42.0); -// -// try std.testing.expectEqual(42.0, c.data[0]); -// } -// -// test "TensorAlloc | RLS heap add vec3" { -// const allocator = std.testing.allocator; -// const Meter = Tensor(f32, .{ .L = 1 }, .{}, &.{3}); -// -// const a = Meter{ .data = .{ 1.0, 2.0, 3.0 } }; -// const b = Meter{ .data = .{ 10.0, 20.0, 30.0 } }; -// -// const c = try allocator.create(@TypeOf(a.add(b))); -// defer allocator.destroy(c); -// c.* = a.add(b); // RLS: direct into heap -// -// try std.testing.expectEqual(11.0, c.data[0]); -// try std.testing.expectEqual(22.0, c.data[1]); -// try std.testing.expectEqual(33.0, c.data[2]); -// } -// -// test "TensorAlloc | RLS heap cross-scale add" { -// const allocator = std.testing.allocator; -// const Meter = Tensor(i64, .{ .L = 1 }, .{}, &.{1}); -// const KiloMeter = Tensor(i64, .{ .L = 1 }, .{ .L = .k }, &.{1}); -// -// const c = try allocator.create(@TypeOf(Meter.splat(0).add(KiloMeter.splat(0)))); -// defer allocator.destroy(c); -// c.* = Meter.splat(500).add(KiloMeter.splat(1)); // 500 + 1000 = 1500m -// -// try std.testing.expectEqual(1500, c.data[0]); -// } -// -// test "TensorAlloc | RLS heap matmul" { -// const allocator = std.testing.allocator; -// const A = Tensor(f32, .{}, .{}, &.{ 2, 3 }); -// const B = Tensor(f32, .{}, .{}, &.{ 3, 2 }); -// -// const a = A{ .data = .{ 1, 2, 3, 4, 5, 6 } }; -// const b = B{ .data = .{ 7, 8, 9, 10, 11, 12 } }; -// -// const c = try allocator.create(@TypeOf(a.contract(b, 1, 0))); -// defer allocator.destroy(c); -// c.* = a.contract(b, 1, 0); // RLS: direct into heap -// -// const R = Tensor(f32, .{}, .{}, &.{ 2, 2 }); -// try std.testing.expectEqual(58.0, c.data[R.idx(.{ 0, 0 })]); -// try std.testing.expectEqual(64.0, c.data[R.idx(.{ 0, 1 })]); -// try std.testing.expectEqual(139.0, c.data[R.idx(.{ 1, 0 })]); -// try std.testing.expectEqual(154.0, c.data[R.idx(.{ 1, 1 })]); -// } -// -// test "TensorAlloc | RLS heap big tensor (near 1MB limit)" { -// const allocator = std.testing.allocator; -// // 31249 * 32bits = 999_968 bits — just under compile error -// const BigVec = Tensor(f32, .{ .L = 1 }, .{}, &.{31249}); -// -// const a = try allocator.create(BigVec); -// defer allocator.destroy(a); -// a.* = BigVec.splat(1.0); -// -// const b = try allocator.create(BigVec); -// defer allocator.destroy(b); -// b.* = BigVec.splat(2.0); -// -// const c = try allocator.create(@TypeOf(a.add(b.*))); -// defer allocator.destroy(c); -// c.* = a.add(b.*); // RLS: ~125KB result direct into heap -// -// try std.testing.expectEqual(3.0, c.data[0]); -// try std.testing.expectEqual(3.0, c.data[31248]); -// } + +test "TensorAlloc | Slice 1D basic" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const Vec = Tensor(i32, .{}, .{}, &.{5}); + var v = try Vec.load(alloc, &.{ 10, 20, 30, 40, 50 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = 1, .end = 4 }}); + defer s.deinit(alloc); + + 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 "TensorAlloc | Slice 1D full range" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(f32, .{}, .{}, &.{4}); + const v = try Vec.load(alloc, &.{ 1.0, 2.0, 3.0, 4.0 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = 0, .end = 4 }}); + defer s.deinit(alloc); + + try std.testing.expectEqual(4, @TypeOf(s).total); + inline for (0..4) |i| try std.testing.expectEqual(v.data[i], s.data[i]); +} + +test "TensorAlloc | Slice 1D single element" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(i64, .{}, .{}, &.{6}); + const v = try Vec.load(alloc, &.{ 5, 10, 15, 20, 25, 30 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = 3, .end = 4 }}); + defer s.deinit(alloc); + + try std.testing.expectEqual(1, @TypeOf(s).total); + try std.testing.expectEqual(20, s.data[0]); +} + +test "TensorAlloc | Slice 1D preserves dims and scales" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Meter = Tensor(i128, .{ .L = 1 }, .{ .L = .k }, &.{5}); + const v = try Meter.load(alloc, &.{ 1, 2, 3, 4, 5 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = 0, .end = 3 }}); + defer s.deinit(alloc); + + const S = @TypeOf(s); + try std.testing.expectEqual(1, S.dims.get(.L)); + try std.testing.expectEqual(Meter.scales.get(.L), S.scales.get(.L)); +} + +test "TensorAlloc | Slice 2D rows" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Mat = Tensor(i32, .{}, .{}, &.{ 4, 3 }); + const m = try Mat.load(alloc, &.{ + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, + 10, 11, 12, + }); + defer m.deinit(alloc); + + // rows [1,3), all cols + const s = try m.slice(alloc, .{ .{ .start = 1, .end = 3 }, .{ .start = 0, .end = 3 } }); + defer s.deinit(alloc); + + try std.testing.expectEqual(6, @TypeOf(s).total); + try std.testing.expectEqual(4, s.data[0]); + try std.testing.expectEqual(5, s.data[1]); + try std.testing.expectEqual(6, s.data[2]); + try std.testing.expectEqual(7, s.data[3]); + try std.testing.expectEqual(8, s.data[4]); + try std.testing.expectEqual(9, s.data[5]); +} + +test "TensorAlloc | Slice 2D cols" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Mat = Tensor(i32, .{}, .{}, &.{ 3, 4 }); + const m = try Mat.load(alloc, &.{ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + }); + defer m.deinit(alloc); + + // all rows, cols [1,3) + const s = try m.slice(alloc, .{ .{ .start = 0, .end = 3 }, .{ .start = 1, .end = 3 } }); + defer s.deinit(alloc); + + const S = @TypeOf(s); + try std.testing.expectEqual(3, S.shape[0]); + try std.testing.expectEqual(2, S.shape[1]); + try std.testing.expectEqual(2, s.data[0]); + try std.testing.expectEqual(3, s.data[1]); + try std.testing.expectEqual(6, s.data[2]); + try std.testing.expectEqual(7, s.data[3]); + try std.testing.expectEqual(10, s.data[4]); + try std.testing.expectEqual(11, s.data[5]); +} + +test "TensorAlloc | Slice 2D subblock" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Mat = Tensor(f64, .{}, .{}, &.{ 4, 4 }); + const m = try Mat.load(alloc, &.{ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + }); + defer m.deinit(alloc); + + // centre 2x2 + const s = try m.slice(alloc, .{ .{ .start = 1, .end = 3 }, .{ .start = 1, .end = 3 } }); + defer s.deinit(alloc); + + try std.testing.expectEqual(4, @TypeOf(s).total); + try std.testing.expectApproxEqAbs(6.0, s.data[0], 1e-9); + try std.testing.expectApproxEqAbs(7.0, s.data[1], 1e-9); + try std.testing.expectApproxEqAbs(10.0, s.data[2], 1e-9); + try std.testing.expectApproxEqAbs(11.0, s.data[3], 1e-9); +} + +test "TensorAlloc | Slice then add" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Meter = Tensor(i32, .{ .L = 1 }, .{}, &.{5}); + const a = try Meter.load(alloc, &.{ 1, 2, 3, 4, 5 }); + defer a.deinit(alloc); + const b = try Meter.load(alloc, &.{ 10, 20, 30, 40, 50 }); + defer b.deinit(alloc); + + const sa = try a.slice(alloc, .{.{ .start = 0, .end = 3 }}); + defer sa.deinit(alloc); + const sb = try b.slice(alloc, .{.{ .start = 2, .end = 5 }}); + defer sb.deinit(alloc); + + const r = try sa.add(alloc, sb); + defer r.deinit(alloc); + + try std.testing.expectEqual(31, r.data[0]); // 1+30 + try std.testing.expectEqual(42, r.data[1]); // 2+40 + try std.testing.expectEqual(53, r.data[2]); // 3+50 +} + +test "TensorAlloc | Slice then scale convert" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const KiloMeter = Tensor(i64, .{ .L = 1 }, .{ .L = .k }, &.{4}); + const Meter = Tensor(i64, .{ .L = 1 }, .{}, &.{2}); + const v = try KiloMeter.load(alloc, &.{ 1, 2, 3, 4 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = 1, .end = 3 }}); // {2, 3} km + defer s.deinit(alloc); + + const converted = try s.to(alloc, Meter); + defer converted.deinit(alloc); + + try std.testing.expectEqual(2000, converted.data[0]); + try std.testing.expectEqual(3000, converted.data[1]); +} + +test "TensorAlloc | Slice 1D negative start" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(i32, .{}, .{}, &.{5}); + const v = try Vec.load(alloc, &.{ 10, 20, 30, 40, 50 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = -3, .end = 5 }}); // [2,5) → 30,40,50 + defer s.deinit(alloc); + + 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 "TensorAlloc | Slice 1D negative end" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(i32, .{}, .{}, &.{5}); + const v = try Vec.load(alloc, &.{ 10, 20, 30, 40, 50 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = 1, .end = -1 }}); // [1,4) → 20,30,40 + defer s.deinit(alloc); + + 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 "TensorAlloc | Slice 1D both negative" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(i64, .{}, .{}, &.{6}); + const v = try Vec.load(alloc, &.{ 5, 10, 15, 20, 25, 30 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = -4, .end = -1 }}); // [2,5) → 15,20,25 + defer s.deinit(alloc); + + 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 "TensorAlloc | Slice 1D null start" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(i32, .{}, .{}, &.{5}); + const v = try Vec.load(alloc, &.{ 10, 20, 30, 40, 50 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .end = -2 }}); // [:-2] → 10,20,30 + defer s.deinit(alloc); + + 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 "TensorAlloc | Slice 1D null end" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Vec = Tensor(i32, .{}, .{}, &.{5}); + const v = try Vec.load(alloc, &.{ 10, 20, 30, 40, 50 }); + defer v.deinit(alloc); + + const s = try v.slice(alloc, .{.{ .start = -3 }}); // [-3:] → 30,40,50 + defer s.deinit(alloc); + + 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 "TensorAlloc | Slice 2D negative & null indices" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + const Mat = Tensor(i32, .{}, .{}, &.{ 4, 4 }); + const m = try Mat.load(alloc, &.{ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16, + }); + defer m.deinit(alloc); + + // last 2 rows, last 2 cols → same as subblock test [2,4)x[2,4) + const s = try m.slice(alloc, .{ .{ .start = -2, .end = 4 }, .{ .start = -2 } }); + defer s.deinit(alloc); + + 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]); +}