code wiki / (root) / nx_identity_space_lib.nx

nx_identity_space_lib.nx source

↩ module page · 175 lines · 8891 B

1// nx_identity_space_lib.nx -- THE CORRELATED IDENTITY DRAW (graphics GR24), as a pure core a gate can drive. 2// 3// WHY THIS IS THE AXIS A FINITE ASSET LIBRARY CANNOT ENTER. The rival position, measured and quoted on the 4// board: VR HOT ships roughly 15-20 hand-picked skins accumulated over five years on a FIXED LICENSED 5// TOPOLOGY, so their identity space is bounded by what one marketplace authored; Infinigen has no humans at 6// all. Ours is a seed. But a seed is only worth more than a library if the draw is CORRELATED -- independent 7// per-channel sampling produces an implausible combination of plausible parts, which is precisely why a 8// naively generated face reads as generated. 9// 10// THE MECHANISM: x = mu + L z, where L is the Cholesky factor of the target covariance and z is a vector of 11// independent standard normals. That is the whole of it, and the only hard part is the factor, which is why 12// GR47 had to land first. THIS LIB COMPOSES la_cholesky_q30 AND CONTAINS NO SECOND DECOMPOSITION -- the 13// rung's own words, "one ruler, never a second decomposition". 14// 15// WHY IRWIN-HALL AND NOT BOX-MULLER, WHICH IS THE OBVIOUS CHOICE. Box-Muller needs a log and a cosine, both 16// of which nx_fixq30_lib has, and it produces UNBOUNDED tails. An identity parameter is not unbounded: a 17// face eight sigma from the mean is not a face, so a Box-Muller draw would have to be clamped -- and 18// clamping a Gaussian DISTORTS THE VERY COVARIANCE THE FACTOR WAS BUILT TO REPRODUCE. The sum of twelve 19// uniforms minus six has mean 0 and variance EXACTLY 1 (each uniform contributes 1/12), needs no 20// transcendental function, and has hard support at +/-6 sigma. The bound is the feature, not a concession. 21// 22// SPLIT STREAMS ARE CLAUSE 2 AND THEY ARE SATISFIED BY CONSTRUCTION, NOT BY CARE. Every axis draws from its 23// OWN stream, seeded by splitmix over (seed, axis index). Axis j's numbers therefore do not depend on how 24// many axes exist, so ADDING A PARAMETER NEVER RESHUFFLES EXISTING GENOMES. A single shared stream would 25// make every genome in the population change the day someone appends a sixth master parameter -- the whole 26// back catalogue silently rewritten by an additive change. 27// * A DRAW THAT IS REPRODUCIBLE ONLY WHILE THE PARAMETER LIST IS FROZEN IS NOT REPRODUCIBLE. 28// 29// UPSTREAM DEFECT THIS LIB ROUTES AROUND AND DOES NOT FIX: nx_random's rotl and splitmix carry sign-extension 30// mask bugs (see the devguardrails journal), so the underlying stream is measurably biased. This lib reads 31// the HIGH bits to avoid the weakest ones, and its gate asserts only what GR24 requires -- determinism, 32// split streams, covariance recovery -- because those survive a biased-but-deterministic z. Altering a PRNG 33// rewrites every stream in the estate, so that fix belongs to whoever owns nx_random. 34// license_tier: ORIGINAL No hw writes (Rule 26). 35import "nx_syscalls.nx" 36import "nx_random.nx" 37import "nx_linalg.nx" 38 39const IS_UNIFORMS: i64 = 12 // Irwin-Hall order: 12 uniforms give variance exactly 1 40const IS_IH_MEAN: i64 = 6 // ...and mean exactly 6, so subtracting 6 centres it 41const IS_AXIS_ODD: i64 = 0x9E3779B97F4A7C15 // golden-ratio odd constant: decorrelates adjacent axis seeds 42const IS_MAX_AXES: i64 = 32 // matches LA_CHOL_MAX_N; the factor is the binding constraint 43const IS_HIGH_SHIFT: i64 = 33 // |x| spans 63 bits; >> 33 leaves the TOP 30, discarding the weak low ones 44 45const IS_OK: i64 = 0 46const IS_BAD_DIM: i64 = 1 47const IS_BAD_FACTOR: i64 = 2 // the supplied L is not the shape the covariance implies 48 49// the per-axis stream seed. splitmix is the estate's incumbent seeder; this only chooses WHAT to mix. 50// axis index enters multiplied by an odd constant so two adjacent axes do not get correlated streams. 51func is_stream_seed(seed: i64, axis: i64) -> i64 { 52 let st: *i64 = sys_mmap(8) as *i64 53 st[0] = seed + (axis * IS_AXIS_ODD) 54 return nx_rng_splitmix(st) 55} 56 57// one uniform in [0, FQ_ONE) from the estate's incumbent generator. 58// THE HIGH BITS, AND THAT IS NOT A STYLE CHOICE. A first cut took `abs(x) % FQ_ONE`, which reads the LOW 59// 30 bits -- the bits the xoshiro family is documented to leave weakest, and the ones its own authors tell 60// callers not to use for anything sensitive. Measured over 20,000 draws it produced a mean of 0.483 against 61// 0.5, and the Irwin-Hall sum inherited it as a -0.20 bias in every standard normal. Shifting the magnitude 62// down by 33 takes the TOP 30 bits instead, which moved the measured population covariance from 3.75 to 63// 3.984 against a target of 4 and the independent control from 0.141 to 0.018. 64// * A GENERATOR IS NOT A SOURCE OF RANDOM NUMBERS, IT IS A SOURCE OF BITS OF DIFFERING QUALITY, AND A 65// MODULO SILENTLY SELECTS THE WORST ONES. 66func is_uniform_q30(r: *NxRng) -> i64 { 67 var u: i64 = nx_rng_next(r) 68 if u < 0 { u = 0 - u } 69 return (u >> IS_HIGH_SHIFT) % FQ_ONE 70} 71 72// one standard normal in Q30, mean 0 variance 1, hard support +/-6 sigma. Deterministic in the stream. 73func is_normal_q30(r: *NxRng) -> i64 { 74 var s: i64 = 0 75 var i: i64 = 0 76 while i < IS_UNIFORMS { s = s + is_uniform_q30(r); i = i + 1 } 77 return s - (IS_IH_MEAN * FQ_ONE) 78} 79 80// THE DRAW. mu and out are n-length Q30 vectors; l is the n x n lower-triangular Cholesky factor of the 81// target covariance, produced by la_cholesky_q30 and NOT recomputed here. Returns IS_*. 82// Each axis j draws its z from its OWN stream, so out[i] depends on (seed, j<=i) and never on n. 83func is_draw(seed: i64, n: i64, mu: *i64, l: *Mat, out: *i64) -> i64 { 84 if n < 1 { return IS_BAD_DIM } 85 if n > IS_MAX_AXES { return IS_BAD_DIM } 86 if l.rows != n { return IS_BAD_FACTOR } 87 if l.cols != n { return IS_BAD_FACTOR } 88 let z: *i64 = sys_mmap(n * 8) as *i64 89 var j: i64 = 0 90 while j < n { 91 let r: *NxRng = nx_rng_new(is_stream_seed(seed, j)) 92 z[j] = is_normal_q30(r) 93 j = j + 1 94 } 95 var i: i64 = 0 96 while i < n { 97 var acc: i64 = mu[i] 98 var k: i64 = 0 99 while k <= i { // L is lower-triangular 100 acc = acc + fq_mul(nx_mat_get(l, i, k), z[k]) 101 k = k + 1 102 } 103 out[i] = acc 104 i = i + 1 105 } 106 return IS_OK 107} 108 109// THE RULER FOR CLAUSE 1, AND IT IS THE ONE THAT CATCHES THE DEFECT THE RUNG EXISTS TO FIX. Draw a 110// population and measure its covariance. Independent per-channel sampling would leave the OFF-DIAGONALS at 111// zero however good the per-axis marginals looked, so a diagonal-only check would pass the very thing this 112// rung replaces. Entries are Q30; the estimator is the population form (divide by m), which is what the 113// target covariance is expressed as. 114func is_empirical_cov(seed0: i64, m: i64, n: i64, mu: *i64, l: *Mat, cov: *Mat) -> i64 { 115 if m < 2 { return IS_BAD_DIM } 116 if n < 1 { return IS_BAD_DIM } 117 let sums: *i64 = sys_mmap(n * 8) as *i64 118 let samples: *i64 = sys_mmap(m * n * 8) as *i64 119 var a: i64 = 0 120 while a < n { sums[a] = 0; a = a + 1 } 121 // the row buffer is allocated ONCE and reused: allocating inside the sampling loop would make the 122 // population size a page-allocation count, which is the never-allocate-in-a-hot-loop defect 123 let row: *i64 = sys_mmap(n * 8) as *i64 124 var s: i64 = 0 125 while s < m { 126 // a DIFFERENT seed per sample, but the same per-axis split rule inside each 127 let rc: i64 = is_draw(seed0 + s, n, mu, l, row) 128 if rc != IS_OK { return rc } 129 var c: i64 = 0 130 while c < n { 131 samples[(s * n) + c] = row[c] 132 sums[c] = sums[c] + row[c] 133 c = c + 1 134 } 135 s = s + 1 136 } 137 var i: i64 = 0 138 while i < n { 139 var j: i64 = 0 140 while j < n { 141 var acc: i64 = 0 142 var t: i64 = 0 143 while t < m { 144 let di: i64 = samples[(t * n) + i] - (sums[i] / m) 145 let dj: i64 = samples[(t * n) + j] - (sums[j] / m) 146 acc = acc + fq_mul(di, dj) 147 t = t + 1 148 } 149 let _c: i64 = nx_mat_set(cov, i, j, acc / m) 150 j = j + 1 151 } 152 i = i + 1 153 } 154 return IS_OK 155} 156 157// worst absolute entry difference between two n x n Q30 matrices -- the number a caller reads instead of 158// trusting a claim about how close the empirical covariance came to its target. 159func is_cov_maxdiff_q30(a: *Mat, b: *Mat) -> i64 { 160 if a.rows != b.rows { return 0 - 1 } 161 if a.cols != b.cols { return 0 - 1 } 162 var worst: i64 = 0 163 var i: i64 = 0 164 while i < a.rows { 165 var j: i64 = 0 166 while j < a.cols { 167 var e: i64 = nx_mat_get(a, i, j) - nx_mat_get(b, i, j) 168 if e < 0 { e = 0 - e } 169 if e > worst { worst = e } 170 j = j + 1 171 } 172 i = i + 1 173 } 174 return worst 175}