Source code for minilink.planning.policy_synthesis.lookup_policy

"""Lookup-table feedback: a dynamic-programming policy as a state-feedback block."""

import numpy as np
from scipy.interpolate import RegularGridInterpolator

from minilink.core.system import System

###############################################################################
# Public API
###############################################################################


[docs] class LookupTableController(System): """ State-feedback controller backed by a policy lookup table. Wraps a discrete policy ``pi`` (action ids over a :class:`~minilink.planning.policy_synthesis.discretizer.StateSpaceGrid`) as a state-feedback block ``u = pi(x)``. Its input port is named ``x``, so it composes with a plant through ``controller @ plant`` and simulates like any other block. Each input axis is interpolated independently, so the discrete table yields a smooth command. Parameters ---------- grid : StateSpaceGrid Grid the policy is defined on. pi : array of int, shape (nodes_n,) Greedy policy as action ids by node id. interpolation : {"linear", "nearest"}, optional Per-axis input interpolation method. """ def __init__(self, grid, pi, *, interpolation: str = "linear") -> None: super().__init__() self.name = "Lookup Table Controller" # Parameters: the grid and the policy's action ids self.grid = grid self.pi = np.asarray(pi, dtype=int) # Ports: their bounds mirror the grid's domain (plot sweeps and color limits) n = grid.n m = grid.m X = grid.X U = grid.U self.add_input_port("x", dim=n, lower_bound=X.lower, upper_bound=X.upper) self.add_output_port( "u", dim=m, function=self.ctl, dependencies=("x",), lower_bound=U.box.lower, upper_bound=U.box.upper, ) # One interpolator per input axis over the policy's input table u_table = grid.input_from_policy(self.pi) levels = tuple(grid.x_levels) self._interp = [ RegularGridInterpolator( levels, u_table[:, k].reshape(grid.x_grid_shape), method=interpolation, bounds_error=False, fill_value=None, ) for k in range(m) ]
[docs] def action(self, x) -> np.ndarray: """Return the interpolated command ``u`` at a single state ``x``.""" interpolators = self._interp x = np.asarray(x, dtype=float).reshape(1, -1) # Each input axis read from its own interpolated table u = np.array([float(interp(x)[0]) for interp in interpolators]) return u
[docs] def ctl(self, x, u, t=0, params=None): """State feedback; the ``x`` input port carries the plant state in ``u``.""" return self.action(u)