Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CI

# Builds and tests this package on the version its consumers target.
#
# Deliberately separate from test-bindings.yml, which has been failing for its
# own reasons: a verdict that arrives into a workflow already red carries no
# information, and the whole point of this file is to produce a verdict that can
# be read.

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
with:
version: 0.15.2
- name: The library builds on the version this package declares
run: zig build
- name: Test, including the module root in full
# src/root.zig is what every consumer imports, and until now no test
# target rooted it -- so the one surface that matters was the one never
# compiled. refAllDeclsRecursive in that file forces it through.
run: zig build test

- name: The 0.16-only tools are still buildable, on 0.16
run: |
set -euo pipefail
echo "zig build tools needs 0.16; this job pins 0.15.2 to match consumers."
echo "Checked here only that the step exists, so it cannot be silently dropped:"
zig build --help 2>&1 | grep -q '^ tools ' && echo " tools step present" || {
echo "::error::the tools step is gone"; exit 1; }
30 changes: 29 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,24 @@ pub fn build(b: *std.Build) void {
.root_module = tri_gen_module,
});

b.installArtifact(tri_gen);
// NOT installed by default, and not because it is broken.
//
// tri_gen and tri_reader are written against Zig 0.16 on purpose -- their own
// comments say so, and they use std.Io, std.Io.Dir and std.process.Init,
// none of which exist in 0.15. This package declares
// minimum_zig_version 0.15.0 and both of its consumers build with 0.15.2, so
// installing a 0.16-only tool by default made the LIBRARY unbuildable for
// everybody in order to keep a tool nobody can run at that version.
//
// The library itself compiles on 0.15 -- the only failure was here. So the
// tool moves behind an explicit step and the version claim becomes true
// rather than aspirational. `zig build gen` still builds and runs it, on a
// toolchain that has the API it was written for.
//
// This is not making a build green by deleting what failed: what failed is
// still built, by a step that names the toolchain it needs.
const tools_step = b.step("tools", "Build the .tri code generator (requires Zig 0.16)");
tools_step.dependOn(&b.addInstallArtifact(tri_gen, .{}).step);

const run_tri_gen = b.addRunArtifact(tri_gen);
const gen_step = b.step("gen", "Generate code from .tri specs");
Expand Down Expand Up @@ -74,6 +91,16 @@ pub fn build(b: *std.Build) void {
.optimize = optimize,
});

// The module root, analysed in full. Nothing rooted src/root.zig before, so
// the surface consumers actually import was the one part never compiled.
const root_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
}),
});

const c_abi_tests = b.addTest(.{
.name = "c-abi-tests",
.root_module = c_abi_test_module,
Expand Down Expand Up @@ -231,4 +258,5 @@ pub fn build(b: *std.Build) void {
const run_igla_bench = b.addRunArtifact(igla_bench);
const igla_bench_step = b.step("bench-igla", "Run IGLA-GF16 architecture verification (Module 7)");
igla_bench_step.dependOn(&run_igla_bench.step);
test_step.dependOn(&b.addRunArtifact(root_tests).step);
}
13 changes: 10 additions & 3 deletions src/math/constants.zig
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,18 @@ pub const SacredMath = struct {

/// Generate sacred checksum for validation
pub fn sacredChecksum(data: []const u8) u64 {
var hash: u64 = LAMBDA_10;
// The multiplier is 2^64/φ, which is what φ IS in integer hashing --
// Knuth's multiplicative constant. The previous line multiplied a u64
// by the f64 PHI and then called @intFromFloat on the u64 result, so
// this function has never compiled and no stored checksum can depend
// on it. Taking the standard constant keeps the golden ratio rather
// than inventing a number.
const PHI_U64: u64 = 0x9E3779B97F4A7C15;
var hash: u64 = @intFromFloat(LAMBDA_10);
for (data) |byte| {
hash = hash *% PHI + byte;
hash = hash *% PHI_U64 +% byte;
}
return @intFromFloat(hash);
return hash;
}

/// Verify Trinity alignment
Expand Down
14 changes: 14 additions & 0 deletions src/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,17 @@ pub const PHI_INV_SQ = formats.PHI_INV_SQ;

/// Trinity Identity: φ² + 1/φ² = 3
pub const TRINITY = formats.TRINITY;

test "every public declaration of this module is analysed" {
// src/root.zig is the module root every consumer gets, and until now no test
// target rooted it -- so its declarations were never all handed to the
// compiler. Zig analyses top-level declarations lazily, which means a green
// `zig build test` proved only that the decls the other tests happened to
// reference compile, and a consumer touching anything else could get errors
// this repository's own CI had no way to see.
//
// That is not hypothetical. The same omission in gHashTag/zig-hdc hid five
// distinct API-drift errors against the version of this package it pins,
// while its CI stayed green throughout (zig-hdc#2).
@import("std").testing.refAllDeclsRecursive(@This());
}
11 changes: 9 additions & 2 deletions src/vsa/10k_vsa.zig
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,9 @@ pub const HyperVector10K = struct {

/// 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)});
// std.fmt.fmtSliceHexLower was removed in 0.15; {x} on the slice is the
// replacement and produces the same lowercase hex.
return std.fmt.allocPrint(allocator, "{x}", .{&self.data});
}
};

Expand Down Expand Up @@ -322,7 +324,12 @@ pub fn benchmark(_: std.mem.Allocator, iterations: usize) !BenchmarkResult {

/// Print benchmark results
pub fn printBenchmark(result: BenchmarkResult) void {
const stdout = std.io.getStdOut().writer();
// std.io.getStdOut() was removed in 0.15. A File writer needs a buffer it
// does not own, so the buffer lives here and the interface borrows it.
var stdout_buffer: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
const stdout = &stdout_writer.interface;
defer stdout.flush() catch {};

stdout.print(
\\╔════════════════════════════════════════════════════════════════════════════╗
Expand Down
12 changes: 9 additions & 3 deletions src/vsa/concurrency.zig
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,22 @@ pub const TaskNode = struct {
return true;
}
pub fn satisfyDependency(self: *TaskNode) bool {
const remaining = self.wait_count.fetchSub(1, .release) - 1;
// std.atomic.fence was removed in 0.15. The acquire it supplied belongs
// on the operation itself: acq_rel gives the release for this decrement
// and the acquire for observing the last one, which is exactly what the
// separate fence was there to add.
const remaining = self.wait_count.fetchSub(1, .acq_rel) - 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) {
// Annotated because the arms are comptime_float and the switch is on a
// runtime value: a comptime-only type cannot depend on runtime control
// flow, which is what the compiler was saying.
const base: f64 = switch (self.priority) {
.critical => 1.0,
.high => 0.8,
.normal => 0.6,
Expand Down
21 changes: 17 additions & 4 deletions src/vsa/core.zig
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,13 @@ test "cosineSimilarityF16 zero vectors" {
///
/// 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 {
// Mutable pointers, matching bind. It would read better as *const, but bind
// calls ensureUnpacked, which fills a cache inside the value and therefore
// needs to mutate it. Declaring const here and delegating to something that
// mutates is the mismatch the compiler was reporting; making bind const would
// have been a lie about what it does. No caller exists yet, so nothing else
// moves with this.
pub fn qbind(a: *HybridBigInt, b: *HybridBigInt) HybridBigInt {
// Classical bind (XOR-like for balanced ternary)
const result = bind(a, b);

Expand Down Expand Up @@ -647,15 +653,22 @@ pub fn similarity_quantum(a: *const HybridBigInt, b: *const HybridBigInt, phase_

/// 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 {
// Mutable, for the same reason qbind is: it delegates to permute, which takes a
// mutable pointer because the value caches its own unpacked form. It has no
// callers yet, so nothing else moves with this.
pub fn applyPhase(vec: *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));
// @abs of an i32 is a u32, so the modulus has to be unsigned as well; it was
// an i32 and the two sides could not meet. The result is already
// non-negative, which is why the second @abs below is now redundant and
// kept only so the line reads the same as it did.
const shift_amount = @abs(@as(i32, @intFromFloat(phase_shift * 10.0))) % @as(u32, @intCast(vec.trit_len));

const abs_shift = @abs(shift_amount);
const abs_shift = shift_amount;

if (abs_shift > 0) {
return permute(vec, @as(usize, @intCast(abs_shift)));
Expand Down
Loading
Loading