blob: 428c29d23e8263d558bbaf6942f4822b3bc75e35 [file] [log] [blame]
Austin Schuh048fb602013-10-07 23:31:04 -07001#!/usr/bin/python
2
3import numpy
4import sys
5import polytope
6import drivetrain
7import controls
8from matplotlib import pylab
9
10__author__ = 'Austin Schuh (austin.linux@gmail.com)'
11
12
13def CoerceGoal(region, K, w, R):
14 """Intersects a line with a region, and finds the closest point to R.
15
16 Finds a point that is closest to R inside the region, and on the line
17 defined by K X = w. If it is not possible to find a point on the line,
18 finds a point that is inside the region and closest to the line. This
19 function assumes that
20
21 Args:
22 region: HPolytope, the valid goal region.
23 K: numpy.matrix (2 x 1), the matrix for the equation [K1, K2] [x1; x2] = w
24 w: float, the offset in the equation above.
25 R: numpy.matrix (2 x 1), the point to be closest to.
26
27 Returns:
28 numpy.matrix (2 x 1), the point.
29 """
30
31 if region.IsInside(R):
32 return R
33
34 perpendicular_vector = K.T / numpy.linalg.norm(K)
35 parallel_vector = numpy.matrix([[perpendicular_vector[1, 0]],
36 [-perpendicular_vector[0, 0]]])
37
38 # We want to impose the constraint K * X = w on the polytope H * X <= k.
39 # We do this by breaking X up into parallel and perpendicular components to
40 # the half plane. This gives us the following equation.
41 #
42 # parallel * (parallel.T \dot X) + perpendicular * (perpendicular \dot X)) = X
43 #
44 # Then, substitute this into the polytope.
45 #
46 # H * (parallel * (parallel.T \dot X) + perpendicular * (perpendicular \dot X)) <= k
47 #
48 # Substitute K * X = w
49 #
50 # H * parallel * (parallel.T \dot X) + H * perpendicular * w <= k
51 #
52 # Move all the knowns to the right side.
53 #
54 # H * parallel * ([parallel1 parallel2] * X) <= k - H * perpendicular * w
55 #
56 # Let t = parallel.T \dot X, the component parallel to the surface.
57 #
58 # H * parallel * t <= k - H * perpendicular * w
59 #
60 # This is a polytope which we can solve, and use to figure out the range of X
61 # that we care about!
62
63 t_poly = polytope.HPolytope(
64 region.H * parallel_vector,
65 region.k - region.H * perpendicular_vector * w)
66
67 vertices = t_poly.Vertices()
68
69 if vertices.shape[0]:
70 # The region exists!
71 # Find the closest vertex
72 min_distance = numpy.infty
73 closest_point = None
74 for vertex in vertices:
75 point = parallel_vector * vertex + perpendicular_vector * w
76 length = numpy.linalg.norm(R - point)
77 if length < min_distance:
78 min_distance = length
79 closest_point = point
80
81 return closest_point
82 else:
83 # Find the vertex of the space that is closest to the line.
84 region_vertices = region.Vertices()
85 min_distance = numpy.infty
86 closest_point = None
87 for vertex in region_vertices:
88 point = vertex.T
89 length = numpy.abs((perpendicular_vector.T * point)[0, 0])
90 if length < min_distance:
91 min_distance = length
92 closest_point = point
93
94 return closest_point
95
96
97class VelocityDrivetrain(object):
98 def __init__(self):
99 self.drivetrain = drivetrain.Drivetrain()
100
101 # X is [lvel, rvel]
102 self.X = numpy.matrix(
103 [[0.0],
104 [0.0]])
105
106 self.A = numpy.matrix(
107 [[self.drivetrain.A[1, 1], self.drivetrain.A[1, 3]],
108 [self.drivetrain.A[3, 1], self.drivetrain.A[3, 3]]])
109
110 self.B = numpy.matrix(
111 [[self.drivetrain.B[1, 0], self.drivetrain.B[1, 1]],
112 [self.drivetrain.B[3, 0], self.drivetrain.B[3, 1]]])
113
114 # FF * X = U (steady state)
115 self.FF = self.B.I * (numpy.eye(2) - self.A)
116
117 self.U_poly = polytope.HPolytope(
118 numpy.matrix([[1, 0],
119 [-1, 0],
120 [0, 1],
121 [0, -1]]),
122 numpy.matrix([[12],
123 [12],
124 [12],
125 [12]]))
126
127 self.U_max = numpy.matrix(
128 [[12.0],
129 [12.0]])
130 self.U_min = numpy.matrix(
131 [[-12.0000000000],
132 [-12.0000000000]])
133
134 self.K = controls.dplace(self.A, self.B, [0.3, 0.3])
135
136 self.dt = 0.01
137
138 self.R = numpy.matrix(
139 [[0.0],
140 [0.0]])
141
142 self.vmax = (
143 self.U_max[0, 0] * self.B[0, :].sum() / (1 - self.A[0, :].sum()))
144
145 self.xfiltered = 0.0
146
147 # U = self.K[0, :].sum() * (R - xfiltered) + self.FF[0, :].sum() * R
148 # throttle * 12.0 = (self.K[0, :].sum() + self.FF[0, :].sum()) * R
149 # - self.K[0, :].sum() * xfiltered
150
151 # R = (throttle * 12.0 + self.K[0, :].sum() * xfiltered) /
152 # (self.K[0, :].sum() + self.FF[0, :].sum())
153
154 # U = (K + FF) * R - K * X
155 # (K + FF) ^-1 * (U + K * X) = R
156
157 # (K + FF) ^-1 * (throttle * 12.0 + K * throttle * self.vmax) = R
158 # Xn+1 = A * X + B * (throttle * 12.0)
159
160 # xfiltered = self.A[0, :].sum() + B[0, :].sum() * throttle * 12.0
161 self.ttrust = 1.1
162
163 def Update(self, throttle, steering):
164 # Invert the plant to figure out how the velocity filter would have to work
165 # out in order to filter out the forwards negative inertia.
166 # This math assumes that the left and right power and velocity are equals,
167 # and that the plant is the same on the left and right.
168 # TODO(aschuh): Prove that this is right again and figure out what ttrust
169 # means...
170 fvel = ((throttle * 12.0 + self.ttrust * self.K[0, :].sum() * self.xfiltered)
171 / (self.ttrust * self.K[0, :].sum() + self.FF[0, :].sum()))
172 self.xfiltered = (self.A[0, :].sum() * self.xfiltered +
173 self.B[0, :].sum() * throttle * 12.0)
174
175 fvel = 12 / self.FF[0, :].sum() * throttle
176
177 # Constant radius means that angualar_velocity / linear_velocity = constant.
178 # Compute the left and right velocities.
179 left_velocity = fvel - steering * numpy.abs(fvel)
180 right_velocity = fvel + steering * numpy.abs(fvel)
181
182 # Write this constraint in the form of K * R = w
183 # angular velocity / linear velocity = constant
184 # (left - right) / (left + right) = constant
185 # left - right = constant * left + constant * right
186
187 # (fvel - steering * numpy.abs(fvel) - fvel - steering * numpy.abs(fvel)) /
188 # (fvel - steering * numpy.abs(fvel) + fvel + steering * numpy.abs(fvel)) =
189 # constant
190 # (- 2 * steering * numpy.abs(fvel)) / (2 * fvel) = constant
191 # (-steering * sign(fvel)) = constant
192 # (-steering * sign(fvel)) * (left + right) = left - right
193 # (steering * sign(fvel) + 1) * left + (steering * sign(fvel) - 1) * right = 0
194
195 equality_k = numpy.matrix(
196 [[1 + steering * numpy.sign(fvel), -(1 - steering * numpy.sign(fvel))]])
197 equality_w = 0.0
198
199 self.R[0, 0] = left_velocity
200 self.R[1, 0] = right_velocity
201
202 # Construct a constraint on R by manipulating the constraint on U
203 # Start out with H * U <= k
204 # U = FF * R + K * (R - X)
205 # H * (FF * R + K * R - K * X) <= k
206 # H * (FF + K) * R <= k + H * K * X
207 R_poly = polytope.HPolytope(
208 self.U_poly.H * (self.K + self.FF),
209 self.U_poly.k + self.U_poly.H * self.K * self.X)
210
211 # Limit R back inside the box.
212 self.boxed_R = CoerceGoal(R_poly, equality_k, equality_w, self.R)
213
214 FF_volts = self.FF * self.boxed_R
215 self.U_ideal = self.K * (self.boxed_R - self.X) + FF_volts
216
217 # Verify that the steering angle has not flipped.
218 assert((self.boxed_R[1, 0] - self.boxed_R[0, 0]) * steering >= 0)
219
220 self.U = numpy.clip(self.U_ideal, self.U_min, self.U_max)
221 self.X = self.A * self.X + self.B * self.U
222
223
224def main(argv):
225 drivetrain = VelocityDrivetrain()
226
227 vl_plot = []
228 vr_plot = []
229 ul_plot = []
230 ur_plot = []
231 radius_plot = []
232 t_plot = []
233 for t in numpy.arange(0, 1.5, drivetrain.dt):
234 if t < 0.5:
235 drivetrain.Update(throttle=0.60, steering=0.3)
236 elif t < 1.0:
237 drivetrain.Update(throttle=0.60, steering=-0.3)
238 else:
239 drivetrain.Update(throttle=0.00, steering=0.3)
240 t_plot.append(t)
241 vl_plot.append(drivetrain.X[0, 0])
242 vr_plot.append(drivetrain.X[1, 0])
243 ul_plot.append(drivetrain.U[0, 0])
244 ur_plot.append(drivetrain.U[1, 0])
245
246 fwd_velocity = (drivetrain.X[1, 0] + drivetrain.X[0, 0]) / 2
247 turn_velocity = (drivetrain.X[1, 0] - drivetrain.X[0, 0])
248 if fwd_velocity < 0.0000001:
249 radius_plot.append(turn_velocity)
250 else:
251 radius_plot.append(turn_velocity / fwd_velocity)
252
253 pylab.plot(t_plot, vl_plot, label='left velocity')
254 pylab.plot(t_plot, vr_plot, label='right velocity')
255 pylab.plot(t_plot, ul_plot, label='left power')
256 pylab.plot(t_plot, ur_plot, label='right power')
257 pylab.plot(t_plot, radius_plot, label='radius')
258 pylab.legend()
259 pylab.show()
260 return 0
261
262if __name__ == '__main__':
263 sys.exit(main(sys.argv))