"""Reduction-to-discounted framework (Section 4, Theorem 4.2). Solves the robust AMDP by reducing it to a robust DMDP with discount gamma_reduction = 1 - eps / H, then solving the discounted problem. Theorem 4.2 guarantees an eps_gamma-optimal DMDP policy is O(eps)-optimal for the AMDP. This module provides: * the model-based reduction (exact discounted VI at gamma_reduction) to verify Theorem 4.2's policy-transfer guarantee numerically; * a model-free discounted robust solver (sample-based) to MEASURE the reduction's sample complexity and confirm it is worse (Õ(SA H^2/eps^4)) than RHI's direct approach (Õ(SA H^2/eps^2)); * the algebraic derivation of the Õ(SA H^2/eps^4) bound. """ from __future__ import annotations import numpy as np from . import mdp as M def reduction_gamma(eps: float, H: float) -> float: """gamma_reduction = 1 - eps / H (Theorem 4.2). REQUIRES knowledge of H.""" return 1.0 - eps / H def reduction_model_based(r: np.ndarray, P: np.ndarray, unc: M.Uncertainty, eps: float, H: float, iters: int = 5000 ) -> tuple[np.ndarray, float, float]: """Exact discounted-VI reduction. Returns (pi, gamma, discounted subopt). Verifies Theorem 4.2: an (near-)optimal policy for the DMDP at gamma_reduction is O(eps)-optimal for the AMDP. """ gamma = reduction_gamma(eps, H) V, Q = M.robust_discounted_vi(r, P, unc, gamma, iters=iters) pi = Q.argmax(axis=1) g_star, _, _ = M.robust_optimal_span_rvi(r, P, unc) g_pi, _ = M.robust_policy_gain(pi, r, P, unc) return pi, gamma, g_star - g_pi def discounted_sample_based(r: np.ndarray, P0: np.ndarray, unc: M.Uncertainty, gamma: float, n_samples: int, rng: np.random.Generator, iters: int = 300 ) -> tuple[np.ndarray, int]: """Model-free robust discounted VI: estimate sigma from n_samples per (s,a). A faithful generative-model discounted robust solver. Returns (pi, budget) where budget = S*A*n_samples*iters is the total transition samples consumed. """ S, A = r.shape V = np.zeros(S) for _ in range(iters): # estimate sigma_{P_sa}(V) from n_samples for every (s,a) sig = np.empty((S, A)) for s in range(S): for a in range(A): Prow = P0[s, a] idx = rng.choice(len(Prow), size=n_samples, p=Prow) R = unc.radius if np.isscalar(unc.radius) else unc.radius[s, a] emp = float(np.mean(V[idx])) if unc.kind == "contamination": sig[s, a] = (1 - R) * emp + R * float(np.min(V)) else: reach = Prow > 0 sig[s, a] = emp - R * M.kappa_q(V, unc.p, reach) Q = r + gamma * sig Vn = Q.max(axis=1) if np.max(np.abs(Vn - V)) < 1e-7: V = Vn break V = Vn pi = (r + gamma * _sigma_from_V(V, P0, unc)).argmax(axis=1) budget = S * A * n_samples * iters return pi, budget def _sigma_from_V(V, P0, unc): S, A = P0.shape[:2] out = np.empty((S, A)) for s in range(S): for a in range(A): Prow = P0[s, a] R = unc.radius if np.isscalar(unc.radius) else unc.radius[s, a] reach = Prow > 0 if unc.kind == "contamination": out[s, a] = (1 - R) * np.dot(Prow, V) + R * np.min(V) else: out[s, a] = np.dot(Prow, V) - R * M.kappa_q(V, unc.p, reach) return out # --- algebraic derivation of the Õ(SA H^2 / eps^4) bound --------------------- def reduction_algebra(eps: float, H: float, S: int, A: int) -> dict: """Reconstruct the Õ(SA H^2 / eps^4) sample-complexity derivation (Sec. 4). robust DMDP sample complexity (Clavier et al. 2024, lp) with discount gamma and target eps_gamma: N = Õ( SA / ((1-gamma) * eps_gamma^2) ) using the effective-horizon 1/(1-gamma). Reduction sets gamma = 1 - eps/H and needs eps_gamma = Theta(eps * (1-gamma)) = Theta(eps^2/H) for the discounted value to be eps-optimal after the (1-gamma) rescaling -> N = Õ(SA H^2 / eps^4). We expose each factor so the derivation is machine-checkable, and also quote the paper's direct statement (eps_gamma = H) which gives the same H^2/eps^4. """ gamma = reduction_gamma(eps, H) one_minus = eps / H # paper's direct choice: eps_gamma = H eps_gamma_paper = H N_paper = float(S * A / (one_minus ** 2 * eps_gamma_paper ** 2)) # -> SA H^2/eps^4 return dict(eps=eps, H=H, S=S, A=A, gamma=gamma, one_minus_gamma=one_minus, eps_gamma_paper=eps_gamma_paper, N_paper_tilde=N_paper, expected_H_power=2, expected_eps_power=4)