Skip to content
AI360Xpert
Beta

MAXQ Value Function Decomposition

Instead of solving a massive problem all at once, MAXQ breaks it into a tree of smaller jobs that only look at the details they need to finish.

MAXQ task graph hierarchy decomposing value functions into recursive child values and parent completion functions with state abstraction.
MAXQ task graph hierarchy decomposing value functions into recursive child values and parent completion functions with state abstraction.

Why Does This Exist?

Flat reinforcement learning algorithms scale poorly in long-horizon problems with complex subgoal structures. When an agent must navigate across rooms, pick up objects, unlock doors, and deliver cargo, choosing from raw primitive motor commands at every single time step leads to exponential sample complexity and sluggish exploration.

While the Options framework (Sutton et al., 1999) introduced temporally extended actions, options treat subpolicies as black boxes without breaking down the value function itself. An option still evaluates values across the full global state space, offering no built-in mechanism to ignore irrelevant state dimensions.

MAXQ Value Function Decomposition (Dietterich, 2000) solves this limitation by decomposing the core Markov Decision Process into a directed acyclic graph of subtasks. Crucially, MAXQ splits the value function itself into:

  1. Child Execution Values V(a,s)V(a, s): The reward earned while executing an immediate child subtask.
  2. Completion Functions C(i,s,a)C(i, s, a): The expected future reward of completing the rest of the parent task after the child subtask terminates.

By explicitly formalizing completion functions and state abstraction, MAXQ enables subtasks to ignore irrelevant state variables, dramatically compressing the state space and facilitating modular subtask reuse across different parent routines.

Think of It Like This

A modular automotive assembly line in a car factory

Imagine a modern manufacturing plant tasked with building an automobile from raw parts (BuildCar).

  • Flat RL: A single robot arm stands in the center of the warehouse. At every millisecond, it must choose which single bolt to turn among 30,000 components, tracking the temperature of the engine, the tire pressure, and the stitch count of the leather seats simultaneously. Learning this way would take centuries.
  • The MAXQ Approach: The plant manager organizes production into a hierarchical task graph:
    • Root task BuildCar delegates work to three specialized workstations: AssembleEngine, MountChassis, and InstallInterior.
    • The technician inside InstallInterior focuses entirely on their localized subtask.
    • State Abstraction: The interior technician only needs to know the seat anchor positions and upholstery color. They have zero need to know the engine cylinder displacement, camshaft timing, or brake fluid level. Those variables are abstracted away.
    • Subtask Termination: The interior subtask terminates as soon as the seats and dashboard are bolted down.
    • The Completion Function: Once the interior workstation finishes, the plant manager evaluates what remains to complete BuildCar (InstallElectronics and PaintJob).

Where the analogy stops: A car factory runs a fixed, pre-scheduled assembly line. In reinforcement learning, the environment is stochastic and dynamic. The agent must dynamically decide which subtask to invoke based on real-time sensor observations (ss) and recursively evaluate completion functions (C(i,s,a)C(i, s, a)) to choose optimal subtask dispatching.

How It Actually Works

MAXQ Task Graph and Recursive Value Decomposition

The overall MDP is decomposed into a hierarchy of subtasks {M0,M1,…,MK}\{M_0, M_1, \dots, M_K\}, where M0M_0 represents the root task. The hierarchy forms a Directed Acyclic Graph (DAG) called the MAXQ Task Graph.

1. Formal Subtask Definition

Each subtask node MiM_i is defined by a tuple Mi=⟨Ti,Ai,R~i⟩M_i = \langle T_i, A_i, \tilde{R}_i \rangle:

  • Termination Predicate Ti(s)T_i(s): A boolean condition that determines whether subtask MiM_i has completed (e.g., TNav(s)=TrueT_{\text{Nav}}(s) = \text{True} when taxi coordinates match destination coordinates).
  • Child Actions AiA_i: The set of actions that MiM_i can invoke. Children can be other subtasks or primitive actions.
  • Pseudo-Reward Function R~i(s)\tilde{R}_i(s): An optional intrinsic reward function used to shape subtask policies toward desirable terminal subgoals.

2. Value Function Decomposition

Let Qπ(i,s,a)Q^\pi(i, s, a) be the expected cumulative discounted reward of executing child action a∈Aia \in A_i from state ss within parent subtask MiM_i.

MAXQ decomposes this value function recursively:

Qπ(i,s,a)=Vπ(a,s)+Cπ(i,s,a)Q^\pi(i, s, a) = V^\pi(a, s) + C^\pi(i, s, a)

where:

  • Vπ(a,s)V^\pi(a, s) (Child Value): The expected cumulative reward earned during the execution of child action aa until aa terminates: Vπ(a,s)={∑s′P(s′∣s,a)R(s′∣s,a)if a is primitiveQπ(a,s,πa(s))if a is a subtaskV^\pi(a, s) = \begin{cases} \sum_{s'} P(s' \mid s, a) R(s' \mid s, a) & \text{if } a \text{ is primitive} \\ Q^\pi(a, s, \pi_a(s)) & \text{if } a \text{ is a subtask} \end{cases}
  • Cπ(i,s,a)C^\pi(i, s, a) (Completion Function): The expected cumulative discounted reward of completing the rest of parent subtask MiM_i after child action aa terminates: Cπ(i,s,a)=∑s′,NP(s′,N∣s,a) γNQπ(i,s′,πi(s′))C^\pi(i, s, a) = \sum_{s', N} P(s', N \mid s, a) \, \gamma^N Q^\pi(i, s', \pi_i(s')) where P(s′,N∣s,a)P(s', N \mid s, a) is the probability that child subtask aa terminates in state s′s' after NN environment steps.

3. Recursive Policy Execution

At any subtask level MiM_i, the agent selects the child action that maximizes the sum of child value and parent completion value:

πi(s)=arg⁡max⁡a∈Ai[V(a,s)+C(i,s,a)]\pi_i(s) = \arg\max_{a \in A_i} \Big[ V(a, s) + C(i, s, a) \Big]

Execution recurses down the task graph until a primitive motor action is dispatched to the environment.

4. State Abstraction

Each subtask MiM_i is equipped with an abstraction mapping ϕi(s)\phi_i(s) that filters out state dimensions irrelevant to MiM_i. For example, a navigation subtask MNavM_{\text{Nav}} only requires the current (x,y)(x, y) coordinates and target coordinates (xtarget,ytarget)(x_{\text{target}}, y_{\text{target}}), completely ignoring passenger status or final delivery destination. This shields lower-level subtasks from exponential state explosion.

Worked numerical example

Consider the classic Taxi environment where a taxi must pick up a passenger and drop them off at a destination.

Setup:

  • Current state: s=⟨taxi_pos=(1,1), pass_loc=(1,3), dest=(4,4)⟩s = \langle \text{taxi\_pos}=(1, 1), \, \text{pass\_loc}=(1, 3), \, \text{dest}=(4, 4) \rangle.
  • Step cost: r=−1.0r = -1.0 per move. Illegal action penalty: r=−10.0r = -10.0.
  • Parent subtask: MGetM_{\text{Get}} (pickup passenger).
  • Candidate child actions for MGetM_{\text{Get}}:
    1. Subtask MNav(s,pass_loc)M_{\text{Nav}}(s, \text{pass\_loc}): Navigate to passenger.
    2. Primitive action Pickup: Attempt pickup at current location.

Step 1: Evaluate Child Action 1 (MNavM_{\text{Nav}})

  • Child Value V(MNav,s)V(M_{\text{Nav}}, s): Taxi is at (1,1)(1, 1) and passenger is at (1,3)(1, 3). Manhattan distance is ∣1−1∣+∣3−1∣=2|1 - 1| + |3 - 1| = 2 steps North. V(MNav,s)=2×(−1.0)=−2.0V(M_{\text{Nav}}, s) = 2 \times (-1.0) = -2.0
  • Completion Function C(MGet,s,MNav)C(M_{\text{Get}}, s, M_{\text{Nav}}): Once MNavM_{\text{Nav}} completes, the taxi arrives at (1,3)(1, 3). To terminate parent subtask MGetM_{\text{Get}} (passenger inside taxi), the agent must execute 1 legal Pickup step: C(MGet,s,MNav)=1×(−1.0)=−1.0C(M_{\text{Get}}, s, M_{\text{Nav}}) = 1 \times (-1.0) = -1.0
  • Total Q-value: Q(MGet,s,MNav)=V(MNav,s)+C(MGet,s,MNav)=−2.0+(−1.0)=−3.0Q(M_{\text{Get}}, s, M_{\text{Nav}}) = V(M_{\text{Nav}}, s) + C(M_{\text{Get}}, s, M_{\text{Nav}}) = -2.0 + (-1.0) = -3.0

Step 2: Evaluate Child Action 2 (Immediate Pickup)

  • Child Value V(Pickup,s)V(\text{Pickup}, s): Taxi attempts to pick up at (1,1)(1, 1) where no passenger is present. The environment incurs an illegal action penalty: V(Pickup,s)=−10.0V(\text{Pickup}, s) = -10.0
  • Completion Function C(MGet,s,Pickup)C(M_{\text{Get}}, s, \text{Pickup}): Because the illegal pickup failed, the passenger remains at (1,3)(1, 3). Completing MGetM_{\text{Get}} still requires navigating 2 steps and executing 1 legal pickup: C(MGet,s,Pickup)=2(−1.0)+1(−1.0)=−3.0C(M_{\text{Get}}, s, \text{Pickup}) = 2(-1.0) + 1(-1.0) = -3.0
  • Total Q-value: Q(MGet,s,Pickup)=V(Pickup,s)+C(MGet,s,Pickup)=−10.0+(−3.0)=−13.0Q(M_{\text{Get}}, s, \text{Pickup}) = V(\text{Pickup}, s) + C(M_{\text{Get}}, s, \text{Pickup}) = -10.0 + (-3.0) = -13.0

Step 3: Action Selection

Evaluating the decision rule:

πGet(s)=arg⁡max⁡(Q(MGet,s,MNav), Q(MGet,s,Pickup))=arg⁡max⁡(−3.0,−13.0)=MNav\pi_{\text{Get}}(s) = \arg\max \Big( Q(M_{\text{Get}}, s, M_{\text{Nav}}), \, Q(M_{\text{Get}}, s, \text{Pickup}) \Big) = \arg\max(-3.0, -13.0) = M_{\text{Nav}}

The hierarchical policy selects MNavM_{\text{Nav}}, correctly prioritizing navigation over an illegal premature pickup.

Code

from dataclasses import dataclassfrom typing import List, Tuple

@dataclassclass SubtaskOption:    """Represents a candidate child subtask or primitive action."""
    name: str    is_primitive: bool    child_value_v: float    completion_value_c: float
    @property    def total_q_value(self) -> float:        return self.child_value_v + self.completion_value_c

class MAXQTaskGraphEvaluator:    """Demonstrates MAXQ Value Function Decomposition (Dietterich 2000):
    Q(i, s, a) = V(a, s) + C(i, s, a)    """
    def __init__(        self, step_cost: float = -1.0, illegal_penalty: float = -10.0    ) -> None:        self.step_cost = step_cost        self.illegal_penalty = illegal_penalty
    def compute_nav_child_value(        self,        current_pos: Tuple[int, int],        target_pos: Tuple[int, int],    ) -> float:        """Computes V(M_Nav, s) using Manhattan distance step costs."""        distance = abs(current_pos[0] - target_pos[0]) + abs(            current_pos[1] - target_pos[1]        )        return distance * self.step_cost
    def compute_pickup_child_value(        self,        taxi_pos: Tuple[int, int],        pass_pos: Tuple[int, int],    ) -> float:        """Computes V(Pickup, s): legal pickup cost or illegal penalty."""        if taxi_pos == pass_pos:            return self.step_cost        return self.illegal_penalty
    def evaluate_m_get_subtask(        self,        taxi_pos: Tuple[int, int],        pass_pos: Tuple[int, int],    ) -> List[SubtaskOption]:        """Evaluates child choices under parent subtask M_Get."""        # 1. Option: Call Navigation child M_Nav        v_nav = self.compute_nav_child_value(taxi_pos, pass_pos)        # Completing M_Get after arriving requires 1 pickup step        c_nav = self.step_cost        opt_nav = SubtaskOption(            name="M_Nav",            is_primitive=False,            child_value_v=v_nav,            completion_value_c=c_nav,        )
        # 2. Option: Call primitive Pickup immediately        v_pickup = self.compute_pickup_child_value(taxi_pos, pass_pos)        # Completing M_Get after illegal pickup requires 2 nav steps + 1 legal pickup        dist = abs(taxi_pos[0] - pass_pos[0]) + abs(taxi_pos[1] - pass_pos[1])        c_pickup = (dist + 1) * self.step_cost        opt_pickup = SubtaskOption(            name="Pickup",            is_primitive=True,            child_value_v=v_pickup,            completion_value_c=c_pickup,        )
        return [opt_nav, opt_pickup]
    @staticmethod    def select_best_subtask(options: List[SubtaskOption]) -> SubtaskOption:        """Selects argmax_{a} [ V(a, s) + C(i, s, a) ]."""        return max(options, key=lambda opt: opt.total_q_value)

# Test scenario matching the worked numerical exampleevaluator = MAXQTaskGraphEvaluator(step_cost=-1.0, illegal_penalty=-10.0)taxi_start = (1, 1)passenger_location = (1, 3)
options = evaluator.evaluate_m_get_subtask(taxi_start, passenger_location)
for opt in options:    print(        f"Child: {opt.name:<6} | V(a, s): {opt.child_value_v:5.1f} | "        f"C(i, s, a): {opt.completion_value_c:5.1f} | Q(i, s, a): {opt.total_q_value:5.1f}"    )# -> Child: M_Nav  | V(a, s):  -2.0 | C(i, s, a):  -1.0 | Q(i, s, a):  -3.0# -> Child: Pickup | V(a, s): -10.0 | C(i, s, a):  -3.0 | Q(i, s, a): -13.0
best_choice = evaluator.select_best_subtask(options)print(    f"Selected Subtask: {best_choice.name} (Q = {best_choice.total_q_value:.1f})")# -> Selected Subtask: M_Nav (Q = -3.0)
# Verification assertionsassert options[0].name == "M_Nav"assert options[0].child_value_v == -2.0assert options[0].completion_value_c == -1.0assert options[0].total_q_value == -3.0
assert options[1].name == "Pickup"assert options[1].child_value_v == -10.0assert options[1].completion_value_c == -3.0assert options[1].total_q_value == -13.0
assert best_choice.name == "M_Nav"

Watch Out For

Recursive Optimality vs. Global Optimality in Subtask Hierarchies

A fundamental theoretical limitation of the MAXQ framework is that it guarantees recursive optimality, not global optimality.

In recursive optimality, each subtask policy πi\pi_i is optimal given the fixed policies of its child subtasks. However, a child subtask is blind to the future goals of sibling or parent tasks.

  • Example: In a navigation subtask MNavM_{\text{Nav}}, the agent may have two doors that both lead out of a room in 3 steps. The child subtask considers them equally optimal and arbitrarily picks Door 1. However, Door 2 would have placed the agent directly adjacent to the passenger for the next subtask, saving 10 steps overall. Because MNavM_{\text{Nav}} terminates locally, it produces a globally sub-optimal trajectory.

The Fix:

  1. Pseudo-Rewards R~i(s)\tilde{R}_i(s): Carefully design intrinsic terminal pseudo-rewards that reward subtasks for terminating in specific states that maximize the parent completion function C(i,s,a)C(i, s, a).
  2. Hierarchical Fine-Tuning: After pre-training modular subtasks under MAXQ decomposition, unfreeze the hierarchy and allow joint end-to-end fine-tuning across boundary states.

The Quick Version

  • MAXQ decomposes complex Markov Decision Processes into a hierarchical Directed Acyclic Graph (DAG) of reusable subtasks with localized termination predicates Ti(s)T_i(s).
  • The core value decomposition splits action-values into child execution value V(a,s)V(a, s) and parent completion value C(i,s,a)C(i, s, a): Q(i,s,a)=V(a,s)+C(i,s,a)Q(i, s, a) = V(a, s) + C(i, s, a).
  • State abstraction shields lower-level subtasks by filtering out state variables irrelevant to their local objectives, exponentially shrinking the effective state space.
  • MAXQ achieves recursive optimality rather than global optimality; child subtasks optimize local subgoals independently of global parent contexts unless guided by pseudo-rewards.