Apply one sweep of the Bellman optimality operator to a table of action values.
The Bellman expectation equation averages over whatever the policy does. The Bellman optimality equation replaces that average with a maximum, which is what makes it non-linear and what makes it describe the best achievable behaviour rather than some particular behaviour:
q∗(s,a)=r(s,a)+γ∑s′p(s′∣s,a)maxa′q∗(s′,a′)
Read the structure carefully. The action a in state s is given — you are committed to it, so no max there. The max lives at the successor state, encoding "after this action I will act optimally from then on". Turning that equation into an assignment gives the optimality operator, and repeatedly applying it is value iteration.
This operator is a γ-contraction in the max norm, so for gamma < 1 it has a unique fixed point and iterating from any starting table converges to q∗. And because the max is attained by some action in every state, a deterministic optimal policy always exists — you never need to randomise to be optimal in a finite MDP.
Implement:
def q_optimality_backup(P, R, q, gamma):
...
Return the new table as a list of n_states lists of n_actions floats. Use the old q for every entry of the sweep (a synchronous update); do not read back values you wrote during this same sweep.
Nested lists of floats in, a nested list of floats out, rounded to 4 decimals by the grader.
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]]
q = [[1.0, 3.0], [4.0, -1.0]]
print([[round(x, 4) for x in row] for row in q_optimality_backup(P, R, q, 0.5)])
Output:
[[2.0, 2.5], [3.5, 2.0]]
The successor-state maxima are max(q[0]) = 3.0 and max(q[1]) = 4.0. Then q'(0,0) = 0.0 + 0.5*4.0 = 2.0, q'(0,1) = 1.0 + 0.5*3.0 = 2.5, and so on.
q_optimality_backup([[[0.0, 1.0], [1.0, 0.0]], [[1.0, 0.0], [0.0, 1.0]]], [[0.0, 1.0], [2.0, 0.0]], [[1.0, 3.0], [4.0, -1.0]], 0.5)
[[2.0, 2.5], [3.5, 2.0]]
First take the max over actions at each successor state: max(q[0])=3.0, max(q[1])=4.0. Action 0 in state 0 lands in state 1 with certainty, so q'(0,0)=0.0+0.54.0=2.0. Action 1 in state 0 lands in state 0, so q'(0,1)=1.0+0.53.0=2.5.
1 <= n_states <= 100, 1 <= n_actions <= 20P[s][a] sums to 1.0.0 <= gamma <= 1.0q table.