code wiki / (root) / nx_collocation_test.nx

nx_collocation_test.nx source

↩ module page · 115 lines · 5800 B

1// nx_collocation_test.nx -- smoke for collocation association metrics. 2 3import "nx_syscalls.nx" 4import "nx_tier.nx" 5import "nx_essentials.nx" 6import "nx_collocation.nx" 7 8func main() -> nx_int { 9 // === Test 1: independence (PMI ≈ 0) ============================= 10 // If p(a,b) == p(a) * p(b) then PMI = log2(1) = 0. 11 // n_a = 100, n_b = 100, n_total = 10000 -> p(a) = p(b) = 0.01 12 // p(a,b) under independence = 0.0001 -> n_ab = 1 13 let pmi_indep: nx_int = nx_col_pmi_q10(100, 100, 1, 10000) 14 // Expect ≈ 0 (Q10). Allow ±256 (±0.25) slack for log2 refinement. 15 if pmi_indep > 256 { return 1 } 16 if pmi_indep < 0 - 256 { return 2 } 17 if nx_col_pmi_band(pmi_indep) != NX_COL_BAND_WEAK { return 3 } 18 19 // === Test 2: strong positive association ======================== 20 // Same n_a, n_b, n_total but n_ab = 50 (50x more co-occurrence 21 // than independence predicted). 22 let pmi_strong: nx_int = nx_col_pmi_q10(100, 100, 50, 10000) 23 // log2(50) - log2(1) = ~5.64 -> Q10 ~ 5775; should land in STRONG 24 if pmi_strong < NX_COL_PMI_STRONG_Q10 { return 10 } 25 if nx_col_pmi_band(pmi_strong) != NX_COL_BAND_STRONG { return 11 } 26 27 // === Test 3: very strong (idiomatic) ============================ 28 // n_ab equals n_a entirely (every occurrence of a co-occurs with b). 29 let pmi_very: nx_int = nx_col_pmi_q10(100, 100, 100, 10000) 30 // log2(10000 * 100 / (100 * 100)) = log2(100) ≈ 6.64 -> Q10 ~ 6800 31 if nx_col_pmi_band(pmi_very) != NX_COL_BAND_VERY_STRONG { return 20 } 32 33 // === Test 4: zero co-occurrence -> PMI_FLOOR ==================== 34 let pmi_zero: nx_int = nx_col_pmi_q10(100, 100, 0, 10000) 35 if pmi_zero != nx_col_pmi_floor() { return 30 } 36 if nx_col_pmi_band(pmi_zero) != NX_COL_BAND_NONE { return 31 } 37 38 // === Test 5: degenerate inputs =================================== 39 if nx_col_pmi_q10(0, 100, 5, 10000) != 0 { return 40 } 40 if nx_col_pmi_q10(100, 0, 5, 10000) != 0 { return 41 } 41 if nx_col_pmi_q10(100, 100, 5, 0) != 0 { return 42 } 42 43 // === Test 6: log-likelihood smoke =============================== 44 // 45 // Strong association case: same n_a=100, n_b=100, n_ab=50, N=10000. 46 // Expected E11 = 100*100/10000 = 1. O11 = 50, way above expected. 47 // LL should be substantial (Dunning's stat is non-negative; large 48 // when observed and expected diverge sharply). 49 let ll_strong: nx_int = nx_col_log_likelihood_q10(100, 100, 50, 10000) 50 if ll_strong < NX_COL_LL_MODERATE_Q10 { return 50 } 51 let ll_band_strong: nx_int = nx_col_log_likelihood_band(ll_strong) 52 if nx_col_band_is_valid(ll_band_strong) != 1 { return 51 } 53 54 // Independence: LL should be near zero. 55 let ll_indep: nx_int = nx_col_log_likelihood_q10(100, 100, 1, 10000) 56 if ll_indep > NX_COL_LL_WEAK_Q10 { return 52 } 57 if nx_col_log_likelihood_band(ll_indep) != NX_COL_BAND_NONE { return 53 } 58 59 // === Test 7: T-score ============================================ 60 // 61 // T = (O - E) / sqrt(O); strong case n_ab=50, E11=1 62 // -> T ≈ (50-1)/sqrt(50) = 49/7.07 ≈ 6.93 -> Q10 ~ 7100 63 let t_strong: nx_int = nx_col_t_score_q10(100, 100, 50, 10000) 64 if nx_col_t_score_band(t_strong) != NX_COL_BAND_VERY_STRONG { return 60 } 65 66 // Independence: T near 0 67 let t_indep: nx_int = nx_col_t_score_q10(100, 100, 1, 10000) 68 if nx_col_t_score_band(t_indep) != NX_COL_BAND_NONE { return 61 } 69 70 // Zero co-occurrence 71 if nx_col_t_score_q10(100, 100, 0, 10000) != 0 { return 62 } 72 73 // === Test 8: log-Dice ============================================ 74 // 75 // logDice = 14 + log2(2 * n_ab / (n_a + n_b)) 76 // strong case: 14 + log2(100/200) = 14 + log2(0.5) = 14 - 1 = 13 77 let dice_strong: nx_int = nx_col_log_dice_q10(100, 100, 50) 78 // Expect ~13 Q10 = 13312 79 if dice_strong < 12288 { return 70 } // 12.0 80 if dice_strong > 14336 { return 71 } // 14.0 81 if nx_col_log_dice_band(dice_strong) != NX_COL_BAND_VERY_STRONG { return 72 } 82 83 // Independence: n_ab=1 -> 14 + log2(2/200) = 14 - 6.64 = 7.36 84 let dice_indep: nx_int = nx_col_log_dice_q10(100, 100, 1) 85 // STRONG band (7.36 falls in [7, 10)) 86 if nx_col_log_dice_band(dice_indep) != NX_COL_BAND_STRONG { return 73 } 87 88 // Zero co-occurrence 89 if nx_col_log_dice_q10(100, 100, 0) != 0 { return 74 } 90 91 // === Test 9: emit_all composite ================================= 92 let out: *i64 = (sys_mmap(NX_COL_OUT_FIELDS * NX_SIZEOF_NX_INT)) as *i64 93 nx_col_emit_all(100, 100, 50, 10000, out) 94 // All four metric Q10s should match the standalone calls 95 if out[NX_COL_OUT_PMI_Q10] != pmi_strong { return 80 } 96 if out[NX_COL_OUT_LL_Q10] != ll_strong { return 81 } 97 if out[NX_COL_OUT_T_Q10] != t_strong { return 82 } 98 if out[NX_COL_OUT_DICE_Q10] != dice_strong { return 83 } 99 // All four bands valid sealed-enum 100 if nx_col_band_is_valid(out[NX_COL_OUT_PMI_BAND]) != 1 { return 84 } 101 if nx_col_band_is_valid(out[NX_COL_OUT_LL_BAND]) != 1 { return 85 } 102 if nx_col_band_is_valid(out[NX_COL_OUT_T_BAND]) != 1 { return 86 } 103 if nx_col_band_is_valid(out[NX_COL_OUT_DICE_BAND]) != 1 { return 87 } 104 105 // === Test 10: sealed-enum validators =========================== 106 if nx_col_band_is_valid(NX_COL_BAND_NONE) != 1 { return 90 } 107 if nx_col_band_is_valid(NX_COL_BAND_VERY_STRONG) != 1 { return 91 } 108 if nx_col_band_is_valid(NX_COL_N_BANDS) != 0 { return 92 } 109 if nx_col_band_is_valid(99) != 0 { return 93 } 110 if nx_col_metric_is_valid(NX_COL_METRIC_PMI) != 1 { return 94 } 111 if nx_col_metric_is_valid(NX_COL_METRIC_LOG_DICE) != 1 { return 95 } 112 if nx_col_metric_is_valid(NX_COL_N_METRICS) != 0 { return 96 } 113 114 return 0 115}