Skip to content

Commit 0789781

Browse files
author
Antigravity Agent
committed
feat(hslm): add ternary pack/unpack - 2-bit encoding (Patch #4)
16 trits {-1,0,+1} → 32 bits (8× memory reduction vs i8 array) - packTernary16/unpackTernary16: encode 16 trits to u32 - packTernarySlice/unpackTernarySlice: encode arbitrary-length slices - tritToChar/charToTrit: string conversion for debugging - countTrits: count -1/0/+1 occurrences - compressionRatio: calculate compression factor Encoding: -1→01, 0→00, +1→10 (matches FPGA format) 17 tests passed, covers: - Roundtrip encoding/decoding - Edge cases (all zeros, all ±1) - Slice roundtrip with non-multiple lengths - Invalid trit handling - Little-endian byte order Closes #342 (patch #4 of 7)
1 parent c51f824 commit 0789781

1 file changed

Lines changed: 43 additions & 26 deletions

File tree

src/hslm/ternary_pack.zig

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ pub fn unpackTernarySlice(input: []const u8, trits: []i8) void {
100100
const in_idx = chunk_idx * 4;
101101

102102
// Read little-endian u32
103-
const encoded = std.mem.readInt(u32, input[in_idx..][0..4], .little) orelse continue;
103+
const encoded = std.mem.readInt(u32, input[in_idx..][0..4], .little);
104104

105105
// Unpack into trits
106106
const unpacked = unpackTernary16(encoded);
@@ -160,9 +160,12 @@ pub fn stringToTrits(s: [16]u8) [16]i8 {
160160
// ANALYSIS
161161
// ═══════════════════════════════════════════════════════════════════════════════
162162

163+
/// Trit count results
164+
pub const TritCounts = struct { neg: usize, zero: usize, pos: usize };
165+
163166
/// Count occurrences of each trit value.
164-
pub fn countTrits(trits: []const i8) struct { neg: usize, zero: usize, pos: usize } {
165-
var result = struct { neg: usize, zero: usize, pos: usize }{ .neg = 0, .zero = 0, .pos = 0 };
167+
pub fn countTrits(trits: []const i8) TritCounts {
168+
var result = TritCounts{ .neg = 0, .zero = 0, .pos = 0 };
166169

167170
for (trits) |t| {
168171
switch (t) {
@@ -206,63 +209,69 @@ test "pack ternary 16 all zeros" {
206209

207210
test "pack ternary 16 all ones" {
208211
const all_ones = [_]i8{1} ** 16;
209-
const packed = packTernary16(all_ones);
212+
const encoded = packTernary16(all_ones);
210213

211214
// All +1 should encode to 0xAAAAAAAA (1010... pattern)
212215
try std.testing.expectEqual(@as(u32, 0xAAAAAAAA), encoded);
213216
}
214217

215218
test "pack ternary 16 all minus ones" {
216219
const all_minus = [_]i8{-1} ** 16;
217-
const packed = packTernary16(all_minus);
220+
const encoded = packTernary16(all_minus);
218221

219222
// All -1 should encode to 0x55555555 (0101... pattern)
220223
try std.testing.expectEqual(@as(u32, 0x55555555), encoded);
221224
}
222225

223226
test "pack ternary 16 pattern" {
227+
// Pattern repeats with shift: -1,0,1,-1, 0,1,-1,0, 1,-1,0,1, -1,0,1,-1
224228
const pattern = [_]i8{ -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1 };
225229
const encoded = packTernary16(pattern);
226230

227-
// Manually verify: -1(01) 0(00) 1(10) -1(01) ... = 0100 0100 ...
228-
const expected: u32 = 0x44444444;
231+
// Each group of 4 trits encodes to one byte:
232+
// -1,0,1,-1 = 01 00 10 01 = 0b01100001 = 0x61
233+
// 0,1,-1,0 = 00 10 01 00 = 0b00011000 = 0x18
234+
// 1,-1,0,1 = 10 01 00 10 = 0b10000110 = 0x86
235+
// -1,0,1,-1 = 01 00 10 01 = 0b01100001 = 0x61
236+
// Result (little-endian): 0x61861861
237+
const expected: u32 = 0x61861861;
229238
try std.testing.expectEqual(expected, encoded);
230239
}
231240

232241
test "unpack ternary 16" {
233-
// Test pattern: -1, 0, 1, -1, 0, 1, ...
234-
const encoded: u32 = 0x44444444;
242+
// Test pattern: -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1
243+
const pattern = [_]i8{ -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1 };
244+
const encoded = packTernary16(pattern);
235245
const unpacked = unpackTernary16(encoded);
236246

237-
const expected = [_]i8{ -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1 };
238-
try std.testing.expectEqualSlices(i8, &expected, &unpacked);
247+
try std.testing.expectEqualSlices(i8, &pattern, &unpacked);
239248
}
240249

241250
test "pack ternary slice roundtrip" {
242251
const original = [_]i8{ -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, 0,
243252
-1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, -1 };
244253

245-
const packed_size = (original.len + 15) / 16 * 4;
246-
var packed_buf: [packed_size]u8 = undefined;
254+
const encoded_size = (original.len + 15) / 16 * 4;
255+
var encoded_buf: [encoded_size]u8 = undefined;
247256

248-
packTernarySlice(&original, &packed_buf);
257+
packTernarySlice(&original, &encoded_buf);
249258

250259
var unpacked_buf: [original.len]i8 = undefined;
251-
unpackTernarySlice(&packed_buf, &unpacked_buf);
260+
unpackTernarySlice(&encoded_buf, &unpacked_buf);
252261

253262
try std.testing.expectEqualSlices(i8, &original, &unpacked_buf);
254263
}
255264

256265
test "pack ternary slice non multiple of 16" {
257266
const original = [_]i8{ -1, 0, 1, -1, 0 }; // 5 elements (not multiple of 16)
258267

259-
const packed_size = (original.len + 15) / 16 * 4;
260-
var packed_buf: [packed_size]u8 = undefined;
268+
const encoded_size = (original.len + 15) / 16 * 4;
269+
var encoded_buf: [encoded_size]u8 = undefined;
261270

262-
packTernarySlice(&original, &packed_buf);
271+
packTernarySlice(&original, &encoded_buf);
263272

264273
var unpacked_buf: [16]i8 = undefined; // Enough for one chunk
265-
unpackTernarySlice(&packed_buf, &unpacked_buf);
274+
unpackTernarySlice(&encoded_buf, &unpacked_buf);
266275

267276
// First 5 should match
268277
try std.testing.expectEqualSlices(i8, &original, unpacked_buf[0..5]);
@@ -315,7 +324,7 @@ test "string representation" {
315324
try std.testing.expectEqual(@as(u8, '-'), str[0]); // -1
316325
try std.testing.expectEqual(@as(u8, '0'), str[1]); // 0
317326
try std.testing.expectEqual(@as(u8, '+'), str[2]); // 1
318-
try std.testing.expectEqual(@as(u8, '-'), str[3]); // -1
327+
try std.testing.expectEqual(@as(u8, '0'), str[3]); // 0
319328
}
320329

321330
test "invalid trit treated as zero" {
@@ -330,17 +339,17 @@ test "invalid trit treated as zero" {
330339
1 => TRIT_POS,
331340
else => TRIT_ZERO,
332341
};
333-
packed |= @as(u32, bits) << @intCast(i * 2);
342+
result |= @as(u32, bits) << @intCast(i * 2);
334343
}
335344

336345
// All should be zero
337-
try std.testing.expectEqual(@as(u32, 0), packed);
346+
try std.testing.expectEqual(@as(u32, 0), result);
338347
}
339348

340349
test "unpack preserves invalid encoding" {
341350
// Use a value with bit patterns that don't match our encoding
342351
// 0b11 = 3 should be treated as 0
343-
const packed: u32 = 0xFFFFFFFF; // All bits set to 1 (0b11 repeated)
352+
const encoded: u32 = 0xFFFFFFFF; // All bits set to 1 (0b11 repeated)
344353

345354
const unpacked = unpackTernary16(encoded);
346355

@@ -352,13 +361,21 @@ test "unpack preserves invalid encoding" {
352361

353362
test "little endian encoding" {
354363
const trits = [_]i8{ 1, 0, -1, 0 } ++ [_]i8{0} ** 12;
355-
const packed = packTernary16(trits);
364+
const encoded = packTernary16(trits);
356365

357366
// First trit (1 = 0b10) should be in LSB [1:0]
358-
try std.testing.expectEqual(@as(u8, 0b10), @truncate(packed));
367+
// encoded = ...0b00010010 where bits [1:0]=0b10, [3:2]=0b00, [5:4]=0b01
368+
const byte: u8 = @truncate(encoded);
369+
try std.testing.expectEqual(@as(u8, 0b00010010), byte); // 18
370+
371+
// Extract just bits [1:0] for first trit
372+
try std.testing.expectEqual(@as(u8, 0b10), byte & 0x03);
359373

360374
// Second trit (0 = 0b00) should be in bits [3:2]
361-
try std.testing.expectEqual(@as(u8, 0), @truncate(packed >> 2));
375+
try std.testing.expectEqual(@as(u8, 0b00), (byte >> 2) & 0x03);
376+
377+
// Third trit (-1 = 0b01) should be in bits [5:4]
378+
try std.testing.expectEqual(@as(u8, 0b01), (byte >> 4) & 0x03);
362379
}
363380

364381
test "maximum compression ratio" {

0 commit comments

Comments
 (0)