Skip to content

Updated system class functionality #721

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

Merged
merged 18 commits into from
Apr 16, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/python-package-conda.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ on: [push, pull_request]

jobs:
test-linux:
name: Python ${{ matrix.python-version }}${{ matrix.slycot && format(' with Slycot from {0}', matrix.slycot) || ' without Slycot' }}${{ matrix.array-and-matrix == 1 && ', array and matrix' || '' }}
name: Python ${{ matrix.python-version }}${{ matrix.slycot && format(' with Slycot from {0}', matrix.slycot) || ' without Slycot' }}${{ matrix.pandas && ', with pandas' || '' }}${{ matrix.array-and-matrix == 1 && ', array and matrix' || '' }}
runs-on: ubuntu-latest

strategy:
max-parallel: 5
matrix:
python-version: [3.7, 3.9]
slycot: ["", "conda"]
pandas: [""]
array-and-matrix: [0]
include:
- python-version: 3.9
slycot: conda
pandas: conda
array-and-matrix: 1

steps:
Expand All @@ -41,6 +43,9 @@ jobs:
if [[ '${{matrix.slycot}}' == 'conda' ]]; then
conda install -c conda-forge slycot
fi
if [[ '${{matrix.pandas}}' == 'conda' ]]; then
conda install -c conda-forge pandas
fi

- name: Test with pytest
env:
Expand Down
1 change: 1 addition & 0 deletions control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
from .margins import *
from .mateqn import *
from .modelsimp import *
from .namedio import *
from .nichols import *
from .phaseplot import *
from .pzmap import *
Expand Down
2 changes: 1 addition & 1 deletion control/canonical.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# RMM, 10 Nov 2012

from .exception import ControlNotImplemented, ControlSlycot
from .lti import issiso
from .namedio import issiso
from .statesp import StateSpace, _convert_to_statespace
from .statefbk import ctrb, obsv

Expand Down
2 changes: 1 addition & 1 deletion control/dtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

"""

from .lti import isctime
from .namedio import isctime
from .statesp import StateSpace

__all__ = ['sample_system', 'c2d']
Expand Down
13 changes: 13 additions & 0 deletions control/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,16 @@ def slycot_check():
except:
slycot_installed = False
return slycot_installed


# Utility function to see if pandas is installed
pandas_installed = None
def pandas_check():
global pandas_installed
if pandas_installed is None:
try:
import pandas
pandas_installed = True
except:
pandas_installed = False
return pandas_installed
5 changes: 5 additions & 0 deletions control/flatsys/flatsys.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ def __init__(self,

# Save the length of the flat flag

def __str__(self):
return f"{NonlinearIOSystem.__str__(self)}\n\n" \
+ f"Forward: {self.forward}\n" \
+ f"Reverse: {self.reverse}"

def forward(self, x, u, params={}):

"""Compute the flat flag given the states and input.
Expand Down
10 changes: 10 additions & 0 deletions control/flatsys/linflat.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,13 @@ def reverse(self, zflag, params):
x = self.Tinv @ z
u = zflag[0][-1] - self.F @ z
return np.reshape(x, self.nstates), np.reshape(u, self.ninputs)

# Update function
def _rhs(self, t, x, u, params={}):
# Use LinearIOSystem._rhs instead of default (MRO) NonlinearIOSystem
return LinearIOSystem._rhs(self, t, x, u)

# output function
def _out(self, t, x, u, params={}):
# Use LinearIOSystem._out instead of default (MRO) NonlinearIOSystem
return LinearIOSystem._out(self, t, x, u)
73 changes: 73 additions & 0 deletions control/flatsys/systraj.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
# SUCH DAMAGE.

import numpy as np
from ..timeresp import TimeResponseData

class SystemTrajectory:
"""Class representing a system trajectory.
Expand Down Expand Up @@ -117,3 +118,75 @@ def eval(self, tlist):
self.system.reverse(zflag, self.params)

return xd, ud

# Return the system trajectory as a TimeResponseData object
def response(self, tlist, transpose=False, return_x=False, squeeze=None):
"""Return the trajectory of a system as a TimeResponseData object

Evaluate the trajectory at a list of time points, returning the state
and input vectors for the trajectory:

response = traj.response(tlist)
time, yd, ud = response.time, response.outputs, response.inputs

Parameters
----------
tlist : 1D array
List of times to evaluate the trajectory.

transpose : bool, optional
If True, transpose all input and output arrays (for backward
compatibility with MATLAB and :func:`scipy.signal.lsim`).
Default value is False.

return_x : bool, optional
If True, return the state vector when assigning to a tuple
(default = False). See :func:`forced_response` for more details.

squeeze : bool, optional
By default, if a system is single-input, single-output (SISO) then
the output response is returned as a 1D array (indexed by time).
If squeeze=True, remove single-dimensional entries from the shape
of the output even if the system is not SISO. If squeeze=False,
keep the output as a 3D array (indexed by the output, input, and
time) even if the system is SISO. The default value can be set
using config.defaults['control.squeeze_time_response'].

Returns
-------
results : TimeResponseData
Time response represented as a :class:`TimeResponseData` object
containing the following properties:

* time (array): Time values of the output.

* outputs (array): Response of the system. If the system is SISO
and squeeze is not True, the array is 1D (indexed by time). If
the system is not SISO or ``squeeze`` is False, the array is 3D
(indexed by the output, trace, and time).

* states (array): Time evolution of the state vector, represented
as either a 2D array indexed by state and time (if SISO) or a 3D
array indexed by state, trace, and time. Not affected by
``squeeze``.

* inputs (array): Input(s) to the system, indexed in the same
manner as ``outputs``.

The return value of the system can also be accessed by assigning
the function to a tuple of length 2 (time, output) or of length 3
(time, output, state) if ``return_x`` is ``True``.

"""
# Compute the state and input response using the eval function
sys = self.system
xout, uout = self.eval(tlist)
yout = np.array([
sys.output(tlist[i], xout[:, i], uout[:, i])
for i in range(len(tlist))]).transpose()

return TimeResponseData(
tlist, yout, xout, uout, issiso=sys.issiso(),
input_labels=sys.input_labels, output_labels=sys.output_labels,
state_labels=sys.state_labels,
transpose=transpose, return_x=return_x, squeeze=squeeze)
Loading