Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Switch LTI class and subclasses to use ninputs, noutputs, nstates #515

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 8 additions & 8 deletions control/bdalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def series(sys1, *sysn):
Raises
------
ValueError
if `sys2.inputs` does not equal `sys1.outputs`
if `sys2.ninputs` does not equal `sys1.noutputs`
if `sys1.dt` is not compatible with `sys2.dt`

See Also
Expand Down Expand Up @@ -336,25 +336,25 @@ def connect(sys, Q, inputv, outputv):
"""
inputv, outputv, Q = np.asarray(inputv), np.asarray(outputv), np.asarray(Q)
# check indices
index_errors = (inputv - 1 > sys.inputs) | (inputv < 1)
index_errors = (inputv - 1 > sys.ninputs) | (inputv < 1)
if np.any(index_errors):
raise IndexError(
"inputv index %s out of bounds" % inputv[np.where(index_errors)])
index_errors = (outputv - 1 > sys.outputs) | (outputv < 1)
index_errors = (outputv - 1 > sys.noutputs) | (outputv < 1)
if np.any(index_errors):
raise IndexError(
"outputv index %s out of bounds" % outputv[np.where(index_errors)])
index_errors = (Q[:,0:1] - 1 > sys.inputs) | (Q[:,0:1] < 1)
index_errors = (Q[:,0:1] - 1 > sys.ninputs) | (Q[:,0:1] < 1)
if np.any(index_errors):
raise IndexError(
"Q input index %s out of bounds" % Q[np.where(index_errors)])
index_errors = (np.abs(Q[:,1:]) - 1 > sys.outputs)
index_errors = (np.abs(Q[:,1:]) - 1 > sys.noutputs)
if np.any(index_errors):
raise IndexError(
"Q output index %s out of bounds" % Q[np.where(index_errors)])

# first connect
K = np.zeros((sys.inputs, sys.outputs))
K = np.zeros((sys.ninputs, sys.noutputs))
for r in np.array(Q).astype(int):
inp = r[0]-1
for outp in r[1:]:
Expand All @@ -365,8 +365,8 @@ def connect(sys, Q, inputv, outputv):
sys = sys.feedback(np.array(K), sign=1)

# now trim
Ytrim = np.zeros((len(outputv), sys.outputs))
Utrim = np.zeros((sys.inputs, len(inputv)))
Ytrim = np.zeros((len(outputv), sys.noutputs))
Utrim = np.zeros((sys.ninputs, len(inputv)))
for i,u in enumerate(inputv):
Utrim[u-1,i] = 1.
for i,y in enumerate(outputv):
Expand Down
14 changes: 7 additions & 7 deletions control/canonical.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,24 @@ def reachable_form(xsys):
zsys.B[0, 0] = 1.0
zsys.A = zeros_like(xsys.A)
Apoly = poly(xsys.A) # characteristic polynomial
for i in range(0, xsys.states):
for i in range(0, xsys.nstates):
zsys.A[0, i] = -Apoly[i+1] / Apoly[0]
if (i+1 < xsys.states):
if (i+1 < xsys.nstates):
zsys.A[i+1, i] = 1.0

# Compute the reachability matrices for each set of states
Wrx = ctrb(xsys.A, xsys.B)
Wrz = ctrb(zsys.A, zsys.B)

if matrix_rank(Wrx) != xsys.states:
if matrix_rank(Wrx) != xsys.nstates:
raise ValueError("System not controllable to working precision.")

# Transformation from one form to another
Tzx = solve(Wrx.T, Wrz.T).T # matrix right division, Tzx = Wrz * inv(Wrx)

# Check to make sure inversion was OK. Note that since we are inverting
# Wrx and we already checked its rank, this exception should never occur
if matrix_rank(Tzx) != xsys.states: # pragma: no cover
if matrix_rank(Tzx) != xsys.nstates: # pragma: no cover
raise ValueError("Transformation matrix singular to working precision.")

# Finally, compute the output matrix
Expand Down Expand Up @@ -133,9 +133,9 @@ def observable_form(xsys):
zsys.C[0, 0] = 1
zsys.A = zeros_like(xsys.A)
Apoly = poly(xsys.A) # characteristic polynomial
for i in range(0, xsys.states):
for i in range(0, xsys.nstates):
zsys.A[i, 0] = -Apoly[i+1] / Apoly[0]
if (i+1 < xsys.states):
if (i+1 < xsys.nstates):
zsys.A[i, i+1] = 1

# Compute the observability matrices for each set of states
Expand All @@ -145,7 +145,7 @@ def observable_form(xsys):
# Transformation from one form to another
Tzx = solve(Wrz, Wrx) # matrix left division, Tzx = inv(Wrz) * Wrx

if matrix_rank(Tzx) != xsys.states:
if matrix_rank(Tzx) != xsys.nstates:
raise ValueError("Transformation matrix singular to working precision.")

# Finally, compute the output matrix
Expand Down
54 changes: 27 additions & 27 deletions control/frdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,11 @@ def __init__(self, *args, **kwargs):
def __str__(self):
"""String representation of the transfer function."""

mimo = self.inputs > 1 or self.outputs > 1
mimo = self.ninputs > 1 or self.noutputs > 1
outstr = ['Frequency response data']

for i in range(self.inputs):
for j in range(self.outputs):
for i in range(self.ninputs):
for j in range(self.noutputs):
if mimo:
outstr.append("Input %i to output %i:" % (i + 1, j + 1))
outstr.append('Freq [rad/s] Response')
Expand Down Expand Up @@ -201,12 +201,12 @@ def __add__(self, other):
other = _convert_to_FRD(other, omega=self.omega)

# Check that the input-output sizes are consistent.
if self.inputs != other.inputs:
if self.ninputs != other.ninputs:
raise ValueError("The first summand has %i input(s), but the \
second has %i." % (self.inputs, other.inputs))
if self.outputs != other.outputs:
second has %i." % (self.ninputs, other.ninputs))
if self.noutputs != other.noutputs:
raise ValueError("The first summand has %i output(s), but the \
second has %i." % (self.outputs, other.outputs))
second has %i." % (self.noutputs, other.noutputs))

return FRD(self.fresp + other.fresp, other.omega)

Expand Down Expand Up @@ -236,14 +236,14 @@ def __mul__(self, other):
other = _convert_to_FRD(other, omega=self.omega)

# Check that the input-output sizes are consistent.
if self.inputs != other.outputs:
if self.ninputs != other.noutputs:
raise ValueError(
"H = G1*G2: input-output size mismatch: "
"G1 has %i input(s), G2 has %i output(s)." %
(self.inputs, other.outputs))
(self.ninputs, other.noutputs))

inputs = other.inputs
outputs = self.outputs
inputs = other.ninputs
outputs = self.noutputs
fresp = empty((outputs, inputs, len(self.omega)),
dtype=self.fresp.dtype)
for i in range(len(self.omega)):
Expand All @@ -263,14 +263,14 @@ def __rmul__(self, other):
other = _convert_to_FRD(other, omega=self.omega)

# Check that the input-output sizes are consistent.
if self.outputs != other.inputs:
if self.noutputs != other.ninputs:
raise ValueError(
"H = G1*G2: input-output size mismatch: "
"G1 has %i input(s), G2 has %i output(s)." %
(other.inputs, self.outputs))
(other.ninputs, self.noutputs))

inputs = self.inputs
outputs = other.outputs
inputs = self.ninputs
outputs = other.noutputs

fresp = empty((outputs, inputs, len(self.omega)),
dtype=self.fresp.dtype)
Expand All @@ -290,8 +290,8 @@ def __truediv__(self, other):
else:
other = _convert_to_FRD(other, omega=self.omega)

if (self.inputs > 1 or self.outputs > 1 or
other.inputs > 1 or other.outputs > 1):
if (self.ninputs > 1 or self.noutputs > 1 or
other.ninputs > 1 or other.noutputs > 1):
raise NotImplementedError(
"FRD.__truediv__ is currently only implemented for SISO "
"systems.")
Expand All @@ -313,8 +313,8 @@ def __rtruediv__(self, other):
else:
other = _convert_to_FRD(other, omega=self.omega)

if (self.inputs > 1 or self.outputs > 1 or
other.inputs > 1 or other.outputs > 1):
if (self.ninputs > 1 or self.noutputs > 1 or
other.ninputs > 1 or other.noutputs > 1):
raise NotImplementedError(
"FRD.__rtruediv__ is currently only implemented for "
"SISO systems.")
Expand Down Expand Up @@ -392,10 +392,10 @@ def eval(self, omega, squeeze=None):
else:
out = self.fresp[:, :, elements]
else:
out = empty((self.outputs, self.inputs, len(omega_array)),
out = empty((self.noutputs, self.ninputs, len(omega_array)),
dtype=complex)
for i in range(self.outputs):
for j in range(self.inputs):
for i in range(self.noutputs):
for j in range(self.ninputs):
for k, w in enumerate(omega_array):
frraw = splev(w, self.ifunc[i, j], der=0)
out[i, j, k] = frraw[0] + 1.0j * frraw[1]
Expand All @@ -406,7 +406,7 @@ def __call__(self, s, squeeze=None):
"""Evaluate system's transfer function at complex frequencies.

Returns the complex frequency response `sys(s)` of system `sys` with
`m = sys.inputs` number of inputs and `p = sys.outputs` number of
`m = sys.ninputs` number of inputs and `p = sys.noutputs` number of
outputs.

To evaluate at a frequency omega in radians per second, enter
Expand Down Expand Up @@ -474,10 +474,10 @@ def feedback(self, other=1, sign=-1):

other = _convert_to_FRD(other, omega=self.omega)

if (self.outputs != other.inputs or self.inputs != other.outputs):
if (self.noutputs != other.ninputs or self.ninputs != other.noutputs):
raise ValueError(
"FRD.feedback, inputs/outputs mismatch")
fresp = empty((self.outputs, self.inputs, len(other.omega)),
fresp = empty((self.noutputs, self.ninputs, len(other.omega)),
dtype=complex)
# TODO: vectorize this
# TODO: handle omega re-mapping
Expand All @@ -487,9 +487,9 @@ def feedback(self, other=1, sign=-1):
fresp[:, :, k] = np.dot(
self.fresp[:, :, k],
linalg.solve(
eye(self.inputs)
eye(self.ninputs)
+ np.dot(other.fresp[:, :, k], self.fresp[:, :, k]),
eye(self.inputs))
eye(self.ninputs))
)

return FRD(fresp, other.omega, smooth=(self.ifunc is not None))
Expand Down
6 changes: 3 additions & 3 deletions control/freqplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ def bode_plot(syslist, omega=None,

mags, phases, omegas, nyquistfrqs = [], [], [], []
for sys in syslist:
if sys.inputs > 1 or sys.outputs > 1:
if sys.ninputs > 1 or sys.noutputs > 1:
# TODO: Add MIMO bode plots.
raise NotImplementedError(
"Bode is currently only implemented for SISO systems.")
Expand Down Expand Up @@ -582,7 +582,7 @@ def nyquist_plot(syslist, omega=None, plot=True, label_freq=0,
num=50, endpoint=True, base=10.0)

for sys in syslist:
if sys.inputs > 1 or sys.outputs > 1:
if sys.ninputs > 1 or sys.noutputs > 1:
# TODO: Add MIMO nyquist plots.
raise NotImplementedError(
"Nyquist is currently only implemented for SISO systems.")
Expand Down Expand Up @@ -672,7 +672,7 @@ def gangof4_plot(P, C, omega=None, **kwargs):
-------
None
"""
if P.inputs > 1 or P.outputs > 1 or C.inputs > 1 or C.outputs > 1:
if P.ninputs > 1 or P.noutputs > 1 or C.ninputs > 1 or C.noutputs > 1:
# TODO: Add MIMO go4 plots.
raise NotImplementedError(
"Gang of four is currently only implemented for SISO systems.")
Expand Down
22 changes: 11 additions & 11 deletions control/iosys.py
Original file line number Diff line number Diff line change
Expand Up @@ -659,25 +659,25 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None,

# Create the I/O system object
super(LinearIOSystem, self).__init__(
inputs=linsys.inputs, outputs=linsys.outputs,
states=linsys.states, params={}, dt=linsys.dt, name=name)
inputs=linsys.ninputs, outputs=linsys.noutputs,
states=linsys.nstates, params={}, dt=linsys.dt, name=name)

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

# Process input, output, state lists, if given
# Make sure they match the size of the linear system
ninputs, self.input_index = self._process_signal_list(
inputs if inputs is not None else linsys.inputs, prefix='u')
if ninputs is not None and linsys.inputs != ninputs:
inputs if inputs is not None else linsys.ninputs, prefix='u')
if ninputs is not None and linsys.ninputs != ninputs:
raise ValueError("Wrong number/type of inputs given.")
noutputs, self.output_index = self._process_signal_list(
outputs if outputs is not None else linsys.outputs, prefix='y')
if noutputs is not None and linsys.outputs != noutputs:
outputs if outputs is not None else linsys.noutputs, prefix='y')
if noutputs is not None and linsys.noutputs != noutputs:
raise ValueError("Wrong number/type of outputs given.")
nstates, self.state_index = self._process_signal_list(
states if states is not None else linsys.states, prefix='x')
if nstates is not None and linsys.states != nstates:
states if states is not None else linsys.nstates, prefix='x')
if nstates is not None and linsys.nstates != nstates:
raise ValueError("Wrong number/type of states given.")

def _update_params(self, params={}, warning=True):
Expand Down Expand Up @@ -1345,9 +1345,9 @@ def __init__(self, io_sys, ss_sys=None):
# Initialize the state space attributes
if isinstance(ss_sys, StateSpace):
# Make sure the dimension match
if io_sys.ninputs != ss_sys.inputs or \
io_sys.noutputs != ss_sys.outputs or \
io_sys.nstates != ss_sys.states:
if io_sys.ninputs != ss_sys.ninputs or \
io_sys.noutputs != ss_sys.noutputs or \
io_sys.nstates != ss_sys.nstates:
raise ValueError("System dimensions for first and second "
"arguments must match.")
StateSpace.__init__(self, ss_sys, remove_useless=False)
Expand Down
48 changes: 42 additions & 6 deletions control/lti.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,46 @@ def __init__(self, inputs=1, outputs=1, dt=None):
"""Assign the LTI object's numbers of inputs and ouputs."""

# Data members common to StateSpace and TransferFunction.
self.inputs = inputs
self.outputs = outputs
self.ninputs = inputs
self.noutputs = outputs
self.dt = dt

#
# Getter and setter functions for legacy state attributes
#
# For this iteration, generate a deprecation warning whenever the
# getter/setter is called. For a future iteration, turn it into a
# future warning, so that users will see it.
#

@property
def inputs(self):
warn("The LTI `inputs` attribute will be deprecated in a future "
"release. Use `ninputs` instead.",
DeprecationWarning, stacklevel=2)
return self.ninputs

@inputs.setter
def inputs(self, value):
warn("The LTI `inputs` attribute will be deprecated in a future "
"release. Use `ninputs` instead.",
DeprecationWarning, stacklevel=2)
self.ninputs = value

@property
def outputs(self):
warn("The LTI `outputs` attribute will be deprecated in a future "
"release. Use `noutputs` instead.",
DeprecationWarning, stacklevel=2)
return self.noutputs

@outputs.setter
def outputs(self, value):
warn("The LTI `outputs` attribute will be deprecated in a future "
"release. Use `noutputs` instead.",
DeprecationWarning, stacklevel=2)
self.noutputs = value

def isdtime(self, strict=False):
"""
Check to see if a system is a discrete-time system
Expand Down Expand Up @@ -88,7 +124,7 @@ def isctime(self, strict=False):

def issiso(self):
'''Check to see if a system is single input, single output'''
return self.inputs == 1 and self.outputs == 1
return self.ninputs == 1 and self.noutputs == 1

def damp(self):
'''Natural frequency, damping ratio of system poles
Expand Down Expand Up @@ -126,7 +162,7 @@ def frequency_response(self, omega, squeeze=None):
G(exp(j*omega*dt)) = mag*exp(j*phase).

In general the system may be multiple input, multiple output (MIMO),
where `m = self.inputs` number of inputs and `p = self.outputs` number
where `m = self.ninputs` number of inputs and `p = self.noutputs` number
of outputs.

Parameters
Expand Down Expand Up @@ -475,7 +511,7 @@ def evalfr(sys, x, squeeze=None):

Returns the complex frequency response `sys(x)` where `x` is `s` for
continuous-time systems and `z` for discrete-time systems, with
`m = sys.inputs` number of inputs and `p = sys.outputs` number of
`m = sys.ninputs` number of inputs and `p = sys.noutputs` number of
outputs.

To evaluate at a frequency omega in radians per second, enter
Expand Down Expand Up @@ -532,7 +568,7 @@ def freqresp(sys, omega, squeeze=None):
"""Frequency response of an LTI system at multiple angular frequencies.

In general the system may be multiple input, multiple output (MIMO), where
`m = sys.inputs` number of inputs and `p = sys.outputs` number of
`m = sys.ninputs` number of inputs and `p = sys.noutputs` number of
outputs.

Parameters
Expand Down