Skip to content

Keyword argument checking #713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 30, 2022
Merged
5 changes: 4 additions & 1 deletion control/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def set_defaults(module, **keywords):
if not isinstance(module, str):
raise ValueError("module must be a string")
for key, val in keywords.items():
keyname = module + '.' + key
if keyname not in defaults and f"deprecated.{keyname}" not in defaults:
raise TypeError(f"unrecognized keyword: {key}")
defaults[module + '.' + key] = val


Expand Down Expand Up @@ -289,6 +292,6 @@ def use_legacy_defaults(version):
set_defaults('control', squeeze_time_response=True)

# switched mirror_style of nyquist from '-' to '--'
set_defaults('nyqist', mirror_style='-')
set_defaults('nyquist', mirror_style='-')

return (major, minor, patch)
6 changes: 5 additions & 1 deletion control/frdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,11 @@ def __init__(self, *args, **kwargs):

"""
# TODO: discrete-time FRD systems?
smooth = kwargs.get('smooth', False)
smooth = kwargs.pop('smooth', False)

# Make sure there were no extraneous keywords
if kwargs:
raise TypeError("unrecognized keywords: ", str(kwargs))

if len(args) == 2:
if not isinstance(args[0], FRD) and isinstance(args[0], LTI):
Expand Down
6 changes: 2 additions & 4 deletions control/freqplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,12 +221,10 @@ def bode_plot(syslist, omega=None,
# Get the current figure

if 'sisotool' in kwargs:
fig = kwargs['fig']
fig = kwargs.pop('fig')
ax_mag = fig.axes[0]
ax_phase = fig.axes[2]
sisotool = kwargs['sisotool']
del kwargs['fig']
del kwargs['sisotool']
sisotool = kwargs.pop('sisotool')
else:
fig = plt.gcf()
ax_mag = None
Expand Down
41 changes: 25 additions & 16 deletions control/iosys.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def __init__(self, inputs=None, outputs=None, states=None, params={},
# timebase
self.dt = kwargs.pop('dt', config.defaults['control.default_dt'])

# Make sure there were no extraneous keyworks
# Make sure there were no extraneous keywords
if kwargs:
raise TypeError("unrecognized keywords: ", str(kwargs))

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

# Create the state space system
linsys = LinearIOSystem(
StateSpace(A, B, C, D, self.dt, remove_useless=False),
StateSpace(A, B, C, D, self.dt, remove_useless_states=False),
name=name, **kwargs)

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

# Initalize additional state space variables
StateSpace.__init__(self, linsys, remove_useless=False)
StateSpace.__init__(self, linsys, remove_useless_states=False)

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

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

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

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

# Make sure there were no extraneous keywords
if kwargs:
raise TypeError("unrecognized keywords: ", str(kwargs))

# Set the default method to 'RK45'
if solve_ivp_kwargs.get('method', None) is None:
solve_ivp_kwargs['method'] = 'RK45'

# Make sure all input arguments got parsed
if kwargs:
raise TypeError("unknown parameters %s" % kwargs)

# Sanity checking on the input
if not isinstance(sys, InputOutputSystem):
raise TypeError("System of type ", type(sys), " not valid")
Expand Down Expand Up @@ -1829,7 +1829,7 @@ def ivp_rhs(t, x):

def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
iu=None, iy=None, ix=None, idx=None, dx0=None,
return_y=False, return_result=False, **kw):
return_y=False, return_result=False):
"""Find the equilibrium point for an input/output system.

Returns the value of an equilibrium point given the initial state and
Expand Down Expand Up @@ -1933,7 +1933,7 @@ def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
# Take u0 as fixed and minimize over x
# TODO: update to allow discrete time systems
def ode_rhs(z): return sys._rhs(t, z, u0)
result = root(ode_rhs, x0, **kw)
result = root(ode_rhs, x0)
z = (result.x, u0, sys._out(t, result.x, u0))
else:
# Take y0 as fixed and minimize over x and u
Expand All @@ -1944,7 +1944,7 @@ def rootfun(z):
return np.concatenate(
(sys._rhs(t, x, u), sys._out(t, x, u) - y0), axis=0)
z0 = np.concatenate((x0, u0), axis=0) # Put variables together
result = root(rootfun, z0, **kw) # Find the eq point
result = root(rootfun, z0) # Find the eq point
x, u = np.split(result.x, [nstates]) # Split result back in two
z = (x, u, sys._out(t, x, u))

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

# Finally, call the root finding function
result = root(rootfun, z0, **kw)
result = root(rootfun, z0)

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

if end and kwargs:
raise TypeError("unknown parameters %s" % kwargs)
raise TypeError("unrecognized keywords: ", str(kwargs))

return value


Expand Down Expand Up @@ -2237,7 +2238,15 @@ def ss(*args, **kwargs):
>>> sys2 = ss(sys_tf)

"""
sys = _ss(*args, keywords=kwargs)
# Extract the keyword arguments needed for StateSpace (via _ss)
ss_kwlist = ('dt', 'remove_useless_states')
ss_kwargs = {}
for kw in ss_kwlist:
if kw in kwargs:
ss_kwargs[kw] = kwargs.pop(kw)

# Create the statespace system and then convert to I/O system
sys = _ss(*args, keywords=ss_kwargs)
return LinearIOSystem(sys, **kwargs)


Expand Down
2 changes: 1 addition & 1 deletion control/optimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def __init__(
self.minimize_kwargs.update(kwargs.pop(
'minimize_kwargs', config.defaults['optimal.minimize_kwargs']))

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

Expand Down
6 changes: 5 additions & 1 deletion control/pzmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ def pzmap(sys, plot=None, grid=None, title='Pole Zero Map', **kwargs):
import warnings
warnings.warn("'Plot' keyword is deprecated in pzmap; use 'plot'",
FutureWarning)
plot = kwargs['Plot']
plot = kwargs.pop('Plot')

# Make sure there were no extraneous keywords
if kwargs:
raise TypeError("unrecognized keywords: ", str(kwargs))

# Get parameter values
plot = config._get_param('pzmap', 'plot', plot, True)
Expand Down
4 changes: 4 additions & 0 deletions control/rlocus.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ def root_locus(sys, kvect=None, xlim=None, ylim=None,
# Check for sisotool mode
sisotool = False if 'sisotool' not in kwargs else True

# Make sure there were no extraneous keywords
if not sisotool and kwargs:
raise TypeError("unrecognized keywords: ", str(kwargs))

# Create the Plot
if plot:
if sisotool:
Expand Down
18 changes: 6 additions & 12 deletions control/statefbk.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def place_varga(A, B, p, dtime=False, alpha=None):


# contributed by Sawyer B. Fuller <minster@uw.edu>
def lqe(*args, **keywords):
def lqe(*args, method=None):
"""lqe(A, G, C, QN, RN, [, NN])

Linear quadratic estimator design (Kalman filter) for continuous-time
Expand Down Expand Up @@ -356,18 +356,15 @@ def lqe(*args, **keywords):
# Process the arguments and figure out what inputs we received
#

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

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

# If we were passed a discrete time system as the first arg, use dlqe()
if isinstance(args[0], LTI) and isdtime(args[0], strict=True):
# Call dlqe
return dlqe(*args, **keywords)

# If we were passed a state space system, use that to get system matrices
if isinstance(args[0], StateSpace):
A = np.array(args[0].A, ndmin=2, dtype=float)
Expand Down Expand Up @@ -409,7 +406,7 @@ def lqe(*args, **keywords):


# contributed by Sawyer B. Fuller <minster@uw.edu>
def dlqe(*args, **keywords):
def dlqe(*args, method=None):
"""dlqe(A, G, C, QN, RN, [, N])

Linear quadratic estimator design (Kalman filter) for discrete-time
Expand Down Expand Up @@ -480,9 +477,6 @@ def dlqe(*args, **keywords):
# Process the arguments and figure out what inputs we received
#

# Get the method to use (if specified as a keyword)
method = keywords.get('method', None)

# Get the system description
if (len(args) < 3):
raise ControlArgument("not enough input arguments")
Expand Down
9 changes: 9 additions & 0 deletions control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ def __init__(self, *args, keywords=None, **kwargs):
self.dt = dt
self.nstates = A.shape[1]

# Make sure there were no extraneous keywords
if keywords:
raise TypeError("unrecognized keywords: ", str(keywords))

if 0 == self.nstates:
# static gain
# matrix's default "empty" shape is 1x0
Expand Down Expand Up @@ -1776,7 +1780,12 @@ def _ss(*args, keywords=None, **kwargs):
"""Internal function to create StateSpace system"""
if len(args) == 4 or len(args) == 5:
return StateSpace(*args, keywords=keywords, **kwargs)

elif len(args) == 1:
# Make sure there were no extraneous keywords
if kwargs:
raise TypeError("unrecognized keywords: ", str(kwargs))

from .xferfcn import TransferFunction
sys = args[0]
if isinstance(sys, StateSpace):
Expand Down
8 changes: 4 additions & 4 deletions control/tests/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ class TestConfig:
sys = ct.tf([10], [1, 2, 1])

def test_set_defaults(self):
ct.config.set_defaults('config', test1=1, test2=2, test3=None)
assert ct.config.defaults['config.test1'] == 1
assert ct.config.defaults['config.test2'] == 2
assert ct.config.defaults['config.test3'] is None
ct.config.set_defaults('freqplot', dB=1, deg=2, Hz=None)
assert ct.config.defaults['freqplot.dB'] == 1
assert ct.config.defaults['freqplot.deg'] == 2
assert ct.config.defaults['freqplot.Hz'] is None

@mplcleanup
def test_get_param(self):
Expand Down
6 changes: 6 additions & 0 deletions control/tests/frd_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,3 +472,9 @@ def test_repr_str(self):
10.000 0.2 +4j
100.000 0.1 +6j"""
assert str(sysm) == refm

def test_unrecognized_keyword(self):
h = TransferFunction([1], [1, 2, 2])
omega = np.logspace(-1, 2, 10)
with pytest.raises(TypeError, match="unrecognized keyword"):
frd = FRD(h, omega, unknown=None)
10 changes: 5 additions & 5 deletions control/tests/interconnect_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,27 +188,27 @@ def test_interconnect_exceptions():

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

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

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

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

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

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


Expand Down
13 changes: 10 additions & 3 deletions control/tests/iosys_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,8 @@ def test_interconnect_unused_input():
outputs=['u'],
name='k')

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


# warn if explicity ignored input in fact used
with pytest.warns(UserWarning, match=r"Input\(s\) specified as ignored is \(are\) used:") as record:
with pytest.warns(
UserWarning,
match=r"Input\(s\) specified as ignored is \(are\) used:") \
as record:
h = ct.interconnect([g,s,k],
inputs=['r'],
outputs=['y'],
ignore_inputs=['u','n'])

with pytest.warns(UserWarning, match=r"Input\(s\) specified as ignored is \(are\) used:") as record:
with pytest.warns(
UserWarning,
match=r"Input\(s\) specified as ignored is \(are\) used:") \
as record:
h = ct.interconnect([g,s,k],
inputs=['r'],
outputs=['y'],
Expand Down
Loading