Projekt 2

Strona: SEZAM - System Edukacyjnych Zasobów Akademickich i Multimedialnych
Kurs: Integracja Technik Sztucznej Inteligencji
Książka: Projekt 2
Wydrukowane przez użytkownika: Gość
Data: poniedziałek, 6 października 2025, 07:39

Opis

Budowa i badanie regulatora rozmytego

1. Zadanie

Celem zadania jest zaprojektowanie, implementacja oraz dostrojenie regulatora rozmytego, który ma zastąpić podstawowy regulator PID.

2. Realizacja

Należy wykonać następujące kroki:

  1. Zaprojektować strukturę rozmytego układu regulacji. Układ regulacji powinien uwzględniać niesymetryczne działanie układu oraz ewentualną nieliniowość obiektu.
  2. Wykonać implementację regulatora rozmytego i uruchomić regulator w trybie symulacji.
  3. Dostroić parametry regulatora, tak aby uzyskać jakość regulacji porównywalną z jakością regulatora otrzymanego w projekcie 1 (po optymalizacji).
  4. Ocenić jakość działania układu regulacji.
  5. Porównać otrzymane rezultaty z tymi z projektu 1 i skomentować wyniki.

3. Założenia

  • Optymalizację wykonać dla punkt pracy:  PV_{PP}=0.2 [m] . Proszę zwrócić uwagę na niesymetryczne właściwości obiektu - inną dynamikę napełniania i opróżniania.
  • Ocenę jakości działania układu regulacji wykonać dla zmian wokół punktu pracy:  \Delta SP=±0.05 [m] oraz dla skrajnie innych punktów pracy (bardzo małych bardzo dużych). W tym celu należy przygotować odpowiedni scenariusz testowy. Zakładamy, że układ regulacji działa z okresem próbkowania  t_p^{cont}=0.2 [s] . Z takim okresem zbierane będą dane pomiarowe.
  • Do oceny wykorzystywać te same wskaźniki jakości co w projekcie 1>.

4. Uwagi

  • Regulator rozmyty musi działać w pętli sprzężenia zwrotnego, w każdym kroku symulacji musi wypracować odpowiednie sterowanie. Przygotowany symulator modeluje działanie obiektu (procesu) jak i układu regulacji.
  • Struktura wewnętrzna symulatora składa się ze ścieżek przetwarzania (Path), które z kolei zbudowane są z poszczególnych bloków funkcyjnych (pochodne klasy Block). Uproszona wersja struktury, konieczna do zrozumienia jak zaimplementować i uruchomić nowy blok funkcyjny, została przedstawiona na rysunku poniżej. Szczególnym rodzajem bloku funkcyjnego jest podsystem (Subsystem), który zbudowany jest z określonych składowych bloków funkcyjnych. Poszczególne podsystemy zdefiniowane są w postaci odrębnych klas w bibliotece szablonów (templates.py).

    Uproszczony schemat struktury wewnętrznej symulatora
    Uproszczony schemat struktury wewnętrznej symulatora
  • Przykładem utworzenia podsystemu użytkownika jest szablon regulatora PID – klasa PID (w templates.py). Uproszczony listing klasy pokazano poniżej. Wszystkie elementy składowe regulatora PID i ich konfiguracja zawarte są w konstruktorze __init__().

    Listing: Definicja klasy PID (skrót)
    
    """
        Implementation of classical PID controller.
    """
    class PID(Subsystem):
        """
            @param _name unique (in parent object) block name
            @param _inputs required inputs ([Value, ...]): SP [m], PV [m], mode, cv_man
            @param _tp sampling time
        """
        def __init__(self, _name, _inputs, _tp):
            super().__init__(_name, _inputs)
    
            # Define processing structure in the form of interconnected subblocks
            # - add function block
            sp_filt = self.add_block( FirstOrderInertia('sp_filt', _tp, 1, 0.1) )
            # - add block input and connect to subsystem input
            sp_filt.add_input(self.input(0))
    	...
            # Define subsystem outputs (add and connect to block output): CV [%], e
            self.add_output(cv.output(0))
            self.add_output(e.output(0))
                

    Użytkownik odpowiedzialny jest za:

    • dodanie poszczególnych bloków składowych,
    • podłączenie wejść bloków do wejść podsystemu lub innych bloków,
    • zdefiniowanie niezbędnych wyjść podsystemu.

    Ważne jest aby nowy regulator rozmyty miał te same wyjścia (i w tej samej kolejności) co regulator klasyczny PID (czyli CV i e, dodatkowo dodano wyjście de) gdyż ma go zastąpić, a inne bloki korzystają z wyjścia regulatora.

  • Przykładem zastosowania ścieżki jest ścieżka modułu sterowania (controller_sim) zdefiniowana w symulatorze. Ze względu na podłączenie kolejnych bloków do wyjścia regulatora zastosowano tą samą nazwę bloku regulatora rozmytego co regulatora PID.

    Listing: Fragment konstruktora TtsPidSim.__init__() związany z definiowaniem podsystemu sterowania
    
    # Controller path
    #   working with tpcont = tpsim * pr.control (default=0.2 [s])
    controller_sim = Path('controller_sim', self.pr['control'])
    self.paths['controller_sim'] = controller_sim
    
    #   PV measurement. Block also used to make control feedback.
    pv_noise = controller_sim.add_block( Random('pv_noise', 0, 0.0013333333333) )
    pv_measure = controller_sim.add_block( SumDiff('pv_measure', [1, 1]) )
    pv_measure.add_input(pv_noise.output(0))
    
    controller = controller_sim.add_block( PID('controller', [sp.output(0), pv_measure.output(0), cvman.output(0)], self.tp*self.pr['control']) )
    #   add ins/outs: controller parameters
    self.add_in_var('kp', controller.blocks['kp'].const)
    self.add_in_var('Ti', controller.blocks['ti'].const)
    self.add_in_var('Td', controller.blocks['td'].const)
    self.add_in_var('Bias', controller.blocks['bias'].const)
    self.add_in_var('Ienable', controller.blocks['i_enable'].const)
    self.add_in_var('Denable', controller.blocks['d_enable'].const)
    self.add_in_var('PIDmode', controller.blocks['mode'].const)
    
    #   - monitoring variables: CV, PV, e
    self.add_out_var('CV', controller.output(0))
    self.add_out_var('PV', pv_measure.output(0))
    self.add_out_var('e', controller.output(1))
                
  • Dodanie nowej struktury przetwarzania, takiej jak regulator rozmyty, można zrealizować poprzez wykorzystanie jednej lub kilku z następujących możliwości:

    • dodanie nowego bloku funkcyjnego – wygodne rozwiązanie do realizacji określonych cyklicznych obliczeń na zestawie sygnałów wejściowych i wyznaczających zestaw sygnałów wyjściowych, np. implementacja modelu rozmytego o 2 wejściach i jednym wyjściu,
    • dodanie nowej ścieżki – wygodne rozwiązanie do realizacji złożonego przetwarzania składającego się z kilku bloków funkcyjnych (dostępnych w symulatorze lub dodanych przez użytkownika),
    • modyfikacja metody simulation_step() symulatora - nie jest to zalecane podejście,
    • realizacja przetwarzania poza symulatorem, w tym przypadku symulator działa bez regulatora, a wymiana danych następuje w każdym kroku poprzez zdefiniowane wejścia/wyjścia symulatora - nie jest to zalecane podejście.

5. Przykłady

W przykładzie wykorzystano bibliotekę Fuzzy Logic do realizacji prostego rozmytego systemu wnioskującego. Kod zapisano w notebooku Jupyter-a project_2.ipynb.

Testy prowadzone będą przy założeniu zmian  \Delta SP=±0.05 [m] wokół ustalonego punktu pracy  SP=0.2 [m] , okres symulacji  t_{end}=2700 [s] .

Załóżmy, że chcemy zaprojektować regulator rozmyty o następującej strukturze:

  • wygnały wejściowe: odchyłka  e , pochodna odchyłki  \Delta e ,
  • dla każdego wejścia zdefiniujemy dwa zbiory rozmyte o liniowych funkcjach przynależności typu Z i S położonych symetrycznie względem osi y,

    Wykorzystany kształt funkcji przynależności na wejściach modelu rozmytego
    Wykorzystany kształt funkcji przynależności na wejściach modelu rozmytego
  • dla wyjścia  \Delta CV zdefiniowano trzy singletony położone symetrycznie względem osi y,

    Wykorzystany kształt funkcji przynależności na wejściach modelu rozmytego
    Wykorzystany kształt funkcji przynależności na wejściach modelu rozmytego
  • baza wiedzy systemu wnioskującego:

    Reguły wnioskowania regulatora rozmytego
    Reguła
    R1:  (e=N \wedge \Delta e=N) \Rightarrow (\Delta CV=N)
    R1:  (e=N \wedge \Delta e=P) \Rightarrow (\Delta CV=Z)
    R1:  (e=P \wedge \Delta e=N) \Rightarrow (\Delta CV=Z)
    R1:  (e=P \wedge \Delta e=P) \Rightarrow (\Delta CV=P)
  • wartość wyjściowa z modelu rozmytego  |Delta CV podawana jest na blok całkujący w celu wyznaczenia wartości  CV .

Założono następujące zmienności sygnałów wejściowych i wyjścia:

 e=: ke=0.05

 \Delta e=:kde=0.0005

 \Delta CV=:kdCV=\frac{10}{t_p^{cont}}

Implementację modelu rozmytego w bibliotece Fuzzy Logic pokazano na poniższym listingu. W implementacji wykorzystano skalowanie przedziałów zmienności we/wy do przedziału   przy wykorzystaniu współczynników  ke ,  kde i  kdCV . Ze względu na właściwości biblioteki singletony przybliżono bardzo stromą funkcją trójkątną.

Listing: Implementacja modelu rozmytego w bibliotece Fuzzy Logic

from fuzzylogic.classes import Domain, Set, Rule
from fuzzylogic.functions import R, S, triangular
import matplotlib.pyplot as plt

# Precyzja obliczeń
prec = 0.001

# Definicja zmiennej rozmyte e
e_dom = Domain("e", -1, 1, prec)
e_dom.N = S(-1,1)
e_dom.P = R(-1,1)
plt.figure()
e_dom.N.plot()
e_dom.P.plot()
plt.title('Zmienna lingwistyczna e')

# Definicja zmiennej rozmyte de
de_dom = Domain("de", -1, 1, prec)
de_dom.N = S(-1,1)
de_dom.P = R(-1,1)
plt.figure()
de_dom.N.plot()
de_dom.P.plot()
plt.title('Zmienna lingwistyczna de')

# Definicja zmiennej rozmyte dCV
dcv_dom = Domain("dCV", -1-prec, 1+prec, prec)
dcv_dom.N = triangular(-1-prec, -1+prec)
dcv_dom.Z = triangular(-prec, prec)
dcv_dom.P = triangular(1-prec, 1+prec)
plt.figure()
dcv_dom.N.plot()
dcv_dom.Z.plot()
dcv_dom.P.plot()
plt.title('Zmienna lingwistyczna dCV')

# Definicja reguł wnioskowania
R1 = Rule({(e_dom.N, de_dom.N): dcv_dom.N})
R2 = Rule({(e_dom.N, de_dom.P): dcv_dom.Z})
R3 = Rule({(e_dom.P, de_dom.N): dcv_dom.Z})
R4 = Rule({(e_dom.P, de_dom.P): dcv_dom.P})

# Definicja bazy wiedzy modelu rozmytego
rules = R1 | R2 | R3 | R4

# Przykład wnioskowania
values = {e_dom: -0.25, de_dom: -0.005}
print(R1(values), R2(values), R3(values), R4(values), "=>", rules(values))
    
Zamodelowane funkcje przynależności zmiennej lingwistycznej e
Zamodelowane funkcje przynależności zmiennej lingwistycznej  e
Zamodelowane funkcje przynależności zmiennej lingwistycznej de
Zamodelowane funkcje przynależności zmiennej lingwistycznej  \Delta e
Zamodelowane funkcje przynależności zmiennej lingwistycznej dCV
Zamodelowane funkcje przynależności zmiennej lingwistycznej  \Delta CV

Aby osadzić regulator rozmyty w symulatorze przygotowano dedykowany blok funkcyjny SimpleFuzzyController (fuzzy.py). Parametry modelu ke, kde i kdCV zdefiniowano jako sygnały wejściowe bloku, a nie jako parametry wewnętrzne. Wejścia te zostaną wykorzystane w kolejnym projekcie.

Listing: Implementacja modelu rozmytego w postaci dedykowanego bloku funkcyjnego

"""
    Prosty regulator rozmyty z dwoma wejściami: e, de
    i jednym wyjściem dCV.
"""
# Simple fuzzy kontroller with 2 inputs: e, de
# and 1 output: dCV

class SimpleFuzzyController(Block):
    def __init__(self, _name):
        super().__init__(_name)

        prec = 0.001
        self.e_dom = Domain("e", -1, 1, prec)
        self.e_dom.N = S(-1, 1)
        self.e_dom.P = R(-1, 1)

        self.de_dom = Domain("de", -1, 1, prec)
        self.de_dom.N = S(-1, 1)
        self.de_dom.P = R(-1, 1)

        self.dcv_dom = Domain("dCV", -1-prec, 1+prec, prec)
        self.dcv_dom.N = triangular(-1-prec, -1+prec)
        self.dcv_dom.Z = triangular(-prec, prec)
        self.dcv_dom.P = triangular(1-prec, 1+prec)

        R1 = Rule({(self.e_dom.N, self.de_dom.N): self.dcv_dom.N})
        R2 = Rule({(self.e_dom.N, self.de_dom.P): self.dcv_dom.Z})
        R3 = Rule({(self.e_dom.P, self.de_dom.N): self.dcv_dom.Z})
        R4 = Rule({(self.e_dom.P, self.de_dom.P): self.dcv_dom.P})
        self.rules = R1 | R2 | R3 | R4

        self.add_output()

    def calculate(self):
        ke = self.input_val(0)
        kde = self.input_val(1)
        kdcv = self.input_val(2)
        e = self.input_val(3)
        de = self.input_val(4)
        values = {self.e_dom: e/ke, self.de_dom: de/kde}
        self.output_val(0, self.rules(values)*kdcv )
    

W kolejnym kroku przygotowano podsystem sterowania rozmytego FuzzyControl, w którym wykorzystano przygotowany blok funkcyjny. Zabieg ten wykonano w celu uporządkowania kodu symulatora. Krok ten może zostać pominięty, wtedy elementy składowe tego podsystemu trzeba zdefiniować bezpośrednio w jednej ze ścieżek przetwarzania.

Listing: Implementacja sterowania rozmytego w postaci dedykowanego podsystemu

class FuzzyControl(Subsystem):
    """
        Konstruktor
        Należy dodać wejścia: SP [m], PV [m]

        @param _tp Czas próbkowania z jakim będzie działać regulator
    """
    def __init__(self, _name, _inputs, _tp):
        super().__init__(_name, _inputs)

        # Obliczenie e=SP-PV oraz de/dt
        e = self.add_block( SumDiff('e', [1, -1]) )
        e.add_input(self.input(0))
        e.add_input(self.input(1))

        de = self.add_block( RealDifferentiator('de', _tp, 1, 5) )
        de.add_input(e.output(0))

        # Bloki do przechowywania parametrów
        ke = self.add_block( Const('ke', 1) )
        kde = self.add_block( Const('kde', 0.01) )
        kdcv = self.add_block( Const('kdCV', 1) )

        # Blok regulatora fuzzy
        fcontroller = self.add_block( SimpleFuzzyController('fcontroller') )
        fcontroller.add_input(ke.output(0))
        fcontroller.add_input(kde.output(0))
        fcontroller.add_input(kdcv.output(0))
        fcontroller.add_input(e.output(0))
        fcontroller.add_input(de.output(0))

        # Całkowanie sygnału dCV
        cv = self.add_block( FirstOrderIntegrator('cv', _tp, 1, 1, [0, 100]) )
        cv.add_input(fcontroller.output(0))

        # Dodanie wyjść: CV [%], e [m]
        self.add_output(cv.output(0))
        self.add_output(e.output(0))
        self.add_output(de.output(0))
    

W ostatnim kroku należy podmienić w symulatorze (konstruktor klasy TtsPidSim) zawartość ścieżki przetwarzania controller z poleceń obsługujących regulator PID:


controller = controller_sim.add_block( PID('controller', [sp.output(0), pv_measure.output(0), cvman.output(0)], self.tp*self.pr['control']) )
#   add ins/outs: controller parameters
self.add_in_var('kp', controller.blocks['kp'].const)
self.add_in_var('Ti', controller.blocks['ti'].const)
self.add_in_var('Td', controller.blocks['td'].const)
self.add_in_var('Bias', controller.blocks['bias'].const)
self.add_in_var('Ienable', controller.blocks['i_enable'].const)
self.add_in_var('Denable', controller.blocks['d_enable'].const)
self.add_in_var('PIDmode', controller.blocks['mode'].const)

na obsługę regulatora rozmytego:


controller = controller_sim.add_block( FuzzyControl('controller', [sp.output(0), pv_measure.output(0)], self.tp*self.pr['control']) )
#   add ins/outs: controller parameters
self.add_in_var('ke', controller.blocks['ke'].const)
self.add_in_var('kde', controller.blocks['kde'].const)
self.add_in_var('kdCV', controller.blocks['kdCV'].const)

#   - monitoring variables: de
self.add_out_var('de', controller.output(2))

Poniżej pokazano przykładowe wyniki z działania regulatora rozmytego. Jak widać jakość regulacji pozostawia wiele do życzenia – zdecydowanie należy poprawić nastawy tego regulatora, a zapewne także zmienić jego strukturę.

Przykład działania regulatora rozmytego
Przykład działania regulatora rozmytego
Przykład działania regulatora rozmytego