Piotr Banaszkiewicz

Identyfikacja obiektu regulacji

Najpierw zaimportuję wszystkie potrzebne moduły Pythona do załadowania plików Matlaba oraz wygenerowania odpowiednich wykresów.


In [1]:
%matplotlib inline
import numpy as np
import scipy.io  # do odczytania pliku z Matlaba
import scipy.signal  # do obiektów transmitancji
import scipy.interpolate  # do interpolacji i porównania wyników symulacji
import sympy  # do obliczeń symbolicznych
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(font="DejaVu Sans")
sns.set_context('poster')

s = sympy.symbols('s')  # zdefiniowana tutaj, aby nie tracić czasu inicjalizacji za każdym razem gdy jest wywoływana RMSE3

In [2]:
matlab = scipy.io.loadmat("obiekt.mat")

In [3]:
obiekt = matlab['y']
matlab['y'].shape


Out[3]:
(60, 1)

Jak widać, obiekt został zaimportowany poprawnie. Możemy teraz łatwo wyświetlić zapisaną odpowiedź skokową tego obiektu:


In [4]:
plt.xlabel(u'Czas')
plt.ylabel(u'Odpowiedź')
plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')
plt.legend()


Out[4]:
<matplotlib.legend.Legend at 0x7f5fd20358d0>

Odpowiedź tego rzeczywistego obiektu regulacji odbiega znacząco od "ładnych", akademickich obiektów regulacji. Wynika to z faktu, że na obiekt oddziaływały zakłócenia. Pomimo tego możemy powiedzieć o tym obiekcie, że albo jest niskiego rzędu wraz z opóźnieniem, albo wysokiego rzędu, gdyż tylko takie obiekty mają podobne odpowiedzi skokowe.

Obiekt inercyjny 1. rzędu z opóźnieniem

Transmitancja takiego obiektu wynosi: $$ G(s) = \frac{ke^{-s\theta}}{Ts+1} $$

Parametrami $k$, $T$ oraz $\theta$ możemy manipulować. Jednym z zastosowanych podczas zajęć podejść była identyfikacja metodą prób i błędów.

Metoda prób i błędów

Własnoręcznie zidentyfikowałem takie parametry obiektu:


In [5]:
k = 2.135
T = 15.57
theta = 7.1

Zobaczmy odpowiedź skokową obiektu o ww. transmitancji i tychże parametrach:


In [6]:
plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')

obiekt1_pb = scipy.signal.lti([k], [T, 1])

time_space = np.linspace(0, 59, 1000)
obiekt1_pb_y = obiekt1_pb.output(U=[0 if t < theta else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt1_pb_y[1], label=u'Obiekt przybliżony metodą prób i błędów')

plt.legend()


Out[6]:
<matplotlib.legend.Legend at 0x7f5fd1ef7c50>

Niestety z powodu braku osbługi outputdelay w pakietach naukowych Pythona, musiałem opóźnienie zasymulować inaczej: odpowiedź skokowa (1) zaczyna się dopiero od $t = \theta$, wcześniej $t = 0$.

Możemy zobaczyć zbliżoną postać odpowiedzi do rzeczywistego obiektu. Aby sprawdzić jak "blisko" jest zasymulowany obiekt do obiektu rzeczywistego użyłem pomiaru pierwiastka błędu średniokwadratowego:


In [7]:
def RMSE_interp(y1, t1, y2, t2):
    "1-D interpolate first series of data, compute Mean Square Error for both data"
    interp = scipy.interpolate.InterpolatedUnivariateSpline(t1, y1)
    y1_interpolated = interp(t2)
    error = np.subtract(y2, y1_interpolated)
    return np.sum(error ** 2) / len(error)

def RMSE1(args):
    global obiekt
    k, T, theta = args
    obiekt2 = scipy.signal.lti([k], [T, 1])
    time = np.linspace(0, 59, 1000)
    obiekt2_y = obiekt2.output(U=[0 if t < theta else 1 for t in time], T=time)
    return RMSE_interp(obiekt[:,0], range(60), obiekt2_y[1], time)

In [8]:
RMSE_interp(obiekt[:,0], range(60), obiekt1_pb_y[1], time_space)


Out[8]:
0.0016202483656148508

In [9]:
RMSE1([k, T, theta])


Out[9]:
0.0016202483656148508

Jak widać przy okazji zdefiniowałem funkcję, którą będzie później można wykorzystać w metodzie automatycznej optymalizacji, opartej na szukaniu minimum lokalnego. Funkcja ta, RMSE1, interpoluje odpowiedź obiektu rzeczywistego, gdyż jest on oparty tylko na 60 pomiarach. Aby uzyskać większą dokładność w znalezieniu $\theta$, postanowiłem wykorzystać 1000 punktów.

Metoda automatycznej optymalizacji

Jak wspomniałem wyżej, metoda ta opiera się na szukaniu minimum lokalnego w pobliżu pewnego punktu. W pakiecie scipy Pythona znajduje się wiele funkcji optymalizujących, ja skorzystam z najprostszej fmin.


In [10]:
obiekt1_args = k2, T2, theta2 = scipy.optimize.fmin(RMSE1, (k, T, theta))


Optimization terminated successfully.
         Current function value: 0.001495
         Iterations: 67
         Function evaluations: 128

Jak widać algorytmowi udało się znaleźć lepszą (mniejszą) wartość MSE:


In [11]:
RMSE1([k, T, theta]) - RMSE1([k2, T2, theta2])


Out[11]:
0.00012527944690041864

Parametry, które uzyskał algorytm, różnią się niewiele od tych odkrytych przeze mnie "ręcznie":


In [12]:
k2, T2, theta2


Out[12]:
(2.1479136510969608, 15.626136356553655, 7.0178563540140857)

Porównanie wyników obydwóch metod


In [13]:
plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')

obiekt1_pb = scipy.signal.lti([k], [T, 1])

time_space = np.linspace(0, 59, 1000)
obiekt1_pb_y = obiekt1_pb.output(U=[0 if t < theta else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt1_pb_y[1], label=u'Obiekt przybliżony metodą prób i błędów')

obiekt1_opt = scipy.signal.lti([k2], [T2, 1])

time_space = np.linspace(0, 59, 1000)
obiekt1_opt_y = obiekt1_opt.output(U=[0 if t < theta2 else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt1_opt_y[1], label=u'Obiekt przybliżony metodą optymalizacji')

plt.xlabel(u'Czas')
plt.ylabel(u'Odpowiedź')
plt.legend(loc=2)


Out[13]:
<matplotlib.legend.Legend at 0x7f5fd1de0a50>

Różnica jest tak niewielka, że musiałem powiększyć wyraźnie wykres, aby była chociaż trochę zauważalna. Świadczy to bardzo dobrze o ręcznym wyniku identyfikacji obiektu. Pozwolę sobie przypomnieć wyniki błędu średniokwadratowego. Metoda prób i błędów:


In [14]:
RMSE1((k, T, theta))


Out[14]:
0.0016202483656148508

Metoda optymalizacji:


In [15]:
RMSE1((k2, T2, theta2))


Out[15]:
0.0014949689187144322

Obiekt inercyjny 2. rzędu z opóźnieniem

Transmitancja takiego obiektu wynosi: $$ H(s) = \frac{ke^{-s\theta}}{(T_1s+1)(T_2s+1)} = \frac{ke^{-s\theta}}{T_1T_2s^2 + (T_1+T_2)s + 1}$$

Parametrami $k$, $T_1$, $T_2$ oraz $\theta$ możemy, jak w poprzednim przykładzie, manipulować. W przypadku tego obiektu będziemy posługiwać się tylko metodą automatycznego szukania minimalnego błędu średniokwadratowego.

Metoda automatycznej optymalizacji

Na początek zdefinuję funkcję RMSE2 zwracającą błąd średniokwadratowy dla nowego obiektu inercyjnego drugiego rzędu. Jest ona bardzo zbliżona do funkcji RMSE1 dla poprzedniego obiektu (nadal wykorzystujemy interpolację dla obiektu rzeczywistego):


In [16]:
def RMSE2(args):
    global obiekt
    k, T1, T2, theta = args
    obiekt2 = scipy.signal.lti([k], [T1 * T2, T1 + T2, 1])
    time = np.linspace(0, 59, 100)
    obiekt2_y = obiekt2.output(U=[0 if t < theta else 1 for t in time], T=time)
    return RMSE_interp(obiekt[:,0], range(60), obiekt2_y[1], time)

Zobaczmy, jak zadziała ta funkcja dla przykładowych danych $k = 2$, $T_1 = 2$, $T_2 = 13$, $\theta = 6$:


In [17]:
k = 2
T1 = 2
T2 = 13
theta = 6

print RMSE2((k, T1, T2, theta))

plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')

obiekt2 = scipy.signal.lti([k], [T1 * T2, T1 + T2, 1])

time_space = np.linspace(0, 59, 100)
obiekt2_y = obiekt2.output(U=[0 if t < theta else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt2_y[1], label=u'Obiekt przybliżony metodą optymalizacji')

plt.xlabel(u'Czas')
plt.ylabel(u'Odpowiedź')
plt.legend(loc=2)


0.00542083084015
Out[17]:
<matplotlib.legend.Legend at 0x7f5fd0208d90>

Sprawdźmy, czy damy radę zoptymalizować ten wynik:


In [18]:
obiekt2_args = k_2, T1_2, T2_2, theta_2 = scipy.optimize.fmin(RMSE2, (k, T1, T2, theta))


Optimization terminated successfully.
         Current function value: 0.001333
         Iterations: 119
         Function evaluations: 208

Metoda optymalizacji otrzymała dużo lepszy wynik, chociaż trochę gorszy niż w przypadku obiektu inercyjnego 1. rzędu.


In [19]:
k_2, T1_2, T2_2, theta_2, RMSE2((k_2, T1_2, T2_2, theta_2))


Out[19]:
(2.1332876280357231,
 1.5936551465747144,
 15.032764266133078,
 5.9316191681831638,
 0.0013332569816151283)

In [20]:
plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')

obiekt2 = scipy.signal.lti([k_2], [T1_2 * T2_2, T1_2 + T2_2, 1])

time_space = np.linspace(0, 59, 100)
obiekt2_y = obiekt2.output(U=[0 if t < theta_2 else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt2_y[1], label=u'Obiekt przybliżony metodą optymalizacji')

plt.xlabel(u'Czas')
plt.ylabel(u'Odpowiedź')
plt.legend(loc=2)


Out[20]:
<matplotlib.legend.Legend at 0x7f5fd1dbe810>

Podczas moich testów okazało się, że funkcja scipy.optimize.fmin jest bardzo czuła na punkt początkowy w przypadku tego obiektu i łatwo może znaleźć nieoptymalny wynik.

Obiekt wieloinercyjny bez opóźnienia

Transmitancja takiego obiektu wynosi: $$ J(s) = \frac{k}{(Ts+1)^n} $$

Parametrami $k$, $T$, oraz $n$ możemy, jak w poprzednich przykładach, manipulować. W przypadku tego obiektu będziemy ponownie posługiwać się tylko metodą automatycznego szukania minimalnego błędu średniokwadratowego.

Metoda automatycznej optymalizacji

W tym przykładzie użyjemy pakietu do obliczeń symbolicznych Pythona, Sympy. Wykorzystamy go, aby utworzyć wielomian $n$-tego stopnia, który jest mianownikiem szukanej transmitancji.


In [21]:
def RMSE3(args):
    global obiekt
    global s
    k, T, n = args
    
    n = int(n)  # musi być całkowity wykładnik
    
    poly = sympy.expand((T * s + 1) ** n).as_poly()
    coeffs = poly.all_coeffs()
    
    #print poly, coeffs, k
    obiekt2 = scipy.signal.lti([k], [float(x) for x in coeffs])
    
    time = np.linspace(0, 59, 100)
    obiekt2_y = obiekt2.output(U=[1 for t in time], T=time)
    return RMSE_interp(obiekt[:,0], range(60), obiekt2_y[1], time)

In [22]:
obiekt3_args = k, T, n = scipy.optimize.fmin(RMSE3, (2, 4, 3))


Optimization terminated successfully.
         Current function value: 0.002345
         Iterations: 57
         Function evaluations: 119

Już teraz możemy ocenić, że dla tych danych wejściowych ten obiekt jest najgorzej przybliżony. Zobaczmy to na wykresie:


In [23]:
n = int(n)
plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')

poly = sympy.expand((T * s + 1) ** n).as_poly()
coeffs = poly.all_coeffs()
obiekt3 = scipy.signal.lti([k], [float(x) for x in coeffs])

time_space = np.linspace(0, 59, 100)
obiekt3_y = obiekt3.output(U=[1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt3_y[1], label=u'Obiekt przybliżony metodą optymalizacji')

plt.xlabel(u'Czas')
plt.ylabel(u'Odpowiedź')
plt.legend(loc=2)


Out[23]:
<matplotlib.legend.Legend at 0x7f5fcbe73390>

Porównanie i podsumowanie

Przeprowadziłem analizę dla różnych typów obiektów. W przypadku każdego z nich identyfikacja przebiegła pomyślnie: obiekt rzeczywisty został dosyć dokładnie przybliżony numerycznie:


In [24]:
plt.plot(range(60), obiekt[:,0], label=u'Obiekt rzeczywisty')

k, T, theta = obiekt1_args
obiekt1 = scipy.signal.lti([k], [T, 1])
time_space = np.linspace(0, 59, 1000)
obiekt1_y = obiekt1.output(U=[0 if t < theta else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt1_y[1], label=u'Obiekt inercyjny 1. rzędu')

error1 = RMSE1(obiekt1_args)

k, T1, T2, theta = obiekt2_args
obiekt2 = scipy.signal.lti([k], [T1 * T2, T1 + T2, 1])
time_space = np.linspace(0, 59, 100)
obiekt2_y = obiekt2.output(U=[0 if t < theta else 1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt2_y[1], label=u'Obiekt inercyjny 2. rzędu')

error2 = RMSE2(obiekt2_args)

k, T, n = obiekt3_args
error3 = RMSE3(obiekt3_args)
n = int(n)

poly = sympy.expand((T * s + 1) ** n).as_poly()
coeffs = poly.all_coeffs()
obiekt3 = scipy.signal.lti([k], [float(x) for x in coeffs])

obiekt3_y = obiekt3.output(U=[1 for t in time_space], T=time_space)
plt.plot(time_space, obiekt3_y[1], label=u'Obiekt wieloinercyjny bez opóźnienia')

plt.xlabel(u'Czas')
plt.ylabel(u'Odpowiedź')
plt.legend(loc=4)


Out[24]:
<matplotlib.legend.Legend at 0x7f5fcbd4c610>

Poniżej przedstawiam wyliczone błędy średniokwadratowe dla poszczególnych obiektów:


In [25]:
print "Obiekt inercyjny 1. rzędu: ", error1
print "Obiekt inercyjny 2. rzędu: ", error2
print "Obiekt wieloinercyjny bez opóźnienia: ", error3


Obiekt inercyjny 1. rzędu:  0.00149496891871
Obiekt inercyjny 2. rzędu:  0.00133325698162
Obiekt wieloinercyjny bez opóźnienia:  0.00234511425377

Najlepiej dopasowany został obiekt inercyjny 2. rzędu z opóźnieniem.