Skip to content
5 changes: 4 additions & 1 deletion control/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ def reset_defaults():
from .rlocus import _rlocus_defaults
defaults.update(_rlocus_defaults)

from .namedio import _namedio_defaults
defaults.update(_namedio_defaults)

from .xferfcn import _xferfcn_defaults
defaults.update(_xferfcn_defaults)

Expand Down Expand Up @@ -285,7 +288,7 @@ def use_legacy_defaults(version):
set_defaults('control', default_dt=None)

# changed iosys naming conventions
set_defaults('iosys', state_name_delim='.',
set_defaults('namedio', state_name_delim='.',
duplicate_system_name_prefix='copy of ',
duplicate_system_name_suffix='',
linearized_system_name_prefix='',
Expand Down
29 changes: 27 additions & 2 deletions control/dtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
__all__ = ['sample_system', 'c2d']

# Sample a continuous time system
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None,
name=None, copy_names=True, **kwargs):
"""
Convert a continuous time system to discrete time by sampling

Expand All @@ -72,12 +73,35 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
prewarp_frequency : float within [0, infinity)
The frequency [rad/s] at which to match with the input continuous-
time system's magnitude and phase (only valid for method='bilinear')
name : string, optional
Set the name of the sampled system. If not specified and
if `copy_names` is `False`, a generic name <sys[id]> is generated
with a unique integer id. If `copy_names` is `True`, the new system
name is determined by adding the prefix and suffix strings in
config.defaults['namedio.sampled_system_name_prefix'] and
config.defaults['namedio.sampled_system_name_suffix'], with the
default being to add the suffix '$sampled'.
copy_names : bool, Optional
If True, copy the names of the input signals, output
signals, and states to the sampled system.

Returns
-------
sysd : linsys
Discrete time system, with sampling rate Ts

Additional Parameters
---------------------
inputs : int, list of str or None, optional
Description of the system inputs. If not specified, the origional
system inputs are used. See :class:`NamedIOSystem` for more
information.
outputs : int, list of str or None, optional
Description of the system outputs. Same format as `inputs`.
states : int, list of str, or None, optional
Description of the system states. Same format as `inputs`. Only
available if the system is :class:`StateSpace`.

Notes
-----
See :meth:`StateSpace.sample` or :meth:`TransferFunction.sample` for
Expand All @@ -94,7 +118,8 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None):
raise ValueError("First argument must be continuous time system")

return sysc.sample(Ts,
method=method, alpha=alpha, prewarp_frequency=prewarp_frequency)
method=method, alpha=alpha, prewarp_frequency=prewarp_frequency,
name=name, copy_names=copy_names, **kwargs)


def c2d(sysc, Ts, method='zoh', prewarp_frequency=None):
Expand Down
65 changes: 29 additions & 36 deletions control/iosys.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,7 @@
'interconnect', 'summing_junction']

# Define module default parameter values
_iosys_defaults = {
'iosys.state_name_delim': '_',
'iosys.duplicate_system_name_prefix': '',
'iosys.duplicate_system_name_suffix': '$copy',
'iosys.linearized_system_name_prefix': '',
'iosys.linearized_system_name_suffix': '$linearized'
}
_iosys_defaults = {}


class InputOutputSystem(NamedIOSystem):
Expand Down Expand Up @@ -515,7 +509,7 @@ def feedback(self, other=1, sign=-1, params=None):
return newsys

def linearize(self, x0, u0, t=0, params=None, eps=1e-6,
name=None, copy=False, **kwargs):
name=None, copy_names=False, **kwargs):
"""Linearize an input/output system at a given state and input.

Return the linearization of an input/output system at a given state
Expand Down Expand Up @@ -571,25 +565,26 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6,

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

# Set the names the system, inputs, outputs, and states
if copy:
# Set the system name, inputs, outputs, and states
if 'copy' in kwargs:
copy_names = kwargs.pop('copy')
warn("keyword 'copy' is deprecated. please use 'copy_names'",
DeprecationWarning)

if copy_names:
linsys._copy_names(self)
if name is None:
linsys.name = \
config.defaults['iosys.linearized_system_name_prefix'] + \
self.name + \
config.defaults['iosys.linearized_system_name_suffix']
linsys.ninputs, linsys.input_index = self.ninputs, \
self.input_index.copy()
linsys.noutputs, linsys.output_index = \
self.noutputs, self.output_index.copy()
linsys.nstates, linsys.state_index = \
self.nstates, self.state_index.copy()

return linsys
config.defaults['namedio.linearized_system_name_prefix']+\
linsys.name+\
config.defaults['namedio.linearized_system_name_suffix']
else:
linsys.name = name

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

class LinearIOSystem(InputOutputSystem, StateSpace):
"""Input/output representation of a linear (state space) system.
Expand Down Expand Up @@ -966,7 +961,7 @@ def __init__(self, syslist, connections=None, inplist=None, outlist=None,

if states is None:
states = []
state_name_delim = config.defaults['iosys.state_name_delim']
state_name_delim = config.defaults['namedio.state_name_delim']
for sys, sysname in sysobj_name_dct.items():
states += [sysname + state_name_delim +
statename for statename in sys.state_index.keys()]
Expand Down Expand Up @@ -2192,19 +2187,17 @@ def linearize(sys, xeq, ueq=None, t=0, params=None, **kw):
params : dict, optional
Parameter values for the systems. Passed to the evaluation functions
for the system as default values, overriding internal defaults.
copy : bool, Optional
If `copy` is True, copy the names of the input signals, output signals,
and states to the linearized system. If `name` is not specified,
the system name is set to the input system name with the string
'_linearized' appended.
name : string, optional
Set the name of the linearized system. If not specified and
if `copy` is `False`, a generic name <sys[id]> is generated
with a unique integer id. If `copy` is `True`, the new system
if `copy_names` is `False`, a generic name <sys[id]> is generated
with a unique integer id. If `copy_names` is `True`, the new system
name is determined by adding the prefix and suffix strings in
config.defaults['iosys.linearized_system_name_prefix'] and
config.defaults['iosys.linearized_system_name_suffix'], with the
config.defaults['namedio.linearized_system_name_prefix'] and
config.defaults['namedio.linearized_system_name_suffix'], with the
default being to add the suffix '$linearized'.
copy_names : bool, Optional
If True, Copy the names of the input signals, output signals, and
states to the linearized system.

Returns
-------
Expand All @@ -2216,7 +2209,7 @@ def linearize(sys, xeq, ueq=None, t=0, params=None, **kw):
---------------------
inputs : int, list of str or None, optional
Description of the system inputs. If not specified, the origional
system inputs are used. See :class:`InputOutputSystem` for more
system inputs are used. See :class:`NamedIOSystem` for more
information.
outputs : int, list of str or None, optional
Description of the system outputs. Same format as `inputs`.
Expand Down Expand Up @@ -2728,8 +2721,8 @@ def interconnect(syslist, connections=None, inplist=None, outlist=None,
If a system is duplicated in the list of systems to be connected,
a warning is generated and a copy of the system is created with the
name of the new system determined by adding the prefix and suffix
strings in config.defaults['iosys.linearized_system_name_prefix']
and config.defaults['iosys.linearized_system_name_suffix'], with the
strings in config.defaults['namedio.linearized_system_name_prefix']
and config.defaults['namedio.linearized_system_name_suffix'], with the
default being to add the suffix '$copy'$ to the system name.

It is possible to replace lists in most of arguments with tuples instead,
Expand Down
36 changes: 29 additions & 7 deletions control/namedio.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@
# and other similar classes to allow naming of signals.

import numpy as np
from copy import copy
from copy import deepcopy
from warnings import warn
from . import config

__all__ = ['issiso', 'timebase', 'common_timebase', 'timebaseEqual',
'isdtime', 'isctime']

# Define module default parameter values
_namedio_defaults = {
'namedio.state_name_delim': '_',
'namedio.duplicate_system_name_prefix': '',
'namedio.duplicate_system_name_suffix': '$copy',
'namedio.linearized_system_name_prefix': '',
'namedio.linearized_system_name_suffix': '$linearized',
'namedio.sampled_system_name_prefix': '',
'namedio.sampled_system_name_suffix': '$sampled'
}


class NamedIOSystem(object):
def __init__(
self, name=None, inputs=None, outputs=None, states=None, **kwargs):
Expand Down Expand Up @@ -88,26 +99,37 @@ def __str__(self):
def _find_signal(self, name, sigdict):
return sigdict.get(name, None)

def _copy_names(self, sys):
"""copy the signal and system name of sys. Name is given as a keyword
in case a specific name (e.g. append 'linearized') is desired. """
self.name = sys.name
self.ninputs, self.input_index = \
sys.ninputs, sys.input_index.copy()
self.noutputs, self.output_index = \
sys.noutputs, sys.output_index.copy()
self.nstates, self.state_index = \
sys.nstates, sys.state_index.copy()

def copy(self, name=None, use_prefix_suffix=True):
"""Make a copy of an input/output system

A copy of the system is made, with a new name. The `name` keyword
can be used to specify a specific name for the system. If no name
is given and `use_prefix_suffix` is True, the name is constructed
by prepending config.defaults['iosys.duplicate_system_name_prefix']
and appending config.defaults['iosys.duplicate_system_name_suffix'].
by prepending config.defaults['namedio.duplicate_system_name_prefix']
and appending config.defaults['namedio.duplicate_system_name_suffix'].
Otherwise, a generic system name of the form `sys[<id>]` is used,
where `<id>` is based on an internal counter.

"""
# Create a copy of the system
newsys = copy(self)
newsys = deepcopy(self)

# Update the system name
if name is None and use_prefix_suffix:
# Get the default prefix and suffix to use
dup_prefix = config.defaults['iosys.duplicate_system_name_prefix']
dup_suffix = config.defaults['iosys.duplicate_system_name_suffix']
dup_prefix = config.defaults['namedio.duplicate_system_name_prefix']
dup_suffix = config.defaults['namedio.duplicate_system_name_suffix']
newsys.name = self._name_or_default(
dup_prefix + self.name + dup_suffix)
else:
Expand Down
51 changes: 42 additions & 9 deletions control/statesp.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@
from .exception import ControlSlycot
from .frdata import FrequencyResponseData
from .lti import LTI, _process_frequency_response
from .namedio import common_timebase, isdtime
from .namedio import _process_namedio_keywords
from .namedio import common_timebase, isdtime, _process_namedio_keywords, \
_process_dt_keyword
from . import config
from copy import deepcopy

Expand Down Expand Up @@ -359,7 +359,7 @@ def __init__(self, *args, init_namedio=True, **kwargs):
raise TypeError("unrecognized keyword(s): ", str(kwargs))

# Reset shapes (may not be needed once np.matrix support is removed)
if 0 == self.nstates:
if self._isstatic():
# static gain
# matrix's default "empty" shape is 1x0
A.shape = (0, 0)
Expand Down Expand Up @@ -1298,7 +1298,8 @@ def __getitem__(self, indices):
return StateSpace(self.A, self.B[:, j], self.C[i, :],
self.D[i, j], self.dt)

def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None,
name=None, copy_names=True, **kwargs):
"""Convert a continuous time system to discrete time

Creates a discrete-time system from a continuous-time system by
Expand All @@ -1317,22 +1318,42 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
alpha=0)
* backward_diff: Backwards differencing ("gbt" with alpha=1.0)
* zoh: zero-order hold (default)

alpha : float within [0, 1]
The generalized bilinear transformation weighting parameter, which
should only be specified with method="gbt", and is ignored
otherwise

prewarp_frequency : float within [0, infinity)
The frequency [rad/s] at which to match with the input continuous-
time system's magnitude and phase (the gain=1 crossover frequency,
for example). Should only be specified with method='bilinear' or
'gbt' with alpha=0.5 and ignored otherwise.
name : string, optional
Set the name of the sampled system. If not specified and
if `copy_names` is `False`, a generic name <sys[id]> is generated
with a unique integer id. If `copy_names` is `True`, the new system
name is determined by adding the prefix and suffix strings in
config.defaults['namedio.sampled_system_name_prefix'] and
config.defaults['namedio.sampled_system_name_suffix'], with the
default being to add the suffix '$sampled'.
copy_names : bool, Optional
If True, copy the names of the input signals, output
signals, and states to the sampled system.

Returns
-------
sysd : StateSpace
Discrete time system, with sampling rate Ts
Discrete-time system, with sampling rate Ts

Additional Parameters
---------------------
inputs : int, list of str or None, optional
Description of the system inputs. If not specified, the origional
system inputs are used. See :class:`InputOutputSystem` for more
information.
outputs : int, list of str or None, optional
Description of the system outputs. Same format as `inputs`.
states : int, list of str, or None, optional
Description of the system states. Same format as `inputs`.

Notes
-----
Expand All @@ -1347,14 +1368,26 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None):
if not self.isctime():
raise ValueError("System must be continuous time system")

sys = (self.A, self.B, self.C, self.D)
if (method == 'bilinear' or (method == 'gbt' and alpha == 0.5)) and \
prewarp_frequency is not None:
Twarp = 2 * np.tan(prewarp_frequency * Ts/2)/prewarp_frequency
else:
Twarp = Ts
sys = (self.A, self.B, self.C, self.D)
Ad, Bd, C, D, _ = cont2discrete(sys, Twarp, method, alpha)
return StateSpace(Ad, Bd, C, D, Ts)
sysd = StateSpace(Ad, Bd, C, D, Ts)
# copy over the system name, inputs, outputs, and states
if copy_names:
sysd._copy_names(self)
if name is None:
sysd.name = \
config.defaults['namedio.sampled_system_name_prefix'] +\
sysd.name + \
config.defaults['namedio.sampled_system_name_suffix']
else:
sysd.name = name
# pass desired signal names if names were provided
return StateSpace(sysd, **kwargs)

def dcgain(self, warn_infinite=False):
"""Return the zero-frequency gain
Expand Down
Loading