Skip to content

Commit dbf64f2

Browse files
committed
simplify _copy_names, fix docstring errors, add names to sample_system and unit tests, namedio.copy is now deepcopy
1 parent c345261 commit dbf64f2

File tree

8 files changed

+127
-60
lines changed

8 files changed

+127
-60
lines changed

control/dtime.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
__all__ = ['sample_system', 'c2d']
5454

5555
# Sample a continuous time system
56-
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
56+
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
57+
name=None, copy_names=True, **kwargs):
5758
"""
5859
Convert a continuous time system to discrete time by sampling
5960
@@ -72,12 +73,35 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
7273
prewarp_frequency : float within [0, infinity)
7374
The frequency [rad/s] at which to match with the input continuous-
7475
time system's magnitude and phase (only valid for method='bilinear')
76+
name : string, optional
77+
Set the name of the sampled system. If not specified and
78+
if `copy_names` is `False`, a generic name <sys[id]> is generated
79+
with a unique integer id. If `copy_names` is `True`, the new system
80+
name is determined by adding the prefix and suffix strings in
81+
config.defaults['namedio.sampled_system_name_prefix'] and
82+
config.defaults['namedio.sampled_system_name_suffix'], with the
83+
default being to add the suffix '$sampled'.
84+
copy_names : bool, Optional
85+
If True, copy the names of the input signals, output
86+
signals, and states to the sampled system.
7587
7688
Returns
7789
-------
7890
sysd : linsys
7991
Discrete time system, with sampling rate Ts
8092
93+
Additional Parameters
94+
---------------------
95+
inputs : int, list of str or None, optional
96+
Description of the system inputs. If not specified, the origional
97+
system inputs are used. See :class:`NamedIOSystem` for more
98+
information.
99+
outputs : int, list of str or None, optional
100+
Description of the system outputs. Same format as `inputs`.
101+
states : int, list of str, or None, optional
102+
Description of the system states. Same format as `inputs`. Only
103+
available if the system is :class:`StateSpace`.
104+
81105
Notes
82106
-----
83107
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample` for
@@ -94,7 +118,8 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
94118
raise ValueError("First argument must be continuous time system")
95119

96120
return sysc.sample(Ts,
97-
method=method, alpha=alpha, prewarp_frequency=prewarp_frequency)
121+
method=method, alpha=alpha, prewarp_frequency=prewarp_frequency,
122+
name=name, copy_names=copy_names, **kwargs)
98123

99124

100125
def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):

control/iosys.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -568,16 +568,23 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6,
568568
StateSpace(A, B, C, D, self.dt, remove_useless_states=False))
569569

570570
# Set the system name, inputs, outputs, and states
571+
if copy in kwargs:
572+
copy_names = kwargs.pop('copy')
573+
warn("keyword 'copy' is deprecated. please use 'copy_names'",
574+
DeprecationWarning)
575+
571576
if copy_names:
577+
linsys._copy_names(self)
572578
if name is None:
573-
name = \
574-
config.defaults['namedio.linearized_system_name_prefix'] +\
575-
self.name + \
579+
linsys.name = \
580+
config.defaults['namedio.linearized_system_name_prefix']+\
581+
linsys.name+\
576582
config.defaults['namedio.linearized_system_name_suffix']
577-
linsys._copy_names(self, name=name)
578-
linsys = LinearIOSystem(linsys, name=name, **kwargs)
579-
return linsys
583+
else:
584+
linsys.name = name
580585

586+
# re-init to include desired signal names if names were provided
587+
return LinearIOSystem(linsys, **kwargs)
581588

582589
class LinearIOSystem(InputOutputSystem, StateSpace):
583590
"""Input/output representation of a linear (state space) system.
@@ -2180,19 +2187,17 @@ def linearize(sys, xeq, ueq=None, t=0, params=None, **kw):
21802187
params : dict, optional
21812188
Parameter values for the systems. Passed to the evaluation functions
21822189
for the system as default values, overriding internal defaults.
2183-
copy_names : bool, Optional
2184-
If `copy_names` is True, copy the names of the input signals, output
2185-
signals, and states to the linearized system. If `name` is not
2186-
specified, the system name is set to the input system name with the
2187-
string '_linearized' appended.
21882190
name : string, optional
21892191
Set the name of the linearized system. If not specified and
2190-
if `copy` is `False`, a generic name <sys[id]> is generated
2191-
with a unique integer id. If `copy` is `True`, the new system
2192+
if `copy_names` is `False`, a generic name <sys[id]> is generated
2193+
with a unique integer id. If `copy_names` is `True`, the new system
21922194
name is determined by adding the prefix and suffix strings in
21932195
config.defaults['namedio.linearized_system_name_prefix'] and
21942196
config.defaults['namedio.linearized_system_name_suffix'], with the
21952197
default being to add the suffix '$linearized'.
2198+
copy_names : bool, Optional
2199+
If True, Copy the names of the input signals, output signals, and
2200+
states to the linearized system.
21962201
21972202
Returns
21982203
-------

control/namedio.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# and other similar classes to allow naming of signals.
77

88
import numpy as np
9-
from copy import copy
9+
from copy import deepcopy
1010
from warnings import warn
1111
from . import config
1212

@@ -99,13 +99,10 @@ def __str__(self):
9999
def _find_signal(self, name, sigdict):
100100
return sigdict.get(name, None)
101101

102-
def _copy_names(self, sys, name=None):
102+
def _copy_names(self, sys):
103103
"""copy the signal and system name of sys. Name is given as a keyword
104104
in case a specific name (e.g. append 'linearized') is desired. """
105-
if name is None:
106-
self.name = sys.name
107-
else:
108-
self.name = name
105+
self.name = sys.name
109106
self.ninputs, self.input_index = \
110107
sys.ninputs, sys.input_index.copy()
111108
self.noutputs, self.output_index = \
@@ -126,7 +123,7 @@ def copy(self, name=None, use_prefix_suffix=True):
126123
127124
"""
128125
# Create a copy of the system
129-
newsys = copy(self)
126+
newsys = deepcopy(self)
130127

131128
# Update the system name
132129
if name is None and use_prefix_suffix:

control/statesp.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def __init__(self, *args, init_namedio=True, **kwargs):
357357
states=states, dt=dt)
358358
elif kwargs:
359359
raise TypeError("unrecognized keyword(s): ", str(kwargs))
360-
360+
361361
# Reset shapes (may not be needed once np.matrix support is removed)
362362
if self._isstatic():
363363
# static gain
@@ -1298,7 +1298,7 @@ def __getitem__(self, indices):
12981298
return StateSpace(self.A, self.B[:, j], self.C[i, :],
12991299
self.D[i, j], self.dt)
13001300

1301-
def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
1301+
def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
13021302
name=None, copy_names=True, **kwargs):
13031303
"""Convert a continuous time system to discrete time
13041304
@@ -1327,19 +1327,17 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
13271327
time system's magnitude and phase (the gain=1 crossover frequency,
13281328
for example). Should only be specified with method='bilinear' or
13291329
'gbt' with alpha=0.5 and ignored otherwise.
1330-
copy_names : bool, Optional
1331-
If `copy_names` is True, copy the names of the input signals, output
1332-
signals, and states to the sampled system. If `name` is not
1333-
specified, the system name is set to the input system name with the
1334-
string '_sampled' appended.
13351330
name : string, optional
13361331
Set the name of the sampled system. If not specified and
1337-
if `copy` is `False`, a generic name <sys[id]> is generated
1338-
with a unique integer id. If `copy` is `True`, the new system
1332+
if `copy_names` is `False`, a generic name <sys[id]> is generated
1333+
with a unique integer id. If `copy_names` is `True`, the new system
13391334
name is determined by adding the prefix and suffix strings in
13401335
config.defaults['namedio.sampled_system_name_prefix'] and
13411336
config.defaults['namedio.sampled_system_name_suffix'], with the
13421337
default being to add the suffix '$sampled'.
1338+
copy_names : bool, Optional
1339+
If True, copy the names of the input signals, output
1340+
signals, and states to the sampled system.
13431341
13441342
Returns
13451343
-------
@@ -1380,15 +1378,16 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
13801378
sysd = StateSpace(Ad, Bd, C, D, Ts)
13811379
# copy over the system name, inputs, outputs, and states
13821380
if copy_names:
1381+
sysd._copy_names(self)
13831382
if name is None:
1384-
name = \
1383+
sysd.name = \
13851384
config.defaults['namedio.sampled_system_name_prefix'] +\
1386-
self.name + \
1385+
sysd.name + \
13871386
config.defaults['namedio.sampled_system_name_suffix']
1388-
sysd._copy_names(self, name=name)
1389-
# pass desired signal names if names were provided
1390-
sysd = StateSpace(sysd, name=name, **kwargs)
1391-
return sysd
1387+
else:
1388+
sysd.name = name
1389+
# pass desired signal names if names were provided
1390+
return StateSpace(sysd, **kwargs)
13921391

13931392
def dcgain(self, warn_infinite=False):
13941393
"""Return the zero-frequency gain

control/tests/discrete_test.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,4 +458,47 @@ def test_signal_names(self, tsys):
458458
assert ssd.state_labels == ['a', 'b', 'c']
459459
assert ssd.output_labels == ['y']
460460
assert tfd.input_labels == ['u']
461-
assert tfd.output_labels == ['y']
461+
assert tfd.output_labels == ['y']
462+
463+
ssd = sample_system(ssc, 0.1)
464+
tfd = sample_system(tfc, 0.1)
465+
assert ssd.input_labels == ['u']
466+
assert ssd.state_labels == ['a', 'b', 'c']
467+
assert ssd.output_labels == ['y']
468+
assert tfd.input_labels == ['u']
469+
assert tfd.output_labels == ['y']
470+
471+
# system names and signal name override
472+
sysc = StateSpace(1.1, 1, 1, 1, inputs='u', outputs='y', states='a')
473+
474+
sysd = sample_system(sysc, 0.1, name='sampled')
475+
assert sysd.name == 'sampled'
476+
assert sysd.find_input('u') == 0
477+
assert sysd.find_output('y') == 0
478+
assert sysd.find_state('a') == 0
479+
480+
# If we copy signal names w/out a system name, append '$sampled'
481+
sysd = sample_system(sysc, 0.1)
482+
assert sysd.name == sysc.name + '$sampled'
483+
484+
# If copy is False, signal names should not be copied
485+
sysd_nocopy = sample_system(sysc, 0.1, copy_names=False)
486+
assert sysd_nocopy.find_input('u') is None
487+
assert sysd_nocopy.find_output('y') is None
488+
assert sysd_nocopy.find_state('a') is None
489+
490+
# if signal names are provided, they should override those of sysc
491+
sysd_newnames = sample_system(sysc, 0.1,
492+
inputs='v', outputs='x', states='b')
493+
assert sysd_newnames.find_input('v') == 0
494+
assert sysd_newnames.find_input('u') is None
495+
assert sysd_newnames.find_output('x') == 0
496+
assert sysd_newnames.find_output('y') is None
497+
assert sysd_newnames.find_state('b') == 0
498+
assert sysd_newnames.find_state('a') is None
499+
# test just one name
500+
sysd_newnames = sample_system(sysc, 0.1, inputs='v')
501+
assert sysd_newnames.find_input('v') == 0
502+
assert sysd_newnames.find_input('u') is None
503+
assert sysd_newnames.find_output('y') == 0
504+
assert sysd_newnames.find_output('x') is None

control/tests/iosys_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,12 +231,6 @@ def test_linearize_named_signals(self, kincar):
231231
linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True)
232232
assert linearized.name == kincar.name + '$linearized'
233233

234-
# Test legacy version as well
235-
ct.use_legacy_defaults('0.8.4')
236-
ct.config.use_numpy_matrix(False) # np.matrix deprecated
237-
linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True)
238-
assert linearized.name == kincar.name + '_linearized'
239-
240234
# If copy is False, signal names should not be copied
241235
lin_nocopy = kincar.linearize(0, 0, copy_names=False)
242236
assert lin_nocopy.find_input('v') is None
@@ -257,6 +251,12 @@ def test_linearize_named_signals(self, kincar):
257251
assert linearized_newnames.find_output('x') is None
258252
assert linearized_newnames.find_output('y') is None
259253

254+
# Test legacy version as well
255+
ct.use_legacy_defaults('0.8.4')
256+
ct.config.use_numpy_matrix(False) # np.matrix deprecated
257+
linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True)
258+
assert linearized.name == kincar.name + '_linearized'
259+
260260
def test_connect(self, tsys):
261261
# Define a couple of (linear) systems to interconnection
262262
linsys1 = tsys.siso_linsys

control/tests/kwargs_test.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup):
183183
'tf': test_unrecognized_kwargs,
184184
'tf2io' : test_unrecognized_kwargs,
185185
'tf2ss' : test_unrecognized_kwargs,
186+
'sample_system' : test_unrecognized_kwargs,
186187
'flatsys.point_to_point':
187188
flatsys_test.TestFlatSys.test_point_to_point_errors,
188189
'flatsys.solve_flat_ocp':

control/xferfcn.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@ def _common_den(self, imag_tol=None, allow_nonproper=False):
10901090

10911091
return num, den, denorder
10921092

1093-
def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
1093+
def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
10941094
name=None, copy_names=True, **kwargs):
10951095
"""Convert a continuous-time system to discrete time
10961096
@@ -1119,19 +1119,17 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
11191119
time system's magnitude and phase (the gain=1 crossover frequency,
11201120
for example). Should only be specified with method='bilinear' or
11211121
'gbt' with alpha=0.5 and ignored otherwise.
1122-
copy_names : bool, Optional
1123-
If `copy_names` is True, copy the names of the input signals, output
1124-
signals, and states to the sampled system. If `name` is not
1125-
specified, the system name is set to the input system name with the
1126-
string '_sampled' appended.
11271122
name : string, optional
11281123
Set the name of the sampled system. If not specified and
1129-
if `copy` is `False`, a generic name <sys[id]> is generated
1130-
with a unique integer id. If `copy` is `True`, the new system
1124+
if `copy_names` is `False`, a generic name <sys[id]> is generated
1125+
with a unique integer id. If `copy_names` is `True`, the new system
11311126
name is determined by adding the prefix and suffix strings in
11321127
config.defaults['namedio.sampled_system_name_prefix'] and
11331128
config.defaults['namedio.sampled_system_name_suffix'], with the
11341129
default being to add the suffix '$sampled'.
1130+
copy_names : bool, Optional
1131+
If True, copy the names of the input signals, output
1132+
signals, and states to the sampled system.
11351133
11361134
Returns
11371135
-------
@@ -1146,8 +1144,6 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
11461144
information.
11471145
outputs : int, list of str or None, optional
11481146
Description of the system outputs. Same format as `inputs`.
1149-
states : int, list of str, or None, optional
1150-
Description of the system states. Same format as `inputs`.
11511147
11521148
Notes
11531149
-----
@@ -1178,15 +1174,16 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
11781174
sysd = TransferFunction(numd[0, :], dend, Ts)
11791175
# copy over the system name, inputs, outputs, and states
11801176
if copy_names:
1177+
sysd._copy_names(self)
11811178
if name is None:
1182-
name = \
1179+
sysd.name = \
11831180
config.defaults['namedio.sampled_system_name_prefix'] +\
1184-
self.name + \
1181+
sysd.name + \
11851182
config.defaults['namedio.sampled_system_name_suffix']
1186-
sysd._copy_names(self, name=name)
1187-
# pass desired signal names if names were provided
1188-
sysd = TransferFunction(sysd, name=name, **kwargs)
1189-
return sysd
1183+
else:
1184+
sysd.name = name
1185+
# pass desired signal names if names were provided
1186+
return TransferFunction(sysd, name=name, **kwargs)
11901187

11911188
def dcgain(self, warn_infinite=False):
11921189
"""Return the zero-frequency (or DC) gain

0 commit comments

Comments
 (0)