Skip to content

Commit c82605b

Browse files
Merge branch 'main' into fix_isstatic-12Nov2022
2 parents c425d2c + b32e355 commit c82605b

File tree

4 files changed

+126
-46
lines changed

4 files changed

+126
-46
lines changed

control/iosys.py

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ class for a set of subclasses that are used to implement specific
126126
# Allow ndarray * InputOutputSystem to give IOSystem._rmul_() priority
127127
__array_priority__ = 12 # override ndarray, matrix, SS types
128128

129-
def __init__(self, params={}, **kwargs):
129+
def __init__(self, params=None, **kwargs):
130130
"""Create an input/output system.
131131
132132
The InputOutputSystem constructor is used to create an input/output
@@ -148,7 +148,7 @@ def __init__(self, params={}, **kwargs):
148148
states=states, name=name, dt=dt)
149149

150150
# default parameters
151-
self.params = params.copy()
151+
self.params = {} if params is None else params.copy()
152152

153153
def __mul__(sys2, sys1):
154154
"""Multiply two input/output systems (series interconnection)"""
@@ -357,7 +357,7 @@ def _update_params(self, params, warning=False):
357357
if warning:
358358
warn("Parameters passed to InputOutputSystem ignored.")
359359

360-
def _rhs(self, t, x, u, params={}):
360+
def _rhs(self, t, x, u):
361361
"""Evaluate right hand side of a differential or difference equation.
362362
363363
Private function used to compute the right hand side of an
@@ -369,23 +369,24 @@ def _rhs(self, t, x, u, params={}):
369369
NotImplemented("Evaluation not implemented for system of type ",
370370
type(self))
371371

372-
def dynamics(self, t, x, u):
372+
def dynamics(self, t, x, u, params=None):
373373
"""Compute the dynamics of a differential or difference equation.
374374
375375
Given time `t`, input `u` and state `x`, returns the value of the
376376
right hand side of the dynamical system. If the system is continuous,
377377
returns the time derivative
378378
379-
dx/dt = f(t, x, u)
379+
dx/dt = f(t, x, u[, params])
380380
381381
where `f` is the system's (possibly nonlinear) dynamics function.
382382
If the system is discrete-time, returns the next value of `x`:
383383
384-
x[t+dt] = f(t, x[t], u[t])
384+
x[t+dt] = f(t, x[t], u[t][, params])
385385
386-
Where `t` is a scalar.
386+
where `t` is a scalar.
387387
388-
The inputs `x` and `u` must be of the correct length.
388+
The inputs `x` and `u` must be of the correct length. The `params`
389+
argument is an optional dictionary of parameter values.
389390
390391
Parameters
391392
----------
@@ -395,14 +396,17 @@ def dynamics(self, t, x, u):
395396
current state
396397
u : array_like
397398
input
399+
params : dict (optional)
400+
system parameter values
398401
399402
Returns
400403
-------
401404
dx/dt or x[t+dt] : ndarray
402405
"""
406+
self._update_params(params)
403407
return self._rhs(t, x, u)
404408

405-
def _out(self, t, x, u, params={}):
409+
def _out(self, t, x, u):
406410
"""Evaluate the output of a system at a given state, input, and time
407411
408412
Private function used to compute the output of of an input/output
@@ -414,13 +418,13 @@ def _out(self, t, x, u, params={}):
414418
# If no output function was defined in subclass, return state
415419
return x
416420

417-
def output(self, t, x, u):
421+
def output(self, t, x, u, params=None):
418422
"""Compute the output of the system
419423
420424
Given time `t`, input `u` and state `x`, returns the output of the
421425
system:
422426
423-
y = g(t, x, u)
427+
y = g(t, x, u[, params])
424428
425429
The inputs `x` and `u` must be of the correct length.
426430
@@ -432,14 +436,17 @@ def output(self, t, x, u):
432436
current state
433437
u : array_like
434438
input
439+
params : dict (optional)
440+
system parameter values
435441
436442
Returns
437443
-------
438444
y : ndarray
439445
"""
446+
self._update_params(params)
440447
return self._out(t, x, u)
441448

442-
def feedback(self, other=1, sign=-1, params={}):
449+
def feedback(self, other=1, sign=-1, params=None):
443450
"""Feedback interconnection between two input/output systems
444451
445452
Parameters
@@ -507,7 +514,7 @@ def feedback(self, other=1, sign=-1, params={}):
507514
# Return the newly created system
508515
return newsys
509516

510-
def linearize(self, x0, u0, t=0, params={}, eps=1e-6,
517+
def linearize(self, x0, u0, t=0, params=None, eps=1e-6,
511518
name=None, copy=False, **kwargs):
512519
"""Linearize an input/output system at a given state and input.
513520
@@ -651,7 +658,7 @@ def __init__(self, linsys, **kwargs):
651658
# Note: don't use super() to override StateSpace MRO
652659
InputOutputSystem.__init__(
653660
self, inputs=inputs, outputs=outputs, states=states,
654-
params={}, dt=dt, name=name)
661+
params=None, dt=dt, name=name)
655662

656663
# Initalize additional state space variables
657664
StateSpace.__init__(
@@ -668,7 +675,7 @@ def __init__(self, linsys, **kwargs):
668675
#: number of states, use :attr:`nstates`.
669676
states = property(StateSpace._get_states, StateSpace._set_states)
670677

671-
def _update_params(self, params={}, warning=True):
678+
def _update_params(self, params=None, warning=True):
672679
# Parameters not supported; issue a warning
673680
if params and warning:
674681
warn("Parameters passed to LinearIOSystems are ignored.")
@@ -756,7 +763,7 @@ class NonlinearIOSystem(InputOutputSystem):
756763
defaults.
757764
758765
"""
759-
def __init__(self, updfcn, outfcn=None, params={}, **kwargs):
766+
def __init__(self, updfcn, outfcn=None, params=None, **kwargs):
760767
"""Create a nonlinear I/O system given update and output functions."""
761768
# Process keyword arguments
762769
name, inputs, outputs, states, dt = _process_namedio_keywords(
@@ -791,7 +798,7 @@ def __init__(self, updfcn, outfcn=None, params={}, **kwargs):
791798
"(and nstates not known).")
792799

793800
# Initialize current parameters to default parameters
794-
self._current_params = params.copy()
801+
self._current_params = {} if params is None else params.copy()
795802

796803
def __str__(self):
797804
return f"{InputOutputSystem.__str__(self)}\n\n" + \
@@ -838,7 +845,8 @@ def __call__(sys, u, params=None, squeeze=None):
838845
def _update_params(self, params, warning=False):
839846
# Update the current parameter values
840847
self._current_params = self.params.copy()
841-
self._current_params.update(params)
848+
if params:
849+
self._current_params.update(params)
842850

843851
def _rhs(self, t, x, u):
844852
xdot = self.updfcn(t, x, u, self._current_params) \
@@ -862,20 +870,22 @@ class InterconnectedSystem(InputOutputSystem):
862870
See :func:`~control.interconnect` for a list of parameters.
863871
864872
"""
865-
def __init__(self, syslist, connections=[], inplist=[], outlist=[],
866-
params={}, warn_duplicate=None, **kwargs):
873+
def __init__(self, syslist, connections=None, inplist=None, outlist=None,
874+
params=None, warn_duplicate=None, **kwargs):
867875
"""Create an I/O system from a list of systems + connection info."""
868876
# Convert input and output names to lists if they aren't already
869-
if not isinstance(inplist, (list, tuple)):
877+
if inplist is not None and not isinstance(inplist, (list, tuple)):
870878
inplist = [inplist]
871-
if not isinstance(outlist, (list, tuple)):
879+
if outlist is not None and not isinstance(outlist, (list, tuple)):
872880
outlist = [outlist]
873881

874882
# Check if dt argument was given; if not, pull from systems
875883
dt = kwargs.pop('dt', None)
876884

877885
# Process keyword arguments (except dt)
878-
defaults = {'inputs': len(inplist), 'outputs': len(outlist)}
886+
defaults = {
887+
'inputs': len(inplist or []),
888+
'outputs': len(outlist or [])}
879889
name, inputs, outputs, states, _ = _process_namedio_keywords(
880890
kwargs, defaults, end=True)
881891

@@ -894,6 +904,12 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
894904

895905
# Go through the system list and keep track of counts, offsets
896906
for sysidx, sys in enumerate(syslist):
907+
# If we were passed a SS or TF system, convert to LinearIOSystem
908+
if isinstance(sys, (StateSpace, TransferFunction)) and \
909+
not isinstance(sys, LinearIOSystem):
910+
sys = LinearIOSystem(sys)
911+
syslist[sysidx] = sys
912+
897913
# Make sure time bases are consistent
898914
dt = common_timebase(dt, sys.dt)
899915

@@ -969,7 +985,7 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
969985

970986
# Convert the list of interconnections to a connection map (matrix)
971987
self.connect_map = np.zeros((ninputs, noutputs))
972-
for connection in connections:
988+
for connection in connections or []:
973989
input_index = self._parse_input_spec(connection[0])
974990
for output_spec in connection[1:]:
975991
output_index, gain = self._parse_output_spec(output_spec)
@@ -980,7 +996,7 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
980996

981997
# Convert the input list to a matrix: maps system to subsystems
982998
self.input_map = np.zeros((ninputs, self.ninputs))
983-
for index, inpspec in enumerate(inplist):
999+
for index, inpspec in enumerate(inplist or []):
9841000
if isinstance(inpspec, (int, str, tuple)):
9851001
inpspec = [inpspec]
9861002
if not isinstance(inpspec, list):
@@ -995,7 +1011,7 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
9951011

9961012
# Convert the output list to a matrix: maps subsystems to system
9971013
self.output_map = np.zeros((self.noutputs, noutputs + ninputs))
998-
for index, outspec in enumerate(outlist):
1014+
for index, outspec in enumerate(outlist or []):
9991015
if isinstance(outspec, (int, str, tuple)):
10001016
outspec = [outspec]
10011017
if not isinstance(outspec, list):
@@ -1009,13 +1025,14 @@ def __init__(self, syslist, connections=[], inplist=[], outlist=[],
10091025
self.output_map[index, ylist_index] += gain
10101026

10111027
# Save the parameters for the system
1012-
self.params = params.copy()
1028+
self.params = {} if params is None else params.copy()
10131029

10141030
def _update_params(self, params, warning=False):
10151031
for sys in self.syslist:
10161032
local = sys.params.copy() # start with system parameters
10171033
local.update(self.params) # update with global params
1018-
local.update(params) # update with locally passed parameters
1034+
if params:
1035+
local.update(params) # update with locally passed parameters
10191036
sys._update_params(local, warning=warning)
10201037

10211038
def _rhs(self, t, x, u):
@@ -1565,7 +1582,7 @@ def __init__(self, io_sys, ss_sys=None):
15651582

15661583

15671584
def input_output_response(
1568-
sys, T, U=0., X0=0, params={},
1585+
sys, T, U=0., X0=0, params=None,
15691586
transpose=False, return_x=False, squeeze=None,
15701587
solve_ivp_kwargs={}, t_eval='T', **kwargs):
15711588
"""Compute the output response of a system to a given input.
@@ -1781,7 +1798,7 @@ def input_output_response(
17811798

17821799
# Update the parameter values
17831800
sys._update_params(params)
1784-
1801+
17851802
#
17861803
# Define a function to evaluate the input at an arbitrary time
17871804
#
@@ -1900,7 +1917,7 @@ def ivp_rhs(t, x):
19001917
transpose=transpose, return_x=return_x, squeeze=squeeze)
19011918

19021919

1903-
def find_eqpt(sys, x0, u0=[], y0=None, t=0, params={},
1920+
def find_eqpt(sys, x0, u0=None, y0=None, t=0, params=None,
19041921
iu=None, iy=None, ix=None, idx=None, dx0=None,
19051922
return_y=False, return_result=False):
19061923
"""Find the equilibrium point for an input/output system.
@@ -2151,7 +2168,7 @@ def rootfun(z):
21512168

21522169

21532170
# Linearize an input/output system
2154-
def linearize(sys, xeq, ueq=[], t=0, params={}, **kw):
2171+
def linearize(sys, xeq, ueq=None, t=0, params=None, **kw):
21552172
"""Linearize an input/output system at a given state and input.
21562173
21572174
This function computes the linearization of an input/output system at a
@@ -2242,7 +2259,7 @@ def ss(*args, **kwargs):
22422259
Convert a linear system into space system form. Always creates a
22432260
new system, even if sys is already a state space system.
22442261
2245-
``ss(updfcn, outfucn)``
2262+
``ss(updfcn, outfcn)``
22462263
Create a nonlinear input/output system with update function ``updfcn``
22472264
and output function ``outfcn``. See :class:`NonlinearIOSystem` for
22482265
more information.
@@ -2523,9 +2540,9 @@ def tf2io(*args, **kwargs):
25232540

25242541

25252542
# Function to create an interconnected system
2526-
def interconnect(syslist, connections=None, inplist=[], outlist=[], params={},
2527-
check_unused=True, ignore_inputs=None, ignore_outputs=None,
2528-
warn_duplicate=None, **kwargs):
2543+
def interconnect(syslist, connections=None, inplist=None, outlist=None,
2544+
params=None, check_unused=True, ignore_inputs=None,
2545+
ignore_outputs=None, warn_duplicate=None, **kwargs):
25292546
"""Interconnect a set of input/output systems.
25302547
25312548
This function creates a new system that is an interconnection of a set of
@@ -2767,10 +2784,10 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[], params={},
27672784
connections = []
27682785

27692786
# If inplist/outlist is not present, try using inputs/outputs instead
2770-
if not inplist and inputs is not None:
2771-
inplist = list(inputs)
2772-
if not outlist and outputs is not None:
2773-
outlist = list(outputs)
2787+
if inplist is None:
2788+
inplist = list(inputs or [])
2789+
if outlist is None:
2790+
outlist = list(outputs or [])
27742791

27752792
# Process input list
27762793
if not isinstance(inplist, (list, tuple)):

control/statesp.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ def dcgain(self, warn_infinite=False):
13881388
"""
13891389
return self._dcgain(warn_infinite)
13901390

1391-
def dynamics(self, t, x, u=None):
1391+
def dynamics(self, t, x, u=None, params=None):
13921392
"""Compute the dynamics of the system
13931393
13941394
Given input `u` and state `x`, returns the dynamics of the state-space
@@ -1422,6 +1422,9 @@ def dynamics(self, t, x, u=None):
14221422
dx/dt or x[t+dt] : ndarray
14231423
14241424
"""
1425+
if params is not None:
1426+
warn("params keyword ignored for StateSpace object")
1427+
14251428
x = np.reshape(x, (-1, 1)) # force to a column in case matrix
14261429
if np.size(x) != self.nstates:
14271430
raise ValueError("len(x) must be equal to number of states")
@@ -1434,7 +1437,7 @@ def dynamics(self, t, x, u=None):
14341437
return (self.A @ x).reshape((-1,)) \
14351438
+ (self.B @ u).reshape((-1,)) # return as row vector
14361439

1437-
def output(self, t, x, u=None):
1440+
def output(self, t, x, u=None, params=None):
14381441
"""Compute the output of the system
14391442
14401443
Given input `u` and state `x`, returns the output `y` of the
@@ -1464,6 +1467,9 @@ def output(self, t, x, u=None):
14641467
-------
14651468
y : ndarray
14661469
"""
1470+
if params is not None:
1471+
warn("params keyword ignored for StateSpace object")
1472+
14671473
x = np.reshape(x, (-1, 1)) # force to a column in case matrix
14681474
if np.size(x) != self.nstates:
14691475
raise ValueError("len(x) must be equal to number of states")

control/tests/statesp_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,7 @@ def test_linfnorm_ct_mimo(self, ct_siso):
11401140
np.testing.assert_allclose(gpeak, refgpeak)
11411141
np.testing.assert_allclose(fpeak, reffpeak)
11421142

1143+
11431144
@pytest.mark.parametrize("args, static", [
11441145
(([], [], [], 1), True), # ctime, empty state
11451146
(([], [], [], 1, 1), True), # dtime, empty state
@@ -1153,3 +1154,13 @@ def test_linfnorm_ct_mimo(self, ct_siso):
11531154
def test_isstatic(args, static):
11541155
sys = ct.StateSpace(*args)
11551156
assert sys._isstatic() == static
1157+
1158+
# Make sure that using params for StateSpace objects generates a warning
1159+
def test_params_warning():
1160+
sys = StateSpace(-1, 1, 1, 0)
1161+
1162+
with pytest.warns(UserWarning, match="params keyword ignored"):
1163+
sys.dynamics(0, [0], [0], {'k': 5})
1164+
1165+
with pytest.warns(UserWarning, match="params keyword ignored"):
1166+
sys.output(0, [0], [0], {'k': 5})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy