VKVlad KulikovPublic / lectures
RL for LLM2 / 13

Module 2. State, Value, and Bellman Equations

Try each exercise first. Expand worked answers or project guidance when needed; their open state is shared between RU/EN.

Download course files (.zip) 382.1 KB

The archive preserves the source folder structure, both languages, and supporting files.

State, observation, and hidden dynamics

Classify each case using the most accurate label:

  • A. the observation is already a sufficient Markov state;
  • B. the observation is incomplete, so partial observability is the natural model.
  1. Only a ball's position is visible, while velocity is hidden and the dynamics have inertia.

  2. A chess position includes the side to move, castling rights, and the en-passant status.

  3. Pure autoregressive generation exposes the prompt and complete prefix, and the transition only appends the selected token.

  4. An agent sees only the last two messages. An earlier tool message identified the open file, but it has been dropped from context; the next response depends on that file.

Write 1-?, 2-?, 3-?, 4-?. Then explain why “state is a compressed history” is a useful construction but not a universal definition of the environment state.

Show answer and solution

Answer: 1-B, 2-A, 3-A, 4-B.

  1. Equal positions with different velocities induce different next-position distributions.

  2. The complete chess position, including rule-relevant flags, is sufficient for the next legal transition.

  3. Under the stated abstraction, the prompt and full prefix are sufficient: the selected token determines the next prefix, while token sampling is represented by the policy.

  4. Two complete histories can identify different open files yet share the same last two messages. The omitted observation changes the prediction of the next response, so the short window is insufficient.

In MDP theory, a state is a variable relative to which the Markov property holds. A representation constructed from history may serve as that state, but it can be incomplete, redundant, or different from the physical state of the environment.

What belongs to an MDP

Choose three correct statements about a finite Markov decision process (MDP).

  1. The policy π(a∣s)\pi(a\mid s) is part of P(s′∣s,a)P(s'\mid s,a) and cannot change without changing the MDP.

  2. The mean rˉ(s,a)\bar r(s,a) is obtained by averaging rˉ(s,a,s′)\bar r(s,a,s') over next states with probabilities P(s′∣s,a)P(s'\mid s,a); no conditional independence is needed.

  3. The Markov property requires state to retain past information that matters for the next transition once the action is known.

  4. A text transcript of tool use is automatically a complete state.

  5. The same MDP can be paired with different policies.

  6. A reward received on entry to a terminal state must be discarded because the terminal value is zero.

Show answer and solution

Answers: 2, 3, and 5.

The law of total expectation gives rˉ(s,a)=∑s′P(s′∣s,a)rˉ(s,a,s′)\bar r(s,a)=\sum_{s'}P(s'\mid s,a)\bar r(s,a,s'). Equality of all next-state conditional means would be an additional assumption, but averaging is always valid for integrable rewards. Markov sufficiency concerns future dynamics given the current state and action. A policy belongs to the agent's behavior, so different policies can operate in the same MDP.

Statement 1 conflates agent and environment. Statement 4 fails for an arbitrary text window that may omit relevant earlier observations; hidden state alone does not make the complete history insufficient. Statement 6 confuses reward on the terminal transition with continuation after that transition: the reward remains in Rt+1R_{t+1}, while the terminal continuation value is zero.

Termination versus truncation

For each transition, choose the correct one-step TD target. Let r=1r=1, γ=0.9\gamma=0.9, and V(s′)=4V(s')=4.

  1. The task reaches its terminal goal.

  2. Data collection stops at an external limit although the process could continue.

  3. The task is defined as “finish within 100 steps”; remaining budget is part of state and has been exhausted.

Options:

  • A. y=r=1y=r=1;
  • B. y=r+γV(s′)=4.6y=r+\gamma V(s')=4.6.

Write 1-?, 2-?, 3-?, and explain why a single legacy done flag is insufficient.

Show answer and solution

Answer: 1-A, 2-B, 3-A.

Using

mt=1{transition is not terminal},yt=rt+1+γmtV(st+1),m_t=\mathbf 1\{\text{transition is not terminal}\}, \qquad y_t=r_{t+1}+\gamma m_tV(s_{t+1}),

cases 1 and 3 have mt=0m_t=0, while external truncation in case 2 has mt=1m_t=1. Therefore their targets are 11, 4.64.6, and 11.

A single done flag does not reveal whether continuation should be masked or bootstrapped.

V, Q, and advantage

Choose three correct statements that follow from the definitions without changing the MDP.

  1. Vπ(s)V^\pi(s) always equals max⁡aQπ(s,a)\max_aQ^\pi(s,a).

  2. Vπ(s)=∑aπ(a∣s)Qπ(s,a)V^\pi(s)=\sum_a\pi(a\mid s)Q^\pi(s,a).

  3. Aπ(s,a)>0A^\pi(s,a)>0 means the action is better than the current policy's average action in that state.

  4. Arbitrary reward changes leave AπA^\pi unchanged.

  5. ∑aAπ(s,a)=0\sum_aA^\pi(s,a)=0 for every policy.

  6. Adding a common constant c(s)c(s) to every Q(s,a)Q(s,a) in one state, then recomputing VV, leaves its advantages unchanged.

Select all applicable statements.

Show answer and solution

Answers: 2, 3, and 6.

They follow from

Vπ(s)=∑aπ(a∣s)Qπ(s,a),Aπ(s,a)=Qπ(s,a)−Vπ(s).V^\pi(s)=\sum_a\pi(a\mid s)Q^\pi(s,a), \qquad A^\pi(s,a)=Q^\pi(s,a)-V^\pi(s).

Statement 6 is a useful consequence: both QQ and the policy average VV shift by c(s)c(s), so their difference is unchanged.

Statement 5 is missing policy weights; the correct identity is ∑aπ(a∣s)Aπ(s,a)=0\sum_a\pi(a\mid s)A^\pi(s,a)=0. An arbitrary reward modification can change advantages throughout the MDP.

Bellman equations and operators

Choose five correct statements.

  1. The Bellman expectation equation for a fixed policy is linear in VπV^\pi.

  2. The optimality operator replaces policy averaging over actions with a maximum.

  3. A Bellman equation is itself a complete neural-network training algorithm.

  4. For 0≤γ<10\le\gamma<1, TπT_\pi and T∗T_* are γ\gamma-contractions in the max norm.

  5. At γ=1\gamma=1, some proper episodic problems remain solvable, but the usual strict-contraction proof no longer applies.

  6. A small Bellman residual always implies an equally small value error, independently of γ\gamma.

  7. The fixed point of TπT_\pi is the value of the fixed policy.

Show answer and solution

Answers: 1, 2, 4, 5, and 7.

The fixed-policy operator TπV=rπ+γPπVT_\pi V=r_\pi+\gamma P_\pi V is affine (linear only when rπ=0r_\pi=0), while its fixed-point equation is a linear system in VV; the optimality operator takes a maximum; and for γ<1\gamma<1 both are contractions in ∥⋅∥∞\|\cdot\|_\infty. The fixed point of TπT_\pi is VπV^\pi.

Statement 3 is false because Bellman consistency does not specify data collection, parameterization, or optimization. Statement 5 records the important γ=1\gamma=1 boundary. Statement 6 is false because

∥V^−Vπ∥∞≤∥TπV^−V^∥∞1−γ.\|\widehat V-V^\pi\|_\infty \le \frac{\|T_\pi\widehat V-\widehat V\|_\infty}{1-\gamma}.

Monte Carlo, TD(0), and multi-step targets

Match each method to its target.

MethodTarget
A. Monte Carlo1. Rt+1+γV(St+1)R_{t+1}+\gamma V(S_{t+1})
B. TD(0)2. the complete observed GtG_t
C. nn-step target3. several observed rewards followed by V(St+n)V(S_{t+n})

Write A-?, B-?, C-?.

Then choose the most accurate statement:

  1. MC always has lower variance than TD.

  2. TD is always more accurate at every data budget.

  3. The trade-off depends on the environment, horizon, representation, step size, and data budget.

  4. Bootstrapping means resampling episodes with replacement.

Show answer and solution

Matching: A-2, B-1, C-3.
Additional answer: 3.

MC waits for an observed trajectory tail. TD(0) substitutes a current continuation estimate after one transition. An nn-step target observes several rewards before bootstrapping.

In RL, bootstrapping means updating an estimate from another current estimate. It is not the statistical bootstrap. Bias, variance, and update delay depend on the protocol, so there is no universal ranking.

Why Q-learning is off-policy

Actions are collected by an ε\varepsilon-greedy policy. Choose the single most accurate statement.

  1. SARSA and Q-learning are both off-policy because exploratory actions are random.

  2. SARSA uses the actually selected At+1A_{t+1}, whereas Q-learning uses max⁡a′Q(St+1,a′)\max_{a'}Q(S_{t+1},a'); Q-learning therefore evaluates a greedy target policy from ε\varepsilon-greedy behavior data.

  3. Q-learning becomes off-policy only when a replay buffer is added.

  4. SARSA cannot use ε\varepsilon-greedy exploration.

Show answer and solution

Answer: 2.

SARSA's target follows the next action of the behavior policy. Q-learning's target follows a greedy policy even when behavior remains exploratory. That mismatch defines the off-policy relationship; a replay buffer is neither necessary nor sufficient as a definition.

Group baselines and maximization bias

Choose four correct statements.

  1. The group mean reward in canonical GRPO is an exact token-level Vπ(st)V^\pi(s_t) for every prefix.

  2. Group normalization creates a relative signal without a learned critic, while remaining a sample statistic over complete responses.

  3. The maximum of noisy unbiased estimates can exhibit positive selection bias.

  4. Double Q-learning separates action selection from evaluation across two estimators.

  5. Double Q-learning is exactly unbiased under every error dependence structure.

  6. Selecting the highest-scoring LLM response under a noisy reward model shares a statistical mechanism with maximization bias, but is not literally tabular Q-learning.

Show answer and solution

Answers: 2, 3, 4, and 6.

Statement 6 is a limited analogy: the selection mechanism is shared, while the algorithms and objects are not identical.

Canonical GRPO uses response-group statistics rather than an exact token-level critic. Maximizing noisy estimates favors positive noise. Double Q-learning weakens that coupling by selecting with one estimator and evaluating with another, but it does not remove every source of error and may underestimate in some settings.

Compute V and A

At state ss,

Qπ(s,a1)=2,Qπ(s,a2)=6,Q^\pi(s,a_1)=2, \qquad Q^\pi(s,a_2)=6,

and

π(a1∣s)=0.25,π(a2∣s)=0.75.\pi(a_1\mid s)=0.25, \qquad \pi(a_2\mid s)=0.75.

Compute Vπ(s)V^\pi(s) and both advantages. Verify the policy-weighted zero-mean identity.

Show answer and solution
Vπ(s)=0.25⋅2+0.75⋅6=5.V^\pi(s)=0.25\cdot2+0.75\cdot6=5.

Hence

Aπ(s,a1)=−3,Aπ(s,a2)=1.A^\pi(s,a_1)=-3, \qquad A^\pi(s,a_2)=1.

The weighted check is

0.25(−3)+0.75(1)=0.0.25(-3)+0.75(1)=0.

The unweighted sum is −2-2; it is the policy-weighted average that must vanish.

One Bellman optimality backup

State ss has two actions.

  • a1a_1 deterministically reaches s1s_1 with reward 11, where V(s1)=3V(s_1)=3.
  • a2a_2 reaches s2s_2 with probability 0.50.5, reward 00, and V(s2)=6V(s_2)=6; or reaches s3s_3 with probability 0.50.5, reward 22, and V(s3)=1V(s_3)=1.

Let γ=0.9\gamma=0.9. Compute both one-step action values, (T∗V)(s)(T_*V)(s), and the greedy action.

Show answer and solution
Q(s,a1)=1+0.9⋅3=3.7.Q(s,a_1)=1+0.9\cdot3=3.7.

For a2a_2,

Q(s,a2)=0.5(0+0.9⋅6)+0.5(2+0.9⋅1)=2.7+1.45=4.15.\begin{aligned} Q(s,a_2) &=0.5(0+0.9\cdot6)+0.5(2+0.9\cdot1)\\ &=2.7+1.45=4.15. \end{aligned}

Therefore (T∗V)(s)=4.15(T_*V)(s)=4.15 and the greedy action is a2a_2.

A one-state Bellman system

There is one non-terminal state ss and one terminal state. Under a fixed policy, state ss

  • moves to the terminal with probability 0.50.5 and reward 00;
  • returns to ss with probability 0.50.5 and reward 11.

With γ=0.9\gamma=0.9, write and solve the Bellman equation for Vπ(s)V^\pi(s).

Show answer and solution
Vπ(s)=0.5⋅0+0.5(1+0.9Vπ(s)).V^\pi(s)=0.5\cdot0+0.5\bigl(1+0.9V^\pi(s)\bigr).

Thus

0.55Vπ(s)=0.5,Vπ(s)=1011≈0.90909.0.55V^\pi(s)=0.5, \qquad V^\pi(s)=\frac{10}{11}\approx0.90909.

A TD update with a terminal mask

Let

V(s)=2,V(s′)=3,r=1,γ=0.9,α=0.1.V(s)=2,\quad V(s')=3,\quad r=1,\quad \gamma=0.9,\quad \alpha=0.1.

Compute the new V(s)V(s) for

  1. an ordinary non-terminal transition;

  2. a true terminal transition;

  3. an external truncation while the process could continue.

Show answer and solution

The update is V(s)←V(s)+α(y−V(s))V(s)\leftarrow V(s)+\alpha(y-V(s)).

  1. y=1+0.9⋅3=3.7y=1+0.9\cdot3=3.7, so Vnew(s)=2.17V_{new}(s)=2.17.

  2. y=1y=1, so Vnew(s)=1.9V_{new}(s)=1.9.

  3. External truncation retains the continuation, so the result is again 2.172.17.

A three-step target

Observed rewards are [1,2,3][1,2,3], γ=0.5\gamma=0.5, and V(St+3)=4V(S_{t+3})=4. Compute

Gt:t+3=Rt+1+γRt+2+γ2Rt+3+γ3V(St+3).G_{t:t+3}=R_{t+1}+\gamma R_{t+2}+\gamma^2R_{t+3}+\gamma^3V(S_{t+3}).

What changes if the third transition truly terminates the task?

Show answer and solution

For a non-terminal end of the window,

1+0.5⋅2+0.52⋅3+0.53⋅4=3.25.1+0.5\cdot2+0.5^2\cdot3+0.5^3\cdot4=3.25.

At a true terminal, only the continuation is removed:

1+1+0.75=2.75.1+1+0.75=2.75.

Standard and Double-Q targets

At the next state,

QA(s′,⋅)=[1,4,3],QB(s′,⋅)=[10,2,8].Q^A(s',\cdot)=[1,4,3], \qquad Q^B(s',\cdot)=[10,2,8].

Let r=0.5r=0.5 and γ=0.9\gamma=0.9.

  1. Compute a standard Q-learning target using QAQ^A for both selection and evaluation.

  2. Compute a Double-Q target that selects with QAQ^A and evaluates with QBQ^B.

  3. Explain the difference.

Show answer and solution

Standard Q-learning uses max⁡aQA(s′,a)=4\max_aQ^A(s',a)=4:

yQ=0.5+0.9⋅4=4.1.y_Q=0.5+0.9\cdot4=4.1.

Double Q selects the second index with QAQ^A and evaluates that same index with QBQ^B:

yDouble=0.5+0.9⋅2=2.3.y_{Double}=0.5+0.9\cdot2=2.3.

The selector regards action 2 as best, while the independent evaluator assigns it a low value. Selection noise is therefore not automatically reused as evaluation noise.

Compute V from Q and form advantages

Implement two functions over arrays of shape (n_states, n_actions).

Python
import numpy as np


def v_from_q(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    """V(s) = sum_a policy[s,a] * q_values[s,a]."""
    raise NotImplementedError


def advantage(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    """A(s,a) = Q(s,a) - V(s)."""
    raise NotImplementedError

Use the public checks below to verify shapes and the policy-weighted zero mean; the reference module provides further examples.

Open checks:

Python
Q = np.array([[1.0, 3.0], [0.0, 2.0]])
pi = np.array([[0.5, 0.5], [0.2, 0.8]])
np.testing.assert_allclose(v_from_q(Q, pi), [2.0, 1.6])
np.testing.assert_allclose(advantage(Q, pi), [[-1.0, 1.0], [-1.6, 0.4]])
np.testing.assert_allclose((pi * advantage(Q, pi)).sum(axis=1), 0.0, atol=1e-12)
Show answer and solution
Python
import numpy as np


def v_from_q(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    q = np.asarray(q_values, dtype=float)
    pi = np.asarray(policy, dtype=float)
    if q.ndim != 2 or pi.shape != q.shape:
        raise ValueError("q_values and policy must have the same 2D shape")
    if np.any(pi < 0.0) or not np.allclose(pi.sum(axis=1), 1.0):
        raise ValueError("each policy row must be a probability distribution")
    return np.sum(pi * q, axis=1)


def advantage(q_values: np.ndarray, policy: np.ndarray) -> np.ndarray:
    q = np.asarray(q_values, dtype=float)
    return q - v_from_q(q, policy)[:, None]

The extra axis turns the state-value vector into a column, allowing one V(s)V(s) to be subtracted from every action in that state.

Exact policy evaluation

Solve the Bellman linear system over non-terminal states.

Python
def exact_policy_value(
    transitions: np.ndarray,
    rewards: np.ndarray,
    policy: np.ndarray,
    gamma: float,
    terminals: np.ndarray,
) -> np.ndarray:
    """Return zero for terminal-state values."""
    raise NotImplementedError

Construct PπP_\pi and rπr_\pi, then solve (I−γPNN)VN=rN(I-\gamma P_{NN})V_N=r_N on the non-terminal index set NN.

Open check:

Python
P = np.zeros((2, 1, 2))
R = np.zeros_like(P)
P[0, 0] = [0.5, 0.5]
R[0, 0] = [1.0, 0.0]
P[1, 0, 1] = 1.0
pi = np.ones((2, 1))
terminal = np.array([False, True])
np.testing.assert_allclose(
    exact_policy_value(P, R, pi, 0.9, terminal),
    [10.0 / 11.0, 0.0],
)
Show answer and solution
Python
import numpy as np


def exact_policy_value(transitions, rewards, policy, gamma, terminals):
    p = np.asarray(transitions, dtype=float)
    r = np.asarray(rewards, dtype=float)
    pi = np.asarray(policy, dtype=float)
    terminal = np.asarray(terminals, dtype=bool)

    if p.ndim != 3 or r.shape != p.shape:
        raise ValueError("P and R must have shape (nS, nA, nS)")
    if pi.shape != p.shape[:2] or terminal.shape != (p.shape[0],):
        raise ValueError("policy or terminal mask has the wrong shape")

    p_pi = np.einsum("sa,san->sn", pi, p)
    r_pi = np.einsum("sa,san,san->s", pi, p, r)
    nonterminal = np.flatnonzero(~terminal)
    values = np.zeros(p.shape[0], dtype=float)
    if nonterminal.size:
        p_nn = p_pi[np.ix_(nonterminal, nonterminal)]
        values[nonterminal] = np.linalg.solve(
            np.eye(nonterminal.size) - gamma * p_nn,
            r_pi[nonterminal],
        )
    return values

Reward on entry to a terminal is already contained in the predecessor's reward equation. Only continuation after the terminal is zero.

One Bellman optimality sweep

Implement one synchronous value-iteration backup.

Python
def bellman_optimality_sweep(
    transitions: np.ndarray,
    rewards: np.ndarray,
    values: np.ndarray,
    gamma: float,
    terminals: np.ndarray,
) -> np.ndarray:
    """Return T_* V with zero terminal components."""
    raise NotImplementedError

Every new state value must be computed from the same old vector values.

Open check for one synchronous sweep:

Python
P = np.zeros((3, 2, 3))
R = np.zeros_like(P)
P[0, 0, 1] = 1.0
P[0, 1, 2] = 1.0
R[0, 0, 1] = 1.0
R[0, 1, 2] = 2.0
P[1, :, 1] = 1.0
P[2, :, 2] = 1.0
V = np.array([0.0, 3.0, 1.0])
out = bellman_optimality_sweep(P, R, V, 0.9, np.zeros(3, dtype=bool))
assert abs(out[0] - 3.7) < 1e-12
Show answer and solution
Python
import numpy as np


def bellman_optimality_sweep(transitions, rewards, values, gamma, terminals):
    p = np.asarray(transitions, dtype=float)
    r = np.asarray(rewards, dtype=float)
    v = np.asarray(values, dtype=float)
    terminal = np.asarray(terminals, dtype=bool)

    continuation = v.copy()
    continuation[terminal] = 0.0
    q = np.sum(
        p * (r + gamma * continuation[None, None, :]),
        axis=2,
    )
    updated = np.max(q, axis=1)
    updated[terminal] = 0.0
    return updated

Repeated application with a stopping rule gives value iteration. The notebook implements that complete experiment and stores the full propagation trace.

TD targets with explicit termination

Implement one-step and nn-step targets. An external truncated=True flag must not mask the continuation by itself.

Python
def td_target(reward, next_value, gamma, *, terminated, truncated=False):
    raise NotImplementedError


def n_step_target(rewards, bootstrap_value, gamma, *, terminated_at_end):
    raise NotImplementedError

The numerical checks are 2.82.8, 1.01.0, 2.82.8 for ordinary/terminal/truncated one-step transitions and 3.253.25, 2.752.75 for the two three-step cases in Task B5.

Open checks:

Python
assert td_target(1, 2, 0.9, terminated=False) == 2.8
assert td_target(1, 2, 0.9, terminated=True) == 1.0
assert td_target(1, 2, 0.9, terminated=False, truncated=True) == 2.8
assert abs(n_step_target([1, 2, 3], 4, 0.5, terminated_at_end=False) - 3.25) < 1e-12
assert abs(n_step_target([1, 2, 3], 4, 0.5, terminated_at_end=True) - 2.75) < 1e-12
Show answer and solution
Python
import math


def td_target(reward, next_value, gamma, *, terminated, truncated=False):
    if not math.isfinite(reward) or not math.isfinite(gamma):
        raise ValueError("reward and gamma must be finite")
    if not 0.0 <= gamma <= 1.0:
        raise ValueError("gamma must be in [0, 1]")
    if not isinstance(terminated, bool) or not isinstance(truncated, bool):
        raise TypeError("flags must be boolean")
    if terminated:
        return float(reward)
    if not math.isfinite(next_value):
        raise ValueError("next_value must be finite for a non-terminal target")
    return float(reward + gamma * next_value)


def n_step_target(rewards, bootstrap_value, gamma, *, terminated_at_end):
    if not math.isfinite(gamma) or not 0.0 <= gamma <= 1.0:
        raise ValueError("gamma must be finite and in [0, 1]")
    if not isinstance(terminated_at_end, bool):
        raise TypeError("terminated_at_end must be boolean")
    total = 0.0
    discount = 1.0
    for reward in rewards:
        reward = float(reward)
        if not math.isfinite(reward):
            raise ValueError("rewards must be finite")
        total += discount * reward
        discount *= gamma
    if terminated_at_end:
        return total
    if not math.isfinite(bootstrap_value):
        raise ValueError("bootstrap_value must be finite for a non-terminal target")
    return total + discount * float(bootstrap_value)

truncated is retained in the interface to make the stopping reason visible. The bootstrap mask is controlled by task termination.

SARSA and Q-learning targets

Implement the local targets for a known next-state action-value vector.

Python
def q_learning_target(reward, next_q_values, gamma, *, terminated):
    raise NotImplementedError


def sarsa_target(reward, next_q_values, next_action, gamma, *, terminated):
    raise NotImplementedError

Q-learning uses a maximum; SARSA uses the action actually selected by the behavior policy. Neither should bootstrap at a true terminal.

Open checks:

Python
q_next = np.array([1.0, 4.0, 3.0])
assert q_learning_target(0.5, q_next, 0.9, terminated=False) == 4.1
assert sarsa_target(0.5, q_next, 2, 0.9, terminated=False) == 3.2
assert q_learning_target(1.0, np.array([]), 0.9, terminated=True) == 1.0
assert sarsa_target(1.0, np.array([]), 99, 0.9, terminated=True) == 1.0
Show answer and solution
Python
import numpy as np


def q_learning_target(reward, next_q_values, gamma, *, terminated):
    if terminated:
        return float(reward)
    q_next = np.asarray(next_q_values, dtype=float)
    if q_next.ndim != 1 or q_next.size == 0:
        raise ValueError("next_q_values must be a non-empty vector")
    return float(reward + gamma * np.max(q_next))


def sarsa_target(reward, next_q_values, next_action, gamma, *, terminated):
    if terminated:
        return float(reward)
    q_next = np.asarray(next_q_values, dtype=float)
    if q_next.ndim != 1 or not 0 <= next_action < q_next.size:
        raise ValueError("invalid next action")
    return float(reward + gamma * q_next[next_action])

The full SARSA loop must choose next_action using the same behavior policy that will act on the next transition.

Selection and evaluation in Double Q-learning

Implement a Double-Q target. One estimator selects the action index; the other evaluates that selected index.

Python
def double_q_target(
    reward: float,
    selector_q: np.ndarray,
    evaluator_q: np.ndarray,
    gamma: float,
    *,
    terminated: bool,
) -> float:
    raise NotImplementedError

For selector=[1,4,3], evaluator=[10,2,8], r=0.5r=0.5, and γ=0.9\gamma=0.9, the non-terminal result must be 2.32.3.

Open checks:

Python
qa = np.array([1.0, 4.0, 3.0])
qb = np.array([10.0, 2.0, 8.0])
assert double_q_target(0.5, qa, qb, 0.9, terminated=False) == 2.3
assert double_q_target(0.5, qa, qb, 0.9, terminated=True) == 0.5
Show answer and solution
Python
import numpy as np


def double_q_target(reward, selector_q, evaluator_q, gamma, *, terminated):
    if terminated:
        return float(reward)
    selector = np.asarray(selector_q, dtype=float)
    evaluator = np.asarray(evaluator_q, dtype=float)
    if selector.ndim != 1 or evaluator.shape != selector.shape or selector.size == 0:
        raise ValueError("Q vectors must be non-empty and have the same shape")
    selected = int(np.argmax(selector))
    return float(reward + gamma * evaluator[selected])

Taking np.max(evaluator) would let the second table choose a different action. Double Q-learning evaluates the action selected by the first table.