Compute vπ by sweeping the Bellman expectation backup until the values stop moving.
Solving (I−γPπ)v=Rπ directly costs O(n3). Iterative policy evaluation instead turns the Bellman expectation equation into an assignment and applies it over and over:
vk+1(s)←∑aπ(a∣s)[r(s,a)+γ∑s′p(s′∣s,a)vk(s′)]
Each sweep costs O(n2∣A∣) and the operator is a γ-contraction, so vk→vπ from any initialisation. Starting from all zeros is conventional.
When to stop is the part that needs a decision. The standard rule tracks the largest change any state made during a sweep,
Δ=maxs∣vk+1(s)−vk(s)∣
and halts once Δ<θ. Note what this is: a measure of how much the estimate is still moving, not of how far it is from the truth. The two are related — the remaining error is bounded by roughly γΔ/(1−γ) — but as gamma approaches 1 that bound blows up, and a small Δ stops meaning a small error. This problem uses a synchronous sweep: every new value is computed from the previous sweep's table.
Implement:
def policy_evaluation(pi, P, R, gamma, theta=1e-8, max_sweeps=100000):
...
Return the final v as a list of floats.
Nested lists of floats in, a list of floats out, rounded to 4 decimals by the grader.
pi = [[1.0, 0.0], [1.0, 0.0]]
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]]
print([round(x, 4) for x in policy_evaluation(pi, P, R, 0.9)])
Output:
[1.0, 0.0]
The policy always takes action 0. From state 0 it collects reward 1 and moves to state 1, which is absorbing with zero reward, so v(1) = 0 and v(0) = 1 + 0.9*0 = 1.
policy_evaluation([[1.0, 0.0], [1.0, 0.0]], [[[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]
State 1 is absorbing with zero reward so its value stays 0. State 0 takes action 0, collects reward 1 and moves to state 1, so v(0) = 1 + 0.9*0 = 1. The sweeps converge to this after two passes.
1 <= n_states <= 200, 1 <= n_actions <= 20pi[s] sums to 1; each P[s][a] sums to 1.0.0 <= gamma <= 1.0. gamma = 1.0 is only used with MDPs that reach an absorbing zero-reward state.theta and max_sweeps are keyword arguments with the defaults shown.