Files
2026-07-13 12:09:03 +08:00

133 lines
3.8 KiB
Python

GRID = 4
TERMINAL = (3, 3)
ACTIONS = ("up", "down", "left", "right")
DELTAS = {"up": (-1, 0), "down": (1, 0), "left": (0, -1), "right": (0, 1)}
SLIP = 0.1
def states():
return [(r, c) for r in range(GRID) for c in range(GRID)]
def apply_move(state, direction):
dr, dc = DELTAS[direction]
r, c = state
nr = min(max(r + dr, 0), GRID - 1)
nc = min(max(c + dc, 0), GRID - 1)
return (nr, nc)
def perpendiculars(action):
if action in ("up", "down"):
return ("left", "right")
return ("up", "down")
def transitions(state, action):
if state == TERMINAL:
return [(state, 0.0, 1.0)]
outcomes = []
p_intended = 1.0 - SLIP
outcomes.append((apply_move(state, action), -1.0, p_intended))
for perp in perpendiculars(action):
outcomes.append((apply_move(state, perp), -1.0, SLIP / 2.0))
return outcomes
def q_value(state, action, V, gamma):
return sum(p * (r + gamma * V[s_next]) for s_next, r, p in transitions(state, action))
def policy_evaluation(policy, gamma=0.99, tol=1e-6, max_iter=5000):
V = {s: 0.0 for s in states()}
for _ in range(max_iter):
delta = 0.0
for state in states():
if state == TERMINAL:
continue
dist = policy(state)
v = sum(pi_a * q_value(state, action, V, gamma) for action, pi_a in dist.items())
delta = max(delta, abs(v - V[state]))
V[state] = v
if delta < tol:
return V
return V
def greedy_from_V(V, gamma=0.99):
policy = {}
for state in states():
if state == TERMINAL:
policy[state] = "up"
continue
best = max(ACTIONS, key=lambda a: q_value(state, a, V, gamma))
policy[state] = best
return policy
def policy_iteration(gamma=0.99, tol=1e-6):
policy = {s: "up" for s in states()}
sweeps = 0
for it in range(100):
V = policy_evaluation(lambda s: {policy[s]: 1.0}, gamma=gamma, tol=tol)
sweeps += 1
new_policy = greedy_from_V(V, gamma)
if new_policy == policy:
return V, policy, it + 1
policy = new_policy
return V, policy, 100
def value_iteration(gamma=0.99, tol=1e-6, max_iter=5000):
V = {s: 0.0 for s in states()}
for it in range(max_iter):
delta = 0.0
for state in states():
if state == TERMINAL:
continue
v = max(q_value(state, action, V, gamma) for action in ACTIONS)
delta = max(delta, abs(v - V[state]))
V[state] = v
if delta < tol:
return V, greedy_from_V(V, gamma), it + 1
return V, greedy_from_V(V, gamma), max_iter
def print_V(V, title):
print(f" {title}")
for r in range(GRID):
row = " ".join(f"{V[(r, c)]:7.2f}" for c in range(GRID))
print(" " + row)
def print_policy(policy, title):
arrows = {"up": "^", "down": "v", "left": "<", "right": ">"}
print(f" {title}")
for r in range(GRID):
row = " ".join(arrows[policy[(r, c)]] if (r, c) != TERMINAL else "." for c in range(GRID))
print(" " + row)
def main():
print("=== 4x4 stochastic GridWorld (slip=0.1), value iteration ===")
V_vi, pi_vi, n_vi = value_iteration(gamma=0.99)
print_V(V_vi, f"V* (converged in {n_vi} sweeps)")
print()
print_policy(pi_vi, "optimal policy")
print()
print("=== Same MDP, policy iteration ===")
V_pi, pi_pi, n_pi = policy_iteration(gamma=0.99)
print_V(V_pi, f"V* (converged in {n_pi} outer iters)")
print()
print_policy(pi_pi, "optimal policy")
print()
V_match = max(abs(V_vi[s] - V_pi[s]) for s in states())
print(f"sup-norm |V_vi - V_pi| = {V_match:.2e} (should be ~0)")
print(f"policies identical? {pi_vi == pi_pi}")
if __name__ == "__main__":
main()