Run TD(0) over a recorded stream of transitions and return the updated value table.
Monte Carlo prediction has to wait for the episode to end before it can update anything. TD(0) does not wait: after every single transition it forms a target from the reward it just received plus its own current estimate of where it landed.
δt=Rt+1+γV(St+1)−V(St)V(St)←V(St)+αδt
δt is the TD error — the surprise, the gap between what you predicted and what one step of reality plus your own next prediction say. Using your own estimate inside the target is bootstrapping, and it is what makes TD biased early on but dramatically lower variance than waiting for a full return.
The detail that breaks implementations: at a terminal transition the target is just the reward. V(terminal)=0 by definition, so bootstrapping off the state after termination is not merely wrong, it leaks value into episodes that already ended. Every real implementation gates the bootstrap term on a done flag.
The updates are online: each transition uses the table as updated by all previous transitions, so revisiting a state within the stream compounds.
Implement:
def td0_prediction(v_init, transitions, alpha, gamma):
...
Return the final table as a list of floats.
A list of floats and a list of tuples in; a list of floats out, rounded to 4 decimals by the grader.
v = [0.0, 0.0, 0.0]
transitions = [(0, 1.0, 1, False), (1, 2.0, 2, True)]
print([round(x, 4) for x in td0_prediction(v, transitions, 0.5, 0.9)])
Output:
[0.5, 1.0, 0.0]
First transition: target 1.0 + 0.9*0.0 = 1.0, so V(0) = 0 + 0.5(1.0 - 0) = 0.5*. Second transition is terminal, so the target is just 2.0 and V(1) = 0 + 0.5(2.0 - 0) = 1.0*.
td0_prediction([0.0, 0.0, 0.0], [(0, 1.0, 1, False), (1, 2.0, 2, True)], 0.5, 0.9)
[0.5, 1.0, 0.0]
The first transition is non-terminal so the target is 1.0 + 0.9V(1) = 1.0, giving V(0) = 0 + 0.5(1.0 - 0) = 0.5. The second is terminal so the target is just the reward 2.0, giving V(1) = 0 + 0.5*(2.0 - 0) = 1.0. V(2) is never the source state of a transition, so it stays 0.
1 <= len(v_init) <= 1000, 0 <= len(transitions) <= 1000000.0 <= alpha <= 1.0, 0.0 <= gamma <= 1.0done is True the target is r alone — do not add gamma * v[s_next].v_init must not be modified; return a new list.