Run value iteration to convergence on a finite MDP and return both the optimal value function and the optimal deterministic policy.
Policy iteration alternates a full evaluation with a greedy improvement. The evaluation is the expensive half, and value iteration collapses it away: it turns out you can truncate policy evaluation after a single sweep, provided that sweep uses the max instead of the policy average.
vk+1(s)←maxa[r(s,a)+γ∑s′p(s′∣s,a)vk(s′)]
That is one update, doing evaluation and improvement at the same time. It is the Bellman optimality operator, a γ-contraction in the max norm, so it converges to the unique fixed point v∗ from any start.
The policy is not maintained during the loop. It is extracted once at the end, greedily with respect to the converged values. This is the sharpest illustration of generalized policy iteration: evaluation and improvement do not have to complete, or even be separate steps, for the pair to converge — they only have to keep pushing toward consistency and greediness.
A practical note: the values converge much faster than the greedy policy changes, so the extracted policy is usually optimal long before delta gets small. Stopping early therefore costs you accurate values, rarely a good policy.
Implement:
def value_iteration(P, R, gamma, theta=1e-10, max_sweeps=100000):
...
Return a tuple (v, policy) — a list of floats and a list of ints.
Nested lists of floats in. A (list_of_floats, list_of_ints) tuple out; the grader rounds the values to 4 decimals.
P = [[[0.0, 1.0], [1.0, 0.0]], [[0.0, 1.0], [0.0, 1.0]]]
R = [[1.0, 0.0], [0.0, 0.0]]
v, policy = value_iteration(P, R, 0.9)
print([round(x, 4) for x in v], policy)
Output:
[1.0, 0.0] [0, 0]
State 1 is absorbing with zero reward, so v(1) = 0. In state 0, action 0 earns 1 and moves to state 1 while action 1 earns 0 and stays put, so v(0) = 1 and the optimal action is 0.
value_iteration([[[0.0, 1.0], [1.0, 0.0]], [[0.0, 1.0], [0.0, 1.0]]], [[1.0, 0.0], [0.0, 0.0]], 0.9)
[1.0, 0.0] [0, 0]
State 1 self-loops with zero reward so v(1)=0. In state 0 action 0 pays 1 then leaves, action 1 pays 0 and stays, so the optimality backup fixes v(0)=1 and the greedy action in state 0 is 0. In state 1 both actions are identical, so the tie-break picks action 0.
1 <= n_states <= 200, 1 <= n_actions <= 20P[s][a] sums to 1.0.0 <= gamma < 1.0 for the general case; gamma = 0.0 must give the greedy-on-immediate-reward answer.(v, policy) in that order.