8000 allow passing of params (for nl systems) in time responses · python-control/python-control@7c322e2 · GitHub
[go: up one dir, main page]

Skip to content

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7c322e2

Browse files
committed
allow passing of params (for nl systems) in time responses
1 parent e1e33e4 commit 7c322e2

File tree

3 files changed

+68
-18
lines changed

3 files changed

+68
-18
lines changed

control/nlsys.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,8 @@ def input_output_response(
13671367
13681368
* inputs (array): Input(s) to the system, indexed by input and time.
13691369
1370+
* params (dict): Parameters values used for the simulation.
1371+
13701372
The return value of the system can also be accessed by assigning the
13711373
function to a tuple of length 2 (time, output) or of length 3 (time,
13721374
output, state) if ``return_x`` is ``True``. If the input/output
@@ -1643,7 +1645,7 @@ def ivp_rhs(t, x):
16431645
raise TypeError("Can't determine system type")
16441646

16451647
return TimeResponseData(
1646-
soln.t, y, soln.y, u, issiso=sys.issiso(),
1648+
soln.t, y, soln.y, u, params=params, issiso=sys.issiso(),
16471649
output_labels=sys.output_labels, input_labels=sys.input_labels,
16481650
state_labels=sys.state_labels, sysname=sys.name,
16491651
title="Input/output response for " + sys.name,

control/tests/trdata_test.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ def test_trdata_labels():
236236
np.testing.assert_equal(
237237
response.input_labels, ["u[%d]" % i for i in range(sys.ninputs)])
238238

239-
# Make sure the selected input and output are both correctly transferred to the response
239+
# Make sure the selected input and output are both correctly
240+
# transferred to the response
240241
for nu in range(sys.ninputs):
241242
for ny in range(sys.noutputs):
242243
step_response = ct.step_response(sys, T, input=nu, output=ny)
@@ -339,6 +340,37 @@ def test_trdata_multitrace():
339340
np.zeros(5), np.ones(5), np.zeros((1, 5)), np.zeros(6))
340341

341342

343+
@pytest.mark.parametrize("func, args", [
344+
(ct.step_response, ()),
345+
(ct.initial_response, (1, )),
346+
(ct.forced_response, (0, 1)),
347+
(ct.input_output_response, (0, 1)),
348+
])
349+
@pytest.mark.parametrize("dt", [0, 1])
350+
def test_trdata_params(func, args, dt):
351+
# Create a nonlinear system with parameters, neutrally stable
352+
nlsys = ct.nlsys(
353+
lambda t, x, u, params: params['a'] * x[0] + u[0],
354+
states = 1, inputs = 1, outputs = 1, params={'a': 0}, dt=dt)
355+
lnsys = ct.ss([[-0.5]], [[1]], [[1]], 0, dt=dt)
356+
357+
# Compute the response, setting parameters to make things stable
358+
timevec = np.linspace(0, 1) if dt == 0 else np.arange(0, 10, 1)
359+
nlresp = func(nlsys, timevec, *args, params={'a': -0.5})
360+
lnresp = func(lnsys, timevec, *args)
361+
362+
# Make sure the modified system was stable
363+
np.testing.assert_allclose(
364+
nlresp.states, lnresp.states, rtol=1e-3, atol=1e-5)
365+
assert lnresp.params == None
366+
assert nlresp.params['a'] == -0.5
367+
368+
# Make sure the match was not accidental
369+
bdresp = func(nlsys, timevec, *args)
370+
assert not np.allclose(
371+
bdresp.states, nlresp.states, rtol=1e-3, atol=1e-5)
372+
373+
342374
def test_trdata_exceptions():
343375
# Incorrect dimension for time vector
344376
with pytest.raises(ValueError, match="Time vector must be 1D"):

control/timeresp.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ class TimeResponseData:
173173
sysname : str, optional
174174
Name of the system that created the data.
175175
176+
params : dict, optional
177+
If system is a nonlinear I/O system, set parameter values.
178+
176179
plot_inputs : bool, optional
177180
Whether or not to plot the inputs by default (can be overridden in
178181
the plot() method)
@@ -227,7 +230,7 @@ def __init__(
227230
output_labels=None, state_labels=None, input_labels=None,
228231
title=None, transpose=False, return_x=False, squeeze=None,
229232
multi_trace=False, trace_labels=None, trace_types=None,
230-
plot_inputs=True, sysname=None
233+
plot_inputs=True, sysname=None, params=None
231234
):
232235
"""Create an input/output time response object.
233236
@@ -315,6 +318,7 @@ def __init__(
315318
raise ValueError("Time vector must be 1D array")
316319
self.title = title
317320
self.sysname = sysname
321+
self.params = params
318322

319323
#
320324
# Output vector (and number of traces)
@@ -856,7 +860,7 @@ def shape_matches(s_legal, s_actual):
856860

857861

858862
# Forced response of a linear system
859-
def forced_response(sys, T=None, U=0., X0=0., transpose=False,
863+
def forced_response(sys, T=None, U=0., X0=0., transpose=False, params=None,
860864
interpolate=False, return_x=None, squeeze=None):
861865
"""Compute the output of a linear system given the input.
862866
@@ -889,6 +893,9 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
889893
X0 : array_like or float, default=0.
890894
Initial condition.
891895
896+
params : dict, optional
897+
If system is a nonlinear I/O system, set parameter values.
898+
892899
transpose : bool, default=False
893900
If True, transpose all input and output arrays (for backward
894901
compatibility with MATLAB and :func:`scipy.signal.lsim`).
@@ -981,7 +988,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
981988
warnings.warn(
982989
"interpolation not supported for nonlinear I/O systems")
983990
return input_output_response(
984-
sys, T, U, X0, transpose=transpose,
991+
sys, T, U, X0, params=params, transpose=transpose,
985992
return_x=return_x, squeeze=squeeze)
986993
else:
987994
raise TypeError('Parameter ``sys``: must be a ``StateSpace`` or'
@@ -1169,7 +1176,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
11691176
yout = np.transpose(yout)
11701177

11711178
return TimeResponseData(
1172-
tout, yout, xout, U, issiso=sys.issiso(),
1179+
tout, yout, xout, U, params=params, issiso=sys.issiso(),
11731180
output_labels=sys.output_labels, input_labels=sys.input_labels,
11741181
state_labels=sys.state_labels, sysname=sys.name, plot_inputs=True,
11751182
title="Forced response for " + sys.name, trace_types=['forced'],
@@ -1256,7 +1263,7 @@ def _process_time_response(
12561263

12571264

12581265
def step_response(sys, T=None, X0=0, input=None, output=None, T_num=None,
1259-
transpose=False, return_x=False, squeeze=None):
1266+
transpose=False, return_x=False, squeeze=None, params=None):
12601267
# pylint: disable=W0622
12611268
"""Compute the step response for a linear system.
12621269
@@ -1298,6 +1305,9 @@ def step_response(sys, T=None, X0=0, input=None, output=None, T_num=None,
12981305
Only report the step response for the listed output. If not
12991306
specified, all outputs are reported.
13001307
1308+
params : dict, optional
1309+
If system is a nonlinear I/O system, set parameter values.
1310+
13011311
T_num : int, optional
13021312
Number of time steps to use in simulation if T is not provided as an
13031313
array (autocomputed if not given); ignored if sys is discrete-time.
@@ -1403,7 +1413,7 @@ def step_response(sys, T=None, X0=0, input=None, output=None, T_num=None,
14031413
U = np.zeros((sys.ninputs, T.size))
14041414
U[i, :] = np.ones_like(T)
14051415

1406-
response = forced_response(sys, T, U, X0, squeeze=True)
1416+
response = forced_response(sys, T, U, X0, squeeze=True, params=params)
14071417
inpidx = i if input is None else 0
14081418
yout[:, inpidx, :] = response.y if output is None \
14091419
else response.y[output]
@@ -1424,11 +1434,11 @@ def step_response(sys, T=None, X0=0, input=None, output=None, T_num=None,
14241434
output_labels=output_labels, input_labels=input_labels,
14251435
state_labels=sys.state_labels, title="Step response for " + sys.name,
14261436
transpose=transpose, return_x=return_x, squeeze=squeeze,
1427-
sysname=sys.name, trace_labels=trace_labels,
1437+
sysname=sys.name, params=params, trace_labels=trace_labels,
14281438
trace_types=trace_types, plot_inputs=False)
14291439

14301440

1431-
def step_info(sysdata, T=None, T_num=None, yfinal=None,
1441+
def step_info(sysdata, T=None, T_num=None, yfinal=None, params=None,
14321442
SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1, 0.9)):
14331443
"""
14341444
Step response characteristics (Rise time, Settling Time, Peak and others).
@@ -1451,6 +1461,8 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None,
14511461
systems to simulate and the last value of the the response data is
14521462
used for a given time series of response data. Scalar for SISO,
14531463
(noutputs, ninputs) array_like for MIMO systems.
1464+
params : dict, optional
1465+
If system is a nonlinear I/O system, set parameter values.
14541466
SettlingTimeThreshold : float, optional
14551467
Defines the error to compute settling time (default = 0.02)
14561468
RiseTimeLimits : tuple (lower_threshold, upper_theshold)
@@ -1536,7 +1548,7 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None,
15361548
from .nlsys import NonlinearIOSystem
15371549

15381550
if isinstance(sysdata, (StateSpace, TransferFunction, NonlinearIOSystem)):
1539-
T, Yout = step_response(sysdata, T, squeeze=False)
1551+
T, Yout = step_response(sysdata, T, squeeze=False, params=params)
15401552
if yfinal:
15411553
InfValues = np.atleast_2d(yfinal)
15421554
else:
@@ -1651,7 +1663,7 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None,
16511663
return ret[0][0] if retsiso else ret
16521664

16531665

1654-
def initial_response(sys, T=None, X0=0, output=None, T_num=None,
1666+
def initial_response(sys, T=None, X0=0, output=None, T_num=None, params=None,
16551667
transpose=False, return_x=False, squeeze=None):
16561668
# pylint: disable=W0622
16571669
"""Compute the initial condition response for a linear system.
@@ -1684,6 +1696,9 @@ def initial_response(sys, T=None, X0=0, output=None, T_num=None,
16841696
Number of time steps to use in simulation if T is not provided as an
16851697
array (autocomputed if not given); ignored if sys is discrete-time.
16861698
1699+
params : dict, optional
1700+
If system is a nonlinear I/O system, set parameter values.
1701+
16871702
transpose : bool, optional
16881703
If True, transpose all input and output arrays (for backward
16891704
compatibility with MATLAB and :func:`scipy.signal.lsim`). Default
@@ -1748,7 +1763,7 @@ def initial_response(sys, T=None, X0=0, output=None, T_num=None,
17481763
raise ValueError("invalid value of T for this type of system")
17491764

17501765
# Compute the forced response
1751-
response = forced_response(sys, T, 0, X0)
1766+
response = forced_response(sys, T, 0, X0, params=params)
17521767

17531768
# Figure out if the system is SISO or not
17541769
issiso = sys.issiso() or output is not None
@@ -1760,15 +1775,16 @@ def initial_response(sys, T=None, X0=0, output=None, T_num=None,
17601775

17611776
# Store the response without an input
17621777
return TimeResponseData(
1763-
response.t, yout, response.x, None, issiso=issiso,
1778+
response.t, yout, response.x, None, params=params, issiso=issiso,
17641779
output_labels=output_labels, input_labels=None,
17651780
state_labels=sys.state_labels, sysname=sys.name,
17661781
title="Initial response for " + sys.name, trace_types=['initial'],
17671782
transpose=transpose, return_x=return_x, squeeze=squeeze)
17681783

17691784

1770-
def impulse_response(sys, T=None, input=None, output=None, T_num=None,
1771-
transpose=False, return_x=False, squeeze=None):
1785+
def impulse_response(
1786+
sys, T=None, input=None, output=None, T_num=None, params=None,
1787+
transpose=False, return_x=False, squeeze=None):
17721788
# pylint: disable=W0622
17731789
"""Compute the impulse response for a linear system.
17741790
@@ -1916,7 +1932,7 @@ def impulse_response(sys, T=None, input=None, output=None, T_num=None,
19161932
U[i, 0] = 1./sys.dt # unit area impulse
19171933

19181934
# Simulate the impulse response fo this input
1919-
response = forced_response(sys, T, U, X0)
1935+
response = forced_response(sys, T, U, X0, params=params)
19201936

19211937
# Store the output (and states)
19221938
inpidx = i if input is None else 0
@@ -1935,7 +1951,7 @@ def impulse_response(sys, T=None, input=None, output=None, T_num=None,
19351951
else sys.output_labels[output]
19361952

19371953
return TimeResponseData(
1938-
response.time, yout, xout, uout, issiso=issiso,
1954+
response.time, yout, xout, uout, params=params, issiso=issiso,
19391955
output_labels=output_labels, input_labels=input_labels,
19401956
state_labels=sys.state_labels, trace_labels=trace_labels,
19411957
trace_types=trace_types, title="Impulse response for " + sys.name,

0 commit comments

Comments
 (0)
0