Skip to content

Commit afa4967

Browse files
committed
add flatsys.systraj.response() to create TimeResponseData object
1 parent dc5a392 commit afa4967

File tree

6 files changed

+169
-39
lines changed

6 files changed

+169
-39
lines changed

control/flatsys/linflat.py

+10
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,13 @@ def reverse(self, zflag, params):
140140
x = self.Tinv @ z
141141
u = zflag[0][-1] - self.F @ z
142142
return np.reshape(x, self.nstates), np.reshape(u, self.ninputs)
143+
144+
# Update function
145+
def _rhs(self, t, x, u, params={}):
146+
# Use LinearIOSystem._rhs instead of default (MRO) NonlinearIOSystem
147+
return LinearIOSystem._rhs(self, t, x, u)
148+
149+
# output function
150+
def _out(self, t, x, u, params={}):
151+
# Use LinearIOSystem._out instead of default (MRO) NonlinearIOSystem
152+
return LinearIOSystem._out(self, t, x, u)

control/flatsys/systraj.py

+73
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
# SUCH DAMAGE.
3838

3939
import numpy as np
40+
from ..timeresp import TimeResponseData
4041

4142
class SystemTrajectory:
4243
"""Class representing a system trajectory.
@@ -117,3 +118,75 @@ def eval(self, tlist):
117118
self.system.reverse(zflag, self.params)
118119

119120
return xd, ud
121+
122+
# Return the system trajectory as a TimeResponseData object
123+
def response(self, tlist, transpose=False, return_x=False, squeeze=None):
124+
"""Return the trajectory of a system as a TimeResponseData object
125+
126+
Evaluate the trajectory at a list of time points, returning the state
127+
and input vectors for the trajectory:
128+
129+
response = traj.response(tlist)
130+
time, yd, ud = response.time, response.outputs, response.inputs
131+
132+
Parameters
133+
----------
134+
tlist : 1D array
135+
List of times to evaluate the trajectory.
136+
137+
transpose : bool, optional
138+
If True, transpose all input and output arrays (for backward
139+
compatibility with MATLAB and :func:`scipy.signal.lsim`).
140+
Default value is False.
141+
142+
return_x : bool, optional
143+
If True, return the state vector when assigning to a tuple
144+
(default = False). See :func:`forced_response` for more details.
145+
146+
squeeze : bool, optional
147+
By default, if a system is single-input, single-output (SISO) then
148+
the output response is returned as a 1D array (indexed by time).
149+
If squeeze=True, remove single-dimensional entries from the shape
150+
of the output even if the system is not SISO. If squeeze=False,
151+
keep the output as a 3D array (indexed by the output, input, and
152+
time) even if the system is SISO. The default value can be set
153+
using config.defaults['control.squeeze_time_response'].
154+
155+
Returns
156+
-------
157+
results : TimeResponseData
158+
Time response represented as a :class:`TimeResponseData` object
159+
containing the following properties:
160+
161+
* time (array): Time values of the output.
162+
163+
* outputs (array): Response of the system. If the system is SISO
164+
and squeeze is not True, the array is 1D (indexed by time). If
165+
the system is not SISO or ``squeeze`` is False, the array is 3D
166+
(indexed by the output, trace, and time).
167+
168+
* states (array): Time evolution of the state vector, represented
169+
as either a 2D array indexed by state and time (if SISO) or a 3D
170+
array indexed by state, trace, and time. Not affected by
171+
``squeeze``.
172+
173+
* inputs (array): Input(s) to the system, indexed in the same
174+
manner as ``outputs``.
175+
176+
The return value of the system can also be accessed by assigning
177+
the function to a tuple of length 2 (time, output) or of length 3
178+
(time, output, state) if ``return_x`` is ``True``.
179+
180+
"""
181+
# Compute the state and input response using the eval function
182+
sys = self.system
183+
xout, uout = self.eval(tlist)
184+
yout = np.array([
185+
sys.output(tlist[i], xout[:, i], uout[:, i])
186+
for i in range(len(tlist))]).transpose()
187+
188+
return TimeResponseData(
189+
tlist, yout, xout, uout, issiso=sys.issiso(),
190+
input_labels=sys.input_labels, output_labels=sys.output_labels,
191+
state_labels=sys.state_labels,
192+
transpose=transpose, return_x=return_x, squeeze=squeeze)

control/tests/flatsys_test.py

+26
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,29 @@ def test_point_to_point_errors(self):
378378
with pytest.raises(TypeError, match="unrecognized keyword"):
379379
traj_method = fs.point_to_point(
380380
flat_sys, timepts, x0, u0, xf, uf, solve_ivp_method=None)
381+
382+
@pytest.mark.parametrize(
383+
"xf, uf, Tf",
384+
[([1, 0], [0], 2),
385+
([0, 1], [0], 3),
386+
([1, 1], [1], 4)])
387+
def test_response(self, xf, uf, Tf):
388+
# Define a second order integrator
389+
sys = ct.StateSpace([[-1, 1], [0, -2]], [[0], [1]], [[1, 0]], 0)
390+
flatsys = fs.LinearFlatSystem(sys)
391+
392+
# Define the basis set
393+
poly = fs.PolyFamily(6)
394+
395+
x1, u1, = [0, 0], [0]
396+
traj = fs.point_to_point(flatsys, Tf, x1, u1, xf, uf, basis=poly)
397+
398+
# Compute the response the regular way
399+
T = np.linspace(0, Tf, 10)
400+
x, u = traj.eval(T)
401+
402+
# Recompute using response()
403+
response = traj.response(T, squeeze=False)
404+
np.testing.assert_equal(T, response.time)
405+
np.testing.assert_equal(u, response.inputs)
406+
np.testing.assert_equal(x, response.states)

control/tests/kwargs_test.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ def test_unrecognized_kwargs():
115115
with pytest.raises(TypeError, match="unrecognized keyword"):
116116
function(*args, **kwargs, unknown=None)
117117

118+
# If we opened any figures, close them to avoid matplotlib warnings
119+
if plt.gca():
120+
plt.close('all')
121+
118122

119123
def test_matplotlib_kwargs():
120124
# Create a SISO system for use in parameterized tests
@@ -141,7 +145,7 @@ def test_matplotlib_kwargs():
141145
with pytest.raises(AttributeError, match="has no property"):
142146
function(*args, **kwargs, unknown=None)
143147

144-
# If we opened any figures, close them
148+
# If we opened any figures, close them to avoid matplotlib warnings
145149
if plt.gca():
146150
plt.close('all')
147151

@@ -171,7 +175,7 @@ def test_matplotlib_kwargs():
171175
'lqr': test_unrecognized_kwargs,
172176
'nyquist': test_matplotlib_kwargs,
173177
'nyquist_plot': test_matplotlib_kwargs,
174-
'pzmap': test_matplotlib_kwargs,
178+
'pzmap': test_unrecognized_kwargs,
175179
'rlocus': test_unrecognized_kwargs,
176180
'root_locus': test_unrecognized_kwargs,
177181
'rss': test_unrecognized_kwargs,

doc/classes.fig

+54-37
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,10 @@ Letter
77
Single
88
-2
99
1200 2
10-
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
11-
5400 3375 6600 3375 6600 3825 5400 3825 5400 3375
12-
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
13-
6900 2175 8100 2175 8100 2625 6900 2625 6900 2175
14-
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
15-
7275 3375 8925 3375 8925 3825 7275 3825 7275 3375
1610
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
1711
9750 3375 12075 3375 12075 4725 9750 4725 9750 3375
1812
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
1913
9750 6000 12075 6000 12075 7350 9750 7350 9750 6000
20-
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
21-
4425 975 6525 975 6525 1425 4425 1425 4425 975
22-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
23-
7875 2550 10875 3450
24-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
25-
5850 6075 5850 6975
26-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
27-
4350 6075 5625 6975
28-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
29-
5925 3750 5925 5775
3014
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 1 0 2
3115
1 1 1.00 60.00 120.00
3216
8925 3600 9750 3600
@@ -48,28 +32,13 @@ Single
4832
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 1 0 2
4933
1 1 1.00 60.00 120.00
5034
2700 5400 3075 5850
51-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
52-
4125 4875 5400 5775
5335
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
5436
1650 4500 6750 4500 6750 7425 1650 7425 1650 4500
5537
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
5638
1650 7950 6150 7950 6150 8550 1650 8550 1650 7950
57-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 -1 0 0 2
58-
2400 5400 2400 8025
59-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
60-
5250 1350 3825 4575
61-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
62-
3300 4875 3000 5100
63-
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 1 0 2
64-
1 1 1.00 60.00 120.00
65-
4350 4875 5625 5775
6639
2 1 0 2 4 7 50 -1 -1 0.000 0 0 -1 1 0 2
6740
1 1 1.00 60.00 120.00
6841
2775 8175 4200 8175
69-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
70-
7575 2550 8025 3450
71-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 -1 0 0 2
72-
9075 7800 9675 7800
7342
2 1 0 2 4 7 50 -1 -1 0.000 0 0 -1 1 0 2
7443
1 1 1.00 60.00 120.00
7544
9075 8100 9675 8100
@@ -78,16 +47,63 @@ Single
7847
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 0 1 2
7948
1 1 1.00 60.00 120.00
8049
4725 5925 5175 5925
81-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
82-
7350 2550 6225 3450
83-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
84-
5775 1350 7575 2250
8550
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 1 1 2
8651
1 1 1.00 60.00 120.00
8752
1 1 1.00 60.00 120.00
8853
6525 3600 7275 3600
89-
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 0 2
54+
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 1 0 2
55+
1 1 1.00 60.00 120.00
56+
5775 8175 9975 6300
57+
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
58+
5400 3375 6600 3375 6600 3900 5400 3900 5400 3375
59+
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
60+
7050 2175 8100 2175 8100 2700 7050 2700 7050 2175
61+
2 2 1 1 1 7 50 -1 -1 4.000 0 0 -1 0 0 5
62+
4500 975 6525 975 6525 1500 4500 1500 4500 975
63+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
64+
1 0 1.00 60.00 90.00
65+
5250 1350 3825 4575
66+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
67+
1 0 1.00 60.00 90.00
68+
5775 1350 7575 2250
69+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
70+
1 0 1.00 60.00 90.00
71+
7875 2550 10875 3450
72+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
73+
1 0 1.00 60.00 90.00
74+
7575 2550 8025 3450
75+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
76+
1 0 1.00 60.00 90.00
77+
7350 2550 6225 3450
78+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
79+
1 0 1.00 60.00 90.00
80+
3300 4875 3000 5100
81+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
82+
1 0 1.00 60.00 90.00
9083
3825 4875 3825 5775
84+
2 1 0 2 4 7 50 -1 -1 0.000 0 0 7 1 0 2
85+
1 1 1.00 60.00 120.00
86+
4350 4875 5625 5775
87+
2 2 1 1 0 7 50 -1 -1 4.000 0 0 -1 0 0 5
88+
7350 3375 8925 3375 8925 3900 7350 3900 7350 3375
89+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 -1 0 1 2
90+
1 0 1.00 60.00 90.00
91+
9075 7800 9675 7800
92+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
93+
1 0 1.00 60.00 90.00
94+
4350 6075 5625 6975
95+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 -1 0 1 2
96+
1 0 1.00 60.00 90.00
97+
2400 5400 2400 8025
98+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
99+
1 0 1.00 60.00 90.00
100+
5850 6075 5850 6975
101+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
102+
1 0 1.00 60.00 90.00
103+
4125 4875 5400 5775
104+
2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2
105+
1 0 1.00 60.00 90.00
106+
5925 3750 5925 5775
91107
4 0 0 50 -1 0 12 0.0000 4 165 885 5400 3300 statesp.py\001
92108
4 0 0 50 -1 0 12 0.0000 4 195 420 8175 2325 lti.py\001
93109
4 2 0 50 -1 0 12 0.0000 4 195 885 8925 3300 xferfcn.py\001
@@ -119,7 +135,6 @@ Single
119135
4 1 1 50 -1 16 12 0.0000 4 210 1875 10875 6300 TimeResponseData\001
120136
4 0 4 50 -1 16 12 0.0000 4 210 1155 10950 6675 to_pandas()\001
121137
4 1 1 50 -1 16 12 0.0000 4 210 1800 10875 7200 pandas.DataFrame\001
122-
4 1 4 50 -1 16 12 0.0000 4 210 2295 8325 6450 input_output_response()\001
123138
4 0 1 50 -1 16 12 0.0000 4 210 1755 9750 7875 Class dependency\001
124139
4 0 4 50 -1 16 12 0.0000 4 210 2475 9750 8175 Conversion [via function()]\001
125140
4 0 0 50 -1 0 12 0.0000 4 150 1380 9750 8475 Source code file\001
@@ -130,3 +145,5 @@ Single
130145
4 1 4 50 -1 16 12 0.0000 4 210 600 6975 3825 tf2ss()\001
131146
4 1 4 50 -1 16 12 0.0000 4 210 600 6975 3450 ss2tf()\001
132147
4 1 4 50 -1 16 12 0.0000 4 210 300 5025 6150 ic()\001
148+
4 1 4 50 -1 16 12 0.0000 4 210 2295 8325 6075 input_output_response()\001
149+
4 2 4 50 -1 16 12 0.0000 4 210 1035 8175 6975 response()\001

doc/classes.pdf

690 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)