Skip to content

Commit 8f3615b

Browse files
committed
add param processing to create_statefbk_iosystem
1 parent 34d5d03 commit 8f3615b

File tree

3 files changed

+73
-19
lines changed

3 files changed

+73
-19
lines changed

control/statefbk.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,8 @@ def create_statefbk_iosystem(
586586
controller_type=None, xd_labels=None, ud_labels=None, ref_labels=None,
587587
feedfwd_pattern='trajgen', gainsched_indices=None,
588588
gainsched_method='linear', control_indices=None, state_indices=None,
589-
name=None, inputs=None, outputs=None, states=None, **kwargs):
589+
name=None, inputs=None, outputs=None, states=None, params=None,
590+
**kwargs):
590591
r"""Create an I/O system using a (full) state feedback controller.
591592
592593
This function creates an input/output system that implements a
@@ -751,6 +752,10 @@ def create_statefbk_iosystem(
751752
System name. If unspecified, a generic name <sys[id]> is generated
752753
with a unique integer id.
753754
755+
params : dict, optional
756+
System parameter values. By default, these will be copied from
757+
`sys` and `ctrl`, but can be overriden with this keyword.
758+
754759
Examples
755760
--------
756761
>>> import control as ct
@@ -773,7 +778,8 @@ def create_statefbk_iosystem(
773778
if not isinstance(sys, NonlinearIOSystem):
774779
raise ControlArgument("Input system must be I/O system")
775780

776-
# Process (legacy) keywords
781+
# Process keywords
782+
params = sys.params if params is None else params
777783
controller_type = _process_legacy_keyword(
778784
kwargs, 'type', 'controller_type', controller_type)
779785
if kwargs:
@@ -970,10 +976,10 @@ def _control_output(t, states, inputs, params):
970976

971977
return u
972978

973-
params = {} if gainsched else {'K': K}
979+
ctrl_params = {} if gainsched else {'K': K}
974980
ctrl = NonlinearIOSystem(
975981
_control_update, _control_output, name=name, inputs=inputs,
976-
outputs=outputs, states=states, params=params)
982+
outputs=outputs, states=states, params=ctrl_params)
977983

978984
elif controller_type == 'iosystem' and feedfwd_pattern == 'trajgen':
979985
# Use the passed system to compute feedback compensation
@@ -1061,7 +1067,8 @@ def _control_output(t, states, inputs, params):
10611067
[sys, ctrl] if estimator == sys else [sys, ctrl, estimator],
10621068
name=sys.name + "_" + ctrl.name, add_unused=True,
10631069
inplist=inplist, inputs=input_labels,
1064-
outlist=outlist, outputs=output_labels
1070+
outlist=outlist, outputs=output_labels,
1071+
params= ctrl.params | params
10651072
)
10661073
return ctrl, closed
10671074

control/tests/statefbk_test.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -930,9 +930,10 @@ def unicycle_update(t, x, u, params):
930930

931931
return ct.NonlinearIOSystem(
932932
unicycle_update, None,
933-
inputs = ['v', 'phi'],
934-
outputs = ['x', 'y', 'theta'],
935-
states = ['x_', 'y_', 'theta_'])
933+
inputs=['v', 'phi'],
934+
outputs=['x', 'y', 'theta'],
935+
states=['x_', 'y_', 'theta_'],
936+
params={'a': 1}) # only used for testing params
936937

937938
from math import pi
938939

@@ -1194,3 +1195,40 @@ def test_create_statefbk_errors():
11941195

11951196
with pytest.raises(ControlArgument, match="feedfwd_pattern != 'refgain'"):
11961197
ct.create_statefbk_iosystem(sys, K, Kf, feedfwd_pattern='trajgen')
1198+
1199+
1200+
def test_create_statefbk_params(unicycle):
1201+
# Speeds and angles at which to compute the gains
1202+
speeds = [1, 5, 10]
1203+
angles = np.linspace(0, pi/2, 4)
1204+
points = list(itertools.product(speeds, angles))
1205+
1206+
# Gains for each speed (using LQR controller)
1207+
Q = np.identity(unicycle.nstates)
1208+
R = np.identity(unicycle.ninputs)
1209+
gain, _, _ = ct.lqr(unicycle.linearize([0, 0, 0], [5, 0]), Q, R)
1210+
1211+
#
1212+
# Schedule on desired speed and angle
1213+
#
1214+
1215+
# Create a linear controller
1216+
ctrl, clsys = ct.create_statefbk_iosystem(unicycle, gain)
1217+
assert [k for k in ctrl.params.keys()] == []
1218+
assert [k for k in clsys.params.keys()] == ['a']
1219+
assert clsys.params['a'] == 1
1220+
1221+
# Create a nonlinear controller
1222+
ctrl, clsys = ct.create_statefbk_iosystem(
1223+
unicycle, gain, controller_type='nonlinear')
1224+
assert [k for k in ctrl.params.keys()] == ['K']
1225+
assert [k for k in clsys.params.keys()] == ['K', 'a']
1226+
assert clsys.params['a'] == 1
1227+
1228+
# Override the default parameters
1229+
ctrl, clsys = ct.create_statefbk_iosystem(
1230+
unicycle, gain, controller_type='nonlinear', params={'a': 2, 'b': 1})
1231+
assert [k for k in ctrl.params.keys()] == ['K']
1232+
assert [k for k in clsys.params.keys()] == ['K', 'a', 'b']
1233+
assert clsys.params['a'] == 2
1234+
assert clsys.params['b'] == 1

examples/steering-gainsched.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ def vehicle_output(t, x, u, params):
4646
return x # return x, y, theta (full state)
4747

4848
# Define the vehicle steering dynamics as an input/output system
49-
vehicle = ct.NonlinearIOSystem(
49+
vehicle = ct.nlsys(
5050
vehicle_update, vehicle_output, states=3, name='vehicle',
51-
inputs=('v', 'phi'),
52-
outputs=('x', 'y', 'theta'))
51+
inputs=('v', 'phi'), outputs=('x', 'y', 'theta'),
52+
params={'wheelbase': 3, 'maxsteer': 0.5})
5353

5454
#
5555
# Gain scheduled controller
@@ -89,10 +89,12 @@ def control_output(t, x, u, params):
8989
return np.array([v, phi])
9090

9191
# Define the controller as an input/output system
92-
controller = ct.NonlinearIOSystem(
92+
controller = ct.nlsys(
9393
None, control_output, name='controller', # static system
9494
inputs=('ex', 'ey', 'etheta', 'vd', 'phid'), # system inputs
95-
outputs=('v', 'phi') # system outputs
95+
outputs=('v', 'phi'), # system outputs
96+
params={'longpole': -2, 'latpole1': -1/2 + sqrt(-7)/2,
97+
'latpole2': -1/2 - sqrt(-7)/2, 'wheelbase': 3}
9698
)
9799

98100
#
@@ -113,7 +115,7 @@ def trajgen_output(t, x, u, params):
113115
return np.array([vref * t, yref, 0, vref, 0])
114116

115117
# Define the trajectory generator as an input/output system
116-
trajgen = ct.NonlinearIOSystem(
118+
trajgen = ct.nlsys(
117119
None, trajgen_output, name='trajgen',
118120
inputs=('vref', 'yref'),
119121
outputs=('xd', 'yd', 'thetad', 'vd', 'phid'))
@@ -156,10 +158,13 @@ def trajgen_output(t, x, u, params):
156158
inplist=['trajgen.vref', 'trajgen.yref'],
157159
inputs=['yref', 'vref'],
158160

159-
# System outputs
161+
# System outputs
160162
outlist=['vehicle.x', 'vehicle.y', 'vehicle.theta', 'controller.v',
161163
'controller.phi'],
162-
outputs=['x', 'y', 'theta', 'v', 'phi']
164+
outputs=['x', 'y', 'theta', 'v', 'phi'],
165+
166+
# Parameters
167+
params=trajgen.params | vehicle.params | controller.params,
163168
)
164169

165170
# Set up the simulation conditions
@@ -220,7 +225,8 @@ def trajgen_output(t, x, u, params):
220225
# Create the gain scheduled system
221226
controller, _ = ct.create_statefbk_iosystem(
222227
vehicle, (gains, points), name='controller', ud_labels=['vd', 'phid'],
223-
gainsched_indices=['vd', 'theta'], gainsched_method='linear')
228+
gainsched_indices=['vd', 'theta'], gainsched_method='linear',
229+
params=vehicle.params | controller.params)
224230

225231
# Connect everything together (note that controller inputs are different)
226232
steering = ct.interconnect(
@@ -245,10 +251,13 @@ def trajgen_output(t, x, u, params):
245251
inplist=['trajgen.vref', 'trajgen.yref'],
246252
inputs=['yref', 'vref'],
247253

248-
# System outputs
254+
# System outputs
249255
outlist=['vehicle.x', 'vehicle.y', 'vehicle.theta', 'controller.v',
250256
'controller.phi'],
251-
outputs=['x', 'y', 'theta', 'v', 'phi']
257+
outputs=['x', 'y', 'theta', 'v', 'phi'],
258+
259+
# Parameters
260+
params=steering.params
252261
)
253262

254263
# Plot the results to compare to the previous case

0 commit comments

Comments
 (0)