blob: 664d0aa1e396fa449b6ec00db7810f3f1fabc20c [file] [log] [blame]
Austin Schuh2a1abec2024-07-10 20:31:16 -07001#!/usr/bin/python3
2
3import numpy
4import sys, os
5import casadi
6from numpy.testing import assert_array_equal, assert_array_almost_equal
7import unittest
8
Austin Schuh27694fa2024-07-20 16:29:49 -07009from frc971.control_loops.swerve import bigcaster_dynamics
Austin Schuh2a1abec2024-07-10 20:31:16 -070010from frc971.control_loops.swerve import dynamics
Austin Schuh27694fa2024-07-20 16:29:49 -070011from frc971.control_loops.swerve import nocaster_dynamics
justinT2107d41ed2024-07-31 21:12:31 -070012from frc971.control_loops.swerve import physics_test_utils as utils
Austin Schuhb8b34be2024-07-14 16:06:19 -070013
14
Austin Schuh6927bc32024-07-14 17:24:56 -070015class TestSwervePhysics(unittest.TestCase):
Austin Schuh2a1abec2024-07-10 20:31:16 -070016 I = numpy.zeros((8, 1))
17
Austin Schuh6ea789e2024-07-27 13:45:53 -070018 def to_velocity_state(self, X):
19 return dynamics.to_velocity_state(X)
20
21 def swerve_full_dynamics(self, X, U, skip_compare=False):
22 X_velocity = self.to_velocity_state(X)
23 Xdot = self.position_swerve_full_dynamics(X, U)
24 if not skip_compare:
25 velocity_states = self.to_velocity_state(Xdot)
26 velocity_physics = self.velocity_swerve_physics(X_velocity, U)
27 self.assertLess(
28 numpy.linalg.norm(velocity_states - velocity_physics),
29 2e-2,
30 msg=
31 f'Norm failed, full physics -> {velocity_states.T}, velocity physics -> {velocity_physics}, difference -> {velocity_physics - velocity_states}',
32 )
33
34 return Xdot
35
Austin Schuh27694fa2024-07-20 16:29:49 -070036 def wrap(self, python_module):
Austin Schuh6ea789e2024-07-27 13:45:53 -070037 self.position_swerve_full_dynamics = utils.wrap(
38 python_module.swerve_full_dynamics)
39
40 evaluated_fn = python_module.velocity_swerve_physics(
41 casadi.SX.sym("X", dynamics.NUM_VELOCITY_STATES, 1),
42 casadi.SX.sym("U", 8, 1))
43 self.velocity_swerve_physics = lambda X, U: numpy.array(
44 evaluated_fn(X, U))
45
Austin Schuhb8b34be2024-07-14 16:06:19 -070046 self.contact_patch_velocity = [
justinT2107d41ed2024-07-31 21:12:31 -070047 utils.wrap_module(python_module.contact_patch_velocity, i)
Austin Schuh27694fa2024-07-20 16:29:49 -070048 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070049 ]
50 self.wheel_ground_velocity = [
justinT2107d41ed2024-07-31 21:12:31 -070051 utils.wrap_module(python_module.wheel_ground_velocity, i)
Austin Schuh27694fa2024-07-20 16:29:49 -070052 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070053 ]
54 self.wheel_slip_velocity = [
justinT2107d41ed2024-07-31 21:12:31 -070055 utils.wrap_module(python_module.wheel_slip_velocity, i)
Austin Schuh27694fa2024-07-20 16:29:49 -070056 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070057 ]
justinT2107d41ed2024-07-31 21:12:31 -070058 self.wheel_force = [
59 utils.wrap_module(python_module.wheel_force, i) for i in range(4)
60 ]
61 self.module_angular_accel = [
62 utils.wrap_module(python_module.module_angular_accel, i)
63 for i in range(4)
64 ]
65 self.F = [utils.wrap_module(python_module.F, i) for i in range(4)]
Austin Schuhb8b34be2024-07-14 16:06:19 -070066 self.mounting_location = [
justinT2107d41ed2024-07-31 21:12:31 -070067 utils.wrap_module(python_module.mounting_location, i)
68 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070069 ]
70
71 self.slip_angle = [
justinT2107d41ed2024-07-31 21:12:31 -070072 utils.wrap_module(python_module.slip_angle, i) for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070073 ]
74 self.slip_ratio = [
justinT2107d41ed2024-07-31 21:12:31 -070075 utils.wrap_module(python_module.slip_ratio, i) for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070076 ]
justinT2107d41ed2024-07-31 21:12:31 -070077 self.Ms = [utils.wrap_module(python_module.Ms, i) for i in range(4)]
Austin Schuh27694fa2024-07-20 16:29:49 -070078
79 def setUp(self):
80 self.wrap(dynamics)
Austin Schuh2a1abec2024-07-10 20:31:16 -070081
82 def test_contact_patch_velocity(self):
83 """Tests that the contact patch velocity makes sense."""
84 for i in range(4):
justinT2107d41ed2024-07-31 21:12:31 -070085 contact_patch_velocity = self.contact_patch_velocity[i]
86 wheel_ground_velocity = self.wheel_ground_velocity[i]
Austin Schuh2a1abec2024-07-10 20:31:16 -070087
88 # No angular velocity should result in just linear motion.
89 for velocity in [
90 numpy.array([[1.5], [0.0]]),
91 numpy.array([[0.0], [1.0]]),
92 numpy.array([[-1.5], [0.0]]),
93 numpy.array([[0.0], [-1.0]]),
94 numpy.array([[2.0], [-1.7]]),
95 ]:
96 for theta in [0.0, 1.0, -1.0, 100.0]:
Austin Schuhb8b34be2024-07-14 16:06:19 -070097 patch_velocity = contact_patch_velocity(
justinT2107d41ed2024-07-31 21:12:31 -070098 utils.state_vector(velocity=velocity, theta=theta),
99 self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700100
101 assert_array_equal(patch_velocity, velocity)
102
103 # Now, test that spinning the robot results in module velocities.
104 # We are assuming that each module is on a square robot.
105 module_center_of_mass_angle = i * numpy.pi / 2.0 + numpy.pi / 4.0
106 for theta in [-module_center_of_mass_angle, 0.0, 1.0, -1.0, 100.0]:
107 for omega in [0.65, -0.1]:
108 # Point each module to the center to make the math easier.
Austin Schuhb8b34be2024-07-14 16:06:19 -0700109 patch_velocity = contact_patch_velocity(
justinT2107d41ed2024-07-31 21:12:31 -0700110 utils.state_vector(
111 velocity=numpy.array([[0.0], [0.0]]),
112 theta=theta,
113 omega=omega,
114 module_angle=module_center_of_mass_angle,
115 ),
116 self.I,
117 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700118
119 assert_array_almost_equal(
120 patch_velocity,
Austin Schuh6927bc32024-07-14 17:24:56 -0700121 (dynamics.ROBOT_WIDTH / numpy.sqrt(2.0) -
justinT2107d41ed2024-07-31 21:12:31 -0700122 dynamics.CASTER) * omega * numpy.array([
123 [-numpy.sin(theta + module_center_of_mass_angle)],
124 [numpy.cos(theta + module_center_of_mass_angle)],
125 ]),
126 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700127
128 # Point the wheel along +x, rotate it by theta, then spin it.
129 # Confirm the velocities come out right.
Austin Schuhb8b34be2024-07-14 16:06:19 -0700130 patch_velocity = contact_patch_velocity(
justinT2107d41ed2024-07-31 21:12:31 -0700131 utils.state_vector(
Austin Schuhb8b34be2024-07-14 16:06:19 -0700132 velocity=numpy.array([[0.0], [0.0]]),
133 theta=-module_center_of_mass_angle,
134 module_omega=omega,
justinT2107d41ed2024-07-31 21:12:31 -0700135 module_angle=(theta + module_center_of_mass_angle),
136 ),
137 self.I,
138 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700139
140 assert_array_almost_equal(
justinT2107d41ed2024-07-31 21:12:31 -0700141 patch_velocity,
142 -dynamics.CASTER * omega *
143 numpy.array([[-numpy.sin(theta)], [numpy.cos(theta)]]),
144 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700145
146 # Now, test that the rotation back to wheel coordinates works.
147 # The easiest way to do this is to point the wheel in a direction,
148 # move in that direction, and confirm that there is no lateral velocity.
149 for robot_angle in [0.0, 1.0, -5.0]:
150 for module_angle in [0.0, 1.0, -5.0]:
151 wheel_patch_velocity = numpy.array(
152 wheel_ground_velocity(
justinT2107d41ed2024-07-31 21:12:31 -0700153 utils.state_vector(
154 velocity=numpy.array([
155 [numpy.cos(robot_angle + module_angle)],
156 [numpy.sin(robot_angle + module_angle)],
157 ]),
158 theta=robot_angle,
159 module_angle=module_angle,
160 ),
161 self.I,
162 ))
Austin Schuh2a1abec2024-07-10 20:31:16 -0700163
164 assert_array_almost_equal(wheel_patch_velocity,
165 numpy.array([[1], [0]]))
166
167 def test_slip_angle(self):
168 """Tests that the slip_angle calculation works."""
169 velocity = numpy.array([[1.5], [0.0]])
170
171 for i in range(4):
Austin Schuh27694fa2024-07-20 16:29:49 -0700172 for wrap in range(-1, 2):
Austin Schuh2a1abec2024-07-10 20:31:16 -0700173 for theta in [0.0, 0.6, -0.4]:
174 module_angle = numpy.pi * wrap + theta
175
Austin Schuh27694fa2024-07-20 16:29:49 -0700176 # We have redefined the angle to be the sin of the angle.
177 # That way, when the module flips directions, the slip angle also flips
178 # directions to keep it stable.
justinT2107d41ed2024-07-31 21:12:31 -0700179 computed_angle = self.slip_angle[i](
180 utils.state_vector(velocity=velocity,
181 module_angle=numpy.pi * wrap +
182 theta),
183 self.I,
184 )[0, 0]
Austin Schuh2a1abec2024-07-10 20:31:16 -0700185
Austin Schuh27694fa2024-07-20 16:29:49 -0700186 expected = numpy.sin(numpy.pi * wrap + theta)
187
188 self.assertAlmostEqual(
189 expected,
190 computed_angle,
justinT2107d41ed2024-07-31 21:12:31 -0700191 msg=f"Trying wrap {wrap} theta {theta}",
192 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700193
Austin Schuhb8b34be2024-07-14 16:06:19 -0700194 def test_wheel_torque(self):
195 """Tests that the per module self aligning forces have the right signs."""
Austin Schuh27694fa2024-07-20 16:29:49 -0700196 # Point all the modules in a little bit.
justinT2107d41ed2024-07-31 21:12:31 -0700197 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700198 velocity=numpy.array([[1.0], [0.0]]),
199 module_angles=[-0.001, -0.001, 0.001, 0.001],
200 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700201 xdot_equal = self.swerve_full_dynamics(X, self.I)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700202
203 self.assertGreater(xdot_equal[2, 0], 0.0)
204 self.assertAlmostEqual(xdot_equal[3, 0], 0.0, places=1)
205 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
206
207 self.assertGreater(xdot_equal[6, 0], 0.0)
208 self.assertAlmostEqual(xdot_equal[7, 0], 0.0, places=1)
209 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
210
211 self.assertLess(xdot_equal[10, 0], 0.0)
212 self.assertAlmostEqual(xdot_equal[11, 0], 0.0, places=1)
213 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
214
215 self.assertLess(xdot_equal[14, 0], 0.0)
216 self.assertAlmostEqual(xdot_equal[15, 0], 0.0, places=1)
217 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
218
219 # Shouldn't be spinning.
220 self.assertAlmostEqual(xdot_equal[21, 0], 0.0, places=2)
221
Austin Schuh27694fa2024-07-20 16:29:49 -0700222 # Now, make the bot want to go left by going to the other side.
Austin Schuhb8b34be2024-07-14 16:06:19 -0700223 # The wheels will be going too fast based on our calcs, so they should be decelerating.
justinT2107d41ed2024-07-31 21:12:31 -0700224 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700225 velocity=numpy.array([[1.0], [0.0]]),
226 module_angles=[0.01, 0.01, 0.01, 0.01],
227 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700228 xdot_left = self.swerve_full_dynamics(X, self.I)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700229
Austin Schuh27694fa2024-07-20 16:29:49 -0700230 self.assertLess(xdot_left[2, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700231 self.assertLess(xdot_left[3, 0], 0.0)
232 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
233
Austin Schuh27694fa2024-07-20 16:29:49 -0700234 self.assertLess(xdot_left[6, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700235 self.assertLess(xdot_left[7, 0], 0.0)
236 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
237
Austin Schuh27694fa2024-07-20 16:29:49 -0700238 self.assertLess(xdot_left[10, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700239 self.assertLess(xdot_left[11, 0], 0.0)
240 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
241
Austin Schuh27694fa2024-07-20 16:29:49 -0700242 self.assertLess(xdot_left[14, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700243 self.assertLess(xdot_left[15, 0], 0.0)
244 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
245
246 self.assertGreater(xdot_left[19, 0], 0.0001)
Austin Schuh27694fa2024-07-20 16:29:49 -0700247 self.assertGreater(xdot_left[20, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700248 # Shouldn't be spinning.
249 self.assertAlmostEqual(xdot_left[21, 0], 0.0)
250
251 # And now do it to the right too.
justinT2107d41ed2024-07-31 21:12:31 -0700252 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700253 velocity=numpy.array([[1.0], [0.0]]),
254 module_angles=[-0.01, -0.01, -0.01, -0.01],
255 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700256 xdot_right = self.swerve_full_dynamics(X, self.I)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700257
Austin Schuh27694fa2024-07-20 16:29:49 -0700258 self.assertGreater(xdot_right[2, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700259 self.assertLess(xdot_right[3, 0], 0.0)
260 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
261
Austin Schuh27694fa2024-07-20 16:29:49 -0700262 self.assertGreater(xdot_right[6, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700263 self.assertLess(xdot_right[7, 0], 0.0)
264 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
265
Austin Schuh27694fa2024-07-20 16:29:49 -0700266 self.assertGreater(xdot_right[10, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700267 self.assertLess(xdot_right[11, 0], 0.0)
268 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
269
Austin Schuh27694fa2024-07-20 16:29:49 -0700270 self.assertGreater(xdot_right[14, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700271 self.assertLess(xdot_right[15, 0], 0.0)
272 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
273
274 self.assertGreater(xdot_right[19, 0], 0.0001)
Austin Schuh27694fa2024-07-20 16:29:49 -0700275 self.assertLess(xdot_right[20, 0], -0.05)
276 # Shouldn't be spinning.
277 self.assertAlmostEqual(xdot_right[21, 0], 0.0)
278
279 def test_wheel_torque_backwards_nocaster(self):
280 """Tests that the per module self aligning forces have the right signs when going backwards."""
281 self.wrap(nocaster_dynamics)
282 # Point all the modules in a little bit, going backwards.
justinT2107d41ed2024-07-31 21:12:31 -0700283 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700284 velocity=numpy.array([[1.0], [0.0]]),
285 module_angles=[
286 numpy.pi - 0.001,
287 numpy.pi - 0.001,
288 numpy.pi + 0.001,
289 numpy.pi + 0.001,
290 ],
291 drive_wheel_velocity=-1,
292 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700293 xdot_equal = self.swerve_full_dynamics(X, self.I)
Austin Schuh27694fa2024-07-20 16:29:49 -0700294
295 self.assertGreater(xdot_equal[2, 0], 0.0, msg="Steering backwards")
296 self.assertAlmostEqual(xdot_equal[3, 0], 0.0, places=1)
297 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
298
299 self.assertGreater(xdot_equal[6, 0], 0.0, msg="Steering backwards")
300 self.assertAlmostEqual(xdot_equal[7, 0], 0.0, places=1)
301 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
302
303 self.assertLess(xdot_equal[10, 0], 0.0, msg="Steering backwards")
304 self.assertAlmostEqual(xdot_equal[11, 0], 0.0, places=1)
305 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
306
307 self.assertLess(xdot_equal[14, 0], 0.0, msg="Steering backwards")
308 self.assertAlmostEqual(xdot_equal[15, 0], 0.0, places=1)
309 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
310
311 # Shouldn't be spinning.
312 self.assertAlmostEqual(xdot_equal[21, 0], 0.0, places=2)
313
314 # Now, make the bot want to go left by going to the other side.
315 # The wheels will be going too fast based on our calcs, so they should be decelerating.
justinT2107d41ed2024-07-31 21:12:31 -0700316 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700317 velocity=numpy.array([[1.0], [0.0]]),
318 module_angles=[numpy.pi + 0.01] * 4,
319 drive_wheel_velocity=-1,
320 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700321 xdot_left = self.swerve_full_dynamics(X, self.I)
Austin Schuh27694fa2024-07-20 16:29:49 -0700322
323 self.assertLess(xdot_left[2, 0], -0.05)
324 self.assertGreater(xdot_left[3, 0], 0.0)
325 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
326
327 self.assertLess(xdot_left[6, 0], -0.05)
328 self.assertGreater(xdot_left[7, 0], 0.0)
329 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
330
331 self.assertLess(xdot_left[10, 0], -0.05)
332 self.assertGreater(xdot_left[11, 0], 0.0)
333 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
334
335 self.assertLess(xdot_left[14, 0], -0.05)
336 self.assertGreater(xdot_left[15, 0], 0.0)
337 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
338
339 self.assertGreater(xdot_left[19, 0], 0.0001)
340 self.assertGreater(xdot_left[20, 0], 0.05)
341 # Shouldn't be spinning.
342 self.assertAlmostEqual(xdot_left[21, 0], 0.0)
343
344 # And now do it to the right too.
justinT2107d41ed2024-07-31 21:12:31 -0700345 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700346 velocity=numpy.array([[1.0], [0.0]]),
347 drive_wheel_velocity=-1,
348 module_angles=[-0.01 + numpy.pi] * 4,
349 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700350 xdot_right = self.swerve_full_dynamics(X, self.I)
Austin Schuh27694fa2024-07-20 16:29:49 -0700351
352 self.assertGreater(xdot_right[2, 0], 0.05)
353 self.assertGreater(xdot_right[3, 0], 0.0)
354 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
355
356 self.assertGreater(xdot_right[6, 0], 0.05)
357 self.assertGreater(xdot_right[7, 0], 0.0)
358 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
359
360 self.assertGreater(xdot_right[10, 0], 0.05)
361 self.assertGreater(xdot_right[11, 0], 0.0)
362 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
363
364 self.assertGreater(xdot_right[14, 0], 0.05)
365 self.assertGreater(xdot_right[15, 0], 0.0)
366 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
367
368 self.assertGreater(xdot_right[19, 0], 0.0001)
369 self.assertLess(xdot_right[20, 0], -0.05)
370 # Shouldn't be spinning.
371 self.assertAlmostEqual(xdot_right[21, 0], 0.0)
372
373 def test_wheel_torque_backwards_caster(self):
374 """Tests that the per module self aligning forces have the right signs when going backwards with a lot of caster."""
375 self.wrap(bigcaster_dynamics)
376 # Point all the modules in a little bit, going backwards.
justinT2107d41ed2024-07-31 21:12:31 -0700377 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700378 velocity=numpy.array([[1.0], [0.0]]),
379 module_angles=[
380 numpy.pi - 0.001,
381 numpy.pi - 0.001,
382 numpy.pi + 0.001,
383 numpy.pi + 0.001,
384 ],
385 drive_wheel_velocity=-1,
386 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700387 xdot_equal = self.swerve_full_dynamics(X, self.I)
Austin Schuh27694fa2024-07-20 16:29:49 -0700388
389 self.assertLess(xdot_equal[2, 0], 0.0, msg="Steering backwards")
390 self.assertAlmostEqual(xdot_equal[3, 0], 0.0, places=1)
391 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
392
393 self.assertLess(xdot_equal[6, 0], 0.0, msg="Steering backwards")
394 self.assertAlmostEqual(xdot_equal[7, 0], 0.0, places=1)
395 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
396
397 self.assertGreater(xdot_equal[10, 0], 0.0, msg="Steering backwards")
398 self.assertAlmostEqual(xdot_equal[11, 0], 0.0, places=1)
399 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
400
401 self.assertGreater(xdot_equal[14, 0], 0.0, msg="Steering backwards")
402 self.assertAlmostEqual(xdot_equal[15, 0], 0.0, places=1)
403 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
404
405 # Shouldn't be spinning.
406 self.assertAlmostEqual(xdot_equal[21, 0], 0.0, places=2)
407
408 # Now, make the bot want to go left by going to the other side.
409 # The wheels will be going too fast based on our calcs, so they should be decelerating.
justinT2107d41ed2024-07-31 21:12:31 -0700410 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700411 velocity=numpy.array([[1.0], [0.0]]),
412 module_angles=[numpy.pi + 0.01] * 4,
413 drive_wheel_velocity=-1,
414 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700415 xdot_left = self.swerve_full_dynamics(X, self.I)
Austin Schuh27694fa2024-07-20 16:29:49 -0700416
417 self.assertGreater(xdot_left[2, 0], -0.05)
418 self.assertGreater(xdot_left[3, 0], 0.0)
419 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
420
421 self.assertGreater(xdot_left[6, 0], -0.05)
422 self.assertGreater(xdot_left[7, 0], 0.0)
423 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
424
425 self.assertGreater(xdot_left[10, 0], -0.05)
426 self.assertGreater(xdot_left[11, 0], 0.0)
427 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
428
429 self.assertGreater(xdot_left[14, 0], -0.05)
430 self.assertGreater(xdot_left[15, 0], 0.0)
431 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
432
433 self.assertGreater(xdot_left[19, 0], 0.0001)
434 self.assertGreater(xdot_left[20, 0], 0.05)
435 # Shouldn't be spinning.
436 self.assertAlmostEqual(xdot_left[21, 0], 0.0)
437
438 # And now do it to the right too.
justinT2107d41ed2024-07-31 21:12:31 -0700439 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700440 velocity=numpy.array([[1.0], [0.0]]),
441 drive_wheel_velocity=-1,
442 module_angles=[-0.01 + numpy.pi] * 4,
443 )
Austin Schuh6ea789e2024-07-27 13:45:53 -0700444 xdot_right = self.swerve_full_dynamics(X, self.I)
Austin Schuh27694fa2024-07-20 16:29:49 -0700445
446 self.assertLess(xdot_right[2, 0], 0.05)
447 self.assertGreater(xdot_right[3, 0], 0.0)
448 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
449
450 self.assertLess(xdot_right[6, 0], 0.05)
451 self.assertGreater(xdot_right[7, 0], 0.0)
452 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
453
454 self.assertLess(xdot_right[10, 0], 0.05)
455 self.assertGreater(xdot_right[11, 0], 0.0)
456 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
457
458 self.assertLess(xdot_right[14, 0], 0.05)
459 self.assertGreater(xdot_right[15, 0], 0.0)
460 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
461
462 self.assertGreater(xdot_right[19, 0], 0.0001)
463 self.assertLess(xdot_right[20, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700464 # Shouldn't be spinning.
465 self.assertAlmostEqual(xdot_right[21, 0], 0.0)
466
Austin Schuh2a1abec2024-07-10 20:31:16 -0700467 def test_wheel_forces(self):
468 """Tests that the per module forces have the right signs."""
469 for i in range(4):
justinT2107d41ed2024-07-31 21:12:31 -0700470 wheel_force = self.wheel_force[i]
Austin Schuhb8b34be2024-07-14 16:06:19 -0700471
justinT2107d41ed2024-07-31 21:12:31 -0700472 X = utils.state_vector()
Austin Schuhb8b34be2024-07-14 16:06:19 -0700473 robot_equal = wheel_force(X, self.I)
Austin Schuh6ea789e2024-07-27 13:45:53 -0700474 xdot_equal = self.swerve_full_dynamics(X, self.I)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700475 self.assertEqual(robot_equal[0, 0], 0.0)
476 self.assertEqual(robot_equal[1, 0], 0.0)
477 self.assertEqual(xdot_equal[2 + 4 * i], 0.0)
478 self.assertEqual(xdot_equal[3 + 4 * i], 0.0)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700479
480 # Robot is moving faster than the wheels, it should decelerate.
justinT2107d41ed2024-07-31 21:12:31 -0700481 X = utils.state_vector(dx=0.01)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700482 robot_faster = wheel_force(X, self.I)
Austin Schuh6ea789e2024-07-27 13:45:53 -0700483 xdot_faster = self.swerve_full_dynamics(X,
484 self.I,
485 skip_compare=True)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700486 self.assertLess(robot_faster[0, 0], -0.1)
487 self.assertEqual(robot_faster[1, 0], 0.0)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700488 self.assertGreater(xdot_faster[3 + 4 * i], 0.0)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700489
490 # Robot is now going slower than the wheels. It should accelerate.
justinT2107d41ed2024-07-31 21:12:31 -0700491 X = utils.state_vector(dx=-0.01)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700492 robot_slower = wheel_force(X, self.I)
Austin Schuh6ea789e2024-07-27 13:45:53 -0700493 xdot_slower = self.swerve_full_dynamics(X,
494 self.I,
495 skip_compare=True)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700496 self.assertGreater(robot_slower[0, 0], 0.1)
497 self.assertEqual(robot_slower[1, 0], 0.0)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700498 self.assertLess(xdot_slower[3 + 4 * i], 0.0)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700499
500 # Positive lateral velocity -> negative force.
justinT2107d41ed2024-07-31 21:12:31 -0700501 robot_left = wheel_force(utils.state_vector(dy=0.01), self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700502 self.assertEqual(robot_left[0, 0], 0.0)
503 self.assertLess(robot_left[1, 0], -0.1)
504
505 # Negative lateral velocity -> positive force.
justinT2107d41ed2024-07-31 21:12:31 -0700506 robot_right = wheel_force(utils.state_vector(dy=-0.01), self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700507 self.assertEqual(robot_right[0, 0], 0.0)
508 self.assertGreater(robot_right[1, 0], 0.1)
509
510
justinT2107d41ed2024-07-31 21:12:31 -0700511if __name__ == "__main__":
Austin Schuh2a1abec2024-07-10 20:31:16 -0700512 unittest.main()