This notebook describes the details of forming the coefficient matrix and ordinate vector from walking data for a linear least squares solution to the optimal gains from a simple full state feedback controller employed by the human while walking.
!pip freeze
Cython==0.19.1 -e git+git@github.com:moorepants/DynamicistToolKit.git@83822a2634756ad8a0a1701a0cb3cca3c07877d4#egg=DynamicistToolKit-dev Jinja2==2.7.1 MarkupSafe==0.18 argparse==1.2.1 coverage==3.6 ipython==1.0.0 matplotlib==1.3.0 nose==1.3.0 numpy==1.7.1 pandas==0.12.0 pyparsing==2.0.1 python-dateutil==2.1 pytz==2013b pyzmq==13.1.0 scipy==0.12.0 six==1.3.0 sympy==0.7.3 tornado==3.1 wsgiref==0.1.2
from sympy import init_printing
init_printing()
from sympy import symbols, Matrix, Symbol, zeros, eye
n = 3 # number of time samples in each cycle (step)
m = 2 # number of cycles (steps)
p = 2 # number of sensors
q = 2 # number of controls
First form the equation that generates the controls given the error in the sensors at a single time step during a single foot step.
mm(t)=m(t)lc+K(t)se(t)=m(t)lc+K(t)(slc(t)−sm(t))Now rearrange the equations such that we have one linear in both the gains, K(t), and in m∗(t):
mm(t)=m(t)lc+K(t)slc(t)−K(t)sm(t)=m∗(t)−K(t)sm(t)The "measured" controls, mm(t):
mm = Matrix(q, 1, lambda i, j: Symbol('m_m_q={}'.format(i)))
mm
The unknown gains, K:
K = Matrix(q, p, lambda i, j: Symbol('k_q={},p={}'.format(i, j)))
K
The unknown "limit cycle" controls, m∗(t):
ms = Matrix(q, 1, lambda i, j: Symbol('m^*_q={}'.format(i)))
ms
The measured sensors, sm(t):
sm = Matrix(p, 1, lambda i, j: Symbol('s_m_p={}'.format(i)))
sm
And the equation:
0=m(t)−m∗(t)+K(t)s(t)zero = mm - ms + K * sm
zero
Now for a simple linear least squares the equations can be massage into this form:
Ax=bwhich gives and over determined system of linear equations.
We define x as:
x = K.reshape(q * p, 1).col_join(ms)
x
To form the A matrix for a single time step in a single foot step the equation can be rearranged:
A = zeros(q, q * p)
for row in range(q):
A[row, row * p:(row + 1) * p] = sm.T
A = A.row_join(-eye(q))
A
b = -mm
b
Now we can check to make sure that our new form gives the same answer as the previous form:
zero2 = A * x - b
zero2
assert zero == zero2
Now for n timesteps we want to form the A matrix such that there are different gains for each time step.
gain_matrices = []
control_vectors = []
control_star_vectors = []
sensor_vectors = []
for time_step in range(n):
gain_matrices.append(Matrix(q, p, lambda i, j: Symbol('k^{}_q={},p={}'.format(time_step, i, j))))
control_vectors.append(Matrix(q, 1, lambda i, j: Symbol('m^{}_m_q={}'.format(time_step, i))))
control_star_vectors.append(Matrix(q, 1, lambda i, j: Symbol('m^*{}_q={}'.format(time_step, i))))
sensor_vectors.append(Matrix(p, 1, lambda i, j: Symbol('s^{}_m_p={}'.format(time_step, i))))
gain_matrices
control_vectors
control_star_vectors
sensor_vectors
Picking off a trio we should get our familiar equation back:
zero = control_vectors[0] - control_star_vectors[0] + gain_matrices[0] * sensor_vectors[0]
zero
The larger A matrix for all of the time steps can be formed by stacking the A matrices from a single time step diagonally:
Am = zeros(n * q, n * (q + q * p))
for time_step in range(n):
An = zeros(q, q * p)
for row in range(q):
An[row, row * p:(row + 1) * p] = sensor_vectors[time_step].T
An = An.row_join(-eye(q))
num_rows, num_cols = An.shape
Am[time_step * num_rows:(time_step + 1) * num_rows, time_step * num_cols:(time_step + 1) * num_cols] = An
Am
The control vector and the gain vector are simple column stacks:
bm = -control_vectors[0]
for control in control_vectors[1:]:
bm = bm.col_join(-control)
bm
xm = gain_matrices[0].reshape(q * p, 1).col_join(control_star_vectors[0])
for gain, control_star in zip(gain_matrices[1:], control_star_vectors[1:]):
xm = xm.col_join(gain.reshape(q * p, 1)).col_join(control_star)
xm
Now the original equations can be regenerated with:
Am * xm - bm
We have n(pq+q) unknowns and nq equations so we need at least 1+p cycles to solve for the unknowns.
n * (p*q + q)
1 + p
Am.shape, bm.shape
b = bm
A = Am
for step in range(p):
b = b.col_join(bm)
A = A.col_join(Am)
A.shape, b.shape
The following should compute the least squares solution but since I didn't actually create new equations for the steps, then you get NaN as a solution.
x = A.LDLsolve(b)
x
type(x[0])
sympy.core.numbers.NaN