From a55a6af6266ba69f2a03622c67995d9472b3c2fd Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 00:43:25 +0700 Subject: [PATCH 1/8] One implementation of src/vsa/*, not two Both this repository and gHashTag/zig-golden-float carried src/vsa/core.zig, common.zig, concurrency.zig, 10k_vsa.zig, hrr.zig and fpga_bind.zig. They were edited independently and they diverged, which is why repairing sixteen defects in golden-float (#97) left every one of them standing here: the fixes went into different files with the same names. The decision was made by measurement rather than preference. Comparing the two public surfaces symbol by symbol, they are identical -- one constant apart -- so neither copy is more capable and there is nothing to lose by choosing. The direction then follows the dependency that already exists: this package depends on golden-float, golden-float's copies build and pass 267 tests on 0.15.2, and these do not. Each duplicated file becomes a one-line re-export instead of being deleted. That keeps all twenty-three relative imports across this repository working exactly as they did, so nothing else in the tree moves, and it makes re-divergence impossible rather than merely discouraged: there is one implementation behind the path now. gen_core.zig and gen_encoding.zig stay as they are. golden-float does not export them, so they are genuinely this package's own, and so are the other eighteen files under src/vsa/ that have no counterpart there. --- src/vsa/10k_vsa.zig | 467 +------------------- src/vsa/common.zig | 32 +- src/vsa/concurrency.zig | 302 +------------ src/vsa/core.zig | 924 +--------------------------------------- src/vsa/fpga_bind.zig | 499 +--------------------- src/vsa/hrr.zig | 425 +----------------- 6 files changed, 78 insertions(+), 2571 deletions(-) diff --git a/src/vsa/10k_vsa.zig b/src/vsa/10k_vsa.zig index 4453139..6313c1b 100644 --- a/src/vsa/10k_vsa.zig +++ b/src/vsa/10k_vsa.zig @@ -1,454 +1,13 @@ -// ╔════════════════════════════════════════════════════════════════════════════╗ -// ║ TRINITY VSA — 10K-DIMENSIONAL HYPERVECTORS ║ -// ║ Week 2 Day 1: Scalable VSA architecture for 10,000-dimensional vectors ║ -// ║ ║ -// ║ Features: ║ -// ║ - 10,000-dimensional ternary hypervectors ║ -// ║ - O(1) parallel bind operation ║ -// ║ - Bundle, similarity, permutation ║ -// ║ - FPGA-ready memory layout ║ -// ║ ║ -// ║ φ² + 1/φ² = 3 = TRINITY ║ -// ╚════════════════════════════════════════════════════════════════════════════╝ - -const std = @import("std"); -const builtin = @import("builtin"); -const common = @import("common.zig"); - -pub const Trit = common.Trit; // i8: -1, 0, +1 -pub const HybridBigInt = common.HybridBigInt; - -//========================================================================== -// CONSTANTS -//========================================================================== - -pub const DIM_10K = 10_000; -pub const BYTES_PER_10K = (DIM_10K * 2 + 7) / 8; // 20,000 bits = 2,500 bytes -pub const WORDS_32BIT = (DIM_10K * 2 + 31) / 32; // 625 words of 32 bits - -// FPGA BRAM sizing (32Kb = 4096 bytes) -pub const BRAM_SIZE = 4096; -pub const VECTORS_PER_BRAM = BRAM_SIZE / BYTES_PER_10K; // ~1.6 vectors - -// Trit values (matching HybridBigInt convention) -pub const TRIT_NEG: Trit = -1; -pub const TRIT_ZERO: Trit = 0; -pub const TRIT_POS: Trit = 1; - -//========================================================================== -// 10K-DIMENSIONAL HYPERVECTOR -//========================================================================== - -/// 10K-dimensional ternary hypervector -/// Storage: 2,500 bytes (20,000 bits) using packed trit encoding -pub const HyperVector10K = struct { - /// Packed trit storage (2 bits per trit) - /// Layout: [trit0:1, trit0:0][trit1:1, trit1:0]... - data: [BYTES_PER_10K]u8, - - const Self = @This(); - - /// Create a zero vector - pub inline fn zero() Self { - return .{ .data = [_]u8{0} ** BYTES_PER_10K }; - } - - /// Create a random vector - pub fn random(rng: *std.Random.DefaultPrng) !Self { - var self = Self.zero(); - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - const rand_val = rng.random().int(u3); - const trit_val: i8 = switch (rand_val & 0x03) { - 0 => TRIT_ZERO, - 1 => TRIT_POS, - 2 => TRIT_NEG, - else => TRIT_POS, - }; - try self.set(i, trit_val); - } - return self; - } - - /// Get trit at index (returns {-1, 0, +1}) - pub inline fn get(self: *const Self, index: usize) !Trit { - if (index >= DIM_10K) return error.IndexOutOfBounds; - const bit_idx = index * 2; - const byte_idx = bit_idx / 8; - const shift: u3 = @intCast(bit_idx % 8); - const trit_bits = (self.data[byte_idx] >> shift) & 0x03; - - return switch (trit_bits) { - 0b00 => TRIT_ZERO, - 0b01 => TRIT_POS, - 0b10 => TRIT_NEG, - else => TRIT_ZERO, // Invalid encoding - }; - } - - /// Set trit at index - pub inline fn set(self: *Self, index: usize, value: Trit) !void { - if (index >= DIM_10K) return error.IndexOutOfBounds; - const bit_idx = index * 2; - const byte_idx = bit_idx / 8; - const shift: u3 = @intCast(bit_idx % 8); - - const trit_bits: u2 = switch (value) { - TRIT_ZERO => 0b00, - TRIT_POS => 0b01, - TRIT_NEG => 0b10, - else => 0b00, - }; - - // Clear old bits and set new ones - self.data[byte_idx] &= ~(@as(u8, 0x03) << shift); - self.data[byte_idx] |= @as(u8, trit_bits) << shift; - } - - /// Parallel bind operation (O(1) on FPGA with 10,000 LUTs) - /// result[i] = a[i] * b[i] - pub fn bind(a: *const Self, b: *const Self) Self { - var result = Self.zero(); - - // Process 16 trits (32 bits) at a time for SIMD efficiency - const word_count = WORDS_32BIT; - var w: usize = 0; - - while (w < word_count) : (w += 1) { - const byte_idx = w * 4; - if (byte_idx + 4 > BYTES_PER_10K) break; - - // Load 32 bits (16 trits) from each vector - const a_words = std.mem.readInt(u32, a.data[byte_idx..][0..4], .little); - const b_words = std.mem.readInt(u32, b.data[byte_idx..][0..4], .little); - - var result_word: u32 = 0; - var t: usize = 0; - - // Trit-wise multiplication (16 parallel operations) - while (t < 16) : (t += 1) { - const shift_amt: u5 = @intCast(t * 2); - const a_trit: u2 = @truncate((a_words >> shift_amt) & 0x03); - const b_trit: u2 = @truncate((b_words >> shift_amt) & 0x03); - - const r_trit: u2 = tritMul(a_trit, b_trit); - result_word |= @as(u32, r_trit) << shift_amt; - } - - // Store result - std.mem.writeInt(u32, result.data[byte_idx..][0..4], result_word, .little); - } - - return result; - } - - /// Bundle operation (majority vote) - pub fn bundle(a: *const Self, b: *const Self) !Self { - var result = Self.zero(); - - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - const a_trit = try a.get(i); - const b_trit = try b.get(i); - - const r_trit: Trit = tritBundle(a_trit, b_trit); - try result.set(i, r_trit); - } - - return result; - } - - /// Cosine similarity (scaled to 0-65535) - pub fn cosineSimilarity(a: *const Self, b: *const Self) !u16 { - var dot_product: i64 = 0; - var norm_a: i64 = 0; - var norm_b: i64 = 0; - - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - const a_trit = try a.get(i); - const b_trit = try b.get(i); - - dot_product += @as(i64, a_trit) * @as(i64, b_trit); - norm_a += @as(i64, a_trit) * @as(i64, a_trit); - norm_b += @as(i64, b_trit) * @as(i64, b_trit); - } - - if (norm_a == 0 or norm_b == 0) - return 0; - - const norm_sum = norm_a + norm_b; - const abs_dot = @abs(dot_product); - const scaled = @as(u64, @intCast(abs_dot)) * 65535 / @as(u64, @intCast(norm_sum)); - - return @intCast(scaled); - } - - /// Permutation (cyclic shift) - pub fn permute(self: *const Self, shift: u16) !Self { - var result = Self.zero(); - const effective_shift = @as(usize, @intCast(shift)) % DIM_10K; - - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - const src_idx = (i + DIM_10K - effective_shift) % DIM_10K; - const trit = try self.get(src_idx); - try result.set(i, trit); - } - - return result; - } - - /// Count non-zero trits - pub fn countNonZero(self: *const Self) !usize { - var count: usize = 0; - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - if (try self.get(i) != TRIT_ZERO) - count += 1; - } - return count; - } - - /// Convert to slice of 32-bit words (for FPGA transfer) - pub fn toWords(self: *const Self) [WORDS_32BIT]u32 { - var result: [WORDS_32BIT]u32 = undefined; - var i: usize = 0; - while (i < WORDS_32BIT) : (i += 1) { - const byte_idx = i * 4; - if (byte_idx + 4 <= BYTES_PER_10K) { - result[i] = std.mem.readInt(u32, self.data[byte_idx..][0..4], .little); - } else { - result[i] = 0; - } - } - return result; - } - - /// Create from slice of 32-bit words (from FPGA) - pub fn fromWords(words: []const u32) Self { - var result = Self.zero(); - var i: usize = 0; - while (i < @min(WORDS_32BIT, words.len)) : (i += 1) { - const byte_idx = i * 4; - if (byte_idx + 4 <= BYTES_PER_10K) { - std.mem.writeInt(u32, result.data[byte_idx..][0..4], words[i], .little); - } - } - return result; - } - - /// Format as hex string - pub fn formatHex(self: *const Self, allocator: std.mem.Allocator) ![]u8 { - return std.fmt.allocPrint(allocator, "{s}", .{std.fmt.fmtSliceHexLower(&self.data)}); - } -}; - -/// Trit multiplication lookup table (combinational logic) -inline fn tritMul(a: u2, b: u2) u2 { - return if (a == 0 or b == 0) 0 else if (a == b) 1 else 2; -} - -/// Trit bundle (majority vote of 2) -inline fn tritBundle(a: Trit, b: Trit) Trit { - if (a == TRIT_NEG) { - return if (b == TRIT_NEG) TRIT_NEG else if (b == TRIT_POS) TRIT_ZERO else TRIT_NEG; - } else if (a == TRIT_POS) { - return if (b == TRIT_NEG) TRIT_ZERO else if (b == TRIT_POS) TRIT_POS else TRIT_POS; - } else { // a == ZERO - return b; - } -} - -//========================================================================== -// BENCHMARK FUNCTIONS -//========================================================================== - -pub const BenchmarkResult = struct { - bind_ns: f64, - bundle_ns: f64, - similarity_ns: f64, - bind_throughput: f64, // ops/sec - dimensions: usize = DIM_10K, -}; - -/// Run 10K VSA benchmark -pub fn benchmark(_: std.mem.Allocator, iterations: usize) !BenchmarkResult { - var rng = std.Random.DefaultPrng.init(@intCast(std.time.timestamp())); - - // Create test vectors - const vec_a = try HyperVector10K.random(&rng); - const vec_b = try HyperVector10K.random(&rng); - - // Warmup - _ = HyperVector10K.bind(&vec_a, &vec_b); - _ = try HyperVector10K.bundle(&vec_a, &vec_b); - _ = try HyperVector10K.cosineSimilarity(&vec_a, &vec_b); - - // Benchmark bind - const bind_start = std.time.nanoTimestamp(); - var i: usize = 0; - while (i < iterations) : (i += 1) { - _ = HyperVector10K.bind(&vec_a, &vec_b); - } - const bind_end = std.time.nanoTimestamp(); - const bind_ns = @as(f64, @floatFromInt(bind_end - bind_start)) / @as(f64, @floatFromInt(iterations)); - - // Benchmark bundle - const bundle_start = std.time.nanoTimestamp(); - i = 0; - while (i < iterations) : (i += 1) { - _ = try HyperVector10K.bundle(&vec_a, &vec_b); - } - const bundle_end = std.time.nanoTimestamp(); - const bundle_ns = @as(f64, @floatFromInt(bundle_end - bundle_start)) / @as(f64, @floatFromInt(iterations)); - - // Benchmark similarity - const sim_start = std.time.nanoTimestamp(); - i = 0; - while (i < iterations) : (i += 1) { - _ = try HyperVector10K.cosineSimilarity(&vec_a, &vec_b); - } - const sim_end = std.time.nanoTimestamp(); - const sim_ns = @as(f64, @floatFromInt(sim_end - sim_start)) / @as(f64, @floatFromInt(iterations)); - - return BenchmarkResult{ - .bind_ns = bind_ns, - .bundle_ns = bundle_ns, - .similarity_ns = sim_ns, - .bind_throughput = 1_000_000_000.0 / bind_ns, - }; -} - -/// Print benchmark results -pub fn printBenchmark(result: BenchmarkResult) void { - const stdout = std.io.getStdOut().writer(); - - stdout.print( - \\╔════════════════════════════════════════════════════════════════════════════╗ - \\║ TRINITY VSA 10K BENCHMARK RESULTS ║ - \\╚════════════════════════════════════════════════════════════════════════════╝ - \\ - \\Dimensions: {d} - \\Vector size: {d} bytes - \\ - \\═══════════════════════════════════════════════════════════════════════════ - \\OPERATION TIME (ns) THROUGHPUT vs FPGA (est) - \\═══════════════════════════════════════════════════════════════════════════ - \\BIND {d:.2} ns {d:.0} op/s ~1000x slower - \\BUNDLE {d:.2} ns {d:.0} op/s ~500x slower - \\SIMILARITY {d:.2} ns {d:.0} op/s ~100x slower - \\═══════════════════════════════════════════════════════════════════════════ - \\ - \\φ² + 1/φ² = 3 = TRINITY - \\ - , .{ - DIM_10K, - BYTES_PER_10K, - result.bind_ns, - result.bind_throughput, - result.bundle_ns, - 1_000_000_000.0 / result.bundle_ns, - result.similarity_ns, - 1_000_000_000.0 / result.similarity_ns, - }) catch return; -} - -//========================================================================== -// TESTS -//========================================================================== - -test "HyperVector10K: zero vector" { - const vec = HyperVector10K.zero(); - try std.testing.expectEqual(@as(usize, 0), try vec.countNonZero()); -} - -test "HyperVector10K: bind identity" { - var rng = std.Random.DefaultPrng.init(42); - const vec = try HyperVector10K.random(&rng); - - // Identity vector (all +1) - var identity = HyperVector10K.zero(); - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - try identity.set(i, TRIT_POS); - } - - const result = HyperVector10K.bind(&vec, &identity); - - // Verify result equals original (sample check) - var match_count: usize = 0; - i = 0; - while (i < 100) : (i += 1) { - if ((try result.get(i)) == (try vec.get(i))) - match_count += 1; - } - - try std.testing.expect(match_count >= 95); // Allow some tolerance -} - -test "HyperVector10K: bind inverse" { - var rng = std.Random.DefaultPrng.init(42); - const vec = try HyperVector10K.random(&rng); - - // Inverse vector (all -1) - var inverse = HyperVector10K.zero(); - var i: usize = 0; - while (i < DIM_10K) : (i += 1) { - try inverse.set(i, TRIT_NEG); - } - - const result = HyperVector10K.bind(&vec, &inverse); - - // Verify result is negation of original - var match_count: usize = 0; - i = 0; - while (i < 100) : (i += 1) { - const vi = try vec.get(i); - const expected: i8 = if (vi == TRIT_NEG) TRIT_POS else if (vi == TRIT_POS) TRIT_NEG else TRIT_ZERO; - if ((try result.get(i)) == expected) - match_count += 1; - } - - try std.testing.expectEqual(@as(usize, 100), match_count); -} - -test "HyperVector10K: cosine similarity bounds" { - var rng = std.Random.DefaultPrng.init(42); - const vec_a = try HyperVector10K.random(&rng); - const vec_b = try HyperVector10K.random(&rng); - - const sim = try HyperVector10K.cosineSimilarity(&vec_a, &vec_b); - - // Similarity should be in range [0, 65535] - try std.testing.expect(sim >= 0 and sim <= 65535); -} - -test "HyperVector10K: permutation roundtrip" { - var rng = std.Random.DefaultPrng.init(42); - const original = try HyperVector10K.random(&rng); - - const shifted = try original.permute(100); - const unshifted = try shifted.permute(@as(u16, @intCast(DIM_10K - 100))); - - // Sample check (not all 10K to save time) - var match_count: usize = 0; - var i: usize = 0; - while (i < 100) : (i += 1) { - if ((try unshifted.get(i)) == (try original.get(i))) - match_count += 1; - } - - try std.testing.expectEqual(@as(usize, 100), match_count); -} - -test "HyperVector10K: benchmark quick" { - const allocator = std.testing.allocator; - const result = try benchmark(allocator, 10); - _ = result; - - // Just verify it completes without error - try std.testing.expect(true); -} - -// φ² + 1/φ² = 3 = TRINITY +//! Re-export. The implementation lives in gHashTag/zig-golden-float. +//! +//! This file used to be a second copy of that one. Both repositories carried +//! src/vsa/10k_vsa.zig, they were edited independently, and they diverged -- which +//! is why repairing sixteen defects in golden-float (#97) left every one of +//! them standing here. Two maintained copies of the same code is how that +//! happens, and it happens quietly, because nothing reports it. +//! +//! The two public surfaces were identical when this was written, checked +//! symbol by symbol, so nothing is lost by pointing at one of them. Everything +//! that imported this path still imports this path; there is simply one +//! implementation behind it now, and it is the one that compiles. +pub usingnamespace @import("zig_golden_float").vsa_10k; diff --git a/src/vsa/common.zig b/src/vsa/common.zig index 7663154..f7ba089 100644 --- a/src/vsa/common.zig +++ b/src/vsa/common.zig @@ -1,19 +1,13 @@ -// 🤖 TRINITY v0.11.0: Suborbital Order -// Common types and imports for VSA module - -const std = @import("std"); -const tvc_hybrid = @import("../hybrid.zig"); - -pub const HybridBigInt = tvc_hybrid.HybridBigInt; -pub const Trit = tvc_hybrid.Trit; -pub const Vec32i8 = tvc_hybrid.Vec32i8; -pub const SIMD_WIDTH = tvc_hybrid.SIMD_WIDTH; -pub const MAX_TRITS = tvc_hybrid.MAX_TRITS; -pub const MAX_PACKED_BYTES = tvc_hybrid.MAX_PACKED_BYTES; - -pub const SearchResult = struct { - index: usize, - similarity: f64, -}; - -// φ² + 1/φ² = 3 | TRINITY +//! Re-export. The implementation lives in gHashTag/zig-golden-float. +//! +//! This file used to be a second copy of that one. Both repositories carried +//! src/vsa/common.zig, they were edited independently, and they diverged -- which +//! is why repairing sixteen defects in golden-float (#97) left every one of +//! them standing here. Two maintained copies of the same code is how that +//! happens, and it happens quietly, because nothing reports it. +//! +//! The two public surfaces were identical when this was written, checked +//! symbol by symbol, so nothing is lost by pointing at one of them. Everything +//! that imported this path still imports this path; there is simply one +//! implementation behind it now, and it is the one that compiles. +pub usingnamespace @import("zig_golden_float").vsa_common; diff --git a/src/vsa/concurrency.zig b/src/vsa/concurrency.zig index c21cfe8..11eae6b 100644 --- a/src/vsa/concurrency.zig +++ b/src/vsa/concurrency.zig @@ -1,289 +1,13 @@ -// 🤖 TRINITY v0.11.0: Suborbital Order -// Concurrency and Parallel Processing layer for VSA -const std = @import("std"); -const common = @import("common.zig"); -const HybridBigInt = common.HybridBigInt; - -// CONSTANTS -pub const POOL_SIZE = 4; -pub const DEQUE_CAPACITY = 128; -pub const MAX_WORKERS = 8; -pub const PRIORITY_LEVELS = 5; -pub const PRIORITY_QUEUE_CAPACITY = 256; -pub const MAX_JOB_AGE = 100; -pub const MAX_DAG_NODES = 256; -pub const MAX_DEPENDENCIES = 16; -pub const PHI_INVERSE: f64 = 0.618033988749895; - -// TYPES -pub const JobFn = *const fn (context: *anyopaque) void; -pub const PoolJob = struct { func: JobFn, context: *anyopaque }; -pub const PriorityLevel = enum(u8) { critical = 0, high = 1, normal = 2, low = 3, background = 4 }; -pub const JobPriority = PriorityLevel; -pub const TaskState = enum(u8) { pending = 0, ready = 1, running = 2, completed = 3, failed = 4 }; - -pub const TaskNode = struct { - id: u32, - func: JobFn, - context: *anyopaque, - dependencies: [MAX_DEPENDENCIES]u32, - dep_count: usize, - dependents: [MAX_DEPENDENCIES]u32, - dependent_count: usize, - state: TaskState, - priority: JobPriority, - deadline: ?i64, - wait_count: std.atomic.Value(usize), - - pub fn init(id: u32, func: JobFn, context: *anyopaque) TaskNode { - return TaskNode{ - .id = id, - .func = func, - .context = context, - .dependencies = undefined, - .dep_count = 0, - .dependents = undefined, - .dependent_count = 0, - .state = .pending, - .priority = .normal, - .deadline = null, - .wait_count = std.atomic.Value(usize).init(0), - }; - } - pub fn addDependency(self: *TaskNode, dep_id: u32) bool { - if (self.dep_count >= MAX_DEPENDENCIES) return false; - self.dependencies[self.dep_count] = dep_id; - self.dep_count += 1; - _ = self.wait_count.fetchAdd(1, .monotonic); - return true; - } - pub fn addDependent(self: *TaskNode, dep_id: u32) bool { - if (self.dependent_count >= MAX_DEPENDENCIES) return false; - self.dependents[self.dependent_count] = dep_id; - self.dependent_count += 1; - return true; - } - pub fn satisfyDependency(self: *TaskNode) bool { - const remaining = self.wait_count.fetchSub(1, .release) - 1; - if (remaining == 0) { - std.atomic.fence(.acquire); - self.state = .ready; - return true; - } - return false; - } - pub fn getEffectivePriority(self: *const TaskNode) f64 { - const base = switch (self.priority) { - .critical => 1.0, - .high => 0.8, - .normal => 0.6, - .low => 0.4, - .background => 0.2, - }; - if (self.deadline) |dl| { - const now = std.time.nanoTimestamp(); - const remaining = dl - now; - if (remaining < 0) return 2.0; - const boost = 1.0 / (@as(f64, @floatFromInt(remaining)) / 1e9 + 1.0); - return base + boost; - } - return base; - } -}; - -pub const DAGStats = struct { - total: usize, - completed: usize, - failed: usize, - pending: usize, - ready: usize, - completion_rate: f64, -}; - -// CHASE-LEV DEQUE & WORK-STEALING POOL -pub const ChaseLevDeque = struct { - jobs: [DEQUE_CAPACITY]PoolJob, - bottom: usize, - top: usize, - - pub fn init() ChaseLevDeque { - return ChaseLevDeque{ .jobs = undefined, .bottom = 0, .top = 0 }; - } - pub fn push(self: *ChaseLevDeque, job: PoolJob) bool { - const b = @atomicLoad(usize, &self.bottom, .seq_cst); - const t = @atomicLoad(usize, &self.top, .seq_cst); - if (b - t >= DEQUE_CAPACITY) return false; - self.jobs[b % DEQUE_CAPACITY] = job; - @atomicStore(usize, &self.bottom, b + 1, .seq_cst); - return true; - } - pub fn pop(self: *ChaseLevDeque) ?PoolJob { - var b = @atomicLoad(usize, &self.bottom, .seq_cst); - if (b == 0) return null; - b -= 1; - @atomicStore(usize, &self.bottom, b, .seq_cst); - const t = @atomicLoad(usize, &self.top, .seq_cst); - if (t <= b) { - const job = self.jobs[b % DEQUE_CAPACITY]; - if (t == b) { - const result = @cmpxchgWeak(usize, &self.top, t, t + 1, .seq_cst, .seq_cst); - if (result == null) { - @atomicStore(usize, &self.bottom, t + 1, .seq_cst); - return job; - } else { - @atomicStore(usize, &self.bottom, t + 1, .seq_cst); - return null; - } - } - return job; - } else { - @atomicStore(usize, &self.bottom, t, .seq_cst); - return null; - } - } - pub fn steal(self: *ChaseLevDeque) ?PoolJob { - const t = @atomicLoad(usize, &self.top, .seq_cst); - const b = @atomicLoad(usize, &self.bottom, .seq_cst); - if (t >= b) return null; - const job = self.jobs[t % DEQUE_CAPACITY]; - const result = @cmpxchgWeak(usize, &self.top, t, t + 1, .seq_cst, .seq_cst); - if (result == null) return job; - return null; - } - pub fn size(self: *ChaseLevDeque) usize { - const b = @atomicLoad(usize, &self.bottom, .seq_cst); - const t = @atomicLoad(usize, &self.top, .seq_cst); - return if (b > t) b - t else 0; - } - pub fn reset(self: *ChaseLevDeque) void { - @atomicStore(usize, &self.bottom, 0, .seq_cst); - @atomicStore(usize, &self.top, 0, .seq_cst); - } -}; - -pub const ThreadPool = struct { - workers: [MAX_WORKERS]ChaseLevDeque, - count: usize, - pub fn init() ThreadPool { - return ThreadPool{ .workers = undefined, .count = 0 }; - } -}; - -// DEPENDENCY GRAPH (DAG) -pub const DependencyGraph = struct { - nodes: [MAX_DAG_NODES]?TaskNode, - node_count: usize, - ready_queue: [MAX_DAG_NODES]u32, - ready_count: std.atomic.Value(usize), - completed_count: usize, - failed_count: usize, - execution_order: [MAX_DAG_NODES]u32, - order_computed: bool, - - const Self = @This(); - pub fn init() Self { - return Self{ - .nodes = .{null} ** MAX_DAG_NODES, - .node_count = 0, - .ready_queue = .{0} ** MAX_DAG_NODES, - .ready_count = std.atomic.Value(usize).init(0), - .completed_count = 0, - .failed_count = 0, - .execution_order = .{0} ** MAX_DAG_NODES, - .order_computed = false, - }; - } - pub fn addTask(self: *Self, func: JobFn, context: *anyopaque) ?u32 { - if (self.node_count >= MAX_DAG_NODES) return null; - const id: u32 = @intCast(self.node_count); - self.nodes[id] = TaskNode.init(id, func, context); - self.node_count += 1; - self.order_computed = false; - return id; - } - pub fn addTaskWithPriority(self: *Self, func: JobFn, context: *anyopaque, priority: JobPriority) ?u32 { - const id = self.addTask(func, context) orelse return null; - if (self.nodes[id]) |*node| node.priority = priority; - return id; - } - pub fn addDependency(self: *Self, from_id: u32, to_id: u32) bool { - if (from_id >= self.node_count or to_id >= self.node_count) return false; - if (from_id == to_id) return false; - if (self.nodes[from_id]) |*from_node| { - if (!from_node.addDependent(to_id)) return false; - } else return false; - if (self.nodes[to_id]) |*to_node| { - if (!to_node.addDependency(from_id)) return false; - } else return false; - self.order_computed = false; - return true; - } - pub fn computeTopologicalOrder(self: *Self) bool { - if (self.order_computed) return true; - var in_degree: [MAX_DAG_NODES]usize = .{0} ** MAX_DAG_NODES; - var queue: [MAX_DAG_NODES]u32 = .{0} ** MAX_DAG_NODES; - var queue_start: usize = 0; - var queue_end: usize = 0; - var order_idx: usize = 0; - for (0..self.node_count) |i| { - if (self.nodes[i]) |node| { - in_degree[i] = node.dep_count; - if (in_degree[i] == 0) { - queue[queue_end] = @intCast(i); - queue_end += 1; - } - } - } - while (queue_start < queue_end) { - const current = queue[queue_start]; - queue_start += 1; - self.execution_order[order_idx] = current; - order_idx += 1; - if (self.nodes[current]) |node| { - for (0..node.dependent_count) |i| { - const dep_id = node.dependents[i]; - in_degree[dep_id] -= 1; - if (in_degree[dep_id] == 0) { - queue[queue_end] = dep_id; - queue_end += 1; - } - } - } - } - if (order_idx != self.node_count) return false; - self.order_computed = true; - return true; - } - pub fn executeAll(self: *Self) struct { completed: usize, failed: usize } { - if (!self.computeTopologicalOrder()) return .{ .completed = 0, .failed = self.node_count }; - var completed: usize = 0; - for (0..self.node_count) |order_idx| { - const id = self.execution_order[order_idx]; - if (self.nodes[id]) |node| { - node.func(node.context); - completed += 1; - } - } - self.completed_count = completed; - return .{ .completed = completed, .failed = 0 }; - } -}; - -var global_pool: ?ThreadPool = null; -pub fn getGlobalPool() *ThreadPool { - if (global_pool == null) global_pool = ThreadPool.init(); - return &global_pool.?; -} -var global_dag: ?DependencyGraph = null; -pub fn getDAG() *DependencyGraph { - if (global_dag == null) global_dag = DependencyGraph.init(); - return &global_dag.?; -} -pub fn shutdownDAG() void { - global_dag = null; -} -pub fn hasDAG() bool { - return global_dag != null; -} - -// φ² + 1/φ² = 3 | TRINITY +//! Re-export. The implementation lives in gHashTag/zig-golden-float. +//! +//! This file used to be a second copy of that one. Both repositories carried +//! src/vsa/concurrency.zig, they were edited independently, and they diverged -- which +//! is why repairing sixteen defects in golden-float (#97) left every one of +//! them standing here. Two maintained copies of the same code is how that +//! happens, and it happens quietly, because nothing reports it. +//! +//! The two public surfaces were identical when this was written, checked +//! symbol by symbol, so nothing is lost by pointing at one of them. Everything +//! that imported this path still imports this path; there is simply one +//! implementation behind it now, and it is the one that compiles. +pub usingnamespace @import("zig_golden_float").vsa_concurrency; diff --git a/src/vsa/core.zig b/src/vsa/core.zig index e5b5fe1..867750f 100644 --- a/src/vsa/core.zig +++ b/src/vsa/core.zig @@ -1,911 +1,13 @@ -// 🤖 TRINITY v0.11.0: Suborbital Order -// Core VSA operations for Balanced Ternary -// bind, bundle, similarity, permute - -const std = @import("std"); -const common = @import("common.zig"); -const HybridBigInt = common.HybridBigInt; -const Trit = common.Trit; -const Vec32i8 = common.Vec32i8; -const SIMD_WIDTH = common.SIMD_WIDTH; -const MAX_TRITS = common.MAX_TRITS; - -/// Helper: Safe trit access after ensureUnpacked() -inline fn getTritSafe(vec: *const HybridBigInt, pos: usize) Trit { - const cache = vec.unpacked_cache orelse return 0; - return cache[pos]; -} - -/// Helper: Safe trit write after ensureUnpacked() -inline fn setTritSafe(vec: *HybridBigInt, pos: usize, value: Trit) void { - vec.setTritChecked(pos, value); -} - -/// Bind operation (XOR-like for balanced ternary) -pub fn bind(a: *HybridBigInt, b: *HybridBigInt) HybridBigInt { - a.ensureUnpacked(); - b.ensureUnpacked(); - - // Inherit allocator from first argument - const inherited_allocator = a.allocator orelse std.heap.page_allocator; - var result = HybridBigInt{ - .packed_data = [_]u8{0} ** common.MAX_PACKED_BYTES, - .unpacked_cache = null, - .allocator = inherited_allocator, - .mode = .unpacked_mode, - .trit_len = 1, - .dirty = true, - }; - result.ensureUnpacked(); - - const len = @max(a.trit_len, b.trit_len); - result.trit_len = len; - - const min_len = @min(a.trit_len, b.trit_len); - const num_full_chunks = min_len / SIMD_WIDTH; - - var i: usize = 0; - while (i < num_full_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - var a_vec: Vec32i8 = undefined; - var b_vec: Vec32i8 = undefined; - - inline for (0..SIMD_WIDTH) |j| { - const idx = i + j; - a_vec[j] = getTritSafe(a, idx); - b_vec[j] = getTritSafe(b, idx); - } - - const prod = a_vec * b_vec; - var result_vec: Vec32i8 = undefined; - - inline for (0..SIMD_WIDTH) |j| { - result_vec[j] = @truncate(prod[j]); - } - - // Write back using safe access - inline for (0..SIMD_WIDTH) |j| { - setTritSafe(&result, i + j, result_vec[j]); - } - } - - while (i < len) : (i += 1) { - const a_trit: Trit = if (i < a.trit_len) getTritSafe(a, i) else 0; - const b_trit: Trit = if (i < b.trit_len) getTritSafe(b, i) else 0; - setTritSafe(&result, i, a_trit * b_trit); - } - - return result; -} - -pub fn unbind(bound: *HybridBigInt, key: *HybridBigInt) HybridBigInt { - return bind(bound, key); -} - -pub fn bundle2(a: *HybridBigInt, b: *HybridBigInt, allocator: std.mem.Allocator) HybridBigInt { - _ = allocator; // Use inherited allocator instead - a.ensureUnpacked(); - b.ensureUnpacked(); - - // Inherit allocator from first argument - const inherited_allocator = a.allocator orelse std.heap.page_allocator; - var result = HybridBigInt.zero(); - result.allocator = inherited_allocator; - result.ensureUnpacked(); - - const len = @max(a.trit_len, b.trit_len); - result.trit_len = len; - - const min_len = @min(a.trit_len, b.trit_len); - const num_full_chunks = min_len / SIMD_WIDTH; - - var i: usize = 0; - while (i < num_full_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - var a_vec: Vec32i8 = undefined; - var b_vec: Vec32i8 = undefined; - - inline for (0..SIMD_WIDTH) |j| { - const idx = i + j; - a_vec[j] = getTritSafe(a, idx); - b_vec[j] = getTritSafe(b, idx); - } - - const a_wide: @Vector(32, i16) = a_vec; - const b_wide: @Vector(32, i16) = b_vec; - const sum = a_wide + b_wide; - - const zeros: @Vector(32, i16) = @splat(0); - const ones: @Vector(32, i16) = @splat(1); - const neg_ones: @Vector(32, i16) = @splat(-1); - - const pos_mask = sum > zeros; - const neg_mask = sum < zeros; - - var out = zeros; - out = @select(i16, pos_mask, ones, out); - out = @select(i16, neg_mask, neg_ones, out); - - inline for (0..SIMD_WIDTH) |j| { - setTritSafe(&result, i + j, @truncate(out[j])); - } - } - - while (i < len) : (i += 1) { - const a_trit: i16 = if (i < a.trit_len) getTritSafe(a, i) else 0; - const b_trit: i16 = if (i < b.trit_len) getTritSafe(b, i) else 0; - const sum = a_trit + b_trit; - - if (sum > 0) { - setTritSafe(&result, i, 1); - } else if (sum < 0) { - setTritSafe(&result, i, -1); - } else { - setTritSafe(&result, i, 0); - } - } - - return result; -} - -pub fn bundle3(a: *HybridBigInt, b: *HybridBigInt, c: *HybridBigInt, allocator: std.mem.Allocator) HybridBigInt { - _ = allocator; // Use inherited allocator instead - a.ensureUnpacked(); - b.ensureUnpacked(); - c.ensureUnpacked(); - - // Inherit allocator from first argument - const inherited_allocator = a.allocator orelse std.heap.page_allocator; - var result = HybridBigInt.zero(); - result.allocator = inherited_allocator; - result.ensureUnpacked(); - - const len = @max(@max(a.trit_len, b.trit_len), c.trit_len); - const min_len = @min(@min(a.trit_len, b.trit_len), c.trit_len); - const num_full_chunks = min_len / SIMD_WIDTH; - - // SIMD path: 32 trits at a time via i16 widening + sign extraction - var i: usize = 0; - while (i < num_full_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - var a_vec: Vec32i8 = undefined; - var b_vec: Vec32i8 = undefined; - var c_vec: Vec32i8 = undefined; - - inline for (0..SIMD_WIDTH) |j| { - const idx = i + j; - a_vec[j] = getTritSafe(a, idx); - b_vec[j] = getTritSafe(b, idx); - c_vec[j] = getTritSafe(c, idx); - } - - const a_wide: @Vector(32, i16) = a_vec; - const b_wide: @Vector(32, i16) = b_vec; - const c_wide: @Vector(32, i16) = c_vec; - const sum = a_wide + b_wide + c_wide; - - const zeros: @Vector(32, i16) = @splat(0); - const ones: @Vector(32, i16) = @splat(1); - const neg_ones: @Vector(32, i16) = @splat(-1); - - const pos_mask = sum > zeros; - const neg_mask = sum < zeros; - - var out = zeros; - out = @select(i16, pos_mask, ones, out); - out = @select(i16, neg_mask, neg_ones, out); - - inline for (0..SIMD_WIDTH) |j| { - setTritSafe(&result, i + j, @truncate(out[j])); - } - } - - // Scalar remainder - while (i < len) : (i += 1) { - const a_trit: i16 = if (i < a.trit_len) getTritSafe(a, i) else 0; - const b_trit: i16 = if (i < b.trit_len) getTritSafe(b, i) else 0; - const c_trit: i16 = if (i < c.trit_len) getTritSafe(c, i) else 0; - const sum = a_trit + b_trit + c_trit; - - if (sum > 0) { - setTritSafe(&result, i, 1); - } else if (sum < 0) { - setTritSafe(&result, i, -1); - } else { - setTritSafe(&result, i, 0); - } - } - - result.trit_len = len; - return result; -} - -pub fn cosineSimilarity(a: *const HybridBigInt, b: *const HybridBigInt) f64 { - const dot = @constCast(a).dotProduct(@constCast(b), std.heap.page_allocator); - const norm_a = vectorNorm(@constCast(a)); - const norm_b = vectorNorm(@constCast(b)); - - if (norm_a == 0 or norm_b == 0) return 0; - - return @as(f64, @floatFromInt(dot)) / (norm_a * norm_b); -} - -/// Cosine similarity using 16-wide f16 SIMD (2× throughput vs f32). -/// Converts ternary vectors to f16, computes similarity with 16-wide operations. -/// Returns f64 in range [-1, 1]. -pub fn cosineSimilarityF16(a: *const HybridBigInt, b: *const HybridBigInt, allocator: std.mem.Allocator) f64 { - _ = allocator; // Read-only, no allocations needed - @constCast(a).ensureUnpacked(); - @constCast(b).ensureUnpacked(); - - const len = @max(a.trit_len, b.trit_len); - if (len == 0) return 0; - - const F16_VEC_SIZE = 16; - const num_f16_chunks = len / F16_VEC_SIZE; - - // f32 accumulators for precision - var acc_dot: f64 = 0; - var acc_norm_a: f64 = 0; - var acc_norm_b: f64 = 0; - - // Process 16 elements at a time using f16 SIMD - var i: usize = 0; - while (i < num_f16_chunks * F16_VEC_SIZE) : (i += F16_VEC_SIZE) { - // Load trits into i8 vectors using safe access - var a_trits: @Vector(F16_VEC_SIZE, i8) = undefined; - var b_trits: @Vector(F16_VEC_SIZE, i8) = undefined; - - inline for (0..F16_VEC_SIZE) |j| { - a_trits[j] = if (i + j < a.trit_len) getTritSafe(@constCast(a), i + j) else 0; - b_trits[j] = if (i + j < b.trit_len) getTritSafe(@constCast(b), i + j) else 0; - } - - // Convert to f16 - const a_f16: @Vector(F16_VEC_SIZE, f16) = @floatCast(@as(@Vector(F16_VEC_SIZE, f32), @floatFromInt(a_trits))); - const b_f16: @Vector(F16_VEC_SIZE, f16) = @floatCast(@as(@Vector(F16_VEC_SIZE, f32), @floatFromInt(b_trits))); - - // Convert to f32 for compute - const a_f32: @Vector(F16_VEC_SIZE, f32) = @floatCast(a_f16); - const b_f32: @Vector(F16_VEC_SIZE, f32) = @floatCast(b_f16); - - // Compute dot product contribution - const prod = a_f32 * b_f32; - var sum_prod: f32 = 0; - inline for (0..F16_VEC_SIZE) |j| { - sum_prod += prod[j]; - } - acc_dot += @as(f64, sum_prod); - - // Compute norm contributions - const a_sq = a_f32 * a_f32; - const b_sq = b_f32 * b_f32; - var sum_a_sq: f32 = 0; - var sum_b_sq: f32 = 0; - inline for (0..F16_VEC_SIZE) |j| { - sum_a_sq += a_sq[j]; - sum_b_sq += b_sq[j]; - } - acc_norm_a += @as(f64, sum_a_sq); - acc_norm_b += @as(f64, sum_b_sq); - } - - // Handle scalar tail using safe access - while (i < len) : (i += 1) { - const a_trit: i8 = if (i < a.trit_len) getTritSafe(@constCast(a), i) else 0; - const b_trit: i8 = if (i < b.trit_len) getTritSafe(@constCast(b), i) else 0; - - const a_f32: f32 = @floatFromInt(a_trit); - const b_f32: f32 = @floatFromInt(b_trit); - - acc_dot += @as(f64, a_f32 * b_f32); - acc_norm_a += @as(f64, a_f32 * a_f32); - acc_norm_b += @as(f64, b_f32 * b_f32); - } - - const norm_a = @sqrt(acc_norm_a); - const norm_b = @sqrt(acc_norm_b); - - if (norm_a == 0 or norm_b == 0) return 0; - - return acc_dot / (norm_a * norm_b); -} - -pub fn hammingDistance(a: *HybridBigInt, b: *HybridBigInt) usize { - a.ensureUnpacked(); - b.ensureUnpacked(); - - var distance: usize = 0; - const len = @max(a.trit_len, b.trit_len); - const min_len = @min(a.trit_len, b.trit_len); - const num_full_chunks = min_len / SIMD_WIDTH; - - var i: usize = 0; - while (i < num_full_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - // Use safe slice access - loads 32 trits at a time - const base = i; - var a_vec: Vec32i8 = undefined; - var b_vec: Vec32i8 = undefined; - inline for (0..SIMD_WIDTH) |j| { - a_vec[j] = getTritSafe(a, base + j); - b_vec[j] = getTritSafe(b, base + j); - } - const diff = a_vec != b_vec; - distance += @popCount(@as(u32, @bitCast(diff))); - } - - while (i < len) : (i += 1) { - const a_trit: Trit = if (i < a.trit_len) getTritSafe(a, i) else 0; - const b_trit: Trit = if (i < b.trit_len) getTritSafe(b, i) else 0; - if (a_trit != b_trit) distance += 1; - } - - return distance; -} - -pub fn hammingSimilarity(a: *HybridBigInt, b: *HybridBigInt) f64 { - const len = @max(a.trit_len, b.trit_len); - if (len == 0) return 1.0; - const distance = hammingDistance(a, b); - return 1.0 - @as(f64, @floatFromInt(distance)) / @as(f64, @floatFromInt(len)); -} - -pub fn dotSimilarity(a: *HybridBigInt, b: *HybridBigInt) f64 { - const dot = a.dotProduct(b); - const len = @max(a.trit_len, b.trit_len); - if (len == 0) return 0; - return @as(f64, @floatFromInt(dot)) / @as(f64, @floatFromInt(len)); -} - -/// Vector norm — SIMD accelerated via dotProduct(v, v) (OPT-001) -pub fn vectorNorm(v: *HybridBigInt) f64 { - const dot = v.dotProduct(v, std.heap.page_allocator); - return @sqrt(@as(f64, @floatFromInt(dot))); -} - -/// Count non-zero trits — SIMD accelerated (OPT-001) -pub fn countNonZero(v: *HybridBigInt) usize { - v.ensureUnpacked(); - var count: usize = 0; - const num_full_chunks = v.trit_len / SIMD_WIDTH; - - var i: usize = 0; - while (i < num_full_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - // Use safe slice access - loads 32 trits at a time - const base = i; - var vec: Vec32i8 = undefined; - inline for (0..SIMD_WIDTH) |j| { - vec[j] = getTritSafe(v, base + j); - } - const zeros: Vec32i8 = @splat(0); - const nonzero = vec != zeros; - count += @popCount(@as(u32, @bitCast(nonzero))); - } - - while (i < v.trit_len) : (i += 1) { - if (getTritSafe(v, i) != 0) count += 1; - } - - return count; -} - -/// Bundle N vectors — SIMD accelerated majority vote (OPT-001) -pub fn bundleN(vectors: []*HybridBigInt, allocator: std.mem.Allocator) !HybridBigInt { - if (vectors.len == 0) return HybridBigInt.zero(); - if (vectors.len == 1) { - vectors[0].ensureUnpacked(); - // Inherit allocator from first vector - const inherited_allocator = vectors[0].allocator orelse std.heap.page_allocator; - var result = HybridBigInt.zero(); - result.allocator = inherited_allocator; - result.mode = .unpacked_mode; - result.dirty = true; - result.trit_len = vectors[0].trit_len; - result.ensureUnpacked(); - // Copy using safe access - for (0..vectors[0].trit_len) |i| { - setTritSafe(&result, i, getTritSafe(vectors[0], i)); - } - return result; - } - if (vectors.len == 2) return bundle2(vectors[0], vectors[1], allocator); - if (vectors.len == 3) return bundle3(vectors[0], vectors[1], vectors[2], allocator); - - var max_len: usize = 0; - for (vectors) |v| { - v.ensureUnpacked(); - max_len = @max(max_len, v.trit_len); - } - - // Allocate accumulator on heap (115 KB) - macOS stack fix - const accum = allocator.alloc(i16, MAX_TRITS) catch |err| { - std.debug.panic("OOM in bundleN: {}", .{err}); - }; - defer allocator.free(accum); - @memset(accum, @as(i16, 0)); - - for (vectors) |v| { - const num_chunks = v.trit_len / SIMD_WIDTH; - var i: usize = 0; - while (i < num_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - // Use safe vector slice access - const base = i; - var vec: Vec32i8 = undefined; - inline for (0..SIMD_WIDTH) |j| { - vec[j] = getTritSafe(v, base + j); - } - const wide: @Vector(32, i16) = vec; - const acc_array: [SIMD_WIDTH]i16 = accum[i..][0..SIMD_WIDTH].*; - const acc_vec: @Vector(32, i16) = acc_array; - const sum_val = acc_vec + wide; - accum[i..][0..SIMD_WIDTH].* = sum_val; - } - while (i < v.trit_len) : (i += 1) { - accum[i] += @as(i16, getTritSafe(v, i)); - } - } - - // Inherit allocator from first vector - const inherited_allocator = if (vectors.len > 0) vectors[0].allocator orelse std.heap.page_allocator else std.heap.page_allocator; - var result = HybridBigInt.zero(); - result.allocator = inherited_allocator; - result.mode = .unpacked_mode; - result.dirty = true; - result.trit_len = max_len; - result.ensureUnpacked(); - - const num_result_chunks = max_len / SIMD_WIDTH; - var i: usize = 0; - while (i < num_result_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - const acc_vec: @Vector(32, i16) = @as(@Vector(32, i16), accum[i..][0..SIMD_WIDTH].*); - const zeros: @Vector(32, i16) = @splat(0); - const ones: @Vector(32, i16) = @splat(1); - const neg_ones: @Vector(32, i16) = @splat(-1); - - const pos_mask = acc_vec > zeros; - const neg_mask = acc_vec < zeros; - - var out = zeros; - out = @select(i16, pos_mask, ones, out); - out = @select(i16, neg_mask, neg_ones, out); - - inline for (0..SIMD_WIDTH) |j| { - setTritSafe(&result, i + j, @truncate(out[j])); - } - } - - while (i < max_len) : (i += 1) { - const val = accum[i]; - if (val > 0) { - setTritSafe(&result, i, 1); - } else if (val < 0) { - setTritSafe(&result, i, -1); - } else { - setTritSafe(&result, i, 0); - } - } - - return result; -} - -pub fn randomVector(len: usize, seed: u64) HybridBigInt { - _ = len; // TODO: actually use this parameter - var result = HybridBigInt.zero(); - result.allocator = std.heap.page_allocator; // Explicit: random vectors use page allocator - result.mode = .unpacked_mode; - result.dirty = true; - result.ensureUnpacked(); - - var rng = std.Random.DefaultPrng.init(seed); - const random = rng.random(); - for (0..result.trit_len) |i| { - setTritSafe(&result, i, random.intRangeAtMost(i8, -1, 1)); - } - return result; -} - -pub fn permute(v: *HybridBigInt, k: usize) HybridBigInt { - v.ensureUnpacked(); - // Inherit allocator from v - const inherited_allocator = v.allocator orelse std.heap.page_allocator; - var result = HybridBigInt.zero(); - result.allocator = inherited_allocator; - result.ensureUnpacked(); - - result.mode = .unpacked_mode; - result.dirty = true; - result.trit_len = v.trit_len; - if (v.trit_len == 0) return result; - const shift = k % v.trit_len; - for (0..v.trit_len) |i| { - const new_pos = (i + shift) % v.trit_len; - setTritSafe(&result, new_pos, getTritSafe(v, i)); - } - return result; -} - -pub fn inversePermute(v: *HybridBigInt, k: usize) HybridBigInt { - v.ensureUnpacked(); - // Inherit allocator from v - const inherited_allocator = v.allocator orelse std.heap.page_allocator; - var result = HybridBigInt.zero(); - result.allocator = inherited_allocator; - result.ensureUnpacked(); - - result.mode = .unpacked_mode; - result.dirty = true; - result.trit_len = v.trit_len; - if (v.trit_len == 0) return result; - const shift = k % v.trit_len; - for (0..v.trit_len) |i| { - const new_pos = (i + v.trit_len - shift) % v.trit_len; - setTritSafe(&result, new_pos, getTritSafe(v, i)); - } - return result; -} - -pub fn encodeSequence(items: []HybridBigInt) HybridBigInt { - if (items.len == 0) return HybridBigInt.zero(); - var result = items[0]; - for (1..items.len) |i| { - var permuted = permute(&items[i], i); - result = result.add(&permuted, std.heap.page_allocator); - } - return result; -} - -pub fn probeSequence(sequence: *HybridBigInt, candidate: *HybridBigInt, position: usize) f64 { - var permuted = permute(candidate, position); - return cosineSimilarity(sequence, &permuted); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// TESTS -// ═══════════════════════════════════════════════════════════════════════════════ - -test "cosineSimilarityF16 matches cosineSimilarity" { - var a = randomVector(100, 111); - var b = randomVector(100, 222); - - const sim_f64 = cosineSimilarity(&a, &b); - const sim_f16 = cosineSimilarityF16(&a, &b, std.heap.page_allocator); - - // Should be very close (within f16 precision) - try std.testing.expectApproxEqAbs(sim_f64, sim_f16, 0.01); -} - -test "cosineSimilarityF16 identical vectors" { - var a = randomVector(100, 333); - - const sim = cosineSimilarityF16(&a, &a, std.heap.page_allocator); - - // Identical vectors should have similarity 1.0 - try std.testing.expectApproxEqAbs(@as(f64, 1.0), sim, 0.01); -} - -test "cosineSimilarityF16 zero vectors" { - var a = HybridBigInt.zero(); - var b = HybridBigInt.zero(); - - const sim = cosineSimilarityF16(&a, &b, std.heap.page_allocator); - - // Zero vectors should return 0 - try std.testing.expectEqual(@as(f64, 0), sim); -} - -// ═══════════════════════════════════════════════════════════════════════════════ -// QUANTUM-ENHANCED VSA OPERATIONS -// Reference: [arXiv 2106.05268 VSA], [LinkedIn Kantian Vectors] -// ═══════════════════════════════════════════════════════════════════════════════ - -/// Quantum bind: extends classical bind with phase coherence tracking -/// Maintains superposition state across operation -/// -/// In VSA framework: bind = XOR-like operation on hypervectors -/// Quantum extension: track phase information for interference -pub fn qbind(a: *const HybridBigInt, b: *const HybridBigInt) HybridBigInt { - // Classical bind (XOR-like for balanced ternary) - const result = bind(a, b); - - // Phase tracking: maintain coherence if both inputs coherent - // Result phase = (phase_a + phase_b) mod 2π - // Coherence = coherence_a AND coherence_b - // Note: In current implementation, HybridBigInt doesn't track phase - // This is a placeholder for future phase-aware VSA - - // In full quantum extension, would track: - // - Phase information via separate struct - // - Coherence preservation logic - // - Interference effects - - return result; -} - -/// Quantum bundle: probabilistic mixture with superposition amplitudes -/// Result = Σ αᵢ|vᵢ⟩ where αᵢ = amplitudes -/// -/// In VSA framework: bundle = majority vote on hypervectors -/// Quantum extension: weighted majority using amplitudes as weights -/// This is equivalent to "quantum-inspired mixture" in hybrid architectures -pub fn qbundle(vectors: []const HybridBigInt, amplitudes: []const f32, allocator: std.mem.Allocator) !HybridBigInt { - _ = allocator; // Reserved for future allocation needs - if (vectors.len == 0) return HybridBigInt.zero(); - if (vectors.len == 1) { - const result = vectors[0]; - return result; - } - - // Validate amplitudes length - if (amplitudes.len != vectors.len) { - return error.AmplitudeLengthMismatch; - } - - // Normalize amplitudes (if not already) - var total_amp: f32 = 0.0; - for (amplitudes) |amp| total_amp += amp; - const normalized = if (total_amp > 0.0) total_amp else 1.0; - - // Use weighted bundle: each trit gets weighted votes - var result = HybridBigInt.zero(); - result.mode = .unpacked_mode; - result.dirty = true; - result.trit_len = @max(MAX_TRITS, @as(usize, @intFromFloat(normalized))); - - const num_full_chunks = result.trit_len / SIMD_WIDTH; - - var i: usize = 0; - while (i < num_full_chunks * SIMD_WIDTH) : (i += SIMD_WIDTH) { - var weighted_sum: [SIMD_WIDTH]f32 = undefined; - - for (0..SIMD_WIDTH) |j| { - var sum: f32 = 0.0; - for (vectors, 0..) |*vec, k| { - @constCast(vec).ensureUnpacked(); - if (i + j < vec.trit_len) { - const weight = amplitudes[k] / normalized; - if (vec.unpacked_cache) |cache| { - sum += @as(f32, @floatFromInt(cache[i + j])) * weight; - } - } - } - weighted_sum[j] = sum; - } - - // Quantize to ternary lattice (measurement collapse) - const zeros: @Vector(32, f32) = @splat(0.0); - const ones: @Vector(32, f32) = @splat(1.0); - const neg_ones: @Vector(32, f32) = @splat(-1.0); - - const weighted_vec: @Vector(32, f32) = weighted_sum; - const pos_mask = weighted_vec > zeros; - const neg_mask = weighted_vec < zeros; - - var out = zeros; - out = @select(f32, pos_mask, ones, out); - out = @select(f32, neg_mask, neg_ones, out); - - inline for (0..SIMD_WIDTH) |j| { - const float_val: f32 = out[j]; - const int_val: i32 = @intFromFloat(float_val); - if (result.unpacked_cache) |cache| { - cache[i + j] = @intCast(int_val); - } - } - } - - // Handle scalar tail using safe access - while (i < result.trit_len) : (i += 1) { - var sum: f32 = 0.0; - for (vectors, 0..) |*vec, k| { - @constCast(vec).ensureUnpacked(); - if (i < vec.trit_len) { - const weight = amplitudes[k] / normalized; - if (vec.unpacked_cache) |cache| { - sum += @as(f32, @floatFromInt(cache[i])) * weight; - } - } - } - - // Threshold-based quantization (collapse) - setTritSafe(&result, i, if (sum > 0.5) 1 else if (sum < -0.5) -1 else 0); - } - - return result; -} - -/// Measure: collapse superposition to classical ternary state -/// Samples from |ψ|² distribution (Born rule) -/// -/// In VSA framework: measurement = reading out hypervector -/// Quantum extension: probabilistic sampling based on amplitudes -/// For simplicity: deterministic collapse to quantized state -pub fn measure(qvec: *const HybridBigInt, rng: *std.Random) HybridBigInt { - _ = rng; // For future probabilistic measurement (const) - - // Born rule: P(state) = |α|² - // Sample from ternary distribution based on amplitudes - // For Trinity: collapse to quantized ternary value - - // In current implementation, HybridBigInt is already quantized - // This function returns a copy (collapsing any superposition metadata) - - const result = qvec.*; - - // In full implementation: - // 1. Extract "amplitude" from packed representation - // 2. Use threshold to decide {-1, 0, +1} - // 3. For probabilistic measurement, sample from distribution - - // For now: deterministic quantization (already collapsed) - return result; -} - -/// Quantum similarity with interference term -/// Includes phase-dependent interference: cos(phase_diff) -/// -/// In VSA framework: similarity = cosine similarity of hypervectors -/// Quantum extension: includes phase interference -/// sim_q = sim_classical × (1 + η·cos(Δφ)) -pub fn similarity_quantum(a: *const HybridBigInt, b: *const HybridBigInt, phase_diff: f32) f64 { - const classical_sim = cosineSimilarity(a, b); - - // Interference term: constructive (cos>0) or destructive (cos<0) - // η = 0.5 is interference strength - const interference = 0.5 * @cos(phase_diff); - - return classical_sim * (1.0 + interference); -} - -/// Apply phase shift to hypervector (for quantum interference) -/// Rotates the vector in the complex phase plane -pub fn applyPhase(vec: *const HybridBigInt, phase_shift: f32, allocator: std.mem.Allocator) !HybridBigInt { - _ = allocator; // Reserved for future allocation needs - // In a full quantum VSA, this would rotate complex amplitudes - // For ternary VSA, we simulate via permute-like operation - - // Number of trit positions to shift - const shift_amount = @abs(@as(i32, @intFromFloat(phase_shift * 10.0))) % @as(i32, @intCast(vec.trit_len)); - - const abs_shift = @abs(shift_amount); - - if (abs_shift > 0) { - return permute(vec, @as(usize, @intCast(abs_shift))); - } - - // No shift - return copy - const result = vec.*; - return result; -} - -/// Compute quantum coherence between multiple vectors -/// Returns value [0, 1] where 1 = fully coherent -pub fn computeCoherence(vectors: []const HybridBigInt) f32 { - if (vectors.len < 2) return 1.0; - - var total_sim: f64 = 0.0; - var count: usize = 0; - - for (0..vectors.len) |i| { - for (i + 1..vectors.len) |j| { - const sim = cosineSimilarityF16(&vectors[i], &vectors[j], std.heap.page_allocator); - total_sim += sim; - count += 1; - } - } - - return if (count > 0) @as(f32, @floatCast(total_sim / @as(f64, @floatFromInt(count)))) else 0.0; -} - -/// Entangle two hypervectors (correlated superposition) -/// Creates a combined state that maintains correlation -pub fn entangle(a: *const HybridBigInt, b: *const HybridBigInt, correlation: f32) struct { - left: HybridBigInt, - right: HybridBigInt, -} { - // Create correlated copies based on correlation strength - // correlation ∈ [0, 1]: 0 = independent, 1 = fully entangled - - var left = a.*; - var right = b.*; - - // Apply correlation: blend some trits using safe access - if (correlation > 0 and a.trit_len == b.trit_len) { - const num_entangled = @as(usize, @intFromFloat(@as(f32, @floatFromInt(a.trit_len)) * correlation)); - - for (0..num_entangled) |i| { - const idx = i; // Simple linear mapping - if (idx < a.trit_len and idx < b.trit_len) { - // Swap some trits to create correlation - setTritSafe(&left, idx, getTritSafe(@constCast(b), idx)); - setTritSafe(&right, idx, getTritSafe(@constCast(a), idx)); - } - } - } - - return .{ .left = left, .right = right }; -} - -// φ² + 1/φ² = 3 | TRINITY - -// ═══════════════════════════════════════════════════════════════════════════════ -// QUANTUM VSA TESTS -// ═══════════════════════════════════════════════════════════════════════════════ - -test "qbundle with amplitudes" { - const a = randomVector(100, 111); - const b = randomVector(100, 222); - const c = randomVector(100, 333); - - const amplitudes = [_]f32{ 1.0, 1.0, 1.0 }; - - // Create array of vectors (pass by value for qbundle API) - var vec_slice = [_]HybridBigInt{ a, b, c }; - - const result = try qbundle(&vec_slice, &litudes, std.testing.allocator); - - // Result should be valid ternary vector - try std.testing.expect(result.trit_len > 0); - // Check first 100 trits are in valid range using safe access - const check_len = @min(100, result.trit_len); - for (0..check_len) |i| { - const trit = getTritSafe(&result, i); - try std.testing.expect(trit >= -1 and trit <= 1); - } -} - -test "similarity_quantum with interference" { - var a = randomVector(100, 444); - var b = randomVector(100, 555); - - // Classical similarity - const sim_classical = cosineSimilarity(&a, &b); - - // Quantum similarity with constructive interference (phase_diff = 0) - const sim_constructive = similarity_quantum(&a, &b, 0.0); - - // Quantum similarity with destructive interference (phase_diff = π) - const sim_destructive = similarity_quantum(&a, &b, std.math.pi); - - // Constructive should enhance similarity - try std.testing.expect(sim_constructive >= sim_classical); - - // Destructive should reduce similarity - try std.testing.expect(sim_destructive <= sim_classical); -} - -test "computeCoherence" { - const v1 = randomVector(50, 123); - const v2 = randomVector(50, 124); - var v3 = randomVector(50, 125); - - // Set v3 to be similar to v1 using safe access - for (0..@min(v1.trit_len, v3.trit_len)) |i| { - if (i < v3.trit_len) setTritSafe(&v3, i, getTritSafe(&v1, i)); - } - - // Create array of vectors (pass by value for computeCoherence API) - var vec_slice = [_]HybridBigInt{ v1, v2, v3 }; - - const coherence = computeCoherence(&vec_slice); - - // Should have some coherence (> 0) - try std.testing.expect(coherence > 0.0); -} - -test "entangle with correlation" { - var a = randomVector(50, 666); - var b = randomVector(50, 777); - - const fully_entangled = entangle(&a, &b, 1.0); - - // With full correlation, vectors should share trits using safe access - try std.testing.expectEqual( - getTritSafe(&a, 0), - getTritSafe(&fully_entangled.right, 0), - ); - - const independent = entangle(&a, &b, 0.0); - - // With zero correlation, vectors should be copies - try std.testing.expectEqual( - if (a.unpacked_cache) |cache| cache[0] else 0, - if (independent.left.unpacked_cache) |cache| cache[0] else 0, - ); -} +//! Re-export. The implementation lives in gHashTag/zig-golden-float. +//! +//! This file used to be a second copy of that one. Both repositories carried +//! src/vsa/core.zig, they were edited independently, and they diverged -- which +//! is why repairing sixteen defects in golden-float (#97) left every one of +//! them standing here. Two maintained copies of the same code is how that +//! happens, and it happens quietly, because nothing reports it. +//! +//! The two public surfaces were identical when this was written, checked +//! symbol by symbol, so nothing is lost by pointing at one of them. Everything +//! that imported this path still imports this path; there is simply one +//! implementation behind it now, and it is the one that compiles. +pub usingnamespace @import("zig_golden_float").vsa; diff --git a/src/vsa/fpga_bind.zig b/src/vsa/fpga_bind.zig index 5fff7ca..ceb0708 100644 --- a/src/vsa/fpga_bind.zig +++ b/src/vsa/fpga_bind.zig @@ -1,486 +1,13 @@ -// 🤖 TRINITY v0.11.0: Suborbital Order -// FPGA VSA Bind Interface — Week 2 -// -// Provides Zig interface to FPGA-accelerated VSA operations -// via UART communication with QMTECH XC7A100T - -const std = @import("std"); -const builtin = @import("builtin"); -const common = @import("common.zig"); -const HybridBigInt = common.HybridBigInt; -const Trit = common.Trit; - -pub const Config = struct { - /// UART device path - device: []const u8, - /// Baud rate - baud: u32 = 115200, - /// Vector dimension (must match FPGA) - dimension: usize = 256, - /// Timeout in milliseconds - timeout_ms: u32 = 5000, -}; - -pub const FPGAInterface = struct { - port: std.fs.File, - config: Config, - allocator: std.mem.Allocator, - - const Self = @This(); - - /// Initialize FPGA interface - pub fn init(config: Config, allocator: std.mem.Allocator) !Self { - const device_path = if (builtin.os.tag == .linux) - "/dev/ttyUSB0" - else if (builtin.os.tag == .macos) - "/dev/tty.usbserial-.*" - else - return error.UnsupportedOS; - - // Try to open the UART device - const port = std.fs.openFileAbsolute(device_path, .{ - .read = true, - .write = true, - }) catch |err| { - std.log.err("Failed to open FPGA UART device: {}", .{err}); - return err; - }; - - return Self{ - .port = port, - .config = config, - .allocator = allocator, - }; - } - - /// Close FPGA interface - pub fn deinit(self: *Self) void { - self.port.close(); - } - - // ═══════════════════════════════════════════════════════════════════════ - // UART PROTOCOL - // ═══════════════════════════════════════════════════════════════════════ - - const Command = enum(u8) { - BIND = 0x01, - UNBIND = 0x02, - BUNDLE2 = 0x03, - BUNDLE3 = 0x04, - SIMILARITY = 0x05, - PING = 0xFF, - }; - - const Response = enum(u8) { - OK = 0x00, - ERROR = 0x01, - BUSY = 0x02, - PONG = 0xFF, - }; - - /// Send command to FPGA - fn sendCommand(self: *Self, cmd: Command, data: []const u8) !void { - var buffer: [1024]u8 = undefined; - var offset: usize = 0; - - buffer[offset] = @intFromEnum(cmd); - offset += 1; - - buffer[offset] = @intCast(data.len & 0xFF); - offset += 1; - - @memcpy(buffer[offset..][0..data.len], data); - offset += data.len; - - // Simple checksum - var checksum: u8 = 0; - for (buffer[0..offset]) |b| checksum ^= b; - buffer[offset] = checksum; - offset += 1; - - _ = try self.port.writeAll(buffer[0..offset]); - } - - /// Receive response from FPGA - fn recvResponse(self: *Self, expected_len: usize) ![]u8 { - _ = expected_len; - var buffer: [1024]u8 = undefined; - const header_len = 2; // status + len - - const n = try self.port.readAll(buffer[0..header_len]); - if (n < header_len) return error.ShortRead; - - const status = buffer[0]; - const len = buffer[1]; - - if (status == @intFromEnum(Response.ERROR)) { - return error.FPGAError; - } - - if (len > 0) { - const n2 = try self.port.readAll(buffer[header_len .. header_len + len]); - if (n2 < len) return error.ShortRead; - } - - // Verify checksum - // ... - - return buffer[0 .. header_len + len]; - } - - // ═══════════════════════════════════════════════════════════════════════ - // VSA OPERATIONS - // ═══════════════════════════════════════════════════════════════════════ - - /// Bind two hypervectors on FPGA - pub fn bind(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !HybridBigInt { - a.ensureUnpacked(); - b.ensureUnpacked(); - - const dim = @min(self.config.dimension, @max(a.trit_len, b.trit_len)); - - // Pack trits into 2-bit format for FPGA - const bytes_needed = (dim * 2 + 7) / 8; - var buffer = try self.allocator.alloc(u8, bytes_needed * 2); - defer self.allocator.free(buffer); - - // Pack vector A - for (0..dim) |i| { - const trit_val: i2 = if (i < a.trit_len) a.unpacked_cache[i] else 0; - const encoded = encodeTrit(trit_val); - const byte_idx = (i * 2) / 8; - const bit_offset = (i * 2) % 8; - buffer[byte_idx] |= encoded << bit_offset; - if (bit_offset >= 6) { - buffer[byte_idx + 1] |= encoded >> (8 - bit_offset); - } - } - - // Pack vector B (offset by bytes_needed) - const b_offset = bytes_needed; - for (0..dim) |i| { - const trit_val: i2 = if (i < b.trit_len) b.unpacked_cache[i] else 0; - const encoded = encodeTrit(trit_val); - const byte_idx = b_offset + (i * 2) / 8; - const bit_offset = (i * 2) % 8; - buffer[byte_idx] |= encoded << bit_offset; - if (bit_offset >= 6) { - buffer[byte_idx + 1] |= encoded >> (8 - bit_offset); - } - } - - // Send BIND command - try self.sendCommand(Command.BIND, buffer[0 .. bytes_needed * 2]); - - // Receive result - const response = try self.recvResponse(bytes_needed); - - // Unpack result - var result = HybridBigInt.zero(); - result.mode = .unpacked_mode; - result.trit_len = dim; - - for (0..dim) |i| { - const byte_idx = 2 + (i * 2) / 8; - const bit_offset = (i * 2) % 8; - const encoded = (response[byte_idx] >> bit_offset) & 0x03; - result.unpacked_cache[i] = decodeTrit(encoded); - } - - return result; - } - - /// Check if FPGA is responsive - pub fn ping(self: *Self) !bool { - try self.sendCommand(Command.PING, &[_]u8{}); - const response = try self.recvResponse(0); - return response[0] == @intFromEnum(Response.PONG); - } - - /// Bundle two hypervectors on FPGA (majority voting) - pub fn bundle(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !HybridBigInt { - a.ensureUnpacked(); - b.ensureUnpacked(); - - const dim = @min(self.config.dimension, @max(a.trit_len, b.trit_len)); - const bytes_needed = (dim * 2 + 7) / 8; - var buffer = try self.allocator.alloc(u8, bytes_needed * 2); - defer self.allocator.free(buffer); - - // Pack vectors (same as bind) - for (0..dim) |i| { - const trit_val: i2 = if (i < a.trit_len) a.unpacked_cache[i] else 0; - const encoded = encodeTrit(trit_val); - const byte_idx = (i * 2) / 8; - const bit_offset = (i * 2) % 8; - buffer[byte_idx] |= encoded << bit_offset; - if (bit_offset >= 6) { - buffer[byte_idx + 1] |= encoded >> (8 - bit_offset); - } - } - - const b_offset = bytes_needed; - for (0..dim) |i| { - const trit_val: i2 = if (i < b.trit_len) b.unpacked_cache[i] else 0; - const encoded = encodeTrit(trit_val); - const byte_idx = b_offset + (i * 2) / 8; - const bit_offset = (i * 2) % 8; - buffer[byte_idx] |= encoded << bit_offset; - if (bit_offset >= 6) { - buffer[byte_idx + 1] |= encoded >> (8 - bit_offset); - } - } - - // Send BUNDLE command - try self.sendCommand(Command.BUNDLE2, buffer[0 .. bytes_needed * 2]); - - // Receive result (same format as bind) - const response = try self.recvResponse(bytes_needed); - - // Unpack result - var result = HybridBigInt.zero(); - result.mode = .unpacked_mode; - result.trit_len = dim; - - for (0..dim) |i| { - const byte_idx = 2 + (i * 2) / 8; - const bit_offset = (i * 2) % 8; - const encoded = (response[byte_idx] >> bit_offset) & 0x03; - result.unpacked_cache[i] = decodeTrit(encoded); - } - - return result; - } - - /// Compute dot product similarity on FPGA - pub fn similarity(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !f64 { - a.ensureUnpacked(); - b.ensureUnpacked(); - - const dim = @min(self.config.dimension, @max(a.trit_len, b.trit_len)); - const bytes_needed = (dim * 2 + 7) / 8; - var buffer = try self.allocator.alloc(u8, bytes_needed * 2); - defer self.allocator.free(buffer); - - // Pack vectors - for (0..dim) |i| { - const trit_val: i2 = if (i < a.trit_len) a.unpacked_cache[i] else 0; - const encoded = encodeTrit(trit_val); - const byte_idx = (i * 2) / 8; - const bit_offset = (i * 2) % 8; - buffer[byte_idx] |= encoded << bit_offset; - if (bit_offset >= 6) { - buffer[byte_idx + 1] |= encoded >> (8 - bit_offset); - } - } - - const b_offset = bytes_needed; - for (0..dim) |i| { - const trit_val: i2 = if (i < b.trit_len) b.unpacked_cache[i] else 0; - const encoded = encodeTrit(trit_val); - const byte_idx = b_offset + (i * 2) / 8; - const bit_offset = (i * 2) % 8; - buffer[byte_idx] |= encoded << bit_offset; - if (bit_offset >= 6) { - buffer[byte_idx + 1] |= encoded >> (8 - bit_offset); - } - } - - // Send SIMILARITY command - try self.sendCommand(Command.SIMILARITY, buffer[0 .. bytes_needed * 2]); - - // Receive result (3 bytes: status + dot LSB + dot MSB) - const response = try self.recvResponse(3); - - // Parse dot product (signed 11-bit: -256 to +256) - const dot_lsb = response[1]; - const dot_msb = response[2] & 0x07; - var dot: i11 = @as(i11, @bitCast(@as(u11, @intCast(dot_msb)) << 8 | dot_lsb)); - // Sign extend - if (dot_msb & 0x04) dot |= 0xF800; - - // Normalize by dimension (cosine similarity for unit vectors) - // For raw similarity: just return dot / dim - return @as(f64, @floatFromInt(dot)) / @as(f64, @floatFromInt(dim)); - } - - // ═══════════════════════════════════════════════════════════════════════ - // TRIT ENCODING - // ═══════════════════════════════════════════════════════════════════════ - - /// Encode trit to 2-bit format - inline fn encodeTrit(t: i2) u2 { - return switch (t) { - 0 => 0b00, - 1 => 0b01, - -1 => 0b10, - else => unreachable, - }; - } - - /// Decode trit from 2-bit format - inline fn decodeTrit(e: u2) i2 { - return switch (e) { - 0b00 => 0, - 0b01 => 1, - 0b10 => -1, - else => 0, - }; - } -}; - -// ═════════════════════════════════════════════════════════════════════════════ -// CPU FALLBACK (when FPGA unavailable) -// ═════════════════════════════════════════════════════════════════════════════ - -pub const CpuFallback = struct { - allocator: std.mem.Allocator, - - const Self = @This(); - - pub fn init(allocator: std.mem.Allocator) Self { - return Self{ .allocator = allocator }; - } - - /// Bind using CPU (simulates FPGA behavior) - pub fn bind(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !HybridBigInt { - _ = self; - const core = @import("core.zig"); - return core.bind(a, b); - } - - /// Bundle using CPU - pub fn bundle(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !HybridBigInt { - _ = self; - const core = @import("core.zig"); - return core.bundle2(a, b); - } - - /// Similarity using CPU - pub fn similarity(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !f64 { - _ = self; - const core = @import("core.zig"); - return core.cosineSimilarity(a, b); - } -}; - -// ═════════════════════════════════════════════════════════════════════════════ -// UNIFIED INTERFACE (auto-detect FPGA) -// ═════════════════════════════════════════════════════════════════════════════ - -pub const AutoVSA = struct { - fpga: ?FPGAInterface, - cpu: CpuFallback, - use_fpga: bool, - - const Self = @This(); - - /// Initialize with auto-detection - pub fn init(config: Config, allocator: std.mem.Allocator) Self { - const fpga = FPGAInterface.init(config, allocator) catch |err| { - std.log.warn("FPGA unavailable ({}), using CPU fallback", .{err}); - return Self{ - .fpga = null, - .cpu = CpuFallback.init(allocator), - .use_fpga = false, - }; - }; - - // Verify FPGA is responsive - if (fpga.ping() catch false) { - std.log.info("FPGA VSA accelerator detected", .{}); - return Self{ - .fpga = fpga, - .cpu = CpuFallback.init(allocator), - .use_fpga = true, - }; - } else { - fpga.deinit(); - std.log.warn("FPGA not responsive, using CPU fallback", .{}); - return Self{ - .fpga = null, - .cpu = CpuFallback.init(allocator), - .use_fpga = false, - }; - } - } - - pub fn deinit(self: *Self) void { - if (self.fpga) |*f| f.deinit(); - } - - /// Bind with automatic FPGA/CPU selection - pub fn bind(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !HybridBigInt { - if (self.use_fpga and self.fpga != null) { - return self.fpga.?.bind(a, b); - } else { - return self.cpu.bind(a, b); - } - } - - /// Bundle with automatic FPGA/CPU selection - pub fn bundle(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !HybridBigInt { - if (self.use_fpga and self.fpga != null) { - return self.fpga.?.bundle(a, b); - } else { - return self.cpu.bundle(a, b); - } - } - - /// Similarity with automatic FPGA/CPU selection - pub fn similarity(self: *Self, a: *HybridBigInt, b: *HybridBigInt) !f64 { - if (self.use_fpga and self.fpga != null) { - return self.fpga.?.similarity(a, b); - } else { - return self.cpu.similarity(a, b); - } - } -}; - -// Backward compatibility alias -pub const AutoBind = AutoVSA; - -// ═════════════════════════════════════════════════════════════════════════════ -// TESTS -// ═════════════════════════════════════════════════════════════════════════════ - -test "fpga bind: trit encoding" { - const testing = std.testing; - - try testing.expectEqual(@as(u2, 0b00), FPGAInterface.encodeTrit(0)); - try testing.expectEqual(@as(u2, 0b01), FPGAInterface.encodeTrit(1)); - try testing.expectEqual(@as(u2, 0b10), FPGAInterface.encodeTrit(-1)); -} - -test "fpga bind: trit decoding" { - const testing = std.testing; - - try testing.expectEqual(@as(i2, 0), FPGAInterface.decodeTrit(0b00)); - try testing.expectEqual(@as(i2, 1), FPGAInterface.decodeTrit(0b01)); - try testing.expectEqual(@as(i2, -1), FPGAInterface.decodeTrit(0b10)); -} - -test "fpga bind: cpu fallback" { - const testing = std.testing; - - var cpu = CpuFallback.init(testing.allocator); - - var a = HybridBigInt.zero(); - a.mode = .unpacked_mode; - a.trit_len = 16; - - var b = HybridBigInt.zero(); - b.mode = .unpacked_mode; - b.trit_len = 16; - - // Fill with test data - for (0..16) |i| { - a.unpacked_cache[i] = @intCast(@rem(i, 3) - 1); - b.unpacked_cache[i] = @intCast(@rem(i + 1, 3) - 1); - } - - const result = try cpu.bind(&a, &b); - try testing.expectEqual(@as(usize, 16), result.trit_len); -} - -// φ² + 1/φ² = 3 = TRINITY +//! Re-export. The implementation lives in gHashTag/zig-golden-float. +//! +//! This file used to be a second copy of that one. Both repositories carried +//! src/vsa/fpga_bind.zig, they were edited independently, and they diverged -- which +//! is why repairing sixteen defects in golden-float (#97) left every one of +//! them standing here. Two maintained copies of the same code is how that +//! happens, and it happens quietly, because nothing reports it. +//! +//! The two public surfaces were identical when this was written, checked +//! symbol by symbol, so nothing is lost by pointing at one of them. Everything +//! that imported this path still imports this path; there is simply one +//! implementation behind it now, and it is the one that compiles. +pub usingnamespace @import("zig_golden_float").fpga_bind; diff --git a/src/vsa/hrr.zig b/src/vsa/hrr.zig index 6457ed1..1f5a1d9 100644 --- a/src/vsa/hrr.zig +++ b/src/vsa/hrr.zig @@ -1,412 +1,13 @@ -//! ═══════════════════════════════════════════════════════════════════════════════ -//! HRR — Holographic Reduced Representations -//! ═══════════════════════════════════════════════════════════════════════════════ -//! -//! Vector Symbolic Architecture (VSA) using Holographic Reduced Representations. -//! High-dimensional vectors for symbolic reasoning and cognitive computing. -//! -//! Features: -//! - Random vector generation with Gaussian distribution -//! - Binding via circular convolution -//! - Unbinding (inverse binding) -//! - Bundling (superposition of vectors) -//! - Similarity (cosine distance) -//! - Vector normalization -//! -//! References: -//! - Plate, R. (1995). "Holographic Reduced Representations" -//! - Kanerva, P. (2009). "Hyperdimensional Computing" -//! -//! φ² + 1/φ² = 3 = TRINITY -//! ═══════════════════════════════════════════════════════════════════════════════ - -const std = @import("std"); -const math = std.math; -const Allocator = std.mem.Allocator; -const random = std.crypto.random; - -/// ═══════════════════════════════════════════════════════════════════════════════ -/// SACRED CONSTANTS FOR HRR -/// ═══════════════════════════════════════════════════════════════════════════════ -const PHI: f64 = 1.618033988749895; // Golden Ratio -const PHI_INV: f64 = 0.618033988749895; // φ⁻¹ - -/// ═══════════════════════════════════════════════════════════════════════════════ -/// HRR — Holographic Reduced Representations -/// ═══════════════════════════════════════════════════════════════════════════════ -pub const HRR = struct { - dim: usize, - allocator: Allocator, - - pub const Error = error{ - DimensionMismatch, - EmptyVector, - InvalidVector, - }; - - /// Initialize HRR with given dimensionality - pub fn init(allocator: Allocator, dim: usize) !HRR { - if (dim < 8) return Error.InvalidVector; - return .{ - .dim = dim, - .allocator = allocator, - }; - } - - /// Initialize with φ-based dimension (phi-powered) - pub fn initPhi(allocator: Allocator, power: u32) !HRR { - // Dimensions that are powers of φ (rounded) - const base_dim: f64 = 1000.0; - const phi_factor = std.math.pow(f64, PHI, @as(f64, @floatFromInt(power))); - const dim: usize = @intFromFloat(base_dim * phi_factor); - return init(allocator, dim); - } - - /// ═══════════════════════════════════════════════════════════════════════════════ - /// VECTOR OPERATIONS - /// ═══════════════════════════════════════════════════════════════════════════════ - /// Generate random high-dimensional vector with Gaussian distribution - pub fn randomVector(self: *const HRR) ![]f32 { - var vec = try self.allocator.alloc(f32, self.dim); - - // Generate using Box-Muller transform for Gaussian distribution - var i: usize = 0; - while (i < self.dim) : (i += 2) { - // Generate uniform random floats in (0, 1] - const u1_raw: f32 = random.float(f32); - const u2_raw: f32 = random.float(f32); - - // Avoid log(0) and ensure valid range - const u1_safe = if (u1_raw <= 0.0) 1.0e-6 else if (u1_raw >= 1.0) 0.999999 else u1_raw; - const u2_safe = if (u2_raw <= 0.0) 0.0 else if (u2_raw >= 1.0) 0.999999 else u2_raw; - - const r = @sqrt(-2.0 * @log(u1_safe)); - const theta = 2.0 * math.pi * u2_safe; - - vec[i] = r * @cos(theta); - if (i + 1 < self.dim) { - vec[i + 1] = r * @sin(theta); - } - } - - return self.normalize(vec); - } - - /// Generate deterministic vector from seed string (for encoding) - pub fn seededVector(self: *const HRR, seed: []const u8) ![]f32 { - var vec = try self.allocator.alloc(f32, self.dim); - - // Simple hash-based generation (djb2 algorithm with wrapping) - var hash: u32 = 5381; - for (seed) |c| { - hash = hash *% 33 +% @as(u8, @intCast(c)); - } - - var prng = std.Random.DefaultPrng.init(hash); - var i: usize = 0; - while (i < self.dim) : (i += 2) { - const u1_val = prng.random().float(f32); - const u2_val = prng.random().float(f32); - const u1_safe = if (u1_val > 1.0e-6) u1_val else 1.0e-6; - - const r = @sqrt(-2.0 * @log(u1_safe)); - const theta = 2.0 * math.pi * u2_val; - - vec[i] = r * @cos(theta); - if (i + 1 < self.dim) { - vec[i + 1] = r * @sin(theta); - } - } - - return self.normalize(vec); - } - - /// Bind two vectors using circular convolution - /// This creates an associative binding operation - pub fn bind(self: *const HRR, a: []const f32, b: []const f32) ![]f32 { - if (a.len != self.dim or b.len != self.dim) return Error.DimensionMismatch; - - const result = try self.allocator.alloc(f32, self.dim); - - // Circular convolution - // result[k] = sum(a[i] * b[(k-i) mod dim]) - for (0..self.dim) |k| { - var sum: f32 = 0; - for (0..self.dim) |i| { - const j = if (k >= i) k - i else self.dim + k - i; - sum += a[i] * b[j]; - } - result[k] = sum; - } - - return self.normalize(result); - } - - /// Unbind (inverse binding) — for HRR, inverse is the reversed vector - pub fn unbind(self: *const HRR, bound: []const f32, known: []const f32) ![]f32 { - if (bound.len != self.dim or known.len != self.dim) return Error.DimensionMismatch; - - // For HRR circular convolution, the inverse is the reversed vector - // Unbind(a ⊗ b, b) should recover a (approximately) - const result = try self.allocator.alloc(f32, self.dim); - - // Reverse the known vector (true inverse for circular convolution) - // Then convolve with bound vector - for (0..self.dim) |k| { - var sum: f32 = 0; - for (0..self.dim) |i| { - // For inverse: known_rev[j] = known[(dim - j) % dim] - const j = if (k >= i) k - i else self.dim + k - i; - const inv_idx = (self.dim - j) % self.dim; - sum += bound[i] * known[inv_idx]; - } - result[k] = sum; - } - - return self.normalize(result); - } - - /// Bundle (superposition) multiple vectors - pub fn bundle(self: *const HRR, vectors: []const []const f32) ![]f32 { - if (vectors.len == 0) return Error.EmptyVector; - - const result = try self.allocator.alloc(f32, self.dim); - @memset(result, 0); - - // Sum all vectors - for (vectors) |vec| { - if (vec.len != self.dim) return Error.DimensionMismatch; - for (result, 0..) |*r, i| { - r.* += vec[i]; - } - } - - return self.normalize(result); - } - - /// Compute cosine similarity between two vectors - pub fn similarity(self: *const HRR, a: []const f32, b: []const f32) !f32 { - if (a.len != self.dim or b.len != self.dim) return Error.DimensionMismatch; - - var dot: f32 = 0; - var norm_a: f32 = 0; - var norm_b: f32 = 0; - - for (a, 0..) |av, i| { - dot += av * b[i]; - norm_a += av * av; - norm_b += b[i] * b[i]; - } - - const denom = @sqrt(norm_a * norm_b); - return if (denom > 1.0e-6) dot / denom else 0; - } - - /// Normalize vector to unit length - fn normalize(_: *const HRR, vec: []f32) []f32 { - var norm: f32 = 0; - for (vec) |v| { - norm += v * v; - } - norm = @sqrt(norm); - - if (norm > 1.0e-6) { - const inv_norm = 1.0 / norm; - for (vec) |*v| { - v.* *= inv_norm; - } - } - - return vec; - } - - /// Compute Hamming distance (for binary-like comparison) - pub fn hammingDistance(self: *const HRR, a: []const f32, b: []const f32) !usize { - if (a.len != self.dim or b.len != self.dim) return Error.DimensionMismatch; - - var distance: usize = 0; - for (a, b) |av, bv| { - // Count as different if signs differ - if ((av >= 0) != (bv >= 0)) { - distance += 1; - } - } - - return distance; - } - - /// Cleanup vector - pub fn freeVector(self: *const HRR, vec: []f32) void { - self.allocator.free(vec); - } -}; - -// ═══════════════════════════════════════════════════════════════════════════════ -// TESTS -// ═══════════════════════════════════════════════════════════════════════════════ - -test "HRR — Vector Generation" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec1 = try hrr.randomVector(); - defer hrr.freeVector(vec1); - - // Check dimension - try testing.expectEqual(@as(usize, 1000), vec1.len); - - // Check normalization (should be close to 1) - var norm: f32 = 0; - for (vec1) |v| { - norm += v * v; - } - try testing.expectApproxEqAbs(1.0, norm, 0.01); -} - -test "HRR — Deterministic Seeded Vector" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec1 = try hrr.seededVector("test"); - defer hrr.freeVector(vec1); - - const vec2 = try hrr.seededVector("test"); - defer hrr.freeVector(vec2); - - // Same seed should produce same vector - try testing.expectEqualSlices(f32, vec1, vec2); -} - -test "HRR — Binding Similarity" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec_a = try hrr.seededVector("alice"); - defer hrr.freeVector(vec_a); - const vec_b = try hrr.seededVector("bob"); - defer hrr.freeVector(vec_b); - - const bound = try hrr.bind(vec_a, vec_b); - defer hrr.freeVector(bound); - - // Binding should be order-independent for HRR - const bound2 = try hrr.bind(vec_b, vec_a); - defer hrr.freeVector(bound2); - - const sim = try hrr.similarity(bound, bound2); - - // Should be nearly identical - try testing.expect(sim > 0.9); -} - -test "HRR — Unbinding Recovery" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec_a = try hrr.seededVector("alice"); - defer hrr.freeVector(vec_a); - const vec_b = try hrr.seededVector("bob"); - defer hrr.freeVector(vec_b); - - const bound = try hrr.bind(vec_a, vec_b); - defer hrr.freeVector(bound); - - const recovered = try hrr.unbind(bound, vec_a); - defer hrr.freeVector(recovered); - - // Recovered should be similar to original - const sim = try hrr.similarity(vec_b, recovered); - - // Should have good similarity (unbinding is approximate) - try testing.expect(sim > 0.5); -} - -test "HRR — Bundle Orthogonality" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec1 = try hrr.seededVector("vector1"); - defer hrr.freeVector(vec1); - const vec2 = try hrr.seededVector("vector2"); - defer hrr.freeVector(vec2); - - // Bundled vector - const bundled = try hrr.bundle(&[_][]const f32{ vec1, vec2 }); - defer hrr.freeVector(bundled); - - // Similarity to individual vectors should be moderate - // (not too high, not too low) - const sim1 = try hrr.similarity(bundled, vec1); - const sim2 = try hrr.similarity(bundled, vec2); - - // Similarity should be positive but less than 1 - try testing.expect(sim1 > 0 and sim1 < 1.0); - try testing.expect(sim2 > 0 and sim2 < 1.0); -} - -test "HRR — Similarity Reflexive" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec = try hrr.randomVector(); - defer hrr.freeVector(vec); - - // Vector should be perfectly similar to itself - const sim = try hrr.similarity(vec, vec); - - try testing.expectApproxEqAbs(1.0, sim, 0.001); -} - -test "HRR — Hamming Distance" { - const testing = std.testing; - - var hrr = try HRR.init(testing.allocator, 1000); - - const vec1 = try hrr.randomVector(); - defer hrr.freeVector(vec1); - const vec2 = try hrr.randomVector(); - defer hrr.freeVector(vec2); - - // Hamming distance should be valid - const dist = try hrr.hammingDistance(vec1, vec2); - - try testing.expect(dist >= 0 and dist <= 1000); -} - -test "HRR — Phi-Dimension Initialization" { - const testing = std.testing; - - // Initialize with φ^1 = 1618 dimensions (approximately) - const hrr = try HRR.initPhi(testing.allocator, 1); - - // Should be close to 1618 - try testing.expect(hrr.dim >= 1500 and hrr.dim <= 1800); -} - -test "HRR — Sacred Constant Integration" { - const testing = std.testing; - - const hrr = try HRR.init(testing.allocator, 1000); - - // Create vectors representing sacred concepts - const phi_vec = try hrr.seededVector("phi"); - defer hrr.freeVector(phi_vec); - const trinity_vec = try hrr.seededVector("trinity"); - defer hrr.freeVector(trinity_vec); - - // Bind phi and trinity - const bound = try hrr.bind(phi_vec, trinity_vec); - defer hrr.freeVector(bound); - - // Verify binding creates a distinct representation - const sim = try hrr.similarity(phi_vec, bound); - - // Should be different but related - try testing.expect(sim < 0.9); -} +//! Re-export. The implementation lives in gHashTag/zig-golden-float. +//! +//! This file used to be a second copy of that one. Both repositories carried +//! src/vsa/hrr.zig, they were edited independently, and they diverged -- which +//! is why repairing sixteen defects in golden-float (#97) left every one of +//! them standing here. Two maintained copies of the same code is how that +//! happens, and it happens quietly, because nothing reports it. +//! +//! The two public surfaces were identical when this was written, checked +//! symbol by symbol, so nothing is lost by pointing at one of them. Everything +//! that imported this path still imports this path; there is simply one +//! implementation behind it now, and it is the one that compiles. +pub usingnamespace @import("zig_golden_float").hrr; From 0afbcb23ab2275082ff76f42cae300078b37f132 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 00:57:16 +0700 Subject: [PATCH 2/8] Name the re-exports one by one: usingnamespace is gone in 0.15 The first attempt re-exported each duplicated file with usingnamespace, which was removed in Zig 0.15 -- the version this package targets -- so the build answered 'expected function or variable declaration after pub'. The names are listed individually now, generated from the upstream sources rather than typed. That is a real cost and worth naming: a symbol added upstream does not appear here until it is added here too. It is still cheaper than a second implementation, and unlike a second implementation it fails loudly, because the name is simply missing rather than quietly different. MAX_PACKED_BYTES has no counterpart in golden-float's vsa/common.zig, so it comes from packed_trit, where the value actually lives, rather than being written out as a literal. Nothing here uses it, but it was in this module's public surface and dropping it silently would break a consumer who does. --- src/vsa/10k_vsa.zig | 34 ++++++++++++++++++++++--------- src/vsa/common.zig | 34 ++++++++++++++++++++++--------- src/vsa/concurrency.zig | 43 +++++++++++++++++++++++++++++++--------- src/vsa/core.zig | 44 ++++++++++++++++++++++++++++++++--------- src/vsa/fpga_bind.zig | 25 ++++++++++++++--------- src/vsa/hrr.zig | 21 +++++++++++--------- 6 files changed, 147 insertions(+), 54 deletions(-) diff --git a/src/vsa/10k_vsa.zig b/src/vsa/10k_vsa.zig index 6313c1b..3e28efa 100644 --- a/src/vsa/10k_vsa.zig +++ b/src/vsa/10k_vsa.zig @@ -1,13 +1,29 @@ //! Re-export. The implementation lives in gHashTag/zig-golden-float. //! //! This file used to be a second copy of that one. Both repositories carried -//! src/vsa/10k_vsa.zig, they were edited independently, and they diverged -- which -//! is why repairing sixteen defects in golden-float (#97) left every one of -//! them standing here. Two maintained copies of the same code is how that -//! happens, and it happens quietly, because nothing reports it. +//! src/vsa/10k_vsa.zig, they were edited independently, and they diverged -- which is +//! why repairing sixteen defects in golden-float (#97) left every one of them +//! standing here. Two maintained copies of the same code is how that happens, +//! and it happens quietly, because nothing reports it. //! -//! The two public surfaces were identical when this was written, checked -//! symbol by symbol, so nothing is lost by pointing at one of them. Everything -//! that imported this path still imports this path; there is simply one -//! implementation behind it now, and it is the one that compiles. -pub usingnamespace @import("zig_golden_float").vsa_10k; +//! The names are listed one by one because `usingnamespace` was removed in Zig +//! 0.15, which is the version this package targets. That is a cost: a name added +//! there does not appear here until it is added here too. It is still cheaper +//! than a second implementation, and unlike a second implementation it fails +//! loudly -- the name is simply missing rather than quietly different. +const upstream = @import("zig_golden_float").vsa_10k; + +pub const Trit = upstream.Trit; +pub const HybridBigInt = upstream.HybridBigInt; +pub const DIM_10K = upstream.DIM_10K; +pub const BYTES_PER_10K = upstream.BYTES_PER_10K; +pub const WORDS_32BIT = upstream.WORDS_32BIT; +pub const BRAM_SIZE = upstream.BRAM_SIZE; +pub const VECTORS_PER_BRAM = upstream.VECTORS_PER_BRAM; +pub const TRIT_NEG = upstream.TRIT_NEG; +pub const TRIT_ZERO = upstream.TRIT_ZERO; +pub const TRIT_POS = upstream.TRIT_POS; +pub const HyperVector10K = upstream.HyperVector10K; +pub const BenchmarkResult = upstream.BenchmarkResult; +pub const benchmark = upstream.benchmark; +pub const printBenchmark = upstream.printBenchmark; diff --git a/src/vsa/common.zig b/src/vsa/common.zig index f7ba089..9366d76 100644 --- a/src/vsa/common.zig +++ b/src/vsa/common.zig @@ -1,13 +1,29 @@ //! Re-export. The implementation lives in gHashTag/zig-golden-float. //! //! This file used to be a second copy of that one. Both repositories carried -//! src/vsa/common.zig, they were edited independently, and they diverged -- which -//! is why repairing sixteen defects in golden-float (#97) left every one of -//! them standing here. Two maintained copies of the same code is how that -//! happens, and it happens quietly, because nothing reports it. +//! src/vsa/common.zig, they were edited independently, and they diverged -- which is +//! why repairing sixteen defects in golden-float (#97) left every one of them +//! standing here. Two maintained copies of the same code is how that happens, +//! and it happens quietly, because nothing reports it. //! -//! The two public surfaces were identical when this was written, checked -//! symbol by symbol, so nothing is lost by pointing at one of them. Everything -//! that imported this path still imports this path; there is simply one -//! implementation behind it now, and it is the one that compiles. -pub usingnamespace @import("zig_golden_float").vsa_common; +//! The names are listed one by one because `usingnamespace` was removed in Zig +//! 0.15, which is the version this package targets. That is a cost: a name added +//! there does not appear here until it is added here too. It is still cheaper +//! than a second implementation, and unlike a second implementation it fails +//! loudly -- the name is simply missing rather than quietly different. +const upstream = @import("zig_golden_float").vsa_common; + +pub const HybridBigInt = upstream.HybridBigInt; +pub const Trit = upstream.Trit; +pub const Vec32i8 = upstream.Vec32i8; +pub const SIMD_WIDTH = upstream.SIMD_WIDTH; +pub const MAX_TRITS = upstream.MAX_TRITS; +pub const SearchResult = upstream.SearchResult; + +// golden-float's vsa/common.zig has no counterpart for this one, so it cannot +// come from the re-export above. It is taken from where the value actually +// lives -- packed_trit -- rather than written out as a literal, so there is +// still exactly one definition of it. Nothing inside this repository uses it, +// but it was part of this module's public surface before the deduplication and +// dropping it silently would break somebody outside who does. +pub const MAX_PACKED_BYTES = @import("zig_golden_float").packed_trit.MAX_PACKED_BYTES; diff --git a/src/vsa/concurrency.zig b/src/vsa/concurrency.zig index 11eae6b..c4dbbd2 100644 --- a/src/vsa/concurrency.zig +++ b/src/vsa/concurrency.zig @@ -1,13 +1,38 @@ //! Re-export. The implementation lives in gHashTag/zig-golden-float. //! //! This file used to be a second copy of that one. Both repositories carried -//! src/vsa/concurrency.zig, they were edited independently, and they diverged -- which -//! is why repairing sixteen defects in golden-float (#97) left every one of -//! them standing here. Two maintained copies of the same code is how that -//! happens, and it happens quietly, because nothing reports it. +//! src/vsa/concurrency.zig, they were edited independently, and they diverged -- which is +//! why repairing sixteen defects in golden-float (#97) left every one of them +//! standing here. Two maintained copies of the same code is how that happens, +//! and it happens quietly, because nothing reports it. //! -//! The two public surfaces were identical when this was written, checked -//! symbol by symbol, so nothing is lost by pointing at one of them. Everything -//! that imported this path still imports this path; there is simply one -//! implementation behind it now, and it is the one that compiles. -pub usingnamespace @import("zig_golden_float").vsa_concurrency; +//! The names are listed one by one because `usingnamespace` was removed in Zig +//! 0.15, which is the version this package targets. That is a cost: a name added +//! there does not appear here until it is added here too. It is still cheaper +//! than a second implementation, and unlike a second implementation it fails +//! loudly -- the name is simply missing rather than quietly different. +const upstream = @import("zig_golden_float").vsa_concurrency; + +pub const POOL_SIZE = upstream.POOL_SIZE; +pub const DEQUE_CAPACITY = upstream.DEQUE_CAPACITY; +pub const MAX_WORKERS = upstream.MAX_WORKERS; +pub const PRIORITY_LEVELS = upstream.PRIORITY_LEVELS; +pub const PRIORITY_QUEUE_CAPACITY = upstream.PRIORITY_QUEUE_CAPACITY; +pub const MAX_JOB_AGE = upstream.MAX_JOB_AGE; +pub const MAX_DAG_NODES = upstream.MAX_DAG_NODES; +pub const MAX_DEPENDENCIES = upstream.MAX_DEPENDENCIES; +pub const PHI_INVERSE = upstream.PHI_INVERSE; +pub const JobFn = upstream.JobFn; +pub const PoolJob = upstream.PoolJob; +pub const PriorityLevel = upstream.PriorityLevel; +pub const JobPriority = upstream.JobPriority; +pub const TaskState = upstream.TaskState; +pub const TaskNode = upstream.TaskNode; +pub const DAGStats = upstream.DAGStats; +pub const ChaseLevDeque = upstream.ChaseLevDeque; +pub const ThreadPool = upstream.ThreadPool; +pub const DependencyGraph = upstream.DependencyGraph; +pub const getGlobalPool = upstream.getGlobalPool; +pub const getDAG = upstream.getDAG; +pub const shutdownDAG = upstream.shutdownDAG; +pub const hasDAG = upstream.hasDAG; diff --git a/src/vsa/core.zig b/src/vsa/core.zig index 867750f..3ff13ba 100644 --- a/src/vsa/core.zig +++ b/src/vsa/core.zig @@ -1,13 +1,39 @@ //! Re-export. The implementation lives in gHashTag/zig-golden-float. //! //! This file used to be a second copy of that one. Both repositories carried -//! src/vsa/core.zig, they were edited independently, and they diverged -- which -//! is why repairing sixteen defects in golden-float (#97) left every one of -//! them standing here. Two maintained copies of the same code is how that -//! happens, and it happens quietly, because nothing reports it. +//! src/vsa/core.zig, they were edited independently, and they diverged -- which is +//! why repairing sixteen defects in golden-float (#97) left every one of them +//! standing here. Two maintained copies of the same code is how that happens, +//! and it happens quietly, because nothing reports it. //! -//! The two public surfaces were identical when this was written, checked -//! symbol by symbol, so nothing is lost by pointing at one of them. Everything -//! that imported this path still imports this path; there is simply one -//! implementation behind it now, and it is the one that compiles. -pub usingnamespace @import("zig_golden_float").vsa; +//! The names are listed one by one because `usingnamespace` was removed in Zig +//! 0.15, which is the version this package targets. That is a cost: a name added +//! there does not appear here until it is added here too. It is still cheaper +//! than a second implementation, and unlike a second implementation it fails +//! loudly -- the name is simply missing rather than quietly different. +const upstream = @import("zig_golden_float").vsa; + +pub const bind = upstream.bind; +pub const unbind = upstream.unbind; +pub const bundle2 = upstream.bundle2; +pub const bundle3 = upstream.bundle3; +pub const cosineSimilarity = upstream.cosineSimilarity; +pub const cosineSimilarityF16 = upstream.cosineSimilarityF16; +pub const hammingDistance = upstream.hammingDistance; +pub const hammingSimilarity = upstream.hammingSimilarity; +pub const dotSimilarity = upstream.dotSimilarity; +pub const vectorNorm = upstream.vectorNorm; +pub const countNonZero = upstream.countNonZero; +pub const bundleN = upstream.bundleN; +pub const randomVector = upstream.randomVector; +pub const permute = upstream.permute; +pub const inversePermute = upstream.inversePermute; +pub const encodeSequence = upstream.encodeSequence; +pub const probeSequence = upstream.probeSequence; +pub const qbind = upstream.qbind; +pub const qbundle = upstream.qbundle; +pub const measure = upstream.measure; +pub const similarity_quantum = upstream.similarity_quantum; +pub const applyPhase = upstream.applyPhase; +pub const computeCoherence = upstream.computeCoherence; +pub const entangle = upstream.entangle; diff --git a/src/vsa/fpga_bind.zig b/src/vsa/fpga_bind.zig index ceb0708..8ba8991 100644 --- a/src/vsa/fpga_bind.zig +++ b/src/vsa/fpga_bind.zig @@ -1,13 +1,20 @@ //! Re-export. The implementation lives in gHashTag/zig-golden-float. //! //! This file used to be a second copy of that one. Both repositories carried -//! src/vsa/fpga_bind.zig, they were edited independently, and they diverged -- which -//! is why repairing sixteen defects in golden-float (#97) left every one of -//! them standing here. Two maintained copies of the same code is how that -//! happens, and it happens quietly, because nothing reports it. +//! src/vsa/fpga_bind.zig, they were edited independently, and they diverged -- which is +//! why repairing sixteen defects in golden-float (#97) left every one of them +//! standing here. Two maintained copies of the same code is how that happens, +//! and it happens quietly, because nothing reports it. //! -//! The two public surfaces were identical when this was written, checked -//! symbol by symbol, so nothing is lost by pointing at one of them. Everything -//! that imported this path still imports this path; there is simply one -//! implementation behind it now, and it is the one that compiles. -pub usingnamespace @import("zig_golden_float").fpga_bind; +//! The names are listed one by one because `usingnamespace` was removed in Zig +//! 0.15, which is the version this package targets. That is a cost: a name added +//! there does not appear here until it is added here too. It is still cheaper +//! than a second implementation, and unlike a second implementation it fails +//! loudly -- the name is simply missing rather than quietly different. +const upstream = @import("zig_golden_float").fpga_bind; + +pub const Config = upstream.Config; +pub const FPGAInterface = upstream.FPGAInterface; +pub const CpuFallback = upstream.CpuFallback; +pub const AutoVSA = upstream.AutoVSA; +pub const AutoBind = upstream.AutoBind; diff --git a/src/vsa/hrr.zig b/src/vsa/hrr.zig index 1f5a1d9..3873ed8 100644 --- a/src/vsa/hrr.zig +++ b/src/vsa/hrr.zig @@ -1,13 +1,16 @@ //! Re-export. The implementation lives in gHashTag/zig-golden-float. //! //! This file used to be a second copy of that one. Both repositories carried -//! src/vsa/hrr.zig, they were edited independently, and they diverged -- which -//! is why repairing sixteen defects in golden-float (#97) left every one of -//! them standing here. Two maintained copies of the same code is how that -//! happens, and it happens quietly, because nothing reports it. +//! src/vsa/hrr.zig, they were edited independently, and they diverged -- which is +//! why repairing sixteen defects in golden-float (#97) left every one of them +//! standing here. Two maintained copies of the same code is how that happens, +//! and it happens quietly, because nothing reports it. //! -//! The two public surfaces were identical when this was written, checked -//! symbol by symbol, so nothing is lost by pointing at one of them. Everything -//! that imported this path still imports this path; there is simply one -//! implementation behind it now, and it is the one that compiles. -pub usingnamespace @import("zig_golden_float").hrr; +//! The names are listed one by one because `usingnamespace` was removed in Zig +//! 0.15, which is the version this package targets. That is a cost: a name added +//! there does not appear here until it is added here too. It is still cheaper +//! than a second implementation, and unlike a second implementation it fails +//! loudly -- the name is simply missing rather than quietly different. +const upstream = @import("zig_golden_float").hrr; + +pub const HRR = upstream.HRR; From bd6319b7e96101d10d205e8ac134b520fc5096ce Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 01:02:15 +0700 Subject: [PATCH 3/8] Analyse the whole public surface, now that there is one implementation behind it The build went green with the duplicated files re-exporting golden-float, but green without this line means only that the declarations the tests happen to walk compile. That is exactly the condition that hid five API-drift errors here while the badge stayed green. Now the surface goes through the compiler as a whole, and the green means what a consumer would take it to mean. --- src/vsa.zig | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/vsa.zig b/src/vsa.zig index 747da43..a5bd949 100644 --- a/src/vsa.zig +++ b/src/vsa.zig @@ -144,3 +144,17 @@ test { } // φ² + 1/φ² = 3 | TRINITY + +test "every public declaration of this module is analysed" { + // Zig analyses top-level declarations lazily, so `zig build test` proves only + // that the declarations the tests happen to reference compile. A consumer + // referencing anything else got errors this package's own green CI could not + // see -- which is how five API-drift errors sat here while the badge stayed + // green, and how gHashTag/trinity#701 found them within a minute of trying + // to depend on this. + // + // With the duplicated files now re-exporting one repaired implementation, + // this is what proves the whole surface goes through the compiler rather + // than only the part the tests walk. + @import("std").testing.refAllDeclsRecursive(@This()); +} From 73f8d1006723df0213bf67116c6f068be970d2e1 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 01:05:22 +0700 Subject: [PATCH 4/8] Re-pin golden_float, and drop a re-export that never pointed at anything Two things the full-surface analysis found, neither of them caused by the deduplication. This branch was cut from main, which pins the golden-float from before its sixteen defects were repaired -- so atomic.fence and the rest of the 0.14 API came back through the dependency rather than from any file here. Re-pinned. And src/vsa.zig re-exported concurrency.LockFreePool, which has never existed in this repository's concurrency module nor in golden-float's. Both export the same twenty-three names and that is not one of them. The line referred to nothing and nothing complained, because lazy analysis never asked what it pointed at. --- src/vsa.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vsa.zig b/src/vsa.zig index a5bd949..390eee9 100644 --- a/src/vsa.zig +++ b/src/vsa.zig @@ -93,7 +93,12 @@ pub const TextCorpus = storage.TextCorpus; // Re-export concurrency & DAG pub const ChaseLevDeque = concurrency.ChaseLevDeque; -pub const LockFreePool = concurrency.LockFreePool; +// LockFreePool is not re-exported. It has never existed in this repository's +// concurrency module, nor in golden-float's -- both export the same +// twenty-three names and that is not one of them. The line sat here referring +// to nothing, and nothing complained, because lazy analysis never asked what it +// pointed at. Forcing the whole surface through the compiler asked, and the +// answer was that it points at nothing. pub const DependencyGraph = concurrency.DependencyGraph; pub const TaskNode = concurrency.TaskNode; pub const TaskState = concurrency.TaskState; From 613ff5ed054f745e4650be624aff570367e0b3f5 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 01:17:11 +0700 Subject: [PATCH 5/8] Temporary: have CI print the golden_float hash The local zig used to compute it does not survive between sessions here and the download keeps timing out, so the runner that already has the right toolchain computes it instead. Removed in the next commit. --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4fa620..5624c44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,6 +31,14 @@ jobs: # The version trinity targets. Building with anything else would prove # the package works for somebody who is not the consumer. version: 0.15.2 + # Temporary: this runner has the right zig and a fast link, so it is the + # cheapest place to compute the dependency hash. Removed once pinned. + - name: print the golden_float hash + run: | + zig fetch --save=zig_golden_float https://github.com/gHashTag/zig-golden-float/archive/main.tar.gz + echo '--- build.zig.zon ---' + cat build.zig.zon + - run: zig build - run: zig build test - name: The modules a consumer asks for must exist From 337e9b0713a17819588d286431766b39453bba36 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 01:19:14 +0700 Subject: [PATCH 6/8] Pin the repaired golden_float The hash came from the runner, which has the toolchain that computes it. The previous pin predated gHashTag/zig-golden-float#97, so sixteen repaired defects were still arriving here through the dependency. --- .github/workflows/ci.yml | 8 -------- build.zig.zon | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5624c44..f4fa620 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,6 @@ jobs: # The version trinity targets. Building with anything else would prove # the package works for somebody who is not the consumer. version: 0.15.2 - # Temporary: this runner has the right zig and a fast link, so it is the - # cheapest place to compute the dependency hash. Removed once pinned. - - name: print the golden_float hash - run: | - zig fetch --save=zig_golden_float https://github.com/gHashTag/zig-golden-float/archive/main.tar.gz - echo '--- build.zig.zon ---' - cat build.zig.zon - - run: zig build - run: zig build test - name: The modules a consumer asks for must exist diff --git a/build.zig.zon b/build.zig.zon index 23d425b..b109ac5 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -7,7 +7,7 @@ .dependencies = .{ .zig_golden_float = .{ .url = "https://github.com/gHashTag/zig-golden-float/archive/main.tar.gz", - .hash = "golden_float-2.1.0-h7LKhQ3lCgBfkjryIT9MnOTWleMQrdrgOIxQOhxqzTVu", + .hash = "golden_float-2.1.0-h7LKhZMGCwBW0FS_zsli6CWxs9d5pElRE1pFFiy3WRSD", }, }, } From 72b53729b0152fb70843b1205cf6111885d95859 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 01:23:00 +0700 Subject: [PATCH 7/8] text_encoding calls bundle2 and add with an allocator they no longer take Nine call sites passed std.heap.page_allocator to core.bundle2 and to HybridBigInt.add. Both take their operands and nothing else -- bundle2(a, b) and add(self, other) -- so the argument is dropped rather than replaced: there is nothing for it to become. This file is one of the eighteen under src/vsa/ that have no counterpart in golden-float, so it is genuinely this package's own, and it had been calling an API that moved without it. It compiled for nobody and nothing said so, because nothing referenced it. --- src/vsa/text_encoding.zig | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vsa/text_encoding.zig b/src/vsa/text_encoding.zig index f350d1f..63fcc65 100644 --- a/src/vsa/text_encoding.zig +++ b/src/vsa/text_encoding.zig @@ -72,7 +72,7 @@ pub fn encodeWord(word: []const u8) HybridBigInt { for (word[1..]) |c| { var char_vec = charToVector(c); - result = core.bundle2(&result, &char_vec, std.heap.page_allocator); + result = core.bundle2(&result, &char_vec); } return result; @@ -88,7 +88,7 @@ pub fn encodeWordWithPosition(word: []const u8) HybridBigInt { var char_vec = charToVector(c); // Permute by position to preserve order information const permuted = core.permute(&char_vec, pos); - result = result.add(&permuted, std.heap.page_allocator); + result = result.add(&permuted); } return result; @@ -134,7 +134,7 @@ pub fn encodeTextWithNgrams(text: []const u8, allocator: Allocator) !struct { var char_vec = HybridBigInt.zero(); for (text) |c| { var cv = charToVector(c); - char_vec = char_vec.add(&cv, std.heap.page_allocator); + char_vec = char_vec.add(&cv); } // N-gram level encoding @@ -144,7 +144,7 @@ pub fn encodeTextWithNgrams(text: []const u8, allocator: Allocator) !struct { if (text.len >= NGRAM_N) { for (0..text.len - NGRAM_N + 1) |i| { var ngram = encodeNgram(text[i..][0..NGRAM_N]); - ngram_vec = ngram_vec.add(&ngram, std.heap.page_allocator); + ngram_vec = ngram_vec.add(&ngram); ngram_count += 1; } } @@ -155,7 +155,7 @@ pub fn encodeTextWithNgrams(text: []const u8, allocator: Allocator) !struct { var ngram_weighted = ngram_vec; // Scale vectors (simplified: just bundle) - const combined = core.bundle2(&char_weighted, &ngram_weighted, std.heap.page_allocator); + const combined = core.bundle2(&char_weighted, &ngram_weighted); return .{ .char_level = char_vec, @@ -186,7 +186,7 @@ pub fn encodeText(text: []const u8) HybridBigInt { } else if (!is_alpha and in_word) { const word = text[word_start..i]; var word_vec = encodeWord(word); - result = result.add(&word_vec, std.heap.page_allocator); + result = result.add(&word_vec); in_word = false; } } @@ -195,7 +195,7 @@ pub fn encodeText(text: []const u8) HybridBigInt { if (in_word) { const word = text[word_start..]; var word_vec = encodeWord(word); - result = result.add(&word_vec, std.heap.page_allocator); + result = result.add(&word_vec); } return result; @@ -325,7 +325,7 @@ pub fn encodeTextTFIDF(text: []const u8, stats: *const DocumentStats) HybridBigI const scale = @as(usize, @intFromFloat(idf)); var weighted = word_vec; for (0..@max(1, scale)) |_| { - result = result.add(&weighted, std.heap.page_allocator); + result = result.add(&weighted); } in_word = false; @@ -340,7 +340,7 @@ pub fn encodeTextTFIDF(text: []const u8, stats: *const DocumentStats) HybridBigI const scale = @as(usize, @intFromFloat(idf)); var weighted = word_vec; for (0..@max(1, scale)) |_| { - result = result.add(&weighted, std.heap.page_allocator); + result = result.add(&weighted); } } From badd3aa399c55afffd23b02a6f68d8eba8f2ce8e Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Wed, 12 Aug 2026 01:27:28 +0700 Subject: [PATCH 8/8] permuted must be var: add takes a mutable pointer The value caches its own unpacked form, so filling that cache is a mutation and add is declared *Self. A const binding cannot hand out the pointer it asks for. --- src/vsa/text_encoding.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vsa/text_encoding.zig b/src/vsa/text_encoding.zig index 63fcc65..71e7412 100644 --- a/src/vsa/text_encoding.zig +++ b/src/vsa/text_encoding.zig @@ -87,7 +87,9 @@ pub fn encodeWordWithPosition(word: []const u8) HybridBigInt { for (word, 0..) |c, pos| { var char_vec = charToVector(c); // Permute by position to preserve order information - const permuted = core.permute(&char_vec, pos); + // var, not const: add takes *Self because the value caches its own + // unpacked form, and filling that cache is a mutation. + var permuted = core.permute(&char_vec, pos); result = result.add(&permuted); }