67 lines
2.1 KiB
Python

"""This module asserts correct runtime behaviour of the pyrate.act.pid module."""
# Test environment
from unittest import TestCase
# Mathematics
from numpy import array
from numpy import eye
from numpy import Inf
from numpy.linalg import norm
from numpy import vstack
# Package under test
from pyrate.act.control import Lqr
class TestLqr(TestCase):
"""Test for correct runtime behaviour in pyrate.act.control.lqr."""
# In this context, we reproduce a common controller notation
# pylint: disable=invalid-name
def setUp(self) -> None:
"""Setup the LQR specification for testing."""
# Model specification
self.A = array([[0, 1], [0, 0]])
self.B = array([0, 1])[:, None]
self.C = array([1, 0])[None, :]
self.dt = 0.5
# Cost matrix specification
self.Q = eye(2)
self.R = array([[1.0]])
# State and target
# Target is already reached in this example
self.state = vstack([0.0, 0.0])
self.desired = vstack([0.0])
def test_process_tracking(self) -> None:
"""Assert that a pandas.DataFrame with process data is created."""
# Initialize PID controller
lqr = Lqr(self.A, self.B, self.C, self.Q, self.R, self.dt, keep_trace=True)
# Execute a few control steps
state = self.state.copy()
previous_error = Inf
for _ in range(10):
control_signal = lqr.control(desired=self.desired, state=self.state)
state += self.A.dot(state) + self.B.dot(control_signal)
error = norm(state - self.desired).item() # Convert from numpy scalar to Python float
assert error < previous_error or error == 0, "Error did not get lower."
previous_error = error
# Assert correct process tracing with LQR controller
assert lqr.process is not None, "LQR did not keep trace of process"
assert len(lqr.process.index) == 10, "LQR has not traced enough steps"
# Reset PID
lqr.reset()
# Assert correct reset to initial state
assert len(lqr.process.index) == 0, "LQR has not dropped process trace properly"