nx_cosine_similarity.nx source
↩ module page · 165 lines · 6317 B
1// nx_cosine_similarity.nx -- signed Q10 cosine between two vectors.
2//
3// Universal vector-space similarity. Same primitive over any nx_int
4// feature vectors of equal length: image-feature embeddings (for the
5// "AI grows by ingesting image" textual-inversion roadmap), audio
6// MFCC frames, token frequency histograms, latent embeddings,
7// document term vectors, code AST-bag features.
8//
9// Math:
10// cos(theta) = dot(a, b) / (||a|| * ||b||)
11// dot(a, b) = sum a[i] * b[i]
12// ||a|| = sqrt(dot(a, a))
13//
14// All math nx_int. Newton-Raphson integer sqrt (O(log n) iterations,
15// no f64). Output signed Q10 in [-Q, +Q]:
16// +Q = parallel (same direction)
17// 0 = orthogonal
18// -Q = anti-parallel (opposite direction)
19//
20// Sealed-enum bands (substrate dual-reading cardinal):
21// NX_COSINE_BAND_ANTIPARALLEL cos <= -921 (essentially -1)
22// NX_COSINE_BAND_OPPOSED cos < -307
23// NX_COSINE_BAND_ORTHOGONAL -307 <= cos <= +307
24// NX_COSINE_BAND_ALIGNED cos > +307
25// NX_COSINE_BAND_PARALLEL cos >= +921
26//
27// USE CASES (image-gen + general):
28// - concept-embedding lookup: incoming "new outfit" image -> feature
29// vector -> nearest concept in nishi-library concept store.
30// - prompt similarity over learned embeddings (next layer above
31// string-similarity).
32// - clustering: pairwise cosine grid + hierarchical merge.
33// - recommendation: user-liked-image features cosine-compared
34// against gallery features.
35//
36// EDGE CASES:
37// - empty vectors (n == 0) -> return 0 (orthogonal-by-vacuum)
38// - zero vector (norm == 0) -> return 0 (cosine undefined)
39// - length mismatch -> NX_COSINE_LENGTH_MISMATCH sentinel
40//
41// OVERFLOW NOTE: dot product accumulates n * max(|a_i| * |b_i|).
42// For n = 1024 and |a_i|, |b_i| <= Q10 = 1024, max dot = 1024 *
43// 1024 * 1024 ~ 1.07e9 << i64 ceiling. Larger vectors or wider
44// component ranges may require nx_int -> nx_i128 swap via nx_tier.
45// The substrate carries the dot-product magnitude implicitly via
46// the norm computation; under typical Q10 embedding ranges this is
47// fine.
48//
49// Idea-provenance (patent-clean): Salton 1971 SMART system +
50// Singhal 2001 modern IR survey + Newton-Raphson 1685 integer
51// sqrt. Re-derived from public research; no source code referenced.
52//
53// genealogy_id: salton_1971_smart + singhal_2001_modern_ir +
54// newton_raphson_1685 + bracewell_1965_fourier
55// lineage_id: cosine_similarity_q10_signed
56
57// nx_safety_envelope:
58// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
59// sil_target: SIL1
60// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
61// verdict: NOT_YET_EVALUATED
62
63import "nx_syscalls.nx"
64import "nx_tier.nx"
65
66const NX_COSINE_Q: nx_int = 1024
67const NX_COSINE_LENGTH_MISMATCH: nx_int = -2147483647 // sentinel
68
69// Sealed-enum bands
70const NX_COSINE_BAND_ANTIPARALLEL: nx_int = 0
71const NX_COSINE_BAND_OPPOSED: nx_int = 1
72const NX_COSINE_BAND_ORTHOGONAL: nx_int = 2
73const NX_COSINE_BAND_ALIGNED: nx_int = 3
74const NX_COSINE_BAND_PARALLEL: nx_int = 4
75const NX_COSINE_N_BANDS: nx_int = 5
76
77// ===== Newton-Raphson integer sqrt ===================================
78//
79// Returns floor(sqrt(n)) for n >= 0. Converges in O(log n) iterations.
80// Standard textbook integer-sqrt; bit-identical across architectures.
81
82func _cosine_isqrt(n: nx_int) -> nx_int {
83 if n < 0 { return 0 }
84 if n < 2 { return n }
85 var x: nx_int = n
86 var y: nx_int = (x + 1) / 2
87 while y < x {
88 x = y
89 y = (x + n / x) / 2
90 }
91 return x
92}
93
94// ===== Dot product ===================================================
95
96func _cosine_dot(a: *nx_int, b: *nx_int, n: nx_int) -> nx_int {
97 var sum: nx_int = 0
98 var i: nx_int = 0
99 while i < n {
100 sum = sum + a[i] * b[i]
101 i = i + 1
102 }
103 return sum
104}
105
106// ===== Public cosine =================================================
107//
108// Computes signed Q10 cosine. Returns NX_COSINE_LENGTH_MISMATCH if
109// the two vectors have different lengths (caller's contract violation).
110// Returns 0 for any zero-vector or empty input.
111
112func nx_cosine_similarity(a: *nx_int, n_a: nx_int, b: *nx_int, n_b: nx_int) -> nx_int {
113 if n_a != n_b { return NX_COSINE_LENGTH_MISMATCH }
114 if n_a == 0 { return 0 }
115
116 let dot_ab: nx_int = _cosine_dot(a, b, n_a)
117 let norm_a_sq: nx_int = _cosine_dot(a, a, n_a)
118 let norm_b_sq: nx_int = _cosine_dot(b, b, n_a)
119 if norm_a_sq == 0 { return 0 }
120 if norm_b_sq == 0 { return 0 }
121
122 let norm_a: nx_int = _cosine_isqrt(norm_a_sq)
123 let norm_b: nx_int = _cosine_isqrt(norm_b_sq)
124 if norm_a == 0 { return 0 }
125 if norm_b == 0 { return 0 }
126
127 // cos_q10 = dot * Q / (norm_a * norm_b)
128 // For signed dot, the sign propagates through the division.
129 let denom: nx_int = norm_a * norm_b
130 if denom == 0 { return 0 }
131 var cos_q10: nx_int = (dot_ab * NX_COSINE_Q) / denom
132
133 // Clamp to [-Q, +Q] -- guards against accumulated rounding error
134 // in cases where ||a||*||b|| is slightly off floor(sqrt(...)).
135 if cos_q10 > NX_COSINE_Q { cos_q10 = NX_COSINE_Q }
136 if cos_q10 < (0 - NX_COSINE_Q) { cos_q10 = 0 - NX_COSINE_Q }
137 return cos_q10
138}
139
140// ===== Qualitative band classifier ==================================
141
142func nx_cosine_classify(cos_q10: nx_int) -> nx_int {
143 if cos_q10 <= (0 - 921) { return NX_COSINE_BAND_ANTIPARALLEL }
144 if cos_q10 < (0 - 307) { return NX_COSINE_BAND_OPPOSED }
145 if cos_q10 <= 307 { return NX_COSINE_BAND_ORTHOGONAL }
146 if cos_q10 < 921 { return NX_COSINE_BAND_ALIGNED }
147 return NX_COSINE_BAND_PARALLEL
148}
149
150func nx_cosine_band_is_valid(band: nx_int) -> nx_int {
151 if band < 0 { return 0 }
152 if band >= NX_COSINE_N_BANDS { return 0 }
153 return 1
154}
155
156// ===== Convenience: cosine distance = 1 - cosine_similarity =========
157//
158// Distance in [0, 2*Q] for general vectors, [0, Q] for non-negative.
159// Useful when clustering algorithms want a non-negative dissimilarity.
160
161func nx_cosine_distance(a: *nx_int, n_a: nx_int, b: *nx_int, n_b: nx_int) -> nx_int {
162 let s: nx_int = nx_cosine_similarity(a, n_a, b, n_b)
163 if s == NX_COSINE_LENGTH_MISMATCH { return NX_COSINE_LENGTH_MISMATCH }
164 return NX_COSINE_Q - s
165}