Simulace dynamických systémů v jazyce Java

© Jiří Macur, 2006

3          Simulace nelineárního dynamického systému

Při demonstraci chaotického chování dynamického systému se obvykle používá tzv. rotátor, což je rovinné kyvadlo s hmotností m v homogenním gravitačním poli g s nehmotným, avšak tuhým závěsem délky l. Pohyb kyvadla pak není omezen na malé kmity, ale může vykonávat i kruhový pohyb s proměnnou úhlovou rychlostí w. Rotátor pak může vykazovat chaotické chování v případě působení vnější vynucující síly. Nejprve však vytvoříme nástroje pro simulaci volného rotátoru.

            Stav volného rotátoru je plně určen dvěmi veličinami: úhlovou výchylkou f a úhlovou rychlostí w, přičemž platí:

                                                                                                (3.1)

Kanonický tvar dynamického systému je pak

                                                                                                (3.2)

Pro simulaci budeme používat objektový přístup v jazyce Java, pro grafický výstup použijeme nejjednodušší grafickou knihovnu AWT. Předpokládáme, že čtenář je obeznámen se základy objektového programování, všechny konstrukce budeme vytvářet s důrazem spíše na ilustrativnost než na efektivitu algoritmů tak, aby byl patrný přímočarý vztah mezi modelovaným dynamickým systémem a jeho objektovou implementací.

3.1        Objekt dynamického systému

Objektu rotátor přiřadíme následující atributy:

 

Typ atributu

Název

Význam

Parametry systému

hmotnost

l

délka závěsu

g

gravitační zrychlení

Stavové proměnné

fi

úhlová výchylka

omega 

úhlová rychlost

Pomocné proměnné pro vizualizaci pohybu

oldx, oldy

stav rotátoru v rast. souřadnicích

width, height

rozměry zobrazované plochy

x0,y0

souřadnice upevnění závěsu

fg, bg

barva popředí a pozadí

r, d

poloměr a průměr závaží rotátoru

 

3.2        První program pro simulaci systému

Jednoduchá implementace animovaného pohybu rotátoru pak může být následující:

Program 1

 

import java.applet.*;

import java.awt.*;

public class simul extends Applet

{     

       private double l = 0.3, m = 1;                 //parametry

       private double fi = 3, omega = 0;              //stavové proměnné

       private int oldx,oldy;                         //uložení stavu v rastru

       private int x0,y0;                             //souřadnice upevnění závěsu

       private int width,height;                      //rozměry zobraz. plochy

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

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

       private int r = 5, d = 10;                     //poloměr a průměr závaží

       private double g = 9.81;                       //gravitační zrychlení

       private double h = 0.005;                      //krok numerické metody

       private boolean konec;                         //příznak zastavení výpočtu

 

       public void init()                             //inicializace appletu

       {

             Dimension d = this.size();              //zjištění rozměru plochy

             width = d.width;                        //nastavení měřítka;

height = d.height;

             x0 = width/2; y0 = height/2;            //závěs do středu plochy

             setBackground(bg);

       konec = false;                          //povolení cyklu výpočtu

}

      

       public void stop()                             //zastaveni appletu

{

       konec = true;                           //zastaví výpočet

       super.stop();                           //metoda mateřské třídy

}

 

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

       {

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

             g.drawString("Animace rotátoru 1",10,15);

 

             while (!konec)                          //cyklus vlastního výpočtu

             {     

                    step();                           //novy stav rotátoru

                    show(g);                          //animace

                    try                               //zpomalení výpočtu

                    {

                           Thread.sleep(5);           //pozastav thread na x ms

                    } catch(InterruptedException e){};

             }     

}

 

       public double f1(double fi,double omega) //definice dyn. systému

       {

             return omega;                           //první složka vekt. funkce

       }

      

       public double f2(double fi,double omega)

       {

             return -Math.sin(fi)*g/l;               //druhá složka vekt. funkce

       }

                   

       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);        //nastavení nového stavu

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

       }

      

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

       {                                              //do kartézských rastru

             return (int)(x0+width*l*Math.sin(fi));

       }

      

       public int y(double fi)

       {

             return (int)(y0+height*l*Math.cos(fi));

       }

 

       public void show(Graphics g)                   //zobrazení stavu

       {     

             g.setColor(bg);                         //skryj původní stav

             g.drawOval(oldx-r,oldy-r,d,d);

             g.drawLine(x0,y0,oldx,oldy);

             oldx = x(fi); oldy = y(fi);             //zapamatuj si stav

             g.setColor(fg);                         //zobraz nový stav

             g.drawOval(oldx-r,oldy-r,d,d);

             g.drawLine(x0,y0,oldx,oldy);

       }

}            // konec appletu simul

Uvedený program je velmi jednoduchý, přesto k němu uvedeme několik poznámek:

Třída simul je odvozena od třídy applet, která obsahuje potřebnou funkcionalitu pro vložení programu do prostředí prohlížeče Internetu. Tato funkcionalita je zabezpečena zejména množinou metod, z nichž některé slouží jako ovladače událostí – tj. jsou volány automaticky systémovým prostředím podle událostí, k nimž v systému došlo. Typickým reprezentantem je metoda init(), která je automaticky vyvolána při inicializaci třídy (zastupuje konstruktor při vytvoření instance). Metodu paint(Graphics) vyvolá systém při potřebě obnovy obsahu okna (např. při jeho odkrytí). Parametr metody (objekt třídy Graphics) systém naplní hodnotami grafického kontextu, platnými pro okno, v němž se applet prezentuje. Metoda stop() je automaticky vyvolána při zastavení appletu (např. při zavření okna prohlížeče). Pro správnou činnost naší třídy bychom měli alespoň tyto metody mateřské třídy nově definovat.

V metodě init tedy zjistíme rozměry plochy appletu, kterou definuje implicitně prostředí (tj. prohlížeč) a nastavíme měřítko tak, aby se naše animace do vymezeného prostoru správně umístila. Dále zde nastavíme barvu pozadí. Nastavíme také logickou proměnnou konec, která určuje trvání výpočtu.

V metodě stop pouze zakážeme další pokračování výpočtu, který se odehrává v následující metodě paint. Pak pokračujeme v zastavování appletu tak, jak je definováno v jeho mateřské třídě.

V metodě paint vykreslíme popředí okna, tj. popisný text a spustíme cyklus výpočtu nového stavu a jeho animované vykreslování. Tento postup není, jak dále uvidíme, zcela správný, avšak pro první pokus postačí. Pro výpočet nového stavu je volána metoda step naší třídy simul a metoda show pro animované zobrazování. Na většině dnešních počítačů by však byl pohyb systému příliš rychlý, proto ho zpomalíme tak, že při každém průchodu cyklem "uspíme" applet na 5ms. Tento postup je výhodný z několika důvodů:


1.      animace bude probíhat stejně rychle na různě výkonných počítačích

2.      při uspání vždy dojde k předání řízení ve prospěch jiných procesů v počítači – bude tedy možné i nadále rozumně pracovat s jinými aplikacemi a zejména bude snadné náš applet ukončit.

Mechanizmus předání řízení Thread.sleep může vyvolat výjimku (jedná se o systémovou záležitost), kterou musíme ošetřit – uvedený způsob, kdy na výjimku nijak nereagujeme, sice není příliš doporučován, ale pro naše účely kvůli jednoduchosti postačí.

Metoda step obsahuje implementaci klasické metody Runge-Kutta – z původního stavu vypočítá nový stav dynamického systému. Ten je definován vektorovou funkcí (3.2), přičemž její složky jsou implementovány funkcemi f1 a f2.

Metoda show smaže zobrazení původního stavu a vykreslí náš dynamický systém v novém stavu. K tomu však potřebuje přístup ke grafickým zdrojům, což jí zajistí parametr třídy Graphics, který byl naplněn systémem při automatickém volání metody paint(Graphics). K vykreslení nového stavu je zapotřebí transformace z polárních do kartézských souřadnic, kterou provádějí funkce x a y. Protože je zapotřebí si při animaci zapamatovat starý stav (aby ho bylo možné smazat), uloží metoda show již transformované hodnoty pro vykreslení stavu v rastrových souřadnicích do atributů xold a yold tak, aby je měla rychle k dispozici a nemusela znovu provádět transformaci.

Poznámka: Mazání stavu překreslením jednoduchého obrázku rotátoru barvou pozadí je z hlediska rychlosti daleko efektivnější než celkové smazání celé zobrazované plochy.

Uvedený text uložíme do souboru simul.java a přeložíme do p-kódu příkazem

> javac simul.java

Při bezchybném překladu vytvoří překladač vytvoří ve stejném adresáři soubor s přeloženou třídou simul.class.

Applet je závislý na prostředí prohlížeče – velmi jednoduchá stránka v jazyce HTML, která při interpretaci prohlížečem applet vyvolá, může vypadat např. takto:

<HTML>

<HEAD>

<TITLE>Simulace</TITLE>

</HEAD>

<BODY bgcolor="gray">

<H1 align="center">Rotátor</H1>

<P align='center'>

<APPLET CODE='simul.class' width='300' height='300'></APPLET>

</P>

</BODY>

</HTML>

Text uložíme do souboru simul.htm do stejného adresáře, v němž je nachystán náš applet simul.class. Stránku s appletem lze vyvolat prohlížečem lokálně nebo ji umístit na server WWW – pak bude přístupná v Internetu.

Celkově je patrné, že program je velmi přímočarý a jednoduchý, provádí simulaci rychle a z hlediska očekávaného chování – nepříliš dobře. Rychle totiž zjistíme, že prohlížeč je činností appletu zcela zablokován, při překrytí animace se původní obsah pozadí neobnoví, při pokusu o obnovení obsahu okna může dojít dokonce k havárii prohlížeče a je třeba ho zastavit systémovými prostředky. Celý problém je v nesprávném použití metody paint pro řízení běhu appletu. Metoda pro nás sice spustí výpočet, ale už se nezastaví a události, které by metodu paint měly opět vyvolat, ji nemohou reentrantně spustit.



Obr. 3.1 První program simulující netlumený rotátor bez buzení

Spustit program 1

3.3        Použití nezávislých procesů a událostí

Náš program tedy upravíme tak, aby byl celý mechanizmus řízení procesů zjevnější, i když poněkud složitější. Budeme při tom vycházet z následujících požadavků, které nám jazyk Java umožňuje pohodlně splnit:

          nechť je objekt rotátoru co nejvíce izolován od prostředí tak, aby bylo možné spouštět a zastavovat paralelně s ním jiné procesy podle potřeby

          případné změny jeho atributů (nastavení nových počátečních podmínek) nechť provádí jiný objekt, který je schopen komunikovat s uživatelem

          jiný "vizualizační" objekt bude objektu rotátoru také poskytovat prostředky pro zobrazování stavu.

Pomocí správce plochy (layout manager) typu border rozdělíme oblast na dva nestejně velké díly. Do prvního (severního) vložíme panel s ovládacími prvky (textová pole pro nastavení počátečních podmínek dynamického systému), do druhého (centrálního) větší oblast pro vykreslování.

Poznámka: I v našem předcházejícím příkladě byl implicitně použit nejjednodušší správce plochy, který ji však nijak nedělil. Použití správce bylo zděděno z třídy applet.


 

3.3.1       Události

Ovládací komponenty umožní uživateli vložit do běžícího appletu nové výchozí hodnoty. V jazyce Java je interakce mezi programem a uživatelem (nebo jinými procesy) řešena obecně na základě událostí (events). Prvky, které události generují (tlačítka, textová pole apod.), si zároveň registrují své možné posluchače (objekty, které události zachytí a zpracují). Posluchači však musejí být na tuto činnost připraveni – implementují patřičné rozhraní pro zachycení události. Schematicky je celý mechanizmus následující:

Ke každé interaktivní komponentě je přiřazeno spektrum událostí, které může generovat (např. komponenta tlačítko – button, může generovat události Action, Focus, Mouse, …). Písmeno E v obrázku je pak třeba nahradit konkrétní událostí, s níž chceme pracovat. Pro událost Action (stisk tlačítka) například:

1.      registrujeme posluchače metodou addActionListener(<posluchač>)

2.      posluchač musí implementovat rozhraní ActionListener

3.      definuje tedy metodu actionPerformed(ActionEvent e)

Které události může prvek generovat, jak se nazývají potřebné metody (ovladače) pro zpracování těchto událostí, to vše nalezneme snadno v dokumentaci JDK.

Z následujícího programu jsou podrobnosti použití události zcela zřejmé.

3.3.2       Vlákna

Samotný objekt rotátoru umístíme do samostatné třídy, která bude mít na starosti pouze výpočet nových stavů rotátoru. Starost o jejich zobrazování, resp. nastavování nových počátečních stavů svěříme jiným třídám. Navíc třídu rotátoru umístíme do separátního procesního vlákna, což lze v prostředí Javy provést velmi snadno – stačí naši třídu učinit potomkem třídy Thread, která zajistí potřebnou funkcionalitu. Výhodou umístění rotátoru do procesu odlišného od celého appletu je možnost zvýšení priority, pozastavování nebo přednostního přidělení procesu jinému procesoru (pokud je přítomen). Hlavní metodou (tělem vlákna) je metoda run(), kterou musíme nově definovat. Obsah této metody je tvořen obvykle nějakým delším výpočtem, v našem případě sem umístíme cyklus výpočtu nových stavů rotátoru. Spuštění těla se neprovádí přímo, o spuštění se postará metoda start(), kterou zavoláme po vytvoření instance vlákna. Vlákno zanikne automaticky s koncem metody run(), který musíme ošetřit tak, abychom se vyvarovali dalšího běhu vlákna i v případě, že ostatní části programu již skončily.

Struktura našeho programu bude tvořena čtyřmi třídami:

Třída

Zděděna od

Popis činnosti

simul

Applet

Rozdělí plochu na dvě části, do první umístí panel Vstup s ovládacími prvky, do druhé zobrazovací třídu Anim. Vytvoří instanci třídy Rotator a nastartuje její vlákno. Zachycuje události z ovládacího prvku panelu a předává nová data do objektu třídy Rotator.

Vstup

Panel

Definuje v kontejneru panelu dvě vstupní pole pro nové počáteční hodnoty stavu rotátoru a tlačítko, které generuje událost. Ovladač události je ve třídě simul, která předává referenci na sebe při volání konstruktoru třídy Vstup. Ovladač po zachycení události přečte obsah vstupních polí v panelu a po konverzi je předá rotátoru.

Anim

Canvas

Vytváří univerzální kreslící plochu, v níž se prezentuje stav rotátoru. Hlavní metodou třídy je metoda show(double fi), kterou volá objekt rotátoru, po vypočtení nového stavu. Veškeré transformace zobrazení jsou umístěny v třídě Anim. Aby mohl rotátor třídu využívat, dostává po vytvoření odkaz na instanci třídy Anim.

Rotator

Thread

Definuje vlastní dynamický systém a simuluje jeho chování. V simulačním cyklu testuje proměnnou zmena, která slouží k synchronizaci s vnucenou změnou stavu z vnějšího prostředí. Cyklus simulace končí nastavením příznaku konec, což zajistí obdoba destruktoru appletu.

Z uvedené tabulky je patrné, že applet simul má určitou řídící roli – vytváří instance ostatních tříd, předává jim potřebná data, zachytává jejích události. Panel vstup soustřeďuje komponenty pro interakci s uživatelem, zatímco Anim se stará o zobrazování. Je třeba mít na zřeteli, že strukturální návrh aplikace by mohl vypadat i zcela odlišně a byl by rovněž funkční. V této oblasti neexistuje žádná předem stanovená metodika a vše závisí na preferencích programátora.

Poznámka: Z hlediska principů OOP si trochu ulehčujeme situaci, když přistupujeme z appletu přímo k proměnným jiných tříd. Nemá však smysl snažit se o objektový purismus – naše programy se nerozrostou do té míry, aby porušení zapouzdření stavu objektů mohlo vést ke zmatkům a přehlednost a rychlost výsledné aplikace je pro nás důležitější než objektová "čistota".

Náš vylepšený druhý program tedy může vypadat například takto:

Program 2

 

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

 

public class simul extends Applet implements ActionListener

//třída implementuje rozhraní zpracování události od tlač. v panelu vstup

{

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

       private Anim anim;

       private Rotator rot;

      

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

       {

             setLayout(new BorderLayout());    //zavedení správce plochy

             vstup = new Vstup(this);   //při vytvoření instance panelu   přidáme odkaz

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

             anim = new Anim(300,300);  //vytvoření instance zobrazovací třídy

             add(anim,"Center");        //zařazení do správy plochy doprostřed

             anim.repaint();            //vynucení překreslení po umístění

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

             rot.anim = this.anim;      //předání odkazu na zobrazovací třídu

             rot.start();               //spuštění těla vlákna rotátoru

       }

      

       public void stop()                //konec appletu ("destruktor")

       {

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

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

       }

      

       public void actionPerformed(ActionEvent e)

       //obsluha události od tlačítka panelu

       {

//do poč. hodnot rotátoru vlož načtené údaje

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

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

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

       }

}                                       //konec třídy simul

 

class Vstup extends Panel        

{

       public TextField fi0,omega0;      //třída obsahuje dvě veřejná vstupní pole

       private Button odeslat;           //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

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

             fi0 = new TextField("0",4);       //vytvoří instanci vstního pole

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

             add(new Label("Omega:"));

             omega0 = new TextField("0",4);

             add(omega0);

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

             add(odeslat);

//tlačítko si registruje ovladač své události

             odeslat.addActionListener(posluchac);  

       }

}                                       //konec třídy Vstup

 

class Anim extends Canvas               //třída Canvas ("plátno") se používá

{                                       //k zobrazení rastrové grafiky

       private int width,height;         //atributy geometrie oblasti

       private int x0,y0;                //bod závěsu rotátoru

       private double l=0.3;             //délka rotátoru

       private int oldx=0,oldy=0;        //zapamatovaný stav v rastrových souřadnicích

       private int r=5,d=10;             //poloměr a průměr závaží rotátoru

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

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

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

 

       Anim(int height0, int width0)     //konstruktor

       {

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

             width = width0;            //uložíme geometrii plochy

             height = height0;

             x0 = width/2; y0 = height/2;      //umístíme závěs do středu

             this.setSize(width,height);       //nastavíme požadované rozměry

       }

 

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

       {

             g.setColor(fg);

             setBackground(bg);

             g.drawString("Animace rotátoru 1",10,15);

       }

 


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

       {                                        //do kartézských ratrových

             return (int)(x0+width*l*Math.sin(fi));

       }

      

       public int y(double fi)

       {

             return (int)(y0+width*l*Math.cos(fi));

       }

 

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

       {

             g = this.getGraphics();

             g.setColor(bg);                   //skryj původní stav

             g.drawOval(oldx-r,oldy-r,d,d);

             g.drawLine(x0,y0,oldx,oldy);

             oldx=x(fi);oldy=y(fi);            //vypočítej reprezentaci nového stavu

             g.setColor(fg);                   //zobraz nový stav

             g.drawOval(oldx-r,oldy-r,d,d);

             g.drawLine(x0,y0,oldx,oldy);

       }

}                                              //konec třídy Anim

 

class Rotator extends Thread                   //dědíme 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

       public boolean zmena = false;           //indikátor změny z vnějšku

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

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

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

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

 

       public Rotator()                        //konstruktor

       {

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

       }

 

       public void setState()                  //nastavení nespojitého nového stavu

       {

             if (zmena)

             {

                    fi = fi0; omega = omega0;

                    zmena = false;

             }

       }

      

       public double f1(double x1,double x2)   //definice dynamického systému

       {

             return x2;                       

       }

      

       public double f2(double x1,double x2)

       {

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

       }

            

       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

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

       }


 

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

       {

             while(!konec)

             {

                    setState();                //je-li požadována změna, proveď ji

                    step();                    //nový stav

                    anim.showState(fi);        //zobrazení stavu

                    try                        //prodleva (předání řízení)

                    {

                           Thread.sleep(5);

                    } catch(InterruptedException e) {};

             }

       }

}                                              //konec třídy Rotator

                                 

Poznámky k programu 2:

          V sekci pro import balíků musíme zařadit java.awt.event.*

          Applet simul musí implementovat rozhraní ActionListener, aby mohl zpracovat událost od tlačítka. Deklarovaná implementace spočívá v definici metody actionPerformed(ActionEvent e), která bude po události automaticky vyvolána. Název metody a typ parametru události nalezneme v dokumentaci JDK.

          V ovladači události přečteme obsah textových polí jiné třídy – proto musí být odpovídající komponenty v panelu Vstup veřejné. Konverze řetězců, které tyto komponenty poskytují pomocí metody getText(), na potřebný typ double se může zdát poněkud složitá, je však často používaná. Ke konverzi použijeme statickou metodu valueOf(string) třídy Double, která však nevrací skalární hodnotu ale objekt. Z objektu pak vyjmeme potřebný atribut metodou doubleValue().

          Při předání nového stavu do objektu rotátor musíme zajistit správnou synchronizaci (rotátor běží nezávisle na procesu appletu). Proto nastavíme nové hodnoty stavu do veřejných atributů rotátoru a příznakem změny informujeme vlákno rotátoru, aby si při první vhodné příležitosti nastavilo nový počáteční stav objektu. K synchronizaci vláken existují v třídě Thread i lepší nástroje, avšak jejich použití v našem programu by bylo zbytečně robustní a komplikované.



Obr. 3.2. Výstup simulace pohybu rotátoru se zadáváním počátečních podmínek

Spustit program 2


Obsah