SabaPivot's picture
download
raw
11.4 kB
"""Claim 4 -- Corollary 3.7 of arXiv:2601.20180.
"Corollary 3.7 establishes an unconditional information-theoretic lower bound: any
algorithm requires 2^Omega(d) expected-risk-minimization (ERM) queries to find an
eps-performatively stable point even when rho <= 1 + O_eps(eps), and even for
constant eps."
The corollary is Theorem 3.6 (Hirsch, Papadimitriou & Vavasis 1989) pushed through
the reduction of Proposition 3.2. Executable content:
A. Proposition 3.2's reduction g(x) = (1-lambda)x + lambda T(x), lambda = eps/eps':
the fixed-point-gap identity, the Lipschitz/rho bound, and -- the crux of the
corollary -- that one ERM query reveals T(x) exactly, so the query lower bound
transfers one-for-one.
B. the arithmetic of Theorem 3.6: c((1/eps - 10)L)^{d-2} is 2^{Omega(d)} for every
constant eps <= 1/12, so "even when eps is a constant" is justified; and the
regime in which the base exceeds 1 at all.
C. TOY: a hidden-path Brouwer family in d dimensions (the HPV mechanism at reduced
scale) where the query cost of every strategy we implement grows like m^{d/2};
the exponent is fitted. This is our own construction, not the HPV instance.
"""
import numpy as np
import sympy as sp
from common import FIGS, dump
SEED = 20260725
rng = np.random.default_rng(SEED)
res = {"seed": SEED, "claim": "Corollary 3.7 (2^Omega(d) ERM queries)"}
EPS_PRIME = 0.088 / 6
# =====================================================================
# A. Proposition 3.2
# =====================================================================
def make_T(d, rng, L=1.0):
"""A random L-Lipschitz self-map of the unit ball."""
A = rng.normal(size=(d, d))
A = L * A / np.linalg.norm(A, 2)
c = rng.uniform(-0.2, 0.2, size=d)
def T(x):
y = A @ np.tanh(x) + c # tanh is 1-Lipschitz, so Lip(T) <= L
n = np.linalg.norm(y)
return y if n <= 1 else y / n
return T, A
# In Proposition 3.2, eps' is the target fixed-point accuracy for T and eps the
# performative accuracy, with lambda = eps/eps'. Corollary 3.7 instantiates eps'
# at a constant (Theorem 3.6 needs eps' <= 1/12 for a 2^(d-2) bound), so lambda is
# the free knob and lambda <= 1 is the meaningful regime.
EPS_PRIME_TARGET = 1.0 / 12
checks = []
for d in [2, 5, 20]:
for lam in [1.0, 0.5, 0.1, 0.01]:
eps = lam * EPS_PRIME_TARGET
T, A = make_T(d, rng, L=1.0)
def g(x, T=T, lam=lam):
return (1 - lam) * x + lam * T(x)
gap_id, lip, recov = [], 0.0, []
for _ in range(400):
x = rng.uniform(-1, 1, size=d)
x = x / max(1.0, np.linalg.norm(x))
y = rng.uniform(-1, 1, size=d)
y = y / max(1.0, np.linalg.norm(y))
# (i) fixed-point-gap identity ||x - G(x)|| = lambda ||x - T(x)||
gap_id.append(
abs(np.linalg.norm(x - g(x)) - lam * np.linalg.norm(x - T(x)))
)
# (ii) empirical Lipschitz constant of g (= rho, since alpha = beta = 1)
if np.linalg.norm(x - y) > 1e-9:
lip = max(
lip, float(np.linalg.norm(g(x) - g(y)) / np.linalg.norm(x - y))
)
# (iii) one ERM query G(x) reveals T(x) exactly
recov.append(float(np.abs((g(x) - (1 - lam) * x) / lam - T(x)).max()))
checks.append(
{
"d": d,
"eps": eps,
"lambda": lam,
"max_fixed_point_gap_identity_error": float(max(gap_id)),
"empirical_rho": lip,
"rho_bound_1_plus_lambda_L": 1 + lam * 1.0,
"rho_within_bound": bool(lip <= 1 + lam + 1e-9),
"max_T_recovery_error_from_one_ERM_query": float(max(recov)),
}
)
res["proposition_3_2"] = checks
print(
"A. Prop 3.2: gap identity max error %.2e | rho <= 1+lambda*L in %d/%d | "
"T(x) recovered from a single ERM query to %.2e"
% (
max(c["max_fixed_point_gap_identity_error"] for c in checks),
sum(c["rho_within_bound"] for c in checks),
len(checks),
max(c["max_T_recovery_error_from_one_ERM_query"] for c in checks),
)
)
res["query_equivalence"] = {
"statement": "G(x) = (1-lambda)x + lambda T(x) with lambda and x known, so "
"T(x) = (G(x) - (1-lambda)x)/lambda: the ERM oracle and the "
"T-oracle are information-theoretically equivalent, and the "
"Hirsch et al. bound transfers with no loss.",
"max_recovery_error": max(
c["max_T_recovery_error_from_one_ERM_query"] for c in checks
),
}
# =====================================================================
# B. the arithmetic of Theorem 3.6 -> 2^Omega(d)
# =====================================================================
epsm, Lm, dm = sp.symbols("epsilon L d", positive=True)
base = (1 / epsm - 10) * Lm
rows = []
for e_val in [
sp.Rational(1, 11),
sp.Rational(1, 12),
sp.Rational(1, 20),
sp.Rational(1, 100),
sp.Rational(1, 10),
]:
b = sp.simplify(base.subs({epsm: e_val, Lm: 1}))
rows.append(
{
"eps": str(e_val),
"base_at_L_1": str(b),
"base_float": float(b),
"base_gt_1_nontrivial": bool(b > 1),
"base_ge_2_gives_2_to_the_d": bool(b >= 2),
}
)
res["theorem_3_6_arithmetic"] = {
"bound": "c * ((1/eps - 10) L)^(d-2)",
"rows": rows,
"eps_threshold_for_base_2": str(sp.solve(sp.Eq(1 / epsm - 10, 2), epsm)),
"eps_threshold_for_base_1": str(sp.solve(sp.Eq(1 / epsm - 10, 1), epsm)),
"conclusion": "for any constant eps <= 1/12 the bound is at least c*2^(d-2) = "
"2^Omega(d); for eps > 1/11 the base drops below 1 and the bound "
"is vacuous, so 'even when eps is a constant' means a constant "
"below 1/11.",
}
print(
"B. Theorem 3.6 base (1/eps - 10)L at L=1: "
+ ", ".join("eps=%s -> %.3f" % (r["eps"], r["base_float"]) for r in rows)
)
print("B. base >= 2 (i.e. 2^Omega(d)) iff eps <= 1/12; base > 1 iff eps < 1/11")
# combined with Prop 3.2: rho <= 1 + lambda L = 1 + eps/eps' L
res["combined_regime"] = {
"rho_bound": "1 + (eps/eps') L",
"eps_prime": EPS_PRIME,
"rho_at_eps_1_over_12_L_1": 1 + (1 / 12) / EPS_PRIME,
"note": "Corollary 3.7 as stated says rho <= 1 + O_eps(eps); the reduction "
"gives rho <= 1 + eps/eps' * L, which is O(eps) for fixed eps' and L.",
}
# =====================================================================
# C. TOY: hidden-path Brouwer family, ERM-query cost vs dimension
# =====================================================================
def make_hidden_path(d, m, length, rng):
"""A self-avoiding walk on the grid {0..m-1}^d; only its end is a fixed point.
Off the path the direction field is the default +e1, so an off-path query is
consistent with every instance whose path misses that cell: the query reveals
only "not here". This is the mechanism of Hirsch et al. (1989) at toy scale.
"""
cell = np.zeros(d, dtype=int)
path = [tuple(cell)]
seen = {tuple(cell)}
for _ in range(length - 1):
opts = []
for i in range(d):
for s in (-1, 1):
nxt = cell.copy()
nxt[i] += s
if 0 <= nxt[i] < m and tuple(nxt) not in seen:
opts.append(nxt)
if not opts:
break
cell = opts[rng.integers(len(opts))]
path.append(tuple(cell))
seen.add(tuple(cell))
return path, set(path)
def solve_by_probing(path, pathset, d, m, rng, max_queries=10_000_000):
"""Query random cells until one lies on the path, then follow it to the end.
Following the path is free of search: each on-path query reveals the successor.
"""
end = path[-1]
q = 0
while q < max_queries:
c = tuple(int(rng.integers(m)) for _ in range(d))
q += 1
if c in pathset:
i = path.index(c)
q += len(path) - 1 - i # walk to the end, one ERM query per step
return q, True
if c == end:
return q, True
return q, False
toy = []
m = 3
for d in range(2, 10):
N = m**d
length = max(2, int(round(np.sqrt(N))))
qs = []
for trial in range(40):
path, pathset = make_hidden_path(d, m, length, rng)
q, ok = solve_by_probing(path, pathset, d, m, rng)
qs.append(q)
toy.append(
{
"d": d,
"grid_side": m,
"cells": N,
"path_length": length,
"median_queries": float(np.median(qs)),
"mean_queries": float(np.mean(qs)),
"trials": len(qs),
}
)
print(
"C. d=%d cells=%6d path=%4d median ERM queries = %.1f"
% (d, N, length, np.median(qs))
)
ds = np.array([t["d"] for t in toy], dtype=float)
lq = np.log(np.array([t["median_queries"] for t in toy]))
slope, intercept = np.polyfit(ds, lq, 1)
r2 = 1 - ((lq - (slope * ds + intercept)) ** 2).sum() / ((lq - lq.mean()) ** 2).sum()
res["toy_hidden_path"] = {
"label": "toy",
"blocker": "the true Hirsch-Papadimitriou-Vavasis instance is a continuous "
"Brouwer map on [0,1]^d whose construction we do not implement; this "
"is a discrete hidden-path family with the same information-hiding "
"mechanism, run at m=3 and d<=9.",
"measurements": toy,
"log_queries_slope_per_dimension": float(slope),
"predicted_slope_half_log_m": float(0.5 * np.log(m)),
"R2": float(r2),
"implied_base": float(np.exp(slope)),
"predicted_base_sqrt_m": float(np.sqrt(m)),
}
print(
"C. fit: log(median queries) = %.4f*d + %.3f (R^2=%.4f) -> queries ~ %.3f^d "
"(predicted sqrt(m) = %.3f, i.e. 2^Omega(d))"
% (slope, intercept, r2, np.exp(slope), np.sqrt(m))
)
# the same search driven purely by ERM queries of the performative instance
lam = 0.05
recover_err = []
for t in toy[:4]:
d = t["d"]
for _ in range(200):
x = rng.uniform(-1, 1, size=d)
Tx = rng.uniform(-1, 1, size=d)
G = (1 - lam) * x + lam * Tx
recover_err.append(float(np.abs((G - (1 - lam) * x) / lam - Tx).max()))
res["erm_query_drives_the_search"] = {
"max_error_recovering_T_from_ERM": float(max(recover_err)),
"conclusion": "every ERM query of the constructed performative instance yields "
"T(x) exactly, so the query counts above are simultaneously ERM "
"query counts.",
}
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
plt.figure(figsize=(5.4, 4))
plt.semilogy(
ds, [t["median_queries"] for t in toy], "o-", label="measured (median of 40)"
)
plt.semilogy(
ds,
np.exp(intercept) * np.exp(slope * ds),
"k--",
label="fit %.2f^d" % np.exp(slope),
)
plt.xlabel("dimension d")
plt.ylabel("ERM queries to find the fixed point")
plt.title("hidden-path family: queries grow exponentially in d")
plt.legend(fontsize=8)
plt.grid(alpha=0.3, which="both")
plt.tight_layout()
p = FIGS + "/claim4_query_lb.png"
plt.savefig(p, dpi=130)
print("wrote", p)
res["verdict"] = {
"reduction_and_query_equivalence_verified": bool(
max(c["max_T_recovery_error_from_one_ERM_query"] for c in checks) < 1e-9
),
"exponential_arithmetic_verified": True,
"empirical_family": "toy",
}
dump("claim4_erm_query_lb.json", res)

Xet Storage Details

Size:
11.4 kB
·
Xet hash:
406cc8a9a8bee1e58a737e9f8368f327a1d6d0f1c30bacd54048698d70db7ae2

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