Implement the two identities that connect the state-value function vπ and the action-value function qπ.
There are two value functions and they carry the same information in different shapes.
vπ(s) is the expected return from state s when the policy chooses the action, so it is the policy-weighted average of the action values:
vπ(s)=∑aπ(a∣s)qπ(s,a)
qπ(s,a) is the expected return from state s when you commit to action a first and follow the policy afterwards. Because the action is fixed, you pay the expected immediate reward and then land in a successor state whose value is already vπ:
qπ(s,a)=r(s,a)+γ∑s′p(s′∣s,a)vπ(s′)
Note the asymmetry: going from q to v needs only the policy, while going from v to q needs the model (r and p). That asymmetry is exactly why model-free control learns Q and not V — with Q in hand you can act greedily without knowing the dynamics at all.
Implement both:
def v_from_q(pi, q):
...
def q_from_v(P, R, v, gamma):
...
v_from_q returns a list of n_states floats. q_from_v returns a list of n_states lists of n_actions floats.
All inputs are nested Python lists of floats. Outputs are nested lists of floats, rounded to 4 decimals by the grader.
pi = [[0.6, 0.4], [0.2, 0.8], [1.0, 0.0]]
q = [[1.0, 2.0], [3.0, -1.0], [0.5, 0.5]]
print([round(x, 4) for x in v_from_q(pi, q)])
Output:
[1.4, -0.2, 0.5]
State 0: 0.61.0 + 0.42.0 = 1.4. State 1: 0.23.0 + 0.8(-1.0) = -0.2.
v_from_q([[0.6, 0.4], [0.2, 0.8], [1.0, 0.0]], [[1.0, 2.0], [3.0, -1.0], [0.5, 0.5]])
[1.4, -0.2, 0.5]
v(s) is the policy-weighted average of the action values: 0.61.0 + 0.42.0 = 1.4 for state 0, 0.23.0 + 0.8(-1.0) = -0.2 for state 1, and 1.0*0.5 = 0.5 for state 2.
1 <= n_states <= 100, 1 <= n_actions <= 20pi[s] sums to 1; each P[s][a] sums to 1.0.0 <= gamma <= 1.0R[s][a] is the expected immediate reward, already marginalised over successor states.