Dynamic Programming in RL
Dynamic programming computes optimal policies by breaking sequential decisions into overlapping subproblems and bootstrapping value estimates using a complete mathematical model of the environment.
Why Does This Exist?
In sequential decision-making, an agent aims to choose actions that maximize cumulative rewards over an extended or infinite horizon. A naive search over all possible future action sequences explodes exponentially with time: an agent planning steps ahead with available actions must evaluate distinct trajectories. For a modest horizon of with actions, this requires comparing over distinct paths—an intractable combinatorial explosion.
Richard Bellman resolved this dilemma through the Principle of Optimality: an optimal policy has the property that whatever the initial state and initial decision are, the remaining decisions must constitute an optimal policy with regard to the state resulting from the first decision. Because sequential decisions satisfy recursive subproblem overlap, the global optimization problem can be decomposed into local, one-step subproblems.
Dynamic Programming (DP) provides the collection of algorithms that solve Markov Decision Processes by turning Bellman equations into iterative assignment updates. Unlike model-free reinforcement learning methods—such as Temporal Difference Learning or Monte Carlo Methods—which must sample transitions through trial-and-error interaction, DP operates as a planning framework. It assumes a perfect mathematical model of the environment's physics and reward mechanics, executing exhaustive expectations across all possible outcomes. This eliminates sample variance entirely and forms the theoretical backbone for Value Functions and policy optimization.
Think of It Like This
GPS route recalculation with a preloaded city blueprint
Imagine an advanced satellite navigation computer preloaded with a centimeter-accurate offline blueprint of every street, intersection, turn restriction, and statistical traffic delay in a metropolis.
You are sitting at an intersection () wanting the fastest route to the central railway station. The GPS does not need to dispatch physical test vehicles into random alleys to find out where they lead—an approach analogous to trial-and-error model-free exploration. Because it holds the complete map graph and travel cost tables ( and ), it computes the optimal journey offline.
If the GPS already knows the estimated travel time from every adjacent intersection () to the destination, recalculating the optimal decision at intersection requires looking only one step ahead:
- Examine each available street you could take ().
- Factor in the expected delay on that specific street segment ().
- Add the precomputed travel duration from the destination intersection onward ().
- Select the turn with the minimum total expected time.
By caching and iteratively updating the travel times of neighboring intersections, the system solves the global navigation problem without simulating endless full routes.
Where the analogy stops: Standard road navigation maps are largely deterministic—turning left onto Main Street lands you on Main Street with certainty. In reinforcement learning MDPs, DP explicitly computes expectations over stochastic environments where turning onto an icy street might skid you into several alternative intersections with known probabilities ().
How It Actually Works
Bellman Backup Operators and Mathematical Formulation
Dynamic programming requires complete access to the four-tuple transition dynamics of a finite Markov Decision Process:
- : The set of all valid states.
- : The set of all possible actions.
- : The transition probability kernel.
- : The immediate scalar reward received after transitioning from to under action .
- : The discount factor ensuring convergence over infinite horizons.
The expected reward for taking action in state is formalized as:
The Bootstrap Principle
Dynamic programming updates its value estimates based on other value estimates. Rather than waiting until the end of an episode to compute empirical returns (as in Monte Carlo rollouts), DP performs bootstrapping: it truncates the horizon after a single step and substitutes the current cached value estimate for the remainder of the trajectory.
Bellman Expectation Backup Operator ()
For a fixed policy , the value function satisfies the linear system known as the Bellman expectation equation. In DP, this equation is turned into an operational update operator :
Iterative policy evaluation repeatedly applies this operator: .
Bellman Optimality Backup Operator ()
To identify the optimal policy , the Bellman optimality backup operator performs a greedy maximization over all available actions:
Equivalently, in terms of state-action values :
Contraction Mapping and Guaranteed Convergence
A critical property of the dynamic programming operators is that both and are -contraction mappings in the supremum norm ():
Because , Banach's Fixed-Point Theorem guarantees that:
- A unique optimal fixed point exists such that .
- Repeated application converges exponentially to from any arbitrary initial vector .
Dynamic programming organizes these backups into two canonical algorithmic paradigms:
- Policy Iteration: Alternates between exhaustive policy evaluation (solving for ) and greedy policy improvement ().
- Value Iteration: Applies the optimality operator directly across all states in each sweep, interleaving policy evaluation and improvement into a single step.
Worked numerical example
Consider an agent in target state with discount factor . The agent has two candidate actions: and .
The environment dynamics model defines the following branches:
- Action (stochastic with two successor states and ):
- Transition to : probability , immediate reward , cached estimate .
- Transition to : probability , immediate reward , cached estimate .
- Action (deterministic transition to successor state ):
- Transition to : probability , immediate reward , cached estimate .
Step 1: Compute discounted branch targets for action
For each successor state , compute the one-step target :
- Target for branch :
- Target for branch :
Step 2: Compute the expectation for action (Action-Value )
Apply the probability-weighted Bellman expectation across both branches:
Step 3: Compute the expectation for action (Action-Value )
Compute the one-step target for the deterministic branch:
Step 4: Apply the Bellman Optimality Backup Operator
The Bellman optimality operator selects the maximum value among all actions:
The derived greedy policy for state selects:
The value of state has been updated from its prior value to purely by backing up expectations from cached successor state estimates and the known transition model.
Code
from dataclasses import dataclassfrom typing import Dict, List, Tuple
@dataclass(frozen=True)class Transition: """Represents a stochastic branch in the environment dynamics model.""" prob: float # P(s' | s, a) next_state: str # Successor state s' reward: float # Immediate reward R(s, a, s')
class ModelMDP: """Tabular Markov Decision Process solved via Dynamic Programming backups."""
def __init__( self, states: List[str], actions: List[str], transitions: Dict[Tuple[str, str], List[Transition]], gamma: float = 0.9, ) -> None: self.states = states self.actions = actions self.transitions = transitions self.gamma = gamma
def compute_q_value( self, state: str, action: str, value_function: Dict[str, float] ) -> float: """ Computes Q(s, a) using the full Bellman expectation backup: Q(s, a) = sum_{s'} P(s'|s,a) * [ R(s,a,s') + gamma * V(s') ] """ branches = self.transitions.get((state, action), []) if not branches: return 0.0
q_val = 0.0 for branch in branches: discounted_future = self.gamma * value_function[branch.next_state] q_val += branch.prob * (branch.reward + discounted_future) return q_val
def bellman_optimality_backup( self, state: str, value_function: Dict[str, float] ) -> Tuple[float, str]: """ Executes a 1-step Bellman Optimality Backup for a single state: V(s) <- max_a Q(s, a) """ action_values = { action: self.compute_q_value(state, action, value_function) for action in self.actions } best_action = max(action_values, key=lambda a: action_values[a]) best_value = action_values[best_action] return best_value, best_action
def full_sweep_backup( self, value_function: Dict[str, float] ) -> Tuple[Dict[str, float], Dict[str, str]]: """Performs one synchronous dynamic programming sweep across all states.""" new_v: Dict[str, float] = {} greedy_policy: Dict[str, str] = {}
for s in self.states: v_val, best_act = self.bellman_optimality_backup(s, value_function) new_v[s] = round(v_val, 4) greedy_policy[s] = best_act
return new_v, greedy_policy
if __name__ == "__main__": # Define states and actions matching the worked numerical example states = ["s", "s'_1", "s'_2", "s'_3"] actions = ["action_a", "action_b"]
transitions = { ("s", "action_a"): [ Transition(prob=0.8, next_state="s'_1", reward=2.0), Transition(prob=0.2, next_state="s'_2", reward=5.0), ], ("s", "action_b"): [ Transition(prob=1.0, next_state="s'_3", reward=1.0), ], # Terminal successor states with zero rewards ("s'_1", "action_a"): [Transition(prob=1.0, next_state="s'_1", reward=0.0)], ("s'_1", "action_b"): [Transition(prob=1.0, next_state="s'_1", reward=0.0)], ("s'_2", "action_a"): [Transition(prob=1.0, next_state="s'_2", reward=0.0)], ("s'_2", "action_b"): [Transition(prob=1.0, next_state="s'_2", reward=0.0)], ("s'_3", "action_a"): [Transition(prob=1.0, next_state="s'_3", reward=0.0)], ("s'_3", "action_b"): [Transition(prob=1.0, next_state="s'_3", reward=0.0)], }
# Initial cached state-value estimates V_k v_k = { "s": 0.0, "s'_1": 10.0, "s'_2": 4.0, "s'_3": 9.0, }
env = ModelMDP(states, actions, transitions, gamma=0.9)
print("--- 1-Step DP Bellman Backup for State 's' ---") for act in actions: q = env.compute_q_value("s", act, v_k) print(f"Q(s, {act}) = {q:.4f}")
new_v_s, best_action = env.bellman_optimality_backup("s", v_k) print(f"\nUpdated Value V_{{k+1}}(s) = {new_v_s:.4f}") print(f"Greedy Policy pi_{{k+1}}(s) = '{best_action}'")
# Perform a full-sweep backup across the entire state space updated_v, greedy_policy = env.full_sweep_backup(v_k) print("\nFull-sweep updated values:", updated_v) print("Derived greedy policy: ", greedy_policy)
# Expected Output: # --- 1-Step DP Bellman Backup for State 's' --- # Q(s, action_a) = 10.5200 # Q(s, action_b) = 9.1000 # # Updated Value V_{k+1}(s) = 10.5200 # Greedy Policy pi_{k+1}(s) = 'action_a' # # Full-sweep updated values: {'s': 10.52, "s'_1": 9.0, "s'_2": 3.6, "s'_3": 8.1} # Derived greedy policy: {'s': 'action_a', "s'_1": 'action_a', "s'_2": 'action_a', "s'_3": 'action_a'}Watch Out For
The fatal dependency on a perfect environment model
Dynamic programming is fundamentally a planning algorithm, not a learning algorithm. Its Bellman backup equations require computing the exact mathematical expectation . If transition probabilities or reward dynamics are unknown, noisy, or non-stationary, pure DP cannot execute a single step.
In practical real-world problems like robotics or stock trading, the environment dynamics cannot be written down in closed form. Practitioners attempting to apply DP in model-free settings face total failure.
The Fix: When the environment is accessible only via interaction samples , use model-free reinforcement learning methods such as Temporal Difference Learning (Q-learning, SARSA) or Monte Carlo Methods. Alternatively, apply Model-Based RL to learn an empirical transition model before executing DP planning backups.
The curse of dimensionality and state space explosion
A synchronous DP sweep iterates over every state and evaluates every action across all possible transitions . For dense transition models, the computational complexity per sweep is .
While polynomial in the number of states, grows exponentially with the number of state variables—a barrier Bellman termed the "curse of dimensionality." A robotic arm with 10 continuous joint angles or a game with 30 discrete state attributes has an astronomical state space that exhausts memory and renders tabular DP sweeps impossible.
The Fix: Transition from tabular dynamic programming to Approximate Dynamic Programming (ADP). Replace tabular state arrays with parametric function approximation (such as deep neural networks in Deep Q-Networks and Actor-Critic architectures), or employ asynchronous updates and sample-based tree search (such as Monte Carlo Tree Search) to focus Bellman backups only on trajectories visited by the agent.
The Quick Version
- Planning with a perfect model: Dynamic programming solves MDPs by computing exact expectations over fully known transition distributions and reward functions , eliminating sample variance.
- Bootstrapping via contraction mappings: It avoids exponential trajectory rollouts by recursively updating current state values using cached values of successor states via contraction operators ( and ), guaranteeing unique convergence.
- Exhaustive backup tree: Unlike sample-based RL which samples single transitions , DP performs full tree backups across every action and every stochastic successor state.
- Foundation of modern RL: While tabular DP suffers from the curse of dimensionality in large state spaces, its Bellman backup formulations serve as the mathematical bedrock for policy iteration, value iteration, and deep reinforcement learning.