[py] Fixup new acceleration classes (#8459)

I tried to sync `mostrobotpy` with `allwpilib` and was getting a
compilation error I had not seen before when it tried to do the stub
generation (which `allwpilib` does not do).

Luckily, I was able to debug it here by writing some unit tests (i.e.,
having Gemini convert the C++ tests into python) that failed in a
similar way. The main problem was needing to write a custom constructor
for the class and adding a `force_type_casters`. I used `ChassisSpeed`
as my main example, but I did not copy all of the other custom code like
overriding the index operator, `__repr__` operator, feet helpers, etc. I
can gladly add those in.

In the future, we should start enforce a policy that if you add a C++ or
Java unit test, you also have to add a python test. That developer might
have gotten more stuck on the minutia of how to fix it, but this problem
would have at least been caught earlier before it landed.
This commit is contained in:
PJ Reiniger
2025-12-17 22:19:12 -05:00
committed by GitHub
parent d6b54bbae2
commit a38499dcd7
9 changed files with 551 additions and 0 deletions

View File

@@ -0,0 +1,119 @@
import math
import pytest
from wpimath.kinematics import ChassisAccelerations
from wpimath.geometry import Rotation2d
from wpimath.units import feetToMeters
K_EPSILON = 1e-9
def test_default_constructor():
accelerations = ChassisAccelerations()
assert accelerations.ax == pytest.approx(0.0, abs=K_EPSILON)
assert accelerations.ay == pytest.approx(0.0, abs=K_EPSILON)
assert accelerations.alpha == pytest.approx(0.0, abs=K_EPSILON)
def test_parameterized_constructor():
# 1.0 m/s^2, 2.0 m/s^2, 3.0 rad/s^2
accelerations = ChassisAccelerations(1.0, 2.0, 3.0)
assert accelerations.ax == pytest.approx(1.0, abs=K_EPSILON)
assert accelerations.ay == pytest.approx(2.0, abs=K_EPSILON)
assert accelerations.alpha == pytest.approx(3.0, abs=K_EPSILON)
def test_to_robot_relative():
start_accel = ChassisAccelerations(1.0, 0.0, 0.5)
chassis_accelerations = start_accel.toRobotRelative(Rotation2d.fromDegrees(-90))
assert chassis_accelerations.ax == pytest.approx(0.0, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(1.0, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(0.5, abs=K_EPSILON)
def test_to_field_relative():
start_accel = ChassisAccelerations(1.0, 0.0, 0.5)
chassis_accelerations = start_accel.toFieldRelative(Rotation2d.fromDegrees(45))
expected_val = 1.0 / math.sqrt(2.0)
assert chassis_accelerations.ax == pytest.approx(expected_val, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(expected_val, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(0.5, abs=K_EPSILON)
def test_plus():
left = ChassisAccelerations(1.0, 0.5, 0.75)
right = ChassisAccelerations(2.0, 1.5, 0.25)
chassis_accelerations = left + right
assert chassis_accelerations.ax == pytest.approx(3.0, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(2.0, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(1.0, abs=K_EPSILON)
def test_minus():
left = ChassisAccelerations(1.0, 0.5, 0.75)
right = ChassisAccelerations(2.0, 0.5, 0.25)
chassis_accelerations = left - right
assert chassis_accelerations.ax == pytest.approx(-1.0, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(0.0, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(0.5, abs=K_EPSILON)
def test_unary_minus():
accel = ChassisAccelerations(1.0, 0.5, 0.75)
chassis_accelerations = -accel
assert chassis_accelerations.ax == pytest.approx(-1.0, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(-0.5, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(-0.75, abs=K_EPSILON)
def test_multiplication():
accel = ChassisAccelerations(1.0, 0.5, 0.75)
chassis_accelerations = accel * 2.0
assert chassis_accelerations.ax == pytest.approx(2.0, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(1.0, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(1.5, abs=K_EPSILON)
def test_division():
accel = ChassisAccelerations(2.0, 1.0, 1.5)
chassis_accelerations = accel / 2.0
assert chassis_accelerations.ax == pytest.approx(1.0, abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(0.5, abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(0.75, abs=K_EPSILON)
def test_feet_constructor():
chassis_accelerations = ChassisAccelerations.fromFps(10, 11, math.radians(45))
assert chassis_accelerations.ax == pytest.approx(feetToMeters(10), abs=K_EPSILON)
assert chassis_accelerations.ay == pytest.approx(feetToMeters(11), abs=K_EPSILON)
assert chassis_accelerations.alpha == pytest.approx(math.radians(45), abs=K_EPSILON)
assert chassis_accelerations.ax_fpss == pytest.approx(10, abs=K_EPSILON)
assert chassis_accelerations.ay_fpss == pytest.approx(11, abs=K_EPSILON)
assert chassis_accelerations.alpha_dpss == pytest.approx(45, abs=K_EPSILON)
def test_repr():
chassis_accelerations = ChassisAccelerations(1, 2, 3)
assert (
str(chassis_accelerations)
== "ChassisAccelerations(ax=1.000000, ay=2.000000, alpha=3.000000)"
)

View File

@@ -0,0 +1,85 @@
import pytest
from wpimath.kinematics import DifferentialDriveWheelAccelerations
from wpimath.units import feetToMeters
K_EPSILON = 1e-9
def test_default_constructor():
wheel_accelerations = DifferentialDriveWheelAccelerations()
assert wheel_accelerations.left == pytest.approx(0.0, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(0.0, abs=K_EPSILON)
def test_parameterized_constructor():
wheel_accelerations = DifferentialDriveWheelAccelerations(1.5, 2.5)
assert wheel_accelerations.left == pytest.approx(1.5, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(2.5, abs=K_EPSILON)
def test_plus():
left = DifferentialDriveWheelAccelerations(1.0, 0.5)
right = DifferentialDriveWheelAccelerations(2.0, 1.5)
wheel_accelerations = left + right
assert wheel_accelerations.left == pytest.approx(3.0, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(2.0, abs=K_EPSILON)
def test_minus():
left = DifferentialDriveWheelAccelerations(1.0, 0.5)
right = DifferentialDriveWheelAccelerations(2.0, 0.5)
wheel_accelerations = left - right
assert wheel_accelerations.left == pytest.approx(-1.0, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(0.0, abs=K_EPSILON)
def test_unary_minus():
accel = DifferentialDriveWheelAccelerations(1.0, 0.5)
wheel_accelerations = -accel
assert wheel_accelerations.left == pytest.approx(-1.0, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(-0.5, abs=K_EPSILON)
def test_multiplication():
accel = DifferentialDriveWheelAccelerations(1.0, 0.5)
wheel_accelerations = accel * 2.0
assert wheel_accelerations.left == pytest.approx(2.0, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(1.0, abs=K_EPSILON)
def test_division():
accel = DifferentialDriveWheelAccelerations(1.0, 0.5)
wheel_accelerations = accel / 2.0
assert wheel_accelerations.left == pytest.approx(0.5, abs=K_EPSILON)
assert wheel_accelerations.right == pytest.approx(0.25, abs=K_EPSILON)
def test_feet_constructor():
accel = DifferentialDriveWheelAccelerations.fromFps(10, 11)
assert accel.left == pytest.approx(feetToMeters(10), abs=K_EPSILON)
assert accel.right == pytest.approx(feetToMeters(11), abs=K_EPSILON)
assert accel.left_fpss == pytest.approx(10, abs=K_EPSILON)
assert accel.right_fpss == pytest.approx(11, abs=K_EPSILON)
def test_repr():
accel = DifferentialDriveWheelAccelerations(1, 2)
assert (
str(accel)
== "DifferentialDriveWheelAccelerations(left=1.000000, right=2.000000)"
)

View File

@@ -0,0 +1,105 @@
import pytest
from wpimath.kinematics import MecanumDriveWheelAccelerations
from wpimath.units import feetToMeters
# Epsilon matching the C++ test
K_EPSILON = 1e-9
def test_default_constructor():
wheel_accelerations = MecanumDriveWheelAccelerations()
assert wheel_accelerations.frontLeft == pytest.approx(0.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(0.0, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(0.0, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(0.0, abs=K_EPSILON)
def test_parameterized_constructor():
# Accelerations: FL=1.0, FR=2.0, RL=3.0, RR=4.0 m/s^2
wheel_accelerations = MecanumDriveWheelAccelerations(1.0, 2.0, 3.0, 4.0)
assert wheel_accelerations.frontLeft == pytest.approx(1.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(2.0, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(3.0, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(4.0, abs=K_EPSILON)
def test_plus():
left = MecanumDriveWheelAccelerations(1.0, 0.5, 0.75, 0.25)
right = MecanumDriveWheelAccelerations(1.0, 1.5, 0.25, 1.75)
wheel_accelerations = left + right
assert wheel_accelerations.frontLeft == pytest.approx(2.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(2.0, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(1.0, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(2.0, abs=K_EPSILON)
def test_minus():
left = MecanumDriveWheelAccelerations(1.0, 0.5, 0.75, 0.25)
right = MecanumDriveWheelAccelerations(2.0, 1.5, 0.25, 0.75)
wheel_accelerations = left - right
assert wheel_accelerations.frontLeft == pytest.approx(-1.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(-1.0, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(0.5, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(-0.5, abs=K_EPSILON)
def test_unary_minus():
accel = MecanumDriveWheelAccelerations(1.0, 0.5, 0.75, 0.25)
wheel_accelerations = -accel
assert wheel_accelerations.frontLeft == pytest.approx(-1.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(-0.5, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(-0.75, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(-0.25, abs=K_EPSILON)
def test_multiplication():
accel = MecanumDriveWheelAccelerations(1.0, 0.5, 0.75, 0.25)
wheel_accelerations = accel * 2.0
assert wheel_accelerations.frontLeft == pytest.approx(2.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(1.0, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(1.5, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(0.5, abs=K_EPSILON)
def test_division():
accel = MecanumDriveWheelAccelerations(2.0, 1.0, 1.5, 0.5)
wheel_accelerations = accel / 2.0
assert wheel_accelerations.frontLeft == pytest.approx(1.0, abs=K_EPSILON)
assert wheel_accelerations.frontRight == pytest.approx(0.5, abs=K_EPSILON)
assert wheel_accelerations.rearLeft == pytest.approx(0.75, abs=K_EPSILON)
assert wheel_accelerations.rearRight == pytest.approx(0.25, abs=K_EPSILON)
def test_feet_constructor():
accel = MecanumDriveWheelAccelerations.fromFps(10, 11, 12, 13)
assert accel.frontLeft == pytest.approx(feetToMeters(10), abs=K_EPSILON)
assert accel.frontRight == pytest.approx(feetToMeters(11), abs=K_EPSILON)
assert accel.rearLeft == pytest.approx(feetToMeters(12), abs=K_EPSILON)
assert accel.rearRight == pytest.approx(feetToMeters(13), abs=K_EPSILON)
assert accel.front_left_fpss == pytest.approx(10, abs=K_EPSILON)
assert accel.front_right_fpss == pytest.approx(11, abs=K_EPSILON)
assert accel.rear_left_fpss == pytest.approx(12, abs=K_EPSILON)
assert accel.rear_right_fpss == pytest.approx(13, abs=K_EPSILON)
def test_repr():
accel = MecanumDriveWheelAccelerations(1, 2, 3, 4)
assert (
str(accel)
== "MecanumDriveWheelAccelerations(frontLeft=1.000000, frontRight=2.000000, rearLeft=3.000000, rearRight=4.000000)"
)

View File

@@ -0,0 +1,48 @@
import pytest
import math
from wpimath.kinematics import SwerveModuleAcceleration
from wpimath.geometry import Rotation2d
from wpimath.units import feetToMeters
K_EPSILON = 1e-9
def test_default_constructor():
module_accelerations = SwerveModuleAcceleration()
assert module_accelerations.acceleration == pytest.approx(0.0, abs=K_EPSILON)
assert module_accelerations.angle.radians() == pytest.approx(0.0, abs=K_EPSILON)
def test_parameterized_constructor():
module_accelerations = SwerveModuleAcceleration(2.5, Rotation2d(1.5))
assert module_accelerations.acceleration == pytest.approx(2.5, abs=K_EPSILON)
assert module_accelerations.angle.radians() == pytest.approx(1.5, abs=K_EPSILON)
def test_equals():
module_accelerations1 = SwerveModuleAcceleration(2.0, Rotation2d(1.5))
module_accelerations2 = SwerveModuleAcceleration(2.0, Rotation2d(1.5))
module_accelerations3 = SwerveModuleAcceleration(2.1, Rotation2d(1.5))
assert module_accelerations1 == module_accelerations2
assert module_accelerations1 != module_accelerations3
def test_feet_constructor():
accel = SwerveModuleAcceleration.fromFps(10, Rotation2d(1.5))
assert accel.acceleration == pytest.approx(feetToMeters(10), abs=K_EPSILON)
assert accel.angle.radians() == pytest.approx(1.5, abs=K_EPSILON)
assert accel.acceleration_fpss == pytest.approx(10, abs=K_EPSILON)
def test_repr():
accel = SwerveModuleAcceleration(1, Rotation2d(1.5))
assert (
str(accel) == "SwerveModuleAcceleration(acceleration=1.000000, angle=1.500000)"
)