Simulace dynamických systémů v jazyce Java

© Jiří Macur, 2006

4         Metody pro zkoumání chování dynamických systémů

4.1        Fázový portrét

Animace chování systému může být sice působivá, avšak v podstatě je bezcenná. Daleko více informací o systému nám poskytne fázový portrét – soustava trajektorií ve fázovém prostoru s různými počátečními podmínkami. Zobrazování fázového portrétu je ještě jednodušší než animace pohybu. Pracovní plocha portrétu je však dobrým kandidátem na komfortnější získání počátečních podmínek – pouhým kliknutím myší do roviny fázového prostoru. Předcházející program proto rozšíříme o následující vlastnosti:

          Zobrazovací oblasti budou dvě: pro fázový portrét a vlastní animaci (kterou ještě pro kontrolu ponecháme).

          Počáteční podmínky nebudeme nastavovat číselně, ale kliknutím myši v první oblasti.

          Z panelu vstup vyřadíme textová pole pro vstup počátečního stavu a naopak do něj vložíme vstup hodnoty tlumení C, o které rozšíříme náš model dynamického systému:

                                                                                                   (4.1)

          Vzhledem k tomu, že animace je poměrně náročný proces na zdroje počítače (jako každý grafický výstup), zařadíme do vstupního panelu příznak, kterým budeme moci vypnout zobrazování pohybu a zpožďovacího mechanizmu ve vlákně rotátoru. Fázový portrét se pak bude vykreslovat daleko rychleji.

          Navíc přidáme do panelu tlačítko pro vymazání obsahu fázového portrétu tak, abychom mohli vykreslit jiný portrét s jinými vlastnostmi.

Kvůli přehlednosti probereme jednotlivé třídy separátně a nebudeme používat mechanizmus dědění z dřívějších verzí našeho programu.

Program 3

Návrh implementace třídy Rotator

 

class Rotator extends Thread                   //dědí funkcionalitu třídy vlákna

{

       public double l=0.3, m=1;               //parametry dyn. systému

       public double fi,omega;                 //stavové proměnné

       public double fi0 = 3, omega0 = 0;      //počáteční hodnoty se mění z vnějšku

       public double tlumeni = 0;              //hodnota tlumení se mění z vnějšku

       public boolean zmena = false;           //indikátor změny počátečních podmínek

       public boolean konec = false;           //indikátor ukončení výpočtu

       public boolean animace = true;          //příznak zobrazení animace

       private double g = 9.81;                //grav. zrychlení

       private double h = 0.005;               //diskretizační krok

       public Anim anim = null;                //reference na zobrazovací třídy

       public Portret portret = null;

 

       public Rotator()                        //konstruktor

       {

             fi = fi0; omega = omega0;         //počáteční podmínky

       }


 

public void setState()                         //nastaví nespojitý nový stav

       {

             if (zmena)

             {

                    fi = fi0;

                    omega = omega0;

                    portret.moveState(fi,omega);      //přemístí pero v portrétu

                    zmena = false;

             }

       }

      

       public double f1(double x1,double x2)          //definice dyn. systému

       {

             return x2;                       

       }

      

       public double f2(double x1,double x2)

       {

             return -Math.sin(x1)*g/l-tlumeni*x2;

       }

            

       public void step()                             //výpočet nového stavu

       {

             double h2 = 0.5*h;

      

             double k11 = f1(fi,omega);              //algoritmus Runge-Kutta

             double k12 = f2(fi,omega);

             double k21 = f1(fi+h2*k11,omega+h2*k12);

             double k22 = f2(fi+h2*k11,omega+h2*k12);

             double k31 = f1(fi+h2*k21,omega+h2*k22);

             double k32 = f2(fi+h2*k21,omega+h2*k22);

             double k41 = f1(fi+h*k31,omega+h*k32);

             double k42 = f2(fi+h*k31,omega+h*k32);

      

             fi += h/6*(k11+2*k21+2*k31+k41);        //nový stav

             if (Math.abs(fi) >  Math.PI)            //mapovani cyklické proměnné

             {

                    fi = fi - 2*Math.PI*Math.abs(fi)/fi;

                    portret.moveState(fi,omega);      //přemístí pero v portrétu

             }

             omega += h/6*(k12+2*k22+2*k32+k42);

       }

      

       public void run()                              //tělo vlákna

       {

             while(!konec)

             {

                    setState();                       //změní stav v případě potřeby

                    step();                           //spojitá změna stavu

                    if (animace)                      //zobrazit animaci?

                    {

                           anim.showState(fi);        //animace pohybu

                           try

                           {

                                  Thread.sleep(5);    //zpomalení

                           } catch(InterruptedException e) {};

                    }

                    portret.showState(fi,omega);      //vykreslení portrétu

             }

       }

}                                                     //konec třídy Rotator

 

Komentář ke změnám proti původní verzi:

Objekt prezentuje svůj stav pomocí dvou vnějších tříd. Kromě třídy Anim dostává z řídicího appletu  odkaz na objekt další třídy Portret.

V metodě setState() provedeme kromě změny stavových proměnných ještě přesun "pera" na novou pozici ve fázovém portrétu. Vzhledem k nespojité změně stavu by se jinak ve fázovém portrétu vykreslily rušivé spojnice mezi posledním vypočteným stavem a novými počátečními podmínkami. Přesun nám zajistí metoda třídy pro vykreslení fázového portrétu (viz dále).

Složka f2() definiční funkce dynamického systému je rozšířena o tlumení.

V metodě step() pro výpočet nového stavu testujeme, zda se cyklická proměnná fi nedostala mimo interval < –p, p >. Pokud ano, provedeme její mapování na tento interval (odečteme/přičteme 2p). Tuto operaci jsme mohli provést i v předcházejícím případě (stav systému se při tomto mapování zřejmě nemění), avšak u animace pohybu větší úhly nevadily. V případě fázového portrétu bychom se však při rotačním pohybu rotátoru dostali mimo vykreslovanou oblast. Pří mapování musíme opět přemístit pero ve fázovém portrétu.

V metodě run() ošetříme možnost potlačení zobrazování animace pohybu. Příznak animace je nastavován zvenčí jiným objektem. Při vykreslování nového stavu ve fázovém portrétu použijeme jeho metodu portret.showState().

Návrh implementace třídy Portret

 

class Portret extends Canvas

{

       private int width,height;               //geometrie oblasti

       private int fi0,omega0;                 //střed oblasti v rastrových souřadnicích

       private double scaleFi,scaleOmega;      //měřítko

       private int x1,y1;                      //původní stav v rastrových souřadnicích

       private int x2,y2;                      //nový stav v rastrových souřadnicích

       private Color fg=Color.black;           //barva popředí

       private Color bg=Color.white;           //barva pozadí

       private Graphics g;                     //uložení grafického kontextu

       public Rotator rot;                     //reference na objekt rotátoru

 

       Portret(int height0, int width0)        //konstruktor

       {

             super();                          //použije funkcionalitu mateřské třídy

             width = width0; height = height0;

             fi0 = width/2;                    //výpočet měřítka a posunutí

             omega0 = height/2;

             scaleFi = (double)fi0/Math.PI;    //zobrazovaný interval: +/- pi

             scaleOmega = (double)omega0/20;   //zobrazovaný interval: +/- 20

             this.setSize(width,height);       //nastaví velikost komponenty plátna

             this.addMouseListener(new tempMouse()); //registrace posl. událostí myši

       }

 

       public void paint(Graphics g)           //ovladač překreslení

       {

             g.setColor(fg);

             setBackground(bg);

             g.drawString("Fázový portrét 1",10,15);

       }

 

       public int x(double fi)                 //transf. z úhlové souřadnice

       {                                       //do rastrového portrétu

             return (int)(fi0+scaleFi*fi);

       }

      

       public int y(double omega)              //transf. úhlové rychlosti

       {                                       //do rastrového portrétu

             return (int)(omega0+scaleOmega*omega);

       }

 

       public void showState(double fi,double omega)  //zobrazení stavu

       {

             g = this.getGraphics();           //získání grafického kontextu

             g.setColor(fg);                   //nastavení barvy

             x2 = x(fi); y2 = y(omega);        //transformace stavu do nových souřadnic

             g.drawLine(x1,y1,x2,y2);          //vykreslení trajektorie

             x1 = x2; y1 = y2;                 //zapamatování stavu pro příští zobrazení

       }

 

       public void moveState(double fi,double omega)  //zmena zobr. výchozího stavu při

       {                                              //nespojité změně

             x1 = x(fi); y1 = y(omega);

       }

 

       class tempMouse extends MouseAdapter    //vnitřní třída pro zprac. událostí myši

       {

             public void mousePressed(MouseEvent e)  //ovladač kliknutí v portrétu

             {

                    rot.fi0 = (double)(e.getX()-fi0)/scaleFi;      //zpětná tranformace

                    rot.omega0 = (double)(e.getY()-omega0)/scaleOmega;//z rastr. souř.

                    rot.zmena = true;                       //informuje rotátor o změně

             }

       }

}

 

Komentář k návrhu třídy Portret:

Třída je opět zděděna od třídy Canvas ("plátno"). Logicky se příliš neliší od již dříve vytvořené třídy Anim. V konstruktoru opět nastavíme velikost oblasti a vypočteme potřebné konstanty pro transformaci měřítka.

Novinkou je registrace posluchače obecných událostí generovaných myší pro nastavení nového počátečního stavu systému. Je zřejmé, že potřebné informace o zpětné transformaci z fázového portrétu na stavové proměnné rotátoru obsahuje třída Portret. Proto by měla obsahovat ovladač událostí "kliknutí myši" v portrétu, který však musí pracovat s instancí rotátoru. Mezi atributy třídy je tedy reference na objekt rotátoru, kterou naplní řídicí applet. Se schématem ovladače události uvedeného v předcházející kapitole však bohužel nevystačíme. Myš totiž generuje celé spektrum událostí, které by všechny měla zpracovat naše třída. Nás však zajímá jediný druh události – "kliknutí". K tomu, abychom mohli definovat pouze jednu ovládací metodu z celé škály možností, slouží v AWT speciální třída MouseAdapter. Naše třída Portret je však již zděděna od třídy Canvas a Java vícenásobnou dědičnost nepodporuje. Na druhé straně jiná třída obsahující ovladač zase nebude mít přístup k informacím o vlastnostech fázového portrétu. Elegantním řešením tohoto dilematu je zavedení vnitřní třídy v třídě Portret. V registraci posluchače události tedy vytvoříme instanci této vnitřní třídy tempMouse, v níž bude potřebný ovladač definován.

Metoda paint opět definuje obsah pozadí. Bude vyvolána automaticky systémem při potřebě obnovení obsahu plochy.

Metody x a y definují transformace stavových proměnných rotátoru do rastrových souřadnic portrétu.

Hlavní metodu showState použije rotátor pro prezentaci svého stavu ve fázovém portrétu. Vykreslujeme v ní úsečky mezi původním a novým stavem rotátoru. K uchování obou stavů slouží atributy třídy x1, y1 a x2, y2.

Metoda moveState slouží k "přemístění pera" ve fázovém portrétu, jinými slovy k definici nového výchozího stavu, z něhož bude kreslena úsečka do cílového vypočteného stavu.

Naposled je uvedena definice vnitřní třídy tempMouse odvozená od MouseAdapter. Ve třídě je redefinována jediná metoda mousePressed, která slouží jako ovladač události stisknutí tlačítka myši. Parametr metody (objekt třídy MouseEvent) je naplněn automaticky systémem a obsahuje informaci o souřadnicích grafického kurzoru v komponentě, která událost vyvolala. Souřadnice jsou dostupné pomocí metod getX a getY – než je však předáme rotátoru, musíme provést zpětnou transformaci z rastrových souřadnic portrétu na stavové proměnné rotátoru. Nakonec informujeme rotátor o změně počátečních podmínek.

Návrh implementace třídy Anim

V této třídě nedojde oproti dřívější verzi (program 2) k žádné změně.

Návrh implementace třídy Vstup

class Vstup extends Panel        

{

       public TextField tlumeni;               //třída obsahuje veřejné vstupní pole

       public Checkbox animace;                //zatrhávací pole

       private Button vymazat;                 //a jedno tlačítko

 

       Vstup(simul posluchac)                  //konstruktor dostane odkaz na posluchače

       {                                       //aby ho mohl zaregistrovat

             setLayout(new FlowLayout());      //definuje správce své plochy - implic.

 

             vymazat = new Button("Vymazat");  //vytvoří instanci tlačítka

             add(vymazat);

             vymazat.addActionListener(posluchac);   //registrace ovladače své události

 

             animace = new Checkbox("Animace",true); //vytvoří instanci zatr. pole

             add(animace);                           //zařadí ho do panelu

             animace.addItemListener(posluchac);     //registrace ovladače své události

 

             add(new Label("Tlumení:"));             //do plochy přidá popisný text

             tlumeni = new TextField("0",6);         //vytvoří instanci textového pole

             add(tlumeni);                           //zařadí ho do panelu

             tlumeni.addActionListener(posluchac);   //registrace ovladače své události

       }

}

Třída další komentář v podstatě nepotřebuje. Tlačítko slouží k vyvolání události, kterou smažeme fázový portrét. Proti dřívějšímu návrhu jsme svěřili generování události pro načtení textového pole přímo komponentě TextField. Událost vznikne při odeslání hodnoty klávesou [Enter]. Zatrhávací políčko (checkbox) generuje svůj typ události, zatímco ostatní prvky v panelu generují stejnou událost typu Action.

Návrh implementace třídy simul

 

public class simul extends Applet implements ActionListener,ItemListener

                                  //implementuje dvě rozhraní pro zpracování události

{

       private Vstup vstup;                    //obsahuje 4 podřízené třídy

       private Anim anim;

       private Portret portret;

       private Rotator rot;

      

       public void init()                      //inicializace appletu ("konstruktor")

       {

             setLayout(new BorderLayout());    //zavede správce plochy

             vstup = new Vstup(this);          //při vytv. panelu předá odkaz na sebe

             add(vstup,"North");               //zařadí panel do správy plochy nahoru

             anim = new Anim(300,300);         //vytvoří objekt pro zobrazení

             add(anim,"East");                 //zařadí do správy plochy vpravo

             anim.repaint();                   //vynutí překreslení

             portret = new Portret(300,300);   //vytvoření objektu fázového portrétu

             add(portret,"West");              //zařadí do správy plochy vlevo

             portret.repaint();                //vynutí překreslení

 

             rot = new Rotator();              //vytvoří objektu rotátoru

             rot.anim = this.anim;             //předá odkazy na zobrazovací objekty

             rot.portret = this.portret;

 

             portret.rot = this.rot;           //předá portrétu odkaz na rotátor

 

             rot.start();                      //spustí tělo vlákna rotátoru

       }


       public void stop()                      //konec appletu (obdoba destruktoru)

       {

             rot.konec = true;                 //konec vlákna rotátoru

             super.stop();              //dokončí destrukci mateřskou třídou

       }

      

       public void actionPerformed(ActionEvent e)     //obsluha události od panelu

       {

             if(e.getSource().equals(vstup.tlumeni)) //podle zdroje události

                                                      //vlož novou hodnotu tlumení

                    rot.tlumeni=(Double.valueOf(vstup.tlumeni.getText())).doubleValue();

             else

                    portret.repaint();                //nebo smaž portrét

       }

 

       public void itemStateChanged(ItemEvent e)      //při změně stavu "checkboxu"

       {

             rot.animace = vstup.animace.getState(); //podle zatržení pole

       }                                              //nastav příznak v rotátoru

}

Komentář změn v řídicím appletu simul:

Třída obsluhuje dva typy událostí Action a Item, musí proto implementovat dvě potřebná rozhraní.

V inicializační části třídy vytvoříme instance našich uvedených tříd a zařadíme je opět pomocí správce plochy na potřebná místa. Metoda repaint() našich zobrazovacích tříd je volána proto, aby se objekty správně zobrazily po zařazení do plochy.

Po vytvoření objektu rotátoru mu předáme odkaz na zobrazovací objekty a naopak objektu portrétu předáme odkaz na instanci rotátoru (portrét potřebuje pracovat s počátečními hodnotami stavu rotátoru). Potřebné atributy s referencemi v objektech musí být tedy veřejné.

V definici metod implementujících rozhraní pro zpracování událostí je jedna novinka: jak tlačítko, tak textové pole, generují události typu Action a protože je zachycuje stejný objekt, musí to pochopitelně dělat jednou společnou metodou actionPerformed. Abychom rozlišili, o jakou událost se vlastně jedná, použijeme metodu getSource objektu události ActionEvent, která poskytuje informaci o objektu, v němž k události došlo.

Ovladač události od zatrhávacího políčka reaguje na změnu jeho stavu a je triviální – podle stavu pole nastaví v rotátoru příznak pro animaci.



Obr. 4.1. Fázový portrét s animací bez přítomnosti tlumení - program 3

Spustit program 3

Úlohy:

          Vyzkoušejte funkcionalitu nového appletu.

          Ověřte si, že při vypnutí animace je rychlost simulace mnohem vyšší, což je dáno nejen tím, že jsme odstranili čekací stavy vlákna rotátoru, ale také vysokými nároky grafických výstupů.

          Při malé záporné hodnotě tlumení lze vidět, že pro počáteční hodnoty blízké nule je prakticky nemožné odhadnout, který výsledný stav (rotátor rotuje v kladném nebo záporném směru) nastane.

4.2        Poincarého mapa

Poincarého mapa představuje průnik trajektorie ve fázového prostoru s obecnou nadplochou, která je obvykle realizována nadrovinou s dimenzí o jednotku nižšší než je dimenze fázového prostoru. Trajektorie procházející touto nadplochou se redukuje na posloupnost bodů, jejíž teoretické vlastnosti zde zkoumat nebudeme, omezíme se pouze na její realizaci pro jednoduchý systém našeho rotátoru. Z vizualizace  vyplyne význam mapy Poincarého názorným způsobem automaticky.

Pro realizaci roviny řezu musíme nejprve náš systém rozšířit o další dimenzi. Zavedeme proto vnější harmonickou budicí sílu F(t) = A cosW t = A cosq(t), která působí na rotátor nezávisle na jeho stavu. Stav dynamického systému bude tedy určen navíc proměnnou fází vnější síly. Po zavedení bezrozměrných konstant lze model našeho systému snadno přepsat do tvaru

                                                                           (4.2)

kde koeficient q vyjadřuje tlumení, koeficient p amplitudu, W frekvenci vnější síly, přičemž q (fáze vnější síly) tvoří další složku stavového vektoru.

Vzhledem k tomu, že třetí stavová proměnná je opět cyklická, můžeme redukovat obor jejích hodnot na interval <0,2p>, neboť fyzikální stav systému se zřejmě nezmění, přičteme-li k fázi vnější síly plný úhel.

Navíc zvolíme podmínku pro fázi vnější síly q = 2(k)p  za realizaci Poincarého mapy. Jedná se v tomto případě jednak o rovinu řezu, ale vzhledem k lineární závislosti fáze na čase, také o periodické vzorkování trajektorie synchronizované s harmonickou vnější silou (stroboskop).

Při simulaci tedy ve chvíli, kdy fáze q dosáhne hodnotu 2p,  zobrazíme okamžitý stav ostatních proměnných j a w. Fázi pak začneme počítat opět od nuly.

Při simulaci již nebudeme zobrazovat v podstatě zbytečnou animaci pohybu, místo ní zobrazíme Poincarého mapu. Fázový portrét si opět zobrazíme, musíme mít však na paměti, že se jedná o projekci trojrozměrného portrétu do roviny q = 0.

Ve vstupním panelu budeme vkládat hodnoty parametrů systému, tj. hodnotu konstant p, q, W.

Počáteční podmínky budeme opět zadávat kliknutím myši ve fázovém portrétu, přičemž pro třetí stavovou proměnnou zvolíme vždy hodnotu q0 = 0.

Pro přehlednost uvedeme opět návrh implementace jednotlivých tříd nové verze programu


Program 4

Návrh implementace třídy Rotator

class Rotator extends Thread                   //dědí funkcionalitu třídy vlákna

{

       public double p=1, q=2, omg=0.666667;   //parametry dyn. systému

       public double fi,omega,theta;           //stavové proměnné

       public double fi0 = 0, omega0 = 0;      //počáteční hodnoty se mění z vnějšku

       public boolean zmena = false;           //indikátor změny počátečních podmínek

       public boolean konec = false;           //indikátor ukončení výpočtu

       public boolean trajekt = true;          //příznak zobrazování portrétu

       private double g = 9.81;                //grav. zrychlení

       private double h = 0.005;               //diskretizační krok

       public Mapa mapa = null;                //reference na zobrazovací třídy

       public Portret portret = null;

 

       public Rotator()                        //konstruktor

       {

             fi = fi0;                         //počáteční podmínky

             omega = omega0;

             theta = 0;

       }

 

       public void setState()                  //nastaví nespojitý nový stav

       {

             if (zmena)

             {

                    fi = fi0;

                    omega = omega0;

                    theta = 0;

                    portret.moveState(fi,omega);            //přemístí pero v portrétu

                    zmena = false;

             }

       }

      

       public double f1(double x1,double x2,double x3)       //definice dyn. systému

       {

             return x2;                       

       }

      

       public double f2(double x1,double x2,double x3)

       {

             return -Math.sin(x1)-x2/q+p*Math.cos(x3);

       }

 

       public double f3(double x1,double x2,double x3)

       {

             return omg;

       }

 

       public void step()                                    //výpočet nového stavu

       {

             double h2 = 0.5*h;

      

             double k11 = f1(fi,omega,theta);               //algoritmus Runge-Kutta

             double k12 = f2(fi,omega,theta);

             double k13 = f3(fi,omega,theta);

             double k21 = f1(fi+h2*k11,omega+h2*k12,theta+h2*k13);

             double k22 = f2(fi+h2*k11,omega+h2*k12,theta+h2*k13);

             double k23 = f3(fi+h2*k11,omega+h2*k12,theta+h2*k13);

             double k31 = f1(fi+h2*k21,omega+h2*k22,theta+h2*k23);

             double k32 = f2(fi+h2*k21,omega+h2*k22,theta+h2*k23);

             double k33 = f3(fi+h2*k21,omega+h2*k22,theta+h2*k23);

             double k41 = f1(fi+h*k31,omega+h*k32,theta+h*k33);

             double k42 = f2(fi+h*k31,omega+h*k32,theta+h*k33);

             double k43 = f3(fi+h*k31,omega+h*k32,theta+h*k33);

      

             fi += h/6*(k11+2*k21+2*k31+k41);               //nový stav

             omega += h/6*(k12+2*k22+2*k32+k42);

             theta += h/6*(k13+2*k23+2*k33+k43);

 

             if (Math.abs(fi) >  Math.PI)            //mapovani cyklické proměnné

             {

                    fi -= 2*Math.PI*Math.abs(fi)/fi;

                    portret.moveState(fi,omega);      //přemístí pero v portrétu

             }

 

             if (theta > 2*Math.PI)                  //mapování fáze

             {

                    theta -= 2*Math.PI;

                    mapa.showState(fi,omega);         //zobrazení bodu v mapě

             }

 

             if (trajekt)                            //zobrazit trajektorii?

                    portret.showState(fi,omega);      //vykreslení portrétu

       }

      

       public void run()                              //tělo vlákna

       {

             while(!konec)

             {

                    setState();                       //změní stav v případě potřeby

                    step();                           //spojitá změna stavu

             }

       }

}

Ve třídě rotátoru došlo zejména k rozšíření modelu systému na trojdimenzionální variantu. V metodě step() jsme použili rozšíření metody Runge-Kutta tak, aby byla souvislost mezi modelem a numerickou metodu jasně patrná.

Při redukci fáze vnější síly na interval <0,2p> vyneseme zároveň bod do Poincarého mapy.

Abychom měli prezentaci vývoje systému pohromadě, pokud je zapnuto zobrazení trajektorie, vykreslíme nový stav také již v metodě step().

Metoda run() se tedy poněkud zjednoduší: vlákno již nepozastavujeme a zobrazovací metody voláme uvnitř metody step().

Návrh implementace třídy Mapa

 

class Mapa extends Canvas

{

       public int width,height;                //geometrie oblasti

       public int fi0,omega0;                  //střed oblasti v rastrových souřadnicích

       public double scaleFi,scaleOmega;       //měřítko

       public int x1,y1;                       //stav rotátoru v rastrových souřadnicích

       public Color fg=Color.black;            //barva popředí

       public Color bg=Color.white;            //barva pozadí

       public Graphics g;                      //uložení grafického kontextu

 

       Mapa(int height0, int width0)           //konstruktor

       {

             super();                          //použije funkcionalitu mateřské třídy

             width = width0;

             height = height0;

             fi0 = width/2;                    //výpočet měřítka a posunutí

             omega0 = height/2;

             scaleFi = (double)fi0/Math.PI;    //zobrazovaný interval: +/- pi

             scaleOmega = (double)omega0/5;    //zobrazovaný interval: +/- 5

             this.setSize(width,height);       //nastaví velikost komponenty plátna

       }

 

       public void paint(Graphics g)           //ovladač překreslení

       {

             g.setColor(fg);

             setBackground(bg);

             g.drawString("Poincarého mapa 1",10,15);

       }

 

       public int x(double fi)                        //transf. z úhlové souřadnice

       {                                              //do rastrového portrétu

             return (int)(fi0+scaleFi*fi);

       }

      

       public int y(double omega)                     //transf. úhlové rychlosti

       {                                              //do rastrového portrétu

             return (int)(omega0+scaleOmega*omega);

       }

 

       public void showState(double fi,double omega)  //zobrazení stavu

       {

             g = this.getGraphics();                 //získání grafického kontextu

             g.setColor(fg);                         //nastavení barvy

             x1 = x(fi); y1 = y(omega);              //transformace do nových souřadnic

             g.drawLine(x1,y1,x1,y1);                //vykreslení bodu v mapě

       }

}

Je zřejmé, že třída se liší pouze málo od prezentační třídy Portret z příkladu 3. V této třídě však nezadáváme počáteční podmínky ani nepotřebujeme přesouvat pero (nevykreslují se žádné úsečky), proto je ještě jednodušší. V metodě showState() kvůli zobrazení bodu vykreslíme úsečku s nulovou délkou.

Následující třída Portret má mnoho společného s třídou Mapa. Kromě stejně velké oblasti s ní sdílí i transformační vztahy, konstruktor a většinu atributů. Je tedy výhodné třídu Portret odvodit od třídy Mapa. To je také důvod, proč všechny atributy ve třídě Mapa deklarujeme jako veřejné (soukromé atributy dědit nelze).

Návrh implementace třídy Portret

 

class Portret extends Mapa                     //dědíme od třídy Mapa

{

       private int x2,y2;                      //nový stav v rastrových souřadnicích

       public Rotator rot;                     //reference na objekt rotátoru

                                               //ostatní atributy zdědíme

       Portret(int height0, int width0)        //konstruktor

       {

             super(height0,width0);            //použije funkcionalitu mateřské třídy

             this.addMouseListener(new tempMouse()); //registruje posluchače událostí

       }

 

       public void paint(Graphics g)           //ovladač překreslení redefinujeme

       {

             g.setColor(fg);

             setBackground(bg);

             g.drawString("Fázový portrét 1",10,15);

       }

 

       public void showState(double fi,double omega)  //zobrazení stavu redefinujeme

       {

             g = this.getGraphics();           //získání grafického kontextu

             g.setColor(fg);                   //nastavení barvy

             x2 = x(fi); y2 = y(omega);        //transformace stavu do nových souřadnic

             g.drawLine(x1,y1,x2,y2);          //vykreslení trajektorie

             x1 = x2; y1 = y2;                 //zapamatování stavu pro příští zobrazení

       }

 

       public void moveState(double fi,double omega)  //přesun zobr. výchozího stavu při

       {                                              //nespojité změně poč. podmínek

             x1 = x(fi); y1 = y(omega);

       }


 

       class tempMouse extends MouseAdapter    //vnitřní třída pro zprac. událostí myši

       {

             public void mousePressed(MouseEvent e)  //ovladač kliknutí v portrétu

             {

                    rot.fi0 = (double)(e.getX()-fi0)/scaleFi;      //zpětná tranformace

                    rot.omega0 = (double)(e.getY()-omega0)/scaleOmega;    //z rast. souř.

                    rot.zmena = true;                              //informuje o změně

             }

       }

}

Definice třídy Portret se v podstatě neliší od příkladu 3. Uvádíme ji zde spíše z didaktických důvodů v podobě rozšíření zděděné třídy Mapa. Z metodického hlediska by bylo patrně vhodnější navrhnout jako předka obou tříd společnou abstraktní zobrazovací třídu.

Návrh implementace třídy Vstup

 

class Vstup extends Panel        

{

       public TextField p,q,omg;               //třída obsahuje veřejná vstupní pole

       public Checkbox trajekt;                //zatrhávací pole

       public Button vymazat,odeslat;          //a dvě tlačítka

 

       Vstup(simul posluchac)                  //konstruktor dostane odkaz na posluchače

       {                                       //aby ho mohl zaregistrovat

             setLayout(new FlowLayout());      //definuje správce své plochy - implic.

 

             vymazat = new Button("Vymazat");  //vytvoří instanci tlačítka

             add(vymazat);

             vymazat.addActionListener(posluchac);   //tlačítko si registruje ovladač

 

             trajekt = new Checkbox("Traj.",true);   //vytvoří instanci checkboxu

             add(trajekt);                           //zařadí ji do panelu

             trajekt.addItemListener(posluchac);     //prvek si registruje ovladač

 

             add(new Label("P:"));             //do plochy přidá popisný text

             p = new TextField("1",4);         //vytvoří instanci vstupního pole

             add(p);                           //s parametry impl. hodnota a délka pole

             add(new Label("Q:"));             //do plochy přidá popisný text

             q = new TextField("2",4);         //vytvoří instanci vstupního pole

             add(q);

             add(new Label("Omg:"));           //do plochy přidá popisný text

             omg = new TextField("0.66667",4); //vytvoří instanci vstupního pole

             add(omg);

 

             odeslat = new Button("Odeslat");        //vytvoří instanci tlačítka

             add(odeslat);

             odeslat.addActionListener(posluchac);   //tlačítko si registruje ovladač

       }

}

Třída Vstup nyní vytváří tři vstupní textová pole pro všechny parametry dynamického systému – v takovém případě je výhodnější načítat obsah všech polí pomocí jedné události generované tlačítkem [odeslat]. Zatrhávací pole [traj.] povoluje kromě zobrazení mapy také vykreslování trajektorie (resp. její projekce). Ověřte si, že potlačení trajektorií významně urychlí běh programu.

V našem návrhu se nevyskytují proti příkladu 3 již žádné nové programové konstrukce.

Návrh implementace třídy simul

 

public class simul extends Applet implements ActionListener,ItemListener

                                  //implementuje dvě rozhraní pro zpracování události

{

       private Vstup vstup;                    //obsahuje 4 podřízené třídy

       private Mapa mapa;

       private Portret portret;

       private Rotator rot;

      

       public void init()                      //inicializace appletu ("konstruktor")

       {

             setLayout(new BorderLayout());    //zavede správce plochy

             vstup = new Vstup(this);          //předá odkaz na sebe

             add(vstup,"North");               //zařadí panel do správy plochy nahoru

             mapa = new Mapa(300,300);         //vytvoří instanci zobrazovací třídy

             add(mapa,"East");                 //zařadí do správy plochy vpravo

             mapa.repaint();                   //vynutí překreslení

             portret = new Portret(300,300);   //instance zobraz. třídy pro fáz. portrét

             add(portret,"West");              //zařadí do správy plochy vlevo

             portret.repaint();

 

             rot = new Rotator();              //vytvoří instanci objektu rotátor

             rot.mapa = this.mapa;             //předá odkazy na zobrazovací třídy

             rot.portret = this.portret;

 

             portret.rot = this.rot;           //předá portrétu odkaz na rotátor

 

             rot.start();                      //spustí tělo vlákna rotátoru

       }

      

       public void stop()                      //konec appletu (obdoba destruktoru)

       {

             rot.konec = true;                 //konec vlákna rotátoru

             super.stop();                     //dokončí destrukci mateřskou třídou

       }

      

       public void actionPerformed(ActionEvent e)     //obsluha události od panelu

       {

             if(e.getSource().equals(vstup.odeslat))

             {

                    rot.p = (Double.valueOf(vstup.p.getText())).doubleValue();

                    rot.q = (Double.valueOf(vstup.q.getText())).doubleValue();

                    rot.omg = (Double.valueOf(vstup.omg.getText())).doubleValue();

             }

             else

             {

                    portret.repaint();

                    mapa.repaint();

             }

       }

 

       public void itemStateChanged(ItemEvent e)      //při změně checkboxu panelu

       {

             rot.trajekt = vstup.trajekt.getState(); //podle zatržení pole

       }                                              //nastav příznak v rotátoru

}

Proti příkladu 3 je v tomto návrhu rovněž jen málo změn. Roli předcházející třídy Anim hraje nová třída Mapa, v metodách reagujících na události z panelu vstup používáme kombinaci mechanizmů použitých v příkladu 2 a 3, tj. načtení hodnot více textových polí a rozlišení zdroje události od různých tlačítek.

Úlohy:

          Prověřte různé druhy chování systému – jednoduchý cyklus, násobné cykly, kvaziperiodické chování, chaotické chování, současný výskyt více druhů cyklů. Ponechejte přitom původní hodnotu parametrů q a W, měňte pouze amplitudu budicí síly.

          Experimentujte se změnou frekvence budicí síly, všímejte si chování systému v okolí rezonance (frekvence budicí síly se blíží k vlastní frekvenci systému).

          Experimentujte s přechodovými částmi trajektorií, všímejte si virtuální atraktory přitahující trajektorie, přičemž je výsledným stavem jiný atraktor. Všimněte si také vyjádření virtuálních atraktorů v Poincarého mapě.

          Všimněte si, že existují nepravděpodobné atraktory (ve smyslu malé oblasti přitažlivost), které se při malé změně parametrů rozpadají. Při opatrné změně parametrů lze sledovat hysterezní chování.

          Pokuste se modifikovat uvedený program tak, aby zaznamenával nejen body v mapě, ale i hustotu stavů, tj. odlišoval nepravými barvami počet průchodů trajektorie jednotlivými oblastmi v mapě. Při této příležitosti můžete také modifikovat metodu paint tak, aby se při překrytí mapy neztratila informace o již zaznamenaných bodech.


Obr. 4.2. Chaotická trajektorie a její Poincarého mapa v programu 4

Spustit program 4

Obsah