Skip to content

Commit 6e56b73

Browse files
committed
add gainsched label processing + unit tests for errors
1 parent 5fcf2b5 commit 6e56b73

File tree

2 files changed

+54
-7
lines changed

2 files changed

+54
-7
lines changed

control/statefbk.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,9 @@ def create_statefbk_iosystem(
788788
# Generate the list of labels using the argument as a format string
789789
ud_labels = [ud_labels.format(i=i) for i in range(sys.ninputs)]
790790

791+
# Create the string of labels for the control system
792+
input_labels = xd_labels + ud_labels + estimator.output_labels
793+
791794
# Process gainscheduling variables, if present
792795
if gainsched:
793796
# Create a copy of the scheduling variable indices (default = empty)
@@ -797,10 +800,13 @@ def create_statefbk_iosystem(
797800
# Make sure the scheduling variable indices are the right length
798801
if len(gainsched_indices) != points.shape[1]:
799802
raise ControlArgument(
800-
"Length of gainsched_indices must match dimension of"
803+
"length of gainsched_indices must match dimension of"
801804
" scheduling variables")
802805

803-
# TODO: Process scheduling variables
806+
# Process scheduling variables
807+
for i, idx in enumerate(gainsched_indices):
808+
if isinstance(idx, str):
809+
gainsched_indices[i] = input_labels.index(gainsched_indices[i])
804810

805811
# Create interpolating function
806812
if method == 'nearest':
@@ -855,9 +861,8 @@ def _control_output(t, states, inputs, params):
855861
params = {} if gainsched else {'K': K}
856862
ctrl = NonlinearIOSystem(
857863
_control_update, _control_output, name='control',
858-
inputs=xd_labels + ud_labels + estimator.output_labels,
859-
outputs=list(sys.input_index.keys()), params=params,
860-
states=nintegrators)
864+
inputs=input_labels, outputs=list(sys.input_index.keys()),
865+
params=params, states=nintegrators)
861866

862867
elif type == 'linear' or type is None:
863868
# Create the matrices implementing the controller

control/tests/statefbk_test.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,8 @@ def test_gainsched_unicycle(unicycle, method):
849849
# Make sure that gains are different from 'nearest'
850850
if method is not None and method != 'nearest':
851851
ctrl_nearest, clsys_nearest = ct.create_statefbk_iosystem(
852-
unicycle, (gains, points, 'nearest'), gainsched_indices=[3, 2])
852+
unicycle, (gains, points, 'nearest'),
853+
gainsched_indices=['ud[0]', 2])
853854
nearest_lin = clsys_nearest.linearize(xe, [xd, ud])
854855
assert not np.allclose(
855856
np.sort(clsys_lin.poles()), np.sort(nearest_lin.poles()), rtol=1e-2)
@@ -880,7 +881,8 @@ def test_gainsched_unicycle(unicycle, method):
880881

881882
# Create gain scheduled controller
882883
ctrl, clsys = ct.create_statefbk_iosystem(
883-
unicycle, (gains, points), gainsched_indices=[3, 7])
884+
unicycle, (gains, points),
885+
ud_labels=['vd', 'phid'], gainsched_indices=['vd', 'theta'])
884886

885887
# Check the gain at the selected points
886888
for speed, angle in points:
@@ -903,3 +905,43 @@ def test_gainsched_unicycle(unicycle, method):
903905
resp = ct.input_output_response(clsys, timepts, [Xd, Ud], X0)
904906
np.testing.assert_allclose(
905907
resp.states[:, -1], Xd[:, -1], atol=1e-2, rtol=1e-2)
908+
909+
def test_gainsched_errors(unicycle):
910+
# Set up gain schedule (same as previous test)
911+
speeds = [1, 5, 10]
912+
angles = np.linspace(0, pi/2, 4)
913+
points = list(itertools.product(speeds, angles))
914+
915+
Q = np.identity(unicycle.nstates)
916+
R = np.identity(unicycle.ninputs)
917+
gains = [np.array(ct.lqr(unicycle.linearize(
918+
[0, 0, angle], [speed, 0]), Q, R)[0]) for speed, angle in points]
919+
920+
# Make sure the generic case works OK
921+
ctrl, clsys = ct.create_statefbk_iosystem(
922+
unicycle, (gains, points), gainsched_indices=[3, 2])
923+
xd, ud = np.array([0, 0, angles[0]]), np.array([speeds[0], 0])
924+
ctrl_lin = ctrl.linearize([], [xd, ud, xd*0])
925+
K, S, E = ct.lqr(unicycle.linearize(xd, ud), Q, R)
926+
np.testing.assert_allclose(
927+
ctrl_lin.D[-xd.size:, -xd.size:], -K, rtol=1e-2)
928+
929+
# Wrong type of gain schedule argument
930+
with pytest.raises(ControlArgument, match="gain must be an array"):
931+
ctrl, clsys = ct.create_statefbk_iosystem(
932+
unicycle, [gains, points], gainsched_indices=[3, 2])
933+
934+
# Mismatched dimensions for gains and points
935+
with pytest.raises(ControlArgument, match="length of gainsched_indices"):
936+
ctrl, clsys = ct.create_statefbk_iosystem(
937+
unicycle, (gains, [speeds]), gainsched_indices=[3, 2])
938+
939+
# Unknown gain scheduling variable label
940+
with pytest.raises(ValueError, match=".* not in list"):
941+
ctrl, clsys = ct.create_statefbk_iosystem(
942+
unicycle, (gains, points), gainsched_indices=['stuff', 2])
943+
944+
# Unknown gain scheduling method
945+
with pytest.raises(ControlArgument, match="unknown gain scheduling method"):
946+
ctrl, clsys = ct.create_statefbk_iosystem(
947+
unicycle, (gains, points, 'stuff'), gainsched_indices=[3, 2])

0 commit comments

Comments
 (0)