Skip to content

Commit cb6d9d7

Browse files
authored
Merge pull request #713 from murrayrm/kwargs_20Mar2022
Keyword argument checking
2 parents cfe21de + 87cb31a commit cb6d9d7

17 files changed

+302
-76
lines changed

control/config.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ def set_defaults(module, **keywords):
7373
if not isinstance(module, str):
7474
raise ValueError("module must be a string")
7575
for key, val in keywords.items():
76+
keyname = module + '.' + key
77+
if keyname not in defaults and f"deprecated.{keyname}" not in defaults:
78+
raise TypeError(f"unrecognized keyword: {key}")
7679
defaults[module + '.' + key] = val
7780

7881

@@ -289,6 +292,6 @@ def use_legacy_defaults(version):
289292
set_defaults('control', squeeze_time_response=True)
290293

291294
# switched mirror_style of nyquist from '-' to '--'
292-
set_defaults('nyqist', mirror_style='-')
295+
set_defaults('nyquist', mirror_style='-')
293296

294297
return (major, minor, patch)

control/frdata.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,11 @@ def __init__(self, *args, **kwargs):
150150
151151
"""
152152
# TODO: discrete-time FRD systems?
153-
smooth = kwargs.get('smooth', False)
153+
smooth = kwargs.pop('smooth', False)
154+
155+
# Make sure there were no extraneous keywords
156+
if kwargs:
157+
raise TypeError("unrecognized keywords: ", str(kwargs))
154158

155159
if len(args) == 2:
156160
if not isinstance(args[0], FRD) and isinstance(args[0], LTI):

control/freqplot.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,10 @@ def bode_plot(syslist, omega=None,
221221
# Get the current figure
222222

223223
if 'sisotool' in kwargs:
224-
fig = kwargs['fig']
224+
fig = kwargs.pop('fig')
225225
ax_mag = fig.axes[0]
226226
ax_phase = fig.axes[2]
227-
sisotool = kwargs['sisotool']
228-
del kwargs['fig']
229-
del kwargs['sisotool']
227+
sisotool = kwargs.pop('sisotool')
230228
else:
231229
fig = plt.gcf()
232230
ax_mag = None

control/iosys.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
148148
# timebase
149149
self.dt = kwargs.pop('dt', config.defaults['control.default_dt'])
150150

151-
# Make sure there were no extraneous keyworks
151+
# Make sure there were no extraneous keywords
152152
if kwargs:
153153
raise TypeError("unrecognized keywords: ", str(kwargs))
154154

@@ -560,7 +560,7 @@ def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
560560

561561
# Create the state space system
562562
linsys = LinearIOSystem(
563-
StateSpace(A, B, C, D, self.dt, remove_useless=False),
563+
StateSpace(A, B, C, D, self.dt, remove_useless_states=False),
564564
name=name, **kwargs)
565565

566566
# Set the names the system, inputs, outputs, and states
@@ -660,7 +660,7 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,
660660
states=linsys.nstates, params={}, dt=linsys.dt, name=name)
661661

662662
# Initalize additional state space variables
663-
StateSpace.__init__(self, linsys, remove_useless=False)
663+
StateSpace.__init__(self, linsys, remove_useless_states=False)
664664

665665
# Process input, output, state lists, if given
666666
# Make sure they match the size of the linear system
@@ -790,9 +790,9 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None,
790790
params=params, dt=dt, name=name
791791
)
792792

793-
# Make sure all input arguments got parsed
793+
# Make sure there were no extraneous keywords
794794
if kwargs:
795-
raise TypeError("unknown parameters %s" % kwargs)
795+
raise TypeError("unrecognized keywords: ", str(kwargs))
796796

797797
# Check to make sure arguments are consistent
798798
if updfcn is None:
@@ -1551,7 +1551,7 @@ def __init__(self, io_sys, ss_sys=None):
15511551
io_sys.nstates != ss_sys.nstates:
15521552
raise ValueError("System dimensions for first and second "
15531553
"arguments must match.")
1554-
StateSpace.__init__(self, ss_sys, remove_useless=False)
1554+
StateSpace.__init__(self, ss_sys, remove_useless_states=False)
15551555

15561556
else:
15571557
raise TypeError("Second argument must be a state space system.")
@@ -1656,14 +1656,14 @@ def input_output_response(
16561656
raise ValueError("ivp_method specified more than once")
16571657
solve_ivp_kwargs['method'] = kwargs.pop('solve_ivp_method')
16581658

1659+
# Make sure there were no extraneous keywords
1660+
if kwargs:
1661+
raise TypeError("unrecognized keywords: ", str(kwargs))
1662+
16591663
# Set the default method to 'RK45'
16601664
if solve_ivp_kwargs.get('method', None) is None:
16611665
solve_ivp_kwargs['method'] = 'RK45'
16621666

1663-
# Make sure all input arguments got parsed
1664-
if kwargs:
1665-
raise TypeError("unknown parameters %s" % kwargs)
1666-
16671667
# Sanity checking on the input
16681668
if not isinstance(sys, InputOutputSystem):
16691669
raise TypeError("System of type ", type(sys), " not valid")
@@ -1829,7 +1829,7 @@ def ivp_rhs(t, x):
18291829

18301830
def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
18311831
iu=None, iy=None, ix=None, idx=None, dx0=None,
1832-
return_y=False, return_result=False, **kw):
1832+
return_y=False, return_result=False):
18331833
"""Find the equilibrium point for an input/output system.
18341834
18351835
Returns the value of an equilibrium point given the initial state and
@@ -1933,7 +1933,7 @@ def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
19331933
# Take u0 as fixed and minimize over x
19341934
# TODO: update to allow discrete time systems
19351935
def ode_rhs(z): return sys._rhs(t, z, u0)
1936-
result = root(ode_rhs, x0, **kw)
1936+
result = root(ode_rhs, x0)
19371937
z = (result.x, u0, sys._out(t, result.x, u0))
19381938
else:
19391939
# Take y0 as fixed and minimize over x and u
@@ -1944,7 +1944,7 @@ def rootfun(z):
19441944
return np.concatenate(
19451945
(sys._rhs(t, x, u), sys._out(t, x, u) - y0), axis=0)
19461946
z0 = np.concatenate((x0, u0), axis=0) # Put variables together
1947-
result = root(rootfun, z0, **kw) # Find the eq point
1947+
result = root(rootfun, z0) # Find the eq point
19481948
x, u = np.split(result.x, [nstates]) # Split result back in two
19491949
z = (x, u, sys._out(t, x, u))
19501950

@@ -2056,7 +2056,7 @@ def rootfun(z):
20562056
z0 = np.concatenate((x[state_vars], u[input_vars]), axis=0)
20572057

20582058
# Finally, call the root finding function
2059-
result = root(rootfun, z0, **kw)
2059+
result = root(rootfun, z0)
20602060

20612061
# Extract out the results and insert into x and u
20622062
x[state_vars] = result.x[:nstate_vars]
@@ -2134,7 +2134,8 @@ def _parse_signal_parameter(value, name, kwargs, end=False):
21342134
value = kwargs.pop(name)
21352135

21362136
if end and kwargs:
2137-
raise TypeError("unknown parameters %s" % kwargs)
2137+
raise TypeError("unrecognized keywords: ", str(kwargs))
2138+
21382139
return value
21392140

21402141

@@ -2237,7 +2238,15 @@ def ss(*args, **kwargs):
22372238
>>> sys2 = ss(sys_tf)
22382239
22392240
"""
2240-
sys = _ss(*args, keywords=kwargs)
2241+
# Extract the keyword arguments needed for StateSpace (via _ss)
2242+
ss_kwlist = ('dt', 'remove_useless_states')
2243+
ss_kwargs = {}
2244+
for kw in ss_kwlist:
2245+
if kw in kwargs:
2246+
ss_kwargs[kw] = kwargs.pop(kw)
2247+
2248+
# Create the statespace system and then convert to I/O system
2249+
sys = _ss(*args, keywords=ss_kwargs)
22412250
return LinearIOSystem(sys, **kwargs)
22422251

22432252

control/optimal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def __init__(
154154
self.minimize_kwargs.update(kwargs.pop(
155155
'minimize_kwargs', config.defaults['optimal.minimize_kwargs']))
156156

157-
# Make sure all input arguments got parsed
157+
# Make sure there were no extraneous keywords
158158
if kwargs:
159159
raise TypeError("unrecognized keyword(s): ", str(kwargs))
160160

control/pzmap.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,11 @@ def pzmap(sys, plot=None, grid=None, title='Pole Zero Map', **kwargs):
9191
import warnings
9292
warnings.warn("'Plot' keyword is deprecated in pzmap; use 'plot'",
9393
FutureWarning)
94-
plot = kwargs['Plot']
94+
plot = kwargs.pop('Plot')
95+
96+
# Make sure there were no extraneous keywords
97+
if kwargs:
98+
raise TypeError("unrecognized keywords: ", str(kwargs))
9599

96100
# Get parameter values
97101
plot = config._get_param('pzmap', 'plot', plot, True)

control/rlocus.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
168168
# Check for sisotool mode
169169
sisotool = False if 'sisotool' not in kwargs else True
170170

171+
# Make sure there were no extraneous keywords
172+
if not sisotool and kwargs:
173+
raise TypeError("unrecognized keywords: ", str(kwargs))
174+
171175
# Create the Plot
172176
if plot:
173177
if sisotool:

control/statefbk.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def place_varga(A, B, p, dtime=False, alpha=None):
261261

262262

263263
# contributed by Sawyer B. Fuller <minster@uw.edu>
264-
def lqe(*args, **keywords):
264+
def lqe(*args, method=None):
265265
"""lqe(A, G, C, QN, RN, [, NN])
266266
267267
Linear quadratic estimator design (Kalman filter) for continuous-time
@@ -356,18 +356,15 @@ def lqe(*args, **keywords):
356356
# Process the arguments and figure out what inputs we received
357357
#
358358

359-
# Get the method to use (if specified as a keyword)
360-
method = keywords.get('method', None)
359+
# If we were passed a discrete time system as the first arg, use dlqe()
360+
if isinstance(args[0], LTI) and isdtime(args[0], strict=True):
361+
# Call dlqe
362+
return dlqe(*args, method=method)
361363

362364
# Get the system description
363365
if (len(args) < 3):
364366
raise ControlArgument("not enough input arguments")
365367

366-
# If we were passed a discrete time system as the first arg, use dlqe()
367-
if isinstance(args[0], LTI) and isdtime(args[0], strict=True):
368-
# Call dlqe
369-
return dlqe(*args, **keywords)
370-
371368
# If we were passed a state space system, use that to get system matrices
372369
if isinstance(args[0], StateSpace):
373370
A = np.array(args[0].A, ndmin=2, dtype=float)
@@ -409,7 +406,7 @@ def lqe(*args, **keywords):
409406

410407

411408
# contributed by Sawyer B. Fuller <minster@uw.edu>
412-
def dlqe(*args, **keywords):
409+
def dlqe(*args, method=None):
413410
"""dlqe(A, G, C, QN, RN, [, N])
414411
415412
Linear quadratic estimator design (Kalman filter) for discrete-time
@@ -480,9 +477,6 @@ def dlqe(*args, **keywords):
480477
# Process the arguments and figure out what inputs we received
481478
#
482479

483-
# Get the method to use (if specified as a keyword)
484-
method = keywords.get('method', None)
485-
486480
# Get the system description
487481
if (len(args) < 3):
488482
raise ControlArgument("not enough input arguments")

control/statesp.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,10 @@ def __init__(self, *args, keywords=None, **kwargs):
340340
self.dt = dt
341341
self.nstates = A.shape[1]
342342

343+
# Make sure there were no extraneous keywords
344+
if keywords:
345+
raise TypeError("unrecognized keywords: ", str(keywords))
346+
343347
if 0 == self.nstates:
344348
# static gain
345349
# matrix's default "empty" shape is 1x0
@@ -1776,7 +1780,12 @@ def _ss(*args, keywords=None, **kwargs):
17761780
"""Internal function to create StateSpace system"""
17771781
if len(args) == 4 or len(args) == 5:
17781782
return StateSpace(*args, keywords=keywords, **kwargs)
1783+
17791784
elif len(args) == 1:
1785+
# Make sure there were no extraneous keywords
1786+
if kwargs:
1787+
raise TypeError("unrecognized keywords: ", str(kwargs))
1788+
17801789
from .xferfcn import TransferFunction
17811790
sys = args[0]
17821791
if isinstance(sys, StateSpace):

control/tests/config_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ class TestConfig:
2323
sys = ct.tf([10], [1, 2, 1])
2424

2525
def test_set_defaults(self):
26-
ct.config.set_defaults('config', test1=1, test2=2, test3=None)
27-
assert ct.config.defaults['config.test1'] == 1
28-
assert ct.config.defaults['config.test2'] == 2
29-
assert ct.config.defaults['config.test3'] is None
26+
ct.config.set_defaults('freqplot', dB=1, deg=2, Hz=None)
27+
assert ct.config.defaults['freqplot.dB'] == 1
28+
assert ct.config.defaults['freqplot.deg'] == 2
29+
assert ct.config.defaults['freqplot.Hz'] is None
3030

3131
@mplcleanup
3232
def test_get_param(self):

control/tests/frd_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,9 @@ def test_repr_str(self):
472472
10.000 0.2 +4j
473473
100.000 0.1 +6j"""
474474
assert str(sysm) == refm
475+
476+
def test_unrecognized_keyword(self):
477+
h = TransferFunction([1], [1, 2, 2])
478+
omega = np.logspace(-1, 2, 10)
479+
with pytest.raises(TypeError, match="unrecognized keyword"):
480+
frd = FRD(h, omega, unknown=None)

control/tests/interconnect_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,27 +188,27 @@ def test_interconnect_exceptions():
188188

189189
# Unrecognized arguments
190190
# LinearIOSystem
191-
with pytest.raises(TypeError, match="unknown parameter"):
191+
with pytest.raises(TypeError, match="unrecognized keyword"):
192192
P = ct.LinearIOSystem(ct.rss(2, 1, 1), output_name='y')
193193

194194
# Interconnect
195-
with pytest.raises(TypeError, match="unknown parameter"):
195+
with pytest.raises(TypeError, match="unrecognized keyword"):
196196
T = ct.interconnect((P, C, sumblk), input_name='r', output='y')
197197

198198
# Interconnected system
199-
with pytest.raises(TypeError, match="unknown parameter"):
199+
with pytest.raises(TypeError, match="unrecognized keyword"):
200200
T = ct.InterconnectedSystem((P, C, sumblk), input_name='r', output='y')
201201

202202
# NonlinearIOSytem
203-
with pytest.raises(TypeError, match="unknown parameter"):
203+
with pytest.raises(TypeError, match="unrecognized keyword"):
204204
nlios = ct.NonlinearIOSystem(
205205
None, lambda t, x, u, params: u*u, input_count=1, output_count=1)
206206

207207
# Summing junction
208208
with pytest.raises(TypeError, match="input specification is required"):
209209
sumblk = ct.summing_junction()
210210

211-
with pytest.raises(TypeError, match="unknown parameter"):
211+
with pytest.raises(TypeError, match="unrecognized keyword"):
212212
sumblk = ct.summing_junction(input_count=2, output_count=2)
213213

214214

control/tests/iosys_test.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,7 +1580,8 @@ def test_interconnect_unused_input():
15801580
outputs=['u'],
15811581
name='k')
15821582

1583-
with pytest.warns(UserWarning, match=r"Unused input\(s\) in InterconnectedSystem"):
1583+
with pytest.warns(
1584+
UserWarning, match=r"Unused input\(s\) in InterconnectedSystem"):
15841585
h = ct.interconnect([g,s,k],
15851586
inputs=['r'],
15861587
outputs=['y'])
@@ -1611,13 +1612,19 @@ def test_interconnect_unused_input():
16111612

16121613

16131614
# warn if explicity ignored input in fact used
1614-
with pytest.warns(UserWarning, match=r"Input\(s\) specified as ignored is \(are\) used:") as record:
1615+
with pytest.warns(
1616+
UserWarning,
1617+
match=r"Input\(s\) specified as ignored is \(are\) used:") \
1618+
as record:
16151619
h = ct.interconnect([g,s,k],
16161620
inputs=['r'],
16171621
outputs=['y'],
16181622
ignore_inputs=['u','n'])
16191623

1620-
with pytest.warns(UserWarning, match=r"Input\(s\) specified as ignored is \(are\) used:") as record:
1624+
with pytest.warns(
1625+
UserWarning,
1626+
match=r"Input\(s\) specified as ignored is \(are\) used:") \
1627+
as record:
16211628
h = ct.interconnect([g,s,k],
16221629
inputs=['r'],
16231630
outputs=['y'],

0 commit comments

Comments
 (0)