Differentially flat systems

The control.flatsys package contains a set of classes and functions that can be used to compute trajectories for differentially flat systems.

A differentially flat system is defined by creating an object using the FlatSystem class, which has member functions for mapping the system state and input into and out of flat coordinates. The point_to_point() function can be used to create a trajectory between two endpoints, written in terms of a set of basis functions defined using the BasisFamily class. The resulting trajectory is return as a SystemTrajectory object and can be evaluated using the eval() member function.

Overview of differential flatness

A nonlinear differential equation of the form

\dot x = f(x, u), \qquad x \in R^n, u \in R^m

is differentially flat if there exists a function \alpha such that

z = \alpha(x, u, \dot u\, \dots, u^{(p)})

and we can write the solutions of the nonlinear system as functions of z and a finite number of derivatives

x &= \beta(z, \dot z, \dots, z^{(q)}) \\
u &= \gamma(z, \dot z, \dots, z^{(q)}).

For a differentially flat system, all of the feasible trajectories for the system can be written as functions of a flat output z(\cdot) and its derivatives. The number of flat outputs is always equal to the number of system inputs.

Differentially flat systems are useful in situations where explicit trajectory generation is required. Since the behavior of a flat system is determined by the flat outputs, we can plan trajectories in output space, and then map these to appropriate inputs. Suppose we wish to generate a feasible trajectory for the the nonlinear system

\dot x = f(x, u), \qquad x(0) = x_0,\, x(T) = x_f.

If the system is differentially flat then

x(0) &= \beta\bigl(z(0), \dot z(0), \dots, z^{(q)}(0) \bigr) = x_0, \\
x(T) &= \gamma\bigl(z(T), \dot z(T), \dots, z^{(q)}(T) \bigr) = x_f,

and we see that the initial and final condition in the full state space depends on just the output z and its derivatives at the initial and final times. Thus any trajectory for z that satisfies these boundary conditions will be a feasible trajectory for the system, using equation~eqref{eq:trajgen:flat2state} to determine the full state space and input trajectories.

In particular, given initial and final conditions on z and its derivatives that satisfy the initial and final conditions any curve z(\cdot) satisfying those conditions will correspond to a feasible trajectory of the system. We can parameterize the flat output trajectory using a set of smooth basis functions \psi_i(t):

z(t) = \sum_{i=1}^N \alpha_i \psi_i(t), \qquad \alpha_i \in R

We seek a set of coefficients \alpha_i, i = 1, \dots, N such that z(t) satisfies the boundary conditions for x(0) and x(T). The derivatives of the flat output can be computed in terms of the derivatives of the basis functions:

\dot z(t) &= \sum_{i=1}^N \alpha_i \dot \psi_i(t) \\
&\,\vdots \\
\dot z^{(q)}(t) &= \sum_{i=1}^N \alpha_i \psi^{(q)}_i(t).

We can thus write the conditions on the flat outputs and their derivatives as

\begin{bmatrix}
  \psi_1(0) & \psi_2(0) & \dots & \psi_N(0) \\
  \dot \psi_1(0) & \dot \psi_2(0) & \dots & \dot \psi_N(0) \\
  \vdots & \vdots & & \vdots \\
  \psi^{(q)}_1(0) & \psi^{(q)}_2(0) & \dots & \psi^{(q)}_N(0) \\[1ex]
  \psi_1(T) & \psi_2(T) & \dots & \psi_N(T) \\
  \dot \psi_1(T) & \dot \psi_2(T) & \dots & \dot \psi_N(T) \\
  \vdots & \vdots & & \vdots \\
  \psi^{(q)}_1(T) & \psi^{(q)}_2(T) & \dots & \psi^{(q)}_N(T) \\
\end{bmatrix}
\begin{bmatrix} \alpha_1 \\ \vdots \\ \alpha_N \end{bmatrix} =
\begin{bmatrix}
  z(0) \\ \dot z(0) \\ \vdots \\ z^{(q)}(0) \\[1ex]
  z(T) \\ \dot z(T) \\ \vdots \\ z^{(q)}(T) \\
\end{bmatrix}

This equation is a linear equation of the form

M \alpha = \begin{bmatrix} \bar z(0) \\ \bar z(T) \end{bmatrix}

where \bar z is called the flat flag for the system. Assuming that M has a sufficient number of columns and that it is full column rank, we can solve for a (possibly non-unique) \alpha that solves the trajectory generation problem.

Module usage

To create a trajectory for a differentially flat system, a FlatSystem object must be created. This is done by specifying the forward and reverse mappings between the system state/input and the differentially flat outputs and their derivatives (“flat flag”).

The forward() method computes the flat flag given a state and input:

zflag = sys.forward(x, u)

The reverse() method computes the state and input given the flat flag:

x, u = sys.reverse(zflag)

The flag \bar z is implemented as a list of flat outputs z_i and their derivatives up to order q_i:

zflag[i][j] = z_i^{(j)}

The number of flat outputs must match the number of system inputs.

For a linear system, a flat system representation can be generated using the LinearFlatSystem class:

flatsys = control.flatsys.LinearFlatSystem(linsys)

For more general systems, the FlatSystem object must be created manually

flatsys = control.flatsys.FlatSystem(nstate, ninputs, forward, reverse)

In addition to the flat system descriptionn, a set of basis functions \phi_i(t) must be chosen. The FlatBasis class is used to represent the basis functions. A polynomial basis function of the form 1, t, t^2:, ... can be computed using the class, which is initialized by passing the desired order of the polynomial basis set:

polybasis = control.flatsys.PolyBasis(N)

Once the system and basis function have been defined, the point_to_point() function can be used to compute a trajectory between initial and final states and inputs:

traj = control.flatsys.point_to_point(x0, u0, xf, uf, Tf, basis=polybasis)

The returned object has class SystemTrajectory and can be used to compute the state and input trajectory between the initial and final condition:

xd, ud = traj.eval(T)

where T is a list of times on which the trajectory should be evaluated (e.g., T = numpy.linspace(0, Tf, M).

Example

To illustrate how we can use a two degree-of-freedom design to improve the performance of the system, consider the problem of steering a car to change lanes on a road. We use the non-normalized form of the dynamics, which are derived Feedback Systems by Astrom and Murray, Example 3.11.

import control.flatsys as fs

# Function to take states, inputs and return the flat flag
def vehicle_flat_forward(x, u, params={}):
    # Get the parameter values
    b = params.get('wheelbase', 3.)

    # Create a list of arrays to store the flat output and its derivatives
    zflag = [np.zeros(3), np.zeros(3)]

    # Flat output is the x, y position of the rear wheels
    zflag[0][0] = x[0]
    zflag[1][0] = x[1]

    # First derivatives of the flat output
    zflag[0][1] = u[0] * np.cos(x[2])  # dx/dt
    zflag[1][1] = u[0] * np.sin(x[2])  # dy/dt

    # First derivative of the angle
    thdot = (u[0]/b) * np.tan(u[1])

    # Second derivatives of the flat output (setting vdot = 0)
    zflag[0][2] = -u[0] * thdot * np.sin(x[2])
    zflag[1][2] =  u[0] * thdot * np.cos(x[2])

    return zflag

# Function to take the flat flag and return states, inputs
def vehicle_flat_reverse(zflag, params={}):
    # Get the parameter values
    b = params.get('wheelbase', 3.)

    # Create a vector to store the state and inputs
    x = np.zeros(3)
    u = np.zeros(2)

    # Given the flat variables, solve for the state
    x[0] = zflag[0][0]  # x position
    x[1] = zflag[1][0]  # y position
    x[2] = np.arctan2(zflag[1][1], zflag[0][1])  # tan(theta) = ydot/xdot

    # And next solve for the inputs
    u[0] = zflag[0][1] * np.cos(x[2]) + zflag[1][1] * np.sin(x[2])
    u[1] = np.arctan2(
        (zflag[1][2] * np.cos(x[2]) - zflag[0][2] * np.sin(x[2])), u[0]/b)

    return x, u

vehicle_flat = fs.FlatSystem(
    3, 2, forward=vehicle_flat_forward, reverse=vehicle_flat_reverse)

To find a trajectory from an initial state x_0 to a final state x_\text{f} in time T_\text{f} we solve a point-to-point trajectory generation problem. We also set the initial and final inputs, whi ch sets the vehicle velocity v and steering wheel angle \delta at the endpoints.

# Define the endpoints of the trajectory
x0 = [0., -2., 0.]; u0 = [10., 0.]
xf = [100., 2., 0.]; uf = [10., 0.]
Tf = 10

# Define a set of basis functions to use for the trajectories
poly = fs.PolyFamily(6)

# Find a trajectory between the initial condition and the final condition
traj = fs.point_to_point(vehicle_flat, x0, u0, xf, uf, Tf, basis=poly)

# Create the trajectory
t = np.linspace(0, Tf, 100)
x, u = traj.eval(t)

Module classes and functions

Flat systems classes

BasisFamily(N) Base class for implementing basis functions for flat systems.
FlatSystem(forward, reverse[, updfcn, …]) Base class for representing a differentially flat system.
LinearFlatSystem(linsys[, inputs, outputs, …])
PolyFamily(N) Polynomial basis functions.
SystemTrajectory(sys, basis[, coeffs, flaglen]) Class representing a system trajectory.

Flat systems functions

point_to_point(sys, x0, u0, xf, uf, Tf[, …]) Compute trajectory between an initial and final conditions.