Skip to content

Commit 7c7b344

Browse files
committed
Add tests for wpimath.system
1 parent 0a7ecd0 commit 7c7b344

File tree

1 file changed

+186
-4
lines changed

1 file changed

+186
-4
lines changed
Lines changed: 186 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,188 @@
1-
import wpimath.system
2-
import wpimath.system.plant
1+
import importlib.util
2+
import math
33

4+
import pytest
45

5-
def test_todo():
6-
pass
6+
import wpimath.system as system
7+
8+
9+
if importlib.util.find_spec("numpy") is None:
10+
pytest.skip("numpy is not available", allow_module_level=True)
11+
12+
import numpy as np
13+
14+
15+
def test_rk4_exponential():
16+
y0 = np.array([[0.0]])
17+
18+
y1 = system.RK4(lambda x: np.array([[math.exp(x[0, 0])]]), y0, 0.1)
19+
20+
assert math.isclose(y1[0, 0], math.exp(0.1) - math.exp(0.0), abs_tol=1e-3)
21+
22+
23+
def test_rk4_exponential_with_u():
24+
y0 = np.array([[0.0]])
25+
26+
y1 = system.RK4(
27+
lambda x, u: np.array([[math.exp(u[0, 0] * x[0, 0])]]),
28+
y0,
29+
np.array([[1.0]]),
30+
0.1,
31+
)
32+
33+
assert math.isclose(y1[0, 0], math.exp(0.1) - math.exp(0.0), abs_tol=1e-3)
34+
35+
36+
def test_rk4_time_varying():
37+
y0 = np.array([[12.0 * math.exp(5.0) / math.pow(math.exp(5.0) + 1.0, 2.0)]])
38+
39+
y1 = system.RK4(
40+
lambda t, x: np.array([[x[0, 0] * (2.0 / (math.exp(t) + 1.0) - 1.0)]]),
41+
5.0,
42+
y0,
43+
1.0,
44+
)
45+
46+
expected = 12.0 * math.exp(6.0) / math.pow(math.exp(6.0) + 1.0, 2.0)
47+
assert math.isclose(y1[0, 0], expected, abs_tol=1e-3)
48+
49+
50+
def test_rkdp_zero():
51+
y1 = system.RKDP(
52+
lambda x, u: np.zeros((1, 1)),
53+
np.array([[0.0]]),
54+
np.array([[0.0]]),
55+
0.1,
56+
)
57+
58+
assert math.isclose(y1[0, 0], 0.0, abs_tol=1e-3)
59+
60+
61+
def test_rkdp_exponential():
62+
y0 = np.array([[0.0]])
63+
64+
y1 = system.RKDP(
65+
lambda x, u: np.array([[math.exp(x[0, 0])]]),
66+
y0,
67+
np.array([[0.0]]),
68+
0.1,
69+
)
70+
71+
assert math.isclose(y1[0, 0], math.exp(0.1) - math.exp(0.0), abs_tol=1e-3)
72+
73+
74+
def test_rkdp_time_varying():
75+
y0 = np.array([[12.0 * math.exp(5.0) / math.pow(math.exp(5.0) + 1.0, 2.0)]])
76+
77+
y1 = system.RKDP(
78+
lambda t, x: np.array([[x[0, 0] * (2.0 / (math.exp(t) + 1.0) - 1.0)]]),
79+
5.0,
80+
y0,
81+
1.0,
82+
1e-12,
83+
)
84+
85+
expected = 12.0 * math.exp(6.0) / math.pow(math.exp(6.0) + 1.0, 2.0)
86+
assert math.isclose(y1[0, 0], expected, abs_tol=1e-3)
87+
88+
89+
def test_numerical_jacobian():
90+
a = np.array(
91+
[
92+
[1.0, 2.0, 4.0, 1.0],
93+
[5.0, 2.0, 3.0, 4.0],
94+
[5.0, 1.0, 3.0, 2.0],
95+
[1.0, 1.0, 3.0, 7.0],
96+
]
97+
)
98+
99+
def ax_fn(x):
100+
return a @ x
101+
102+
new_a = system.numericalJacobian(ax_fn, np.zeros((4, 1)))
103+
np.testing.assert_allclose(new_a, a, rtol=1e-6, atol=1e-5)
104+
105+
106+
def test_numerical_jacobian_x_u_square():
107+
a = np.array(
108+
[
109+
[1.0, 2.0, 4.0, 1.0],
110+
[5.0, 2.0, 3.0, 4.0],
111+
[5.0, 1.0, 3.0, 2.0],
112+
[1.0, 1.0, 3.0, 7.0],
113+
]
114+
)
115+
b = np.array([[1.0, 1.0], [2.0, 1.0], [3.0, 2.0], [3.0, 7.0]])
116+
117+
def axbu_fn(x, u, *args):
118+
return a @ x + b @ u
119+
120+
x0 = np.zeros((4, 1))
121+
u0 = np.zeros((2, 1))
122+
new_a = system.numericalJacobianX(axbu_fn, x0, u0)
123+
new_b = system.numericalJacobianU(axbu_fn, x0, u0)
124+
np.testing.assert_allclose(new_a, a, rtol=1e-6, atol=1e-5)
125+
np.testing.assert_allclose(new_b, b, rtol=1e-6, atol=1e-5)
126+
127+
128+
def test_numerical_jacobian_x_u_rectangular():
129+
c = np.array(
130+
[
131+
[1.0, 2.0, 4.0, 1.0],
132+
[5.0, 2.0, 3.0, 4.0],
133+
[5.0, 1.0, 3.0, 2.0],
134+
]
135+
)
136+
d = np.array([[1.0, 1.0], [2.0, 1.0], [3.0, 2.0]])
137+
138+
def cxdu_fn(x, u, *args):
139+
return c @ x + d @ u
140+
141+
x0 = np.zeros((4, 1))
142+
u0 = np.zeros((2, 1))
143+
new_c = system.numericalJacobianX(cxdu_fn, x0, u0)
144+
new_d = system.numericalJacobianU(cxdu_fn, x0, u0)
145+
np.testing.assert_allclose(new_c, c, rtol=1e-6, atol=1e-5)
146+
np.testing.assert_allclose(new_d, d, rtol=1e-6, atol=1e-5)
147+
148+
149+
def test_numerical_jacobian_x_passes_extra_args():
150+
a = np.array([[2.0, -1.0], [0.5, 3.0]])
151+
b = np.array([[1.0], [4.0]])
152+
x0 = np.zeros((2, 1))
153+
u0 = np.zeros((1, 1))
154+
155+
seen = {}
156+
157+
def axbu_fn(x, u, args):
158+
seen["args"] = args
159+
scale, bias = args
160+
return scale * (a @ x) + bias * (b @ u)
161+
162+
new_a = system.numericalJacobianX(axbu_fn, x0, u0, 2.5, -3.0)
163+
164+
assert "args" in seen
165+
normalized = seen["args"][0] if len(seen["args"]) == 1 else seen["args"]
166+
assert normalized == (2.5, -3.0)
167+
np.testing.assert_allclose(new_a, 2.5 * a, rtol=1e-6, atol=1e-5)
168+
169+
170+
def test_numerical_jacobian_u_passes_extra_args():
171+
a = np.array([[1.0, 0.0], [0.0, -2.0]])
172+
b = np.array([[1.5], [-0.5]])
173+
x0 = np.zeros((2, 1))
174+
u0 = np.zeros((1, 1))
175+
176+
seen = {}
177+
178+
def axbu_fn(x, u, args):
179+
seen["args"] = args
180+
scale, bias = args
181+
return scale * (a @ x) + bias * (b @ u)
182+
183+
new_b = system.numericalJacobianU(axbu_fn, x0, u0, 4.0, 0.25)
184+
185+
assert "args" in seen
186+
normalized = seen["args"][0] if len(seen["args"]) == 1 else seen["args"]
187+
assert normalized == (4.0, 0.25)
188+
np.testing.assert_allclose(new_b, 0.25 * b, rtol=1e-6, atol=1e-5)

0 commit comments

Comments
 (0)