An MDP is fully specified by one four-argument function p(s′,r∣s,a). Derive the two quantities every algorithm actually consumes: the expected immediate reward r(s,a) and the state-transition probabilities p(s′∣s,a).
The five-tuple definition of an MDP is ⟨S,A,p,r,γ⟩, and the single object that carries all the dynamics is
p(s′,r∣s,a)=Pr{St+1=s′,Rt+1=r∣St=s,At=a}
a joint distribution over the pair (next state, reward). Almost no algorithm uses it in that form. Bellman backups need two marginals of it:
r(s,a)=∑s′∑rrp(s′,r∣s,a)p(s′∣s,a)=∑rp(s′,r∣s,a)
The subtlety is that the same successor state can be reached with different rewards. When you marginalise out the reward you must accumulate those probabilities, not overwrite them. A stochastic reward on a fixed transition is common (a noisy sensor, a randomised bonus), and an implementation that keeps only the last entry it saw produces a transition matrix whose rows do not sum to 1 — after which every value estimate downstream is quietly wrong.
Implement:
def expected_reward(dynamics, s, a):
...
def transition_probs(dynamics, s, a, n_states):
...
dynamics is a dict mapping the key (s, a) to a list of (s_next, reward, prob) tuples. The probabilities for one (s, a) key sum to 1.
Input is a dict keyed by (state, action) tuples. Outputs are a float and a list of floats, rounded to 4 decimals by the grader.
dyn = {(0, 0): [(0, 1.0, 0.3), (1, 1.0, 0.2), (1, -1.0, 0.4), (0, 2.0, 0.1)]}
print(round(expected_reward(dyn, 0, 0), 4))
print([round(p, 4) for p in transition_probs(dyn, 0, 0, 3)])
Output:
0.3
[0.4, 0.6, 0.0]
Expected reward: 0.31 + 0.21 + 0.4(-1) + 0.12 = 0.3**. State 0 is reached by two entries (0.3 + 0.1 = 0.4), state 1 by two entries (0.2 + 0.4 = 0.6).
dyn = {(0, 0): [(0, 1.0, 0.3), (1, 1.0, 0.2), (1, -1.0, 0.4), (0, 2.0, 0.1)]}
expected_reward(dyn, 0, 0), transition_probs(dyn, 0, 0, 3)0.3 [0.4, 0.6, 0.0]
r(s,a) sums reward*prob over every entry: 0.3 + 0.2 - 0.4 + 0.2 = 0.3. p(s'|s,a) sums probability over rewards, so the two entries landing in state 0 combine to 0.4 and the two landing in state 1 combine to 0.6.
1 <= n_states <= 100(s, a) may contain the same s_next more than once with different rewards.(s, a) sum to 1.(s, a) must appear as 0.0 in the returned list.