sketch_cusum_test.nx source
↩ module page · 121 lines · 3710 B
1// sketch_cusum_test.nx -- CUSUM change-point detector verification.
2
3import "syscalls.nx"
4import "sketch_cusum.nx"
5import "sketch_types.nx"
6
7func main() -> i64 {
8 // ---- alloc ----
9 let c: *Cusum = nx_cusum_alloc(100, 5, 50) // k=100, delta=5, h=50
10 if c == (0 as *Cusum) { return __syscall(93, 5, 0, 0, 0, 0, 0) }
11 // Reject delta < 0.
12 if nx_cusum_alloc(100, -1, 50) != (0 as *Cusum) {
13 return __syscall(93, 6, 0, 0, 0, 0, 0)
14 }
15 // Reject h <= 0.
16 if nx_cusum_alloc(100, 5, 0) != (0 as *Cusum) {
17 return __syscall(93, 7, 0, 0, 0, 0, 0)
18 }
19
20 // ---- stable stream around k=100: no alarm ----
21 var i: i64 = 0
22 while i < 50 {
23 // Alternating +/-3 jitter around 100, well within slack delta=5.
24 let v: i64 = 100 + (i % 2) * 3 - (i % 2) * (1 - (i % 2))
25 nx_cusum_add(c, 100 + (i & 1) * 2 - 1) // +/-1 jitter
26 i = i + 1
27 }
28 if nx_cusum_verdict(c) != NX_CUSUM_NORMAL {
29 return __syscall(93, 10, 0, 0, 0, 0, 0)
30 }
31 if nx_cusum_n(c) != 50 {
32 return __syscall(93, 11, 0, 0, 0, 0, 0)
33 }
34 if nx_cusum_n_alarms(c) != 0 {
35 return __syscall(93, 12, 0, 0, 0, 0, 0)
36 }
37
38 // ---- UPWARD shift: alarm fires ----
39 let c2: *Cusum = nx_cusum_alloc(100, 2, 30) // k=100, delta=2, h=30
40 // Baseline stable.
41 i = 0
42 while i < 20 {
43 nx_cusum_add(c2, 100)
44 i = i + 1
45 }
46 // Now jump: insert 15 of value 120 (way above baseline).
47 i = 0
48 while i < 15 {
49 nx_cusum_add(c2, 120)
50 i = i + 1
51 }
52 // s_pos += (120 - 100 - 2) = 18 per step. After 2 steps: 36 > h=30.
53 if nx_cusum_verdict(c2) != NX_CUSUM_SHIFT_UP {
54 return __syscall(93, 20, 0, 0, 0, 0, 0)
55 }
56 // step() returns verdict AND resets.
57 let v_up: i64 = nx_cusum_step(c2)
58 if v_up != NX_CUSUM_SHIFT_UP {
59 return __syscall(93, 21, 0, 0, 0, 0, 0)
60 }
61 // After step, s_pos and s_neg are 0; n_alarms = 1.
62 if nx_cusum_s_pos(c2) != 0 {
63 return __syscall(93, 22, 0, 0, 0, 0, 0)
64 }
65 if nx_cusum_n_alarms(c2) != 1 {
66 return __syscall(93, 23, 0, 0, 0, 0, 0)
67 }
68 if nx_cusum_verdict(c2) != NX_CUSUM_NORMAL {
69 return __syscall(93, 24, 0, 0, 0, 0, 0)
70 }
71
72 // ---- DOWNWARD shift ----
73 let c3: *Cusum = nx_cusum_alloc(100, 2, 30)
74 i = 0
75 while i < 20 {
76 nx_cusum_add(c3, 100)
77 i = i + 1
78 }
79 i = 0
80 while i < 15 {
81 nx_cusum_add(c3, 80) // 20 below k
82 i = i + 1
83 }
84 if nx_cusum_verdict(c3) != NX_CUSUM_SHIFT_DOWN {
85 return __syscall(93, 30, 0, 0, 0, 0, 0)
86 }
87
88 // ---- small drift within delta slack: no alarm ----
89 let c4: *Cusum = nx_cusum_alloc(100, 10, 30) // generous slack
90 i = 0
91 while i < 100 {
92 nx_cusum_add(c4, 105) // drift of 5, within slack 10
93 i = i + 1
94 }
95 if nx_cusum_verdict(c4) != NX_CUSUM_NORMAL {
96 return __syscall(93, 40, 0, 0, 0, 0, 0)
97 }
98
99 // ---- typed envelope ----
100 let q: *ApproxI64 = nx_cusum_query(c2)
101 if q.envelope_kind != NX_ENV_ABS {
102 return __syscall(93, 50, 0, 0, 0, 0, 0)
103 }
104 if q.param_a != 0 { // exact verdict
105 return __syscall(93, 51, 0, 0, 0, 0, 0)
106 }
107 if q.maturity != NX_MATURITY_PRODUCTION {
108 return __syscall(93, 52, 0, 0, 0, 0, 0)
109 }
110
111 // ---- reset ----
112 nx_cusum_reset(c2)
113 if nx_cusum_s_pos(c2) != 0 {
114 return __syscall(93, 60, 0, 0, 0, 0, 0)
115 }
116 if nx_cusum_s_neg(c2) != 0 {
117 return __syscall(93, 61, 0, 0, 0, 0, 0)
118 }
119
120 return 0
121}