blob: 7ea7123958a5bcb92fe2a0229626645f2c9a2246 [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
9from frc971.control_loops.swerve import dynamics
10
11
12def state_vector(velocity=numpy.array([[1.0], [0.0]]),
13 dx=0.0,
14 dy=0.0,
15 theta=0.0,
16 omega=0.0,
17 module_omega=0.0,
18 module_angle=0.0):
19 """Returns the state vector with the requested state."""
20 X_initial = numpy.zeros((25, 1))
21 # All the wheels are spinning at the speed needed to hit the velocity in m/s
22 X_initial[2, 0] = module_omega
23 X_initial[3, 0] = numpy.linalg.norm(velocity) / (dynamics.WHEEL_RADIUS)
24
25 X_initial[6, 0] = module_omega
26 X_initial[7, 0] = numpy.linalg.norm(velocity) / (dynamics.WHEEL_RADIUS)
27
28 X_initial[10, 0] = module_omega
29 X_initial[11, 0] = numpy.linalg.norm(velocity) / (dynamics.WHEEL_RADIUS)
30
31 X_initial[14, 0] = module_omega
32 X_initial[15, 0] = numpy.linalg.norm(velocity) / (dynamics.WHEEL_RADIUS)
33
34 X_initial[0, 0] = module_angle
35 X_initial[4, 0] = module_angle
36 X_initial[8, 0] = module_angle
37 X_initial[12, 0] = module_angle
38
39 X_initial[18, 0] = theta
40
41 X_initial[19, 0] = velocity[0, 0] + dx
42 X_initial[20, 0] = velocity[1, 0] + dy
43 X_initial[21, 0] = omega
44
45 return X_initial
46
47
48class TestHPolytope(unittest.TestCase):
49 I = numpy.zeros((8, 1))
50
51 def setUp(self):
52 pass
53
54 def test_contact_patch_velocity(self):
55 """Tests that the contact patch velocity makes sense."""
56 for i in range(4):
57 contact_patch_velocity = dynamics.contact_patch_velocity(
58 i, casadi.SX.sym("X", 25, 1), casadi.SX.sym("U", 8, 1))
59 wheel_ground_velocity = dynamics.wheel_ground_velocity(
60 i, casadi.SX.sym("X", 25, 1), casadi.SX.sym("U", 8, 1))
61
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]:
71 patch_velocity = numpy.array(
72 contact_patch_velocity(
73 state_vector(velocity=velocity, theta=theta),
74 self.I))
75
76 assert_array_equal(patch_velocity, velocity)
77
78 # Now, test that spinning the robot results in module velocities.
79 # We are assuming that each module is on a square robot.
80 module_center_of_mass_angle = i * numpy.pi / 2.0 + numpy.pi / 4.0
81 for theta in [-module_center_of_mass_angle, 0.0, 1.0, -1.0, 100.0]:
82 for omega in [0.65, -0.1]:
83 # Point each module to the center to make the math easier.
84 patch_velocity = numpy.array(
85 contact_patch_velocity(
86 state_vector(
87 velocity=numpy.array([[0.0], [0.0]]),
88 theta=theta,
89 omega=omega,
90 module_angle=module_center_of_mass_angle),
91 self.I))
92
93 assert_array_almost_equal(
94 patch_velocity,
95 (dynamics.ROBOT_WIDTH / numpy.sqrt(2.0) +
96 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 # Point the wheel along +x, rotate it by theta, then spin it.
101 # Confirm the velocities come out right.
102 patch_velocity = numpy.array(
103 contact_patch_velocity(
104 state_vector(
105 velocity=numpy.array([[0.0], [0.0]]),
106 theta=-module_center_of_mass_angle,
107 module_omega=omega,
108 module_angle=(theta +
109 module_center_of_mass_angle)),
110 self.I))
111
112 assert_array_almost_equal(
113 patch_velocity,
114 dynamics.CASTER * omega *
115 numpy.array([[-numpy.sin(theta)], [numpy.cos(theta)]]))
116
117 # Now, test that the rotation back to wheel coordinates works.
118 # The easiest way to do this is to point the wheel in a direction,
119 # move in that direction, and confirm that there is no lateral velocity.
120 for robot_angle in [0.0, 1.0, -5.0]:
121 for module_angle in [0.0, 1.0, -5.0]:
122 wheel_patch_velocity = numpy.array(
123 wheel_ground_velocity(
124 state_vector(velocity=numpy.array(
125 [[numpy.cos(robot_angle + module_angle)],
126 [numpy.sin(robot_angle + module_angle)]]),
127 theta=robot_angle,
128 module_angle=module_angle), self.I))
129
130 assert_array_almost_equal(wheel_patch_velocity,
131 numpy.array([[1], [0]]))
132
133 def test_slip_angle(self):
134 """Tests that the slip_angle calculation works."""
135 velocity = numpy.array([[1.5], [0.0]])
136
137 for i in range(4):
138 x = casadi.SX.sym("x")
139 y = casadi.SX.sym("y")
140 half_atan2 = casadi.Function('half_atan2', [y, x],
141 [dynamics.half_atan2(y, x)])
142
143 slip_angle = dynamics.slip_angle(i, casadi.SX.sym("X", 25, 1),
144 casadi.SX.sym("U", 8, 1))
145
146 for wrap in range(-3, 3):
147 for theta in [0.0, 0.6, -0.4]:
148 module_angle = numpy.pi * wrap + theta
149
150 self.assertAlmostEqual(
151 theta,
152 half_atan2(numpy.sin(module_angle),
153 numpy.cos(module_angle)))
154
155 computed_angle = slip_angle(
156 state_vector(velocity=velocity,
157 module_angle=numpy.pi * wrap + theta),
158 self.I)
159
160 self.assertAlmostEqual(theta, computed_angle)
161
162 def test_wheel_forces(self):
163 """Tests that the per module forces have the right signs."""
164 for i in range(4):
165 wheel_force = dynamics.wheel_force(i, casadi.SX.sym("X", 25, 1),
166 casadi.SX.sym("U", 8, 1))
167
168 # Robot is moving faster than the wheels, it should decelerate.
169 robot_faster = numpy.array(
170 wheel_force(state_vector(dx=0.01), self.I))
171 self.assertLess(robot_faster[0, 0], -0.1)
172 self.assertEqual(robot_faster[1, 0], 0.0)
173
174 # Robot is now going slower than the wheels. It should accelerate.
175 robot_slower = numpy.array(
176 wheel_force(state_vector(dx=-0.01), self.I))
177 self.assertGreater(robot_slower[0, 0], 0.1)
178 self.assertEqual(robot_slower[1, 0], 0.0)
179
180 # Positive lateral velocity -> negative force.
181 robot_left = numpy.array(wheel_force(state_vector(dy=0.01),
182 self.I))
183 self.assertEqual(robot_left[0, 0], 0.0)
184 self.assertLess(robot_left[1, 0], -0.1)
185
186 # Negative lateral velocity -> positive force.
187 robot_right = numpy.array(
188 wheel_force(state_vector(dy=-0.01), self.I))
189 self.assertEqual(robot_right[0, 0], 0.0)
190 self.assertGreater(robot_right[1, 0], 0.1)
191
192
193if __name__ == '__main__':
194 unittest.main()