nx_sketch_ucb1.nx
buildroot/runtime/nx_sketch_ucb1.nx
about
sketch_ucb1.nx -- UCB1 multi-armed bandit (Auer-Cesa-Bianchi-Fischer 2002).
Online-learning primitive. Given N "arms" (actions), select the
best one over time while balancing EXPLORATION (try arms we
haven't pulled) and EXPLOITATION (favor arms with high observed
reward).
UCB1 selection rule:
score_i = mean_i + sqrt(2 * ln(N_total) / count_i)
pull arg max score_i
Where:
mean_i = sum_reward_i / count_i (estimated value)
sqrt(...) = exploration bonus (shrinks as count_i grows)
Arms with count_i = 0 have score = +inf (must-pull first).
PROVEN PROPERTIES (Auer 2002):
regret bound: O(sqrt(K * N * ln(N)))
where K = #arms, N = total pulls. Near-optimal for adversarial
problems; optimal up to log factor for stochastic.
USE CASES (extends NishiLang substrate beyond DataSketches scope):
- A/B testing with multi-arm allocation (Bayesian alternative)
- Recommendation with cold-start exploration
- Hyperparameter tuning under budget constraints
- Game AI move selection
INTEGER-FIXED-POINT IMPLEMENTATION:
- rewards scaled to PPM (0..1_000_000 = [0, 1.0])
- mean_i = sum_reward_ppm / count_i
- ln(N) ≈ (bitlen(N) - 1) * 693147 / 1000 ppm (factor of ln(2))
- sqrt() via isqrt (Newton)
- score_i in PPM
LOSSLESS-LANGUAGE DISCIPLINE: nx_ucb_query returns ApproxI64 with
NX_ENV_REL_STDDEV = 1/sqrt(count_i). Bandits are stochastic by
nature; the envelope honestly declares estimation uncertainty.
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_sketch_types.nx
imported by: nobody (leaf or entry point)
structs
| 54 | struct Ucb1 |
consts
| 49 | const NX_UCB_ARMS_MIN: i64 = 2 |
| 50 | const NX_UCB_ARMS_MAX: i64 = 10000 |
| 51 | const NX_UCB_LN2_PPM: i64 = 693147 // ln(2) * 1_000_000 |
| 52 | const NX_UCB_REWARD_MAX: i64 = 1000000 // 1.0 in PPM |
functions
| 63 | func nx_ucb_alloc(n_arms: i64) -> *Ucb1 calls 1: sys_mmap |
| 85 | func nx_ucb_isqrt(x: i64) -> i64 |
| 108 | func nx_ucb_bitlen(x: i64) -> i64 called by 1: nx_ucb_ln_ppm |
| 120 | func nx_ucb_ln_ppm(n: i64) -> i64 |
| 139 | func nx_ucb_score(b: *Ucb1, arm: i64) -> i64 |
| 156 | func nx_ucb_select(b: *Ucb1) -> i64 calls 1: nx_ucb_score |
| 176 | func nx_ucb_update(b: *Ucb1, arm: i64, reward_ppm: i64) -> i64 |
| 189 | func nx_ucb_mean_ppm(b: *Ucb1, arm: i64) -> i64 |
| 195 | func nx_ucb_count(b: *Ucb1, arm: i64) -> i64 |
| 200 | func nx_ucb_best_arm(b: *Ucb1) -> i64 calls 1: nx_ucb_mean_ppm |
| 219 | func nx_ucb_query(b: *Ucb1, arm: i64) -> *ApproxI64 |
| 233 | func nx_ucb_memory_bytes(b: *Ucb1) -> i64 |