From 055ed394ef76b198d21d3839c04019c4305822ee Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Tue, 22 Nov 2022 21:30:12 -0800 Subject: [PATCH 1/7] initial commit that preserves signal names during cont-to-discrete transformation --- control/dtime.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/control/dtime.py b/control/dtime.py index b05d22b96..a09f5f501 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -47,7 +47,8 @@ """ -from .namedio import isctime +from .namedio import isctime, _process_namedio_keywords +from .iosys import ss from .statesp import StateSpace __all__ = ['sample_system', 'c2d'] @@ -92,9 +93,10 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None): # Make sure we have a continuous time system if not isctime(sysc): raise ValueError("First argument must be continuous time system") - - return sysc.sample(Ts, - method=method, alpha=alpha, prewarp_frequency=prewarp_frequency) + name, inputs, outputs, states, _ = _process_namedio_keywords(defaults=sysc) + return ss(sysc.sample(Ts, + method=method, alpha=alpha, prewarp_frequency=prewarp_frequency), + name=name, inputs=inputs, outputs=outputs, states=states) def c2d(sysc, Ts, method='zoh', prewarp_frequency=None): From 1fd68c7f7d1d28b800ec29926b6ff620e701dff0 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Tue, 22 Nov 2022 21:59:12 -0800 Subject: [PATCH 2/7] changed named signal handling to occur in sys.sample methods. added unit tests. --- control/dtime.py | 10 ++++------ control/statesp.py | 7 +++++-- control/tests/discrete_test.py | 13 +++++++++++++ control/xferfcn.py | 5 ++++- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/control/dtime.py b/control/dtime.py index a09f5f501..b05d22b96 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -47,8 +47,7 @@ """ -from .namedio import isctime, _process_namedio_keywords -from .iosys import ss +from .namedio import isctime from .statesp import StateSpace __all__ = ['sample_system', 'c2d'] @@ -93,10 +92,9 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None): # Make sure we have a continuous time system if not isctime(sysc): raise ValueError("First argument must be continuous time system") - name, inputs, outputs, states, _ = _process_namedio_keywords(defaults=sysc) - return ss(sysc.sample(Ts, - method=method, alpha=alpha, prewarp_frequency=prewarp_frequency), - name=name, inputs=inputs, outputs=outputs, states=states) + + return sysc.sample(Ts, + method=method, alpha=alpha, prewarp_frequency=prewarp_frequency) def c2d(sysc, Ts, method='zoh', prewarp_frequency=None): diff --git a/control/statesp.py b/control/statesp.py index af549cff6..b47a2894f 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -1347,14 +1347,17 @@ 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) + # get and pass along same signal names + _, inputs, outputs, states, _ = _process_namedio_keywords(defaults=self) + return StateSpace(Ad, Bd, C, D, Ts, + inputs=inputs, outputs=outputs, states=states) def dcgain(self, warn_infinite=False): """Return the zero-frequency gain diff --git a/control/tests/discrete_test.py b/control/tests/discrete_test.py index cb0ce3c76..0842cbf59 100644 --- a/control/tests/discrete_test.py +++ b/control/tests/discrete_test.py @@ -446,3 +446,16 @@ def test_discrete_bode(self, tsys): np.testing.assert_array_almost_equal(omega, omega_out) np.testing.assert_array_almost_equal(mag_out, np.absolute(H_z)) np.testing.assert_array_almost_equal(phase_out, np.angle(H_z)) + + def test_signal_names(self, tsys): + "test that signal names are preserved in conversion to discrete-time" + ssc = StateSpace(tsys.siso_ss1c, + inputs='u', outputs='y', states=['a', 'b', 'c']) + ssd = ssc.sample(0.1) + tfc = TransferFunction(tsys.siso_tf1c, inputs='u', outputs='y') + tfd = tfc.sample(0.1) + assert ssd.input_labels == ['u'] + assert ssd.state_labels == ['a', 'b', 'c'] + assert ssd.output_labels == ['y'] + assert tfd.input_labels == ['u'] + assert tfd.output_labels == ['y'] \ No newline at end of file diff --git a/control/xferfcn.py b/control/xferfcn.py index d3671c533..bcb1130e2 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -1149,7 +1149,10 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None): else: Twarp = Ts numd, dend, _ = cont2discrete(sys, Twarp, method, alpha) - return TransferFunction(numd[0, :], dend, Ts) + # get and pass along same signal names + _, inputs, outputs, _, _ = _process_namedio_keywords(defaults=self) + return TransferFunction(numd[0, :], dend, Ts, + inputs=inputs, outputs=outputs) def dcgain(self, warn_infinite=False): """Return the zero-frequency (or DC) gain From 65e051fdedd1239141d24b955d34a214db24d10a Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Wed, 23 Nov 2022 10:51:10 -0800 Subject: [PATCH 3/7] create function to copy system names, move default name parameters to namedio, update sys.sample to enable signal names to be passed. --- control/config.py | 3 +++ control/iosys.py | 48 +++++++++++++++------------------------- control/namedio.py | 33 +++++++++++++++++++++++----- control/statesp.py | 55 ++++++++++++++++++++++++++++++++++++---------- control/xferfcn.py | 46 +++++++++++++++++++++++++++++++++----- 5 files changed, 132 insertions(+), 53 deletions(-) diff --git a/control/config.py b/control/config.py index 32f5f2eef..3f2814d42 100644 --- a/control/config.py +++ b/control/config.py @@ -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) diff --git a/control/iosys.py b/control/iosys.py index 9b33d2161..fd3dcd749 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -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): @@ -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 @@ -574,20 +568,14 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6, StateSpace(A, B, C, D, self.dt, remove_useless_states=False), name=name, **kwargs) - # Set the names the system, inputs, outputs, and states - if copy: + # Set the system name, inputs, outputs, and states + if copy_names: if name is None: - linsys.name = \ - config.defaults['iosys.linearized_system_name_prefix'] + \ + name = \ + config.defaults['namedio.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() - + config.defaults['namedio.linearized_system_name_suffix'] + linsys._copy_names(self, name=name) return linsys @@ -966,7 +954,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()] @@ -2192,18 +2180,18 @@ 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. + copy_names : bool, Optional + If `copy_names` 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 is generated with a unique integer id. If `copy` 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'. Returns @@ -2728,8 +2716,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, diff --git a/control/namedio.py b/control/namedio.py index 254f310ff..52e68671b 100644 --- a/control/namedio.py +++ b/control/namedio.py @@ -12,7 +12,18 @@ __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): @@ -88,14 +99,26 @@ def __str__(self): def _find_signal(self, name, sigdict): return sigdict.get(name, None) + def _copy_names(self, sys, name=None): + """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. """ + if name is None: + 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[]` is used, where `` is based on an internal counter. @@ -106,8 +129,8 @@ def copy(self, name=None, use_prefix_suffix=True): # 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: diff --git a/control/statesp.py b/control/statesp.py index b47a2894f..b5a5fe26f 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -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 @@ -357,9 +357,9 @@ def __init__(self, *args, init_namedio=True, **kwargs): states=states, dt=dt) elif 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) @@ -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 @@ -1317,22 +1318,44 @@ 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. + copy_names : bool, Optional + If `copy_names` is True, copy the names of the input signals, output + signals, and states to the sampled system. If `name` is not + specified, the system name is set to the input system name with the + string '_sampled' appended. + name : string, optional + Set the name of the sampled system. If not specified and + if `copy` is `False`, a generic name is generated + with a unique integer id. If `copy` 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'. 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 ----- @@ -1354,10 +1377,18 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None): Twarp = Ts sys = (self.A, self.B, self.C, self.D) Ad, Bd, C, D, _ = cont2discrete(sys, Twarp, method, alpha) - # get and pass along same signal names - _, inputs, outputs, states, _ = _process_namedio_keywords(defaults=self) - return StateSpace(Ad, Bd, C, D, Ts, - inputs=inputs, outputs=outputs, states=states) + sysd = StateSpace(Ad, Bd, C, D, Ts) + # copy over the system name, inputs, outputs, and states + if copy_names: + if name is None: + name = \ + config.defaults['namedio.sampled_system_name_prefix'] +\ + self.name + \ + config.defaults['namedio.sampled_system_name_suffix'] + sysd._copy_names(self, name=name) + # pass desired signal names if names were provided + sysd = StateSpace(sysd, **kwargs) + return sysd def dcgain(self, warn_infinite=False): """Return the zero-frequency gain diff --git a/control/xferfcn.py b/control/xferfcn.py index bcb1130e2..a5d52967a 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -1090,7 +1090,8 @@ def _common_den(self, imag_tol=None, allow_nonproper=False): return num, den, denorder - 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 @@ -1118,11 +1119,35 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None): 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. + copy_names : bool, Optional + If `copy_names` is True, copy the names of the input signals, output + signals, and states to the sampled system. If `name` is not + specified, the system name is set to the input system name with the + string '_sampled' appended. + name : string, optional + Set the name of the sampled system. If not specified and + if `copy` is `False`, a generic name is generated + with a unique integer id. If `copy` 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'. Returns ------- sysd : TransferFunction system - Discrete time system, with sample period Ts + Discrete-time system, with sample period 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`. Notes ----- @@ -1149,11 +1174,20 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None): else: Twarp = Ts numd, dend, _ = cont2discrete(sys, Twarp, method, alpha) - # get and pass along same signal names - _, inputs, outputs, _, _ = _process_namedio_keywords(defaults=self) - return TransferFunction(numd[0, :], dend, Ts, - inputs=inputs, outputs=outputs) + sysd = TransferFunction(numd[0, :], dend, Ts) + # copy over the system name, inputs, outputs, and states + if copy_names: + if name is None: + name = \ + config.defaults['namedio.sampled_system_name_prefix'] +\ + self.name + \ + config.defaults['namedio.sampled_system_name_suffix'] + sysd._copy_names(self, name=name) + # pass desired signal names if names were provided + sysd = TransferFunction(sysd, **kwargs) + return sysd + def dcgain(self, warn_infinite=False): """Return the zero-frequency (or DC) gain From 28277f3e77b7b2871bec906af101e40f6735bce0 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Wed, 23 Nov 2022 12:09:41 -0800 Subject: [PATCH 4/7] add unit tests and fixes to pass unit tests --- control/config.py | 2 +- control/iosys.py | 2 +- control/namedio.py | 2 ++ control/tests/iosys_test.py | 8 +++---- control/tests/kwargs_test.py | 2 ++ control/tests/statesp_test.py | 40 +++++++++++++++++++++++++++++++++-- control/tests/xferfcn_test.py | 31 +++++++++++++++++++++++++++ 7 files changed, 79 insertions(+), 8 deletions(-) diff --git a/control/config.py b/control/config.py index 3f2814d42..ccee252fc 100644 --- a/control/config.py +++ b/control/config.py @@ -288,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='', diff --git a/control/iosys.py b/control/iosys.py index fd3dcd749..40eaba9f5 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -2204,7 +2204,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`. diff --git a/control/namedio.py b/control/namedio.py index 52e68671b..9f82e5929 100644 --- a/control/namedio.py +++ b/control/namedio.py @@ -104,6 +104,8 @@ def _copy_names(self, sys, name=None): in case a specific name (e.g. append 'linearized') is desired. """ if name is None: self.name = sys.name + else: + self.name = name self.ninputs, self.input_index = \ sys.ninputs, sys.input_index.copy() self.noutputs, self.output_index = \ diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index 7d3b9fee9..09542bcaa 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -216,7 +216,7 @@ def test_linearize(self, tsys, kincar): @pytest.mark.usefixtures("editsdefaults") def test_linearize_named_signals(self, kincar): # Full form of the call - linearized = kincar.linearize([0, 0, 0], [0, 0], copy=True, + linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True, name='linearized') assert linearized.name == 'linearized' assert linearized.find_input('v') == 0 @@ -228,17 +228,17 @@ def test_linearize_named_signals(self, kincar): assert linearized.find_state('theta') == 2 # If we copy signal names w/out a system name, append '$linearized' - linearized = kincar.linearize([0, 0, 0], [0, 0], copy=True) + linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True) assert linearized.name == kincar.name + '$linearized' # Test legacy version as well ct.use_legacy_defaults('0.8.4') ct.config.use_numpy_matrix(False) # np.matrix deprecated - linearized = kincar.linearize([0, 0, 0], [0, 0], copy=True) + linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True) assert linearized.name == kincar.name + '_linearized' # If copy is False, signal names should not be copied - lin_nocopy = kincar.linearize(0, 0, copy=False) + lin_nocopy = kincar.linearize(0, 0, copy_names=False) assert lin_nocopy.find_input('v') is None assert lin_nocopy.find_output('x') is None assert lin_nocopy.find_state('x') is None diff --git a/control/tests/kwargs_test.py b/control/tests/kwargs_test.py index 2dc7f0563..065c4673f 100644 --- a/control/tests/kwargs_test.py +++ b/control/tests/kwargs_test.py @@ -198,8 +198,10 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup): 'NonlinearIOSystem.__init__': interconnect_test.test_interconnect_exceptions, 'StateSpace.__init__': test_unrecognized_kwargs, + 'StateSpace.sample': test_unrecognized_kwargs, 'TimeResponseData.__call__': trdata_test.test_response_copy, 'TransferFunction.__init__': test_unrecognized_kwargs, + 'TransferFunction.sample': test_unrecognized_kwargs, } # diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 6fcdade22..41f0c893a 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -820,8 +820,42 @@ def test_error_u_dynamics_mimo(self, u, sys222): sys222.dynamics(0, (1, 1), u) with pytest.raises(ValueError): sys222.output(0, (1, 1), u) - - + + def test_sample_named_signals(self): + sysc = ct.StateSpace(1.1, 1, 1, 1, inputs='u', outputs='y', states='a') + + # Full form of the call + sysd = sysc.sample(0.1, name='sampled') + assert sysd.name == 'sampled' + assert sysd.find_input('u') == 0 + assert sysd.find_output('y') == 0 + assert sysd.find_state('a') == 0 + + # If we copy signal names w/out a system name, append '$sampled' + sysd = sysc.sample(0.1) + assert sysd.name == sysc.name + '$sampled' + + # If copy is False, signal names should not be copied + sysd_nocopy = sysc.sample(0.1, copy_names=False) + assert sysd_nocopy.find_input('u') is None + assert sysd_nocopy.find_output('y') is None + assert sysd_nocopy.find_state('a') is None + + # if signal names are provided, they should override those of sysc + sysd_newnames = sysc.sample(0.1, inputs='v', outputs='x', states='b') + assert sysd_newnames.find_input('v') == 0 + assert sysd_newnames.find_input('u') is None + assert sysd_newnames.find_output('x') == 0 + assert sysd_newnames.find_output('y') is None + assert sysd_newnames.find_state('b') == 0 + assert sysd_newnames.find_state('a') is None + # test just one name + sysd_newnames = sysc.sample(0.1, inputs='v') + assert sysd_newnames.find_input('v') == 0 + assert sysd_newnames.find_input('u') is None + assert sysd_newnames.find_output('y') == 0 + assert sysd_newnames.find_output('x') is None + class TestRss: """These are tests for the proper functionality of statesp.rss.""" @@ -1164,3 +1198,5 @@ def test_params_warning(): with pytest.warns(UserWarning, match="params keyword ignored"): sys.output(0, [0], [0], {'k': 5}) + + diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py index 894da5594..e4a2b3ec0 100644 --- a/control/tests/xferfcn_test.py +++ b/control/tests/xferfcn_test.py @@ -986,6 +986,37 @@ def test_repr(self, Hargs, ref): np.testing.assert_array_almost_equal(H.num[p][m], H2.num[p][m]) np.testing.assert_array_almost_equal(H.den[p][m], H2.den[p][m]) assert H.dt == H2.dt + + def test_sample_named_signals(self): + sysc = ct.TransferFunction(1.1, (1, 2), inputs='u', outputs='y') + + # Full form of the call + sysd = sysc.sample(0.1, name='sampled') + assert sysd.name == 'sampled' + assert sysd.find_input('u') == 0 + assert sysd.find_output('y') == 0 + + # If we copy signal names w/out a system name, append '$sampled' + sysd = sysc.sample(0.1) + assert sysd.name == sysc.name + '$sampled' + + # If copy is False, signal names should not be copied + sysd_nocopy = sysc.sample(0.1, copy_names=False) + assert sysd_nocopy.find_input('u') is None + assert sysd_nocopy.find_output('y') is None + + # if signal names are provided, they should override those of sysc + sysd_newnames = sysc.sample(0.1, inputs='v', outputs='x') + assert sysd_newnames.find_input('v') == 0 + assert sysd_newnames.find_input('u') is None + assert sysd_newnames.find_output('x') == 0 + assert sysd_newnames.find_output('y') is None + # test just one name + sysd_newnames = sysc.sample(0.1, inputs='v') + assert sysd_newnames.find_input('v') == 0 + assert sysd_newnames.find_input('u') is None + assert sysd_newnames.find_output('y') == 0 + assert sysd_newnames.find_output('x') is None class TestLTIConverter: From c34526108c58ba681a0da3f1d8fcfa6a09e448c7 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Wed, 23 Nov 2022 12:35:53 -0800 Subject: [PATCH 5/7] a few more unit tests and improvements to system name handling: --- control/iosys.py | 4 ++-- control/statesp.py | 2 +- control/tests/iosys_test.py | 14 ++++++++++++++ control/xferfcn.py | 4 ++-- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/control/iosys.py b/control/iosys.py index 40eaba9f5..5b941e964 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -565,8 +565,7 @@ 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 system name, inputs, outputs, and states if copy_names: @@ -576,6 +575,7 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6, self.name + \ config.defaults['namedio.linearized_system_name_suffix'] linsys._copy_names(self, name=name) + linsys = LinearIOSystem(linsys, name=name, **kwargs) return linsys diff --git a/control/statesp.py b/control/statesp.py index b5a5fe26f..561b2d343 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -1387,7 +1387,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, config.defaults['namedio.sampled_system_name_suffix'] sysd._copy_names(self, name=name) # pass desired signal names if names were provided - sysd = StateSpace(sysd, **kwargs) + sysd = StateSpace(sysd, name=name, **kwargs) return sysd def dcgain(self, warn_infinite=False): diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index 09542bcaa..6bed7cd16 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -243,6 +243,20 @@ def test_linearize_named_signals(self, kincar): assert lin_nocopy.find_output('x') is None assert lin_nocopy.find_state('x') is None + # if signal names are provided, they should override those of kincar + linearized_newnames = kincar.linearize([0, 0, 0], [0, 0], + name='linearized', + copy_names=True, inputs=['v2', 'phi2'], outputs=['x2','y2']) + assert linearized_newnames.name == 'linearized' + assert linearized_newnames.find_input('v2') == 0 + assert linearized_newnames.find_input('phi2') == 1 + assert linearized_newnames.find_input('v') is None + assert linearized_newnames.find_input('phi') is None + assert linearized_newnames.find_output('x2') == 0 + assert linearized_newnames.find_output('y2') == 1 + assert linearized_newnames.find_output('x') is None + assert linearized_newnames.find_output('y') is None + def test_connect(self, tsys): # Define a couple of (linear) systems to interconnection linsys1 = tsys.siso_linsys diff --git a/control/xferfcn.py b/control/xferfcn.py index a5d52967a..79e46de7e 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -1185,9 +1185,9 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, config.defaults['namedio.sampled_system_name_suffix'] sysd._copy_names(self, name=name) # pass desired signal names if names were provided - sysd = TransferFunction(sysd, **kwargs) + sysd = TransferFunction(sysd, name=name, **kwargs) return sysd - + def dcgain(self, warn_infinite=False): """Return the zero-frequency (or DC) gain From dbf64f2e57bc457a63384424abd32defae2bbd1a Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Wed, 23 Nov 2022 20:20:43 -0800 Subject: [PATCH 6/7] simplify _copy_names, fix docstring errors, add names to sample_system and unit tests, namedio.copy is now deepcopy --- control/dtime.py | 29 ++++++++++++++++++++-- control/iosys.py | 31 +++++++++++++---------- control/namedio.py | 11 +++------ control/statesp.py | 29 +++++++++++----------- control/tests/discrete_test.py | 45 +++++++++++++++++++++++++++++++++- control/tests/iosys_test.py | 12 ++++----- control/tests/kwargs_test.py | 1 + control/xferfcn.py | 29 ++++++++++------------ 8 files changed, 127 insertions(+), 60 deletions(-) diff --git a/control/dtime.py b/control/dtime.py index b05d22b96..9dddd86a3 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -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 @@ -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 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 @@ -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): diff --git a/control/iosys.py b/control/iosys.py index 5b941e964..b90d35ee3 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -568,16 +568,23 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6, StateSpace(A, B, C, D, self.dt, remove_useless_states=False)) # 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: - name = \ - config.defaults['namedio.linearized_system_name_prefix'] +\ - self.name + \ + linsys.name = \ + config.defaults['namedio.linearized_system_name_prefix']+\ + linsys.name+\ config.defaults['namedio.linearized_system_name_suffix'] - linsys._copy_names(self, name=name) - linsys = LinearIOSystem(linsys, name=name, **kwargs) - return linsys + 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. @@ -2180,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_names : bool, Optional - If `copy_names` 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 is generated - with a unique integer id. If `copy` is `True`, the new system + if `copy_names` is `False`, a generic name 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.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 ------- diff --git a/control/namedio.py b/control/namedio.py index 9f82e5929..a94d1a9f5 100644 --- a/control/namedio.py +++ b/control/namedio.py @@ -6,7 +6,7 @@ # 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 @@ -99,13 +99,10 @@ def __str__(self): def _find_signal(self, name, sigdict): return sigdict.get(name, None) - def _copy_names(self, sys, 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. """ - if name is None: - self.name = sys.name - else: - self.name = name + self.name = sys.name self.ninputs, self.input_index = \ sys.ninputs, sys.input_index.copy() self.noutputs, self.output_index = \ @@ -126,7 +123,7 @@ def copy(self, name=None, use_prefix_suffix=True): """ # Create a copy of the system - newsys = copy(self) + newsys = deepcopy(self) # Update the system name if name is None and use_prefix_suffix: diff --git a/control/statesp.py b/control/statesp.py index 561b2d343..7843cb33f 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -357,7 +357,7 @@ def __init__(self, *args, init_namedio=True, **kwargs): states=states, dt=dt) elif kwargs: raise TypeError("unrecognized keyword(s): ", str(kwargs)) - + # Reset shapes (may not be needed once np.matrix support is removed) if self._isstatic(): # static gain @@ -1298,7 +1298,7 @@ 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 @@ -1327,19 +1327,17 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, 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. - copy_names : bool, Optional - If `copy_names` is True, copy the names of the input signals, output - signals, and states to the sampled system. If `name` is not - specified, the system name is set to the input system name with the - string '_sampled' appended. name : string, optional Set the name of the sampled system. If not specified and - if `copy` is `False`, a generic name is generated - with a unique integer id. If `copy` is `True`, the new system + if `copy_names` is `False`, a generic name 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 ------- @@ -1380,15 +1378,16 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, 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: - name = \ + sysd.name = \ config.defaults['namedio.sampled_system_name_prefix'] +\ - self.name + \ + sysd.name + \ config.defaults['namedio.sampled_system_name_suffix'] - sysd._copy_names(self, name=name) - # pass desired signal names if names were provided - sysd = StateSpace(sysd, name=name, **kwargs) - return sysd + 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 diff --git a/control/tests/discrete_test.py b/control/tests/discrete_test.py index 0842cbf59..4cf28a21b 100644 --- a/control/tests/discrete_test.py +++ b/control/tests/discrete_test.py @@ -458,4 +458,47 @@ def test_signal_names(self, tsys): assert ssd.state_labels == ['a', 'b', 'c'] assert ssd.output_labels == ['y'] assert tfd.input_labels == ['u'] - assert tfd.output_labels == ['y'] \ No newline at end of file + assert tfd.output_labels == ['y'] + + ssd = sample_system(ssc, 0.1) + tfd = sample_system(tfc, 0.1) + assert ssd.input_labels == ['u'] + assert ssd.state_labels == ['a', 'b', 'c'] + assert ssd.output_labels == ['y'] + assert tfd.input_labels == ['u'] + assert tfd.output_labels == ['y'] + + # system names and signal name override + sysc = StateSpace(1.1, 1, 1, 1, inputs='u', outputs='y', states='a') + + sysd = sample_system(sysc, 0.1, name='sampled') + assert sysd.name == 'sampled' + assert sysd.find_input('u') == 0 + assert sysd.find_output('y') == 0 + assert sysd.find_state('a') == 0 + + # If we copy signal names w/out a system name, append '$sampled' + sysd = sample_system(sysc, 0.1) + assert sysd.name == sysc.name + '$sampled' + + # If copy is False, signal names should not be copied + sysd_nocopy = sample_system(sysc, 0.1, copy_names=False) + assert sysd_nocopy.find_input('u') is None + assert sysd_nocopy.find_output('y') is None + assert sysd_nocopy.find_state('a') is None + + # if signal names are provided, they should override those of sysc + sysd_newnames = sample_system(sysc, 0.1, + inputs='v', outputs='x', states='b') + assert sysd_newnames.find_input('v') == 0 + assert sysd_newnames.find_input('u') is None + assert sysd_newnames.find_output('x') == 0 + assert sysd_newnames.find_output('y') is None + assert sysd_newnames.find_state('b') == 0 + assert sysd_newnames.find_state('a') is None + # test just one name + sysd_newnames = sample_system(sysc, 0.1, inputs='v') + assert sysd_newnames.find_input('v') == 0 + assert sysd_newnames.find_input('u') is None + assert sysd_newnames.find_output('y') == 0 + assert sysd_newnames.find_output('x') is None diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index 6bed7cd16..cc9c3e721 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -231,12 +231,6 @@ def test_linearize_named_signals(self, kincar): linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True) assert linearized.name == kincar.name + '$linearized' - # Test legacy version as well - ct.use_legacy_defaults('0.8.4') - ct.config.use_numpy_matrix(False) # np.matrix deprecated - linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True) - assert linearized.name == kincar.name + '_linearized' - # If copy is False, signal names should not be copied lin_nocopy = kincar.linearize(0, 0, copy_names=False) assert lin_nocopy.find_input('v') is None @@ -257,6 +251,12 @@ def test_linearize_named_signals(self, kincar): assert linearized_newnames.find_output('x') is None assert linearized_newnames.find_output('y') is None + # Test legacy version as well + ct.use_legacy_defaults('0.8.4') + ct.config.use_numpy_matrix(False) # np.matrix deprecated + linearized = kincar.linearize([0, 0, 0], [0, 0], copy_names=True) + assert linearized.name == kincar.name + '_linearized' + def test_connect(self, tsys): # Define a couple of (linear) systems to interconnection linsys1 = tsys.siso_linsys diff --git a/control/tests/kwargs_test.py b/control/tests/kwargs_test.py index 065c4673f..20a1c8e9c 100644 --- a/control/tests/kwargs_test.py +++ b/control/tests/kwargs_test.py @@ -183,6 +183,7 @@ def test_matplotlib_kwargs(function, nsysargs, moreargs, kwargs, mplcleanup): 'tf': test_unrecognized_kwargs, 'tf2io' : test_unrecognized_kwargs, 'tf2ss' : test_unrecognized_kwargs, + 'sample_system' : test_unrecognized_kwargs, 'flatsys.point_to_point': flatsys_test.TestFlatSys.test_point_to_point_errors, 'flatsys.solve_flat_ocp': diff --git a/control/xferfcn.py b/control/xferfcn.py index 79e46de7e..84188a63f 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -1090,7 +1090,7 @@ def _common_den(self, imag_tol=None, allow_nonproper=False): return num, den, denorder - 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 @@ -1119,19 +1119,17 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, 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. - copy_names : bool, Optional - If `copy_names` is True, copy the names of the input signals, output - signals, and states to the sampled system. If `name` is not - specified, the system name is set to the input system name with the - string '_sampled' appended. name : string, optional Set the name of the sampled system. If not specified and - if `copy` is `False`, a generic name is generated - with a unique integer id. If `copy` is `True`, the new system + if `copy_names` is `False`, a generic name 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 ------- @@ -1146,8 +1144,6 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, 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 ----- @@ -1178,15 +1174,16 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, sysd = TransferFunction(numd[0, :], dend, Ts) # copy over the system name, inputs, outputs, and states if copy_names: + sysd._copy_names(self) if name is None: - name = \ + sysd.name = \ config.defaults['namedio.sampled_system_name_prefix'] +\ - self.name + \ + sysd.name + \ config.defaults['namedio.sampled_system_name_suffix'] - sysd._copy_names(self, name=name) - # pass desired signal names if names were provided - sysd = TransferFunction(sysd, name=name, **kwargs) - return sysd + else: + sysd.name = name + # pass desired signal names if names were provided + return TransferFunction(sysd, name=name, **kwargs) def dcgain(self, warn_infinite=False): """Return the zero-frequency (or DC) gain From 16d9e6af15ba245dc9964aa062e704bf833909ee Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Wed, 23 Nov 2022 20:39:35 -0800 Subject: [PATCH 7/7] fix deprecated functionality of copy keyword in iosys.linearize --- control/iosys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/iosys.py b/control/iosys.py index b90d35ee3..205bfe1f5 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -568,7 +568,7 @@ def linearize(self, x0, u0, t=0, params=None, eps=1e-6, StateSpace(A, B, C, D, self.dt, remove_useless_states=False)) # Set the system name, inputs, outputs, and states - if copy in kwargs: + if 'copy' in kwargs: copy_names = kwargs.pop('copy') warn("keyword 'copy' is deprecated. please use 'copy_names'", DeprecationWarning) 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