sketch_ucb1.nx
buildroot/runtime/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 · 2 importers
imports: syscalls.nxsketch_types.nx
imported by: sketch_thompson_vs_ucb1_bench.nxsketch_ucb1_test.nx
structs
| 48 | struct Ucb1 { |
consts
| 43 | const NX_UCB_ARMS_MIN: i64 = 2 |
| 44 | const NX_UCB_ARMS_MAX: i64 = 10000 |
| 45 | const NX_UCB_LN2_PPM: i64 = 693147 // ln(2) * 1_000_000 |
| 46 | const NX_UCB_REWARD_MAX: i64 = 1000000 // 1.0 in PPM |
functions
| 57 | func nx_ucb_alloc(n_arms: i64) -> *Ucb1 { |
| 79 | func nx_ucb_isqrt(x: i64) -> i64 { |
| 102 | func nx_ucb_bitlen(x: i64) -> i64 {
called by 1: nx_ucb_ln_ppm |
| 114 | func nx_ucb_ln_ppm(n: i64) -> i64 { |
| 133 | func nx_ucb_score(b: *Ucb1, arm: i64) -> i64 { |
| 150 | func nx_ucb_select(b: *Ucb1) -> i64 { |
| 170 | func nx_ucb_update(b: *Ucb1, arm: i64, reward_ppm: i64) -> i64 { |
| 183 | func nx_ucb_mean_ppm(b: *Ucb1, arm: i64) -> i64 { |
| 189 | func nx_ucb_count(b: *Ucb1, arm: i64) -> i64 {
called by 1: main |
| 194 | func nx_ucb_best_arm(b: *Ucb1) -> i64 { |
| 213 | func nx_ucb_query(b: *Ucb1, arm: i64) -> *ApproxI64 { |
| 227 | func nx_ucb_memory_bytes(b: *Ucb1) -> i64 {
called by 1: main |