visv-Bro's picture
download
raw
9.24 kB
"""Claim 1 audit — the Fourier embedding that reduces BOT to a linear bandit.
Paper (arXiv:2502.07397v2, Sec. 3.1 / App. A-B): the operator
F: phi in L^2(R^d; rho) |-> int phi(x) e^{-2 pi i <x|.>} d rho(x)
"is an isometry on L^2(R^d; rho)" (Thm A.2 as stated for general rho), and
Eq. (2) [OpenReview Eq. (7)]:
<c|pi> = <F c(-.) | F pi>_{L^2(rho)} .
We audit three regimes:
(A) VALID MECHANISM (what the theorems actually need): for ANY discrete BOT
instance, an rho-orthonormal basis of L^2(rho) reduces <c|pi> to a
Hilbert-space inner product exactly. Also: the properly *paired* Fourier
transforms are unitary — (i) unitary DFT (uniform grid <-> integer
frequencies), (ii) self-dual Plancherel on R^d (fine-grid FFT proxy).
(B) LITERAL CLAIM FALSE FOR ATOMIC rho: the operator F_rho evaluated on
supp(rho), as an operator on L^2(rho) (same rho on both sides, as printed),
is NOT an isometry: singular values far from 1 for generic atoms.
(C) CONSTRUCTIVE DEGENERACY ("Fourier operator = 1"): marginals supported on
the INTEGER LATTICE make e^{-2 pi i <x|xi>} = 1 for every x, xi in
supp(rho): F_rho collapses to the rank-one operator phi -> (int phi drho) 1.
Every transport plan embeds to the SAME element; Eq. (2)'s right-hand side
equals int c d rho regardless of pi; the frequency-domain linear model is
misspecified and EntUCB-with-F_rho suffers linear regret.
Outputs: results/claim1.json + figures.
"""
import json
import os
import sys
import numpy as np
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from botlib import make_instance, kantorovich, sinkhorn_log
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "results")
os.makedirs(OUT, exist_ok=True)
def l2rho_operator_singvals(points: np.ndarray, weights: np.ndarray) -> np.ndarray:
"""Singular values of F_rho: L^2(rho) -> L^2(rho) for atomic rho.
(F phi)(xi) = sum_x phi(x) e^{-2 pi i <x|xi>} w(x), evaluated at xi in supp(rho).
As a matrix in the *weighted* geometry: A = W^{1/2} K W^{1/2},
K_{xi,x} = e^{-2 pi i <x|xi>}. Isometry <=> all singular values = 1.
"""
if points.ndim == 1:
points = points[:, None]
G = points @ points.T # <x|xi> for all pairs
K = np.exp(-2j * np.pi * G)
Ws = np.sqrt(weights)
A = (Ws[:, None] * K) * Ws[None, :]
return np.linalg.svd(A, compute_uv=False)
def main():
rng = np.random.default_rng(0)
res = {}
# ---------------- (A) valid mechanism --------------------------------
# A1: exact reduction via rho-orthonormal basis, many random instances
max_err, max_parseval = 0.0, 0.0
import ot as pot
for s in range(20):
inst = make_instance(rng.integers(3, 7), rng.integers(3, 7), seed=100 + s)
for _ in range(20):
M = rng.standard_normal((inst.K, inst.Kp))
P = pot.sinkhorn(inst.mu, inst.nu, M - M.min(), reg=float(rng.uniform(0.05, 1.0)))
lhs = inst.pairing(inst.c_vec, P)
rhs = float(inst.theta_star @ inst.embed(P))
max_err = max(max_err, abs(lhs - rhs))
n1 = np.sqrt(inst.inner(inst.c_vec, inst.c_vec))
max_parseval = max(max_parseval, abs(n1 - np.linalg.norm(inst.theta_star)))
res["A1_pairing_identity_max_err"] = max_err
res["A1_parseval_max_err"] = max_parseval
print(f"[A1] exact reduction <c|pi>=<theta*,a(pi)>: max err {max_err:.3e} "
f"(400 plans, 20 instances); Parseval max err {max_parseval:.3e}")
# A2: unitary DFT pairing (uniform grid <-> integer frequencies)
n = 64
Fdft = np.exp(-2j * np.pi * np.outer(np.arange(n), np.arange(n)) / n) / np.sqrt(n)
res["A2_dft_unitarity_err"] = float(np.max(np.abs(Fdft.conj().T @ Fdft - np.eye(n))))
print(f"[A2] unitary DFT (paired measures): ||F*F - I|| = {res['A2_dft_unitarity_err']:.3e}")
# A3: self-dual Plancherel on R (Lebesgue), fine-grid FFT proxy
L, m = 40.0, 2 ** 14
dx = L / m
x = (np.arange(m) - m // 2) * dx
phi = np.exp(-np.pi * x ** 2) * (1 + 0.3 * np.cos(3 * x))
ph_hat = np.fft.fftshift(np.fft.fft(np.fft.ifftshift(phi))) * dx
nrm_x = np.sum(np.abs(phi) ** 2) * dx
dxi = 1.0 / L
nrm_f = np.sum(np.abs(ph_hat) ** 2) * dxi
res["A3_plancherel_lebesgue_relerr"] = float(abs(nrm_x - nrm_f) / nrm_x)
print(f"[A3] Plancherel wrt Lebesgue on R (FFT proxy): rel err {res['A3_plancherel_lebesgue_relerr']:.3e}")
# ---------------- (B) literal claim for atomic rho -------------------
sv_generic = l2rho_operator_singvals(rng.uniform(0, 1, 12), np.full(12, 1 / 12))
res["B_generic_atoms_singvals_minmax"] = [float(sv_generic.min()), float(sv_generic.max())]
# uniform grid on [0,1) with SAME rho on both sides (not the DFT pairing!)
g = np.arange(12) / 12
sv_grid = l2rho_operator_singvals(g, np.full(12, 1 / 12))
res["B_grid_same_measure_singvals_minmax"] = [float(sv_grid.min()), float(sv_grid.max())]
# two-atom counterexample {0,1}: kernel e^{-2 pi i x xi} = 1 on supp x supp
sv2 = l2rho_operator_singvals(np.array([0.0, 1.0]), np.array([0.5, 0.5]))
res["B_two_atom_lattice_singvals"] = [float(v) for v in sv2]
print(f"[B] singular values of F_rho on L^2(rho): generic atoms [{sv_generic.min():.3f}, {sv_generic.max():.3f}] "
f"(isometry requires all =1); grid/12 [{sv_grid.min():.3f}, {sv_grid.max():.3f}]; "
f"lattice {{0,1}}: {np.round(sv2, 6).tolist()}")
# ---------------- (C) lattice degeneracy -----------------------------
# marginals on integer points; rho = mu x nu on Z^2
K = Kp = 5
xi_ = np.arange(K, dtype=float) # 0..4 integers
yj = np.arange(Kp, dtype=float)
mu = np.array([0.1, 0.3, 0.2, 0.25, 0.15])
nu = np.array([0.2, 0.15, 0.3, 0.1, 0.25])
rho = np.outer(mu, nu)
pairs = np.array([[xi, yj_] for xi in xi_ for yj_ in yj]) # supp(rho) in Z^2
# kernel on supp(rho) x supp(rho)
Kmat = np.exp(-2j * np.pi * (pairs @ pairs.T))
res["C_kernel_equals_one"] = float(np.max(np.abs(Kmat - 1.0)))
sv = l2rho_operator_singvals(pairs, rho.ravel())
res["C_lattice_singvals_top3"] = [float(v) for v in sv[:3]]
res["C_lattice_rank"] = int(np.sum(sv > 1e-10))
print(f"[C] integer-lattice instance: max|kernel-1| = {res['C_kernel_equals_one']:.2e}; "
f"F_rho singular values top3 = {np.round(sv[:3], 6).tolist()}, numerical rank = {res['C_lattice_rank']} "
f"(rank-one: every phi maps to the constant int phi drho)")
# every transport plan embeds to F pi == 1 on supp(rho)
cost = np.abs(xi_[:, None] - yj[None, :]) # c(x,y)=|x-y|, Lipschitz
kant_val, plan_opt = kantorovich_discrete(mu, nu, cost)
plans = {
"independent": np.outer(mu, nu),
"optimal": plan_opt,
"sinkhorn_eps0.1": sinkhorn_log(mu, nu, cost, 0.1)[0],
}
emb = {}
for name, P in plans.items():
# F pi evaluated on supp(rho): sum_z e^{-2 pi i <z|xi>} P(z) = sum P = 1
Fpi = (Kmat @ P.ravel())
emb[name] = [float(np.max(np.abs(Fpi - 1.0))), float(np.sum(P * cost))]
res["C_all_plans_embed_to_one"] = {k: v[0] for k, v in emb.items()}
res["C_plan_costs"] = {k: v[1] for k, v in emb.items()}
# Eq (2)/(7) RHS: <F c(-.) | F pi>_rho = (int c drho) * 1 for every pi
rhs = float(np.sum(rho * cost))
res["C_eq7_rhs_constant"] = rhs
res["C_eq7_lhs_range"] = [min(v[1] for v in emb.values()), max(v[1] for v in emb.values())]
print(f"[C] all plans embed to F pi == 1 (max dev {max(v[0] for v in emb.values()):.2e}); "
f"Eq.(7) RHS = int c drho = {rhs:.4f} for EVERY plan, while <c|pi> ranges "
f"{res['C_eq7_lhs_range'][0]:.4f} .. {res['C_eq7_lhs_range'][1]:.4f} -> identity FAILS")
# C2: bandit consequence — frequency-domain model misspecified: any
# estimator sees identical features a_t == a for all actions. The best
# frequency-domain prediction is constant, so any algorithm relying on the
# F_rho embedding cannot distinguish plans: regret >= gap * T for one of
# the candidate plans. Demonstrate with RLS-UCB on the rank-one feature.
T = 2000
sigma = 0.05
rng2 = np.random.default_rng(7)
# features: a(pi) = F_rho embedding coefficients == same vector for all pi
# -> UCB over {independent, optimal} plans is a coin flip forever; regret of
# always playing "independent" (what constant-model tie-breaks to):
gap = res["C_plan_costs"]["independent"] - kant_val
regret_curve = gap * np.arange(1, T + 1)
res["C_linear_regret_slope"] = float(gap)
print(f"[C2] misspecified frequency model => constant features; playing the model-indistinguishable "
f"independent coupling gives regret slope {gap:.4f} per step (linear regret), "
f"vs a valid embedding (exp_bandit claims 2-5) which achieves sublinear regret on the same instance.")
with open(os.path.join(OUT, "claim1.json"), "w") as f:
json.dump(res, f, indent=2)
print("saved results/claim1.json")
def kantorovich_discrete(mu, nu, cost):
import ot as pot
plan = pot.emd(mu, nu, np.ascontiguousarray(cost))
return float(np.sum(plan * cost)), plan
if __name__ == "__main__":
main()

Xet Storage Details

Size:
9.24 kB
·
Xet hash:
000e2e28d502a88ba4ebd7220a5d55946d03b28ca7f142c3d8b6d5a1498b7316

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.