Utility-Weighted Left-Edge Quantization / experiment.py
Mechanism confirmed, baseline not beaten
1import json
2import numpy as np
3from scipy.optimize import minimize_scalar
4
5SEED = 3148
6rng = np.random.default_rng(SEED)
7
8# A bounded activation model makes the infinite-domain construction numerically
9# concrete while preserving the paper's local asymptotics.
10XMAX = 8.0
11RATE = 1.0
12SCALE = 0.5
13
14
15def sample_x(n=400000):
16 u = rng.random(n)
17 # inverse CDF of Exp(1) conditional on x <= XMAX
18 return -np.log(1.0 - u * (1.0 - np.exp(-RATE * XMAX))) / RATE
19
20
21def density(x):
22 z = 1.0 - np.exp(-RATE * XMAX)
23 return RATE * np.exp(-RATE * x) / z
24
25
26def utility(x):
27 return np.log1p(np.maximum(x, 0.0) / SCALE) / np.log1p(XMAX / SCALE)
28
29
30def utility_prime(x):
31 return 1.0 / ((SCALE + x) * np.log1p(XMAX / SCALE))
32
33
34def weighted_thresholds(n, grid_n=200001):
35 grid = np.linspace(0.0, XMAX, grid_n)
36 g = np.sqrt(density(grid) * utility_prime(grid))
37 # cumulative trapezoidal integration
38 cdf = np.concatenate([[0.0], np.cumsum((g[:-1] + g[1:]) * np.diff(grid) * 0.5)])
39 cdf /= cdf[-1]
40 targets = np.arange(1, n) / n
41 internal = np.interp(targets, cdf, grid)
42 return np.r_[0.0, internal, XMAX]
43
44
45def uniform_thresholds(n):
46 return np.linspace(0.0, XMAX, n + 1)
47
48
49def left_edge_quantize(x, edges):
50 # edges has n+1 entries; the last edge is the finite support endpoint.
51 idx = np.searchsorted(edges, x, side="right") - 1
52 return edges[np.clip(idx, 0, len(edges) - 2)]
53
54
55def lloyd_max(x, n, iterations=80):
56 # Standard MSE scalar quantizer, with nearest-centroid reconstruction.
57 cent = np.quantile(x, (np.arange(n) + 0.5) / n)
58 for _ in range(iterations):
59 boundaries = (cent[:-1] + cent[1:]) * 0.5
60 labels = np.searchsorted(boundaries, x)
61 new = cent.copy()
62 for k in range(n):
63 vals = x[labels == k]
64 if len(vals):
65 new[k] = vals.mean()
66 if np.max(np.abs(new - cent)) < 1e-10:
67 break
68 cent = new
69 return cent
70
71
72def lloyd_quantize(x, cent):
73 boundaries = (cent[:-1] + cent[1:]) * 0.5
74 return cent[np.searchsorted(boundaries, x)]
75
76
77def utility_constant():
78 grid = np.linspace(0.0, XMAX, 400001)
79 g = np.sqrt(density(grid) * utility_prime(grid))
80 z = np.trapz(g, grid)
81 return 0.5 * z * z
82
83
84def evaluate(x, q, ideal_utility=None):
85 if ideal_utility is None:
86 ideal_utility = utility(x)
87 gap = ideal_utility - utility(q)
88 return {
89 "utility_gap": float(np.mean(gap)),
90 "n_gap": None,
91 "mse": float(np.mean((x - q) ** 2)),
92 "upward_fraction": float(np.mean(q > x + 1e-12)),
93 "mean_abs_error": float(np.mean(np.abs(x - q))),
94 }
95
96
97def main():
98 x = sample_x()
99 ideal = utility(x)
100 c_theory = utility_constant()
101 rows = []
102 scaling = []
103 for n in [4, 8, 16, 32, 64]:
104 ew = weighted_thresholds(n)
105 eu = uniform_thresholds(n)
106 qw = left_edge_quantize(x, ew)
107 qu = left_edge_quantize(x, eu)
108 cent = lloyd_max(x, n)
109 ql = lloyd_quantize(x, cent)
110 rw = evaluate(x, qw, ideal)
111 ru = evaluate(x, qu, ideal)
112 rl = evaluate(x, ql, ideal)
113 rw["n_gap"] = n * rw["utility_gap"]
114 ru["n_gap"] = n * ru["utility_gap"]
115 rl["n_gap"] = n * rl["utility_gap"]
116 rows.append({"n": n, "weighted_left": rw, "uniform_left": ru, "mse_lloyd": rl})
117 scaling.append({"n": n, "weighted_n_gap": rw["n_gap"], "uniform_n_gap": ru["n_gap"], "theory_Cw": c_theory})
118
119 # Direct numerical check of the local allocation rule: weighted bins have
120 # nearly equal integrals of sqrt(p Q'), unlike uniform bins.
121 e = weighted_thresholds(32)
122 grid = np.linspace(0, XMAX, 300001)
123 g = np.sqrt(density(grid) * utility_prime(grid))
124 gcdf = np.concatenate([[0], np.cumsum((g[:-1] + g[1:]) * np.diff(grid) / 2)])
125 bin_mass = np.diff(np.interp(e, grid, gcdf))
126 allocation_check = {
127 "weighted_g_mass_cv": float(np.std(bin_mass) / np.mean(bin_mass)),
128 "weighted_g_mass_min": float(bin_mass.min()),
129 "weighted_g_mass_max": float(bin_mass.max()),
130 }
131
132 result = {
133 "seed": SEED,
134 "distribution": "Exp(rate=1) truncated to [0,8]",
135 "utility": "log1p(x/0.5)/log1p(8/0.5)",
136 "theory_Cw": c_theory,
137 "results": rows,
138 "scaling": scaling,
139 "allocation_check": allocation_check,
140 "interpretation": {
141 "claimed_signal": "weighted left-edge utility gap scales approximately as Cw/n and is lower than simple uniform left-edge",
142 "conservative_rule": "weighted and uniform left-edge have zero upward rounding by construction",
143 "mse_control": "Lloyd-Max minimizes squared error, not the monotone left-edge utility gap",
144 },
145 }
146 with open("results.json", "w") as f:
147 json.dump(result, f, indent=2)
148 print(json.dumps(result, indent=2))
149
150
151if __name__ == "__main__":
152 main()