Skip to content
AI360Xpert
Beta

Polynomials and Fourier Basis in RL

Instead of struggling to fit complex value landscapes with unstable polynomial curves, the Fourier basis decomposes continuous states into harmonious, orthogonal cosine waves of increasing frequency.

Comparing polynomial basis functions with the bounded, orthogonal Fourier cosine basis for linear value function approximation in continuous state spaces.
Comparing polynomial basis functions with the bounded, orthogonal Fourier cosine basis for linear value function approximation in continuous state spaces.

Why Does This Exist?

When reinforcement learning moves from discrete gridworlds to continuous physical systems—such as controlling a robot arm, balancing a cart-pole, or piloting a spacecraft—tabular Q-learning breaks down because the number of distinct states is infinite. The agent must approximate value functions using function approximation:

v^(s,w)=w⊤ϕ(s)\hat{v}(\mathbf{s}, \mathbf{w}) = \mathbf{w}^\top \boldsymbol{\phi}(\mathbf{s})

Where ϕ(s)\boldsymbol{\phi}(\mathbf{s}) is a feature vector that transforms raw continuous state coordinates s∈Rk\mathbf{s} \in \mathbb{R}^k into a higher-dimensional space where linear methods can represent non-linear value surfaces.

The most naive feature transformation is the polynomial basis, constructing features from powers and cross-products (1,s1,s2,s1s2,s12,…1, s_1, s_2, s_1 s_2, s_1^2, \dots). While mathematically straightforward, polynomials suffer from fatal pathologies in reinforcement learning:

  • Global interference: A temporal difference error observed in one corner of the state space modifies polynomial weights that warp value predictions everywhere else, causing catastrophic forgetting.
  • Runge's phenomenon and unbounded divergence: Higher-order polynomial features shoot toward ±∞\pm \infty near the boundaries of the state space, making gradient updates violently unstable.
  • Ill-conditioned Hessians: Polynomial features are non-orthogonal, causing gradient descent to oscillate erratically across narrow error valleys.

To overcome these structural flaws, George Konidaris, Sarah Osentoski, and Philip Thomas (2011) introduced the Fourier basis for reinforcement learning. By using orthogonal cosine harmonics over a normalized hypercube [0,1]k[0, 1]^k, the Fourier basis guarantees bounded feature outputs in [−1,1][-1, 1], eliminates destructive global interference, and provides a mathematically grounded frequency-dependent learning rate rule that outperforms polynomials and radial basis functions across continuous control benchmarks.

Think of It Like This

Musical Synthesizers: Taylor Series Polynomials vs. Fourier Harmonic Overtones

Imagine trying to synthesize the warm acoustic resonance of a concert cello on an electronic keyboard:

  • The Polynomial Approach (Brute-Force Taylor Approximation): You try to craft the cello's sound wave by superimposing steep algebraic powers: x,x2,x3,x4,x5x, x^2, x^3, x^4, x^5. Because high powers shoot off to infinity at the edges, turning up the volume on x5x^5 to fix a tiny buzzing artifact at the end of the note completely destroys the pitch in the middle of the wave. The entire keyboard screeches with wild, unpredictable distortion.
  • The Fourier Basis Approach (Harmonic Synthesis): You synthesize the cello using pure sinusoidal harmonics. You start with the fundamental pitch (the low-frequency base tone, cos⁡(πs)\cos(\pi s)), and then add subtle second, third, and fourth harmonic overtones of increasing frequency (cos⁡(2πs),cos⁡(3πs)\cos(2\pi s), \cos(3\pi s)) at decreasing volumes. Because each harmonic is mathematically orthogonal, adjusting a high-frequency overtone adds fine musical texture without altering the fundamental pitch. The sound remains rich, stable, and perfectly bounded within the speaker's dynamic range.

Where the analogy stops: In sound synthesis, harmonics travel across continuous time. In reinforcement learning, the Fourier basis projects continuous spatial state vectors s∈[0,1]k\mathbf{s} \in [0, 1]^k onto multi-dimensional frequency vectors ci∈{0,…,n}k\mathbf{c}_i \in \{0, \dots, n\}^k to approximate state values and action-value surfaces.

How It Actually Works

Polynomial Limitations and Konidaris' Fourier Cosine Basis

Consider a continuous state space of dimension kk, where the state vector is s=[s1,s2,…,sk]⊤\mathbf{s} = [s_1, s_2, \dots, s_k]^\top.

1. The Polynomial Basis

For an order-nn polynomial basis, each feature ϕi(s)\phi_i(\mathbf{s}) is formed by integer exponents ci,j∈{0,1,…,n}c_{i, j} \in \{0, 1, \dots, n\}:

ϕi(s)=∏j=1ksjci,j\phi_i(\mathbf{s}) = \prod_{j=1}^k s_j^{c_{i, j}}

For a 2D state space s=[s1,s2]⊤\mathbf{s} = [s_1, s_2]^\top with order n=2n = 2, the complete basis consists of (n+1)k=9(n+1)^k = 9 features: ϕ(s)=[1,s1,s2,s12,s22,s1s2,s12s2,s1s22,s12s22]⊤\boldsymbol{\phi}(\mathbf{s}) = \left[ 1, s_1, s_2, s_1^2, s_2^2, s_1 s_2, s_1^2 s_2, s_1 s_2^2, s_1^2 s_2^2 \right]^\top

Because these features are not orthogonal over the state domain, their inner products are heavily coupled. Updating weight wiw_i for s12s_1^2 alters values across the entire state space, making semi-gradient TD updates sluggish and prone to divergence.

2. The Fourier Cosine Basis

Konidaris et al. demonstrated that using only the cosine functions of a multidimensional Fourier series provides an exceptional basis for linear value approximation. Cosine functions are symmetric about the origin, naturally representing boundary conditions where derivative fluxes approach zero.

Before feature evaluation, continuous state coordinates must be normalized into the unit hypercube: sj∈[0,1]∀j∈{1,…,k}s_j \in [0, 1] \quad \forall j \in \{1, \dots, k\}

For an order-nn Fourier basis, each basis feature ϕi(s)\phi_i(\mathbf{s}) is defined by an integer coefficient vector ci=[ci,1,ci,2,…,ci,k]⊤\mathbf{c}_i = [c_{i, 1}, c_{i, 2}, \dots, c_{i, k}]^\top with each ci,j∈{0,1,…,n}c_{i, j} \in \{0, 1, \dots, n\}:

ϕi(s)=cos⁡(πci⊤s)=cos⁡(π∑j=1kci,jsj)\phi_i(\mathbf{s}) = \cos\left( \pi \mathbf{c}_i^\top \mathbf{s} \right) = \cos\left( \pi \sum_{j=1}^k c_{i, j} s_j \right)

The total number of basis features for an order-nn expansion in kk dimensions is (n+1)k(n + 1)^k.

3. Why the Fourier Basis Excels in RL

The Fourier basis possesses three mathematical properties that make it uniquely suited for temporal difference learning:

  1. Strict Boundedness: ϕi(s)∈[−1,1]∀s∈[0,1]k\phi_i(\mathbf{s}) \in [-1, 1] \quad \forall \mathbf{s} \in [0, 1]^k Features cannot explode, preventing numerical overflow and gradient spikes.

  2. Orthogonality: The basis functions form an orthogonal set under the L2L^2 inner product over [0,1]k[0, 1]^k: ∫[0,1]kcos⁡(πci⊤s)cos⁡(πcj⊤s) ds=0for ci≠cj\int_{[0, 1]^k} \cos(\pi \mathbf{c}_i^\top \mathbf{s}) \cos(\pi \mathbf{c}_j^\top \mathbf{s}) \, d\mathbf{s} = 0 \quad \text{for } \mathbf{c}_i \ne \mathbf{c}_j This near-zero cross-correlation keeps the gradient covariance matrix well-conditioned, drastically reducing destructive interference between coarse and fine features.

  3. The Natural Frequency-Scaled Step Size Rule: In Fourier analysis, the L2L^2 norm of the gradient scales with frequency ∥ci∥2\|\mathbf{c}_i\|_2. Konidaris et al. proved that applying a uniform learning rate α\alpha causes high-frequency features to update too aggressively, producing severe high-frequency oscillations ("ringing").

    To maintain uniform parameter convergence rates across all spatial scales, learning rates must be scaled inversely with the Euclidean norm of the coefficient vector: αi={αif ∥ci∥2=0α∥ci∥2if ∥ci∥2>0\alpha_i = \begin{cases} \alpha & \text{if } \|\mathbf{c}_i\|_2 = 0 \\ \frac{\alpha}{\|\mathbf{c}_i\|_2} & \text{if } \|\mathbf{c}_i\|_2 > 0 \end{cases} Where ∥ci∥2=∑j=1kci,j2\|\mathbf{c}_i\|_2 = \sqrt{\sum_{j=1}^k c_{i, j}^2}.

This rule automatically lets low-frequency features capture broad value topography quickly, while high-frequency features gently refine localized details without instability.

Worked numerical example

Consider a 2D continuous state space (e.g., Mountain Car position and velocity) with order n=1n = 1.

Let:

  • Continuous state s=[0.2,0.8]⊤∈[0,1]2\mathbf{s} = [0.2, 0.8]^\top \in [0, 1]^2
  • Base learning rate α=0.01\alpha = 0.01
  • Order n=1  ⟹  (1+1)2=4n = 1 \implies (1 + 1)^2 = 4 basis vectors c0,…,c3∈{0,1}2\mathbf{c}_0, \dots, \mathbf{c}_3 \in \{0, 1\}^2

Let us compute the features ϕi(s)\phi_i(\mathbf{s}) and scaled learning rates αi\alpha_i step by step:

1. Feature 0: Bias Term (c0=[0,0]⊤\mathbf{c}_0 = [0, 0]^\top)

  • Frequency norm: ∥c0∥2=02+02=0.0\|\mathbf{c}_0\|_2 = \sqrt{0^2 + 0^2} = 0.0
  • Inner product: c0⊤s=0(0.2)+0(0.8)=0.0\mathbf{c}_0^\top \mathbf{s} = 0(0.2) + 0(0.8) = 0.0
  • Feature value: ϕ0(s)=cos⁡(π×0.0)=1.0000\phi_0(\mathbf{s}) = \cos(\pi \times 0.0) = \mathbf{1.0000}
  • Learning rate: α0=α=0.0100\alpha_0 = \alpha = \mathbf{0.0100}

2. Feature 1: Variation along Dimension 2 (c1=[0,1]⊤\mathbf{c}_1 = [0, 1]^\top)

  • Frequency norm: ∥c1∥2=02+12=1.0\|\mathbf{c}_1\|_2 = \sqrt{0^2 + 1^2} = 1.0
  • Inner product: c1⊤s=0(0.2)+1(0.8)=0.8\mathbf{c}_1^\top \mathbf{s} = 0(0.2) + 1(0.8) = 0.8
  • Feature value: ϕ1(s)=cos⁡(0.8π)=cos⁡(144∘)=−0.8090\phi_1(\mathbf{s}) = \cos(0.8 \pi) = \cos(144^\circ) = \mathbf{-0.8090}
  • Learning rate: α1=0.011.0=0.0100\alpha_1 = \frac{0.01}{1.0} = \mathbf{0.0100}

3. Feature 2: Variation along Dimension 1 (c2=[1,0]⊤\mathbf{c}_2 = [1, 0]^\top)

  • Frequency norm: ∥c2∥2=12+02=1.0\|\mathbf{c}_2\|_2 = \sqrt{1^2 + 0^2} = 1.0
  • Inner product: c2⊤s=1(0.2)+0(0.8)=0.2\mathbf{c}_2^\top \mathbf{s} = 1(0.2) + 0(0.8) = 0.2
  • Feature value: ϕ2(s)=cos⁡(0.2π)=cos⁡(36∘)=+0.8090\phi_2(\mathbf{s}) = \cos(0.2 \pi) = \cos(36^\circ) = \mathbf{+0.8090}
  • Learning rate: α2=0.011.0=0.0100\alpha_2 = \frac{0.01}{1.0} = \mathbf{0.0100}

4. Feature 3: Coupled Diagonal Variation (c3=[1,1]⊤\mathbf{c}_3 = [1, 1]^\top)

  • Frequency norm: ∥c3∥2=12+12=2≈1.4142\|\mathbf{c}_3\|_2 = \sqrt{1^2 + 1^2} = \sqrt{2} \approx 1.4142
  • Inner product: c3⊤s=1(0.2)+1(0.8)=1.0\mathbf{c}_3^\top \mathbf{s} = 1(0.2) + 1(0.8) = 1.0
  • Feature value: ϕ3(s)=cos⁡(1.0π)=cos⁡(180∘)=−1.0000\phi_3(\mathbf{s}) = \cos(1.0 \pi) = \cos(180^\circ) = \mathbf{-1.0000}
  • Scaled learning rate: α3=0.012=0.011.4142≈0.007071\alpha_3 = \frac{0.01}{\sqrt{2}} = \frac{0.01}{1.4142} \approx \mathbf{0.007071}

Summary Table

Index iiFrequency Vector ci\mathbf{c}_iFrequency Norm ∥ci∥2\|\mathbf{c}_i\|_2Feature ϕi(s)\phi_i(\mathbf{s})Scaled Step Size αi\alpha_iSpatial Role
00[0,0]⊤[0, 0]^\top0.00000.0000+1.0000+1.00000.0100000.010000Baseline DC bias
11[0,1]⊤[0, 1]^\top1.00001.0000−0.8090-0.80900.0100000.010000Pure vertical axis mode
22[1,0]⊤[1, 0]^\top1.00001.0000+0.8090+0.80900.0100000.010000Pure horizontal axis mode
33[1,1]⊤[1, 1]^\top1.41421.4142−1.0000-1.00000.0070710.007071Coupled diagonal interaction

Notice how α3\alpha_3 is automatically damped by 30%30\% to ensure high-frequency cross-terms do not oscillate during value updates.

Code

The following complete, type-hinted Python implementation constructs a multi-dimensional Fourier basis encoder, precomputes frequency-scaled learning rates, and updates a linear value approximator with assertion checks.

import itertoolsimport mathfrom typing import List, Tuple

class FourierBasis:    """Fourier Cosine Basis linear feature encoder for continuous RL states.
    References:        Konidaris, Osentoski, and Thomas (AAAI 2011).        'Value Function Approximation in Reinforcement Learning using the Fourier Basis'    """
    def __init__(        self,        state_dim: int,        order: int,        state_bounds: List[Tuple[float, float]],        base_alpha: float = 0.01,    ) -> None:        self.state_dim: int = state_dim        self.order: int = order        self.state_bounds: List[Tuple[float, float]] = state_bounds        self.base_alpha: float = base_alpha
        # Generate all multi-frequency coefficient vectors c in {0, ..., order}^k        coord_ranges = [range(order + 1) for _ in range(state_dim)]        self.c_vectors: List[List[int]] = [            list(c) for c in itertools.product(*coord_ranges)        ]        self.num_features: int = len(self.c_vectors)
        # Precompute frequency-scaled step sizes: alpha_i = alpha / ||c_i||_2        self.alphas: List[float] = []        for c in self.c_vectors:            norm = math.sqrt(sum(ci**2 for ci in c))            self.alphas.append(base_alpha if norm == 0 else base_alpha / norm)
        # Initialize linear weights vector to zero        self.weights: List[float] = [0.0] * self.num_features
    def normalize_state(self, state: List[float]) -> List[float]:        """Linearly projects continuous state into unit hypercube [0, 1]^k."""        normalized: List[float] = []        for s_val, (s_min, s_max) in zip(state, self.state_bounds):            clamped = max(s_min, min(s_max, s_val))            norm_val = (                (clamped - s_min) / (s_max - s_min) if s_max > s_min else 0.0            )            normalized.append(norm_val)        return normalized
    def encode(self, state: List[float]) -> List[float]:        """Computes basis features phi_i(s) = cos(pi * c_i^T s)."""        s_norm = self.normalize_state(state)        features: List[float] = []        for c in self.c_vectors:            dot_product = sum(ci * si for ci, si in zip(c, s_norm))            features.append(math.cos(math.pi * dot_product))        return features
    def predict(self, state: List[float]) -> float:        """Computes linear approximation V(s) = w^T phi(s)."""        features = self.encode(state)        return sum(w * phi for w, phi in zip(self.weights, features))
    def update(self, state: List[float], target: float) -> float:        """Applies semi-gradient TD update with frequency-scaled learning rates."""        features = self.encode(state)        prediction = sum(w * phi for w, phi in zip(self.weights, features))        td_error = target - prediction
        # w_i <- w_i + alpha_i * td_error * phi_i(s)        for i in range(self.num_features):            self.weights[i] += self.alphas[i] * td_error * features[i]
        return td_error

# Define 2D continuous state space with raw domain boundariesstate_boundaries = [(0.0, 10.0), (-5.0, 5.0)]fourier_encoder = FourierBasis(    state_dim=2, order=1, state_bounds=state_boundaries, base_alpha=0.01)
# Raw state that maps exactly to normalized coords [0.2, 0.8]# s_0 = 2.0 -> (2.0 - 0) / 10 = 0.2# s_1 = 3.0 -> (3.0 - (-5)) / 10 = 0.8query_state = [2.0, 3.0]phi_features = fourier_encoder.encode(query_state)
print("Computed Fourier Features phi(s):")for i, (c, phi, a) in enumerate(    zip(fourier_encoder.c_vectors, phi_features, fourier_encoder.alphas)):    print(f"  Feature {i} (c={c}): phi = {phi:+.4f}, alpha = {a:.6f}")
# Verification assertionsassert len(phi_features) == 4, "Incorrect feature dimension"assert round(phi_features[0], 4) == 1.0000, "Bias term incorrect"assert round(phi_features[1], 4) == -0.8090, "Dim 2 feature incorrect"assert round(phi_features[2], 4) == 0.8090, "Dim 1 feature incorrect"assert round(phi_features[3], 4) == -1.0000, "Coupled diagonal incorrect"assert round(fourier_encoder.alphas[3], 6) == round(    0.01 / math.sqrt(2), 6), "Scaled alpha incorrect"
# Verify semi-gradient value updateinitial_prediction = fourier_encoder.predict(query_state)fourier_encoder.update(query_state, target=5.0)updated_prediction = fourier_encoder.predict(query_state)
print(f"\nInitial V(s): {initial_prediction:.4f}")print(f"Updated V(s): {updated_prediction:.4f}")assert (    updated_prediction > initial_prediction), "Value failed to increase toward target!"print("All assertions passed: Fourier basis correctly encoded and updated!")
# -> Expected output:# -> Computed Fourier Features phi(s):# ->   Feature 0 (c=[0, 0]): phi = +1.0000, alpha = 0.010000# ->   Feature 1 (c=[0, 1]): phi = -0.8090, alpha = 0.010000# ->   Feature 2 (c=[1, 0]): phi = +0.8090, alpha = 0.010000# ->   Feature 3 (c=[1, 1]): phi = -1.0000, alpha = 0.007071# -> # -> Initial V(s): 0.0000# -> Updated V(s): 0.1508# -> All assertions passed: Fourier basis correctly encoded and updated!

Watch Out For

Skipping [0, 1] State Normalization or Using Uniform Learning Rates

Two primary traps degrade Fourier basis performance in continuous reinforcement learning:

Trap 1: Passing Unnormalized States: The Fourier cosine basis strictly assumes that inputs live on the normalized interval s∈[0,1]k\mathbf{s} \in [0, 1]^k. If you pass raw physical coordinates (such as position ∈[−1.2,0.6]\in [-1.2, 0.6] and velocity ∈[−0.07,0.07]\in [-0.07, 0.07]), the cosine argument πc⊤s\pi \mathbf{c}^\top \mathbf{s} wraps through dozens of arbitrary cycles, destroying spatial coherence and causing value predictions to resemble random noise.

  • The Fix: Always clip and normalize state variables linearly into [0,1][0, 1] using known environmental domain bounds.

Trap 2: Omitting the Frequency-Scaled Learning Rate: Setting a constant step size αi=α\alpha_i = \alpha across all frequencies causes high-order features to update too quickly relative to low-order features. Because high-frequency waves oscillate rapidly over small spatial intervals, uniform updates cause severe "ringing" artifacts and gradient overshoot.

  • The Fix: Always scale learning rates inversely by the coefficient norm αi=α/∥ci∥2\alpha_i = \alpha / \|\mathbf{c}_i\|_2 (retaining α0=α\alpha_0 = \alpha for the zero-frequency vector).

The Quick Version

  • Polynomial limitations: Polynomial basis functions (s1c1s2c2s_1^{c_1} s_2^{c_2}) suffer from severe global interference, non-orthogonal collinearity, and boundary divergence.
  • Fourier cosine basis: Projects continuous normalized states s∈[0,1]k\mathbf{s} \in [0, 1]^k onto orthogonal cosine harmonics ϕi(s)=cos⁡(πci⊤s)\phi_i(\mathbf{s}) = \cos(\pi \mathbf{c}_i^\top \mathbf{s}).
  • Strict boundedness: Every Fourier basis feature is guaranteed to remain inside [−1,1][-1, 1], preventing gradient blowups and numerical instability.
  • Frequency-scaled step sizes: Scaling learning rates by α/∥ci∥2\alpha / \|\mathbf{c}_i\|_2 stabilizes high-frequency updates, allowing smooth value landscapes to form without ringing artifacts.