Given a value function and the model, produce the greedy policy with respect to it, and report whether the policy changed.
The policy improvement theorem is the engine of every control algorithm here. If you have vπ and you build a new deterministic policy that acts greedily with respect to it,
π′(s)=argmaxa[r(s,a)+γ∑s′p(s′∣s,a)vπ(s′)]
then vπ′(s)≥vπ(s) for every state. Not on average, not eventually — every state, guaranteed. One greedy step can never make the policy worse.
The second half of the theorem is what gives you a stopping rule: if the greedy policy is identical to the one you started from, then vπ already satisfies the Bellman optimality equation, so π is optimal and you are done. That equality check is the policy_stable flag in policy iteration.
Because floating-point ties do occur (symmetric actions in a gridworld, for instance), you need a deterministic tie-break or the "did it change?" test will flip-flop forever between equally good policies. Here: break ties toward the lowest action index.
Implement:
def policy_improvement(policy, v, P, R, gamma):
...
Return a tuple (new_policy, stable) where new_policy is a list of ints and stable is a bool that is True exactly when new_policy == policy.
Lists in; a (list, bool) tuple out.
P = [[[0.0, 1.0], [1.0, 0.0]], [[1.0, 0.0], [0.0, 1.0]]]
R = [[0.0, 1.0], [2.0, 0.0]]
v = [1.0, 5.0]
new_policy, stable = policy_improvement([0, 1], v, P, R, 0.9)
print(new_policy, stable)
Output:
[0, 1] True
State 0: action 0 gives 0.0 + 0.9*5.0 = 4.5 and action 1 gives 1.0 + 0.9*1.0 = 1.9, so action 0 wins. State 1: action 0 gives 2.0 + 0.9*1.0 = 2.9 and action 1 gives 0.0 + 0.9*5.0 = 4.5, so action 1 wins. The greedy policy [0, 1] equals the policy passed in, so stable is True and policy iteration would halt here.
policy_improvement([0, 1], [1.0, 5.0], [[[0.0, 1.0], [1.0, 0.0]], [[1.0, 0.0], [0.0, 1.0]]], [[0.0, 1.0], [2.0, 0.0]], 0.9)
[0, 1] True
State 0: q(0,0)=0.0+0.95.0=4.5 beats q(0,1)=1.0+0.91.0=1.9, so the greedy action is 0. State 1: q(1,0)=2.0+0.91.0=2.9 versus q(1,1)=0.0+0.95.0=4.5, so the greedy action is 1. The greedy policy [0, 1] matches the input policy, so stable is True.
1 <= n_states <= 200, 1 <= n_actions <= 20stable must be True only when the new policy equals the input policy in every state.0.0 <= gamma <= 1.0(new_policy, stable) in that order.