code wiki / (root) / nx_sketch_comparator.nx

nx_sketch_comparator.nx source

↩ module page · 220 lines · 8159 B

1// sketch_comparator.nx -- sealed-verdict comparator for head-to-head stomp claims. 2// 3// THE VALIDATION PRIMITIVE. Every claim of the form "our primitive 4// beats incumbent X on axis Y" must pass through this. Sealed verdicts: 5// 6// NX_CMP_VERDICT_BEATS our primitive measurably better 7// NX_CMP_VERDICT_EQUIVALENT within tolerance band; can't distinguish 8// NX_CMP_VERDICT_LOSES incumbent measurably better 9// NX_CMP_VERDICT_INCONCLUSIVE missing data or degenerate inputs 10// 11// THREE INDEPENDENT AXES: 12// ACCURACY -- whoever's closer to ground truth wins 13// MEMORY -- smaller bytes wins 14// TIME -- faster wall-time wins 15// 16// COMPOSITE verdict aggregates 3 axis verdicts via majority vote with 17// inconclusive-axis discounting. A 2/3 majority with one INCONCLUSIVE 18// is enough to call. A 1/1/1 tie returns EQUIVALENT. 19// 20// All inputs are i64; caller supplies measurements from external runs. 21// This primitive does NOT itself run benchmarks -- it normalizes the 22// VERDICT against typed tolerance, so claims become falsifiable. 23// 24// LOSSLESS-LANGUAGE DISCIPLINE: result struct carries verdict + signed 25// delta in PPM (positive = our wins) + ppb confidence. 26 27// nx_safety_envelope: 28// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 29// sil_target: SIL1 30// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 31// verdict: NOT_YET_EVALUATED 32 33import "nx_syscalls.nx" 34import "nx_sketch_types.nx" 35const NX_MAGIC_1000000000: i64 = 1000000000 36const NX_MAGIC_500000000: i64 = 500000000 37const NX_MAGIC_1000000: i64 = 1000000 38 39const NX_CMP_VERDICT_BEATS: i64 = 0 40const NX_CMP_VERDICT_EQUIVALENT: i64 = 1 41const NX_CMP_VERDICT_LOSES: i64 = 2 42const NX_CMP_VERDICT_INCONCLUSIVE: i64 = 3 43 44const NX_CMP_AXIS_ACCURACY: i64 = 0 45const NX_CMP_AXIS_MEMORY: i64 = 1 46const NX_CMP_AXIS_TIME: i64 = 2 47 48struct ComparisonResult { 49 verdict: i64, // sealed enum 50 delta_ppm: i64, // signed: positive = ours wins (by this amount in PPM) 51 conf_ppb: i64, // confidence (degenerate inputs lower this) 52 axis: i64, // which axis was compared 53} 54 55// === abs ========================================================== 56 57func nx_cmp_iabs(x: i64) -> i64 { 58 if x < 0 { return -x } 59 return x 60} 61 62// === ACCURACY axis ================================================ 63// 64// Inputs: our_value, their_value, ground_truth, tolerance_ppm. 65// Closer to truth wins; tie if both within tolerance of equal distance. 66// 67// delta_ppm = (their_error - our_error) * 1_000_000 / |truth| 68// positive = our is closer (ours wins) 69// negative = their is closer (we lose) 70 71func nx_cmp_accuracy(our: i64, theirs: i64, truth: i64, tolerance_ppm: i64) -> *ComparisonResult { 72 let raw: *u8 = sys_mmap(40) 73 let r: *ComparisonResult = raw as *ComparisonResult 74 r.axis = NX_CMP_AXIS_ACCURACY 75 r.conf_ppb = NX_MAGIC_1000000000 76 if truth == 0 { 77 // Cannot normalize relative error. 78 r.verdict = NX_CMP_VERDICT_INCONCLUSIVE 79 r.delta_ppm = 0 80 r.conf_ppb = NX_MAGIC_500000000 // half-confidence (degenerate) 81 return r 82 } 83 let our_err: i64 = nx_cmp_iabs(our - truth) 84 let their_err: i64 = nx_cmp_iabs(theirs - truth) 85 let truth_abs: i64 = nx_cmp_iabs(truth) 86 // delta = (their_err - our_err) / truth, in PPM 87 let delta_ppm: i64 = ((their_err - our_err) * NX_MAGIC_1000000) / truth_abs 88 r.delta_ppm = delta_ppm 89 if delta_ppm > tolerance_ppm { 90 r.verdict = NX_CMP_VERDICT_BEATS 91 } 92 if delta_ppm < -tolerance_ppm { 93 r.verdict = NX_CMP_VERDICT_LOSES 94 } 95 if delta_ppm <= tolerance_ppm { 96 if delta_ppm >= -tolerance_ppm { 97 r.verdict = NX_CMP_VERDICT_EQUIVALENT 98 } 99 } 100 return r 101} 102 103// === MEMORY axis ================================================== 104// 105// Inputs: our_bytes, their_bytes, tolerance_ppm. 106// Smaller wins. delta_ppm = (their - our) * 1_000_000 / their. 107 108func nx_cmp_memory(our_bytes: i64, their_bytes: i64, tolerance_ppm: i64) -> *ComparisonResult { 109 let raw: *u8 = sys_mmap(40) 110 let r: *ComparisonResult = raw as *ComparisonResult 111 r.axis = NX_CMP_AXIS_MEMORY 112 r.conf_ppb = NX_MAGIC_1000000000 113 if their_bytes <= 0 { 114 r.verdict = NX_CMP_VERDICT_INCONCLUSIVE 115 r.delta_ppm = 0 116 r.conf_ppb = NX_MAGIC_500000000 117 return r 118 } 119 let delta_ppm: i64 = ((their_bytes - our_bytes) * NX_MAGIC_1000000) / their_bytes 120 r.delta_ppm = delta_ppm 121 if delta_ppm > tolerance_ppm { 122 r.verdict = NX_CMP_VERDICT_BEATS 123 } 124 if delta_ppm < -tolerance_ppm { 125 r.verdict = NX_CMP_VERDICT_LOSES 126 } 127 if delta_ppm <= tolerance_ppm { 128 if delta_ppm >= -tolerance_ppm { 129 r.verdict = NX_CMP_VERDICT_EQUIVALENT 130 } 131 } 132 return r 133} 134 135// === TIME axis ==================================================== 136// 137// Inputs: our_us, their_us, tolerance_ppm. Faster wins. 138// delta_ppm = (their - our) * 1_000_000 / their. 139 140func nx_cmp_time(our_us: i64, their_us: i64, tolerance_ppm: i64) -> *ComparisonResult { 141 let raw: *u8 = sys_mmap(40) 142 let r: *ComparisonResult = raw as *ComparisonResult 143 r.axis = NX_CMP_AXIS_TIME 144 r.conf_ppb = NX_MAGIC_1000000000 145 if their_us <= 0 { 146 r.verdict = NX_CMP_VERDICT_INCONCLUSIVE 147 r.delta_ppm = 0 148 r.conf_ppb = NX_MAGIC_500000000 149 return r 150 } 151 let delta_ppm: i64 = ((their_us - our_us) * NX_MAGIC_1000000) / their_us 152 r.delta_ppm = delta_ppm 153 if delta_ppm > tolerance_ppm { 154 r.verdict = NX_CMP_VERDICT_BEATS 155 } 156 if delta_ppm < -tolerance_ppm { 157 r.verdict = NX_CMP_VERDICT_LOSES 158 } 159 if delta_ppm <= tolerance_ppm { 160 if delta_ppm >= -tolerance_ppm { 161 r.verdict = NX_CMP_VERDICT_EQUIVALENT 162 } 163 } 164 return r 165} 166 167// === COMPOSITE verdict ============================================ 168// 169// Aggregate three axis verdicts. Rules (sealed): 170// - All three BEATS -> COMPOSITE BEATS (high confidence) 171// - 2 BEATS + 1 EQUIVALENT -> BEATS 172// - 2 BEATS + 1 LOSES -> BEATS but lower confidence (mixed) 173// - 1 BEATS + 2 EQUIVALENT -> BEATS (one clear win) 174// - All EQUIVALENT -> EQUIVALENT 175// - 1+ LOSES outweighs equal-count BEATS -> LOSES (loss-averse) 176// - Any 2+ INCONCLUSIVE -> INCONCLUSIVE 177// 178// This is a deliberate LOSS-AVERSE policy: tied verdicts default to 179// EQUIVALENT, not BEATS. We don't claim wins unless they're clear. 180 181func nx_cmp_composite(acc: *ComparisonResult, mem: *ComparisonResult, 182 tim: *ComparisonResult) -> i64 { 183 // Count INCONCLUSIVE results -- if 2+, refuse. 184 var n_inc: i64 = 0 185 if acc.verdict == NX_CMP_VERDICT_INCONCLUSIVE { n_inc = n_inc + 1 } 186 if mem.verdict == NX_CMP_VERDICT_INCONCLUSIVE { n_inc = n_inc + 1 } 187 if tim.verdict == NX_CMP_VERDICT_INCONCLUSIVE { n_inc = n_inc + 1 } 188 if n_inc >= 2 { return NX_CMP_VERDICT_INCONCLUSIVE } 189 190 var n_beats: i64 = 0 191 var n_loses: i64 = 0 192 if acc.verdict == NX_CMP_VERDICT_BEATS { n_beats = n_beats + 1 } 193 if mem.verdict == NX_CMP_VERDICT_BEATS { n_beats = n_beats + 1 } 194 if tim.verdict == NX_CMP_VERDICT_BEATS { n_beats = n_beats + 1 } 195 if acc.verdict == NX_CMP_VERDICT_LOSES { n_loses = n_loses + 1 } 196 if mem.verdict == NX_CMP_VERDICT_LOSES { n_loses = n_loses + 1 } 197 if tim.verdict == NX_CMP_VERDICT_LOSES { n_loses = n_loses + 1 } 198 199 // Loss-averse: any loss beats equal beats. 200 if n_loses > n_beats { return NX_CMP_VERDICT_LOSES } 201 if n_loses == n_beats { 202 if n_beats == 0 { return NX_CMP_VERDICT_EQUIVALENT } 203 return NX_CMP_VERDICT_EQUIVALENT // tie -> equivalent 204 } 205 // n_beats > n_loses 206 return NX_CMP_VERDICT_BEATS 207} 208 209// === query (typed envelope) ======================================= 210 211func nx_cmp_query(r: *ComparisonResult) -> *ApproxI64 { 212 return nx_approx_new(r.verdict, NX_ENV_ABS, 0, 213 r.conf_ppb, 214 NX_MATURITY_PRODUCTION, 215 NX_ADV_HONEST) 216} 217 218func nx_cmp_memory_bytes(r: *ComparisonResult) -> i64 { 219 return 40 220}