blob: 2ce82ef2bf3e2b3c3735c4c2ff1cbb1743c5e52b [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
Austin Schuh3233d7f2024-07-27 17:51:52 -0700510 def test_steer_accel(self):
511 """Tests that applying a steer torque accelerates the steer reasonably."""
512
513 for velocity in [
514 numpy.array([[0.0], [0.0]]),
515 numpy.array([[1.0], [0.0]]),
516 numpy.array([[2.0], [0.0]])
517 ]:
518 module_angles = [0.0] * 4
519
520 X = utils.state_vector(
521 velocity=velocity,
522 omega=0.0,
523 module_angles=module_angles,
524 )
525 steer_I = numpy.array([[1.0], [0.0]] * 4)
526 xdot = self.swerve_full_dynamics(X, steer_I)
527
528 self.assertAlmostEqual(xdot[dynamics.STATE_OMEGAS0, 0],
529 1.4,
530 places=0)
531 self.assertAlmostEqual(xdot[dynamics.STATE_OMEGAS1, 0],
532 1.4,
533 places=0)
534 self.assertAlmostEqual(xdot[dynamics.STATE_OMEGAS2, 0],
535 1.4,
536 places=0)
537 self.assertAlmostEqual(xdot[dynamics.STATE_OMEGAS3, 0],
538 1.4,
539 places=0)
540
541 def test_drive_accel(self):
542 """Tests that applying a drive torque accelerates the drive reasonably."""
543
544 for velocity in [
545 numpy.array([[0.01], [0.0]]),
546 numpy.array([[1.0], [0.0]]),
547 numpy.array([[2.0], [0.0]])
548 ]:
549 module_angles = [0.0] * 4
550
551 X = utils.state_vector(
552 velocity=velocity,
553 omega=0.0,
554 module_angles=module_angles,
555 )
556 steer_I = numpy.array([[0.0], [100.0]] * 4)
557 # Since the wheels are spinning at the same speed as the ground, there is no force accelerating the robot. Which means there is not steering moment. The two physics diverge pretty heavily.
558 xdot = self.swerve_full_dynamics(X, steer_I, skip_compare=True)
559
560 self.assertGreater(xdot[dynamics.STATE_OMEGAD0, 0], 100.0)
561 self.assertGreater(xdot[dynamics.STATE_OMEGAD1, 0], 100.0)
562 self.assertGreater(xdot[dynamics.STATE_OMEGAD2, 0], 100.0)
563 self.assertGreater(xdot[dynamics.STATE_OMEGAD3, 0], 100.0)
564
565 X = utils.state_vector(
566 velocity=velocity,
567 omega=0.0,
568 module_angles=module_angles,
569 )
570 steer_I = numpy.array([[0.0], [-100.0]] * 4)
571 xdot = self.swerve_full_dynamics(X, steer_I, skip_compare=True)
572
573 self.assertLess(xdot[dynamics.STATE_OMEGAD0, 0], -100.0)
574 self.assertLess(xdot[dynamics.STATE_OMEGAD1, 0], -100.0)
575 self.assertLess(xdot[dynamics.STATE_OMEGAD2, 0], -100.0)
576 self.assertLess(xdot[dynamics.STATE_OMEGAD3, 0], -100.0)
577
Austin Schuh2a1abec2024-07-10 20:31:16 -0700578
justinT2107d41ed2024-07-31 21:12:31 -0700579if __name__ == "__main__":
Austin Schuh2a1abec2024-07-10 20:31:16 -0700580 unittest.main()