Add Lyapunov stable controller.
Change-Id: If8635e9e5122705259eb7fd975e4784575f3b3e2
diff --git a/frc971/control_loops/python/BUILD b/frc971/control_loops/python/BUILD
index c0b71f7..2f50468 100644
--- a/frc971/control_loops/python/BUILD
+++ b/frc971/control_loops/python/BUILD
@@ -108,7 +108,7 @@
"//external:python-gflags",
"//external:python-glog",
"//frc971/control_loops/python:controls",
- "//y2018/control_loops/python:polydrivetrain_lib",
+ "//y2016/control_loops/python:polydrivetrain_lib",
"@matplotlib",
],
)
diff --git a/frc971/control_loops/python/spline.py b/frc971/control_loops/python/spline.py
index d045f45..e69d874 100644
--- a/frc971/control_loops/python/spline.py
+++ b/frc971/control_loops/python/spline.py
@@ -8,11 +8,13 @@
import numpy
import scipy
import scipy.integrate
+import scipy.optimize
import sys
from frc971.control_loops.python import polydrivetrain
from frc971.control_loops.python import drivetrain
-import y2018.control_loops.python.drivetrain
+from frc971.control_loops.python import controls
+import y2016.control_loops.python.drivetrain
"""This file is my playground for implementing spline following.
@@ -236,7 +238,7 @@
# table to get an alpha -> distance calculation. Gaussian Quadrature
# is quite accurate, so we can get away with fewer points here than we
# might think.
- for alpha in numpy.linspace(0.0, 1.0, num_alpha)[:-1]:
+ for alpha in numpy.linspace(0.0, 1.0, num_alpha)[1:]:
self._point_distances.append(
scipy.integrate.fixed_quad(spline_velocity, alpha, alpha + 1.0
/ (num_alpha - 1.0))[0] +
@@ -325,6 +327,10 @@
return dtheta_points / numpy.linalg.norm(dspline_points, axis=0)
+ def dtheta_dt(self, distance, velocity, dspline_points=None, ddspline_points=None):
+ """Returns the angular velocity as a function of time."""
+ return self.dtheta(distance, dspline_points, ddspline_points) * velocity
+
def ddtheta(self,
distance,
dspline_points=None,
@@ -394,11 +400,13 @@
return 0.0
return (f(t, y) - a0) / y
- return (RungeKutta(integrablef, v, x, dx) - v) + numpy.sqrt(2.0 * a0 * dx + v * v)
+ return (RungeKutta(integrablef, v, x, dx) - v
+ ) + numpy.sqrt(2.0 * a0 * dx + v * v)
class Trajectory(object):
- def __init__(self, path, drivetrain, longitudal_accel, lateral_accel, distance_count):
+ def __init__(self, path, drivetrain, longitudal_accel, lateral_accel,
+ distance_count):
self._path = path
self._drivetrain = drivetrain
self.distances = numpy.linspace(0.0,
@@ -441,42 +449,6 @@
K5 = self._B_inverse * K2
return K3, K4, K5
- def curvature_voltage_pass(self, plan):
- plan = plan.copy()
- for i, distance in enumerate(self.distances):
- current_ddtheta = self._path.ddtheta(distance)[0]
- current_dtheta = self._path.dtheta(distance)[0]
- # We've now got the equation:
- # K2 * d^2x/dt^2 + K1 (dx/dt)^2 = A * K2 * dx/dt + B * U
- # Now, rephrase it as K3 v^2 + K4 v = U
- K3, K4, K5 = self.compute_K345(current_dtheta, current_ddtheta)
- # But, we are going to assume that d^2x/dt^2 = 0
-
- x = []
- for a, b in [(K3[0, 0], K4[0, 0]), (K3[1, 0], K4[1, 0])]:
- for c in [12.0, -12.0]:
- middle = b * b - 4.0 * a * c
- if middle >= 0.0:
- x.append((-b + numpy.sqrt(middle)) / (2.0 * a))
- x.append((-b - numpy.sqrt(middle)) / (2.0 * a))
-
- maxx = 0.0
- for newx in x:
- if newx < 0.0:
- continue
- U = (K3 * newx * newx + K4 * newx).T
- # TODO(austin): We know that one of these *will* be +-12.0. Only
- # check the other one.
- if not (numpy.abs(U) > 12.0 + 1e-6).any():
- maxx = max(newx, maxx)
-
- if maxx == 0.0:
- print('Could not solve')
- return None
- plan[i] = min(plan[i], maxx)
- pass
- return plan
-
def forward_acceleration(self, x, v):
current_ddtheta = self._path.ddtheta(x)[0]
current_dtheta = self._path.dtheta(x)[0]
@@ -562,7 +534,7 @@
return plan
# TODO(austin): The plan should probably not be passed in...
- def ff_voltage(self, plan, distance):
+ def ff_accel(self, plan, distance):
if distance < self.distances[1]:
after_index = 1
before_index = after_index - 1
@@ -598,7 +570,10 @@
else:
velocity = vbackward
accel = self.backward_acceleration(distance, velocity)
+ return (distance, velocity, accel)
+ def ff_voltage(self, plan, distance):
+ _, velocity, accel = self.ff_accel(plan, distance)
current_ddtheta = self._path.ddtheta(distance)[0]
current_dtheta = self._path.dtheta(distance)[0]
# TODO(austin): Factor these out.
@@ -610,6 +585,20 @@
U = K5 * accel + K3 * velocity * velocity + K4 * velocity
return U
+ def goal_state(self, distance, velocity):
+ width = (
+ self._drivetrain.robot_radius_l + self._drivetrain.robot_radius_r)
+ goal = numpy.matrix(numpy.zeros((5, 1)))
+
+ goal[0:2, :] = self._path.xy(distance)
+ goal[2, :] = self._path.theta(distance)
+
+ Ter = numpy.linalg.inv(numpy.matrix([[0.5, 0.5], [-1.0 / width, 1.0 / width]]))
+ goal[3:5, :] = Ter * numpy.matrix(
+ [[velocity], [self._path.dtheta_dt(distance, velocity)]])
+ return goal
+
+
def main(argv):
# Build up the control point matrix
start = numpy.matrix([[0.0, 0.0]]).T
@@ -784,9 +773,9 @@
# derivitives of our spline.
velocity_drivetrain = polydrivetrain.VelocityDrivetrainModel(
- y2018.control_loops.python.drivetrain.kDrivetrain)
+ y2016.control_loops.python.drivetrain.kDrivetrain)
position_drivetrain = drivetrain.Drivetrain(
- y2018.control_loops.python.drivetrain.kDrivetrain)
+ y2016.control_loops.python.drivetrain.kDrivetrain)
longitudal_accel = 3.0
lateral_accel = 2.0
@@ -825,47 +814,132 @@
pylab.ylabel("velocity (m/s)")
pylab.legend()
- voltages = numpy.matrix(numpy.zeros((2, len(distances))))
- for i in range(len(distances)):
- voltages[:, i] = trajectory.ff_voltage(backward_accel_plan,
- distances[i])
-
- # Now, let's integrate up the feed forwards voltage to see where we end up.
- spline_state = numpy.matrix(numpy.zeros((4, 1)))
- # x, y, theta, vl, vr
- state = numpy.matrix(numpy.zeros((5, 1)))
dt = 0.005
- num_timesteps = 400
- spline_states = numpy.matrix(numpy.zeros((4, num_timesteps)))
- states = numpy.matrix(numpy.zeros((5, num_timesteps)))
- Us = numpy.matrix(numpy.zeros((2, num_timesteps)))
- t = numpy.array(range(num_timesteps)) * dt
- for i in range(num_timesteps):
- spline_distance = 0.5 * (spline_state[0, 0] + spline_state[2, 0])
- spline_velocity = 0.5 * (spline_state[1, 0] + spline_state[3, 0])
- def distance_spline_diffeq(t, x):
- spline_distance = 0.5 * (x[0, 0] + x[2, 0])
- spline_velocity = 0.5 * (x[1, 0] + x[3, 0])
- U = trajectory.ff_voltage(
- backward_accel_plan,
- spline_distance)
- dXdt = (position_drivetrain.A_continuous * x +
- position_drivetrain.B_continuous * U)
- return dXdt
- spline_state = RungeKutta(distance_spline_diffeq, spline_state, i * dt, dt)
+ # Now, let's integrate up the path centric coordinates to get a distance,
+ # velocity, and acceleration as a function of time to follow the path.
- def spline_diffeq(t, x):
- spline_distance = 0.5 * (spline_state[0, 0] + spline_state[2, 0])
- spline_velocity = 0.5 * (spline_state[1, 0] + spline_state[3, 0])
- velocity = x[3:, :]
+ length_plan_t = [0.0]
+ length_plan_x = [numpy.matrix(numpy.zeros((2, 1)))]
+ length_plan_v = [0.0]
+ length_plan_a = [trajectory.ff_accel(backward_accel_plan, 0.0)[2]]
+ t = 0.0
+ spline_state = length_plan_x[-1][0:2, :]
+
+ while spline_state[0, 0] < path.length():
+ t += dt
+ def along_spline_diffeq(t, x):
+ _, v, a = trajectory.ff_accel(backward_accel_plan, x[0, 0])
+ return numpy.matrix([[x[1, 0]], [a]])
+
+ spline_state = RungeKutta(along_spline_diffeq,
+ spline_state.copy(), t, dt)
+
+ d, v, a = trajectory.ff_accel(backward_accel_plan, length_plan_x[-1][0, 0])
+
+ length_plan_v.append(v)
+ length_plan_a.append(a)
+ length_plan_t.append(t)
+ length_plan_x.append(spline_state.copy())
+ spline_state[1, 0] = v
+
+ xva_plan = numpy.matrix(numpy.zeros((3, len(length_plan_t))))
+ u_plan = numpy.matrix(numpy.zeros((2, len(length_plan_t))))
+ state_plan = numpy.matrix(numpy.zeros((5, len(length_plan_t))))
+
+ state = numpy.matrix(numpy.zeros((5, 1)))
+ state[3, 0] = 0.1
+ state[4, 0] = 0.1
+ states = numpy.matrix(numpy.zeros((5, len(length_plan_t))))
+ full_us = numpy.matrix(numpy.zeros((2, len(length_plan_t))))
+ x_es = []
+ y_es = []
+ theta_es = []
+ vel_es = []
+ omega_es = []
+ omega_rs = []
+ omega_cs = []
+
+ width = (velocity_drivetrain.robot_radius_l +
+ velocity_drivetrain.robot_radius_r)
+ Ter = numpy.matrix([[0.5, 0.5], [-1.0 / width, 1.0 / width]])
+
+ for i in xrange(len(length_plan_t)):
+ xva_plan[0, i] = length_plan_x[i][0, 0]
+ xva_plan[1, i] = length_plan_v[i]
+ xva_plan[2, i] = length_plan_a[i]
+
+ xy_r = path.xy(xva_plan[0, i])
+ x_r = xy_r[0, 0]
+ y_r = xy_r[1, 0]
+ theta_r = path.theta(xva_plan[0, i])[0]
+ vel_omega_r = numpy.matrix(
+ [[xva_plan[1, i]],
+ [path.dtheta_dt(xva_plan[0, i], xva_plan[1, i])[0]]])
+ vel_lr = numpy.linalg.inv(Ter) * vel_omega_r
+
+ state_plan[:, i] = numpy.matrix(
+ [[x_r], [y_r], [theta_r], [vel_lr[0, 0]], [vel_lr[1, 0]]])
+ u_plan[:, i] = trajectory.ff_voltage(backward_accel_plan, xva_plan[0, i])
+
+ Q = numpy.matrix(
+ numpy.diag([
+ 1.0 / (0.05**2), 1.0 / (0.05**2), 1.0 / (0.2**2), 1.0 / (0.5**2),
+ 1.0 / (0.5**2)
+ ]))
+ R = numpy.matrix(numpy.diag([1.0 / (12.0**2), 1.0 / (12.0**2)]))
+ kMinVelocity = 0.1
+
+ for i in xrange(len(length_plan_t)):
+ states[:, i] = state
+
+ theta = state[2, 0]
+ sintheta = numpy.sin(theta)
+ costheta = numpy.cos(theta)
+ linear_velocity = (state[3, 0] + state[4, 0]) / 2.0
+ if abs(linear_velocity) < kMinVelocity / 100.0:
+ linear_velocity = 0.1
+ elif abs(linear_velocity) > kMinVelocity:
+ pass
+ elif linear_velocity > 0:
+ linear_velocity = kMinVelocity
+ elif linear_velocity < 0:
+ linear_velocity = -kMinVelocity
+
+ width = (velocity_drivetrain.robot_radius_l +
+ velocity_drivetrain.robot_radius_r)
+ A_linearized_continuous = numpy.matrix([[
+ 0.0, 0.0, -sintheta * linear_velocity, 0.5 * costheta, 0.5 *
+ costheta
+ ], [
+ 0.0, 0.0, costheta * linear_velocity, 0.5 * sintheta, 0.5 *
+ sintheta
+ ], [0.0, 0.0, 0.0, -1.0 / width, 1.0 / width],
+ [0.0, 0.0, 0.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0, 0.0, 0.0]])
+ A_linearized_continuous[3:5, 3:5] = velocity_drivetrain.A_continuous
+ B_linearized_continuous = numpy.matrix(numpy.zeros((5, 2)))
+ B_linearized_continuous[3:5, :] = velocity_drivetrain.B_continuous
+
+ A, B = controls.c2d(A_linearized_continuous, B_linearized_continuous,
+ dt)
+
+ if i >= 0:
+ K = controls.dlqr(A, B, Q, R)
+ print("K", K)
+ print("eig", numpy.linalg.eig(A - B * K)[0])
+ goal_state = trajectory.goal_state(xva_plan[0, i], xva_plan[1, i])
+ state_error = goal_state - state
+
+ U = (trajectory.ff_voltage(backward_accel_plan, xva_plan[0, i]) + K *
+ (state_error))
+
+ def spline_diffeq(U, t, x):
+ velocity = x[3:5, :]
theta = x[2, 0]
linear_velocity = (velocity[0, 0] + velocity[1, 0]) / 2.0
angular_velocity = (velocity[1, 0] - velocity[0, 0]) / (
velocity_drivetrain.robot_radius_l +
velocity_drivetrain.robot_radius_r)
- U = trajectory.ff_voltage(
- backward_accel_plan,
- spline_distance + numpy.linalg.norm(x[0:2, 0] - state[0:2, 0]))
accel = (velocity_drivetrain.A_continuous * velocity +
velocity_drivetrain.B_continuous * U)
return numpy.matrix(
@@ -873,45 +947,32 @@
[numpy.sin(theta) * linear_velocity], [angular_velocity],
[accel[0, 0]], [accel[1, 0]]])
- U = trajectory.ff_voltage(backward_accel_plan, spline_distance)
- state = RungeKutta(spline_diffeq, state, i * dt, dt)
- spline_states[:, i] = spline_state
- states[:, i] = state
- Us[:, i] = U
+ full_us[:, i] = U
- spline_distances = numpy.array(
- (numpy.array(spline_states)[0, :] + numpy.array(spline_states)[2, :]) /
- 2.0)
+ state = RungeKutta(lambda t, x: spline_diffeq(U, t, x),
+ state, i * dt, dt)
+
pylab.figure()
- pylab.plot(distances, numpy.array(voltages)[0, :], label='vl')
- pylab.plot(distances, numpy.array(voltages)[1, :], label='vr')
+ pylab.plot(length_plan_t, numpy.array(xva_plan)[0, :], label='x')
+ pylab.plot(length_plan_t, [x[1, 0] for x in length_plan_x], label='v')
+ pylab.plot(length_plan_t, numpy.array(xva_plan)[1, :], label='planv')
+ pylab.plot(length_plan_t, numpy.array(xva_plan)[2, :], label='a')
+
+ pylab.plot(length_plan_t, numpy.array(full_us)[0, :], label='vl')
+ pylab.plot(length_plan_t, numpy.array(full_us)[1, :], label='vr')
pylab.legend()
pylab.figure()
pylab.plot(
+ numpy.array(states)[0, :],
+ numpy.array(states)[1, :],
+ label='robot')
+ pylab.plot(
numpy.array(spline_points)[0, :],
numpy.array(spline_points)[1, :],
label='spline')
- pylab.plot(
- numpy.array(states)[0, :], numpy.array(states)[1, :], label="robot")
pylab.legend()
- pylab.figure()
- pylab.plot(
- spline_distances, (numpy.array(states)[0, :] - numpy.array(
- path.xy(spline_distances))[0, :]) * 100.0,
- label='robotx_error * 100')
- pylab.plot(
- spline_distances, (numpy.array(states)[1, :] - numpy.array(
- path.xy(spline_distances))[1, :]) * 100.0,
- label='roboty_error * 100')
- pylab.plot(
- spline_distances, (numpy.array(states)[2, :] - numpy.array(
- path.theta(spline_distances))) * 100.0,
- label='robottheta_error * 100')
- pylab.plot(distances, numpy.array(voltages)[0, :], label='voltsl')
- pylab.plot(distances, numpy.array(voltages)[1, :], label='voltsr')
- pylab.legend()
def a(_, x):
return 2.0
diff --git a/y2016/control_loops/python/BUILD b/y2016/control_loops/python/BUILD
index 08a34e8..49393ad 100644
--- a/y2016/control_loops/python/BUILD
+++ b/y2016/control_loops/python/BUILD
@@ -38,10 +38,14 @@
"polydrivetrain.py",
],
restricted_to = ["//tools:k8"],
+ visibility = ["//visibility:public"],
deps = [
+ ":python_init",
"//external:python-gflags",
"//external:python-glog",
"//frc971/control_loops/python:controls",
+ "//frc971/control_loops/python:drivetrain",
+ "//frc971/control_loops/python:polydrivetrain",
],
)