blob: c7d60e9a948044b336ab183e097a4473efc2e7af [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 Schuh27694fa2024-07-20 16:29:49 -070018 def wrap(self, python_module):
justinT2107d41ed2024-07-31 21:12:31 -070019 self.swerve_physics = utils.wrap(python_module.swerve_physics)
Austin Schuhb8b34be2024-07-14 16:06:19 -070020 self.contact_patch_velocity = [
justinT2107d41ed2024-07-31 21:12:31 -070021 utils.wrap_module(python_module.contact_patch_velocity, i)
Austin Schuh27694fa2024-07-20 16:29:49 -070022 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070023 ]
24 self.wheel_ground_velocity = [
justinT2107d41ed2024-07-31 21:12:31 -070025 utils.wrap_module(python_module.wheel_ground_velocity, i)
Austin Schuh27694fa2024-07-20 16:29:49 -070026 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070027 ]
28 self.wheel_slip_velocity = [
justinT2107d41ed2024-07-31 21:12:31 -070029 utils.wrap_module(python_module.wheel_slip_velocity, i)
Austin Schuh27694fa2024-07-20 16:29:49 -070030 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070031 ]
justinT2107d41ed2024-07-31 21:12:31 -070032 self.wheel_force = [
33 utils.wrap_module(python_module.wheel_force, i) for i in range(4)
34 ]
35 self.module_angular_accel = [
36 utils.wrap_module(python_module.module_angular_accel, i)
37 for i in range(4)
38 ]
39 self.F = [utils.wrap_module(python_module.F, i) for i in range(4)]
Austin Schuhb8b34be2024-07-14 16:06:19 -070040 self.mounting_location = [
justinT2107d41ed2024-07-31 21:12:31 -070041 utils.wrap_module(python_module.mounting_location, i)
42 for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070043 ]
44
45 self.slip_angle = [
justinT2107d41ed2024-07-31 21:12:31 -070046 utils.wrap_module(python_module.slip_angle, i) for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070047 ]
48 self.slip_ratio = [
justinT2107d41ed2024-07-31 21:12:31 -070049 utils.wrap_module(python_module.slip_ratio, i) for i in range(4)
Austin Schuhb8b34be2024-07-14 16:06:19 -070050 ]
justinT2107d41ed2024-07-31 21:12:31 -070051 self.Ms = [utils.wrap_module(python_module.Ms, i) for i in range(4)]
Austin Schuh27694fa2024-07-20 16:29:49 -070052
53 def setUp(self):
54 self.wrap(dynamics)
Austin Schuh2a1abec2024-07-10 20:31:16 -070055
56 def test_contact_patch_velocity(self):
57 """Tests that the contact patch velocity makes sense."""
58 for i in range(4):
justinT2107d41ed2024-07-31 21:12:31 -070059 contact_patch_velocity = self.contact_patch_velocity[i]
60 wheel_ground_velocity = self.wheel_ground_velocity[i]
Austin Schuh2a1abec2024-07-10 20:31:16 -070061
62 # No angular velocity should result in just linear motion.
63 for velocity in [
64 numpy.array([[1.5], [0.0]]),
65 numpy.array([[0.0], [1.0]]),
66 numpy.array([[-1.5], [0.0]]),
67 numpy.array([[0.0], [-1.0]]),
68 numpy.array([[2.0], [-1.7]]),
69 ]:
70 for theta in [0.0, 1.0, -1.0, 100.0]:
Austin Schuhb8b34be2024-07-14 16:06:19 -070071 patch_velocity = contact_patch_velocity(
justinT2107d41ed2024-07-31 21:12:31 -070072 utils.state_vector(velocity=velocity, theta=theta),
73 self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -070074
75 assert_array_equal(patch_velocity, velocity)
76
77 # Now, test that spinning the robot results in module velocities.
78 # We are assuming that each module is on a square robot.
79 module_center_of_mass_angle = i * numpy.pi / 2.0 + numpy.pi / 4.0
80 for theta in [-module_center_of_mass_angle, 0.0, 1.0, -1.0, 100.0]:
81 for omega in [0.65, -0.1]:
82 # Point each module to the center to make the math easier.
Austin Schuhb8b34be2024-07-14 16:06:19 -070083 patch_velocity = contact_patch_velocity(
justinT2107d41ed2024-07-31 21:12:31 -070084 utils.state_vector(
85 velocity=numpy.array([[0.0], [0.0]]),
86 theta=theta,
87 omega=omega,
88 module_angle=module_center_of_mass_angle,
89 ),
90 self.I,
91 )
Austin Schuh2a1abec2024-07-10 20:31:16 -070092
93 assert_array_almost_equal(
94 patch_velocity,
Austin Schuh6927bc32024-07-14 17:24:56 -070095 (dynamics.ROBOT_WIDTH / numpy.sqrt(2.0) -
justinT2107d41ed2024-07-31 21:12:31 -070096 dynamics.CASTER) * omega * numpy.array([
97 [-numpy.sin(theta + module_center_of_mass_angle)],
98 [numpy.cos(theta + module_center_of_mass_angle)],
99 ]),
100 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700101
102 # Point the wheel along +x, rotate it by theta, then spin it.
103 # Confirm the velocities come out right.
Austin Schuhb8b34be2024-07-14 16:06:19 -0700104 patch_velocity = contact_patch_velocity(
justinT2107d41ed2024-07-31 21:12:31 -0700105 utils.state_vector(
Austin Schuhb8b34be2024-07-14 16:06:19 -0700106 velocity=numpy.array([[0.0], [0.0]]),
107 theta=-module_center_of_mass_angle,
108 module_omega=omega,
justinT2107d41ed2024-07-31 21:12:31 -0700109 module_angle=(theta + module_center_of_mass_angle),
110 ),
111 self.I,
112 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700113
114 assert_array_almost_equal(
justinT2107d41ed2024-07-31 21:12:31 -0700115 patch_velocity,
116 -dynamics.CASTER * omega *
117 numpy.array([[-numpy.sin(theta)], [numpy.cos(theta)]]),
118 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700119
120 # Now, test that the rotation back to wheel coordinates works.
121 # The easiest way to do this is to point the wheel in a direction,
122 # move in that direction, and confirm that there is no lateral velocity.
123 for robot_angle in [0.0, 1.0, -5.0]:
124 for module_angle in [0.0, 1.0, -5.0]:
125 wheel_patch_velocity = numpy.array(
126 wheel_ground_velocity(
justinT2107d41ed2024-07-31 21:12:31 -0700127 utils.state_vector(
128 velocity=numpy.array([
129 [numpy.cos(robot_angle + module_angle)],
130 [numpy.sin(robot_angle + module_angle)],
131 ]),
132 theta=robot_angle,
133 module_angle=module_angle,
134 ),
135 self.I,
136 ))
Austin Schuh2a1abec2024-07-10 20:31:16 -0700137
138 assert_array_almost_equal(wheel_patch_velocity,
139 numpy.array([[1], [0]]))
140
141 def test_slip_angle(self):
142 """Tests that the slip_angle calculation works."""
143 velocity = numpy.array([[1.5], [0.0]])
144
145 for i in range(4):
Austin Schuh27694fa2024-07-20 16:29:49 -0700146 for wrap in range(-1, 2):
Austin Schuh2a1abec2024-07-10 20:31:16 -0700147 for theta in [0.0, 0.6, -0.4]:
148 module_angle = numpy.pi * wrap + theta
149
Austin Schuh27694fa2024-07-20 16:29:49 -0700150 # We have redefined the angle to be the sin of the angle.
151 # That way, when the module flips directions, the slip angle also flips
152 # directions to keep it stable.
justinT2107d41ed2024-07-31 21:12:31 -0700153 computed_angle = self.slip_angle[i](
154 utils.state_vector(velocity=velocity,
155 module_angle=numpy.pi * wrap +
156 theta),
157 self.I,
158 )[0, 0]
Austin Schuh2a1abec2024-07-10 20:31:16 -0700159
Austin Schuh27694fa2024-07-20 16:29:49 -0700160 expected = numpy.sin(numpy.pi * wrap + theta)
161
162 self.assertAlmostEqual(
163 expected,
164 computed_angle,
justinT2107d41ed2024-07-31 21:12:31 -0700165 msg=f"Trying wrap {wrap} theta {theta}",
166 )
Austin Schuh2a1abec2024-07-10 20:31:16 -0700167
Austin Schuhb8b34be2024-07-14 16:06:19 -0700168 def test_wheel_torque(self):
169 """Tests that the per module self aligning forces have the right signs."""
Austin Schuh27694fa2024-07-20 16:29:49 -0700170 # Point all the modules in a little bit.
justinT2107d41ed2024-07-31 21:12:31 -0700171 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700172 velocity=numpy.array([[1.0], [0.0]]),
173 module_angles=[-0.001, -0.001, 0.001, 0.001],
174 )
Austin Schuhb8b34be2024-07-14 16:06:19 -0700175 xdot_equal = self.swerve_physics(X, self.I)
176
177 self.assertGreater(xdot_equal[2, 0], 0.0)
178 self.assertAlmostEqual(xdot_equal[3, 0], 0.0, places=1)
179 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
180
181 self.assertGreater(xdot_equal[6, 0], 0.0)
182 self.assertAlmostEqual(xdot_equal[7, 0], 0.0, places=1)
183 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
184
185 self.assertLess(xdot_equal[10, 0], 0.0)
186 self.assertAlmostEqual(xdot_equal[11, 0], 0.0, places=1)
187 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
188
189 self.assertLess(xdot_equal[14, 0], 0.0)
190 self.assertAlmostEqual(xdot_equal[15, 0], 0.0, places=1)
191 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
192
193 # Shouldn't be spinning.
194 self.assertAlmostEqual(xdot_equal[21, 0], 0.0, places=2)
195
Austin Schuh27694fa2024-07-20 16:29:49 -0700196 # Now, make the bot want to go left by going to the other side.
Austin Schuhb8b34be2024-07-14 16:06:19 -0700197 # The wheels will be going too fast based on our calcs, so they should be decelerating.
justinT2107d41ed2024-07-31 21:12:31 -0700198 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700199 velocity=numpy.array([[1.0], [0.0]]),
200 module_angles=[0.01, 0.01, 0.01, 0.01],
201 )
Austin Schuhb8b34be2024-07-14 16:06:19 -0700202 xdot_left = self.swerve_physics(X, self.I)
203
Austin Schuh27694fa2024-07-20 16:29:49 -0700204 self.assertLess(xdot_left[2, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700205 self.assertLess(xdot_left[3, 0], 0.0)
206 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
207
Austin Schuh27694fa2024-07-20 16:29:49 -0700208 self.assertLess(xdot_left[6, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700209 self.assertLess(xdot_left[7, 0], 0.0)
210 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
211
Austin Schuh27694fa2024-07-20 16:29:49 -0700212 self.assertLess(xdot_left[10, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700213 self.assertLess(xdot_left[11, 0], 0.0)
214 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
215
Austin Schuh27694fa2024-07-20 16:29:49 -0700216 self.assertLess(xdot_left[14, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700217 self.assertLess(xdot_left[15, 0], 0.0)
218 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
219
220 self.assertGreater(xdot_left[19, 0], 0.0001)
Austin Schuh27694fa2024-07-20 16:29:49 -0700221 self.assertGreater(xdot_left[20, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700222 # Shouldn't be spinning.
223 self.assertAlmostEqual(xdot_left[21, 0], 0.0)
224
225 # And now do it to the right too.
justinT2107d41ed2024-07-31 21:12:31 -0700226 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700227 velocity=numpy.array([[1.0], [0.0]]),
228 module_angles=[-0.01, -0.01, -0.01, -0.01],
229 )
Austin Schuhb8b34be2024-07-14 16:06:19 -0700230 xdot_right = self.swerve_physics(X, self.I)
231
Austin Schuh27694fa2024-07-20 16:29:49 -0700232 self.assertGreater(xdot_right[2, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700233 self.assertLess(xdot_right[3, 0], 0.0)
234 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
235
Austin Schuh27694fa2024-07-20 16:29:49 -0700236 self.assertGreater(xdot_right[6, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700237 self.assertLess(xdot_right[7, 0], 0.0)
238 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
239
Austin Schuh27694fa2024-07-20 16:29:49 -0700240 self.assertGreater(xdot_right[10, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700241 self.assertLess(xdot_right[11, 0], 0.0)
242 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
243
Austin Schuh27694fa2024-07-20 16:29:49 -0700244 self.assertGreater(xdot_right[14, 0], 0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700245 self.assertLess(xdot_right[15, 0], 0.0)
246 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
247
248 self.assertGreater(xdot_right[19, 0], 0.0001)
Austin Schuh27694fa2024-07-20 16:29:49 -0700249 self.assertLess(xdot_right[20, 0], -0.05)
250 # Shouldn't be spinning.
251 self.assertAlmostEqual(xdot_right[21, 0], 0.0)
252
253 def test_wheel_torque_backwards_nocaster(self):
254 """Tests that the per module self aligning forces have the right signs when going backwards."""
255 self.wrap(nocaster_dynamics)
256 # Point all the modules in a little bit, going backwards.
justinT2107d41ed2024-07-31 21:12:31 -0700257 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700258 velocity=numpy.array([[1.0], [0.0]]),
259 module_angles=[
260 numpy.pi - 0.001,
261 numpy.pi - 0.001,
262 numpy.pi + 0.001,
263 numpy.pi + 0.001,
264 ],
265 drive_wheel_velocity=-1,
266 )
267 xdot_equal = self.swerve_physics(X, self.I)
268
269 self.assertGreater(xdot_equal[2, 0], 0.0, msg="Steering backwards")
270 self.assertAlmostEqual(xdot_equal[3, 0], 0.0, places=1)
271 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
272
273 self.assertGreater(xdot_equal[6, 0], 0.0, msg="Steering backwards")
274 self.assertAlmostEqual(xdot_equal[7, 0], 0.0, places=1)
275 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
276
277 self.assertLess(xdot_equal[10, 0], 0.0, msg="Steering backwards")
278 self.assertAlmostEqual(xdot_equal[11, 0], 0.0, places=1)
279 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
280
281 self.assertLess(xdot_equal[14, 0], 0.0, msg="Steering backwards")
282 self.assertAlmostEqual(xdot_equal[15, 0], 0.0, places=1)
283 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
284
285 # Shouldn't be spinning.
286 self.assertAlmostEqual(xdot_equal[21, 0], 0.0, places=2)
287
288 # Now, make the bot want to go left by going to the other side.
289 # The wheels will be going too fast based on our calcs, so they should be decelerating.
justinT2107d41ed2024-07-31 21:12:31 -0700290 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700291 velocity=numpy.array([[1.0], [0.0]]),
292 module_angles=[numpy.pi + 0.01] * 4,
293 drive_wheel_velocity=-1,
294 )
295 xdot_left = self.swerve_physics(X, self.I)
296
297 self.assertLess(xdot_left[2, 0], -0.05)
298 self.assertGreater(xdot_left[3, 0], 0.0)
299 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
300
301 self.assertLess(xdot_left[6, 0], -0.05)
302 self.assertGreater(xdot_left[7, 0], 0.0)
303 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
304
305 self.assertLess(xdot_left[10, 0], -0.05)
306 self.assertGreater(xdot_left[11, 0], 0.0)
307 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
308
309 self.assertLess(xdot_left[14, 0], -0.05)
310 self.assertGreater(xdot_left[15, 0], 0.0)
311 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
312
313 self.assertGreater(xdot_left[19, 0], 0.0001)
314 self.assertGreater(xdot_left[20, 0], 0.05)
315 # Shouldn't be spinning.
316 self.assertAlmostEqual(xdot_left[21, 0], 0.0)
317
318 # And now do it to the right too.
justinT2107d41ed2024-07-31 21:12:31 -0700319 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700320 velocity=numpy.array([[1.0], [0.0]]),
321 drive_wheel_velocity=-1,
322 module_angles=[-0.01 + numpy.pi] * 4,
323 )
324 xdot_right = self.swerve_physics(X, self.I)
325
326 self.assertGreater(xdot_right[2, 0], 0.05)
327 self.assertGreater(xdot_right[3, 0], 0.0)
328 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
329
330 self.assertGreater(xdot_right[6, 0], 0.05)
331 self.assertGreater(xdot_right[7, 0], 0.0)
332 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
333
334 self.assertGreater(xdot_right[10, 0], 0.05)
335 self.assertGreater(xdot_right[11, 0], 0.0)
336 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
337
338 self.assertGreater(xdot_right[14, 0], 0.05)
339 self.assertGreater(xdot_right[15, 0], 0.0)
340 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
341
342 self.assertGreater(xdot_right[19, 0], 0.0001)
343 self.assertLess(xdot_right[20, 0], -0.05)
344 # Shouldn't be spinning.
345 self.assertAlmostEqual(xdot_right[21, 0], 0.0)
346
347 def test_wheel_torque_backwards_caster(self):
348 """Tests that the per module self aligning forces have the right signs when going backwards with a lot of caster."""
349 self.wrap(bigcaster_dynamics)
350 # Point all the modules in a little bit, going backwards.
justinT2107d41ed2024-07-31 21:12:31 -0700351 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700352 velocity=numpy.array([[1.0], [0.0]]),
353 module_angles=[
354 numpy.pi - 0.001,
355 numpy.pi - 0.001,
356 numpy.pi + 0.001,
357 numpy.pi + 0.001,
358 ],
359 drive_wheel_velocity=-1,
360 )
361 xdot_equal = self.swerve_physics(X, self.I)
362
363 self.assertLess(xdot_equal[2, 0], 0.0, msg="Steering backwards")
364 self.assertAlmostEqual(xdot_equal[3, 0], 0.0, places=1)
365 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
366
367 self.assertLess(xdot_equal[6, 0], 0.0, msg="Steering backwards")
368 self.assertAlmostEqual(xdot_equal[7, 0], 0.0, places=1)
369 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
370
371 self.assertGreater(xdot_equal[10, 0], 0.0, msg="Steering backwards")
372 self.assertAlmostEqual(xdot_equal[11, 0], 0.0, places=1)
373 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
374
375 self.assertGreater(xdot_equal[14, 0], 0.0, msg="Steering backwards")
376 self.assertAlmostEqual(xdot_equal[15, 0], 0.0, places=1)
377 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
378
379 # Shouldn't be spinning.
380 self.assertAlmostEqual(xdot_equal[21, 0], 0.0, places=2)
381
382 # Now, make the bot want to go left by going to the other side.
383 # The wheels will be going too fast based on our calcs, so they should be decelerating.
justinT2107d41ed2024-07-31 21:12:31 -0700384 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700385 velocity=numpy.array([[1.0], [0.0]]),
386 module_angles=[numpy.pi + 0.01] * 4,
387 drive_wheel_velocity=-1,
388 )
389 xdot_left = self.swerve_physics(X, self.I)
390
391 self.assertGreater(xdot_left[2, 0], -0.05)
392 self.assertGreater(xdot_left[3, 0], 0.0)
393 self.assertGreater(self.Ms[0](X, self.I)[0, 0], 0.0)
394
395 self.assertGreater(xdot_left[6, 0], -0.05)
396 self.assertGreater(xdot_left[7, 0], 0.0)
397 self.assertGreater(self.Ms[1](X, self.I)[0, 0], 0.0)
398
399 self.assertGreater(xdot_left[10, 0], -0.05)
400 self.assertGreater(xdot_left[11, 0], 0.0)
401 self.assertGreater(self.Ms[2](X, self.I)[0, 0], 0.0)
402
403 self.assertGreater(xdot_left[14, 0], -0.05)
404 self.assertGreater(xdot_left[15, 0], 0.0)
405 self.assertGreater(self.Ms[3](X, self.I)[0, 0], 0.0)
406
407 self.assertGreater(xdot_left[19, 0], 0.0001)
408 self.assertGreater(xdot_left[20, 0], 0.05)
409 # Shouldn't be spinning.
410 self.assertAlmostEqual(xdot_left[21, 0], 0.0)
411
412 # And now do it to the right too.
justinT2107d41ed2024-07-31 21:12:31 -0700413 X = utils.state_vector(
Austin Schuh27694fa2024-07-20 16:29:49 -0700414 velocity=numpy.array([[1.0], [0.0]]),
415 drive_wheel_velocity=-1,
416 module_angles=[-0.01 + numpy.pi] * 4,
417 )
418 xdot_right = self.swerve_physics(X, self.I)
419
420 self.assertLess(xdot_right[2, 0], 0.05)
421 self.assertGreater(xdot_right[3, 0], 0.0)
422 self.assertLess(self.Ms[0](X, self.I)[0, 0], 0.0)
423
424 self.assertLess(xdot_right[6, 0], 0.05)
425 self.assertGreater(xdot_right[7, 0], 0.0)
426 self.assertLess(self.Ms[1](X, self.I)[0, 0], 0.0)
427
428 self.assertLess(xdot_right[10, 0], 0.05)
429 self.assertGreater(xdot_right[11, 0], 0.0)
430 self.assertLess(self.Ms[2](X, self.I)[0, 0], 0.0)
431
432 self.assertLess(xdot_right[14, 0], 0.05)
433 self.assertGreater(xdot_right[15, 0], 0.0)
434 self.assertLess(self.Ms[3](X, self.I)[0, 0], 0.0)
435
436 self.assertGreater(xdot_right[19, 0], 0.0001)
437 self.assertLess(xdot_right[20, 0], -0.05)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700438 # Shouldn't be spinning.
439 self.assertAlmostEqual(xdot_right[21, 0], 0.0)
440
Austin Schuh2a1abec2024-07-10 20:31:16 -0700441 def test_wheel_forces(self):
442 """Tests that the per module forces have the right signs."""
443 for i in range(4):
justinT2107d41ed2024-07-31 21:12:31 -0700444 wheel_force = self.wheel_force[i]
Austin Schuhb8b34be2024-07-14 16:06:19 -0700445
justinT2107d41ed2024-07-31 21:12:31 -0700446 X = utils.state_vector()
Austin Schuhb8b34be2024-07-14 16:06:19 -0700447 robot_equal = wheel_force(X, self.I)
448 xdot_equal = self.swerve_physics(X, self.I)
449 self.assertEqual(robot_equal[0, 0], 0.0)
450 self.assertEqual(robot_equal[1, 0], 0.0)
451 self.assertEqual(xdot_equal[2 + 4 * i], 0.0)
452 self.assertEqual(xdot_equal[3 + 4 * i], 0.0)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700453
454 # Robot is moving faster than the wheels, it should decelerate.
justinT2107d41ed2024-07-31 21:12:31 -0700455 X = utils.state_vector(dx=0.01)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700456 robot_faster = wheel_force(X, self.I)
457 xdot_faster = self.swerve_physics(X, self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700458 self.assertLess(robot_faster[0, 0], -0.1)
459 self.assertEqual(robot_faster[1, 0], 0.0)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700460 self.assertGreater(xdot_faster[3 + 4 * i], 0.0)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700461
462 # Robot is now going slower than the wheels. It should accelerate.
justinT2107d41ed2024-07-31 21:12:31 -0700463 X = utils.state_vector(dx=-0.01)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700464 robot_slower = wheel_force(X, self.I)
465 xdot_slower = self.swerve_physics(X, self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700466 self.assertGreater(robot_slower[0, 0], 0.1)
467 self.assertEqual(robot_slower[1, 0], 0.0)
Austin Schuhb8b34be2024-07-14 16:06:19 -0700468 self.assertLess(xdot_slower[3 + 4 * i], 0.0)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700469
470 # Positive lateral velocity -> negative force.
justinT2107d41ed2024-07-31 21:12:31 -0700471 robot_left = wheel_force(utils.state_vector(dy=0.01), self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700472 self.assertEqual(robot_left[0, 0], 0.0)
473 self.assertLess(robot_left[1, 0], -0.1)
474
475 # Negative lateral velocity -> positive force.
justinT2107d41ed2024-07-31 21:12:31 -0700476 robot_right = wheel_force(utils.state_vector(dy=-0.01), self.I)
Austin Schuh2a1abec2024-07-10 20:31:16 -0700477 self.assertEqual(robot_right[0, 0], 0.0)
478 self.assertGreater(robot_right[1, 0], 0.1)
479
480
justinT2107d41ed2024-07-31 21:12:31 -0700481if __name__ == "__main__":
Austin Schuh2a1abec2024-07-10 20:31:16 -0700482 unittest.main()