Skip to content

Commit f1cf3f6

Browse files
committed
Merge branch 'iv'
2 parents 7af0d69 + d0c6932 commit f1cf3f6

5 files changed

Lines changed: 178 additions & 60 deletions

File tree

test/test_utils.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,8 @@ def test_checkSystem(self):
5858
checkSystem(a,b)
5959

6060
def test_systemOrder(self):
61-
with self.assertRaises(ValueError):
62-
systemOrder(0, 0)
63-
64-
with self.assertRaises(ValueError):
65-
systemOrder(1, 0)
66-
67-
with self.assertRaises(ValueError):
68-
systemOrder([0], 0)
69-
70-
with self.assertRaises(ValueError):
71-
systemOrder([0], [0])
72-
73-
with self.assertRaises(ValueError):
74-
systemOrder([0, 0], [0, 0])
75-
76-
with self.assertRaises(ValueError):
77-
systemOrder([0], [0, 0])
78-
79-
61+
self.assertEqual(systemOrder(0, 0), (0, 0))
62+
self.assertEqual(systemOrder(1, 0), (0, 0))
8063
self.assertEqual(systemOrder([1],[1]), (0, 0))
8164
self.assertEqual(systemOrder([1, 1],[1, 1]), (1,1))
8265
self.assertEqual(systemOrder([1, 1, 3],[1, 1]), (2,1))

vrft/extended_tf.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# extended_tf.py - Extended definition of the discrete
2+
# transfer function implemented in scipy.signal.
3+
# Supports arithmetical operations between transfer function
4+
# and feedback loop computation
5+
#
6+
# Code author: [Alessio Russo - alessior@kth.se]
7+
# Last update: 07th January 2020, by alessior@kth.se
8+
#
19
# Copyright [2020] [Alessio Russo - alessior@kth.se]
210
# This file is part of PythonVRFT.
311
# PythonVRFT is free software: you can redistribute it and/or modify
@@ -10,9 +18,7 @@
1018
# You should have received a copy of the GNU General Public License
1119
# along with PythonVRFT. If not, see <http://www.gnu.org/licenses/>.
1220
#
13-
# Code author: [Alessio Russo - alessior@kth.se]
14-
# Last update: 06th January 2020, by alessior@kth.se
15-
#
21+
1622

1723
from __future__ import division
1824

@@ -25,6 +31,12 @@
2531

2632

2733
class ExtendedTF(scipysig.ltisys.TransferFunctionDiscrete):
34+
"""
35+
Extended definition of the discrete transfer function implemented in scipy.signal.
36+
Supports arithmetical operations between transfer function and feedback loop
37+
computation
38+
"""
39+
2840
def __init__(self, num: np.ndarray, den: np.ndarray, dt: float):
2941
self._dt = dt
3042
super().__init__(num, den, dt=dt)
@@ -97,6 +109,7 @@ def __rsub__(self, other):
97109
return ExtendedTF(numer, denom, dt=self._dt)
98110

99111
def feedback(self):
112+
""" Computes T(z)/(1+T(z)) """
100113
num = self.num
101114
den = self.den
102115
den = polyadd(num, den)

vrft/iddata.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# iddata.py - iddata object definition
2+
# Analogous to the iddata object in Matlab sysid
3+
#
4+
# Code author: [Alessio Russo - alessior@kth.se]
5+
# Last update: 07th January 2020, by alessior@kth.se
6+
#
17
# Copyright [2017-2020] [Alessio Russo - alessior@kth.se]
28
# This file is part of PythonVRFT.
39
# PythonVRFT is free software: you can redistribute it and/or modify
@@ -10,17 +16,33 @@
1016
# You should have received a copy of the GNU General Public License
1117
# along with PythonVRFT. If not, see <http://www.gnu.org/licenses/>.
1218
#
13-
# Code author: [Alessio Russo - alessior@kth.se]
14-
# Last update: 06th January 2020, by alessior@kth.se
15-
#
19+
1620

1721
import numpy as np
1822

1923
class iddata(object):
24+
"""
25+
iddata is a class analogous to the iddata object in Matlab
26+
It is used to save input/output data.
27+
"""
28+
2029
def __init__(self, y: np.ndarray = None,
2130
u: np.ndarray = None,
2231
ts: float = None,
2332
y0: np.ndarray = None):
33+
"""
34+
Input/output data (suppors SISO systems only)
35+
Parameters
36+
----------
37+
y: np.ndarray
38+
Output data
39+
u: np.ndarray
40+
Input data
41+
ts: float
42+
sampling time
43+
y0: np.ndarray
44+
Initial conditions
45+
"""
2446
self.y = np.array(y) if not isinstance(y, np.ndarray) else np.array([y]).flatten()
2547
self.u = np.array(u) if not isinstance(u, np.ndarray) else np.array([u]).flatten()
2648
self.ts = float(ts)
@@ -32,6 +54,7 @@ def __init__(self, y: np.ndarray = None,
3254

3355

3456
def checkData(self):
57+
""" Checks validity of the data """
3558
if (self.y.shape != self.u.shape):
3659
raise ValueError("Input and output size do not match.")
3760

@@ -47,4 +70,5 @@ def checkData(self):
4770
return True
4871

4972
def copy(self):
73+
""" Returns a copy of the object """
5074
return iddata(self.y, self.u, self.ts, self.y0)

vrft/utils.py

Lines changed: 89 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# utils.py - VRFT utility functions
2+
#
3+
# Code author: [Alessio Russo - alessior@kth.se]
4+
# Last update: 07th January 2020, by alessior@kth.se
5+
#
16
# Copyright [2017-2020] [Alessio Russo - alessior@kth.se]
27
# This file is part of PythonVRFT.
38
# PythonVRFT is free software: you can redistribute it and/or modify
@@ -10,21 +15,40 @@
1015
# You should have received a copy of the GNU General Public License
1116
# along with PythonVRFT. If not, see <http://www.gnu.org/licenses/>.
1217
#
13-
# Code author: [Alessio Russo - alessior@kth.se]
14-
# Last update: 06th January 2020, by alessior@kth.se
15-
#
18+
1619

1720
import numpy as np
1821
import scipy.signal as scipysig
1922
from vrft.iddata import iddata
23+
from typing import overload
2024

2125
def Doperator(p: int, q: int, x: float) -> np.ndarray:
2226
D = np.zeros((p * q, q))
2327
for i in range(q):
2428
D[i * p:(i + 1) * p, i] = x
2529
return D
2630

31+
@overload
32+
def checkSystem(tf: scipysig.dlti) -> bool:
33+
"""Returns true if a transfer function is causal
34+
Parameters
35+
----------
36+
tf : scipy.signal.dlti
37+
discrete time rational transfer function
38+
"""
39+
40+
return checkSystem(tf.num, tf.den)
41+
2742
def checkSystem(num: np.ndarray, den: np.ndarray) -> bool:
43+
"""Returns true if a transfer function is causal
44+
Parameters
45+
----------
46+
num : np.ndarray
47+
numerator of the transfer function
48+
den : np.ndarray
49+
denominator of the transfer function
50+
51+
"""
2852
try:
2953
M, N = systemOrder(num, den)
3054
except ValueError:
@@ -35,7 +59,37 @@ def checkSystem(num: np.ndarray, den: np.ndarray) -> bool:
3559

3660
return True
3761

62+
@overload
63+
def systemOrder(tf: scipysig.dlti) -> tuple:
64+
"""Returns the order of the numerator and denominator
65+
of a transfer function
66+
Parameters
67+
----------
68+
tf : scipy.signal.dlti
69+
discrete time rational transfer function
70+
71+
Returns
72+
----------
73+
(num, den): tuple
74+
Tuple containing the orders
75+
"""
76+
return systemOrder(tf.num, tf.den)
77+
3878
def systemOrder(num: np.ndarray, den: np.ndarray) -> tuple:
79+
"""Returns the order of the numerator and denominator
80+
of a transfer function
81+
Parameters
82+
----------
83+
num : np.ndarray
84+
numerator of the transfer function
85+
den : np.ndarray
86+
denominator of the transfer function
87+
88+
Returns
89+
----------
90+
(num, den): tuple
91+
Tuple containing the orders
92+
"""
3993
den = den if isinstance(den, np.ndarray) else np.array([den]).flatten()
4094
num = num if isinstance(num, np.ndarray) else np.array([num]).flatten()
4195

@@ -45,32 +99,23 @@ def systemOrder(num: np.ndarray, den: np.ndarray) -> tuple:
4599
if den.ndim == 0:
46100
den = np.expand_dims(den, axis=0)
47101

48-
N = den.size
49-
M = num.size
50-
51-
denOrder = -1
52-
numOrder = -1
53-
54-
for i, d in enumerate(den):
55-
if not np.isclose(d, 0):
56-
denOrder = N - i - 1
57-
break
58-
59-
for i, n in enumerate(num):
60-
if not np.isclose(n, 0):
61-
numOrder = M - i - 1
62-
break
63-
64-
if (denOrder == -1):
65-
raise ValueError("Denominator can not be zero.")
66-
67-
if (numOrder == -1):
68-
raise ValueError("Numerator can not be zero.")
69-
70-
return (numOrder, denOrder)
102+
return (np.poly1d(num).order, np.poly1d(den).order)
71103

72104

73105
def filter_iddata(data: iddata, L: scipysig.dlti) -> iddata:
106+
"""Filter data in an iddata object
107+
Parameters
108+
----------
109+
data : iddata
110+
iddata object containing the data to filter
111+
L : scipy.signal.dlti
112+
Transfer function used to filter the data
113+
114+
Returns
115+
-------
116+
data : iddata
117+
Filtered iddata object
118+
"""
74119
t_start = 0
75120
t_step = data.ts
76121
t_end = len(data.y) * t_step
@@ -84,9 +129,25 @@ def filter_iddata(data: iddata, L: scipysig.dlti) -> iddata:
84129
return data
85130

86131
def deconvolve_signal(T: scipysig.dlti, x: np.ndarray, dt: float) -> np.ndarray:
132+
"""Deconvolve a signal x using a specified transfer function T
133+
Parameters
134+
----------
135+
T : scipy.signal.dlti
136+
Discrete-time rational transfer function used to
137+
deconvolve the signal
138+
x : np.ndarray
139+
Signal to deconvolve
140+
dt: float
141+
Sampling time
142+
143+
Returns
144+
-------
145+
signal : np.ndarray
146+
Deconvolved signal
147+
"""
87148
impulse = scipysig.dimpulse(T)[1][0].flatten()
88149
idx1 = np.argwhere(impulse != 0)[0].item()
89150
idx2 = np.argwhere(np.isclose(impulse[idx1:], 0.) == True)
90151
idx2 = -1 if idx2.size == 0 else idx2[0].item()
91-
recovered, _ = scipysig.deconvolve(x, impulse[idx1:idx2])
92-
return recovered[np.argwhere(impulse != 0)[0].item():]
152+
signal, _ = scipysig.deconvolve(x, impulse[idx1:idx2])
153+
return signal[np.argwhere(impulse != 0)[0].item():]

vrft/vrft_algo.py

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# Copyright [2017-2020] [Alessio Russo - alessior@kth.se]
1+
# vrft_algo.py - VRFT algorithm implementation
2+
#
3+
# Code author: [Alessio Russo - alessior@kth.se]
4+
# Last update: 07th January 2020, by alessior@kth.se
5+
#
6+
# Copyright(c) [2017-2020] [Alessio Russo - alessior@kth.se]
27
# This file is part of PythonVRFT.
38
# PythonVRFT is free software: you can redistribute it and/or modify
49
# it under the terms of the GNU General Public License as published by
@@ -10,9 +15,7 @@
1015
# You should have received a copy of the GNU General Public License
1116
# along with PythonVRFT. If not, see <http://www.gnu.org/licenses/>.
1217
#
13-
# Code author: [Alessio Russo - alessior@kth.se]
14-
# Last update: 06th January 2020, by alessior@kth.se
15-
#
18+
1619

1720
from vrft.iddata import iddata
1821
from vrft.utils import systemOrder, checkSystem, \
@@ -110,16 +113,50 @@ def control_response(data: iddata, error: np.ndarray, control: list):
110113
phi = phi.T
111114
return phi
112115

113-
def compute_vrft(data: iddata, refModel: scipysig.dlti, control: list, L: scipysig.dlti):
114-
115-
data = filter_iddata(data, L)
116+
def compute_vrft(data: iddata, refModel: scipysig.dlti,
117+
control: list, prefilter: scipysig.dlti = None):
118+
"""Compute VRFT Controller
119+
Parameters
120+
----------
121+
data : iddata
122+
iddata object containing data from experiments
123+
refModel : scipy.signal.dlti
124+
Discrete Transfer Function representing the reference model
125+
control : list
126+
list of discrete transfer functions, representing the control basis
127+
prefilter : scipy.signal.dlti, optional
128+
Filter used to pre-filter the data
129+
130+
Returns
131+
-------
132+
theta : np.ndarray
133+
Coefficients computed for the control basis
134+
r : np.ndarray
135+
Virtual reference signal
136+
phi: np.ndarray
137+
Toeplitz matrix used to compute theta
138+
loss: float
139+
VRFT loss
140+
final_control: scipy.signal.dlti
141+
Final controller
142+
"""
143+
144+
# Prefilter the data
145+
if prefilter is not None and isinstance(prefilter, scipysig.dlti):
146+
data = filter_iddata(data, prefilter)
147+
148+
# Compute virtual reference
116149
r, n = virtualReference(data,
117150
refModel.num,
118151
refModel.den)
119152

153+
# Compute control response given the virtual reference
120154
phi = control_response(data, np.subtract(r, data.y[:n]), control)
155+
156+
# Compute MSE minimizer
121157
theta, phi = calc_minimum(data, phi)
122158
loss = compute_vrft_loss(data, phi, theta)
123159

160+
# Final controller
124161
final_control = np.dot(theta, control)
125162
return theta, r, phi, loss, final_control

0 commit comments

Comments
 (0)