Skip to content

Commit e23bf5f

Browse files
Add test for StatefulSysModel
1 parent 2e3b96b commit e23bf5f

1 file changed

Lines changed: 92 additions & 0 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#
2+
# ISC License
3+
#
4+
# Copyright (c) 2025, Autonomous Vehicle Systems Lab, University of Colorado at Boulder
5+
#
6+
# Permission to use, copy, modify, and/or distribute this software for any
7+
# purpose with or without fee is hereby granted, provided that the above
8+
# copyright notice and this permission notice appear in all copies.
9+
#
10+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17+
18+
from Basilisk.utilities import macros
19+
from Basilisk.utilities import SimulationBaseClass
20+
21+
try:
22+
from Basilisk.simulation import mujoco
23+
from Basilisk.simulation import StatefulSysModel
24+
couldImportMujoco = True
25+
except:
26+
couldImportMujoco = False
27+
28+
import pytest
29+
import numpy as np
30+
31+
@pytest.mark.skipif(not couldImportMujoco, reason="Compiled Basilisk without --mujoco")
32+
def test_stateful():
33+
"""Tests that ``StatefulSysModel`` works as expected.
34+
35+
We use a simple ``StatefulSysModel`` with a single state. We check
36+
that said state is registered with the expected name and that its
37+
value evolves as we would expect.
38+
"""
39+
40+
# Declared inside, since StatefulSysModel may be undefined if not running with mujoco
41+
class ExponentialStateModel(StatefulSysModel.StatefulSysModel):
42+
"""A simple model with one state, whose derivative is dx/dt = x*t."""
43+
44+
def registerStates(self, registerer: StatefulSysModel.DynParamRegisterer):
45+
"""Called once during InitializeSimulation"""
46+
self.xState = registerer.registerState(1, 1, "x")
47+
48+
def UpdateState(self, CurrentSimNanos):
49+
"""Called at every integrator step"""
50+
t = macros.NANO2SEC * CurrentSimNanos
51+
x = self.xState.getState()[0][0]
52+
self.xState.setDerivative( [[t*x]] )
53+
54+
55+
dt = 0.01 # s
56+
tf = 1 # s
57+
58+
# Create sim, process, and task
59+
scSim = SimulationBaseClass.SimBaseClass()
60+
dynProcess = scSim.CreateNewProcess("test")
61+
dynProcess.addTask(scSim.CreateNewTask("test", macros.sec2nano(dt)))
62+
63+
scene = mujoco.MJScene("<mujoco/>") # empty scene, no multi-body dynamics
64+
scSim.AddModelToTask("test", scene)
65+
66+
expState = ExponentialStateModel()
67+
expState.ModelTag = "testModel"
68+
69+
scene.AddModelToDynamicsTask(expState)
70+
71+
# Run the sim
72+
scSim.InitializeSimulation()
73+
expState.xState.setState([[1]]) # initialize state to 1
74+
75+
# Run for tf seconds
76+
scSim.ConfigureStopTime(macros.sec2nano(tf))
77+
scSim.ExecuteSimulation()
78+
79+
# Check that the state name has the model tag and ID prepended
80+
expected_name = f"{expState.ModelTag}_{expState.moduleID}_x"
81+
assert expState.xState.getName() == expected_name, f"{expState.xState.getName()} != {expected_name}"
82+
83+
# The state follows dx/dt=x*t for x(0) = 1
84+
# So we expect x(tf=1) to be e^(tf^2/2)
85+
expected = np.exp( tf**2 / 2 )
86+
assert expState.xState.getState()[0][0] == pytest.approx(expected)
87+
88+
if __name__ == "__main__":
89+
if True:
90+
test_stateful()
91+
else:
92+
pytest.main([__file__])

0 commit comments

Comments
 (0)